0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Terraform×AWS】SSHの踏み台をやめて、Session ManagerでプライベートなRDSに接続する

0
Posted at

プライベートサブネットにあるRDSへ手元から接続するために、パブリックサブネットに踏み台サーバーを置いてSSHで接続している環境は多いのではないでしょうか。この方法では、SSH鍵の配布と管理、22番ポートの開放、接続元IPアドレスの管理といった手間とリスクがついて回ります。

この記事では、AWS Systems ManagerのSession Managerを使い、SSH鍵もインバウンドの開放も使わずにRDSへ接続する仕組みをTerraformで作ります。

SSH踏み台の問題

  • SSH鍵の管理:鍵の配布、退職者の鍵の削除、漏えい時の差し替えが必要になる
  • ポートの開放:22番ポートをインターネットに向けて開ける必要がある。接続元IPで絞っても、在宅勤務などでIPが変わるたびに更新が要る
  • 誰が接続したか追いにくい:共有の鍵を使うと、操作した人を特定できない

Session Managerの仕組み

Session Managerでは、接続の向きが逆になります。

  1. 中継用のEC2インスタンスで動くSSMエージェントが、内側からSystems Managerに接続する
  2. 利用者は、AWS CLIでSystems Managerにセッションの開始を依頼する
  3. Systems Managerが、利用者と中継用インスタンスの通信を橋渡しする
  4. 中継用インスタンスが、RDSへの通信を転送する(ポートフォワーディング)

中継用インスタンスは引き続き必要ですが、置き場所はプライベートサブネットで、パブリックIPもインバウンドのルールも不要です。**認証はIAMで行うので、SSH鍵は存在しません。**誰がいつ接続したかは、CloudTrailに記録されます。

中継用インスタンスのIAMロール

SSMエージェントがSystems Managerと通信するための権限を付けます。AWS管理ポリシーのAmazonSSMManagedInstanceCoreで足ります。

resource "aws_iam_role" "ssm_relay" {
  name = "ssm-relay-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "ec2.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "ssm_core" {
  role       = aws_iam_role.ssm_relay.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "ssm_relay" {
  name = "ssm-relay-profile"
  role = aws_iam_role.ssm_relay.name
}

中継用インスタンスとセキュリティグループ

variable "vpc_id" {
  type = string
}

variable "private_subnet_id" {
  type = string
}

variable "rds_security_group_id" {
  type = string
}

data "aws_ssm_parameter" "al2023" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
}

resource "aws_security_group" "ssm_relay" {
  name   = "ssm-relay-sg"
  vpc_id = var.vpc_id

  egress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [var.rds_security_group_id]
  }
}

resource "aws_vpc_security_group_ingress_rule" "rds_from_relay" {
  security_group_id            = var.rds_security_group_id
  referenced_security_group_id = aws_security_group.ssm_relay.id
  from_port                    = 5432
  to_port                      = 5432
  ip_protocol                  = "tcp"
}

resource "aws_instance" "ssm_relay" {
  ami                         = data.aws_ssm_parameter.al2023.value
  instance_type               = "t4g.nano"
  subnet_id                   = var.private_subnet_id
  vpc_security_group_ids      = [aws_security_group.ssm_relay.id]
  iam_instance_profile        = aws_iam_instance_profile.ssm_relay.name
  associate_public_ip_address = false

  metadata_options {
    http_tokens = "required"
  }

  tags = {
    Name = "ssm-relay"
  }
}

ポイントは次のとおりです。

  • インバウンドのルールがない:中継用インスタンスのセキュリティグループには、ingressを1つも書いていない。SSMエージェントは内側から外へ接続するので、受け付ける通信がない
  • アウトバウンドは2つだけ:Systems Managerとの通信(443番)と、RDSへの通信(5432番)
  • AMI:Amazon Linux 2023にはSSMエージェントが最初から入っている。SSMパラメータから最新のAMI IDを取得している
  • http_tokens = "required":インスタンスメタデータへのアクセスをIMDSv2に限定する。認証情報の窃取を防ぐための基本設定

