postgresql 使用Terraform的AWS RDS IAM身份验证

bxpogfeg  于 2023-08-04  发布在  PostgreSQL
关注(0)|答案(4)|浏览(218)

我正试图正确设置我的基础设施,没有密码或密钥铺设左右。AWS RDS提供了一个选项来执行此操作,方法是允许用户(应用程序)使用生成的令牌进行身份验证。
然而,在文档中,其中一个步骤(this one)需要在Postgres数据库中运行一个查询来创建一个用户并授予他特定的权限:

CREATE USER test_rds WITH LOGIN;
GRANT rds_iam TO test_rds;

字符串
我想用Terraform配置整个堆栈。我查找了一些在RDS示例化后运行查询(here)的“技巧”,可以使用:

resource "null_resource" "db_setup" {
  depends_on = ["aws_db_instance.your_database_instance",   "aws_security_group.sg_allowing_external_access"]

    provisioner "local-exec" {
 // run shell commands to manually psql into the db


或者:

resource "aws_instance" "web" {

  provisioner "remote-exec" {
    inline = [
   // run shell commands to manually psql into the db


但它们都需要创建主密码并以某种方式在“脚本”内传递它。
有没有可能用Terraform干净地做到这一点,没有硬编码的密码被传递?
我很乐意配置数据库,只允许特定的EC2/ECS示例使用正确的permissions来访问它,在我的git存储库中没有任何密码。

vwoqyblh

vwoqyblh1#

要预配RDS数据库用户以进行IAM身份验证,可以添加以下terraform配置:

resource "postgresql_role" "db_user" {
  name  = "db_userx"
  login = true
  roles = ["rds_iam"]
}

字符串
上面的代码使用了cyrilgdn/postgresql提供程序。如果使用aws_db_instance属性配置提供程序的连接参数,如下所示,则会强制执行正确的依赖顺序。

provider "postgresql" {
  host            = aws_db_instance.web.address
  port            = aws_db_instance.web.port
  database        = "postgres"
  username        = aws_db_instance.web.username
  password        = aws_db_instance.web.password
  sslmode         = "require"
  connect_timeout = 15
  superuser       = false # postgres user is not a true superuser in RDS
}


RDS User Guide现在包括以下内容:
对于PostgreSQL,如果将IAM角色(rds_iam)添加到主用户,则IAM身份验证优先于密码身份验证,因此主用户必须以IAM用户身份登录

nhjlsmyf

nhjlsmyf2#

这里涉及的几个组件对您所要求的内容缺乏适当的支持,即:

  • RDS不提供直接创建具有分配给主用户的rds_iam角色的示例的方法
  • cyrilgdn/postgresql不支持IAM数据库身份验证

但是,我们可以在两者之间进行工作。

需求

我假设运行terraform的机器有:

  • 与RDS示例的网络连接
  • 已安装psql cli客户端
  • jq安装
  • 已安装AWS cli(v2)
  • 已配置AWS身份,具有足够的权限:
  • 创建RDS示例
  • 使用RDS IAM进行身份验证

如何实现网络连接(将示例暴露到互联网,在ec2上运行terraform,代理...)和AWS身份(访问密钥,AWS SSO,ec2上的默认角色...)取决于您和您的上下文。
我个人使用AWS CLI命名的配置文件与AWS SSO,所以你会到处出售--profile,随时删除它们。

配置

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.58"
    }
    postgresql = {
      source  = "cyrilgdn/postgresql"
      version = "~> 1.20"
    }
  }
  required_version = ">= 0.14.9"
}

# Feel free to change the AWS auth config to match your own
provider "aws" {
  region  = "<some_region>"
  profile = "my-aws-profile"
  assume_role {
    role_arn = "<some_role>"
    external_id = "<some_external_id>"
  }
  allowed_account_ids = ["<some_account_id>"]
}

# Instead of using passwords, we use IAM database authentication whenever possible :
# https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html
# Users set to use it will not have a long-lived password, and will instead derive a temporary
# password from their AWS IAM identity.
# When this is enabled on an instance, any given pg role can only connect through one
# method ; either password or IAM ; depending on whether it has the rds_iam role.
#
# AWS does not provide a way to create an instance where the master user, created with
# the instance, has this role. So we initially create it with a password, and manually connect
# with a psql client immediately after creating the RDS instance, to switch it to RDS IAM auth.
# Once this is done, we can connect normally (ie through IAM) with the pg provider
resource "random_password" "rds_bootstrap_master_password" {
  length  = 30
  upper   = true
  lower   = true
  numeric = true
  special = false
}

# Now let's create the RDS instance itself with this password
resource "aws_db_instance" "app" {
  username = "terraform"
  password = random_password.rds_bootstrap_master_password.result
  iam_database_authentication_enabled = true

  # Put the rest of your configuration here, there are many more required parameters

  # Once the RDS instance is created, manually switch the terraform user to
  # IAM database authentication. We also delete its password, although technically
  # it isn't really required, as enabling IAM db auth disables it.
  # You can get the CA file with :
  # `curl -o rds-ca-2019.pem https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem`
  provisioner "local-exec" {
    interpreter = ["bash", "-c"]
    command     = <<EOF
      psql "
        host=${self.address}
        port=${self.port}
        sslmode=verify-full
        sslrootcert=rds-ca-2019.pem
        dbname=postgres
        user=${self.username}
        password=${self.password}
      " --command="
        GRANT rds_iam TO ${self.username};
        ALTER ROLE ${self.username} WITH PASSWORD NULL;
      "
    EOF
  }
}

# Since the postgresql provider does not natively support deriving short-lived postgresql creds from
# an AWS identity, we use the AWS cli to do it
data "external" "rds_auth_token" {
  program = [
    "bash", "-c", replace(
      <<-EOF
        aws rds generate-db-auth-token
          --hostname  ${aws_db_instance.app.address}
          --port      ${aws_db_instance.app.port}
          --region    ${replace(aws_db_instance.app.availability_zone, "/[[:lower:]]$/", "")}
          --username  ${aws_db_instance.app.username}
          --profile   my-aws-profile
        | jq --raw-input '{ password: . }'
      EOF
    , "\n", " ")
  ]
}

# Please note that for this provider to work, you need direct network access to the RDS instance
provider "postgresql" {
  scheme    = "awspostgres"
  host      = aws_db_instance.app.address
  port      = aws_db_instance.app.port
  sslmode   = "verify-full" # NB : the "awspostgres" scheme takes care of finding the CA by itself
  username  = aws_db_instance.app.username
  password  = data.external.rds_auth_token.result.password
  superuser = false
}

# This is a dummy user to test your configuration. Create whatever you need with the postgres provider here
resource "postgresql_role" "dummy" {
  name  = "tf_dummy_role"
  login = false
  roles = ["rds_iam"]
}

字符串

说明

在这两种情况下,我们都通过使用AWS cli来解决缺乏第一方支持的问题。

  • 对于主用户,我们只需要一个初始连接来运行一个SQL查询。一个local-exec正是我们在这里寻找的,因为它不添加任何东西到terraform依赖图,而做的正是我们需要的(一次本地命令后立即创建资源)
  • 对于随后的每次运行,我们需要生成一个短期访问令牌,用于postgresql提供程序。external provider就是为此而设计的。它确实需要JSON输出;幸运的是,我们可以使用一些jq-fu,这使我们不必编写单独的 Package 器脚本。

这个配置应该允许您完全引导一个RDS示例,没有任何类型的长期秘密!
希望能帮上忙。

pxiryf3j

pxiryf3j3#

为了完成我之前的回答,postgresql提供程序实际上对RDS IAM身份验证提供了本机支持。因此,我们可以简化这一部分,只保留用于初始设置的provisionner:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.58"
    }
    postgresql = {
      source  = "cyrilgdn/postgresql"
      version = "~> 1.20"
    }
  }
  required_version = ">= 0.14.9"
}

