Skip to content

Commit

Permalink
operator: Add defaulting webhook for Enterprise fields
Browse files Browse the repository at this point in the history
- Update Console validating webhook to mutating webhook
- Refactor required keys used for validating/mutating to vars
  • Loading branch information
pvsune committed Sep 5, 2022
1 parent d8fc43b commit c06ec16
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 32 deletions.
30 changes: 15 additions & 15 deletions src/go/k8s/config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,15 @@ webhooks:
resources:
- clusters
sideEffects: None

---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
creationTimestamp: null
name: validating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate-redpanda-vectorized-io-v1alpha1-cluster
path: /mutate-redpanda-vectorized-io-v1alpha1-console
failurePolicy: Fail
name: vcluster.kb.io
name: mconsole.kb.io
rules:
- apiGroups:
- redpanda.vectorized.io
Expand All @@ -54,17 +45,26 @@ webhooks:
- CREATE
- UPDATE
resources:
- clusters
- consoles
sideEffects: None

---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
creationTimestamp: null
name: validating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
name: webhook-service
namespace: system
path: /validate-redpanda-vectorized-io-v1alpha1-console
path: /validate-redpanda-vectorized-io-v1alpha1-cluster
failurePolicy: Fail
name: vconsole.kb.io
name: vcluster.kb.io
rules:
- apiGroups:
- redpanda.vectorized.io
Expand All @@ -74,5 +74,5 @@ webhooks:
- CREATE
- UPDATE
resources:
- consoles
- clusters
sideEffects: None
2 changes: 1 addition & 1 deletion src/go/k8s/controllers/redpanda/console_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ var _ = Describe("Console controller", func() {
It("Should create Enterprise fields in ConfigMap", func() {
var (
rbacName = fmt.Sprintf("%s-rbac", ConsoleName)
rbacDataKey = "rbac.yaml"
rbacDataKey = consolepkg.EnterpriseRBACDataKey
rbacDataVal = `roleBindings:
- roleName: admin
metadata:
Expand Down
2 changes: 1 addition & 1 deletion src/go/k8s/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func main() {
os.Exit(1)
}
hookServer := mgr.GetWebhookServer()
hookServer.Register("/validate-redpanda-vectorized-io-v1alpha1-console", &webhook.Admission{Handler: &redpandawebhooks.ConsoleValidator{Client: mgr.GetClient()}})
hookServer.Register("/mutate-redpanda-vectorized-io-v1alpha1-console", &webhook.Admission{Handler: &redpandawebhooks.ConsoleHandler{Client: mgr.GetClient()}})
}

if err = (&redpandacontrollers.ConsoleReconciler{
Expand Down
21 changes: 15 additions & 6 deletions src/go/k8s/pkg/console/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,25 @@ func (cm *ConfigMap) genEnterprise() (e Enterprise) {
return Enterprise{
RBAC: EnterpriseRBAC{
Enabled: cm.consoleobj.Spec.Enterprise.RBAC.Enabled,
RoleBindingsFilepath: fmt.Sprintf("%s/%s", enterpriseRBACMountPath, "rbac.yaml"),
RoleBindingsFilepath: fmt.Sprintf("%s/%s", enterpriseRBACMountPath, EnterpriseRBACDataKey),
},
}
}
return e
}

var (
defaultLicenseSecretKey = "license"
defaultJWTSecretKey = "jwt"
// DefaultLicenseSecretKey is the defautl key in Enterprise License SecretKeyRef
DefaultLicenseSecretKey = "license"

// DefaultJWTSecretKey is the defautl key in Enterprise JWT SecretKeyRef
DefaultJWTSecretKey = "jwt"

// EnterpriseRBACDataKey is the required key in Enterprise RBAC
EnterpriseRBACDataKey = "rbac.yaml"

// EnterpriseGoogleSADataKey is the required key in EnterpriseLoginGoogle SA
EnterpriseGoogleSADataKey = "sa.json"
)

func (cm *ConfigMap) genLogin(ctx context.Context) (e EnterpriseLogin, err error) {
Expand All @@ -183,7 +192,7 @@ func (cm *ConfigMap) genLogin(ctx context.Context) (e EnterpriseLogin, err error
if err != nil {
return e, err
}
jwt, err := provider.JWTSecretRef.GetValue(jwtSecret, defaultJWTSecretKey)
jwt, err := provider.JWTSecretRef.GetValue(jwtSecret, DefaultJWTSecretKey)
if err != nil {
return e, err
}
Expand Down Expand Up @@ -212,7 +221,7 @@ func (cm *ConfigMap) genLogin(ctx context.Context) (e EnterpriseLogin, err error
}
if dir := provider.Google.Directory; dir != nil {
enterpriseLogin.Google.Directory = &EnterpriseLoginGoogleDirectory{
ServiceAccountFilepath: fmt.Sprintf("%s/%s", enterpriseGoogleSAMountPath, "sa.json"),
ServiceAccountFilepath: fmt.Sprintf("%s/%s", enterpriseGoogleSAMountPath, EnterpriseGoogleSADataKey),
TargetPrincipal: provider.Google.Directory.TargetPrincipal,
}
}
Expand All @@ -228,7 +237,7 @@ func (cm *ConfigMap) genLicense(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
licenseValue, err := license.GetValue(licenseSecret, defaultLicenseSecretKey)
licenseValue, err := license.GetValue(licenseSecret, DefaultLicenseSecretKey)
if err != nil {
return "", err
}
Expand Down
63 changes: 54 additions & 9 deletions src/go/k8s/webhooks/redpanda/console_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,35 @@ package redpanda

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"

redpandav1alpha1 "github.com/redpanda-data/redpanda/src/go/k8s/apis/redpanda/v1alpha1"
consolepkg "github.com/redpanda-data/redpanda/src/go/k8s/pkg/console"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// +kubebuilder:webhook:path=/validate-redpanda-vectorized-io-v1alpha1-console,mutating=false,failurePolicy=fail,sideEffects=None,groups="redpanda.vectorized.io",resources=consoles,verbs=create;update,versions=v1alpha1,name=vconsole.kb.io,admissionReviewVersions=v1
// +kubebuilder:webhook:path=/mutate-redpanda-vectorized-io-v1alpha1-console,mutating=true,failurePolicy=fail,sideEffects=None,groups="redpanda.vectorized.io",resources=consoles,verbs=create;update,versions=v1alpha1,name=mconsole.kb.io,admissionReviewVersions=v1

// ConsoleValidator validates Consoles
type ConsoleValidator struct {
// ConsoleHandler implements admission.Handler
// It creates a webhook by using the lower level webhook handler
// REF https://github.com/kubernetes-sigs/kubebuilder/issues/1216
type ConsoleHandler struct {
Client client.Client
decoder *admission.Decoder
}

// Handle processes admission for Console
func (v *ConsoleValidator) Handle(
func (v *ConsoleHandler) Handle(
ctx context.Context, req admission.Request, // nolint:gocritic // interface not require pointer
) admission.Response {
console := &redpandav1alpha1.Console{}

err := v.decoder.Decode(req, console)
if err != nil {
if err := v.decoder.Decode(req, console); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

Expand All @@ -43,14 +47,55 @@ func (v *ConsoleValidator) Handle(
return admission.Errored(http.StatusBadRequest, err)
}

return admission.Allowed("")
if err := ValidateEnterpriseRBAC(ctx, v.Client, console); err != nil {
if errors.Is(err, ErrEnterpriseRBACDataKeyNotFound) {
return admission.Denied(fmt.Sprintf("configmap %s/%s %s", console.GetNamespace(), console.Spec.Enterprise.RBAC.RoleBindingsRef.Name, err))
}
return admission.Errored(http.StatusBadRequest, err)
}

if err := ValidateEnterpriseGoogleSA(ctx, v.Client, console); err != nil {
if errors.Is(err, ErrEnterpriseGoogleSADataKeyNotFound) {
return admission.Denied(fmt.Sprintf("configmap %s/%s %s", console.GetNamespace(), console.Spec.Login.Google.Directory.ServiceAccountRef.Name, err))
}
return admission.Errored(http.StatusBadRequest, err)
}

// Implement defaulting
response, err := Default(console)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
return *response
}

// Default implements admission defaulting
func Default(console *redpandav1alpha1.Console) (*admission.Response, error) {
original, err := json.Marshal(console.DeepCopy())
if err != nil {
return nil, err
}

if login := console.Spec.Login; login != nil && login.JWTSecretRef.Key == "" {
login.JWTSecretRef.Key = consolepkg.DefaultJWTSecretKey
}
if license := console.Spec.LicenseRef; license != nil && license.Key == "" {
license.Key = consolepkg.DefaultLicenseSecretKey
}

current, err := json.Marshal(console)
if err != nil {
return nil, err
}
response := admission.PatchResponseFromRaw(original, current)
return &response, nil
}

// ConsoleValidator implements admission.DecoderInjector.
// ConsoleHandler implements admission.DecoderInjector.
// A decoder will be automatically injected.

// InjectDecoder injects the decoder.
func (v *ConsoleValidator) InjectDecoder(d *admission.Decoder) error {
func (v *ConsoleHandler) InjectDecoder(d *admission.Decoder) error {
v.decoder = d
return nil
}
48 changes: 48 additions & 0 deletions src/go/k8s/webhooks/redpanda/validate_enterprise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Package redpanda defines Webhooks for redpanda API group
package redpanda

import (
"context"
"fmt"

redpandav1alpha1 "github.com/redpanda-data/redpanda/src/go/k8s/apis/redpanda/v1alpha1"
consolepkg "github.com/redpanda-data/redpanda/src/go/k8s/pkg/console"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
// ErrEnterpriseRBACDataKeyNotFound error when "rbac.yaml" not found in RBAC ref
ErrEnterpriseRBACDataKeyNotFound = fmt.Errorf("must contain key '%s'", consolepkg.EnterpriseRBACDataKey)

// ErrEnterpriseGoogleSADataKeyNotFound error when "sa.json" not found in Google SA ref
ErrEnterpriseGoogleSADataKeyNotFound = fmt.Errorf("must contain key '%s'", consolepkg.EnterpriseGoogleSADataKey)
)

// ValidateEnterpriseRBAC validates the referenced RBAC ConfigMap
func ValidateEnterpriseRBAC(ctx context.Context, cl client.Client, console *redpandav1alpha1.Console) error {
if enterprise := console.Spec.Enterprise; enterprise != nil {
configmap := &corev1.ConfigMap{}
if err := cl.Get(ctx, client.ObjectKey{Namespace: console.GetNamespace(), Name: enterprise.RBAC.RoleBindingsRef.Name}, configmap); err != nil {
return err
}
if _, ok := configmap.Data[consolepkg.EnterpriseRBACDataKey]; !ok {
return ErrEnterpriseRBACDataKeyNotFound
}
}
return nil
}

// ValidateEnterpriseGoogleSA validates the referenced Google SA ConfigMap
func ValidateEnterpriseGoogleSA(ctx context.Context, cl client.Client, console *redpandav1alpha1.Console) error {
if login := console.Spec.Login; login != nil && login.Google != nil && login.Google.Directory != nil {
configmap := &corev1.ConfigMap{}
if err := cl.Get(ctx, client.ObjectKey{Namespace: console.GetNamespace(), Name: login.Google.Directory.ServiceAccountRef.Name}, configmap); err != nil {
return err
}
if _, ok := configmap.Data[consolepkg.EnterpriseGoogleSADataKey]; !ok {
return ErrEnterpriseGoogleSADataKeyNotFound
}
}
return nil
}

0 comments on commit c06ec16

Please sign in to comment.