-
Notifications
You must be signed in to change notification settings - Fork 1
/
pan_getcert
261 lines (240 loc) · 9.53 KB
/
pan_getcert
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#!/bin/bash
## Usage info
show_help() {
cat << EOF
Usage: ${0##*/} [-hv] -c CERT_CN [-n CERT_NAME] [-Y] [OPTIONS] FQDN
This script requests a certificate from FreeIPA using ipa-getcert and calls a partner
script to deploy the certificate to a Palo Alto firewall or Panorama.
FQDN Fully qualified name of the Palo Alto firewall or Panorama
interface. Must be reachable from this host on port TCP/443.
-c CERT_CN REQUIRED. Common Name (Subject) of the certificate (must be a
FQDN). Will also present in the certificate as a SAN.
OPTIONS:
-n CERT_NAME Name of the certificate in PanOS configuration. Defaults to the
certificate Common Name.
-Y Parsed to pan_instcert to append the current year '_YYYY' to
the certificate name.
-k key(path|ext) API key file location or extension.
-S SERVICE Service of the Service Principal. Default: HTTP.
-T CERT_PROFILE Ask IPA to process the request using the named profile or
template.
-G TYPE Type of key to be generated if one is not already in place.
IPA CA uses RSA by default. (RSA, DSA, EC or ECDSA)
-b BITS In case a new key pair needs to be generated, this option
specifies the size of the key. Default: 2048 (RSA/DSA).
EC: 256, 384 or 512. See certmonger.conf for the default.
-p PROFILE_NAME Apply the certificate to a (primary) SSL/TLS Service Profile.
-s PROFILE_NAME Apply the certificate to a (secondary) SSL/TLS Service Profile.
-h Display this help and exit.
-v Verbose mode.
EOF
}
## Fixed variables
API_KEY="/etc/ipa/.panrc"
INST_CERT="/usr/local/bin/pan_instcert"
VERBOSE=0
OPTIND=1
## Read/interpret optional arguments
while getopts c:n:YS:T:G:b:p:s:k:vh opt; do
case $opt in
c) CERT_CN=$OPTARG
;;
n) CERT_NAME=$OPTARG
;;
Y) YEAR="_$(date +'%Y')"
;;
k) API_KEY=$OPTARG
;;
S) SERVICE=$OPTARG
;;
T) CERT_PROFILE=$OPTARG
;;
G) TYPE=$OPTARG
;;
b) BITS=$OPTARG
;;
p) if grep -q -P '(^[\w-]+$)' <<< "$OPTARG"; then
SSL_TLS_PROFILE_1=$OPTARG
else
printf 'ERROR: Invalid first SSL/TLS Profile name given: %s\n' "$OPTARG"
printf 'Ensure the name does not contain dots or spaces.\n'
exit 2
fi
;;
s) if grep -q -P '(^[\w-]+$)' <<< "$OPTARG"; then
SSL_TLS_PROFILE_2=$OPTARG
else
printf 'ERROR: Invalid second SSL/TLS Profile name given: %s\n' "$OPTARG"
printf 'Ensure the name does not contain dots or spaces.\n'
exit 2
fi
;;
v) VERBOSE=$((VERBOSE+1))
;;
h) show_help
exit 0
;;
*) show_help >&2
exit 1
;;
esac
done
shift "$((OPTIND-1))" # Discard the options and sentinel --
## The user must have sudo rights for ipa-getcert
if ! sudo ipa-getcert list &> /dev/null; then
echo "Please ensure you have sudo privileges for ipa-getcert"
exit 1
fi
# Check that a Common Name name was given
if [[ -z $CERT_CN ]]; then
printf 'ERROR: Missing -c, a certificate requires a Common Name.\n\n'
show_help >&2
exit 1
elif grep -q -P '(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63}$)' <<< "$CERT_CN"; then
CERT_CN=${CERT_CN,,}
if (( $VERBOSE > 0 )); then
printf 'Certificate Common Name: %s\n' "$CERT_CN"
fi
fi
if [[ -z $CERT_NAME ]]; then
CERT_NAME=$CERT_CN
elif (( $VERBOSE > 0 )); then
printf 'Parsed certificate Name: %s\n' "$CERT_NAME"
fi
# Check that a single valid FQDN was parsed for the Palo Alto API interface
if grep -q -P '(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}\.)+[a-zA-Z]{2,63}$)' <<< "$@"; then
PAN_MGMT="${@,,}"
printf ' verbose=%d\n CERT_CN: %s\n CERT_NAME: %s\n PAN_FQDN: %s\n' "$VERBOSE" "$CERT_CN" "$CERT_NAME$YEAR" "$PAN_MGMT"
if ! nc -z $PAN_MGMT 443 2>/dev/null; then
printf 'ERROR: Palo Alto device unreachable at: https://%s/\n' "$PAN_MGMT"
exit 4
fi
else
printf 'ERROR: A valid FQDN is required to issue a certificate.\n'
printf 'Parsed string: %s\n\n' "$@"
show_help >&2
exit 4
fi
# Check if an alternate API key file path or extension was given
if [[ "$API_KEY" != "/etc/ipa/.panrc" ]]; then
if [ -d "API_KEY" ]; then
# Parsed string is a directory
API_KEY="${API_KEY}/.panrc"
elif [[ "$API_KEY" != *\/* ]]; then
# Parsed string is a file extension
API_KEY="/etc/ipa/.panrc.$API_KEY"
fi
if [ -f "$API_KEY" ]; then
printf 'Parsed API key file exists: %s\n' "$API_KEY"
else
printf "ERROR: Parsed API key file doesn't exist: %s\n" "$API_KEY"
show_help >&2
exit 1
fi
fi
# Set the default service for the Service Principal
if [[ -z $SERVICE ]]; then
SERVICE="HTTP"
fi
# Verify a valid IPA CA Certificate Profile is parsed
if [[ ! -z $CERT_PROFILE ]]; then
if ! (ipa certprofile-find --id="$CERT_PROFILE"); then
printf "ERROR: Certificate profile %s doesn't exist\n" "$CERT_PROFILE"
exit 6
else
(( $VERBOSE > 0 )) && printf 'Found certificate profile: %s\n' "$CERT_PROFILE"
fi
fi
# Verify the parsed certificate type and bit length
if [[ ! -z $TYPE ]]; then
TYPE=${TYPE^^}
if [[ "$TYPE" =~ ^(RSA|DSA)$ ]]; then
if [ "$BITS" -lt 2048 ]; then
printf 'ERROR: RSA/DSA key length must be at least 2048 bits\n'
exit 6
else
(( $VERBOSE > 0 )) && printf 'Key type/length: %s/%d' "$TYPE" "$BITS"
fi
elif [[ "$TYPE" =~ ^(EC|ECDSA)$ ]]; then
if [ "$BITS" -lt 256 ]; then
printf 'ERROR: EC/ECDSA key longth must be at least 256 bits\n'
exit 6
else
(( $VERBOSE > 0 )) && printf 'Key type/length: %s/%d\n' "$TYPE" "$BITS"
fi
else
printf 'ERROR: Unsupported key type: %s\n' "$TYPE"
exit 6
fi
fi
# Parse IPA CA Certificate Profile to ipa-getcert
if [[ -z $CERT_PROFILE ]]; then
IPA_OPTS=""
else
IPA_OPTS="-T $CERT_PROFILE"
fi
# Parse certificate key type and length to ipa-getcert
if [[ ! -z $TYPE ]]; then
IPA_OPTS="$IPA_OPTS -G $TYPE"
if [[ ! -z $BITS ]]; then
IPA_OPTS="$IPA_OPTS -g $BITS"
fi
fi
# Parse YEAR option to pan_instcert
if ! [[ -z $YEAR ]]; then
INST_OPTS="$INST_OPTS -Y"
fi
# Parse API key file to pan_instcert
if [[ "$API_KEY" != "/etc/ipa/.panrc" ]]; then
INST_OPTS="$INST_OPTS -k $API_KEY"
fi
# Verbosely log SSL/TLS profile names (if any) and only allow secondary if a primary is given
if [[ ! -z $SSL_TLS_PROFILE_1 ]]; then
INST_OPTS="$INST_OPTS -p $SSL_TLS_PROFILE_1"
(( $VERBOSE > 0 )) && printf 'Primary SSL/TLS Profile name: %s\n' "$SSL_TLS_PROFILE_1"
if [[ ! -z $SSL_TLS_PROFILE_2 ]]; then
INST_OPTS="$INST_OPTS -s $SSL_TLS_PROFILE_2"
(( $VERBOSE > 0 )) && printf 'Secondary SSL/TLS Profile name: %s\n' "$SSL_TLS_PROFILE_2"
fi
else
SSL_TLS_PROFILE_2=""
(( $VERBOSE > 0 )) && echo "No SSL/TLS Profile names parsed."
fi
## Requirements
# Installed packages: ipa-getcert
# Simple check to see if freeipa-client is installed.
if [[ ! -d /etc/ipa ]]; then
echo 'ERROR: /etc/ipa/ does not exist. This script must be run from an enrolled FreeIPA client.'
exit 5
fi
## Request the certificate from FreeIPA
sudo ipa-getcert request $IPA_OPTS -N $CERT_CN -K $SERVICE/$CERT_CN -k /etc/ssl/private/${CERT_CN}.key -f /etc/ssl/certs/${CERT_CN}.crt -D $CERT_CN -C "$INST_CERT -c $CERT_CN -n $CERT_NAME $INST_OPTS $PAN_MGMT"
if (( $VERBOSE > 0 )); then
printf 'Certificate requested for: %s\n' "$CERT_CN"
fi
# Verify state of the certificate is MONITORING, use a 300 second timeout.
STEP=1
SECONDS=0
CERT_STAT="requested"
while [[ $SECONDS -lt 300 ]] && [[ "$CERT_STAT" != "monitoring" ]]; do
sleep $STEP
(( $VERBOSE > 0 )) && printf '\r Waiting for status: %d seconds.' "$SECONDS"
if [[ $(sudo ipa-getcert status -f /etc/ssl/certs/${CERT_CN}.crt -v) =~ (POST_SAVED_CERT) ]] && [[ "$CERT_STAT" == "requested" ]]; then
C_ISSUE=$SECONDS
printf '\r Certificate issue took %d seconds, waiting for the post-save process to finish.\n' "$C_ISSUE"
CERT_STAT="post_saved_cert"
elif [[ $(sudo ipa-getcert status -f /etc/ssl/certs/${CERT_CN}.crt -v) =~ (MONITORING) ]]; then
printf '\r Certificate install and commit by the post-save process on: %s took %d seconds.\n' "$PAN_MGMT" "$(($SECONDS-$C_ISSUE))"
CERT_STAT="monitoring"
fi
done
if [[ "$CERT_STAT" != "monitoring" ]]; then
printf 'ERROR: Problem encountered with the requested certificate or installation.\nManual intervention required for: %s\n' "$CERT_CN"
CMD="ipa-getcert status -f /etc/ssl/certs/${CERT_CN}.crt -v"
CERT_STATUS=$($CMD)
printf ' - Certificate request - %s\nCheck Certmonger status with: %s\n' "${CERT_STATUS,}" "$CMD"
printf ' - Once resolved run the following command to deploy the certificate:\n %s\n' "ipa-getcert resubmit -f /etc/ssl/certs/${CERT_CN}.crt"
printf ' - If stuck in POST_SAVED_CERT, then the post-save command likely failed.\n Check /var/log/pan_instcert.log, CPU load and abort the post-save process.\n\n'
exit 10
fi
echo "FINISHED: Check the Palo Alto firewall or Panorama to check the commit succeeded."