~damien/ca-provision-example

a8625591402637b4aec1a9f904b1ce90e5f167de — Damien Radtke 2 years ago
Initial commit
6 files changed, 155 insertions(+), 0 deletions(-)

A .gitignore
A README.md
A ca-new
A inspect-cert
A provision-cert
A server.tf
A  => .gitignore +10 -0
@@ 1,10 @@
*.crt
*.csr
*.key
*.pem
*.srl
*.tfstate
*.tfstate.*
.terraform
.terraform.lock.hcl
secrets.tfvars

A  => README.md +14 -0
@@ 1,14 @@
This repo demonstrates a simple approach for provisioning servers with
certificates signed by a custom certificate authority.

In order to test it, you need to define a `linode_token` variable inside
`secrets.tfvars`, and then run:

```sh
$ ./ca-new consul-agent
$ terraform apply -var-file secrets.tfvars
```

This will provision a server with
`/etc/ssl/consul-agent/server1.dc1.consul.{crt,key}`, which represents a
certificate and key that have been signed by the local `consul-agent` CA.

A  => ca-new +21 -0
@@ 1,21 @@
#!/usr/bin/env bash
#
# Inspired by https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/
#

set -eu -o pipefail

if [[ $# -ne 1 ]]; then
	echo "usage: $0 <name>"
	exit 1
fi

# Generate a new private key for the CA.
# If you want to output an encrypted private key, which will prompt for a passphrase,
# add a cipher parameter, i.e. -des3
openssl genrsa -out "$1-ca-key.pem" 2048

# Use the key to generate a self-signed certificate.
# See: https://docs.oracle.com/cd/E24191_01/common/tutorials/authz_cert_attributes.html
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout "$1-ca-key.pem" -out "$1-ca.pem" \
	-subj "/CN=$1 CA"

A  => inspect-cert +13 -0
@@ 1,13 @@
#!/usr/bin/env bash
#
# From https://www.sslshopper.com/article-most-common-openssl-commands.html
#

set -eu -o pipefail

if [[ $# -ne 1 ]]; then
	echo "usage: $0 <cert>"
	exit 1
fi

openssl x509 -in "$1" -text -noout

A  => provision-cert +63 -0
@@ 1,63 @@
#!/usr/bin/env bash
#
# Example:
#
#   provision-cert --ca consul-agent --name server1.dc2.consul --addr 1.1.1.1 --owner consul
#

set -eu -o pipefail

while [[ $# -gt 0 ]]; do
	case "$1" in
		--ca) ca="$2"; shift 2;;
		--name) name="$2"; shift 2;;
		--addr) addr="$2"; shift 2;;
		--owner) owner="$2"; shift 2;;
		--days) days="$2"; shift 2;;
		*) echo "unrecognized parameter: $1"; exit 1;;
	esac
done

tmp="$(mktemp --directory)"
trap "rm -rf '${tmp}'" EXIT

# Clean up old known host and add the new key to the known_hosts file
# This isn't _ideal_, but it's better than disabling host key checking for every SSH command.
ssh-keygen -R "${addr}"
ssh-keyscan -v -T 300 "${addr}" >> ~/.ssh/known_hosts

echo "Provisioning certificate for ${name} signed by ${ca}..."

# Generate a private key and CSR remotely
ssh root@"${addr}" <<EOF
mkdir '/etc/ssl/${ca}'
cd '/etc/ssl/${ca}'
openssl req -new -newkey rsa:2048 -nodes -keyout '${name}.key' -out '${name}.csr' -subj '/CN=${name}'
EOF

# Copy the CSR down for cert generation
scp root@"${addr}":"/etc/ssl/${ca}/${name}.csr" "${tmp}/"

# Sign the CSR to get a certificate
if [[ -f "${ca}-ca.srl" ]]; then
	serial_arg="-CAserial ${ca}-ca.srl"
else
	serial_arg="-CAcreateserial"
fi

openssl x509 -req \
	-in "${tmp}/${name}.csr" \
	-CA "${ca}-ca.pem" \
	-CAkey "${ca}-ca-key.pem" \
	${serial_arg} \
	-out "${tmp}/${name}.crt" \
	-days "${days:-365}"

# Copy the certificate back up
scp "${tmp}/${name}.crt" root@"${addr}":"/etc/ssl/${ca}/${name}.crt"

# Clean up the remote CSR
ssh root@"${addr}" "rm /etc/ssl/${ca}/${name}.csr"

# Update the owner.
ssh root@"${addr}" "chown -R ${owner}:nobody /etc/ssl/${ca}"

A  => server.tf +34 -0
@@ 1,34 @@
terraform {
  required_providers {
    linode = {
      source  = "linode/linode"
      version = ">= 0.0.1"
    }
  }
}

variable "linode_token" {
  type = string
}

provider "linode" {
  token = var.linode_token
}


data "linode_profile" "me" {}

locals {
  cn = "server1.dc1.consul"
}

resource "linode_instance" "server" {
  region           = "ca-central"
  image            = "linode/opensuse15.3"
  type             = "g6-nanode-1"
  authorized_users = [data.linode_profile.me.username]

  provisioner "local-exec" {
    command = "./provision-cert --addr ${self.ip_address} --ca consul-agent --name ${local.cn} --owner consul"
  }
}