Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor models to support external DNS certificate automation #580

Merged
merged 19 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
37 changes: 0 additions & 37 deletions api/deployments/component_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 0 additions & 3 deletions api/deployments/deployment_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)

Expand Down
16 changes: 8 additions & 8 deletions api/deployments/models/component_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -45,6 +46,7 @@ type componentBuilder struct {
identity *Identity
notifications *Notifications
hpa *HorizontalScalingSummary
externalDNS []ExternalDNS
errors []error
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Expand Down
20 changes: 20 additions & 0 deletions api/deployments/models/component_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
106 changes: 106 additions & 0 deletions api/deployments/models/tls.go
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package models
package models_test

import (
"bytes"
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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},
Expand Down
Loading
Loading