[アップデート]TerraformのAWS Transfer Family用のモジュールがSFTPコネクタに対応しました。

[アップデート]TerraformのAWS Transfer Family用のモジュールがSFTPコネクタに対応しました。

2025.09.04

はじめに

先日AWS Transfer Family向けのTerraformのモジュールにSFTPコネクタ用のサブモジュールが追加されました。

https://aws.amazon.com/about-aws/whats-new/2025/08/aws-transfer-family-terraform-sftp-connectors/

SFTPコネクタは2年ほど前にリリースされたTransfer Familyサービス群のうちの一つの機能で、これを用いることでSFTPクライアントを別途準備することなくマネージド用いてS3とSFTPサーバ間のファイル転送の実行が可能となります。

https://dev.classmethod.jp/articles/aws-transfer-family-release-sftp-connector/

元々Transfer FamilyのSFTPサーバ向けのモジュールが提供されていましたが、今回はそこに対してコネクタ用のサブモジュールが追加されTerraformでSFTPコネクタが作成できるようになった形となります。

今回は0.2.4を利用しますが、v0.2.0からの対応となります。

terraform-transfer-family-module-015

terraform-transfer-family-module-020

使ってみる

実際に利用してデプロイしてみます。

ドキュメントおよびリポジトリは以下となります。

https://github.com/aws-ia/terraform-aws-transfer-family

https://registry.terraform.io/modules/aws-ia/transfer-family/aws/

パラメータ

執筆時点ではモジュールの親側のドキュメントやGithubのREADMEを見るとs3_bucket_arnurlが必須となっていましたが、実際に利用してみる限りそちらの記載は誤りでサブモジュール側のドキュメントが正しい形になっていそうです。

https://registry.terraform.io/modules/aws-ia/transfer-family/aws/0.2.4/submodules/transfer-connectors

※ VSCodeでもs3_bucket_arnが必須の旨のSyntax Error出るんですよね...

vscode-module-error

今回利用したv0.2.4時点でのパラメータは以下の通りです。

パラメータ名 説明 備考
access_role(必須) SFTPコネクターにアタッチするIAMロールのARN
url(必須) 接続先SFTPサーバーのURL
connector_name コネクタ名
logging_role CloudWatch Logs出力用のIAMロールARN
secret_name 認証情報を格納するSecretsの名前 sftp_username,sftp_private_keyを指定する場合
sftp_username SFTP認証用のユーザー名
sftp_private_key SFTP認証用の秘密鍵
user_secret_id 認証情報を格納したAWS Secrets ManagerシークレットのARN 上記2点を指定する代わりに既存のSecretを利用可
secrets_manager_kms_key_arn SecretsのKMS暗号化キーARN CMKを利用する場合
security_policy_name セキュリティポリシー名
test_connector_post_deployment デプロイ後にコネクター接続をテストするかどうか trueとした場合かつ接続成功の場合、検証用の公開鍵が登録される(後述)
trusted_host_keys 接続先検証用の公開鍵
tags リソースに割り当てるタグ

パラメータの大枠は以前マネジメントコンソールから構築した際の記事を見ていただくとわかるかと思いますのでこちらもご参照ください。

https://dev.classmethod.jp/articles/aws-transfer-family-release-sftp-connector/

https://dev.classmethod.jp/articles/sftp-connector-add-security-policy-setting/

コード

今回上記を利用して構築した際のコードの全量は以下に格納しています。

https://github.com/cm-suzuki-junya/sample-terraform-sftp-connector

IAMロールやS3バケット等は別途必要がありますので今回は以下の2種のモジュールに分割して作成しています。

.
└── modules
    ├── s3-storage         # 転送先バケット
    └── sftp-connector     # SFTPコネクタリソース(IAMロール含む)

s3-storageはバケットを作ってるだけなので割愛しますがsftp-connectorのmodule/resourceはtransfer-connectorsとIAMロールの作成になります。
secretsは少し横着する形になりますが既存のものを使っています(以前の検証時のものを再利用)。

module "transfer_connectors" {
  source = "aws-ia/transfer-family/aws//modules/transfer-connectors"

  url         = "sftp://${var.sftp_host}:${var.sftp_port}"
  access_role = aws_iam_role.connector_role.arn

  user_secret_id = var.user_secret_id
  trusted_host_keys = var.sftp_trusted_host_keys
  test_connector_post_deployment = true

  tags = {
    Name = var.connector_name
  }
}

resource "aws_iam_role" "connector_role" {
  name = "${var.connector_name}-connector-role"

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

}

resource "aws_iam_role_policy" "connector_s3_policy" {
  name = "${var.connector_name}-s3-policy"
  role = aws_iam_role.connector_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:DeleteObject",
          "s3:GetObjectVersion",
          "s3:GetObjectTagging",
          "s3:PutObjectTagging"
        ]
        Resource = "${var.s3_bucket_arn}/*"
      },
      {
        Effect = "Allow"
        Action = [
          "s3:ListBucket",
          "s3:GetBucketLocation"
        ]
        Resource = var.s3_bucket_arn
      }
    ]
  })
}

resource "aws_iam_role_policy" "connector_secrets_policy" {
  name = "${var.connector_name}-secrets-policy"
  role = aws_iam_role.connector_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:GetSecretValue",
          "secretsmanager:DescribeSecret"
        ]
        Resource = var.user_secret_id
      }
    ]
  })
}

実行

道中ログは単にリソースが生成されるだけなので割愛しますが以下のようにコネクタが作成されます。

created-connector-by-terraform

特に指定はしていないもののCloudWatch Logs向けのロールが作成されるのと、信頼できるホストキーが作成される形となります。

ロールはログ出力用のAWS管理ポリシーであるAWSTransferLoggingAccessが割り当てられます。

created-connector-log-role-by-terraform

ホストキーについてはtest_connector_post_deployment = trueとすると所定のスクリプトが実行され生成されます。

ログに出力されていたスクリプトを取り出しおよび成形すると以下のようなコードが流れており、これによりホストキーが自動生成されているようです。

#!/bin/sh

echo "Waiting 10 seconds for connector to be fully ready..."
sleep 10

if ! command -v aws &> /dev/null; then
  echo "AWS CLI not found - connector testing skipped"
  echo "Deployment completed successfully"
  exit 0
fi

AWS_VERSION=$(aws --version 2>&1 | cut -d'/' -f2 | cut -d' ' -f1)
if [ -n "$AWS_VERSION" ]; then
  MAJOR=$(echo "$AWS_VERSION" | cut -d'.' -f1)
  MINOR=$(echo "$AWS_VERSION" | cut -d'.' -f2)

  if [ "$MAJOR" -lt 2 ] || ([ "$MAJOR" -eq 2 ] && [ "$MINOR" -lt 28 ]); then
    echo "AWS CLI version $AWS_VERSION detected - connector testing requires version 2.28.x or above"
    echo "connector testing skipped"
    echo "Deployment completed successfully"
    exit 0
  fi
  echo "AWS CLI version $AWS_VERSION - version check passed"
else
  echo "Could not determine AWS CLI version - connector testing skipped"
  echo "Deployment completed successfully"
  exit 0
fi

if ! command -v jq &> /dev/null; then
  echo "jq not found - connector testing skipped"
  echo "Deployment completed successfully"
  exit 0
fi

echo "Testing connection to discover host key..."

MAX_RETRIES=3
RETRY_COUNT=0
HOST_KEY=""

while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ -z "$HOST_KEY" ]; do
  RETRY_COUNT=$((RETRY_COUNT + 1))
  echo "Attempt $RETRY_COUNT/$MAX_RETRIES: Testing connection..."

  DISCOVERY_RESULT=$(aws transfer test-connection \
    --connector-id c-xxxxx \
    --region ap-northeast-1 \
    --output json 2>/dev/null || echo '{}')

  echo "DEBUG - Discovery Result: $DISCOVERY_RESULT"

  STATUS=$(echo "$DISCOVERY_RESULT" | jq -r '.Status // empty')
  echo "DEBUG - Status: $STATUS"

  if [ "$STATUS" = "ERROR" ]; then
    ERROR_MSG=$(echo "$DISCOVERY_RESULT" | jq -r '.StatusMessage // empty')
    echo "Connection test failed: $ERROR_MSG"
    echo "DEBUG - Full error response: $DISCOVERY_RESULT"

    if echo "$ERROR_MSG" | grep -q "Cannot access secret manager"; then
      echo "Secret manager not ready, waiting 10 seconds..."
      sleep 10
      continue
    fi
  elif [ "$STATUS" = "OK" ]; then
    echo "Connection test successful - connector is properly configured"
    echo "Deployment completed successfully"
    exit 0
  fi

  HOST_KEY=$(echo "$DISCOVERY_RESULT" | jq -r '.SftpConnectionDetails.HostKey // empty')
  echo "DEBUG - Host Key: $HOST_KEY"

  if [ -n "$HOST_KEY" ] && [ "$HOST_KEY" != "null" ]; then
    echo "Host key discovered: $HOST_KEY"
    break
  else
    echo "Host key not found, retrying in 10s..."
    sleep 10
  fi
done

if [ -n "$HOST_KEY" ] && [ "$HOST_KEY" != "null" ]; then
  echo "Updating connector with discovered host key..."
  UPDATE_RESULT=$(aws transfer update-connector \
    --connector-id c-xxxxx \
    --region ap-northeast-1 \
    --url "sftp://sftp.example.com:22" \
    --access-role "arn:aws:iam::111122223333:role/dev-sftp-connector-connector-role" \
    --logging-role "arn:aws:iam::111122223333:role/transfer-connector-logging-role-sftp-connector" \
    --sftp-config "UserSecretId=arn:aws:secretsmanager:ap-northeast-1:111122223333:secret:aws/transfer/sftp-connector-bw-bastion-xxxxx,TrustedHostKeys=$HOST_KEY" \
    --output json)

  echo "DEBUG - Update Result: $UPDATE_RESULT"

  echo "Testing final connection with trusted host key..."
  FINAL_TEST=$(aws transfer test-connection \
    --connector-id c-xxxxx \
    --region ap-northeast-1 \
    --output json)

  echo "DEBUG - Final Test Result: $FINAL_TEST"

  FINAL_STATUS=$(echo "$FINAL_TEST" | jq -r '.Status')
  echo "Final connection status: $FINAL_STATUS"

  if [ "$FINAL_STATUS" = "OK" ]; then
    echo "Connector configured and tested successfully"
  else
    echo "Final test failed: $FINAL_STATUS"
  fi
fi

こちらのアップデートは執筆していなかったのですが2025/04にあったアップデートでの追加機能に関連するスクリプトと見られます。

https://aws.amazon.com/about-aws/whats-new/2025/04/aws-transfer-family-configuration-options-sftp-connectors/

上記のアップデート以前ははホストキー(公開鍵)はユーザがサーバより取り出してSFTPコネクタ側に登録する必要があったのですが、このアップデートよりテスト接続時にホストキーをサービス側で取得し登録できるようになっており、上記のスクリプトはこれを利用した登録スクリプトとなります。

マネジメントコンソールの場合はこういった形でテスト接続時にキーを検出し登録することができます。

sftp-connector-first-test-connect

sftp-connector-test-public-key

なお上記スクリプトは仮に実行時に接続失敗となってもterraform apply自体は成功扱いとなるのでご注意ください。

キーがうまく登録されていなければapplyのログを確認してみてください。自分はしばらくAWS CLIをアップデートしておらずバージョン不足で失敗していました。

終わりに

TerraformでSFTPコネクタの構築を実施してみました。

SFTPコネクタ側は既存の機能ですので目新しい話はありませんがIaCのできる手段が増えるのはとてもありがたいところです。

今回はSFTPサーバや認証情報を格納しているSecretsは既存のものを使う形をとっているためシンプルとなっていますが、SFTPサーバの事態の作成からワンライナーで完結しようとすると接続用のユーザをサーバ内で作成+秘密鍵も生成(or持ち込み)、さらにその情報をTerraformのモジュール側もしくはSecrets Managerに持ち込まないといけないので少し手間かもしれません。

SFTPコネクタはどちらかといえばAWS CLI等を入れられない既存SFTPサーバ向けの機能とは思いますがそういったケースに衝突した場合はご注意ください。

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.