diff --git a/authentik.old/.terraform.lock.hcl b/authentik.old/.terraform.lock.hcl new file mode 100644 index 0000000..b72c3c5 --- /dev/null +++ b/authentik.old/.terraform.lock.hcl @@ -0,0 +1,77 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/goauthentik/authentik" { + version = "2024.10.2" + constraints = "2024.10.2" + hashes = [ + "h1:qjDOLb8+12kZHSM3VsItQCsZYJhDMD4bNKSZi15HQ28=", + "zh:06c6c9bb2716052fefc1013ed1a77a12159d5625fe43857700c282e80e2fbba1", + "zh:121e45b3d3675df24e2c1bb107e2ed15fc9f1ec8b602b9bdaebec71481addf0c", + "zh:2aec74c8df3e3eb56fb09edcb1c7f43c91f932b2ef2327aa855ba0819f11169e", + "zh:4f2bf009f43293a24cc8941d4bbab340a53f569a9331aa615a7934f500a64290", + "zh:64b150655b47c60e6ae72a2ee754f5019b2baabd4dc292a6b2b960b3a206e218", + "zh:78bf3fd7cbac489d23a620743e5af5b85b31fc548433cf86f0861878b68f2666", + "zh:7ce7a02671056d476d17652d780ee2bd309ce34eb77746719b7b277ca66b7c58", + "zh:84fdb911186918cbba86c1390ce18a4423f0d748216f2d9c8421801b34b41f16", + "zh:95db38fb110302707cd70471f5cb2bf361ed6d5987f7b6fe5f3c5855f9dc9b64", + "zh:9c24dbf6512637bb1d4201a901dddef0210b440ad8b02717ca1167b75afa6882", + "zh:a83bc8bfe87e44c788c3c974e764c7bfb1c5fb982f427a5b928c50e55b48dea6", + "zh:b5a4d5d1f2f0e8d65ad29a23bfd72d0d4e3e06e9bacea9463a10e67137833409", + "zh:d1e08a662ab7c80373bc13446c9b316a671fcddec6aeffef7ab3649d1bbfb76b", + "zh:e1c50a791f2d53f7b464ab122f92062547d5a4ad71297f5e7f0375453cd2034f", + ] +} + +provider "registry.opentofu.org/hashicorp/kubernetes" { + version = "2.31.0" + constraints = "2.31.0" + hashes = [ + "h1:MfkGdRph9sDol+ukIgIigdXuLLpC2JPUHH5oF2zEfTM=", + "h1:z2qlqn6WbrjbezwQo4vvlwAgVUGz59klzDU4rlYhYi8=", + "zh:0dd25babf78a88a61dd329b8c18538a295ea63630f1b69575e7898c89307da39", + "zh:3138753e4b2ce6e9ffa5d65d73e9236169ff077c10089c7dc71031a0a139ff6d", + "zh:644f94692dc33de0bb1183c307ae373efbf4ef4cb92654ccc646a5716edf9593", + "zh:6cc630e43193220b1599e3227286cc4e3ca195910e8c56b6bacb50c5b5176dbf", + "zh:764173875e77aa482da4dca9fec5f77c455d028848edfc394aa7dac5dfed6afd", + "zh:7b1d380362d50ffbb3697483036ae351b0571e93b33754255cde6968e62b839f", + "zh:a1d93ca3d8d1ecdd3b69242d16ff21c91b34e2e98f02a3b2d02c908aeb45189b", + "zh:b471d0ab56dbf19c95fba68d2ef127bdb353be96a2be4c4a3dcd4d0db4b4180a", + "zh:d610f725ded4acd3d31a240472bb283aa5e657ed020395bdefea18d094b8c2bf", + "zh:d7f3ddd636ad5af6049922f212feb24830b7158410819c32073bf81c359cd2fa", + ] +} + +provider "registry.opentofu.org/hashicorp/local" { + version = "2.5.2" + hashes = [ + "h1:6lS+5A/4WFAqY3/RHWFRBSiFVLPRjvLaUgxPQvjXLHU=", + "zh:25b95b76ceaa62b5c95f6de2fa6e6242edbf51e7fc6c057b7f7101aa4081f64f", + "zh:3c974fdf6b42ca6f93309cf50951f345bfc5726ec6013b8832bcd3be0eb3429e", + "zh:5de843bf6d903f5cca97ce1061e2e06b6441985c68d013eabd738a9e4b828278", + "zh:86beead37c7b4f149a54d2ae633c99ff92159c748acea93ff0f3603d6b4c9f4f", + "zh:8e52e81d3dc50c3f79305d257da7fde7af634fed65e6ab5b8e214166784a720e", + "zh:9882f444c087c69559873b2d72eec406a40ede21acb5ac334d6563bf3a2387df", + "zh:a4484193d110da4a06c7bffc44cc6b61d3b5e881cd51df2a83fdda1a36ea25d2", + "zh:a53342426d173e29d8ee3106cb68abecdf4be301a3f6589e4e8d42015befa7da", + "zh:d25ef2aef6a9004363fc6db80305d30673fc1f7dd0b980d41d863b12dacd382a", + "zh:fa2d522fb323e2121f65b79709fd596514b293d816a1d969af8f72d108888e4c", + ] +} + +provider "registry.opentofu.org/hashicorp/random" { + version = "3.6.3" + hashes = [ + "h1:Ry0Lr0zaoicslZlcUR4rAySPpl/a7QupfMfuAxhW3fw=", + "zh:1bfd2e54b4eee8c761a40b6d99d45880b3a71abc18a9a7a5319204da9c8363b2", + "zh:21a15ac74adb8ba499aab989a4248321b51946e5431219b56fc827e565776714", + "zh:221acfac3f7a5bcd6cb49f79a1fca99da7679bde01017334bad1f951a12d85ba", + "zh:3026fcdc0c1258e32ab519df878579160b1050b141d6f7883b39438244e08954", + "zh:50d07a7066ea46873b289548000229556908c3be746059969ab0d694e053ee4c", + "zh:54280cdac041f2c2986a585f62e102bc59ef412cad5f4ebf7387c2b3a357f6c0", + "zh:632adf40f1f63b0c5707182853c10ae23124c00869ffff05f310aef2ed26fcf3", + "zh:b8c2876cce9a38501d14880a47e59a5182ee98732ad7e576e9a9ce686a46d8f5", + "zh:f27e6995e1e9fe3914a2654791fc8d67cdce44f17bf06e614ead7dfd2b13d3ae", + "zh:f423f2b7e5c814799ad7580b5c8ae23359d8d342264902f821c357ff2b3c6d3d", + ] +} diff --git a/authentik.old/Makefile b/authentik.old/Makefile new file mode 100644 index 0000000..9d23b4e --- /dev/null +++ b/authentik.old/Makefile @@ -0,0 +1,8 @@ +init: + @tofu init + +plan: + @tofu plan -out tfplan + +apply:plan + @tofu apply tfplan diff --git a/authentik.old/books.tf b/authentik.old/books.tf new file mode 100644 index 0000000..0a768b7 --- /dev/null +++ b/authentik.old/books.tf @@ -0,0 +1,48 @@ +resource "random_id" "books_client_id" { + + byte_length = 16 +} + +resource "authentik_provider_oauth2" "books" { + name = "AudioBookShelf" + # Required. You can use the output of: + # $ openssl rand -hex 16 + client_id = random_id.books_client_id.id + authentication_flow = data.authentik_flow.default-authentication-flow.id + authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id + invalidation_flow = data.authentik_flow.default-invalidation-flow.id + + client_type = "public" + + allowed_redirect_uris = [ + { + url = "https://books.lab.cowley.tech/", + matched_mode = "strict" + }, + { + matched_mode = "regex", + url = ".*" + } + ] + + sub_mode = "user_email" + + property_mappings = [ + data.authentik_property_mapping_provider_scope.scope-email.id, + data.authentik_property_mapping_provider_scope.scope-profile.id, + data.authentik_property_mapping_provider_scope.scope-openid.id, + ] + lifecycle { + ignore_changes = [ + signing_key, + authentication_flow, + ] + } +} + +resource "authentik_application" "books" { + name = "AudioBookShelf" + slug = "audiobookshelf" + protocol_provider = authentik_provider_oauth2.books.id + open_in_new_tab = true +} diff --git a/authentik.old/chat.tf b/authentik.old/chat.tf new file mode 100644 index 0000000..087ee52 --- /dev/null +++ b/authentik.old/chat.tf @@ -0,0 +1,58 @@ +resource "random_id" "chat_client_id" { + byte_length = 16 +} + +resource "authentik_provider_oauth2" "chat" { + name = "Chat" + # Required. You can use the output of: + # $ openssl rand -hex 16 + client_id = random_id.chat_client_id.id + + # Optional: will be generated if not provided + # client_secret = "my_client_secret" + + authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id + invalidation_flow = data.authentik_flow.default-invalidation-flow.id + + allowed_redirect_uris = [ + { + matched_mode = "strict", + url = "https://chat.lab.cowley.tech/oauth/oidc/callback", + }, + { + matched_mode = "regex", + url = ".*" + } + ] + property_mappings = [ + data.authentik_property_mapping_provider_scope.scope-openid.id, + data.authentik_property_mapping_provider_scope.scope-email.id, + data.authentik_property_mapping_provider_scope.scope-profile.id, + ] + lifecycle { + ignore_changes = [ + signing_key, + authentication_flow, + ] + } +} + +resource "authentik_application" "chat" { + name = "Chat" + slug = "chat" + protocol_provider = authentik_provider_oauth2.chat.id +} + +resource "kubernetes_secret" "chat" { + metadata { + name = "open-webui-authentik" + namespace = "ollama" + } + data = { + OAUTH_CLIENT_ID = authentik_provider_oauth2.chat.client_id + OAUTH_CLIENT_SECRET = authentik_provider_oauth2.chat.client_secret + OPENID_PROVIDER_URL = "https://auth.lab.cowley.tech/application/o/chat/.well-known/openid-configuration" + OAUTH_PROVIDER_NAME = "Authentik" + OAUTH_SCOPES = "openid email profile" + } +} diff --git a/authentik.old/dashy.tf b/authentik.old/dashy.tf new file mode 100644 index 0000000..31b69d1 --- /dev/null +++ b/authentik.old/dashy.tf @@ -0,0 +1,47 @@ +resource "random_id" "dashy_client_id" { + byte_length = 16 +} + +resource "authentik_provider_oauth2" "dashy" { + name = "Dashy" + # Required. You can use the output of: + # $ openssl rand -hex 16 + client_id = random_id.dashy_client_id.id + authentication_flow = data.authentik_flow.default-authentication-flow.id + authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id + invalidation_flow = data.authentik_flow.default-invalidation-flow.id + + client_type = "public" + + allowed_redirect_uris = [ + { + matched_mode = "strict", + url = "https://dash.lab.cowley.tech/", + }, + { + matched_mode = "regex", + url = ".*" + } + ] + + sub_mode = "user_email" + + property_mappings = [ + data.authentik_property_mapping_provider_scope.scope-email.id, + data.authentik_property_mapping_provider_scope.scope-profile.id, + data.authentik_property_mapping_provider_scope.scope-openid.id, + ] + lifecycle { + ignore_changes = [ + signing_key, + authentication_flow, + ] + } +} + +resource "authentik_application" "dashy" { + name = "Dashy" + slug = "dashy" + protocol_provider = authentik_provider_oauth2.dashy.id + open_in_new_tab = true +} diff --git a/authentik.old/data.tf b/authentik.old/data.tf new file mode 100644 index 0000000..772f054 --- /dev/null +++ b/authentik.old/data.tf @@ -0,0 +1,21 @@ +data "authentik_flow" "default-provider-authorization-implicit-consent" { + slug = "default-provider-authorization-implicit-consent" +} + +data "authentik_flow" "default-authentication-flow" { + slug = "default-authentication-flow" +} +data "authentik_flow" "default-invalidation-flow" { + slug = "default-invalidation-flow" +} +data "authentik_property_mapping_provider_scope" "scope-email" { + name = "authentik default OAuth Mapping: OpenID 'email'" +} + +data "authentik_property_mapping_provider_scope" "scope-profile" { + name = "authentik default OAuth Mapping: OpenID 'profile'" +} + +data "authentik_property_mapping_provider_scope" "scope-openid" { + name = "authentik default OAuth Mapping: OpenID 'openid'" +} diff --git a/authentik.old/docs.tf b/authentik.old/docs.tf new file mode 100644 index 0000000..f01634d --- /dev/null +++ b/authentik.old/docs.tf @@ -0,0 +1,28 @@ +#resource "authentik_provider_proxy" "docs" { +# name = "docs" +# authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id +# external_host = "https://docs.lab.cowley.tech" +# internal_host = "http://homelab-docs.docs.svc.cluster.local" +#} +#resource "authentik_application" "docs" { +# name = "Homelab Docs" +# slug = "homelab-docs" +# protocol_provider = authentik_provider_proxy.docs.id +# meta_launch_url = "https://docs.lab.cowley.tech" +#} +#resource "authentik_outpost" "docs" { +# name = "docs" +# protocol_providers = [ +# authentik_provider_proxy.docs.id +# ] +# config = jsonencode({ +# "kubernetes_namespace": "docs", +# "kubernetes_ingress_class_name": "nginx", +# }) +# service_connection = authentik_service_connection_kubernetes.local.id +#} +# +#resource "authentik_service_connection_kubernetes" "local" { +# name = "local" +# local = true +#} diff --git a/authentik.old/foo.bar b/authentik.old/foo.bar new file mode 100755 index 0000000..197536f --- /dev/null +++ b/authentik.old/foo.bar @@ -0,0 +1 @@ +Zo7QLQh2eAe2XCUv6yOKZ0GRcW3k9zCFEqLUmHe0Mq3SyMED27YMGM1gKKe4xi2iqY4m4RPQ9eWI4NUygmWLISuaUnpa6GNZACrnnC4wcde1fEqzG4GwXawZ2HOQE51V \ No newline at end of file diff --git a/authentik.old/forgejo.tf b/authentik.old/forgejo.tf new file mode 100644 index 0000000..7e0ad26 --- /dev/null +++ b/authentik.old/forgejo.tf @@ -0,0 +1,57 @@ +resource "random_id" "forgejo_client_id" { + byte_length = 16 +} + +resource "authentik_provider_oauth2" "forgejo" { + name = "Forgejo" + # Required. You can use the output of: + # $ openssl rand -hex 16 + client_id = random_id.forgejo_client_id.id + + # Optional: will be generated if not provided + # client_secret = "my_client_secret" + + authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id + invalidation_flow = data.authentik_flow.default-invalidation-flow.id + + allowed_redirect_uris = [ + { + matched_mode = "strict" + url = "https://code.lab.cowley.tech/user/oauth2/authentik/callback" + } + ] + property_mappings = [ + data.authentik_property_mapping_provider_scope.scope-email.id, + data.authentik_property_mapping_provider_scope.scope-profile.id, + data.authentik_property_mapping_provider_scope.scope-openid.id, + ] + lifecycle { + ignore_changes = [ + signing_key, + authentication_flow, + ] + } +} + +resource "authentik_application" "forgejo" { + name = "ForgeJo" + slug = "forgejo" + protocol_provider = authentik_provider_oauth2.forgejo.id +} + +resource "authentik_group" "forgejo-admins" { + name = "gitadmin" +} +resource "authentik_group" "forgejo-users" { + name = "gituser" +} +resource "kubernetes_secret" "forgejo-oauth" { + metadata { + name = "forgejo-oauth" + namespace = "forgejo" + } + data = { + "key" = authentik_provider_oauth2.forgejo.client_id + "secret" = authentik_provider_oauth2.forgejo.client_secret + } +} diff --git a/authentik.old/grafana.tf b/authentik.old/grafana.tf new file mode 100644 index 0000000..97d85f9 --- /dev/null +++ b/authentik.old/grafana.tf @@ -0,0 +1,80 @@ + +resource "random_id" "client_id" { + byte_length = 16 +} + +resource "authentik_provider_oauth2" "grafana" { + name = "Grafana" + # Required. You can use the output of: + # $ openssl rand -hex 16 + client_id = random_id.client_id.id + + # Optional: will be generated if not provided + # client_secret = "my_client_secret" + + authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id + invalidation_flow = data.authentik_flow.default-invalidation-flow.id + + allowed_redirect_uris = [ + { + matched_mode = "strict", + url = "https://grafana.lab.cowley.tech/login/generic_oauth" + }, + ] + + property_mappings = [ + data.authentik_property_mapping_provider_scope.scope-email.id, + data.authentik_property_mapping_provider_scope.scope-profile.id, + data.authentik_property_mapping_provider_scope.scope-openid.id, + ] + + lifecycle { + ignore_changes = [ + signing_key, + authentication_flow, + ] + } +} + +resource "authentik_application" "grafana" { + name = "Grafana" + slug = "grafana" + protocol_provider = authentik_provider_oauth2.grafana.id +} + +resource "authentik_group" "grafana_admins" { + name = "Grafana Admins" +} + +resource "authentik_group" "grafana_editors" { + name = "Grafana Editors" +} + +resource "authentik_group" "grafana_viewers" { + name = "Grafana Viewers" +} + +resource "kubernetes_secret" "grafana-authentik" { + metadata { + name = "grafana-authentik" + namespace = "monitoring" + } + data = { + "GF_AUTH_GENERIC_OAUTH_ENABLED" = "true" + "GF_AUTH_GENERIC_OAUTH_CLIENT_ID" = authentik_provider_oauth2.grafana.client_id + "GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET" = authentik_provider_oauth2.grafana.client_secret + "GF_AUTH_GENERIC_OAUTH_NAME" = "authentik" + "GF_AUTH_GENERIC_OAUTH_SCOPES" = "openid profile email" + "GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP" = "true" + "GF_AUTH_GENERIC_OAUTH_AUTH_URL" = "https://auth.lab.cowley.tech/application/o/authorize/" + "GF_AUTH_GENERIC_OAUTH_TOKEN_URL" = "https://auth.lab.cowley.tech/application/o/token/" + "GF_AUTH_GENERIC_OAUTH_API_URL" = "https://auth.lab.cowley.tech/application/o/userinfo/" + "GF_AUTH_SIGNOUT_REDIRECT_URL" = "https://auth.lab.cowley.tech/application/o/grafana/end-session/" + "GF_AUTH_GENERIC_SIGNOUT_REDIRECT_URL" = "https://auth.lab.cowley.tech/application/o/grafana/end-session/" + # Optionally enable auto-login (bypasses Grafana login screen) + "GF_AUTH_OAUTH_AUTO_LOGIN" = "false" + # Optionally map user groups to Grafana roles + "GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH" = "contains(groups, 'Grafana Admins') && 'Admin' || contains(groups, 'Grafana Editors') && 'Editor' || 'Viewer'" + + } +} diff --git a/authentik.old/groups.tf b/authentik.old/groups.tf new file mode 100644 index 0000000..20129db --- /dev/null +++ b/authentik.old/groups.tf @@ -0,0 +1,7 @@ +data "authentik_group" "admins" { + name = "authentik Admins" +} + +resource "authentik_group" "arr-users" { + name = "arr_users" +} diff --git a/authentik.old/immich.tf b/authentik.old/immich.tf new file mode 100644 index 0000000..50fc71f --- /dev/null +++ b/authentik.old/immich.tf @@ -0,0 +1,69 @@ +#data "authentik_flow" "default-provider-authorization-implicit-consent" { +# slug = "default-provider-authorization-implicit-consent" +#} +# +#data "authentik_property_mapping_provider_scope" "scope-email" { +# name = "authentik default OAuth Mapping: OpenID 'email'" +#} +# +#data "authentik_property_mapping_provider_scope" "scope-profile" { +# name = "authentik default OAuth Mapping: OpenID 'profile'" +#} +# +#data "authentik_property_mapping_provider_scope" "scope-openid" { +# name = "authentik default OAuth Mapping: OpenID 'openid'" +#} +# +resource "random_id" "immich_client_id" { + byte_length = 16 +} + +resource "authentik_provider_oauth2" "immich" { + name = "Immich" + # Required. You can use the output of: + # $ openssl rand -hex 16 + client_id = random_id.immich_client_id.id + + # Optional: will be generated if not provided + # client_secret = "my_client_secret" + + authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id + invalidation_flow = data.authentik_flow.default-invalidation-flow.id + + allowed_redirect_uris = [ + { + matched_mode = "strict" + url = "app.immich:///oauth-callback", + }, + { + matched_mode = "strict" + url = "https://photos.lab.cowley.tech/auth/login", + }, + { + matched_mode = "strict" + url = "https://photos.lab.cowley.tech/user-settings", + } + ] + property_mappings = [ + data.authentik_property_mapping_provider_scope.scope-email.id, + data.authentik_property_mapping_provider_scope.scope-profile.id, + data.authentik_property_mapping_provider_scope.scope-openid.id, + ] + lifecycle { + ignore_changes = [ + signing_key, + authentication_flow, + ] + } +} + +resource "authentik_application" "immich" { + name = "Immich" + slug = "immich" + protocol_provider = authentik_provider_oauth2.immich.id +} + +resource "local_file" "foo" { + content = authentik_provider_oauth2.immich.client_secret + filename = "${path.module}/foo.bar" +} diff --git a/authentik.old/jellyfin.tf b/authentik.old/jellyfin.tf new file mode 100644 index 0000000..c1dd225 --- /dev/null +++ b/authentik.old/jellyfin.tf @@ -0,0 +1,50 @@ +resource "random_id" "jellyfin_client_id" { + byte_length = 16 +} + +resource "authentik_provider_oauth2" "jellyfin" { + name = "Jellyfin" + client_id = random_id.jellyfin_client_id.id + + authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id + invalidation_flow = data.authentik_flow.default-invalidation-flow.id + + allowed_redirect_uris = [ + { + matched_mode = "strict", + url = "https://jellyfin.lab.cowley.tech/sso/OID/start/authentik", + }, + { + matched_mode = "regex", + url = ".*", + } + ] + + property_mappings = [ + data.authentik_property_mapping_provider_scope.scope-email.id, + data.authentik_property_mapping_provider_scope.scope-profile.id, + data.authentik_property_mapping_provider_scope.scope-openid.id, + ] + lifecycle { + ignore_changes = [ + signing_key, + authentication_flow, + ] + } +} + +resource "authentik_application" "jellyfin" { + name = "Jellyfin" + slug = "jellyfin" + protocol_provider = authentik_provider_oauth2.jellyfin.id + meta_launch_url = "https://jellyfin.lab.cowley.tech/sso/OID/start/authentik" +} +resource "kubernetes_secret" "jellyfin_oidc" { + metadata { + name = "jellyfin-oidc" + namespace = "jellyfin" + } + data = { + client-secret = authentik_provider_oauth2.jellyfin.client_secret + } +} diff --git a/authentik.old/lidarr.tf b/authentik.old/lidarr.tf new file mode 100644 index 0000000..f1e610c --- /dev/null +++ b/authentik.old/lidarr.tf @@ -0,0 +1,20 @@ +#resource "authentik_provider_proxy" "lidarr" { +# name = "lidarr" +# internal_host = "http://lidarr.jellyfin:8686" +# external_host = "https://lidarr.lab.cowley.tech" +# authorization_flow = data.authentik_flow.default-provider-authorization-implicit-consent.id +#} +# +#resource "authentik_outpost" "lidarr" { +# name = "lidarr-outpost" +# protocol_providers = [ +# authentik_provider_proxy.lidarr.id +# ] +#} +# +#resource "authentik_application" "lidarr" { +# name = "Lidarr" +# slug = "lidarr" +# +# protocol_provider = authentik_provider_proxy.lidarr.id +#} diff --git a/authentik.old/nextcloud.tf b/authentik.old/nextcloud.tf new file mode 100644 index 0000000..5eb75c1 --- /dev/null +++ b/authentik.old/nextcloud.tf @@ -0,0 +1,75 @@ +#data "authentik_property_mapping_provider_scope" "nextcloud" { +# name = "Nextcloud Profile" +#} +resource "authentik_property_mapping_provider_scope" "nextcloud-scope" { + name = "Nextcloud Profile" + scope_name = "profile" + expression = <