API key and OPA
Learn how to combine the API key and OPA extauth modules to perform multi-step authentication for incoming requests.
When a client sends an API key to authenticate with another service in the cluster, the gateway proxy can extract and validate the API key by using the API key extauth module. In addition, if the API key contains additional metadata, such as the user ID or email address, Gloo automatically extracts these metadata fields from the API key, and forwards them with the API key to the OPA module where additional validation checks can be performed.
In this guide, you can try out different API key and OPA configurations, such as:
- Successfully validate requests with additional API key metadata by using the OPA extauth module.
- Deny requests that do not provide additional metadata in the API key.
- Create an OPA rule to allow only certain API keys.
If you import or export resources across workspaces, your policies might not apply. For more information, see Import and export policies.
Before you begin
This guide assumes that you use the same names for components like clusters, workspaces, and namespaces as in the getting started. If you have different names, make sure to update the sample configuration files in this guide.
- Set up Gloo Mesh Gateway in a single cluster.
- Install Bookinfo and other sample apps.
Configure an HTTP listener on your gateway and set up basic routing for the sample apps.
Make sure that the external auth service is installed and running. If not, install the external auth service.
kubectl get pods -A -l app=ext-auth-service
Make sure that the
extAuthService.extAuth.apiKey.metadataToState=true
field is set on your external auth server. In Gloo Mesh Gateway version 2.6.0 and later, this setting ensures that Gloo automatically extracts metadata fields from the API key to forward to OPA for validation checks.Check the Helm releases in your cluster. Depending on your installation method, you either have only a main installation release (such as
gloo-platform
), or a main installation and a separate add-ons release (such asgloo-agent-addons
), in addition to your CRDs release.helm ls -A
Get your current installation values.
- If you have only one release for your installation, get those values. Note that your Helm release might have a different name.
helm get values gloo-platform -n gloo-mesh -o yaml > gloo-values.yaml open gloo-values.yaml
- If you have a separate add-ons release, get those values.
helm get values gloo-agent-addons -n gloo-mesh -o yaml > gloo-agent-addons.yaml open gloo-agent-addons.yaml
- If you have only one release for your installation, get those values. Note that your Helm release might have a different name.
Add the following settings to allow API key metadata to be sent to the OPA state.
extAuthService: enabled: true extAuth: apiKey: metadataToState: true
Upgrade your Helm release.
- If you have only one release for your installation, upgrade the
gloo-platform
release. Note that your Helm release might have a different name.helm upgrade gloo-platform gloo-platform/gloo-platform \ --namespace gloo-mesh \ -f gloo-values.yaml \ --version $GLOO_VERSION
- If you have a separate add-ons release, upgrade the
gloo-agent-addons
release.helm upgrade gloo-agent-addons gloo-platform/gloo-platform \ --namespace gloo-mesh \ -f gloo-agent-addons.yaml \ --version $GLOO_VERSION
- If you have only one release for your installation, upgrade the
Make sure that you have the following CLI tools, or something comparable:
base64
to encode strings.
Configure API key and OPA external auth
Create an extauth server that you use to enforce the extauth policies in this guide.
kubectl apply -f - <<EOF apiVersion: admin.gloo.solo.io/v2 kind: ExtAuthServer metadata: name: ext-auth-server namespace: bookinfo spec: destinationServer: port: number: 8083 ref: cluster: $CLUSTER_NAME name: ext-auth-service namespace: gloo-mesh EOF
Create a Kubernetes secret that stores your API key and additional metadata, such as the user ID and email address.
kubectl -n bookinfo create secret generic user-glooy \ --type extauth.solo.io/apikey \ --from-literal=user-id=user-id-glooy \ --from-literal=user-email=glooy@solo.io \ --from-literal=user-name=glooy \ --from-literal=api-key=N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy
Label the secret so that you can reference this secret in your extauth policy more easily.
kubectl -n bookinfo label secret user-glooy extauth=apikey
Create a configmap for an OPA rule that validates the user’s email address that you added earlier. The following examples verifies that the email address in the
user-email
field ends withsolo.io
.kubectl apply -f - <<EOF apiVersion: v1 kind: ConfigMap metadata: name: allow-api-key-from-trusted-email-domain namespace: bookinfo labels: team: infrastructure data: policy.rego: | package test default allow = false allow { endswith(input.state["api_key_data"]["Metadata"]["user-email"], "@solo.io") } EOF
Create an extauth policy that references both the secret that contains the API key and the configmap with your OPA rule. Note that Gloo Mesh Gateway automatically extracts the
user-email
field from the API key and passes it on to the OPA module for further validation.kubectl apply -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: ExtAuthPolicy metadata: name: ratings-apikey namespace: bookinfo spec: applyToRoutes: - route: labels: route: ratings config: server: name: ext-auth-server namespace: bookinfo cluster: $CLUSTER_NAME glooAuth: configs: - name: APIKey apiKeyAuth: headerName: api-key k8sSecretApikeyStorage: labelSelector: extauth: apikey - name: opa opaAuth: modules: - name: allow-api-key-from-trusted-email-domain namespace: bookinfo query: "data.test.allow == true" EOF
Send a request to the ratings app and pass the API key that you added to the Kubernetes secret.
- HTTP:
curl -vik --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
- HTTPS:
curl -vik --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
Example output:
< HTTP/2 200 HTTP/2 200 < content-type: application/json content-type: application/json < date: Tue, 28 Mar 2023 20:16:40 GMT date: Tue, 28 Mar 2023 20:16:40 GMT < x-envoy-upstream-service-time: 5 x-envoy-upstream-service-time: 5 < server: istio-envoy server: istio-envoy * Connection #0 to host www.example.com left intact {"id":1,"ratings":{"Reviewer1":5,"Reviewer2":4}}%
- HTTP:
Create another configmap and add an OPA rule that references metadata that does not exist in the API key.
kubectl apply -f - <<EOF apiVersion: v1 kind: ConfigMap metadata: name: allow-api-key-from-certain-cost-centers namespace: bookinfo labels: team: infrastructure data: policy.rego: | package test default allow = false allow { startswith(input.state["api_key_data"]["Metadata"]["user-cost-center"], "733") } EOF
Update the extauth policy to reference the new configmap.
kubectl apply -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: ExtAuthPolicy metadata: name: ratings-apikey namespace: bookinfo spec: applyToRoutes: - route: labels: route: ratings config: server: name: ext-auth-server namespace: bookinfo cluster: $CLUSTER_NAME glooAuth: configs: - name: APIKey apiKeyAuth: headerName: api-key k8sSecretApikeyStorage: labelSelector: extauth: apikey - name: opa opaAuth: modules: - name: allow-api-key-from-certain-cost-centers namespace: bookinfo query: "data.test.allow == true" EOF
Send another request to the ratings service. This time, the request is denied, because the new
user-cost-center
metadata field could not be extracted from the API key to be processed by the OPA module.- HTTP:
curl -vik --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
- HTTPS:
curl -vik --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
Example output:
* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)! < HTTP/2 403 HTTP/2 403 < date: Tue, 28 Mar 2023 20:51:24 GMT date: Tue, 28 Mar 2023 20:51:24 GMT < server: istio-envoy server: istio-envoy
- HTTP:
Add the
user-cost-center
field to the API key.kubectl -n bookinfo get secret user-glooy -o json | jq --arg cost_center "$(echo -n 73355 | base64)" '.data["user-cost-center"]=$cost_center' | kubectl apply -f -
Send another request to the ratings service. This time, the request succeeds as the
user-cost-center
can be extracted and forwarded to the OPA extauth module for further validation.- HTTP:
curl -vik --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
- HTTPS:
curl -vik --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
Example output:
* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)! < HTTP/2 200 HTTP/2 200 < content-type: application/json content-type: application/json < date: Tue, 28 Mar 2023 20:54:17 GMT date: Tue, 28 Mar 2023 20:54:17 GMT < x-envoy-upstream-service-time: 3 x-envoy-upstream-service-time: 3 < server: istio-envoy server: istio-envoy
- HTTP:
Create another configmap and add another OPA rule to verify the API key itself and deny requests that use a specific API key.
kubectl apply -f - <<EOF apiVersion: v1 kind: ConfigMap metadata: name: allow-api-key-from-allowlist namespace: bookinfo labels: team: infrastructure data: policy.rego: | package test default allow = false is_key_allowed = true { allowed_keys := { "N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy", "974b3a3f-0aa9-4a94-bfe7-3fd42942d5e3" } input.state["api_key_value"] == allowed_keys[_] } allow { # deny any keys listed in the allowed_keys array. not is_key_allowed } EOF
Update the extauth policy to use the new OPA rule.
kubectl apply -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: ExtAuthPolicy metadata: name: ratings-apikey namespace: bookinfo spec: applyToRoutes: - route: labels: route: ratings config: server: name: ext-auth-server namespace: bookinfo cluster: $CLUSTER_NAME glooAuth: configs: - name: APIKey apiKeyAuth: k8sSecretApikeyStorage: labelSelector: extauth: apikey - name: opa opaAuth: modules: - name: allow-api-key-from-allowlist namespace: bookinfo query: "data.test.allow == true" EOF
Send another request to the ratings app. This time, the request is denied, because the API key that is used in the curl request is part of the API keys that are not allowed to be forwarded to the ratings app.
- HTTP:
curl -vik --resolve www.example.com:80:${INGRESS_GW_IP} http://www.example.com:80/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
- HTTPS:
curl -vik --resolve www.example.com:443:${INGRESS_GW_IP} https://www.example.com:443/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
Example output:
* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)! < HTTP/2 403 HTTP/2 403 < date: Tue, 28 Mar 2023 20:54:45 GMT date: Tue, 28 Mar 2023 20:54:45 GMT < server: istio-envoy server: istio-envoy
- HTTP:
Cleanup
You can optionally remove the resources that you set up as part of this guide.
kubectl delete extauthpolicy ratings-apikey -n bookinfo
kubectl delete configmap allow-api-key-from-allowlist -n bookinfo
kubectl delete configmap allow-api-key-from-certain-cost-centers -n bookinfo
kubectl delete configmap allow-api-key-from-trusted-email-domain -n bookinfo
kubectl delete secret user-glooy -n bookinfo
Known limitations
When you have multiple Kubernetes secrets that share the same label, and you use labels to reference the Kubernetes secret, the extauth policy passes the API key and metadata information from the first Kubernetes secret that is found to the OPA module.