How to create secure SFTP servers in AWS

Over two and a half years ago, I wrote about SFTP on this blog saying, “Despite years of attempts to retire inter-organization file transfers based on SFTP, the protocol is alive and well and continues to be deeply, deeply embedded in enterprises’ workflows.”

Nothing has changed. And I doubt it soon will. SFTP is still a major protocol used by many enterprises to exchange crucial data among themselves.

Unfortunately, most of the them do it insecurely.

There’s more — a lot more — to securing SFTP beyond relying exclusively on ssh encryption.

And that’s the purpose of this blog post: to describe a deployment of SFTP using the AWS Transfer service that is arguably more secure than what one might achieve by creating an AWS SFTP server manually and then attempting to secure it after the fact. The example here — a reference architecture — integrates many different AWS resources together to make an AWS SFTP server more secure. The only effective way to do this is in code. So this post includes a complete, tested CloudFormation template that deploys the SFTP reference architecture in one deployment, including all required resources.

The template also makes the SFTP reference architecture repeatable. You need only change three parameters to make it work for you. Or, you can customize it to fit your needs and security requirements and still get a repeatable, secure AWS SFTP server every time. In fact, this template can be the basis for a secure, low-cost, cloud-scalable Managed File Transfer (MFT) system. You can create just the aspects of MFT you need without having to resort to MFT vendors, with their legacy, IaaS offerings or their transfer clouds which make you give them too much control over this core capability.

How to make SFTP servers secure

This CloudFormation template deploys some basic concepts from the reference architecture:

  • SFTP should always run in a dedicated VPC. Sorry, I just don’t buy the counterargument that it should be part of your regular network. It shouldn’t attach to a transit gateway; it shouldn’t have routing to any other VPC. It should be a VPC all unto itself. The CloudFormation template creates a VPC with a single public subnet, an internet gateway, a security group permitting TCP 22 from anywhere and associates the proper routing and an EIP. Because of this latter exposure you would not want an SFTP server accessible from your normal VPCs, especially one with a publicly exposed IP address. So, a dedicated VPC is perfect.
  • You need an externally-resolvable DNS name for the SFTP server. The template creates a Route 53 hosted zone and a CNAME record for the subdomain of that zone that points to the SFTP server. This makes it easy for the other end to connect to you by name instead of by IP. There’s no security advantage in using IP addresses alone, as so many SFTP servers do. It’s tempting to use IP addresses only so you can allow them in your firewall. If that makes you feel better, fine. But good security isn’t just about blocking traffic and endlessly administering changing IP addresss; it’s about having tight control of authentication. So…
  • The SFTP server accepts only public key/private key authentication. This is the most secure way to manage an SFTP server, especially one you use to connect to partners, vendors, customers and suppliers. In this template, a single user named sftpuser is created and a public key that has been pre-loaded into AWS Secrets Manager is assigned to it. sftpuser has an IAM role assigned to it that scopes its access to a specific S3 key (“subdirectory”). You can add additional users and private keys to the template if you wish but my suggestion is that you create one entire deployment for each entity you connect with. The cost is low enough to support this and by keeping a one-server-per-connection environment, you can respond non-disruptively to changes and remove a connection by simply deleting the stack.
  • An encrypted S3 bucket is used for source and destination transfers. You could spend a lot of time getting the S3 encryption and access policies correct. But here, the CloudFormation template does it for you.
  • SFTP activity is logged in CloudWatch. This may seem obvious but it’s not a default so the template sets it up.

The image below summarizes the resources created by the template and the relationships among the elements. You will see how this is a more secure SFTP environment, one which allows you to isolate SFTP into a VPC of its own, locks it down to a single connection partner and user but still allows properly authorized applications to access incoming and outgoing files via S3. You can also automate workflows based on the data using AWS Lambda to add true MFT capabilities, again both securely and at lower cost than MFT products.

AWS SFTP Reference Architecture
AWS SFTP Reference Architecture Diagram (click to enlarge)

How to run the example SFTP CloudFormation template

You can change any or all parameters and resources in the SFTP CloudFormation template. But only three are really needed to create a useable deployment. I’d recommend you change just these three parameters to suit your environment, run the template and see how it all fits together before customizing the template further.

Two of the three parameters describe the DNS settings. In the image below, you can see the Route 53 hosted zone (sftp.air11.com) from a deployment I created. In this hosted zone (which is a subdomain of air11.com), I created a host named refarch1. That host is CNAME’d to the default AWS Transfer for SFTP hostname. Note that for this to work, I had to add NS records to the air11.com root domain which is registered elsewhere. If your domain is registered in Route 53, you should still create a subdomain and create the proper NS records to point to the nameservers that Route 53 assigns to the hosted zone.

