From 5e1669dcba17871f954e74e9e89785423434d761 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Thu, 31 Aug 2023 09:23:04 -0700 Subject: [PATCH 01/14] Add documentation for TLS configuration of NLK- better titles - tweaks - Add Under Development notice - prefer References section over inline links to external documentation - address PR feedback - correct use of [!WARNING] --- docs/tls/CA-MTLS.md | 87 +++++++++++++++++++ docs/tls/CA-TLS.md | 77 +++++++++++++++++ docs/tls/CERTIFICATE-AUTHORITY.md | 44 ++++++++++ docs/tls/CLIENT-CERTIFICATE.md | 50 +++++++++++ docs/tls/DOCUMENT-HIERARCHY.md | 51 ++++++++++++ docs/tls/KUBERNETES-SECRETS.md | 82 ++++++++++++++++++ docs/tls/NGINX-PLUS-CONFIGURATION.md | 82 ++++++++++++++++++ docs/tls/NO-TLS.md | 57 +++++++++++++ docs/tls/README.md | 120 +++++++++++++++++++++++++++ docs/tls/SERVER-CERTIFICATE.md | 70 ++++++++++++++++ docs/tls/SS-MTLS.md | 91 ++++++++++++++++++++ docs/tls/SS-TLS.md | 83 ++++++++++++++++++ 12 files changed, 894 insertions(+) create mode 100644 docs/tls/CA-MTLS.md create mode 100644 docs/tls/CA-TLS.md create mode 100644 docs/tls/CERTIFICATE-AUTHORITY.md create mode 100644 docs/tls/CLIENT-CERTIFICATE.md create mode 100644 docs/tls/DOCUMENT-HIERARCHY.md create mode 100644 docs/tls/KUBERNETES-SECRETS.md create mode 100644 docs/tls/NGINX-PLUS-CONFIGURATION.md create mode 100644 docs/tls/NO-TLS.md create mode 100644 docs/tls/README.md create mode 100644 docs/tls/SERVER-CERTIFICATE.md create mode 100644 docs/tls/SS-MTLS.md create mode 100644 docs/tls/SS-TLS.md diff --git a/docs/tls/CA-MTLS.md b/docs/tls/CA-MTLS.md new file mode 100644 index 0000000..1e72dda --- /dev/null +++ b/docs/tls/CA-MTLS.md @@ -0,0 +1,87 @@ +# Mutual TLS with Certificate Authority (CA) certificates + +This mode allows NLK to verify it is connecting to the correct NGINX Plus instance, allows NGINX Plus to verify it is connecting to the correct NLK, and encrypts the data between NLK and NGINX Plus. + +## Overview + +Mutual TLS is used to encrypt the traffic between NLK and NGINX Plus, to ensure NLK verifies the NGINX Plus server, and to ensure NGINX Plus verifies NLK. + +## Certificates + +To configure this mode, the following certificates are required: + +- Server Certificate +- Client Certificate + +See the following sections for instructions on how to create these certificates. + +### Certificate Authority (CA) + +Provided by the user. + +### Server Certificate (NGINX Plus) + +Use your own certificate authority (CA) to generate a server certificate and key. + +### Client Certificate (NLK) + +Use your own certificate authority (CA) to generate a client certificate and key. + +## Kubernetes Secrets + +NLK accesses the necessary certificates for each mode from Kubernetes Secrets. For this mode, the following Kubernetes Secret(s) are required: +- Client Certificate + +To create the Kubernetes Secret containing the CA certificate, refer to the [Kubernetes Secrets](./KUBERNETES-SECRETS.md) guide; +the name and location of the certificate(s) created above should be used. The name of the Secret will be needed for the ConfigMap (discussed below). + +## ConfigMap + + +NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. + +Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. + +For this mode, the `mode` and `clientCertificate` fields need to be included. The `mode` field should be set to `ca-mtls` +and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. + +The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + mode: "ca-mtls" + clientCertificate: "nlk-tls-client-secret" +``` + +## Deployment + +Save the above ConfigMap definition to a file named `ca-mtls-configmap.yaml`, then deploy the ConfigMap using the following command: + +```bash +kubectl apply -f docs/tls/ca-mtls-configmap.yaml +``` + +## Configuring NGINX Plus + +Refer to the [NGINX Plus Configuration](./NGINX-PLUS-CONFIGURATION.md) guide for instructions on configuring NGINX Plus to use the certificates created above. + +The steps in both the ["One-way TLS"](./NGINX-PLUS-CONFIGURATION.md#one-way-tls) section and the ["Mutual TLS"](./NGINX-PLUS-CONFIGURATION.md#mutual-tls) section are required for this mode. + +## Verification + +To verify the ConfigMap was deployed correctly, run the following command: + +```bash +kubectl get configmap -n nlk nlk-config -o yaml +``` + +The output should match the ConfigMap above. + +To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. + diff --git a/docs/tls/CA-TLS.md b/docs/tls/CA-TLS.md new file mode 100644 index 0000000..3429ed5 --- /dev/null +++ b/docs/tls/CA-TLS.md @@ -0,0 +1,77 @@ +# One-way TLS with Certificate Authority (CA) certificates + +This mode allows NLK to verify it is connecting to the correct NGINX Plus instance, and encrypts the data between NLK and NGINX Plus. + +## Overview + +One-way TLS is used to encrypt the traffic between NLK and NGINX Plus, and to ensure NLK verifies the NGINX Plus server; +however, the NGINX Plus server _does not_ validate NLK. + +## Certificates + +To configure this mode, the following certificates are required: + +- Server Certificate + +See the following sections for instructions on how to create these certificates. + +### Certificate Authority (CA) + +Provided by the user. + +### Server Certificate (NGINX Plus) + +Use your certificate authority (CA) to generate a server certificate and key. + +## Kubernetes Secrets + +No Kubernetes Secrets are required for this mode. + +## ConfigMap + +NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. + +Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. + +For this mode, only the `mode` fields needs to be included. The `mode` field should be set to `ca-tls`. + +The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): + + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + mode: "ca-tls" +``` + +## Deployment + +Save the above ConfigMap definition to a file named `ca-tls-configmap.yaml`, then deploy the ConfigMap using the following command: + +```bash +kubectl apply -f docs/tls/ca-tls-configmap.yaml +``` + +## Configuring NGINX Plus + +Refer to the [NGINX Plus Configuration](./NGINX-PLUS-CONFIGURATION.md) guide for instructions on configuring NGINX Plus to use the certificates created above. + +Only the steps in the ["One-way TLS"](./NGINX-PLUS-CONFIGURATION.md#one-way-tls) section are required for this mode. +Use the certificate and key from your CA to configure NGINX Plus. + +## Verification + +To verify the ConfigMap was deployed correctly, run the following command: + +```bash +kubectl get configmap -n nlk nlk-config -o yaml +``` + +The output should match the ConfigMap above. + +To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. diff --git a/docs/tls/CERTIFICATE-AUTHORITY.md b/docs/tls/CERTIFICATE-AUTHORITY.md new file mode 100644 index 0000000..fe44fe8 --- /dev/null +++ b/docs/tls/CERTIFICATE-AUTHORITY.md @@ -0,0 +1,44 @@ +# Generate a Certificate Authority (CA) + +When using self-signed certificates, the first step is to generate the Certificate Authority (CA). + +The following commands will generate the CA certificate and key: + +```bash +openssl req -newkey rsa:2048 -nodes -x509 -out ca.crt -keyout ca.key +``` + +You will be prompted to enter the Distinguished Name (DN) information for the CA. + +Alternatively, you can provide the DN information in a file, an example is shown below: + +```bash +[ req ] +distinguished_name = dn +prompt = no +req_extensions = req_ext + +[ req_ext ] +basicConstraints = CA:TRUE +keyUsage = critical, keyCertSign, cRLSign + +[ dn ] +C=[COUNTRY] +ST=[STATE] +L=[LOCALITY] +O=[ORGANIZATION] +OU=[ORGANIZATION_UNIT] +``` + +Create a file using the above as a template and update the values in the `[ dn ]` section; then use following command to generate the CA certificate and key: + +```bash +openssl req -newkey rsa:2048 -nodes -x509 -config ca.cnf -out ca.crt -keyout ca.key +``` + +The output of the above command will be the CA certificate (`ca.crt`) and key (`ca.key`). + +## References + +- [Distinguished Name reference](http://certificate.fyicenter.com/2098_OpenSSL_req_-distinguished_name_Configuration_Section.html) + diff --git a/docs/tls/CLIENT-CERTIFICATE.md b/docs/tls/CLIENT-CERTIFICATE.md new file mode 100644 index 0000000..4ff86c9 --- /dev/null +++ b/docs/tls/CLIENT-CERTIFICATE.md @@ -0,0 +1,50 @@ +# Generate a client certificate + +When using self-signed certificates in the `ss-mtls` mode, a certificate needs to be generated for NLK. + +The certificate has the same basic field as the CA certificate, with the addition of `clientAuth` in the `extendedKeyUsage` field: + +```bash +[ req ] +distinguished_name = dn +prompt = no + +[ dn ] +C=[COUNTRY] +ST=[STATE] +L=[LOCALITY] +O=[ORGANIZATION] +OU=[ORGANIZATION_UNIT] + +[ client ] +extendedKeyUsage = clientAuth +``` + +Create a file using the above as a template and update the values in the `[ dn ]` section; then use following command to generate the certificate request and key: + +```bash +openssl genrsa -out client.key 2048 +openssl req -new -key client.key -config client.cnf -out client.csr +``` + +The output of the above commands will be the client certificate request (`client.csr`) and key (`client.key`). + +##### Sign the client certificate + +```bash +openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256 -extfile client.cnf -extensions client +``` + +The output of the above command will be the client certificate (`client.crt`). + +#### Verify the Client Certificate has the correct extendedKeyUsage + +```bash +openssl x509 -in client.crt -noout -purpose | grep 'SSL client :' +``` + +Look for `SSL client : Yes` in the output. + +## References + +- [Distinguished Name reference](http://certificate.fyicenter.com/2098_OpenSSL_req_-distinguished_name_Configuration_Section.html) \ No newline at end of file diff --git a/docs/tls/DOCUMENT-HIERARCHY.md b/docs/tls/DOCUMENT-HIERARCHY.md new file mode 100644 index 0000000..2d3deb2 --- /dev/null +++ b/docs/tls/DOCUMENT-HIERARCHY.md @@ -0,0 +1,51 @@ +```mermaid +graph LR + README[README.md] + SS-TLS[SS-TLS.md] + SS-MTLS[SS-MTLS.md] + CA-TLS[CA-TLS.md] + CA-MTLS[CA-MTLS.md] + NO-TLS[NO-TLS.md] + CERTIFICATE-AUTHORITY[CERTIFICATE-AUTHORITY.md] + CLIENT-CERTIFICATE[CLIENT-CERTIFICATE.md] + SERVER-CERTIFICATE[SERVER-CERTIFICATE.md] + KUBERNETES-SECRETS[KUBERNETES-SECRETS.md] + NGINX-PLUS-CONFIGURATION[NGINX-PLUS-CONFIGURATION.md] + KUBERNETES-SECRETS[KUBERNETES-SECRETS.md] + + subgraph "README.md" + README + end + + subgraph "NO-TLS links" + README --> NO-TLS + end + + subgraph "SS-TLS links" + README --> SS-TLS + SS-TLS --> CERTIFICATE-AUTHORITY + SS-TLS --> SERVER-CERTIFICATE + SS-TLS --> KUBERNETES-SECRETS + SS-TLS --> NGINX-PLUS-CONFIGURATION + end + + subgraph "SS-MTLS links" + README --> SS-MTLS + SS-MTLS --> CERTIFICATE-AUTHORITY + SS-MTLS --> SERVER-CERTIFICATE + SS-MTLS --> CLIENT-CERTIFICATE + SS-MTLS --> KUBERNETES-SECRETS + SS-MTLS --> NGINX-PLUS-CONFIGURATION + end + + subgraph "CA-TLS links" + README --> CA-TLS + CA-TLS --> NGINX-PLUS-CONFIGURATION + end + + subgraph "CA-MTLS links" + README --> CA-MTLS + CA-MTLS --> KUBERNETES-SECRETS + CA-MTLS --> NGINX-PLUS-CONFIGURATION + end +``` \ No newline at end of file diff --git a/docs/tls/KUBERNETES-SECRETS.md b/docs/tls/KUBERNETES-SECRETS.md new file mode 100644 index 0000000..2ea8379 --- /dev/null +++ b/docs/tls/KUBERNETES-SECRETS.md @@ -0,0 +1,82 @@ +# Kubernetes Secrets + +## Overview + +Kubernetes Secrets are used to provide the required certificates to NLK. There are two ways to create the Secrets: +- Using `kubectl` +- Using yaml files + +The filenames for the certificates created are required for both methods. The examples below assume the certificates were generated in `/tmp` +and follow the naming conventions in the documentation. + +## Using `kubectl` + +The easiest way to create the Secret(s) is by using `kubectl`: + +```bash +kubectl create secret tls -n nlk nlk-tls-ca-secret --cert=/tmp/ca.crt --key=/tmp/ca.key +kubectl create secret tls -n nlk nlk-tls-server-secret --cert=/tmp/server.crt --key=/tmp/server.key +kubectl create secret tls -n nlk nlk-tls-client-secret --cert=/tmp/client.crt --key=/tmp/client.key +``` + +## Using yaml files + +The Secrets can also be created using yaml files. The following is an example of a yaml file for the Client Secret (note that the `data` values are truncated): + +```yaml +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVCVEN... + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0l... +kind: Secret +metadata: + name: nlk-tls-ca-secret +type: kubernetes.io/tls +``` + +Note: While it is possible to generate the values for `tls.crt` and `tls.key` manually, the above yaml can be generated using the following command: + +```bash +kubectl create secret tls -n nlk nlk-tls-ca-secret --cert=/tmp/ca.crt --key=/tmp/ca.key --dry-run=client -o yaml > ca-secret.yaml +kubectl create secret tls -n nlk nlk-tls-server-secret --cert=/tmp/server.crt --key=/tmp/server.key --dry-run=client -o yaml > server-secret.yaml +kubectl create secret tls -n nlk nlk-tls-client-secret --cert=/tmp/client.crt --key=/tmp/client.key --dry-run=client -o yaml > client-secret.yaml +``` + +> [!WARNING] +> It is important that these files do not make their way into a public repository or other storage location where they can be accessed by unauthorized users. + + +Once the yaml files are generated they can be applied using `kubectl`: + +```bash +kubectl apply -f ca-secret.yaml +kubectl apply -f server-secret.yaml +kubectl apply -f client-secret.yaml +``` + +# Verification + +The Secrets can be verified using `kubectl`: + +```bash +kubectl describe secret -n nlk nlk-tls-ca-secret +kubectl describe secret -n nlk nlk-tls-server-secret +kubectl describe secret -n nlk nlk-tls-client-secret +``` + +The output should look similar to the example above. + +To see the actual values of the certificates, the following command can be used: + +```bash +kubectl get secret -n nlk nlk-tls-ca-secret -o json | jq -r '.data["tls.crt"], .data["tls.key"]' | base64 -d +kubectl get secret -n nlk nlk-tls-server-secret -o json | jq -r '.data["tls.crt"], .data["tls.key"]' | base64 -d +kubectl get secret -n nlk nlk-tls-client-secret -o json | jq -r '.data["tls.crt"], .data["tls.key"]' | base64 -d +``` + +Note that this requires `jq` to be installed. + +## References + +- [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) +- [kubectl dry run flags](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-dry-run-em-) \ No newline at end of file diff --git a/docs/tls/NGINX-PLUS-CONFIGURATION.md b/docs/tls/NGINX-PLUS-CONFIGURATION.md new file mode 100644 index 0000000..e622c83 --- /dev/null +++ b/docs/tls/NGINX-PLUS-CONFIGURATION.md @@ -0,0 +1,82 @@ +# Configuring NGINX Plus + +## Configuring the server certificate + +Depending on the mode chosen, some files will need to be copied to the NGINX Plus host. The following table shows which files are required for each mode: + +| Mode | Server Certificate | CA Certificate | +| ---- | ------------------ |--------------------| +| no-tls | | | | +| ss-tls | :heavy_check_mark: | :heavy_check_mark: | +| ss-mtls | :heavy_check_mark: | :heavy_check_mark: | +| ca-tls | :heavy_check_mark: | | +| ca-mtls | :heavy_check_mark: | | + + +Copy the necessary server files for the chosen mode to the NGINX host; place them in the `/etc/ssl/certs/nginx` directory. + +### One-way TLS + +The following applies to modes `ss-tls`, `ss-mtls`, `ca-tls`, and `ca-mtls`. + +To configure NGINX Plus to use the `server.crt` and `server.key` files for TLS, +add the following to the `http` or `server` context in the `/etc/nginx/nginx.conf` file: + +```bash +http { + ssl_certificate /etc/ssl/certs/nginx/server.crt; + ssl_certificate_key /etc/ssl/certs/nginx/server.key; +} +``` + +Reload the NGINX Plus configuration to apply the changes. + +```bash +nginx -s reload +``` + +### Mutual TLS + +In the `/etc/nginx/nginx.conf` file, add the following to the `http` or `server` context: + +#### Self-signed certificates + +When using `ss-mtls` mode, the CA certificate must be provided to NGINX Plus: + +```bash +http { + ssl_client_certificate /etc/ssl/certs/nginx/ca.crt; + ssl_verify_client on; + ssl_verify_depth 3; +} +``` + +This will configure NGINX Plus to use the `ca.crt` file for client authentication. + +#### CA-signed certificates + +When using `ca-mtls` mode, the `ssl_client_certificate` directive is not required: + +```bash +http { + ssl_verify_client on; + ssl_verify_depth 3; +} +``` + +Reload the NGINX Plus configuration to apply the changes. + +```bash +nginx -s reload +``` + +Test with curl: + +```bash +curl --cert client.crt --key client.key --cacert ca.crt https:///api +``` + +## References + +- [NGINX `ssl_certificate` directive](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate) +- [NGINX `ssl_client_certificate` directive](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_client_certificate) \ No newline at end of file diff --git a/docs/tls/NO-TLS.md b/docs/tls/NO-TLS.md new file mode 100644 index 0000000..e4aacd3 --- /dev/null +++ b/docs/tls/NO-TLS.md @@ -0,0 +1,57 @@ +# No TLS Mode + +This mode is the easiest to configure, but provides no security. Choose this for development environments, or test +environments where security is not strictly necessary. + +Note: you should test with one of the TLS / mTLS modes before deploying to production to ensure familiarity with the configuration and process. + +## Overview + +This is the default mode of operation for NLK. It offers no verification of either side of the connection, nor any encryption of the data. + +## Certificates + +No certificates are required for this mode. + +## Kubernetes Secrets + +No Kubernetes Secrets are required for this mode. + +## ConfigMap + +NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. + +For this mode, only the `mode` field needs to be included, and should be set to `no-tls` (or omitted altogether as this is the default mode). + +The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints) + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + mode: "no-tls" +``` + +## Deployment + +Save the above ConfigMap definition to a file named `no-tls-configmap.yaml`, then deploy the ConfigMap using the following command: + +```bash +kubectl apply -f docs/tls/no-tls-configmap.yaml +``` + +## Verification + +To verify the ConfigMap was deployed correctly, run the following command: + +```bash +kubectl get configmap -n nlk nlk-config -o yaml +``` + +The output should match the ConfigMap above. + +To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. diff --git a/docs/tls/README.md b/docs/tls/README.md new file mode 100644 index 0000000..19326f4 --- /dev/null +++ b/docs/tls/README.md @@ -0,0 +1,120 @@ +# Securing communication between NLK and NGINX Plus with TLS / mTLS + +> [!WARNING] +> THIS FEATURE IS IN DEVELOPMENT. THIS NOTICE WILL BE REMOVED WHEN THE IMPLEMENTATION IS COMPLETE. + +This document describes how to configure TLS / mTLS to secure communication between NLK and NGINX Plus. + +For development and test environments, using secure communication may not be necessary; however, in production environments +communications should be encrypted. More importantly, communications should be restricted to known actors. + +Using TLS / mTLS provides the following benefits: +- Confidentiality: communications are encrypted +- Integrity: communications are protected from tampering +- Authentication: communications are restricted to known actors + +NLK supports three options for securing communications between the NLK and NGINX Plus: + +1. No TLS +2. TLS with self-signed certificates +3. TLS with certificates signed by a Certificate Authority (CA) + +Within the TLS options there are two sub-options: + +1. One-way TLS +2. Mutual TLS + +The following diagram shows a visual representation of the One-way and Mutual TLS options +(the "CA Certificate" can be either self-signed or signed by a well-known CA): + +```mermaid +graph LR + CACertificate[CA Certificate] + + subgraph "One-way TLS" + NGINXPlusCert[NGINX Plus Certificate] + NLK[nginx-loadbalancer-kubernetes] + NGINXPlus[NGINX Plus] + NGINXPlusCert -->|Used by| NLK + NLK -->|Verifies| NGINXPlus + end + + subgraph "Mutual TLS" + NLKCert[NLK Certificate] + MNGINXPlusCert[NGINX Plus Certificate] + MNLK[nginx-loadbalancer-kubernetes] + MNGINXPlus[NGINX Plus] + NLKCert -->|Used by| MNGINXPlus + MNGINXPlus -->|Verifies| MNLK + MNGINXPlusCert -->|Used by| MNLK + MNLK -->|Verifies| MNGINXPlus + end + +CACertificate -->|Used for Signing| NLKCert +CACertificate -->|Used for Signing| NGINXPlusCert +CACertificate -->|Used for Signing| MNGINXPlusCert +``` + + +## No TLS + +This option, denoted as `no-tls`, is the easiest to configure, but provides no security. It is not recommended for production environments. + +This is the default mode of operation for NLK. + +## TLS with self-signed certificates + +This option is more secure than no TLS. With this option the certificates are self-signed, and therefore not trusted by default. +This means that NGINX Plus and NLK will need to be configured to trust the certificates. + +### One-way TLS + +In this mode, denoted as `ss-tls`, NLK will trust the server certificate, but NLK presents no certificate to be validated. +Since self-signed certificates are used, NLK will need to be configured to trust the server certificate. +This is done by adding the CA certificate to NLK's trust store. In order to do this, +the CA certificate will need to be provided to NLK via a Kubernetes Secret. + +Instructions for configuring One-way TLS with self-signed certificates can be found [here](SS-TLS.md). + +### Mutual TLS + +In this mode, denoted as `ss-mtls`, NLK will trust the server certificate, and the server will trust the NLK certificate. +Since self-signed certificates are used, NLK will need to be configured to trust the server certificate. Additionally, +the server will need to be configured to trust the NLK certificate. In order to do this, the CA certificate will need to be +provided to NLK via a Kubernetes Secret, and to the server by copying the CA certificate to the NGINX Plus host. + +Instructions for configuring Mutual TLS with self-signed certificates can be found [here](SS-MTLS.md). + +## TLS with certificates signed by a Certificate Authority (CA) + +This is the most secure option. With this option the certificates are signed by a CA, and therefore trusted by default. + +### One-way TLS + +In this mode, denoted as `ca-tls`, NLK will trust the server certificate, but NLK presents no certificate and cannot be verified by the server. + +Instructions for configuring One-way TLS with a CA-signed certificate can be found [here](CA-TLS.md). + +### Mutual TLS + +In this mode, denoted as `ca-mtls`, NLK will trust the server certificate, and the server will trust the NLK certificate. + +Instructions for configuring Mutual TLS with a CA-signed certificate can be found [here](CA-MTLS.md). + +## Considerations + +No TLS may be acceptable for development and testing, but is not recommended for production environments. + +If the highest level of trust and security is not required, but secure communication is, then mutual TLS with self-signed certificates is recommended. +This option provides a reasonable level of security, especially when the environments are sufficiently hardened and access is controlled. + +If the highest level of trust and security is required then Mutual TLS with certificates signed by a CA is recommended. + +## Next Steps + +Follow the guide appropriate for your preferred mode: +- [No TLS](NO-TLS.md) +- [One-way TLS with self-signed certificates](SS-TLS.md) +- [Mutual TLS with self-signed certificates](SS-MTLS.md) +- [One-way TLS with certificates signed by a Certificate Authority (CA)](CA-TLS.md) +- [Mutal TLS with certificates signed by a Certificate Authority (CA)](CA-MTLS.md) diff --git a/docs/tls/SERVER-CERTIFICATE.md b/docs/tls/SERVER-CERTIFICATE.md new file mode 100644 index 0000000..4102541 --- /dev/null +++ b/docs/tls/SERVER-CERTIFICATE.md @@ -0,0 +1,70 @@ +# Generate a server certificate + +When using self-signed certificates in the `ss-tls` and `ss-mtls` modes, a certificate needs to be generated for the NGINX Plus server. + +The certificate has the same basic fields as the CA certificate, but with a few additional fields. + +The following is an example of a configuration file for server certificates: + +```bash +[ req ] +distinguished_name = dn +req_extensions = req_ext +prompt = no + +[ dn ] +C=[COUNTRY] +ST=[STATE] +L=[LOCALITY] +O=[ORGANIZATION] +OU=[ORGANIZATION_UNIT] +CN=[COMMON_NAME] + +[ req_ext ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names +extendedKeyUsage = serverAuth + +[ alt_names ] +DNS.1 = mydomain.com +DNS.2 = server.mydomain.com +DNS.3 = *.mydomain.com +IP.1 = 10.0.0.10 +IP.2 = 10.0.0.11 +``` + +Create a file using this as a template, the following example commands use the name `server.cnf`. + +Be sure to update the Distinguished Name (DN) information and the SAN information (DNS / IP entries in the `alt_names` section) as appropriate. +Doing so ensures that the certificate is valid for the server by providing an unambiguous match between the server (IP addresses and/or domain names) and the certificate. + +The following commands will generate the server certificate request and key: + +```bash +openssl genrsa -out server.key 2048 +openssl req -new -key server.key -config server.cnf -out server.csr +``` + +The output of the above commands will be the server certificate request (`server.csr`) and key (`server.key`). + +##### Sign the server certificate + +```bash +openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256 -extensions req_ext -extfile server.cnf +``` + +The output of the above command will be the server certificate (`server.crt`). + +##### Verify the Server Certificate has the SAN + +```bash +openssl x509 -in server.crt -text -noout | grep -A 1 "Subject Alternative Name" +``` + +Look for the DNS / IP entries in the `Subject Alternative Name` section in the output; the values entered in the `alt_names` section of the `server.cnf` file should be present. + +## References + +- [OpenSSL extensions documentation](https://www.openssl.org/docs/manmaster/man5/x509v3_config.html) +- [Distinguished Name reference](http://certificate.fyicenter.com/2098_OpenSSL_req_-distinguished_name_Configuration_Section.html) diff --git a/docs/tls/SS-MTLS.md b/docs/tls/SS-MTLS.md new file mode 100644 index 0000000..ba62dd3 --- /dev/null +++ b/docs/tls/SS-MTLS.md @@ -0,0 +1,91 @@ +# Mutual TLS with self-signed certificates + +This mode allows NLK to verify it is connecting to the correct NGINX Plus instance, allows NGINX Plus to verify it is connecting to the correct NLK, and encrypts the data between NLK and NGINX Plus. + + +## Overview + +Mutual TLS is used to encrypt the traffic between NLK and NGINX Plus, to ensure NLK verifies the NGINX Plus server, and to ensure NGINX Plus verifies NLK. + + +## Certificates + +To configure this mode, the following certificates are required: + +- CA Certificate +- Server Certificate +- Client Certificate + +See the following sections for instructions on how to create these certificates. + +### Certificate Authority (CA) + +Follow the instructions in the [Certificate Authority](./CERTIFICATE-AUTHORITY.md) guide to create a self-signed CA certificate and key. + +### Server Certificate (NGINX Plus) + +Follow the instructions in the [Server Certificate](./SERVER-CERTIFICATE.md) guide to create a self-signed server certificate and key. + +### Client Certificate (NLK) + +Follow the instructions in the [Client Certificate](./CLIENT-CERTIFICATE.md) guide to create a self-signed client certificate and key. + +## Kubernetes Secrets + +NLK accesses the necessary certificates for each mode from Kubernetes Secrets. For this mode, the following Kubernetes Secret(s) are required: +- CA Certificate +- Client Certificate + +To create the Kubernetes Secret containing the CA certificate, refer to the [Kubernetes Secrets](./KUBERNETES-SECRETS.md) guide; +the name and location of the certificate(s) created above should be used. The name of the Secret will be needed for the ConfigMap (discussed below). + +## ConfigMap + +NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. + +Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. + +For this mode, the `mode`, `caCertificates`, and `clientCertificate` fields need to be included. The `mode` field should be set to `ss-mtls`, +the `caCertificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above, +and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. + +The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + mode: "ss-mtls" + caCertificate: "nlk-tls-ca-secret" + clientCertificate: "nlk-tls-client-secret" +``` + +## Deployment + +Save the above ConfigMap definition to a file named `ss-mtls-configmap.yaml`, then deploy the ConfigMap using the following command: + +```bash +kubectl apply -f docs/tls/ss-mtls-configmap.yaml +``` + +## Configuring NGINX Plus + +Refer to the [NGINX Plus Configuration](./NGINX-PLUS-CONFIGURATION.md) guide for instructions on configuring NGINX Plus to use the certificates created above. + +The steps in both the ["One-way TLS"](./NGINX-PLUS-CONFIGURATION.md#one-way-tls) section and the ["Mutual TLS"](./NGINX-PLUS-CONFIGURATION.md#mutual-tls) section are required for this mode. + +## Verification + +To verify the ConfigMap was deployed correctly, run the following command: + +```bash +kubectl get configmap -n nlk nlk-config -o yaml +``` + +The output should match the ConfigMap above. + +To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. diff --git a/docs/tls/SS-TLS.md b/docs/tls/SS-TLS.md new file mode 100644 index 0000000..aeee4a0 --- /dev/null +++ b/docs/tls/SS-TLS.md @@ -0,0 +1,83 @@ +# One-way TLS with self-signed certificates + +This mode allows NLK to verify it is connecting to the correct NGINX Plus instance, and encrypts the data between NLK and NGINX Plus. + +## Overview + +One-way TLS is used to encrypt the traffic between NLK and NGINX Plus, and to ensure NLK verifies the NGINX Plus server; +however, the NGINX Plus server _does not_ validate NLK. + +## Certificates + +To configure this mode, the following certificates are required: + +- CA Certificate +- Server Certificate + +See the following sections for instructions on how to create these certificates. + +### Certificate Authority (CA) + +Follow the instructions in the [Certificate Authority](./CERTIFICATE-AUTHORITY.md) guide to create a self-signed CA certificate and key. + +### Server Certificate (NGINX Plus) + +Follow the instructions in the [Server Certificate](./SERVER-CERTIFICATE.md) guide to create a self-signed server certificate and key. + +## Kubernetes Secrets + +NLK accesses the necessary certificates for each mode from Kubernetes Secrets. For this mode, the following Kubernetes Secret(s) are required: +- CA Certificate + +To create the Kubernetes Secret containing the CA certificate, refer to the [Kubernetes Secrets](./KUBERNETES-SECRETS.md) guide; +the name and location of the certificate(s) created above should be used. The name of the Secret will be needed for the ConfigMap (discussed below). + +## ConfigMap + +NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. + +Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. + +For this mode, both the `mode` and `caCertificates` fields need to be included. The `mode` field should be set to `ss-tls`, +and the `caCertificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above. + +The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): + + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + mode: "ss-tls" + caCertificate: "nlk-tls-ca-secret" +``` + +## Deployment + +Save the above ConfigMap definition to a file named `ss-tls-configmap.yaml`, then deploy the ConfigMap using the following command: + +```bash +kubectl apply -f docs/tls/ss-tls-configmap.yaml +``` + +## Configuring NGINX Plus + +Refer to the [NGINX Plus Configuration](./NGINX-PLUS-CONFIGURATION.md) guide for instructions on configuring NGINX Plus to use the certificates created above. + +Only the steps in the ["One-way TLS"](./NGINX-PLUS-CONFIGURATION.md#one-way-tls) section are required for this mode. + +## Verification + +To verify the ConfigMap was deployed correctly, run the following command: + +```bash +kubectl get configmap -n nlk nlk-config -o yaml +``` + +The output should match the ConfigMap above. + +To verify NLK is running, follow the instructions in either the [TCP](../tcp/tcp-installation-guide.md) or [HTTP](../http/http-installation-guide.md) guides. From 44ab15046cf915fd56fddf18d14ea14381c9e6e7 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Fri, 22 Sep 2023 12:49:30 -0700 Subject: [PATCH 02/14] - `mode` to `tlsMode` --- docs/tls/CA-MTLS.md | 4 ++-- docs/tls/CA-TLS.md | 4 ++-- docs/tls/NO-TLS.md | 4 ++-- docs/tls/SS-MTLS.md | 4 ++-- docs/tls/SS-TLS.md | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/tls/CA-MTLS.md b/docs/tls/CA-MTLS.md index 1e72dda..6692739 100644 --- a/docs/tls/CA-MTLS.md +++ b/docs/tls/CA-MTLS.md @@ -42,7 +42,7 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, the `mode` and `clientCertificate` fields need to be included. The `mode` field should be set to `ca-mtls` +For this mode, the `tlsMode` and `clientCertificate` fields need to be included. The `tlsMode` field should be set to `ca-mtls` and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -55,7 +55,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - mode: "ca-mtls" + tlsMode: "ca-mtls" clientCertificate: "nlk-tls-client-secret" ``` diff --git a/docs/tls/CA-TLS.md b/docs/tls/CA-TLS.md index 3429ed5..ed828cc 100644 --- a/docs/tls/CA-TLS.md +++ b/docs/tls/CA-TLS.md @@ -33,7 +33,7 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, only the `mode` fields needs to be included. The `mode` field should be set to `ca-tls`. +For this mode, only the `tlsMode` fields needs to be included. The `tlsMode` field should be set to `ca-tls`. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -46,7 +46,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - mode: "ca-tls" + tlsMode: "ca-tls" ``` ## Deployment diff --git a/docs/tls/NO-TLS.md b/docs/tls/NO-TLS.md index e4aacd3..8831c84 100644 --- a/docs/tls/NO-TLS.md +++ b/docs/tls/NO-TLS.md @@ -21,7 +21,7 @@ No Kubernetes Secrets are required for this mode. NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, only the `mode` field needs to be included, and should be set to `no-tls` (or omitted altogether as this is the default mode). +For this mode, only the `tlsMode` field needs to be included, and should be set to `no-tls` (or omitted altogether as this is the default mode). The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints) @@ -33,7 +33,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - mode: "no-tls" + tlsMode: "no-tls" ``` ## Deployment diff --git a/docs/tls/SS-MTLS.md b/docs/tls/SS-MTLS.md index ba62dd3..c716889 100644 --- a/docs/tls/SS-MTLS.md +++ b/docs/tls/SS-MTLS.md @@ -45,7 +45,7 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, the `mode`, `caCertificates`, and `clientCertificate` fields need to be included. The `mode` field should be set to `ss-mtls`, +For this mode, the `tlsMode`, `caCertificates`, and `clientCertificate` fields need to be included. The `tlsMode` field should be set to `ss-mtls`, the `caCertificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above, and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. @@ -59,7 +59,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - mode: "ss-mtls" + tlsMode: "ss-mtls" caCertificate: "nlk-tls-ca-secret" clientCertificate: "nlk-tls-client-secret" ``` diff --git a/docs/tls/SS-TLS.md b/docs/tls/SS-TLS.md index aeee4a0..7b92039 100644 --- a/docs/tls/SS-TLS.md +++ b/docs/tls/SS-TLS.md @@ -38,7 +38,7 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, both the `mode` and `caCertificates` fields need to be included. The `mode` field should be set to `ss-tls`, +For this mode, both the `tlsMode` and `caCertificates` fields need to be included. The `tlsMode` field should be set to `ss-tls`, and the `caCertificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -52,7 +52,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - mode: "ss-tls" + tlsMode: "ss-tls" caCertificate: "nlk-tls-ca-secret" ``` From a39a9042e2391f619cc30817f639e7ad219be73e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 00:48:55 +0000 Subject: [PATCH 03/14] Bump github/codeql-action from 2.21.4 to 2.21.5 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.21.4 to 2.21.5. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/a09933a12a80f87b87005513f0abb1494c27a716...00e563ead9f72a8461b24876bee2d0c2e8bd2ee8) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-sign-image.yml | 12 ++++++------ .github/workflows/scorecard.yml | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-sign-image.yml b/.github/workflows/build-and-sign-image.yml index 7673774..b95b1da 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -25,15 +25,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Install cosign - uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 #v3.0.2 + uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.0.2 with: cosign-release: 'v1.13.1' - name: Log into registry ${{ env.REGISTRY }} for ${{ github.actor }} - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -41,13 +41,13 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 + uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Build Docker Image id: docker-build-and-push - uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 + uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 with: context: . file: ./Dockerfile @@ -73,7 +73,7 @@ jobs: ignore-unfixed: 'true' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.2.11 + uses: github/codeql-action/upload-sarif@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.2.11 continue-on-error: true with: sarif_file: 'trivy-results-${{ inputs.image }}.sarif' diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 25bc1fb..75ce997 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -68,5 +68,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3 + uses: github/codeql-action/upload-sarif@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.21.5 with: sarif_file: results.sarif From 2e1a8c464ae21180302ca7f86b9f9fe467e47ab3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 00:48:42 +0000 Subject: [PATCH 04/14] Bump sigstore/cosign-installer from 3.1.1 to 3.1.2 Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.1.1 to 3.1.2. - [Release notes](https://github.com/sigstore/cosign-installer/releases) - [Commits](https://github.com/sigstore/cosign-installer/compare/6e04d228eb30da1757ee4e1dd75a0ec73a653e06...11086d25041f77fe8fe7b9ea4e48e3b9192b8f19) --- updated-dependencies: - dependency-name: sigstore/cosign-installer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build-and-sign-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-sign-image.yml b/.github/workflows/build-and-sign-image.yml index b95b1da..1a84e17 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -28,7 +28,7 @@ jobs: uses: actions/checkout@v3 - name: Install cosign - uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.0.2 + uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 #v3.0.2 with: cosign-release: 'v1.13.1' From 1c3188495815ab8def6ae8da6019991f13005fb9 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Fri, 8 Sep 2023 09:16:32 -0700 Subject: [PATCH 05/14] - renames for consistency - Checkpoint - DEMO - DEMO - Implements Certificates - Certificates fleshed out and tests underway - Refactoring of authorization tests required due to changes in the shape of the Certificates module. - FRT (Fix Red Tests) - minor cleanup - Base implementation of Certificate and informer - authentication factory implemented - rename, and doc start - CHECKPOINT: File creation --- .gitignore | 4 + ca-secret.yaml | 10 + client-secret.yaml | 10 + cmd/certificates-test-harness/doc.go | 10 + cmd/certificates-test-harness/main.go | 79 ++++ cmd/configuration-test-harness/doc.go | 1 + cmd/configuration-test-harness/main.go | 80 ++++ cmd/nginx-loadbalancer-kubernetes/main.go | 4 +- cmd/tls-config-factory-test-harness/doc.go | 1 + cmd/tls-config-factory-test-harness/main.go | 229 +++++++++ deployments/deployment/configmap.yaml | 8 +- deployments/deployment/deployment.yaml | 3 +- deployments/rbac/clusterrole.yaml | 2 +- docs/DEMO/COMMANDS.md | 111 +++++ docs/DEMO/SLIDE-1.md | 5 + docs/DEMO/SLIDE-2.md | 9 + docs/DEMO/SLIDE-3.md | 11 + docs/DEMO/SLIDE-4.md | 43 ++ docs/DEMO/SLIDE-5.md | 22 + docs/DEMO/SLIDE-6.md | 12 + docs/DEMO/SLIDE-7.md | 5 + docs/DEMO/SLIDE-8.md | 2 + docs/tls/CA-MTLS.md | 4 +- docs/tls/CA-TLS.md | 4 +- docs/tls/FIFTY-THOUSAND-FOOT-TLDR.md | 102 ++++ docs/tls/NO-TLS.md | 4 +- docs/tls/SS-MTLS.md | 12 +- docs/tls/SS-TLS.md | 6 +- go.mod | 5 + go.sum | 7 + internal/authentication/doc.go | 10 + internal/authentication/factory.go | 115 +++++ internal/authentication/factory_test.go | 444 ++++++++++++++++++ internal/certification/certificates.go | 195 ++++++++ internal/certification/certificates_test.go | 244 ++++++++++ internal/certification/doc.go | 10 + .../communication/{client.go => factory.go} | 17 +- .../{client_test.go => factory_test.go} | 23 +- internal/communication/roundtripper_test.go | 11 +- internal/configuration/settings.go | 104 +++- internal/synchronization/synchronizer.go | 2 +- server-secret.yaml | 10 + 42 files changed, 1932 insertions(+), 58 deletions(-) create mode 100644 ca-secret.yaml create mode 100644 client-secret.yaml create mode 100644 cmd/certificates-test-harness/doc.go create mode 100644 cmd/certificates-test-harness/main.go create mode 100644 cmd/configuration-test-harness/doc.go create mode 100644 cmd/configuration-test-harness/main.go create mode 100644 cmd/tls-config-factory-test-harness/doc.go create mode 100644 cmd/tls-config-factory-test-harness/main.go create mode 100644 docs/DEMO/COMMANDS.md create mode 100644 docs/DEMO/SLIDE-1.md create mode 100644 docs/DEMO/SLIDE-2.md create mode 100644 docs/DEMO/SLIDE-3.md create mode 100644 docs/DEMO/SLIDE-4.md create mode 100644 docs/DEMO/SLIDE-5.md create mode 100644 docs/DEMO/SLIDE-6.md create mode 100644 docs/DEMO/SLIDE-7.md create mode 100644 docs/DEMO/SLIDE-8.md create mode 100644 docs/tls/FIFTY-THOUSAND-FOOT-TLDR.md create mode 100644 internal/authentication/doc.go create mode 100644 internal/authentication/factory.go create mode 100644 internal/authentication/factory_test.go create mode 100644 internal/certification/certificates.go create mode 100644 internal/certification/certificates_test.go create mode 100644 internal/certification/doc.go rename internal/communication/{client.go => factory.go} (71%) rename internal/communication/{client_test.go => factory_test.go} (75%) create mode 100644 server-secret.yaml diff --git a/.gitignore b/.gitignore index 44d148d..2228b05 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ Thumbs.db ######## *.log /misc-dev/ + +cover* +tmp/ +docs/tls/DESIGN.md diff --git a/ca-secret.yaml b/ca-secret.yaml new file mode 100644 index 0000000..5aabd61 --- /dev/null +++ b/ca-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURUekNDQWpjQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1HUXgKQ3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcFhZWE5vYVc1bmRHOXVNUkF3RGdZRFZRUUhEQWRUWldGMApkR3hsTVE0d0RBWURWUVFLREFWT1IwbE9XREVlTUJ3R0ExVUVDd3dWUTI5dGJYVnVhWFI1SUNZZ1FXeHNhV0Z1ClkyVnpNQjRYRFRJek1UQXdNakl6TURReU9Wb1hEVEl6TVRFd01USXpNRFF5T1Zvd1pERUxNQWtHQTFVRUJoTUMKVlZNeEV6QVJCZ05WQkFnTUNsZGhjMmhwYm1kMGIyNHhFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVgpCQW9NQlU1SFNVNVlNUjR3SEFZRFZRUUxEQlZEYjIxdGRXNXBkSGtnSmlCQmJHeHBZVzVqWlhNd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEc0xteW9vc2NKNXZQbmFER3FCZkozYmQxT2F0NXYKYjQwSjcrc0ROamxhUFNGRjdNZGJyN2JFOFhkRUNGOHZCYkE2MkUwTVJRYXJHMFl5aWJBOG05OFV5TVJ1R0VRTApSZkltZ2pHVWtXdkRmU0ViSkJLZ1RXay83ckJPeDVRY1lFVnlCZ2Z2SDZMWk5hampic0VFaUFjalVObkp3WkNpCjdIWjJDSVZ5NVhpUmlKWVZLbGZwN3QvYlIwTXRtWVNEMlh6L0RoZXBRMnpkQ25zNnpFK3Q5aGxINnorcThydUUKam45ZTJaUGNBeTlHc1lNVi9WSTdIK0ZyMVBaREJJNUhtb1pZeUo4MTVqcWhiQUxGeU00Z0x3V1JlSVJuek9aZApNeVV0QW8rM1Vic1ZwQ0NOTE9XSk1WeXVUUVV1U3QwOFlXNTFDeGtESlBUMDY0bFAyZXBLQ2RQcEFnTUJBQUV3CkRRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMUStGRzVxNDVSV0JFK1JwNkpwUlZWakphMFF6ZE44V1ljcWUwTDkKVlJPbG5aSkxva2JGNzJrOEdrZ0t3YzNVZ21IRzFMbmMvdzlTbFJ1Ti9uRkMySEtZbWxqWHkwaHpWaFZLbDRjRgozc1NKV0c2dzBkcXFNVmk0bDBybFVDQlk3cDBTQXA4eFdLd2ZoanE0NU1YSGlwNERRUzVTUXUrWjJiSCtTN3pzCjNxcGhDaEpkNkpGZUNnL0pGQ1VIWjZGRXk0ZitJcGl1bU01SENKakJUcHdEVUdLcHB3a2pPeWp6Ym1Vc1hEcUYKRkIvYzZ4MnluYjlROGQ4MG1oREV5V0dFWDhtbE1ycXNrblFZd2JTenkyUitudnFLOFp3NTFGbDRSUU96K1lPZAphMUlTemZKWkYwdEtqcittS0ZSODZXWm05VWNOT1JnWjQ1WjVRNWI1V1AvMVF6dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRRHNMbXlvb3NjSjV2UG4KYURHcUJmSjNiZDFPYXQ1dmI0MEo3K3NETmpsYVBTRkY3TWRicjdiRThYZEVDRjh2QmJBNjJFME1SUWFyRzBZeQppYkE4bTk4VXlNUnVHRVFMUmZJbWdqR1VrV3ZEZlNFYkpCS2dUV2svN3JCT3g1UWNZRVZ5Qmdmdkg2TFpOYWpqCmJzRUVpQWNqVU5uSndaQ2k3SFoyQ0lWeTVYaVJpSllWS2xmcDd0L2JSME10bVlTRDJYei9EaGVwUTJ6ZENuczYKekUrdDlobEg2eitxOHJ1RWpuOWUyWlBjQXk5R3NZTVYvVkk3SCtGcjFQWkRCSTVIbW9aWXlKODE1anFoYkFMRgp5TTRnTHdXUmVJUm56T1pkTXlVdEFvKzNVYnNWcENDTkxPV0pNVnl1VFFVdVN0MDhZVzUxQ3hrREpQVDA2NGxQCjJlcEtDZFBwQWdNQkFBRUNnZ0VBWWV3Tm1RMkVRSkxFNVZqSjNwaUFvd3VtQ2ZFOU1DNnI1MGJWeFlzaDFFd3MKRTNYTVlqTkVML3Q5VzNPdEl5M1VsMUUvQUt0TnpIdU9hejJ6R0MzNEhBSHhqMFA0VWtRNTFjVjlFUUFLRWc4NwpQcW1DSDN4NCtzelh4Skh5MHFFSHFmTGVMMEtLbmt3bExjYXB1RnM5dW1LM0tYTmJxSEVwM0Y1RUZoTVdIaUFiClV4cWpYZXJKWXBZMXU3RVhwUHFWaSt3dFhkWDNuT092bStZMzNLemRGNTErTXBpOHVkMGpTY0p2VEtIYXpKV1oKZE40aFF4SEpEOGpMZm8zNDRNWEFYQndkWFVuN1V4NW9qU0dVemtnU1pKTnBkQVNaUU5lN2E1Qk4xalREZ29LaQp0My9kS0NFTmFsMGdUQVI1MFArYWJxeWhSOFUxbVVuT1dsWDN1ekJsa1FLQmdRRDNFS09tWm16Z293Nk5mOWp2CmpkdzJOUUQ1Q2NlWEk4cWlxRjlMNWZoVWV0b29PajA2WlZpYjBEejNWcWlqVnRITFBQV2pRQ2ZwWC9DVHdkUTkKajNHTVBDOEhIZWJkeEZqTHpRSWlDUXAvaGFlc2RGTUhHRzFBak92VDRNdkxOaklWM29JOCszZjhIWk81Q0pYQgpJdW1xcG82YWJhV094VEhDS2pYY2YxNTI0d0tCZ1FEMHVRWGNJWlJrNjdrYU5PZWhjLzFuU2QyTVJSVG1zV3JYClFjTEtQczdWVFkwWlA1T2hsU1pRVkM2MW9PUWtuZnFScGhVbnJVVDF1NXFRbzJUSGhGcmN4a3Qrd1hCenhDWDgKUXZXY1RTTmkvZ0Y2Rkx1OEsyYXlOVlBQTDFNQjJ4ZGxTL0Z0SDRLSG5RWHM1NFM5MVZHRTR5OUd6aDIwWEcwOQp4K0FiQzlPM3d3S0JnSGNuWURXbFdrY3dmSmxEbW1WV0htbEtRTkRhcFphLzNUOTdRcEtCTTdYU2xob21sRmJ3CmY3Nk52SWx4RXQzTHhseGxadlkzdjhmdXpFRUdqd3l0Zkk2c2krVzd4eGNYVmRmY1pIWHp0RXR5TXo2WnoxMHgKcTZjaEQ2OWMwQXlPYzdOV1g2dDNnQk5vVkZFOTBiT1cyZWpDY1M0TFNYaEVwRTNITzdpKytOa1BBb0dBZW9CYgo1Sk9TbXVvOG9GZTNVMlNpaHArOUhVZy9iRE9IamZWSE1zSTUreUIwN3h5YUpCcHJNVzdTYXV6OUJ5OWxqSjhjCm05M3FWUy94OFZFNVUzNTNsV2hWeGovQ3NOQ1JTek9oaXZvNktvV0g2N3FSTjJKcVorNjE0MUtITkxpZGY0R0MKZXVONURiV1dqNzVjL2tIWUtyTW1xVVRvTGE3T3FFeHpiRmFCUnMwQ2dZQUN6UlBiQTBqK0JyOWpxenFKYU56VQpPZzJ4aUFhZlNuTzBxTkREZWZNdXhZSnFMZ0llMG9HRUExb1JlcDhvTDNDeWZYd2h3U2xqMlRjeGZZTFR5MENKCmZBejF4QjY3SUF3cGlvZnI0K1ZXekF4SC9BbnlhVGNZVGdtakdKdlhtZ3l1RGU3ZGJsa3lJU1M5ZTBLc1JzeTYKQ2hPdVZoQy96bjErbm9BMkNsdzFJdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-ca-secret + namespace: nlk +type: kubernetes.io/tls diff --git a/client-secret.yaml b/client-secret.yaml new file mode 100644 index 0000000..e703594 --- /dev/null +++ b/client-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVIRENDQXdTZ0F3SUJBZ0lVUFRrVExlS3FNQlRlNFZhQXpiL1hXcSt6THM4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eEVEQU9CZ05WQkFjTQpCMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0ZFc1cGRIa2dKaUJCCmJHeHBZVzVqWlhNd0hoY05Nak14TURBeU1qTXdOak0zV2hjTk1qUXhNREF4TWpNd05qTTNXakJrTVFzd0NRWUQKVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLVjJGemFHbHVaM1J2YmpFUU1BNEdBMVVFQnd3SFUyVmhkSFJzWlRFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEhqQWNCZ05WQkFzTUZVTnZiVzExYm1sMGVTQW1JRUZzYkdsaGJtTmxjekNDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSzRPY1h4YWVrNkVQbTBDZUo5Z21iRlEKbFg1THlHNlUybEZIOFBFY3UvUlVTenJRQW1Rckh5di9mMlFBb3Q5VzhGNVlpaVVlQi85TkVGY1M2ZUVEK00xbwpVMy9ubEdHS21qTlQ5amNpbnBDZ0lYVUZ5UVQvWkd3QjFVWEdZYUViMHFWOFVVUlpTS1AxWXI5SzJBTzRxWE95CkFGTjZyUkdrdzBZTEErd3FEcDQxdFBNZEZZWWcrRit4OFZ5S1hLckZScUZRTFNPa2hzQXNwT3RZcjNOMkIzUlMKVGozeXFyNUZkc2w0VklSYWZCMWlhbGhSRWdnbDZjekZZaHZmci9kNjdNcHJvVlhudUZ5cS9OUDAvbXRnWXhvRwpKRVFoQmhnOFR4eUhiZ1lsTTFXZkxQTDh0SG5WUTZZVTVzamwrcG8vRU1yR24wNiszNnRwdksyT0RmNkJ6SDhDCkF3RUFBYU9CeFRDQndqQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFkQmdOVkhRNEVGZ1FVMkdrWmcxakUKYlcyN0lJSjV5cTB3ekJXa1FzY3dnWXNHQTFVZEl3U0JnekNCZ0tGb3BHWXdaREVMTUFrR0ExVUVCaE1DVlZNeApFekFSQmdOVkJBZ01DbGRoYzJocGJtZDBiMjR4RURBT0JnTlZCQWNNQjFObFlYUjBiR1V4RGpBTUJnTlZCQW9NCkJVNUhTVTVZTVI0d0hBWURWUVFMREJWRGIyMXRkVzVwZEhrZ0ppQkJiR3hwWVc1alpYT0NGRjRpSllsZ1hWKzYKSzFyUTQvUlpxcTJNbFFNZk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQWkwais2eDMrZjJ4ZXJlQlg2OFNmWAozM0tmc0tOaGJIOXJKeU9ROXViV1RrdHdaQnVOUmFKNEptWUtFa0IxUnVnOTFVVmdMdmZzWm5VY1FTOHRQUU8rCnNEcHFEamdwdnlZbU1LSm1HVHZ0M0tmK3JpV0dtU3g3ZDZUb0R5bGpNZ2dUdmJ4dFZhQTNtak9UcGFzb0ZWTzMKcXlVU29sUCtoZzI5M2Z2Q1JaeWhNTTd0ejdEdUIzRmFjR1JHNmNoaE55N0UzaGRpd2JjVUdIQ0VGN0ZwbWNLTgpOTlhyUVMwOW9GeUVmZEd3TkFHOUtPVHZkVDNZUzFqcmo3QnROOGlMSGxQUFFNaFk3aWhTUnlVUmN1S05vSC9BCnRTQm83eFJWV3BZMTIrMEJhaERjQ1lndzJ0RjMvd2Q1ekhwb1RuZi9YZFJiMm4vUzBKbTZueE54NUdlbDhMK2EKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ3VEbkY4V25wT2hENXQKQW5pZllKbXhVSlYrUzhodWxOcFJSL0R4SEx2MFZFczYwQUprS3g4ci8zOWtBS0xmVnZCZVdJb2xIZ2YvVFJCWApFdW5oQS9qTmFGTi81NVJoaXBvelUvWTNJcDZRb0NGMUJja0UvMlJzQWRWRnhtR2hHOUtsZkZGRVdVaWo5V0svClN0Z0R1S2x6c2dCVGVxMFJwTU5HQ3dQc0tnNmVOYlR6SFJXR0lQaGZzZkZjaWx5cXhVYWhVQzBqcEliQUxLVHIKV0s5emRnZDBVazQ5OHFxK1JYYkplRlNFV253ZFltcFlVUklJSmVuTXhXSWIzNi8zZXV6S2E2RlY1N2hjcXZ6VAo5UDVyWUdNYUJpUkVJUVlZUEU4Y2gyNEdKVE5Wbnl6eS9MUjUxVU9tRk9iSTVmcWFQeERLeHA5T3Z0K3JhYnl0CmpnMytnY3gvQWdNQkFBRUNnZ0VBSXo2VWQwaEE1TjQ5WDhoMDBWejNzaUp0cXYzQWI3ZmZmejd3aUhvM2l2RjQKckVlTGZHb0k3VmxXbTlMUEtDZE1FK2Fjem9oR3VVa0xDbjZ2Y2h0aVNZR2JDdGJEUW44VTIxamdqZWlLTUNIawp0SFAvOE8yZ0VZakxmVTMrM2VjcTM4eU5EaWlBSDRja1FEVHhDY3ZlTUNtMmpERFdrN0NIeEFxZCtEZko3dm45Cnp2enY2TkkzQWhWZktwYmszV0RONmlRajdhelNxN2dPV0JnaS81d3hmcnJqYnpRbGU0YWRvRHZXUFcxbXg4TEcKbFNQekUyWUVIbHRGN2lFbTFTTW9SMFRtQ28xOVYrTUFjQTRLek4yUk05WXNGRER0b1hSMjRodm1wS0ZiazdIQwpITm01ZTd6dUdYK1NFOW1sbDBST1RuMXhTQUw1bjMvaFI5MzFINzdGeVFLQmdRRHhoWWhiNkN2Wm5Nb3krejVWCnR4VFh6dFR4UG0yOVpYaHdVTEZVYUphVUR3Rk13MlVhMld5ZGlscDMzUkZwWlFPNnZwZXl4NE55RjVBTmh6TzMKZE11VktmMTNWekR4NkxROXpRNng3R0NmekRkdDllWTc2aUwxRzBJV3NUZUZaTWduSDE2WUNVaVFlMVRhNVNPOQphWXN5eGErTkFIaU4yVDZydGNuZ0FKMjNPUUtCZ1FDNGZaR0JaZmtQRFlQaEpKM0RmSUcyM1hua1UrM29rUElSCitWcHBmSkhjVnZ4TDFYREd4Zk5tVGZpcmFJZUYvK0ZUdGJsb0Q4eEZTeU9zZGwwNTJsVlhtV0svV3hRSGl2c2IKL1I2WHJLNjRjZXdtMVFQY3dtek1Lc24vOW02UlVsdlF3VmUyVGYzYWxHYzRVWFBCVGtJZDlESTFaWlV5a2ZmUQppQXhPL0NXcGR3S0JnQzEySlNTbm54bG5HZWhld216LytUeG1Bazhtb1NGMWFDWThDaVVKU3M2enhGcmVyTGxSCkU5RFRxaFBGMlBFdHduWDBTam1zdEdGVmJoZ2R5dTVOWGNUR0VwL1VHYkp2U3Y0WEN4MFNrVjJDNHl3ZmpTYloKKzVxSGR2a3VnblRwYzROcHREU0tDczZuYUdHTG9CNlhMMHh2U1l3UStxQTR0RU0rQkxIVmE5cUJBb0dCQUk1NApXYzlsb2lvZnM4SS85cDBxSHpuS1d3RWFWMVVMNmdRN1hiaXNmQzk5OVNQUzFsNktLMmJMdThjUzErV0JMczdvClBSL0JZMnYzbExyd1JSb1NJMm1jaUFkaUhGdWUxa0JNL2p6L0c0WlFZNSt4VEdSRXVLUUtQeWd0ZEVGQktxcFIKUkowQ0taR01uUkYreFRkNGFkS2I2OUlVZWwwdElBU25xMm1yaXFJTkFvR0FKcmtDS2Rxejg5blRYd3FnbmZrWgpSbVB5Tk1sMjlidDNIT1ZsR3ZoTjJib3lBeHFaYk4xLzdaWE5OSlFKR3J0TUx0QzFXY3o1bXE0czY1QW5KSFhaCkhQWVRyWGdmdjc2SHROMG95TDcxbFBUOTZpU3RHc0tWSmx1R1MvT3I2TXh2Nm5XTEY0aG1mUURqaGpSNVgvdEsKTW1XV0J5Q2JRTU9mRm5hRjB2azJuTXM9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-client-secret + namespace: nlk +type: kubernetes.io/tls diff --git a/cmd/certificates-test-harness/doc.go b/cmd/certificates-test-harness/doc.go new file mode 100644 index 0000000..538bed9 --- /dev/null +++ b/cmd/certificates-test-harness/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +/* +Package certificates_test_harness includes functionality boostrap and test the certification.Certificates implplementation. +*/ + +package main diff --git a/cmd/certificates-test-harness/main.go b/cmd/certificates-test-harness/main.go new file mode 100644 index 0000000..3be7045 --- /dev/null +++ b/cmd/certificates-test-harness/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "errors" + "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/sirupsen/logrus" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + "path/filepath" +) + +func main() { + logrus.SetLevel(logrus.DebugLevel) + err := run() + if err != nil { + logrus.Fatal(err) + } +} + +func run() error { + logrus.Info("certificates-test-harness::run") + + ctx := context.Background() + var err error + + k8sClient, err := buildKubernetesClient() + if err != nil { + return fmt.Errorf(`error building a Kubernetes client: %w`, err) + } + + certificates, err := certification.NewCertificates(ctx, k8sClient) + if err != nil { + return fmt.Errorf(`error occurred creating certificates: %w`, err) + } + + err = certificates.Initialize() + if err != nil { + return fmt.Errorf(`error occurred initializing certificates: %w`, err) + } + + go certificates.Run() + + <-ctx.Done() + return nil +} + +func buildKubernetesClient() (*kubernetes.Clientset, error) { + logrus.Debug("Watcher::buildKubernetesClient") + + var kubeconfig *string + var k8sConfig *rest.Config + + k8sConfig, err := rest.InClusterConfig() + if errors.Is(err, rest.ErrNotInCluster) { + if home := homedir.HomeDir(); home != "" { + path := filepath.Join(home, ".kube", "config") + kubeconfig = &path + + k8sConfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) + if err != nil { + return nil, fmt.Errorf(`error occurred building the kubeconfig: %w`, err) + } + } else { + return nil, fmt.Errorf(`not running in a Cluster: %w`, err) + } + } else if err != nil { + return nil, fmt.Errorf(`error occurred getting the Cluster config: %w`, err) + } + + client, err := kubernetes.NewForConfig(k8sConfig) + if err != nil { + return nil, fmt.Errorf(`error occurred creating a client: %w`, err) + } + return client, nil +} diff --git a/cmd/configuration-test-harness/doc.go b/cmd/configuration-test-harness/doc.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/cmd/configuration-test-harness/doc.go @@ -0,0 +1 @@ +package main diff --git a/cmd/configuration-test-harness/main.go b/cmd/configuration-test-harness/main.go new file mode 100644 index 0000000..56e8b5d --- /dev/null +++ b/cmd/configuration-test-harness/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "errors" + "fmt" + configuration2 "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/sirupsen/logrus" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + "path/filepath" +) + +func main() { + logrus.SetLevel(logrus.DebugLevel) + err := run() + if err != nil { + logrus.Fatal(err) + } +} + +func run() error { + logrus.Info("configuration-test-harness::run") + + ctx := context.Background() + var err error + + k8sClient, err := buildKubernetesClient() + if err != nil { + return fmt.Errorf(`error building a Kubernetes client: %w`, err) + } + + configuration, err := configuration2.NewSettings(ctx, k8sClient) + if err != nil { + return fmt.Errorf(`error occurred creating configuration: %w`, err) + } + + err = configuration.Initialize() + if err != nil { + return fmt.Errorf(`error occurred initializing configuration: %w`, err) + } + + go configuration.Run() + + <-ctx.Done() + + return err +} + +func buildKubernetesClient() (*kubernetes.Clientset, error) { + logrus.Debug("Watcher::buildKubernetesClient") + + var kubeconfig *string + var k8sConfig *rest.Config + + k8sConfig, err := rest.InClusterConfig() + if errors.Is(err, rest.ErrNotInCluster) { + if home := homedir.HomeDir(); home != "" { + path := filepath.Join(home, ".kube", "config") + kubeconfig = &path + + k8sConfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) + if err != nil { + return nil, fmt.Errorf(`error occurred building the kubeconfig: %w`, err) + } + } else { + return nil, fmt.Errorf(`not running in a Cluster: %w`, err) + } + } else if err != nil { + return nil, fmt.Errorf(`error occurred getting the Cluster config: %w`, err) + } + + client, err := kubernetes.NewForConfig(k8sConfig) + if err != nil { + return nil, fmt.Errorf(`error occurred creating a client: %w`, err) + } + return client, nil +} diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index c5891e2..6e6a8bc 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -19,6 +19,7 @@ import ( ) func main() { + logrus.SetLevel(logrus.DebugLevel) err := run() if err != nil { logrus.Fatal(err) @@ -44,6 +45,8 @@ func run() error { return fmt.Errorf(`error occurred initializing settings: %w`, err) } + go settings.Run() + synchronizerWorkqueue, err := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) if err != nil { return fmt.Errorf(`error occurred building a workqueue: %w`, err) @@ -71,7 +74,6 @@ func run() error { return fmt.Errorf(`error occurred initializing the watcher: %w`, err) } - go settings.Run() go handler.Run(ctx.Done()) go synchronizer.Run(ctx.Done()) diff --git a/cmd/tls-config-factory-test-harness/doc.go b/cmd/tls-config-factory-test-harness/doc.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/cmd/tls-config-factory-test-harness/doc.go @@ -0,0 +1 @@ +package main diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go new file mode 100644 index 0000000..3e9ef48 --- /dev/null +++ b/cmd/tls-config-factory-test-harness/main.go @@ -0,0 +1,229 @@ +package main + +import ( + "bufio" + "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/sirupsen/logrus" + "os" +) + +const ( + CaCertificateSecretKey = "nlk-tls-ca-secret" + ClientCertificateSecretKey = "nlk-tls-client-secret" +) + +type TlsConfiguration struct { + Description string + Settings configuration.Settings +} + +func main() { + logrus.SetLevel(logrus.DebugLevel) + + configurations := buildConfigMap() + + for name, settings := range configurations { + fmt.Print("\033[H\033[2J") + + logrus.Infof("\n\n\t*** Building TLS config for <<< %s >>>\n\n", name) + + tlsConfig, err := authentication.NewTlsConfig(&settings.Settings) + if err != nil { + panic(err) + } + + rootCaCount := 0 + certificateCount := 0 + + if tlsConfig.RootCAs != nil { + rootCaCount = len(tlsConfig.RootCAs.Subjects()) + } + + if tlsConfig.Certificates != nil { + certificateCount = len(tlsConfig.Certificates) + } + + logrus.Infof("Successfully built TLS config: \n\tDescription: %s \n\tRootCA count: %v\n\tCertificate count: %v", settings.Description, rootCaCount, certificateCount) + + bufio.NewReader(os.Stdin).ReadBytes('\n') + } + + fmt.Print("\033[H\033[2J") + logrus.Infof("\n\n\t*** All done! ***\n\n") +} + +func buildConfigMap() map[string]TlsConfiguration { + configurations := make(map[string]TlsConfiguration) + + configurations["ss-tls"] = TlsConfiguration{ + Description: "Self-signed TLS requires just a CA certificate", + Settings: ssTlsConfig(), + } + + configurations["ss-mtls"] = TlsConfiguration{ + Description: "Self-signed mTLS requires a CA certificate and a client certificate", + Settings: ssMtlsConfig(), + } + + configurations["ca-tls"] = TlsConfiguration{ + Description: "CA TLS requires no certificates", + Settings: caTlsConfig(), + } + + configurations["ca-mtls"] = TlsConfiguration{ + Description: "CA mTLS requires a client certificate", + Settings: caMtlsConfig(), + } + + return configurations +} + +func ssTlsConfig() configuration.Settings { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + return configuration.Settings{ + TlsMode: "ss-tls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } +} + +func ssMtlsConfig() configuration.Settings { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + return configuration.Settings{ + TlsMode: "ss-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } +} + +func caTlsConfig() configuration.Settings { + return configuration.Settings{ + TlsMode: "ca-tls", + } +} + +func caMtlsConfig() configuration.Settings { + certificates := make(map[string]map[string][]byte) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + return configuration.Settings{ + TlsMode: "ca-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } +} + +func caCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIDTzCCAjcCFA4Zdj3E9TdjOP48eBRDGRLfkj7CMA0GCSqGSIb3DQEBCwUAMGQx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0 +dGxlMQ4wDAYDVQQKDAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFu +Y2VzMB4XDTIzMDkyOTE3MTY1MVoXDTIzMTAyOTE3MTY1MVowZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxDjAMBgNV +BAoMBU5HSU5YMR4wHAYDVQQLDBVDb21tdW5pdHkgJiBBbGxpYW5jZXMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwlI4ZvJ/6hvqULFVL+1ZSRDTPQ48P +umehJhPz6xPhC9UkeTe2FZxm2Rsi1I5QXm/bTG2OcX775jgXzae9NQjctxwrz4Ks +LOWUvRkkfhQR67xk0Noux76/9GWGnB+Fapn54tlWql6uHQfOu1y7MCRkZ27zHbkk +lq4Oa2RmX8rIyECWgbTyL0kETBVJU8bYORQ5JjhRlz08inq3PggY8blrehIetrWN +dw+gzcqdvAI2uSCodHTHM/77KipnYmPiSiDjSDRlXdxTG8JnyIB78IoH/sw6RyBm +CvVa3ytvKziXAvbBoXq5On5WmMRF97p/MmBc53ExMuDZjA4fisnViS0PAgMBAAEw +DQYJKoZIhvcNAQELBQADggEBAJeoa2P59zopLjBInx/DnWn1N1CmFLb0ejKxG2jh +cOw15Sx40O0XrtrAto38iu4R/bkBeNCSUILlT+A3uYDila92Dayvls58WyIT3meD +G6+Sx/QDF69+4AXpVy9mQ+hxcofpFA32+GOMXwmk2OrAcdSkkGSBhZXgvTpQ64dl +xSiQ5EQW/K8LoBoEOXfjIZJNPORgKn5MI09AY7/47ycKDKTUU2yO8AtIHYKttw0x +kfIg7QOdo1F9IXVpGjJI7ynyrgsCEYxMoDyH42Dq84eKgrUFLEXemEz8hgdFgK41 +0eUYhAtzWHbRPBp+U/34CQoZ5ChNFp2YipvtXrzKE8KLkuM= +-----END CERTIFICATE----- +` +} + +func clientCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL +BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM +CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu +dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK +DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk +H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI +ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto +oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 +Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA +TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 +MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx +g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM +Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy +MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ +KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt +eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK +ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE +Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH +R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k +Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= +-----END CERTIFICATE----- +` +} + +// clientKeyPEM returns a PEM-encoded client key. +// Note: The key is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func clientKeyPEM() string { + return ` +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub +NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 +/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g +bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek +sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 +R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde +wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 +CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 +rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg +NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e +/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB +ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md +MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ +Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT +FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe +OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 +X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE +1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex +JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig +iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp +r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy +SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB +OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ +sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC +mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 +z/3KkMx4uqJXZyvQrmkolSg= +-----END PRIVATE KEY----- +` +} + +func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string][]byte { + return map[string][]byte{ + certification.CertificateKey: []byte(certificatePEM), + certification.CertificateKeyKey: []byte(keyPEM), + } +} + +func buildCaCertificateEntry(certificatePEM string) map[string][]byte { + return map[string][]byte{ + certification.CertificateKey: []byte(certificatePEM), + } +} diff --git a/deployments/deployment/configmap.yaml b/deployments/deployment/configmap.yaml index 8889baf..91522a6 100644 --- a/deployments/deployment/configmap.yaml +++ b/deployments/deployment/configmap.yaml @@ -1,8 +1,10 @@ apiVersion: v1 kind: ConfigMap data: - nginx-hosts: - "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + nginx-hosts: "https://192.168.96.207/api" + tls-mode: "ss-mtls" + ca-certificate: "nlk-tls-ca-secret" + client-certificate: "nlk-tls-client-secret" metadata: name: nlk-config - namespace: nlk + namespace: nlk \ No newline at end of file diff --git a/deployments/deployment/deployment.yaml b/deployments/deployment/deployment.yaml index 4c871c2..11fa61f 100644 --- a/deployments/deployment/deployment.yaml +++ b/deployments/deployment/deployment.yaml @@ -17,7 +17,8 @@ spec: spec: containers: - name: nginx-loadbalancer-kubernetes - image: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes:latest + image: ciroque/nginx-loadbalancer-kubernetes:dev-11 +# image: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes:125 imagePullPolicy: Always ports: - name: http diff --git a/deployments/rbac/clusterrole.yaml b/deployments/rbac/clusterrole.yaml index 9edf9e0..c50bed8 100644 --- a/deployments/rbac/clusterrole.yaml +++ b/deployments/rbac/clusterrole.yaml @@ -6,5 +6,5 @@ metadata: rules: - apiGroups: - "" - resources: ["services", "nodes", "configmaps"] + resources: ["services", "nodes", "configmaps", "secrets"] verbs: ["get", "watch", "list"] diff --git a/docs/DEMO/COMMANDS.md b/docs/DEMO/COMMANDS.md new file mode 100644 index 0000000..5cf7125 --- /dev/null +++ b/docs/DEMO/COMMANDS.md @@ -0,0 +1,111 @@ +# Demo commands + +## Creation / Updte / Deletion of ConfigMap + +Run the configuration test harness: + +```bash +go run cmd/configuration-test-harness/main.go +``` + +Run each of the following commands in turn and watch the output of the configuration test harness. + +Create: + +```bash +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + tls-mode: "ss-mtls" + ca-certificate: "nlk-tls-ca-secret" + client-certificate: "nlk-tls-client-secret" +EOF +``` + +Update: + +```bash +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api,http://10.1.1.6:9000/api" + tls-mode: "ss-mtls" + ca-certificate: "nlk-tls-ca-secret" + client-certificate: "nlk-tls-client-secret" +EOF +``` + +Delete: + +```bash +kubectl delete configmap nlk-config -n nlk +``` + +## Creation / Update / Deletion of Certificate Secrets + +Run the certificates test harness: + +```bash +go run cmd/certificates-test-harness/main.go +``` + +Run each of the following commands in turn and watch the output of the certificates test harness. + +Create: + +```bash +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURUekNDQWpjQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1HUXgKQ3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcFhZWE5vYVc1bmRHOXVNUkF3RGdZRFZRUUhEQWRUWldGMApkR3hsTVE0d0RBWURWUVFLREFWT1IwbE9XREVlTUJ3R0ExVUVDd3dWUTI5dGJYVnVhWFI1SUNZZ1FXeHNhV0Z1ClkyVnpNQjRYRFRJek1UQXdNakl6TURReU9Wb1hEVEl6TVRFd01USXpNRFF5T1Zvd1pERUxNQWtHQTFVRUJoTUMKVlZNeEV6QVJCZ05WQkFnTUNsZGhjMmhwYm1kMGIyNHhFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVgpCQW9NQlU1SFNVNVlNUjR3SEFZRFZRUUxEQlZEYjIxdGRXNXBkSGtnSmlCQmJHeHBZVzVqWlhNd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEc0xteW9vc2NKNXZQbmFER3FCZkozYmQxT2F0NXYKYjQwSjcrc0ROamxhUFNGRjdNZGJyN2JFOFhkRUNGOHZCYkE2MkUwTVJRYXJHMFl5aWJBOG05OFV5TVJ1R0VRTApSZkltZ2pHVWtXdkRmU0ViSkJLZ1RXay83ckJPeDVRY1lFVnlCZ2Z2SDZMWk5hampic0VFaUFjalVObkp3WkNpCjdIWjJDSVZ5NVhpUmlKWVZLbGZwN3QvYlIwTXRtWVNEMlh6L0RoZXBRMnpkQ25zNnpFK3Q5aGxINnorcThydUUKam45ZTJaUGNBeTlHc1lNVi9WSTdIK0ZyMVBaREJJNUhtb1pZeUo4MTVqcWhiQUxGeU00Z0x3V1JlSVJuek9aZApNeVV0QW8rM1Vic1ZwQ0NOTE9XSk1WeXVUUVV1U3QwOFlXNTFDeGtESlBUMDY0bFAyZXBLQ2RQcEFnTUJBQUV3CkRRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMUStGRzVxNDVSV0JFK1JwNkpwUlZWakphMFF6ZE44V1ljcWUwTDkKVlJPbG5aSkxva2JGNzJrOEdrZ0t3YzNVZ21IRzFMbmMvdzlTbFJ1Ti9uRkMySEtZbWxqWHkwaHpWaFZLbDRjRgozc1NKV0c2dzBkcXFNVmk0bDBybFVDQlk3cDBTQXA4eFdLd2ZoanE0NU1YSGlwNERRUzVTUXUrWjJiSCtTN3pzCjNxcGhDaEpkNkpGZUNnL0pGQ1VIWjZGRXk0ZitJcGl1bU01SENKakJUcHdEVUdLcHB3a2pPeWp6Ym1Vc1hEcUYKRkIvYzZ4MnluYjlROGQ4MG1oREV5V0dFWDhtbE1ycXNrblFZd2JTenkyUitudnFLOFp3NTFGbDRSUU96K1lPZAphMUlTemZKWkYwdEtqcittS0ZSODZXWm05VWNOT1JnWjQ1WjVRNWI1V1AvMVF6dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRRHNMbXlvb3NjSjV2UG4KYURHcUJmSjNiZDFPYXQ1dmI0MEo3K3NETmpsYVBTRkY3TWRicjdiRThYZEVDRjh2QmJBNjJFME1SUWFyRzBZeQppYkE4bTk4VXlNUnVHRVFMUmZJbWdqR1VrV3ZEZlNFYkpCS2dUV2svN3JCT3g1UWNZRVZ5Qmdmdkg2TFpOYWpqCmJzRUVpQWNqVU5uSndaQ2k3SFoyQ0lWeTVYaVJpSllWS2xmcDd0L2JSME10bVlTRDJYei9EaGVwUTJ6ZENuczYKekUrdDlobEg2eitxOHJ1RWpuOWUyWlBjQXk5R3NZTVYvVkk3SCtGcjFQWkRCSTVIbW9aWXlKODE1anFoYkFMRgp5TTRnTHdXUmVJUm56T1pkTXlVdEFvKzNVYnNWcENDTkxPV0pNVnl1VFFVdVN0MDhZVzUxQ3hrREpQVDA2NGxQCjJlcEtDZFBwQWdNQkFBRUNnZ0VBWWV3Tm1RMkVRSkxFNVZqSjNwaUFvd3VtQ2ZFOU1DNnI1MGJWeFlzaDFFd3MKRTNYTVlqTkVML3Q5VzNPdEl5M1VsMUUvQUt0TnpIdU9hejJ6R0MzNEhBSHhqMFA0VWtRNTFjVjlFUUFLRWc4NwpQcW1DSDN4NCtzelh4Skh5MHFFSHFmTGVMMEtLbmt3bExjYXB1RnM5dW1LM0tYTmJxSEVwM0Y1RUZoTVdIaUFiClV4cWpYZXJKWXBZMXU3RVhwUHFWaSt3dFhkWDNuT092bStZMzNLemRGNTErTXBpOHVkMGpTY0p2VEtIYXpKV1oKZE40aFF4SEpEOGpMZm8zNDRNWEFYQndkWFVuN1V4NW9qU0dVemtnU1pKTnBkQVNaUU5lN2E1Qk4xalREZ29LaQp0My9kS0NFTmFsMGdUQVI1MFArYWJxeWhSOFUxbVVuT1dsWDN1ekJsa1FLQmdRRDNFS09tWm16Z293Nk5mOWp2CmpkdzJOUUQ1Q2NlWEk4cWlxRjlMNWZoVWV0b29PajA2WlZpYjBEejNWcWlqVnRITFBQV2pRQ2ZwWC9DVHdkUTkKajNHTVBDOEhIZWJkeEZqTHpRSWlDUXAvaGFlc2RGTUhHRzFBak92VDRNdkxOaklWM29JOCszZjhIWk81Q0pYQgpJdW1xcG82YWJhV094VEhDS2pYY2YxNTI0d0tCZ1FEMHVRWGNJWlJrNjdrYU5PZWhjLzFuU2QyTVJSVG1zV3JYClFjTEtQczdWVFkwWlA1T2hsU1pRVkM2MW9PUWtuZnFScGhVbnJVVDF1NXFRbzJUSGhGcmN4a3Qrd1hCenhDWDgKUXZXY1RTTmkvZ0Y2Rkx1OEsyYXlOVlBQTDFNQjJ4ZGxTL0Z0SDRLSG5RWHM1NFM5MVZHRTR5OUd6aDIwWEcwOQp4K0FiQzlPM3d3S0JnSGNuWURXbFdrY3dmSmxEbW1WV0htbEtRTkRhcFphLzNUOTdRcEtCTTdYU2xob21sRmJ3CmY3Nk52SWx4RXQzTHhseGxadlkzdjhmdXpFRUdqd3l0Zkk2c2krVzd4eGNYVmRmY1pIWHp0RXR5TXo2WnoxMHgKcTZjaEQ2OWMwQXlPYzdOV1g2dDNnQk5vVkZFOTBiT1cyZWpDY1M0TFNYaEVwRTNITzdpKytOa1BBb0dBZW9CYgo1Sk9TbXVvOG9GZTNVMlNpaHArOUhVZy9iRE9IamZWSE1zSTUreUIwN3h5YUpCcHJNVzdTYXV6OUJ5OWxqSjhjCm05M3FWUy94OFZFNVUzNTNsV2hWeGovQ3NOQ1JTek9oaXZvNktvV0g2N3FSTjJKcVorNjE0MUtITkxpZGY0R0MKZXVONURiV1dqNzVjL2tIWUtyTW1xVVRvTGE3T3FFeHpiRmFCUnMwQ2dZQUN6UlBiQTBqK0JyOWpxenFKYU56VQpPZzJ4aUFhZlNuTzBxTkREZWZNdXhZSnFMZ0llMG9HRUExb1JlcDhvTDNDeWZYd2h3U2xqMlRjeGZZTFR5MENKCmZBejF4QjY3SUF3cGlvZnI0K1ZXekF4SC9BbnlhVGNZVGdtakdKdlhtZ3l1RGU3ZGJsa3lJU1M5ZTBLc1JzeTYKQ2hPdVZoQy96bjErbm9BMkNsdzFJdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-ca-secret + namespace: nlk +type: kubernetes.io/tls +EOF +``` + +Update: + +```bash +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsekNDQTMrZ0F3SUJBZ0lVUFRrVExlS3FNQlRlNFZhQXpiL1hXcSt6THRBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eEVEQU9CZ05WQkFjTQpCMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0ZFc1cGRIa2dKaUJCCmJHeHBZVzVqWlhNd0hoY05Nak14TURBeU1qTXdPRFEyV2hjTk1qUXhNREF4TWpNd09EUTJXakI3TVFzd0NRWUQKVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLVjJGemFHbHVaM1J2YmpFUU1BNEdBMVVFQnd3SFUyVmhkSFJzWlRFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEhqQWNCZ05WQkFzTUZVTnZiVzExYm1sMGVTQW1JRUZzYkdsaGJtTmxjekVWCk1CTUdBMVVFQXd3TWJYbGtiMjFoYVc0dVkyOXRNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUIKQ2dLQ0FRRUF2RjJSVklsVEQvQWVSQkZSNTNrb1dlZmlhQ09keVJvWnJRSm04RGZJdk5vQ2pTdkNPUEhzZ2VSSQptcmhxcEhkc2FSK2RqSjNRbzFKemljeS9YNHgxRy9SSGRNM0d3OXNuTS9jdHBvb1FSZUtFTi90T1FnVnBMaEZQClE4K3cvaDVRSGZWWFFQcFdyeGhjTkFiK1hrbzk5SVBndE81blNvazExK2pEOGhJSVlTTUEvVElwUFBPeXNKdnkKOUhKUjBEY3pqWE1VQTR0dGphYTZ0cm8xNm81WEZsdDZPM251YTFkc0VLYVRJQmhWbXI4ZGJWVFd2c1VNYTV6WgpyR0pSa25Bc01RV1lxSVBBVG1MdXlaeHlYTG1WQkp4U1FzWTZqYVllLzcreXZGdUVCUTZ4MDByZ1Z0THpzWi9NCitZSHg2VUVXRFoxYzZuZWxxdlFja1FvSzdTd3VLUUlEQVFBQm80SUJLRENDQVNRd0NRWURWUjBUQkFJd0FEQUwKQmdOVkhROEVCQU1DQmVBd1NBWURWUjBSQkVFd1A0SU1iWGxrYjIxaGFXNHVZMjl0Z2hOelpYSjJaWEl1YlhsawpiMjFoYVc0dVkyOXRnZzRxTG0xNVpHOXRZV2x1TG1OdmJZY0VDZ0FBQ29jRUNnQUFDekFUQmdOVkhTVUVEREFLCkJnZ3JCZ0VGQlFjREFUQWRCZ05WSFE0RUZnUVVkOUVieTR0TDNZSjhqSTd6RzVKR2p5TW5vVDR3Z1lzR0ExVWQKSXdTQmd6Q0JnS0ZvcEdZd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eApFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0CmRXNXBkSGtnSmlCQmJHeHBZVzVqWlhPQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFCRFY0OWtOanpCZmpPMjlNWVd1ZFlMNktzYlFrdlFDdzZ3dEF6cVJ2Smd5WEtBbXYzcwp6OFJjZHFNRTZ2bXdWZUxpS1ZDeDFSOXY1dlhBS0hMNmFSSi9QMk1QUm1Ic0pNM2MxSVF2NjAzVVYzaCtQL3JICll3QTVVRzBCMXlMaWs3N3RIdWZtRGFKdlRZMGpvcWJ0cVEwU1ByWjh6TGJQdmo3VTc1bStHWEx5VEp0UnRjdDUKSVJTblg0NlZxbWs0Q1l2dW1SaDJJTy8yUHpKNkpUbzA5VFpEOGpDbyttbHIzSXhXOHBsRDBYSkQ1YXY4cEQ2dApDZ2xnR2FyakRWSmVCQUZobkVIWStYelZDYzJVSktVblNiaDVWUVRLdno2dCtCWTlldi9LOFJGYlcxWVd3N1Q1CmQ0UzEyaC94cUpXcUJOdXFJeldkMTMvbTZaaHoraHorb0VvRQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzhYWkZVaVZNUDhCNUUKRVZIbmVTaFo1K0pvSTUzSkdobXRBbWJ3TjhpODJnS05LOEk0OGV5QjVFaWF1R3FrZDJ4cEg1Mk1uZENqVW5PSgp6TDlmakhVYjlFZDB6Y2JEMnljejl5Mm1paEJGNG9RMyswNUNCV2t1RVU5RHo3RCtIbEFkOVZkQStsYXZHRncwCkJ2NWVTajMwZytDMDdtZEtpVFhYNk1QeUVnaGhJd0Q5TWlrODg3S3dtL0wwY2xIUU56T05jeFFEaTIyTnBycTIKdWpYcWpsY1dXM283ZWU1clYyd1FwcE1nR0ZXYXZ4MXRWTmEreFF4cm5ObXNZbEdTY0N3eEJaaW9nOEJPWXU3SgpuSEpjdVpVRW5GSkN4anFOcGg3L3Y3SzhXNFFGRHJIVFN1Qlcwdk94bjh6NWdmSHBRUllOblZ6cWQ2V3E5QnlSCkNncnRMQzRwQWdNQkFBRUNnZ0VBQnRtRGh6dnhKeU5pV1FqNk0xSjVaYW81Z1BPZW5CUC9aVnV3MWtFVHVIa0QKQ1JMVGRCSlVEb3NqR3NFNXNONWVOUVVVZG1zU0RiRUVzNDVHL1VOVWRONUdyNWdyWEs3bzk0cExBTmlFd1UzUgo1TFBybU1TdFJQZ1YxaytaK09aWVNudnVudFhHUzRWZ0ZrenFJMlZNRXlBQ2pxeHhXWFFHTkdJL1VkdXNXS3FICkNlOWNGMXp0UGxBWkVIRU9JNTJEcVFZK3FhUUtNSjVIT3RNS1F6SzY5MGF0VUJmbWZxUCsxYTh4Vm1VV1cyaVgKNWpuN3IzeDRJdENwSlpwTHM2SzZRS29aNU9qd25nSTc4MkxuQ3V0cC9NZmg5RmVYSlFRRVppYXMzOW44c29NcwpCN0ZKQnpKalQwVlQxR2hVWnNwSUhra1AwR3pBQTd2Y3MwNitDQWc5Z1FLQmdRRG1CYTQ3MnlLYmwvZUIyY3BFCjdZeXVmaHk1M093OTFzNkkwYnQxYWZyajNvcHExV1VVWUJiOWhIUm1CRXkzVVE2V1JoenltdG1MdXBRTWh4enkKeThSMVFtNW1RUTI1T3lsNGl3TFZQdVE3Ty94dzh6UUdNR1pBY08zQ3NuYUM5Y3YxZFpnZzcvQ1BUTlpsY2RxZAp2OXFqNnpwVHFrT21OVnlFUGxPeFI3aHJ5UUtCZ1FEUm80WVNwcDRONHRlSWhIaG1uZGpYaVZkSDYyM3oxQldICnV1U3M0L0M2RDVjaUhWRTJYZnZ2cnQ4eEhBbEVycGtoaVFUZ1JzZHVwblVhVVFodUtaT1RNTXVGcDAzblA1cmMKUjlxY1MxQUVQUXZMVFNZN0dqaEptaExEVUM0WktGYmo4bXhkSWVBK09YL21mank4SFJTMWYxYncwR3owZCtvNQozVnlyOUU0ZllRS0JnUUNuY1QwckwxTGJCdDNTZFpMcmFDMC9uR2dXMkg1VWFia0JHZ09tN2hZSHFLa0VLZ0VoCnV1MGhjVGsyUml6K1NSQWdUanVtVXhqSHdYTWlSM3pJTlpMMmRQeGVqVDZMTjBqeUNlZHZDaEFrR24raVRUZnkKeFdxNXdEc2p2cnZNaTFjRWdLelVWVFc5YXdhcTVCMXJOZ3pYeEZVNk1EaDhsbDJabXJGYjNNU2dHUUtCZ0F1cQpmT2lHeXg3Y3M3L09GMkVtZ1kyay8rMXBwWW0vRUorbi85ZTdLNGMvSE5yeUpMWFF6eGRNZFBFbnJVQmNNdnRSCnc2cXpaWis3dGFLTVJkclRoM25XYWt6NnZYUVQ3d3M1R0dwQUtxakJ1T2xNVnNkTk16cXRUMFA5TDBPSklpUzMKTmQ2TTV3eXZhSFdzS3JjUkt6amFhRDBvYkJmQ29JOHR5VjFzVC9paEFvR0FWNHJzMU14d2VkRVVPTFo4Y0ZBQgpvYTE1MG41dVgwUGQ1Nm92enRXNHZoR2JyOVpjQ294dnR0a0VqQUNvTzdyanhBb1JyYVk1U1A0WTdHaHJxb043CkZYeFYrVVh0aGxyU21SQWUzS25ndzA4bkRpT0Fia0NZdFlGVGs3d3d3V0xYU2VYUWRCdUx1aWRqZ05PL2RhbkkKczUzVXU3OGlnRVdPa21YWThGYm1OVEU9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-ca-secret + namespace: nlk +type: kubernetes.io/tls +EOF +``` + +Delete: + +```bash +kubectl delete secret nlk-tls-ca-secret -n nlk +``` + +## Generation of TLS Configuration based on ConfigMap and Certificate Secrets + +Run the TLS configuration test harness: + +```bash +go run cmd/tls-configuration-test-harness/main.go +``` + +The test harness will iterate through the tls modes and show basic information about the generated TLS configuration. diff --git a/docs/DEMO/SLIDE-1.md b/docs/DEMO/SLIDE-1.md new file mode 100644 index 0000000..50766a2 --- /dev/null +++ b/docs/DEMO/SLIDE-1.md @@ -0,0 +1,5 @@ +# TLS in NLK + +NGINX Loadbalancer for Kubernetes, or NLK, is a Kubernetes Controller that uses NGINX Plus to load balance traffic to Kubernetes Services. + +[Next](SLIDE-2.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-2.md b/docs/DEMO/SLIDE-2.md new file mode 100644 index 0000000..74f6d97 --- /dev/null +++ b/docs/DEMO/SLIDE-2.md @@ -0,0 +1,9 @@ +## What is TLS? + +Transport Layer Security, or TLS, is a cryptographic protocol that provides end-to-end security of data sent between applications over the Internet; TLS is the successor to Secure Sockets Layer, or SSL. + +TLS serves two primary purposes: +* Authentications of actors in a communication channel; +* Encryption of data sent between actors in a communication channel; + +[Next](SLIDE-3.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-3.md b/docs/DEMO/SLIDE-3.md new file mode 100644 index 0000000..c9dcdf2 --- /dev/null +++ b/docs/DEMO/SLIDE-3.md @@ -0,0 +1,11 @@ +## TLS uses Certificates + +A Transport Layer Security (TLS) certificate, also known as an SSL certificate (Secure Sockets Layer), is a digital document that plays a crucial role in securing internet communications. Imagine it as a special, electronic passport for websites. + +A TLS certificate includes a "Chain of Trust" where there are multiple certificates that are used to verify the authenticity of the certificate. The certificate at the top of the chain is called the Root Certificate Authority (CA) Certificate. The Root CA Certificate is used to sign the certificates below it in the chain. The Root CA Certificate is not signed by any other certificate in the chain. + +The Root CA Certificate is used to sign the Intermediate CA Certificate. The Intermediate CA Certificate is used to sign the TLS Certificate. The TLS Certificate is used to sign the TLS Certificate Signing Request (CSR). The TLS Certificate Signing Request is used to sign the TLS Certificate. + +Root CA certificates can be expensive and are not required for most use cases. An alternative to purchasing an Intermediate CA certificate is to use a self-signed certificate. Self-signed certificates are free and can be used to sign TLS certificates. + +[Next](SLIDE-4.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-4.md b/docs/DEMO/SLIDE-4.md new file mode 100644 index 0000000..c5bc336 --- /dev/null +++ b/docs/DEMO/SLIDE-4.md @@ -0,0 +1,43 @@ +## One-way TLS and Mutual TLS + +There are two types of TLS: one-way TLS and mutual TLS. + +### One-way TLS + +One-way TLS is the most common type of TLS. In one-way TLS, the client verifies the server's identity, but the server does not verify the client's identity. One-way TLS is used to secure the connection between the client and the server. + +### Mutual TLS + +Mutual TLS is less common than one-way TLS. In mutual TLS, the client verifies the server's identity, and the server verifies the client's identity. Mutual TLS is used to secure the connection between the client and the server. + +The following diagram shows the difference between one-way TLS and mutual TLS. + +```mermaid +graph LR +CACertificate[CA Certificate] + + subgraph "One-way TLS" + NGINXPlusCert[NGINX Plus Certificate] + NLK[nginx-loadbalancer-kubernetes] + NGINXPlus[NGINX Plus] + NGINXPlusCert -->|Used by| NLK + NLK -->|Verifies| NGINXPlus + end + + subgraph "Mutual TLS" + NLKCert[NLK Certificate] + MNGINXPlusCert[NGINX Plus Certificate] + MNLK[nginx-loadbalancer-kubernetes] + MNGINXPlus[NGINX Plus] + NLKCert -->|Used by| MNGINXPlus + MNGINXPlus -->|Verifies| MNLK + MNGINXPlusCert -->|Used by| MNLK + MNLK -->|Verifies| MNGINXPlus + end + +CACertificate -->|Used for Signing| NLKCert +CACertificate -->|Used for Signing| NGINXPlusCert +CACertificate -->|Used for Signing| MNGINXPlusCert +``` + +[Next](SLIDE-5.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-5.md b/docs/DEMO/SLIDE-5.md new file mode 100644 index 0000000..fb0526a --- /dev/null +++ b/docs/DEMO/SLIDE-5.md @@ -0,0 +1,22 @@ +## TLS in NLK + +NLK supports three options for securing communications between the NLK and NGINX Plus: + +1. No TLS +2. TLS with self-signed certificates +3. TLS with certificates signed by a Certificate Authority (CA) + +Within the TLS options there are two sub-options: + +1. One-way TLS +2. Mutual TLS + +This gives five options for securing communications between the NLK and NGINX Plus. + +* No TLS: No authentication nor encryption is used. +* One-way TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. +* One-way TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. +* Mutual TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. +* Mutual TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. + +[Next](SLIDE-6.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-6.md b/docs/DEMO/SLIDE-6.md new file mode 100644 index 0000000..542f226 --- /dev/null +++ b/docs/DEMO/SLIDE-6.md @@ -0,0 +1,12 @@ +### Configuring TLS in NLK + +NLK uses a Kubernetes ConfigMap to configure TLS. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. + +There are three fields in the ConfigMap that are used to configure TLS: +* tls-mode: The TLS mode to use. Valid values are `none`, `ss-tls`, `ss-mtls`, `ca-tls`, and `ca-mtls`. +* ca-certificate: The CA certificate to use. This field is only used when `tls-mode` is set to `ss-tls` or `ss-mtls`. This certificate contains the "Chain of Trust" that is used to verify the authenticity of the TLS certificate. +* client-certificate: The client certificate to use. This field is only used when `tls-mode` is set to `ss-mtls` or `ca-mtls`. This certificate is provided to the NGINX Plus hosts for client authentication. + +The fields required depend on the `tls-mode` value. + +[Next](SLIDE-7.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-7.md b/docs/DEMO/SLIDE-7.md new file mode 100644 index 0000000..498494f --- /dev/null +++ b/docs/DEMO/SLIDE-7.md @@ -0,0 +1,5 @@ +### How NLK uses TLS + +NLK uses the `github.com/nginxinc/nginx-plus-go-client` to communicate with the NGINX Plus API. This library accepts a low-level HTTP client that is used for the actual communication with the NGINX Plus hosts. NLK generates a TLS Configuration based on the configured `tls-mode` and provides it to the low-level HTTP client. + +[Next](SLIDE-8.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-8.md b/docs/DEMO/SLIDE-8.md new file mode 100644 index 0000000..796e048 --- /dev/null +++ b/docs/DEMO/SLIDE-8.md @@ -0,0 +1,2 @@ +# Demo + diff --git a/docs/tls/CA-MTLS.md b/docs/tls/CA-MTLS.md index 6692739..5f3be73 100644 --- a/docs/tls/CA-MTLS.md +++ b/docs/tls/CA-MTLS.md @@ -42,7 +42,7 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, the `tlsMode` and `clientCertificate` fields need to be included. The `tlsMode` field should be set to `ca-mtls` +For this mode, the `tls-mode` and `clientCertificate` fields need to be included. The `tls-mode` field should be set to `ca-mtls` and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -55,7 +55,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "ca-mtls" + tls-mode: "ca-mtls" clientCertificate: "nlk-tls-client-secret" ``` diff --git a/docs/tls/CA-TLS.md b/docs/tls/CA-TLS.md index ed828cc..379b532 100644 --- a/docs/tls/CA-TLS.md +++ b/docs/tls/CA-TLS.md @@ -33,7 +33,7 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, only the `tlsMode` fields needs to be included. The `tlsMode` field should be set to `ca-tls`. +For this mode, only the `tls-mode` fields needs to be included. The `tls-mode` field should be set to `ca-tls`. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -46,7 +46,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "ca-tls" + tls-mode: "ca-tls" ``` ## Deployment diff --git a/docs/tls/FIFTY-THOUSAND-FOOT-TLDR.md b/docs/tls/FIFTY-THOUSAND-FOOT-TLDR.md new file mode 100644 index 0000000..729ff20 --- /dev/null +++ b/docs/tls/FIFTY-THOUSAND-FOOT-TLDR.md @@ -0,0 +1,102 @@ +# TLS in NLK + +NGINX Loadbalancer for Kubernetes, or NLK, is a Kubernetes Controller that uses NGINX Plus to load balance traffic to Kubernetes Services. + +## What is TLS? + +Transport Layer Security, or TLS, is a cryptographic protocol that provides end-to-end security of data sent between applications over the Internet; TLS is the successor to Secure Sockets Layer, or SSL[^1]. + +TLS serves two primary purposes: +* Authentications of actors in a communication channel; +* Encryption of data sent between actors in a communication channel; + +## TLS uses Certificates + +A Transport Layer Security (TLS) certificate, also known as an SSL certificate (Secure Sockets Layer), is a digital document that plays a crucial role in securing internet communications. Imagine it as a special, electronic passport for websites. + +A TLS certificate includes a "Chain of Trust" where there are multiple certificates that are used to verify the authenticity of the certificate. The certificate at the top of the chain is called the Root Certificate Authority (CA) Certificate. The Root CA Certificate is used to sign the certificates below it in the chain. The Root CA Certificate is not signed by any other certificate in the chain. + +The Root CA Certificate is used to sign the Intermediate CA Certificate. The Intermediate CA Certificate is used to sign the TLS Certificate. The TLS Certificate is used to sign the TLS Certificate Signing Request (CSR). The TLS Certificate Signing Request is used to sign the TLS Certificate. + +Root CA certificates can be expensive and are not required for most use cases. An alternative to purchasing an Intermediate CA certificate is to use a self-signed certificate. Self-signed certificates are free and can be used to sign TLS certificates. + +## One-way TLS and Mutual TLS + +There are two types of TLS: one-way TLS and mutual TLS. + +### One-way TLS + +One-way TLS is the most common type of TLS. In one-way TLS, the client verifies the server's identity, but the server does not verify the client's identity. One-way TLS is used to secure the connection between the client and the server. + +### Mutual TLS + +Mutual TLS is less common than one-way TLS. In mutual TLS, the client verifies the server's identity, and the server verifies the client's identity. Mutual TLS is used to secure the connection between the client and the server. + +The following diagram shows the difference between one-way TLS and mutual TLS. + +```mermaid +graph LR +CACertificate[CA Certificate] + + subgraph "One-way TLS" + NGINXPlusCert[NGINX Plus Certificate] + NLK[nginx-loadbalancer-kubernetes] + NGINXPlus[NGINX Plus] + NGINXPlusCert -->|Used by| NLK + NLK -->|Verifies| NGINXPlus + end + + subgraph "Mutual TLS" + NLKCert[NLK Certificate] + MNGINXPlusCert[NGINX Plus Certificate] + MNLK[nginx-loadbalancer-kubernetes] + MNGINXPlus[NGINX Plus] + NLKCert -->|Used by| MNGINXPlus + MNGINXPlus -->|Verifies| MNLK + MNGINXPlusCert -->|Used by| MNLK + MNLK -->|Verifies| MNGINXPlus + end + +CACertificate -->|Used for Signing| NLKCert +CACertificate -->|Used for Signing| NGINXPlusCert +CACertificate -->|Used for Signing| MNGINXPlusCert +``` + +## TLS in NLK + +NLK supports three options for securing communications between the NLK and NGINX Plus: + +1. No TLS +2. TLS with self-signed certificates +3. TLS with certificates signed by a Certificate Authority (CA) + +Within the TLS options there are two sub-options: + +1. One-way TLS +2. Mutual TLS + +This gives five options for securing communications between the NLK and NGINX Plus. + +* No TLS: No authentication nor encryption is used. +* One-way TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. +* One-way TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. +* Mutual TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. +* Mutual TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. + +### Configuring TLS in NLK + +NLK uses a Kubernetes ConfigMap to configure TLS. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. + +There are three fields in the ConfigMap that are used to configure TLS: +* tls-mode: The TLS mode to use. Valid values are `none`, `ss-tls`, `ss-mtls`, `ca-tls`, and `ca-mtls`. +* ca-certificate: The CA certificate to use. This field is only used when `tls-mode` is set to `ss-tls` or `ss-mtls`. This certificate contains the "Chain of Trust" that is used to verify the authenticity of the TLS certificate. +* client-certificate: The client certificate to use. This field is only used when `tls-mode` is set to `ss-mtls` or `ca-mtls`. This certificate is provided to the NGINX Plus hosts for client authentication. + +The fields required depend on the `tls-mode` value. + +### How NLK uses TLS + +NLK uses the `github.com/nginxinc/nginx-plus-go-client` to communicate with the NGINX Plus API. This library accepts a low-level HTTP client that is used for the actual communication with the NGINX Plus hosts. NLK generates a TLS Configuration based on the configured `tls-mode` and provides it to the low-level HTTP client. + + +[^1]: Source: [TLS Basics](https://www.internetsociety.org/deploy360/tls/basics/) \ No newline at end of file diff --git a/docs/tls/NO-TLS.md b/docs/tls/NO-TLS.md index 8831c84..1a4b7c8 100644 --- a/docs/tls/NO-TLS.md +++ b/docs/tls/NO-TLS.md @@ -21,7 +21,7 @@ No Kubernetes Secrets are required for this mode. NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, only the `tlsMode` field needs to be included, and should be set to `no-tls` (or omitted altogether as this is the default mode). +For this mode, only the `tls-mode` field needs to be included, and should be set to `no-tls` (or omitted altogether as this is the default mode). The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints) @@ -33,7 +33,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "no-tls" + tls-mode: "no-tls" ``` ## Deployment diff --git a/docs/tls/SS-MTLS.md b/docs/tls/SS-MTLS.md index c716889..bab97b0 100644 --- a/docs/tls/SS-MTLS.md +++ b/docs/tls/SS-MTLS.md @@ -45,9 +45,9 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, the `tlsMode`, `caCertificates`, and `clientCertificate` fields need to be included. The `tlsMode` field should be set to `ss-mtls`, -the `caCertificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above, -and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. +For this mode, the `tls-mode`, `ca-certificates`, and `client-certificate` fields need to be included. The `tls-mode` field should be set to `ss-mtls`, +the `ca-certificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above, +and the `client-certificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -59,9 +59,9 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "ss-mtls" - caCertificate: "nlk-tls-ca-secret" - clientCertificate: "nlk-tls-client-secret" + tls-mode: "ss-mtls" + ca-certificate: "nlk-tls-ca-secret" + client-certificate: "nlk-tls-client-secret" ``` ## Deployment diff --git a/docs/tls/SS-TLS.md b/docs/tls/SS-TLS.md index 7b92039..8f37d90 100644 --- a/docs/tls/SS-TLS.md +++ b/docs/tls/SS-TLS.md @@ -38,8 +38,8 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, both the `tlsMode` and `caCertificates` fields need to be included. The `tlsMode` field should be set to `ss-tls`, -and the `caCertificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above. +For this mode, both the `tls-mode` and `ca-certificates` fields need to be included. The `tls-mode` field should be set to `ss-tls`, +and the `ca-certificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -52,7 +52,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "ss-tls" + tls-mode: "ss-tls" caCertificate: "nlk-tls-ca-secret" ``` diff --git a/go.mod b/go.mod index 9cfca92..65fe627 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -26,12 +27,16 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum index 47e46ca..b3e6449 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -128,6 +130,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -159,6 +163,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -166,6 +172,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/internal/authentication/doc.go b/internal/authentication/doc.go new file mode 100644 index 0000000..109255e --- /dev/null +++ b/internal/authentication/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +/* +Package authentication includes functionality to secure communications between NLK and NGINX Plus hosts. +*/ + +package authentication diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go new file mode 100644 index 0000000..21b3458 --- /dev/null +++ b/internal/authentication/factory.go @@ -0,0 +1,115 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + * + * Factory for creating tls.Config objects based on the provided `tls-mode`. + */ + +package authentication + +import ( + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/sirupsen/logrus" +) + +func NewTlsConfig(settings *configuration.Settings) (*tls.Config, error) { + logrus.Debugf("Creating TLS config for mode: '%s'", settings.TlsMode) + switch settings.TlsMode { + case "ss-tls": // needs ca cert + return buildSelfSignedTlsConfig(settings.Certificates) + + case "ss-mtls": // needs ca cert and client cert + return buildSelfSignedMtlsConfig(settings.Certificates) + + case "ca-tls": // needs nothing + return buildBasicTlsConfig(false), nil + + case "ca-mtls": // needs client cert + return buildCaTlsConfig(settings.Certificates) + + default: // no-tls, needs nothing + return buildBasicTlsConfig(true), nil + } +} + +func buildSelfSignedTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { + logrus.Debug("Building self-signed TLS config") + certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) + if err != nil { + return nil, err + } + + return &tls.Config{ + InsecureSkipVerify: false, + RootCAs: certPool, + }, nil +} + +func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.Config, error) { + logrus.Debug("buildSelfSignedMtlsConfig Building self-signed mTLS config") + certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) + if err != nil { + return nil, err + } + + certificate, err := buildCertificates(certificates.GetClientCertificate()) + if err != nil { + return nil, err + } + logrus.Debugf("buildSelfSignedMtlsConfig Certificate: %v", certificate) + + return &tls.Config{ + InsecureSkipVerify: false, + RootCAs: certPool, + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{certificate}, + }, nil +} + +func buildBasicTlsConfig(skipVerify bool) *tls.Config { + logrus.Debug("Building basic TLS config") + return &tls.Config{ + InsecureSkipVerify: skipVerify, + } +} + +func buildCaTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { + logrus.Debug("Building CA TLS config") + certificate, err := buildCertificates(certificates.GetClientCertificate()) + if err != nil { + return nil, err + } + + return &tls.Config{ + InsecureSkipVerify: false, + Certificates: []tls.Certificate{certificate}, + }, nil +} + +func buildCertificates(privateKeyPEM []byte, certificatePEM []byte) (tls.Certificate, error) { + logrus.Debug("Building certificates") + return tls.X509KeyPair(certificatePEM, privateKeyPEM) +} + +func buildCaCertificatePool(caCert []byte) (*x509.CertPool, error) { + logrus.Debugf("Building CA certificate pool") + block, _ := pem.Decode(caCert) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block containing CA certificate") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("error parsing certificate: %w", err) + } + + caCertPool := x509.NewCertPool() + caCertPool.AddCert(cert) + + return caCertPool, nil +} diff --git a/internal/authentication/factory_test.go b/internal/authentication/factory_test.go new file mode 100644 index 0000000..d106eb7 --- /dev/null +++ b/internal/authentication/factory_test.go @@ -0,0 +1,444 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +package authentication + +import ( + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "testing" +) + +const ( + CaCertificateSecretKey = "nlk-tls-ca-secret" + ClientCertificateSecretKey = "nlk-tls-client-secret" +) + +func TestTlsFactory_EmptyStringModeDefaultsToNoTls(t *testing.T) { + settings := configuration.Settings{ + TlsMode: "", + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != true { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be true`) + } +} + +func TestTlsFactory_UnspecifiedModeDefaultsToNoTls(t *testing.T) { + settings := configuration.Settings{} + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != true { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be true`) + } +} + +func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-tls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != false { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) + } + + if len(tlsConfig.Certificates) != 0 { + t.Fatalf(`tlsConfig.Certificates should be empty`) + } + + if tlsConfig.RootCAs == nil { + t.Fatalf(`tlsConfig.RootCAs should not be nil`) + } +} + +func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-tls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "failed to decode PEM block containing CA certificate" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificateDataPEM()) + + settings := configuration.Settings{ + TlsMode: "ss-tls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "error parsing certificate: x509: inner and outer signature algorithm identifiers don't match" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != false { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) + } + + if len(tlsConfig.Certificates) == 0 { + t.Fatalf(`tlsConfig.Certificates should not be empty`) + } + + if tlsConfig.RootCAs == nil { + t.Fatalf(`tlsConfig.RootCAs should not be nil`) + } +} + +func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "failed to decode PEM block containing CA certificate" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "tls: failed to find any PEM data in certificate input" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +func TestTlsFactory_CaTlsMode(t *testing.T) { + settings := configuration.Settings{ + TlsMode: "ca-tls", + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != false { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) + } + + if len(tlsConfig.Certificates) != 0 { + t.Fatalf(`tlsConfig.Certificates should be empty`) + } + + if tlsConfig.RootCAs != nil { + t.Fatalf(`tlsConfig.RootCAs should be nil`) + } +} + +func TestTlsFactory_CaMtlsMode(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ca-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != false { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) + } + + if len(tlsConfig.Certificates) == 0 { + t.Fatalf(`tlsConfig.Certificates should not be empty`) + } + + if tlsConfig.RootCAs != nil { + t.Fatalf(`tlsConfig.RootCAs should be nil`) + } +} + +func TestTlsFactory_CaMtlsModeClientCertificateError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ca-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "tls: failed to find any PEM data in certificate input" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +// caCertificatePEM returns a PEM-encoded CA certificate. +// Note: The certificate is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func caCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIDTzCCAjcCFA4Zdj3E9TdjOP48eBRDGRLfkj7CMA0GCSqGSIb3DQEBCwUAMGQx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0 +dGxlMQ4wDAYDVQQKDAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFu +Y2VzMB4XDTIzMDkyOTE3MTY1MVoXDTIzMTAyOTE3MTY1MVowZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxDjAMBgNV +BAoMBU5HSU5YMR4wHAYDVQQLDBVDb21tdW5pdHkgJiBBbGxpYW5jZXMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwlI4ZvJ/6hvqULFVL+1ZSRDTPQ48P +umehJhPz6xPhC9UkeTe2FZxm2Rsi1I5QXm/bTG2OcX775jgXzae9NQjctxwrz4Ks +LOWUvRkkfhQR67xk0Noux76/9GWGnB+Fapn54tlWql6uHQfOu1y7MCRkZ27zHbkk +lq4Oa2RmX8rIyECWgbTyL0kETBVJU8bYORQ5JjhRlz08inq3PggY8blrehIetrWN +dw+gzcqdvAI2uSCodHTHM/77KipnYmPiSiDjSDRlXdxTG8JnyIB78IoH/sw6RyBm +CvVa3ytvKziXAvbBoXq5On5WmMRF97p/MmBc53ExMuDZjA4fisnViS0PAgMBAAEw +DQYJKoZIhvcNAQELBQADggEBAJeoa2P59zopLjBInx/DnWn1N1CmFLb0ejKxG2jh +cOw15Sx40O0XrtrAto38iu4R/bkBeNCSUILlT+A3uYDila92Dayvls58WyIT3meD +G6+Sx/QDF69+4AXpVy9mQ+hxcofpFA32+GOMXwmk2OrAcdSkkGSBhZXgvTpQ64dl +xSiQ5EQW/K8LoBoEOXfjIZJNPORgKn5MI09AY7/47ycKDKTUU2yO8AtIHYKttw0x +kfIg7QOdo1F9IXVpGjJI7ynyrgsCEYxMoDyH42Dq84eKgrUFLEXemEz8hgdFgK41 +0eUYhAtzWHbRPBp+U/34CQoZ5ChNFp2YipvtXrzKE8KLkuM= +-----END CERTIFICATE----- +` +} + +func invalidCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIClzCCAX+gAwIBAgIJAIfPhC0RG6CwMA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNV +BAMMDm9pbCBhdXRob3JpdHkwHhcNMjAwNDA3MTUwOTU1WhcNMjEwNDA2MTUwOTU1 +WjBMMSAwHgYDVQQLDBd5b3VuZy1jaGFsbGVuZ2UgdGVzdCBjb25zdW1lczEfMB0G +A1UECwwWc28wMS5jb3Jwb3JhdGlvbnNvY2lhbDEhMB8GA1UEAwwYc29tMS5jb3Jw +b3JhdGlvbnNvY2lhbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDGRX31uzy+yLUOz7wOJHHm2dzrDgUbC6RZDjURvZxyt2Zi5wYWsEB5r5YhN7L0 +y1R9f+MGwNITIz9nYZuU/PLFOvzF5qX7A8TbdgjZEqvXe2NZ9J2z3iWvYQLN8Py3 +nv/Y6wadgXEBRCNNuIg/bQ9XuOr9tfB6j4Ut1GLU0eIlV/L3Rf9Y6SgrAl+58ITj +Wrg3Js/Wz3J2JU4qBD8U4I3XvUyfnX2SAG8Llm4KBuYz7g63Iu05s6RnmG+Xhu2T +5f2DWZUeATWbAlUW/M4NLO1+5H0gOr0TGulETQ6uElMchT7s/H6Rv1CV+CNCCgEI +adRjWJq9yQ+KrE+urSMCXu8XAgMBAAGjUzBRMB0GA1UdDgQWBBRb40pKGU4lNvqB +1f5Mz3t0N/K3hzAfBgNVHSMEGDAWgBRb40pKGU4lNvqB1f5Mz3t0N/K3hzAPBgNV +HREECDAGhwQAAAAAAAAwCgYIKoZIzj0EAwIDSAAwRQIhAP3ST/mXyRXsU2ciRoE +gE6trllODFY+9FgT6UbF2TwzAiAAuaUxtbk6uXLqtD5NtXqOQf0Ckg8GQxc5V1G2 +9PqTXQ== +-----END CERTIFICATE----- +` +} + +// Yoinked from https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/crypto/x509/x509_test.go, line 3385 +// This allows the `buildCaCertificatePool(...)` --> `x509.ParseCertificate(...)` call error branch to be covered. +func invalidCertificateDataPEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIBBzCBrqADAgECAgEAMAoGCCqGSM49BAMCMAAwIhgPMDAwMTAxMDEwMDAwMDBa +GA8wMDAxMDEwMTAwMDAwMFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOqV +EDuVXxwZgIU3+dOwv1SsMu0xuV48hf7xmK8n7sAMYgllB+96DnPqBeboJj4snYnx +0AcE0PDVQ1l4Z3YXsQWjFTATMBEGA1UdEQEB/wQHMAWCA2FzZDAKBggqhkjOPQQD +AwNIADBFAiBi1jz/T2HT5nAfrD7zsgR+68qh7Erc6Q4qlxYBOgKG4QIhAOtjIn+Q +tA+bq+55P3ntxTOVRq0nv1mwnkjwt9cQR9Fn +-----END CERTIFICATE----- +` +} + +// clientCertificatePEM returns a PEM-encoded client certificate. +// Note: The certificate is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func clientCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL +BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM +CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu +dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK +DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk +H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI +ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto +oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 +Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA +TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 +MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx +g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM +Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy +MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ +KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt +eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK +ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE +Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH +R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k +Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= +-----END CERTIFICATE----- +` +} + +// clientKeyPEM returns a PEM-encoded client key. +// Note: The key is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func clientKeyPEM() string { + return ` +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub +NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 +/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g +bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek +sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 +R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde +wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 +CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 +rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg +NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e +/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB +ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md +MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ +Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT +FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe +OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 +X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE +1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex +JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig +iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp +r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy +SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB +OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ +sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC +mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 +z/3KkMx4uqJXZyvQrmkolSg= +-----END PRIVATE KEY----- +` +} + +func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string][]byte { + return map[string][]byte{ + certification.CertificateKey: []byte(certificatePEM), + certification.CertificateKeyKey: []byte(keyPEM), + } +} + +func buildCaCertificateEntry(certificatePEM string) map[string][]byte { + return map[string][]byte{ + certification.CertificateKey: []byte(certificatePEM), + } +} diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go new file mode 100644 index 0000000..93321a1 --- /dev/null +++ b/internal/certification/certificates.go @@ -0,0 +1,195 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + * + * Establishes a Watcher for the Kubernetes Secrets that contain the various certificates and keys used to generate a tls.Config object; + * exposes the certificates and keys. + */ + +package certification + +import ( + "context" + "fmt" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +// TODO: This needs to use the settings for the secret names... + +const ( + // SecretsNamespace is the value used to filter the Secrets Resource in the Informer. + SecretsNamespace = "nlk" + + // CertificateKey is the key for the certificate in the Secret. + CertificateKey = "tls.crt" + + // CertificateKeyKey is the key for the certificate key in the Secret. + CertificateKeyKey = "tls.key" +) + +type Certificates struct { + Certificates map[string]map[string][]byte + + // Context is the context used to control the application. + Context context.Context + + // CaCertificateSecretKey is the name of the Secret that contains the Certificate Authority certificate. + CaCertificateSecretKey string + + // ClientCertificateSecretKey is the name of the Secret that contains the Client certificate. + ClientCertificateSecretKey string + + // informer is the SharedInformer used to watch for changes to the Secrets . + informer cache.SharedInformer + + // K8sClient is the Kubernetes client used to communicate with the Kubernetes API. + k8sClient kubernetes.Interface + + // eventHandlerRegistration is the object used to track the event handlers with the SharedInformer. + eventHandlerRegistration cache.ResourceEventHandlerRegistration +} + +// NewCertificates factory method that returns a new Certificates object. +func NewCertificates(ctx context.Context, k8sClient kubernetes.Interface) (*Certificates, error) { + return &Certificates{ + k8sClient: k8sClient, + Context: ctx, + Certificates: nil, + }, nil +} + +// GetCACertificate returns the Certificate Authority certificate. +func (c *Certificates) GetCACertificate() []byte { + bytes := c.Certificates[c.CaCertificateSecretKey][CertificateKey] + + return bytes +} + +// GetClientCertificate returns the Client certificate and key. +func (c *Certificates) GetClientCertificate() ([]byte, []byte) { + keyBytes := c.Certificates[c.ClientCertificateSecretKey][CertificateKeyKey] + certificateBytes := c.Certificates[c.ClientCertificateSecretKey][CertificateKey] + + return keyBytes, certificateBytes +} + +// Initialize initializes the Certificates object. Sets up a SharedInformer for the Secrets Resource. +func (c *Certificates) Initialize() error { + logrus.Info("Certificates::Initialize") + + var err error + + c.Certificates = make(map[string]map[string][]byte) + + informer, err := c.buildInformer() + if err != nil { + return fmt.Errorf(`error occurred building an informer: %w`, err) + } + + c.informer = informer + + err = c.initializeEventHandlers() + if err != nil { + return fmt.Errorf(`error occurred initializing event handlers: %w`, err) + } + + return nil +} + +// Run starts the SharedInformer. +func (c *Certificates) Run() error { + logrus.Info("Certificates::Run") + + if c.informer == nil { + return fmt.Errorf(`initialize must be called before Run`) + } + + c.informer.Run(c.Context.Done()) + + <-c.Context.Done() + + return nil +} + +func (c *Certificates) buildInformer() (cache.SharedInformer, error) { + logrus.Debug("Certificates::buildInformer") + + options := informers.WithNamespace(SecretsNamespace) + factory := informers.NewSharedInformerFactoryWithOptions(c.k8sClient, 0, options) + informer := factory.Core().V1().Secrets().Informer() + + return informer, nil +} + +func (c *Certificates) initializeEventHandlers() error { + logrus.Debug("Certificates::initializeEventHandlers") + + var err error + + handlers := cache.ResourceEventHandlerFuncs{ + AddFunc: c.handleAddEvent, + DeleteFunc: c.handleDeleteEvent, + UpdateFunc: c.handleUpdateEvent, + } + + c.eventHandlerRegistration, err = c.informer.AddEventHandler(handlers) + if err != nil { + return fmt.Errorf(`error occurred registering event handlers: %w`, err) + } + + return nil +} + +func (c *Certificates) handleAddEvent(obj interface{}) { + logrus.Debug("Certificates::handleAddEvent") + + secret, ok := obj.(*corev1.Secret) + if !ok { + logrus.Errorf("Certificates::handleAddEvent: unable to cast object to Secret") + return + } + + c.Certificates[secret.Name] = map[string][]byte{} + + for k, v := range secret.Data { + c.Certificates[secret.Name][k] = v + } + + logrus.Debugf("Certificates::handleAddEvent: certificates (%d)", len(c.Certificates)) +} + +func (c *Certificates) handleDeleteEvent(obj interface{}) { + logrus.Debug("Certificates::handleDeleteEvent") + + secret, ok := obj.(*corev1.Secret) + if !ok { + logrus.Errorf("Certificates::handleDeleteEvent: unable to cast object to Secret") + return + } + + if c.Certificates[secret.Name] != nil { + delete(c.Certificates, secret.Name) + } + + logrus.Debugf("Certificates::handleDeleteEvent: certificates (%d)", len(c.Certificates)) +} + +func (c *Certificates) handleUpdateEvent(obj interface{}, obj2 interface{}) { + logrus.Debug("Certificates::handleUpdateEvent") + + secret, ok := obj.(*corev1.Secret) + if !ok { + logrus.Errorf("Certificates::handleUpdateEvent: unable to cast object to Secret") + return + } + + for k, v := range secret.Data { + c.Certificates[secret.Name][k] = v + } + + logrus.Debugf("Certificates::handleUpdateEvent: certificates (%d)", len(c.Certificates)) +} diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go new file mode 100644 index 0000000..b741c52 --- /dev/null +++ b/internal/certification/certificates_test.go @@ -0,0 +1,244 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +package certification + +import ( + "context" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + "testing" + "time" +) + +const ( + CaCertificateSecretKey = "nlk-tls-ca-secret" +) + +func TestNewCertificate(t *testing.T) { + ctx := context.Background() + + certificates, err := NewCertificates(ctx, nil) + + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if certificates == nil { + t.Fatalf(`certificates should not be nil`) + } +} + +func TestCertificates_Initialize(t *testing.T) { + certificates, err := NewCertificates(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + err = certificates.Initialize() + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } +} + +func TestCertificates_RunWithoutInitialize(t *testing.T) { + certificates, err := NewCertificates(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + err = certificates.Run() + if err == nil { + t.Fatalf(`Expected error`) + } + + if err.Error() != `initialize must be called before Run` { + t.Fatalf(`Unexpected error: %v`, err) + } +} + +func TestCertificates_EmptyCertificates(t *testing.T) { + certificates, err := NewCertificates(context.Background(), nil) + if err != nil { + t.Fatalf(`error building Certificates: %v`, err) + } + + err = certificates.Initialize() + if err != nil { + t.Fatalf(`error Initializing Certificates: %v`, err) + } + + caBytes := certificates.GetCACertificate() + if caBytes != nil { + t.Fatalf(`Expected nil CA certificate`) + } + + clientKey, clientCert := certificates.GetClientCertificate() + if clientKey != nil { + t.Fatalf(`Expected nil client key`) + } + if clientCert != nil { + t.Fatalf(`Expected nil client certificate`) + } +} + +func TestCertificates_ExerciseHandlers(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + k8sClient := fake.NewSimpleClientset() + + certificates, err := NewCertificates(ctx, k8sClient) + if err != nil { + t.Fatalf(`error building Certificates: %v`, err) + } + + _ = certificates.Initialize() + + certificates.CaCertificateSecretKey = CaCertificateSecretKey + //certificates.ClientCertificateSecretKey = "nlk-tls-client-secret" + + go func() { + err := certificates.Run() + if err != nil { + t.Fatalf("error running Certificates: %v", err) + } + }() + + cache.WaitForCacheSync(ctx.Done(), certificates.informer.HasSynced) + + secret := buildSecret() + + /* -- Test Create -- */ + + created, err := k8sClient.CoreV1().Secrets(SecretsNamespace).Create(ctx, secret, metav1.CreateOptions{}) + if err != nil { + t.Fatalf(`error creating the Secret: %v`, err) + } + + if created.Name != secret.Name { + t.Fatalf(`Expected name %v, got %v`, secret.Name, created.Name) + } + + time.Sleep(2 * time.Second) + + caBytes := certificates.GetCACertificate() + if caBytes == nil { + t.Fatalf(`Expected non-nil CA certificate`) + } + + /* -- Test Update -- */ + + secret.Labels = map[string]string{"updated": "true"} + _, err = k8sClient.CoreV1().Secrets(SecretsNamespace).Update(ctx, secret, metav1.UpdateOptions{}) + if err != nil { + t.Fatalf(`error updating the Secret: %v`, err) + } + + time.Sleep(2 * time.Second) + + caBytes = certificates.GetCACertificate() + if caBytes == nil { + t.Fatalf(`Expected non-nil CA certificate`) + } + + /* -- Test Delete -- */ + + err = k8sClient.CoreV1().Secrets(SecretsNamespace).Delete(ctx, secret.Name, metav1.DeleteOptions{}) + if err != nil { + t.Fatalf(`error deleting the Secret: %v`, err) + } + + time.Sleep(2 * time.Second) + + caBytes = certificates.GetCACertificate() + if caBytes != nil { + t.Fatalf(`Expected nil CA certificate, got: %v`, caBytes) + } +} + +func buildSecret() *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: CaCertificateSecretKey, + Namespace: SecretsNamespace, + }, + Data: map[string][]byte{ + CertificateKey: []byte(certificatePEM()), + CertificateKeyKey: []byte(keyPEM()), + }, + Type: corev1.SecretTypeTLS, + } +} + +// certificatePEM returns a PEM-encoded client certificate. +// Note: The certificate is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func certificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL +BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM +CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu +dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK +DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk +H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI +ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto +oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 +Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA +TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 +MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx +g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM +Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy +MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ +KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt +eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK +ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE +Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH +R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k +Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= +-----END CERTIFICATE----- +` +} + +// keyPEM returns a PEM-encoded client key. +// Note: The key is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func keyPEM() string { + return ` +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub +NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 +/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g +bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek +sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 +R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde +wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 +CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 +rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg +NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e +/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB +ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md +MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ +Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT +FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe +OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 +X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE +1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex +JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig +iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp +r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy +SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB +OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ +sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC +mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 +z/3KkMx4uqJXZyvQrmkolSg= +-----END PRIVATE KEY----- +` +} diff --git a/internal/certification/doc.go b/internal/certification/doc.go new file mode 100644 index 0000000..3388ea0 --- /dev/null +++ b/internal/certification/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +/* +Package certification includes functionality to access the Secrets containing the TLS Certificates. +*/ + +package certification diff --git a/internal/communication/client.go b/internal/communication/factory.go similarity index 71% rename from internal/communication/client.go rename to internal/communication/factory.go index fb7d80d..9a3d411 100644 --- a/internal/communication/client.go +++ b/internal/communication/factory.go @@ -7,6 +7,9 @@ package communication import ( "crypto/tls" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/sirupsen/logrus" netHttp "net/http" "time" ) @@ -14,9 +17,9 @@ import ( // NewHttpClient is a factory method to create a new Http Client with a default configuration. // RoundTripper is a wrapper around the default net/communication Transport to add additional headers, in this case, // the Headers are configured for JSON. -func NewHttpClient() (*netHttp.Client, error) { +func NewHttpClient(settings *configuration.Settings) (*netHttp.Client, error) { headers := NewHeaders() - tlsConfig := NewTlsConfig() + tlsConfig := NewTlsConfig(settings) transport := NewTransport(tlsConfig) roundTripper := NewRoundTripper(headers, transport) @@ -38,8 +41,14 @@ func NewHeaders() []string { // NewTlsConfig is a factory method to create a new basic Tls Config. // More attention should be given to the use of `InsecureSkipVerify: true`, as it is not recommended for production use. -func NewTlsConfig() *tls.Config { - return &tls.Config{InsecureSkipVerify: true} +func NewTlsConfig(settings *configuration.Settings) *tls.Config { + tlsConfig, err := authentication.NewTlsConfig(settings) + if err != nil { + logrus.Warnf("Failed to create TLS config: %v", err) + return &tls.Config{InsecureSkipVerify: true} + } + + return tlsConfig } // NewTransport is a factory method to create a new basic Http Transport. diff --git a/internal/communication/client_test.go b/internal/communication/factory_test.go similarity index 75% rename from internal/communication/client_test.go rename to internal/communication/factory_test.go index 2f3a82f..f25abef 100644 --- a/internal/communication/client_test.go +++ b/internal/communication/factory_test.go @@ -6,11 +6,16 @@ package communication import ( + "context" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "k8s.io/client-go/kubernetes/fake" "testing" ) func TestNewHttpClient(t *testing.T) { - client, err := NewHttpClient() + k8sClient := fake.NewSimpleClientset() + settings, err := configuration.NewSettings(context.Background(), k8sClient) + client, err := NewHttpClient(settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) @@ -41,20 +46,10 @@ func TestNewHeaders(t *testing.T) { } } -func TestNewTlsConfig(t *testing.T) { - config := NewTlsConfig() - - if config == nil { - t.Fatalf(`config should not be nil`) - } - - if !config.InsecureSkipVerify { - t.Fatalf(`config.InsecureSkipVerify should be true`) - } -} - func TestNewTransport(t *testing.T) { - config := NewTlsConfig() + k8sClient := fake.NewSimpleClientset() + settings, _ := configuration.NewSettings(context.Background(), k8sClient) + config := NewTlsConfig(settings) transport := NewTransport(config) if transport == nil { diff --git a/internal/communication/roundtripper_test.go b/internal/communication/roundtripper_test.go index d28cf53..4185b6d 100644 --- a/internal/communication/roundtripper_test.go +++ b/internal/communication/roundtripper_test.go @@ -7,13 +7,18 @@ package communication import ( "bytes" + "context" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "k8s.io/client-go/kubernetes/fake" netHttp "net/http" "testing" ) func TestNewRoundTripper(t *testing.T) { + k8sClient := fake.NewSimpleClientset() + settings, _ := configuration.NewSettings(context.Background(), k8sClient) headers := NewHeaders() - transport := NewTransport(NewTlsConfig()) + transport := NewTransport(NewTlsConfig(settings)) roundTripper := NewRoundTripper(headers, transport) if roundTripper == nil { @@ -42,8 +47,10 @@ func TestNewRoundTripper(t *testing.T) { } func TestRoundTripperRoundTrip(t *testing.T) { + k8sClient := fake.NewSimpleClientset() + settings, err := configuration.NewSettings(context.Background(), k8sClient) headers := NewHeaders() - transport := NewTransport(NewTlsConfig()) + transport := NewTransport(NewTlsConfig(settings)) roundTripper := NewRoundTripper(headers, transport) request, err := NewRequest("GET", "http://example.com", nil) diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 12c9823..8c8874a 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -8,8 +8,10 @@ package configuration import ( "context" "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -22,6 +24,9 @@ const ( // ConfigMapsNamespace is the value used to filter the ConfigMaps Resource in the Informer. ConfigMapsNamespace = "nlk" + // ConfigMapName is the name of the ConfigMap that contains the configuration for the application. + ConfigMapName = "nlk-config" + // ResyncPeriod is the value used to set the resync period for the Informer. ResyncPeriod = 0 @@ -104,8 +109,14 @@ type Settings struct { // NginxPlusHosts is a list of Nginx Plus hosts that will be used to update the Border Servers. NginxPlusHosts []string + // TlsMode is the value used to determine which of the five TLS modes will be used to communicate with the Border Servers (see: ../../docs/tls/README.md). + TlsMode string + + // Certificates is the object used to retrieve the certificates and keys used to communicate with the Border Servers. + Certificates *certification.Certificates + // K8sClient is the Kubernetes client used to communicate with the Kubernetes API. - K8sClient *kubernetes.Clientset + K8sClient kubernetes.Interface // informer is the SharedInformer used to watch for changes to the ConfigMap . informer cache.SharedInformer @@ -124,10 +135,12 @@ type Settings struct { } // NewSettings creates a new Settings object with default values. -func NewSettings(ctx context.Context, k8sClient *kubernetes.Clientset) (*Settings, error) { +func NewSettings(ctx context.Context, k8sClient kubernetes.Interface) (*Settings, error) { settings := &Settings{ - Context: ctx, - K8sClient: k8sClient, + Context: ctx, + K8sClient: k8sClient, + TlsMode: "", + Certificates: nil, Handler: HandlerSettings{ RetryCount: 5, Threads: 1, @@ -164,6 +177,29 @@ func (s *Settings) Initialize() error { var err error + certificates, err := certification.NewCertificates(s.Context, s.K8sClient) + if err != nil { + return fmt.Errorf(`error occurred creating certificates: %w`, err) + } + + err = certificates.Initialize() + if err != nil { + return fmt.Errorf(`error occurred initializing certificates: %w`, err) + } + + s.Certificates = certificates + + go certificates.Run() + + logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieving nlk-config ConfigMap") + configMap, err := s.K8sClient.CoreV1().ConfigMaps(ConfigMapsNamespace).Get(s.Context, "nlk-config", metav1.GetOptions{}) + if err != nil { + return err + } + + s.handleUpdateEvent(nil, configMap) + logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieved nlk-config ConfigMap") + informer, err := s.buildInformer() if err != nil { return fmt.Errorf(`error occurred building ConfigMap informer: %w`, err) @@ -220,32 +256,63 @@ func (s *Settings) initializeEventListeners() error { func (s *Settings) handleAddEvent(obj interface{}) { logrus.Debug("Settings::handleAddEvent") - s.handleUpdateEvent(obj, nil) + if _, yes := isOurConfig(obj); yes { + s.handleUpdateEvent(nil, obj) + } } -func (s *Settings) handleDeleteEvent(_ interface{}) { +func (s *Settings) handleDeleteEvent(obj interface{}) { logrus.Debug("Settings::handleDeleteEvent") - s.updateHosts([]string{}) + if _, yes := isOurConfig(obj); yes { + s.updateHosts([]string{}) + } } -func (s *Settings) handleUpdateEvent(obj interface{}, _ interface{}) { +func (s *Settings) handleUpdateEvent(_ interface{}, obj interface{}) { logrus.Debug("Settings::handleUpdateEvent") - configMap, ok := obj.(*corev1.ConfigMap) - if !ok { - logrus.Errorf("Settings::handleUpdateEvent: could not convert obj to ConfigMap") + configMap, yes := isOurConfig(obj) + if !yes { return } hosts, found := configMap.Data["nginx-hosts"] - if !found { - logrus.Errorf("Settings::handleUpdateEvent: nginx-hosts key not found in ConfigMap") - return + if found { + newHosts := s.parseHosts(hosts) + s.updateHosts(newHosts) + } else { + logrus.Warnf("Settings::handleUpdateEvent: nginx-hosts key not found in ConfigMap") + } + + tlsMode, found := configMap.Data["tls-mode"] + if found { + s.TlsMode = tlsMode + logrus.Debugf("Settings::handleUpdateEvent: tls-mode: %s", s.TlsMode) + } else { + s.TlsMode = "no-tls" + logrus.Warnf("Settings::handleUpdateEvent: tls-mode key not found in ConfigMap, defaulting to 'no-tls'") } - newHosts := s.parseHosts(hosts) - s.updateHosts(newHosts) + caCertificateSecretKey, found := configMap.Data["ca-certificate"] + if found { + s.Certificates.CaCertificateSecretKey = caCertificateSecretKey + logrus.Debugf("Settings::handleUpdateEvent: ca-certificate: %s", s.Certificates.CaCertificateSecretKey) + } else { + s.Certificates.CaCertificateSecretKey = "" + logrus.Warnf("Settings::handleUpdateEvent: ca-certificate key not found in ConfigMap") + } + + clientCertificateSecretKey, found := configMap.Data["client-certificate"] + if found { + s.Certificates.ClientCertificateSecretKey = clientCertificateSecretKey + logrus.Debugf("Settings::handleUpdateEvent: client-certificate: %s", s.Certificates.ClientCertificateSecretKey) + } else { + s.Certificates.ClientCertificateSecretKey = "" + logrus.Warnf("Settings::handleUpdateEvent: client-certificate key not found in ConfigMap") + } + + logrus.Debugf("Settings::handleUpdateEvent: \n\tHosts: %v,\n\tSettings: %v ", s.NginxPlusHosts, configMap) } func (s *Settings) parseHosts(hosts string) []string { @@ -255,3 +322,8 @@ func (s *Settings) parseHosts(hosts string) []string { func (s *Settings) updateHosts(hosts []string) { s.NginxPlusHosts = hosts } + +func isOurConfig(obj interface{}) (*corev1.ConfigMap, bool) { + configMap, ok := obj.(*corev1.ConfigMap) + return configMap, ok && configMap.Name == ConfigMapName && configMap.Namespace == ConfigMapsNamespace +} diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 2f6c421..1061b01 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -107,7 +107,7 @@ func (s *Synchronizer) buildBorderClient(event *core.ServerUpdateEvent) (applica var err error - httpClient, err := communication.NewHttpClient() + httpClient, err := communication.NewHttpClient(s.settings) if err != nil { return nil, fmt.Errorf(`error creating HTTP client: %v`, err) } diff --git a/server-secret.yaml b/server-secret.yaml new file mode 100644 index 0000000..8299224 --- /dev/null +++ b/server-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsekNDQTMrZ0F3SUJBZ0lVUFRrVExlS3FNQlRlNFZhQXpiL1hXcSt6THRBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eEVEQU9CZ05WQkFjTQpCMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0ZFc1cGRIa2dKaUJCCmJHeHBZVzVqWlhNd0hoY05Nak14TURBeU1qTXdPRFEyV2hjTk1qUXhNREF4TWpNd09EUTJXakI3TVFzd0NRWUQKVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLVjJGemFHbHVaM1J2YmpFUU1BNEdBMVVFQnd3SFUyVmhkSFJzWlRFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEhqQWNCZ05WQkFzTUZVTnZiVzExYm1sMGVTQW1JRUZzYkdsaGJtTmxjekVWCk1CTUdBMVVFQXd3TWJYbGtiMjFoYVc0dVkyOXRNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUIKQ2dLQ0FRRUF2RjJSVklsVEQvQWVSQkZSNTNrb1dlZmlhQ09keVJvWnJRSm04RGZJdk5vQ2pTdkNPUEhzZ2VSSQptcmhxcEhkc2FSK2RqSjNRbzFKemljeS9YNHgxRy9SSGRNM0d3OXNuTS9jdHBvb1FSZUtFTi90T1FnVnBMaEZQClE4K3cvaDVRSGZWWFFQcFdyeGhjTkFiK1hrbzk5SVBndE81blNvazExK2pEOGhJSVlTTUEvVElwUFBPeXNKdnkKOUhKUjBEY3pqWE1VQTR0dGphYTZ0cm8xNm81WEZsdDZPM251YTFkc0VLYVRJQmhWbXI4ZGJWVFd2c1VNYTV6WgpyR0pSa25Bc01RV1lxSVBBVG1MdXlaeHlYTG1WQkp4U1FzWTZqYVllLzcreXZGdUVCUTZ4MDByZ1Z0THpzWi9NCitZSHg2VUVXRFoxYzZuZWxxdlFja1FvSzdTd3VLUUlEQVFBQm80SUJLRENDQVNRd0NRWURWUjBUQkFJd0FEQUwKQmdOVkhROEVCQU1DQmVBd1NBWURWUjBSQkVFd1A0SU1iWGxrYjIxaGFXNHVZMjl0Z2hOelpYSjJaWEl1YlhsawpiMjFoYVc0dVkyOXRnZzRxTG0xNVpHOXRZV2x1TG1OdmJZY0VDZ0FBQ29jRUNnQUFDekFUQmdOVkhTVUVEREFLCkJnZ3JCZ0VGQlFjREFUQWRCZ05WSFE0RUZnUVVkOUVieTR0TDNZSjhqSTd6RzVKR2p5TW5vVDR3Z1lzR0ExVWQKSXdTQmd6Q0JnS0ZvcEdZd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eApFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0CmRXNXBkSGtnSmlCQmJHeHBZVzVqWlhPQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFCRFY0OWtOanpCZmpPMjlNWVd1ZFlMNktzYlFrdlFDdzZ3dEF6cVJ2Smd5WEtBbXYzcwp6OFJjZHFNRTZ2bXdWZUxpS1ZDeDFSOXY1dlhBS0hMNmFSSi9QMk1QUm1Ic0pNM2MxSVF2NjAzVVYzaCtQL3JICll3QTVVRzBCMXlMaWs3N3RIdWZtRGFKdlRZMGpvcWJ0cVEwU1ByWjh6TGJQdmo3VTc1bStHWEx5VEp0UnRjdDUKSVJTblg0NlZxbWs0Q1l2dW1SaDJJTy8yUHpKNkpUbzA5VFpEOGpDbyttbHIzSXhXOHBsRDBYSkQ1YXY4cEQ2dApDZ2xnR2FyakRWSmVCQUZobkVIWStYelZDYzJVSktVblNiaDVWUVRLdno2dCtCWTlldi9LOFJGYlcxWVd3N1Q1CmQ0UzEyaC94cUpXcUJOdXFJeldkMTMvbTZaaHoraHorb0VvRQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzhYWkZVaVZNUDhCNUUKRVZIbmVTaFo1K0pvSTUzSkdobXRBbWJ3TjhpODJnS05LOEk0OGV5QjVFaWF1R3FrZDJ4cEg1Mk1uZENqVW5PSgp6TDlmakhVYjlFZDB6Y2JEMnljejl5Mm1paEJGNG9RMyswNUNCV2t1RVU5RHo3RCtIbEFkOVZkQStsYXZHRncwCkJ2NWVTajMwZytDMDdtZEtpVFhYNk1QeUVnaGhJd0Q5TWlrODg3S3dtL0wwY2xIUU56T05jeFFEaTIyTnBycTIKdWpYcWpsY1dXM283ZWU1clYyd1FwcE1nR0ZXYXZ4MXRWTmEreFF4cm5ObXNZbEdTY0N3eEJaaW9nOEJPWXU3SgpuSEpjdVpVRW5GSkN4anFOcGg3L3Y3SzhXNFFGRHJIVFN1Qlcwdk94bjh6NWdmSHBRUllOblZ6cWQ2V3E5QnlSCkNncnRMQzRwQWdNQkFBRUNnZ0VBQnRtRGh6dnhKeU5pV1FqNk0xSjVaYW81Z1BPZW5CUC9aVnV3MWtFVHVIa0QKQ1JMVGRCSlVEb3NqR3NFNXNONWVOUVVVZG1zU0RiRUVzNDVHL1VOVWRONUdyNWdyWEs3bzk0cExBTmlFd1UzUgo1TFBybU1TdFJQZ1YxaytaK09aWVNudnVudFhHUzRWZ0ZrenFJMlZNRXlBQ2pxeHhXWFFHTkdJL1VkdXNXS3FICkNlOWNGMXp0UGxBWkVIRU9JNTJEcVFZK3FhUUtNSjVIT3RNS1F6SzY5MGF0VUJmbWZxUCsxYTh4Vm1VV1cyaVgKNWpuN3IzeDRJdENwSlpwTHM2SzZRS29aNU9qd25nSTc4MkxuQ3V0cC9NZmg5RmVYSlFRRVppYXMzOW44c29NcwpCN0ZKQnpKalQwVlQxR2hVWnNwSUhra1AwR3pBQTd2Y3MwNitDQWc5Z1FLQmdRRG1CYTQ3MnlLYmwvZUIyY3BFCjdZeXVmaHk1M093OTFzNkkwYnQxYWZyajNvcHExV1VVWUJiOWhIUm1CRXkzVVE2V1JoenltdG1MdXBRTWh4enkKeThSMVFtNW1RUTI1T3lsNGl3TFZQdVE3Ty94dzh6UUdNR1pBY08zQ3NuYUM5Y3YxZFpnZzcvQ1BUTlpsY2RxZAp2OXFqNnpwVHFrT21OVnlFUGxPeFI3aHJ5UUtCZ1FEUm80WVNwcDRONHRlSWhIaG1uZGpYaVZkSDYyM3oxQldICnV1U3M0L0M2RDVjaUhWRTJYZnZ2cnQ4eEhBbEVycGtoaVFUZ1JzZHVwblVhVVFodUtaT1RNTXVGcDAzblA1cmMKUjlxY1MxQUVQUXZMVFNZN0dqaEptaExEVUM0WktGYmo4bXhkSWVBK09YL21mank4SFJTMWYxYncwR3owZCtvNQozVnlyOUU0ZllRS0JnUUNuY1QwckwxTGJCdDNTZFpMcmFDMC9uR2dXMkg1VWFia0JHZ09tN2hZSHFLa0VLZ0VoCnV1MGhjVGsyUml6K1NSQWdUanVtVXhqSHdYTWlSM3pJTlpMMmRQeGVqVDZMTjBqeUNlZHZDaEFrR24raVRUZnkKeFdxNXdEc2p2cnZNaTFjRWdLelVWVFc5YXdhcTVCMXJOZ3pYeEZVNk1EaDhsbDJabXJGYjNNU2dHUUtCZ0F1cQpmT2lHeXg3Y3M3L09GMkVtZ1kyay8rMXBwWW0vRUorbi85ZTdLNGMvSE5yeUpMWFF6eGRNZFBFbnJVQmNNdnRSCnc2cXpaWis3dGFLTVJkclRoM25XYWt6NnZYUVQ3d3M1R0dwQUtxakJ1T2xNVnNkTk16cXRUMFA5TDBPSklpUzMKTmQ2TTV3eXZhSFdzS3JjUkt6amFhRDBvYkJmQ29JOHR5VjFzVC9paEFvR0FWNHJzMU14d2VkRVVPTFo4Y0ZBQgpvYTE1MG41dVgwUGQ1Nm92enRXNHZoR2JyOVpjQ294dnR0a0VqQUNvTzdyanhBb1JyYVk1U1A0WTdHaHJxb043CkZYeFYrVVh0aGxyU21SQWUzS25ndzA4bkRpT0Fia0NZdFlGVGs3d3d3V0xYU2VYUWRCdUx1aWRqZ05PL2RhbkkKczUzVXU3OGlnRVdPa21YWThGYm1OVEU9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-server-secret + namespace: nlk +type: kubernetes.io/tls From 05dd66b374709b8bdbbe3977710ca05bd13c3616 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Tue, 24 Oct 2023 11:30:56 -0700 Subject: [PATCH 06/14] - Putting the cherry on top - Putting a bow on it - Final corrections and enhancements - Let the configuration settings determine log level --- cmd/nginx-loadbalancer-kubernetes/main.go | 1 - deployments/deployment/configmap.yaml | 11 ++++---- deployments/deployment/deployment.yaml | 3 +-- docs/tls/SS-TLS.md | 2 +- internal/authentication/factory.go | 14 +++++----- internal/configuration/settings.go | 31 +++++++++++++++++++++++ 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index 6e6a8bc..6936557 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -19,7 +19,6 @@ import ( ) func main() { - logrus.SetLevel(logrus.DebugLevel) err := run() if err != nil { logrus.Fatal(err) diff --git a/deployments/deployment/configmap.yaml b/deployments/deployment/configmap.yaml index 91522a6..fd30dbe 100644 --- a/deployments/deployment/configmap.yaml +++ b/deployments/deployment/configmap.yaml @@ -1,10 +1,11 @@ apiVersion: v1 kind: ConfigMap data: - nginx-hosts: "https://192.168.96.207/api" - tls-mode: "ss-mtls" - ca-certificate: "nlk-tls-ca-secret" - client-certificate: "nlk-tls-client-secret" + nginx-hosts: "https://10.0.0.1:9000/api" + tls-mode: "no-tls" + ca-certificate: "" + client-certificate: "" + log-level: "warn" metadata: name: nlk-config - namespace: nlk \ No newline at end of file + namespace: nlk diff --git a/deployments/deployment/deployment.yaml b/deployments/deployment/deployment.yaml index 11fa61f..4c871c2 100644 --- a/deployments/deployment/deployment.yaml +++ b/deployments/deployment/deployment.yaml @@ -17,8 +17,7 @@ spec: spec: containers: - name: nginx-loadbalancer-kubernetes - image: ciroque/nginx-loadbalancer-kubernetes:dev-11 -# image: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes:125 + image: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes:latest imagePullPolicy: Always ports: - name: http diff --git a/docs/tls/SS-TLS.md b/docs/tls/SS-TLS.md index 8f37d90..3c30b11 100644 --- a/docs/tls/SS-TLS.md +++ b/docs/tls/SS-TLS.md @@ -53,7 +53,7 @@ metadata: data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" tls-mode: "ss-tls" - caCertificate: "nlk-tls-ca-secret" + ca-certificate: "nlk-tls-ca-secret" ``` ## Deployment diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go index 21b3458..5d94343 100644 --- a/internal/authentication/factory.go +++ b/internal/authentication/factory.go @@ -18,7 +18,7 @@ import ( ) func NewTlsConfig(settings *configuration.Settings) (*tls.Config, error) { - logrus.Debugf("Creating TLS config for mode: '%s'", settings.TlsMode) + logrus.Debugf("authentication::NewTlsConfig Creating TLS config for mode: '%s'", settings.TlsMode) switch settings.TlsMode { case "ss-tls": // needs ca cert return buildSelfSignedTlsConfig(settings.Certificates) @@ -38,7 +38,7 @@ func NewTlsConfig(settings *configuration.Settings) (*tls.Config, error) { } func buildSelfSignedTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debug("Building self-signed TLS config") + logrus.Debugf("authentication::buildSelfSignedTlsConfig Building self-signed TLS config, CA Secret Key(%v)", certificates.CaCertificateSecretKey) certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) if err != nil { return nil, err @@ -51,7 +51,7 @@ func buildSelfSignedTlsConfig(certificates *certification.Certificates) (*tls.Co } func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debug("buildSelfSignedMtlsConfig Building self-signed mTLS config") + logrus.Debugf("authentication::buildSelfSignedMtlsConfig Building self-signed mTLS config, CA Secret Key(%v), Client Certificate Key(%v)", certificates.CaCertificateSecretKey, certificates.ClientCertificateSecretKey) certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) if err != nil { return nil, err @@ -72,14 +72,14 @@ func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.C } func buildBasicTlsConfig(skipVerify bool) *tls.Config { - logrus.Debug("Building basic TLS config") + logrus.Debugf("authentication::buildBasicTlsConfig skipVerify(%v)", skipVerify) return &tls.Config{ InsecureSkipVerify: skipVerify, } } func buildCaTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debug("Building CA TLS config") + logrus.Debugf("authentication::buildCaTlsConfig, Client Certificate Key(%v)", certificates.ClientCertificateSecretKey) certificate, err := buildCertificates(certificates.GetClientCertificate()) if err != nil { return nil, err @@ -92,12 +92,12 @@ func buildCaTlsConfig(certificates *certification.Certificates) (*tls.Config, er } func buildCertificates(privateKeyPEM []byte, certificatePEM []byte) (tls.Certificate, error) { - logrus.Debug("Building certificates") + logrus.Debugf("authentication::buildCertificates, Private Key(%v), Certificate(%v)", privateKeyPEM, certificatePEM) return tls.X509KeyPair(certificatePEM, privateKeyPEM) } func buildCaCertificatePool(caCert []byte) (*x509.CertPool, error) { - logrus.Debugf("Building CA certificate pool") + logrus.Debugf("authentication::buildCaCertificatePool, CA Certificate(%v)", caCert) block, _ := pem.Decode(caCert) if block == nil { return nil, fmt.Errorf("failed to decode PEM block containing CA certificate") diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 8c8874a..d9f1d3b 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -312,6 +312,8 @@ func (s *Settings) handleUpdateEvent(_ interface{}, obj interface{}) { logrus.Warnf("Settings::handleUpdateEvent: client-certificate key not found in ConfigMap") } + setLogLevel(configMap.Data["log-level"]) + logrus.Debugf("Settings::handleUpdateEvent: \n\tHosts: %v,\n\tSettings: %v ", s.NginxPlusHosts, configMap) } @@ -327,3 +329,32 @@ func isOurConfig(obj interface{}) (*corev1.ConfigMap, bool) { configMap, ok := obj.(*corev1.ConfigMap) return configMap, ok && configMap.Name == ConfigMapName && configMap.Namespace == ConfigMapsNamespace } + +func setLogLevel(logLevel string) { + logrus.Debugf("Settings::setLogLevel: %s", logLevel) + switch logLevel { + case "panic": + logrus.SetLevel(logrus.PanicLevel) + + case "fatal": + logrus.SetLevel(logrus.FatalLevel) + + case "error": + logrus.SetLevel(logrus.ErrorLevel) + + case "warn": + logrus.SetLevel(logrus.WarnLevel) + + case "info": + logrus.SetLevel(logrus.InfoLevel) + + case "debug": + logrus.SetLevel(logrus.DebugLevel) + + case "trace": + logrus.SetLevel(logrus.TraceLevel) + + default: + logrus.SetLevel(logrus.WarnLevel) + } +} From bda3429359c93ebe00cfeddd33f0a2465d43f1e8 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Tue, 7 Nov 2023 13:09:16 -0800 Subject: [PATCH 07/14] - Bug fix --- internal/certification/certificates.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go index 93321a1..4a990c1 100644 --- a/internal/certification/certificates.go +++ b/internal/certification/certificates.go @@ -178,10 +178,10 @@ func (c *Certificates) handleDeleteEvent(obj interface{}) { logrus.Debugf("Certificates::handleDeleteEvent: certificates (%d)", len(c.Certificates)) } -func (c *Certificates) handleUpdateEvent(obj interface{}, obj2 interface{}) { +func (c *Certificates) handleUpdateEvent(oldValue interface{}, newValue interface{}) { logrus.Debug("Certificates::handleUpdateEvent") - secret, ok := obj.(*corev1.Secret) + secret, ok := newValue.(*corev1.Secret) if !ok { logrus.Errorf("Certificates::handleUpdateEvent: unable to cast object to Secret") return From 641f0f62d4321a960f9b93a147ceef86dc88b8e9 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Tue, 7 Nov 2023 13:10:24 -0800 Subject: [PATCH 08/14] - remove dead code --- internal/certification/certificates_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go index b741c52..f756460 100644 --- a/internal/certification/certificates_test.go +++ b/internal/certification/certificates_test.go @@ -100,7 +100,6 @@ func TestCertificates_ExerciseHandlers(t *testing.T) { _ = certificates.Initialize() certificates.CaCertificateSecretKey = CaCertificateSecretKey - //certificates.ClientCertificateSecretKey = "nlk-tls-client-secret" go func() { err := certificates.Run() From 9eb4e84154103990cc6a82d6053454d5b860cdeb Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:17:37 -0800 Subject: [PATCH 09/14] Do not need an error here. --- internal/certification/certificates.go | 4 ++-- internal/configuration/settings.go | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go index 4a990c1..dc35ca2 100644 --- a/internal/certification/certificates.go +++ b/internal/certification/certificates.go @@ -54,12 +54,12 @@ type Certificates struct { } // NewCertificates factory method that returns a new Certificates object. -func NewCertificates(ctx context.Context, k8sClient kubernetes.Interface) (*Certificates, error) { +func NewCertificates(ctx context.Context, k8sClient kubernetes.Interface) *Certificates { return &Certificates{ k8sClient: k8sClient, Context: ctx, Certificates: nil, - }, nil + } } // GetCACertificate returns the Certificate Authority certificate. diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index d9f1d3b..3c2715b 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -177,10 +177,7 @@ func (s *Settings) Initialize() error { var err error - certificates, err := certification.NewCertificates(s.Context, s.K8sClient) - if err != nil { - return fmt.Errorf(`error occurred creating certificates: %w`, err) - } + certificates := certification.NewCertificates(s.Context, s.K8sClient) err = certificates.Initialize() if err != nil { From 6d7b421d89c676dc259cc276cfec90cf736c7fa5 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:19:22 -0800 Subject: [PATCH 10/14] Stop leaking certificates!! --- internal/authentication/factory.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go index 5d94343..293989b 100644 --- a/internal/authentication/factory.go +++ b/internal/authentication/factory.go @@ -38,7 +38,7 @@ func NewTlsConfig(settings *configuration.Settings) (*tls.Config, error) { } func buildSelfSignedTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debugf("authentication::buildSelfSignedTlsConfig Building self-signed TLS config, CA Secret Key(%v)", certificates.CaCertificateSecretKey) + logrus.Debug("authentication::buildSelfSignedTlsConfig Building self-signed TLS config") certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) if err != nil { return nil, err @@ -51,7 +51,7 @@ func buildSelfSignedTlsConfig(certificates *certification.Certificates) (*tls.Co } func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debugf("authentication::buildSelfSignedMtlsConfig Building self-signed mTLS config, CA Secret Key(%v), Client Certificate Key(%v)", certificates.CaCertificateSecretKey, certificates.ClientCertificateSecretKey) + logrus.Debug("authentication::buildSelfSignedMtlsConfig Building self-signed mTLS config") certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) if err != nil { return nil, err @@ -79,7 +79,7 @@ func buildBasicTlsConfig(skipVerify bool) *tls.Config { } func buildCaTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { - logrus.Debugf("authentication::buildCaTlsConfig, Client Certificate Key(%v)", certificates.ClientCertificateSecretKey) + logrus.Debug("authentication::buildCaTlsConfig") certificate, err := buildCertificates(certificates.GetClientCertificate()) if err != nil { return nil, err @@ -92,12 +92,12 @@ func buildCaTlsConfig(certificates *certification.Certificates) (*tls.Config, er } func buildCertificates(privateKeyPEM []byte, certificatePEM []byte) (tls.Certificate, error) { - logrus.Debugf("authentication::buildCertificates, Private Key(%v), Certificate(%v)", privateKeyPEM, certificatePEM) + logrus.Debug("authentication::buildCertificates") return tls.X509KeyPair(certificatePEM, privateKeyPEM) } func buildCaCertificatePool(caCert []byte) (*x509.CertPool, error) { - logrus.Debugf("authentication::buildCaCertificatePool, CA Certificate(%v)", caCert) + logrus.Debug("authentication::buildCaCertificatePool") block, _ := pem.Decode(caCert) if block == nil { return nil, fmt.Errorf("failed to decode PEM block containing CA certificate") From ad45897678b355bdec25f255c12f07f954369894 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:20:30 -0800 Subject: [PATCH 11/14] Remove dead comment --- internal/certification/certificates.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go index dc35ca2..6595cf9 100644 --- a/internal/certification/certificates.go +++ b/internal/certification/certificates.go @@ -18,8 +18,6 @@ import ( "k8s.io/client-go/tools/cache" ) -// TODO: This needs to use the settings for the secret names... - const ( // SecretsNamespace is the value used to filter the Secrets Resource in the Informer. SecretsNamespace = "nlk" From 183421d51e422c7aee7ebb9f1d3707cc2105eedf Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:27:26 -0800 Subject: [PATCH 12/14] - update test for new code --- cmd/certificates-test-harness/main.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/certificates-test-harness/main.go b/cmd/certificates-test-harness/main.go index 3be7045..44d4a4e 100644 --- a/cmd/certificates-test-harness/main.go +++ b/cmd/certificates-test-harness/main.go @@ -32,10 +32,7 @@ func run() error { return fmt.Errorf(`error building a Kubernetes client: %w`, err) } - certificates, err := certification.NewCertificates(ctx, k8sClient) - if err != nil { - return fmt.Errorf(`error occurred creating certificates: %w`, err) - } + certificates := certification.NewCertificates(ctx, k8sClient) err = certificates.Initialize() if err != nil { From 29148b2edf0085fbf28c21248609e373976fe910 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:33:30 -0800 Subject: [PATCH 13/14] - update test for new code --- internal/certification/certificates_test.go | 32 ++++++--------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go index f756460..c8edf14 100644 --- a/internal/certification/certificates_test.go +++ b/internal/certification/certificates_test.go @@ -22,11 +22,7 @@ const ( func TestNewCertificate(t *testing.T) { ctx := context.Background() - certificates, err := NewCertificates(ctx, nil) - - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } + certificates := NewCertificates(ctx, nil) if certificates == nil { t.Fatalf(`certificates should not be nil`) @@ -34,24 +30,18 @@ func TestNewCertificate(t *testing.T) { } func TestCertificates_Initialize(t *testing.T) { - certificates, err := NewCertificates(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } + certificates := NewCertificates(context.Background(), nil) - err = certificates.Initialize() + err := certificates.Initialize() if err != nil { t.Fatalf(`Unexpected error: %v`, err) } } func TestCertificates_RunWithoutInitialize(t *testing.T) { - certificates, err := NewCertificates(context.Background(), nil) - if err != nil { - t.Fatalf(`Unexpected error: %v`, err) - } + certificates := NewCertificates(context.Background(), nil) - err = certificates.Run() + err := certificates.Run() if err == nil { t.Fatalf(`Expected error`) } @@ -62,12 +52,9 @@ func TestCertificates_RunWithoutInitialize(t *testing.T) { } func TestCertificates_EmptyCertificates(t *testing.T) { - certificates, err := NewCertificates(context.Background(), nil) - if err != nil { - t.Fatalf(`error building Certificates: %v`, err) - } + certificates := NewCertificates(context.Background(), nil) - err = certificates.Initialize() + err := certificates.Initialize() if err != nil { t.Fatalf(`error Initializing Certificates: %v`, err) } @@ -92,10 +79,7 @@ func TestCertificates_ExerciseHandlers(t *testing.T) { k8sClient := fake.NewSimpleClientset() - certificates, err := NewCertificates(ctx, k8sClient) - if err != nil { - t.Fatalf(`error building Certificates: %v`, err) - } + certificates := NewCertificates(ctx, k8sClient) _ = certificates.Initialize() From 6ccc4f79620b26f8e3b103c2dfc4fa73ba988d2c Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 14:43:20 -0800 Subject: [PATCH 14/14] Tidy go.mod --- go.mod | 1 - go.sum | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/go.mod b/go.mod index 65fe627..38f6adb 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,6 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum index b3e6449..867f71f 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -253,8 +251,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -302,13 +298,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -317,8 +309,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=