Move terraform files into modules
This commit is contained in:
parent
4e495dbc51
commit
bae9fe9e0f
14 changed files with 286 additions and 104 deletions
64
modules/infrastructure/hcloud.tf
Normal file
64
modules/infrastructure/hcloud.tf
Normal file
|
@ -0,0 +1,64 @@
|
|||
resource "hcloud_ssh_key" "default" {
|
||||
name = var.default_ssh_key.name
|
||||
public_key = var.default_ssh_key.public_key
|
||||
}
|
||||
|
||||
data "template_file" "cloud_init" {
|
||||
template = "${file("./tf-templates/cloud-init.yaml.tpl")}"
|
||||
|
||||
vars = {
|
||||
tailscale_authkey = "${tailscale_tailnet_key.cloud_init_key.key}"
|
||||
default_ssh_key = var.default_ssh_key.public_key
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
hetzner_hosts = {for key, val in var.hosts : key => val if val.provider == "hetzner"}
|
||||
}
|
||||
|
||||
resource "hcloud_primary_ip" "node_ipv4_addresses" {
|
||||
for_each = local.hetzner_hosts
|
||||
name = "primary_ipv4_${each.value.hostname}"
|
||||
datacenter = each.value.datacenter
|
||||
type = "ipv4"
|
||||
assignee_type = "server"
|
||||
auto_delete = false
|
||||
}
|
||||
|
||||
resource "hcloud_primary_ip" "node_ipv6_addresses" {
|
||||
for_each = local.hetzner_hosts
|
||||
name = "primary_ipv6_${each.value.hostname}"
|
||||
datacenter = each.value.datacenter
|
||||
type = "ipv6"
|
||||
assignee_type = "server"
|
||||
auto_delete = false
|
||||
}
|
||||
|
||||
# Create a server
|
||||
resource "hcloud_server" "nodes" {
|
||||
for_each = local.hetzner_hosts
|
||||
name = each.value.hostname
|
||||
datacenter = each.value.datacenter
|
||||
image = each.value.image
|
||||
server_type = each.value.server_type
|
||||
ssh_keys = [hcloud_ssh_key.default.id]
|
||||
user_data = "${data.template_file.cloud_init.rendered}"
|
||||
public_net {
|
||||
ipv4 = hcloud_primary_ip.node_ipv4_addresses[each.key].id
|
||||
ipv6 = hcloud_primary_ip.node_ipv6_addresses[each.key].id
|
||||
}
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
ssh_keys,
|
||||
user_data
|
||||
]
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "hcloud_rdns" "nodes_rdns" {
|
||||
for_each = local.hetzner_hosts
|
||||
server_id = hcloud_server.nodes[each.key].id
|
||||
ip_address = hcloud_server.nodes[each.key].ipv4_address
|
||||
dns_ptr = each.value.rdns
|
||||
}
|
53
modules/infrastructure/main.tf
Normal file
53
modules/infrastructure/main.tf
Normal file
|
@ -0,0 +1,53 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
hcloud = {
|
||||
source = "hetznercloud/hcloud"
|
||||
version = "~> 1.45.0"
|
||||
}
|
||||
ovh = {
|
||||
source = "ovh/ovh"
|
||||
version = "~> 0.48.0"
|
||||
}
|
||||
scaleway = {
|
||||
source = "scaleway/scaleway"
|
||||
version = "~> 2.43.0"
|
||||
}
|
||||
tailscale = {
|
||||
source = "tailscale/tailscale"
|
||||
version = "~> 0.16.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
provider "hcloud" {
|
||||
token = "${var.hcloud_token}"
|
||||
}
|
||||
|
||||
provider "ovh" {
|
||||
endpoint = "ovh-eu"
|
||||
application_key = "${var.ovh_application_key}"
|
||||
application_secret = "${var.ovh_application_secret}"
|
||||
consumer_key = "${var.ovh_consumer_key}"
|
||||
}
|
||||
|
||||
provider "scaleway" {
|
||||
organization_id = "${var.scaleway_organization_id}"
|
||||
project_id = "${var.scaleway_project_id}"
|
||||
access_key = "${var.scaleway_access_key}"
|
||||
secret_key = "${var.scaleway_secret_key}"
|
||||
region = "nl-ams"
|
||||
zone = "nl-ams-1"
|
||||
}
|
||||
|
||||
provider "tailscale" {
|
||||
api_key = "${var.tailscale_api_key}"
|
||||
tailnet = "${var.tailscale_tailnet}"
|
||||
}
|
||||
|
||||
|
||||
locals {
|
||||
services_auth = {for key, val in var.services : key => val if val.auth}
|
||||
services_database = {for key, val in var.services : key => val if val.database}
|
||||
services_s3 = {for key, val in var.services : key => val if val.s3}
|
||||
}
|
37
modules/infrastructure/output.tf
Normal file
37
modules/infrastructure/output.tf
Normal file
|
@ -0,0 +1,37 @@
|
|||
output "hosts" {
|
||||
value = {
|
||||
for subdomain in distinct([for record in ovh_domain_zone_record.server_records : record.subdomain]) :
|
||||
subdomain => {
|
||||
"ipv4_address" = try(
|
||||
ovh_domain_zone_record.server_records["${subdomain}:ipv4"].target,
|
||||
null
|
||||
)
|
||||
"ipv6_address" = try(
|
||||
ovh_domain_zone_record.server_records["${subdomain}:ipv6"].target,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output "scaleway_data" {
|
||||
value = {
|
||||
for key in keys(scaleway_iam_application.service_applications) : key => {
|
||||
"access_key" = scaleway_iam_api_key.service_keys[key].access_key
|
||||
"secret_key" = scaleway_iam_api_key.service_keys[key].secret_key
|
||||
"name" = scaleway_object_bucket.service_buckets[key].name
|
||||
"region" = scaleway_object_bucket.service_buckets[key].region
|
||||
"endpoint" = scaleway_object_bucket.service_buckets[key].endpoint
|
||||
"api_endpoint" = scaleway_object_bucket.service_buckets[key].api_endpoint
|
||||
}
|
||||
}
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "scaleway_registry_endpoint_public" {
|
||||
value = scaleway_registry_namespace.public.endpoint
|
||||
}
|
||||
|
||||
output "scaleway_registry_endpoint_private" {
|
||||
value = scaleway_registry_namespace.private.endpoint
|
||||
}
|
74
modules/infrastructure/ovh.tf
Normal file
74
modules/infrastructure/ovh.tf
Normal file
|
@ -0,0 +1,74 @@
|
|||
locals {
|
||||
contabo_hosts = {for key, val in var.hosts : key => val if val.provider == "contabo"}
|
||||
server_addresses = flatten([
|
||||
[
|
||||
for host in local.contabo_hosts : [
|
||||
{
|
||||
hostname = host.hostname
|
||||
ipv4_address = host.ipv4_address
|
||||
ipv6_address = host.ipv6_address
|
||||
},
|
||||
]
|
||||
],
|
||||
[
|
||||
for host in hcloud_server.nodes : [
|
||||
{
|
||||
hostname = host.name
|
||||
ipv4_address = host.ipv4_address
|
||||
ipv6_address = host.ipv6_address
|
||||
},
|
||||
]
|
||||
]
|
||||
])
|
||||
|
||||
server_addresses_separated = flatten([
|
||||
for host in local.server_addresses : [
|
||||
{
|
||||
hostname = host.hostname
|
||||
key = "${host.hostname}:ipv4"
|
||||
address = host.ipv4_address
|
||||
},
|
||||
{
|
||||
hostname = host.hostname
|
||||
key = "${host.hostname}:ipv6"
|
||||
address = host.ipv6_address
|
||||
},
|
||||
]
|
||||
])
|
||||
|
||||
tailscale_host_addresses = flatten([
|
||||
for host in data.tailscale_devices.nodes.devices : [
|
||||
for index, address in host.addresses : {
|
||||
hostname = host.hostname
|
||||
key = "${host.hostname}:${index}"
|
||||
address = address
|
||||
}
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
resource "ovh_domain_zone_record" "server_records" {
|
||||
for_each = { for entry in local.server_addresses_separated: entry.key => entry }
|
||||
zone = "serguzim.net"
|
||||
subdomain = each.value.hostname
|
||||
fieldtype = strcontains(each.value.address, ":") ? "AAAA" : "A"
|
||||
ttl = 3600
|
||||
target = each.value.address
|
||||
}
|
||||
|
||||
resource "ovh_domain_zone_record" "tailscale_vpn" {
|
||||
for_each = { for entry in local.tailscale_host_addresses: entry.key => entry }
|
||||
zone = "serguzim.net"
|
||||
subdomain = "${each.value.hostname}.vpn"
|
||||
fieldtype = strcontains(each.value.address, ":") ? "AAAA" : "A"
|
||||
ttl = 600
|
||||
target = each.value.address
|
||||
}
|
||||
|
||||
resource "ovh_domain_zone_record" "status_page_cname" {
|
||||
zone = "serguzim.net"
|
||||
subdomain = "status"
|
||||
fieldtype = "CNAME"
|
||||
ttl = 3600
|
||||
target = "status.serguzim.me."
|
||||
}
|
94
modules/infrastructure/scaleway.tf
Normal file
94
modules/infrastructure/scaleway.tf
Normal file
|
@ -0,0 +1,94 @@
|
|||
data "scaleway_account_project" "project" {
|
||||
project_id = "${var.scaleway_project_id}"
|
||||
}
|
||||
|
||||
resource "scaleway_account_ssh_key" "default" {
|
||||
name = var.default_ssh_key.name
|
||||
public_key = var.default_ssh_key.public_key
|
||||
}
|
||||
|
||||
data "scaleway_iam_user" "serguzim" {
|
||||
email = "tobias@msrg.cc"
|
||||
}
|
||||
|
||||
resource "scaleway_iam_application" "service_applications" {
|
||||
for_each = local.services_s3
|
||||
name = each.value.name
|
||||
}
|
||||
|
||||
resource "scaleway_iam_policy" "service_storage_policies" {
|
||||
for_each = local.services_s3
|
||||
name = "${each.key}_storage_policy"
|
||||
application_id = scaleway_iam_application.service_applications[each.key].id
|
||||
rule {
|
||||
project_ids = [data.scaleway_account_project.project.id]
|
||||
permission_set_names = ["ObjectStorageFullAccess"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "scaleway_object_bucket" "service_buckets" {
|
||||
for_each = local.services_s3
|
||||
name = "${each.value.name}.serguzim.me"
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "scaleway_object_bucket_policy" "service_bucket_policies" {
|
||||
for_each = local.services_s3
|
||||
bucket = scaleway_object_bucket.service_buckets[each.key].id
|
||||
policy = jsonencode({
|
||||
Version = "2023-04-17",
|
||||
Id = "${each.key}_bucket_policy",
|
||||
Statement = [
|
||||
{
|
||||
Sid = "Scaleway secure statement"
|
||||
Effect = "Allow"
|
||||
Action = "*"
|
||||
Principal = {
|
||||
SCW = "user_id:${data.scaleway_iam_user.serguzim.id}"
|
||||
}
|
||||
Resource = [
|
||||
"${scaleway_object_bucket.service_buckets[each.key].name}",
|
||||
"${scaleway_object_bucket.service_buckets[each.key].name}/*",
|
||||
]
|
||||
},
|
||||
{
|
||||
Sid = "${each.key} statement"
|
||||
Effect = "Allow"
|
||||
Action = "*"
|
||||
Principal = {
|
||||
SCW = "application_id:${scaleway_iam_application.service_applications[each.key].id}"
|
||||
}
|
||||
Resource = [
|
||||
"${scaleway_object_bucket.service_buckets[each.key].name}",
|
||||
"${scaleway_object_bucket.service_buckets[each.key].name}/*",
|
||||
]
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "time_rotating" "rotate_after_a_year" {
|
||||
rotation_years = 1
|
||||
}
|
||||
|
||||
resource "scaleway_iam_api_key" "service_keys" {
|
||||
for_each = local.services_s3
|
||||
description = "Service key for ${each.key}"
|
||||
application_id = scaleway_iam_application.service_applications[each.key].id
|
||||
expires_at = time_rotating.rotate_after_a_year.rotation_rfc3339
|
||||
}
|
||||
|
||||
|
||||
resource "scaleway_registry_namespace" "public" {
|
||||
name = "public.serguzim.net"
|
||||
description = "Public container registry for serguzim.net"
|
||||
is_public = true
|
||||
}
|
||||
|
||||
resource "scaleway_registry_namespace" "private" {
|
||||
name = "private.serguzim.net"
|
||||
description = "Private container registry for serguzim.net"
|
||||
is_public = false
|
||||
}
|
11
modules/infrastructure/tailscale.tf
Normal file
11
modules/infrastructure/tailscale.tf
Normal file
|
@ -0,0 +1,11 @@
|
|||
resource "tailscale_tailnet_key" "cloud_init_key" {
|
||||
reusable = true
|
||||
ephemeral = false
|
||||
preauthorized = true
|
||||
expiry = 21600 # 6 hours
|
||||
description = "Cloud-init key used by opentofu"
|
||||
}
|
||||
|
||||
data "tailscale_devices" "nodes" {
|
||||
name_prefix = "node"
|
||||
}
|
75
modules/infrastructure/variables.tf
Normal file
75
modules/infrastructure/variables.tf
Normal file
|
@ -0,0 +1,75 @@
|
|||
variable "hcloud_token" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
|
||||
variable "ovh_application_key" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ovh_application_secret" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ovh_consumer_key" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
|
||||
variable "scaleway_organization_id" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "scaleway_project_id" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "scaleway_access_key" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "scaleway_secret_key" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
|
||||
variable "tailscale_api_key" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "tailscale_tailnet" {
|
||||
sensitive = false
|
||||
}
|
||||
|
||||
|
||||
variable "default_ssh_key" {
|
||||
type = object({
|
||||
name = string
|
||||
public_key = string
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
variable "services" {
|
||||
type = map(object({
|
||||
name = string
|
||||
subdomain = string
|
||||
auth = bool
|
||||
auth_redirects = optional(list(string))
|
||||
s3 = bool
|
||||
database = bool
|
||||
}))
|
||||
}
|
||||
|
||||
variable "hosts" {
|
||||
type = map(object({
|
||||
hostname = string
|
||||
rdns = string
|
||||
provider = string
|
||||
ipv4_address = optional(string)
|
||||
ipv6_address = optional(string)
|
||||
image = optional(string)
|
||||
server_type = optional(string)
|
||||
datacenter = optional(string)
|
||||
}))
|
||||
}
|
33
modules/services/authentik.tf
Normal file
33
modules/services/authentik.tf
Normal file
|
@ -0,0 +1,33 @@
|
|||
data "authentik_flow" "default_authorization_flow" {
|
||||
slug = "default-provider-authorization-implicit-consent"
|
||||
}
|
||||
|
||||
data "authentik_certificate_key_pair" "default" {
|
||||
name = "auth.serguzim.me"
|
||||
}
|
||||
|
||||
data "authentik_property_mapping_provider_scope" "default_scopes" {
|
||||
managed_list = [
|
||||
"goauthentik.io/providers/oauth2/scope-email",
|
||||
"goauthentik.io/providers/oauth2/scope-openid",
|
||||
"goauthentik.io/providers/oauth2/scope-profile"
|
||||
]
|
||||
}
|
||||
|
||||
resource "authentik_provider_oauth2" "service_providers" {
|
||||
for_each = local.services_auth
|
||||
name = each.value.name
|
||||
client_type = "confidential"
|
||||
client_id = each.value.name
|
||||
authorization_flow = data.authentik_flow.default_authorization_flow.id
|
||||
redirect_uris = each.value.auth_redirects
|
||||
property_mappings = data.authentik_property_mapping_provider_scope.default_scopes.ids
|
||||
signing_key = data.authentik_certificate_key_pair.default.id
|
||||
}
|
||||
|
||||
resource "authentik_application" "service_applications" {
|
||||
for_each = local.services_auth
|
||||
name = each.value.name
|
||||
slug = "${each.value.subdomain}-serguzim-me"
|
||||
protocol_provider = authentik_provider_oauth2.service_providers[each.key].id
|
||||
}
|
33
modules/services/main.tf
Normal file
33
modules/services/main.tf
Normal file
|
@ -0,0 +1,33 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
authentik = {
|
||||
source = "goauthentik/authentik"
|
||||
version = "~> 2024.8.0"
|
||||
}
|
||||
postgresql = {
|
||||
source = "cyrilgdn/postgresql"
|
||||
version = "~> 1.23.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "authentik" {
|
||||
url = "${var.authentik_url}"
|
||||
token = "${var.authentik_token}"
|
||||
}
|
||||
|
||||
provider "postgresql" {
|
||||
host = "${var.postgresql_host}"
|
||||
port = "${var.postgresql_port}"
|
||||
database = "postgres"
|
||||
username = "${var.postgresql_username}"
|
||||
password = "${var.postgresql_password}"
|
||||
sslmode = "verify-full"
|
||||
connect_timeout = 15
|
||||
}
|
||||
|
||||
locals {
|
||||
services_auth = {for key, val in var.services : key => val if val.auth}
|
||||
services_database = {for key, val in var.services : key => val if val.database}
|
||||
services_s3 = {for key, val in var.services : key => val if val.s3}
|
||||
}
|
21
modules/services/output.tf
Normal file
21
modules/services/output.tf
Normal file
|
@ -0,0 +1,21 @@
|
|||
output "authentik_data" {
|
||||
value = {
|
||||
for key in keys(authentik_application.service_applications) : key => {
|
||||
"base_url" = "${var.authentik_url}/application/o/${authentik_application.service_applications[key].slug}/"
|
||||
"client_id" = authentik_provider_oauth2.service_providers[key].client_id
|
||||
"client_secret" = authentik_provider_oauth2.service_providers[key].client_secret
|
||||
}
|
||||
}
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "postgresql_data" {
|
||||
value = {
|
||||
for key in keys(postgresql_database.service_databases) : key => {
|
||||
"user" = postgresql_role.service_roles[key].name
|
||||
"pass" = postgresql_role.service_roles[key].password
|
||||
"database" = postgresql_database.service_databases[key].name
|
||||
}
|
||||
}
|
||||
sensitive = true
|
||||
}
|
18
modules/services/postgresql.tf
Normal file
18
modules/services/postgresql.tf
Normal file
|
@ -0,0 +1,18 @@
|
|||
resource "random_password" "postgresql_service_passwords" {
|
||||
for_each = local.services_database
|
||||
length = 32
|
||||
special = false
|
||||
}
|
||||
|
||||
resource "postgresql_role" "service_roles" {
|
||||
for_each = local.services_database
|
||||
name = each.value.name
|
||||
login = true
|
||||
password = random_password.postgresql_service_passwords[each.key].result
|
||||
}
|
||||
|
||||
resource "postgresql_database" "service_databases" {
|
||||
for_each = local.services_database
|
||||
name = each.value.name
|
||||
owner = postgresql_role.service_roles[each.key].name
|
||||
}
|
33
modules/services/variables.tf
Normal file
33
modules/services/variables.tf
Normal file
|
@ -0,0 +1,33 @@
|
|||
variable "authentik_url" {
|
||||
}
|
||||
|
||||
variable "authentik_token" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
|
||||
variable "postgresql_host" {
|
||||
}
|
||||
|
||||
variable "postgresql_port" {
|
||||
}
|
||||
|
||||
variable "postgresql_username" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "postgresql_password" {
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
|
||||
variable "services" {
|
||||
type = map(object({
|
||||
name = string
|
||||
subdomain = string
|
||||
auth = bool
|
||||
auth_redirects = optional(list(string))
|
||||
s3 = bool
|
||||
database = bool
|
||||
}))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue