diff --git a/api/docs/BUILD b/api/docs/BUILD index d26ef6d158172..da38a524aed6f 100644 --- a/api/docs/BUILD +++ b/api/docs/BUILD @@ -36,6 +36,7 @@ proto_library( "//envoy/config/filter/http/header_to_metadata/v2:header_to_metadata", "//envoy/config/filter/http/health_check/v2:health_check", "//envoy/config/filter/http/ip_tagging/v2:ip_tagging", + "//envoy/config/filter/http/jwt_authn/v2alpha:jwt_authn", "//envoy/config/filter/http/lua/v2:lua", "//envoy/config/filter/http/rate_limit/v2:rate_limit", "//envoy/config/filter/http/rbac/v2:rbac", diff --git a/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto b/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto index 85e134bcc82d3..153264e4f1242 100644 --- a/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto +++ b/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto @@ -11,10 +11,19 @@ import "google/protobuf/empty.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -// This message specifies how a JSON Web Token (JWT) can be verified. JWT format is defined -// `here `_. Please see `OAuth2.0 -// `_ and `OIDC1.0 `_ for -// the authentication flow. +// Please see following for JWT authentication flow: +// +// * `JSON Web Token (JWT) `_ +// * `The OAuth 2.0 Authorization Framework `_ +// * `OpenID Connect `_ +// +// A JwtProvider message specifies how a JSON Web Token (JWT) can be verified. It specifies: +// +// * issuer: the principal that issues the JWT. It has to match the one from the token. +// * allowed audiences: the ones in the token have to be listed here. +// * how to fetch public key JWKS to verify the token signature. +// * how to extract JWT token in the request. +// * how to pass successfully verified token payload. // // Example: // @@ -32,15 +41,15 @@ import "validate/validate.proto"; // seconds: 300 // message JwtProvider { - // Identifies the principal that issued the JWT. See `here - // `_. Usually a URL or an email address. + // Specify the `principal `_ that issued + // the JWT, usually a URL or an email address. // // Example: https://securetoken.google.com // Example: 1234567-compute@developer.gserviceaccount.com // string issuer = 1 [(validate.rules).string.min_bytes = 1]; - // The list of JWT `audiences `_. that are + // The list of JWT `audiences `_ are // allowed to access. A JWT containing any of these audiences will be accepted. If not specified, // will not check audiences in the token. // @@ -54,8 +63,8 @@ message JwtProvider { // repeated string audiences = 2; - // `JSON Web Key Set `_ is needed. to validate - // signature of the JWT. This field specifies where to fetch JWKS. + // `JSON Web Key Set (JWKS) `_ is needed to + // validate signature of a JWT. This field specifies where to fetch JWKS. oneof jwks_source_specifier { option (validate.required) = true; @@ -90,7 +99,7 @@ message JwtProvider { // .. code-block:: yaml // // local_jwks: - // inline_string: "ACADADADADA" + // inline_string: ACADADADADA // envoy.api.v2.core.DataSource local_jwks = 4; } @@ -103,18 +112,16 @@ message JwtProvider { // // If no explicit location is specified, the following default locations are tried in order: // - // 1. The Authorization header using the Bearer schema. See `here - // `_. Example: + // 1. The Authorization header using the `Bearer schema + // `_. Example:: // - // Authorization: Bearer . + // Authorization: Bearer . // - // 2. `access_token` query parameter. See `this - // `_ + // 2. `access_token `_ query parameter. // - // Multiple JWTs can be verified for a request. Each JWT has to be extracted from the locations - // its issuer specified or from the default locations. - + // its provider specified or from the default locations. + // // Specify the HTTP headers to extract JWT token. For examples, following config: // // .. code-block:: yaml @@ -149,10 +156,6 @@ message JwtProvider { // base64_encoded(jwt_payload_in_JSON) // // If it is not specified, the payload will not be forwarded. - // Multiple JWTs in a request from different issuers will be supported. Multiple JWTs from the - // same issuer will not be supported. Each issuer can config this `forward_payload_header`. If - // multiple JWTs from different issuers want to forward their payloads, their - // `forward_payload_header` should be different. string forward_payload_header = 8; } @@ -201,37 +204,37 @@ message ProviderWithAudiences { // # Example 1: not required with an empty message // // # Example 2: require A -// provider_name: "provider-A" +// provider_name: provider-A // // # Example 3: require A or B // requires_any: // requirements: -// - provider_name: "provider-A" -// - provider_name: "provider-B" +// - provider_name: provider-A +// - provider_name: provider-B // // # Example 4: require A and B // requires_all: // requirements: -// - provider_name: "provider-A" -// - provider_name: "provider-B" +// - provider_name: provider-A +// - provider_name: provider-B // // # Example 5: require A and (B or C) // requires_all: // requirements: -// - provider_name: "provider-A" +// - provider_name: provider-A // - requires_any: // requirements: -// - provider_name: "provider-B" -// - provider_name: "provider-C" +// - provider_name: provider-B +// - provider_name: provider-C // // # Example 6: require A or (B and C) // requires_any: // requirements: -// - provider_name: "provider-A" +// - provider_name: provider-A // - requires_all: // requirements: -// - provider_name: "provider-B" -// - provider_name: "provider-C" +// - provider_name: provider-B +// - provider_name: provider-C // message JwtRequirement { oneof requires_type { @@ -277,7 +280,7 @@ message JwtRequirementAndList { // .. code-block:: yaml // // - match: -// prefix: "/healthz" +// prefix: /healthz // // In above example, "requires" field is empty for /healthz prefix match, // it means that requests matching the path prefix don't require JWT authentication. @@ -287,8 +290,8 @@ message JwtRequirementAndList { // .. code-block:: yaml // // - match: -// prefix: "/" -// requires: { provider_name: "provider-A" } +// prefix: / +// requires: { provider_name: provider-A } // // In above example, all requests matched the path prefix require jwt authentication // from "provider-A". @@ -301,7 +304,7 @@ message RequirementRule { // .. code-block:: yaml // // match: - // prefix: "/" + // prefix: / // envoy.api.v2.route.RouteMatch match = 1 [(validate.rules).message.required = true]; @@ -333,22 +336,22 @@ message RequirementRule { // rules: // # Not jwt verification is required for /health path // - match: -// prefix: "/health" +// prefix: /health // // # Jwt verification for provider1 is required for path prefixed with "prefix" // - match: -// prefix: "/prefix" +// prefix: /prefix // requires: -// provider_name: "provider1" +// provider_name: provider1 // // # Jwt verification for either provider1 or provider2 is required for all other requests. // - match: -// prefix: "/" +// prefix: / // requires: // requires_any: // requirements: -// - provider_name: "provider1" -// - provider_name: "provider2" +// - provider_name: provider1 +// - provider_name: provider2 // message JwtAuthentication { // Map of provider names to JwtProviders. @@ -380,22 +383,26 @@ message JwtAuthentication { // .. code-block:: yaml // // rules: - // - match: { prefix: "/healthz" } - // - match: { prefix: "/baz" } + // - match: + // prefix: /healthz + // - match: + // prefix: /baz // requires: - // provider_name: "provider1" - // - match: { prefix: "/foo" } + // provider_name: provider1 + // - match: + // prefix: /foo // requires: // requires_any: // requirements: - // - provider_name: "provider1" - // - provider_name: "provider2" - // - match: { prefix: "/bar" } + // - provider_name: provider1 + // - provider_name: provider2 + // - match: + // prefix: /bar // requires: // requires_all: // requirements: - // - provider_name: "provider1" - // - provider_name: "provider2" + // - provider_name: provider1 + // - provider_name: provider2 // repeated RequirementRule rules = 2; } diff --git a/docs/build.sh b/docs/build.sh index ac4a5520fea08..eecdab72416ef 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -87,6 +87,7 @@ PROTO_RST=" /envoy/config/filter/http/health_check/v2/health_check/envoy/config/filter/http/health_check/v2/health_check.proto.rst /envoy/config/filter/http/header_to_metadata/v2/header_to_metadata/envoy/config/filter/http/header_to_metadata/v2/header_to_metadata.proto.rst /envoy/config/filter/http/ip_tagging/v2/ip_tagging/envoy/config/filter/http/ip_tagging/v2/ip_tagging.proto.rst + /envoy/config/filter/http/jwt_authn/v2alpha/jwt_authn/envoy/config/filter/http/jwt_authn/v2alpha/config.proto.rst /envoy/config/filter/http/lua/v2/lua/envoy/config/filter/http/lua/v2/lua.proto.rst /envoy/config/filter/http/rate_limit/v2/rate_limit/envoy/config/filter/http/rate_limit/v2/rate_limit.proto.rst /envoy/config/filter/http/rbac/v2/rbac/envoy/config/filter/http/rbac/v2/rbac.proto.rst diff --git a/docs/root/configuration/http_filters/http_filters.rst b/docs/root/configuration/http_filters/http_filters.rst index 91af21e0e7bf9..49900f74ac538 100644 --- a/docs/root/configuration/http_filters/http_filters.rst +++ b/docs/root/configuration/http_filters/http_filters.rst @@ -18,6 +18,7 @@ HTTP filters health_check_filter header_to_metadata_filter ip_tagging_filter + jwt_authn_filter lua_filter rate_limit_filter rbac_filter diff --git a/docs/root/configuration/http_filters/jwt_authn_filter.rst b/docs/root/configuration/http_filters/jwt_authn_filter.rst new file mode 100644 index 0000000000000..1e22742e9f7b5 --- /dev/null +++ b/docs/root/configuration/http_filters/jwt_authn_filter.rst @@ -0,0 +1,187 @@ +.. _config_http_filters_jwt_authn: + +JWT Authentication +================== + +This HTTP filter can be used to verify JSON Web Token (JWT). It will verify its signature, audiences and issuer. It will also check its time restrictions, such as expiration and nbf (not before) time. If the JWT verification fails, its request will be rejected. If the JWT verification succeeds, its payload can be forwarded to the upstream for further authorization if desired. + +JWKS is needed to verify JWT signatures. They can be specified in the filter config or can be fetched remotely from a JWKS server. + +.. attention:: + Only ES256 and RS256 are supported for the JWT alg. + +Configuration +------------- + +This HTTP :ref:`filter config ` has two fields: + +* Field *providers* specifies how a JWT should be verified, such as where to extract the token, where to fetch the public key (JWKS) and where to output its payload. +* Field *rules* specifies matching rules and their requirements. If a request matches a rule, its requirement applies. The requirement specifies which JWT providers should be used. + +JwtProvider +~~~~~~~~~~~ + +:ref:`JwtProvider ` specifies how a JWT should be verified. It has the following fields: + +* *issuer*: the principal that issued the JWT, usually a URL or an email address. +* *audiences*: a list of JWT audiences allowed to access. A JWT containing any of these audiences will be accepted. + If not specified, the audiences in JWT will not be checked. +* *local_jwks*: fetch JWKS in local data source, either in a local file or embedded in the inline string. +* *remote_jwks*: fetch JWKS from a remote HTTP server, also specify cache duration. +* *forward*: if true, JWT will be forwarded to the upstream. +* *from_headers*: extract JWT from HTTP headers. +* *from_params*: extract JWT from query parameters. +* *forward_payload_header*: forward the JWT payload in the specified HTTP header. + +Default Extract Location +~~~~~~~~~~~~~~~~~~~~~~~~ + +If *from_headers* and *from_params* is empty, the default location to extract JWT is from HTTP header:: + + Authorization: Bearer + +If fails to extract a JWT from above header, then check query parameter key *access_token* as in this example:: + + /path?access_token= + +In the :ref:`filter config `, *providers* is a map, to map *provider_name* to a :ref:`JwtProvider `. The *provider_name* must be unique, it is referred in the `JwtRequirement ` in its *provider_name* field. + +.. important:: + For *remote_jwks*, a **jwks_cluster** cluster is required. + +Due to above requirement, `OpenID Connect Discovery `_ is not supported since the URL to fetch JWKS is in the response of the discovery. It is not easy to setup a cluster config for a dynamic URL. + +Remote JWKS config example +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: yaml + + providers: + provider_name1: + issuer: https://example.com + audiences: + - bookstore_android.apps.googleusercontent.com + - bookstore_web.apps.googleusercontent.com + remote_jwks: + http_uri: + uri: https://example.com/jwks.json + cluster: example_jwks_cluster + cache_duration: + seconds: 300 + +Above example fetches JWSK from a remote server with URL https://example.com/jwks.json. The token will be extracted from the default extract locations. The token will not be forwarded to upstream. JWT payload will not be added to the request header. + +Following cluster **example_jwks_cluster** is needed to fetch JWKS. + +.. code-block:: yaml + + cluster: + name: example_jwks_cluster + type: STRICT_DNS + hosts: + socket_address: + address: example.com + port_value: 80 + + +Inline JWKS config example +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Another config example using inline JWKS: + +.. code-block:: yaml + + providers: + provider_name2: + issuer: https://example2.com + local_jwks: + inline_string: PUBLIC-KEY + from_headers: + - name: jwt-assertion + forward: true + forward_payload_header: x-jwt-payload + +Above example uses config inline string to specify JWKS. The JWT token will be extracted from HTTP headers as:: + + jwt-assertion: . + +JWT payload will be added to the request header as following format:: + + x-jwt-payload: base64_encoded(jwt_payload_in_JSON) + +RequirementRule +~~~~~~~~~~~~~~~ + +:ref:`RequirementRule ` has two fields: + +* Field *match* specifies how a request can be matched; e.g. by HTTP headers, or by query parameters, or by path prefixes. +* Field *requires* specifies the JWT requirement, e.g. which provider is required. + +.. important:: + - **If a request matches multiple rules, the first matched rule will apply**. + - If the matched rule has empty *requires* field, **JWT verification is not required**. + - If a request doesn't match any rules, **JWT verification is not required**. + +Single requirement config example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: yaml + + providers: + jwt_provider1: + issuer: https://example.com + audiences: + audience1 + local_jwks: + inline_string: PUBLIC-KEY + rules: + - match: + prefix: /health + - match: + prefix: /api + requires: + provider_and_audiences: + provider_name: jwt_provider1 + audiences: + api_audience + - match: + prefix: / + requires: + provider_name: jwt_provider1 + +Above config uses single requirement rule, each rule may have either an empty requirement or a single requirement with one provider name. + +Group requirement config example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: yaml + + providers: + provider1: + issuer: https://provider1.com + local_jwks: + inline_string: PUBLIC-KEY + provider2: + issuer: https://provider2.com + local_jwks: + inline_string: PUBLIC-KEY + rules: + - match: + prefix: /any + requires: + requires_any: + requirements: + - provider_name: provider1 + - provider_name: provider2 + - match: + prefix: /all + requires: + requires_all: + requirements: + - provider_name: provider1 + - provider_name: provider2 + +Above config uses more complex *group* requirements: + +* The first *rule* specifies *requires_any*; if any of **provider1** or **provider2** requirement is satisfied, the request is OK to proceed. +* The second *rule* specifies *requires_all*; only if both **provider1** and **provider2** requirements are satisfied, the request is OK to proceed.