mirror of https://gitlab.com/gitlab-org/cli.git
docs: document security release process
This commit is contained in:
parent
b98db52416
commit
92b115f9a8
|
@ -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).
|
|
@ -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
|
Loading…
Reference in New Issue