Banzai Cloud Logo Close
Home Products Benefits Blog Company Contact
Author Marton Sereg

Introduction to Istio access control

Istio claims that it helps to connect, secure, control and observe services. We’ve blogged a lot about connect, even more about observe, and also had a few articles about secure. But so far, we haven’t really touched control. This post tries to fill that gap, and discusses Istio’s access control model, or more specifically AuthorizationPolicies.

Architecture 🔗︎

Istio Authorization can be used to enforce access control rules between workloads. It basically answers the question: who can access what, under which specific conditions? Just like any other mesh configuration, authorization rules can be specified through Kubernetes CRDs. The API is quite simple, it consists of a single CRD, called AuthorizationPolicy, but more on the YAML details later. First, let’s see how are these rules enforced in Istio.

If you’re reading this article, you should already be familiar with Istio’s high level architecture, but here’s a (very) brief recap. Istio has a data plane, and a control plane. The data plane consists of sidecar proxies running alongside the application containers in the same pod, and they are responsible for forwarding all incoming, and outgoing traffic to the application. The control plane on the other hand is accepting user configuration through CRDs, and - among a few other things - transforms these CRDs to Envoy configuration and delivers it to the proxies. The sidecars are Envoy proxies, and the control plane is now basically a single service, called istiod.

Similarly to telemetry and traffic management, the real deal happens in the data plane. All checks are performed runtime by the Envoy proxy’s authorization engine. A request is evaluated against the authorization policies when it arrives to the proxy. Then Envoy returns the result, either ALLOW or DENY.

Authorizing requests in Istio

A brief history of Istio access control 🔗︎

For someone who’s just getting to know Istio, it can be confusing that they may bump into blog posts about Istio access control containing mentions of CRDs like ClusterRbacConfig, ServiceRole, ServiceRoleBinding. Those resources were part of the v1alpha1 API, that is now completely replaced by the v1beta1 API. The new API was introduced in Istio 1.4, and from Istio 1.6, the old API is not supported anymore. If you’re looking for a migration path, I’d recommend to read the official blog post.

The new model simplifies configuration (one CRD instead of three), supports ingress and egress gateways, and better aligns with the Istio configuration model, as it is applied to workloads instead of services.

Kubernetes NetworkPolicies 🔗︎

When talking about AuthorizationPolicies, we have to mention Kubernetes NetworkPolicies, because they are quite similar in terms of what problem they are trying to solve. The Kubernetes docs define network policies as follows:

A network policy is a specification of how groups of pods are allowed to communicate with each other and other network endpoints. For more details about network policies check out our blog post, Exploring Network Policies in Kubernetes.

There’s no easy answer to which one is better?, because they are good at different things. Istio policy enforcement works at the application layer (L7), - that’s where the Envoy proxies operate - while Kubernetes network policies work at the network (L3) and transport layers (L4). Kubernetes network policies are implemented by different networking solutions, like Calico. These solutions are running a controller that’s watching NetworkPolicies, and configures the underlying networking layer accordingly.

Operating at the application layer has its advantages. Because Envoy understands different protocols (most commonly HTTP), it allows for a rich set of attributes to base policy decisions on. A few examples are policies based on HTTP methods, URIs, or HTTP headers. A NetworkPolicy cannot do these, because these concepts are unknown at the network and transport layers. But operating at the network layer has the advantage of being universal, since all network applications use IP. So you can apply policies regardless of the layer 7 protocol, and these will be enforced in the kernel space. It’s extremely fast, but not as flexible as Envoy policies.

Another difference worth mentioning is that NetworkPolicies work in an additive, whitelist model. When a NetworkPolicy selects a specific pod, that pod will reject any connections, except those that are explicitly allowed. These policies are additive, they do not conflict, and order of evaluation is irrelevant. AuthorizationPolicies on the other hand have DENY and ALLOW rules as well, that complicates things a bit, but again, allows for more flexible rules.

So should you use Istio AuthorizationPolicies over plain Kubernetes NetworkPolicies? Well, it always depends on your use case. If you want to have a finer grained authorization model, you should go with Istio, but if your only requirement is that “pod A should only be able to communicate with pod B”, then NetworkPolicies are just as good. Or you can even use the two concepts side-by-side.

Authorization policies 🔗︎

Istio authorization doesn’t need to be explicitly enabled. When no AuthorizationPolicies select a workload, all requests are allowed. To enforce access control, you have to apply at least one AuthorizationPolicy resource.

To start experimenting with Istio and AuthorizationPolicies, we suggest to try Backyards and get up and running with an example application in minutes. Backyards provides an Istio control panel where you can track, visualize or even manage your Istio YAML configuration.

To get started, point the KUBECONFIG env variable to your cluster, and install Istio with Backyards:

curl https://getbackyards.sh | sh && backyards install -a

Backyards is Banzai Cloud’s Istio service mesh distribution. You can test and evaluate it in non-production environments. Contact us if you’re interested in using Backyards in production.

Scope 🔗︎

AuthorizationPolicies can be mesh-, namespace-, and workload-wide depending on the namespace and the spec/selector field. The namespace of the resource determines the namespace where the rules will be enforced. When the spec/selector field is omitted, the rules are namespace-wide. The selector, that is a standard Kubernetes label selector, can be used to restrict the policy to specific workload(s) in the namespace, making the policy workload-wide. Just like with the PeerAuthentication resource, putting it in the root Istio namespace (usually istio-system), without a selector has a special effect: these rules will be enforced mesh-wide, in all namespaces.

