Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deploy hook for Ruckus ZoneDirector / Unleashed #4832

Merged
merged 13 commits into from
Dec 7, 2024
173 changes: 173 additions & 0 deletions deploy/ruckus.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/usr/bin/env sh

# Here is a script to deploy cert to Ruckus ZoneDirector / Unleashed.
#
# Public domain, 2024, Tony Rielly <https://github.com/ms264556>
#
# ```sh
# acme.sh --deploy -d ruckus.example.com --deploy-hook ruckus
# ```
#
# Then you need to set the environment variables for the
# deploy script to work.
#
# ```sh
# export RUCKUS_HOST=myruckus.example.com
# export RUCKUS_USER=myruckususername
# export RUCKUS_PASS=myruckuspassword
#
# acme.sh --deploy -d myruckus.example.com --deploy-hook ruckus
# ```
#
# returns 0 means success, otherwise error.

######## Public functions #####################

#domain keyfile certfile cafile fullchain
ruckus_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_err_code=0

_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"

_getdeployconf RUCKUS_HOST
_getdeployconf RUCKUS_USER
_getdeployconf RUCKUS_PASS

if [ -z "$RUCKUS_HOST" ]; then
_debug "Using _cdomain as RUCKUS_HOST, please set if not correct."
RUCKUS_HOST="$_cdomain"
fi

if [ -z "$RUCKUS_USER" ]; then
_err "Need to set the env variable RUCKUS_USER"
return 1
fi

if [ -z "$RUCKUS_PASS" ]; then
_err "Need to set the env variable RUCKUS_PASS"
return 1
fi

_savedeployconf RUCKUS_HOST "$RUCKUS_HOST"
_savedeployconf RUCKUS_USER "$RUCKUS_USER"
_savedeployconf RUCKUS_PASS "$RUCKUS_PASS"

_debug RUCKUS_HOST "$RUCKUS_HOST"
_debug RUCKUS_USER "$RUCKUS_USER"
_debug RUCKUS_PASS "$RUCKUS_PASS"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use _secure_debug for password, so that you won't leak it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


export HTTPS_INSECURE=1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need "insecure" here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruckus devices ship with 1024 bit self-signed server certificates, so curl will fail with error 60 unless I set HTTPS_INSECURE=1.

Copy link

@uberjay uberjay Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overriding HTTPS_INSECURE to always be enabled seems wrong to me -- for initial deployment one can add --insecure: acme.sh --deploy -d your.domain.here --deploy-hook ruckus --insecure. On subsequent cert deploy runs, HTTPS_INSECURE=1 should no longer be necessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem. That's fixed now.

export ACME_HTTP_NO_REDIRECTS=1

_info Discovering the login URL
_get "https://$RUCKUS_HOST" >/dev/null
_login_url="$(_response_header 'Location')"
if [ -n "$_login_url" ]; then
_login_path=$(echo "$_login_url" | sed 's|https\?://[^/]\+||')
if [ -z "$_login_path" ]; then
# redirect was to a different host
_get "$_login_url" >/dev/null
_login_url="$(_response_header 'Location')"
fi
fi

if [ -z "${_login_url}" ]; then
_err "Connection failed: couldn't find login page."
return 1
fi

_base_url=$(dirname "$_login_url")
_login_page=$(basename "$_login_url")

if [ "$_login_page" = "index.html" ]; then
_err "Connection temporarily unavailable: Unleashed Rebuilding."
return 1
fi

if [ "$_login_page" = "wizard.jsp" ]; then
_err "Connection failed: Setup Wizard not complete."
return 1
fi

_info Login
_username_encoded="$(printf "%s" "$RUCKUS_USER" | _url_encode)"
_password_encoded="$(printf "%s" "$RUCKUS_PASS" | _url_encode)"
_login_query="$(printf "%s" "username=${_username_encoded}&password=${_password_encoded}&ok=Log+In")"
_post "$_login_query" "$_login_url" >/dev/null

_login_code="$(_response_code)"
if [ "$_login_code" = "200" ]; then
_err "Login failed: incorrect credentials."
return 1
fi

_info Collect Session Cookie
_H1="Cookie: $(_response_cookie)"
export _H1
_info Collect CSRF Token
_H2="X-CSRF-Token: $(_response_header 'HTTP_X_CSRF_TOKEN')"
export _H2

_info "Uploading certificate"
_post_upload "uploadcert" "$_cfullchain"

_info "Uploading private key"
_post_upload "uploadprivatekey" "$_ckey"

_info "Replacing certificate"
_replace_cert_ajax='<ajax-request action="docmd" comp="system" updater="rid.0.5" xcmd="replace-cert" checkAbility="6" timeout="-1"><xcmd cmd="replace-cert" cn="'$RUCKUS_HOST'"/></ajax-request>'
_post "$_replace_cert_ajax" "$_base_url/_cmdstat.jsp" >/dev/null

info "Rebooting"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use _info

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, and I also fixed the unquoted _info strings

_cert_reboot_ajax='<ajax-request action="docmd" comp="worker" updater="rid.0.5" xcmd="cert-reboot" checkAbility="6"><xcmd cmd="cert-reboot" action="undefined"/></ajax-request>'
_post "$_cert_reboot_ajax" "$_base_url/_cmdstat.jsp" >/dev/null

return 0
}

_response_code() {
< "$HTTP_HEADER" _egrep_o "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n" | _egrep_o "^[0-9]*"
}

_response_header() {
< "$HTTP_HEADER" grep -i "^$1:" | cut -d ':' -f 2- | tr -d "\r\n\t "
}

_response_cookie() {
_response_header 'Set-Cookie' | awk -F';' '{for(i=1;i<=NF;i++) if (tolower($i) !~ /(path|domain|expires|max-age|secure|httponly|samesite)/) printf "%s; ", $i}' | sed 's/; $//'
}

_post_upload() {
_post_action="$1"
_post_file="$2"

_post_boundary="----FormBoundary$(date "+%s%N")"

_post_data="$({
printf -- "--%s\r\n" "$_post_boundary"
printf -- "Content-Disposition: form-data; name=\"u\"; filename=\"%s\"\r\n" "$_post_action"
printf -- "Content-Type: application/octet-stream\r\n\r\n"
printf -- "%s\r\n" "$(cat "$_post_file")"

printf -- "--%s\r\n" "$_post_boundary"
printf -- "Content-Disposition: form-data; name=\"action\"\r\n\r\n"
printf -- "%s\r\n" "$_post_action"

printf -- "--%s\r\n" "$_post_boundary"
printf -- "Content-Disposition: form-data; name=\"callback\"\r\n\r\n"
printf -- "%s\r\n" "uploader_$_post_action"

printf -- "--%s--\r\n\r\n" "$_post_boundary"
})"

_post "$_post_data" "$_base_url/_upload.jsp?request_type=xhr" "" "" "multipart/form-data; boundary=$_post_boundary" >/dev/null
}