From 831fc46d9e4ebb059473f137ef6c012373c6179c Mon Sep 17 00:00:00 2001 From: Florian Bacher Date: Mon, 15 May 2023 08:40:07 +0200 Subject: [PATCH] chore: use cert-manager library in lifecycle-operator and metrics-operator to reduce code duplication (#1379) Signed-off-by: Florian Bacher Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> --- .../cmd/certificates/certificatehandler.go | 22 -- .../fake/certificatehandler_mock.go | 116 ------- metrics-operator/cmd/certificates/watcher.go | 132 -------- .../cmd/certificates/watcher_test.go | 318 ------------------ metrics-operator/cmd/webhook/builder.go | 85 ----- metrics-operator/cmd/webhook/builder_test.go | 33 -- metrics-operator/cmd/webhook/manager.go | 43 --- metrics-operator/cmd/webhook/manager_test.go | 54 --- metrics-operator/config/manager/manager.yaml | 2 - metrics-operator/go.mod | 3 +- metrics-operator/go.sum | 2 + metrics-operator/main.go | 21 +- .../cmd/certificates/certificatehandler.go | 22 -- .../fake/certificatehandler_mock.go | 116 ------- operator/cmd/certificates/watcher.go | 132 -------- operator/cmd/certificates/watcher_test.go | 318 ------------------ operator/cmd/webhook/builder.go | 96 ------ operator/cmd/webhook/builder_test.go | 33 -- operator/cmd/webhook/manager.go | 43 --- operator/cmd/webhook/manager_test.go | 54 --- operator/config/manager/manager.yaml | 2 - operator/go.mod | 3 +- operator/go.sum | 2 + operator/main.go | 33 +- 24 files changed, 54 insertions(+), 1631 deletions(-) delete mode 100644 metrics-operator/cmd/certificates/certificatehandler.go delete mode 100644 metrics-operator/cmd/certificates/fake/certificatehandler_mock.go delete mode 100644 metrics-operator/cmd/certificates/watcher.go delete mode 100644 metrics-operator/cmd/certificates/watcher_test.go delete mode 100644 metrics-operator/cmd/webhook/builder.go delete mode 100644 metrics-operator/cmd/webhook/builder_test.go delete mode 100644 metrics-operator/cmd/webhook/manager.go delete mode 100644 metrics-operator/cmd/webhook/manager_test.go delete mode 100644 operator/cmd/certificates/certificatehandler.go delete mode 100644 operator/cmd/certificates/fake/certificatehandler_mock.go delete mode 100644 operator/cmd/certificates/watcher.go delete mode 100644 operator/cmd/certificates/watcher_test.go delete mode 100644 operator/cmd/webhook/builder.go delete mode 100644 operator/cmd/webhook/builder_test.go delete mode 100644 operator/cmd/webhook/manager.go delete mode 100644 operator/cmd/webhook/manager_test.go diff --git a/metrics-operator/cmd/certificates/certificatehandler.go b/metrics-operator/cmd/certificates/certificatehandler.go deleted file mode 100644 index ba9450fad9..0000000000 --- a/metrics-operator/cmd/certificates/certificatehandler.go +++ /dev/null @@ -1,22 +0,0 @@ -package certificates - -import ( - "crypto/x509" - "encoding/pem" -) - -//go:generate moq -pkg fake -skip-ensure -out ./fake/certificatehandler_mock.go . ICertificateHandler -type ICertificateHandler interface { - Decode(data []byte) (p *pem.Block, rest []byte) - Parse(der []byte) (*x509.Certificate, error) -} - -type defaultCertificateHandler struct { -} - -func (c defaultCertificateHandler) Decode(data []byte) (p *pem.Block, rest []byte) { - return pem.Decode(data) -} -func (c defaultCertificateHandler) Parse(der []byte) (*x509.Certificate, error) { - return x509.ParseCertificate(der) -} diff --git a/metrics-operator/cmd/certificates/fake/certificatehandler_mock.go b/metrics-operator/cmd/certificates/fake/certificatehandler_mock.go deleted file mode 100644 index 45a5eacbae..0000000000 --- a/metrics-operator/cmd/certificates/fake/certificatehandler_mock.go +++ /dev/null @@ -1,116 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package fake - -import ( - "crypto/x509" - "encoding/pem" - "sync" -) - -// ICertificateHandlerMock is a mock implementation of certificates.ICertificateHandler. -// -// func TestSomethingThatUsesICertificateHandler(t *testing.T) { -// -// // make and configure a mocked certificates.ICertificateHandler -// mockedICertificateHandler := &ICertificateHandlerMock{ -// DecodeFunc: func(data []byte) (*pem.Block, []byte) { -// panic("mock out the Decode method") -// }, -// ParseFunc: func(der []byte) (*x509.Certificate, error) { -// panic("mock out the Parse method") -// }, -// } -// -// // use mockedICertificateHandler in code that requires certificates.ICertificateHandler -// // and then make assertions. -// -// } -type ICertificateHandlerMock struct { - // DecodeFunc mocks the Decode method. - DecodeFunc func(data []byte) (*pem.Block, []byte) - - // ParseFunc mocks the Parse method. - ParseFunc func(der []byte) (*x509.Certificate, error) - - // calls tracks calls to the methods. - calls struct { - // Decode holds details about calls to the Decode method. - Decode []struct { - // Data is the data argument value. - Data []byte - } - // Parse holds details about calls to the Parse method. - Parse []struct { - // Der is the der argument value. - Der []byte - } - } - lockDecode sync.RWMutex - lockParse sync.RWMutex -} - -// Decode calls DecodeFunc. -func (mock *ICertificateHandlerMock) Decode(data []byte) (*pem.Block, []byte) { - if mock.DecodeFunc == nil { - panic("ICertificateHandlerMock.DecodeFunc: method is nil but ICertificateHandler.Decode was just called") - } - callInfo := struct { - Data []byte - }{ - Data: data, - } - mock.lockDecode.Lock() - mock.calls.Decode = append(mock.calls.Decode, callInfo) - mock.lockDecode.Unlock() - return mock.DecodeFunc(data) -} - -// DecodeCalls gets all the calls that were made to Decode. -// Check the length with: -// -// len(mockedICertificateHandler.DecodeCalls()) -func (mock *ICertificateHandlerMock) DecodeCalls() []struct { - Data []byte -} { - var calls []struct { - Data []byte - } - mock.lockDecode.RLock() - calls = mock.calls.Decode - mock.lockDecode.RUnlock() - return calls -} - -// Parse calls ParseFunc. -func (mock *ICertificateHandlerMock) Parse(der []byte) (*x509.Certificate, error) { - if mock.ParseFunc == nil { - panic("ICertificateHandlerMock.ParseFunc: method is nil but ICertificateHandler.Parse was just called") - } - callInfo := struct { - Der []byte - }{ - Der: der, - } - mock.lockParse.Lock() - mock.calls.Parse = append(mock.calls.Parse, callInfo) - mock.lockParse.Unlock() - return mock.ParseFunc(der) -} - -// ParseCalls gets all the calls that were made to Parse. -// Check the length with: -// -// len(mockedICertificateHandler.ParseCalls()) -func (mock *ICertificateHandlerMock) ParseCalls() []struct { - Der []byte -} { - var calls []struct { - Der []byte - } - mock.lockParse.RLock() - calls = mock.calls.Parse - mock.lockParse.RUnlock() - return calls -} diff --git a/metrics-operator/cmd/certificates/watcher.go b/metrics-operator/cmd/certificates/watcher.go deleted file mode 100644 index 3621679ccc..0000000000 --- a/metrics-operator/cmd/certificates/watcher.go +++ /dev/null @@ -1,132 +0,0 @@ -package certificates - -import ( - "bytes" - "context" - "errors" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/go-logr/logr" - "github.com/spf13/afero" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - certificateRenewalInterval = 6 * time.Hour - ServerKey = "tls.key" - ServerCert = "tls.crt" - CertThreshold = 5 * time.Minute -) - -type CertificateWatcher struct { - apiReader client.Reader - fs afero.Fs - certificateDirectory string - namespace string - certificateSecretName string - certificateTreshold time.Duration - ICertificateHandler - Log logr.Logger -} - -func NewCertificateWatcher(reader client.Reader, certDir string, namespace string, secretName string, log logr.Logger) *CertificateWatcher { - return &CertificateWatcher{ - apiReader: reader, - fs: afero.NewOsFs(), - certificateDirectory: certDir, - namespace: namespace, - certificateSecretName: secretName, - ICertificateHandler: defaultCertificateHandler{}, - certificateTreshold: CertThreshold, - Log: log, - } -} - -func (watcher *CertificateWatcher) watchForCertificatesSecret() { - for { - <-time.After(certificateRenewalInterval) - watcher.Log.Info("checking for new certificates") - if err := watcher.updateCertificatesFromSecret(); err != nil { - watcher.Log.Error(err, "failed to update certificates") - } else { - watcher.Log.Info("updated certificate successfully") - } - } -} - -func (watcher *CertificateWatcher) updateCertificatesFromSecret() error { - var secret corev1.Secret - - err := watcher.apiReader.Get(context.TODO(), - client.ObjectKey{Name: watcher.certificateSecretName, Namespace: watcher.namespace}, &secret) - if err != nil { - return err - } - - watcher.Log.Info("checking dir", "watcher.certificateDirectory ", watcher.certificateDirectory) - if _, err = watcher.fs.Stat(watcher.certificateDirectory); os.IsNotExist(err) { - err = watcher.fs.MkdirAll(watcher.certificateDirectory, 0755) - if err != nil { - return fmt.Errorf("could not create cert directory: %w", err) - } - } - - for _, filename := range []string{ServerCert, ServerKey} { - if err = watcher.ensureCertificateFile(secret, filename); err != nil { - return err - } - } - isValid, err := watcher.ValidateCertificateExpiration(secret.Data[ServerCert], certificateRenewalInterval, time.Now()) - if err != nil { - return err - } else if !isValid { - return fmt.Errorf("certificate is outdated") - } - return nil -} - -func (watcher *CertificateWatcher) ensureCertificateFile(secret corev1.Secret, filename string) error { - f := filepath.Join(watcher.certificateDirectory, filename) - data, err := afero.ReadFile(watcher.fs, f) - if os.IsNotExist(err) || !bytes.Equal(data, secret.Data[filename]) { - return afero.WriteFile(watcher.fs, f, secret.Data[filename], 0666) - } - return err - -} - -func (watcher *CertificateWatcher) WaitForCertificates() { - for threshold := time.Now().Add(watcher.certificateTreshold); time.Now().Before(threshold); { - - if err := watcher.updateCertificatesFromSecret(); err != nil { - if k8serrors.IsNotFound(err) { - watcher.Log.Info("waiting for certificate secret to be available.") - } else { - watcher.Log.Error(err, "failed to update certificates") - } - time.Sleep(10 * time.Second) - continue - } - break - } - go watcher.watchForCertificatesSecret() -} - -func (watcher *CertificateWatcher) ValidateCertificateExpiration(certData []byte, renewalThreshold time.Duration, now time.Time) (bool, error) { - if block, _ := watcher.Decode(certData); block == nil { - watcher.Log.Error(errors.New("can't decode PEM file"), "failed to parse certificate") - return false, nil - } else if cert, err := watcher.Parse(block.Bytes); err != nil { - watcher.Log.Error(err, "failed to parse certificate") - return false, err - } else if now.After(cert.NotAfter.Add(-renewalThreshold)) { - watcher.Log.Info("certificate is outdated, waiting for new ones", "Valid until", cert.NotAfter.UTC()) - return false, nil - } - return true, nil -} diff --git a/metrics-operator/cmd/certificates/watcher_test.go b/metrics-operator/cmd/certificates/watcher_test.go deleted file mode 100644 index 4f4301e313..0000000000 --- a/metrics-operator/cmd/certificates/watcher_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package certificates - -import ( - "bytes" - "crypto/x509" - "encoding/pem" - "os" - "path/filepath" - "testing" - "time" - - "github.com/go-logr/logr/testr" - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/certificates/fake" - fakeclient "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/fake" - "github.com/pkg/errors" - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const CACERT = `-----BEGIN CERTIFICATE----- -MIICPTCCAeKgAwIBAgIRAMIV/0UqFGHgKSYOWBdx/KcwCgYIKoZIzj0EAwIwczEL -MAkGA1UEBhMCQVQxCzAJBgNVBAgTAktMMRMwEQYDVQQHEwpLbGFnZW5mdXJ0MQ4w -DAYDVQQKEwVLZXB0bjEZMBcGA1UECxMQTGlmZWN5Y2xlVG9vbGtpdDEXMBUGA1UE -AwwOKi5rZXB0bi1ucy5zdmMwHhcNMjMwNDE5MTEwNDUzWhcNMjQwNDE4MTEwNDUz -WjBzMQswCQYDVQQGEwJBVDELMAkGA1UECBMCS0wxEzARBgNVBAcTCktsYWdlbmZ1 -cnQxDjAMBgNVBAoTBUtlcHRuMRkwFwYDVQQLExBMaWZlY3ljbGVUb29sa2l0MRcw -FQYDVQQDDA4qLmtlcHRuLW5zLnN2YzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA -BPxAP4JTJfwKz/P32dXuyfVi7kinQPebSYwF/gRAUcN0dCAi6GnxbI2OXlcU0guD -zHXv3VRh3EX2fiNszcfKaCajVzBVMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAK -BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQUGe/8XYV1HsZs -nWsyrOCCGr/sQDAKBggqhkjOPQQDAgNJADBGAiEAkcPaCANDXW5Uillrof0VrnPw -ow49D22Gsrh7YM+vmTQCIQDU1L5IT0Zz+bdIyFSsDnEUXZDeydNv56DoSLh+358Y -aw== ------END CERTIFICATE-----` - -const CAKEY = `-----BEGIN PRIVATE KEY----- -MHcCAQEEII5SAqBxINKatksyu2mTvLZZhfEOpNinYJDwlQjkfreboAoGCCqGSM49 -AwEHoUQDQgAE/EA/glMl/ArP8/fZ1e7J9WLuSKdA95tJjAX+BEBRw3R0ICLoafFs -jY5eVxTSC4PMde/dVGHcRfZ+I2zNx8poJg== ------END PRIVATE KEY-----` - -const uniqueIDPEM = `-----BEGIN CERTIFICATE----- -MIIFsDCCBJigAwIBAgIIrOyC1ydafZMwDQYJKoZIhvcNAQEFBQAwgY4xgYswgYgG -A1UEAx6BgABNAGkAYwByAG8AcwBvAGYAdAAgAEYAbwByAGUAZgByAG8AbgB0ACAA -VABNAEcAIABIAFQAVABQAFMAIABJAG4AcwBwAGUAYwB0AGkAbwBuACAAQwBlAHIA -dABpAGYAaQBjAGEAdABpAG8AbgAgAEEAdQB0AGgAbwByAGkAdAB5MB4XDTE0MDEx -ODAwNDEwMFoXDTE1MTExNTA5Mzc1NlowgZYxCzAJBgNVBAYTAklEMRAwDgYDVQQI -EwdqYWthcnRhMRIwEAYDVQQHEwlJbmRvbmVzaWExHDAaBgNVBAoTE3N0aG9ub3Jl -aG90ZWxyZXNvcnQxHDAaBgNVBAsTE3N0aG9ub3JlaG90ZWxyZXNvcnQxJTAjBgNV -BAMTHG1haWwuc3Rob25vcmVob3RlbHJlc29ydC5jb20wggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCvuu0qpI+Ko2X84Twkf84cRD/rgp6vpgc5Ebejx/D4 -PEVON5edZkazrMGocK/oQqIlRxx/lefponN/chlGcllcVVPWTuFjs8k+Aat6T1qp -4iXxZekAqX+U4XZMIGJD3PckPL6G2RQSlF7/LhGCsRNRdKpMWSTbou2Ma39g52Kf -gsl3SK/GwLiWpxpcSkNQD1hugguEIsQYLxbeNwpcheXZtxbBGguPzQ7rH8c5vuKU -BkMOzaiNKLzHbBdFSrua8KWwCJg76Vdq/q36O9GlW6YgG3i+A4pCJjXWerI1lWwX -Ktk5V+SvUHGey1bkDuZKJ6myMk2pGrrPWCT7jP7WskChAgMBAAGBCQBCr1dgEleo -cKOCAfswggH3MIHDBgNVHREEgbswgbiCHG1haWwuc3Rob25vcmVob3RlbHJlc29y -dC5jb22CIGFzaGNoc3ZyLnN0aG9ub3JlaG90ZWxyZXNvcnQuY29tgiRBdXRvRGlz -Y292ZXIuc3Rob25vcmVob3RlbHJlc29ydC5jb22CHEF1dG9EaXNjb3Zlci5ob3Rl -bHJlc29ydC5jb22CCEFTSENIU1ZSghdzdGhvbm9yZWhvdGVscmVzb3J0LmNvbYIP -aG90ZWxyZXNvcnQuY29tMCEGCSsGAQQBgjcUAgQUHhIAVwBlAGIAUwBlAHIAdgBl -AHIwHQYDVR0OBBYEFMAC3UR4FwAdGekbhMgnd6lMejtbMAsGA1UdDwQEAwIFoDAT -BgNVHSUEDDAKBggrBgEFBQcDATAJBgNVHRMEAjAAMIG/BgNVHQEEgbcwgbSAFGfF -6xihk+gJJ5TfwvtWe1UFnHLQoYGRMIGOMYGLMIGIBgNVBAMegYAATQBpAGMAcgBv -AHMAbwBmAHQAIABGAG8AcgBlAGYAcgBvAG4AdAAgAFQATQBHACAASABUAFQAUABT -ACAASQBuAHMAcABlAGMAdABpAG8AbgAgAEMAZQByAHQAaQBmAGkAYwBhAHQAaQBv -AG4AIABBAHUAdABoAG8AcgBpAHQAeYIIcKhXEmBXr0IwDQYJKoZIhvcNAQEFBQAD -ggEBABlSxyCMr3+ANr+WmPSjyN5YCJBgnS0IFCwJAzIYP87bcTye/U8eQ2+E6PqG -Q7Huj7nfHEw9qnGo+HNyPp1ad3KORzXDb54c6xEoi+DeuPzYHPbn4c3hlH49I0aQ -eWW2w4RslSWpLvO6Y7Lboyz2/Thk/s2kd4RHxkkWpH2ltPqJuYYg3X6oM5+gIFHJ -WGnh+ojZ5clKvS5yXh3Wkj78M6sb32KfcBk0Hx6NkCYPt60ODYmWtvqwtw6r73u5 -TnTYWRNvo2svX69TriL+CkHY9O1Hkwf2It5zHl3gNiKTJVaak8AuEz/CKWZneovt -yYLwhUhg3PX5Co1VKYE+9TxloiE= ------END CERTIFICATE-----` - -var ERR_BAD_CERT = errors.New("bad cert") - -var emptySecret = v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-cert", - }, -} - -var goodSecret = v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-cert", - }, - Data: map[string][]byte{ - ServerCert: []byte(CACERT), - ServerKey: []byte(CAKEY), - }, -} - -func TestCertificateWatcher_ValidateCertificateExpiration(t *testing.T) { - - tests := []struct { - name string - certHandler ICertificateHandler - certData []byte - renewalThreshold time.Duration - now time.Time - want bool - wantErr error - }{ - { - name: "certificate cannot be decoded", - certHandler: &fake.ICertificateHandlerMock{ - DecodeFunc: func(data []byte) (p *pem.Block, rest []byte) { - return nil, nil //fake a failure in the decoding - }, - ParseFunc: nil, - }, - want: false, - }, - { - name: "certificate cannot be parsed", - certHandler: &fake.ICertificateHandlerMock{ - DecodeFunc: func(data []byte) (p *pem.Block, rest []byte) { - return &pem.Block{Type: "test", Bytes: []byte("testdata")}, nil - }, - ParseFunc: func(der []byte) (*x509.Certificate, error) { - return nil, ERR_BAD_CERT - }, - }, - want: false, - wantErr: ERR_BAD_CERT, - }, - { - name: "good certificate - unexpired", - certData: []byte(uniqueIDPEM), - certHandler: defaultCertificateHandler{}, - want: true, - }, - { - name: "good certificate - expired", - certData: []byte(uniqueIDPEM), - now: time.Now(), //setting up now makes sure that the threshold is passed - certHandler: defaultCertificateHandler{}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - ICertificateHandler: tt.certHandler, - Log: testr.New(t), - } - got, err := watcher.ValidateCertificateExpiration(tt.certData, tt.renewalThreshold, tt.now) - if tt.wantErr != nil { - require.Error(t, err) - t.Log("want:", tt.wantErr, "got:", err) - require.True(t, errors.Is(tt.wantErr, err)) - } - require.Equal(t, got, tt.want) - }) - } -} - -func TestCertificateWatcher_ensureCertificateFile(t *testing.T) { - - certdir := t.TempDir() - f := filepath.Join(certdir, ServerCert) - err := os.WriteFile(f, goodSecret.Data[ServerCert], 0666) - require.Nil(t, err) - baddir := t.TempDir() - f = filepath.Join(baddir, ServerCert) - err = os.WriteFile(f, goodSecret.Data[ServerKey], 0666) - require.Nil(t, err) - tests := []struct { - name string - fs afero.Fs - secret v1.Secret - filename string - certDir string - wantErr bool - err string - }{ - { - name: "if good cert exist in fs no error", - secret: goodSecret, - certDir: certdir, - filename: ServerCert, - wantErr: false, - }, - - { - name: "if unexisting file name, we expect a file system error", - secret: emptySecret, - filename: "$%&/())=$§%/=", - certDir: baddir, - wantErr: true, - err: "no such file or directory", - }, - - { - name: "wrong file content is replaced with updated cert", - certDir: baddir, - secret: goodSecret, - filename: ServerCert, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - fs: afero.NewOsFs(), - certificateDirectory: tt.certDir, - } - err := watcher.ensureCertificateFile(tt.secret, tt.filename) - if !tt.wantErr { - require.Nil(t, err) - f = filepath.Join(tt.certDir, ServerCert) - data, err := os.ReadFile(f) - if err != nil { - panic(err) - } - if !bytes.Equal(data, tt.secret.Data[tt.filename]) { - t.Errorf("ensureCertificateFile()data %v was not replaced with %v", data, tt.secret.Data[tt.filename]) - } - } else { - require.Contains(t, err.Error(), tt.err) - } - }) - } -} - -func TestCertificateWatcher_updateCertificatesFromSecret(t *testing.T) { - - oldDir := t.TempDir() - os.Remove(oldDir) - - tests := []struct { - name string - apiReader client.Reader - certificateDirectory string - namespace string - certificateSecretName string - wantErr error - }{ - { - name: "certificate not found", - apiReader: fakeclient.NewClient(), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("secrets \"my-cert\" not found"), - }, - { - name: "outdated certificate found, nothing in dir", - apiReader: fakeclient.NewClient(&emptySecret), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("certificate is outdated"), - }, - - { - name: "outdated certificate found, not existing in dir", - apiReader: fakeclient.NewClient(&emptySecret), - certificateDirectory: oldDir, - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("certificate is outdated"), - }, - { - name: "good certificate - not stored", - apiReader: fakeclient.NewClient(&goodSecret), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - apiReader: tt.apiReader, - fs: afero.NewOsFs(), - certificateDirectory: tt.certificateDirectory, - namespace: tt.namespace, - certificateSecretName: tt.certificateSecretName, - ICertificateHandler: defaultCertificateHandler{}, - Log: testr.New(t), - } - err := watcher.updateCertificatesFromSecret() - if tt.wantErr == nil { - require.Nil(t, err) - } else { - require.NotNil(t, err) - require.Contains(t, err.Error(), tt.wantErr.Error()) - } - }) - } -} - -func TestNewCertificateWatcher(t *testing.T) { - logger := testr.New(t) - client := fakeclient.NewClient() - want := &CertificateWatcher{ - apiReader: client, - fs: afero.NewOsFs(), - namespace: "default", - certificateSecretName: "my-secret", - certificateDirectory: "test", - certificateTreshold: CertThreshold, - ICertificateHandler: defaultCertificateHandler{}, - Log: testr.New(t), - } - got := NewCertificateWatcher(client, "test", "default", "my-secret", logger) - require.EqualValues(t, got, want) - -} diff --git a/metrics-operator/cmd/webhook/builder.go b/metrics-operator/cmd/webhook/builder.go deleted file mode 100644 index 311acff4ac..0000000000 --- a/metrics-operator/cmd/webhook/builder.go +++ /dev/null @@ -1,85 +0,0 @@ -package webhook - -import ( - "flag" - - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/certificates" - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/config" - cmdManager "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/manager" - "github.com/pkg/errors" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -const ( - flagCertificateDirectory = "certs-dir" - flagCertificateFileName = "cert" - flagCertificateKeyFileName = "cert-key" - secretCertsName = "klt-certs" -) - -var ( - certificateDirectory string - certificateFileName string - certificateKeyFileName string -) - -type Builder struct { - configProvider config.Provider - managerProvider cmdManager.Provider - namespace string - podName string -} - -func NewWebhookBuilder() Builder { - return Builder{} -} - -func (builder Builder) SetConfigProvider(provider config.Provider) Builder { - builder.configProvider = provider - return builder -} - -func (builder Builder) SetManagerProvider(provider cmdManager.Provider) Builder { - builder.managerProvider = provider - return builder -} - -func (builder Builder) SetNamespace(namespace string) Builder { - builder.namespace = namespace - return builder -} - -func (builder Builder) SetPodName(podName string) Builder { - builder.podName = podName - return builder -} - -func (builder Builder) GetManagerProvider() cmdManager.Provider { - if builder.managerProvider == nil { - builder.managerProvider = NewWebhookManagerProvider(certificateDirectory, certificateKeyFileName, certificateFileName) - } - - return builder.managerProvider -} - -func (builder Builder) Run(webhookManager manager.Manager) error { - - addFlags() - builder.GetManagerProvider().SetupWebhookServer(webhookManager) - - certificates. - NewCertificateWatcher(webhookManager.GetAPIReader(), webhookManager.GetWebhookServer().CertDir, builder.namespace, secretCertsName, ctrl.Log.WithName("Webhook Cert Manager")). - WaitForCertificates() - - signalHandler := ctrl.SetupSignalHandler() - err := webhookManager.Start(signalHandler) - return errors.WithStack(err) -} - -func addFlags() { - flag.StringVar(&certificateDirectory, flagCertificateDirectory, "/tmp/webhook/certs", "Directory to look certificates for.") - flag.StringVar(&certificateFileName, flagCertificateFileName, "tls.crt", "File name for the public certificate.") - flag.StringVar(&certificateKeyFileName, flagCertificateKeyFileName, "tls.key", "File name for the private key.") - flag.Parse() -} diff --git a/metrics-operator/cmd/webhook/builder_test.go b/metrics-operator/cmd/webhook/builder_test.go deleted file mode 100644 index fad6091c7d..0000000000 --- a/metrics-operator/cmd/webhook/builder_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package webhook - -import ( - "testing" - - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/fake" - "github.com/stretchr/testify/assert" -) - -func TestWebhookCommandBuilder(t *testing.T) { - - t.Run("set config provider", func(t *testing.T) { - builder := NewWebhookBuilder() - - assert.NotNil(t, builder) - - expectedProvider := &fake.MockProvider{} - builder = builder.SetConfigProvider(expectedProvider) - - assert.Equal(t, expectedProvider, builder.configProvider) - }) - t.Run("set manager provider", func(t *testing.T) { - expectedProvider := &fake.MockWebhookManager{} - builder := NewWebhookBuilder().SetManagerProvider(expectedProvider) - - assert.Equal(t, expectedProvider, builder.managerProvider) - }) - t.Run("set namespace", func(t *testing.T) { - builder := NewWebhookBuilder().SetNamespace("namespace") - - assert.Equal(t, "namespace", builder.namespace) - }) -} diff --git a/metrics-operator/cmd/webhook/manager.go b/metrics-operator/cmd/webhook/manager.go deleted file mode 100644 index 0e0fedccc6..0000000000 --- a/metrics-operator/cmd/webhook/manager.go +++ /dev/null @@ -1,43 +0,0 @@ -package webhook - -import ( - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -const ( - metricsBindAddress = ":8383" - port = 8443 -) - -type WebhookProvider struct { - certificateDirectory string - certificateFileName string - keyFileName string -} - -func NewWebhookManagerProvider(certificateDirectory string, keyFileName string, certificateFileName string) WebhookProvider { - return WebhookProvider{ - certificateDirectory: certificateDirectory, - certificateFileName: certificateFileName, - keyFileName: keyFileName, - } -} - -func (provider WebhookProvider) createOptions(scheme *runtime.Scheme, namespace string) ctrl.Options { - return ctrl.Options{ - Scheme: scheme, - MetricsBindAddress: metricsBindAddress, - Port: port, - Namespace: namespace, - } -} - -func (provider WebhookProvider) SetupWebhookServer(mgr manager.Manager) { - webhookServer := mgr.GetWebhookServer() - webhookServer.CertDir = provider.certificateDirectory - webhookServer.KeyName = provider.keyFileName - webhookServer.CertName = provider.certificateFileName - -} diff --git a/metrics-operator/cmd/webhook/manager_test.go b/metrics-operator/cmd/webhook/manager_test.go deleted file mode 100644 index 413fbf357f..0000000000 --- a/metrics-operator/cmd/webhook/manager_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package webhook - -import ( - "testing" - - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/fake" - cmdManager "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/manager" - "github.com/stretchr/testify/assert" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -func TestCreateOptions(t *testing.T) { - - t.Run("implements interface", func(t *testing.T) { - var provider cmdManager.Provider = NewWebhookManagerProvider("certs-dir", "key-file", "cert-file") - - providerImpl := provider.(WebhookProvider) - assert.Equal(t, "certs-dir", providerImpl.certificateDirectory) - assert.Equal(t, "key-file", providerImpl.keyFileName) - assert.Equal(t, "cert-file", providerImpl.certificateFileName) - }) - t.Run("creates options", func(t *testing.T) { - provider := WebhookProvider{} - options := provider.createOptions(scheme.Scheme, "test-namespace") - - assert.NotNil(t, options) - assert.Equal(t, "test-namespace", options.Namespace) - assert.Equal(t, scheme.Scheme, options.Scheme) - assert.Equal(t, metricsBindAddress, options.MetricsBindAddress) - assert.Equal(t, port, options.Port) - }) - t.Run("configures webhooks server", func(t *testing.T) { - provider := NewWebhookManagerProvider("certs-dir", "key-file", "cert-file") - expectedWebhookServer := &webhook.Server{} - - mgr := &fake.MockManager{ - GetWebhookServerFunc: func() *webhook.Server { - return expectedWebhookServer - }, - } - - provider.SetupWebhookServer(mgr) - - assert.Equal(t, "certs-dir", expectedWebhookServer.CertDir) - assert.Equal(t, "key-file", expectedWebhookServer.KeyName) - assert.Equal(t, "cert-file", expectedWebhookServer.CertName) - - mgrWebhookServer := mgr.GetWebhookServer() - assert.Equal(t, "certs-dir", mgrWebhookServer.CertDir) - assert.Equal(t, "key-file", mgrWebhookServer.KeyName) - assert.Equal(t, "cert-file", mgrWebhookServer.CertName) - }) -} diff --git a/metrics-operator/config/manager/manager.yaml b/metrics-operator/config/manager/manager.yaml index 433317c78d..cb44f8a113 100644 --- a/metrics-operator/config/manager/manager.yaml +++ b/metrics-operator/config/manager/manager.yaml @@ -45,8 +45,6 @@ spec: - /manager args: - webhook-server - # OLM mounts the certificates here, so we reuse it for simplicity - - --certs-dir=/tmp/k8s-webhook-server/serving-certs/ - --leader-elect # Secure port for the metrics adapter - --adapter-port=6443 diff --git a/metrics-operator/go.mod b/metrics-operator/go.mod index 70161a0fb2..9cc919cedf 100644 --- a/metrics-operator/go.mod +++ b/metrics-operator/go.mod @@ -8,11 +8,11 @@ require ( github.com/go-logr/logr v1.2.4 github.com/gorilla/mux v1.8.0 github.com/kelseyhightower/envconfig v1.4.0 + github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f github.com/open-feature/go-sdk v1.3.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 github.com/prometheus/common v0.42.0 - github.com/spf13/afero v1.9.5 github.com/stretchr/testify v1.8.2 k8s.io/api v0.26.4 k8s.io/apiextensions-apiserver v0.26.4 @@ -73,6 +73,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cobra v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect diff --git a/metrics-operator/go.sum b/metrics-operator/go.sum index c501681c0e..799bc6ae6f 100644 --- a/metrics-operator/go.sum +++ b/metrics-operator/go.sum @@ -263,6 +263,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f h1:ildQjDQmBjX3fdAShsQJN7dkKfhivlnlDbEh/Etjli4= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/metrics-operator/main.go b/metrics-operator/main.go index 930e47103f..bc99c602b7 100644 --- a/metrics-operator/main.go +++ b/metrics-operator/main.go @@ -26,12 +26,13 @@ import ( "time" "github.com/kelseyhightower/envconfig" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/certificates" + certCommon "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/common" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/webhook" metricsv1alpha1 "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha1" metricsv1alpha2 "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" metricsv1alpha3 "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" - cmdConfig "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/config" "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/metrics/adapter" - "github.com/keptn/lifecycle-toolkit/metrics-operator/cmd/webhook" metricscontroller "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/metrics" keptnserver "github.com/keptn/lifecycle-toolkit/metrics-operator/pkg/metrics" "github.com/open-feature/go-sdk/pkg/openfeature" @@ -154,10 +155,22 @@ func main() { webhookBuilder := webhook.NewWebhookBuilder(). SetNamespace(env.PodNamespace). SetPodName(env.PodName). - SetConfigProvider(cmdConfig.NewKubeConfigProvider()) + SetManagerProvider( + webhook.NewWebhookManagerProvider( + mgr.GetWebhookServer().CertDir, "tls.key", "tls.crt"), + ). + SetCertificateWatcher( + certificates.NewCertificateWatcher( + mgr.GetAPIReader(), + mgr.GetWebhookServer().CertDir, + env.PodNamespace, + certCommon.SecretName, + setupLog, + ), + ) setupLog.Info("starting webhook and manager") - if err1 := webhookBuilder.Run(mgr); err1 != nil { + if err := webhookBuilder.Run(mgr, nil); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } diff --git a/operator/cmd/certificates/certificatehandler.go b/operator/cmd/certificates/certificatehandler.go deleted file mode 100644 index ba9450fad9..0000000000 --- a/operator/cmd/certificates/certificatehandler.go +++ /dev/null @@ -1,22 +0,0 @@ -package certificates - -import ( - "crypto/x509" - "encoding/pem" -) - -//go:generate moq -pkg fake -skip-ensure -out ./fake/certificatehandler_mock.go . ICertificateHandler -type ICertificateHandler interface { - Decode(data []byte) (p *pem.Block, rest []byte) - Parse(der []byte) (*x509.Certificate, error) -} - -type defaultCertificateHandler struct { -} - -func (c defaultCertificateHandler) Decode(data []byte) (p *pem.Block, rest []byte) { - return pem.Decode(data) -} -func (c defaultCertificateHandler) Parse(der []byte) (*x509.Certificate, error) { - return x509.ParseCertificate(der) -} diff --git a/operator/cmd/certificates/fake/certificatehandler_mock.go b/operator/cmd/certificates/fake/certificatehandler_mock.go deleted file mode 100644 index 45a5eacbae..0000000000 --- a/operator/cmd/certificates/fake/certificatehandler_mock.go +++ /dev/null @@ -1,116 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package fake - -import ( - "crypto/x509" - "encoding/pem" - "sync" -) - -// ICertificateHandlerMock is a mock implementation of certificates.ICertificateHandler. -// -// func TestSomethingThatUsesICertificateHandler(t *testing.T) { -// -// // make and configure a mocked certificates.ICertificateHandler -// mockedICertificateHandler := &ICertificateHandlerMock{ -// DecodeFunc: func(data []byte) (*pem.Block, []byte) { -// panic("mock out the Decode method") -// }, -// ParseFunc: func(der []byte) (*x509.Certificate, error) { -// panic("mock out the Parse method") -// }, -// } -// -// // use mockedICertificateHandler in code that requires certificates.ICertificateHandler -// // and then make assertions. -// -// } -type ICertificateHandlerMock struct { - // DecodeFunc mocks the Decode method. - DecodeFunc func(data []byte) (*pem.Block, []byte) - - // ParseFunc mocks the Parse method. - ParseFunc func(der []byte) (*x509.Certificate, error) - - // calls tracks calls to the methods. - calls struct { - // Decode holds details about calls to the Decode method. - Decode []struct { - // Data is the data argument value. - Data []byte - } - // Parse holds details about calls to the Parse method. - Parse []struct { - // Der is the der argument value. - Der []byte - } - } - lockDecode sync.RWMutex - lockParse sync.RWMutex -} - -// Decode calls DecodeFunc. -func (mock *ICertificateHandlerMock) Decode(data []byte) (*pem.Block, []byte) { - if mock.DecodeFunc == nil { - panic("ICertificateHandlerMock.DecodeFunc: method is nil but ICertificateHandler.Decode was just called") - } - callInfo := struct { - Data []byte - }{ - Data: data, - } - mock.lockDecode.Lock() - mock.calls.Decode = append(mock.calls.Decode, callInfo) - mock.lockDecode.Unlock() - return mock.DecodeFunc(data) -} - -// DecodeCalls gets all the calls that were made to Decode. -// Check the length with: -// -// len(mockedICertificateHandler.DecodeCalls()) -func (mock *ICertificateHandlerMock) DecodeCalls() []struct { - Data []byte -} { - var calls []struct { - Data []byte - } - mock.lockDecode.RLock() - calls = mock.calls.Decode - mock.lockDecode.RUnlock() - return calls -} - -// Parse calls ParseFunc. -func (mock *ICertificateHandlerMock) Parse(der []byte) (*x509.Certificate, error) { - if mock.ParseFunc == nil { - panic("ICertificateHandlerMock.ParseFunc: method is nil but ICertificateHandler.Parse was just called") - } - callInfo := struct { - Der []byte - }{ - Der: der, - } - mock.lockParse.Lock() - mock.calls.Parse = append(mock.calls.Parse, callInfo) - mock.lockParse.Unlock() - return mock.ParseFunc(der) -} - -// ParseCalls gets all the calls that were made to Parse. -// Check the length with: -// -// len(mockedICertificateHandler.ParseCalls()) -func (mock *ICertificateHandlerMock) ParseCalls() []struct { - Der []byte -} { - var calls []struct { - Der []byte - } - mock.lockParse.RLock() - calls = mock.calls.Parse - mock.lockParse.RUnlock() - return calls -} diff --git a/operator/cmd/certificates/watcher.go b/operator/cmd/certificates/watcher.go deleted file mode 100644 index 3621679ccc..0000000000 --- a/operator/cmd/certificates/watcher.go +++ /dev/null @@ -1,132 +0,0 @@ -package certificates - -import ( - "bytes" - "context" - "errors" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/go-logr/logr" - "github.com/spf13/afero" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - certificateRenewalInterval = 6 * time.Hour - ServerKey = "tls.key" - ServerCert = "tls.crt" - CertThreshold = 5 * time.Minute -) - -type CertificateWatcher struct { - apiReader client.Reader - fs afero.Fs - certificateDirectory string - namespace string - certificateSecretName string - certificateTreshold time.Duration - ICertificateHandler - Log logr.Logger -} - -func NewCertificateWatcher(reader client.Reader, certDir string, namespace string, secretName string, log logr.Logger) *CertificateWatcher { - return &CertificateWatcher{ - apiReader: reader, - fs: afero.NewOsFs(), - certificateDirectory: certDir, - namespace: namespace, - certificateSecretName: secretName, - ICertificateHandler: defaultCertificateHandler{}, - certificateTreshold: CertThreshold, - Log: log, - } -} - -func (watcher *CertificateWatcher) watchForCertificatesSecret() { - for { - <-time.After(certificateRenewalInterval) - watcher.Log.Info("checking for new certificates") - if err := watcher.updateCertificatesFromSecret(); err != nil { - watcher.Log.Error(err, "failed to update certificates") - } else { - watcher.Log.Info("updated certificate successfully") - } - } -} - -func (watcher *CertificateWatcher) updateCertificatesFromSecret() error { - var secret corev1.Secret - - err := watcher.apiReader.Get(context.TODO(), - client.ObjectKey{Name: watcher.certificateSecretName, Namespace: watcher.namespace}, &secret) - if err != nil { - return err - } - - watcher.Log.Info("checking dir", "watcher.certificateDirectory ", watcher.certificateDirectory) - if _, err = watcher.fs.Stat(watcher.certificateDirectory); os.IsNotExist(err) { - err = watcher.fs.MkdirAll(watcher.certificateDirectory, 0755) - if err != nil { - return fmt.Errorf("could not create cert directory: %w", err) - } - } - - for _, filename := range []string{ServerCert, ServerKey} { - if err = watcher.ensureCertificateFile(secret, filename); err != nil { - return err - } - } - isValid, err := watcher.ValidateCertificateExpiration(secret.Data[ServerCert], certificateRenewalInterval, time.Now()) - if err != nil { - return err - } else if !isValid { - return fmt.Errorf("certificate is outdated") - } - return nil -} - -func (watcher *CertificateWatcher) ensureCertificateFile(secret corev1.Secret, filename string) error { - f := filepath.Join(watcher.certificateDirectory, filename) - data, err := afero.ReadFile(watcher.fs, f) - if os.IsNotExist(err) || !bytes.Equal(data, secret.Data[filename]) { - return afero.WriteFile(watcher.fs, f, secret.Data[filename], 0666) - } - return err - -} - -func (watcher *CertificateWatcher) WaitForCertificates() { - for threshold := time.Now().Add(watcher.certificateTreshold); time.Now().Before(threshold); { - - if err := watcher.updateCertificatesFromSecret(); err != nil { - if k8serrors.IsNotFound(err) { - watcher.Log.Info("waiting for certificate secret to be available.") - } else { - watcher.Log.Error(err, "failed to update certificates") - } - time.Sleep(10 * time.Second) - continue - } - break - } - go watcher.watchForCertificatesSecret() -} - -func (watcher *CertificateWatcher) ValidateCertificateExpiration(certData []byte, renewalThreshold time.Duration, now time.Time) (bool, error) { - if block, _ := watcher.Decode(certData); block == nil { - watcher.Log.Error(errors.New("can't decode PEM file"), "failed to parse certificate") - return false, nil - } else if cert, err := watcher.Parse(block.Bytes); err != nil { - watcher.Log.Error(err, "failed to parse certificate") - return false, err - } else if now.After(cert.NotAfter.Add(-renewalThreshold)) { - watcher.Log.Info("certificate is outdated, waiting for new ones", "Valid until", cert.NotAfter.UTC()) - return false, nil - } - return true, nil -} diff --git a/operator/cmd/certificates/watcher_test.go b/operator/cmd/certificates/watcher_test.go deleted file mode 100644 index 0b393332d8..0000000000 --- a/operator/cmd/certificates/watcher_test.go +++ /dev/null @@ -1,318 +0,0 @@ -package certificates - -import ( - "bytes" - "crypto/x509" - "encoding/pem" - "os" - "path/filepath" - "testing" - "time" - - "github.com/go-logr/logr/testr" - "github.com/keptn/lifecycle-toolkit/operator/cmd/certificates/fake" - fakeclient "github.com/keptn/lifecycle-toolkit/operator/controllers/common/fake" - "github.com/pkg/errors" - "github.com/spf13/afero" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const CACERT = `-----BEGIN CERTIFICATE----- -MIICPTCCAeKgAwIBAgIRAMIV/0UqFGHgKSYOWBdx/KcwCgYIKoZIzj0EAwIwczEL -MAkGA1UEBhMCQVQxCzAJBgNVBAgTAktMMRMwEQYDVQQHEwpLbGFnZW5mdXJ0MQ4w -DAYDVQQKEwVLZXB0bjEZMBcGA1UECxMQTGlmZWN5Y2xlVG9vbGtpdDEXMBUGA1UE -AwwOKi5rZXB0bi1ucy5zdmMwHhcNMjMwNDE5MTEwNDUzWhcNMjQwNDE4MTEwNDUz -WjBzMQswCQYDVQQGEwJBVDELMAkGA1UECBMCS0wxEzARBgNVBAcTCktsYWdlbmZ1 -cnQxDjAMBgNVBAoTBUtlcHRuMRkwFwYDVQQLExBMaWZlY3ljbGVUb29sa2l0MRcw -FQYDVQQDDA4qLmtlcHRuLW5zLnN2YzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA -BPxAP4JTJfwKz/P32dXuyfVi7kinQPebSYwF/gRAUcN0dCAi6GnxbI2OXlcU0guD -zHXv3VRh3EX2fiNszcfKaCajVzBVMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAK -BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQUGe/8XYV1HsZs -nWsyrOCCGr/sQDAKBggqhkjOPQQDAgNJADBGAiEAkcPaCANDXW5Uillrof0VrnPw -ow49D22Gsrh7YM+vmTQCIQDU1L5IT0Zz+bdIyFSsDnEUXZDeydNv56DoSLh+358Y -aw== ------END CERTIFICATE-----` - -const CAKEY = `-----BEGIN PRIVATE KEY----- -MHcCAQEEII5SAqBxINKatksyu2mTvLZZhfEOpNinYJDwlQjkfreboAoGCCqGSM49 -AwEHoUQDQgAE/EA/glMl/ArP8/fZ1e7J9WLuSKdA95tJjAX+BEBRw3R0ICLoafFs -jY5eVxTSC4PMde/dVGHcRfZ+I2zNx8poJg== ------END PRIVATE KEY-----` - -const uniqueIDPEM = `-----BEGIN CERTIFICATE----- -MIIFsDCCBJigAwIBAgIIrOyC1ydafZMwDQYJKoZIhvcNAQEFBQAwgY4xgYswgYgG -A1UEAx6BgABNAGkAYwByAG8AcwBvAGYAdAAgAEYAbwByAGUAZgByAG8AbgB0ACAA -VABNAEcAIABIAFQAVABQAFMAIABJAG4AcwBwAGUAYwB0AGkAbwBuACAAQwBlAHIA -dABpAGYAaQBjAGEAdABpAG8AbgAgAEEAdQB0AGgAbwByAGkAdAB5MB4XDTE0MDEx -ODAwNDEwMFoXDTE1MTExNTA5Mzc1NlowgZYxCzAJBgNVBAYTAklEMRAwDgYDVQQI -EwdqYWthcnRhMRIwEAYDVQQHEwlJbmRvbmVzaWExHDAaBgNVBAoTE3N0aG9ub3Jl -aG90ZWxyZXNvcnQxHDAaBgNVBAsTE3N0aG9ub3JlaG90ZWxyZXNvcnQxJTAjBgNV -BAMTHG1haWwuc3Rob25vcmVob3RlbHJlc29ydC5jb20wggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCvuu0qpI+Ko2X84Twkf84cRD/rgp6vpgc5Ebejx/D4 -PEVON5edZkazrMGocK/oQqIlRxx/lefponN/chlGcllcVVPWTuFjs8k+Aat6T1qp -4iXxZekAqX+U4XZMIGJD3PckPL6G2RQSlF7/LhGCsRNRdKpMWSTbou2Ma39g52Kf -gsl3SK/GwLiWpxpcSkNQD1hugguEIsQYLxbeNwpcheXZtxbBGguPzQ7rH8c5vuKU -BkMOzaiNKLzHbBdFSrua8KWwCJg76Vdq/q36O9GlW6YgG3i+A4pCJjXWerI1lWwX -Ktk5V+SvUHGey1bkDuZKJ6myMk2pGrrPWCT7jP7WskChAgMBAAGBCQBCr1dgEleo -cKOCAfswggH3MIHDBgNVHREEgbswgbiCHG1haWwuc3Rob25vcmVob3RlbHJlc29y -dC5jb22CIGFzaGNoc3ZyLnN0aG9ub3JlaG90ZWxyZXNvcnQuY29tgiRBdXRvRGlz -Y292ZXIuc3Rob25vcmVob3RlbHJlc29ydC5jb22CHEF1dG9EaXNjb3Zlci5ob3Rl -bHJlc29ydC5jb22CCEFTSENIU1ZSghdzdGhvbm9yZWhvdGVscmVzb3J0LmNvbYIP -aG90ZWxyZXNvcnQuY29tMCEGCSsGAQQBgjcUAgQUHhIAVwBlAGIAUwBlAHIAdgBl -AHIwHQYDVR0OBBYEFMAC3UR4FwAdGekbhMgnd6lMejtbMAsGA1UdDwQEAwIFoDAT -BgNVHSUEDDAKBggrBgEFBQcDATAJBgNVHRMEAjAAMIG/BgNVHQEEgbcwgbSAFGfF -6xihk+gJJ5TfwvtWe1UFnHLQoYGRMIGOMYGLMIGIBgNVBAMegYAATQBpAGMAcgBv -AHMAbwBmAHQAIABGAG8AcgBlAGYAcgBvAG4AdAAgAFQATQBHACAASABUAFQAUABT -ACAASQBuAHMAcABlAGMAdABpAG8AbgAgAEMAZQByAHQAaQBmAGkAYwBhAHQAaQBv -AG4AIABBAHUAdABoAG8AcgBpAHQAeYIIcKhXEmBXr0IwDQYJKoZIhvcNAQEFBQAD -ggEBABlSxyCMr3+ANr+WmPSjyN5YCJBgnS0IFCwJAzIYP87bcTye/U8eQ2+E6PqG -Q7Huj7nfHEw9qnGo+HNyPp1ad3KORzXDb54c6xEoi+DeuPzYHPbn4c3hlH49I0aQ -eWW2w4RslSWpLvO6Y7Lboyz2/Thk/s2kd4RHxkkWpH2ltPqJuYYg3X6oM5+gIFHJ -WGnh+ojZ5clKvS5yXh3Wkj78M6sb32KfcBk0Hx6NkCYPt60ODYmWtvqwtw6r73u5 -TnTYWRNvo2svX69TriL+CkHY9O1Hkwf2It5zHl3gNiKTJVaak8AuEz/CKWZneovt -yYLwhUhg3PX5Co1VKYE+9TxloiE= ------END CERTIFICATE-----` - -var ERR_BAD_CERT = errors.New("bad cert") - -var emptySecret = v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-cert", - }, -} - -var goodSecret = v1.Secret{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-cert", - }, - Data: map[string][]byte{ - ServerCert: []byte(CACERT), - ServerKey: []byte(CAKEY), - }, -} - -func TestCertificateWatcher_ValidateCertificateExpiration(t *testing.T) { - - tests := []struct { - name string - certHandler ICertificateHandler - certData []byte - renewalThreshold time.Duration - now time.Time - want bool - wantErr error - }{ - { - name: "certificate cannot be decoded", - certHandler: &fake.ICertificateHandlerMock{ - DecodeFunc: func(data []byte) (p *pem.Block, rest []byte) { - return nil, nil //fake a failure in the decoding - }, - ParseFunc: nil, - }, - want: false, - }, - { - name: "certificate cannot be parsed", - certHandler: &fake.ICertificateHandlerMock{ - DecodeFunc: func(data []byte) (p *pem.Block, rest []byte) { - return &pem.Block{Type: "test", Bytes: []byte("testdata")}, nil - }, - ParseFunc: func(der []byte) (*x509.Certificate, error) { - return nil, ERR_BAD_CERT - }, - }, - want: false, - wantErr: ERR_BAD_CERT, - }, - { - name: "good certificate - unexpired", - certData: []byte(uniqueIDPEM), - certHandler: defaultCertificateHandler{}, - want: true, - }, - { - name: "good certificate - expired", - certData: []byte(uniqueIDPEM), - now: time.Now(), //setting up now makes sure that the threshold is passed - certHandler: defaultCertificateHandler{}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - ICertificateHandler: tt.certHandler, - Log: testr.New(t), - } - got, err := watcher.ValidateCertificateExpiration(tt.certData, tt.renewalThreshold, tt.now) - if tt.wantErr != nil { - require.Error(t, err) - t.Log("want:", tt.wantErr, "got:", err) - require.True(t, errors.Is(tt.wantErr, err)) - } - require.Equal(t, got, tt.want) - }) - } -} - -func TestCertificateWatcher_ensureCertificateFile(t *testing.T) { - - certdir := t.TempDir() - f := filepath.Join(certdir, ServerCert) - err := os.WriteFile(f, goodSecret.Data[ServerCert], 0666) - require.Nil(t, err) - baddir := t.TempDir() - f = filepath.Join(baddir, ServerCert) - err = os.WriteFile(f, goodSecret.Data[ServerKey], 0666) - require.Nil(t, err) - tests := []struct { - name string - fs afero.Fs - secret v1.Secret - filename string - certDir string - wantErr bool - err string - }{ - { - name: "if good cert exist in fs no error", - secret: goodSecret, - certDir: certdir, - filename: ServerCert, - wantErr: false, - }, - - { - name: "if unexisting file name, we expect a file system error", - secret: emptySecret, - filename: "$%&/())=$§%/=", - certDir: baddir, - wantErr: true, - err: "no such file or directory", - }, - - { - name: "wrong file content is replaced with updated cert", - certDir: baddir, - secret: goodSecret, - filename: ServerCert, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - fs: afero.NewOsFs(), - certificateDirectory: tt.certDir, - } - err := watcher.ensureCertificateFile(tt.secret, tt.filename) - if !tt.wantErr { - require.Nil(t, err) - f = filepath.Join(tt.certDir, ServerCert) - data, err := os.ReadFile(f) - if err != nil { - panic(err) - } - if !bytes.Equal(data, tt.secret.Data[tt.filename]) { - t.Errorf("ensureCertificateFile()data %v was not replaced with %v", data, tt.secret.Data[tt.filename]) - } - } else { - require.Contains(t, err.Error(), tt.err) - } - }) - } -} - -func TestCertificateWatcher_updateCertificatesFromSecret(t *testing.T) { - - oldDir := t.TempDir() - os.Remove(oldDir) - - tests := []struct { - name string - apiReader client.Reader - certificateDirectory string - namespace string - certificateSecretName string - wantErr error - }{ - { - name: "certificate not found", - apiReader: fakeclient.NewClient(), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("secrets \"my-cert\" not found"), - }, - { - name: "outdated certificate found, nothing in dir", - apiReader: fakeclient.NewClient(&emptySecret), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("certificate is outdated"), - }, - - { - name: "outdated certificate found, not existing in dir", - apiReader: fakeclient.NewClient(&emptySecret), - certificateDirectory: oldDir, - namespace: "default", - certificateSecretName: "my-cert", - wantErr: errors.New("certificate is outdated"), - }, - { - name: "good certificate - not stored", - apiReader: fakeclient.NewClient(&goodSecret), - certificateDirectory: t.TempDir(), - namespace: "default", - certificateSecretName: "my-cert", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - watcher := &CertificateWatcher{ - apiReader: tt.apiReader, - fs: afero.NewOsFs(), - certificateDirectory: tt.certificateDirectory, - namespace: tt.namespace, - certificateSecretName: tt.certificateSecretName, - ICertificateHandler: defaultCertificateHandler{}, - Log: testr.New(t), - } - err := watcher.updateCertificatesFromSecret() - if tt.wantErr == nil { - require.Nil(t, err) - } else { - require.NotNil(t, err) - require.Contains(t, err.Error(), tt.wantErr.Error()) - } - }) - } -} - -func TestNewCertificateWatcher(t *testing.T) { - logger := testr.New(t) - client := fakeclient.NewClient() - want := &CertificateWatcher{ - apiReader: client, - fs: afero.NewOsFs(), - namespace: "default", - certificateSecretName: "my-secret", - certificateDirectory: "test", - certificateTreshold: CertThreshold, - ICertificateHandler: defaultCertificateHandler{}, - Log: testr.New(t), - } - got := NewCertificateWatcher(client, "test", "default", "my-secret", logger) - require.EqualValues(t, got, want) - -} diff --git a/operator/cmd/webhook/builder.go b/operator/cmd/webhook/builder.go deleted file mode 100644 index d87b0ef591..0000000000 --- a/operator/cmd/webhook/builder.go +++ /dev/null @@ -1,96 +0,0 @@ -package webhook - -import ( - "flag" - - "github.com/keptn/lifecycle-toolkit/operator/cmd/certificates" - "github.com/keptn/lifecycle-toolkit/operator/cmd/config" - cmdManager "github.com/keptn/lifecycle-toolkit/operator/cmd/manager" - "github.com/keptn/lifecycle-toolkit/operator/webhooks" - "github.com/keptn/lifecycle-toolkit/operator/webhooks/pod_mutator" - "github.com/pkg/errors" - "go.opentelemetry.io/otel" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -const ( - FlagCertificateDirectory = "certs-dir" - FlagCertificateFileName = "cert" - FlagCertificateKeyFileName = "cert-key" -) - -var ( - certificateDirectory string - certificateFileName string - certificateKeyFileName string -) - -type Builder struct { - configProvider config.Provider - managerProvider cmdManager.Provider - namespace string - podName string -} - -func NewWebhookBuilder() Builder { - return Builder{} -} - -func (builder Builder) SetConfigProvider(provider config.Provider) Builder { - builder.configProvider = provider - return builder -} - -func (builder Builder) SetManagerProvider(provider cmdManager.Provider) Builder { - builder.managerProvider = provider - return builder -} - -func (builder Builder) SetNamespace(namespace string) Builder { - builder.namespace = namespace - return builder -} - -func (builder Builder) SetPodName(podName string) Builder { - builder.podName = podName - return builder -} - -func (builder Builder) GetManagerProvider() cmdManager.Provider { - if builder.managerProvider == nil { - builder.managerProvider = NewWebhookManagerProvider(certificateDirectory, certificateKeyFileName, certificateFileName) - } - - return builder.managerProvider -} - -func (builder Builder) Run(webhookManager manager.Manager) error { - - addFlags() - builder.GetManagerProvider().SetupWebhookServer(webhookManager) - - certificates. - NewCertificateWatcher(webhookManager.GetAPIReader(), webhookManager.GetWebhookServer().CertDir, builder.namespace, webhooks.SecretCertsName, ctrl.Log.WithName("Webhook Cert Manager")). - WaitForCertificates() - - webhookManager.GetWebhookServer().Register("/mutate-v1-pod", &webhook.Admission{ - Handler: &pod_mutator.PodMutatingWebhook{ - Client: webhookManager.GetClient(), - Tracer: otel.Tracer("keptn/webhook"), - Recorder: webhookManager.GetEventRecorderFor("keptn/webhook"), - Log: ctrl.Log.WithName("Mutating Webhook"), - }}) - - signalHandler := ctrl.SetupSignalHandler() - err := webhookManager.Start(signalHandler) - return errors.WithStack(err) -} - -func addFlags() { - flag.StringVar(&certificateDirectory, FlagCertificateDirectory, "/tmp/webhook/certs", "Directory to look certificates for.") - flag.StringVar(&certificateFileName, FlagCertificateFileName, "tls.crt", "File name for the public certificate.") - flag.StringVar(&certificateKeyFileName, FlagCertificateKeyFileName, "tls.key", "File name for the private key.") - flag.Parse() -} diff --git a/operator/cmd/webhook/builder_test.go b/operator/cmd/webhook/builder_test.go deleted file mode 100644 index 1e5118573a..0000000000 --- a/operator/cmd/webhook/builder_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package webhook - -import ( - "testing" - - "github.com/keptn/lifecycle-toolkit/operator/cmd/fake" - "github.com/stretchr/testify/assert" -) - -func TestWebhookCommandBuilder(t *testing.T) { - - t.Run("set config provider", func(t *testing.T) { - builder := NewWebhookBuilder() - - assert.NotNil(t, builder) - - expectedProvider := &fake.MockProvider{} - builder = builder.SetConfigProvider(expectedProvider) - - assert.Equal(t, expectedProvider, builder.configProvider) - }) - t.Run("set manager provider", func(t *testing.T) { - expectedProvider := &fake.MockWebhookManager{} - builder := NewWebhookBuilder().SetManagerProvider(expectedProvider) - - assert.Equal(t, expectedProvider, builder.managerProvider) - }) - t.Run("set namespace", func(t *testing.T) { - builder := NewWebhookBuilder().SetNamespace("namespace") - - assert.Equal(t, "namespace", builder.namespace) - }) -} diff --git a/operator/cmd/webhook/manager.go b/operator/cmd/webhook/manager.go deleted file mode 100644 index 0e0fedccc6..0000000000 --- a/operator/cmd/webhook/manager.go +++ /dev/null @@ -1,43 +0,0 @@ -package webhook - -import ( - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" -) - -const ( - metricsBindAddress = ":8383" - port = 8443 -) - -type WebhookProvider struct { - certificateDirectory string - certificateFileName string - keyFileName string -} - -func NewWebhookManagerProvider(certificateDirectory string, keyFileName string, certificateFileName string) WebhookProvider { - return WebhookProvider{ - certificateDirectory: certificateDirectory, - certificateFileName: certificateFileName, - keyFileName: keyFileName, - } -} - -func (provider WebhookProvider) createOptions(scheme *runtime.Scheme, namespace string) ctrl.Options { - return ctrl.Options{ - Scheme: scheme, - MetricsBindAddress: metricsBindAddress, - Port: port, - Namespace: namespace, - } -} - -func (provider WebhookProvider) SetupWebhookServer(mgr manager.Manager) { - webhookServer := mgr.GetWebhookServer() - webhookServer.CertDir = provider.certificateDirectory - webhookServer.KeyName = provider.keyFileName - webhookServer.CertName = provider.certificateFileName - -} diff --git a/operator/cmd/webhook/manager_test.go b/operator/cmd/webhook/manager_test.go deleted file mode 100644 index 3e0913648e..0000000000 --- a/operator/cmd/webhook/manager_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package webhook - -import ( - "testing" - - "github.com/keptn/lifecycle-toolkit/operator/cmd/fake" - cmdManager "github.com/keptn/lifecycle-toolkit/operator/cmd/manager" - "github.com/stretchr/testify/assert" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -func TestCreateOptions(t *testing.T) { - - t.Run("implements interface", func(t *testing.T) { - var provider cmdManager.Provider = NewWebhookManagerProvider("certs-dir", "key-file", "cert-file") - - providerImpl := provider.(WebhookProvider) - assert.Equal(t, "certs-dir", providerImpl.certificateDirectory) - assert.Equal(t, "key-file", providerImpl.keyFileName) - assert.Equal(t, "cert-file", providerImpl.certificateFileName) - }) - t.Run("creates options", func(t *testing.T) { - provider := WebhookProvider{} - options := provider.createOptions(scheme.Scheme, "test-namespace") - - assert.NotNil(t, options) - assert.Equal(t, "test-namespace", options.Namespace) - assert.Equal(t, scheme.Scheme, options.Scheme) - assert.Equal(t, metricsBindAddress, options.MetricsBindAddress) - assert.Equal(t, port, options.Port) - }) - t.Run("configures webhooks server", func(t *testing.T) { - provider := NewWebhookManagerProvider("certs-dir", "key-file", "cert-file") - expectedWebhookServer := &webhook.Server{} - - mgr := &fake.MockManager{ - GetWebhookServerFunc: func() *webhook.Server { - return expectedWebhookServer - }, - } - - provider.SetupWebhookServer(mgr) - - assert.Equal(t, "certs-dir", expectedWebhookServer.CertDir) - assert.Equal(t, "key-file", expectedWebhookServer.KeyName) - assert.Equal(t, "cert-file", expectedWebhookServer.CertName) - - mgrWebhookServer := mgr.GetWebhookServer() - assert.Equal(t, "certs-dir", mgrWebhookServer.CertDir) - assert.Equal(t, "key-file", mgrWebhookServer.KeyName) - assert.Equal(t, "cert-file", mgrWebhookServer.CertName) - }) -} diff --git a/operator/config/manager/manager.yaml b/operator/config/manager/manager.yaml index 1558e8c7a0..1c6b9ecd41 100644 --- a/operator/config/manager/manager.yaml +++ b/operator/config/manager/manager.yaml @@ -40,8 +40,6 @@ spec: - /manager args: - webhook-server - # OLM mounts the certificates here, so we reuse it for simplicity - - --certs-dir=/tmp/k8s-webhook-server/serving-certs/ - --leader-elect # Secure port for the metrics adapter - --adapter-port=6443 diff --git a/operator/go.mod b/operator/go.mod index 834f082fa8..5feeb0250b 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -8,13 +8,13 @@ require ( github.com/go-logr/logr v1.2.4 github.com/imdario/mergo v0.3.15 github.com/kelseyhightower/envconfig v1.4.0 + github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 github.com/magiconair/properties v1.8.7 github.com/onsi/ginkgo/v2 v2.9.4 github.com/onsi/gomega v1.27.6 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.1 - github.com/spf13/afero v1.9.5 github.com/stretchr/testify v1.8.2 go.opentelemetry.io/otel v1.11.2 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 @@ -80,6 +80,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/spf13/afero v1.9.5 // indirect github.com/spf13/pflag v1.0.5 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect diff --git a/operator/go.sum b/operator/go.sum index 926148111b..f2c77b678a 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -206,6 +206,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f h1:ildQjDQmBjX3fdAShsQJN7dkKfhivlnlDbEh/Etjli4= +github.com/keptn/lifecycle-toolkit/klt-cert-manager v0.0.0-20230504143450-0fd922ad161f/go.mod h1:sEy6RzY7TzpDQGfbmyteR7K+NrmZ2g5wPz0FzhUNE+U= github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4 h1:LI+iOb7v1zIAtHQum79CbV+4HB1PCAim+TuCCRRsW7o= github.com/keptn/lifecycle-toolkit/metrics-operator v0.0.0-20230413082525-dd15d4a0e0e4/go.mod h1:8rQ1flqblBWy43k4xJnoaMUA7e50zP95QIab3z6NCw4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= diff --git a/operator/main.go b/operator/main.go index f022405e00..d7b70e9b87 100644 --- a/operator/main.go +++ b/operator/main.go @@ -25,13 +25,14 @@ import ( argov1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/kelseyhightower/envconfig" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/certificates" + certCommon "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/common" + "github.com/keptn/lifecycle-toolkit/klt-cert-manager/pkg/webhook" metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" lifecyclev1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha1" lifecyclev1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2" lifecyclev1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" optionsv1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/options/v1alpha1" - cmdConfig "github.com/keptn/lifecycle-toolkit/operator/cmd/config" - "github.com/keptn/lifecycle-toolkit/operator/cmd/webhook" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnapp" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnappcreationrequest" @@ -42,7 +43,9 @@ import ( "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnworkload" "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnworkloadinstance" controlleroptions "github.com/keptn/lifecycle-toolkit/operator/controllers/options" + "github.com/keptn/lifecycle-toolkit/operator/webhooks/pod_mutator" "github.com/prometheus/client_golang/prometheus/promhttp" + "go.opentelemetry.io/otel" otelprom "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/sdk/metric" corev1 "k8s.io/api/core/v1" @@ -53,6 +56,7 @@ import ( ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + ctrlWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" ) var ( @@ -318,10 +322,31 @@ func main() { webhookBuilder := webhook.NewWebhookBuilder(). SetNamespace(env.PodNamespace). SetPodName(env.PodName). - SetConfigProvider(cmdConfig.NewKubeConfigProvider()) + SetManagerProvider( + webhook.NewWebhookManagerProvider( + mgr.GetWebhookServer().CertDir, "tls.key", "tls.crt"), + ). + SetCertificateWatcher( + certificates.NewCertificateWatcher( + mgr.GetAPIReader(), + mgr.GetWebhookServer().CertDir, + env.PodNamespace, + certCommon.SecretName, + setupLog, + ), + ) setupLog.Info("starting webhook and manager") - if err1 := webhookBuilder.Run(mgr); err1 != nil { + if err := webhookBuilder.Run(mgr, map[string]*ctrlWebhook.Admission{ + "/mutate-v1-pod": { + Handler: &pod_mutator.PodMutatingWebhook{ + Client: mgr.GetClient(), + Tracer: otel.Tracer("keptn/webhook"), + Recorder: mgr.GetEventRecorderFor("keptn/webhook"), + Log: ctrl.Log.WithName("Mutating Webhook"), + }, + }, + }); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) }