[アップデート]TerraformのAWS Transfer Family用のモジュールがSFTPコネクタに対応しました。
はじめに
先日AWS Transfer Family向けのTerraformのモジュールにSFTPコネクタ用のサブモジュールが追加されました。
SFTPコネクタは2年ほど前にリリースされたTransfer Familyサービス群のうちの一つの機能で、これを用いることでSFTPクライアントを別途準備することなくマネージド用いてS3とSFTPサーバ間のファイル転送の実行が可能となります。
元々Transfer FamilyのSFTPサーバ向けのモジュールが提供されていましたが、今回はそこに対してコネクタ用のサブモジュールが追加されTerraformでSFTPコネクタが作成できるようになった形となります。
今回は0.2.4を利用しますが、v0.2.0からの対応となります。
使ってみる
実際に利用してデプロイしてみます。
ドキュメントおよびリポジトリは以下となります。
パラメータ
執筆時点ではモジュールの親側のドキュメントやGithubのREADMEを見るとs3_bucket_arn
とurl
が必須となっていましたが、実際に利用してみる限りそちらの記載は誤りでサブモジュール側のドキュメントが正しい形になっていそうです。
※ VSCodeでもs3_bucket_arn
が必須の旨のSyntax 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 |
リソースに割り当てるタグ |
パラメータの大枠は以前マネジメントコンソールから構築した際の記事を見ていただくとわかるかと思いますのでこちらもご参照ください。
コード
今回上記を利用して構築した際のコードの全量は以下に格納しています。
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
}
]
})
}
実行
道中ログは単にリソースが生成されるだけなので割愛しますが以下のようにコネクタが作成されます。
特に指定はしていないもののCloudWatch Logs向けのロールが作成されるのと、信頼できるホストキーが作成される形となります。
ロールはログ出力用のAWS管理ポリシーであるAWSTransferLoggingAccess
が割り当てられます。
ホストキーについては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にあったアップデートでの追加機能に関連するスクリプトと見られます。
上記のアップデート以前ははホストキー(公開鍵)はユーザがサーバより取り出してSFTPコネクタ側に登録する必要があったのですが、このアップデートよりテスト接続時にホストキーをサービス側で取得し登録できるようになっており、上記のスクリプトはこれを利用した登録スクリプトとなります。
マネジメントコンソールの場合はこういった形でテスト接続時にキーを検出し登録することができます。
なお上記スクリプトは仮に実行時に接続失敗となってもterraform apply自体は成功扱いとなるのでご注意ください。
キーがうまく登録されていなければapplyのログを確認してみてください。自分はしばらくAWS CLIをアップデートしておらずバージョン不足で失敗していました。
終わりに
TerraformでSFTPコネクタの構築を実施してみました。
SFTPコネクタ側は既存の機能ですので目新しい話はありませんがIaCのできる手段が増えるのはとてもありがたいところです。
今回はSFTPサーバや認証情報を格納しているSecretsは既存のものを使う形をとっているためシンプルとなっていますが、SFTPサーバの事態の作成からワンライナーで完結しようとすると接続用のユーザをサーバ内で作成+秘密鍵も生成(or持ち込み)、さらにその情報をTerraformのモジュール側もしくはSecrets Managerに持ち込まないといけないので少し手間かもしれません。
SFTPコネクタはどちらかといえばAWS CLI等を入れられない既存SFTPサーバ向けの機能とは思いますがそういったケースに衝突した場合はご注意ください。