diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 36d9e04..3051923 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -512,12 +512,12 @@ jobs: args: - webhook env: - - name: TLS-CERT + - name: TLS_CERT valueFrom: secretKeyRef: name: ziti-webhook-server-cert key: tls.crt - - name: TLS-PRIVATE-KEY + - name: TLS_PRIVATE_KEY valueFrom: secretKeyRef: name: ziti-webhook-server-cert diff --git a/.gitignore b/.gitignore index 6d6f2eb..c48dfc1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ # local temp files /ziti-agent.yaml +/ziti-client.yaml \ No newline at end of file diff --git a/BUILD.md b/BUILD.md index d65ecfd..ae8716e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -7,28 +7,31 @@ docker build --tag ziti-k8s-agent:local --load . ``` -## Override the Default Image +## Load the Image for Local Development + +Load the image in your development environment. + +### KIND ```bash -export ZITI_AGENT_IMAGE=ziti-k8s-agent:local +kind load docker-image ziti-k8s-agent:local ``` -## Regenerate the Manifest +### Minikube ```bash -./generate-ziti-webhook-spec.bash +minikube image load ziti-k8s-agent:local ``` -## Load the Image for Local Development - -### KIND +## Override the Default Image ```bash -kind load docker-image ziti-k8s-agent:local +export ZITI_AGENT_IMAGE=ziti-k8s-agent:local +export ZITI_AGENT_IMAGE_PULL_POLICY=Never ``` -### Minikube +## Regenerate the Manifest ```bash -minikube image load ziti-k8s-agent:local +./generate-ziti-webhook-spec.bash ``` diff --git a/demo/README.md b/demo/README.md index ad6a432..ac6237a 100644 --- a/demo/README.md +++ b/demo/README.md @@ -3498,12 +3498,12 @@ spec: args: - webhook env: - - name: TLS-CERT + - name: TLS_CERT valueFrom: secretKeyRef: name: ziti-webhook-server-cert key: tls.crt - - name: TLS-PRIVATE-KEY + - name: TLS_PRIVATE_KEY valueFrom: secretKeyRef: name: ziti-webhook-server-cert diff --git a/deployment/ziti-agent.yaml b/deployment/ziti-agent.yaml deleted file mode 100644 index d3cf72e..0000000 --- a/deployment/ziti-agent.yaml +++ /dev/null @@ -1,210 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: $WEBHOOK_NAMESPACE - ---- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - name: selfsigned-issuer - namespace: $WEBHOOK_NAMESPACE -spec: - selfSigned: {} - ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: ziti-admission-cert - namespace: $WEBHOOK_NAMESPACE -spec: - secretName: ziti-webhook-server-cert - duration: 2160h # 90d - renewBefore: 360h # 15d - subject: - organizations: - - netfoundry - commonName: ziti-admission-service.$WEBHOOK_NAMESPACE.svc - isCA: false - privateKey: - algorithm: RSA - encoding: PKCS1 - size: 2048 - rotationPolicy: Always - usages: - - server auth - - client auth - dnsNames: - - ziti-admission-service.$WEBHOOK_NAMESPACE.svc.cluster.local - - ziti-admission-service.$WEBHOOK_NAMESPACE.svc - issuerRef: - kind: Issuer - name: selfsigned-issuer - ---- -apiVersion: v1 -kind: Service -metadata: - name: ziti-admission-service - namespace: $WEBHOOK_NAMESPACE -spec: - selector: - app: ziti-admission-webhook - ports: - - name: https - protocol: TCP - port: 443 - targetPort: 9443 - type: ClusterIP - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: ziti-admission-wh-deployment - namespace: $WEBHOOK_NAMESPACE -spec: - replicas: 1 - selector: - matchLabels: - app: ziti-admission-webhook - template: - metadata: - labels: - app: ziti-admission-webhook - spec: - containers: - - name: ziti-admission-webhook - image: docker.io/netfoundry/ziti-k8s-agent:latest - imagePullPolicy: Always - ports: - - containerPort: 9443 - args: - - webhook - env: - - name: TLS-CERT - valueFrom: - secretKeyRef: - name: ziti-webhook-server-cert - key: tls.crt - - name: TLS-PRIVATE-KEY - valueFrom: - secretKeyRef: - name: ziti-webhook-server-cert - key: tls.key - - name: ZITI_CTRL_MGMT_API - valueFrom: - configMapKeyRef: - name: ziti-ctrl-cfg - key: zitiMgmtApi - - name: ZITI_CTRL_ADMIN_CERT - valueFrom: - secretKeyRef: - name: ziti-ctrl-tls - key: tls.crt - - name: ZITI_CTRL_ADMIN_KEY - valueFrom: - secretKeyRef: - name: ziti-ctrl-tls - key: tls.key - - name: ZITI_ROLE_KEY - valueFrom: - configMapKeyRef: - name: ziti-ctrl-cfg - key: zitiRoleKey - - name: POD_SECURITY_CONTEXT_OVERRIDE - valueFrom: - configMapKeyRef: - name: ziti-ctrl-cfg - key: podSecurityContextOverride - - name: SEARCH_DOMAIN_LIST - valueFrom: - configMapKeyRef: - name: ziti-ctrl-cfg - key: SearchDomainList - ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - name: ziti-tunnel-sidecar - annotations: - cert-manager.io/inject-ca-from: $WEBHOOK_NAMESPACE/ziti-admission-cert -webhooks: - - name: tunnel.ziti.webhook - admissionReviewVersions: ["v1"] - namespaceSelector: - matchLabels: - openziti/ziti-tunnel: namespace - # objectSelector: - # matchLabels: - # openziti/ziti-tunnel: pod - rules: - - operations: ["CREATE","UPDATE","DELETE"] - apiGroups: [""] - apiVersions: ["v1","v1beta1"] - resources: ["pods"] - scope: "*" - clientConfig: - service: - name: ziti-admission-service - namespace: $WEBHOOK_NAMESPACE - port: 443 - path: "/ziti-tunnel" - caBundle: "" - sideEffects: None - timeoutSeconds: 30 - ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - namespace: $WEBHOOK_NAMESPACE - name: ziti-agent-wh-roles -rules: -- apiGroups: [""] # "" indicates the core API group - resources: ["secrets"] - verbs: ["get", "list", "create", "delete"] -- apiGroups: [""] - resources: ["services"] - verbs: ["get"] - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: ziti-agent-wh -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: ziti-agent-wh-roles -subjects: -- kind: ServiceAccount - name: default - namespace: $WEBHOOK_NAMESPACE - ---- -apiVersion: v1 -kind: Secret -metadata: - name: ziti-ctrl-tls - namespace: $WEBHOOK_NAMESPACE -type: kubernetes.io/tls -stringData: - tls.crt: $NF_ADMIN_IDENTITY_CERT - tls.key: $NF_ADMIN_IDENTITY_KEY - tls.ca: $NF_ADMIN_IDENTITY_CA - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: ziti-ctrl-cfg - namespace: $WEBHOOK_NAMESPACE -data: - zitiMgmtApi: $CTRL_MGMT_API - zitiRoleKey: identity.openziti.io/role-attributes - podSecurityContextOverride: "true" - SearchDomainList: "" diff --git a/generate-ziti-agent-manifest.bash b/generate-ziti-agent-manifest.bash index 7542fd1..69365fb 100755 --- a/generate-ziti-agent-manifest.bash +++ b/generate-ziti-agent-manifest.bash @@ -24,7 +24,7 @@ IDENTITY_CERT=$(jq -r '.id.cert' "$IDENTITY_FILE" | sed -E 's/^pem://' | base64 IDENTITY_KEY=$(jq -r '.id.key' "$IDENTITY_FILE" | sed -E 's/^pem://' | base64 -w0) IDENTITY_CA=$(jq -r '.id.ca' "$IDENTITY_FILE" | sed -E 's/^pem://' | base64 -w0) -cat < ziti-agent.yaml +cat <| ziti-agent.yaml --- apiVersion: cert-manager.io/v1 @@ -103,18 +103,19 @@ spec: containers: - name: ziti-admission-webhook image: ${ZITI_AGENT_IMAGE:-docker.io/netfoundry/ziti-k8s-agent} - imagePullPolicy: IfNotPresent + imagePullPolicy: ${ZITI_AGENT_IMAGE_PULL_POLICY:-IfNotPresent} ports: - containerPort: 9443 - args: - - webhook + args: [ "webhook" ] env: - - name: TLS-CERT + - name: ZITI_AGENT_LOG_LEVEL + value: ${ZITI_AGENT_LOG_LEVEL:-info} + - name: TLS_CERT valueFrom: secretKeyRef: name: ziti-webhook-server-cert key: tls.crt - - name: TLS-PRIVATE-KEY + - name: TLS_PRIVATE_KEY valueFrom: secretKeyRef: name: ziti-webhook-server-cert diff --git a/ziti-agent/cmd/webhook/config.go b/ziti-agent/cmd/webhook/config.go index b67d16e..1d2a68d 100644 --- a/ziti-agent/cmd/webhook/config.go +++ b/ziti-agent/cmd/webhook/config.go @@ -21,7 +21,7 @@ type MissingCmdLineVarError struct { func configTLS(cert, key []byte) *tls.Config { sCert, err := tls.X509KeyPair(cert, key) if err != nil { - klog.Fatalf("Failed to load the 509x certs %v", err) + klog.Fatalf("Failed to load the webhook server's x509 cert %v", err) } return &tls.Config{ Certificates: []tls.Certificate{sCert}, @@ -37,150 +37,125 @@ func (e *MissingCmdLineVarError) Error() string { } func lookupEnvVars() { - // Declare variables - var value string - var ok bool - var cert []byte - var key []byte - var port int - var sidecarImage string - var sidecarImageVersion string - var sidecarPrefix string - var zitiCtrlMgmtApi string - var zitiAdminCert []byte - var zitiAdminKey []byte - var clusterDnsServiceIP string - var searchDomains []string - var zitiRoleKey string - // Environmental Variables to override the commandline inputs - value, ok = os.LookupEnv("TLS-CERT") + value, ok := os.LookupEnv("TLS_CERT") if ok && len(value) > 0 { cert = []byte(value) - } else { - if cert == nil { - klog.Error(&MissingEnvVarError{variable: "TLS-CERT"}) - klog.Error(&MissingCmdLineVarError{variable: "TLS-CERT"}) - } + } + if len(cert) == 0 { + klog.Error(&MissingEnvVarError{variable: "TLS_CERT"}) + klog.Error(&MissingCmdLineVarError{variable: "TLS_CERT"}) } - value, ok = os.LookupEnv("TLS-PRIVATE-KEY") + value, ok = os.LookupEnv("TLS_PRIVATE_KEY") if ok && len(value) > 0 { key = []byte(value) - } else { - if key == nil { - klog.Error(&MissingEnvVarError{variable: "TLS-PRIVATE-KEY"}) - klog.Error(&MissingCmdLineVarError{variable: "TLS-PRIVATE-KEY"}) - } + } + if len(key) == 0 { + klog.Error(&MissingEnvVarError{variable: "TLS_PRIVATE_KEY"}) + klog.Error(&MissingCmdLineVarError{variable: "TLS_PRIVATE_KEY"}) } value, ok = os.LookupEnv("PORT") if ok && len(value) > 0 { - port, _ = strconv.Atoi(value) - } else { - if port <= 0 { - klog.Error(&MissingEnvVarError{variable: "PORT"}) - klog.Error(&MissingCmdLineVarError{variable: "PORT"}) + var err error + port, err = strconv.Atoi(value) + if err != nil { + klog.Info(err) } } + if port == 0 { + klog.Error(&MissingEnvVarError{variable: "PORT"}) + klog.Error(&MissingCmdLineVarError{variable: "PORT"}) + } value, ok = os.LookupEnv("SIDECAR_IMAGE") if ok && len(value) > 0 { sidecarImage = value - } else { - if len(sidecarImage) == 0 { - klog.Error(&MissingEnvVarError{variable: "SIDECAR_IMAGE"}) - klog.Error(&MissingCmdLineVarError{variable: "SIDECAR_IMAGE"}) - } + } + if len(sidecarImage) == 0 { + klog.Error(&MissingEnvVarError{variable: "SIDECAR_IMAGE"}) + klog.Error(&MissingCmdLineVarError{variable: "SIDECAR_IMAGE"}) } value, ok = os.LookupEnv("SIDECAR_IMAGE_VERSION") if ok && len(value) > 0 { sidecarImageVersion = value - } else { - if len(sidecarImageVersion) == 0 { - klog.Error(&MissingEnvVarError{variable: "SIDECAR_IMAGE_VERSION"}) - klog.Error(&MissingCmdLineVarError{variable: "SIDECAR_IMAGE_VERSION"}) - } + } + if len(sidecarImageVersion) == 0 { + klog.Error(&MissingEnvVarError{variable: "SIDECAR_IMAGE_VERSION"}) + klog.Error(&MissingCmdLineVarError{variable: "SIDECAR_IMAGE_VERSION"}) } value, ok = os.LookupEnv("SIDECAR_PREFIX") if ok && len(value) > 0 { sidecarPrefix = value - } else { - if len(sidecarPrefix) == 0 { - klog.Error(&MissingEnvVarError{variable: "SIDECAR_PREFIX"}) - klog.Error(&MissingCmdLineVarError{variable: "SIDECAR_PREFIX"}) - } + } + if len(sidecarPrefix) == 0 { + klog.Error(&MissingEnvVarError{variable: "SIDECAR_PREFIX"}) + klog.Error(&MissingCmdLineVarError{variable: "SIDECAR_PREFIX"}) } value, ok = os.LookupEnv("ZITI_CTRL_MGMT_API") if ok && len(value) > 0 { zitiCtrlMgmtApi = value - } else { - if len(zitiCtrlMgmtApi) == 0 { - klog.Error(&MissingEnvVarError{variable: "ZITI_CTRL_MGMT_API"}) - klog.Error(&MissingCmdLineVarError{variable: "ZITI_CTRL_MGMT_API"}) - } + } + if len(zitiCtrlMgmtApi) == 0 { + klog.Error(&MissingEnvVarError{variable: "ZITI_CTRL_MGMT_API"}) + klog.Error(&MissingCmdLineVarError{variable: "ZITI_CTRL_MGMT_API"}) } - value, ok = os.LookupEnv("ZITI_CTRL_ADMIN_CERT") + value, ok = os.LookupEnv("ZITI_ADMIN_CERT") if ok && len(value) > 0 { zitiAdminCert = []byte(value) - } else { - if zitiAdminCert == nil { - klog.Error(&MissingEnvVarError{variable: "ZITI_CTRL_ADMIN_CERT"}) - klog.Error(&MissingCmdLineVarError{variable: "ZITI_CTRL_ADMIN_CERT"}) - } + } + if zitiAdminCert == nil { + klog.Error(&MissingEnvVarError{variable: "ZITI_ADMIN_CERT"}) + klog.Error(&MissingCmdLineVarError{variable: "ZITI_ADMIN_CERT"}) } - value, ok = os.LookupEnv("ZITI_CTRL_ADMIN_KEY") + value, ok = os.LookupEnv("ZITI_ADMIN_KEY") if ok && len(value) > 0 { zitiAdminKey = []byte(value) - } else { - if zitiAdminKey == nil { - klog.Error(&MissingEnvVarError{variable: "ZITI_CTRL_ADMIN_KEY"}) - klog.Error(&MissingCmdLineVarError{variable: "ZITI_CTRL_ADMIN_KEY"}) - } + } + if zitiAdminKey == nil { + klog.Error(&MissingEnvVarError{variable: "ZITI_ADMIN_KEY"}) + klog.Error(&MissingCmdLineVarError{variable: "ZITI_ADMIN_KEY"}) } value, ok = os.LookupEnv("POD_SECURITY_CONTEXT_OVERRIDE") if ok && len(value) > 0 { + var err error podSecurityOverride, err = strconv.ParseBool(value) if err != nil { klog.Info(err) } } - value, ok = os.LookupEnv("CLUSTER_DNS_SVC_IP") + value, ok = os.LookupEnv("SEARCH_DOMAINS") if ok && len(value) > 0 { - clusterDnsServiceIP = value - } else { - if len(clusterDnsServiceIP) == 0 { - klog.Error(&MissingEnvVarError{variable: "CLUSTER_DNS_SVC_IP"}) - klog.Error(&MissingCmdLineVarError{variable: "CLUSTER_DNS_SVC_IP"}) - klog.Infof("Custom DNS Server IP not set, Cluster DNS IP will be used instead") - } + searchDomains = strings.Split(value, ",") } - - value, ok = os.LookupEnv("SEARCH_DOMAIN_LIST") - if ok && len(value) > 0 { - searchDomains = []string(strings.Split(value, " ")) - } else { - if len(searchDomains) == 0 { - klog.Error(&MissingEnvVarError{variable: "SEARCH_DOMAIN_LIST"}) - klog.Error(&MissingCmdLineVarError{variable: "SEARCH_DOMAIN_LIST"}) - klog.Infof("Custom DNS search domains not set, Kubernetes default domains will be used instead") - } + if len(searchDomains) == 0 { + klog.Error(&MissingEnvVarError{variable: "SEARCH_DOMAINS"}) + klog.Error(&MissingCmdLineVarError{variable: "SEARCH_DOMAINS"}) + klog.Infof("Custom DNS search domains not set, Kubernetes default domains will be used instead") } value, ok = os.LookupEnv("ZITI_ROLE_KEY") if ok && len(value) > 0 { zitiRoleKey = value - } else { - if len(zitiRoleKey) == 0 { - klog.Error(&MissingEnvVarError{variable: "ZITI_ROLE_KEY"}) - klog.Error(&MissingCmdLineVarError{variable: "ZITI_ROLE_KEY"}) + } + if len(zitiRoleKey) == 0 { + klog.Error(&MissingEnvVarError{variable: "ZITI_ROLE_KEY"}) + klog.Error(&MissingCmdLineVarError{variable: "ZITI_ROLE_KEY"}) + } + + value, ok = os.LookupEnv("ZITI_AGENT_LOG_LEVEL") + if ok && len(value) > 0 { + switch value { + case "debug", "verbose": + verbose = true } } } diff --git a/ziti-agent/cmd/webhook/pods.go b/ziti-agent/cmd/webhook/pods.go index acfebbf..8b454a0 100644 --- a/ziti-agent/cmd/webhook/pods.go +++ b/ziti-agent/cmd/webhook/pods.go @@ -51,9 +51,9 @@ func zitiTunnel(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse { switch ar.Request.Operation { case "CREATE": - klog.Infof(fmt.Sprintf("%s", ar.Request.Operation)) - klog.Infof(fmt.Sprintf("Object: %s", ar.Request.Object.Raw)) - klog.Infof(fmt.Sprintf("OldObject: %s", ar.Request.OldObject.Raw)) + klog.Info(ar.Request.Operation) + klog.Infof("Object: %s", ar.Request.Object.Raw) + klog.Infof("OldObject: %s", ar.Request.OldObject.Raw) if _, _, err := deserializer.Decode(ar.Request.Object.Raw, nil, &pod); err != nil { klog.Error(err) return toV1AdmissionResponse(err) diff --git a/ziti-agent/cmd/webhook/root.go b/ziti-agent/cmd/webhook/root.go index 4b6e840..71becfd 100644 --- a/ziti-agent/cmd/webhook/root.go +++ b/ziti-agent/cmd/webhook/root.go @@ -25,6 +25,7 @@ var ( searchDomainList string searchDomains []string zitiRoleKey string + verbose bool value string ok bool err error @@ -63,6 +64,8 @@ and takes appropriate actions, i.e. create/delete ziti identity, secret, etc.`, "Override the security context at pod level, i.e. runAsNonRoot: false") webhookCmd.Flags().StringVar(&clusterDnsServiceIP, "cluster-dns-svc-ip", "", "Cluster DNS Service IP") + webhookCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, + "Enable verbose (DEBUG level) logging") webhookCmd.Flags().StringVar(&searchDomainList, "search-domain-list", "", "A list of DNS search domains as space seperated string i.e. 'value1 value2'") webhookCmd.Flags().StringVar(&zitiRoleKey, "ziti-role-key", "", diff --git a/ziti-agent/cmd/webhook/webhook.go b/ziti-agent/cmd/webhook/webhook.go index f8d684c..08edb93 100644 --- a/ziti-agent/cmd/webhook/webhook.go +++ b/ziti-agent/cmd/webhook/webhook.go @@ -2,6 +2,7 @@ package webhook import ( "encoding/json" + "flag" "fmt" "io" "net/http" @@ -15,10 +16,6 @@ import ( "k8s.io/klog/v2" ) -var ( - runtimeScheme = runtime.NewScheme() -) - func init() { /* AdmissionReview is registered for version admission.k8s.io/v1 or admission.k8s.io/v1beta1 @@ -132,6 +129,18 @@ func serveZitiTunnelSC(w http.ResponseWriter, r *http.Request) { } func webhook(cmd *cobra.Command, args []string) { + // Initialize logging first + klog.InitFlags(nil) + _ = flag.Set("v", "2") // Set to INFO level by default + flag.Parse() + + // load env vars to override the command line vars if any + lookupEnvVars() + + // Increase log level to DEBUG if verbose mode is enabled + if verbose { + _ = flag.Set("v", "4") + } klog.Infof("Current version is %s", common.Version) @@ -147,6 +156,9 @@ func webhook(cmd *cobra.Command, args []string) { klog.Info(err) } } + if cert == nil || key == nil { + panic("Cert and key required, but one or both are missing") + } // process ziti admin user certs passed from the file through the command line if zitiCtrlClientCertFile != "" && zitiCtrlClientKeyFile != "" { @@ -161,10 +173,6 @@ func webhook(cmd *cobra.Command, args []string) { } } - // load env vars to override the command line vars if any - lookupEnvVars() - - klog.Infof("AC WH Server is listening on port %d", port) http.HandleFunc("/ziti-tunnel", serveZitiTunnelSC) server := &http.Server{ Addr: fmt.Sprintf(":%d", port), @@ -174,4 +182,5 @@ func webhook(cmd *cobra.Command, args []string) { if err != nil { panic(err) } + klog.Infof("ziti agent webhook server is listening on port %d", port) }