diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 993a8ffc..4b12bc67 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -74,6 +74,16 @@ jobs: with: name: images path: /tmp + - name: Install Helm + run: | + wget https://get.helm.sh/helm-v3.16.1-linux-amd64.tar.gz + tar -zxf helm-v3.16.1-linux-amd64.tar.gz + mv linux-amd64/helm /usr/local/bin/helm + - name: Install cert-manager + run: | + helm repo add jetstack https://charts.jetstack.io + helm repo update + helm install cert-manager jetstack/cert-manager --version v1.11.0 --set installCRDs=true - name: Load images into kind cluster run: | kind load image-archive /tmp/k8s-sidecar.tar --name sidecar-testing @@ -94,6 +104,7 @@ jobs: wait_for_pod_ready "sidecar" wait_for_pod_ready "sidecar-basicauth-args" wait_for_pod_ready "sidecar-5xx" + wait_for_pod_ready "sidecar-tls" wait_for_pod_ready "sidecar-pythonscript" wait_for_pod_ready "sidecar-pythonscript-logfile" wait_for_pod_ready "sidecar-logtofile-pythonscript" @@ -116,6 +127,7 @@ jobs: wait_for_pod_log $p $r done done + wait_for_pod_log "sidecar-tls" "tls-url-configmap-200" # 10 more seconds after the last thing appeared in the logs. sleep 10 - name: Retrieve pod logs @@ -127,6 +139,7 @@ jobs: kubectl logs sidecar-pythonscript > /tmp/logs/sidecar-pythonscript.log kubectl logs sidecar-pythonscript-logfile > /tmp/logs/sidecar-pythonscript-logfile.log kubectl logs dummy-server-pod > /tmp/logs/dummy-server.log + kubectl logs sidecar-tls > /tmp/logs/sidecar-tls.log - name: Upload artifacts (pod logs) uses: actions/upload-artifact@v3 with: @@ -135,6 +148,7 @@ jobs: - name: Download expected files from cluster run: | mkdir /tmp/sidecar + mkdir /tmp/sidecar-tls mkdir /tmp/sidecar-5xx echo "Downloading resource files from sidecar..." kubectl cp sidecar:/tmp/hello.world /tmp/sidecar/hello.world @@ -170,12 +184,16 @@ jobs: kubectl cp sidecar-5xx:/tmp-5xx/secured.txt /tmp/sidecar-5xx/secured.txt kubectl cp sidecar-5xx:/tmp-5xx/similar-configmap.txt /tmp/sidecar-5xx/similar-configmap.txt kubectl cp sidecar-5xx:/tmp-5xx/similar-secret.txt /tmp/sidecar-5xx/similar-secret.txt + + echo "Downloading resource files from sidecar-tls pod" + kubectl cp sidecar-tls:/tmp/200-tls.txt /tmp/sidecar-tls/200-tls.txt - name: Upload artifacts (expected files from cluster) uses: actions/upload-artifact@v3 with: name: expected-files_${{ matrix.k8s.maj_min }} path: | /tmp/sidecar/** + /tmp/sidecar-tls/** /tmp/sidecar-5xx/** - name: Update Configmaps and Secrets run: | @@ -208,6 +226,8 @@ jobs: echo -n "I'm very similar" | diff - /tmp/sidecar/similar-configmap.txt && echo -n "I'm very similar" | diff - /tmp/sidecar/similar-secret.txt && echo -n "allowed" | diff - /tmp/sidecar/secured.txt && + [ -f /tmp/sidecar-tls/200-tls.txt ] && echo "200-tls.txt file created" && + echo -n "200" | diff - /tmp/sidecar-tls/200-tls.txt && [ ! -f /tmp/sidecar/500.txt ] && echo "No 5xx file created" && ls /tmp/sidecar/script_result - name: Verify sidecar-basicauth-args pod file after initial sync diff --git a/README.md b/README.md index e97b659b..3d8df1f2 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ If the filename ends with `.url` suffix, the content will be processed as a URL | `REQ_USERNAME` | Username to use for basic authentication for requests to `REQ_URL` and for `*.url` triggered requests | false | - | string | | `REQ_PASSWORD` | Password to use for basic authentication for requests to `REQ_URL` and for `*.url` triggered requests | false | - | string | | `REQ_BASIC_AUTH_ENCODING` | Which encoding to use for username and password as [by default it's undefined](https://datatracker.ietf.org/doc/html/rfc7617) (e.g. `utf-8`). | false | `latin1` | string | +| `REQ_CLIENT_CERT` | [PATH] Client Certificate to be used by k8s-sidecar when communicating with the `REQ_URL` | false | - | string | +| `REQ_CLIENT_KEY` | [PATH] Client Key to be used by k8s-sidecar when communicating with the `REQ_URL` | false | - | string | | `SCRIPT` | Absolute path to a script to execute after a configmap got reloaded. It runs before calls to `REQ_URI`. If the file is not executable it will be passed to `sh`. Otherwise it's executed as is. [Shebangs](https://en.wikipedia.org/wiki/Shebang_(Unix)) known to work are `#!/bin/sh` and `#!/usr/bin/env python` | false | - | string | | `ERROR_THROTTLE_SLEEP` | How many seconds to wait before watching resources again when an error occurs | false | `5` | integer | | `SKIP_TLS_VERIFY` | Set to `true` to skip tls verification for kube api calls | false | - | boolean | diff --git a/src/helpers.py b/src/helpers.py index 6114ae11..ded1a742 100755 --- a/src/helpers.py +++ b/src/helpers.py @@ -165,12 +165,18 @@ def request(url, method, enable_5xx=False, payload=None): if url is None: logger.warning(f"No url provided. Doing nothing.") return - + client_cert, client_key = get_client_cert_and_key_paths() + if client_cert and client_key: + logger.debug("Client certificates are configured: Client cert path: %s, Client key path: %s", client_cert, client_key) + cert = (client_cert, client_key) + else: + logger.debug("Client certificates are not configured. Skipping client certificate authentication.") + cert = None # If method is not provided use GET as default if method == "GET" or not method: - res = r.get("%s" % url, auth=auth, timeout=REQ_TIMEOUT, verify=REQ_TLS_VERIFY) + res = r.get("%s" % url, cert=cert, auth=auth, timeout=REQ_TIMEOUT, verify=REQ_TLS_VERIFY) elif method == "POST": - res = r.post("%s" % url, auth=auth, json=payload, timeout=REQ_TIMEOUT, verify=REQ_TLS_VERIFY) + res = r.post("%s" % url, cert=cert, auth=auth, json=payload, timeout=REQ_TIMEOUT, verify=REQ_TLS_VERIFY) else: logger.warning(f"Invalid REQ_METHOD: '{method}', please use 'GET' or 'POST'. Doing nothing.") return @@ -216,3 +222,9 @@ def execute(script_path): logger.debug(f"Script exit code: {result.returncode}") except subprocess.CalledProcessError as e: logger.error(f"Script failed with error: {e}") + + +def get_client_cert_and_key_paths(): + client_cert = os.getenv("REQ_CLIENT_CERT") + client_key = os.getenv("REQ_CLIENT_KEY") + return client_cert, client_key diff --git a/test/resources/resources.yaml b/test/resources/resources.yaml index 7b683e1f..2e50d46f 100644 --- a/test/resources/resources.yaml +++ b/test/resources/resources.yaml @@ -101,3 +101,13 @@ metadata: binaryData: # Base64 encoded url is 'http://dummy-server/static/kubelogo.png' url-downloaded-kubelogo.png.url: "aHR0cDovL2R1bW15LXNlcnZlci9zdGF0aWMva3ViZWxvZ28ucG5n" +--- +# For client-certificate-auth +apiVersion: v1 +kind: ConfigMap +metadata: + name: tls-url-configmap-200 + labels: + findme-with-tls: "yup" +data: + 200-tls.txt.url: "https://dummy-server-tls/200" \ No newline at end of file diff --git a/test/resources/sidecar.yaml b/test/resources/sidecar.yaml index 40ca9c44..ed7b40f3 100644 --- a/test/resources/sidecar.yaml +++ b/test/resources/sidecar.yaml @@ -388,4 +388,155 @@ metadata: type: Opaque stringData: username: "user1" - password: "abcdefghijklmnopqrstuvwxyz" \ No newline at end of file + password: "abcdefghijklmnopqrstuvwxyz" +--- +# For client-certificate-auth +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: default +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: root-ca + namespace: default +spec: + isCA: true + commonName: root-ca + subject: + organizations: + - Kiwigrid + organizationalUnits: + - k8s-sidecar + secretName: ca-secret + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: Issuer + group: cert-manager.io +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: root-ca-issuer + namespace: default +spec: + ca: + secretName: ca-secret +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: dummy-server + namespace: default +spec: + secretName: server-tls + isCA: false + usages: + - server auth + - client auth + dnsNames: + - "dummy-server-tls.default.svc.cluster.local" + - "dummy-server-tls" + issuerRef: + name: root-ca-issuer +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: sidecar-client + namespace: default +spec: + secretName: client-tls + isCA: false + usages: + - server auth + - client auth + dnsNames: + - "dummy-server-tls.default.svc.cluster.local" + - "dummy-server-tls" + issuerRef: + name: root-ca-issuer +--- +apiVersion: v1 +kind: Pod +metadata: + name: dummy-server-tls-pod + namespace: default + labels: + app: dummyserver-tls +spec: + serviceAccountName: sample-acc + containers: + - name: dummy-server-tls + command: ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "443", "--ssl-keyfile", "/opt/server-creds/tls.key", "--ssl-certfile", "/opt/server-creds/tls.crt", "--ssl-cert-reqs", "2", "--ssl-ca-certs","/opt/root-creds/ca.crt"] + image: dummy-server:1.0.0 + ports: + - containerPort: 443 + volumeMounts: + - name: server-creds + mountPath: /opt/server-creds/ + - name: root-creds + mountPath: /opt/root-creds/ + volumes: + - name: server-creds + secret: + secretName: server-tls + - name: root-creds + secret: + secretName: ca-secret +--- +apiVersion: v1 +kind: Service +metadata: + name: dummy-server-tls +spec: + selector: + app: dummyserver-tls + ports: + - port: 443 + targetPort: 443 + name: https +--- +apiVersion: v1 +kind: Pod +metadata: + name: sidecar-tls + namespace: default +spec: + serviceAccountName: sample-acc + containers: + - name: sidecar-tls + image: kiwigrid/k8s-sidecar:testing + volumeMounts: + - name: shared-volume + mountPath: /tmp/ + - name: client-tls-creds + mountPath: /opt/client-tls-creds/ + env: + - name: LABEL + value: "findme-with-tls" + - name: FOLDER + value: /tmp/ + - name: RESOURCE + value: both + - name: LOG_LEVEL + value: "DEBUG" + - name: REQ_CLIENT_CERT + value: "/opt/client-tls-creds/tls.crt" + - name: REQ_CLIENT_KEY + value: "/opt/client-tls-creds/tls.key" + - name: REQ_SKIP_TLS_VERIFY + value: "true" + volumes: + - name: shared-volume + emptyDir: {} + - name: client-tls-creds + secret: + secretName: client-tls \ No newline at end of file