Last active
May 30, 2020 20:46
-
-
Save QNimbus/08dd3a44cddfc7b8ffc22aad1f012645 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
## | |
## Title: deploy_freenas.sh | |
## Description: Script to deploy uploaded certificates for use with VMWare ESXi | |
## Author: B. van wetten | |
## Created date: 28-01-2020 | |
## Updated date: 12-02-2020 | |
## Version: 0.3 | |
## GitHub Gist: https://gist.github.com/QNimbus/08dd3a44cddfc7b8ffc22aad1f012645 | |
## | |
## Usage: deploy_freenas.sh | |
## Notes: For remote use (e.g. SSH) use the following command as example: | |
## cat fullchain.cer /var/etc/acme-client/home/lan.besqua.red/lan.besqua.red.key | ssh -oStrictHostKeyChecking=no -T -i <keyfile> data-admin@<freenas host> | |
## | |
## You can use this in your 'authorized_keys' file to run this command remotely: | |
## command="/bin/cat | /usr/bin/xargs -0 -r -J % /mnt/storage/scripts/certs/deploy_freenas.sh -u <username> -p '<password>' -n <certname> -c % --",no-pty,from="1.2.3.4/32" ssh-rsa <public-key-here>== data-admin | |
# Shell utilities | |
RM=$(which rm); [[ $? != 0 ]] && echo "Command 'rm' not found" >&2 && exit 1 | |
CP=$(which cp); [[ $? != 0 ]] && echo "Command 'cp' not found" >&2 && exit 1 | |
MV=$(which mv); [[ $? != 0 ]] && echo "Command 'mv' not found" >&2 && exit 1 | |
JQ=$(which jq); [[ $? != 0 ]] && echo "Command 'jq' not found" >&2 && exit 1 | |
FIND=$(which find); [[ $? != 0 ]] && echo "Command 'find' not found" >&2 && exit 1 | |
CURL=$(which curl); [[ $? != 0 ]] && echo "Command 'curl' not found" >&2 && exit 1 | |
XARGS=$(which xargs); [[ $? != 0 ]] && echo "Command 'xargs' not found" >&2 && exit 1 | |
PYTHON=$(which python); [[ $? != 0 ]] && echo "Command 'python' not found" >&2 && exit 1 | |
OPENSSL=$(which openssl); [[ $? != 0 ]] && echo "Command 'openssl' not found" >&2 && exit 1 | |
GETOPT=$(which getopt); [[ $? != 0 ]] && echo "Command 'getopt' not found" >&2 && exit 1 | |
# Initialize variables | |
_PWD="/mnt/storage/scripts/certs" | |
_ME=$(basename "${0}") | |
_TMPFILE="tmp-${$}-${RANDOM}" | |
# Option strings | |
SHORT=hu:p:n: | |
LONG=help,username:,password:,name: | |
########################################################################## | |
# Init # | |
########################################################################## | |
# Cleanup any temp files after script execution (traps signals: 0, 1, 2, 15) | |
trap '${FIND} . -maxdepth 1 -name "${_TMPFILE}*" -type f -print0 | ${XARGS} -I % -0 rm %' INT TERM HUP EXIT | |
# Read certificate from command line and remove from positional arguments | |
for((i=1; i<=$#; i++)) | |
do | |
_cur=$i | |
_next=$(expr $i + 1) | |
_param=${!_cur} | |
_next_param=${!_next:-} | |
if [[ "${_param}" =~ (^-c$) && ! -z "${_next_param}" ]] | |
then | |
echo -en "${_next_param}" > "${_TMPFILE}.pem" | |
set -- "${@:1:_cur-1}" "${@:_cur+2}" | |
fi | |
done | |
# Get command line arguments | |
if [[ "${#}" -gt 0 ]] | |
then | |
getopt -T > /dev/null | |
if [ $? -eq 4 ]; then | |
# GNU enhanced getopt is available | |
_ARGS=$(${GETOPT} --name "$_ME" --long ${LONG} --options ${SHORT} -- "$@") | |
else | |
# Original getopt is available (no long option names, no whitespace, no sorting) | |
_ARGS=$(${GETOPT} ${SHORT} "$@") | |
fi | |
if [ $? -ne 0 ]; then | |
echo "$_ME: usage error (use -h for help)" >&2 | |
exit 2 | |
fi | |
fi | |
############################################################################### | |
# Functions # | |
############################################################################### | |
# _print_usage() | |
# | |
# Usage: | |
# _print_usage | |
# | |
# Print the program usage information. | |
_print_usage() { | |
cat <<HEREDOC | |
___ ____ ___ _ ____ _ _ ____ ____ ____ ____ _ _ ____ ____ | |
| \ |___ |__] | | | \_/ |___ |__/ |___ |___ |\ | |__| [__ | |
|__/ |___ | |___ |__| | ___ | | \ |___ |___ | \| | | ___] | |
Usage: | |
${_ME} -u username -p password -n name -c file | |
${_ME} -h | |
Options: | |
-h Show this screen | |
-n Certificate name | |
-c Certificate file | |
-u FreeNAS API access username | |
-p FreeNAS API access password | |
HEREDOC | |
exit ${1:-0} | |
} | |
# _json_escape() | |
# | |
# Usage: | |
# _json_escape variable value | |
# | |
# Escape a JSON object (e.g. for use with 'curl') | |
_json_escape () { | |
printf '%s' "$1" | ${PYTHON} -c 'import json,sys; print(json.dumps(sys.stdin.read()))' | |
} | |
# _set_variable() | |
# | |
# Usage: | |
# _set_variable variable value | |
# | |
# Sets the variable to a value if not already set. Otherwise exits with an error message | |
_set_variable() | |
{ | |
local varname="$1" | |
shift | |
if [ -z "${!varname:-}" ]; then | |
# eval="${varname}=${@@Q}" | |
eval "$varname=\"$@\"" | |
else | |
echo "Error: $varname already set" | |
usage | |
fi | |
} | |
# _parse_commandline_arguments() | |
# | |
# Usage: | |
# _parse_commandline_arguments | |
# | |
# Parses and validates commandline arguments and populates appropriate variables. | |
_parse_commandline_arguments() | |
{ | |
while true; | |
do | |
case "${1:-}" in | |
-u|--username) | |
[[ -z "${2}" || "${2}" == *[[:space:]]* || "${2}" == -* ]] && { echo "$_ME: $1 needs a value" >&2; _print_usage 1; } | |
_set_variable USERNAME "${2}" | |
shift 2 | |
;; | |
-p|--password) | |
[[ -z "${2}" || "${2}" == *[[:space:]]* || "${2}" == -* ]] && { echo "$_ME: $1 needs a value" >&2; _print_usage 1; } | |
_set_variable PASSWORD "${2}" | |
shift 2 | |
;; | |
-n|--name) | |
[[ -z "${2}" || "${2}" == *[[:space:]]* || "${2}" == -* ]] && { echo "$_ME: $1 needs a value" >&2; _print_usage 1; } | |
_set_variable CERT_NAME "${2}" | |
shift 2 | |
;; | |
\?) echo "$_ME: Unknown option -$1" >&2; | |
exit 1 | |
;; | |
:) echo "$_ME: -$1 needs a value" >&2; | |
exit 1 | |
;; | |
*) shift | |
break | |
;; | |
esac | |
done | |
} | |
# _validate_parameters() | |
# | |
# Usage: | |
# _validate_parameters | |
# | |
# Performs several checks on the supplied command line arguments | |
_validate_parameters() { | |
# Check if $USERNAME and $PASSWORD are set and not empty | |
[[ -z "${USERNAME:-}" || -z "${PASSWORD:-}" ]] && echo -e "$_ME: Username and password are required\n" && _print_usage 1; | |
# Check certificates | |
[[ ! -f "${_TMPFILE}.pem" || ! $(${OPENSSL} rsa -in "${_TMPFILE}.pem" -check 2> /dev/null) ]] && echo -e "$_ME: Invalid or no certificate supplied\n" && _print_usage 1; | |
# Check if $USERNAME and $PASSWORD are set and not empty | |
[[ -z "${CERT_NAME:-}" ]] && echo -e "$_ME: Certificate name is required\n" && _print_usage 1; | |
} | |
############################################################################### | |
# Main # | |
############################################################################### | |
# _main() | |
# | |
# Usage: | |
# _main [<options>] [<arguments>] | |
# | |
# Description: | |
# Entry point for the program, handling basic option parsing and dispatching. | |
_main() { | |
# Avoid complex option parsing when only one program option is expected. | |
if [[ "${@:-}" =~ -h|--help ]] | |
then | |
_print_usage | |
else | |
_parse_commandline_arguments "$@" | |
_validate_parameters | |
# Extract certificate, key and certificate authority chain | |
${OPENSSL} x509 -in "${_TMPFILE}.pem" -outform PEM -out "${_TMPFILE}.cert.pem" || exit 1 | |
${OPENSSL} pkey -out "${_TMPFILE}.key.pem" -in "${_TMPFILE}.pem" || exit 1 | |
${OPENSSL} crl2pkcs7 -nocrl -certfile "${_TMPFILE}.pem" > "${_TMPFILE}.pkcs7" || exit 1 | |
${OPENSSL} pkcs7 -print_certs -out "${_TMPFILE}.chain.pem" -in "${_TMPFILE}.pkcs7" || exit 1 | |
chain=$(<${_TMPFILE}.chain.pem) | |
key=$(<${_TMPFILE}.key.pem) | |
chain=$(_json_escape "${chain}") | |
key=$(_json_escape "${key}") | |
# Check if certificate already exists | |
existing_cert_id=$(curl -X GET "https://${HOST:-localhost}/api/v2.0/certificate" -H "accept: */*" --silent --insecure --user "${USERNAME}:${PASSWORD}" | jq ".[] | select(.name == \"${CERT_NAME}\") | .id") | |
if [[ -n $existing_cert_id ]] | |
then | |
# Rename existing certificate to prevent conflict | |
rename_job_id=$(curl -X PUT "https://${HOST:-localhost}/api/v2.0/certificate/id/${existing_cert_id}" -H "accept: */*" -H "Content-Type: application/json" --silent --insecure --user "root:tvAQCGWx7aBCAhp&" -d "{\"name\":\"${CERT_NAME}-old\"}") | |
# [START] Give FreeNAS time to complete job | |
success=false | |
for retry in {0..5} | |
do | |
# Wait for 1 second | |
sleep 1 | |
# Get job result | |
result=$(curl -X GET "https://${HOST:-localhost}/api/v2.0/core/get_jobs" -H "accept: */*" --silent --insecure --user "${USERNAME}:${PASSWORD}" | jq ".[] | select(.id == ${rename_job_id}) | .state") | |
# Stop looping if job was completed successfully | |
[[ "${result}" =~ SUCCESS ]] && success=true && break | |
done | |
[[ "${success}" = false ]] && echo -e "Failed to rename old certificate\nResult: ${result}\n" && exit 1; | |
# [END] Give FreeNAS time to complete job | |
fi | |
# Upload new certificate | |
create_job_id=$(curl -X POST "https://${HOST:-localhost}/api/v2.0/certificate" -H "accept: */*" -H "Content-Type: application/json" --silent --insecure --user "root:tvAQCGWx7aBCAhp&" -d "{\"name\":\"${CERT_NAME}\",\"certificate\":${chain},\"privatekey\":${key},\"create_type\":\"CERTIFICATE_CREATE_IMPORTED\"}") | |
# [START] Give FreeNAS time to complete job | |
success=false | |
for retry in {0..5} | |
do | |
# Wait for 1 second | |
sleep 1 | |
# Get job result | |
result=$(curl -X GET "https://${HOST:-localhost}/api/v2.0/core/get_jobs" -H "accept: */*" --silent --insecure --user "${USERNAME}:${PASSWORD}" | jq ".[] | select(.id == ${create_job_id}) | .state") | |
# Stop looping if job was completed successfully | |
[[ "${result}" =~ SUCCESS ]] && success=true && break | |
done | |
[[ "${success}" = false ]] && echo -e "Failed to create new certificate\nResult: ${result}\n" && exit 1; | |
# [END] Give FreeNAS time to complete job | |
# Get new certificate id | |
new_cert_id=$(curl -X GET "https://${HOST:-localhost}/api/v2.0/certificate" -H "accept: */*" --silent --insecure --user "${USERNAME}:${PASSWORD}" | jq ".[] | select(.name == \"${CERT_NAME}\") | .id") | |
# If certificate was not found, exit (should not happen) | |
if [[ -z $new_cert_id ]] | |
then | |
echo -e "Could not find newly created certificate '${CERT_NAME}'\n" && exit 1; | |
fi | |
# Set new certificate as active | |
result=$(curl -X PUT "https://${HOST:-localhost}/api/v2.0/system/general" -H "accept: */*" -H "Content-Type: application/json" --write-out "%{http_code}" --output /dev/null --silent --insecure --user "root:tvAQCGWx7aBCAhp&" -d "{\"ui_certificate\":\"${new_cert_id}\"}") | |
[[ ! "${result}" =~ ^20[0-9]$ ]] && echo -e "Failed to activate new certificate\nResult: ${result}\n" && exit 1; | |
# If we found an existing certificate earlier - remove it | |
if [[ -n $existing_cert_id ]] | |
then | |
delete_cert_job_id=$(curl -X DELETE "https://${HOST:-localhost}/api/v2.0/certificate/id/${existing_cert_id}" -H "accept: */*" -H "Content-Type: application/json" --silent --insecure --user "root:tvAQCGWx7aBCAhp&") | |
# [START] Give FreeNAS time to complete job | |
success=false | |
for retry in {0..5} | |
do | |
# Wait for 1 second | |
sleep 1 | |
# Get job result | |
result=$(curl -X GET "https://${HOST:-localhost}/api/v2.0/core/get_jobs" -H "accept: */*" --silent --insecure --user "${USERNAME}:${PASSWORD}" | jq ".[] | select(.id == ${delete_cert_job_id}) | .state") | |
# Stop looping if job was completed successfully | |
[[ "${result}" =~ SUCCESS ]] && success=true && break | |
done | |
[[ "${success}" = false ]] && echo -e "Failed to remove old certificate\nResult: ${result}\n" && exit 1; | |
# [END] Give FreeNAS time to complete job | |
fi | |
# Restart web ui (does not return HTTP 200 because web server restarts) | |
result=$(curl -X GET "https://${HOST:-localhost}/api/v2.0/system/general/ui_restart" -H "accept: */*" -H "Content-Type: application/json" --write-out "%{http_code}" --output /dev/null --silent --insecure --user "root:tvAQCGWx7aBCAhp&") | |
fi | |
} | |
# Call `_main` after everything has been defined. | |
_main "$@" | |
exit 0; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment