Skip to content

Instantly share code, notes, and snippets.

@awendland
Last active June 24, 2018 05:14
Show Gist options
  • Save awendland/16d5dbbe925e1ad6a5e8b23da4c809f5 to your computer and use it in GitHub Desktop.
Save awendland/16d5dbbe925e1ad6a5e8b23da4c809f5 to your computer and use it in GitHub Desktop.
Generate a CA and issue cert pairs for usage by a docker server and client
#! /bin/bash
###############################################################################
# Generate the server and client certificates required to secure and verify
# both the Docker daemon and Docker client when communicating over an HTTP
# socket using TLS.
#
# NOTE: These steps can all be completed manually as well. Simply follow the
# requirements specified by the DIY comments to generate the appropriate certs.
# This website provides good information about the process:
# https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html
#
# Requires:
# - DOCKER_HOST: Hostname of the machine running the Docker Daemon
# - OUTPUT_DIR: Directory to output the certificate files in, defaults to "./certs"
#
# NOTE:
# - What does this secure?
# - This will produce certificates for the server and the client signed by
# the same CA. These certificates will be setup so that the communication
# between the Docker daemon and client is encrypted with TLS (ensuring
# that no one can eavesdrop), that the client is verified to to ensure
# that it is a recognized actor (or at least one with a certificate signed
# by the CA). However, this will not verify that the server has a
# certificate signed by the CA. This is because a hostname and IP would
# have to be set, which may not be stable/known immediately, and which
# bring minimal security value.
# - How should the client connect to the server?
# - Docker client should be run with
# - `--tlsverify`
# - `--tlscacert=$CA_PATH`
# - `--tlscert=$CLIENT_CERT_PATH`
# - `--tlskey=$CLIENT_KEY_PATH`
# - Certificate Lifetime
# - Certificates are issued with 1 year TTLs
# - Headless Execution
# - It's likely that user input will be required to use the CA
###############################################################################
set -e
# ############
# Requirements
# ############
if [ -z "$DOCKER_HOST" ]; then
echo -n "Must set DOCKER_HOST that the docker host has been deployed at (\$DOCKER_HOST)"
exit 1
fi
# Ensure that the output directory exists
mkdir -p "${OUTPUT_DIR:=./certs}"
cd "$OUTPUT_DIR"
secret_dir="secrets"
mkdir -p "$secret_dir"
# ##########
# Generation
# ##########
# Attempt to use the brew version of openssl
# shellcheck disable=SC2012
openssl_version=$(ls -t /usr/local/Cellar/openssl | tail -1)
openssl="/usr/local/Cellar/openssl/$openssl_version/bin/openssl"
# Ensure that the version of openssl is new enough
if [[ "$("$openssl" version)" < "OpenSSL 1.0.0" ]]; then
echo "Must use an OpenSSL version of at least 1.0.0"
exit 1
fi
echo "Using $("$openssl" version) from $openssl"
# Generate a password for the CA
# DIY: create a random password, this can be done through any mechanism
pass_path="$secret_dir/ca-pass.txt"
ca_path="ca.pem"
ca_key_path="$secret_dir/ca-key.pem"
"$openssl" rand -base64 12 > "$pass_path"
# Generate CA for this docker host (or whole swarm)
# DIY: create the CA private & public keys for a year and lockdown file permissions
"$openssl" req -x509 -newkey rsa:4096 -days 375 -keyout "$ca_key_path" -passout file:"$pass_path" -sha256 -out "$ca_path" \
-subj "/CN=$DOCKER_HOST"
chmod 400 $ca_key_path
chmod 444 $ca_path
# Generate server private key
# DIY: generate an RSA key of 4096 bits and lockdown file permissions
"$openssl" genrsa -out server-key.pem 4096
chmod 400 server-key.pem
# Create certificate signing request
# DIY: create a certificate request using sha256
"$openssl" req -subj "/CN=${DOCKER_HOST}" -sha256 -new -key server-key.pem -out server.csr
# Setup alternative names on the cert
# DIY: ensure that these alternative names are set to make interacting with the daemon easier
echo "subjectAltName = DNS:${DOCKER_HOST},DNS:localhost,IP:127.0.0.1" > extfile.cnf
# Sign the server certificate
# DIY: generate a certificate using the CA with an expiration around 1 year; lockdown permissions
"$openssl" x509 -req -days 375 -sha256 -in server.csr -CA "$ca_path" -CAkey "$ca_key_path" \
-passin file:"$pass_path" -CAcreateserial -extfile extfile.cnf -out server.pem
chmod 444 server.pem
rm server.csr extfile.cnf
# Generate the client private key
# DIY: generate an RSA key of 4096 bits and lockdown file permissions
"$openssl" genrsa -out key.pem 4096
chmod 400 key.pem
# Create certificate signing request
# DIY: generate an RSA key of 4096 bits and lockdown file permissions
"$openssl" req -subj "/CN=docker-client" -new -key key.pem -out client.csr
# Setup client certificate parameters
# DIY: set client parameters to limit the cert to only being for client auth
echo extendedKeyUsage = clientAuth > extfile.cnf
# Sign the client certificate
# DIY: generate a certificate using the CA with an expiration around 1 year; lockdown permissions
"$openssl" x509 -req -days 375 -sha256 -in client.csr -CA "$ca_path" -CAkey "$ca_key_path" \
-passin file:"$pass_path" -CAcreateserial -extfile extfile.cnf -out cert.pem
chmod 444 cert.pem
rm extfile.cnf client.csr
# Generate a file for easily setting up env vars to use these docker certs
# For bash
export_env_sh="export_env.sh"
# shellcheck disable=SC2016
export_dir_sh_cmd='"$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"'
cat <<EOT > "$export_env_sh"
#! /bin/bash
export DOCKER_TLS_VERIFY=true
export DOCKER_CERT_PATH=$export_dir_sh_cmd
EOT
chmod +x "$export_env_sh"
# For fish
export_env_fish="export_env.fish"
export_dir_fish_cmd='(cd (dirname (status --current-filename)); and pwd)'
cat <<EOT > "$export_env_fish"
#! /bin/fish
export DOCKER_TLS_VERIFY=true
export DOCKER_CERT_PATH=$export_dir_fish_cmd
# TODO: fix from changing the user's cwd
EOT
chmod +x "$export_env_fish"
cat <<EOT
Password for the CA stored in: $pass_path
Make sure to save this along with ca-key.pem!
Run the following command to make Docker automatically use these certs
# If you need to delete previous certs first:
# rm ~/.docker/{ca,cert,key}.pem
cp "$OUTPUT_DIR/{ca,cert,key}.pem" ~/.docker
# If you want to setup just the current shell for docker-compose
source "$OUTPUT_DIR/$export_env_sh"
# or "$OUTPUT_DIR/$export_env_fish" if you're using fish
EOT
@awendland
Copy link
Author

awendland commented Jun 24, 2018

curl -fsSL https://gist.githubusercontent.com/awendland/16d5dbbe925e1ad6a5e8b23da4c809f5/raw/gen_docker_certs.sh | bash

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment