Skip to content

Commit

Permalink
Impl: JWT claim authorization (#4167)
Browse files Browse the repository at this point in the history
* set reasonable length validation

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* Gateway API

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* generate xds

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* e2e test

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* minor change

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix lint

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix lint

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix test

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix test

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix e2e

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* add test

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* add more e2e tests

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix gen

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix typo

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* Use provider name as the PayloadInMetada sinde issuer is optional

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* minor change

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* address comments

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* Normalize the scopes to facilitate matching in Authorization

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix lint

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix e2e

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* fix e2e

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* minor wording

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* add test for nested claims and StringArray value type

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* add xds translator test for nested claim

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* minor change

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

---------

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
  • Loading branch information
zhaohuabing authored Sep 24, 2024
1 parent 4a3e324 commit 8dfd151
Show file tree
Hide file tree
Showing 46 changed files with 2,730 additions and 86 deletions.
38 changes: 34 additions & 4 deletions api/v1alpha1/authorization_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ type Authorization struct {
// AuthorizationRule defines a single authorization rule.
type AuthorizationRule struct {
// Name is a user-friendly name for the rule.
// If not specified, Envoy Gateway will generate a unique name for the rule.n
// If not specified, Envoy Gateway will generate a unique name for the rule.
//
// +optional
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
Name *string `json:"name,omitempty"`

// Action defines the action to be taken if the rule matches.
Expand All @@ -45,7 +48,8 @@ type AuthorizationRule struct {
// Principal specifies the client identity of a request.
// A client identity can be a client IP, a JWT claim, username from the Authorization header,
// or any other identity that can be extracted from a custom header.
// Currently, only the client IP is supported.

// If there are multiple principal types, all principals must match for the rule to match.
//
// +kubebuilder:validation:XValidation:rule="(has(self.clientCIDRs) || has(self.jwt))",message="at least one of clientCIDRs or jwt must be specified"
type Principal struct {
Expand All @@ -60,7 +64,8 @@ type Principal struct {
// You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
// the `ClientTrafficPolicy` to configure how the client IP is detected.
// +optional
ClientCIDRs []CIDR `json:"clientCIDRs"`
// +kubebuilder:validation:MinItems=1
ClientCIDRs []CIDR `json:"clientCIDRs,omitempty"`

// JWT authorize the request based on the JWT claims and scopes.
// Note: in order to use JWT claims for authorization, you must configure the
Expand All @@ -76,12 +81,23 @@ type Principal struct {
//
// +kubebuilder:validation:XValidation:rule="(has(self.claims) || has(self.scopes))",message="at least one of claims or scopes must be specified"
type JWTPrincipal struct {
// Provider is the name of the JWT provider that used to verify the JWT token.
// In order to use JWT claims for authorization, you must configure the JWT
// authentication with the same provider in the same `SecurityPolicy`.
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
Provider string `json:"provider"`

// Claims are the claims in a JWT token.
//
// If multiple claims are specified, all claims must match for the rule to match.
// For example, if there are two claims: one for the audience and one for the issuer,
// the rule will match only if both the audience and the issuer match.
//
// +optional
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=16
Claims []JWTClaim `json:"claims,omitempty"`

// Scopes are a special type of claim in a JWT token that represents the permissions of the client.
Expand All @@ -90,8 +106,11 @@ type JWTPrincipal struct {
// as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749#page-23.
//
// If multiple scopes are specified, all scopes must match for the rule to match.
//
// +optional
Scopes []string `json:"scopes,omitempty"`
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=16
Scopes []JWTScope `json:"scopes,omitempty"`
}

// JWTClaim specifies a claim in a JWT token.
Expand All @@ -101,10 +120,14 @@ type JWTClaim struct {
// represent the full path to the claim.
// For example, if the claim is in the "department" field in the "organization" field,
// the name should be "organization.department".
//
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
Name string `json:"name"`

// ValueType is the type of the claim value.
// Only String and StringArray types are supported for now.
//
// +kubebuilder:validation:Enum=String;StringArray
// +kubebuilder:default=String
// +unionDiscriminator
Expand All @@ -115,9 +138,16 @@ type JWTClaim struct {
// If the claim is a string type, the specified value must match exactly.
// If the claim is a string array type, the specified value must match one of the values in the array.
// If multiple values are specified, one of the values must match for the rule to match.
//
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=16
Values []string `json:"values"`
}

// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
type JWTScope string

type JWTClaimValueType string

const (
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ spec:
name:
description: |-
Name is a user-friendly name for the rule.
If not specified, Envoy Gateway will generate a unique name for the rule.n
If not specified, Envoy Gateway will generate a unique name for the rule.
maxLength: 253
minLength: 1
type: string
principal:
description: |-
Expand All @@ -109,6 +111,7 @@ spec:
A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64".
pattern: ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+))
type: string
minItems: 1
type: array
jwt:
description: |-
Expand All @@ -134,6 +137,8 @@ spec:
represent the full path to the claim.
For example, if the claim is in the "department" field in the "organization" field,
the name should be "organization.department".
maxLength: 253
minLength: 1
type: string
valueType:
default: String
Expand All @@ -152,12 +157,24 @@ spec:
If multiple values are specified, one of the values must match for the rule to match.
items:
type: string
maxItems: 16
minItems: 1
type: array
required:
- name
- values
type: object
maxItems: 16
minItems: 1
type: array
provider:
description: |-
Provider is the name of the JWT provider that used to verify the JWT token.
In order to use JWT claims for authorization, you must configure the JWT
authentication with the same provider in the same `SecurityPolicy`.
maxLength: 253
minLength: 1
type: string
scopes:
description: |-
Scopes are a special type of claim in a JWT token that represents the permissions of the client.
Expand All @@ -167,8 +184,14 @@ spec:
If multiple scopes are specified, all scopes must match for the rule to match.
items:
maxLength: 253
minLength: 1
type: string
maxItems: 16
minItems: 1
type: array
required:
- provider
type: object
x-kubernetes-validations:
- message: at least one of claims or scopes must be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,12 @@
"providers": {
"httproute/envoy-gateway-system/backend/rule/0/match/0/www_example_com/example": {
"forward": true,
"normalizePayloadInMetadata": {
"spaceDelimitedClaims": [
"scope"
]
},
"payloadInMetadata": "example",
"remoteJwks": {
"asyncFetch": {},
"cacheDuration": "300s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ xds:
providers:
httproute/envoy-gateway-system/backend/rule/0/match/0/www_example_com/example:
forward: true
normalizePayloadInMetadata:
spaceDelimitedClaims:
- scope
payloadInMetadata: example
remoteJwks:
asyncFetch: {}
cacheDuration: 300s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ xds:
providers:
httproute/envoy-gateway-system/backend/rule/0/match/0/www_example_com/example:
forward: true
normalizePayloadInMetadata:
spaceDelimitedClaims:
- scope
payloadInMetadata: example
remoteJwks:
asyncFetch: {}
cacheDuration: 300s
Expand Down
2 changes: 2 additions & 0 deletions internal/gatewayapi/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,8 @@ func (t *Translator) buildAuthorization(policy *egv1a1.SecurityPolicy) (*ir.Auth
principal.ClientCIDRs = append(principal.ClientCIDRs, cidrMatch)
}

principal.JWT = rule.Principal.JWT

var name string
if rule.Name != nil && *rule.Name != "" {
name = *rule.Name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-1
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
name: httproute-1
spec:
hostnames:
- www.example.com
parentRefs:
- namespace: envoy-gateway
name: gateway-1
sectionName: http
rules:
- matches:
- path:
value: "/foo"
backendRefs:
- name: service-1
port: 8080
securityPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
namespace: envoy-gateway
name: policy-for-gateway # This policy should attach httproute-2
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: gateway-1
jwt:
providers:
- name: example1
issuer: https://two.example.com
audiences:
- two.foo.com
remoteJWKS:
uri: https://two.example.com/jwt/public-key/jwks.json
authorization:
defaultAction: Deny
rules:
- name: "allow-jwt-claim"
action: Deny
principal:
jwt:
provider: example1
scopes:
- "foo"
- "bar"
claims:
- name: "sub"
values:
- "1234567890"
- name: "roles"
valueType: "StringArray"
values:
- "admin"
- "superuser"
Loading

0 comments on commit 8dfd151

Please sign in to comment.