SFTP hosted zone and CNAME records for SFTP server
SFTP hosted zone and CNAME records for SFTP server (click to enlarge)

This screenshot shows the relationship between the parameters in the CloudFormation template and the SFTP public key that will be assigned to sftpuser from AWS Secrets Manager. Only the public key is required so AWS Transfer for SFTP can associate it with sftpuser. Note that AWS Secrets Manager is also a good place to store the private key as I did here. The template doesn’t use this but it’s a way to make sure the private key isn’t lost and helps support the one-server-per-partner-connection idea at the heart this this reference architecture.

Store SFTP public and private keys in AWS Secrets Manager
Store SFTP public and private keys in AWS Secrets Manager (click to enlarge)

Using Filezilla to transfer files to/from AWS SFTP

Here is an animated .gif showing how you would use a utility like Filezilla to connect to an AWS SFTP server created by the template. Note that when you enter the server information into Filezilla, be sure to use the sftp:// method indicator in the hostname field.

Using Filezilla to connect to AWS SFTP servers
Using Filezilla to connect to AWS SFTP servers (click to enlarge)

The example SFTP template

Below is the complete SFTP CloudFormation template. It’s in YAML. But if you prefer JSON, you can easily convert this to JSON using the AWS CloudFormation Designer. Note that the three parameters discussed above have generic names you will need to change.

One parting piece of advice: do not give in to the temptation to use password authentication. I could (and maybe one day will) write a blog post about why that’s a disaster for inter-enterprise communication. But I hope my many years of hard knocks in the field with SFTP will help convince you that password authentication is both dangerous and administratively impossible to control.

I am very interested in your feedback and hope this helps you deploy a secure SFTP environment in AWS. I appreciate any comments left below or feel free to tweet me at @yobyot.

---
AWSTemplateFormatVersion: "2010-09-09"
Description: >-
  This sample AWS CloudFormation template creates an R53 hosted domain which points to a CNAME,
  a VPC with a single public subnet with an associated IGW, a S3 bucket for the SFTP server to 
  use and and AWS SFTP transfer endpoint. 
  Portions of this template are derived from this GitHub Gist: https://gist.github.com/glnds/dac9fb18c3cad10ba42a203526b8caf2
  Copyright 2021 Air11 Technology LLC
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
   
  http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  Alex Neihaus 2021-08-23
Parameters:
  VpcCidr:
    Default: 10.10.0.0/16
    Description: CIDR block for entire VPC.
    Type: String
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
    ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
  SftpPublicSubnetCidr:
    Default: 10.10.10.0/24
    Description: CIDR block for the public subnet
    Type: String
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
    ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
  SftpAllowedNetworks:
    AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/([0-9]|[1-2][0-9]|3[0-2]))$
    ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-28
    Default: 0.0.0.0/0
    Description: Networks allowed to ssh to instances in public subnet.
    Type: String
  SftpHostedZoneName:
    Type: String
    Default: sftp.yourdomain.com.
    Description: The DNS name of the Route 53 hosted zone which will contain the SFTP Server. Override when launching the template to use a different DNS domain name
  SftpServerDnsName:
    Type: String
    Default: refarch1
    Description: The DNS host name of the SFTP server in SftpHostedZoneName. Override when launching the template to use a different DNS host name
  
  SshKeyUserSecretName:
    Type: String
    Default: sftp-server-keypair
    Description: This is the name of the Secrets Manager secret containing the public key for the SFTP Server. Override when launching the template to use a different key
  SshUserPublicKeyJsonKey:
    Type: String
    Default: public-key
    Description: This is the name of the Secrets Manager secret containing the public key for the SFTP Server. Override when launching the template to use a different key
  
  S3BucketName:
    Type: String
    Default: ref-architecture-sftp-bucket
    Description: The name of the S3 bucket to be created for use by the SFTP server. Override when launching the template to use a different bucket name
  
  OwnerTagValue:
    Type: String
    Default: 'Alex Neihaus'
    Description: String used in the Owner tag applied to many resources in this template
  SomeOtherTag:
    Type: String
    Default: '123456'
    Description: An example of adding another tag to many of the resources in this template
  
