mirror of
https://github.com/dehydrated-io/dehydrated.git
synced 2026-03-20 08:14:00 +01:00
experimental json.sh support
This commit is contained in:
80
dehydrated
80
dehydrated
@@ -17,7 +17,9 @@ umask 077 # paranoid umask, we're creating private keys
|
||||
exec 3>&-
|
||||
exec 4>&-
|
||||
|
||||
VERSION="0.6.5"
|
||||
. ./json.sh
|
||||
|
||||
VERSION="0.6.6"
|
||||
|
||||
# Find directory in which this script is stored by traversing all symbolic links
|
||||
SOURCE="${0}"
|
||||
@@ -298,7 +300,8 @@ init_system() {
|
||||
fi
|
||||
|
||||
# Get CA URLs
|
||||
CA_DIRECTORY="$(http_request get "${CA}")"
|
||||
CA_DIRECTORY_RAW="$(http_request get "${CA}")"
|
||||
CA_DIRECTORY="$(printf "%s" "${CA_DIRECTORY_RAW}" | jsonsh)"
|
||||
|
||||
# Automatic discovery of API version
|
||||
if [[ "${API}" = "auto" ]]; then
|
||||
@@ -407,11 +410,11 @@ init_system() {
|
||||
# Read account information or request from CA if missing
|
||||
if [[ -e "${ACCOUNT_KEY_JSON}" ]]; then
|
||||
if [[ ${API} -eq 1 ]]; then
|
||||
ACCOUNT_ID="$(cat "${ACCOUNT_KEY_JSON}" | get_json_int_value id)"
|
||||
ACCOUNT_ID="$(cat "${ACCOUNT_KEY_JSON}" | jsonsh | get_json_int_value id)"
|
||||
ACCOUNT_URL="${CA_REG}/${ACCOUNT_ID}"
|
||||
else
|
||||
if [[ -e "${ACCOUNT_ID_JSON}" ]]; then
|
||||
ACCOUNT_URL="$(cat "${ACCOUNT_ID_JSON}" | get_json_string_value url)"
|
||||
ACCOUNT_URL="$(cat "${ACCOUNT_ID_JSON}" | jsonsh | get_json_string_value url)"
|
||||
fi
|
||||
# if account URL is not storred, fetch it from the CA
|
||||
if [[ -z "${ACCOUNT_URL:-}" ]]; then
|
||||
@@ -468,41 +471,6 @@ hex2bin() {
|
||||
printf -- "$(cat | _sed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')"
|
||||
}
|
||||
|
||||
# Get string value from json dictionary
|
||||
get_json_string_value() {
|
||||
local filter
|
||||
filter=$(printf 's/.*"%s": *"\([^"]*\)".*/\\1/p' "$1")
|
||||
sed -n "${filter}"
|
||||
}
|
||||
|
||||
# Get array value from json dictionary
|
||||
get_json_array_value() {
|
||||
local filter
|
||||
filter=$(printf 's/.*"%s": *\\[\([^]]*\)\\].*/\\1/p' "$1")
|
||||
sed -n "${filter}"
|
||||
}
|
||||
|
||||
# Get sub-dictionary from json
|
||||
get_json_dict_value() {
|
||||
local filter
|
||||
filter=$(printf 's/.*"%s": *{\([^}]*\)}.*/\\1/p' "$1")
|
||||
sed -n "${filter}"
|
||||
}
|
||||
|
||||
# Get integer value from json
|
||||
get_json_int_value() {
|
||||
local filter
|
||||
filter=$(printf 's/.*"%s": *\([0-9]*\).*/\\1/p' "$1")
|
||||
sed -n "${filter}"
|
||||
}
|
||||
|
||||
rm_json_arrays() {
|
||||
local filter
|
||||
filter='s/\[[^][]*\]/null/g'
|
||||
# remove three levels of nested arrays
|
||||
sed -e "${filter}" -e "${filter}" -e "${filter}"
|
||||
}
|
||||
|
||||
# OpenSSL writes to stderr/stdout even when there are no errors. So just
|
||||
# display the output if the exit code was != 0 to simplify debugging.
|
||||
_openssl() {
|
||||
@@ -702,14 +670,14 @@ sign_csr() {
|
||||
|
||||
echo " + Requesting new certificate order from CA..."
|
||||
order_location="$(signed_request "${CA_NEW_ORDER}" '{"identifiers": '"${challenge_identifiers}"'}' 4>&1 | grep -i ^Location: | awk '{print $2}' | tr -d '\r\n')"
|
||||
result="$(signed_request "${order_location}" "" | clean_json)"
|
||||
result="$(signed_request "${order_location}" "" | jsonsh)"
|
||||
|
||||
order_authorizations="$(echo ${result} | get_json_array_value authorizations)"
|
||||
order_authorizations="$(echo "${result}" | get_json_array_values authorizations)"
|
||||
finalize="$(echo "${result}" | get_json_string_value finalize)"
|
||||
|
||||
local idx=0
|
||||
for uri in ${order_authorizations}; do
|
||||
authorizations[${idx}]="$(echo "${uri}" | _sed -e 's/\"(.*)".*/\1/')"
|
||||
authorizations[${idx}]="${uri}"
|
||||
idx=$((idx+1))
|
||||
done
|
||||
echo " + Received ${idx} authorizations URLs from the CA"
|
||||
@@ -727,14 +695,14 @@ sign_csr() {
|
||||
for authorization in ${authorizations[*]}; do
|
||||
if [[ "${API}" -eq 2 ]]; then
|
||||
# Receive authorization ($authorization is authz uri)
|
||||
response="$(signed_request "$(echo "${authorization}" | _sed -e 's/\"(.*)".*/\1/')" "" | clean_json)"
|
||||
response="$(signed_request "$(echo "${authorization}" | _sed -e 's/\"(.*)".*/\1/')" "" | jsonsh)"
|
||||
identifier="$(echo "${response}" | get_json_dict_value identifier | get_json_string_value value)"
|
||||
echo " + Handling authorization for ${identifier}"
|
||||
else
|
||||
# Request new authorization ($authorization is altname)
|
||||
identifier="${authorization}"
|
||||
echo " + Requesting authorization for ${identifier}..."
|
||||
response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${identifier}"'"}}' | clean_json)"
|
||||
response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${identifier}"'"}}' | jsonsh)"
|
||||
fi
|
||||
|
||||
# Check if authorization has already been validated
|
||||
@@ -744,20 +712,22 @@ sign_csr() {
|
||||
fi
|
||||
|
||||
# Find challenge in authorization
|
||||
challenges="$(echo "${response}" | _sed 's/.*"challenges": \[(\{.*\})\].*/\1/')"
|
||||
challenge="$(<<<"${challenges}" _sed -e 's/^[^\[]+\[(.+)\]$/\1/' -e 's/\}(, (\{)|(\]))/}\'$'\n''\2/g' | grep \""${CHALLENGETYPE}"\" || true)"
|
||||
if [ -z "${challenge}" ]; then
|
||||
allowed_validations="$(grep -Eo '"type": "[^"]+"' <<< "${challenges}" | grep -Eo ' "[^"]+"' | _sed -e 's/"//g' -e 's/^ //g')"
|
||||
challengeindex="$(echo "${response}" | grep -E '^\["challenges",[0-9]+,"type"\]\s+"'"${CHALLENGETYPE}"'"' | cut -d',' -f2 || true)"
|
||||
|
||||
if [ -z "${challengeindex}" ]; then
|
||||
allowed_validations="$(echo "${response}" | grep -E '^\["challenges",[0-9]+,"type"\]' | sed -e 's/\[[^\]*\]\s*//g' -e 's/^"//' -e 's/"$//' | tr '\n' ' ')"
|
||||
_exiterr "Validating this certificate is not possible using ${CHALLENGETYPE}. Possible validation methods are: ${allowed_validations}"
|
||||
fi
|
||||
challenge="$(echo "${response}" | get_json_dict_value -p '"challenges",'"${challengeindex}")"
|
||||
|
||||
# Gather challenge information
|
||||
challenge_names[${idx}]="${identifier}"
|
||||
challenge_tokens[${idx}]="$(echo "${challenge}" | get_json_string_value token)"
|
||||
|
||||
if [[ ${API} -eq 2 ]]; then
|
||||
challenge_uris[${idx}]="$(echo "${challenge}" | _sed 's/"validationRecord": ?\[[^]]+\]//g' | get_json_string_value url)"
|
||||
challenge_uris[${idx}]="$(echo "${challenge}" | get_json_string_value url)"
|
||||
else
|
||||
challenge_uris[${idx}]="$(echo "${challenge}" | _sed 's/"validationRecord": ?\[[^]]+\]//g' | get_json_string_value uri)"
|
||||
challenge_uris[${idx}]="$(echo "${challenge}" | get_json_dict_value validationRecord | get_json_string_value uri)"
|
||||
fi
|
||||
|
||||
# Prepare challenge tokens and deployment parameters
|
||||
@@ -810,12 +780,12 @@ sign_csr() {
|
||||
|
||||
# Ask the acme-server to verify our challenge and wait until it is no longer pending
|
||||
if [[ ${API} -eq 1 ]]; then
|
||||
result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauths[${idx}]}"'"}' | clean_json)"
|
||||
result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauths[${idx}]}"'"}' | jsonsh)"
|
||||
else
|
||||
result="$(signed_request "${challenge_uris[${idx}]}" '{}' | clean_json)"
|
||||
result="$(signed_request "${challenge_uris[${idx}]}" '{}' | jsonsh)"
|
||||
fi
|
||||
|
||||
reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
|
||||
reqstatus="$(echo "${result}" | get_json_string_value status)"
|
||||
|
||||
while [[ "${reqstatus}" = "pending" ]]; do
|
||||
sleep 1
|
||||
@@ -824,7 +794,7 @@ sign_csr() {
|
||||
else
|
||||
result="$(http_request get "${challenge_uris[${idx}]}")"
|
||||
fi
|
||||
reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
|
||||
reqstatus="$(echo "${result}" | get_json_string_value status)"
|
||||
done
|
||||
|
||||
[[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}"
|
||||
@@ -870,7 +840,7 @@ sign_csr() {
|
||||
crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | "${OPENSSL}" base64 -e)"
|
||||
crt="$( printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" )"
|
||||
else
|
||||
result="$(signed_request "${finalize}" '{"csr": "'"${csr64}"'"}' | clean_json)"
|
||||
result="$(signed_request "${finalize}" '{"csr": "'"${csr64}"'"}' | jsonsh)"
|
||||
while :; do
|
||||
status="$(echo "${result}" | get_json_string_value status)"
|
||||
case "${status}"
|
||||
|
||||
190
json.sh
Normal file
190
json.sh
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Generate json.sh path matching string
|
||||
json_path() {
|
||||
if [ ! "${1}" = "-p" ]; then
|
||||
printf '"%s"' "${1}"
|
||||
else
|
||||
printf '%s' "${2}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get string value from json dictionary
|
||||
get_json_string_value() {
|
||||
local filter
|
||||
filter="$(printf 's/.*\[%s\]\s*"\([^"]*\)"/\\1/p' "$(json_path "${1:-}" "${2:-}")")"
|
||||
sed -n "${filter}"
|
||||
}
|
||||
|
||||
# Get array values from json dictionary
|
||||
get_json_array_values() {
|
||||
grep -E '^\["'"$1"'",[0-9]*\]' | sed -e 's/\[[^\]*\]\s*//g' -e 's/^"//' -e 's/"$//'
|
||||
}
|
||||
|
||||
# Get sub-dictionary from json
|
||||
get_json_dict_value() {
|
||||
local filter
|
||||
echo "$(json_path "${1:-}" "${2:-}")"
|
||||
filter="$(printf 's/.*\[%s\]\s*\(.*\)/\\1/p' "$(json_path "${1:-}" "${2:-}")")"
|
||||
sed -n "${filter}" | jsonsh
|
||||
}
|
||||
|
||||
# Get integer value from json
|
||||
get_json_int_value() {
|
||||
local filter
|
||||
filter="$(printf 's/.*\[%s\]\s*\([^"]*\)/\\1/p' "$(json_path "${1:-}" "${2:-}")")"
|
||||
sed -n "${filter}"
|
||||
}
|
||||
|
||||
jsonsh() {
|
||||
# Modified from https://github.com/dominictarr/JSON.sh
|
||||
# Original Copyright (c) 2011 Dominic Tarr
|
||||
# Licensed under The MIT License
|
||||
|
||||
throw() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
awk_egrep () {
|
||||
local pattern_string=$1
|
||||
|
||||
gawk '{
|
||||
while ($0) {
|
||||
start=match($0, pattern);
|
||||
token=substr($0, start, RLENGTH);
|
||||
print token;
|
||||
$0=substr($0, start+RLENGTH);
|
||||
}
|
||||
}' pattern="$pattern_string"
|
||||
}
|
||||
|
||||
tokenize () {
|
||||
local GREP
|
||||
local ESCAPE
|
||||
local CHAR
|
||||
|
||||
if echo "test string" | egrep -ao --color=never "test" >/dev/null 2>&1
|
||||
then
|
||||
GREP='egrep -ao --color=never'
|
||||
else
|
||||
GREP='egrep -ao'
|
||||
fi
|
||||
|
||||
if echo "test string" | egrep -o "test" >/dev/null 2>&1
|
||||
then
|
||||
ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
|
||||
CHAR='[^[:cntrl:]"\\]'
|
||||
else
|
||||
GREP=awk_egrep
|
||||
ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
|
||||
CHAR='[^[:cntrl:]"\\\\]'
|
||||
fi
|
||||
|
||||
local STRING="\"$CHAR*($ESCAPE$CHAR*)*\""
|
||||
local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?'
|
||||
local KEYWORD='null|false|true'
|
||||
local SPACE='[[:space:]]+'
|
||||
|
||||
# Force zsh to expand $A into multiple words
|
||||
local is_wordsplit_disabled=$(unsetopt 2>/dev/null | grep -c '^shwordsplit$')
|
||||
if [ $is_wordsplit_disabled != 0 ]; then setopt shwordsplit; fi
|
||||
$GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$"
|
||||
if [ $is_wordsplit_disabled != 0 ]; then unsetopt shwordsplit; fi
|
||||
}
|
||||
|
||||
parse_array () {
|
||||
local index=0
|
||||
local ary=''
|
||||
read -r token
|
||||
case "$token" in
|
||||
']') ;;
|
||||
*)
|
||||
while :
|
||||
do
|
||||
parse_value "$1" "$index"
|
||||
index=$((index+1))
|
||||
ary="$ary""$value"
|
||||
read -r token
|
||||
case "$token" in
|
||||
']') break ;;
|
||||
',') ary="$ary," ;;
|
||||
*) throw "EXPECTED , or ] GOT ${token:-EOF}" ;;
|
||||
esac
|
||||
read -r token
|
||||
done
|
||||
;;
|
||||
esac
|
||||
value=$(printf '[%s]' "$ary") || value=
|
||||
:
|
||||
}
|
||||
|
||||
parse_object () {
|
||||
local key
|
||||
local obj=''
|
||||
read -r token
|
||||
case "$token" in
|
||||
'}') ;;
|
||||
*)
|
||||
while :
|
||||
do
|
||||
case "$token" in
|
||||
'"'*'"') key=$token ;;
|
||||
*) throw "EXPECTED string GOT ${token:-EOF}" ;;
|
||||
esac
|
||||
read -r token
|
||||
case "$token" in
|
||||
':') ;;
|
||||
*) throw "EXPECTED : GOT ${token:-EOF}" ;;
|
||||
esac
|
||||
read -r token
|
||||
parse_value "$1" "$key"
|
||||
obj="$obj$key:$value"
|
||||
read -r token
|
||||
case "$token" in
|
||||
'}') break ;;
|
||||
',') obj="$obj," ;;
|
||||
*) throw "EXPECTED , or } GOT ${token:-EOF}" ;;
|
||||
esac
|
||||
read -r token
|
||||
done
|
||||
;;
|
||||
esac
|
||||
value=$(printf '{%s}' "$obj") || value=
|
||||
:
|
||||
}
|
||||
|
||||
parse_value () {
|
||||
local jpath="${1:+$1,}${2:-}" isleaf=0 isempty=0 print=0
|
||||
case "$token" in
|
||||
'{') parse_object "$jpath" ;;
|
||||
'[') parse_array "$jpath" ;;
|
||||
# At this point, the only valid single-character tokens are digits.
|
||||
''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;;
|
||||
*) value=$token
|
||||
# replace solidus ("\/") in json strings with normalized value: "/"
|
||||
value=$(echo "$value" | sed 's#\\/#/#g')
|
||||
isleaf=1
|
||||
[ "$value" = '""' ] && isempty=1
|
||||
;;
|
||||
esac
|
||||
[ "$value" = '' ] && return
|
||||
[ -z "$jpath" ] && return # do not print head
|
||||
|
||||
printf "[%s]\t%s\n" "$jpath" "$value"
|
||||
:
|
||||
}
|
||||
|
||||
parse () {
|
||||
read -r token
|
||||
parse_value
|
||||
read -r token
|
||||
case "$token" in
|
||||
'') ;;
|
||||
*) throw "EXPECTED EOF GOT $token" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
tokenize | parse
|
||||
}
|
||||
# vi: expandtab sw=2 ts=2
|
||||
Reference in New Issue
Block a user