[Tech blog] Transfer Family와 CloudFormation을 사용한 SFTP환경의 구축 방법
들어가며
지난 1년간 업무를 하며 ‘폐쇄망=인터넷 액세스가 불가한 안전한 환경에서 운영을 하고 싶다’는 요청을 받는 일이 늘어났습니다.
경우에 따라 구성은 바뀌지만, 폐쇄 공간에 안전하게 파일을 캡처하는 수단으로 Transfer Family의 이용을 검토했습니다.
이번에는 ‘SFTP 처리의 인프라 구축의 자동화를 실시하는 방법’이라는 테마로 블로그를 작성하고자 합니다.
목차
1. IaC에서 기본 인프라 배포
2. Transfer Family 사용자 세트
3. SFTP 작업
1. IaC에서 기본 인프라 배포
CloudFormation에서 다음과 유사한 AWS 리소스를 배포합니다.
% tree
.
└── cfn
├── network.yml
├── s3.yml
├── transfer-user.yml
└── transfer.ymlS3
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
Prefix:
Type: String
Default: test
Resources:
SFTPBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${Prefix}-sftp-bucket
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
Outputs:
SFTPBucket:
Value: !Ref SFTPBucket
Export:
Name: !Sub SFTPBucket- 파일을 저장할 S3 버킷.
- 파라미터의 Prefix는 환경에 맞추어 문자열을 넣어 주세요. 리소스 이름의 접두사로 반영됩니다.
BucketName: !Sub ${Prefix}-sftp-bucket
네트워크 리소스/기타 Transfer Family 관련 리소스
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
Prefix:
Type: String
Default: test
VPCCiderBlock:
Type: String
Default: 10.0.0.0/16
PublicCiderBlock1A:
Type: String
Default: 10.0.1.0/24
PublicCiderBlock1C:
Type: String
Default: 10.0.2.0/24
PrivateCiderBlock1A:
Type: String
Default: 10.0.3.0/24
PrivateCiderBlock1C:
Type: String
Default: 10.0.4.0/24
Source:
Type: "String"
Default: "0.0.0.0/0"
Description: "Source of Connection"
Resources:
TransferVPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !Ref VPCCiderBlock
EnableDnsHostnames: "true"
EnableDnsSupport: "true"
TransferInternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: "Name"
Value: "transfer-igw"
AttachTransferIGW:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref "TransferInternetGateway"
VpcId: !Ref "TransferVPC"
TransferPublicSubnet1A:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PublicCiderBlock1A
VpcId: !Ref TransferVPC
Tags:
- Key: "Name"
Value: "TransferPublicSubnet1A"
TransferPublicSubnet1C:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PublicCiderBlock1C
VpcId: !Ref TransferVPC
Tags:
- Key: "Name"
Value: "TransferPublicSubnet1C"
TransferPrivateSubnet1A:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PrivateCiderBlock1A
VpcId: !Ref TransferVPC
Tags:
- Key: "Name"
Value: "TransferPrivateSubnet1A"
TransferPrivateSubnet1C:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PrivateCiderBlock1C
VpcId: !Ref TransferVPC
Tags:
- Key: "Name"
Value: "TransferPrivateSubnet1C"
TransferPublicRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref TransferVPC
Tags:
- Key: "Name"
Value: "TransferPublicRouteTable"
TransferPrivateRouteTableA:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref TransferVPC
Tags:
- Key: "Name"
Value: "TransferPrivateRouteTableA"
TransferPrivateRouteTableC:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref TransferVPC
Tags:
- Key: "Name"
Value: "TransferPrivateRouteTableC"
AttachTransferPublicRouteIGW:
Type: "AWS::EC2::Route"
DependsOn:
- TransferInternetGateway
- AttachTransferIGW
Properties:
RouteTableId: !Ref TransferPublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref TransferInternetGateway
AtachPublicSubnetRouteA:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref TransferPublicSubnet1A
RouteTableId: !Ref TransferPublicRouteTable
AtachPublicSubnetRouteC:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref TransferPublicSubnet1C
RouteTableId: !Ref TransferPublicRouteTable
AtachPrivateSubnetRouteA:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref TransferPrivateSubnet1A
RouteTableId: !Ref TransferPrivateRouteTableA
AtachPrivateSubnetRouteC:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref TransferPrivateSubnet1C
RouteTableId: !Ref TransferPrivateRouteTableC
ElasticIP1:
Type: "AWS::EC2::EIP"
Properties:
Domain: "vpc"
TransferSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "SG of Upload"
VpcId: !Ref TransferVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref Source
TransferIAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: "transferfamily-sftp-role"
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- transfer.amazonaws.com
Action:
- sts:AssumeRole
Path: /
InlinePolicy:
Type: AWS::IAM::Policy
Properties:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- s3:ListBucket
- s3:GetBucketLocation
Resource: "arn:aws:s3:::*"
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:DeleteObjectVersion
- s3:DeleteObject
- s3:GetObjectVersion
- s3:PutObjectACL
Resource: "arn:aws:s3:::*"
PolicyName: "trasfer-inline"
Roles:
- !Ref TransferIAMRole
TransferLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: "/aws/transfer/sftp-logs"
Outputs:
TransferVPC:
Value: !Ref TransferVPC
Export:
Name: !Sub TransferVPC
TransferInternetGateway:
Value: !Ref TransferInternetGateway
Export:
Name: !Sub TransferInternetGateway
TransferPublicSubnet1A:
Value: !Ref TransferPublicSubnet1A
Export:
Name: !Sub TransferPublicSubnet1A
TransferPublicSubnet1C:
Value: !Ref TransferPublicSubnet1C
Export:
Name: !Sub TransferPublicSubnet1C
TransferPrivateSubnet1A:
Value: !Ref TransferPrivateSubnet1A
Export:
Name: !Sub TransferPrivateSubnet1A
TransferPrivateSubnet1C:
Value: !Ref TransferPrivateSubnet1C
Export:
Name: !Sub TransferPrivateSubnet1C
TransferIAMRole:
Value: !GetAtt TransferIAMRole.Arn
Export:
Name: !Sub TransferIAMRole
TransferLogGroup:
Value: !GetAtt TransferLogGroup.Arn
Export:
Name: !Sub TransferLogGroup
ElasticIP1:
Value: !GetAtt ElasticIP1.AllocationId
Export:
Name: !Sub ElasticIP1
TransferSecurityGroup:
Value: !Ref TransferSecurityGroup
Export:
Name: !Sub TransferSecurityGroup- VPC와 서브넷의 네트워크 범위는 환경에 맞게 변경 해 주세요.
Transfer Family 배포
AWSTemplateFormatVersion: "2010-09-09"
Resources:
SFTP:
Type: AWS::Transfer::Server
Properties:
Domain: S3
Protocols:
- SFTP
EndpointType: VPC
EndpointDetails:
AddressAllocationIds:
- !ImportValue ElasticIP1
SecurityGroupIds:
- !ImportValue TransferSecurityGroup
SubnetIds:
- !ImportValue TransferPublicSubnet1A
VpcId: !ImportValue TransferVPC
IdentityProviderType: SERVICE_MANAGED
StructuredLogDestinations:
- !ImportValue TransferLogGroup
Outputs:
SFTP:
Value: !GetAtt SFTP.ServerId
Export:
Name: !Sub SFTP2. Transfer Family 사용자 세트
https://docs.aws.amazon.com/ko_kr/transfer/latest/userguide/create-user.html
사용자 만들기
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
UserName:
Type: String
Default: tanaka
Resources:
TransferUserIn:
Type: AWS::Transfer::User
Properties:
ServerId: !ImportValue SFTP
HomeDirectory: !Sub
- "/${ImportedValue}/${UserName}"
- ImportedValue: !ImportValue SFTPBucket
HomeDirectoryType: "PATH"
Role: !ImportValue TransferIAMRole
UserName: !Sub "${UserNameSSH 키 등록
https://docs.aws.amazon.com/ko_kr/transfer/latest/userguide/key-management.html

- AWS Management Console에서 Transfer Family 세부정보 페이지로 이동합니다. 생성한 User의 SSH public keys에 공개키를 등록합니다.
3. SFTP 조작
https://docs.aws.amazon.com/ko_kr/transfer/latest/userguide/transfer-file.html
% sftp -i [※transfer-key] [※sftp_user]@[※service_endpoint]
※ transfer-key: SSH 프라이빗 키
※ sftp_user: 사용자 이름
※ service_endpoint: 선택한 서버의 AWS Transfer Family 콘솔에 표시된 서버 엔드포인트
상기를 환경에 맞게 변경하고 명령을 실행하십시오.
sftp> put hello.txt Uploading hello.txt to /xxxxx-bucket/xxxx/hello.txt hello.txt
S3 버킷 장소에 파일이 업로드되었습니다
- 작성자: Aga Hiroaki
- 원문: https://zenn.dev/megazone_jp/articles/08d763f1d73f85
- 번역: Cloud Technology Center 박지은 매니저

