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

Update ServiceIntentions CRD for JWT auth #2213

Merged
merged 2 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .changelog/2213.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
helm: Update the ServiceIntentions CRD to support `JWT` fields.
```
75 changes: 75 additions & 0 deletions charts/consul/templates/crd-serviceintentions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,43 @@ spec:
have intentions defined.
type: string
type: object
jwt:
description: JWT specifies the configuration to validate a JSON Web
Token for all incoming requests.
properties:
providers:
description: Providers is a list of providers to consider when
verifying a JWT.
items:
properties:
name:
description: Name is the name of the JWT provider. There
MUST be a corresponding "jwt-provider" config entry with
this name.
type: string
verifyClaims:
description: VerifyClaims is a list of additional claims
to verify in a JWT's payload.
items:
properties:
path:
description: Path is the path to the claim in the
token JSON.
items:
type: string
type: array
value:
description: Value is the expected value at the given
path. If the type at the path is a list then we
verify that this value is contained in the list.
If the type at the path is a string then we verify
that this value matches.
type: string
type: object
type: array
type: object
type: array
type: object
sources:
description: Sources is the list of all intention sources and the
authorization granted to those sources. The order of this list does
Expand Down Expand Up @@ -183,6 +220,44 @@ spec:
match on the HTTP request path.
type: string
type: object
jwt:
description: JWT specifies configuration to validate a
JSON Web Token for incoming requests.
properties:
providers:
description: Providers is a list of providers to consider
when verifying a JWT.
items:
properties:
name:
description: Name is the name of the JWT provider.
There MUST be a corresponding "jwt-provider"
config entry with this name.
type: string
verifyClaims:
description: VerifyClaims is a list of additional
claims to verify in a JWT's payload.
items:
properties:
path:
description: Path is the path to the claim
in the token JSON.
items:
type: string
type: array
value:
description: Value is the expected value
at the given path. If the type at the
path is a list then we verify that this
value is contained in the list. If the
type at the path is a string then we
verify that this value matches.
type: string
type: object
type: array
type: object
type: array
type: object
type: object
type: array
samenessGroup:
Expand Down
96 changes: 94 additions & 2 deletions control-plane/api/v1alpha1/serviceintentions_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type ServiceIntentionsSpec struct {
// The order of this list does not matter, but out of convenience Consul will always store this
// reverse sorted by intention precedence, as that is the order that they will be evaluated at enforcement time.
Sources SourceIntentions `json:"sources,omitempty"`
// JWT specifies the configuration to validate a JSON Web Token for all incoming requests.
JWT *IntentionJWTRequirement `json:"jwt,omitempty"`
}

type IntentionDestination struct {
Expand Down Expand Up @@ -108,6 +110,8 @@ type IntentionPermission struct {
Action IntentionAction `json:"action,omitempty"`
// HTTP is a set of HTTP-specific authorization criteria.
HTTP *IntentionHTTPPermission `json:"http,omitempty"`
// JWT specifies configuration to validate a JSON Web Token for incoming requests.
JWT *IntentionJWTRequirement `json:"jwt,omitempty"`
}

type IntentionHTTPPermission struct {
Expand Down Expand Up @@ -142,6 +146,30 @@ type IntentionHTTPHeaderPermission struct {
Invert bool `json:"invert,omitempty"`
}

type IntentionJWTRequirement struct {
// Providers is a list of providers to consider when verifying a JWT.
Providers []*IntentionJWTProvider `json:"providers,omitempty"`
}

type IntentionJWTProvider struct {
// Name is the name of the JWT provider. There MUST be a corresponding
// "jwt-provider" config entry with this name.
Name string `json:"name,omitempty"`

// VerifyClaims is a list of additional claims to verify in a JWT's payload.
VerifyClaims []*IntentionJWTClaimVerification `json:"verifyClaims,omitempty"`
}

type IntentionJWTClaimVerification struct {
// Path is the path to the claim in the token JSON.
Path []string `json:"path,omitempty"`

// Value is the expected value at the given path. If the type at the path
// is a list then we verify that this value is contained in the list. If
// the type at the path is a string then we verify that this value matches.
Value string `json:"value,omitempty"`
}

// IntentionAction is the action that the intention represents. This
// can be "allow" or "deny" to allowlist or denylist intentions.
type IntentionAction string
Expand Down Expand Up @@ -226,6 +254,7 @@ func (in *ServiceIntentions) ToConsul(datacenter string) api.ConfigEntry {
Name: in.Spec.Destination.Name,
Namespace: in.Spec.Destination.Namespace,
Sources: in.Spec.Sources.toConsul(),
JWT: in.Spec.JWT.toConsul(),
Meta: meta(datacenter),
}
}
Expand Down Expand Up @@ -297,6 +326,8 @@ func (in *ServiceIntentions) Validate(consulMeta common.ConsulMeta) error {

errs = append(errs, in.validateNamespaces(consulMeta.NamespacesEnabled)...)

errs = append(errs, in.Spec.JWT.validate(path.Child("jwt"))...)

if len(errs) > 0 {
return apierrors.NewInvalid(
schema.GroupKind{Group: ConsulHashicorpGroup, Kind: common.ServiceIntentions},
Expand Down Expand Up @@ -394,6 +425,7 @@ func (in IntentionPermissions) toConsul() []*capi.IntentionPermission {
consulIntentionPermissions = append(consulIntentionPermissions, &capi.IntentionPermission{
Action: permission.Action.toConsul(),
HTTP: permission.HTTP.toConsul(),
JWT: permission.JWT.toConsul(),
})
}
return consulIntentionPermissions
Expand Down Expand Up @@ -429,15 +461,54 @@ func (in IntentionHTTPHeaderPermissions) toConsul() []capi.IntentionHTTPHeaderPe
return headerPermissions
}

func (in *IntentionJWTRequirement) toConsul() *capi.IntentionJWTRequirement {
if in == nil {
return nil
}
var providers []*capi.IntentionJWTProvider
for _, p := range in.Providers {
providers = append(providers, p.toConsul())
}
return &capi.IntentionJWTRequirement{
Providers: providers,
}
}

func (in *IntentionJWTProvider) toConsul() *capi.IntentionJWTProvider {
if in == nil {
return nil
}
var claims []*capi.IntentionJWTClaimVerification
for _, c := range in.VerifyClaims {
claims = append(claims, c.toConsul())
}
return &capi.IntentionJWTProvider{
Name: in.Name,
VerifyClaims: claims,
}
}

func (in *IntentionJWTClaimVerification) toConsul() *capi.IntentionJWTClaimVerification {
if in == nil {
return nil
}
return &capi.IntentionJWTClaimVerification{
Path: in.Path,
Value: in.Value,
}
}

func (in IntentionPermissions) validate(path *field.Path) field.ErrorList {
var errs field.ErrorList
for i, permission := range in {
if err := permission.Action.validate(path.Child("permissions").Index(i)); err != nil {
permPath := path.Child("permissions").Index(i)
if err := permission.Action.validate(permPath); err != nil {
errs = append(errs, err)
}
if permission.HTTP != nil {
errs = append(errs, permission.HTTP.validate(path.Child("permissions").Index(i))...)
errs = append(errs, permission.HTTP.validate(permPath)...)
}
errs = append(errs, permission.JWT.validate(permPath.Child("jwt"))...)
}
return errs
}
Expand Down Expand Up @@ -540,6 +611,27 @@ func numNotEmpty(ss ...string) int {
return count
}

func (in *IntentionJWTRequirement) validate(path *field.Path) field.ErrorList {
var errs field.ErrorList
if in == nil {
return errs
}

for i, p := range in.Providers {
if err := p.validate(path.Child("providers").Index(i)); err != nil {
errs = append(errs, err)
}
}
return errs
}

func (in *IntentionJWTProvider) validate(path *field.Path) *field.Error {
if in != nil && in.Name == "" {
return field.Invalid(path.Child("name"), in.Name, "JWT provider name is required")
}
return nil
}

// sourceIntentionSortKey returns a string that can be used to sort intention
// sources.
func sourceIntentionSortKey(ixn *capi.SourceIntention) string {
Expand Down
Loading