Giving up on IAM. 15 minute tokens aint worth it.
This commit is contained in:
parent
8cf322e671
commit
777fe555a0
11 changed files with 74 additions and 237 deletions
cloud/aws/rds_serverless
|
@ -6,6 +6,16 @@ resource "random_pet" "admin_user" {
|
|||
count = var.admin_username == null ? 1 : 0
|
||||
separator = "_"
|
||||
}
|
||||
variable "admin_password" {
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
resource "random_password" "admin_pass" {
|
||||
count = var.admin_username == null ? 1 : 0
|
||||
special = false
|
||||
length = 32
|
||||
}
|
||||
locals {
|
||||
admin_username = coalesce(var.admin_username, random_pet.admin_user[0].id)
|
||||
admin_password = nonsensitive(coalesce(var.admin_password, random_password.admin_pass[0].result))
|
||||
}
|
|
@ -13,8 +13,8 @@ resource "local_file" "debug" {
|
|||
read = aws_rds_cluster_endpoint.endpoint["read"].endpoint
|
||||
}
|
||||
admin = {
|
||||
username = local.admin.username
|
||||
password = local.admin.password
|
||||
username = local.admin_username
|
||||
password = local.admin_password
|
||||
}
|
||||
}
|
||||
tenants = {
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
data "aws_iam_policy_document" "assume_role_policy" {
|
||||
statement {
|
||||
actions = ["sts:AssumeRole"]
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["ec2.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
data "aws_iam_policy_document" "rds_instance_policy" {
|
||||
statement {
|
||||
actions = ["iam:GetGroup"]
|
||||
effect = "Allow"
|
||||
resources = ["*"]
|
||||
}
|
||||
statement {
|
||||
actions = ["iam:GetSSHPublicKey", "iam:ListSSHPublicKeys"]
|
||||
effect = "Allow"
|
||||
resources = ["arn:aws:iam::*:user/*"]
|
||||
}
|
||||
statement {
|
||||
actions = ["ec2:*"]
|
||||
effect = "Allow"
|
||||
resources = ["*"]
|
||||
}
|
||||
statement {
|
||||
actions = ["rds:*"]
|
||||
effect = "Allow"
|
||||
resources = ["*"]
|
||||
}
|
||||
statement {
|
||||
actions = ["secretsmanager:*"]
|
||||
effect = "Allow"
|
||||
resources = ["*"]
|
||||
}
|
||||
}
|
||||
resource "aws_iam_policy" "rds_instance_policy" {
|
||||
policy = data.aws_iam_policy_document.rds_instance_policy.json
|
||||
name = join("", [local.app_name, "RDS", "InstancePolicy"])
|
||||
path = "/${join("/", [local.app_name, "RDS", "InstancePolicy"])}/"
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
resource "aws_iam_role" "rds_instance_policy" {
|
||||
assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
|
||||
name = join("", [local.app_name, "RDS", ])
|
||||
path = "/${join("/", [local.app_name, "RDS", ])}/"
|
||||
force_detach_policies = true
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_iam_policy_attachment" "rds_instance_policy" {
|
||||
name = aws_iam_policy.rds_instance_policy.name
|
||||
policy_arn = aws_iam_policy.rds_instance_policy.arn
|
||||
roles = [aws_iam_role.rds_instance_policy.name]
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
module "tenants" {
|
||||
depends_on = [aws_rds_cluster.cluster, aws_rds_cluster_instance.instance]
|
||||
for_each = var.tenants
|
||||
source = "./tenant"
|
||||
username = each.value.username
|
||||
database = each.value.database
|
||||
app_name = local.app_name
|
||||
vpc_id = data.aws_vpc.current.id
|
||||
aws_profile = var.aws_profile
|
||||
cluster_id = aws_rds_cluster.cluster.id
|
||||
super_user_iam_role_name = aws_iam_role.rds_instance_policy.name
|
||||
engine = aws_rds_cluster.cluster.engine
|
||||
admin_username = local.admin.username
|
||||
admin_password = local.admin.password
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{
|
||||
"TerraformRDSClusterName" = var.instance_name
|
||||
"TerraformRDSTenantName" = each.value.username
|
||||
}
|
||||
)
|
||||
}
|
|
@ -3,13 +3,17 @@ locals {
|
|||
for key, tenant in module.tenants : key => {
|
||||
username = tenant.username
|
||||
database = tenant.database
|
||||
access_key = tenant.access_key
|
||||
secret_key = tenant.secret_key
|
||||
auth_token = tenant.auth_token
|
||||
password = tenant.password
|
||||
connection_string = tenant.connection_string
|
||||
}
|
||||
}
|
||||
}
|
||||
output "tenants" {
|
||||
value = local.output_tenants
|
||||
}
|
||||
output "admin" {
|
||||
value = {
|
||||
username = local.admin_username
|
||||
password = local.admin_password
|
||||
}
|
||||
}
|
|
@ -15,15 +15,6 @@ resource "aws_kms_key" "db_key" {
|
|||
}
|
||||
)
|
||||
}
|
||||
resource "aws_kms_key" "master_key" {
|
||||
description = "RDS ${var.instance_name} Master Account Key"
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{
|
||||
TerraformSecretType = "RDSMasterAccountKey"
|
||||
}
|
||||
)
|
||||
}
|
||||
resource "aws_rds_cluster" "cluster" {
|
||||
cluster_identifier = local.sanitised_name
|
||||
engine_mode = "provisioned"
|
||||
|
@ -31,8 +22,7 @@ resource "aws_rds_cluster" "cluster" {
|
|||
engine_version = data.aws_rds_engine_version.latest.version
|
||||
database_name = local.admin_username
|
||||
master_username = local.admin_username
|
||||
manage_master_user_password = true
|
||||
master_user_secret_kms_key_id = aws_kms_key.master_key.arn
|
||||
master_password = local.admin_password
|
||||
storage_encrypted = true
|
||||
enable_local_write_forwarding = true
|
||||
backup_retention_period = var.backup_retention_period_days
|
||||
|
@ -61,17 +51,6 @@ resource "aws_rds_cluster" "cluster" {
|
|||
)
|
||||
}
|
||||
|
||||
data "aws_secretsmanager_secret" "admin" {
|
||||
arn = join("", aws_rds_cluster.cluster.master_user_secret.*.secret_arn)
|
||||
}
|
||||
data "aws_secretsmanager_secret_version" "admin" {
|
||||
secret_id = data.aws_secretsmanager_secret.admin.id
|
||||
version_stage = "AWSCURRENT"
|
||||
}
|
||||
locals {
|
||||
admin = nonsensitive(jsondecode(data.aws_secretsmanager_secret_version.admin.secret_string))
|
||||
}
|
||||
|
||||
resource "aws_rds_cluster_instance" "instance" {
|
||||
cluster_identifier = aws_rds_cluster.cluster.id
|
||||
identifier_prefix = "${local.sanitised_name}-"
|
||||
|
|
|
@ -4,8 +4,8 @@ locals {
|
|||
host = data.aws_rds_cluster.cluster.endpoint
|
||||
port = local.is_mysql ? 3306 : 5432
|
||||
}
|
||||
mysql_command = "${var.mysql_binary} -h ${data.ssh_tunnel.db.local.host} -P ${data.ssh_tunnel.db.local.port} -u ${var.admin_username}"
|
||||
postgres_command = "${var.postgres_binary} -h ${data.ssh_tunnel.db.local.host} -p ${data.ssh_tunnel.db.local.port} -U ${var.admin_username} -d ${var.admin_username}"
|
||||
mysql_command = try("${var.mysql_binary} -h ${data.ssh_tunnel.db.local.host} -P ${data.ssh_tunnel.db.local.port} -u ${var.admin_username}", "")
|
||||
postgres_command = try("${var.postgres_binary} -h ${data.ssh_tunnel.db.local.host} -p ${data.ssh_tunnel.db.local.port} -U ${var.admin_username} -d ${var.admin_username}", "")
|
||||
database_environment_variables = {
|
||||
PGPASSWORD = !local.is_mysql ? var.admin_password : null,
|
||||
MYSQL_PWD = local.is_mysql ? var.admin_password : null,
|
||||
|
@ -40,32 +40,39 @@ resource "terraform_data" "db" {
|
|||
)
|
||||
environment = local.database_environment_variables
|
||||
}
|
||||
#provisioner "local-exec" {
|
||||
# command = (local.is_mysql
|
||||
# ? "echo \"CREATE USER IF NOT EXISTS '${var.username}' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS' | ${local.mysql_command}"
|
||||
# : "echo \"CREATE USER ${var.username}; GRANT rds_iam TO ${var.username}\" | ${local.postgres_command}"
|
||||
# )
|
||||
# environment = local.database_environment_variables
|
||||
#}
|
||||
provisioner "local-exec" {
|
||||
command = (local.is_mysql
|
||||
? "echo \"CREATE USER IF NOT EXISTS '${var.username}' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS' | ${local.mysql_command}"
|
||||
: "echo \"CREATE USER ${var.username}; GRANT rds_iam TO ${var.username}\" | ${local.postgres_command}"
|
||||
? "echo \"CREATE USER IF NOT EXISTS '${local.username}' IDENTIFIED BY '${local.password}'\" | ${local.mysql_command}"
|
||||
: "echo \"CREATE USER ${local.username} WITH PASSWORD '${local.password}; \" | ${local.postgres_command}"
|
||||
)
|
||||
environment = local.database_environment_variables
|
||||
}
|
||||
provisioner "local-exec" {
|
||||
command = (local.is_mysql
|
||||
? "GRANT ALL PRIVILEGES ON ${var.database}.* TO '${var.username}'@'%'\""
|
||||
: ""
|
||||
? "echo \"GRANT ALL PRIVILEGES ON ${local.database}.* TO '${local.username}'@'%'\" | ${local.mysql_command}"
|
||||
: "echo \"ALTER DATABASE ${local.database} OWNER TO ${local.username}\" | ${local.postgres_command}"
|
||||
)
|
||||
environment = local.database_environment_variables
|
||||
}
|
||||
#provisioner "local-exec" {
|
||||
# when = destroy
|
||||
# command = (local.is_mysql
|
||||
# ? "DROP USER '${var.username}'@'%';"
|
||||
# : "DROP USER ${var.username};"
|
||||
# ? "DROP USER '${local.username}'@'%';"
|
||||
# : "DROP USER ${local.username};"
|
||||
# )
|
||||
#}
|
||||
#provisioner "local-exec" {
|
||||
# when = destroy
|
||||
# command = (local.is_mysql
|
||||
# ? "echo 'DROP DATABASE ${var.database}' | ${local.mysql_command}"
|
||||
# : "echo 'DROP DATABASE ${var.database}' | ${local.postgres_command}"
|
||||
# ? "echo 'DROP DATABASE ${local.database}' | ${local.mysql_command}"
|
||||
# : "echo 'DROP DATABASE ${local.database}' | ${local.postgres_command}"
|
||||
# )
|
||||
#}
|
||||
}
|
|
@ -13,6 +13,16 @@ variable "username" {
|
|||
type = string
|
||||
description = "The username for the tenant"
|
||||
}
|
||||
variable "password" {
|
||||
type = string
|
||||
description = "The password for the tenant"
|
||||
default = null
|
||||
}
|
||||
resource "random_password" "password" {
|
||||
count = var.password == null ? 1 : 0
|
||||
special = false
|
||||
length = 32
|
||||
}
|
||||
variable "database" {
|
||||
type = string
|
||||
description = "The database for the tenant"
|
||||
|
@ -20,6 +30,7 @@ variable "database" {
|
|||
locals {
|
||||
username = lower(var.username)
|
||||
database = lower(var.database)
|
||||
password = try(random_password.password[0].result, var.password)
|
||||
}
|
||||
variable "app_name" {
|
||||
type = string
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
output "username" {
|
||||
value = local.username
|
||||
}
|
||||
output "password" {
|
||||
value = local.password
|
||||
}
|
||||
output "database" {
|
||||
value = local.database
|
||||
}
|
||||
output "access_key" {
|
||||
value = aws_iam_access_key.tenants.id
|
||||
}
|
||||
output "secret_key" {
|
||||
value = aws_iam_access_key.tenants.secret
|
||||
}
|
||||
output "auth_token" {
|
||||
value = data.external.rds_auth_token.result.password
|
||||
}
|
||||
|
||||
output "connection_string" {
|
||||
value = join(" ", [
|
||||
"mysql",
|
||||
|
@ -20,6 +15,6 @@ output "connection_string" {
|
|||
"-P", data.aws_rds_cluster.cluster.port,
|
||||
"-D", local.database,
|
||||
"-u", local.username,
|
||||
"-p'${data.external.rds_auth_token.result.password}'",
|
||||
"-p'${local.password}'",
|
||||
])
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
data "aws_arn" "tenant" {
|
||||
arn = join(":", [
|
||||
"arn", "aws", "rds-db",
|
||||
data.aws_region.current.name,
|
||||
data.aws_caller_identity.current.account_id,
|
||||
"dbuser",
|
||||
"${lower(data.aws_rds_cluster.cluster.cluster_resource_id)}/${local.username}"
|
||||
])
|
||||
}
|
||||
data "aws_iam_policy_document" "assume_role_policy" {
|
||||
statement {
|
||||
actions = ["sts:AssumeRole"]
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["ec2.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
data "aws_iam_policy_document" "tenant_user_policy_document" {
|
||||
statement {
|
||||
actions = ["rds-db:*"]
|
||||
effect = "Allow"
|
||||
resources = [data.aws_arn.tenant.arn]
|
||||
}
|
||||
}
|
||||
|
||||
# Create IAM and key for each tenant
|
||||
resource "aws_iam_user" "tenant" {
|
||||
name = join("", ["RDS", var.app_name, title(local.username)])
|
||||
tags = var.tags
|
||||
path = "/${join("/", [var.app_name, "RDS", title(local.username), ])}/"
|
||||
}
|
||||
|
||||
data "external" "rds_auth_token" {
|
||||
program = [
|
||||
"bash", "-c", replace(
|
||||
<<-EOF
|
||||
aws
|
||||
--profile ${var.aws_profile}
|
||||
rds
|
||||
generate-db-auth-token
|
||||
--hostname ${data.aws_rds_cluster.cluster.endpoint}
|
||||
--port ${data.aws_rds_cluster.cluster.port}
|
||||
--region ${replace(data.aws_region.current.name, "/[[:lower:]]$/", "")}
|
||||
--username ${local.username}
|
||||
| jq --raw-input '{ password: . }'
|
||||
EOF
|
||||
, "\n", " ")
|
||||
]
|
||||
}
|
||||
|
||||
# Here's that key we were just talking about
|
||||
resource "aws_iam_access_key" "tenants" {
|
||||
user = aws_iam_user.tenant.name
|
||||
status = var.is_active ? "Active" : "Inactive"
|
||||
}
|
||||
|
||||
# Make a role for the IAM user to assume
|
||||
resource "aws_iam_role" "rds_instance_policy_per_user" {
|
||||
assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
|
||||
name = join("", [var.app_name, "RDS", "Tenant", title(local.username), ])
|
||||
path = "/${join("/", [var.app_name, "RDS", "Tenant", title(local.username), ])}/"
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# Create a policy per-tenant
|
||||
resource "aws_iam_policy" "tenant_policy" {
|
||||
name = join("", [var.app_name, "RDS", title(local.username), "TenantUserPolicy"])
|
||||
path = "/RDS/${var.app_name}/${local.username}/"
|
||||
description = "Allow user ${local.username} Data Access to RDS database ${local.database}"
|
||||
policy = data.aws_iam_policy_document.tenant_user_policy_document.json
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# find the superuser role if supplied
|
||||
data "aws_iam_role" "superuser" {
|
||||
count = var.super_user_iam_role_name != null ? 1 : 0
|
||||
name = var.super_user_iam_role_name
|
||||
}
|
||||
# Attach the tenant policy role to the tenants role
|
||||
resource "aws_iam_policy_attachment" "tenant_policy_attachment" {
|
||||
name = join("", [var.app_name, "RDS", title(local.username), "TenantUserPolicyAttachment"])
|
||||
policy_arn = aws_iam_policy.tenant_policy.arn
|
||||
roles = compact([
|
||||
aws_iam_role.rds_instance_policy_per_user.name,
|
||||
try(data.aws_iam_role.superuser[0].name, null)
|
||||
])
|
||||
}
|
||||
|
||||
# Create the tenants own group
|
||||
resource "aws_iam_group" "tenant" {
|
||||
name = join("", [var.app_name, "RDS", title(local.username), "TenantGroup"])
|
||||
path = "/${join("/", [var.app_name, "RDS", title(local.username), ])}/"
|
||||
}
|
||||
|
||||
# Attach the tenant role to the tenant group
|
||||
resource "aws_iam_group_policy_attachment" "tenant_policy_attachment" {
|
||||
group = aws_iam_group.tenant.name
|
||||
policy_arn = aws_iam_policy.tenant_policy.arn
|
||||
}
|
||||
|
||||
# Attach the tenant user to the tenant group
|
||||
resource "aws_iam_group_membership" "tenant" {
|
||||
name = join("", [var.app_name, "RDS", title(local.username), "TenantGroupMembership"])
|
||||
group = aws_iam_group.tenant.name
|
||||
users = [aws_iam_user.tenant.name]
|
||||
}
|
21
cloud/aws/rds_serverless/tenants.tf
Normal file
21
cloud/aws/rds_serverless/tenants.tf
Normal file
|
@ -0,0 +1,21 @@
|
|||
module "tenants" {
|
||||
depends_on = [aws_rds_cluster.cluster, aws_rds_cluster_instance.instance]
|
||||
for_each = var.tenants
|
||||
source = "./tenant"
|
||||
username = each.value.username
|
||||
database = each.value.database
|
||||
app_name = local.app_name
|
||||
vpc_id = data.aws_vpc.current.id
|
||||
aws_profile = var.aws_profile
|
||||
cluster_id = aws_rds_cluster.cluster.id
|
||||
engine = aws_rds_cluster.cluster.engine
|
||||
admin_username = local.admin_username
|
||||
admin_password = local.admin_password
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{
|
||||
"TerraformRDSClusterName" = var.instance_name
|
||||
"TerraformRDSTenantName" = each.value.username
|
||||
}
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue