Skip to content

Instantly share code, notes, and snippets.

@Jamesits
Last active May 6, 2024 08:30
Show Gist options
  • Save Jamesits/850150e86b70fbd864ef85c9331923b9 to your computer and use it in GitHub Desktop.
Save Jamesits/850150e86b70fbd864ef85c9331923b9 to your computer and use it in GitHub Desktop.
Let's Encrypt cert requesting and signing (using acme.sh) for Cisco ASA / AnyConnect
#!/bin/bash
set -Eeuo pipefail
# Automatic cert requesting and signing for Cisco ASA
#
# Requirements
# - Domain hosted in Aliyun DNS (for other DNS services, adjust the acme.sh arguments)
# - ASA with rest-agent enabled
# - Local computer: openssl, git, ca-certificates
#
# Notes
# - If 2 users execute `write memory` at the same time, you might result in a dead lock.
# - Don't forget to import the intermediate CA to the ASA so that ASA will send a working certificate chain.
# - IPSec VPN certificate will not be updated. (You can modify this script to achieve it.)
#
# License
# Copyright 2021 James Swineson <github@public.swineson.me>
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Config
# ASA HTTPS server URL base
ASA_BASE_URLS=(
"https://192.168.1.1"
)
# ASA login username
ASA_USERNAME="admin"
# ASA login password
ASA_PASSWORD="admin"
# Name prefix for the new trustpoint
ASA_CERT_PREFIX="le_ras_"
# The interface where SSL VPN is enabled on
ASA_SSLVPN_INTERFACE="outside"
# Aliyun API Key
Ali_Key=''
# Aliyun API Secret
Ali_Secret=''
# The domain name of the certificate
DOMAIN_NAME='*.ras.example.com'
# Password used to encrypt PFX; only used temporary. You can use a random string here but do not use symbols.
PFX_EXPORT_PASSWORD='114514'
# The actual program starts here
cd "$( dirname "${BASH_SOURCE[0]}" )"
# install acme.sh
#curl https://get.acme.sh | sh
if [ ! -d "./acme.sh/.git" ]; then
>&2 echo "Installing dependencies..."
git clone --depth 1 https://github.com/acmesh-official/acme.sh.git
else
>&2 echo "Updating dependencies..."
pushd ./acme.sh
git pull
popd
fi
# get certificate
#rm -rf temp build || true
mkdir -p temp build
# acme.sh config
# https://github.com/acmesh-official/acme.sh/wiki/Change-default-CA-to-ZeroSSL
./acme.sh/acme.sh --set-default-ca --server letsencrypt
export Ali_Key
export Ali_Secret
>&2 echo -e "Signing certificate for ${DOMAIN_NAME}..."
# if acme.sh quits with non-zero code, then MAYBE the cert doesn't need to be renewed
./acme.sh/acme.sh --log --log-level 1 --issue --home "./temp" --dns dns_ali --domain "${DOMAIN_NAME}" --cert-file "./build/pubkey.crt" --key-file "./build/privkey.pem" --ca-file "./build/ca.crt" --fullchain-file "./build/fullchain.crt" || exit 0
>&2 echo -e "Converting certificate..."
openssl pkcs12 -export -out "./build/cert.pfx" -inkey "./build/privkey.pem" -in "./build/pubkey.crt" -CAfile "./build/ca.crt" -password pass:"${PFX_EXPORT_PASSWORD}"
# upload certificate
ASA_CERT_NAME="${ASA_CERT_PREFIX}$(date +"%s")"
>&2 echo -e "Creating TrustPoint ${ASA_CERT_NAME}..."
for URL_PREFIX in "${ASA_BASE_URLS[@]}"; do
>&2 echo -e "Device ${URL_PREFIX}"
# REST agent doc:
# - https://github.com/nomyownnet/cert-asa-install/blob/master/certinstall.py
#
# caveats:
# - https://www.reddit.com/r/networking/comments/fh14v6/asa_rest_api_authorization_required/fk89f8z
# - https://www.reddit.com/r/Cisco/comments/fh124z/asa_rest_api_authorization_required/
# - https://community.cisco.com/t5/network-security/asa-rest-api-authentication-issues/td-p/3842541
# get token
>&2 echo -e "Authenticating to ASA..."
ASA_TOKEN=$(curl --insecure --silent --show-error -u "${ASA_USERNAME}:${ASA_PASSWORD}" --request "POST" --header "User-Agent: REST API Agent" -i "${URL_PREFIX}/api/tokenservices" | grep 'X-Auth-Token' | cut -d' ' -f2 | sed 's/\r//g')
#echo "Token: ${ASA_TOKEN}"
# add new certificate
>&2 echo -e "\nUploading certificate..."
curl --insecure --request "POST" --header "User-Agent: REST API Agent" --header "Content-Type: application/json" --header "X-Auth-Token: ${ASA_TOKEN}" \
--data "{\"certPass\": \"${PFX_EXPORT_PASSWORD}\", \"kind\": \"object#IdentityCertificate\", \"certText\": [\"-----BEGIN PKCS12-----\", $(openssl base64 -in ./build/cert.pfx | sed "s/^/\"/g" | sed "s/$/\",/g") \"-----END PKCS12-----\"], \"name\": \"${ASA_CERT_NAME}\"}" \
"${URL_PREFIX}/api/certificate/identity" || true # curl will return "(52) Empty reply from server"
# set certificate to work with SSLVPN
>&2 echo -e "\nUpdating SSL VPN configuration..."
curl --insecure --request "POST" --header "User-Agent: REST API Agent" --header "Content-Type: application/json" --header "X-Auth-Token: ${ASA_TOKEN}" \
--data "{\"commands\": [\"ssl trust-point ${ASA_CERT_NAME} ${ASA_SSLVPN_INTERFACE}\", \"write memory\"]}" \
"${URL_PREFIX}/api/cli"
# invalidate token
>&2 echo -e "\nLogging out..."
curl --insecure --request 'DELETE' --header "User-Agent: REST API Agent" --header "X-Auth-Token: ${ASA_TOKEN}" "${URL_PREFIX}/api/tokenservices/${ASA_TOKEN}"
>&2 echo -e "\n"
done
echo "All done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment