Using AWS Lambda to integrate your identity provider - AWS Transfer Family

Using AWS Lambda to integrate your identity provider

This topic describes how to create an AWS Lambda function that connects to your custom identity provider. You can use any custom identity provider, such as Okta, Secrets Manager, OneLogin, or a custom data store that includes authorization and authentication logic.

For most use cases, the recommended way to configure a custom identity provider is to use the Custom identity provider solution.

Note

Before you create a Transfer Family server that uses Lambda as the identity provider, you must create the function. For an example Lambda function, see Example Lambda functions. Or, you can deploy a CloudFormation stack that uses one of the Lambda function templates. Also, make sure your Lambda function uses a resource-based policy that trusts Transfer Family. For an example policy, see Lambda resource-based policy.

  1. Open the AWS Transfer Family console.

  2. Choose Create server to open the Create server page. For Choose an identity provider, choose Custom Identity Provider, as shown in the following screenshot.

    The Choose an identity provider console section with Custom identity provider selected. Also has the default value selected, which is that users can authenticate using either their password or key.
    Note

    The choice of authentication methods is only available if you enable SFTP as one of the protocols for your Transfer Family server.

  3. Make sure the default value, Use AWS Lambda to connect your identity provider, is selected.

  4. For AWS Lambda function, choose the name of your Lambda function.

  5. Fill in the remaining boxes, and then choose Create server. For details on the remaining steps for creating a server, see Configuring an SFTP, FTPS, or FTP server endpoint.

Lambda resource-based policy

You must have a policy that references the Transfer Family server and Lambda ARNs. For example, you could use the following policy with your Lambda function that connects to your identity provider. The policy is escaped JSON as a string.

"Policy": "{ "Version": "2012-10-17", "Id": "default", "Statement": [ { "Sid": "AllowTransferInvocation", "Effect": "Allow", "Principal": { "Service": "transfer.amazonaws.com" }, "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:region:account-id:function:my-lambda-auth-function", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:transfer:region:account-id:server/server-id" } } } ] }"
Note

In the example policy above, replace each user input placeholder with your own information.

Event message structure

The event message structure from SFTP server sent to the authorizer Lambda function for a custom IDP is as follows.

{ "username": "value", "password": "value", "protocol": "SFTP", "serverId": "s-abcd123456", "sourceIp": "192.168.0.100" }

Where username and password are the values for the sign-in credentials that are sent to the server.

For example, you enter the following command to connect:

sftp bobusa@server_hostname

You are then prompted to enter your password:

Enter password: mysecretpassword

You can check this from your Lambda function by printing the passed event from within the Lambda function. It should look similar to the following text block.

{ "username": "bobusa", "password": "mysecretpassword", "protocol": "SFTP", "serverId": "s-abcd123456", "sourceIp": "192.168.0.100" }

The event structure is similar for FTP and FTPS: the only difference is those values are used for the protocol parameter, rather than SFTP.

Lambda functions for authentication

To implement different authentication strategies, edit the Lambda function. To help you meet your application's needs, you can deploy a CloudFormation stack. For more information about Lambda, see the AWS Lambda Developer Guide or Building Lambda functions with Node.js.

Valid Lambda values

The following table describes details for the values that Transfer Family accepts for Lambda functions that are used for custom identity providers.

Value Description Required

Role

Specifies the Amazon Resource Name (ARN) of the IAM role that controls your users' access to your Amazon S3 bucket or Amazon EFS file system. The policies attached to this role determine the level of access that you want to provide your users when transferring files into and out of your Amazon S3 or Amazon EFS file system. The IAM role should also contain a trust relationship that allows the server to access your resources when servicing your users' transfer requests.

For details on establishing a trust relationship, see To establish a trust relationship.

Required

PosixProfile

The full POSIX identity, including user ID (Uid), group ID (Gid), and any secondary group IDs (SecondaryGids), that controls your users' access to your Amazon EFS file systems. The POSIX permissions that are set on files and directories in your file system determine the level of access your users get when transferring files into and out of your Amazon EFS file systems.

Required for Amazon EFS backing storage

PublicKeys

A list of SSH public key values that are valid for this user. An empty list implies that this is not a valid login. Must not be returned during password authentication.

Optional

Policy

A session policy for your user so that you can use the same IAM role across multiple users. This policy scopes down user access to portions of their Amazon S3 bucket.

Optional

HomeDirectoryType

The type of landing directory (folder) that you want your users' home directory to be when they log in to the server.

  • If you set it to PATH, the user sees the absolute Amazon S3 bucket or Amazon EFS paths as is in their file transfer protocol clients.

  • If you set it to LOGICAL, you must provide mappings in the HomeDirectoryDetails parameter to make Amazon S3 or Amazon EFS paths visible to your users.

Optional

HomeDirectoryDetails

Logical directory mappings that specify which Amazon S3 or Amazon EFS paths and keys should be visible to your user and how you want to make them visible. You must specify the Entry and Target pair, where Entry shows how the path is made visible and Target is the actual Amazon S3 or Amazon EFS path.

Required if HomeDirectoryType has a value of LOGICAL

HomeDirectory

The landing directory for a user when they log in to the server using the client.

Optional

Note

HomeDirectoryDetails is a string representation of a JSON map. This is in contrast to PosixProfile, which is an actual JSON map object, and PublicKeys which is a JSON array of strings. See the code examples for the language-specific details.

Example Lambda functions

This section presents some example Lambda functions, in both NodeJS and Python.

Note

In these examples, the user, role, POSIX profile, password, and home directory details are all examples, and must be replaced with your actual values.

Logical home directory, NodeJS

The following NodeJS example function provides the details for a user that has a logical home directory.

// GetUserConfig Lambda exports.handler = (event, context, callback) => { console.log("Username:", event.username, "ServerId: ", event.serverId); var response; // Check if the username presented for authentication is correct. This doesn't check the value of the server ID, only that it is provided. if (event.serverId !== "" && event.username == 'example-user') { var homeDirectoryDetails = [ { Entry: "/", Target: "/fs-faa1a123" } ]; response = { Role: 'arn:aws:iam::123456789012:role/transfer-access-role', // The user is authenticated if and only if the Role field is not blank PosixProfile: {"Gid": 65534, "Uid": 65534}, // Required for EFS access, but not needed for S3 HomeDirectoryDetails: JSON.stringify(homeDirectoryDetails), HomeDirectoryType: "LOGICAL", }; // Check if password is provided if (!event.password) { // If no password provided, return the user's SSH public key response['PublicKeys'] = [ "ssh-rsa abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" ]; // Check if password is correct } else if (event.password !== 'Password1234') { // Return HTTP status 200 but with no role in the response to indicate authentication failure response = {}; } } else { // Return HTTP status 200 but with no role in the response to indicate authentication failure response = {}; } callback(null, response); };
Path-based home directory, NodeJS

The following NodeJS example function provides the details for a user that has a path-based home directory.

// GetUserConfig Lambda exports.handler = (event, context, callback) => { console.log("Username:", event.username, "ServerId: ", event.serverId); var response; // Check if the username presented for authentication is correct. This doesn't check the value of the server ID, only that it is provided. // There is also event.protocol (one of "FTP", "FTPS", "SFTP") and event.sourceIp (e.g., "127.0.0.1") to further restrict logins. if (event.serverId !== "" && event.username == 'example-user') { response = { Role: 'arn:aws:iam::123456789012:role/transfer-access-role', // The user is authenticated if and only if the Role field is not blank Policy: '', // Optional, JSON stringified blob to further restrict this user's permissions HomeDirectory: '/fs-faa1a123' // Not required, defaults to '/' }; // Check if password is provided if (!event.password) { // If no password provided, return the user's SSH public key response['PublicKeys'] = [ "ssh-rsa abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" ]; // Check if password is correct } else if (event.password !== 'Password1234') { // Return HTTP status 200 but with no role in the response to indicate authentication failure response = {}; } } else { // Return HTTP status 200 but with no role in the response to indicate authentication failure response = {}; } callback(null, response); };
Logical home directory, Python

The following Python example function provides the details for a user that has a logical home directory.

# GetUserConfig Python Lambda with LOGICAL HomeDirectoryDetails import json def lambda_handler(event, context): print("Username: {}, ServerId: {}".format(event['username'], event['serverId'])) response = {} # Check if the username presented for authentication is correct. This doesn't check the value of the server ID, only that it is provided. if event['serverId'] != '' and event['username'] == 'example-user': homeDirectoryDetails = [ { 'Entry': '/', 'Target': '/fs-faa1a123' } ] response = { 'Role': 'arn:aws:iam::123456789012:role/transfer-access-role', # The user will be authenticated if and only if the Role field is not blank 'PosixProfile': {"Gid": 65534, "Uid": 65534}, # Required for EFS access, but not needed for S3 'HomeDirectoryDetails': json.dumps(homeDirectoryDetails), 'HomeDirectoryType': "LOGICAL" } # Check if password is provided if event.get('password', '') == '': # If no password provided, return the user's SSH public key response['PublicKeys'] = [ "ssh-rsa abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" ] # Check if password is correct elif event['password'] != 'Password1234': # Return HTTP status 200 but with no role in the response to indicate authentication failure response = {} else: # Return HTTP status 200 but with no role in the response to indicate authentication failure response = {} return response
Path-based home directory, Python

The following Python example function provides the details for a user that has a path-based home directory.

# GetUserConfig Python Lambda with PATH HomeDirectory def lambda_handler(event, context): print("Username: {}, ServerId: {}".format(event['username'], event['serverId'])) response = {} # Check if the username presented for authentication is correct. This doesn't check the value of the server ID, only that it is provided. # There is also event.protocol (one of "FTP", "FTPS", "SFTP") and event.sourceIp (e.g., "127.0.0.1") to further restrict logins. if event['serverId'] != '' and event['username'] == 'example-user': response = { 'Role': 'arn:aws:iam::123456789012:role/transfer-access-role', # The user will be authenticated if and only if the Role field is not blank 'Policy': '', # Optional, JSON stringified blob to further restrict this user's permissions 'HomeDirectory': '/fs-fs-faa1a123', 'HomeDirectoryType': "PATH" # Not strictly required, defaults to PATH } # Check if password is provided if event.get('password', '') == '': # If no password provided, return the user's SSH public key response['PublicKeys'] = [ "ssh-rsa abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789" ] # Check if password is correct elif event['password'] != 'Password1234': # Return HTTP status 200 but with no role in the response to indicate authentication failure response = {} else: # Return HTTP status 200 but with no role in the response to indicate authentication failure response = {} return response

Testing your configuration

After you create your custom identity provider, you should test your configuration.

Console
To test your configuration by using the AWS Transfer Family console
  1. Open the AWS Transfer Family console.

  2. On the Servers page, choose your new server, choose Actions, and then choose Test.

  3. Enter the text for Username and Password that you set when you deployed the AWS CloudFormation stack. If you kept the default options, the username is myuser and the password is MySuperSecretPassword.

  4. Choose the Server protocol and enter the IP address for Source IP, if you set them when you deployed the AWS CloudFormation stack.

CLI
To test your configuration by using the AWS CLI
  1. Run the test-identity-provider command. Replace each user input placeholder with your own information, as described in the subsequent steps.

    aws transfer test-identity-provider --server-id s-1234abcd5678efgh --user-name myuser --user-password MySuperSecretPassword --server-protocol FTP --source-ip 127.0.0.1
  2. Enter the server ID.

  3. Enter the username and password that you set when you deployed the AWS CloudFormation stack. If you kept the default options, the username is myuser and the password is MySuperSecretPassword.

  4. Enter the server protocol and source IP address, if you set them when you deployed the AWS CloudFormation stack.

If user authentication succeeds, the test returns a StatusCode: 200 HTTP response, an empty string Message: "" (which would contain a reason for failure otherwise), and a Response field.

Note

In the response example below, the Response field is a JSON object that has been "stringified" (converted into a flat JSON string that can be used inside a program), and contains the details of the user's roles and permissions.

{ "Response":"{\"Policy\":\"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Sid\\\":\\\"ReadAndListAllBuckets\\\",\\\"Effect\\\":\\\"Allow\\\",\\\"Action\\\":[\\\"s3:ListAllMybuckets\\\",\\\"s3:GetBucketLocation\\\",\\\"s3:ListBucket\\\",\\\"s3:GetObjectVersion\\\",\\\"s3:GetObjectVersion\\\"],\\\"Resource\\\":\\\"*\\\"}]}\",\"Role\":\"arn:aws:iam::000000000000:role/MyUserS3AccessRole\",\"HomeDirectory\":\"/\"}", "StatusCode": 200, "Message": "" }

Lambda function templates

You can deploy an AWS CloudFormation stack that uses a Lambda function for authentication. We provide several templates that authenticate and authorize your users using sign-in credentials. You can modify these templates or AWS Lambda code to further customize user access.

Note

You can create a FIPS-enabled AWS Transfer Family server through AWS CloudFormation by specifying a FIPS-enabled security policy in your template. Available security policies are described in Security policies for AWS Transfer Family servers

To create an AWS CloudFormation stack to use for authentication
  1. Open the AWS CloudFormation console at https://console.aws.amazon.com/cloudformation.

  2. Follow the instructions for deploying an AWS CloudFormation stack from an existing template in Selecting a stack template in the AWS CloudFormation User Guide.

  3. Use one of the following templates to create a Lambda function to use for authentication in Transfer Family.

    • Classic (Amazon Cognito) stack template

      A basic template for creating a AWS Lambda for use as a custom identity provider in AWS Transfer Family. It authenticates against Amazon Cognito for password-based authentication and public keys are returned from an Amazon S3 bucket if public key based authentication is used. After deployment, you can modify the Lambda function code to do something different.

    • AWS Secrets Manager stack template

      A basic template that uses AWS Lambda with an AWS Transfer Family server to integrate Secrets Manager as an identity provider. It authenticates against an entry in AWS Secrets Manager of the format aws/transfer/server-id/username. Additionally, the secret must hold the key-value pairs for all user properties returned to Transfer Family. After deployment, you can modify the Lambda function code to do something different.

    • Okta stack template: A basic template that uses AWS Lambda with an AWS Transfer Family server to integrate Okta as a custom identity provider.

    • Okta-mfa stack template: A basic template that uses AWS Lambda with an AWS Transfer Family server to integrate Okta, with Multi Factor Authentication, as a custom identity provider.

    • Azure Active Directory template: details for this stack are described in the blog post Authenticating to AWS Transfer Family with Azure Active Directory and AWS Lambda.

    After the stack has been deployed, you can view details about it on the Outputs tab in the CloudFormation console.

    Deploying one of these stacks is the easiest way to integrate a custom identity provider into the Transfer Family workflow.