From 97b0be41c5b58483405054ca03cf36d59e72a9a5 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Thu, 6 Feb 2025 13:59:42 -0500 Subject: [PATCH 1/7] Prepare for authorization configuration --- .../authorizationconfiguration_types.go | 16 +++++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 24 +++++++++++++++++++ .../templates/cms/controller-cm.yaml | 1 + deploy/helm/jumpstarter/values.yaml | 4 ++++ internal/config/config.go | 5 ++++ 5 files changed, 50 insertions(+) create mode 100644 api/v1alpha1/authorizationconfiguration_types.go diff --git a/api/v1alpha1/authorizationconfiguration_types.go b/api/v1alpha1/authorizationconfiguration_types.go new file mode 100644 index 00000000..1d5a3a38 --- /dev/null +++ b/api/v1alpha1/authorizationconfiguration_types.go @@ -0,0 +1,16 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AuthorizationConfiguration provides versioned configuration for authorization. +type AuthorizationConfiguration struct { + metav1.TypeMeta +} + +func init() { + SchemeBuilder.Register(&AuthorizationConfiguration{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7f21fd38..d46dec01 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -58,6 +58,30 @@ func (in *AuthenticationConfiguration) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthorizationConfiguration) DeepCopyInto(out *AuthorizationConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationConfiguration. +func (in *AuthorizationConfiguration) DeepCopy() *AuthorizationConfiguration { + if in == nil { + return nil + } + out := new(AuthorizationConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AuthorizationConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Client) DeepCopyInto(out *Client) { *out = *in diff --git a/deploy/helm/jumpstarter/charts/jumpstarter-controller/templates/cms/controller-cm.yaml b/deploy/helm/jumpstarter/charts/jumpstarter-controller/templates/cms/controller-cm.yaml index 620f09f0..facc3573 100644 --- a/deploy/helm/jumpstarter/charts/jumpstarter-controller/templates/cms/controller-cm.yaml +++ b/deploy/helm/jumpstarter/charts/jumpstarter-controller/templates/cms/controller-cm.yaml @@ -11,3 +11,4 @@ metadata: {{ end }} data: authentication: {{- .Values.authenticationConfig | toYaml | indent 1 }} + authorization: {{- .Values.authorizationConfig | toYaml | indent 1 }} diff --git a/deploy/helm/jumpstarter/values.yaml b/deploy/helm/jumpstarter/values.yaml index 6b90c60c..19481729 100644 --- a/deploy/helm/jumpstarter/values.yaml +++ b/deploy/helm/jumpstarter/values.yaml @@ -94,6 +94,10 @@ jumpstarter-controller: # claim: "sub" # prefix: "" + authorizationConfig: | + apiVersion: jumpstarter.dev/v1alpha1 + kind: authorizationConfiguration + grpc: hostname: "" routerHostname: "" diff --git a/internal/config/config.go b/internal/config/config.go index f16255c0..57878376 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -24,6 +24,11 @@ func LoadConfiguration( return nil, err } + _, ok := configmap.Data["authorization"] + if !ok { + return nil, fmt.Errorf("LoadConfiguration: missing authorization section") + } + rawAuthenticationConfiguration, ok := configmap.Data["authentication"] if !ok { return nil, fmt.Errorf("LoadConfiguration: missing authentication section") From 9fed5a83f3a14ba10abd1c998cd2bf80b6745625 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Thu, 6 Feb 2025 15:11:13 -0500 Subject: [PATCH 2/7] Implement CEL based authorizer --- cmd/main.go | 8 ++- internal/authorization/cel.go | 123 ++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 internal/authorization/cel.go diff --git a/cmd/main.go b/cmd/main.go index bfe1c8b0..74a50986 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -202,11 +202,17 @@ func main() { os.Exit(1) } + authz, err := authorization.NewCELAuthorizer(watchClient, oidcSigner.Prefix()) + if err != nil { + setupLog.Error(err, "unable to construct cel authorizer") + os.Exit(1) + } + if err = (&service.ControllerService{ Client: watchClient, Scheme: mgr.GetScheme(), Authn: authentication.NewBearerTokenAuthenticator(authenticator), - Authz: authorization.NewBasicAuthorizer(watchClient, oidcSigner.Prefix()), + Authz: authz, Attr: authorization.NewMetadataAttributesGetter(authorization.MetadataAttributesGetterConfig{ NamespaceKey: "jumpstarter-namespace", ResourceKey: "jumpstarter-kind", diff --git a/internal/authorization/cel.go b/internal/authorization/cel.go new file mode 100644 index 00000000..97989419 --- /dev/null +++ b/internal/authorization/cel.go @@ -0,0 +1,123 @@ +package authorization + +import ( + "context" + "fmt" + + celgo "github.com/google/cel-go/cel" + jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter-controller/api/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/authorization/authorizer" + "k8s.io/apiserver/pkg/authorization/cel" + "k8s.io/apiserver/pkg/cel/environment" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type CELAuthorizer struct { + reader client.Reader + prefix string + compiler cel.Compiler +} +type Expression struct { + Expression string +} + +func (v *Expression) GetExpression() string { + return v.Expression +} + +func (v *Expression) ReturnTypes() []*celgo.Type { + return []*celgo.Type{celgo.BoolType} +} + +func NewCELAuthorizer(reader client.Reader, prefix string) (authorizer.Authorizer, error) { + env, err := environment.MustBaseEnvSet( + environment.DefaultCompatibilityVersion(), + false, + ).Extend(environment.VersionedOptions{ + IntroducedVersion: environment.DefaultCompatibilityVersion(), + EnvOptions: []celgo.EnvOption{ + celgo.Variable("self", celgo.DynType), + celgo.Variable("user", celgo.DynType), + celgo.Variable("prefix", celgo.StringType), + }, + }) + if err != nil { + return nil, err + } + + compiler := cel.NewCompiler(env) + + return &CELAuthorizer{ + reader: reader, + prefix: prefix, + compiler: compiler, + }, nil +} + +func (b *CELAuthorizer) Authorize( + ctx context.Context, + attributes authorizer.Attributes, +) (authorizer.Decision, string, error) { + var self map[string]interface{} + var err error + + switch attributes.GetResource() { + case "Exporter": + var e jumpstarterdevv1alpha1.Exporter + if err := b.reader.Get(ctx, client.ObjectKey{ + Namespace: attributes.GetNamespace(), + Name: attributes.GetName(), + }, &e); err != nil { + return authorizer.DecisionDeny, "failed to get exporter", err + } + self, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&e) + if err != nil { + return authorizer.DecisionDeny, "failed to serialize exporter", err + } + case "Client": + var c jumpstarterdevv1alpha1.Client + if err := b.reader.Get(ctx, client.ObjectKey{ + Namespace: attributes.GetNamespace(), + Name: attributes.GetName(), + }, &c); err != nil { + return authorizer.DecisionDeny, "failed to get client", err + } + self, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&c) + if err != nil { + return authorizer.DecisionDeny, "failed to serialize client", err + } + default: + return authorizer.DecisionDeny, "invalid object kind", nil + } + + compiled, err := b.compiler.CompileCELExpression(&Expression{ + Expression: "(has(self.spec.username) ? self.spec.username : prefix + self.kind.lowerAscii() + ':' + self.metadata.namespace + ':' + self.metadata.name + ':' + self.metadata.uid) == user.username", + }) + + user := attributes.GetUser() + value, _, err := compiled.Program.Eval(map[string]any{ + "self": self, + "user": map[string]any{ + "username": user.GetName(), + "uid": user.GetUID(), + "groups": user.GetGroups(), + "extra": user.GetExtra(), + }, + "prefix": b.prefix, + }) + if err != nil { + return authorizer.DecisionDeny, "failed to evaluate expression", err + } + + result, ok := value.Value().(bool) + if !ok { + return authorizer.DecisionDeny, "failed to evaluate expression", fmt.Errorf("result type mismatch") + } + + if result { + return authorizer.DecisionAllow, "", nil + } else { + return authorizer.DecisionDeny, "permission denied", nil + } +} From b153a1ab08e9e8791461e8671b70fe5d6840815d Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Thu, 6 Feb 2025 15:17:53 -0500 Subject: [PATCH 3/7] Pass object type to cel script --- internal/authorization/cel.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/authorization/cel.go b/internal/authorization/cel.go index 97989419..4b0e6618 100644 --- a/internal/authorization/cel.go +++ b/internal/authorization/cel.go @@ -40,6 +40,7 @@ func NewCELAuthorizer(reader client.Reader, prefix string) (authorizer.Authorize celgo.Variable("self", celgo.DynType), celgo.Variable("user", celgo.DynType), celgo.Variable("prefix", celgo.StringType), + celgo.Variable("kind", celgo.StringType), }, }) if err != nil { @@ -92,7 +93,7 @@ func (b *CELAuthorizer) Authorize( } compiled, err := b.compiler.CompileCELExpression(&Expression{ - Expression: "(has(self.spec.username) ? self.spec.username : prefix + self.kind.lowerAscii() + ':' + self.metadata.namespace + ':' + self.metadata.name + ':' + self.metadata.uid) == user.username", + Expression: "(has(self.spec.username) ? self.spec.username : prefix + kind.lowerAscii() + ':' + self.metadata.namespace + ':' + self.metadata.name + ':' + self.metadata.uid) == user.username", }) user := attributes.GetUser() @@ -105,6 +106,7 @@ func (b *CELAuthorizer) Authorize( "extra": user.GetExtra(), }, "prefix": b.prefix, + "kind": attributes.GetResource(), }) if err != nil { return authorizer.DecisionDeny, "failed to evaluate expression", err From 857ff4a76a01f6cb3842579864731ce4e29b5f27 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Mon, 17 Feb 2025 16:26:04 -0500 Subject: [PATCH 4/7] Implement loading authorization configuration --- .../authorizationconfiguration_types.go | 6 +++ api/v1alpha1/zz_generated.deepcopy.go | 20 +++++++++ cmd/main.go | 10 +---- deploy/helm/jumpstarter/values.yaml | 6 ++- internal/authorization/cel.go | 27 ++++++------ internal/authorization/config.go | 42 +++++++++++++++++++ internal/config/config.go | 35 +++++++++++----- 7 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 internal/authorization/config.go diff --git a/api/v1alpha1/authorizationconfiguration_types.go b/api/v1alpha1/authorizationconfiguration_types.go index 1d5a3a38..f53ea6be 100644 --- a/api/v1alpha1/authorizationconfiguration_types.go +++ b/api/v1alpha1/authorizationconfiguration_types.go @@ -9,6 +9,12 @@ import ( // AuthorizationConfiguration provides versioned configuration for authorization. type AuthorizationConfiguration struct { metav1.TypeMeta + Type string `json:"type"` + CEL *CELConfiguration `json:"cel,omitempty"` +} + +type CELConfiguration struct { + Expression string `json:"expression"` } func init() { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index d46dec01..4fcc5071 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -62,6 +62,11 @@ func (in *AuthenticationConfiguration) DeepCopyObject() runtime.Object { func (in *AuthorizationConfiguration) DeepCopyInto(out *AuthorizationConfiguration) { *out = *in out.TypeMeta = in.TypeMeta + if in.CEL != nil { + in, out := &in.CEL, &out.CEL + *out = new(CELConfiguration) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthorizationConfiguration. @@ -82,6 +87,21 @@ func (in *AuthorizationConfiguration) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CELConfiguration) DeepCopyInto(out *CELConfiguration) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CELConfiguration. +func (in *CELConfiguration) DeepCopy() *CELConfiguration { + if in == nil { + return nil + } + out := new(CELConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Client) DeepCopyInto(out *Client) { *out = *in diff --git a/cmd/main.go b/cmd/main.go index 74a50986..6dfea9b2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -152,7 +152,7 @@ func main() { os.Exit(1) } - authenticator, err := config.LoadConfiguration( + authenticator, authorizer, err := config.LoadConfiguration( context.Background(), mgr.GetAPIReader(), mgr.GetScheme(), @@ -202,17 +202,11 @@ func main() { os.Exit(1) } - authz, err := authorization.NewCELAuthorizer(watchClient, oidcSigner.Prefix()) - if err != nil { - setupLog.Error(err, "unable to construct cel authorizer") - os.Exit(1) - } - if err = (&service.ControllerService{ Client: watchClient, Scheme: mgr.GetScheme(), Authn: authentication.NewBearerTokenAuthenticator(authenticator), - Authz: authz, + Authz: authorizer, Attr: authorization.NewMetadataAttributesGetter(authorization.MetadataAttributesGetterConfig{ NamespaceKey: "jumpstarter-namespace", ResourceKey: "jumpstarter-kind", diff --git a/deploy/helm/jumpstarter/values.yaml b/deploy/helm/jumpstarter/values.yaml index 19481729..edaccb28 100644 --- a/deploy/helm/jumpstarter/values.yaml +++ b/deploy/helm/jumpstarter/values.yaml @@ -96,7 +96,11 @@ jumpstarter-controller: authorizationConfig: | apiVersion: jumpstarter.dev/v1alpha1 - kind: authorizationConfiguration + kind: AuthorizationConfiguration + type: CEL + cel: + expression: (has(self.spec.username) ? self.spec.username : prefix + kind.lowerAscii() + ':' + self.metadat +a.namespace + ':' + self.metadata.name + ':' + self.metadata.uid) == user.username grpc: hostname: "" diff --git a/internal/authorization/cel.go b/internal/authorization/cel.go index 4b0e6618..d0c6c32e 100644 --- a/internal/authorization/cel.go +++ b/internal/authorization/cel.go @@ -14,9 +14,9 @@ import ( ) type CELAuthorizer struct { - reader client.Reader - prefix string - compiler cel.Compiler + reader client.Reader + prefix string + program celgo.Program } type Expression struct { Expression string @@ -30,7 +30,7 @@ func (v *Expression) ReturnTypes() []*celgo.Type { return []*celgo.Type{celgo.BoolType} } -func NewCELAuthorizer(reader client.Reader, prefix string) (authorizer.Authorizer, error) { +func NewCELAuthorizer(reader client.Reader, prefix string, expression string) (authorizer.Authorizer, error) { env, err := environment.MustBaseEnvSet( environment.DefaultCompatibilityVersion(), false, @@ -49,10 +49,17 @@ func NewCELAuthorizer(reader client.Reader, prefix string) (authorizer.Authorize compiler := cel.NewCompiler(env) + compiled, err := compiler.CompileCELExpression(&Expression{ + Expression: expression, + }) + if err != nil { + return nil, err + } + return &CELAuthorizer{ - reader: reader, - prefix: prefix, - compiler: compiler, + reader: reader, + prefix: prefix, + program: compiled.Program, }, nil } @@ -92,12 +99,8 @@ func (b *CELAuthorizer) Authorize( return authorizer.DecisionDeny, "invalid object kind", nil } - compiled, err := b.compiler.CompileCELExpression(&Expression{ - Expression: "(has(self.spec.username) ? self.spec.username : prefix + kind.lowerAscii() + ':' + self.metadata.namespace + ':' + self.metadata.name + ':' + self.metadata.uid) == user.username", - }) - user := attributes.GetUser() - value, _, err := compiled.Program.Eval(map[string]any{ + value, _, err := b.program.Eval(map[string]any{ "self": self, "user": map[string]any{ "username": user.GetName(), diff --git a/internal/authorization/config.go b/internal/authorization/config.go new file mode 100644 index 00000000..ae1a3a0e --- /dev/null +++ b/internal/authorization/config.go @@ -0,0 +1,42 @@ +package authorization + +import ( + "context" + "fmt" + + jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter-controller/api/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apiserver/pkg/authorization/authorizer" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func LoadAuthorizationConfiguration( + ctx context.Context, + scheme *runtime.Scheme, + configuration []byte, + reader client.Reader, + prefix string, +) (authorizer.Authorizer, error) { + var authorizationConfiguration jumpstarterdevv1alpha1.AuthorizationConfiguration + if err := runtime.DecodeInto( + serializer.NewCodecFactory(scheme, serializer.EnableStrict). + UniversalDecoder(jumpstarterdevv1alpha1.GroupVersion), + configuration, + &authorizationConfiguration, + ); err != nil { + return nil, err + } + + switch authorizationConfiguration.Type { + case "Basic": + return NewBasicAuthorizer(reader, prefix), nil + case "CEL": + if authorizationConfiguration.CEL == nil { + return nil, fmt.Errorf("CEL authorizer configuration missing") + } + return NewCELAuthorizer(reader, prefix, authorizationConfiguration.CEL.Expression) + default: + return nil, fmt.Errorf("unsupported authorizer type: %s", authorizationConfiguration.Type) + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 57878376..eca6b948 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,10 +4,12 @@ import ( "context" "fmt" + "github.com/jumpstarter-dev/jumpstarter-controller/internal/authorization" "github.com/jumpstarter-dev/jumpstarter-controller/internal/oidc" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authorization/authorizer" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -18,23 +20,18 @@ func LoadConfiguration( key client.ObjectKey, signer *oidc.Signer, certificateAuthority string, -) (authenticator.Token, error) { +) (authenticator.Token, authorizer.Authorizer, error) { var configmap corev1.ConfigMap if err := client.Get(ctx, key, &configmap); err != nil { - return nil, err - } - - _, ok := configmap.Data["authorization"] - if !ok { - return nil, fmt.Errorf("LoadConfiguration: missing authorization section") + return nil, nil, err } rawAuthenticationConfiguration, ok := configmap.Data["authentication"] if !ok { - return nil, fmt.Errorf("LoadConfiguration: missing authentication section") + return nil, nil, fmt.Errorf("LoadConfiguration: missing authentication section") } - authenticator, err := oidc.LoadAuthenticationConfiguration( + authn, err := oidc.LoadAuthenticationConfiguration( ctx, scheme, []byte(rawAuthenticationConfiguration), @@ -42,8 +39,24 @@ func LoadConfiguration( certificateAuthority, ) if err != nil { - return nil, err + return nil, nil, err + } + + rawAuthorizationConfiguration, ok := configmap.Data["authorization"] + if !ok { + return nil, nil, fmt.Errorf("LoadConfiguration: missing authorization section") + } + + authz, err := authorization.LoadAuthorizationConfiguration( + ctx, + scheme, + []byte(rawAuthorizationConfiguration), + client, + signer.Prefix(), + ) + if err != nil { + return nil, nil, err } - return authenticator, nil + return authn, authz, nil } From 0c9f00937948e3dba8ac6116445c1143231c7419 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Mon, 17 Feb 2025 16:29:13 -0500 Subject: [PATCH 5/7] Simplify config loading --- cmd/main.go | 7 +++---- deploy/helm/jumpstarter/values.yaml | 4 ++-- internal/config/config.go | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 6dfea9b2..19f5b0a2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -40,7 +40,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter-controller/api/v1alpha1" - "github.com/jumpstarter-dev/jumpstarter-controller/internal/authentication" "github.com/jumpstarter-dev/jumpstarter-controller/internal/authorization" "github.com/jumpstarter-dev/jumpstarter-controller/internal/config" "github.com/jumpstarter-dev/jumpstarter-controller/internal/controller" @@ -152,7 +151,7 @@ func main() { os.Exit(1) } - authenticator, authorizer, err := config.LoadConfiguration( + authn, authz, err := config.LoadConfiguration( context.Background(), mgr.GetAPIReader(), mgr.GetScheme(), @@ -205,8 +204,8 @@ func main() { if err = (&service.ControllerService{ Client: watchClient, Scheme: mgr.GetScheme(), - Authn: authentication.NewBearerTokenAuthenticator(authenticator), - Authz: authorizer, + Authn: authn, + Authz: authz, Attr: authorization.NewMetadataAttributesGetter(authorization.MetadataAttributesGetterConfig{ NamespaceKey: "jumpstarter-namespace", ResourceKey: "jumpstarter-kind", diff --git a/deploy/helm/jumpstarter/values.yaml b/deploy/helm/jumpstarter/values.yaml index edaccb28..29faa89e 100644 --- a/deploy/helm/jumpstarter/values.yaml +++ b/deploy/helm/jumpstarter/values.yaml @@ -99,8 +99,8 @@ jumpstarter-controller: kind: AuthorizationConfiguration type: CEL cel: - expression: (has(self.spec.username) ? self.spec.username : prefix + kind.lowerAscii() + ':' + self.metadat -a.namespace + ':' + self.metadata.name + ':' + self.metadata.uid) == user.username + expression: "(has(self.spec.username) ? self.spec.username : prefix + kind.lowerAscii() + ':' + self.metadat +a.namespace + ':' + self.metadata.name + ':' + self.metadata.uid) == user.username" grpc: hostname: "" diff --git a/internal/config/config.go b/internal/config/config.go index eca6b948..3a773e84 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,11 +4,11 @@ import ( "context" "fmt" + "github.com/jumpstarter-dev/jumpstarter-controller/internal/authentication" "github.com/jumpstarter-dev/jumpstarter-controller/internal/authorization" "github.com/jumpstarter-dev/jumpstarter-controller/internal/oidc" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authorization/authorizer" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -20,7 +20,7 @@ func LoadConfiguration( key client.ObjectKey, signer *oidc.Signer, certificateAuthority string, -) (authenticator.Token, authorizer.Authorizer, error) { +) (authentication.ContextAuthenticator, authorizer.Authorizer, error) { var configmap corev1.ConfigMap if err := client.Get(ctx, key, &configmap); err != nil { return nil, nil, err @@ -58,5 +58,5 @@ func LoadConfiguration( return nil, nil, err } - return authn, authz, nil + return authentication.NewBearerTokenAuthenticator(authn), authz, nil } From 6fafdedfeca3d072896357918966984421a4eb06 Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Mon, 17 Feb 2025 16:32:39 -0500 Subject: [PATCH 6/7] Fill username field on object --- deploy/helm/jumpstarter/values.yaml | 3 +-- internal/authorization/cel.go | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/deploy/helm/jumpstarter/values.yaml b/deploy/helm/jumpstarter/values.yaml index 29faa89e..7d75a795 100644 --- a/deploy/helm/jumpstarter/values.yaml +++ b/deploy/helm/jumpstarter/values.yaml @@ -99,8 +99,7 @@ jumpstarter-controller: kind: AuthorizationConfiguration type: CEL cel: - expression: "(has(self.spec.username) ? self.spec.username : prefix + kind.lowerAscii() + ':' + self.metadat -a.namespace + ':' + self.metadata.name + ':' + self.metadata.uid) == user.username" + expression: "self.spec.username == user.username" grpc: hostname: "" diff --git a/internal/authorization/cel.go b/internal/authorization/cel.go index d0c6c32e..92d42004 100644 --- a/internal/authorization/cel.go +++ b/internal/authorization/cel.go @@ -83,6 +83,7 @@ func (b *CELAuthorizer) Authorize( if err != nil { return authorizer.DecisionDeny, "failed to serialize exporter", err } + self["spec"].(map[string]any)["username"] = e.Username(b.prefix) case "Client": var c jumpstarterdevv1alpha1.Client if err := b.reader.Get(ctx, client.ObjectKey{ @@ -95,6 +96,7 @@ func (b *CELAuthorizer) Authorize( if err != nil { return authorizer.DecisionDeny, "failed to serialize client", err } + self["spec"].(map[string]any)["username"] = c.Username(b.prefix) default: return authorizer.DecisionDeny, "invalid object kind", nil } From ce035dc76a8e554a4d764dbf4c3585043e73556b Mon Sep 17 00:00:00 2001 From: Nick Cao Date: Mon, 17 Feb 2025 16:34:22 -0500 Subject: [PATCH 7/7] Drop prefix from cel context --- internal/authorization/cel.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/authorization/cel.go b/internal/authorization/cel.go index 92d42004..88861092 100644 --- a/internal/authorization/cel.go +++ b/internal/authorization/cel.go @@ -37,10 +37,9 @@ func NewCELAuthorizer(reader client.Reader, prefix string, expression string) (a ).Extend(environment.VersionedOptions{ IntroducedVersion: environment.DefaultCompatibilityVersion(), EnvOptions: []celgo.EnvOption{ + celgo.Variable("kind", celgo.StringType), celgo.Variable("self", celgo.DynType), celgo.Variable("user", celgo.DynType), - celgo.Variable("prefix", celgo.StringType), - celgo.Variable("kind", celgo.StringType), }, }) if err != nil { @@ -103,6 +102,7 @@ func (b *CELAuthorizer) Authorize( user := attributes.GetUser() value, _, err := b.program.Eval(map[string]any{ + "kind": attributes.GetResource(), "self": self, "user": map[string]any{ "username": user.GetName(), @@ -110,8 +110,6 @@ func (b *CELAuthorizer) Authorize( "groups": user.GetGroups(), "extra": user.GetExtra(), }, - "prefix": b.prefix, - "kind": attributes.GetResource(), }) if err != nil { return authorizer.DecisionDeny, "failed to evaluate expression", err