2017-03-09 15:12:01 -05:00
|
|
|
# Overview - Security model
|
|
|
|
|
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
Whenever a PR job is run on CircleCI, we want to build `angular.io` and host the build artifacts on
|
2017-03-09 15:12:01 -05:00
|
|
|
a publicly accessible server so that collaborators (developers, designers, authors, etc) can preview
|
|
|
|
the changes without having to checkout and build the app locally.
|
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
This document discusses the security considerations associated with moving build artifacts as
|
|
|
|
part of the CI process and serving them publicly.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
|
|
|
|
|
|
|
## Security objectives
|
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
- **Prevent hosting arbitrary content to on servers.**
|
|
|
|
Since there is no restriction on who can submit a PR, we cannot allow arbitrary untrusted PRs'
|
|
|
|
build artifacts to be hosted.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
- **Prevent overwriting other people's hosted build artifacts.**
|
|
|
|
There needs to be a mechanism in place to ensure that the hosted content does indeed correspond
|
2017-03-09 15:12:01 -05:00
|
|
|
to the PR indicated by its URL.
|
|
|
|
|
|
|
|
- **Prevent arbitrary access on the server.**
|
2018-05-09 13:54:53 -04:00
|
|
|
Since the PR author has full access over the build artifacts that would be hosted, we must
|
|
|
|
ensure that the build artifacts will not have arbitrary access to the server or expose sensitive
|
2017-03-09 15:12:01 -05:00
|
|
|
info.
|
|
|
|
|
|
|
|
|
|
|
|
## Issues / Caveats
|
|
|
|
|
|
|
|
- Because the PR author can change the scripts run on CI, any security mechanisms must be immune to
|
|
|
|
such changes.
|
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
- For security reasons, encrypted CircleCI variables are not available to PRs, so we can't rely on
|
2017-03-09 15:12:01 -05:00
|
|
|
them to implement security.
|
|
|
|
|
|
|
|
|
|
|
|
## Implemented approach
|
|
|
|
|
|
|
|
|
|
|
|
### In a nutshell
|
|
|
|
The implemented approach can be broken up to the following sub-tasks:
|
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
0. Receive notification from CircleCI of a completed build.
|
|
|
|
1. Verify that the build is valid and download the artifact.
|
2017-06-18 18:15:07 -04:00
|
|
|
2. Fetch the PR's metadata, including author and labels.
|
|
|
|
3. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
|
|
|
|
4. If necessary, update the corresponding PR's verification status.
|
|
|
|
5. Deploy the artifacts to the corresponding PR's directory.
|
|
|
|
6. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
|
2017-03-09 15:12:01 -05:00
|
|
|
during deployment will remain valid until the artifacts are removed).
|
2018-08-15 08:47:45 -04:00
|
|
|
7. Prevent hosted preview files from accessing anything outside their directory.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
|
|
|
|
|
|
|
### Implementation details
|
|
|
|
This section describes how each of the aforementioned sub-tasks is accomplished:
|
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
0. **Receive notification from CircleCI of a completed build**
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2018-08-15 08:47:45 -04:00
|
|
|
CircleCI is configured to trigger a webhook on our preview-server whenever a build completes.
|
2018-05-09 13:54:53 -04:00
|
|
|
The payload contains the number of the build that completed.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
1. **Verify that the build is valid and download the artifact.**
|
|
|
|
|
|
|
|
We cannot trust that the data in the webhook trigger is authentic, so we only extract the build
|
|
|
|
number and then run a direct query against the CircleCI API to get hold of the real data for
|
|
|
|
the given build number.
|
|
|
|
|
|
|
|
If the build was not successful then we ignore this trigger. Otherwise we check that the
|
|
|
|
associated github organisation and repository are what we expect (e.g. angular/angular).
|
|
|
|
|
|
|
|
Next we make another call to the CircleCI API to get a list of the URLS for artifacts of that
|
|
|
|
build. If there is one that matches the configured artifact path, we download the contents of the
|
|
|
|
build artifact and store it in a local folder. This download has a maximum size limit to prevent
|
2018-08-15 08:47:45 -04:00
|
|
|
PRs from producing artifacts that are so large they would cause the preview server to crash.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
2. **Fetch the PR's metadata, including author and labels**.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
Once we have securely downloaded the artifact for a build, we retrieve the PR's metadata -
|
2017-06-18 18:15:07 -04:00
|
|
|
including the author's username and the labels - using the
|
|
|
|
[GitHub API](https://developer.github.com/v3/).
|
2017-03-09 15:12:01 -05:00
|
|
|
To avoid rate-limit restrictions, we use a Personal Access Token (issued by
|
|
|
|
[@mary-poppins](https://github.com/mary-poppins)).
|
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
3. **Check whether the PR can be automatically verified as "trusted"**.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
"Trusted" means that we are confident that the build artifacts are suitable for being deployed
|
|
|
|
and publicly accessible on the preview server. There are two ways to check that:
|
|
|
|
1. We can verify that the PR has a pre-determined label, which marks it as "safe for preview".
|
|
|
|
Such a label can only have been added by a maintainer (with the necessary rights) and
|
|
|
|
designates that they have manually verified the PR contents.
|
|
|
|
2. We can verify (again using the GitHub API) the author's membership in one of the
|
|
|
|
whitelisted/trusted GitHub teams. For this operation, we need a Personal Access Token with the
|
|
|
|
`read:org` scope issued by a user that can "see" the specified GitHub organization.
|
|
|
|
Here too, we use the token by @mary-poppins.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
4. **If necessary update the corresponding PR's verification status**.
|
|
|
|
|
|
|
|
Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e.
|
|
|
|
whether it is publicly accessible or not), based on the new verification status. For example, if
|
|
|
|
a PR was initially considered "not trusted" but the check triggered by a new build determined
|
2018-08-15 08:47:45 -04:00
|
|
|
otherwise, the PR (and all the previously hosted previews) are made public. It works the same
|
2017-06-18 18:15:07 -04:00
|
|
|
way if a PR has gone from "trusted" to "not trusted".
|
|
|
|
|
|
|
|
5. **Deploy the artifacts to the corresponding PR's directory.**
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
With the preceding steps, we have verified that the build artifacts are valid.
|
|
|
|
Additionally, we have determined whether the PR can be trusted to have its previews
|
2017-06-18 18:15:07 -04:00
|
|
|
publicly accessible or whether further verification is necessary. The artifacts will be stored to
|
|
|
|
the PR's directory, but will not be publicly accessible unless the PR has been verified.
|
|
|
|
Essentially, as long as sub-tasks 1, 2 and 3 can be securely accomplished, it is possible to
|
2018-05-09 13:54:53 -04:00
|
|
|
"project" the trust we have in a team's members through the PR to the build artifacts.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
6. **Prevent overwriting previously deployed artifacts**.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
In order to enforce this restriction (and ensure that the deployed artifacts' validity is
|
2018-05-09 13:54:53 -04:00
|
|
|
preserved throughout their "lifetime"), the server that handles the artifacts (currently a Node.js
|
|
|
|
Express server) rejects builds that have already been handled.
|
|
|
|
_Note: A PR can contain multiple builds; one for each SHA that was built on CircleCI._
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2018-08-15 08:47:45 -04:00
|
|
|
7. **Prevent hosted preview files from accessing anything outside their directory.**
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2018-08-15 08:47:45 -04:00
|
|
|
Nginx (which is used to serve the hosted preview) has been configured to not follow symlinks
|
|
|
|
outside of the directory where the preview files are stored.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
|
|
|
|
|
|
|
## Assumptions / Things to keep in mind
|
|
|
|
|
2018-05-09 13:54:53 -04:00
|
|
|
- Other than the initial webhook trigger, which provides a build number, all requests for data come
|
2018-08-15 08:47:45 -04:00
|
|
|
from the preview-server making requests to well defined API endpoints (e.g. CircleCI and Github).
|
|
|
|
This means that any secret access keys need only be stored on the preview-server and not on any of
|
2018-05-09 13:54:53 -04:00
|
|
|
the CI build infrastructure (e.g. CircleCI).
|
|
|
|
|
2018-08-15 08:47:45 -04:00
|
|
|
- Each trusted PR author has full control over the content that is hosted as a preview for their PRs.
|
|
|
|
Part of the security model relies on the trustworthiness of these authors.
|
2017-03-09 15:12:01 -05:00
|
|
|
|
2018-08-15 08:47:45 -04:00
|
|
|
- Adding the specified label on a PR to mark it as trusted, gives the author full control over
|
|
|
|
the content that is hosted for the specific PR preview (e.g. by pushing more commits to it).
|
|
|
|
The user adding the label is responsible for ensuring that this control is not abused and that
|
|
|
|
the PR is either closed (one way of another) or the access is revoked.
|