Skip to content
This repository has been archived by the owner on Jun 4, 2021. It is now read-only.

Support specifying custom SSH public key. #970

Merged
merged 18 commits into from
Oct 14, 2017

Conversation

cpu
Copy link
Collaborator

@cpu cpu commented Sep 28, 2017

Prior to this PR Streisand universally assumed the user's SSH private key was ~/.ssh/id_rsa. The public component was then used to provision cloud instances.

This PR adds a new streisand_ssh_private_key var to the global_vars/default-site.yml file. The site-specific override for these defaults, ~/.streisand/site.yml is now customized by playbooks/customize.yml when the user specifies that they want to use a different SSH key. The public component is expected to exist at the same file path with a .pub suffix to keep things simple.

This PR proactively sets the ansible_ssh_private_key_file for the Streisand SSH key specified. This ensures Ansible will load & use this private key to access the streisand-host bypassing the need to use a ssh-agent (though it will play nicely if you do).

The validation playbook is updated to check that the streisand_ssh_private_key exists. The corresponding shell based checks for the SSH key were removed from the ./streisand wrapper script since they are now replaced by these Ansible tasks. Similarly the existing server playbook
now performs its own quick test of a raw_cmd on the server instead of doing a SSH check in the ./streisand wrapper. This means things fail a little bit later in the process if you can't actually SSH to the
existing server but it isn't much later in the process and its nice to rm some shell script code.

Two unrelated bugs were fixed with the existing server provisioning: the genesis role was not being saved as a fact on localhost causing the diagnostics to print "unknown" for the genesis role used. Secondly, because no gather_facts: yes for the localhost was run for the existing server provision other diagnostic information was missing. This was also the root cause of Issue #739.

Resolves #739, #300

I have tested this PR with a few providers (Linode, DigitalOcean, Existing Server, Localhost) using my own SSH key loaded in an ssh-agent ahead of time as well as with a brand new ed25519 keypair that used a passphrase. Both cases worked as expected.

This commit updates Streisand to allow enabling/disabling the core
services at install time, or by customizing a site specific config file
before running Streisand.

Ansible's prompt functionality is unfortunately crippled with respect to
templating and `when` conditions. This makes it tricky to accomplish the
customization we want where you are only prompted for each service if
you want to customize the overall installation.

To allowing changing services on a per-site basis & remembering the
decision the wrapper script now creates a `$HOME/.streisand` directory
with a `$HOME/.streisand/site.yml` site specific config file. By
default it is populated with the `global_vars/default-site.yml` file
from the Streisand repository.

The `streisand` wrapper conditionally invokes a separate playbook
(`playbooks/customize.yml`) for customization that rewrites the
`$HOME/.streisand/site.yml` vars file based on what the user specifies.
It's a little bit ugly but It Works(!). Further refinement welcome!

This methodology also supports customizing the installed services
non-interactively by editing `$HOME/.streisand/site.yml` ahead of
running `./streisand` and skipping the customization step. This is
useful if (for example) you only ever want to install Wireguard on your
Streisand instances. You can create a `$HOME/.streisand/site.yml` that
only enables Wireguard and all of your Streisand instances will be
provisioned accordingly.

The validation role is run after customization to ensure that the
choices are valid and don't result in (for e.g.) no services enabled.

Presently both Travis and the Vagrant `streisand-host` ignore the
`$HOME/.streisand.yml` and use the `global_vars/default-site.yml` vars
resulting in a provision with all services enabled.
Prior to this commit Streisand universally assumed the user's SSH
public key was `~/.ssh/id_rsa.pub`. This SSH key was then used to
provision cloud instances.

This commit adds a new `streisand_ssh_key` var to the
`global_vars/default-site.yml` file. The site-specific override for
these defaults, `~/.streisand/site.yml` is now customized by
`playbooks/customize.yml` when the user specifies that they want to use
a different SSH pubkey. The user is expected to have the private
component loaded into their ssh agent or to customize Ansible to know
how to load it.

The validation playbook is updated to check that the `streisand_ssh_key`
exists. The corresponding shell based checks for the SSH key were
removed from the `./streisand` wrapper script since they are now
replaced by these Ansible tasks. Similarly the existing server playbook
now performs its own quick test of a raw_cmd on the server instead of
doing a SSH check in the `./streisand` wrapper. This means things fail
a little bit later in the process if you can't actually SSH to the
existing server but it isn't *much* later in the process and its nice to
rm some shell script code.

Three unrelated bugs were fixed with the existing server provisioning.
First: There was a typo in the `./streisand` wrapper script's
`local_provision` function that was running a non-existent playbook.
Second: the genesis role was not being saved as a fact on localhost
causing the diagnostics to print "unknown" for the genesis role used.
Lastly, because no `gather_facts: yes` for the localhost was run for the
existing server provision other diagnostic information was missing. This
was also the root cause of Issue StreisandEffect#739. All of the above are now fixed!
Yay \o/
This commit reworks the previous custom SSH key support in order to
allow setting the `ansible_ssh_private_key_file` fact. Previously the
customization role allowed setting a SSH *public key* that would be used
for server provisioning. It was assumed that the user would load this
key in their ssh-agent before running Streisand.

This commit updates support to instead proactively set the
`ansible_ssh_private_key_file` for the Streisand SSH key specified. This
ensures Ansible will load & use this private key to access the
streisand-host bypassing the need to use a ssh-agent. Doing so required
changing the customization to prompt for the *private key* and adjusting
other roles accordingly.

Work is done to ensure that the existing-server provisioning SSH test
still functions the way it did when the `streisand` wrapper script was
used to test with a direct `ssh` invocation.
This commit updates the `tests/run.yml` and `tests/vars_ci.yml` to
support the custom SSH key work. Primarily this means making sure that
the one-off SSH key generated in `/tmp` is properly configured as the
Streisand SSH key.
Copy link
Collaborator

@alimakki alimakki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor grammatical nitpick, otherwise I was able to run your branch with a newly generated ssh-key against DO with no issues.

Nice work @cpu!

@@ -4,6 +4,10 @@
gather_facts: no

vars_prompt:
- name: streisand_ssh_private_key
prompt: "Enter the path to your SSH private key? Press enter for default "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Enter the path to your SSH private key, or press enter for default" reads a bit better in my opinion. Either way the question mark can be dropped.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! fixed in ef529d1

rescue:
- fail:
msg: "Unable to SSH to existing streisand-host.\nEnsure private key corresponding to \"{{ streisand_ssh_key }}\" is loaded in your SSH key agent.\nTry using `ssh-keygen -i {{ streisand_ssh_key }} to generate your key if it does not exist\n"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good hint 👍

Copy link
Collaborator

@alimakki alimakki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\o/

Copy link
Member

@nopdotcom nopdotcom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be simpler if we could guarantee Ansible runs under an ssh-agent, either an existing one or a freshly launched one? I ask because my ssh-agent talks to my Yubikey, so in some sense I don't have an id_rsa, only an id_rsa.pub.

We do need something id_rsa.pub-like, because we need to push it into authorized_keys. If we were super-crazy, we could fish the pubkeys out of the agent with ssh-add -L: have the user select from the human-readable names in the last column, probably.

Backing up a little, this PR resolves those bug reports and it answers immediate needs, so I'm going to hit the Approve button, since what PR sets out to do, it does nicely.

(In the Docker-hacks version of ./streisand I force the user to authenticate with ssh-add. Here

@cpu
Copy link
Collaborator Author

cpu commented Oct 11, 2017

Would this be simpler if we could guarantee Ansible runs under an ssh-agent, either an existing one or a freshly launched one? I ask because my ssh-agent talks to my Yubikey, so in some sense I don't have an id_rsa, only an id_rsa.pub.

It doesn't sound simpler to me :-) I'll give it some thought but I likely won't work on it myself if you're interested in trying as a follow-up. If so creating a second issue with some prelim details and assigning it to yourself would be great. Thanks!

@cpu cpu merged commit ca22ac9 into StreisandEffect:master Oct 14, 2017
@cpu cpu deleted the cpu-specify-ssh-key branch October 14, 2017 17:22
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

hostvars['127.0.0.1']['ansible_system'] may not exist
3 participants