diff --git a/examples/custom-resources/tls-passthrough/README.md b/examples/custom-resources/tls-passthrough/README.md index 0e2c844d44..e8b2d4df88 100644 --- a/examples/custom-resources/tls-passthrough/README.md +++ b/examples/custom-resources/tls-passthrough/README.md @@ -46,8 +46,9 @@ You can see how the Secure App is implemented in the `secure-app.yaml` file. 1. Save the HTTPS port of the Ingress Controller where TLS Passthrough is enabled into a shell variable: - ```console - $ IC_HTTPS_PORT= +```console +IC_HTTPS_PORT= +``` 1. Save the HTTPS port of the Ingress Controller into a shell variable: diff --git a/examples/custom-resources/transport-server-sni/README.md b/examples/custom-resources/transport-server-sni/README.md new file mode 100644 index 0000000000..9c287e541e --- /dev/null +++ b/examples/custom-resources/transport-server-sni/README.md @@ -0,0 +1,110 @@ +# TransportServer SNI + +In this example we create two different TransportServers that listen on the same interface, which are distinguished by their Host field. +The applications (a TCP echo server, and MongoDB) will be accessed via `ncat` and `mongosh`. +The `ncat` binary is available via `nmap`. On mac/linux this can be installed via homebrew/linuxbrew with `brew install nmap` +`mongosh` installation instructions are [available here](https://www.mongodb.com/docs/mongodb-shell/install/). + +## Create a GlobalConfiguration resource with the following listener + +```yaml +listeners: + - name: tcp-listener + port: 7000 + protocol: TCP +``` + +## Add a custom port to the NGINX Ingress Controller pod with the Helm chart + +```yaml +controller.customPorts: + - name: port + containerPort: 7000 + protocol: TCP +``` + +## Add a custom port to the NGINX Ingress Controller service + +```yaml +controller.service.customPorts: + - name: tcp-port + port: 7000 + protocol: TCP + targetPort: 7000 +``` + +## Use `kubectl` to create the cafe-secret, and mongo-secret. These secrets are used for TLS in the TransportServers + +`kubectl apply -f cafe-secret.yaml` +`kubectl apply -f mongo-secret.yaml` + +## Create the mongo and tcp echo example applications + +`kubectl apply -f mongo.yaml` +`kubectl apply -f tcp-echo-server.yaml` + +## Wait until these are ready + +`kubectl get deploy -w` + +## Create the TransportServers for each application + +`kubectl apply -f cafe-transport-server.yaml` +`kubectl apply -f mongo-transport-server.yaml` + +## Ensure they are in valid state + +`kubectl get ts` + +```shell +NAME STATE REASON AGE +cafe-ts Valid AddedOrUpdated 2m +mongo-ts Valid AddedOrUpdated 2m +``` + +## Set up /etc/hosts or DNS + +This example uses a local NGINX Ingress Controller instance, so the /etc/hosts file +is being used to set cafe.example.com and mongo.example.com to localhost. +In a production instance, the server names would be set at the DNS layer. +`cat /etc/hosts` + +```shell +... +127.0.0.1 cafe.example.com +127.0.0.1 mongo.example.com +``` + +## Expose port 7000 of the LoadBalancer service + +`kubectl port-forward svc/my-release-nginx-ingress-controller 7000:7000` + +## Use `ncat` to ping cafe.example.com on port 7000 with SSL + +`ncat --ssl cafe.example.com 7000` +When you write a message you should receive the following response: + +```shell +hi +hi +``` + +Close the connection (CTRL+ c), then view the NGINX Ingress Controller logs. + +The request and response should both be 2 bytes. + +```shell +127.0.0.1 [24/Sep/2024:15:48:58 +0000] TCP 200 3 3 2.702 "- +``` + +## Use mongosh to connect to the mongodb container through the TransportServer on port 7000 + +`mongosh --host mongo.example.com --port 7000 --tls --tlsAllowInvalidCertificates` + +```shell +test> show dbs +admin 40.00 KiB +config 60.00 KiB +local 40.00 KiB +test> +``` diff --git a/examples/custom-resources/transport-server-sni/cafe-secret.yaml b/examples/custom-resources/transport-server-sni/cafe-secret.yaml new file mode 100644 index 0000000000..4129b88e76 --- /dev/null +++ b/examples/custom-resources/transport-server-sni/cafe-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY1ekNDQTgrZ0F3SUJBZ0lVR2dXT0JNTkR2TXNWOGY3OWc0MlpSZy9KaWc0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZSXhDekFKQmdOVkJBWVRBbGhZTVJJd0VBWURWUVFJREFsVGRHRjBaVTVoYldVeEVUQVBCZ05WQkFjTQpDRU5wZEhsT1lXMWxNUlF3RWdZRFZRUUtEQXREYjIxd1lXNTVUbUZ0WlRFYk1Ca0dBMVVFQ3d3U1EyOXRjR0Z1CmVWTmxZM1JwYjI1T1lXMWxNUmt3RndZRFZRUUREQkJqWVdabExtVjRZVzF3YkdVdVkyOXRNQjRYRFRJME1Ea3kKTXpFd01EUXdNVm9YRFRNME1Ea3lNVEV3TURRd01Wb3dnWUl4Q3pBSkJnTlZCQVlUQWxoWU1SSXdFQVlEVlFRSQpEQWxUZEdGMFpVNWhiV1V4RVRBUEJnTlZCQWNNQ0VOcGRIbE9ZVzFsTVJRd0VnWURWUVFLREF0RGIyMXdZVzU1ClRtRnRaVEViTUJrR0ExVUVDd3dTUTI5dGNHRnVlVk5sWTNScGIyNU9ZVzFsTVJrd0Z3WURWUVFEREJCallXWmwKTG1WNFlXMXdiR1V1WTI5dE1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBbjkvZwpkMml5NEZyaGNtS3ZXQTVHZXFiS1c5WU1xeml6Vm4yRExkd215enkvS2FUdTdtUlkvUWRsTkxHaVhIM1NEa0FkCkhtZFJEK0Zob1ZHaWxoTjEzcTI0azdyRSt6dk1iMlRqdnBnMmVLYVd1dDBHaGQ3V3l3TnpIUU9WcWJCM2o2U2QKSE1sNlJKK25ERHo2Yi9adkRiNm9nQjBucUowZjBWN0tBMHFFalYxVld2dXI4YVZpVlVxK0tBOVMveEdJMHZSSwo2NE9BS2xIUUF4a2xGWWhheHVjcWRsS0owQlRXekE0Z0gzUTZlQi9CRjNOMTJDQjgrMVEzZG54bU5TVnpLbXp1CmJYdm1jK1RPUjg0V3ovemlaMTl1K3RuRHg2aklNajZZQXd2M0tzSVlYbmV1ZEgycUZKRG5FMmZwYjNnZzRxbm0KQnduY0ZlU1poY09nZWdnR1RmKzRTc3YwRUNuamNNdXhteTEwMTNwY0h1a2prTUNmVGJsUGZTU0NNS24rVnlGZQpUNzlSeUkxM3hmL0JXOSt0aTFad1IwYVpkWk5jV1R2Um53R1M1bWhnelJXclhRUmdJQlVVVjk2N3cxRVVONDJVCkE3ZjBVSzZlYks5bExBMnNZWmJJSWUrbllRY1d1OVRuNXo5YnVwK2w3aU5hUDYxWXdwWGdrUTFyNHlSamhMeUEKajVha1Z5VnROYzZmSWltaXlWV0Y1QzBaZnNIYzF3cUpLb2lXQzBtT1dyNEVRSVorRkY2WC9FRytSMmhodjZkQQpGcndpd3BHUE9kWkROQzNqNFBqVzVreWlYMlh2Vm1RWTUvbkxNdFpUaFF6VHpadG8zT3M4d1RsRjFyQlZkd05iCi9zNTh2dnlTQitsYk5Sd215TEErQVYvOGQ2L2VNcGZjS0ZyYU85OENBd0VBQWFOVE1GRXdIUVlEVlIwT0JCWUUKRktEejZlREZLaC93ZDVLTHpPMXpCMDR2UGVRSU1COEdBMVVkSXdRWU1CYUFGS0R6NmVERktoL3dkNUtMek8xegpCMDR2UGVRSU1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFKQnd5WVlyCllXa3RvRGJPYlpHRUFXT0lEcjFTb3NWRDVIRXdkazV6ZTJuT05oQXFHVWszOHpUT3V1VVZRNk5lT1c1UWtML3oKVTFUa3dwUlNFNjFpbVZHOUdjYWY5ampkSjd5VGhsOGpmZXUyWTF4RHo1OUpmbWJ1WUo2SGE4ZmR4STAxQXZ3RgoxaERYY1ZEcTZoalhhb0pKMVZta1NiTEJPc1JXaGVzWGl1K3FiN3NSOUhlZmJmaTUxandQb2NFblE4YzNiWXF5CmpIRkwxS1JJekIyR2VYVGk2WngwN2lqcDZIeGJSQUFEWnBuRGN0blM1UVQvTGFoYklIZVNNTExMK21vdnUrSUgKTVgwQmtaYmJjRk94L3d2ZlN1SXdveEVXR1RjVGJIYnJGanVUUDRkalpPdFhybElSK1RJTmRiaFhaMm1XZVdHbgpRa0tkcyt4a3puNzNZbG9xZCtPZGZ2d1dJYXVFYktuaXdQUk01NHZqK0dnbndiWmZNWXpnY292cE5BYzZjMmlmCkNNdHlHWDBUREptSjRVWC9FaFFSYTArSURMbFZTMWc2Y3IxWmVQM1N1MWpkSnhBSFVwUjZwSVYrWGdsTENDdGYKQXdDUW9BUWtUeXkyb0Y1Z0hWOGVuc28zVE15cmI5R1NBWDF0UEdJL080L3VCRDBqRzQ4anZsZEI3dzZKbjdmSwoyS21DWnIwYlRDdU9vWnVuZHA3OHA2R1ozb3lVOXBxcTFTUmU0MTdjSlVuNzR3S05TTkd0U0xTNm9OZ3FQQ2h6Cm9ZTy9zK0NHL295c0JUcTBma2VBYXdMZ2oxVG9ybGNsaEt6M05uWUtLZmhDVFBLTDVVUFZLNjVXQ3V2djlSVWgKVUZvM2F2TnhhSmpWUEY4V0FVUnRZem82bXBadFRITm53ZkJTCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQ2YzK0IzYUxMZ1d1RnkKWXE5WURrWjZwc3BiMWd5ck9MTldmWU10M0NiTFBMOHBwTzd1WkZqOUIyVTBzYUpjZmRJT1FCMGVaMUVQNFdHaApVYUtXRTNYZXJiaVR1c1Q3Tzh4dlpPTyttRFo0cHBhNjNRYUYzdGJMQTNNZEE1V3BzSGVQcEowY3lYcEVuNmNNClBQcHY5bThOdnFpQUhTZW9uUi9SWHNvRFNvU05YVlZhKzZ2eHBXSlZTcjRvRDFML0VZalM5RXJyZzRBcVVkQUQKR1NVVmlGckc1eXAyVW9uUUZOYk1EaUFmZERwNEg4RVhjM1hZSUh6N1ZEZDJmR1kxSlhNcWJPNXRlK1p6NU01SAp6aGJQL09KblgyNzYyY1BIcU1neVBwZ0RDL2Nxd2hoZWQ2NTBmYW9Va09jVForbHZlQ0RpcWVZSENkd1Y1Sm1GCnc2QjZDQVpOLzdoS3kvUVFLZU53eTdHYkxYVFhlbHdlNlNPUXdKOU51VTk5SklJd3FmNVhJVjVQdjFISWpYZkYKLzhGYjM2MkxWbkJIUnBsMWsxeFpPOUdmQVpMbWFHRE5GYXRkQkdBZ0ZSUlgzcnZEVVJRM2paUUR0L1JRcnA1cwpyMlVzRGF4aGxzZ2g3NmRoQnhhNzFPZm5QMXU2bjZYdUkxby9yVmpDbGVDUkRXdmpKR09FdklDUGxxUlhKVzAxCnpwOGlLYUxKVllYa0xSbCt3ZHpYQ29rcWlKWUxTWTVhdmdSQWhuNFVYcGY4UWI1SGFHRy9wMEFXdkNMQ2tZODUKMWtNMExlUGcrTmJtVEtKZlplOVdaQmpuK2NzeTFsT0ZETlBObTJqYzZ6ekJPVVhXc0ZWM0Exdit6bnkrL0pJSAo2VnMxSENiSXNENEJYL3gzcjk0eWw5d29XdG83M3dJREFRQUJBb0lDQUNkQmRZQmNlTytWNFIyUkZiVHRiR2paClkzN0JSRU1XblJKenB5NHZqR2NDOTMxbVBqVFM5dmJLUmhOMk9vT3pjVXlHZVovcGhvSDd1VmsvRGtrRFprSFQKTGlzNEJQNGJaTXRGWHBhQ0VYMzJpYlJBYVVXZHZlZ0RaTlNPK01TOXk5MjljY2FMd2pYdmJia1hqL2JGNytiVQpGZE8vVk9tV0N5WUJ2R0NxZjNtbW5UckY2U1pna1pDWDFiRkljZnluZFkwMjV0NkZYNGNFcDZyYkZidi95eXBqCndJMWxIdW0wOURrT2p0eXFVV0VGaXdnVEZiQ0g2YWhjdVhHaWdnWXl0K0NHOXRSelE5YlpLNzE5NFNRWTJBN0IKNUNJOExsSnNJeHdUT29naysvL0h3T3dSUHdqamdrdWllTnJPL1FhZDNKVkxXbXdJQTc1c2J6WGxIeFpYdWhReApFZTlTTVNaRE5JMGMvcGdDVXloL2h5Y3ZYWVJKb1dIMnZYTDYzL1EyRWJVVVZVcXpzcEpjV0xMbkhSSFdBQjZQCmtPb0FMeEs2M3lpQTU5KzREQ0ZZMGM0dXd4WHM5YnZlVWk5UUdxaUtoZkFaNmE3Ymh4ajNvRWIvRDZ0dWZKb1QKMnIxUXFKTlJ4N0RzMmJUc3J6SEgyYmVKR0R4bFJ4djdaNmdSTzdHd2lrU25veDlMcnY3YzMwUHp0WjZENXhwQwpoVHdmM0VpRWlrZ2pOM1FGd2RaUXVqV3RxWXQ1UWVGYUxpL2Q3VXZteFdZNWcrWTYyQnFkNnBpWFFJSlFVbDZUCmZmU0U2OEYvZWF6QnhPQm9INUMvWWcrd1kyVEpTb1BzK3NFUUtnYlNJTmRQNHU4N3FiQ056YWZ4WjFySTN1dmgKR3NZTWpSbm50c3RKamJXVEJkakJBb0lCQVFEZXpsY3dVNFJYbnhKVENSTjFQZUx1UHI2aXFnc09uY1lRRXNuTgpZemdwOXF6eC9SSTJodGZCYnN6TkM2RUZ3eDBDZy8wRnpRbVhqWEQ3VmhQS0UrOHBXMmQzNTg4YTBwU0xSbFBOCkprUnFxR2ovbzhIaEtmTGdCYy9BSGU3dkJEZG1UTXh1d0o1SmFmMWZ3OGtxNEJyRXROTi83SVF3MkNYcFNIK2MKSEhSZnBTM1JidDArbW9xK2RTRW1pU2h3UGgxL2FJbng4ZnBKbTFmaU1QdXhvYStzOXNUVmQ3Ky9GSjdIaWNZSApSQThQRHdQT2RMQ2loQlZFT3liSkduTlpaSFhYQTA4MThhNnpjdjU2aUM2VGZzSkhWZUg4a2Nma1IzblI4eTUvCkx2MnI1QzVaZWJlbW9ZaVhBTC9sVnZ0QUNGMXE3UW1zdlRkQ2R3R1lvaXBMbksvNUFvSUJBUUMzc1YvcFFOVlEKSjNsZExFem9OVUdBZWFwL0NNU2tuY0lUUXNnMUI3SDJHclIyR201VGNuVGE0SE5Rbmt3a2h3YUxyTUlmVDFXZAorMFVvR3M5ZXdyYkZERWtKR0MxQVd2RmFIREk3Q3NOYUFUM2FWYTE5R1ZRWFg2NnkrWEF2QXFTOXpOU25id0J0CnM5UDFFVURwMko1SGtyNlVLUHZjSnY3Q0MyR3J5emZaTy85MFpTTlFQUXdKMXFqZ2NNOUJpMlBYSkE0OENldnMKK3RvaGFqUWFIMStVY0VqNkQ0SHhoQ1RtcTBxM2YvdFNudk9iaTNOYklEWjNEWmR4ejVPelJSVmpLNWlSOGZDNQpzSVVKckxCSXRZeWpUeHAzTzROUmRFaHhzamdBZTZrSTV2dm95RVAvRFZwTFlQU0s3cFpoVjJ4YUpXUkZUUVZzCjhrKzZDbGduWGZDWEFvSUJBUUM5QjY4dFR3NHZFTVNKTW1BUnprbWovQlBkQ2d1TGdRd3pRdDEzcGNCV3lmUDgKOHNycS9BZzlFbllyV0x4cW1Sa1pzMFdPRUdFYzlXRnZ1NTNhaW9NVVFYcE5YcHgxazBkM3lsajY2b2FOUHdpbQpLeGNvbzJCdDlFQklMSjAwcUEwZ2UvUE4yeG53Q3o1dWF6dFhadjhPK0tPZ0d0Z2tZSjM1aUFyTU5jLzkvYlFiCnhjVnJnYzVJdkRNOThJd2dmbktrVDlzSkxGVSs4YzdrRnM3VDYrdVNBV01LQVNqclF1RmJSV1ovYjV5ZkdBd1EKc3l2UkZlSzlHcnBUVUYrZzdmeVVTVGlBK2VWUVZqWFZXNGk0bG9qWjRPRjBXWEtRR0p3Z0pnUEMzK2xVVnFtRQpQQ0kxKzBKWmFzZGtHaUhjTjd5YUpUVmFHc2F4V3lvOWh3Zi9VcFp4QW9JQkFDQ0hDem5ObmpoTVZTUlhsT0xGCmsyekJucHhTSENnZU8yQ1h3Y1lLTDh3cG5HMFJieG5kdWEyTWN6OENXTzlhN2FETUhhL1hwNHlMRXdydi9HcUcKUmtFTVZONkVabmJ2NDY4V01ScmRaQXhMRGYzY2tCVUg2Q2tmYTFzTDZuNllsRDE3eU9oQk1xMDZXNzBZcWdyKwpyY0IwenNTRG9WMnhsZ2tjWk5ZNzdRN05uZ1dwWnlCdFB2VjdDbnA3MzJkMjNGNGJaMTNnVCtPdDQvUm96d01WCkxTS200M1ZNUzdGTnVnOFNvKzlzZlQ5N0lCNGFDbnBIY1AyUjdaQmN0b1hYSk50anUrZVVGUkY4bllKQ0R4RkEKL0w5cVlZQmRqSHBmQWZrSUd2eVM2VExIWERJelREOGN5VEZ4NEx1OVZlbTB4bDRNSXY1V2pqQmxsQktZaEZXcwpQODhDZ2dFQkFKUjR5UUIrREtoRE1ZOGlHOXpYWUJpWmZ5MFI2c21oeEJycTREZWd4cElkajBydHUyMFNuWDdsCnN3YlZsN2NmdWRxUi9tVEhnZ1lYeGJSS3UxUXhmN3NHTW4zWVpRY1AxZDVJelFMOWZFQitidGJsdWY5VTB4NDIKUzM0U0l6Z25pd0hPQnN3M2ZLYm1Gbnc1dmtNd2RoemlBT1R0elRwcmU3ckdhaEpwZlk0eFRWWjZpK3pjQ1pCWgo0ZHhSTjlQWnBiQm4vNmJDTFY3S0I3ZmY0Uk04b3c3K1l4aHFFTFhxcnVQS2paMnRRSWs2M2hzZklMd0tiRWIrCkhEM2VLZEhNc2hhbXFXZzI0NnlORW5nQUZCQzhoVU5kQ0pmeUthUlhkSlV5eDhqcWlKM3ErdnpnNFBieUhqeW0KZEQzM2pVT2UwdHdoSWJxKytUaXZCa1ptSDBsSStnVT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= +kind: Secret +metadata: + creationTimestamp: null + name: cafe-secret +type: kubernetes.io/tls diff --git a/examples/custom-resources/transport-server-sni/cafe-transport-server.yaml b/examples/custom-resources/transport-server-sni/cafe-transport-server.yaml new file mode 100644 index 0000000000..638d6496a8 --- /dev/null +++ b/examples/custom-resources/transport-server-sni/cafe-transport-server.yaml @@ -0,0 +1,17 @@ +apiVersion: k8s.nginx.org/v1 +kind: TransportServer +metadata: + name: cafe-ts +spec: + host: cafe.example.com + listener: + name: tcp-listener + protocol: TCP + tls: + secret: cafe-secret + upstreams: + - name: tcp-echo + service: tcp-echo-service + port: 7000 + action: + pass: tcp-echo diff --git a/examples/custom-resources/transport-server-sni/mongo-secret.yaml b/examples/custom-resources/transport-server-sni/mongo-secret.yaml new file mode 100644 index 0000000000..54c0c3d964 --- /dev/null +++ b/examples/custom-resources/transport-server-sni/mongo-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY2VENDQTlHZ0F3SUJBZ0lVTGRHUktVc3FrZktiV1JheWwvaTdrSkVvWnRjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZTXhDekFKQmdOVkJBWVRBbGhZTVJJd0VBWURWUVFJREFsVGRHRjBaVTVoYldVeEVUQVBCZ05WQkFjTQpDRU5wZEhsT1lXMWxNUlF3RWdZRFZRUUtEQXREYjIxd1lXNTVUbUZ0WlRFYk1Ca0dBMVVFQ3d3U1EyOXRjR0Z1CmVWTmxZM1JwYjI1T1lXMWxNUm93R0FZRFZRUUREQkZ0YjI1bmJ5NWxlR0Z0Y0d4bExtTnZiVEFlRncweU5EQTUKTWpNeE1EQTFNVGxhRncwek5EQTVNakV4TURBMU1UbGFNSUdETVFzd0NRWURWUVFHRXdKWVdERVNNQkFHQTFVRQpDQXdKVTNSaGRHVk9ZVzFsTVJFd0R3WURWUVFIREFoRGFYUjVUbUZ0WlRFVU1CSUdBMVVFQ2d3TFEyOXRjR0Z1CmVVNWhiV1V4R3pBWkJnTlZCQXNNRWtOdmJYQmhibmxUWldOMGFXOXVUbUZ0WlRFYU1CZ0dBMVVFQXd3UmJXOXUKWjI4dVpYaGhiWEJzWlM1amIyMHdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDRHdBd2dnSUtBb0lDQVFEUAppQngwMDU3MjB2S2JVMGJsZUNjcTg2N0RJREkzbnZ1VjdiMVhCaEJHOFJ5S1MveDREc2pPYjMwaVZtVm1MR29xCm5WVnk5bnN5VDBEMmhydnE2eUp4VEtBMmtTNFVOd3JJZDBhc3FqSmxITWFpbkNmbmtGLzB4RmwxZERvYUEydncKelZZYnpQd3NBRlQrZGk2cG1hcnFQVFlNditiL0NhQnFib3dBQWFtUjhLemdGRTZDSFNZc1EwSzFEV2xhbmRBOAoxRXlFT0p0dndjQXFjVHVXTXl6eGMzUUx6dmM4Z0hGcG1NdlNEUUtvL2xpbytUYlFaOW1ldXJKTTY0dUVtRThzCmloVDNxREo2OUE0VkpDQWs0cGVmZ0p6cjNnN1ExOTBvVmpwOGtXZ0NIMkl3OHdXUGVjYUVySXFHcmo2YkNzcHUKSHVQaGlaQnB4aDlmOUhmWCt1NHBpNytGU2VPaC8xU0xEdE9qVzAwV256TnFJSGJvSmhLd2MyRXU3Z2xkc2VNdgp5a0VQbWtKNDkyNXN1Rmsxc3U5RWdDeDBhM1JwWCtxNERsSjFHaEl6QUJja0hOVXplUXJWVGdYTHkwdk1wZGdCClVXU3hkMWdGdGM2ZGp6WEoxZUxwcEVPaEtQdmQrVWN2eEpPb2NLekx3a1BGNmNueVl2NElFUnFZWE9UUktlNFcKamFKR05xWisvNGRJQXh4M0UxamtyZUR0UUNzMVByaFBLaGpVdVlKaVI5UFFsdEFUa2w4MXQ1NzEyeDExYUQ0SgpNMWRsOXdoSUlNcjFoQzF6Wms5SkZXdkluRytCTEdBY3d4cjduY2pLTEpzcjBpS3R2aFEzbndWdG9GWnlzWERCClBBdU5QclFMM0JaT3JKanpvV3FJdU5lNVRqY3FiTG9WREx2NzMwMytXUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEUKRmdRVVpjTjdpUitRNGkrb3U2YS9aR01JUXQrRWJRY3dId1lEVlIwakJCZ3dGb0FVWmNON2lSK1E0aStvdTZhLwpaR01JUXQrRWJRY3dEd1lEVlIwVEFRSC9CQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FnRUFKYVRXCnFENnEyRm51RmtJcmJIbkxPdFdFTDUxTC9QMncyUWZqMTcrSWlJeDRNd3hzS082ZEtHbUdlazY2Sy9sSjdBaEIKWngyRkFiOFdERmpPeVFtL3Axc1lQME82L1RyUzYrYjRlbndaR3p3YXVoVWdXR0M3djByRlp0VnNMNm5pdGJkMAp6VExGZ1V6QkJHR3cwZ3E2ekF4ZG5BNXo1VkFyYjhzcGZFNzZ1Q1FWbEdYVXhua0Fka2FYVXlOYXU1YXo0VW1WClMzRjIvaG53RG5XYUpZUmxqMTJ2SFYxWmxiamwxMzE3Uk0rRHZMSkRwV29JTkdzRzd0SG9pTGQ1dXJJeWYxZ0oKZXdJN1FXYTQ5UE1MWCszYnNSbjQ4dnJZUGxqWGcvdkhqYlZvUWlzMmFCak1sSDNCczdMVGVvVWZqVERsU2c3cApHd3ZzaW41dWJPZFAvTGRrWllaTFhiMWkvT1E3cjFpZTJ3OTQyd1RZT3NKRk5QVlB4N3JPV1BNeHd0Q0tUeE41Ck5URXFPM0F5dXZpUW9tRVhhL1RyL2xybnFSWS8rcXNpL2J2d01QWWlIS0RiTmJMM1NQaSt4bmU0Y3NxQ0d5L3QKMGVsaHVYVHpDTXUvcXdvM2ZNVXc4VFBBMXcvOEZTMWh4ZWkzY0JhL3VxQzFPS2s3aDRTLzJocXljaFd5ekhIRApMNUdob2RGZ0NkVTZaSUZJSnJzQS9uQ09maGZWUXJJMFRRZWlMSHR6OXd0T2tWTXhPeWRpcGpVMEgySjErRElwCi8vQzUrM2FpNWlxTkZnUTNZNEp5SGFlYmh3NW5lcjh1U3JmN3NnMit1a1dVU0QxamhnRVQxVWEzTTNUeG00bmYKNXhXOFV1aTFDYjVMdEM2clgvV1A4SG4veTJid2Iyc0swMGJYb1Y4PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRRFBpQngwMDU3MjB2S2IKVTBibGVDY3E4NjdESURJM252dVY3YjFYQmhCRzhSeUtTL3g0RHNqT2IzMGlWbVZtTEdvcW5WVnk5bnN5VDBEMgpocnZxNnlKeFRLQTJrUzRVTndySWQwYXNxakpsSE1haW5DZm5rRi8weEZsMWREb2FBMnZ3elZZYnpQd3NBRlQrCmRpNnBtYXJxUFRZTXYrYi9DYUJxYm93QUFhbVI4S3pnRkU2Q0hTWXNRMEsxRFdsYW5kQTgxRXlFT0p0dndjQXEKY1R1V015enhjM1FMenZjOGdIRnBtTXZTRFFLby9saW8rVGJRWjltZXVySk02NHVFbUU4c2loVDNxREo2OUE0VgpKQ0FrNHBlZmdKenIzZzdRMTkwb1ZqcDhrV2dDSDJJdzh3V1BlY2FFcklxR3JqNmJDc3B1SHVQaGlaQnB4aDlmCjlIZlgrdTRwaTcrRlNlT2gvMVNMRHRPalcwMFduek5xSUhib0poS3djMkV1N2dsZHNlTXZ5a0VQbWtKNDkyNXMKdUZrMXN1OUVnQ3gwYTNScFgrcTREbEoxR2hJekFCY2tITlV6ZVFyVlRnWEx5MHZNcGRnQlVXU3hkMWdGdGM2ZApqelhKMWVMcHBFT2hLUHZkK1VjdnhKT29jS3pMd2tQRjZjbnlZdjRJRVJxWVhPVFJLZTRXamFKR05xWisvNGRJCkF4eDNFMWprcmVEdFFDczFQcmhQS2hqVXVZSmlSOVBRbHRBVGtsODF0NTcxMngxMWFENEpNMWRsOXdoSUlNcjEKaEMxelprOUpGV3ZJbkcrQkxHQWN3eHI3bmNqS0xKc3IwaUt0dmhRM253VnRvRlp5c1hEQlBBdU5QclFMM0JaTwpySmp6b1dxSXVOZTVUamNxYkxvVkRMdjczMDMrV1FJREFRQUJBb0lDQUU3ZDJYNldPMmR1WkE4ZUZ5ZTJRU0JFCkNlcVNUak13QWtrSVYzZCtVT284ejgxSXNqSEg0SXorOW0xNXFzQW82ZEczQjlXUUVPSmVGd0I0MUdvaW9HeXgKSTRPSktaczZEYW1BRm9ZZ2lkVStHY2lMRW1rZ1J5OEQvVUV6QWErSUZGbW5GdTJxdVR4WmhmTkw0MURGbXB1NAoxbFVEQ3B4cVFxR2YwQ2xpZUZnRFFCZEo4RW5uSE80ZVEzZjltRWQ5Q0xsTkxxVGl4RU0wdkx3RVd4SXA4WTd5CmdxdklJOUhFdUJUYW9iNTUva1JOb0ZEYW9IZVR0N0pvSGNFNGxFVTRBb0taR1AzQzJDZzhuaXR2bHAyZDFPUWoKSXI5S0hKUkdMSUFiUU0rOURHc2VGUmtvQ2Jsc0hFS29OVjZZVWlkbWN1WmxhOUYyajBCN0w4b3Q0K3RhcTIzRApYendTSWFrWXBlaFVrYlRrbDJDak9FTHkxaCsrOFYzcFZqak5LdW9pRHhKWTdSSzFMQk9obExlSG5YUzlGY2hGCmVGRVZlb0c2ZlY0QWtFdGJocDJjRExacGF0UWg5ZUNzbmhUT2d2a002WjZ2ZWlTcGpvTUtleGM3bnRVZEJyWFAKQ3lSemVkSEFaaEh5MnBvREFvYU80aW5yOEI2U2VwWlJiaTBPK1ZXQXJkNDJ3UWhXdWZRUFQ1VExVSEoyR3VjeQpkME5wZk9VRXJYT3RVdU11NFNRU2lQbHJ6Ykx3NThrOFdKUVU2TXRGTnpXQ29yTG9TNWVmNU9LclBFYXh3SmhNCnh5Q1FPaWhLQk1SWE1IUXNib2JQZjhOQlJqYVNtK1ZzL0ZpOXdiYnVLZTVpYUhQenB2YVlzMmpqYzcyYWIzMVcKL2V4cUtSQmtEUVQvcmRPSHdJWHZBb0lCQVFEKzdIKzJyM3EwVVMvY0VHalZlSlovdzJXQzJJRCsxNGY3eHdvKwo5V05iWUdyY3NoZTRVSC9JSVpEL3RHQ2MrZ01mSlhTckk5OVhXejZ5Y0p5YXhTdTFsQmxsWEduTk5JM1AwYXpPCmdRc2FjenpjR1JLdk5WTWIyZmJDVEdHR3JVMXZjQVNET1ltZDZha3laemxBaWN4aHI3bStuMWZrLzY1bWFBeEgKTFBleXQwYWMydEdVL293MWJwV1hvRi9OL05QMTRsZ2ZMU2pXQmpWVEQ0bnByenNBU1RsK1hCLzRuK01wUG9CZwo4K2poYW5mUHFDY2MyQW5VMXAvTGZKSDZ6MWJkcHpDNVVQaFF6K2t3a2gwa3Q1cDRQOW5kV3FIWkw1cDlzS1J6Cld3S3pVQld4VjB3VDBOQzRnRlBtYzJaRGpTR0hlSXJLeWx1YVdIS2w4eVcwNGRldkFvSUJBUURRYUdVSlBtT08KQlZzUFROanhTdStzVGg4czNUZFhLUVIwQU02Y0l4UmVrVHpaODg2L3ptUFhlQThsNmROV2c1L0hCTE4rbUllNApnZnV2YWxnMEh5TWI3TC8vbEtYcHpFQTRncVdxU29MaXd4c1JHcUU1NFNud1g3QXV6K0NsT1lTUlNSQXpmeC9BCnE2QS9rOHR6SzN5NVF4Rk5Sc0NFak1GdWY5ak1yWCs0aXU0c3JvZEdUMXkvRE9hZ2twRmphVWZpL2NiaTM4V2sKdVhvK0ZoYmV1Yys0N1VzWjRXWDJSOGg1Uy9sUVhBYkxKU0lwMjlSZy9EK2pZdUllZTZXbk9pdzZWVkRweHhlYgp6RDh3SGVJTHhuRHdZbXprTzVrSG5ZVU5ubUtmWFovU1NpdWpkN0ZvQ1pMZld0R1gzQlFSdVNtRHlQSnBmSTlKCmVtNTl3bkkxVEFSM0FvSUJBUUNBa1l5VCtZcThPSm9YdGhyNVZ2a29kTWJVcUJiZThKci9xOUlLRUw3TWppTTMKTFliekNYNTQxQjBLS2RIME9jK3JQTHZMdUtyaXB2MUhCNjZrREQ5UU0rSmZFYTIydGdPenhYOFBJMXdUT2YxKwowQkp4VlVhV0xHYmNkYU5XUmo5Z3JiRkk4WkxybHJZajJwV3diQTh0VVhBdnFMT3VwaGt5UXRXMmJBSjlHeHc4CjdjdDRCcTEySVZESENUWm9jRlFDbGVaMXl0UG1wWGp0YkUvVkVQQ0Q0MnBneFZ2R2kvVUlqeUkyUTYyM1NuZ2sKdmgwRDJoMlRQNitWOUR5M3J5eVRXOGdpSHFrdU1MM2VKa01XaXBjWWdMT0RoVHROaTBteWpJMVVOSmwzRURQdwpuaU9iZHR4ZHdUTVBiaklzYlpoMGQ2SWdSdERPVmo5MFhONHVqUnkzQW9JQkFFNTRRREt1endWV2R1Ylg1SWRWCjA5Qm95Y3cycnZPZWVoTERpd2UzSGFCTnh6KzVVUXRmUnJDR2dBMmljUFNPTXNiWXVremNXWjNiTTB6bEdiam0KVUczZlFwdVUrTE9ET0ZzT3RobmNYRlBOYW8rU0cwcVR3UnJFcksyemo3NG1YZ2ZtSHJlRkVndVZrNHpjdFNuMApJYzRQdHFBR0Y4N1F3TFErWnY4S0JLRVRqb0k4WktyUWp2ZFFnRFhOZWZpWVYzemNXTnBycnh0S3l3QTlpUGJyCnQ0N0ZxaFZnak9laU41V1VTWmM4VDBLR0JNc0YvbjFWL1JBajEwZnEvb0Jzb3VLRDVTZGcwejdTTktpRlYrdGYKR0g4cVVCM1BZdHMvTUMza2lQWEFac0RqTkhNa1NpUUdGc3NLZ3doTzBTK3JMRHAybXUraytyNkwzclp6VkZWRQovaGtDZ2dFQUxpdGI5U29mLy9HenBpcmpTMW16SXNRWUxBWW9ETkZGNE5wYzV1Q0pDWEJJTmVFQnl4YlVzZFRVCkRyTnZ2NG1oNTF5SU1WVHVuM3MzKzFUU25lVjZwdjlIS3JicTlEdU85YXBUOFZnV1E5ZGU5YkVaU0lLVG5iVE0KcUVoLzNIelpKcmNRWjMxWEdTZm5NN0NsVjdPQWM4bVNiT3M4NDlGLzg4UEg0Y3VrYVRWNmZPUnFOaW54RXhuZQpqbnk3ajUxT01aWTQ4TVIvaGJhOVk3QWM3TE1Ec0pQdFZ5cStBemU4ODRreU5ZV2dtZHJHanBaVnhLeDQzMjFJCmhXM1NUam9SZEZBMFVCdENyT2ZUalF0cThPQzZqL1QrYWdwa0g2VjFQVkw3Y29PbjREU2RIb1RqdnJFZ0QrYTgKVzY1ODFvdHdha2tMK0FGalU0MkVHaTkxWnJLSUx3PT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= +kind: Secret +metadata: + creationTimestamp: null + name: mongo-secret +type: kubernetes.io/tls diff --git a/examples/custom-resources/transport-server-sni/mongo-transport-server.yaml b/examples/custom-resources/transport-server-sni/mongo-transport-server.yaml new file mode 100644 index 0000000000..529fb13128 --- /dev/null +++ b/examples/custom-resources/transport-server-sni/mongo-transport-server.yaml @@ -0,0 +1,17 @@ +apiVersion: k8s.nginx.org/v1 +kind: TransportServer +metadata: + name: mongo-ts +spec: + host: mongo.example.com + tls: + secret: mongo-secret + listener: + name: tcp-listener + protocol: TCP + upstreams: + - name: mongo + service: mongodb + port: 27017 + action: + pass: mongo diff --git a/examples/custom-resources/transport-server-sni/mongo.yaml b/examples/custom-resources/transport-server-sni/mongo.yaml new file mode 100644 index 0000000000..ef3498704f --- /dev/null +++ b/examples/custom-resources/transport-server-sni/mongo.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: mongodb +spec: + selector: + app: mongodb + ports: + - protocol: TCP + port: 27017 + targetPort: 27017 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongodb +spec: + replicas: 1 + selector: + matchLabels: + app: mongodb + template: + metadata: + labels: + app: mongodb + spec: + containers: + - name: mongodb + image: mongo:latest + ports: + - containerPort: 27017 + volumeMounts: + - name: storage + mountPath: /data/db + volumes: + - name: storage + emptyDir: {} diff --git a/examples/custom-resources/transport-server-sni/tcp-echo-server.yaml b/examples/custom-resources/transport-server-sni/tcp-echo-server.yaml new file mode 100644 index 0000000000..e505296f4a --- /dev/null +++ b/examples/custom-resources/transport-server-sni/tcp-echo-server.yaml @@ -0,0 +1,36 @@ + apiVersion: apps/v1 + kind: Deployment + metadata: + name: tcp-echo-server + spec: + replicas: 1 + selector: + matchLabels: + app: tcp-echo-server + template: + metadata: + labels: + app: tcp-echo-server + spec: + containers: + - name: tcp-echo-server + image: alpine + command: ["/bin/sh"] + args: + - -c + - nc -lk -p 7000 -e /bin/cat + ports: + - containerPort: 7000 +--- +apiVersion: v1 +kind: Service +metadata: + name: tcp-echo-service +spec: + selector: + app: tcp-echo-server + ports: + - protocol: TCP + port: 7000 + targetPort: 7000 + type: ClusterIP diff --git a/internal/configs/configurator.go b/internal/configs/configurator.go index 4e23006ba5..fd6982376d 100644 --- a/internal/configs/configurator.go +++ b/internal/configs/configurator.go @@ -770,8 +770,9 @@ func (cnf *Configurator) addOrUpdateTransportServer(transportServerEx *Transport cnf.transportServers[name] = transportServerEx // update TLS Passthrough Hosts config in case we have a TLS Passthrough TransportServer - // only TLS Passthrough TransportServers have non-empty hosts - if transportServerEx.TransportServer.Spec.Host != "" { + // A non empty Host, may be a TLS Passthrough TransportServer but we have to check for the existence of the TLS Passthrough listener also, as TransportServers that terminate at the NGINX level can have non empty Hosts now too + isTLSPassthrough := transportServerEx.TransportServer.Spec.Listener.Name == conf_v1.TLSPassthroughListenerName + if transportServerEx.TransportServer.Spec.Host != "" && isTLSPassthrough { key := generateNamespaceNameKey(&transportServerEx.TransportServer.ObjectMeta) cnf.tlsPassthroughPairs[key] = tlsPassthroughPair{ Host: transportServerEx.TransportServer.Spec.Host, diff --git a/internal/configs/configurator_test.go b/internal/configs/configurator_test.go index 8406bc7c06..058c2e6906 100644 --- a/internal/configs/configurator_test.go +++ b/internal/configs/configurator_test.go @@ -15,9 +15,11 @@ import ( "github.com/nginxinc/kubernetes-ingress/internal/configs/version1" "github.com/nginxinc/kubernetes-ingress/internal/configs/version2" + "github.com/nginxinc/kubernetes-ingress/internal/k8s/secrets" "github.com/nginxinc/kubernetes-ingress/internal/nginx" conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1" "github.com/nginxinc/kubernetes-ingress/pkg/apis/dos/v1beta1" + api_v1 "k8s.io/api/core/v1" ) func createTestStaticConfigParams() *StaticConfigParams { @@ -1686,6 +1688,21 @@ func TestGetVitualServerCountsNotExistingVS(t *testing.T) { } } +func TestAddOrUpdateTransportServer(t *testing.T) { + t.Parallel() + cnf := createTestConfigurator(t) + + ts := createTransportServerExWithHostNoTLSPassthrough() + + warnings, err := cnf.AddOrUpdateTransportServer(&ts) + if err != nil { + t.Errorf("AddOrUpdateTransportServer returned: \n%v, but expected: \n%v", err, nil) + } + if len(warnings) != 0 { + t.Errorf("AddOrUpdateTransportServer returned warnings: %v", warnings) + } +} + var ( invalidVirtualServerEx = &VirtualServerEx{ VirtualServer: &conf_v1.VirtualServer{}, @@ -1796,6 +1813,45 @@ func TestGenerateApDosAllowListFileContent(t *testing.T) { } } +func createTransportServerExWithHostNoTLSPassthrough() TransportServerEx { + return TransportServerEx{ + SecretRefs: map[string]*secrets.SecretReference{ + "default/echo-secret": { + Secret: &api_v1.Secret{ + Type: api_v1.SecretTypeTLS, + }, + Path: "secret.pem", + }, + }, + TransportServer: &conf_v1.TransportServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "echo-app", + Namespace: "default", + }, + Spec: conf_v1.TransportServerSpec{ + Listener: conf_v1.TransportServerListener{ + Name: "tcp-listener", + Protocol: "TCP", + }, + Host: "example.com", + TLS: &conf_v1.TransportServerTLS{ + Secret: "echo-secret", + }, + Upstreams: []conf_v1.TransportServerUpstream{ + { + Name: "echo-app", + Service: "echo-app", + Port: 7000, + }, + }, + Action: &conf_v1.TransportServerAction{ + Pass: "echo-app", + }, + }, + }, + } +} + var ( // custom test Main Template represents a main-template passed via ConfigMap customTestMainTemplate = `# TEST NEW MAIN TEMPLATE diff --git a/internal/configs/transportserver.go b/internal/configs/transportserver.go index c78db4e217..7dabd46dfa 100644 --- a/internal/configs/transportserver.go +++ b/internal/configs/transportserver.go @@ -98,13 +98,18 @@ func generateTransportServerConfig(p transportServerConfigParams) (*version2.Tra if p.transportServerEx.TransportServer.Spec.Listener.Name == conf_v1.TLSPassthroughListenerName { statusZone = p.transportServerEx.TransportServer.Spec.Host } + host := p.transportServerEx.TransportServer.Spec.Host + isTLSPassthrough := p.transportServerEx.TransportServer.Spec.Listener.Name == conf_v1.TLSPassthroughListenerName + serverName := generateServerName(host, isTLSPassthrough) + isUDP := p.transportServerEx.TransportServer.Spec.Listener.Protocol == "UDP" tsConfig := &version2.TransportServerConfig{ Server: version2.StreamServer{ - TLSPassthrough: p.transportServerEx.TransportServer.Spec.Listener.Name == conf_v1.TLSPassthroughListenerName, + ServerName: serverName, + TLSPassthrough: isTLSPassthrough, UnixSocket: generateUnixSocket(p.transportServerEx), Port: p.listenerPort, - UDP: p.transportServerEx.TransportServer.Spec.Listener.Protocol == "UDP", + UDP: isUDP, StatusZone: statusZone, ProxyRequests: proxyRequests, ProxyResponses: proxyResponses, @@ -351,3 +356,10 @@ func generateLoadBalancingMethod(method string) string { } return method } + +func generateServerName(host string, isTLSPassthrough bool) string { + if isTLSPassthrough { + return "" + } + return host +} diff --git a/internal/configs/transportserver_test.go b/internal/configs/transportserver_test.go index cf8edfbc1e..c0abb19b66 100644 --- a/internal/configs/transportserver_test.go +++ b/internal/configs/transportserver_test.go @@ -524,6 +524,7 @@ func TestGenerateTransportServerConfigForTLSPassthrough(t *testing.T) { Port: 2020, UDP: false, StatusZone: "example.com", + ServerName: "", ProxyPass: "ts_default_tcp-server_tcp-app", Name: "tcp-server", Namespace: "default", @@ -642,6 +643,7 @@ func TestGenerateTransportServerConfigForBackupServiceNGINXPlus(t *testing.T) { Port: 2020, UDP: false, StatusZone: "example.com", + ServerName: "", ProxyPass: "ts_default_tcp-server_tcp-app", Name: "tcp-server", Namespace: "default", @@ -715,6 +717,7 @@ func TestGenerateTransportServerConfig_DoesNotGenerateBackupOnMissingBackupName( Port: 2020, UDP: false, StatusZone: "example.com", + ServerName: "", ProxyPass: "ts_default_tcp-server_tcp-app", Name: "tcp-server", Namespace: "default", @@ -789,6 +792,7 @@ func TestGenerateTransportServerConfig_DoesNotGenerateBackupOnMissingBackupPort( Port: 2020, UDP: false, StatusZone: "example.com", + ServerName: "", ProxyPass: "ts_default_tcp-server_tcp-app", Name: "tcp-server", Namespace: "default", @@ -863,6 +867,7 @@ func TestGenerateTransportServerConfig_DoesNotGenerateBackupOnMissingBackupPortA Port: 2020, UDP: false, StatusZone: "example.com", + ServerName: "", ProxyPass: "ts_default_tcp-server_tcp-app", Name: "tcp-server", Namespace: "default", @@ -1285,6 +1290,123 @@ func TestGenerateTransportServerConfig_UsesNotExistignSocketOnNotPlusAndNoEndpoi } } +func TestGenerateTransportServerConfigForTCPWithTLSWithHost(t *testing.T) { + t.Parallel() + transportServerEx := TransportServerEx{ + TransportServer: &conf_v1.TransportServer{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "tcp-server", + Namespace: "default", + }, + Spec: conf_v1.TransportServerSpec{ + Host: "cafe.example.com", + Listener: conf_v1.TransportServerListener{ + Name: "tcp-listener", + Protocol: "TCP", + }, + TLS: &conf_v1.TransportServerTLS{ + Secret: "my-secret", + }, + Upstreams: []conf_v1.TransportServerUpstream{ + { + Name: "tcp-app", + Service: "tcp-app-svc", + Port: 5001, + MaxFails: intPointer(3), + FailTimeout: "40s", + }, + }, + UpstreamParameters: &conf_v1.UpstreamParameters{ + ConnectTimeout: "30s", + NextUpstream: false, + }, + SessionParameters: &conf_v1.SessionParameters{ + Timeout: "50s", + }, + Action: &conf_v1.TransportServerAction{ + Pass: "tcp-app", + }, + }, + }, + Endpoints: map[string][]string{ + "default/tcp-app-svc:5001": { + "10.0.0.20:5001", + }, + }, + DisableIPV6: false, + SecretRefs: map[string]*secrets.SecretReference{ + "default/my-secret": { + Secret: &api_v1.Secret{ + Type: api_v1.SecretTypeTLS, + }, + Path: "/etc/nginx/secrets/default-my-secret", + }, + }, + } + + listenerPort := 2020 + + expected := &version2.TransportServerConfig{ + Upstreams: []version2.StreamUpstream{ + { + Name: "ts_default_tcp-server_tcp-app", + Servers: []version2.StreamUpstreamServer{ + { + Address: "10.0.0.20:5001", + MaxFails: 3, + FailTimeout: "40s", + }, + }, + UpstreamLabels: version2.UpstreamLabels{ + ResourceName: "tcp-server", + ResourceType: "transportserver", + ResourceNamespace: "default", + Service: "tcp-app-svc", + }, + LoadBalancingMethod: "random two least_conn", + }, + }, + Server: version2.StreamServer{ + Port: 2020, + UDP: false, + StatusZone: "tcp-listener", + ServerName: "cafe.example.com", + ProxyPass: "ts_default_tcp-server_tcp-app", + Name: "tcp-server", + Namespace: "default", + ProxyConnectTimeout: "30s", + ProxyNextUpstream: false, + ProxyNextUpstreamTries: 0, + ProxyNextUpstreamTimeout: "0s", + ProxyTimeout: "50s", + HealthCheck: nil, + ServerSnippets: []string{}, + SSL: &version2.StreamSSL{ + Enabled: true, + Certificate: "/etc/nginx/secrets/default-my-secret", + CertificateKey: "/etc/nginx/secrets/default-my-secret", + }, + }, + StreamSnippets: []string{}, + StaticSSLPath: "/etc/nginx/secret", + } + + result, warnings := generateTransportServerConfig(transportServerConfigParams{ + transportServerEx: &transportServerEx, + listenerPort: listenerPort, + isPlus: true, + isResolverConfigured: false, + isDynamicReloadEnabled: false, + staticSSLPath: "/etc/nginx/secret", + }) + if len(warnings) != 0 { + t.Errorf("want no warnings, got %v", warnings) + } + if !cmp.Equal(expected, result) { + t.Errorf("generateTransportServerConfig() mismatch (-want +got):\n%s", cmp.Diff(expected, result)) + } +} + func TestGenerateTransportServerConfigForTCPWithTLS(t *testing.T) { t.Parallel() transportServerEx := TransportServerEx{ diff --git a/internal/configs/version2/__snapshots__/templates_test.snap b/internal/configs/version2/__snapshots__/templates_test.snap index c2a797fd0d..1b90fc3072 100644 --- a/internal/configs/version2/__snapshots__/templates_test.snap +++ b/internal/configs/version2/__snapshots__/templates_test.snap @@ -1,4 +1,62 @@ +[TestExecuteTemplateForNGINXOSSTransportServerWithSNI - 1] + +upstream cafe-upstream { + zone cafe-upstream 512k; + server 10.0.0.20:5001 max_fails=0 fail_timeout= max_conns=0; +} +server { + listen 1234 ssl; + listen [::]:1234 ssl; + server_name "cafe.example.com"; + ssl_certificate cafe-secret.pem; + ssl_certificate_key cafe-secret.pem; + proxy_requests 1; + proxy_responses 2; + + proxy_pass cafe-upstream; + + proxy_timeout 10s; + proxy_connect_timeout 10s; + proxy_next_upstream on; + proxy_next_upstream_timeout 10s; + proxy_next_upstream_tries 5; +} + +--- + +[TestExecuteTemplateForNGINXPlusTransportServerWithSNI - 1] + +upstream cafe-upstream { + zone cafe-upstream 512k; + server 10.0.0.20:5001 max_fails=0 fail_timeout= max_conns=0; +} + + +server { + listen 1234 ssl; + listen [::]:1234 ssl; + server_name "cafe.example.com"; + ssl_certificate cafe-secret.pem; + ssl_certificate_key cafe-secret.pem; + + status_zone ; + proxy_requests 1; + proxy_responses 2; + + proxy_pass cafe-upstream; + + + + proxy_timeout 10s; + proxy_connect_timeout 10s; + proxy_next_upstream on; + proxy_next_upstream_timeout 10s; + proxy_next_upstream_tries 5; +} + +--- + [TestExecuteTemplateForTransportServerWithBackupServerForNGINXPlus - 1] upstream udp-upstream { diff --git a/internal/configs/version2/nginx-plus.transportserver.tmpl b/internal/configs/version2/nginx-plus.transportserver.tmpl index 8b959b548f..3d37d3dfa9 100644 --- a/internal/configs/version2/nginx-plus.transportserver.tmpl +++ b/internal/configs/version2/nginx-plus.transportserver.tmpl @@ -40,6 +40,7 @@ server { set_real_ip_from unix:; {{- else }} {{ makeTransportListener $s | printf }} + {{- with makeServerName $s }}{{ printf "\t%s" . }}{{- end }} {{- end }} {{- if $ssl.Enabled }} diff --git a/internal/configs/version2/nginx.transportserver.tmpl b/internal/configs/version2/nginx.transportserver.tmpl index eff9df8665..7715fff38a 100644 --- a/internal/configs/version2/nginx.transportserver.tmpl +++ b/internal/configs/version2/nginx.transportserver.tmpl @@ -24,6 +24,7 @@ server { set_real_ip_from unix:; {{- else }} {{ makeTransportListener $s | printf }} + {{- with makeServerName $s }}{{ printf "\t%s" . }}{{- end }} {{- end }} {{- if $ssl.Enabled }} diff --git a/internal/configs/version2/stream.go b/internal/configs/version2/stream.go index ef2e67daba..b7d7ff9e8b 100644 --- a/internal/configs/version2/stream.go +++ b/internal/configs/version2/stream.go @@ -37,6 +37,7 @@ type StreamUpstreamBackupServer struct { // StreamServer defines a server in the stream module. type StreamServer struct { + ServerName string TLSPassthrough bool UnixSocket string Port int diff --git a/internal/configs/version2/template_helper.go b/internal/configs/version2/template_helper.go index 95e8f59d78..8dec8347ab 100644 --- a/internal/configs/version2/template_helper.go +++ b/internal/configs/version2/template_helper.go @@ -224,6 +224,13 @@ func makeHeaderQueryValue(apiKey APIKey) string { return fmt.Sprintf("\"%s\"", strings.Join(parts, "")) } +func makeServerName(s StreamServer) string { + if s.TLSPassthrough || s.ServerName == "" || s.SSL == nil { + return "" + } + return fmt.Sprintf("server_name \"%s\";", s.ServerName) +} + var helperFunctions = template.FuncMap{ "headerListToCIMap": headerListToCIMap, "hasCIKey": hasCIKey, @@ -238,4 +245,5 @@ var helperFunctions = template.FuncMap{ "makeSecretPath": commonhelpers.MakeSecretPath, "makeHeaderQueryValue": makeHeaderQueryValue, "makeTransportListener": makeTransportListener, + "makeServerName": makeServerName, } diff --git a/internal/configs/version2/template_helper_test.go b/internal/configs/version2/template_helper_test.go index 8c2ea4cb9c..58d166657d 100644 --- a/internal/configs/version2/template_helper_test.go +++ b/internal/configs/version2/template_helper_test.go @@ -613,6 +613,43 @@ func TestMakeTransportIPListener(t *testing.T) { } } +func TestMakeServerName(t *testing.T) { + t.Parallel() + + testCases := []struct { + server StreamServer + expected string + }{ + {server: StreamServer{ + TLSPassthrough: false, + ServerName: "cafe.example.com", + SSL: &StreamSSL{}, + }, expected: "server_name \"cafe.example.com\";"}, + {server: StreamServer{ + TLSPassthrough: true, + ServerName: "cafe.example.com", + SSL: &StreamSSL{}, + }, expected: ""}, + {server: StreamServer{ + TLSPassthrough: false, + ServerName: "", + SSL: &StreamSSL{}, + }, expected: ""}, + {server: StreamServer{ + TLSPassthrough: false, + ServerName: "cafe.example.com", + SSL: nil, + }, expected: ""}, + } + + for _, tc := range testCases { + got := makeServerName(tc.server) + if got != tc.expected { + t.Errorf("Function generated wrong config, got %q but expected %q.", got, tc.expected) + } + } +} + func newContainsTemplate(t *testing.T) *template.Template { t.Helper() tmpl, err := template.New("testTemplate").Funcs(helperFunctions).Parse(`{{contains .InputString .Substring}}`) diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index aa0f276c68..f5a1548bec 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -486,6 +486,26 @@ func TestExecuteTemplateForTransportServerWithResolver(t *testing.T) { snaps.MatchSnapshot(t, string(got)) } +func TestExecuteTemplateForNGINXOSSTransportServerWithSNI(t *testing.T) { + t.Parallel() + executor := newTmplExecutorNGINX(t) + got, err := executor.ExecuteTransportServerTemplate(&transportServerCfgWithSNI) + if err != nil { + t.Errorf("Failed to execute template: %v", err) + } + snaps.MatchSnapshot(t, string(got)) +} + +func TestExecuteTemplateForNGINXPlusTransportServerWithSNI(t *testing.T) { + t.Parallel() + executor := newTmplExecutorNGINXPlus(t) + got, err := executor.ExecuteTransportServerTemplate(&transportServerCfgWithSNI) + if err != nil { + t.Errorf("Failed to execute template: %v", err) + } + snaps.MatchSnapshot(t, string(got)) +} + func TestTransportServerForNginx(t *testing.T) { t.Parallel() executor := newTmplExecutorNGINX(t) @@ -5321,6 +5341,37 @@ var ( }, } + transportServerCfgWithSNI = TransportServerConfig{ + Upstreams: []StreamUpstream{ + { + Name: "cafe-upstream", + Servers: []StreamUpstreamServer{ + { + Address: "10.0.0.20:5001", + }, + }, + }, + }, + Server: StreamServer{ + Port: 1234, + ServerName: "cafe.example.com", + TLSPassthrough: false, + SSL: &StreamSSL{ + Enabled: true, + Certificate: "cafe-secret.pem", + CertificateKey: "cafe-secret.pem", + }, + ProxyRequests: createPointerFromInt(1), + ProxyResponses: createPointerFromInt(2), + ProxyPass: "cafe-upstream", + ProxyTimeout: "10s", + ProxyConnectTimeout: "10s", + ProxyNextUpstream: true, + ProxyNextUpstreamTimeout: "10s", + ProxyNextUpstreamTries: 5, + }, + } + transportServerCfgWithSSL = TransportServerConfig{ Upstreams: []StreamUpstream{ { diff --git a/internal/k8s/configuration.go b/internal/k8s/configuration.go index b600213754..139bf3f7a4 100644 --- a/internal/k8s/configuration.go +++ b/internal/k8s/configuration.go @@ -101,6 +101,16 @@ type IngressConfiguration struct { ChildWarnings map[string][]string } +type listenerHostKey struct { + ListenerName string + Host string +} + +// used for sorting +func (lhk listenerHostKey) String() string { + return fmt.Sprintf("%s|%s", lhk.ListenerName, lhk.Host) +} + // NewRegularIngressConfiguration creates an IngressConfiguration from an Ingress resource. func NewRegularIngressConfiguration(ing *networking.Ingress) *IngressConfiguration { return &IngressConfiguration{ @@ -332,9 +342,9 @@ type TransportServerMetrics struct { // The IC needs to ensure that at any point in time the NGINX config on the filesystem reflects the state // of the objects in the Configuration. type Configuration struct { - hosts map[string]Resource - listeners map[string]*TransportServerConfiguration - listenerMap map[string]conf_v1.Listener + hosts map[string]Resource + listenerHosts map[listenerHostKey]*TransportServerConfiguration + listenerMap map[string]conf_v1.Listener // only valid resources with the matching IngressClass are stored ingresses map[string]*networking.Ingress @@ -389,7 +399,7 @@ func NewConfiguration( ) *Configuration { return &Configuration{ hosts: make(map[string]Resource), - listeners: make(map[string]*TransportServerConfiguration), + listenerHosts: make(map[listenerHostKey]*TransportServerConfiguration), ingresses: make(map[string]*networking.Ingress), virtualServers: make(map[string]*conf_v1.VirtualServer), virtualServerRoutes: make(map[string]*conf_v1.VirtualServerRoute), @@ -611,7 +621,7 @@ func (c *Configuration) AddOrUpdateGlobalConfiguration(gc *conf_v1.GlobalConfigu c.globalConfiguration = gc c.setGlobalConfigListenerMap() - listenerChanges, listenerProblems := c.rebuildListeners() + listenerChanges, listenerProblems := c.rebuildListenerHosts() changes = append(changes, listenerChanges...) problems = append(problems, listenerProblems...) @@ -633,7 +643,7 @@ func (c *Configuration) DeleteGlobalConfiguration() ([]ResourceChange, []Configu c.globalConfiguration = nil c.setGlobalConfigListenerMap() - listenerChanges, listenerProblems := c.rebuildListeners() + listenerChanges, listenerProblems := c.rebuildListenerHosts() changes = append(changes, listenerChanges...) problems = append(problems, listenerProblems...) @@ -671,7 +681,7 @@ func (c *Configuration) AddOrUpdateTransportServer(ts *conf_v1.TransportServer) } } - changes, problems := c.rebuildListeners() + changes, problems := c.rebuildListenerHosts() if c.isTLSPassthroughEnabled { hostChanges, hostProblems := c.rebuildHosts() @@ -681,7 +691,7 @@ func (c *Configuration) AddOrUpdateTransportServer(ts *conf_v1.TransportServer) } if validationErr != nil { - // If the invalid resource has an active host/listener, rebuildHosts/rebuildListeners will create a change + // If the invalid resource has an active host/listener, rebuildHosts/rebuildListenerHosts will create a change // to remove the resource. // Here we add the validationErr to that change. kind := getResourceKeyWithKind(transportServerKind, &ts.ObjectMeta) @@ -696,7 +706,7 @@ func (c *Configuration) AddOrUpdateTransportServer(ts *conf_v1.TransportServer) // On the other hand, the invalid resource might not have any active host/listener. // Or the resource was invalid before and is still invalid (in some different way). - // In those cases, rebuildHosts/rebuildListeners will create no change for that resource. + // In those cases, rebuildHosts/rebuildListenerHosts will create no change for that resource. // To make sure the validationErr is reported to the user, we create a problem. p := ConfigurationProblem{ Object: ts, @@ -722,7 +732,7 @@ func (c *Configuration) DeleteTransportServer(key string) ([]ResourceChange, []C delete(c.transportServers, key) - changes, problems := c.rebuildListeners() + changes, problems := c.rebuildListenerHosts() if c.isTLSPassthroughEnabled { hostChanges, hostProblems := c.rebuildHosts() @@ -734,13 +744,13 @@ func (c *Configuration) DeleteTransportServer(key string) ([]ResourceChange, []C return changes, problems } -func (c *Configuration) rebuildListeners() ([]ResourceChange, []ConfigurationProblem) { - newListeners, newTSConfigs := c.buildListenersAndTSConfigurations() +func (c *Configuration) rebuildListenerHosts() ([]ResourceChange, []ConfigurationProblem) { + newListenerHosts, newTSConfigs := c.buildListenerHostsAndTSConfigurations() - removedListeners, updatedListeners, addedListeners := detectChangesInListeners(c.listeners, newListeners) - changes := createResourceChangesForListeners(removedListeners, updatedListeners, addedListeners, c.listeners, newListeners) + removedListenerHosts, updatedListenerHosts, addedListenerHosts := detectChangesInListenerHosts(c.listenerHosts, newListenerHosts) + changes := createResourceChangesForListeners(removedListenerHosts, updatedListenerHosts, addedListenerHosts, c.listenerHosts, newListenerHosts) - c.listeners = newListeners + c.listenerHosts = newListenerHosts changes = squashResourceChanges(changes) @@ -766,15 +776,14 @@ func (c *Configuration) rebuildListeners() ([]ResourceChange, []ConfigurationPro return changes, newOrUpdatedProblems } -func (c *Configuration) buildListenersAndTSConfigurations() (newListeners map[string]*TransportServerConfiguration, newTSConfigs map[string]*TransportServerConfiguration) { - newListeners = make(map[string]*TransportServerConfiguration) - newTSConfigs = make(map[string]*TransportServerConfiguration) +func (c *Configuration) buildListenerHostsAndTSConfigurations() (map[listenerHostKey]*TransportServerConfiguration, map[string]*TransportServerConfiguration) { + newListenerHosts := make(map[listenerHostKey]*TransportServerConfiguration) + newTSConfigs := make(map[string]*TransportServerConfiguration) for key, ts := range c.transportServers { if ts.Spec.Listener.Protocol == conf_v1.TLSPassthroughListenerProtocol { continue } - tsc := NewTransportServerConfiguration(ts) newTSConfigs[key] = tsc @@ -800,23 +809,27 @@ func (c *Configuration) buildListenersAndTSConfigurations() (newListeners map[st tsc.IPv4 = listener.IPv4 tsc.IPv6 = listener.IPv6 - holder, exists := newListeners[listener.Name] + host := ts.Spec.Host + listenerKey := listenerHostKey{ListenerName: listener.Name, Host: host} + + holder, exists := newListenerHosts[listenerKey] if !exists { - newListeners[listener.Name] = tsc + newListenerHosts[listenerKey] = tsc continue } - warning := fmt.Sprintf("listener %s is taken by another resource", listener.Name) + // another TransportServer exists with the same listener and host + warning := fmt.Sprintf("listener %s and host %s are taken by another resource", listener.Name, host) if !holder.Wins(tsc) { holder.AddWarning(warning) - newListeners[listener.Name] = tsc + newListenerHosts[listenerKey] = tsc } else { tsc.AddWarning(warning) } } - return newListeners, newTSConfigs + return newListenerHosts, newTSConfigs } func (c *Configuration) buildListenersForVSConfiguration(vsc *VirtualServerConfiguration) { @@ -877,7 +890,7 @@ func (c *Configuration) GetResourcesWithFilter(filter resourceFilter) []Resource } if filter.TransportServers { - for _, r := range c.listeners { + for _, r := range c.listenerHosts { resources[r.GetKeyWithKind()] = r } } @@ -973,8 +986,8 @@ func (c *Configuration) findResourcesForResourceReference(namespace string, name } } - for _, l := range getSortedTransportServerConfigurationKeys(c.listeners) { - tsConfig := c.listeners[l] + for _, lh := range getSortedListenerHostKeys(c.listenerHosts) { + tsConfig := c.listenerHosts[lh] if checker.IsReferencedByTransportServer(namespace, name, tsConfig.TransportServer) { result = append(result, tsConfig) @@ -1061,15 +1074,25 @@ func detectChangesInProblems(newProblems map[string]ConfigurationProblem, oldPro return result } -func (c *Configuration) addProblemsForTSConfigsWithoutActiveListener(tsConfigs map[string]*TransportServerConfiguration, problems map[string]ConfigurationProblem) { +func (c *Configuration) addProblemsForTSConfigsWithoutActiveListener( + tsConfigs map[string]*TransportServerConfiguration, + problems map[string]ConfigurationProblem, +) { for _, tsc := range tsConfigs { - holder, exists := c.listeners[tsc.TransportServer.Spec.Listener.Name] + listenerName := tsc.TransportServer.Spec.Listener.Name + host := tsc.TransportServer.Spec.Host + hostDescription := "empty host" + if host != "" { + hostDescription = host + } + key := listenerHostKey{ListenerName: listenerName, Host: host} + holder, exists := c.listenerHosts[key] if !exists { p := ConfigurationProblem{ Object: tsc.TransportServer, IsError: false, Reason: "Rejected", - Message: fmt.Sprintf("Listener %s doesn't exist", tsc.TransportServer.Spec.Listener.Name), + Message: fmt.Sprintf("Listener %s doesn't exist", listenerName), } problems[tsc.GetKeyWithKind()] = p continue @@ -1080,7 +1103,7 @@ func (c *Configuration) addProblemsForTSConfigsWithoutActiveListener(tsConfigs m Object: tsc.TransportServer, IsError: false, Reason: "Rejected", - Message: fmt.Sprintf("Listener %s is taken by another resource", tsc.TransportServer.Spec.Listener.Name), + Message: fmt.Sprintf("Listener %s with host %s is taken by another resource", listenerName, hostDescription), } problems[tsc.GetKeyWithKind()] = p } @@ -1301,8 +1324,12 @@ func createResourceChangesForHosts(removedHosts []string, updatedHosts []string, return append(deleteChanges, changes...) } -func createResourceChangesForListeners(removedListeners []string, updatedListeners []string, addedListeners []string, oldListeners map[string]*TransportServerConfiguration, - newListeners map[string]*TransportServerConfiguration, +func createResourceChangesForListeners( + removedListeners []listenerHostKey, + updatedListeners []listenerHostKey, + addedListeners []listenerHostKey, + oldListeners map[listenerHostKey]*TransportServerConfiguration, + newListeners map[listenerHostKey]*TransportServerConfiguration, ) []ResourceChange { var changes []ResourceChange var deleteChanges []ResourceChange @@ -1653,7 +1680,7 @@ func (c *Configuration) GetTransportServerMetrics() *TransportServerMetrics { } } - for _, tsConfig := range c.listeners { + for _, tsConfig := range c.listenerHosts { if tsConfig.TransportServer.Spec.Listener.Protocol == "TCP" { metrics.TotalTCP++ } else { @@ -1746,14 +1773,16 @@ func getSortedTransportServerKeys(m map[string]*conf_v1.TransportServer) []strin return keys } -func getSortedTransportServerConfigurationKeys(m map[string]*TransportServerConfiguration) []string { - var keys []string +func getSortedListenerHostKeys(m map[listenerHostKey]*TransportServerConfiguration) []listenerHostKey { + var keys []listenerHostKey for k := range m { keys = append(keys, k) } - sort.Strings(keys) + sort.Slice(keys, func(i, j int) bool { + return keys[i].String() < keys[j].String() + }) return keys } @@ -1806,33 +1835,31 @@ func detectChangesInHosts(oldHosts map[string]Resource, newHosts map[string]Reso return removedHosts, updatedHosts, addedHosts } -func detectChangesInListeners(oldListeners map[string]*TransportServerConfiguration, newListeners map[string]*TransportServerConfiguration) (removedListeners []string, - updatedListeners []string, addedListeners []string, -) { - for _, l := range getSortedTransportServerConfigurationKeys(oldListeners) { - _, exists := newListeners[l] - if !exists { - removedListeners = append(removedListeners, l) - } - } +func detectChangesInListenerHosts( + oldListenerHosts map[listenerHostKey]*TransportServerConfiguration, + newListenerHosts map[listenerHostKey]*TransportServerConfiguration, +) (removedListenerHosts []listenerHostKey, updatedListenerHosts []listenerHostKey, addedListenerHosts []listenerHostKey) { + oldKeys := getSortedListenerHostKeys(oldListenerHosts) + newKeys := getSortedListenerHostKeys(newListenerHosts) - for _, l := range getSortedTransportServerConfigurationKeys(newListeners) { - _, exists := oldListeners[l] - if !exists { - addedListeners = append(addedListeners, l) + oldKeysSet := make(map[listenerHostKey]struct{}) + for _, key := range oldKeys { + oldKeysSet[key] = struct{}{} + if _, exists := newListenerHosts[key]; !exists { + removedListenerHosts = append(removedListenerHosts, key) } } - for _, l := range getSortedTransportServerConfigurationKeys(newListeners) { - oldR, exists := oldListeners[l] - if !exists { - continue - } - - if !oldR.IsEqual(newListeners[l]) { - updatedListeners = append(updatedListeners, l) + for _, key := range newKeys { + if _, exists := oldListenerHosts[key]; !exists { + addedListenerHosts = append(addedListenerHosts, key) + } else { + oldConfig := oldListenerHosts[key] + if !oldConfig.IsEqual(newListenerHosts[key]) { + updatedListenerHosts = append(updatedListenerHosts, key) + } } } - return removedListeners, updatedListeners, addedListeners + return removedListenerHosts, updatedListenerHosts, addedListenerHosts } diff --git a/internal/k8s/configuration_test.go b/internal/k8s/configuration_test.go index a040f1703b..b8d0289f09 100644 --- a/internal/k8s/configuration_test.go +++ b/internal/k8s/configuration_test.go @@ -2006,6 +2006,136 @@ func TestAddTransportServer(t *testing.T) { } } +func TestAddTransportServerWithHost(t *testing.T) { + configuration := createTestConfiguration() + + listeners := []conf_v1.Listener{ + { + Name: "tcp-7777", + Port: 7777, + Protocol: "TCP", + }, + } + + addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) + + secretName := "echo-secret" + + ts := createTestTransportServerWithHost("transportserver", "echo.example.com", "tcp-7777", secretName) + + // no problems are expected for all cases + var expectedProblems []ConfigurationProblem + var expectedChanges []ResourceChange + + // Add TransportServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts, + }, + }, + } + + changes, problems := configuration.AddOrUpdateTransportServer(ts) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Update TransportServer + + updatedTS := ts.DeepCopy() + updatedTS.Generation++ + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Make TransportServer invalid + + invalidTS := updatedTS.DeepCopy() + invalidTS.Generation++ + invalidTS.Spec.Upstreams = nil + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + Error: `spec.action.pass: Not found: "myapp"`, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(invalidTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Restore TransportServer + + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.AddOrUpdateTransportServer(updatedTS) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + + // Delete TransportServer + + expectedChanges = []ResourceChange{ + { + Op: Delete, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: updatedTS, + }, + }, + } + + changes, problems = configuration.DeleteTransportServer("default/transportserver") + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("DeleteTransportServer() returned unexpected result (-want +got):\n%s", diff) + } +} + func TestAddTransportServerForTLSPassthrough(t *testing.T) { configuration := createTestConfiguration() @@ -3283,7 +3413,7 @@ func TestPortCollisions(t *testing.T) { Object: ts2, IsError: false, Reason: "Rejected", - Message: "Listener tcp-7777 is taken by another resource", + Message: "Listener tcp-7777 with host empty host is taken by another resource", }, } @@ -3303,7 +3433,7 @@ func TestPortCollisions(t *testing.T) { Object: ts3, IsError: false, Reason: "Rejected", - Message: "Listener tcp-7777 is taken by another resource", + Message: "Listener tcp-7777 with host empty host is taken by another resource", }, } @@ -3710,6 +3840,14 @@ func createTestTransportServer(name string, listenerName string, listenerProtoco } } +func createTestTransportServerWithHost(name string, host string, listenerName string, secretName string) *conf_v1.TransportServer { + ts := createTestTransportServer(name, listenerName, "TCP") + ts.Spec.Host = host + ts.Spec.TLS = &conf_v1.TransportServerTLS{Secret: secretName} + + return ts +} + func createTestTLSPassthroughTransportServer(name string, host string) *conf_v1.TransportServer { ts := createTestTransportServer(name, conf_v1.TLSPassthroughListenerName, conf_v1.TLSPassthroughListenerProtocol) ts.Spec.Host = host @@ -4068,7 +4206,7 @@ func TestFindResourcesForResourceReference(t *testing.T) { }, expected: []Resource{ configuration.hosts["ts.example.com"], - configuration.listeners["tcp-7777"], + configuration.listenerHosts[listenerHostKey{ListenerName: "tcp-7777", Host: ""}], }, msg: "only TransportServers", }, @@ -4124,7 +4262,7 @@ func TestGetResources(t *testing.T) { expected := []Resource{ configuration.hosts["foo.example.com"], configuration.hosts["abc.example.com"], - configuration.listeners["tcp-7777"], + configuration.listenerHosts[listenerHostKey{ListenerName: "tcp-7777", Host: ""}], configuration.hosts["qwe.example.com"], } @@ -4153,7 +4291,7 @@ func TestGetResources(t *testing.T) { expected = []Resource{ configuration.hosts["abc.example.com"], - configuration.listeners["tcp-7777"], + configuration.listenerHosts[listenerHostKey{ListenerName: "tcp-7777", Host: ""}], } result = configuration.GetResourcesWithFilter(resourceFilter{TransportServers: true}) @@ -4616,3 +4754,141 @@ var ( }, } ) + +func TestTransportServerListenerHostCollisions(t *testing.T) { + configuration := createTestConfiguration() + + listeners := []conf_v1.Listener{ + { + Name: "tcp-7777", + Port: 7777, + Protocol: "TCP", + }, + { + Name: "tcp-8888", + Port: 8888, + Protocol: "TCP", + }, + } + + addOrUpdateGlobalConfiguration(t, configuration, listeners, noChanges, noProblems) + + // Create TransportServers with the same listener and host + ts1 := createTestTransportServerWithHost("ts1", "example.com", "tcp-7777", "secret1") + ts2 := createTestTransportServerWithHost("ts2", "example.com", "tcp-7777", "secret2") // same listener and host + ts3 := createTestTransportServerWithHost("ts3", "example.org", "tcp-7777", "secret3") // different host + ts4 := createTestTransportServer("ts4", "tcp-7777", "TCP") // No host same listener + ts5 := createTestTransportServer("ts5", "tcp-7777", "TCP") // same as ts4 to induce error with empty host twice + ts6 := createTestTransportServerWithHost("ts6", "example.com", "tcp-8888", "secret4") // different listener + + // Add ts1 to the configuration + expectedChanges := []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts1, + }, + }, + } + changes, problems := configuration.AddOrUpdateTransportServer(ts1) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts1) returned unexpected result (-want +got):\n%s", diff) + } + if len(problems) != 0 { + t.Errorf("AddOrUpdateTransportServer(ts1) returned problems %v", problems) + } + + // Try to add ts2, should be rejected due to conflict + changes, problems = configuration.AddOrUpdateTransportServer(ts2) + expectedChanges = nil // No changes expected + expectedProblems := []ConfigurationProblem{ + { + Object: ts2, + IsError: false, + Reason: "Rejected", + Message: "Listener tcp-7777 with host example.com is taken by another resource", + }, + } + + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts2) returned unexpected changes (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts2) returned unexpected problems (-want +got):\n%s", diff) + } + + // Add ts3 with a different host, should be accepted + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts3, + }, + }, + } + changes, problems = configuration.AddOrUpdateTransportServer(ts3) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts3) returned unexpected result (-want +got):\n%s", diff) + } + if len(problems) != 0 { + t.Errorf("AddOrUpdateTransportServer(ts3) returned problems %v", problems) + } + + // Add ts4 with no host, should be accepted + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 7777, + TransportServer: ts4, + }, + }, + } + changes, problems = configuration.AddOrUpdateTransportServer(ts4) + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts4) returned unexpected result (-want +got):\n%s", diff) + } + if len(problems) != 0 { + t.Errorf("AddOrUpdateTransportServer(ts4) returned problems %v", problems) + } + + // Try to add ts5 with no host, should be rejected due to conflict + changes, problems = configuration.AddOrUpdateTransportServer(ts5) + expectedChanges = nil + expectedProblems = []ConfigurationProblem{ + { + Object: ts5, + IsError: false, + Reason: "Rejected", + Message: "Listener tcp-7777 with host empty host is taken by another resource", + }, + } + + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts5) returned unexpected changes (-want +got):\n%s", diff) + } + if diff := cmp.Diff(expectedProblems, problems); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts5) returned unexpected problems (-want +got):\n%s", diff) + } + + // Try to add ts6 with different listener, but same domain as initial ts, should be fine as different listener + changes, problems = configuration.AddOrUpdateTransportServer(ts6) + expectedChanges = []ResourceChange{ + { + Op: AddOrUpdate, + Resource: &TransportServerConfiguration{ + ListenerPort: 8888, + TransportServer: ts6, + }, + }, + } + if diff := cmp.Diff(expectedChanges, changes); diff != "" { + t.Errorf("AddOrUpdateTransportServer(ts6) returned unexpected changes (-want +got):\n%s", diff) + } + + if len(problems) != 0 { + t.Errorf("AddOrUpdateTransportServer(ts6) returned problems %v", problems) + } +} diff --git a/pkg/apis/configuration/validation/transportserver.go b/pkg/apis/configuration/validation/transportserver.go index a4dac4f54c..9812e1a5d9 100644 --- a/pkg/apis/configuration/validation/transportserver.go +++ b/pkg/apis/configuration/validation/transportserver.go @@ -38,7 +38,7 @@ func (tsv *TransportServerValidator) validateTransportServerSpec(spec *conf_v1.T allErrs := tsv.validateTransportListener(&spec.Listener, fieldPath.Child("listener")) isTLSPassthroughListener := isPotentialTLSPassthroughListener(&spec.Listener) - allErrs = append(allErrs, validateTransportServerHost(spec.Host, fieldPath.Child("host"), isTLSPassthroughListener)...) + allErrs = append(allErrs, validateTransportServerHost(spec.Host, fieldPath.Child("host"), isTLSPassthroughListener, spec.Listener.Protocol, spec.TLS)...) upstreamErrs, upstreamNames := validateTransportServerUpstreams(spec.Upstreams, fieldPath.Child("upstreams"), tsv.isPlus) allErrs = append(allErrs, upstreamErrs...) @@ -57,22 +57,37 @@ func (tsv *TransportServerValidator) validateTransportServerSpec(spec *conf_v1.T allErrs = append(allErrs, validateSnippets(spec.StreamSnippets, fieldPath.Child("streamSnippets"), tsv.snippetsEnabled)...) - allErrs = append(allErrs, validateTLS(spec.TLS, isTLSPassthroughListener, fieldPath.Child("tls"))...) + hostSpecified := spec.Host != "" + allErrs = append(allErrs, validateTLS(spec.TLS, isTLSPassthroughListener, fieldPath.Child("tls"), hostSpecified)...) return allErrs } -func validateTLS(tls *conf_v1.TransportServerTLS, isTLSPassthrough bool, fieldPath *field.Path) field.ErrorList { - if tls == nil { +func validateTLS( + tls *conf_v1.TransportServerTLS, + isTLSPassthroughListener bool, + fieldPath *field.Path, + hostSpecified bool, +) field.ErrorList { + if isTLSPassthroughListener { + if tls != nil { + return field.ErrorList{field.Forbidden(fieldPath, "cannot specify tls for TLS Passthrough TransportServers")} + } return nil } - if isTLSPassthrough { - return field.ErrorList{field.Forbidden(fieldPath, "cannot specify secret for tls passthrough")} + + if hostSpecified { + if tls == nil || tls.Secret == "" { + return field.ErrorList{field.Required(fieldPath, "must specify spec.tls.secret when host is specified, and the TransportServer is not using the TLS Passthrough listener")} + } + return validateSecretName(tls.Secret, fieldPath.Child("secret")) } - if tls.Secret == "" { - return field.ErrorList{field.Required(fieldPath, "must specify secret for tls")} + + if tls != nil && tls.Secret != "" { + return validateSecretName(tls.Secret, fieldPath.Child("secret")) } - return validateSecretName(tls.Secret, fieldPath.Child("secret")) + + return nil } func validateSnippets(serverSnippet string, fieldPath *field.Path, snippetsEnabled bool) field.ErrorList { @@ -82,14 +97,32 @@ func validateSnippets(serverSnippet string, fieldPath *field.Path, snippetsEnabl return nil } -func validateTransportServerHost(host string, fieldPath *field.Path, isTLSPassthroughListener bool) field.ErrorList { - if !isTLSPassthroughListener { +func validateTransportServerHost(host string, fieldPath *field.Path, isTLSPassthroughListener bool, protocol string, tls *conf_v1.TransportServerTLS) field.ErrorList { + if protocol == "UDP" { if host != "" { - return field.ErrorList{field.Forbidden(fieldPath, "host field is allowed only for TLS Passthrough TransportServers")} + return field.ErrorList{field.Forbidden(fieldPath, "host field is not allowed for UDP TransportServers")} } return nil } - return validateHost(host, fieldPath) + + if host != "" { + if !isTLSPassthroughListener { + if tls == nil || tls.Secret == "" { + return field.ErrorList{field.Required(fieldPath, "must specify spec.tls.secret when host is specified for non-TLS Passthrough TransportServers")} + } + return validateHost(host, fieldPath) + } + if tls != nil && tls.Secret != "" { + return field.ErrorList{field.Required(fieldPath, "must not specify spec.tls.secret when host using TLS Passthrough")} + } + return validateHost(host, fieldPath) + } + + if isTLSPassthroughListener { + return field.ErrorList{field.Required(fieldPath, "must specify host for TLS Passthrough TransportServers")} + } + + return nil } func (tsv *TransportServerValidator) validateTransportListener(listener *conf_v1.TransportServerListener, fieldPath *field.Path) field.ErrorList { diff --git a/pkg/apis/configuration/validation/transportserver_test.go b/pkg/apis/configuration/validation/transportserver_test.go index a9613748b8..9e7b163c36 100644 --- a/pkg/apis/configuration/validation/transportserver_test.go +++ b/pkg/apis/configuration/validation/transportserver_test.go @@ -279,26 +279,93 @@ func TestValidateTransportServerUpstreams_FailsOnInvalidInput(t *testing.T) { func TestValidateTransportServerHost(t *testing.T) { t.Parallel() - tests := []struct { + validCases := []struct { host string isTLSPassthroughListener bool + protocol string + tls *conf_v1.TransportServerTLS }{ { host: "", isTLSPassthroughListener: false, + protocol: "TCP", + tls: &conf_v1.TransportServerTLS{ + Secret: "secret-name", + }, }, + { host: "nginx.org", - isTLSPassthroughListener: true, + isTLSPassthroughListener: false, + protocol: "TCP", + tls: &conf_v1.TransportServerTLS{ + Secret: "secret-name", + }, }, } - for _, test := range tests { - allErrs := validateTransportServerHost(test.host, field.NewPath("host"), test.isTLSPassthroughListener) + for _, test := range validCases { + allErrs := validateTransportServerHost(test.host, field.NewPath("host"), test.isTLSPassthroughListener, test.protocol, test.tls) if len(allErrs) > 0 { t.Errorf("validateTransportServerHost(%q, %v) returned errors %v for valid input", test.host, test.isTLSPassthroughListener, allErrs) } } + + invalidCases := []struct { + host string + isTLSPassthroughListener bool + protocol string + tls *conf_v1.TransportServerTLS + }{ + // Invalid case: host is empty but isTLSPassthroughListener is true + { + host: "", + isTLSPassthroughListener: true, + protocol: "TLS_PASSTHROUGH", + tls: &conf_v1.TransportServerTLS{ + Secret: "secret-name", + }, + }, + { + host: "nginx.org", + isTLSPassthroughListener: false, + protocol: "UDP", + tls: &conf_v1.TransportServerTLS{ + Secret: "secret-name", + }, + }, + { + host: "invalid host name", + isTLSPassthroughListener: true, + protocol: "TCP", + tls: &conf_v1.TransportServerTLS{ + Secret: "secret-name", + }, + }, + { + host: "nginx.org", + isTLSPassthroughListener: true, + protocol: "UDP", + tls: &conf_v1.TransportServerTLS{ + Secret: "secret-name", + }, + }, + { + host: "nginx.org", + isTLSPassthroughListener: true, + protocol: "TLS_PASSTHROUGH", + tls: &conf_v1.TransportServerTLS{ + Secret: "secret-name", + }, + }, + } + + for _, test := range invalidCases { + allErrs := validateTransportServerHost(test.host, field.NewPath("host"), test.isTLSPassthroughListener, test.protocol, test.tls) + if len(allErrs) == 0 { + t.Errorf("validateTransportServerHost(%q, %v) returned no errors for invalid input", test.host, test.isTLSPassthroughListener) + } + } } func TestValidateTransportServerLoadBalancingMethod(t *testing.T) { @@ -460,19 +527,35 @@ func TestValidateTransportServerHost_FailsOnInvalidInput(t *testing.T) { tests := []struct { host string isTLSPassthroughListener bool + protocol string + tls *conf_v1.TransportServerTLS }{ { host: "nginx.org", isTLSPassthroughListener: false, + protocol: "TCP", + tls: nil, // TLS and TLS.secret are required when not using tls passthrough and using a host + }, + { + host: "nginx.org", + isTLSPassthroughListener: false, + protocol: "TCP", + tls: &conf_v1.TransportServerTLS{ + Secret: "", // TLS and TLS.secret are required when not using tls passthrough and using a host + }, }, { host: "", isTLSPassthroughListener: true, + protocol: "TCP", + tls: &conf_v1.TransportServerTLS{ + Secret: "secret-name", + }, }, } for _, test := range tests { - allErrs := validateTransportServerHost(test.host, field.NewPath("host"), test.isTLSPassthroughListener) + allErrs := validateTransportServerHost(test.host, field.NewPath("host"), test.isTLSPassthroughListener, test.protocol, test.tls) if len(allErrs) == 0 { t.Errorf("validateTransportServerHost(%q, %v) returned no errors for invalid input", test.host, test.isTLSPassthroughListener) } @@ -1041,17 +1124,39 @@ func TestValidateMatchExpect_FailsOnInvalidInput(t *testing.T) { func TestValidateTsTLS(t *testing.T) { t.Parallel() - validTLSes := []*conf_v1.TransportServerTLS{ - nil, + + type testCase struct { + tls *conf_v1.TransportServerTLS + isTLSPassthrough bool + hostSpecified bool + } + + validTestCases := []testCase{ + { + tls: nil, + isTLSPassthrough: false, + hostSpecified: false, + }, + { + tls: &conf_v1.TransportServerTLS{ + Secret: "my-secret", + }, + isTLSPassthrough: false, + hostSpecified: true, + }, { - Secret: "my-secret", + tls: &conf_v1.TransportServerTLS{ + Secret: "my-secret", + }, + isTLSPassthrough: false, + hostSpecified: false, }, } - for _, tls := range validTLSes { - allErrs := validateTLS(tls, false, field.NewPath("tls")) + for _, tc := range validTestCases { + allErrs := validateTLS(tc.tls, tc.isTLSPassthrough, field.NewPath("tls"), tc.hostSpecified) if len(allErrs) > 0 { - t.Errorf("validateTLS() returned errors %v for valid input %v", allErrs, tls) + t.Errorf("validateTLS() returned errors %v for valid input %+v", allErrs, tc) } } } @@ -1061,35 +1166,40 @@ func TestValidateTsTLS_FailsOnInvalidInput(t *testing.T) { invalidTLSes := []struct { tls *conf_v1.TransportServerTLS isTLSPassthrough bool + hostSpecified bool }{ { tls: &conf_v1.TransportServerTLS{ Secret: "-", }, isTLSPassthrough: false, + hostSpecified: true, }, { tls: &conf_v1.TransportServerTLS{ Secret: "a/b", }, isTLSPassthrough: false, + hostSpecified: true, }, { tls: &conf_v1.TransportServerTLS{ Secret: "my-secret", }, isTLSPassthrough: true, + hostSpecified: true, }, { tls: &conf_v1.TransportServerTLS{ Secret: "", }, isTLSPassthrough: false, + hostSpecified: true, }, } for _, test := range invalidTLSes { - allErrs := validateTLS(test.tls, test.isTLSPassthrough, field.NewPath("tls")) + allErrs := validateTLS(test.tls, test.isTLSPassthrough, field.NewPath("tls"), test.hostSpecified) if len(allErrs) == 0 { t.Errorf("validateTLS() returned no errors for invalid input %v", test) } diff --git a/site/content/configuration/host-and-listener-collisions.md b/site/content/configuration/host-and-listener-collisions.md index 53485d3062..33ceb5deba 100644 --- a/site/content/configuration/host-and-listener-collisions.md +++ b/site/content/configuration/host-and-listener-collisions.md @@ -106,11 +106,13 @@ It is *not* possible to merge configuration for multiple TransportServer resourc --- -## Listener collisions +## Listener/Host collisions -Listener collisions occur when multiple TransportServer resources (Configured for TCP/UDP load balancing) target the same `listener`. +Listener/Host collisions occur when multiple TransportServer resources (configured for TCP/UDP load balancing) specify the same combination of `spec.listener.name` and `spec.host`. -NGINX Ingress Controller will choose the winner, which will own the listener. +The combination of `spec.listener.name` and `spec.host` must be unique among all TransportServer resources. If two TransportServer resources specify the same spec.listener.name and spec.host, one of them will be rejected to prevent conflicts. In the case where spec.host is not specified, it is considered an empty string. + +NGINX Ingress Controller will choose the winner, which will own that listener and host combination. --- @@ -126,6 +128,7 @@ Consider the following two resources: metadata: name: tcp-1 spec: + host: dns.example.com listener: name: dns-tcp protocol: TCP @@ -140,6 +143,7 @@ Consider the following two resources: metadata: name: tcp-2 spec: + host: dns.example.com listener: name: dns-tcp protocol: TCP diff --git a/site/content/configuration/transportserver-resource.md b/site/content/configuration/transportserver-resource.md index f39648a482..3be578fca1 100644 --- a/site/content/configuration/transportserver-resource.md +++ b/site/content/configuration/transportserver-resource.md @@ -28,6 +28,7 @@ The TransportServer resource defines load balancing configuration for TCP, UDP, metadata: name: dns-tcp spec: + host: dns.example.com listener: name: dns-tcp protocol: TCP @@ -87,7 +88,7 @@ The TransportServer resource defines load balancing configuration for TCP, UDP, |Field | Description | Type | Required | | ---| ---| ---| --- | |``listener`` | The listener on NGINX that will accept incoming connections/datagrams. | [listener](#listener) | Yes | -|``host`` | The host (domain name) of the server. Must be a valid subdomain as defined in RFC 1123, such as ``my-app`` or ``hello.example.com``. Wildcard domains like ``*.example.com`` are not allowed. Required for TLS Passthrough load balancing. | ``string`` | No | +|``host`` | The host (domain name) of the server. Must be a valid subdomain as defined in RFC 1123, such as ``my-app`` or ``hello.example.com``. Wildcard domains like ``*.example.com`` are not allowed. When specified, NGINX will use this host for SNI-based routing. For TLS Passthrough, this field is required. For TCP with TLS termination, specifying the host enables SNI routing and requires specifying a TLS secret.| ``string`` | No | |``tls`` | The TLS termination configuration. Not supported for TLS Passthrough load balancing. | [tls](#tls) | No | |``upstreams`` | A list of upstreams. | [[]upstream](#upstream) | Yes | |``upstreamParameters`` | The upstream parameters. | [upstreamParameters](#upstreamparameters) | No | @@ -103,6 +104,8 @@ The TransportServer resource defines load balancing configuration for TCP, UDP, The listener field references a listener that NGINX will use to accept incoming traffic for the TransportServer. For TCP and UDP, the listener must be defined in the [GlobalConfiguration resource]({{< relref "configuration/global-configuration/globalconfiguration-resource.md" >}}). When referencing a listener, both the name and the protocol must match. For TLS Passthrough, use the built-in listener with the name `tls-passthrough` and the protocol `TLS_PASSTHROUGH`. +The combination of ``spec.listener.name`` and ``spec.host`` must be unique among all TransportServers. If two TransportServers specify the same combination of ``spec.listener.name`` and ``spec.host``, one of them will be rejected to prevent conflicts. In the case where no host is specified, it is considered an empty string. + An example: ```yaml @@ -120,7 +123,7 @@ listener: ### TLS -The tls field defines TLS configuration for a TransportServer. Please note the current implementation supports TLS termination on multiple ports, where each application owns a dedicated port - NGINX Ingress Controller terminates TLS connections on each port, where each application uses its own cert/key, and routes connections to appropriate application (service) based on that incoming port (any TLS connection regardless of the SNI on a port will be routed to the application that corresponds to that port). An example configuration is shown below: +The tls field defines TLS configuration for a TransportServer. When using TLS termination (not TLS Passthrough), you can specify the host field to enable SNI-based routing, allowing multiple applications to share the same listener port and be distinguished by the TLS SNI hostname. Each application can use its own TLS certificate and key specified via the secret field. ```yaml secret: cafe-secret diff --git a/tests/data/transport-server-with-host/cafe-secret.yaml b/tests/data/transport-server-with-host/cafe-secret.yaml new file mode 100644 index 0000000000..4129b88e76 --- /dev/null +++ b/tests/data/transport-server-with-host/cafe-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUY1ekNDQTgrZ0F3SUJBZ0lVR2dXT0JNTkR2TXNWOGY3OWc0MlpSZy9KaWc0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZSXhDekFKQmdOVkJBWVRBbGhZTVJJd0VBWURWUVFJREFsVGRHRjBaVTVoYldVeEVUQVBCZ05WQkFjTQpDRU5wZEhsT1lXMWxNUlF3RWdZRFZRUUtEQXREYjIxd1lXNTVUbUZ0WlRFYk1Ca0dBMVVFQ3d3U1EyOXRjR0Z1CmVWTmxZM1JwYjI1T1lXMWxNUmt3RndZRFZRUUREQkJqWVdabExtVjRZVzF3YkdVdVkyOXRNQjRYRFRJME1Ea3kKTXpFd01EUXdNVm9YRFRNME1Ea3lNVEV3TURRd01Wb3dnWUl4Q3pBSkJnTlZCQVlUQWxoWU1SSXdFQVlEVlFRSQpEQWxUZEdGMFpVNWhiV1V4RVRBUEJnTlZCQWNNQ0VOcGRIbE9ZVzFsTVJRd0VnWURWUVFLREF0RGIyMXdZVzU1ClRtRnRaVEViTUJrR0ExVUVDd3dTUTI5dGNHRnVlVk5sWTNScGIyNU9ZVzFsTVJrd0Z3WURWUVFEREJCallXWmwKTG1WNFlXMXdiR1V1WTI5dE1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBbjkvZwpkMml5NEZyaGNtS3ZXQTVHZXFiS1c5WU1xeml6Vm4yRExkd215enkvS2FUdTdtUlkvUWRsTkxHaVhIM1NEa0FkCkhtZFJEK0Zob1ZHaWxoTjEzcTI0azdyRSt6dk1iMlRqdnBnMmVLYVd1dDBHaGQ3V3l3TnpIUU9WcWJCM2o2U2QKSE1sNlJKK25ERHo2Yi9adkRiNm9nQjBucUowZjBWN0tBMHFFalYxVld2dXI4YVZpVlVxK0tBOVMveEdJMHZSSwo2NE9BS2xIUUF4a2xGWWhheHVjcWRsS0owQlRXekE0Z0gzUTZlQi9CRjNOMTJDQjgrMVEzZG54bU5TVnpLbXp1CmJYdm1jK1RPUjg0V3ovemlaMTl1K3RuRHg2aklNajZZQXd2M0tzSVlYbmV1ZEgycUZKRG5FMmZwYjNnZzRxbm0KQnduY0ZlU1poY09nZWdnR1RmKzRTc3YwRUNuamNNdXhteTEwMTNwY0h1a2prTUNmVGJsUGZTU0NNS24rVnlGZQpUNzlSeUkxM3hmL0JXOSt0aTFad1IwYVpkWk5jV1R2Um53R1M1bWhnelJXclhRUmdJQlVVVjk2N3cxRVVONDJVCkE3ZjBVSzZlYks5bExBMnNZWmJJSWUrbllRY1d1OVRuNXo5YnVwK2w3aU5hUDYxWXdwWGdrUTFyNHlSamhMeUEKajVha1Z5VnROYzZmSWltaXlWV0Y1QzBaZnNIYzF3cUpLb2lXQzBtT1dyNEVRSVorRkY2WC9FRytSMmhodjZkQQpGcndpd3BHUE9kWkROQzNqNFBqVzVreWlYMlh2Vm1RWTUvbkxNdFpUaFF6VHpadG8zT3M4d1RsRjFyQlZkd05iCi9zNTh2dnlTQitsYk5Sd215TEErQVYvOGQ2L2VNcGZjS0ZyYU85OENBd0VBQWFOVE1GRXdIUVlEVlIwT0JCWUUKRktEejZlREZLaC93ZDVLTHpPMXpCMDR2UGVRSU1COEdBMVVkSXdRWU1CYUFGS0R6NmVERktoL3dkNUtMek8xegpCMDR2UGVRSU1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFKQnd5WVlyCllXa3RvRGJPYlpHRUFXT0lEcjFTb3NWRDVIRXdkazV6ZTJuT05oQXFHVWszOHpUT3V1VVZRNk5lT1c1UWtML3oKVTFUa3dwUlNFNjFpbVZHOUdjYWY5ampkSjd5VGhsOGpmZXUyWTF4RHo1OUpmbWJ1WUo2SGE4ZmR4STAxQXZ3RgoxaERYY1ZEcTZoalhhb0pKMVZta1NiTEJPc1JXaGVzWGl1K3FiN3NSOUhlZmJmaTUxandQb2NFblE4YzNiWXF5CmpIRkwxS1JJekIyR2VYVGk2WngwN2lqcDZIeGJSQUFEWnBuRGN0blM1UVQvTGFoYklIZVNNTExMK21vdnUrSUgKTVgwQmtaYmJjRk94L3d2ZlN1SXdveEVXR1RjVGJIYnJGanVUUDRkalpPdFhybElSK1RJTmRiaFhaMm1XZVdHbgpRa0tkcyt4a3puNzNZbG9xZCtPZGZ2d1dJYXVFYktuaXdQUk01NHZqK0dnbndiWmZNWXpnY292cE5BYzZjMmlmCkNNdHlHWDBUREptSjRVWC9FaFFSYTArSURMbFZTMWc2Y3IxWmVQM1N1MWpkSnhBSFVwUjZwSVYrWGdsTENDdGYKQXdDUW9BUWtUeXkyb0Y1Z0hWOGVuc28zVE15cmI5R1NBWDF0UEdJL080L3VCRDBqRzQ4anZsZEI3dzZKbjdmSwoyS21DWnIwYlRDdU9vWnVuZHA3OHA2R1ozb3lVOXBxcTFTUmU0MTdjSlVuNzR3S05TTkd0U0xTNm9OZ3FQQ2h6Cm9ZTy9zK0NHL295c0JUcTBma2VBYXdMZ2oxVG9ybGNsaEt6M05uWUtLZmhDVFBLTDVVUFZLNjVXQ3V2djlSVWgKVUZvM2F2TnhhSmpWUEY4V0FVUnRZem82bXBadFRITm53ZkJTCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQ2YzK0IzYUxMZ1d1RnkKWXE5WURrWjZwc3BiMWd5ck9MTldmWU10M0NiTFBMOHBwTzd1WkZqOUIyVTBzYUpjZmRJT1FCMGVaMUVQNFdHaApVYUtXRTNYZXJiaVR1c1Q3Tzh4dlpPTyttRFo0cHBhNjNRYUYzdGJMQTNNZEE1V3BzSGVQcEowY3lYcEVuNmNNClBQcHY5bThOdnFpQUhTZW9uUi9SWHNvRFNvU05YVlZhKzZ2eHBXSlZTcjRvRDFML0VZalM5RXJyZzRBcVVkQUQKR1NVVmlGckc1eXAyVW9uUUZOYk1EaUFmZERwNEg4RVhjM1hZSUh6N1ZEZDJmR1kxSlhNcWJPNXRlK1p6NU01SAp6aGJQL09KblgyNzYyY1BIcU1neVBwZ0RDL2Nxd2hoZWQ2NTBmYW9Va09jVForbHZlQ0RpcWVZSENkd1Y1Sm1GCnc2QjZDQVpOLzdoS3kvUVFLZU53eTdHYkxYVFhlbHdlNlNPUXdKOU51VTk5SklJd3FmNVhJVjVQdjFISWpYZkYKLzhGYjM2MkxWbkJIUnBsMWsxeFpPOUdmQVpMbWFHRE5GYXRkQkdBZ0ZSUlgzcnZEVVJRM2paUUR0L1JRcnA1cwpyMlVzRGF4aGxzZ2g3NmRoQnhhNzFPZm5QMXU2bjZYdUkxby9yVmpDbGVDUkRXdmpKR09FdklDUGxxUlhKVzAxCnpwOGlLYUxKVllYa0xSbCt3ZHpYQ29rcWlKWUxTWTVhdmdSQWhuNFVYcGY4UWI1SGFHRy9wMEFXdkNMQ2tZODUKMWtNMExlUGcrTmJtVEtKZlplOVdaQmpuK2NzeTFsT0ZETlBObTJqYzZ6ekJPVVhXc0ZWM0Exdit6bnkrL0pJSAo2VnMxSENiSXNENEJYL3gzcjk0eWw5d29XdG83M3dJREFRQUJBb0lDQUNkQmRZQmNlTytWNFIyUkZiVHRiR2paClkzN0JSRU1XblJKenB5NHZqR2NDOTMxbVBqVFM5dmJLUmhOMk9vT3pjVXlHZVovcGhvSDd1VmsvRGtrRFprSFQKTGlzNEJQNGJaTXRGWHBhQ0VYMzJpYlJBYVVXZHZlZ0RaTlNPK01TOXk5MjljY2FMd2pYdmJia1hqL2JGNytiVQpGZE8vVk9tV0N5WUJ2R0NxZjNtbW5UckY2U1pna1pDWDFiRkljZnluZFkwMjV0NkZYNGNFcDZyYkZidi95eXBqCndJMWxIdW0wOURrT2p0eXFVV0VGaXdnVEZiQ0g2YWhjdVhHaWdnWXl0K0NHOXRSelE5YlpLNzE5NFNRWTJBN0IKNUNJOExsSnNJeHdUT29naysvL0h3T3dSUHdqamdrdWllTnJPL1FhZDNKVkxXbXdJQTc1c2J6WGxIeFpYdWhReApFZTlTTVNaRE5JMGMvcGdDVXloL2h5Y3ZYWVJKb1dIMnZYTDYzL1EyRWJVVVZVcXpzcEpjV0xMbkhSSFdBQjZQCmtPb0FMeEs2M3lpQTU5KzREQ0ZZMGM0dXd4WHM5YnZlVWk5UUdxaUtoZkFaNmE3Ymh4ajNvRWIvRDZ0dWZKb1QKMnIxUXFKTlJ4N0RzMmJUc3J6SEgyYmVKR0R4bFJ4djdaNmdSTzdHd2lrU25veDlMcnY3YzMwUHp0WjZENXhwQwpoVHdmM0VpRWlrZ2pOM1FGd2RaUXVqV3RxWXQ1UWVGYUxpL2Q3VXZteFdZNWcrWTYyQnFkNnBpWFFJSlFVbDZUCmZmU0U2OEYvZWF6QnhPQm9INUMvWWcrd1kyVEpTb1BzK3NFUUtnYlNJTmRQNHU4N3FiQ056YWZ4WjFySTN1dmgKR3NZTWpSbm50c3RKamJXVEJkakJBb0lCQVFEZXpsY3dVNFJYbnhKVENSTjFQZUx1UHI2aXFnc09uY1lRRXNuTgpZemdwOXF6eC9SSTJodGZCYnN6TkM2RUZ3eDBDZy8wRnpRbVhqWEQ3VmhQS0UrOHBXMmQzNTg4YTBwU0xSbFBOCkprUnFxR2ovbzhIaEtmTGdCYy9BSGU3dkJEZG1UTXh1d0o1SmFmMWZ3OGtxNEJyRXROTi83SVF3MkNYcFNIK2MKSEhSZnBTM1JidDArbW9xK2RTRW1pU2h3UGgxL2FJbng4ZnBKbTFmaU1QdXhvYStzOXNUVmQ3Ky9GSjdIaWNZSApSQThQRHdQT2RMQ2loQlZFT3liSkduTlpaSFhYQTA4MThhNnpjdjU2aUM2VGZzSkhWZUg4a2Nma1IzblI4eTUvCkx2MnI1QzVaZWJlbW9ZaVhBTC9sVnZ0QUNGMXE3UW1zdlRkQ2R3R1lvaXBMbksvNUFvSUJBUUMzc1YvcFFOVlEKSjNsZExFem9OVUdBZWFwL0NNU2tuY0lUUXNnMUI3SDJHclIyR201VGNuVGE0SE5Rbmt3a2h3YUxyTUlmVDFXZAorMFVvR3M5ZXdyYkZERWtKR0MxQVd2RmFIREk3Q3NOYUFUM2FWYTE5R1ZRWFg2NnkrWEF2QXFTOXpOU25id0J0CnM5UDFFVURwMko1SGtyNlVLUHZjSnY3Q0MyR3J5emZaTy85MFpTTlFQUXdKMXFqZ2NNOUJpMlBYSkE0OENldnMKK3RvaGFqUWFIMStVY0VqNkQ0SHhoQ1RtcTBxM2YvdFNudk9iaTNOYklEWjNEWmR4ejVPelJSVmpLNWlSOGZDNQpzSVVKckxCSXRZeWpUeHAzTzROUmRFaHhzamdBZTZrSTV2dm95RVAvRFZwTFlQU0s3cFpoVjJ4YUpXUkZUUVZzCjhrKzZDbGduWGZDWEFvSUJBUUM5QjY4dFR3NHZFTVNKTW1BUnprbWovQlBkQ2d1TGdRd3pRdDEzcGNCV3lmUDgKOHNycS9BZzlFbllyV0x4cW1Sa1pzMFdPRUdFYzlXRnZ1NTNhaW9NVVFYcE5YcHgxazBkM3lsajY2b2FOUHdpbQpLeGNvbzJCdDlFQklMSjAwcUEwZ2UvUE4yeG53Q3o1dWF6dFhadjhPK0tPZ0d0Z2tZSjM1aUFyTU5jLzkvYlFiCnhjVnJnYzVJdkRNOThJd2dmbktrVDlzSkxGVSs4YzdrRnM3VDYrdVNBV01LQVNqclF1RmJSV1ovYjV5ZkdBd1EKc3l2UkZlSzlHcnBUVUYrZzdmeVVTVGlBK2VWUVZqWFZXNGk0bG9qWjRPRjBXWEtRR0p3Z0pnUEMzK2xVVnFtRQpQQ0kxKzBKWmFzZGtHaUhjTjd5YUpUVmFHc2F4V3lvOWh3Zi9VcFp4QW9JQkFDQ0hDem5ObmpoTVZTUlhsT0xGCmsyekJucHhTSENnZU8yQ1h3Y1lLTDh3cG5HMFJieG5kdWEyTWN6OENXTzlhN2FETUhhL1hwNHlMRXdydi9HcUcKUmtFTVZONkVabmJ2NDY4V01ScmRaQXhMRGYzY2tCVUg2Q2tmYTFzTDZuNllsRDE3eU9oQk1xMDZXNzBZcWdyKwpyY0IwenNTRG9WMnhsZ2tjWk5ZNzdRN05uZ1dwWnlCdFB2VjdDbnA3MzJkMjNGNGJaMTNnVCtPdDQvUm96d01WCkxTS200M1ZNUzdGTnVnOFNvKzlzZlQ5N0lCNGFDbnBIY1AyUjdaQmN0b1hYSk50anUrZVVGUkY4bllKQ0R4RkEKL0w5cVlZQmRqSHBmQWZrSUd2eVM2VExIWERJelREOGN5VEZ4NEx1OVZlbTB4bDRNSXY1V2pqQmxsQktZaEZXcwpQODhDZ2dFQkFKUjR5UUIrREtoRE1ZOGlHOXpYWUJpWmZ5MFI2c21oeEJycTREZWd4cElkajBydHUyMFNuWDdsCnN3YlZsN2NmdWRxUi9tVEhnZ1lYeGJSS3UxUXhmN3NHTW4zWVpRY1AxZDVJelFMOWZFQitidGJsdWY5VTB4NDIKUzM0U0l6Z25pd0hPQnN3M2ZLYm1Gbnc1dmtNd2RoemlBT1R0elRwcmU3ckdhaEpwZlk0eFRWWjZpK3pjQ1pCWgo0ZHhSTjlQWnBiQm4vNmJDTFY3S0I3ZmY0Uk04b3c3K1l4aHFFTFhxcnVQS2paMnRRSWs2M2hzZklMd0tiRWIrCkhEM2VLZEhNc2hhbXFXZzI0NnlORW5nQUZCQzhoVU5kQ0pmeUthUlhkSlV5eDhqcWlKM3ErdnpnNFBieUhqeW0KZEQzM2pVT2UwdHdoSWJxKytUaXZCa1ptSDBsSStnVT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= +kind: Secret +metadata: + creationTimestamp: null + name: cafe-secret +type: kubernetes.io/tls diff --git a/tests/data/transport-server-with-host/transport-server-with-host.yaml b/tests/data/transport-server-with-host/transport-server-with-host.yaml new file mode 100644 index 0000000000..2729dce546 --- /dev/null +++ b/tests/data/transport-server-with-host/transport-server-with-host.yaml @@ -0,0 +1,17 @@ +apiVersion: k8s.nginx.org/v1 +kind: TransportServer +metadata: + name: transport-server +spec: + host: cafe.example.com + listener: + name: dns-tcp + protocol: TCP + tls: + secret: cafe-secret + upstreams: + - name: dns-app + service: coredns + port: 5353 + action: + pass: dns-app diff --git a/tests/suite/test_transport_server_tcp_load_balance.py b/tests/suite/test_transport_server_tcp_load_balance.py index b0c007c74d..675df61cbc 100644 --- a/tests/suite/test_transport_server_tcp_load_balance.py +++ b/tests/suite/test_transport_server_tcp_load_balance.py @@ -186,7 +186,7 @@ def test_tcp_request_load_balanced_multiple(self, kube_apis, crd_ingress_control response["status"] and response["status"]["reason"] == "Rejected" and response["status"]["state"] == "Warning" - and response["status"]["message"] == "Listener tcp-server is taken by another resource" + and response["status"]["message"] == "Listener tcp-server with host empty host is taken by another resource" ) # Step 3, remove the default TransportServer with the same port diff --git a/tests/suite/test_transport_server_udp_load_balance.py b/tests/suite/test_transport_server_udp_load_balance.py index 841fc43bf9..86fa8596ec 100644 --- a/tests/suite/test_transport_server_udp_load_balance.py +++ b/tests/suite/test_transport_server_udp_load_balance.py @@ -204,7 +204,7 @@ def test_udp_request_load_balanced_multiple(self, kube_apis, crd_ingress_control response["status"] and response["status"]["reason"] == "Rejected" and response["status"]["state"] == "Warning" - and response["status"]["message"] == "Listener udp-server is taken by another resource" + and response["status"]["message"] == "Listener udp-server with host empty host is taken by another resource" ) # Step 3, remove the default TransportServer with the same port diff --git a/tests/suite/test_transport_server_with_host.py b/tests/suite/test_transport_server_with_host.py new file mode 100644 index 0000000000..ca65df5556 --- /dev/null +++ b/tests/suite/test_transport_server_with_host.py @@ -0,0 +1,80 @@ +import pytest +from settings import TEST_DATA +from suite.utils.custom_resources_utils import patch_ts_from_yaml, read_custom_resource +from suite.utils.resources_utils import ( + create_secret_from_yaml, + get_events_for_object, + get_ts_nginx_template_conf, + wait_before_test, +) + + +@pytest.mark.ts +@pytest.mark.parametrize( + "crd_ingress_controller, transport_server_setup", + [ + ( + { + "type": "complete", + "extra_args": [ + "-global-configuration=nginx-ingress/nginx-configuration", + "-enable-leader-election=false", + "-enable-snippets", + ], + }, + {"example": "transport-server-status"}, + ) + ], + indirect=True, +) +class TestTransportServerWithHost: + def test_ts_with_host( + self, kube_apis, crd_ingress_controller, transport_server_setup, ingress_controller_prerequisites + ): + """ + Test TransportServer with Host field without TLS Passthrough + """ + + # TS with Host needs a secret + secret_src = f"{TEST_DATA}/transport-server-with-host/cafe-secret.yaml" + create_secret_from_yaml(kube_apis.v1, transport_server_setup.namespace, secret_src) + + # Update the status TS from the example with one which uses a Host + patch_src = f"{TEST_DATA}/transport-server-with-host/transport-server-with-host.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + patch_src, + transport_server_setup.namespace, + ) + wait_before_test() + + conf = get_ts_nginx_template_conf( + kube_apis.v1, + transport_server_setup.namespace, + transport_server_setup.name, + transport_server_setup.ingress_pod_name, + ingress_controller_prerequisites.namespace, + ) + print(conf) + + std_src = f"{TEST_DATA}/transport-server-status/standard/transport-server.yaml" + patch_ts_from_yaml( + kube_apis.custom_objects, + transport_server_setup.name, + std_src, + transport_server_setup.namespace, + ) + + conf_lines = [line.strip() for line in conf.split("\n")] + assert 'server_name "cafe.example.com";' in conf_lines + + ts_events = get_events_for_object(kube_apis.v1, transport_server_setup.namespace, transport_server_setup.name) + ts_latest_event = ts_events[-1] + print(ts_latest_event) + assert ts_latest_event.reason == "AddedOrUpdated" and ts_latest_event.type == "Normal" + + ts_info = read_custom_resource( + kube_apis.custom_objects, transport_server_setup.namespace, "transportservers", transport_server_setup.name + ) + assert ts_info["status"] and ts_info["status"]["state"] == "Valid"