├── docs ├── _config.yml ├── vault-agent-auto-inject-webhook-0.0.0.tgz ├── vault-agent-auto-inject-webhook-0.1.0.tgz ├── vault-agent-auto-inject-webhook-0.2.0.tgz ├── vault-agent-auto-inject-webhook-0.2.1.tgz ├── vault-agent-auto-inject-webhook-0.3.0.tgz ├── vault-agent-auto-inject-webhook-0.4.0.tgz ├── index.yaml └── index.md ├── test ├── cluster-issuer.yaml ├── test-app.yaml └── webhook.yaml ├── helm ├── vault-agent-auto-inject-webhook │ ├── templates │ │ ├── service_account.yaml │ │ ├── service.yaml │ │ ├── pod_disruption_budget.yaml │ │ ├── hpa.yaml │ │ ├── certificate.yaml │ │ ├── monitoring.yaml │ │ ├── mutating_webhook_configuration.yaml │ │ └── deployment.yaml │ ├── Chart.yaml │ ├── .helmignore │ └── values.yaml └── package.sh ├── terraform ├── data.tf ├── main.tf ├── namespace.tf ├── service-account.tf ├── pdb.tf ├── hpa.tf ├── service-monitor.tf ├── certificate.tf ├── services.tf ├── mutating-webhook-configuration.tf ├── deployment.tf ├── variables.tf └── README.md ├── .gitignore ├── go.mod ├── Dockerfile ├── cmd ├── webhook_test.go └── webhook.go ├── LICENSE ├── README.md ├── .circleci └── config.yml └── go.sum /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /test/cluster-issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1alpha2 2 | kind: ClusterIssuer 3 | metadata: 4 | name: selfsigning-issuer 5 | spec: 6 | selfSigned: {} -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/templates/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ .Values.serviceAccount.name }} -------------------------------------------------------------------------------- /docs/vault-agent-auto-inject-webhook-0.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patoarvizu/vault-agent-auto-inject-webhook/HEAD/docs/vault-agent-auto-inject-webhook-0.0.0.tgz -------------------------------------------------------------------------------- /docs/vault-agent-auto-inject-webhook-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patoarvizu/vault-agent-auto-inject-webhook/HEAD/docs/vault-agent-auto-inject-webhook-0.1.0.tgz -------------------------------------------------------------------------------- /docs/vault-agent-auto-inject-webhook-0.2.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patoarvizu/vault-agent-auto-inject-webhook/HEAD/docs/vault-agent-auto-inject-webhook-0.2.0.tgz -------------------------------------------------------------------------------- /docs/vault-agent-auto-inject-webhook-0.2.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patoarvizu/vault-agent-auto-inject-webhook/HEAD/docs/vault-agent-auto-inject-webhook-0.2.1.tgz -------------------------------------------------------------------------------- /docs/vault-agent-auto-inject-webhook-0.3.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patoarvizu/vault-agent-auto-inject-webhook/HEAD/docs/vault-agent-auto-inject-webhook-0.3.0.tgz -------------------------------------------------------------------------------- /docs/vault-agent-auto-inject-webhook-0.4.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patoarvizu/vault-agent-auto-inject-webhook/HEAD/docs/vault-agent-auto-inject-webhook-0.4.0.tgz -------------------------------------------------------------------------------- /terraform/data.tf: -------------------------------------------------------------------------------- 1 | data kubernetes_namespace ns { 2 | for_each = var.create_namespace ? {} : {"ns": true} 3 | metadata { 4 | name = var.namespace_name 5 | } 6 | } -------------------------------------------------------------------------------- /terraform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | kubernetes = { 4 | version = ">= 2.8.0" 5 | } 6 | } 7 | required_version = ">= 0.14.0" 8 | } -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | description: Vault agent auto-inject webhook 3 | name: vault-agent-auto-inject-webhook 4 | version: 0.4.0 5 | -------------------------------------------------------------------------------- /terraform/namespace.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_namespace ns { 2 | for_each = var.create_namespace ? {"ns": true} : {} 3 | metadata { 4 | name = var.namespace_name 5 | labels = var.namespace_labels 6 | } 7 | } -------------------------------------------------------------------------------- /terraform/service-account.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_service_account vault_agent_webhook { 2 | metadata { 3 | name = "vault-agent-webhook" 4 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 5 | } 6 | } -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: vault-agent-webhook 5 | labels: 6 | app: vault-agent-webhook 7 | spec: 8 | type: ClusterIP 9 | ports: 10 | - protocol: TCP 11 | port: 443 12 | targetPort: https 13 | selector: 14 | app: vault-agent-webhook -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | vendor/ 16 | 17 | build/ 18 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/patoarvizu/vault-agent-auto-inject-webhook 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/prometheus/client_golang v0.9.2 7 | github.com/radovskyb/watcher v1.0.7 8 | github.com/slok/kubewebhook v0.3.0 9 | k8s.io/api v0.0.0-20191206001707-7edad22604e1 10 | k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d 11 | k8s.io/client-go v0.0.0-20190918200256-06eb1244587a 12 | ) 13 | -------------------------------------------------------------------------------- /helm/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm package helm/vault-agent-auto-inject-webhook/ 4 | version=$(cat helm/vault-agent-auto-inject-webhook/Chart.yaml | yaml2json | jq -r '.version') 5 | mv vault-agent-auto-inject-webhook-$version.tgz docs/ 6 | helm repo index docs --url https://patoarvizu.github.io/vault-agent-auto-inject-webhook 7 | helm-docs 8 | mv helm/vault-agent-auto-inject-webhook/README.md docs/index.md 9 | git add docs/ -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/templates/pod_disruption_budget.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.podDisruptionBudget.enable }} 2 | apiVersion: policy/v1 3 | kind: PodDisruptionBudget 4 | metadata: 5 | name: vault-agent-webhook 6 | labels: 7 | app: vault-agent-webhook 8 | vault-control-plane: "true" 9 | spec: 10 | {{ toYaml .Values.podDisruptionBudget.availability }} 11 | selector: 12 | matchLabels: 13 | app: vault-agent-webhook 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /terraform/pdb.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_pod_disruption_budget_v1 vault_agent_webhook { 2 | metadata { 3 | name = "vault-agent-webhook" 4 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 5 | } 6 | spec { 7 | max_unavailable = var.pdb_max_unavaiable 8 | selector { 9 | match_labels = { 10 | app = "vault-agent-webhook" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/templates/hpa.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.hpa.enable }} 2 | apiVersion: {{ .Values.hpa.apiVersion }} 3 | kind: HorizontalPodAutoscaler 4 | metadata: 5 | name: vault-agent-webhook 6 | spec: 7 | minReplicas: {{ .Values.hpa.minReplicas }} 8 | maxReplicas: {{ .Values.hpa.maxReplicas }} 9 | scaleTargetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: vault-agent-webhook 13 | metrics: {{- toYaml .Values.hpa.metricsScalingConfiguration | nindent 2 }} 14 | {{- end }} -------------------------------------------------------------------------------- /terraform/hpa.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_horizontal_pod_autoscaler_v1 vault_agent_webhook { 2 | for_each = var.hpa_enable ? {"hpa": true} : {} 3 | metadata { 4 | name = "vault-agent-webhook" 5 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 6 | } 7 | spec { 8 | min_replicas = var.hpa.min_replicas 9 | max_replicas = var.hpa.max_replicas 10 | scale_target_ref { 11 | api_version = "apps/v1" 12 | kind = "Deployment" 13 | name = kubernetes_deployment.vault_agent_webhook.metadata[0].name 14 | } 15 | target_cpu_utilization_percentage = var.hpa.cpu_average_utilization 16 | } 17 | } -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/templates/certificate.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.certManager.injectSecret }} 2 | apiVersion: {{ .Values.certManager.apiVersion }} 3 | kind: Certificate 4 | metadata: 5 | name: vault-agent-webhook 6 | spec: 7 | secretName: vault-agent-webhook 8 | duration: {{ .Values.certManager.duration }} 9 | renewBefore: {{ .Values.certManager.renewBefore }} 10 | commonName: vault-agent-webhook 11 | dnsNames: 12 | - vault-agent-webhook 13 | - vault-agent-webhook.{{ .Release.Namespace }} 14 | - vault-agent-webhook.{{ .Release.Namespace }}.svc 15 | issuerRef: 16 | name: {{ .Values.certManager.issuerRef.name }} 17 | kind: {{ .Values.certManager.issuerRef.kind }} 18 | {{- end }} -------------------------------------------------------------------------------- /terraform/service-monitor.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_manifest servicemonitor_vault_agent_webhook { 2 | for_each = var.service_monitor_enable ? {"service-monitor": true} : {} 3 | manifest = { 4 | apiVersion = "monitoring.coreos.com/v1" 5 | kind = "ServiceMonitor" 6 | metadata = { 7 | name = "vault-agent-webhook" 8 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 9 | labels = var.service_monitor_custom_labels 10 | } 11 | spec = { 12 | endpoints = [ 13 | { 14 | path = "/" 15 | port = "metrics" 16 | }, 17 | ] 18 | selector = { 19 | matchLabels = { 20 | app = "vault-agent-webhook" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/templates/monitoring.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.prometheusMonitoring.enable }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: vault-agent-webhook-metrics 6 | labels: 7 | app: vault-agent-webhook 8 | spec: 9 | type: ClusterIP 10 | ports: 11 | - protocol: TCP 12 | port: 8081 13 | targetPort: metrics 14 | name: metrics 15 | selector: 16 | app: vault-agent-webhook 17 | 18 | --- 19 | 20 | apiVersion: monitoring.coreos.com/v1 21 | kind: ServiceMonitor 22 | metadata: 23 | name: vault-agent-webhook 24 | {{- if .Values.prometheusMonitoring.serviceMonitor.customLabels }} 25 | labels: {{ toYaml .Values.prometheusMonitoring.serviceMonitor.customLabels | nindent 4 }} 26 | {{- end }} 27 | spec: 28 | endpoints: 29 | - port: metrics 30 | path: / 31 | selector: 32 | matchLabels: 33 | app: vault-agent-webhook 34 | {{- end }} -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.16 as builder 2 | ARG TARGETARCH 3 | ARG TARGETVARIANT 4 | 5 | WORKDIR /workspace 6 | 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | RUN go mod download 10 | 11 | COPY cmd/webhook.go cmd/webhook.go 12 | 13 | RUN CGO_ENABLED=0 GOOS=linux GOARM=$(if [ "$TARGETVARIANT" = "v7" ]; then echo "7"; fi) GOARCH=$TARGETARCH go build -o vault-agent-auto-inject-webhook cmd/webhook.go 14 | 15 | FROM gcr.io/distroless/static:nonroot-amd64 16 | 17 | ARG GIT_COMMIT="unspecified" 18 | LABEL GIT_COMMIT=$GIT_COMMIT 19 | 20 | ARG GIT_TAG="" 21 | LABEL GIT_TAG=$GIT_TAG 22 | 23 | ARG COMMIT_TIMESTAMP="unspecified" 24 | LABEL COMMIT_TIMESTAMP=$COMMIT_TIMESTAMP 25 | 26 | ARG AUTHOR_EMAIL="unspecified" 27 | LABEL AUTHOR_EMAIL=$AUTHOR_EMAIL 28 | 29 | ARG SIGNATURE_KEY="undefined" 30 | LABEL SIGNATURE_KEY=$SIGNATURE_KEY 31 | 32 | COPY --from=builder /workspace/vault-agent-auto-inject-webhook / 33 | 34 | CMD /vault-agent-auto-inject-webhook -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/templates/mutating_webhook_configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | name: vault-agent-webhook 5 | labels: 6 | app: vault-agent-webhook 7 | {{- if .Values.certManager.injectSecret }} 8 | annotations: 9 | cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/vault-agent-webhook 10 | {{- end }} 11 | webhooks: 12 | - name: vault-agent-webhook.vault.asapp.engineering 13 | rules: 14 | - apiGroups: 15 | - "" 16 | apiVersions: 17 | - v1 18 | operations: 19 | - CREATE 20 | - UPDATE 21 | resources: 22 | - pods 23 | failurePolicy: {{ .Values.failurePolicy }} 24 | sideEffects: None 25 | admissionReviewVersions: 26 | - v1beta1 27 | - v1 28 | namespaceSelector: {{ toYaml .Values.namespaceSelector | nindent 4}} 29 | clientConfig: 30 | caBundle: {{ .Values.caBundle }} 31 | service: 32 | name: vault-agent-webhook 33 | namespace: {{ .Release.Namespace }} 34 | path: / -------------------------------------------------------------------------------- /terraform/certificate.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_manifest certificate_vault_agent_webhook { 2 | for_each = var.cert_manager_enable ? {"certificate": true} : {} 3 | manifest = { 4 | apiVersion = var.cert_manager.api_version 5 | kind = "Certificate" 6 | metadata = { 7 | name = "vault-agent-webhook" 8 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 9 | } 10 | spec = { 11 | commonName = "vault-agent-webhook" 12 | dnsNames = [ 13 | "vault-agent-webhook", 14 | format("vault-agent-webhook.%s", var.namespace_name), 15 | format("vault-agent-webhook.%s.svc", var.namespace_name), 16 | ] 17 | duration = format("%s0m0s", var.cert_manager.duration) 18 | issuerRef = { 19 | kind = var.cert_manager.issuer_ref.kind 20 | name = var.cert_manager.issuer_ref.name 21 | } 22 | renewBefore = format("%s0m0s", var.cert_manager.renew_before) 23 | secretName = var.certificate_secret_name 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /terraform/services.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_service vault_agent_webhook { 2 | metadata { 3 | name = "vault-agent-webhook" 4 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 5 | } 6 | spec { 7 | port { 8 | port = 443 9 | protocol = "TCP" 10 | target_port = "https" 11 | } 12 | selector = { 13 | app = "vault-agent-webhook" 14 | } 15 | type = "ClusterIP" 16 | } 17 | } 18 | 19 | resource kubernetes_service vault_agent_webhook_metrics { 20 | for_each = var.service_monitor_enable ? {"metrics": true} : {} 21 | metadata { 22 | name = "vault-agent-webhook-metrics" 23 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 24 | } 25 | spec { 26 | port { 27 | name = "metrics" 28 | port = 8081 29 | protocol = "TCP" 30 | target_port = "metrics" 31 | } 32 | selector = { 33 | app = "vault-agent-webhook" 34 | } 35 | type = "ClusterIP" 36 | } 37 | } -------------------------------------------------------------------------------- /terraform/mutating-webhook-configuration.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_mutating_webhook_configuration_v1 vault_agent_webhook { 2 | metadata { 3 | annotations = { 4 | "cert-manager.io/inject-ca-from" = format("%s/vault-agent-webhook", var.namespace_name) 5 | } 6 | name = "vault-agent-webhook" 7 | } 8 | webhook { 9 | client_config { 10 | service { 11 | name = kubernetes_service.vault_agent_webhook.metadata[0].name 12 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 13 | path = "/" 14 | } 15 | } 16 | failure_policy = var.failure_policy 17 | side_effects = "None" 18 | admission_review_versions = [ 19 | "v1", 20 | "v1beta1", 21 | ] 22 | name = "vault-agent-webhook.patoarvizu.dev" 23 | dynamic "namespace_selector" { 24 | for_each = var.webhook_namespace_selector_expressions 25 | content { 26 | match_expressions { 27 | key = namespace_selector.value["key"] 28 | operator = namespace_selector.value["operator"] 29 | } 30 | } 31 | } 32 | rule { 33 | api_groups = [ 34 | "", 35 | ] 36 | api_versions = [ 37 | "v1", 38 | ] 39 | operations = [ 40 | "CREATE", 41 | "UPDATE", 42 | ] 43 | resources = [ 44 | "pods", 45 | ] 46 | } 47 | } 48 | lifecycle { 49 | ignore_changes = [webhook[0].client_config[0].ca_bundle] 50 | } 51 | } -------------------------------------------------------------------------------- /test/test-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: test 5 | labels: 6 | vault-agent-auto-inject-webhook: "true" 7 | --- 8 | apiVersion: v1 9 | kind: ConfigMap 10 | metadata: 11 | name: vault-agent-config 12 | namespace: test 13 | data: 14 | vault-agent-config.hcl: |- 15 | exit_after_auth = false 16 | pid_file = "/home/vault/pidfile" 17 | auto_auth { 18 | method "kubernetes" { 19 | mount_path = "{{ getenv "KUBERNETES_AUTH_PATH" }}" 20 | config = { 21 | role = "{{ getenv "SERVICE" }}" 22 | } 23 | } 24 | } 25 | cache { 26 | use_auto_auth_token = true 27 | } 28 | vault { 29 | address = "{{ getenv "TARGET_VAULT_ADDRESS" }}" 30 | ca_path = "/opt/vault/certs/ca.crt" 31 | } 32 | listener "tcp" { 33 | address = "127.0.0.1:8200" 34 | tls_disable = true 35 | } 36 | --- 37 | apiVersion: v1 38 | kind: ConfigMap 39 | metadata: 40 | name: init-container-vault-agent-config 41 | namespace: test 42 | data: 43 | vault-agent-config.hcl: |- 44 | exit_after_auth = true 45 | auto_auth { 46 | method "kubernetes" { 47 | mount_path = "{{ getenv "KUBERNETES_AUTH_PATH" }}" 48 | config = { 49 | role = "{{ getenv "SERVICE" }}" 50 | } 51 | } 52 | sink "file" { 53 | config = { 54 | path = "/vault-agent/.vault-token" 55 | } 56 | } 57 | } 58 | vault { 59 | address = "{{ getenv "TARGET_VAULT_ADDRESS" }}" 60 | tls_skip_verify = true 61 | } -------------------------------------------------------------------------------- /docs/index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | vault-agent-auto-inject-webhook: 4 | - apiVersion: v2 5 | created: "2023-08-03T12:58:27.737292-04:00" 6 | description: Vault agent auto-inject webhook 7 | digest: 2537172594331510e93527d4e1794d5d9618024e23b9dee48a22fefbd4e37b2e 8 | name: vault-agent-auto-inject-webhook 9 | urls: 10 | - https://patoarvizu.github.io/vault-agent-auto-inject-webhook/vault-agent-auto-inject-webhook-0.4.0.tgz 11 | version: 0.4.0 12 | - apiVersion: v2 13 | created: "2023-08-03T12:58:27.736425-04:00" 14 | description: Vault agent auto-inject webhook 15 | digest: 9ab2e642f2e9a27d0f4c90afd1a58880866f8114e9b934ec75373f9a66ae98cf 16 | name: vault-agent-auto-inject-webhook 17 | urls: 18 | - https://patoarvizu.github.io/vault-agent-auto-inject-webhook/vault-agent-auto-inject-webhook-0.3.0.tgz 19 | version: 0.3.0 20 | - apiVersion: v2 21 | created: "2023-08-03T12:58:27.735381-04:00" 22 | description: Vault agent auto-inject webhook 23 | digest: e05d7f2eaba69f1800ed78d96e3a6eaa2912982d0a59e391b4a6a6cbbe5204d4 24 | name: vault-agent-auto-inject-webhook 25 | urls: 26 | - https://patoarvizu.github.io/vault-agent-auto-inject-webhook/vault-agent-auto-inject-webhook-0.2.1.tgz 27 | version: 0.2.1 28 | - apiVersion: v2 29 | created: "2023-08-03T12:58:27.734275-04:00" 30 | description: Vault agent auto-inject webhook 31 | digest: f0951ec709bfdbf482472242733b39b5f6d5a913437bd46f8b4d9653f6578c85 32 | name: vault-agent-auto-inject-webhook 33 | urls: 34 | - https://patoarvizu.github.io/vault-agent-auto-inject-webhook/vault-agent-auto-inject-webhook-0.2.0.tgz 35 | version: 0.2.0 36 | - apiVersion: v2 37 | created: "2023-08-03T12:58:27.73323-04:00" 38 | description: Vault agent auto-inject webhook 39 | digest: 2da09a31eb20a128b60e5dc6761a353bb8ac938e3dbafbce1d2aa3be9c7b44f0 40 | name: vault-agent-auto-inject-webhook 41 | urls: 42 | - https://patoarvizu.github.io/vault-agent-auto-inject-webhook/vault-agent-auto-inject-webhook-0.1.0.tgz 43 | version: 0.1.0 44 | - apiVersion: v1 45 | created: "2023-08-03T12:58:27.732249-04:00" 46 | description: Vault agent auto-inject webhook 47 | digest: 77e938e8bce1bb95537e1a070eb0b54677d699f81e464d2ac4bffb6bf5c5af11 48 | name: vault-agent-auto-inject-webhook 49 | urls: 50 | - https://patoarvizu.github.io/vault-agent-auto-inject-webhook/vault-agent-auto-inject-webhook-0.0.0.tgz 51 | version: 0.0.0 52 | generated: "2023-08-03T12:58:27.729738-04:00" 53 | -------------------------------------------------------------------------------- /terraform/deployment.tf: -------------------------------------------------------------------------------- 1 | resource kubernetes_deployment vault_agent_webhook { 2 | metadata { 3 | name = "vault-agent-webhook" 4 | namespace = var.create_namespace ? kubernetes_namespace.ns["ns"].metadata[0].name : data.kubernetes_namespace.ns["ns"].metadata[0].name 5 | } 6 | spec { 7 | replicas = var.replicas 8 | selector { 9 | match_labels = { 10 | app = "vault-agent-webhook" 11 | } 12 | } 13 | template { 14 | metadata { 15 | labels = { 16 | app = "vault-agent-webhook" 17 | } 18 | } 19 | spec { 20 | container { 21 | command = [ 22 | "/vault-agent-auto-inject-webhook", 23 | "-tls-cert-file", 24 | "/tls/tls.crt", 25 | "-tls-key-file", 26 | "/tls/tls.key", 27 | "-ca-cert-secret-name", 28 | var.ca_cert_secret_name, 29 | "-vault-image-version", 30 | var.vault_image_version, 31 | "-annotation-prefix", 32 | var.annotation_prefix, 33 | "-target-vault-address", 34 | var.target_vault_address, 35 | "-gomplate-image", 36 | var.gomplate_image, 37 | "-kubernetes-auth-path", 38 | var.kubernetes_auth_path, 39 | "-default-config-map-name", 40 | var.default_config_map_name, 41 | "-cpu-request", 42 | var.cpu_request, 43 | "-cpu-limit", 44 | var.cpu_limit, 45 | "-memory-request", 46 | var.memory_request, 47 | "-memory-limit", 48 | var.memory_limit, 49 | "-mount-ca-cert-secret", 50 | ] 51 | image = format("patoarvizu/vault-agent-auto-inject-webhook:%s", var.image_version) 52 | image_pull_policy = var.image_pull_policy 53 | name = "vault-agent-webhook" 54 | port { 55 | container_port = 4443 56 | name = "https" 57 | } 58 | dynamic "port" { 59 | for_each = var.service_monitor_enable ? {metrics: true} : {} 60 | content { 61 | container_port = 8081 62 | name = "metrics" 63 | } 64 | } 65 | volume_mount { 66 | mount_path = "/tls" 67 | name = "tls" 68 | } 69 | } 70 | service_account_name = kubernetes_service_account.vault_agent_webhook.metadata[0].name 71 | volume { 72 | name = "tls" 73 | secret { 74 | secret_name = var.certificate_secret_name 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: vault-agent-webhook 5 | spec: 6 | {{- if not .Values.hpa.enable }} 7 | replicas: {{ .Values.replicas }} 8 | {{- end }} 9 | selector: 10 | matchLabels: 11 | app: vault-agent-webhook 12 | template: 13 | metadata: 14 | labels: 15 | app: vault-agent-webhook 16 | spec: 17 | {{- if .Values.affinity }} 18 | affinity: {{ toYaml .Values.affinity | nindent 10 }} 19 | {{- end }} 20 | serviceAccountName: {{ .Values.serviceAccount.name }} 21 | containers: 22 | - name: vault-agent-webhook 23 | image: patoarvizu/vault-agent-auto-inject-webhook:{{ .Values.imageVersion }} 24 | command: 25 | - /vault-agent-auto-inject-webhook 26 | - -tls-cert-file 27 | - {{ .Values.tls.mountPath }}/tls.crt 28 | - -tls-key-file 29 | - {{ .Values.tls.mountPath }}/tls.key 30 | - -ca-cert-secret-name 31 | - {{ .Values.flags.caCertSecretName }} 32 | - -vault-image-version 33 | - {{ .Values.flags.vaultImageVersion }} 34 | - -annotation-prefix 35 | - {{ .Values.flags.annotationPrefix }} 36 | - -target-vault-address 37 | - {{ .Values.flags.targetVaultAddress | default (printf "https://vault.%s:8200" .Release.Namespace) }} 38 | - -gomplate-image 39 | - {{ .Values.flags.gomplateImage }} 40 | - -kubernetes-auth-path 41 | - {{ .Values.flags.kubernetesAuthPath }} 42 | - -default-config-map-name 43 | - {{ .Values.flags.defaultConfigMapName }} 44 | - -cpu-request 45 | - {{ .Values.flags.resources.requests.cpu }} 46 | - -cpu-limit 47 | - {{ .Values.flags.resources.limits.cpu }} 48 | - -memory-request 49 | - {{ .Values.flags.resources.requests.memory }} 50 | - -memory-limit 51 | - {{ .Values.flags.resources.limits.memory }} 52 | {{- if .Values.flags.mountCACertSecret }} 53 | - -mount-ca-cert-secret 54 | {{- end }} 55 | {{- if .Values.resources }} 56 | resources: {{ toYaml .Values.resources | nindent 10 }} 57 | {{- end }} 58 | ports: 59 | - name: https 60 | containerPort: 4443 61 | - name: metrics 62 | containerPort: 8081 63 | imagePullPolicy: {{ .Values.imagePullPolicy }} 64 | volumeMounts: 65 | - name: tls 66 | mountPath: {{ .Values.tls.mountPath }} 67 | volumes: 68 | - name: tls 69 | secret: 70 | {{- if .Values.certManager.injectSecret }} 71 | secretName: vault-agent-webhook 72 | {{- else }} 73 | secretName: {{ .Values.tls.secretName }} 74 | {{- end }} -------------------------------------------------------------------------------- /test/webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: vault 5 | --- 6 | apiVersion: cert-manager.io/v1alpha2 7 | kind: Certificate 8 | metadata: 9 | name: vault-agent-auto-inject-webhook 10 | namespace: vault 11 | spec: 12 | secretName: vault-agent-auto-inject-webhook 13 | duration: 10m 14 | renewBefore: 2m 15 | commonName: vault-agent-auto-inject-webhook 16 | dnsNames: 17 | - vault-agent-auto-inject-webhook 18 | - vault-agent-auto-inject-webhook.vault 19 | - vault-agent-auto-inject-webhook.vault.svc 20 | issuerRef: 21 | name: selfsigning-issuer 22 | kind: ClusterIssuer 23 | --- 24 | apiVersion: v1 25 | kind: ServiceAccount 26 | metadata: 27 | name: vault-agent-auto-inject-webhook 28 | namespace: vault 29 | --- 30 | apiVersion: apps/v1 31 | kind: Deployment 32 | metadata: 33 | name: vault-agent-auto-inject-webhook 34 | namespace: vault 35 | spec: 36 | selector: 37 | matchLabels: 38 | app: vault-agent-auto-inject-webhook 39 | template: 40 | metadata: 41 | labels: 42 | app: vault-agent-auto-inject-webhook 43 | spec: 44 | serviceAccountName: vault-agent-auto-inject-webhook 45 | containers: 46 | - name: vault-agent-auto-inject-webhook 47 | image: patoarvizu/vault-agent-auto-inject-webhook:latest 48 | imagePullPolicy: IfNotPresent 49 | command: 50 | - /vault-agent-auto-inject-webhook 51 | - -tls-cert-file 52 | - /tls/tls.crt 53 | - -tls-key-file 54 | - /tls/tls.key 55 | - -mount-ca-cert-secret 56 | ports: 57 | - name: https 58 | containerPort: 4443 59 | volumeMounts: 60 | - name: tls 61 | mountPath: /tls 62 | volumes: 63 | - name: tls 64 | secret: 65 | secretName: vault-agent-auto-inject-webhook 66 | --- 67 | apiVersion: v1 68 | kind: Service 69 | metadata: 70 | name: vault-agent-auto-inject-webhook 71 | namespace: vault 72 | labels: 73 | app: vault-agent-auto-inject-webhook 74 | spec: 75 | type: ClusterIP 76 | ports: 77 | - protocol: TCP 78 | port: 443 79 | targetPort: https 80 | selector: 81 | app: vault-agent-auto-inject-webhook 82 | --- 83 | apiVersion: admissionregistration.k8s.io/v1beta1 84 | kind: MutatingWebhookConfiguration 85 | metadata: 86 | name: vault-agent-auto-inject-webhook-test 87 | labels: 88 | app: vault-agent-auto-inject-webhook 89 | annotations: 90 | cert-manager.io/inject-ca-from: vault/vault-agent-auto-inject-webhook 91 | webhooks: 92 | - name: webhook.vault.patoarvizu.dev 93 | rules: 94 | - apiGroups: 95 | - "" 96 | apiVersions: 97 | - v1 98 | operations: 99 | - CREATE 100 | - UPDATE 101 | resources: 102 | - pods 103 | failurePolicy: Fail 104 | namespaceSelector: 105 | matchExpressions: 106 | - key: vault-agent-auto-inject-webhook 107 | operator: Exists 108 | clientConfig: 109 | caBundle: Cg== 110 | service: 111 | name: vault-agent-auto-inject-webhook 112 | namespace: vault 113 | path: / -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # vault-agent-auto-inject-webhook 2 | 3 |  4 | 5 | Vault agent auto-inject webhook 6 | 7 | ## Values 8 | 9 | | Key | Type | Default | Description | 10 | |-----|------|---------|-------------| 11 | | affinity | string | `nil` | Affinity/anti-affinity rules for pod scheduling according to the [documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity). This map will be set as is on the Deployment object. | 12 | | caBundle | string | `"Cg=="` | The base64-encoded public CA certificate to be set on the `MutatingWebhookConfiguration`. Note that it defaults to `Cg==` which is a base64-encoded empty string. If this value is not automatically set by cert-manager, or some other mutating webhook, this should be set explicitly. | 13 | | certManager.apiVersion | string | `"cert-manager.io/v1"` | The `apiVersion` of the `Certificate` object created by the chart. It depends on the versions made available by the specific cert-manager running on the cluster. | 14 | | certManager.duration | string | `"2160h"` | The value to be set directly on the `duration` field of the `Certificate`. | 15 | | certManager.injectSecret | bool | `true` | Enables auto-injection of a certificate managed by [cert-manager](https://github.com/jetstack/cert-manager). | 16 | | certManager.issuerRef | object | `{"kind":"ClusterIssuer","name":"selfsigning-issuer"}` | The `name` and `kind` of the cert-manager issuer to be used. | 17 | | certManager.renewBefore | string | `"360h"` | The value to be set directly on the `renewBefore` field of the `Certificate`. | 18 | | failurePolicy | string | `"Ignore"` | The value to set directly on the `failurePolicy` of the `MutatingWebhookConfiguration`. Valid values are `Fail` or `Ignore`. | 19 | | flags.annotationPrefix | string | `"vault.patoarvizu.dev"` | The value to be set on the `-annotation-prefix` flag. | 20 | | flags.caCertSecretName | string | `"vault-tls"` | The value to be set on the `-ca-cert-secret-name` flag. | 21 | | flags.defaultConfigMapName | string | `"vault-agent-config"` | The value to be set on the `-default-config-map-name` flag. | 22 | | flags.gomplateImage | string | `"hairyhenderson/gomplate:v3"` | The value to be set to the `-gomplate-image` flag. | 23 | | flags.kubernetesAuthPath | string | `"auth/kubernetes"` | The value to be set on the `-kubernetes-auth-path` flag. | 24 | | flags.mountCACertSecret | bool | `true` | The value to be set on the `-mount-ca-cert-secret` flag. | 25 | | flags.resources.limits.cpu | string | `"100m"` | The value to be set on the `-cpu-limit` flag. | 26 | | flags.resources.limits.memory | string | `"256Mi"` | The value to be set on the `-memory-limit` flag. | 27 | | flags.resources.requests.cpu | string | `"50m"` | The value to be set on the `-cpu-request` flag. | 28 | | flags.resources.requests.memory | string | `"128Mi"` | The value to be set on the `-memory-request` flag. | 29 | | flags.targetVaultAddress | string | `nil` | The value to be set on the `-target-vault-address` flag. If not specified, it will default to https://vault.{{ .Release.Namespace }}:8200. | 30 | | flags.vaultImageVersion | string | `"1.4.0"` | The value to be set on the `-vault-image-version` flag. | 31 | | hpa.apiVersion | string | `"autoscaling/v2"` | The `apiVersion` of the `HorizontalPodAutoscaler` to create. The metrics configuration options vary depending on this value. | 32 | | hpa.enable | bool | `false` | Create a `HorizontalPodAutoscaler` object to control dynamic replication of the webhook. If this is set to `false`, all values under `hpa` are ignored. | 33 | | hpa.maxReplicas | int | `20` | The maximum number of replicas to attempt to maintain at all times. | 34 | | hpa.metricsScalingConfiguration | list | `[{"resource":{"name":"cpu","target":{"averageUtilization":80,"type":"Utilization"}},"type":"Resource"}]` | The scaling configuration to be injected directly into the `HorizontalPodAutoscaler` object. | 35 | | hpa.minReplicas | int | `3` | The minimum number of replicas to attempt to maintain at all times. | 36 | | imagePullPolicy | string | `"IfNotPresent"` | The imagePullPolicy to be used on the webhook. | 37 | | imageVersion | string | `"v0.5.0"` | The image version used for the webhook. | 38 | | namespaceSelector | object | `{"matchExpressions":[{"key":"vault-control-plane","operator":"DoesNotExist"}]}` | A label selector expression to determine what namespaces should be in scope for the mutating webhook. | 39 | | podDisruptionBudget.availability.maxUnavailable | int | `0` | The default availability is set to `maxUnavailable: 0` (if `podDisruptionBudget.enable` is `true`). | 40 | | podDisruptionBudget.enable | bool | `true` | Create a `PodDisruptionBudget` object to control replication availability. You can find more info about disruption budgets in Kubernetes [here](https://kubernetes.io/docs/tasks/run-application/configure-pdb/). | 41 | | prometheusMonitoring.enable | bool | `true` | Create the `Service` and `ServiceMonitor` objects to enable Prometheus monitoring on the webhook. | 42 | | prometheusMonitoring.serviceMonitor.customLabels | string | `nil` | Custom labels to add to the ServiceMonitor object. | 43 | | replicas | int | `3` | The number of replicas of the webhook to run. | 44 | | resources | string | `nil` | Map of cpu/memory resources and limits, to be set on the webhook | 45 | | serviceAccount.name | string | `"vault-agent-webhook"` | The name of the `ServiceAccount` to be created. | 46 | | tls.mountPath | string | `"/tls"` | The path where the CA cert from the secret should be mounted. | 47 | | tls.secretName | string | `"vault-agent-webhook"` | The name of the `Secret` from which the CA cert will be mounted. This value is ignored if `.certManager.injectSecret` is set to `true`. | 48 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | ############ 2 | # Required # 3 | ############ 4 | 5 | variable image_version { 6 | type = string 7 | description = "The label of the image to run." 8 | } 9 | 10 | ############ 11 | # Optional # 12 | ############ 13 | 14 | variable create_namespace { 15 | type = bool 16 | default = true 17 | description = "If true, a new namespace will be created with the name set to the value of the namespace_name variable. If false, it will look up an existing namespace with the name of the value of the namespace_name variable." 18 | } 19 | 20 | variable namespace_name { 21 | type = string 22 | default = "vault" 23 | description = "The name of the namespace to create or look up." 24 | } 25 | 26 | variable namespace_labels { 27 | type = map 28 | default = {} 29 | description = "The set of labels to add to the namespace (if one needs to be created)." 30 | } 31 | 32 | variable replicas { 33 | type = number 34 | default = 3 35 | description = "The number of replicas of the webhook server to run." 36 | } 37 | 38 | variable image_pull_policy { 39 | type = string 40 | default = "IfNotPresent" 41 | description = "The value of imagePullPolicy to set on the Deployment object." 42 | } 43 | 44 | variable failure_policy { 45 | type = string 46 | default = "Ignore" 47 | description = "The value of failurePolicy to set on the MutatingWebhookConfiguration object." 48 | } 49 | 50 | variable pdb_max_unavaiable { 51 | type = number 52 | default = 0 53 | description = "The value of maxUnavailable to set on the PodDisruptionBudget object." 54 | } 55 | 56 | variable hpa_enable { 57 | type = bool 58 | default = false 59 | description = "If set to true, a HorizontalPodAutoscaler object will be created." 60 | } 61 | 62 | variable hpa { 63 | type = object({ 64 | min_replicas = number 65 | max_replicas = number 66 | cpu_average_utilization = number 67 | }) 68 | default = { 69 | min_replicas = 3 70 | max_replicas = 20 71 | cpu_average_utilization = 80 72 | } 73 | description = "Object to configure the HorizontalPodAutoscaler object (if one is being created)." 74 | } 75 | 76 | variable certificate_secret_name { 77 | type = string 78 | default = "vault-agent-webhook" 79 | description = "The name of the Secret to be referenced from the Deployment object to mount as the certificate." 80 | } 81 | 82 | variable cert_manager_enable { 83 | type = bool 84 | default = true 85 | description = "If true, a Certificate object will be created and mounted on the pods. **NOTE:** this requires cert-manager to be running on the target cluster." 86 | } 87 | 88 | variable cert_manager { 89 | type = object({ 90 | api_version = string 91 | duration = string 92 | renew_before = string 93 | issuer_ref = object( 94 | { 95 | name = string 96 | kind = string 97 | } 98 | ) 99 | }) 100 | default = { 101 | api_version = "cert-manager.io/v1" 102 | duration = "2160h" 103 | renew_before = "360h" 104 | issuer_ref = { 105 | name = "selfsigning-issuer" 106 | kind = "ClusterIssuer" 107 | } 108 | } 109 | description = "Object to configure the Certificate object (if one is being created)." 110 | } 111 | 112 | variable annotation_prefix { 113 | type = string 114 | default = "vault.patoarvizu.dev" 115 | description = "The value to be passed to the -annotation-prefix flag." 116 | } 117 | 118 | variable target_vault_address { 119 | type = string 120 | default = "https://vault:8200" 121 | description = "The value to be passed to the -target-vault-address flag." 122 | } 123 | 124 | variable gomplate_image { 125 | type = string 126 | default = "hairyhenderson/gomplate:v3" 127 | description = "The value to be passed to the -gomplate-image flag." 128 | } 129 | 130 | variable kubernetes_auth_path { 131 | type = string 132 | default = "auth/kubernetes" 133 | description = "The value to be passed to the -kubernetes-auth-path flag." 134 | } 135 | 136 | variable vault_image_version { 137 | type = string 138 | default = "1.4.0" 139 | description = "The value to be passed to the -vault-image-version flag." 140 | } 141 | 142 | variable default_config_map_name { 143 | type = string 144 | default = "vault-agent-config" 145 | description = "The value to be passed to the -default-config-map-name flag." 146 | } 147 | 148 | variable ca_cert_secret_name { 149 | type = string 150 | default = "vault-tls" 151 | description = "The value to be passed to the -ca-cert-secret-name flag." 152 | } 153 | 154 | variable cpu_request { 155 | type = string 156 | default = "50m" 157 | description = "The value to be passed to the -cpu-request flag." 158 | } 159 | 160 | variable memory_request { 161 | type = string 162 | default = "128Mi" 163 | description = "The value to be passed to the -memory-request flag." 164 | } 165 | 166 | variable cpu_limit { 167 | type = string 168 | default = "100m" 169 | description = "The value to be passed to the -cpu-limit flag." 170 | } 171 | 172 | variable memory_limit { 173 | type = string 174 | default = "256Mi" 175 | description = "The value to be passed to the -memory-limit flag." 176 | } 177 | 178 | variable service_monitor_enable { 179 | type = bool 180 | default = true 181 | description = "If true a ServiceMonitor object will be created, and a /metrics endpoint will be exposed. **NOTE:** this requires the Prometheus operator to be running on the target cluster." 182 | } 183 | 184 | variable service_monitor_custom_labels { 185 | type = map 186 | default = {} 187 | description = "Custom labels to add to the `ServiceMonitor` object." 188 | } 189 | 190 | variable webhook_namespace_selector_expressions { 191 | type = list(object({ 192 | key = string 193 | operator = string 194 | })) 195 | default = [ 196 | { 197 | key: "vault-control-plane" 198 | operator: "DoesNotExist" 199 | } 200 | ] 201 | description = "The list of expressions to match the namespaces where this webhook will operate." 202 | } -------------------------------------------------------------------------------- /helm/vault-agent-auto-inject-webhook/values.yaml: -------------------------------------------------------------------------------- 1 | certManager: 2 | # certManager.injectSecret -- Enables auto-injection of a certificate managed by [cert-manager](https://github.com/jetstack/cert-manager). 3 | injectSecret: true 4 | # certManager.apiVersion -- The `apiVersion` of the `Certificate` object created by the chart. 5 | # It depends on the versions made available by the specific cert-manager running on the cluster. 6 | apiVersion: cert-manager.io/v1 7 | # certManager.duration -- The value to be set directly on the `duration` field of the `Certificate`. 8 | duration: 2160h 9 | # certManager.renewBefore -- The value to be set directly on the `renewBefore` field of the `Certificate`. 10 | renewBefore: 360h 11 | # certManager.issuerRef -- The `name` and `kind` of the cert-manager issuer to be used. 12 | issuerRef: 13 | name: selfsigning-issuer 14 | kind: ClusterIssuer 15 | 16 | serviceAccount: 17 | # serviceAccount.name -- The name of the `ServiceAccount` to be created. 18 | name: vault-agent-webhook 19 | 20 | flags: 21 | # flags.annotationPrefix -- The value to be set on the `-annotation-prefix` flag. 22 | annotationPrefix: vault.patoarvizu.dev 23 | # flags.targetVaultAddress -- The value to be set on the `-target-vault-address` flag. 24 | # If not specified, it will default to https://vault.{{ .Release.Namespace }}:8200. 25 | targetVaultAddress: 26 | # flags.gomplateImage -- The value to be set to the `-gomplate-image` flag. 27 | gomplateImage: hairyhenderson/gomplate:v3 28 | # flags.kubernetesAuthPath -- The value to be set on the `-kubernetes-auth-path` flag. 29 | kubernetesAuthPath: auth/kubernetes 30 | # flags.vaultImageVersion -- The value to be set on the `-vault-image-version` flag. 31 | vaultImageVersion: 1.4.0 32 | # flags.defaultConfigMapName -- The value to be set on the `-default-config-map-name` flag. 33 | defaultConfigMapName: vault-agent-config 34 | # flags.mountCACertSecret -- The value to be set on the `-mount-ca-cert-secret` flag. 35 | mountCACertSecret: true 36 | # flags.caCertSecretName -- The value to be set on the `-ca-cert-secret-name` flag. 37 | caCertSecretName: vault-tls 38 | resources: 39 | requests: 40 | # flags.resources.requests.cpu -- The value to be set on the `-cpu-request` flag. 41 | cpu: 50m 42 | # flags.resources.requests.memory -- The value to be set on the `-memory-request` flag. 43 | memory: 128Mi 44 | limits: 45 | # flags.resources.limits.cpu -- The value to be set on the `-cpu-limit` flag. 46 | cpu: 100m 47 | # flags.resources.limits.memory -- The value to be set on the `-memory-limit` flag. 48 | memory: 256Mi 49 | 50 | # replicas -- The number of replicas of the webhook to run. 51 | replicas: 3 52 | # imageVersion -- The image version used for the webhook. 53 | imageVersion: v0.5.0 54 | # imagePullPolicy -- The imagePullPolicy to be used on the webhook. 55 | imagePullPolicy: IfNotPresent 56 | # failurePolicy -- The value to set directly on the `failurePolicy` of the `MutatingWebhookConfiguration`. Valid values are `Fail` or `Ignore`. 57 | failurePolicy: Ignore 58 | # caBundle -- The base64-encoded public CA certificate to be set on the `MutatingWebhookConfiguration`. 59 | # Note that it defaults to `Cg==` which is a base64-encoded empty string. 60 | # If this value is not automatically set by cert-manager, or some other mutating webhook, this should be set explicitly. 61 | caBundle: Cg== 62 | 63 | podDisruptionBudget: 64 | # podDisruptionBudget.enable -- Create a `PodDisruptionBudget` object to control replication availability. 65 | # You can find more info about disruption budgets in Kubernetes [here](https://kubernetes.io/docs/tasks/run-application/configure-pdb/). 66 | enable: true 67 | # podDisruptionBudget.availability: -- The availability criteria to use for the `PodDisruptionBudget` object. 68 | # This map only supports one key, either `maxUnavailable` or `minAvailable`. 69 | availability: 70 | # podDisruptionBudget.availability.maxUnavailable -- The default availability is set to `maxUnavailable: 0` (if `podDisruptionBudget.enable` is `true`). 71 | maxUnavailable: 0 72 | # namespaceSelector -- A label selector expression to determine what namespaces should be in scope for the mutating webhook. 73 | namespaceSelector: 74 | matchExpressions: 75 | - key: vault-control-plane 76 | operator: DoesNotExist 77 | 78 | tls: 79 | # tls.mountPath -- The path where the CA cert from the secret should be mounted. 80 | mountPath: /tls 81 | # tls.secretName -- The name of the `Secret` from which the CA cert will be mounted. This value is ignored if `.certManager.injectSecret` is set to `true`. 82 | secretName: vault-agent-webhook 83 | 84 | hpa: 85 | # hpa.enable -- Create a `HorizontalPodAutoscaler` object to control dynamic replication of the webhook. If this is set to `false`, all values under `hpa` are ignored. 86 | enable: false 87 | # hpa.apiVersion -- The `apiVersion` of the `HorizontalPodAutoscaler` to create. The metrics configuration options vary depending on this value. 88 | apiVersion: autoscaling/v2 89 | # hpa.minReplicas -- The minimum number of replicas to attempt to maintain at all times. 90 | minReplicas: 3 91 | # hpa.maxReplicas -- The maximum number of replicas to attempt to maintain at all times. 92 | maxReplicas: 20 93 | # hpa.metricsScalingConfiguration -- The scaling configuration to be injected directly into the `HorizontalPodAutoscaler` object. 94 | metricsScalingConfiguration: 95 | - type: Resource 96 | resource: 97 | name: cpu 98 | target: 99 | averageUtilization: 80 100 | type: Utilization 101 | 102 | prometheusMonitoring: 103 | # prometheusMonitoring.enable -- Create the `Service` and `ServiceMonitor` objects to enable Prometheus monitoring on the webhook. 104 | enable: true 105 | serviceMonitor: 106 | # prometheusMonitoring.serviceMonitor.customLabels -- Custom labels to add to the ServiceMonitor object. 107 | customLabels: 108 | # affinity -- Affinity/anti-affinity rules for pod scheduling according to the [documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity). 109 | # This map will be set as is on the Deployment object. 110 | affinity: 111 | 112 | # resources -- Map of cpu/memory resources and limits, to be set on the webhook 113 | resources: 114 | -------------------------------------------------------------------------------- /terraform/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Requirements 4 | 5 | | Name | Version | 6 | |------|---------| 7 | | [terraform](#requirement\_terraform) | >= 0.14.0 | 8 | | [kubernetes](#requirement\_kubernetes) | ~> 2.8.0 | 9 | 10 | ## Providers 11 | 12 | | Name | Version | 13 | |------|---------| 14 | | [kubernetes](#provider\_kubernetes) | ~> 2.8.0 | 15 | 16 | ## Modules 17 | 18 | No modules. 19 | 20 | ## Resources 21 | 22 | | Name | Type | 23 | |------|------| 24 | | [kubernetes_deployment.vault_agent_webhook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/deployment) | resource | 25 | | [kubernetes_horizontal_pod_autoscaler_v1.vault_agent_webhook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/horizontal_pod_autoscaler_v1) | resource | 26 | | [kubernetes_manifest.certificate_vault_agent_webhook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | 27 | | [kubernetes_manifest.servicemonitor_vault_agent_webhook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest) | resource | 28 | | [kubernetes_mutating_webhook_configuration_v1.vault_agent_webhook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/mutating_webhook_configuration_v1) | resource | 29 | | [kubernetes_namespace.ns](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace) | resource | 30 | | [kubernetes_pod_disruption_budget_v1.vault_agent_webhook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/pod_disruption_budget_v1) | resource | 31 | | [kubernetes_service.vault_agent_webhook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource | 32 | | [kubernetes_service.vault_agent_webhook_metrics](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service) | resource | 33 | | [kubernetes_service_account.vault_agent_webhook](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service_account) | resource | 34 | | [kubernetes_namespace.ns](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/data-sources/namespace) | data source | 35 | 36 | ## Inputs 37 | 38 | | Name | Description | Type | Default | Required | 39 | |------|-------------|------|---------|:--------:| 40 | | [annotation\_prefix](#input\_annotation\_prefix) | The value to be passed to the -annotation-prefix flag. | `string` | `"vault.patoarvizu.dev"` | no | 41 | | [ca\_cert\_secret\_name](#input\_ca\_cert\_secret\_name) | The value to be passed to the -ca-cert-secret-name flag. | `string` | `"vault-tls"` | no | 42 | | [cert\_manager](#input\_cert\_manager) | Object to configure the Certificate object (if one is being created). |
object({
api_version = string
duration = string
renew_before = string
issuer_ref = object(
{
name = string
kind = string
}
)
}) | {
"api_version": "cert-manager.io/v1",
"duration": "2160h",
"issuer_ref": {
"kind": "ClusterIssuer",
"name": "selfsigning-issuer"
},
"renew_before": "360h"
} | no |
43 | | [cert\_manager\_enable](#input\_cert\_manager\_enable) | If true, a Certificate object will be created and mounted on the pods. **NOTE:** this requires cert-manager to be running on the target cluster. | `bool` | `true` | no |
44 | | [certificate\_secret\_name](#input\_certificate\_secret\_name) | The name of the Secret to be referenced from the Deployment object to mount as the certificate. | `string` | `"vault-agent-webhook"` | no |
45 | | [cpu\_limit](#input\_cpu\_limit) | The value to be passed to the -cpu-limit flag. | `string` | `"100m"` | no |
46 | | [cpu\_request](#input\_cpu\_request) | The value to be passed to the -cpu-request flag. | `string` | `"50m"` | no |
47 | | [create\_namespace](#input\_create\_namespace) | If true, a new namespace will be created with the name set to the value of the namespace\_name variable. If false, it will look up an existing namespace with the name of the value of the namespace\_name variable. | `bool` | `true` | no |
48 | | [default\_config\_map\_name](#input\_default\_config\_map\_name) | The value to be passed to the -default-config-map-name flag. | `string` | `"vault-agent-config"` | no |
49 | | [failure\_policy](#input\_failure\_policy) | The value of failurePolicy to set on the MutatingWebhookConfiguration object. | `string` | `"Ignore"` | no |
50 | | [gomplate\_image](#input\_gomplate\_image) | The value to be passed to the -gomplate-image flag. | `string` | `"hairyhenderson/gomplate:v3"` | no |
51 | | [hpa](#input\_hpa) | Object to configure the HorizontalPodAutoscaler object (if one is being created). | object({
min_replicas = number
max_replicas = number
cpu_average_utilization = number
}) | {
"cpu_average_utilization": 80,
"max_replicas": 20,
"min_replicas": 3
} | no |
52 | | [hpa\_enable](#input\_hpa\_enable) | If set to true, a HorizontalPodAutoscaler object will be created. | `bool` | `false` | no |
53 | | [image\_pull\_policy](#input\_image\_pull\_policy) | The value of imagePullPolicy to set on the Deployment object. | `string` | `"IfNotPresent"` | no |
54 | | [image\_version](#input\_image\_version) | The label of the image to run. | `string` | n/a | yes |
55 | | [kubernetes\_auth\_path](#input\_kubernetes\_auth\_path) | The value to be passed to the -kubernetes-auth-path flag. | `string` | `"auth/kubernetes"` | no |
56 | | [memory\_limit](#input\_memory\_limit) | The value to be passed to the -memory-limit flag. | `string` | `"256Mi"` | no |
57 | | [memory\_request](#input\_memory\_request) | The value to be passed to the -memory-request flag. | `string` | `"128Mi"` | no |
58 | | [namespace\_labels](#input\_namespace\_labels) | The set of labels to add to the namespace (if one needs to be created). | `map` | `{}` | no |
59 | | [namespace\_name](#input\_namespace\_name) | The name of the namespace to create or look up. | `string` | `"vault"` | no |
60 | | [pdb\_max\_unavaiable](#input\_pdb\_max\_unavaiable) | The value of maxUnavailable to set on the PodDisruptionBudget object. | `number` | `0` | no |
61 | | [replicas](#input\_replicas) | The number of replicas of the webhook server to run. | `number` | `3` | no |
62 | | [service\_monitor\_custom\_labels](#input\_service\_monitor\_custom\_labels) | Custom labels to add to the `ServiceMonitor` object. | `map` | `{}` | no |
63 | | [service\_monitor\_enable](#input\_service\_monitor\_enable) | If true a ServiceMonitor object will be created, and a /metrics endpoint will be exposed. **NOTE:** this requires the Prometheus operator to be running on the target cluster. | `bool` | `true` | no |
64 | | [target\_vault\_address](#input\_target\_vault\_address) | The value to be passed to the -target-vault-address flag. | `string` | `"https://vault:8200"` | no |
65 | | [vault\_image\_version](#input\_vault\_image\_version) | The value to be passed to the -vault-image-version flag. | `string` | `"1.4.0"` | no |
66 | | [webhook\_namespace\_selector\_expressions](#input\_webhook\_namespace\_selector\_expressions) | The list of expressions to match the namespaces where this webhook will operate. | list(object({
key = string
operator = string
})) | [| no | 67 | 68 | ## Outputs 69 | 70 | No outputs. 71 | -------------------------------------------------------------------------------- /cmd/webhook_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "time" 7 | 8 | appsv1 "k8s.io/api/apps/v1" 9 | apiv1 "k8s.io/api/core/v1" 10 | "k8s.io/apimachinery/pkg/api/resource" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | "k8s.io/apimachinery/pkg/util/wait" 13 | "k8s.io/client-go/kubernetes" 14 | "k8s.io/client-go/tools/clientcmd" 15 | ) 16 | 17 | var baseTestAppDeployment = &appsv1.Deployment{ 18 | ObjectMeta: metav1.ObjectMeta{ 19 | Namespace: "test", 20 | }, 21 | Spec: appsv1.DeploymentSpec{ 22 | Selector: &metav1.LabelSelector{ 23 | MatchLabels: map[string]string{}, 24 | }, 25 | Template: apiv1.PodTemplateSpec{ 26 | ObjectMeta: metav1.ObjectMeta{}, 27 | Spec: apiv1.PodSpec{ 28 | Containers: []apiv1.Container{ 29 | { 30 | Image: "alpine", 31 | Command: []string{ 32 | "sh", 33 | "-c", 34 | "while true; do sleep 5; done", 35 | }, 36 | }, 37 | }, 38 | }, 39 | }, 40 | }, 41 | } 42 | 43 | var overrideConfigMap = &apiv1.ConfigMap{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: "override-vault-agent-config", 46 | Namespace: "test", 47 | }, 48 | Data: map[string]string{ 49 | "vault-agent-config.hcl": "vault {}", 50 | }, 51 | } 52 | 53 | var clientset *kubernetes.Clientset 54 | 55 | func createTestAppDeployment(name string, mode string, extraAnnotations map[string]string) *appsv1.Deployment { 56 | testAppDeployment := baseTestAppDeployment 57 | nameMode := name + "-" + mode 58 | testAppDeployment.ObjectMeta.Name = nameMode 59 | testAppDeployment.Spec.Selector.MatchLabels = map[string]string{"app": nameMode} 60 | testAppDeployment.Spec.Template.ObjectMeta.Labels = map[string]string{"app": nameMode} 61 | annotations := map[string]string{"vault.patoarvizu.dev/agent-auto-inject": mode} 62 | for k, v := range extraAnnotations { 63 | annotations[k] = v 64 | } 65 | testAppDeployment.Spec.Template.ObjectMeta.Annotations = annotations 66 | testAppDeployment.Spec.Template.Spec.Containers[0].Name = nameMode 67 | return testAppDeployment 68 | } 69 | 70 | func deployTestAppAndWait(deployment *appsv1.Deployment) (pod *apiv1.Pod, e error) { 71 | deploymentClient := clientset.AppsV1().Deployments(deployment.ObjectMeta.Namespace) 72 | _, err := deploymentClient.Create(deployment) 73 | if err != nil { 74 | return nil, err 75 | } 76 | err = wait.Poll(time.Second, time.Second*10, func() (done bool, err error) { 77 | podList, _ := clientset.CoreV1().Pods(deployment.ObjectMeta.Namespace).List(metav1.ListOptions{ 78 | LabelSelector: "app=" + deployment.ObjectMeta.Name, 79 | }) 80 | if len(podList.Items) > 0 { 81 | return true, nil 82 | } 83 | return false, nil 84 | }) 85 | if err != nil { 86 | return nil, err 87 | } 88 | podList, err := clientset.CoreV1().Pods(deployment.ObjectMeta.Namespace).List(metav1.ListOptions{ 89 | LabelSelector: "app=" + deployment.ObjectMeta.Name, 90 | }) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return &podList.Items[0], nil 95 | } 96 | 97 | func TestMain(m *testing.M) { 98 | kubeconfig := os.Getenv("KUBECONFIG") 99 | config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig) 100 | cs, _ := kubernetes.NewForConfig(config) 101 | clientset = cs 102 | exitCode := m.Run() 103 | deploymentClient := cs.AppsV1().Deployments("test") 104 | deploymentList, _ := deploymentClient.List(metav1.ListOptions{}) 105 | dpb := metav1.DeletePropagationBackground 106 | for _, d := range deploymentList.Items { 107 | deploymentClient.Delete(d.Name, &metav1.DeleteOptions{PropagationPolicy: &dpb}) 108 | } 109 | configMapClient := clientset.CoreV1().ConfigMaps("test") 110 | configMapClient.Delete(overrideConfigMap.ObjectMeta.Name, &metav1.DeleteOptions{}) 111 | os.Exit(exitCode) 112 | } 113 | 114 | func TestOverwriteAgentConfig(t *testing.T) { 115 | configMapClient := clientset.CoreV1().ConfigMaps("test") 116 | configMapClient.Create(overrideConfigMap) 117 | deployment := createTestAppDeployment("test-app-override", "init-container", map[string]string{"vault.patoarvizu.dev/agent-config-map": "override-vault-agent-config"}) 118 | pod, err := deployTestAppAndWait(deployment) 119 | if err != nil { 120 | t.Errorf("Error: %v", err) 121 | } 122 | volumeFound := false 123 | for _, v := range pod.Spec.Volumes { 124 | if v.ConfigMap != nil && v.ConfigMap.Name == "override-vault-agent-config" { 125 | volumeFound = true 126 | } 127 | } 128 | if !volumeFound { 129 | t.Error("Volume 'override-vault-agent-config' is not found") 130 | } 131 | volumeMountFound := false 132 | for _, i := range pod.Spec.InitContainers { 133 | if i.Name != "config-template" { 134 | continue 135 | } 136 | for _, m := range i.VolumeMounts { 137 | if m.Name == "vault-config-template" { 138 | volumeMountFound = true 139 | } 140 | } 141 | } 142 | if !volumeMountFound { 143 | t.Error("Volume mount 'vault-config-template' is not found") 144 | } 145 | } 146 | 147 | func TestWebhookInit(t *testing.T) { 148 | deployment := createTestAppDeployment("test-app-init-container", "init-container", nil) 149 | pod, err := deployTestAppAndWait(deployment) 150 | if err != nil { 151 | t.Errorf("Error: %v", err) 152 | } 153 | foundVaultAgentInitContainer := func() bool { 154 | for _, i := range pod.Spec.InitContainers { 155 | if i.Name == "vault-agent" { 156 | return true 157 | } 158 | } 159 | return false 160 | }() 161 | if !foundVaultAgentInitContainer { 162 | t.Error("Init container 'vault-agent' wasn't injected when agent-auto-inject annotation is 'init-cintainer'") 163 | } 164 | } 165 | 166 | func TestWebhookSidecar(t *testing.T) { 167 | deployment := createTestAppDeployment("test-app-sidecar", "sidecar", nil) 168 | pod, err := deployTestAppAndWait(deployment) 169 | if err != nil { 170 | t.Errorf("Error: %v", err) 171 | } 172 | foundVolume := func() bool { 173 | for _, v := range pod.Spec.Volumes { 174 | if v.Name == "vault-tls" { 175 | return true 176 | } 177 | } 178 | return false 179 | }() 180 | if !foundVolume { 181 | t.Error("Volume 'vault-tls' not found") 182 | } 183 | foundVaultAgentContainer := func() bool { 184 | for _, c := range pod.Spec.Containers { 185 | if c.Name == "vault-agent" { 186 | return true 187 | } 188 | } 189 | return false 190 | }() 191 | if !foundVaultAgentContainer { 192 | t.Error("Sidecar container 'vault-agent' not found") 193 | } 194 | foundCaCertVolumeMount := func() bool { 195 | for _, c := range pod.Spec.Containers { 196 | if c.Name == "vault-agent" { 197 | for _, m := range c.VolumeMounts { 198 | if m.Name == "vault-tls" { 199 | return true 200 | } 201 | } 202 | } 203 | } 204 | return false 205 | }() 206 | if !foundCaCertVolumeMount { 207 | t.Error("Volume mount 'vault-tls' for sidecar container not found") 208 | } 209 | foundConfigTemplateInitContainer := func() bool { 210 | for _, i := range pod.Spec.InitContainers { 211 | if i.Name == "config-template" { 212 | return true 213 | } 214 | } 215 | return false 216 | }() 217 | if !foundConfigTemplateInitContainer { 218 | t.Error("Init container 'config-template' not found") 219 | } 220 | foundVaultAddrEnvironmentVariable := func() bool { 221 | foundInAllContainers := true 222 | for _, c := range pod.Spec.Containers { 223 | found := false 224 | if c.Name == "vault-agent" { 225 | continue 226 | } 227 | for _, e := range c.Env { 228 | if e.Name == "VAULT_ADDR" { 229 | found = true 230 | } 231 | } 232 | foundInAllContainers = foundInAllContainers && found 233 | } 234 | return foundInAllContainers 235 | }() 236 | if !foundVaultAddrEnvironmentVariable { 237 | t.Error("Environment variable 'VAULT_ADDR' not found") 238 | } 239 | foundServiceAccountMount := func() bool { 240 | for _, c := range pod.Spec.Containers { 241 | if c.Name != "vault-agent" { 242 | continue 243 | } 244 | for _, m := range c.VolumeMounts { 245 | if m.MountPath == "/var/run/secrets/kubernetes.io/serviceaccount" { 246 | return true 247 | } 248 | } 249 | } 250 | return false 251 | }() 252 | if !foundServiceAccountMount { 253 | t.Error("Service account with path '/var/run/secrets/kubernetes.io/serviceaccount' not found in 'vault-agent' sidecar") 254 | } 255 | } 256 | 257 | func TestDefaultResources(t *testing.T) { 258 | deployment := createTestAppDeployment("test-app-default-resources", "sidecar", nil) 259 | pod, err := deployTestAppAndWait(deployment) 260 | if err != nil { 261 | t.Errorf("Error: %v", err) 262 | } 263 | for _, c := range pod.Spec.Containers { 264 | if c.Name != "vault-agent" { 265 | continue 266 | } 267 | if !c.Resources.Requests.Cpu().Equal(resource.MustParse("50m")) { 268 | t.Error("CPU request isn't the default '50m'") 269 | } 270 | if !c.Resources.Limits.Cpu().Equal(resource.MustParse("100m")) { 271 | t.Error("CPU limit isn't the default '100m'") 272 | } 273 | if !c.Resources.Requests.Memory().Equal(resource.MustParse("128Mi")) { 274 | t.Error("Memory request isn't the default '128Mi'") 275 | } 276 | if !c.Resources.Limits.Memory().Equal(resource.MustParse("256Mi")) { 277 | t.Error("Memory limit isn't the default '256Mi'") 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /cmd/webhook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "flag" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "time" 11 | 12 | "github.com/radovskyb/watcher" 13 | whhttp "github.com/slok/kubewebhook/pkg/http" 14 | "github.com/slok/kubewebhook/pkg/log" 15 | mutatingwh "github.com/slok/kubewebhook/pkg/webhook/mutating" 16 | corev1 "k8s.io/api/core/v1" 17 | "k8s.io/apimachinery/pkg/api/resource" 18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 | 20 | "github.com/prometheus/client_golang/prometheus" 21 | "github.com/prometheus/client_golang/prometheus/promhttp" 22 | "github.com/slok/kubewebhook/pkg/observability/metrics" 23 | ) 24 | 25 | const ( 26 | sidecarInjectionMode = "sidecar" 27 | initContainerInjectionMode = "init-container" 28 | agentAutoInjectAnnotation = "agent-auto-inject" 29 | configMapOverrideAnnotation = "agent-config-map" 30 | vaultAgentVolumeMountName = "vault-agent" 31 | vaultAgentVolumeMountPath = "/vault-agent" 32 | caCertMountPath = "/opt/vault/certs" 33 | ) 34 | 35 | type webhookCfg struct { 36 | certFile string 37 | keyFile string 38 | addr string 39 | metricsAddr string 40 | annotationPrefix string 41 | targetVaultAddress string 42 | kubernetesAuthPath string 43 | vaultImageVersion string 44 | defaultConfigMapName string 45 | cpuRequest string 46 | cpuLimit string 47 | memoryRequest string 48 | memoryLimit string 49 | mountCACertSecret bool 50 | caCertSecretName string 51 | gomplateImage string 52 | } 53 | 54 | var cfg = &webhookCfg{} 55 | var injectionMode string 56 | var cachedCertificate tls.Certificate 57 | 58 | func getServiceAccountMount(containers []corev1.Container) (serviceAccountMount corev1.VolumeMount) { 59 | mountSearch: 60 | for _, container := range containers { 61 | for _, mount := range container.VolumeMounts { 62 | if mount.MountPath == "/var/run/secrets/kubernetes.io/serviceaccount" { 63 | serviceAccountMount = mount 64 | break mountSearch 65 | } 66 | } 67 | } 68 | return serviceAccountMount 69 | } 70 | 71 | func injectVaultSidecar(_ context.Context, obj metav1.Object) (bool, error) { 72 | logger := &log.Std{} 73 | pod, ok := obj.(*corev1.Pod) 74 | if !ok { 75 | return false, nil 76 | } 77 | 78 | if pod.Annotations == nil || len(pod.Annotations) == 0 { 79 | return false, nil 80 | } 81 | 82 | if pod.Annotations["vault-sidecar-injected"] == "true" { 83 | return false, nil 84 | } 85 | 86 | if val, ok := pod.Annotations[fmt.Sprintf("%s/%s", cfg.annotationPrefix, agentAutoInjectAnnotation)]; !ok && val != sidecarInjectionMode && val != initContainerInjectionMode { 87 | return false, nil 88 | } else { 89 | injectionMode = val 90 | } 91 | 92 | configMapName := cfg.defaultConfigMapName 93 | if val, ok := pod.Annotations[fmt.Sprintf("%s/%s", cfg.annotationPrefix, configMapOverrideAnnotation)]; ok && val != "" { 94 | configMapName = val 95 | } 96 | serviceAccountMount := getServiceAccountMount(pod.Spec.Containers) 97 | logger.Infof("Injecting Vault sidecar into pod with service account %s", pod.Spec.ServiceAccountName) 98 | for i, c := range pod.Spec.Containers { 99 | if injectionMode == initContainerInjectionMode { 100 | pod.Spec.Containers[i].VolumeMounts = append(pod.Spec.Containers[i].VolumeMounts, corev1.VolumeMount{ 101 | Name: vaultAgentVolumeMountName, 102 | MountPath: vaultAgentVolumeMountPath, 103 | }) 104 | } else { 105 | found := false 106 | for _, e := range c.Env { 107 | if e.Name == "VAULT_ADDR" { 108 | e.Value = "http://127.0.0.1:8200" 109 | found = true 110 | } 111 | } 112 | if !found { 113 | pod.Spec.Containers[i].Env = append(pod.Spec.Containers[i].Env, corev1.EnvVar{Name: "VAULT_ADDR", Value: "http://127.0.0.1:8200"}) 114 | } 115 | } 116 | } 117 | 118 | pod.Spec.Volumes = append(pod.Spec.Volumes, 119 | corev1.Volume{ 120 | Name: "vault-config", 121 | VolumeSource: corev1.VolumeSource{ 122 | EmptyDir: &corev1.EmptyDirVolumeSource{ 123 | Medium: corev1.StorageMediumMemory, 124 | }, 125 | }, 126 | }, 127 | corev1.Volume{ 128 | Name: "vault-config-template", 129 | VolumeSource: corev1.VolumeSource{ 130 | ConfigMap: &corev1.ConfigMapVolumeSource{ 131 | LocalObjectReference: corev1.LocalObjectReference{ 132 | Name: configMapName, 133 | }, 134 | }, 135 | }, 136 | }, 137 | ) 138 | 139 | if cfg.mountCACertSecret { 140 | defaultMode := int32(0644) 141 | optional := bool(true) 142 | pod.Spec.Volumes = append(pod.Spec.Volumes, 143 | corev1.Volume{ 144 | Name: cfg.caCertSecretName, 145 | VolumeSource: corev1.VolumeSource{ 146 | Secret: &corev1.SecretVolumeSource{ 147 | SecretName: cfg.caCertSecretName, 148 | Optional: &optional, 149 | DefaultMode: &defaultMode, 150 | }, 151 | }, 152 | }, 153 | ) 154 | } 155 | 156 | if injectionMode == initContainerInjectionMode { 157 | pod.Spec.Volumes = append(pod.Spec.Volumes, 158 | corev1.Volume{ 159 | Name: vaultAgentVolumeMountName, 160 | VolumeSource: corev1.VolumeSource{ 161 | EmptyDir: &corev1.EmptyDirVolumeSource{ 162 | Medium: corev1.StorageMediumMemory, 163 | }, 164 | }, 165 | }, 166 | ) 167 | } 168 | 169 | pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{ 170 | Name: "config-template", 171 | Image: cfg.gomplateImage, 172 | Command: []string{ 173 | "/gomplate", 174 | "--file", 175 | "/etc/template/vault-agent-config.hcl", 176 | "--out", 177 | "/etc/vault/vault-agent-config.hcl", 178 | }, 179 | Env: []corev1.EnvVar{ 180 | { 181 | Name: "SERVICE", 182 | Value: pod.Spec.ServiceAccountName, 183 | }, 184 | { 185 | Name: "TARGET_VAULT_ADDRESS", 186 | Value: cfg.targetVaultAddress, 187 | }, 188 | { 189 | Name: "KUBERNETES_AUTH_PATH", 190 | Value: cfg.kubernetesAuthPath, 191 | }, 192 | }, 193 | VolumeMounts: []corev1.VolumeMount{ 194 | { 195 | Name: "vault-config", 196 | MountPath: "/etc/vault", 197 | }, 198 | { 199 | Name: "vault-config-template", 200 | MountPath: "/etc/template", 201 | }, 202 | }, 203 | }) 204 | 205 | caCertVolumeMount := corev1.VolumeMount{ 206 | Name: cfg.caCertSecretName, 207 | MountPath: caCertMountPath, 208 | ReadOnly: true, 209 | } 210 | volumeMounts := []corev1.VolumeMount{ 211 | { 212 | Name: "vault-config", 213 | MountPath: "/etc/vault", 214 | }, 215 | serviceAccountMount, 216 | } 217 | if cfg.mountCACertSecret { 218 | volumeMounts = append(volumeMounts, caCertVolumeMount) 219 | } 220 | if injectionMode == sidecarInjectionMode { 221 | pod.Spec.Containers = append(pod.Spec.Containers, corev1.Container{ 222 | Name: "vault-agent", 223 | Image: "vault:" + cfg.vaultImageVersion, 224 | Args: []string{ 225 | "agent", 226 | "-config=/etc/vault/vault-agent-config.hcl", 227 | }, 228 | VolumeMounts: volumeMounts, 229 | Resources: corev1.ResourceRequirements{ 230 | Limits: corev1.ResourceList{ 231 | corev1.ResourceCPU: resource.MustParse(cfg.cpuLimit), 232 | corev1.ResourceMemory: resource.MustParse(cfg.memoryLimit), 233 | }, 234 | Requests: corev1.ResourceList{ 235 | corev1.ResourceCPU: resource.MustParse(cfg.cpuRequest), 236 | corev1.ResourceMemory: resource.MustParse(cfg.memoryRequest), 237 | }, 238 | }, 239 | }) 240 | } else if injectionMode == initContainerInjectionMode { 241 | pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{ 242 | Name: "vault-agent", 243 | Image: "vault:" + cfg.vaultImageVersion, 244 | Args: []string{ 245 | "agent", 246 | "-config=/etc/vault/vault-agent-config.hcl", 247 | }, 248 | VolumeMounts: append(volumeMounts, corev1.VolumeMount{ 249 | Name: vaultAgentVolumeMountName, 250 | MountPath: vaultAgentVolumeMountPath, 251 | }), 252 | Resources: corev1.ResourceRequirements{ 253 | Limits: corev1.ResourceList{ 254 | corev1.ResourceCPU: resource.MustParse(cfg.cpuLimit), 255 | corev1.ResourceMemory: resource.MustParse(cfg.memoryLimit), 256 | }, 257 | Requests: corev1.ResourceList{ 258 | corev1.ResourceCPU: resource.MustParse(cfg.cpuRequest), 259 | corev1.ResourceMemory: resource.MustParse(cfg.memoryRequest), 260 | }, 261 | }, 262 | }) 263 | } 264 | 265 | if pod.Annotations == nil { 266 | pod.Annotations = make(map[string]string) 267 | } 268 | pod.Annotations["vault-sidecar-injected"] = "true" 269 | 270 | return false, nil 271 | } 272 | 273 | func main() { 274 | logger := &log.Std{} 275 | logger.Infof("Starting webhook!") 276 | 277 | fl := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 278 | fl.StringVar(&cfg.certFile, "tls-cert-file", "", "TLS certificate file") 279 | fl.StringVar(&cfg.keyFile, "tls-key-file", "", "TLS key file") 280 | fl.StringVar(&cfg.annotationPrefix, "annotation-prefix", "vault.patoarvizu.dev", "Prefix of the annotations the webhook will process") 281 | fl.StringVar(&cfg.targetVaultAddress, "target-vault-address", "https://vault:8200", "Address of remote Vault API") 282 | fl.StringVar(&cfg.kubernetesAuthPath, "kubernetes-auth-path", "auth/kubernetes", "Path to Vault Kubernetes auth endpoint") 283 | fl.StringVar(&cfg.vaultImageVersion, "vault-image-version", "1.3.0", "Tag on the 'vault' Docker image to inject with the sidecar") 284 | fl.StringVar(&cfg.gomplateImage, "gomplate-image", "hairyhenderson/gomplate:v3", "The full name (repository and tag) of the gomplate image for the init container") 285 | fl.StringVar(&cfg.defaultConfigMapName, "default-config-map-name", "vault-agent-config", "The name of the ConfigMap to be used for the Vault agent configuration by default, unless overwritten by annotation") 286 | fl.BoolVar(&cfg.mountCACertSecret, "mount-ca-cert-secret", false, "Indicate if the Secret indicated by the -ca-cert-secret-name flag should be mounted on the Vault agent container") 287 | fl.StringVar(&cfg.caCertSecretName, "ca-cert-secret-name", "vault-tls", "The name of the secret in the target namespace to mount and use as a CA cert") 288 | fl.StringVar(&cfg.cpuRequest, "cpu-request", "50m", "The amount of CPU units to request for the Vault agent sidecar") 289 | fl.StringVar(&cfg.cpuLimit, "cpu-limit", "100m", "The amount of CPU units to limit to on the Vault agent sidecar") 290 | fl.StringVar(&cfg.memoryRequest, "memory-request", "128Mi", "The amount of memory units to request for the Vault agent sidecar") 291 | fl.StringVar(&cfg.memoryLimit, "memory-limit", "256Mi", "The amount of memory units to limit to on the Vault agent sidecar") 292 | fl.StringVar(&cfg.addr, "listen-addr", ":4443", "The address to start the server") 293 | fl.StringVar(&cfg.metricsAddr, "metrics-addr", ":8081", "The address where the Prometheus-style metrics are published") 294 | 295 | fl.Parse(os.Args[1:]) 296 | 297 | w := watcher.New() 298 | defer w.Close() 299 | w.SetMaxEvents(1) 300 | w.FilterOps(watcher.Write) 301 | err := w.Add(cfg.certFile) 302 | if err != nil { 303 | logger.Errorf("Error: %v", err) 304 | } 305 | go func() { 306 | for { 307 | select { 308 | case <-w.Event: 309 | err = cacheCertificate(cfg.certFile, cfg.keyFile) 310 | if err != nil { 311 | logger.Errorf("Error refreshing certificate: %v", err) 312 | os.Exit(1) 313 | } 314 | case <-w.Closed: 315 | logger.Errorf("Certificate file watch closed") 316 | os.Exit(1) 317 | } 318 | } 319 | }() 320 | go w.Start(time.Millisecond * 100) 321 | 322 | pm := mutatingwh.MutatorFunc(injectVaultSidecar) 323 | 324 | mcfg := mutatingwh.WebhookConfig{ 325 | Name: "vaultSidecarInjector", 326 | Obj: &corev1.Pod{}, 327 | } 328 | reg := prometheus.NewRegistry() 329 | metricsRec := metrics.NewPrometheus(reg) 330 | wh, err := mutatingwh.NewWebhook(mcfg, pm, nil, metricsRec, logger) 331 | if err != nil { 332 | logger.Errorf("Error creating webhook: %v", err) 333 | os.Exit(1) 334 | } 335 | whHandler, err := whhttp.HandlerFor(wh) 336 | if err != nil { 337 | logger.Errorf("Error creating webhook handler: %v", err) 338 | os.Exit(1) 339 | } 340 | webhookError := make(chan error) 341 | go func() { 342 | err = cacheCertificate(cfg.certFile, cfg.keyFile) 343 | if err != nil { 344 | logger.Errorf("Error loading certificate: %v", err) 345 | os.Exit(1) 346 | } 347 | server := http.Server{Addr: cfg.addr, Handler: whHandler, TLSConfig: &tls.Config{GetCertificate: getCertificate}} 348 | webhookError <- server.ListenAndServeTLS(cfg.certFile, cfg.keyFile) 349 | }() 350 | metricsError := make(chan error) 351 | promHandler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) 352 | go func() { 353 | metricsError <- http.ListenAndServe(cfg.metricsAddr, promHandler) 354 | }() 355 | if <-webhookError != nil { 356 | logger.Errorf("Error serving webhook: %v", <-webhookError) 357 | os.Exit(1) 358 | } 359 | if <-metricsError != nil { 360 | logger.Errorf("Error serving metrics: %v", <-metricsError) 361 | os.Exit(1) 362 | } 363 | } 364 | 365 | func cacheCertificate(certfile, keyfile string) error { 366 | cert, err := tls.LoadX509KeyPair(certfile, keyfile) 367 | if err != nil { 368 | return err 369 | } 370 | cachedCertificate = cert 371 | return nil 372 | } 373 | 374 | func getCertificate(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { 375 | return &cachedCertificate, nil 376 | } 377 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vault agent auto-inject webhook 2 | 3 |  4 |       5 | 6 | 7 | 8 | - [Intro](#intro) 9 | - [Running the webhook](#running-the-webhook) 10 | - [Configuring the webhook](#configuring-the-webhook) 11 | - [Webhook command-line flags](#webhook-command-line-flags) 12 | - [ConfigMap](#configmap) 13 | - [Auto-mount CA cert](#auto-mount-ca-cert) 14 | - [Init containers](#init-containers) 15 | - [Metrics](#metrics) 16 | - [Auto-reloading certificate](#auto-reloading-certificate) 17 | - [For security nerds](#for-security-nerds) 18 | - [Docker images are signed and published to Docker Hub's Notary server](#docker-images-are-signed-and-published-to-docker-hubs-notary-server) 19 | - [Docker images are labeled with Git and GPG metadata](#docker-images-are-labeled-with-git-and-gpg-metadata) 20 | - [Multi-architecture images](#multi-architecture-images) 21 | - [Help wanted!](#help-wanted) 22 | 23 | 24 | 25 | ## Intro 26 | 27 | This webhook is a companion to the [`vault-dynamic-configuration-operator`](https://github.com/patoarvizu/vault-dynamic-configuration-operator) but it can be deployed indepentendly as a Kubernetes [Mutating Webhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/), that can modify Pods to automatically inject a Vault agent sidecar, including a rendered configuration template taken from a `ConfigMap` corresponding to the service's identity, as well as modify the environment variables on all containers in the Pod to inject a `VAULT_ADDR` environment variable that points to the sidecar agent. To do this, annotate your workload (`Deployment`, `StatefulSet`, `DaemonSet`, etc.) with `vault.patoarvizu.dev/agent-auto-inject: sidecar` to have the webhook modify the generated Pods as they are created. 28 | 29 | ### Running the webhook 30 | 31 | The webhook can be run as a `Deployment` on the same cluster, as long as it's exposed as a `Service`, and accepts TLS connections. More details on how to deploy mutating webhooks in Kubernetes can be found on the link above, but this section will cover high-level details. 32 | 33 | Your `Deployment` and `Service` will look like those of any other service you run in your cluster. One important difference is that this service has to serve TLS, so the `-tls-cert-file`, and `-tls-key-file` parameters have to be supplied. Your `Deployment` manifest will look something like this: 34 | 35 | ```yaml 36 | kind: Deployment 37 | ... 38 | containers: 39 | - name: vault-agent-auto-inject-webhook 40 | image: patoarvizu/vault-agent-auto-inject-webhook:latest 41 | command: 42 | - /vault-agent-auto-inject-webhook 43 | - -tls-cert-file 44 | - /tls/tls.crt 45 | - -tls-key-file 46 | - /tls/tls.key 47 | ports: 48 | - name: https 49 | containerPort: 4443 50 | volumeMounts: 51 | - name: tls 52 | mountPath: /tls 53 | volumes: 54 | - name: tls 55 | secret: 56 | secretName: vault-agent-auto-inject-webhook 57 | ``` 58 | 59 | This assumes that there is a `Secret` called `vault-agent-auto-inject-webhook` that contains the `tls.crt` and `tls.key` files that can be mounted on the container and passed to the webhook. The [`cert-manager`](https://github.com/jetstack/cert-manager/) project makes it really easy to generate certificates as Kubernetes `Secret`s to be used for cases like this. 60 | 61 | ### Configuring the webhook 62 | 63 | The other important piece is deploying the `MutatingWebhookConfiguration` itself, which would look like this: 64 | 65 | ```yaml 66 | apiVersion: admissionregistration.k8s.io/v1beta1 67 | kind: MutatingWebhookConfiguration 68 | metadata: 69 | name: vault-agent-auto-inject-webhook 70 | labels: 71 | app: vault-agent-auto-inject-webhook 72 | webhooks: 73 | - name: vault.patoarvizu.dev 74 | rules: 75 | - apiGroups: 76 | - "" 77 | apiVersions: 78 | - v1 79 | operations: 80 | - CREATE 81 | - UPDATE 82 | resources: 83 | - pods 84 | failurePolicy: Ignore 85 | clientConfig: 86 | caBundle: ${CA_BUNDLE} 87 | service: 88 | name: vault-agent-auto-inject-webhook 89 | namespace: default 90 | path: / 91 | ``` 92 | 93 | The `clientConfig` map field should match the `Service` that sits in front of your `Deployment` from above. Notice that the `caBundle` field only contains the `${CA_BUNDLE}` placeholder. The actual value of this should be the base64-encoded public CA certificate that signed the `tls.crt` that your webhook is running with, which will depend on how those certificates were generated. 94 | 95 | **TIP:** If you created the webhook certificates above using `cert-manager`, you can use the [`cert-manager.io/inject-ca-from` annotation](https://docs.cert-manager.io/en/latest/reference/cainjector.html) on the `MutatingWebhookConfiguration` and `cert-manager` will automatically inject the corresponding CA cert into the object. 96 | 97 | ### Webhook command-line flags 98 | 99 | Flag | Description | Default 100 | -----|-------------|-------- 101 | `-tls-cert-file` | TLS certificate file | 102 | `-tls-key-file` | TLS key file | 103 | `-annotation-prefix` | Prefix of the annotations the webhook will process | `vault.patoarvizu.dev` 104 | `-target-vault-address` | Address of remote Vault API | `https://vault:8200` 105 | `-gomplate-image` | The full name (repository and tag) of the gomplate image for the init container | `hairyhenderson/gomplate:v3` 106 | `-kubernetes-auth-path` | Path to Vault Kubernetes auth endpoint | `auth/kubernetes` 107 | `-vault-image-version` | Tag on the 'vault' Docker image to inject with the sidecar | `1.3.0` 108 | `-default-config-map-name` | The name of the ConfigMap to be used for the Vault agent configuration by default, unless overwritten by annotation | `vault-agent-config` 109 | `-mount-ca-cert-secret` | Indicate if the Secret indicated by the -ca-cert-secret-name flag should be mounted on the Vault agent container | `false` 110 | `-ca-cert-secret-name` | The name of the secret in the target namespace to mount and use as a CA cert | `vault-tls` 111 | `-cpu-request` | The amount of CPU units to request for the Vault agent sidecar") | `50m` 112 | `-cpu-limit` | The amount of CPU units to limit to on the Vault agent sidecar") | `100m` 113 | `-memory-request` | The amount of memory units to request for the Vault agent sidecar") | `128Mi` 114 | `-memory-limit` | The amount of memory units to limit to on the Vault agent sidecar") | `256Mi` 115 | `-listen-addr` | The address to start the server | `:4443` 116 | `-metrics-addr` | The address where the Prometheus-style metrics are published | `:8081` 117 | 118 | ### ConfigMap 119 | 120 | The webhook expects that a `ConfigMap` named `vault-agent-config` (or something else, if the `-default-config-map-name` was passed to the server) will exist in the same namespace as the target Pod (**NOT** in the same namespace as the webhook itself), that will contain only one key, called `vault-agent-config.hcl`, which will contain a [Go template](https://golang.org/pkg/text/template/) that will be rendered into the Vault agent configuration using [`gomplate`](https://github.com/hairyhenderson/gomplate), and will have the following environment variables available to be discovered with the `getenv` function: 121 | 122 | Environment variable | Value 123 | ---------------------|------ 124 | `SERVICE` | The name of the `ServiceAccount` attached to the pod 125 | `TARGET_VAULT_ADDRESS` | The value of the `-target-vault-address` parameter (or its default) 126 | `KUBERNETES_AUTH_PATH` | The value of the `-kubernetes-auth-path` parameter (or its default) 127 | 128 | ### Auto-mount CA cert 129 | 130 | If enabled with the `-mount-ca-cert-secret` flag, the webhook can automatically create a volume from the secret indicated by the `-ca-cert-secret-name` flag. The volume will then be mounted at `/opt/vault/certs/` on the Vault agent container **only**, so the `vault-agent-config.hcl` file can use the [`ca_cert` field](https://www.vaultproject.io/docs/agent/index.html#inlinecode-ca_cert-string-optional-1) in the `vault` stanza, instead of skipping verification with `tls_skip_verify = true`. 131 | 132 | ### Init containers 133 | 134 | Alternatively, the webhook can inject the Vault agent as an init container instead of a sidecar, which is useful for short-lived workloads, like `Job`s and `CronJob`s. In that case, the init container should use a configuration that has `exit_after_auth = true` so the init container exists after authenticating and doesn't remain long-lived. Doing so, would cause the container to never exit past the init container phase. The config file should also contain at least one [file sink](https://www.vaultproject.io/docs/agent/autoauth/sinks/file.html). The webhook will also modify the containers to mount an additional volume on `/vault-agent` that can be used as a file sink. 135 | 136 | To do this, annotate your workload with `vault.patoarvizu.dev/agent-auto-inject: init-container`. 137 | 138 | Usually, a given config file will only be suitable for either long-lived sidecars or short-lived init containers. If the default config map (`vault-agent-config` by default, or the overwrite if `-default-config-map-name` was provided) is not suitable for a specific application, it can be overwritten with the `vault.patoarvizu.dev/agent-config-map` annotation. If set, the value should be the name of a `ConfigMap` in the same namespace that that the webhook should use to inject, instead of the default one. 139 | 140 | ### Metrics 141 | 142 | The webhook will also expose Prometheus-style metrics on port HTTP/8081 (unless overwritten with `-metrics-addr`), ready to be scraped. The metrics are provided by the underlying [slok/kubewebhook](https://github.com/slok/kubewebhook) framework and include `admission_reviews_total`, `admission_review_errors_total`, and `admission_review_duration_seconds`. 143 | 144 | ### Auto-reloading certificate 145 | 146 | The server performs a hot reload if the underlying TLS certificate (indicated by the `-tls-cert-file` flag) on disk is modified. This is helpful when using automatic certificate provisioners like cert-manager that will do automatic rotation of the certificates but can't control the lifecycle of the workloads using the certificate. 147 | 148 | The way this is achieved is by initially loading the certificate and keeping it in a local cache, then using the [radovskyb/watcher](https://github.com/radovskyb/watcher) library to watch for changes on the file and updating the cached version if the file changes. 149 | 150 | ## For security nerds 151 | 152 | **NOTE:** Due to technical issues with the Notary client, starting on January 4th 2023 and until further notice new images will NOT be signed. The images will still be built for multi-architecture, and will include the Git and GPG metadata, but they won't pass Docker Content Trust validation if you have it enabled. 153 | 154 | ### Docker images are signed and published to Docker Hub's Notary server 155 | 156 | The [Notary](https://github.com/theupdateframework/notary) project is a CNCF incubating project that aims to provide trust and security to software distribution. Docker Hub runs a Notary server at https://notary.docker.io for the repositories it hosts. 157 | 158 | [Docker Content Trust](https://docs.docker.com/engine/security/trust/content_trust/) is the mechanism used to verify digital signatures and enforce security by adding a validating layer. 159 | 160 | You can inspect the signed tags for this project by doing `docker trust inspect --pretty docker.io/patoarvizu/vault-agent-auto-inject-webhook`, or (if you already have `notary` installed) `notary -d ~/.docker/trust/ -s https://notary.docker.io list docker.io/patoarvizu/vault-agent-auto-inject-webhook`. 161 | 162 | If you run `docker pull` with `DOCKER_CONTENT_TRUST=1`, the Docker client will only pull images that come from registries that have a Notary server attached (like Docker Hub). 163 | 164 | ### Docker images are labeled with Git and GPG metadata 165 | 166 | In addition to the digital validation done by Docker on the image itself, you can do your own human validation by making sure the image's content matches the Git commit information (including tags if there are any) and that the GPG signature on the commit matches the key on the commit on github.com. 167 | 168 | For example, if you run `docker pull patoarvizu/vault-agent-auto-inject-webhook:c1201e30e90d9d8fd2f2f65f2552236013cdcbe8` to pull the image tagged with that commit id, then run `docker inspect patoarvizu/vault-agent-auto-inject-webhook:c1201e30e90d9d8fd2f2f65f2552236013cdcbe8 | jq -r '.[0].ContainerConfig.Labels'` (assuming you have [jq](https://stedolan.github.io/jq/) installed) you should see that the `GIT_COMMIT` label matches the tag on the image. Furthermore, if you go to https://github.com/patoarvizu/vault-agent-auto-inject-webhook/commit/c1201e30e90d9d8fd2f2f65f2552236013cdcbe8 (notice the matching commit id), and click on the **Verified** button, you should be able to confirm that the GPG key ID used to match this commit matches the value of the `SIGNATURE_KEY` label, and that the key belongs to the `AUTHOR_EMAIL` label. When an image belongs to a commit that was tagged, it'll also include a `GIT_TAG` label, to further validate that the image matches the content. 169 | 170 | Keep in mind that this isn't tamper-proof. A malicious actor with access to publish images can create one with malicious content but with values for the labels matching those of a valid commit id. However, when combined with Docker Content Trust, the certainty of using a legitimate image is increased because the chances of a bad actor having both the credentials for publishing images, as well as Notary signing credentials are significantly lower and even in that scenario, compromised signing keys can be revoked or rotated. 171 | 172 | Here's the list of included Docker labels: 173 | 174 | - `AUTHOR_EMAIL` 175 | - `COMMIT_TIMESTAMP` 176 | - `GIT_COMMIT` 177 | - `GIT_TAG` 178 | - `SIGNATURE_KEY` 179 | 180 | ## Multi-architecture images 181 | 182 | Manifests published with the semver tag (e.g. `patoarvizu/vault-agent-auto-inject-webhook:v0.5.0`), as well as `latest` are multi-architecture manifest lists. In addition to those, there are architecture-specific tags that correspond to an image manifest directly, tagged with the corresponding architecture as a suffix, e.g. `v0.15.0-amd64`. Both types (image manifests or manifest lists) are signed with Notary as described above. 183 | 184 | Here's the list of architectures the images are being built for, and their corresponding suffixes for images: 185 | 186 | - `linux/amd64`, `-amd64` 187 | - `linux/arm64`, `-arm64` 188 | - `linux/arm/v7`, `-arm7` 189 | 190 | ## Help wanted! 191 | 192 | All Issues or PRs on this repo are welcome, even if it's for a typo or an open-ended question. -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | snyk: snyk/snyk@1.1.2 5 | 6 | executors: 7 | vm: 8 | machine: 9 | image: ubuntu-2004:202111-02 10 | resource_class: large 11 | environment: 12 | GOLANG_VERSION: 1.12.10 13 | NOTARY_VERSION: 0.6.1 14 | KUBECTL_VERSION: 1.21.5 15 | K3D_VERSION: 4.4.8 16 | GOPATH: /home/circleci/go 17 | GO111MODULE: "on" 18 | DOCKER_CLI_EXPERIMENTAL: enabled 19 | working_directory: /home/circleci/go/src/github.com/patoarvizu/vault-agent-auto-inject-webhook 20 | vm-arm: 21 | machine: 22 | image: ubuntu-2004:202111-02 23 | resource_class: arm.medium 24 | environment: 25 | GOLANG_VERSION: 1.12.10 26 | NOTARY_VERSION: 0.6.1 27 | KUBECTL_VERSION: 1.21.5 28 | K3D_VERSION: 4.4.8 29 | GOPATH: /home/circleci/go 30 | GO111MODULE: "on" 31 | DOCKER_CLI_EXPERIMENTAL: enabled 32 | working_directory: /home/circleci/go/src/github.com/patoarvizu/vault-agent-auto-inject-webhook 33 | 34 | jobs: 35 | test: 36 | executor: vm 37 | steps: 38 | - checkout 39 | - restore_cache: 40 | keys: 41 | - vault-agent-auto-inject-webhook-golang-cache-{{ checksum "go.sum" }} 42 | - run: 43 | name: Install golang 44 | command: | 45 | sudo rm -rf /usr/local/go 46 | curl -Lo go.linux-amd64.tar.gz "https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz" 47 | sudo tar -C /usr/local -xzf go.linux-amd64.tar.gz 48 | mkdir -p ${HOME}/go/bin 49 | echo 'export PATH="$GOPATH/bin:$PATH"' >> "${BASH_ENV}" 50 | - run: 51 | name: Build image 52 | command: | 53 | docker build -t patoarvizu/vault-agent-auto-inject-webhook:latest . 54 | - snyk/scan: 55 | docker-image-name: patoarvizu/vault-agent-auto-inject-webhook:latest 56 | fail-on-issues: true 57 | project: patoarvizu/vault-agent-auto-inject-webhook 58 | severity-threshold: low 59 | - run: 60 | name: Install kubectl 61 | command: | 62 | curl -Lo kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl" 63 | chmod +x kubectl 64 | sudo mv kubectl /usr/local/bin/ 65 | mkdir -p "${HOME}/.kube" 66 | touch "${HOME}/.kube/config" 67 | - run: 68 | name: Install k3d 69 | command: | 70 | wget -q -O - https://raw.githubusercontent.com/rancher/k3d/main/install.sh | TAG=v${K3D_VERSION} bash 71 | - run: 72 | name: Install Helm 73 | command: | 74 | wget https://get.helm.sh/helm-v3.3.1-linux-amd64.tar.gz 75 | tar -zxvf helm-v3.3.1-linux-amd64.tar.gz 76 | chmod +x linux-amd64/helm 77 | sudo mv linux-amd64/helm /usr/local/bin/ 78 | - run: 79 | name: Run tests 80 | command: | 81 | export KUBECONFIG=~/.k3d/k3s-default-config 82 | k3d cluster create --image rancher/k3s:v1.21.8-k3s1 83 | k3d image import patoarvizu/vault-agent-auto-inject-webhook:latest 84 | kubectl apply -f https://raw.githubusercontent.com/patoarvizu/common-manifests/master/cert-manager/cert-manager-v0.14.1.yaml 85 | kubectl rollout status -n cert-manager deployment/cert-manager-webhook -w 86 | kubectl apply -f https://raw.githubusercontent.com/patoarvizu/common-manifests/master/cert-manager/cluster-issuer.yaml 87 | kubectl create ns vault 88 | helm install vault-agent-webhook helm/vault-agent-auto-inject-webhook/ -n vault --set prometheusMonitoring.enable=false --set replicas=1 --set imageVersion=latest 89 | while [ "$(kubectl -n vault get deployment vault-agent-webhook -o jsonpath={.status.readyReplicas})" != "1" ]; do 90 | sleep 1 91 | done 92 | kubectl apply -f test/test-app.yaml 93 | go test github.com/patoarvizu/vault-agent-auto-inject-webhook/cmd -count=1 -v 94 | no_output_timeout: 30m 95 | - save_cache: 96 | key: vault-agent-auto-inject-webhook-golang-cache-{{ checksum "go.sum" }} 97 | paths: 98 | - /home/circleci/go/pkg/mod/cache 99 | 100 | build-and-push-amd64-image: 101 | executor: vm 102 | steps: 103 | - checkout 104 | - run: 105 | name: Install notary 106 | command: | 107 | curl -L https://github.com/theupdateframework/notary/releases/download/v${NOTARY_VERSION}/notary-Linux-amd64 -o notary 108 | chmod +x notary 109 | sudo mv notary /usr/local/bin 110 | - run: 111 | name: Log in to Docker Hub 112 | command: | 113 | docker login --username $DOCKER_HUB_USER --password $DOCKER_HUB_ACCESS_TOKEN 114 | - run: 115 | name: Build and push amd64 image 116 | command: | 117 | docker buildx create --name cci-builder --use 118 | echo $ENCODED_NOTARY_ROLE_KEY | base64 -d > ${HOME}/vault-agent-auto-inject-webhook-circleci.key 119 | export NOTARY_DELEGATION_PASSPHRASE=$NOTARY_ROLE_PASSPHRASE 120 | notary -s https://notary.docker.io -d ~/.docker/trust key import ${HOME}/vault-agent-auto-inject-webhook-circleci.key --role vault-agent-auto-inject-webhook-circleci 121 | export DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$NOTARY_DELEGATION_PASSPHRASE 122 | OPERATOR_BUILD_ARGS="--build-arg GIT_COMMIT=$CIRCLE_SHA1 --build-arg GIT_TAG=$CIRCLE_TAG --build-arg COMMIT_TIMESTAMP=$(git log -1 --format=%at) --build-arg AUTHOR_EMAIL=$(git log -1 --format=%ae) --build-arg SIGNATURE_KEY=$(git log -1 --format=%GK)" 123 | # export DOCKER_CONTENT_TRUST=1 124 | VERSION=${CIRCLE_TAG:-latest} 125 | docker buildx build --progress=plain --platform=linux/amd64 --load $OPERATOR_BUILD_ARGS . -t patoarvizu/vault-agent-auto-inject-webhook:latest-amd64 -t patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-amd64 -t patoarvizu/vault-agent-auto-inject-webhook:$VERSION-amd64 126 | docker push patoarvizu/vault-agent-auto-inject-webhook:latest-amd64 127 | docker push patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-amd64 128 | docker push patoarvizu/vault-agent-auto-inject-webhook:$VERSION-amd64 129 | 130 | build-arm64-image: 131 | executor: vm-arm 132 | steps: 133 | - checkout 134 | - run: 135 | name: Build arm64 image 136 | command: | 137 | docker buildx create --name cci-builder --use 138 | OPERATOR_BUILD_ARGS="--build-arg GIT_COMMIT=$CIRCLE_SHA1 --build-arg GIT_TAG=$CIRCLE_TAG --build-arg COMMIT_TIMESTAMP=$(git log -1 --format=%at) --build-arg AUTHOR_EMAIL=$(git log -1 --format=%ae) --build-arg SIGNATURE_KEY=$(git log -1 --format=%GK)" 139 | docker buildx build --progress=plain --platform=linux/arm64 --cache-to=type=local,dest=/tmp/latest-arm64-cache,mode=max $OPERATOR_BUILD_ARGS . -t patoarvizu/vault-agent-auto-inject-webhook:latest-arm64 140 | - persist_to_workspace: 141 | root: /tmp/ 142 | paths: 143 | - latest-arm64-cache 144 | 145 | push-arm64-image: 146 | executor: vm 147 | steps: 148 | - checkout 149 | - attach_workspace: 150 | at: /tmp/ 151 | - run: 152 | name: Install notary 153 | command: | 154 | curl -L https://github.com/theupdateframework/notary/releases/download/v${NOTARY_VERSION}/notary-Linux-amd64 -o notary 155 | chmod +x notary 156 | sudo mv notary /usr/local/bin 157 | - run: 158 | name: Log in to Docker Hub 159 | command: | 160 | docker login --username $DOCKER_HUB_USER --password $DOCKER_HUB_ACCESS_TOKEN 161 | - run: 162 | name: Sign and push arm64 image 163 | command: | 164 | sudo apt-get update 165 | sudo apt-get install qemu-user -y 166 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 167 | docker buildx create --name cci-builder --use 168 | echo $ENCODED_NOTARY_ROLE_KEY | base64 -d > ${HOME}/vault-agent-auto-inject-webhook-circleci.key 169 | export NOTARY_DELEGATION_PASSPHRASE=$NOTARY_ROLE_PASSPHRASE 170 | notary -s https://notary.docker.io -d ~/.docker/trust key import ${HOME}/vault-agent-auto-inject-webhook-circleci.key --role vault-agent-auto-inject-webhook-circleci 171 | export DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$NOTARY_DELEGATION_PASSPHRASE 172 | OPERATOR_BUILD_ARGS="--build-arg GIT_COMMIT=$CIRCLE_SHA1 --build-arg GIT_TAG=$CIRCLE_TAG --build-arg COMMIT_TIMESTAMP=$(git log -1 --format=%at) --build-arg AUTHOR_EMAIL=$(git log -1 --format=%ae) --build-arg SIGNATURE_KEY=$(git log -1 --format=%GK)" 173 | # export DOCKER_CONTENT_TRUST=1 174 | VERSION=${CIRCLE_TAG:-latest} 175 | docker buildx build --progress=plain --platform=linux/arm64 --cache-from=type=local,src=/tmp/latest-arm64-cache --load $OPERATOR_BUILD_ARGS . -t patoarvizu/vault-agent-auto-inject-webhook:latest-arm64 176 | docker tag patoarvizu/vault-agent-auto-inject-webhook:latest-arm64 patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-arm64 177 | docker tag patoarvizu/vault-agent-auto-inject-webhook:latest-arm64 patoarvizu/vault-agent-auto-inject-webhook:$VERSION-arm64 178 | docker push patoarvizu/vault-agent-auto-inject-webhook:latest-arm64 179 | docker push patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-arm64 180 | docker push patoarvizu/vault-agent-auto-inject-webhook:$VERSION-arm64 181 | no_output_timeout: 60m 182 | 183 | build-arm7-image: 184 | executor: vm-arm 185 | steps: 186 | - checkout 187 | - run: 188 | name: Build arm7 image 189 | command: | 190 | docker buildx create --name cci-builder --use 191 | OPERATOR_BUILD_ARGS="--build-arg GIT_COMMIT=$CIRCLE_SHA1 --build-arg GIT_TAG=$CIRCLE_TAG --build-arg COMMIT_TIMESTAMP=$(git log -1 --format=%at) --build-arg AUTHOR_EMAIL=$(git log -1 --format=%ae) --build-arg SIGNATURE_KEY=$(git log -1 --format=%GK)" 192 | docker buildx build --progress=plain --platform=linux/arm/v7 --cache-to=type=local,dest=/tmp/latest-arm7-cache,mode=max $OPERATOR_BUILD_ARGS . -t patoarvizu/vault-agent-auto-inject-webhook:latest-arm7 193 | - persist_to_workspace: 194 | root: /tmp/ 195 | paths: 196 | - latest-arm7-cache 197 | 198 | push-arm7-image: 199 | executor: vm 200 | steps: 201 | - checkout 202 | - attach_workspace: 203 | at: /tmp/ 204 | - run: 205 | name: Install notary 206 | command: | 207 | curl -L https://github.com/theupdateframework/notary/releases/download/v${NOTARY_VERSION}/notary-Linux-amd64 -o notary 208 | chmod +x notary 209 | sudo mv notary /usr/local/bin 210 | - run: 211 | name: Log in to Docker Hub 212 | command: | 213 | docker login --username $DOCKER_HUB_USER --password $DOCKER_HUB_ACCESS_TOKEN 214 | - run: 215 | name: Sign and push arm7 image 216 | command: | 217 | sudo apt-get update 218 | sudo apt-get install qemu-user -y 219 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 220 | docker buildx create --name cci-builder --use 221 | echo $ENCODED_NOTARY_ROLE_KEY | base64 -d > ${HOME}/vault-agent-auto-inject-webhook-circleci.key 222 | export NOTARY_DELEGATION_PASSPHRASE=$NOTARY_ROLE_PASSPHRASE 223 | notary -s https://notary.docker.io -d ~/.docker/trust key import ${HOME}/vault-agent-auto-inject-webhook-circleci.key --role vault-agent-auto-inject-webhook-circleci 224 | export DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$NOTARY_DELEGATION_PASSPHRASE 225 | OPERATOR_BUILD_ARGS="--build-arg GIT_COMMIT=$CIRCLE_SHA1 --build-arg GIT_TAG=$CIRCLE_TAG --build-arg COMMIT_TIMESTAMP=$(git log -1 --format=%at) --build-arg AUTHOR_EMAIL=$(git log -1 --format=%ae) --build-arg SIGNATURE_KEY=$(git log -1 --format=%GK)" 226 | # export DOCKER_CONTENT_TRUST=1 227 | VERSION=${CIRCLE_TAG:-latest} 228 | docker buildx build --progress=plain --platform=linux/arm/v7 --cache-from=type=local,src=/tmp/latest-arm7-cache --load $OPERATOR_BUILD_ARGS . -t patoarvizu/vault-agent-auto-inject-webhook:latest-arm7 229 | docker tag patoarvizu/vault-agent-auto-inject-webhook:latest-arm7 patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-arm7 230 | docker tag patoarvizu/vault-agent-auto-inject-webhook:latest-arm7 patoarvizu/vault-agent-auto-inject-webhook:$VERSION-arm7 231 | docker push patoarvizu/vault-agent-auto-inject-webhook:latest-arm7 232 | docker push patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-arm7 233 | docker push patoarvizu/vault-agent-auto-inject-webhook:$VERSION-arm7 234 | no_output_timeout: 60m 235 | 236 | push-combined-image: 237 | executor: vm 238 | steps: 239 | - checkout 240 | - run: 241 | name: Install notary 242 | command: | 243 | curl -L https://github.com/theupdateframework/notary/releases/download/v${NOTARY_VERSION}/notary-Linux-amd64 -o notary 244 | chmod +x notary 245 | sudo mv notary /usr/local/bin 246 | - run: 247 | name: Log in to Docker Hub 248 | command: | 249 | docker login --username $DOCKER_HUB_USER --password $DOCKER_HUB_ACCESS_TOKEN 250 | - run: 251 | name: Sign and push combined image 252 | command: | 253 | docker buildx create --name cci-builder --use 254 | echo $ENCODED_NOTARY_ROLE_KEY | base64 -d > ${HOME}/vault-agent-auto-inject-webhook-circleci.key 255 | export NOTARY_DELEGATION_PASSPHRASE=$NOTARY_ROLE_PASSPHRASE 256 | notary -s https://notary.docker.io -d ~/.docker/trust key import ${HOME}/vault-agent-auto-inject-webhook-circleci.key --role vault-agent-auto-inject-webhook-circleci 257 | export DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$NOTARY_DELEGATION_PASSPHRASE 258 | # export DOCKER_CONTENT_TRUST=1 259 | VERSION=${CIRCLE_TAG:-latest} 260 | docker manifest create patoarvizu/vault-agent-auto-inject-webhook:latest --amend patoarvizu/vault-agent-auto-inject-webhook:latest-amd64 --amend patoarvizu/vault-agent-auto-inject-webhook:latest-arm64 patoarvizu/vault-agent-auto-inject-webhook:latest-arm7 261 | docker manifest push patoarvizu/vault-agent-auto-inject-webhook:latest 262 | docker manifest create patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1 --amend patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-amd64 --amend patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-arm64 patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1-arm7 263 | docker manifest push patoarvizu/vault-agent-auto-inject-webhook:$CIRCLE_SHA1 264 | docker manifest create patoarvizu/vault-agent-auto-inject-webhook:$VERSION --amend patoarvizu/vault-agent-auto-inject-webhook:$VERSION-amd64 --amend patoarvizu/vault-agent-auto-inject-webhook:$VERSION-arm64 patoarvizu/vault-agent-auto-inject-webhook:$VERSION-arm7 265 | docker manifest push patoarvizu/vault-agent-auto-inject-webhook:$VERSION 266 | # DIGEST=$(docker buildx imagetools inspect patoarvizu/vault-agent-auto-inject-webhook:latest | grep Digest | cut -d':' -f3) 267 | # LENGTH=$(( $(docker buildx imagetools inspect patoarvizu/vault-agent-auto-inject-webhook:latest --raw | wc -c) - 1 )) 268 | # export NOTARY_AUTH=$(echo $DOCKER_HUB_USER:$DOCKER_HUB_ACCESS_TOKEN | base64) 269 | # notary -s https://notary.docker.io -d ~/.docker/trust addhash -p docker.io/patoarvizu/vault-agent-auto-inject-webhook latest $LENGTH --sha256 $DIGEST -r targets/releases 270 | # notary -s https://notary.docker.io -d ~/.docker/trust addhash -p docker.io/patoarvizu/vault-agent-auto-inject-webhook $CIRCLE_SHA1 $LENGTH --sha256 $DIGEST -r targets/releases 271 | # notary -s https://notary.docker.io -d ~/.docker/trust addhash -p docker.io/patoarvizu/vault-agent-auto-inject-webhook $VERSION $LENGTH --sha256 $DIGEST -r targets/releases 272 | 273 | workflows: 274 | version: 2 275 | build-operator: 276 | jobs: 277 | - test: 278 | context: authentication-tokens 279 | filters: 280 | tags: 281 | only: /^v\d+\.\d+.\d+$/ 282 | - build-and-push-amd64-image: 283 | requires: 284 | - test 285 | context: authentication-tokens 286 | filters: 287 | tags: 288 | only: /^v\d+\.\d+.\d+$/ 289 | - build-arm64-image: 290 | requires: 291 | - test 292 | filters: 293 | tags: 294 | only: /^v\d+\.\d+.\d+$/ 295 | - push-arm64-image: 296 | requires: 297 | - build-arm64-image 298 | context: authentication-tokens 299 | filters: 300 | tags: 301 | only: /^v\d+\.\d+.\d+$/ 302 | - build-arm7-image: 303 | requires: 304 | - test 305 | filters: 306 | tags: 307 | only: /^v\d+\.\d+.\d+$/ 308 | - push-arm7-image: 309 | requires: 310 | - build-arm7-image 311 | context: authentication-tokens 312 | filters: 313 | tags: 314 | only: /^v\d+\.\d+.\d+$/ 315 | - push-combined-image: 316 | requires: 317 | - build-and-push-amd64-image 318 | - push-arm64-image 319 | - push-arm7-image 320 | context: authentication-tokens 321 | filters: 322 | branches: 323 | ignore: /^.*$/ 324 | tags: 325 | only: /^v\d+\.\d+.\d+$/ -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 3 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 4 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 5 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 6 | github.com/appscode/jsonpatch v0.0.0-20180911074601-5af499cf01c8 h1:UPvo0sEDBWGIb/nxKyC07j8r3ZH0Qtd0V3aDDM9VpjI= 7 | github.com/appscode/jsonpatch v0.0.0-20180911074601-5af499cf01c8/go.mod h1:4AJxUpXUhv4N+ziTvIcWWXgeorXpxPZOfk9HdEVr96M= 8 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 9 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 10 | github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= 11 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 16 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 17 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 18 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 19 | github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 20 | github.com/evanphx/json-patch v4.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 21 | github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= 22 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 23 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 24 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 25 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 26 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 27 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 28 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 29 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 30 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 31 | github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 32 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 33 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= 34 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 35 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 36 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 37 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 38 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 39 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 40 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 41 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 42 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 43 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 44 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 45 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 46 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 47 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 48 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 49 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 50 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= 51 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 52 | github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= 53 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 54 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 55 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 56 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 57 | github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= 58 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 59 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 60 | github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 61 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 62 | github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= 63 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 64 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 65 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 66 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 67 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 68 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 69 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 70 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 71 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 72 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 73 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 74 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 75 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 76 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 77 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 78 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 79 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 80 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 81 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 82 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 83 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 84 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 85 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 86 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 87 | github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 88 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 89 | github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= 90 | github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 91 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 92 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 93 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 94 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 95 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 96 | github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= 97 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 98 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 99 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 100 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= 101 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 102 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= 103 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 104 | github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= 105 | github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= 106 | github.com/slok/kubewebhook v0.3.0 h1:2IuV3HH+nYMqsGOZzsEBRmNgds3cDypKQ9ZygS063x8= 107 | github.com/slok/kubewebhook v0.3.0/go.mod h1:DfobyP9GEzrdO/5dwhKPWtY7g8EAAwJNwDMEj7EJvDI= 108 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 109 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 110 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 111 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 112 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 113 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 114 | github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= 115 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 116 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 117 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 118 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 119 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 120 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 121 | github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= 122 | github.com/uber/jaeger-client-go v2.14.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 123 | github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 124 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 125 | golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 126 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 127 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 128 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 129 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 130 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 131 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 132 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 133 | golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 134 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= 135 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 136 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= 137 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 138 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 139 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 140 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 142 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 143 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 144 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 145 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= 146 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 148 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 149 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 150 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 151 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 152 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g= 153 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 154 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 155 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 156 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 157 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 158 | google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= 159 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 160 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 161 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 162 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 163 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 164 | gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 165 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 166 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 167 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 168 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 169 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 170 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 171 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 172 | k8s.io/api v0.0.0-20190528110122-9ad12a4af326/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 173 | k8s.io/api v0.0.0-20190918195907-bd6ac527cfd2/go.mod h1:AOxZTnaXR/xiarlQL0JUfwQPxjmKDvVYoRp58cA7lUo= 174 | k8s.io/api v0.0.0-20191206001707-7edad22604e1 h1:Mcy2UO0z79WhIbPaHHqk6xSiR+Qn/5iWCxRcC5MnS+A= 175 | k8s.io/api v0.0.0-20191206001707-7edad22604e1/go.mod h1:WxerFZ1DOp5g/hA844ZoiGxrDSkaeY1Y4pBD58zoMsk= 176 | k8s.io/apimachinery v0.0.0-20190221084156-01f179d85dbc/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 177 | k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d/go.mod h1:3jediapYqJ2w1BFw7lAZPCx7scubsTfosqHkhXCWJKw= 178 | k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d h1:q+OZmYewHJeMCzwpHkXlNTtk5bvaUMPCikKvf77RBlo= 179 | k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= 180 | k8s.io/client-go v0.0.0-20190918200256-06eb1244587a h1:huOvPq1vO7dkuw9rZPYsLGpFmyGvy6L8q6mDItgkdQ4= 181 | k8s.io/client-go v0.0.0-20190918200256-06eb1244587a/go.mod h1:3YAcTbI2ArBRmhHns5vlHRX8YQqvkVYpz+U/N5i1mVU= 182 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 183 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 184 | k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 185 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 186 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 187 | k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 188 | k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 189 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4= 190 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= 191 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 192 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 193 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 194 | --------------------------------------------------------------------------------
{
"key": "vault-control-plane",
"operator": "DoesNotExist"
}
]