API key and OPA
You can 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 service mesh, the sidecar 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, you can specify these fields in the headersFromMetadataEntry
section of your extauth policy. This way, Gloo Mesh can extract these metadata fields from the API key and add them as headers to the request. The headers and the API key are then forwarded 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.
To learn more about each module that is being used as part of this guide, see the API key and OPA guides.
If you import or export resources across workspaces, your policies might not apply. For more information, see Import and export policies.
Before you begin
- Complete the multicluster getting started guide to set up the following testing environment.
- Three clusters along with environment variables for the clusters and their Kubernetes contexts.
- The Gloo Platform CLI,
meshctl
, along with other CLI tools such askubectl
andistioctl
. - The Gloo management server in the management cluster, and the Gloo agents in the workload clusters.
- Istio installed in the workload clusters.
- A simple Gloo workspace setup.
- Install Bookinfo and other sample apps.
- Make sure that the external auth service is installed and running. If not, install the external auth service.
kubectl get pods --context ${REMOTE_CONTEXT1} -A -l app=ext-auth-service
- 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 --context $REMOTE_CONTEXT1 -f - <<EOF apiVersion: admin.gloo.solo.io/v2 kind: ExtAuthServer metadata: name: ext-auth-server namespace: bookinfo spec: destinationServer: port: number: 8083 ref: cluster: $REMOTE_CLUSTER1 name: ext-auth-service namespace: gloo-mesh-addons EOF
-
Create a Kubernetes secret that stores your API key and additional metadata, such as the user ID and email address.
kubectl -n bookinfo --context $REMOTE_CONTEXT1 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 --context $REMOTE_CONTEXT1 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
x-user-email
header ends withsolo.io
. Note that the OPA rules refers to thex-user-email
header and not the actualuser-email
field in the Kubernetes secret. The mapping of theuser-email
field to thex-user-email
request header is done when you create the extauth policy in the next step.kubectl apply --context $REMOTE_CONTEXT1 -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["x-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. In this example, you configure Gloo Mesh to extract the
user-email
field from the API key and add it as thex-user-email
header to the request so that it can be passed on to the OPA module for further validation.kubectl apply --context $REMOTE_CONTEXT1 -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: ExtAuthPolicy metadata: name: ratings-apikey namespace: bookinfo spec: applyToDestinations: - selector: labels: app: ratings config: server: name: ext-auth-server namespace: bookinfo cluster: $REMOTE_CLUSTER1 glooAuth: configs: - name: APIKey apiKeyAuth: headerName: api-key headersFromMetadataEntry: x-user-email: name: user-email required: true 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.
Create a temporary curl pod in the
bookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.- Create the curl pod.
kubectl run -it -n bookinfo --context $REMOTE_CONTEXT1 curl \ --image=curlimages/curl:7.73.0 --rm -- sh
- Send a request to the ratings app.
curl http://ratings:9080/ratings/1 -v -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
Use the
kubectl debug
command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v http://ratings:9080/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
If the output has an error about
EphemeralContainers
, see Ephemeral containers don’t work when testing Bookinfo.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}}%
- Create the curl pod.
-
Create another configmap and add an OPA rule that references metadata that does not exist in the API key.
kubectl apply --context $REMOTE_CONTEXT1 -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["x-user-cost-center"], "733") } EOF
-
Update the extauth policy to reference the new configmap.
kubectl apply --context $REMOTE_CONTEXT1 -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: ExtAuthPolicy metadata: name: ratings-apikey namespace: bookinfo spec: applyToDestinations: - selector: labels: app: ratings config: server: name: ext-auth-server namespace: bookinfo cluster: $REMOTE_CLUSTER1 glooAuth: configs: - name: APIKey apiKeyAuth: headerName: api-key headersFromMetadataEntry: x-user-email: name: user-email required: true 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
x-user-cost-center
metadata field could not be extracted from the API key to be processed by the OPA module.Create a temporary curl pod in the
bookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.- Create the curl pod.
kubectl run -it -n bookinfo --context $REMOTE_CONTEXT1 curl \ --image=curlimages/curl:7.73.0 --rm -- sh
- Send a request to the ratings app.
curl http://ratings:9080/ratings/1 -v -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
Use the
kubectl debug
command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v http://ratings:9080/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
If the output has an error about
EphemeralContainers
, see Ephemeral containers don’t work when testing Bookinfo.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
- Create the curl pod.
-
Add the
x-user-cost-center
field to the API key.-
Edit the Kubernetes secret that stores your API key.
kubectl edit secret user-glooy -n bookinfo --context $REMOTE_CONTEXT1
-
Add the
user-cost-center
value to thedata
section as shown in the following example. Note that theuser-cost-center
represents the base64-encoded value for 73355.... apiVersion: v1 data: api-key: TjJZd01ESXhaVEV0TkdVek5TMWpOemd6TFRSa1lqQXRZakUyWXpSa1pHVm1OamN5 user-cost-center: NzMzNTU= user-email: Z2xvb3lAc29sby5pbw== user-id: dXNlci1pZC1nbG9veQ== user-name: Z2xvb3k= ...
-
-
Update the extauth policy to add the
x-user-cost-center
to theheadersFromMetadataEntry
section so that the field can be extracted from the API key and passed to the OPA extauth module as a header.kubectl apply --context $REMOTE_CONTEXT1 -f - <<EOF apiVersion: security.policy.gloo.solo.io/v2 kind: ExtAuthPolicy metadata: name: ratings-apikey namespace: bookinfo spec: applyToDestinations: - selector: labels: app: ratings config: server: name: ext-auth-server namespace: bookinfo cluster: $REMOTE_CLUSTER1 glooAuth: configs: - name: APIKey apiKeyAuth: headerName: api-key headersFromMetadataEntry: x-user-email: name: user-email required: true x-user-cost-center: name: user-cost-center required: true 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 succeeds as the
x-user-cost-center
can be extracted and forwarded to the OPA extauth module for further validation.Create a temporary curl pod in the
bookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.- Create the curl pod.
kubectl run -it -n bookinfo --context $REMOTE_CONTEXT1 curl \ --image=curlimages/curl:7.73.0 --rm -- sh
- Send a request to the ratings app.
curl http://ratings:9080/ratings/1 -v -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
Use the
kubectl debug
command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v http://ratings:9080/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
If the output has an error about
EphemeralContainers
, see Ephemeral containers don’t work when testing Bookinfo.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
- Create the curl pod.
-
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 --context $REMOTE_CONTEXT1 -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 --context $REMOTE_CONTEXT1 -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: $REMOTE_CLUSTER1 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.
Create a temporary curl pod in the
bookinfo
namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.- Create the curl pod.
kubectl run -it -n bookinfo --context $REMOTE_CONTEXT1 curl \ --image=curlimages/curl:7.73.0 --rm -- sh
- Send a request to the ratings app.
curl http://ratings:9080/ratings/1 -v -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
Use the
kubectl debug
command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v http://ratings:9080/ratings/1 -i -H "api-key: N2YwMDIxZTEtNGUzNS1jNzgzLTRkYjAtYjE2YzRkZGVmNjcy"
If the output has an error about
EphemeralContainers
, see Ephemeral containers don’t work when testing Bookinfo.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
- Create the curl pod.
Cleanup
You can optionally remove the resources that you created as part of this guide.
kubectl delete extauthpolicy ratings-apikey -n bookinfo --context $REMOTE_CONTEXT1
kubectl delete configmap allow-api-key-from-allowlist -n bookinfo --context $REMOTE_CONTEXT1
kubectl delete configmap allow-api-key-from-certain-cost-centers -n bookinfo --context $REMOTE_CONTEXT1
kubectl delete configmap allow-api-key-from-trusted-email-domain -n bookinfo --context $REMOTE_CONTEXT1
kubectl delete secret user-glooy -n bookinfo --context $REMOTE_CONTEXT1
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.