diff --git a/api/v1alpha1/nginxingresscontroller_types.go b/api/v1alpha1/nginxingresscontroller_types.go index 76345902..df0336b9 100644 --- a/api/v1alpha1/nginxingresscontroller_types.go +++ b/api/v1alpha1/nginxingresscontroller_types.go @@ -50,6 +50,30 @@ type NginxIngressControllerSpec struct { // will be from the Azure LoadBalancer annotations here https://cloud-provider-azure.sigs.k8s.io/topics/loadbalancer/#loadbalancer-annotations // +optional LoadBalancerAnnotations map[string]string `json:"loadBalancerAnnotations,omitempty"` + + // DefaultSSLCertificate defines whether the NginxIngressController should use a certain SSL certificate by default. + // If this field is omitted, no default certificate will be used. + // +optional + DefaultSSLCertificate *DefaultSSLCertificate `json:"defaultSSLCertificate,omitempty"` +} + +type DefaultSSLCertificate struct { + // Secret is a struct that holds the name and namespace fields used for the default ssl secret + // +optional + Secret *Secret `json:"secret,omitempty"` +} + +// Secret is a struct that holds a name and namespace to be used in DefaultSSLCertificate +type Secret struct { + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^[a-z0-9][-a-z0-9\.]*[a-z0-9]$` + Name string `json:"name"` + + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^[a-z0-9][-a-z0-9\.]*[a-z0-9]$` + Namespace string `json:"namespace"` } // NginxIngressControllerStatus defines the observed state of NginxIngressController diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4b7e070e..0fc273d2 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -9,6 +9,26 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DefaultSSLCertificate) DeepCopyInto(out *DefaultSSLCertificate) { + *out = *in + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + *out = new(Secret) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultSSLCertificate. +func (in *DefaultSSLCertificate) DeepCopy() *DefaultSSLCertificate { + if in == nil { + return nil + } + out := new(DefaultSSLCertificate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManagedObjectReference) DeepCopyInto(out *ManagedObjectReference) { *out = *in @@ -93,6 +113,11 @@ func (in *NginxIngressControllerSpec) DeepCopyInto(out *NginxIngressControllerSp (*out)[key] = val } } + if in.DefaultSSLCertificate != nil { + in, out := &in.DefaultSSLCertificate, &out.DefaultSSLCertificate + *out = new(DefaultSSLCertificate) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NginxIngressControllerSpec. @@ -131,3 +156,18 @@ func (in *NginxIngressControllerStatus) DeepCopy() *NginxIngressControllerStatus in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Secret) DeepCopyInto(out *Secret) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Secret. +func (in *Secret) DeepCopy() *Secret { + if in == nil { + return nil + } + out := new(Secret) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/approuting.kubernetes.azure.com_nginxingresscontrollers.yaml b/config/crd/bases/approuting.kubernetes.azure.com_nginxingresscontrollers.yaml index 7fb31244..c52bc983 100644 --- a/config/crd/bases/approuting.kubernetes.azure.com_nginxingresscontrollers.yaml +++ b/config/crd/bases/approuting.kubernetes.azure.com_nginxingresscontrollers.yaml @@ -61,6 +61,30 @@ spec: x-kubernetes-validations: - message: Value is immutable rule: self == oldSelf + defaultSSLCertificate: + description: DefaultSSLCertificate defines whether the NginxIngressController + should use a certain SSL certificate by default. If this field is + omitted, no default certificate will be used. + properties: + secret: + description: Secret is a struct that holds the name and namespace + fields used for the default ssl secret + properties: + name: + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9][-a-z0-9\.]*[a-z0-9]$ + type: string + namespace: + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9][-a-z0-9\.]*[a-z0-9]$ + type: string + required: + - name + - namespace + type: object + type: object ingressClassName: default: nginx.approuting.kubernetes.azure.com description: IngressClassName is the name of the IngressClass that diff --git a/go.mod b/go.mod index fa91481d..174420c7 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/prometheus/common v0.44.0 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.25.0 - gomodules.xyz/jsonpatch/v2 v2.4.0 k8s.io/api v0.28.1 k8s.io/apiextensions-apiserver v0.28.1 k8s.io/apimachinery v0.28.1 @@ -73,6 +72,7 @@ require ( golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/pkg/controller/nginxingress/nginx_ingress_controller.go b/pkg/controller/nginxingress/nginx_ingress_controller.go index b5a0ee9d..cb7959a5 100644 --- a/pkg/controller/nginxingress/nginx_ingress_controller.go +++ b/pkg/controller/nginxingress/nginx_ingress_controller.go @@ -518,7 +518,7 @@ func ToNginxIngressConfig(nic *approutingv1alpha1.NginxIngressController, defaul resourceName = DefaultNicResourceName } - return &manifests.NginxIngressConfig{ + nginxIng := &manifests.NginxIngressConfig{ ControllerClass: cc, ResourceName: resourceName, IcName: nic.Spec.IngressClassName, @@ -526,4 +526,11 @@ func ToNginxIngressConfig(nic *approutingv1alpha1.NginxIngressController, defaul Annotations: nic.Spec.LoadBalancerAnnotations, }, } + + if nic.Spec.DefaultSSLCertificate != nil && + nic.Spec.DefaultSSLCertificate.Secret.Name != "" && nic.Spec.DefaultSSLCertificate.Secret.Namespace != "" { + nginxIng.DefaultSSLCertificate = nic.Spec.DefaultSSLCertificate.Secret.Namespace + "/" + nic.Spec.DefaultSSLCertificate.Secret.Name + } + + return nginxIng } diff --git a/pkg/controller/nginxingress/nginx_ingress_controller_test.go b/pkg/controller/nginxingress/nginx_ingress_controller_test.go index 224d3fde..01a2b8b6 100644 --- a/pkg/controller/nginxingress/nginx_ingress_controller_test.go +++ b/pkg/controller/nginxingress/nginx_ingress_controller_test.go @@ -807,6 +807,9 @@ func TestIsUnreconcilableError(t *testing.T) { func TestToNginxIngressConfig(t *testing.T) { defaultCc := "defaultControllerClass" + FakeDefaultSSLCert := getFakeDefaultSSLCert("fake", "fakenamespace") + FakeDefaultSSLCertNoName := getFakeDefaultSSLCert("", "fakenamespace") + FakeDefaultSSLCertNoNamespace := getFakeDefaultSSLCert("fake", "") cases := []struct { name string nic *approutingv1alpha1.NginxIngressController @@ -883,6 +886,76 @@ func TestToNginxIngressConfig(t *testing.T) { IcName: "ingressClassName", }, }, + { + name: "default controller class with DefaultSSLCertificate", + nic: &approutingv1alpha1.NginxIngressController{ + TypeMeta: metav1.TypeMeta{ + APIVersion: approutingv1alpha1.GroupVersion.String(), + Kind: "NginxIngressController", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: DefaultNicName, + }, + Spec: approutingv1alpha1.NginxIngressControllerSpec{ + ControllerNamePrefix: DefaultNicResourceName, + IngressClassName: DefaultIcName, + DefaultSSLCertificate: FakeDefaultSSLCert, + }, + }, + want: manifests.NginxIngressConfig{ + ControllerClass: defaultCc, + ResourceName: DefaultNicResourceName, + IcName: DefaultIcName, + ServiceConfig: &manifests.ServiceConfig{}, + DefaultSSLCertificate: FakeDefaultSSLCert.Secret.Namespace + "/" + FakeDefaultSSLCert.Secret.Name, + }, + }, + { + name: "default controller class with DefaultSSLCertificate with no name", + nic: &approutingv1alpha1.NginxIngressController{ + TypeMeta: metav1.TypeMeta{ + APIVersion: approutingv1alpha1.GroupVersion.String(), + Kind: "NginxIngressController", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: DefaultNicName, + }, + Spec: approutingv1alpha1.NginxIngressControllerSpec{ + ControllerNamePrefix: DefaultNicResourceName, + IngressClassName: DefaultIcName, + DefaultSSLCertificate: FakeDefaultSSLCertNoName, + }, + }, + want: manifests.NginxIngressConfig{ + ControllerClass: defaultCc, + ResourceName: DefaultNicResourceName, + IcName: DefaultIcName, + ServiceConfig: &manifests.ServiceConfig{}, + }, + }, + { + name: "default controller class with DefaultSSLCertificate with no namespace", + nic: &approutingv1alpha1.NginxIngressController{ + TypeMeta: metav1.TypeMeta{ + APIVersion: approutingv1alpha1.GroupVersion.String(), + Kind: "NginxIngressController", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: DefaultNicName, + }, + Spec: approutingv1alpha1.NginxIngressControllerSpec{ + ControllerNamePrefix: DefaultNicResourceName, + IngressClassName: DefaultIcName, + DefaultSSLCertificate: FakeDefaultSSLCertNoNamespace, + }, + }, + want: manifests.NginxIngressConfig{ + ControllerClass: defaultCc, + ResourceName: DefaultNicResourceName, + IcName: DefaultIcName, + ServiceConfig: &manifests.ServiceConfig{}, + }, + }, } for _, c := range cases { @@ -892,3 +965,13 @@ func TestToNginxIngressConfig(t *testing.T) { }) } } + +func getFakeDefaultSSLCert(name, namespace string) *approutingv1alpha1.DefaultSSLCertificate { + fakecert := &approutingv1alpha1.DefaultSSLCertificate{ + Secret: &approutingv1alpha1.Secret{ + Name: name, + Namespace: namespace, + }, + } + return fakecert +} diff --git a/pkg/manifests/fixtures/nginx/internal-with-ssl-cert.json b/pkg/manifests/fixtures/nginx/internal-with-ssl-cert.json new file mode 100644 index 00000000..4cff8690 --- /dev/null +++ b/pkg/manifests/fixtures/nginx/internal-with-ssl-cert.json @@ -0,0 +1,674 @@ +[ + { + "kind": "Namespace", + "apiVersion": "v1", + "metadata": { + "name": "test-namespace", + "creationTimestamp": null + }, + "spec": {}, + "status": {} + }, + { + "kind": "IngressClass", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "nginx-private", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "spec": { + "controller": "test-controller-class" + } + }, + { + "kind": "ServiceAccount", + "apiVersion": "v1", + "metadata": { + "name": "nginx", + "namespace": "test-namespace", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + } + }, + { + "kind": "ClusterRole", + "apiVersion": "rbac.authorization.k8s.io/v1", + "metadata": { + "name": "nginx", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "rules": [ + { + "verbs": [ + "list", + "watch" + ], + "apiGroups": [ + "" + ], + "resources": [ + "configmaps", + "endpoints", + "nodes", + "pods", + "secrets", + "namespaces" + ] + }, + { + "verbs": [ + "list", + "watch" + ], + "apiGroups": [ + "coordination.k8s.io" + ], + "resources": [ + "leases" + ] + }, + { + "verbs": [ + "get" + ], + "apiGroups": [ + "" + ], + "resources": [ + "nodes" + ] + }, + { + "verbs": [ + "get", + "list", + "watch" + ], + "apiGroups": [ + "" + ], + "resources": [ + "services" + ] + }, + { + "verbs": [ + "get", + "watch", + "list" + ], + "apiGroups": [ + "networking.k8s.io" + ], + "resources": [ + "ingresses" + ] + }, + { + "verbs": [ + "create", + "patch" + ], + "apiGroups": [ + "" + ], + "resources": [ + "events" + ] + }, + { + "verbs": [ + "update" + ], + "apiGroups": [ + "networking.k8s.io" + ], + "resources": [ + "ingresses/status" + ] + }, + { + "verbs": [ + "get", + "list", + "watch" + ], + "apiGroups": [ + "networking.k8s.io" + ], + "resources": [ + "ingressclasses" + ] + }, + { + "verbs": [ + "list", + "watch", + "get" + ], + "apiGroups": [ + "discovery.k8s.io" + ], + "resources": [ + "endpointslices" + ] + } + ] + }, + { + "kind": "Role", + "apiVersion": "rbac.authorization.k8s.io/v1", + "metadata": { + "name": "nginx", + "namespace": "test-namespace", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "rules": [ + { + "verbs": [ + "get" + ], + "apiGroups": [ + "" + ], + "resources": [ + "namespaces" + ] + }, + { + "verbs": [ + "update" + ], + "apiGroups": [ + "" + ], + "resources": [ + "configmaps" + ] + }, + { + "verbs": [ + "get", + "list", + "watch" + ], + "apiGroups": [ + "" + ], + "resources": [ + "configmaps", + "pods", + "secrets", + "endpoints" + ] + }, + { + "verbs": [ + "get", + "list", + "watch" + ], + "apiGroups": [ + "" + ], + "resources": [ + "services" + ] + }, + { + "verbs": [ + "get", + "list", + "watch" + ], + "apiGroups": [ + "networking.k8s.io" + ], + "resources": [ + "ingresses" + ] + }, + { + "verbs": [ + "update" + ], + "apiGroups": [ + "networking.k8s.io" + ], + "resources": [ + "ingresses/status" + ] + }, + { + "verbs": [ + "get", + "list", + "watch" + ], + "apiGroups": [ + "networking.k8s.io" + ], + "resources": [ + "ingressclasses" + ] + }, + { + "verbs": [ + "get", + "update" + ], + "apiGroups": [ + "coordination.k8s.io" + ], + "resources": [ + "leases" + ], + "resourceNames": [ + "nginx" + ] + }, + { + "verbs": [ + "create" + ], + "apiGroups": [ + "coordination.k8s.io" + ], + "resources": [ + "leases" + ] + }, + { + "verbs": [ + "create", + "patch" + ], + "apiGroups": [ + "" + ], + "resources": [ + "events" + ] + }, + { + "verbs": [ + "list", + "watch", + "get" + ], + "apiGroups": [ + "discovery.k8s.io" + ], + "resources": [ + "endpointslices" + ] + } + ] + }, + { + "kind": "ClusterRoleBinding", + "apiVersion": "rbac.authorization.k8s.io/v1", + "metadata": { + "name": "nginx", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "nginx", + "namespace": "test-namespace" + } + ], + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "ClusterRole", + "name": "nginx" + } + }, + { + "kind": "RoleBinding", + "apiVersion": "rbac.authorization.k8s.io/v1", + "metadata": { + "name": "nginx", + "namespace": "test-namespace", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "subjects": [ + { + "kind": "ServiceAccount", + "name": "nginx", + "namespace": "test-namespace" + } + ], + "roleRef": { + "apiGroup": "rbac.authorization.k8s.io", + "kind": "Role", + "name": "nginx" + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "nginx", + "namespace": "test-namespace", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + }, + "annotations": { + "prometheus.io/port": "10254", + "prometheus.io/scrape": "true" + } + }, + "spec": { + "ports": [ + { + "name": "http", + "port": 80, + "targetPort": "http" + }, + { + "name": "https", + "port": 443, + "targetPort": "https" + }, + { + "name": "prometheus", + "port": 10254, + "targetPort": "prometheus" + } + ], + "selector": { + "app": "nginx" + }, + "type": "LoadBalancer", + "externalTrafficPolicy": "Local" + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "apps/v1", + "metadata": { + "name": "nginx", + "namespace": "test-namespace", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "spec": { + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app": "nginx", + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator" + }, + "annotations": { + "openservicemesh.io/sidecar-injection": "enabled", + "prometheus.io/port": "10254", + "prometheus.io/scrape": "true" + } + }, + "spec": { + "containers": [ + { + "name": "controller", + "image": "test-registry/oss/kubernetes/ingress/nginx-ingress-controller:v1.9.4", + "args": [ + "/nginx-ingress-controller", + "--ingress-class=nginx-private", + "--controller-class=test-controller-class", + "--election-id=nginx", + "--publish-service=$(POD_NAMESPACE)/nginx", + "--configmap=$(POD_NAMESPACE)/nginx", + "--enable-annotation-validation=true", + "--http-port=8080", + "--https-port=8443", + "--default-ssl-certificate=fakenamespace/fakename" + ], + "ports": [ + { + "name": "http", + "containerPort": 8080 + }, + { + "name": "https", + "containerPort": 8443 + }, + { + "name": "prometheus", + "containerPort": 10254 + } + ], + "env": [ + { + "name": "POD_NAME", + "valueFrom": { + "fieldRef": { + "fieldPath": "metadata.name" + } + } + }, + { + "name": "POD_NAMESPACE", + "valueFrom": { + "fieldRef": { + "fieldPath": "metadata.namespace" + } + } + } + ], + "resources": { + "requests": { + "cpu": "500m", + "memory": "127Mi" + } + }, + "readinessProbe": { + "httpGet": { + "path": "/healthz", + "port": 10254, + "scheme": "HTTP" + }, + "initialDelaySeconds": 10, + "timeoutSeconds": 1, + "periodSeconds": 5, + "successThreshold": 1, + "failureThreshold": 3 + }, + "securityContext": { + "runAsUser": 101 + } + } + ], + "serviceAccountName": "nginx", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "kubernetes.azure.com/cluster", + "operator": "Exists" + }, + { + "key": "type", + "operator": "NotIn", + "values": [ + "virtual-kubelet" + ] + }, + { + "key": "kubernetes.io/os", + "operator": "In", + "values": [ + "linux" + ] + } + ] + } + ] + }, + "preferredDuringSchedulingIgnoredDuringExecution": [ + { + "weight": 100, + "preference": { + "matchExpressions": [ + { + "key": "kubernetes.azure.com/mode", + "operator": "In", + "values": [ + "system" + ] + } + ] + } + } + ] + } + }, + "tolerations": [ + { + "key": "CriticalAddonsOnly", + "operator": "Exists" + } + ], + "priorityClassName": "system-cluster-critical", + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "kubernetes.io/hostname", + "whenUnsatisfiable": "ScheduleAnyway", + "labelSelector": { + "matchLabels": { + "app": "nginx" + } + } + } + ] + } + }, + "strategy": {}, + "revisionHistoryLimit": 2 + }, + "status": {} + }, + { + "kind": "ConfigMap", + "apiVersion": "v1", + "metadata": { + "name": "nginx", + "namespace": "test-namespace", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "data": { + "allow-snippet-annotations": "true", + "annotation-value-word-blocklist": "load_module,lua_package,_by_lua,location,root,proxy_pass,serviceaccount,{,},'" + } + }, + { + "kind": "HorizontalPodAutoscaler", + "apiVersion": "autoscaling/v1", + "metadata": { + "name": "nginx", + "namespace": "test-namespace", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "spec": { + "scaleTargetRef": { + "kind": "Deployment", + "name": "nginx", + "apiVersion": "apps/v1" + }, + "minReplicas": 2, + "maxReplicas": 100, + "targetCPUUtilizationPercentage": 80 + }, + "status": { + "currentReplicas": 0, + "desiredReplicas": 0 + } + }, + { + "kind": "PodDisruptionBudget", + "apiVersion": "policy/v1", + "metadata": { + "name": "nginx", + "namespace": "test-namespace", + "creationTimestamp": null, + "labels": { + "app.kubernetes.io/component": "ingress-controller", + "app.kubernetes.io/managed-by": "aks-app-routing-operator", + "app.kubernetes.io/name": "nginx" + } + }, + "spec": { + "selector": { + "matchLabels": { + "app": "nginx" + } + }, + "maxUnavailable": 1 + }, + "status": { + "disruptionsAllowed": 0, + "currentHealthy": 0, + "desiredHealthy": 0, + "expectedPods": 0 + } + } + ] \ No newline at end of file diff --git a/pkg/manifests/nginx.go b/pkg/manifests/nginx.go index 74072173..d1adf28e 100644 --- a/pkg/manifests/nginx.go +++ b/pkg/manifests/nginx.go @@ -111,10 +111,11 @@ var ( // NginxIngressConfig defines configuration options for required resources for an Ingress type NginxIngressConfig struct { - ControllerClass string // controller class which is equivalent to controller field of IngressClass - ResourceName string // name given to all resources - IcName string // IngressClass name - ServiceConfig *ServiceConfig // service config that specifies details about the LB, defaults if nil + ControllerClass string // controller class which is equivalent to controller field of IngressClass + ResourceName string // name given to all resources + IcName string // IngressClass name + ServiceConfig *ServiceConfig // service config that specifies details about the LB, defaults if nil + DefaultSSLCertificate string // namespace/name used to create SSL certificate for the default HTTPS server (catch-all) } func (n *NginxIngressConfig) PodLabels() map[string]string { @@ -426,6 +427,22 @@ func newNginxIngressControllerDeployment(conf *config.Config, ingressConfig *Ngi selector := &metav1.LabelSelector{MatchLabels: ingressConfig.PodLabels()} + deploymentArgs := []string{ + "/nginx-ingress-controller", + "--ingress-class=" + ingressConfig.IcName, + "--controller-class=" + ingressConfig.ControllerClass, + "--election-id=" + ingressConfig.ResourceName, + "--publish-service=$(POD_NAMESPACE)/" + ingressConfig.ResourceName, + "--configmap=$(POD_NAMESPACE)/" + ingressConfig.ResourceName, + "--enable-annotation-validation=true", + "--http-port=8080", + "--https-port=8443", + } + + if ingressConfig.DefaultSSLCertificate != "" { + deploymentArgs = append(deploymentArgs, "--default-ssl-certificate="+ingressConfig.DefaultSSLCertificate) + } + return &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", @@ -457,17 +474,7 @@ func newNginxIngressControllerDeployment(conf *config.Config, ingressConfig *Ngi Containers: []corev1.Container{*withPodRefEnvVars(withTypicalReadinessProbe(10254, &corev1.Container{ Name: "controller", Image: path.Join(conf.Registry, "/oss/kubernetes/ingress/nginx-ingress-controller:"+controllerImageTag), - Args: []string{ - "/nginx-ingress-controller", - "--ingress-class=" + ingressConfig.IcName, - "--controller-class=" + ingressConfig.ControllerClass, - "--election-id=" + ingressConfig.ResourceName, - "--publish-service=$(POD_NAMESPACE)/" + ingressConfig.ResourceName, - "--configmap=$(POD_NAMESPACE)/" + ingressConfig.ResourceName, - "--enable-annotation-validation=true", - "--http-port=8080", - "--https-port=8443", - }, + Args: deploymentArgs, SecurityContext: &corev1.SecurityContext{ RunAsUser: util.Int64Ptr(101), }, diff --git a/pkg/manifests/nginx_test.go b/pkg/manifests/nginx_test.go index 7019570d..b47b39bd 100644 --- a/pkg/manifests/nginx_test.go +++ b/pkg/manifests/nginx_test.go @@ -107,6 +107,29 @@ var ( IcName: "nginx-private", }, }, + { + Name: "internal-with-ssl-cert", + Conf: &config.Config{ + NS: "test-namespace", + Registry: "test-registry", + MSIClientID: "test-msi-client-id", + TenantID: "test-tenant-id", + Cloud: "test-cloud", + Location: "test-location", + }, + Deploy: &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-operator-deploy", + UID: "test-operator-deploy-uid", + }, + }, + IngConfig: &NginxIngressConfig{ + ControllerClass: "test-controller-class", + ResourceName: "nginx", + IcName: "nginx-private", + DefaultSSLCertificate: "fakenamespace/fakename", + }, + }, } classTestCases = []struct { Name string diff --git a/testing/e2e/suites/nginxIngressController.go b/testing/e2e/suites/nginxIngressController.go index b46cc36d..9b9b444d 100644 --- a/testing/e2e/suites/nginxIngressController.go +++ b/testing/e2e/suites/nginxIngressController.go @@ -76,6 +76,42 @@ func nicTests(in infra.Provisioned) []test { return fmt.Errorf("able to create NginxIngressController with invalid controllerNamePrefix '%s'", testNIC.Spec.ControllerNamePrefix) } + testNIC = manifests.NewNginxIngressController("nginx-ingress-controller", "nginxingressclass") + testNIC.Spec.DefaultSSLCertificate = &v1alpha1.DefaultSSLCertificate{ + Secret: &v1alpha1.Secret{ + Name: "Invalid+@Name", + Namespace: "validnamespace", + }, + } + lgr.Info("creating NginxIngressController with invalid Secret field") + if err := c.Create(ctx, testNIC); err == nil { + return fmt.Errorf("able to create NginxIngressController despite invalid Secret Name'%s'", testNIC.Spec.ControllerNamePrefix) + } + + testNIC = manifests.NewNginxIngressController("nginx-ingress-controller", "nginxingressclass") + testNIC.Spec.DefaultSSLCertificate = &v1alpha1.DefaultSSLCertificate{ + Secret: &v1alpha1.Secret{ + Name: "validname", + Namespace: "Invalid+@Namespace", + }, + } + lgr.Info("creating NginxIngressController with invalid Secret field") + if err := c.Create(ctx, testNIC); err == nil { + return fmt.Errorf("able to create NginxIngressController despite invalid Secret Namespace'%s'", testNIC.Spec.ControllerNamePrefix) + } + + testNIC = manifests.NewNginxIngressController("nginx-ingress-controller", "nginxingressclass") + testNIC.Spec.DefaultSSLCertificate = &v1alpha1.DefaultSSLCertificate{ + Secret: &v1alpha1.Secret{ + Name: "validname", + Namespace: "", + }, + } + lgr.Info("creating NginxIngressController with empty Secret field") + if err := c.Create(ctx, testNIC); err == nil { + return fmt.Errorf("able to create NginxIngressController despite missing Secret field'%s'", testNIC.Spec.ControllerNamePrefix) + } + lgr.Info("finished testing") return nil },