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

Add runner-level config to enforce PR security #783

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ashb
Copy link

@ashb ashb commented Nov 2, 2020

This closes #494 at the runner level to give "just enough protection"
to allow using self-hosted runners with public repos.

This PR could do with a bit of a tidy up (some strings should likely become constants/enums, and a few things should possibly be added as methods on the GitHubContext class) but I wanted to get a 👍 from the approach first.

(Also C# is not my "native" language so there may be some oddities in here)

By default, the current behaviour is unchanged -- all jobs passed to the runner are executed.

If the .runner config file has this block added to it:

  "pullRequestSecurity": {}

Then by only PRs from "CONTRIBUTORS" (as defined by the field in https://docs.github.com/en/free-pro-team@latest/graphql/reference/objects#pullrequest -- nothing for us to have to work out ourselves.)

It is also possible to explicitly list users that are allowed to run jobs on this worker:

  "pullRequestSecurity": {
    "allowedAuthors": ["ashb"]
  }

Or to only allow the given users, but not all contributors:

  "pullRequestSecurity": {
    "allowContributors": false,
    "allowedAuthors": ["ashb"]
  }

Owners of the repo are always allowed to run jobs.

@ashb
Copy link
Author

ashb commented Nov 2, 2020

@chrispat Tagging you since you commented on #494.

I agree that solving this at the GitHub level would be a better solution, but on apache/airflow we are struggling without being able to use self-hosted runners (we share an actions queue with every other Apache Software Foundation project using Github, so leads to rather unpredictable and uncontrollable queue lengths.)

So here is a "maybe good enough approach" -- happy to redesign things/rework as needed, but I think this is a good stop-gap measure.

Thoughts?

@ashb
Copy link
Author

ashb commented Nov 2, 2020

(Oh and of course this will need tests adding.)

@ashb
Copy link
Author

ashb commented Nov 3, 2020

Question: should these security settings also apply to branch builds?

@pietroalbini
Copy link

This is similar to what we're doing to use self-hosted runners in the Rust project. Our current solution of using a fork with a patch similar to this is far from ideal, as we need to be quick to rebase our changes on top of the latest releases before the runner stops working due to the auto-update being prevented. Not having to rebase the fork every week or two by having a patch land in the runner would be awesome.

@ashb
Copy link
Author

ashb commented Nov 10, 2020

@pietroalbini Would love to collaborate on this. Does this PR do everything you need for Rust?

@pietroalbini
Copy link

We don't plan to execute any pull request build on self-hosted runners, so a toggle to just reject all PRs (even if those are sent by owners) would be best for Rust. For reference this is the patch on our fork we're currently running in prod.

@ashb
Copy link
Author

ashb commented Nov 10, 2020

Gotcha, yes that makes sense. I'll roll that in to my PR.

@hross
Copy link
Contributor

hross commented Nov 25, 2020

This has been open a while as a draft (and original issue) so I thought I would add some commentary here so you know that we are talking/thinking about this. A couple of thoughts:

  • Practically speaking, I like this approach because it is simple and accomplishes something nice for a class of customer/use case.
  • I agree with @chrispat that we should address this on the service, but there is obviously a much higher barrier there (UI, experience, etc).
  • If we ship a feature like this, with a config variable, in the runner we are locked into supporting the data contract... forever, for all intents and purposes (if this ships to an on premises customer, for instance, there is a long support timeline). So we have to be very deliberate about what contracts we introduce, even though introducing them is pretty easy.
  • This might be lulling you into some level of false security, since the runner config itself could be modified by some malicious entity and then used to run PRs that shouldn't be run, unsuspectingly. If these settings are stored at the server level there is no local way to modify them and would close this vector of attack.

So TLDR for now is I don't think we would merge this, but this is a great concept that we will talk about internally and see how we can tackle a better experience.

I also want to mention we really appreciate your contribution and willingness to dig into the code so please don't take us not merging the PR as a reason to not continue to push on interesting ideas like this.

@pietroalbini
Copy link

pietroalbini commented Nov 25, 2020

Thanks for looking into this @hross!

This might be lulling you into some level of false security, since the runner config itself could be modified by some malicious entity and then used to run PRs that shouldn't be run, unsuspectingly. If these settings are stored at the server level there is no local way to modify them and would close this vector of attack.

I'm not sure that's actually a threat this PR is meant to solve. My understanding is, this PR would successfully prevent people without write access to the repository from ever submitting a build to the runner, so the only way the configuration change could happen is if the malicious entity already has write access to the repo (which allows them to just push a branch instead of opening PRs).

Also, if the runner is executed on a persistent VM then a malicious entity with the ability to change the configuration could also reconfigure the runner to point to a different repository without the server side limitation. If the runner is executed on an ephemeral VM instead, the configuration change wouldn't matter as the change wouldn't persist across executions.

@hross
Copy link
Contributor

hross commented Nov 25, 2020

You are totally right about this, of course (and all of your other comments):

Also, if the runner is executed on a persistent VM then a malicious entity with the ability to change the configuration could also reconfigure the runner to point to a different repository without the server side limitation. If the runner is executed on an ephemeral VM instead, the configuration change wouldn't matter as the change wouldn't persist across executions.

It's more about what does an inexperienced user think about a "security setting" and do they understand the implications of what you're explaining? Do we design for that (ideally yes). In any case it's something we have to think about before we'd release a setting like this as "official".

@pietroalbini
Copy link

pietroalbini commented Nov 25, 2020

It's more about what does an inexperienced user think about a "security setting" and do they understand the implications of what you're explaining? Do we design for that (ideally yes). In any case it's something we have to think about before we'd release a setting like this as "official".

I see your point!

A possible solution would be to call this a "filter" instead of a security feature, so that people who stumble into it without any extra context won't be misled by its name. Then, when the feature is documented on docs.github.com some extra context can be added there to explain what this feature does and doesn't protects against.

I thought about possible designs for the configuration with the new "filter" naming:

  • A simpler one which only allows choosing between everyone, collaborators or nobody:

    {
      "pullRequestsFilter": "allowFromEveryone|allowFromCollaborators|reject"
    }
  • A more complete but potentially more confusing one, which allows to configure more complex filters:

    {
      "pullRequests": {
        "action": "allow|filter|reject",
        "filters": {
          "collaborators": true,
          "authors": ["pietroalbini", "ashb"]
        }
      }
    }

I'm also really happy to collaborate with y'all to brainstorm possible documentation

@ashb
Copy link
Author

ashb commented Jan 12, 2021

Having now started using this "in anger" the current config scheme I have in this PR is clearly not right.

@ashb ashb force-pushed the pr-security-options branch 2 times, most recently from 30ad312 to f326681 Compare January 15, 2021 09:57
@ashb
Copy link
Author

ashb commented Jan 17, 2021

I've just run in to a less-than ideal behaviour (which is likely only a problem for the apache org and any other umbrella orgs): the MEMBER author_association wins out over the contributor (or more accurately, my user is not a a contributor but a member)

This means that for us, it would allow every apache author to be able to run on our self-hosted runners, which isn't what we want -- just anyone able to commit to the repo is what we are aiming for.

@ashb ashb force-pushed the pr-security-options branch 4 times, most recently from d395557 to bed3b45 Compare January 24, 2021 09:56
@ashb ashb force-pushed the pr-security-options branch 3 times, most recently from 07c0b78 to bdd9830 Compare January 31, 2021 09:26
@jens-maus
Copy link

jens-maus commented Feb 1, 2021

@ashb I have followed this PR here and find your enhancement to the actions/runner very useful. It is really a pity that GitHub seems to ignore it so far because I think it would be great if they could add some more security for using self-hosted runners also for public repos. And your PR seem to provide some quite straight forward way of securing the use of self-hosted runners in public repos and I still hope GitHub will once pickup your idea and implement it because we really need some additional level of security here for using self-hosted runners also in public repos!

Nevertheless, do you have any resource where you publish own/separate builds of actions/runner with your PR modification? This would allow to use these instead of the GitHub version of actions/runner which is know to be prone to the pull-request security issue we all know.

@ashb
Copy link
Author

ashb commented Feb 1, 2021

Nevertheless, do you have any resource where you publish own/separate builds of actions/runner with your PR modification? This would allow to use these instead of the GitHub version of actions/runner which is know to be prone to the pull-request security issue we all know.

My fork is set up with automation (GitHub actions of course) to automatically build a release with my changes incorporated when ever a release is published in the upstream repo https://github.com/ashb/runner/releases

@jens-maus
Copy link

That's great! And if using your runner instead of the standard github one it will also automatically update itself then from your fork and not switch back to the official one?

@ashb
Copy link
Author

ashb commented Feb 1, 2021

That's great! And if using your runner instead of the standard github one it will also automatically update itself then from your fork and not switch back to the official one?

No, not yet sadly. I haven't worked out the message/mechanism that GitHub use to out that message (or the contents of that message) so I cant "redirect" the update.

@jens-maus
Copy link

No, not yet sadly. I haven't worked out the message/mechanism that GitHub use to out that message (or the contents of that message) so I cant "redirect" the update.

That's indeed sad. I also couldn't quickly grep for the update url or similar in the runner source code. Too bad that GitHub isn't providing more documentation on the actual runner implementation. Do you know at least of a way to disable the automatic update? Because otherwise changes are high that if I run your PR secured runner variant it will be automatically updated to the official runner once github released a new one. And this would remove the additional PR security of your work...

@ashb ashb force-pushed the pr-security-options branch 2 times, most recently from ebac631 to 42aaf7b Compare July 8, 2023 09:05
@ashb ashb force-pushed the pr-security-options branch 2 times, most recently from 5495e71 to 634127d Compare July 18, 2023 09:05
@ashb ashb force-pushed the pr-security-options branch 3 times, most recently from f9ebe67 to 0ffde16 Compare July 26, 2023 09:05
@ashb ashb force-pushed the pr-security-options branch 3 times, most recently from ee7a966 to b89c5fa Compare August 3, 2023 09:05
@ashb ashb force-pushed the pr-security-options branch 4 times, most recently from ccd14e2 to 678f186 Compare August 11, 2023 09:05
@ashb ashb force-pushed the pr-security-options branch 4 times, most recently from b270539 to ef89e5a Compare August 17, 2023 09:05
@ashb ashb force-pushed the pr-security-options branch 3 times, most recently from e4f33ba to 6057f98 Compare August 26, 2023 09:04
@ashb ashb force-pushed the pr-security-options branch 5 times, most recently from 53963f9 to e292d48 Compare September 8, 2023 09:05
This allows self-hosted runners to limit who can run jobs to give "just
enough protection" to allow using self-hosted runners with public repos.

By default, the current behaviour is unchanged -- all jobs passed to the
runner are executed.

If the `.runner` config file has this block added to it:

```
  "pullRequestSecurity": {}
```

Then by only PRs from "CONTRIBUTORS" and "OWNERS" can run (as defined by
the field in
https://docs.github.com/en/free-pro-team@latest/graphql/reference/objects#pullrequest
-- nothing for us to have to work out ourselves.)

It is also possible to explicitly list users that are allowed to run
jobs on this worker:

```
  "pullRequestSecurity": {
    "allowedAuthors": ["ashb"]
  }
```

Or to _only_ allow the given users, but not all contributors:

```
  "pullRequestSecurity": {
    "allowContributors": false,
    "allowedAuthors": ["ashb"]
  }
```

Owners of the repo are always allowed to run jobs.

If an allowed user pushes a commit to a PR from a non-allowed user, than
_that_ build will be allowed to run on the self-hosted runner.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
8 participants