前陣子同事在詢問怎麼讓外部使用者有授權的訪問 Amazon S3,當下第一時間想到的就是 AWS Transfer family 這個 Service,Transfer family 支援 FTP、FTPS、SFTP 三種協定,以安全性來說都建議至少採用 FTPS (SSL) 或 SFTP (SSH) 加密協定,在這篇也是紀錄一下如何透過 AWS CDK 建立一套 Transfer family。
這篇所有的 CDK 程式碼都放在 Github:shazi7804/cdk-samples 有興趣的可以看完整版。
Transfer family Server
首先必須要有一個 Amazon S3 bucket 存放使用者上傳下載的位置:
const bucket = new s3.Bucket(this, 'BucketOfHome', {
bucketName: 'transfer-family-sftp-' + this.region + '-' + this.account,
encryption: s3.BucketEncryption.S3_MANAGED,
versioned: true
});
建議 encryption 跟 versioned 都打開,防止意外刪除或覆蓋造成的杯具 …
再來是 Transfer Family 所使用的 Service-Role
const role = new iam.Role(this, 'AWSTransferFamilyServiceRole', {
roleName: 'AWSTransferLoggingAccess',
assumedBy: new iam.ServicePrincipal('transfer.amazonaws.com'),
})
role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSTransferLoggingAccess'))
AWSTransferLoggingAccess 這個 Managed Policy 提供了 Transfer family 有權限將使用者訪問紀錄寫到 Cloudwatch logs,而預設 Cloudwatch logs 預設保存 Never expire 如果不需要長期保存記得將時間設定到適當的時間。
每一個 User 也必須定義 IAM Role 的訪問權限
const userRole = new iam.Role(this, 'AWSTransferUsersAccessRole', {
roleName: 'AWSTransferUsersAccess',
assumedBy: new iam.ServicePrincipal("transfer.amazonaws.com"),
});
userRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:ListBucket'],
resources: [bucket.bucketArn],
})
);
userRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:GetObject',
's3:GetObjectAcl',
's3:GetObjectVersion',
's3:PutObject',
's3:PutObjectACL',
's3:DeleteObject',
's3:DeleteObjectVersion'
],
resources: [`${bucket.bucketArn}/*`],
})
);
產生出來的 IAM Policy 會長的像這樣:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::${BUCKET}",
"Effect": "Allow"
},
{
"Action": [
"s3:GetObject",
"s3:GetObjectAcl",
"s3:GetObjectVersion",
"s3:PutObject",
"s3:PutObjectACL",
"s3:DeleteObject",
"s3:DeleteObjectVersion"
],
"Resource": "arn:aws:s3:::${BUCKET}/*",
"Effect": "Allow"
}
]
}
最後就是 Transfer family Server,由於作者在寫 AWS CDK 的時候 Transfer family 還沒有 Layer 2 resource,所以會用 Cfn 撰寫:
const server = new tf.CfnServer(this, 'TransferFamilyServer', {
protocols: ['SFTP'],
identityProviderType: 'SERVICE_MANAGED',
loggingRole: role.roleArn
});
指定 SFTP Protocol 和驗證方式後,也是很簡單的幾行就搞定。
cdk deploy
後會建立幾個 resources:
- Amazon S3 bucket
- IAM Role for Transfer family service
- IAM Role for Transfer family users
- Transfer family server
接著畫面跳轉到 AWS Transfer family 的 Management Console 就可以找到剛剛建立的 Transfer server
當狀態變成 Online 後就可以 Add user 訪問權限啦:
Role 記得選擇 AWSTransferUsersAccess,而 Home directory 也要選到與 Role Policy 相同的 S3 bucket,否則即便登入也沒辦法訪問 Home 目錄。
當 Restricted
打勾時使用者會被鎖在自己的家目錄下而無法訪問其他層級的目錄,這是在 FTP 上很常見的權限處理方式。
由於 Transfer family SFTP 的驗證是採用 SSH public/private keys 進行驗證,如果是 macOS/Linux 可以用 ssh-keygen 來產生 ssh key (Windows 也有 OpenSSH 或是 puttygen 可以用)
$ ssh-keygen -P "" -m PEM -f shazi7804
拿到兩隻檔案分別是 shazi7804 (private key) 和 shazi7804.pub (public key),將 public key 填上 Add user 欄位內。
Transfer family users
在使用者端可以選擇有支援 SFTP Client 的 FileZilla 並且了解以下資訊:
- Transfer family endpoint:s-xxxxxx.server.transfer.${region}.amazonaws.com
- Protocol:SFTP
- SSH Private key (shazi7804)
建立站台後就可以成功連上 Transfer family server 啦!!
而在 Cloudwatch logs 上每一個 user 都會有獨立的 Log stream 紀錄 login, read, write 等資料:
shazi7804.2291717934357386 CONNECTED SourceIP=36.231.106.61 User=shazi7804 HomeDir=LOGICAL Client=SSH-2.0-FileZilla_3.54.1 Role=arn:aws:iam::0123456789:role/AWSTransferUsersAccess Kex=curve25519-sha256@libssh.org Ciphers=aes256-gcm@openssh.com,aes256-gcm@openssh.com
shazi7804.2291717934357386 OPEN Path=/transfer-family-sftp-us-east-1-0123456789/shazi7804/.zshrc Mode=CREATE|TRUNCATE|WRITE
shazi7804.2291717934357386 CLOSE Path=/transfer-family-sftp-us-east-1-0123456789/shazi7804/.zshrc BytesIn=3208
shazi7804.2291717934357386 OPEN Path=/transfer-family-sftp-us-east-1-0123456789/shazi7804/.zshrc Mode=READ
shazi7804.2291717934357386 CLOSE Path=/transfer-family-sftp-us-east-1-0123456789/shazi7804/.zshrc BytesOut=3208