diff --git a/api/v1alpha1/client_helpers.go b/api/v1alpha1/client_helpers.go new file mode 100644 index 00000000..ab2a1356 --- /dev/null +++ b/api/v1alpha1/client_helpers.go @@ -0,0 +1,11 @@ +package v1alpha1 + +import "strings" + +func (c *Client) Username(prefix string) string { + if c.Spec.Username != nil { + return *c.Spec.Username + } else { + return prefix + strings.Join([]string{"client", c.Namespace, c.Name, string(c.UID)}, ":") + } +} diff --git a/api/v1alpha1/exporter_helpers.go b/api/v1alpha1/exporter_helpers.go new file mode 100644 index 00000000..4448e038 --- /dev/null +++ b/api/v1alpha1/exporter_helpers.go @@ -0,0 +1,11 @@ +package v1alpha1 + +import "strings" + +func (e *Exporter) Username(prefix string) string { + if e.Spec.Username != nil { + return *e.Spec.Username + } else { + return prefix + strings.Join([]string{"exporter", e.Namespace, e.Name, string(e.UID)}, ":") + } +} diff --git a/internal/authorization/basic.go b/internal/authorization/basic.go index 1d2f55c0..e11061a4 100644 --- a/internal/authorization/basic.go +++ b/internal/authorization/basic.go @@ -13,7 +13,7 @@ type BasicAuthorizer struct { prefix string } -func NewBasicAuthorizer(reader client.Reader, prefix string) *BasicAuthorizer { +func NewBasicAuthorizer(reader client.Reader, prefix string) authorizer.Authorizer { return &BasicAuthorizer{reader: reader, prefix: prefix} } @@ -30,7 +30,7 @@ func (b *BasicAuthorizer) Authorize( }, &e); err != nil { return authorizer.DecisionDeny, "failed to get exporter", err } - if ExporterAuthorizedUsername(&e, b.prefix) == attributes.GetUser().GetName() { + if e.Username(b.prefix) == attributes.GetUser().GetName() { return authorizer.DecisionAllow, "", nil } else { return authorizer.DecisionDeny, "", nil @@ -43,7 +43,7 @@ func (b *BasicAuthorizer) Authorize( }, &c); err != nil { return authorizer.DecisionDeny, "failed to get client", err } - if ClientAuthorizedUsername(&c, b.prefix) == attributes.GetUser().GetName() { + if c.Username(b.prefix) == attributes.GetUser().GetName() { return authorizer.DecisionAllow, "", nil } else { return authorizer.DecisionDeny, "", nil @@ -52,21 +52,3 @@ func (b *BasicAuthorizer) Authorize( return authorizer.DecisionDeny, "invalid object kind", nil } } - -func ClientAuthorizedUsername(c *jumpstarterdevv1alpha1.Client, prefix string) string { - if c.Spec.Username == nil { - return prefix + "client:" + c.Namespace + ":" + c.Name + ":" + string(c.UID) - } else { - return *c.Spec.Username - } -} - -func ExporterAuthorizedUsername(e *jumpstarterdevv1alpha1.Exporter, prefix string) string { - if e.Spec.Username == nil { - return prefix + "exporter:" + e.Namespace + ":" + e.Name + ":" + string(e.UID) - } else { - return *e.Spec.Username - } -} - -var _ = authorizer.Authorizer(&BasicAuthorizer{}) diff --git a/internal/controller/client_controller.go b/internal/controller/client_controller.go index cd24cf7b..7b26cf30 100644 --- a/internal/controller/client_controller.go +++ b/internal/controller/client_controller.go @@ -30,7 +30,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter-controller/api/v1alpha1" - "github.com/jumpstarter-dev/jumpstarter-controller/internal/authorization" "github.com/jumpstarter-dev/jumpstarter-controller/internal/oidc" ) @@ -94,7 +93,7 @@ func (r *ClientReconciler) clientSecretExists( } token, ok := secret.Data["token"] - if !ok || r.Signer.Verify(string(token)) != nil { + if !ok || r.Signer.UnsafeValidate(string(token)) != nil { logger.Info("reconcileStatusCredential: the client secret is invalid", "client", client.Name) return false, r.Delete(ctx, secret) } @@ -153,7 +152,7 @@ func (r *ClientReconciler) reconcileStatusEndpoint( } func (r *ClientReconciler) secretForClient(client *jumpstarterdevv1alpha1.Client) (*corev1.Secret, error) { - token, err := r.Signer.Token(authorization.ClientAuthorizedUsername(client, r.Signer.Prefix())) + token, err := r.Signer.Token(client.Username(r.Signer.Prefix())) if err != nil { return nil, err } diff --git a/internal/controller/exporter_controller.go b/internal/controller/exporter_controller.go index cff156aa..d8d8a3a9 100644 --- a/internal/controller/exporter_controller.go +++ b/internal/controller/exporter_controller.go @@ -29,7 +29,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" jumpstarterdevv1alpha1 "github.com/jumpstarter-dev/jumpstarter-controller/api/v1alpha1" - "github.com/jumpstarter-dev/jumpstarter-controller/internal/authorization" "github.com/jumpstarter-dev/jumpstarter-controller/internal/oidc" ) @@ -107,7 +106,7 @@ func (r *ExporterReconciler) exporterSecretExists( token, ok := secret.Data["token"] - if !ok || r.Signer.Verify(string(token)) != nil { + if !ok || r.Signer.UnsafeValidate(string(token)) != nil { logger.Info("reconcileStatusCredential: the exporter secret is invalid", "exporter", exporter.Name) return false, r.Delete(ctx, secret) } @@ -193,7 +192,7 @@ func (r *ExporterReconciler) reconcileStatusEndpoint( } func (r *ExporterReconciler) secretForExporter(exporter *jumpstarterdevv1alpha1.Exporter) (*corev1.Secret, error) { - token, err := r.Signer.Token(authorization.ExporterAuthorizedUsername(exporter, r.Signer.Prefix())) + token, err := r.Signer.Token(exporter.Username(r.Signer.Prefix())) if err != nil { return nil, err } diff --git a/internal/oidc/op.go b/internal/oidc/op.go index 1d426e88..7589898a 100644 --- a/internal/oidc/op.go +++ b/internal/oidc/op.go @@ -18,6 +18,8 @@ import ( "github.com/zitadel/oidc/v3/pkg/op" ) +const tokenPlaceholder string = "placeholder for external OIDC provider access token" + type Signer struct { privatekey *ecdsa.PrivateKey issuer string @@ -90,7 +92,10 @@ func (k *Signer) Register(group gin.IRoutes) { }) } -func (k *Signer) Verify(token string) error { +func (k *Signer) UnsafeValidate(token string) error { + if token == tokenPlaceholder { + return nil + } _, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) { return &k.privatekey.PublicKey, nil }, @@ -104,14 +109,14 @@ func (k *Signer) Verify(token string) error { } func (k *Signer) Token( - subject string, + username string, ) (string, error) { - if !strings.HasPrefix(subject, k.prefix) { - return "placeholder for external OIDC provider access token", nil + if !strings.HasPrefix(username, k.prefix) { + return tokenPlaceholder, nil } return jwt.NewWithClaims(jwt.SigningMethodES256, jwt.RegisteredClaims{ Issuer: k.issuer, - Subject: strings.TrimPrefix(subject, k.prefix), + Subject: strings.TrimPrefix(username, k.prefix), Audience: []string{k.audience}, IssuedAt: jwt.NewNumericDate(time.Now()), ExpiresAt: jwt.NewNumericDate(time.Now().Add(365 * 24 * time.Hour)), // FIXME: rotate keys on expiration