How to push to github from Travis CI

January 26, 2019

Use a github Deploy key, which is scoped to a single repo, instead of a Personal Access token, which has push rights to all your public repos.

This approach is only as secure as the private key that Travis creates for your github repo.

Approach from https://stackoverflow.com/a/22977235.

Software used for this HOWTO:

  1. OSX Mojave
  2. OpenSSH_7.9p1, LibreSSL 2.7.3
  3. travis gem 1.8.9
  4. openssl (LibreSSL 2.6.5)

Steps

1. Install Travis CI command-line client

Use this to encrypt a password and write it to .travis.yml.
$ gem install travis
...
$
    

2. Create a GitHub deploy key

A github deploy key is just the public half of an SSH key pair.
$ cd ~/src/mycode/shmig
$ ssh-keygen -t rsa -b 4096 -C "mkbucc@gmail.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/mark/.ssh/id_rsa): ./travis_key
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in ./travis_key.
Your public key has been saved in ./travis_key.pub.
...
$ cat travis_key.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDCDzTsOqLcIm88P...
$ 
    

3. Add deploy key to your repo

GitHub -> your repository -> settings -> Deploy Keys -> "Add deploy key"

4. Encode private key with big random password

Output 32 pseudo-random bytes in hexadecimal encoding. Then use this as a password to encrypt the private key. When I pasted a password generated this way in DashLanes How secure is my password, then answer was three sesvigintillion years.
$ password=$(openssl rand -hex 32)
$ cat travis_key | openssl aes-256-cbc -k "$password" -md sha256 -a  > travis_key.enc
    

5. Give Travis access to the password

The Travis CI encryption scheme is to create an RSA key pair for each registered repository, “to keep the private key private, but makes the repository’s public key available to everyone.”
$ echo "" > .travis.yml
$ travis encrypt travis_key_password=$password --add env.matrix
Detected repository as mbucc/shmig, is this correct? |yes| 
$ grep -A 2 ^env .travis.yml
env:
  matrix:
    secure: VBF7EjJ/e2ibYLpaURDrWnrMsTn0mBPPeJiNSaoqrB/Z6Kg54PF+nZiMzo0kL9y3yi8PKlmTvfG3uPx3N7tCuQDVehH47yeSozT2QX/Vbod3RObpUsETT8LbSgyDmgi2+F4aZzsY+MtkLnv28wvcMN2kM4HjGL/0ROKIDL15ChFsmvn+BA7yUnVm43vQJcYRRlxDDRSdofJBHxtguPWjOvK8ARF5hSML/kl3mZZTmPXOR2W/WxnQXNSHkgaAoir/3ElSGVVV1ipN1ifWNdzlJI8Tcod9cDNE0DscOqMWC/7V9mEZUosHBN4ZIrn84ijBuiKYoWMoN1M5TRXwIYxsKiLf9+IwqRHaV0Q36Ktkoj5j4CG4xOPeS+oVHkJ6vFptr3hzgWPcKzi5zGzS8dQfnbk3PmxXDJyt9S3eVpc7HVjWVU5rqgBI912Z/vhgK6pJ/txPVJDw67sgA8h8G4UtoqMJpBwST4BIyjO33BCvFUpMT8WloGRiPCMBSub8WAJTjZ7Cy3E6l/iSpBVTRvr6Gqb9f4of61MUOjKiUyrdG05g21sm/RlNNrm2lg5vgZjq8AsEnxlKn5ONHWJjspivBDgMUH7PN5F3e1ZRfd2AievtjQC5FS7wU2ogN74O3Bor5y0UgJPRRl0/YL5Z7xFOz7Eo3qvp9JRTTqobgIz33+A=
$
$
$ git add travis_key.enc
$ git commit -m 'Encrypted private key half of github deploy key.'
$ git add .travis.yml
$ git commit -a -m 'Add encrypted password that can decrypt private github deploy key.'
    

6. Setup private key for push to git

I added the following lines to my test runner script:

In the script that runs on Travis, decrypt the private key, and configure SSH client to use that key for the github.com host. Make sure to use the SSH URL for github remote, not the https one! Finally, add the github public SSH key to the known hosts file.
openssl aes-256-cbc -k "$travis_key_password" -d -md sha256 -a -in travis_key.enc -out travis_key
echo "Host github.com" > ~/.ssh/config
echo "  IdentityFile  $(pwd)/travis_key" >> ~/.ssh/config
chmod 400 travis_key
git remote set-url origin git@github.com:mbucc/shmig_test.git
echo "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" > ~/.ssh/known_hosts 
if ! git push -v ; then
  _err "git push error"
fi

Stumbles along the way

1. digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:539:

alpine:3.8|apk update|apk add|bash,sqlite|/bin/bash|sqlite3 [PASS]
shmig-net
bad decrypt
139902961870496:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:539:
remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/mbucc/shmig_test.git/'
[Sat Jan 26 16:37:24 UTC 2019] git push error

I had forgotten to commit the .travis.yml, so the travis_key_password variable was not in the Travis build environment. Doh!

Before I The default digest was changed from MD5 to SHA256 in Openssl 1.1, per https://stackoverflow.com/a/39641378. realized this, I spent a good amount of time making sure the digest used by my laptop openssl matched the one used by the Travis build box openssl. Hence the -md sha256 argument to openssl in both environments.

2. remote: Invalid username or password

git remote set-url origin git@github.com:mbucc/shmig_test.git

This one took a long while to figure out. You must use the git URL for the remote in order for the SSH private key to work.

3. Permissions 0664 for '/home/travis/build/mbucc/shmig_test/travis_key' are too open.

chmod 400 ./travis_key