Refactor webhook cmd to be reused
Signed-off-by: James Munnelly <james@munnelly.eu>
This commit is contained in:
parent
6da95758a4
commit
053e3fc74c
@ -21,10 +21,9 @@ go_library(
|
||||
importpath = "github.com/jetstack/cert-manager/cmd/webhook",
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
"//pkg/logs:go_default_library",
|
||||
"//pkg/webhook:go_default_library",
|
||||
"//pkg/webhook/handlers:go_default_library",
|
||||
"//pkg/webhook/server:go_default_library",
|
||||
"//cmd/webhook/app:go_default_library",
|
||||
"//cmd/webhook/app/options:go_default_library",
|
||||
"@com_github_spf13_pflag//:go_default_library",
|
||||
"@io_k8s_klog//:go_default_library",
|
||||
"@io_k8s_klog//klogr:go_default_library",
|
||||
],
|
||||
@ -47,7 +46,10 @@ filegroup(
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//cmd/webhook/app:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
34
cmd/webhook/app/BUILD.bazel
Normal file
34
cmd/webhook/app/BUILD.bazel
Normal file
@ -0,0 +1,34 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["webhook.go"],
|
||||
importpath = "github.com/jetstack/cert-manager/cmd/webhook/app",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd/webhook/app/options:go_default_library",
|
||||
"//pkg/logs:go_default_library",
|
||||
"//pkg/webhook:go_default_library",
|
||||
"//pkg/webhook/handlers:go_default_library",
|
||||
"//pkg/webhook/server:go_default_library",
|
||||
"@com_github_go_logr_logr//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//cmd/webhook/app/options:all-srcs",
|
||||
"//cmd/webhook/app/testing:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
23
cmd/webhook/app/options/BUILD.bazel
Normal file
23
cmd/webhook/app/options/BUILD.bazel
Normal file
@ -0,0 +1,23 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["options.go"],
|
||||
importpath = "github.com/jetstack/cert-manager/cmd/webhook/app/options",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["@com_github_spf13_pflag//:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
53
cmd/webhook/app/options/options.go
Normal file
53
cmd/webhook/app/options/options.go
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2020 The Jetstack cert-manager contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type WebhookOptions struct {
|
||||
ListenPort int
|
||||
HealthzPort int
|
||||
|
||||
TLSCertFile string
|
||||
TLSKeyFile string
|
||||
TLSCipherSuites []string
|
||||
}
|
||||
|
||||
func (o *WebhookOptions) AddFlags(fs *pflag.FlagSet) {
|
||||
// TODO: rename secure-port to listen-port
|
||||
fs.IntVar(&o.ListenPort, "secure-port", 6443, "port number to listen on for secure TLS connections")
|
||||
fs.IntVar(&o.HealthzPort, "healthz-port", 6080, "port number to listen on for insecure healthz connections")
|
||||
fs.StringVar(&o.TLSCertFile, "tls-cert-file", "", "path to the file containing the TLS certificate to serve with")
|
||||
fs.StringVar(&o.TLSKeyFile, "tls-private-key-file", "", "path to the file containing the TLS private key to serve with")
|
||||
fs.StringSliceVar(&o.TLSCipherSuites, "tls-cipher-suites", defaultCipherSuites, "comma separated list of TLS 1.2 cipher suites to use (TLS 1.3 cipher suites are not configurable)")
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCipherSuites = []string{
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA",
|
||||
}
|
||||
)
|
||||
29
cmd/webhook/app/testing/BUILD.bazel
Normal file
29
cmd/webhook/app/testing/BUILD.bazel
Normal file
@ -0,0 +1,29 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["testwebhook.go"],
|
||||
importpath = "github.com/jetstack/cert-manager/cmd/webhook/app/testing",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd/webhook/app:go_default_library",
|
||||
"//cmd/webhook/app/options:go_default_library",
|
||||
"//pkg/logs:go_default_library",
|
||||
"//pkg/util/pki:go_default_library",
|
||||
"@com_github_spf13_pflag//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
172
cmd/webhook/app/testing/testwebhook.go
Normal file
172
cmd/webhook/app/testing/testwebhook.go
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2020 The Jetstack cert-manager contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/jetstack/cert-manager/cmd/webhook/app"
|
||||
"github.com/jetstack/cert-manager/cmd/webhook/app/options"
|
||||
logf "github.com/jetstack/cert-manager/pkg/logs"
|
||||
"github.com/jetstack/cert-manager/pkg/util/pki"
|
||||
)
|
||||
|
||||
var log = logf.Log.WithName("webhook-server-test")
|
||||
|
||||
type StopFunc func()
|
||||
|
||||
type ServerOptions struct {
|
||||
// URL is the base path/URL that the webhook server can be accessed on.
|
||||
// This is typically of the form: https://127.0.0.1:12345.
|
||||
URL string
|
||||
|
||||
// CAPEM is PEM data containing the CA used to validate connections to the
|
||||
// webhook.
|
||||
// If `--tls-cert-file` or `--tls-private-key-file` are explicitly provided
|
||||
// as flags, this field will be empty.
|
||||
CAPEM []byte
|
||||
}
|
||||
|
||||
func StartWebhookServer(t *testing.T, args []string) (ServerOptions, StopFunc) {
|
||||
// Allow user to override options using flags
|
||||
opts := &options.WebhookOptions{}
|
||||
fs := pflag.NewFlagSet("testset", pflag.ExitOnError)
|
||||
opts.AddFlags(fs)
|
||||
// Parse the arguments passed in into the WebhookOptions struct
|
||||
fs.Parse(args)
|
||||
|
||||
var caPEM []byte
|
||||
tempDir, err := ioutil.TempDir("", "webhook-tls-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.TLSKeyFile == "" && opts.TLSCertFile == "" {
|
||||
// Generate a CA and serving certificate
|
||||
ca, certificatePEM, privateKeyPEM, err := generateTLSAssets()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate PKI assets: %v", err)
|
||||
}
|
||||
|
||||
caPEM = ca
|
||||
if err := ioutil.WriteFile(filepath.Join(tempDir, "tls.crt"), certificatePEM, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(tempDir, "tls.key"), privateKeyPEM, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
opts.TLSKeyFile = filepath.Join(tempDir, "tls.key")
|
||||
opts.TLSCertFile = filepath.Join(tempDir, "tls.crt")
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
go func() {
|
||||
if err := app.RunServer(log, *opts, stopCh); err != nil {
|
||||
t.Fatalf("error running webhook server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return ServerOptions{
|
||||
URL: fmt.Sprintf("https://127.0.0.1:%d", opts.ListenPort),
|
||||
CAPEM: caPEM,
|
||||
}, func() {
|
||||
close(stopCh)
|
||||
if err := os.RemoveAll(tempDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateTLSAssets() (caPEM, certificatePEM, privateKeyPEM []byte, err error) {
|
||||
caPK, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
rootCA := &x509.Certificate{
|
||||
Version: 3,
|
||||
BasicConstraintsValid: true,
|
||||
SerialNumber: big.NewInt(1658),
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "testing-ca",
|
||||
},
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
IsCA: true,
|
||||
}
|
||||
rootCADER, err := x509.CreateCertificate(rand.Reader, rootCA, rootCA, caPK.Public(), caPK)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
rootCA, err = x509.ParseCertificate(rootCADER)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
servingCert := &x509.Certificate{
|
||||
Version: 3,
|
||||
BasicConstraintsValid: true,
|
||||
SerialNumber: big.NewInt(1659),
|
||||
PublicKeyAlgorithm: x509.RSA,
|
||||
DNSNames: []string{"localhost"},
|
||||
IPAddresses: []net.IP{{127, 0, 0, 1}},
|
||||
NotBefore: time.Now().Add(-1 * time.Hour),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
servingPK, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
servingDER, err := x509.CreateCertificate(rand.Reader, servingCert, rootCA, servingPK.Public(), caPK)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
servingCert, err = x509.ParseCertificate(servingDER)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// encoding PKI data to PEM
|
||||
privateKeyPEM, err = pki.EncodePKCS8PrivateKey(servingPK)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
caPEM, err = pki.EncodeX509(rootCA)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
certificatePEM, err = pki.EncodeX509(servingCert)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
63
cmd/webhook/app/webhook.go
Normal file
63
cmd/webhook/app/webhook.go
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2020 The Jetstack cert-manager contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
||||
"github.com/jetstack/cert-manager/cmd/webhook/app/options"
|
||||
"github.com/jetstack/cert-manager/pkg/logs"
|
||||
"github.com/jetstack/cert-manager/pkg/webhook"
|
||||
"github.com/jetstack/cert-manager/pkg/webhook/handlers"
|
||||
"github.com/jetstack/cert-manager/pkg/webhook/server"
|
||||
)
|
||||
|
||||
var validationHook handlers.ValidatingAdmissionHook = handlers.NewRegistryBackedValidator(logs.Log, webhook.Scheme, webhook.ValidationRegistry)
|
||||
var mutationHook handlers.MutatingAdmissionHook = handlers.NewSchemeBackedDefaulter(logs.Log, webhook.Scheme)
|
||||
var conversionHook handlers.ConversionHook = handlers.NewSchemeBackedConverter(logs.Log, webhook.Scheme)
|
||||
|
||||
func RunServer(log logr.Logger, opts options.WebhookOptions, stopCh <-chan struct{}) error {
|
||||
var source server.CertificateSource
|
||||
if opts.TLSCertFile == "" || opts.TLSKeyFile == "" {
|
||||
log.Info("warning: serving insecurely as tls certificate data not provided")
|
||||
} else {
|
||||
log.Info("enabling TLS as certificate file flags specified")
|
||||
source = &server.FileCertificateSource{
|
||||
CertPath: opts.TLSCertFile,
|
||||
KeyPath: opts.TLSKeyFile,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
srv := server.Server{
|
||||
ListenAddr: fmt.Sprintf(":%d", opts.ListenPort),
|
||||
HealthzAddr: fmt.Sprintf(":%d", opts.HealthzPort),
|
||||
EnablePprof: true,
|
||||
CertificateSource: source,
|
||||
CipherSuites: opts.TLSCipherSuites,
|
||||
ValidationWebhook: validationHook,
|
||||
MutationWebhook: mutationHook,
|
||||
ConversionWebhook: conversionHook,
|
||||
Log: log,
|
||||
}
|
||||
if err := srv.Run(stopCh); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -17,89 +17,31 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
goflag "flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/klogr"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/logs"
|
||||
"github.com/jetstack/cert-manager/pkg/webhook"
|
||||
"github.com/jetstack/cert-manager/pkg/webhook/handlers"
|
||||
"github.com/jetstack/cert-manager/pkg/webhook/server"
|
||||
"github.com/jetstack/cert-manager/cmd/webhook/app"
|
||||
"github.com/jetstack/cert-manager/cmd/webhook/app/options"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCipherSuites = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256," +
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384," +
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305," +
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA," +
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA," +
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256," +
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384," +
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA," +
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA"
|
||||
)
|
||||
|
||||
var (
|
||||
securePort int
|
||||
healthzPort int
|
||||
tlsCertFile string
|
||||
tlsKeyFile string
|
||||
tlsCipherSuites string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.IntVar(&healthzPort, "healthz-port", 6080, "port number to listen on for insecure healthz connections")
|
||||
flag.IntVar(&securePort, "secure-port", 6443, "port number to listen on for secure TLS connections")
|
||||
flag.StringVar(&tlsCertFile, "tls-cert-file", "", "path to the file containing the TLS certificate to serve with")
|
||||
flag.StringVar(&tlsKeyFile, "tls-private-key-file", "", "path to the file containing the TLS private key to serve with")
|
||||
flag.StringVar(&tlsCipherSuites, "tls-cipher-suites", defaultCipherSuites, "comma separated list of TLS 1.2 cipher suites to use (TLS 1.3 cipher suites are not configurable)")
|
||||
}
|
||||
|
||||
var validationHook handlers.ValidatingAdmissionHook = handlers.NewRegistryBackedValidator(logs.Log, webhook.Scheme, webhook.ValidationRegistry)
|
||||
var mutationHook handlers.MutatingAdmissionHook = handlers.NewSchemeBackedDefaulter(logs.Log, webhook.Scheme)
|
||||
var conversionHook handlers.ConversionHook = handlers.NewSchemeBackedConverter(logs.Log, webhook.Scheme)
|
||||
|
||||
func main() {
|
||||
klog.InitFlags(flag.CommandLine)
|
||||
flag.Parse()
|
||||
gofs := &goflag.FlagSet{}
|
||||
klog.InitFlags(gofs)
|
||||
pflag.CommandLine.AddGoFlagSet(gofs)
|
||||
opts := &options.WebhookOptions{}
|
||||
opts.AddFlags(pflag.CommandLine)
|
||||
pflag.Parse()
|
||||
|
||||
log := klogr.New()
|
||||
stopCh := setupSignalHandler()
|
||||
|
||||
var source server.CertificateSource
|
||||
if tlsCertFile == "" || tlsKeyFile == "" {
|
||||
log.Info("warning: serving insecurely as tls certificate data not provided")
|
||||
} else {
|
||||
log.Info("enabling TLS as certificate file flags specified")
|
||||
source = &server.FileCertificateSource{
|
||||
CertPath: tlsCertFile,
|
||||
KeyPath: tlsKeyFile,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
var cipherSuites []string
|
||||
if len(tlsCipherSuites) > 0 {
|
||||
cipherSuites = strings.Split(tlsCipherSuites, ",")
|
||||
}
|
||||
|
||||
srv := server.Server{
|
||||
ListenAddr: fmt.Sprintf(":%d", securePort),
|
||||
HealthzAddr: fmt.Sprintf(":%d", healthzPort),
|
||||
EnablePprof: true,
|
||||
CertificateSource: source,
|
||||
CipherSuites: cipherSuites,
|
||||
ValidationWebhook: validationHook,
|
||||
MutationWebhook: mutationHook,
|
||||
ConversionWebhook: conversionHook,
|
||||
Log: log,
|
||||
}
|
||||
if err := srv.Run(stopCh); err != nil {
|
||||
if err := app.RunServer(log, *opts, stopCh); err != nil {
|
||||
log.Error(err, "error running server")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user