TransferFamilyの論理ディレクトリについて調べてみた

2022.08.23

お久しぶりです。新卒の南です。 今回は案件でAWS Transfer Familyを使う機会がありました。

今回は機能の一つである論理ディレクトリに関してまとめられている記事が少なかったので、解説していきます。

論理ディレクトリとは?

論理ディレクトリを使用することでストレージ階層内の目的の場所にユーザーのルートディレクトリを設定できます。 chrootコマンドみたいな感じですね。
この機能を使うことによってSFTPサーバのディレクトリ構造を簡素化することができ、自由に仮想ディレクトリ構造を構築できます。

論理ディレクトリを利用するメリット

S3バケット名を隠すことができる

第一のメリットとして、サーバーを利用するユーザーがS3バケット名を気にすることなく利用することが出来ます。

classmethodというS3バケットに対してSFTPサーバーを設定し、cm-minamiというホームディレクトリを設定した場合、ユーザーには

論理ディレクトリを使わない場合
$ pwd
/classmethod/cm-minami

といった感じで表示がされますが、論理ディレクトリを使うことでS3バケット名を隠すことができます。

論理ディレクトリを使った場合
$ pwd
/

また、スコープダウンポリシーによって

/classmethod/cm-minami

でアクセス制御されている場合、cd ..コマンドで上位の階層に移動してしまった際、Permission deniedになってしまいます。
そうなってしまった場合、ユーザーは一度ログアウトして再びログインしなければなりませんが、論理ディレクトリを利用することでこの状況が発生するのを防止できます。

論理ディレクトリを使わない場合
$ pwd
/classmethod/cm-minami
$ cd ..
$ pwd
/classmethod
$ ls 
Couldn't read directory: Permission denied
論理ディレクトリを使った場合
$ pwd
/
$ cd ..
$ pwd
/
$ ls
#cm-minami以下のファイルが表示される

ここからはあまり知られていない隠れたメリットになります。(個人的にはこっち方がメインのメリットな気が)

複数のS3バケットを組み合わせることができる

論理ディレクトリを利用することで、S3バケットの任意の場所にあるターゲットと組み合わせて独自のディレクトリ構造を作成することができます。
設定の際は実際に論理ディレクトリで表示させたいエントリーと任意のS3バケットの場所のターゲットをペアで設定することになります。

こちらのような構成の場合、以下のようなエントリーとターゲットの組み合わせとなります。

[
    {"Entry": "/archive", "Target": "/bucketA/pics"}, 
    {"Entry": "/projectX", "Target": "/bucketB/PROJECTX"},
    {"Entry": "/projectY", "Target": "/bucketB/project_y"},
    {"Entry": "/projectZ", "Target": "/bucketC/projectZ/projectZ-v2"}
]

S3のアクセスポイントも利用可能

S3 アクセスポイントで、バケット名形式のエイリアス(別名)が利用可能になったことで、こちらもターゲットとして指定することが可能となりました!

これにより細かいアクセス制御も簡単になります。
詳細については次回のブログをお待ちください。

論理ディレクトリの設定方法

現時点(2022/08/19)でマネジメントコンソール上では論理ディレクトリを設定することができません。 AWSCLIもしくはCloudformationを利用する必要があります。

今回はAWS CLIを使った方法を紹介します。

SFTPサーバーや必要なIAMロール周りは以下のCfnテンプレートを参考ください。

AWSTemplateFormatVersion: '2010-09-09'

Resources:

#S3バケットの作成
  HomeS3bucketA:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
      BucketName: !Join
        - '-'
        - - 'sample-bucket-a'
          - !Ref 'AWS::AccountId'

  HomeS3bucketB:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
      BucketName: !Join
        - '-'
        - - 'sample-bucket-b'
          - !Ref 'AWS::AccountId'

  HomeS3bucketC:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
      BucketName: !Join
        - '-'
        - - 'sample-bucket-c'
          - !Ref 'AWS::AccountId'

