Skip to content

Commit

Permalink
implement authorization options for kcp
Browse files Browse the repository at this point in the history
On-behalf-of: @SAP christoph.mewes@sap.com
  • Loading branch information
xrstf committed Feb 4, 2025
1 parent 8d4d66b commit 2981f0c
Show file tree
Hide file tree
Showing 16 changed files with 432 additions and 12 deletions.
30 changes: 30 additions & 0 deletions config/crd/bases/operator.kcp.io_rootshards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,36 @@ spec:
type: string
type: object
type: object
authorization:
properties:
webhook:
properties:
allowPaths:
description: |-
A list of HTTP paths to skip during authorization, i.e. these are authorized without contacting the 'core' kubernetes server.
If specified, completely overwrites the default of [/healthz,/readyz,/livez].
items:
type: string
type: array
cacheAuthorizedTTL:
description: The duration to cache 'authorized' responses
from the webhook authorizer.
type: string
cacheUnauthorizedTTL:
description: The duration to cache 'unauthorized' responses
from the webhook authorizer.
type: string
configSecretName:
description: |-
Name of a Kubernetes Secret that contains a kubeconfig formatted file that defines the
authorization webhook configuration.
type: string
version:
description: The API version of the authorization.k8s.io SubjectAccessReview
to send to and expect from the webhook.
type: string
type: object
type: object
cache:
description: Cache configures the cache server (with a Kubernetes-like
API) used by a sharded kcp instance.
Expand Down
30 changes: 30 additions & 0 deletions config/crd/bases/operator.kcp.io_shards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,36 @@ spec:
type: string
type: object
type: object
authorization:
properties:
webhook:
properties:
allowPaths:
description: |-
A list of HTTP paths to skip during authorization, i.e. these are authorized without contacting the 'core' kubernetes server.
If specified, completely overwrites the default of [/healthz,/readyz,/livez].
items:
type: string
type: array
cacheAuthorizedTTL:
description: The duration to cache 'authorized' responses
from the webhook authorizer.
type: string
cacheUnauthorizedTTL:
description: The duration to cache 'unauthorized' responses
from the webhook authorizer.
type: string
configSecretName:
description: |-
Name of a Kubernetes Secret that contains a kubeconfig formatted file that defines the
authorization webhook configuration.
type: string
version:
description: The API version of the authorization.k8s.io SubjectAccessReview
to send to and expect from the webhook.
type: string
type: object
type: object
clusterDomain:
type: string
etcd:
Expand Down
4 changes: 2 additions & 2 deletions internal/resources/rootshard/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ func DeploymentReconciler(rootShard *operatorv1alpha1.RootShard) reconciling.Nam
dep.Spec.Replicas = ptr.To[int32](2)
}

dep, err := utils.ApplyAuditConfiguration(dep, rootShard.Spec.Audit)
dep, err := utils.ApplyCommonShardConfig(dep, &rootShard.Spec.CommonShardSpec)
if err != nil {
return nil, fmt.Errorf("failed to apply audit configuration: %w", err)
return nil, fmt.Errorf("failed to shard configuration: %w", err)
}

return dep, nil
Expand Down
4 changes: 2 additions & 2 deletions internal/resources/shard/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ func DeploymentReconciler(shard *operatorv1alpha1.Shard, rootShard *operatorv1al
dep.Spec.Replicas = ptr.To[int32](2)
}

dep, err := utils.ApplyAuditConfiguration(dep, shard.Spec.Audit)
dep, err := utils.ApplyCommonShardConfig(dep, &shard.Spec.CommonShardSpec)
if err != nil {
return nil, fmt.Errorf("failed to apply audit configuration: %w", err)
return nil, fmt.Errorf("failed to shard configuration: %w", err)
}

return dep, nil
Expand Down
2 changes: 1 addition & 1 deletion internal/resources/utils/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1"
)

