Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composite Actions Support ADR #1144

Merged
merged 6 commits into from
Jul 27, 2021
Merged

Composite Actions Support ADR #1144

merged 6 commits into from
Jul 27, 2021

Conversation

thboop
Copy link
Collaborator

@thboop thboop commented Jun 10, 2021

Rendered
We welcome community feedback, please feel free to comment on this ADR!

This ADR outlines the next steps we are taking to change composite run steps into composite actions, and specifically mentions how certain scenarios will work.


- `if`, `continue-on-error`, `timeout-minutes` could be supported in composite run/uses steps. These values were not originally supported in our composite run steps implementation.
- Browsing the community forums and runner repo, there hasn't been a lot of noise asking for these features, so we will hold off on them.
- These values passed as input into the composite action will **not** be carried over as input into the individual steps the composite action runs.
Copy link
Collaborator

@ericsciple ericsciple Jun 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will be more interesting after we support all action types. +1 to keep current work scoped to supporting all action types. Then composite will be more useful.

These will be required in the future as we push on composite actions to be a general step-reuse mechanism.

- We could make it optional in composite run steps, and follow the trend of workflow run steps and add the `defaults` functionality
- However, encouraging action authors to explicitly state the `shell` forces them to do the right thing regarding considering cross platform support.
- We can change this in the future as needed, but for now its largely a convenience feature that adds no new functionality, let's wait on more user feedback before we consider this further.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a section to discuss the behavior for local actions. Assuming it's supported with the same limitations today (or we could be more strict). Today local actions don't support pre (ignored)

@bewuethr
Copy link

bewuethr commented Jun 14, 2021

As per your comment, adding a use case: I'm working on an action that regularly pulls data from a Slack workspace, processes it and posts some graphs and statistics.

Optionally, it can also send a Telegram message, for which I use another existing action. I have to describe how to use this other action and have my own action produce output that can be used with it so users can set up a workflow – if I were able to just integrate the other action, and it looks I will be, then I could hide all the complexity and achieve the same result with just my action having to be set up.

In other words: this look great, looking forward to using it 🙂

@afirth
Copy link

afirth commented Jun 14, 2021

adding a use case:
lots of microservices deployed to k8s, one per namespace. finally they all deploy with the same set of commands, basically AWS login, asdf install, skaffold deploy. This workflow is then duplicated into every single repo.

same story for linting rules + actions (identical in every go repo, for example).

currently using a fork of https://github.com/varunsridharan/action-github-workflow-sync to distribute actions to each repo. effective secret handling is a must, as well as caching and log visibility. TBH the current setup is not bad, just clunky and has no versions/releases


### If, continue-on-error, timeout-minutes - Not being considered at this time

- `if`, `continue-on-error`, `timeout-minutes` could be supported in composite run/uses steps. These values were not originally supported in our composite run steps implementation.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to prioritize the if keyword sooner rather than later. Feedback from @david-gang

I see that if is not planned to be supported. This could be helpful for us. for example if i have two workflows which are nearly identical but have a big chunk of identical steps. part of the steps contain if clauses. Now i can't unite them in one action and call this action from both workflows. If the if would be supported i could do it. I know that i can use a javascript action but why would i need javascript if it is just a number of bash steps.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


### Pre/Post Steps in nested Actions

- We do not plan on adding the ability to configure a customizable pre or post step for composite actions at this time. However, we will execute the pre and post steps of any actions referenced in a composite action.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback from @david-gang regarding this. Could be very helpful for cleanup scenarios. The workaround is writing a JS/container action to reference from your composite action, but that isn't ideal.

The google cli needs a service account json. I have this json stored in a secret. so i want to write a composite action which writes a secret to a file and a post step where the file is deleted. This is not possible without post build step.

@rnett
Copy link

rnett commented Jun 14, 2021

I have a use case that really wants an explicit post step: caching. I have a fairly complicated gradle cache setup, with post-steps requiring ifs (i.e. stopping the gradle daemon, deleting lock files. There are OS-specific parts, thus the ifs). See here. Ideally I would like to move the Java setup and gradlew chmods into the composite action as well, but they would need to be disable-able (more ifs).

@timharris777
Copy link

Very exciting! Our engineers have been waiting for this for a while now. We would be happy to test any early releases on our self-hosted runners and provide feedback if needed. Thanks again!

@joeyparrish
Copy link

This seems like an area rife with potential for supply chain attacks.

Today, if my workflow depends on action A/B@v1, I can be certain that v1 will not change. If I want to review the safety of that action, I can do it once per upgrade that I choose to undertake.

However, if action A/B@v1 could depend on action C/D@main, and C/D is taken over by a malicious actor, my workflow could be compromised without any changes on my end. This could lead to stolen secrets and further malicious changes to my repo.

Before actions gain the capability to include other actions, please spend some time considering the potential for attacks through indirect dependencies, and please outline steps GitHub would take to mitigate that potential.

For example, here's an idea that could help: Require workflows to opt-in to indirect dependencies, or to allow-list specific indirect dependencies and versions. This would help by keeping workflow maintainers aware of what they are consuming.

@umarcor
Copy link

umarcor commented Jun 17, 2021

@joeyparrish isn't that a general concern with consuming any tool, since most software tools nowadays have dependencies? I can't see why composite actions increase the attack surface compared to consuming Actions. In fact, people who want to provide the features of composite actions can already achieve it if they use the Actions without the uses field (which is just syntactic sugar). Overall, I share your concern with security, but I don't see what specific steps you expect.

@rnett
Copy link

rnett commented Jun 17, 2021

What if instead of requiring opt-in for indirect dependencies you required an opt-in for non-static (i.e. non-tag) versions? Think "Composite action A depends on action B@main, which is a non static version. You must specify a version of B to use using the versions block." It would prevent silent updates, which I agree is necessary, but without as much effort.

@kuhnroyal
Copy link

Today, if my workflow depends on action A/B@v1, I can be certain that v1 will not change.

@joeyparrish As far as I can tell that is not true in practice. A lot of actions re-tag their smaller updates based on semver so you don't even notice the action has changed. And if the A action is compromised then a malicious actor can also just change the v1 tag and you don't notice it.

I agree with your points, but the problem actually starts with the first action you use.

@umarcor
Copy link

umarcor commented Jun 17, 2021

To build on what @kuhnroyal said, having "moving tags" is the officially recommended approach by Github (Actions). A lof of actions re-tag their smaller updates because of a lack of proper semver support. This is also related to the recommendation of adding node_modules to the repo, for all JavaScript Actions. In an attempt to make it as simple as possible for unexperienced users, several bad practices are recommended as the first option. See:

Many of us would be really happy with semver being understood by GitHub Action's parser/interpreter. Having the same syntax as npm or yarn seems sensible, since the ecosystem is mainly based on JS.

@joeyparrish
Copy link

You are all correct. Supply chain attacks can occur through node module deps and tag rewrites just as well as the scenario I suggested.

Perhaps I had too narrow a view and chose a poor venue. Is there a better place to discuss these potential security issues?

@umarcor
Copy link

umarcor commented Jun 18, 2021

@joeyparrish what about asking the feature to be integrated into dependabot and the regular security updates that GitHub suggests? Currently, it scans setup.py , package.jon, go.mod, etc. depending on the languages/package managers used in your repo. The uses fields of the workflows might be considered "another dependency declaration format". Then, the inheritance, notifying about vulnerabilities deep downstream, etc. should reuse the existing plumbing. When that's in place, users might be allowed to "automatically disable the workflows which have some severity level above a given type".

@thboop
Copy link
Collaborator Author

thboop commented Jun 18, 2021

However, if action A/B@v1 could depend on action C/D@main, and C/D is taken over by a malicious actor, my workflow could be compromised without any changes on my end. This could lead to stolen secrets and further malicious changes to my repo.

For third party actions, we recommend pinning to a sha. We will update this guidance to recommend you do that for actions inside a composite action as well. This mitigates the supply-chain attack.

allow-list specific indirect dependencies and versions.

Good news on this front! Composite actions will also respect the actions policies, so you can state which actions you want to be runnable in your org/repo/enterprise. This allows you to specify specific tags or sha's.

isn't that a general concern with consuming any tool, since most software tools nowadays have dependencies? I can't see why composite actions increase the attack surface compared to consuming Actions

Javascript actions have the same issue when pinning to a major version tag, if a node module updates and is compromised, you would end up having your action compromised. As posted above pinning to a sha mitigates this.

Perhaps I had too narrow a view and chose a poor venue. Is there a better place to discuss these potential security issues?

We have a bug bounty program, please submit security vulnerabilities there!

feature to be integrated into dependabot and the regular security updates that GitHub suggests?

Great idea, Let me dig and see if we can get dependabot to do automatic updates for the action.yaml file as well. I'll loop back on this.

having "moving tags" is the officially recommended approach by Github (Actions)

That is correct, but we hope to improve this solution in the future. However, it's out of scope for this feature.

### If, continue-on-error, timeout-minutes - Not being considered at this time

- `if`, `continue-on-error`, `timeout-minutes` could be supported in composite run/uses steps. These values were not originally supported in our composite run steps implementation.
- Browsing the community forums and runner repo, there hasn't been a lot of noise asking for these features, so we will hold off on them.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add a usecase, I think some of the early composite actions we'll see will be wrappers around cache + dependency management.

One of the things we do is to conditionally run npm install depending on if we get a cache hit against the os + node + npm versions, which saves us around 1:30m ontop of the standard npm caching.

This is a lot of boilerplate (especially if you adhere to one task per job/workflow), and easy to get wrong, so conditional support would be massive for this usecase, to avoid the complexities of having to write a full action for what should essentially be a composition of actions.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two more important use cases for if are executing steps only if appropriate in context (i.e. depending on the action's inputs) and capturing details of a failure. I wrote this just now assuming it would work, and was sad when it didn't:

   - name: Build
      shell: bash
      if: inputs.build_targets != 'skip'
      run: make $BUILD_TARGETS
      env:
        CC: ${{ inputs.compiler }}
        BUILD_TARGETS: ${{ inputs.build_targets }}

    - name: Test
      shell: bash
      if: inputs.test_targets != 'skip'
      run: make $TEST_TARGETS
      env:
        CC: ${{ inputs.compiler }}
        TEST_TARGETS: ${{ inputs.test_targets }}

    - name: Failure details
      shell: bash
      if: ${{ failure() }}
      run: |
        dump_log () {
          if [ -s "$1" ]; then
            echo "::group::$1"
            echo '::stop-commands::resume-commands-50YEO1zJ8HSXH4Zy'
            cat "$1"
            echo '::resume-commands-50YEO1zJ8HSXH4Zy::'
            echo '::endgroup::'
          fi
        }
        dump_log config.log
        for ts in $(find . -name 'test-suite*.log' -printf '%P\n'); do
          dump_log "$ts"
        done

It would work (with slight adjustments) in a workflow file, but then I'd need, let's see, at least six copies of it.

@jescalan
Copy link

jescalan commented Jul 8, 2021

I just want to say that this is awesome and I am very excited to use this feature! 💖

@thboop thboop marked this pull request as ready for review July 27, 2021 00:49
@thboop thboop requested a review from a team as a code owner July 27, 2021 00:49
ericsciple
ericsciple previously approved these changes Jul 27, 2021
@thboop thboop merged commit 5281434 into main Jul 27, 2021
@thboop thboop deleted the thboop/CompositeAdr branch July 27, 2021 13:29
@thboop
Copy link
Collaborator Author

thboop commented Aug 18, 2021

Uses steps are live on dotcom, for more information please see: https://github.com/actions/runner/blob/main/docs/adrs/1144-composite-actions.md

@figadore
Copy link

figadore commented Aug 19, 2021

Uses steps are live on dotcom

What does this mean? I thought it meant it was ready for use, but I'm still seeing

Required property is missing: shell

when I try to do things like this

runs:
  using: "composite"
  steps:
    - name: Checkout
      uses: actions/checkout@master

Edit: never mind, I got it working. Turns out it was a different step that was missing the shell key 🤦

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.