Linkerd’s server authorization is surprisingly about allowing traffic, not just restricting it.
Let’s watch it in action. Imagine a simple web service, webapp, running on Kubernetes. By default, any pod in the cluster can talk to webapp. We want to change that.
Here’s our webapp deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
replicas: 1
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
And here’s a Service for it:
apiVersion: v1
kind: Service
metadata:
name: webapp
spec:
selector:
app: webapp
ports:
- protocol: TCP
port: 80
targetPort: 80
Without any Linkerd authorization, we can curl our webapp service from any other pod:
# From a different pod (e.g., a busybox pod)
kubectl exec -it <some-other-pod-name> -- curl webapp:80
This will successfully return <h1>Hello from pod webapp-xyz...</h1>.
Now, let’s introduce Linkerd’s Server resource to control inbound traffic. A Server defines which other Linkerd-enabled workloads are allowed to connect to a specific workload’s port.
Here’s how we create a Server to allow only the frontend service to connect to our webapp:
apiVersion: linkerd.io/v1alpha1
kind: Server
metadata:
name: webapp-auth
namespace: default # The namespace where webapp is running
spec:
selector:
matchLabels:
app: webapp # Selects the webapp pod(s)
port: 80 # The port on webapp to secure
# The allow list: only workloads matching these selectors are permitted.
# If this list is empty, no traffic is allowed.
allow:
- namespace: default
selector:
app: frontend # Only allow 'frontend' pods
With this Server in place, and assuming our frontend service is also Linkerd-enabled and has the label app: frontend, traffic from frontend to webapp:80 will be allowed. However, traffic from any other Linkerd-enabled workload (or even un-meshed workloads if they’re trying to reach the meshed webapp) will be denied.
You’ll see a 503 Service Unavailable response when trying to curl webapp:80 from an unauthorized pod. This is because the Linkerd proxy on the webapp pod receives the request, checks it against the Server resource, finds no matching allow rule, and terminates the connection.
The Server resource operates at the destination workload. It inspects incoming requests and decides whether to permit them based on the identity of the source workload (as established by the client’s Linkerd proxy). This is a crucial distinction: authorization is enforced by the receiving side.
The selector in the Server resource targets the pods that will have their inbound traffic controlled. The allow list specifies which sources are permitted. Each entry in allow is a namespace and selector pair that identifies a group of source pods. If a request arrives and its source workload doesn’t match any of the allow entries, it’s rejected.
The most surprising part is how this interacts with TLS. Linkerd’s mTLS encrypts traffic between proxies. The Server authorization policy is applied after the mTLS handshake is successful, meaning it’s authorizing based on the identity of the client, which is securely established via mTLS. You can’t spoof an identity to bypass the policy.
Linkerd’s authorization policies are implemented by the inbound proxy of the destination workload. This means the decision to allow or deny a request is made by the proxy running alongside your application’s container, not by the client’s proxy.
Once you’ve successfully implemented server authorization, the next logical step is to explore fine-grained HTTP route authorization using AuthorizationPolicy resources.