#!/bin/bash

set -e

function usage {
  cat << EOT
usage: create-vault-ca --domain=DOMAIN --component=COMPONENT
                       --vault-addr=VAULT_ADDR --vault-token=VAULT_TOKEN
                       [--cert-max-ttl=CERT_MAX_TTL] [--cert-ttl=CERT_TTL] 
                       [--token-max-ttl=TOKEN_MAX_TTL] [--token-ttl=TOKEN_TTL] 
                       [--ca-ttl=CA_TTL] [-h/--help]

Create a new CA for a component on the Vault.
A component is be a service which need to use certs auth.

The Vault need to be unsealed and initialized to be able to run the script.

NOTE: all TTLs need to be specified as number of hours, like 24h (remember the final h).

Documentation: https://github.com/crisidev/vault-ca/blob/master/README.md

mandatory arguments:
  --component=COMPONENT, -c=COMPONENT
                    Component used
  --domain=DOMAIN, -d=DOMAIN
                    CA domain
  --vault-addr=VAULT_ADDR, -a=VAULT_ADDR
                    Vault admin token
  --vault-token=VAULT_TOKEN, -v=VAULT_TOKEN
                    Vault admin token

optional arguments:
  -h, --help        Show this help message and exit
  --cert-max-ttl=CERT_MAX_TTL, -C=CERT_MAX_TTL
                    Max TTL for a new certificate, in hours, default to 43800h (5 years)
  --cert-ttl=CERT_TTL, -c=CERT_TTL
                    Default TTL for a new certificate, in hours, default to 730h (1 month)
  --token-max-ttl=TOKEN_MAX_TTL, -T=TOKEN_MAX_TTL
                    Max TTL for an authentication token, in hours, default to 43800h (5 years)
  --token-ttl=TOKEN_TTL, -t=TOKEN_TTL
                    Default TTL for an authentication token, in hours, default to 8760h (1 year)
  --ca-ttl=CA_TTL, -C=CA_TTL
                    TTL for the root CA, in hours, default to 43800h (5 years)
EOT
}

# parse command line arguments
while [ "$1" != "" ]; do
  PARAM=$(echo $1 | awk -F= '{print $1}')
  VALUE=$(echo $1 | sed 's/^[^=]*=//g')
  case ${PARAM} in
    "-h" | "--help")
      usage
      exit 0
      ;;
    "-d" | "--domain")
      DOMAIN=${VALUE}
      ;;
    "-c" | "--component")
      COMPONENT=${VALUE}
      ;;
    "-a" | "--vault-addr")
      VAULT_ADDR=${VALUE}
      ;;
    "-v" | "--vault-token")
      VAULT_TOKEN=${VALUE}
      ;;
    "-P" | "--cert-max-ttl")
      CERT_MAX_TTL=${VALUE}
      ;;
    "-p" | "--cert-ttl")
      CERT_TTL=${VALUE}
      ;;
    "-T" | "--token-max-ttl")
      TOKEN_MAX_TTL=${VALUE}
      ;;
    "-t" | "--token-ttl")
      TOKEN_TTL=${VALUE}
      ;;
    "-C" | "--ca-ttl")
      CA_TTL=${VALUE}
      ;;
    *)
      echo "ERROR: unknown parameter \"$PARAM\""
      echo
      usage
      exit 1
      ;;
  esac
  shift
done

# check mandatory arguments
if [ -z "${DOMAIN}" ] || [ -z ${COMPONENT} ] || [ -z "${VAULT_TOKEN}" ] || [ -z "${VAULT_ADDR}" ]; then
  usage
  exit 1
fi

export VAULT_TOKEN
export VAULT_ADDR

# max ttls
CERT_MAX_TTL=${CERT_MAX_TTL:-43800h}    # 5 years
TOKEN_MAX_TTL=${TOKEN_MAX_TTL:-43800h}  # 5 years

# default ttls
CA_TTL=${CA_TTL:-43800h}                # 5 years
CERT_TTL=${CERT_TTL:-730h}              # 1 month
TOKEN_TTL=${TOKEN_TTL:-8760h}           # 1 year

# vault ca path
CA_PATH=pki/${DOMAIN}

function read_yes_no {
  MESSAGE=$1
  echo -n "${MESSAGE} "
  read answer
  if ! echo "$answer" | grep -iq "^y" ;then
    echo "exiting."
    exit 1
  fi
}

function mount_pki {
# mount vault path for this CA and tune expiration to $MAX_TTL
  vault mount -path ${CA_PATH} pki
  vault mount-tune -max-lease-ttl=${CA_TTL} ${CA_PATH}
}

function generate_root_ca {
  # generate root CA
  vault write ${CA_PATH}/root/generate/internal common_name=${DOMAIN} exclude_cn_from_sans=true ttl=${CA_TTL}
}

function generate_cert_role {
  # create a role name "cert" able to generate certificates
  vault write ${CA_PATH}/roles/cert \
          allow_any_name=true allow_bare_domains=true \
          allow_subdomains=true allow_glob_domains=true \
          allow_localhost=true allow_ip_sans=true \
          ou=${COMPONENT} organization=${DOMAIN} \
          ttl=${CERT_TTL} max_ttl=${CERT_MAX_TTL}
}

function generate_cert_policy {
# policy for to allow access only to token releated to $COMPONENT
  cat <<EOT | vault policy-write ${CA_PATH}/cert -
path "${CA_PATH}/issue/cert" {
  policy = "write"
}
EOT
}

function configure_services_token {
  # configuration of token generation options and allowed policies
  vault write auth/token/roles/services \
          explicit_max_ttl=${TOKEN_MAX_TTL} ttl=${TOKEN_TTL} \
          period=${TOKEN_TTL} renewable=true \
          orphan=true allowed_policies="${CA_PATH}/cert"
}

function configure_users_token {
  # configuration of token generation options and allowed policies
  vault write auth/token/roles/users \
          explicit_max_ttl=${TOKEN_MAX_TTL} ttl=${TOKEN_TTL} \
          period=${TOKEN_TTL} renewable=true \
          orphan=true allowed_policies="${CA_PATH}/cert"
}


function generate_services_token {
  # generate token for services
  echo
  echo "Generating services token (REMEMBER TO SAVE IT)"
  vault token-create -policy="$CA_PATH/cert" -role="services"
  read_yes_no "Have you saved the services token above? [y|N]"
}

function generate_users_token {
  # genrate token for users
  echo
  echo "Generating users token (REMEMBER TO SAVE IT)"
  vault token-create -policy="$CA_PATH/cert" -role="users"
  read_yes_no "Have you saved the users token above? [y|N]"
}

function print_fetching_info {
  # echo last informations
  echo
  echo "To boostrap the CA use \"fetch-ssl-cert -c ${COMPONENT} -n <common_name> -t <services_token> -b -o <output_dir>\""
  echo "To create / renew a certificate use \"fetch-ssl-cert -c ${COMPONENT} -n <common_name> -t <services_token> -o <output_dir>\""
}

read_yes_no "Are you sure this script have not been already run on this vault? It can break / override configs [y|N]"

mount_pki
generate_root_ca
generate_cert_role
generate_cert_policy
configure_services_token
configure_users_token
generate_services_token
generate_users_token
print_fetching_info

exit 0
