gitness
gitness copied to clipboard
GitHub Application support
For a while now I have wanted to enable support for GitHub Applications as an alternative to the current oauth application implementation. The introduction of GitHub Checks, which is only available to GitHub Applications, is increasing the priority of this feature.
Support for GitHub applications will require some changes to how Drone works. Here are some key differences between GitHub applications and oauth applications:
GitHub Apps can look at /installation/repositories to see repositories the installation can access.
GitHub Apps receive webhooks when repositories are added or removed from the installation.
By default, GitHub Apps have a single webhook that receives the events they are configured to receive for every repository they have access to.
GitHub Apps ask for repository contents permission and use your installation token to authenticate via HTTP-based Git.
The 0.9 codebase implements a number of meaningful abstractions that should help simplify the transition to GitHub applications.
Security Considerations
The primary concern I have at this time is with regards to git access and cloning:
GitHub Apps ask for repository contents permission and use your installation token to authenticate via HTTP-based Git. The token is used as the HTTP password.
The challenge here is the clone happens inside the vm. This means we need to send the token down to every agent and inject into the pipeline environment. This token grants access to every repository and registered drone user. If an agent is compromised or the token is exposed, an attacker will potentially have access to significantly more data than they would with our current implementation.
Token Expiration
I should note that the tokens have a 1 hour expiration, however, we can assume that even a few minutes would be enough time for an attacker to clone every repository in the system with the stolen token.
This also brings me to the second design challenge. If the token expires every 1 hour, we need understand the impact of a token expiring midway through build execution. In a multi-stage build we can expect the pipeline to clone a repository or dependency using the token / netrc file many minutes into the build, and need to account for the fact that the token could be expired.
This has me wondering if we need to adjust the current behavior of Drone to use SSH keys to clone the repository, and provide repository owners an option to configure a user token that can be optionally used to clone additional repositories or dependencies. This token would optionally be exposed to the pipeline environment in a netrc file. This makes the exposure of the token opt-in, and prevents us from exposing the GitHub installation token to our agents, minimizing the impact of token theft.
cc @gtaylor @tboerger @metalmatze @tonglil any thoughts?
This makes the exposure of the token opt-in, and prevents us from exposing the GitHub installation token to our agents, minimizing the impact of token theft.
I think that sounds reasonable. For what it's worth, we tend to only build our own private repositories, so our threat profile re: token theft may look a bit different than what you'd be thinking about in SaaS Drone.
The complexity of a token expiring mid build for one hour token expirations is a pretty large edge case?
Although I'm assuming that someone.. somewhere will be running builds like this?
This is a global token and it expires every hour. This means we have to have a strategy for when a build starts just as the token is about to expire (e.g. token expires in 2 minutes, and a build starts just prior to its renewal). This will be exacerbated by the fact that we will begin supporting multi-machine pipeline execution. The clone is re-executed every time a pipeline stage moves to a new machine, which means the clone would be very likely to execute many minutes (maybe ever 60 minutes) into the build. I would therefore anticipate this issue being encountered on a daily basis for many teams, unless we find a way to address.
The above issue is rather minor, however, compared to the fact that we would need to inject this global token into every pipeline. The global token has full access to every user and every repository in the system. As someone who hosts a public Drone instance, this is non-starter for me.
The thought of reverting back to SSH keys for cloning is a bit disappointing, however, they would address both of these issues.
Supporting apps seems to be important to get it properly integrated into github.
For the security perspective I would tend to ssh key or token per repository or maybe even on the organization level?
SSH keys essentially changes the access model from an "all access" user personal-access-token (PAT) to a repo-specific key.
This means private git submodules and other private git repo cloning (npm/go/etc libraries) now will not work without additional authorization or authorized mirror (increased security, prevents laziness).
Could be a bit cumbersome to make modifications in places, but it does ensure that a token in one place does not grant wide access to other resources.
I was hoping that GitHub Apps would have an authorization method to allow each repo to use a dedicated token for that repo. Users can still upload their own PAT to Drone and create a .netrc
file themselves.
Unfortunately the recommended method for cloning with github apps is worse from a security perspective for our particular use case and design constraints, since you have to send the global installation token down to the agent in order to clone the repository. I will probably engage github support before I finalize a decision, just to ensure I am not misunderstanding how this all works.
Could be a bit cumbersome to make modifications in places
I was thinking we could add a dedicated textarea field in the user interface for providing a netrc file. This would add an extra step to the configuration if you clone private dependencies, but perhaps we can simplify the data entry and the runtime netrc file generation. We could also include the ability to provide a global netrc file, similar to global secrets and global registry credentials, to reduce this burden.
Alternatively, the above paragraph could also apply to ssh keys. We could provide the ability to override the deploy key with a custom ssh key that has access to multiple repositories. We could then include the ability to provide global ssh keys.
But yea, this is a classic case of security vs convenience. Unfortunately I am not sure there is a great way to provide both, but I am most certainly open to suggestions and very much hope to be proven wrong :)
Isn't the token for each individual installation per repository? There is an installation Id for each registered repository and a single integration id.
For a very basic setup I needed to store the per installation id for each organisation to generate an access token. So the security issue would be only within different repositories from the same organisation.
since there is api for deploy key, why would we use deploy key for clone? it's readonly access permission.
I wanted to provide another update. Working through most of the issues and making good progress. Here is an update on where things stand.
- I've updated the codebase to request the oauth token right before use, with a forced refresh, so that it can be used for cloning without risk of expiration. Previously mentioned cloning issues are solved.
Here are remaining todos that require implementation:
- Update the webhook endpoint to use a global secret for signature verification, instead of the current per-repository secret. I also need to update the webhook creation function to be a no-op for github applications, since they don't need creation. This one is easy.
- The
/user/repos
endpoint is not enabled for github applications, which means we have no way of asking github "what are all the repositories a user can access". Since drone delegates all access control to GitHub this can be problematic. Per this thread it appears to be on the roadmap.
EDIT1
It looks like we can emulate the behavior of /users/repos
with the following steps:
- get the list of user installations at
/user/installations
- for each installation, get the list of repositories at
/user/installations/:id/repositories
EDIT2
@xhermann sorry to at-mention you. I was able to figure it out :)
Also forgot to ask: Are we using a global webhook instead of per-repo hooks?
@gtaylor yes, planning to use global webhooks. I am hoping this will be supported in the initial implementation, but we could fallback to per-repository webhooks to push out an mvp sooner. TBD
Thanks for implementing and providing this great CI solution. Is there an ETA for this particular feature?
Are there any tasks you could break down for us that need to be implemented before Github App support can continue? I'd be keen to help out with this as it's something my organisation really wants, but it's hard to get started on a new codebase with something this complex (judging from the previous comments it'll be a multi-step process to get this over the finish-line).
Any first steps that we could tackle would be much appreciated!
Any update on this issue? Looking forward for this
Yeah, we are now stuck with an organization not allowed to give us an oauth app. A GitHub app with limited repo access would be the solution.
Looking for the update as well
Revisiting this, it seems like some kind of GH App support was added to the scm
driver here, and the code is integrated with Harness's version of Drone here.
I wonder if there's a way to surface this use here so that non-Harness Drone installations can use it in places like https://github.com/harness/drone/blob/fdf51e70b2b592fbb4d38b31e5ab8add8455fe18/handler/api/user/sync.go#L56?
Or alternatively, could a different scm
driver be created for GHAs..