From c3d795fda80e95b11029040822dbfe2abdd2527f Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Wed, 7 Oct 2020 15:17:34 -0700 Subject: [PATCH 1/8] Add support for EgressMTLS --- deployments/common/policy-definition.yaml | 22 +++ deployments/helm-chart/crds/policy.yaml | 20 +++ .../egress-mtls/README.md | 60 +++++++ .../egress-mtls/egress-mtls-secret.yaml | 8 + .../egress-mtls/egress-mtls.yaml | 12 ++ .../egress-mtls/egress-trusted-ca-secret.yaml | 7 + .../egress-mtls/secure-app.yaml | 79 +++++++++ .../egress-mtls/virtual-server.yaml | 16 ++ internal/configs/configurator.go | 20 ++- internal/configs/version2/http.go | 15 ++ .../version2/nginx-plus.virtualserver.tmpl | 19 +++ .../configs/version2/nginx.virtualserver.tmpl | 18 ++ internal/configs/version2/templates_test.go | 11 ++ internal/configs/virtualserver.go | 46 ++++- internal/configs/virtualserver_test.go | 161 ++++++++++++++++-- internal/k8s/controller.go | 71 +++++++- internal/k8s/controller_test.go | 2 +- internal/k8s/secret.go | 18 +- internal/k8s/secret_test.go | 8 +- pkg/apis/configuration/v1alpha1/types.go | 14 ++ .../v1alpha1/zz_generated.deepcopy.go | 41 +++++ .../configuration/validation/common_test.go | 4 + pkg/apis/configuration/validation/policy.go | 25 ++- .../configuration/validation/policy_test.go | 34 ++++ 24 files changed, 682 insertions(+), 49 deletions(-) create mode 100644 examples-of-custom-resources/egress-mtls/README.md create mode 100644 examples-of-custom-resources/egress-mtls/egress-mtls-secret.yaml create mode 100644 examples-of-custom-resources/egress-mtls/egress-mtls.yaml create mode 100644 examples-of-custom-resources/egress-mtls/egress-trusted-ca-secret.yaml create mode 100644 examples-of-custom-resources/egress-mtls/secure-app.yaml create mode 100644 examples-of-custom-resources/egress-mtls/virtual-server.yaml 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..19dd7827b2 --- /dev/null +++ b/examples-of-custom-resources/egress-mtls/README.md @@ -0,0 +1,60 @@ +# Egress MTLS + +In this example, we deploy a 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 + +Create the application deployment, service and secret: +``` +$ kubectl apply -f secure-webapp.yaml +``` + +## Step 2 - Deploy the Egress MLTS Secret + +Create a secret with the name `egress-mtls-secret` that will be used for authentication to the Secure Web 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 certificates of the Secure Web 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 3. + +## 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..93e135df35 --- /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" \ No newline at end of file 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..84dfaec002 --- /dev/null +++ b/examples-of-custom-resources/egress-mtls/virtual-server.yaml @@ -0,0 +1,16 @@ +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: webapp +spec: + host: webapp.example.com + upstreams: + - name: secure-app + service: secure-app + port: 8443 + 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..301211daac 100644 --- a/internal/configs/configurator.go +++ b/internal/configs/configurator.go @@ -407,19 +407,27 @@ func (cnf *Configurator) addOrUpdateOpenTracingTracerConfig(content string) erro func (cnf *Configurator) addOrUpdateVirtualServer(virtualServerEx *VirtualServerEx) (Warnings, error) { var tlsPemFileName string var ingressMTLSFileName string + var egressMTLSFileName string + var trustedCAFileName string name := getFileNameForVirtualServer(virtualServerEx.VirtualServer) if virtualServerEx.TLSSecret != nil { tlsPemFileName = cnf.addOrUpdateTLSSecret(virtualServerEx.TLSSecret) } if virtualServerEx.IngressMTLSCert != nil { - ingressMTLSFileName = cnf.addOrUpdateIngressMTLSecret(virtualServerEx.IngressMTLSCert) + ingressMTLSFileName = cnf.addOrUpdateCASecret(virtualServerEx.IngressMTLSCert) + } + if virtualServerEx.TrustedCASecret != nil { + trustedCAFileName = cnf.addOrUpdateCASecret(virtualServerEx.TrustedCASecret) + } + if virtualServerEx.EgressTLSSecret != nil { + egressMTLSFileName = cnf.addOrUpdateTLSSecret(virtualServerEx.EgressTLSSecret) } jwtKeys := cnf.addOrUpdateJWKSecretsForVirtualServer(virtualServerEx.JWTKeys) 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, egressMTLSFileName, trustedCAFileName) content, err := cnf.templateExecutorV2.ExecuteVirtualServerTemplate(&vsCfg) if err != nil { return warnings, fmt.Errorf("Error generating VirtualServer config: %v: %v", name, err) @@ -585,7 +593,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 +628,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() diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index 719470a1e7..cb16a893e7 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -86,6 +86,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 +135,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..a3503155d7 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -236,6 +236,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..88bc7c1239 100644 --- a/internal/configs/version2/nginx.virtualserver.tmpl +++ b/internal/configs/version2/nginx.virtualserver.tmpl @@ -195,6 +195,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..01313c36a9 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -57,6 +57,8 @@ type VirtualServerEx struct { TLSSecret *api_v1.Secret JWTKeys map[string]*api_v1.Secret IngressMTLSCert *api_v1.Secret + EgressTLSSecret *api_v1.Secret + TrustedCASecret *api_v1.Secret VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool Policies map[string]*conf_v1alpha1.Policy @@ -214,11 +216,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, egressMTLSPemFileName string, trustedCAFileName 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, egressMTLSPemFileName, trustedCAFileName) // 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 +327,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, egressMTLSPemFileName, trustedCAFileName) limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...) if len(r.Matches) > 0 { @@ -351,6 +353,10 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualS } else { upstreamName := virtualServerUpstreamNamer.GetNameForUpstreamFromAction(r.Action) upstream := crUpstreams[upstreamName] + if routePoliciesCfg.EgressMTLS != nil { + upstream.TLS.Enable = true + } + proxySSLName := generateProxySSLName(upstream.Service, vsEx.VirtualServer.Namespace) loc, returnLoc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, r.ErrorPages, false, @@ -387,11 +393,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, egressMTLSPemFileName, trustedCAFileName) // 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, egressMTLSPemFileName, trustedCAFileName) } limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...) @@ -483,12 +489,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, egressMTLSPemFileName string, trustedCAFileName string) policiesCfg { var policyErrorReturn *version2.Return var allow, deny []string var limitReqOptions version2.LimitReqOptions @@ -496,6 +503,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 +594,30 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own VerifyDepth: verifyDepth, } + } else if pol.Spec.EgressMTLS != nil { + if context != routeContext { + vsc.addWarningf(owner, `EgressMTLS policy is not allowed in the %v context`, context) + policyError = true + break + } + if egressMTLS != nil { + vsc.addWarningf(owner, "Multiple egressMTLS policies are not allowed. EgressMTLS policy %q will be ignored", key) + continue + } + + egressMTLS = &version2.EgressMTLS{ + Certificate: generateString(egressMTLSPemFileName, ""), + CertificateKey: generateString(egressMTLSPemFileName, ""), + Ciphers: generateString(pol.Spec.EgressMTLS.Ciphers, "DEFAULT"), + Protocols: generateString(pol.Spec.EgressMTLS.Protocols, "TLSv1 TLSv1.1 TLSv1.2"), + VerifyServer: generateBool(pol.Spec.EgressMTLS.VerifyServer, false), + VerifyDepth: generateIntFromPointer(pol.Spec.EgressMTLS.VerifyDepth, 1), + SessionReuse: generateBool(pol.Spec.EgressMTLS.SessionReuse, true), + ServerName: generateBool(pol.Spec.EgressMTLS.ServerName, false), + TrustedCert: generateString(trustedCAFileName, ""), + SSLName: generateString(pol.Spec.EgressMTLS.SSLName, "$proxy_host"), + } + } } else { vsc.addWarningf(owner, "Policy %s is missing or invalid", key) @@ -609,6 +641,7 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own LimitReqs: limitReqs, JWTAuth: JWTAuth, IngressMTLS: ingressMTLS, + EgressMTLS: egressMTLS, ErrorReturn: policyErrorReturn, } } @@ -670,6 +703,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..a23c27ce80 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 @@ -597,9 +602,11 @@ func TestGenerateVirtualServerConfig(t *testing.T) { isResolverConfigured := false tlsPemFileName := "" ingressMTLSFileName := "" + egressMTLSFileName := "" + trustedCAFileName := "" vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{TLSPassthrough: true}) jwtKeys := make(map[string]string) - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -705,7 +712,9 @@ func TestGenerateVirtualServerConfigWithSpiffeCerts(t *testing.T) { vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, staticConfigParams) jwtKeys := make(map[string]string) ingressMTLSFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + egressMTLSFileName := "" + trustedCAFileName := "" + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -975,7 +984,9 @@ func TestGenerateVirtualServerConfigForVirtualServerWithSplits(t *testing.T) { vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{}) jwtKeys := make(map[string]string) ingressMTLSFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + egressMTLSFileName := "" + trustedCAFileName := "" + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -1278,7 +1289,9 @@ func TestGenerateVirtualServerConfigForVirtualServerWithMatches(t *testing.T) { vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{}) jwtKeys := make(map[string]string) ingressMTLSFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + egressMTLSFileName := "" + trustedCAFileName := "" + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -1751,7 +1764,9 @@ func TestGenerateVirtualServerConfigForVirtualServerWithReturns(t *testing.T) { vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{}) jwtKeys := make(map[string]string) ingressMTLSFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName) + egressMTLSFileName := "" + trustedCAFileName := "" + result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) if !reflect.DeepEqual(result, expected) { t.Errorf("GenerateVirtualServerConfig returned \n%+v but expected \n%+v", result, expected) } @@ -1767,6 +1782,8 @@ func TestGeneratePolicies(t *testing.T) { vsNamespace := "default" vsName := "test" ingressMTLSCertPath := "/etc/nginx/secrets/default-ingress-mtls-secret" + egressMTLSCertPath := "/etc/nginx/secrets/default-egress-mtls-secret" + caCertPath := "/etc/nginx/secrets/default-egress-trusted-ca-secret" tlsPemFileName := "/etc/nginx/secrets/default-tls-secret" tests := []struct { @@ -2009,15 +2026,50 @@ 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: createPointerFromBool(true), + SessionReuse: createPointerFromBool(false), + TrustedCertSecret: "egress-ca-secret", + }, + }, + }, + }, + jwtKeys: nil, + context: "route", + expected: policiesCfg{ + EgressMTLS: &version2.EgressMTLS{ + Certificate: egressMTLSCertPath, + CertificateKey: egressMTLSCertPath, + Ciphers: "DEFAULT", + Protocols: "TLSv1 TLSv1.1 TLSv1.2", + ServerName: true, + SessionReuse: false, + VerifyDepth: 1, + TrustedCert: caCertPath, + 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, egressMTLSCertPath, caCertPath) + 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) @@ -2040,6 +2092,8 @@ func TestGeneratePoliciesFails(t *testing.T) { jwtKeys map[string]string ingressMTLSFileName string tlsPemFileName string + egressMTLSFileName string + trustedCAFileName string context string expected policiesCfg expectedWarnings Warnings @@ -2357,7 +2411,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 +2444,94 @@ func TestGeneratePoliciesFails(t *testing.T) { }, msg: "ingress mtls missing TLS config", }, + { + 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", + }, + }, + }, + }, + jwtKeys: nil, + egressMTLSFileName: "/etc/nginx/secrets/default-ingress-mtls-secret", + context: "server", + expected: policiesCfg{ + ErrorReturn: &version2.Return{ + Code: 500, + }, + }, + expectedWarnings: map[runtime.Object][]string{ + nil: { + `EgressMTLS policy is not allowed in the server context`, + }, + }, + msg: "egress mtls in the wrong context", + }, + { + 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", + egressMTLSFileName: "/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 are not allowed. EgressMTLS policy "default/egress-mtls-policy2" will be ignored`, + }, + }, + msg: "multi egress mtls", + }, } 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.egressMTLSFileName, test.trustedCAFileName) + 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 bca74a7e9d..438495cb4f 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) } @@ -2031,11 +2031,11 @@ 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 } endpoints := make(map[string][]string) @@ -2082,6 +2082,20 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. glog.Warningf("Error getting policy for VirtualServer %s/%s: %v", virtualServer.Namespace, virtualServer.Name, err) } policies = append(policies, vsRoutePolicies...) + + egressSecret, err := lbc.getEgressMTLSSecret(policies) + if err != nil { + glog.Warningf("Error getting EgressMTLS secret for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) + } else { + virtualServerEx.EgressTLSSecret = egressSecret + } + + trustedCA, err := lbc.getTrustedCertSecret(policies) + if err != nil { + glog.Warningf("Error getting TrustedCertSecret secret for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) + } else { + virtualServerEx.TrustedCASecret = trustedCA + } } for _, vsr := range virtualServerRoutes { @@ -2096,6 +2110,13 @@ 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) } + + egressSecret, err := lbc.getEgressMTLSSecret(policies) + if err != nil { + glog.Warningf("Error getting EgressMTLS secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) + } else { + virtualServerEx.EgressTLSSecret = egressSecret + } } for _, u := range vsr.Spec.Upstreams { @@ -2228,7 +2249,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) } @@ -2238,6 +2259,38 @@ func (lbc *LoadBalancerController) getIngressMTLSSecret(policies []*conf_v1alpha return nil, nil } +func (lbc *LoadBalancerController) getEgressMTLSSecret(policies []*conf_v1alpha1.Policy) (*api_v1.Secret, error) { + for _, pol := range policies { + if pol.Spec.EgressMTLS == nil || pol.Spec.EgressMTLS.TLSSecret == "" { + continue + } + secretKey := fmt.Sprintf("%v/%v", pol.Namespace, pol.Spec.EgressMTLS.TLSSecret) + secret, err := lbc.getAndValidateSecret(secretKey) + if err != nil { + return nil, fmt.Errorf("Error getting or validating the EgressMTLS secret %v for the policy %v/%v: %v", secretKey, pol.Namespace, pol.Name, err) + } + return secret, nil + } + + return nil, nil +} + +func (lbc *LoadBalancerController) getTrustedCertSecret(policies []*conf_v1alpha1.Policy) (*api_v1.Secret, error) { + for _, pol := range policies { + if pol.Spec.EgressMTLS == nil || pol.Spec.EgressMTLS.TrustedCertSecret == "" { + continue + } + secretKey := fmt.Sprintf("%v/%v", pol.Namespace, pol.Spec.EgressMTLS.TrustedCertSecret) + secret, err := lbc.getAndValidateCASecret(secretKey) + if err != nil { + return nil, fmt.Errorf("Error getting or validating the TrustedCert secret %v for the policy %v/%v: %v", secretKey, pol.Namespace, pol.Name, err) + } + return secret, nil + } + + return nil, nil +} + func (lbc *LoadBalancerController) getPoliciesForSecret(secretNamespace string, secretName string) []*conf_v1alpha1.Policy { return findPoliciesForSecret(lbc.getAllPolicies(), secretNamespace, secretName) } @@ -2250,6 +2303,8 @@ 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) } } diff --git a/internal/k8s/controller_test.go b/internal/k8s/controller_test.go index 3187e754de..5297b7cbec 100644 --- a/internal/k8s/controller_test.go +++ b/internal/k8s/controller_test.go @@ -826,7 +826,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"), } diff --git a/internal/k8s/secret.go b/internal/k8s/secret.go index beea15b624..ba15feb76e 100644 --- a/internal/k8s/secret.go +++ b/internal/k8s/secret.go @@ -10,15 +10,15 @@ import ( 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" +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..ef016e3b60 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..5c03e5f73b 100644 --- a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go @@ -50,6 +50,42 @@ 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.VerifyServer != nil { + in, out := &in.VerifyServer, &out.VerifyServer + *out = new(bool) + **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 + } + if in.ServerName != nil { + in, out := &in.ServerName, &out.ServerName + *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 +303,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..be206297d3 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`") } @@ -146,6 +151,24 @@ func validateIngressMTLS(ingressMTLS *v1alpha1.IngressMTLS, fieldPath *field.Pat } +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 != nil && *egressMTLS.VerifyServer == true && egressMTLS.TrustedCertSecret == "" { + return append(allErrs, field.Required(fieldPath.Child("trustedCertSecret"), "trustedCertSecret is required when enabling verifyServer")) + } + allErrs = append(allErrs, validateSecretName(egressMTLS.TrustedCertSecret, fieldPath.Child("trustedCertSecret"))...) + + if egressMTLS.VerifyDepth != nil { + allErrs = append(allErrs, validatePositiveIntOrZero(*egressMTLS.VerifyDepth, fieldPath.Child("verifyDepth"))...) + } + + return allErrs + +} + var validateVerifyClientKeyParameters = map[string]bool{ "on": true, "off": true, diff --git a/pkg/apis/configuration/validation/policy_test.go b/pkg/apis/configuration/validation/policy_test.go index c6c1ae85a2..d941e191f2 100644 --- a/pkg/apis/configuration/validation/policy_test.go +++ b/pkg/apis/configuration/validation/policy_test.go @@ -597,3 +597,37 @@ 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: createPointerFromBool(true), + VerifyDepth: createPointerFromInt(2), + }, + msg: "verify server set to true", + }, + { + eg: &v1alpha1.EgressMTLS{ + VerifyServer: createPointerFromBool(false), + }, + msg: "verify server set to false", + }, + } + for _, test := range tests { + allErrs := validateEgressMTLS(test.eg, field.NewPath("egressMTLS")) + if len(allErrs) != 0 { + t.Errorf("validateIngressMTLS() returned errors %v for valid input for the case of %v", allErrs, test.msg) + } + } +} From 028f11805839197f865075fa0f38ea2747bb9f8c Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Thu, 8 Oct 2020 13:11:33 -0700 Subject: [PATCH 2/8] Apply suggestions from code review Co-authored-by: Michael Pleshakov Co-authored-by: Lorcan McVeigh --- examples-of-custom-resources/egress-mtls/README.md | 8 ++++---- examples-of-custom-resources/egress-mtls/egress-mtls.yaml | 2 +- internal/configs/virtualserver.go | 2 +- pkg/apis/configuration/validation/policy.go | 4 ++-- pkg/apis/configuration/validation/policy_test.go | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples-of-custom-resources/egress-mtls/README.md b/examples-of-custom-resources/egress-mtls/README.md index 19dd7827b2..466e21cff5 100644 --- a/examples-of-custom-resources/egress-mtls/README.md +++ b/examples-of-custom-resources/egress-mtls/README.md @@ -1,6 +1,6 @@ # Egress MTLS -In this example, we deploy a web application, configure load balancing for it via a VirtualServer, and apply an Egress MTLS policy. +In this example, we deploy a secure web application, configure load balancing for it via a VirtualServer, and apply an Egress MTLS policy. ## Prerequisites @@ -18,12 +18,12 @@ In this example, we deploy a web application, configure load balancing for it vi Create the application deployment, service and secret: ``` -$ kubectl apply -f secure-webapp.yaml +$ 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 the Secure Web Application: +Create a secret with the name `egress-mtls-secret` that will be used for authentication to application: ``` $ kubectl apply -f egress-mtls-secret.yaml ``` @@ -49,7 +49,7 @@ 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 3. +Note that the VirtualServer references the policy `egress-mtls-policy` created in Step 4. ## Step 6 - Test the Configuration diff --git a/examples-of-custom-resources/egress-mtls/egress-mtls.yaml b/examples-of-custom-resources/egress-mtls/egress-mtls.yaml index 93e135df35..e16cdf8bb7 100644 --- a/examples-of-custom-resources/egress-mtls/egress-mtls.yaml +++ b/examples-of-custom-resources/egress-mtls/egress-mtls.yaml @@ -9,4 +9,4 @@ spec: verifyServer: on verifyDepth: 2 serverName: on - sslName: "secure-app.example.com" \ No newline at end of file + sslName: secure-app.example.com diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index 01313c36a9..338fb1a707 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -601,7 +601,7 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own break } if egressMTLS != nil { - vsc.addWarningf(owner, "Multiple egressMTLS policies are not allowed. EgressMTLS policy %q will be ignored", key) + vsc.addWarningf(owner, "Multiple egressMTLS policies in the same context is not valid. EgressMTLS policy %q will be ignored", key) continue } diff --git a/pkg/apis/configuration/validation/policy.go b/pkg/apis/configuration/validation/policy.go index be206297d3..8f64e90afb 100644 --- a/pkg/apis/configuration/validation/policy.go +++ b/pkg/apis/configuration/validation/policy.go @@ -156,8 +156,8 @@ func validateEgressMTLS(egressMTLS *v1alpha1.EgressMTLS, fieldPath *field.Path) allErrs = append(allErrs, validateSecretName(egressMTLS.TLSSecret, fieldPath.Child("tlsSecret"))...) - if egressMTLS.VerifyServer != nil && *egressMTLS.VerifyServer == true && egressMTLS.TrustedCertSecret == "" { - return append(allErrs, field.Required(fieldPath.Child("trustedCertSecret"), "trustedCertSecret is required when enabling verifyServer")) + if egressMTLS.VerifyServer != nil && *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"))...) diff --git a/pkg/apis/configuration/validation/policy_test.go b/pkg/apis/configuration/validation/policy_test.go index d941e191f2..3c4b96345f 100644 --- a/pkg/apis/configuration/validation/policy_test.go +++ b/pkg/apis/configuration/validation/policy_test.go @@ -627,7 +627,7 @@ func TestValidateEgressMTLS(t *testing.T) { for _, test := range tests { allErrs := validateEgressMTLS(test.eg, field.NewPath("egressMTLS")) if len(allErrs) != 0 { - t.Errorf("validateIngressMTLS() returned errors %v for valid input for the case of %v", allErrs, test.msg) + t.Errorf("validateEgressMTLS() returned errors %v for valid input for the case of %v", allErrs, test.msg) } } } From 262aee398239a4ab008664fcb6efe1053a549a19 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Thu, 8 Oct 2020 23:19:52 -0700 Subject: [PATCH 3/8] PR feedback --- .../egress-mtls/README.md | 1 + .../egress-mtls/virtual-server.yaml | 4 +- internal/configs/configurator.go | 36 ++- internal/configs/virtualserver.go | 31 +-- internal/configs/virtualserver_test.go | 99 +++---- internal/k8s/controller.go | 69 +++-- internal/k8s/controller_test.go | 243 +++++++++++++++++- internal/k8s/secret.go | 2 +- pkg/apis/configuration/validation/policy.go | 13 +- .../configuration/validation/policy_test.go | 47 ++++ 10 files changed, 405 insertions(+), 140 deletions(-) diff --git a/examples-of-custom-resources/egress-mtls/README.md b/examples-of-custom-resources/egress-mtls/README.md index 466e21cff5..024a74313e 100644 --- a/examples-of-custom-resources/egress-mtls/README.md +++ b/examples-of-custom-resources/egress-mtls/README.md @@ -15,6 +15,7 @@ In this example, we deploy a secure web application, configure load balancing fo ``` ## 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: ``` diff --git a/examples-of-custom-resources/egress-mtls/virtual-server.yaml b/examples-of-custom-resources/egress-mtls/virtual-server.yaml index 84dfaec002..bc2222ff78 100644 --- a/examples-of-custom-resources/egress-mtls/virtual-server.yaml +++ b/examples-of-custom-resources/egress-mtls/virtual-server.yaml @@ -8,9 +8,11 @@ spec: - name: secure-app service: secure-app port: 8443 + tls: + enable: true routes: - path: / policies: - - name: egress-mtls-policy + - name: egress-mtls-policy action: pass: secure-app diff --git a/internal/configs/configurator.go b/internal/configs/configurator.go index 301211daac..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 ( @@ -407,8 +407,6 @@ func (cnf *Configurator) addOrUpdateOpenTracingTracerConfig(content string) erro func (cnf *Configurator) addOrUpdateVirtualServer(virtualServerEx *VirtualServerEx) (Warnings, error) { var tlsPemFileName string var ingressMTLSFileName string - var egressMTLSFileName string - var trustedCAFileName string name := getFileNameForVirtualServer(virtualServerEx.VirtualServer) if virtualServerEx.TLSSecret != nil { @@ -417,17 +415,12 @@ func (cnf *Configurator) addOrUpdateVirtualServer(virtualServerEx *VirtualServer if virtualServerEx.IngressMTLSCert != nil { ingressMTLSFileName = cnf.addOrUpdateCASecret(virtualServerEx.IngressMTLSCert) } - if virtualServerEx.TrustedCASecret != nil { - trustedCAFileName = cnf.addOrUpdateCASecret(virtualServerEx.TrustedCASecret) - } - if virtualServerEx.EgressTLSSecret != nil { - egressMTLSFileName = cnf.addOrUpdateTLSSecret(virtualServerEx.EgressTLSSecret) - } 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, egressMTLSFileName, trustedCAFileName) + 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) @@ -668,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() @@ -743,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/virtualserver.go b/internal/configs/virtualserver.go index 338fb1a707..97aa20d5bc 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -57,7 +57,7 @@ type VirtualServerEx struct { TLSSecret *api_v1.Secret JWTKeys map[string]*api_v1.Secret IngressMTLSCert *api_v1.Secret - EgressTLSSecret *api_v1.Secret + EgressTLSSecrets map[string]*api_v1.Secret TrustedCASecret *api_v1.Secret VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool @@ -216,11 +216,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, egressMTLSPemFileName string, trustedCAFileName 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, egressMTLSPemFileName, trustedCAFileName) + 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 @@ -327,7 +327,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, egressMTLSPemFileName, trustedCAFileName) + r.Policies, vsEx.Policies, jwtKeys, "", routeContext, tlsPemFileName, egressMTLSSecrets) limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...) if len(r.Matches) > 0 { @@ -353,9 +353,6 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(vsEx *VirtualS } else { upstreamName := virtualServerUpstreamNamer.GetNameForUpstreamFromAction(r.Action) upstream := crUpstreams[upstreamName] - if routePoliciesCfg.EgressMTLS != nil { - upstream.TLS.Enable = true - } proxySSLName := generateProxySSLName(upstream.Service, vsEx.VirtualServer.Namespace) @@ -393,11 +390,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, egressMTLSPemFileName, trustedCAFileName) + 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, egressMTLSPemFileName, trustedCAFileName) + vsEx.VirtualServer.Name, vsrPoliciesFromVs[vsrNamespaceName], vsEx.Policies, jwtKeys, "", routeContext, tlsPemFileName, egressMTLSSecrets) } limitReqZones = append(limitReqZones, routePoliciesCfg.LimitReqZones...) @@ -495,7 +492,7 @@ type policiesCfg struct { // 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, egressMTLSPemFileName string, trustedCAFileName 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 @@ -595,26 +592,24 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own } } else if pol.Spec.EgressMTLS != nil { - if context != routeContext { - vsc.addWarningf(owner, `EgressMTLS policy is not allowed in the %v context`, context) - policyError = true - break - } if egressMTLS != nil { vsc.addWarningf(owner, "Multiple egressMTLS policies in the same context is not valid. EgressMTLS policy %q will be ignored", key) continue } + egressMTLSPemFileName := fmt.Sprintf("%v/%v", polNamespace, pol.Spec.EgressMTLS.TLSSecret) + trustedCAFileName := fmt.Sprintf("%v/%v", polNamespace, pol.Spec.EgressMTLS.TrustedCertSecret) + egressMTLS = &version2.EgressMTLS{ - Certificate: generateString(egressMTLSPemFileName, ""), - CertificateKey: generateString(egressMTLSPemFileName, ""), + Certificate: egressMTLSSecrets[egressMTLSPemFileName], + CertificateKey: egressMTLSSecrets[egressMTLSPemFileName], Ciphers: generateString(pol.Spec.EgressMTLS.Ciphers, "DEFAULT"), Protocols: generateString(pol.Spec.EgressMTLS.Protocols, "TLSv1 TLSv1.1 TLSv1.2"), VerifyServer: generateBool(pol.Spec.EgressMTLS.VerifyServer, false), VerifyDepth: generateIntFromPointer(pol.Spec.EgressMTLS.VerifyDepth, 1), SessionReuse: generateBool(pol.Spec.EgressMTLS.SessionReuse, true), ServerName: generateBool(pol.Spec.EgressMTLS.ServerName, false), - TrustedCert: generateString(trustedCAFileName, ""), + TrustedCert: egressMTLSSecrets[trustedCAFileName], SSLName: generateString(pol.Spec.EgressMTLS.SSLName, "$proxy_host"), } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index a23c27ce80..23d1979d1a 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -602,11 +602,10 @@ func TestGenerateVirtualServerConfig(t *testing.T) { isResolverConfigured := false tlsPemFileName := "" ingressMTLSFileName := "" - egressMTLSFileName := "" - trustedCAFileName := "" vsc := newVirtualServerConfigurator(&baseCfgParams, isPlus, isResolverConfigured, &StaticConfigParams{TLSPassthrough: true}) jwtKeys := make(map[string]string) - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) + 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) } @@ -711,10 +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 := "" - egressMTLSFileName := "" - trustedCAFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) + 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) } @@ -983,10 +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 := "" - egressMTLSFileName := "" - trustedCAFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) + 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) } @@ -1288,10 +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 := "" - egressMTLSFileName := "" - trustedCAFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) + 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) } @@ -1763,10 +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 := "" - egressMTLSFileName := "" - trustedCAFileName := "" - result, warnings := vsc.GenerateVirtualServerConfig(&virtualServerEx, tlsPemFileName, jwtKeys, ingressMTLSFileName, egressMTLSFileName, trustedCAFileName) + 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) } @@ -1782,17 +1777,16 @@ func TestGeneratePolicies(t *testing.T) { vsNamespace := "default" vsName := "test" ingressMTLSCertPath := "/etc/nginx/secrets/default-ingress-mtls-secret" - egressMTLSCertPath := "/etc/nginx/secrets/default-egress-mtls-secret" - caCertPath := "/etc/nginx/secrets/default-egress-trusted-ca-secret" 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{ @@ -2040,23 +2034,27 @@ func TestGeneratePolicies(t *testing.T) { TLSSecret: "egress-mtls-secret", ServerName: createPointerFromBool(true), SessionReuse: createPointerFromBool(false), - TrustedCertSecret: "egress-ca-secret", + 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: egressMTLSCertPath, - CertificateKey: egressMTLSCertPath, + 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, - TrustedCert: caCertPath, + TrustedCert: "/etc/nginx/secrets/default-egress-trusted-ca-secret", SSLName: "$proxy_host", }, }, @@ -2067,7 +2065,7 @@ func TestGeneratePolicies(t *testing.T) { 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, egressMTLSCertPath, caCertPath) + 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) } @@ -2090,9 +2088,9 @@ 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 - egressMTLSFileName string trustedCAFileName string context string expected policiesCfg @@ -2444,37 +2442,6 @@ func TestGeneratePoliciesFails(t *testing.T) { }, msg: "ingress mtls missing TLS config", }, - { - 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", - }, - }, - }, - }, - jwtKeys: nil, - egressMTLSFileName: "/etc/nginx/secrets/default-ingress-mtls-secret", - context: "server", - expected: policiesCfg{ - ErrorReturn: &version2.Return{ - Code: 500, - }, - }, - expectedWarnings: map[runtime.Object][]string{ - nil: { - `EgressMTLS policy is not allowed in the server context`, - }, - }, - msg: "egress mtls in the wrong context", - }, { policyRefs: []conf_v1.PolicyReference{ { @@ -2502,9 +2469,11 @@ func TestGeneratePoliciesFails(t *testing.T) { }, }, }, - jwtKeys: nil, - context: "route", - egressMTLSFileName: "/etc/nginx/secrets/default-egress-mtls-secret", + 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", @@ -2519,7 +2488,7 @@ func TestGeneratePoliciesFails(t *testing.T) { }, expectedWarnings: map[runtime.Object][]string{ nil: { - `Multiple egressMTLS policies are not allowed. EgressMTLS policy "default/egress-mtls-policy2" will be ignored`, + `Multiple egressMTLS policies in the same context is not valid. EgressMTLS policy "default/egress-mtls-policy2" will be ignored`, }, }, msg: "multi egress mtls", @@ -2529,7 +2498,7 @@ func TestGeneratePoliciesFails(t *testing.T) { 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, test.egressMTLSFileName, test.trustedCAFileName) + 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) } diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 438495cb4f..8ac7474a00 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -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 != "" { @@ -2038,6 +2039,11 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. virtualServerEx.IngressMTLSCert = ingressSecret } + err = lbc.getEgressMTLSSecrets(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) externalNameSvcs := make(map[string]bool) podsByIP := make(map[string]configs.PodInfo) @@ -2083,18 +2089,9 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. } policies = append(policies, vsRoutePolicies...) - egressSecret, err := lbc.getEgressMTLSSecret(policies) - if err != nil { - glog.Warningf("Error getting EgressMTLS secret for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) - } else { - virtualServerEx.EgressTLSSecret = egressSecret - } - - trustedCA, err := lbc.getTrustedCertSecret(policies) + err = lbc.getEgressMTLSSecrets(policies, virtualServerEx.EgressTLSSecrets) if err != nil { - glog.Warningf("Error getting TrustedCertSecret secret for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) - } else { - virtualServerEx.TrustedCASecret = trustedCA + glog.Warningf("Error getting EgressMTLS secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) } } @@ -2111,11 +2108,9 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. glog.Warningf("Error getting JWT secrets for VirtualServerRoute %v/%v: %v", vsr.Namespace, vsr.Name, err) } - egressSecret, err := lbc.getEgressMTLSSecret(policies) + err = lbc.getEgressMTLSSecrets(policies, virtualServerEx.EgressTLSSecrets) if err != nil { glog.Warningf("Error getting EgressMTLS secrets for VirtualServer %v/%v: %v", virtualServer.Namespace, virtualServer.Name, err) - } else { - virtualServerEx.EgressTLSSecret = egressSecret } } @@ -2259,36 +2254,30 @@ func (lbc *LoadBalancerController) getIngressMTLSSecret(policies []*conf_v1alpha return nil, nil } -func (lbc *LoadBalancerController) getEgressMTLSSecret(policies []*conf_v1alpha1.Policy) (*api_v1.Secret, error) { +func (lbc *LoadBalancerController) getEgressMTLSSecrets(policies []*conf_v1alpha1.Policy, egressSecrets map[string]*api_v1.Secret) error { for _, pol := range policies { - if pol.Spec.EgressMTLS == nil || pol.Spec.EgressMTLS.TLSSecret == "" { + if pol.Spec.EgressMTLS == nil { continue } - secretKey := fmt.Sprintf("%v/%v", pol.Namespace, pol.Spec.EgressMTLS.TLSSecret) - secret, err := lbc.getAndValidateSecret(secretKey) - if err != nil { - return nil, fmt.Errorf("Error getting or validating the EgressMTLS secret %v for the policy %v/%v: %v", secretKey, pol.Namespace, pol.Name, err) - } - return secret, nil - } - - return nil, nil -} - -func (lbc *LoadBalancerController) getTrustedCertSecret(policies []*conf_v1alpha1.Policy) (*api_v1.Secret, error) { - for _, pol := range policies { - if pol.Spec.EgressMTLS == nil || pol.Spec.EgressMTLS.TrustedCertSecret == "" { - 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 } - secretKey := fmt.Sprintf("%v/%v", pol.Namespace, pol.Spec.EgressMTLS.TrustedCertSecret) - secret, err := lbc.getAndValidateCASecret(secretKey) - if err != nil { - return nil, fmt.Errorf("Error getting or validating the TrustedCert secret %v for the policy %v/%v: %v", secretKey, pol.Namespace, pol.Name, err) + 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 secret, nil } - return nil, nil + return nil } func (lbc *LoadBalancerController) getPoliciesForSecret(secretNamespace string, secretName string) []*conf_v1alpha1.Policy { @@ -2305,6 +2294,8 @@ func findPoliciesForSecret(policies []*conf_v1alpha1.Policy, secretNamespace str 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 5297b7cbec..ecb0d54fa7 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" @@ -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 TestGetEgressMTLSSecrets(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-ingress-mtls-secret": + return invalidSecret, true, errors.New("secret is missing ingress-mtls key in data") + default: + return nil, false, errors.New("GetByKey error") + } + }, + }, + }, + } + + egressTLSSecrets := make(map[string]*v1.Secret) + + err := lbc.getEgressMTLSSecrets(test.policies, egressTLSSecrets) + if (err != nil) != test.wantErr { + t.Errorf("getEgressMTLSSecrets() returned %v, for the case of %v", err, test.msg) + } + if diff := cmp.Diff(test.expectedEgressMTLSSecrets, egressTLSSecrets); diff != "" { + t.Errorf("getEgressMTLSSecrets() '%v' mismatch (-want +got):\n%s", test.msg, diff) + } + } +} diff --git a/internal/k8s/secret.go b/internal/k8s/secret.go index ba15feb76e..8231137d04 100644 --- a/internal/k8s/secret.go +++ b/internal/k8s/secret.go @@ -9,7 +9,7 @@ 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. +// CAKey is the key of the data field of a Secret where the certificate authority must be stored. const CAKey = "ca.crt" const ( diff --git a/pkg/apis/configuration/validation/policy.go b/pkg/apis/configuration/validation/policy.go index 8f64e90afb..eebad77e66 100644 --- a/pkg/apis/configuration/validation/policy.go +++ b/pkg/apis/configuration/validation/policy.go @@ -148,7 +148,6 @@ 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 { @@ -165,8 +164,20 @@ func validateEgressMTLS(egressMTLS *v1alpha1.EgressMTLS, fieldPath *field.Path) 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 3c4b96345f..10b3131ce2 100644 --- a/pkg/apis/configuration/validation/policy_test.go +++ b/pkg/apis/configuration/validation/policy_test.go @@ -623,6 +623,12 @@ func TestValidateEgressMTLS(t *testing.T) { }, 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")) @@ -631,3 +637,44 @@ func TestValidateEgressMTLS(t *testing.T) { } } } + +func TestValidateEgressMTLSInvalid(t *testing.T) { + tests := []struct { + eg *v1alpha1.EgressMTLS + msg string + }{ + { + eg: &v1alpha1.EgressMTLS{ + VerifyServer: createPointerFromBool(true), + }, + msg: "verify server set to true", + }, + { + eg: &v1alpha1.EgressMTLS{ + TrustedCertSecret: "-foo-", + }, + msg: "invalid secret name", + }, + { + eg: &v1alpha1.EgressMTLS{ + TrustedCertSecret: "ingress-mtls-secret", + VerifyServer: createPointerFromBool(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) + } + } +} From 3ee836ae7d50fd58d624222dbe9be3562b0a33e9 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Fri, 9 Oct 2020 12:23:52 -0700 Subject: [PATCH 4/8] PR feedback --- internal/configs/virtualserver.go | 25 +++++++--- internal/configs/virtualserver_test.go | 68 ++++++++++++++++++++++++++ internal/k8s/controller.go | 8 +-- internal/k8s/controller_test.go | 12 ++--- 4 files changed, 97 insertions(+), 16 deletions(-) diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index 97aa20d5bc..c6b77069a1 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -58,7 +58,6 @@ type VirtualServerEx struct { JWTKeys map[string]*api_v1.Secret IngressMTLSCert *api_v1.Secret EgressTLSSecrets map[string]*api_v1.Secret - TrustedCASecret *api_v1.Secret VirtualServerRoutes []*conf_v1.VirtualServerRoute ExternalNameSvcs map[string]bool Policies map[string]*conf_v1alpha1.Policy @@ -597,19 +596,33 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own continue } - egressMTLSPemFileName := fmt.Sprintf("%v/%v", polNamespace, pol.Spec.EgressMTLS.TLSSecret) - trustedCAFileName := fmt.Sprintf("%v/%v", polNamespace, pol.Spec.EgressMTLS.TrustedCertSecret) + 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: egressMTLSSecrets[egressMTLSPemFileName], - CertificateKey: egressMTLSSecrets[egressMTLSPemFileName], + Certificate: egressMTLSPemFileName, + CertificateKey: egressMTLSPemFileName, Ciphers: generateString(pol.Spec.EgressMTLS.Ciphers, "DEFAULT"), Protocols: generateString(pol.Spec.EgressMTLS.Protocols, "TLSv1 TLSv1.1 TLSv1.2"), VerifyServer: generateBool(pol.Spec.EgressMTLS.VerifyServer, false), VerifyDepth: generateIntFromPointer(pol.Spec.EgressMTLS.VerifyDepth, 1), SessionReuse: generateBool(pol.Spec.EgressMTLS.SessionReuse, true), ServerName: generateBool(pol.Spec.EgressMTLS.ServerName, false), - TrustedCert: egressMTLSSecrets[trustedCAFileName], + TrustedCert: trustedCAFileName, SSLName: generateString(pol.Spec.EgressMTLS.SSLName, "$proxy_host"), } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index 23d1979d1a..eda5046bc5 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -2493,6 +2493,74 @@ func TestGeneratePoliciesFails(t *testing.T) { }, 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 { diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 8ac7474a00..249d5a9e9f 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -2039,7 +2039,7 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. virtualServerEx.IngressMTLSCert = ingressSecret } - err = lbc.getEgressMTLSSecrets(policies, virtualServerEx.EgressTLSSecrets) + 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) } @@ -2089,7 +2089,7 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. } policies = append(policies, vsRoutePolicies...) - err = lbc.getEgressMTLSSecrets(policies, virtualServerEx.EgressTLSSecrets) + 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) } @@ -2108,7 +2108,7 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. glog.Warningf("Error getting JWT secrets for VirtualServerRoute %v/%v: %v", vsr.Namespace, vsr.Name, err) } - err = lbc.getEgressMTLSSecrets(policies, virtualServerEx.EgressTLSSecrets) + 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) } @@ -2254,7 +2254,7 @@ func (lbc *LoadBalancerController) getIngressMTLSSecret(policies []*conf_v1alpha return nil, nil } -func (lbc *LoadBalancerController) getEgressMTLSSecrets(policies []*conf_v1alpha1.Policy, egressSecrets map[string]*api_v1.Secret) error { +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 diff --git a/internal/k8s/controller_test.go b/internal/k8s/controller_test.go index ecb0d54fa7..7e06b94a81 100644 --- a/internal/k8s/controller_test.go +++ b/internal/k8s/controller_test.go @@ -1487,7 +1487,7 @@ func TestGetIngressMTLSSecret(t *testing.T) { } } -func TestGetEgressMTLSSecrets(t *testing.T) { +func TestAddEgressMTLSSecrets(t *testing.T) { validSecret := &v1.Secret{ ObjectMeta: meta_v1.ObjectMeta{ Name: "valid-egress-mtls-secret", @@ -1651,8 +1651,8 @@ func TestGetEgressMTLSSecrets(t *testing.T) { return validSecret, true, nil case "default/valid-egress-trusted-secret": return validSecret2, true, nil - case "default/invalid-ingress-mtls-secret": - return invalidSecret, true, errors.New("secret is missing ingress-mtls key in data") + 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") } @@ -1663,12 +1663,12 @@ func TestGetEgressMTLSSecrets(t *testing.T) { egressTLSSecrets := make(map[string]*v1.Secret) - err := lbc.getEgressMTLSSecrets(test.policies, egressTLSSecrets) + err := lbc.addEgressMTLSSecrets(test.policies, egressTLSSecrets) if (err != nil) != test.wantErr { - t.Errorf("getEgressMTLSSecrets() returned %v, for the case of %v", err, test.msg) + t.Errorf("addEgressMTLSSecrets() returned %v, for the case of %v", err, test.msg) } if diff := cmp.Diff(test.expectedEgressMTLSSecrets, egressTLSSecrets); diff != "" { - t.Errorf("getEgressMTLSSecrets() '%v' mismatch (-want +got):\n%s", test.msg, diff) + t.Errorf("addEgressMTLSSecrets() '%v' mismatch (-want +got):\n%s", test.msg, diff) } } } From 75493a9124d9a08824769e1286a42045cd11a5e4 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Fri, 9 Oct 2020 12:24:27 -0700 Subject: [PATCH 5/8] Update examples-of-custom-resources/egress-mtls/README.md Co-authored-by: Michael Pleshakov --- examples-of-custom-resources/egress-mtls/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples-of-custom-resources/egress-mtls/README.md b/examples-of-custom-resources/egress-mtls/README.md index 024a74313e..e3c3765645 100644 --- a/examples-of-custom-resources/egress-mtls/README.md +++ b/examples-of-custom-resources/egress-mtls/README.md @@ -31,7 +31,7 @@ $ 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 certificates of the Secure Web Application: +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 ``` From 321fca5e189d06e526d34aaa8ee10e5fb051f798 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Fri, 9 Oct 2020 12:37:40 -0700 Subject: [PATCH 6/8] Add policy in server context --- internal/configs/version2/http.go | 1 + .../version2/nginx-plus.virtualserver.tmpl | 18 ++++++++++++++++++ .../configs/version2/nginx.virtualserver.tmpl | 18 ++++++++++++++++++ internal/configs/virtualserver.go | 1 + 4 files changed, 38 insertions(+) diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index cb16a893e7..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 } diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index a3503155d7..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 }} diff --git a/internal/configs/version2/nginx.virtualserver.tmpl b/internal/configs/version2/nginx.virtualserver.tmpl index 88bc7c1239..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 }} diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index c6b77069a1..c77397aded 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -469,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, From 03202703cb89c1f01d6c5c34f62f09eb3efe6cd3 Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Fri, 9 Oct 2020 13:20:31 -0700 Subject: [PATCH 7/8] Remove pointers --- internal/configs/virtualserver.go | 4 ++-- internal/configs/virtualserver_test.go | 3 ++- pkg/apis/configuration/v1alpha1/types.go | 4 ++-- .../configuration/v1alpha1/zz_generated.deepcopy.go | 10 ---------- pkg/apis/configuration/validation/policy.go | 2 +- pkg/apis/configuration/validation/policy_test.go | 9 +++++---- 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index c77397aded..180de3f2cf 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -619,10 +619,10 @@ func (vsc *virtualServerConfigurator) generatePolicies(owner runtime.Object, own CertificateKey: egressMTLSPemFileName, Ciphers: generateString(pol.Spec.EgressMTLS.Ciphers, "DEFAULT"), Protocols: generateString(pol.Spec.EgressMTLS.Protocols, "TLSv1 TLSv1.1 TLSv1.2"), - VerifyServer: generateBool(pol.Spec.EgressMTLS.VerifyServer, false), + VerifyServer: pol.Spec.EgressMTLS.VerifyServer, VerifyDepth: generateIntFromPointer(pol.Spec.EgressMTLS.VerifyDepth, 1), SessionReuse: generateBool(pol.Spec.EgressMTLS.SessionReuse, true), - ServerName: generateBool(pol.Spec.EgressMTLS.ServerName, false), + ServerName: pol.Spec.EgressMTLS.ServerName, TrustedCert: trustedCAFileName, SSLName: generateString(pol.Spec.EgressMTLS.SSLName, "$proxy_host"), } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index eda5046bc5..9196474b32 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -2032,7 +2032,7 @@ func TestGeneratePolicies(t *testing.T) { Spec: conf_v1alpha1.PolicySpec{ EgressMTLS: &conf_v1alpha1.EgressMTLS{ TLSSecret: "egress-mtls-secret", - ServerName: createPointerFromBool(true), + ServerName: true, SessionReuse: createPointerFromBool(false), TrustedCertSecret: "egress-trusted-ca-secret", }, @@ -2054,6 +2054,7 @@ func TestGeneratePolicies(t *testing.T) { ServerName: true, SessionReuse: false, VerifyDepth: 1, + VerifyServer: false, TrustedCert: "/etc/nginx/secrets/default-egress-trusted-ca-secret", SSLName: "$proxy_host", }, diff --git a/pkg/apis/configuration/v1alpha1/types.go b/pkg/apis/configuration/v1alpha1/types.go index ef016e3b60..b0ef7c2c59 100644 --- a/pkg/apis/configuration/v1alpha1/types.go +++ b/pkg/apis/configuration/v1alpha1/types.go @@ -159,13 +159,13 @@ type IngressMTLS struct { // EgressMTLS defines an Egress MTLS policy. type EgressMTLS struct { TLSSecret string `json:"tlsSecret"` - VerifyServer *bool `json:"verifyServer"` + 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"` + ServerName bool `json:"serverName"` SSLName string `json:"sslName"` } diff --git a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go index 5c03e5f73b..0ad913889c 100644 --- a/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/configuration/v1alpha1/zz_generated.deepcopy.go @@ -53,11 +53,6 @@ func (in *Action) DeepCopy() *Action { // 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.VerifyServer != nil { - in, out := &in.VerifyServer, &out.VerifyServer - *out = new(bool) - **out = **in - } if in.VerifyDepth != nil { in, out := &in.VerifyDepth, &out.VerifyDepth *out = new(int) @@ -68,11 +63,6 @@ func (in *EgressMTLS) DeepCopyInto(out *EgressMTLS) { *out = new(bool) **out = **in } - if in.ServerName != nil { - in, out := &in.ServerName, &out.ServerName - *out = new(bool) - **out = **in - } return } diff --git a/pkg/apis/configuration/validation/policy.go b/pkg/apis/configuration/validation/policy.go index eebad77e66..92f79d1eed 100644 --- a/pkg/apis/configuration/validation/policy.go +++ b/pkg/apis/configuration/validation/policy.go @@ -155,7 +155,7 @@ func validateEgressMTLS(egressMTLS *v1alpha1.EgressMTLS, fieldPath *field.Path) allErrs = append(allErrs, validateSecretName(egressMTLS.TLSSecret, fieldPath.Child("tlsSecret"))...) - if egressMTLS.VerifyServer != nil && *egressMTLS.VerifyServer && egressMTLS.TrustedCertSecret == "" { + 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"))...) diff --git a/pkg/apis/configuration/validation/policy_test.go b/pkg/apis/configuration/validation/policy_test.go index 10b3131ce2..acfda3489e 100644 --- a/pkg/apis/configuration/validation/policy_test.go +++ b/pkg/apis/configuration/validation/policy_test.go @@ -612,14 +612,15 @@ func TestValidateEgressMTLS(t *testing.T) { { eg: &v1alpha1.EgressMTLS{ TrustedCertSecret: "tls-secret", - VerifyServer: createPointerFromBool(true), + VerifyServer: true, VerifyDepth: createPointerFromInt(2), + ServerName: false, }, msg: "verify server set to true", }, { eg: &v1alpha1.EgressMTLS{ - VerifyServer: createPointerFromBool(false), + VerifyServer: false, }, msg: "verify server set to false", }, @@ -645,7 +646,7 @@ func TestValidateEgressMTLSInvalid(t *testing.T) { }{ { eg: &v1alpha1.EgressMTLS{ - VerifyServer: createPointerFromBool(true), + VerifyServer: true, }, msg: "verify server set to true", }, @@ -658,7 +659,7 @@ func TestValidateEgressMTLSInvalid(t *testing.T) { { eg: &v1alpha1.EgressMTLS{ TrustedCertSecret: "ingress-mtls-secret", - VerifyServer: createPointerFromBool(true), + VerifyServer: true, VerifyDepth: createPointerFromInt(-1), }, msg: "invalid depth", From 484728a91306da3c194b0b371cf0ee584a0744fa Mon Sep 17 00:00:00 2001 From: Luca Comellini Date: Fri, 9 Oct 2020 14:21:34 -0700 Subject: [PATCH 8/8] Github added spaces instead of tabs --- internal/k8s/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/k8s/controller.go b/internal/k8s/controller.go index 238c8386ae..1db054154e 100644 --- a/internal/k8s/controller.go +++ b/internal/k8s/controller.go @@ -2092,7 +2092,7 @@ func (lbc *LoadBalancerController) createVirtualServerEx(virtualServer *conf_v1. 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)