Last active
May 7, 2022 10:26
-
-
Save khooz/31401fbcc4ac3006dd66136b0de6ff88 to your computer and use it in GitHub Desktop.
OLCNE certificate generator helper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash -e | |
# | |
# Copyright (c) 2019-2021 Oracle and/or its affiliates. All rights reserved. | |
# Licensed under the GNU General Public License Version 3 as shown at https://www.gnu.org/licenses/gpl-3.0.txt. | |
# This script is made to generate certificates to run the olcne control plane (agent, api-server and cli) | |
# See the help text for more information | |
# User inputs that can be modified by the CLI | |
DEBUG="false" | |
NODES="" | |
CERT_DIR="$(pwd)/configs/certificates" | |
CA_KEY="" | |
CA_CRT="" | |
BYO_CA="false" | |
ONE_CERT="false" | |
CERT_COUNTRY="US" | |
CERT_STATE="North Carolina" | |
CERT_LOCALITY="Whynot" | |
CERT_ORGANIZATION="your-company" | |
CERT_ORGANIZATION_UNIT="private cloud" | |
CERT_COMMON_NAME="example.com" | |
# Destination of certs on every node | |
CERT_PATH_PRODUCTION="" | |
# Destination of certs while they are being generated | |
CERT_PATH_TMP="" | |
# Used to loop through the nodes list in mutiple places | |
NODES_ARRAY="" | |
node_names="" | |
alt_names_ip_str="IP.1 = 127.0.0.1" | |
alt_names_dns_str="DNS.1 = localhost" | |
IP_COUNTER=2 | |
DNS_COUNTER=2 | |
function init { | |
# Strip any trailing slashes off of CERT_DIR | |
CERT_DIR=${CERT_DIR%/} | |
CERT_PATH_PRODUCTION="${CERT_DIR}/production" | |
CERT_PATH_TMP="${CERT_DIR}/tmp-olcne" | |
# Assert the cert destination paths exist | |
mkdir -p "${CERT_PATH_PRODUCTION}" | |
mkdir -p "${CERT_PATH_TMP}" | |
# Validate Nodes flag is defined | |
if [ "$NODES" == "" ]; then | |
echo "[ERROR] --nodes is a required field." | |
usage | |
fi | |
# Validate the CA and Key are used at the same time | |
# If the cert or key cli flags were missed error since they must both be defined | |
if [[ ${CA_KEY} == "" && ${CA_CRT} != "" || ${CA_KEY} != "" && ${CA_CRT} == "" ]]; then | |
echo "[ERROR] Can't generate certs when only one [-C | --byo-ca-cert] or [-K | --byo-ca-key] cli" | |
echo " flags are used. Please validate both Bring Your Own CA CERT and KEY flags exist and try again" | |
exit 1 | |
fi | |
DEFAULT_CA_KEY="${CERT_PATH_PRODUCTION}/ca.key" | |
DEFAULT_CA_CRT="${CERT_PATH_PRODUCTION}/ca.cert" | |
# Set Defaults | |
if [[ ${CA_KEY} == "" ]]; then | |
CA_KEY="${DEFAULT_CA_KEY}" | |
fi | |
if [[ ${CA_CRT} == "" ]]; then | |
CA_CRT="${DEFAULT_CA_CRT}" | |
fi | |
if [[ ${ONE_CERT} != "true" ]]; then | |
# Load nodes array by splitting the node string | |
local IFS=',' | |
read -r -a NODES_ARRAY <<< "${NODES}" | |
else | |
NODES_ARRAY="${NODES}" | |
fi | |
} | |
function usage { | |
echo "The Platform Bootstrap tool is used to generate certificate to be used for the Oracle Linux Cloud Native Environment" | |
echo "" | |
echo " -n | --nodes <NODES>" | |
echo " Comma separated list of nodes" | |
echo " -c | --cert-dir <CERT_DIR>" | |
echo " Directory where the certificates will be created (optional)" | |
echo " -C | --byo-ca-cert <PATH_TO_YOUR_CA_CERT>" | |
echo " Bring Your Own(byo) CA that will be used to generate the node specific cert and key. If not specified one will be created (optional)" | |
echo " -K | --byo-ca-key <PATH_TO_YOUR_CA_KEY>" | |
echo " Bring Your Own(byo) CA Key that will be used to generate the node specific cert and key. If not specified one will be created (optional)" | |
echo " -RC | --cert-request-country <YOUR_COMPANY_COUNTRY>" | |
echo " Country Name (2 letter code) (optional) (Default: ${CERT_COUNTRY})" | |
echo " -RS | --cert-request-state <YOUR_COMPANY_STATE>" | |
echo " State or Province Name (full name) (optional) (Default: ${CERT_STATE})" | |
echo " -RL | --cert-request-locality <YOUR_COMPANY_LOCALITY>" | |
echo " Locality Name (eg, city) (optional) (Default: ${CERT_LOCALITY})" | |
echo " -RO | --cert-request-organization <YOUR_COMPANY_ORGANIZATION_NAME>" | |
echo " Organization Name (eg, company) (optional) (Default: ${CERT_ORGANIZATION})" | |
echo " -ROU | --cert-request-organization-unit <YOUR_COMPANY_ORGANIZATION_UNIT>" | |
echo " Organizational Unit Name (eg, section) (optional) (Default: ${CERT_ORGANIZATION_UNIT})" | |
echo " -RCN | --cert-request-common-name <YOUR_COMMON_NAME>" | |
echo " Common Name (eg, YOUR name) (optional) (Default: ${CERT_COMMON_NAME})" | |
echo " -o | --one-cert" | |
echo " Store all the node IPs and DNS in 1 certifcate rather than making a per node cert" | |
echo " -d | --debug" | |
echo " Set up bash debuging" | |
echo " -h | --help" | |
echo " Show the help message" | |
echo "usage: " | |
echo " $0 [ --cert-dir <CERT_DIR> --nodes <NODES> --debug --help ]" | |
echo "" | |
echo "example: " | |
echo " sudo mkdir -p /etc/olcne/pki" | |
echo " sudo $0 --nodes 100.100.237.105,100.100.237.106 --cert-dir /etc/olcne/pki" | |
exit 1 | |
} | |
function all_nodes_string { | |
delim=" " | |
if [[ ${ONE_CERT} == "true" ]]; then | |
delim="," | |
fi | |
echo "${NODES_ARRAY[@]}" | sed "s/${delim}/ /g" | |
} | |
function all_node_names_string { | |
delim=" " | |
if [[ ${ONE_CERT} == "true" ]]; then | |
delim="," | |
fi | |
echo "${node_names[@]}" | sed "s/${delim}/ /g" | |
} | |
# Returns the destionion of the cert. If 'node' is "" then we assume its the | |
# production destination path | |
function get_path { | |
local file=$1 | |
local node=$2 | |
if [[ ${node} == "" ]] || [[ ${ONE_CERT} == "true" ]]; then | |
# Print the production path | |
echo "${CERT_PATH_PRODUCTION}/${file}" | |
else | |
if [ "${node}" != "\${olcne_node}" ]; then | |
mkdir -p "${CERT_PATH_TMP}/${node}" | |
fi | |
# Print the temp path | |
echo "${CERT_PATH_TMP}/${node}/${file}" | |
fi | |
} | |
# Standardized path to all the certificate information. If no 'node' is specified | |
# the production destination path is returned, this is used only when tranfering the | |
# certs to the other nodes | |
function csr_details_path { | |
local node=$1 | |
local name="csr.info" | |
get_path "${name}" "${node}" | |
} | |
function node_csr_path { | |
local node=$1 | |
local name="node.csr" | |
get_path "${name}" "${node}" | |
} | |
function node_key_path { | |
local node=$1 | |
local name="node.key" | |
get_path "${name}" "${node}" | |
} | |
function node_cert_path { | |
local node=$1 | |
local name="node.cert" | |
get_path "${name}" "${node}" | |
} | |
# Not generating the path for these since they are users input. Just making these functions to be consistant | |
function ca_key_path { | |
echo "${CA_KEY}" | |
} | |
# Not generating the path for these since they are users input. Just making these functions to be consistant | |
function ca_cert_path { | |
echo "${CA_CRT}" | |
} | |
function get_ca_cert_file_name { | |
basename "${CA_CRT}" | |
} | |
function transfer_script_path { | |
echo "${CERT_DIR}/olcne-tranfer-certs.sh" | |
} | |
function is_ip { | |
local ip=$1 | |
local ret=1 | |
# check the given ip contains exactly 4 octets | |
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then | |
#lets break the ip into octets array | |
local IFS='.' | |
read -r -a ip <<< "${ip}" | |
#lets restore IFS once we are done with it | |
#check each octet is <=255 | |
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ | |
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] | |
ret=$? | |
fi | |
return $ret | |
} | |
function transfer_script_print_info { | |
echo "-----------------------------------------------------------" | |
echo "Script To Transfer Certs: $(transfer_script_path)" | |
echo "-----------------------------------------------------------" | |
} | |
function create_transfer_script { | |
cat > "$(transfer_script_path)"<<EOF | |
#!/bin/bash -e | |
# | |
# Copyright (c) 2019-2021 Oracle and/or its affiliates. All rights reserved. | |
# Licensed under the GNU General Public License Version 3 as shown at https://www.gnu.org/licenses/gpl-3.0.txt. | |
# Temporary script to transfer olcne generated certs to nodes | |
ID_FILE=${1:-~/.ssh/id_rsa} | |
USER=${2:-opc} | |
for olcne_node in $(all_node_names_string); do | |
ca_cert_path="$(ca_cert_path)" | |
node_key_path_str="$(node_key_path '${olcne_node}')" | |
node_cert_path_str="$(node_cert_path '${olcne_node}')" | |
SSH="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i \${ID_FILE} \${USER}@\${olcne_node}" | |
SCP="scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i \${ID_FILE}" | |
echo "[INFO] Copying certificate files to: '\${olcne_node}' as: '\${USER}'" | |
# Ensure cert directory exists on the remote | |
\${SSH} "sudo mkdir -p ${CERT_PATH_PRODUCTION}" | |
# Copy the CA Cert to the remote | |
\${SCP} \${ca_cert_path} \${USER}@\${olcne_node}: | |
\${SSH} "sudo mv $(get_ca_cert_file_name) ${CERT_PATH_PRODUCTION}/$(get_ca_cert_file_name)" | |
# Copy the Node Key to the remote | |
\${SCP} \${node_key_path_str} \${USER}@\${olcne_node}: | |
\${SSH} "sudo mv node.key ${CERT_PATH_PRODUCTION}/node.key" | |
# Copy the Node Cert to the remote | |
\${SCP} \${node_cert_path_str} \${USER}@\${olcne_node}: | |
\${SSH} "sudo mv node.cert ${CERT_PATH_PRODUCTION}/node.cert" | |
# Give the 'olcne' user access to the certs dir | |
\${SSH} "sudo chown -R olcne:olcne ${CERT_PATH_PRODUCTION}" | |
done | |
# Validate the 'olcne' user has access all the way to the certificates | |
sudo -u olcne ls ${CERT_PATH_PRODUCTION} | |
EOF | |
} | |
function exit_handler { | |
local returncode=$? | |
if [ $returncode -eq 0 ]; then | |
transfer_script_print_info | |
echo "[SUCCESS] Generated certs and file transfer script!" | |
echo "[INFO] CA Cert: ${CA_KEY}" | |
echo "[INFO] CA Key: ${CA_CRT}" | |
echo "[WARNING] The CA Key is the only way to generate more certificates, ensure it is stored in long term storage" | |
echo "[USER STEP #1] Please ensure you have ssh access from this machine to: ${NODES}" | |
echo "[USER STEP #2] Run script to transfer files: bash -ex $(transfer_script_path)" | |
else | |
echo "[ERROR] Failed to generate the certs" | |
fi | |
} | |
trap "exit_handler" EXIT | |
function gen_csr { | |
local csr_name=$1 | |
local alt_names_ip_str=$2 | |
local alt_names_dns_str=$3 | |
cat > "${csr_name}" <<-EOF | |
[req] | |
default_bits = 2048 | |
encrypt_key = no # Change to encrypt the private key using des3 or similar | |
default_md = sha256 | |
prompt = no | |
utf8 = yes | |
# Speify the DN here so we aren't prompted (along with prompt = no above). | |
distinguished_name = req_distinguished_name | |
# Extensions for SAN IP and SAN DNS | |
req_extensions = v3_req | |
# Be sure to update the subject to match your organization. | |
[req_distinguished_name] | |
countryName=${CERT_COUNTRY} | |
ST=${CERT_STATE} | |
localityName=${CERT_LOCALITY} | |
0.organizationName=${CERT_ORGANIZATION} | |
organizationalUnitName=${CERT_ORGANIZATION_UNIT} | |
commonName=${CERT_COMMON_NAME} | |
# Allow client and server auth. You may want to only allow server auth. | |
# Link to SAN names. | |
[v3_req] | |
basicConstraints = CA:FALSE | |
subjectKeyIdentifier = hash | |
keyUsage = digitalSignature, keyEncipherment | |
extendedKeyUsage = clientAuth, serverAuth | |
subjectAltName = @alt_names | |
# Alternative names are specified as IP.# and DNS.# for IP addresses and | |
# DNS accordingly. | |
[ alt_names ] | |
${alt_names_ip_str} | |
${alt_names_dns_str} | |
EOF | |
} | |
function get_alt_names_ip { | |
alt_ips="$@" | |
alt_names_ip_str="IP.1 = 127.0.0.1" | |
COUNTER=2 | |
for alt_ip in $(echo ${alt_ips} | sed "s/,/ /g"); do | |
alt_names_ip_str="$alt_names_ip_str | |
IP.${COUNTER} = ${alt_ip}" | |
let COUNTER=COUNTER+1 | |
done | |
echo "${alt_names_ip_str}" | |
} | |
function get_alt_names_dns { | |
alt_domains="$@" | |
alt_names_dns_str="DNS.1 = localhost" | |
COUNTER=2 | |
for alt_domain in $(echo ${alt_domains} | sed "s/,/ /g"); do | |
alt_names_dns_str="$alt_names_dns_str | |
DNS.${COUNTER} = ${alt_domain}" | |
let COUNTER=COUNTER+1 | |
done | |
echo "${alt_names_dns_str}" | |
} | |
function get_alt_names { | |
alt_names_ip_str="IP.1 = 127.0.0.1" | |
alt_names_dns_str="DNS.1 = localhost" | |
IP_COUNTER=2 | |
DNS_COUNTER=2 | |
for alt in "$@"; do | |
if is_ip "${alt}"; then | |
alt_names_ip_str="$alt_names_ip_str | |
IP.${IP_COUNTER} = ${alt}" | |
(( IP_COUNTER=IP_COUNTER+1 )) | |
else | |
alt_names_dns_str="$alt_names_dns_str | |
DNS.${DNS_COUNTER} = ${alt}" | |
(( DNS_COUNTER=DNS_COUNTER+1 )) | |
fi | |
done | |
} | |
function main { | |
init | |
# If the user doesn't specify a CA generate one | |
if [[ ${BYO_CA} != "true" ]]; then | |
echo "[INFO] Generating CA" | |
# Generate a CA for 10 years | |
openssl req \ | |
-new \ | |
-newkey rsa:2048 \ | |
-days 3650 \ | |
-nodes \ | |
-x509 \ | |
-subj "/C=${CERT_COUNTRY}/ST=${CERT_STATE}/L=${CERT_LOCALITY}/O=${CERT_ORGANIZATION}" \ | |
-keyout "$(ca_key_path)" \ | |
-out "$(ca_cert_path)" | |
fi | |
OLD_IFS="$IFS" | |
IFS=" " | |
echo "${NODES_ARRAY[@]}" | |
# Assemble cert sans | |
for olcne_node in "${NODES_ARRAY[@]}"; do | |
echo "${olcne_node}" | |
OLD_IFS="$IFS" | |
local IFS=':' | |
read -r -a node_arr <<< "${olcne_node}" | |
IFS="$OLD_IFS" | |
#shellcheck disable=SC2128 | |
node_name="${node_arr}" | |
node_names+=("${node_name}") | |
echo "[INFO] Generating certs for ${node_name}" | |
# Create node specific directory | |
mkdir -p "${CERT_PATH_TMP}/${node_name}" | |
# Default vaules | |
# alt_names_ip_str="IP.1 = 127.0.0.1" | |
# alt_names_dns_str="DNS.1 = localhost" | |
# # Check if the node is an IP or DNS | |
# if is_ip "$olcne_node"; then | |
# alt_names_ip_str=$(get_alt_names_ip "${olcne_node}") | |
# else | |
# alt_names_dns_str=$(get_alt_names_dns "${olcne_node}") | |
# fi | |
get_alt_names "${node_arr[@]}" | |
echo "${alt_names_ip_str}" | |
echo "${alt_names_dns_str}" | |
# Create the CSR | |
gen_csr "$(csr_details_path "${node_name}")" "${alt_names_ip_str}" "${alt_names_dns_str}" | |
# Create the node key | |
openssl genrsa -out "$(node_key_path "${node_name}")" 2048 | |
# Sign the node key with the CSR | |
openssl req \ | |
-new -key "$(node_key_path "${node_name}")" \ | |
-out "$(node_csr_path "${node_name}")" \ | |
-config "$(csr_details_path "${node_name}")" | |
# Generate the node cert for 3 years | |
openssl x509 \ | |
-req \ | |
-days 1095 \ | |
-in "$(node_csr_path "${node_name}")" \ | |
-CA "$(ca_cert_path)" \ | |
-CAkey "$(ca_key_path)" \ | |
-CAcreateserial \ | |
-extensions v3_req \ | |
-extfile "$(csr_details_path "${node_name}")" \ | |
-out "$(node_cert_path "${node_name}")" | |
# ouptut cert if we are debugging | |
if [[ ${DEBUG} == "true" ]]; then | |
openssl x509 -in "$(node_cert_path "${node_name}")" -noout -text | |
fi | |
done | |
create_transfer_script | |
} | |
# MAIN | |
while [[ $# -gt 0 ]]; do | |
key="$1" | |
case $key in | |
-c | --cert-dir ) | |
CERT_DIR="$2" | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-n | --nodes ) | |
NODES=$2 | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-o | --one-cert ) | |
ONE_CERT=true | |
shift # shift past argument | |
;; | |
-C | --byo-ca-cert ) | |
CA_CRT=$2 | |
BYO_CA=true | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-K | --byo-ca-key ) | |
CA_KEY=$2 | |
BYO_CA=true | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-RC | --cert-request-country ) | |
CERT_COUNTRY=$2 | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-RS | --cert-request-state ) | |
CERT_STATE=$2 | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-RL | --cert-request-locality ) | |
CERT_LOCALITY=$2 | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-RO | --cert-request-organization ) | |
CERT_ORGANIZATION=$2 | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-ROU | --cert-request-organization-unit ) | |
CERT_ORGANIZATION_UNIT=$2 | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-RCN | --cert-request-common-name ) | |
CERT_COMMON_NAME=$2 | |
shift # shift past argument | |
shift # shift past value | |
;; | |
-d | --debug ) | |
DEBUG="true" | |
set -x | |
shift # shift past argument | |
;; | |
-h | --help ) | |
usage | |
exit | |
;; | |
*) # unknown option | |
echo "[ERROR] unknown argument: ${key}" | |
usage | |
exit 1 | |
;; | |
esac | |
done | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Use colon(
:
) to delimit tuples of IPs and DNs e.g.:opn00-17-0-0-10.pool.example.com:10.0.0.17,wn00-33-0-0-10.pool.example.com:10.0.0.33,wn02-34-0-0-10.pool.example.com:10.0.0.34