diff --git a/deploy/rancher.sh b/deploy/rancher.sh new file mode 100644 index 00000000..c34586b3 --- /dev/null +++ b/deploy/rancher.sh @@ -0,0 +1,270 @@ +#!/bin/bash + +# Deploy and maintain certificates within Rancher environments + +# here are the defaults, overridable via env vars + +#export RANCHER_CONFIG=${HOME}/.rancher/cli.json +#export RANCHER_ENV= + +# usage: + +# - download rancher-cli from your rancher server and use it to create cli.json +# the format of the file is quite simple, so you can just create your own +# ! also run chmod 600 ~/.rancher/cli.json, since rancher-cli doesn't +# - for multiple servers override RANCHER_CONFIG +# - for multiple environments on a server set RANCHER_ENV appropriately; +# otherwise the one within cli.json is used +# - each run with --deploy saves the rancher configuration into the domain conf file in acme.sh +# the list will be used when running acme.sh --renew[-all] to push the certificate onto rancher environments +# you have to keep the rancher config files for renewal to work +# - to remove a rancher server from a certificate you have to edit its conf file + +# deploy example: +# acme.sh --deploy -d my.website.com --deploy-hook rancher +# RANCHER_ENV=1a6 acme.sh --deploy -d my.website.com --deploy-hook rancher + +# renew example: +# acme.sh --renew -d my.website.com + + +######## Private functions ##################### + +# save rancher environment configuration in the domain config file +function _rancher_savedomainconf () { + local _rancherEnvId="$1" + local _rancherConfigFile="$2" + + # use this hash to store the config pair + local _configId=$(echo "${_rancherEnvId}@${_rancherConfigFile}" | md5sum | head -c 8) + + _info "Saving rancher information in domain file under id $_configId: environment $_rancherEnvId, config file $_rancherConfigFile" + _savedomainconf "Rancher_ConfigFile_$_configId" "$_rancherConfig" + _savedomainconf "Rancher_EnvId_$_configId" "$_rancherEnvId" + + local _rancherEnvs=$(_readdomainconf Rancher_Configs) + if [[ ! "$_rancherConfigs" = *"$_configId"* ]] ; then + _savedomainconf "Rancher_Configs" "$_rancherConfigs $_configId" + fi +} + +# read rancher's cli.json file +function _rancher_read_configfile () { + local _rancherConfigFile="$1" + _info "Reading rancher configuration $_rancherConfig" + + if [ ! -r "${_rancherConfig}" ] ; then + _err "cannot read rancher configuration" + return 1 + fi + eval $(jq --monochrome-output < "${_rancherConfig}" \ + '@sh "_rancherUrl=\(.url)","_accessKey=\(.accessKey)","_secretKey=\(.secretKey)","_envId=\(.environment)"' | xargs) + _debug _rancherUrl "$_rancherUrl" + _debug _accessKey "$_accessKey" + _secure_debug _secretKey "$_secretKey" + _debug _envId "$_envId" + + # when set by rancher-cli rancerUrl has an unwanted trailing "/schemas" + _rancherUrl=${_rancherUrl%/schemas} + + return 0 +} + +# deploy a new certificate into a rancher environment +function _rancher_deploy_cert () { + local _cert=$(<"$_ccert") + local _chain=$(<"$_cca") + local _privkey=$(<"$_ckey") + + local _curlUrl="$_rancherUrl/projects/$_envId/certificates" + local _curlMethod="POST" + local _curlAuth="$_accessKey:$_secretKey" + local _certJson=$(jq --null-input --compact-output \ + --arg cert "$_cert" \ + --arg chain "$_chain" \ + --arg privkey "$_privkey" \ + --arg name "$_cdomain" \ + '{type:"certificate",cert:$cert,certChain:$chain,key:$privkey,name:$name}') + + _debug _curlUrl "$_curlUrl" + _debug _curlMethod "$_curlMethod" + _secure_debug _curlAuth "$_curlAuth" + _secure_debug _certJson "$_certJson" + + local _curlResult=$(curl -s \ + -u "${_curlAuth}" \ + -X "${_curlMethod}" \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d "${_certJson}" \ + "${_curlUrl}" | + jq -r 'if (.type == "error") then "error: status="+(.status|tostring)+", code="+(.code|tostring)+", detail="+(.detail|tostring) else "success" end') + _debug _curlResult "$_curlResult" + if [ "$_curlResult" == "success" ] ; then + _info "Certificate successfully deployed" + return 0 + else + _err "Deployment failed: $_curlResult" + return 1 + fi +} + +# get the id of an existing certificate by domain from a rancher environment +function _get_rancher_certId () { + local _curlUrl="$_rancherUrl/projects/$_envId/certificates" + local _curlMethod="GET" + local _curlAuth="$_accessKey:$_secretKey" + local _filter=".data[] | select(.CN==\"$_cdomain\") | .id" + + _debug _curlUrl "$_curlUrl" + _debug _curlMethod "$_curlMethod" + _secure_debug _curlAuth "$_curlAuth" + _debug _filter "$_filter" + + local _curlResult=$(curl -s \ + -u "${_curlAuth}" \ + -X "${_curlMethod}" \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + "${_curlUrl}" | + jq -r "$_filter") + echo $_curlResult +} + +# update an existing certificate on a rancher environment +function _rancher_update_cert () { + local _certId=$(_get_rancher_certId) + + if [ "$_certId" == "" ] ; then + _err "Cannot find certificate for domain $_cdomain on rancher environment $_envId at url $_rancherUrl" + return 1 + fi + + _info "Found that certificate $_cdomain has id $_certId on rancher environment $_envId at url $_rancherUrl" + + local _cert=$(<"$_ccert") + local _chain=$(<"$_cca") + local _privkey=$(<"$_ckey") + + local _curlUrl="$_rancherUrl/projects/$_envId/certificates/$_certId" + local _curlMethod="PUT" + local _curlAuth="$_accessKey:$_secretKey" + local _certJson=$(jq --null-input --compact-output \ + --arg cert "$_cert" \ + --arg chain "$_chain" \ + --arg privkey "$_privkey" \ + --arg name "$_cdomain" \ + '{type:"certificate",cert:$cert,certChain:$chain,key:$privkey,name:$name}') + + _debug _curlUrl "$_curlUrl" + _debug _curlMethod "$_curlMethod" + _secure_debug _curlAuth "$_curlAuth" + _secure_debug _certJson "$_certJson" + + local _curlResult=$(curl -s \ + -u "${_curlAuth}" \ + -X "${_curlMethod}" \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d "${_certJson}" \ + "${_curlUrl}" | + jq -r 'if (.type == "error") then "error: status="+(.status|tostring)+", code="+(.code|tostring)+", detail="+(.detail|tostring) else "success" end') + _debug _curlResult "$_curlResult" + + if [ "$_curlResult" == "success" ] ; then + _info "Certificate successfully updated" + return 0 + else + _err "Certificate update failed: $_curlResult" + return 1 + fi +} + +# deploy a new certificate into a rancher environment +function _rancher_deploy () { + local _defaultRancherConfig=${HOME}/.rancher/cli.json + local _rancherConfig=${RANCHER_CONFIG:-${_defaultRancherConfig}} + + _rancher_read_configfile "${_rancherConfig}" + local _success=$? + if [ "$_success" != "0" ] ; then + return 1 + fi + + if [ -n "${RANCHER_ENV}" ] ; then + _envId="${RANCHER_ENV}" + fi + + if [ -z "$_envId" ] ; then + _err "Empty rancher env ID" + return 1 + fi + + _info "Deploying certificate $_cdomain into rancher environment $_envId at $_rancherUrl" + _rancher_deploy_cert + local _success=$? + if [ "$_success" == "0" ] ; then + _rancher_savedomainconf "$_envId" "$_rancherConfig" + return 0 + else + return 1 + fi +} + +# renew an existing certificate in a rancher environment +function _rancher_renew () { + local _rancherConfigs=$(_readdomainconf Rancher_Configs) + _info "Found rancher env configs: $_rancherConfigs" + for _configId in $_rancherConfigs ; do + _info "Processing rancher config $_configId" + local _rancherConfig=$(_readdomainconf "Rancher_ConfigFile_$_configId") + _rancher_read_configfile "${_rancherConfig}" + _envId=$(_readdomainconf "Rancher_EnvId_$_configId") + local _success=$? + if [ "$_success" != "0" ] ; then + return 1 + fi + + _info "Updating certificate $_cdomain from rancher environment $_envId at $_rancherUrl" + _rancher_update_cert + local _success=$? + if [ "$_success" != "0" ] ; then + return 1 + fi + done + + return 0 +} + + + +######## Public functions ##################### + + +# domain keyfile certfile cafile fullchain +rancher_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if ! _exists jq; then + _err "The command jq is not found." + return 1 + fi + + _debug IS_RENEW "$IS_RENEW" + if [ "$IS_RENEW" == "1" ] ; then + _rancher_renew + else + _rancher_deploy + fi + return $? +}