Skip to content

Commit

Permalink
fix: add authorizationGroups property to K8s SAR authorization, fixes #…
Browse files Browse the repository at this point in the history
…506

Signed-off-by: Dhiraj Bokde <[email protected]>
  • Loading branch information
dhirajsb committed Nov 20, 2024
1 parent ca4e46e commit 5cbdfea
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 15 deletions.
3 changes: 3 additions & 0 deletions api/v1beta3/auth_config_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,9 @@ type KubernetesSubjectAccessReviewAuthorizationSpec struct {
// Groups the user must be a member of or, if `user` is omitted, the groups to check for authorization in the Kubernetes RBAC.
Groups []string `json:"groups,omitempty"`

// AuthorizationGroups is a value or selector to use as groups to check for authorization in the Kubernetes RBAC.
AuthorizationGroups *ValueOrSelector `json:"authorizationGroups,omitempty"`

// Use resourceAttributes to check permissions on Kubernetes resources.
// If omitted, it performs a non-resource SubjectAccessReview, with verb and path inferred from the request.
// +optional
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta3/zz_generated.deepcopy.go

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

9 changes: 8 additions & 1 deletion controllers/auth_config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,14 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf
}
}

translatedAuthorization.KubernetesAuthz, err = authorization_evaluators.NewKubernetesAuthz(authorinoUser, authorization.KubernetesSubjectAccessReview.Groups, authorinoResourceAttributes)
var authorinoGroups expressions.Value
if authorization.KubernetesSubjectAccessReview.AuthorizationGroups != nil {
authorinoGroups, err = valueFrom(authorization.KubernetesSubjectAccessReview.AuthorizationGroups)
if err != nil {
return nil, err
}
}
translatedAuthorization.KubernetesAuthz, err = authorization_evaluators.NewKubernetesAuthz(authorinoUser, authorization.KubernetesSubjectAccessReview.Groups, authorinoGroups, authorinoResourceAttributes)
if err != nil {
return nil, err
}
Expand Down
17 changes: 17 additions & 0 deletions install/crd/authorino.kuadrant.io_authconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2796,6 +2796,23 @@ spec:
kubernetesSubjectAccessReview:
description: Authorization by Kubernetes SubjectAccessReview
properties:
authorizationGroups:
description: AuthorizationGroups is a value or selector
to use as groups to check for authorization in the Kubernetes
RBAC.
properties:
expression:
type: string
selector:
description: |-
Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!").
Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used.
The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip.
type: string
value:
description: Static value
x-kubernetes-preserve-unknown-fields: true
type: object
groups:
description: Groups the user must be a member of or, if
`user` is omitted, the groups to check for authorization
Expand Down
17 changes: 17 additions & 0 deletions install/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3104,6 +3104,23 @@ spec:
kubernetesSubjectAccessReview:
description: Authorization by Kubernetes SubjectAccessReview
properties:
authorizationGroups:
description: AuthorizationGroups is a value or selector
to use as groups to check for authorization in the Kubernetes
RBAC.
properties:
expression:
type: string
selector:
description: |-
Simple path selector to fetch content from the authorization JSON (e.g. 'request.method') or a string template with variables that resolve to patterns (e.g. "Hello, {auth.identity.name}!").
Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used.
The following Authorino custom modifiers are supported: @extract:{sep:" ",pos:0}, @replace{old:"",new:""}, @case:upper|lower, @base64:encode|decode and @strip.
type: string
value:
description: Static value
x-kubernetes-preserve-unknown-fields: true
type: object
groups:
description: Groups the user must be a member of or, if
`user` is omitted, the groups to check for authorization
Expand Down
34 changes: 26 additions & 8 deletions pkg/evaluators/authorization/kubernetes_authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package authorization

