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

@ -21,6 +21,7 @@ terraform {
inputs = {
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", "")

View File

@ -25,6 +25,7 @@ dependency "s3" {
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"
@ -120,6 +121,7 @@ inputs = {
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, {})
@ -135,6 +137,7 @@ inputs = {
}
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,6 +38,22 @@ 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" ? {
@ -49,7 +72,17 @@ locals {
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(
} : 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

View File

@ -10,6 +10,12 @@ variable "secrets" {
db = optional(string, "")
user = optional(string, "")
bucket = optional(string, "") # Имя бакета для s3 секретов
service_account = optional(string, "") # Имя YC Service Account для yc_sa секретов
}), {})
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({
@ -47,6 +53,17 @@ 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({

View File

@ -1,5 +1,20 @@
locals {
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