Skip to content
You are viewing the latest documentation for Solo Enterprise for kgateway. To access the documentation for older versions, use the version switcher.

API keys

Page as Markdown

Authenticate requests by using an API key.

About API keys

API keys are secure, long-lived UUIDs that clients provide when they send a request to your service. You might use API keys in the following scenarios:

  • You know the set of users that need access to your service. These users do not change often, or you have automation that easily generates or deletes the API key when the users do change.
  • You want direct control over how the credentials are generated and expire.
When you use API keys, your services are only as secure as the API keys. Storing and rotating the API key securely is up to the user.

API keys in Solo Enterprise for kgateway

To secure your services with API keys, first provide Solo Enterprise for kgateway with your API keys in the form of Kubernetes secrets. Then in the external auth policy, you refer to the secrets in one of two ways.

  • Specify a label selector that matches the label of one or more API key secrets. Labels are the more flexible, scalable approach.
  • Refer to the name and namespace of each secret.

Solo Enterprise for kgateway matches a request to a route that is secured by the external auth policy. The request must have a valid API key in a header. You can configure the name of the expected header. If the header is missing, or the API key is invalid, Solo Enterprise for kgateway denies the request and returns a 401 response.

Internally, Solo Enterprise for kgateway maps API keys to user identities for all API keys in the system. The user identity for an API key is the name of the secret that has the API key. Solo Enterprise for kgateway adds the user identity to the request as a header, x-user-id by default. Solo Enterprise for kgateway can use this header in subsequent filters. Note that for security purposes, Solo Enterprise for kgateway sanitizes the header from the response before the response leaves the gateway proxy.

Before you begin

  1. Follow the Get started guide to install Solo Enterprise for kgateway.

  2. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.

  3. Get the external address of the gateway and save it in an environment variable.

    export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
    echo $INGRESS_GW_ADDRESS  
    kubectl port-forward deployment/http -n kgateway-system 8080:8080

Step 1: Set up your API keys

Store your API key data in Kubernetes secrets so that the external auth service can access them to check incoming requests.

You can configure API key auth in three modes:

Option 1: Raw API key (default behavior)

  1. From your API management tool such as Gloo Portal, generate an API key to use for your app’s domain. The examples in this guide use N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy.

  2. Create a Kubernetes secret to store your API key.

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: infra-apikey
      namespace: httpbin
      labels:
        team: infrastructure
    type: extauth.solo.io/apikey
    stringData:
      api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy
    EOF
  3. Verify that the secret is created. Note that the data.api-key value is base64 encoded.

    kubectl get secret infra-apikey -n httpbin -oyaml

    Example output:

    apiVersion: v1
    kind: Secret
    metadata:
      name: infra-apikey
      namespace: httpbin
      labels:
        team: infrastructure
    type: extauth.solo.io/apikey
    data:
      api-key: TjJZd01ESXhaVEV0TkdVek5TMWpOemd6TFRSa1lqQXRZakUyWXpSa1pHVm1OamN5

Option 2: HMAC digest

  1. Create a raw API key, a shared secret, and the HMAC digest value to store in Kubernetes.

    export RAW_API_KEY='N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy'
    export HMAC_SHARED_SECRET="$(openssl rand -base64 64 | tr -d '\n')"
    export API_KEY_DIGEST="$(printf '%s' "$RAW_API_KEY" | openssl dgst -sha256 -hmac "$HMAC_SHARED_SECRET" -binary | openssl base64 | tr -d '\n')"

    RAW_API_KEY is the value that clients send in the api-key request header. HMAC_SHARED_SECRET is the secret key used to compute the digest and is stored in step 2. API_KEY_DIGEST is the computed digest value and is stored in step 3. In the AuthConfig, apiKeyAuth.hmac.sharedSecretRef points to the step 2 secret.

  2. Create the shared-secret Kubernetes secret that stores HMAC_SHARED_SECRET.

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: apikey-hmac-secret
      namespace: httpbin
    type: extauth.solo.io/accountcredentials
    stringData:
      password: ${HMAC_SHARED_SECRET}
    EOF
  3. Create the API key secret and store API_KEY_DIGEST (not RAW_API_KEY) in stringData.api-key.

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: infra-apikey
      namespace: httpbin
      labels:
        team: infrastructure
    type: extauth.solo.io/apikey
    stringData:
      api-key: ${API_KEY_DIGEST}
    EOF
  4. Verify both secrets are created.

    kubectl get secret infra-apikey apikey-hmac-secret -n httpbin

Option 3: SHA256 digest pair validation

  1. Create client IDs, raw client secrets, and the SHA256 digests to store in Kubernetes.

    export CLIENT_ID_A='client-a'
    export CLIENT_SECRET_A='digest-client-secret-a'
    export CLIENT_SECRET_DIGEST_A="$(printf '%s' "$CLIENT_SECRET_A" | openssl dgst -sha256 -binary | openssl base64 | tr -d '\n')"
    
    export CLIENT_ID_B='client-b'
    export CLIENT_SECRET_B='digest-client-secret-b'
    export CLIENT_SECRET_DIGEST_B="$(printf '%s' "$CLIENT_SECRET_B" | openssl dgst -sha256 -binary | openssl base64 | tr -d '\n')"
    • CLIENT_SECRET_A and CLIENT_SECRET_B are the plaintext values that clients send in the client_secret request header.
    • CLIENT_SECRET_DIGEST_A and CLIENT_SECRET_DIGEST_B are the base64-encoded raw SHA256 digests that you store in stringData.api-key.
    • CLIENT_ID_A and CLIENT_ID_B are stored in the same Secrets and matched against the client_id request header.
  2. Create Kubernetes Secrets for each valid client_id and client_secret pair.

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: apikey-digest-client-a
      namespace: httpbin
      labels:
        team: infrastructure
    type: extauth.solo.io/apikey
    stringData:
      api-key: ${CLIENT_SECRET_DIGEST_A}
      client_id: ${CLIENT_ID_A}
    ---
    apiVersion: v1
    kind: Secret
    metadata:
      name: apikey-digest-client-b
      namespace: httpbin
      labels:
        team: infrastructure
    type: extauth.solo.io/apikey
    stringData:
      api-key: ${CLIENT_SECRET_DIGEST_B}
      client_id: ${CLIENT_ID_B}
    EOF

    Note that stringData.api-key must store the base64-encoded raw SHA256 digest, not the hex output of sha256sum.

  3. Verify that the Secrets are created.

    kubectl get secret apikey-digest-client-a apikey-digest-client-b -n httpbin
  4. Optional: If multiple clients share the same client_secret, you can store the same digest in multiple API key Secrets and use different client_id values in each Secret. Later in the AuthConfig, match.headers is used to resolve the request to the correct Secret at runtime.

Step 2: Enforce API key authentication

Use AuthConfig and EnterpriseKgatewayTrafficPolicy resources to apply the auth rules to the routes that you want to secure.

  1. Create an AuthConfig resource with your API key authentication rules.

    If you created a raw API key and stored the API key in the stringData.api-key field of the Kubernetes secret, use this AuthConfig.

    kubectl apply -f- <<EOF
    apiVersion: extauth.solo.io/v1
    kind: AuthConfig
    metadata:
      name: apikey-auth
      namespace: httpbin
    spec:
      configs:
        - apiKeyAuth:
            # The request header name that holds the API key.
            # This field is optional and defaults to api-key if not present.
            headerName: api-key
            labelSelector:
              team: infrastructure
    EOF

    If you stored the HMAC digest in the stringData.api-key field of your Kubernetes secret, use this AuthConfig. The following example uses the same namespace as the HMAC shared-secret for simplicity, but you can also reference a shared secret in another namespace by setting apiKeyAuth.hmac.sharedSecretRef.namespace.

    kubectl apply -f- <<EOF
    apiVersion: extauth.solo.io/v1
    kind: AuthConfig
    metadata:
      name: apikey-auth
      namespace: httpbin
    spec:
      configs:
        - apiKeyAuth:
            # The request header name that holds the API key.
            # This field is optional and defaults to api-key if not present.
            headerName: api-key
            hmac:
              # defaults to HMAC_SHA256.
              algorithm: HMAC_SHA256
              sharedSecretRef:
                name: apikey-hmac-secret
                namespace: httpbin
            labelSelector:
              team: infrastructure
    EOF

    Note that in HMAC mode, clients still send the raw API key in the api-key request header. Solo Enterprise for kgateway computes the digest and compares it to the stored digest value.

    If you stored SHA256 digests of client secrets in the stringData.api-key field of your Kubernetes Secrets, use this AuthConfig. The following example digests the plaintext client_secret request header and requires the client_id request header to match the same Secret.

    kubectl apply -f- <<EOF
    apiVersion: extauth.solo.io/v1
    kind: AuthConfig
    metadata:
      name: apikey-auth
      namespace: httpbin
    spec:
      configs:
        - apiKeyAuth:
            # The request header name that holds the plaintext client secret.
            headerName: client_secret
            digest:
              # defaults to SHA256.
              algorithm: SHA256
            match:
              headers:
                - name: client_id
            labelSelector:
              team: infrastructure
    EOF

    In digest mode, clients still send the plaintext client_secret and client_id request headers. Solo Enterprise for kgateway computes the SHA256 digest of the headerName value and compares it to the stored api-key value. Each match.headers[].name entry must match a same-named key on the selected Secret, such as client_id. If you use custom header names such as x-ibm-client-secret and x-ibm-client-id, set those names in headerName and match.headers, respectively, and store the same key names in the Secret data. Note that apiKeyAuth.digest and apiKeyAuth.hmac are mutually exclusive, and match is only valid when digest is configured.

    Note that the gateway proxy automatically translates all headers to lowercase. For example if you set headerName: api-key to headerName: API-key instead, the gateway proxy still expects the API key to be sent in the lowercase api-key request header. The same lowercase behavior applies to configured match header names such as client_id.
  2. Create a EnterpriseKgatewayTrafficPolicy resource that refers to the AuthConfig that you created. The following policy applies external auth to all routes that the Gateway serves.

    kubectl apply -f - <<EOF
    apiVersion: enterprisekgateway.solo.io/v1alpha1
    kind: EnterpriseKgatewayTrafficPolicy
    metadata:
      name: test-extauth-policy
      namespace: kgateway-system
    spec:
      targetRefs:
        - name: http
          group: gateway.networking.k8s.io
          kind: Gateway
      entExtAuth:
        authConfigRef:
          name: apikey-auth
          namespace: httpbin
    EOF

Step 3: Verify that your routes are secured

Send various requests to verify that API key auth is enforced for your routes.

  1. Send a request to the httpbin app without any credentials. Verify that your request is denied and that you get back a 401 HTTP response code.

    curl -i http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com"
    curl -i localhost:8080/status/200 -H "host: www.example.com"

    Example output:

    * Mark bundle as not supporting multiuse
    < HTTP/1.1 401 Unauthorized
    < date: Fri, 19 Apr 2024 17:41:01 GMT
    < server: envoy
    < content-length: 0
  2. Send another request to the httpbin app with valid credentials for the mode that you configured. Verify that the request succeeds and that you get back a 200 HTTP response code.

    curl -i http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com" \
    -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"

    Port-forward for local testing:

    curl -i localhost:8080/status/200 -H "host: www.example.com" \
    -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"

    In HMAC mode, clients still send the raw API key in the api-key request header.

    LoadBalancer IP address or hostname:

    curl -i http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com" \
    -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"

    Port-forward for local testing:

    curl -i localhost:8080/status/200 -H "host: www.example.com" \
    -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"

    LoadBalancer IP address or hostname:

    curl -i http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com" \
    -H "client_id: client-a" \
    -H "client_secret: digest-client-secret-a"

    Port-forward for local testing:

    curl -i localhost:8080/status/200 -H "host: www.example.com" \
    -H "client_id: client-a" \
    -H "client_secret: digest-client-secret-a"

    Example output:

    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    < access-control-allow-credentials: true
    < access-control-allow-origin: *
    < date: Fri, 19 Apr 2024 17:44:06 GMT
    < content-length: 0
    < x-envoy-upstream-service-time: 0
    < server: envoy
  3. If you configured SHA256 digest pair validation, send a request with a mismatched client_id and client_secret. Verify that the request is denied and that you get back a 401 HTTP response code.

    curl -i http://$INGRESS_GW_ADDRESS:8080/status/200 -H "host: www.example.com" \
    -H "client_id: client-b" \
    -H "client_secret: digest-client-secret-a"
    curl -i localhost:8080/status/200 -H "host: www.example.com" \
    -H "client_id: client-b" \
    -H "client_secret: digest-client-secret-a"

    Example output:

    * Mark bundle as not supporting multiuse
    < HTTP/1.1 401 Unauthorized
    < date: Fri, 19 Apr 2024 17:41:01 GMT
    < server: envoy
    < content-length: 0

Cleanup

You can optionally remove the resources that you set up as part of this guide.
kubectl delete authconfig apikey-auth -n httpbin
kubectl delete EnterpriseKgatewayTrafficPolicy test-extauth-policy -n kgateway-system
kubectl delete secret infra-apikey -n httpbin
kubectl delete secret apikey-hmac-secret -n httpbin --ignore-not-found
kubectl delete secret apikey-digest-client-a apikey-digest-client-b -n httpbin --ignore-not-found
Was this page helpful?