diff --git a/.github/workflows/oci-auth-cd.yaml b/.github/workflows/oci-auth-cd.yaml new file mode 100644 index 0000000000..6de937097a --- /dev/null +++ b/.github/workflows/oci-auth-cd.yaml @@ -0,0 +1,93 @@ +name: CD / OCI Authentication Sidecar + +on: + pull_request: + branches: + - "master" + paths: + - "go/oci-auth/**" + push: + tags: + - 'v*.*.*' + +permissions: + contents: read + +env: + GOPATH: /home/runner/go + GOBIN: /home/runner/go/bin + GOPROXY: "https://proxy.golang.org" + +jobs: + test: + name: Unit test + runs-on: ubuntu-20.04 + defaults: + run: + shell: bash + working-directory: go/oci-auth + timeout-minutes: 5 + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: go/oci-auth/go.mod + cache: true + - run: go mod download + - run: PATH=$PATH:$GOPATH/bin make --directory=.. tools + - run: PATH=$PATH:$GOPATH/bin make test + publish-docker: + name: Build and push oci-auth container + runs-on: ubuntu-20.04 + defaults: + run: + shell: bash + working-directory: go/oci-auth + needs: [ test ] + permissions: + contents: 'read' + id-token: 'write' + packages: 'write' + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + - id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/pluralsh/oci-auth + gcr.io/pluralsh/oci-auth + docker.io/pluralsh/oci-auth + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: google-github-actions/auth@v1 + with: + workload_identity_provider: 'projects/${{ secrets.GOOGLE_PROJECT_ID }}/locations/global/workloadIdentityPools/github/providers/github' + service_account: 'terraform@pluralsh.iam.gserviceaccount.com' + token_format: 'access_token' + create_credentials_file: true + - uses: google-github-actions/setup-gcloud@v1.0.1 + - run: gcloud auth configure-docker -q + - uses: docker/login-action@v3 + with: + username: mjgpluralsh + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + - uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3.0.0 + - uses: docker/build-push-action@v5.1.0 + with: + context: "./go" + file: "./go/oci-auth/Dockerfile" + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + GIT_COMMIT=${{ github.sha }} + VERSION=${{ steps.meta.outputs.version }} diff --git a/.github/workflows/oci-auth-ci.yaml b/.github/workflows/oci-auth-ci.yaml new file mode 100644 index 0000000000..96958bc6ae --- /dev/null +++ b/.github/workflows/oci-auth-ci.yaml @@ -0,0 +1,68 @@ +name: CI / OCI Authentication Sidecar +on: + push: + branches: + - "master" + paths: + - ".github/workflows/oci-auth-ci.yaml" + - "go/oci-auth/**" + pull_request: + branches: + - "**" + paths: + - ".github/workflows/oci-auth-ci.yaml" + - "go/oci-auth/**" +permissions: + contents: read +env: + GOPATH: /home/runner/go/ + GOPROXY: "https://proxy.golang.org" +jobs: + build: + name: Build + runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: go/oci-auth + timeout-minutes: 5 + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: go/oci-auth/go.mod + cache: true + - run: go mod download + - run: PATH=$PATH:$GOPATH/bin make --directory=.. tools + - run: PATH=$PATH:$GOPATH/bin make build + unit-test: + name: Unit tests + runs-on: ubuntu-20.04 + defaults: + run: + shell: bash + working-directory: go/oci-auth + timeout-minutes: 5 + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: go/oci-auth/go.mod + cache: true + - run: go mod download + - run: PATH=$PATH:$GOPATH/bin make --directory=.. tools + - run: PATH=$PATH:$GOPATH/bin make test + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version-file: go/oci-auth/go.mod + check-latest: true + - uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 + with: + version: v1.59 + working-directory: go/oci-auth + args: --timeout=30m diff --git a/go/go.work b/go/go.work index dd621ed427..296586c07e 100644 --- a/go/go.work +++ b/go/go.work @@ -1,7 +1,8 @@ -go 1.22.0 +go 1.22.5 use ( ./client // github.com/pluralsh/console/go/client ./controller // github.com/pluralsh/console/go/controller + ./oci-auth // github.com/pluralsh/console/go/oci-auth ./tools // github.com/pluralsh/console/go/tools ) diff --git a/go/oci-auth/.gitignore b/go/oci-auth/.gitignore new file mode 100644 index 0000000000..dd85e7ab11 --- /dev/null +++ b/go/oci-auth/.gitignore @@ -0,0 +1,26 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/* +tmp/* +dist/* +Dockerfile.cross + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Kubernetes Generated files - skip generated files, except for vendored files +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +.vscode +*.swp +*.swo +*~ diff --git a/go/oci-auth/.golangci.yml b/go/oci-auth/.golangci.yml new file mode 100644 index 0000000000..8345d5caf8 --- /dev/null +++ b/go/oci-auth/.golangci.yml @@ -0,0 +1,33 @@ +run: + allow-parallel-runners: true +issues: + max-same-issues: 0 +linters: + disable-all: true + enable: + - dupl + - durationcheck + - errcheck + - exportloopref + - forcetypeassert + - goconst + - gocyclo + - godot + - gofmt + - goimports + - gosimple + - govet + - ineffassign + - lll + - makezero + - misspell + - nakedret + - nilerr + - prealloc + - predeclared + - staticcheck + - tenv + - typecheck + - unconvert + - unparam + - unused diff --git a/go/oci-auth/.goreleaser.yml b/go/oci-auth/.goreleaser.yml new file mode 100644 index 0000000000..5e672a98de --- /dev/null +++ b/go/oci-auth/.goreleaser.yml @@ -0,0 +1,61 @@ +# Visit https://goreleaser.com for documentation on how to customize this behavior. + +# Requires a GoReleaser Pro to run +partial: + by: goos + +project_name: plural-oci-auth-sidecar + +monorepo: + tag_prefix: v + +before: + hooks: + - go mod tidy + +builds: + - env: + - CGO_ENABLED=0 + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + ldflags: + - '-s -w -X github.com/pluralsh/console/go/oci-auth/internal/environment.Version={{.Version}} -X github.com/pluralsh/console/go/oci-auth/internal/environment.Commit={{.Commit}}' + goos: + - freebsd + - windows + - linux + - darwin + goarch: + - amd64 + - '386' + - arm + - arm64 + ignore: + - goos: darwin + goarch: '386' + binary: '{{ .ProjectName }}_v{{ .Version }}' + +archives: + - format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' + +checksum: + name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' + +snapshot: + name_template: "{{ incpatch .Version }}-next" + +changelog: + sort: asc + use: github-native + filters: + exclude: + - '^docs:' + - '^test:' + +release: + name_template: "{{ .ProjectName }}-v{{ .Version }}" + header: | + ## Plural OCI Authentication Sidecar release ({{ .Date }}) + Welcome to this new release of the Plural OCI Authentication Sidecar! diff --git a/go/oci-auth/Dockerfile b/go/oci-auth/Dockerfile new file mode 100644 index 0000000000..53526b5809 --- /dev/null +++ b/go/oci-auth/Dockerfile @@ -0,0 +1,30 @@ +FROM golang:1.22 as builder +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /workspace/oci-auth + +# Retrieve application dependencies. +# This allows the container build to reuse cached dependencies. +# Expecting to copy go.mod and if present go.sum. +COPY oci-auth/go.* ./ +RUN go mod download + +COPY oci-auth/internal ./internal +COPY oci-auth/main.go ./ + +# Build +# the GOARCH has not a default value to allow the binary be built according to the host where the command +# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO +# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, +# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -ldflags '-s -w -X github.com/pluralsh/console/go/oci-auth/internal/environment.Version=${VERSION} -X github.com/pluralsh/console/go/oci-auth/internal/environment.Commit=${GIT_COMMIT}' -a -o oci-auth . + +# Use distroless as minimal base image to package the oci-auth binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/oci-auth/oci-auth . +USER 65532:65532 + +ENTRYPOINT ["/oci-auth"] diff --git a/go/oci-auth/Makefile b/go/oci-auth/Makefile new file mode 100644 index 0000000000..eb82665248 --- /dev/null +++ b/go/oci-auth/Makefile @@ -0,0 +1,52 @@ +ROOT_DIRECTORY := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/../.. + +include $(ROOT_DIRECTORY)/go/paths.mk +include $(TOOLS_BINARIES_MAKEFILE) + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +##@ General + +.PHONY: help +help: ## show 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) + +.PHONY: show-dependency-updates +show-dependency-updates: ## show possible dependency updates + go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}} {{.Version}} -> {{.Update.Version}}{{end}}' -m all + +.PHONY: update-dependencies +update-dependencies: ## update dependencies + go get -u ./... + go mod tidy + +##@ Build + +.PHONY: build +build: ## build binary + go build -o bin/oci-auth . + +.PHONY: run +run: ## run locally + go run ./cmd/main.go + +.PHONY: release +release: lint test ## builds release version of the app, requires GoReleaser to work + goreleaser build --clean --single-target --snapshot + +##@ Checks + +.PHONY: lint +lint: ## run linters + @$(GOLANGCI_LINT) run ./... + +.PHONY: fix +fix: ## run linters and fix found issues + @$(GOLANGCI_LINT) run --fix ./... + +.PHONY: test +test: ## run tests + go test ./... diff --git a/go/oci-auth/go.mod b/go/oci-auth/go.mod new file mode 100644 index 0000000000..99e7d35db7 --- /dev/null +++ b/go/oci-auth/go.mod @@ -0,0 +1,75 @@ +module github.com/pluralsh/console/go/oci-auth + +go 1.22.5 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 + github.com/aws/aws-sdk-go-v2 v1.26.1 + github.com/aws/aws-sdk-go-v2/config v1.27.11 + github.com/aws/aws-sdk-go-v2/credentials v1.17.11 + github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 + github.com/fluxcd/pkg/oci v0.38.1 + github.com/gin-gonic/gin v1.10.0 + github.com/google/go-containerregistry v0.20.2 + github.com/samber/lo v1.46.0 + github.com/spf13/pflag v1.0.5 + k8s.io/klog/v2 v2.130.1 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect + github.com/aws/smithy-go v1.20.2 // indirect + github.com/bytedance/sonic v1.12.1 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/docker/cli v27.1.1+incompatible // indirect + github.com/docker/docker-credential-helpers v0.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.9.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + sigs.k8s.io/controller-runtime v0.18.1 // indirect +) diff --git a/go/oci-auth/go.sum b/go/oci-auth/go.sum new file mode 100644 index 0000000000..00575c7dfe --- /dev/null +++ b/go/oci-auth/go.sum @@ -0,0 +1,191 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/config v1.27.11 h1:f47rANd2LQEYHda2ddSCKYId18/8BhSRM4BULGmfgNA= +github.com/aws/aws-sdk-go-v2/config v1.27.11/go.mod h1:SMsV78RIOYdve1vf36z8LmnszlRWkwMQtomCAI0/mIE= +github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4 h1:Qr9W21mzWT3RhfYn9iAux7CeRIdbnTAqmiOlASqQgZI= +github.com/aws/aws-sdk-go-v2/service/ecr v1.27.4/go.mod h1:if7ybzzjOmDB8pat9FE35AHTY6ZxlYSy3YviSmFZv8c= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 h1:vN8hEbpRnL7+Hopy9dzmRle1xmDc7o8tmY0klsr175w= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.5/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24= +github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= +github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= +github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= +github.com/fluxcd/pkg/oci v0.38.1 h1:JIiZvi8WS5eoLIieJqL2kI8R875pK1PiVVijYlMTpNg= +github.com/fluxcd/pkg/oci v0.38.1/go.mod h1:mYVSxnpVutRmWu6mpwxm7hXFn6qdhLEjspL04ej/WZU= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= +github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +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/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= +github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/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/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/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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k= +golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +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/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +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= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +sigs.k8s.io/controller-runtime v0.18.1 h1:RpWbigmuiylbxOCLy0tGnq1cU1qWPwNIQzoJk+QeJx4= +sigs.k8s.io/controller-runtime v0.18.1/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= diff --git a/go/oci-auth/internal/args/args.go b/go/oci-auth/internal/args/args.go new file mode 100644 index 0000000000..9f63be5ac2 --- /dev/null +++ b/go/oci-auth/internal/args/args.go @@ -0,0 +1,35 @@ +package args + +import ( + "flag" + "net" + + "github.com/spf13/pflag" + "k8s.io/klog/v2" +) + +var ( + argAddress = pflag.IP("address", net.IPv4(0, 0, 0, 0), "address on which to serve the port") + argPort = pflag.Int("port", 8000, "port to listen to for incoming requests") + argTokenFile = pflag.String("token-file", "/token", "path to auth token file") +) + +func init() { + fs := flag.NewFlagSet("", flag.PanicOnError) + klog.InitFlags(fs) + _ = fs.Set("v", "1") + pflag.CommandLine.AddGoFlagSet(fs) + pflag.Parse() +} + +func Port() int { + return *argPort +} + +func Address() net.IP { + return *argAddress +} + +func TokenFile() string { + return *argTokenFile +} diff --git a/go/oci-auth/internal/environment/version.go b/go/oci-auth/internal/environment/version.go new file mode 100644 index 0000000000..35b33679a3 --- /dev/null +++ b/go/oci-auth/internal/environment/version.go @@ -0,0 +1,15 @@ +package environment + +const dev = "dev" + +var ( + // Version is managed by GoReleaser, see: https://goreleaser.com/cookbooks/using-main.version/ + Version = dev + + // Commit is managed by GoReleaser, see: https://goreleaser.com/cookbooks/using-main.version/ + Commit = "none" +) + +func IsDev() bool { + return Version == dev +} diff --git a/go/oci-auth/internal/handlers/auth/authentication.go b/go/oci-auth/internal/handlers/auth/authentication.go new file mode 100644 index 0000000000..f5564d9158 --- /dev/null +++ b/go/oci-auth/internal/handlers/auth/authentication.go @@ -0,0 +1,74 @@ +package auth + +import ( + "context" + "fmt" + "time" + + "github.com/google/go-containerregistry/pkg/authn" +) + +type Provider string + +const ( + AWS Provider = "AWS" + Azure Provider = "AZURE" + GCP Provider = "GCP" + Basic Provider = "BASIC" +) + +type AuthenticationRequest struct { + URL string `json:"url"` + Provider Provider `json:"provider"` + AWS *AWSCredentials `json:"aws,omitempty"` + Azure *AzureCredentials `json:"azure,omitempty"` + GCP *GCPCredentials `json:"gcp,omitempty"` + Basic *BasicCredentials `json:"basic,omitempty"` +} + +type AWSCredentials struct { + AccessKeyID *string `json:"accessKeyID,omitempty"` + SecretAccessKey *string `json:"secretAccessKey,omitempty"` + SessionToken *string `json:"sessionToken,omitempty"` + AssumeRoleARN *string `json:"assumeRoleARN,omitempty"` + Region *string `json:"region,omitempty"` +} + +type AzureCredentials struct { + TenantID string `json:"tenantID"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` +} + +type GCPCredentials struct { + ApplicationCredentials string `json:"applicationCredentials"` +} + +type BasicCredentials struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type AuthenticationResponse struct { + authn.AuthConfig + Expiry *time.Time `json:"expiry,omitempty"` +} + +func authenticate(ctx context.Context, request *AuthenticationRequest) (*AuthenticationResponse, error) { + if request == nil { + return nil, fmt.Errorf("request cannot be nil") + } + + switch request.Provider { + case AWS: + return authenticateAWS(ctx, request.URL, request.AWS) + case Azure: + return authenticateAzure(ctx, request.URL, request.Azure) + case GCP: + return authenticateGCP(ctx, request.URL, request.GCP) + case Basic: + return authenticateBasic(request.Basic) + default: + return nil, fmt.Errorf("unknown auth provider: %q", request.Provider) + } +} diff --git a/go/oci-auth/internal/handlers/auth/aws.go b/go/oci-auth/internal/handlers/auth/aws.go new file mode 100644 index 0000000000..3af14f8567 --- /dev/null +++ b/go/oci-auth/internal/handlers/auth/aws.go @@ -0,0 +1,69 @@ +package auth + +import ( + "context" + "fmt" + + awssdk "github.com/aws/aws-sdk-go-v2/aws" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + awscreds "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/fluxcd/pkg/oci/auth/aws" +) + +func authenticateAWS(ctx context.Context, url string, credentials *AWSCredentials) (*AuthenticationResponse, error) { + config, err := getConfig(ctx, credentials) + if err != nil { + return nil, err + } + + client := aws.NewClient() + client.WithConfig(config) + + auth, expiry, err := client.LoginWithExpiry(ctx, true, url) + if err != nil { + return nil, err + } + + cfg, err := auth.Authorization() + if err != nil { + return nil, err + } + if cfg == nil { + return nil, fmt.Errorf("no authorization configuration found") + } + + return &AuthenticationResponse{ + AuthConfig: *cfg, + Expiry: &expiry, + }, nil +} + +func getConfig(ctx context.Context, credentials *AWSCredentials) (*awssdk.Config, error) { + // If credentials are not provided in the request, then use default credentials. + if credentials == nil { + return nil, nil + } + + // Otherwise use provided credentials. + config, err := awsconfig.LoadDefaultConfig(ctx) + if err != nil { + return nil, err + } + + if credentials.Region != nil { + config.Region = *credentials.Region + } + + if credentials.AccessKeyID != nil && credentials.SecretAccessKey != nil && credentials.SessionToken != nil { + config.Credentials = awscreds.NewStaticCredentialsProvider( + *credentials.AccessKeyID, *credentials.SecretAccessKey, *credentials.SessionToken) + } + + if credentials.AssumeRoleARN != nil { + config.Credentials = stscreds.NewAssumeRoleProvider(sts.NewFromConfig(config), *credentials.AssumeRoleARN) + } + + return &config, nil +} diff --git a/go/oci-auth/internal/handlers/auth/azure.go b/go/oci-auth/internal/handlers/auth/azure.go new file mode 100644 index 0000000000..368a0bd9ee --- /dev/null +++ b/go/oci-auth/internal/handlers/auth/azure.go @@ -0,0 +1,82 @@ +package auth + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/samber/lo" +) + +func authenticateAzure(ctx context.Context, url string, credentials *AzureCredentials) ( + *AuthenticationResponse, error) { + split := strings.SplitN(url, "/", 2) + if len(split) < 1 { + return nil, fmt.Errorf("invalid URL: %s", url) + } + endpoint := fmt.Sprintf("https://%s", split[0]) + + accessToken, err := getAccessToken(ctx, endpoint, credentials) + if err != nil { + return nil, err + } + + acrAccessToken, err := ExchangeACRAccessToken(endpoint, accessToken.Token) + if err != nil { + return nil, err + } + + return &AuthenticationResponse{ + AuthConfig: authn.AuthConfig{ + // nolint:lll + // See: https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication?tabs=azure-cli#az-acr-login-with---expose-token + Username: "00000000-0000-0000-0000-000000000000", + Password: acrAccessToken, + }, + Expiry: lo.ToPtr(time.Now().Add(defaultCacheExpirationInSeconds)), + }, nil +} + +func getAccessToken(ctx context.Context, endpoint string, credentials *AzureCredentials) (azcore.AccessToken, error) { + cloudCfg := getCloudConfiuration(endpoint) + options := policy.TokenRequestOptions{ + Scopes: []string{cloudCfg.Services[cloud.ResourceManager].Endpoint + "/" + ".default"}, + } + + // If credentials are provided in the request, then use them. + if credentials != nil { + cred, err := azidentity.NewClientSecretCredential( + credentials.TenantID, credentials.ClientID, credentials.ClientSecret, nil) + if err != nil { + return azcore.AccessToken{}, err + } + + return cred.GetToken(ctx, options) + } + + // Otherwise use default credentials. + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return azcore.AccessToken{}, err + } + + return cred.GetToken(ctx, options) +} + +func getCloudConfiuration(endpoint string) cloud.Configuration { + if strings.HasSuffix(endpoint, ".azurecr.cn") { + return cloud.AzureChina + } + + if strings.HasSuffix(endpoint, ".azurecr.us") { + return cloud.AzureGovernment + } + + return cloud.AzurePublic +} diff --git a/go/oci-auth/internal/handlers/auth/azure_token_exchanger.go b/go/oci-auth/internal/handlers/auth/azure_token_exchanger.go new file mode 100644 index 0000000000..528f85bd0d --- /dev/null +++ b/go/oci-auth/internal/handlers/auth/azure_token_exchanger.go @@ -0,0 +1,79 @@ +/* +MIT License + +Copyright (c) 2020 Microsoft Azure + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package auth + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" +) + +const defaultCacheExpirationInSeconds = 600 + +// ExchangeACRAccessToken exchanges an ARM access token to an ACR access token. +func ExchangeACRAccessToken(endpoint, accessToken string) (string, error) { + exchangeURL, err := url.Parse(endpoint) + if err != nil { + return "", err + } + exchangeURL.Path = path.Join(exchangeURL.Path, "oauth2/exchange") + + parameters := url.Values{} + parameters.Add("grant_type", "access_token") + parameters.Add("service", exchangeURL.Hostname()) + parameters.Add("access_token", accessToken) + + response, err := http.PostForm(exchangeURL.String(), parameters) + if err != nil { + return "", fmt.Errorf("failed to send token exchange request: %w", err) + } + defer response.Body.Close() + + responseBody, err := io.ReadAll(response.Body) + if err != nil { + return "", fmt.Errorf("failed to read request body: %w", err) + } + + if response.StatusCode != http.StatusOK { + return "", fmt.Errorf("ACR token exchange endpoint returned error status: %d, response: %s", + response.StatusCode, string(responseBody)) + } + + var tokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + Resource string `json:"resource"` + TokenType string `json:"token_type"` + } + err = json.Unmarshal(responseBody, &tokenResponse) + if err != nil { + return "", fmt.Errorf("failed to read token exchange response: %w, response: %s", err, string(responseBody)) + } + + return tokenResponse.RefreshToken, nil +} diff --git a/go/oci-auth/internal/handlers/auth/basic.go b/go/oci-auth/internal/handlers/auth/basic.go new file mode 100644 index 0000000000..fe6c9cd12a --- /dev/null +++ b/go/oci-auth/internal/handlers/auth/basic.go @@ -0,0 +1,21 @@ +package auth + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/authn" +) + +func authenticateBasic(credentials *BasicCredentials) (*AuthenticationResponse, error) { + if credentials == nil { + return nil, fmt.Errorf("no basic credentials provided") + } + + return &AuthenticationResponse{ + AuthConfig: authn.AuthConfig{ + Username: credentials.Username, + Password: credentials.Password, + }, + Expiry: nil, + }, nil +} diff --git a/go/oci-auth/internal/handlers/auth/gcp.go b/go/oci-auth/internal/handlers/auth/gcp.go new file mode 100644 index 0000000000..bd6cf3c50a --- /dev/null +++ b/go/oci-auth/internal/handlers/auth/gcp.go @@ -0,0 +1,63 @@ +package auth + +import ( + "context" + "encoding/base64" + "fmt" + + "github.com/fluxcd/pkg/oci/auth/gcp" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" +) + +const ( + jsonKeyUsername = "_json_key" + jsonKeyEncodedUsername = "_json_key_base64" +) + +func authenticateGCP(ctx context.Context, url string, credentials *GCPCredentials) (*AuthenticationResponse, error) { + // If credentials are provided in the request, then use them. + if credentials != nil { + return &AuthenticationResponse{ + AuthConfig: authn.AuthConfig{ + Username: GetUsername(credentials.ApplicationCredentials), + Password: credentials.ApplicationCredentials, + }, + Expiry: nil, + }, nil + } + + // Otherwise use default credentials. + ref, err := name.ParseReference(url) + if err != nil { + return nil, fmt.Errorf("could not parse reference from %s url: %w", url, err) + } + + auth, expiry, err := gcp.NewClient().LoginWithExpiry(ctx, true, url, ref) + if err != nil { + return nil, err + } + + cfg, err := auth.Authorization() + if err != nil { + return nil, err + } + if cfg == nil { + return nil, fmt.Errorf("no authorization configuration found") + } + + return &AuthenticationResponse{ + AuthConfig: *cfg, + Expiry: &expiry, + }, nil +} + +// See: https://cloud.google.com/artifact-registry/docs/docker/authentication#json-key +func GetUsername(applicationCredentials string) string { + _, err := base64.StdEncoding.DecodeString(applicationCredentials) + if err == nil { + return jsonKeyEncodedUsername + } + + return jsonKeyUsername +} diff --git a/go/oci-auth/internal/handlers/auth/handler.go b/go/oci-auth/internal/handlers/auth/handler.go new file mode 100644 index 0000000000..0950f72ee9 --- /dev/null +++ b/go/oci-auth/internal/handlers/auth/handler.go @@ -0,0 +1,28 @@ +package auth + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/pluralsh/console/go/oci-auth/internal/router" +) + +func init() { + router.RootGroup().POST("/auth", handleAuth) +} + +func handleAuth(c *gin.Context) { + request := new(AuthenticationRequest) + if err := c.Bind(request); err != nil { + c.JSON(http.StatusBadRequest, err) + return + } + + response, err := authenticate(c.Request.Context(), request) + if err != nil { + c.String(http.StatusInternalServerError, err.Error()) + return + } + + c.JSON(http.StatusOK, response) +} diff --git a/go/oci-auth/internal/handlers/health/handler.go b/go/oci-auth/internal/handlers/health/handler.go new file mode 100644 index 0000000000..6ce7eed1e0 --- /dev/null +++ b/go/oci-auth/internal/handlers/health/handler.go @@ -0,0 +1,16 @@ +package health + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/pluralsh/console/go/oci-auth/internal/router" +) + +func init() { + router.Router().GET("/health", handleHealth) +} + +func handleHealth(c *gin.Context) { + c.String(http.StatusOK, "OK") +} diff --git a/go/oci-auth/internal/router/router.go b/go/oci-auth/internal/router/router.go new file mode 100644 index 0000000000..d74b9bba6b --- /dev/null +++ b/go/oci-auth/internal/router/router.go @@ -0,0 +1,76 @@ +package router + +import ( + "net/http" + "os" + "strings" + + "github.com/gin-gonic/gin" + "github.com/pluralsh/console/go/oci-auth/internal/args" + "github.com/pluralsh/console/go/oci-auth/internal/environment" + "k8s.io/klog/v2" +) + +var ( + router *gin.Engine + rootGroup *gin.RouterGroup +) + +func init() { + if !environment.IsDev() { + gin.SetMode(gin.ReleaseMode) + } + + router = gin.Default() + _ = router.SetTrustedProxies(nil) + + rootGroup = router.Group("/") + rootGroup.Use(authMiddleware()) +} + +func authMiddleware() gin.HandlerFunc { + if args.TokenFile() == "" { + klog.Fatal("Auth token file is not specified") + } + + return func(c *gin.Context) { + requestHeaderToken := c.GetHeader("Authorization") + splitRequestHeaderToken := strings.Split(requestHeaderToken, "Token") + if len(splitRequestHeaderToken) != 2 { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"}) + c.Abort() + return + } + + requestToken := strings.TrimSpace(splitRequestHeaderToken[1]) + if requestToken == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing token"}) + c.Abort() + return + } + + token, err := os.ReadFile(args.TokenFile()) + if err != nil { + klog.Error("Could not read token file, got error:", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not read token file"}) + c.Abort() + return + } + + if requestToken != string(token) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + c.Abort() + return + } + + c.Next() + } +} + +func Router() *gin.Engine { + return router +} + +func RootGroup() *gin.RouterGroup { + return rootGroup +} diff --git a/go/oci-auth/main.go b/go/oci-auth/main.go new file mode 100644 index 0000000000..057a281499 --- /dev/null +++ b/go/oci-auth/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + + "github.com/pluralsh/console/go/oci-auth/internal/args" + "github.com/pluralsh/console/go/oci-auth/internal/environment" + "github.com/pluralsh/console/go/oci-auth/internal/router" + "k8s.io/klog/v2" + + // Importing route packages forces route registration. + _ "github.com/pluralsh/console/go/oci-auth/internal/handlers/auth" + _ "github.com/pluralsh/console/go/oci-auth/internal/handlers/health" +) + +func main() { + klog.Infof("Starting OCI authentication sidecar version %s, commit %s", environment.Version, environment.Commit) + + err := router.Router().Run(fmt.Sprintf("%s:%d", args.Address(), args.Port())) + if err != nil { + return + } +}