Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

[WIP] Add the ability to support authentication to AWS ECR #1455

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ godeps=$(shell go list -f '{{join .Deps "\n"}}' $1 | grep -v /vendor/ | xargs go

FLUXD_DEPS:=$(call godeps,./cmd/fluxd)
FLUXCTL_DEPS:=$(call godeps,./cmd/fluxctl)
ECR_SIDECAR_DEPS:=$(call godeps,./sidecar/aws/cmd)
HELM_OPERATOR_DEPS:=$(call godeps,./cmd/helm-operator)

IMAGE_TAG:=$(shell ./docker/image-tag)
VCS_REF:=$(shell git rev-parse HEAD)
BUILD_DATE:=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')

all: $(GOPATH)/bin/fluxctl $(GOPATH)/bin/fluxd $(GOPATH)/bin/helm-operator build/.flux.done build/.helm-operator.done
all: $(GOPATH)/bin/fluxctl $(GOPATH)/bin/fluxd $(GOPATH)/bin/helm-operator $(GOPATH)/bin/ecr-sidecar build/.flux.done build/.ecr-sidecar.done build/.helm-operator.done

release-bins:
for arch in amd64; do \
Expand Down Expand Up @@ -48,13 +49,18 @@ build/.%.done: docker/Dockerfile.%
-f build/docker/$*/Dockerfile.$* ./build/docker/$*
touch $@

build/.flux.done: build/fluxd build/kubectl docker/ssh_config docker/kubeconfig docker/verify_known_hosts.sh
build/.flux.done: build/fluxd build/ecr-sidecar build/kubectl docker/ssh_config docker/kubeconfig docker/verify_known_hosts.sh
build/.ecr-sidecar.done: build/ecr-sidecar
build/.helm-operator.done: build/helm-operator build/kubectl docker/ssh_config docker/verify_known_hosts.sh

build/fluxd: $(FLUXD_DEPS)
build/fluxd: cmd/fluxd/*.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $@ $(LDFLAGS) -ldflags "-X main.version=$(shell ./docker/image-tag)" ./cmd/fluxd

build/ecr-sidecar: $(ECR_SIDECAR_DEPS)
build/ecr-sidecar: sidecar/aws/cmd/*.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $@ $(LDFLAGS) -ldflags "-X main.version=$(shell ./docker/image-tag)" ./sidecar/aws/cmd

build/helm-operator: $(HELM_OPERATOR_DEPS)
build/helm-operator: cmd/helm-operator/*.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $@ $(LDFLAGS) -ldflags "-X main.version=$(shell ./docker/image-tag)" ./cmd/helm-operator
Expand All @@ -75,6 +81,10 @@ $(GOPATH)/bin/fluxd: $(FLUXD_DEPS)
$(GOPATH)/bin/fluxd: cmd/fluxd/*.go
go install ./cmd/fluxd

$(GOPATH)/bin/ecr-sidecar: $(FLUXD_DEPS)
$(GOPATH)/bin/ecr-sidecar: sidecar/aws/cmd/*.go
go install ./sidecar/aws/cmd

$(GOPATH)/bin/helm-operator: $(HELM_OPERATOR_DEPS)
$(GOPATH)/bin/help-operator: cmd/helm-operator/*.go
go install ./cmd/helm-operator
Expand Down
2 changes: 1 addition & 1 deletion chart/flux/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v1
appVersion: "1.7.1"
description: Flux is a tool that automatically ensures that the state of a cluster matches what is specified in version control
name: flux
version: 0.3.4
version: 0.3.5
home: https://weave.works
sources:
- https://github.com/weaveworks/flux
Expand Down
9 changes: 9 additions & 0 deletions chart/flux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ The following tables lists the configurable parameters of the Weave Flux chart a
| `helmOperator.tls.caContent` | Certificate Authority content used to validate the Tiller server certificate | None
| `token` | Weave Cloud service token | None
| `extraEnvs` | Extra environment variables for the Flux pod | `[]`
| `podAnnotations` | Extra pod annotations for the Flux pod | `[]`
| `sidecar.aws.enabled` | Deploy AWS ECR repository authenticator as a sidecar to flux |`false`
| `sidecar.aws.registryIds` | The AWS Account IDs of ECR repositories that needs authentication. This is a comma separated value. E.g `1234567890,0987654321` | None
| `sidecar.aws.region` | The AWS Region of the ECR repositories that needs authentication | `us-east-1`
| `sidecar.aws.fetchInterval` | The period at which to fetch for a new token from the AWS Account IDs | `1h`
| `sidecar.aws.image.repository` | ECR sidecar image repository | `quay.io/weaveworks/ecr-sidecar`
| `sidecar.aws.image.tag` | ECR sidecar image tag | `latest`
| `sidecar.aws.image.pullPolicy` | ECR sidecar image pull policy | `IfNotPresent`


Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example:

Expand Down
17 changes: 17 additions & 0 deletions chart/flux/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ spec:
labels:
app: {{ template "flux.name" . }}
release: {{ .Release.Name }}
{{- if .Values.podAnnotations }}
annotations:
{{ toYaml .Values.podAnnotations | indent 8 }}
{{- end }}
spec:
{{- if .Values.serviceAccount.create }}
serviceAccountName: {{ template "flux.serviceAccountName" . }}
Expand All @@ -42,6 +46,19 @@ spec:
emptyDir:
medium: Memory
containers:
{{- if .Values.sidecar.aws.enabled }}
- name: aws
image: "{{ .Values.sidecar.aws.image.repository }}:{{ .Values.sidecar.aws.image.tag }}"
imagePullPolicy: {{ .Values.sidecar.aws.image.pullPolicy }}
args:
- --registry-ids={{ .Values.sidecar.aws.registryIds }}
- --region={{ .Values.sidecar.aws.region }}
- --fetch-interval={{ .Values.sidecar.aws.fetchInterval }}
ports:
- name: http
containerPort: 3031
protocol: TCP
{{- end }}
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
Expand Down
15 changes: 14 additions & 1 deletion chart/flux/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ token: ""

replicaCount: 1

podAnnotations: {}

image:
repository: quay.io/weaveworks/flux
tag: 1.7.1
Expand Down Expand Up @@ -81,6 +83,17 @@ tolerations: []

affinity: {}

sidecar:
aws:
enabled: false
image:
repository: quay.io/weaveworks/ecr-sidecar
tag: latest
pullPolicy: IfNotPresent
registryIds: ""
region: us-east-1
fetchInterval: 1h

git:
# URL of git repo with Kubernetes manifests; e.g. git.url=ssh://[email protected]/weaveworks/flux-example
url: ""
Expand Down Expand Up @@ -154,4 +167,4 @@ kube:
extraEnvs: []
# extraEnvs:
# - name: FOO
# value: bar
# value: bar
16 changes: 16 additions & 0 deletions docker/Dockerfile.ecr-sidecar
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM alpine:3.6

RUN apk add --no-cache ca-certificates tini

COPY ./ecr-sidecar /usr/local/bin

ENTRYPOINT [ "/sbin/tini", "--", "ecr-sidecar" ]

ARG BUILD_DATE
ARG VCS_REF

# These will change for every build
LABEL org.opencontainers.image.revision="$VCS_REF" \
org.opencontainers.image.created="$BUILD_DATE" \
org.label-schema.vcs-ref="$VCS_REF" \
org.label-schema.build-date="$BUILD_DATE"
46 changes: 46 additions & 0 deletions registry/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package registry

import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strings"

"github.com/weaveworks/flux/sidecar/aws/ecr"
)

// GetECRRegistryCredential is used by the Flux daemon to communicate to the AWS sidecar url
// to GET the appropriate ECR repository's docker username and password. If the input
// host was not found from the sidecar's JSON response, an empty credential will be returned.
func GetECRRegistryCredential(host, sidecarURL string) (credential, error) {
res, err := http.Get(sidecarURL)
if err != nil {
return credential{}, nil
}
defer res.Body.Close()
cred := &ecr.DockerCredential{}
if err := json.NewDecoder(res.Body).Decode(&cred); err != nil {
return credential{}, nil
}
for auth, entry := range cred.Auths {
hostRegistryID := strings.Split(host, ".")[0]
authHost := strings.TrimPrefix(auth, "https://")
authRegistryID := strings.Split(authHost, ".")[0]
if hostRegistryID == authRegistryID {
decodedAuth, err := base64.StdEncoding.DecodeString(entry.Auth)
if err != nil {
return credential{}, err
}
authParts := strings.SplitN(string(decodedAuth), ":", 2)
return credential{
registry: host,
provenance: ecr.SidecarAWSURL,
username: authParts[0],
password: strings.TrimSpace(authParts[1]),
}, nil
}
}
return credential{},
fmt.Errorf("%s: unable to find auth for host %s", ecr.SidecarAWSURL, host)
}
71 changes: 71 additions & 0 deletions registry/aws_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package registry

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)

const (
credentialJSONResponse = `{
"auths": {
"https://12345.dkr.ecr-use-east-1.amazonaws.com": {
"auth": "QVdTOmVjcnBhc3N3b3JkCg=="
},
"https://602401143452.dkr.ecr.us-east-1.amazonaws.com": {
"auth": "QVdTOnh4eHh4Cg=="
}
}}`
awsUser = "AWS"
credentialHost1 = "12345.dkr.ecr-use-east-1.amazonaws.com"
credentialDecodedPassword1 = "ecrpassword"
credentialHost2 = "602401143452.dkr.ecr.us-east-1.amazonaws.com"
credentialDecodedPassword2 = "xxxxx"
)

func TestGetECRRegistryCredential(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(credentialJSONResponse))
}

ts := httptest.NewServer(http.HandlerFunc(handler))
defer ts.Close()

tt := []struct {
pass bool
expectedUser string
expectedPassword string
registryHost string
}{
{true, awsUser, credentialDecodedPassword1, credentialHost1},
{true, awsUser, credentialDecodedPassword2, credentialHost2},
{false, awsUser, "qweqwewq", "unknownprofile.dkr.ecr.us-east-1.amazonaws.com"},
}

for _, tc := range tt {
if tc.pass {
t.Run("known registry host must pass", func(t *testing.T) {
creds, err := GetECRRegistryCredential(tc.registryHost, ts.URL)
if err != nil {
t.Fatal(err)
}
if creds.username != tc.expectedUser {
t.Fatalf("incorrect decoded username: got %s, want %s!", creds.username, tc.expectedUser)
}
if creds.password != tc.expectedPassword {
t.Fatalf("incorrect decoded password. got %s, want %s!", creds.password, tc.expectedPassword)
}
fmt.Println("--- INFO: got ", creds)
})
} else {
t.Run("unknown registry host must fail", func(t *testing.T) {
_, err := GetECRRegistryCredential(tc.registryHost, ts.URL)
if err == nil {
t.Fatalf("expected to fail test when fetching a non existent host %s but it went ok!", tc.registryHost)
}
})
}
}
}
2 changes: 1 addition & 1 deletion registry/client_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (f *RemoteClientFactory) Succeed(repo image.CanonicalName) {
// store adapts a set of pre-selected creds to be an
// auth.CredentialsStore
type store struct {
auth creds
auth credential
}

func (s *store) Basic(url *url.URL) (string, string) {
Expand Down
Loading