🔐

踏み台サーバとSSM Session ManagerでAamazon Auroraへネットワークに穴を開けずにアクセスする

2023/08/02に公開

こんにちはへたれです。
株式会社Aidemyでエンジニアとして開発業務に携っております。

先日Lab Bankという化学業界の研究室向けSaaSをリリースいたしました。
研究室のDXを加速し、より研究開発にフォーカスできるプラットフォームを提供していきます。

はじめに

みなさんはトラブル対応時などのDBへのセキュリティ経路をどのように確保されているでしょうか?
特にDBはセンシティブな情報を扱うことも多く、なるべく穴を開けたくないという気持ちはインフラに携わるエンジニア共通の悩みだと思います。
自分もメンテナンス性とセキュリティ要件とのトレードオフで迷うことは非常に多いです...

本記事では、AWS公式ブログでも紹介されている方法で、セキュアなアクセス環境を構築してみました。
またサンプルのTerraformも公開していますので、是非試してみてください!!

運用フロー

システム構成

ネットワーク上には踏み台となるEC2インスタンスとAuroraクラスタが存在します。
踏み台、Auroraクラスタ共に外部からのインバウンドアクセスを許可せずに、NATを経由してのアウトバウンドアクセスのみを許可します。
(これはSession ManagerはVPCに属するサービスではないため、SSM Agentからインターネットへのリーチが必要となるためです)
開発者はSession Managerのポートフォワード機能を使ってAuroraクラスタへのアクセスを行います。

ネットワークとDBの用意

まずはネットワーク用意します。
SSM Agentはインターネットへのアウトバウンドが必要となるため、NATゲートウェイを通じてでられるようにします。
(VPC Endpointを使うことで、インターネットへのアウトバウンドなしでの利用も可能ですが、ここでは対応してしません)

network.tf
# ---------------------
# ゲートウェイ
# ---------------------

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "sample-igw"
  }
}

resource "aws_eip" "natgw" {
  tags = {
    Name = "sample-natgw"
  }
}

resource "aws_nat_gateway" "natgw" {
  allocation_id = aws_eip.natgw.id
  subnet_id     = aws_subnet.nat.id
  tags = {
    Name = "NAT GW"
  }
}

# ---------------------
# ルーティング
# ---------------------

resource "aws_route_table" "to_igw" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
}

resource "aws_route_table" "to_natgw" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.natgw.id
  }
}

resource "aws_route_table_association" "nat_to_igw" {
  subnet_id      = aws_subnet.nat.id
  route_table_id = aws_route_table.to_igw.id
}

resource "aws_route_table_association" "local_1a_to_natgw" {
  subnet_id      = aws_subnet.local_1a.id
  route_table_id = aws_route_table.to_natgw.id
}

resource "aws_route_table_association" "local_1c_to_natgw" {
  subnet_id      = aws_subnet.local_1c.id
  route_table_id = aws_route_table.to_natgw.id
}

次にDBを作成します。
インバウンド許可を出しているのは踏み台サーバからのみです。

db.tf
# ---------------------
# Aurora Cluster
# ---------------------

resource "aws_rds_cluster" "aurora_cluster" {
  cluster_identifier = "sample-aurora-cluster"
  engine             = "aurora-postgresql"
  database_name      = "sample"

  # マスタユーザーのパスワードは、AWS Secrets Managerで管理
  master_username             = "sample_user"
  manage_master_user_password = true

  vpc_security_group_ids = [aws_security_group.aurora_cluster.id]
  db_subnet_group_name   = aws_db_subnet_group.aurora.name
  storage_encrypted      = true

  # サンプルコードなのでterraform destroyで綺麗に削除したい
  deletion_protection     = false
  skip_final_snapshot     = true
  backup_retention_period = 1

  tags = {
    Name = "sample-aurora-cluster"
  }
}

resource "aws_rds_cluster_instance" "aurora_cluster_instance" {
  cluster_identifier = aws_rds_cluster.aurora_cluster.id
  # ts.medium はPostgres互換の最小サイズ
  instance_class = "db.t3.medium"
  engine         = aws_rds_cluster.aurora_cluster.engine
  engine_version = aws_rds_cluster.aurora_cluster.engine_version

  tags = {
    Name = "sample-aurora-cluster-instance"
  }
}

