diff --git a/Makefile b/Makefile index a15b9fc0..531141f4 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ mocks: bootstrap mockgen -source ./api/deployments/deployment_handler.go -destination ./api/deployments/mock/deployment_handler_mock.go -package mock mockgen -source ./api/environments/job_handler.go -destination ./api/environments/mock/job_handler_mock.go -package mock mockgen -source ./api/environments/environment_handler.go -destination ./api/environments/mock/environment_handler_mock.go -package mock - mockgen -source ./api/utils/tlsvalidator/interface.go -destination ./api/utils/tlsvalidator/mock/tls_secret_validator_mock.go -package mock + mockgen -source ./api/utils/tlsvalidation/interface.go -destination ./api/utils/tlsvalidation/mock/tls_secret_validator_mock.go -package mock mockgen -source ./api/utils/jobscheduler/interface.go -destination ./api/utils/jobscheduler/mock/job_scheduler_factory_mock.go -package mock mockgen -source ./api/events/event_handler.go -destination ./api/events/mock/event_handler_mock.go -package mock @@ -44,9 +44,8 @@ build-kaniko: .PHONY: swagger swagger: SHELL:=/bin/bash swagger: bootstrap - swagger generate spec -o ./swagger.json --scan-models --exclude-deps - swagger validate ./swagger.json - mv swagger.json ./swaggerui/html/swagger.json + swagger generate spec -o ./swaggerui/html/swagger.json --scan-models --exclude-deps + swagger validate ./swaggerui/html/swagger.json .PHONY: $(BINS) $(BINS): bootstrap diff --git a/api/deployments/component_controller_test.go b/api/deployments/component_controller_test.go index 66f20377..df19a0fa 100644 --- a/api/deployments/component_controller_test.go +++ b/api/deployments/component_controller_test.go @@ -100,43 +100,6 @@ func TestGetComponents_active_deployment(t *testing.T) { assert.Equal(t, 1, len(job.Replicas)) } -func TestGetComponents_WithExternalAlias_ContainsTLSSecrets(t *testing.T) { - // Setup - commonTestUtils, controllerTestUtils, client, radixclient, promclient, secretProviderClient := setupTest(t) - err := utils.ApplyDeploymentWithSync(client, radixclient, promclient, commonTestUtils, secretProviderClient, operatorUtils.ARadixDeployment(). - WithAppName("any-app"). - WithEnvironment("prod"). - WithDeploymentName(anyDeployName). - WithJobComponents(). - WithComponents( - operatorUtils.NewDeployComponentBuilder(). - WithName("frontend"). - WithPort("http", 8080). - WithPublicPort("http"). - WithDNSExternalAlias("some.alias.com"). - WithDNSExternalAlias("another.alias.com"))) - require.NoError(t, err) - - // Test - endpoint := createGetComponentsEndpoint(anyAppName, anyDeployName) - - responseChannel := controllerTestUtils.ExecuteRequest("GET", endpoint) - response := <-responseChannel - - assert.Equal(t, 200, response.Code) - - var components []deploymentModels.Component - err = controllertest.GetResponseBody(response, &components) - require.NoError(t, err) - - frontend := getComponentByName("frontend", components) - assert.Equal(t, 4, len(frontend.Secrets)) - assert.Equal(t, "some.alias.com-cert", frontend.Secrets[0]) - assert.Equal(t, "some.alias.com-key", frontend.Secrets[1]) - assert.Equal(t, "another.alias.com-cert", frontend.Secrets[2]) - assert.Equal(t, "another.alias.com-key", frontend.Secrets[3]) -} - func TestGetComponents_WithVolumeMount_ContainsVolumeMountSecrets(t *testing.T) { // Setup commonTestUtils, controllerTestUtils, client, radixclient, promclient, secretProviderClient := setupTest(t) diff --git a/api/deployments/deployment_controller_test.go b/api/deployments/deployment_controller_test.go index e875cceb..42cc1f1c 100644 --- a/api/deployments/deployment_controller_test.go +++ b/api/deployments/deployment_controller_test.go @@ -359,12 +359,10 @@ func TestGetDeployment_TwoDeploymentsFirstDeployment_ReturnsDeploymentWithCompon WithImage("radixdev.azurecr.io/some-image:imagetag"). WithName("frontend"). WithPort("http", 8080). - WithPublic(true). WithReplicas(commontest.IntPtr(1)), builders.NewDeployComponentBuilder(). WithImage("radixdev.azurecr.io/another-image:imagetag"). WithName("backend"). - WithPublic(false). WithReplicas(commontest.IntPtr(1)))) require.NoError(t, err) @@ -387,7 +385,6 @@ func TestGetDeployment_TwoDeploymentsFirstDeployment_ReturnsDeploymentWithCompon builders.NewDeployComponentBuilder(). WithImage("radixdev.azurecr.io/another-second-image:imagetag"). WithName("backend"). - WithPublic(false). WithReplicas(commontest.IntPtr(1)))) require.NoError(t, err) diff --git a/api/deployments/models/component_builder.go b/api/deployments/models/component_builder.go index 61721ae3..b44f887d 100644 --- a/api/deployments/models/component_builder.go +++ b/api/deployments/models/component_builder.go @@ -24,6 +24,7 @@ type ComponentBuilder interface { WithAuxiliaryResource(AuxiliaryResource) ComponentBuilder WithNotifications(*v1.Notifications) ComponentBuilder WithHorizontalScalingSummary(*HorizontalScalingSummary) ComponentBuilder + WithExternalDNS(externalDNS []ExternalDNS) ComponentBuilder BuildComponentSummary() (*ComponentSummary, error) BuildComponent() (*Component, error) } @@ -45,6 +46,7 @@ type componentBuilder struct { identity *Identity notifications *Notifications hpa *HorizontalScalingSummary + externalDNS []ExternalDNS errors []error } @@ -100,14 +102,6 @@ func (b *componentBuilder) WithComponent(component v1.RadixCommonDeployComponent b.ports = ports b.secrets = component.GetSecrets() - if b.secrets == nil { - b.secrets = []string{} - } - - for _, externalAlias := range component.GetDNSExternalAlias() { - b.secrets = append(b.secrets, externalAlias+suffix.ExternalDNSTLSCert) - b.secrets = append(b.secrets, externalAlias+suffix.ExternalDNSTLSKey) - } for _, volumeMount := range component.GetVolumeMounts() { volumeMountType := deployment.GetCsiAzureVolumeMountType(&volumeMount) @@ -189,6 +183,11 @@ func (b *componentBuilder) WithHorizontalScalingSummary(hpa *HorizontalScalingSu return b } +func (b *componentBuilder) WithExternalDNS(externalDNS []ExternalDNS) ComponentBuilder { + b.externalDNS = externalDNS + return b +} + func (b *componentBuilder) buildError() error { if len(b.errors) == 0 { return nil @@ -230,6 +229,7 @@ func (b *componentBuilder) BuildComponent() (*Component, error) { AuxiliaryResource: b.auxResource, Identity: b.identity, Notifications: b.notifications, + ExternalDNS: b.externalDNS, HorizontalScalingSummary: b.hpa, }, b.buildError() } diff --git a/api/deployments/models/component_deployment.go b/api/deployments/models/component_deployment.go index 9f183195..948aea4e 100644 --- a/api/deployments/models/component_deployment.go +++ b/api/deployments/models/component_deployment.go @@ -98,9 +98,29 @@ type Component struct { // Notifications is the spec for notification about internal events or changes Notifications *Notifications `json:"notifications,omitempty"` + // Array of external DNS configurations + // + // required: false + ExternalDNS []ExternalDNS `json:"externalDNS,omitempty"` + AuxiliaryResource `json:",inline"` } +// ExternalDNS describes an external DNS entry for a component +// swagger:model ExternalDNS +type ExternalDNS struct { + // Fully Qualified Domain Name + // + // required: true + // example: site.example.com + FQDN string `json:"fqdn"` + + // TLS configuration + // + // required: true + TLS TLS `json:"tls"` +} + // Identity describes external identities type Identity struct { // Azure identity diff --git a/api/deployments/models/tls.go b/api/deployments/models/tls.go new file mode 100644 index 00000000..d7d4ab91 --- /dev/null +++ b/api/deployments/models/tls.go @@ -0,0 +1,106 @@ +package models + +import ( + "crypto/x509" + "encoding/pem" + "time" +) + +// swagger:enum TLSStatusEnum +type TLSStatusEnum string + +const ( + // TLS certificate and private key not set + TLSStatusPending TLSStatusEnum = "Pending" + // TLS certificate and private key is valid + TLSStatusConsistent TLSStatusEnum = "Consistent" + // TLS certificate and private key is invalid + TLSStatusInvalid TLSStatusEnum = "Invalid" +) + +// TLS configuration and status for external DNS +// swagger:model TLS +type TLS struct { + // UseAutomation describes if TLS certificate is automatically issued using automation (ACME) + // + // required: true + UseAutomation bool `json:"useAutomation"` + + // Status of TLS certificate and private key + // + // required: true + // example: Consistent + Status TLSStatusEnum `json:"status"` + + // StatusMessages contains a list of messages related to Status + // + // required: false + StatusMessages []string `json:"statusMessages,omitempty"` + + // Certificates holds the X509 certificate chain + // The first certificate in the list should be the host certificate and the rest should be intermediate certificates + // + // required: false + Certificates []X509Certificate `json:"certificates,omitempty"` +} + +// X509Certificate holds information about a X509 certificate +// swagger:model X509Certificate +type X509Certificate struct { + // Subject contains the distinguished name for the certificate + // + // required: true + // example: CN=mysite.example.com,O=MyOrg,L=MyLocation,C=NO + Subject string `json:"subject"` + // Issuer contains the distinguished name for the certificate's issuer + // + // required: true + // example: CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US + Issuer string `json:"issuer"` + // NotBefore defines the lower date/time validity boundary + // + // required: true + // swagger:strfmt date-time + // example: 2022-08-09T00:00:00Z + NotBefore time.Time `json:"notBefore"` + // NotAfter defines the uppdater date/time validity boundary + // + // required: true + // swagger:strfmt date-time + // example: 2023-08-25T23:59:59Z + NotAfter time.Time `json:"notAfter"` + // DNSNames defines list of Subject Alternate Names in the certificate + // + // required: false + DNSNames []string `json:"dnsNames,omitempty"` +} + +// ParseX509CertificatesFromPEM builds an array of X509Certificate from PEM encoded data +func ParseX509CertificatesFromPEM(certBytes []byte) []X509Certificate { + var certs []X509Certificate + for len(certBytes) > 0 { + var block *pem.Block + block, certBytes = pem.Decode(certBytes) + if block == nil { + break + } + if block.Type != "CERTIFICATE" { + continue + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + continue + } + + certs = append(certs, X509Certificate{ + Subject: cert.Subject.String(), + Issuer: cert.Issuer.String(), + DNSNames: cert.DNSNames, + NotBefore: cert.NotBefore, + NotAfter: cert.NotAfter, + }) + } + + return certs +} diff --git a/api/secrets/models/tls_certificate_test.go b/api/deployments/models/tls_test.go similarity index 77% rename from api/secrets/models/tls_certificate_test.go rename to api/deployments/models/tls_test.go index 68a61159..49b9c478 100644 --- a/api/secrets/models/tls_certificate_test.go +++ b/api/deployments/models/tls_test.go @@ -1,4 +1,4 @@ -package models +package models_test import ( "bytes" @@ -11,19 +11,20 @@ import ( "testing" "time" + "github.com/equinor/radix-api/api/deployments/models" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) -func Test_TlsCertificateTestSuite(t *testing.T) { - suite.Run(t, new(tlsCertificateTestSuite)) +func Test_X509CertificateTestSuite(t *testing.T) { + suite.Run(t, new(x509CertificateTestSuite)) } -type tlsCertificateTestSuite struct { +type x509CertificateTestSuite struct { suite.Suite } -func (s *tlsCertificateTestSuite) Test_ParseTLSCertificatesFromPEM_ValidPEM() { +func (s *x509CertificateTestSuite) Test_ParseX509CertificatesFromPEM_ValidPEM() { cn1, ca1, dns1 := "cn1", "ca1", []string{"dns1_1", "dns1_2"} notBefore1, _ := time.Parse("2006-01-02", "2020-07-01") notAfter1, _ := time.Parse("2006-01-02", "2020-08-01") @@ -36,20 +37,20 @@ func (s *tlsCertificateTestSuite) Test_ParseTLSCertificatesFromPEM_ValidPEM() { b := bytes.NewBuffer(cert1) b.Write(cert2) - expected := []TLSCertificate{ + expected := []models.X509Certificate{ {Subject: "CN=" + cn1, Issuer: "CN=" + ca1, NotBefore: notBefore1, NotAfter: notAfter1, DNSNames: dns1}, {Subject: "CN=" + cn2, Issuer: "CN=" + ca2, NotBefore: notBefore2, NotAfter: notAfter2, DNSNames: dns2}, } - certs := ParseTLSCertificatesFromPEM(b.Bytes()) + certs := models.ParseX509CertificatesFromPEM(b.Bytes()) s.Equal(expected, certs) } -func (s *tlsCertificateTestSuite) Test_ParseTLSCertificatesFromPEM_EmptyPEM() { - certs := ParseTLSCertificatesFromPEM(nil) +func (s *x509CertificateTestSuite) Test_ParseX509CertificatesFromPEM_EmptyPEM() { + certs := models.ParseX509CertificatesFromPEM(nil) s.Empty(certs) } -func (s *tlsCertificateTestSuite) Test_ParseTLSCertificatesFromPEM_NonCertificatePEM() { +func (s *x509CertificateTestSuite) Test_ParseX509CertificatesFromPEM_NonCertificatePEM() { cn1, ca1, dns1 := "cn1", "ca1", []string{"dns1_1", "dns1_2"} notBefore1, _ := time.Parse("2006-01-02", "2020-07-01") notAfter1, _ := time.Parse("2006-01-02", "2020-08-01") @@ -66,14 +67,14 @@ func (s *tlsCertificateTestSuite) Test_ParseTLSCertificatesFromPEM_NonCertificat b := bytes.NewBuffer(cert1) b.Write(certBuf.Bytes()) - expected := []TLSCertificate{ + expected := []models.X509Certificate{ {Subject: "CN=" + cn1, Issuer: "CN=" + ca1, NotBefore: notBefore1, NotAfter: notAfter1, DNSNames: dns1}, } - certs := ParseTLSCertificatesFromPEM(b.Bytes()) + certs := models.ParseX509CertificatesFromPEM(b.Bytes()) s.Equal(expected, certs) } -func (s *tlsCertificateTestSuite) Test_ParseTLSCertificatesFromPEM_InvalidPEMData() { +func (s *x509CertificateTestSuite) Test_ParseX509CertificatesFromPEM_InvalidPEMData() { cn1, ca1, dns1 := "cn1", "ca1", []string{"dns1_1", "dns1_2"} notBefore1, _ := time.Parse("2006-01-02", "2020-07-01") notAfter1, _ := time.Parse("2006-01-02", "2020-08-01") @@ -90,14 +91,14 @@ func (s *tlsCertificateTestSuite) Test_ParseTLSCertificatesFromPEM_InvalidPEMDat b := bytes.NewBuffer(cert1) b.Write(certBuf.Bytes()) - expected := []TLSCertificate{ + expected := []models.X509Certificate{ {Subject: "CN=" + cn1, Issuer: "CN=" + ca1, NotBefore: notBefore1, NotAfter: notAfter1, DNSNames: dns1}, } - certs := ParseTLSCertificatesFromPEM(b.Bytes()) + certs := models.ParseX509CertificatesFromPEM(b.Bytes()) s.Equal(expected, certs) } -func (s *tlsCertificateTestSuite) buildCert(certCN, issuerCN string, notBefore, notAfter time.Time, dnsNames []string) []byte { +func (s *x509CertificateTestSuite) buildCert(certCN, issuerCN string, notBefore, notAfter time.Time, dnsNames []string) []byte { ca := &x509.Certificate{ SerialNumber: big.NewInt(1111), Subject: pkix.Name{CommonName: issuerCN}, diff --git a/api/environments/environment_controller_externaldns_test.go b/api/environments/environment_controller_externaldns_test.go new file mode 100644 index 00000000..4e301259 --- /dev/null +++ b/api/environments/environment_controller_externaldns_test.go @@ -0,0 +1,281 @@ +package environments + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "testing" + "time" + + deploymentModels "github.com/equinor/radix-api/api/deployments/models" + environmentModels "github.com/equinor/radix-api/api/environments/models" + controllertest "github.com/equinor/radix-api/api/test" + tlsvalidationmock "github.com/equinor/radix-api/api/utils/tlsvalidation/mock" + v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + commontest "github.com/equinor/radix-operator/pkg/apis/test" + operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" + radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + secretsstoreclient "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned" +) + +func Test_ExternalDnsTestSuite(t *testing.T) { + suite.Run(t, new(externalDnsTestSuite)) +} + +type externalDnsTestSuite struct { + suite.Suite + tlsValidator *tlsvalidationmock.MockValidator + commonTestUtils *commontest.Utils + envvironmentTestUtils *controllertest.Utils + kubeClient kubernetes.Interface + radixClient radixclient.Interface + secretProviderClient secretsstoreclient.Interface + deployment *v1.RadixDeployment + appName string + componentName string + environmentName string + alias string +} + +func (s *externalDnsTestSuite) buildCertificate(certCN, issuerCN string, dnsNames []string, notBefore, notAfter time.Time) []byte { + ca := &x509.Certificate{ + SerialNumber: big.NewInt(1111), + Subject: pkix.Name{CommonName: issuerCN}, + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + } + caPrivKey, _ := rsa.GenerateKey(rand.Reader, 4096) + cert := &x509.Certificate{ + SerialNumber: big.NewInt(2222), + Subject: pkix.Name{CommonName: certCN}, + DNSNames: dnsNames, + NotBefore: notBefore, + NotAfter: notAfter, + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + } + certPrivKey, _ := rsa.GenerateKey(rand.Reader, 4096) + certBytes, _ := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) + certPEM := new(bytes.Buffer) + err := pem.Encode(certPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + require.NoError(s.T(), err) + return certPEM.Bytes() +} + +func (s *externalDnsTestSuite) SetupTest() { + ctrl := gomock.NewController(s.T()) + s.tlsValidator = tlsvalidationmock.NewMockValidator(ctrl) + s.commonTestUtils, s.envvironmentTestUtils, _, s.kubeClient, s.radixClient, _, s.secretProviderClient = setupTest(s.T(), []EnvironmentHandlerOptions{WithTLSValidator(s.tlsValidator)}) + + s.appName, s.componentName, s.environmentName, s.alias = "any-app", "backend", "dev", "cdn.myalias.com" + + deployment, err := s.commonTestUtils.ApplyDeployment(operatorutils. + ARadixDeployment(). + WithAppName(s.appName). + WithEnvironment(s.environmentName). + WithComponents(operatorutils.NewDeployComponentBuilder().WithName(s.componentName).WithExternalDNS(v1.RadixDeployExternalDNS{FQDN: s.alias})). + WithImageTag("master")) + require.NoError(s.T(), err) + s.deployment = deployment + + _, err = s.commonTestUtils.ApplyApplication(operatorutils. + ARadixApplication(). + WithAppName(s.appName). + WithEnvironment(s.environmentName, "master"). + WithComponents(operatorutils. + AnApplicationComponent(). + WithName(s.componentName))) + require.NoError(s.T(), err) +} + +func (s *externalDnsTestSuite) executeRequest(appName, envName string) (environment *environmentModels.Environment, statusCode int, err error) { + responseChannel := s.envvironmentTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/environments/%s", appName, envName)) + response := <-responseChannel + var env environmentModels.Environment + err = controllertest.GetResponseBody(response, &env) + if err == nil { + environment = &env + } + statusCode = response.Code + return +} + +func (s *externalDnsTestSuite) Test_ExternalDNS_Consistent() { + notBefore, _ := time.Parse("2006-01-02", "2020-07-01") + notAfter, _ := time.Parse("2006-01-02", "2020-08-01") + certCN, issuerCN := "one.example.com", "issuer.example.com" + dnsNames := []string{"dns1", "dns2"} + keyBytes, certBytes := []byte("any key"), s.buildCertificate(certCN, issuerCN, dnsNames, notBefore, notAfter) + + s.tlsValidator.EXPECT().ValidateX509Certificate(certBytes, keyBytes, s.alias).Return(true, nil).Times(1) + + sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) + sut.tlsValidator = s.tlsValidator + + _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.alias, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: keyBytes, + corev1.TLSCertKey: certBytes, + }, + }, + metav1.CreateOptions{}, + ) + require.NoError(s.T(), err) + + environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) + s.Equal(statusCode, 200) + s.NoError(err) + + expectedExternalDNS := []deploymentModels.ExternalDNS{{ + FQDN: s.alias, + TLS: deploymentModels.TLS{ + Status: deploymentModels.TLSStatusConsistent, + Certificates: []deploymentModels.X509Certificate{{ + Subject: "CN=" + certCN, + Issuer: "CN=" + issuerCN, + NotBefore: notBefore, + NotAfter: notAfter, + DNSNames: dnsNames, + }}, + }, + }} + s.ElementsMatch(expectedExternalDNS, environment.ActiveDeployment.Components[0].ExternalDNS) +} + +func (s *externalDnsTestSuite) Test_ExternalDNS_MissingPrivateKeyData() { + notBefore, _ := time.Parse("2006-01-02", "2020-07-01") + notAfter, _ := time.Parse("2006-01-02", "2020-08-01") + certCN, issuerCN := "one.example.com", "issuer.example.com" + dnsNames := []string{"dns1", "dns2"} + certBytes := s.buildCertificate(certCN, issuerCN, dnsNames, notBefore, notAfter) + + sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) + sut.tlsValidator = s.tlsValidator + + _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.alias, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: nil, + corev1.TLSCertKey: certBytes, + }, + }, + metav1.CreateOptions{}, + ) + require.NoError(s.T(), err) + + environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) + s.Equal(statusCode, 200) + s.NoError(err) + + expectedExternalDNS := []deploymentModels.ExternalDNS{{ + FQDN: s.alias, + TLS: deploymentModels.TLS{ + Status: deploymentModels.TLSStatusPending, + }, + }} + s.ElementsMatch(expectedExternalDNS, environment.ActiveDeployment.Components[0].ExternalDNS) +} + +func (s *externalDnsTestSuite) Test_ExternalDNS_MissingCertData() { + keyBytes := []byte("any key") + sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) + sut.tlsValidator = s.tlsValidator + + _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.alias, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: keyBytes, + corev1.TLSCertKey: nil, + }, + }, + metav1.CreateOptions{}, + ) + require.NoError(s.T(), err) + + environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) + s.Equal(statusCode, 200) + s.NoError(err) + + expectedExternalDNS := []deploymentModels.ExternalDNS{{ + FQDN: s.alias, + TLS: deploymentModels.TLS{ + Status: deploymentModels.TLSStatusPending, + }, + }} + s.ElementsMatch(expectedExternalDNS, environment.ActiveDeployment.Components[0].ExternalDNS) +} + +func (s *externalDnsTestSuite) Test_ExternalDNS_CertDataValidationError() { + notBefore, _ := time.Parse("2006-01-02", "2020-07-01") + notAfter, _ := time.Parse("2006-01-02", "2020-08-01") + certCN, issuerCN := "one.example.com", "issuer.example.com" + dnsNames := []string{"dns1", "dns2"} + keyBytes, certBytes := []byte("any key"), s.buildCertificate(certCN, issuerCN, dnsNames, notBefore, notAfter) + certValidationMsg := "any msg" + + s.tlsValidator.EXPECT().ValidateX509Certificate(certBytes, keyBytes, s.alias).Return(false, []string{certValidationMsg}).Times(1) + + sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) + sut.tlsValidator = s.tlsValidator + + _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.alias, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: keyBytes, + corev1.TLSCertKey: certBytes, + }, + }, + metav1.CreateOptions{}, + ) + require.NoError(s.T(), err) + + environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) + s.Equal(statusCode, 200) + s.NoError(err) + + expectedExternalDNS := []deploymentModels.ExternalDNS{{ + FQDN: s.alias, + TLS: deploymentModels.TLS{ + Status: deploymentModels.TLSStatusInvalid, + StatusMessages: []string{certValidationMsg}, + Certificates: []deploymentModels.X509Certificate{{ + Subject: "CN=" + certCN, + Issuer: "CN=" + issuerCN, + NotBefore: notBefore, + NotAfter: notAfter, + DNSNames: dnsNames, + }}, + }, + }} + s.ElementsMatch(expectedExternalDNS, environment.ActiveDeployment.Components[0].ExternalDNS) +} diff --git a/api/environments/environment_controller_secrets_test.go b/api/environments/environment_controller_secrets_test.go index fc009d74..05d5187b 100644 --- a/api/environments/environment_controller_secrets_test.go +++ b/api/environments/environment_controller_secrets_test.go @@ -1,33 +1,22 @@ package environments import ( - "bytes" "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "fmt" - "math/big" "strings" "testing" - "time" environmentModels "github.com/equinor/radix-api/api/environments/models" secretModels "github.com/equinor/radix-api/api/secrets/models" "github.com/equinor/radix-api/api/secrets/suffix" controllertest "github.com/equinor/radix-api/api/test" - tlsvalidatormock "github.com/equinor/radix-api/api/utils/tlsvalidator/mock" "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/pointers" operatordefaults "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - commontest "github.com/equinor/radix-operator/pkg/apis/test" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" batchv1 "k8s.io/api/batch/v1" @@ -146,48 +135,6 @@ func (s *secretHandlerTestSuite) TestSecretHandler_GetSecrets() { }, }, }, - { - name: "External alias secrets with no secrets, must build secrets from deploy component", - components: []v1.RadixDeployComponent{{Name: componentName1, DNSExternalAlias: []string{"deployed-alias-1", "deployed-alias-2"}}}, - expectedSecrets: []secretModels.Secret{ - { - Name: "deployed-alias-1-key", - DisplayName: "Key", - Type: secretModels.SecretTypeClientCert, - Resource: "deployed-alias-1", - Component: componentName1, - Status: secretModels.Pending.String(), - ID: secretModels.SecretIdKey, - }, - { - Name: "deployed-alias-1-cert", - DisplayName: "Certificate", - Type: secretModels.SecretTypeClientCert, - Resource: "deployed-alias-1", - Component: componentName1, - Status: secretModels.Pending.String(), - ID: secretModels.SecretIdCert, - }, - { - Name: "deployed-alias-2-key", - DisplayName: "Key", - Type: secretModels.SecretTypeClientCert, - Resource: "deployed-alias-2", - Component: componentName1, - Status: secretModels.Pending.String(), - ID: secretModels.SecretIdKey, - }, - { - Name: "deployed-alias-2-cert", - DisplayName: "Certificate", - Type: secretModels.SecretTypeClientCert, - Resource: "deployed-alias-2", - Component: componentName1, - Status: secretModels.Pending.String(), - ID: secretModels.SecretIdCert, - }, - }, - }, { name: "Azure Blob volumes credential secrets with no secrets", components: []v1.RadixDeployComponent{ @@ -818,399 +765,6 @@ func (s *secretHandlerTestSuite) TestSecretHandler_GetAuthenticationSecrets() { } } -func Test_ExternalDnsAliasSecretTestSuite(t *testing.T) { - suite.Run(t, new(externalDnsAliasSecretTestSuite)) -} - -type externalDnsAliasSecretTestSuite struct { - suite.Suite - tlsValidator *tlsvalidatormock.MockTLSSecretValidator - commonTestUtils *commontest.Utils - envvironmentTestUtils *controllertest.Utils - kubeClient kubernetes.Interface - radixClient radixclient.Interface - secretProviderClient secretsstoreclient.Interface - deployment *v1.RadixDeployment - appName string - componentName string - environmentName string - alias string -} - -func (s *externalDnsAliasSecretTestSuite) buildCertificate(certCN, issuerCN string, dnsNames []string, notBefore, notAfter time.Time) []byte { - ca := &x509.Certificate{ - SerialNumber: big.NewInt(1111), - Subject: pkix.Name{CommonName: issuerCN}, - IsCA: true, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - } - caPrivKey, _ := rsa.GenerateKey(rand.Reader, 4096) - cert := &x509.Certificate{ - SerialNumber: big.NewInt(2222), - Subject: pkix.Name{CommonName: certCN}, - DNSNames: dnsNames, - NotBefore: notBefore, - NotAfter: notAfter, - SubjectKeyId: []byte{1, 2, 3, 4, 6}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - } - certPrivKey, _ := rsa.GenerateKey(rand.Reader, 4096) - certBytes, _ := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) - certPEM := new(bytes.Buffer) - err := pem.Encode(certPEM, &pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - require.NoError(s.T(), err) - return certPEM.Bytes() -} - -func (s *externalDnsAliasSecretTestSuite) SetupTest() { - ctrl := gomock.NewController(s.T()) - s.tlsValidator = tlsvalidatormock.NewMockTLSSecretValidator(ctrl) - s.commonTestUtils, s.envvironmentTestUtils, _, s.kubeClient, s.radixClient, _, s.secretProviderClient = setupTest(s.T(), []EnvironmentHandlerOptions{WithTLSSecretValidator(s.tlsValidator)}) - - s.appName, s.componentName, s.environmentName, s.alias = "any-app", "backend", "dev", "cdn.myalias.com" - - deployment, err := s.commonTestUtils.ApplyDeployment(operatorutils. - ARadixDeployment(). - WithAppName(s.appName). - WithEnvironment(s.environmentName). - WithComponents(operatorutils.NewDeployComponentBuilder().WithName(s.componentName).WithDNSExternalAlias(s.alias)). - WithImageTag("master")) - require.NoError(s.T(), err) - s.deployment = deployment - - _, err = s.commonTestUtils.ApplyApplication(operatorutils. - ARadixApplication(). - WithAppName(s.appName). - WithEnvironment(s.environmentName, "master"). - WithComponents(operatorutils. - AnApplicationComponent(). - WithName(s.componentName))) - require.NoError(s.T(), err) -} - -func (s *externalDnsAliasSecretTestSuite) executeRequest(appName, envName string) (environment *environmentModels.Environment, statusCode int, err error) { - responseChannel := s.envvironmentTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/environments/%s", appName, envName)) - response := <-responseChannel - var env environmentModels.Environment - err = controllertest.GetResponseBody(response, &env) - if err == nil { - environment = &env - } - statusCode = response.Code - return -} - -func (s *externalDnsAliasSecretTestSuite) Test_ExternalAliasSecret_Consistent() { - notBefore, _ := time.Parse("2006-01-02", "2020-07-01") - notAfter, _ := time.Parse("2006-01-02", "2020-08-01") - certCN, issuerCN := "one.example.com", "issuer.example.com" - dnsNames := []string{"dns1", "dns2"} - keyBytes, certBytes := []byte("any key"), s.buildCertificate(certCN, issuerCN, dnsNames, notBefore, notAfter) - - s.tlsValidator.EXPECT().ValidateTLSKey(keyBytes).Return(true, nil).Times(1) - s.tlsValidator.EXPECT().ValidateTLSCertificate(certBytes, keyBytes, s.alias).Return(true, nil).Times(1) - - sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) - sut.tlsSecretValidator = s.tlsValidator - - _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.alias, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: keyBytes, - corev1.TLSCertKey: certBytes, - }, - }, - metav1.CreateOptions{}, - ) - require.NoError(s.T(), err) - - environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) - s.Equal(statusCode, 200) - s.NoError(err) - - expectedSecrets := []secretModels.Secret{ - {Name: s.alias + "-key", DisplayName: "Key", - Status: secretModels.Consistent.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdKey, - }, - {Name: s.alias + "-cert", DisplayName: "Certificate", - Status: secretModels.Consistent.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdCert, - TLSCertificates: []secretModels.TLSCertificate{ - { - Subject: "CN=" + certCN, - Issuer: "CN=" + issuerCN, - NotBefore: notBefore, - NotAfter: notAfter, - DNSNames: dnsNames, - }, - }, - }, - } - s.ElementsMatch(expectedSecrets, environment.Secrets) -} - -func (s *externalDnsAliasSecretTestSuite) Test_ExternalAliasSecret_MissingKeyData() { - notBefore, _ := time.Parse("2006-01-02", "2020-07-01") - notAfter, _ := time.Parse("2006-01-02", "2020-08-01") - certCN, issuerCN := "one.example.com", "issuer.example.com" - dnsNames := []string{"dns1", "dns2"} - certBytes := s.buildCertificate(certCN, issuerCN, dnsNames, notBefore, notAfter) - - s.tlsValidator.EXPECT().ValidateTLSCertificate(certBytes, nil, s.alias).Return(true, nil).Times(1) - - sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) - sut.tlsSecretValidator = s.tlsValidator - - _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.alias, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: nil, - corev1.TLSCertKey: certBytes, - }, - }, - metav1.CreateOptions{}, - ) - require.NoError(s.T(), err) - - environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) - s.Equal(statusCode, 200) - s.NoError(err) - - expectedSecrets := []secretModels.Secret{ - {Name: s.alias + "-key", DisplayName: "Key", - Status: secretModels.Pending.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdKey, - }, - {Name: s.alias + "-cert", DisplayName: "Certificate", - Status: secretModels.Consistent.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdCert, - TLSCertificates: []secretModels.TLSCertificate{ - { - Subject: "CN=" + certCN, - Issuer: "CN=" + issuerCN, - NotBefore: notBefore, - NotAfter: notAfter, - DNSNames: dnsNames, - }, - }, - }, - } - s.ElementsMatch(expectedSecrets, environment.Secrets) -} - -func (s *externalDnsAliasSecretTestSuite) Test_ExternalAliasSecret_KeyDataValidationError() { - notBefore, _ := time.Parse("2006-01-02", "2020-07-01") - notAfter, _ := time.Parse("2006-01-02", "2020-08-01") - certCN, issuerCN := "one.example.com", "issuer.example.com" - dnsNames := []string{"dns1", "dns2"} - keyBytes, certBytes := []byte("any key"), s.buildCertificate(certCN, issuerCN, dnsNames, notBefore, notAfter) - keyValidationMsg := "any message" - - s.tlsValidator.EXPECT().ValidateTLSKey(keyBytes).Return(false, []string{keyValidationMsg}).Times(1) - s.tlsValidator.EXPECT().ValidateTLSCertificate(certBytes, keyBytes, s.alias).Return(true, nil).Times(1) - - sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) - sut.tlsSecretValidator = s.tlsValidator - - _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.alias, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: keyBytes, - corev1.TLSCertKey: certBytes, - }, - }, - metav1.CreateOptions{}, - ) - require.NoError(s.T(), err) - - environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) - s.Equal(statusCode, 200) - s.NoError(err) - - expectedSecrets := []secretModels.Secret{ - {Name: s.alias + "-key", DisplayName: "Key", - Status: secretModels.Invalid.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdKey, - StatusMessages: []string{keyValidationMsg}, - }, - {Name: s.alias + "-cert", DisplayName: "Certificate", - Status: secretModels.Consistent.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdCert, - TLSCertificates: []secretModels.TLSCertificate{ - { - Subject: "CN=" + certCN, - Issuer: "CN=" + issuerCN, - NotBefore: notBefore, - NotAfter: notAfter, - DNSNames: dnsNames, - }, - }, - }, - } - s.ElementsMatch(expectedSecrets, environment.Secrets) -} - -func (s *externalDnsAliasSecretTestSuite) Test_ExternalAliasSecret_MissingCertData() { - keyBytes := []byte("any key") - - s.tlsValidator.EXPECT().ValidateTLSKey(keyBytes).Return(true, nil).Times(1) - - sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) - sut.tlsSecretValidator = s.tlsValidator - - _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.alias, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: keyBytes, - corev1.TLSCertKey: nil, - }, - }, - metav1.CreateOptions{}, - ) - require.NoError(s.T(), err) - - environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) - s.Equal(statusCode, 200) - s.NoError(err) - - expectedSecrets := []secretModels.Secret{ - {Name: s.alias + "-key", DisplayName: "Key", - Status: secretModels.Consistent.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdKey, - }, - {Name: s.alias + "-cert", DisplayName: "Certificate", - Status: secretModels.Pending.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdCert, - }, - } - s.ElementsMatch(expectedSecrets, environment.Secrets) -} - -func (s *externalDnsAliasSecretTestSuite) Test_ExternalAliasSecret_CertDataParseError() { - keyBytes, certBytes := []byte("any key"), []byte("any cert") - - s.tlsValidator.EXPECT().ValidateTLSKey(keyBytes).Return(true, nil).Times(1) - s.tlsValidator.EXPECT().ValidateTLSCertificate(certBytes, keyBytes, s.alias).Return(true, nil).Times(1) - - sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) - sut.tlsSecretValidator = s.tlsValidator - - _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.alias, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: keyBytes, - corev1.TLSCertKey: certBytes, - }, - }, - metav1.CreateOptions{}, - ) - require.NoError(s.T(), err) - - environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) - s.Equal(statusCode, 200) - s.NoError(err) - - expectedSecrets := []secretModels.Secret{ - {Name: s.alias + "-key", DisplayName: "Key", - Status: secretModels.Consistent.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdKey, - }, - {Name: s.alias + "-cert", DisplayName: "Certificate", - Status: secretModels.Consistent.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdCert, - }, - } - s.ElementsMatch(expectedSecrets, environment.Secrets) -} - -func (s *externalDnsAliasSecretTestSuite) Test_ExternalAliasSecret_CertDataValidationError() { - notBefore, _ := time.Parse("2006-01-02", "2020-07-01") - notAfter, _ := time.Parse("2006-01-02", "2020-08-01") - certCN, issuerCN := "one.example.com", "issuer.example.com" - dnsNames := []string{"dns1", "dns2"} - keyBytes, certBytes := []byte("any key"), s.buildCertificate(certCN, issuerCN, dnsNames, notBefore, notAfter) - certValidationMsg := "any msg" - - s.tlsValidator.EXPECT().ValidateTLSKey(keyBytes).Return(true, nil).Times(1) - s.tlsValidator.EXPECT().ValidateTLSCertificate(certBytes, keyBytes, s.alias).Return(false, []string{certValidationMsg}).Times(1) - - sut := initHandler(s.kubeClient, s.radixClient, s.secretProviderClient) - sut.tlsSecretValidator = s.tlsValidator - - _, err := s.kubeClient.CoreV1().Secrets(s.appName+"-"+s.environmentName).Create(context.Background(), - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: s.alias, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: keyBytes, - corev1.TLSCertKey: certBytes, - }, - }, - metav1.CreateOptions{}, - ) - require.NoError(s.T(), err) - - environment, statusCode, err := s.executeRequest(s.appName, s.environmentName) - s.Equal(statusCode, 200) - s.NoError(err) - - expectedSecrets := []secretModels.Secret{ - {Name: s.alias + "-key", DisplayName: "Key", - Status: secretModels.Consistent.String(), - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdKey, - }, - {Name: s.alias + "-cert", DisplayName: "Certificate", - Status: secretModels.Invalid.String(), - StatusMessages: []string{certValidationMsg}, - Resource: s.alias, - Type: secretModels.SecretTypeClientCert, Component: s.componentName, ID: secretModels.SecretIdCert, - TLSCertificates: []secretModels.TLSCertificate{ - { - Subject: "CN=" + certCN, - Issuer: "CN=" + issuerCN, - NotBefore: notBefore, - NotAfter: notAfter, - DNSNames: dnsNames, - }, - }, - }, - } - s.ElementsMatch(expectedSecrets, environment.Secrets) -} - func (s *secretHandlerTestSuite) assertSecrets(scenario *getSecretScenario, secrets []secretModels.Secret) { s.Equal(len(scenario.expectedSecrets), len(secrets)) secretMap := testGetSecretMap(secrets) diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index d3252419..18b9db9c 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -75,7 +75,7 @@ func setupTest(t *testing.T, envHandlerOpts []EnvironmentHandlerOptions) (*commo require.NoError(t, err) // secretControllerTestUtils is used for issuing HTTP request and processing responses - secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, secretproviderclient, secrets.NewSecretController()) + secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, secretproviderclient, secrets.NewSecretController(nil)) // controllerTestUtils is used for issuing HTTP request and processing responses environmentControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, secretproviderclient, NewEnvironmentController(NewEnvironmentHandlerFactory(envHandlerOpts...))) @@ -818,54 +818,6 @@ func Test_GetEnvironmentEvents_Controller(t *testing.T) { }) } -// secret tests -func TestUpdateSecret_TLSSecretForExternalAlias_UpdatedOk(t *testing.T) { - // Setup - commonTestUtils, environmentControllerTestUtils, controllerTestUtils, client, radixclient, promclient, secretproviderclient := setupTest(t, nil) - err := utils.ApplyDeploymentWithSync(client, radixclient, promclient, commonTestUtils, secretproviderclient, operatorutils.ARadixDeployment(). - WithAppName(anyAppName). - WithEnvironment(anyEnvironment). - WithRadixApplication(operatorutils.ARadixApplication(). - WithAppName(anyAppName). - WithEnvironment(anyEnvironment, "master"). - WithDNSExternalAlias("some.alias.com", anyEnvironment, anyComponentName). - WithDNSExternalAlias("another.alias.com", anyEnvironment, anyComponentName)). - WithComponents( - operatorutils.NewDeployComponentBuilder(). - WithName(anyComponentName). - WithPort("http", 8080). - WithPublicPort("http"). - WithDNSExternalAlias("some.alias.com"). - WithDNSExternalAlias("another.alias.com"))) - require.NoError(t, err) - - // Test - responseChannel := environmentControllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/environments/%s", anyAppName, anyEnvironment)) - response := <-responseChannel - - environment := environmentModels.Environment{} - err = controllertest.GetResponseBody(response, &environment) - require.NoError(t, err) - assert.Equal(t, 4, len(environment.Secrets)) - assert.True(t, contains(environment.Secrets, "some.alias.com-cert")) - assert.True(t, contains(environment.Secrets, "some.alias.com-key")) - assert.True(t, contains(environment.Secrets, "another.alias.com-cert")) - assert.True(t, contains(environment.Secrets, "another.alias.com-key")) - - parameters := secretModels.SecretParameters{ - SecretValue: "anyValue", - } - - putUrl := fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/secrets/%s", anyAppName, anyEnvironment, anyComponentName, environment.Secrets[0].Name) - responseChannel = controllerTestUtils.ExecuteRequestWithParameters("PUT", putUrl, parameters) - response = <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) - - responseChannel = controllerTestUtils.ExecuteRequestWithParameters("PUT", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/secrets/%s", anyAppName, anyEnvironment, anyComponentName, environment.Secrets[1].Name), parameters) - response = <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) -} - func TestUpdateSecret_AccountSecretForComponentVolumeMount_UpdatedOk(t *testing.T) { // Setup commonTestUtils, environmentControllerTestUtils, controllerTestUtils, client, radixclient, promclient, secretProviderClient := setupTest(t, nil) diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 9be72e82..9202fc41 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -17,7 +17,7 @@ import ( "github.com/equinor/radix-api/api/utils" "github.com/equinor/radix-api/api/utils/jobscheduler" "github.com/equinor/radix-api/api/utils/predicate" - "github.com/equinor/radix-api/api/utils/tlsvalidator" + "github.com/equinor/radix-api/api/utils/tlsvalidation" "github.com/equinor/radix-api/models" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/slice" @@ -60,10 +60,10 @@ func WithEventHandler(eventHandler events.EventHandler) EnvironmentHandlerOption } } -// WithTLSSecretValidator configures the tlsSecretValidator used by EnvironmentHandler -func WithTLSSecretValidator(validator tlsvalidator.TLSSecretValidator) EnvironmentHandlerOptions { +// WithTLSValidator configures the tlsValidator used by EnvironmentHandler +func WithTLSValidator(validator tlsvalidation.Validator) EnvironmentHandlerOptions { return func(eh *EnvironmentHandler) { - eh.tlsSecretValidator = validator + eh.tlsValidator = validator } } @@ -99,7 +99,7 @@ type EnvironmentHandler struct { accounts models.Accounts kubeUtil *kube.Kube kubeUtilForServiceAccount *kube.Kube - tlsSecretValidator tlsvalidator.TLSSecretValidator + tlsValidator tlsvalidation.Validator jobSchedulerHandlerFactory jobscheduler.HandlerFactoryInterface } @@ -204,7 +204,7 @@ func (eh EnvironmentHandler) GetEnvironment(ctx context.Context, appName, envNam return nil, err } - env := apimodels.BuildEnvironment(rr, ra, re, rdList, rjList, deploymentList, componentPodList, hpaList, secretList, secretProviderClassList, eh.tlsSecretValidator) + env := apimodels.BuildEnvironment(rr, ra, re, rdList, rjList, deploymentList, componentPodList, hpaList, secretList, secretProviderClassList, eh.tlsValidator) return env, nil } diff --git a/api/jobs/job_handler.go b/api/jobs/job_handler.go index cfa950e5..2fe4ddb5 100644 --- a/api/jobs/job_handler.go +++ b/api/jobs/job_handler.go @@ -194,7 +194,7 @@ func getPipelineRunModel(pipelineRun *pipelinev1.PipelineRun) *jobModels.Pipelin } runCondition := getLastReadyCondition(pipelineRun.Status.Conditions) if runCondition != nil { - pipelineRunModel.Status = runCondition.Reason + pipelineRunModel.Status = jobModels.TaskRunReason(runCondition.Reason) pipelineRunModel.StatusMessage = runCondition.Message } return &pipelineRunModel @@ -221,7 +221,7 @@ func getPipelineRunTaskModelByTaskSpec(pipelineRun *pipelinev1.PipelineRun, task pipelineTaskModel.Ended = radixutils.FormatTime(taskRun.Status.CompletionTime) taskCondition := getLastReadyCondition(taskRun.Status.Conditions) if taskCondition != nil { - pipelineTaskModel.Status = taskCondition.Reason + pipelineTaskModel.Status = jobModels.PipelineRunReason(taskCondition.Reason) pipelineTaskModel.StatusMessage = taskCondition.Message } logEmbeddedCommandIndex := strings.Index(pipelineTaskModel.StatusMessage, "for logs run") @@ -238,13 +238,13 @@ func buildPipelineRunTaskStepModels(taskRun *pipelinev1.TaskRun) []jobModels.Pip if stepStatus.Terminated != nil { stepModel.Started = radixutils.FormatTime(&stepStatus.Terminated.StartedAt) stepModel.Ended = radixutils.FormatTime(&stepStatus.Terminated.FinishedAt) - stepModel.Status = stepStatus.Terminated.Reason + stepModel.Status = jobModels.TaskRunReason(stepStatus.Terminated.Reason) stepModel.StatusMessage = stepStatus.Terminated.Message } else if stepStatus.Running != nil { stepModel.Started = radixutils.FormatTime(&stepStatus.Running.StartedAt) - stepModel.Status = jobModels.Running.String() + stepModel.Status = jobModels.TaskRunReasonRunning } else if stepStatus.Waiting != nil { - stepModel.Status = stepStatus.Waiting.Reason + stepModel.Status = jobModels.TaskRunReason(stepStatus.Waiting.Reason) stepModel.StatusMessage = stepStatus.Waiting.Message } stepsModels = append(stepsModels, stepModel) diff --git a/api/jobs/job_handler_test.go b/api/jobs/job_handler_test.go index 9c9de62e..b3bed268 100644 --- a/api/jobs/job_handler_test.go +++ b/api/jobs/job_handler_test.go @@ -181,7 +181,8 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob() { {Name: comp2Name, Type: comp2Type, Image: comp2Image}, } - //nolint:staticcheck // SA1019 we want to make sure that Components is populated for backward compatibility (at least for a while) + //nolint:staticcheck + //lint:ignore SA1019 we want to make sure that Components is populated for backward compatibility (at least for a while) s.ElementsMatch(slice.PointersOf(expectedComponents), actualJob.Components) expectedSteps := []jobModels.Step{ {Name: step1Name, PodName: step1Pod, Status: string(step1Condition), Started: &step1Started.Time, Ended: &step1Ended.Time, Components: step1Components}, diff --git a/api/jobs/models/job.go b/api/jobs/models/job.go index 31209f94..7a04f51b 100644 --- a/api/jobs/models/job.go +++ b/api/jobs/models/job.go @@ -86,12 +86,6 @@ type Job struct { // required: false PromotedFromDeployment string `json:"promotedFromDeployment,omitempty"` - // PromotedDeploymentName the name of the deployment that was promoted - // - // required: false - // example: component-6hznh - PromotedDeploymentName string `json:"promotedDeploymentName,omitempty"` - // PromotedFromEnvironment the name of the environment that was promoted from // // required: false diff --git a/api/jobs/models/pipeline_run.go b/api/jobs/models/pipeline_run.go index 1fbbdd84..6a12f655 100644 --- a/api/jobs/models/pipeline_run.go +++ b/api/jobs/models/pipeline_run.go @@ -24,8 +24,7 @@ type PipelineRun struct { // Status of the step // // required: false - // example: Started - Status string `json:"status"` + Status TaskRunReason `json:"status"` // StatusMessage of the task // diff --git a/api/jobs/models/pipeline_run_task.go b/api/jobs/models/pipeline_run_task.go index 236dd3df..5e39a934 100644 --- a/api/jobs/models/pipeline_run_task.go +++ b/api/jobs/models/pipeline_run_task.go @@ -1,5 +1,7 @@ package models +import tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + // PipelineRunTask holds general information about pipeline run task // swagger:model PipelineRunTask type PipelineRunTask struct { @@ -30,8 +32,7 @@ type PipelineRunTask struct { // Status of the task // // required: false - // example: Running - Status string `json:"status"` + Status PipelineRunReason `json:"status"` // StatusMessage of the task // @@ -50,3 +51,100 @@ type PipelineRunTask struct { // example: 2006-01-02T15:04:05Z Ended string `json:"ended"` } + +// PipelineRunReason copies the fields from github.com/tektoncd/pipeline so go-swagger can map the enums +// swagger:enum PipelineRunReason +type PipelineRunReason tektonv1.PipelineRunReason +const ( + // PipelineRunReasonStarted is the reason set when the PipelineRun has just started + PipelineRunReasonStarted PipelineRunReason = "Started" + // PipelineRunReasonRunning is the reason set when the PipelineRun is running + PipelineRunReasonRunning PipelineRunReason = "Running" + // PipelineRunReasonSuccessful is the reason set when the PipelineRun completed successfully + PipelineRunReasonSuccessful PipelineRunReason = "Succeeded" + // PipelineRunReasonCompleted is the reason set when the PipelineRun completed successfully with one or more skipped Tasks + PipelineRunReasonCompleted PipelineRunReason = "Completed" + // PipelineRunReasonFailed is the reason set when the PipelineRun completed with a failure + PipelineRunReasonFailed PipelineRunReason = "Failed" + // PipelineRunReasonCancelled is the reason set when the PipelineRun cancelled by the user + // This reason may be found with a corev1.ConditionFalse status, if the cancellation was processed successfully + // This reason may be found with a corev1.ConditionUnknown status, if the cancellation is being processed or failed + PipelineRunReasonCancelled PipelineRunReason = "Cancelled" + // PipelineRunReasonPending is the reason set when the PipelineRun is in the pending state + PipelineRunReasonPending PipelineRunReason = "PipelineRunPending" + // PipelineRunReasonTimedOut is the reason set when the PipelineRun has timed out + PipelineRunReasonTimedOut PipelineRunReason = "PipelineRunTimeout" + // PipelineRunReasonStopping indicates that no new Tasks will be scheduled by the controller, and the + // pipeline will stop once all running tasks complete their work + PipelineRunReasonStopping PipelineRunReason = "PipelineRunStopping" + // PipelineRunReasonCancelledRunningFinally indicates that pipeline has been gracefully cancelled + // and no new Tasks will be scheduled by the controller, but final tasks are now running + PipelineRunReasonCancelledRunningFinally PipelineRunReason = "CancelledRunningFinally" + // PipelineRunReasonStoppedRunningFinally indicates that pipeline has been gracefully stopped + // and no new Tasks will be scheduled by the controller, but final tasks are now running + PipelineRunReasonStoppedRunningFinally PipelineRunReason = "StoppedRunningFinally" + // ReasonCouldntGetPipeline indicates that the reason for the failure status is that the + // associated Pipeline couldn't be retrieved + PipelineRunReasonCouldntGetPipeline PipelineRunReason = "CouldntGetPipeline" + // ReasonInvalidBindings indicates that the reason for the failure status is that the + // PipelineResources bound in the PipelineRun didn't match those declared in the Pipeline + PipelineRunReasonInvalidBindings PipelineRunReason = "InvalidPipelineResourceBindings" + // ReasonInvalidWorkspaceBinding indicates that a Pipeline expects a workspace but a + // PipelineRun has provided an invalid binding. + PipelineRunReasonInvalidWorkspaceBinding PipelineRunReason = "InvalidWorkspaceBindings" + // ReasonInvalidTaskRunSpec indicates that PipelineRun.Spec.TaskRunSpecs[].PipelineTaskName is defined with + // a not exist taskName in pipelineSpec. + PipelineRunReasonInvalidTaskRunSpec PipelineRunReason = "InvalidTaskRunSpecs" + // ReasonParameterTypeMismatch indicates that the reason for the failure status is that + // parameter(s) declared in the PipelineRun do not have the some declared type as the + // parameters(s) declared in the Pipeline that they are supposed to override. + PipelineRunReasonParameterTypeMismatch PipelineRunReason = "ParameterTypeMismatch" + // ReasonObjectParameterMissKeys indicates that the object param value provided from PipelineRun spec + // misses some keys required for the object param declared in Pipeline spec. + PipelineRunReasonObjectParameterMissKeys PipelineRunReason = "ObjectParameterMissKeys" + // ReasonParamArrayIndexingInvalid indicates that the use of param array indexing is not under correct api fields feature gate + // or the array is out of bound. + PipelineRunReasonParamArrayIndexingInvalid PipelineRunReason = "ParamArrayIndexingInvalid" + // ReasonCouldntGetTask indicates that the reason for the failure status is that the + // associated Pipeline's Tasks couldn't all be retrieved + PipelineRunReasonCouldntGetTask PipelineRunReason = "CouldntGetTask" + // ReasonParameterMissing indicates that the reason for the failure status is that the + // associated PipelineRun didn't provide all the required parameters + PipelineRunReasonParameterMissing PipelineRunReason = "ParameterMissing" + // ReasonFailedValidation indicates that the reason for failure status is + // that pipelinerun failed runtime validation + PipelineRunReasonFailedValidation PipelineRunReason = "PipelineValidationFailed" + // PipelineRunReasonCouldntGetPipelineResult indicates that the pipeline fails to retrieve the + // referenced result. This could be due to failed TaskRuns or Runs that were supposed to produce + // the results + PipelineRunReasonCouldntGetPipelineResult PipelineRunReason = "CouldntGetPipelineResult" + // ReasonInvalidGraph indicates that the reason for the failure status is that the + // associated Pipeline is an invalid graph (a.k.a wrong order, cycle, …) + PipelineRunReasonInvalidGraph PipelineRunReason = "PipelineInvalidGraph" + // ReasonCouldntCancel indicates that a PipelineRun was cancelled but attempting to update + // all of the running TaskRuns as cancelled failed. + PipelineRunReasonCouldntCancel PipelineRunReason = "PipelineRunCouldntCancel" + // ReasonCouldntTimeOut indicates that a PipelineRun was timed out but attempting to update + // all of the running TaskRuns as timed out failed. + PipelineRunReasonCouldntTimeOut PipelineRunReason = "PipelineRunCouldntTimeOut" + // ReasonInvalidMatrixParameterTypes indicates a matrix contains invalid parameter types + PipelineRunReasonInvalidMatrixParameterTypes PipelineRunReason = "InvalidMatrixParameterTypes" + // ReasonInvalidTaskResultReference indicates a task result was declared + // but was not initialized by that task + PipelineRunReasonInvalidTaskResultReference PipelineRunReason = "InvalidTaskResultReference" + // ReasonRequiredWorkspaceMarkedOptional indicates an optional workspace + // has been passed to a Task that is expecting a non-optional workspace + PipelineRunReasonRequiredWorkspaceMarkedOptional PipelineRunReason = "RequiredWorkspaceMarkedOptional" + // ReasonResolvingPipelineRef indicates that the PipelineRun is waiting for + // its pipelineRef to be asynchronously resolved. + PipelineRunReasonResolvingPipelineRef PipelineRunReason = "ResolvingPipelineRef" + // ReasonResourceVerificationFailed indicates that the pipeline fails the trusted resource verification, + // it could be the content has changed, signature is invalid or public key is invalid + PipelineRunReasonResourceVerificationFailed PipelineRunReason = "ResourceVerificationFailed" + // ReasonCreateRunFailed indicates that the pipeline fails to create the taskrun or other run resources + PipelineRunReasonCreateRunFailed PipelineRunReason = "CreateRunFailed" + // ReasonCELEvaluationFailed indicates the pipeline fails the CEL evaluation + PipelineRunReasonCELEvaluationFailed PipelineRunReason = "CELEvaluationFailed" + // PipelineRunReasonInvalidParamValue indicates that the PipelineRun Param input value is not allowed. + PipelineRunReasonInvalidParamValue PipelineRunReason = "InvalidParamValue" +) diff --git a/api/jobs/models/pipeline_run_task_step.go b/api/jobs/models/pipeline_run_task_step.go index e999cc0b..392d6b82 100644 --- a/api/jobs/models/pipeline_run_task_step.go +++ b/api/jobs/models/pipeline_run_task_step.go @@ -1,5 +1,7 @@ package models +import tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1" + // PipelineRunTaskStep holds general information about pipeline run task steps // swagger:model PipelineRunTaskStep type PipelineRunTaskStep struct { @@ -12,8 +14,7 @@ type PipelineRunTaskStep struct { // Status of the task // // required: false - // example: Completed - Status string `json:"status"` + Status TaskRunReason `json:"status"` // StatusMessage of the task // @@ -32,3 +33,53 @@ type PipelineRunTaskStep struct { // example: 2006-01-02T15:04:05Z Ended string `json:"ended"` } + +// TaskRunReason copies the fields from github.com/tektoncd/pipeline so go-swagger can map the enums +// swagger:enum TaskRunReason +type TaskRunReason tektonv1.TaskRunReason + +const ( + // TaskRunReasonStarted is the reason set when the TaskRun has just started + TaskRunReasonStarted TaskRunReason = "Started" + // TaskRunReasonRunning is the reason set when the TaskRun is running + TaskRunReasonRunning TaskRunReason = "Running" + // TaskRunReasonSuccessful is the reason set when the TaskRun completed successfully + TaskRunReasonSuccessful TaskRunReason = "Succeeded" + // TaskRunReasonFailed is the reason set when the TaskRun completed with a failure + TaskRunReasonFailed TaskRunReason = "Failed" + // TaskRunReasonToBeRetried is the reason set when the last TaskRun execution failed, and will be retried + TaskRunReasonToBeRetried TaskRunReason = "ToBeRetried" + // TaskRunReasonCancelled is the reason set when the TaskRun is cancelled by the user + TaskRunReasonCancelled TaskRunReason = "TaskRunCancelled" + // TaskRunReasonTimedOut is the reason set when one TaskRun execution has timed out + TaskRunReasonTimedOut TaskRunReason = "TaskRunTimeout" + // TaskRunReasonResolvingTaskRef indicates that the TaskRun is waiting for + // its taskRef to be asynchronously resolved. + TaskRunReasonResolvingTaskRef = "ResolvingTaskRef" + // TaskRunReasonResolvingStepActionRef indicates that the TaskRun is waiting for + // its StepAction's Ref to be asynchronously resolved. + TaskRunReasonResolvingStepActionRef = "ResolvingStepActionRef" + // TaskRunReasonImagePullFailed is the reason set when the step of a task fails due to image not being pulled + TaskRunReasonImagePullFailed TaskRunReason = "TaskRunImagePullFailed" + // TaskRunReasonResultLargerThanAllowedLimit is the reason set when one of the results exceeds its maximum allowed limit of 1 KB + TaskRunReasonResultLargerThanAllowedLimit TaskRunReason = "TaskRunResultLargerThanAllowedLimit" + // TaskRunReasonStopSidecarFailed indicates that the sidecar is not properly stopped. + TaskRunReasonStopSidecarFailed TaskRunReason = "TaskRunStopSidecarFailed" + // TaskRunReasonInvalidParamValue indicates that the TaskRun Param input value is not allowed. + TaskRunReasonInvalidParamValue TaskRunReason = "InvalidParamValue" + // TaskRunReasonFailedResolution indicated that the reason for failure status is + // that references within the TaskRun could not be resolved + TaskRunReasonFailedResolution TaskRunReason = "TaskRunResolutionFailed" + // TaskRunReasonFailedValidation indicated that the reason for failure status is + // that taskrun failed runtime validation + TaskRunReasonFailedValidation TaskRunReason = "TaskRunValidationFailed" + // TaskRunReasonTaskFailedValidation indicated that the reason for failure status is + // that task failed runtime validation + TaskRunReasonTaskFailedValidation TaskRunReason = "TaskValidationFailed" + // TaskRunReasonResourceVerificationFailed indicates that the task fails the trusted resource verification, + // it could be the content has changed, signature is invalid or public key is invalid + TaskRunReasonResourceVerificationFailed TaskRunReason = "ResourceVerificationFailed" + // TaskRunReasonFailureIgnored is the reason set when the Taskrun has failed due to pod execution error and the failure is ignored for the owning PipelineRun. + // TaskRuns failed due to reconciler/validation error should not use this reason. + TaskRunReasonFailureIgnored TaskRunReason = "FailureIgnored" +) diff --git a/api/models/component.go b/api/models/component.go index 8510ea1f..a2b98cda 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -6,6 +6,7 @@ import ( deploymentModels "github.com/equinor/radix-api/api/deployments/models" "github.com/equinor/radix-api/api/utils" "github.com/equinor/radix-api/api/utils/predicate" + "github.com/equinor/radix-api/api/utils/tlsvalidation" commonutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/slice" operatordefaults "github.com/equinor/radix-operator/pkg/apis/defaults" @@ -19,25 +20,27 @@ import ( ) // BuildComponents builds a list of Component models. -func BuildComponents(ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler) []*deploymentModels.Component { +func BuildComponents(ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, tlsValidator tlsvalidation.Validator) []*deploymentModels.Component { var components []*deploymentModels.Component for _, component := range rd.Spec.Components { - components = append(components, buildComponent(&component, ra, rd, deploymentList, podList, hpaList)) + components = append(components, buildComponent(&component, ra, rd, deploymentList, podList, hpaList, secretList, tlsValidator)) } for _, job := range rd.Spec.Jobs { - components = append(components, buildComponent(&job, ra, rd, deploymentList, podList, hpaList)) + components = append(components, buildComponent(&job, ra, rd, deploymentList, podList, hpaList, secretList, tlsValidator)) } return components } -func buildComponent(radixComponent radixv1.RadixCommonDeployComponent, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler) *deploymentModels.Component { +func buildComponent(radixComponent radixv1.RadixCommonDeployComponent, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, tlsValidator tlsvalidation.Validator) *deploymentModels.Component { + builder := deploymentModels.NewComponentBuilder(). WithComponent(radixComponent). WithStatus(deploymentModels.ConsistentComponent). - WithHorizontalScalingSummary(getHpaSummary(ra.Name, radixComponent.GetName(), hpaList)) + WithHorizontalScalingSummary(getHpaSummary(ra.Name, radixComponent.GetName(), hpaList)). + WithExternalDNS(getComponentExternalDNS(radixComponent, secretList, tlsValidator)) componentPods := slice.FindAll(podList, predicate.IsPodForComponent(ra.Name, radixComponent.GetName())) @@ -69,6 +72,54 @@ func buildComponent(radixComponent radixv1.RadixCommonDeployComponent, ra *radix return component } +func getComponentExternalDNS(component radixv1.RadixCommonDeployComponent, secretList []corev1.Secret, tlsValidator tlsvalidation.Validator) []deploymentModels.ExternalDNS { + var externalDNSList []deploymentModels.ExternalDNS + + if tlsValidator == nil { + tlsValidator = tlsvalidation.DefaultValidator() + } + + for _, externalAlias := range component.GetExternalDNS() { + var certData, keyData []byte + status := deploymentModels.TLSStatusConsistent + + if secretValue, ok := slice.FindFirst(secretList, isSecretWithName(externalAlias.FQDN)); ok { + certData = secretValue.Data[corev1.TLSCertKey] + keyData = secretValue.Data[corev1.TLSPrivateKeyKey] + if certValue, keyValue := strings.TrimSpace(string(certData)), strings.TrimSpace(string(keyData)); len(certValue) == 0 || len(keyValue) == 0 || strings.EqualFold(certValue, secretDefaultData) || strings.EqualFold(keyValue, secretDefaultData) { + status = deploymentModels.TLSStatusPending + } + } else { + status = deploymentModels.TLSStatusPending + } + + var x509Certs []deploymentModels.X509Certificate + var statusMessages []string + if status == deploymentModels.TLSStatusConsistent { + x509Certs = append(x509Certs, deploymentModels.ParseX509CertificatesFromPEM(certData)...) + + if certIsValid, messages := tlsValidator.ValidateX509Certificate(certData, keyData, externalAlias.FQDN); !certIsValid { + status = deploymentModels.TLSStatusInvalid + statusMessages = append(statusMessages, messages...) + } + } + + externalDNSList = append(externalDNSList, + deploymentModels.ExternalDNS{ + FQDN: externalAlias.FQDN, + TLS: deploymentModels.TLS{ + UseAutomation: externalAlias.UseCertificateAutomation, + Status: status, + StatusMessages: statusMessages, + Certificates: x509Certs, + }, + }, + ) + } + + return externalDNSList +} + func getComponentStatus(component radixv1.RadixCommonDeployComponent, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, pods []corev1.Pod) deploymentModels.ComponentStatus { environmentConfig := utils.GetComponentEnvironmentConfig(ra, rd.Spec.Environment, component.GetName()) if component.GetType() == radixv1.RadixComponentTypeComponent { diff --git a/api/models/deployment.go b/api/models/deployment.go index d8f2a7b4..26bd5060 100644 --- a/api/models/deployment.go +++ b/api/models/deployment.go @@ -2,6 +2,7 @@ package models import ( deploymentModels "github.com/equinor/radix-api/api/deployments/models" + "github.com/equinor/radix-api/api/utils/tlsvalidation" "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" appsv1 "k8s.io/api/apps/v1" @@ -10,8 +11,8 @@ import ( ) // BuildDeployment builds a Deployment model. -func BuildDeployment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler) *deploymentModels.Deployment { - components := BuildComponents(ra, rd, deploymentList, podList, hpaList) +func BuildDeployment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, tlsValidator tlsvalidation.Validator) *deploymentModels.Deployment { + components := BuildComponents(ra, rd, deploymentList, podList, hpaList, secretList, tlsValidator) // The only error that can be returned from DeploymentBuilder is related to errors from github.com/imdario/mergo // This type of error will only happen if incorrect objects (e.g. incompatible structs) are sent as arguments to mergo, diff --git a/api/models/environment.go b/api/models/environment.go index 5a03f931..d141fd56 100644 --- a/api/models/environment.go +++ b/api/models/environment.go @@ -4,7 +4,7 @@ import ( deploymentModels "github.com/equinor/radix-api/api/deployments/models" environmentModels "github.com/equinor/radix-api/api/environments/models" secretModels "github.com/equinor/radix-api/api/secrets/models" - "github.com/equinor/radix-api/api/utils/tlsvalidator" + "github.com/equinor/radix-api/api/utils/tlsvalidation" "github.com/equinor/radix-common/utils/slice" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" appsv1 "k8s.io/api/apps/v1" @@ -14,7 +14,7 @@ import ( ) // BuildEnvironment builds and Environment model. -func BuildEnvironment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplication, re *radixv1.RadixEnvironment, rdList []radixv1.RadixDeployment, rjList []radixv1.RadixJob, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, secretProviderClassList []secretsstorev1.SecretProviderClass, tlsValidator tlsvalidator.TLSSecretValidator) *environmentModels.Environment { +func BuildEnvironment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplication, re *radixv1.RadixEnvironment, rdList []radixv1.RadixDeployment, rjList []radixv1.RadixJob, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, secretProviderClassList []secretsstorev1.SecretProviderClass, tlsValidator tlsvalidation.Validator) *environmentModels.Environment { var buildFromBranch string var activeDeployment *deploymentModels.Deployment var secrets []secretModels.Secret @@ -24,8 +24,8 @@ func BuildEnvironment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplicatio } if activeRd, ok := slice.FindFirst(rdList, isActiveDeploymentForAppAndEnv(ra.Name, re.Spec.EnvName)); ok { - activeDeployment = BuildDeployment(rr, ra, &activeRd, deploymentList, podList, hpaList) - secrets = BuildSecrets(secretList, secretProviderClassList, &activeRd, tlsValidator) + activeDeployment = BuildDeployment(rr, ra, &activeRd, deploymentList, podList, hpaList, secretList, tlsValidator) + secrets = BuildSecrets(secretList, secretProviderClassList, &activeRd) } return &environmentModels.Environment{ diff --git a/api/models/secret.go b/api/models/secret.go index 929a4870..8cc732b6 100644 --- a/api/models/secret.go +++ b/api/models/secret.go @@ -7,7 +7,6 @@ import ( "github.com/equinor/radix-api/api/secrets/suffix" "github.com/equinor/radix-api/api/utils/predicate" "github.com/equinor/radix-api/api/utils/secret" - "github.com/equinor/radix-api/api/utils/tlsvalidator" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/defaults" operatordeployment "github.com/equinor/radix-operator/pkg/apis/deployment" @@ -22,10 +21,9 @@ import ( const secretDefaultData = "xx" // BuildSecrets builds a list of Secret models. -func BuildSecrets(secretList []corev1.Secret, secretProviderClassList []secretsstorev1.SecretProviderClass, rd *radixv1.RadixDeployment, tlsValidator tlsvalidator.TLSSecretValidator) []secretModels.Secret { +func BuildSecrets(secretList []corev1.Secret, secretProviderClassList []secretsstorev1.SecretProviderClass, rd *radixv1.RadixDeployment) []secretModels.Secret { var secrets []secretModels.Secret secrets = append(secrets, getSecretsForDeployment(secretList, rd)...) - secrets = append(secrets, getSecretsForTLSCertificates(secretList, rd, tlsValidator)...) secrets = append(secrets, getSecretsForVolumeMounts(secretList, rd)...) secrets = append(secrets, getSecretsForAuthentication(secretList, rd)...) secrets = append(secrets, getSecretsForSecretRefs(secretList, secretProviderClassList, rd)...) @@ -100,83 +98,6 @@ func getSecretsForDeployment(secretList []corev1.Secret, rd *radixv1.RadixDeploy return secretDTOsMap } -func getSecretsForTLSCertificates(secretList []corev1.Secret, rd *radixv1.RadixDeployment, tlsValidator tlsvalidator.TLSSecretValidator) []secretModels.Secret { - if tlsValidator == nil { - tlsValidator = tlsvalidator.DefaultValidator() - } - - var secrets []secretModels.Secret - for _, component := range rd.Spec.Components { - for _, externalAlias := range component.DNSExternalAlias { - var certData, keyData []byte - certStatus := secretModels.Consistent - keyStatus := secretModels.Consistent - - if secretValue, ok := slice.FindFirst(secretList, isSecretWithName(externalAlias)); ok { - certData = secretValue.Data[corev1.TLSCertKey] - if certValue := strings.TrimSpace(string(certData)); len(certValue) == 0 || strings.EqualFold(certValue, secretDefaultData) { - certStatus = secretModels.Pending - certData = nil - } - - keyData = secretValue.Data[corev1.TLSPrivateKeyKey] - if keyValue := strings.TrimSpace(string(keyData)); len(keyValue) == 0 || strings.EqualFold(keyValue, secretDefaultData) { - keyStatus = secretModels.Pending - keyData = nil - } - } else { - certStatus = secretModels.Pending - keyStatus = secretModels.Pending - } - - var tlsCerts []secretModels.TLSCertificate - var certStatusMessages []string - if certStatus == secretModels.Consistent { - tlsCerts = append(tlsCerts, secretModels.ParseTLSCertificatesFromPEM(certData)...) - - if certIsValid, messages := tlsValidator.ValidateTLSCertificate(certData, keyData, externalAlias); !certIsValid { - certStatus = secretModels.Invalid - certStatusMessages = append(certStatusMessages, messages...) - } - } - - var keyStatusMessages []string - if keyStatus == secretModels.Consistent { - if keyIsValid, messages := tlsValidator.ValidateTLSKey(keyData); !keyIsValid { - keyStatus = secretModels.Invalid - keyStatusMessages = append(keyStatusMessages, messages...) - } - } - - secrets = append(secrets, - secretModels.Secret{ - Name: externalAlias + suffix.ExternalDNSTLSCert, - DisplayName: "Certificate", - Type: secretModels.SecretTypeClientCert, - Resource: externalAlias, - ID: secretModels.SecretIdCert, - Component: component.GetName(), - Status: certStatus.String(), - StatusMessages: certStatusMessages, - TLSCertificates: tlsCerts, - }, - secretModels.Secret{ - Name: externalAlias + suffix.ExternalDNSTLSKey, - DisplayName: "Key", - Type: secretModels.SecretTypeClientCert, - Resource: externalAlias, - Component: component.GetName(), - ID: secretModels.SecretIdKey, - Status: keyStatus.String(), - StatusMessages: keyStatusMessages, - }, - ) - } - } - - return secrets -} - func getSecretsForVolumeMounts(secretList []corev1.Secret, rd *radixv1.RadixDeployment) []secretModels.Secret { var secrets []secretModels.Secret for _, component := range rd.Spec.Components { diff --git a/api/secrets/models/secret.go b/api/secrets/models/secret.go index b3d7aba3..bd3e4ac2 100644 --- a/api/secrets/models/secret.go +++ b/api/secrets/models/secret.go @@ -47,20 +47,9 @@ type Secret struct { // - NotAvailable = Secret is available in external secret configuration but not in cluster // // required: false - // enum: Pending,Consistent,NotAvailable,Invalid + // enum: Pending,Consistent,NotAvailable // example: Consistent Status string `json:"status,omitempty"` - - // StatusMessages contains a list of messages related to the Status - // - // required: false - StatusMessages []string `json:"statusMessages,omitempty"` - - // TLSCertificates holds the TLS certificate and certificate authorities (CA) - // The first certificate in the list should be the TLS certificate and the rest should be CA certificates - // - // required: false - TLSCertificates []TLSCertificate `json:"tlsCertificates,omitempty"` } // swagger:enum SecretType @@ -68,7 +57,6 @@ type SecretType string const ( SecretTypeGeneric SecretType = "generic" - SecretTypeClientCert SecretType = "client-cert" SecretTypeAzureBlobFuseVolume SecretType = "azure-blob-fuse-volume" SecretTypeCsiAzureBlobVolume SecretType = "csi-azure-blob-volume" SecretTypeCsiAzureKeyVaultCreds SecretType = "csi-azure-key-vault-creds" @@ -78,8 +66,6 @@ const ( ) const ( - SecretIdKey string = "key" - SecretIdCert string = "cert" SecretIdClientId string = "clientId" SecretIdClientSecret string = "clientSecret" SecretIdAccountName string = "accountName" diff --git a/api/secrets/models/secret_status.go b/api/secrets/models/secret_status.go index f59dea75..1c2bdad7 100644 --- a/api/secrets/models/secret_status.go +++ b/api/secrets/models/secret_status.go @@ -13,9 +13,6 @@ const ( // NotAvailable In external secret configuration but in cluster NotAvailable - // Invalid when secret value is set, but the format of the value is invalid - Invalid - numStatuses ) @@ -23,5 +20,5 @@ func (p SecretStatus) String() string { if p >= numStatuses { return "Unsupported" } - return [...]string{"Pending", "Consistent", "NotAvailable", "Invalid"}[p] + return [...]string{"Pending", "Consistent", "NotAvailable"}[p] } diff --git a/api/secrets/models/tls_certificate.go b/api/secrets/models/tls_certificate.go deleted file mode 100644 index 3a23fed7..00000000 --- a/api/secrets/models/tls_certificate.go +++ /dev/null @@ -1,68 +0,0 @@ -package models - -import ( - "crypto/x509" - "encoding/pem" - "time" -) - -// TLSCertificate holds information about a TLS certificate -// swagger:model TLSCertificate -type TLSCertificate struct { - // Subject contains the distinguished name for the certificate - // - // required: true - // example: CN=mysite.example.com,O=MyOrg,L=MyLocation,C=NO - Subject string `json:"subject"` - // Issuer contains the distinguished name for the certificate's issuer - // - // required: true - // example: CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US - Issuer string `json:"issuer"` - // NotBefore defines the lower date/time validity boundary - // - // required: true - // swagger:strfmt date-time - // example: 2022-08-09T00:00:00Z - NotBefore time.Time `json:"notBefore"` - // NotAfter defines the uppdater date/time validity boundary - // - // required: true - // swagger:strfmt date-time - // example: 2023-08-25T23:59:59Z - NotAfter time.Time `json:"notAfter"` - // DNSNames defines list of Subject Alternate Names in the certificate - // - // required: false - DNSNames []string `json:"dnsNames,omitempty"` -} - -// ParseTLSCertificatesFromPEM builds an array TLSCertificate from PEM encoded data -func ParseTLSCertificatesFromPEM(certBytes []byte) []TLSCertificate { - var certs []TLSCertificate - for len(certBytes) > 0 { - var block *pem.Block - block, certBytes = pem.Decode(certBytes) - if block == nil { - break - } - if block.Type != "CERTIFICATE" { - continue - } - - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - continue - } - - certs = append(certs, TLSCertificate{ - Subject: cert.Subject.String(), - Issuer: cert.Issuer.String(), - DNSNames: cert.DNSNames, - NotBefore: cert.NotBefore, - NotAfter: cert.NotAfter, - }) - } - - return certs -} diff --git a/api/secrets/models/update_externaldns_tls.go b/api/secrets/models/update_externaldns_tls.go new file mode 100644 index 00000000..52fdac45 --- /dev/null +++ b/api/secrets/models/update_externaldns_tls.go @@ -0,0 +1,20 @@ +package models + +// UpdateExternalDNSTLSRequest describes request body for setting private key and certificate for external DNS TLS +// swagger:model UpdateExternalDnsTlsRequest +type UpdateExternalDNSTLSRequest struct { + // Private key in PEM format + // + // required: true + PrivateKey string `json:"privateKey"` + + // X509 certificate in PEM format + // + // required: true + Certificate string `json:"certificate"` + + // Skip validation of certificate and private key + // + // required: false + SkipValidation bool `json:"skipValidation"` +} diff --git a/api/secrets/secret_controller.go b/api/secrets/secret_controller.go index d5f94c2c..f1bea4ec 100644 --- a/api/secrets/secret_controller.go +++ b/api/secrets/secret_controller.go @@ -5,6 +5,7 @@ import ( "net/http" secretModels "github.com/equinor/radix-api/api/secrets/models" + "github.com/equinor/radix-api/api/utils/tlsvalidation" "github.com/equinor/radix-api/models" "github.com/gorilla/mux" ) @@ -13,38 +14,40 @@ const rootPath = "/applications/{appName}" type secretController struct { *models.DefaultController + tlsValidator tlsvalidation.Validator } // NewSecretController Constructor -func NewSecretController() models.Controller { - return &secretController{} +func NewSecretController(tlsValidator tlsvalidation.Validator) models.Controller { + return &secretController{ + tlsValidator: tlsValidator, + } } // GetRoutes List the supported routes of this handler -func (ec *secretController) GetRoutes() models.Routes { +func (c *secretController) GetRoutes() models.Routes { routes := models.Routes{ models.Route{ Path: rootPath + "/environments/{envName}/components/{componentName}/secrets/{secretName}", Method: "PUT", - HandlerFunc: ec.ChangeComponentSecret, + HandlerFunc: c.ChangeComponentSecret, }, models.Route{ Path: rootPath + "/environments/{envName}/components/{componentName}/secrets/azure/keyvault/{azureKeyVaultName}", Method: "GET", - HandlerFunc: ec.GetAzureKeyVaultSecretVersions, + HandlerFunc: c.GetAzureKeyVaultSecretVersions, + }, + models.Route{ + Path: rootPath + "/environments/{envName}/components/{componentName}/externaldns/{fqdn}/tls", + Method: "PUT", + HandlerFunc: c.UpdateComponentExternalDNSTLS, }, - // TODO reimplement change-secrets individually for each secret type - // models.Route{ - // Path: rootPath + "/environments/{envName}/components/{componentName}/secrets/azure/keyvault/clientid/{azureKeyVaultName}", - // Method: "PUT", - // HandlerFunc: ChangeSecretAzureKeyVaultClientId, - // }, } return routes } // ChangeComponentSecret Modifies an application environment component secret -func (ec *secretController) ChangeComponentSecret(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { +func (c *secretController) ChangeComponentSecret(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { // swagger:operation PUT /applications/{appName}/environments/{envName}/components/{componentName}/secrets/{secretName} environment changeComponentSecret // --- // summary: Update an application environment component secret @@ -108,22 +111,22 @@ func (ec *secretController) ChangeComponentSecret(accounts models.Accounts, w ht var secretParameters secretModels.SecretParameters if err := json.NewDecoder(r.Body).Decode(&secretParameters); err != nil { - ec.ErrorResponse(w, r, err) + c.ErrorResponse(w, r, err) return } - handler := Init(WithAccounts(accounts)) + handler := c.getSecretHandler(accounts) if err := handler.ChangeComponentSecret(r.Context(), appName, envName, componentName, secretName, secretParameters); err != nil { - ec.ErrorResponse(w, r, err) + c.ErrorResponse(w, r, err) return } - ec.JSONResponse(w, r, "Success") + c.JSONResponse(w, r, "Success") } // GetAzureKeyVaultSecretVersions Get Azure Key vault secret versions for a component -func (ec *secretController) GetAzureKeyVaultSecretVersions(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { +func (c *secretController) GetAzureKeyVaultSecretVersions(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { // swagger:operation GET /applications/{appName}/environments/{envName}/components/{componentName}/secrets/azure/keyvault/{azureKeyVaultName} environment getAzureKeyVaultSecretVersions // --- // summary: Get Azure Key vault secret versions for a component @@ -189,13 +192,96 @@ func (ec *secretController) GetAzureKeyVaultSecretVersions(accounts models.Accou azureKeyVaultName := mux.Vars(r)["azureKeyVaultName"] secretName := r.FormValue("secretName") - handler := Init(WithAccounts(accounts)) + handler := c.getSecretHandler(accounts) secretStatuses, err := handler.GetAzureKeyVaultSecretVersions(appName, envName, componentName, azureKeyVaultName, secretName) if err != nil { - ec.ErrorResponse(w, r, err) + c.ErrorResponse(w, r, err) + return + } + + c.JSONResponse(w, r, secretStatuses) +} + +// UpdateComponentExternalDNSTLS Set external DNS TLS private key and certificate for a component +func (c *secretController) UpdateComponentExternalDNSTLS(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { + // swagger:operation PUT /applications/{appName}/environments/{envName}/components/{componentName}/externaldns/{fqdn}/tls component updateComponentExternalDnsTls + // --- + // summary: Set external DNS TLS private key certificate for a component + // parameters: + // - name: appName + // in: path + // description: Name of application + // type: string + // required: true + // - name: envName + // in: path + // description: secret of Radix application + // type: string + // required: true + // - name: componentName + // in: path + // description: secret component of Radix application + // type: string + // required: true + // - name: fqdn + // in: path + // description: FQDN to be updated + // type: string + // required: true + // - name: tlsData + // in: body + // description: New TLS private key and certificate + // required: true + // schema: + // "$ref": "#/definitions/UpdateExternalDnsTlsRequest" + // - name: Impersonate-User + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) + // type: string + // required: false + // - name: Impersonate-Group + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) + // type: string + // required: false + // responses: + // "200": + // description: success + // "400": + // description: "Invalid application" + // "401": + // description: "Unauthorized" + // "403": + // description: "Forbidden" + // "404": + // description: "Not found" + // "409": + // description: "Conflict" + // "500": + // description: "Internal server error" + + appName := mux.Vars(r)["appName"] + envName := mux.Vars(r)["envName"] + componentName := mux.Vars(r)["componentName"] + fqdn := mux.Vars(r)["fqdn"] + + var requestBody secretModels.UpdateExternalDNSTLSRequest + if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil { + c.ErrorResponse(w, r, err) + return + } + + handler := c.getSecretHandler(accounts) + + if err := handler.UpdateComponentExternalDNSSecretData(r.Context(), appName, envName, componentName, fqdn, requestBody.Certificate, requestBody.PrivateKey, requestBody.SkipValidation); err != nil { + c.ErrorResponse(w, r, err) return } - ec.JSONResponse(w, r, secretStatuses) + c.JSONResponse(w, r, "Success") +} + +func (c *secretController) getSecretHandler(accounts models.Accounts) *SecretHandler { + return Init(WithAccounts(accounts), WithTLSValidator(c.tlsValidator)) } diff --git a/api/secrets/secret_controller_test.go b/api/secrets/secret_controller_test.go index 13cd2f9c..6465bc51 100644 --- a/api/secrets/secret_controller_test.go +++ b/api/secrets/secret_controller_test.go @@ -7,17 +7,22 @@ import ( "net/http/httptest" "testing" - _ "github.com/equinor/radix-api/api/events" secretModels "github.com/equinor/radix-api/api/secrets/models" controllertest "github.com/equinor/radix-api/api/test" + "github.com/equinor/radix-api/api/utils/tlsvalidation" + tlsvalidationmock "github.com/equinor/radix-api/api/utils/tlsvalidation/mock" + radixhttp "github.com/equinor/radix-common/net/http" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" commontest "github.com/equinor/radix-operator/pkg/apis/test" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + "github.com/golang/mock/gomock" prometheusclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned" prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -37,7 +42,7 @@ const ( subscriptionId = "12347718-c8f8-4995-bfbb-02655ff1f89c" ) -func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, kubernetes.Interface, radixclient.Interface, prometheusclient.Interface, secretsstorevclient.Interface) { +func setupTest(t *testing.T, tlsValidator tlsvalidation.Validator) (*commontest.Utils, *controllertest.Utils, kubernetes.Interface, radixclient.Interface, prometheusclient.Interface, secretsstorevclient.Interface) { // Setup kubeclient := kubefake.NewSimpleClientset() radixclient := fake.NewSimpleClientset() @@ -50,7 +55,7 @@ func setupTest(t *testing.T) (*commontest.Utils, *controllertest.Utils, kubernet require.NoError(t, err) // secretControllerTestUtils is used for issuing HTTP request and processing responses - secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, secretproviderclient, NewSecretController()) + secretControllerTestUtils := controllertest.NewTestUtils(kubeclient, radixclient, secretproviderclient, NewSecretController(tlsValidator)) return &commonTestUtils, &secretControllerTestUtils, kubeclient, radixclient, prometheusclient, secretproviderclient } @@ -92,7 +97,7 @@ func executeUpdateSecretTest(t *testing.T, oldSecretValue, updateSecret, updateC SecretValue: updateSecretValue, } - commonTestUtils, controllerTestUtils, kubeclient, _, _, _ := setupTest(t) + commonTestUtils, controllerTestUtils, kubeclient, _, _, _ := setupTest(t, nil) appBuilder := operatorutils. ARadixApplication(). WithAppName(anyAppName) @@ -243,3 +248,175 @@ func TestUpdateSecret_NonExistingEnvironment_Missing(t *testing.T) { assert.Equal(t, http.StatusNotFound, response.Code) assert.Equal(t, fmt.Sprintf("secrets \"%s\" not found", secretObjName), errorResponse.Err.Error()) } + +type externalDNSSecretTestSuite struct { + suite.Suite + controllerTestUtils *controllertest.Utils + commonTestUtils *commontest.Utils + tlsValidator *tlsvalidationmock.MockValidator + kubeClient kubernetes.Interface + radixClient radixclient.Interface +} + +func Test_ExternalDNSSecretTestSuite(t *testing.T) { + suite.Run(t, new(externalDNSSecretTestSuite)) +} + +func (s *externalDNSSecretTestSuite) SetupTest() { + ctrl := gomock.NewController(s.T()) + s.tlsValidator = tlsvalidationmock.NewMockValidator(ctrl) + s.commonTestUtils, s.controllerTestUtils, s.kubeClient, s.radixClient, _, _ = setupTest(s.T(), s.tlsValidator) +} + +func (s *externalDNSSecretTestSuite) setupTestResources(appName, envName, componentName string, externalAliases []radixv1.RadixDeployExternalDNS, rdCondition radixv1.RadixDeployCondition) error { + _, err := s.commonTestUtils.ApplyDeployment( + operatorutils.NewDeploymentBuilder(). + WithRadixApplication( + operatorutils.NewRadixApplicationBuilder(). + WithAppName(appName). + WithRadixRegistration( + operatorutils.NewRegistrationBuilder(). + WithName(appName), + ), + ). + WithCondition(rdCondition). + WithAppName(appName). + WithEnvironment(envName). + WithComponents( + operatorutils.NewDeployComponentBuilder(). + WithName(componentName). + WithExternalDNS(externalAliases...), + ), + ) + + return err +} + +func (s *externalDNSSecretTestSuite) setupSecretForExternalDNS(namespace, fqdn string, cert []byte, privateKey []byte) error { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: fqdn}, + Type: corev1.SecretTypeTLS, + Data: map[string][]byte{corev1.TLSCertKey: cert, corev1.TLSPrivateKeyKey: privateKey}, + } + _, err := s.kubeClient.CoreV1().Secrets(namespace).Create(context.Background(), secret, metav1.CreateOptions{}) + return err +} + +func (s *externalDNSSecretTestSuite) executeRequest(appName, envName, componentName, fqdn string, body *secretModels.UpdateExternalDNSTLSRequest) *httptest.ResponseRecorder { + endpoint := fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/externaldns/%s/tls", appName, envName, componentName, fqdn) + responseCh := s.controllerTestUtils.ExecuteRequestWithParameters(http.MethodPut, endpoint, body) + return <-responseCh +} + +func (s *externalDNSSecretTestSuite) Test_UpdateSuccess() { + appName, envName, componentName, fqdn := "app", "env", "comp", "my.example.com" + privateKey, cert := "any private key", "any certificate" + ns := operatorutils.GetEnvironmentNamespace(appName, envName) + s.Require().NoError(s.setupTestResources(appName, envName, componentName, []radixv1.RadixDeployExternalDNS{{FQDN: fqdn}}, radixv1.DeploymentActive)) + s.Require().NoError(s.setupSecretForExternalDNS(ns, fqdn, nil, nil)) + s.tlsValidator.EXPECT().ValidateX509Certificate([]byte(cert), []byte(privateKey), fqdn).Return(true, nil).Times(1) + + response := s.executeRequest(appName, envName, componentName, fqdn, &secretModels.UpdateExternalDNSTLSRequest{PrivateKey: privateKey, Certificate: cert}) + s.Equal(200, response.Code) + expectedSecretData := map[string][]byte{ + corev1.TLSCertKey: []byte(cert), + corev1.TLSPrivateKeyKey: []byte(privateKey), + } + secret, err := s.kubeClient.CoreV1().Secrets(ns).Get(context.Background(), fqdn, metav1.GetOptions{}) + s.Require().NoError(err) + s.Equal(expectedSecretData, secret.Data) +} + +func (s *externalDNSSecretTestSuite) Test_SkipValidationDoesNotCallValidator() { + appName, envName, componentName, fqdn := "app", "env", "comp", "my.example.com" + privateKey, cert := "any private key", "any certificate" + ns := operatorutils.GetEnvironmentNamespace(appName, envName) + s.Require().NoError(s.setupTestResources(appName, envName, componentName, []radixv1.RadixDeployExternalDNS{{FQDN: fqdn}}, radixv1.DeploymentActive)) + s.Require().NoError(s.setupSecretForExternalDNS(ns, fqdn, nil, nil)) + + response := s.executeRequest(appName, envName, componentName, fqdn, &secretModels.UpdateExternalDNSTLSRequest{PrivateKey: privateKey, Certificate: cert, SkipValidation: true}) + s.Equal(200, response.Code) + expectedSecretData := map[string][]byte{ + corev1.TLSCertKey: []byte(cert), + corev1.TLSPrivateKeyKey: []byte(privateKey), + } + secret, err := s.kubeClient.CoreV1().Secrets(ns).Get(context.Background(), fqdn, metav1.GetOptions{}) + s.Require().NoError(err) + s.Equal(expectedSecretData, secret.Data) +} + +func (s *externalDNSSecretTestSuite) Test_RadixDeploymentNotActive() { + appName, envName, componentName, fqdn := "app", "env", "comp", "my.example.com" + privateKey, cert := "any private key", "any certificate" + s.Require().NoError(s.setupTestResources(appName, envName, componentName, []radixv1.RadixDeployExternalDNS{{FQDN: fqdn}}, radixv1.DeploymentInactive)) + + response := s.executeRequest(appName, envName, componentName, fqdn, &secretModels.UpdateExternalDNSTLSRequest{PrivateKey: privateKey, Certificate: cert}) + s.Equal(404, response.Code) + var status radixhttp.Error + s.Require().NoError(controllertest.GetResponseBody(response, &status)) + s.Equal(fmt.Sprintf("No active deployment found for application %q in environment %q", appName, envName), status.Message) +} + +func (s *externalDNSSecretTestSuite) Test_NonExistingComponent() { + appName, envName, componentName, fqdn := "app", "env", "comp", "my.example.com" + privateKey, cert := "any private key", "any certificate" + s.Require().NoError(s.setupTestResources(appName, envName, "othercomp", []radixv1.RadixDeployExternalDNS{{FQDN: fqdn}}, radixv1.DeploymentActive)) + + response := s.executeRequest(appName, envName, componentName, fqdn, &secretModels.UpdateExternalDNSTLSRequest{PrivateKey: privateKey, Certificate: cert}) + s.Equal(404, response.Code) + var status radixhttp.Error + s.Require().NoError(controllertest.GetResponseBody(response, &status)) + s.Equal(fmt.Sprintf("Component %q does not exist", componentName), status.Message) +} + +func (s *externalDNSSecretTestSuite) Test_NonExistingExternalDNS() { + appName, envName, componentName, fqdn := "app", "env", "comp", "my.example.com" + privateKey, cert := "any private key", "any certificate" + s.Require().NoError(s.setupTestResources(appName, envName, componentName, []radixv1.RadixDeployExternalDNS{{FQDN: "other.example.com"}}, radixv1.DeploymentActive)) + + response := s.executeRequest(appName, envName, componentName, fqdn, &secretModels.UpdateExternalDNSTLSRequest{PrivateKey: privateKey, Certificate: cert}) + s.Equal(404, response.Code) + var status radixhttp.Error + s.Require().NoError(controllertest.GetResponseBody(response, &status)) + s.Equal(fmt.Sprintf("External DNS %q not configured for component", fqdn), status.Message) +} + +func (s *externalDNSSecretTestSuite) Test_ExternalDNSUsesAutomation() { + appName, envName, componentName, fqdn := "app", "env", "comp", "my.example.com" + privateKey, cert := "any private key", "any certificate" + s.Require().NoError(s.setupTestResources(appName, envName, componentName, []radixv1.RadixDeployExternalDNS{{FQDN: fqdn, UseCertificateAutomation: true}}, radixv1.DeploymentActive)) + + response := s.executeRequest(appName, envName, componentName, fqdn, &secretModels.UpdateExternalDNSTLSRequest{PrivateKey: privateKey, Certificate: cert}) + s.Equal(400, response.Code) + var status radixhttp.Error + s.Require().NoError(controllertest.GetResponseBody(response, &status)) + s.Equal(fmt.Sprintf("External DNS %q is configured to use certificate automation", fqdn), status.Message) +} + +func (s *externalDNSSecretTestSuite) Test_CertificateValidationError() { + appName, envName, componentName, fqdn := "app", "env", "comp", "my.example.com" + privateKey, cert := "any private key", "any certificate" + validationMsg1, validationMsg2 := "validation error 1", "validation error 2" + s.Require().NoError(s.setupTestResources(appName, envName, componentName, []radixv1.RadixDeployExternalDNS{{FQDN: fqdn}}, radixv1.DeploymentActive)) + s.tlsValidator.EXPECT().ValidateX509Certificate([]byte(cert), []byte(privateKey), fqdn).Return(false, []string{validationMsg1, validationMsg2}).Times(1) + + response := s.executeRequest(appName, envName, componentName, fqdn, &secretModels.UpdateExternalDNSTLSRequest{PrivateKey: privateKey, Certificate: cert}) + s.Equal(400, response.Code) + var status radixhttp.Error + s.Require().NoError(controllertest.GetResponseBody(response, &status)) + s.Equal(fmt.Sprintf("%s, %s", validationMsg1, validationMsg2), status.Message) + s.ErrorContains(status.Err, "TLS failed validation") +} + +func (s *externalDNSSecretTestSuite) Test_NonExistingSecretFails() { + appName, envName, componentName, fqdn := "app", "env", "comp", "my.example.com" + privateKey, cert := "any private key", "any certificate" + s.Require().NoError(s.setupTestResources(appName, envName, componentName, []radixv1.RadixDeployExternalDNS{{FQDN: fqdn}}, radixv1.DeploymentActive)) + s.tlsValidator.EXPECT().ValidateX509Certificate([]byte(cert), []byte(privateKey), fqdn).Return(true, nil).Times(1) + + response := s.executeRequest(appName, envName, componentName, fqdn, &secretModels.UpdateExternalDNSTLSRequest{PrivateKey: privateKey, Certificate: cert}) + s.Equal(500, response.Code) + var status radixhttp.Error + s.Require().NoError(controllertest.GetResponseBody(response, &status)) + s.Equal(fmt.Sprintf("Failed to update TLS private key and certificate for %q", fqdn), status.Message) +} diff --git a/api/secrets/secret_handler.go b/api/secrets/secret_handler.go index b9261e98..e7b05b78 100644 --- a/api/secrets/secret_handler.go +++ b/api/secrets/secret_handler.go @@ -2,16 +2,21 @@ package secrets import ( "context" + "fmt" "strings" "github.com/equinor/radix-api/api/deployments" + "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/secrets/models" "github.com/equinor/radix-api/api/secrets/suffix" "github.com/equinor/radix-api/api/utils/labelselector" + "github.com/equinor/radix-api/api/utils/predicate" sortUtils "github.com/equinor/radix-api/api/utils/sort" + "github.com/equinor/radix-api/api/utils/tlsvalidation" apiModels "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" - radixutils "github.com/equinor/radix-common/utils" + commonutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" @@ -44,48 +49,43 @@ func WithAccounts(accounts apiModels.Accounts) SecretHandlerOptions { } } +// WithAccounts configures all SecretHandler fields +func WithTLSValidator(tlsValidator tlsvalidation.Validator) SecretHandlerOptions { + return func(eh *SecretHandler) { + eh.tlsValidator = tlsValidator + } +} + // SecretHandler Instance variables type SecretHandler struct { userAccount apiModels.Account serviceAccount apiModels.Account deployHandler deployments.DeployHandler + tlsValidator tlsvalidation.Validator } // Init Constructor. // Use the WithAccounts configuration function to configure a 'ready to use' SecretHandler. // SecretHandlerOptions are processed in the sequence they are passed to this function. -func Init(opts ...SecretHandlerOptions) SecretHandler { - eh := SecretHandler{} +func Init(opts ...SecretHandlerOptions) *SecretHandler { + eh := &SecretHandler{} for _, opt := range opts { - opt(&eh) + opt(eh) } return eh } // ChangeComponentSecret handler for HandleChangeComponentSecret -func (eh SecretHandler) ChangeComponentSecret(ctx context.Context, appName, envName, componentName, secretName string, componentSecret models.SecretParameters) error { +func (eh *SecretHandler) ChangeComponentSecret(ctx context.Context, appName, envName, componentName, secretName string, componentSecret models.SecretParameters) error { newSecretValue := componentSecret.SecretValue if strings.TrimSpace(newSecretValue) == "" { return radixhttp.ValidationError("Secret", "New secret value is empty") } - ns := operatorutils.GetEnvironmentNamespace(appName, envName) - var secretObjName, partName string - - if strings.HasSuffix(secretName, suffix.ExternalDNSTLSCert) { - // This is the cert part of the TLS secret - secretObjName = strings.TrimSuffix(secretName, suffix.ExternalDNSTLSCert) - partName = corev1.TLSCertKey - - } else if strings.HasSuffix(secretName, suffix.ExternalDNSTLSKey) { - // This is the key part of the TLS secret - secretObjName = strings.TrimSuffix(secretName, suffix.ExternalDNSTLSKey) - partName = corev1.TLSPrivateKeyKey - - } else if strings.HasSuffix(secretName, defaults.BlobFuseCredsAccountKeyPartSuffix) { + if strings.HasSuffix(secretName, defaults.BlobFuseCredsAccountKeyPartSuffix) { // This is the account key part of the blobfuse cred secret secretObjName = strings.TrimSuffix(secretName, defaults.BlobFuseCredsAccountKeyPartSuffix) partName = defaults.BlobFuseCredsAccountKeyPart @@ -133,29 +133,81 @@ func (eh SecretHandler) ChangeComponentSecret(ctx context.Context, appName, envN // This is a regular secret secretObjName = operatorutils.GetComponentSecretName(componentName) partName = secretName - } - secretObject, err := eh.userAccount.Client.CoreV1().Secrets(ns).Get(ctx, secretObjName, metav1.GetOptions{}) + ns := operatorutils.GetEnvironmentNamespace(appName, envName) + return eh.setSecretKeyValue(ctx, ns, secretObjName, map[string][]byte{partName: []byte(newSecretValue)}) +} + +func (eh *SecretHandler) UpdateComponentExternalDNSSecretData(ctx context.Context, appName, envName, componentName, fqdn string, certificate, privateKey string, skipValidation bool) error { + rdList, err := kubequery.GetRadixDeploymentsForEnvironment(ctx, eh.userAccount.RadixClient, appName, envName) if err != nil { - return err + return radixhttp.UnexpectedError("Failed to get deployments", err) } - if secretObject.Data == nil { - secretObject.Data = make(map[string][]byte) + activeRd, found := slice.FindFirst(rdList, func(rd radixv1.RadixDeployment) bool { return predicate.IsActiveRadixDeployment(rd) }) + if !found { + return radixhttp.NotFoundError(fmt.Sprintf("No active deployment found for application %q in environment %q", appName, envName)) } - secretObject.Data[partName] = []byte(newSecretValue) + component := activeRd.GetComponentByName(componentName) + if component == nil { + return radixhttp.NotFoundError(fmt.Sprintf("Component %q does not exist", componentName)) + } + + externalDNS, found := slice.FindFirst(component.GetExternalDNS(), func(rded radixv1.RadixDeployExternalDNS) bool { return rded.FQDN == fqdn }) + if !found { + return radixhttp.NotFoundError(fmt.Sprintf("External DNS %q not configured for component", fqdn)) + } + + if externalDNS.UseCertificateAutomation { + return &radixhttp.Error{Type: radixhttp.User, Message: fmt.Sprintf("External DNS %q is configured to use certificate automation", fqdn)} + } - _, err = eh.userAccount.Client.CoreV1().Secrets(ns).Update(ctx, secretObject, metav1.UpdateOptions{}) + certificateBytes, privateKeyBytes := []byte(certificate), []byte(privateKey) + + if !skipValidation { + tlsValidator := eh.getTLSValidatorOrDefault() + + if valid, validationMsgs := tlsValidator.ValidateX509Certificate(certificateBytes, privateKeyBytes, fqdn); !valid { + return radixhttp.ValidationError("TLS", strings.Join(validationMsgs, ", ")) + } + } + + ns := operatorutils.GetEnvironmentNamespace(appName, envName) + if err := eh.setSecretKeyValue(ctx, ns, fqdn, map[string][]byte{corev1.TLSCertKey: certificateBytes, corev1.TLSPrivateKeyKey: privateKeyBytes}); err != nil { + return radixhttp.UnexpectedError(fmt.Sprintf("Failed to update TLS private key and certificate for %q", fqdn), err) + } + + return nil +} + +func (eh *SecretHandler) getTLSValidatorOrDefault() tlsvalidation.Validator { + if commonutils.IsNil(eh.tlsValidator) { + return tlsvalidation.DefaultValidator() + } + return eh.tlsValidator +} + +func (eh *SecretHandler) setSecretKeyValue(ctx context.Context, namespace, secretName string, keyValue map[string][]byte) error { + secret, err := eh.userAccount.Client.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) if err != nil { return err } - return nil + if secret.Data == nil { + secret.Data = make(map[string][]byte) + } + + for k, v := range keyValue { + secret.Data[k] = v + } + + _, err = eh.userAccount.Client.CoreV1().Secrets(secret.Namespace).Update(ctx, secret, metav1.UpdateOptions{}) + return err } -func (eh SecretHandler) getAzureKeyVaultSecretVersionsMap(appName, envNamespace, componentName, azureKeyVaultName string) (secretIdToPodNameToSecretVersionMap, error) { +func (eh *SecretHandler) getAzureKeyVaultSecretVersionsMap(appName, envNamespace, componentName, azureKeyVaultName string) (secretIdToPodNameToSecretVersionMap, error) { secretProviderClassMap, err := eh.getAzureKeyVaultSecretProviderClassMapForAppComponentStorage(appName, envNamespace, componentName, azureKeyVaultName) if err != nil { return nil, err @@ -177,10 +229,10 @@ func (eh SecretHandler) getAzureKeyVaultSecretVersionsMap(appName, envNamespace, secretStatusMap[secretVersion.ID][secretsInPod.Status.PodName] = secretVersion.Version } } - return secretStatusMap, nil // map[secretType/secretName][podName]secretVersion + return secretStatusMap, nil } -func (eh SecretHandler) getAzureKeyVaultSecretProviderClassMapForAppComponentStorage(appName, envNamespace, componentName, azureKeyVaultName string) (map[string]secretsstorev1.SecretProviderClass, error) { +func (eh *SecretHandler) getAzureKeyVaultSecretProviderClassMapForAppComponentStorage(appName, envNamespace, componentName, azureKeyVaultName string) (map[string]secretsstorev1.SecretProviderClass, error) { labelSelector := getAzureKeyVaultSecretRefSecretProviderClassLabels(appName, componentName, azureKeyVaultName).String() return eh.getSecretProviderClassMapForLabelSelector(envNamespace, labelSelector) } @@ -194,7 +246,7 @@ func getAzureKeyVaultSecretRefSecretProviderClassLabels(appName string, componen } } -func (eh SecretHandler) getSecretProviderClassMapForLabelSelector(envNamespace, labelSelector string) (map[string]secretsstorev1.SecretProviderClass, error) { +func (eh *SecretHandler) getSecretProviderClassMapForLabelSelector(envNamespace, labelSelector string) (map[string]secretsstorev1.SecretProviderClass, error) { secretProviderClassList, err := eh.serviceAccount.SecretProviderClient.SecretsstoreV1().SecretProviderClasses(envNamespace). List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector}) if err != nil { @@ -209,7 +261,7 @@ func (eh SecretHandler) getSecretProviderClassMapForLabelSelector(envNamespace, } // GetAzureKeyVaultSecretVersions Gets list of Azure Key vault secret versions for the storage in the component -func (eh SecretHandler) GetAzureKeyVaultSecretVersions(appName, envName, componentName, azureKeyVaultName, secretId string) ([]models.AzureKeyVaultSecretVersion, error) { +func (eh *SecretHandler) GetAzureKeyVaultSecretVersions(appName, envName, componentName, azureKeyVaultName, secretId string) ([]models.AzureKeyVaultSecretVersion, error) { var envNamespace = operatorutils.GetEnvironmentNamespace(appName, envName) azureKeyVaultSecretMap, err := eh.getAzureKeyVaultSecretVersionsMap(appName, envNamespace, componentName, azureKeyVaultName) if err != nil { @@ -223,7 +275,7 @@ func (eh SecretHandler) GetAzureKeyVaultSecretVersions(appName, envName, compone return eh.getAzKeyVaultSecretVersions(appName, envNamespace, componentName, podList.Items, azureKeyVaultSecretMap[secretId]) } -func (eh SecretHandler) getAzKeyVaultSecretVersions(appName string, envNamespace string, componentName string, pods []corev1.Pod, podSecretVersionMap podNameToSecretVersionMap) ([]models.AzureKeyVaultSecretVersion, error) { +func (eh *SecretHandler) getAzKeyVaultSecretVersions(appName string, envNamespace string, componentName string, pods []corev1.Pod, podSecretVersionMap podNameToSecretVersionMap) ([]models.AzureKeyVaultSecretVersion, error) { jobMap, err := eh.getJobMap(appName, envNamespace, componentName) if err != nil { return nil, err @@ -237,7 +289,7 @@ func (eh SecretHandler) getAzKeyVaultSecretVersions(appName string, envNamespace podCreated := pod.GetCreationTimestamp() azureKeyVaultSecretVersion := models.AzureKeyVaultSecretVersion{ ReplicaName: pod.GetName(), - ReplicaCreated: radixutils.FormatTime(&podCreated), + ReplicaCreated: commonutils.FormatTime(&podCreated), Version: secretVersion, } if _, ok := pod.ObjectMeta.Labels[kube.RadixPodIsJobAuxObjectLabel]; ok { @@ -256,12 +308,12 @@ func (eh SecretHandler) getAzKeyVaultSecretVersions(appName string, envNamespace } azureKeyVaultSecretVersion.JobName = jobName jobCreated := job.GetCreationTimestamp() - azureKeyVaultSecretVersion.JobCreated = radixutils.FormatTime(&jobCreated) + azureKeyVaultSecretVersion.JobCreated = commonutils.FormatTime(&jobCreated) if batchName, ok := pod.ObjectMeta.Labels[kube.RadixBatchNameLabel]; ok { if batch, ok := jobMap[batchName]; ok { azureKeyVaultSecretVersion.BatchName = batchName batchCreated := batch.GetCreationTimestamp() - azureKeyVaultSecretVersion.BatchCreated = radixutils.FormatTime(&batchCreated) + azureKeyVaultSecretVersion.BatchCreated = commonutils.FormatTime(&batchCreated) } } azKeyVaultSecretVersions = append(azKeyVaultSecretVersions, azureKeyVaultSecretVersion) @@ -269,7 +321,7 @@ func (eh SecretHandler) getAzKeyVaultSecretVersions(appName string, envNamespace return azKeyVaultSecretVersions, nil } -func (eh SecretHandler) getJobMap(appName, namespace, componentName string) (map[string]batchv1.Job, error) { +func (eh *SecretHandler) getJobMap(appName, namespace, componentName string) (map[string]batchv1.Job, error) { jobMap := make(map[string]batchv1.Job) jobList, err := eh.userAccount.Client.BatchV1().Jobs(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: labelselector.JobAndBatchJobsForComponent(appName, componentName)}) if err != nil { diff --git a/api/secrets/secret_handler_test.go b/api/secrets/secret_handler_test.go index 38cdd945..710c03e5 100644 --- a/api/secrets/secret_handler_test.go +++ b/api/secrets/secret_handler_test.go @@ -266,138 +266,6 @@ func (s *secretHandlerTestSuite) TestSecretHandler_ChangeSecrets() { }, expectedError: true, }, - { - name: "Change External DNS cert in the component", - components: []v1.RadixDeployComponent{{ - Name: componentName1, - Secrets: []string{"some-external-dns-secret"}, - }}, - secretName: "some-external-dns-secret", - secretDataKey: corev1.TLSCertKey, - secretValue: "current tls certificate text\nline2\nline3", - secretExists: true, - changingSecretComponentName: componentName1, - changingSecretName: "some-external-dns-secret-cert", - changingSecretParams: secretModels.SecretParameters{ - SecretValue: "new tls certificate text\nline2\nline3", - Type: secretModels.SecretTypeClientCert, - }, - expectedError: false, - }, - { - name: "Change External DNS cert in the job", - jobs: []v1.RadixDeployJobComponent{{ - Name: jobName1, - Secrets: []string{"some-external-dns-secret"}, - }}, - secretName: "some-external-dns-secret", - secretDataKey: corev1.TLSCertKey, - secretValue: "current tls certificate text\nline2\nline3", - secretExists: true, - changingSecretComponentName: jobName1, - changingSecretName: "some-external-dns-secret-cert", - changingSecretParams: secretModels.SecretParameters{ - SecretValue: "new tls certificate text\nline2\nline3", - Type: secretModels.SecretTypeClientCert, - }, - expectedError: false, - }, - { - name: "Failed change of not existing External DNS cert in the component", - components: []v1.RadixDeployComponent{{ - Name: componentName1, - Secrets: []string{"some-external-dns-secret"}, - }}, - secretExists: false, - changingSecretComponentName: componentName1, - changingSecretName: "some-external-dns-secret-cert", - changingSecretParams: secretModels.SecretParameters{ - SecretValue: "new tls certificate text\nline2\nline3", - Type: secretModels.SecretTypeClientCert, - }, - expectedError: true, - }, - { - name: "Failed change of not existing External DNS cert in the job", - jobs: []v1.RadixDeployJobComponent{{ - Name: jobName1, - Secrets: []string{"some-external-dns-secret"}, - }}, - secretExists: false, - changingSecretComponentName: jobName1, - changingSecretName: "some-external-dns-secret-cert", - changingSecretParams: secretModels.SecretParameters{ - SecretValue: "new tls certificate text\nline2\nline3", - Type: secretModels.SecretTypeClientCert, - }, - expectedError: true, - }, - { - name: "Change External DNS key in the component", - components: []v1.RadixDeployComponent{{ - Name: componentName1, - Secrets: []string{"some-external-dns-secret"}, - }}, - secretName: "some-external-dns-secret", - secretDataKey: corev1.TLSPrivateKeyKey, - secretValue: "current tls key text\nline2\nline3", - secretExists: true, - changingSecretComponentName: componentName1, - changingSecretName: "some-external-dns-secret-key", - changingSecretParams: secretModels.SecretParameters{ - SecretValue: "new tls key text\nline2\nline3", - Type: secretModels.SecretTypeClientCert, - }, - expectedError: false, - }, - { - name: "Change External DNS key in the job", - jobs: []v1.RadixDeployJobComponent{{ - Name: jobName1, - Secrets: []string{"some-external-dns-secret"}, - }}, - secretName: "some-external-dns-secret", - secretDataKey: corev1.TLSPrivateKeyKey, - secretValue: "current tls key text\nline2\nline3", - secretExists: true, - changingSecretComponentName: jobName1, - changingSecretName: "some-external-dns-secret-key", - changingSecretParams: secretModels.SecretParameters{ - SecretValue: "new tls key text\nline2\nline3", - Type: secretModels.SecretTypeClientCert, - }, - expectedError: false, - }, - { - name: "Failed change of not existing External DNS key in the component", - components: []v1.RadixDeployComponent{{ - Name: componentName1, - Secrets: []string{"some-external-dns-secret"}, - }}, - secretExists: false, - changingSecretComponentName: componentName1, - changingSecretName: "some-external-dns-secret-key", - changingSecretParams: secretModels.SecretParameters{ - SecretValue: "new tls key text\nline2\nline3", - Type: secretModels.SecretTypeClientCert, - }, - expectedError: true, - }, - { - name: "Failed change of not existing External DNS key in the job", - jobs: []v1.RadixDeployJobComponent{{ - Name: jobName1, - Secrets: []string{"some-external-dns-secret"}, - }}, - secretExists: false, - changingSecretComponentName: jobName1, - changingSecretName: "some-external-dns-secret-key", - changingSecretParams: secretModels.SecretParameters{ - SecretValue: "new tls key text\nline2\nline3", - Type: secretModels.SecretTypeClientCert, - }, - expectedError: true, - }, { name: "Change CSI Azure Blob volume account name in the component", components: []v1.RadixDeployComponent{{ @@ -845,7 +713,7 @@ func (s *secretHandlerTestSuite) TestSecretHandler_ChangeSecrets() { changingSecretName: "client-certificate1" + suffix.ClientCertificate, changingSecretParams: secretModels.SecretParameters{ SecretValue: "new client certificate\nline2\nline3", - Type: secretModels.SecretTypeClientCert, + Type: secretModels.SecretTypeClientCertificateAuth, }, expectedError: false, }, @@ -859,7 +727,7 @@ func (s *secretHandlerTestSuite) TestSecretHandler_ChangeSecrets() { changingSecretName: "client-certificate1" + suffix.ClientCertificate, changingSecretParams: secretModels.SecretParameters{ SecretValue: "new client certificate\nline2\nline3", - Type: secretModels.SecretTypeClientCert, + Type: secretModels.SecretTypeClientCertificateAuth, }, expectedError: true, }, diff --git a/api/secrets/suffix/suffix.go b/api/secrets/suffix/suffix.go index e8e54dfa..8ac6ca65 100644 --- a/api/secrets/suffix/suffix.go +++ b/api/secrets/suffix/suffix.go @@ -2,10 +2,6 @@ package suffix const ( - //ExternalDNSTLSCert TLS certificate for external DNS - ExternalDNSTLSCert = "-cert" - //ExternalDNSTLSKey TLS key for external DNS - ExternalDNSTLSKey = "-key" //ClientCertificate Client certificate ClientCertificate = "-clientcertca" //OAuth2ClientSecret Client secret of OAuth2 diff --git a/api/test/utils.go b/api/test/utils.go index e3555560..ef72c9f9 100644 --- a/api/test/utils.go +++ b/api/test/utils.go @@ -51,9 +51,9 @@ func (tu *Utils) ExecuteUnAuthorizedRequest(method, endpoint string) <-chan *htt response := make(chan *httptest.ResponseRecorder) go func() { rr := httptest.NewRecorder() + defer close(response) router.NewServer("anyClusterName", NewKubeUtilMock(tu.client, tu.radixclient, tu.secretproviderclient), tu.controllers...).ServeHTTP(rr, req) response <- rr - close(response) }() return response @@ -75,9 +75,9 @@ func (tu *Utils) ExecuteRequestWithParameters(method, endpoint string, parameter response := make(chan *httptest.ResponseRecorder) go func() { rr := httptest.NewRecorder() + defer close(response) router.NewServer("anyClusterName", NewKubeUtilMock(tu.client, tu.radixclient, tu.secretproviderclient), tu.controllers...).ServeHTTP(rr, req) response <- rr - close(response) }() return response @@ -105,7 +105,6 @@ func GetErrorResponse(response *httptest.ResponseRecorder) (*radixhttp.Error, er func GetResponseBody(response *httptest.ResponseRecorder, target interface{}) error { reader := bytes.NewReader(response.Body.Bytes()) //To allow read from response body multiple times body, _ := io.ReadAll(reader) - log.Infof(string(body)) return json.Unmarshal(body, target) } diff --git a/api/utils/test.go b/api/utils/test.go index 9e1528f3..aea9def4 100644 --- a/api/utils/test.go +++ b/api/utils/test.go @@ -5,6 +5,7 @@ import ( "github.com/equinor/radix-operator/pkg/apis/application" "github.com/equinor/radix-operator/pkg/apis/applicationconfig" + "github.com/equinor/radix-operator/pkg/apis/config" "github.com/equinor/radix-operator/pkg/apis/config/dnsalias" "github.com/equinor/radix-operator/pkg/apis/deployment" "github.com/equinor/radix-operator/pkg/apis/ingress" @@ -88,6 +89,6 @@ func ApplyDeploymentWithSync(client kubernetes.Interface, radixclient radixclien kubeUtils, _ := kube.New(client, radixclient, secretproviderclient) rd, _ := commonTestUtils.ApplyDeployment(deploymentBuilder) - deploymentSyncer := deployment.NewDeploymentSyncer(client, kubeUtils, radixclient, prometheusClient, registrationBuilder.BuildRR(), rd, "123456", 443, 10, []ingress.AnnotationProvider{}, []deployment.AuxiliaryResourceManager{}) + deploymentSyncer := deployment.NewDeploymentSyncer(client, kubeUtils, radixclient, prometheusClient, registrationBuilder.BuildRR(), rd, []ingress.AnnotationProvider{}, []deployment.AuxiliaryResourceManager{}, &config.Config{}) return deploymentSyncer.OnSync() } diff --git a/api/utils/tlsvalidation/interface.go b/api/utils/tlsvalidation/interface.go new file mode 100644 index 00000000..64c95968 --- /dev/null +++ b/api/utils/tlsvalidation/interface.go @@ -0,0 +1,9 @@ +package tlsvalidation + +// Validator defines methods to validate certificate and private key for TLS +type Validator interface { + // ValidateX509Certificate validates the certificate, dnsName and private key + // certBytes and keyBytes must be in PEM format + // Returns false if validation fails, along with a list of validation error messages + ValidateX509Certificate(certBytes, keyBytes []byte, dnsName string) (bool, []string) +} diff --git a/api/utils/tlsvalidation/mock/tls_secret_validator_mock.go b/api/utils/tlsvalidation/mock/tls_secret_validator_mock.go new file mode 100644 index 00000000..ddc472cb --- /dev/null +++ b/api/utils/tlsvalidation/mock/tls_secret_validator_mock.go @@ -0,0 +1,49 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./api/utils/tlsvalidation/interface.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockValidator is a mock of Validator interface. +type MockValidator struct { + ctrl *gomock.Controller + recorder *MockValidatorMockRecorder +} + +// MockValidatorMockRecorder is the mock recorder for MockValidator. +type MockValidatorMockRecorder struct { + mock *MockValidator +} + +// NewMockValidator creates a new mock instance. +func NewMockValidator(ctrl *gomock.Controller) *MockValidator { + mock := &MockValidator{ctrl: ctrl} + mock.recorder = &MockValidatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockValidator) EXPECT() *MockValidatorMockRecorder { + return m.recorder +} + +// ValidateX509Certificate mocks base method. +func (m *MockValidator) ValidateX509Certificate(certBytes, keyBytes []byte, dnsName string) (bool, []string) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidateX509Certificate", certBytes, keyBytes, dnsName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].([]string) + return ret0, ret1 +} + +// ValidateX509Certificate indicates an expected call of ValidateX509Certificate. +func (mr *MockValidatorMockRecorder) ValidateX509Certificate(certBytes, keyBytes, dnsName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateX509Certificate", reflect.TypeOf((*MockValidator)(nil).ValidateX509Certificate), certBytes, keyBytes, dnsName) +} diff --git a/api/utils/tlsvalidator/validator.go b/api/utils/tlsvalidation/validator.go similarity index 89% rename from api/utils/tlsvalidator/validator.go rename to api/utils/tlsvalidation/validator.go index 1d054edc..b5307835 100644 --- a/api/utils/tlsvalidator/validator.go +++ b/api/utils/tlsvalidation/validator.go @@ -1,4 +1,4 @@ -package tlsvalidator +package tlsvalidation import ( "crypto/ecdsa" @@ -13,13 +13,13 @@ import ( var defaultValidator = validator{} -func DefaultValidator() TLSSecretValidator { +func DefaultValidator() Validator { return &defaultValidator } type validator struct{} -func (v *validator) ValidateTLSKey(keyBytes []byte) (valid bool, failedValidationMessages []string) { +func (v *validator) ValidatePrivateKey(keyBytes []byte) (valid bool, failedValidationMessages []string) { defer func() { valid = len(failedValidationMessages) == 0 }() @@ -68,7 +68,7 @@ func (v *validator) ValidateTLSKey(keyBytes []byte) (valid bool, failedValidatio return } -func (v *validator) ValidateTLSCertificate(certBytes, keyBytes []byte, dnsName string) (valid bool, failedValidationMessages []string) { +func (v *validator) ValidateX509Certificate(certBytes, keyBytes []byte, dnsName string) (valid bool, failedValidationMessages []string) { defer func() { valid = len(failedValidationMessages) == 0 }() diff --git a/api/utils/tlsvalidator/interface.go b/api/utils/tlsvalidator/interface.go deleted file mode 100644 index d00dc6b9..00000000 --- a/api/utils/tlsvalidator/interface.go +++ /dev/null @@ -1,14 +0,0 @@ -package tlsvalidator - -// TLSSecretValidator defines methods to validate certificate and private key for TLS -type TLSSecretValidator interface { - // ValidateTLSKey validates the private key - // keyBytes must be in PEM format - // Returns false is keyBytes is invalid, along with a list of validation error messages - ValidateTLSKey(keyBytes []byte) (bool, []string) - - // ValidateTLSCertificate validates the certificate, dnsName and private key - // certBytes and keyBytes must be in PEM format - // Returns false if validation fails, along with a list of validation error messages - ValidateTLSCertificate(certBytes, keyBytes []byte, dnsName string) (bool, []string) -} diff --git a/api/utils/tlsvalidator/mock/tls_secret_validator_mock.go b/api/utils/tlsvalidator/mock/tls_secret_validator_mock.go deleted file mode 100644 index d6bbbd83..00000000 --- a/api/utils/tlsvalidator/mock/tls_secret_validator_mock.go +++ /dev/null @@ -1,64 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./api/utils/tlsvalidator/interface.go - -// Package mock is a generated GoMock package. -package mock - -import ( - reflect "reflect" - - gomock "github.com/golang/mock/gomock" -) - -// MockTLSSecretValidator is a mock of TLSSecretValidator interface. -type MockTLSSecretValidator struct { - ctrl *gomock.Controller - recorder *MockTLSSecretValidatorMockRecorder -} - -// MockTLSSecretValidatorMockRecorder is the mock recorder for MockTLSSecretValidator. -type MockTLSSecretValidatorMockRecorder struct { - mock *MockTLSSecretValidator -} - -// NewMockTLSSecretValidator creates a new mock instance. -func NewMockTLSSecretValidator(ctrl *gomock.Controller) *MockTLSSecretValidator { - mock := &MockTLSSecretValidator{ctrl: ctrl} - mock.recorder = &MockTLSSecretValidatorMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTLSSecretValidator) EXPECT() *MockTLSSecretValidatorMockRecorder { - return m.recorder -} - -// ValidateTLSCertificate mocks base method. -func (m *MockTLSSecretValidator) ValidateTLSCertificate(certBytes, keyBytes []byte, dnsName string) (bool, []string) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateTLSCertificate", certBytes, keyBytes, dnsName) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].([]string) - return ret0, ret1 -} - -// ValidateTLSCertificate indicates an expected call of ValidateTLSCertificate. -func (mr *MockTLSSecretValidatorMockRecorder) ValidateTLSCertificate(certBytes, keyBytes, dnsName interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateTLSCertificate", reflect.TypeOf((*MockTLSSecretValidator)(nil).ValidateTLSCertificate), certBytes, keyBytes, dnsName) -} - -// ValidateTLSKey mocks base method. -func (m *MockTLSSecretValidator) ValidateTLSKey(keyBytes []byte) (bool, []string) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateTLSKey", keyBytes) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].([]string) - return ret0, ret1 -} - -// ValidateTLSKey indicates an expected call of ValidateTLSKey. -func (mr *MockTLSSecretValidatorMockRecorder) ValidateTLSKey(keyBytes interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateTLSKey", reflect.TypeOf((*MockTLSSecretValidator)(nil).ValidateTLSKey), keyBytes) -} diff --git a/go.mod b/go.mod index 90180e04..39052d30 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module github.com/equinor/radix-api go 1.21 require ( - github.com/equinor/radix-common v1.7.1 + github.com/equinor/radix-common v1.8.0 github.com/equinor/radix-job-scheduler v1.8.5 - github.com/equinor/radix-operator v1.47.2 + github.com/equinor/radix-operator v1.49.0 github.com/evanphx/json-patch/v5 v5.7.0 github.com/go-swagger/go-swagger v0.30.5 github.com/golang-jwt/jwt/v4 v4.5.0 @@ -41,6 +41,7 @@ require ( github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/elnormous/contenttype v1.0.4 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/felixge/httpsnoop v1.0.4 // indirect diff --git a/go.sum b/go.sum index e6a75551..16b2a634 100644 --- a/go.sum +++ b/go.sum @@ -73,18 +73,20 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elnormous/contenttype v1.0.4 h1:FjmVNkvQOGqSX70yvocph7keC8DtmJaLzTTq6ZOQCI8= +github.com/elnormous/contenttype v1.0.4/go.mod h1:5KTOW8m1kdX1dLMiUJeN9szzR2xkngiv2K+RVZwWBbI= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/equinor/radix-common v1.7.1 h1:kl7Tuo2VEo2WHGm/vkvktrZ9t9S3Nht7Mob3CSIzcJI= -github.com/equinor/radix-common v1.7.1/go.mod h1:M6mhgHtFQ3rnjJnyOuECXiZOh7XQ5xVeHMyCAU+YPzQ= +github.com/equinor/radix-common v1.8.0 h1:4mMu36mvJi2QSPEiKOUUJG/Z5EUrPkf0/lRO1qzkt5c= +github.com/equinor/radix-common v1.8.0/go.mod h1:8wGBEAa6auVB3yQ5pImahQzrL3w1ZYTu6N7EXLpJKUk= github.com/equinor/radix-job-scheduler v1.8.5 h1:ahw6FkFpPV167B1/w7/aQKpVMmU5Vc7UBO8411ZtYck= github.com/equinor/radix-job-scheduler v1.8.5/go.mod h1:rNIQU1eCInLV8Yl+5SRITOUU52QwYioYrIGQcsDc3kg= -github.com/equinor/radix-operator v1.47.2 h1:QYOIDhx0zfjrHcipl/AuO+O8cMOqQ6ELESoIsj0BWZo= -github.com/equinor/radix-operator v1.47.2/go.mod h1:kwwnvyW1WKCKiXVSKNhkG7zAe1sFC2XW9IbNZsCCgRw= +github.com/equinor/radix-operator v1.49.0 h1:yNdb0zocBw7GdWybAGUm2lWmk6I+xeeCjvrN8xjVTyA= +github.com/equinor/radix-operator v1.49.0/go.mod h1:i8A6V/g1OM+Zk2lAASZaoX+lHdJIZYYZHA586SHB2p8= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= @@ -360,6 +362,7 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/main.go b/main.go index b2e2c567..2d4bfddd 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/equinor/radix-api/api/secrets" + "github.com/equinor/radix-api/api/utils/tlsvalidation" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-api/api/environmentvariables" @@ -110,7 +111,7 @@ func getControllers() ([]models.Controller, error) { buildsecrets.NewBuildSecretsController(), buildstatus.NewBuildStatusController(buildStatus), alerting.NewAlertingController(), - secrets.NewSecretController(), + secrets.NewSecretController(tlsvalidation.DefaultValidator()), }, nil } diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index cf56e1ab..c930c879 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -1897,6 +1897,89 @@ } } }, + "/applications/{appName}/environments/{envName}/components/{componentName}/externaldns/{fqdn}/tls": { + "put": { + "tags": [ + "component" + ], + "summary": "Set external DNS TLS private key certificate for a component", + "operationId": "updateComponentExternalDnsTls", + "parameters": [ + { + "type": "string", + "description": "Name of application", + "name": "appName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "secret of Radix application", + "name": "envName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "secret component of Radix application", + "name": "componentName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "FQDN to be updated", + "name": "fqdn", + "in": "path", + "required": true + }, + { + "description": "New TLS private key and certificate", + "name": "tlsData", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UpdateExternalDnsTlsRequest" + } + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", + "name": "Impersonate-User", + "in": "header" + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set)", + "name": "Impersonate-Group", + "in": "header" + } + ], + "responses": { + "200": { + "description": "success" + }, + "400": { + "description": "Invalid application" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + }, + "409": { + "description": "Conflict" + }, + "500": { + "description": "Internal server error" + } + } + } + }, "/applications/{appName}/environments/{envName}/components/{componentName}/replicas/{podName}/logs": { "get": { "tags": [ @@ -5412,6 +5495,14 @@ "image" ], "properties": { + "externalDNS": { + "description": "Array of external DNS configurations", + "type": "array", + "items": { + "$ref": "#/definitions/ExternalDNS" + }, + "x-go-name": "ExternalDNS" + }, "horizontalScalingSummary": { "$ref": "#/definitions/HorizontalScalingSummary" }, @@ -6047,6 +6138,26 @@ }, "x-go-package": "github.com/equinor/radix-api/api/events/models" }, + "ExternalDNS": { + "description": "ExternalDNS describes an external DNS entry for a component", + "type": "object", + "required": [ + "fqdn", + "tls" + ], + "properties": { + "fqdn": { + "description": "Fully Qualified Domain Name", + "type": "string", + "x-go-name": "FQDN", + "example": "site.example.com" + }, + "tls": { + "$ref": "#/definitions/TLS" + } + }, + "x-go-package": "github.com/equinor/radix-api/api/deployments/models" + }, "HorizontalScalingSummary": { "description": "HorizontalScalingSummary describe the summary of horizontal scaling of a component", "type": "object", @@ -6216,12 +6327,6 @@ "x-go-name": "Pipeline", "example": "build-deploy" }, - "promotedDeploymentName": { - "description": "PromotedDeploymentName the name of the deployment that was promoted", - "type": "string", - "x-go-name": "PromotedDeploymentName", - "example": "component-6hznh" - }, "promotedFromDeployment": { "description": "RadixDeployment name, which is promoted", "type": "string", @@ -6608,10 +6713,28 @@ "example": "2006-01-02T15:04:05Z" }, "status": { - "description": "Status of the step", + "description": "Status of the step\nStarted TaskRunReasonStarted TaskRunReasonStarted is the reason set when the TaskRun has just started\nRunning TaskRunReasonRunning TaskRunReasonRunning is the reason set when the TaskRun is running\nSucceeded TaskRunReasonSuccessful TaskRunReasonSuccessful is the reason set when the TaskRun completed successfully\nFailed TaskRunReasonFailed TaskRunReasonFailed is the reason set when the TaskRun completed with a failure\nToBeRetried TaskRunReasonToBeRetried TaskRunReasonToBeRetried is the reason set when the last TaskRun execution failed, and will be retried\nTaskRunCancelled TaskRunReasonCancelled TaskRunReasonCancelled is the reason set when the TaskRun is cancelled by the user\nTaskRunTimeout TaskRunReasonTimedOut TaskRunReasonTimedOut is the reason set when one TaskRun execution has timed out\nTaskRunImagePullFailed TaskRunReasonImagePullFailed TaskRunReasonImagePullFailed is the reason set when the step of a task fails due to image not being pulled\nTaskRunResultLargerThanAllowedLimit TaskRunReasonResultLargerThanAllowedLimit TaskRunReasonResultLargerThanAllowedLimit is the reason set when one of the results exceeds its maximum allowed limit of 1 KB\nTaskRunStopSidecarFailed TaskRunReasonStopSidecarFailed TaskRunReasonStopSidecarFailed indicates that the sidecar is not properly stopped.\nInvalidParamValue TaskRunReasonInvalidParamValue TaskRunReasonInvalidParamValue indicates that the TaskRun Param input value is not allowed.\nTaskRunResolutionFailed TaskRunReasonFailedResolution TaskRunReasonFailedResolution indicated that the reason for failure status is that references within the TaskRun could not be resolved\nTaskRunValidationFailed TaskRunReasonFailedValidation TaskRunReasonFailedValidation indicated that the reason for failure status is that taskrun failed runtime validation\nTaskValidationFailed TaskRunReasonTaskFailedValidation TaskRunReasonTaskFailedValidation indicated that the reason for failure status is that task failed runtime validation\nResourceVerificationFailed TaskRunReasonResourceVerificationFailed TaskRunReasonResourceVerificationFailed indicates that the task fails the trusted resource verification, it could be the content has changed, signature is invalid or public key is invalid\nFailureIgnored TaskRunReasonFailureIgnored TaskRunReasonFailureIgnored is the reason set when the Taskrun has failed due to pod execution error and the failure is ignored for the owning PipelineRun. TaskRuns failed due to reconciler/validation error should not use this reason.", "type": "string", - "x-go-name": "Status", - "example": "Started" + "enum": [ + "Started", + "Running", + "Succeeded", + "Failed", + "ToBeRetried", + "TaskRunCancelled", + "TaskRunTimeout", + "TaskRunImagePullFailed", + "TaskRunResultLargerThanAllowedLimit", + "TaskRunStopSidecarFailed", + "InvalidParamValue", + "TaskRunResolutionFailed", + "TaskRunValidationFailed", + "TaskValidationFailed", + "ResourceVerificationFailed", + "FailureIgnored" + ], + "x-go-enum-desc": "Started TaskRunReasonStarted TaskRunReasonStarted is the reason set when the TaskRun has just started\nRunning TaskRunReasonRunning TaskRunReasonRunning is the reason set when the TaskRun is running\nSucceeded TaskRunReasonSuccessful TaskRunReasonSuccessful is the reason set when the TaskRun completed successfully\nFailed TaskRunReasonFailed TaskRunReasonFailed is the reason set when the TaskRun completed with a failure\nToBeRetried TaskRunReasonToBeRetried TaskRunReasonToBeRetried is the reason set when the last TaskRun execution failed, and will be retried\nTaskRunCancelled TaskRunReasonCancelled TaskRunReasonCancelled is the reason set when the TaskRun is cancelled by the user\nTaskRunTimeout TaskRunReasonTimedOut TaskRunReasonTimedOut is the reason set when one TaskRun execution has timed out\nTaskRunImagePullFailed TaskRunReasonImagePullFailed TaskRunReasonImagePullFailed is the reason set when the step of a task fails due to image not being pulled\nTaskRunResultLargerThanAllowedLimit TaskRunReasonResultLargerThanAllowedLimit TaskRunReasonResultLargerThanAllowedLimit is the reason set when one of the results exceeds its maximum allowed limit of 1 KB\nTaskRunStopSidecarFailed TaskRunReasonStopSidecarFailed TaskRunReasonStopSidecarFailed indicates that the sidecar is not properly stopped.\nInvalidParamValue TaskRunReasonInvalidParamValue TaskRunReasonInvalidParamValue indicates that the TaskRun Param input value is not allowed.\nTaskRunResolutionFailed TaskRunReasonFailedResolution TaskRunReasonFailedResolution indicated that the reason for failure status is that references within the TaskRun could not be resolved\nTaskRunValidationFailed TaskRunReasonFailedValidation TaskRunReasonFailedValidation indicated that the reason for failure status is that taskrun failed runtime validation\nTaskValidationFailed TaskRunReasonTaskFailedValidation TaskRunReasonTaskFailedValidation indicated that the reason for failure status is that task failed runtime validation\nResourceVerificationFailed TaskRunReasonResourceVerificationFailed TaskRunReasonResourceVerificationFailed indicates that the task fails the trusted resource verification, it could be the content has changed, signature is invalid or public key is invalid\nFailureIgnored TaskRunReasonFailureIgnored TaskRunReasonFailureIgnored is the reason set when the Taskrun has failed due to pod execution error and the failure is ignored for the owning PipelineRun. TaskRuns failed due to reconciler/validation error should not use this reason.", + "x-go-name": "Status" }, "statusMessage": { "description": "StatusMessage of the task", @@ -6668,10 +6791,45 @@ "example": "2006-01-02T15:04:05Z" }, "status": { - "description": "Status of the task", + "description": "Status of the task\nStarted PipelineRunReasonStarted PipelineRunReasonStarted is the reason set when the PipelineRun has just started\nRunning PipelineRunReasonRunning PipelineRunReasonRunning is the reason set when the PipelineRun is running\nSucceeded PipelineRunReasonSuccessful PipelineRunReasonSuccessful is the reason set when the PipelineRun completed successfully\nCompleted PipelineRunReasonCompleted PipelineRunReasonCompleted is the reason set when the PipelineRun completed successfully with one or more skipped Tasks\nFailed PipelineRunReasonFailed PipelineRunReasonFailed is the reason set when the PipelineRun completed with a failure\nCancelled PipelineRunReasonCancelled PipelineRunReasonCancelled is the reason set when the PipelineRun cancelled by the user This reason may be found with a corev1.ConditionFalse status, if the cancellation was processed successfully This reason may be found with a corev1.ConditionUnknown status, if the cancellation is being processed or failed\nPipelineRunPending PipelineRunReasonPending PipelineRunReasonPending is the reason set when the PipelineRun is in the pending state\nPipelineRunTimeout PipelineRunReasonTimedOut PipelineRunReasonTimedOut is the reason set when the PipelineRun has timed out\nPipelineRunStopping PipelineRunReasonStopping PipelineRunReasonStopping indicates that no new Tasks will be scheduled by the controller, and the pipeline will stop once all running tasks complete their work\nCancelledRunningFinally PipelineRunReasonCancelledRunningFinally PipelineRunReasonCancelledRunningFinally indicates that pipeline has been gracefully cancelled and no new Tasks will be scheduled by the controller, but final tasks are now running\nStoppedRunningFinally PipelineRunReasonStoppedRunningFinally PipelineRunReasonStoppedRunningFinally indicates that pipeline has been gracefully stopped and no new Tasks will be scheduled by the controller, but final tasks are now running\nCouldntGetPipeline PipelineRunReasonCouldntGetPipeline ReasonCouldntGetPipeline indicates that the reason for the failure status is that the associated Pipeline couldn't be retrieved\nInvalidPipelineResourceBindings PipelineRunReasonInvalidBindings ReasonInvalidBindings indicates that the reason for the failure status is that the PipelineResources bound in the PipelineRun didn't match those declared in the Pipeline\nInvalidWorkspaceBindings PipelineRunReasonInvalidWorkspaceBinding ReasonInvalidWorkspaceBinding indicates that a Pipeline expects a workspace but a PipelineRun has provided an invalid binding.\nInvalidTaskRunSpecs PipelineRunReasonInvalidTaskRunSpec ReasonInvalidTaskRunSpec indicates that PipelineRun.Spec.TaskRunSpecs[].PipelineTaskName is defined with a not exist taskName in pipelineSpec.\nParameterTypeMismatch PipelineRunReasonParameterTypeMismatch ReasonParameterTypeMismatch indicates that the reason for the failure status is that parameter(s) declared in the PipelineRun do not have the some declared type as the parameters(s) declared in the Pipeline that they are supposed to override.\nObjectParameterMissKeys PipelineRunReasonObjectParameterMissKeys ReasonObjectParameterMissKeys indicates that the object param value provided from PipelineRun spec misses some keys required for the object param declared in Pipeline spec.\nParamArrayIndexingInvalid PipelineRunReasonParamArrayIndexingInvalid ReasonParamArrayIndexingInvalid indicates that the use of param array indexing is not under correct api fields feature gate or the array is out of bound.\nCouldntGetTask PipelineRunReasonCouldntGetTask ReasonCouldntGetTask indicates that the reason for the failure status is that the associated Pipeline's Tasks couldn't all be retrieved\nParameterMissing PipelineRunReasonParameterMissing ReasonParameterMissing indicates that the reason for the failure status is that the associated PipelineRun didn't provide all the required parameters\nPipelineValidationFailed PipelineRunReasonFailedValidation ReasonFailedValidation indicates that the reason for failure status is that pipelinerun failed runtime validation\nCouldntGetPipelineResult PipelineRunReasonCouldntGetPipelineResult PipelineRunReasonCouldntGetPipelineResult indicates that the pipeline fails to retrieve the referenced result. This could be due to failed TaskRuns or Runs that were supposed to produce the results\nPipelineInvalidGraph PipelineRunReasonInvalidGraph ReasonInvalidGraph indicates that the reason for the failure status is that the associated Pipeline is an invalid graph (a.k.a wrong order, cycle, …)\nPipelineRunCouldntCancel PipelineRunReasonCouldntCancel ReasonCouldntCancel indicates that a PipelineRun was cancelled but attempting to update all of the running TaskRuns as cancelled failed.\nPipelineRunCouldntTimeOut PipelineRunReasonCouldntTimeOut ReasonCouldntTimeOut indicates that a PipelineRun was timed out but attempting to update all of the running TaskRuns as timed out failed.\nInvalidMatrixParameterTypes PipelineRunReasonInvalidMatrixParameterTypes ReasonInvalidMatrixParameterTypes indicates a matrix contains invalid parameter types\nInvalidTaskResultReference PipelineRunReasonInvalidTaskResultReference ReasonInvalidTaskResultReference indicates a task result was declared but was not initialized by that task\nRequiredWorkspaceMarkedOptional PipelineRunReasonRequiredWorkspaceMarkedOptional ReasonRequiredWorkspaceMarkedOptional indicates an optional workspace has been passed to a Task that is expecting a non-optional workspace\nResolvingPipelineRef PipelineRunReasonResolvingPipelineRef ReasonResolvingPipelineRef indicates that the PipelineRun is waiting for its pipelineRef to be asynchronously resolved.\nResourceVerificationFailed PipelineRunReasonResourceVerificationFailed ReasonResourceVerificationFailed indicates that the pipeline fails the trusted resource verification, it could be the content has changed, signature is invalid or public key is invalid\nCreateRunFailed PipelineRunReasonCreateRunFailed ReasonCreateRunFailed indicates that the pipeline fails to create the taskrun or other run resources\nCELEvaluationFailed PipelineRunReasonCELEvaluationFailed ReasonCELEvaluationFailed indicates the pipeline fails the CEL evaluation\nInvalidParamValue PipelineRunReasonInvalidParamValue PipelineRunReasonInvalidParamValue indicates that the PipelineRun Param input value is not allowed.", "type": "string", - "x-go-name": "Status", - "example": "Running" + "enum": [ + "Started", + "Running", + "Succeeded", + "Completed", + "Failed", + "Cancelled", + "PipelineRunPending", + "PipelineRunTimeout", + "PipelineRunStopping", + "CancelledRunningFinally", + "StoppedRunningFinally", + "CouldntGetPipeline", + "InvalidPipelineResourceBindings", + "InvalidWorkspaceBindings", + "InvalidTaskRunSpecs", + "ParameterTypeMismatch", + "ObjectParameterMissKeys", + "ParamArrayIndexingInvalid", + "CouldntGetTask", + "ParameterMissing", + "PipelineValidationFailed", + "CouldntGetPipelineResult", + "PipelineInvalidGraph", + "PipelineRunCouldntCancel", + "PipelineRunCouldntTimeOut", + "InvalidMatrixParameterTypes", + "InvalidTaskResultReference", + "RequiredWorkspaceMarkedOptional", + "ResolvingPipelineRef", + "ResourceVerificationFailed", + "CreateRunFailed", + "CELEvaluationFailed", + "InvalidParamValue" + ], + "x-go-enum-desc": "Started PipelineRunReasonStarted PipelineRunReasonStarted is the reason set when the PipelineRun has just started\nRunning PipelineRunReasonRunning PipelineRunReasonRunning is the reason set when the PipelineRun is running\nSucceeded PipelineRunReasonSuccessful PipelineRunReasonSuccessful is the reason set when the PipelineRun completed successfully\nCompleted PipelineRunReasonCompleted PipelineRunReasonCompleted is the reason set when the PipelineRun completed successfully with one or more skipped Tasks\nFailed PipelineRunReasonFailed PipelineRunReasonFailed is the reason set when the PipelineRun completed with a failure\nCancelled PipelineRunReasonCancelled PipelineRunReasonCancelled is the reason set when the PipelineRun cancelled by the user This reason may be found with a corev1.ConditionFalse status, if the cancellation was processed successfully This reason may be found with a corev1.ConditionUnknown status, if the cancellation is being processed or failed\nPipelineRunPending PipelineRunReasonPending PipelineRunReasonPending is the reason set when the PipelineRun is in the pending state\nPipelineRunTimeout PipelineRunReasonTimedOut PipelineRunReasonTimedOut is the reason set when the PipelineRun has timed out\nPipelineRunStopping PipelineRunReasonStopping PipelineRunReasonStopping indicates that no new Tasks will be scheduled by the controller, and the pipeline will stop once all running tasks complete their work\nCancelledRunningFinally PipelineRunReasonCancelledRunningFinally PipelineRunReasonCancelledRunningFinally indicates that pipeline has been gracefully cancelled and no new Tasks will be scheduled by the controller, but final tasks are now running\nStoppedRunningFinally PipelineRunReasonStoppedRunningFinally PipelineRunReasonStoppedRunningFinally indicates that pipeline has been gracefully stopped and no new Tasks will be scheduled by the controller, but final tasks are now running\nCouldntGetPipeline PipelineRunReasonCouldntGetPipeline ReasonCouldntGetPipeline indicates that the reason for the failure status is that the associated Pipeline couldn't be retrieved\nInvalidPipelineResourceBindings PipelineRunReasonInvalidBindings ReasonInvalidBindings indicates that the reason for the failure status is that the PipelineResources bound in the PipelineRun didn't match those declared in the Pipeline\nInvalidWorkspaceBindings PipelineRunReasonInvalidWorkspaceBinding ReasonInvalidWorkspaceBinding indicates that a Pipeline expects a workspace but a PipelineRun has provided an invalid binding.\nInvalidTaskRunSpecs PipelineRunReasonInvalidTaskRunSpec ReasonInvalidTaskRunSpec indicates that PipelineRun.Spec.TaskRunSpecs[].PipelineTaskName is defined with a not exist taskName in pipelineSpec.\nParameterTypeMismatch PipelineRunReasonParameterTypeMismatch ReasonParameterTypeMismatch indicates that the reason for the failure status is that parameter(s) declared in the PipelineRun do not have the some declared type as the parameters(s) declared in the Pipeline that they are supposed to override.\nObjectParameterMissKeys PipelineRunReasonObjectParameterMissKeys ReasonObjectParameterMissKeys indicates that the object param value provided from PipelineRun spec misses some keys required for the object param declared in Pipeline spec.\nParamArrayIndexingInvalid PipelineRunReasonParamArrayIndexingInvalid ReasonParamArrayIndexingInvalid indicates that the use of param array indexing is not under correct api fields feature gate or the array is out of bound.\nCouldntGetTask PipelineRunReasonCouldntGetTask ReasonCouldntGetTask indicates that the reason for the failure status is that the associated Pipeline's Tasks couldn't all be retrieved\nParameterMissing PipelineRunReasonParameterMissing ReasonParameterMissing indicates that the reason for the failure status is that the associated PipelineRun didn't provide all the required parameters\nPipelineValidationFailed PipelineRunReasonFailedValidation ReasonFailedValidation indicates that the reason for failure status is that pipelinerun failed runtime validation\nCouldntGetPipelineResult PipelineRunReasonCouldntGetPipelineResult PipelineRunReasonCouldntGetPipelineResult indicates that the pipeline fails to retrieve the referenced result. This could be due to failed TaskRuns or Runs that were supposed to produce the results\nPipelineInvalidGraph PipelineRunReasonInvalidGraph ReasonInvalidGraph indicates that the reason for the failure status is that the associated Pipeline is an invalid graph (a.k.a wrong order, cycle, …)\nPipelineRunCouldntCancel PipelineRunReasonCouldntCancel ReasonCouldntCancel indicates that a PipelineRun was cancelled but attempting to update all of the running TaskRuns as cancelled failed.\nPipelineRunCouldntTimeOut PipelineRunReasonCouldntTimeOut ReasonCouldntTimeOut indicates that a PipelineRun was timed out but attempting to update all of the running TaskRuns as timed out failed.\nInvalidMatrixParameterTypes PipelineRunReasonInvalidMatrixParameterTypes ReasonInvalidMatrixParameterTypes indicates a matrix contains invalid parameter types\nInvalidTaskResultReference PipelineRunReasonInvalidTaskResultReference ReasonInvalidTaskResultReference indicates a task result was declared but was not initialized by that task\nRequiredWorkspaceMarkedOptional PipelineRunReasonRequiredWorkspaceMarkedOptional ReasonRequiredWorkspaceMarkedOptional indicates an optional workspace has been passed to a Task that is expecting a non-optional workspace\nResolvingPipelineRef PipelineRunReasonResolvingPipelineRef ReasonResolvingPipelineRef indicates that the PipelineRun is waiting for its pipelineRef to be asynchronously resolved.\nResourceVerificationFailed PipelineRunReasonResourceVerificationFailed ReasonResourceVerificationFailed indicates that the pipeline fails the trusted resource verification, it could be the content has changed, signature is invalid or public key is invalid\nCreateRunFailed PipelineRunReasonCreateRunFailed ReasonCreateRunFailed indicates that the pipeline fails to create the taskrun or other run resources\nCELEvaluationFailed PipelineRunReasonCELEvaluationFailed ReasonCELEvaluationFailed indicates the pipeline fails the CEL evaluation\nInvalidParamValue PipelineRunReasonInvalidParamValue PipelineRunReasonInvalidParamValue indicates that the PipelineRun Param input value is not allowed.", + "x-go-name": "Status" }, "statusMessage": { "description": "StatusMessage of the task", @@ -6707,10 +6865,28 @@ "example": "2006-01-02T15:04:05Z" }, "status": { - "description": "Status of the task", + "description": "Status of the task\nStarted TaskRunReasonStarted TaskRunReasonStarted is the reason set when the TaskRun has just started\nRunning TaskRunReasonRunning TaskRunReasonRunning is the reason set when the TaskRun is running\nSucceeded TaskRunReasonSuccessful TaskRunReasonSuccessful is the reason set when the TaskRun completed successfully\nFailed TaskRunReasonFailed TaskRunReasonFailed is the reason set when the TaskRun completed with a failure\nToBeRetried TaskRunReasonToBeRetried TaskRunReasonToBeRetried is the reason set when the last TaskRun execution failed, and will be retried\nTaskRunCancelled TaskRunReasonCancelled TaskRunReasonCancelled is the reason set when the TaskRun is cancelled by the user\nTaskRunTimeout TaskRunReasonTimedOut TaskRunReasonTimedOut is the reason set when one TaskRun execution has timed out\nTaskRunImagePullFailed TaskRunReasonImagePullFailed TaskRunReasonImagePullFailed is the reason set when the step of a task fails due to image not being pulled\nTaskRunResultLargerThanAllowedLimit TaskRunReasonResultLargerThanAllowedLimit TaskRunReasonResultLargerThanAllowedLimit is the reason set when one of the results exceeds its maximum allowed limit of 1 KB\nTaskRunStopSidecarFailed TaskRunReasonStopSidecarFailed TaskRunReasonStopSidecarFailed indicates that the sidecar is not properly stopped.\nInvalidParamValue TaskRunReasonInvalidParamValue TaskRunReasonInvalidParamValue indicates that the TaskRun Param input value is not allowed.\nTaskRunResolutionFailed TaskRunReasonFailedResolution TaskRunReasonFailedResolution indicated that the reason for failure status is that references within the TaskRun could not be resolved\nTaskRunValidationFailed TaskRunReasonFailedValidation TaskRunReasonFailedValidation indicated that the reason for failure status is that taskrun failed runtime validation\nTaskValidationFailed TaskRunReasonTaskFailedValidation TaskRunReasonTaskFailedValidation indicated that the reason for failure status is that task failed runtime validation\nResourceVerificationFailed TaskRunReasonResourceVerificationFailed TaskRunReasonResourceVerificationFailed indicates that the task fails the trusted resource verification, it could be the content has changed, signature is invalid or public key is invalid\nFailureIgnored TaskRunReasonFailureIgnored TaskRunReasonFailureIgnored is the reason set when the Taskrun has failed due to pod execution error and the failure is ignored for the owning PipelineRun. TaskRuns failed due to reconciler/validation error should not use this reason.", "type": "string", - "x-go-name": "Status", - "example": "Completed" + "enum": [ + "Started", + "Running", + "Succeeded", + "Failed", + "ToBeRetried", + "TaskRunCancelled", + "TaskRunTimeout", + "TaskRunImagePullFailed", + "TaskRunResultLargerThanAllowedLimit", + "TaskRunStopSidecarFailed", + "InvalidParamValue", + "TaskRunResolutionFailed", + "TaskRunValidationFailed", + "TaskValidationFailed", + "ResourceVerificationFailed", + "FailureIgnored" + ], + "x-go-enum-desc": "Started TaskRunReasonStarted TaskRunReasonStarted is the reason set when the TaskRun has just started\nRunning TaskRunReasonRunning TaskRunReasonRunning is the reason set when the TaskRun is running\nSucceeded TaskRunReasonSuccessful TaskRunReasonSuccessful is the reason set when the TaskRun completed successfully\nFailed TaskRunReasonFailed TaskRunReasonFailed is the reason set when the TaskRun completed with a failure\nToBeRetried TaskRunReasonToBeRetried TaskRunReasonToBeRetried is the reason set when the last TaskRun execution failed, and will be retried\nTaskRunCancelled TaskRunReasonCancelled TaskRunReasonCancelled is the reason set when the TaskRun is cancelled by the user\nTaskRunTimeout TaskRunReasonTimedOut TaskRunReasonTimedOut is the reason set when one TaskRun execution has timed out\nTaskRunImagePullFailed TaskRunReasonImagePullFailed TaskRunReasonImagePullFailed is the reason set when the step of a task fails due to image not being pulled\nTaskRunResultLargerThanAllowedLimit TaskRunReasonResultLargerThanAllowedLimit TaskRunReasonResultLargerThanAllowedLimit is the reason set when one of the results exceeds its maximum allowed limit of 1 KB\nTaskRunStopSidecarFailed TaskRunReasonStopSidecarFailed TaskRunReasonStopSidecarFailed indicates that the sidecar is not properly stopped.\nInvalidParamValue TaskRunReasonInvalidParamValue TaskRunReasonInvalidParamValue indicates that the TaskRun Param input value is not allowed.\nTaskRunResolutionFailed TaskRunReasonFailedResolution TaskRunReasonFailedResolution indicated that the reason for failure status is that references within the TaskRun could not be resolved\nTaskRunValidationFailed TaskRunReasonFailedValidation TaskRunReasonFailedValidation indicated that the reason for failure status is that taskrun failed runtime validation\nTaskValidationFailed TaskRunReasonTaskFailedValidation TaskRunReasonTaskFailedValidation indicated that the reason for failure status is that task failed runtime validation\nResourceVerificationFailed TaskRunReasonResourceVerificationFailed TaskRunReasonResourceVerificationFailed indicates that the task fails the trusted resource verification, it could be the content has changed, signature is invalid or public key is invalid\nFailureIgnored TaskRunReasonFailureIgnored TaskRunReasonFailureIgnored is the reason set when the Taskrun has failed due to pod execution error and the failure is ignored for the owning PipelineRun. TaskRuns failed due to reconciler/validation error should not use this reason.", + "x-go-name": "Status" }, "statusMessage": { "description": "StatusMessage of the task", @@ -7213,34 +7389,16 @@ "enum": [ "Pending", "Consistent", - "NotAvailable", - "Invalid" + "NotAvailable" ], "x-go-name": "Status", "example": "Consistent" }, - "statusMessages": { - "description": "StatusMessages contains a list of messages related to the Status", - "type": "array", - "items": { - "type": "string" - }, - "x-go-name": "StatusMessages" - }, - "tlsCertificates": { - "description": "TLSCertificates holds the TLS certificate and certificate authorities (CA)\nThe first certificate in the list should be the TLS certificate and the rest should be CA certificates", - "type": "array", - "items": { - "$ref": "#/definitions/TLSCertificate" - }, - "x-go-name": "TLSCertificates" - }, "type": { - "description": "Type of the secret\ngeneric SecretTypeGeneric\nclient-cert SecretTypeClientCert\nazure-blob-fuse-volume SecretTypeAzureBlobFuseVolume\ncsi-azure-blob-volume SecretTypeCsiAzureBlobVolume\ncsi-azure-key-vault-creds SecretTypeCsiAzureKeyVaultCreds\ncsi-azure-key-vault-item SecretTypeCsiAzureKeyVaultItem\nclient-cert-auth SecretTypeClientCertificateAuth\noauth2-proxy SecretTypeOAuth2Proxy", + "description": "Type of the secret\ngeneric SecretTypeGeneric\nazure-blob-fuse-volume SecretTypeAzureBlobFuseVolume\ncsi-azure-blob-volume SecretTypeCsiAzureBlobVolume\ncsi-azure-key-vault-creds SecretTypeCsiAzureKeyVaultCreds\ncsi-azure-key-vault-item SecretTypeCsiAzureKeyVaultItem\nclient-cert-auth SecretTypeClientCertificateAuth\noauth2-proxy SecretTypeOAuth2Proxy", "type": "string", "enum": [ "generic", - "client-cert", "azure-blob-fuse-volume", "csi-azure-blob-volume", "csi-azure-key-vault-creds", @@ -7248,7 +7406,7 @@ "client-cert-auth", "oauth2-proxy" ], - "x-go-enum-desc": "generic SecretTypeGeneric\nclient-cert SecretTypeClientCert\nazure-blob-fuse-volume SecretTypeAzureBlobFuseVolume\ncsi-azure-blob-volume SecretTypeCsiAzureBlobVolume\ncsi-azure-key-vault-creds SecretTypeCsiAzureKeyVaultCreds\ncsi-azure-key-vault-item SecretTypeCsiAzureKeyVaultItem\nclient-cert-auth SecretTypeClientCertificateAuth\noauth2-proxy SecretTypeOAuth2Proxy", + "x-go-enum-desc": "generic SecretTypeGeneric\nazure-blob-fuse-volume SecretTypeAzureBlobFuseVolume\ncsi-azure-blob-volume SecretTypeCsiAzureBlobVolume\ncsi-azure-key-vault-creds SecretTypeCsiAzureKeyVaultCreds\ncsi-azure-key-vault-item SecretTypeCsiAzureKeyVaultItem\nclient-cert-auth SecretTypeClientCertificateAuth\noauth2-proxy SecretTypeOAuth2Proxy", "x-go-name": "Type", "example": "client-cert" } @@ -7269,11 +7427,10 @@ "example": "p4$sW0rDz" }, "type": { - "description": "Type of the secret\ngeneric SecretTypeGeneric\nclient-cert SecretTypeClientCert\nazure-blob-fuse-volume SecretTypeAzureBlobFuseVolume\ncsi-azure-blob-volume SecretTypeCsiAzureBlobVolume\ncsi-azure-key-vault-creds SecretTypeCsiAzureKeyVaultCreds\ncsi-azure-key-vault-item SecretTypeCsiAzureKeyVaultItem\nclient-cert-auth SecretTypeClientCertificateAuth\noauth2-proxy SecretTypeOAuth2Proxy", + "description": "Type of the secret\ngeneric SecretTypeGeneric\nazure-blob-fuse-volume SecretTypeAzureBlobFuseVolume\ncsi-azure-blob-volume SecretTypeCsiAzureBlobVolume\ncsi-azure-key-vault-creds SecretTypeCsiAzureKeyVaultCreds\ncsi-azure-key-vault-item SecretTypeCsiAzureKeyVaultItem\nclient-cert-auth SecretTypeClientCertificateAuth\noauth2-proxy SecretTypeOAuth2Proxy", "type": "string", "enum": [ "generic", - "client-cert", "azure-blob-fuse-volume", "csi-azure-blob-volume", "csi-azure-key-vault-creds", @@ -7281,7 +7438,7 @@ "client-cert-auth", "oauth2-proxy" ], - "x-go-enum-desc": "generic SecretTypeGeneric\nclient-cert SecretTypeClientCert\nazure-blob-fuse-volume SecretTypeAzureBlobFuseVolume\ncsi-azure-blob-volume SecretTypeCsiAzureBlobVolume\ncsi-azure-key-vault-creds SecretTypeCsiAzureKeyVaultCreds\ncsi-azure-key-vault-item SecretTypeCsiAzureKeyVaultItem\nclient-cert-auth SecretTypeClientCertificateAuth\noauth2-proxy SecretTypeOAuth2Proxy", + "x-go-enum-desc": "generic SecretTypeGeneric\nazure-blob-fuse-volume SecretTypeAzureBlobFuseVolume\ncsi-azure-blob-volume SecretTypeCsiAzureBlobVolume\ncsi-azure-key-vault-creds SecretTypeCsiAzureKeyVaultCreds\ncsi-azure-key-vault-item SecretTypeCsiAzureKeyVaultItem\nclient-cert-auth SecretTypeClientCertificateAuth\noauth2-proxy SecretTypeOAuth2Proxy", "x-go-name": "Type", "example": "azure-blob-fuse-volume" } @@ -7363,50 +7520,49 @@ }, "x-go-package": "github.com/equinor/radix-api/api/jobs/models" }, - "TLSCertificate": { - "description": "TLSCertificate holds information about a TLS certificate", + "TLS": { + "description": "TLS configuration and status for external DNS", "type": "object", "required": [ - "subject", - "issuer", - "notBefore", - "notAfter" + "useAutomation", + "status" ], "properties": { - "dnsNames": { - "description": "DNSNames defines list of Subject Alternate Names in the certificate", + "certificates": { + "description": "Certificates holds the X509 certificate chain\nThe first certificate in the list should be the host certificate and the rest should be intermediate certificates", "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/X509Certificate" }, - "x-go-name": "DNSNames" - }, - "issuer": { - "description": "Issuer contains the distinguished name for the certificate's issuer", - "type": "string", - "x-go-name": "Issuer", - "example": "CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US" + "x-go-name": "Certificates" }, - "notAfter": { - "description": "NotAfter defines the uppdater date/time validity boundary", + "status": { + "description": "Status of TLS certificate and private key\nPending TLSStatusPending TLS certificate and private key not set\nConsistent TLSStatusConsistent TLS certificate and private key is valid\nInvalid TLSStatusInvalid TLS certificate and private key is invalid", "type": "string", - "format": "date-time", - "x-go-name": "NotAfter" + "enum": [ + "Pending", + "Consistent", + "Invalid" + ], + "x-go-enum-desc": "Pending TLSStatusPending TLS certificate and private key not set\nConsistent TLSStatusConsistent TLS certificate and private key is valid\nInvalid TLSStatusInvalid TLS certificate and private key is invalid", + "x-go-name": "Status", + "example": "Consistent" }, - "notBefore": { - "description": "NotBefore defines the lower date/time validity boundary", - "type": "string", - "format": "date-time", - "x-go-name": "NotBefore" + "statusMessages": { + "description": "StatusMessages contains a list of messages related to Status", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "StatusMessages" }, - "subject": { - "description": "Subject contains the distinguished name for the certificate", - "type": "string", - "x-go-name": "Subject", - "example": "CN=mysite.example.com,O=MyOrg,L=MyLocation,C=NO" + "useAutomation": { + "description": "UseAutomation describes if TLS certificate is automatically issued using automation (ACME)", + "type": "boolean", + "x-go-name": "UseAutomation" } }, - "x-go-package": "github.com/equinor/radix-api/api/secrets/models" + "x-go-package": "github.com/equinor/radix-api/api/deployments/models" }, "UpdateAlertingConfig": { "description": "UpdateAlertingConfig contains fields for updating alert settings", @@ -7429,6 +7585,33 @@ }, "x-go-package": "github.com/equinor/radix-api/api/alerting/models" }, + "UpdateExternalDnsTlsRequest": { + "description": "UpdateExternalDNSTLSRequest describes request body for setting private key and certificate for external DNS TLS", + "type": "object", + "required": [ + "privateKey", + "certificate" + ], + "properties": { + "certificate": { + "description": "X509 certificate in PEM format", + "type": "string", + "x-go-name": "Certificate" + }, + "privateKey": { + "description": "Private key in PEM format", + "type": "string", + "x-go-name": "PrivateKey" + }, + "skipValidation": { + "description": "Skip validation of certificate and private key", + "type": "boolean", + "x-go-name": "SkipValidation" + } + }, + "x-go-name": "UpdateExternalDNSTLSRequest", + "x-go-package": "github.com/equinor/radix-api/api/secrets/models" + }, "UpdateReceiverConfigSecrets": { "description": "UpdateReceiverConfigSecrets defines secrets to be updated", "type": "object", @@ -7459,6 +7642,51 @@ } }, "x-go-package": "github.com/equinor/radix-api/api/alerting/models" + }, + "X509Certificate": { + "description": "X509Certificate holds information about a X509 certificate", + "type": "object", + "required": [ + "subject", + "issuer", + "notBefore", + "notAfter" + ], + "properties": { + "dnsNames": { + "description": "DNSNames defines list of Subject Alternate Names in the certificate", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "DNSNames" + }, + "issuer": { + "description": "Issuer contains the distinguished name for the certificate's issuer", + "type": "string", + "x-go-name": "Issuer", + "example": "CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US" + }, + "notAfter": { + "description": "NotAfter defines the uppdater date/time validity boundary", + "type": "string", + "format": "date-time", + "x-go-name": "NotAfter" + }, + "notBefore": { + "description": "NotBefore defines the lower date/time validity boundary", + "type": "string", + "format": "date-time", + "x-go-name": "NotBefore" + }, + "subject": { + "description": "Subject contains the distinguished name for the certificate", + "type": "string", + "x-go-name": "Subject", + "example": "CN=mysite.example.com,O=MyOrg,L=MyLocation,C=NO" + } + }, + "x-go-package": "github.com/equinor/radix-api/api/deployments/models" } }, "securityDefinitions": {