Resources:
# The next few resources create a VPC with a single public subnet, an IGW, an SG that permits only TCP 22 from 0.0.0.0/0, an associated route table and connects them all together.
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      CidrBlock: !Ref 'VpcCidr'
      Tags:
      - Key: Name
        Value: !Sub 'Vpc ${VpcCidr}'
      - Key: Owner
        Value: !Ref OwnerTagValue
      - Key: AppID
        Value: !Ref SomeOtherTag
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: !Sub 'IGW ${VpcCidr}'
      - Key: CloudFormationStack
        Value: !Ref 'AWS::StackId'
      - Key: Owner
        Value: !Ref OwnerTagValue
      - Key: AppID
        Value: !Ref SomeOtherTag
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref 'Vpc'
      CidrBlock: !Ref 'SftpPublicSubnetCidr'
      Tags:
      - Key: Name
        Value: !Sub 'PublicSubnet ${SftpPublicSubnetCidr}'
      - Key: Owner
        Value: !Ref OwnerTagValue
      - Key: AppID
        Value: !Ref SomeOtherTag
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref 'Vpc'
      InternetGatewayId: !Ref 'InternetGateway'
  Eip:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
      - Key: Name
        Value: !Sub 'Eip ${Vpc}'
      - Key: Owner
        Value: !Ref OwnerTagValue
      - Key: AppID
        Value: !Ref SomeOtherTag
  PublicSubnetRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref 'Vpc'
      Tags:
      - Key: Name
        Value: !Sub 'RouteTable ${Vpc}'
      - Key: Owner
        Value: !Ref OwnerTagValue
      - Key: AppID
        Value: !Ref SomeOtherTag
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref 'PublicSubnetRouteTable'
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref 'InternetGateway'
  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref 'PublicSubnet'
      RouteTableId: !Ref 'PublicSubnetRouteTable'
  PublicSubnetSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref 'Vpc'
      GroupDescription: Enable SSH access via port 22
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: '0.0.0.0/0'
      Tags:
      - Key: Name
        Value: !Sub 'VpcSecurityGroup ${SftpPublicSubnetCidr}'
      - Key: Owner
        Value: !Ref OwnerTagValue
      - Key: AppID
        Value: !Ref SomeOtherTag
# The next two resources create a hosted R53 zone whose domain and host name are specified by parameters. The host record is a CNAME to the SFTP server name. If a subdomain is used (for example, sftp1.subdomain.example.com) make sure that example.com's records have NS records pointing to the nameservers R53 sets up for subdomain.example.com.
  R53HostedZone:
    Type: AWS::Route53::HostedZone
    Properties:
      Name: !Sub ${SftpHostedZoneName}
      HostedZoneTags:
        - Key: Name
          Value: !Sub 'HostedZoneName ${SftpHostedZoneName}'
        - Key: Owner
          Value: !Ref OwnerTagValue
        - Key: AppID
          Value: !Ref SomeOtherTag
  SftpServerDNSRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      Name: !Sub ${SftpServerDnsName}.${SftpHostedZoneName}
      HostedZoneId: !Ref R53HostedZone
      Type: CNAME
      TTL: 300
      ResourceRecords:
        - !Sub ${SftpServer.ServerId}.server.transfer.${AWS::Region}.amazonaws.com
# The next resource creates an S3 bucket with the correct encryption (S3-SSE) to be used by the SFTP server.
  SftpServerS3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      BucketName: !Ref S3BucketName
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: 'AES256' # This specifies SSE-S3 encryption
      Tags:
      - Key: Owner
        Value: !Ref OwnerTagValue
      - Key: AppID
        Value: !Ref SomeOtherTag
# This resource creates an IAM role and associated policy to permit logging of SFTP server sends/receives/errors in CloudWatch. The logs are log group log-group:/aws/transfer
  SftpServerLoggingRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - transfer.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: ReferenceArchitectureSftpServerCloudWatchLogging
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:DescribeLogStreams
                  - logs:CreateLogGroup
                  - logs:PutLogEvents
                Resource: 'arn:aws:logs:*:*:log-group:/aws/transfer/*'
# The following resource creates the SFTP server with a VPC endpoint, placing it in the VPC just created.
  SftpServer:
    Type: AWS::Transfer::Server
    Properties:
      LoggingRole: !GetAtt SftpServerLoggingRole.Arn
      Domain: S3
      EndpointType: VPC
      EndpointDetails: 
        VpcId: !Ref Vpc
        SubnetIds: 
        - !Ref PublicSubnet
        SecurityGroupIds:
        - !Ref PublicSubnetSg
        AddressAllocationIds:
        - !GetAtt Eip.AllocationId
      Protocols: 
      - SFTP
      Tags:
        - Key: Name
          Value: !Ref SftpServerDnsName
        - Key: Owner
          Value: !Ref OwnerTagValue
        - Key: AppID
          Value: !Ref SomeOtherTag
