|
#!/usr/bin/env bash |
|
################################################################################ |
|
# Created by: |
|
# Johannes Tegnér <johannes@jitesoft.com> |
|
# |
|
# MIT License |
|
# |
|
# Copyright (c) 2019 Jitesoft |
|
# |
|
# Permission is hereby granted, free of charge, to any person obtaining a copy |
|
# of this software and associated documentation files (the "Software"), to deal |
|
# in the Software without restriction, including without limitation the rights |
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
# copies of the Software, and to permit persons to whom the Software is |
|
# furnished to do so, subject to the following conditions: |
|
# |
|
# The above copyright notice and this permission notice shall be included in all |
|
# copies or substantial portions of the Software. |
|
# |
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
# SOFTWARE. |
|
# ------------------------------------------------------------------------------ |
|
# |
|
# This script is used to create a set of base certificates for usage in Ansible |
|
# and other tools. |
|
# It generates certificates in pem form and a `certificates.vault.yml` file |
|
# which can be used to be encrypted and imported in ansible playbooks. |
|
# |
|
# The default certificates it creates are the following: |
|
# kubernetes/ |
|
# client.pem |
|
# client-key.pem |
|
# server.pem |
|
# server-key.pem |
|
# dns/ |
|
# client.pem |
|
# client-key.pem |
|
# server.pem |
|
# server-key.pem |
|
# etcd/ |
|
# client.pem |
|
# client-key.pem |
|
# server.pem |
|
# server-key.pem |
|
# peer.pem |
|
# peer-key.pem |
|
# |
|
# Required files to run this script: |
|
# /gen.sh (this file) |
|
# /request/types/ (all certificate types to be able to generate) |
|
# client.json |
|
# peer.json |
|
# server.json |
|
# /authkey.txt (authentication key for cfssl) |
|
# |
|
# Required software: |
|
# * python 'json.tool' |
|
# * bash |
|
# |
|
# Required remote servers: |
|
# Cfssl ( https://github.com/cloudflare/cfssl ) |
|
################################################################################ |
|
|
|
# The remote server used (change if you do not run cfssl on localhost. |
|
API_SERVER_URI="127.0.0.1:8888" |
|
# Gets the directory of the script. |
|
BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" |
|
# Change this variable to set another directory where files will be generated. |
|
OUT_DIR="${BASE_DIR}/gen" |
|
# Auth key to use for cfssl. |
|
AUTH_KEY=$(cat ${BASE_DIR}/authkey.txt) |
|
|
|
# To add more certificate domains (to generate more folders and certs) |
|
# add a new key to the hashmap below and set the cert types needed to the |
|
# value of the new hashmap key. |
|
declare -A CERTIFICATES |
|
CERTIFICATES["kubernetes"]="client server" |
|
CERTIFICATES["dns"]="client server" |
|
CERTIFICATES["etcd"]="client server peer" |
|
# Todo: Create a config file for this? |
|
|
|
CURRENT_DIR=$(pwd) |
|
|
|
# |
|
# This method is used to indent a block of text from a file with a set number of spaces. |
|
# |
|
# Required arguments to pass is: |
|
# * integer - Amount of spaces |
|
# * string - File path |
|
# |
|
indent_all_from_file() { |
|
SPACES=$(printf "%*s" $1 '') |
|
while IFS= read -r line |
|
do |
|
echo "${SPACES}${line}" |
|
done < "${2}" |
|
} |
|
|
|
# |
|
# This method is used to indent a block of text by a set number of spaces. |
|
# |
|
# Required arguments to pass is: |
|
# * integer - Amount of spaces |
|
# * string - Text block |
|
# |
|
indent_all() { |
|
SPACES=$(printf "%*s" $1 '') |
|
|
|
while IFS= read -r line |
|
do |
|
echo "${SPACES}${line}" |
|
done < <(printf '%s\n' "${2}") |
|
} |
|
|
|
# |
|
# This method creates a yaml string for a cert domain. |
|
# Example: |
|
# "dns: |
|
# client: |
|
# key: | <cert> |
|
# cert: | <cert>" |
|
# |
|
# Required arguments to pass is: |
|
# * string - Cert domain (must exist in the `CERTIFICATES` array). |
|
# |
|
print_yml_cert () { |
|
# $1: object name. |
|
echo "${1}:" |
|
for val in ${CERTIFICATES["${1}"]}; do |
|
echo " ${val}: |
|
key: | |
|
$(indent_all_from_file 6 "${OUT_DIR}/${1}/${val}-key.pem") |
|
cert: | |
|
$(indent_all_from_file 6 "${OUT_DIR}/${1}/${val}.pem")" |
|
done |
|
} |
|
|
|
# |
|
# This method queries the CFSSL api endpoints to generate and sign a new certificate. |
|
# When generated, the certificates will be printed to their intended folders. |
|
# |
|
# Required arguments to pass is: |
|
# * string - Name of the 'request-type' or cert type (client, server etc). |
|
# * string - Name of the cert, the resulting files will be <name>-key.pem and <name>.pem |
|
# * string - Path to the output directory. |
|
# |
|
gen_cert() { |
|
CERT_REQUEST=$(cat "${1}") |
|
CERT_NAME="${3}/${2}" |
|
|
|
# Create a request and send it to the api, the request uses a json file to fetch |
|
# the type of certificate to generate. |
|
REQUEST=$(printf '{"request": %s}' "${CERT_REQUEST}") |
|
CERTS=$(curl -Ls -d "${REQUEST}" ${API_SERVER_URI}/api/v1/cfssl/newcert) |
|
# Create temporary variables for all the certificates and the sign request. |
|
PRIVATE="$(echo ${CERTS} | \ |
|
python -m json.tool | \ |
|
grep private_key | \ |
|
cut -f4 -d '"')" |
|
PUBLIC="$(echo ${CERTS} | \ |
|
python -m json.tool | \ |
|
grep -m 1 certificate | \ |
|
cut -f4 -d '"')" |
|
SIGN_REQUEST="$(echo ${CERTS} | python -m json.tool | \ |
|
grep certificate_request | cut -f4 -d '"')" |
|
# Create a json object for the sign request. |
|
CF_REQUEST=$(printf '{"certificate_request": "%s"}' "${SIGN_REQUEST}"}) |
|
# HMAC with the auth key to create a key for the request. |
|
KEY=$(echo ${CF_REQUEST} | \ |
|
openssl dgst -sha256 -mac HMAC -macopt hexkey:${AUTH_KEY} -binary | \ |
|
base64) |
|
# Encode the request itself as a base64 string. |
|
ENC_REQUEST=$(echo ${CF_REQUEST} | base64 | tr -d '\n') |
|
# Create a new request including the encoded request and the hmac key. |
|
REQUEST=$(printf '{"request": "%s", "token": "%s"}' "${ENC_REQUEST}" "${KEY}") |
|
# And send to the sign endpoint. |
|
RESULT=$(curl -Ls -d "${REQUEST}" ${API_SERVER_URI}/api/v1/cfssl/authsign) |
|
# Create the certificate files. |
|
echo -e "$(echo ${RESULT} | \ |
|
python -m json.tool | \ |
|
grep certificate | \ |
|
cut -f4 -d '"')" > ${CERT_NAME}.pem |
|
echo -e "${PRIVATE}" > ${CERT_NAME}-key.pem |
|
echo -e "${SIGN_REQUEST}" > ${CERT_NAME}.csr |
|
} |
|
|
|
# Create a new int-CA for the whole cluster to use. |
|
echo "Changing directory to the output directory." |
|
mkdir -p gen && cd gen |
|
echo "Creating folder structure..." |
|
for key in ${!CERTIFICATES[@]}; do |
|
mkdir -p ${OUT_DIR}/${key} |
|
echo "Created ${OUT_DIR}/${key}." |
|
done |
|
|
|
echo "Fetching certificates from the api server..." |
|
|
|
for key in ${!CERTIFICATES[@]}; do |
|
echo "${key}..." |
|
for val in ${CERTIFICATES["${key}"]}; do |
|
echo "${val}" |
|
gen_cert "${BASE_DIR}/request-types/${val}.json" "${val}" "${BASE_DIR}/gen/${key}" |
|
done |
|
echo "Done generating certificates for ${key}." |
|
done |
|
|
|
echo "Fetching CA cert." |
|
CA_CERT=$(curl -Ls -d '{"label": "primary"}' ${API_SERVER_URI}/api/v1/cfssl/info | python -m json.tool | grep certificate | cut -f4 -d '"') |
|
echo -e ${CA_CERT} > ${OUT_DIR}/ca.pem |
|
|
|
echo "Generating new certificates vault file..." |
|
YAML="vault: |
|
certificates: |
|
ca: |
|
pem: |
|
cert: | |
|
$(indent_all_from_file 10 "${BASE_DIR}/gen/ca.pem") |
|
$(for key in ${!CERTIFICATES[@]}; do |
|
indent_all 4 "$(print_yml_cert "${key}")"; |
|
done) |
|
" |
|
|
|
echo "${YAML}" | sed '/^[[:space:]]*$/d' > ${OUT_DIR}/certificates.vault.yml |
|
cd ${CURRENT_DIR} |
|
echo "Done." |