The following is a workload-wide policy, that applies to pods in the backyards-demo namespace that have the app=catalog label.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: get-only
  namespace: backyards-demo
spec:
  selector:
    matchLabels:
      app: catalog
  action: ALLOW
  rules:
  - to:
    - operation:
         methods: ["GET"]

Action 🔗︎

Unlike NetworkPolicies, AuthorizationPolicies support both ALLOW and DENY actions. If any ALLOW policies are applied to a workload, traffic is denied to that workload by default, and only those requests that are explicitly configured are allowed. It could be a bit confusing at first, especially that the default action is ALLOW, so a policy like this will deny all traffic in a namespace:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: backyards-demo
spec:
  {}

While this one allows all traffic:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-any
  namespace: backyards-demo
spec:
  rules:
  - {}

The deny policies take precedence over allow policies, so for example if there are conflicting rules, where a policy allows GET requests, and another denies them, the deny policy will be applied. When multiple policies are applied to the same workload, Istio applies them additively.

Rules 🔗︎

An authorization policy contains a list of rules, that describe which requests are matched, and then allowed or denied based on the action. Rules are built of three parts: sources, operations and conditions. Sources are specified in the from field, and answer the who? question. Operations are listed in the to field, and answer the what? question. Then at last, conditions are described in the when field and answer the when? question.

Let’s see a concrete example:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: catalog
  namespace: backyards-demo
spec:
  selector:
    matchLabels:
      app: catalog
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/backyards-demo/sa/frontpage"]
    to:
    - operation:
        methods: ["GET"]

This AuthorizationPolicy is applied to the catalog workload in the backyards-demo namespace, and while not explicitly specified, it’s an ALLOW rule, so it will deny all traffic that doesn’t match the rules described here. The rules contain a source, that means that traffic is allowed only from a workload with the cluster.local/ns/backyards-demo/sa/frontpage identity (service account). It also contains an operation, that only matches GET requests. It doesn’t contain a condition, which means match any conditions. So to recap, the above policy allows GET requests from workloads with the cluster.local/ns/backyards-demo/sa/frontpage identity to backyard-demo/catalog, and denies everything else.

In the example, the source is a principal, but it can be requestPrincipals, namespaces or ipBlocks as well. Istio also support exclusion matching, by providing the same fields with a not prefix. So for example notNamespaces: default would match sources from all namespaces, except from default.

Let’s take a look at the operation field as well: along methods, valid matchers are hosts, ports, paths and their exclusion pairs, like notHosts.

In most cases the when field can be omitted, it’s usually only used in complex scenarios, but it can be used to further customize request matching with a list of supported Istio attributes. For example the below example matches request header values:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: header-matching
 namespace: backyards-demo
spec:
 selector:
   matchLabels:
     app: catalog
 action: ALLOW
 rules:
 - from:
   - source:
       principals: ["cluster.local/ns/backyards-demo/sa/frontpage"]
   to:
   - operation:
       methods: ["GET"]
   when:
   - key: request.headers[version]
     values: ["v1", "v2"]

Finally, take a look at a more complex rule to see how it matches requests when most fields contain multiple entries:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: example-policy
 namespace: backyards-demo
spec:
  selector:
    matchLabels:
      app: movies
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/backyards-demo/sa/catalog"]
    - source:
        namespaces: ["backyards-test"]
    to:
    - operation:
        methods: ["GET"]
        paths: ["/api/v1*"]
    - operation:
        methods: ["POST"]
        paths: ["/api/v1*"]
 - from:
   - source:
       principals: ["cluster.local/ns/backyards-demo/sa/bookings"]
   to:
   - operation:
       methods: ["GET"]
       paths: ["/api/v1/movies*"]
   when:
   - key: request.auth.claims[iss]
     values: ["https://accounts.banzaicloud.io"]

This final example contains two separate rules in one policy with an ALLOW action.

  • these rules are enforced for the pods that match the label selector app=movies in the backyards-demo namespace
  • it allows GET requests to /api/v1* OR POST requests to /api/v1* from workloads in the backyards-test namespace, OR from workloads with the cluster.local/ns/backyards-demo/sa/catalog service account
  • it also allows GET requests to /api/v1/movies* from workloads with the cluster.local/ns/backyards-demo/sa/bookings service account, when the request has a valid JWT token, issued by “https://accounts.banzaicloud.io”
  • it denies every other request to the movies workload

Notes 🔗︎

  • the same goal could have been achieved with two different AuthorizationPolicy entries for the two different rules
  • mutual TLS is required to securely pass information between Envoy proxies, and it’s needed for some of the fields, like source.principals, source.namespaces, or the connection.sni condition
  • plain TCP traffic can also be authorized by Istio, but in that case the hosts, methods and paths operations have no effect, as well as the request_principals field in the source section and some of the custom conditions
  • most fields support exact, prefix, suffix and presence value matching: prefix and suffix is when the value starts or ends with a *, presence matching is * and it’s used to specify anything but empty
  • an example for presence matching is source.principals: ["*"], that means all authenticated requests

Summary 🔗︎

Istio can be used to enforce access control between workloads in the service mesh using the AuthorizationPolicy custom resource. This kind of access control is enforced at the application layer by the Envoy sidecar proxies. It gives the user a very powerful and flexible, yet performant way of authorization between Kubernetes workloads.

Never miss a post again!
Schedule a Backyards demo

If you are interested in our technology and open source projects, follow us on GitHub, LinkedIn, or Twitter, or get in touch on Slack: