Skip to content

Commit

Permalink
feat: add webhook CertWatcher and flags for custom certificate config…
Browse files Browse the repository at this point in the history
…uration

- Introduced CertWatcher for webhook server to dynamically reload certificates.
- Added CLI flags (--webhook-cert-path, --webhook-cert-name, --webhook-cert-key) to allow users to provide custom certificate paths.
- Updated manager to integrate CertWatcher into webhook TLS options.
- Ensured backward compatibility with default self-signed certificates if no custom certs are provided.
  • Loading branch information
camilamacedo86 committed Dec 14, 2024
1 parent d240946 commit 1421b14
Show file tree
Hide file tree
Showing 27 changed files with 512 additions and 192 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/test-e2e-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ jobs:
KUSTOMIZATION_FILE_PATH="testdata/project-v4/config/default/kustomization.yaml"
sed -i '25s/^#//' $KUSTOMIZATION_FILE_PATH
# Uncomment all cert-manager injections
sed -i '55,168s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '170,185s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '57,170s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '172,187s/^#//' $KUSTOMIZATION_FILE_PATH
cd testdata/project-v4/
go mod tidy
Expand Down Expand Up @@ -85,10 +85,10 @@ jobs:
# Uncomment only ValidatingWebhookConfiguration
# from cert-manager replaces; we are leaving defaulting uncommented
# since this sample has no defaulting webhooks
sed -i '55,121s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '57,123s/^#//' $KUSTOMIZATION_FILE_PATH
# Uncomment only --conversion webhooks CA injection
sed -i '153,168s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '170,185s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '155,170s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '172,187s/^#//' $KUSTOMIZATION_FILE_PATH
cd testdata/project-v4-with-plugins/
go mod tidy
Expand Down Expand Up @@ -128,8 +128,8 @@ jobs:
KUSTOMIZATION_FILE_PATH="testdata/project-v4-multigroup/config/default/kustomization.yaml"
sed -i '25s/^#//' $KUSTOMIZATION_FILE_PATH
# Uncomment all cert-manager injections
sed -i '55,168s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '170,185s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '57,170s/^#//' $KUSTOMIZATION_FILE_PATH
sed -i '172,187s/^#//' $KUSTOMIZATION_FILE_PATH
cd testdata/project-v4-multigroup
go mod tidy
Expand Down
41 changes: 40 additions & 1 deletion docs/book/src/cronjob-tutorial/testdata/project/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/tls"
"flag"
"os"
"path/filepath"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
Expand All @@ -30,6 +31,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
Expand Down Expand Up @@ -74,6 +76,7 @@ func main() {
/*
*/
var metricsAddr string
var webhookCertPath, webhookCertName, webhookCertKey string
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
Expand All @@ -87,6 +90,9 @@ func main() {
"Enabling this will ensure there is only one active controller manager.")
flag.BoolVar(&secureMetrics, "metrics-secure", true,
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
opts := zap.Options{
Expand All @@ -112,8 +118,33 @@ func main() {
tlsOpts = append(tlsOpts, disableHTTP2)
}

// Create watchers for metrics certificates
var webhookCertWatcher *certwatcher.CertWatcher

// Initial webhook TLS options
webhookTLSOpts := append([]func(*tls.Config){}, tlsOpts...)

if len(webhookCertPath) > 0 {
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
"webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)

var err error
webhookCertWatcher, err = certwatcher.New(
filepath.Join(webhookCertPath, webhookCertName),
filepath.Join(webhookCertPath, webhookCertKey),
)
if err != nil {
setupLog.Error(err, "Failed to initialize webhook certificate watcher")
os.Exit(1)
}

webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) {
config.GetCertificate = webhookCertWatcher.GetCertificate
})
}

webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: tlsOpts,
TLSOpts: webhookTLSOpts,
})

// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
Expand Down Expand Up @@ -196,6 +227,14 @@ func main() {
}
// +kubebuilder:scaffold:builder

if webhookCertWatcher != nil {
setupLog.Info("Adding webhook certificate watcher to manager")
if err := mgr.Add(webhookCertWatcher); err != nil {
setupLog.Error(err, "unable to add webhook certificate watcher to manager")
os.Exit(1)
}
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ patches:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
# crd/kustomization.yaml
- path: manager_webhook_patch.yaml
target:
kind: Deployment

# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
# Uncomment the following replacements to add the cert-manager CA injection annotations
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
labels:
app.kubernetes.io/name: project
app.kubernetes.io/managed-by: kustomize
spec:
template:
spec:
containers:
- name: manager
ports:
- containerPort: 9443
name: webhook-server
protocol: TCP
volumeMounts:
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: cert
readOnly: true
volumes:
- name: cert
secret:
defaultMode: 420
secretName: webhook-server-cert
# This patch ensures the webhook certificates are properly mounted in the manager container.
# It configures the necessary volume, volume mounts, and container ports.
- op: add
path: /spec/template/spec/containers/0/args/-
value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs
- op: add
path: /spec/template/spec/containers/0/volumeMounts
value: []
- op: add
path: /spec/template/spec/containers/0/volumeMounts/-
value:
mountPath: /tmp/k8s-webhook-server/serving-certs
name: webhook-certs
readOnly: true
- op: add
path: /spec/template/spec/containers/0/ports
value: []
- op: add
path: /spec/template/spec/containers/0/ports/-
value:
containerPort: 9443
name: webhook-server
protocol: TCP
- op: add
path: /spec/template/spec/volumes
value: []
- op: add
path: /spec/template/spec/volumes/-
value:
name: webhook-certs
secret:
secretName: webhook-server-cert
Original file line number Diff line number Diff line change
Expand Up @@ -4118,6 +4118,7 @@ spec:
- --metrics-bind-address=:8443
- --leader-elect
- --health-probe-bind-address=:8081
- --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs
command:
- /manager
image: controller:latest
Expand Down Expand Up @@ -4152,7 +4153,7 @@ spec:
- ALL
volumeMounts:
- mountPath: /tmp/k8s-webhook-server/serving-certs
name: cert
name: webhook-certs
readOnly: true
securityContext:
runAsNonRoot: true
Expand All @@ -4161,9 +4162,8 @@ spec:
serviceAccountName: project-controller-manager
terminationGracePeriodSeconds: 10
volumes:
- name: cert
- name: webhook-certs
secret:
defaultMode: 420
secretName: webhook-server-cert
---
apiVersion: admissionregistration.k8s.io/v1
Expand Down
41 changes: 40 additions & 1 deletion docs/book/src/getting-started/testdata/project/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"crypto/tls"
"flag"
"os"
"path/filepath"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
Expand All @@ -29,6 +30,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
Expand All @@ -54,6 +56,7 @@ func init() {

func main() {
var metricsAddr string
var webhookCertPath, webhookCertName, webhookCertKey string
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
Expand All @@ -67,6 +70,9 @@ func main() {
"Enabling this will ensure there is only one active controller manager.")
flag.BoolVar(&secureMetrics, "metrics-secure", true,
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
opts := zap.Options{
Expand All @@ -92,8 +98,33 @@ func main() {
tlsOpts = append(tlsOpts, disableHTTP2)
}

// Create watchers for metrics certificates
var webhookCertWatcher *certwatcher.CertWatcher

// Initial webhook TLS options
webhookTLSOpts := append([]func(*tls.Config){}, tlsOpts...)

if len(webhookCertPath) > 0 {
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
"webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)

var err error
webhookCertWatcher, err = certwatcher.New(
filepath.Join(webhookCertPath, webhookCertName),
filepath.Join(webhookCertPath, webhookCertKey),
)
if err != nil {
setupLog.Error(err, "Failed to initialize webhook certificate watcher")
os.Exit(1)
}

webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) {
config.GetCertificate = webhookCertWatcher.GetCertificate
})
}

webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: tlsOpts,
TLSOpts: webhookTLSOpts,
})

// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
Expand Down Expand Up @@ -158,6 +189,14 @@ func main() {
}
// +kubebuilder:scaffold:builder

if webhookCertWatcher != nil {
setupLog.Info("Adding webhook certificate watcher to manager")
if err := mgr.Add(webhookCertWatcher); err != nil {
setupLog.Error(err, "unable to add webhook certificate watcher to manager")
os.Exit(1)
}
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ patches:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
# crd/kustomization.yaml
#- path: manager_webhook_patch.yaml
# target:
# kind: Deployment

# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
# Uncomment the following replacements to add the cert-manager CA injection annotations
Expand Down
41 changes: 40 additions & 1 deletion docs/book/src/multiversion-tutorial/testdata/project/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/tls"
"flag"
"os"
"path/filepath"

// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
Expand All @@ -31,6 +32,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
Expand Down Expand Up @@ -73,6 +75,7 @@ func main() {
/*
*/
var metricsAddr string
var webhookCertPath, webhookCertName, webhookCertKey string
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
Expand All @@ -86,6 +89,9 @@ func main() {
"Enabling this will ensure there is only one active controller manager.")
flag.BoolVar(&secureMetrics, "metrics-secure", true,
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
opts := zap.Options{
Expand All @@ -111,8 +117,33 @@ func main() {
tlsOpts = append(tlsOpts, disableHTTP2)
}

// Create watchers for metrics certificates
var webhookCertWatcher *certwatcher.CertWatcher

// Initial webhook TLS options
webhookTLSOpts := append([]func(*tls.Config){}, tlsOpts...)

if len(webhookCertPath) > 0 {
setupLog.Info("Initializing webhook certificate watcher using provided certificates",
"webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)

var err error
webhookCertWatcher, err = certwatcher.New(
filepath.Join(webhookCertPath, webhookCertName),
filepath.Join(webhookCertPath, webhookCertKey),
)
if err != nil {
setupLog.Error(err, "Failed to initialize webhook certificate watcher")
os.Exit(1)
}

webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) {
config.GetCertificate = webhookCertWatcher.GetCertificate
})
}

webhookServer := webhook.NewServer(webhook.Options{
TLSOpts: tlsOpts,
TLSOpts: webhookTLSOpts,
})

// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
Expand Down Expand Up @@ -199,6 +230,14 @@ func main() {
/*
*/

if webhookCertWatcher != nil {
setupLog.Info("Adding webhook certificate watcher to manager")
if err := mgr.Add(webhookCertWatcher); err != nil {
setupLog.Error(err, "unable to add webhook certificate watcher to manager")
os.Exit(1)
}
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
Expand Down
Loading

0 comments on commit 1421b14

Please sign in to comment.