mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-20 22:59:42 -05:00
fix: ECR authentication (#409)
* chore: Adding makefile targets for local building. * fix: Implement proper parsing of different ECR auth mechanisms. * chore: Fix typos and cleanup Makefil [skip ci]
This commit is contained in:
parent
100db97ffa
commit
d12e13fac9
5 changed files with 98 additions and 5 deletions
19
GNUmakefile
19
GNUmakefile
|
|
@ -2,11 +2,28 @@ TEST?=$$(go list ./... |grep -v 'vendor')
|
||||||
GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)
|
GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)
|
||||||
PKG_NAME=internal/provider
|
PKG_NAME=internal/provider
|
||||||
|
|
||||||
|
# Values to install the provider locally for testing purposes
|
||||||
|
HOSTNAME=registry.terraform.io
|
||||||
|
NAMESPACE=kreuzwerker
|
||||||
|
NAME=docker
|
||||||
|
BINARY=terraform-provider-${NAME}
|
||||||
|
VERSION=9.9.9
|
||||||
|
OS_ARCH=darwin_arm64
|
||||||
|
|
||||||
|
.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile website-link-check website-lint website-lint-fix
|
||||||
|
|
||||||
default: build
|
default: build
|
||||||
|
|
||||||
build: fmtcheck
|
build: fmtcheck
|
||||||
go install
|
go install
|
||||||
|
|
||||||
|
local-build:
|
||||||
|
go build -o ${BINARY}
|
||||||
|
|
||||||
|
local-install: local-build
|
||||||
|
mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH}
|
||||||
|
mv ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH}
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
go install github.com/katbyte/terrafmt
|
go install github.com/katbyte/terrafmt
|
||||||
go install github.com/client9/misspell/cmd/misspell
|
go install github.com/client9/misspell/cmd/misspell
|
||||||
|
|
@ -90,8 +107,6 @@ website-lint-fix:
|
||||||
@docker run --rm -v $(PWD):/markdown 06kellyjac/markdownlint-cli --fix docs/
|
@docker run --rm -v $(PWD):/markdown 06kellyjac/markdownlint-cli --fix docs/
|
||||||
@terrafmt fmt ./docs --pattern '*.md'
|
@terrafmt fmt ./docs --pattern '*.md'
|
||||||
|
|
||||||
.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile website-link-check website-lint website-lint-fix
|
|
||||||
|
|
||||||
chlog-%:
|
chlog-%:
|
||||||
@echo "Generating CHANGELOG.md"
|
@echo "Generating CHANGELOG.md"
|
||||||
git-chglog --next-tag $* -o CHANGELOG.md
|
git-chglog --next-tag $* -o CHANGELOG.md
|
||||||
|
|
|
||||||
50
internal/provider/authentication_helpers.go
Normal file
50
internal/provider/authentication_helpers.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
b64 "encoding/base64"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ECR HTTP authentication needs Bearer token in the Authorization header
|
||||||
|
// This token is an JWT which was b64 encoded again
|
||||||
|
// Depending on the aws cli command, the returned token is different
|
||||||
|
// "aws ecr get-login-password" is a simply JWT, which needs to be prefixed with "AWS:" and then b64 encoded
|
||||||
|
// "aws ecr get-authorization-token" is the best case, everything is encoded properly
|
||||||
|
// in case someone passes an base64 decoded token from "aws ecr get-authorization-token" we need to b64 encode it again
|
||||||
|
func normalizeECRPasswordForHTTPUsage(password string) string {
|
||||||
|
if strings.HasPrefix(password, "ey") {
|
||||||
|
return b64.StdEncoding.EncodeToString([]byte("AWS:" + password))
|
||||||
|
} else if strings.HasPrefix(password, "AWS:") {
|
||||||
|
return b64.StdEncoding.EncodeToString([]byte(password))
|
||||||
|
}
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
// Docker operations need a JWT, so this function basically does the opposite as `normalizeECRPasswordForHTTPUsage`
|
||||||
|
// aws ecr get-authorization-token does not return a JWT, but a base64 encoded string which we need to decode
|
||||||
|
func normalizeECRPasswordForDockerCLIUsage(password string) string {
|
||||||
|
if strings.HasPrefix(password, "ey") {
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(password, "AWS:") {
|
||||||
|
decodedPassword, err := b64.StdEncoding.DecodeString(password)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error creating registry request: %s", err)
|
||||||
|
}
|
||||||
|
password = string(decodedPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
return password[4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func isECRRepositoryURL(url string) bool {
|
||||||
|
if url == "public.ecr.aws" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Regexp is based on the ecr urls shown in https://docs.aws.amazon.com/AmazonECR/latest/userguide/registry_auth.html
|
||||||
|
var ecrRexp = regexp.MustCompile(`^.*?dkr\.ecr\..*?\.amazonaws\.com$`)
|
||||||
|
return ecrRexp.MatchString(url)
|
||||||
|
}
|
||||||
19
internal/provider/authentication_helpers_test.go
Normal file
19
internal/provider/authentication_helpers_test.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsECRRepositoryURL(t *testing.T) {
|
||||||
|
|
||||||
|
if !isECRRepositoryURL("2385929435838.dkr.ecr.eu-central-1.amazonaws.com") {
|
||||||
|
t.Fatalf("Expected true")
|
||||||
|
}
|
||||||
|
if !isECRRepositoryURL("39e39fmgvkgd.dkr.ecr.us-east-1.amazonaws.com") {
|
||||||
|
t.Fatalf("Expected true")
|
||||||
|
}
|
||||||
|
if isECRRepositoryURL("39e39fmgvkgd.dkr.ecrus-east-1.amazonaws.com") {
|
||||||
|
t.Fatalf("Expected false")
|
||||||
|
}
|
||||||
|
if !isECRRepositoryURL("public.ecr.aws") {
|
||||||
|
t.Fatalf("Expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -102,12 +102,17 @@ func getImageDigest(registry, image, tag, username, password string, insecureSki
|
||||||
}
|
}
|
||||||
|
|
||||||
if username != "" {
|
if username != "" {
|
||||||
if registry != "ghcr.io" {
|
if registry != "ghcr.io" && !isECRRepositoryURL(registry) && registry != "gcr.io" {
|
||||||
req.SetBasicAuth(username, password)
|
req.SetBasicAuth(username, password)
|
||||||
|
} else {
|
||||||
|
if isECRRepositoryURL(registry) {
|
||||||
|
password = normalizeECRPasswordForHTTPUsage(password)
|
||||||
|
req.Header.Add("Authorization", "Basic "+password)
|
||||||
} else {
|
} else {
|
||||||
req.Header.Add("Authorization", "Bearer "+b64.StdEncoding.EncodeToString([]byte(password)))
|
req.Header.Add("Authorization", "Bearer "+b64.StdEncoding.EncodeToString([]byte(password)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We accept schema v2 manifests and manifest lists, and also OCI types
|
// We accept schema v2 manifests and manifest lists, and also OCI types
|
||||||
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
|
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
|
||||||
|
|
|
||||||
|
|
@ -223,8 +223,12 @@ func providerSetToRegistryAuth(authList []interface{}) (*AuthConfigs, error) {
|
||||||
// username/password or the given config file
|
// username/password or the given config file
|
||||||
if username, ok := auth["username"]; ok && username.(string) != "" {
|
if username, ok := auth["username"]; ok && username.(string) != "" {
|
||||||
log.Println("[DEBUG] Using username for registry auths:", username)
|
log.Println("[DEBUG] Using username for registry auths:", username)
|
||||||
|
password := auth["password"].(string)
|
||||||
|
if isECRRepositoryURL(registryHostname) {
|
||||||
|
password = normalizeECRPasswordForDockerCLIUsage(password)
|
||||||
|
}
|
||||||
authConfig.Username = auth["username"].(string)
|
authConfig.Username = auth["username"].(string)
|
||||||
authConfig.Password = auth["password"].(string)
|
authConfig.Password = password
|
||||||
|
|
||||||
// Note: check for config_file_content first because config_file has a default which would be used
|
// Note: check for config_file_content first because config_file has a default which would be used
|
||||||
// nevertheless config_file_content is set or not. The default has to be kept to check for the
|
// nevertheless config_file_content is set or not. The default has to be kept to check for the
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue