Skip to content
This repository has been archived by the owner on Oct 3, 2024. It is now read-only.

chore: refactor Go CronJob to Pepr OnSchedule capability #15

Closed
wants to merge 47 commits into from
Closed
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
78883ac
Refactor and resolve undefined errors
Dec 1, 2023
d4bd035
Initial structure for OnSchedule capability
Dec 1, 2023
d2ade1a
Merge branch 'main' into OnSchedule-refactor
Dec 1, 2023
4946a06
Merge branch 'main' into OnSchedule-refactor
Dec 5, 2023
4ea8aa8
Bump Pepr to v0.18.1
Dec 5, 2023
6bdf167
More cleanup to references to Go cronjob
Dec 5, 2023
ea72aca
Moar cleanup
Dec 5, 2023
d238968
Initial implementation refreshECRToken()
Dec 6, 2023
dbdaed5
Update error message
Dec 6, 2023
63a4340
Uncomment ECRHook capability
Dec 6, 2023
78afbd0
Fix lint error and rebuild module
Dec 6, 2023
0ef5f44
Update Pepr to v0.19.0
Dec 6, 2023
4a7e003
Remove language matrix for scan-codeql workflow
Dec 7, 2023
6aab959
Bump Pepr version to v0.19.0 in zarf config file
Dec 7, 2023
ecc8a75
Remove updateSecret() and improve error handling
Dec 8, 2023
bd21504
Fix update secret logic and cleanup
Dec 8, 2023
4d0eb0e
Fix CodeQL language spec in workflow
Dec 8, 2023
0c90619
Fix capitalization in docker secret data
Dec 8, 2023
1e69091
Set default schedule to run every 5 hours (mirrors EKS Anywhere)
Dec 8, 2023
d947bdb
Remove empty line
Dec 8, 2023
79d8cc6
Make error handling more consistent and less redundant
Dec 8, 2023
e031edd
Update capabilities/ecr-private.ts
lucasrod16 Dec 8, 2023
969d3f2
Update capabilities/ecr-public.ts
lucasrod16 Dec 8, 2023
9229874
Update capabilities/ecr-webhook/lib/ecr.ts
lucasrod16 Dec 8, 2023
1e7482d
Update capabilities/ecr-webhook/lib/utils.ts
lucasrod16 Dec 8, 2023
a37e285
Use blob URL with pinned commit sha for EKS Anywhere cronjob reference
Dec 8, 2023
7413693
Validate valid ECR URL in refreshECRToken() by calling isECRregistry()
Dec 8, 2023
94f7a11
Improve error handling when there are no repoNames returned from getR…
Dec 8, 2023
f0d2c6d
Handle the error case when a deployed component is not found in a zar…
Dec 8, 2023
7a05b5c
Remove k8s helper functions/shims
Dec 8, 2023
e16618a
Move Zarf constants to constants.ts file
Dec 8, 2023
fbff390
Patch Pepr watcher deployment image ref with image from Zarf managed …
Dec 8, 2023
254a060
Filter zarf image pull secrets by label
Dec 8, 2023
79e0975
Update Zarf version to v0.31.4
Dec 12, 2023
f81f9a9
Add support and a test case for parsing offical dockerhub images
Dec 12, 2023
f867c50
Validate ECR URL in createRepos()
Dec 12, 2023
5d61e89
Run make gen-schema
Dec 12, 2023
78dae89
Update capabilities/lib/constants.ts
lucasrod16 Dec 12, 2023
fe09917
Change zarfManagedByLabel constant to managedByLabel
Dec 12, 2023
020350d
Validate non-empty string value for authToken
Dec 12, 2023
0910130
Rebuilt module
Dec 12, 2023
4424308
Filter namespace by Zarf label rather than secret and remove 404 err …
Dec 12, 2023
faef6fc
Update capabilities/lib/zarf.ts
lucasrod16 Dec 13, 2023
b2e8320
Remove unnecessary try/catch block and get secret call
Dec 13, 2023
f9b9f16
Remove log message that will never be seen
Dec 18, 2023
7903a34
Ensure the zarf state secrets gets updated with new ECR token
Dec 18, 2023
86dce4c
Add suffix to debug log in workflow
Dec 18, 2023
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
4 changes: 0 additions & 4 deletions .github/codeql.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
paths-ignore:
- build/**

query-filters:
- exclude:
id: go/path-injection
27 changes: 1 addition & 26 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,8 @@ jobs:
- name: Install tools
uses: defenseunicorns/zarf/.github/actions/install-tools@main

- name: Setup Go
uses: defenseunicorns/zarf/.github/actions/golang@main

- name: Build ECR credential-helper binary
run: make build-credential-helper-linux-amd

- name: "ECR Credential Helper: Login to GHCR"
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ghcr.io
username: dummy
password: ${{ github.token }}

- name: "ECR Credential Helper: Build and Publish the Image"
run: docker buildx build --push --platform linux/amd64 --tag ghcr.io/defenseunicorns/zarf-init-aws/ecr-credential-helper:$GITHUB_REF_NAME .

# TODO@jeff-mccoy: Setup cosign signing key secrets in repo
# - name: "ECR Credential Helper: Sign the Image"
# run: cosign sign --key awskms:///${{ secrets.COSIGN_AWS_KMS_KEY }} -a release-engineer=https://github.com/${{ github.actor }} -a version=$GITHUB_REF_NAME ghcr.io/defenseunicorns/zarf-init-aws/ecr-credential-helper:$GITHUB_REF_NAME
# env:
# COSIGN_EXPERIMENTAL: 1
# AWS_REGION: ${{ secrets.COSIGN_AWS_REGION }}
# AWS_ACCESS_KEY_ID: ${{ secrets.COSIGN_AWS_KEY_ID }}
# AWS_SECRET_ACCESS_KEY: ${{ secrets.COSIGN_AWS_ACCESS_KEY }}

- name: Build AWS init package for release
run: make release-aws-init-package CREDENTIAL_HELPER_IMAGE_TAG=$GITHUB_REF_NAME
run: make release-aws-init-package

- name: Publish AWS Init Package as OCI and Skeleton
run: make publish-aws-init-package ARCH=amd64 REPOSITORY_URL=ghcr.io/defenseunicorns/packages
Expand Down
17 changes: 2 additions & 15 deletions .github/workflows/scan-codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,10 @@ jobs:
contents: read
security-events: write

strategy:
fail-fast: false
matrix:
language: ["go", "javascript", "typescript"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Setup Go
uses: defenseunicorns/zarf/.github/actions/golang@main

- name: Setup NodeJS
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
with:
Expand All @@ -54,13 +44,10 @@ jobs:
env:
CODEQL_EXTRACTOR_GO_BUILD_TRACING: on
with:
languages: ${{ matrix.language }}
languages: typescript
config-file: ./.github/codeql.yaml

- name: Build
run: make build-credential-helper-linux-amd

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
with:
category: "/language:${{matrix.language}}"
category: "/language:typescript"
2 changes: 0 additions & 2 deletions .github/workflows/scan-cves.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ on:
paths:
- "**/package.json"
- "**/package-lock.json"
- "go.mod"
- "go.sum"

jobs:
validate:
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/test-aws-init-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ jobs:
- name: Install Node dependencies
run: npm ci

- name: Setup Go
uses: defenseunicorns/zarf/.github/actions/golang@main

- name: Build ECR Pepr module
run: make build-module

Expand Down
4 changes: 0 additions & 4 deletions .grype.yaml

This file was deleted.

21 changes: 21 additions & 0 deletions .vscode/pepr.code-snippets
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"Create a new Pepr capability": {
"prefix": "create pepr capability",
"body": [
"import { Capability, a } from 'pepr';",
"",
"export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = new Capability({",
"\tname: '${TM_FILENAME_BASE}',",
"\tdescription: '${1:A brief description of this capability.}',",
"\tnamespaces: [${2:}],",
"});",
"",
"// Use the 'When' function to create a new action",
"const { When } = ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/};",
"",
"// When(a.<Kind>).Is<Event>().Then(change => change.<changes>",
"When(${3:})"
],
"description": "Creates a new Pepr capability with a specified description, optional namespaces, and adds a When statement for the specified value."
}
}
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"debug.javascript.terminalOptions": {
"enableTurboSourcemaps": true,
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"node_modules/pepr/**"
]
}
}
8 changes: 0 additions & 8 deletions Dockerfile

This file was deleted.

19 changes: 4 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# Provide a default value for the operating system architecture used in tests, e.g. " APPLIANCE_MODE=true|false make test-e2e ARCH=arm64"
ARCH ?= amd64
ZARF_VERSION ?= $$(zarf version)
CREDENTIAL_HELPER_BIN := ./build/zarf-ecr-credential-helper
CLUSTER_NAME ?= ""
INSTANCE_TYPE ?= t3.small
EKS_PACKAGE := ./build/zarf-package-distro-eks-multi-0.0.5.tar.zst
Expand Down Expand Up @@ -55,20 +54,12 @@ test-gen-schema:
$(MAKE) gen-schema
hack/gen-schema/check-gen-schema.sh

# Note: the path to the main.go file is not used due to https://github.com/golang/go/issues/51831#issuecomment-1074188363
build-credential-helper-linux-amd: ## Build the ECR credential helper binary for Linux on AMD64
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./build/zarf-ecr-credential-helper

build-local-credential-helper-image: ## Build the ECR credential helper image to be used in a locally built init package
@test -s $(CREDENTIAL_HELPER_BIN) || $(MAKE) build-credential-helper-linux-amd
docker buildx build --load --platform linux/$(ARCH) --tag ghcr.io/defenseunicorns/zarf-init-aws/ecr-credential-helper:local .

aws-init-package: ## Build the AWS Zarf init package
ZARF_CONFIG="zarf-config.example.yaml" zarf package create -o build -a $(ARCH) --confirm .

# INTERNAL: used to build a release version of the AWS init package with a specific credential-helper image
release-aws-init-package:
ZARF_CONFIG="zarf-config.example.yaml" zarf package create -o build -a $(ARCH) --set CREDENTIAL_HELPER_IMAGE_TAG=$(CREDENTIAL_HELPER_IMAGE_TAG) --confirm .
ZARF_CONFIG="zarf-config.example.yaml" zarf package create -o build -a $(ARCH) --confirm .

# INTERNAL: used to publish the AWS init package
publish-aws-init-package:
Expand Down Expand Up @@ -116,19 +107,18 @@ delete-iam: ## Delete AWS IAM policies and roles used in CI

update-zarf-config: ## Generate a new Zarf config file with registry type and IAM role ARN values to use for 'zarf init'
@cd iam || exit \
&& node ../hack/update-zarf-config/index.mjs "$(REGISTRY_TYPE)" "$$(PULUMI_CONFIG_PASSPHRASE="" pulumi stack output webhookRoleArn)" "$$(PULUMI_CONFIG_PASSPHRASE="" pulumi stack output credentialHelperRoleArn)"
&& node ../hack/update-zarf-config/index.mjs "$(REGISTRY_TYPE)" "$$(PULUMI_CONFIG_PASSPHRASE="" pulumi stack output roleArn)"

deploy-init-package-private: ## Run zarf init to deploy the AWS init package configured with private ECR registry
@cd build || exit \
&& ZARF_CONFIG="../zarf-config.yaml" zarf init \
--registry-url="$$(aws sts get-caller-identity --query 'Account' --output text).dkr.ecr.us-east-1.amazonaws.com" \
--registry-push-username="AWS" \
--registry-push-password="$$(aws ecr get-login-password --region us-east-1)" \
--components="zarf-ecr-credential-helper" \
--confirm

delete-private-repos: ## Delete private ECR repos created by deploying the AWS init package
@repos="defenseunicorns/pepr/controller defenseunicorns/zarf/agent defenseunicorns/zarf-init-aws/ecr-credential-helper"; \
@repos="defenseunicorns/pepr/controller defenseunicorns/zarf/agent"; \
for repo in $${repos}; do \
aws ecr delete-repository --repository-name "$$repo" --force --region us-east-1 || true; \
done
Expand All @@ -139,11 +129,10 @@ deploy-init-package-public: ## Run zarf init to deploy the AWS init package conf
--registry-url="$$(aws ecr-public describe-registries --query 'registries[0].registryUri' --output text --region us-east-1)" \
--registry-push-username="AWS" \
--registry-push-password="$$(aws ecr-public get-login-password --region us-east-1)" \
--components="zarf-ecr-credential-helper" \
--confirm

delete-public-repos: ## Delete public ECR repos created by deploying the AWS init package
@repos="defenseunicorns/pepr/controller defenseunicorns/zarf/agent defenseunicorns/zarf-init-aws/ecr-credential-helper"; \
@repos="defenseunicorns/pepr/controller defenseunicorns/zarf/agent"; \
for repo in $${repos}; do \
aws ecr-public delete-repository --repository-name "$$repo" --force --region us-east-1 || true; \
done
Expand Down
23 changes: 5 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,13 @@ This repository contains the Zarf init package for AWS that uses [ECR](https://a

- AWS CLI configured with the necessary permissions to describe and create ECR repositories, and fetch ECR tokens

- Create IAM role for the Pepr webhook to be able to list and create ECR repositories
- See an [example role for reference](iam/json/ecr-webhook-role.json). Be sure to replace the `{{AWS_ACCOUNT_ID}}` and `{{EKS_CLUSTER_ID}}` placeholders, as well as the AWS region with your values.
- Create IAM role for the Pepr controller to be able to list and create ECR repositories, as well as fetch ECR tokens
- See an [example role for reference](iam/json/ecr-role.json). Be sure to replace the `{{AWS_ACCOUNT_ID}}` and `{{EKS_CLUSTER_ID}}` placeholders, as well as the AWS region with your values.

- You will need to create an IAM policy with the appropriate permissions and attach it to the role. See an [example policy for reference](iam/json/ecr-webhook-policy.json).
- You will need to create an IAM policy with the appropriate permissions and attach it to the role. See an [example policy for reference](iam/json/ecr-policy.json).

***Note***: If you only need to work with a private ECR registry, the `ecr-public:` prefixed actions can be removed from the policy. Likewise, if you only need to work with a public ECR registry, the `ecr:` prefixed actions can be removed from the policy.

- (Optional) Create IAM role for the `zarf-ecr-credential-helper` to be able to fetch new ECR auth tokens
- The credential helper is an optional component and is NOT required to use ECR as an external Zarf registry. It can be used if you are looking for an automated solution for keeping your image pull secrets updated with valid ECR auth tokens. Frequent rotation of ECR tokens in image pull secrets is required because they expire after 12 hours. <https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_GetAuthorizationToken.html>

- See an [example role for reference](iam/json/ecr-credential-helper-role.json). Be sure to replace the `{{AWS_ACCOUNT_ID}}` and `{{EKS_CLUSTER_ID}}` placeholders, as well as the AWS region with your values.

- You will need to create an IAM policy with the appropriate permissions and attach it to the role. See an [example policy for reference](iam/json/ecr-credential-helper-policy.json).

***Note***: If you only need to work with a private ECR registry, the `ecr-public:` prefixed actions can be removed from the policy. Likewise, if you only need to work with a public ECR registry, the `ecr:` prefixed actions can be removed from the policy.

### Get the Zarf init package

```bash
Expand All @@ -61,16 +52,14 @@ zarf package pull oci://ghcr.io/defenseunicorns/packages/init-aws:$(zarf version

package:
deploy:
components: zarf-ecr-credential-helper
set:
registry_type: private

# Change me to your AWS region if needed
aws_region: us-east-1

# Set IAM role ARNs
ecr_hook_role_arn: <YOUR_WEBHOOK_ROLE_ARN>
ecr_credential_helper_role_arn: <YOUR_CREDENTIAL_HELPER_ROLE_ARN>
ecr_role_arn: <YOUR_ROLE_ARN>

```

Expand Down Expand Up @@ -98,7 +87,6 @@ zarf package pull oci://ghcr.io/defenseunicorns/packages/init-aws:$(zarf version

package:
deploy:
components: zarf-ecr-credential-helper
set:
registry_type: public

Expand All @@ -107,8 +95,7 @@ zarf package pull oci://ghcr.io/defenseunicorns/packages/init-aws:$(zarf version
aws_region: us-east-1

# Set IAM role ARNs
ecr_hook_role_arn: <YOUR_WEBHOOK_ROLE_ARN>
ecr_credential_helper_role_arn: <YOUR_CREDENTIAL_HELPER_ROLE_ARN>
ecr_role_arn: <YOUR_ROLE_ARN>

```

Expand Down
67 changes: 67 additions & 0 deletions capabilities/ecr-credential-helper/credential-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Capability } from "pepr";
import { ECRPrivate } from "../ecr-private";
import { ECRPublic } from "../ecr-public";
import { isPrivateECRURL, isPublicECRURL } from "../lib/utils";
import { updateZarfManagedImageSecrets } from "../lib/zarf";
import { isECRregistry } from "../lib/ecr";

/**
* The ECR Credential Helper Capability refreshes ECR tokens for Zarf image pull secrets.
*/
export const ECRCredentialHelper = new Capability({
name: "ecr-credential-helper",
description: "Refreshes ECR tokens for Zarf image pull secrets",
namespaces: ["pepr-system"],
});

const { OnSchedule } = ECRCredentialHelper;

/**
* Following the same schedule used by the EKS Anywhere CronJob used to refresh ECR tokens.
* https://github.com/aws/eks-anywhere-packages/blob/492a930b6cc1791208b9ac6da9907331350ace5a/charts/eks-anywhere-packages/templates/cronjob.yaml#L18
*/
OnSchedule({
name: "refresh-ecr-token",
every: 5,
unit: "hours",
run: async () => {
await refreshECRToken();
},
});

async function refreshECRToken(): Promise<void> {
let authToken: string = "";
const region = process.env.AWS_REGION;

if (region === undefined) {
throw new Error("AWS_REGION environment variable is not defined.");
}

try {
const result = await isECRregistry();

if (!result.isECR) {
throw new Error(
`A valid ECR URL was not found in the Zarf state secret: ${result.registryURL}\n
Please provide a valid ECR registry URL.\n
Example: '123456789012.dkr.ecr.us-east-1.amazonaws.com'`,
);
}

if (isPrivateECRURL(result.registryURL)) {
const ecrPrivate = new ECRPrivate(region);
authToken = await ecrPrivate.fetchECRToken();
}

if (isPublicECRURL(result.registryURL)) {
const ecrPublic = new ECRPublic(region);
authToken = await ecrPublic.fetchECRToken();
}

await updateZarfManagedImageSecrets(result.registryURL, authToken);
} catch (err) {
lucasrod16 marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(
`unable to update ECR token in Zarf image pull secrets: ${err}`,
);
}
}
lucasrod16 marked this conversation as resolved.
Show resolved Hide resolved
27 changes: 24 additions & 3 deletions capabilities/ecr-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
CreateRepositoryCommandInput,
DescribeRepositoriesCommand,
DescribeRepositoriesCommandInput,
GetAuthorizationTokenCommand,
} from "@aws-sdk/client-ecr";
import { Log } from "pepr";
import { ECRProvider } from "./ecr-provider";
Expand Down Expand Up @@ -63,8 +64,9 @@ export class ECRPrivate implements ECRProvider {
}
return existingRepositories;
} catch (err) {
Log.error(`Error checking for existing ECR repositories: ${err}`);
return [];
throw new Error(
`error listing existing private ECR repositories: ${err}`,
);
}
}

Expand Down Expand Up @@ -98,7 +100,26 @@ export class ECRPrivate implements ECRProvider {
}
}
} catch (err) {
Log.error(`Error creating ECR repositories: ${err}`);
throw new Error(`error creating ECR repositories: ${err}`);
}
}

async fetchECRToken(): Promise<string> {
try {
const authOutput = await this.ecr.send(
new GetAuthorizationTokenCommand({}),
);

if (authOutput.authorizationData.length === 0) {
throw new Error("No authorization data received from ECR");
}
if (authOutput.authorizationData[0].authorizationToken === "") {
throw new Error("Empty authorization token received from ECR");
}

return authOutput.authorizationData[0].authorizationToken;
} catch (err) {
throw new Error(`error fetching ECR token: ${err}`);
}
}
}
1 change: 1 addition & 0 deletions capabilities/ecr-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
export interface ECRProvider {
listExistingRepositories(repoNames: string[]): Promise<string[]>;
createRepositories(repoNames: string[], accountId?: string): Promise<void>;
fetchECRToken(): Promise<string>;
}
Loading