provider "aws" {
  # whatever auth method you prefer
}

# Instead of using passwords, we use IAM database authentication whenever possible :
# https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html
# Users set to use it will not have a long-lived password, and will instead derive a temporary
# password from their AWS IAM identity.
# When this is enabled on an instance, any given pg role can only connect through one
# method ; either password or IAM ; depending on whether it has the rds_iam role.
#
# AWS does not provide a way to create an instance where the master user, created with
# the instance, has this role. So we initially create it with a password, and manually connect
# with a psql client immediately after creating the RDS instance, to switch it to RDS IAM auth.
# Once this is done, we can connect normally (ie through IAM) with the pg provider
resource "random_password" "rds_bootstrap_master_password" {
  length  = 30
  upper   = true
  lower   = true
  numeric = true
  special = false
}

# Now let's create the RDS instance itself with this password
resource "aws_db_instance" "app" {
  username = "terraform"
  password = random_password.rds_bootstrap_master_password.result
  iam_database_authentication_enabled = true

  # Put the rest of your configuration here, there are many more required parameters

  # Once the RDS instance is created, manually switch the terraform user to
  # IAM database authentication. We also delete its password, although technically
  # it isn't really required, as enabling IAM db auth disables it.
  # You can get the CA file with :
  # `curl -o rds-ca-2019.pem https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem`
  provisioner "local-exec" {
    interpreter = ["bash", "-c"]
    command     = <<EOF
      psql "
        host=${self.address}
        port=${self.port}
        sslmode=verify-full
        sslrootcert=rds-ca-2019.pem
        dbname=postgres
        user=${self.username}
        password=${self.password}
      " --command="
        GRANT rds_iam TO ${self.username};
        ALTER ROLE ${self.username} WITH PASSWORD NULL;
      "
    EOF
  }
}

# Please note that for this provider to work, you need direct network access to the RDS instance
provider "postgresql" {
  scheme    = "awspostgres"
  host      = aws_db_instance.app.address
  port      = aws_db_instance.app.port
  sslmode   = "verify-full" # NB : the "awspostgres" scheme takes care of finding the CA by itself
  username  = aws_db_instance.app.username
  password  = data.external.rds_auth_token.result.password
  superuser = false
}

# This is a dummy user to test your configuration. Create whatever you need with the postgres provider here
resource "postgresql_role" "dummy" {
  name  = "tf_dummy_role"
  login = false
  roles = ["rds_iam"]
}

字符串

zsbz8rwp

zsbz8rwp4#

为RDS数据库用户/角色启用IAM身份验证后,将无法再对该用户/角色使用基于密码的身份验证。
这意味着您可以使用安全性较低的密码,甚至可以生成一个随机密码(使用random_id resource),用于设置主密码并首先用于身份验证,以便您可以向主用户和您创建的任何其他用户授予rds_iam权限。
虽然这个密码最终会出现在状态文件中(即使是随机生成的),但正如前面提到的,一旦应用了rds_iam授权,您就不能使用这个密码登录数据库了。

相关问题