Configuring Per-Route Authorization Policy

In addition to enforcing authorization at the service level, finer-grained authorization policies can also be configured for individual HTTP routes. In this example, we’ll use the Books demo app to demonstrate how to control which clients can access particular routes on a service.

This is an advanced example that demonstrates more complex policy configuration. For a basic introduction to Linkerd authorization policy, start with the Restricting Access to Services example. For more comprehensive documentation of the policy resources, see the Authorization policy reference.

Prerequisites

To use this guide, you’ll need to have Linkerd installed on your cluster, along with its Viz extension. Follow the Installing Linkerd Guide if you haven’t already done this.

Install the Books demo application

Inject and install the Books demo application:

  1. $ kubectl create ns booksapp && \
  2. curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/booksapp.yml \
  3. | linkerd inject - \
  4. | kubectl -n booksapp apply -f -

This command creates a namespace for the demo, downloads its Kubernetes resource manifest, injects Linkerd into the application, and uses kubectl to apply it to your cluster. The app comprises the Kubernetes deployments and services that run in the booksapp namespace.

Confirm that the Linkerd data plane was injected successfully:

  1. $ linkerd check -n booksapp --proxy -o short

You can take a quick look at all the components that were added to your cluster by running:

  1. $ kubectl -n booksapp get all

Once the rollout has completed successfully, you can access the app itself by port-forwarding webapp locally:

  1. $ kubectl -n booksapp port-forward svc/webapp 7000 &

Open http://localhost:7000/ in your browser to see the frontend.

Frontend
Frontend

Creating a Server resource

Both the books service and the webapp service in the demo application are clients of the authors service.

However, these services send different requests to the authors service. The books service should only send GET requests to the /authors/:id.json route, to get the author associated with a particular book. Meanwhile, the webapp service may also send DELETE and PUT requests to /authors, and POST requests to /authors.json, as it allows the user to create and delete authors.

Since the books service should never need to create or delete authors, we will create separate authorization policies for the webapp and books services, restricting which services can access individual routes of the authors service.

First, let’s run the linkerd viz authz command to list the authorization resources that currently exist for the authors deployment:

  1. $ linkerd viz authz -n booksapp deploy/authors
  2. ROUTE SERVER AUTHORIZATION UNAUTHORIZED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
  3. default default:all-unauthenticated default/all-unauthenticated 0.0rps 70.31% 8.1rps 1ms 43ms 49ms
  4. probe default:all-unauthenticated default/probe 0.0rps 100.00% 0.3rps 1ms 1ms 1ms

By default, the authors deployment uses the cluster’s default authorization policy, “all-unauthenticated”. In addition, a separate authorization is generated to allow liveness and readiness probes from the kubelet.

First, we’ll create a Server resource for the authors deployment’s service port. For details on Server resources, see here.

  1. kubectl apply -f - <<EOF
  2. ---
  3. apiVersion: policy.linkerd.io/v1beta1
  4. kind: Server
  5. metadata:
  6. name: authors-server
  7. namespace: booksapp
  8. spec:
  9. podSelector:
  10. matchLabels:
  11. app: authors
  12. project: booksapp
  13. port: service
  14. EOF

Now that we’ve defined a Server for the authors Deployment, we can run the linkerd viz authz command again, and see that all traffic to authors is currently unauthorized:

  1. $ linkerd viz authz -n booksapp deploy/authors
  2. ROUTE SERVER AUTHORIZATION UNAUTHORIZED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
  3. default authors-server 9.5rps 0.00% 0.0rps 0ms 0ms 0ms
  4. probe authors-server default/probe 0.0rps 100.00% 0.1rps 1ms 1ms 1ms
  5. default default:all-unauthenticated default/all-unauthenticated 0.0rps 100.00% 0.1rps 1ms 1ms 1ms
  6. probe default:all-unauthenticated default/probe 0.0rps 100.00% 0.2rps 1ms 1ms 1ms

Next, we’ll create per-route policy resources to authorize traffic to the authors deployment.

Creating per-route policy resources

The HTTPRoute resource is used to configure policy for individual HTTP routes, by defining how to match a request for a given route. We will now create HTTPRoute resources for the authors service.

Configuring Per-Route Authorization Policy - 图2

Note

Routes configured in service profiles are different from HTTPRoute resources. Service profile routes allow you to collect per-route metrics and configure client-side behavior such as retries and timeouts. HTTPRoute resources, on the other hand, can be the target of AuthorizationPolicies and allow you to specify per-route authorization.

First, let’s create an HTTPRoute that matches GET requests to the authors service’s API:

  1. kubectl apply -f - <<EOF
  2. ---
  3. apiVersion: policy.linkerd.io/v1beta1
  4. kind: HTTPRoute
  5. metadata:
  6. name: authors-get-route
  7. namespace: booksapp
  8. spec:
  9. parentRefs:
  10. - name: authors-server
  11. kind: Server
  12. group: policy.linkerd.io
  13. rules:
  14. - matches:
  15. - path:
  16. value: "/authors.json"
  17. method: GET
  18. - path:
  19. value: "/authors/"
  20. type: "PathPrefix"
  21. method: GET
  22. EOF

Configuring Per-Route Authorization Policy - 图3

Note

Two versions of the HTTPRoute resource may be used with Linkerd:

  • The upstream version provided by the Gateway API, with the gateway.networking.k8s.io API group
  • A Linkerd-specific CRD provided by Linkerd, with the policy.linkerd.io API group

The two HTTPRoute resource definitions are similar, but the Linkerd version implements experimental features not yet available with the upstream Gateway API resource definition. See the HTTPRoute reference documentation for details.

This will create an HTTPRoute targeting the authors-server Server resource we defined previously. The rules section defines a list of matches, which determine which requests match the HTTPRoute. Here, we ’ve defined a match rule that matches GET requests to the path /authors.json, and a second match rule that matches GET requests to paths starting with the path segment /authors.

Now that we’ve created a route, we can associate policy with that route. We’ll create an AuthorizationPolicy resource that defines policy for our HTTPRoute:

  1. kubectl apply -f - <<EOF
  2. ---
  3. apiVersion: policy.linkerd.io/v1alpha1
  4. kind: AuthorizationPolicy
  5. metadata:
  6. name: authors-get-policy
  7. namespace: booksapp
  8. spec:
  9. targetRef:
  10. group: policy.linkerd.io
  11. kind: HTTPRoute
  12. name: authors-get-route
  13. requiredAuthenticationRefs:
  14. - name: authors-get-authn
  15. kind: MeshTLSAuthentication
  16. group: policy.linkerd.io
  17. ---
  18. apiVersion: policy.linkerd.io/v1alpha1
  19. kind: MeshTLSAuthentication
  20. metadata:
  21. name: authors-get-authn
  22. namespace: booksapp
  23. spec:
  24. identities:
  25. - "books.booksapp.serviceaccount.identity.linkerd.cluster.local"
  26. - "webapp.booksapp.serviceaccount.identity.linkerd.cluster.local"
  27. EOF

This command creates an AuthorizationPolicy whose targetRef selects the authors-get-route HTTPRoute resource we just created. An AuthorizationPolicy resource can require a a variety of forms of authentication. In this case, we we’ve defined a MeshTLSAuthentication resource, named authors-get-authn, that requires the TLS identity of the client to match the ServiceAccount of either the books service or the webapp service.

Additionally, because we have created an HTTPRoute referencing the authors service, the default route for liveness and readiness probes will no longer be used, and the authors service will become unready.

Therefore, we must also create a HTTPRoute and AuthorizationPolicy so that probes from the Kubelet are still authorized:

  1. kubectl apply -f - <<EOF
  2. ---
  3. apiVersion: policy.linkerd.io/v1beta1
  4. kind: HTTPRoute
  5. metadata:
  6. name: authors-probe-route
  7. namespace: booksapp
  8. spec:
  9. parentRefs:
  10. - name: authors-server
  11. kind: Server
  12. group: policy.linkerd.io
  13. rules:
  14. - matches:
  15. - path:
  16. value: "/ping"
  17. method: GET
  18. ---
  19. apiVersion: policy.linkerd.io/v1alpha1
  20. kind: NetworkAuthentication
  21. metadata:
  22. name: authors-probe-authn
  23. namespace: booksapp
  24. spec:
  25. networks:
  26. - cidr: 0.0.0.0/0
  27. - cidr: ::/0
  28. ---
  29. apiVersion: policy.linkerd.io/v1alpha1
  30. kind: AuthorizationPolicy
  31. metadata:
  32. name: authors-probe-policy
  33. namespace: booksapp
  34. spec:
  35. targetRef:
  36. group: policy.linkerd.io
  37. kind: HTTPRoute
  38. name: authors-probe-route
  39. requiredAuthenticationRefs:
  40. - name: authors-probe-authn
  41. kind: NetworkAuthentication
  42. group: policy.linkerd.io
  43. EOF

Here, we use the NetworkAuthentication resource (rather than MeshTLSAuthentication) to authenticate only probes coming from the local network (0.0.0.0).

Running linkerd viz authz again, we can now see that our new policies exist:

  1. $ linkerd viz authz -n booksapp deploy/authors
  2. ROUTE SERVER AUTHORIZATION UNAUTHORIZED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
  3. authors-get-route authors-server authorizationpolicy/authors-get-policy 0.0rps 100.00% 0.1rps 2ms 2ms 2ms
  4. authors-probe-route authors-server authorizationpolicy/authors-probe-policy 0.0rps 100.00% 0.1rps 1ms 1ms 1ms
  5. default default:all-unauthenticated default/all-unauthenticated 0.0rps 100.00% 0.1rps 2ms 2ms 2ms
  6. probe default:all-unauthenticated default/probe 0.0rps 100.00% 0.2rps 1ms 1ms 1ms

And, if we open http://localhost:7000/ and interact with the frontend, the lists of authors and books are still displayed correctly.

Authorizing additional routes

However, if we try to create a new author or delete an existing author in the web UI, we may notice that something is amiss.

Attempting to delete an author results in a “not found” error in the web UI:

Not found
Not found

and similarly, adding a new author takes us to an error page.

This is because creating or deleting an author will send a PUT or DELETE request, respectively, from webapp to authors. The route we created to authorize GET requests does not match PUT or DELETE requests, so the authors proxy rejects those requests with a 404 error.

To resolve this, we’ll create an additional HTTPRoute resource that matches PUT, POST, and DELETE requests:

  1. kubectl apply -f - <<EOF
  2. ---
  3. apiVersion: policy.linkerd.io/v1beta1
  4. kind: HTTPRoute
  5. metadata:
  6. name: authors-modify-route
  7. namespace: booksapp
  8. spec:
  9. parentRefs:
  10. - name: authors-server
  11. kind: Server
  12. group: policy.linkerd.io
  13. rules:
  14. - matches:
  15. - path:
  16. value: "/authors/"
  17. type: "PathPrefix"
  18. method: DELETE
  19. - path:
  20. value: "/authors/"
  21. type: "PathPrefix"
  22. method: PUT
  23. - path:
  24. value: "/authors.json"
  25. method: POST
  26. EOF

What happens if we try to delete an author now? We still see a failure, but a different one:

Internal server error
Internal server error

This is because we have created a route matching DELETE, PUT, and POST requests, but we haven’t authorized requests to that route. Running the linkerd viz authz command again confirms this — note the unauthorized requests to authors-modify-route:

  1. $ linkerd viz authz -n booksapp deploy/authors
  2. ROUTE SERVER AUTHORIZATION UNAUTHORIZED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
  3. authors-get-route authors-server authorizationpolicy/authors-get-policy - - - - - -
  4. authors-modify-route authors-server 9.7rps 0.00% 0.0rps 0ms 0ms 0ms
  5. authors-probe-route authors-server authorizationpolicy/authors-probe-policy 0.0rps 100.00% 0.1rps 1ms 1ms 1ms
  6. default default:all-unauthenticated default/all-unauthenticated 0.0rps 100.00% 0.1rps 1ms 1ms 1ms
  7. probe default:all-unauthenticated default/probe 0.0rps 100.00% 0.2rps 1ms 1ms 1ms

Now, let’s create authorization and authentication policy resources to authorize this route:

  1. kubectl apply -f - <<EOF
  2. ---
  3. apiVersion: policy.linkerd.io/v1alpha1
  4. kind: AuthorizationPolicy
  5. metadata:
  6. name: authors-modify-policy
  7. namespace: booksapp
  8. spec:
  9. targetRef:
  10. group: policy.linkerd.io
  11. kind: HTTPRoute
  12. name: authors-modify-route
  13. requiredAuthenticationRefs:
  14. - name: authors-modify-authn
  15. kind: MeshTLSAuthentication
  16. group: policy.linkerd.io
  17. ---
  18. apiVersion: policy.linkerd.io/v1alpha1
  19. kind: MeshTLSAuthentication
  20. metadata:
  21. name: authors-modify-authn
  22. namespace: booksapp
  23. spec:
  24. identities:
  25. - "webapp.booksapp.serviceaccount.identity.linkerd.cluster.local"
  26. EOF

These configurations are very similar to the AuthorizationPolicy and MeshTLSAuthentication resources we created in the previous section. However, in this case, we only authenticate the webapp deployment’s ServiceAccount (and not the books deployment) to access this route.

Now, if we attempt to delete an author in the frontend once again, we can:

Author deleted
Author deleted

Similarly, we can now create a new author successfully, as well:

Author created
Author created

Running the linkerd viz authz command one last time, we now see that all traffic is authorized:

  1. $ linkerd viz authz -n booksapp deploy/authors
  2. ROUTE SERVER AUTHORIZATION UNAUTHORIZED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99
  3. authors-get-route authors-server authorizationpolicy/authors-get-policy 0.0rps 100.00% 0.1rps 0ms 0ms 0ms
  4. authors-modify-route authors-server authorizationpolicy/authors-modify-policy 0.0rps 100.00% 0.0rps 0ms 0ms 0ms
  5. authors-probe-route authors-server authorizationpolicy/authors-probe-policy 0.0rps 100.00% 0.1rps 1ms 1ms 1ms
  6. default default:all-unauthenticated default/all-unauthenticated 0.0rps 100.00% 0.1rps 1ms 1ms 1ms
  7. probe default:all-unauthenticated default/probe 0.0rps 100.00% 0.2rps 1ms 1ms 1ms

Next Steps

We’ve now covered the basics of configuring per-route authorization policies with Linkerd. For more practice, try creating additional policies to restrict access to the books service as well. Or, to learn more about Linkerd authorization policy in general, and the various configurations that are available, see the Policy reference docs.