# ---------------------
# Security Group
# ---------------------

resource "aws_security_group" "aurora_cluster" {
  name   = "sample-aurora-cluster-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.bastion.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "sample-aurora-cluster-sg"
  }
}

踏み台サーバの用意

最後に踏み台サーバを用意します。
今回利用するAMIはAmazon Linux 2023なので、SSM Agentは最初からインストールされています。
以下の条件で作成します。

  • User dataでSSM Agentを起動
  • インスタンスプロファイルにarn:aws:iam::aws:policy/AmazonSSMManagedInstanceCoreのポリシをアタッチ
  • インバウンドはどこからも許可しない
bastion.tf
# ---------------------
# EC2インスタンス
# ---------------------

resource "aws_instance" "bastion" {
  # Amazon Linux 2023
  ami                    = "ami-0d3bbfd074edd7acb"
  iam_instance_profile   = aws_iam_instance_profile.bastion.name
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.local_1a.id
  vpc_security_group_ids = [aws_security_group.bastion.id]

  # デフォルトでは起動していない
  user_data                   = <<EOS
#!/bin/bash
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent
EOS
  user_data_replace_on_change = true

  tags = {
    Name = "bastion"
  }
}

# ---------------------
# IAM Role
# ---------------------

data "aws_iam_policy_document" "bastion_assume_role" {
  statement {
    effect = "Allow"
    actions = [
      "sts:AssumeRole",
    ]
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

data "aws_iam_policy" "ssm_core" {
  arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy" "bastion_ssm_core" {
  name   = "bastion-policy"
  role   = aws_iam_role.bastion.name
  policy = data.aws_iam_policy.ssm_core.policy
}

resource "aws_iam_role" "bastion" {
  name               = "bastion"
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.bastion_assume_role.json
}

resource "aws_iam_instance_profile" "bastion" {
  name = "bastion"
  role = aws_iam_role.bastion.name
}

# ---------------------
# Security Group
# ---------------------

resource "aws_security_group" "bastion" {
  name        = "bastion"
  description = "bastion"
  vpc_id      = aws_vpc.main.id

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

あとはこれを terrafrom applyするだけです。 (`・ω・)b

アクセス方法

サンプルコードをapplyすると、outputに踏み台サーバのインスタンスIDとAuroraのエンドポイント(ローカルのドメイン名)が出力されます。

$ terraform apply
...
aurora_endpoint = "sample-aurora-cluster.cluster-xxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
bastion_instance_id = "i-xxxxxxxxxx"

またDBのパスワードはSecrets Managerに保管されているため、ここから取得してください。

次にローカルのポートから踏み台サーバにポートフォワードを行います。
先ほど取得したデータを用いて以下のコマンドで実施可能です。

$ aws ssm start-session \
    --target i-xxxxxxxxxx \
    --document-name AWS-StartPortForwardingSessionToRemoteHost \
    --parameters '{"host":["sample-aurora-cluster.cluster-xxxxxxxxx.ap-northeast-1.rds.amazonaws.com"],"portNumber":["5432"], "localPortNumber":["5432"]}'

あとはお好みのツールを用いて、ローカルホストへアクセスするだけでOKです。
画像はDBeaverでの設定例です。

おわりに

今回は踏み台となるEC2に対してSSM Session Mnanagerでのポートフォワードを行うことにより、DBや踏み台に対してインターネットからのインバウンドを許容することなく、かつ任意のツールでDBを管理することができました。
本記事ではTerraformのコードも公開していますので是非気軽に試してみてください。
また今回は紹介しませんでしたが、実運用においてはIAM認証でのアクセスやVPC Endpoint経由でのSession Managerの利用なども組み合わせるとよりセキュアになるかなと思います。

より安心してサービスを利用していただけるよう、これからもセキュアな構成を目指していきます。

Aidemy Tech Blog

Discussion