Compare commits

...

38 Commits

Author SHA1 Message Date
Maanav Shah f6164cf702 Merge pull request #511 from drone-plugins/CI-21939-fix
feat: [CI-21939]: reverting the changes in docker version
2026-06-04 11:39:54 +05:30
Chirag S f946dac9ae feat: [CI-21939]: reverting changes in the docker version 2026-06-03 10:22:02 +05:30
Maanav Shah 801ca62c15 Merge pull request #509 from maxknee/mk/update-docker
update docker version
2026-05-20 11:12:47 +05:30
dhiraj.chhawchharia@harness.io 95df76b75d Update pipeline drone-docker-harness 2026-05-05 12:23:39 +05:30
dhiraj.chhawchharia@harness.io d9b3767c94 Update pipeline drone-docker-harness 2026-05-05 12:08:01 +05:30
raghav.gupta@harness.io 1482f4d794 Update pipeline drone-docker-harness 2026-05-05 11:27:32 +05:30
Max Knee 19d0d55c04 update docker version
Signed-off-by: Max Knee <max.knee@nytimes.com>
2026-04-07 13:40:36 -04:00
raghav.gupta@harness.io ce792a8072 Update pipeline drone-docker-harness 2026-04-07 16:40:41 +05:30
raghav.gupta@harness.io 33ffeb9986 Update pipeline drone-docker-harness 2026-04-07 16:40:14 +05:30
raghav.gupta@harness.io 0eaa5cb475 Update pipeline drone-docker-harness 2026-04-07 16:27:37 +05:30
raghav.gupta@harness.io 4277ba8a92 Update pipeline drone-docker-harness 2026-04-07 16:27:02 +05:30
raghav.gupta@harness.io a95059bc84 Update pipeline drone-docker-harness 2026-04-07 16:26:36 +05:30
raghav.gupta@harness.io 3ba3d25d31 Update pipeline drone-docker-harness 2026-04-07 16:25:39 +05:30
raghav.gupta@harness.io a6ed4e0fb0 Update pipeline drone-docker-harness 2026-04-07 14:21:04 +05:30
raghav.gupta@harness.io c5bbcaaff5 Update pipeline drone-docker-harness 2026-04-07 14:15:57 +05:30
raghav.gupta@harness.io b5191aec1c Update pipeline drone-docker-harness 2026-04-07 14:15:18 +05:30
raghav.gupta@harness.io 4eeea21716 Update pipeline drone-docker-harness 2026-04-07 14:12:09 +05:30
raghav.gupta@harness.io cff5ad3593 Update pipeline drone-docker-harness 2026-04-07 11:55:40 +05:30
ebtasam-faridy 3209af48cb Merge pull request #508 from drone-plugins/Remove-EoL-Components
fix: [CI-21707]: Remove EoL Components
2026-04-02 15:36:34 +05:30
Gargithakur01 a58ca41cd7 small-fix 2026-04-01 16:20:05 +05:30
Gargithakur01 59d39ec66a Fix discarded error in drone-acr cert setup
Made-with: Cursor
2026-04-01 16:11:23 +05:30
Gargithakur01 d1a514b832 update-go-version-in-yaml 2026-04-01 15:45:00 +05:30
Gargithakur01 fd52c4bfb0 Update pipeline to use golang:1.25.7
Updated all golang images from 1.24.11 and 1.23.0 to 1.25.7 to match the go.mod requirement (go 1.25.7).

Made-with: Cursor
2026-04-01 12:52:00 +05:30
Gargithakur01 f6f31ef8de Merge branch 'master' into Remove-EoL-Components
Resolved conflicts in cmd/drone-ecr/main.go and go.mod:
- Kept slog logging from our branch instead of logrus
- Integrated AWS SDK v2 dependencies from master
- Updated tagExists call to include ctx parameter from master

