Skip to content
Merged
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

This file was deleted.

This file was deleted.

2 changes: 1 addition & 1 deletion remediation/workflow/hardenrunner/addaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func AddAction(inputYaml, action string, pinActions, pinToImmutable bool, skipCo
}

if updated && pinActions {
out, _, err = pin.PinAction(action, out, nil, pinToImmutable, nil)
out, _, err = pin.PinActionWithPatFallback(action, out, nil, pinToImmutable, nil)
if err != nil {
return out, updated, err
}
Expand Down
6 changes: 6 additions & 0 deletions remediation/workflow/metadata/actionmetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Workflow struct {
//On string `yaml:"on"`
Env Env `yaml:"env"`
Jobs Jobs `yaml:"jobs"`
Runs Runs `yaml:"runs"`
}
type Step struct {
Run string `yaml:"run"`
Expand All @@ -36,6 +37,11 @@ type Job struct {
Steps []Step `yaml:"steps"`
}

type Runs struct {
Using string `yaml:"using"`
Steps []Step `yaml:"steps"`
}

type Container struct {
Image string `yaml:"image"`
Options string `yaml:"options"`
Expand Down
45 changes: 34 additions & 11 deletions remediation/workflow/pin/pinactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,21 @@ func PinActions(inputYaml string, exemptedActions []string, pinToImmutable bool,
for _, step := range job.Steps {
if len(step.Uses) > 0 {
localUpdated := false
out, localUpdated, err = PinAction(step.Uses, out, exemptedActions, pinToImmutable, actionCommitMap)
out, localUpdated, err = PinActionWithPatFallback(step.Uses, out, exemptedActions, pinToImmutable, actionCommitMap)
if err != nil {
return out, updated, err
}
updated = updated || localUpdated
}
}
}

// For composite actions
if workflow.Runs.Using == "composite" {
for _, run := range workflow.Runs.Steps {
if len(run.Uses) > 0 {
localUpdated := false
out, localUpdated, err = PinActionWithPatFallback(run.Uses, out, exemptedActions, pinToImmutable, actionCommitMap)
if err != nil {
return out, updated, err
}
Expand All @@ -41,7 +55,25 @@ func PinActions(inputYaml string, exemptedActions []string, pinToImmutable bool,
return out, updated, nil
}

func PinAction(action, inputYaml string, exemptedActions []string, pinToImmutable bool, actionCommitMap map[string]string) (string, bool, error) {
func PinActionWithPatFallback(action, inputYaml string, exemptedActions []string, pinToImmutable bool, actionCommitMap map[string]string) (string, bool, error) {
// use secure repo token
PAT := os.Getenv("SECURE_REPO_PAT")
if PAT == "" {
PAT = os.Getenv("PAT")
log.Println("SECURE_REPO_PAT is not set, using PAT")
} else {
log.Println("SECURE_REPO_PAT is set")
}
out, updated, err := PinAction(action, inputYaml, PAT, exemptedActions, pinToImmutable, actionCommitMap)
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") {
PAT = os.Getenv("PAT")
log.Println("[RETRY] SECURE_REPO_PAT is not set, using PAT")
return PinAction(action, inputYaml, PAT, exemptedActions, pinToImmutable, actionCommitMap)
}
return out, updated, err
}

func PinAction(action, inputYaml, PAT string, exemptedActions []string, pinToImmutable bool, actionCommitMap map[string]string) (string, bool, error) {
updated := false

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

// use secure repo token
PAT := os.Getenv("SECURE_REPO_PAT")
if PAT == "" {
PAT = os.Getenv("PAT")
log.Println("SECURE_REPO_PAT is not set, using PAT")
} else {
log.Println("SECURE_REPO_PAT is set")
}

ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: PAT},
Expand Down
38 changes: 38 additions & 0 deletions remediation/workflow/pin/pinactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,36 @@ func TestPinActions(t *testing.T) {
}
]`))

httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/setup-java/commits/v4",
httpmock.NewStringResponder(200, `c12b8546b67672ee38ac87bea491ac94a587f7cc`))

httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/setup-java/git/matching-refs/tags/v4.",
httpmock.NewStringResponder(200,
`[
{
"ref": "refs/tags/v4.5.5",
"object": {
"sha": "c12b8546b67672ee38ac87bea491ac94a587f7cc",
"type": "commit"
}
}
]`))

httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/checkout/commits/v4",
httpmock.NewStringResponder(200, `c12b8546b67672ee38ac87bea491ac94a587f7ch`))

httpmock.RegisterResponder("GET", "https://api.github.com/repos/actions/checkout/git/matching-refs/tags/v4.",
httpmock.NewStringResponder(200,
`[
{
"ref": "refs/tags/v4.5.6",
"object": {
"sha": "c12b8546b67672ee38ac87bea491ac94a587f7sh",
"type": "commit"
}
}
]`))

httpmock.RegisterResponder("GET", "https://api.github.com/repos/rohith/publish-nuget/commits/v2",
httpmock.NewStringResponder(200, `c12b8546b67672ee38ac87bea491ac94a587f7cc`))

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

Expand All @@ -348,6 +380,12 @@ func TestPinActions(t *testing.T) {
}
}

if tt.fileName == "action.yml" {
actionCommitMap = map[string]string{
"actions/checkout@v4": "c12b8546b67672ee38ac87bea491ac94a587f7sh",
}
}

output, gotUpdated, err = PinActions(string(input), tt.exemptedActions, tt.pinToImmutable, actionCommitMap)
if tt.wantUpdated != gotUpdated {
t.Errorf("test failed wantUpdated %v did not match gotUpdated %v", tt.wantUpdated, gotUpdated)
Expand Down
149 changes: 149 additions & 0 deletions testfiles/pinactions/input/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
name: 'Create Component Version Composite Action'
description: 'Create Component Version, Add Version Link, Add Version Files, Add Version Properties'
branding:
icon: 'arrow-up-right'
color: 'purple'
inputs:
component:
description: 'Component Name/ID'
required: true
versionname:
description: 'Component Version Name'
required: true
description:
description: 'Component version description'
required: false
default: 'Created by following GitHub action: Create Component Version Composite Action'
linkName:
description: 'Name of link to add to component version'
required: false
default: 'GitHub Composition Action Link Name'
link:
description: 'Value of the link to add to component version'
required: false
base:
description: 'Local base directory containing files to upload if file upload is required'
required: false
offset:
description: 'Target path offset (the directory in the version files to which these files should be added)'
required: false
include:
description: 'An include file pattern for selecting files to add (may be repeated)'
required: false
exclude:
description: 'An exclude file pattern for excluding files (may be repeated). Overrides includes.'
required: false
saveExecuteBits:
description: 'Saves execute bits for files.'
required: false
type: boolean
versionProperties:
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'
required: false
urlType:
description: 'URL protocol to use to connect to DevOps Deploy hostname'
required: false
default: 'https:'
hostname:
description: 'DevOps Deploy hostname'
required: true
port:
description: 'port'
required: true
default: '8443'
username:
description: 'DevOps Deploy username'
required: false
password:
description: 'DevOps Deploy password'
required: false
secret: true
authToken:
description: 'DevOps Deploy authentication token'
required: false
secret: true
runs:
using: "composite"

steps:
# Download udclient package and command shell scripts from composite action github repository
- uses: actions/checkout@v4
with:
repository: jwcarmichael12/test-composite-action
sparse-checkout: |
artifacts/devops-deploy-client.zip
scripts/addVersionFiles.sh
scripts/addVersionProperties.sh
sparse-checkout-cone-mode: false
path: deploy

# Setup java environment needed to run the udclient
- uses: actions/setup-java@v4
with:
distribution: 'adopt-openj9' # See 'Supported distributions' for available options
java-version: '11'

# Expand the udclient package to access the executable
- name: Install udclient
id: udclient-install
run: unzip deploy/artifacts/devops-deploy-client.zip
shell: bash

# Setup global environment variables used by udclient
- name: Set global environment variables.
id: set-env-variables
run: |
if [ ! -z ${{ inputs.authtoken }} ]; then echo "DS_AUTH_TOKEN=${{ inputs.authtoken }}" >> $GITHUB_ENV; fi &&
if [ ! -z ${{ inputs.username }} ]; then echo "DS_USERNAME=${{ inputs.username }}" >> $GITHUB_ENV; fi &&
if [ ! -z ${{ inputs.password }} ]; then echo "DS_PASSWORD=${{ inputs.password }}" >> $GITHUB_ENV; fi &&
echo "DS_WEB_URL=${{ inputs.urlType }}//${{ inputs.hostname }}:${{ inputs.port }}" >> $GITHUB_ENV
shell: bash

# Create component version
- name: Create component version
id: create-component-version
run: udclient/udclient createVersion -component "${{ inputs.component }}" -name "${{ inputs.versionname }}" -description "${{ inputs.description }}" --importing true
shell: bash

# Add Version Link if one is specified
- name: Add component version link
id: add-version-link
if: inputs.linkName && inputs.link
run: udclient/udclient addVersionLink -component "${{ inputs.component }}" -version "${{ inputs.versionname }}" -linkName "${{ inputs.linkName }}" -link "${{ inputs.link }}"
shell: bash

# Add Version Files if specified
- name: Add component version files
id: add-version-files
if: ${{ inputs.base }}
run: |
deploy/scripts/addVersionFiles.sh
shell: bash
env:
FILES_CMD: "udclient/udclient"
FILES_COMPONENTNAME: "${{ inputs.component }}"
FILES_VERSIONNAME: "${{ inputs.versionname }}"
FILES_BASE: "${{ inputs.base }}"
FILES_OFFSET: "${{ inputs.offset }}"
FILES_INCLUDE: "${{ inputs.include }}"
FILES_EXCLUDE: "${{ inputs.exclude }}"
FILES_SAVEEXECUTEBITS: ${{ inputs.saveExecuteBits }}

# Add Version Properties if specified
- name: Add component version properties
id: add-version-properties
if: ${{ inputs.versionProperties }}
run: |
deploy/scripts/addVersionProperties.sh
shell: bash
env:
VERSION_PROPERTIES_CMD: "udclient/udclient"
VERSION_PROPERTIES_COMPONENTNAME: "${{ inputs.component }}"
VERSION_PROPERTIES_VERSIONNAME: "${{ inputs.versionname }}"
VERSION_PROPERTIES: "${{ inputs.versionProperties }}"

# Tell devops deploy server that component version creation is complete
- name: Finish Importing
id: finish-importing
run: udclient/udclient finishedImporting -component "${{ inputs.component }}" -version "${{ inputs.versionname }}"
shell: bash
Loading