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-09-06 11:28:20 -04:00
- **Prevent hosting arbitrary content on our servers.**
Since there is no restriction on who can submit a PR, we cannot allow arbitrary, untrusted PRs'
2018-05-09 13:54:53 -04:00
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-09-06 11:28:20 -04:00
1. Receive notification from CircleCI of a completed build.
2. Verify that the build is valid and can have a preview.
3. Download the build artifact.
4. Fetch the PR's metadata, including author and labels.
5. Check whether the PR can be automatically verified as "trusted" (based on its author or labels).
6. If necessary, update the corresponding PR's verification status.
7. Deploy the artifacts to the corresponding PR's directory.
8. 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-09-06 11:28:20 -04:00
9. 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-09-06 11:28:20 -04:00
1. **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-09-06 11:28:20 -04:00
2. **Verify that the build is valid and can have a preview.**
2018-05-09 13:54:53 -04:00
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.
2018-09-06 11:28:20 -04:00
We perform a number of preliminary checks:
- Was the webhook triggered by the designated CircleCI job (currently `aio_preview` )?
- Was the build successful?
- Are the associated GitHub organisation and repository what we expect (e.g. `angular/angular` )?
- Has the PR touched any files that might affect the angular.io app (currently the `aio/` or
`packages/` directories, ignoring spec files)?
2018-05-09 13:54:53 -04:00
2018-09-06 11:28:20 -04:00
If any of the preliminary checks fails, the process is aborted and not preview is generated.
3. **Download the build artifact.**
Next we make another call to the CircleCI API to get a list of the URLs for artifacts of that
2018-05-09 13:54:53 -04:00
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
2018-09-06 11:28:20 -04:00
4. **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 )).
2018-09-06 11:28:20 -04:00
5. **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
2020-09-23 16:27:30 -04:00
trusted GitHub teams. For this operation, we need a Personal Access Token with the
2017-06-18 18:15:07 -04:00
`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
2018-09-06 11:28:20 -04:00
6. **If necessary update the corresponding PR's verification status** .
2017-06-18 18:15:07 -04:00
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-09-06 11:28:20 -04:00
otherwise, the PR (and all the previously downloaded 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".
2018-09-06 11:28:20 -04:00
7. **Deploy the artifacts to the corresponding PR's directory.**
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 publicly accessible or whether
further verification is necessary.
2017-03-09 15:12:01 -05:00
2018-09-06 11:28:20 -04:00
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 2, 3, 4 and 5 can be securely
accomplished, it is possible to "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
2018-09-06 11:28:20 -04:00
8. **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-09-06 11:28:20 -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.
2018-05-09 13:54:53 -04:00
_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-09-06 11:28:20 -04:00
9. **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-09-06 11:28:20 -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-09-06 11:28:20 -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.