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)
|
||||
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
|
||||
|
||||
build: fmtcheck
|
||||
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:
|
||||
go install github.com/katbyte/terrafmt
|
||||
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/
|
||||
@terrafmt fmt ./docs --pattern '*.md'
|
||||
|
||||
.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile website-link-check website-lint website-lint-fix
|
||||
|
||||
chlog-%:
|
||||
@echo "Generating 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 registry != "ghcr.io" {
|
||||
if registry != "ghcr.io" && !isECRRepositoryURL(registry) && registry != "gcr.io" {
|
||||
req.SetBasicAuth(username, password)
|
||||
} else {
|
||||
if isECRRepositoryURL(registry) {
|
||||
password = normalizeECRPasswordForHTTPUsage(password)
|
||||
req.Header.Add("Authorization", "Basic "+password)
|
||||
} else {
|
||||
req.Header.Add("Authorization", "Bearer "+b64.StdEncoding.EncodeToString([]byte(password)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We accept schema v2 manifests and manifest lists, and also OCI types
|
||||
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
|
||||
if username, ok := auth["username"]; ok && username.(string) != "" {
|
||||
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.Password = auth["password"].(string)
|
||||
authConfig.Password = password
|
||||
|
||||
// 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
|
||||
|
|
|
|||
Loading…
Reference in a new issue