add SA creation logic

This commit is contained in:
Kochetkov S 2026-02-19 12:12:40 +03:00
parent 31c3bf338a
commit 3286b26777
8 changed files with 221 additions and 88 deletions

View File

@ -59,6 +59,19 @@ environments:
# lifecycle:
# ignore_changes: false
# # Пример секрета из SA ключей
# - name: dwg-sa-secret
# namespace: pulse
# type: yc_sa
# dependencies:
# service_account: dwg-sa
# yc_sa_key_names: # как назвать ключи в секрете
# access_key: access_key
# secret_key: secret_key
# service_account_id: sa_id
# lifecycle:
# ignore_changes: false
buckets:
- name: pulse-prod
acl: private
@ -67,6 +80,12 @@ environments:
cors:
enabled: false
# yc_service_accounts:
# - name: dwg-sa
# description: "DWG SA Uploader"
# folder_roles:
# - storage.uploader
# create_static_access_key: true
databases:
@ -81,4 +100,3 @@ environments:
password_special: false
conn_limit: 10

View File

@ -20,8 +20,9 @@ terraform {
}
inputs = {
buckets = local.env_config.buckets
folder_id = get_env("YC_PROD_FOLDER_ID", get_env("YC_FOLDER_ID", ""))
buckets = local.env_config.buckets
yc_service_accounts = try(local.env_config.yc_service_accounts, [])
folder_id = get_env("YC_PROD_FOLDER_ID", get_env("YC_FOLDER_ID", ""))
yc_token = get_env("YC_TOKEN", "")
yc_service_account_key_file = get_env("YC_SERVICE_ACCOUNT_KEY_FILE", "")

View File

@ -21,10 +21,11 @@ dependency "namespace" {
dependency "s3" {
config_path = "../s3"
mock_outputs = {
bucket_name = "mock-bucket"
access_key = "mock-access-key"
secret_key = "mock-secret-key"
buckets = {}
bucket_name = "mock-bucket"
access_key = "mock-access-key"
secret_key = "mock-secret-key"
buckets = {}
service_accounts = {}
}
mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"]
skip_outputs = get_env("TG_SKIP_DEPENDENCY_OUTPUTS", "false") == "true"
@ -115,16 +116,17 @@ terraform {
inputs = {
secrets = [
for secret in local.env_config.secrets : {
name = secret.name
namespace = secret.namespace
secret_type = secret.type
registry_url = try(secret.registry_url, "")
dependencies = try(secret.dependencies, {})
custom_keys = try(secret.custom_keys, {})
random_keys = try(secret.random_keys, {})
labels = try(secret.labels, {})
annotations = try(secret.annotations, {})
ignore_changes = try(secret.lifecycle.ignore_changes, false)
name = secret.name
namespace = secret.namespace
secret_type = secret.type
registry_url = try(secret.registry_url, "")
dependencies = try(secret.dependencies, {})
yc_sa_key_names = try(secret.yc_sa_key_names, {})
custom_keys = try(secret.custom_keys, {})
random_keys = try(secret.random_keys, {})
labels = try(secret.labels, {})
annotations = try(secret.annotations, {})
ignore_changes = try(secret.lifecycle.ignore_changes, false)
}
]
@ -134,7 +136,8 @@ inputs = {
secret_key = dependency.s3.outputs.secret_key
}
s3_buckets_map = dependency.s3.outputs.buckets
s3_buckets_map = dependency.s3.outputs.buckets
yc_service_accounts_map = try(dependency.s3.outputs.service_accounts, {})
database_outputs_map = dependency.database.outputs.database_outputs_map

View File

@ -6,9 +6,16 @@ locals {
"dockerconfigjson" = "kubernetes.io/dockerconfigjson"
"database" = "Opaque"
"s3" = "Opaque"
"yc_sa" = "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 : secret.name => secret
}
@ -31,46 +38,72 @@ locals {
)
}
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"
}
secrets_data = {
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 == "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 == "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, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
# Если указан конкретный 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, "")
},
try(secret.custom_keys, {}),
{
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, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
) : merge(
try(secret.custom_keys, {}),
{
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
)
try(secret.custom_keys, {}),
{
for key, config in try(secret.random_keys, {}) : key => random_password.secrets["${name}:${key}"].result
}
)
}
secrets_with_data = {

View File

@ -1,23 +1,29 @@
variable "secrets" {
description = "Список секретов для создания"
type = list(object({
name = string
namespace = string
secret_type = string
registry_url = optional(string, "")
dependencies = optional(object({
cluster = optional(string, "")
db = optional(string, "")
user = optional(string, "")
bucket = optional(string, "") # Имя бакета для s3 секретов
name = string
namespace = string
secret_type = string
registry_url = optional(string, "")
dependencies = optional(object({
cluster = optional(string, "")
db = optional(string, "")
user = optional(string, "")
bucket = optional(string, "") # Имя бакета для s3 секретов
service_account = optional(string, "") # Имя YC Service Account для yc_sa секретов
}), {})
custom_keys = optional(map(string), {})
random_keys = optional(map(object({
yc_sa_key_names = optional(object({
access_key = optional(string, "access_key")
secret_key = optional(string, "secret_key")
service_account_id = optional(string, "service_account_id")
}), {})
custom_keys = optional(map(string), {})
random_keys = optional(map(object({
length = optional(number, 32)
special = optional(bool, false)
})), {}) # Случайные ключи
labels = optional(map(string), {})
annotations = optional(map(string), {})
labels = optional(map(string), {})
annotations = optional(map(string), {})
ignore_changes = optional(bool, false)
}))
}
@ -47,25 +53,36 @@ variable "s3_buckets_map" {
default = {}
}
variable "yc_service_accounts_map" {
description = "Map YC service accounts (name => data)"
type = map(object({
service_account_id = string
access_key = optional(string, "")
secret_key = optional(string, "")
folder_roles = optional(list(string), [])
}))
default = {}
}
variable "database_outputs_map" {
description = "Outputs от модулей Database, ключ = cluster_id:database_name:user_name"
type = map(object({
host = string
host = string
database_name = string
user_name = string
password = string
user_name = string
password = string
}))
default = {}
}
variable "constants" {
description = "Константные значения (CA сертификаты, endpoints и т.д.)"
type = map(string)
default = {}
type = map(string)
default = {}
}
variable "env_vars" {
description = "Environment variables"
type = map(string)
default = {}
type = map(string)
default = {}
}

View File

@ -1,5 +1,20 @@
locals {
buckets_map = { for bucket in var.buckets : bucket.name => bucket }
buckets_map = { for bucket in var.buckets : bucket.name => bucket }
yc_service_accounts_map = { for sa in var.yc_service_accounts : sa.name => sa }
yc_service_account_folder_roles = length(local.yc_service_accounts_map) > 0 ? merge([
for sa_name, sa in local.yc_service_accounts_map : {
for role in try(sa.folder_roles, []) : "${sa_name}:${role}" => {
sa_name = sa_name
role = role
}
}
]...) : {}
yc_service_accounts_with_static_key = {
for sa_name, sa in local.yc_service_accounts_map : sa_name => sa
if try(sa.create_static_access_key, true)
}
}
resource "yandex_iam_service_account" "sa" {
@ -54,3 +69,25 @@ resource "yandex_storage_bucket_iam_binding" "uploader" {
depends_on = [yandex_storage_bucket.this]
}
resource "yandex_iam_service_account" "custom_sa" {
for_each = local.yc_service_accounts_map
name = each.value.name
description = try(each.value.description, "")
}
resource "yandex_resourcemanager_folder_iam_member" "custom_sa_role" {
for_each = local.yc_service_account_folder_roles
folder_id = var.folder_id
role = each.value.role
member = "serviceAccount:${yandex_iam_service_account.custom_sa[each.value.sa_name].id}"
}
resource "yandex_iam_service_account_static_access_key" "custom_sa_key" {
for_each = local.yc_service_accounts_with_static_key
service_account_id = yandex_iam_service_account.custom_sa[each.key].id
description = "Static access key for ${each.key}"
}

View File

@ -27,3 +27,16 @@ output "buckets" {
}
sensitive = true
}
output "service_accounts" {
description = "Map of additional YC service accounts (name => data)"
value = {
for name, sa in yandex_iam_service_account.custom_sa : name => {
service_account_id = sa.id
access_key = try(yandex_iam_service_account_static_access_key.custom_sa_key[name].access_key, "")
secret_key = try(yandex_iam_service_account_static_access_key.custom_sa_key[name].secret_key, "")
folder_roles = try(local.yc_service_accounts_map[name].folder_roles, [])
}
}
sensitive = true
}

View File

@ -18,6 +18,17 @@ variable "buckets" {
}))
}
variable "yc_service_accounts" {
description = "Additional Yandex Cloud service accounts with optional folder roles and static keys"
type = list(object({
name = string
description = optional(string, "")
folder_roles = optional(list(string), [])
create_static_access_key = optional(bool, true)
}))
default = []
}
variable "folder_id" {
description = "Yandex Cloud folder ID"
type = string