Skip to content

Commit

Permalink
API: authorization api for jwt claims (envoyproxy#4009)
Browse files Browse the repository at this point in the history
* authorization api for jwt claims

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

* fix gen check

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

* add comments

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

* change claim type to value type

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

* add well-known claims to API

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

* update api

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

* update comments

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

* Update api/v1alpha1/authorization_types.go

Co-authored-by: Arko Dasgupta <arkodg@users.noreply.github.com>
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* Update api/v1alpha1/authorization_types.go

Co-authored-by: Arko Dasgupta <arkodg@users.noreply.github.com>
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* Update api/v1alpha1/authorization_types.go

Co-authored-by: Arko Dasgupta <arkodg@users.noreply.github.com>
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>

* address comments

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

* add CEL validation

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

* fix cel test

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

---------

Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
Co-authored-by: Arko Dasgupta <arkodg@users.noreply.github.com>
Co-authored-by: zirain <zirain2009@gmail.com>
  • Loading branch information
3 people authored Sep 6, 2024
1 parent 5998980 commit 4ed16f2
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 12 deletions.
73 changes: 70 additions & 3 deletions api/v1alpha1/authorization_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,95 @@ type AuthorizationRule struct {
Action AuthorizationAction `json:"action"`

// Principal specifies the client identity of a request.
// If there are multiple principal types, all principals must match for the rule to match.
// For example, if there are two principals: one for client IP and one for JWT claim,
// the rule will match only if both the client IP and the JWT claim match.
Principal Principal `json:"principal"`
}

// 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.
//
// +kubebuilder:validation:XValidation:rule="(has(self.clientCIDRs) || has(self.jwt))",message="at least one of clientCIDRs or jwt must be specified"
type Principal struct {
// ClientCIDRs are the IP CIDR ranges of the client.
// Valid examples are "192.168.1.0/24" or "2001:db8::/64"
//
// If multiple CIDR ranges are specified, one of the CIDR ranges must match
// the client IP for the rule to match.
//
// The client IP is inferred from the X-Forwarded-For header, a custom header,
// or the proxy protocol.
// You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
// the `ClientTrafficPolicy` to configure how the client IP is detected.
// +kubebuilder:validation:MinItems=1
// +optional
ClientCIDRs []CIDR `json:"clientCIDRs"`

// TODO: Zhaohuabing the MinItems=1 validation can be relaxed to allow empty list
// after other principal types are supported. However, at least one principal is required
// JWT authorize the request based on the JWT claims and scopes.
// Note: in order to use JWT claims for authorization, you must configure the
// JWT authentication in the same `SecurityPolicy`.
// +optional
// +notImplementedHide
JWT *JWTPrincipal `json:"jwt,omitempty"`
}

// JWTPrincipal specifies the client identity of a request based on the JWT claims and scopes.
// At least one of the claims or scopes must be specified.
// Claims and scopes are And-ed together if both are specified.
//
// +kubebuilder:validation:XValidation:rule="(has(self.claims) || has(self.scopes))",message="at least one of claims or scopes must be specified"
type JWTPrincipal struct {
// 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
Claims []JWTClaim `json:"claims,omitempty"`

// Scopes are a special type of claim in a JWT token that represents the permissions of the client.
//
// The value of the scopes field should be a space delimited string that is expected in the scope parameter,
// 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"`
}

// JWTClaim specifies a claim in a JWT token.
type JWTClaim struct {
// Name is the name of the claim.
// If it is a nested claim, use a dot (.) separated string as the name to
// 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".
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
// +optional
ValueType *JWTClaimValueType `json:"valueType,omitempty"`

// Values are the values that the claim must match.
// 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.
Values []string `json:"values"`
}

type JWTClaimValueType string

const (
JWTClaimValueTypeString JWTClaimValueType = "String"
JWTClaimValueTypeStringArray JWTClaimValueType = "StringArray"
)

// AuthorizationAction defines the action to be taken if a rule matches.
// +kubebuilder:validation:Enum=Allow;Deny
type AuthorizationAction string
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/securitypolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type SecurityPolicy struct {
// +kubebuilder:validation:XValidation:rule="has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == 'gateway.networking.k8s.io') : true ", message="this policy can only have a targetRefs[*].group of gateway.networking.k8s.io"
// +kubebuilder:validation:XValidation:rule="has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in ['Gateway', 'HTTPRoute', 'GRPCRoute']) : true ", message="this policy can only have a targetRefs[*].kind of Gateway/HTTPRoute/GRPCRoute"
// +kubebuilder:validation:XValidation:rule="has(self.targetRefs) ? self.targetRefs.all(ref, !has(ref.sectionName)) : true",message="this policy does not yet support the sectionName field"
// +kubebuilder:validation:XValidation:rule="(has(self.authorization) && has(self.authorization.rules) && self.authorization.rules.exists(r, has(r.principal.jwt))) ? has(self.jwt) : true", message="if authorization.rules.principal.jwt is used, jwt must be defined"
//
// SecurityPolicySpec defines the desired state of SecurityPolicy.
type SecurityPolicySpec struct {
Expand Down
57 changes: 57 additions & 0 deletions 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 @@ -85,14 +85,20 @@ spec:
If not specified, Envoy Gateway will generate a unique name for the rule.n
type: string
principal:
description: Principal specifies the client identity of
a request.
description: |-
Principal specifies the client identity of a request.
If there are multiple principal types, all principals must match for the rule to match.
For example, if there are two principals: one for client IP and one for JWT claim,
the rule will match only if both the client IP and the JWT claim match.
properties:
clientCIDRs:
description: |-
ClientCIDRs are the IP CIDR ranges of the client.
Valid examples are "192.168.1.0/24" or "2001:db8::/64"
If multiple CIDR ranges are specified, one of the CIDR ranges must match
the client IP for the rule to match.
The client IP is inferred from the X-Forwarded-For header, a custom header,
or the proxy protocol.
You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in
Expand All @@ -103,11 +109,75 @@ 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
required:
- clientCIDRs
jwt:
description: |-
JWT authorize the request based on the JWT claims and scopes.
Note: in order to use JWT claims for authorization, you must configure the
JWT authentication in the same `SecurityPolicy`.
properties:
claims:
description: |-
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.
items:
description: JWTClaim specifies a claim in a JWT
token.
properties:
name:
description: |-
Name is the name of the claim.
If it is a nested claim, use a dot (.) separated string as the name to
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".
type: string
valueType:
default: String
description: |-
ValueType is the type of the claim value.
Only String and StringArray types are supported for now.
enum:
- String
- StringArray
type: string
values:
description: |-
Values are the values that the claim must match.
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.
items:
type: string
type: array
required:
- name
- values
type: object
type: array
scopes:
description: |-
Scopes are a special type of claim in a JWT token that represents the permissions of the client.
The value of the scopes field should be a space delimited string that is expected in the scope parameter,
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.
items:
type: string
type: array
type: object
x-kubernetes-validations:
- message: at least one of claims or scopes must be
specified
rule: (has(self.claims) || has(self.scopes))
type: object
x-kubernetes-validations:
- message: at least one of clientCIDRs or jwt must be specified
rule: (has(self.clientCIDRs) || has(self.jwt))
required:
- action
- principal
Expand Down Expand Up @@ -2321,6 +2391,10 @@ spec:
- message: this policy does not yet support the sectionName field
rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, !has(ref.sectionName))
: true'
- message: if authorization.rules.principal.jwt is used, jwt must be defined
rule: '(has(self.authorization) && has(self.authorization.rules) &&
self.authorization.rules.exists(r, has(r.principal.jwt))) ? has(self.jwt)
: true'
status:
description: Status defines the current status of SecurityPolicy.
properties:
Expand Down
Loading

0 comments on commit 4ed16f2

Please sign in to comment.