From 92577ca53f858a51a203dd1f240986a59c549191 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Thu, 31 Aug 2023 09:23:04 -0700 Subject: [PATCH 01/30] 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 c28c2480c6d65256df44e3bb0cd2316c22024431 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Fri, 22 Sep 2023 12:49:30 -0700 Subject: [PATCH 02/30] - `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 5a5f34d6b2a36ea94c8b601927fbfca7702fc957 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/30] 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 0d578a1965f3f07b6d60d2728f1864f4b9f7e7ba 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/30] 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 33ad22f15d42173eb94ca8fd6bb008a4b73ed14e Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Fri, 8 Sep 2023 09:16:32 -0700 Subject: [PATCH 05/30] - 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 576a20430fe791800be6f9978a39230c3e982f92 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Tue, 24 Oct 2023 11:30:56 -0700 Subject: [PATCH 06/30] - 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 5dce8cb28b2d29cd658268603fedbe9096c1d355 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Tue, 7 Nov 2023 13:09:16 -0800 Subject: [PATCH 07/30] - 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 5222aa0da377071066fca9b3430e7d3b0b1526a7 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Tue, 7 Nov 2023 13:10:24 -0800 Subject: [PATCH 08/30] - 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 5c9adeea0373447fdbb941759b81755677751546 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:17:37 -0800 Subject: [PATCH 09/30] 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 345ad78ceed698eeeb2f468aa1957ff18861cb15 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:19:22 -0800 Subject: [PATCH 10/30] 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 b11266413fced8e23af9f2c50fb32f81526a57ae Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:20:30 -0800 Subject: [PATCH 11/30] 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 73d1d6034d6e09f23ed5644bc44b41a8de0069e5 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:27:26 -0800 Subject: [PATCH 12/30] - 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 d4aadb69b63c55a51044cf5e1a4121c225114068 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 13:33:30 -0800 Subject: [PATCH 13/30] - 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 4c03217f1f25619e9084b3b34a71465189b1e761 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 14:43:20 -0800 Subject: [PATCH 14/30] 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= From 5bfb56927922fd17899f61d8c8b1767767172194 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:55:21 +0000 Subject: [PATCH 15/30] Bump github/codeql-action from 2.22.3 to 2.22.8 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.22.3 to 2.22.8. - [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/0116bc2df50751f9724a2e35ef1f24d22f90e4e1...407ffafae6a767df3e0230c3df91b6443ae8df75) --- 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 | 2 +- .github/workflows/scorecard.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-sign-image.yml b/.github/workflows/build-and-sign-image.yml index 1a84e17..1692060 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -73,7 +73,7 @@ jobs: ignore-unfixed: 'true' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@00e563ead9f72a8461b24876bee2d0c2e8bd2ee8 # v2.2.11 + uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # 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 75ce997..cdbece1 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -68,6 +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 + uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 with: sarif_file: results.sarif From b048157905a987395d508a0e8aa21959e17f5c8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:54:49 +0000 Subject: [PATCH 16/30] Bump docker/build-push-action from 5.0.0 to 5.1.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/0565240e2d4ab88bba5387d719585280857ece09...4a13e500e55cf31b7a5d59a38ab2040ab0f42f56) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... 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 1692060..7686e80 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -47,7 +47,7 @@ jobs: - name: Build Docker Image id: docker-build-and-push - uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 with: context: . file: ./Dockerfile From adf3a5eebca6fa9904b137bd9a88ad5a7814853f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:55:33 +0000 Subject: [PATCH 17/30] Bump sigstore/cosign-installer from 3.1.2 to 3.2.0 Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.1.2 to 3.2.0. - [Release notes](https://github.com/sigstore/cosign-installer/releases) - [Commits](https://github.com/sigstore/cosign-installer/compare/11086d25041f77fe8fe7b9ea4e48e3b9192b8f19...1fc5bd396d372bee37d608f955b336615edf79c8) --- updated-dependencies: - dependency-name: sigstore/cosign-installer dependency-type: direct:production update-type: version-update:semver-minor ... 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 7686e80..3a9bfdd 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@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 #v3.0.2 + uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 #v3.0.2 with: cosign-release: 'v1.13.1' From 5548fb1bc4d1dc5b1b0cfae291f180a1fa12b007 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 00:10:52 +0000 Subject: [PATCH 18/30] Bump aquasecurity/trivy-action from 0.12.0 to 0.14.0 Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.12.0 to 0.14.0. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/fbd16365eb88e12433951383f5e99bd901fc618f...2b6a709cf9c4025c5438138008beaddbb02086f0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor ... 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 3a9bfdd..ceb30fa 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -64,7 +64,7 @@ jobs: # NOTE: This runs statically against the latest tag in Docker Hub which was not produced by this workflow # This should be updated once this workflow is fully implemented - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@fbd16365eb88e12433951383f5e99bd901fc618f # 0.12.0 + uses: aquasecurity/trivy-action@2b6a709cf9c4025c5438138008beaddbb02086f0 # 0.14.0 continue-on-error: true with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest From f77cc0b9d8695187331f7250d3aa071f1b79f58b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 00:38:43 +0000 Subject: [PATCH 19/30] Bump ossf/scorecard-action from 2.3.0 to 2.3.1 Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/483ef80eb98fb506c348f7d62e28055e49fe2398...0864cf19026789058feabb7e87baa5f140aac736) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index cdbece1..0e0d49c 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -37,7 +37,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0 + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 with: results_file: results.sarif results_format: sarif From b48ad7f934ff97c67eafa4e30b6f3bbead29166a Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Mon, 27 Nov 2023 15:15:49 -0800 Subject: [PATCH 20/30] Rename arguments for clarity and ensure correct usage --- internal/certification/certificates.go | 2 +- internal/configuration/settings.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go index 6595cf9..ac31702 100644 --- a/internal/certification/certificates.go +++ b/internal/certification/certificates.go @@ -176,7 +176,7 @@ func (c *Certificates) handleDeleteEvent(obj interface{}) { logrus.Debugf("Certificates::handleDeleteEvent: certificates (%d)", len(c.Certificates)) } -func (c *Certificates) handleUpdateEvent(oldValue interface{}, newValue interface{}) { +func (c *Certificates) handleUpdateEvent(_ interface{}, newValue interface{}) { logrus.Debug("Certificates::handleUpdateEvent") secret, ok := newValue.(*corev1.Secret) diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 3c2715b..624dfbc 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -266,10 +266,10 @@ func (s *Settings) handleDeleteEvent(obj interface{}) { } } -func (s *Settings) handleUpdateEvent(_ interface{}, obj interface{}) { +func (s *Settings) handleUpdateEvent(_ interface{}, newValue interface{}) { logrus.Debug("Settings::handleUpdateEvent") - configMap, yes := isOurConfig(obj) + configMap, yes := isOurConfig(newValue) if !yes { return } From 46904859cdbb3bc0a46c0ade12d3b14b9ae751c3 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Wed, 29 Nov 2023 15:16:06 -0800 Subject: [PATCH 21/30] Remove the development feature warning --- docs/tls/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/tls/README.md b/docs/tls/README.md index 19326f4..2827aad 100644 --- a/docs/tls/README.md +++ b/docs/tls/README.md @@ -1,8 +1,5 @@ # 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 From 360536bb456a317dd177487deb82023d6f477788 Mon Sep 17 00:00:00 2001 From: "Sergey A. Osokin" Date: Wed, 29 Nov 2023 17:46:20 -0500 Subject: [PATCH 22/30] Add Helm Charts --- charts/nlk/.helmignore | 2 + charts/nlk/Chart.yaml | 16 +++ charts/nlk/templates/_helpers.tpl | 110 ++++++++++++++++ charts/nlk/templates/clusterrole.yaml | 19 +++ charts/nlk/templates/clusterrolebinding.yaml | 15 +++ charts/nlk/templates/nlk-configmap.yaml | 13 ++ charts/nlk/templates/nlk-deployment.yaml | 44 +++++++ charts/nlk/templates/nlk-secret.yaml | 8 ++ charts/nlk/templates/nlk-serviceaccount.yaml | 7 ++ charts/nlk/values.yaml | 126 +++++++++++++++++++ 10 files changed, 360 insertions(+) create mode 100644 charts/nlk/.helmignore create mode 100644 charts/nlk/Chart.yaml create mode 100644 charts/nlk/templates/_helpers.tpl create mode 100644 charts/nlk/templates/clusterrole.yaml create mode 100644 charts/nlk/templates/clusterrolebinding.yaml create mode 100644 charts/nlk/templates/nlk-configmap.yaml create mode 100644 charts/nlk/templates/nlk-deployment.yaml create mode 100644 charts/nlk/templates/nlk-secret.yaml create mode 100644 charts/nlk/templates/nlk-serviceaccount.yaml create mode 100644 charts/nlk/values.yaml diff --git a/charts/nlk/.helmignore b/charts/nlk/.helmignore new file mode 100644 index 0000000..5c131e5 --- /dev/null +++ b/charts/nlk/.helmignore @@ -0,0 +1,2 @@ +# Patterns to ignore when building packages. +.png diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml new file mode 100644 index 0000000..218fbd0 --- /dev/null +++ b/charts/nlk/Chart.yaml @@ -0,0 +1,16 @@ +apiVersion: v2 +appVersion: 0.1.0 +description: NGINX LoadBalancer for Kubernetes +name: nginx-loadbalancer-kubernetes +home: https://github.com/nginxinc/nginx-loadbalancer-kubernetes +icon: https://raw.githubusercontent.com/nginxinc/nginx-loadbalancer-kubernetes/main/nlk-logo.svg +keywords: +- nginx +- loadbalancer +- ingress +kubeVersion: '>= 1.22.0-0' +maintainers: + name: "@ciroque" + name: "@chrisakker" +type: application +version: 0.0.1 diff --git a/charts/nlk/templates/_helpers.tpl b/charts/nlk/templates/_helpers.tpl new file mode 100644 index 0000000..60fa7ee --- /dev/null +++ b/charts/nlk/templates/_helpers.tpl @@ -0,0 +1,110 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "nlk.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "nlk.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create a default fully qualified nlk name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "nlk.nlk.fullname" -}} +{{- printf "%s-%s" (include "nlk.fullname" .) .Values.nlk.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified nlk service name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "nlk.nlk.service.name" -}} +{{- default (include "nlk.nlk.fullname" .) .Values.serviceNameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "nlk.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "nlk.labels" -}} +helm.sh/chart: {{ include "nlk.chart" . }} +{{ include "nlk.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "nlk.selectorLabels" -}} +{{- if .Values.nlk.selectorLabels -}} +{{ toYaml .Values.nlk.selectorLabels }} +{{- else -}} +app.kubernetes.io/name: {{ include "nlk.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} +{{- end -}} + +{{/* +Expand the name of the configmap. +*/}} +{{- define "nlk.configName" -}} +{{- if .Values.nlk.customConfigMap -}} +{{ .Values.nlk.customConfigMap }} +{{- else -}} +{{- default (include "nlk.fullname" .) .Values.nlk.config.name -}} +{{- end -}} +{{- end -}} + +{{/* +Expand service account name. +*/}} +{{- define "nlk.serviceAccountName" -}} +{{- default (include "nlk.fullname" .) .Values.nlk.serviceAccount.name -}} +{{- end -}} + +{{- define "nlk.tag" -}} +{{- default .Chart.AppVersion .Values.nlk.image.tag -}} +{{- end -}} + +{{/* +Expand image name. +*/}} +{{- define "nlk.image" -}} +{{- if .Values.nlk.image.digest -}} +{{- printf "%s@%s" .Values.nlk.image.repository .Values.nlk.image.digest -}} +{{- else -}} +{{- printf "%s:%s" .Values.nlk.image.repository (include "nlk.tag" .) -}} +{{- end -}} +{{- end -}} + +{{- define "nlk.prometheus.serviceName" -}} +{{- printf "%s-%s" (include "nlk.fullname" .) "prometheus-service" -}} +{{- end -}} diff --git a/charts/nlk/templates/clusterrole.yaml b/charts/nlk/templates/clusterrole.yaml new file mode 100644 index 0000000..4d03a4a --- /dev/null +++ b/charts/nlk/templates/clusterrole.yaml @@ -0,0 +1,19 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: resource-get-watch-list + namespace: nlk +rules: + - apiGroups: + - "" + resources: + - configmaps + - nodes + - secrets + - services + verbs: + - get + - list + - watch +{{- end }} diff --git a/charts/nlk/templates/clusterrolebinding.yaml b/charts/nlk/templates/clusterrolebinding.yaml new file mode 100644 index 0000000..b989dfe --- /dev/null +++ b/charts/nlk/templates/clusterrolebinding.yaml @@ -0,0 +1,15 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: "nginx-loadbalancer-kubernetes:resource-get-watch-list" + namespace: nlk +subjects: +- kind: ServiceAccount + name: {{ .Values.nlk.serviceAccount.name }} + namespace: nlk +roleRef: + kind: ClusterRole + name: resource-get-watch-list + apiGroup: rbac.authorization.k8s.io +{{- end }} diff --git a/charts/nlk/templates/nlk-configmap.yaml b/charts/nlk/templates/nlk-configmap.yaml new file mode 100644 index 0000000..8d59573 --- /dev/null +++ b/charts/nlk/templates/nlk-configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: +{{- if .Values.nlk.config.entries.hosts }} + nginx-hosts: "{{ .Values.nlk.config.entries.hosts }}" +{{- end }} + tls-mode: "{{ .Values.nlk.defaultTLS.tls-mode }}" + ca-certificate: "{{ .Values.nlk.defaultTLS.ca-certificate }}" + client-certificate: "{{ .Values.nlk.defaultTLS.client-certificate }}" + log-level: "{{ .Values.nlk.logLevel }}" diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml new file mode 100644 index 0000000..105bd69 --- /dev/null +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-nlk + namespace: nlk + labels: + app: nlk +spec: + replicas: {{ .Values.nlk.replicaCount }} + selector: + matchLabels: + app: nlk + template: + metadata: + labels: + app: nlk + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.nlk.image.repository }}:{{ .Values.nlk.image.tag }}" + imagePullPolicy: {{ .Values.nlk.image.pullPolicy }} + ports: +{{- range $key, $value := .Values.nlk.containerPort }} + - name: {{ $key }} + containerPort: {{ $value }} + protocol: TCP +{{- end }} +{{- if .Values.nlk.liveStatus.enable }} + livenessProbe: + httpGet: + path: /livez + port: {{ .Values.nlk.liveStatus.port }} + initialDelaySeconds: {{ .Values.nlk.liveStatus.initialDelaySeconds }} + periodSeconds: {{ .Values.nlk.readyStatus.periodSeconds }} +{{- end }} +{{- if .Values.nlk.readyStatus.enable }} + readinessProbe: + httpGet: + path: /readyz + port: {{ .Values.nlk.readyStatus.port }} + initialDelaySeconds: {{ .Values.nlk.readyStatus.initialDelaySeconds }} + periodSeconds: {{ .Values.nlk.readyStatus.periodSeconds }} +{{- end }} + serviceAccountName: {{ .Values.nlk.serviceAccount.name }} diff --git a/charts/nlk/templates/nlk-secret.yaml b/charts/nlk/templates/nlk-secret.yaml new file mode 100644 index 0000000..e3cae1b --- /dev/null +++ b/charts/nlk/templates/nlk-secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: nginx-loadbalancer-kubernetes-secret + namespace: nlk + annotations: + kubernetes.io/service-account.name: {{ .Values.nlk.serviceAccount.name }} +type: kubernetes.io/service-account-token diff --git a/charts/nlk/templates/nlk-serviceaccount.yaml b/charts/nlk/templates/nlk-serviceaccount.yaml new file mode 100644 index 0000000..a9d3560 --- /dev/null +++ b/charts/nlk/templates/nlk-serviceaccount.yaml @@ -0,0 +1,7 @@ +{{- if .Values.rbac.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nginx-loadbalancer-kubernetes + namespace: nlk +{{- end }} diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml new file mode 100644 index 0000000..b8a74dd --- /dev/null +++ b/charts/nlk/values.yaml @@ -0,0 +1,126 @@ +nlk: + name: nginx-loadbalancer-kubernetes + + kind: deployment + + replicaCount: 1 + + image: + repository: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: latest + + imagePullSecrets: [] + nameOverride: "" + fullnameOverride: "" + + serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: nginx-loadbalancer-kubernetes + + podAnnotations: {} + podLabels: {} + + podSecurityContext: {} + # fsGroup: 2000 + + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + service: + type: ClusterIP + port: 80 + + ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + resources: + requests: + cpu: 100m + memory: 128Mi + # limits: + # cpu: 100m + # memory: 128Mi + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + + # Additional volumes on the output Deployment definition. + volumes: [] + # - name: foo + # secret: + # secretName: mysecret + # optional: false + + # Additional volumeMounts on the output Deployment definition. + volumeMounts: [] + # - name: foo + # mountPath: "/etc/foo" + # readOnly: true + + nodeSelector: {} + + tolerations: [] + + affinity: {} + + config: + entries: + hosts: + "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + + defaultTLS: + tls-mode: "no-tls" + ca-certificate: "" + client-certificate: "" + + logLevel: "warn" + + containerPort: + http: 51031 + + liveStatus: + enable: true + port: 51031 + initialDelaySeconds: 5 + periodSeconds: 2 + + readyStatus: + enable: true + port: 51031 + initialDelaySeconds: 5 + periodSeconds: 2 + +rbac: + ## Configures RBAC. + create: true From 110352c525c0167a9a763facb0072d4bbfc566eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 00:40:32 +0000 Subject: [PATCH 23/30] Bump aquasecurity/trivy-action from 0.14.0 to 0.16.0 Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.14.0 to 0.16.0. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/2b6a709cf9c4025c5438138008beaddbb02086f0...91713af97dc80187565512baba96e4364e983601) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor ... 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 ceb30fa..5944799 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -64,7 +64,7 @@ jobs: # NOTE: This runs statically against the latest tag in Docker Hub which was not produced by this workflow # This should be updated once this workflow is fully implemented - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@2b6a709cf9c4025c5438138008beaddbb02086f0 # 0.14.0 + uses: aquasecurity/trivy-action@91713af97dc80187565512baba96e4364e983601 # 0.16.0 continue-on-error: true with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest From b95f49d3ed64cc985b8116f4a71327e86b0d398a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 00:40:28 +0000 Subject: [PATCH 24/30] Bump actions/setup-go from 4 to 5 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 3d0c238..72ae009 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: 1.19 From 956181f54e3fda0dccf205105db917fc673586aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 00:40:24 +0000 Subject: [PATCH 25/30] Bump docker/metadata-action from 4.6.0 to 5.3.0 Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.6.0 to 5.3.0. - [Release notes](https://github.com/docker/metadata-action/releases) - [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md) - [Commits](https://github.com/docker/metadata-action/compare/818d4b7b91585d195f67373fd9cb0332e31a7175...31cebacef4805868f9ce9a0cb03ee36c32df2ac4) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-type: direct:production update-type: version-update:semver-major ... 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 5944799..9502516 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -41,7 +41,7 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 + uses: docker/metadata-action@31cebacef4805868f9ce9a0cb03ee36c32df2ac4 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} From 6329274920faab3f36f808fe1a778f6cbc3bf16f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 20:52:57 +0000 Subject: [PATCH 26/30] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... 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 9502516..52d3f8e 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install cosign uses: sigstore/cosign-installer@1fc5bd396d372bee37d608f955b336615edf79c8 #v3.0.2 From caa76e4b1302825a8aafabe59894484902fe4614 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 00:43:14 +0000 Subject: [PATCH 27/30] Bump docker/login-action from 2.2.0 to 3.0.0 Bumps [docker/login-action](https://github.com/docker/login-action) from 2.2.0 to 3.0.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/465a07811f14bebb1938fbed4728c6a1ff8901fc...343f7c4344506bcbf9b4de18042ae17996df046d) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... 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 52d3f8e..250bd6d 100644 --- a/.github/workflows/build-and-sign-image.yml +++ b/.github/workflows/build-and-sign-image.yml @@ -33,7 +33,7 @@ jobs: cosign-release: 'v1.13.1' - name: Log into registry ${{ env.REGISTRY }} for ${{ github.actor }} - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} From 537b14a26182d7e0b9bd6d2a17551316880b93f8 Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Thu, 7 Dec 2023 15:30:38 -0800 Subject: [PATCH 28/30] TLS Mode is now an enum - Improve enum readability - Improve TLS Mode configuration validation and application - Better error message --- cmd/tls-config-factory-test-harness/main.go | 8 +-- internal/authentication/factory.go | 16 +++-- internal/authentication/factory_test.go | 37 +++-------- internal/configuration/settings.go | 28 +++++--- internal/configuration/tlsmodes.go | 46 +++++++++++++ internal/configuration/tlsmodes_test.go | 74 +++++++++++++++++++++ 6 files changed, 163 insertions(+), 46 deletions(-) create mode 100644 internal/configuration/tlsmodes.go create mode 100644 internal/configuration/tlsmodes_test.go diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go index 3e9ef48..b5fda78 100644 --- a/cmd/tls-config-factory-test-harness/main.go +++ b/cmd/tls-config-factory-test-harness/main.go @@ -87,7 +87,7 @@ func ssTlsConfig() configuration.Settings { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) return configuration.Settings{ - TlsMode: "ss-tls", + TlsMode: configuration.SelfSignedTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, @@ -100,7 +100,7 @@ func ssMtlsConfig() configuration.Settings { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) return configuration.Settings{ - TlsMode: "ss-mtls", + TlsMode: configuration.SelfSignedMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, @@ -109,7 +109,7 @@ func ssMtlsConfig() configuration.Settings { func caTlsConfig() configuration.Settings { return configuration.Settings{ - TlsMode: "ca-tls", + TlsMode: configuration.CertificateAuthorityTLS, } } @@ -118,7 +118,7 @@ func caMtlsConfig() configuration.Settings { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) return configuration.Settings{ - TlsMode: "ca-mtls", + TlsMode: configuration.CertificateAuthorityMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go index 293989b..8b8d06e 100644 --- a/internal/authentication/factory.go +++ b/internal/authentication/factory.go @@ -20,20 +20,24 @@ import ( func NewTlsConfig(settings *configuration.Settings) (*tls.Config, error) { logrus.Debugf("authentication::NewTlsConfig Creating TLS config for mode: '%s'", settings.TlsMode) switch settings.TlsMode { - case "ss-tls": // needs ca cert + + case configuration.NoTLS: + return buildBasicTlsConfig(true), nil + + case configuration.SelfSignedTLS: // needs ca cert return buildSelfSignedTlsConfig(settings.Certificates) - case "ss-mtls": // needs ca cert and client cert + case configuration.SelfSignedMutualTLS: // needs ca cert and client cert return buildSelfSignedMtlsConfig(settings.Certificates) - case "ca-tls": // needs nothing + case configuration.CertificateAuthorityTLS: // needs nothing return buildBasicTlsConfig(false), nil - case "ca-mtls": // needs client cert + case configuration.CertificateAuthorityMutualTLS: // needs client cert return buildCaTlsConfig(settings.Certificates) - default: // no-tls, needs nothing - return buildBasicTlsConfig(true), nil + default: + return nil, fmt.Errorf("unknown TLS mode: %s", settings.TlsMode) } } diff --git a/internal/authentication/factory_test.go b/internal/authentication/factory_test.go index d106eb7..7ecd610 100644 --- a/internal/authentication/factory_test.go +++ b/internal/authentication/factory_test.go @@ -16,25 +16,6 @@ const ( 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{} @@ -57,7 +38,7 @@ func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) settings := configuration.Settings{ - TlsMode: "ss-tls", + TlsMode: configuration.SelfSignedTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -92,7 +73,7 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) settings := configuration.Settings{ - TlsMode: "ss-tls", + TlsMode: configuration.SelfSignedTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, @@ -113,7 +94,7 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificateDataPEM()) settings := configuration.Settings{ - TlsMode: "ss-tls", + TlsMode: configuration.SelfSignedTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -137,7 +118,7 @@ func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) settings := configuration.Settings{ - TlsMode: "ss-mtls", + TlsMode: configuration.SelfSignedMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -173,7 +154,7 @@ func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) settings := configuration.Settings{ - TlsMode: "ss-mtls", + TlsMode: configuration.SelfSignedMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, @@ -195,7 +176,7 @@ func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) settings := configuration.Settings{ - TlsMode: "ss-mtls", + TlsMode: configuration.SelfSignedMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -215,7 +196,7 @@ func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { func TestTlsFactory_CaTlsMode(t *testing.T) { settings := configuration.Settings{ - TlsMode: "ca-tls", + TlsMode: configuration.CertificateAuthorityTLS, } tlsConfig, err := NewTlsConfig(&settings) @@ -245,7 +226,7 @@ func TestTlsFactory_CaMtlsMode(t *testing.T) { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) settings := configuration.Settings{ - TlsMode: "ca-mtls", + TlsMode: configuration.CertificateAuthorityMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, CaCertificateSecretKey: CaCertificateSecretKey, @@ -281,7 +262,7 @@ func TestTlsFactory_CaMtlsModeClientCertificateError(t *testing.T) { certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) settings := configuration.Settings{ - TlsMode: "ca-mtls", + TlsMode: configuration.CertificateAuthorityMutualTLS, Certificates: &certification.Certificates{ Certificates: certificates, }, diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 624dfbc..d15ad84 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -110,7 +110,7 @@ type Settings struct { 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 + TlsMode TLSMode // Certificates is the object used to retrieve the certificates and keys used to communicate with the Border Servers. Certificates *certification.Certificates @@ -139,7 +139,7 @@ func NewSettings(ctx context.Context, k8sClient kubernetes.Interface) (*Settings settings := &Settings{ Context: ctx, K8sClient: k8sClient, - TlsMode: "", + TlsMode: NoTLS, Certificates: nil, Handler: HandlerSettings{ RetryCount: 5, @@ -282,13 +282,12 @@ func (s *Settings) handleUpdateEvent(_ interface{}, newValue interface{}) { 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) + tlsMode, err := validateTlsMode(configMap) + if err != nil { + // NOTE: the TLSMode defaults to NoTLS on startup, or the last known good value if previously set. + logrus.Errorf("There was an error with the configured TLS Mode. TLS Mode has NOT been changed. The current mode is: '%v'. Error: %v. ", s.TlsMode, err) } else { - s.TlsMode = "no-tls" - logrus.Warnf("Settings::handleUpdateEvent: tls-mode key not found in ConfigMap, defaulting to 'no-tls'") + s.TlsMode = tlsMode } caCertificateSecretKey, found := configMap.Data["ca-certificate"] @@ -314,6 +313,19 @@ func (s *Settings) handleUpdateEvent(_ interface{}, newValue interface{}) { logrus.Debugf("Settings::handleUpdateEvent: \n\tHosts: %v,\n\tSettings: %v ", s.NginxPlusHosts, configMap) } +func validateTlsMode(configMap *corev1.ConfigMap) (TLSMode, error) { + tlsConfigMode, tlsConfigModeFound := configMap.Data["tls-mode"] + if !tlsConfigModeFound { + return NoTLS, fmt.Errorf(`tls-mode key not found in ConfigMap`) + } + + if tlsMode, tlsModeFound := TLSModeMap[tlsConfigMode]; tlsModeFound { + return tlsMode, nil + } + + return NoTLS, fmt.Errorf(`invalid tls-mode value: %s`, tlsConfigMode) +} + func (s *Settings) parseHosts(hosts string) []string { return strings.Split(hosts, ",") } diff --git a/internal/configuration/tlsmodes.go b/internal/configuration/tlsmodes.go new file mode 100644 index 0000000..2f7271f --- /dev/null +++ b/internal/configuration/tlsmodes.go @@ -0,0 +1,46 @@ +/* + * 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 configuration + +const ( + NoTLS TLSMode = iota + CertificateAuthorityTLS + CertificateAuthorityMutualTLS + SelfSignedTLS + SelfSignedMutualTLS +) + +const ( + NoTLSString = "no-tls" + CertificateAuthorityTLSString = "ca-tls" + CertificateAuthorityMutualTLSString = "ca-mtls" + SelfSignedTLSString = "ss-tls" + SelfSignedMutualTLSString = "ss-mtls" +) + +type TLSMode int + +var TLSModeMap = map[string]TLSMode{ + NoTLSString: NoTLS, + CertificateAuthorityTLSString: CertificateAuthorityTLS, + CertificateAuthorityMutualTLSString: CertificateAuthorityMutualTLS, + SelfSignedTLSString: SelfSignedTLS, + SelfSignedMutualTLSString: SelfSignedMutualTLS, +} + +func (t TLSMode) String() string { + modes := []string{ + NoTLSString, + CertificateAuthorityTLSString, + CertificateAuthorityMutualTLSString, + SelfSignedTLSString, + SelfSignedMutualTLSString, + } + if t < NoTLS || t > SelfSignedMutualTLS { + return "" + } + return modes[t] +} diff --git a/internal/configuration/tlsmodes_test.go b/internal/configuration/tlsmodes_test.go new file mode 100644 index 0000000..62abf96 --- /dev/null +++ b/internal/configuration/tlsmodes_test.go @@ -0,0 +1,74 @@ +/* + * 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 configuration + +import ( + "testing" +) + +func Test_String(t *testing.T) { + mode := NoTLS.String() + if mode != "no-tls" { + t.Errorf("Expected TLSModeNoTLS to be 'no-tls', got '%s'", mode) + } + + mode = CertificateAuthorityTLS.String() + if mode != "ca-tls" { + t.Errorf("Expected TLSModeCaTLS to be 'ca-tls', got '%s'", mode) + } + + mode = CertificateAuthorityMutualTLS.String() + if mode != "ca-mtls" { + t.Errorf("Expected TLSModeCaMTLS to be 'ca-mtls', got '%s'", mode) + } + + mode = SelfSignedTLS.String() + if mode != "ss-tls" { + t.Errorf("Expected TLSModeSsTLS to be 'ss-tls', got '%s'", mode) + } + + mode = SelfSignedMutualTLS.String() + if mode != "ss-mtls" { + t.Errorf("Expected TLSModeSsMTLS to be 'ss-mtls', got '%s',", mode) + } + + mode = TLSMode(5).String() + if mode != "" { + t.Errorf("Expected TLSMode(5) to be '', got '%s'", mode) + } +} + +func Test_TLSModeMap(t *testing.T) { + mode := TLSModeMap["no-tls"] + if mode != NoTLS { + t.Errorf("Expected TLSModeMap['no-tls'] to be TLSModeNoTLS, got '%d'", mode) + } + + mode = TLSModeMap["ca-tls"] + if mode != CertificateAuthorityTLS { + t.Errorf("Expected TLSModeMap['ca-tls'] to be TLSModeCaTLS, got '%d'", mode) + } + + mode = TLSModeMap["ca-mtls"] + if mode != CertificateAuthorityMutualTLS { + t.Errorf("Expected TLSModeMap['ca-mtls'] to be TLSModeCaMTLS, got '%d'", mode) + } + + mode = TLSModeMap["ss-tls"] + if mode != SelfSignedTLS { + t.Errorf("Expected TLSModeMap['ss-tls'] to be TLSModeSsTLS, got '%d'", mode) + } + + mode = TLSModeMap["ss-mtls"] + if mode != SelfSignedMutualTLS { + t.Errorf("Expected TLSModeMap['ss-mtls'] to be TLSModeSsMTLS, got '%d'", mode) + } + + mode = TLSModeMap["invalid"] + if mode != TLSMode(0) { + t.Errorf("Expected TLSModeMap['invalid'] to be TLSMode(0), got '%d'", mode) + } +} From 24ea3e75ad8b39ca11a56035ba6dc0bb3d3e8a2e Mon Sep 17 00:00:00 2001 From: abdennour Date: Sat, 16 Dec 2023 01:54:24 +0300 Subject: [PATCH 29/30] fix: helm Chart erros + hard coding (#159) Still hardcoding namespace because the application cannot use another namespaces . it resolves #158 Special thanks to @abdennour for this helpful contribution! ---------- Co-authored-by: @ciroque --- .gitattributes | 3 +++ charts/nlk/Chart.yaml | 7 +++++-- charts/nlk/templates/_helpers.tpl | 4 ++-- charts/nlk/templates/clusterrole.yaml | 3 +-- charts/nlk/templates/clusterrolebinding.yaml | 7 +++---- charts/nlk/templates/nlk-configmap.yaml | 7 ++++--- charts/nlk/templates/nlk-deployment.yaml | 6 +++--- charts/nlk/templates/nlk-secret.yaml | 4 ++-- charts/nlk/templates/nlk-serviceaccount.yaml | 2 +- charts/nlk/values.yaml | 6 ++---- 10 files changed, 26 insertions(+), 23 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4f80541 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text eol=lf + +*.png binary \ No newline at end of file diff --git a/charts/nlk/Chart.yaml b/charts/nlk/Chart.yaml index 218fbd0..c11d885 100644 --- a/charts/nlk/Chart.yaml +++ b/charts/nlk/Chart.yaml @@ -1,3 +1,4 @@ +--- apiVersion: v2 appVersion: 0.1.0 description: NGINX LoadBalancer for Kubernetes @@ -10,7 +11,9 @@ keywords: - ingress kubeVersion: '>= 1.22.0-0' maintainers: - name: "@ciroque" - name: "@chrisakker" +- name: "@ciroque" +- name: "@chrisakker" +- name: "@abdennour" + type: application version: 0.0.1 diff --git a/charts/nlk/templates/_helpers.tpl b/charts/nlk/templates/_helpers.tpl index 60fa7ee..17a6405 100644 --- a/charts/nlk/templates/_helpers.tpl +++ b/charts/nlk/templates/_helpers.tpl @@ -99,9 +99,9 @@ Expand image name. */}} {{- define "nlk.image" -}} {{- if .Values.nlk.image.digest -}} -{{- printf "%s@%s" .Values.nlk.image.repository .Values.nlk.image.digest -}} +{{- printf "%s/%s@%s" .Values.nlk.image.registry .Values.nlk.image.repository .Values.nlk.image.digest -}} {{- else -}} -{{- printf "%s:%s" .Values.nlk.image.repository (include "nlk.tag" .) -}} +{{- printf "%s/%s:%s" .Values.nlk.image.registry .Values.nlk.image.repository (include "nlk.tag" .) -}} {{- end -}} {{- end -}} diff --git a/charts/nlk/templates/clusterrole.yaml b/charts/nlk/templates/clusterrole.yaml index 4d03a4a..4164475 100644 --- a/charts/nlk/templates/clusterrole.yaml +++ b/charts/nlk/templates/clusterrole.yaml @@ -2,8 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: resource-get-watch-list - namespace: nlk + name: {{ .Release.Namespace }}-{{ include "nlk.fullname" . }} rules: - apiGroups: - "" diff --git a/charts/nlk/templates/clusterrolebinding.yaml b/charts/nlk/templates/clusterrolebinding.yaml index b989dfe..0ccd455 100644 --- a/charts/nlk/templates/clusterrolebinding.yaml +++ b/charts/nlk/templates/clusterrolebinding.yaml @@ -2,14 +2,13 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: "nginx-loadbalancer-kubernetes:resource-get-watch-list" - namespace: nlk + name: {{ .Release.Namespace }}-{{ include "nlk.fullname" . }} subjects: - kind: ServiceAccount - name: {{ .Values.nlk.serviceAccount.name }} + name: {{ include "nlk.fullname" . }} namespace: nlk roleRef: kind: ClusterRole - name: resource-get-watch-list + name: {{ .Release.Namespace }}-{{ include "nlk.fullname" . }} apiGroup: rbac.authorization.k8s.io {{- end }} diff --git a/charts/nlk/templates/nlk-configmap.yaml b/charts/nlk/templates/nlk-configmap.yaml index 8d59573..482b8cb 100644 --- a/charts/nlk/templates/nlk-configmap.yaml +++ b/charts/nlk/templates/nlk-configmap.yaml @@ -7,7 +7,8 @@ data: {{- if .Values.nlk.config.entries.hosts }} nginx-hosts: "{{ .Values.nlk.config.entries.hosts }}" {{- end }} - tls-mode: "{{ .Values.nlk.defaultTLS.tls-mode }}" - ca-certificate: "{{ .Values.nlk.defaultTLS.ca-certificate }}" - client-certificate: "{{ .Values.nlk.defaultTLS.client-certificate }}" + tls-mode: "{{ index .Values.nlk.defaultTLS "tls-mode" }}" + ca-certificate: "{{ index .Values.nlk.defaultTLS "ca-certificate" }}" + client-certificate: "{{ index .Values.nlk.defaultTLS "client-certificate" }}" log-level: "{{ .Values.nlk.logLevel }}" + diff --git a/charts/nlk/templates/nlk-deployment.yaml b/charts/nlk/templates/nlk-deployment.yaml index 105bd69..fb55d77 100644 --- a/charts/nlk/templates/nlk-deployment.yaml +++ b/charts/nlk/templates/nlk-deployment.yaml @@ -1,7 +1,7 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Release.Name }}-nlk + name: {{ include "nlk.fullname" . }} namespace: nlk labels: app: nlk @@ -17,7 +17,7 @@ spec: spec: containers: - name: {{ .Chart.Name }} - image: "{{ .Values.nlk.image.repository }}:{{ .Values.nlk.image.tag }}" + image: {{ include "nlk.image" .}} imagePullPolicy: {{ .Values.nlk.image.pullPolicy }} ports: {{- range $key, $value := .Values.nlk.containerPort }} @@ -41,4 +41,4 @@ spec: initialDelaySeconds: {{ .Values.nlk.readyStatus.initialDelaySeconds }} periodSeconds: {{ .Values.nlk.readyStatus.periodSeconds }} {{- end }} - serviceAccountName: {{ .Values.nlk.serviceAccount.name }} + serviceAccountName: {{ include "nlk.fullname" . }} diff --git a/charts/nlk/templates/nlk-secret.yaml b/charts/nlk/templates/nlk-secret.yaml index e3cae1b..ff7d7ff 100644 --- a/charts/nlk/templates/nlk-secret.yaml +++ b/charts/nlk/templates/nlk-secret.yaml @@ -1,8 +1,8 @@ apiVersion: v1 kind: Secret metadata: - name: nginx-loadbalancer-kubernetes-secret + name: {{ include "nlk.fullname" . }} namespace: nlk annotations: - kubernetes.io/service-account.name: {{ .Values.nlk.serviceAccount.name }} + kubernetes.io/service-account.name: {{ include "nlk.fullname" . }} type: kubernetes.io/service-account-token diff --git a/charts/nlk/templates/nlk-serviceaccount.yaml b/charts/nlk/templates/nlk-serviceaccount.yaml index a9d3560..5bdca4f 100644 --- a/charts/nlk/templates/nlk-serviceaccount.yaml +++ b/charts/nlk/templates/nlk-serviceaccount.yaml @@ -2,6 +2,6 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: nginx-loadbalancer-kubernetes + name: {{ include "nlk.fullname" . }} namespace: nlk {{- end }} diff --git a/charts/nlk/values.yaml b/charts/nlk/values.yaml index b8a74dd..394bc1f 100644 --- a/charts/nlk/values.yaml +++ b/charts/nlk/values.yaml @@ -6,7 +6,8 @@ nlk: replicaCount: 1 image: - repository: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes + registry: ghcr.io + repository: nginxinc/nginx-loadbalancer-kubernetes pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. tag: latest @@ -22,9 +23,6 @@ nlk: automount: true # Annotations to add to the service account annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: nginx-loadbalancer-kubernetes podAnnotations: {} podLabels: {} From 2ee0441d6561c6910ae89dca9611a8ebafe35a97 Mon Sep 17 00:00:00 2001 From: Javier Evans <4.1.4.1.done@gmail.com> Date: Fri, 15 Dec 2023 14:58:19 -0800 Subject: [PATCH 30/30] fix: use SecretBytes type for cert values to prevent accidental printing (#147) We save the values of the provided certs that we retrieve from Kubernetes secrets in the `Certificates` attribute on the `Certificates` struct. This is sensitive information that we want to make sure stays out of the logs and any stack traces. A common approach to this is to create a type definition for sensitive values that implements `Stringer` and `JSON` interfaces and cast the sensitive data to that value. Fixes issues #145 --- .tool-versions | 1 + cmd/tls-config-factory-test-harness/main.go | 21 +++++++------ internal/authentication/factory_test.go | 34 +++++++++++---------- internal/certification/certificates.go | 19 ++++++++---- internal/core/secret_bytes.go | 21 +++++++++++++ internal/core/secret_bytes_test.go | 31 +++++++++++++++++++ 6 files changed, 95 insertions(+), 32 deletions(-) create mode 100644 .tool-versions create mode 100644 internal/core/secret_bytes.go create mode 100644 internal/core/secret_bytes_test.go diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..09548d5 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.19.13 diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go index b5fda78..3f46d4f 100644 --- a/cmd/tls-config-factory-test-harness/main.go +++ b/cmd/tls-config-factory-test-harness/main.go @@ -6,6 +6,7 @@ import ( "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/nginxinc/kubernetes-nginx-ingress/internal/core" "github.com/sirupsen/logrus" "os" ) @@ -82,7 +83,7 @@ func buildConfigMap() map[string]TlsConfiguration { } func ssTlsConfig() configuration.Settings { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) @@ -95,7 +96,7 @@ func ssTlsConfig() configuration.Settings { } func ssMtlsConfig() configuration.Settings { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) @@ -114,7 +115,7 @@ func caTlsConfig() configuration.Settings { } func caMtlsConfig() configuration.Settings { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) return configuration.Settings{ @@ -215,15 +216,15 @@ z/3KkMx4uqJXZyvQrmkolSg= ` } -func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string][]byte { - return map[string][]byte{ - certification.CertificateKey: []byte(certificatePEM), - certification.CertificateKeyKey: []byte(keyPEM), +func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string]core.SecretBytes { + return map[string]core.SecretBytes{ + certification.CertificateKey: core.SecretBytes([]byte(certificatePEM)), + certification.CertificateKeyKey: core.SecretBytes([]byte(keyPEM)), } } -func buildCaCertificateEntry(certificatePEM string) map[string][]byte { - return map[string][]byte{ - certification.CertificateKey: []byte(certificatePEM), +func buildCaCertificateEntry(certificatePEM string) map[string]core.SecretBytes { + return map[string]core.SecretBytes{ + certification.CertificateKey: core.SecretBytes([]byte(certificatePEM)), } } diff --git a/internal/authentication/factory_test.go b/internal/authentication/factory_test.go index 7ecd610..a535200 100644 --- a/internal/authentication/factory_test.go +++ b/internal/authentication/factory_test.go @@ -6,9 +6,11 @@ package authentication import ( + "testing" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" - "testing" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" ) const ( @@ -34,7 +36,7 @@ func TestTlsFactory_UnspecifiedModeDefaultsToNoTls(t *testing.T) { } func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) settings := configuration.Settings{ @@ -69,7 +71,7 @@ func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { } func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) settings := configuration.Settings{ @@ -90,7 +92,7 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { } func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificateDataPEM()) settings := configuration.Settings{ @@ -113,7 +115,7 @@ func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) } func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) @@ -149,7 +151,7 @@ func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { } func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) @@ -171,7 +173,7 @@ func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { } func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) @@ -222,7 +224,7 @@ func TestTlsFactory_CaTlsMode(t *testing.T) { } func TestTlsFactory_CaMtlsMode(t *testing.T) { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) settings := configuration.Settings{ @@ -257,7 +259,7 @@ func TestTlsFactory_CaMtlsMode(t *testing.T) { } func TestTlsFactory_CaMtlsModeClientCertificateError(t *testing.T) { - certificates := make(map[string]map[string][]byte) + certificates := make(map[string]map[string]core.SecretBytes) certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) @@ -411,15 +413,15 @@ z/3KkMx4uqJXZyvQrmkolSg= ` } -func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string][]byte { - return map[string][]byte{ - certification.CertificateKey: []byte(certificatePEM), - certification.CertificateKeyKey: []byte(keyPEM), +func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string]core.SecretBytes { + return map[string]core.SecretBytes{ + certification.CertificateKey: core.SecretBytes([]byte(certificatePEM)), + certification.CertificateKeyKey: core.SecretBytes([]byte(keyPEM)), } } -func buildCaCertificateEntry(certificatePEM string) map[string][]byte { - return map[string][]byte{ - certification.CertificateKey: []byte(certificatePEM), +func buildCaCertificateEntry(certificatePEM string) map[string]core.SecretBytes { + return map[string]core.SecretBytes{ + certification.CertificateKey: core.SecretBytes([]byte(certificatePEM)), } } diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go index ac31702..53bd843 100644 --- a/internal/certification/certificates.go +++ b/internal/certification/certificates.go @@ -11,11 +11,14 @@ 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" + + "github.com/nginxinc/kubernetes-nginx-ingress/internal/core" ) const ( @@ -30,7 +33,7 @@ const ( ) type Certificates struct { - Certificates map[string]map[string][]byte + Certificates map[string]map[string]core.SecretBytes // Context is the context used to control the application. Context context.Context @@ -61,14 +64,14 @@ func NewCertificates(ctx context.Context, k8sClient kubernetes.Interface) *Certi } // GetCACertificate returns the Certificate Authority certificate. -func (c *Certificates) GetCACertificate() []byte { +func (c *Certificates) GetCACertificate() core.SecretBytes { bytes := c.Certificates[c.CaCertificateSecretKey][CertificateKey] return bytes } // GetClientCertificate returns the Client certificate and key. -func (c *Certificates) GetClientCertificate() ([]byte, []byte) { +func (c *Certificates) GetClientCertificate() (core.SecretBytes, core.SecretBytes) { keyBytes := c.Certificates[c.ClientCertificateSecretKey][CertificateKeyKey] certificateBytes := c.Certificates[c.ClientCertificateSecretKey][CertificateKey] @@ -81,7 +84,7 @@ func (c *Certificates) Initialize() error { var err error - c.Certificates = make(map[string]map[string][]byte) + c.Certificates = make(map[string]map[string]core.SecretBytes) informer, err := c.buildInformer() if err != nil { @@ -151,10 +154,14 @@ func (c *Certificates) handleAddEvent(obj interface{}) { return } - c.Certificates[secret.Name] = map[string][]byte{} + c.Certificates[secret.Name] = map[string]core.SecretBytes{} + // Input from the secret comes in the form + // tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVCVEN... + // tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0l... + // Where the keys are `tls.crt` and `tls.key` and the values are []byte for k, v := range secret.Data { - c.Certificates[secret.Name][k] = v + c.Certificates[secret.Name][k] = core.SecretBytes(v) } logrus.Debugf("Certificates::handleAddEvent: certificates (%d)", len(c.Certificates)) diff --git a/internal/core/secret_bytes.go b/internal/core/secret_bytes.go new file mode 100644 index 0000000..0bbc3bf --- /dev/null +++ b/internal/core/secret_bytes.go @@ -0,0 +1,21 @@ +package core + +import ( + "encoding/json" +) + +// Wraps byte slices which potentially could contain +// sensitive data that should not be output to the logs. +// This will output [REDACTED] if attempts are made +// to print this type in logs, serialize to JSON, or +// otherwise convert it to a string. +// Usage: core.SecretBytes(myByteSlice) +type SecretBytes []byte + +func (sb SecretBytes) String() string { + return "[REDACTED]" +} + +func (sb SecretBytes) MarshalJSON() ([]byte, error) { + return json.Marshal("[REDACTED]") +} diff --git a/internal/core/secret_bytes_test.go b/internal/core/secret_bytes_test.go new file mode 100644 index 0000000..8dd8024 --- /dev/null +++ b/internal/core/secret_bytes_test.go @@ -0,0 +1,31 @@ +/* + * 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 core + +import ( + "encoding/json" + "fmt" + "testing" +) + +func TestSecretBytesToString(t *testing.T) { + sensitive := SecretBytes([]byte("If you can see this we have a problem")) + + expected := "foo [REDACTED] bar" + result := fmt.Sprintf("foo %v bar", sensitive) + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } +} + +func TestSecretBytesToJSON(t *testing.T) { + sensitive, _ := json.Marshal(SecretBytes([]byte("If you can see this we have a problem"))) + expected := `foo "[REDACTED]" bar` + result := fmt.Sprintf("foo %v bar", string(sensitive)) + if result != expected { + t.Errorf("Expected %s, got %s", expected, result) + } +}