Skip to content

Commit 5e42463

Browse files
Merge pull request #2556 from vamshi-stepsecurity/fix/pin-composite-actions
Fix: Pin composite actions
2 parents 0f82786 + f1dc374 commit 5e42463

File tree

8 files changed

+377
-16
lines changed

8 files changed

+377
-16
lines changed

knowledge-base/actions/home-assistant/actions/helpers/cas/action-security.yml

Lines changed: 0 additions & 2 deletions
This file was deleted.

knowledge-base/actions/home-assistant/actions/helpers/codenotary/action-security.yml

Lines changed: 0 additions & 2 deletions
This file was deleted.

remediation/workflow/hardenrunner/addaction.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func AddAction(inputYaml, action string, pinActions, pinToImmutable bool, skipCo
5151
}
5252

5353
if updated && pinActions {
54-
out, _, err = pin.PinAction(action, out, nil, pinToImmutable, nil)
54+
out, _, err = pin.PinActionWithPatFallback(action, out, nil, pinToImmutable, nil)
5555
if err != nil {
5656
return out, updated, err
5757
}

remediation/workflow/metadata/actionmetadata.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Workflow struct {
2020
//On string `yaml:"on"`
2121
Env Env `yaml:"env"`
2222
Jobs Jobs `yaml:"jobs"`
23+
Runs Runs `yaml:"runs"`
2324
}
2425
type Step struct {
2526
Run string `yaml:"run"`
@@ -36,6 +37,11 @@ type Job struct {
3637
Steps []Step `yaml:"steps"`
3738
}
3839

40+
type Runs struct {
41+
Using string `yaml:"using"`
42+
Steps []Step `yaml:"steps"`
43+
}
44+
3945
type Container struct {
4046
Image string `yaml:"image"`
4147
Options string `yaml:"options"`

remediation/workflow/pin/pinactions.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,21 @@ func PinActions(inputYaml string, exemptedActions []string, pinToImmutable bool,
2929
for _, step := range job.Steps {
3030
if len(step.Uses) > 0 {
3131
localUpdated := false
32-
out, localUpdated, err = PinAction(step.Uses, out, exemptedActions, pinToImmutable, actionCommitMap)
32+
out, localUpdated, err = PinActionWithPatFallback(step.Uses, out, exemptedActions, pinToImmutable, actionCommitMap)
33+
if err != nil {
34+
return out, updated, err
35+
}
36+
updated = updated || localUpdated
37+
}
38+
}
39+
}
40+
41+
// For composite actions
42+
if workflow.Runs.Using == "composite" {
43+
for _, run := range workflow.Runs.Steps {
44+
if len(run.Uses) > 0 {
45+
localUpdated := false
46+
out, localUpdated, err = PinActionWithPatFallback(run.Uses, out, exemptedActions, pinToImmutable, actionCommitMap)
3347
if err != nil {
3448
return out, updated, err
3549
}
@@ -41,7 +55,25 @@ func PinActions(inputYaml string, exemptedActions []string, pinToImmutable bool,
4155
return out, updated, nil
4256
}
4357

44-
func PinAction(action, inputYaml string, exemptedActions []string, pinToImmutable bool, actionCommitMap map[string]string) (string, bool, error) {
58+
func PinActionWithPatFallback(action, inputYaml string, exemptedActions []string, pinToImmutable bool, actionCommitMap map[string]string) (string, bool, error) {
59+
// use secure repo token
60+
PAT := os.Getenv("SECURE_REPO_PAT")
61+
if PAT == "" {
62+
PAT = os.Getenv("PAT")
63+
log.Println("SECURE_REPO_PAT is not set, using PAT")
64+
} else {
65+
log.Println("SECURE_REPO_PAT is set")
66+
}
67+
out, updated, err := PinAction(action, inputYaml, PAT, exemptedActions, pinToImmutable, actionCommitMap)
68+
if err != nil && strings.Contains(err.Error(), "organization has an IP allow list enabled, and your IP address is not permitted to access this resource") {
69+
PAT = os.Getenv("PAT")
70+
log.Println("[RETRY] SECURE_REPO_PAT is not set, using PAT")
71+
return PinAction(action, inputYaml, PAT, exemptedActions, pinToImmutable, actionCommitMap)
72+
}
73+
return out, updated, err
74+
}
75+
76+
func PinAction(action, inputYaml, PAT string, exemptedActions []string, pinToImmutable bool, actionCommitMap map[string]string) (string, bool, error) {
4577
updated := false
4678

4779
if !strings.Contains(action, "@") || strings.HasPrefix(action, "docker://") {
@@ -63,15 +95,6 @@ func PinAction(action, inputYaml string, exemptedActions []string, pinToImmutabl
6395
owner := splitOnSlash[0]
6496
repo := splitOnSlash[1]
6597

66-
// use secure repo token
67-
PAT := os.Getenv("SECURE_REPO_PAT")
68-
if PAT == "" {
69-
PAT = os.Getenv("PAT")
70-
log.Println("SECURE_REPO_PAT is not set, using PAT")
71-
} else {
72-
log.Println("SECURE_REPO_PAT is set")
73-
}
74-
7598
ctx := context.Background()
7699
ts := oauth2.StaticTokenSource(
77100
&oauth2.Token{AccessToken: PAT},

remediation/workflow/pin/pinactions_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,36 @@ func TestPinActions(t *testing.T) {
173173
}
174174
]`))
175175

176+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/setup-java/commits/v4",
177+
httpmock.NewStringResponder(200, `c12b8546b67672ee38ac87bea491ac94a587f7cc`))
178+
179+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/setup-java/git/matching-refs/tags/v4.",
180+
httpmock.NewStringResponder(200,
181+
`[
182+
{
183+
"ref": "refs/tags/v4.5.5",
184+
"object": {
185+
"sha": "c12b8546b67672ee38ac87bea491ac94a587f7cc",
186+
"type": "commit"
187+
}
188+
}
189+
]`))
190+
191+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/checkout/commits/v4",
192+
httpmock.NewStringResponder(200, `c12b8546b67672ee38ac87bea491ac94a587f7ch`))
193+
194+
httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/checkout/git/matching-refs/tags/v4.",
195+
httpmock.NewStringResponder(200,
196+
`[
197+
{
198+
"ref": "refs/tags/v4.5.6",
199+
"object": {
200+
"sha": "c12b8546b67672ee38ac87bea491ac94a587f7sh",
201+
"type": "commit"
202+
}
203+
}
204+
]`))
205+
176206
httpmock.RegisterResponder("GET", "https://api.github.com/repos/rohith/publish-nuget/commits/v2",
177207
httpmock.NewStringResponder(200, `c12b8546b67672ee38ac87bea491ac94a587f7cc`))
178208

@@ -326,6 +356,8 @@ func TestPinActions(t *testing.T) {
326356
{fileName: "exemptaction.yml", wantUpdated: true, exemptedActions: []string{"actions/checkout", "rohith/*", "praveen/*", "aman-*/*", "*/seperate*", "starc/*"}, pinToImmutable: true},
327357
{fileName: "donotpintoimmutable.yml", wantUpdated: true, pinToImmutable: false},
328358
{fileName: "invertedcommas.yml", wantUpdated: true, pinToImmutable: false},
359+
{fileName: "pinusingmap.yml", wantUpdated: true, pinToImmutable: true},
360+
{fileName: "action.yml", wantUpdated: true, pinToImmutable: false},
329361
}
330362
for _, tt := range tests {
331363

@@ -348,6 +380,12 @@ func TestPinActions(t *testing.T) {
348380
}
349381
}
350382

383+
if tt.fileName == "action.yml" {
384+
actionCommitMap = map[string]string{
385+
"actions/checkout@v4": "c12b8546b67672ee38ac87bea491ac94a587f7sh",
386+
}
387+
}
388+
351389
output, gotUpdated, err = PinActions(string(input), tt.exemptedActions, tt.pinToImmutable, actionCommitMap)
352390
if tt.wantUpdated != gotUpdated {
353391
t.Errorf("test failed wantUpdated %v did not match gotUpdated %v", tt.wantUpdated, gotUpdated)
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
name: 'Create Component Version Composite Action'
2+
description: 'Create Component Version, Add Version Link, Add Version Files, Add Version Properties'
3+
branding:
4+
icon: 'arrow-up-right'
5+
color: 'purple'
6+
inputs:
7+
component:
8+
description: 'Component Name/ID'
9+
required: true
10+
versionname:
11+
description: 'Component Version Name'
12+
required: true
13+
description:
14+
description: 'Component version description'
15+
required: false
16+
default: 'Created by following GitHub action: Create Component Version Composite Action'
17+
linkName:
18+
description: 'Name of link to add to component version'
19+
required: false
20+
default: 'GitHub Composition Action Link Name'
21+
link:
22+
description: 'Value of the link to add to component version'
23+
required: false
24+
base:
25+
description: 'Local base directory containing files to upload if file upload is required'
26+
required: false
27+
offset:
28+
description: 'Target path offset (the directory in the version files to which these files should be added)'
29+
required: false
30+
include:
31+
description: 'An include file pattern for selecting files to add (may be repeated)'
32+
required: false
33+
exclude:
34+
description: 'An exclude file pattern for excluding files (may be repeated). Overrides includes.'
35+
required: false
36+
saveExecuteBits:
37+
description: 'Saves execute bits for files.'
38+
required: false
39+
type: boolean
40+
versionProperties:
41+
description: 'Properties to set on the component version. Each property must name name:value:secure. If you have multiple properties, then they should be separated by a new line character'
42+
required: false
43+
urlType:
44+
description: 'URL protocol to use to connect to DevOps Deploy hostname'
45+
required: false
46+
default: 'https:'
47+
hostname:
48+
description: 'DevOps Deploy hostname'
49+
required: true
50+
port:
51+
description: 'port'
52+
required: true
53+
default: '8443'
54+
username:
55+
description: 'DevOps Deploy username'
56+
required: false
57+
password:
58+
description: 'DevOps Deploy password'
59+
required: false
60+
secret: true
61+
authToken:
62+
description: 'DevOps Deploy authentication token'
63+
required: false
64+
secret: true
65+
runs:
66+
using: "composite"
67+
68+
steps:
69+
# Download udclient package and command shell scripts from composite action github repository
70+
- uses: actions/checkout@v4
71+
with:
72+
repository: jwcarmichael12/test-composite-action
73+
sparse-checkout: |
74+
artifacts/devops-deploy-client.zip
75+
scripts/addVersionFiles.sh
76+
scripts/addVersionProperties.sh
77+
sparse-checkout-cone-mode: false
78+
path: deploy
79+
80+
# Setup java environment needed to run the udclient
81+
- uses: actions/setup-java@v4
82+
with:
83+
distribution: 'adopt-openj9' # See 'Supported distributions' for available options
84+
java-version: '11'
85+
86+
# Expand the udclient package to access the executable
87+
- name: Install udclient
88+
id: udclient-install
89+
run: unzip deploy/artifacts/devops-deploy-client.zip
90+
shell: bash
91+
92+
# Setup global environment variables used by udclient
93+
- name: Set global environment variables.
94+
id: set-env-variables
95+
run: |
96+
if [ ! -z ${{ inputs.authtoken }} ]; then echo "DS_AUTH_TOKEN=${{ inputs.authtoken }}" >> $GITHUB_ENV; fi &&
97+
if [ ! -z ${{ inputs.username }} ]; then echo "DS_USERNAME=${{ inputs.username }}" >> $GITHUB_ENV; fi &&
98+
if [ ! -z ${{ inputs.password }} ]; then echo "DS_PASSWORD=${{ inputs.password }}" >> $GITHUB_ENV; fi &&
99+
echo "DS_WEB_URL=${{ inputs.urlType }}//${{ inputs.hostname }}:${{ inputs.port }}" >> $GITHUB_ENV
100+
shell: bash
101+
102+
# Create component version
103+
- name: Create component version
104+
id: create-component-version
105+
run: udclient/udclient createVersion -component "${{ inputs.component }}" -name "${{ inputs.versionname }}" -description "${{ inputs.description }}" --importing true
106+
shell: bash
107+
108+
# Add Version Link if one is specified
109+
- name: Add component version link
110+
id: add-version-link
111+
if: inputs.linkName && inputs.link
112+
run: udclient/udclient addVersionLink -component "${{ inputs.component }}" -version "${{ inputs.versionname }}" -linkName "${{ inputs.linkName }}" -link "${{ inputs.link }}"
113+
shell: bash
114+
115+
# Add Version Files if specified
116+
- name: Add component version files
117+
id: add-version-files
118+
if: ${{ inputs.base }}
119+
run: |
120+
deploy/scripts/addVersionFiles.sh
121+
shell: bash
122+
env:
123+
FILES_CMD: "udclient/udclient"
124+
FILES_COMPONENTNAME: "${{ inputs.component }}"
125+
FILES_VERSIONNAME: "${{ inputs.versionname }}"
126+
FILES_BASE: "${{ inputs.base }}"
127+
FILES_OFFSET: "${{ inputs.offset }}"
128+
FILES_INCLUDE: "${{ inputs.include }}"
129+
FILES_EXCLUDE: "${{ inputs.exclude }}"
130+
FILES_SAVEEXECUTEBITS: ${{ inputs.saveExecuteBits }}
131+
132+
# Add Version Properties if specified
133+
- name: Add component version properties
134+
id: add-version-properties
135+
if: ${{ inputs.versionProperties }}
136+
run: |
137+
deploy/scripts/addVersionProperties.sh
138+
shell: bash
139+
env:
140+
VERSION_PROPERTIES_CMD: "udclient/udclient"
141+
VERSION_PROPERTIES_COMPONENTNAME: "${{ inputs.component }}"
142+
VERSION_PROPERTIES_VERSIONNAME: "${{ inputs.versionname }}"
143+
VERSION_PROPERTIES: "${{ inputs.versionProperties }}"
144+
145+
# Tell devops deploy server that component version creation is complete
146+
- name: Finish Importing
147+
id: finish-importing
148+
run: udclient/udclient finishedImporting -component "${{ inputs.component }}" -version "${{ inputs.versionname }}"
149+
shell: bash

0 commit comments

Comments
 (0)