Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 0a6294db authored by Raphael JOIE's avatar Raphael JOIE
Browse files

Merge branch '1-experiment-vault' into 'main'

Resolve "experiment vault"

Closes #1

See merge request !1
parents e4aba38a eff62f56
Branches
Tags v0.1.0
1 merge request!1Resolve "experiment vault"
Pipeline #119685 passed
include:
- project: 'digit-c4/dev/best-practices'
file: 'gitlab-ci/test-static-scan.yml'
ref: main
stages:
- test
test-static-scan-gitleaks:
stage: test
## Generate documentation
```shell
terraform-docs markdown table --output-file README.md --output-mode inject --indent 3 .
```
# Remote container deployment using Terraform
Deploy a Docker container, with Vaulted mounted files and environment variables
```tf
module nginx {
source = "https://code.europa.eu/digit-c4/dev/terraform-container.git"
image = "nginx"
vault_path = "cubbyhole/foo"
files_host_dir = "/home/joierap/test"
env = {
"LOG_LEVEL": "DEBUG"
}
vault_env = {
"DB_PASSWORD": {}
}
files = {
"/opt/lib/test.crt" = {
content = "blabetiblou"
}
}
vault_files = {
"/etc/nginx/conf.d/nginx.conf.example" = {
secret_key = "nginx.conf"
}
}
}
```
## API Documentation
<!-- BEGIN_TF_DOCS -->
### Requirements
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13 |
| <a name="requirement_docker"></a> [docker](#requirement\_docker) | 3.0.2 |
| <a name="requirement_remote"></a> [remote](#requirement\_remote) | 0.2.1 |
| <a name="requirement_vault"></a> [vault](#requirement\_vault) | 3.23.0 |
### Providers
| Name | Version |
|------|---------|
| <a name="provider_docker"></a> [docker](#provider\_docker) | 3.0.2 |
| <a name="provider_remote"></a> [remote](#provider\_remote) | 0.2.1 |
| <a name="provider_vault"></a> [vault](#provider\_vault) | 3.23.0 |
### Modules
No modules.
### Resources
| Name | Type |
|------|------|
| [docker_container.container](https://registry.terraform.io/providers/kreuzwerker/docker/3.0.2/docs/resources/container) | resource |
| [docker_image.image](https://registry.terraform.io/providers/kreuzwerker/docker/3.0.2/docs/resources/image) | resource |
| [remote_file.file](https://registry.terraform.io/providers/widespot/remote/0.2.1/docs/resources/file) | resource |
| [remote_file.vault_file](https://registry.terraform.io/providers/widespot/remote/0.2.1/docs/resources/file) | resource |
| [remote_folder.files_host_dir](https://registry.terraform.io/providers/widespot/remote/0.2.1/docs/resources/folder) | resource |
| [vault_generic_secret.secret](https://registry.terraform.io/providers/hashicorp/vault/3.23.0/docs/data-sources/generic_secret) | data source |
### Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_env"></a> [env](#input\_env) | A mapping of environment variables and values | `map(string)` | `{}` | no |
| <a name="input_files"></a> [files](#input\_files) | A mapping of file content to bind-mount on the container. The file countent is set to the mandatory `content` attribute of the mappin object, and the mount path is either the `mount_path` attribute if provided, or the mapping key. Before to be mounted, the files are created on the host machine in the `files_host_dir` and named after the key or the key hash | <pre>map(object({<br> content = string<br> host_filename = optional(string)<br> mount_path = optional(string)<br> }))</pre> | `{}` | no |
| <a name="input_files_host_dir"></a> [files\_host\_dir](#input\_files\_host\_dir) | The path to a directory where the files must be stored on the remote Docker host .Directory cannot pre-exist and will be erased when the module is desotryed | `string` | n/a | yes |
| <a name="input_image"></a> [image](#input\_image) | Docker image | `string` | n/a | yes |
| <a name="input_image_keep_locally"></a> [image\_keep\_locally](#input\_image\_keep\_locally) | Whether the image should stay on host after destroy | `bool` | `true` | no |
| <a name="input_name"></a> [name](#input\_name) | Container name, if none provided `image` will be used instead | `string` | `null` | no |
| <a name="input_vault_env"></a> [vault\_env](#input\_vault\_env) | A mapping of environment variable and Vault key. Default value of `vault_path` attribute is the `vault_path` variable. Default `secret_key` is the entry key. | <pre>map(object({<br> vault_path = optional(string)<br> secret_key = optional(string)<br> }))</pre> | `{}` | no |
| <a name="input_vault_files"></a> [vault\_files](#input\_vault\_files) | Files to mount, content loaded from Vault. Default value of `vault_path` attribute is the `vault_path` variable. Default `secret_key` is the entry key. Default `host_filename` is the `sha256` hash of the entry key. Default `mount_path` is the entry key | <pre>map(object({<br> vault_path = optional(string)<br> secret_key = optional(string)<br> host_filename = optional(string)<br> mount_path = optional(string)<br> }))</pre> | `{}` | no |
| <a name="input_vault_path"></a> [vault\_path](#input\_vault\_path) | default Vault path to use when not provided in one of the `vault_env` or `vault_files` entries. Required when an entry in those inputs is missing the `vault_path` attribute | `string` | `null` | no |
### Outputs
No outputs.
<!-- END_TF_DOCS -->
## Examples
see implemented example in `/example` directory
### `files` attribute default values
```
files = {
"/opt/lib/test.crt" = {
content = "blabetiblou"
# Default value is the entry key
# mount_path = "/opt/lib/test.crt"
# Default value is sha256 of the entry key
# host_filename = sha256("opt/lib/test.crt")
}
}
```
### `vault_files` default values
```
vault_files = {
"/etc/nginx/conf.d/nginx.conf.example" = {
# secret_key = "/etc/nginx/conf.d/nginx.conf.example"
# Default value is the entry key
# mount_path = "/etc/nginx/conf.d/nginx.conf.example"
# Default value is sha256 of the entry key
# host_filename = sha256("opt/lib/test.crt")
# vault_path = var.vault_path
}
}
```
\ No newline at end of file
data.tf 0 → 100644
data "vault_generic_secret" "secret" {
for_each = toset(distinct(compact(
concat([var.vault_path], [for k,v in var.vault_env: v.vault_path], [for k,v in var.vault_files: v.vault_path])
)))
path = each.value
}
locals {
vault_env = [for k,v in var.vault_env: "${k}=${
data.vault_generic_secret.secret[coalesce(v.vault_path, var.vault_path)].data[coalesce(v.secret_key, k)]
}"]
}
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/vault" {
version = "3.23.0"
constraints = "3.23.0"
hashes = [
"h1:+MSu9iMAo/4bBfVuKnf1Q+FVj7O1MLk5JO3TxFZ+dHU=",
"zh:0f5d14007a4ae668cb2f52ed91985ace0169153643026bd44b9cf3704f271ded",
"zh:2e6b6435f3ee58c0a2c633e05b9e0091d31e653433028e455b681b601ac36712",
"zh:342bf9ff3f2500576354612fd5b04dd18e9f79d32fc9d913c435db78f72b2c50",
"zh:55e08eed3b1d8e5c395fa1ffd71cb98755dad6c52dcc4f48ea1faa3538ebe995",
"zh:613379682dc8ff5e447fc0a1d99a5f94015d18555cafc1adb8d3e5766c491222",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:8417ba0ac1c0708e4192a8eb4855cef6c64dc8e9b18e0944579d4b5031824ba9",
"zh:9218066d107c14dbc53061966dde55f88c056782458e2b59df1d185a948e2308",
"zh:b4f87e51739ccba8429f6d03f037a236261373dcd49df9edb6e17d41e013b155",
"zh:b864a6766d6690d416658b6996de06c844bcca2eb516cde4c639a3a267174440",
"zh:da1343c22bc096810f4902dfb035ae42920fd74fb9346c8ab4a0f93809b529c6",
"zh:e49210cc09b96932844950ff4fd2cf034fea0041612f43fb79d01e44ce030c8c",
]
}
provider "registry.terraform.io/kreuzwerker/docker" {
version = "3.0.2"
constraints = "3.0.2"
hashes = [
"h1:cT2ccWOtlfKYBUE60/v2/4Q6Stk1KYTNnhxSck+VPlU=",
"zh:15b0a2b2b563d8d40f62f83057d91acb02cd0096f207488d8b4298a59203d64f",
"zh:23d919de139f7cd5ebfd2ff1b94e6d9913f0977fcfc2ca02e1573be53e269f95",
"zh:38081b3fe317c7e9555b2aaad325ad3fa516a886d2dfa8605ae6a809c1072138",
"zh:4a9c5065b178082f79ad8160243369c185214d874ff5048556d48d3edd03c4da",
"zh:5438ef6afe057945f28bce43d76c4401254073de01a774760169ac1058830ac2",
"zh:60b7fadc287166e5c9873dfe53a7976d98244979e0ab66428ea0dea1ebf33e06",
"zh:61c5ec1cb94e4c4a4fb1e4a24576d5f39a955f09afb17dab982de62b70a9bdd1",
"zh:a38fe9016ace5f911ab00c88e64b156ebbbbfb72a51a44da3c13d442cd214710",
"zh:c2c4d2b1fd9ebb291c57f524b3bf9d0994ff3e815c0cd9c9bcb87166dc687005",
"zh:d567bb8ce483ab2cf0602e07eae57027a1a53994aba470fa76095912a505533d",
"zh:e83bf05ab6a19dd8c43547ce9a8a511f8c331a124d11ac64687c764ab9d5a792",
"zh:e90c934b5cd65516fbcc454c89a150bfa726e7cf1fe749790c7480bbeb19d387",
"zh:f05f167d2eaf913045d8e7b88c13757e3cf595dd5cd333057fdafc7c4b7fed62",
"zh:fcc9c1cea5ce85e8bcb593862e699a881bd36dffd29e2e367f82d15368659c3d",
]
}
provider "registry.terraform.io/widespot/remote" {
version = "0.2.1"
constraints = "0.2.1"
hashes = [
"h1:3JvhOkNsEMSFI1LnpvxXZUZx6qdkc9dz0rAZt+pelhI=",
"zh:02f85972dbd29a2ae17eb1006c0c02cde0eef5d297510d89d608c1083c863228",
"zh:062a937da205b2b66f5d9bf17f42984cc803b360a83cec545d097b580c78719b",
"zh:58558319bf6ae438c17679be498e7b8415e0ac28a5f948a4bdb5dcfd817c6e0c",
"zh:71a67bf5f22a301fe486d6db8f7a0ee014137bf0a0012bccc921a2d8b67223da",
"zh:7290bcb9ef4d7a4a81010fc0527ab8c4db5d79f2ac1dd1892cfd82e5bece33de",
"zh:79d62c1339a1a466abbc81ed51f304402dcd7fd0d8368c2e35a7454d4fd0b877",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:a0b04c453961acfc767ed7abc3e0f45d17eb1170501b17316aaa31965ddf306f",
"zh:a4b62fa2cf55f05214b95714b29099281c766488766407d4fc6cc1ae433c66ae",
"zh:aa8a22b0aacde63ee30bff298c0ea8e2968790472c6a73608a5cd3781a02d473",
"zh:c5bd0a7e56700ac5544bcbbd2201f8407d07fbc4a28db17ce333d3dbddc54845",
"zh:c9982205e3a5319c69144e1d25744cc6ee24618fe95384671cb7fc387a46e803",
"zh:d395cb87b00ad9fe7c486d38ced5426a27a5e0d0ee95cb29c12b9046a354284a",
"zh:e34fee161f9a18129d1179d970760ee7da69550d1966c38090fb2310421dbb7f",
"zh:f73d60fc83e4903960bdf17461a8e17e0c4b45174fdbc0c96a2c4cb44cc1709a",
]
}
resource "vault_generic_secret" "example" {
path = "cubbyhole/foo"
data_json = jsonencode(
{
"/etc/nginx/conf.d/nginx.conf.example" = file("nginx.conf"),
"DB_PASSWORD" = "UlTra5eCuR1t?"
}
)
}
module nginx {
source = "../"
name = "nms-nginx"
image = "nginx"
files_host_dir = "/home/joierap/test"
vault_path = vault_generic_secret.example.path
env = {
"LOG_LEVEL": "DEBUG"
}
vault_env = {
"DB_PASSWORD": {}
}
files = {
"/opt/lib/test.crt" = {
content = "ceci est un contenu"
}
}
vault_files = {
"/etc/nginx/conf.d/nginx.conf.example" = {}
}
# Secrets normally pre-exists. If not, the secret dependencies have to be
# listed here, or the module will try to load it at terraform plan time
depends_on = [
vault_generic_secret.example
]
}
user www www; ## Default: nobody
worker_processes 5; ## Default: 1
error_log logs/error.log;
pid logs/nginx.pid;
worker_rlimit_nofile 8192;
events {
worker_connections 4096; ## Default: 1024
}
http {
include conf/mime.types;
include /etc/nginx/proxy.conf;
include /etc/nginx/fastcgi.conf;
index index.html index.htm index.php;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
tcp_nopush on;
server_names_hash_bucket_size 128; # this seems to be required for some vhosts
server { # php/fastcgi
listen 80;
server_name domain1.com www.domain1.com;
access_log logs/domain1.access.log main;
root html;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:1025;
}
}
server { # simple reverse-proxy
listen 80;
server_name domain2.com www.domain2.com;
access_log logs/domain2.access.log main;
# serve static files
location ~ ^/(images|javascript|js|css|flash|media|static)/ {
root /var/www/virtual/big.server.com/htdocs;
expires 30d;
}
# pass requests for dynamic content to rails/turbogears/zope, et al
location / {
proxy_pass http://127.0.0.1:8080;
}
}
upstream big_server_com {
server 127.0.0.3:8000 weight=5;
server 127.0.0.3:8001 weight=5;
server 192.168.0.1:8000;
server 192.168.0.1:8001;
}
server { # simple load balancing
listen 80;
server_name big.server.com;
access_log logs/big.server.access.log main;
location / {
proxy_pass http://big_server_com;
}
}
}
terraform {
required_version = ">= 0.13"
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "3.0.2"
}
remote = {
source = "widespot/remote"
version = "0.2.1"
}
vault = {
source = "hashicorp/vault"
version = "3.23.0"
}
}
}
provider "remote" {
host = var.remote_host
username = var.remote_username
password = var.remote_password
}
provider "docker" {
host = "ssh://${var.remote_host}"
ssh_opts = ["-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"]
}
provider "vault" {
address = var.vault_host
token = var.vault_token
namespace = var.vault_namespace
# Required for use with NMS Vault because the tokens don't have the right to
# generate child token. (see doc https://registry.terraform.io/providers/hashicorp/vault/latest/docs#token)
skip_child_token = true
}
variable remote_username {
type = string
}
variable remote_password {
type = string
}
variable remote_host {
type = string
}
variable vault_host {
type = string
}
variable vault_token {
type = string
}
variable vault_namespace{
type = string
}
resource "docker_image" "image" {
name = "ubuntu:latest"
name = var.image
keep_locally = var.image_keep_locally
}
resource "remote_file" "volume_file" {
for_each = var.volume_files
resource "remote_folder" "files_host_dir" {
path = var.files_host_dir
}
resource "remote_file" "vault_file" {
for_each = var.vault_files
content = data.vault_generic_secret.secret[coalesce(var.vault_path, var.vault_path)].data[coalesce(each.value.secret_key, each.key)]
path = "${remote_folder.files_host_dir.path}/${coalesce(each.value.host_filename, sha256(each.key))}"
}
resource "remote_file" "file" {
for_each = var.files
content = each.value
path = "${var.volumes_root_path}/${var.container_name}/volume_files/${each.key}"
content = each.value.content
path = "${remote_folder.files_host_dir.path}/${coalesce(each.value.host_filename, sha256(each.key))}"
}
resource "docker_container" "container" {
image = docker_image.image.image_id
name = "foo"
name = coalesce(var.name, var.image)
env = toset(concat([for k, v in var.env: "${k}=${v}"], local.vault_env))
dynamic "volumes" {
for_each = remote_file.file
content {
container_path = var.files[volumes.key].mount_path == null ? volumes.key : var.files[volumes.key].mount_path
host_path = volumes.value.path
}
}
dynamic "volumes" {
for_each = remote_file.volume_file
for_each = remote_file.vault_file
content {
container_path = volumes.key
container_path = var.vault_files[volumes.key].mount_path == null ? volumes.key : var.vault_files[volumes.key].mount_path
host_path = volumes.value.path
}
}
......
variable "container_name" {
variable "name" {
type = string
default = null
description = "Container name, if none provided `image` will be used instead"
}
variable "image" {
type = string
description = "Docker image"
}
variable "volumes_root_path" {
variable "image_keep_locally" {
type = bool
default = true
description = "Whether the image should stay on host after destroy"
}
variable "files_host_dir" {
type = string
description = "The path to a directory where the files must be stored on the remote Docker host .Directory cannot pre-exist and will be erased when the module is desotryed"
}
variable "env" {
type = map(string)
default = {}
description = "A mapping of environment variables and values"
}
variable "vault_host" {
type = string
default = null
}
variable "vault_namespace" {
type = string
default = null
}
variable "vault_token" {
variable "vault_path" {
type = string
default = null
description = "default Vault path to use when not provided in one of the `vault_env` or `vault_files` entries. Required when an entry in those inputs is missing the `vault_path` attribute"
}
variable "vault_env_value" {
variable "vault_env" {
type = map(object({
vault_engine = string
vault_path = string
secret_key = string
vault_path = optional(string)
secret_key = optional(string)
}))
description = "A mapping of environment variable and Vault key"
description = "A mapping of environment variable and Vault key. Default value of `vault_path` attribute is the `vault_path` variable. Default `secret_key` is the entry key."
default = {}
}
variable "vault_files" {
type = map(object({
vault_engine = string
vault_path = string
secret_key = string
mount_path = string
vault_path = optional(string)
secret_key = optional(string)
host_filename = optional(string)
mount_path = optional(string)
}))
default = {}
description = "Files to mount, content loaded from Vault. Default value of `vault_path` attribute is the `vault_path` variable. Default `secret_key` is the entry key. Default `host_filename` is the `sha256` hash of the entry key. Default `mount_path` is the entry key"
}
variable "volume_files" {
variable "files" {
type = map(object({
content = string
host_filename = optional(string)
mount_path = optional(string)
}))
description = "A mapping of mounting paths and content"
default = {}
description = "A mapping of file content to bind-mount on the container. The file countent is set to the mandatory `content` attribute of the mappin object, and the mount path is either the `mount_path` attribute if provided, or the mapping key. Before to be mounted, the files are created on the host machine in the `files_host_dir` and named after the key or the key hash"
}
terraform {
required_version = ">= 0.13"
required_providers {
docker = {
source = "kreuzwerker/docker"
......@@ -7,7 +9,12 @@ terraform {
remote = {
source = "widespot/remote"
version = "0.1.1"
version = "0.2.1"
}
vault = {
source = "hashicorp/vault"
version = "3.23.0"
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment