diff --git a/.drone.yml b/.drone.yml index 5520b23..5a7be91 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,6 +15,9 @@ environment: ANSIBLE_FORCE_COLOR: true ANSIBLE_HOME: /drone/src/.ansible ANSIBLE_VAULT_PASSWORD_FILE: "/drone/src/vault_pass" + SUMMON_PROVIDER: /drone/src/summon-wrapper + PASSAGE_DIR: /drone/src/.passage/store + PASSAGE_IDENTITIES_FILE: /drone/src/ssh_key node: ansible: "true" @@ -28,11 +31,13 @@ steps: from_secret: vault_pass SSH_KEY: from_secret: ssh_key + GIT_SSH_COMMAND: ssh -i /drone/src/ssh_key -o StrictHostKeyChecking=no commands: - echo $${VAULT_PASS} > /drone/src/vault_pass - echo $${SSH_KEY} | base64 -d > /drone/src/ssh_key - chmod 600 /drone/src/ssh_key - chmod 600 /drone/src/vault_pass + - git clone ssh://git@git.tobiasmanske.de:7779/tobias/infrastructure-vault.git $${PASSAGE_DIR} - name: Prepare Runner image: registry.tobiasmanske.de/ansible-runner:latest pull: always @@ -41,6 +46,13 @@ steps: - mkdir $ANSIBLE_HOME - ansible-galaxy install -r requirements.yaml - ansible-playbook --private-key ../ssh_key --inventory=inventory.yaml runner-pre.yaml + - name: Run Terraform + image: registry.tobiasmanske.de/terraform-runner:latest + pull: always + commands: + - cd tf-stage-1 + - summon terraform init + - summon terraform plan # do not run right now. this is still in debug :christ: - name: Run Ansible image: registry.tobiasmanske.de/ansible-runner:latest pull: always diff --git a/summon-wrapper b/summon-wrapper new file mode 100755 index 0000000..21404b4 --- /dev/null +++ b/summon-wrapper @@ -0,0 +1,3 @@ +#!/bin/sh +passage show $(echo "${@}"|tr : \ ) + diff --git a/tf-stage-1/dns-tobiasmanske-de.tf b/tf-stage-1/dns-tobiasmanske-de.tf new file mode 100644 index 0000000..92f258b --- /dev/null +++ b/tf-stage-1/dns-tobiasmanske-de.tf @@ -0,0 +1,44 @@ +module "dns-tobiasmanske-de" { + source = "./modules/dns" + + account_id = var.cloudflare_account_id + zone = "tobiasmanske.de" + + records = [ + { type = "A", name = "web", value = "185.216.177.198" }, + { type = "AAAA", name = "web", value = "2a03:4000:4f:9f2::1" }, + { type = "CNAME", name = "@", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "s3", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "minio", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "doc", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "drone", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "git", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "meet", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "oauth", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "registry", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "registry-auth", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "traefik-fa", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "repo", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "rss", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "search", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "test", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "calendar", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "www", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "grafana", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "prometheus", value = "web.tobiasmanske.de" }, + { type = "CNAME", name = "status", value = "mon1.hel1.chaoswg.org" }, + { type = "CNAME", name = "auth", value = "infra.unruhig.eu" }, + { type = "TXT", name = "@", value = "google-site-verification=I7WrzPjqHIL6EATWd8UWfvx6ScDzqjA3DGZi-J-F1e0" }, + + # Mail settings + { type = "A", name = "mail", value = "202.61.232.207" }, + { type = "MX", name = "@", value = "mxe8cf.netcup.net", priority = 50 }, + { type = "MX", name = "@", value = "mail.tobiasmanske.de", priority = 10 }, + { type = "CNAME", name = "autoconfig", value = "autoconfig.netcup.net" }, + { type = "CNAME", name = "key1._domainkey", value = "key1._domainkey.webhosting.systems" }, + { type = "CNAME", name = "key2._domainkey", value = "key2._domainkey.webhosting.systems" }, + { type = "TXT", name = "@", value = "v=spf1 mx include:_spf.webhosting.systems -all" }, + { type = "TXT", name = "_dmarc", value = "v=DMARC1; p=quarantine;pct=100;rua=mailto:postmaster+dmarc@tobiasmanske.de;" }, + + ] +} diff --git a/tf-stage-1/dns-unruhig-eu.tf b/tf-stage-1/dns-unruhig-eu.tf new file mode 100644 index 0000000..4b0f805 --- /dev/null +++ b/tf-stage-1/dns-unruhig-eu.tf @@ -0,0 +1,12 @@ +module "dns-unruhig-eu" { + source = "./modules/dns" + + account_id = var.cloudflare_account_id + zone = "unruhig.eu" + + records = [ + { type = "A", name = "infra", value = "37.221.198.143" }, + { type = "AAAA", name = "infra", value = "2a03:4000:9:176::1" }, + ] + +} diff --git a/tf-stage-1/main.tf b/tf-stage-1/main.tf new file mode 100644 index 0000000..bd2f364 --- /dev/null +++ b/tf-stage-1/main.tf @@ -0,0 +1,26 @@ +terraform { + backend "s3" { + bucket = "infra" + key = "terraform/keycloak.tfstate" + endpoint = "https://s3.tobiasmanske.de" + force_path_style = true + region = "us-east-1" + skip_credentials_validation = true + skip_region_validation = true + skip_metadata_api_check = true + } + required_providers { + keycloak = { + source = "mrparkers/keycloak" + version = "~> 4.3.0" + } + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 4.0" + } + } +} + +data "keycloak_realm" "realm" { + realm = var.realm +} diff --git a/tf-stage-1/modules/dns/dns.tf b/tf-stage-1/modules/dns/dns.tf new file mode 100644 index 0000000..149d656 --- /dev/null +++ b/tf-stage-1/modules/dns/dns.tf @@ -0,0 +1,14 @@ +resource "cloudflare_zone" "zone" { + account_id = var.account_id + zone = var.zone +} + +resource "cloudflare_record" "records" { + zone_id = cloudflare_zone.zone.id + for_each = { for record in var.records : uuidv5("dns", "${record.type}/${record.name}/${record.value}") => record } # Hackery. + name = each.value.name + value = each.value.value + type = each.value.type + ttl = 1 + priority = each.value.type == "MX" ? each.value.priority : null +} diff --git a/tf-stage-1/modules/dns/main.tf b/tf-stage-1/modules/dns/main.tf new file mode 100644 index 0000000..b2ab089 --- /dev/null +++ b/tf-stage-1/modules/dns/main.tf @@ -0,0 +1,9 @@ +terraform { + required_providers { + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 4.0" + } + } +} + diff --git a/tf-stage-1/modules/dns/variables.tf b/tf-stage-1/modules/dns/variables.tf new file mode 100644 index 0000000..cf2f66f --- /dev/null +++ b/tf-stage-1/modules/dns/variables.tf @@ -0,0 +1,10 @@ +variable "account_id" { + type = string + sensitive = true +} +variable "zone" { + type = string +} +variable "records" { + type = set(object({ type = string, name = string, value = string, priority = optional(number) })) +} diff --git a/tf-stage-1/modules/kc-client/client.tf b/tf-stage-1/modules/kc-client/client.tf new file mode 100644 index 0000000..877f63e --- /dev/null +++ b/tf-stage-1/modules/kc-client/client.tf @@ -0,0 +1,74 @@ +data "keycloak_realm" "realm" { + realm = var.realm +} + +resource "keycloak_openid_client" "client" { + realm_id = data.keycloak_realm.realm.id + client_id = var.client_id + client_secret = var.client_secret + + name = var.client_name + description = var.description + + enabled = var.enabled + access_type = var.access_type + client_authenticator_type = var.client_authenticator_type + + root_url = var.root_url + base_url = var.base_url + admin_url = var.admin_url + backchannel_logout_url = var.backchannel_logout_url + + valid_redirect_uris = var.valid_redirect_uris + web_origins = var.web_origins + + login_theme = var.login_theme + + standard_flow_enabled = true + implicit_flow_enabled = false + direct_access_grants_enabled = true + service_accounts_enabled = false + frontchannel_logout_enabled = false + +} + +resource "keycloak_role" "restricted-access" { + realm_id = data.keycloak_realm.realm.id + client_id = keycloak_openid_client.client.id + name = "restricted-access" + description = "Restricts access to the client" +} + +resource "keycloak_role" "admin-role" { + realm_id = data.keycloak_realm.realm.id + client_id = keycloak_openid_client.client.id + name = "${var.admin_role_name != null ? "${var.admin_role_name}" : "${var.client_name}-admin"}" + description = "Client Admin permissions" +} + +resource "keycloak_group" "access_group" { + realm_id = data.keycloak_realm.realm.id + name = var.client_name +} + +resource "keycloak_group" "admin_group" { + realm_id = data.keycloak_realm.realm.id + parent_id = keycloak_group.access_group.id + name = "${var.client_name}-admin" +} + +resource "keycloak_group_roles" "access_group_roles" { + realm_id = data.keycloak_realm.realm.id + group_id = keycloak_group.access_group.id + role_ids = [ + keycloak_role.restricted-access.id + ] +} + +resource "keycloak_group_roles" "admin_group_roles" { + realm_id = data.keycloak_realm.realm.id + group_id = keycloak_group.admin_group.id + role_ids = [ + keycloak_role.admin-role.id + ] +} diff --git a/tf-stage-1/modules/kc-client/main.tf b/tf-stage-1/modules/kc-client/main.tf new file mode 100644 index 0000000..e2885aa --- /dev/null +++ b/tf-stage-1/modules/kc-client/main.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + keycloak = { + source = "mrparkers/keycloak" + version = "~> 4.3.0" + } + } +} diff --git a/tf-stage-1/modules/kc-client/outputs.tf b/tf-stage-1/modules/kc-client/outputs.tf new file mode 100644 index 0000000..cc52cde --- /dev/null +++ b/tf-stage-1/modules/kc-client/outputs.tf @@ -0,0 +1,12 @@ +output "client" { + value = keycloak_openid_client.client +} +output "admin_group" { + value = keycloak_group.admin_group +} +output "access_group" { + value = keycloak_group.access_group +} +output "realm" { + value = data.keycloak_realm.realm +} diff --git a/tf-stage-1/modules/kc-client/variables.tf b/tf-stage-1/modules/kc-client/variables.tf new file mode 100644 index 0000000..5a8fe81 --- /dev/null +++ b/tf-stage-1/modules/kc-client/variables.tf @@ -0,0 +1,75 @@ +variable "client_id" { + type = string +} +variable "client_name" { + type = string +} + +variable "admin_role_name" { + type = string + default = null +} + +variable "client_secret" { + type = string + default = null + sensitive = true +} + +variable "description" { + type = string +} +variable "access_type" { + type = string + default = "CONFIDENTIAL" +} + +variable "realm" { + type = string +} + +variable "client_authenticator_type" { + type = string + default = "client-secret" +} + +variable "root_url" { # requires web_origins, admin_url and valid_redirect_uris to be set... + type = string + default = null +} + +variable "admin_url" { + type = string + default = null +} + +variable "base_url" { + type = string + default = null +} + +variable "web_origins" { + type = list(string) + default = null +} + +variable "backchannel_logout_url" { + type = string + default = null + description = "The URL to which a backchannel logout request will be sent from the Keycloak server." +} +variable "valid_redirect_uris" { + type = list(string) +} +variable "enabled" { + type = bool + default = true +} + + +# Default settings for all clients: + +variable "login_theme" { + type = string + default = "keywind" +} diff --git a/tf-stage-1/providers.tf b/tf-stage-1/providers.tf new file mode 100644 index 0000000..17c3143 --- /dev/null +++ b/tf-stage-1/providers.tf @@ -0,0 +1,10 @@ +provider "keycloak" { + client_id = "terraform" + client_secret = var.keycloak_client_secret + url = "https://auth.tobiasmanske.de" + realm = "tobiasmanske.de" +} + +provider "cloudflare" { + api_token = var.cloudflare_api_token +} diff --git a/tf-stage-1/secrets.yml b/tf-stage-1/secrets.yml new file mode 100644 index 0000000..c2498af --- /dev/null +++ b/tf-stage-1/secrets.yml @@ -0,0 +1,9 @@ +--- +TF_VAR_grafana_secret: !var keycloak/grafana/secret +TF_VAR_hedgedoc_secret: !var keycloak/hedgedoc/secret +TF_VAR_miniflux_secret: !var keycloak/miniflux/secret +TF_VAR_keycloak_client_secret: !var keycloak/terraform/secret +TF_VAR_cloudflare_api_token: !var extern/cloudflare/api_token +TF_VAR_cloudflare_account_id: !var extern/cloudflare/account_id +AWS_ACCESS_KEY_ID: !var terraform/state/s3_access_key +AWS_SECRET_ACCESS_KEY: !var terraform/state/s3_secret_key diff --git a/tf-stage-1/service-gitea.tf b/tf-stage-1/service-gitea.tf new file mode 100644 index 0000000..ea69fa6 --- /dev/null +++ b/tf-stage-1/service-gitea.tf @@ -0,0 +1,37 @@ +module "giteaclient" { + source = "./modules/kc-client" + + realm = var.realm + client_id = "gitea" + client_name = "Gitea" + description = "git.tobiasmanske.de" + root_url = "https://git.tobiasmanske.de" + admin_url = "https://git.tobiasmanske.de" + base_url = "" + valid_redirect_uris = ["https://git.tobiasmanske.de/user/oauth2/Keycloak/callback"] + web_origins = ["https://git.tobiasmanske.de"] +} + +resource "keycloak_openid_user_property_protocol_mapper" "gitea-username-mapper" { + realm_id = module.giteaclient.realm.id + client_id = module.giteaclient.client.id + + name = "username" + user_property = "username" + claim_name = "preferred_username" + add_to_userinfo = true + add_to_access_token = true + add_to_id_token = false +} + +resource "keycloak_openid_user_client_role_protocol_mapper" "gitea-role-mapper" { + realm_id = module.giteaclient.realm.id + client_id = module.giteaclient.client.id + # client_id_for_role_mappings = module.giteaclient.client.id + multivalued = true + name = "user-client-role-mapper" + claim_name = "roles" + add_to_userinfo = true + add_to_access_token = true + add_to_id_token = false +} diff --git a/tf-stage-1/service_debug.tf b/tf-stage-1/service_debug.tf new file mode 100644 index 0000000..206b7c9 --- /dev/null +++ b/tf-stage-1/service_debug.tf @@ -0,0 +1,8 @@ +module "kc-client" { + source = "./modules/kc-client" + client_id = "debug" + client_name = "Debug client" + description = "A client for debugging purposes" + realm = var.realm + valid_redirect_uris = ["http://localhost:8080/*"] +} diff --git a/tf-stage-1/service_grafana.tf b/tf-stage-1/service_grafana.tf new file mode 100644 index 0000000..b5da716 --- /dev/null +++ b/tf-stage-1/service_grafana.tf @@ -0,0 +1,70 @@ +module "grafanaclient" { + source = "./modules/kc-client" + + realm = var.realm + client_id = "grafana" + client_name = "Grafana" + client_secret = var.grafana_secret + description = "https://grafana.tobiasmanske.de" + admin_role_name = "serveradmin" + + root_url = "https://grafana.tobiasmanske.de" + admin_url = "https://grafana.tobiasmanske.de" + base_url = "https://grafana.tobiasmanske.de" + valid_redirect_uris = ["https://grafana.tobiasmanske.de/*"] + web_origins = ["https://grafana.tobiasmanske.de"] +} + +resource "keycloak_openid_group_membership_protocol_mapper" "grafana-membership-mapper" { + realm_id = module.grafanaclient.realm.id + client_id = module.grafanaclient.client.id + + name = "Group Mapper" + claim_name = "groups" + full_path = false + add_to_userinfo = true + add_to_access_token = false + add_to_id_token = true +} + +resource "keycloak_openid_user_property_protocol_mapper" "grafana-username-mapper" { + realm_id = module.grafanaclient.realm.id + client_id = module.grafanaclient.client.id + + name = "username" + user_property = "username" + claim_name = "preferred_username" + add_to_userinfo = true + add_to_access_token = true + add_to_id_token = false +} + +resource "keycloak_openid_user_client_role_protocol_mapper" "grafana-role-mapper" { + realm_id = module.grafanaclient.realm.id + client_id = module.grafanaclient.client.id + multivalued = true + name = "user-client-role-mapper" + claim_name = "resource_access.$${client_id}.roles" + add_to_userinfo = true + add_to_access_token = true + add_to_id_token = false +} + +resource "keycloak_role" "grafana-admin" { + realm_id = module.grafanaclient.realm.id + client_id = module.grafanaclient.client.id + name = "admin" + description = "Admin" +} +resource "keycloak_role" "grafana-editor" { + realm_id = module.grafanaclient.realm.id + client_id = module.grafanaclient.client.id + name = "editor" + description = "Editor" +} +resource "keycloak_role" "grafana-viewer" { + realm_id = module.grafanaclient.realm.id + client_id = module.grafanaclient.client.id + name = "viewer" + description = "Viewer" +} diff --git a/tf-stage-1/service_hedgedoc.tf b/tf-stage-1/service_hedgedoc.tf new file mode 100644 index 0000000..0c6061e --- /dev/null +++ b/tf-stage-1/service_hedgedoc.tf @@ -0,0 +1,51 @@ +module "hedgedocclient" { + source = "./modules/kc-client" + + realm = var.realm + client_id = "hedgedoc" + client_name = "hedgedoc" + client_secret = var.hedgedoc_secret + description = "doc.tobiasmanske.de" + root_url = "https://doc.tobiasmanske.de" + admin_url = "" + base_url = "" + valid_redirect_uris = ["https://doc.tobiasmanske.de/*"] + web_origins = ["https://doc.tobiasmanske.de"] +} + + +resource "keycloak_openid_user_session_note_protocol_mapper" "hedgedoc-id-mapper" { + realm_id = module.hedgedocclient.realm.id + client_id = module.hedgedocclient.client.id + name = "id" + + claim_name = "clientId" + claim_value_type = "String" + session_note = "clientId" + add_to_access_token = true + add_to_id_token = true +} + +resource "keycloak_openid_user_session_note_protocol_mapper" "hedgedoc-host-mapper" { + realm_id = module.hedgedocclient.realm.id + client_id = module.hedgedocclient.client.id + name = "host" + + claim_name = "clientHost" + claim_value_type = "String" + session_note = "clientHost" + add_to_access_token = true + add_to_id_token = true +} + +resource "keycloak_openid_user_session_note_protocol_mapper" "hedgedoc-ip-mapper" { + realm_id = module.hedgedocclient.realm.id + client_id = module.hedgedocclient.client.id + name = "ip" + + claim_name = "clientAddress" + claim_value_type = "String" + session_note = "clientAddress" + add_to_access_token = true + add_to_id_token = true +} diff --git a/tf-stage-1/service_miniflux.tf b/tf-stage-1/service_miniflux.tf new file mode 100644 index 0000000..61d6578 --- /dev/null +++ b/tf-stage-1/service_miniflux.tf @@ -0,0 +1,51 @@ +module "minifluxclient" { + source = "./modules/kc-client" + + realm = var.realm + client_id = "miniflux" + client_name = "Miniflux" + client_secret = var.miniflux_secret + description = "rss.tobiasmanske.de" + root_url = "https://rss.tobiasmanske.de" + admin_url = "" + base_url = "" + valid_redirect_uris = ["/oauth2/oidc/callback"] + web_origins = [] +} + + +resource "keycloak_openid_user_session_note_protocol_mapper" "miniflux-id-mapper" { + realm_id = module.minifluxclient.realm.id + client_id = module.minifluxclient.client.id + name = "id" + + claim_name = "clientId" + claim_value_type = "String" + session_note = "clientId" + add_to_access_token = true + add_to_id_token = true +} + +resource "keycloak_openid_user_session_note_protocol_mapper" "miniflux-host-mapper" { + realm_id = module.minifluxclient.realm.id + client_id = module.minifluxclient.client.id + name = "host" + + claim_name = "clientHost" + claim_value_type = "String" + session_note = "clientHost" + add_to_access_token = true + add_to_id_token = true +} + +resource "keycloak_openid_user_session_note_protocol_mapper" "miniflux-ip-mapper" { + realm_id = module.minifluxclient.realm.id + client_id = module.minifluxclient.client.id + name = "ip" + + claim_name = "clientAddress" + claim_value_type = "String" + session_note = "clientAddress" + add_to_access_token = true + add_to_id_token = true +} diff --git a/tf-stage-1/terraform.tfvars b/tf-stage-1/terraform.tfvars new file mode 100644 index 0000000..ef7e419 --- /dev/null +++ b/tf-stage-1/terraform.tfvars @@ -0,0 +1 @@ +realm = "tobiasmanske.de" diff --git a/tf-stage-1/user_ialistannen.tf b/tf-stage-1/user_ialistannen.tf new file mode 100644 index 0000000..c39a0ab --- /dev/null +++ b/tf-stage-1/user_ialistannen.tf @@ -0,0 +1,13 @@ +data "keycloak_user" "ialistannen" { + realm_id = data.keycloak_realm.realm.id + username = "ialistannen" +} + +resource "keycloak_user_groups" "ialistannen_groups" { + realm_id = data.keycloak_realm.realm.id + user_id = data.keycloak_user.ialistannen.id + exhaustive = false + group_ids = [ + module.hedgedocclient.access_group.id, + ] +} diff --git a/tf-stage-1/user_rad4day.tf b/tf-stage-1/user_rad4day.tf new file mode 100644 index 0000000..2269220 --- /dev/null +++ b/tf-stage-1/user_rad4day.tf @@ -0,0 +1,18 @@ +data "keycloak_user" "rad4day" { + realm_id = data.keycloak_realm.realm.id + username = "rad4day" +} + +resource "keycloak_user_groups" "rad4day_groups" { + realm_id = data.keycloak_realm.realm.id + user_id = data.keycloak_user.rad4day.id + exhaustive = false + group_ids = [ + module.giteaclient.access_group.id, + module.grafanaclient.access_group.id, + module.giteaclient.admin_group.id, + module.grafanaclient.admin_group.id, + module.hedgedocclient.access_group.id, + module.minifluxclient.access_group.id, + ] +} diff --git a/tf-stage-1/variables.tf b/tf-stage-1/variables.tf new file mode 100644 index 0000000..15bdb86 --- /dev/null +++ b/tf-stage-1/variables.tf @@ -0,0 +1,30 @@ +variable "realm" { + type = string +} + +variable "grafana_secret" { + type = string + sensitive = true +} + +variable "hedgedoc_secret" { + type = string + sensitive = true +} + +variable "miniflux_secret" { + type = string + sensitive = true +} +variable "cloudflare_api_token" { + type = string + sensitive = true +} +variable "cloudflare_account_id" { + type = string + sensitive = true +} +variable "keycloak_client_secret" { + type = string + sensitive = true +}