Incomplete auth flow
This commit is contained in:
parent
9ce551d953
commit
83875080c8
12 changed files with 348 additions and 32 deletions
|
@ -6,7 +6,7 @@ resource "random_pet" "admin_user" {
|
|||
count = var.admin_username == null ? 1 : 0
|
||||
separator = "_"
|
||||
}
|
||||
|
||||
locals {
|
||||
admin_username = coalesce(var.admin_username, random_pet.admin_user[0].id)
|
||||
admin_token = ""
|
||||
}
|
2
cloud/aws/rds_serverless/aws.tf
Normal file
2
cloud/aws/rds_serverless/aws.tf
Normal file
|
@ -0,0 +1,2 @@
|
|||
data "aws_region" "current" {}
|
||||
data "aws_caller_identity" "current" {}
|
|
@ -1,12 +1,18 @@
|
|||
resource "local_file" "debug" {
|
||||
content = jsonencode({
|
||||
instance_name = var.instance_name,
|
||||
tennants = var.tennants,
|
||||
application_arn = try(var.application.arn, null),
|
||||
application_name = try(var.application.name, null),
|
||||
engine_user = var.engine,
|
||||
engine_actual = data.aws_rds_engine_version.latest.engine
|
||||
engine_version_actual = data.aws_rds_engine_version.latest.version,
|
||||
rds = {
|
||||
instance_name = var.instance_name,
|
||||
tennants = var.tenants,
|
||||
application_arn = try(var.application.arn, null),
|
||||
application_name = try(var.application.name, null),
|
||||
engine_user = var.engine,
|
||||
engine_actual = data.aws_rds_engine_version.latest.engine
|
||||
engine_version_actual = data.aws_rds_engine_version.latest.version,
|
||||
}
|
||||
tenants = {
|
||||
input = var.tenants
|
||||
output = local.output_tenants
|
||||
}
|
||||
})
|
||||
filename = "${path.root}/.debug/aws/rds/serverless/${var.instance_name}.json"
|
||||
file_permission = "0600"
|
||||
|
|
68
cloud/aws/rds_serverless/iam.data.tf
Normal file
68
cloud/aws/rds_serverless/iam.data.tf
Normal file
|
@ -0,0 +1,68 @@
|
|||
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}"]
|
||||
}
|
||||
}
|
32
cloud/aws/rds_serverless/iam.policies.tf
Normal file
32
cloud/aws/rds_serverless/iam.policies.tf
Normal file
|
@ -0,0 +1,32 @@
|
|||
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"
|
||||
}
|
16
cloud/aws/rds_serverless/iam.roles.rds.tf
Normal file
16
cloud/aws/rds_serverless/iam.roles.rds.tf
Normal file
|
@ -0,0 +1,16 @@
|
|||
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
|
||||
}
|
51
cloud/aws/rds_serverless/iam.roles.tenants.tf
Normal file
51
cloud/aws/rds_serverless/iam.roles.tenants.tf
Normal file
|
@ -0,0 +1,51 @@
|
|||
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
|
||||
}
|
37
cloud/aws/rds_serverless/iam.users.tf
Normal file
37
cloud/aws/rds_serverless/iam.users.tf
Normal file
|
@ -0,0 +1,37 @@
|
|||
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"
|
||||
}
|
||||
}
|
|
@ -3,11 +3,14 @@ variable "instance_name" {
|
|||
description = "The name of the RDS serverless instance"
|
||||
default = "serverless-multitennant"
|
||||
}
|
||||
variable "tennants" {
|
||||
locals {
|
||||
sanitised_name = lower(replace(var.instance_name, "[^a-zA-Z0-9]", "-"))
|
||||
}
|
||||
variable "tenants" {
|
||||
type = map(object({
|
||||
username = string
|
||||
password = string
|
||||
database = string
|
||||
active = optional(bool, true)
|
||||
}))
|
||||
default = null
|
||||
}
|
||||
|
@ -74,3 +77,17 @@ variable "skip_final_snapshot" {
|
|||
default = false
|
||||
}
|
||||
|
||||
variable "enable_performance_insights" {
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "bastion" {
|
||||
description = "The ssh bastion to use for creating the database"
|
||||
type = object({
|
||||
host = string
|
||||
user = optional(string)
|
||||
password = optional(string)
|
||||
private_key = optional(string)
|
||||
})
|
||||
}
|
|
@ -6,39 +6,118 @@ data "aws_rds_engine_version" "latest" {
|
|||
values = ["provisioned"]
|
||||
}
|
||||
}
|
||||
resource "aws_kms_key" "db_key" {
|
||||
description = "RDS ${var.instance_name} Encryption Key"
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{
|
||||
TerraformSecretType = "RDSMasterEncryptionKey"
|
||||
}
|
||||
)
|
||||
}
|
||||
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_prefix = "${var.instance_name}-"
|
||||
engine_mode = "provisioned"
|
||||
engine = data.aws_rds_engine_version.latest.engine
|
||||
engine_version = data.aws_rds_engine_version.latest.version
|
||||
database_name = local.admin_username
|
||||
master_username = local.admin_username
|
||||
manage_master_user_password = true
|
||||
storage_encrypted = true
|
||||
enable_local_write_forwarding = true
|
||||
backup_retention_period = var.backup_retention_period_days
|
||||
skip_final_snapshot = var.skip_final_snapshot
|
||||
preferred_backup_window = var.backup_window
|
||||
cluster_identifier = local.sanitised_name
|
||||
engine_mode = "provisioned"
|
||||
engine = data.aws_rds_engine_version.latest.engine
|
||||
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
|
||||
storage_encrypted = true
|
||||
enable_local_write_forwarding = true
|
||||
backup_retention_period = var.backup_retention_period_days
|
||||
skip_final_snapshot = var.skip_final_snapshot
|
||||
preferred_backup_window = var.backup_window
|
||||
iam_database_authentication_enabled = true
|
||||
kms_key_id = aws_kms_key.db_key.arn
|
||||
apply_immediately = true
|
||||
db_subnet_group_name = aws_db_subnet_group.sg.name
|
||||
|
||||
serverlessv2_scaling_configuration {
|
||||
max_capacity = var.scaling.max_capacity
|
||||
min_capacity = var.scaling.min_capacity
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = false
|
||||
}
|
||||
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{
|
||||
Name = var.instance_name
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_rds_cluster_instance" "instance" {
|
||||
cluster_identifier = aws_rds_cluster.cluster.id
|
||||
identifier_prefix = "${local.sanitised_name}-"
|
||||
instance_class = "db.serverless"
|
||||
engine = aws_rds_cluster.cluster.engine
|
||||
engine_version = aws_rds_cluster.cluster.engine_version
|
||||
apply_immediately = true
|
||||
publicly_accessible = false
|
||||
db_subnet_group_name = aws_rds_cluster.cluster.db_subnet_group_name
|
||||
|
||||
performance_insights_enabled = var.enable_performance_insights
|
||||
performance_insights_retention_period = var.enable_performance_insights ? 7 : null
|
||||
performance_insights_kms_key_id = var.enable_performance_insights ? aws_kms_key.db_key.arn : null
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = false
|
||||
}
|
||||
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_rds_cluster_instance" "instance" {
|
||||
cluster_identifier = aws_rds_cluster.cluster.id
|
||||
instance_class = "db.serverless"
|
||||
engine = aws_rds_cluster.cluster.engine
|
||||
engine_version = aws_rds_cluster.cluster.engine_version
|
||||
apply_immediately = true
|
||||
publicly_accessible = false
|
||||
resource "aws_rds_cluster_endpoint" "endpoint" {
|
||||
depends_on = [aws_rds_cluster_instance.instance]
|
||||
for_each = { "write" = "ANY", "read" = "READER" }
|
||||
cluster_endpoint_identifier = join("-", [local.sanitised_name, each.key, "endpoint"])
|
||||
cluster_identifier = aws_rds_cluster.cluster.id
|
||||
custom_endpoint_type = each.value
|
||||
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
resource "null_resource" "database_create" {
|
||||
for_each = var.tenants
|
||||
depends_on = [aws_rds_cluster_instance.instance]
|
||||
triggers = {
|
||||
cluster_id = aws_rds_cluster.cluster.id
|
||||
}
|
||||
connection {
|
||||
type = "ssh"
|
||||
host = var.bastion.host
|
||||
user = var.bastion.user
|
||||
password = var.bastion.password
|
||||
private_key = var.bastion.private_key
|
||||
}
|
||||
provisioner "remote-exec" {
|
||||
inline = [
|
||||
"PGPASSWORD=${local.admin_token} psql -h ${aws_rds_cluster_endpoint.endpoint["write"].endpoint} -U ${local.admin_username} -d ${local.admin_username} -c \"CREATE DATABASE ${each.value.database}\""
|
||||
]
|
||||
}
|
||||
}
|
||||
output "endpoints" {
|
||||
value = {
|
||||
for key, endpoint in aws_rds_cluster_endpoint.endpoint : key => endpoint.endpoint
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ data "aws_subnets" "subnets" {
|
|||
}
|
||||
}
|
||||
resource "aws_db_subnet_group" "sg" {
|
||||
name = "${var.instance_name}-subnet-group"
|
||||
name = lower(join("-", [var.instance_name, "subnet-group"]))
|
||||
subnet_ids = data.aws_subnets.subnets.ids
|
||||
tags = merge(
|
||||
try(var.application.application_tag, {}),
|
||||
|
|
|
@ -8,11 +8,19 @@ terraform {
|
|||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "3.6.2"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
local = {
|
||||
source = "hashicorp/local"
|
||||
version = "~>2.1"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
postgresql = {
|
||||
source = "cyrilgdn/postgresql"
|
||||
version = "~> 1.0"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue