Skip to content

Commit 171cfd9

Browse files
Add way to configure image registries without secret store integration for fedramp_high (#11196)
* Fix nits from #10605 * non-secret-store mode * update containers api client * clean up fedramp region code * add tests * changeset * containers-shared minor * prettify --------- Co-authored-by: Nikita Sharma <nsharma@cloudflare.com>
1 parent c0e249e commit 171cfd9

File tree

7 files changed

+249
-84
lines changed

7 files changed

+249
-84
lines changed

.changeset/evil-paws-check.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": minor
3+
"@cloudflare/containers-shared": minor
4+
---
5+
6+
For containers being created in a FedRAMP high environment, registry credentials are encrypted by the container platform.
7+
Update wrangler to correctly send a request to configure a registry for FedRAMP containers.

packages/containers-shared/src/client/models/ImageRegistryAuth.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
/* tslint:disable */
33
/* eslint-disable */
44

5+
import type { SecretsStoreRef } from "./SecretsStoreRef";
6+
57
/**
6-
* A JSON string that encodes the auth required to authenticate with an external image registry. The format of the JSON object is determined by the registry being configured.
8+
* Credentials needed to authenticate with an external image registry.
79
*/
810
export type ImageRegistryAuth = {
11+
/**
12+
* The format of this value is determined by the registry being configured.
13+
*/
914
public_credential: string;
10-
private_credential: {
11-
store_id: string;
12-
secret_name: string;
13-
};
15+
private_credential: string | SecretsStoreRef;
1416
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* istanbul ignore file */
2+
/* tslint:disable */
3+
/* eslint-disable */
4+
5+
/**
6+
* A reference to a secret stored in Secrets Store
7+
*/
8+
export type SecretsStoreRef = {
9+
/**
10+
* Store ID where the secret is stored
11+
*/
12+
store_id: string;
13+
/**
14+
* Name of the secret being referenced
15+
*/
16+
secret_name: string;
17+
};

packages/containers-shared/src/images.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,9 @@ export function resolveImageName(accountId: string, image: string): string {
206206
}
207207

208208
/**
209-
* get type of container registry, and validate
210-
* currently we support cloudflare managed registries and AWS ECR
211-
* when using cloudflare mananged registries we expect CLOUDFLARE_CONTAINER_REGISTRY to be set
209+
* Get type of container registry, and validate.
210+
* Currently we support Cloudflare managed registries and AWS ECR.
211+
* When using Cloudflare managed registries we expect CLOUDFLARE_CONTAINER_REGISTRY to be set
212212
*/
213213
export const getAndValidateRegistryType = (domain: string): RegistryPattern => {
214214
// TODO: use parseImageName when that gets moved to this package

packages/wrangler/src/__tests__/containers/registries.test.ts

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { http, HttpResponse } from "msw";
2-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
2+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33
import { mockAccount } from "../cloudchamber/utils";
44
import { mockAccountId, mockApiToken } from "../helpers/mock-account-id";
55
import { mockCLIOutput } from "../helpers/mock-cli-output";
@@ -66,6 +66,43 @@ describe("containers registries configure", () => {
6666
`);
6767
});
6868

69+
it("should validate command line arguments for Secrets Store", async () => {
70+
const domain = "123456789012.dkr.ecr.us-west-2.amazonaws.com";
71+
await expect(
72+
runWrangler(
73+
`containers registries configure ${domain} --public-credential=test-id --disableSecretsStore`
74+
)
75+
).rejects.toThrowErrorMatchingInlineSnapshot(
76+
`[Error: Secrets Store can only be disabled in FedRAMP compliance regions.]`
77+
);
78+
79+
// Set compliance region to FedRAMP High
80+
vi.stubEnv("CLOUDFLARE_COMPLIANCE_REGION", "fedramp_high");
81+
await expect(
82+
runWrangler(
83+
`containers registries configure ${domain} --aws-access-key-id=test-access-key-id --secret-store-id=storeid`
84+
)
85+
).rejects.toThrowErrorMatchingInlineSnapshot(
86+
`[Error: Secrets Store is not supported in FedRAMP compliance regions. You must set --disableSecretsStore.]`
87+
);
88+
89+
await expect(
90+
runWrangler(
91+
`containers registries configure ${domain} --aws-access-key-id=test-access-key-id --secret-store-id=storeid --disableSecretsStore`
92+
)
93+
).rejects.toThrowErrorMatchingInlineSnapshot(
94+
`[Error: Arguments secret-store-id and disableSecretsStore are mutually exclusive]`
95+
);
96+
97+
await expect(
98+
runWrangler(
99+
`containers registries configure ${domain} --aws-access-key-id=test-access-key-id --secret-name=secret-name --disableSecretsStore`
100+
)
101+
).rejects.toThrowErrorMatchingInlineSnapshot(
102+
`[Error: Arguments secret-name and disableSecretsStore are mutually exclusive]`
103+
);
104+
});
105+
69106
it("should no-op on cloudflare registry (default)", async () => {
70107
await runWrangler(
71108
`containers registries configure registry.cloudflare.com --public-credential=test-id`
@@ -83,6 +120,75 @@ describe("containers registries configure", () => {
83120
`);
84121
});
85122

123+
describe("FedRAMP compliance region", () => {
124+
beforeEach(() => {
125+
vi.stubEnv("CLOUDFLARE_COMPLIANCE_REGION", "fedramp_high");
126+
});
127+
128+
it("should configure AWS ECR registry with interactive prompts", async () => {
129+
setIsTTY(true);
130+
const awsEcrDomain = "123456789012.dkr.ecr.us-west-2.amazonaws.com";
131+
mockPrompt({
132+
text: "Enter AWS Secret Access Key:",
133+
options: { isSecret: true },
134+
result: "test-secret-access-key",
135+
});
136+
137+
mockPutRegistry({
138+
domain: "123456789012.dkr.ecr.us-west-2.amazonaws.com",
139+
is_public: false,
140+
auth: {
141+
public_credential: "test-access-key-id",
142+
private_credential: "test-secret-access-key",
143+
},
144+
kind: "ECR",
145+
});
146+
147+
await runWrangler(
148+
`containers registries configure ${awsEcrDomain} --aws-access-key-id=test-access-key-id --disableSecretsStore`
149+
);
150+
151+
expect(cliStd.stdout).toMatchInlineSnapshot(`
152+
"╭ Configure a container registry
153+
154+
│ Configuring AWS ECR registry: 123456789012.dkr.ecr.us-west-2.amazonaws.com
155+
156+
│ Getting AWS Secret Access Key...
157+
158+
╰ Registry configuration completed
159+
160+
"
161+
`);
162+
});
163+
164+
describe("non-interactive", () => {
165+
beforeEach(() => {
166+
setIsTTY(false);
167+
});
168+
const awsEcrDomain = "123456789012.dkr.ecr.us-west-2.amazonaws.com";
169+
const mockStdIn = useMockStdin({ isTTY: false });
170+
171+
it("should accept the secret from piped input", async () => {
172+
const secret = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
173+
174+
mockStdIn.send(secret);
175+
mockPutRegistry({
176+
domain: awsEcrDomain,
177+
is_public: false,
178+
auth: {
179+
public_credential: "test-access-key-id",
180+
private_credential: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
181+
},
182+
kind: "ECR",
183+
});
184+
185+
await runWrangler(
186+
`containers registries configure ${awsEcrDomain} --public-credential=test-access-key-id --disableSecretsStore`
187+
);
188+
});
189+
});
190+
});
191+
86192
describe("AWS ECR registry configuration", () => {
87193
it("should configure AWS ECR registry with interactive prompts", async () => {
88194
setIsTTY(true);
@@ -219,7 +325,6 @@ describe("containers registries configure", () => {
219325
│ Getting AWS Secret Access Key...
220326
221327
222-
223328
│ Setting up integration with Secrets Store...
224329
225330

packages/wrangler/src/containers/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export const containers = (
100100
)
101101
.command(
102102
"registries",
103-
// hide for now so it doesn't show up in help while we not publicly available
103+
// hide for now so it doesn't show up in help while not publicly available
104104
// "Configure and manage non-Cloudflare registries",
105105
false,
106106
(args) => registryCommands(args).command(subHelp)

0 commit comments

Comments
 (0)