Refactor webhook cmd to be reused

Signed-off-by: James Munnelly <james@munnelly.eu>
This commit is contained in:
James Munnelly 2020-02-27 18:11:59 +00:00 committed by Maartje Eyskens
parent 6da95758a4
commit 053e3fc74c
8 changed files with 392 additions and 74 deletions

View File

@ -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"],
)

View 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"],
)

View 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"],
)

View 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",
}
)

View 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"],
)

View 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
}

View 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
}

View File

@ -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)
}