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:
Martin 2022-07-15 12:25:36 +02:00 committed by GitHub
parent 100db97ffa
commit d12e13fac9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 5 deletions

View file

@ -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

View 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)
}

View 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")
}
}

View file

@ -102,10 +102,15 @@ 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 {
req.Header.Add("Authorization", "Bearer "+b64.StdEncoding.EncodeToString([]byte(password)))
if isECRRepositoryURL(registry) {
password = normalizeECRPasswordForHTTPUsage(password)
req.Header.Add("Authorization", "Basic "+password)
} else {
req.Header.Add("Authorization", "Bearer "+b64.StdEncoding.EncodeToString([]byte(password)))
}
}
}

View file

@ -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