“Simply” enable SFTP access to AWS ECS

Or why it took 10 hours to implement it

Arthurio
3 min readDec 11, 2023

It all has to start with the keyword “simple”, isn’t it? Hint for juniors — if you see that word, run.

I understand it may sound easy to “simply enable FTP access”. But, what happens behind the scenes in the ECS infrastructure is the following:

There is no so-to-say “VM/server/instance” where FTP can be added/enabled and everything will work out of the box.

The following things are needed for SFTP access in ECS infrastructure:

  • Spin up an EC2 instance that will act as a proxy, add all the needed security groups, etc.
  • Create EFS for the paths that will be needed for FTP access.
  • Use EFS mounting points for multiple folders.
  • Attach Elastic IP to EC2 so that the connection can be saved in your favorite FTP software and the IP won’t change.
  • Mount that EFS for all containers.
  • Mount EFS to EC2 (User Data can be used for the mount commands).
  • Enable SFTP access in EC2 (Add User Data commands that will allow SFTP access to EC2, add users, create a password, etc (make sure only needed paths can be accessed and nothing else)).

Everything above is done with AWS CDK in TypeScript. Add multiple deploys, two environments (staging, production), and all things considered, I would say you will be lucky to get this working in 10 hours.

I won’t dive into details, as the point of the article is about the general idea of how FTP access can be created for ECS, but I will show a few useful examples of how to attach EFS to EC2 and how to enable SFTP access to EC2 with User Data commands.

Here is an example of how to attach EFS to EC2 with AWS CDK (TypeScript):

public mountEFS(efs: EFS[], mountPointBase = '/mnt/efs'): EC2 {
this.instance.userData.addCommands(
'yum check-update -y',
'yum upgrade -y',
'yum install -y amazon-efs-utils',
'yum install -y nfs-utils',
)

for (const fs of efs) {
fs.securityGroup.addIngressRule(
Peer.securityGroupId(this.securityGroup.securityGroupId),
Port.tcp(2049),
`EC2`)

if (fs.accessPoints === undefined) {
this.instance.userData.addCommands(
'file_system_id_1=' + fs.fileSystem.fileSystemId,
'efs_mount_point_1=' + mountPointBase + "/" + fs.name,
"mkdir -p \"${efs_mount_point_1}\"",
"test -f \"/sbin/mount.efs\" && echo \"${file_system_id_1}:/ ${efs_mount_point_1} efs defaults,_netdev\" >> /etc/fstab || " +
"echo \"${file_system_id_1}.efs." + Stack.of(this).region + ".amazonaws.com:/ ${efs_mount_point_1} nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,_netdev 0 0\" >> /etc/fstab",
)
} else {
for (const [path, accessPoint] of Object.entries(fs.accessPoints)) {
this.instance.userData.addCommands(
'file_system_id_1=' + fs.fileSystem.fileSystemId,
'efs_mount_point_1=' + mountPointBase + path,
"mkdir -p \"${efs_mount_point_1}\"",
"test -f \"/sbin/mount.efs\" && echo \"${file_system_id_1}:/ ${efs_mount_point_1} efs defaults,_netdev,tls,accesspoint=" + accessPoint.accessPointId + " 0 0\" >> /etc/fstab"
)
}
}
}

this.instance.userData.addCommands('mount -a -t efs,nfs4 defaults')

return this
}

The above function works both with and without EFS access points. Simply pass EFS to it and it will add Security Group and necessary User Data commands to EC2, which will mount EFS to it and it will be available at “/mnt/efs”.

This example enables SFTP access with chroot to the provided chrootDir directory:

    
public enableSFTP(users: String[], chrootDir = '/mnt/efs'): EC2 {
for (const user of users) {
this.instance.userData.addCommands(
"sudo adduser " + user,
"sudo echo -e 'Match user " + user + "\n ChrootDirectory " + chrootDir + "\n X11Forwarding no\n AllowTcpForwarding no\n PermitTunnel no\n AllowAgentForwarding no\n ForceCommand internal-sftp -d /' >> /etc/ssh/sshd_config",
)
}

this.instance.userData.addCommands(
"sudo sed -i '/^PasswordAuthentication/s/no/yes/' /etc/ssh/sshd_config",
"sudo sed -i '/^Subsystem sftp/s+/usr/libexec/openssh/sftp-server+internal-sftp+' /etc/ssh/sshd_config",
"sudo systemctl restart sshd"
);

return this;
}

Both were tested on Amazon Linux 2, but the code can be adjusted to other Linux-based systems as well.

--

--