Desktop and Application Streaming

Application Online Trial Expiration Control with Amazon AppStream 2.0

Many customers use Amazon AppStream 2.0 to provide online trials of their desktop applications. Our ISV Workshop Series shows you how to make your applications available through your website, delivered by AppStream 2.0. However, some customers need that access to be for a limited time. Based on their requirements, there will be different expiration periods for their online trial access.  For instance, some customer’s online trials can last days, hours, or hours spread across multiple days.

This post shows you how to modify the ISV Workshop Series SaaS Portal workshop to add a timed element to your application’s access.

Overview

This solution is based upon Creating a SaaS portal with Amazon AppStream 2.0 workshop. This portal lets interested customers create an account and sign up an online trial for a web-based software.  Expanding on that, this blog lets you control online trial expiration of this web-based software.

The solution uses Amazon CloudWatchAmazon CognitoAWS Lambda, and Amazon AppStream 2.0.  Using CloudWatch Events to schedule an AWS Lambda function. The Lambda function lists all Amazon Cognito users for the online trial and determine whether their trial period has expired.  If their trial period has expired, the Lambda function expires any active AppStream 2.0 sessions for these users and disable them in Amazon Cognito.  Finally, the Lambda function emails these users to inform them that their online trial has expired.

The high-level architecture is shown in the following diagram.

The steps to control online trial expiration are as follows:

  1. Create an IAM policy for a custom role.
  2. Create an IAM service role for Lambda function execution.
  3. Create and configure an AWS Lambda function.
  4. Configure CloudWatch Events to trigger your Lambda function.

We recommend familiarity with Amazon AppStream 2.0 and other AWS services mentioned previously.  The expected time to complete this solution is about two to three hours.

Prerequisites

To follow this solution walkthrough, you must have the following resources:

Step 1: Create an IAM policy for a custom role

In this step, you create a custom IAM policy to grant Lambda permissions. This allows the Lambda function to handle your users’ online trial expiration. The IAM policy grants permissions to perform the following actions:

  • AppStream 2.0 describe sessions
  • Amazon Cognito list users
  • Amazon Cognito disable user
  • Amazon SES send email
  • Amazon CloudWatch logging

Complete the following steps to create the custom IAM policy.

  1. Open the IAM console.
  2. In the navigation pane, choose Policies.
  3. If this is your first time choosing Policies, the Welcome to Managed Policies page appears. Choose Get started.
  4. Choose Create policy.
  5. Choose the JSON tab.
  6. Copy and paste the following JSON policy into the policy document box.  Please use your account related information to fill in the placeholders in this policy.
    {
    	"Version": "2012-10-17",
    	"Statement": [{
    			"Effect": "Allow",
    			"Action": [
    				"appstream:DescribeSessions",
    			"appstream:ExpireSession",
    				"cognito-idp:AdminDisableUser",
    				"cognito-idp:ListUsers"
    			],
    			"Resource": [
    				"arn:aws:cognito-idp:<region-code>:<AWS-Account-Number>:userpool/<USEPOOLID>",
    				"arn:aws:appstream:<region-code>:<AWS-Account-Number>:fleet/<FLEETNAME>",
    				"arn:aws:appstream:<region-code>:<AWS-Account-Number>:stack/<STACKNAME>"
    			]
    		},
    		{
    			"Effect": "Allow",
    			"Action": [
    				"ses:SendEmail",
    				"logs:CreateLogStream",
    				"logs:CreateLogGroup",
    				"logs:PutLogEvents"
    			],
    			"Resource": "*"
    		}
    	]
    }
  7. When you’re done, choose Review policy.
  8. For Name, enter a unique name.
  9. Choose Create policy.

Step 2: Create an IAM service role for Lambda function execution

An IAM service role is required to allow Lambda to access resources in other services on your behalf. Complete the following steps to create an IAM service role and attach the policy created in the previous step.

  1. Open the IAM console.
  2. In the navigation pane, under Roles, choose Create role.
  3. For Select type of trusted entity, keep AWS service selected.
  4. Choose Lambda, and then choose Next: Permissions.
  5. In the Filter policies search box, type the policy name created in previous step. When the policy appears in the list, select the check box next to the policy name.
  6. Choose Next: Tags. Although you can specify a tag for the policy, a tag is not required.
  7. Choose Next: Review.
  8. For Role name, enter a unique name.
  9. Choose Create role.

Step 3: Create and configure a Lambda function

