API keys
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.
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
Follow the Get started guide to install Solo Enterprise for kgateway.
Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
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_ADDRESSkubectl 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)
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.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 EOFVerify that the secret is created. Note that the
data.api-keyvalue is base64 encoded.kubectl get secret infra-apikey -n httpbin -oyamlExample 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
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_KEYis the value that clients send in theapi-keyrequest header.HMAC_SHARED_SECRETis the secret key used to compute the digest and is stored in step 2.API_KEY_DIGESTis the computed digest value and is stored in step 3. In the AuthConfig,apiKeyAuth.hmac.sharedSecretRefpoints to the step 2 secret.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} EOFCreate the API key secret and store
API_KEY_DIGEST(notRAW_API_KEY) instringData.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} EOFVerify both secrets are created.
kubectl get secret infra-apikey apikey-hmac-secret -n httpbin
Option 3: SHA256 digest pair validation
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_AandCLIENT_SECRET_Bare the plaintext values that clients send in theclient_secretrequest header.CLIENT_SECRET_DIGEST_AandCLIENT_SECRET_DIGEST_Bare the base64-encoded raw SHA256 digests that you store instringData.api-key.CLIENT_ID_AandCLIENT_ID_Bare stored in the same Secrets and matched against theclient_idrequest header.
Create Kubernetes Secrets for each valid
client_idandclient_secretpair.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} EOFNote that
stringData.api-keymust store the base64-encoded raw SHA256 digest, not the hex output ofsha256sum.Verify that the Secrets are created.
kubectl get secret apikey-digest-client-a apikey-digest-client-b -n httpbinOptional: If multiple clients share the same
client_secret, you can store the same digest in multiple API key Secrets and use differentclient_idvalues in each Secret. Later in the AuthConfig,match.headersis 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.
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-keyfield 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 EOFIf you stored the HMAC digest in the
stringData.api-keyfield 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 settingapiKeyAuth.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 EOFNote that in HMAC mode, clients still send the raw API key in the
api-keyrequest 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-keyfield of your Kubernetes Secrets, use this AuthConfig. The following example digests the plaintextclient_secretrequest header and requires theclient_idrequest 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 EOFIn digest mode, clients still send the plaintext
client_secretandclient_idrequest headers. Solo Enterprise for kgateway computes the SHA256 digest of theheaderNamevalue and compares it to the storedapi-keyvalue. Eachmatch.headers[].nameentry must match a same-named key on the selected Secret, such asclient_id. If you use custom header names such asx-ibm-client-secretandx-ibm-client-id, set those names inheaderNameandmatch.headers, respectively, and store the same key names in the Secret data. Note thatapiKeyAuth.digestandapiKeyAuth.hmacare mutually exclusive, andmatchis only valid whendigestis configured.Note that the gateway proxy automatically translates all headers to lowercase. For example if you setheaderName: api-keytoheaderName: API-keyinstead, the gateway proxy still expects the API key to be sent in the lowercaseapi-keyrequest header. The same lowercase behavior applies to configured match header names such asclient_id.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.
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: 0Send 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-keyrequest 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: envoyIf you configured SHA256 digest pair validation, send a request with a mismatched
client_idandclient_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