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
|
count = var.admin_username == null ? 1 : 0
|
||||||
separator = "_"
|
separator = "_"
|
||||||
}
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
admin_username = coalesce(var.admin_username, random_pet.admin_user[0].id)
|
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" {
|
resource "local_file" "debug" {
|
||||||
content = jsonencode({
|
content = jsonencode({
|
||||||
instance_name = var.instance_name,
|
rds = {
|
||||||
tennants = var.tennants,
|
instance_name = var.instance_name,
|
||||||
application_arn = try(var.application.arn, null),
|
tennants = var.tenants,
|
||||||
application_name = try(var.application.name, null),
|
application_arn = try(var.application.arn, null),
|
||||||
engine_user = var.engine,
|
application_name = try(var.application.name, null),
|
||||||
engine_actual = data.aws_rds_engine_version.latest.engine
|
engine_user = var.engine,
|
||||||
engine_version_actual = data.aws_rds_engine_version.latest.version,
|
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"
|
filename = "${path.root}/.debug/aws/rds/serverless/${var.instance_name}.json"
|
||||||
file_permission = "0600"
|
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"
|
description = "The name of the RDS serverless instance"
|
||||||
default = "serverless-multitennant"
|
default = "serverless-multitennant"
|
||||||
}
|
}
|
||||||
variable "tennants" {
|
locals {
|
||||||
|
sanitised_name = lower(replace(var.instance_name, "[^a-zA-Z0-9]", "-"))
|
||||||
|
}
|
||||||
|
variable "tenants" {
|
||||||
type = map(object({
|
type = map(object({
|
||||||
username = string
|
username = string
|
||||||
password = string
|
|
||||||
database = string
|
database = string
|
||||||
|
active = optional(bool, true)
|
||||||
}))
|
}))
|
||||||
default = null
|
default = null
|
||||||
}
|
}
|
||||||
|
@ -74,3 +77,17 @@ variable "skip_final_snapshot" {
|
||||||
default = false
|
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"]
|
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" {
|
resource "aws_rds_cluster" "cluster" {
|
||||||
cluster_identifier_prefix = "${var.instance_name}-"
|
cluster_identifier = local.sanitised_name
|
||||||
engine_mode = "provisioned"
|
engine_mode = "provisioned"
|
||||||
engine = data.aws_rds_engine_version.latest.engine
|
engine = data.aws_rds_engine_version.latest.engine
|
||||||
engine_version = data.aws_rds_engine_version.latest.version
|
engine_version = data.aws_rds_engine_version.latest.version
|
||||||
database_name = local.admin_username
|
database_name = local.admin_username
|
||||||
master_username = local.admin_username
|
master_username = local.admin_username
|
||||||
manage_master_user_password = true
|
manage_master_user_password = true
|
||||||
storage_encrypted = true
|
master_user_secret_kms_key_id = aws_kms_key.master_key.arn
|
||||||
enable_local_write_forwarding = true
|
storage_encrypted = true
|
||||||
backup_retention_period = var.backup_retention_period_days
|
enable_local_write_forwarding = true
|
||||||
skip_final_snapshot = var.skip_final_snapshot
|
backup_retention_period = var.backup_retention_period_days
|
||||||
preferred_backup_window = var.backup_window
|
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 {
|
serverlessv2_scaling_configuration {
|
||||||
max_capacity = var.scaling.max_capacity
|
max_capacity = var.scaling.max_capacity
|
||||||
min_capacity = var.scaling.min_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(
|
tags = merge(
|
||||||
try(var.application.application_tag, {}),
|
try(var.application.application_tag, {}),
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_rds_cluster_instance" "instance" {
|
resource "aws_rds_cluster_endpoint" "endpoint" {
|
||||||
cluster_identifier = aws_rds_cluster.cluster.id
|
depends_on = [aws_rds_cluster_instance.instance]
|
||||||
instance_class = "db.serverless"
|
for_each = { "write" = "ANY", "read" = "READER" }
|
||||||
engine = aws_rds_cluster.cluster.engine
|
cluster_endpoint_identifier = join("-", [local.sanitised_name, each.key, "endpoint"])
|
||||||
engine_version = aws_rds_cluster.cluster.engine_version
|
cluster_identifier = aws_rds_cluster.cluster.id
|
||||||
apply_immediately = true
|
custom_endpoint_type = each.value
|
||||||
publicly_accessible = false
|
|
||||||
tags = merge(
|
tags = merge(
|
||||||
try(var.application.application_tag, {}),
|
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" {
|
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
|
subnet_ids = data.aws_subnets.subnets.ids
|
||||||
tags = merge(
|
tags = merge(
|
||||||
try(var.application.application_tag, {}),
|
try(var.application.application_tag, {}),
|
||||||
|
|
|
@ -8,11 +8,19 @@ terraform {
|
||||||
}
|
}
|
||||||
random = {
|
random = {
|
||||||
source = "hashicorp/random"
|
source = "hashicorp/random"
|
||||||
version = "3.6.2"
|
version = "~> 3.0"
|
||||||
}
|
}
|
||||||
local = {
|
local = {
|
||||||
source = "hashicorp/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