summaryrefslogtreecommitdiff
path: root/scripts/security-harness
blob: c101cd0345415bc80f9c38dc8295a4fecbabb48c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env ruby

# frozen_string_literal: true

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
  #!/bin/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