diff --git a/deployments/common/policy-definition.yaml b/deployments/common/policy-definition.yaml index df932458db..fd09abc967 100644 --- a/deployments/common/policy-definition.yaml +++ b/deployments/common/policy-definition.yaml @@ -53,6 +53,28 @@ spec: type: array items: type: string + egressMTLS: + description: EgressMTLS defines an Egress MTLS policy. + type: object + properties: + ciphers: + type: string + protocols: + type: string + serverName: + type: boolean + sessionReuse: + type: boolean + sslName: + type: string + tlsSecret: + type: string + trustedCertSecret: + type: string + verifyDepth: + type: integer + verifyServer: + type: boolean ingressMTLS: description: IngressMTLS defines an Ingress MTLS policy. type: object diff --git a/deployments/helm-chart/crds/policy.yaml b/deployments/helm-chart/crds/policy.yaml index 5157d5852d..8ecb8e7c3b 100644 --- a/deployments/helm-chart/crds/policy.yaml +++ b/deployments/helm-chart/crds/policy.yaml @@ -55,6 +55,26 @@ spec: type: array items: type: string + egressMTLS: + description: EgressMTLS defines an Egress MTLS policy. + type: object + properties: + ciphers: + type: string + protocols: + type: string + serverName: + type: boolean + sessionReuse: + type: boolean + tlsSecret: + type: string + trustedCertSecret: + type: string + verifyDepth: + type: integer + verifyServer: + type: boolean ingressMTLS: description: IngressMTLS defines an Ingress MTLS policy. type: object diff --git a/examples-of-custom-resources/egress-mtls/README.md b/examples-of-custom-resources/egress-mtls/README.md new file mode 100644 index 0000000000..e3c3765645 --- /dev/null +++ b/examples-of-custom-resources/egress-mtls/README.md @@ -0,0 +1,61 @@ +# Egress MTLS + +In this example, we deploy a secure web application, configure load balancing for it via a VirtualServer, and apply an Egress MTLS policy. + +## Prerequisites + +1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) instructions to deploy the Ingress Controller. +1. Save the public IP address of the Ingress Controller into a shell variable: + ``` + $ IC_IP=XXX.YYY.ZZZ.III + ``` +1. Save the HTTP port of the Ingress Controller into a shell variable: + ``` + $ IC_HTTP_PORT= + ``` + +## Step 1 - Deploy a Secure Web Application +The application requires clients to use TLS and present a client TLS certificate which it will verify. + +Create the application deployment, service and secret: +``` +$ kubectl apply -f secure-app.yaml +``` + +## Step 2 - Deploy the Egress MLTS Secret + +Create a secret with the name `egress-mtls-secret` that will be used for authentication to application: +``` +$ kubectl apply -f egress-mtls-secret.yaml +``` + +## Step 3 - Deploy the Trusted CA Secret + +Create a secret with the name `egress-trusted-ca-secret` that will be used to verify the certificate of the application: +``` +$ kubectl apply -f egress-trusted-ca-secret.yaml +``` + +## Step 4 - Deploy the Egress MTLS Policy + +Create a policy with the name `egress-mtls-policy` that references the secrets from the previous steps: +``` +$ kubectl apply -f egress-mtls.yaml +``` + +## Step 5 - Configure Load Balancing + +Create a VirtualServer resource for the web application: +``` +$ kubectl apply -f virtual-server.yaml +``` + +Note that the VirtualServer references the policy `egress-mtls-policy` created in Step 4. + +## Step 6 - Test the Configuration + +Access the secure backend with the following command: +``` +$ curl --resolve webapp.example.com:$IC_HTTP_PORT:$IC_IP http://webapp.example.com:$IC_HTTP_PORT/ +hello from pod secure-app-8cb576989-7hdhp +``` diff --git a/examples-of-custom-resources/egress-mtls/egress-mtls-secret.yaml b/examples-of-custom-resources/egress-mtls/egress-mtls-secret.yaml new file mode 100644 index 0000000000..3dc10cef79 --- /dev/null +++ b/examples-of-custom-resources/egress-mtls/egress-mtls-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: egress-mtls-secret +type: Opaque +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVRENDQWpnQ0ZHRUVvSmlwZ2QrdWY4OGI5T0prQ256SHlNMjBNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1HWXgKQ3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRRVEVXTUJRR0ExVUVCd3dOVTJGdUlFWnlZVzVqYVhOagpiekVPTUF3R0ExVUVDZ3dGVGtkSlRsZ3hEREFLQmdOVkJBc01BMHRKUXpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzClpTNWpiMjB3SUJjTk1qQXhNREE0TURJek5qSXdXaGdQTWpFeU1EQTVNVFF3TWpNMk1qQmFNR0V4Q3pBSkJnTlYKQkFZVEFsVlRNUXN3Q1FZRFZRUUlEQUpEUVRFV01CUUdBMVVFQnd3TlUyRnVJRVp5WVc1amFYTmpiekVPTUF3RwpBMVVFQ2d3RlRrZEpUbGd4RERBS0JnTlZCQXNNQTB0SlF6RVBNQTBHQTFVRUF3d0dZMnhwWlc1ME1JSUJJakFOCkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQW84WnVtQ1drUTQ0ckdwN2tVUU5ZTlRjMk1pMU4KSGp0NmxvQUVYMnUvVjFrN1Zma01qRnpBc0p2TTZoY1FaOFhaN2xJR2RTMUZNL1VlM0VUNFJFRmVqaWxscVJ3eQp3ejBqdjZoWXJ0Q05hbm1UakVmclZnSmxZcTNIa3QvNmluWmFuSmw4WXBCZ0U5bkQ2MFpGWDVybDY3MzdUNUFiCm9oSDZUYnFwVktCTGp3dlVFMXdoLzhFM25ueDNmdEd3Z1BqSGZHUGtqL0RlNmpZNi9vUDBOZFdnUWlFM0dZZEEKc2xkZlQ3NFFzYUxlQno3T3U1TWZyaFpXRlZBWHYvSVoraU9oUjRwNXUzMWY2aXRjb0krNGxrVTdnbUdPeEJwegpmYWV2Nk1MVGV6Ujh2R2Y1WnZVWXVUZ1FkZWVGQnl5ejhGR2lnczM4eURNc25qL1I0WWE4V2UzZ2R3SURBUUFCCk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQW1CRkxydEhzeUs1dlZZZjZraE1ZRDhRZHZCRUh0UjM3ajBlM28KdWJqY3JCdmcwTldma1lkMzhJWVU5VDFvYTJJaVJKdWxFWWRGZVkreHVReERtdUQyb1JpbWpYdDBaN1lhYWVMVwpIbG4zV3pqTU5MUkhGblg4aFFodUpUZWJlNTRMQ2pBM3JLZVkwRWJ4b2lDUTdzd0VGNjV3bkN4OUk2bUlieFZkCnF2MXVOZjBkN3lkbVZ1TFIwc0RiMTE5K3dvRFo2eEpjZDRZN2x6TjV2cjluUDYzR3h0bFIwQm51OGI2a3UzVXAKZnpzdHFFaTlLcFh5Nm54NE5NQ0RDN3htTUFEZldpK1BjSlJTSThSeTVLeVp2ck9FMVRpcjJ3d21iVlAwN3QvNgpSby91RGNZL0VzdUk1VFVNcmU2R3lIS2UrY09YRzRLVytQaDZSMlZjVy9mZ25jRkUKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBbzhadW1DV2tRNDRyR3A3a1VRTllOVGMyTWkxTkhqdDZsb0FFWDJ1L1YxazdWZmtNCmpGekFzSnZNNmhjUVo4WFo3bElHZFMxRk0vVWUzRVQ0UkVGZWppbGxxUnd5d3owanY2aFlydENOYW5tVGpFZnIKVmdKbFlxM0hrdC82aW5aYW5KbDhZcEJnRTluRDYwWkZYNXJsNjczN1Q1QWJvaEg2VGJxcFZLQkxqd3ZVRTF3aAovOEUzbm54M2Z0R3dnUGpIZkdQa2ovRGU2alk2L29QME5kV2dRaUUzR1lkQXNsZGZUNzRRc2FMZUJ6N091NU1mCnJoWldGVkFYdi9JWitpT2hSNHA1dTMxZjZpdGNvSSs0bGtVN2dtR094QnB6ZmFldjZNTFRlelI4dkdmNVp2VVkKdVRnUWRlZUZCeXl6OEZHaWdzMzh5RE1zbmovUjRZYThXZTNnZHdJREFRQUJBb0lCQUZxTU9WVXhhcEpYSmdtLwowNmZlMjRRQWxKeEZZNS9WYnIrSzQrQUY2SEpZeVcrTkZKeitJeU1CNE5lRURQUHk5YnpINlA0Y0cyalFtMUZaCmFKSUVPazBMSWtQUGJWbDRraTFDM0ZVNnhSVWJScktHaU55eTBtOHVlV3NCSHJvekdZSC9jai9rWnRwU3lmbG8KanE1L0s2T2RGRGMzQzdBZGxIQXBSYm1lTG1oVlZXWWZKQ2VDb0w0bEF1SHE0SVJ5aThrSnJrYzdrUnh5QUZhbApyQjgvTW5HZzZTWGlJOXdiUUdDMUltb3IxTzFlaC9KNmRhNktPRWMwNTc1ZkJvWFYxcFhORmVHUHdKZmhabU1kCndGU0FyUXVkYVA4SmhqZ0ZqK0s5SG52YllVOGkzYzlPa01pN25YeTBZZ3lrQUd0bDNrQzBZUVU2SHBJRlBXakMKSVdaMHVERUNnWUVBMEZHc3pVQjltYWJJWmFvUndsMEJ2ZUh1Z1QzVEZaVDIwSVVkTmdGWTM1YVZHN3Bpa21YRAp3U2oxREdwczJJYU9heThGTHhxRlpRa0lNVWo1VkFRTDZubWJqdjZZcnJlUlE1WmN0WENCc3hSQkN6TUpaL2lOCmV2NVJaT1p6S2VhZUtjVFUwaFplVUc4RS8zeEJxYlJkNWdSSXEzYi9YQW9KbVIvazhIR2VSbDBDZ1lFQXlVSzcKMlQ0NFV6b0JQaFN3L2NtZk9zN3FEWjBVUHZ0L2N3RWZubytGSHJkTzdsaS9UQmRlTC9vdDE5ZlJ6Ulh6dGZiaAo5VFJCNnpPNWVUYTZ5b2lReDdpVnFGbEtlT0JuMVl5dG5JQ3Zod3NaSHBMZklLMGlvUW9QVFUrdjFnZ3dyR3RQCnk3VHk0dkRYcG1xelZKN25zeDhxSDBVclhjRXBjQkFMY3IyYnJPTUNnWUJ4RWlQdE5IZjRFbUxyYTZoQUN5T0sKNFRzSHczcnpWK3V2dXREa0kwOE43VG41MXV6eDhYS1RRWnZncFZOM0kzSGlFNStJdGFoNThyRVRyenhGc2Z5Vgp4SFArNVlvU0sya1plbC9QeThWYVlqLzd5RFB1enlaYi8vWkdBaCs0T29qV3V4T1pCTE1ralYzMUhvaTFpRVQvCk9Jd0pKWW50ZHpWR2U5MVQ5UmVuc1FLQmdCVklHemtEblUwZnF4WDNkaWUvOURoeHZNb252QVpVN2NzcGFVQ0oKQ0ppUTVhWEtlS0FCTUpKK0wrN05BWUJnTzk4WDloakpwOWJKSjJtcjRlQ2N4RUo2UUVrRytCc0VEN2JESTNDVApJdnh5cmZ0UHFJeDZBbGxwQ09reDdmUUI5Yk9sdlZCYkYvdnJYOEpYTWhOV29rV01uQldtYU1tSExXeU9KY2ZDClNQM2xBb0dBTmtYT2xxa2FuZnJJUkM0aEREZ3BHREEvR052aEJjZTZaUE1YQTlpT2d0cFR5Vk4yVnQ5K1RNZnQKTUV4VVJyUnJzWXZoRUV0UlN4SmRQeG40eUdoL0xhb3F0R1VlTHg3UEUydE1WeW1mamFQd0NCMTk0ZS9YRW9uMgpqamp1Q1N0ZEdTS0kwOEw2R1hVNDJuL3I1Y3BseS9OOVk0ZHpKTmVFbVgzeXUvOUNWcXc9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== \ No newline at end of file diff --git a/examples-of-custom-resources/egress-mtls/egress-mtls.yaml b/examples-of-custom-resources/egress-mtls/egress-mtls.yaml new file mode 100644 index 0000000000..e16cdf8bb7 --- /dev/null +++ b/examples-of-custom-resources/egress-mtls/egress-mtls.yaml @@ -0,0 +1,12 @@ +apiVersion: k8s.nginx.org/v1alpha1 +kind: Policy +metadata: + name: egress-mtls-policy +spec: + egressMTLS: + tlsSecret: egress-mtls-secret + trustedCertSecret: egress-trusted-ca-secret + verifyServer: on + verifyDepth: 2 + serverName: on + sslName: secure-app.example.com diff --git a/examples-of-custom-resources/egress-mtls/egress-trusted-ca-secret.yaml b/examples-of-custom-resources/egress-mtls/egress-trusted-ca-secret.yaml new file mode 100644 index 0000000000..1771361ebb --- /dev/null +++ b/examples-of-custom-resources/egress-mtls/egress-trusted-ca-secret.yaml @@ -0,0 +1,7 @@ +kind: Secret +metadata: + name: egress-trusted-ca-secret +apiVersion: v1 +type: Opaque +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURyVENDQXBXZ0F3SUJBZ0lVRk5raHZnakkrTFhORTlGMkV2Wkk4T0dJUTlrd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pqRUxNQWtHQTFVRUJoTUNWVk14Q3pBSkJnTlZCQWdNQWtOQk1SWXdGQVlEVlFRSERBMVRZVzRnUm5KaApibU5wYzJOdk1RNHdEQVlEVlFRS0RBVk9SMGxPV0RFTU1Bb0dBMVVFQ3d3RFMwbERNUlF3RWdZRFZRUUREQXRsCmVHRnRjR3hsTG1OdmJUQWVGdzB5TURFd01EZ3dNak13TkRKYUZ3MHlNREV4TURjd01qTXdOREphTUdZeEN6QUoKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJREFKRFFURVdNQlFHQTFVRUJ3d05VMkZ1SUVaeVlXNWphWE5qYnpFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEREQUtCZ05WQkFzTUEwdEpRekVVTUJJR0ExVUVBd3dMWlhoaGJYQnNaUzVqCmIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEamFtR0ZtYnIwT1ZKUXRaakgKYVAzTmN2WnRYNVFXMW1zYU4wc0ZPOGxLaWpkTmo1V3RVcVlBQThKU2M5VnkyRXB5VVg2NzVFVnpqajZ1Vjg1MwpzT3BvL012NSs1SkJiUmFOUUJOSkJBOW9IeFJLay9VWjRIdVMyQ1pWODYrS1dXYk1yc1J2cE9GeHZzVHZpbTB3ClNPcEdJMjVneW5LQUFnS1BQMHhTbldmKzdwNDJkTDFnbzdnSW1SVVhtamhUV0d0M2pES01SeDBmNWdLTHhuRVAKOVFiUmd6MXlGcGVWdk1VTTZTTzVvd055U2I1NFlPdWo5d3c5bkFGVlN6ckpDRXV5cmQ2TGlnQS9MZkM5QWlLMwpJOEQwUHMxOGxPYWt0Zk5PaGJ2akZsRSsyd3cvazA5MEJYL0gxWUVFSXFhZXZiTTRpRU8yZEh5SXd3WEJ5U2VDCmNDWUJBZ01CQUFHalV6QlJNQjBHQTFVZERnUVdCQlFmeWlQZWVzdGd4OVNVaVcyU0N2LzBYamNHVHpBZkJnTlYKSFNNRUdEQVdnQlFmeWlQZWVzdGd4OVNVaVcyU0N2LzBYamNHVHpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwRwpDU3FHU0liM0RRRUJDd1VBQTRJQkFRQThFT3JXSkkyL0piTlNpb0oyZk5WOUVOMkt1QXc4NFNxMkQ2K3ZWRDM0ClB5bEdSZWVmUkhUWjdNY0N0aWlmNGM0dnFkcURFcHZtR3VGUWVvekxBVHAxOExaZU1WdVJvSERoRVFhZUk0amEKTFlqTk9uL21aU0hGNEp1UzRSSWR0L2pBWnRPaXgwTUljY1RXQi9qNHRDRm5udVhseHhkNFE1YkZmQXF1OWVBSgpCVldnbTQ5RHM4QW9DNmxpWUExT3hQaFJHWGpmQjZDV0FWWitCNnRhNnBBREl6S2IzZmJUNndXaU1hRjBWV3loCm5lUFRUSmVZbGdpelR3V1FDbmRaejB6S0xUejY4eTBuanNJc1ptZXJVVFVZTy9EbXY3MVpMeGtZcVNiVmIrS3oKdFo0TnRQbDl3ZlhHcnAxcnZwRTJmQzB2V0xhVWttdlpXRXZYS2R5NmYrZUMKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= \ No newline at end of file diff --git a/examples-of-custom-resources/egress-mtls/secure-app.yaml b/examples-of-custom-resources/egress-mtls/secure-app.yaml new file mode 100644 index 0000000000..dc2cf2870e --- /dev/null +++ b/examples-of-custom-resources/egress-mtls/secure-app.yaml @@ -0,0 +1,79 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: secure-app +spec: + replicas: 1 + selector: + matchLabels: + app: secure-app + template: + metadata: + labels: + app: secure-app + spec: + containers: + - name: secure-app + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8443 + volumeMounts: + - name: secret + mountPath: /etc/nginx/ssl + readOnly: true + - name: config-volume + mountPath: /etc/nginx/conf.d + volumes: + - name: secret + secret: + secretName: app-tls-secret + - name: config-volume + configMap: + name: secure-config +--- +apiVersion: v1 +kind: Service +metadata: + name: secure-app +spec: + ports: + - port: 8443 + targetPort: 8443 + protocol: TCP + name: https + selector: + app: secure-app +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: secure-config +data: + app.conf: |- + server { + listen 8443 ssl; + + server_name secure-app.example.com; + + ssl_certificate /etc/nginx/ssl/tls.crt; + ssl_certificate_key /etc/nginx/ssl/tls.key; + + ssl_verify_client on; + ssl_client_certificate /etc/nginx/ssl/ca.crt; + + default_type text/plain; + + location / { + return 200 "hello from pod $hostname\n"; + } + } +--- +apiVersion: v1 +kind: Secret +metadata: + name: app-tls-secret +type: Opaque +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURZRENDQWtnQ0ZHRUVvSmlwZ2QrdWY4OGI5T0prQ256SHlNMnpNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1HWXgKQ3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSURBSkRRVEVXTUJRR0ExVUVCd3dOVTJGdUlFWnlZVzVqYVhOagpiekVPTUF3R0ExVUVDZ3dGVGtkSlRsZ3hEREFLQmdOVkJBc01BMHRKUXpFVU1CSUdBMVVFQXd3TFpYaGhiWEJzClpTNWpiMjB3SUJjTk1qQXhNREE0TURJek16UXlXaGdQTWpFeU1EQTVNVFF3TWpNek5ESmFNSEV4Q3pBSkJnTlYKQkFZVEFsVlRNUXN3Q1FZRFZRUUlEQUpEUVRFV01CUUdBMVVFQnd3TlUyRnVJRVp5WVc1amFYTmpiekVPTUF3RwpBMVVFQ2d3RlRrZEpUbGd4RERBS0JnTlZCQXNNQTB0SlF6RWZNQjBHQTFVRUF3d1djMlZqZFhKbExXRndjQzVsCmVHRnRjR3hsTG1OdmJUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQUxCRHRtM0sKekJQQkVCOEtRclY1QVArYmtQaVppd0oveTBoVWZKVWVodTFvYWNWQlJ2NDJaMFJzVm8wWWhMeFpLMmVNZmpNRgpLRXI3UHhKS3ppSHd5VjNCZUhyOWNYUHZLeGkxOVMwbm9JazhSUWZNRm4zZC9Db0pPaDdwd2xOMkZ6NWhhS1hLCmJIUWdEN1c5cWZYbG9FMXJlNUlIeEVSZmx2cVFKUnZQUzZJcU5aZ1VaRlhKa3hrRU1sMUFvYnZtTHZUeWpoL04KaG5YVWhRSWZDVjBRRFlvempWMXduallBOW00RVJMM1JGNDBrNXBKWUZ1OWNVcTlydHJRNFREdU1tVzZ4VkZIVQpIZFhoRDlDVFBLTUcwa0VCV241em5ySzdqelFrMTdiUVpvOHpWUUVFY2tnMGM0T3dPaXY4WnNJRlhUZE9hTVJLClMvL3p4ZjUwNFVBLzRMRUNBd0VBQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQVh4NXZEdFkrSGhDUzRDdW8KajdhZXdJQ0tKNVN1eVRuK1dTWFl5bkg4cDY1Mm5ZVFZZTnRHMnhCODdGbEdSd1prZnZKQjUraG1RRVZDSmZJUwpFUTU5cFlkVmI5TDJVbXhlSVN6MmRZVFdkbDltTWp0bFJuVXBQWjljU3poRTlrK2ttYzlKVWMwQVk4cmpaQ05qClI0Z2M0dzVLaS96WXpUdkFTQ2ozVmx0a0VkNUpac2Y1bmkydEtvaE1YVS9rNjFOY2JiQXNaM28vK0Y2NGg2bFoKZzg3UzZvVDJTV1VSQmxWUEJXNzlrTVNmL0FjdXZmQS9lOERZQVR5MTFubmVqRFFLMmF2WkxMbEZrSjFPRGtmOQplc0tuUGdhUTNkak9JRzhueXhRRFB1cmVkaEhyb0pWaC90T2xnOU5jRis2TTh1cG1uNXMzWnMwRDdOTkViWEhpCi9Uc09Qdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBc0VPMmJjck1FOEVRSHdwQ3RYa0EvNXVRK0ptTEFuL0xTRlI4bFI2RzdXaHB4VUZHCi9qWm5SR3hXalJpRXZGa3JaNHgrTXdVb1N2cy9Fa3JPSWZESlhjRjRldjF4Yys4ckdMWDFMU2VnaVR4RkI4d1cKZmQzOEtnazZIdW5DVTNZWFBtRm9wY3BzZENBUHRiMnA5ZVdnVFd0N2tnZkVSRitXK3BBbEc4OUxvaW8xbUJSawpWY21UR1FReVhVQ2h1K1l1OVBLT0g4MkdkZFNGQWg4SlhSQU5pak9OWFhDZU5nRDJiZ1JFdmRFWGpTVG1rbGdXCjcxeFNyMnUydERoTU80eVpickZVVWRRZDFlRVAwSk04b3diU1FRRmFmbk9lc3J1UE5DVFh0dEJtanpOVkFRUnkKU0RSemc3QTZLL3htd2dWZE4wNW94RXBMLy9QRi9uVGhRRC9nc1FJREFRQUJBb0lCQVFDYlZFQjZSbUZLOHpLYQp0L2NMWnNQTGZMTG5jSitBcDA5andSZzhiTGRvbFQvSVZYc1RZS3ZtTHphaWhqM0tvU1hQelk0VXRmSEpDZG1OCjdCLzA1dlQ4eXhhNkE0Z3NLMUttSndzVkxMdWExR0xjdVN3RTVVOW5WOVJCRTZ4czBqOHZlTFc0akViQW10UmQKU3ZOd3YxZGhnbHRWMThSTW9KUXk0RDNqWU96OHpKQ3JLR0hKdHVxbmVBVVBZRW5TMGIrQmRHT2IrNFZ0V1VMUwpERXBwQnVWaHpSYzB6V0VqaytNWkVhS2JkaFJiOTFIM2VnMXoreEV6VWRzT3lhNmF6SFpuY1ZHWmRZOXlKbnlZCm54cVZ0TFNYWEtLb0JNa0twdFdPcXppaVJLOUU3REFDeHc3bm1ySGFRMVU3RThBQjhEbVM4V3o1ZjVYS1d5c2wKRjhxdWxUekZBb0dCQU45WXRyWnVLWURSM085ME45QlVYeUROcGNQS01zOG4wd2VIeFNHY29OdFF4Rk9zejJCYwpWZTJ5NTNadzYwaUhhTy9kV1VaZGc0Y1cwNEs3TVJWSTR5VzRHWGQ1Vzc0VTlOSDNSckZWTmFNM1IwRzVtRSs3CjZYU0pxOCtkZXZOeGRqeUFyY2pIMy9HMTFZMHh6T3ZmMGc1THRDUGV6ci8xUGtKNHludHNJbU9QQW9HQkFNb0kKMXVVMTlmTEUwUXNvcWZRYVZhSk9SY0JCM1hjK2pPeG9sK3FTNnhpcjNiNjkwSCtobTZaVnJsQnFvMHIyem5HYwowS2hiTytyNUpZOG5oT2orS2haU1hnbk9vUVNDSktsb05zdnJlMEp4cFR5TkQ0blFsS1BNRlE5dXo5NW9BR2YyCkpsUm1ZekRCNHBxQ1M0MCt2cU5jcUdvRk5xOHBnZVhFUkx4SUhsZS9Bb0dBT2o3RmIyL3RMT1lONnZodzhjaC8KQW12KzliOU9YczJ3Ny83TlB3Vzh3VlVmemY1OEdsSWFLcUlFVU5RRDErZFFTM1Y3S2FtTGVuaE9jb0prdTN0RgptWG1ZVXByNGZPcTBLZW1GcXd0Z3NJN2k2MVgrVFpUZ3ZmNkZLdUNUeHRicEVjQXhFZkptQ1gvMUVCeFRwNU80CkVQaFhJeCtnNVZpTWd3TkRNc3F4Y1NzQ2dZRUF2MzNUV1Z0azY1NmEySzNKTUlJSmFwWXZ6SU5oU3hXeGNwM00KWjl5ckVpNU1OZThwZ04rSGJRZkcrWmVwZ0hNZngxV3YrL0xGZnZoNUhnK0NEV2hpTWRnT0k5T0NrSWhlQVdleQprR040NThoWnFtTzFONVhJWE53aGxnZ2M1QnZGVHNSakhob1JwL1FOT1ErSVZxOEMrRW5wb3R1Z01qUHdWL3hJCmZnRUpLWGNDZ1lBRzNvb0dIdG9ZbnlQd0hkSXJmWUpEMHRnTDJjL054bnE3MVYvTGVpRE1pdjhmMGp1dXU2ckMKb2xqN3ZGalEvQUdQeXZBV3p5WTVzd3crcUVHOWNTRG91WFFNMkJ1MXc3aHVxMkR4cWM3STl6TzJEZVMzVDBIbgpjWWxQaXVuY1FjUWsyTFpTOCtWTE85RDVYQmdOT1BSSnd2NU1PcytTUUVadGJMd2J3ZWh6M0E9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURyVENDQXBXZ0F3SUJBZ0lVRk5raHZnakkrTFhORTlGMkV2Wkk4T0dJUTlrd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pqRUxNQWtHQTFVRUJoTUNWVk14Q3pBSkJnTlZCQWdNQWtOQk1SWXdGQVlEVlFRSERBMVRZVzRnUm5KaApibU5wYzJOdk1RNHdEQVlEVlFRS0RBVk9SMGxPV0RFTU1Bb0dBMVVFQ3d3RFMwbERNUlF3RWdZRFZRUUREQXRsCmVHRnRjR3hsTG1OdmJUQWVGdzB5TURFd01EZ3dNak13TkRKYUZ3MHlNREV4TURjd01qTXdOREphTUdZeEN6QUoKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJREFKRFFURVdNQlFHQTFVRUJ3d05VMkZ1SUVaeVlXNWphWE5qYnpFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEREQUtCZ05WQkFzTUEwdEpRekVVTUJJR0ExVUVBd3dMWlhoaGJYQnNaUzVqCmIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEamFtR0ZtYnIwT1ZKUXRaakgKYVAzTmN2WnRYNVFXMW1zYU4wc0ZPOGxLaWpkTmo1V3RVcVlBQThKU2M5VnkyRXB5VVg2NzVFVnpqajZ1Vjg1MwpzT3BvL012NSs1SkJiUmFOUUJOSkJBOW9IeFJLay9VWjRIdVMyQ1pWODYrS1dXYk1yc1J2cE9GeHZzVHZpbTB3ClNPcEdJMjVneW5LQUFnS1BQMHhTbldmKzdwNDJkTDFnbzdnSW1SVVhtamhUV0d0M2pES01SeDBmNWdLTHhuRVAKOVFiUmd6MXlGcGVWdk1VTTZTTzVvd055U2I1NFlPdWo5d3c5bkFGVlN6ckpDRXV5cmQ2TGlnQS9MZkM5QWlLMwpJOEQwUHMxOGxPYWt0Zk5PaGJ2akZsRSsyd3cvazA5MEJYL0gxWUVFSXFhZXZiTTRpRU8yZEh5SXd3WEJ5U2VDCmNDWUJBZ01CQUFHalV6QlJNQjBHQTFVZERnUVdCQlFmeWlQZWVzdGd4OVNVaVcyU0N2LzBYamNHVHpBZkJnTlYKSFNNRUdEQVdnQlFmeWlQZWVzdGd4OVNVaVcyU0N2LzBYamNHVHpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwRwpDU3FHU0liM0RRRUJDd1VBQTRJQkFRQThFT3JXSkkyL0piTlNpb0oyZk5WOUVOMkt1QXc4NFNxMkQ2K3ZWRDM0ClB5bEdSZWVmUkhUWjdNY0N0aWlmNGM0dnFkcURFcHZtR3VGUWVvekxBVHAxOExaZU1WdVJvSERoRVFhZUk0amEKTFlqTk9uL21aU0hGNEp1UzRSSWR0L2pBWnRPaXgwTUljY1RXQi9qNHRDRm5udVhseHhkNFE1YkZmQXF1OWVBSgpCVldnbTQ5RHM4QW9DNmxpWUExT3hQaFJHWGpmQjZDV0FWWitCNnRhNnBBREl6S2IzZmJUNndXaU1hRjBWV3loCm5lUFRUSmVZbGdpelR3V1FDbmRaejB6S0xUejY4eTBuanNJc1ptZXJVVFVZTy9EbXY3MVpMeGtZcVNiVmIrS3oKdFo0TnRQbDl3ZlhHcnAxcnZwRTJmQzB2V0xhVWttdlpXRXZYS2R5NmYrZUMKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= \ No newline at end of file diff --git a/examples-of-custom-resources/egress-mtls/virtual-server.yaml b/examples-of-custom-resources/egress-mtls/virtual-server.yaml new file mode 100644 index 0000000000..bc2222ff78 --- /dev/null +++ b/examples-of-custom-resources/egress-mtls/virtual-server.yaml @@ -0,0 +1,18 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: webapp +spec: + host: webapp.example.com + upstreams: + - name: secure-app + service: secure-app + port: 8443 + tls: + enable: true + routes: + - path: / + policies: + - name: egress-mtls-policy + action: + pass: secure-app diff --git a/internal/configs/configurator.go b/internal/configs/configurator.go index 935b55a308..22da4ba709 100644 --- a/internal/configs/configurator.go +++ b/internal/configs/configurator.go @@ -45,8 +45,8 @@ const WildcardSecretName = "wildcard" // JWTKeyKey is the key of the data field of a Secret where the JWK must be stored. const JWTKeyKey = "jwk" -// IngressMTLSKey is the key of the data field of a Secret where the cert must be stored. -const IngressMTLSKey = "ca.crt" +// CAKey is the key of the data field of a Secret where the cert must be stored. +const CAKey = "ca.crt" // SPIFFE filenames and modes const ( @@ -413,13 +413,14 @@ func (cnf *Configurator) addOrUpdateVirtualServer(virtualServerEx *VirtualServer tlsPemFileName = cnf.addOrUpdateTLSSecret(virtualServerEx.TLSSecret) } if virtualServerEx.IngressMTLSCert != nil { - ingressMTLSFileName = cnf.addOrUpdateIngressMTLSecret(virtualServerEx.IngressMTLSCert) + ingressMTLSFileName = cnf.addOrUpdateCASecret(virtualServerEx.IngressMTLSCert) } jwtKeys := cnf.addOrUpdateJWKSecretsForVirtualServer(virtualServerEx.JWTKeys) + egressMTLSSecrets := cnf.addOrUpdateEgressMTLSecretsForVirtualServer(virtualServerEx.EgressTLSSecrets) vsc := newVirtualServerConfigurator(cnf.cfgParams, cnf.isPlus, cnf.IsResolverConfigured(), cnf.staticCfgParams) - vsCfg, warnings := vsc.GenerateVirtualServerConfig(virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + vsCfg, warnings := vsc.GenerateVirtualServerConfig(virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSSecrets) content, err := cnf.templateExecutorV2.ExecuteVirtualServerTemplate(&vsCfg) if err != nil { return warnings, fmt.Errorf("Error generating VirtualServer config: %v: %v", name, err) @@ -585,7 +586,7 @@ func (cnf *Configurator) updateJWKSecret(ingEx *IngressEx) string { return cnf.nginxManager.GetFilenameForSecret(ingEx.Ingress.Namespace + "-" + ingEx.JWTKey.Name) } -func (cnf *Configurator) addOrUpdateIngressMTLSecret(secret *api_v1.Secret) string { +func (cnf *Configurator) addOrUpdateCASecret(secret *api_v1.Secret) string { name := objectMetaToFileName(&secret.ObjectMeta) data := GenerateCAFileContent(secret) return cnf.nginxManager.CreateSecret(name, data, nginx.TLSSecretFileMode) @@ -620,9 +621,9 @@ func (cnf *Configurator) AddOrUpdateJWKSecret(secret *api_v1.Secret, virtualServ return allWarnings, nil } -// AddOrUpdateIngressMTLSSecret adds a IngressMTLS secret to the filesystem or updates it if it already exists. -func (cnf *Configurator) AddOrUpdateIngressMTLSSecret(secret *api_v1.Secret, virtualServerExes []*VirtualServerEx) (Warnings, error) { - cnf.addOrUpdateIngressMTLSecret(secret) +// AddOrUpdateCASecret adds a CA secret to the filesystem or updates it if it already exists. +func (cnf *Configurator) AddOrUpdateCASecret(secret *api_v1.Secret, virtualServerExes []*VirtualServerEx) (Warnings, error) { + cnf.addOrUpdateCASecret(secret) allWarnings := newWarnings() @@ -660,6 +661,25 @@ func (cnf *Configurator) addOrUpdateJWKSecretsForVirtualServer(jwtKeys map[strin return jwkSecrets } +func (cnf *Configurator) addOrUpdateEgressMTLSecretsForVirtualServer(egressMTLSsecrets map[string]*api_v1.Secret) map[string]string { + + secrets := make(map[string]string) + var filename string + + for v, k := range egressMTLSsecrets { + if _, exists := k.Data[api_v1.TLSCertKey]; exists { + filename = cnf.addOrUpdateTLSSecret(k) + } + if _, exists := k.Data[CAKey]; exists { + filename = cnf.addOrUpdateCASecret(k) + + } + secrets[v] = filename + } + + return secrets +} + // AddOrUpdateResources adds or updates configuration for resources. func (cnf *Configurator) AddOrUpdateResources(ingExes []*IngressEx, mergeableIngresses []*MergeableIngresses, virtualServerExes []*VirtualServerEx) (Warnings, error) { allWarnings := newWarnings() @@ -735,7 +755,7 @@ func GenerateCertAndKeyFileContent(secret *api_v1.Secret) []byte { func GenerateCAFileContent(secret *api_v1.Secret) []byte { var res bytes.Buffer - res.Write(secret.Data[IngressMTLSKey]) + res.Write(secret.Data[CAKey]) return res.Bytes() } diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index 719470a1e7..60f44155aa 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -68,6 +68,7 @@ type Server struct { LimitReqs []LimitReq JWTAuth *JWTAuth IngressMTLS *IngressMTLS + EgressMTLS *EgressMTLS PoliciesErrorReturn *Return } @@ -86,6 +87,20 @@ type IngressMTLS struct { VerifyDepth int } +// EgressMTLS defines TLS configuration for a location. +type EgressMTLS struct { + Certificate string + CertificateKey string + VerifyServer bool + VerifyDepth int + Ciphers string + Protocols string + TrustedCert string + SessionReuse bool + ServerName bool + SSLName string +} + // Location defines a location. type Location struct { Path string @@ -121,6 +136,7 @@ type Location struct { LimitReqOptions LimitReqOptions LimitReqs []LimitReq JWTAuth *JWTAuth + EgressMTLS *EgressMTLS PoliciesErrorReturn *Return } diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index df4d542a3e..71e2290f2b 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -142,6 +142,24 @@ server { auth_jwt_key_file {{ .Secret }}; {{ end }} + {{ with $s.EgressMTLS }} + {{ if .Certificate }} + proxy_ssl_certificate {{ .Certificate }}; + proxy_ssl_certificate_key {{ .CertificateKey }}; + {{ end }} + {{ if .TrustedCert }} + proxy_ssl_trusted_certificate {{ .TrustedCert }}; + {{ end }} + + proxy_ssl_verify {{ if .VerifyServer }}on{{else}}off{{end}}; + proxy_ssl_verify_depth {{ .VerifyDepth }}; + proxy_ssl_protocols {{ .Protocols }}; + proxy_ssl_ciphers {{ .Ciphers }}; + proxy_ssl_session_reuse {{ if .SessionReuse }}on{{else}}off{{end}}; + proxy_ssl_server_name {{ if .ServerName }}on{{else}}off{{end}}; + proxy_ssl_name {{ .SSLName }}; + {{ end }} + {{ range $snippet := $s.Snippets }} {{- $snippet }} {{ end }} @@ -236,6 +254,25 @@ server { auth_jwt_key_file {{ .Secret }}; {{ end }} + {{ with $l.EgressMTLS }} + {{ if .Certificate }} + proxy_ssl_certificate {{ .Certificate }}; + proxy_ssl_certificate_key {{ .CertificateKey }}; + {{ end }} + {{ if .TrustedCert }} + proxy_ssl_trusted_certificate {{ .TrustedCert }}; + {{ end }} + + proxy_ssl_verify {{ if .VerifyServer }}on{{else}}off{{end}}; + proxy_ssl_verify_depth {{ .VerifyDepth }}; + proxy_ssl_protocols {{ .Protocols }}; + proxy_ssl_ciphers {{ .Ciphers }}; + proxy_ssl_session_reuse {{ if .SessionReuse }}on{{else}}off{{end}}; + proxy_ssl_server_name {{ if .ServerName }}on{{else}}off{{end}}; + proxy_ssl_name {{ .SSLName }}; + {{ end }} + + {{ range $e := $l.ErrorPages }} error_page {{ $e.Codes }} {{ if ne 0 $e.ResponseCode }}={{ $e.ResponseCode }}{{ end }} "{{ $e.Name }}"; {{ end }} diff --git a/internal/configs/version2/nginx.virtualserver.tmpl b/internal/configs/version2/nginx.virtualserver.tmpl index de08b3f6f2..54ebf40d44 100644 --- a/internal/configs/version2/nginx.virtualserver.tmpl +++ b/internal/configs/version2/nginx.virtualserver.tmpl @@ -120,6 +120,24 @@ server { {{ if $rl.Delay }} delay={{ $rl.Delay }}{{ end }}{{ if $rl.NoDelay }} nodelay{{ end }}; {{ end }} + {{ with $s.EgressMTLS }} + {{ if .Certificate }} + proxy_ssl_certificate {{ .Certificate }}; + proxy_ssl_certificate_key {{ .CertificateKey }}; + {{ end }} + {{ if .TrustedCert }} + proxy_ssl_trusted_certificate {{ .TrustedCert }}; + {{ end }} + + proxy_ssl_verify {{ if .VerifyServer }}on{{else}}off{{end}}; + proxy_ssl_verify_depth {{ .VerifyDepth }}; + proxy_ssl_protocols {{ .Protocols }}; + proxy_ssl_ciphers {{ .Ciphers }}; + proxy_ssl_session_reuse {{ if .SessionReuse }}on{{else}}off{{end}}; + proxy_ssl_server_name {{ if .ServerName }}on{{else}}off{{end}}; + proxy_ssl_name {{ .SSLName }}; + {{ end }} + {{ range $snippet := $s.Snippets }} {{- $snippet }} {{ end }} @@ -195,6 +213,24 @@ server { {{ if $rl.Delay }} delay={{ $rl.Delay }}{{ end }}{{ if $rl.NoDelay }} nodelay{{ end }}; {{ end }} + {{ with $l.EgressMTLS }} + {{ if .Certificate }} + proxy_ssl_certificate {{ .Certificate }}; + proxy_ssl_certificate_key {{ .CertificateKey }}; + {{ end }} + {{ if .TrustedCert }} + proxy_ssl_trusted_certificate {{ .TrustedCert }}; + {{ end }} + + proxy_ssl_verify {{ if .VerifyServer }}on{{else}}off{{end}}; + proxy_ssl_verify_depth {{ .VerifyDepth }}; + proxy_ssl_protocols {{ .Protocols }}; + proxy_ssl_ciphers {{ .Ciphers }}; + proxy_ssl_session_reuse {{ if .SessionReuse }}on{{else}}off{{end}}; + proxy_ssl_server_name {{ if .ServerName }}on{{else}}off{{end}}; + proxy_ssl_name {{ .SSLName }}; + {{ end }} + {{ range $e := $l.ErrorPages }} error_page {{ $e.Codes }} {{ if ne 0 $e.ResponseCode }}={{ $e.ResponseCode }}{{ end }} "{{ $e.Name }}"; {{ end }} diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index 2ea4cef492..6e9c5d1446 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -194,6 +194,17 @@ var virtualServerCfg = VirtualServerConfig{ Always: true, }, }, + EgressMTLS: &EgressMTLS{ + Certificate: "egress-mtls-secret.pem", + CertificateKey: "egress-mtls-secret.pem", + VerifyServer: true, + VerifyDepth: 1, + Ciphers: "DEFAULT", + Protocols: "TLSv1.3", + TrustedCert: "trusted-cert.pem", + SessionReuse: true, + ServerName: true, + }, }, { Path: "@loc0", diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index dac3f0e205..180de3f2cf 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -57,6 +57,7 @@ type VirtualServerEx struct { TLSSecret *api_v1.Secret JWTKeys map[string]*api_v1.Secret IngressMTLSCert *api_v1.Secret + EgressTLSSecrets map[string]*api_v1.Secret VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool Policies map[string]*conf_v1alpha1.Policy @@ -214,11 +215,11 @@ func (vsc *virtualServerConfigurator) generateEndpointsForUpstream(owner runtime } // GenerateVirtualServerConfig generates a full configuration for a VirtualServer -func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualServerEx, tlsPemFileName string, jwtKeys map[string]string, ingressMTLSPemFileName string) (version2.VirtualServerConfig, Warnings) { +func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualServerEx, tlsPemFileName string, jwtKeys map[string]string, ingressMTLSPemFileName string, egressMTLSSecrets map[string]string) (version2.VirtualServerConfig, Warnings) { vsc.clearWarnings() policiesCfg := vsc.generatePolicies(vsEx.VirtualServer, vsEx.VirtualServer.Namespace, vsEx.VirtualServer.Namespace, - vsEx.VirtualServer.Name, vsEx.VirtualServer.Spec.Policies, vsEx.Policies, jwtKeys, ingressMTLSPemFileName, specContext, tlsPemFileName) + vsEx.VirtualServer.Name, vsEx.VirtualServer.Spec.Policies, vsEx.Policies, jwtKeys, ingressMTLSPemFileName, specContext, tlsPemFileName, egressMTLSSecrets) // crUpstreams maps an UpstreamName to its conf_v1.Upstream as they are generated // necessary for generateLocation to know what Upstream each Location references @@ -325,7 +326,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualS vsLocSnippets := r.LocationSnippets // ingressMTLSPemFileName argument is always empty for route policies routePoliciesCfg := vsc.generatePolicies(vsEx.VirtualServer, vsEx.VirtualServer.Namespace, vsEx.VirtualServer.Namespace, vsEx.VirtualServer.Name, - r.Policies, vsEx.Policies, jwtKeys, "", routeContext, tlsPemFileName) + r.Policies, vsEx.Policies, jwtKeys, "", routeContext, tlsPemFileName, egressMTLSSecrets) limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...) if len(r.Matches) > 0 { @@ -351,6 +352,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualS } else { upstreamName := virtualServerUpstreamNamer.GetNameForUpstreamFromAction(r.Action) upstream := crUpstreams[upstreamName] + proxySSLName := generateProxySSLName(upstream.Service, vsEx.VirtualServer.Namespace) loc, returnLoc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, r.ErrorPages, false, @@ -387,11 +389,11 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualS } // ingressMTLSPemFileName argument is always empty for route policies routePoliciesCfg := vsc.generatePolicies(vsr, vsr.Namespace, vsEx.VirtualServer.Namespace, vsEx.VirtualServer.Name, - r.Policies, vsEx.Policies, jwtKeys, "", subRouteContext, tlsPemFileName) + r.Policies, vsEx.Policies, jwtKeys, "", subRouteContext, tlsPemFileName, egressMTLSSecrets) // use the VirtualServer route policies if the route does not define any if len(r.Policies) == 0 { routePoliciesCfg = vsc.generatePolicies(vsEx.VirtualServer, vsEx.VirtualServer.Namespace, vsEx.VirtualServer.Namespace, - vsEx.VirtualServer.Name, vsrPoliciesFromVs[vsrNamespaceName], vsEx.Policies, jwtKeys, "", routeContext, tlsPemFileName) + vsEx.VirtualServer.Name, vsrPoliciesFromVs[vsrNamespaceName], vsEx.Policies, jwtKeys, "", routeContext, tlsPemFileName, egressMTLSSecrets) } limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...) @@ -467,6 +469,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualS LimitReqs: policiesCfg.LimitReqs, JWTAuth: policiesCfg.JWTAuth, IngressMTLS: policiesCfg.IngressMTLS, + EgressMTLS: policiesCfg.EgressMTLS, PoliciesErrorReturn: policiesCfg.ErrorReturn, }, SpiffeCerts: vsc.spiffeCerts, @@ -483,12 +486,13 @@ type policiesCfg struct { LimitReqs []version2.LimitReq JWTAuth *version2.JWTAuth IngressMTLS *version2.IngressMTLS + EgressMTLS *version2.EgressMTLS ErrorReturn *version2.Return } // TODO refactor generatePolicies func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, ownerNamespace string, vsNamespace string, - vsName string, policyRefs []conf_v1.PolicyReference, policies map[string]*conf_v1alpha1.Policy, jwtKeys map[string]string, ingressMTLSPemFileName string, context string, tlsPemFileName string) policiesCfg { + vsName string, policyRefs []conf_v1.PolicyReference, policies map[string]*conf_v1alpha1.Policy, jwtKeys map[string]string, ingressMTLSPemFileName string, context string, tlsPemFileName string, egressMTLSSecrets map[string]string) policiesCfg { var policyErrorReturn *version2.Return var allow, deny []string var limitReqOptions version2.LimitReqOptions @@ -496,6 +500,7 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own var limitReqs []version2.LimitReq var JWTAuth *version2.JWTAuth var ingressMTLS *version2.IngressMTLS + var egressMTLS *version2.EgressMTLS var policyError bool for _, p := range policyRefs { @@ -586,6 +591,42 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own VerifyDepth: verifyDepth, } + } else if pol.Spec.EgressMTLS != nil { + if egressMTLS != nil { + vsc.addWarningf(owner, "Multiple egressMTLS policies in the same context is not valid. EgressMTLS policy %q will be ignored", key) + continue + } + + egressTLSSecret := fmt.Sprintf("%v/%v", polNamespace, pol.Spec.EgressMTLS.TLSSecret) + TrustedCertSecret := fmt.Sprintf("%v/%v", polNamespace, pol.Spec.EgressMTLS.TrustedCertSecret) + + trustedCAFileName, trustedEsists := egressMTLSSecrets[TrustedCertSecret] + if pol.Spec.EgressMTLS.TrustedCertSecret != "" && !trustedEsists { + vsc.addWarningf(owner, `EgressMTLS policy %q references a Secret which does not exist`, key) + policyError = true + break + } + + egressMTLSPemFileName, tlsExists := egressMTLSSecrets[egressTLSSecret] + if pol.Spec.EgressMTLS.TLSSecret != "" && !tlsExists { + vsc.addWarningf(owner, `EgressMTLS policy %q references a Secret which does not exist`, key) + policyError = true + break + } + + egressMTLS = &version2.EgressMTLS{ + Certificate: egressMTLSPemFileName, + CertificateKey: egressMTLSPemFileName, + Ciphers: generateString(pol.Spec.EgressMTLS.Ciphers, "DEFAULT"), + Protocols: generateString(pol.Spec.EgressMTLS.Protocols, "TLSv1 TLSv1.1 TLSv1.2"), + VerifyServer: pol.Spec.EgressMTLS.VerifyServer, + VerifyDepth: generateIntFromPointer(pol.Spec.EgressMTLS.VerifyDepth, 1), + SessionReuse: generateBool(pol.Spec.EgressMTLS.SessionReuse, true), + ServerName: pol.Spec.EgressMTLS.ServerName, + TrustedCert: trustedCAFileName, + SSLName: generateString(pol.Spec.EgressMTLS.SSLName, "$proxy_host"), + } + } } else { vsc.addWarningf(owner, "Policy %s is missing or invalid", key) @@ -609,6 +650,7 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own LimitReqs: limitReqs, JWTAuth: JWTAuth, IngressMTLS: ingressMTLS, + EgressMTLS: egressMTLS, ErrorReturn: policyErrorReturn, } } @@ -670,6 +712,7 @@ func addPoliciesCfgToLocation(cfg policiesCfg, location *version2.Location) { location.LimitReqOptions = cfg.LimitReqOptions location.LimitReqs = cfg.LimitReqs location.JWTAuth = cfg.JWTAuth + location.EgressMTLS = cfg.EgressMTLS location.PoliciesErrorReturn = cfg.ErrorReturn } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index a4c7b7d1e5..9196474b32 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -5,6 +5,7 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" "github.com/nginxinc/kubernetes-ingress/internal/nginx" conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" @@ -13,6 +14,10 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +func createPointerFromBool(b bool) *bool { + return &b +} + func TestVirtualServerExString(t *testing.T) { tests := []struct { input *VirtualServerEx @@ -599,7 +604,8 @@ func TestGenerateVirtualServerConfig(t *testing.T) { ingressMTLSFileName := "" vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{TLSPassthrough: true}) jwtKeys := make(map[string]string) - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + egressMTLSSecrets := make(map[string]string) + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSSecrets) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -704,8 +710,9 @@ func TestGenerateVirtualServerConfigWithSpiffeCerts(t *testing.T) { staticConfigParams := &StaticConfigParams{TLSPassthrough: true, NginxServiceMesh: true} vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, staticConfigParams) jwtKeys := make(map[string]string) + egressMTLSSecrets := make(map[string]string) ingressMTLSFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSSecrets) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -974,8 +981,9 @@ func TestGenerateVirtualServerConfigForVirtualServerWithSplits(t *testing.T) { tlsPemFileName := "" vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{}) jwtKeys := make(map[string]string) + egressMTLSSecrets := make(map[string]string) ingressMTLSFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSSecrets) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -1277,8 +1285,9 @@ func TestGenerateVirtualServerConfigForVirtualServerWithMatches(t *testing.T) { tlsPemFileName := "" vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{}) jwtKeys := make(map[string]string) + egressMTLSSecrets := make(map[string]string) ingressMTLSFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSSecrets) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -1750,8 +1759,9 @@ func TestGenerateVirtualServerConfigForVirtualServerWithReturns(t *testing.T) { tlsPemFileName := "" vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{}) jwtKeys := make(map[string]string) + egressMTLSSecrets := make(map[string]string) ingressMTLSFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSSecrets) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -1770,12 +1780,13 @@ func TestGeneratePolicies(t *testing.T) { tlsPemFileName := "/etc/nginx/secrets/default-tls-secret" tests := []struct { - policyRefs []conf_v1.PolicyReference - policies map[string]*conf_v1alpha1.Policy - jwtKeys map[string]string - context string - expected policiesCfg - msg string + policyRefs []conf_v1.PolicyReference + policies map[string]*conf_v1alpha1.Policy + jwtKeys map[string]string + egressMTLSSecrets map[string]string + context string + expected policiesCfg + msg string }{ { policyRefs: []conf_v1.PolicyReference{ @@ -2009,15 +2020,55 @@ func TestGeneratePolicies(t *testing.T) { }, msg: "ingressMTLS reference", }, + { + policyRefs: []conf_v1.PolicyReference{ + { + Name: "egress-mtls-policy", + Namespace: "default", + }, + }, + policies: map[string]*conf_v1alpha1.Policy{ + "default/egress-mtls-policy": { + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "egress-mtls-secret", + ServerName: true, + SessionReuse: createPointerFromBool(false), + TrustedCertSecret: "egress-trusted-ca-secret", + }, + }, + }, + }, + jwtKeys: nil, + egressMTLSSecrets: map[string]string{ + "default/egress-mtls-secret": "/etc/nginx/secrets/default-egress-mtls-secret", + "default/egress-trusted-ca-secret": "/etc/nginx/secrets/default-egress-trusted-ca-secret", + }, + context: "route", + expected: policiesCfg{ + EgressMTLS: &version2.EgressMTLS{ + Certificate: "/etc/nginx/secrets/default-egress-mtls-secret", + CertificateKey: "/etc/nginx/secrets/default-egress-mtls-secret", + Ciphers: "DEFAULT", + Protocols: "TLSv1 TLSv1.1 TLSv1.2", + ServerName: true, + SessionReuse: false, + VerifyDepth: 1, + VerifyServer: false, + TrustedCert: "/etc/nginx/secrets/default-egress-trusted-ca-secret", + SSLName: "$proxy_host", + }, + }, + msg: "egressMTLS reference", + }, } vsc := newVirtualServerConfigurator(&ConfigParams{}, false, false, &StaticConfigParams{}) for _, test := range tests { - result := vsc.generatePolicies(owner, ownerNamespace, vsNamespace, vsName, test.policyRefs, test.policies, test.jwtKeys, ingressMTLSCertPath, test.context, tlsPemFileName) - if !reflect.DeepEqual(result, test.expected) { - t.Errorf("generatePolicies() returned \n%+v but expected \n%+v for the case of %s", result, test.expected, - test.msg) + result := vsc.generatePolicies(owner, ownerNamespace, vsNamespace, vsName, test.policyRefs, test.policies, test.jwtKeys, ingressMTLSCertPath, test.context, tlsPemFileName, test.egressMTLSSecrets) + if diff := cmp.Diff(test.expected, result); diff != "" { + t.Errorf("generatePolicies() '%v' mismatch (-want +got):\n%s", test.msg, diff) } if len(vsc.warnings) > 0 { t.Errorf("generatePolicies() returned unexpected warnings %v for the case of %s", vsc.warnings, test.msg) @@ -2038,8 +2089,10 @@ func TestGeneratePoliciesFails(t *testing.T) { policyRefs []conf_v1.PolicyReference policies map[string]*conf_v1alpha1.Policy jwtKeys map[string]string + egressMTLSSecrets map[string]string ingressMTLSFileName string tlsPemFileName string + trustedCAFileName string context string expected policiesCfg expectedWarnings Warnings @@ -2357,7 +2410,7 @@ func TestGeneratePoliciesFails(t *testing.T) { `IngressMTLS policy is not allowed in the route context`, }, }, - msg: "ingress mtls reference missing secret", + msg: "ingress mtls in the wrong context", }, { policyRefs: []conf_v1.PolicyReference{ @@ -2390,15 +2443,133 @@ func TestGeneratePoliciesFails(t *testing.T) { }, msg: "ingress mtls missing TLS config", }, + { + policyRefs: []conf_v1.PolicyReference{ + { + Name: "egress-mtls-policy", + Namespace: "default", + }, + { + Name: "egress-mtls-policy2", + Namespace: "default", + }, + }, + policies: map[string]*conf_v1alpha1.Policy{ + "default/egress-mtls-policy": { + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "egress-mtls-secret", + }, + }, + }, + "default/egress-mtls-policy2": { + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "egress-mtls-secret2", + }, + }, + }, + }, + jwtKeys: nil, + context: "route", + egressMTLSSecrets: map[string]string{ + "default/egress-mtls-secret": "/etc/nginx/secrets/default-egress-mtls-secret", + }, + expected: policiesCfg{ + EgressMTLS: &version2.EgressMTLS{ + Certificate: "/etc/nginx/secrets/default-egress-mtls-secret", + CertificateKey: "/etc/nginx/secrets/default-egress-mtls-secret", + VerifyServer: false, + VerifyDepth: 1, + Ciphers: "DEFAULT", + Protocols: "TLSv1 TLSv1.1 TLSv1.2", + SessionReuse: true, + SSLName: "$proxy_host", + }, + }, + expectedWarnings: map[runtime.Object][]string{ + nil: { + `Multiple egressMTLS policies in the same context is not valid. EgressMTLS policy "default/egress-mtls-policy2" will be ignored`, + }, + }, + msg: "multi egress mtls", + }, + { + policyRefs: []conf_v1.PolicyReference{ + { + Name: "egress-mtls-policy", + Namespace: "default", + }, + }, + policies: map[string]*conf_v1alpha1.Policy{ + "default/egress-mtls-policy": { + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TrustedCertSecret: "egress-tusted-secret", + SSLName: "foo.com", + }, + }, + }, + }, + jwtKeys: nil, + context: "route", + egressMTLSSecrets: map[string]string{ + "default/egress-mtls-secret": "/etc/nginx/secrets/default-egress-mtls-secret", + }, + expected: policiesCfg{ + ErrorReturn: &version2.Return{ + Code: 500, + }, + }, + expectedWarnings: map[runtime.Object][]string{ + nil: { + `EgressMTLS policy "default/egress-mtls-policy" references a Secret which does not exist`, + }, + }, + msg: "egress mtls referencing missing CA secret", + }, + { + policyRefs: []conf_v1.PolicyReference{ + { + Name: "egress-mtls-policy", + Namespace: "default", + }, + }, + policies: map[string]*conf_v1alpha1.Policy{ + "default/egress-mtls-policy": { + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "egress-mtls-secret", + SSLName: "foo.com", + }, + }, + }, + }, + jwtKeys: nil, + context: "route", + egressMTLSSecrets: map[string]string{ + "default/egress-trusted-secret": "/etc/nginx/secrets/default-egress-trusted-secret", + }, + expected: policiesCfg{ + ErrorReturn: &version2.Return{ + Code: 500, + }, + }, + expectedWarnings: map[runtime.Object][]string{ + nil: { + `EgressMTLS policy "default/egress-mtls-policy" references a Secret which does not exist`, + }, + }, + msg: "egress mtls referencing missing tls secret", + }, } for _, test := range tests { vsc := newVirtualServerConfigurator(&ConfigParams{}, false, false, &StaticConfigParams{}) - result := vsc.generatePolicies(owner, ownerNamespace, vsNamespace, vsName, test.policyRefs, test.policies, test.jwtKeys, test.ingressMTLSFileName, test.context, test.tlsPemFileName) - if !reflect.DeepEqual(result, test.expected) { - t.Errorf("generatePolicies() returned \n%+v but expected \n%+v for the case of %s", result, test.expected, - test.msg) + result := vsc.generatePolicies(owner, ownerNamespace, vsNamespace, vsName, test.policyRefs, test.policies, test.jwtKeys, test.ingressMTLSFileName, test.context, test.tlsPemFileName, test.egressMTLSSecrets) + if diff := cmp.Diff(test.expected, result); diff != "" { + t.Errorf("generatePolicies() '%v' mismatch (-want +got):\n%s", test.msg, diff) } if !reflect.DeepEqual(vsc.warnings, test.expectedWarnings) { t.Errorf("generatePolicies() returned warnings of \n%v but expected \n%v for the case of %s", vsc.warnings, test.expectedWarnings, test.msg) diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 999386c9d8..1db054154e 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -1491,9 +1491,9 @@ func (lbc *LoadBalancerController) handleSecretUpdate(secret *api_v1.Secret, res if kind == JWK { _, _, virtualServerExes := lbc.createExtendedResources(resources) warnings, addOrUpdateErr = lbc.configurator.AddOrUpdateJWKSecret(secret, virtualServerExes) - } else if kind == IngressMTLS { + } else if kind == CA { _, _, virtualServerExes := lbc.createExtendedResources(resources) - warnings, addOrUpdateErr = lbc.configurator.AddOrUpdateIngressMTLSSecret(secret, virtualServerExes) + warnings, addOrUpdateErr = lbc.configurator.AddOrUpdateCASecret(secret, virtualServerExes) } else { ingressExes, mergeableIngresses, virtualServerExes := lbc.createExtendedResources(resources) warnings, addOrUpdateErr = lbc.configurator.AddOrUpdateTLSSecret(secret, ingressExes, mergeableIngresses, virtualServerExes) @@ -1757,13 +1757,13 @@ func (lbc *LoadBalancerController) getAndValidateJWTSecret(secretKey string) (*a return secret, nil } -func (lbc *LoadBalancerController) getAndValidateIngressMTLSSecret(secretKey string) (*api_v1.Secret, error) { +func (lbc *LoadBalancerController) getAndValidateCASecret(secretKey string) (*api_v1.Secret, error) { secret, err := lbc.getSecret(secretKey) if err != nil { return nil, err } - err = ValidateIngressMTLSSecret(secret) + err = ValidateCASecret(secret) if err != nil { return nil, fmt.Errorf("error validating secret %v", secretKey) } @@ -2007,8 +2007,9 @@ func (lbc *LoadBalancerController) getAppProtectPolicy(ing *networking.Ingress) func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1.VirtualServer, virtualServerRoutes []*conf_v1.VirtualServerRoute) *configs.VirtualServerEx { virtualServerEx := configs.VirtualServerEx{ - VirtualServer: virtualServer, - JWTKeys: make(map[string]*api_v1.Secret), + VirtualServer: virtualServer, + JWTKeys: make(map[string]*api_v1.Secret), + EgressTLSSecrets: make(map[string]*api_v1.Secret), } if virtualServer.Spec.TLS != nil && virtualServer.Spec.TLS.Secret != "" { @@ -2031,11 +2032,16 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. glog.Warningf("Error getting JWT secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) } - secret, err := lbc.getIngressMTLSSecret(policies) + ingressSecret, err := lbc.getIngressMTLSSecret(policies) if err != nil { - glog.Warningf("Error getting IngressMTLS secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) + glog.Warningf("Error getting IngressMTLS secret for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) } else { - virtualServerEx.IngressMTLSCert = secret + virtualServerEx.IngressMTLSCert = ingressSecret + } + + err = lbc.addEgressMTLSSecrets(policies, virtualServerEx.EgressTLSSecrets) + if err != nil { + glog.Warningf("Error getting EgressMTLS secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) } endpoints := make(map[string][]string) @@ -2083,6 +2089,10 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. } policies = append(policies, vsRoutePolicies...) + err = lbc.addEgressMTLSSecrets(policies, virtualServerEx.EgressTLSSecrets) + if err != nil { + glog.Warningf("Error getting EgressMTLS secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) + } err = lbc.addJWTSecrets(vsRoutePolicies, virtualServerEx.JWTKeys) if err != nil { glog.Warningf("Error getting JWT secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) @@ -2101,6 +2111,11 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. if err != nil { glog.Warningf("Error getting JWT secrets for VirtualServerRoute %v/%v: %v", vsr.Namespace, vsr.Name, err) } + + err = lbc.addEgressMTLSSecrets(policies, virtualServerEx.EgressTLSSecrets) + if err != nil { + glog.Warningf("Error getting EgressMTLS secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) + } } for _, u := range vsr.Spec.Upstreams { @@ -2233,7 +2248,7 @@ func (lbc *LoadBalancerController) getIngressMTLSSecret(policies []*conf_v1alpha continue } secretKey := fmt.Sprintf("%v/%v", pol.Namespace, pol.Spec.IngressMTLS.ClientCertSecret) - secret, err := lbc.getAndValidateIngressMTLSSecret(secretKey) + secret, err := lbc.getAndValidateCASecret(secretKey) if err != nil { return nil, fmt.Errorf("Error getting or validating the IngressMTLS secret %v for the policy %v/%v: %v", secretKey, pol.Namespace, pol.Name, err) } @@ -2243,6 +2258,32 @@ func (lbc *LoadBalancerController) getIngressMTLSSecret(policies []*conf_v1alpha return nil, nil } +func (lbc *LoadBalancerController) addEgressMTLSSecrets(policies []*conf_v1alpha1.Policy, egressSecrets map[string]*api_v1.Secret) error { + for _, pol := range policies { + if pol.Spec.EgressMTLS == nil { + continue + } + if pol.Spec.EgressMTLS.TLSSecret != "" { + secretKey := fmt.Sprintf("%v/%v", pol.Namespace, pol.Spec.EgressMTLS.TLSSecret) + secret, err := lbc.getAndValidateSecret(secretKey) + if err != nil { + return fmt.Errorf("Error getting or validating the EgressMTLS secret %v for the policy %v/%v: %v", secretKey, pol.Namespace, pol.Name, err) + } + egressSecrets[secretKey] = secret + } + if pol.Spec.EgressMTLS.TrustedCertSecret != "" { + secretKey := fmt.Sprintf("%v/%v", pol.Namespace, pol.Spec.EgressMTLS.TrustedCertSecret) + secret, err := lbc.getAndValidateCASecret(secretKey) + if err != nil { + return fmt.Errorf("Error getting or validating the TrustedCert secret %v for the policy %v/%v: %v", secretKey, pol.Namespace, pol.Name, err) + } + egressSecrets[secretKey] = secret + } + } + + return nil +} + func (lbc *LoadBalancerController) getPoliciesForSecret(secretNamespace string, secretName string) []*conf_v1alpha1.Policy { return findPoliciesForSecret(lbc.getAllPolicies(), secretNamespace, secretName) } @@ -2255,6 +2296,10 @@ func findPoliciesForSecret(policies []*conf_v1alpha1.Policy, secretNamespace str res = append(res, pol) } else if pol.Spec.JWTAuth != nil && pol.Spec.JWTAuth.Secret == secretName && pol.Namespace == secretNamespace { res = append(res, pol) + } else if pol.Spec.EgressMTLS != nil && pol.Spec.EgressMTLS.TLSSecret == secretName && pol.Namespace == secretNamespace { + res = append(res, pol) + } else if pol.Spec.EgressMTLS != nil && pol.Spec.EgressMTLS.TrustedCertSecret == secretName && pol.Namespace == secretNamespace { + res = append(res, pol) } } diff --git a/internal/k8s/controller_test.go b/internal/k8s/controller_test.go index 3187e754de..7e06b94a81 100644 --- a/internal/k8s/controller_test.go +++ b/internal/k8s/controller_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/nginxinc/kubernetes-ingress/internal/configs" "github.com/nginxinc/kubernetes-ingress/internal/configs/version1" "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" @@ -826,7 +827,7 @@ func TestGetPolicies(t *testing.T) { expectedPolicies := []*conf_v1alpha1.Policy{validPolicy} expectedErrors := []error{ - errors.New("Policy default/invalid-policy is invalid: spec: Invalid value: \"\": must specify exactly one of: `accessControl`, `rateLimit`, `ingressMTLS`, `jwt`"), + errors.New("Policy default/invalid-policy is invalid: spec: Invalid value: \"\": must specify exactly one of: `accessControl`, `rateLimit`, `ingressMTLS`, `egressMTLS`, `jwt`"), errors.New("Policy nginx-ingress/valid-policy doesn't exist"), errors.New("Failed to get policy nginx-ingress/some-policy: GetByKey error"), } @@ -1103,7 +1104,7 @@ func TestFindPoliciesForSecret(t *testing.T) { ingTLSPol := &conf_v1alpha1.Policy{ ObjectMeta: meta_v1.ObjectMeta{ - Name: "ingress-tmls-policy", + Name: "ingress-mtls-policy", Namespace: "default", }, Spec: conf_v1alpha1.PolicySpec{ @@ -1112,6 +1113,28 @@ func TestFindPoliciesForSecret(t *testing.T) { }, }, } + egTLSPol := &conf_v1alpha1.Policy{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "egress-mtls-policy", + Namespace: "default", + }, + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "egress-mtls-secret", + }, + }, + } + egTLSPol2 := &conf_v1alpha1.Policy{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "egress-trusted-policy", + Namespace: "default", + }, + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TrustedCertSecret: "egress-trusted-secret", + }, + }, + } tests := []struct { policies []*conf_v1alpha1.Policy @@ -1155,11 +1178,39 @@ func TestFindPoliciesForSecret(t *testing.T) { expected: []*v1alpha1.Policy{ingTLSPol}, msg: "Find policy in default ns, ignore other types", }, + { + policies: []*conf_v1alpha1.Policy{egTLSPol}, + secretNamespace: "default", + secretName: "egress-mtls-secret", + expected: []*v1alpha1.Policy{egTLSPol}, + msg: "Find policy in default ns", + }, + { + policies: []*conf_v1alpha1.Policy{jwtPol1, egTLSPol}, + secretNamespace: "default", + secretName: "egress-mtls-secret", + expected: []*v1alpha1.Policy{egTLSPol}, + msg: "Find policy in default ns, ignore other types", + }, + { + policies: []*conf_v1alpha1.Policy{egTLSPol2}, + secretNamespace: "default", + secretName: "egress-trusted-secret", + expected: []*v1alpha1.Policy{egTLSPol2}, + msg: "Find policy in default ns", + }, + { + policies: []*conf_v1alpha1.Policy{egTLSPol, egTLSPol2}, + secretNamespace: "default", + secretName: "egress-trusted-secret", + expected: []*v1alpha1.Policy{egTLSPol2}, + msg: "Find policy in default ns, ignore other types", + }, } for _, test := range tests { result := findPoliciesForSecret(test.policies, test.secretNamespace, test.secretName) - if !reflect.DeepEqual(result, test.expected) { - t.Errorf("findPoliciesForSecret() returned \n%v but expected \n%v for the case of %s", result, test.expected, test.msg) + if diff := cmp.Diff(test.expected, result); diff != "" { + t.Errorf("findPoliciesForSecret() '%v' mismatch (-want +got):\n%s", test.msg, diff) } } } @@ -1435,3 +1486,189 @@ func TestGetIngressMTLSSecret(t *testing.T) { } } + +func TestAddEgressMTLSSecrets(t *testing.T) { + validSecret := &v1.Secret{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "valid-egress-mtls-secret", + Namespace: "default", + }, + Data: map[string][]byte{"tls.key": nil, "tls.crt": nil}, + } + validSecret2 := &v1.Secret{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "valid-egress-trusted-secret", + Namespace: "default", + }, + Data: map[string][]byte{"ca.crt": nil}, + } + + invalidSecret := &v1.Secret{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "invalid-egress-mtls-secret", + Namespace: "default", + }, + Data: nil, + } + + tests := []struct { + policies []*conf_v1alpha1.Policy + expectedEgressMTLSSecrets map[string]*v1.Secret + wantErr bool + msg string + }{ + { + policies: []*conf_v1alpha1.Policy{ + { + ObjectMeta: meta_v1.ObjectMeta{ + Name: "egress-mtls-policy", + Namespace: "default", + }, + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "valid-egress-mtls-secret", + }, + }, + }, + }, + expectedEgressMTLSSecrets: map[string]*v1.Secret{ + "default/valid-egress-mtls-secret": validSecret, + }, + wantErr: false, + msg: "test getting valid TLS secret", + }, + { + policies: []*conf_v1alpha1.Policy{ + { + ObjectMeta: meta_v1.ObjectMeta{ + Name: "egress-egress-trusted-policy", + Namespace: "default", + }, + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TrustedCertSecret: "valid-egress-trusted-secret", + }, + }, + }, + }, + expectedEgressMTLSSecrets: map[string]*v1.Secret{ + "default/valid-egress-trusted-secret": validSecret2, + }, + wantErr: false, + msg: "test getting valid TrustedCA secret", + }, + { + policies: []*conf_v1alpha1.Policy{ + { + ObjectMeta: meta_v1.ObjectMeta{ + Name: "egress-mtls-policy", + Namespace: "default", + }, + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "valid-egress-mtls-secret", + TrustedCertSecret: "valid-egress-trusted-secret", + }, + }, + }, + }, + expectedEgressMTLSSecrets: map[string]*v1.Secret{ + "default/valid-egress-mtls-secret": validSecret, + "default/valid-egress-trusted-secret": validSecret2, + }, + wantErr: false, + msg: "test getting valid secrets", + }, + { + policies: []*conf_v1alpha1.Policy{}, + expectedEgressMTLSSecrets: map[string]*v1.Secret{}, + wantErr: false, + msg: "test getting valid secret with no policy", + }, + { + policies: []*conf_v1alpha1.Policy{ + { + ObjectMeta: meta_v1.ObjectMeta{ + Name: "ingress-mtls-policy", + Namespace: "default", + }, + Spec: conf_v1alpha1.PolicySpec{ + AccessControl: &conf_v1alpha1.AccessControl{ + Allow: []string{"127.0.0.1"}, + }, + }, + }, + }, + expectedEgressMTLSSecrets: map[string]*v1.Secret{}, + wantErr: false, + msg: "test getting valid secret with wrong policy", + }, + { + policies: []*conf_v1alpha1.Policy{ + { + ObjectMeta: meta_v1.ObjectMeta{ + Name: "egress-mtls-policy", + Namespace: "default", + }, + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "non-existing-ingress-mtls-secret", + }, + }, + }, + }, + expectedEgressMTLSSecrets: map[string]*v1.Secret{}, + wantErr: true, + msg: "test getting secret that does not exist", + }, + { + policies: []*conf_v1alpha1.Policy{ + { + ObjectMeta: meta_v1.ObjectMeta{ + Name: "egress-mtls-policy", + Namespace: "default", + }, + Spec: conf_v1alpha1.PolicySpec{ + EgressMTLS: &conf_v1alpha1.EgressMTLS{ + TLSSecret: "invalid-egress-mtls-secret", + }, + }, + }, + }, + expectedEgressMTLSSecrets: map[string]*v1.Secret{}, + wantErr: true, + msg: "test getting invalid secret", + }, + } + + for _, test := range tests { + lbc := LoadBalancerController{ + secretLister: storeToSecretLister{ + &cache.FakeCustomStore{ + GetByKeyFunc: func(key string) (item interface{}, exists bool, err error) { + switch key { + case "default/valid-egress-mtls-secret": + return validSecret, true, nil + case "default/valid-egress-trusted-secret": + return validSecret2, true, nil + case "default/invalid-egress-mtls-secret": + return invalidSecret, true, errors.New("secret is missing egress-mtls key in data") + default: + return nil, false, errors.New("GetByKey error") + } + }, + }, + }, + } + + egressTLSSecrets := make(map[string]*v1.Secret) + + err := lbc.addEgressMTLSSecrets(test.policies, egressTLSSecrets) + if (err != nil) != test.wantErr { + t.Errorf("addEgressMTLSSecrets() returned %v, for the case of %v", err, test.msg) + } + if diff := cmp.Diff(test.expectedEgressMTLSSecrets, egressTLSSecrets); diff != "" { + t.Errorf("addEgressMTLSSecrets() '%v' mismatch (-want +got):\n%s", test.msg, diff) + } + } +} diff --git a/internal/k8s/secret.go b/internal/k8s/secret.go index beea15b624..8231137d04 100644 --- a/internal/k8s/secret.go +++ b/internal/k8s/secret.go @@ -9,16 +9,16 @@ import ( // JWTKeyKey is the key of the data field of a Secret where the JWK must be stored. const JWTKeyKey = "jwk" -// ingressMTLSKey is the key of the data field of a Secret where the certificate authority must be stored. -const IngressMTLSKey = "ca.crt" +// CAKey is the key of the data field of a Secret where the certificate authority must be stored. +const CAKey = "ca.crt" const ( // TLS Secret TLS = iota + 1 // JWK Secret JWK - // IgressMTLS Secret - IngressMTLS + // CA Secret + CA ) // ValidateTLSSecret validates the secret. If it is valid, the function returns nil. @@ -49,10 +49,10 @@ func ValidateJWKSecret(secret *v1.Secret) error { return nil } -// ValidateIngressMTLSSecret validates the secret. If it is valid, the function returns nil. -func ValidateIngressMTLSSecret(secret *v1.Secret) error { - if _, exists := secret.Data[IngressMTLSKey]; !exists { - return fmt.Errorf("Secret doesn't have %v", IngressMTLSKey) +// ValidateCASecret validates the secret. If it is valid, the function returns nil. +func ValidateCASecret(secret *v1.Secret) error { + if _, exists := secret.Data[CAKey]; !exists { + return fmt.Errorf("Secret doesn't have %v", CAKey) } return nil @@ -66,8 +66,8 @@ func GetSecretKind(secret *v1.Secret) (int, error) { if err := ValidateJWKSecret(secret); err == nil { return JWK, nil } - if err := ValidateIngressMTLSSecret(secret); err == nil { - return IngressMTLS, nil + if err := ValidateCASecret(secret); err == nil { + return CA, nil } return 0, fmt.Errorf("Unknown Secret") diff --git a/internal/k8s/secret_test.go b/internal/k8s/secret_test.go index 8ecc263213..73fb132197 100644 --- a/internal/k8s/secret_test.go +++ b/internal/k8s/secret_test.go @@ -37,7 +37,7 @@ func TestGetSecretKind(t *testing.T) { "ca.crt": nil, }, }, - expected: IngressMTLS, + expected: CA, }, { secret: &v1.Secret{ ObjectMeta: meta_v1.ObjectMeta{ @@ -105,7 +105,7 @@ func TestValidateTLSSecretFail(t *testing.T) { } } -func TestValidateIngressMTLSSecretFail(t *testing.T) { +func TesValidateCASecretFail(t *testing.T) { s := &v1.Secret{ ObjectMeta: meta_v1.ObjectMeta{ @@ -118,9 +118,9 @@ func TestValidateIngressMTLSSecretFail(t *testing.T) { } e := errors.New("Secret doesn't have ca.crt") - err := ValidateIngressMTLSSecret(s) + err := ValidateCASecret(s) if err.Error() != e.Error() { - t.Errorf("ValidateIngressMTLSSecret() return %v but expected %v", err, e) + t.Errorf("ValidateCASecret() return %v but expected %v", err, e) } } diff --git a/pkg/apis/configuration/v1alpha1/types.go b/pkg/apis/configuration/v1alpha1/types.go index 3a2a0967e8..b0ef7c2c59 100644 --- a/pkg/apis/configuration/v1alpha1/types.go +++ b/pkg/apis/configuration/v1alpha1/types.go @@ -120,6 +120,7 @@ type PolicySpec struct { RateLimit *RateLimit `json:"rateLimit"` JWTAuth *JWTAuth `json:"jwt"` IngressMTLS *IngressMTLS `json:"ingressMTLS"` + EgressMTLS *EgressMTLS `json:"egressMTLS"` } // AccessControl defines an access policy based on the source IP of a request. @@ -155,6 +156,19 @@ type IngressMTLS struct { VerifyDepth *int `json:"verifyDepth"` } +// EgressMTLS defines an Egress MTLS policy. +type EgressMTLS struct { + TLSSecret string `json:"tlsSecret"` + VerifyServer bool `json:"verifyServer"` + VerifyDepth *int `json:"verifyDepth"` + Protocols string `json:"protocols"` + SessionReuse *bool `json:"sessionReuse"` + Ciphers string `json:"ciphers"` + TrustedCertSecret string `json:"trustedCertSecret"` + ServerName bool `json:"serverName"` + SSLName string `json:"sslName"` +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // PolicyList is a list of the Policy resources. diff --git a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go index eadad2a948..0ad913889c 100644 --- a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go @@ -50,6 +50,32 @@ func (in *Action) DeepCopy() *Action { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EgressMTLS) DeepCopyInto(out *EgressMTLS) { + *out = *in + if in.VerifyDepth != nil { + in, out := &in.VerifyDepth, &out.VerifyDepth + *out = new(int) + **out = **in + } + if in.SessionReuse != nil { + in, out := &in.SessionReuse, &out.SessionReuse + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressMTLS. +func (in *EgressMTLS) DeepCopy() *EgressMTLS { + if in == nil { + return nil + } + out := new(EgressMTLS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GlobalConfiguration) DeepCopyInto(out *GlobalConfiguration) { *out = *in @@ -267,6 +293,11 @@ func (in *PolicySpec) DeepCopyInto(out *PolicySpec) { *out = new(IngressMTLS) (*in).DeepCopyInto(*out) } + if in.EgressMTLS != nil { + in, out := &in.EgressMTLS, &out.EgressMTLS + *out = new(EgressMTLS) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/apis/configuration/validation/common_test.go b/pkg/apis/configuration/validation/common_test.go index 667790b135..dcaa0a05c5 100644 --- a/pkg/apis/configuration/validation/common_test.go +++ b/pkg/apis/configuration/validation/common_test.go @@ -10,6 +10,10 @@ func createPointerFromInt(n int) *int { return &n } +func createPointerFromBool(b bool) *bool { + return &b +} + func TestValidateVariable(t *testing.T) { var validVars = map[string]bool{ "scheme": true, diff --git a/pkg/apis/configuration/validation/policy.go b/pkg/apis/configuration/validation/policy.go index 08169a733d..92f79d1eed 100644 --- a/pkg/apis/configuration/validation/policy.go +++ b/pkg/apis/configuration/validation/policy.go @@ -47,8 +47,13 @@ func validatePolicySpec(spec *v1alpha1.PolicySpec, fieldPath *field.Path, isPlus fieldCount++ } + if spec.EgressMTLS != nil { + allErrs = append(allErrs, validateEgressMTLS(spec.EgressMTLS, fieldPath.Child("egressMTLS"))...) + fieldCount++ + } + if fieldCount != 1 { - msg := "must specify exactly one of: `accessControl`, `rateLimit`, `ingressMTLS`" + msg := "must specify exactly one of: `accessControl`, `rateLimit`, `ingressMTLS`, `egressMTLS`" if isPlus { msg = fmt.Sprint(msg, ", `jwt`") } @@ -143,7 +148,36 @@ func validateIngressMTLS(ingressMTLS *v1alpha1.IngressMTLS, fieldPath *field.Pat allErrs = append(allErrs, validatePositiveIntOrZero(*ingressMTLS.VerifyDepth, fieldPath.Child("verifyDepth"))...) } return allErrs +} + +func validateEgressMTLS(egressMTLS *v1alpha1.EgressMTLS, fieldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + allErrs = append(allErrs, validateSecretName(egressMTLS.TLSSecret, fieldPath.Child("tlsSecret"))...) + + if egressMTLS.VerifyServer && egressMTLS.TrustedCertSecret == "" { + return append(allErrs, field.Required(fieldPath.Child("trustedCertSecret"), "must be set when verifyServer is 'true'")) + } + allErrs = append(allErrs, validateSecretName(egressMTLS.TrustedCertSecret, fieldPath.Child("trustedCertSecret"))...) + + if egressMTLS.VerifyDepth != nil { + allErrs = append(allErrs, validatePositiveIntOrZero(*egressMTLS.VerifyDepth, fieldPath.Child("verifyDepth"))...) + } + + allErrs = append(allErrs, validateSSLName(egressMTLS.SSLName, fieldPath.Child("sslName"))...) + + return allErrs +} +func validateSSLName(name string, fieldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if name != "" { + for _, msg := range validation.IsDNS1123Subdomain(name) { + allErrs = append(allErrs, field.Invalid(fieldPath, name, msg)) + } + } + return allErrs } var validateVerifyClientKeyParameters = map[string]bool{ diff --git a/pkg/apis/configuration/validation/policy_test.go b/pkg/apis/configuration/validation/policy_test.go index c6c1ae85a2..acfda3489e 100644 --- a/pkg/apis/configuration/validation/policy_test.go +++ b/pkg/apis/configuration/validation/policy_test.go @@ -597,3 +597,85 @@ func TestValidateIngressMTLSVerifyClient(t *testing.T) { } } } + +func TestValidateEgressMTLS(t *testing.T) { + tests := []struct { + eg *v1alpha1.EgressMTLS + msg string + }{ + { + eg: &v1alpha1.EgressMTLS{ + TLSSecret: "mtls-secret", + }, + msg: "tls secret", + }, + { + eg: &v1alpha1.EgressMTLS{ + TrustedCertSecret: "tls-secret", + VerifyServer: true, + VerifyDepth: createPointerFromInt(2), + ServerName: false, + }, + msg: "verify server set to true", + }, + { + eg: &v1alpha1.EgressMTLS{ + VerifyServer: false, + }, + msg: "verify server set to false", + }, + { + eg: &v1alpha1.EgressMTLS{ + SSLName: "foo.com", + }, + msg: "ssl name", + }, + } + for _, test := range tests { + allErrs := validateEgressMTLS(test.eg, field.NewPath("egressMTLS")) + if len(allErrs) != 0 { + t.Errorf("validateEgressMTLS() returned errors %v for valid input for the case of %v", allErrs, test.msg) + } + } +} + +func TestValidateEgressMTLSInvalid(t *testing.T) { + tests := []struct { + eg *v1alpha1.EgressMTLS + msg string + }{ + { + eg: &v1alpha1.EgressMTLS{ + VerifyServer: true, + }, + msg: "verify server set to true", + }, + { + eg: &v1alpha1.EgressMTLS{ + TrustedCertSecret: "-foo-", + }, + msg: "invalid secret name", + }, + { + eg: &v1alpha1.EgressMTLS{ + TrustedCertSecret: "ingress-mtls-secret", + VerifyServer: true, + VerifyDepth: createPointerFromInt(-1), + }, + msg: "invalid depth", + }, + { + eg: &v1alpha1.EgressMTLS{ + SSLName: "foo.com;", + }, + msg: "invalid name", + }, + } + + for _, test := range tests { + allErrs := validateEgressMTLS(test.eg, field.NewPath("egressMTLS")) + if len(allErrs) == 0 { + t.Errorf("validateEgressMTLS() returned no errors for invalid input for the case of %v", test.msg) + } + } +}