mirror of
https://gitlab.sarex.io/infra/terraform-contour-mirror.git
synced 2026-08-08 13:11:35 +03:00
feat(yc-s3): use bucket policy instead of folder-level IAM roles
- Remove yandex_resourcemanager_folder_iam_member with storage.editor - Add yandex_storage_bucket_policy for bucket-level access control - SA now has access only to its own bucket via bucket policy - Add role variable (storage.uploader, storage.viewer, storage.editor) - Add README.md with module documentation - Remove comments from module files
This commit is contained in:
parent
6a141f8d51
commit
fb3b9894d4
236
README.md
Normal file
236
README.md
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
# Terraform Infrastructure
|
||||||
|
|
||||||
|
Infrastructure as Code для управления ресурсами Yandex Cloud и Kubernetes.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
terraform/
|
||||||
|
├── infrastructure.yaml # Конфигурация всех ресурсов (единый источник правды)
|
||||||
|
├── live/ # Terragrunt конфигурации по окружениям
|
||||||
|
│ ├── terragrunt.hcl # Общие настройки (backend, providers)
|
||||||
|
│ └── stage/ # Stage окружение
|
||||||
|
│ ├── env.hcl
|
||||||
|
│ ├── namespace/
|
||||||
|
│ ├── database/
|
||||||
|
│ ├── s3/
|
||||||
|
│ └── secrets/
|
||||||
|
└── modules/ # Terraform модули
|
||||||
|
├── k8s-namespace/ # Создание Kubernetes namespaces
|
||||||
|
├── k8s-secret/ # Создание Kubernetes secrets (с зависимостями)
|
||||||
|
├── k8s-secrets/ # Простое создание secrets
|
||||||
|
├── yc-database/ # PostgreSQL users и databases в Yandex Cloud
|
||||||
|
└── yc-s3/ # S3 бакеты с изолированным доступом
|
||||||
|
```
|
||||||
|
|
||||||
|
## Принцип работы
|
||||||
|
|
||||||
|
### infrastructure.yaml
|
||||||
|
|
||||||
|
Единый файл конфигурации для всех ресурсов. Структура:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
environments:
|
||||||
|
stage: # Имя окружения
|
||||||
|
namespaces: # Kubernetes namespaces
|
||||||
|
- name: pulse
|
||||||
|
labels: {}
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
buckets: # S3 бакеты
|
||||||
|
- name: pulse-stage
|
||||||
|
acl: private
|
||||||
|
role: storage.uploader # опционально
|
||||||
|
versioning:
|
||||||
|
enabled: false
|
||||||
|
cors:
|
||||||
|
enabled: true
|
||||||
|
allowed_methods: [GET, PUT]
|
||||||
|
|
||||||
|
databases: # PostgreSQL databases
|
||||||
|
- cluster_id: "xxx"
|
||||||
|
database:
|
||||||
|
name: mydb
|
||||||
|
extensions: [pg_stat_statements]
|
||||||
|
user:
|
||||||
|
name: myuser
|
||||||
|
password_length: 32
|
||||||
|
conn_limit: 10
|
||||||
|
|
||||||
|
secrets: # Kubernetes secrets
|
||||||
|
- name: my-secret
|
||||||
|
namespace: pulse
|
||||||
|
type: database # database | s3 | dockerconfigjson | opaque
|
||||||
|
dependencies:
|
||||||
|
cluster: "xxx"
|
||||||
|
db: mydb
|
||||||
|
user: myuser
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terragrunt
|
||||||
|
|
||||||
|
Terragrunt читает `infrastructure.yaml` и передаёт данные в модули:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
locals {
|
||||||
|
infra_config = yamldecode(file("${local.repo_root}/infrastructure.yaml"))
|
||||||
|
env_config = local.infra_config.environments[local.env_name]
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
buckets = local.env_config.buckets
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Порядок применения
|
||||||
|
|
||||||
|
Модули имеют зависимости и применяются в порядке:
|
||||||
|
|
||||||
|
```
|
||||||
|
namespace → database → s3 → secrets
|
||||||
|
```
|
||||||
|
|
||||||
|
## Модули
|
||||||
|
|
||||||
|
### yc-s3
|
||||||
|
|
||||||
|
Создание S3 бакетов с изолированным доступом.
|
||||||
|
|
||||||
|
**Особенности:**
|
||||||
|
- Каждый бакет получает отдельный Service Account
|
||||||
|
- SA не имеет глобальных IAM ролей на storage
|
||||||
|
- Доступ к бакету только через bucket policy
|
||||||
|
- SA может работать только со своим бакетом
|
||||||
|
|
||||||
|
**Роли:**
|
||||||
|
- `storage.uploader` (default) — PutObject, DeleteObject, GetObject
|
||||||
|
- `storage.viewer` — GetObject, ListBucket
|
||||||
|
- `storage.editor` — все операции
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```yaml
|
||||||
|
buckets:
|
||||||
|
- name: my-bucket
|
||||||
|
acl: private
|
||||||
|
role: storage.uploader # опционально
|
||||||
|
```
|
||||||
|
|
||||||
|
### yc-database
|
||||||
|
|
||||||
|
Создание PostgreSQL users и databases в существующем кластере.
|
||||||
|
|
||||||
|
**Особенности:**
|
||||||
|
- Пароль генерируется автоматически
|
||||||
|
- Поддержка extensions
|
||||||
|
- Поддержка permissions на другие БД
|
||||||
|
- `ignore_changes` на password
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```yaml
|
||||||
|
databases:
|
||||||
|
- cluster_id: "c9qa2coo5ukgcg93fldm"
|
||||||
|
database:
|
||||||
|
name: mydb
|
||||||
|
extensions: [pg_stat_statements]
|
||||||
|
user:
|
||||||
|
name: myuser
|
||||||
|
password_length: 32
|
||||||
|
conn_limit: 10
|
||||||
|
permissions: [other_db]
|
||||||
|
```
|
||||||
|
|
||||||
|
### k8s-secret
|
||||||
|
|
||||||
|
Создание Kubernetes secrets с поддержкой зависимостей.
|
||||||
|
|
||||||
|
**Типы секретов:**
|
||||||
|
- `database` — host, port, database, username, password, ca.crt
|
||||||
|
- `s3` — access_key, secret_key, bucket, endpoint
|
||||||
|
- `dockerconfigjson` — credentials для container registry
|
||||||
|
- `opaque` — произвольные данные
|
||||||
|
|
||||||
|
**Особенности:**
|
||||||
|
- Автоматическое получение данных из database/s3 модулей
|
||||||
|
- Поддержка `random_keys` для генерации паролей
|
||||||
|
- Поддержка `custom_keys` для статических значений
|
||||||
|
- `ignore_changes` для предотвращения перезаписи
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```yaml
|
||||||
|
secrets:
|
||||||
|
- name: my-db-secret
|
||||||
|
namespace: pulse
|
||||||
|
type: database
|
||||||
|
dependencies:
|
||||||
|
cluster: "c9qa2coo5ukgcg93fldm"
|
||||||
|
db: mydb
|
||||||
|
user: myuser
|
||||||
|
random_keys:
|
||||||
|
session_key:
|
||||||
|
length: 32
|
||||||
|
special: false
|
||||||
|
lifecycle:
|
||||||
|
ignore_changes: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### k8s-namespace
|
||||||
|
|
||||||
|
Создание Kubernetes namespaces.
|
||||||
|
|
||||||
|
**Пример:**
|
||||||
|
```yaml
|
||||||
|
namespaces:
|
||||||
|
- name: pulse
|
||||||
|
labels:
|
||||||
|
environment: stage
|
||||||
|
annotations:
|
||||||
|
managed-by: terraform
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Локальный запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd terraform/live/stage/s3
|
||||||
|
terragrunt init
|
||||||
|
terragrunt plan
|
||||||
|
terragrunt apply
|
||||||
|
```
|
||||||
|
|
||||||
|
### Применение всех модулей
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd terraform/live/stage
|
||||||
|
terragrunt run-all plan
|
||||||
|
terragrunt run-all apply
|
||||||
|
```
|
||||||
|
|
||||||
|
### Переменные окружения
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export YC_TOKEN="..." # IAM токен (или YC_SERVICE_ACCOUNT_KEY_FILE)
|
||||||
|
export YC_CLOUD_ID="..."
|
||||||
|
export YC_FOLDER_ID="..." # Default folder
|
||||||
|
export YC_STAGE_FOLDER_ID="..." # Stage folder (опционально)
|
||||||
|
export KUBECONFIG="~/.kube/config"
|
||||||
|
export KUBE_CONTEXT="stage"
|
||||||
|
export S3_ACCESS_KEY="..." # Для terraform state
|
||||||
|
export S3_SECRET_KEY="..."
|
||||||
|
export DOCKER_REGISTRY_USERNAME="..." # Для dockerconfigjson secrets
|
||||||
|
export DOCKER_REGISTRY_PASSWORD="..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
|
||||||
|
Pipeline автоматически:
|
||||||
|
1. Читает изменённые файлы
|
||||||
|
2. Определяет затронутые модули
|
||||||
|
3. Запускает `terragrunt plan` / `terragrunt apply`
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Terraform >= 1.0
|
||||||
|
- Terragrunt >= 0.45
|
||||||
|
- Yandex Cloud провайдер ~> 0.100
|
||||||
|
- IAM токен с правами `storage.admin` для создания бакетов и bucket policy
|
||||||
@ -1,27 +1,20 @@
|
|||||||
# Универсальный модуль для создания S3 бакетов
|
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
buckets_map = { for bucket in var.buckets : bucket.name => bucket }
|
buckets_map = { for bucket in var.buckets : bucket.name => bucket }
|
||||||
|
|
||||||
|
role_actions = {
|
||||||
|
"storage.uploader" = ["s3:PutObject", "s3:DeleteObject", "s3:GetObject"]
|
||||||
|
"storage.viewer" = ["s3:GetObject", "s3:ListBucket"]
|
||||||
|
"storage.editor" = ["s3:*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Создание сервисного аккаунта для каждого бакета
|
|
||||||
resource "yandex_iam_service_account" "sa" {
|
resource "yandex_iam_service_account" "sa" {
|
||||||
for_each = local.buckets_map
|
for_each = local.buckets_map
|
||||||
|
|
||||||
name = "${each.key}-sa"
|
name = "${each.key}-sa"
|
||||||
description = "Service account for ${each.key} bucket"
|
description = "Service account for ${each.key} bucket (access via bucket policy only)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Назначение роли storage.editor сервисному аккаунту
|
|
||||||
resource "yandex_resourcemanager_folder_iam_member" "storage_editor" {
|
|
||||||
for_each = local.buckets_map
|
|
||||||
|
|
||||||
folder_id = var.folder_id
|
|
||||||
role = "storage.editor"
|
|
||||||
member = "serviceAccount:${yandex_iam_service_account.sa[each.key].id}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Создание статического ключа доступа
|
|
||||||
resource "yandex_iam_service_account_static_access_key" "sa_key" {
|
resource "yandex_iam_service_account_static_access_key" "sa_key" {
|
||||||
for_each = local.buckets_map
|
for_each = local.buckets_map
|
||||||
|
|
||||||
@ -29,14 +22,13 @@ resource "yandex_iam_service_account_static_access_key" "sa_key" {
|
|||||||
description = "Static access key for ${each.key} bucket"
|
description = "Static access key for ${each.key} bucket"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Создание S3 бакета
|
|
||||||
resource "yandex_storage_bucket" "this" {
|
resource "yandex_storage_bucket" "this" {
|
||||||
for_each = local.buckets_map
|
for_each = local.buckets_map
|
||||||
|
|
||||||
bucket = each.key
|
bucket = each.key
|
||||||
acl = each.value.acl
|
acl = each.value.acl
|
||||||
|
folder_id = var.folder_id
|
||||||
|
|
||||||
# Версионирование объектов
|
|
||||||
dynamic "versioning" {
|
dynamic "versioning" {
|
||||||
for_each = try(each.value.versioning.enabled, false) ? [1] : []
|
for_each = try(each.value.versioning.enabled, false) ? [1] : []
|
||||||
content {
|
content {
|
||||||
@ -44,7 +36,6 @@ resource "yandex_storage_bucket" "this" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Настройка CORS
|
|
||||||
dynamic "cors_rule" {
|
dynamic "cors_rule" {
|
||||||
for_each = try(each.value.cors.enabled, false) ? [1] : []
|
for_each = try(each.value.cors.enabled, false) ? [1] : []
|
||||||
content {
|
content {
|
||||||
@ -56,3 +47,29 @@ resource "yandex_storage_bucket" "this" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "yandex_storage_bucket_policy" "this" {
|
||||||
|
for_each = local.buckets_map
|
||||||
|
|
||||||
|
bucket = yandex_storage_bucket.this[each.key].bucket
|
||||||
|
|
||||||
|
policy = jsonencode({
|
||||||
|
Version = "2012-10-17"
|
||||||
|
Statement = [
|
||||||
|
{
|
||||||
|
Sid = "AllowServiceAccountAccess"
|
||||||
|
Effect = "Allow"
|
||||||
|
Principal = {
|
||||||
|
CanonicalUser = [yandex_iam_service_account.sa[each.key].id]
|
||||||
|
}
|
||||||
|
Action = local.role_actions[coalesce(each.value.role, var.default_bucket_role)]
|
||||||
|
Resource = [
|
||||||
|
"arn:aws:s3:::${each.key}",
|
||||||
|
"arn:aws:s3:::${each.key}/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
depends_on = [yandex_storage_bucket.this]
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
# Для обратной совместимости - первый бакет
|
|
||||||
output "bucket_name" {
|
output "bucket_name" {
|
||||||
description = "Name of the first bucket (for backward compatibility)"
|
description = "Name of the first bucket (for backward compatibility)"
|
||||||
value = length(var.buckets) > 0 ? yandex_storage_bucket.this[var.buckets[0].name].bucket : ""
|
value = length(var.buckets) > 0 ? yandex_storage_bucket.this[var.buckets[0].name].bucket : ""
|
||||||
@ -16,14 +15,13 @@ output "secret_key" {
|
|||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Новые outputs - map всех бакетов
|
|
||||||
output "buckets" {
|
output "buckets" {
|
||||||
description = "Map of all buckets (name => data)"
|
description = "Map of all buckets (name => data)"
|
||||||
value = {
|
value = {
|
||||||
for name, bucket in yandex_storage_bucket.this : name => {
|
for name, bucket in yandex_storage_bucket.this : name => {
|
||||||
bucket_name = bucket.bucket
|
bucket_name = bucket.bucket
|
||||||
access_key = yandex_iam_service_account_static_access_key.sa_key[name].access_key
|
access_key = yandex_iam_service_account_static_access_key.sa_key[name].access_key
|
||||||
secret_key = yandex_iam_service_account_static_access_key.sa_key[name].secret_key
|
secret_key = yandex_iam_service_account_static_access_key.sa_key[name].secret_key
|
||||||
service_account_id = yandex_iam_service_account.sa[name].id
|
service_account_id = yandex_iam_service_account.sa[name].id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
variable "buckets" {
|
variable "buckets" {
|
||||||
description = "List of S3 buckets to create"
|
description = "List of S3 buckets to create"
|
||||||
type = list(object({
|
type = list(object({
|
||||||
name = string
|
name = string
|
||||||
acl = optional(string, "private")
|
acl = optional(string, "private")
|
||||||
|
role = optional(string, null)
|
||||||
versioning = optional(object({
|
versioning = optional(object({
|
||||||
enabled = bool
|
enabled = bool
|
||||||
}), { enabled = false })
|
}), { enabled = false })
|
||||||
@ -21,3 +22,9 @@ variable "folder_id" {
|
|||||||
description = "Yandex Cloud folder ID"
|
description = "Yandex Cloud folder ID"
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "default_bucket_role" {
|
||||||
|
description = "Default role for SA on bucket level (storage.uploader, storage.viewer, storage.editor)"
|
||||||
|
type = string
|
||||||
|
default = "storage.uploader"
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user