Desktop and Application Streaming

Using serverless AWS services as an external authenticator for NICE DCV

NICE DCV is a high-performance remote display protocol that provides additional authentication flexibility through DCV external authentication. For a user to gain access to a secure DCV session stream, they must be authenticated against the display protocol. By default, DCV uses system authentication, which delegates authentication to the underlying operating system. With using external authentication, customers are able to customize the authentication process for DCV users.

External authentication validates authentication tokens to provide access to the DCV session stream. Customers looking to use custom tokens, such as multi-factor authentication (MFA) codes or unique identifiers, are able to use external authentication to meet these requirements. This is especially useful when DCV sessions are streamed through a DCV Connection Gateway. DCV Connection Gateways require an authentication token and a session ID to successfully establish connections.

In this blog, you will configure AWS Lambda functions to generate and validate authentication tokens. To invoke token validation, you will create an Amazon API Gateway that is securely accessed through an Amazon Virtual Private Cloud (Amazon VPC) endpoint. To store your token information, you will use Amazon DynamoDB.

Time to read 15 minutes
Time to complete One hour
Cost to complete <$10
Learning level 300
Services used Amazon Elastic Compute Cloud (Amazon EC2), AWS Lambda, Amazon API Gateway, NICE DCV, Amazon DynamoDB

Architecture

Architecture diagram of a DCV user retrieving an authentication token and using it to connect to a DCV session.

In the preceding diagram, the user invokes the Get-Token Lambda function to receive an authentication token. This token is stored in DynamoDB for future validation. The user then connects to a DCV instance passing in the authentication token. That token is passed through a private endpoint to API Gateway, which triggers a Lambda function to validate the token. If the token is valid, the user begins their session stream.

Prerequisites

To follow this blog, you will need AWS Identity and Access Management (IAM) permissions to:

  • Create an API Gateway
  • Create a VPC endpoint
  • Create a DynamoDB table
  • Create and invoke a Lambda function
  • Create an IAM Role and Policy

You will need an existing DCV Amazon EC2 instance in an Amazon VPC. This instance can be based on the DCV AWS Marketplace AMI, DCV AWS CloudFormation template, or manually created. To connect, you need to have a DCV client installed. OS-based clients can be downloaded from the DCV downloads page. If you would prefer to use a browser-based connection, confirm you are connecting with a DCV-supported browser. Note that browser-based connections currently do not support QUIC connections.

Walkthrough

Step 1: Create a VPC endpoint for API Gateway

In this step, you create a VPC endpoint to provide secure, private access to the API Gateway. This is used for token validation without going to the service’s public endpoint.

  1. Navigate to the Amazon VPC Endpoints console.
  2. Select Create endpoint.
  3. (Optional) Assign a Name tag to the endpoint.
  4. Verify the AWS services option is selected in Service Category.
  5. In the Services search box, insert execute-api, select the proposed filter (formatted as com.amazonaws.REGION.execute-api) and then select the com.amazonaws.REGION.execute-api item.
  6. In the VPC section, select your VPC ID from the dropdown.
  7. In the Subnets section, select the Availability Zone and their subnets where the DCV instance was created.
  8. In the Security groups section, select a security group that will allow access to the endpoint within the subnet.
  9. In the Policy section, select Full access.
  10. Finally, select Create endpoint.
  11. Take a note of the VPC endpoint ID, it is needed later.

Step 2: Create a DynamoDB table

In this step, you will create a DynamoDB table that stores the authentication tokens. This table is configured to expire tokens after 24 hours.

  1. Navigate to the Amazon DynamoDB console.
  2. Select Tables from the list on the left.
  3. Select Create table.
  4. In the Table name box insert AuthenticationTokensTable.
  5. In the Partition key box insert AuthToken and set the type to String.
  6. Select Create table.
  7. Select Tables from the list on the left.
  8. Choose the AuthenticationTokensTable table.
  9. In the Additional settings tab, in the Time to Live (TTL) section, choose Turn on.
  10. In the Enable Time to Live (TTL) page, enter ExpirationTime in the TTL attribute name box.
  11. Choose Turn on TTL to save the settings and enable TTL.

Step 3: Create the Get-Token Lambda function

In this step, you create the Get-Token Lambda function. This function allows the user to generate an authentication token to be used to authenticate with the DCV server.

  1. Navigate to the AWS Lambda console.
  2. Select Create function.
  3. Verify Author from scratch is selected.
  4. Insert Get-Token in the Function name box.
  5. In the Runtime box, select Python 3.9.
  6. In the Architecture box, select arm64.
  7. Select Create function.
  8. Once the function is created, replace the content of the Lambda function with the following:
    • import boto3
      import json
      import random
      import string
      import time
      
      TABLE_NAME = "AuthenticationTokensTable"
      TOKEN_LENGTH = 32
      TOKEN_EXPIRATION_TIME_SECONDS = 24 * 60 * 60 # 24 Hours
      
      def generate_token():
          letters = string.ascii_letters + string.digits
          return ''.join(random.choice(letters) for i in range(TOKEN_LENGTH))
      
      def lambda_handler(event, context):
          try:
              userName = event['user']
              sessionId = event['sessionId']
              expirationTime = int(time.time()) + TOKEN_EXPIRATION_TIME_SECONDS 
              authToken = generate_token()
              
              dynamodb = boto3.client("dynamodb")
              scan = dynamodb.put_item(
                  TableName=TABLE_NAME,
                  Item={
                      "AuthToken": {"S": authToken},
                      "SessionId": {"S": sessionId},
                      "UserName": {"S": userName},
                      "ExpirationTime": {"N" : str(expirationTime) }
                  }
              )
              
              return {
                  'statusCode': 200,
                  'body': authToken
              }
          except:
              return {
                  'statusCode': 500,
                  'body': 'Failed to generate token'
              }
  9. Select Deploy.
  10. Navigate to the Configuration tab, select Permissions from the list on the left.
  11. Select the role link below Role name.
  12. Select the Add permissions button and then Create inline policy.
  13. Select the Choose a service link near Service and select DynamoDB.
  14. In Actions, select PutItem under Write.
  15. In Resources, select Specific and then select Add ARN. In the dialog, insert the region where you created the DynamoDB table in the Region box and AuthenticationTokensTable in the Table name box. Then select Add.
  16. Select Review policy.
  17. Insert a name for the policy in the Name box.
  18. Select Create policy.

Step 4: Create the Validate-Token Lambda function

In this step, you create the Validate-Token Lambda function. This function allows the DCV server to validate the authentication token passed by the user when connecting to the DCV session.

  1. Navigate to the AWS Lambda console.
  2. Select Create function.
  3. Verify Author from scratch is selected.
  4. Insert Validate-Token in the Function name box.
  5. In the Runtime box, select Python 3.9.
  6. In the Architecture box, select arm64.
  7. Select Create function.
  8. Once the function is created, replace the content of the Lambda function with the following:
    • import json
      import boto3
      import time
      from urllib import parse
      
      TABLE_NAME = "AuthenticationTokensTable"
      
      def lambda_handler(event, context):
          try:
              params = dict(parse.parse_qsl(event['body'], strict_parsing=True))
              
              authToken = params.get('authenticationToken')
              sessionId = params.get('sessionId')
          
              if authToken == None or sessionId == None:
                  return {
                      'statusCode': 200,
                      'body': '<auth result="no"><message>Invalid format</message></auth>'
                  } 
          
              dynamodb = boto3.client("dynamodb")
              scan = dynamodb.scan(
                  TableName=TABLE_NAME,
                  FilterExpression="AuthToken = :AuthToken AND SessionId = :SessionId",
                  ExpressionAttributeValues={
                      ":AuthToken": {"S": authToken},
                      ":SessionId": {"S": sessionId}
                      }
              )
              
              count = scan["Count"]
              if count:
                  expirationTime = int(scan["Items"][0]["ExpirationTime"]["N"])
                  if expirationTime < int(time.time()):
                      return {
                          'statusCode': 200,
                          'body': '<auth result="no"><message>Token expired</message></auth>'
                      }
                      
                  username = scan["Items"][0]["UserName"]["S"]
                  return {
                      'statusCode': 200,
                      'body': '<auth result="yes"><username>' + username + '</username></auth>'
                  }
              else:
                  return {
                      'statusCode': 200,
                      'body': '<auth result="no"><message>Authentication token not found</message></auth>'
                  }
          except:
              return {
                  'statusCode': 200,
                  'body': '<auth result="no"><message>Generic error</message></auth>'
              }
  9. Select on Deploy.
  10. Navigate to the Configuration tab, select Permissions from the list on the left and the select the link below Role name.
  11. Select the Add permissions button and then Create inline policy.
  12. Select the Choose a service link near Service and select DynamoDB.
  13. In Actions, select Scan under Read.
  14. In Resources, select Specific and then select Add ARN in the table section. In the dialog, insert the region where you created the DynamoDB table in the Region box and AuthenticationTokensTable in the Table name box. Select Add.
  15. Select Review policy.
  16. Insert a name for the policy in the Name box.
  17. Select Create policy.

Step 5: Create an API Gateway

In this step, you create an API Gateway. This acts as a private trigger to invoke the Validate-Token function you created in the previous step.

  1. Navigate to the Amazon API Gateway console.
  2. In the REST API Private section, select the Build button.
  3. In the opened dialog, choose OK.
  4. Confirm REST is selected in the Choose the protocol section.
  5. In the Create new API section, select New API.
  6. Insert ValidateToken in the API name box.
  7. In the Endpoint Type box, select Private.
  8. In the VPC Endpoint IDs box insert the VPC endpoint ID from step 1. It may be required to select the Add button to add it.
  9. Select Create API.
  10. Once the API is created, in the Actions box, select Create method.
  11. Select POST and select the accept button.
  12. Confirm Lambda function is selected in Integration type.
  13. Toggle the Use Lambda Proxy Integration checkbox.
  14. In the Lambda Function box, insert Validate-Token.
  15. Select Save and then Ok.
  16. Select Resource Policy from the list on the left.
  17. Select the Source VPC Allowlist button at the bottom.
  18. In the newly added policy, replace {{stageNameOrWildcard}}/{{httpVerbOrWildcard}}/{{resourcePathOrWildcard}} with Beta/POST/ and “aws:sourceVpc”: “vpcID” with  “aws:sourceVpce”: “VPC Endpoint ID from Step 1
    • Note that aws:sourceVpc has been replaced with aws:sourceVpce.
  19. Select Save.
  20. Select Resources from the list on the left.
  21. In the Actions box, select Deploy API.
  22. Select [New Stage] from the Deployment stage box.
  23. Insert Beta in the Stage name box.
  24. Select Deploy.
  25. Take a note of the invoke URL of the newly created API and the API Id. The URL is in the format https://API-Id.execute-api.REGION.amazonaws.com/Beta/ and the API Id can be retrieved from it. They will be needed later to configure the DCV server and create an IAM role.

Step 6: Create an IAM role for the DCV instance

In this step, you create an IAM role for your DCV EC2 instance. The IAM role allows the DCV server to invoke the API Gateway to validate incoming authentication tokens as well as license properly.

  1. Navigate to the AWS IAM console.
  2. Select Create Policy.
  3. Select the Choose a service link in the Service section and select ExecuteAPI.
  4. In Actions, select Invoke under Write.
  5. In Resources, select the Specific radio button. Within the execute-api-general section, select Add ARN. In the dialog, insert the following:
    • In the Region box the region where you created the API Gateway
    • In the Api id box the API id retrieved at the end of step 5.
    • In the Stage box insert Beta
    • In the Method box insert POST
    • In the Api specific resource path box insert ValidateToken
    • Select Add.
  6. Select Next: Tags.
  7. Select Next: Review.
  8. Insert ValidateTokenApiPolicy in the Name box.
  9. Select Create policy.
  10. Choose Roles from the list on the left.
  11. Select the Create role button.
  12. Select EC2 in the Use case section.
  13. Select Next.
  14. Insert ValidateTokenApiPolicy in the filter box and then select ValidateTokenApiPolicy.
  15. Add a policy that meets the minimum permissions to access the DCV Amazon Simple Storage Service (Amazon S3) licensing bucket.
  16. Select Next.
  17. Insert ValidateTokenApiRole in the Name box.
  18. Select the Create role button.

Step 7: Testing your configuration

In this step, you update the auth-token-verifier setting within your DCV configuration. Once updated, you generate a token and connect.

  1. To use external authentication, you will need to configure DCV to point to your external authentication server within your DCV server configuration. Replace APIGatewayURL with the URL you took note of in step 5.
    • Windows (PowerShell):
      • New-ItemProperty -Path "Microsoft.PowerShell.Core\Registry::\HKEY_USERS\S-1-5-18\Software\GSettings\com\nicesoftware\dcv\security" -Name auth-token-verifier -PropertyType String -Value "APIGatewayURL" -Force
    • Linux (Bash):
      • sed -i --expression 's|#auth-token-verifier="https://127.0.0.1:8444"|auth-token-verifier="APIGatewayURL"|' /etc/dcv/dcv.conf
  2. Navigate to the AWS CloudShell console or a terminal with the AWS Command Line Interface configured.
  3. Invoke the following command and take note of the authentication token produced as output. Replace DCVLocalUser with the user connecting to DCV. If you are not using a console session, replace console with your respective session Id.
    • aws lambda invoke --function-name Get-Token --payload '{ "user": "DCVLocalUser", "sessionId": "console" }' --cli-binary-format raw-in-base64-out response.json > /dev/null && cat response.json | jq -r .body
  4. Navigate to the Amazon EC2 console.
  5. Locate your targeted DCV server and toggle the checkbox for this instance.
  6. A details window will populate at the bottom of the page for your selected instance. Take note of the IP address or DNS name you are connecting to. For example, if chose to connect from the public internet, use the content of the Public IPv4 address or Public IPv4 DNS attributes.
  7. Open your NICE DCV client and input connection string. Replace NICE DCV server’s IP or DNS name and the authentication token that you took note of earlier in this step. For console sessions, the connection notation is the following:
    • OS-based clients: IP-or-DNS/?authToken=AUTHENTICATION_TOKEN#console > Connect
    • Browser-based clients: https://IP-or-DNS:8443/?authToken=AUTHENTICATION_TOKEN#console
  8. By default, NICE DCV server will use a self-signed certificate for the connection. You will need to trust this certificate before connecting by performing the following:
    • OS-based clients: Select the Trust & Connect button on the popup window.
    • Browser-based clients: The webpage will give you a connection. This differs browser to browser, but you will need to proceed DCV server URL. This is typically done by selecting the Advanced button and then proceeding to the URL.
  9. You are now connected to your DCV session. Review the DCV user guide for session usage details.

Clean up

To avoid unwanted charges, you need to delete the VPC endpoint created in step one. Also, delete the DynamoDB table created in step two and the API Gateway created in step five. Finally, terminate any DCV instances you provisioned for this walkthrough.

Conclusions

In this blog post, you created a serverless external authenticator for DCV server by leveraging API Gateway and Lambda. This configuration can be used with workloads that are facilitated by a portal, or an automation that returns connection information to the user. In production, users should not be directly invoking the Get-Token Lambda function; a portal-like experience mitigates that. You may update the Lambda configuration to use an authentication token that pertains to your environment, such as MFA code or another unique identifier.

Silvio is a Senior Software Engineer for AWS End User Computing and
he has almost 17 years of experience working in the software industry.
He has been working on the DCV streaming protocol since joining AWS in 2017.