Skip to content

Commit f43b8d2

Browse files
Merge pull request #2525 from step-security/skip-hardenrunner-for-containers
Add skipHardenRunnerForContainers option to exclude container jobs fr…
2 parents 005fba8 + d649f14 commit f43b8d2

File tree

9 files changed

+210
-3
lines changed

9 files changed

+210
-3
lines changed

remediation/workflow/hardenrunner/addaction.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const (
1515
HardenRunnerActionName = "Harden the runner (Audit all outbound calls)"
1616
)
1717

18-
func AddAction(inputYaml, action string, pinActions, pinToImmutable bool) (string, bool, error) {
18+
func AddAction(inputYaml, action string, pinActions, pinToImmutable bool, skipContainerJobs bool) (string, bool, error) {
1919
workflow := metadata.Workflow{}
2020
updated := false
2121
err := yaml.Unmarshal([]byte(inputYaml), &workflow)
@@ -29,6 +29,10 @@ func AddAction(inputYaml, action string, pinActions, pinToImmutable bool) (strin
2929
if metadata.IsCallingReusableWorkflow(job) {
3030
continue
3131
}
32+
// Skip adding action for jobs running in containers if skipContainerJobs is true
33+
if skipContainerJobs && job.Container.Image != "" {
34+
continue
35+
}
3236
alreadyPresent := false
3337
for _, step := range job.Steps {
3438
if len(step.Uses) > 0 && strings.HasPrefix(step.Uses, HardenRunnerActionPath) {

remediation/workflow/hardenrunner/addaction_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func TestAddAction(t *testing.T) {
3333
if err != nil {
3434
t.Fatalf("error reading test file")
3535
}
36-
got, gotUpdated, err := AddAction(string(input), tt.args.action, false, false)
36+
got, gotUpdated, err := AddAction(string(input), tt.args.action, false, false, false)
3737

3838
if gotUpdated != tt.wantUpdated {
3939
t.Errorf("AddAction() updated = %v, wantUpdated %v", gotUpdated, tt.wantUpdated)
@@ -53,3 +53,26 @@ func TestAddAction(t *testing.T) {
5353
})
5454
}
5555
}
56+
57+
func TestAddActionWithContainer(t *testing.T) {
58+
const inputDirectory = "../../../testfiles/addaction/input"
59+
const outputDirectory = "../../../testfiles/addaction/output"
60+
61+
// Test container job with skipContainerJobs = true
62+
input, err := ioutil.ReadFile(path.Join(inputDirectory, "container-job.yml"))
63+
if err != nil {
64+
t.Fatalf("error reading test file")
65+
}
66+
67+
// Test: Skip container jobs when skipContainerJobs = true
68+
got, gotUpdated, err := AddAction(string(input), "step-security/harden-runner@v2", false, false, true)
69+
if err != nil {
70+
t.Errorf("AddAction() with skipContainerJobs=true error = %v", err)
71+
}
72+
if gotUpdated {
73+
t.Errorf("AddAction() with skipContainerJobs=true should not update container job")
74+
}
75+
if got != string(input) {
76+
t.Errorf("AddAction() with skipContainerJobs=true should not modify the yaml")
77+
}
78+
}

remediation/workflow/metadata/actionmetadata.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,17 @@ type Job struct {
3131
Permissions Permissions `yaml:"permissions"`
3232
Uses string `yaml:"uses"`
3333
Env Env `yaml:"env"`
34+
Container Container `yaml:"container"`
3435
// RunsOn []string `yaml:"runs-on"`
3536
Steps []Step `yaml:"steps"`
3637
}
3738

39+
type Container struct {
40+
Image string `yaml:"image"`
41+
Options string `yaml:"options"`
42+
Env Env `yaml:"env"`
43+
}
44+
3845
type Jobs map[string]Job
3946
type With map[string]string
4047
type Env map[string]string

remediation/workflow/secureworkflow.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d
2323
ignoreMissingKBs := false
2424
enableLogging := false
2525
addEmptyTopLevelPermissions := false
26+
skipHardenRunnerForContainers := false
2627
exemptedActions, pinToImmutable, maintainedActionsMap := []string{}, false, map[string]string{}
2728

2829
if len(params) > 0 {
@@ -73,6 +74,10 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d
7374
addEmptyTopLevelPermissions = true
7475
}
7576

77+
if queryStringParams["skipHardenRunnerForContainers"] == "true" {
78+
skipHardenRunnerForContainers = true
79+
}
80+
7681
if enableLogging {
7782
// Log query parameters
7883
paramsJSON, _ := json.MarshalIndent(queryStringParams, "", " ")
@@ -157,7 +162,7 @@ func SecureWorkflow(queryStringParams map[string]string, inputYaml string, svc d
157162
log.Printf("Harden runner action is exempted from pinning")
158163
}
159164
}
160-
secureWorkflowReponse.FinalOutput, addedHardenRunner, _ = hardenrunner.AddAction(secureWorkflowReponse.FinalOutput, HardenRunnerActionPathWithTag, pinHardenRunner, pinToImmutable)
165+
secureWorkflowReponse.FinalOutput, addedHardenRunner, _ = hardenrunner.AddAction(secureWorkflowReponse.FinalOutput, HardenRunnerActionPathWithTag, pinHardenRunner, pinToImmutable, skipHardenRunnerForContainers)
161166
if enableLogging {
162167
log.Printf("Added harden runner: %v", addedHardenRunner)
163168
}

remediation/workflow/secureworkflow_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,95 @@ func TestSecureWorkflow(t *testing.T) {
278278
}
279279
}
280280

281+
func TestSecureWorkflowContainerJob(t *testing.T) {
282+
const inputDirectory = "../../testfiles/secureworkflow/input"
283+
const outputDirectory = "../../testfiles/secureworkflow/output"
284+
285+
httpmock.Activate()
286+
defer httpmock.DeactivateAndReset()
287+
288+
// Mock APIs for actions/checkout
289+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/checkout/commits/v3",
290+
httpmock.NewStringResponder(200, `c85c95e3d7251135ab7dc9ce3241c5835cc595a9`))
291+
292+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/checkout/git/matching-refs/tags/v3.",
293+
httpmock.NewStringResponder(200,
294+
`[
295+
{
296+
"ref": "refs/tags/v3.5.3",
297+
"object": {
298+
"sha": "c85c95e3d7251135ab7dc9ce3241c5835cc595a9",
299+
"type": "commit"
300+
}
301+
}
302+
]`),
303+
)
304+
305+
// Mock APIs for step-security/harden-runner
306+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/harden-runner/commits/v2",
307+
httpmock.NewStringResponder(200, `17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6`))
308+
309+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/step-security/harden-runner/git/matching-refs/tags/v2.",
310+
httpmock.NewStringResponder(200,
311+
`[
312+
{
313+
"ref": "refs/tags/v2.8.1",
314+
"object": {
315+
"sha": "17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6",
316+
"type": "commit"
317+
}
318+
}
319+
]`),
320+
)
321+
322+
var err error
323+
var input []byte
324+
input, err = ioutil.ReadFile(path.Join(inputDirectory, "container-job.yml"))
325+
326+
if err != nil {
327+
log.Fatal(err)
328+
}
329+
330+
os.Setenv("KBFolder", "../../knowledge-base/actions")
331+
332+
// Test with skipHardenRunnerForContainers = true
333+
queryParams := make(map[string]string)
334+
queryParams["skipHardenRunnerForContainers"] = "true"
335+
queryParams["addProjectComment"] = "false"
336+
337+
output, err := SecureWorkflow(queryParams, string(input), &mockDynamoDBClient{})
338+
339+
if err != nil {
340+
t.Errorf("Error not expected")
341+
}
342+
343+
// Verify that harden runner was not added
344+
if output.AddedHardenRunner {
345+
t.Errorf("Harden runner should not be added for container job with skipHardenRunnerForContainers=true")
346+
}
347+
348+
// Verify that the output matches expected output file
349+
expectedOutput, err := ioutil.ReadFile(path.Join(outputDirectory, "container-job.yml"))
350+
if err != nil {
351+
log.Fatal(err)
352+
}
353+
354+
if output.FinalOutput != string(expectedOutput) {
355+
t.Errorf("test failed container-job.yml did not match expected output\nExpected:\n%s\n\nGot:\n%s",
356+
string(expectedOutput), output.FinalOutput)
357+
}
358+
359+
// Verify permissions were added
360+
if !output.AddedPermissions {
361+
t.Errorf("Permissions should be added even for container jobs")
362+
}
363+
364+
// Verify actions were pinned
365+
if !output.PinnedActions {
366+
t.Errorf("Actions should be pinned even for container jobs")
367+
}
368+
}
369+
281370
func TestSecureWorkflowEmptyPermissions(t *testing.T) {
282371
const inputDirectory = "../../testfiles/secureworkflow/input"
283372
const outputDirectory = "../../testfiles/secureworkflow/output"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: "Container job workflow"
2+
3+
on:
4+
push:
5+
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
container:
11+
image: cgr.dev/chainguard/wolfi-base@sha256:91ed94ec4e72368a9b5113f2ffb1d8e783a91db489011a89d9fad3e3816a75ba
12+
options: >-
13+
--health-cmd pg_isready
14+
--health-interval 10s
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v3
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: "Container job workflow"
2+
3+
on:
4+
push:
5+
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
container:
11+
image: cgr.dev/chainguard/wolfi-base@sha256:91ed94ec4e72368a9b5113f2ffb1d8e783a91db489011a89d9fad3e3816a75ba
12+
options: >-
13+
--health-cmd pg_isready
14+
--health-interval 10s
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v3
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: "Container job workflow"
2+
3+
on:
4+
push:
5+
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
container:
11+
image: cgr.dev/chainguard/wolfi-base@sha256:91ed94ec4e72368a9b5113f2ffb1d8e783a91db489011a89d9fad3e3816a75ba
12+
options: >-
13+
--health-cmd pg_isready
14+
--health-interval 10s
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v3
19+
- name: Run tests
20+
run: npm test
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: "Container job workflow"
2+
3+
on:
4+
push:
5+
6+
7+
permissions:
8+
contents: read
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
container:
14+
image: cgr.dev/chainguard/wolfi-base@sha256:91ed94ec4e72368a9b5113f2ffb1d8e783a91db489011a89d9fad3e3816a75ba
15+
options: >-
16+
--health-cmd pg_isready
17+
--health-interval 10s
18+
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
22+
- name: Run tests
23+
run: npm test

0 commit comments

Comments
 (0)