terraform-contour-mirror/modules/k8s-secret/main.tf

320 lines
13 KiB
HCL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

locals {
# Маппинг наших типов на Kubernetes типы
k8s_secret_type_map = {
"dockerconfigjson" = "kubernetes.io/dockerconfigjson"
"database" = "Opaque"
"s3" = "Opaque"
"yc_sa" = "Opaque"
"rabbitmq" = "Opaque"
"kafka" = "Opaque"
"opaque" = "Opaque"
}
default_yc_sa_key_names = {
access_key = "access_key"
secret_key = "secret_key"
service_account_id = "service_account_id"
}
secrets_map = {
for idx, secret in var.secrets : (try(secret.resource_key, "") != "" ? secret.resource_key : secret.name) => secret
}
random_keys_flat = merge([
for secret_name, secret in local.secrets_map : {
for key, config in try(secret.random_keys, {}) : "${secret_name}:${key}" => {
secret_name = secret_name
key = key
length = try(config.length, 32)
special = try(config.special, false)
}
}
]...)
database_outputs_by_secret = {
for name, secret in local.secrets_map : name => try(
var.database_outputs_map["${try(secret.dependencies.cluster, "")}:${try(secret.dependencies.db, "")}:${try(secret.dependencies.user, "")}"],
null
)
}
rabbitmq_outputs_by_secret = {
for name, secret in local.secrets_map : name => try(
var.rabbitmq_outputs_map[
format(
"%s:%s:%s",
try(secret.dependencies.rabbitmq_ref, "default"),
try(secret.dependencies.rabbitmq_vhost, try(secret.dependencies.vhost, "")),
try(secret.dependencies.rabbitmq_user, try(secret.dependencies.user, ""))
)
],
null
)
}
kafka_outputs_by_secret = {
for name, secret in local.secrets_map : name => try(
var.kafka_outputs_map[
format(
"%s:%s",
try(secret.dependencies.kafka_ref, try(secret.dependencies.cluster_ref, try(secret.dependencies.cluster, ""))),
try(secret.dependencies.kafka_user, try(secret.dependencies.user, ""))
)
],
null
)
}
rabbitmq_default_credentials_by_secret = {
for name, secret in local.secrets_map : name => local.rabbitmq_outputs_by_secret[name] == null ? {} : {
host = local.rabbitmq_outputs_by_secret[name].host
hostname = "${local.rabbitmq_outputs_by_secret[name].host}:${local.rabbitmq_outputs_by_secret[name].port}"
port = local.rabbitmq_outputs_by_secret[name].port
vhost = local.rabbitmq_outputs_by_secret[name].vhost
user = local.rabbitmq_outputs_by_secret[name].username
username = local.rabbitmq_outputs_by_secret[name].username
password = local.rabbitmq_outputs_by_secret[name].password
uri = local.rabbitmq_outputs_by_secret[name].uri
management_endpoint = local.rabbitmq_outputs_by_secret[name].management_endpoint
}
if secret.secret_type == "rabbitmq"
}
rabbitmq_credentials_by_secret = {
for name, secret in local.secrets_map : name => length(try(secret.credential_keys, {})) == 0 ? local.rabbitmq_default_credentials_by_secret[name] : {
for key, source in try(secret.credential_keys, {}) : key => try(local.rabbitmq_default_credentials_by_secret[name][source], "")
}
if secret.secret_type == "rabbitmq" && local.rabbitmq_outputs_by_secret[name] != null
}
kafka_default_credentials_by_secret = {
for name, secret in local.secrets_map : name => local.kafka_outputs_by_secret[name] == null ? {} : {
host = local.kafka_outputs_by_secret[name].host
hostname = local.kafka_outputs_by_secret[name].hostname
port = local.kafka_outputs_by_secret[name].port
username = local.kafka_outputs_by_secret[name].username
user = local.kafka_outputs_by_secret[name].username
password = local.kafka_outputs_by_secret[name].password
sasl_mechanism = local.kafka_outputs_by_secret[name].sasl_mechanism
security_protocol = local.kafka_outputs_by_secret[name].security_protocol
bootstrap_server = local.kafka_outputs_by_secret[name].bootstrap_server
bootstrap_servers = local.kafka_outputs_by_secret[name].bootstrap_servers
bootstrap_servers_json = local.kafka_outputs_by_secret[name].bootstrap_servers_json
}
if secret.secret_type == "kafka"
}
kafka_credentials_by_secret = {
for name, secret in local.secrets_map : name => length(try(secret.credential_keys, {})) == 0 ? local.kafka_default_credentials_by_secret[name] : {
for key, source in try(secret.credential_keys, {}) : key => try(local.kafka_default_credentials_by_secret[name][source], "")
}
if secret.secret_type == "kafka" && local.kafka_outputs_by_secret[name] != null
}
yc_sa_sources = {
for name, secret in local.secrets_map : name => try(
var.yc_service_accounts_map[try(secret.dependencies.service_account, "")],
null
)
if secret.secret_type == "yc_sa"
}
yc_sa_key_names_by_secret = {
for name, secret in local.secrets_map : name => merge(
local.default_yc_sa_key_names,
try(secret.yc_sa_key_names, {})
)
if secret.secret_type == "yc_sa"
}
# Значения, захваченные из live-кластера (adopt 1:1). Приходят из расшифрованного
# secret-values файла как map(resource_key => map(key => base64(raw_value))).
# k8s хранит .data уже в base64, поэтому base64decode даёт исходное значение байт-в-байт.
secrets_data_override = {
for name, secret in local.secrets_map : name => {
for key, b64 in try(var.secret_values[name], {}) : key => base64decode(b64)
}
if length(try(var.secret_values[name], {})) > 0
}
secrets_data_computed = {
for name, secret in local.secrets_map : name =>
secret.secret_type == "dockerconfigjson" ? {
".dockerconfigjson" = jsonencode({
auths = {
"${secret.registry_url}" = {
username = try(var.env_vars.DOCKER_REGISTRY_USERNAME, "")
password = try(var.env_vars.DOCKER_REGISTRY_PASSWORD, "")
auth = base64encode("${try(var.env_vars.DOCKER_REGISTRY_USERNAME, "")}:${try(var.env_vars.DOCKER_REGISTRY_PASSWORD, "")}")
}
}
})
} : secret.secret_type == "s3" ? {
# Если указан конкретный bucket в dependencies - используем его, иначе fallback на s3_outputs
access_key = try(var.s3_buckets_map[secret.dependencies.bucket].access_key, var.s3_outputs.access_key)
secret_key = try(var.s3_buckets_map[secret.dependencies.bucket].secret_key, var.s3_outputs.secret_key)
bucket = try(var.s3_buckets_map[secret.dependencies.bucket].bucket_name, var.s3_outputs.bucket_name)
endpoint = try(var.constants.s3_endpoint, "https://storage.yandexcloud.net")
} : secret.secret_type == "yc_sa" ? merge(
{
(local.yc_sa_key_names_by_secret[name].access_key) = try(local.yc_sa_sources[name].access_key, "")
(local.yc_sa_key_names_by_secret[name].secret_key) = try(local.yc_sa_sources[name].secret_key, "")
(local.yc_sa_key_names_by_secret[name].service_account_id) = try(local.yc_sa_sources[name].service_account_id, "")
endpoint = "https://storage.yandexcloud.net"
},
try(secret.custom_keys, {}),
{
for key, constant_name in try(secret.constant_keys, {}) : key => try(var.constants[constant_name], "")
},
{
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
) : secret.secret_type == "database" && local.database_outputs_by_secret[name] != null ? merge(
# Базовые поля из database outputs
{
host = local.database_outputs_by_secret[name].host
port = try(var.constants.postgres_port, "6432")
database = local.database_outputs_by_secret[name].database_name
username = local.database_outputs_by_secret[name].user_name
password = local.database_outputs_by_secret[name].password
"ca.crt" = try(var.constants.postgres_ca, "")
},
# Кастомные статические поля
try(secret.custom_keys, {}),
{
for key, constant_name in try(secret.constant_keys, {}) : key => try(var.constants[constant_name], "")
},
# Рандомные поля
{
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
) : secret.secret_type == "rabbitmq" && local.rabbitmq_outputs_by_secret[name] != null ? merge(
local.rabbitmq_credentials_by_secret[name],
try(secret.custom_keys, {}),
{
for key, constant_name in try(secret.constant_keys, {}) : key => try(var.constants[constant_name], "")
},
{
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
) : secret.secret_type == "kafka" && local.kafka_outputs_by_secret[name] != null ? merge(
local.kafka_credentials_by_secret[name],
try(secret.custom_keys, {}),
{
for key, constant_name in try(secret.constant_keys, {}) : key => try(var.constants[constant_name], "")
},
{
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
) : merge(
try(secret.custom_keys, {}),
{
for key, constant_name in try(secret.constant_keys, {}) : key => try(var.constants[constant_name], "")
},
{
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
)
}
# Если для секрета заданы live-значения (adopt) - используем их 1:1,
# иначе собираем data из outputs/custom/constant/random как обычно.
secrets_data = {
for name, secret in local.secrets_map : name => (
contains(keys(local.secrets_data_override), name)
? local.secrets_data_override[name]
: local.secrets_data_computed[name]
)
}
secrets_with_data = {
for name, secret in local.secrets_map : name => {
name = secret.name
namespace = secret.namespace
secret_type = secret.secret_type
k8s_type = try(local.k8s_secret_type_map[secret.secret_type], "Opaque")
labels = try(secret.labels, {})
annotations = try(secret.annotations, {})
ignore_changes = try(secret.ignore_changes, false)
data = try(local.secrets_data[name], {})
}
}
secrets_with_ignore = {
for name, secret in local.secrets_with_data : name => secret
if secret.ignore_changes
}
secrets_without_ignore = {
for name, secret in local.secrets_with_data : name => secret
if !secret.ignore_changes
}
}
resource "random_password" "secrets" {
for_each = local.random_keys_flat
length = each.value.length
special = each.value.special
upper = true
lower = true
numeric = true
lifecycle {
ignore_changes = all
}
}
resource "kubernetes_secret" "with_ignore" {
for_each = local.secrets_with_ignore
metadata {
name = each.value.name
namespace = each.value.namespace
labels = try(each.value.labels, {})
annotations = try(each.value.annotations, {})
}
type = each.value.k8s_type
# Совпадает с импортированным состоянием (провайдер по умолчанию ставит true и
# даёт phantom "+ wait_for_service_account_token = true" на adopt-секретах).
# Актуально только для secret-ов типа service-account-token; тут все Opaque.
wait_for_service_account_token = false
data = each.value.data
lifecycle {
# Игнорируем data, type и metadata чтобы не было phantom updates
ignore_changes = [data, type, metadata]
}
depends_on = [random_password.secrets]
}
resource "kubernetes_secret" "without_ignore" {
for_each = local.secrets_without_ignore
metadata {
name = each.value.name
namespace = each.value.namespace
labels = try(each.value.labels, {})
annotations = try(each.value.annotations, {})
}
type = each.value.k8s_type
# Совпадает с импортированным состоянием (иначе phantom "+ wait_for_service_account_token = true").
wait_for_service_account_token = false
data = each.value.data
lifecycle {
# type и metadata игнорируем чтобы не было phantom updates
ignore_changes = [type, metadata]
}
depends_on = [random_password.secrets]
}