[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 버킷 장소에 파일이 업로드되었습니다

게시물 주소가 복사되었습니다.

이런 콘텐츠도 있어요!