Transport layer security and Tyk

Today, TLS, or transport layer security, is a modern pillar supporting the underlying communication between devices over the internet. If you’ve visited any current website, chances are you’re exchanging packets through the TLS protocol. 

In this blog, I’ll take you through the end-to-end process of generating a self-signed certificate and launching an instance of the Tyk Gateway to protect your data in flight with TLS! We will use host-based routing through Tyk and add TLS to an existing service!

Figure 1: Leveraging an API Gateway 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 encompassing *.localhost. It’s not possible to issue wildcard certificates for top-level domains referenced here. Thus, today, we will create a wildcard certificate for a subdomain of localhost, for example, *.tyk.localhost. 

First, we need to edit our /etc/hosts file to 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

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 let’s remedy that. 

Let’s begin generating a self-signed certificate for *.tyk.localhost. First, 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 the following command to create 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/[email protected]"
# 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/[email protected],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 for which we wish to have the certificate valid and the wildcard entry with DNS.5 = *.tyk.local. DNS.6 and DNS.7 are necessary since we will use 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 is adding 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 that we have our certificates, let’s use them to secure services in 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 containing all the Tyk stack components.

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

Let’s 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 licence

Edit the ./confs/tyk.env and ensure the environment variables are configured. These options enable SSL for the Tyk-Gateway, and instruct it 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 from which they can read. 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! Let’s take a breather and summarise what we’ve done so far:

  1. Generate a Root CA, as well as an associated Root Certificate
  2. Generate a local Certificate
  3. 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! We must create an API definition within Tyk reverse proxying to our service to do this!

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

}


And that’s a wrap! We’ve taken an existing service
and managed it through Tyk. Instead of httpbin.org, we can substitute it with a service running locally, either by IP address or a current public service hosted on a cloud provider such as AWS, GCP or Azure. Well done!