├── README.md ├── bin ├── compile ├── detect └── release └── test ├── id_rsa └── ssh_keys_test.sh /README.md: -------------------------------------------------------------------------------- 1 | # heroku-buildpack-git-deploy-keys 2 | Heroku buildpack to let you add an SSH private key to an heroku app so it can access private GitHub repositories during `bundle install` 3 | 4 | ### Installation 5 | 6 | ### Step 1 7 | Create a ```GIT_DEPLOY_KEY``` environment variable with the private key that you registered on your Heroku 8 | [Heroku Instructions](https://devcenter.heroku.com/articles/config-vars#setting-up-config-vars-for-a-deployed-application) 9 | 10 | I do 11 | 12 | ``` 13 | heroku config:set GIT_DEPLOY_KEY="`cat /path/to/key`" 14 | ``` 15 | 16 | ### Step 2 17 | Optionally configure `GIT_HOST`, `GIT_USER` and `GIT_HOST_HASH`. If not provided, they will default to `github.com`,`git` and github hashes respectively. 18 | 19 | ``` 20 | heroku config:set GIT_HOST=my-git-host.example.com 21 | heroku config:set GIT_USER=git-party 22 | heroku config:set GIT_HOST_HASH="git-host.example.com ssh-rsa AAABBBCCC...CCCXXX" 23 | ``` 24 | 25 | Getting the host key can be a pain so the following is a quick and dirty solution; 26 | 1st backup and clear out your ~/.ssh/known_hosts file, then connect to each host with ssh, which will prompt you with host hash fingerprint. Verify these and accept the connection. 27 | When you're done doing that you can do 28 | 29 | ``` 30 | heroku config:set GIT_HOST_HASH="`cat ~/.ssh/known_hosts`" 31 | ``` 32 | 33 | Then restore your known_hosts backup file. 34 | 35 | ### Step 3 36 | Use this custom repository as custom buildpack for heroku deployment. 37 | This buildpack should be executed first as it takes care of setting up the SSH environment, for accessing private 38 | repos. 39 | 40 | ``` 41 | heroku buildpacks:set --index 1 "https://github.com/siassaj/heroku-buildpack-git-deploy-keys.git#master" 42 | heroku buildpacks:add 'heroku/ruby' 43 | ``` 44 | The `--index 1` tells heroku to run this custom buildpack before other buildpacks. 45 | Read more about using third-party buildpacks in heroku https://devcenter.heroku.com/articles/third-party-buildpacks#using-a-custom-buildpack 46 | 47 | `heroku buildpacks:add 'heroku/ruby'` tells heroku to use the default buildpack for Ruby applications. 48 | Use the appropriate buildpack for your application. 49 | Default buildpacks available in Heroku https://devcenter.heroku.com/articles/buildpacks#officially-supported-buildpacks 50 | 51 | ### Development / Testing 52 | 53 | #### WARNING 54 | Testing on your local machine with the test runner _will_ clobber ~/.ssh/known_hosts and ~/.ssh/private_key file if they exist. I just destroyed my ~/.ssh/id_rsa and ~/.ssh/known_hosts testing this. I renamed the key being used to private_key (also because we can't be sure it'll be an RSA id anyway) so it should be less devastating but you've been warned. Best is probably to chmod everything in ~/.ssh to 0400. 55 | 56 | A great way to test is using Heroku's buildpack test runner. See https://github.com/heroku/heroku-buildpack-testrunner. To set up, run these commands: 57 | 58 | #### Shoutout 59 | This package draws very heavily from 60 | https://github.com/fs-webdev/heroku-buildpack-ssh-keys 61 | That project's gone now, but I'd still like to thank it's main writer Tim Shadel (https://github.com/timshadel) for the work. 62 | -------------------------------------------------------------------------------- /bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | BUILD_DIR="$1" 4 | CACHE_DIR="$2" 5 | ENV_DIR="$3" 6 | SSH_DIR="${HOME}/.ssh" 7 | 8 | # Helper functions 9 | 10 | alert() { 11 | echo "$1" | while IFS= read -r line; do 12 | echo " !!!! $line" 13 | done 14 | } 15 | 16 | arrow() { 17 | echo "$1" | while IFS= read -r line; do 18 | echo ":::::> $line" 19 | done 20 | } 21 | 22 | read_env() { 23 | local env_file="$1" 24 | local env_file_path="${ENV_DIR}/${env_file}" 25 | 26 | if [[ "$env_file" == *"GITHUB"* ]] && [[ -f "$env_file_path" ]]; then 27 | alert "${env_file} is deprecated, please switch to ${env_file/GITHUB/GIT}" 28 | fi 29 | 30 | if [[ -f "$env_file_path" ]]; then 31 | cat "$env_file_path" 32 | fi 33 | } 34 | 35 | fetch_github_hash() { 36 | arrow "GIT_HOST_HASH not set, assuming github hash" 37 | 38 | # GitHub IP range and SSH key 39 | local github_base="github.com" 40 | local github_rsa_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=" 41 | local github_ecdsa_key="ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=" 42 | local github_ed25519_key="sh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl" 43 | 44 | echo "${github_base} ${github_rsa_key} 45 | ${github_base} ${github_ecdsa_key} 46 | ${github_base} ${github_ed25519_key}" 47 | } 48 | 49 | arrow "############################################" 50 | arrow " GIT DEPLOY KEY BUILDPACK " 51 | arrow "############################################" 52 | 53 | arrow "ssh dir is ${SSH_DIR}" 54 | 55 | # Get deploy key from environment 56 | ssh_key=$(read_env 'GIT_DEPLOY_KEY') 57 | key_file_path="${ENV_DIR}/GIT_DEPLOY_KEY" 58 | 59 | if [[ -z "$ssh_key" ]]; then 60 | ssh_key=$(read_env 'GITHUB_DEPLOY_KEY') 61 | key_file_path="${ENV_DIR}/GITHUB_DEPLOY_KEY" 62 | fi 63 | 64 | if [[ -z "$ssh_key" ]]; then 65 | alert "GIT_DEPLOY_KEY not set" 66 | alert " Try \`heroku config:add GIT_DEPLOY_KEY=\`" 67 | exit 1 68 | fi 69 | 70 | chmod 0600 "$key_file_path" 71 | 72 | # Get host hash 73 | host_hash=$(read_env 'GIT_HOST_HASH') 74 | if [[ -z "$host_hash" ]]; then 75 | host_hash=$(read_env 'GITHUB_HOST_HASH') 76 | fi 77 | if [[ -z "$host_hash" ]]; then 78 | host_hash=$(fetch_github_hash) 79 | fi 80 | 81 | # Get git host 82 | git_host=$(read_env 'GIT_HOST') 83 | if [[ -z "$git_host" ]]; then 84 | arrow 'GIT_HOST not set, assuming github.com' 85 | git_host='github.com' 86 | fi 87 | 88 | # Get git user 89 | git_user=$(read_env 'GIT_USER') 90 | if [[ -z "$git_user" ]]; then 91 | arrow "GIT_USER not set, assuming 'git'" 92 | git_user='git' 93 | fi 94 | 95 | # Process and validate SSH key 96 | temp_dir=$(mktemp -d) 97 | trap 'rm -rf "$temp_dir"' EXIT 98 | 99 | ssh-keygen -e -P '' -f "$key_file_path" < /dev/null > "${temp_dir}/ssh_buildpack_key.pub.rfc" 2>/dev/null 100 | ssh-keygen -i -P '' -f "${temp_dir}/ssh_buildpack_key.pub.rfc" > "${temp_dir}/ssh_buildpack_key.pub" 2>/dev/null 101 | 102 | # Get SSH version and generate fingerprint accordingly 103 | ssh_version=$(ssh -V 2>&1 | grep -o 'OpenSSH_[0-9.]*' | cut -d_ -f2) 104 | if [[ $(echo "$ssh_version" | cut -d. -f1) -ge 7 ]] || [[ "$ssh_version" == "6.9"* ]] || [[ "$ssh_version" == "6.8"* ]]; then 105 | fingerprint=$(ssh-keygen -l -E md5 -f "${temp_dir}/ssh_buildpack_key.pub" | awk '{print $2}') 106 | else 107 | fingerprint=$(ssh-keygen -l -f "${temp_dir}/ssh_buildpack_key.pub" | awk '{print $2}') 108 | fi 109 | 110 | temp_key=$(echo "$fingerprint" | tr -d ':' | grep -iE '[a-f0-9]{32}') 111 | 112 | if [[ -z "$temp_key" ]]; then 113 | alert "GIT_DEPLOY_KEY was invalid" 114 | exit 1 115 | else 116 | arrow "Using GIT_DEPLOY_KEY ${fingerprint}" 117 | fi 118 | 119 | # Create SSH directory and files 120 | mkdir -p "$SSH_DIR" 121 | chmod 0700 "$SSH_DIR" 122 | 123 | # Write private key 124 | echo "$ssh_key" > "${SSH_DIR}/private_key" 125 | chmod 0600 "${SSH_DIR}/private_key" 126 | arrow "Wrote ssh key to user's ssh dir" 127 | 128 | # Write known hosts 129 | echo "$host_hash" > "${SSH_DIR}/known_hosts" 130 | chmod 0600 "${SSH_DIR}/known_hosts" 131 | arrow "Wrote host hash to user's known hosts" 132 | 133 | # Write SSH config 134 | cat > "${SSH_DIR}/config" << EOF 135 | Host ${git_host} 136 | User ${git_user} 137 | IdentityFile ${SSH_DIR}/private_key 138 | EOF 139 | chmod 0600 "${SSH_DIR}/config" 140 | arrow "Wrote config to user's config" 141 | arrow "SSH CONFIG" 142 | arrow "$(cat "${SSH_DIR}/config")" 143 | -------------------------------------------------------------------------------- /bin/detect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #bin/detect 3 | 4 | echo "Git Deploy Keys" 5 | exit 0 6 | -------------------------------------------------------------------------------- /bin/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -f $1/failed ] 4 | then 5 | exit 1 6 | else 7 | echo "--- {}" 8 | fi 9 | -------------------------------------------------------------------------------- /test/id_rsa: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /test/ssh_keys_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | . ${BUILDPACK_TEST_RUNNER_HOME}/lib/test_utils.sh 3 | 4 | 5 | afterSetUp() 6 | { 7 | cp ${BUILDPACK_HOME}/test/id_rsa ${ENV_DIR}/GIT_DEPLOY_KEY 8 | } 9 | 10 | testSSHKeysDroppedOnDisk() 11 | { 12 | compile 13 | assertCapturedSuccess 14 | assertFileMD5 "c36450c2b759b174a55e6ed4f113eada" "$HOME/.ssh/private_key" 15 | assertTrue "[ -e $HOME/.ssh/known_hosts ]" 16 | } 17 | --------------------------------------------------------------------------------