mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-06-13 23:36:45 +03:00
a10607f90a
* terraform: add cloud-agnostic core renderer module Renders per-node weed argv, systemd units, config files, disk-mount and secret-fetch scripts, and cloud-init from an address map. Creates zero cloud resources. Flags verified against the weed binary: volume uses -mserver for the master list, gRPC is -port.grpc (auto http+10000), minFreeSpacePercent is a string, filer store via -defaultStoreDir. * terraform: add mTLS and JWT security module Generates the CA, per-component certs with distinct CNs, and JWT signing keys via the tls/random providers. Emits a core_security object plus PEMs for secret-store delivery. * terraform: add AWS deployment module and examples Reserves stable ENIs first, renders config via the core, then creates instances, prevent_destroy EBS data disks mounted at /data, and the cluster security group. With enable_security, generates certs/JWT, stores them in SSM SecureString, grants an instance role, and fetches them at boot so secrets stay out of user_data. Keyed for_each on every stateful tier. * terraform: add local cluster test harnesses run_local_cluster.sh and run_local_secure.sh render a cluster with the core and run real weed processes, asserting master quorum, volume registration, filer/s3 round-trips, mutual-TLS formation, and JWT enforcement. Use an isolated high port range with a guard so they never touch a cluster already running on the machine. The weed binary defaults to $(go env GOPATH)/bin/weed. * terraform: add CI workflow and README fmt/validate/tofu-test plus smoke jobs that build weed and run both harnesses. * terraform: guard against empty filesystem UUID in mount script An empty UUID made grep -q match any fstab line, skipping the fstab entry and breaking the mount. Fail fast when blkid returns no UUID. * terraform: sanitize cluster name in WEED_CLUSTER env keys Hyphens or spaces in cluster_name produced invalid systemd/bash env var names; map non-alphanumerics to underscores. * terraform: omit empty jwt.signing block from security.toml With enable_security and no JWT key, the template emitted [jwt.signing] key="". Gate the block on a non-empty key and cover it with a test. * terraform: mark core security input as sensitive The security object carries JWT signing keys; keep them out of plan output and known values. * terraform: enforce jwt_length minimum of 32 * terraform: note region/AZ coupling in HA example * terraform: guard WORKDIR before recursive delete in test harnesses * terraform: fix README fence language and test count * terraform: handle embedded s3 with no filer nodes Indexing sort(keys(var.filers))[0] errored at plan time when embedded S3 was enabled but no filers were defined; fall back to an empty config source. * terraform: scope kms:Decrypt to a configurable key arn Replace the hardcoded Resource="*" with a kms_key_arn variable (default "*") so production can restrict decrypt to a specific CMK. * terraform: encrypt EBS data volumes at rest Set encrypted = true on the volume/filer data disks and the all-in-one example disk. * terraform: protect filer instances from API termination Filers hold the leveldb2 metadata store, so they are stateful and get the same disable_api_termination as masters and volumes. * terraform: stop instance before detaching in all-in-one example * terraform: drop stale references to the removed plan doc * terraform: correct stale mount-step comment in aws module * terraform: mark Terraform support as experimental in README
137 lines
5.1 KiB
Terraform
137 lines
5.1 KiB
Terraform
# =============================================================================
|
|
# mTLS + JWT generation and secret delivery (active when enable_security=true).
|
|
#
|
|
# Generates the CA + per-component certs + JWT keys with the security submodule,
|
|
# stores them (plus the rendered security.toml / S3 config) in SSM Parameter
|
|
# Store as SecureString, and renders a boot fetch-secrets.sh that pulls them via
|
|
# the instance role. This keeps secrets OUT of cloud-init user_data / IMDS.
|
|
# Secrets still live in Terraform state (TF-generated); prefer Vault PKI for the
|
|
# CA in production.
|
|
# =============================================================================
|
|
|
|
locals {
|
|
all_private_ips = concat(
|
|
[for k, v in var.masters : v.private_ip],
|
|
[for k, v in var.volumes : v.private_ip],
|
|
[for k, v in var.filers : v.private_ip],
|
|
[for k, v in var.s3_nodes : v.private_ip],
|
|
)
|
|
|
|
ssm_prefix = "/seaweedfs/${var.name}"
|
|
deliver_s3_config = length(var.s3_nodes) > 0 || var.embedded_s3.enabled
|
|
|
|
# security.toml is identical across nodes; take it from the first master.
|
|
first_master = var.enable_security ? sort(keys(var.masters))[0] : ""
|
|
security_toml_path = "/etc/seaweedfs/security.toml"
|
|
s3_config_path = "/etc/seaweedfs/s3_config.json"
|
|
|
|
# Boot fetch entries: cert files + security.toml + (optional) S3 config.
|
|
fetch_entries = var.enable_security ? concat(
|
|
[for m in module.security[0].secret_manifest : { param = "${local.ssm_prefix}/certs/${m.key}", path = m.path, mode = m.mode }],
|
|
[{ param = "${local.ssm_prefix}/security.toml", path = local.security_toml_path, mode = "0600" }],
|
|
local.deliver_s3_config ? [{ param = "${local.ssm_prefix}/s3_config.json", path = local.s3_config_path, mode = "0600" }] : [],
|
|
) : []
|
|
|
|
fetch_script = var.enable_security ? templatefile("${path.module}/templates/fetch-secrets.sh.tftpl", {
|
|
run_as_user = "seaweedfs"
|
|
entries = local.fetch_entries
|
|
}) : ""
|
|
|
|
core_security = var.enable_security ? module.security[0].core_security : var.security
|
|
}
|
|
|
|
module "security" {
|
|
count = var.enable_security ? 1 : 0
|
|
source = "../security"
|
|
internal_domain = var.internal_domain
|
|
cert_dir = var.cert_dir
|
|
ip_sans = local.all_private_ips
|
|
}
|
|
|
|
# ---- SSM SecureString parameters -------------------------------------------
|
|
resource "aws_ssm_parameter" "cert" {
|
|
for_each = var.enable_security ? { for m in module.security[0].secret_manifest : m.key => m } : {}
|
|
name = "${local.ssm_prefix}/certs/${each.key}"
|
|
type = "SecureString"
|
|
value = module.security[0].secret_contents[each.key]
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "security_toml" {
|
|
count = var.enable_security ? 1 : 0
|
|
name = "${local.ssm_prefix}/security.toml"
|
|
type = "SecureString"
|
|
value = module.core.secret_files_by_node["master-${local.first_master}"][local.security_toml_path]
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "s3_config" {
|
|
count = var.enable_security && local.deliver_s3_config ? 1 : 0
|
|
name = "${local.ssm_prefix}/s3_config.json"
|
|
type = "SecureString"
|
|
# identical content across s3/filer nodes; render once via the security module's
|
|
# JSON is not available here, so read it back from any node that carries it.
|
|
value = local.s3_config_source
|
|
tags = var.tags
|
|
}
|
|
|
|
locals {
|
|
# Pull the rendered S3 identity JSON from whichever node carries it.
|
|
s3_config_source = !local.deliver_s3_config ? "" : (
|
|
length(var.s3_nodes) > 0 ?
|
|
module.core.secret_files_by_node["s3-${sort(keys(var.s3_nodes))[0]}"][local.s3_config_path] :
|
|
(length(var.filers) > 0 ? module.core.secret_files_by_node["filer-${sort(keys(var.filers))[0]}"][local.s3_config_path] : "")
|
|
)
|
|
}
|
|
|
|
# ---- IAM: instance role allowed to read the SSM params ----------------------
|
|
data "aws_iam_policy_document" "assume" {
|
|
count = var.enable_security ? 1 : 0
|
|
statement {
|
|
actions = ["sts:AssumeRole"]
|
|
principals {
|
|
type = "Service"
|
|
identifiers = ["ec2.amazonaws.com"]
|
|
}
|
|
}
|
|
}
|
|
|
|
data "aws_iam_policy_document" "ssm_read" {
|
|
count = var.enable_security ? 1 : 0
|
|
statement {
|
|
sid = "ReadSeaweedfsParams"
|
|
actions = ["ssm:GetParameter", "ssm:GetParameters"]
|
|
resources = ["arn:aws:ssm:*:*:parameter${local.ssm_prefix}/*"]
|
|
}
|
|
statement {
|
|
sid = "DecryptSecureStrings"
|
|
actions = ["kms:Decrypt"]
|
|
resources = [var.kms_key_arn] # defaults to "*"; set a CMK ARN in production
|
|
condition {
|
|
test = "StringEquals"
|
|
variable = "kms:ViaService"
|
|
values = ["ssm.*.amazonaws.com"]
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_iam_role" "node" {
|
|
count = var.enable_security ? 1 : 0
|
|
name_prefix = "${var.name}-node-"
|
|
assume_role_policy = data.aws_iam_policy_document.assume[0].json
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "ssm_read" {
|
|
count = var.enable_security ? 1 : 0
|
|
name = "ssm-read"
|
|
role = aws_iam_role.node[0].id
|
|
policy = data.aws_iam_policy_document.ssm_read[0].json
|
|
}
|
|
|
|
resource "aws_iam_instance_profile" "node" {
|
|
count = var.enable_security ? 1 : 0
|
|
name_prefix = "${var.name}-node-"
|
|
role = aws_iam_role.node[0].name
|
|
}
|