RDS側のセキュリティグループには、中継用インスタンスからの5432番を許可するルールを追加しています。

Systems Managerへの経路

SSMエージェントは、Systems ManagerのAPIにHTTPSで接続します。プライベートサブネットからの経路は次のどちらかです。

  • NATゲートウェイがある場合:そのまま通信できる。追加の設定は不要
  • NATゲートウェイがない場合:ssm、ssmmessages、ec2messagesの3つのインターフェース型VPCエンドポイントが必要
data "aws_region" "current" {}

resource "aws_vpc_endpoint" "ssm" {
  for_each = toset(["ssm", "ssmmessages", "ec2messages"])

  vpc_id              = var.vpc_id
  service_name        = "com.amazonaws.${data.aws_region.current.name}.${each.value}"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = [var.private_subnet_id]
  security_group_ids  = [aws_security_group.vpc_endpoint.id]
  private_dns_enabled = true
}

aws_security_group.vpc_endpointは、VPC内からの443番を許可するセキュリティグループです。インターフェース型エンドポイントには時間料金がかかるため、NATゲートウェイがある環境では作らなくてもかまいません。

接続する利用者のIAM権限

利用者には、中継用インスタンスへのポートフォワーディングだけを許可します。

data "aws_caller_identity" "current" {}

resource "aws_iam_policy" "rds_port_forward" {
  name = "rds-port-forward"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = "ssm:StartSession"
        Resource = [
          aws_instance.ssm_relay.arn,
          "arn:aws:ssm:${data.aws_region.current.name}::document/AWS-StartPortForwardingSessionToRemoteHost"
        ]
      },
      {
        Effect   = "Allow"
        Action   = ["ssm:TerminateSession", "ssm:ResumeSession"]
        Resource = "arn:aws:ssm:*:${data.aws_caller_identity.current.account_id}:session/$${aws:userid}-*"
      }
    ]
  })
}
  • StartSessionの対象を絞る:中継用インスタンスと、ポートフォワーディング用のドキュメントだけを許可する。これで、シェルのセッションは開けない
  • 自分のセッションだけ終了できる:TerminateSessionとResumeSessionは、自分が開始したセッションに限定している

$${aws:userid} の $$ は、TerraformにIAMポリシー変数として ${aws:userid} をそのまま出力させるためのエスケープです。

RDSに接続する

利用者の手元には、AWS CLIとSession Managerプラグインが必要です。次のコマンドで、手元の15432番ポートをRDSの5432番につなぎます。

aws ssm start-session \
  --target i-0123456789abcdef0 \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"host":["mydb.xxxxxxxx.ap-northeast-1.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["15432"]}'

セッションを開いたまま、別のターミナルから手元のポートに接続します。

psql -h localhost -p 15432 -U app_user -d appdb

DBクライアントのGUIツールでも、接続先をlocalhost:15432にするだけで使えます。

記録として残るもの

ポートフォワーディングのセッションで残る記録は次のとおりです。

  • CloudTrail:誰が、いつ、どのインスタンスにセッションを開始・終了したか
  • Session Managerのセッション履歴:セッションの開始・終了時刻と利用者

一方、**ポートフォワーディングで流れたSQLの中身は、Session Managerのログには残りません。**シェルのセッションとは異なり、通信をそのまま転送しているためです。誰がどのSQLを実行したかまで記録したい場合は、RDS側で監査ログ(PostgreSQLならpgAuditなど)を有効にします。

まとめ

  • Session Managerを使えば、SSH鍵もインバウンドの開放もなしにRDSへ接続できる
  • 中継用インスタンスはプライベートサブネットに置き、インバウンドのルールは持たせない
  • 利用者のIAM権限は、中継用インスタンスへのポートフォワーディングだけに絞る
  • 接続の記録はCloudTrailに残るが、SQLの中身は残らない。必要ならRDS側で監査ログを取る

鍵とポートの管理から解放されるだけでなく、「誰が接続したか」をIAMの単位で追えるようになります。踏み台を新しく作るなら、最初からSession Managerで構成することをおすすめします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?