[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.yml
S3
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 SFTP
2. 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 "${UserName
SSH 키 등록
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 박지은 매니저