Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/patch-remove-vulnerable-cli-proxy-pin.md

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

5 changes: 0 additions & 5 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,6 @@
"digest": "sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1",
"pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"
},
"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43": {
"image": "ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43",
"digest": "sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab",
"pinned_image": "ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43@sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab"
},
"ghcr.io/github/gh-aw-firewall/squid:0.27.43": {
"image": "ghcr.io/github/gh-aw-firewall/squid:0.27.43",
"digest": "sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d",
Expand Down
5 changes: 0 additions & 5 deletions pkg/actionpins/data/action_pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,6 @@
"digest": "sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1",
"pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"
},
"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43": {
"image": "ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43",
"digest": "sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab",
"pinned_image": "ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43@sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab"
},
"ghcr.io/github/gh-aw-firewall/squid:0.27.43": {
"image": "ghcr.io/github/gh-aw-firewall/squid:0.27.43",
"digest": "sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d",
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/awf_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2241,7 +2241,7 @@ func TestBuildAWFImageTagWithDigests(t *testing.T) {
assert.Contains(t, tag, "squid=sha256:", "should include squid digest metadata")
assert.Contains(t, tag, "agent=sha256:", "should include agent digest metadata")
assert.Contains(t, tag, "api-proxy=sha256:", "should include api-proxy digest metadata")
assert.Contains(t, tag, "cli-proxy=sha256:", "should include cli-proxy digest metadata")
assert.NotContains(t, tag, "cli-proxy=sha256:", "should omit cli-proxy digest metadata when no embedded pin exists")
})

