commit 42ec743a008362446842b5f501c61ca1a1dae1b8 Author: Julian Tölle Date: Wed Jan 30 19:49:47 2019 +0100 bitwarden deployment diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23fcdf8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +keys/id_terraform* +credentials.tfvars +terraform.tfstate* +.terraform \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b8eed6 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +TF=terraform +TFFLAGS=-var-file=credentials.tfvars +VALIDATE=terraform validate -check-variables=false + +apply: init + $(TF) apply $(TFFLAGS) + +plan: init + $(TF) plan $(TFFLAGS) + +destroy: init + $(TF) destroy $(TFFLAGS) + +lint: init + $(VALIDATE) modules/docker_node + $(VALIDATE) modules/floating_ip + $(VALIDATE) services/bitwarden + $(VALIDATE) . + +init: keys + $(TF) init + +keys: keys/id_terraform + echo "No private key found! Generating Terraform SSH Keys." + ./bootstrap-keys.sh \ No newline at end of file diff --git a/bootstrap-keys.sh b/bootstrap-keys.sh new file mode 100755 index 0000000..8ad8366 --- /dev/null +++ b/bootstrap-keys.sh @@ -0,0 +1,2 @@ +ssh-keygen -t rsa -C "terraform@narando.de" -f keys/id_terraform +chmod 600 keys/id_terraform* \ No newline at end of file diff --git a/keys/README b/keys/README new file mode 100755 index 0000000..d15860e --- /dev/null +++ b/keys/README @@ -0,0 +1 @@ +Please execute ./bootstrap-keys.sh to generate TLS key for Terraform. \ No newline at end of file diff --git a/main.tf b/main.tf new file mode 100755 index 0000000..69a5a75 --- /dev/null +++ b/main.tf @@ -0,0 +1,11 @@ +module bitwarden { + source = "services/bitwarden" + + location = "${var.hcloud_location}" + ssh_key_id = "${hcloud_ssh_key.terraform.id}" + bitwarden_admin_email = "${var.admin_email}" +} + +variable admin_email { + type = "string" +} diff --git a/modules/docker_node/main.tf b/modules/docker_node/main.tf new file mode 100755 index 0000000..4baef14 --- /dev/null +++ b/modules/docker_node/main.tf @@ -0,0 +1,20 @@ +resource "hcloud_server" "node" { + name = "${var.name}" + image = "${var.image}" + server_type = "${var.server_type}" + location = "${var.location}" + + ssh_keys = ["${var.ssh_key_id}"] + + connection { + private_key = "${file("./keys/id_terraform")}" + } + + provisioner "remote-exec" { + scripts = [ + "modules/docker_node/scripts/wait-cloud-init.sh", + "modules/docker_node/scripts/install-docker.sh", + "modules/docker_node/scripts/install-docker-compose.sh", + ] + } +} diff --git a/modules/docker_node/outputs.tf b/modules/docker_node/outputs.tf new file mode 100755 index 0000000..714a180 --- /dev/null +++ b/modules/docker_node/outputs.tf @@ -0,0 +1,7 @@ +output ip { + value = "${hcloud_server.node.ipv4_address}" +} + +output id { + value = "${hcloud_server.node.id}" +} diff --git a/modules/docker_node/scripts/install-docker-compose.sh b/modules/docker_node/scripts/install-docker-compose.sh new file mode 100755 index 0000000..14d7bfc --- /dev/null +++ b/modules/docker_node/scripts/install-docker-compose.sh @@ -0,0 +1,3 @@ +curl -L "https://github.com/docker/compose/releases/download/1.23.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose +docker-compose --version \ No newline at end of file diff --git a/modules/docker_node/scripts/install-docker.sh b/modules/docker_node/scripts/install-docker.sh new file mode 100755 index 0000000..3d04c76 --- /dev/null +++ b/modules/docker_node/scripts/install-docker.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e +# Source: https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-using-the-repository + +echo "# apt-get update" +apt-get update +echo "# apt-get upgrade -y" +DEBIAN_FRONTEND='noninteractive' apt-get -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' upgrade + +# Add Repository +echo "# apt-get install" +apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + software-properties-common +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - +add-apt-repository \ + "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) \ + stable" + +echo "# apt-get update" +apt-get update + +# Install Docker +echo "# apt-get install docker-ce" +apt-get install -y docker-ce diff --git a/modules/docker_node/scripts/wait-cloud-init.sh b/modules/docker_node/scripts/wait-cloud-init.sh new file mode 100755 index 0000000..29fc9ad --- /dev/null +++ b/modules/docker_node/scripts/wait-cloud-init.sh @@ -0,0 +1,13 @@ +# cloud-init is running at boot time and blocking access to apt. +# Before doing anything we should wait for it to finish. +# cloud-init creates a file after finishing boot. + +echo "Waiting for cloud-init to finish provisioning the instance." +while [ ! -f /var/lib/cloud/instance/boot-finished ] +do + echo "#" + sleep 2 +done + +# Wait some more to be sure +sleep 10 \ No newline at end of file diff --git a/modules/docker_node/vars.tf b/modules/docker_node/vars.tf new file mode 100755 index 0000000..d65b798 --- /dev/null +++ b/modules/docker_node/vars.tf @@ -0,0 +1,22 @@ +variable name { + type = "string" +} + +variable image { + type = "string" + default = "ubuntu-18.04" +} + +variable server_type { + type = "string" + default = "cx11" +} + +variable location { + type = "string" + default = "nbg1" +} + +variable ssh_key_id { + type = "string" +} diff --git a/modules/floating_ip/files/99-floating.cfg b/modules/floating_ip/files/99-floating.cfg new file mode 100755 index 0000000..92843b9 --- /dev/null +++ b/modules/floating_ip/files/99-floating.cfg @@ -0,0 +1,4 @@ +auto eth0:1 +iface eth0:1 inet static + address ${FLOATING_IP} + netmask 255.255.255.255 \ No newline at end of file diff --git a/modules/floating_ip/main.tf b/modules/floating_ip/main.tf new file mode 100755 index 0000000..b4d995d --- /dev/null +++ b/modules/floating_ip/main.tf @@ -0,0 +1,48 @@ +################# +### IP ADDRESS ## +################# + +resource hcloud_floating_ip main { + type = "${var.type}" + description = "${var.host}" + home_location = "${var.location}" +} + +resource "hcloud_rdns" "main" { + floating_ip_id = "${hcloud_floating_ip.main.id}" + ip_address = "${hcloud_floating_ip.main.ip_address}" + dns_ptr = "${var.host}" +} + +################################### +### ASSIGNMENT AND PROVISIONING ### +################################### + +data "template_file" "network_config" { + template = "${file("modules/floating_ip/files/99-floating.cfg")}" + + vars { + FLOATING_IP = "${hcloud_floating_ip.main.ip_address}" + } +} + +resource hcloud_floating_ip_assignment main { + floating_ip_id = "${hcloud_floating_ip.main.id}" + server_id = "${var.server_id}" + + connection = { + host = "${var.server_ip}" + private_key = "${file("keys/id_terraform")}" + } + + provisioner file { + content = "${data.template_file.network_config.rendered}" + destination = "/etc/network/interfaces.d/99-floating.cfg" + } + + provisioner remote-exec { + inline = [ + "ifdown eth0:1 ; ifup eth0:1", + ] + } +} diff --git a/modules/floating_ip/outputs.tf b/modules/floating_ip/outputs.tf new file mode 100755 index 0000000..3b48314 --- /dev/null +++ b/modules/floating_ip/outputs.tf @@ -0,0 +1,3 @@ +output ip { + value = "${hcloud_floating_ip.main.ip_address}" +} diff --git a/modules/floating_ip/vars.tf b/modules/floating_ip/vars.tf new file mode 100755 index 0000000..dcf6cc8 --- /dev/null +++ b/modules/floating_ip/vars.tf @@ -0,0 +1,21 @@ +variable host { + type = "string" +} + +variable type { + type = "string" + default = "ipv4" +} + +variable server_id { + type = "string" +} + +variable server_ip { + type = "string" +} + +variable location { + type = "string" + default = "nbg1" +} diff --git a/output.tf b/output.tf new file mode 100755 index 0000000..2e93e81 --- /dev/null +++ b/output.tf @@ -0,0 +1,3 @@ +output bitwarden_ip { + value = "${module.bitwarden.ip}" +} diff --git a/provider_hcloud.tf b/provider_hcloud.tf new file mode 100755 index 0000000..00cd3bc --- /dev/null +++ b/provider_hcloud.tf @@ -0,0 +1,25 @@ +# Set the variable value in *.tfvars file +# or using -var="hcloud_token=..." CLI option +variable "hcloud_token" {} + +variable "hcloud_location" {} + +# Configure the Hetzner Cloud Provider +provider "hcloud" { + version = "~> 1.7.0" + + token = "${var.hcloud_token}" +} + +####################### +## Terraform SSH Key ## +####################### + +resource "hcloud_ssh_key" "terraform" { + name = "terraform" + public_key = "${file("./keys/id_terraform.pub")}" + + labels = { + "description" = "Used by terraform to provision nodes" + } +} diff --git a/provider_other.tf b/provider_other.tf new file mode 100755 index 0000000..83575b4 --- /dev/null +++ b/provider_other.tf @@ -0,0 +1,7 @@ +provider "null" { + version = "~> 1.0" +} + +provider "template" { + version = "~> 1.0" +} diff --git a/services/bitwarden/files/docker-compose.yaml b/services/bitwarden/files/docker-compose.yaml new file mode 100644 index 0000000..9008dc7 --- /dev/null +++ b/services/bitwarden/files/docker-compose.yaml @@ -0,0 +1,46 @@ +version: "2.1" + +services: + traefik: + image: traefik:1.7 + restart: always + ports: + - 80:80 + - 443:443 + networks: + - web + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${INSTALL_DIR}/traefik.toml:/traefik.toml + - ${INSTALL_DIR}/acme.json:/acme.json + container_name: traefik + + bitwarden: + image: mprasil/bitwarden:latest + restart: always + expose: + - "80" + - "3012" + networks: + - web + volumes: + - ${BITWARDEN_DATA_DIR}/:/data/ + environment: + SIGNUPS_ALLOWED: "false" + SERVER_ADMIN_EMAIL: "${BITWARDEN_ADMIN_EMAIL}" + labels: + - "traefik.frontend.rule=Host:${HOST}" + - "traefik.docker.network=web" + - "traefik.port=80" + - "traefik.enable=true" + - "traefik.web.frontend.rule=Host:${HOST}" + - "traefik.web.port=80" + - "traefik.hub.frontend.rule=Path:/notifications/hub" + - "traefik.hub.port=3012" + - "traefik.negotiate.frontend.rule=Path:/notifications/hub/negotiate" + - "traefik.negotiate.port=80" + container_name: bitwarden + +networks: + web: + name: web diff --git a/services/bitwarden/files/traefik.toml b/services/bitwarden/files/traefik.toml new file mode 100644 index 0000000..d35bd36 --- /dev/null +++ b/services/bitwarden/files/traefik.toml @@ -0,0 +1,28 @@ +debug = true + +logLevel = "INFO" +defaultEntryPoints = ["https","http"] + +[entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.http.redirect] + entryPoint = "https" + [entryPoints.https] + address = ":443" + [entryPoints.https.tls] + +[retry] + +[docker] +endpoint = "unix:///var/run/docker.sock" +watch = true +exposedByDefault = false + +[acme] +email = "julian.toelle97@gmail.com" +storage = "acme.json" +entryPoint = "https" +onHostRule = true + [acme.httpChallenge] + entryPoint = "http" \ No newline at end of file diff --git a/services/bitwarden/main.tf b/services/bitwarden/main.tf new file mode 100755 index 0000000..298a46e --- /dev/null +++ b/services/bitwarden/main.tf @@ -0,0 +1,134 @@ +########## +## NODE ## +########## +module "node" { + source = "../../modules/docker_node" + + name = "${var.name}" + + ssh_key_id = "${var.ssh_key_id}" +} + +############ +## VOLUME ## +############ + +resource "hcloud_volume" "data" { + name = "${var.volume_name}" + size = "${var.volume_size}" + location = "${var.location}" + + format = "ext4" +} + +resource hcloud_volume_attachment "data" { + volume_id = "${hcloud_volume.data.id}" + server_id = "${module.node.id}" + + automount = true +} + +resource null_resource "start-stop-bitwarden" { + # This resource is responsible for starting and stopping Bitwarden before + # changing volume assignments. This should avoid data corruption. + depends_on = ["hcloud_volume_attachment.data", "null_resource.install-bitwarden"] + + triggers = { + id = "${hcloud_volume_attachment.data.id}" + } + + connection = { + host = "${module.node.ip}" + private_key = "${file("keys/id_terraform")}" + } + + provisioner remote-exec { + # Stop bitwarden container before unmounting data volume + when = "destroy" + + inline = [ + "echo Stopping Bitwarden", + "docker stop bitwarden", + ] + } + + provisioner remote-exec { + # Start bitwarden after mounting new volume + inline = [ + "echo Starting Bitwarden", + "cd ${local.install_dir}", + "docker-compose up -d", + ] + } +} + +################ +## IP ADDRESS ## +################ + +module floating_ip { + source = "../../modules/floating_ip" + + location = "${var.location}" + host = "${var.host}" + server_id = "${module.node.id}" + server_ip = "${module.node.ip}" +} + +################# +## APPLICATION ## +################# + +data "template_file" "compose" { + template = "${file("services/bitwarden/files/docker-compose.yaml")}" + + vars = { + INSTALL_DIR = "${local.install_dir}" + BITWARDEN_DATA_DIR = "${local.bitwarden_data_dir}" + BITWARDEN_ADMIN_EMAIL = "${var.bitwarden_admin_email}" + HOST = "${var.host}" + } +} + +resource "null_resource" "install-bitwarden" { + depends_on = ["module.node", "hcloud_volume_attachment.data"] + + triggers { + node_id = "${module.node.id}" + volume_id = "${hcloud_volume.data.id}" + + docker_compose = "${sha1(data.template_file.compose.rendered)}" + traefik_config = "${sha1(file("services/bitwarden/files/traefik.toml"))}" + } + + connection = { + host = "${module.node.ip}" + private_key = "${file("keys/id_terraform")}" + } + + provisioner remote-exec { + inline = [ + "mkdir -p ${local.install_dir}", + "touch ${local.install_dir}/acme.json", + "chmod 600 ${local.install_dir}/acme.json", + ] + } + + provisioner file { + content = "${data.template_file.compose.rendered}" + destination = "${local.install_dir}/docker-compose.yaml" + } + + provisioner file { + source = "services/bitwarden/files/traefik.toml" + destination = "${local.install_dir}/traefik.toml" + } + + provisioner remote-exec { + inline = [ + "cd ${local.install_dir}", + "docker-compose pull", + "docker-compose up -d", + ] + } +} diff --git a/services/bitwarden/output.tf b/services/bitwarden/output.tf new file mode 100755 index 0000000..cf946df --- /dev/null +++ b/services/bitwarden/output.tf @@ -0,0 +1,3 @@ +output ip { + value = "${module.floating_ip.ip}" +} diff --git a/services/bitwarden/vars.tf b/services/bitwarden/vars.tf new file mode 100755 index 0000000..1734ec5 --- /dev/null +++ b/services/bitwarden/vars.tf @@ -0,0 +1,39 @@ +variable location { + type = "string" + default = "nbg1" +} + +variable ssh_key_id { + type = "string" +} + +variable volume_size { + type = "string" + default = 10 +} + +variable name { + type = "string" + default = "bitwarden" +} + +variable volume_name { + type = "string" + default = "bitwarden-data" +} + +variable host { + type = "string" + default = "bitwarden.apricote.de" +} + +variable bitwarden_admin_email { + type = "string" +} + +locals = { + volume_path = "/mnt/${var.volume_name}" + + install_dir = "/opt/${var.name}" + bitwarden_data_dir = "${local.volume_path}" +} diff --git a/terraform.tfvars b/terraform.tfvars new file mode 100755 index 0000000..733af89 --- /dev/null +++ b/terraform.tfvars @@ -0,0 +1 @@ +hcloud_location = "nbg1"