# Security considerations

IAM least privilege, network boundaries, secrets model, and access control for the Wiki.js-on-EKS deployment, per `docs/plan-and-requirements` (PROJECT_PLAN and PRD_00-cross-cutting).

## Bootstrap (layer 00)

- **KMS keys:** Bootstrap provisions two KMS keys: one for the Terraform state bucket (SSE-KMS for state and lockfile) and one for AWS Secrets Manager (encryption of all secrets used by workloads and automation). Key policies restrict usage to authorized principals (Terraform execution role, Secrets Manager, S3).
- **State bucket:** S3 state bucket has versioning enabled, SSE-KMS default encryption, and bucket public access block; state locking uses S3 native lockfile only (`use_lockfile=true`).

## Platform layer (40-platform)

- **Secrets sync**: AWS Secrets Store CSI driver and Secrets Manager provider allow workloads to mount secrets from Secrets Manager without secret values in code, logs, or Terraform state. Only secret ARNs are referenced in manifests.
- **Namespace isolation**: Layer 40 creates `argocd` and `wikijs` namespaces; downstream layers (45, 50) deploy into these namespaces.
- **Observability**: Fluent Bit (CloudWatch Logs) and EKS control-plane logs provide baseline logging; node IAM role or IRSA must have CloudWatch Logs permissions for workload log collection.

## ArgoCD layer (45-argocd)

- **Access**: Argo CD server is internal-only by default (ClusterIP, no Ingress). External exposure requires an explicit, separate change.
- **Admin credentials**: By default Terraform auto-generates admin username and password and stores them in a new Secrets Manager secret; synced into the cluster via SecretProviderClass and a one-off Job (IRSA). Terraform and Parameter Store reference only the secret ARN (`argocd_admin_credentials_secret_arn`); no plaintext in code or Terraform state. To use an existing secret, set `create_auto_argocd_admin_credentials = false` and provide the secret ARN.
- **Repo credentials (private repo)**: If used, repo credentials are stored in AWS Secrets Manager only and synced into the argocd namespace via the platform Secrets Store CSI driver (SecretProviderClass). Terraform references only the secret ARN and Kubernetes secret name; no secret value in code or Terraform state.

## Secrets

- **No secrets in code, logs, or Terraform state.** All sensitive values are stored in AWS Secrets Manager. Terraform and workflows reference only ARNs or identifiers.
- **No GitHub repository-level secrets or variables** for sensitive data. The role ARN(s) are supplied as required inputs when triggering via `workflow_dispatch` (most layers: **deployment_account_role_arn**; 01-dns-main: **deployment_account_role_arn** and **domain_account_role_arn**). Dynamic values are read from Parameter Store after assuming the role.
- RDS master password: Layer 30 (30-data-rds) uses RDS managed master password so Secrets Manager holds the password; only the secret ARN is stored in Parameter Store and Terraform state (no plaintext).

## IAM

- Terraform execution role: least privilege for S3 state bucket (state and lockfile objects), KMS (state key), and SSM Parameter Store under the layer's prefix only.
- **EKS and IRSA**: Layer 20-eks enables OIDC for the cluster; downstream layers (35-storage-s3-assets, 50-app-wikijs) create IAM roles with trust policies referencing the cluster OIDC provider. Workloads assume roles via IRSA/pod identity; no long-lived keys in the cluster.
- Workload identity: IRSA/pod identity for Wiki.js access to S3 assets bucket; no long-lived keys in the cluster.
- **S3 assets bucket (layer 35)**: Bucket policy enforces TLS-only (deny `aws:SecureTransport = false`) and denies unencrypted object uploads; SSE-KMS with a dedicated KMS key. Block public access is applied. The Wiki.js IRSA role has least-privilege permissions: S3 ListBucket on the bucket and object CRUD only on the bucket prefix used by Wiki.js; KMS decrypt and GenerateDataKey on the bucket's KMS key only.
- **Cross-account DNS (domain account)**: Three roles apply: (1) **deployment_account_role_arn** - the role to assume for the deployment account (where all resources are deployed); the workflow assumes this role to run Terraform; used in all layers. (2) **domain_account_role_arn** - the role to assume for the account where the domain and hosted zone are; used only by layer 01-dns-main to create the DNS role there. (3) **dns_assume_role_arn** - the DNS role created by 01-dns-main in the domain account; its trust policy allows only **deployment_account_role_arn** to assume it; Layer 50 runs as the deployment role and assumes **dns_assume_role_arn** to create/update/delete the Route 53 A record. When dispatching 01-dns-main you provide **domain_account_role_arn** and **deployment_account_role_arn**; no broader domain-account access.

## App layer (50-app-wikijs)

- **Secrets**: RDS connection and Wiki.js app secret (session key) are synced from Secrets Manager into the `wikijs` namespace via SecretProviderClass (platform Secrets Store CSI driver). Terraform references only secret ARNs; no secret values in code or state. If no Wiki.js app secret ARN is provided, layer 50 can create a stub Secrets Manager secret (value set out-of-band).
- **IRSA**: Wiki.js service account is annotated with the IRSA role ARN from layer 35 for S3 assets bucket access; same least-privilege model as layer 35.
- **Route 53**: Only the A (alias) record for the Wiki.js FQDN is created in the domain account; no other DNS or domain-account resources.

## Network

- EKS and RDS in private subnets; ALB in public subnets. Security groups restrict RDS access to the cluster only.
- EKS cluster (layer 20-eks) uses cluster and node security groups; OIDC issuer and provider identifiers are published to Parameter Store for least-privilege IRSA role trust policies. Platform/ingress layers use cluster and node security group IDs as needed for ALB-to-pod traffic.
- VPC endpoints (e.g. S3, ECR, STS, Secrets Manager, SSM) to reduce egress and improve security where applicable.

## State and Parameter Store

- **Parameter Store:** Holds only non-secret inter-layer contract values (path: `<prefix>/<region>/<env>/<layer>/<key>`; prefix is set in each layer's `terraform.tfvars`, typically `wikijs`). No raw secrets; only ARNs or IDs when needed. Layer 01-dns-main publishes `dns_role_arn`; layer 50 retrieves it from Parameter Store during plan/apply/destroy (no CI pass-through).
- **Terraform state:** Stored in the bootstrap-provisioned S3 bucket with SSE-KMS encryption and versioning; bucket public access block applied. State locking uses S3 native lockfile only (`use_lockfile=true`); no separate locking service. Terraform must not materialize secrets so that plaintext values land in state.

## Access control

- Destroy workflows are `workflow_dispatch` only and require a typed confirmation string. Use GitHub Environment required reviewers on `tf-<env>-<layer>` to gate destroy.