#S3アクセス用のIAM Role作成
  SFTPRole:
    Type: AWS::IAM::Role
    #DeletionPolicy: Retain
    Properties:
      RoleName: "sample-sftp-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "transfer.amazonaws.com"
            Action:
              - "sts:AssumeRole"
  SFTPPolicy:
    Type: 'AWS::IAM::Policy'
    #DeletionPolicy: Retain
    Properties:
      PolicyName: "sample-sftp-policy"
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - s3:ListBucket
              - s3:GetBucketLocation
            Resource: 
              - !GetAtt HomeS3bucketA.Arn
              - !GetAtt HomeS3bucketB.Arn
              - !GetAtt HomeS3bucketC.Arn

          - Effect: Allow
            Action:
              - s3:PutObject
              - s3:GetObject
              - s3:DeleteObjectVersion
              - s3:DeleteObject
              - s3:GetObjectVersion
            Resource: 
              -!Join
                - ''
                - - 'arn:aws:s3:::'
                    - !Ref HomeS3bucketA
                    - /*
              -!Join
                - ''
                - - 'arn:aws:s3:::'
                    - !Ref HomeS3bucketB
                    - /*
              -!Join
                - ''
                - - 'arn:aws:s3:::'
                    - !Ref HomeS3bucketC
                    - /*
      Roles:
        - !Ref SFTPRole

#ログ書き込み用のIAM Role作成
  SFTPLogRole:
    Type: AWS::IAM::Role
    #DeletionPolicy: Retain
    Properties:
      RoleName: "sample-sftp-log-role"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: transfer.amazonaws.com
            Action: sts:AssumeRole
  SFTPLogPolicy:
    Type: 'AWS::IAM::Policy'
    #DeletionPolicy: Retain
    Properties:
      PolicyName: "sample-sftp-log-policy"
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogStream
              - logs:DescribeLogStreams
              - logs:CreateLogGroup
              - logs:PutLogEvents
            Resource: '*'
      Roles:
        - !Ref SFTPLogRole

#SFTP Serverの作成 
  SFTPServer:
    Type: AWS::Transfer::Server
    #DeletionPolicy: Retain
    Properties:
      EndpointType: PUBLIC
      IdentityProviderType: SERVICE_MANAGED
      LoggingRole: !GetAtt SFTPLogRole.Arn
      SecurityPolicyName: TransferSecurityPolicy-2020-06

ユーザの作成

今回はこちらの構成で作成してみます。

Keyを作成していない場合はssh-keygenで作成しておきます。

 aws transfer create-user --server-id="s-XXXXXXXXXXXXXXXXXXX" --user-name="cm-minami" --role="arn:aws:iam::XXXXXXXXXXXXXXX:role/" --ssh-public-key-body="`cat ~/.ssh/id_rsa.pub`" --home-directory-type LOGICAL --home-directory-mapping ‘[
    {"Entry": "/archive", "Target": "/bucketA/pics"}, 
    {"Entry": "/projectX", "Target": "/bucketB/PROJECTX"},
    {"Entry": "/projectY", "Target": "/bucketB/project_y"},
    {"Entry": "/projectZ", "Target": "/bucketC/projectZ/projectZ-v2"}
]’

このユーザーでログインすると、/archive, /projectX, /projectY, /projectZ があるルートディレクトリに移動します。

$ pwd
/
$ ls
archive
projectX
projectY
projectZ

注意点

  • エントリーが「/」の場合、重複するパスは設定できない為マッピングを1つしか設定できません。
  • ターゲットに異なるS3バケットを指定する際はIAMロールで各S3バケットに対する許可が必要になります。
  • 論理ディレクトリを設定する場合はホームディレクトリ(HomeDirectoryパラメータ)を指定することはできません。

最後に

論理ディレクトリを利用することで、ユーザーによって細かいアクセス制御を行いたい場合に便利になります。 個人的にはコンソール画面で論理ディレクトリを設定出来れば嬉しいのですが、現状APIでのみ設定可能なのでアップデートに期待ですね!