docs: document security release process

This commit is contained in:
Tomas Vik 2022-07-12 06:25:28 +00:00
parent b98db52416
commit 92b115f9a8
2 changed files with 205 additions and 0 deletions

104
docs/security_releases.md Normal file
View File

@ -0,0 +1,104 @@
# Security Releases
This guide is based on the main [`gitlab-org/gitlab` security release process](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/developer.md)
## DO NOT PUSH TO `gitlab-org/cli`
As a developer working on a fix for a security vulnerability, your main concern is not disclosing the vulnerability or the fix before we're ready to publicly disclose it.
To that end, you'll need to be sure that security vulnerabilities are fixed in the [Security Repo](https://gitlab.com/gitlab-org/security/cli).
This is fundamental to our security release process because the [Security Repo](https://gitlab.com/gitlab-org/security/cli) is not publicly-accessible.
## Process
A security fix starts with an issue identifying the vulnerability. In this case, it should be a confidential issue on the `gitlab-org/cli` project on [GitLab.com](https://gitlab.com/)
Once a security issue is assigned to a developer, we follow the same merge request and code review process as any other change, but on the [Security Repo](https://gitlab.com/gitlab-org/security/cli).
### Schema
```mermaid
graph TD;
A[run security-harness] --> B[prepare branches]
B --> C[MR to the release branch]
C --> D[MR to the main]
D --> E[tag, release and validate]
E --> F[push changes to the regular repo]
```
### Preparation
Before starting, add the new `security` remote on your local GitLab repository:
```sh
git remote add security git@gitlab.com:gitlab-org/security/cli.git
```
Finally, run the `scripts/security_harness` script. This script will install a Git `pre-push` hook that will prevent pushing to any remote besides `gitlab.com/gitlab-org/security`, in order to prevent accidental disclosure.
Please make sure the output of running `scripts/security-harness` is:
```
Pushing to remotes other than gitlab.com/gitlab-org/security has been disabled!
```
### Request CVE number
For exploitable security issues, request a CVE number by [creating an issue in `gitlab-org/cves` project](https://gitlab.com/gitlab-org/cves/-/issues/new). **You can do the release before the CVE number is available.** When the CVE number is assigned, add it to the changelog entry[^1].
Example CVE request: https://gitlab.com/gitlab-org/cves/-/issues/21
### Branches
The main objective is to release the security fix as a patch of the latest production release and backporting this fix on `main`.
#### Patch release branch
Before starting the development of the fix, create a branch from the latest released tag. You can see the latest released tag on the [releases page](https://gitlab.com/gitlab-org/cli/-/releases). For example, if the latest release has a tag `v2.2.0` create a branch `security-2-2`. This is going to be the target of the security MRs. Push the branch to the security repo.
```sh
git checkout v2.2.2
git checkout -b security-2-2
git push security security-2-2
```
#### Security fix branch
Your fix is going to be pushed into `security-<issue number>` branch. If you work on issue #9999, you push the fix into `security-9999` branch.
```sh
git checkout security-2-2
git checkout -b security-9999
git push security security-9999
```
### Development
Here, the process diverges from the [`gitlab-org/gitlab` security release process](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/developer.md).
1. **Before developing the fix, make sure that you've already run the `scripts/security-harness` script.**
1. Implement the fix and push it to your branch (`security-9999` for issue #9999).
1. Create an MR to merge `security-9999` to the patch release branch (`security-2-2`) and get it reviewed.
1. Merge the fix (make sure you squash all the MR commits into one).
### Release the change
Follow the [regular release process](release_process.md) to tag a new patch version on the `security-2-2` branch and release it. Patch release for tag `v2.2.0` would have version and tag `v2.2.1`. Push the tag to the security repo.
Validate that the security issue is fixed in in the released binary.
### Backport the fix to `main`
1. Create an MR to merge the security branch `security-2-2` to `main`. Don't squash commits. Delete source branch.
1. In the MR description, add references to all the reviewed MRs that were merged into `security-2-2`.
1. Review the MR and merge it. You don't need an additional reviewer because all of the changes have been reviewed.
Example in VS Code extension: https://gitlab.com/gitlab-org/security/gitlab-vscode-extension/-/merge_requests/8, TODO: replace with example from CLI
## Push changes back to the [CLI Repo](https://gitlab.com/gitlab-org/cli)
1. Push the patch tag to the [Cli Repo](https://gitlab.com/gitlab-org/cli)
1. Merge the [Security Repo](https://gitlab.com/gitlab-org/security/cli) `main` branch with the [Cli Repo](https://gitlab.com/gitlab-org/main) `main` and push to Cli Repo
[^1]: Example from VS Code Extension project: [changelog entry](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/main/CHANGELOG.md#security).

101
scripts/security-harness Executable file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# taken from https://gitlab.com/gitlab-org/gitaly/-/blob/master/scripts/security-harness
require 'digest'
require 'fileutils'
if ENV['NO_COLOR']
SHELL_RED = ''
SHELL_GREEN = ''
SHELL_YELLOW = ''
SHELL_CLEAR = ''
else
SHELL_RED = "\e[1;31m"
SHELL_GREEN = "\e[1;32m"
SHELL_YELLOW = "\e[1;33m"
SHELL_CLEAR = "\e[0m"
end
HOOK_PATH = File.expand_path("../.git/hooks/pre-push", __dir__)
HOOK_DATA = <<~HOOK
#!/usr/bin/env bash
set -e
url="$2"
harness=`dirname "$0"`/../security_harness
if [ -e "$harness" ]
then
if [[ "$url" != *"gitlab-org/security/"* ]]
then
echo "Pushing to remotes other than gitlab.com/gitlab-org/security has been disabled!"
echo "Run scripts/security-harness to disable this check."
echo
exit 1
fi
fi
HOOK
def write_hook
FileUtils.mkdir_p(File.dirname(HOOK_PATH))
File.open(HOOK_PATH, 'w') do |file|
file.write(HOOK_DATA)
end
File.chmod(0755, HOOK_PATH)
end
# Toggle the harness on or off
def toggle
harness_path = File.expand_path('../.git/security_harness', __dir__)
if File.exist?(harness_path)
FileUtils.rm(harness_path)
puts "#{SHELL_YELLOW}Security harness removed -- you can now push to all remotes.#{SHELL_CLEAR}"
else
FileUtils.touch(harness_path)
puts "#{SHELL_GREEN}Security harness installed -- you will only be able to push to gitlab.com/gitlab-org/security!#{SHELL_CLEAR}"
end
end
# If we were to change the script and then check for a pre-existing hook before
# writing, the check would fail even if the user had an unmodified version of
# the old hook. Checking previous version hashes allows us to safely overwrite a
# script that differs from the current version, as long as it's an old one and
# not custom.
def previous_version?(dest_sum)
# SHA256 hashes of previous iterations of the script contained in `DATA`
%w[
010bf0363a911ebab2bd5728d80795ed02388da51815f0b2530d08ae8ac574f0
].include?(dest_sum)
end
if !File.exist?(HOOK_PATH)
write_hook
toggle
else
# Deal with a pre-existing hook
source_sum = Digest::SHA256.hexdigest(HOOK_DATA)
dest_sum = Digest::SHA256.file(HOOK_PATH).hexdigest
if previous_version?(dest_sum)
# Upgrading from a previous version, update in-place
write_hook
toggle
elsif source_sum != dest_sum
# Pre-existing hook we didn't create; do nothing
puts "#{SHELL_RED}#{HOOK_PATH} exists and is different from our hook!"
puts "Remove it and re-run this script to continue.#{SHELL_CLEAR}"
exit 1
else
# No hook update needed, just toggle
toggle
end
end