Made-with: Cursor
2026-03-31 23:03:23 +05:30
Gargithakur01 254f64fc18 fix: [CI-21707]: Remove EoL Components 2026-03-31 22:37:18 +05:30
chhawchharia 2f6803e300 feat: [CI-21342]: Aws migrated and vulnerabilities fixed (#505)
Made-with: Cursor
2026-03-04 11:42:44 +05:30
ebtasam-faridy f5f11face3 Merge pull request #504 from drone-plugins/ci-18951
feat: [CI-18951]: check for HARNESS prefixed proxy variables
2026-02-23 17:07:20 +05:30
Chirag S e70d271e93 feat: [CI-18951]: added a log when harness fallback is used 2026-02-18 10:56:51 +05:30
Chirag S f32aa46ea8 feat: [CI-18951]: added unit tests and better comments for the changes 2026-02-18 10:40:37 +05:30
Chirag S 5810bf8a5a feat: [CI-18951]: check for HARNESS prefixed proxy variables 2026-02-13 14:47:58 +05:30
Anurag Madnawat 23887402c3 [feat]: [CI-20260]: Make daemon retry count configurable (#503) 2026-02-11 11:04:38 +05:30
ebtasam-faridy e9bba4ffcf Update pipeline drone-docker-harness (#502) 2026-01-28 19:07:35 +05:30
ebtasam-faridy 7b900ae75d Ci 20437 (#499)
* fix: [CI-20437] Golang version update for vulnerability fix

* fix: [CI-20437] Golang version update for vulnerability fix
2026-01-28 14:13:48 +05:30
OP (oppenheimer) aabeaaf7bb feat: [CI-20527]: add push-only mode to skip build and push pre-existing images (#500)
* Add push-only support

* Include support for PLUGIN_NO_PUSH as well
2026-01-26 22:55:18 +05:30
Abhay 6799ac9418 fix: [CI-19670]: vul fix for jws version (#497) 2025-11-26 13:41:15 +05:30
tapankarangiya 123a133f01 feat: [CI-19349]: Added oidc support for azure connector (#496)
* feat: [CI-19349]: Added oidc support for azure connector

* feat: [CI-19349]: Added env variables

* feat: [CI-19349]: Added tests

* Update cmd/drone-acr/main.go

* Update cmd/drone-acr/main.go

* feat: [CI-19349]: Added Debug statements

---------

Co-authored-by: OP (oppenheimer) <21008429+Ompragash@users.noreply.github.com>
2025-10-24 11:48:46 +05:30
OP (oppenheimer) 58bfad7a29 feat: [CI-18308]: Add Cosign Image Signing Support (#494)
* Add signing support via cosign

* Updated docker.go

* Add signing support via cosign

* Updated docker.go

* Updated docker.go

* Updated docker.go

* Updated docker.go

* Updated docker.go

* Updated dockerfiles
2025-08-01 00:42:10 +05:30
Raghav 0493478ac1 feat: [CI-17953]: Add warning if base image connector is not provided (#492) 2025-07-09 16:07:40 +05:30
22 changed files with 1703 additions and 299 deletions
+30 -30
View File
@@ -12,7 +12,7 @@ platform:
steps: steps:
- name: vet - name: vet
image: golang:1.23 image: golang:1.24.11
commands: commands:
- go vet ./... - go vet ./...
environment: environment:
@@ -22,7 +22,7 @@ steps:
path: /go path: /go
- name: test - name: test
image: golang:1.23 image: golang:1.24.11
commands: commands:
- go test -cover ./... - go test -cover ./...
environment: environment:
@@ -55,7 +55,7 @@ platform:
steps: steps:
- name: go build - name: go build
image: golang:1.23 image: golang:1.24.11
environment: environment:
CGO_ENABLED: 0 CGO_ENABLED: 0
commands: commands:
@@ -162,7 +162,7 @@ platform:
steps: steps:
- name: go build - name: go build
image: golang:1.23 image: golang:1.24.11
environment: environment:
CGO_ENABLED: 0 CGO_ENABLED: 0
commands: commands:
@@ -264,7 +264,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-docker ./cmd/drone-docker' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-docker ./cmd/drone-docker'
environment: environment:
@@ -275,7 +275,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-docker ./cmd/drone-docker' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-docker ./cmd/drone-docker'
environment: environment:
@@ -285,7 +285,7 @@ steps:
- tag - tag
- name: executable - name: executable
image: golang:1.23 image: golang:1.24.11
commands: commands:
- ./release/linux/amd64/drone-docker --help - ./release/linux/amd64/drone-docker --help
@@ -329,7 +329,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-docker ./cmd/drone-docker' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-docker ./cmd/drone-docker'
environment: environment:
@@ -340,7 +340,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-docker ./cmd/drone-docker' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-docker ./cmd/drone-docker'
environment: environment:
@@ -350,7 +350,7 @@ steps:
- tag - tag
- name: executable - name: executable
image: golang:1.23 image: golang:1.24.11
commands: commands:
- ./release/linux/arm64/drone-docker --help - ./release/linux/arm64/drone-docker --help
@@ -429,7 +429,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-gcr ./cmd/drone-gcr' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-gcr ./cmd/drone-gcr'
environment: environment:
@@ -440,7 +440,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-gcr ./cmd/drone-gcr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-gcr ./cmd/drone-gcr'
environment: environment:
@@ -488,7 +488,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-gcr ./cmd/drone-gcr' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-gcr ./cmd/drone-gcr'
environment: environment:
@@ -499,7 +499,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-gcr ./cmd/drone-gcr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-gcr ./cmd/drone-gcr'
environment: environment:
@@ -582,7 +582,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-gar ./cmd/drone-gar' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-gar ./cmd/drone-gar'
environment: environment:
@@ -593,7 +593,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-gar ./cmd/drone-gar' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-gar ./cmd/drone-gar'
environment: environment:
@@ -641,7 +641,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-gar ./cmd/drone-gar' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-gar ./cmd/drone-gar'
environment: environment:
@@ -652,7 +652,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-gar ./cmd/drone-gar' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-gar ./cmd/drone-gar'
environment: environment:
@@ -734,7 +734,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-ecr ./cmd/drone-ecr' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-ecr ./cmd/drone-ecr'
environment: environment:
@@ -744,7 +744,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-ecr ./cmd/drone-ecr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-ecr ./cmd/drone-ecr'
environment: environment:
@@ -792,7 +792,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-ecr ./cmd/drone-ecr' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-ecr ./cmd/drone-ecr'
environment: environment:
@@ -802,7 +802,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-ecr ./cmd/drone-ecr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-ecr ./cmd/drone-ecr'
environment: environment:
@@ -885,7 +885,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-heroku ./cmd/drone-heroku' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/amd64/drone-heroku ./cmd/drone-heroku'
environment: environment:
@@ -895,7 +895,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-heroku ./cmd/drone-heroku' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/amd64/drone-heroku ./cmd/drone-heroku'
environment: environment:
@@ -944,7 +944,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-heroku ./cmd/drone-heroku' - 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/linux/arm64/drone-heroku ./cmd/drone-heroku'
environment: environment:
@@ -954,7 +954,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-heroku ./cmd/drone-heroku' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/linux/arm64/drone-heroku ./cmd/drone-heroku'
environment: environment:
@@ -1035,7 +1035,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-acr ./cmd/drone-acr' - 'go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-acr ./cmd/drone-acr'
environment: environment:
@@ -1045,7 +1045,7 @@ steps:
exclude: exclude:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-acr ./cmd/drone-acr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-acr ./cmd/drone-acr'
environment: environment:
@@ -1093,7 +1093,7 @@ platform:
steps: steps:
- name: build-push - name: build-push
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-acr ./cmd/drone-acr' - 'go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-acr ./cmd/drone-acr'
environment: environment:
@@ -1104,7 +1104,7 @@ steps:
- tag - tag
- name: build-tag - name: build-tag
image: golang:1.23 image: golang:1.24.11
commands: commands:
- 'go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-acr ./cmd/drone-acr' - 'go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-acr ./cmd/drone-acr'
environment: environment:
+445 -4
View File
@@ -33,7 +33,7 @@ pipeline:
identifier: Run_1 identifier: Run_1
spec: spec:
connectorRef: Plugins_Docker_Hub_Connector connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.23.0 image: golang:1.25.7
shell: Sh shell: Sh
command: go vet ./... command: go vet ./...
- step: - step:
@@ -42,7 +42,7 @@ pipeline:
identifier: Run_2 identifier: Run_2
spec: spec:
connectorRef: Plugins_Docker_Hub_Connector connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.23.0 image: golang:1.25.7
shell: Sh shell: Sh
command: go test -cover ./... command: go test -cover ./...
- parallel: - parallel:
@@ -70,7 +70,7 @@ pipeline:
identifier: Build_Push identifier: Build_Push
spec: spec:
connectorRef: Plugins_Docker_Hub_Connector connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.23.0 image: golang:1.25.7
shell: Sh shell: Sh
command: go build -a -tags netgo -o release/linux/amd64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo> command: go build -a -tags netgo -o release/linux/amd64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo>
envVariables: envVariables:
@@ -157,7 +157,7 @@ pipeline:
identifier: buildpush identifier: buildpush
spec: spec:
connectorRef: Plugins_Docker_Hub_Connector connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.23.0 image: golang:1.25.7
shell: Sh shell: Sh
command: go build -a -tags netgo -o release/linux/arm64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo> command: go build -a -tags netgo -o release/linux/arm64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo>
envVariables: envVariables:
@@ -398,6 +398,412 @@ pipeline:
- acr - acr
buildIntelligence: buildIntelligence:
enabled: false enabled: false
- stage:
name: rf-linux-amd64
identifier: rf_linamd64
description: ""
type: CI
spec:
cloneCodebase: true
caching:
enabled: false
paths: []
platform:
os: Linux
arch: Amd64
runtime:
type: Cloud
spec: {}
execution:
steps:
- step:
type: GitClone
name: Clone RF Dockerfiles
identifier: clone_rf
spec:
connectorRef: RapidFortPlugins
build:
type: branch
spec:
branch: main
cloneDirectory: rf-plugins
- step:
type: Run
name: Build Binary
identifier: build_binary
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.25.7
shell: Sh
command: go build -a -tags netgo -o release/linux/amd64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo>
envVariables:
CGO_ENABLED: "0"
strategy:
matrix:
repo:
- docker
- acr
- ecr
- gar
- step:
type: Plugin
name: RF Build and Push on Tag
identifier: rf_docker_build_push_tag
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/docker
settings:
username: <+secrets.getValue("harnesssecureusername")>
password: <+secrets.getValue("dockerHarnessSecurePwd")>
repo: harnesssecure/<+matrix.repo>
dockerfile: rf-plugins/drone-docker/docker/<+matrix.repo>/Dockerfile.linux.amd64.rf
auto_tag: "true"
auto_tag_suffix: linux-amd64
base_image_username: <+secrets.getValue("harness0HARUsername")>
base_image_password: <+secrets.getValue("harness0HARPAT")>
base_image_registry: harness0.harness.io/oci/docker_artifacts
when:
stageStatus: Success
condition: <+codebase.build.type> == "tag"
strategy:
matrix:
repo:
- docker
- acr
- ecr
- gar
- step:
type: BuildAndPushDockerRegistry
name: RF Build and Push on Branch
identifier: rf_build_push_branch
spec:
connectorRef: harnesssecure
repo: harnesssecure/<+matrix.repo>
tags:
- linux-amd64
caching: false
dockerfile: rf-plugins/drone-docker/docker/<+matrix.repo>/Dockerfile.linux.amd64.rf
envVariables:
PLUGIN_BASE_IMAGE_USERNAME: <+secrets.getValue("harness0HARUsername")>
PLUGIN_BASE_IMAGE_PASSWORD: <+secrets.getValue("harness0HARPAT")>
PLUGIN_BASE_IMAGE_REGISTRY: harness0.harness.io/oci/docker_artifacts
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
repo:
- docker
- acr
- ecr
- gar
variables:
- name: CI_ENABLE_BARE_METAL
type: String
description: ""
required: false
value: "false"
- stage:
name: rf-linux-arm64
identifier: rf_linarm64
description: ""
type: CI
spec:
cloneCodebase: true
caching:
enabled: false
paths: []
platform:
os: Linux
arch: Arm64
runtime:
type: Cloud
spec: {}
execution:
steps:
- step:
type: GitClone
name: Clone RF Dockerfiles
identifier: clone_rf
spec:
connectorRef: RapidFortPlugins
build:
type: branch
spec:
branch: main
cloneDirectory: rf-plugins
- step:
type: Run
name: Build Binary
identifier: build_binary
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.25.7
shell: Sh
command: go build -a -tags netgo -o release/linux/arm64/drone-<+matrix.repo> ./cmd/drone-<+matrix.repo>
envVariables:
CGO_ENABLED: "0"
strategy:
matrix:
repo:
- docker
- acr
- ecr
- gar
- step:
type: Plugin
name: RF Build and Push on Tag
identifier: rf_docker_build_push_tag
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/docker
settings:
username: <+secrets.getValue("harnesssecureusername")>
password: <+secrets.getValue("dockerHarnessSecurePwd")>
repo: harnesssecure/<+matrix.repo>
dockerfile: rf-plugins/drone-docker/docker/<+matrix.repo>/Dockerfile.linux.arm64.rf
auto_tag: "true"
auto_tag_suffix: linux-arm64
base_image_username: <+secrets.getValue("harness0HARUsername")>
base_image_password: <+secrets.getValue("harness0HARPAT")>
base_image_registry: harness0.harness.io/oci/docker_artifacts
when:
stageStatus: Success
condition: <+codebase.build.type> == "tag"
strategy:
matrix:
repo:
- docker
- acr
- ecr
- gar
- step:
type: BuildAndPushDockerRegistry
name: RF Build and Push on Branch
identifier: rf_build_push_branch
spec:
connectorRef: harnesssecure
repo: harnesssecure/<+matrix.repo>
tags:
- linux-arm64
caching: false
dockerfile: rf-plugins/drone-docker/docker/<+matrix.repo>/Dockerfile.linux.arm64.rf
envVariables:
PLUGIN_BASE_IMAGE_USERNAME: <+secrets.getValue("harness0HARUsername")>
PLUGIN_BASE_IMAGE_PASSWORD: <+secrets.getValue("harness0HARPAT")>
PLUGIN_BASE_IMAGE_REGISTRY: harness0.harness.io/oci/docker_artifacts
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
repo:
- docker
- acr
- ecr
- gar
variables:
- name: CI_ENABLE_BARE_METAL
type: String
description: ""
required: false
value: "false"
- stage:
name: win-1809-amd64-rf
identifier: win1809amd64rf
description: ""
type: CI
spec:
cloneCodebase: true
caching:
enabled: true
infrastructure:
type: VM
spec:
type: Pool
spec:
poolName: windows-2019
os: Windows
execution:
steps:
- step:
type: GitClone
name: Clone RF Dockerfiles
identifier: Clone_RF_Dockerfiles
spec:
connectorRef: RapidFortPlugins
cloneDirectory: rf-plugins
build:
type: branch
spec:
branch: main
- step:
type: Run
name: Build Binary
identifier: go_build
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.23.0
shell: Sh
command: |-
# disable cgo
export CGO_ENABLED=0
go build -o release/windows/amd64/drone-<+matrix.repo>.exe ./cmd/drone-<+matrix.repo>
strategy:
matrix:
repo:
- docker
- gcr
- gar
- ecr
- acr
- step:
type: Plugin
name: RF Build and Push on Tag
identifier: RF_Build_and_Push_on_Tag
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/docker
settings:
username: <+secrets.getValue("harnesssecureusername")>
password: <+secrets.getValue("dockerHarnessSecurePwd")>
repo: harnesssecure/<+matrix.repo>
dockerfile: rf-plugins/drone-docker/docker/<+matrix.repo>/Dockerfile.windows.amd64.1809.rf
auto_tag: "true"
auto_tag_suffix: windows-1809-amd64
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
repo:
- docker
- gar
- ecr
- acr
- step:
type: BuildAndPushDockerRegistry
name: RF Build and Push on Branch
identifier: rf_build_push_branch
spec:
connectorRef: harnesssecure
repo: harnesssecure/<+matrix.repo>
tags:
- windows-1809-amd64
caching: false
dockerfile: rf-plugins/drone-docker/docker/<+matrix.repo>/Dockerfile.windows.amd64.1809.rf
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
repo:
- docker
- gar
- ecr
- acr
delegateSelectors:
- windows-vm
- stage:
name: win-ltsc2022-amd64-rf
identifier: winamd64rf
description: ""
type: CI
spec:
cloneCodebase: true
caching:
enabled: false
paths: []
platform:
os: Windows
arch: Amd64
runtime:
type: Cloud
spec: {}
execution:
steps:
- step:
type: GitClone
name: Clone RF Dockerfiles
identifier: Clone_RF_Dockerfiles
spec:
connectorRef: RapidFortPlugins
cloneDirectory: rf-plugins
build:
type: branch
spec:
branch: main
- step:
type: Run
name: Build Binary -ltsc2022
identifier: build_amd64ltsc2022
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: golang:1.23.0
shell: Sh
command: |-
# disable cgo
export CGO_ENABLED=0
go build -o release/windows/amd64/drone-<+matrix.repo>.exe ./cmd/drone-<+matrix.repo>
envVariables:
CGO_ENABLED: "0"
strategy:
matrix:
repo:
- docker
- gcr
- gar
- ecr
- acr
- step:
type: Plugin
name: RF Build and Push on Tag
identifier: RF_Build_and_Push_on_Tag
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/docker
settings:
username: <+secrets.getValue("harnesssecureusername")>
password: <+secrets.getValue("dockerHarnessSecurePwd")>
repo: harnesssecure/<+matrix.repo>
dockerfile: rf-plugins/drone-docker/docker/<+matrix.repo>/Dockerfile.windows.amd64.ltsc2022.rf
auto_tag: "true"
auto_tag_suffix: windows-ltsc2022-amd64
when:
stageStatus: Success
condition: <+codebase.build.type> == "tag"
strategy:
matrix:
repo:
- docker
- gar
- ecr
- acr
- step:
type: BuildAndPushDockerRegistry
name: RF Build and Push on Branch
identifier: rf_build_push_branch
spec:
connectorRef: harnesssecure
repo: harnesssecure/<+matrix.repo>
tags:
- windows-ltsc2022-amd64
caching: false
dockerfile: rf-plugins/drone-docker/docker/<+matrix.repo>/Dockerfile.windows.amd64.ltsc2022.rf
when:
stageStatus: Success
condition: <+codebase.build.type> == "branch"
strategy:
matrix:
repo:
- docker
- gar
- ecr
- acr
buildIntelligence:
enabled: false
- stage: - stage:
name: Manifest and Release name: Manifest and Release
identifier: Manifest identifier: Manifest
@@ -410,6 +816,18 @@ pipeline:
paths: [] paths: []
execution: execution:
steps: steps:
- step:
type: GitClone
name: Clone RF Manifest Templates
identifier: clone_rf_manifest
spec:
connectorRef: RapidFortPlugins
build:
type: branch
spec:
branch: main
cloneDirectory: rf-plugins
contextType: Pipeline
- step: - step:
type: Plugin type: Plugin
name: Manifest name: Manifest
@@ -435,6 +853,29 @@ pipeline:
- ecr - ecr
- heroku - heroku
- acr - acr
- step:
type: Plugin
name: RF Manifest
identifier: rf_manifest
spec:
connectorRef: Plugins_Docker_Hub_Connector
image: plugins/manifest
settings:
username: <+secrets.getValue("harnesssecureusername")>
password: <+secrets.getValue("dockerHarnessSecurePwd")>
auto_tag: "true"
ignore_missing: "true"
spec: rf-plugins/drone-docker/docker/<+matrix.repo>/manifest.tmpl
when:
stageStatus: Success
condition: <+codebase.build.type> == "tag"
strategy:
matrix:
repo:
- docker
- acr
- ecr
- gar
platform: platform:
os: Linux os: Linux
arch: Amd64 arch: Amd64
+162
View File
@@ -0,0 +1,162 @@
# Cosign Integration for Drone-Docker
This document describes how to use the cosign container image signing feature in drone-docker.
## Overview
The drone-docker plugin now supports automatic container image signing using cosign after each successful push. This provides cryptographic verification that images haven't been tampered with.
## Environment Variables
The plugin accepts three cosign-related environment variables:
### `PLUGIN_COSIGN_PRIVATE_KEY` (Required for signing)
- **Description**: Private key for signing (PEM format content or file path)
- **Format**: Either PEM content or file path to private key
- **Usage**: Should be provided via secrets
### `PLUGIN_COSIGN_PASSWORD` (Optional)
- **Description**: Password for encrypted private keys
- **Usage**: Only needed if your private key is password-protected
### `PLUGIN_COSIGN_PARAMS` (Optional)
- **Description**: Additional cosign parameters
- **Examples**:
- `-a build_id=123` (add annotations)
- `--tlog-upload=false` (disable transparency log)
- `--rekor-url=https://custom-rekor.example.com` (custom rekor instance)
## Usage Examples
### 1. Basic Signing (Drone)
```yaml
kind: pipeline
type: docker
name: default
steps:
- name: docker
image: plugins/docker
settings:
repo: myregistry/myapp
tags: latest
cosign_private_key:
from_secret: cosign_private_key
cosign_password:
from_secret: cosign_password
```
### 2. Advanced Signing with Annotations (Drone)
```yaml
steps:
- name: docker
image: plugins/docker
settings:
repo: myregistry/myapp
tags:
- latest
- ${DRONE_BUILD_NUMBER}
cosign_private_key:
from_secret: cosign_private_key
cosign_params: "-a build_id=${DRONE_BUILD_NUMBER} -a commit_sha=${DRONE_COMMIT_SHA} -a branch=${DRONE_BRANCH}"
```
### 3. Harness CI/CD Usage
```yaml
- step:
type: Plugin
name: Build and Sign
identifier: build_and_sign
spec:
connectorRef: account.harnessImage
image: plugins/docker
settings:
repo: myregistry/myapp
tags: <+pipeline.sequenceId>
cosign_private_key: <+secrets.getValue("cosign_private_key")>
cosign_password: <+secrets.getValue("cosign_password")>
cosign_params: "-a harness_build=<+pipeline.sequenceId> -a harness_project=<+project.name>"
```
## Key Management
### Generating Cosign Keys
```bash
# Generate a new key pair
cosign generate-key-pair
# This creates:
# - cosign.key (private key)
# - cosign.pub (public key)
```
### Storing Keys Securely
**Harness Secrets:**
1. Go to Project Settings → Secrets
2. Create new secret with type "File" for private key
3. Create new secret with type "Text" for password
## Security Features
### Automatic Validation
-**Private key format validation**: Ensures PEM format is correct
-**Password requirement detection**: Warns if encrypted key needs password
-**Keyless signing prevention**: Warns that OIDC keyless signing isn't supported
### Error Handling
- **Invalid private key**: `❌ Invalid private key format. Expected PEM format`
- **Missing password**: `🔐 Encrypted private key requires password. Set PLUGIN_COSIGN_PASSWORD`
- **Keyless signing**: `⚠️ WARNING: Keyless signing (OIDC) isn't supported yet in this plugin`
## Signing Behavior
### When Signing Occurs
-**After each successful push**: Images are signed immediately after push
-**Multiple tags**: Each tag gets signed individually
-**Push-only mode**: Works with existing images
-**Dry-run respect**: Skips signing in dry-run mode
### Image References
- **Preferred**: Signs by digest (e.g., `image@sha256:abc123...`) for security
- **Fallback**: Signs by tag if digest unavailable
### Authentication
- **Registry auth**: Automatically uses existing Docker registry credentials
## Verification
To verify a signed image:
```bash
# Verify with public key
cosign verify --key cosign.pub myregistry/myapp:latest
# Verify with annotations
cosign verify --key cosign.pub \
-a build_id=123 \
myregistry/myapp:latest
```
## Troubleshooting
### Common Issues
1. **"cosign: command not found"**
- The container image includes cosign binary
- Use the latest plugin image: `plugins/docker:latest`
2. **"keyless signing not supported"**
- This plugin only supports private key signing
- Don't use `--oidc` or `--identity-token` in `cosign_params`
3. **"encrypted private key requires password"**
- Set `PLUGIN_COSIGN_PASSWORD` environment variable
- Or use an unencrypted private key
4. **Registry authentication issues**
- Cosign uses the same Docker registry credentials
- Ensure Docker login is working first
+12 -2
View File
@@ -17,8 +17,14 @@ import (
"github.com/inhies/go-bytesize" "github.com/inhies/go-bytesize"
) )
// writeCard maintains backward compatibility by using TempTag
func (p Plugin) writeCard() error { func (p Plugin) writeCard() error {
cmd := exec.Command(dockerExe, "inspect", p.Build.TempTag) return p.writeCardForImage(p.Build.TempTag)
}
// writeCardForImage generates card for any image reference
func (p Plugin) writeCardForImage(imageRef string) error {
cmd := exec.Command(dockerExe, "inspect", imageRef)
data, err := cmd.CombinedOutput() data, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return err return err
@@ -38,7 +44,11 @@ func (p Plugin) writeCard() error {
for _, tag := range inspect.RepoTags { for _, tag := range inspect.RepoTags {
sliceTagStruct = append(sliceTagStruct, TagStruct{Tag: tag}) sliceTagStruct = append(sliceTagStruct, TagStruct{Tag: tag})
} }
inspect.ParsedRepoTags = sliceTagStruct[1:] // remove the first tag which is always "hash:latest" if len(sliceTagStruct) > 1 {
inspect.ParsedRepoTags = sliceTagStruct[1:] // remove the first tag which is always "hash:latest"
} else {
inspect.ParsedRepoTags = sliceTagStruct
}
// create the url from repo and registry // create the url from repo and registry
inspect.URL = mapRegistryToURL(p.Daemon.Registry, p.Build.Repo) inspect.URL = mapRegistryToURL(p.Daemon.Registry, p.Build.Repo)
cardData, _ := json.Marshal(inspect) cardData, _ := json.Marshal(inspect)
+55 -29
View File
@@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -16,10 +17,9 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
docker "github.com/drone-plugins/drone-docker" docker "github.com/drone-plugins/drone-docker"
azureutil "github.com/drone-plugins/drone-docker/internal/azure"
) )
type subscriptionUrlResponse struct { type subscriptionUrlResponse struct {
@@ -62,12 +62,14 @@ func main() {
password = getenv("SERVICE_PRINCIPAL_CLIENT_SECRET") password = getenv("SERVICE_PRINCIPAL_CLIENT_SECRET")
// Service principal credentials // Service principal credentials
clientId = getenv("CLIENT_ID") clientId = getenv("CLIENT_ID", "AZURE_CLIENT_ID", "AZURE_APP_ID", "PLUGIN_CLIENT_ID")
clientSecret = getenv("CLIENT_SECRET") clientSecret = getenv("CLIENT_SECRET", "PLUGIN_CLIENT_SECRET")
clientCert = getenv("CLIENT_CERTIFICATE") clientCert = getenv("CLIENT_CERTIFICATE", "PLUGIN_CLIENT_CERTIFICATE")
tenantId = getenv("TENANT_ID") tenantId = getenv("TENANT_ID", "AZURE_TENANT_ID", "PLUGIN_TENANT_ID")
subscriptionId = getenv("SUBSCRIPTION_ID") subscriptionId = getenv("SUBSCRIPTION_ID", "PLUGIN_SUBSCRIPTION_ID")
publicUrl = getenv("DAEMON_REGISTRY") publicUrl = getenv("DAEMON_REGISTRY", "PLUGIN_DAEMON_REGISTRY")
authorityHost = getenv("AZURE_AUTHORITY_HOST", "PLUGIN_AZURE_AUTHORITY_HOST")
idToken = getenv("PLUGIN_OIDC_TOKEN_ID")
) )
// default registry value // default registry value
@@ -79,12 +81,35 @@ func main() {
if username == "" && password == "" { if username == "" && password == "" {
// docker login credentials are not provided // docker login credentials are not provided
var err error var err error
username = defaultUsername username = defaultUsername
if idToken != "" && clientId != "" && tenantId != "" {
slog.Debug("using OIDC authentication flow")
var aadToken string
aadToken, err = azureutil.GetAADAccessTokenViaClientAssertion(context.Background(), tenantId, clientId, idToken, authorityHost)
if err != nil {
slog.Error("failed to get AAD access token", "error", err)
os.Exit(1)
}
var p string
p, err = getPublicUrl(aadToken, registry, subscriptionId)
if err == nil {
publicUrl = p
} else {
fmt.Fprintf(os.Stderr, "failed to get public url with error: %s\n", err)
}
password, err = fetchACRToken(tenantId, aadToken, registry)
if err != nil {
slog.Error("failed to fetch ACR token", "error", err)
os.Exit(1)
}
} else {
password, publicUrl, err = getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, registry) password, publicUrl, err = getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, registry)
if err != nil { if err != nil {
logrus.Fatal(err) slog.Error("failed to get auth", "error", err)
os.Exit(1)
} }
} }
}
// must use the fully qualified repo name. If the // must use the fully qualified repo name. If the
// repo name does not have the registry prefix we // repo name does not have the registry prefix we
@@ -110,7 +135,8 @@ func main() {
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
logrus.Fatal(err) slog.Error("command execution failed", "error", err)
os.Exit(1)
} }
} }
@@ -130,26 +156,26 @@ func getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, regis
if clientCert != "" { if clientCert != "" {
err := setupACRCert(clientCert, acrCertPath) err := setupACRCert(clientCert, acrCertPath)
if err != nil { if err != nil {
errors.Wrap(err, "failed to push setup cert file") slog.Warn("failed to push setup cert file", "error", err)
} }
} }
// Get AZ env // Get AZ env
if err := os.Setenv(clientIdEnv, clientId); err != nil { if err := os.Setenv(clientIdEnv, clientId); err != nil {
return "", "", errors.Wrap(err, "failed to set env variable client Id") return "", "", fmt.Errorf("failed to set env variable client Id: %w", err)
} }
if err := os.Setenv(clientSecretKeyEnv, clientSecret); err != nil { if err := os.Setenv(clientSecretKeyEnv, clientSecret); err != nil {
return "", "", errors.Wrap(err, "failed to set env variable client secret") return "", "", fmt.Errorf("failed to set env variable client secret: %w", err)
} }
if err := os.Setenv(tenantKeyEnv, tenantId); err != nil { if err := os.Setenv(tenantKeyEnv, tenantId); err != nil {
return "", "", errors.Wrap(err, "failed to set env variable tenant Id") return "", "", fmt.Errorf("failed to set env variable tenant Id: %w", err)
} }
if err := os.Setenv(certPathEnv, acrCertPath); err != nil { if err := os.Setenv(certPathEnv, acrCertPath); err != nil {
return "", "", errors.Wrap(err, "failed to set env variable cert path") return "", "", fmt.Errorf("failed to set env variable cert path: %w", err)
} }
env, err := azidentity.NewEnvironmentCredential(nil) env, err := azidentity.NewEnvironmentCredential(nil)
if err != nil { if err != nil {
return "", "", errors.Wrap(err, "failed to get env credentials from azure") return "", "", fmt.Errorf("failed to get env credentials from azure: %w", err)
} }
os.Unsetenv(clientIdEnv) os.Unsetenv(clientIdEnv)
os.Unsetenv(clientSecretKeyEnv) os.Unsetenv(clientSecretKeyEnv)
@@ -162,7 +188,7 @@ func getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, regis
} }
aadToken, err := env.GetToken(context.Background(), policy) aadToken, err := env.GetToken(context.Background(), policy)
if err != nil { if err != nil {
return "", "", errors.Wrap(err, "failed to fetch access token") return "", "", fmt.Errorf("failed to fetch access token: %w", err)
} }
// Get public URL for artifacts // Get public URL for artifacts
@@ -175,7 +201,7 @@ func getAuth(clientId, clientSecret, clientCert, tenantId, subscriptionId, regis
// Fetch token // Fetch token
ACRToken, err := fetchACRToken(tenantId, aadToken.Token, registry) ACRToken, err := fetchACRToken(tenantId, aadToken.Token, registry)
if err != nil { if err != nil {
return "", "", errors.Wrap(err, "failed to fetch ACR token") return "", "", fmt.Errorf("failed to fetch ACR token: %w", err)
} }
return ACRToken, publicUrl, nil return ACRToken, publicUrl, nil
} }
@@ -190,14 +216,14 @@ func fetchACRToken(tenantId, token, registry string) (string, error) {
} }
jsonResponse, err := http.PostForm(fmt.Sprintf("https://%s/oauth2/exchange", registry), formData) jsonResponse, err := http.PostForm(fmt.Sprintf("https://%s/oauth2/exchange", registry), formData)
if err != nil || jsonResponse == nil { if err != nil || jsonResponse == nil {
return "", errors.Wrap(err, "failed to fetch ACR token") return "", fmt.Errorf("failed to fetch ACR token: %w", err)
} }
// fetch token from response // fetch token from response
var response map[string]interface{} var response map[string]interface{}
err = json.NewDecoder(jsonResponse.Body).Decode(&response) err = json.NewDecoder(jsonResponse.Body).Decode(&response)
if err != nil { if err != nil {
return "", errors.Wrap(err, "failed to decode oauth exchange response") return "", fmt.Errorf("failed to decode oauth exchange response: %w", err)
} }
// Parse the refresh_token from the response // Parse the refresh_token from the response
@@ -205,19 +231,19 @@ func fetchACRToken(tenantId, token, registry string) (string, error) {
if refreshToken, ok := t.(string); ok { if refreshToken, ok := t.(string); ok {
return refreshToken, nil return refreshToken, nil
} }
return "", errors.New("failed to cast refresh token from acr") return "", fmt.Errorf("failed to cast refresh token from acr")
} }
return "", errors.Wrap(err, "refresh token not found in response of oauth exchange call") return "", fmt.Errorf("refresh token not found in response of oauth exchange call: %w", err)
} }
func setupACRCert(cert, certPath string) error { func setupACRCert(cert, certPath string) error {
decoded, err := base64.StdEncoding.DecodeString(cert) decoded, err := base64.StdEncoding.DecodeString(cert)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to base64 decode ACR certificate") return fmt.Errorf("failed to base64 decode ACR certificate: %w", err)
} }
err = ioutil.WriteFile(certPath, decoded, 0644) err = ioutil.WriteFile(certPath, decoded, 0644)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to write ACR certificate") return fmt.Errorf("failed to write ACR certificate: %w", err)
} }
return nil return nil
} }
@@ -239,24 +265,24 @@ func getPublicUrl(token, registryUrl, subscriptionId string) (string, error) {
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return "", errors.Wrap(err, "failed to create request for getting container registry setting") return "", fmt.Errorf("failed to create request for getting container registry setting: %w", err)
} }
req.Header.Add("Authorization", "Bearer "+token) req.Header.Add("Authorization", "Bearer "+token)
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return "", errors.Wrap(err, "failed to send request for getting container registry setting") return "", fmt.Errorf("failed to send request for getting container registry setting: %w", err)
} }
defer res.Body.Close() defer res.Body.Close()
var response subscriptionUrlResponse var response subscriptionUrlResponse
err = json.NewDecoder(res.Body).Decode(&response) err = json.NewDecoder(res.Body).Decode(&response)
if err != nil { if err != nil {
return "", errors.Wrap(err, "failed to send request for getting container registry setting") return "", fmt.Errorf("failed to send request for getting container registry setting: %w", err)
} }
if len(response.Value) == 0 { if len(response.Value) == 0 {
return "", errors.New("no id present for base url") return "", fmt.Errorf("no id present for base url")
} }
return basePublicUrl + encodeParam(response.Value[0].ID), nil return basePublicUrl + encodeParam(response.Value[0].ID), nil
} }
+32
View File
@@ -0,0 +1,32 @@
package main
import (
"os"
"testing"
)
func TestGetAuthInputValidation(t *testing.T) {
// missing tenant
if _, _, err := getAuth("client", "secret", "", "", "sub", "registry.azurecr.io"); err == nil {
t.Fatalf("expected error for missing tenantId")
}
// missing clientId
if _, _, err := getAuth("", "secret", "", "tenant", "sub", "registry.azurecr.io"); err == nil {
t.Fatalf("expected error for missing clientId")
}
// missing both secret and cert
if _, _, err := getAuth("client", "", "", "tenant", "sub", "registry.azurecr.io"); err == nil {
t.Fatalf("expected error for missing credentials")
}
}
func TestGetenvAuthorityHost(t *testing.T) {
os.Setenv("AZURE_AUTHORITY_HOST", "https://login.microsoftonline.us")
defer os.Unsetenv("AZURE_AUTHORITY_HOST")
got := getenv("AZURE_AUTHORITY_HOST")
if got != "https://login.microsoftonline.us" {
t.Fatalf("expected AZURE_AUTHORITY_HOST to be returned, got %q", got)
}
}
+53 -12
View File
@@ -1,13 +1,13 @@
package main package main
import ( import (
"log/slog"
"os" "os"
"runtime" "runtime"
"strings" "strings"
"github.com/dchest/uniuri" "github.com/dchest/uniuri"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
docker "github.com/drone-plugins/drone-docker" docker "github.com/drone-plugins/drone-docker"
@@ -33,7 +33,7 @@ func main() {
cli.BoolFlag{ cli.BoolFlag{
Name: "dry-run", Name: "dry-run",
Usage: "dry run disables docker push", Usage: "dry run disables docker push",
EnvVar: "PLUGIN_DRY_RUN", EnvVar: "PLUGIN_DRY_RUN, PLUGIN_NO_PUSH",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "remote.url", Name: "remote.url",
@@ -112,6 +112,12 @@ func main() {
Usage: "don't start the docker daemon", Usage: "don't start the docker daemon",
EnvVar: "PLUGIN_DAEMON_OFF", EnvVar: "PLUGIN_DAEMON_OFF",
}, },
cli.IntFlag{
Name: "daemon.retry-count",
Usage: "number of retry attempts to reach docker daemon",
Value: 15,
EnvVar: "PLUGIN_DAEMON_RETRY_COUNT",
},
cli.StringFlag{ cli.StringFlag{
Name: "dockerfile", Name: "dockerfile",
Usage: "build dockerfile", Usage: "build dockerfile",
@@ -323,10 +329,37 @@ func main() {
Usage: "access token", Usage: "access token",
EnvVar: "ACCESS_TOKEN", EnvVar: "ACCESS_TOKEN",
}, },
// Cosign signing configuration
cli.StringFlag{
Name: "cosign.private-key",
Usage: "cosign private key content or file path for signing",
EnvVar: "PLUGIN_COSIGN_PRIVATE_KEY",
},
cli.StringFlag{
Name: "cosign.password",
Usage: "password for encrypted cosign private key",
EnvVar: "PLUGIN_COSIGN_PASSWORD",
},
cli.StringFlag{
Name: "cosign.params",
Usage: "additional cosign parameters (e.g., annotations, flags)",
EnvVar: "PLUGIN_COSIGN_PARAMS",
},
cli.BoolFlag{
Name: "push-only",
Usage: "skip build and only push images",
EnvVar: "PLUGIN_PUSH_ONLY",
},
cli.StringFlag{
Name: "source-image",
Usage: "source image to tag and push (format: repo:tag)",
EnvVar: "PLUGIN_SOURCE_IMAGE",
},
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
logrus.Fatal(err) slog.Error("application error", "error", err)
os.Exit(1)
} }
} }
@@ -393,11 +426,19 @@ func run(c *cli.Context) error {
DNSSearch: c.StringSlice("daemon.dns-search"), DNSSearch: c.StringSlice("daemon.dns-search"),
MTU: c.String("daemon.mtu"), MTU: c.String("daemon.mtu"),
Experimental: c.Bool("daemon.experimental"), Experimental: c.Bool("daemon.experimental"),
RetryCount: c.Int("daemon.retry-count"),
RegistryType: registryType, RegistryType: registryType,
}, },
BaseImageRegistry: c.String("docker.baseimageregistry"), BaseImageRegistry: c.String("docker.baseimageregistry"),
BaseImageUsername: c.String("docker.baseimageusername"), BaseImageUsername: c.String("docker.baseimageusername"),
BaseImagePassword: c.String("docker.baseimagepassword"), BaseImagePassword: c.String("docker.baseimagepassword"),
Cosign: docker.CosignConfig{
PrivateKey: c.String("cosign.private-key"),
Password: c.String("cosign.password"),
Params: c.String("cosign.params"),
},
PushOnly: c.Bool("push-only"),
SourceImage: c.String("source-image"),
} }
if c.Bool("tags.auto") { if c.Bool("tags.auto") {
@@ -408,16 +449,16 @@ func run(c *cli.Context) error {
tag, err := docker.DefaultTagSuffix( tag, err := docker.DefaultTagSuffix(
c.String("commit.ref"), c.String("commit.ref"),
c.String("tags.suffix"), c.String("tags.suffix"),
) )
if err != nil { if err != nil {
logrus.Printf("cannot build docker image for %s, invalid semantic version", c.String("commit.ref")) slog.Error("cannot build docker image, invalid semantic version", "commit_ref", c.String("commit.ref"), "error", err)
return err return err
}
plugin.Build.Tags = tag
} else {
logrus.Printf("skipping automated docker build for %s", c.String("commit.ref"))
return nil
} }
plugin.Build.Tags = tag
} else {
slog.Info("skipping automated docker build", "commit_ref", c.String("commit.ref"))
return nil
}
} }
return plugin.Exec() return plugin.Exec()
+101 -99
View File
@@ -1,35 +1,31 @@
package main package main
import ( import (
"context"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"log/slog"
"os" "os"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/ecr"
ecrtypes "github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ecr"
docker "github.com/drone-plugins/drone-docker" docker "github.com/drone-plugins/drone-docker"
) )
type ecrAPI interface {
DescribeImages(*ecr.DescribeImagesInput) (*ecr.DescribeImagesOutput, error)
}
const defaultRegion = "us-east-1" const defaultRegion = "us-east-1"
func main() { func main() {
// Load env-file if it exists first
if env := os.Getenv("PLUGIN_ENV_FILE"); env != "" { if env := os.Getenv("PLUGIN_ENV_FILE"); env != "" {
godotenv.Load(env) godotenv.Load(env)
} }
@@ -50,7 +46,6 @@ func main() {
skipPushIfTagExists = parseBoolOrDefault(false, getenv("PLUGIN_SKIP_PUSH_IF_TAG_EXISTS")) skipPushIfTagExists = parseBoolOrDefault(false, getenv("PLUGIN_SKIP_PUSH_IF_TAG_EXISTS"))
) )
// set the region
if region == "" { if region == "" {
region = defaultRegion region = defaultRegion
} }
@@ -62,13 +57,15 @@ func main() {
os.Setenv("AWS_SECRET_ACCESS_KEY", secret) os.Setenv("AWS_SECRET_ACCESS_KEY", secret)
} }
sess, err := session.NewSession(&aws.Config{Region: &region}) ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
if err != nil { if err != nil {
log.Fatal(fmt.Sprintf("error creating aws session: %v", err)) log.Fatal(fmt.Sprintf("error creating aws config: %v", err))
} }
svc := getECRClient(sess, assumeRole, externalId, idToken) svc := getECRClient(cfg, assumeRole, externalId, idToken)
username, password, defaultRegistry, err := getAuthInfo(svc) username, password, defaultRegistry, err := getAuthInfo(ctx, svc)
if registry == "" { if registry == "" {
registry = defaultRegistry registry = defaultRegistry
@@ -83,32 +80,32 @@ func main() {
} }
if create { if create {
err = ensureRepoExists(svc, trimHostname(repo, registry), scanOnPush) err = ensureRepoExists(ctx, svc, trimHostname(repo, registry), scanOnPush)
if err != nil { if err != nil {
log.Fatal(fmt.Sprintf("error creating ECR repo: %v", err)) log.Fatal(fmt.Sprintf("error creating ECR repo: %v", err))
} }
err = updateImageScannningConfig(svc, trimHostname(repo, registry), scanOnPush) err = updateImageScanningConfig(ctx, svc, trimHostname(repo, registry), scanOnPush)
if err != nil { if err != nil {
log.Fatal(fmt.Sprintf("error updating scan on push for ECR repo: %v", err)) log.Fatal(fmt.Sprintf("error updating scan on push for ECR repo: %v", err))
} }
} }
if lifecyclePolicy != "" { if lifecyclePolicy != "" {
p, err := ioutil.ReadFile(lifecyclePolicy) p, err := os.ReadFile(lifecyclePolicy)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := uploadLifeCyclePolicy(svc, string(p), trimHostname(repo, registry)); err != nil { if err := uploadLifeCyclePolicy(ctx, svc, string(p), trimHostname(repo, registry)); err != nil {
log.Fatal(fmt.Sprintf("error uploading ECR lifecycle policy: %v", err)) log.Fatal(fmt.Sprintf("error uploading ECR lifecycle policy: %v", err))
} }
} }
if repositoryPolicy != "" { if repositoryPolicy != "" {
p, err := ioutil.ReadFile(repositoryPolicy) p, err := os.ReadFile(repositoryPolicy)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := uploadRepositoryPolicy(svc, string(p), trimHostname(repo, registry)); err != nil { if err := uploadRepositoryPolicy(ctx, svc, string(p), trimHostname(repo, registry)); err != nil {
log.Fatal(fmt.Sprintf("error uploading ECR repository policy. %v", err)) log.Fatal(fmt.Sprintf("error uploading ECR repository policy. %v", err))
} }
} }
@@ -119,7 +116,6 @@ func main() {
os.Setenv("DOCKER_PASSWORD", password) os.Setenv("DOCKER_PASSWORD", password)
os.Setenv("PLUGIN_REGISTRY_TYPE", "ECR") os.Setenv("PLUGIN_REGISTRY_TYPE", "ECR")
// Skip if tag already exits for both mutable and immutable repos
if skipPushIfTagExists { if skipPushIfTagExists {
tagInput := getenv("PLUGIN_TAG", "PLUGIN_TAGS") tagInput := getenv("PLUGIN_TAG", "PLUGIN_TAGS")
var tags []string var tags []string
@@ -134,25 +130,26 @@ func main() {
} }
} }
repositoryName := trimHostname(repo, registry) repositoryName := trimHostname(repo, registry)
for _, t := range tags { for _, t := range tags {
exists, err := tagExists(svc, repositoryName, t) exists, err := tagExists(ctx, svc, repositoryName, t)
if err != nil { if err != nil {
logrus.Fatalf("Error checking if image exists for tag %s: %v", t, err) slog.Error("error checking if image exists for tag", "tag", t, "error", err)
} os.Exit(1)
if exists { }
logrus.Infof("%s:%s: Image tag exists. Skipping push.", repo, t) if exists {
os.Exit(0) slog.Info("image tag exists, skipping push", "repo", repo, "tag", t)
} os.Exit(0)
} }
} }
}
// invoke the base docker plugin binary
cmd := exec.Command(docker.GetDroneDockerExecCmd()) cmd := exec.Command(docker.GetDroneDockerExecCmd())
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
if err = cmd.Run(); err != nil { if err = cmd.Run(); err != nil {
logrus.Fatal(err) slog.Error("command execution failed", "error", err)
os.Exit(1)
} }
} }
@@ -162,57 +159,63 @@ func trimHostname(repo, registry string) string {
return repo return repo
} }
func ensureRepoExists(svc *ecr.ECR, name string, scanOnPush bool) (err error) { func ensureRepoExists(ctx context.Context, svc *ecr.Client, name string, scanOnPush bool) error {
input := &ecr.CreateRepositoryInput{} _, err := svc.CreateRepository(ctx, &ecr.CreateRepositoryInput{
input.SetRepositoryName(name) RepositoryName: aws.String(name),
input.SetImageScanningConfiguration(&ecr.ImageScanningConfiguration{ScanOnPush: &scanOnPush}) ImageScanningConfiguration: &ecrtypes.ImageScanningConfiguration{
_, err = svc.CreateRepository(input) ScanOnPush: scanOnPush,
},
})
if err != nil { if err != nil {
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ecr.ErrCodeRepositoryAlreadyExistsException { var rae *ecrtypes.RepositoryAlreadyExistsException
// eat it, we skip checking for existing to save two requests if errors.As(err, &rae) {
err = nil return nil
} }
return err
} }
return nil
return
} }
func updateImageScannningConfig(svc *ecr.ECR, name string, scanOnPush bool) (err error) { func updateImageScanningConfig(ctx context.Context, svc *ecr.Client, name string, scanOnPush bool) error {
input := &ecr.PutImageScanningConfigurationInput{} _, err := svc.PutImageScanningConfiguration(ctx, &ecr.PutImageScanningConfigurationInput{
input.SetRepositoryName(name) RepositoryName: aws.String(name),
input.SetImageScanningConfiguration(&ecr.ImageScanningConfiguration{ScanOnPush: &scanOnPush}) ImageScanningConfiguration: &ecrtypes.ImageScanningConfiguration{
_, err = svc.PutImageScanningConfiguration(input) ScanOnPush: scanOnPush,
},
})
return err return err
} }
func uploadLifeCyclePolicy(svc *ecr.ECR, lifecyclePolicy string, name string) (err error) { func uploadLifeCyclePolicy(ctx context.Context, svc *ecr.Client, lifecyclePolicy string, name string) error {
input := &ecr.PutLifecyclePolicyInput{} _, err := svc.PutLifecyclePolicy(ctx, &ecr.PutLifecyclePolicyInput{
input.SetLifecyclePolicyText(lifecyclePolicy) LifecyclePolicyText: aws.String(lifecyclePolicy),
input.SetRepositoryName(name) RepositoryName: aws.String(name),
_, err = svc.PutLifecyclePolicy(input) })
return err return err
} }
func uploadRepositoryPolicy(svc *ecr.ECR, repositoryPolicy string, name string) (err error) { func uploadRepositoryPolicy(ctx context.Context, svc *ecr.Client, repositoryPolicy string, name string) error {
input := &ecr.SetRepositoryPolicyInput{} _, err := svc.SetRepositoryPolicy(ctx, &ecr.SetRepositoryPolicyInput{
input.SetPolicyText(repositoryPolicy) PolicyText: aws.String(repositoryPolicy),
input.SetRepositoryName(name) RepositoryName: aws.String(name),
_, err = svc.SetRepositoryPolicy(input) })
return err return err
} }
func getAuthInfo(svc *ecr.ECR) (username, password, registry string, err error) { func getAuthInfo(ctx context.Context, svc *ecr.Client) (username, password, registry string, err error) {
var result *ecr.GetAuthorizationTokenOutput var result *ecr.GetAuthorizationTokenOutput
var decoded []byte var decoded []byte
result, err = svc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{}) result, err = svc.GetAuthorizationToken(ctx, &ecr.GetAuthorizationTokenInput{})
if err != nil { if err != nil {
return return
} }
if len(result.AuthorizationData) == 0 {
err = fmt.Errorf("no authorization data returned from ECR")
return
}
auth := result.AuthorizationData[0] auth := result.AuthorizationData[0]
token := *auth.AuthorizationToken token := *auth.AuthorizationToken
decoded, err = base64.StdEncoding.DecodeString(token) decoded, err = base64.StdEncoding.DecodeString(token)
@@ -221,7 +224,11 @@ func getAuthInfo(svc *ecr.ECR) (username, password, registry string, err error)
} }
registry = strings.TrimPrefix(*auth.ProxyEndpoint, "https://") registry = strings.TrimPrefix(*auth.ProxyEndpoint, "https://")
creds := strings.Split(string(decoded), ":") creds := strings.SplitN(string(decoded), ":", 2)
if len(creds) < 2 {
err = fmt.Errorf("invalid ECR authorization token format")
return
}
username = creds[0] username = creds[0]
password = creds[1] password = creds[1]
return return
@@ -233,7 +240,6 @@ func parseBoolOrDefault(defaultValue bool, s string) (result bool) {
if err != nil { if err != nil {
result = defaultValue result = defaultValue
} }
return return
} }
@@ -247,55 +253,51 @@ func getenv(key ...string) (s string) {
return return
} }
func getECRClient(sess *session.Session, role string, externalId string, idToken string) *ecr.ECR { func getECRClient(cfg aws.Config, role string, externalId string, idToken string) *ecr.Client {
if role == "" { if role == "" {
return ecr.New(sess) return ecr.NewFromConfig(cfg)
} }
stsSvc := sts.NewFromConfig(cfg)
if idToken != "" { if idToken != "" {
tempFile, err := os.CreateTemp("/tmp", "idToken-*.jwt") provider := stscreds.NewWebIdentityRoleProvider(stsSvc, role, identityToken(idToken))
if err != nil { cfg.Credentials = aws.NewCredentialsCache(provider)
log.Fatalf("Failed to create temporary file: %v", err) return ecr.NewFromConfig(cfg)
} }
defer tempFile.Close()
if err := os.Chmod(tempFile.Name(), 0600); err != nil { var provider *stscreds.AssumeRoleProvider
log.Fatalf("Failed to set file permissions: %v", err) if externalId != "" {
} provider = stscreds.NewAssumeRoleProvider(stsSvc, role, func(o *stscreds.AssumeRoleOptions) {
o.ExternalID = &externalId
if _, err := tempFile.WriteString(idToken); err != nil {
log.Fatalf("Failed to write ID token to temporary file: %v", err)
}
// Create credentials using the path to the ID token file
creds := stscreds.NewWebIdentityCredentials(sess, role, "", tempFile.Name())
return ecr.New(sess, &aws.Config{Credentials: creds})
} else if externalId != "" {
return ecr.New(sess, &aws.Config{
Credentials: stscreds.NewCredentials(sess, role, func(p *stscreds.AssumeRoleProvider) {
p.ExternalID = &externalId
}),
}) })
} else { } else {
return ecr.New(sess, &aws.Config{ provider = stscreds.NewAssumeRoleProvider(stsSvc, role)
Credentials: stscreds.NewCredentials(sess, role),
})
} }
cfg.Credentials = aws.NewCredentialsCache(provider)
return ecr.NewFromConfig(cfg)
} }
func tagExists(svc ecrAPI, repository, tag string) (bool, error) { func tagExists(ctx context.Context, svc *ecr.Client, repository, tag string) (bool, error) {
input := &ecr.DescribeImagesInput{ input := &ecr.DescribeImagesInput{
RepositoryName: aws.String(repository), RepositoryName: aws.String(repository),
ImageIds: []*ecr.ImageIdentifier{ ImageIds: []ecrtypes.ImageIdentifier{
{ImageTag: aws.String(tag)}, {ImageTag: aws.String(tag)},
}, },
} }
output, err := svc.DescribeImages(input) output, err := svc.DescribeImages(ctx, input)
if err != nil { if err != nil {
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == "ImageNotFoundException" { var inf *ecrtypes.ImageNotFoundException
if errors.As(err, &inf) {
return false, nil return false, nil
} }
return false, err return false, err
} }
return len(output.ImageDetails) > 0, nil return len(output.ImageDetails) > 0, nil
} }
type identityToken string
func (t identityToken) GetIdentityToken() ([]byte, error) {
return []byte(t), nil
}
+7 -4
View File
@@ -5,6 +5,7 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"log" "log"
"log/slog"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -15,7 +16,6 @@ import (
"github.com/drone-plugins/drone-docker/internal/gcp" "github.com/drone-plugins/drone-docker/internal/gcp"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
) )
@@ -58,11 +58,13 @@ func loadConfig() Config {
if idToken != "" && projectId != "" && poolId != "" && providerId != "" && serviceAccountEmail != "" { if idToken != "" && projectId != "" && poolId != "" && providerId != "" && serviceAccountEmail != "" {
federalToken, err := gcp.GetFederalToken(idToken, projectId, poolId, providerId) federalToken, err := gcp.GetFederalToken(idToken, projectId, poolId, providerId)
if err != nil { if err != nil {
logrus.Fatalf("Error (getFederalToken): %s", err) slog.Error("getFederalToken error", "error", err)
os.Exit(1)
} }
accessToken, err := gcp.GetGoogleCloudAccessToken(federalToken, serviceAccountEmail) accessToken, err := gcp.GetGoogleCloudAccessToken(federalToken, serviceAccountEmail)
if err != nil { if err != nil {
logrus.Fatalf("Error (getGoogleCloudAccessToken): %s", err) slog.Error("getGoogleCloudAccessToken error", "error", err)
os.Exit(1)
} }
config.AccessToken = accessToken config.AccessToken = accessToken
} else { } else {
@@ -110,7 +112,8 @@ func main() {
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
logrus.Fatal(err) slog.Error("command execution failed", "error", err)
os.Exit(1)
} }
} }
+7 -4
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"log" "log"
"log/slog"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -14,7 +15,6 @@ import (
"github.com/drone-plugins/drone-docker/internal/gcp" "github.com/drone-plugins/drone-docker/internal/gcp"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
) )
@@ -48,11 +48,13 @@ func loadConfig() Config {
if idToken != "" && projectId != "" && poolId != "" && providerId != "" && serviceAccountEmail != "" { if idToken != "" && projectId != "" && poolId != "" && providerId != "" && serviceAccountEmail != "" {
federalToken, err := gcp.GetFederalToken(idToken, projectId, poolId, providerId) federalToken, err := gcp.GetFederalToken(idToken, projectId, poolId, providerId)
if err != nil { if err != nil {
logrus.Fatalf("Error (getFederalToken): %s", err) slog.Error("getFederalToken error", "error", err)
os.Exit(1)
} }
accessToken, err := gcp.GetGoogleCloudAccessToken(federalToken, serviceAccountEmail) accessToken, err := gcp.GetGoogleCloudAccessToken(federalToken, serviceAccountEmail)
if err != nil { if err != nil {
logrus.Fatalf("Error (getGoogleCloudAccessToken): %s", err) slog.Error("getGoogleCloudAccessToken error", "error", err)
os.Exit(1)
} }
config.AccessToken = accessToken config.AccessToken = accessToken
} else { } else {
@@ -103,7 +105,8 @@ func main() {
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
logrus.Fatal(err) slog.Error("command execution failed", "error", err)
os.Exit(1)
} }
} }
+1
View File
@@ -11,6 +11,7 @@ import (
const dockerExe = "/usr/local/bin/docker" const dockerExe = "/usr/local/bin/docker"
const dockerdExe = "/usr/local/bin/dockerd" const dockerdExe = "/usr/local/bin/dockerd"
const dockerHome = "/root/.docker/" const dockerHome = "/root/.docker/"
const cosignExe = "/usr/local/bin/cosign"
func (p Plugin) startDaemon() { func (p Plugin) startDaemon() {
cmd := commandDaemon(p.Daemon) cmd := commandDaemon(p.Daemon)
+2
View File
@@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package docker package docker
@@ -5,6 +6,7 @@ package docker
const dockerExe = "C:\\bin\\docker.exe" const dockerExe = "C:\\bin\\docker.exe"
const dockerdExe = "" const dockerdExe = ""
const dockerHome = "C:\\ProgramData\\docker\\" const dockerHome = "C:\\ProgramData\\docker\\"
const cosignExe = "C:\\bin\\cosign.exe"
func (p Plugin) startDaemon() { func (p Plugin) startDaemon() {
// this is a no-op on windows // this is a no-op on windows
+378 -17
View File
@@ -30,6 +30,7 @@ type (
MTU string // Docker daemon mtu setting MTU string // Docker daemon mtu setting
IPv6 bool // Docker daemon IPv6 networking IPv6 bool // Docker daemon IPv6 networking
Experimental bool // Docker daemon enable experimental mode Experimental bool // Docker daemon enable experimental mode
RetryCount int // Number of retry attempts to reach Docker daemon
RegistryType drone.RegistryType // Docker registry type RegistryType drone.RegistryType // Docker registry type
} }
@@ -76,18 +77,28 @@ type (
SSHKeyPath string // Docker build ssh key path SSHKeyPath string // Docker build ssh key path
} }
// CosignConfig defines Cosign signing parameters.
CosignConfig struct {
PrivateKey string // Private key content (PEM format) or file path
Password string // Password for encrypted private keys
Params string // Additional cosign parameters
}
// Plugin defines the Docker plugin parameters. // Plugin defines the Docker plugin parameters.
Plugin struct { Plugin struct {
Login Login // Docker login configuration Login Login // Docker login configuration
Build Build // Docker build configuration Build Build // Docker build configuration
Daemon Daemon // Docker daemon configuration Daemon Daemon // Docker daemon configuration
Dryrun bool // Docker push is skipped Cosign CosignConfig // Cosign signing configuration
Cleanup bool // Docker purge is enabled Dryrun bool // Docker push is skipped
CardPath string // Card path to write file to Cleanup bool // Docker purge is enabled
ArtifactFile string // Artifact path to write file to CardPath string // Card path to write file to
BaseImageRegistry string // Docker registry to pull base image ArtifactFile string // Artifact path to write file to
BaseImageUsername string // Docker registry username to pull base image BaseImageRegistry string // Docker registry to pull base image
BaseImagePassword string // Docker registry password to pull base image BaseImageUsername string // Docker registry username to pull base image
BaseImagePassword string // Docker registry password to pull base image
PushOnly bool // Push only mode, skips build process
SourceImage string // Source image to push (optional)
} }
Card []struct { Card []struct {
@@ -127,14 +138,18 @@ func (p Plugin) Exec() error {
// poll the docker daemon until it is started. This ensures the daemon is // poll the docker daemon until it is started. This ensures the daemon is
// ready to accept connections before we proceed. // ready to accept connections before we proceed.
maxRetries := p.Daemon.RetryCount
if maxRetries <= 0 {
maxRetries = 15 // default value
}
for i := 0; ; i++ { for i := 0; ; i++ {
cmd := commandInfo() cmd := commandInfo()
err := cmd.Run() err := cmd.Run()
if err == nil { if err == nil {
break break
} }
if i == 15 { if i == maxRetries {
fmt.Println("Unable to reach Docker Daemon after 15 attempts.") fmt.Printf("Unable to reach Docker Daemon after %d attempts.\n", maxRetries)
break break
} }
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
@@ -193,6 +208,10 @@ func (p Plugin) Exec() error {
fmt.Println(out) fmt.Println(out)
return fmt.Errorf("Error authenticating base connector: exit status 1") return fmt.Errorf("Error authenticating base connector: exit status 1")
} }
} else if !p.PushOnly {
// Skip base image connector warning in push-only mode (not pulling anything)
fmt.Println("\033[33mTo ensure consistent and reliable pipeline execution, we recommend setting up a Base Image Connector.\033[0m\n" +
"\033[33mWhile optional at this time, configuring it helps prevent failures caused by Docker Hub's rate limits.\033[0m")
} }
// login to the Docker registry // login to the Docker registry
@@ -218,6 +237,16 @@ func (p Plugin) Exec() error {
} }
} }
// Enforce mutual exclusivity: push-only and dry-run cannot be used together
if p.PushOnly && p.Dryrun {
return fmt.Errorf("conflict: push-only and dry-run cannot be used together")
}
// Handle push-only mode if requested
if p.PushOnly {
return p.pushOnly()
}
if p.Build.Squash && !p.Daemon.Experimental { if p.Build.Squash && !p.Daemon.Experimental {
fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...") fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...")
p.Build.Squash = false p.Build.Squash = false
@@ -246,6 +275,14 @@ func (p Plugin) Exec() error {
cmds = append(cmds, commandBuild(p.Build)) // docker build cmds = append(cmds, commandBuild(p.Build)) // docker build
// Validate cosign configuration if present
if p.shouldSignWithCosign() {
if err := validateCosignConfig(p.Cosign); err != nil {
return fmt.Errorf("cosign validation failed: %w", err)
}
fmt.Println("🔐 Cosign signing enabled - images will be signed after push")
}
for _, tag := range p.Build.Tags { for _, tag := range p.Build.Tags {
cmds = append(cmds, commandTag(p.Build, tag)) // docker tag cmds = append(cmds, commandTag(p.Build, tag)) // docker tag
@@ -287,6 +324,31 @@ func (p Plugin) Exec() error {
} }
} }
// Handle cosign signing after all commands complete (like artifact generation)
if p.shouldSignWithCosign() && !p.Dryrun {
// Set up environment variables for cosign
os.Setenv("COSIGN_YES", "true")
if digest, err := getDigest(p.Build.TempTag); err == nil {
fmt.Printf("🔐 Found image digest: %s\n", digest)
// Sign with digest reference
imageRef := fmt.Sprintf("%s@%s", p.Build.Repo, digest)
cosignCmd := createCosignCommand(imageRef, p.Cosign)
executeCosignCommand(cosignCmd)
} else {
fmt.Printf("⚠️ WARNING: Could not get image digest for cosign signing: %s\n", err)
fmt.Printf(" Falling back to tag-based signing\n")
// Fall back to tag-based signing for each tag
for _, tag := range p.Build.Tags {
imageRef := fmt.Sprintf("%s:%s", p.Build.Repo, tag)
cosignCmd := createCosignCommand(imageRef, p.Cosign)
executeCosignCommand(cosignCmd)
}
}
}
// execute cleanup routines in batch mode // execute cleanup routines in batch mode
if p.Cleanup { if p.Cleanup {
// clear the slice // clear the slice
@@ -535,7 +597,8 @@ func addProxyValue(build *Build, key string) {
// helper function to get a proxy value from the environment. // helper function to get a proxy value from the environment.
// //
// assumes that the upper and lower case versions of are the same. // Checks in order: lowercase key, uppercase key, then HARNESS_<UPPERCASE_KEY>.
// Assumes that the upper and lower case versions are the same value.
func getProxyValue(key string) string { func getProxyValue(key string) string {
value := os.Getenv(key) value := os.Getenv(key)
@@ -543,15 +606,26 @@ func getProxyValue(key string) string {
return value return value
} }
return os.Getenv(strings.ToUpper(key)) value = os.Getenv(strings.ToUpper(key))
if len(value) > 0 {
return value
}
harnessValue := os.Getenv("HARNESS_" + strings.ToUpper(key))
if len(harnessValue) > 0 {
fmt.Printf("Using HARNESS_%s as proxy value for %s\n", strings.ToUpper(key), key)
}
return harnessValue
} }
// helper function that looks to see if a proxy value was set in the build args. // helper function that looks to see if a proxy value was set in the build args.
func hasProxyBuildArg(build *Build, key string) bool { func hasProxyBuildArg(build *Build, key string) bool {
keyUpper := strings.ToUpper(key) keyUpper := strings.ToUpper(key)
harnessKey := "HARNESS_" + keyUpper
for _, s := range build.Args { for _, s := range build.Args {
if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) || strings.HasPrefix(s, harnessKey) {
return true return true
} }
} }
@@ -560,9 +634,10 @@ func hasProxyBuildArg(build *Build, key string) bool {
} }
func hasProxyBuildArgNew(build *Build, key string) bool { func hasProxyBuildArgNew(build *Build, key string) bool {
keyUpper := strings.ToUpper(key) keyUpper := strings.ToUpper(key)
harnessKey := "HARNESS_" + keyUpper
for _, s := range build.ArgsNew { for _, s := range build.ArgsNew {
if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) || strings.HasPrefix(s, harnessKey) {
return true return true
} }
} }
@@ -642,6 +717,11 @@ func isCommandRmi(args []string) bool {
return len(args) > 2 && args[1] == "rmi" return len(args) > 2 && args[1] == "rmi"
} }
// helper to check if args match "cosign sign"
func isCommandCosign(args []string) bool {
return len(args) > 1 && args[0] == cosignExe
}
func commandRmi(tag string) *exec.Cmd { func commandRmi(tag string) *exec.Cmd {
return exec.Command(dockerExe, "rmi", tag) return exec.Command(dockerExe, "rmi", tag)
} }
@@ -678,7 +758,7 @@ func GetDroneDockerExecCmd() string {
} }
func getDigest(buildName string) (string, error) { func getDigest(buildName string) (string, error) {
cmd := exec.Command("docker", "inspect", "--format='{{index .RepoDigests 0}}'", buildName) cmd := exec.Command(dockerExe, "inspect", "--format='{{index .RepoDigests 0}}'", buildName)
output, err := cmd.Output() output, err := cmd.Output()
if err != nil { if err != nil {
return "", err return "", err
@@ -692,3 +772,284 @@ func getDigest(buildName string) (string, error) {
} }
return "", errors.New("unable to fetch digest") return "", errors.New("unable to fetch digest")
} }
// imageExists checks if an image exists in local daemon
func imageExists(tag string) bool {
cmd := exec.Command(dockerExe, "image", "inspect", tag)
return cmd.Run() == nil
}
// getDigestAfterPush gets digest from a pushed image
func getDigestAfterPush(tag string) (string, error) {
cmd := exec.Command(dockerExe, "inspect", "--format", "{{ index (split (index .RepoDigests 0) \"@\") 1 }}", tag)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get digest for %s: %w", tag, err)
}
return strings.TrimSpace(string(output)), nil
}
// shouldSignWithCosign determines if cosign signing should be performed
func (p Plugin) shouldSignWithCosign() bool {
return p.Cosign.PrivateKey != ""
}
// validateCosignConfig validates the cosign configuration
func validateCosignConfig(config CosignConfig) error {
if config.PrivateKey == "" {
return nil // No cosign config, skip silently
}
// Check if cosign binary is available
if _, err := exec.LookPath(cosignExe); err != nil {
fmt.Printf("❌ ERROR: cosign binary not found at %s\n", cosignExe)
fmt.Println(" Ensure you're using a plugin image that includes cosign")
return fmt.Errorf("cosign binary not available: %w", err)
}
// Check if it's trying to use keyless signing
if strings.Contains(config.Params, "--oidc") ||
strings.Contains(config.Params, "--identity-token") {
fmt.Println("⚠️ WARNING: Keyless signing (OIDC) isn't supported yet in this plugin. Use private key signing instead.")
return errors.New("keyless signing not supported")
}
// Validate private key format if it's PEM content
if strings.HasPrefix(config.PrivateKey, "-----BEGIN") {
if !isValidPEMKey(config.PrivateKey) {
return errors.New("❌ Invalid private key format. Expected PEM format")
}
// Check encrypted key password requirement
if isEncryptedPEMKey(config.PrivateKey) && config.Password == "" {
return errors.New("🔐 Encrypted private key requires password. Set PLUGIN_COSIGN_PASSWORD")
}
} else {
// File-based key - check if it's accessible (basic check)
if _, err := os.Stat(config.PrivateKey); err != nil {
fmt.Printf("⚠️ WARNING: Private key file may not be accessible: %s\n", config.PrivateKey)
fmt.Println(" This will be verified during signing")
}
}
return nil
}
// isEncryptedPEMKey checks if a PEM key is encrypted
func isEncryptedPEMKey(pemContent string) bool {
return strings.Contains(pemContent, "ENCRYPTED")
}
// isValidPEMKey performs basic PEM format validation
func isValidPEMKey(pemContent string) bool {
return strings.Contains(pemContent, "-----BEGIN") &&
strings.Contains(pemContent, "-----END") &&
(strings.Contains(pemContent, "PRIVATE KEY") ||
strings.Contains(pemContent, "RSA PRIVATE KEY") ||
strings.Contains(pemContent, "EC PRIVATE KEY"))
}
// createCosignCommand creates a cosign sign command with the given image reference
func createCosignCommand(imageRef string, cosign CosignConfig) *exec.Cmd {
args := []string{"sign", "--yes"}
// Handle private key (content vs file path)
if strings.HasPrefix(cosign.PrivateKey, "-----BEGIN") {
args = append(args, "--key", "env://COSIGN_PRIVATE_KEY")
os.Setenv("COSIGN_PRIVATE_KEY", cosign.PrivateKey)
} else {
args = append(args, "--key", cosign.PrivateKey)
}
// Set password if provided
if cosign.Password != "" {
os.Setenv("COSIGN_PASSWORD", cosign.Password)
}
// Add any extra parameters
if cosign.Params != "" {
extraArgs := strings.Fields(cosign.Params)
args = append(args, extraArgs...)
}
// Add the image reference to sign
args = append(args, imageRef)
return exec.Command(cosignExe, args...)
}
// executeCosignCommand executes the given cosign command and handles errors
func executeCosignCommand(cmd *exec.Cmd) {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("🚀 Executing: %s %s\n", cmd.Path, strings.Join(cmd.Args[1:], " "))
if err := cmd.Run(); err != nil {
fmt.Printf("⚠️ WARNING: Image signing failed: %s\n", err)
fmt.Printf(" Image was pushed successfully but could not be signed\n")
fmt.Printf(" This is not fatal - continuing with the build\n")
}
}
// pushOnly handles pushing images without building them
func (p Plugin) pushOnly() error {
// Check if source image is specified
sourceImageName := p.SourceImage
var sourceTags []string
if sourceImageName == "" {
// If no source image specified, use the repo and first tag
fmt.Println("source_image not provided, using repo and tag value")
sourceImageName = p.Build.Repo
sourceTags = p.Build.Tags
} else {
// If source image is specified, check if it has a tag
lastColonIndex := strings.LastIndex(sourceImageName, ":")
if lastColonIndex > 0 && lastColonIndex < len(sourceImageName) {
// Check if there's a slash after the last colon (indicating it's a port, not a tag)
// For example: registry:5000/image (has slash after colon - port not tag)
// vs image:tag (no slash after colon - it's a tag)
if strings.LastIndex(sourceImageName, "/") > lastColonIndex {
// The last colon is part of the registry:port, not a tag separator
sourceTags = []string{"latest"}
} else {
// The last colon separates the tag
tag := sourceImageName[lastColonIndex+1:]
sourceImageName = sourceImageName[:lastColonIndex]
if tag == "" {
fmt.Printf("No tag specified in source image (or empty tag). Using 'latest' as the default tag.\n")
tag = "latest"
}
sourceTags = []string{tag}
}
} else {
// Default to "latest" if no tag specified
sourceTags = []string{"latest"}
}
fmt.Printf("Using source image: %s with tag(s): %s\n", sourceImageName, strings.Join(sourceTags, ", "))
}
// For each source tag and target tag combination
var digest string
var firstPushedImage string
for _, sourceTag := range sourceTags {
sourceFullImageName := fmt.Sprintf("%s:%s", sourceImageName, sourceTag)
// Check if the source image exists in local daemon
if !imageExists(sourceFullImageName) {
fmt.Printf("Warning: Source image %s not found\n", sourceFullImageName)
// Continue to the next source tag if available, otherwise return error
if len(sourceTags) > 1 {
continue
}
return fmt.Errorf("source image %s not found, cannot push", sourceFullImageName)
}
// For each target tag, tag and push
for _, targetTag := range p.Build.Tags {
targetFullImageName := fmt.Sprintf("%s:%s", p.Build.Repo, targetTag)
// Skip if source and target are identical
if sourceFullImageName == targetFullImageName {
fmt.Printf("Source and target image names are identical: %s\n", sourceFullImageName)
} else {
// Tag the source image with the target name
fmt.Printf("Tagging %s as %s\n", sourceFullImageName, targetFullImageName)
tagCmd := exec.Command(dockerExe, "tag", sourceFullImageName, targetFullImageName)
tagCmd.Stdout = os.Stdout
tagCmd.Stderr = os.Stderr
trace(tagCmd)
if err := tagCmd.Run(); err != nil {
return fmt.Errorf("failed to tag image %s as %s: %w", sourceFullImageName, targetFullImageName, err)
}
}
}
}
// Push all target images
for _, tag := range p.Build.Tags {
fullImageName := fmt.Sprintf("%s:%s", p.Build.Repo, tag)
// Check if image exists in local daemon
if !imageExists(fullImageName) {
return fmt.Errorf("image %s not found, cannot push", fullImageName)
}
// Push image
fmt.Println("Pushing image:", fullImageName)
pushCmd := commandPush(p.Build, tag)
pushCmd.Stdout = os.Stdout
pushCmd.Stderr = os.Stderr
trace(pushCmd)
if err := pushCmd.Run(); err != nil {
return fmt.Errorf("failed to push image %s: %w", fullImageName, err)
}
// Track the first pushed image for card generation
if firstPushedImage == "" {
firstPushedImage = fullImageName
}
// Get the digest after push (we only need one)
if digest == "" {
d, err := getDigestAfterPush(fullImageName)
if err == nil {
digest = d
} else {
fmt.Printf("Warning: Could not get digest for %s: %v\n", fullImageName, err)
}
}
}
// Output the adaptive card
if firstPushedImage != "" {
if err := p.writeCardForImage(firstPushedImage); err != nil {
fmt.Printf("Could not create adaptive card. %s\n", err)
}
}
// Write to artifact file
if p.ArtifactFile != "" && digest != "" {
if err := drone.WritePluginArtifactFile(
p.Daemon.RegistryType,
p.ArtifactFile,
p.Daemon.Registry,
p.Build.Repo,
digest,
p.Build.Tags,
); err != nil {
fmt.Printf("Failed to write plugin artifact file at path: %s with error: %s\n",
p.ArtifactFile, err)
}
}
// Handle cosign signing after push
if p.shouldSignWithCosign() {
// Set up environment variables for cosign
os.Setenv("COSIGN_YES", "true")
if digest != "" {
fmt.Printf("🔐 Found image digest: %s\n", digest)
// Sign with digest reference
imageRef := fmt.Sprintf("%s@%s", p.Build.Repo, digest)
cosignCmd := createCosignCommand(imageRef, p.Cosign)
executeCosignCommand(cosignCmd)
} else {
fmt.Printf("⚠️ WARNING: Could not get image digest for cosign signing\n")
fmt.Printf(" Falling back to tag-based signing\n")
// Fall back to tag-based signing for each tag
for _, tag := range p.Build.Tags {
imageRef := fmt.Sprintf("%s:%s", p.Build.Repo, tag)
cosignCmd := createCosignCommand(imageRef, p.Cosign)
executeCosignCommand(cosignCmd)
}
}
}
return nil
}
+4
View File
@@ -2,5 +2,9 @@ FROM docker:28.1.1-dind
ENV DOCKER_HOST=unix:///var/run/docker.sock ENV DOCKER_HOST=unix:///var/run/docker.sock
# Install cosign for container image signing
RUN wget -O /usr/local/bin/cosign https://github.com/sigstore/cosign/releases/download/v2.5.3/cosign-linux-amd64 \
&& chmod +x /usr/local/bin/cosign
ADD release/linux/amd64/drone-docker /bin/ ADD release/linux/amd64/drone-docker /bin/
ENTRYPOINT ["/usr/local/bin/dockerd-entrypoint.sh", "/bin/drone-docker"] ENTRYPOINT ["/usr/local/bin/dockerd-entrypoint.sh", "/bin/drone-docker"]
+4
View File
@@ -2,5 +2,9 @@ FROM arm64v8/docker:28.1.1-dind
ENV DOCKER_HOST=unix:///var/run/docker.sock ENV DOCKER_HOST=unix:///var/run/docker.sock
# Install cosign for container image signing
RUN wget -O /usr/local/bin/cosign https://github.com/sigstore/cosign/releases/download/v2.5.3/cosign-linux-arm64 \
&& chmod +x /usr/local/bin/cosign
ADD release/linux/arm64/drone-docker /bin/ ADD release/linux/arm64/drone-docker /bin/
ENTRYPOINT ["/usr/local/bin/dockerd-entrypoint.sh", "/bin/drone-docker"] ENTRYPOINT ["/usr/local/bin/dockerd-entrypoint.sh", "/bin/drone-docker"]
@@ -24,6 +24,10 @@ LABEL maintainer="Drone.IO Community <drone-dev@googlegroups.com>" `
org.label-schema.schema-version="1.0" org.label-schema.schema-version="1.0"
RUN mkdir C:\bin RUN mkdir C:\bin
# Install cosign for container image signing
ADD https://github.com/sigstore/cosign/releases/download/v2.5.3/cosign-windows-amd64.exe C:/bin/cosign.exe
COPY --from=download /windows/system32/netapi32.dll /windows/system32/netapi32.dll COPY --from=download /windows/system32/netapi32.dll /windows/system32/netapi32.dll
COPY --from=download /app/docker.exe C:/bin/docker.exe COPY --from=download /app/docker.exe C:/bin/docker.exe
ADD release/windows/amd64/drone-docker.exe C:/bin/drone-docker.exe ADD release/windows/amd64/drone-docker.exe C:/bin/drone-docker.exe
@@ -22,6 +22,10 @@ LABEL maintainer="Drone.IO Community <drone-dev@googlegroups.com>" `
org.label-schema.schema-version="1.0" org.label-schema.schema-version="1.0"
RUN mkdir C:\bin RUN mkdir C:\bin
# Install cosign for container image signing
ADD https://github.com/sigstore/cosign/releases/download/v2.5.3/cosign-windows-amd64.exe C:/bin/cosign.exe
COPY --from=download /windows/system32/netapi32.dll /windows/system32/netapi32.dll COPY --from=download /windows/system32/netapi32.dll /windows/system32/netapi32.dll
COPY --from=download /app/docker.exe C:/bin/docker.exe COPY --from=download /app/docker.exe C:/bin/docker.exe
ADD release/windows/amd64/drone-docker.exe C:/bin/drone-docker.exe ADD release/windows/amd64/drone-docker.exe C:/bin/drone-docker.exe
+88
View File
@@ -1,6 +1,7 @@
package docker package docker
import ( import (
"os"
"os/exec" "os/exec"
"reflect" "reflect"
"strings" "strings"
@@ -179,3 +180,90 @@ func TestCommandBuild(t *testing.T) {
}) })
} }
} }
func TestGetProxyValue(t *testing.T) {
tests := []struct {
name string
key string
envVars map[string]string
expected string
}{
{
name: "lowercase env var set",
key: "http_proxy",
envVars: map[string]string{"http_proxy": "http://proxy:8080"},
expected: "http://proxy:8080",
},
{
name: "uppercase env var set",
key: "http_proxy",
envVars: map[string]string{"HTTP_PROXY": "http://proxy:8080"},
expected: "http://proxy:8080",
},
{
name: "HARNESS prefixed env var set",
key: "http_proxy",
envVars: map[string]string{"HARNESS_HTTP_PROXY": "http://harness-proxy:8080"},
expected: "http://harness-proxy:8080",
},
{
name: "standard takes precedence over HARNESS",
key: "http_proxy",
envVars: map[string]string{
"HTTP_PROXY": "http://standard:8080",
"HARNESS_HTTP_PROXY": "http://harness:8080",
},
expected: "http://standard:8080",
},
{
name: "lowercase takes precedence over uppercase",
key: "no_proxy",
envVars: map[string]string{
"no_proxy": "localhost,127.0.0.1",
"NO_PROXY": "*.example.com",
"HARNESS_NO_PROXY": "*.local",
},
expected: "localhost,127.0.0.1",
},
{
name: "lowercase takes precedence over HARNESS",
key: "https_proxy",
envVars: map[string]string{
"https_proxy": "https://standard:8080",
"HARNESS_HTTPS_PROXY": "https://harness:8080",
},
expected: "https://standard:8080",
},
{
name: "no env var set",
key: "http_proxy",
envVars: map[string]string{},
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clean env
lowercaseKey := tt.key
uppercaseKey := strings.ToUpper(tt.key)
harnessKey := "HARNESS_" + strings.ToUpper(tt.key)
os.Unsetenv(lowercaseKey)
os.Unsetenv(uppercaseKey)
os.Unsetenv(harnessKey)
// Set test environment variables
for k, v := range tt.envVars {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
// Execute and verify
result := getProxyValue(tt.key)
if result != tt.expected {
t.Errorf("getProxyValue(%q) = %q, want %q", tt.key, result, tt.expected)
}
})
}
}
+41 -23
View File
@@ -3,53 +3,71 @@ module github.com/drone-plugins/drone-docker
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
github.com/aws/aws-sdk-go v1.26.7 github.com/aws/aws-sdk-go-v2 v1.41.2
github.com/aws/aws-sdk-go-v2/config v1.32.10
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7
github.com/coreos/go-semver v0.3.0 github.com/coreos/go-semver v0.3.0
github.com/dchest/uniuri v1.2.0 github.com/dchest/uniuri v1.2.0
github.com/drone-plugins/drone-plugin-lib v0.4.1 github.com/drone-plugins/drone-plugin-lib v0.4.1
github.com/drone/drone-go v1.7.1 github.com/drone/drone-go v1.7.1
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743
github.com/joho/godotenv v1.3.0 github.com/joho/godotenv v1.3.0
github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.11.1
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.10.0
github.com/urfave/cli v1.22.2 github.com/urfave/cli v1.22.2
golang.org/x/oauth2 v0.13.0 golang.org/x/oauth2 v0.34.0
google.golang.org/api v0.146.0 google.golang.org/api v0.187.0
) )
require ( require (
cloud.google.com/go/compute v1.23.1 // indirect cloud.google.com/go/auth v0.6.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
github.com/aws/smithy-go v1.24.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/googleapis/gax-go/v2 v2.12.5 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.36.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
golang.org/x/net v0.37.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
golang.org/x/sys v0.31.0 // indirect go.opentelemetry.io/otel v1.39.0 // indirect
golang.org/x/text v0.23.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect
google.golang.org/appengine v1.6.8 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect golang.org/x/crypto v0.46.0 // indirect
google.golang.org/grpc v1.59.0 // indirect golang.org/x/net v0.48.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
go 1.23.0 go 1.25.7
toolchain go1.23.7
+94 -75
View File
@@ -1,8 +1,10 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38=
cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
@@ -17,8 +19,36 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aws/aws-sdk-go v1.26.7 h1:ObjEnmzvSdYy8KVd3me7v/UMyCn81inLy2SyoIPoBkg= github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
github.com/aws/aws-sdk-go v1.26.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3 h1:RtGctYMmkTerGClvdY6bHXdtly4FeYw9wz/NPz62LF8=
github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3/go.mod h1:vBfBu24Ka3/5UZtepbTV0gnc9VPLT8ok+0oDDaYAzn4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 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/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -44,6 +74,13 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 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/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -60,32 +97,27 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 h1:X3Xxno5Ji8idrNiUoFc7QyXpqhSYlDRYQmc7mlpMBzU= github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 h1:X3Xxno5Ji8idrNiUoFc7QyXpqhSYlDRYQmc7mlpMBzU=
github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743/go.mod h1:KrtyD5PFj++GKkFS/7/RRrfnRhAMGQwy75GLCHWrCNs= github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743/go.mod h1:KrtyD5PFj++GKkFS/7/RRrfnRhAMGQwy75GLCHWrCNs=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
@@ -98,116 +130,105 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 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/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.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.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.146.0 h1:9aBYT4vQXt9dhCuLNfwfd3zpwu8atg0yPkjBymwSrOM= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
google.golang.org/api v0.146.0/go.mod h1:OARJqIfoYjXJj4C1AiBSXYZt03qsoz8FQYU6fBEfrHM= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo=
google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8= google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls=
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -217,10 +238,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+75
View File
@@ -0,0 +1,75 @@
package azure
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
const DefaultResource = "https://management.azure.com/"
const defaultAuthorityHost = "https://login.microsoftonline.com"
const defaultHTTPTimeout = 30 * time.Second
// GetAADAccessTokenViaClientAssertion exchanges an external OIDC ID token for an Azure AD access token
func GetAADAccessTokenViaClientAssertion(ctx context.Context, tenantID, clientID, oidcToken, authorityHost string) (string, error) {
resource := DefaultResource
form := url.Values{
"client_id": {clientID},
"scope": {resource + ".default"},
"grant_type": {"client_credentials"},
"client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
"client_assertion": {oidcToken},
}
base := authorityHost
if strings.TrimSpace(base) == "" {
base = defaultAuthorityHost
}
base = strings.TrimRight(base, "/")
endpoint := fmt.Sprintf("%s/%s/oauth2/v2.0/token", base, tenantID)
client := &http.Client{Timeout: defaultHTTPTimeout}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(form.Encode()))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
var aadErr struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
limited := io.LimitedReader{R: resp.Body, N: 4096}
_ = json.NewDecoder(&limited).Decode(&aadErr)
if aadErr.Error != "" {
return "", fmt.Errorf("AAD token request failed: status=%d, error=%s", resp.StatusCode, aadErr.Error)
}
return "", fmt.Errorf("AAD token request failed: status=%d", resp.StatusCode)
}
var payload struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
return "", err
}
if payload.AccessToken == "" {
return "", fmt.Errorf("AAD token response missing access_token")
}
return payload.AccessToken, nil
}
+104
View File
@@ -0,0 +1,104 @@
package azure
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestGetAADAccessTokenViaClientAssertion_Success(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Fatalf("expected POST, got %s", r.Method)
}
if ct := r.Header.Get("Content-Type"); !strings.Contains(ct, "application/x-www-form-urlencoded") {
t.Fatalf("expected form content-type, got %s", ct)
}
if err := r.ParseForm(); err != nil {
t.Fatalf("failed parsing form: %v", err)
}
assertEq(t, r.Form.Get("client_id"), "client")
assertEq(t, r.Form.Get("grant_type"), "client_credentials")
assertEq(t, r.Form.Get("client_assertion_type"), "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
assertEq(t, r.Form.Get("client_assertion"), "idtoken")
assertEq(t, r.Form.Get("scope"), DefaultResource+".default")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"access_token":"AT","token_type":"Bearer","expires_in":3600}`))
}))
defer ts.Close()
tok, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tok != "AT" {
t.Fatalf("expected access token AT, got %q", tok)
}
}
func TestGetAADAccessTokenViaClientAssertion_400WithErrorField(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(`{"error":"invalid_client","error_description":"bad"}`))
}))
defer ts.Close()
_, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err == nil || !strings.Contains(err.Error(), "status=400") || !strings.Contains(err.Error(), "invalid_client") {
t.Fatalf("expected 400 with invalid_client error, got %v", err)
}
}
func TestGetAADAccessTokenViaClientAssertion_400WithoutErrorField(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("{}"))
}))
defer ts.Close()
_, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err == nil || !strings.Contains(err.Error(), "status=400") {
t.Fatalf("expected 400 error, got %v", err)
}
}
func TestGetAADAccessTokenViaClientAssertion_MalformedJSON(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("not-json"))
}))
defer ts.Close()
_, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err == nil {
t.Fatalf("expected JSON decode error, got nil")
}
}
func TestGetAADAccessTokenViaClientAssertion_MissingAccessToken(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"token_type":"Bearer","expires_in":3600}`))
}))
defer ts.Close()
_, err := GetAADAccessTokenViaClientAssertion(context.Background(), "tenant", "client", "idtoken", ts.URL)
if err == nil || !strings.Contains(err.Error(), "missing access_token") {
t.Fatalf("expected missing access_token error, got %v", err)
}
}
func assertEq(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Fatalf("mismatch: got=%q want=%q", got, want)
}
}