diff --git a/Makefile b/Makefile index de38035..4875da0 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=webhook-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=kcl-webhook-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. diff --git a/cmd/webhook-init/main.go b/cmd/webhook-init/main.go index 10c8dfa..f51f4c9 100644 --- a/cmd/webhook-init/main.go +++ b/cmd/webhook-init/main.go @@ -26,17 +26,24 @@ import ( "fmt" "math/big" "os" + "path/filepath" "time" - log "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + + "github.com/sirupsen/logrus" + kwhlogrus "github.com/slok/kubewebhook/v2/pkg/log/logrus" ) func main() { + logrusLogEntry := logrus.NewEntry(logrus.New()) + logrusLogEntry.Logger.SetLevel(logrus.DebugLevel) + logger := kwhlogrus.NewLogrus(logrusLogEntry) + var caPEM, serverCertPEM, serverPrivKeyPEM *bytes.Buffer // CA config ca := &x509.Certificate{ @@ -55,13 +62,15 @@ func main() { // CA private key caPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096) if err != nil { - fmt.Println(err) + logger.Errorf("unable to generate CA private key: %v", err) + os.Exit(1) } // Self signed CA certificate caBytes, err := x509.CreateCertificate(cryptorand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) if err != nil { - fmt.Println(err) + logger.Errorf("unable to self signed CA certificate: %v", err) + os.Exit(1) } // PEM encode CA cert @@ -115,21 +124,35 @@ func main() { Bytes: x509.MarshalPKCS1PrivateKey(serverPrivKey), }) - err = os.MkdirAll("/etc/webhook/certs/", 0666) + var ( + certsPath, _ = os.LookupEnv("CERTS_PATH") + ) + if certsPath == "" { + certsPath = "/etc/webhook/certs/" + } + + err = os.MkdirAll(certsPath, 0666) if err != nil { - log.Panic(err) + logger.Errorf("unable to mkdir %s: %v", certsPath, err) + os.Exit(1) } - err = WriteFile("/etc/webhook/certs/tls.crt", serverCertPEM) + err = WriteFile(filepath.Join(certsPath, "tls.crt"), serverCertPEM) if err != nil { - log.Panic(err) + logger.Errorf("unable to generate tls.crt: %v", err) + os.Exit(1) } - err = WriteFile("/etc/webhook/certs/tls.key", serverPrivKeyPEM) + err = WriteFile(filepath.Join(certsPath, "tls.key"), serverPrivKeyPEM) if err != nil { - log.Panic(err) + logger.Errorf("unable to generate tls.key: %v", err) + os.Exit(1) } // Create `MutatingWebhookConfiguration` resources - createMutationConfig(serverCertPEM) + err = createMutationConfig(serverCertPEM) + if err != nil { + logger.Errorf("unable to create `MutatingWebhookConfiguration` resources: %v", err) + os.Exit(1) + } } // WriteFile writes data in the file at the given path @@ -147,7 +170,9 @@ func WriteFile(filepath string, sCert *bytes.Buffer) error { return nil } -func createMutationConfig(caCert *bytes.Buffer) { +//+kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=mutatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete + +func createMutationConfig(caCert *bytes.Buffer) error { var ( webhookNamespace, _ = os.LookupEnv("WEBHOOK_NAMESPACE") mutationCfgName, _ = os.LookupEnv("MUTATE_CONFIG") @@ -156,11 +181,12 @@ func createMutationConfig(caCert *bytes.Buffer) { config := ctrl.GetConfigOrDie() kubeClient, err := kubernetes.NewForConfig(config) if err != nil { - panic("failed to set go-client") + return err } path := "/mutate" fail := admissionregistrationv1.Fail + sideEffectClassNone := admissionregistrationv1.SideEffectClassNone mutateconfig := &admissionregistrationv1.MutatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{ @@ -184,11 +210,14 @@ func createMutationConfig(caCert *bytes.Buffer) { Resources: []string{"deployments", "pods"}, }, }}, - FailurePolicy: &fail, + SideEffects: &sideEffectClassNone, + AdmissionReviewVersions: []string{"v1", "v1beta1"}, + FailurePolicy: &fail, }}, } if _, err := kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.Background(), mutateconfig, metav1.CreateOptions{}); err != nil { - panic(err) + return err } + return nil } diff --git a/config/all.yaml b/config/all.yaml index 7fbd88f..eb2c71b 100644 --- a/config/all.yaml +++ b/config/all.yaml @@ -63,21 +63,26 @@ spec: apiVersion: v1 kind: ServiceAccount metadata: - labels: - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: kcl-operator - app.kubernetes.io/instance: controller-manager - app.kubernetes.io/managed-by: kustomize - app.kubernetes.io/name: serviceaccount - app.kubernetes.io/part-of: kcl-operator - name: controller-manager + name: kcl-webhook namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: webhook-role + name: kcl-webhook-role rules: +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - krm.kcl.dev resources: @@ -108,14 +113,14 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: webhook-rolebinding + name: kcl-webhook-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: webhook-role + name: kcl-webhook-role subjects: - kind: ServiceAccount - name: default + name: kcl-webhook namespace: default --- apiVersion: v1 @@ -176,6 +181,7 @@ spec: volumeMounts: - mountPath: /etc/webhook/certs name: webhook-certs + serviceAccountName: kcl-webhook volumes: - emptyDir: {} name: webhook-certs diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 10df539..04f05ad 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -2,8 +2,20 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: webhook-role + name: kcl-webhook-role rules: +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - krm.kcl.dev resources: diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 768da9b..a0df8cf 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -1,12 +1,12 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: webhook-rolebinding + name: kcl-webhook-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: webhook-role + name: kcl-webhook-role subjects: - kind: ServiceAccount - name: default - namespace: system + name: kcl-webhook + namespace: default diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml index 7aa7bf3..77980b9 100644 --- a/config/rbac/service_account.yaml +++ b/config/rbac/service_account.yaml @@ -1,12 +1,5 @@ apiVersion: v1 kind: ServiceAccount metadata: - labels: - app.kubernetes.io/name: serviceaccount - app.kubernetes.io/instance: controller-manager - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: kcl-operator - app.kubernetes.io/part-of: kcl-operator - app.kubernetes.io/managed-by: kustomize - name: controller-manager - namespace: system + name: kcl-webhook + namespace: default diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml index 8a81591..7ae8994 100644 --- a/config/webhook/kustomization.yaml +++ b/config/webhook/kustomization.yaml @@ -3,5 +3,5 @@ resources: apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: -- name: controller +- name: kcl-webhook-server newName: kcllang/webhook-server diff --git a/config/webhook/webhook.yaml b/config/webhook/webhook.yaml index dda55b3..14b2ffa 100644 --- a/config/webhook/webhook.yaml +++ b/config/webhook/webhook.yaml @@ -14,6 +14,7 @@ spec: labels: app: kcl-webhook-server spec: + serviceAccountName: kcl-webhook initContainers: - name: kcl-webhook-init image: kcllang/webhook-init diff --git a/pkg/webhook/handler/mutation.go b/pkg/webhook/handler/mutation.go index 3b75940..0b3aefe 100644 --- a/pkg/webhook/handler/mutation.go +++ b/pkg/webhook/handler/mutation.go @@ -22,6 +22,7 @@ import ( //+kubebuilder:rbac:groups=krm.kcl.dev,resources=kclruns,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=krm.kcl.dev,resources=kclruns/status,verbs=get;update;patch //+kubebuilder:rbac:groups=krm.kcl.dev,resources=kclruns/finalizers,verbs=update +//+kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=mutatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete // MutationHandler validates Kubernetes resources using the KCL source. type MutationHandler struct { diff --git a/scripts/deploy_test.sh b/scripts/deploy_test.sh index 2c86b15..6922908 100755 --- a/scripts/deploy_test.sh +++ b/scripts/deploy_test.sh @@ -28,9 +28,48 @@ if [ $ROLLOUT_STATUS -ne 0 ]; then for pod in $PODS; do echo -e "${YELLOW}Pod: $pod${NC}" kubectl describe pod/$pod - echo -e "${YELLOW}Pod logs:${NC}" - kubectl logs $pod + echo -e "${YELLOW}Pod init container logs:${NC}" + kubectl logs $pod -c kcl-webhook-init + echo -e "${YELLOW}Pod main container logs:${NC}" + kubectl logs $pod -c kcl-webhook-server done else echo -e "${GREEN}Deployment rollout successful.${NC}" + echo -e "${YELLOW}Deploying the KCL source...${NC}" + kubectl apply -f- << EOF +apiVersion: krm.kcl.dev/v1alpha1 +kind: KCLRun +metadata: + name: set-annotation +spec: + params: + annotations: + managed-by: kcl-operator + source: | + items = [item | { + metadata.annotations: {"managed-by" = "kcl-operator"} + } for item in option("items")] +EOF + echo -e "${YELLOW}Validating the mutation result by creating a nginx Pod YAML...${NC}" + kubectl apply -f- << EOF +apiVersion: v1 +kind: Pod +metadata: + name: nginx + annotations: + app: nginx +spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 +EOF + echo -e "${YELLOW}Checking if the annotation 'managed-by: kcl-operator' is added to the pod...${NC}" + MANAGED_BY=$(kubectl get po nginx -o yaml | grep kcl-operator) + if [ -n "$MANAGED_BY" ]; then + echo -e "${GREEN}The annotation 'managed-by: kcl-operator' is added to the pod.${NC}" + else + echo -e "${RED}The annotation 'managed-by: kcl-operator' is not added to the pod.${NC}" + fi fi