diff --git a/gencert.sh b/gencert.sh new file mode 100755 index 0000000..39cdbd0 --- /dev/null +++ b/gencert.sh @@ -0,0 +1,237 @@ +#!/bin/sh +# Filename: gencert.sh +# Description: generates self-signed x509 with CA and IPs in SAN +# Version: 0.1 - 30 June 2018 +# Author: Andrey Arapov +# License: GPLv3 +# +# Purpose +# - This script will always produce a self-signed x509 certificate in the +# current path with the IP addresses embedded to x509's SAN. +# It will also produce a CA certificate and can be used by other services +# which may need to authenticate against this self-signed certificate. +# The authentication works in a way that a public CA certificate will be +# used by the client in order to validate the server's certificate. +# +# Application +# Backend requiring x509 running behind reverse proxy +# - This script has been created in order to ease the Minio's SSE-C +# (Server Side Encryption - Customer provided keys) enablement when +# Minio server is running as a backend behind a reverse proxy like Traefik. +# Minio server enables SSE-C only when it detects the x509 certificates. +# Traefik running with docker service provider talks to the backend using +# the IP. The IP usually is not static, hence this script comes handy. +# +# Example usage +# Minio server with Traefik example +# 1. Replace "minio server" command with the following one: +# - "cd /root/.minio/certs && ./gencert.sh --cn minio.example.com && minio server" +# 2. Copy the CA certificate "ca.crt" file to "/usr/local/share/ca-certificates/" and +# run "update-ca-certificates" command which will update +# "/etc/ssl/certs/ca-certificates.crt" file. +# 3. Restart Traefik. +# +# NOTE: Steps 2. and 3. will need to be repeated each time you get a new CA +# certificate. Then they can be automated this way: +# - Start Traefik with this command: +# sh -c "update-ca-certificates && traefik" +# while "/usr/local/share/ca-certificates" path is a host mounted +# path with the CA certificate produced by this script. +# NOTE: I am using Alpine Traefik image, the correct ca certificates path is +# "/usr/local/share/ca-certificates/", otherwise one of these +# https://golang.org/src/crypto/x509/root_linux.go +# +# Script logic +# - generate CA cert if does not find any. +# - always generate server cert on startup to ensure all IP addresses are in +# x509 SAN. +# - warn if the CA cert about to expire (<30 days till expiration). +# - regenerate the CA cert if it finds it has expired. +# +# Notes +# - The CA cert will be valid for 3650 days (10 years). +# - The server cert will be valid for 365 days (1 year). +# - The x509 certs are ECDSA with prime256v1 curve and SHA256 signatures. + +ME=$(printf '%s\n' "${0##*/}") + +function print_help() { + echo -e "[${ME}] HELP: I accept following arguments: + --help - show this message + --cn - certificate's CN name\t\t(MANDATORY) + --key - server key name\t\t\t(default: private.key) + --cert - server cert name\t\t\t(default: public.crt) + --days - server cert expiration in days\t(default: 365) + --cakey - CA key name\t\t\t(default: ca.key) + --ca - CA cert name\t\t\t(default: ca.crt) + --cadays - CA cert expiration in days\t(default: 3650)" +} + +# A POSIX variable +OPTIND=1 # Reset in case getopts has been used previously in the shell. + +ARGUMENT_LIST=( + "cn" + "key" + "cert" + "days" + "cakey" + "ca" + "cadays" +) + +# read arguments +opts=$(getopt \ + --longoptions "help,$(printf "%s:," "${ARGUMENT_LIST[@]}")" \ + --name "$(basename "$0")" \ + --options "" \ + -- "$@" +) + +eval set --$opts + +while [[ $# -gt 0 ]]; do + case "$1" in + --help) + print_help; + exit 0 + ;; + + --cn) + ARG_CN=$2 + shift 2 + ;; + + --key) + ARG_KEY=$2 + shift 2 + ;; + + --cert) + ARG_CERT=$2 + shift 2 + ;; + + --days) + ARG_DAYS=$2 + shift 2 + ;; + + --cakey) + ARG_CAKEY=$2 + shift 2 + ;; + + --ca) + ARG_CA=$2 + shift 2 + ;; + + --cadays) + ARG_CADAYS=$2 + shift 2 + ;; + + *) + break + ;; + esac +done + +if [ -z "${ARG_CN}" ]; then + echo "[${ME}] ERROR: Please specify CN, example \"--cn your.site.com\"" + print_help; + exit 1 +fi + +# For debugging purposes +# echo ARG_CN=$ARG_CN +# echo ARG_KEY=$ARG_KEY +# echo ARG_CERT=$ARG_CERT +# echo ARG_DAYS=$ARG_DAYS +# echo ARG_CAKEY=$ARG_CAKEY +# echo ARG_CA=$ARG_CA +# echo ARG_CADAYS=$ARG_CADAYS + +OPENSSL_CONFIG="openssl.cnf" +CA_KEY="${ARG_CAKEY:-ca.key}" +CA_CERT="${ARG_CA:-ca.crt}" +CA_DAYS="${ARG_CADAYS:-3650}" +SERVER_KEY="${ARG_KEY:-private.key}" +SERVER_CERT="${ARG_CERT:-public.crt}" +DAYS="${ARG_DAYS:-365}" + +# set -x +set -e + +function gen_openssl_config() { + OPENSSL_CONFIG_CONTENT="[ req ] +distinguished_name = req_distinguished_name +[req_distinguished_name] +[ v3_ca ] +basicConstraints = critical, CA:TRUE +keyUsage = critical, digitalSignature, keyEncipherment, keyCertSign +[ v3_req_server ] +basicConstraints = CA:FALSE +keyUsage = critical, digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth +subjectAltName = @alt_names +[ alt_names ]" + + # Gather IPs for SAN + + i=1 + IPS="$(getent ahostsv4 $(hostname) | awk '{print $1}' |sort | uniq)" + echo "[${ME}] Found these IPs: " ${IPS} + PAYLOAD="$(for IP in $IPS; do echo "IP.${i} = ${IP}" ; i=$((i + 1)); done)" + + echo -e "${OPENSSL_CONFIG_CONTENT}\n${PAYLOAD}" > "${OPENSSL_CONFIG}" +} + +function gen_ca() { + echo "[${ME}] Generating new CA: ${CA_KEY} / ${CA_CERT} ..." + openssl ecparam -name prime256v1 -genkey -noout -out "${CA_KEY}" 2>/dev/null + chmod 0600 "${CA_KEY}" + openssl req -x509 -new -sha256 -nodes -key "${CA_KEY}" -days "${CA_DAYS}" -out "${CA_CERT}" \ + -subj "/CN=my-CA" -extensions v3_ca -config "${OPENSSL_CONFIG}" 2>/dev/null +} + +function gen_server_x509() { + echo "[${ME}] Generating new server x509: ${SERVER_KEY} / ${SERVER_CERT} ..." + openssl ecparam -name prime256v1 -genkey -noout -out "${SERVER_KEY}" 2>/dev/null + chmod 0600 "${SERVER_KEY}" + openssl req -new -sha256 -key "${SERVER_KEY}" -subj "/CN=${ARG_CN}" \ + | openssl x509 -req -sha256 -CA "${CA_CERT}" -CAkey "${CA_KEY}" -CAcreateserial \ + -out ${SERVER_CERT} -days "${DAYS}" \ + -extensions v3_req_server -extfile "${OPENSSL_CONFIG}" 2>/dev/null +} + +function start() { + echo "[${ME}] Started in ${PWD} directory." + + gen_openssl_config; + + if [ ! -f "${CA_KEY}" ]; then + echo "[${ME}] Could not find ${CA_KEY} file so I will generate a new one." + gen_ca; + fi + + if ! openssl x509 -in "${CA_CERT}" -noout -checkend 2592000; then + echo "[${ME}] WARNING! Your CA certificate will expire in less than 30 days." + fi + + if ! openssl x509 -in "${CA_CERT}" -noout -checkend 1; then + echo "[${ME}] WARNING! Your CA certificate has expired, so we will generate a new one." + gen_ca; + fi + + # Generate a new server certificate with the detected IPs. + gen_server_x509; + + echo "[${ME}] The certificates have been generated in ${PWD} directory." + + CERT_INFO="$(openssl x509 -in "${SERVER_CERT}" -noout -text)" + echo "${CERT_INFO}" | grep -E "CN=|IP Address|Not\ " +} + +start;