Complete the following steps to create a Lambda function.

  1. Open the Lambda console.
  2. Do one of the following:
    1. If you haven’t created any Lambda functions, a Getting started page displays. Under Getting started, choose Create a function.
    2. If you have created a Lambda function, in the upper right corner of the Functions page, choose Create a function.
  3. On the Create function page, keep Author from scratch selected.
  4. Under Basic information, do the following:
    1. For Name, enter a unique name.
    2. For Runtime, choose Python 3.8.
  5. Under Permissions, expand Choose or create an execution role. Then do the following:
    1. For Execution role, choose Use an existing role.
    2. For Existing role, choose the role created in the previous step.
  6. Choose Create function.
  7. In the Function code editor, copy the following code into the editor, overwrite any existing code.  Please use your account information to fill in the placeholders in the codes.   This Lambda function codes do not handle pagination of the user list from Cognito API calls.  If you have a large number of users for your application online trials, you can use AWS SDK for Python to handle the pagination.
import json
import boto3
import datetime

VERIFIED_EMAIL = '<your verified email address>'
EXPIRE_IN_DAYS = 10
USER_POOL_ID = '<your cognito user pool id>'
STACK = '<STACK_NAME>'
FLEET = '<FLEET_NAME>'

client = boto3.client('cognito-idp')
as2 = boto3.client('appstream')

def user_age(user_created_date):
    tz_info = user_created_date.tzinfo
    age = datetime.datetime.now(tz_info) - user_created_date

    user_age_str = str(age)
    if 'days' not in user_age_str:
        return 0

    days = int(user_age_str.split(',')[0].split(' ')[0])
    return days

def user_end_session(user):
   response = as2.describe_sessions(StackName=STACK, FleetName=FLEET, UserId=user)
   if response['Sessions']:
      Session = response
      SessionID = Session['Sessions'][0]['Id']
      as2.expire_session(SessionId=SessionID)
   else:
      print('No session for that user')

def send_deactivate_email(email_to):
    ses_client = boto3.client('ses')
    data = ' Your online trial has expired. Please contact us for more information.'
  response = ses_client.send_email(
       Source=VERIFIED_EMAIL,
       Destination={
          'ToAddresses': [email_to]
    },
    Message={
        'Subject': {
            'Data': 'Your Online Trial has Expired!'
        },
        'Body': {
            'Text': {
                'Data': data
            }
        }
    })

def lambda_handler(event, context):
  users = client.list_users(
          UserPoolId=USER_POOL_ID)
  # iterate over the returned users and extract username and email
  for user in users['Users']:
     username = user['Username']
     user_created_date = user['UserCreateDate']
     age = user_age(user_created_date)
     print('age %s' % age)
     if age >= EXPIRE_IN_DAYS:
         response = client.admin_disable_user(
                           UserPoolId=USER_POOL_ID,
                           Username=username)
         user_end_session(username)
         send_deactivate_email(username)

  return 'Success!'

Step 4: Configure CloudWatch Events to invoke your Lambda function

To configure CloudWatch Events to invoke your function

  1. Open the Lambda console Functions page.
  2. Choose the function created in the previous step.
  3. Under Designer, choose Add trigger.
  4. Select the trigger EventBridge (CloudWatch Events).
  5. For Rule, choose Create a new rule.
  6. For Rule type, choose Schedule expression.
  7. For Schedule expression, based on your expiration granularity, you can define the schedule expression, such as per hour or per day. For more information on expressions schedules, see Schedule expressions using rate or cron.
  8. Configure the remaining options and choose Add.

Final considerations

To expand on this solutions, you can use AppStream 2.0 on-instance session scripts. This allows you to run your own custom scripts when the users’ streaming sessions starts and ends. Using a custom script at session start, you can record when your users’ streaming sessions begins. At the end of a user’s session, you can use a custom script to calculate the total time of their streaming session. You can then store the total time in an Amazon DynamoDB table. You can then create a Lambda function to periodically check the total time against the expiration configuration.  If a user’s total time exceeds the configured expiration, the Lambda function can disable this user.

Cleaning up

When you complete testing this solution, you need to keep in mind that you are charged for any resources that remain running.  Follow the step-by-step instructions to clean up the related resources created by this solution.

Conclusion

This blog provides a solution to control the expiration time of your application online trial based upon Amazon Cognito, AWS Lambda function, and Amazon AppStream 2.0.  For more information about Amazon AppStream 2.0, see the following:

About the Authors

Changbin Gong is a senior solutions architect at AWS.  He engages with customers to create innovative solutions that address customer business problems and accelerate the adoption of AWS services.  In his spare time, Changbin enjoys reading, running, and traveling.

Ali Othman is a Customer Success Engineer with the AppStream 2.0 service team.