#!/bin/bash # --------------------------------------------------------------------------- # create-getssl-config - Create a config file interactively to obtain an SSL certificate using getssl # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License at for # more details. # Usage: create-getssl-config [-h|--help] [-d|--debug] # Revision history: # 2016-02-04 Created (v0.1) # 2016-02-05 Updated to include more variables. Still not full operational. (v0.2) # 2016-05-04 Corrected typo on DNS_DEL_COMMAND (v0.3) # 2016-05-23 Added option to provide a blank response, for things like SANS. (0.4) # --------------------------------------------------------------------------- PROGNAME=${0##*/} VERSION="0.4" # defaults CA="https://acme-staging.api.letsencrypt.org" AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" ACCOUNT_KEY_LENGTH=4096 WORKING_DIR=~/.getssl DOMAIN_KEY_LENGTH=4096 SSLCONF="$(openssl version -d | cut -d\" -f2)/openssl.cnf" VALIDATE_VIA_DNS="false" RELOAD_CMD="" RENEW_ALLOW="30" PRIVATE_KEY_ALG="rsa" SERVER_TYPE="webserver" CHECK_REMOTE="true" DNS_EXTRA_WAIT=0 clean_up() { # Perform pre-exit housekeeping return } error_exit() { echo -e "${PROGNAME}: ${1:-"Unknown Error"}" >&2 clean_up exit 1 } graceful_exit() { clean_up exit } signal_exit() { # Handle trapped signals case $1 in INT) error_exit "Program interrupted by user" ;; TERM) echo -e "\n$PROGNAME: Program terminated" >&2 graceful_exit ;; *) error_exit "$PROGNAME: Terminating on unknown signal" ;; esac } usage() { echo -e "Usage: $PROGNAME [-h|--help] [-d|--debug]" } log() { echo "[$(date +%Y-%m-%d\ %H:%M:%S)] $*" >> "${PROGNAME}.log" } debug() { if [[ "${_USE_DEBUG:-"0"}" -eq 1 ]]; then echo "$@" fi } info() { echo "$@" } get_user_input() { prompt=$1 DefVal=$2 HelpInfo=$3 response="" echo "" validresponse="false" while [[ "$validresponse" == "false" ]]; do read -p "${prompt} (${DefVal}) : " response if [[ -z $response ]]; then debug "response blank - used default - $DefVal" res=$DefVal validresponse="true" elif [[ "$response" == "h" ]]; then echo "" echo "$HelpInfo" echo "" elif [[ "$response" == "-" ]]; then res="" validresponse="true" else res=$response validresponse="true" fi done } write_getssl_template() { # write out the main template file cat > "$1" <<- _EOF_getssl_ # Uncomment and modify any variables you need # The staging server is best for testing (hence set as default) #CA="https://acme-staging.api.letsencrypt.org" # This server issues full certificates, however has rate limits #CA="https://acme-v01.api.letsencrypt.org" CA="$CA" AGREEMENT="$AGREEMENT" # Set an email address associated with your account - generally set at account level rather than domain. #ACCOUNT_EMAIL="me@example.com" ACCOUNT_KEY_LENGTH=4096 ACCOUNT_KEY="$WORKING_DIR/account.key" PRIVATE_KEY_ALG="rsa" # The command needed to reload apache / nginx or whatever you use #RELOAD_CMD="" # The time period within which you want to allow renewal of a certificate # this prevents hitting some of the rate limits. RENEW_ALLOW="30" # Define the server type. The can either webserver, ldaps or a port number which # will be checked for certificate expiry and also will be checked after # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true SERVER_TYPE="webserver" CHECK_REMOTE="true" # openssl config file. The default should work in most cases. SSLCONF="$SSLCONF" # Use the following 3 variables if you want to validate via DNS #VALIDATE_VIA_DNS="true" #DNS_ADD_COMMAND= #DNS_DEL_COMMAND= # If your DNS-server needs extra time to make sure your DNS changes are readable by the ACME-server (time in seconds) #DNS_EXTRA_WAIT=60 _EOF_getssl_ } write_domain_template() { # write out a template file for a domain. cat > "$1" <<- _EOF_domain_ # Uncomment and modify any variables you need # The staging server is best for testing #CA="https://acme-staging.api.letsencrypt.org" # This server issues full certificates, however has rate limits #CA="https://acme-v01.api.letsencrypt.org" CA="$CA" AGREEMENT="$AGREEMENT" ACCOUNT_EMAIL="$ACCOUNT_EMAIL" ACCOUNT_KEY_LENGTH=$ACCOUNT_KEY_LENGTH ACCOUNT_KEY="$ACCOUNT_KEY" PRIVATE_KEY_ALG="$PRIVATE_KEY_ALG" # Additional domains - this could be multiple domains / subdomains in a comma separated list SANS=${SANS} # Acme Challenge Location. The first entry for the domain, the following ones for each additional domain. # If these start with ssh: then the next variable is assumed to be the hostname and the rest the location. # An ssh key will be needed to provide you with access to the remote server. # If these start with ftp: or sftp: then the next variables are userid:password:servername:ACL_location #ACL=('/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ssh:server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge') ACL=(${ACL[*]}) # Location for all your certs, these can either be on the server (so full path name) or using ssh as for the ACL DOMAIN_CERT_LOCATION="$DOMAIN_CERT_LOCATION" DOMAIN_KEY_LOCATION="$DOMAIN_KEY_LOCATION" CA_CERT_LOCATION="$CA_CERT_LOCATION" DOMAIN_CHAIN_LOCATION="" DOMAIN_PEM_LOCATION="" # The command needed to reload apache / nginx or whatever you use RELOAD_CMD="$RELOAD_CMD" # The time period within which you want to allow renewal of a certificate # this prevents hitting some of the rate limits. RENEW_ALLOW="$RENEW_ALLOW" # Define the server type. The can either webserver, ldaps or a port number which # will be checked for certificate expiry and also will be checked after # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true SERVER_TYPE="$SERVER_TYPE" CHECK_REMOTE="$CHECK_REMOTE" # Use the following 3 variables if you want to validate via DNS VALIDATE_VIA_DNS="$VALIDATE_VIA_DNS" DNS_ADD_COMMAND="$DNS_ADD_COMMAND" DNS_DEL_COMMAND="$DNS_DEL_COMMAND" # If your DNS-server needs extra time to make sure your DNS changes are readable by the ACME-server (time in seconds) DNS_EXTRA_WAIT=$DNS_EXTRA_WAIT _EOF_domain_ } help_message() { cat <<- _EOF_ $PROGNAME ver. $VERSION Create a config file interactively to obtain an SSL certificate using getssl $(usage) Options: -h, --help Display this help message and exit. -d, --debug outputs debug information _EOF_ return } # Trap signals trap "signal_exit TERM" TERM HUP trap "signal_exit INT" INT # Parse command-line while [[ -n $1 ]]; do case $1 in -h | --help) help_message; graceful_exit ;; -d | --debug) _USE_DEBUG=1 ;; -w) shift; WORKING_DIR="$1" ;; -* | --*) usage error_exit "Unknown option $1" ;; *) DOMAIN="$1" ;; esac shift done # Main logic info "" info "This is an interactive script to create a config file for getssl, please answer the following questions" info "Just press return for using the default" info "enter the letter 'h' for help / more information" info "enter the minus character '-' to provide a completely empty response." info "" if [ -f "$WORKING_DIR/getssl.cfg" ]; then debug "reading main config from existing $WORKING_DIR/getssl.cfg" . "$WORKING_DIR/getssl.cfg" fi get_user_input "What is the working directory for getssl" "$WORKING_DIR" \ "The working directory is where getssl saves all the config and certifcates" WORKING_DIR=$res # if the "working directory" doesn't exist, then create it. if [ ! -d "$WORKING_DIR" ]; then debug "Making working directory - $WORKING_DIR" mkdir -p "$WORKING_DIR" fi # if main config file exists, read it, else write default. if [ -f "$WORKING_DIR/getssl.cfg" ]; then debug "reading main config from existing $WORKING_DIR/getssl.cfg" . "$WORKING_DIR/getssl.cfg" else write_getssl_template "$WORKING_DIR/getssl.cfg" fi get_user_input "Domain name" "${DOMAIN}" \ "This should be the primary domain name you want on your SSL certificate" DOMAIN=$res # need to check if domain is valid DOMAIN_DIR="$WORKING_DIR/$DOMAIN" if [ ! -d "$DOMAIN_DIR" ]; then info "Making domain directory - $DOMAIN_DIR" mkdir -p "$DOMAIN_DIR" fi #if domain config file exists, read it. if [ -f "$DOMAIN_DIR/getssl.cfg" ]; then debug "reading config from $DOMAIN_DIR/getssl.cfg" . "$DOMAIN_DIR/getssl.cfg" fi #prompt to use staging server .... best for testing #CA="https://acme-staging.api.letsencrypt.org" # This server issues full certificates, however has rate limits #CA="https://acme-v01.api.letsencrypt.org" #prompt for agreement get_user_input "Agreement" "${AGREEMENT}" \ "This is the agreement with LetsEncrypt, and shouldn't generally be changed" AGREEMENT=$res # Set an email address associated with your account - generally set at account level rather than domain. get_user_input "Account email address" "${ACCOUNT_EMAIL}" \ "The email address that will be used by LetsEncrypt to notify you when your certificate is due for renewal" ACCOUNT_EMAIL=$res get_user_input "Account key location" "${ACCOUNT_KEY}" \ "The location of the account key. " ACCOUNT_KEY=$res get_user_input "Account key length" "${ACCOUNT_KEY_LENGTH}" \ "Account key length - the default is typically the best option" ACCOUNT_KEY_LENGTH=$res get_user_input "Domain private key algorithm" "${PRIVATE_KEY_ALG}" \ "Domain private key algorithm - the default is typically the best option" PRIVATE_KEY_ALG=$res get_user_input "Check server for certificate validity" "$CHECK_REMOTE" \ "If true, getssl will check the live server for certificate validity rather than using the local certs getssl also checks after installation, that the new valid certificate is in place" CHECK_REMOTE=$res if [[ "$CHECK_REMOTE" == "true" ]]; then get_user_input "server type" "$SERVER_TYPE" \ "This can be 'webserver', 'ldap', or a port number that getssl will use for certifcate checks" SERVER_TYPE=$res else SERVER_TYPE="" fi if [[ ${SERVER_TYPE} == "webserver" ]]; then REMOTE_PORT=443 elif [[ ${SERVER_TYPE} == "ldaps" ]]; then REMOTE_PORT=636 elif [[ ${SERVER_TYPE} =~ ^[0-9]+$ ]]; then REMOTE_PORT=SERVER_TYPE fi SANS="www.${DOMAIN}" if [[ ! -z ${REMOTE_PORT} ]]; then # Additional domains - this could be multiple domains / subdomains in a comma separated list EX_CERT=$(echo | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:${REMOTE_PORT}" 2>/dev/null | openssl x509 2>/dev/null) if [ ! -z "${EX_CERT}" ]; then SANS=$(echo "$EX_CERT" | openssl x509 -noout -text 2>/dev/null| grep "Subject Alternative Name" -A2 \ | grep -Eo "DNS:[a-zA-Z 0-9.-]*" | sed "s@DNS:$DOMAIN@@g" | grep -v '^$' | cut -c 5-) SANS=${SANS//$'\n'/','} fi fi get_user_input "Additional domain names" "${SANS}" \ "this could be multiple domains / subdomains in a comma separated list" \ "use the minus sign - if you don't want any SANS" SANS=$res get_user_input "Validate via DNS" "${VALIDATE_VIA_DNS}" \ "If true, getssl will use DNS to validate the domain, if false then http / https will be used" VALIDATE_VIA_DNS=$res if [[ $VALIDATE_VIA_DNS == "true" ]]; then get_user_input "DNS add command" "${DNS_ADD_COMMAND}" \ "location/name of script which will add the token message to DNS" DNS_ADD_COMMAND=$res get_user_input "DNS del command" "${DNS_DEL_COMMAND}" \ "location/name of script which will delete the token message from DNS" DNS_DEL_COMMAND=$res get_user_input "DNS extra wait time" "${DNS_EXTRA_WAIT}" \ "delay time, to wait for DNS to propagate once changed." DNS_EXTRA_WAIT=$res else # find IP of this server LocalIP=$(dig +short @208.67.222.222 myip.opendns.com) #loop over all domains alldomains=$(echo "$DOMAIN,$SANS" | sed "s/,/ /g") dn=0 for d in $alldomains; do # find IP of domain DomainIP=$(dig +short ${d}) # if domain is local, try and find location of files if [[ "${DomainIP}" == "${LocalIP}" ]]; then if [[ ! -d "/var/www/${d}/web" ]]; then ACL[$dn]="/var/www/${DOMAIN}/web/.well-known/acme-challenge" elif [[ ! -d "/var/www/${d}" ]]; then ACL[$dn]="/var/www/${d}/.well-known/acme-challenge" else ACL[$dn]="/var/www/.well-known/acme-challenge" fi else #domain is remote ACL[$dn]="ssh:${d}:/var/www/.well-known/acme-challenge" fi get_user_input "ACL for $d" "${ACL[$dn]}" \ "The Acme challenge location for domaind ${d}. This should be your web root plus .well-known/acme-challenge" ACL[$dn]=$res ((dn++)) done fi # Location for all your certs, these can either be on the server (so full path name) or using ssh as for the ACL #DOMAIN_CERT_LOCATION="ssh:server5:/etc/ssl/domain.crt" #DOMAIN_KEY_LOCATION="ssh:server5:/etc/ssl/domain.key" #CA_CERT_LOCATION="/etc/ssl/chain.crt" #DOMAIN_CHAIN_LOCATION="" this is the domain cert and CA cert #DOMAIN_PEM_LOCATION="" this is the domain_key. domain cert and CA cert # The command needed to reload apache / nginx or whatever you use #RELOAD_CMD="" # The time period within which you want to allow renewal of a certificate # this prevents hitting some of the rate limits. # RENEW_ALLOW="30" # create domain directory if it doesn't exist if [ ! -d "$DOMAIN_DIR" ]; then info "Making domain directory - $DOMAIN_DIR" mkdir -p "$DOMAIN_DIR" fi #Write out domain config write_domain_template "$DOMAIN_DIR/getssl.cfg" # Is it worth, with this create script, setting it to run once on the "happy hacker" CA to test, before # it would then change the CA ( if all OK ) and running on the live LE server ? graceful_exit