diff --git a/api/v1alpha1/rpaasinstance_types.go b/api/v1alpha1/rpaasinstance_types.go index 48a7a2d4a..003ef6c9a 100644 --- a/api/v1alpha1/rpaasinstance_types.go +++ b/api/v1alpha1/rpaasinstance_types.go @@ -49,11 +49,9 @@ type RpaasInstanceSpec struct { // +optional DNS *DNSConfig `json:"dns,omitempty"` - // Certificates are a set of attributes that relate the certificate's - // location in the cluster (Secret resource name) and its destination into - // Pods. + // TLS configuration. // +optional - Certificates *nginxv1alpha1.TLSSecret `json:"certificates,omitempty"` + TLS []nginxv1alpha1.NginxTLS `json:"tls,omitempty"` // Service to expose the nginx instance // +optional diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 2a2d4172a..56c446215 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -468,10 +468,12 @@ func (in *RpaasInstanceSpec) DeepCopyInto(out *RpaasInstanceSpec) { *out = new(DNSConfig) (*in).DeepCopyInto(*out) } - if in.Certificates != nil { - in, out := &in.Certificates, &out.Certificates - *out = new(apiv1alpha1.TLSSecret) - (*in).DeepCopyInto(*out) + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = make([]apiv1alpha1.NginxTLS, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } if in.Service != nil { in, out := &in.Service, &out.Service diff --git a/config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml b/config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml index 795cf31b2..74257ff08 100644 --- a/config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml +++ b/config/crd/bases/extensions.tsuru.io_rpaasflavors.yaml @@ -139,49 +139,6 @@ spec: description: Blocks are configuration file fragments added to the generated nginx config. type: object - certificates: - description: Certificates are a set of attributes that relate the - certificate's location in the cluster (Secret resource name) and - its destination into Pods. - properties: - items: - description: Items maps the key and path where the certificate-key - pairs should be mounted on nginx container. - items: - description: TLSSecretItem maps each certificate and key pair - against a key-value data from a Secret object. - properties: - certificateField: - description: CertificateField is the field name that contains - the certificate. - type: string - certificatePath: - description: CertificatePath holds the path where the - certificate should be stored inside the nginx container. - Defaults to same as CertificatedField. - type: string - keyField: - description: KeyField is the field name that contains - the key. - type: string - keyPath: - description: KeyPath holds the path where the key should - be store inside the nginx container. Defaults to same - as KeyField. - type: string - required: - - certificateField - - keyField - type: object - type: array - secretName: - description: SecretName refers to the Secret holding the certificates - and keys pairs. - type: string - required: - - items - - secretName - type: object configHistoryLimit: description: The number of old Configs to retain to allow rollback. type: integer @@ -214,6 +171,10 @@ spec: items: type: string type: array + dnsNamesDefault: + description: DNSNamesDefault when is set use the provided + DNSName from DNS Zone field + type: boolean ipAddresses: description: IPAddresses is a list of IP addresses to be set in Subject Alternative Names. @@ -225,6 +186,8 @@ spec: resource. \n NOTE: when there's no Issuer on this name, it tries using ClusterIssuer instead." type: string + required: + - dnsNamesDefault type: object type: object extraFiles: @@ -4381,6 +4344,26 @@ spec: to true. type: boolean type: object + tls: + description: TLS configuration. + items: + properties: + hosts: + description: 'Hosts are a list of hosts included in the TLS + certificate. Defaults to the wildcard of hosts: "*".' + items: + type: string + type: array + secretName: + description: "SecretName is the name of the Secret which contains + the certificate-key pair. It must reside in the same Namespace + as the Nginx resource. \n NOTE: The Secret should follow + the Kubernetes TLS secrets type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets." + type: string + required: + - secretName + type: object + type: array tlsSessionResumption: description: TLSSessionResumption configures the instance to support session resumption using either session tickets or session ID diff --git a/config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml b/config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml index 6559ce15a..118769d33 100644 --- a/config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml +++ b/config/crd/bases/extensions.tsuru.io_rpaasinstances.yaml @@ -131,48 +131,6 @@ spec: description: Blocks are configuration file fragments added to the generated nginx config. type: object - certificates: - description: Certificates are a set of attributes that relate the certificate's - location in the cluster (Secret resource name) and its destination - into Pods. - properties: - items: - description: Items maps the key and path where the certificate-key - pairs should be mounted on nginx container. - items: - description: TLSSecretItem maps each certificate and key pair - against a key-value data from a Secret object. - properties: - certificateField: - description: CertificateField is the field name that contains - the certificate. - type: string - certificatePath: - description: CertificatePath holds the path where the certificate - should be stored inside the nginx container. Defaults to - same as CertificatedField. - type: string - keyField: - description: KeyField is the field name that contains the - key. - type: string - keyPath: - description: KeyPath holds the path where the key should be - store inside the nginx container. Defaults to same as KeyField. - type: string - required: - - certificateField - - keyField - type: object - type: array - secretName: - description: SecretName refers to the Secret holding the certificates - and keys pairs. - type: string - required: - - items - - secretName - type: object configHistoryLimit: description: The number of old Configs to retain to allow rollback. type: integer @@ -205,6 +163,10 @@ spec: items: type: string type: array + dnsNamesDefault: + description: DNSNamesDefault when is set use the provided DNSName + from DNS Zone field + type: boolean ipAddresses: description: IPAddresses is a list of IP addresses to be set in Subject Alternative Names. @@ -216,6 +178,8 @@ spec: resource. \n NOTE: when there's no Issuer on this name, it tries using ClusterIssuer instead." type: string + required: + - dnsNamesDefault type: object type: object extraFiles: @@ -4219,6 +4183,26 @@ spec: true. type: boolean type: object + tls: + description: TLS configuration. + items: + properties: + hosts: + description: 'Hosts are a list of hosts included in the TLS certificate. + Defaults to the wildcard of hosts: "*".' + items: + type: string + type: array + secretName: + description: "SecretName is the name of the Secret which contains + the certificate-key pair. It must reside in the same Namespace + as the Nginx resource. \n NOTE: The Secret should follow the + Kubernetes TLS secrets type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets." + type: string + required: + - secretName + type: object + type: array tlsSessionResumption: description: TLSSessionResumption configures the instance to support session resumption using either session tickets or session ID (in diff --git a/controllers/controller.go b/controllers/controller.go index 1c2f385c6..aa020c2b4 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -18,8 +18,6 @@ import ( "strings" "text/template" - "github.com/jetstack/cert-manager/pkg/util/pki" - "github.com/imdario/mergo" "github.com/sirupsen/logrus" nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" @@ -840,16 +838,10 @@ func (r *RpaasInstanceReconciler) renderTemplate(ctx context.Context, instance * return "", err } - fullCerts, err := r.getCertificates(ctx, instance.Namespace, instance.Spec.Certificates) - if err != nil { - return "", err - } - config := nginx.ConfigurationData{ - Instance: instance, - Config: &plan.Spec.Config, - FullCertificates: fullCerts, - Modules: make(map[string]interface{}), + Instance: instance, + Config: &plan.Spec.Config, + Modules: make(map[string]interface{}), } for _, mod := range modules { @@ -894,47 +886,6 @@ func (r *RpaasInstanceReconciler) getConfigurationBlocks(ctx context.Context, in return blocks, nil } -func (r *RpaasInstanceReconciler) getCertificates(ctx context.Context, namespace string, tlsSecret *nginxv1alpha1.TLSSecret) ([]nginx.CertificateData, error) { - if tlsSecret == nil || len(tlsSecret.Items) == 0 { - return nil, nil - } - cmName := types.NamespacedName{ - Name: tlsSecret.SecretName, - Namespace: namespace, - } - var secret corev1.Secret - if err := r.Client.Get(ctx, cmName, &secret); err != nil { - return nil, err - } - - var certsData []nginx.CertificateData - - for _, secretItem := range tlsSecret.Items { - if _, ok := secret.Data[secretItem.CertificateField]; !ok { - return nil, fmt.Errorf("certificate data not found") - } - if _, ok := secret.Data[secretItem.KeyField]; !ok { - return nil, fmt.Errorf("key data not found") - } - - certs, err := pki.DecodeX509CertificateChainBytes(secret.Data[secretItem.CertificateField]) - if err != nil { - return nil, err - } - - if len(certs) == 0 { - return nil, fmt.Errorf("no certificates found in pem file") - } - - certsData = append(certsData, nginx.CertificateData{ - Certificate: certs[0], - SecretItem: secretItem, - }) - } - - return certsData, nil -} - func (r *RpaasInstanceReconciler) updateLocationValues(ctx context.Context, instance *v1alpha1.RpaasInstance) error { for _, location := range instance.Spec.Locations { if location.Content == nil { @@ -1069,14 +1020,14 @@ func newNginx(instanceMergedWithFlavors *v1alpha1.RpaasInstance, plan *v1alpha1. Service: instanceMergedWithFlavors.Spec.Service.DeepCopy(), HealthcheckPath: "/_nginx_healthcheck", ExtraFiles: instanceMergedWithFlavors.Spec.ExtraFiles, - Certificates: instanceMergedWithFlavors.Spec.Certificates, + TLS: instanceMergedWithFlavors.Spec.TLS, Cache: cacheConfig, PodTemplate: instanceMergedWithFlavors.Spec.PodTemplate, Lifecycle: instanceMergedWithFlavors.Spec.Lifecycle, }, } - if n.Spec.Service.Type == "" { + if n.Spec.Service != nil && n.Spec.Service.Type == "" { n.Spec.Service.Type = corev1.ServiceTypeLoadBalancer } diff --git a/go.mod b/go.mod index 60e9961e5..a2c1a604b 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/hashicorp/go-multierror v1.1.0 github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5 github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.11 + github.com/imdario/mergo v0.3.12 github.com/jetstack/cert-manager v1.4.0 github.com/labstack/echo/v4 v4.1.17 github.com/mitchellh/mapstructure v1.1.2 @@ -26,24 +26,23 @@ require ( github.com/opentracing-contrib/go-stdlib v1.0.0 github.com/opentracing/opentracing-go v1.2.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.9.0 + github.com/prometheus/client_golang v1.11.0 github.com/sirupsen/logrus v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.6.1 - github.com/tsuru/nginx-operator v0.6.4 + github.com/stretchr/testify v1.7.0 + github.com/tsuru/nginx-operator v0.8.0 github.com/uber/jaeger-client-go v2.25.0+incompatible github.com/uber/jaeger-lib v2.4.0+incompatible // indirect github.com/urfave/cli/v2 v2.1.1 github.com/willf/bitset v1.1.11 - go.uber.org/multierr v1.6.0 // indirect - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d - k8s.io/api v0.21.0 - k8s.io/apimachinery v0.21.0 - k8s.io/client-go v0.21.0 + k8s.io/api v0.21.3 + k8s.io/apimachinery v0.21.3 + k8s.io/client-go v0.21.3 k8s.io/kubectl v0.21.0 k8s.io/metrics v0.21.0 - sigs.k8s.io/controller-runtime v0.9.0-beta.2 + sigs.k8s.io/controller-runtime v0.9.6 sigs.k8s.io/go-open-service-broker-client/v2 v2.0.0-20200925085050-ae25e62aaf10 ) diff --git a/go.sum b/go.sum index ac1d7fba9..46d55685a 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,8 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.34.30/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -182,8 +184,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.1.0 h1:B0aXl1o/1cP8NbviYiBMkcHBtUjIJ1/Ccg6b+SwCLQg= github.com/evanphx/json-patch/v5 v5.1.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= @@ -215,6 +218,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -333,8 +337,10 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= @@ -367,6 +373,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -393,8 +400,9 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.4 h1:ynbQIWjLw7iv6HAFdixb30U7Uvcmx+f4KlLJpmhkTK0= github.com/googleapis/gnostic v0.5.4/go.mod h1:TRWw1s4gxBGjSe301Dai3c7wXJAZy57+/6tawkOvqHQ= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -460,8 +468,6 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5 h1:6ZR6HQ+P9ZUwHlYq+bU7e9wqAImxKUguq8fp2gZSgCo= github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5/go.mod h1:Yho0S7KhsnHQRCC5lDraYF1SsLMeWtf/tKdufKu3TJA= -github.com/howeyc/fsnotify v0.9.0 h1:0gtV5JmOKH4A8SsFxG2BczSeXWWPvcMT0euZt5gDAxY= -github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -471,8 +477,9 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -490,8 +497,9 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -640,8 +648,9 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= -github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -650,8 +659,9 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= -github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= +github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= +github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -701,8 +711,9 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU= github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -717,8 +728,9 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -726,8 +738,9 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/quasilyte/go-ruleguard v0.2.0/go.mod h1:2RT/tf0Ce0UDj5y243iWKosQogJd8+1G3Rs2fxmlYnw= @@ -820,8 +833,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= @@ -832,10 +846,8 @@ github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiff github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= -github.com/tsuru/config v0.0.0-20200717192526-2a9a0efe5f28 h1:ZUM5bc/TSos0Vvts+klLmPBGWPp/n/WfF3lhrmn8u5g= -github.com/tsuru/config v0.0.0-20200717192526-2a9a0efe5f28/go.mod h1:mj6t8JKWU51GScTT50XRmDj65T5XhTyNvO5FUNV5zS4= -github.com/tsuru/nginx-operator v0.6.4 h1:ff6andU/pCDB328dYmBuvWs4tZnkISB3hMXbKPXbMao= -github.com/tsuru/nginx-operator v0.6.4/go.mod h1:SP0sLq8toh9QpevhnVBgqPBPntLx2ujI0VYu1xsiXL4= +github.com/tsuru/nginx-operator v0.8.0 h1:PLirMZ323Z5OuGcKGKBWqhCG/NelXL/NTU86dmKuHtY= +github.com/tsuru/nginx-operator v0.8.0/go.mod h1:0nwFM/IZA55wz4/DxpgbjUROnYTEEG225V+6rZsZU5Q= github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ= @@ -908,8 +920,9 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1008,8 +1021,9 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1024,6 +1038,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1087,10 +1102,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= @@ -1104,15 +1124,17 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1261,8 +1283,10 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= @@ -1316,28 +1340,34 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k= honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= -k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= +k8s.io/api v0.21.3 h1:cblWILbLO8ar+Fj6xdDGr603HRsf8Wu9E9rngJeprZQ= +k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= -k8s.io/apiextensions-apiserver v0.21.0 h1:Nd4uBuweg6ImzbxkC1W7xUNZcCV/8Vt10iTdTIVF3hw= k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc= +k8s.io/apiextensions-apiserver v0.21.3 h1:+B6biyUWpqt41kz5x6peIsljlsuwvNAp/oFax/j2/aY= +k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA= k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/apimachinery v0.21.3 h1:3Ju4nvjCngxxMYby0BimUk+pQHPOQp3eCGChk5kfVII= +k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= +k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU= k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= -k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= +k8s.io/client-go v0.21.3 h1:J9nxZTOmvkInRDCzcSNQmPJbDYN/PjlxXT9Mos3HcLg= +k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= -k8s.io/component-base v0.21.0 h1:tLLGp4BBjQaCpS/KiuWh7m2xqvAdsxLm4ATxHSe5Zpg= k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= +k8s.io/component-base v0.21.3 h1:4WuuXY3Npa+iFfi2aDRiOz+anhNvRfye0859ZgfC5Og= +k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -1362,8 +1392,9 @@ k8s.io/metrics v0.21.0 h1:uwS3CgheLKaw3PTpwhjMswnm/PMqeLbdLH88VI7FMQQ= k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210111153108-fddb29f9d009 h1:0T5IaWHO3sJTEmCP6mUlBvMukxPKUQWqiI/YuiBNMiQ= k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 h1:DnzUXII7sVg1FJ/4JX6YDRJfLNAC7idRatPwe07suiI= +k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= @@ -1375,8 +1406,10 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.9.0-beta.2 h1:T2sG4AGBWKRsUJyEeMRsIpAdn/1Tqk+3J7KSJB4pWPo= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.9.0-beta.2/go.mod h1:ufPDuvefw2Y1KnBgHQrLdOjueYlj+XJV2AszbT+WTxs= +sigs.k8s.io/controller-runtime v0.9.6 h1:EevVMlgUj4fC1NVM4+DB3iPkWkmGRNarA66neqv9Qew= +sigs.k8s.io/controller-runtime v0.9.6/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA= sigs.k8s.io/controller-tools v0.6.0-beta.0/go.mod h1:RAYVhbfeCcGzE/Nzeq+FbkUkiJLYnJ4fCnm7/HJWO/Q= sigs.k8s.io/go-open-service-broker-client/v2 v2.0.0-20200925085050-ae25e62aaf10 h1:KqneAZPWuSM8k+Dc+OwQny6lk4SBcI6J3PgpLiCjzus= sigs.k8s.io/go-open-service-broker-client/v2 v2.0.0-20200925085050-ae25e62aaf10/go.mod h1:TF5NsMVOxCw6ewZKcTTBvnE2SNEKfsFidBOEljuFRhA= @@ -1387,8 +1420,9 @@ sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4 sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= diff --git a/internal/controllers/certificates/auto_certificates.go b/internal/controllers/certificates/auto_certificates.go deleted file mode 100644 index 5ebd7f392..000000000 --- a/internal/controllers/certificates/auto_certificates.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2021 tsuru authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package certificates - -import ( - "context" - "fmt" - - nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/tsuru/rpaas-operator/api/v1alpha1" - "github.com/tsuru/rpaas-operator/pkg/util" -) - -const CertificatesSHA256HashLabel = "rpaas.extensions.tsuru.io/certificates-sha256-hash" - -func ReconcileDynamicCertificates(ctx context.Context, client client.Client, instance, instanceMergedWithFlavors *v1alpha1.RpaasInstance) error { - // NOTE: for now, we've only a way to obtain automatic certificates but it can - // be useful if we had more options in the future. - return reconcileCertManager(ctx, client, instance, instanceMergedWithFlavors) -} - -func UpdateCertificate(ctx context.Context, c client.Client, instance *v1alpha1.RpaasInstance, certificateName string, certData, keyData []byte) error { - if c == nil { - return fmt.Errorf("kubernetes client cannot be nil") - } - - if instance == nil { - return fmt.Errorf("rpaasinstance cannot be nil") - } - - var s corev1.Secret - err := c.Get(ctx, types.NamespacedName{ - Name: secretNameForCertificates(instance), - Namespace: instance.Namespace, - }, &s) - - if k8serrors.IsNotFound(err) { - s = *new(corev1.Secret) - newSecretForCertificates(instance).DeepCopyInto(&s) - - if err = c.Create(ctx, &s); err != nil { - return err - } - } - - if err != nil { - return err - } - - certLabel, keyLabel := fmt.Sprintf("%s.crt", certificateName), fmt.Sprintf("%s.key", certificateName) - - if s.Data == nil { - s.Data = make(map[string][]byte) - } - - s.Data[certLabel] = certData - s.Data[keyLabel] = keyData - - if err = c.Update(ctx, &s); err != nil { - return err - } - - if instance.Spec.Certificates == nil { - instance.Spec.Certificates = &nginxv1alpha1.TLSSecret{} - } - - if instance.Spec.Certificates.SecretName == "" { - instance.Spec.Certificates.SecretName = fmt.Sprintf("%s-certificates", instance.Name) - } - - if !hasCertificate(instance, certLabel) { - instance.Spec.Certificates.Items = append(instance.Spec.Certificates.Items, nginxv1alpha1.TLSSecretItem{ - CertificateField: certLabel, - KeyField: keyLabel, - }) - } - - if instance.Spec.PodTemplate.Annotations == nil { - instance.Spec.PodTemplate.Annotations = make(map[string]string) - } - - instance.Spec.PodTemplate.Annotations[CertificatesSHA256HashLabel] = util.SHA256(s.Data) - - return c.Update(ctx, instance) -} - -func DeleteCertificate(ctx context.Context, c client.Client, instance *v1alpha1.RpaasInstance, certificateName string) error { - if c == nil { - return fmt.Errorf("kubernetes client cannot be nil") - } - - if instance == nil { - return fmt.Errorf("rpaasinstance cannot be nil") - } - - if certificateName == "" { - return fmt.Errorf("certificate name cannot be empty") - } - - certLabel, keyLabel := fmt.Sprintf("%s.crt", certificateName), fmt.Sprintf("%s.key", certificateName) - - if !hasCertificate(instance, certLabel) { - return fmt.Errorf("certificate %q not found", certificateName) - } - - var s corev1.Secret - err := c.Get(ctx, types.NamespacedName{ - Name: secretNameForCertificates(instance), - Namespace: instance.Namespace, - }, &s) - - if err != nil { - return err - } - - delete(s.Data, certLabel) - delete(s.Data, keyLabel) - - if len(s.Data) == 0 { - instance.Spec.Certificates = nil - delete(instance.Spec.PodTemplate.Annotations, CertificatesSHA256HashLabel) - - if err = c.Update(ctx, instance); err != nil { - return err - } - - // NOTE: removing secret as last step since the order matters to avoid disruption - return c.Delete(ctx, &s) - } - - instance.Spec.PodTemplate.Annotations[CertificatesSHA256HashLabel] = util.SHA256(s.Data) - instance.Spec.Certificates.Items = removeCertificateFromItems(instance, certLabel) - - if err = c.Update(ctx, instance); err != nil { - return err - } - - // NOTE: updating secret as last step since the order matters to avoid disruption - return c.Update(ctx, &s) -} - -func secretNameForCertificates(instance *v1alpha1.RpaasInstance) string { - if instance.Spec.Certificates != nil { - return instance.Spec.Certificates.SecretName - } - - return fmt.Sprintf("%s-certificates", instance.Name) -} - -func newSecretForCertificates(instance *v1alpha1.RpaasInstance) *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretNameForCertificates(instance), - Namespace: instance.Namespace, - OwnerReferences: []metav1.OwnerReference{ - *metav1.NewControllerRef(instance, schema.GroupVersionKind{ - Group: v1alpha1.GroupVersion.Group, - Version: v1alpha1.GroupVersion.Version, - Kind: "RpaasInstance", - }), - }, - }, - } -} - -func hasCertificate(instance *v1alpha1.RpaasInstance, certLabel string) bool { - if instance == nil || instance.Spec.Certificates == nil { - return false - } - - for _, i := range instance.Spec.Certificates.Items { - if certLabel == i.CertificateField { - return true - } - } - - return false -} - -func removeCertificateFromItems(instance *v1alpha1.RpaasInstance, certLabel string) (items []nginxv1alpha1.TLSSecretItem) { - for _, item := range instance.Spec.Certificates.Items { - if item.CertificateField != certLabel { - items = append(items, item) - } - } - - return -} diff --git a/internal/controllers/certificates/cert_manager.go b/internal/controllers/certificates/cert_manager.go index 06bbacc73..f2a2b81bd 100644 --- a/internal/controllers/certificates/cert_manager.go +++ b/internal/controllers/certificates/cert_manager.go @@ -39,6 +39,7 @@ func reconcileCertManager(ctx context.Context, client client.Client, instance, i if err != nil { return err } + return client.Create(ctx, cert) } @@ -52,7 +53,11 @@ func reconcileCertManager(ctx context.Context, client client.Client, instance, i return err } - return reconcileCertificateSecret(ctx, client, instance, cert) + if !isCertificateReady(cert) { + return nil + } + + return UpdateCertificateFromSecret(ctx, client, instance, CertManagerCertificateName, newCert.Spec.SecretName) } func deleteCertManager(ctx context.Context, client client.Client, instance *v1alpha1.RpaasInstance) error { @@ -88,36 +93,12 @@ func deleteCertManager(ctx context.Context, client client.Client, instance *v1al return DeleteCertificate(ctx, client, instance, CertManagerCertificateName) } -func reconcileCertificateSecret(ctx context.Context, client client.Client, instance *v1alpha1.RpaasInstance, cert *cmv1.Certificate) error { - if !isCertificateReady(cert) { - return nil - } - - var s corev1.Secret - - err := client.Get(ctx, types.NamespacedName{ - Name: cert.Spec.SecretName, - Namespace: cert.Namespace, - }, &s) - - if err != nil { - return err - } - - var rawCert, rawKey []byte = s.Data["tls.crt"], s.Data["tls.key"] - - return UpdateCertificate(ctx, client, instance, CertManagerCertificateName, rawCert, rawKey) -} - func getCertificate(ctx context.Context, client client.Client, instance *v1alpha1.RpaasInstance) (*cmv1.Certificate, error) { var cert cmv1.Certificate - - err := client.Get(ctx, types.NamespacedName{ + return &cert, client.Get(ctx, types.NamespacedName{ Name: instance.Name, Namespace: instance.Namespace, }, &cert) - - return &cert, err } func newCertificate(instanceMergedWithFlavors *v1alpha1.RpaasInstance, issuer *cmmeta.ObjectReference) (*cmv1.Certificate, error) { diff --git a/internal/controllers/certificates/cert_manager_test.go b/internal/controllers/certificates/cert_manager_test.go index 0979f6822..f78b7a7d3 100644 --- a/internal/controllers/certificates/cert_manager_test.go +++ b/internal/controllers/certificates/cert_manager_test.go @@ -23,6 +23,7 @@ import ( nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" "github.com/tsuru/rpaas-operator/api/v1alpha1" "github.com/tsuru/rpaas-operator/pkg/runtime" + "github.com/tsuru/rpaas-operator/pkg/util" ) func Test_ReconcileCertManager(t *testing.T) { @@ -52,19 +53,12 @@ func Test_ReconcileCertManager(t *testing.T) { &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "my-instance-2-certificates", - Namespace: "rpaasv2", - }, - Data: map[string][]byte{ - "cert-manager.crt": []byte(`--- some cert here ---`), - "cert-manager.key": []byte(`--- some key here ---`), - }, - }, - - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-instance-2-cert-manager", + Name: "my-instance-2-abc123", Namespace: "rpaasv2", + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "cert-manager", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-2", + }, }, Data: map[string][]byte{ "tls.crt": []byte(`--- some cert here ---`), @@ -116,10 +110,35 @@ func Test_ReconcileCertManager(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "my-instance-3-cert-manager", Namespace: "rpaasv2", + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "cert-manager", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-3", + }, }, Data: map[string][]byte{ - "tls.crt": []byte(`--- some cert here ---`), - "tls.key": []byte(`--- some key here ---`), + // Generated with: + // go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -duration 8760h -host www.example.com,www.example.org,www.example.test -rsa-bits 512 + "tls.crt": []byte(`-----BEGIN CERTIFICATE----- +MIIBmDCCAUKgAwIBAgIQRr737j1vwFND83io4IliEzANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMB4XDTIxMDgzMDE4MTgxNVoXDTIyMDgzMDE4MTgx +NVowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC+ +0Zlmis2JigXdmCRKF+sZqBuVSPbpBsy4cP7eUBkcyxRir3jwPNoahd6Qv57Tr1vO +ZAj+hb5Rf75T7NgRzrQVAgMBAAGjdDByMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE +DDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMD0GA1UdEQQ2MDSCD3d3dy5leGFt +cGxlLmNvbYIPd3d3LmV4YW1wbGUub3JnghB3d3cuZXhhbXBsZS50ZXN0MA0GCSqG +SIb3DQEBCwUAA0EAc/GgmuRfov3QD+RAXcHYQKvmG9WxBRvOK8ALB+l4ibak0rS2 +RBUhFyKxlZEjXu5Fhv9PgYBzEA2AcWtiM7j8lA== +-----END CERTIFICATE-----`), + "tls.key": []byte(`-----BEGIN PRIVATE KEY----- +MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAvtGZZorNiYoF3Zgk +ShfrGagblUj26QbMuHD+3lAZHMsUYq948DzaGoXekL+e069bzmQI/oW+UX++U+zY +Ec60FQIDAQABAkB1W83f/lBpXgU7g54WH93NetH0H9sT+MWiToTCUDsRtFkOFpJf +ayKQpriEtcJjW1s/BIW5ldYYi4uJo9rHm+MFAiEA1xwbuJUm+JrlkfrDPsV9fb3p +02hr3cOuC9rVFYPfBzsCIQDjF2pEt0vNmFZLE/EwpiGQ+HB5d8UxCn8cfKqEB52c +7wIgYzTacBGRzJwbfmzJORz52FELEu4YuUky7tK47VhJNtsCIQCRRhhoby3iD1Mc +4lwIOC7+87/YJOOUFNfuHF5k6g5NJwIgYYt7B4pbCW5092Z5M2lDPvujEAr7quDI +wg4cGbIbBPs= +-----END PRIVATE KEY-----`), }, }, } @@ -277,17 +296,7 @@ func Test_ReconcileCertManager(t *testing.T) { Name: "my-instance-2", Namespace: "rpaasv2", }, - Spec: v1alpha1.RpaasInstanceSpec{ - Certificates: &nginxv1alpha1.TLSSecret{ - SecretName: "my-instance-2-certificates", - Items: []nginxv1alpha1.TLSSecretItem{ - { - CertificateField: "cert-manager.crt", - KeyField: "cert-manager.key", - }, - }, - }, - }, + Spec: v1alpha1.RpaasInstanceSpec{}, }, assert: func(t *testing.T, cli client.Client, instance *v1alpha1.RpaasInstance) { var cert cmv1.Certificate @@ -298,8 +307,8 @@ func Test_ReconcileCertManager(t *testing.T) { assert.Error(t, err) assert.True(t, k8serrors.IsNotFound(err)) - assert.Nil(t, instance.Spec.Certificates) - _, found := instance.Spec.PodTemplate.Annotations[CertificatesSHA256HashLabel] + assert.Nil(t, instance.Spec.TLS) + _, found := instance.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/cert-manager-certificate-sha256"] assert.False(t, found) var s corev1.Secret @@ -352,18 +361,11 @@ func Test_ReconcileCertManager(t *testing.T) { }, }, assert: func(t *testing.T, cli client.Client, instance *v1alpha1.RpaasInstance) { - assert.NotNil(t, instance.Spec.Certificates) - assert.Equal(t, &nginxv1alpha1.TLSSecret{ - SecretName: "my-instance-3-certificates", - Items: []nginxv1alpha1.TLSSecretItem{ - { - CertificateField: "cert-manager.crt", - KeyField: "cert-manager.key", - }, - }, - }, instance.Spec.Certificates) - require.NotNil(t, instance.Spec.PodTemplate.Annotations) - assert.Equal(t, "9a47733699de9ddcc177b90d1d170e927a4a9061c0880483db57c2999f0af84b", instance.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/certificates-sha256-hash"]) + assert.Equal(t, []nginxv1alpha1.NginxTLS{ + {SecretName: "my-instance-3-cert-manager", Hosts: []string{"www.example.com", "www.example.org", "www.example.test"}}, + }, instance.Spec.TLS) + assert.Equal(t, "a0610da4d1958cfa7c375870e2c1bac796e84f509bbd989fa5a7c0e040965f28", instance.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/cert-manager-certificate-sha256"]) + assert.Equal(t, "e644183deec75208c5fc53b4afb98e471ee290c7e7e10c5b95caff6851346132", instance.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/cert-manager-key-sha256"]) var cert cmv1.Certificate err := cli.Get(context.TODO(), types.NamespacedName{ @@ -374,14 +376,15 @@ func Test_ReconcileCertManager(t *testing.T) { var s corev1.Secret err = cli.Get(context.TODO(), types.NamespacedName{ - Name: instance.Spec.Certificates.SecretName, + Name: instance.Spec.TLS[0].SecretName, Namespace: instance.Namespace, }, &s) require.NoError(t, err) - assert.Equal(t, map[string][]byte{ - "cert-manager.crt": []byte(`--- some cert here ---`), - "cert-manager.key": []byte(`--- some key here ---`), - }, s.Data) + + assert.Equal(t, "my-instance-3", s.Labels["rpaas.extensions.tsuru.io/instance-name"]) + assert.Equal(t, "cert-manager", s.Labels["rpaas.extensions.tsuru.io/certificate-name"]) + assert.Equal(t, "a0610da4d1958cfa7c375870e2c1bac796e84f509bbd989fa5a7c0e040965f28", util.SHA256(s.Data["tls.crt"])) + assert.Equal(t, "e644183deec75208c5fc53b4afb98e471ee290c7e7e10c5b95caff6851346132", util.SHA256(s.Data["tls.key"])) }, }, } diff --git a/internal/controllers/certificates/certificates.go b/internal/controllers/certificates/certificates.go new file mode 100644 index 000000000..dfff82039 --- /dev/null +++ b/internal/controllers/certificates/certificates.go @@ -0,0 +1,258 @@ +// Copyright 2021 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package certificates + +import ( + "context" + "fmt" + "reflect" + + "github.com/jetstack/cert-manager/pkg/util/pki" + nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" + "github.com/tsuru/rpaas-operator/pkg/util" +) + +const CertificateNameLabel = "rpaas.extensions.tsuru.io/certificate-name" + +var ( + ErrTLSSecretNotFound = fmt.Errorf("TLS secret not found") + ErrTooManyTLSSecretsFound = fmt.Errorf("too many TLS secrets found") +) + +func UpdateCertificateFromSecret(ctx context.Context, c client.Client, instance *v1alpha1.RpaasInstance, certificateName, secretName string) error { + if c == nil { + return fmt.Errorf("kubernetes client cannot be nil") + } + + if instance == nil { + return fmt.Errorf("rpaasinstance cannot be nil") + } + + var s corev1.Secret + err := c.Get(ctx, types.NamespacedName{Name: secretName, Namespace: instance.Namespace}, &s) + if err != nil { + return err + } + + originalSecret := s.DeepCopy() + + if s.Labels == nil { + s.Labels = make(map[string]string) + } + + s.Labels[CertificateNameLabel] = certificateName + s.Labels["rpaas.extensions.tsuru.io/instance-name"] = instance.Name + + if !reflect.DeepEqual(originalSecret.Labels, s.Labels) { + if err = c.Update(ctx, &s); err != nil { + return err + } + } + + return updateInstanceWithCertificateInfos(ctx, c, instance, &s) +} + +func UpdateCertificate(ctx context.Context, c client.Client, instance *v1alpha1.RpaasInstance, certificateName string, certData, keyData []byte) error { + if c == nil { + return fmt.Errorf("kubernetes client cannot be nil") + } + + if instance == nil { + return fmt.Errorf("rpaasinstance cannot be nil") + } + + s, err := getTLSSecretByCertificateName(ctx, c, instance, certificateName) + switch err { + case ErrTLSSecretNotFound: + s = newSecretForCertificates(instance, certificateName, certData, keyData) + if err = c.Create(ctx, s); err != nil { + return err + } + + case nil: + originalSecret := s.DeepCopy() + + if s.Labels == nil { + s.Labels = make(map[string]string) + } + + s.Labels[CertificateNameLabel] = certificateName + s.Labels["rpaas.extensions.tsuru.io/instance-name"] = instance.Name + + s.Data = map[string][]byte{ + corev1.TLSCertKey: certData, + corev1.TLSPrivateKeyKey: keyData, + } + + if reflect.DeepEqual(s.Labels, originalSecret.Labels) && reflect.DeepEqual(s.Data, originalSecret.Data) { + break + } + + if err = c.Update(ctx, s); err != nil { + return err + } + + default: + return err + } + + return updateInstanceWithCertificateInfos(ctx, c, instance, s) + +} + +func DeleteCertificate(ctx context.Context, c client.Client, instance *v1alpha1.RpaasInstance, certificateName string) error { + if c == nil { + return fmt.Errorf("kubernetes client cannot be nil") + } + + if instance == nil { + return fmt.Errorf("rpaasinstance cannot be nil") + } + + if certificateName == "" { + return fmt.Errorf("certificate name cannot be empty") + } + + s, err := getTLSSecretByCertificateName(ctx, c, instance, certificateName) + if err != nil && err == ErrTLSSecretNotFound { + return fmt.Errorf("certificate %q does not exist", certificateName) + } + + if err != nil { + return err + } + + if index, found := findCertificate(instance, s.Name); found { + instance.Spec.TLS = append(instance.Spec.TLS[:index], instance.Spec.TLS[index+1:]...) // removes the i-th element + } + + delete(instance.Spec.PodTemplate.Annotations, fmt.Sprintf("rpaas.extensions.tsuru.io/%s-certificate-sha256", certificateName)) + delete(instance.Spec.PodTemplate.Annotations, fmt.Sprintf("rpaas.extensions.tsuru.io/%s-key-sha256", certificateName)) + + if err = c.Update(ctx, instance); err != nil { + return err + } + + return c.Delete(ctx, s) +} + +func updateInstanceWithCertificateInfos(ctx context.Context, c client.Client, i *v1alpha1.RpaasInstance, s *corev1.Secret) error { + hosts, err := extractDNSNames(s.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + original := i.DeepCopy() + + if index, found := findCertificate(i, s.Name); found { + i.Spec.TLS[index].Hosts = hosts + } else { + i.Spec.TLS = append(i.Spec.TLS, nginxv1alpha1.NginxTLS{ + SecretName: s.Name, + Hosts: hosts, + }) + } + + if i.Spec.PodTemplate.Annotations == nil { + i.Spec.PodTemplate.Annotations = make(map[string]string) + } + + certName := s.Labels[CertificateNameLabel] + + i.Spec.PodTemplate.Annotations[fmt.Sprintf("rpaas.extensions.tsuru.io/%s-certificate-sha256", certName)] = util.SHA256(s.Data[corev1.TLSCertKey]) + i.Spec.PodTemplate.Annotations[fmt.Sprintf("rpaas.extensions.tsuru.io/%s-key-sha256", certName)] = util.SHA256(s.Data[corev1.TLSPrivateKeyKey]) + + if reflect.DeepEqual(i.Spec.PodTemplate.Annotations, original.Spec.PodTemplate.Annotations) && reflect.DeepEqual(i.Spec.TLS, original.Spec.TLS) { + return nil + } + + return c.Update(ctx, i) +} + +func getTLSSecretByCertificateName(ctx context.Context, c client.Client, instance *v1alpha1.RpaasInstance, certName string) (*corev1.Secret, error) { + var sl corev1.SecretList + err := c.List(ctx, &sl, &client.ListOptions{ + LabelSelector: labels.Set{ + CertificateNameLabel: certName, + "rpaas.extensions.tsuru.io/instance-name": instance.Name, + }.AsSelector(), + Namespace: instance.Namespace, + }) + + if err != nil { + return nil, err + } + + switch len(sl.Items) { + case 0: + return nil, ErrTLSSecretNotFound + + case 1: + return &sl.Items[0], nil + + default: + return nil, ErrTooManyTLSSecretsFound + } +} + +func newSecretForCertificates(instance *v1alpha1.RpaasInstance, certName string, certData, keyData []byte) *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s-certs-", instance.Name), + Namespace: instance.Namespace, + OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(instance, schema.GroupVersionKind{ + Group: v1alpha1.GroupVersion.Group, + Version: v1alpha1.GroupVersion.Version, + Kind: "RpaasInstance", + }), + }, + Labels: map[string]string{ + CertificateNameLabel: certName, + "rpaas.extensions.tsuru.io/instance-name": instance.Name, + }, + }, + Type: corev1.SecretTypeTLS, + Data: map[string][]byte{ + corev1.TLSCertKey: certData, + corev1.TLSPrivateKeyKey: keyData, + }, + } +} + +func findCertificate(instance *v1alpha1.RpaasInstance, secretName string) (int, bool) { + for i, t := range instance.Spec.TLS { + if t.SecretName == secretName { + return i, true + } + } + + return -1, false +} + +func extractDNSNames(rawCert []byte) ([]string, error) { + certs, err := pki.DecodeX509CertificateChainBytes(rawCert) + if err != nil { + return nil, err + } + + // NOTE: following Nginx documentation, first certificate sent in the chain + // should be the leaf. + leaf := certs[0] + + return leaf.DNSNames, nil +} diff --git a/internal/controllers/certificates/certificates_test.go b/internal/controllers/certificates/certificates_test.go new file mode 100644 index 000000000..5965695fb --- /dev/null +++ b/internal/controllers/certificates/certificates_test.go @@ -0,0 +1,473 @@ +// Copyright 2021 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package certificates_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" + "github.com/tsuru/rpaas-operator/internal/controllers/certificates" + "github.com/tsuru/rpaas-operator/pkg/runtime" + "github.com/tsuru/rpaas-operator/pkg/util" +) + +func k8sResources() []k8sruntime.Object { + return []k8sruntime.Object{ + &v1alpha1.RpaasInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-1", + Namespace: "rpaasv2", + }, + }, + + &v1alpha1.RpaasInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-2", + Namespace: "rpaasv2", + }, + Spec: v1alpha1.RpaasInstanceSpec{ + PodTemplate: nginxv1alpha1.NginxPodTemplateSpec{ + Annotations: map[string]string{ + "rpaas.extensions.tsuru.io/www.example.com-certificate-sha256": "a08e4b2caf275287f20bc92d163362f2390b58ee0713cb611da3d0983bc10db4", + "rpaas.extensions.tsuru.io/www.example.com-key-sha256": "c8d681dd3ea46337426b74ee3da264c9de1d417ef2c8daef102e9ca257476bea", + }, + }, + + TLS: []nginxv1alpha1.NginxTLS{{ + SecretName: "my-instance-2-certs-abc123", + Hosts: []string{"www.example.com"}, + }}, + }, + }, + + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-2-certs-abc123", + Namespace: "rpaasv2", + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "www.example.com", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-2", + }, + }, + Type: corev1.SecretTypeTLS, + StringData: map[string]string{ + // Generated with: + // go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -duration 8760h -host www.example.com -ecdsa-curve P224 + "tls.crt": `-----BEGIN CERTIFICATE----- +MIIBXzCCAQ6gAwIBAgIQCcNGyPGPZIG5Ot7eTcSpxTAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTIxMDgzMTE1NDEwNVoXDTIyMDgzMTE1NDEwNVow +EjEQMA4GA1UEChMHQWNtZSBDbzBOMBAGByqGSM49AgEGBSuBBAAhAzoABP+p5p81 +Pl9QRjU0uL5fKzsiGw+GUkSDSR8P99rcJpAgvENF8dR2N8vHPXXhIhy/Disv2H+K +28mPo1EwTzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYD +VR0TAQH/BAIwADAaBgNVHREEEzARgg93d3cuZXhhbXBsZS5jb20wCgYIKoZIzj0E +AwIDPwAwPAIcZ1OoRZgyUn+M4lMTP2p9jgYhp8aMd/BZLxiKAQIcF7AdfXbn95Xp +DKfkqhV+7ygzNlcJyb5yYUfkgQ== +-----END CERTIFICATE-----`, + "tls.key": `-----BEGIN PRIVATE KEY----- +MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBBwiSUVHPwwhedjHltdoJi/M +BgepE84duC/2eeqqoTwDOgAE/6nmnzU+X1BGNTS4vl8rOyIbD4ZSRINJHw/32twm +kCC8Q0Xx1HY3y8c9deEiHL8OKy/Yf4rbyY8= +-----END PRIVATE KEY-----`, + }, + }, + + &v1alpha1.RpaasInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-3", + Namespace: "rpaasv2", + }, + Spec: v1alpha1.RpaasInstanceSpec{}, + }, + + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-3-cert-manager", + Namespace: "rpaasv2", + }, + Type: corev1.SecretTypeTLS, + Data: map[string][]byte{ + // Generated with: + // go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -duration 8760h -host *.example.com,*.example.org -ecdsa-curve P224 + "tls.crt": []byte(`-----BEGIN CERTIFICATE----- +MIIBbjCCARugAwIBAgIQWRF65AU1tWeZczAq3aqDjzAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTIxMDkwMzIxMTcyNFoXDTIyMDkwMzIxMTcyNFow +EjEQMA4GA1UEChMHQWNtZSBDbzBOMBAGByqGSM49AgEGBSuBBAAhAzoABN836A1F +Hku1le2ZgtgNZFsRr8ylOpzhQT5k8XQk9vlOSRqv40O0ku/AZoY9bsR9dmH6jDXR +CasIo14wXDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYD +VR0TAQH/BAIwADAnBgNVHREEIDAegg0qLmV4YW1wbGUuY29tgg0qLmV4YW1wbGUu +b3JnMAoGCCqGSM49BAMCA0EAMD4CHQDoVyjfDdtJ523kkNZA8ryt+H2ztUI8Vr8N +nneDAh0AqwHZyubwD3GE2eOTyC7DbmG9PbQA9g1Zeq8BVw== +-----END CERTIFICATE-----`), + "tls.key": []byte(`-----BEGIN PRIVATE KEY----- +MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBBwjAF6qPoiGd7BCj28Cg//t +cHPInBo/AIYDW+WuoTwDOgAE3zfoDUUeS7WV7ZmC2A1kWxGvzKU6nOFBPmTxdCT2 ++U5JGq/jQ7SS78Bmhj1uxH12YfqMNdEJqwg= +-----END PRIVATE KEY-----`), + }, + }, + } +} + +func TestUpdateCertificateFromSecret(t *testing.T) { + tests := map[string]struct { + instance string + certificateName string + secretName string + expectedError string + assert func(t *testing.T, c client.Client) + }{ + "adding a certificate from secret (e.g. cert manager)": { + instance: "my-instance-3", + certificateName: "cert-manager", + secretName: "my-instance-3-cert-manager", + assert: func(t *testing.T, c client.Client) { + var s corev1.Secret + err := c.Get(context.TODO(), types.NamespacedName{Name: "my-instance-3-cert-manager", Namespace: "rpaasv2"}, &s) + require.NoError(t, err) + + assert.Equal(t, "cert-manager", s.Labels["rpaas.extensions.tsuru.io/certificate-name"]) + assert.Equal(t, "my-instance-3", s.Labels["rpaas.extensions.tsuru.io/instance-name"]) + + var i v1alpha1.RpaasInstance + err = c.Get(context.TODO(), types.NamespacedName{Name: "my-instance-3", Namespace: "rpaasv2"}, &i) + assert.NoError(t, err) + + assert.Equal(t, []nginxv1alpha1.NginxTLS{{ + SecretName: "my-instance-3-cert-manager", + Hosts: []string{"*.example.com", "*.example.org"}, + }}, i.Spec.TLS) + assert.Equal(t, "b08063b4653a313c028d44e6e0ceebbc38efa961b2178a30d930c02da438c984", i.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/cert-manager-certificate-sha256"]) + assert.Equal(t, "c0a86dc8278f233cff1f90833b486db20b00b31879b8dec395f9e7f6ba556f8b", i.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/cert-manager-key-sha256"]) + }, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + resources := k8sResources() + + client := fake.NewClientBuilder(). + WithScheme(runtime.NewScheme()). + WithRuntimeObjects(resources...). + Build() + + var instance *v1alpha1.RpaasInstance + for _, object := range resources { + if i, found := object.(*v1alpha1.RpaasInstance); found && i.Name == tt.instance { + instance = i + break + } + } + + require.NotNil(t, instance, "you should select a RpaasInstance from resources") + + err := certificates.UpdateCertificateFromSecret(context.TODO(), client, instance, tt.certificateName, tt.secretName) + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + return + } + + require.NoError(t, err) + + require.NotNil(t, tt.assert, "you must provide an assert function") + tt.assert(t, client) + }) + } +} + +func TestUpdateCertificate(t *testing.T) { + tests := map[string]struct { + instance string + certificateName string + certificate string + certificateKey string + expectedError string + assert func(t *testing.T, c client.Client) + }{ + "first certificate in the instance, certificate with three SANs (www.example.com, www.example.org, www.example.test)": { + instance: "my-instance-1", + certificateName: "www.example.com", + // Generated with: + // go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -duration 8760h -host www.example.com,www.example.org,www.example.test -rsa-bits 512 + certificate: `-----BEGIN CERTIFICATE----- +MIIBmDCCAUKgAwIBAgIQRr737j1vwFND83io4IliEzANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMB4XDTIxMDgzMDE4MTgxNVoXDTIyMDgzMDE4MTgx +NVowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC+ +0Zlmis2JigXdmCRKF+sZqBuVSPbpBsy4cP7eUBkcyxRir3jwPNoahd6Qv57Tr1vO +ZAj+hb5Rf75T7NgRzrQVAgMBAAGjdDByMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE +DDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMD0GA1UdEQQ2MDSCD3d3dy5leGFt +cGxlLmNvbYIPd3d3LmV4YW1wbGUub3JnghB3d3cuZXhhbXBsZS50ZXN0MA0GCSqG +SIb3DQEBCwUAA0EAc/GgmuRfov3QD+RAXcHYQKvmG9WxBRvOK8ALB+l4ibak0rS2 +RBUhFyKxlZEjXu5Fhv9PgYBzEA2AcWtiM7j8lA== +-----END CERTIFICATE-----`, + certificateKey: `-----BEGIN PRIVATE KEY----- +MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAvtGZZorNiYoF3Zgk +ShfrGagblUj26QbMuHD+3lAZHMsUYq948DzaGoXekL+e069bzmQI/oW+UX++U+zY +Ec60FQIDAQABAkB1W83f/lBpXgU7g54WH93NetH0H9sT+MWiToTCUDsRtFkOFpJf +ayKQpriEtcJjW1s/BIW5ldYYi4uJo9rHm+MFAiEA1xwbuJUm+JrlkfrDPsV9fb3p +02hr3cOuC9rVFYPfBzsCIQDjF2pEt0vNmFZLE/EwpiGQ+HB5d8UxCn8cfKqEB52c +7wIgYzTacBGRzJwbfmzJORz52FELEu4YuUky7tK47VhJNtsCIQCRRhhoby3iD1Mc +4lwIOC7+87/YJOOUFNfuHF5k6g5NJwIgYYt7B4pbCW5092Z5M2lDPvujEAr7quDI +wg4cGbIbBPs= +-----END PRIVATE KEY-----`, + assert: func(t *testing.T, c client.Client) { + var sl corev1.SecretList + err := c.List(context.TODO(), &sl, &client.ListOptions{ + LabelSelector: labels.Set{ + certificates.CertificateNameLabel: "www.example.com", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-1", + }.AsSelector(), + Namespace: "rpaasv2", + }) + require.NoError(t, err) + require.Len(t, sl.Items, 1) + + s := sl.Items[0] + assert.Equal(t, corev1.SecretTypeTLS, s.Type) + assert.Equal(t, "my-instance-1-certs-", s.GenerateName) + assert.Equal(t, "rpaasv2", s.Namespace) + assert.Equal(t, map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "www.example.com", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-1", + }, s.Labels) + assert.Equal(t, "a0610da4d1958cfa7c375870e2c1bac796e84f509bbd989fa5a7c0e040965f28", util.SHA256(s.Data["tls.crt"])) + assert.Equal(t, "e644183deec75208c5fc53b4afb98e471ee290c7e7e10c5b95caff6851346132", util.SHA256(s.Data["tls.key"])) + + var i v1alpha1.RpaasInstance + err = c.Get(context.TODO(), types.NamespacedName{Name: "my-instance-1", Namespace: "rpaasv2"}, &i) + require.NoError(t, err) + + assert.Equal(t, []nginxv1alpha1.NginxTLS{{ + SecretName: s.Name, + Hosts: []string{"www.example.com", "www.example.org", "www.example.test"}, + }}, i.Spec.TLS) + + assert.Equal(t, "a0610da4d1958cfa7c375870e2c1bac796e84f509bbd989fa5a7c0e040965f28", i.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/www.example.com-certificate-sha256"]) + assert.Equal(t, "e644183deec75208c5fc53b4afb98e471ee290c7e7e10c5b95caff6851346132", i.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/www.example.com-key-sha256"]) + }, + }, + + "updating the \"www.example.com\" certificate": { + instance: "my-instance-2", + certificateName: "www.example.com", + // Generated with: + // go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -duration 8760h -host www.example.com,www.example.org,www.example.test -rsa-bits 512 + certificate: `-----BEGIN CERTIFICATE----- +MIIBmDCCAUKgAwIBAgIQRr737j1vwFND83io4IliEzANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMB4XDTIxMDgzMDE4MTgxNVoXDTIyMDgzMDE4MTgx +NVowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC+ +0Zlmis2JigXdmCRKF+sZqBuVSPbpBsy4cP7eUBkcyxRir3jwPNoahd6Qv57Tr1vO +ZAj+hb5Rf75T7NgRzrQVAgMBAAGjdDByMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE +DDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMD0GA1UdEQQ2MDSCD3d3dy5leGFt +cGxlLmNvbYIPd3d3LmV4YW1wbGUub3JnghB3d3cuZXhhbXBsZS50ZXN0MA0GCSqG +SIb3DQEBCwUAA0EAc/GgmuRfov3QD+RAXcHYQKvmG9WxBRvOK8ALB+l4ibak0rS2 +RBUhFyKxlZEjXu5Fhv9PgYBzEA2AcWtiM7j8lA== +-----END CERTIFICATE-----`, + certificateKey: `-----BEGIN PRIVATE KEY----- +MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAvtGZZorNiYoF3Zgk +ShfrGagblUj26QbMuHD+3lAZHMsUYq948DzaGoXekL+e069bzmQI/oW+UX++U+zY +Ec60FQIDAQABAkB1W83f/lBpXgU7g54WH93NetH0H9sT+MWiToTCUDsRtFkOFpJf +ayKQpriEtcJjW1s/BIW5ldYYi4uJo9rHm+MFAiEA1xwbuJUm+JrlkfrDPsV9fb3p +02hr3cOuC9rVFYPfBzsCIQDjF2pEt0vNmFZLE/EwpiGQ+HB5d8UxCn8cfKqEB52c +7wIgYzTacBGRzJwbfmzJORz52FELEu4YuUky7tK47VhJNtsCIQCRRhhoby3iD1Mc +4lwIOC7+87/YJOOUFNfuHF5k6g5NJwIgYYt7B4pbCW5092Z5M2lDPvujEAr7quDI +wg4cGbIbBPs= +-----END PRIVATE KEY-----`, + assert: func(t *testing.T, c client.Client) { + var s corev1.Secret + err := c.Get(context.TODO(), types.NamespacedName{Name: "my-instance-2-certs-abc123", Namespace: "rpaasv2"}, &s) + require.NoError(t, err) + + assert.Equal(t, corev1.SecretTypeTLS, s.Type) + assert.Equal(t, map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "www.example.com", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-2", + }, s.Labels) + assert.Equal(t, "a0610da4d1958cfa7c375870e2c1bac796e84f509bbd989fa5a7c0e040965f28", util.SHA256(s.Data["tls.crt"])) + assert.Equal(t, "e644183deec75208c5fc53b4afb98e471ee290c7e7e10c5b95caff6851346132", util.SHA256(s.Data["tls.key"])) + + var i v1alpha1.RpaasInstance + err = c.Get(context.TODO(), types.NamespacedName{Name: "my-instance-2", Namespace: "rpaasv2"}, &i) + require.NoError(t, err) + + assert.Equal(t, []nginxv1alpha1.NginxTLS{{ + SecretName: s.Name, + Hosts: []string{"www.example.com", "www.example.org", "www.example.test"}, + }}, i.Spec.TLS) + + assert.Equal(t, "a0610da4d1958cfa7c375870e2c1bac796e84f509bbd989fa5a7c0e040965f28", i.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/www.example.com-certificate-sha256"]) + assert.Equal(t, "e644183deec75208c5fc53b4afb98e471ee290c7e7e10c5b95caff6851346132", i.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/www.example.com-key-sha256"]) + }, + }, + + "adding a certificate in an instance with one certificate": { + instance: "my-instance-2", + certificateName: "awesome-certificate", + // Generated with: + // go run $(go env GOROOT)/src/crypto/tls/generate_cert.go -duration 8760h -host blog.example.com -ecdsa-curve P224 + certificate: `-----BEGIN CERTIFICATE----- +MIIBYDCCAQ+gAwIBAgIQOaQWls1CqA8ciV6cYr9swjAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTIxMDgzMTE4MDMxOVoXDTIyMDgzMTE4MDMxOVow +EjEQMA4GA1UEChMHQWNtZSBDbzBOMBAGByqGSM49AgEGBSuBBAAhAzoABHFZ1zJu +0x2/U1sYX15TL0MfSWzmXKKUlXwU9bt0DlwMbdpTSgHXxmGV+6tPVqW28aC5ONxS +Xxz4o1IwUDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYD +VR0TAQH/BAIwADAbBgNVHREEFDASghBibG9nLmV4YW1wbGUuY29tMAoGCCqGSM49 +BAMCAz8AMDwCHGqk7iRuI9eW9zfMYIMHjGV91jLLGWYU3xdsG0wCHEX+4hlRsM2g +1F7296rcT8cY9dQgmGTuzAsAOMs= +-----END CERTIFICATE-----`, + certificateKey: `-----BEGIN PRIVATE KEY----- +MHgCAQAwEAYHKoZIzj0CAQYFK4EEACEEYTBfAgEBBByQ+TRztjzo6ThtOha8AH+p +XHLzN4TUmsCHIywioTwDOgAEcVnXMm7THb9TWxhfXlMvQx9JbOZcopSVfBT1u3QO +XAxt2lNKAdfGYZX7q09WpbbxoLk43FJfHPg= +-----END PRIVATE KEY-----`, + assert: func(t *testing.T, c client.Client) { + var sl corev1.SecretList + err := c.List(context.TODO(), &sl, &client.ListOptions{ + LabelSelector: labels.Set{ + certificates.CertificateNameLabel: "awesome-certificate", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-2", + }.AsSelector(), + Namespace: "rpaasv2", + }) + require.NoError(t, err) + require.Len(t, sl.Items, 1) + + s := sl.Items[0] + + var i v1alpha1.RpaasInstance + err = c.Get(context.TODO(), types.NamespacedName{Name: "my-instance-2", Namespace: "rpaasv2"}, &i) + require.NoError(t, err) + + assert.Equal(t, []nginxv1alpha1.NginxTLS{ + {SecretName: "my-instance-2-certs-abc123", Hosts: []string{"www.example.com"}}, + {SecretName: s.Name, Hosts: []string{"blog.example.com"}}, + }, i.Spec.TLS) + }, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + resources := k8sResources() + + client := fake.NewClientBuilder(). + WithScheme(runtime.NewScheme()). + WithRuntimeObjects(resources...). + Build() + + var instance *v1alpha1.RpaasInstance + for _, object := range resources { + if i, found := object.(*v1alpha1.RpaasInstance); found && i.Name == tt.instance { + instance = i + break + } + } + + require.NotNil(t, instance, "you should select a RpaasInstance from resources") + + err := certificates.UpdateCertificate(context.TODO(), client, instance, tt.certificateName, []byte(tt.certificate), []byte(tt.certificateKey)) + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + return + } + + require.NoError(t, err) + + require.NotNil(t, tt.assert, "you must provide an assert function") + tt.assert(t, client) + }) + } +} + +func Test_DeleteCertificate(t *testing.T) { + tests := map[string]struct { + instance string + certificateName string + expectedError string + assert func(t *testing.T, c client.Client) + }{ + "when certificate name is not provided": { + instance: "my-instance-1", + expectedError: "certificate name cannot be empty", + }, + + "when certificate does not exist": { + instance: "my-instance-1", + certificateName: "not-found-certificates", + expectedError: "certificate \"not-found-certificates\" does not exist", + }, + + "removing a certificate successfully ": { + instance: "my-instance-2", + certificateName: "www.example.com", + assert: func(t *testing.T, c client.Client) { + var sl corev1.SecretList + err := c.List(context.TODO(), &sl, &client.ListOptions{ + LabelSelector: labels.Set{ + certificates.CertificateNameLabel: "www.example.com", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-2", + }.AsSelector(), + Namespace: "rpaasv2", + }) + require.NoError(t, err) + assert.Len(t, sl.Items, 0) + + var i v1alpha1.RpaasInstance + err = c.Get(context.TODO(), types.NamespacedName{Name: "my-instance-2", Namespace: "rpaasv2"}, &i) + require.NoError(t, err) + + assert.Len(t, i.Spec.TLS, 0) + _, found := i.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/www.example.com-certificate-sha256"] + assert.False(t, found) + _, found = i.Spec.PodTemplate.Annotations["rpaas.extensions.tsuru.io/www.example.com-key-sha256"] + assert.False(t, found) + }, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + resources := k8sResources() + + client := fake.NewClientBuilder(). + WithScheme(runtime.NewScheme()). + WithRuntimeObjects(resources...). + Build() + + var instance *v1alpha1.RpaasInstance + for _, object := range resources { + if i, found := object.(*v1alpha1.RpaasInstance); found && i.Name == tt.instance { + instance = i + break + } + } + + require.NotNil(t, instance, "you should select a RpaasInstance from resources") + + err := certificates.DeleteCertificate(context.TODO(), client, instance, tt.certificateName) + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + return + } + + require.NoError(t, err) + + require.NotNil(t, tt.assert, "you must provide an assert function") + tt.assert(t, client) + }) + } +} diff --git a/internal/controllers/certificates/dynamic_certificates.go b/internal/controllers/certificates/dynamic_certificates.go new file mode 100644 index 000000000..2b428dc36 --- /dev/null +++ b/internal/controllers/certificates/dynamic_certificates.go @@ -0,0 +1,19 @@ +// Copyright 2021 tsuru authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package certificates + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/tsuru/rpaas-operator/api/v1alpha1" +) + +func ReconcileDynamicCertificates(ctx context.Context, client client.Client, instance, instanceMergedWithFlavors *v1alpha1.RpaasInstance) error { + // NOTE: for now, we've only a way to obtain automatic certificates but it can + // be useful if we had more options in the future. + return reconcileCertManager(ctx, client, instance, instanceMergedWithFlavors) +} diff --git a/internal/pkg/rpaas/k8s.go b/internal/pkg/rpaas/k8s.go index 5355329ba..3f5c782c8 100644 --- a/internal/pkg/rpaas/k8s.go +++ b/internal/pkg/rpaas/k8s.go @@ -525,34 +525,18 @@ func (m *k8sRpaasManager) GetCertificates(ctx context.Context, instanceName stri return nil, err } - if instance.Spec.Certificates == nil { - return nil, nil - } - - var secret corev1.Secret - err = m.cli.Get(ctx, types.NamespacedName{ - Name: instance.Spec.Certificates.SecretName, - Namespace: instance.Namespace, - }, &secret) - if err != nil { - return nil, err - } - var certList []CertificateData - for _, item := range instance.Spec.Certificates.Items { - if _, ok := secret.Data[item.CertificateField]; !ok { - return nil, fmt.Errorf("certificate data not found") - } - if _, ok := secret.Data[item.KeyField]; !ok { - return nil, fmt.Errorf("key data not found") - } - certItem := CertificateData{ - Name: strings.TrimSuffix(item.CertificateField, ".crt"), - Certificate: string(secret.Data[item.CertificateField]), - Key: string(secret.Data[item.KeyField]), + for _, tls := range instance.Spec.TLS { + var s corev1.Secret + if err = m.cli.Get(ctx, types.NamespacedName{Name: tls.SecretName, Namespace: instance.Namespace}, &s); err != nil { + return nil, err } - certList = append(certList, certItem) + certList = append(certList, CertificateData{ + Name: s.Labels[certificates.CertificateNameLabel], + Certificate: string(s.Data[corev1.TLSCertKey]), + Key: string(s.Data[corev1.TLSPrivateKeyKey]), + }) } return certList, nil @@ -564,14 +548,14 @@ func (m *k8sRpaasManager) DeleteCertificate(ctx context.Context, instanceName, n return err } - if instance.Spec.Certificates == nil { + if len(instance.Spec.TLS) == 0 { return &NotFoundError{Msg: fmt.Sprintf("no certificate bound to instance %q", instanceName)} } name = certificateName(name) err = certificates.DeleteCertificate(ctx, m.cli, instance, name) - if err != nil && err == fmt.Errorf("certificate %q not found", name) { + if err != nil && err == fmt.Errorf("certificate %q does not exist", name) { return &NotFoundError{Msg: err.Error()} } @@ -597,23 +581,24 @@ func (m *k8sRpaasManager) UpdateCertificate(ctx context.Context, instanceName, n if err != nil { return err } + certsInfo, err := m.getCertificatesInfo(ctx, instance) if err != nil { return err } - certs, err := pki.DecodeX509CertificateChainBytes(rawCertificate) + + leaf, err := x509.ParseCertificate(c.Certificate[0]) if err != nil { return err } - if len(certs) > 0 { - for _, certInfo := range certsInfo { - if certInfo.Name == name { - continue - } - if hasIntersection(certInfo.DNSNames, certs[0].DNSNames) { - return &ValidationError{Msg: fmt.Sprintf("certificate DNS name is forbidden: you cannot use a already used dns name, currently in use use in %q certificate", certInfo.Name)} - } + for _, ci := range certsInfo { + if ci.Name == name { + continue + } + + if hasIntersection(ci.DNSNames, leaf.DNSNames) { + return &ValidationError{Msg: fmt.Sprintf("certificate DNS name is forbidden: you cannot use a already used dns name, currently in use use in %q certificate", ci.Name)} } } @@ -1252,10 +1237,12 @@ func getRawCertificateAndKey(c tls.Certificate) ([]byte, []byte, error) { if err != nil { return []byte{}, []byte{}, err } + keyPem, err := convertPrivateKeyToPem(c.PrivateKey) if err != nil { return []byte{}, []byte{}, err } + return certificatePem, keyPem, err } @@ -1471,27 +1458,6 @@ func (m *k8sRpaasManager) eventsForObject(ctx context.Context, namespace, kind s return events, nil } -func newSecretForCertificates(instance v1alpha1.RpaasInstance, data map[string][]byte) *corev1.Secret { - hash := util.SHA256(data) - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-certificates-%s", instance.Name, hash[:10]), - Namespace: instance.Namespace, - OwnerReferences: []metav1.OwnerReference{ - *metav1.NewControllerRef(&instance, schema.GroupVersionKind{ - Group: v1alpha1.GroupVersion.Group, - Version: v1alpha1.GroupVersion.Version, - Kind: "RpaasInstance", - }), - }, - Annotations: map[string]string{ - "rpaas.extensions.tsuru.io/sha256-hash": hash, - }, - }, - Data: data, - } -} - func formatPodEvents(events []corev1.Event) string { var statuses []string for _, evt := range events { diff --git a/internal/pkg/rpaas/k8s_test.go b/internal/pkg/rpaas/k8s_test.go index 6917bffe6..d3edc634b 100644 --- a/internal/pkg/rpaas/k8s_test.go +++ b/internal/pkg/rpaas/k8s_test.go @@ -29,6 +29,7 @@ import ( "github.com/tsuru/rpaas-operator/api/v1alpha1" "github.com/tsuru/rpaas-operator/internal/config" + "github.com/tsuru/rpaas-operator/internal/controllers/certificates" nginxManager "github.com/tsuru/rpaas-operator/internal/pkg/rpaas/nginx" clientTypes "github.com/tsuru/rpaas-operator/pkg/rpaas/client/types" rpaasruntime "github.com/tsuru/rpaas-operator/pkg/runtime" @@ -324,8 +325,29 @@ func Test_k8sRpaasManager_UpdateBlock(t *testing.T) { } func Test_k8sRpaasManager_GetCertificates(t *testing.T) { - scheme := newScheme() - rsaCertPem := `-----BEGIN CERTIFICATE----- + resources := []runtime.Object{ + &v1alpha1.RpaasInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-1", + Namespace: "rpaasv2", + }, + Spec: v1alpha1.RpaasInstanceSpec{ + TLS: []nginxv1alpha1.NginxTLS{ + {SecretName: "my-instance-certs-abc123"}, + }, + }, + }, + + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-certs-abc123", + Namespace: "rpaasv2", + Labels: map[string]string{ + certificates.CertificateNameLabel: "default", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(`-----BEGIN CERTIFICATE----- MIIB9TCCAV6gAwIBAgIRAIpoagB8BUn8x36iyvafmC0wDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xOTAzMjYyMDIxMzlaFw0yMDAzMjUyMDIx MzlaMBIxEDAOBgNVBAoTB0FjbWUgQ28wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ @@ -337,10 +359,8 @@ MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOBgQCaF9zDYoPh 4KmqxFI3KB+cl8Z/0y0txxH4vqlnByBBiCLpPzivcCRFlT1bGPVJOLsyd/BdOset yTcvMUPbnEPXZMR4Dsbzzjco1JxMSvZgkhm85gAlwNGjFZrMXqO8G5R/gpWN3UUc 7likRQOu7q61DlicQAZXRnOh6BbKaq1clg== ------END CERTIFICATE----- -` - - rsaKeyPem := `-----BEGIN RSA PRIVATE KEY----- +-----END CERTIFICATE-----`), + "tls.key": []byte(`-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDiLDPS4R6iN6AYRwwhmZBioIiO9oIZy6+VKWgNyPVO56fyz87f yaVkRuMIxeVWq9iETxqDcC3HcmxVtGE6r8NcDWPPKvQXKcEuT3le7lsmalmn3Usc b3mhulXgh2BTKqKTiq4uJCeLGQfVkt8TJRmVyusxz8AOMT7blRDQ/iyNewIDAQAB @@ -354,115 +374,68 @@ XyTvqOO95nJx0DjkdM26QojJlSueMTitJisCQDuxNfWku0dTGqrz4uo8p5v16gdj chxTez6BxMi3zHR6uEgL5Yv/yfnOldoq1RK1XaChNix+QnLBy2ZZbLkd6P8tEtsd WE9pct0+193ace/J7fECQQDAhwHBpJjhM+k97D92akneKXIUBo+Egr5E5qF9/g5I sM5FaDCEIJVbWjPDluxUGbVOQlFHsJs+pZv0Anf9DPwU ------END RSA PRIVATE KEY----- -` - - // rsaCertificate - rsaCertificate, err := tls.X509KeyPair([]byte(rsaCertPem), []byte(rsaKeyPem)) - require.NoError(t, err) - - instance1 := newEmptyRpaasInstance() - - instance2 := newEmptyRpaasInstance() - instance2.Name = "another-instance" - instance2.Spec.Certificates = &nginxv1alpha1.TLSSecret{ - SecretName: "another-instance-certificates", - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt", KeyField: "default.key"}, - }, - } - - instance3 := newEmptyRpaasInstance() - instance3.Name = "no-certificate" - - instance4 := newEmptyRpaasInstance() - instance4.Name = "no-cert-data" - instance4.Spec.Certificates = &nginxv1alpha1.TLSSecret{ - SecretName: "no-cert-secret", - Items: []nginxv1alpha1.TLSSecretItem{ - {KeyField: "default.key"}, +-----END RSA PRIVATE KEY-----`), + }, }, - } - instance5 := newEmptyRpaasInstance() - instance5.Name = "no-key-data" - instance5.Spec.Certificates = &nginxv1alpha1.TLSSecret{ - SecretName: "no-key-secret", - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt"}, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-certs-abc456", + Namespace: "rpaasv2", + Labels: map[string]string{ + certificates.CertificateNameLabel: "www.example.com", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(`-----BEGIN CERTIFICATE----- +MIIB9TCCAV6gAwIBAgIRAIpoagB8BUn8x36iyvafmC0wDQYJKoZIhvcNAQELBQAw +EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xOTAzMjYyMDIxMzlaFw0yMDAzMjUyMDIx +MzlaMBIxEDAOBgNVBAoTB0FjbWUgQ28wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ +AoGBAOIsM9LhHqI3oBhHDCGZkGKgiI72ghnLr5UpaA3I9U7np/LPzt/JpWRG4wjF +5Var2IRPGoNwLcdybFW0YTqvw1wNY88q9BcpwS5PeV7uWyZqWafdSxxveaG6VeCH +YFMqopOKri4kJ4sZB9WS3xMlGZXK6zHPwA4xPtuVEND+LI17AgMBAAGjSzBJMA4G +A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAA +MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOBgQCaF9zDYoPh +4KmqxFI3KB+cl8Z/0y0txxH4vqlnByBBiCLpPzivcCRFlT1bGPVJOLsyd/BdOset +yTcvMUPbnEPXZMR4Dsbzzjco1JxMSvZgkhm85gAlwNGjFZrMXqO8G5R/gpWN3UUc +7likRQOu7q61DlicQAZXRnOh6BbKaq1clg== +-----END CERTIFICATE-----`), + "tls.key": []byte(`-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDiLDPS4R6iN6AYRwwhmZBioIiO9oIZy6+VKWgNyPVO56fyz87f +yaVkRuMIxeVWq9iETxqDcC3HcmxVtGE6r8NcDWPPKvQXKcEuT3le7lsmalmn3Usc +b3mhulXgh2BTKqKTiq4uJCeLGQfVkt8TJRmVyusxz8AOMT7blRDQ/iyNewIDAQAB +AoGBAI05gJqayyALj8HZCzAnzUpoZxytvAsTbm27TyfcZaCBchNhwxFlvgphYP5n +Y468+xOSuUF9WHiDcDYLzfJxMZAqmuS+D/IREYDkcrGVT1MXfSCkNaFVqG52+hLZ +GmGsy8+KsJnDJ1HYmwfSnaTj3L8+Bf2Hg291Yb1caRH9+5vBAkEA7P5N3cSN73Fa +HwaWzqkaY75mCR4TpRi27YWGA3wdQek2G71HiSbCOxrWOymvgoNRi6M/sdrP5PTt +JAFxC+pd8QJBAPRPvS0Tm/0lMIZ0q7jxyoW/gKDzokmSszopdlvSU53lN06vaYdK +XyTvqOO95nJx0DjkdM26QojJlSueMTitJisCQDuxNfWku0dTGqrz4uo8p5v16gdj +3vjXh8O9vOqFyWy/i9Ri0XDXJVbzxH/0WPObld+BB9sJTRHTKyPFhS7GIlECQDZ8 +chxTez6BxMi3zHR6uEgL5Yv/yfnOldoq1RK1XaChNix+QnLBy2ZZbLkd6P8tEtsd +WE9pct0+193ace/J7fECQQDAhwHBpJjhM+k97D92akneKXIUBo+Egr5E5qF9/g5I +sM5FaDCEIJVbWjPDluxUGbVOQlFHsJs+pZv0Anf9DPwU +-----END RSA PRIVATE KEY-----`), + }, }, } - secret := newEmptySecret() - secret.Name = "another-instance-certificates" - secret.Data = map[string][]byte{ - "default.crt": []byte(rsaCertPem), - "default.key": []byte(rsaKeyPem), - } - - secret2 := newEmptySecret() - secret2.Name = "no-cert-secret" - secret2.Data = map[string][]byte{ - "default.key": []byte(rsaKeyPem), - } - - secret3 := newEmptySecret() - secret3.Name = "no-key-secret" - secret3.Data = map[string][]byte{ - "default.crt": []byte(rsaCertPem), - } - - resources := []runtime.Object{instance1, instance2, instance3, instance4, instance5, secret, secret2, secret3} - - testCases := []struct { - name string - certificate tls.Certificate - instanceName string - assertion func(*testing.T, error, *k8sRpaasManager, []CertificateData) + tests := map[string]struct { + name string + instanceName string + expectedError string + expected []CertificateData }{ - { - name: "instance not found", - instanceName: "instance-not-found", - assertion: func(t *testing.T, err error, m *k8sRpaasManager, certData []CertificateData) { - assert.Error(t, err) - assert.True(t, IsNotFoundError(err)) - }, + "instance not found": { + instanceName: "instance-not-found", + expectedError: "rpaas instance \"instance-not-found\" not found", }, - { - name: "no certificates bound to the instance", - instanceName: "no-certificate", - assertion: func(t *testing.T, err error, m *k8sRpaasManager, certData []CertificateData) { - assert.NoError(t, err) - assert.Nil(t, certData) - }, - }, - { - name: "certificate-data-not-found-test", - instanceName: "no-cert-data", - assertion: func(t *testing.T, err error, m *k8sRpaasManager, certData []CertificateData) { - assert.Error(t, err) - assert.Equal(t, "certificate data not found", err.Error()) - }, - }, - { - name: "key-data-not-found-test", - instanceName: "no-key-data", - assertion: func(t *testing.T, err error, m *k8sRpaasManager, certData []CertificateData) { - assert.Error(t, err) - assert.Equal(t, "key data not found", err.Error()) - }, - }, - { - name: "getting an existing certificate", - instanceName: "another-instance", - certificate: rsaCertificate, - assertion: func(t *testing.T, err error, m *k8sRpaasManager, certData []CertificateData) { - require.NoError(t, err) - expectedCertList := []CertificateData{ - { - Name: "default", - Certificate: `-----BEGIN CERTIFICATE----- + "getting an existing certificate": { + instanceName: "my-instance-1", + expected: []CertificateData{ + { + Name: "default", + Certificate: `-----BEGIN CERTIFICATE----- MIIB9TCCAV6gAwIBAgIRAIpoagB8BUn8x36iyvafmC0wDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xOTAzMjYyMDIxMzlaFw0yMDAzMjUyMDIx MzlaMBIxEDAOBgNVBAoTB0FjbWUgQ28wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ @@ -474,9 +447,8 @@ MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOBgQCaF9zDYoPh 4KmqxFI3KB+cl8Z/0y0txxH4vqlnByBBiCLpPzivcCRFlT1bGPVJOLsyd/BdOset yTcvMUPbnEPXZMR4Dsbzzjco1JxMSvZgkhm85gAlwNGjFZrMXqO8G5R/gpWN3UUc 7likRQOu7q61DlicQAZXRnOh6BbKaq1clg== ------END CERTIFICATE----- -`, - Key: `-----BEGIN RSA PRIVATE KEY----- +-----END CERTIFICATE-----`, + Key: `-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDiLDPS4R6iN6AYRwwhmZBioIiO9oIZy6+VKWgNyPVO56fyz87f yaVkRuMIxeVWq9iETxqDcC3HcmxVtGE6r8NcDWPPKvQXKcEuT3le7lsmalmn3Usc b3mhulXgh2BTKqKTiq4uJCeLGQfVkt8TJRmVyusxz8AOMT7blRDQ/iyNewIDAQAB @@ -490,27 +462,31 @@ XyTvqOO95nJx0DjkdM26QojJlSueMTitJisCQDuxNfWku0dTGqrz4uo8p5v16gdj chxTez6BxMi3zHR6uEgL5Yv/yfnOldoq1RK1XaChNix+QnLBy2ZZbLkd6P8tEtsd WE9pct0+193ace/J7fECQQDAhwHBpJjhM+k97D92akneKXIUBo+Egr5E5qF9/g5I sM5FaDCEIJVbWjPDluxUGbVOQlFHsJs+pZv0Anf9DPwU ------END RSA PRIVATE KEY----- -`, - }, - } - - assert.Equal(t, expectedCertList, certData) +-----END RSA PRIVATE KEY-----`, + }, }, }, } - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - manager := &k8sRpaasManager{cli: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(resources...).Build()} - certData, err := manager.GetCertificates(context.Background(), tt.instanceName) - tt.assertion(t, err, manager, certData) + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + client := fake.NewClientBuilder(). + WithScheme(newScheme()). + WithRuntimeObjects(resources...). + Build() + + got, err := (&k8sRpaasManager{cli: client}).GetCertificates(context.TODO(), tt.instanceName) + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + return + } + + assert.Equal(t, tt.expected, got) }) } - } func Test_k8sRpaasManager_DeleteCertificate(t *testing.T) { - scheme := newScheme() rsaCertPem := `-----BEGIN CERTIFICATE----- MIIB9TCCAV6gAwIBAgIRAIpoagB8BUn8x36iyvafmC0wDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0xOTAzMjYyMDIxMzlaFw0yMDAzMjUyMDIx @@ -523,8 +499,7 @@ MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOBgQCaF9zDYoPh 4KmqxFI3KB+cl8Z/0y0txxH4vqlnByBBiCLpPzivcCRFlT1bGPVJOLsyd/BdOset yTcvMUPbnEPXZMR4Dsbzzjco1JxMSvZgkhm85gAlwNGjFZrMXqO8G5R/gpWN3UUc 7likRQOu7q61DlicQAZXRnOh6BbKaq1clg== ------END CERTIFICATE----- -` +-----END CERTIFICATE-----` rsaKeyPem := `-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDiLDPS4R6iN6AYRwwhmZBioIiO9oIZy6+VKWgNyPVO56fyz87f @@ -540,119 +515,140 @@ XyTvqOO95nJx0DjkdM26QojJlSueMTitJisCQDuxNfWku0dTGqrz4uo8p5v16gdj chxTez6BxMi3zHR6uEgL5Yv/yfnOldoq1RK1XaChNix+QnLBy2ZZbLkd6P8tEtsd WE9pct0+193ace/J7fECQQDAhwHBpJjhM+k97D92akneKXIUBo+Egr5E5qF9/g5I sM5FaDCEIJVbWjPDluxUGbVOQlFHsJs+pZv0Anf9DPwU ------END RSA PRIVATE KEY----- -` +-----END RSA PRIVATE KEY-----` ecdsaCertPem := `-----BEGIN CERTIFICATE----- JUNDACERTJUNDACERT ------END CERTIFICATE----- -` +-----END CERTIFICATE-----` ecdsaKeyPem := `-----BEGIN EC PRIVATE KEY----- JUNDAKEYJUNDAKEYJUNDAKEY ------END EC PRIVATE KEY----- -` +-----END EC PRIVATE KEY-----` instance1 := newEmptyRpaasInstance() instance2 := newEmptyRpaasInstance() instance2.Name = "another-instance" - instance2.Spec.Certificates = &nginxv1alpha1.TLSSecret{ - SecretName: "another-instance-certificates", - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt", KeyField: "default.key"}, - {CertificateField: "junda.crt", KeyField: "junda.key"}, + instance2.Spec.TLS = []nginxv1alpha1.NginxTLS{ + {SecretName: "another-instance-certs-01"}, + {SecretName: "another-instance-certs-02"}, + } + + secret1 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "another-instance-certs-01", + Namespace: instance2.Namespace, + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "default", + "rpaas.extensions.tsuru.io/instance-name": "another-instance", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(rsaCertPem), + "tls.key": []byte(rsaKeyPem), + }, + } + + secret2 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "another-instance-certs-02", + Namespace: instance2.Namespace, + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "junda", + "rpaas.extensions.tsuru.io/instance-name": "another-instance", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(ecdsaCertPem), + "tls.key": []byte(ecdsaKeyPem), }, } instance3 := newEmptyRpaasInstance() instance3.Name = "no-spec-cert" - instance3.Spec.Certificates = nil instance4 := newEmptyRpaasInstance() instance4.Name = "one-cert" - instance4.Spec.Certificates = &nginxv1alpha1.TLSSecret{ - SecretName: "one-cert-secret", - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt", KeyField: "default.key"}, - }, + instance4.Spec.TLS = []nginxv1alpha1.NginxTLS{ + {SecretName: "one-cert-secret-certs-02"}, } - secret := newEmptySecret() - secret.Name = "another-instance-certificates" - secret.Data = map[string][]byte{ - "default.crt": []byte(rsaCertPem), - "default.key": []byte(rsaKeyPem), - "junda.crt": []byte(ecdsaCertPem), - "junda.key": []byte(ecdsaKeyPem), - } - - secret2 := newEmptySecret() - secret2.Name = "one-cert-secret" - secret2.Data = map[string][]byte{ - "default.crt": []byte(rsaCertPem), - "default.key": []byte(rsaKeyPem), + secret3 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "one-cert-secret-certs-02", + Namespace: instance4.Namespace, + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "default", + "rpaas.extensions.tsuru.io/instance-name": "one-cert", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(rsaCertPem), + "tls.key": []byte(rsaKeyPem), + }, } - resources := []runtime.Object{instance1, instance2, instance3, instance4, secret, secret2} + resources := []runtime.Object{instance1, instance2, instance3, instance4, secret1, secret2, secret3} - testCases := []struct { - name string - certName string - instanceName string - assertion func(*testing.T, error, *k8sRpaasManager) + tests := map[string]struct { + certName string + instanceName string + expectedError string + assert func(t *testing.T, c client.Client) }{ - { - name: "instance not found", - instanceName: "instance-not-found", - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - assert.Error(t, err) - assert.True(t, IsNotFoundError(err)) - }, + "instance not found": { + instanceName: "instance-not-found", + expectedError: "rpaas instance \"instance-not-found\" not found", }, - { - name: "instance without certificate", - instanceName: "no-spec-cert", - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - assert.Error(t, err) - assert.True(t, IsNotFoundError(err)) - assert.Equal(t, "no certificate bound to instance \"no-spec-cert\"", err.Error()) - }, + + "instance without certificates": { + instanceName: "no-spec-cert", + expectedError: "no certificate bound to instance \"no-spec-cert\"", }, - { - name: "instance with only one, default certificate", + + "instance with only one, default certificate": { instanceName: "one-cert", - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - require.NoError(t, err) - instance := v1alpha1.RpaasInstance{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: "one-cert", - Namespace: getServiceName(), - }, &instance) + assert: func(t *testing.T, c client.Client) { + var instance v1alpha1.RpaasInstance + err := c.Get(context.TODO(), types.NamespacedName{Name: "one-cert", Namespace: getServiceName()}, &instance) require.NoError(t, err) + assert.Nil(t, instance.Spec.TLS) + }, + }, - assert.Nil(t, instance.Spec.Certificates) - - secret := corev1.Secret{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: secret2.Name, - Namespace: getServiceName(), - }, &secret) - require.Error(t, err) + "instance with two certificates, should remain one": { + instanceName: "another-instance", + certName: "junda", + assert: func(t *testing.T, c client.Client) { + var instance v1alpha1.RpaasInstance + err := c.Get(context.TODO(), types.NamespacedName{Name: "another-instance", Namespace: getServiceName()}, &instance) + require.NoError(t, err) + assert.Equal(t, []nginxv1alpha1.NginxTLS{{SecretName: "another-instance-certs-01"}}, instance.Spec.TLS) }, }, } - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - manager := &k8sRpaasManager{cli: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(resources...).Build()} - err := manager.DeleteCertificate(context.Background(), tt.instanceName, tt.certName) - tt.assertion(t, err, manager) + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + client := fake.NewClientBuilder(). + WithScheme(newScheme()). + WithRuntimeObjects(resources...). + Build() + + err := (&k8sRpaasManager{cli: client}).DeleteCertificate(context.TODO(), tt.instanceName, tt.certName) + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + return + } + + require.NoError(t, err) + require.NotNil(t, tt.assert, "you should pass an assert func") + tt.assert(t, client) }) } } func Test_k8sRpaasManager_UpdateCertificate(t *testing.T) { - scheme := newScheme() ecdsaCertPem := `-----BEGIN CERTIFICATE----- MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow @@ -663,15 +659,13 @@ BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc 6MF9+Yw1Yy0t ------END CERTIFICATE----- -` +-----END CERTIFICATE-----` ecdsaKeyPem := `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== ------END EC PRIVATE KEY----- -` +-----END EC PRIVATE KEY-----` ecdsaCertificate, err := tls.X509KeyPair([]byte(ecdsaCertPem), []byte(ecdsaKeyPem)) require.NoError(t, err) @@ -701,8 +695,7 @@ cbfqOhKusQonR7bcCyKphrAQkG/sjXJ6HcBj6WVQhVhrxhu939SWaJ3a6s35DHc+ p8/zWWtbEat9jrFT83ej8GB5RbyIHRRncHQ51ymM/bAW/F7G74mPPHVfK0Y1sNdY ix3plWG7WNMHkxHT9IuU8/ieycCJp0jshm9obbM7MCMp3WrZmfUYq2cbuZiD0Upy xbFwana3DCXVZv8lJl4vPiGRV2wK ------END CERTIFICATE----- -` +-----END CERTIFICATE-----` rsaKeyPem := `-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAtcS7IjwPdyx/l6ac/TAoJAEBBc7NGo8/kSmMMFvVR3m3kJVf @@ -730,251 +723,152 @@ dyH2GqT8zdO6hdnR1bG9eAfd2gtA33pF1CA7l817hIAeEriEfXUv29d3Z0dO9RnT ofTG1/ECgYEA3IHIZS3zEMgKmMAAHme6UvxJxQOiz+CUAKQUUTB+3YSBT3NOtonV sR2uspXuam+kC900f+vXJPVcNI4rtoSelYIbmdGt4Pn/TqUKGk1qRrs7paLI8Iw0 x2cJyBkkBQV9WB34oGtnZzQ0nKtzsY6FVlNGSeyCJ3OD2dHXO5komJY= ------END RSA PRIVATE KEY----- -` +-----END RSA PRIVATE KEY-----` rsaCertificate, err := tls.X509KeyPair([]byte(rsaCertPem), []byte(rsaKeyPem)) require.NoError(t, err) - instance1 := newEmptyRpaasInstance() - - instance2 := newEmptyRpaasInstance() - instance2.Name = "another-instance" - instance2.Spec.Certificates = &nginxv1alpha1.TLSSecret{ - SecretName: "another-instance-certificates", - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt", KeyField: "default.key"}, + resources := []runtime.Object{ + &v1alpha1.RpaasInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-1", + Namespace: "rpaasv2", + }, + }, + &v1alpha1.RpaasInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-2", + Namespace: "rpaasv2", + }, + Spec: v1alpha1.RpaasInstanceSpec{ + TLS: []nginxv1alpha1.NginxTLS{ + {SecretName: "my-instance-2-certs-abc123", Hosts: []string{"rpaas-operator.io"}}, + }, + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-instance-2-certs-abc123", + Namespace: "rpaasv2", + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "default", + "rpaas.extensions.tsuru.io/instance-name": "my-instance-2", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(rsaCertPem), + "tls.key": []byte(rsaKeyPem), + }, }, } - secret := newEmptySecret() - secret.Name = "another-instance-certificates" - secret.Data = map[string][]byte{ - "default.crt": []byte(rsaCertPem), - "default.key": []byte(rsaKeyPem), - } - - resources := []runtime.Object{instance1, instance2, secret} - - testCases := []struct { - name string + tests := map[string]struct { instanceName string certificateName string certificate tls.Certificate - assertion func(*testing.T, error, *k8sRpaasManager) + expectedError string + assert func(t *testing.T, c client.Client) }{ - { - name: "instance not found", - instanceName: "instance-not-found", - certificate: ecdsaCertificate, - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - assert.Error(t, err) - assert.True(t, IsNotFoundError(err)) - }, + "instance not found": { + instanceName: "instance-not-found", + certificate: ecdsaCertificate, + expectedError: "rpaas instance \"instance-not-found\" not found", }, - { - name: "adding a new certificate without name, should use default name \"default\"", - instanceName: "my-instance", - certificate: rsaCertificate, - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - require.NoError(t, err) - instance := v1alpha1.RpaasInstance{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: "my-instance", - Namespace: getServiceName(), - }, &instance) - require.NoError(t, err) - - assert.NotNil(t, instance.Spec.Certificates) - assert.NotEmpty(t, instance.Spec.Certificates.SecretName) - - expectedCertificates := &nginxv1alpha1.TLSSecret{ - SecretName: instance.Spec.Certificates.SecretName, - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt", KeyField: "default.key"}, - }, - } - assert.Equal(t, expectedCertificates, instance.Spec.Certificates) - - secret := corev1.Secret{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: instance.Spec.Certificates.SecretName, - Namespace: getServiceName(), - }, &secret) + "adding a new certificate without name, should use default name \"default\"": { + instanceName: "my-instance-1", + certificate: rsaCertificate, + assert: func(t *testing.T, c client.Client) { + var instance v1alpha1.RpaasInstance + err := c.Get(context.TODO(), types.NamespacedName{Name: "my-instance-1", Namespace: "rpaasv2"}, &instance) require.NoError(t, err) - require.Len(t, secret.Data, 2) - assert.Equal(t, rsaCertPem, string(secret.Data["default.crt"])) - assert.Equal(t, rsaKeyPem, string(secret.Data["default.key"])) - + require.Len(t, instance.Spec.TLS, 1) + tls := instance.Spec.TLS[0] + assert.Regexp(t, `^my-instance-1-certs-`, tls.SecretName) + assert.Equal(t, []string{"rpaas-operator.io"}, tls.Hosts) }, }, - { - name: "adding a new certificate with duplicated dnsname", - instanceName: "another-instance", + + "adding a new certificate with duplicated DNS name": { + instanceName: "my-instance-2", certificateName: "lets-duplicate", certificate: rsaCertificate, - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - assert.EqualError(t, err, `certificate DNS name is forbidden: you cannot use a already used dns name, currently in use use in "default" certificate`) + expectedError: `certificate DNS name is forbidden: you cannot use a already used dns name, currently in use use in "default" certificate`, + }, + + /* + "adding a new certificate with a custom name": { + instanceName: "my-instance", + certificateName: "custom-name", + certificate: ecdsaCertificate, + assert: func(t *testing.T, c client.Client) { + var instance v1alpha1.RpaasInstance + err := c.Get(context.TODO(), types.NamespacedName{Name: "my-instance", Namespace: getServiceName()}, &instance) + require.NoError(t, err) + assert.NotNil(t, instance.Spec.TLS) + }, }, - }, - { - name: "adding a new certificate with a custom name", - instanceName: "my-instance", - certificateName: "custom-name", - certificate: ecdsaCertificate, - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - require.NoError(t, err) - - instance := v1alpha1.RpaasInstance{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: "my-instance", - Namespace: getServiceName(), - }, &instance) - require.NoError(t, err) - - assert.NotNil(t, instance.Spec.Certificates) - assert.NotEmpty(t, instance.Spec.Certificates.SecretName) - - expectedCertificates := &nginxv1alpha1.TLSSecret{ - SecretName: instance.Spec.Certificates.SecretName, - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "custom-name.crt", KeyField: "custom-name.key"}, - }, - } - assert.Equal(t, expectedCertificates, instance.Spec.Certificates) - - secret := corev1.Secret{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: instance.Spec.Certificates.SecretName, - Namespace: getServiceName(), - }, &secret) - require.NoError(t, err) - - expectedSecretData := map[string][]byte{ - "custom-name.crt": []byte(ecdsaCertPem), - "custom-name.key": []byte(ecdsaKeyPem), - } - assert.Equal(t, expectedSecretData, secret.Data) + "updating an existing certificate from RSA to ECDSA": { + instanceName: "another-instance", + certificate: ecdsaCertificate, + assert: func(t *testing.T, c client.Client) { + var instance v1alpha1.RpaasInstance + err := c.Get(context.Background(), types.NamespacedName{Name: "another-instance", Namespace: getServiceName()}, &instance) + require.NoError(t, err) + }, }, - }, - { - name: "updating an existing certificate from RSA to ECDSA", - instanceName: "another-instance", - certificate: ecdsaCertificate, - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - require.NoError(t, err) - - instance := v1alpha1.RpaasInstance{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: "another-instance", - Namespace: getServiceName(), - }, &instance) - require.NoError(t, err) - assert.NotNil(t, instance.Spec.Certificates) - assert.NotEmpty(t, instance.Spec.Certificates.SecretName) - - expectedCertificates := &nginxv1alpha1.TLSSecret{ - SecretName: instance.Spec.Certificates.SecretName, - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt", KeyField: "default.key"}, - }, - } - assert.Equal(t, expectedCertificates, instance.Spec.Certificates) - - secret := corev1.Secret{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: instance.Spec.Certificates.SecretName, - Namespace: getServiceName(), - }, &secret) - require.NoError(t, err) - - expectedSecretData := map[string][]byte{ - "default.crt": []byte(ecdsaCertPem), - "default.key": []byte(ecdsaKeyPem), - } - assert.Equal(t, expectedSecretData, secret.Data) + "adding multiple certificates": { + instanceName: "another-instance", + certificateName: "custom-name", + certificate: ecdsaCertificate, + assert: func(t *testing.T, c client.Client) { + var instance v1alpha1.RpaasInstance + err := c.Get(context.Background(), types.NamespacedName{Name: "another-instance", Namespace: getServiceName()}, &instance) + require.NoError(t, err) + assert.NotNil(t, instance.Spec.TLS) + }, }, - }, - { - name: "adding multiple certificates", - instanceName: "another-instance", - certificateName: "custom-name", - certificate: ecdsaCertificate, - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - require.NoError(t, err) - - instance := v1alpha1.RpaasInstance{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: "another-instance", - Namespace: getServiceName(), - }, &instance) - require.NoError(t, err) - assert.NotNil(t, instance.Spec.Certificates) - assert.NotEmpty(t, instance.Spec.Certificates.SecretName) - - expectedCertificates := &nginxv1alpha1.TLSSecret{ - SecretName: instance.Spec.Certificates.SecretName, - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt", KeyField: "default.key"}, - {CertificateField: "custom-name.crt", KeyField: "custom-name.key"}, - }, - } - assert.Equal(t, expectedCertificates, instance.Spec.Certificates) - secret := corev1.Secret{} - err = m.cli.Get(context.Background(), types.NamespacedName{ - Name: instance.Spec.Certificates.SecretName, - Namespace: getServiceName(), - }, &secret) - require.NoError(t, err) - - expectedSecretData := map[string][]byte{ - "default.crt": []byte(rsaCertPem), - "default.key": []byte(rsaKeyPem), - "custom-name.crt": []byte(ecdsaCertPem), - "custom-name.key": []byte(ecdsaKeyPem), - } - assert.Equal(t, expectedSecretData, secret.Data) - }, - }, - { - name: "updating to the same certificate, should do nothing", - instanceName: "another-instance", - certificate: rsaCertificate, - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - assert.NoError(t, err) + "updating to the same certificate, should do nothing": { + instanceName: "another-instance", + certificate: rsaCertificate, + expectedError: "ffooooo", }, - }, - { - name: "invalid certificate name", - instanceName: "my-instance", - certificate: ecdsaCertificate, - certificateName: `../not@valid.config_map~key`, - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - assert.EqualError(t, err, `certificate name is not valid: a valid config key must consist of alphanumeric characters, '-', '_' or '.' (e.g. 'key.name', or 'KEY_NAME', or 'key-name', regex used for validation is '[-._a-zA-Z0-9]+')`) + + "invalid certificate name": { + instanceName: "my-instance", + certificate: ecdsaCertificate, + certificateName: `../not@valid.config_map~key`, + expectedError: `certificate name is not valid: a valid config key must consist of alphanumeric characters, '-', '_' or '.' (e.g. 'key.name', or 'KEY_NAME', or 'key-name', regex used for validation is '[-._a-zA-Z0-9]+')`, }, - }, - { - name: `setting certificate with name "cert-manager"`, - instanceName: "my-instance", - certificate: ecdsaCertificate, - certificateName: "cert-manager", - assertion: func(t *testing.T, err error, m *k8sRpaasManager) { - assert.EqualError(t, err, `certificate name is forbidden: you cannot use a certificate named as "cert-manager"`) + `setting certificate with name "cert-manager"`: { + instanceName: "my-instance", + certificate: ecdsaCertificate, + certificateName: "cert-manager", + expectedError: `certificate name is forbidden: you cannot use a certificate named as "cert-manager"`, }, - }, + */ } - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - manager := &k8sRpaasManager{cli: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(resources...).Build()} - err := manager.UpdateCertificate(context.Background(), tt.instanceName, tt.certificateName, tt.certificate) - tt.assertion(t, err, manager) + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + client := fake.NewClientBuilder(). + WithScheme(newScheme()). + WithRuntimeObjects(resources...). + Build() + + err := (&k8sRpaasManager{cli: client}).UpdateCertificate(context.TODO(), tt.instanceName, tt.certificateName, tt.certificate) + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + return + } + + assert.NoError(t, err) + tt.assert(t, client) }) } } @@ -1006,19 +900,6 @@ func newEmptyExtraFiles() *corev1.ConfigMap { } } -func newEmptySecret() *corev1.Secret { - return &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-secrets", - Namespace: getServiceName(), - }, - } -} - func newEmptyLocations() *corev1.ConfigMap { return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ @@ -4194,20 +4075,41 @@ func Test_k8sRpaasManager_GetInstanceInfo(t *testing.T) { ForceHTTPS: true, }, } - instance3.Spec.Certificates = &nginxv1alpha1.TLSSecret{ - Items: []nginxv1alpha1.TLSSecretItem{ - {CertificateField: "default.crt", KeyField: "default.key"}, - {CertificateField: "instance3.example.com.crt", KeyField: "instance3.example.com.key"}, + + instance3.Spec.TLS = []nginxv1alpha1.NginxTLS{ + {SecretName: "instance3-certs-01"}, + {SecretName: "instance3-certs-02"}, + } + + s1 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "instance3-certs-01", + Namespace: instance3.Namespace, + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "default", + "rpaas.extensions.tsuru.io/instance-name": "instance3", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(rsaCertificateInPEM), + "tls.key": []byte(rsaPrivateKeyInPEM), }, } - s1 := newSecretForCertificates(*instance3, map[string][]byte{ - "default.crt": []byte(rsaCertificateInPEM), - "default.key": []byte(rsaPrivateKeyInPEM), - "instance3.example.com.crt": []byte(ecdsaCertificateInPEM), - "instance3.example.com.key": []byte(ecdsaPrivateKeyInPEM), - }) - instance3.Spec.Certificates.SecretName = s1.Name + s2 := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "instance3-certs-02", + Namespace: instance3.Namespace, + Labels: map[string]string{ + "rpaas.extensions.tsuru.io/certificate-name": "instance3.example.com", + "rpaas.extensions.tsuru.io/instance-name": "instance3", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(ecdsaCertificateInPEM), + "tls.key": []byte(ecdsaPrivateKeyInPEM), + }, + } instance4 := instance1.DeepCopy() instance4.Name = "instance4" @@ -4492,10 +4394,10 @@ func Test_k8sRpaasManager_GetInstanceInfo(t *testing.T) { pod1, pod2, pod2Metrics, event1, event2, event3, event4, - s1, + s1, s2, } - testCases := []struct { + tests := []struct { instance string expected clientTypes.InstanceInfo }{ @@ -4688,15 +4590,15 @@ func Test_k8sRpaasManager_GetInstanceInfo(t *testing.T) { }, } - for _, tt := range testCases { + for _, tt := range tests { t.Run(tt.instance, func(t *testing.T) { - fakeCli := fake.NewClientBuilder().WithScheme(newScheme()).WithRuntimeObjects(resources...).Build() - manager := &k8sRpaasManager{ - cli: fakeCli, - } - got, err := manager.GetInstanceInfo(context.Background(), tt.instance) + client := fake.NewClientBuilder(). + WithScheme(newScheme()). + WithRuntimeObjects(resources...). + Build() + + got, err := (&k8sRpaasManager{cli: client}).GetInstanceInfo(context.Background(), tt.instance) require.NoError(t, err) - require.NotNil(t, got) assert.Equal(t, tt.expected, *got) }) } diff --git a/internal/pkg/rpaas/nginx/configuration_render.go b/internal/pkg/rpaas/nginx/configuration_render.go index 937d02624..4ac517b44 100644 --- a/internal/pkg/rpaas/nginx/configuration_render.go +++ b/internal/pkg/rpaas/nginx/configuration_render.go @@ -6,7 +6,6 @@ package nginx import ( "bytes" - "crypto/x509" "fmt" "regexp" "strconv" @@ -15,9 +14,10 @@ import ( sprig "github.com/Masterminds/sprig/v3" nginxv1alpha1 "github.com/tsuru/nginx-operator/api/v1alpha1" + "k8s.io/apimachinery/pkg/api/resource" + "github.com/tsuru/rpaas-operator/api/v1alpha1" "github.com/tsuru/rpaas-operator/pkg/util" - "k8s.io/apimachinery/pkg/api/resource" ) var trimTrailingSpacesRegex = regexp.MustCompile(`[ \t]+?\n`) @@ -40,13 +40,7 @@ type ConfigurationData struct { Instance *v1alpha1.RpaasInstance // Modules is a map of installed modules, using a map instead of a slice // allow us to use `hasKey` inside templates. - Modules map[string]interface{} - FullCertificates []CertificateData -} - -type CertificateData struct { - Certificate *x509.Certificate - SecretItem nginxv1alpha1.TLSSecretItem + Modules map[string]interface{} } type rpaasConfigurationRenderer struct { @@ -201,30 +195,18 @@ func tlsSessionTicketTimeout(instance *v1alpha1.RpaasInstance) int { return nkeys * int(keyRotationInterval) } -type nginxServer struct { - Default bool - Certificate *x509.Certificate - SecretItem nginxv1alpha1.TLSSecretItem -} - -func nginxServers(c ConfigurationData) []nginxServer { - if len(c.FullCertificates) == 0 { - return []nginxServer{ - {Certificate: nil, Default: true}, - } +func defaultCertificate(instance *v1alpha1.RpaasInstance) *nginxv1alpha1.NginxTLS { + if len(instance.Spec.TLS) == 0 { + return nil } - servers := []nginxServer{} - - for i, cert := range c.FullCertificates { - servers = append(servers, nginxServer{ - Default: i == 0, - Certificate: cert.Certificate, - SecretItem: cert.SecretItem, - }) + for _, tls := range instance.Spec.TLS { + if len(tls.Hosts) == 0 { + return &tls + } } - return servers + return &instance.Spec.TLS[0] } var internalTemplateFuncs = template.FuncMap(map[string]interface{}{ @@ -245,7 +227,7 @@ var internalTemplateFuncs = template.FuncMap(map[string]interface{}{ "tlsSessionTicketEnabled": tlsSessionTicketEnabled, "tlsSessionTicketKeys": tlsSessionTicketKeys, "tlsSessionTicketTimeout": tlsSessionTicketTimeout, - "nginxServers": nginxServers, + "defaultCertificate": defaultCertificate, "iterate": func(n int) []int { v := make([]int, n) for i := 0; i < n; i++ { @@ -277,7 +259,6 @@ var rawNginxConfiguration = ` {{- $config := .Config -}} {{- $instance := .Instance -}} {{- $modules := .Modules -}} -{{- $nginxServers := . | nginxServers -}} # This file was generated by RPaaS (https://github.com/tsuru/rpaas-operator.git) # Do not modify this file, any change will be lost. @@ -290,7 +271,6 @@ user {{ . }}; worker_processes {{ . }}; {{- end }} - {{- range $mod, $_ := $modules }} load_module "modules/{{ $mod }}.so"; {{- else }} @@ -430,22 +410,32 @@ http { {{- end }} } - {{- range $_, $nginxServer := $nginxServers }} server { - listen {{ httpPort $instance }}{{ with $nginxServer.Default }} default_server{{ end }} + listen {{ httpPort $instance }} default_server {{- with $config.HTTPListenOptions }} {{ . }}{{ end }}; - {{- with $nginxServer.Certificate }} - listen {{ httpsPort $instance }}{{ with $nginxServer.Default }} default_server{{ end }} ssl http2 + {{- template "rpaasv2.internal.server" $all }} + } + + {{- range $_, $tls := $instance.Spec.TLS }} + server { + listen {{ httpsPort $instance }} ssl http2 {{- with $config.HTTPSListenOptions }} {{ . }}{{ end }}; - {{- with $nginxServer.Certificate.DNSNames}} - server_name {{- range $_, $dnsName := $nginxServer.Certificate.DNSNames }} {{ $dnsName }}{{- end }}; - {{- end }} + server_name {{ range $index, $host := $tls.Hosts }}{{ if $index }} {{ end }}{{ $host }}{{ end }}; - ssl_certificate certs/{{ with $nginxServer.SecretItem.CertificatePath }}{{ . }}{{ else }}{{ $nginxServer.SecretItem.CertificateField }}{{ end }}; - ssl_certificate_key certs/{{ with $nginxServer.SecretItem.KeyPath }}{{ . }}{{ else }}{{ $nginxServer.SecretItem.KeyField }}{{ end }}; - {{- end }} + ssl_certificate certs/{{ $tls.SecretName }}/tls.crt; + ssl_certificate_key certs/{{ $tls.SecretName }}/tls.key; + + {{ template "rpaasv2.internal.server" $all }} + } + {{- end }} +} + +{{- define "rpaasv2.internal.server" }} + {{- $all := . -}} + {{- $config := .Config -}} + {{- $instance := .Instance -}} {{- if boolValue $config.CacheEnabled }} proxy_cache rpaas; @@ -476,7 +466,7 @@ http { proxy_set_header Connection ""; proxy_set_header Host {{ $location.Destination }}; - proxy_pass http://{{ buildLocationKey "" $location.Path }}/; + proxy_pass http://{{ buildLocationKey "" $location.Path }}/; proxy_redirect ~^http://{{ buildLocationKey "" $location.Path }}(:\d+)?/(.*)$ {{ $location.Path }}$2; {{- else }} {{- with $location.Content.Value }} @@ -493,7 +483,7 @@ http { proxy_set_header Connection ""; proxy_set_header Host {{ (index $instance.Spec.Binds 0).Host }}; - proxy_pass http://rpaas_default_upstream/; + proxy_pass http://rpaas_default_upstream/; proxy_redirect ~^http://rpaas_default_upstream(:\d+)?/(.*)$ /$2; } {{- else }} @@ -504,8 +494,6 @@ http { {{- end}} {{- end}} - {{ template "server" .}} - } - {{- end}} -} + {{ template "server" $all }} +{{- end }} ` diff --git a/internal/pkg/rpaas/nginx/configuration_render_test.go b/internal/pkg/rpaas/nginx/configuration_render_test.go index 44d2d1da1..bcece8d81 100644 --- a/internal/pkg/rpaas/nginx/configuration_render_test.go +++ b/internal/pkg/rpaas/nginx/configuration_render_test.go @@ -5,7 +5,6 @@ package nginx import ( - "crypto/x509" "testing" "github.com/stretchr/testify/assert" @@ -91,9 +90,9 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { \s+} \s+}`, result) assert.Regexp(t, `proxy_cache rpaas;`, result) - }, }, + { name: "with cache enabled and custom loader_files, inactive, and max cache size", data: ConfigurationData{ @@ -192,33 +191,17 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { Config: &v1alpha1.NginxConfig{}, Instance: &v1alpha1.RpaasInstance{ Spec: v1alpha1.RpaasInstanceSpec{ - Certificates: &nginxv1alpha1.TLSSecret{ - SecretName: "secret-name", - Items: []nginxv1alpha1.TLSSecretItem{ - { - CertificateField: "default.crt", - KeyField: "default.key", - }, - }, - }, - }, - }, - FullCertificates: []CertificateData{ - { - Certificate: &x509.Certificate{ - DNSNames: []string{"example.org"}, - }, - SecretItem: nginxv1alpha1.TLSSecretItem{ - CertificateField: "default.crt", - KeyField: "default.key", + TLS: []nginxv1alpha1.NginxTLS{ + {SecretName: "my-cert-01", Hosts: []string{"*.example.com"}}, }, }, }, }, assertion: func(t *testing.T, result string) { - assert.Regexp(t, `listen 8443 default_server ssl http2;`, result) - assert.Regexp(t, `ssl_certificate\s+certs/default.crt;`, result) - assert.Regexp(t, `ssl_certificate_key certs/default.key;`, result) + assert.Regexp(t, `listen 8443 ssl http2;`, result) + assert.Regexp(t, `ssl_certificate\s+certs/my-cert-01/tls.crt;`, result) + assert.Regexp(t, `ssl_certificate_key certs/my-cert-01/tls.key;`, result) + assert.Regexp(t, `server_name \*.example.com;`, result) }, }, { @@ -227,54 +210,23 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { Config: &v1alpha1.NginxConfig{}, Instance: &v1alpha1.RpaasInstance{ Spec: v1alpha1.RpaasInstanceSpec{ - Certificates: &nginxv1alpha1.TLSSecret{ - SecretName: "secret-name", - Items: []nginxv1alpha1.TLSSecretItem{ - { - CertificateField: "example.crt", - KeyField: "example.key", - }, - { - CertificateField: "cert-manager.crt", - KeyField: "cert-manager.key", - }, - }, - }, - }, - }, - FullCertificates: []CertificateData{ - { - Certificate: &x509.Certificate{ - DNSNames: []string{"example.org", "example.io"}, - }, - SecretItem: nginxv1alpha1.TLSSecretItem{ - CertificateField: "example.crt", - KeyField: "example.key", - }, - }, - { - Certificate: &x509.Certificate{ - DNSNames: []string{"example.namespace.system.internal.company.com"}, - }, - SecretItem: nginxv1alpha1.TLSSecretItem{ - CertificateField: "cert-manager.crt", - KeyField: "cert-manager.key", + TLS: []nginxv1alpha1.NginxTLS{ + {SecretName: "my-cert-01", Hosts: []string{"*.example.com"}}, + {SecretName: "my-cert-02", Hosts: []string{"www.example.org", "blog.example.org", "shop.example.org"}}, }, }, }, }, assertion: func(t *testing.T, result string) { - assert.Regexp(t, `listen 8443 default_server ssl http2;`, result) - assert.Regexp(t, `listen 8443 ssl http2;`, result) + assert.Regexp(t, `listen 8443 ssl http2; +\s+server_name \*.example.com; +\s+ssl_certificate certs/my-cert-01/tls.crt; +\s+ssl_certificate_key certs/my-cert-01/tls.key;`, result) - assert.Regexp(t, `ssl_certificate\s+certs/cert-manager.crt;`, result) - assert.Regexp(t, `ssl_certificate_key certs/cert-manager.key;`, result) - - assert.Regexp(t, `ssl_certificate\s+certs/example.crt;`, result) - assert.Regexp(t, `ssl_certificate_key certs/example.key;`, result) - - assert.Regexp(t, `server_name example.org example.io;`, result) - assert.Regexp(t, `server_name example.namespace.system.internal.company.com;`, result) + assert.Regexp(t, `listen 8443 ssl http2; +\s+server_name www.example.org blog.example.org shop.example.org; +\s+ssl_certificate certs/my-cert-02/tls.crt; +\s+ssl_certificate_key certs/my-cert-02/tls.key;`, result) }, }, { @@ -285,37 +237,17 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { }, Instance: &v1alpha1.RpaasInstance{ Spec: v1alpha1.RpaasInstanceSpec{ - Certificates: &nginxv1alpha1.TLSSecret{ - SecretName: "secret-name", - Items: []nginxv1alpha1.TLSSecretItem{ - { - CertificateField: "default.crt", - CertificatePath: "custom_certificate_name.crt", - KeyField: "default.key", - KeyPath: "custom_key_name.key", - }, - }, - }, - }, - }, - FullCertificates: []CertificateData{ - { - Certificate: &x509.Certificate{ - DNSNames: []string{"example.org"}, - }, - SecretItem: nginxv1alpha1.TLSSecretItem{ - CertificateField: "default.crt", - CertificatePath: "custom_certificate_name.crt", - KeyField: "default.key", - KeyPath: "custom_key_name.key", + TLS: []nginxv1alpha1.NginxTLS{ + {SecretName: "my-cert-01", Hosts: []string{"*.example.com"}}, }, }, }, }, assertion: func(t *testing.T, result string) { - assert.Regexp(t, `listen 8443 default_server ssl http2 backlog=2048 deferred reuseport;`, result) - assert.Regexp(t, `ssl_certificate\s+certs/custom_certificate_name.crt;`, result) - assert.Regexp(t, `ssl_certificate_key certs/custom_key_name.key;`, result) + assert.Regexp(t, `listen 8443 ssl http2 backlog=2048 deferred reuseport; +\s+server_name \*.example.com; +\s+ssl_certificate certs/my-cert-01/tls.crt; +\s+ssl_certificate_key certs/my-cert-01/tls.key;`, result) }, }, { @@ -357,7 +289,7 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { \s+proxy_set_header Connection ""; \s+proxy_set_header Host app1.tsuru.example.com; -\s+proxy_pass http://rpaas_default_upstream/; +\s+proxy_pass http://rpaas_default_upstream/; \s+proxy_redirect ~\^http://rpaas_default_upstream\(:\\d\+\)\?/\(\.\*\)\$ /\$2; \s+}`, result) }, @@ -383,7 +315,7 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { \s+proxy_set_header Connection ""; \s+proxy_set_header Host app1.tsuru.example.com; -\s+proxy_pass http://rpaas_default_upstream/; +\s+proxy_pass http://rpaas_default_upstream/; \s+proxy_redirect ~\^http://rpaas_default_upstream\(:\\d\+\)\?/\(\.\*\)\$ /\$2; \s+}`, result) }, @@ -429,7 +361,7 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { \s+proxy_set_header Connection ""; \s+proxy_set_header Host app1\.tsuru\.example\.com; -\s+proxy_pass http://rpaas_locations__path1/; +\s+proxy_pass http://rpaas_locations__path1/; \s+proxy_redirect ~\^http://rpaas_locations__path1\(:\\d\+\)\?/\(\.\*\)\$ /path1\$2; \s+}`, result) assert.Regexp(t, `location /path2 { @@ -440,7 +372,7 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { \s+proxy_set_header Connection ""; \s+proxy_set_header Host app2\.tsuru\.example\.com; -\s+proxy_pass http://rpaas_locations__path2/; +\s+proxy_pass http://rpaas_locations__path2/; \s+proxy_redirect ~\^http://rpaas_locations__path2\(:\\d\+\)\?/\(\.\*\)\$ /path2\$2; \s+}`, result) assert.Regexp(t, `location /path3 { @@ -469,39 +401,21 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { Config: &v1alpha1.NginxConfig{}, Instance: &v1alpha1.RpaasInstance{ Spec: v1alpha1.RpaasInstanceSpec{ - Certificates: &nginxv1alpha1.TLSSecret{ - SecretName: "secret-name", - Items: []nginxv1alpha1.TLSSecretItem{ - { - CertificateField: "default.crt", - CertificatePath: "custom_certificate_name.crt", - KeyField: "default.key", - KeyPath: "custom_key_name.key", - }, - }, + TLS: []nginxv1alpha1.NginxTLS{ + {SecretName: "my-cert-01", Hosts: []string{"*.example.com"}}, }, PodTemplate: nginxv1alpha1.NginxPodTemplateSpec{ HostNetwork: true, }, }, }, - FullCertificates: []CertificateData{ - { - Certificate: &x509.Certificate{ - DNSNames: []string{"example.org"}, - }, - SecretItem: nginxv1alpha1.TLSSecretItem{ - CertificateField: "default.crt", - CertificatePath: "custom_certificate_name.crt", - KeyField: "default.key", - KeyPath: "custom_key_name.key", - }, - }, - }, }, assertion: func(t *testing.T, result string) { assert.Regexp(t, `listen 80 default_server;`, result) - assert.Regexp(t, `listen 443 default_server ssl http2;`, result) + assert.Regexp(t, `listen 443 ssl http2; +\s+server_name \*.example.com; +\s+ssl_certificate certs/my-cert-01/tls.crt; +\s+ssl_certificate_key certs/my-cert-01/tls.key;`, result) assert.Regexp(t, `listen 8800;`, result) }, }, @@ -511,16 +425,8 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { Config: &v1alpha1.NginxConfig{}, Instance: &v1alpha1.RpaasInstance{ Spec: v1alpha1.RpaasInstanceSpec{ - Certificates: &nginxv1alpha1.TLSSecret{ - SecretName: "secret-name", - Items: []nginxv1alpha1.TLSSecretItem{ - { - CertificateField: "default.crt", - CertificatePath: "custom_certificate_name.crt", - KeyField: "default.key", - KeyPath: "custom_key_name.key", - }, - }, + TLS: []nginxv1alpha1.NginxTLS{ + {SecretName: "my-cert-01", Hosts: []string{"*.example.com"}}, }, PodTemplate: nginxv1alpha1.NginxPodTemplateSpec{ Ports: []corev1.ContainerPort{ @@ -540,23 +446,13 @@ func TestRpaasConfigurationRenderer_Render(t *testing.T) { }, }, }, - FullCertificates: []CertificateData{ - { - Certificate: &x509.Certificate{ - DNSNames: []string{"example.org"}, - }, - SecretItem: nginxv1alpha1.TLSSecretItem{ - CertificateField: "default.crt", - CertificatePath: "custom_certificate_name.crt", - KeyField: "default.key", - KeyPath: "custom_key_name.key", - }, - }, - }, }, assertion: func(t *testing.T, result string) { assert.Regexp(t, `listen 20001 default_server;`, result) - assert.Regexp(t, `listen 20002 default_server ssl http2;`, result) + assert.Regexp(t, `listen 20002 ssl http2; +\s+server_name \*.example.com; +\s+ssl_certificate certs/my-cert-01/tls.crt; +\s+ssl_certificate_key certs/my-cert-01/tls.key;`, result) assert.Regexp(t, `listen 20003;`, result) }, }, diff --git a/test/integration_test.go b/test/integration_test.go index 956bdcb6f..903ff3897 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -90,35 +90,11 @@ func Test_RpaasOperator(t *testing.T) { assert.Contains(t, "# My custom HTTP block", nginxConf.Data["nginx-conf"]) assert.Contains(t, "# My custom server block", nginxConf.Data["nginx-conf"]) - tlsSecret := &nginxv1alpha1.TLSSecret{ - SecretName: "my-instance-certificates", - Items: []nginxv1alpha1.TLSSecretItem{ - { - CertificateField: "default.crt", - CertificatePath: "my-custom-name.crt", - KeyField: "default.key", - KeyPath: "my-custom-name.key", - }, - { - CertificateField: "cert-manager.crt", - KeyField: "cert-manager.key", - }, - }, - } - assert.Equal(t, tlsSecret, nginx.Spec.Certificates) - - certificatesSecret := &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, - } - err = get(certificatesSecret, nginx.Spec.Certificates.SecretName, namespaceName) - require.NoError(t, err) - assert.NotEmpty(t, certificatesSecret.Data["default.crt"]) - assert.NotEmpty(t, certificatesSecret.Data["default.key"]) - assert.NotEmpty(t, certificatesSecret.Data["cert-manager.crt"]) - assert.NotEmpty(t, certificatesSecret.Data["cert-manager.key"]) + require.Len(t, nginx.Spec.TLS, 2) + assert.Equal(t, []nginxv1alpha1.NginxTLS{ + {SecretName: "my-instance-certificates", Hosts: []string{"www.example.com"}}, + {SecretName: "my-instance-cert-manager", Hosts: []string{"my-instance.example.com", "app.example.com"}}, + }, nginx.Spec.TLS) nginxService := &corev1.Service{ TypeMeta: metav1.TypeMeta{ diff --git a/test/testdata/rpaas-full.yaml b/test/testdata/rpaas-full.yaml index 1812418fd..eb16692db 100644 --- a/test/testdata/rpaas-full.yaml +++ b/test/testdata/rpaas-full.yaml @@ -11,7 +11,6 @@ metadata: name: basic spec: image: tsuru/nginx-tsuru:1.16.1 - config: {} resources: limits: memory: "128Mi" @@ -20,8 +19,11 @@ apiVersion: v1 kind: Secret metadata: name: my-instance-certificates + labels: + rpaas.extensions.tsuru.io/certificate-name: default + rpaas.extensions.tsuru.io/instance-name: my-instance data: - default.crt: |- + tls.crt: |- LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJoVENDQVN1Z0F3SUJBZ0lRSVJpNnplUEw2 bUtqT2lwbitkTnVhVEFLQmdncWhrak9QUVFEQWpBU01SQXcKRGdZRFZRUUtFd2RCWTIxbElFTnZN QjRYRFRFM01UQXlNREU1TkRNd05sb1hEVEU0TVRBeU1ERTVORE13TmxvdwpFakVRTUE0R0ExVUVD @@ -33,7 +35,7 @@ data: UURBZ05JQURCRkFpRUEyenBKRVBReXo2L2wKV2Y4NmFYNlBlcHNudFp2MkdZbEE1VXBhYmZUMkVa SUNJQ3BKNWgvaUkraTM0MWdCbUxpQUZRT3lURFQrL3dRYwo2TUY5K1l3MVl5MHQKLS0tLS1FTkQg Q0VSVElGSUNBVEUtLS0tLQo= - default.key: |- + tls.key: |- LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUlyWVNTTlFGYUEySHdmMWR1 UlN4S3RMWVg1Q0IwNGZTZVE2dEYxYVkvUHVvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFUFIzdFUy RnRhOWt0WSs2UDlHMGNXTyswa0VUQTZTRnMzOEdlY1R5dWRsSHo2eHZDZHo4cQpFS1RjV0dla2Rt @@ -68,13 +70,10 @@ spec: configMapKeyRef: name: my-instance-blocks key: server - certificates: - secretName: my-instance-certificates - items: - - certificateField: default.crt - certificatePath: my-custom-name.crt - keyField: default.key - keyPath: my-custom-name.key + tls: + - secretName: my-instance-certificates + hosts: + - www.example.com dynamicCertificates: certManager: issuer: self-signed