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 22 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
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`.
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.
// 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.
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.
//
// 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"`
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".
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"`
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
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
Loading