# This resource creates an IAM role and associated policy to "step down" (limit) access to specific key (subdirectories) in the S3 bucket. We only create one userid (sftpuser) but this policy can be applied to additional users who may be manually created.
  SftpUserScopeDownRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - transfer.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: ReferenceArchitectureAllowS3ListingAndLocation
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListAllMyBuckets
                  - s3:GetBucketLocation
                Resource: "*"
        - PolicyName: ReferenceArchitectureAllowListingOfUserFolder
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListBucket
                Resource: !GetAtt SftpServerS3Bucket.Arn
        - PolicyName: ReferenceArchitectureAllowAccessToUserBuckets
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:DeleteObject
                  - s3:DeleteObjectVersion
                Resource: !Sub "${SftpServerS3Bucket.Arn}/*"
# This resource creates a user (sftpuser) and associates it with the server and sets up authentication to use the public key retrieved from a Secrets Manger secret. DO NOT USE OR ALLOW THE PRIVATE KEY TO ESCAPE YOUR CONTROL.
  SftpUser:
    Type: AWS::Transfer::User
    Properties:
      ServerId: !GetAtt SftpServer.ServerId
      UserName: sftpuser
      HomeDirectory: !Sub "/${SftpServerS3Bucket}/home/sftpuser"
      Policy: >
        {
          "Version": "2012-10-17",
                  "Statement": [
                      {
                          "Sid": "ReferenceArchitectureAllowS3ListHomeFolderSftpAdminUser",
                          "Effect": "Allow",
                          "Action": "s3:ListBucket",
                          "Resource": "arn:aws:s3:::${transfer:HomeBucket}",
                          "Condition": {
                              "StringLike": {
                                  "s3:prefix": [
                                      "home/${transfer:UserName}/*",
                                      "home/${transfer:UserName}"
                                  ]
                              }
                          }
                      },
                      {
                          "Sid": "ReferenceArchitectureAllowS3ObjectAccessInHomeFolderSftpAdminUser",
                          "Effect": "Allow",
                          "Resource": "arn:aws:s3:::${transfer:HomeDirectory}*",
                          "Action": [
                              "s3:PutObject",
                              "s3:GetObject",
                              "s3:GetObjectVersion",
                              "s3:DeleteObject",
                              "s3:DeleteObjectVersion",
                              "s3:GetObjectACL",
                              "s3:PutObjectACL"
                          ]
                      }
                  ]
          }
      Role: !GetAtt SftpUserScopeDownRole.Arn
      # The user's public key is stored in an AWS Secrets Manager key, along with the private key (which must never be given out externally)
      # A CloudFormation dynamic parameter retrieves the public key using the secret name and JSON key for the secret stored in the Secrets Manager resource
      SshPublicKeys:
        - !Join 
            - ''
            - - '{{resolve:secretsmanager:'
              - !Ref SshKeyUserSecretName
              - ':SecretString:'
              - !Ref SshUserPublicKeyJsonKey
              - '}}'     
      Tags:
      - Key: Owner
        Value: !Ref OwnerTagValue
      - Key: AppID
        Value: !Ref SomeOtherTag
Outputs:
  StackId:
    Description: Stack ID of the CloudFormation Stack
    Value: !Ref 'AWS::StackId'
  VPCId:
    Description: VPCId of the newly created VPC
    Value: !Ref 'Vpc'
  EipAddress:
    Description: EIP allocated to NAT gateway
    Value: !Ref 'Eip'
  PublicSubnetId:
    Description: SubnetId of the public subnet
    Value: !Ref PublicSubnet
  PublicSubnetRouteTableId:
    Description: Public route table
    Value: !Ref PublicSubnetRouteTable
  R53HostedZoneId:
    Description: Route 53 hosted zone ID
    Value: !Ref R53HostedZone
  SftpServerDNSRecord:
    Description: SFTP DNS record
    Value: !Ref SftpServerDNSRecord
  SftpServerS3Bucket:
    Description: S3 bucket used by the SFTP server
    Value: !Ref SftpServerS3Bucket
  SftpServerId:
    Description: ID of SFTP server
    Value: !Ref SftpServer
...

Posted

in

, , ,

by

Comments

One response to “How to create secure SFTP servers in AWS”

  1. suresh Avatar
    suresh

    very informative thanks for sharing.

Leave a Reply

Your email address will not be published. Required fields are marked *