Skip to content
Merged
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
53 changes: 53 additions & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Build & Test

on:
pull_request:
branches:
- '**' # Triggers on all branches

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up QEMU
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-

- name: Build Docker image
uses: docker/build-push-action@v4
with:
context: .
push: false
tags: user/repo:latest
platforms: linux/amd64,linux/arm64

test:
runs-on: ubuntu-latest
needs: build

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '^1.22'

- name: Run Go tests
run: |
go test ./...
54 changes: 54 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Build and Push Docker Image

on:
release:
types: [published] # Triggers on release tagging

jobs:
build:
runs-on: ubuntu-latest
# This job runs only if the tag_name starts with 'v', this avoids conflicts with helm
# chart releases which have ecr-anywhere-helm-chart-{{ .Version }} as the release name
if: startsWith(github.event.release.tag_name, 'v')

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up QEMU
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-

- name: Convert repository name to lowercase
id: lowercase_repo
run: echo "::set-output name=lower_repo::$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')"

- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
ghcr.io/${{ steps.lowercase_repo.outputs.lower_repo }}:latest
ghcr.io/${{ steps.lowercase_repo.outputs.lower_repo }}:${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64

- name: Logout from GitHub Container Registry
run: docker logout ghcr.io
46 changes: 46 additions & 0 deletions .github/workflows/release_helm_chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Release Helm Chart

on:
# Triggers the workflow when Chart.yaml is updated on the main branch
push:
branches:
- main
paths:
- "charts/ecr-anywhere/Chart.yaml"

# Allows you to manually trigger the workflow from GitHub's UI
workflow_dispatch:

jobs:
release_helm_chart:
# Permissions required for the job. In this case, write access to the repository contents is needed.
permissions:
contents: write
# Specifies the type of runner that the job will run on. Here, it's the latest version of Ubuntu.
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

# Configures Git with the GitHub actor's name and email to make commits and tags
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"

# Runs the chart-releaser action which turns your GitHub project into a self-hosted Helm
# chart repo. It does this – during every push to main – by checking each chart in your
# project, and whenever there's a new chart version, creates a corresponding GitHub release
# named for the chart version, adds Helm chart artifacts to the release, and creates or
# updates an index.yaml file with metadata about those releases,
# which is then hosted on GitHub Pages
- name: Run chart-releaser
uses: helm/[email protected]
env:
# GitHub token used by the chart-releaser action
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
# Customizes the name of the chart release
CR_RELEASE_NAME_TEMPLATE: "ecr-anywhere-helm-chart-{{ .Version }}"
33 changes: 33 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Build the sidecar-injector binary
FROM golang:1.22 as builder

WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum

# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download

# Copy the go source
COPY cmd/ cmd/
COPY pkg/ pkg/

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${BUILDPLATFORM} go build -a -o ecr-anywhere-webhook ./cmd/webhook
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${BUILDPLATFORM} go build -a -o ecr-anywhere-refresher ./cmd/refresher

FROM alpine:latest


WORKDIR /

# install binaries
COPY --from=builder /workspace/ecr-anywhere-webhook .
COPY --from=builder /workspace/ecr-anywhere-refresher .

USER 65532:65532

# webhook is the default entrypoint
ENTRYPOINT ["/ecr-anywhere-webhook"]
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,39 @@
# ecr-anywhere
Pull from private ECR repos... anywhere
# ECR Anywhere

## Description
ECR Anywhere makes it easy to use container images hosted in private ECR repositories on any Kubernetes cluster, especially those hosted outside of AWS. It works via two components:

1) A Mutating Webhook that intercepts create/update verbs on labeled Kubernetes Secrets, injecting fresh ECR credentials which expire in 12 hours.
2) A CronJob that periodically checks the specially labeled Kubernetes Secrets to see if they need to be refreshed. If they do, an annotation is updated, synchronously triggering a credential refresh by the Mutating Webhook.

The benefits of this approach are the simplicity in implementation and operations (monitoring/alerting).

From an operational perspective:

1) A properly labeled secret can not be created or updated unless ecr-anywhere is working as expected. There's immediate feedback during operational setup/maintenance.
2) Any automation issues refreshing credentials are known immediately to operators with basic alerting on CronJob failures/pod failures.


## Quick Start

Setup your values.yaml for the helm chart. Specifically include the AWS credentials using the standard AWS SDK environment variables. The easiest way to issue long lived AWS credentials, the most secure way is to use [AWS OIDC](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html) with [Spiffe](https://spiffe.io/). The best reference for AWS SDK environment variables seems to be in the [AWS CLI documentation](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-envvars.html).

```yaml

pod:
container:
env:
- name: AWS_ACCESS_KEY_ID
value: "EXAMPLE"
- name: AWS_SECRET_ACCESS_KEY
value: "EXAMPLE"
- name: AWS_REGION
#important, this must match the region in the image name
value: "us-east-1"
```


```sh
helm install ecr-anywhere ./charts/ecr-anywhere -f values.yaml
```

23 changes: 23 additions & 0 deletions charts/ecr-anywhere/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
24 changes: 24 additions & 0 deletions charts/ecr-anywhere/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: v2
name: ecr-anywhere
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 1.0.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.0.0"
Empty file.
62 changes: 62 additions & 0 deletions charts/ecr-anywhere/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "ecr-anywhere.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "ecr-anywhere.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "ecr-anywhere.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "ecr-anywhere.labels" -}}
helm.sh/chart: {{ include "ecr-anywhere.chart" . }}
{{ include "ecr-anywhere.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "ecr-anywhere.selectorLabels" -}}
app.kubernetes.io/name: {{ include "ecr-anywhere.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "ecr-anywhere.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "ecr-anywhere.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
13 changes: 13 additions & 0 deletions charts/ecr-anywhere/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ .Values.name }}
labels:
app: {{ .Values.name }}
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "update"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
14 changes: 14 additions & 0 deletions charts/ecr-anywhere/templates/clusterrolebinding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ .Values.name }}
labels:
app: {{ .Values.name }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ .Values.name }}
subjects:
- kind: ServiceAccount
name: {{ .Values.name }}
namespace: {{ .Values.namespace }}
Loading