Concepts for Serving Identities in a Kubernetes Environment
On this page
A Typical Kubernetes Deployment
Containers are the ultimate tool for cloud-native applications. Kubernetes is the de-facto platform for managing containers and containerized applications. The platform builds upon container runtimes. You can define a desirable state of the application and Kubernetes maintains that state automatically.
Kubernetes groups containers into pods that in turn form a service. Services are exposed and consumed through an ingress component, reverse-proxy or API gateway. For clients, the API gateway is the single point of entry that hides all the complexity. The following illustration shows a typical deployment:
The above example is very simplified. In practice the architecture is more complex. As a result, security becomes sophisticated because it should cover various layers and aspects. For example, you need to consider the whole software stack - from the hardware, to the operating system, container runtime, container images, containers and the application itself. Authentication and authorization are essential processes of security implementations. There are different kinds of identities involved when securing an application in Kubernetes:
- Identities for managing Kubernetes (DevOps engineers and administrators interacting with the Kubernetes API)
- Identities running the container (accounts of the Operating System that serves the container runtime)
- Workload Identities (services, service accounts within Kubernetes)
- Identities using the application (user accounts, application specific)
When developing applications, you need to protect data in APIs. To be able to apply the security rules, you often need to authenticate the users in applications. This article explains an extensible design for authentication and authorization using security standards. It highlights concepts on how to deliver identity data in a typical Kubernetes deployment using OAuth 2.0 and OpenID Connect. These enable you to secure connections both inside and outside the cluster, using tokens to communicate user identities.
We recommend using OAuth 2.0 and OpenID Connect because the protocols provide the foundation for a token-based architecture that allows the implementation of a zero-trust approach. A zero-trust approach in this context implies that each microservice in the Kubernetes cluster requires requests to carry a valid token. Any unauthenticated request gets rejected ("zero trust"). OAuth 2.0 and OpenID Connect define standardized protocols for how to retrieve and consume such an access token.
The Ingress Controller
In a typical deployment in Kubernetes, services are exposed via an Ingress component as mentioned above. The Ingress controller is responsible for implementing the Ingress. This article, however, does not distinguish between the Kubernetes specific resource types and uses the term ingress for both.
The ingress is assigned an external IP and provides a facade with externally reachable URLs for clients (API). It may load balance traffic and offer name-based virtual hosting for services. However, the ingress is much more than a network component. Therefore, other common names for the ingress are reverse proxy or API gateway.
Since all incoming requests pass the ingress, it lends itself well to a security component. Despite securing the endpoints and terminating TLS traffic (HTTPS), it can validate incoming requests such as their syntax. What is more, the ingress can enforce security policies as part of that functionality. For example, the ingress should reject any requests that do not contain an access token. It should also perform basic validation of provided access tokens, such as checking the validity time or scopes. In this way, the ingress takes the load off the application in the cluster.
The ingress can not only read but also use plugins to adapt incoming requests before forwarding them to the pods. It can, for example, add HTTP headers to the request. It may as part of the processing also call other systems, such as an OAuth server, store and cache data. These features are important because they enable the implementation of a security pattern called the phantom token approach.
The Phantom Token Approach
It is best practice to adopt a secure token design to avoid leaking data, in particular to avoid leaking personally identifiable information (PII) or personal data in general. The phantom token approach is a simple way to achieve this. It combines the security of opaque tokens with the convenience of JWTs. It does so by pairing an opaque token with a JWT. The opaque token serves as a reference to the JWT representation. Instead of having the microservices (that run in containers) call the OAuth Server for resolving the by-reference token on every request the pattern takes advantage of an API gateway.
Clients receive opaque tokens from the OAuth server which they add in their calls to the application. When the API gateway receives a request with a by-reference token, it looks up the JWT, caches the result and replaces the opaque token with the JWT. It then forwards the request to the microservices. In that way the microservices can benefit from the JWT without exposing any data to the client. The article on the phantom token approach discusses the pattern in detail.
The phantom token approach is very suitable for a Kubernetes deployment because of two reasons. First, it only uses a standardized protocol, namely OAuth 2.0 token introspection. Second, the pattern does not need any additional resources because a Kubernetes deployment commonly already has an ingress in place. You just need to modify the ingress, for example with the help of plugins, to implement the pattern.
An API-First IAM
When building your own APIs, you typically follow an API-first approach, which means you design endpoints for clients before writing the API code. You need the same behaviour in an IAM system, which provides standardized OAuth 2.0 and OpenID Connect APIs. However, OAuth 2.0 and OpenID Connect model neither a single nor static API. The core specifications, for example, already define different APIs for obtaining a token. Many extensions have broadened the frameworks since then. API-first in this context means choosing an IAM system that supports the standards you need in order to satisfy your use-cases.
API-first is particularly relevant for the clients because the majority the OAuth 2.0 and OpenID Connect specifications governs token obtainment. Of the various ways defined in OAuth 2.0 and OpenID Connect to obtain a token, the code flow is the most recommended one. Use it together with the extension called Proof Key for Code Exchange (PKCE). Refer to security best (current) practices for more guidance on secure implementation.
Understand the requirements of the clients as well as microservices that will obtain and consume tokens. Make sure that the OAuth server and the Identity and Access Management system in general are able to provide the APIs (and data) required. Be aware that different entities may need different APIs, in other words endpoints may need to behave differently thus they must be customizable.
The Curity Identity Server offers three different services: the authentication service, the token service and the user management service. Each service has dedicated endpoints that are customizable to a great extent. Which endpoints a service offers depends on the selected features and their configuration.
Each instance of the Curity Identity Server runs a so-called service role that defines which of the available endpoints it exposes. Consequently, you can set up an asymmetric cluster to serve several APIs with a single configuration. An asymmetric cluster enables segmentation. For example, you can then configure Kubernetes to only expose the user management endpoints to internal clients whereas other endpoints, i.e. Kubernetes services, are available to external clients via the ingress.
Service roles are a way of enabling an API-first approach using the Curity Identity Server. In addition, you can adapt the token data depending on the context (e.g., the calling client). In combination with service roles, this feature ensures that clients and microservices receive identity data specific to their needs. This means, you can follow the least-privilege principle using a cloud-native approach.
This article discussed different concepts to consider when serving identity data to an application running in Kubernetes. In short, those concepts are:
- Implement a token-based architecture based on OAuth 2.0 and OpenId Connect.
- Use the ingress to perform basic validation of external requests.
- Issue opaque tokens to external clients and apply the phantom token approach.
- Consider an API-first approach, and only expose endpoints and data that are actually consumed.
The latter requires a cloud-native Identity and Access Management system like the Curity Identity Server.