Skip to content

Instantly share code, notes, and snippets.

@khooz
Last active May 7, 2022 10:26
Show Gist options
  • Save khooz/31401fbcc4ac3006dd66136b0de6ff88 to your computer and use it in GitHub Desktop.
Save khooz/31401fbcc4ac3006dd66136b0de6ff88 to your computer and use it in GitHub Desktop.
OLCNE certificate generator helper
#!/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
@khooz
Copy link
Author

khooz commented May 7, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment