Skip to content

Instantly share code, notes, and snippets.

@Johannestegner
Last active June 17, 2019 20:31
Show Gist options
  • Select an option

  • Save Johannestegner/75720c53e651306ecd183856d1a8d497 to your computer and use it in GitHub Desktop.

Select an option

Save Johannestegner/75720c53e651306ecd183856d1a8d497 to your computer and use it in GitHub Desktop.
Script to generate certificates via remote cfssl server.

This file (gen.sh) is intended to be used to produce a set of certificates to be used by Kubernetes, Etcd and a DNS server of choice. It was created to quickly generate a new set of certificates for Jitesoft k8s cloud and generates new certificates and a ansible file for variables.

Use at your own risk, feel free to change and better it any way you wish!

#!/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."
{
"hosts": [
"*"
],
"key": {
"algo": "ecdsa",
"size": 384
},
"profile": "client",
"label": "Client certificate.",
"names": [
{
"C": "SE",
"L": "Lund",
"O": "Jitesoft",
"OU": "Infra-DNS",
"ST": "Skania"
}
]
}
{
"hosts": [
"*"
],
"key": {
"algo": "ecdsa",
"size": 384
},
"profile": "peer",
"label": "Peer certificate.",
"names": [
{
"C": "SE",
"L": "Lund",
"O": "Jitesoft",
"OU": "Infra-DNS",
"ST": "Skania"
}
]
}
{
"hosts": [
"*"
],
"key": {
"algo": "ecdsa",
"size": 384
},
"profile": "server",
"label": "Server certificate.",
"names": [
{
"C": "SE",
"L": "Lund",
"O": "Jitesoft",
"OU": "Infra-DNS",
"ST": "Skania"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment