diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 746c3e878d..61af421a31 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -272,10 +272,26 @@ jobs: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Setup Python Version - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - name: Go Setup + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v6.0.0 + with: + go-version-file: "auto-discovery/kubernetes/go.mod" + + - name: Lint Go Code + working-directory: ./auto-discovery/kubernetes + run: | + go fmt ./... + go vet ./... + + - name: Download Task + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: - python-version: "${{ env.PYTHON_VERSION }}" + name: task + path: ./task + + - name: Make Task globally available + run: | + chmod +x ./task/task && sudo mv ./task/task /usr/local/bin/task - name: Download Kind uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 @@ -309,15 +325,15 @@ jobs: - name: Unit Tests working-directory: ./auto-discovery/kubernetes/pull-secret-extractor - run: make unit-test + run: task unit-test - name: Build Container Image working-directory: ./auto-discovery/kubernetes/pull-secret-extractor - run: make docker-build + run: task docker-build - name: Export Container Image working-directory: ./auto-discovery/kubernetes/pull-secret-extractor - run: make docker-export + run: task docker-export - name: Upload Image As Artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 @@ -339,7 +355,7 @@ jobs: - name: "Run integration tests" working-directory: ./auto-discovery/kubernetes/pull-secret-extractor run: | - make integration-test + task integration-test # ---- Build Stage | AutoDiscovery | Cloud | AWS ---- auto-discovery-cloud-aws: diff --git a/auto-discovery/kubernetes/controllers/container_scan_controller.go b/auto-discovery/kubernetes/controllers/container_scan_controller.go index 736a5f4841..771d6ad48d 100644 --- a/auto-discovery/kubernetes/controllers/container_scan_controller.go +++ b/auto-discovery/kubernetes/controllers/container_scan_controller.go @@ -335,8 +335,7 @@ func getSecretExtractionInitContainer(imageID string, scanConfig config.ScanConf return corev1.Container{ Name: "secret-extraction-to-env", Image: "docker.io/securecodebox/auto-discovery-pull-secret-extractor", - Command: []string{"python"}, - Args: []string{"secret_extraction.py", imageID, temporarySecretName}, + Args: []string{"-imageID", imageID, "-secret", temporarySecretName}, VolumeMounts: volumeMounts, Env: []corev1.EnvVar{ { @@ -355,6 +354,14 @@ func getSecretExtractionInitContainer(imageID string, scanConfig config.ScanConf }, }, }, + { + Name: "POD_UID", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + FieldPath: "metadata.uid", + }, + }, + }, }, } } diff --git a/auto-discovery/kubernetes/pull-secret-extractor/.dockerignore b/auto-discovery/kubernetes/pull-secret-extractor/.dockerignore index ee0eeed1b3..c8ebc7c57a 100644 --- a/auto-discovery/kubernetes/pull-secret-extractor/.dockerignore +++ b/auto-discovery/kubernetes/pull-secret-extractor/.dockerignore @@ -2,5 +2,4 @@ // // SPDX-License-Identifier: Apache-2.0 -integration-test/* -venv/* \ No newline at end of file +venv/* diff --git a/auto-discovery/kubernetes/pull-secret-extractor/Dockerfile b/auto-discovery/kubernetes/pull-secret-extractor/Dockerfile index ddd11c3b14..45500ca3b3 100644 --- a/auto-discovery/kubernetes/pull-secret-extractor/Dockerfile +++ b/auto-discovery/kubernetes/pull-secret-extractor/Dockerfile @@ -2,12 +2,29 @@ # # SPDX-License-Identifier: Apache-2.0 -FROM python:3.13-alpine - -RUN addgroup -g 1001 nikto \ - && adduser -G nikto -s /bin/sh -D -u 1001 nikto -COPY requirements.txt . -RUN pip install -r requirements.txt -COPY --chown=root:root --chmod=755 docker_image.py secret_extraction.py ./ -USER 1001 -CMD ["python", "secret_extraction.py"] +# Build the pull-secret-extractor binary +FROM --platform=$BUILDPLATFORM golang:1.25.0 AS builder + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY main.go main.go +COPY internal/ internal/ + +# Build +ARG TARGETOS TARGETARCH +RUN GOOS="$TARGETOS" GOARCH="$TARGETARCH" CGO_ENABLED=0 go build -a -o secret_extraction main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/secret_extraction . + +ENTRYPOINT ["/secret_extraction"] diff --git a/auto-discovery/kubernetes/pull-secret-extractor/Makefile b/auto-discovery/kubernetes/pull-secret-extractor/Makefile deleted file mode 100644 index 2f2b2b0dbb..0000000000 --- a/auto-discovery/kubernetes/pull-secret-extractor/Makefile +++ /dev/null @@ -1,76 +0,0 @@ -# SPDX-FileCopyrightText: the secureCodeBox authors -# -# SPDX-License-Identifier: Apache-2.0 - -include ../../../prerequisites.mk - -IMG_NS ?= securecodebox - -# Image URL to use all building/pushing image targets -IMG ?= auto-discovery-secret-extractor - -# Tag used for the image -IMG_TAG ?= sha-$$(git rev-parse --short HEAD) - - -##@ General - -# The help target prints out all targets with their descriptions organized -# beneath their categories. The categories are represented by '##@' and the -# target descriptions by '##'. The awk commands is responsible for reading the -# entire set of makefiles included in this invocation, looking for lines of the -# file as xyz: ## something, and then pretty-format the target and help. Then, -# if there's a line with ##@ something, that gets pretty-printed as a category. -# More info on the usage of ANSI control characters for terminal formatting: -# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters -# More info on the awk command: -# http://linuxcommand.org/lc3_adv_awk.php - -.PHONY: help -help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) - - -##@ Development - -.PHONY: test -test: unit-test integration-test - -.PHONY: unit-test -unit-test: - $(PYTHON) -m unittest discover - -.PHONY: integration-test -integration-test: docker-build docker-export kind-import - @echo ".: 🩺 Starting integration test in kind namespace 'integration-tests'." - kubectl delete namespace integration-test --wait || true - kubectl create namespace integration-test - - ./integration-test/test-pod.sh ${IMG_NS}/${IMG}:${IMG_TAG} - kubectl wait --for=condition=ready --timeout=60s -n integration-test pod/init-container-test - - kubectl get secret --namespace integration-test test-secret - -##@ Build - - -.PHONY: docker-build -docker-build: ## Build docker image with the manager. - @echo ".: ⚙️ Build Container Images" - docker build -t ${IMG_NS}/${IMG}:${IMG_TAG} . - -.PHONY: docker-push -docker-push: ## Push docker image with the manager. - docker push ${IMG_NS}/${IMG}:${IMG_TAG} - -.PHONY: docker-export -docker-export: - @echo ".: 💾 Export Container Images" - docker save $(IMG_NS)/$(IMG):$(IMG_TAG) > $(IMG).tar - -##@ Deployment - -.PHONY: kind-import -kind-import: - @echo ".: 💾 Importing the image archive to local kind cluster." - kind load image-archive ./$(IMG).tar \ No newline at end of file diff --git a/auto-discovery/kubernetes/pull-secret-extractor/Taskfile.yaml b/auto-discovery/kubernetes/pull-secret-extractor/Taskfile.yaml new file mode 100644 index 0000000000..32e0f12a7d --- /dev/null +++ b/auto-discovery/kubernetes/pull-secret-extractor/Taskfile.yaml @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +version: "3.44.0" + +vars: + IMG_NS: '{{default "securecodebox" .IMG_NS}}' + IMG: '{{default "auto-discovery-secret-extractor" .IMG}}' + IMG_TAG: + sh: echo "${IMG_TAG:-sha-$(git rev-parse --short HEAD)}" + FULL_IMAGE: "{{.IMG_NS}}/{{.IMG}}/{{.IMG_TAG}}" + +tasks: + unit-test: + desc: Run unit tests + cmds: + - go test ./... + + integration-test: + desc: Run integration tests in kind cluster + deps: + - kind-import + cmds: + - defer: task clean + - 'echo "🩺 Starting integration test in kind namespace integration-tests."' + - cmd: kubectl delete namespace integration-test --wait + ignore_error: true + - kubectl create namespace integration-test + - ./test/integration/test-pod.sh {{.IMG_NS}}/{{.IMG}}:{{.IMG_TAG}} + - kubectl wait --for=condition=ready --timeout=60s -n integration-test pod/init-container-test + - kubectl get secret --namespace integration-test test-secret + + test: + desc: Run all tests (unit and integration) + deps: + - unit-test + - integration-test + + docker-build: + desc: Build docker image with the manager + cmds: + - 'echo "⚙️ Build Container Images"' + - docker build -t {{.IMG_NS}}/{{.IMG}}:{{.IMG_TAG}} . + + docker-export: + desc: Export container image to tar archive + deps: + - docker-build + cmds: + - 'echo "💾 Export Container Images"' + - docker save {{.IMG_NS}}/{{.IMG}}:{{.IMG_TAG}} > {{.IMG}}.tar + + kind-import: + desc: Import container image to local kind cluster + deps: + - docker-export + preconditions: + - sh: test -f {{.IMG}}.tar + msg: "Image archive {{.IMG}}.tar not found. Run 'task docker-export' first." + cmds: + - 'echo "💾 Importing the image archive to local kind cluster."' + - kind load image-archive ./{{.IMG}}.tar + + clean: + desc: Clean up generated files + cmds: + - rm -f {{.IMG}}.tar + + vars: + desc: Display current variable values (useful for debugging) + cmds: + - | + echo "Current variable values:" + echo " IMG_NS: {{.IMG_NS}}" + echo " IMG: {{.IMG}}" + echo " IMG_TAG: {{.IMG_TAG}}" + echo " FULL_IMAGE: {{.IMG_NS}}/{{.IMG}}:{{.IMG_TAG}}" diff --git a/auto-discovery/kubernetes/pull-secret-extractor/docker_image.py b/auto-discovery/kubernetes/pull-secret-extractor/docker_image.py deleted file mode 100644 index d8a8039ff0..0000000000 --- a/auto-discovery/kubernetes/pull-secret-extractor/docker_image.py +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-FileCopyrightText: the secureCodeBox authors -# -# SPDX-License-Identifier: Apache-2.0 - -legacyDefaultDomain = "index.docker.io" -defaultDomain = "docker.io" -officialRepoName = "library" -defaultTag = "latest" - - -def get_domain_from_docker_image(name: str) -> str: - """ - Extracts domain and image from a given docker image. Has the same defaulting behavior when it comes to docker.io image as containerd - Code adapted from https://github.com/containerd/containerd/blob/20de989afcd2fd4edc20e9b85312e49a8bbe152b/reference/docker/normalize.go#L102-L119 - :param name: docker image - :return: tuple container domain and image - """ - try: - i = name.index('/') - except ValueError: - i = -1 - - name_slice = name[:i] - if i == -1 or ':' not in name_slice and '.' not in name_slice and name_slice != 'localhost' and name_slice.lower() == name_slice: - domain = defaultDomain - else: - domain = name[:i] - - if domain == legacyDefaultDomain: - domain = defaultDomain - - return domain - diff --git a/auto-discovery/kubernetes/pull-secret-extractor/go.mod b/auto-discovery/kubernetes/pull-secret-extractor/go.mod new file mode 100644 index 0000000000..8151c1bdc7 --- /dev/null +++ b/auto-discovery/kubernetes/pull-secret-extractor/go.mod @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +module github.com/secureCodeBox/auto-discovery/kubernetes/pull-secret-extractor + +go 1.24.5 + +require ( + k8s.io/api v0.34.0 + k8s.io/apimachinery v0.34.0 + sigs.k8s.io/controller-runtime v0.22.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // 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.6 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/client-go v0.34.0 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/auto-discovery/kubernetes/pull-secret-extractor/go.sum b/auto-discovery/kubernetes/pull-secret-extractor/go.sum new file mode 100644 index 0000000000..7f305f9954 --- /dev/null +++ b/auto-discovery/kubernetes/pull-secret-extractor/go.sum @@ -0,0 +1,180 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +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_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= +k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= +sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/auto-discovery/kubernetes/pull-secret-extractor/requirements.txt.license b/auto-discovery/kubernetes/pull-secret-extractor/go.sum.license similarity index 100% rename from auto-discovery/kubernetes/pull-secret-extractor/requirements.txt.license rename to auto-discovery/kubernetes/pull-secret-extractor/go.sum.license diff --git a/auto-discovery/kubernetes/pull-secret-extractor/internal/docker_image/docker_image.go b/auto-discovery/kubernetes/pull-secret-extractor/internal/docker_image/docker_image.go new file mode 100644 index 0000000000..7993644919 --- /dev/null +++ b/auto-discovery/kubernetes/pull-secret-extractor/internal/docker_image/docker_image.go @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package docker_image + +import ( + "strings" +) + +const ( + legacyDefaultDomain = "index.docker.io" + defaultDomain = "docker.io" + officialRepoName = "library" + defaultTag = "latest" +) + +// GetDomainFromDockerImage extracts domain from a given docker image. +// Has the same defaulting behavior when it comes to docker.io image as containerd. +// Code adapted from https://github.com/containerd/containerd/blob/20de989afcd2fd4edc20e9b85312e49a8bbe152b/reference/docker/normalize.go#L102-L119 +func GetDomainFromDockerImage(name string) string { + i := strings.Index(name, "/") + + var domain string + + if i == -1 { + domain = defaultDomain + } else { + nameSlice := name[:i] + + if !strings.Contains(nameSlice, ":") && + !strings.Contains(nameSlice, ".") && + nameSlice != "localhost" && + strings.ToLower(nameSlice) == nameSlice { + domain = defaultDomain + } else { + domain = nameSlice + } + } + + if domain == legacyDefaultDomain { + domain = defaultDomain + } + + return domain +} diff --git a/auto-discovery/kubernetes/pull-secret-extractor/internal/docker_image/docker_image_test.go b/auto-discovery/kubernetes/pull-secret-extractor/internal/docker_image/docker_image_test.go new file mode 100644 index 0000000000..fbe1668e8c --- /dev/null +++ b/auto-discovery/kubernetes/pull-secret-extractor/internal/docker_image/docker_image_test.go @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package docker_image + +import "testing" + +func TestGetDomainFromDockerImage(t *testing.T) { + testCases := []struct { + name string + image string + expected string + }{ + { + name: "image with no domain", + image: "foo/bar", + expected: "docker.io", + }, + { + name: "image with docker.io domain", + image: "docker.io/foo/bar", + expected: "docker.io", + }, + { + name: "image with non-docker.io domain", + image: "test.xyz/foo/bar", + expected: "test.xyz", + }, + { + name: "single word image", + image: "ubuntu", + expected: "docker.io", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := GetDomainFromDockerImage(tc.image) + if result != tc.expected { + t.Errorf("GetDomainFromDockerImage(%q) = %q; want %q", tc.image, result, tc.expected) + } + }) + } +} diff --git a/auto-discovery/kubernetes/pull-secret-extractor/internal/secret_extraction/secret_extraction.go b/auto-discovery/kubernetes/pull-secret-extractor/internal/secret_extraction/secret_extraction.go new file mode 100644 index 0000000000..83b7e3eaee --- /dev/null +++ b/auto-discovery/kubernetes/pull-secret-extractor/internal/secret_extraction/secret_extraction.go @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package secret_extraction + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +// Constants for config +const ( + dockerConfigFileName = ".dockerconfigjson" + defaultSecretsPath = "/secrets" +) + +// Env variable names +const ( + envPodName = "POD_NAME" + envNameSpace = "NAMESPACE" +) + +type DockerConfigJSON struct { + Auths map[string]AuthEntry `json:"auths"` +} + +type AuthEntry struct { + Auth string `json:"auth,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` +} + +type Credentials struct { + Username string + Password string +} + +func CreateTemporarySecret(ctx context.Context, k8sClient client.Client, temporarySecretName, domain, namespace, podName, podUID, secretsPath string) error { + if temporarySecretName == "" { + return fmt.Errorf("temporary secret name cannot be empty") + } + + if domain == "" { + return fmt.Errorf("domain cannot be empty") + } + + configs, err := readDockerConfigs(secretsPath) + if err != nil { + return fmt.Errorf("failed to read Docker configs: %w", err) + } + + authEntry := findAuthForDomain(domain, configs) + if authEntry == nil { + return fmt.Errorf("no authentication found for domain: %s", domain) + } + + creds, err := extractCredentials(authEntry) + if err != nil { + return fmt.Errorf("failed to extract credentials for domain %s: %w", domain, err) + } + + secret, err := buildSecret(ctx, k8sClient, temporarySecretName, namespace, podName, podUID, creds) + if err != nil { + return fmt.Errorf("failed to build secret: %w", err) + } + + if err := k8sClient.Create(ctx, secret); err != nil { + return fmt.Errorf("failed to create temporary secret: %w", err) + } + + return nil +} + +func readDockerConfigs(basePath string) ([]DockerConfigJSON, error) { + var configs []DockerConfigJSON + + err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + fmt.Printf("Warning: error accessing path %s: %v\n", path, err) + return nil + } + + if info.IsDir() || filepath.Base(path) != dockerConfigFileName { + return nil + } + + config, err := readSingleConfig(path) + if err != nil { + fmt.Printf("Warning: failed to read config from %s: %v\n", path, err) + return nil + } + + configs = append(configs, *config) + return nil + }) + + if err != nil { + return nil, fmt.Errorf("failed to walk directory %s: %w", basePath, err) + } + + return configs, nil +} + +func readSingleConfig(path string) (*DockerConfigJSON, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("failed to open file: %w", err) + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + + var config DockerConfigJSON + if err := json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("failed to parse JSON: %w", err) + } + + return &config, nil +} + +func findAuthForDomain(domain string, configs []DockerConfigJSON) *AuthEntry { + for _, config := range configs { + if auth, exists := config.Auths[domain]; exists { + return &auth + } + } + return nil +} + +func extractCredentials(auth *AuthEntry) (*Credentials, error) { + if auth == nil { + return nil, fmt.Errorf("auth entry is nil") + } + + if auth.Auth != "" { + decoded, err := base64.StdEncoding.DecodeString(auth.Auth) + if err != nil { + return nil, fmt.Errorf("failed to decode auth field: %w", err) + } + + parts := strings.SplitN(string(decoded), ":", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid auth format, expected username:password") + } + + return &Credentials{ + Username: parts[0], + Password: parts[1], + }, nil + } + + if auth.Username != "" && auth.Password != "" { + return &Credentials{ + Username: auth.Username, + Password: auth.Password, + }, nil + } + + return nil, fmt.Errorf("auth entry does not contain valid credentials") +} + +func buildSecret(ctx context.Context, k8sClient client.Client, secretName, namespace, podName, podUID string, creds *Credentials) (*v1.Secret, error) { + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: namespace, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Pod", + Name: podName, + UID: types.UID(podUID), + }, + }, + }, + StringData: map[string]string{ + "username": creds.Username, + "password": creds.Password, + }, + Type: v1.SecretTypeOpaque, + }, nil +} + +func createK8sClient() (client.Client, error) { + cfg, err := config.GetConfig() + if err != nil { + return nil, fmt.Errorf("failed to get Kubernetes config: %w", err) + } + + scheme := runtime.NewScheme() + if err := v1.AddToScheme(scheme); err != nil { + return nil, fmt.Errorf("failed to add core v1 to scheme: %w", err) + } + + k8sClient, err := client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + return nil, fmt.Errorf("failed to create Kubernetes client: %w", err) + } + + return k8sClient, nil +} + +func CreateTemporarySecretFromEnv(temporarySecretName, domain string) error { + namespace := os.Getenv(envNameSpace) + if namespace == "" { + return fmt.Errorf("environment variable %s is not set", envNameSpace) + } + + podName := os.Getenv(envPodName) + if podName == "" { + return fmt.Errorf("environment variable %s is not set", envPodName) + } + + podUID := os.Getenv("POD_UID") + if podUID == "" { + return fmt.Errorf("environment variable %s is not set", podUID) + } + + k8sClient, err := createK8sClient() + if err != nil { + return fmt.Errorf("failed to create Kubernetes client: %w", err) + } + + ctx := context.Background() + return CreateTemporarySecret(ctx, k8sClient, temporarySecretName, domain, namespace, podName, podUID, defaultSecretsPath) +} diff --git a/auto-discovery/kubernetes/pull-secret-extractor/internal/secret_extraction/secret_extraction_test.go b/auto-discovery/kubernetes/pull-secret-extractor/internal/secret_extraction/secret_extraction_test.go new file mode 100644 index 0000000000..99ecb9ffb6 --- /dev/null +++ b/auto-discovery/kubernetes/pull-secret-extractor/internal/secret_extraction/secret_extraction_test.go @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package secret_extraction + +import ( + "context" + "encoding/base64" + "encoding/json" + "os" + "path/filepath" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestCreateTemporarySecret(t *testing.T) { + scheme := runtime.NewScheme() + v1.AddToScheme(scheme) + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "test-namespace", + UID: types.UID("test-uid-123"), + }, + } + + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(pod). + Build() + + tempDir := t.TempDir() + configPath := filepath.Join(tempDir, dockerConfigFileName) + + config := DockerConfigJSON{ + Auths: map[string]AuthEntry{ + "example.com": { + Auth: base64.StdEncoding.EncodeToString([]byte("testuser:testpass")), + }, + }, + } + + configData, _ := json.Marshal(config) + os.WriteFile(configPath, configData, 0644) + + ctx := context.Background() + err := CreateTemporarySecret(ctx, k8sClient, "test-secret", "example.com", "test-namespace", "test-pod", "test-uid-123", tempDir) + + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + + secret := &v1.Secret{} + err = k8sClient.Get(ctx, client.ObjectKey{Name: "test-secret", Namespace: "test-namespace"}, secret) + if err != nil { + t.Fatalf("Secret was not created: %v", err) + } + + expectedUsername := "testuser" + expectedPassword := "testpass" + + if secret.StringData["username"] != expectedUsername { + t.Errorf("Expected username %s, got %s", expectedUsername, string(secret.StringData["username"])) + } + + if secret.StringData["password"] != expectedPassword { + t.Errorf("Expected password %s, got %s", expectedPassword, string(secret.StringData["password"])) + } + + if len(secret.OwnerReferences) != 1 { + t.Fatalf("Expected 1 owner reference, got %d", len(secret.OwnerReferences)) + } + + ownerRef := secret.OwnerReferences[0] + if ownerRef.Name != "test-pod" || ownerRef.UID != "test-uid-123" { + t.Errorf("Owner reference not set correctly: %+v", ownerRef) + } +} + +func TestReadDockerConfigs(t *testing.T) { + tempDir := t.TempDir() + + configPath := filepath.Join(tempDir, dockerConfigFileName) + config := DockerConfigJSON{ + Auths: map[string]AuthEntry{ + "registry.example.com": { + Username: "testuser", + Password: "testpass", + }, + }, + } + + configData, _ := json.Marshal(config) + os.WriteFile(configPath, configData, 0644) + + configs, err := readDockerConfigs(tempDir) + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + + if len(configs) != 1 { + t.Fatalf("Expected 1 config, got %d", len(configs)) + } + + auth, exists := configs[0].Auths["registry.example.com"] + if !exists { + t.Fatal("Expected auth entry for registry.example.com") + } + + if auth.Username != "testuser" || auth.Password != "testpass" { + t.Errorf("Expected testuser/testpass, got %s/%s", auth.Username, auth.Password) + } +} + +func TestExtractCredentials(t *testing.T) { + tests := []struct { + name string + auth *AuthEntry + expectCreds bool + expectError bool + }{ + { + name: "base64 auth field", + auth: &AuthEntry{ + Auth: base64.StdEncoding.EncodeToString([]byte("user:pass")), + }, + expectCreds: true, + }, + { + name: "separate username/password fields", + auth: &AuthEntry{ + Username: "user", + Password: "pass", + }, + expectCreds: true, + }, + { + name: "nil auth entry", + auth: nil, + expectError: true, + }, + { + name: "empty auth entry", + auth: &AuthEntry{}, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + creds, err := extractCredentials(tt.auth) + + if tt.expectError && err == nil { + t.Error("Expected error, got none") + } + + if !tt.expectError && err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + if tt.expectCreds && creds == nil { + t.Error("Expected credentials, got nil") + } + }) + } +} diff --git a/auto-discovery/kubernetes/pull-secret-extractor/main.go b/auto-discovery/kubernetes/pull-secret-extractor/main.go new file mode 100644 index 0000000000..00524801a7 --- /dev/null +++ b/auto-discovery/kubernetes/pull-secret-extractor/main.go @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/secureCodeBox/auto-discovery/kubernetes/pull-secret-extractor/internal/docker_image" + secret_extraction "github.com/secureCodeBox/auto-discovery/kubernetes/pull-secret-extractor/internal/secret_extraction" +) + +const AppName = "pull-secret-extractor" + +type Config struct { + ImageID string + TemporarySecretName string +} + +func parseFlags() (*Config, error) { + config := &Config{} + + flag.StringVar(&config.ImageID, "imageID", "", "Docker image ID to extract domain from (required)") + flag.StringVar(&config.TemporarySecretName, "secret", "", "Name for the temporary secret (required)") + + flag.Parse() + + if config.ImageID == "" { + return nil, fmt.Errorf("image ID is required (use -imageID flag or provide as first argument)") + } + + if config.TemporarySecretName == "" { + return nil, fmt.Errorf("temporary secret name is required (use -secret flag or provide as second argument)") + } + + return config, nil +} + +func run(config *Config) error { + domain := docker_image.GetDomainFromDockerImage(config.ImageID) + if domain == "" { + return fmt.Errorf("failed to extract domain from image ID: %s", config.ImageID) + } + + if err := secret_extraction.CreateTemporarySecretFromEnv(config.TemporarySecretName, domain); err != nil { + return fmt.Errorf("failed to create temporary secret: %w", err) + } + + log.Printf("Successfully created temporary secret '%s' for domain '%s'", + config.TemporarySecretName, domain) + + return nil +} + +func main() { + log.SetPrefix(fmt.Sprintf("[%s] ", AppName)) + log.SetFlags(log.LstdFlags | log.Lshortfile) + + config, err := parseFlags() + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n\n", err) + flag.Usage() + os.Exit(1) + } + + if err := run(config); err != nil { + log.Printf("Application failed: %v", err) + os.Exit(1) + } +} diff --git a/auto-discovery/kubernetes/pull-secret-extractor/requirements.txt b/auto-discovery/kubernetes/pull-secret-extractor/requirements.txt deleted file mode 100644 index da7662dad2..0000000000 --- a/auto-discovery/kubernetes/pull-secret-extractor/requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ -cachetools==5.2.1 -certifi==2024.7.4 -charset-normalizer==3.0.1 -google-auth==2.16.0 -idna==3.7 -kubernetes==25.3.0 -oauthlib==3.2.2 -pyasn1==0.4.8 -pyasn1-modules==0.2.8 -python-dateutil==2.8.2 -PyYAML==6.0.1 -requests==2.32.4 -requests-oauthlib==1.3.1 -rsa==4.9 -six==1.16.0 -urllib3==2.5.0 -websocket-client==1.4.2 diff --git a/auto-discovery/kubernetes/pull-secret-extractor/secret_extraction.py b/auto-discovery/kubernetes/pull-secret-extractor/secret_extraction.py deleted file mode 100644 index 1730a7dce5..0000000000 --- a/auto-discovery/kubernetes/pull-secret-extractor/secret_extraction.py +++ /dev/null @@ -1,128 +0,0 @@ -# SPDX-FileCopyrightText: the secureCodeBox authors -# -# SPDX-License-Identifier: Apache-2.0 - -import glob -import json -import sys -import base64 -import os - -from kubernetes import client, config - -from docker_image import get_domain_from_docker_image - - -def main(): - image_id = sys.argv[1] - temporary_secret_name = sys.argv[2] - - domain = get_domain_from_docker_image(image_id) - - raw_secrets = get_raw_secrets('/secrets') - correct_secret = get_correct_secret(domain, raw_secrets) - - if correct_secret: - username, password = get_user_and_password(correct_secret) - create_temporary_secret(username, password, temporary_secret_name) - print(f"Created temporary pull secret for domain: '{domain}'") - else: - print(f"No secrets found for domain: '{domain}'") - - -def get_raw_secrets(base_path: str): - """Reads in files called '.dockerconfigjson' in the path given and return the content of all files called so - :param base_path: Directory to search for dockerconfigjson files - :returns: List of secrets found in base_path - """ - raw_secrets = [] - for file_name in glob.glob(f'{base_path}/**/.dockerconfigjson', recursive=True): - with open(file_name) as file: - raw_secret = json.load(file) - raw_secrets.append(raw_secret) - return raw_secrets - - -def get_correct_secret(domain: str, secrets) -> dict[str, str]: - """Iterates over given list of secrets to find the secret that matches the URL in the given imageID - :param domain: The domain of the imageID of which the correct secret needs to be identified - :param secrets: List of secrets - :returns: Dict containing the secret matching the given imageID - """ - for secret in secrets: - for url, data in secret['auths'].items(): - if url == domain: - return data - - -def get_user_and_password(raw_secret: dict[str, str]) -> tuple[str, str]: - """Extracts username and password from a given secret - :param raw_secret: Dict containing the secret. Should contain key 'auth' (where username and password are - base64 encoded in a single line like: username:password), or 'username' and 'password' as a separate key - (also base64) - :returns: tuple containing username and password both base64 encoded - :raises KeyError: Structure of given secret does not contain expected structure. - """ - if 'auth' in raw_secret: - # secret is in form "username:password" (base64 encoded) - username_password_combo = decode_base64(raw_secret['auth']) - tmp_list = username_password_combo.split(":") - - # k8s wants the secrets as base64, so the individual values are converted back to base64 - username = encode_base64(tmp_list[0]) - password = encode_base64(tmp_list[1]) - return username, password - - elif 'username' in raw_secret and 'password' in raw_secret: - # username and password are already separated and base64 encoded, no need to do more - username = raw_secret['username'] - password = raw_secret['password'] - return username, password - - else: - raise KeyError('dockerconfigjson secret does not contain expected structure!') - - -def decode_base64(raw_string: str) -> str: - return base64.b64decode(raw_string).decode('utf-8') - - -def encode_base64(string: str) -> str: - return base64.b64encode(string.encode('utf-8')).decode('utf-8') - - -def create_temporary_secret(username: str, password: str, secret_name: str): - """Creates a secret with name 'secret_name' with 'username' and 'password' as data in given namespace. The secret has an ownerReference to the pod this container is running in. - :param username: base64 encoded string representing the desired value of the 'username' field in the secret - :param password: base64 encoded string representing the desired value of the 'password' field in the secret - :param secret_name: Name of the newly created secret - """ - config.load_incluster_config() - v1 = client.CoreV1Api() - - namespace = get_namespace() - - pod_name = get_pod_name() - pod = v1.read_namespaced_pod(name=pod_name, namespace=namespace) - - secret_data = {'username': username, 'password': password} - owner_references = client.V1OwnerReference(api_version='v1', name=pod_name, uid=pod.metadata.uid, kind='Pod') - metadata = client.V1ObjectMeta(name=secret_name, namespace=namespace, owner_references=[owner_references]) - secret_body = client.V1Secret(api_version='v1', kind='Secret', metadata=metadata, data=secret_data, type='Opaque') - v1.create_namespaced_secret(namespace=namespace, body=secret_body) - - -def get_pod_name() -> str: - """Read pod name from environment variable called 'POD_NAME'. - Should be set like this: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/""" - return os.environ['POD_NAME'] - - -def get_namespace() -> str: - """Read pod name from environment variable called 'NAMESPACE'. - Should be set like this: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/""" - return os.environ['NAMESPACE'] - - -if __name__ == '__main__': - main() diff --git a/auto-discovery/kubernetes/pull-secret-extractor/integration-test/test-pod.sh b/auto-discovery/kubernetes/pull-secret-extractor/test/integration/test-pod.sh similarity index 89% rename from auto-discovery/kubernetes/pull-secret-extractor/integration-test/test-pod.sh rename to auto-discovery/kubernetes/pull-secret-extractor/test/integration/test-pod.sh index f72670cbb4..e118627ec9 100755 --- a/auto-discovery/kubernetes/pull-secret-extractor/integration-test/test-pod.sh +++ b/auto-discovery/kubernetes/pull-secret-extractor/test/integration/test-pod.sh @@ -22,7 +22,7 @@ metadata: namespace: integration-test --- apiVersion: v1 -kind: Pod +kind: Pod metadata: name: init-container-test namespace: integration-test @@ -35,12 +35,15 @@ spec: initContainers: - name: init-container-test-container image: $1 - command: ["python"] - args: ["secret_extraction.py", "fake-registry.xyz/ubuntu:32131", "test-secret", "default"] + args: ["-imageID", "fake-registry.xyz/ubuntu:32131", "-secret", "test-secret", "default"] volumeMounts: - name: regcred-volume mountPath: "/secrets/regcred" env: + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid - name: POD_NAME valueFrom: fieldRef: diff --git a/auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_1/.dockerconfigjson b/auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_1/.dockerconfigjson similarity index 100% rename from auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_1/.dockerconfigjson rename to auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_1/.dockerconfigjson diff --git a/auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_1/.dockerconfigjson.license b/auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_1/.dockerconfigjson.license similarity index 100% rename from auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_1/.dockerconfigjson.license rename to auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_1/.dockerconfigjson.license diff --git a/auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_1/not_a_docker_config_json b/auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_1/not_a_docker_config_json similarity index 100% rename from auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_1/not_a_docker_config_json rename to auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_1/not_a_docker_config_json diff --git a/auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_1/not_a_docker_config_json.license b/auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_1/not_a_docker_config_json.license similarity index 100% rename from auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_1/not_a_docker_config_json.license rename to auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_1/not_a_docker_config_json.license diff --git a/auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_2/.dockerconfigjson b/auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_2/.dockerconfigjson similarity index 100% rename from auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_2/.dockerconfigjson rename to auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_2/.dockerconfigjson diff --git a/auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_2/.dockerconfigjson.license b/auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_2/.dockerconfigjson.license similarity index 100% rename from auto-discovery/kubernetes/pull-secret-extractor/test_secrets/secret_2/.dockerconfigjson.license rename to auto-discovery/kubernetes/pull-secret-extractor/test/testdata/secrets/secret_2/.dockerconfigjson.license diff --git a/auto-discovery/kubernetes/pull-secret-extractor/test_docker_image.py b/auto-discovery/kubernetes/pull-secret-extractor/test_docker_image.py deleted file mode 100644 index 945c84dd96..0000000000 --- a/auto-discovery/kubernetes/pull-secret-extractor/test_docker_image.py +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-FileCopyrightText: the secureCodeBox authors -# -# SPDX-License-Identifier: Apache-2.0 - -from unittest import TestCase - -from docker_image import get_domain_from_docker_image - - -class Test(TestCase): - def test_get_domain_from_docker_image_with_no_domain(self): - test_image = "foo/bar" - domain = get_domain_from_docker_image(test_image) - self.assertEqual("docker.io", domain) - - def test_get_domain_from_docker_image_with_dockerio_domain(self): - test_image = "docker.io/foo/bar" - domain = get_domain_from_docker_image(test_image) - self.assertEqual("docker.io", domain) - - def test_get_domain_from_docker_image_with_non_dockerio_domain(self): - test_image = "test.xyz/foo/bar" - domain = get_domain_from_docker_image(test_image) - self.assertEqual("test.xyz", domain) - - def test_get_domain_from_docker_image_with_single_world_image(self): - test_image = "ubuntu" - domain = get_domain_from_docker_image(test_image) - self.assertEqual("docker.io", domain) diff --git a/auto-discovery/kubernetes/pull-secret-extractor/test_secret_extraction.py b/auto-discovery/kubernetes/pull-secret-extractor/test_secret_extraction.py deleted file mode 100644 index a0a6191eb9..0000000000 --- a/auto-discovery/kubernetes/pull-secret-extractor/test_secret_extraction.py +++ /dev/null @@ -1,65 +0,0 @@ -# SPDX-FileCopyrightText: the secureCodeBox authors -# -# SPDX-License-Identifier: Apache-2.0 - -import sys -import unittest - -from unittest.mock import MagicMock - -# mock kubernetes import so it doesnt need to be installed to run these tests -sys.modules['kubernetes'] = MagicMock() -from secret_extraction import * - - -class MyTestCase(unittest.TestCase): - - def test_get_raw_secrets(self): - actual = get_raw_secrets('test_secrets') - - with open('test_secrets/secret_1/.dockerconfigjson') as file: - expected_secret_1 = json.load(file) - - with open('test_secrets/secret_2/.dockerconfigjson') as file: - expected_secret_2 = json.load(file) - - # for some reason assertCountEqual doesnt work here - self.assertIn(expected_secret_1, actual) - self.assertIn(expected_secret_2, actual) - - def test_get_correct_secret(self): - with open('test_secrets/secret_1/.dockerconfigjson') as file: - secret_list = [json.load(file)] - - with open('test_secrets/secret_2/.dockerconfigjson') as file: - secret_list.append(json.load(file)) - - actual = get_correct_secret('localhost:5000', secret_list) - - # testuser:testpassword base64 encoded - expected = {'auth': 'dGVzdHVzZXI6dGVzdHBhc3N3b3Jk'} - - self.assertCountEqual(expected, actual) - - def test_get_user_and_password_given_auth_string(self): - secret = {'auth': 'dGVzdHVzZXI6dGVzdHBhc3N3b3Jk'} - actual = get_user_and_password(secret) - - # testuser, testpassword base64 encoded - expected = ('dGVzdHVzZXI=', 'dGVzdHBhc3N3b3Jk') - - self.assertEqual(expected, actual) - - def test_get_and_password_given_username_and_password_as_separate_string(self): - secret = { - 'username': 'dGVzdHVzZXI=', - 'password': 'dGVzdHBhc3N3b3Jk' - } - actual = get_user_and_password(secret) - - expected = (secret['username'], secret['password']) - self.assertEqual(expected, actual) - - -if __name__ == '__main__': - unittest.main()