- Transfer FamilyはSFTP/FTPS/FTPをフルマネージドで提供、サーバー運用不要
- S3/EFSをバックエンドにしたスケーラブルなファイル転送基盤を即構築
- B2Bファイル連携案件の月単価は70〜100万円、金融・物流で需要急増中
企業間のデータ連携において、**SFTP(SSH File Transfer Protocol)**は2026年現在も最も広く使われているファイル転送プロトコルです。特に金融機関、物流、医療、製造業では、日次バッチ処理やEDI(電子データ交換)でSFTPが必須となっています。
しかし、SFTPサーバーの自前運用はセキュリティパッチ適用、SSH鍵管理、可用性確保、スケーリングと運用負担が大きいのが現実です。AWS Transfer Familyを使えば、これらの運用をすべてAWSに委ね、ファイル転送の自動化に集中できます。
- AWS Transfer Familyの全体像と対応プロトコル
- SFTP/FTPSサーバーの構築手順
- S3連携によるファイル自動処理パイプライン
- 認証方式の選択と設計(SSH鍵、Active Directory、カスタム認証)
- B2Bファイル連携のセキュリティベストプラクティス
AWS Transfer Familyとは
概要
AWS Transfer Familyは、SFTP、FTPS、FTP、AS2プロトコルに対応したフルマネージドファイル転送サービスです。ファイルの保存先としてAmazon S3またはAmazon EFSを使用できます。
| プロトコル | ポート | 暗号化 | 主な用途 |
|---|---|---|---|
| SFTP | 22 | SSH | B2B連携、最も一般的 |
| FTPS | 990 | TLS | レガシーFTPのセキュア化 |
| FTP | 21 | なし | テスト・社内のみ |
| AS2 | 443 | S/MIME | EDI、小売・流通 |
従来のSFTPサーバー運用との比較
| 項目 | EC2自前運用 | Transfer Family |
|---|---|---|
| サーバー管理 | 必要(OS、パッチ) | 不要 |
| 高可用性 | Multi-AZ構成要 | 自動(99.99% SLA) |
| スケーリング | 手動 | 自動 |
| SSH鍵管理 | authorized_keys手動管理 | IAM / カスタム認証 |
| ログ | 自前設定 | CloudWatch自動連携 |
| 料金 | EC2 + EBS | $0.30/時間 + 転送量 |
ユースケース
- 金融機関: 日次の取引データ・帳票ファイルの自動連携
- 物流: 在庫データ・出荷情報のEDI
- 医療: 検査結果・診療レポートのセキュア転送
- 製造: サプライチェーンのデータ連携
- SaaS: 顧客データの一括インポート/エクスポート
SFTPサーバーの構築
Terraformによるインフラ構築
# main.tf
# Transfer Familyサーバー(SFTP)
resource "aws_transfer_server" "sftp" {
identity_provider_type = "SERVICE_MANAGED"
protocols = ["SFTP"]
endpoint_type = "PUBLIC"
logging_role = aws_iam_role.transfer_logging.arn
structured_log_destinations = [
"${aws_cloudwatch_log_group.transfer.arn}:*"
]
security_policy_name = "TransferSecurityPolicy-2024-01"
tags = {
Name = "sftp-server"
Environment = "production"
Project = "b2b-integration"
}
}
# CloudWatch ロググループ
resource "aws_cloudwatch_log_group" "transfer" {
name = "/aws/transfer/${aws_transfer_server.sftp.id}"
retention_in_days = 90
}
# ロギング用IAMロール
resource "aws_iam_role" "transfer_logging" {
name = "transfer-logging-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_attachment" "transfer_logging" {
role = aws_iam_role.transfer_logging.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSTransferLoggingAccess"
}
# S3バケット(ファイル保存先)
resource "aws_s3_bucket" "transfer" {
bucket = "my-sftp-transfer-bucket"
tags = {
Name = "sftp-transfer-files"
}
}
# S3バケットの暗号化設定
resource "aws_s3_bucket_server_side_encryption_configuration" "transfer" {
bucket = aws_s3_bucket.transfer.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.transfer.arn
}
}
}
# KMS暗号化キー
resource "aws_kms_key" "transfer" {
description = "SFTP Transfer encryption key"
deletion_window_in_days = 10
enable_key_rotation = true
}
# S3バケットのライフサイクルルール
resource "aws_s3_bucket_lifecycle_configuration" "transfer" {
bucket = aws_s3_bucket.transfer.id
rule {
id = "archive-old-files"
status = "Enabled"
transition {
days = 30
storage_class = "STANDARD_IA"
}
transition {
days = 90
storage_class = "GLACIER"
}
expiration {
days = 365
}
}
}
ユーザーの作成と権限設定
# SFTPユーザー用IAMロール
resource "aws_iam_role" "sftp_user" {
name = "sftp-user-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "transfer.amazonaws.com"
}
}]
})
}
# S3アクセスポリシー(ユーザーごとのディレクトリ制限)
resource "aws_iam_role_policy" "sftp_user_s3" {
name = "sftp-user-s3-access"
role = aws_iam_role.sftp_user.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowListBucket"
Effect = "Allow"
Action = ["s3:ListBucket"]
Resource = [aws_s3_bucket.transfer.arn]
Condition = {
StringLike = {
"s3:prefix" = ["$${transfer:UserName}/*", "$${transfer:UserName}"]
}
}
},
{
Sid = "AllowObjectAccess"
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:GetObjectVersion"
]
Resource = ["${aws_s3_bucket.transfer.arn}/$${transfer:UserName}/*"]
}
]
})
}
# SFTPユーザー作成
resource "aws_transfer_user" "partner_a" {
server_id = aws_transfer_server.sftp.id
user_name = "partner-a"
role = aws_iam_role.sftp_user.arn
home_directory_type = "LOGICAL"
home_directory_mappings {
entry = "/"
target = "/${aws_s3_bucket.transfer.id}/partner-a"
}
tags = {
Company = "Partner A Co., Ltd."
}
}
# SSH公開鍵の登録
resource "aws_transfer_ssh_key" "partner_a" {
server_id = aws_transfer_server.sftp.id
user_name = aws_transfer_user.partner_a.user_name
body = file("${path.module}/keys/partner-a.pub")
}
ファイル処理の自動化パイプライン
S3イベント → Lambda → 後続処理
ファイルがアップロードされたら自動的に処理するパイプラインを構築します。
# Lambda関数(ファイル処理)
resource "aws_lambda_function" "process_file" {
filename = "lambda/process-file.zip"
function_name = "sftp-file-processor"
role = aws_iam_role.lambda_exec.arn
handler = "index.handler"
runtime = "nodejs20.x"
timeout = 300
memory_size = 512
environment {
variables = {
NOTIFICATION_TOPIC_ARN = aws_sns_topic.file_notification.arn
DDB_TABLE_NAME = aws_dynamodb_table.file_log.name
}
}
}
# S3イベント通知
resource "aws_s3_bucket_notification" "transfer" {
bucket = aws_s3_bucket.transfer.id
lambda_function {
lambda_function_arn = aws_lambda_function.process_file.arn
events = ["s3:ObjectCreated:*"]
filter_suffix = ".csv"
}
lambda_function {
lambda_function_arn = aws_lambda_function.process_file.arn
events = ["s3:ObjectCreated:*"]
filter_suffix = ".xml"
}
}
Lambda関数の実装例:
// lambda/process-file/index.ts
import { S3Event, Context } from 'aws-lambda';
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
import { parse } from 'csv-parse/sync';
const s3 = new S3Client({});
const sns = new SNSClient({});
const ddb = new DynamoDBClient({});
export async function handler(event: S3Event, context: Context) {
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key);
const size = record.s3.object.size;
console.log(`ファイル受信: s3://${bucket}/${key} (${size} bytes)`);
try {
// ファイル取得
const obj = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
const body = await obj.Body!.transformToString('utf-8');
// パートナー名とファイル種別を抽出
const [partner, ...rest] = key.split('/');
const fileName = rest.join('/');
// CSV処理
let recordCount = 0;
if (key.endsWith('.csv')) {
const records = parse(body, { columns: true, skip_empty_lines: true });
recordCount = records.length;
console.log(`CSVレコード数: ${recordCount}`);
// ここでデータベースに投入する処理など
}
// 処理ログをDynamoDBに記録
await ddb.send(new PutItemCommand({
TableName: process.env.DDB_TABLE_NAME!,
Item: {
pk: { S: `PARTNER#${partner}` },
sk: { S: `FILE#${new Date().toISOString()}#${fileName}` },
fileName: { S: fileName },
fileSize: { N: String(size) },
recordCount: { N: String(recordCount) },
status: { S: 'PROCESSED' },
processedAt: { S: new Date().toISOString() },
},
}));
// 成功通知
await sns.send(new PublishCommand({
TopicArn: process.env.NOTIFICATION_TOPIC_ARN!,
Subject: `[SFTP] ファイル処理完了: ${fileName}`,
Message: JSON.stringify({
partner,
fileName,
fileSize: size,
recordCount,
status: 'SUCCESS',
processedAt: new Date().toISOString(),
}),
}));
} catch (error) {
console.error(`ファイル処理エラー: ${key}`, error);
// エラー通知
await sns.send(new PublishCommand({
TopicArn: process.env.NOTIFICATION_TOPIC_ARN!,
Subject: `[SFTP] ファイル処理エラー: ${key}`,
Message: JSON.stringify({
key,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
}),
}));
throw error;
}
}
}
カスタム認証の実装
API Gateway + Lambda認証
取引先ごとに異なる認証ロジック(例:IPアドレス制限、時間帯制限)を実装する場合、カスタム認証プロバイダーを使用します。
# カスタム認証サーバー
resource "aws_transfer_server" "sftp_custom_auth" {
identity_provider_type = "API_GATEWAY"
url = aws_api_gateway_deployment.transfer_auth.invoke_url
invocation_role = aws_iam_role.transfer_auth_invocation.arn
protocols = ["SFTP"]
endpoint_type = "PUBLIC"
logging_role = aws_iam_role.transfer_logging.arn
}
// lambda/custom-auth/index.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient, GetItemCommand } from '@aws-sdk/client-dynamodb';
const ddb = new DynamoDBClient({});
export async function handler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
const username = event.pathParameters?.username;
const password = event.headers?.Password;
const sourceIp = event.requestContext.identity.sourceIp;
const protocol = event.queryStringParameters?.protocol;
const serverId = event.queryStringParameters?.serverId;
console.log(`認証リクエスト: user=${username}, ip=${sourceIp}, protocol=${protocol}`);
// ユーザー情報を取得
const userResult = await ddb.send(new GetItemCommand({
TableName: 'sftp-users',
Key: { username: { S: username! } },
}));
if (!userResult.Item) {
console.log(`認証失敗: ユーザー不明 (${username})`);
return { statusCode: 200, body: JSON.stringify({}) };
}
const user = userResult.Item;
// IPアドレス制限チェック
const allowedIps = user.allowedIps?.SS || [];
if (allowedIps.length > 0 && !allowedIps.includes(sourceIp!)) {
console.log(`認証失敗: IP制限 (${sourceIp} not in ${allowedIps})`);
return { statusCode: 200, body: JSON.stringify({}) };
}
// 時間帯制限チェック(JST 9:00-18:00のみ許可の場合)
if (user.timeRestriction?.BOOL) {
const jstHour = new Date().getUTCHours() + 9;
if (jstHour < 9 || jstHour >= 18) {
console.log(`認証失敗: 時間帯制限 (JST ${jstHour}時)`);
return { statusCode: 200, body: JSON.stringify({}) };
}
}
// 認証成功
console.log(`認証成功: ${username}`);
return {
statusCode: 200,
body: JSON.stringify({
Role: user.role.S,
HomeDirectoryType: 'LOGICAL',
HomeDirectoryDetails: JSON.stringify([
{
Entry: '/',
Target: `/${user.bucket.S}/${username}`,
},
]),
}),
};
}
セキュリティベストプラクティス
セキュリティポリシーの選択
AWS Transfer Familyは複数のセキュリティポリシーを提供しています:
| ポリシー | 最小TLS | 暗号スイート | 推奨用途 |
|---|---|---|---|
| TransferSecurityPolicy-2024-01 | TLS 1.2 | 最新のみ | 本番環境推奨 |
| TransferSecurityPolicy-2023-05 | TLS 1.2 | やや広い | 互換性重視 |
| TransferSecurityPolicy-2020-06 | TLS 1.2 | レガシー含む | レガシー対応 |
VPCエンドポイントでのプライベートアクセス
インターネット経由でなくVPC内部からのみアクセスする場合:
# VPCエンドポイントタイプのSFTPサーバー
resource "aws_transfer_server" "sftp_vpc" {
identity_provider_type = "SERVICE_MANAGED"
protocols = ["SFTP"]
endpoint_type = "VPC"
endpoint_details {
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.sftp.id]
vpc_id = var.vpc_id
}
}
resource "aws_security_group" "sftp" {
name_prefix = "sftp-"
vpc_id = var.vpc_id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.allowed_cidr_blocks
description = "SFTP access from trusted networks"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
監査ログとアラート
# CloudWatch アラート(認証失敗の検知)
resource "aws_cloudwatch_metric_alarm" "sftp_auth_failure" {
alarm_name = "sftp-auth-failure-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "FilesIn"
namespace = "AWS/Transfer"
period = 300
statistic = "Sum"
threshold = 0
alarm_description = "SFTP認証失敗が多発しています"
alarm_actions = [aws_sns_topic.alerts.arn]
dimensions = {
ServerId = aws_transfer_server.sftp.id
}
}
運用と監視
CloudWatchダッシュボード
{
"widgets": [
{
"type": "metric",
"properties": {
"title": "ファイル転送量",
"metrics": [
["AWS/Transfer", "BytesIn", "ServerId", "s-xxxx"],
["AWS/Transfer", "BytesOut", "ServerId", "s-xxxx"]
],
"period": 3600,
"stat": "Sum"
}
},
{
"type": "metric",
"properties": {
"title": "ファイル数",
"metrics": [
["AWS/Transfer", "FilesIn", "ServerId", "s-xxxx"],
["AWS/Transfer", "FilesOut", "ServerId", "s-xxxx"]
],
"period": 3600,
"stat": "Sum"
}
}
]
}
SESエンジニアのキャリア戦略
Transfer Familyスキルの市場価値
B2Bファイル連携は「地味だが無くならない」分野であり、安定した需要があります:
| ポジション | 月単価 | 求められるスキル |
|---|---|---|
| インフラエンジニア | 70〜90万円 | Transfer Family構築、Terraform |
| インテグレーションエンジニア | 80〜110万円 | B2Bデータ連携、Lambda処理 |
| セキュリティエンジニア | 90〜120万円 | 暗号化設計、監査ログ分析 |
スキルの掛け合わせ
- Transfer Family + Step Functions: 複雑なファイル処理ワークフローの構築
- Transfer Family + EventBridge: イベント駆動のリアルタイム連携
- Transfer Family + Glue: ETLパイプラインとの統合
- Transfer Family + KMS: データ暗号化とコンプライアンス対応

まとめ
AWS Transfer Familyは、SFTPサーバーの運用負担をゼロにしながら、セキュアなB2Bファイル連携基盤を実現するサービスです。
- フルマネージド: サーバー管理・パッチ適用・高可用性が自動
- S3統合: ファイル保存先としてS3を使用し、Lambda連携で自動処理
- 柔軟な認証: SSH鍵、Active Directory、カスタム認証に対応
- セキュリティ: KMS暗号化、VPCエンドポイント、監査ログ
B2Bファイル連携のスキルは、金融・物流・製造業のSES案件で安定した需要があります。Transfer Familyを使いこなして、キャリアの幅を広げましょう。
SES BASEでは、AWSインフラ構築・B2Bインテグレーションの高単価案件を多数掲載。Transfer FamilyやTerraformのスキルを活かせる案件をチェックしましょう。