├── .editorconfig ├── .gitignore ├── README.md ├── example └── main.tf ├── helpers └── scripts │ ├── command_exists │ ├── config_write │ ├── consul │ ├── consul_configure │ ├── consul_env │ ├── docker │ ├── download │ ├── is_empty_dir │ ├── json_merge │ ├── json_read │ ├── local_begin │ ├── local_end │ ├── local_prepare │ ├── nomad │ ├── nomad_configure │ ├── nomad_env │ ├── remote_begin │ ├── remote_end │ ├── service_configure │ ├── service_download │ ├── service_install │ ├── service_latest │ ├── service_uninstall │ ├── shred │ ├── sigsum │ ├── tls_cloudflared_enable │ ├── tls_local_add │ ├── tls_local_enable │ ├── vault │ ├── vault_configure │ ├── vault_env │ ├── vault_init │ └── vault_unseal ├── main.tf ├── outputs.tf └── variables.tf /.editorconfig: -------------------------------------------------------------------------------- 1 | # 2018 January 24 2 | # https://github.com/bevry/base 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = false 11 | indent_style = tab 12 | 13 | [{*.mk,*.py}] 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | [*.md] 18 | indent_style = space 19 | indent_size = 4 20 | 21 | [{*.json,*.yml,*.bowerrc,*.babelrc}] 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.json] 26 | insert_final_newline = true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2017 April 12 2 | # https://github.com/bevry/base 3 | 4 | # System Files 5 | **/.DS_Store 6 | 7 | # Temp Files 8 | yarn.lock 9 | **/.docpad.db 10 | **/out.* 11 | **/*.log 12 | **/*.cpuprofile 13 | **/*.heapsnapshot 14 | 15 | # Build Files 16 | build/ 17 | components/ 18 | bower_components/ 19 | node_modules/ 20 | out/ 21 | *output/ 22 | coffeejs/ 23 | coffee/ 24 | es5/ 25 | es2015/ 26 | esnext/ 27 | docs/ 28 | 29 | # Editor Caches 30 | .c9/ 31 | .vscode/ 32 | 33 | # Private Files 34 | .env 35 | .idea 36 | .cake_task_cache 37 | 38 | 39 | # ===================================== 40 | # CUSTOM 41 | 42 | helpers/data 43 | helpers/files 44 | helpers/outputs 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hashistack on Scaleway 2 | 3 | [Terraform](https://www.terraform.io) module to deploy [Consul](https://www.consul.io), [Nomad](https://www.nomadproject.io), [Vault](https://www.vaultproject.io) onto [Scaleway](https://www.scaleway.com) 4 | 5 | This module is currently under construction. I would love assistance. [Please reach out.](https://balupton.com/meet) 6 | 7 | ## Features 8 | 9 | - [x] deploys a consul, vault, nomad, docker cluster to scaleway 10 | - [x] configures firewalls correctly 11 | - [x] uses local TLS via `tls_mode=local` 12 | - [x] uses mutual TLS for consul and vault 13 | - [ ] uses mutual TLS for nomad 14 | - [ ] uses [Cloudflare's Argo Tunnel](https://www.cloudflare.com/products/argo-tunnel/) via `tls_mode=cloudflared` 15 | - [ ] uses [fabio](https://github.com/fabiolb/fabio) or [traefik](https://github.com/containous/traefik) 16 | 17 | ## Preparation 18 | 19 | If you are using MacOS, you will need to do the following: 20 | 21 | ``` bash 22 | brew install coreutils 23 | npm i -g json 24 | ``` 25 | 26 | ## Servers 27 | 28 | Origin Server: 29 | 30 | - Creates consul server + vault server 31 | - Initialises consul 32 | - Initialises vault 33 | - Generates nomad vault configuration 34 | - Generates TLS certificates via vault pki 35 | - Restarts consul and vault with TLS 36 | 37 | Master Server: 38 | 39 | - Creates consul server + nomad server 40 | 41 | Slave Server: 42 | 43 | - Creates consul agent + docker + nomad agent 44 | 45 | 46 | ## Usage 47 | 48 | Refer to [`./example/main.tf`](https://github.com/bevry/terraform-scaleway-hashistack/blob/master/example/main.tf) 49 | 50 | 51 | ## Debugging 52 | 53 | If you need to debug DNS: 54 | 55 | ``` bash 56 | sudo yum install -y net-tools # ifconfig 57 | sudo yum install -y bind-utils # dig 58 | netstat -lnp 59 | netstat -rn 60 | route -n 61 | dig consul.service.consul 62 | dig @127.0.0.1 -p 8600 consul.service.consul SRV 63 | ``` 64 | 65 | 66 | 67 | ## License 68 | 69 | Unless stated otherwise all works are: 70 | 71 | - Copyright © 2018+ [Benjamin Lupton](https://balupton.com) 72 | 73 | and licensed under: 74 | 75 | - [MIT License](http://spdx.org/licenses/MIT.html) 76 | 77 | 78 | -------------------------------------------------------------------------------- /example/main.tf: -------------------------------------------------------------------------------- 1 | # ===================================== 2 | # Variables 3 | 4 | variable "hostname" { 5 | type = "string" 6 | default = "bevry.me" 7 | } 8 | 9 | variable "region" { 10 | type = "string" 11 | default = "par1" 12 | } 13 | 14 | variable "image" { 15 | type = "string" 16 | default = "" 17 | } 18 | 19 | variable "bootscript" { 20 | type = "string" 21 | default = "" 22 | } 23 | 24 | variable "base_server_status" { 25 | type = "string" 26 | default = "stopped" 27 | } 28 | 29 | # ===================================== 30 | # Locals 31 | 32 | provider "scaleway" { 33 | region = "${var.region}" 34 | } 35 | 36 | data "scaleway_bootscript" "centos" { 37 | architecture = "arm64" 38 | name_filter = "mainline 4.14" 39 | } 40 | 41 | data "scaleway_image" "centos" { 42 | architecture = "arm64" 43 | name = "CentOS 7.3" 44 | } 45 | 46 | locals { 47 | image = "${var.image != "" ? "${var.image}" : "${data.scaleway_image.centos.id}"}" 48 | bootscript = "${var.bootscript != "" ? "${var.bootscript}" : "${data.scaleway_bootscript.centos.id}"}" 49 | data_path = "${path.root}/data" 50 | private_key_path = "${path.root}/.ssh/scaleway" 51 | } 52 | 53 | # ===================================== 54 | # Server: Origin 55 | 56 | module "cluster_origin" { 57 | source = "bevry/hashistack/scaleway" 58 | 59 | providers = { 60 | scaleway = "scaleway" 61 | } 62 | 63 | image = "${local.image}" 64 | bootscript = "${local.bootscript}" 65 | data_path = "${local.data_path}" 66 | private_key_path = "${local.private_key_path}" 67 | region = "${var.region}" 68 | hostname = "${var.hostname}" 69 | type = "origin" 70 | count = 1 71 | consul_expect = 1 72 | } 73 | 74 | # ===================================== 75 | # Server: Agents 76 | 77 | module "par1_cluster_master" { 78 | source = "bevry/hashistack/scaleway" 79 | 80 | providers = { 81 | scaleway = "scaleway" 82 | } 83 | 84 | image = "${local.image}" 85 | bootscript = "${local.bootscript}" 86 | data_path = "${local.data_path}" 87 | private_key_path = "${local.private_key_path}" 88 | region = "${var.region}" 89 | hostname = "${var.hostname}" 90 | type = "master" 91 | count = 2 92 | join = "${module.cluster_origin.private_ip}" 93 | consul_expect = 3 94 | nomad_expect = 2 95 | } 96 | 97 | module "par1_cluster_slave" { 98 | source = "bevry/hashistack/scaleway" 99 | 100 | providers = { 101 | scaleway = "scaleway" 102 | } 103 | 104 | image = "${local.image}" 105 | bootscript = "${local.bootscript}" 106 | data_path = "${local.data_path}" 107 | private_key_path = "${local.private_key_path}" 108 | region = "${var.region}" 109 | hostname = "${var.hostname}" 110 | type = "slave" 111 | count = 2 112 | join = "${module.cluster_origin.private_ip}" 113 | } 114 | -------------------------------------------------------------------------------- /helpers/scripts/command_exists: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | type "$1" &> /dev/null -------------------------------------------------------------------------------- /helpers/scripts/config_write: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo "" 6 | 7 | dir="$1" 8 | 9 | for arg in "${@:2}"; do 10 | value="${arg#*=}" 11 | key="${arg%=*}" 12 | echo -n "$value" > "$dir/$key" 13 | done 14 | 15 | echo '' 16 | -------------------------------------------------------------------------------- /helpers/scripts/consul: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Install 8 | ./service_install consul 9 | ./service_configure consul 10 | 11 | # Gossip Encryption 12 | consul_type="$(cat ../data/input/consul_type)" 13 | if test "$consul_type" != "slave"; then 14 | # keyring interaction is not available for clients/slaves, will produce: 15 | # error: Unexpected response code: 500 (keyring operations must run against a server node) 16 | echo 'Configuring Gossip Encryption for consul...' 17 | source ./consul_env 18 | consul_gossip_key="$(cat ../data/shared/auth/consul_gossip_key)" 19 | consul keyring -install="$consul_gossip_key" 20 | consul keyring -use="$consul_gossip_key" 21 | consul keyring -list 22 | fi 23 | 24 | echo '' 25 | -------------------------------------------------------------------------------- /helpers/scripts/consul_configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Variables 8 | user="$(cat ../data/input/consul_user)" 9 | group="$(cat ../data/input/consul_group)" 10 | consul_type="$(cat ../data/input/consul_type)" 11 | private_ip="$(cat ../data/input/private_ip)" 12 | loopback_ip="$(cat ../data/input/loopback_ip)" 13 | join="$(cat ../data/input/join)" 14 | 15 | # Shared 16 | consul_gossip_key="$(cat ../data/shared/auth/consul_gossip_key)" 17 | 18 | # Write configuration files 19 | echo "Writing consul configuration..." 20 | cat > ../data/local/conf/consul.conf < ../data/local/conf/consul_base.json < ../data/local/conf/consul_tls.json < ../data/local/conf/consul_origin.json < ../data/local/conf/consul_master.json < ../data/local/conf/consul_slave.json < ../data/local/conf/consul.json 111 | else 112 | echo 'Configuring Consul without TLS...' 113 | ./json_merge \ 114 | ../data/local/conf/consul_base.json \ 115 | "../data/local/conf/consul_${consul_type}.json" > ../data/local/conf/consul.json 116 | fi 117 | 118 | # DNS 119 | echo "Configuring Consul DNS access..." 120 | if grep "1.1.1.1" < /etc/resolv.conf; then 121 | echo 'nameservers already adjusted' 122 | else 123 | echo 'adjusting nameservers...' 124 | resolv_contents="$(cat /etc/resolv.conf)" 125 | echo -e "nameserver ${loopback_ip}\\nnameserver 1.1.1.1\\nnameserver 1.0.0.1\\n${resolv_contents}" > /etc/resolv.conf 126 | fi 127 | # sudo service networking restart 128 | # systemctl disable NetworkManager 129 | # systemctl enable network 130 | # we can't just do 131 | # sudo echo "nameserver $LOOPBACK_IP" > /etc/resolv.conf 132 | # becasuse /etc/resolv.conf also contains these, which is necessary for scaleway networking perhaps... we could try add them to the consul resolver, but not sure that works 133 | # nameserver 10.1.94.8 134 | # domain cloud.online.net 135 | # search cloud.online.net 136 | 137 | echo '' 138 | -------------------------------------------------------------------------------- /helpers/scripts/consul_env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo '' 4 | 5 | if test "${BASH_SOURCE[0]}" = "${0}"; then 6 | echo "${BASH_SOURCE[0]} must be sourced" 7 | exit 1 8 | fi 9 | 10 | # https://www.consul.io/docs/commands/index.html#environment-variables 11 | if test -f /etc/certs/consul.key; then 12 | export CONSUL_HTTP_SSL=true 13 | export CONSUL_HTTP_SSL_VERIFY=true 14 | export CONSUL_CACERT=/etc/certs/user.ca 15 | export CONSUL_CLIENT_CERT=/etc/certs/user.crt 16 | export CONSUL_CLIENT_KEY=/etc/certs/user.key 17 | # CONSUL_TLS_SERVER_NAME 18 | fi 19 | 20 | echo '' 21 | -------------------------------------------------------------------------------- /helpers/scripts/docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | echo "Installing Docker..." 8 | sudo yum install -y docker 9 | sudo systemctl enable docker 10 | sudo systemctl start docker 11 | 12 | echo '' 13 | -------------------------------------------------------------------------------- /helpers/scripts/download: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | # follow redirects (-L), be quiet (-s), but show errors (-S) 6 | curl -sS -L "$1" -o "$2" -------------------------------------------------------------------------------- /helpers/scripts/is_empty_dir: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | # include hidden files 6 | shopt -s nullglob dotglob 7 | if test -d "$1"; then 8 | files=("$1"/*) 9 | count="${#files[@]}" 10 | test "$count" -eq 0 11 | else 12 | exit 0 13 | fi -------------------------------------------------------------------------------- /helpers/scripts/json_merge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // jq -s '.[0] * .[1]' one.json two.json > out.json 3 | 4 | const fs = require('fs') 5 | 6 | const results = process.argv.slice(2).map(function (value) { 7 | return JSON.parse(fs.readFileSync(value).toString()) 8 | }) 9 | 10 | const result = results.reduce(function (previousValue, currentValue) { 11 | return Object.assign({}, previousValue, currentValue) 12 | }) 13 | 14 | process.stdout.write(JSON.stringify(result, null, ' ')) -------------------------------------------------------------------------------- /helpers/scripts/json_read: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | json "$1" | tr -d '\n' 6 | -------------------------------------------------------------------------------- /helpers/scripts/local_begin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo "" 6 | 7 | # Inputs 8 | data_path="$1" 9 | 10 | # Notify 11 | if ./command_exists say; then 12 | say "server allocated, starting setup" 13 | fi 14 | 15 | # Variables 16 | public_ip="$(cat "$data_path/input/public_ip")" 17 | 18 | # Check latest version 19 | ./service_latest "consul" 20 | ./service_latest "vault" 21 | ./service_latest "nomad" 22 | 23 | # Known Hosts 24 | echo "removing old keys of the host" 25 | ssh-keygen -R "$public_ip" || echo 'no old keys for the host' 26 | echo "adding new keys for the host" 27 | until ssh-keyscan "$public_ip" >> "$HOME/.ssh/known_hosts"; do 28 | sleep 10 29 | done 30 | echo "cleaning up" 31 | ./shred "$HOME/.ssh/known_hosts.old" 32 | echo 'testing the new connection' 33 | echo 'exit' | ssh -T "root@$public_ip" 34 | 35 | echo '' 36 | -------------------------------------------------------------------------------- /helpers/scripts/local_end: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo "" 6 | 7 | # Inputs 8 | data_path="$1" 9 | 10 | # Variables 11 | private_key_path="$(cat "$data_path/input/private_key_path")" 12 | private_ip="$(cat "$data_path/input/private_ip")" 13 | public_ip="$(cat "$data_path/input/public_ip")" 14 | hostname="$(cat "$data_path/input/hostname")" 15 | type="$(cat "$data_path/input/type")" 16 | nomad_type="$(cat "$data_path/input/nomad_type")" 17 | vault_type="$(cat "$data_path/input/vault_type")" 18 | tls_mode="$(cat "$data_path/input/tls_mode")" 19 | 20 | # Copy remote files 21 | if test "$vault_type" = "origin"; then 22 | echo "copying outputs and extracting values" 23 | scp -i "$private_key_path" -r "root@$public_ip:/root/cluster/data/output/*" "$data_path/output" 24 | fi 25 | 26 | # Copy outputs to shared 27 | if test -f "$data_path/output/auth/consul_gossip_key"; then 28 | cp -v "$data_path/output/auth/consul_gossip_key" "$data_path/shared/auth/" 29 | fi 30 | if test -f "$data_path/output/auth/nomad_gossip_key"; then 31 | cp -v "$data_path/output/auth/nomad_gossip_key" "$data_path/shared/auth/" 32 | fi 33 | if test "$type" = "origin"; then 34 | cp -v "$data_path/output/auth/cluster_token" "$data_path/shared/auth/" 35 | cp -v "$data_path/output/auth/nomad_token" "$data_path/shared/auth/" 36 | if test "$tls_mode" = "local"; then 37 | if ! ./is_empty_dir "$data_path/output/cert"; then 38 | cp -v "$data_path/output/cert/"* "$data_path/shared/cert/" 39 | fi 40 | fi 41 | fi 42 | 43 | # Add certificates to system 44 | if test "$tls_mode" = "local"; then 45 | if test "$type" = "origin"; then 46 | ./tls_local_add "$data_path/output/cert/consul" 47 | ./tls_local_add "$data_path/output/cert/vault" 48 | fi 49 | if test -n "$nomad_type"; then 50 | ./tls_local_add "$data_path/output/cert/nomad" 51 | fi 52 | fi 53 | 54 | # IPs 55 | echo '' 56 | echo "Cluster Type: $type" 57 | echo "Public IP: $public_ip" 58 | echo "Private IP: $private_ip" 59 | 60 | # SSH 61 | echo '' 62 | echo 'SSH:' 63 | echo "ssh root@$public_ip" 64 | echo "export private_ip=$private_ip" 65 | 66 | # Consul 67 | echo '' 68 | echo 'Consul Web UI:' 69 | echo "ssh -L 127.0.0.1:8500:127.0.0.1:8500 root@$public_ip" 70 | echo "open http://127.0.0.1:8500" 71 | echo "open https://consul.$hostname:8500" 72 | 73 | # Vault 74 | if test -n "$vault_type"; then 75 | echo '' 76 | echo 'Vault Web UI:' 77 | echo "ssh -L 127.0.0.1:8200:${private_ip}:8200 root@$public_ip" 78 | echo "open http://127.0.0.1:8200/ui" 79 | echo "open https://vault.$hostname:8200/ui" 80 | fi 81 | 82 | # Nomad 83 | if test -n "$nomad_type"; then 84 | echo '' 85 | echo 'Nomad Web UI:' 86 | echo "ssh -L 127.0.0.1:4646:${private_ip}:4646 root@$public_ip" 87 | echo "open http://127.0.0.1:4646" 88 | echo "open https://server.nomad.$hostname:4646" 89 | fi 90 | 91 | # Notify 92 | if ./command_exists say; then 93 | say "server setup, all done" 94 | fi 95 | 96 | 97 | echo "...ALL DONE..." 98 | 99 | echo '' 100 | 101 | -------------------------------------------------------------------------------- /helpers/scripts/local_prepare: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo "" 6 | 7 | # Inputs 8 | root_data_path="$1" 9 | data_path="$1/$2" 10 | type="$3" 11 | 12 | # Link 13 | rm -f "$data_path/shared" 14 | ./shred "$data_path" 15 | if test "$type" = "origin"; then 16 | # @todo detect if certs exist already on origin deploy 17 | # if so ask the user if they would like to reuse them 18 | # and add support for it 19 | # it would require a nomad cluster still persisting, as vault stores its stuff there 20 | # or a restore of a consul dump 21 | # https://www.consul.io/docs/commands/kv/export.html 22 | # https://www.consul.io/docs/commands/kv/import.html 23 | ./shred "$root_data_path/shared" 24 | fi 25 | mkdir -p "$data_path" "$root_data_path/shared/auth" "$root_data_path/shared/cert" "$data_path/input" "$data_path/output" 26 | 27 | # Link shared inside the server's data path 28 | if test "$3" != "base"; then 29 | ln -Fs "$root_data_path/shared" "$data_path/shared" 30 | fi 31 | 32 | # Create Gossip Encryption keys if they don't exist 33 | if ! test -f "$data_path/shared/auth/consul_gossip_key"; then 34 | openssl rand -base64 16 > "$data_path/shared/auth/consul_gossip_key" 35 | fi 36 | if ! test -f "$data_path/shared/auth/nomad_gossip_key"; then 37 | openssl rand -base64 16 > "$data_path/shared/auth/nomad_gossip_key" 38 | fi 39 | 40 | echo '' 41 | 42 | -------------------------------------------------------------------------------- /helpers/scripts/nomad: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Install 8 | ./service_install nomad 9 | ./service_configure nomad 10 | 11 | # Gossip Encryption 12 | nomad_type="$(cat ../data/input/nomad_type)" 13 | if test "$nomad_type" != "slave"; then 14 | # keyring interaction is not available for clients/slaves, will produce: 15 | # error: Unexpected response code: 501 (Invalid method) 16 | echo 'Configuring Gossip Encryption for nomad...' 17 | source ./nomad_env 18 | nomad_gossip_key="$(cat ../data/shared/auth/nomad_gossip_key)" 19 | nomad operator keyring -install="$nomad_gossip_key" 20 | nomad operator keyring -use="$nomad_gossip_key" 21 | nomad operator keyring -list 22 | fi 23 | 24 | echo '' 25 | 26 | -------------------------------------------------------------------------------- /helpers/scripts/nomad_configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Variables 8 | user="$(cat ../data/input/nomad_user)" 9 | group="$(cat ../data/input/nomad_group)" 10 | count="$(cat ../data/input/count)" 11 | nomad_type="$(cat ../data/input/nomad_type)" 12 | private_ip="$(cat ../data/input/private_ip)" 13 | region="$(cat ../data/input/region)" 14 | 15 | # Shared 16 | nomad_token="$(cat ../data/shared/auth/nomad_token)" 17 | nomad_gossip_key="$(cat ../data/shared/auth/nomad_gossip_key)" 18 | 19 | # https://www.nomadproject.io/docs/agent/configuration/index.html 20 | # https://www.nomadproject.io/guides/cluster/automatic.html 21 | # https://www.nomadproject.io/docs/agent/configuration/consul.html 22 | # https://www.nomadproject.io/docs/vault-integration/index.html 23 | # "bootstrap_expect": $nomad_expect, 24 | echo "Writing nomad configuration..." 25 | cat > ../data/local/conf/nomad.conf < ../data/local/conf/nomad_base.json < ../data/local/conf/nomad_tls.json < ../data/local/conf/nomad_master.json < ../data/local/conf/nomad_slave.json < ../data/local/conf/nomad.json 130 | else 131 | echo 'Configuring Nomad without TLS...' 132 | ./json_merge \ 133 | ../data/local/conf/nomad_base.json \ 134 | "../data/local/conf/nomad_${nomad_type}.json" > ../data/local/conf/nomad.json 135 | fi 136 | 137 | echo '' 138 | 139 | -------------------------------------------------------------------------------- /helpers/scripts/nomad_env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo '' 4 | 5 | if test "${BASH_SOURCE[0]}" = "${0}"; then 6 | echo "${BASH_SOURCE[0]} must be sourced" 7 | exit 1 8 | fi 9 | 10 | # Variables 11 | private_ip="$(cat ../data/input/private_ip)" 12 | 13 | # TLS 14 | if test -f /etc/certs/nomad.key; then 15 | # https://www.nomadproject.io/docs/commands/index.html 16 | export NOMAD_ADDR="https://${private_ip}:4646" 17 | # https://www.nomadproject.io/guides/securing-nomad.html#node-certificates 18 | # client.global.nomad for a client node in the global region 19 | # server.global.nomad for a server node in the us-west region 20 | export NOMAD_CACERT=/etc/certs/nomad.ca 21 | export NOMAD_CLIENT_CERT=/etc/certs/nomad.crt 22 | export NOMAD_CLIENT_KEY=/etc/certs/nomad.key 23 | # export NOMAD_TLS_SERVER_NAME 24 | else 25 | export NOMAD_ADDR="http://${private_ip}:4646" 26 | fi 27 | 28 | echo '' -------------------------------------------------------------------------------- /helpers/scripts/remote_begin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Variables 8 | type="$(cat ../data/input/type)" 9 | consul_type="$(cat ../data/input/consul_type)" 10 | vault_type="$(cat ../data/input/vault_type)" 11 | docker_type="$(cat ../data/input/docker_type)" 12 | nomad_type="$(cat ../data/input/nomad_type)" 13 | tls_mode="$(cat ../data/input/tls_mode)" 14 | 15 | # Prepare 16 | mkdir -p \ 17 | ../data/input \ 18 | ../data/local/conf \ 19 | ../data/output/cert \ 20 | ../data/output/vault \ 21 | ../data/output/auth \ 22 | /etc/certs 23 | 24 | echo "Installing dependencies..." 25 | sudo yum update -y 26 | if ! ./command_exists unzip; then 27 | sudo yum install -y unzip 28 | fi 29 | # sudo yum install -y net-tools # ifconfig 30 | # sudo yum install -y bind-tools # dig 31 | 32 | # As JQ does not exist for arm64, use node and json 33 | # https://github.com/stedolan/jq/issues/1655 34 | # https://github.com/trentm/json 35 | if ! ./command_exists node; then 36 | sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 37 | sudo yum install -y nodejs --enablerepo=epel 38 | fi 39 | if ! ./command_exists json; then 40 | sudo npm install -g json 41 | fi 42 | 43 | if test "$tls_mode" = "cloudflared"; then 44 | # this may or may not work due to 45 | # https://github.com/cloudflare/cloudflared/issues/22 46 | # https://github.com/cloudflare/cloudflared/issues/25 47 | sudo yum install -y git 48 | 49 | # install go manually, as yum has older version 50 | # https://golang.org/doc/install 51 | go_version='1.10.1' 52 | go_arch='arm64' 53 | go_url="https://dl.google.com/go/go${go_version}.linux-${go_arch}.tar.gz" 54 | go_file="$(mktemp -d)/go$VERSION.$OS-$ARCH.tar.gz" 55 | ./download "$go_url" "$go_file" 56 | tar -C /usr/local -xzf "$go_file" 57 | export PATH=$PATH:/usr/local/go/bin 58 | shred "$go_file" 59 | 60 | # install cloudflared 61 | sudo go install -v github.com/cloudflare/cloudflared/cmd/cloudflared 62 | fi 63 | 64 | # End of base 65 | if test "$type" = "base"; then 66 | exit 0 67 | fi 68 | 69 | if test "$tls_mode" = "local"; then 70 | if ! ./is_empty_dir ../data/shared/cert; then 71 | cp -v ../data/shared/cert/* /etc/certs 72 | fi 73 | fi 74 | 75 | echo "Configuring ports..." 76 | # https://github.com/bevry/terraform-scaleway-hashistack/issues/11 77 | # @todo not sure if this is necessary when using cloudflared 78 | tr ',' '\n' < ../data/input/ports_local_tcp | while read -r port || [ -n "$port" ]; do 79 | echo "configuring local $port/tcp" 80 | sudo firewall-cmd --zone=public --add-port="$port/tcp" --permanent 81 | done 82 | tr ',' '\n' < ../data/input/ports_local_udp | while read -r port || [ -n "$port" ]; do 83 | echo "configuring local $port/udp" 84 | sudo firewall-cmd --zone=public --add-port="$port/udp" --permanent 85 | done 86 | sudo firewall-cmd --reload 87 | 88 | echo "Configuring services as $type..." 89 | 90 | if test -n "$consul_type"; then 91 | echo "Configuring consul as $consul_type..." 92 | ./consul 93 | fi 94 | 95 | if test -n "$vault_type"; then 96 | echo "Configuring vault as $vault_type..." 97 | ./vault 98 | fi 99 | 100 | if test -n "$docker_type"; then 101 | echo "Configuring docker as $docker_type..." 102 | ./docker 103 | fi 104 | 105 | if test -n "$nomad_type"; then 106 | echo "Configuring nomad as $nomad_type..." 107 | ./nomad 108 | fi 109 | 110 | if test "$tls_mode" = "local"; then 111 | echo "Enabling Local TLS via Vault PKI..." 112 | ./tls_local_enable 113 | elif test "$tls_mode" = "cloudflared"; then 114 | echo 'Enabling TLS via Cloudflare Argo Tunnel (cloudflared)...' 115 | ./tls_cloudflared_enable 116 | fi 117 | 118 | echo '' 119 | -------------------------------------------------------------------------------- /helpers/scripts/remote_end: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | ./shred /root/cluster 8 | 9 | echo '' 10 | -------------------------------------------------------------------------------- /helpers/scripts/service_configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo "" 6 | 7 | # Arguments 8 | service="$1" 9 | 10 | # Variables 11 | user="$(cat "../data/input/${service}_user")" 12 | group="$(cat "../data/input/${service}_group")" 13 | 14 | echo "Running custom configuration script for $service..." 15 | "./${service}_configure" 16 | 17 | echo "Configuring service configuration for $service..." 18 | # service configuration 19 | sudo rm -Rf "/etc/systemd/system/$service.d" 20 | sudo mkdir -p "/etc/systemd/system/$service.d" 21 | sudo mv "../data/local/conf/$service.json" "/etc/systemd/system/$service.d/$service.json" 22 | sudo chmod 0640 "/etc/systemd/system/$service.d/$service.json" 23 | sudo chown "$user:$group" "/etc/systemd/system/$service.d/$service.json" 24 | 25 | echo "Injecting service for $service..." 26 | # service specification 27 | sudo rm -Rf "/etc/systemd/system/$service.service" 28 | sudo mv "../data/local/conf/$service.conf" "/etc/systemd/system/$service.service" 29 | sudo chmod 0640 "/etc/systemd/system/$service.service" 30 | sudo chown "$user:$group" "/etc/systemd/system/$service.service" 31 | 32 | echo "Reload service daeomon $service..." 33 | sudo systemctl daemon-reload 34 | 35 | echo "Enabling service for $service..." 36 | sudo systemctl enable "$service.service" 37 | 38 | echo "Starting service for $service..." 39 | if ! sudo systemctl restart "$service"; then 40 | echo 'failed to start...' 41 | echo 'here is the service status:' 42 | sudo systemctl status "$service" -l 43 | echo 'here are the process logs:' 44 | sudo journalctl -u "$service" 45 | echo 'and here is the manual execution:' 46 | service_exec="$(grep ExecStart < "/etc/systemd/system/$service.service" | sed 's/ExecStart=//')" 47 | $service_exec 48 | fi 49 | sleep 2 50 | 51 | echo '' 52 | -------------------------------------------------------------------------------- /helpers/scripts/service_download: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | 6 | echo "" 7 | 8 | 9 | # Files 10 | temp="$(mktemp -d)" 11 | 12 | # Clean 13 | function finish { 14 | ./shred "$temp" 15 | } 16 | trap finish EXIT 17 | 18 | # Locals 19 | service="$1" 20 | version="$2" 21 | out_file="$3" 22 | platform="linux_arm64" 23 | 24 | # Download ZIP 25 | zip_filename="${service}_${version}_${platform}.zip" 26 | zip_url="https://releases.hashicorp.com/${service}/${version}/${service}_${version}_${platform}.zip" 27 | zip_file="$temp/$service.zip" 28 | ./download "$zip_url" "$zip_file" 29 | 30 | # Download Signature 31 | sig_url="https://releases.hashicorp.com/${service}/${version}/${service}_${version}_SHA256SUMS.sig" 32 | sig_file="$temp/$service.sig" 33 | ./download "$sig_url" "$sig_file" 34 | 35 | # Download Hashes 36 | sha_url="https://releases.hashicorp.com/${service}/${version}/${service}_${version}_SHA256SUMS" 37 | sha_file="$temp/$service.sha" 38 | ./download "$sha_url" "$sha_file" 39 | 40 | # Verify Signature with GPG 41 | key_url="https://keybase.io/hashicorp/pgp_keys.asc" 42 | key_file="$temp/hashicorp.asc" 43 | ./download "$key_url" "$key_file" 44 | gpg --import "$key_file" 45 | gpg --verify "$sig_file" "$sha_file" 46 | 47 | # Verify Signature with Keybase 48 | if ./command_exists keybase; then 49 | keybase pgp verify -d "$sig_file" -S hashicorp -i "$sha_file" 50 | fi 51 | 52 | # Verify Zip 53 | sha_expected="$(grep "$zip_filename" < "$sha_file" | sed 's/ .*//')" 54 | sha_actual="$(./sigsum "$zip_file" | sed 's/ .*//')" 55 | if test "$sha_expected" = "$sha_actual"; then 56 | echo "zip file hash matched" 57 | else 58 | echo "zip file hash did not match!" 59 | echo "expected hash: $sha_expected" 60 | echo "actual hash: $sha_actual" 61 | exit 1 62 | fi 63 | 64 | # Move zip to files 65 | mv "$zip_file" "$out_file" 66 | 67 | 68 | echo '' 69 | 70 | -------------------------------------------------------------------------------- /helpers/scripts/service_install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo "" 6 | 7 | # Locals 8 | temp="$(mktemp -d)" 9 | 10 | # Clean 11 | function finish { 12 | ./shred "$temp" 13 | } 14 | trap finish EXIT 15 | 16 | # Functions 17 | function get_version { 18 | set +o pipefail 19 | "$1" -v | grep --color=never -Eo '[0-9]+\.[0-9]+\.[0-9]+' 20 | set -o pipefail 21 | } 22 | function user_exists { 23 | local user="$1" 24 | id "$user" >/dev/null 2>&1 25 | } 26 | 27 | # Arguments 28 | service="$1" 29 | user="$(cat "../data/input/${service}_user")" 30 | group="$(cat "../data/input/${service}_group")" 31 | expected_version="$(cat "../data/input/${service}_version")" 32 | 33 | echo "Downloading $service..." 34 | ./service_download "$service" "$expected_version" "$temp/$service.zip" 35 | 36 | echo "Unzipping $service..." 37 | unzip -d "$temp" "$temp/$service.zip" 38 | chmod +x "$temp/$service" 39 | 40 | echo "Checking $service version..." 41 | version="$(get_version "$temp/$service")" 42 | if test "$version" != "$expected_version"; then 43 | echo "version was not as expected" 44 | echo "actual version: $version" 45 | echo "desired version: $expected_version" 46 | exit 1 47 | fi 48 | 49 | echo "Moving $service into path..." 50 | sudo mv "$temp/$service" "/usr/local/bin/$service" 51 | 52 | echo "Detecting $service user $user..." 53 | if ! user_exists "$user"; then 54 | echo "Adding $service user $user..." 55 | sudo useradd "$user" 56 | fi 57 | 58 | echo "Setting $service executable permissions..." 59 | # make the executable only runnable by the user 60 | sudo chown "$user:$group" "/usr/local/bin/$service" 61 | sudo chmod 0740 "/usr/local/bin/$service" 62 | 63 | echo "Adjusting $service executable..." 64 | # ensure sensitive data is written by the system 65 | if test "$service" = "vault"; then 66 | echo "Adjusting $service executable for vault..." 67 | sudo setcap cap_ipc_lock=+ep /usr/local/bin/vault 68 | fi 69 | 70 | echo "Initialising data for $service..." 71 | # data configuration 72 | rm -Rf "/opt/$service" 73 | mkdir -p "/opt/$service/data" 74 | sudo chmod -R 0740 "/opt/$service" 75 | sudo chown -R "$user:$group" "/opt/$service" 76 | 77 | echo '' 78 | -------------------------------------------------------------------------------- /helpers/scripts/service_latest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo "" 6 | 7 | # Locals 8 | service="$1" 9 | if test "$#" -eq 2; then 10 | version="$2" 11 | else 12 | version=false 13 | fi 14 | 15 | # Functions 16 | function fetch_latest_version { 17 | curl -s "https://releases.hashicorp.com/$1/index.json" | json versions | json -ka | grep -v "beta" | grep -v "rc" | grep -v "alpha" | sort -V | tail -n 1 18 | } 19 | 20 | # Fetch 21 | latest_version="$(fetch_latest_version "$service")" 22 | 23 | # Check latest version 24 | if test "$version" = false; then 25 | echo "$service [latest $latest_version] https://github.com/hashicorp/$service/blob/master/CHANGELOG.md" 26 | elif test "$latest_version" = "$version"; then 27 | echo "$service [current $version] is [latest $latest_version] https://github.com/hashicorp/$service/blob/master/CHANGELOG.md" 28 | elif test "$latest_version" != "$version"; then 29 | echo "$service [current $version] isnt [latest $latest_version] https://github.com/hashicorp/$service/blob/master/CHANGELOG.md" 30 | fi 31 | 32 | echo '' 33 | -------------------------------------------------------------------------------- /helpers/scripts/service_uninstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo "" 6 | 7 | service="$1" 8 | 9 | if test "$service" = "vault"; then 10 | vault_type="$(cat ../data/input/vault_type)" 11 | if test "$vault_type" = "origin"; then 12 | echo "erasing previous origin vault" 13 | systemctl stop vault 14 | consul kv delete --recurse vault 15 | fi 16 | ./shred ../data/output/vault/* 17 | ./shred ../data/output/auth/*_token 18 | ./shred ../data/output/auth/unseal_key_* 19 | ./shred "/etc/certs/${service}.key" 20 | ./shred "/etc/certs/${service}.crt" 21 | ./shred "/etc/certs/${service}.ca" 22 | else 23 | echo "cannot yet uninstall the service $1" 24 | exit 1 25 | fi 26 | 27 | echo "" 28 | -------------------------------------------------------------------------------- /helpers/scripts/shred: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | 4 | if ./command_exists shred; then 5 | exec="shred" 6 | args="-u" 7 | elif ./command_exists gshred; then 8 | exec="gshred" 9 | args="-u" 10 | else 11 | exec="rm" 12 | args="-Rf" 13 | fi 14 | 15 | for p in "$@"; do 16 | if test -d "$p"; then 17 | find "$p" -type f -exec "$exec" "$args" {} \; 18 | rm -Rfv "$p" 19 | elif test -f "$p"; then 20 | echo "$exec" "$args" "$p" 21 | "$exec" "$args" "$p" 22 | else 23 | echo "no need to shred the path, as it does not seem to exist: $1" 24 | fi 25 | done 26 | 27 | -------------------------------------------------------------------------------- /helpers/scripts/sigsum: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | 4 | if ./command_exists sha256sum; then 5 | sha256sum "$1" 6 | elif ./command_exists shasum; then 7 | shasum -a 256 "$1" 8 | else 9 | echo "missing sha256sum/shasum" 10 | exit 1 11 | fi -------------------------------------------------------------------------------- /helpers/scripts/tls_cloudflared_enable: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | 4 | echo '' 5 | 6 | # Variables 7 | hostname="$(cat ../data/input/hostname)" 8 | 9 | echo 'verify cloudflared works' 10 | cloudflared --version 11 | 12 | # echo 'login to cloudflared' 13 | # cloudflared login 14 | 15 | echo 'create the service' 16 | # cloudflared/config.yml 17 | sudo cloudflared service install 18 | 19 | # echo 'create a tunnel' 20 | # cloudflared --hostname "$hostname" --hello-world 21 | 22 | # /Users/balupton/.cloudflared/cert.pem 23 | echo '' 24 | -------------------------------------------------------------------------------- /helpers/scripts/tls_local_add: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | 4 | echo "" 5 | 6 | # https://github.com/balupton/dotfiles/blob/master/.scripts/commands/cert 7 | 8 | # Inputs 9 | base_path="$1" 10 | name="$(basename "$base_path")" 11 | 12 | # Locals 13 | pass="$(openssl rand -base64 16)" 14 | cert_file="$base_path.crt" 15 | ca_file="$base_path.ca" 16 | key_file="$base_path.key" 17 | bundle_file="$base_path.p12" 18 | 19 | # Actions 20 | echo "creating $name pki bundle" 21 | openssl pkcs12 -export -CAfile "$ca_file" -inkey "$key_file" -in "$cert_file" -password "pass:$pass" -out "$bundle_file" 22 | echo "removing old $name pki bundle from system" 23 | echo '!!! ENTER YOUR OPERATING SYSTEM PASSWORD IN THE PROMPT BELOW TO CONTINUE !!!' 24 | sudo echo 'ok' 25 | subject="$(openssl x509 -subject -in "$cert_file" -noout | sed 's/.*CN=//')" 26 | sudo security delete-identity -c "$subject" || echo "old bundle doesn't exist in system - ok" 27 | echo "adding $name pki bundle to keychain" 28 | sudo security import "$bundle_file" -P "$pass" 29 | echo "trusting $name cert" 30 | sudo security add-trusted-cert -d -r trustAsRoot "$cert_file" 31 | echo "trusting $name cert authority" 32 | sudo security add-trusted-cert -d -r trustAsRoot "$ca_file" 33 | echo "setting $name usage preference" 34 | sudo security set-identity-preference -c "$subject" -s "https://$subject" 35 | 36 | echo '' 37 | -------------------------------------------------------------------------------- /helpers/scripts/tls_local_enable: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Variables 8 | consul_type="$(cat ../data/input/consul_type)" 9 | vault_type="$(cat ../data/input/vault_type)" 10 | loopback_ip="$(cat ../data/input/loopback_ip)" 11 | private_ip="$(cat ../data/input/private_ip)" 12 | public_ip="$(cat ../data/input/public_ip)" 13 | hostname="$(cat ../data/input/hostname)" 14 | 15 | # ------------------------------------- 16 | # Vault Access 17 | 18 | if ! ./command_exists vault; then 19 | ./service_install vault 20 | fi 21 | source ./vault_env 22 | 23 | 24 | # ------------------------------------- 25 | # Consul 26 | 27 | if test "$consul_type" = "origin"; then 28 | echo 'Generating TLS for consul...' 29 | vault write -format=json pki_int/issue/host common_name="consul.$hostname" alt_names="consul.service.consul,server.global.consul" ip_sans="${private_ip},${loopback_ip},${public_ip}" > ../data/output/vault/pki_int_consul.json 30 | json data.private_key < ../data/output/vault/pki_int_consul.json > ../data/output/cert/consul.key 31 | json data.certificate < ../data/output/vault/pki_int_consul.json > ../data/output/cert/consul.crt 32 | json data.issuing_ca < ../data/output/vault/pki_int_consul.json > ../data/output/cert/consul.ca 33 | cp -v ../data/output/cert/vault* /etc/certs 34 | 35 | echo 'Configuring TLS for consul...' 36 | ./service_configure consul 37 | 38 | echo 'Checking consul...' 39 | source ./consul_env 40 | consul info 41 | fi 42 | 43 | 44 | # ------------------------------------- 45 | # Vault 46 | 47 | if test "$vault_type" = "origin"; then 48 | echo 'Generating TLS for vault...' 49 | vault write -format=json pki_int/issue/host common_name="vault.$hostname" alt_names="vault.service.consul,server.global.vault" ip_sans="${private_ip},${loopback_ip},${public_ip}" > ../data/output/vault/pki_int_vault.json 50 | json data.private_key < ../data/output/vault/pki_int_vault.json > ../data/output/cert/vault.key 51 | json data.certificate < ../data/output/vault/pki_int_vault.json > ../data/output/cert/vault.crt 52 | json data.issuing_ca < ../data/output/vault/pki_int_vault.json > ../data/output/cert/vault.ca 53 | cp -v ../data/output/cert/vault* /etc/certs 54 | 55 | echo 'Configuring TLS for vault...' 56 | ./service_configure vault 57 | 58 | echo 'Reunsealing Vault...' 59 | ./vault_unseal 60 | 61 | echo 'Checking vault...' 62 | source ./vault_env 63 | vault status 64 | fi 65 | 66 | 67 | # ------------------------------------- 68 | # Nomad 69 | 70 | # not yet supported 71 | # https://github.com/bevry/terraform-scaleway-hashistack/issues/12 72 | 73 | # if test "$nomad_type" = "master"; then 74 | # vault write -format=json pki_int/issue/host common_name="server.nomad.$hostname" alt_names="server.global.nomad" ip_sans="${private_ip},${loopback_ip},${public_ip}" > ../data/output/vault/pki_int_nomad.json 75 | # json data.private_key < ../data/output/vault/pki_int_nomad.json > ../data/output/cert/nomad.key 76 | # json data.certificate < ../data/output/vault/pki_int_nomad.json > ../data/output/cert/nomad.crt 77 | # json data.issuing_ca < ../data/output/vault/pki_int_nomad.json > ../data/output/cert/nomad.ca 78 | 79 | # echo 'Configuring TLS for nomad...' 80 | # ./service_configure nomad 81 | 82 | # echo 'Checking nomad...' 83 | # source ./nomad_env 84 | # nomad status 85 | 86 | # elif test "$nomad_type" = "slave"; then 87 | # vault write -format=json pki_int/issue/host common_name="client.nomad.$hostname" alt_names="client.global.nomad" ip_sans="${private_ip},${loopback_ip},${public_ip}" > ../data/output/vault/pki_int_nomad.json 88 | # json data.private_key < ../data/output/vault/pki_int_nomad.json > ../data/output/cert/nomad.key 89 | # json data.certificate < ../data/output/vault/pki_int_nomad.json > ../data/output/cert/nomad.crt 90 | # json data.issuing_ca < ../data/output/vault/pki_int_nomad.json > ../data/output/cert/nomad.ca 91 | 92 | # echo 'Configuring TLS for nomad...' 93 | # ./service_configure nomad 94 | 95 | # echo 'Checking nomad...' 96 | # source ./nomad_env 97 | # nomad status 98 | # fi 99 | 100 | echo '' -------------------------------------------------------------------------------- /helpers/scripts/vault: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Install vault 8 | ./service_install vault 9 | ./service_configure vault 10 | 11 | # Initialise vault 12 | ./vault_init 13 | # curl --cacert /etc/certs/vault.ca --cert /etc/certs/vault.crt --key /etc/certs/vault.key "https://${private_ip}:8200" 14 | 15 | echo '' 16 | -------------------------------------------------------------------------------- /helpers/scripts/vault_configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Variables 8 | user="$(cat ../data/input/vault_user)" 9 | group="$(cat ../data/input/vault_group)" 10 | vault_type="$(cat ../data/input/vault_type)" 11 | private_ip="$(cat ../data/input/private_ip)" 12 | 13 | # Check vault server type 14 | if test "$vault_type" != "origin"; then 15 | echo "Currently only support origin vault servers" 16 | exit 1 17 | fi 18 | 19 | echo "Writing vault configuration..." 20 | cat > ../data/local/conf/vault.conf < ../data/local/conf/vault_base.json < ../data/local/conf/vault_tls.json < ../data/local/conf/vault.json 91 | else 92 | echo 'Configuring Vault without TLS...' 93 | cp ../data/local/conf/vault_base.json ../data/local/conf/vault.json 94 | fi 95 | 96 | echo '' 97 | -------------------------------------------------------------------------------- /helpers/scripts/vault_env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo '' 4 | 5 | if test "${BASH_SOURCE[0]}" = "${0}"; then 6 | echo "${BASH_SOURCE[0]} must be sourced" 7 | exit 1 8 | fi 9 | 10 | # Variables 11 | private_ip="$(cat ../data/input/private_ip)" 12 | vault_type="$(cat ../data/input/vault_type)" 13 | if test "$vault_type" = "origin"; then 14 | vault_hostname="$private_ip" 15 | else 16 | vault_hostname='vault.service.consul' 17 | fi 18 | 19 | # Exports 20 | export VAULT_CLI_NO_COLOR=true 21 | 22 | # Configure vault CLI to access the vault server 23 | if test -f /etc/certs/vault.key; then 24 | # https://www.vaultproject.io/docs/commands/index.html#environment-variables 25 | export VAULT_ADDR="https://${vault_hostname}:8200" 26 | export VAULT_CACERT=/etc/certs/vault.ca 27 | export VAULT_CLIENT_CERT=/etc/certs/vault.crt 28 | export VAULT_CLIENT_KEY=/etc/certs/vault.key 29 | # VAULT_TLS_SERVER_NAME 30 | else 31 | export VAULT_ADDR="http://${vault_hostname}:8200" 32 | fi 33 | 34 | # Update the vault token 35 | if test -f ../data/shared/auth/cluster_token; then 36 | echo 'using shared cluster_token' 37 | cluster_token="$(cat ../data/shared/auth/cluster_token)" 38 | export VAULT_TOKEN="$cluster_token" 39 | elif test -f ../data/output/auth/cluster_token; then 40 | echo 'using cluster_token' 41 | cluster_token="$(cat ../data/output/auth/cluster_token)" 42 | export VAULT_TOKEN="$cluster_token" 43 | elif test -f ../data/output/auth/root_token; then 44 | echo 'using root_token' 45 | root_token="$(cat ../data/output/auth/root_token)" 46 | export VAULT_TOKEN="$root_token" 47 | fi 48 | 49 | echo '' 50 | -------------------------------------------------------------------------------- /helpers/scripts/vault_init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Local 8 | five_years=43800h 9 | short_ttl=72h 10 | temp="$(mktemp -d)" 11 | policy_file="$temp/nomad-server-policy.hcl" 12 | role_file="$temp/nomad-cluster-role.json" 13 | 14 | # Clean 15 | function finish { 16 | ./shred "$temp" 17 | } 18 | trap finish EXIT 19 | 20 | # trim colours from vault output 21 | # | sed 's/\x1b\[[0-9;]*m//g' 22 | 23 | # Variables 24 | hostname="$(cat ../data/input/hostname)" 25 | 26 | # Prepare 27 | source ./vault_env 28 | 29 | # Initialise the vault 30 | echo "Initialising the vault..." 31 | vault operator init -format=json > ../data/output/vault/init.json 32 | ./json_read unseal_keys_b64[0] < ../data/output/vault/init.json > ../data/output/auth/unseal_key_1 33 | ./json_read unseal_keys_b64[1] < ../data/output/vault/init.json > ../data/output/auth/unseal_key_2 34 | ./json_read unseal_keys_b64[2] < ../data/output/vault/init.json > ../data/output/auth/unseal_key_3 35 | ./json_read root_token < ../data/output/vault/init.json > ../data/output/auth/root_token 36 | 37 | # Unseal the vault 38 | sleep 2 39 | ./vault_unseal 40 | sleep 2 41 | source ./vault_env 42 | sleep 2 43 | 44 | # Create the cluster token 45 | echo 'Creating the cluster token...' 46 | vault token create -format=json -display-name=cluster > ../data/output/vault/cluster.json 47 | ./json_read auth.client_token < ../data/output/vault/cluster.json > ../data/output/auth/cluster_token 48 | source ./vault_env 49 | 50 | # Enable PKI 51 | # https://www.vaultproject.io/docs/secrets/pki/index.html 52 | 53 | echo 'Creating PKI certificate authorities...' 54 | # root 55 | vault secrets enable pki 56 | vault secrets tune -max-lease-ttl="$five_years" pki 57 | vault write -format=json pki/root/generate/internal common_name="${hostname} Root Authority" ttl="$five_years" > ../data/output/vault/pki.json 58 | vault write -format=json pki/config/urls issuing_certificates="$VAULT_ADDR/v1/pki/ca" crl_distribution_points="$VAULT_ADDR/v1/pki/crl" 59 | vault write -format=json pki/roles/host allowed_domains="$hostname" allow_subdomains=true max_ttl=72h 60 | # intermediate 61 | vault secrets enable -path=pki_int pki 62 | vault secrets tune -max-lease-ttl="$five_years" pki_int 63 | # create certificate signing request 64 | vault write -format=json pki_int/intermediate/generate/internal common_name="${hostname} Intermediate Authority" ttl="$five_years" > ../data/output/vault/pki_int_csr.json 65 | json data.csr < ../data/output/vault/pki_int_csr.json > ../data/output/vault/int.csr 66 | # have the root ca sign the int ca's signing request to generate the certs for the int ca 67 | vault write -format=json pki/root/sign-intermediate csr=@../data/output/vault/int.csr format=pem_bundle > ../data/output/vault/pki_int.json 68 | json data.certificate < ../data/output/vault/pki_int.json > ../data/output/vault/pki_int.cert 69 | # upload the signed certificate to vault 70 | vault write -format=json pki_int/intermediate/set-signed certificate=@../data/output/vault/pki_int.cert 71 | vault write -format=json pki_int/config/urls issuing_certificates="$VAULT_ADDR/v1/pki_int/ca" crl_distribution_points="$VAULT_ADDR/v1/pki_int/crl" 72 | # Allow the creation of certificates 73 | vault write -format=json pki_int/roles/host allowed_domains="$hostname,consul,vault,nomad" allow_subdomains=true max_ttl="$short_ttl" 74 | 75 | # https://www.nomadproject.io/docs/vault-integration/index.html 76 | # @todo perhaps can be replaced with: https://www.vaultproject.io/docs/secrets/nomad/index.html 77 | 78 | echo "Setup vault permissions for nomad..." 79 | ./download "https://nomadproject.io/data/vault/nomad-server-policy.hcl" "$policy_file" 80 | ./download "https://nomadproject.io/data/vault/nomad-cluster-role.json" "$role_file" 81 | vault policy write nomad-server "$policy_file" 82 | vault write /auth/token/roles/nomad-cluster @"$role_file" 83 | vault token create -format=json -policy nomad-server -period "$short_ttl" -orphan > ../data/output/vault/nomad.json 84 | ./json_read auth.client_token < ../data/output/vault/nomad.json > ../data/output/auth/nomad_token 85 | 86 | echo '' 87 | -------------------------------------------------------------------------------- /helpers/scripts/vault_unseal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ueE -o pipefail 3 | cd "$(dirname "$0")" 4 | 5 | echo '' 6 | 7 | # Reload the vault env 8 | source ./vault_env 9 | 10 | # Unseal the vault 11 | echo "Unsealing the vault..." 12 | unseal_key_1="$(cat ../data/output/auth/unseal_key_1)" 13 | unseal_key_2="$(cat ../data/output/auth/unseal_key_2)" 14 | unseal_key_3="$(cat ../data/output/auth/unseal_key_3)" 15 | vault operator unseal "$unseal_key_1" 16 | vault operator unseal "$unseal_key_2" 17 | vault operator unseal "$unseal_key_3" 18 | 19 | echo '' 20 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | # Web Ports: 2 | # [80, 443] 3 | # Consul Ports: 4 | # https://www.consul.io/docs/agent/options.html#ports-used 5 | # Docker Ports: 6 | # https://stackoverflow.com/a/43404044/130638 7 | # => [2375, 2376, 2377, 5000, 4789, 7946] 8 | # https://forums.docker.com/t/docker-ports-in-aws-ec2/15799 9 | # => however, they should not be opened as they are a security risk 10 | locals { 11 | loopback_ip = "127.0.0.1" 12 | docker_types = "${map("slave", "present")}" 13 | docker_type = "${lookup(local.docker_types, var.type, "")}" 14 | consul_user = "root" 15 | consul_group = "root" 16 | consul_version = "1.0.7" 17 | consul_types = "${map("origin", "origin", "master", "master", "slave", "slave")}" 18 | consul_type = "${lookup(local.consul_types, var.type, "")}" 19 | consul_ports_local = [8301, 8302, 8600] 20 | consul_ports_local_tcp = [8300, 8500] 21 | consul_ports_local_udp = [] 22 | nomad_user = "nomad_user" 23 | nomad_group = "nomad_user" 24 | nomad_version = "0.8.1" 25 | nomad_types = "${map("master", "master", "slave", "slave")}" 26 | nomad_type = "${lookup(local.nomad_types, var.type, "")}" 27 | nomad_ports_local = [] 28 | nomad_ports_local_tcp = "${compact(split(" ", local.nomad_type == "" ? "" : "4646 4647 4648"))}" 29 | nomad_ports_local_udp = [] 30 | vault_user = "vault_user" 31 | vault_group = "vault_user" 32 | vault_version = "0.10.1" 33 | vault_types = "${map("origin", "origin")}" 34 | vault_type = "${lookup(local.vault_types, var.type, "")}" 35 | vault_ports_local = [] 36 | vault_ports_local_tcp = "${compact(split(" ", local.vault_type == "" ? "" : "8200 8201"))}" 37 | vault_ports_local_udp = [] 38 | ports_local = "${concat(local.consul_ports_local, local.nomad_ports_local, local.vault_ports_local)}" 39 | ports_local_tcp = "${distinct(concat(local.ports_local, local.consul_ports_local_tcp, local.nomad_ports_local_tcp, local.vault_ports_local_tcp))}" 40 | ports_local_udp = "${distinct(concat(local.ports_local, local.consul_ports_local_udp, local.nomad_ports_local_udp, local.vault_ports_local_udp))}" 41 | 42 | tags_string = "cluster cluster_${var.type} ${local.vault_type != "" ? "vault vault_${local.vault_type}" : ""} ${local.consul_type != "" ? "consul consul_${local.consul_type}" : ""} ${local.nomad_type != "" ? "nomad nomad_${local.nomad_type}" : ""} ${local.docker_type != "" ? "docker docker_${local.docker_type}" : ""}" 43 | tags_array = "${split(" ", local.tags_string)}" 44 | tags = "${compact(local.tags_array)}" 45 | } 46 | 47 | # ===================================== 48 | # Security Groups 49 | 50 | resource "scaleway_security_group" "cluster" { 51 | name = "${var.region}_${var.type}" 52 | description = "${var.type} cluster security group" 53 | enable_default_security = false 54 | } 55 | 56 | # Provision Server 57 | # public_ip = "${element(scaleway_ip.public_ip.*.ip, count.index)}" 58 | resource "scaleway_server" "server" { 59 | count = "${var.count}" 60 | name = "${var.region}_${var.type}_${count.index}" 61 | image = "${var.image}" 62 | bootscript = "${var.bootscript}" 63 | security_group = "${scaleway_security_group.cluster.id}" 64 | type = "ARM64-2GB" 65 | state = "${var.state}" 66 | enable_ipv6 = false 67 | dynamic_ip_required = true 68 | tags = "${local.tags}" 69 | 70 | provisioner "local-exec" { 71 | command = "chmod +x ${path.module}/helpers/scripts/*" 72 | } 73 | 74 | provisioner "local-exec" { 75 | command = "${path.module}/helpers/scripts/local_prepare ${var.data_path} ${var.region}_${var.type}_${count.index} ${var.type}" 76 | } 77 | 78 | provisioner "local-exec" { 79 | command = "${path.module}/helpers/scripts/config_write ${var.data_path}/${var.region}_${var.type}_${count.index}/input tls_mode=${var.tls_mode} consul_user=${local.consul_user} consul_group=${local.consul_group} vault_user=${local.vault_user} vault_group=${local.vault_group} nomad_user=${local.nomad_user} nomad_group=${local.nomad_group} hostname=${var.hostname} private_key_path=${var.private_key_path} ports_local_tcp=${join(",", local.ports_local_tcp)} ports_local_udp=${join(",", local.ports_local_udp)} consul_version=${local.consul_version} consul_type=${local.consul_type} nomad_version=${local.nomad_version} nomad_type=${local.nomad_type} vault_version=${local.vault_version} vault_type=${local.vault_type} docker_type=${local.docker_type} name=${var.region}_${var.type}_${count.index} count=${var.count} join=${var.join} loopback_ip=${local.loopback_ip} type=${var.type} region=${var.region} private_ip=${self.private_ip} public_ip=${self.public_ip} " 80 | } 81 | 82 | provisioner "local-exec" { 83 | command = "${path.module}/helpers/scripts/local_begin ${var.data_path}/${var.region}_${var.type}_${count.index}" 84 | } 85 | 86 | connection { 87 | type = "ssh" 88 | user = "root" 89 | timeout = "180s" 90 | private_key = "${file("${var.private_key_path}")}" 91 | agent = false 92 | } 93 | 94 | provisioner "remote-exec" { 95 | inline = [ 96 | "sysctl kernel.hostname=${var.region}_${var.type}_${count.index}", 97 | "rm -Rf /root/cluster", 98 | "mkdir -p /root/cluster /root/cluster/scripts /root/cluster/data", 99 | ] 100 | } 101 | 102 | provisioner "file" { 103 | source = "${path.module}/helpers/scripts/" 104 | destination = "/root/cluster/scripts" 105 | } 106 | 107 | provisioner "file" { 108 | source = "${var.data_path}/${var.region}_${var.type}_${count.index}/" 109 | destination = "/root/cluster/data" 110 | } 111 | 112 | provisioner "remote-exec" { 113 | inline = [ 114 | "chmod +x /root/cluster/scripts/*", 115 | "/root/cluster/scripts/remote_begin", 116 | ] 117 | } 118 | 119 | provisioner "local-exec" { 120 | command = "${path.module}/helpers/scripts/local_end ${var.data_path}/${var.region}_${var.type}_${count.index}" 121 | } 122 | 123 | provisioner "remote-exec" { 124 | inline = [ 125 | "/root/cluster/scripts/remote_end", 126 | ] 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "private_ip" { 2 | value = "${scaleway_server.server.0.private_ip}" 3 | } 4 | 5 | output "public_ip" { 6 | value = "${scaleway_server.server.0.public_ip}" 7 | } 8 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "tls_mode" { 2 | type = "string" 3 | default = "none" 4 | 5 | # options: 6 | # cloudflared: cloudflare's argo tunnel 7 | # local: generate via vault, not yet functional for nomad 8 | } 9 | 10 | variable "data_path" { 11 | type = "string" 12 | } 13 | 14 | variable "private_key_path" { 15 | type = "string" 16 | } 17 | 18 | variable "type" { 19 | type = "string" # origin, master, slave 20 | } 21 | 22 | variable "state" { 23 | type = "string" # running, stopped 24 | default = "running" 25 | } 26 | 27 | variable "hostname" { 28 | type = "string" 29 | } 30 | 31 | variable "count" { 32 | type = "string" 33 | default = 1 34 | } 35 | 36 | variable "join" { 37 | type = "string" 38 | default = "" 39 | } 40 | 41 | variable "region" { 42 | type = "string" 43 | } 44 | 45 | variable "image" { 46 | type = "string" 47 | } 48 | 49 | variable "bootscript" { 50 | type = "string" 51 | default = "" 52 | } 53 | --------------------------------------------------------------------------------