Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API: authorization api for jwt claims #4009

Merged
merged 25 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fedc86d
authorization api for jwt claims
zhaohuabing Aug 6, 2024
ea79829
fix gen check
zhaohuabing Aug 6, 2024
83d62bc
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 16, 2024
af11c2f
add comments
zhaohuabing Aug 16, 2024
b078cc4
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 21, 2024
b95c868
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 22, 2024
2665e11
change claim type to value type
zhaohuabing Aug 22, 2024
fbea186
Merge remote-tracking branch 'upstream/main' into jwt-claim-auth
zhaohuabing Aug 26, 2024
832f525
add well-known claims to API
zhaohuabing Aug 27, 2024
e30f1db
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 27, 2024
5e26c82
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 27, 2024
22b0f9d
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 28, 2024
c05cc70
update api
zhaohuabing Aug 28, 2024
89fb544
update comments
zhaohuabing Aug 28, 2024
4cc1ca8
Update api/v1alpha1/authorization_types.go
zhaohuabing Aug 29, 2024
7882e17
Update api/v1alpha1/authorization_types.go
zhaohuabing Aug 29, 2024
1ce13d6
Update api/v1alpha1/authorization_types.go
zhaohuabing Aug 29, 2024
b00cf0f
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 29, 2024
6d7aa9d
address comments
zhaohuabing Aug 29, 2024
1affa9b
add CEL validation
zhaohuabing Aug 29, 2024
ea39225
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 30, 2024
20536be
Merge branch 'main' into jwt-claim-auth
zhaohuabing Aug 31, 2024
66e2f9c
Merge branch 'main' into jwt-claim-auth
zirain Sep 4, 2024
41fc238
fix cel test
zhaohuabing Sep 6, 2024
e43f31a
Merge branch 'main' into jwt-claim-auth
zhaohuabing Sep 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions api/v1alpha1/authorization_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ 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"`
}

Expand All @@ -47,17 +50,77 @@ 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
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`.
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
// +optional
// +notImplementedHide
JWT *JWTPrincipal `json:"jwt,omitempty"`
Copy link
Member Author

@zhaohuabing zhaohuabing Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may have better names for the field JWT and type JWTPrincipal :-) @arkodg @missBerg

}

// JWTPrincipal specifies the client identity of a request based on the JWT claims and scopes.
// Claims and scopes are And-ed together if both are specified.
// +kubebuilder:validation:XValidation:rule="(has(self.claims) || has(self.scopes))",message="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.
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
// 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.
//
// EG interprets the value of the scope claim as a space-separated string during matching,
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
// as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749.
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
//
// If multiple scopes are specified, all scopes must match for the rule to match.
Scopes []string `json:"scopes,omitempty"`
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
}

// JWTClaim specifies a claim in a JWT token.
type JWTClaim struct {
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
// 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".
// +optional
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
Name string `json:"name"`

// ValueType is the type of the claim value.
// Only string and string array types are supported for now.
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
// +kubebuilder:validation:Enum=String;StringArray
// +kubebuilder:default=String
// +unionDiscriminator
// +optional
ValueType *JWTClaimValueType `json:"valueType,omitempty"`
Copy link
Contributor

@denniskniep denniskniep Sep 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to specify the Matcher instead of the ClaimValueType? I think that would bring more flexibility.

i.e.

type JWTClaim struct {	
	Name string `json:"name"`
	ValueMatcher JWTClaimValueMatcher `json:"valueMatcher "`
}

type JWTClaimValueMatcher struct {
     equalsAny *JWTClaimValues  `json:"valueType,omitempty"`     
     notEqualsAny *JWTClaimValues  `json:"valueType,omitempty"`
     includesAll *JWTClaimValues  `json:"valueType,omitempty"`
     includesAny *JWTClaimValues  `json:"valueType,omitempty"`
     notIncludesAny *JWTClaimValues  `json:"valueType,omitempty"`
}

type JWTClaimValues struct {
     Values []string `json:"values"`
}
  • equalsAny (assume claim is single string)
  • notEqualsAny (assume claim is single string)
  • includesAll (assume claim is string array)
  • includesAny (assume claim is string array)
  • notIncludesAny (assume claim is string array)
  • ...

If necessary for a certain Matcher we could specify different properties

What do you think?

Copy link
Member Author

@zhaohuabing zhaohuabing Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @denniskniep In theory, we could make this API as flexible as the Envoy RBAC API, but I would like to make this API simpler and easier to understand as it's a user-facing API.

I believe the normal use cases for JWT claim based authorization is that we just compare wether the claims equal with any of the specified values (equalsAny). I can't think of any use cases for a substring matching.

Scope comparison is a bit different, all specified scopes must exist in the JWT. It's handled in a dedicated API field.

For the not use case, it can be accomplished with a Allow default action and a Deny rule:

      rules:
      - default: Allow
      - action: Deny
        principal:
          jwt:
            claims:
            - name: user
              values: [jason, alice]


// 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"`
zhaohuabing marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer using string for bool as "true"/"false" and integer if we need these types in the future. Any is too flexible, as it can represent nested structures besides simple types, which we won't use.

}

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
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 @@ -105,6 +111,68 @@ spec:
type: string
minItems: 1
type: array
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 string array 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:
- 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.

EG interprets the value of the scope claim as a space-separated string during matching,
as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749.

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: one of claims or scopes must be specified
rule: (has(self.claims) || has(self.scopes))
required:
- clientCIDRs
type: object
Expand Down
51 changes: 49 additions & 2 deletions site/content/en/latest/api/extension_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ _Appears in:_
| --- | --- | --- | --- |
| `name` | _string_ | false | Name is a user-friendly name for the rule.<br />If not specified, Envoy Gateway will generate a unique name for the rule.n |
| `action` | _[AuthorizationAction](#authorizationaction)_ | true | Action defines the action to be taken if the rule matches. |
| `principal` | _[Principal](#principal)_ | true | Principal specifies the client identity of a request. |
| `principal` | _[Principal](#principal)_ | true | Principal specifies the client identity of a request.<br />If there are multiple principal types, all principals must match for the rule to match.<br />For example, if there are two principals: one for client IP and one for JWT claim,<br />the rule will match only if both the client IP and the JWT claim match. |


#### BackOffPolicy
Expand Down Expand Up @@ -2118,6 +2118,37 @@ _Appears in:_
| `providers` | _[JWTProvider](#jwtprovider) array_ | true | Providers defines the JSON Web Token (JWT) authentication provider type.<br />When multiple JWT providers are specified, the JWT is considered valid if<br />any of the providers successfully validate the JWT. For additional details,<br />see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/jwt_authn_filter.html. |


#### JWTClaim



JWTClaim specifies a claim in a JWT token.

_Appears in:_
- [JWTPrincipal](#jwtprincipal)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | _string_ | false | Name is the name of the claim.<br />If it is a nested claim, use a dot (.) separated string as the name to<br />represent the full path to the claim.<br />For example, if the claim is in the "department" field in the "organization" field,<br />the name should be "organization.department". |
| `valueType` | _[JWTClaimValueType](#jwtclaimvaluetype)_ | false | ValueType is the type of the claim value.<br />Only string and string array types are supported for now. |
| `values` | _string array_ | true | Values are the values that the claim must match.<br />If the claim is a string type, the specified value must match exactly.<br />If the claim is a string array type, the specified value must match one of the values in the array.<br />If multiple values are specified, one of the values must match for the rule to match. |


#### JWTClaimValueType

_Underlying type:_ _string_



_Appears in:_
- [JWTClaim](#jwtclaim)

| Value | Description |
| ----- | ----------- |
| `String` | |
| `StringArray` | |


#### JWTExtractor


Expand Down Expand Up @@ -2151,6 +2182,22 @@ _Appears in:_
| `valuePrefix` | _string_ | false | ValuePrefix is the prefix that should be stripped before extracting the token.<br />The format would be used by Envoy like "\{ValuePrefix\}<TOKEN>".<br />For example, "Authorization: Bearer <TOKEN>", then the ValuePrefix="Bearer " with a space at the end. |


#### JWTPrincipal



JWTPrincipal specifies the client identity of a request based on the JWT claims and scopes.
Claims and scopes are And-ed together if both are specified.

_Appears in:_
- [Principal](#principal)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `claims` | _[JWTClaim](#jwtclaim) array_ | false | Claims are the claims in a JWT token.<br /><br />If multiple claims are specified, all claims must match for the rule to match.<br />For example, if there are two claims: one for the audience and one for the issuer,<br />the rule will match only if both the audience and the issuer match. |
| `scopes` | _string array_ | true | Scopes are a special type of claim in a JWT token that represents the permissions of the client.<br /><br />EG interprets the value of the scope claim as a space-separated string during matching,<br />as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749.<br /><br />If multiple scopes are specified, all scopes must match for the rule to match. |


#### JWTProvider


Expand Down Expand Up @@ -2664,7 +2711,7 @@ _Appears in:_

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `clientCIDRs` | _[CIDR](#cidr) array_ | true | ClientCIDRs are the IP CIDR ranges of the client.<br />Valid examples are "192.168.1.0/24" or "2001:db8::/64"<br /><br />The client IP is inferred from the X-Forwarded-For header, a custom header,<br />or the proxy protocol.<br />You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in<br />the `ClientTrafficPolicy` to configure how the client IP is detected. |
| `clientCIDRs` | _[CIDR](#cidr) array_ | true | ClientCIDRs are the IP CIDR ranges of the client.<br />Valid examples are "192.168.1.0/24" or "2001:db8::/64"<br /><br />If multiple CIDR ranges are specified, one of the CIDR ranges must match<br />the client IP for the rule to match.<br /><br />The client IP is inferred from the X-Forwarded-For header, a custom header,<br />or the proxy protocol.<br />You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in<br />the `ClientTrafficPolicy` to configure how the client IP is detected. |


#### ProcessingModeOptions
Expand Down
Loading
Loading