istio_request_auth

Istio request authentication interface library.

This library provides the provider and requirer sides of the istio-request-auth relation interface for configuring Istio RequestAuthentication resources via relation data (JWT rules, JWKS endpoints, claim-to-header mapping).

What is this library for?

The istio-ingress-k8s charm wraps a Kubernetes Gateway API of class istio. It can connect to an OAuth 2.0 provider like the oauth2-proxy charm via the forward-auth relation to forward requests via an authentication stack for user authentication.

Istio also natively supports validating a pre-generated JWT against an issuer using a RequestAuthentication Kubernetes resource. This means a request containing a JWT in the header can be natively authenticated by Istio instead of taking a detour via the authentication stack, and the claims from the token are parsed and added as headers to the downstream request. The RequestAuthentication resource is purely an Istio concept offered by the CRDs native to Istio. Hence the interface is named with an istio- prefix.

For applications to take advantage of this feature, they need to tell Istio which issuers they trust and which headers they want the claims in the token to be mapped to. To enable this information exchange and add the appropriate RequestAuthentication resource, the istio-request-auth interface library is introduced.

Security note

If a requirer is connected but has not provided valid (non-empty) jwt_rules, no RequestAuthentication resource is created. This could allow unauthenticated traffic. The provider exposes get_connected_apps so the ingress charm can detect such applications and drop their ingress until valid rules are provided.

Requirer usage:

from charmlibs.interfaces.istio_request_auth import (
    ClaimToHeader,
    JWTRule,
    IstioRequestAuthRequirer,
)

class MyAppCharm(CharmBase):
    def __init__(self, *args):
        super().__init__(*args)
        self.request_auth = IstioRequestAuthRequirer(self)

    def _publish_rules(self):
        self.request_auth.publish_data([
            JWTRule(
                issuer="https://accounts.example.com",
                claim_to_headers=[
                    ClaimToHeader(header="x-user-email", claim="email"),
                ],
            ),
        ])

Provider usage:

from charmlibs.interfaces.istio_request_auth import IstioRequestAuthProvider

class MyIngressCharm(CharmBase):
    def __init__(self, *args):
        super().__init__(*args)
        self.request_auth = IstioRequestAuthProvider(self)

    def _reconcile(self):
        valid = self.request_auth.get_data()
        connected = self.request_auth.get_connected_apps()
        invalid_apps = connected - set(valid.keys())
        # Drop ingress for invalid_apps, create RequestAuthentication for valid
class ClaimToHeader(*, header: str, claim: str)

Bases: BaseModel

Maps a JWT claim to a request header.

header: str
claim: str
class FromHeader(*, name: str, prefix: str | None = None)

Bases: BaseModel

Specifies a header location from which to extract a JWT.

name: str
prefix: str | None
class IstioRequestAuthProvider(
charm: CharmBase,
relation_name: str = 'istio-request-auth',
)

Bases: Object

Provider side of the istio-request-auth interface.

Used by the ingress charm to read JWT authentication rules from all related applications.

Applications that are connected but have not provided valid (non-empty) jwt_rules are excluded from get_data but included in get_connected_apps. Consumers can compare the two sets to identify applications that have not yet provided data:

valid = provider.get_data()
connected = provider.get_connected_apps()
apps_without_data = connected - set(valid.keys())
property is_ready: bool

Check if any related application has provided valid request auth data.

Returns:

True if at least one requirer has published non-empty jwt_rules.

get_connected_apps() set[str]

Return the names of all applications connected over the relation.

This includes apps that have not yet provided valid data.

get_data() dict[str, list[JWTRule]]

Retrieve valid JWT rules from all related applications.

Uses ops.Relation.load to deserialise each application’s databag into RequestAuthData. Only applications whose databag contains a non-empty jwt_rules list are included.

Returns:

A dict mapping application name to its list of JWTRule objects.

class IstioRequestAuthRequirer(
charm: CharmBase,
relation_name: str = 'istio-request-auth',
)

Bases: Object

Requirer side of the istio-request-auth interface.

Used by downstream applications to publish their JWT authentication rules to the ingress charm.

publish_data(
jwt_rules: list[JWTRule],
) None

Publish JWT rules to the provider.

Uses ops.Relation.save to write a RequestAuthData instance to the application databag so jwt_rules appears as a top-level key.

Parameters:

jwt_rules – The JWT validation rules to publish.

class JWTRule(
*,
issuer: str,
jwks_uri: str | None = None,
audiences: list[str] | None = None,
forward_original_token: bool | None = None,
claim_to_headers: list[ClaimToHeader] | None = None,
from_headers: list[FromHeader] | None = None,
)

Bases: BaseModel

A single JWT validation rule provided by the requiring app.

issuer: str
jwks_uri: str | None
audiences: list[str] | None
forward_original_token: bool | None
claim_to_headers: list[ClaimToHeader] | None
from_headers: list[FromHeader] | None