func ApplyAuditConfiguration(deployment *appsv1.Deployment, config *operatorv1alpha1.AuditSpec) (*appsv1.Deployment, error) {
func applyAuditConfiguration(deployment *appsv1.Deployment, config *operatorv1alpha1.AuditSpec) (*appsv1.Deployment, error) {
if len(deployment.Spec.Template.Spec.Containers) == 0 {
return deployment, errors.New("Deployment does not contain any containers")
}
Expand Down
88 changes: 88 additions & 0 deletions internal/resources/utils/authorization.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright 2025 The KCP Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package utils

import (
"errors"
"fmt"
"strings"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"

operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1"
)

func applyAuthorizationConfiguration(deployment *appsv1.Deployment, config *operatorv1alpha1.AuthorizationSpec) (*appsv1.Deployment, error) {
if len(deployment.Spec.Template.Spec.Containers) == 0 {
return deployment, errors.New("Deployment does not contain any containers")
}

if config == nil || config.Webhook == nil {
return deployment, nil
}

return applyAuthorizationWebhookConfiguration(deployment, *config.Webhook), nil
}

func applyAuthorizationWebhookConfiguration(deployment *appsv1.Deployment, config operatorv1alpha1.AuthorizationWebhookSpec) *appsv1.Deployment {
podSpec := deployment.Spec.Template.Spec

var extraArgs []string

if vals := config.AllowPaths; len(vals) > 0 {
extraArgs = append(extraArgs, fmt.Sprintf("--authorization-always-allow-paths=%s", strings.Join(vals, ",")))
}

if val := config.CacheAuthorizedTTL; val != nil {
extraArgs = append(extraArgs, fmt.Sprintf("--authorization-webhook-cache-authorized-ttl=%v", val.String()))
}

if val := config.CacheUnauthorizedTTL; val != nil {
extraArgs = append(extraArgs, fmt.Sprintf("--authorization-webhook-cache-unauthorized-ttl=%v", val.String()))
}

if val := config.Version; val != "" {
extraArgs = append(extraArgs, fmt.Sprintf("--authorization-webhook-version=%s", val))
}

if val := config.ConfigSecretName; val != "" {
volumeName := "authorization-webhook-config"
mountPath := "/etc/kcp/authorization/webhook"

extraArgs = append(extraArgs, fmt.Sprintf("--authorization-webhook-config-file=%s/kubeconfig", mountPath))
podSpec.Containers[0].VolumeMounts = append(podSpec.Containers[0].VolumeMounts, corev1.VolumeMount{
Name: volumeName,
ReadOnly: true,
MountPath: mountPath,
})

deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, corev1.Volume{
Name: volumeName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: val,
},
},
})
}

podSpec.Containers[0].Args = append(podSpec.Containers[0].Args, extraArgs...)
deployment.Spec.Template.Spec = podSpec

return deployment
}
39 changes: 39 additions & 0 deletions internal/resources/utils/shard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright 2025 The KCP Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package utils

import (
"fmt"

appsv1 "k8s.io/api/apps/v1"

operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1"
)

func ApplyCommonShardConfig(deployment *appsv1.Deployment, spec *operatorv1alpha1.CommonShardSpec) (*appsv1.Deployment, error) {
deployment, err := applyAuditConfiguration(deployment, spec.Audit)
if err != nil {
return nil, fmt.Errorf("failed to apply audit configuration: %w", err)
}

deployment, err = applyAuthorizationConfiguration(deployment, spec.Authorization)
if err != nil {
return nil, fmt.Errorf("failed to apply authorization configuration: %w", err)
}

return deployment, nil
}
22 changes: 21 additions & 1 deletion sdk/apis/operator/v1alpha1/shard_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type CommonShardSpec struct {
// Replicas configures how many instances of this shard run in parallel. Defaults to 2 if not set.
Replicas *int32 `json:"replicas,omitempty"`

Audit *AuditSpec `json:"audit,omitempty"`
Audit *AuditSpec `json:"audit,omitempty"`
Authorization *AuthorizationSpec `json:"authorization,omitempty"`
}

type AuditSpec struct {
Expand Down Expand Up @@ -94,6 +95,25 @@ type AuditWebhookSpec struct {
Version string `json:"version,omitempty"`
}

type AuthorizationSpec struct {
Webhook *AuthorizationWebhookSpec `json:"webhook,omitempty"`
}

type AuthorizationWebhookSpec struct {
// A list of HTTP paths to skip during authorization, i.e. these are authorized without contacting the 'core' kubernetes server.
// If specified, completely overwrites the default of [/healthz,/readyz,/livez].
AllowPaths []string `json:"allowPaths,omitempty"`
// The duration to cache 'authorized' responses from the webhook authorizer.
CacheAuthorizedTTL *metav1.Duration `json:"cacheAuthorizedTTL,omitempty"`
// The duration to cache 'unauthorized' responses from the webhook authorizer.
CacheUnauthorizedTTL *metav1.Duration `json:"cacheUnauthorizedTTL,omitempty"`
// Name of a Kubernetes Secret that contains a kubeconfig formatted file that defines the
// authorization webhook configuration.
ConfigSecretName string `json:"configSecretName,omitempty"`
// The API version of the authorization.k8s.io SubjectAccessReview to send to and expect from the webhook.
Version string `json:"version,omitempty"`
}

// ShardStatus defines the observed state of Shard
type ShardStatus struct {
Phase ShardPhase `json:"phase,omitempty"`
Expand Down
55 changes: 55 additions & 0 deletions sdk/apis/operator/v1alpha1/zz_generated.deepcopy.go

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

39 changes: 39 additions & 0 deletions sdk/applyconfiguration/operator/v1alpha1/authorizationspec.go

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

Loading

0 comments on commit 2981f0c

Please sign in to comment.