import (
gocontext "context"
gojson "encoding/json"
"fmt"
"strings"

Expand All @@ -22,7 +23,7 @@ type kubernetesSubjectAccessReviewer interface {
SubjectAccessReviews() kubeAuthzClient.SubjectAccessReviewInterface
}

func NewKubernetesAuthz(user expressions.Value, groups []string, resourceAttributes *KubernetesAuthzResourceAttributes) (*KubernetesAuthz, error) {
func NewKubernetesAuthz(user expressions.Value, groups []string, authorizationGroups expressions.Value, resourceAttributes *KubernetesAuthzResourceAttributes) (*KubernetesAuthz, error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, err
Expand All @@ -34,10 +35,11 @@ func NewKubernetesAuthz(user expressions.Value, groups []string, resourceAttribu
}

return &KubernetesAuthz{
User: user,
Groups: groups,
ResourceAttributes: resourceAttributes,
authorizer: k8sClient.AuthorizationV1(),
User: user,
Groups: groups,
AuthorizationGroups: authorizationGroups,
ResourceAttributes: resourceAttributes,
authorizer: k8sClient.AuthorizationV1(),
}, nil
}

Expand All @@ -51,9 +53,10 @@ type KubernetesAuthzResourceAttributes struct {
}

type KubernetesAuthz struct {
User expressions.Value
Groups []string
ResourceAttributes *KubernetesAuthzResourceAttributes
User expressions.Value
Groups []string
AuthorizationGroups expressions.Value
ResourceAttributes *KubernetesAuthzResourceAttributes

authorizer kubernetesSubjectAccessReviewer
}
Expand Down Expand Up @@ -131,6 +134,21 @@ func (k *KubernetesAuthz) Call(pipeline auth.AuthPipeline, ctx gocontext.Context

if len(k.Groups) > 0 {
subjectAccessReview.Spec.Groups = k.Groups
} else if k.AuthorizationGroups != nil {
resolvedValue, err := k.AuthorizationGroups.ResolveFor(authJSON)
if err != nil {
return nil, err
}
stringJson, err := json.StringifyJSON(resolvedValue)
if err != nil {
return nil, err
}
var resolvedGroups []string
err = gojson.Unmarshal([]byte(stringJson), &resolvedGroups)
if err != nil {
return nil, err
}
subjectAccessReview.Spec.Groups = resolvedGroups
}

log.FromContext(ctx).WithName("kubernetesauthz").V(1).Info("calling kubernetes subject access review api", "subjectaccessreview", subjectAccessReview)
Expand Down
17 changes: 11 additions & 6 deletions pkg/evaluators/authorization/kubernetes_authz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ func (client *k8sAuthorizationClientMock) GetRequest() kubeAuthz.SubjectAccessRe
return client.request
}

func newKubernetesAuthz(user expressions.Value, groups []string, resourceAttributes *KubernetesAuthzResourceAttributes, subjectAccessReviewResponseStatus kubeAuthz.SubjectAccessReviewStatus) *KubernetesAuthz {
func newKubernetesAuthz(user expressions.Value, groups []string, authorizationGroups expressions.Value, resourceAttributes *KubernetesAuthzResourceAttributes, subjectAccessReviewResponseStatus kubeAuthz.SubjectAccessReviewStatus) *KubernetesAuthz {
return &KubernetesAuthz{
User: user,
Groups: groups,
ResourceAttributes: resourceAttributes,
User: user,
Groups: groups,
AuthorizationGroups: authorizationGroups,
ResourceAttributes: resourceAttributes,

// mock the authorizer so we can control the response
authorizer: &k8sAuthorizationClientMock{SubjectAccessReviewStatus: subjectAccessReviewResponseStatus},
Expand All @@ -75,14 +76,15 @@ func TestKubernetesAuthzNonResource_Allowed(t *testing.T) {
defer ctrl.Finish()

pipelineMock := mock_auth.NewMockAuthPipeline(ctrl)
pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http":{"method":"GET","path":"/hello"}}},"auth":{"identity":{"username":"john"}}}`)
pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http":{"method":"GET","path":"/hello"}}},"auth":{"identity":{"username":"john", "groups":["group1","group2"]}}}`)

request := &envoy_auth.AttributeContext_HttpRequest{Method: "GET", Path: "/hello"}
pipelineMock.EXPECT().GetHttp().Return(request)

kubernetesAuth := newKubernetesAuthz(
&json.JSONValue{Pattern: "auth.identity.username"},
[]string{},
&json.JSONValue{Pattern: "auth.identity.groups"},
nil,
kubeAuthz.SubjectAccessReviewStatus{Allowed: true, Reason: ""},
)
Expand Down Expand Up @@ -112,6 +114,7 @@ func TestKubernetesAuthzNonResource_Denied(t *testing.T) {
&json.JSONValue{Pattern: "auth.identity.username"},
[]string{},
nil,
nil,
kubeAuthz.SubjectAccessReviewStatus{Allowed: false, Reason: "some-reason"},
)
authorized, err := kubernetesAuth.Call(pipelineMock, context.TODO())
Expand All @@ -131,11 +134,12 @@ func TestKubernetesAuthzResource_Allowed(t *testing.T) {
defer ctrl.Finish()

pipelineMock := mock_auth.NewMockAuthPipeline(ctrl)
pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http":{"method":"GET","path":"/hello"}}},"auth":{"identity":{"username":"john"}}}`)
pipelineMock.EXPECT().GetAuthorizationJSON().Return(`{"context":{"request":{"http":{"method":"GET","path":"/hello"}}},"auth":{"identity":{"username":"john", "groups":["group1","group2"]}}}`)

kubernetesAuth := newKubernetesAuthz(
&json.JSONValue{Pattern: "auth.identity.username"},
[]string{},
&json.JSONValue{Pattern: "auth.identity.groups"},
&KubernetesAuthzResourceAttributes{Namespace: &json.JSONValue{Static: "default"}},
kubeAuthz.SubjectAccessReviewStatus{Allowed: true, Reason: ""},
)
Expand All @@ -160,6 +164,7 @@ func TestKubernetesAuthzResource_Denied(t *testing.T) {
kubernetesAuth := newKubernetesAuthz(
&json.JSONValue{Pattern: "auth.identity.username"},
[]string{},
nil,
&KubernetesAuthzResourceAttributes{Namespace: &json.JSONValue{Static: "default"}},
kubeAuthz.SubjectAccessReviewStatus{Allowed: false, Reason: "some-reason"},
)
Expand Down

0 comments on commit 5cbdfea

Please sign in to comment.