From 303c51996458c1dfcf33bfa40bbfce92355be7d4 Mon Sep 17 00:00:00 2001 From: Simon Brulhart Date: Thu, 22 Jul 2021 15:13:32 +0200 Subject: [PATCH 1/3] Document how to share SSH keys from non-Linux hosts --- {{cookiecutter.project_slug}}/Dockerfile | 2 +- .../docker-compose.override.example.yml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/Dockerfile b/{{cookiecutter.project_slug}}/Dockerfile index 2b794af..d747490 100644 --- a/{{cookiecutter.project_slug}}/Dockerfile +++ b/{{cookiecutter.project_slug}}/Dockerfile @@ -24,7 +24,7 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN set -x; \ groupadd -g $GROUP_ID app && \ useradd --create-home -u $USER_ID -g app -s /bin/bash app && \ - install -o app -g app -d /code "$VIRTUAL_ENV" + install -o app -g app -d /code "$VIRTUAL_ENV" /home/app/.ssh USER app RUN python -m venv "$VIRTUAL_ENV" WORKDIR /code diff --git a/{{cookiecutter.project_slug}}/docker-compose.override.example.yml b/{{cookiecutter.project_slug}}/docker-compose.override.example.yml index d8adfad..3ebe16d 100644 --- a/{{cookiecutter.project_slug}}/docker-compose.override.example.yml +++ b/{{cookiecutter.project_slug}}/docker-compose.override.example.yml @@ -25,7 +25,7 @@ # ports: # - 127.0.0.1:8025:8025 -# PONTSUN CONFIGURATION +# EXTENDED CONFIGURATION (pontsun, SSH access) # ~~~~~~~~~~~~~~~~~~~~~ # # Set up pontsun (https://github.com/liip/pontsun) and start it. Then visit @@ -48,6 +48,9 @@ # SSH_AUTH_SOCK: /ssh-agent # volumes: # - $SSH_AUTH_SOCK:/ssh-agent +# # SSH agent forwarding only works with Linux hosts +# # On Windows and macOS, you have to mount the private key directly: +# # - ~/.ssh/id_rsa:/home/app/.ssh/id_rsa # build: # args: # <<: *x-build-args From a9ba7b20d4a475bb7376319995f5522208d70a0e Mon Sep 17 00:00:00 2001 From: Simon Brulhart Date: Thu, 22 Jul 2021 15:15:02 +0200 Subject: [PATCH 2/3] Fix loading of environment config The previous method would throw away config set via the command line such as the passphrase entered with "--prompt-for-passphrase". --- {{cookiecutter.project_slug}}/fabfile.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_slug}}/fabfile.py b/{{cookiecutter.project_slug}}/fabfile.py index 3d19a40..6d1bf9b 100644 --- a/{{cookiecutter.project_slug}}/fabfile.py +++ b/{{cookiecutter.project_slug}}/fabfile.py @@ -552,10 +552,12 @@ def load_environment(ctx): conf["environment"] = name # So now conf is the ENVIRONMENTS[env] dict plus "environment" pointing to the name # Push them in the context config dict - ctx.config.load_overrides(conf) - # Add the common_settings in there - ctx.conn = CustomConnection(host=conf["host"], inline_ssh_env=True) - ctx.conn.config.load_overrides(conf) + ctx.config.update(conf) + # Create a connection for this environment, + # sharing most of the configuration with the root context + ctx.conn = CustomConnection( + config=ctx.config.clone(), host=conf["host"], inline_ssh_env=True + ) load_environment.__doc__ = ( """Prepare connection and load config for %s environment""" % name From 04ebd9c95cb7df6f4181a725da63fb4765cb44e7 Mon Sep 17 00:00:00 2001 From: Simon Brulhart Date: Thu, 22 Jul 2021 15:28:00 +0200 Subject: [PATCH 3/3] Prompt for SSH passphrase when necessary Automatically prompt the user for its passphrase when connecting to a remote host using an encrypted SSH key and no agent. The passphrase is remembered until the end of the session. This mimicks the behavior of fabric v1. Note that commands called from the fabfile (git, rsync, scp) will still ask for the passphrase everytime. --- {{cookiecutter.project_slug}}/fabfile.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/fabfile.py b/{{cookiecutter.project_slug}}/fabfile.py index 6d1bf9b..7c77f46 100644 --- a/{{cookiecutter.project_slug}}/fabfile.py +++ b/{{cookiecutter.project_slug}}/fabfile.py @@ -1,4 +1,5 @@ import functools +import getpass import inspect import os import random @@ -12,6 +13,7 @@ from fabric.connection import Connection from invoke import Exit from invoke.exceptions import UnexpectedExit +from paramiko.ssh_exception import PasswordRequiredException ENVIRONMENTS = { "prod": { @@ -62,9 +64,19 @@ def ensure_absolute_path(path): class CustomConnection(Connection): """ - Add helpers function on Connection + Add helpers function on Connection. + Also automatically prompt for password on connection (when necessary). """ + def open(self): + try: + super().open() + except PasswordRequiredException: + # Prompt for passphrase and try again + prompt = "Enter passphrase for unlocking SSH keys: " + self.connect_kwargs["passphrase"] = getpass.getpass(prompt) + super().open() + @property def site_root(self): return self.config.root