diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index b402f42..34b247f 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -72,11 +72,23 @@ jobs: - name: Install Configmaps and Secrets run: | + wait_for_pod_log() { + while [[ $(kubectl logs $1 | grep $2) == "" ]]; do echo "waiting 5 more seconds for '$2' to appear in logs of pod '$1'..." && sleep 5; done + echo "Pod '$1' logs contains '$2'" + } # because the sidecar pods signal ready state before we actually opened up all watching subprocesses, we wait some more time - sleep 15 + sleep 20 echo "Installing resources..." kubectl apply -f "test/resources/resources.yaml" - sleep 15 + pods=("sidecar" "sidecar-5xx") + resources=("sample-configmap" "sample-secret-binary" "absolute-configmap" "relative-configmap" "url-configmap-500" "url-configmap-basic-auth" "sample-configmap") + for p in ${pods[*]}; do + for r in ${resources[*]}; do + wait_for_pod_log $p $r + done + done + # 5 more seconds after the last thing appeared in the logs. + sleep 5 - name: Retrieve pod logs run: | kubectl logs sidecar > /tmp/sidecar.log @@ -97,6 +109,7 @@ jobs: kubectl cp sidecar:/tmp/absolute/absolute.txt /tmp/absolute.txt kubectl cp sidecar:/tmp/relative/relative.txt /tmp/relative.txt kubectl cp sidecar:/tmp/500.txt /tmp/500.txt || true + kubectl cp sidecar:/tmp/secured.txt /tmp/secured.txt echo "Downloading resource files from sidecar-5xx..." kubectl cp sidecar-5xx:/tmp-5xx/hello.world /tmp/5xx/hello.world @@ -108,6 +121,7 @@ jobs: kubectl cp sidecar-5xx:/tmp/absolute/absolute.txt /tmp/5xx/absolute.txt kubectl cp sidecar-5xx:/tmp-5xx/relative/relative.txt /tmp/5xx/relative.txt kubectl cp sidecar-5xx:/tmp-5xx/500.txt /tmp/5xx/500.txt + kubectl cp sidecar-5xx:/tmp-5xx/secured.txt /tmp/5xx/secured.txt - name: Verify files run: | @@ -118,6 +132,8 @@ jobs: diff test/kubelogo.png /tmp/secret-kubelogo.png && echo -n "This absolutely exists" | diff - /tmp/absolute.txt && echo -n "This relatively exists" | diff - /tmp/relative.txt && + echo -n "allowed" | diff - /tmp/secured.txt && + echo -n '{"detail":"Not authenticated"}' | diff - /tmp/5xx/secured.txt && [ ! -f /tmp/500.txt ] && echo "No 5xx file created" && ls /tmp/script_result && echo -n "Hello World!" | diff - /tmp/5xx/hello.world && @@ -126,4 +142,4 @@ jobs: echo -n "This absolutely exists" | diff - /tmp/5xx/absolute.txt && echo -n "This relatively exists" | diff - /tmp/5xx/relative.txt && echo -n "500" | diff - /tmp/5xx/500.txt && - ls /tmp/5xx/script_result + ls /tmp/5xx/script_result \ No newline at end of file diff --git a/README.md b/README.md index fdf4dcd..8e0fc3a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ If the filename ends with `.url` suffix, the content will be processed as a URL | `REQ_TIMEOUT` | How many seconds to wait for the server to send data before giving up for `.url` triggered requests or requests to `REQ_URI` (does not apply to k8s api requests) | false | `10` | float | | `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 | | `SCRIPT` | Absolute path to shell script to execute after a configmap got reloaded. It runs before calls to `REQ_URI` | 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 | @@ -83,4 +84,4 @@ If the filename ends with `.url` suffix, the content will be processed as a URL | `IGNORE_ALREADY_PROCESSED` | Ignore already processed resource version. Avoid numerous checks on same unchanged resource. req kubernetes api >= v1.19 | false | `false` | boolean | | `LOG_LEVEL` | Set the logging level. (DEBUG, INFO, WARN, ERROR, CRITICAL) | false | `INFO` | string | | `LOG_FORMAT` | Set a log format. (JSON or LOGFMT) | false | `JSON` | string | -| `LOG_TZ` | Set the log timezone. (LOCAL or UTC) | false | `LOCAL` | string | +| `LOG_TZ` | Set the log timezone. (LOCAL or UTC) | false | `LOCAL` | string | \ No newline at end of file diff --git a/src/helpers.py b/src/helpers.py index 4155dbf..2fa34d0 100755 --- a/src/helpers.py +++ b/src/helpers.py @@ -5,19 +5,22 @@ import os import subprocess from datetime import datetime -from logger import get_logger import requests from requests.adapters import HTTPAdapter +from requests.auth import HTTPBasicAuth from requests.packages.urllib3.util.retry import Retry +from logger import get_logger + CONTENT_TYPE_TEXT = "ascii" CONTENT_TYPE_BASE64_BINARY = "binary" REQ_RETRY_TOTAL = 5 if os.getenv("REQ_RETRY_TOTAL") is None else int(os.getenv("REQ_RETRY_TOTAL")) REQ_RETRY_CONNECT = 10 if os.getenv("REQ_RETRY_CONNECT") is None else int(os.getenv("REQ_RETRY_CONNECT")) REQ_RETRY_READ = 5 if os.getenv("REQ_RETRY_READ") is None else int(os.getenv("REQ_RETRY_READ")) -REQ_RETRY_BACKOFF_FACTOR = 1.1 if os.getenv("REQ_RETRY_BACKOFF_FACTOR") is None else float(os.getenv("REQ_RETRY_BACKOFF_FACTOR")) +REQ_RETRY_BACKOFF_FACTOR = 1.1 if os.getenv("REQ_RETRY_BACKOFF_FACTOR") is None else float( + os.getenv("REQ_RETRY_BACKOFF_FACTOR")) REQ_TIMEOUT = 10 if os.getenv("REQ_TIMEOUT") is None else float(os.getenv("REQ_TIMEOUT")) # Tune default timeouts as outlined in @@ -102,8 +105,9 @@ def request(url, method, enable_5xx=False, payload=None): username = os.getenv("REQ_USERNAME") password = os.getenv("REQ_PASSWORD") + encoding = 'latin1' if not os.getenv("REQ_BASIC_AUTH_ENCODING") else os.getenv("REQ_BASIC_AUTH_ENCODING") if username and password: - auth = (username, password) + auth = HTTPBasicAuth(username.encode(encoding), password.encode(encoding)) else: auth = None @@ -165,4 +169,4 @@ def execute(script_path): logger.debug(f"Script stderr: {result.stderr}") logger.debug(f"Script exit code: {result.returncode}") except subprocess.CalledProcessError as e: - logger.error(f"Script failed with error: {e}") + logger.error(f"Script failed with error: {e}") \ No newline at end of file diff --git a/test/resources/resources.yaml b/test/resources/resources.yaml index ee621cc..17e33ab 100644 --- a/test/resources/resources.yaml +++ b/test/resources/resources.yaml @@ -51,3 +51,12 @@ metadata: findme: "yup" data: 500.txt.url: "http://dummy-server/500" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: url-configmap-basic-auth + labels: + findme: "yup" +data: + secured.txt.url: "http://dummy-server/secured" \ No newline at end of file diff --git a/test/resources/sidecar.yaml b/test/resources/sidecar.yaml index f2f7543..4d3cbb0 100644 --- a/test/resources/sidecar.yaml +++ b/test/resources/sidecar.yaml @@ -53,14 +53,23 @@ spec: mountPath: /opt/script.sh subPath: script.sh env: - - name: LABEL - value: "findme" - - name: FOLDER - value: /tmp/ - - name: RESOURCE - value: both - - name: SCRIPT - value: "/opt/script.sh" + - name: LABEL + value: "findme" + - name: FOLDER + value: /tmp/ + - name: RESOURCE + value: both + - name: SCRIPT + value: "/opt/script.sh" + - name: REQ_USERNAME + value: "user1" + - name: REQ_PASSWORD + value: "abcdefghijklmnopqrstuvwxyz" + - name: REQ_BASIC_AUTH_ENCODING + # the python server we're using for the tests expects ascii encoding of basic auth credentials, hence we can't use non-ascii characters in the password or username + value: "ascii" + - name: LOG_LEVEL + value: "DEBUG" volumes: - name: shared-volume emptyDir: {} @@ -86,16 +95,18 @@ spec: mountPath: /opt-5xx/script.sh subPath: script.sh env: - - name: LABEL - value: "findme" - - name: FOLDER - value: /tmp-5xx/ - - name: RESOURCE - value: both - - name: SCRIPT - value: "/opt-5xx/script.sh" - - name: ENABLE_5XX - value: "true" + - name: LABEL + value: "findme" + - name: FOLDER + value: /tmp-5xx/ + - name: RESOURCE + value: both + - name: SCRIPT + value: "/opt-5xx/script.sh" + - name: ENABLE_5XX + value: "true" + - name: LOG_LEVEL + value: "DEBUG" volumes: - name: shared-volume emptyDir: {} @@ -129,4 +140,4 @@ spec: ports: - port: 80 targetPort: 80 - name: http + name: http \ No newline at end of file diff --git a/test/server/server.py b/test/server/server.py index 29f198e..c6ea0d3 100644 --- a/test/server/server.py +++ b/test/server/server.py @@ -1,8 +1,12 @@ -from fastapi import FastAPI -import uvicorn +from fastapi import Depends, FastAPI, status, HTTPException +from fastapi.logger import logger +from fastapi.security import HTTPBasic, HTTPBasicCredentials +from starlette.responses import PlainTextResponse app = FastAPI() +basic_auth_scheme = HTTPBasic() + @app.get("/", status_code=200) def read_root(): @@ -27,3 +31,15 @@ async def read_item(): @app.post("/503", status_code=503) async def read_item(): return 503 + + +@app.get("/secured", status_code=200, response_class=PlainTextResponse) +async def read_secure_data(auth: HTTPBasicCredentials = Depends(basic_auth_scheme)): + if auth.username != 'user1' or auth.password != 'abcdefghijklmnopqrstuvwxyz': + logger.warning("[WARN] wrong auth: %s : %s ", auth.username, auth.password) + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=f"Incorrect user (${auth.username}) or password (${auth.password})", + headers={"WWW-Authenticate": "Basic"}, + ) + return 'allowed' \ No newline at end of file