Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Deploying Keys & Private Repos

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $9.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

I want to show you a quick trick. Right now, we're always deploying the master branch:

- hosts: aws
... lines 3 - 6
... lines 8 - 13
ansistrano_git_branch: master # What version of the repository to check out. This can be the full 40-character SHA-1 hash, the literal string HEAD, a branch name, or a tag name
... lines 15 - 18

That probably make sense. But, sometimes, you might want to deploy a different branch, like maybe a feature branch that you're deploying to a beta server. There are a few ways to handle this, but one option is to leverage a native Ansible feature: vars_prompt:

- hosts: aws
... lines 3 - 6
... lines 8 - 24

With this, we can just ask the user, well, us, which branch we want to deploy. Whatever we type will become a new variable called git_branch. For the prompt, say: Enter a branch to deploy. Default the value to master and set private to no... so we can see what we type: this is not a sensitive password:

- hosts: aws
... lines 3 - 6
- name: git_branch
prompt: 'Enter a branch to deploy'
default: master
private: no
... lines 12 - 24

Down below, use the variable: "{{ git_branch }}":

- hosts: aws
... lines 3 - 12
... lines 14 - 19
ansistrano_git_branch: "{{ git_branch }}" # What version of the repository to check out. This can be the full 40-character SHA-1 hash, the literal string HEAD, a branch name, or a tag name
... lines 21 - 24

This is nothing super amazing, but if it's useful, awesome! The downside is that it will ask you a question at the beginning of every deploy. Try it:

ansible-playbook -i ansible/hosts.ini ansible/deploy.yml

There's the prompt! I'll stop the deploy.

How SSH Authentication Works

Now, to the real thing I want to talk about: deploy keys. Right now, the only reason our deploy works is that... well, our repository is public! The server is able to access my repo because anyone can. Go copy the ssh version of the URL and use that for ansistrano_git_repo instead:

- hosts: aws
... lines 3 - 12
... lines 14 - 18
ansistrano_git_repo: "git@github.com:knpuniversity/ansible.git" # Location of the git repository
... lines 20 - 25

Now try the deploy:

ansible-playbook -i ansible/hosts.ini ansible/deploy.yml

It starts off good... but then... error! It says:

Permission denied (public key). Could not read from remote repository

Woh! When you use the ssh protocol for Git, you authenticate with an ssh key. Basically, you generate a private and public key on your machine and then upload the public key to your GitHub account. Once you do that, each time you communicate with GitHub, you send your public key so that GitHub knows who you are and what repositories you have access to. And also, behind the scenes, the private key on your local machine is used to prove that you own that public key. Actually, none of this is special to git, this is how SSH key-based authentication works anywhere.

Even though our repository is still public, you need some valid SSH key pair in order to authenticate... and our server has nothing. That's why this is failing. To fix this, we'll use a deploy key... which will allow our server to clone the repository, whether it's public or private.

Creating a Deploy Key

Here's how it works. First, locally, generate a new public and private key: ssh-keygen -t rsa -b 4096 -C, your email address - ryan@knpuniversity.com then -f ansible/id_rsa:

ssh-keygen -t rsa -b 4096 -C "ryan@knpuniversity.com" -f ansible/id_rsa

You can use a pass phrase if you want, but I won't. When this is done, we have two new fancy files inside the ansible/ directory: id_rsa - the private key - and id_rsa.pub the key to your local pub. I mean, the public key.

Back on GitHub, on the repository, click "Settings" and then "Deploy Keys". Add a deploy key and give it a name that'll help you remember why you added it. Go find the public key - id_rsa.pub - copy it, and paste it here. Add that key!

Boom! The nice thing is that this will only give our server read access to the repository.

Configuring Ansistrano to use the private key

But this is only one half of the equation: we need to tell Ansistrano to use the private key - id_rsa - when it communicates with GitHub.

But that's why we use Ansistrano! They already thought about this, and exposed two variables to help: ansistrano_git_identity_key_path and ansistrano_git_identity_key_remote_path. Basically, we need to store the private key somewhere: it can live on our local machine where we execute Ansible - that's the first variable - or you can put it on the server and use the second variable.

Let's use the first option and store the key locally. Copy the first variable: ansistrano_git_identity_key_path. Set it to {{ playbook_dir }}/id_rsa:

- hosts: aws
... lines 3 - 12
... lines 14 - 20
ansistrano_git_identity_key_path: "{{ playbook_dir }}/id_rsa" # If specified this file is copied over and used as the identity key for the git commands, path is relative to the playbook in which it is used
... lines 22 - 25

playbook_dir is an Ansible variable, and it points to the ansible/ directory: the directory that holds the playbook file. As soon as we do this, Ansistrano will use this private key when it talks to GitHub. And because we've added its partner public key as a deploy key to the repo, it will have access!

Storing the Private Key

Of course, this means that you need to make sure that the id_rsa file exists. You can either do this manually somehow... or you can do something a bit more controversial: commit it to your repository. I'll do that: add the file, then commit: "adding private deploy identity key to repo".

git add ansible/id_rsa
git commit -m "adding private deploy identity key to repo"

This is controversial because I just committed a private key to my repository! That's like committing a password! Why did I do this? Mostly, simplicity! Thanks to this, the private key will always exist.

How bad of a security issue is this? Well, this key only gives you read-only access to the repository. And, if you were already able to download the code... then you were already able to access it. This key doesn't give you any new access. But, if you remove someone from your GitHub repository... they could still use this key to continue accessing it in the future. That's the security risk.

An alternative would be to store the private key on S3, then use the S3 Ansible module to download that onto the server during deployment. Make the decision that's best for you.

Whatever you choose, the point is: the variable is set to a local path on our filesystem where the private key lives. This means... we can deploy again! Try it:

ansible-playbook -i ansible/hosts.ini ansible/deploy.yml

It's working... working... and ... it's done! Scroll up a little. Cool! It ran a few new tasks: "Ensure Git deployment key is up to date" and then later "shred Git deployment key". It uses the key, but then removes it from the server after. Nice!

The server can now pull down our code... even if the repository is private.

Next! Deployment is not working yet: we still need to setup parameters.yml and do a few other things.

Leave a comment!

Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

While the fundamentals of Ansistrano haven't changed, this tutorial is built using Symfony 3, which has significant differences versus Symfony 4 and later.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=5.5.9",
        "doctrine/doctrine-bundle": "^1.6", // 1.6.8
        "doctrine/orm": "^2.5", // v2.7.2
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "sensio/distribution-bundle": "^5.0.19", // v5.0.20
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "symfony/monolog-bundle": "^3.1.0", // v3.1.0
        "symfony/polyfill-apcu": "^1.0", // v1.4.0
        "symfony/swiftmailer-bundle": "^2.3.10", // v2.6.3
        "symfony/symfony": "3.3.*", // v3.3.5
        "twig/twig": "^1.0||^2.0", // v1.34.4
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "predis/predis": "^1.1", // v1.1.1
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0", // v3.3.5
        "doctrine/data-fixtures": "^1.1", // 1.3.3
        "hautelook/alice-bundle": "^1.3" // v1.4.1

What Ansible libraries does this tutorial use?

# ansible/requirements.yml
    src: DavidWittman.redis
    version: 1.2.4
    src: ansistrano.deploy
    version: 2.7.0
    src: ansistrano.rollback
    version: 2.0.1