images/kube-webhook-certgen/rootfs: improvements (#7630)

* images/kube-webhook-certgen/rootfs/README.md: remove trailing whitespace

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs: improve code formatting

Automatically using gofumpt.

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs: remove executable bits from files

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs/cmd: remove unreachable code

log.Fatal(|f) will alread call os.Exit(1), so this code is never
reached.

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs/pkg/k8s: fix unit tests

Right now they fail as everything else migrated from using v1beta1 to
v1.

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs: create clientset in cmd package

So one can easily mock the client, without touching unexported parts of
the code and to soften the dependency between CLI code (kubeconfig
path).

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs/cmd: simplify bool logic

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs/pkg/k8s: improve formatting

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs/pkg/k8s: improve variable names

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs/pkg/k8s: refactor a bit

Move patching logic to separate functions.

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs/pkg/k8s: fix error log messages

In patchMutating() function, log messages were waying still patching
validating webhook.

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>
This commit is contained in:
Mateusz Gozdek 2021-09-16 22:59:26 +02:00 committed by GitHub
parent b3389a1b6f
commit 260910c0a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 94 deletions

0
images/kube-webhook-certgen/rootfs/README.md Executable file → Normal file
View file

9
images/kube-webhook-certgen/rootfs/cmd/create.go Executable file → Normal file
View file

@ -7,17 +7,16 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ( var create = &cobra.Command{
create = &cobra.Command{
Use: "create", Use: "create",
Short: "Generate a ca and server cert+key and store the results in a secret 'secret-name' in 'namespace'", Short: "Generate a ca and server cert+key and store the results in a secret 'secret-name' in 'namespace'",
Long: "Generate a ca and server cert+key and store the results in a secret 'secret-name' in 'namespace'", Long: "Generate a ca and server cert+key and store the results in a secret 'secret-name' in 'namespace'",
PreRun: configureLogging, PreRun: configureLogging,
Run: createCommand} Run: createCommand,
) }
func createCommand(cmd *cobra.Command, args []string) { func createCommand(cmd *cobra.Command, args []string) {
k := k8s.New(cfg.kubeconfig) k := k8s.New(newKubernetesClient(cfg.kubeconfig))
ca := k.GetCaFromSecret(cfg.secretName, cfg.namespace) ca := k.GetCaFromSecret(cfg.secretName, cfg.namespace)
if ca == nil { if ca == nil {
log.Info("creating new secret") log.Info("creating new secret")

15
images/kube-webhook-certgen/rootfs/cmd/patch.go Executable file → Normal file
View file

@ -1,28 +1,24 @@
package cmd package cmd
import ( import (
"os"
"github.com/jet/kube-webhook-certgen/pkg/k8s" "github.com/jet/kube-webhook-certgen/pkg/k8s"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
admissionv1 "k8s.io/api/admissionregistration/v1" admissionv1 "k8s.io/api/admissionregistration/v1"
) )
var ( var patch = &cobra.Command{
patch = &cobra.Command{
Use: "patch", Use: "patch",
Short: "Patch a validatingwebhookconfiguration and mutatingwebhookconfiguration 'webhook-name' by using the ca from 'secret-name' in 'namespace'", Short: "Patch a validatingwebhookconfiguration and mutatingwebhookconfiguration 'webhook-name' by using the ca from 'secret-name' in 'namespace'",
Long: "Patch a validatingwebhookconfiguration and mutatingwebhookconfiguration 'webhook-name' by using the ca from 'secret-name' in 'namespace'", Long: "Patch a validatingwebhookconfiguration and mutatingwebhookconfiguration 'webhook-name' by using the ca from 'secret-name' in 'namespace'",
PreRun: prePatchCommand, PreRun: prePatchCommand,
Run: patchCommand} Run: patchCommand,
) }
func prePatchCommand(cmd *cobra.Command, args []string) { func prePatchCommand(cmd *cobra.Command, args []string) {
configureLogging(cmd, args) configureLogging(cmd, args)
if cfg.patchMutating == false && cfg.patchValidating == false { if !cfg.patchMutating && !cfg.patchValidating {
log.Fatal("patch-validating=false, patch-mutating=false. You must patch at least one kind of webhook, otherwise this command is a no-op") log.Fatal("patch-validating=false, patch-mutating=false. You must patch at least one kind of webhook, otherwise this command is a no-op")
os.Exit(1)
} }
switch cfg.patchFailurePolicy { switch cfg.patchFailurePolicy {
case "": case "":
@ -33,12 +29,11 @@ func prePatchCommand(cmd *cobra.Command, args []string) {
break break
default: default:
log.Fatalf("patch-failure-policy %s is not valid", cfg.patchFailurePolicy) log.Fatalf("patch-failure-policy %s is not valid", cfg.patchFailurePolicy)
os.Exit(1)
} }
} }
func patchCommand(_ *cobra.Command, _ []string) { func patchCommand(_ *cobra.Command, _ []string) {
k := k8s.New(cfg.kubeconfig) k := k8s.New(newKubernetesClient(cfg.kubeconfig))
ca := k.GetCaFromSecret(cfg.secretName, cfg.namespace) ca := k.GetCaFromSecret(cfg.secretName, cfg.namespace)
if ca == nil { if ca == nil {

16
images/kube-webhook-certgen/rootfs/cmd/root.go Executable file → Normal file
View file

@ -7,6 +7,8 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
admissionv1 "k8s.io/api/admissionregistration/v1" admissionv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
) )
var ( var (
@ -81,3 +83,17 @@ func getFormatter(logfmt string) log.Formatter {
log.Fatalf("invalid log format '%s'", logfmt) log.Fatalf("invalid log format '%s'", logfmt)
return nil return nil
} }
func newKubernetesClient(kubeconfig string) kubernetes.Interface {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
log.WithError(err).Fatal("error building kubernetes config")
}
c, err := kubernetes.NewForConfig(config)
if err != nil {
log.WithError(err).Fatal("error creating kubernetes client")
}
return c
}

View file

@ -7,11 +7,12 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
log "github.com/sirupsen/logrus"
"math/big" "math/big"
"net" "net"
"strings" "strings"
"time" "time"
log "github.com/sirupsen/logrus"
) )
// GenerateCerts venerates a ca with a leaf certificate and key and returns the ca, cert and key as PEM encoded slices // GenerateCerts venerates a ca with a leaf certificate and key and returns the ca, cert and key as PEM encoded slices

View file

@ -16,7 +16,6 @@ func handler(w http.ResponseWriter, r *http.Request) {
} }
func TestCertificateCreation(t *testing.T) { func TestCertificateCreation(t *testing.T) {
ca, cert, key := GenerateCerts("localhost") ca, cert, key := GenerateCerts("localhost")
c, err := tls.X509KeyPair(cert, key) c, err := tls.X509KeyPair(cert, key)
@ -30,7 +29,9 @@ func TestCertificateCreation(t *testing.T) {
tr := &http.Transport{ tr := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
RootCAs: caCertPool, RootCAs: caCertPool,
ServerName: "localhost"}} ServerName: "localhost",
},
}
ts := httptest.NewUnstartedServer(http.HandlerFunc(handler)) ts := httptest.NewUnstartedServer(http.HandlerFunc(handler))
ts.TLS = &tls.Config{Certificates: []tls.Certificate{c}} ts.TLS = &tls.Config{Certificates: []tls.Certificate{c}}

61
images/kube-webhook-certgen/rootfs/pkg/k8s/k8s.go Executable file → Normal file
View file

@ -9,42 +9,53 @@ import (
k8serrors "k8s.io/apimachinery/pkg/api/errors" k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
) )
type k8s struct { type k8s struct {
clientset kubernetes.Interface clientset kubernetes.Interface
} }
func New(kubeconfig string) *k8s { func New(clientset kubernetes.Interface) *k8s {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if clientset == nil {
if err != nil { log.Fatal("no kubernetes client given")
log.WithError(err).Fatal("error building kubernetes config")
} }
c, err := kubernetes.NewForConfig(config) return &k8s{
if err != nil { clientset: clientset,
log.WithError(err).Fatal("error creating kubernetes client")
} }
return &k8s{clientset: c}
} }
// PatchWebhookConfigurations will patch validatingWebhook and mutatingWebhook clientConfig configurations with // PatchWebhookConfigurations will patch validatingWebhook and mutatingWebhook clientConfig configurations with
// the provided ca data. If failurePolicy is provided, patch all webhooks with this value // the provided ca data. If failurePolicy is provided, patch all webhooks with this value
func (k8s *k8s) PatchWebhookConfigurations( func (k8s *k8s) PatchWebhookConfigurations(
configurationNames string, ca []byte, configurationName string,
ca []byte,
failurePolicy *admissionv1.FailurePolicyType, failurePolicy *admissionv1.FailurePolicyType,
patchMutating bool, patchValidating bool) { patchMutating bool,
patchValidating bool,
log.Infof("patching webhook configurations '%s' mutating=%t, validating=%t, failurePolicy=%s", configurationNames, patchMutating, patchValidating, *failurePolicy) ) {
log.Infof("patching webhook configurations '%s' mutating=%t, validating=%t, failurePolicy=%s", configurationName, patchMutating, patchValidating, *failurePolicy)
if patchValidating { if patchValidating {
k8s.patchValidating(configurationName, ca, failurePolicy)
} else {
log.Debug("validating hook patching not required")
}
if patchMutating {
k8s.patchMutating(configurationName, ca, failurePolicy)
} else {
log.Debug("mutating hook patching not required")
}
log.Info("Patched hook(s)")
}
func (k8s *k8s) patchValidating(configurationName string, ca []byte, failurePolicy *admissionv1.FailurePolicyType) {
valHook, err := k8s.clientset. valHook, err := k8s.clientset.
AdmissionregistrationV1(). AdmissionregistrationV1().
ValidatingWebhookConfigurations(). ValidatingWebhookConfigurations().
Get(context.TODO(), configurationNames, metav1.GetOptions{}) Get(context.TODO(), configurationName, metav1.GetOptions{})
if err != nil { if err != nil {
log.WithField("err", err).Fatal("failed getting validating webhook") log.WithField("err", err).Fatal("failed getting validating webhook")
} }
@ -63,17 +74,15 @@ func (k8s *k8s) PatchWebhookConfigurations(
log.WithField("err", err).Fatal("failed patching validating webhook") log.WithField("err", err).Fatal("failed patching validating webhook")
} }
log.Debug("patched validating hook") log.Debug("patched validating hook")
} else { }
log.Debug("validating hook patching not required")
}
if patchMutating { func (k8s *k8s) patchMutating(configurationName string, ca []byte, failurePolicy *admissionv1.FailurePolicyType) {
mutHook, err := k8s.clientset. mutHook, err := k8s.clientset.
AdmissionregistrationV1(). AdmissionregistrationV1().
MutatingWebhookConfigurations(). MutatingWebhookConfigurations().
Get(context.TODO(), configurationNames, metav1.GetOptions{}) Get(context.TODO(), configurationName, metav1.GetOptions{})
if err != nil { if err != nil {
log.WithField("err", err).Fatal("failed getting validating webhook") log.WithField("err", err).Fatal("failed getting mutating webhook")
} }
for i := range mutHook.Webhooks { for i := range mutHook.Webhooks {
@ -87,14 +96,9 @@ func (k8s *k8s) PatchWebhookConfigurations(
if _, err = k8s.clientset.AdmissionregistrationV1(). if _, err = k8s.clientset.AdmissionregistrationV1().
MutatingWebhookConfigurations(). MutatingWebhookConfigurations().
Update(context.TODO(), mutHook, metav1.UpdateOptions{}); err != nil { Update(context.TODO(), mutHook, metav1.UpdateOptions{}); err != nil {
log.WithField("err", err).Fatal("failed patching validating webhook") log.WithField("err", err).Fatal("failed patching mutating webhook")
} }
log.Debug("patched mutating hook") log.Debug("patched mutating hook")
} else {
log.Debug("mutating hook patching not required")
}
log.Info("Patched hook(s)")
} }
// GetCaFromSecret will check for the presence of a secret. If it exists, will return the content of the // GetCaFromSecret will check for the presence of a secret. If it exists, will return the content of the
@ -120,7 +124,6 @@ func (k8s *k8s) GetCaFromSecret(secretName string, namespace string) []byte {
// SaveCertsToSecret saves the provided ca, cert and key into a secret in the specified namespace. // SaveCertsToSecret saves the provided ca, cert and key into a secret in the specified namespace.
func (k8s *k8s) SaveCertsToSecret(secretName, namespace, certName, keyName string, ca, cert, key []byte) { func (k8s *k8s) SaveCertsToSecret(secretName, namespace, certName, keyName string, ca, cert, key []byte) {
log.Debugf("saving to secret '%s' in namespace '%s'", secretName, namespace) log.Debugf("saving to secret '%s' in namespace '%s'", secretName, namespace)
secret := &v1.Secret{ secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View file

@ -99,7 +99,8 @@ func TestPatchWebhookConfigurations(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: testWebhookName, Name: testWebhookName,
}, },
Webhooks: []admissionv1.MutatingWebhook{{Name: "m1"}, {Name: "m2"}}}, metav1.CreateOptions{}) Webhooks: []admissionv1.MutatingWebhook{{Name: "m1"}, {Name: "m2"}},
}, metav1.CreateOptions{})
k.clientset. k.clientset.
AdmissionregistrationV1(). AdmissionregistrationV1().
@ -109,7 +110,8 @@ func TestPatchWebhookConfigurations(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: testWebhookName, Name: testWebhookName,
}, },
Webhooks: []admissionv1.ValidatingWebhook{{Name: "v1"}, {Name: "v2"}}}, metav1.CreateOptions{}) Webhooks: []admissionv1.ValidatingWebhook{{Name: "v1"}, {Name: "v2"}},
}, metav1.CreateOptions{})
k.PatchWebhookConfigurations(testWebhookName, ca, &fail, true, true) k.PatchWebhookConfigurations(testWebhookName, ca, &fail, true, true)
@ -117,16 +119,14 @@ func TestPatchWebhookConfigurations(t *testing.T) {
AdmissionregistrationV1(). AdmissionregistrationV1().
MutatingWebhookConfigurations(). MutatingWebhookConfigurations().
Get(context.Background(), testWebhookName, metav1.GetOptions{}) Get(context.Background(), testWebhookName, metav1.GetOptions{})
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
whval, err := k.clientset. whval, err := k.clientset.
AdmissionregistrationV1beta1(). AdmissionregistrationV1().
MutatingWebhookConfigurations(). MutatingWebhookConfigurations().
Get(context.Background(), testWebhookName, metav1.GetOptions{}) Get(context.Background(), testWebhookName, metav1.GetOptions{})
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -155,5 +155,4 @@ func TestPatchWebhookConfigurations(t *testing.T) {
if whval.Webhooks[1].FailurePolicy == nil { if whval.Webhooks[1].FailurePolicy == nil {
t.Errorf("Expected second validating webhook failure policy to be set to %s", fail) t.Errorf("Expected second validating webhook failure policy to be set to %s", fail)
} }
} }