The roles all look right but why can't I login?
This commit is contained in:
parent
7392ccc891
commit
8cf322e671
19 changed files with 416 additions and 254 deletions
|
@ -12,9 +12,9 @@ resource "local_file" "debug" {
|
|||
write = aws_rds_cluster_endpoint.endpoint["write"].endpoint,
|
||||
read = aws_rds_cluster_endpoint.endpoint["read"].endpoint
|
||||
}
|
||||
tunnel = {
|
||||
remote = local.db_tunnel_remote
|
||||
local = data.ssh_tunnel.db.local
|
||||
admin = {
|
||||
username = local.admin.username
|
||||
password = local.admin.password
|
||||
}
|
||||
}
|
||||
tenants = {
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
data "aws_iam_policy_document" "ec2_connect_document" {
|
||||
statement {
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["ec2.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
data "aws_iam_policy_document" "aws_policy_document" {
|
||||
|
||||
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 = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "user_connect_policy_document" {
|
||||
for_each = var.tenants
|
||||
statement {
|
||||
actions = ["rds-db:connect"]
|
||||
effect = "Allow"
|
||||
resources = ["arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${aws_rds_cluster_instance.instance.cluster_identifier}/${each.value.username}"]
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "read_only_policy" {
|
||||
for_each = var.tenants
|
||||
statement {
|
||||
actions = ["rds-db:connect"]
|
||||
effect = "Allow"
|
||||
resources = ["arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${aws_rds_cluster_instance.instance.cluster_identifier}/${each.value.username}"]
|
||||
}
|
||||
}
|
||||
data "aws_iam_policy_document" "read_write_policy" {
|
||||
for_each = var.tenants
|
||||
statement {
|
||||
actions = ["rds-db:connect"]
|
||||
effect = "Allow"
|
||||
resources = ["arn:aws:rds-db:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:dbuser:${aws_rds_cluster_instance.instance.cluster_identifier}/${each.value.username}"]
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
resource "aws_iam_policy" "user_connect_policy" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", var.instance_name, each.value.username, "UserConnectPolicy"])
|
||||
path = "/RDS/${var.instance_name}/${each.value.username}/"
|
||||
description = "Allow DB Access to EC2"
|
||||
policy = data.aws_iam_policy_document.user_connect_policy_document[each.key].json
|
||||
}
|
||||
resource "aws_iam_policy" "ec2_connect_policy" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", var.instance_name, each.value.username, "EC2ConnectPolicy"])
|
||||
path = "/RDS/${var.instance_name}/${each.value.username}/"
|
||||
description = "Allow DB Access to EC2"
|
||||
policy = data.aws_iam_policy_document.aws_policy_document.json
|
||||
}
|
||||
|
||||
# Read only access policy
|
||||
resource "aws_iam_policy" "read_only_policy" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", title(var.instance_name), each.value.username, "ReadOnlyPolicy"])
|
||||
path = "/RDS/${var.instance_name}/${each.value.username}/"
|
||||
description = "Allow Read DB Access to EC2"
|
||||
policy = data.aws_iam_policy_document.read_only_policy[each.key].json
|
||||
}
|
||||
|
||||
# Read-Write access policy
|
||||
resource "aws_iam_policy" "read_write_policy" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", title(var.instance_name), each.value.username, "ReadWritePolicy"])
|
||||
policy = data.aws_iam_policy_document.read_write_policy[each.key].json
|
||||
path = "/RDS/${var.instance_name}/${each.value.username}/"
|
||||
description = "Allow Write DB access to EC2"
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
resource "aws_iam_role" "rds" {
|
||||
assume_role_policy = data.aws_iam_policy_document.ec2_connect_document.json
|
||||
name = join("-", ["RDS", title(var.instance_name), ])
|
||||
path = "/${join("/", [var.application.name, "RDS", ])}/"
|
||||
force_detach_policies = true
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
resource "aws_iam_policy_attachment" "rds_to_tenants" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", title(var.instance_name), title(each.value.username), "SupervisorConnectPolicy"])
|
||||
roles = [aws_iam_role.rds.name]
|
||||
policy_arn = aws_iam_policy.user_connect_policy[each.key].arn
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
resource "aws_iam_role" "ec2_access" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", title(var.instance_name), title(each.value.username), "EC2Access"])
|
||||
path = aws_iam_role.rds.path
|
||||
force_detach_policies = true
|
||||
assume_role_policy = data.aws_iam_policy_document.ec2_connect_document.json
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "user_access" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", title(var.instance_name), title(each.value.username), "UserAccess"])
|
||||
path = aws_iam_role.rds.path
|
||||
force_detach_policies = true
|
||||
assume_role_policy = jsonencode({
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [{
|
||||
"Effect" : "Allow",
|
||||
"Principal" : {
|
||||
"AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
|
||||
},
|
||||
"Action" : ["sts:AssumeRole"]
|
||||
}]
|
||||
})
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
resource "aws_iam_policy_attachment" "policy" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", title(var.instance_name), title(each.value.username), "Policy"])
|
||||
policy_arn = aws_iam_policy.read_only_policy[each.key].arn
|
||||
roles = [
|
||||
aws_iam_role.ec2_access[each.key].name,
|
||||
aws_iam_role.user_access[each.key].name
|
||||
]
|
||||
}
|
||||
resource "aws_iam_group" "user_access" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", title(var.instance_name), title(each.value.username), "UserAccess"])
|
||||
}
|
||||
resource "aws_iam_group_policy" "user_access" {
|
||||
for_each = var.tenants
|
||||
name = join("-", ["RDS", title(var.instance_name), title(each.value.username), "ConnectPolicy"])
|
||||
policy = data.aws_iam_policy_document.user_connect_policy_document[each.key].json
|
||||
group = aws_iam_group.user_access[each.key].name
|
||||
}
|
61
cloud/aws/rds_serverless/iam.system.tf
Normal file
61
cloud/aws/rds_serverless/iam.system.tf
Normal file
|
@ -0,0 +1,61 @@
|
|||
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]
|
||||
}
|
22
cloud/aws/rds_serverless/iam.tenants.tf
Normal file
22
cloud/aws/rds_serverless/iam.tenants.tf
Normal file
|
@ -0,0 +1,22 @@
|
|||
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
|
||||
}
|
||||
)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
resource "aws_iam_user" "tenants" {
|
||||
for_each = var.tenants
|
||||
name = join("-", [var.instance_name, each.value.username])
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
resource "aws_iam_access_key" "tenants" {
|
||||
for_each = var.tenants
|
||||
user = aws_iam_user.tenants[each.key].name
|
||||
status = each.value.active ? "Active" : "Inactive"
|
||||
}
|
||||
resource "aws_iam_group_membership" "tenants" {
|
||||
for_each = var.tenants
|
||||
group = aws_iam_group.user_access[each.key].name
|
||||
name = join("-", ["RDS", var.instance_name, "ReadWrite"])
|
||||
users = [for tenant in aws_iam_user.tenants : tenant.name]
|
||||
}
|
||||
|
||||
locals {
|
||||
output_tenants = {
|
||||
for tenant in var.tenants : tenant.username => {
|
||||
username = tenant.username,
|
||||
database = tenant.database,
|
||||
access_key = aws_iam_access_key.tenants[tenant.username].id,
|
||||
secret_key = aws_iam_access_key.tenants[tenant.username].secret
|
||||
}
|
||||
}
|
||||
}
|
||||
output "tenants" {
|
||||
value = local.output_tenants
|
||||
precondition {
|
||||
condition = length(aws_iam_user.tenants) > 0
|
||||
error_message = "No tenants found"
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ variable "instance_name" {
|
|||
}
|
||||
locals {
|
||||
sanitised_name = lower(replace(var.instance_name, "[^a-zA-Z0-9]", "-"))
|
||||
titled_name = replace(title(join(" ", split("-", local.sanitised_name))), " ", "")
|
||||
app_name = try(var.application.name, local.titled_name)
|
||||
}
|
||||
variable "tenants" {
|
||||
type = map(object({
|
||||
|
@ -24,6 +26,11 @@ variable "application" {
|
|||
})
|
||||
default = null
|
||||
}
|
||||
variable "aws_profile" {
|
||||
type = string
|
||||
description = "AWS profile to use for generating RDS auth token"
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "engine" {
|
||||
type = string
|
||||
|
@ -82,14 +89,3 @@ variable "enable_performance_insights" {
|
|||
default = false
|
||||
}
|
||||
|
||||
|
||||
variable "mysql_binary" {
|
||||
type = string
|
||||
description = "The path to the mysql binary"
|
||||
default = "mariadb"
|
||||
}
|
||||
variable "postgres_binary" {
|
||||
type = string
|
||||
description = "The path to the postgres binary"
|
||||
default = "psql"
|
||||
}
|
15
cloud/aws/rds_serverless/outputs.tf
Normal file
15
cloud/aws/rds_serverless/outputs.tf
Normal file
|
@ -0,0 +1,15 @@
|
|||
locals {
|
||||
output_tenants = {
|
||||
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
|
||||
connection_string = tenant.connection_string
|
||||
}
|
||||
}
|
||||
}
|
||||
output "tenants" {
|
||||
value = local.output_tenants
|
||||
}
|
|
@ -109,36 +109,6 @@ resource "aws_rds_cluster_endpoint" "endpoint" {
|
|||
)
|
||||
}
|
||||
|
||||
locals {
|
||||
db_tunnel_remote = {
|
||||
host = aws_rds_cluster_endpoint.endpoint["write"].endpoint
|
||||
port = var.engine == "aurora-postgres" ? 5432 : (var.engine == "aurora-mysql" ? 3306 : null)
|
||||
}
|
||||
}
|
||||
data "ssh_tunnel" "db" {
|
||||
connection_name = "db-${var.engine}"
|
||||
remote = local.db_tunnel_remote
|
||||
}
|
||||
resource "null_resource" "db" {
|
||||
for_each = var.tenants
|
||||
depends_on = [aws_rds_cluster_instance.instance]
|
||||
provisioner "local-exec" {
|
||||
command = "echo 'Connecting to \"${local.db_tunnel_remote.host}:${local.db_tunnel_remote.port}\" as \"${local.admin.username}\" via \"${data.ssh_tunnel.db.connection_name}\"'"
|
||||
}
|
||||
provisioner "local-exec" {
|
||||
command = (var.engine == "aurora-mysql"
|
||||
? "echo 'CREATE DATABASE ${each.value.database}' | ${var.mysql_binary} -h ${data.ssh_tunnel.db.local.host} -P ${data.ssh_tunnel.db.local.port} -u ${local.admin.username} ${local.admin.username}"
|
||||
: "echo 'CREATE DATABASE ${each.value.database}' | ${var.postgres_binary} -h ${data.ssh_tunnel.db.local.host} -p ${data.ssh_tunnel.db.local.port} -U ${local.admin.username} -d ${local.admin.username}"
|
||||
)
|
||||
environment = {
|
||||
PGPASSWORD = var.engine == "aurora-postgres" ? local.admin.password : null,
|
||||
MYSQL_PWD = var.engine == "aurora-mysql" ? local.admin.password : null,
|
||||
}
|
||||
}
|
||||
triggers = {
|
||||
cluster_id = aws_rds_cluster.cluster.id
|
||||
}
|
||||
}
|
||||
output "endpoints" {
|
||||
value = {
|
||||
for key, endpoint in aws_rds_cluster_endpoint.endpoint : key => endpoint.endpoint
|
||||
|
|
|
@ -18,7 +18,7 @@ resource "aws_security_group_rule" "sgr" {
|
|||
security_group_id = aws_security_group.rds.id
|
||||
type = "ingress"
|
||||
protocol = "tcp"
|
||||
from_port = local.db_tunnel_remote.port
|
||||
to_port = local.db_tunnel_remote.port
|
||||
from_port = var.engine == "aurora-postgres" ? 5432 : 3306
|
||||
to_port = var.engine == "aurora-postgres" ? 5432 : 3306
|
||||
source_security_group_id = var.source_security_group_id
|
||||
}
|
5
cloud/aws/rds_serverless/tenant/aws.tf
Normal file
5
cloud/aws/rds_serverless/tenant/aws.tf
Normal file
|
@ -0,0 +1,5 @@
|
|||
data "aws_region" "current" {}
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_vpc" "current" {
|
||||
id = var.vpc_id
|
||||
}
|
71
cloud/aws/rds_serverless/tenant/db.tf
Normal file
71
cloud/aws/rds_serverless/tenant/db.tf
Normal file
|
@ -0,0 +1,71 @@
|
|||
locals {
|
||||
is_mysql = var.engine == "aurora-mysql"
|
||||
db_tunnel_remote = {
|
||||
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}"
|
||||
database_environment_variables = {
|
||||
PGPASSWORD = !local.is_mysql ? var.admin_password : null,
|
||||
MYSQL_PWD = local.is_mysql ? var.admin_password : null,
|
||||
}
|
||||
}
|
||||
resource "local_file" "debug" {
|
||||
filename = "${path.root}/.debug/aws/rds/serverless/${data.aws_rds_cluster.cluster.cluster_identifier}/${local.username}.json"
|
||||
content = jsonencode({
|
||||
db_tunnel_remote = local.db_tunnel_remote,
|
||||
mysql_command = local.mysql_command,
|
||||
postgres_command = local.postgres_command,
|
||||
database_environment_variables = local.database_environment_variables,
|
||||
})
|
||||
file_permission = "0600"
|
||||
}
|
||||
data "ssh_tunnel" "db" {
|
||||
connection_name = "db-${var.engine}"
|
||||
remote = local.db_tunnel_remote
|
||||
}
|
||||
resource "terraform_data" "db" {
|
||||
triggers_replace = {
|
||||
engine = data.aws_rds_cluster.cluster.engine,
|
||||
cluster_id = data.aws_rds_cluster.cluster.id
|
||||
}
|
||||
provisioner "local-exec" {
|
||||
command = "echo 'Connecting to \"${local.db_tunnel_remote.host}:${local.db_tunnel_remote.port}\" as \"${var.admin_username}\" via \"${data.ssh_tunnel.db.connection_name}\"'"
|
||||
}
|
||||
provisioner "local-exec" {
|
||||
command = (local.is_mysql
|
||||
? "echo 'CREATE DATABASE IF NOT EXISTS ${var.database}' | ${local.mysql_command}"
|
||||
: "echo 'CREATE DATABASE ${var.database}' | ${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}"
|
||||
)
|
||||
environment = local.database_environment_variables
|
||||
}
|
||||
provisioner "local-exec" {
|
||||
command = (local.is_mysql
|
||||
? "GRANT ALL PRIVILEGES ON ${var.database}.* TO '${var.username}'@'%'\""
|
||||
: ""
|
||||
)
|
||||
environment = local.database_environment_variables
|
||||
}
|
||||
#provisioner "local-exec" {
|
||||
# when = destroy
|
||||
# command = (local.is_mysql
|
||||
# ? "DROP USER '${var.username}'@'%';"
|
||||
# : "DROP USER ${var.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}"
|
||||
# )
|
||||
#}
|
||||
}
|
72
cloud/aws/rds_serverless/tenant/input.tf
Normal file
72
cloud/aws/rds_serverless/tenant/input.tf
Normal file
|
@ -0,0 +1,72 @@
|
|||
variable "vpc_id" {
|
||||
type = string
|
||||
description = "VPC ID"
|
||||
}
|
||||
variable "cluster_id" {
|
||||
type = string
|
||||
description = "The cluster identifier"
|
||||
}
|
||||
data "aws_rds_cluster" "cluster" {
|
||||
cluster_identifier = var.cluster_id
|
||||
}
|
||||
variable "username" {
|
||||
type = string
|
||||
description = "The username for the tenant"
|
||||
}
|
||||
variable "database" {
|
||||
type = string
|
||||
description = "The database for the tenant"
|
||||
}
|
||||
locals {
|
||||
username = lower(var.username)
|
||||
database = lower(var.database)
|
||||
}
|
||||
variable "app_name" {
|
||||
type = string
|
||||
description = "The application name"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
type = map(string)
|
||||
description = "Tags to apply to resources"
|
||||
default = {}
|
||||
}
|
||||
variable "aws_profile" {
|
||||
type = string
|
||||
description = "AWS profile to use for generating RDS auth token"
|
||||
default = null
|
||||
}
|
||||
variable "is_active" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
variable "super_user_iam_role_name" {
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
variable "engine" {
|
||||
type = string
|
||||
description = "The engine type of the RDS cluster"
|
||||
validation {
|
||||
error_message = "Engine must be one of 'aurora-postgres' or 'aurora-mysql'"
|
||||
condition = var.engine == "aurora-postgres" || var.engine == "aurora-mysql"
|
||||
}
|
||||
}
|
||||
variable "mysql_binary" {
|
||||
type = string
|
||||
description = "The path to the mysql binary"
|
||||
default = "mariadb"
|
||||
}
|
||||
variable "postgres_binary" {
|
||||
type = string
|
||||
description = "The path to the postgres binary"
|
||||
default = "psql"
|
||||
}
|
||||
variable "admin_username" {
|
||||
type = string
|
||||
description = "The admin user for the database"
|
||||
}
|
||||
variable "admin_password" {
|
||||
type = string
|
||||
description = "The admin password for the database"
|
||||
}
|
25
cloud/aws/rds_serverless/tenant/output.tf
Normal file
25
cloud/aws/rds_serverless/tenant/output.tf
Normal file
|
@ -0,0 +1,25 @@
|
|||
output "username" {
|
||||
value = local.username
|
||||
}
|
||||
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",
|
||||
"-h", data.aws_rds_cluster.cluster.endpoint,
|
||||
"-P", data.aws_rds_cluster.cluster.port,
|
||||
"-D", local.database,
|
||||
"-u", local.username,
|
||||
"-p'${data.external.rds_auth_token.result.password}'",
|
||||
])
|
||||
}
|
107
cloud/aws/rds_serverless/tenant/tenant.tf
Normal file
107
cloud/aws/rds_serverless/tenant/tenant.tf
Normal file
|
@ -0,0 +1,107 @@
|
|||
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]
|
||||
}
|
26
cloud/aws/rds_serverless/tenant/terraform.tf
Normal file
26
cloud/aws/rds_serverless/tenant/terraform.tf
Normal file
|
@ -0,0 +1,26 @@
|
|||
terraform {
|
||||
required_version = "~> 1.6"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
local = {
|
||||
source = "hashicorp/local"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
ssh = {
|
||||
source = "matthewbaggett/ssh"
|
||||
version = "~> 0.1"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,10 +14,6 @@ terraform {
|
|||
source = "hashicorp/local"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
postgresql = {
|
||||
source = "cyrilgdn/postgresql"
|
||||
version = "~> 1.0"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.0"
|
||||
|
|
Loading…
Reference in a new issue