🗂

SSH接続を理解して、ポートフォワーディングでプライベートネットワークのインスタンスへアクセス

2024/11/16に公開

SSHとは

SSH(Secure Shell)は、ネットワーク上で安全に通信するためのプロトコルです。
特にリモートサーバーへの接続に使用され、暗号化された通信を可能とする。

SSHの利用用途

たとえば、SSHは以下のような目的で使用されます:

  1. リモートサーバーへの接続:
    サーバーにログインしてコマンドを実行できます。
  2. ファイル転送:
    scp や sftp コマンドで、安全にファイルを転送します。
  3. ポートフォワーディング:
    通信を暗号化し、VPNのようにセキュアに接続できます。

SSHを実現するために必要なもの

  1. リモートサーバーのIPアドレスまたはホスト名
    接続先のサーバーのアドレスです(例: 192.168.1.10 や example.com)。

  2. SSHクライアント
    接続元のコンピュータで、SSHプロトコルを使用するためのソフトウェアです。
    LinuxやmacOSには標準でインストールされています。Windowsの場合、PowerShell または Windows Terminal からも利用できますが、PuTTY や OpenSSH for Windows がよく使われます。

  3. 認証情報(以下のいずれかが必要)
    ユーザー名とパスワード
    SSH鍵ペア(公開鍵・秘密鍵)
    鍵認証方式はセキュリティが高く、推奨されています。接続先のサーバーと接続元の双方で情報が共有されている必要があります。

  4. SSHサーバー側の設定
    リモートサーバーに SSHサーバー(sshd)がインストールされ、設定が正しく行われている必要があります。 また、クライアント(接続元)からの通信を許可している必要があります。

SSHの基本構文

ssh [オプション] [ユーザー名]@[ホスト名またはIPアドレス]
例:
ssh -i ~/.ssh/id_rsa ec2-user@hostname

ポートフォワーディング

ポートフォワーディングとは

特定のネットワークポートに到達する通信を別の場所に転送する機能です。主に、SSHを用いたSSHポートフォワーディングとして使用される。

主な目的

  1. リモートサーバー上のサービスに安全にアクセスする
  2. ファイアウォールやNATによってブロックされている通信を迂回する
  3. ローカル環境やリモート環境のポートをトンネリングする

ポートフォワーディングの種類

ローカルポートフォワーディング(Local Port Forwarding)
リモートポートフォワーディング(Remote Port Forwarding) ※今回は割愛
ダイナミックポートフォワーディング(Dynamic Port Forwarding) ※今回は割愛

ローカルポートフォワーディング

ローカルホストからリモートサーバーに対してSSHトンネルを構築し、ローカルホストへのパケット通信をリモートサーバーに対して転送する仕組みです。

下記のコマンドを実行すると、ローカルマシンの localhost:9999 への通信は、SSHトンネルを経由してリモートサーバー上の localhost:5432 に転送されます。

ssh -L 9999:localhost:5432 user@remote-server -i ~/.ssh/id_rsa(秘密キー)

※remote-serverがssh接続できることが前提です

SSHトンネルが構築されている状態なので下記をリクエストすると、remote-server:5432にpsql接続ができます。(remote-serverの5432でpsqlからのリクエストを対応できるとする前提)

psql -h localhost -p 9999 -U user dbname

ローカルポートフォワーディングを利用して、ローカルからプライベートネットワークへアクセス

ローカルホストからプライベートネットワークのサーバーに対してポートフォワーディングを利用して、アクセスします。

リソースはTerraformで記載したのでご確認ください。

  • bastionはパブリネットワークに存在して、ssh接続可能です。
  • targetをプライベートネットワークに存在して、bastionからアクセス可能です。

SSHトンネルを実行するコマンド

ssh -L 9999:<targetのprivate-ip>:5432 ec2-user@<bastionのpublic-ip> -i <bastionのsshに利用する秘密鍵> -N

アクセスしてみる

curl localhost:9999
>> curl: (56) Recv failure: Connection reset by peer

curl: (56) Recv failure: Connection reset by peer が返ってきました。targetの5432にサービスが何も動いていないですが、targetにアクセスすることが出来ています。

AWSリソース作成

resource "aws_vpc" "example_vpc" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public_subnet" {
  vpc_id                  = aws_vpc.example_vpc.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
}

resource "aws_subnet" "private_subnet" {
  vpc_id     = aws_vpc.example_vpc.id
  cidr_block = "10.0.2.0/24"
}

resource "aws_internet_gateway" "gateway" {
  vpc_id = aws_vpc.example_vpc.id
}

resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.example_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gateway.id
  }
}

resource "aws_route_table_association" "public_rt_assoc" {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_rt.id
}

resource "aws_security_group" "bastion_sg" {
  vpc_id = aws_vpc.example_vpc.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

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

resource "aws_security_group" "target_sg" {
  vpc_id = aws_vpc.example_vpc.id

  ingress {
    from_port   = 5432
    to_port     = 5432
    protocol    = "tcp"
    cidr_blocks = ["10.0.1.0/24"] # 踏み台サーバーからのみ許可
  }

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

resource "aws_instance" "bastion" {
  ami                    = "ami-034bc4e4fcccfe844"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public_subnet.id
  vpc_security_group_ids = [aws_security_group.bastion_sg.id]

  key_name = "iac-key" # 作成したキーペア名を指定

  tags = {
    Name = "bastion"
  }
}

resource "aws_instance" "target" {
  ami           = "ami-034bc4e4fcccfe844"
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.private_subnet.id
  vpc_security_group_ids = [aws_security_group.target_sg.id
  ]

  tags = {
    Name = "target"
  }
}

output "target_private_id" {
  value = aws_instance.target.private_ip
}

output "bastion_public_ip" {
  value = aws_instance.bastion.public_ip
}

Discussion