panos.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. #!/usr/bin/env sh
  2. # Script to deploy certificates to Palo Alto Networks PANOS via API
  3. # Note PANOS API KEY and IP address needs to be set prior to running.
  4. # The following variables exported from environment will be used.
  5. # If not set then values previously saved in domain.conf file are used.
  6. #
  7. # Firewall admin with superuser and IP address is required.
  8. #
  9. # REQUIRED:
  10. # export PANOS_HOST=""
  11. # export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role
  12. # export PANOS_PASS=""
  13. #
  14. # OPTIONAL
  15. # export PANOS_TEMPLATE="" # Template Name of panorama managed devices
  16. # export PANOS_TEMPLATE_STACK="" # set a Template Stack if certificate should also be pushed automatically
  17. # export PANOS_VSYS="Shared" # name of the vsys to import the certificate
  18. #
  19. # The script will automatically generate a new API key if
  20. # no key is found, or if a saved key has expired or is invalid.
  21. _COMMIT_WAIT_INTERVAL=30 # query commit status every 30 seconds
  22. _COMMIT_WAIT_ITERATIONS=20 # query commit status 20 times (20*30 = 600 seconds = 10 minutes)
  23. # This function is to parse the XML response from the firewall
  24. parse_response() {
  25. type=$2
  26. _debug "API Response: $1"
  27. if [ "$type" = 'keygen' ]; then
  28. status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g')
  29. if [ "$status" = "success" ]; then
  30. panos_key=$(echo "$1" | sed 's/^.*\(<key>\)\(.*\)<\/key>.*/\2/g')
  31. _panos_key=$panos_key
  32. else
  33. message="PAN-OS Key could not be set."
  34. fi
  35. else
  36. if [ "$type" = 'commit' ]; then
  37. job_id=$(echo "$1" | sed 's/^.*\(<job>\)\(.*\)<\/job>.*/\2/g')
  38. _commit_job_id=$job_id
  39. elif [ "$type" = 'job_status' ]; then
  40. job_status=$(echo "$1" | tr -d '\n' | sed 's/^.*<result>\([^<]*\)<\/result>.*/\1/g')
  41. _commit_job_status=$job_status
  42. fi
  43. status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g')
  44. message=$(echo "$1" | tr -d '\n' | sed 's/.*\(<result>\|<msg>\|<line>\)\([^<]*\).*/\2/g')
  45. _debug "Firewall message: $message"
  46. if [ "$type" = 'keytest' ] && [ "$status" != "success" ]; then
  47. _debug "**** API Key has EXPIRED or is INVALID ****"
  48. unset _panos_key
  49. fi
  50. fi
  51. return 0
  52. }
  53. #This function is used to deploy to the firewall
  54. deployer() {
  55. content=""
  56. type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push
  57. panos_url="https://$_panos_host/api/"
  58. export _H1="Content-Type: application/x-www-form-urlencoded"
  59. #Test API Key by performing a lookup
  60. if [ "$type" = 'keytest' ]; then
  61. _debug "**** Testing saved API Key ****"
  62. # Get Version Info to test key
  63. content="type=version&key=$_panos_key"
  64. ## Exclude all scopes for the empty commit
  65. #_exclude_scope="<policy-and-objects>exclude</policy-and-objects><device-and-network>exclude</device-and-network><shared-object>exclude</shared-object>"
  66. #content="type=commit&action=partial&key=$_panos_key&cmd=<commit><partial>$_exclude_scope<admin><member>acmekeytest</member></admin></partial></commit>"
  67. fi
  68. # Generate API Key
  69. if [ "$type" = 'keygen' ]; then
  70. _debug "**** Generating new API Key ****"
  71. content="type=keygen&user=$_panos_user&password=$_panos_pass"
  72. # content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}"
  73. fi
  74. # Deploy Cert or Key
  75. if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then
  76. _debug "**** Deploying $type ****"
  77. #Generate DELIM
  78. delim="-----MultipartDelimiter$(date "+%s%N")"
  79. nl="\015\012"
  80. #Set Header
  81. export _H1="Content-Type: multipart/form-data; boundary=$delim"
  82. if [ "$type" = 'cert' ]; then
  83. panos_url="${panos_url}?type=import"
  84. content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate"
  85. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
  86. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
  87. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
  88. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
  89. if [ "$_panos_template" ]; then
  90. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
  91. fi
  92. if [ "$_panos_vsys" ]; then
  93. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys"
  94. fi
  95. fi
  96. if [ "$type" = 'key' ]; then
  97. panos_url="${panos_url}?type=import"
  98. content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key"
  99. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
  100. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
  101. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
  102. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
  103. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cdomain.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
  104. if [ "$_panos_template" ]; then
  105. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
  106. fi
  107. if [ "$_panos_vsys" ]; then
  108. content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys"
  109. fi
  110. fi
  111. #Close multipart
  112. content="$content${nl}--$delim--${nl}${nl}"
  113. #Convert CRLF
  114. content=$(printf %b "$content")
  115. fi
  116. # Commit changes
  117. if [ "$type" = 'commit' ]; then
  118. _debug "**** Committing changes ****"
  119. #Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution!
  120. if [ "$FORCE" ]; then
  121. _debug "Force switch detected. Committing ALL changes to the firewall."
  122. cmd=$(printf "%s" "<commit><partial><force><admin><member>$_panos_user</member></admin></force></partial></commit>" | _url_encode)
  123. else
  124. _exclude_scope="<policy-and-objects>exclude</policy-and-objects><device-and-network>exclude</device-and-network>"
  125. cmd=$(printf "%s" "<commit><partial>$_exclude_scope<admin><member>$_panos_user</member></admin></partial></commit>" | _url_encode)
  126. fi
  127. content="type=commit&action=partial&key=$_panos_key&cmd=$cmd"
  128. fi
  129. # Query job status
  130. if [ "$type" = 'job_status' ]; then
  131. echo "**** Querying job $_commit_job_id status ****"
  132. cmd=$(printf "%s" "<show><jobs><id>$_commit_job_id</id></jobs></show>" | _url_encode)
  133. content="type=op&key=$_panos_key&cmd=$cmd"
  134. fi
  135. # Push changes
  136. if [ "$type" = 'push' ]; then
  137. echo "**** Pushing changes ****"
  138. cmd=$(printf "%s" "<commit-all><template-stack><name>$_panos_template_stack</name><admin><member>$_panos_user</member></admin></template-stack></commit-all>" | _url_encode)
  139. content="type=commit&action=all&key=$_panos_key&cmd=$cmd"
  140. fi
  141. response=$(_post "$content" "$panos_url" "" "POST")
  142. parse_response "$response" "$type"
  143. # Saving response to variables
  144. response_status=$status
  145. _debug response_status "$response_status"
  146. if [ "$response_status" = "success" ]; then
  147. _debug "Successfully deployed $type"
  148. return 0
  149. elif [ "$_commit_job_status" ]; then
  150. _debug "Commit Job Status = $_commit_job_status"
  151. else
  152. _err "Deploy of type $type failed. Try deploying with --debug to troubleshoot."
  153. _debug "$message"
  154. return 1
  155. fi
  156. }
  157. # This is the main function that will call the other functions to deploy everything.
  158. panos_deploy() {
  159. _cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename
  160. _ckey="$2"
  161. _cfullchain="$5"
  162. # VALID FILE CHECK
  163. if [ ! -f "$_ckey" ] || [ ! -f "$_cfullchain" ]; then
  164. _err "Unable to find a valid key and/or cert. If this is an ECDSA/ECC cert, use the --ecc flag when deploying."
  165. return 1
  166. fi
  167. # PANOS_HOST
  168. if [ "$PANOS_HOST" ]; then
  169. _debug "Detected ENV variable PANOS_HOST. Saving to file."
  170. _savedeployconf PANOS_HOST "$PANOS_HOST" 1
  171. else
  172. _debug "Attempting to load variable PANOS_HOST from file."
  173. _getdeployconf PANOS_HOST
  174. fi
  175. # PANOS USER
  176. if [ "$PANOS_USER" ]; then
  177. _debug "Detected ENV variable PANOS_USER. Saving to file."
  178. _savedeployconf PANOS_USER "$PANOS_USER" 1
  179. else
  180. _debug "Attempting to load variable PANOS_USER from file."
  181. _getdeployconf PANOS_USER
  182. fi
  183. # PANOS_PASS
  184. if [ "$PANOS_PASS" ]; then
  185. _debug "Detected ENV variable PANOS_PASS. Saving to file."
  186. _savedeployconf PANOS_PASS "$PANOS_PASS" 1
  187. else
  188. _debug "Attempting to load variable PANOS_PASS from file."
  189. _getdeployconf PANOS_PASS
  190. fi
  191. # PANOS_KEY
  192. _getdeployconf PANOS_KEY
  193. if [ "$PANOS_KEY" ]; then
  194. _debug "Detected saved key."
  195. _panos_key=$PANOS_KEY
  196. else
  197. _debug "No key detected"
  198. unset _panos_key
  199. fi
  200. # PANOS_TEMPLATE
  201. if [ "$PANOS_TEMPLATE" ]; then
  202. _debug "Detected ENV variable PANOS_TEMPLATE. Saving to file."
  203. _savedeployconf PANOS_TEMPLATE "$PANOS_TEMPLATE" 1
  204. else
  205. _debug "Attempting to load variable PANOS_TEMPLATE from file."
  206. _getdeployconf PANOS_TEMPLATE
  207. fi
  208. # PANOS_TEMPLATE_STACK
  209. if [ "$PANOS_TEMPLATE_STACK" ]; then
  210. _debug "Detected ENV variable PANOS_TEMPLATE_STACK. Saving to file."
  211. _savedeployconf PANOS_TEMPLATE_STACK "$PANOS_TEMPLATE_STACK" 1
  212. else
  213. _debug "Attempting to load variable PANOS_TEMPLATE_STACK from file."
  214. _getdeployconf PANOS_TEMPLATE_STACK
  215. fi
  216. # PANOS_TEMPLATE_STACK
  217. if [ "$PANOS_VSYS" ]; then
  218. _debug "Detected ENV variable PANOS_VSYS. Saving to file."
  219. _savedeployconf PANOS_VSYS "$PANOS_VSYS" 1
  220. else
  221. _debug "Attempting to load variable PANOS_VSYS from file."
  222. _getdeployconf PANOS_VSYS
  223. fi
  224. #Store variables
  225. _panos_host=$PANOS_HOST
  226. _panos_user=$PANOS_USER
  227. _panos_pass=$PANOS_PASS
  228. _panos_template=$PANOS_TEMPLATE
  229. _panos_template_stack=$PANOS_TEMPLATE_STACK
  230. _panos_vsys=$PANOS_VSYS
  231. #Test API Key if found. If the key is invalid, the variable _panos_key will be unset.
  232. if [ "$_panos_host" ] && [ "$_panos_key" ]; then
  233. _debug "**** Testing API KEY ****"
  234. deployer keytest
  235. fi
  236. # Check for valid variables
  237. if [ -z "$_panos_host" ]; then
  238. _err "No host found. If this is your first time deploying, please set PANOS_HOST in ENV variables. You can delete it after you have successfully deployed the certs."
  239. return 1
  240. elif [ -z "$_panos_user" ]; then
  241. _err "No user found. If this is your first time deploying, please set PANOS_USER in ENV variables. You can delete it after you have successfully deployed the certs."
  242. return 1
  243. elif [ -z "$_panos_pass" ]; then
  244. _err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs."
  245. return 1
  246. else
  247. # Generate a new API key if no valid API key is found
  248. if [ -z "$_panos_key" ]; then
  249. _debug "**** Generating new PANOS API KEY ****"
  250. deployer keygen
  251. _savedeployconf PANOS_KEY "$_panos_key" 1
  252. fi
  253. # Confirm that a valid key was generated
  254. if [ -z "$_panos_key" ]; then
  255. _err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again"
  256. return 1
  257. else
  258. deployer cert
  259. deployer key
  260. deployer commit
  261. if [ "$_panos_template_stack" ]; then
  262. # try to get job status for 20 times in 30 sec interval
  263. i=0
  264. while [ "$i" -lt $_COMMIT_WAIT_ITERATIONS ]; do
  265. deployer job_status
  266. if [ "$_commit_job_status" = "OK" ]; then
  267. echo "Commit finished!"
  268. break
  269. fi
  270. sleep $_COMMIT_WAIT_INTERVAL
  271. i=$((i + 1))
  272. done
  273. deployer push
  274. fi
  275. fi
  276. fi
  277. }