t.Run("leaves tag unchanged when digests are unavailable", func(t *testing.T) {
Expand Down
5 changes: 0 additions & 5 deletions pkg/workflow/data/action_pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,6 @@
"digest": "sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1",
"pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"
},
"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43": {
"image": "ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43",
"digest": "sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab",
"pinned_image": "ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43@sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab"
},
"ghcr.io/github/gh-aw-firewall/squid:0.27.43": {
"image": "ghcr.io/github/gh-aw-firewall/squid:0.27.43",
"digest": "sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d",
Expand Down
32 changes: 24 additions & 8 deletions pkg/workflow/docker_firewall_pin_compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ Test workflow.`
}

// TestCompileWorkflow_FirewallImagesPinnedForDefaultVersion is a regression test for
// gh-aw#43307: the four gh-aw-firewall images at the current default version
// (constants.DefaultFirewallVersion) must all be digest-pinned in consumer lock files
// even when no local action-cache is present. This covers the cli-proxy image
// introduced in v0.82 as well as the three legacy images (agent, api-proxy, squid).
// gh-aw#43307: the default gh-aw-firewall images must continue to be emitted in
// consumer lock files even when no local action-cache is present. Images with
// trusted embedded pins (agent, api-proxy, squid) must be digest-pinned, while
// cli-proxy falls back to its tag when no embedded pin exists.
func TestCompileWorkflow_FirewallImagesPinnedForDefaultVersion(t *testing.T) {
// Strip the leading "v" to get the Docker image tag (mirrors getAWFImageTag).
imageTag := strings.TrimPrefix(string(constants.DefaultFirewallVersion), "v")
Expand Down Expand Up @@ -143,17 +143,16 @@ Test workflow.`

yamlStr := string(yaml)

expectedPins := []struct {
expectedPinnedImages := []struct {
name string
image string
}{
{name: "agent", image: constants.DefaultFirewallRegistry + "/agent:" + imageTag},
{name: "api-proxy", image: constants.DefaultFirewallRegistry + "/api-proxy:" + imageTag},
{name: "cli-proxy", image: constants.DefaultFirewallRegistry + "/cli-proxy:" + imageTag},
{name: "squid", image: constants.DefaultFirewallRegistry + "/squid:" + imageTag},
}

for _, expectedPin := range expectedPins {
for _, expectedPin := range expectedPinnedImages {
pin, ok := getEmbeddedContainerPin(expectedPin.image)
if !ok {
t.Fatalf("Expected embedded pin for %s", expectedPin.image)
Expand All @@ -170,11 +169,25 @@ Test workflow.`
}
}

cliProxyImage := constants.DefaultFirewallRegistry + "/cli-proxy:" + imageTag
if strings.Contains(yamlStr, `"image":"`+cliProxyImage+`","digest":"`) {
t.Errorf("Did not expect manifest header to include a digest for %s", cliProxyImage)
}
if strings.Contains(yamlStr, cliProxyImage+"@sha256:") {
t.Errorf("Did not expect %s to be emitted as a digest-pinned image", cliProxyImage)
}
if !strings.Contains(yamlStr, "# - "+cliProxyImage) {
t.Errorf("Expected unpinned container comment for %s", cliProxyImage)
}
if !strings.Contains(yamlStr, cliProxyImage) {
t.Errorf("Expected download reference for %s", cliProxyImage)
}

imageTagParts := []string{
`imageTag`,
imageTag + `,`,
}
for _, expectedPin := range expectedPins {
for _, expectedPin := range expectedPinnedImages {
pin, ok := getEmbeddedContainerPin(expectedPin.image)
if !ok {
t.Fatalf("Expected embedded pin for %s", expectedPin.image)
Expand All @@ -190,6 +203,9 @@ Test workflow.`
t.Errorf("Expected AWF config JSON to include %s", imageTagPart)
}
}
if strings.Contains(yamlStr, "cli-proxy=sha256:") {
t.Errorf("Did not expect AWF config JSON to include cli-proxy digest metadata when no pin exists")
}
}

// TestCompileWorkflow_BuildToolsImagePinnedForArcDind is a regression test for
Expand Down
25 changes: 20 additions & 5 deletions pkg/workflow/docker_pin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ func TestApplyContainerPins(t *testing.T) {
}

// TestApplyContainerPins_DefaultFirewallVersion is a regression test for gh-aw#43307:
// all four gh-aw-firewall images at constants.DefaultFirewallVersion (including cli-proxy,
// which was new in v0.82) must have entries in the embedded pin table so that consumer
// compiles without a local cache still emit digest-pinned references.
// Using constants means the test automatically tracks version bumps.
// the embedded pin table must cover the default firewall sidecars that still publish
// trusted digest pins (agent, api-proxy, squid) so consumer compiles without a local
// cache still emit digest-pinned references. Using constants means the test
// automatically tracks version bumps.
func TestApplyContainerPins_DefaultFirewallVersion(t *testing.T) {
imageTag := strings.TrimPrefix(string(constants.DefaultFirewallVersion), "v")
sidecars := []string{"agent", "api-proxy", "cli-proxy", "squid"}
sidecars := []string{"agent", "api-proxy", "squid"}

for _, sidecar := range sidecars {
image := constants.DefaultFirewallRegistry + "/" + sidecar + ":" + imageTag
Expand All @@ -139,6 +139,21 @@ func TestApplyContainerPins_DefaultFirewallVersion(t *testing.T) {
}
}

func TestApplyContainerPins_DefaultFirewallCliProxyFallsBackToTag(t *testing.T) {
imageTag := strings.TrimPrefix(string(constants.DefaultFirewallVersion), "v")
image := constants.DefaultFirewallRegistry + "/cli-proxy:" + imageTag

_, ok := getEmbeddedContainerPin(image)
require.False(t, ok, "embedded pin should not exist for %s", image)

refs, pinEntries := applyContainerPins([]string{image}, nil)
require.Len(t, refs, 1)
require.Len(t, pinEntries, 1)
assert.Equal(t, image, refs[0], "cli-proxy should fall back to its tag when no embedded pin exists")
assert.Empty(t, pinEntries[0].Digest, "cli-proxy should not report a digest when no embedded pin exists")
assert.Empty(t, pinEntries[0].PinnedImage, "cli-proxy should not report a pinned image when no embedded pin exists")
}

// TestCollectDockerImages_StoresInWorkflowData verifies that collectDockerImages
// populates workflowData.DockerImages and DockerImagePins with the collected image refs.
func TestCollectDockerImages_StoresInWorkflowData(t *testing.T) {
Expand Down