Published on

TLS and Tyk

Authors

TLS and Tyk

In present day, TLS or Transport Layer security is a modern pillar supporting the underlying communication between devices over the internet. If you've visted any modern website, chances are you're exchanging packets through the TLS protocol. Today, I'll be taking you through the end to end process of generating a self signed certificate, and launching a instance of the Tyk-Gateway to protect your data in-flight with TLS! We will be using host-based routing through Tyk and adding TLS to an existing service!

Team
Figure 1: Figure depicting how leveraging an API Gateway in front of your services in diagram (2) allows you to configure security such as TLS at the Gateway level to protect your services.

Prerequisites

This guide is written for OSX, however it is applicable for any linux/unix based distro assuming you have the following prerequisites installed:

  • docker-compose
  • openssl
  • git
  • vi

Suppose we are developing on localhost, and I want to generate a wildcard certificate that encompasses *.localhost. It's not possible to issue wildcard certificates for top level domains - reference here. Thus today we will be generating a wildcard certificate for a subdomain of localhost, say for example *.tyk.localhost.

First, we need to edit our /etc/hosts file such that we can use DNS resolution to map some hostnames to an IP address.

sudo vi /etc/hosts

Add the following lines to the file:

# Entries representing subdomain(s)
127.0.0.1 gateway.tyk.localhost
127.0.0.1 dashboard.tyk.localhost
127.0.0.1 httpbin.tyk.localhost

Now in our browser, when we navigate to https://dashboard.tyk.localhost our browser will know that this domain resolves to 127.0.0.1! You'll notice that we get an error ERR_CONNECTION_REFUSED. This is because we don't have a service running on this IP, so lets remedy that.

Lets begin the process of generating a self-signed certificate for *.tyk.localhost. Initially we need to generate our own SSL Certificate Authority. From ssl.com, a Certificate Authority is an organization that validates identities and binds them to cryptographic key pairs with digital certificates.

# Generate a 2048 bit RSA private key
openssl genrsa \
  -des3 \
  -out ./tykCA.key \
  -passout pass:topsecretpassword \
  2048

With our private key generated, we can use this following command to to generate a root certificate tykCA.pem. This root certificate allows us to sign subsequent certificates that our browser will trust.

# Generate a ROOT certificate
openssl req \
  -x509 \
  -new \
  -nodes \
  -key ./tykCA.key \
  -sha256 \
  -days 825 \
  -out ./tykCA.pem \
  -passin pass:topsecretpassword \
  -subj "/C=CA/ST=Ontario/L=London/O=Tyk/CN=tyk.local/emailAddress=jia@tyk.io"
# Generate a private key for our local SSL certificate
openssl genrsa \
  -out ./tyk.local.key \
  2048
# Generate SSL Certificate CSR
openssl req \
  -new \
  -key ./tyk.local.key \
  -out ./tyk.local.csr \
  -subj "/C=CA/ST=Ontario/L=London/O=Tyk/CN=tyk.local/emailAddress=jia@tyk.io,challengePassword=topsecretpassword"

From here we will populate a configuration extfile tyk.local.ext with the necessary DNS alt_names. We will explicitly list the DNS records we wish to have the certificate valid for, as well as the wildcard entry with DNS.5 = *.tyk.local. Entries DNS.6 and DNS.7 are necessary since we will be using Docker networking to communicate between containers. See the following for an example extfile:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
extendedKeyUsage=serverAuth,clientAuth
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = tyk.local
DNS.2 = dashboard.tyk.localhost
DNS.3 = gateway.tyk.localhost
DNS.4 = httpbin.tyk.localhost
DNS.5 = \*.tyk.local
DNS.6 = tyk-dashboard
DNS.7 = tyk-gateway

With a CA, CA private key, and a subsequent private key for our SSL certificate, we can now generate and sign a new SSL certificate using our root certificate:

# Ensure you obtained or created the tyk.local.ext file
openssl x509 \
  -req \
  -in ./tyk.local.csr \
  -CA ./tykCA.pem \
  -CAkey ./tykCA.key \
  -CAcreateserial \
  -out ./tyk.local.crt \
  -days 825 \
  -sha256 \
  -extfile ./tyk.local.ext \
  -passin pass:topsecretpassword

The final step now we have our SSL certificate is to add the Root CA tykCA.pem to our local trust store so that our operating system knows to trust the certificate we just generated.

# Add CA to Keychain
# You will be prompted for system password
sudo security add-trusted-cert \
  -d \
  -r trustRoot \
  -k /Library/Keychains/System.keychain \
  ./tykCA.pem

Now we have our certificates, lets actually use them to secure services on our local development environment! What better way to do this than an API Gateway? Tyk is a lightweight API Gateway that allows you to add features such as Authentication, Authorization, Access control, and much more to your services, so you can focus on building decoupled and scalable services. Let's pull in an example repository which contains all the components of the Tyk stack.

git clone https://github.com/TykTechnologies/tyk-pro-docker-demo.git

Lets navigate into the newly cloned repository and ensure we instruct Tyk to launch in TLS mode and supply the location(s) of the SSL certificates + private key we just generated. For documentation on TLS and SSL for Tyk, please find the documentation here.

cd tyk-pro-docker-demo

cp .env.example .env
# Ensure you populate the .env file with a dashboard license

Edit the ./confs/tyk.env and ensure the environment variables are configured. These options enable SSL for the Tyk-Gateway, as well as instruct the Gateway to look for the SSL certificate and corresponding private key in the /etc/certs/ directory.

TYK_GW_HTTPSERVEROPTIONS_USESSL=true
TYK_GW_HTTPSERVEROPTIONS_CERTIFICATES='[ { "cert_file": "/etc/certs/tyk.local.crt", "key_file": "/etc/certs/tyk.local.key", "domain_name": "*.tyk.localhost"} ]'
TYK_GW_POLICIES_POLICYCONNECTIONSTRING=https://tyk-dashboard:3000
TYK_GW_DBAPPCONFOPTIONS_CONNECTIONSTRING=https://tyk-dashboard:3000
TYK_GW_HTTPSERVEROPTIONS_SSLINSECURESKIPVERIFY=true

Perform the same for ./confs/tyk_analytics.env. This configuration enables SSL for the Tyk-Dashboard.

TYK_DB_TYKAPI_HOST=https://tyk-gateway
TYK_DB_HTTPSERVEROPTIONS_USESSL=true
TYK_DB_HTTPSERVEROPTIONS_CERTIFICATES='[ { "cert_file": "/etc/certs/tyk.local.crt", "key_file": "/etc/certs/tyk.local.key", "domain_name": "*.tyk.localhost"} ]'
TYK_DB_HTTPSERVEROPTIONS_SSLINSECURESKIPVERIFY=true

Assuming we are still in the tyk-pro-docker-demo directory, create a directory ./volumes/certs and copy over the SSL certificate tyk.local.crt and private key tyk.local.key. We will ultimately create a shared volume between our host machine and the Tyk-Gateway and Tyk-Dashboard containers where they will be able to read from. Here is a docker-compose.yml containing the respective volume mounts and an additional port mapping for a service we will secure with TLS.

docker-compose.yml
  version: '3.9'
  services:
    tyk-dashboard:
      image: tykio/tyk-dashboard:v4.3
      container_name: tyk-dashboard
      environment:
        - TYK_DB_LICENSEKEY=${TYK_DB_LICENSEKEY}
        - TYK_DB_STORAGE_MAIN_TYPE=postgres
        - TYK_DB_STORAGE_MAIN_CONNECTIONSTRING=user=postgres password=topsecretpassword host=tyk-postgres port=5432 database=tyk_analytics
      depends_on:
        tyk-postgres:
          condition: service_healthy
      ports:
        - "3000:3000"
      env_file:
        - ./confs/tyk_analytics.env
      networks:
        - tyk
      volumes:
        - ./volumes/certs:/etc/certs

    tyk-gateway:
      image: tykio/tyk-gateway:v4.3
      container_name: tyk-gateway
      ports:
        - "8080:8080"
        - "443:3100"
      env_file:
        - ./confs/tyk.env
      networks:
        - tyk
      volumes:
        - ./volumes/certs:/etc/certs

    tyk-pump:
      image: tykio/tyk-pump-docker-pub:v1.7
      container_name: tyk-pump
      env_file:
        - ./confs/pump.env
        - ./confs/pump.postgres.env
      depends_on:
        tyk-postgres:
          condition: service_healthy
      networks:
        - tyk

    tyk-redis:
      image: redis
      container_name: tyk-redis
      ports:
        - "6379:6379"
      volumes:
        - redis-data:/data
      networks:
        - tyk

    tyk-postgres:
      image: postgres:latest
      container_name: tyk-postgres

      environment:
        - POSTGRES_DB=tyk_analytics
        - POSTGRES_USER=postgres
        - POSTGRES_PASSWORD=topsecretpassword

      ports:
        - "5432:5432"

      volumes:
        - postgres-data:/var/lib/postgresql/data

      healthcheck:
        test: ["CMD-SHELL", "pg_isready -U postgres"]
        interval: 5s
        timeout: 5s
        retries: 5

      networks:
        - tyk

  volumes:
    redis-data:
    postgres-data:

  networks:
    tyk:

Now we can launch the Tyk stack with a simple one liner command:

docker-compose up

You can now visit https://dashboard.tyk.localhost to visit the Tyk-Dashboard in https mode! Lets take a breather and summarize what we've done so far:

i) Generate a Root CA, as well as an associated Root Certificate

ii) Generate a local Certificate

iii) Spin up the Tyk stack with TLS enabled

We have all the components configured so now we can serve application(s) through Tyk with HTTPS enabled! To do this, we simply need to create an API definition within Tyk reverse proxying to our service!

tyk-apidef.json
{
  "api_id": "685846816f8048da683f3dd6b2a4fbbc",
  "jwt_issued_at_validation_skew": 0,
  "upstream_certificates": {},
  "use_keyless": true,
  "enable_coprocess_auth": false,
  "base_identity_provided_by": "",
  "custom_middleware": {
    "pre": [],
    "post": [],
    "post_key_auth": [],
    "auth_check": {
      "name": "",
      "path": "",
      "require_session": false,
      "raw_body_only": false
    },
    "response": [],
    "driver": "",
    "id_extractor": {
      "extract_from": "",
      "extract_with": "",
      "extractor_config": {}
    }
  },
  "disable_quota": false,
  "custom_middleware_bundle": "",
  "cache_options": {
    "cache_timeout": 60,
    "enable_cache": true,
    "cache_all_safe_requests": false,
    "cache_response_codes": [],
    "enable_upstream_cache_control": false,
    "cache_control_ttl_header": "",
    "cache_by_headers": []
  },
  "enable_ip_blacklisting": false,
  "tag_headers": [],
  "jwt_scope_to_policy_mapping": {},
  "pinned_public_keys": {},
  "expire_analytics_after": 0,
  "external_oauth": {
    "enabled": false,
    "providers": []
  },
  "domain": "httpbin.tyk.localhost",
  "openid_options": {
    "providers": [],
    "segregate_by_client": false
  },
  "jwt_policy_field_name": "",
  "enable_proxy_protocol": false,
  "jwt_default_policies": [],
  "active": true,
  "jwt_expires_at_validation_skew": 0,
  "config_data": {},
  "notifications": {
    "shared_secret": "",
    "oauth_on_keychange_url": ""
  },
  "jwt_client_base_field": "",
  "auth": {
    "disable_header": false,
    "auth_header_name": "Authorization",
    "cookie_name": "",
    "name": "",
    "validate_signature": false,
    "use_param": false,
    "signature": {
      "algorithm": "",
      "header": "",
      "use_param": false,
      "param_name": "",
      "secret": "",
      "allowed_clock_skew": 0,
      "error_code": 0,
      "error_message": ""
    },
    "use_cookie": false,
    "param_name": "",
    "use_certificate": false
  },
  "check_host_against_uptime_tests": false,
  "auth_provider": {
    "name": "",
    "storage_engine": "",
    "meta": {}
  },
  "blacklisted_ips": [],
  "graphql": {
    "schema": "",
    "enabled": false,
    "engine": {
      "field_configs": [],
      "data_sources": []
    },
    "type_field_configurations": [],
    "execution_mode": "proxyOnly",
    "proxy": {
      "auth_headers": {}
    },
    "subgraph": {
      "sdl": ""
    },
    "supergraph": {
      "updated_at": "2023-02-16T15:06:49.746163Z",
      "subgraphs": [],
      "merged_sdl": "",
      "global_headers": {},
      "disable_query_batching": false
    },
    "version": "2",
    "playground": {
      "enabled": false,
      "path": ""
    }
  },
  "hmac_allowed_clock_skew": -1,
  "dont_set_quota_on_create": false,
  "uptime_tests": {
    "check_list": [],
    "config": {
      "expire_utime_after": 0,
      "service_discovery": {
        "use_discovery_service": false,
        "query_endpoint": "",
        "use_nested_query": false,
        "parent_data_path": "",
        "data_path": "",
        "cache_timeout": 60
      },
      "recheck_wait": 0
    }
  },
  "enable_jwt": false,
  "do_not_track": false,
  "name": "httpbin",
  "slug": "httpbin",
  "analytics_plugin": {},
  "oauth_meta": {
    "allowed_access_types": [],
    "allowed_authorize_types": [],
    "auth_login_redirect": ""
  },
  "CORS": {
    "enable": false,
    "max_age": 24,
    "allow_credentials": false,
    "exposed_headers": [],
    "allowed_headers": [
      "Origin",
      "Accept",
      "Content-Type",
      "X-Requested-With",
      "Authorization"
    ],
    "options_passthrough": false,
    "debug": false,
    "allowed_origins": [
      "*"
    ],
    "allowed_methods": [
      "GET",
      "POST",
      "HEAD"
    ]
  },
  "event_handlers": {
    "events": {}
  },
  "proxy": {
    "target_url": "http://httpbin.org",
    "service_discovery": {
      "endpoint_returns_list": false,
      "cache_timeout": 0,
      "parent_data_path": "",
      "query_endpoint": "",
      "use_discovery_service": false,
      "_sd_show_port_path": false,
      "target_path": "",
      "use_target_list": false,
      "use_nested_query": false,
      "data_path": "",
      "port_data_path": ""
    },
    "check_host_against_uptime_tests": false,
    "transport": {
      "ssl_insecure_skip_verify": false,
      "ssl_min_version": 0,
      "proxy_url": "",
      "ssl_ciphers": []
    },
    "target_list": [],
    "preserve_host_header": false,
    "strip_listen_path": true,
    "enable_load_balancing": false,
    "listen_path": "/",
    "disable_strip_slash": true
  },
  "client_certificates": [],
  "use_basic_auth": false,
  "version_data": {
    "not_versioned": true,
    "default_version": "",
    "versions": {
      "Default": {
        "name": "Default",
        "expires": "",
        "paths": {
          "ignored": [],
          "white_list": [],
          "black_list": []
        },
        "use_extended_paths": true,
        "extended_paths": {
          "ignored": [],
          "white_list": [],
          "black_list": [],
          "transform": [],
          "transform_response": [],
          "transform_jq": [],
          "transform_jq_response": [],
          "transform_headers": [],
          "transform_response_headers": [],
          "hard_timeouts": [],
          "circuit_breakers": [],
          "url_rewrites": [],
          "virtual": [],
          "size_limits": [],
          "method_transforms": [],
          "track_endpoints": [],
          "do_not_track_endpoints": [],
          "validate_json": [],
          "internal": [],
          "persist_graphql": []
        },
        "global_headers": {},
        "global_headers_remove": [],
        "global_response_headers": {},
        "global_response_headers_remove": [],
        "ignore_endpoint_case": false,
        "global_size_limit": 0,
        "override_target": ""
      }
    }
  },
  "jwt_scope_claim_name": "",
  "use_standard_auth": false,
  "session_lifetime": 0,
  "hmac_allowed_algorithms": [],
  "disable_rate_limit": false,
  "definition": {
    "enabled": false,
    "name": "",
    "default": "",
    "location": "header",
    "key": "x-api-version",
    "strip_path": false,
    "strip_versioning_data": false,
    "versions": {}
  },
  "use_oauth2": false,
  "jwt_source": "",
  "jwt_signing_method": "",
  "jwt_not_before_validation_skew": 0,
  "use_go_plugin_auth": false,
  "jwt_identity_base_field": "",
  "allowed_ips": [],
  "request_signing": {
    "is_enabled": false,
    "secret": "",
    "key_id": "",
    "algorithm": "",
    "header_list": [],
    "certificate_id": "",
    "signature_header": ""
  },
  "org_id": "63ed428e9b2f320001be54bd",
  "enable_ip_whitelisting": false,
  "global_rate_limit": {
    "rate": 0,
    "per": 0
  },
  "protocol": "https",
  "enable_context_vars": false,
  "tags": [],
  "basic_auth": {
    "disable_caching": false,
    "cache_ttl": 0,
    "extract_from_body": false,
    "body_user_regexp": "",
    "body_password_regexp": ""
  },
  "listen_port": 3100,
  "session_provider": {
    "name": "",
    "storage_engine": "",
    "meta": {}
  },
  "auth_configs": {
    "authToken": {
      "disable_header": false,
      "auth_header_name": "Authorization",
      "cookie_name": "",
      "name": "",
      "validate_signature": false,
      "use_param": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      },
      "use_cookie": false,
      "param_name": "",
      "use_certificate": false
    },
    "basic": {
      "disable_header": false,
      "auth_header_name": "Authorization",
      "cookie_name": "",
      "name": "",
      "validate_signature": false,
      "use_param": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      },
      "use_cookie": false,
      "param_name": "",
      "use_certificate": false
    },
    "coprocess": {
      "disable_header": false,
      "auth_header_name": "Authorization",
      "cookie_name": "",
      "name": "",
      "validate_signature": false,
      "use_param": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      },
      "use_cookie": false,
      "param_name": "",
      "use_certificate": false
    },
    "hmac": {
      "disable_header": false,
      "auth_header_name": "Authorization",
      "cookie_name": "",
      "name": "",
      "validate_signature": false,
      "use_param": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      },
      "use_cookie": false,
      "param_name": "",
      "use_certificate": false
    },
    "jwt": {
      "disable_header": false,
      "auth_header_name": "Authorization",
      "cookie_name": "",
      "name": "",
      "validate_signature": false,
      "use_param": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      },
      "use_cookie": false,
      "param_name": "",
      "use_certificate": false
    },
    "oauth": {
      "disable_header": false,
      "auth_header_name": "Authorization",
      "cookie_name": "",
      "name": "",
      "validate_signature": false,
      "use_param": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      },
      "use_cookie": false,
      "param_name": "",
      "use_certificate": false
    },
    "oidc": {
      "disable_header": false,
      "auth_header_name": "Authorization",
      "cookie_name": "",
      "name": "",
      "validate_signature": false,
      "use_param": false,
      "signature": {
        "algorithm": "",
        "header": "",
        "use_param": false,
        "param_name": "",
        "secret": "",
        "allowed_clock_skew": 0,
        "error_code": 0,
        "error_message": ""
      },
      "use_cookie": false,
      "param_name": "",
      "use_certificate": false
    }
  },
  "strip_auth_data": false,
  "id": "63ed42d69b2f320001be54bf",
  "certificates": [],
  "enable_signature_checking": false,
  "use_openid": false,
  "internal": false,
  "jwt_skip_kid": false,
  "enable_batch_request_support": false,
  "enable_detailed_recording": false,
  "scopes": {
    "jwt": {},
    "oidc": {}
  },
  "response_processors": [],
  "use_mutual_tls_auth": false
}

Well done! We've taken an existing service http://httpbin.org and now we've managed it through Tyk at https://httpbin.tyk.localhost. You can imagine that instead of httpbin.org, we can substitute it with a service running locally either by IP address or an existing public service that is hosted on a cloud provider such as AWS, GCP or Azure! Using an API Gateway in this manner allows for greater separation of concerns. I can focus on simply developing my services- while delegating the hard tasks of access control and security to the open source Tyk-Gateway, a product that has been battle hardened, tested and trusted by enterprises across the globe.