Desktop and Application Streaming

Automate copy of Amazon WorkSpaces tags to hybrid activation resources in AWS Systems Manager

AWS Systems Manager allows customers to manager their Amazon WorkSpaces using hybrid activation managed nodes. Systems Manager provides software inventories, OS patches, and configuration of Windows and Linux WorkSpaces, but it is difficult to identify and map managed node to the corresponding WorkSpaces without any tags. This is a time-consuming process in large organizations where IT Admins manually create these tags for each managed node in Systems Manager. This additional repetitive task leads to inconsistencies and wasted administrative time. The inconsistency in Systems Manager tags can lead to operational errors and other issues.

This blog describes how to implement an automation to copy tags from Amazon WorkSpaces to their corresponding managed node in Systems Manager, deployed using an AWS CloudFormation template.

Time to read 15 minutes
Time to complete 5 minutes
Learning level Expert(400)
Cost to complete (estimated) $5 (can vary with interval rate)
Services used Amazon WorkSpaces
AWS Systems Manager
AWS CloudFormation
AWS Lambda
Amazon EventBridge

Overview of solution

In this solution, we will demonstrate how you can reduce operational burden of manually creating tags for the WorkSpaces managed nodes. To implement this solution, you will use AWS CloudFormation to automatically configure all the required AWS services.

This solution uses Amazon EventBridge to invoke an AWS Lambda function on specified interval. The Lambda function then collects the WorkSpaces and managed nodes in Systems Manager. The Lambda Function matches both resources using the computer name, and then copies the tags of each WorkSpace to its matched managed node. In addition, the Lambda function creates the following tags for identification: Name, WorkspaceID, UserID, BundleID, DirectoryID, DirectoryName, and RegistrationCode.

Architecture Diagram

Walkthrough

In this article, you will perform following activities:

  • Use CloudFormation to deploy the solution to automatically copy WorkSpaces tags to their managed node in Systems Manager.
  • Cleanup resources to prevent unwanted AWS usage charges.

Prerequisites

For this walkthrough, you need the following:

  • An AWS account.
  • An Amazon WorkSpaces deployment
  • An AWS Systems Manager hybrid activation deployment.
  • Permissions to create following service components:
    • AWS Identity and Access Management (IAM) roles and policies
    • AWS Lambda functions
    • Amazon EventBridge rule
  • Permissions to run AWS CloudFormation templates.
  • Basic familiarity with AWS CloudFormation, AWS Systems Manager, Amazon WorkSpaces, and Amazon EventBridge.

Step 1: Deployment of solution via AWS CloudFormation

You will use the provided CloudFormation template to deploy and configure all the required AWS services of this solution. This template cannot be used for WorkSpaces and managed nodes in different accounts or AWS Regions. This deployment is Region specific, and must run in the AWS Region that contains your Amazon WorkSpaces and hybrid activated managed nodes.

Use the following steps to deploy the solution via AWS CloudFormation:

  1. Open a text editor on your local machine.
  2. Copy the below CloudFormation template to your text editor.
    AWSTemplateFormatVersion: '2010-09-09'
      in the same account. It runs as a scheduled cron job in AWS EventBridge.
    Metadata:
      'AWS::CloudFormation::Interface':
        ParameterGroups:
          - Label:
              default: IAM role
            Parameters:
              - IAMRoleName
          - Label:
              default: Lambda function
            Parameters:
              - LambdaFunctionName
              - Timeout
              - Architecture
          - Label:
              default: EventBrdige function
            Parameters:
              - EventBridgeRuleName
              - IntervalRate
        ParameterLabels:
          IAMRoleName:
            default: Provides name of IAM role
          LambdaFunctionName:
            default: Provides name of Lambda function
          Timeout:
            default: Timeout value of Lambda function
          Architecture:
            default: Architecture of Lambda function
          EventBridgeRuleName:
            default: Provides name of EventBridge function
          IntervalRate:
            default: Interval rate of EventBridge function
    Parameters:
      IntervalRate:
        Type: String
        Description: Interval rate of Amazon Event Bridge Rule.
        Default: rate(24 hours)
      EventBridgeRuleName:
        Type: String
        Description: >-
          Provide name of the Eventbridge rule. Name of the region will be added as
          suffix.
        Default: Rule-Automation-WS-Tags-Collector-And-Registrar
      LambdaFunctionName:
        Type: String
        Description: >-
          Provide name of the lambda function which will copy the tags from Amazon
          WorkSpaces to Systems Manager. Name of the region will be added as suffix.
        Default: Fn-Automation-WS-Tags-Collector-And-Registrar
      Architecture:
        Type: String
        Description: >-
          x86_64 is supported in all AWS Regions. arm64 costs less, but is not
          supported in all AWS Regions.
        Default: x86_64
        AllowedValues:
          - arm64
          - x86_64
      Timeout:
        Type: Number
        Description: >-
          The amount of time (in seconds) that Lambda allows the function to run
          before stopping it.
        Default: 5
        MinValue: 1
        MaxValue: 900
        ConstraintDescription: Must be an integer between 1 and 900.
      IAMRoleName:
        Type: String
        Description: >-
          Provide name of the IAM role used by Lambda function to perform task. Name
          of the region will be added as suffix.
        Default: Role-Automation-WS-Tags-Collector-And-Registrar
    Resources:
      cronWSTagsCollector:
        Type: 'AWS::Events::Rule'
        DependsOn: fnWSTagsCollector
        Properties:
          Name: !Sub 
            - '${Name}-${AWS::Region}'
            - Name: !Ref EventBridgeRuleName
          ScheduleExpression: !Ref IntervalRate
          State: ENABLED
          Targets:
            - Arn: !GetAtt 
                - fnWSTagsCollector
                - Arn
              Id: fnWSTagsCollector
      permCronForWSTagCollectorPermission:
        Type: 'AWS::Lambda::Permission'
        DependsOn: fnWSTagsCollector
        Properties:
          Action: 'lambda:InvokeFunction'
          FunctionName: !GetAtt 
            - fnWSTagsCollector
            - Arn
          Principal: events.amazonaws.com
      rWSTagsCollectorRole:
        Type: 'AWS::IAM::Role'
        Properties:
          RoleName: !Sub 
            - '${Name}-${AWS::Region}'
            - Name: !Ref IAMRoleName
          AssumeRolePolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Principal:
                  Service:
                    - lambda.amazonaws.com
                Action:
                  - 'sts:AssumeRole'
          Path: /aws/
          Policies:
            - PolicyName: LambdaInvoke
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action:
                      - 'lambda:InvokeFunction'
                    Resource: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:*'
            - PolicyName: LogCreation
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action:
                      - 'logs:CreateLogGroup'
                      - 'logs:CreateLogStream'
                      - 'logs:PutLogEvents'
                    Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:*'
            - PolicyName: WorkspaceDescribe
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action:
                      - 'workspaces:DescribeWorkspaces'
                      - 'workspaces:DescribeTags'
                    Resource:  
                      - !Sub 'arn:${AWS::Partition}:workspaces:${AWS::Region}:${AWS::AccountId}:workspace/*'
                  - Effect: Allow
                    Action:
                      - 'workspaces:DescribeWorkspaceDirectories'
                    Resource:  
                      - !Sub 'arn:${AWS::Partition}:workspaces:${AWS::Region}:${AWS::AccountId}:directory/*'
            - PolicyName: SSMTagPolicy
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action:
                      - 'ssm:AddTagsToResource'
                      - 'ssm:ListTagsForResource'
                    Resource: !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:managed-instance/*'
                  - Effect: Allow
                    Action:
                      - 'ssm:DescribeInstanceInformation'
                    Resource: !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:*'
      fnWSTagsCollector:
        Type: 'AWS::Lambda::Function'
        Properties:
          FunctionName: !Sub 
            - '${Name}-${AWS::Region}'
            - Name: !Ref LambdaFunctionName
          Handler: index.lambda_handler
          Architectures:
            - !Ref Architecture
          ReservedConcurrentExecutions: 1
          Role: !GetAtt 
            - rWSTagsCollectorRole
            - Arn
          Code:
            ZipFile: !Sub |+
              """
              Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
              SPDX-License-Identifier: MIT-0
    
              Collects tags from the Amazon WorkSpaces and assign the same tags to
              managed instances in AWS Systems Manager.
              """
              import logging
              import boto3
              LOGGER = logging.getLogger()
              LOGGER.setLevel(logging.INFO)
              #defines the connections for Workspaces and SSM agent
              CLIENT = boto3.client('workspaces')
              SSMCLIENT = boto3.client('ssm')
              def lambda_handler(event, context):
                  """declare global variables"""
                  ssm_data = []
                  workspaces_data = []
                  ds_data = []
                  tag_data = []
                  #Collecting the information about directories
                  dscollection = CLIENT.describe_workspace_directories()
                  LOGGER.info(dscollection)
                  while dscollection:
                      ds_data += dscollection['Directories']
                      dscollection = CLIENT.describe_workspace_directories(
                          NextToken=dscollection['NextToken']) if 'NextToken' in dscollection else None
                  #Collecting the information of SSM managed instances
                  ssmcollection = SSMCLIENT.describe_instance_information()
                  LOGGER.info(ssmcollection)
                  while ssmcollection:
                      ssm_data += ssmcollection['InstanceInformationList']
                      ssmcollection = SSMCLIENT.describe_instance_information(
                          NextToken=ssmcollection['NextToken']) if 'NextToken' in ssmcollection else None
                      #Collecting the information of WorkSpaces
                      wscollection = CLIENT.describe_workspaces()
                  while wscollection:
                      workspaces_data += wscollection['Workspaces']
                      wscollection = CLIENT.describe_workspaces(
                          NextToken=wscollection['NextToken']) if 'NextToken' in wscollection else None
    
                  for ssminfo in ssm_data:
                      if 'ComputerName' in ssminfo and 'mi-' in ssminfo['InstanceId']:
                          for workspace in workspaces_data:
                              if workspace['ComputerName'] in ssminfo['ComputerName']:
                                  #name_id = workspace['ComputerName']
                                  #workspace_id = workspace['WorkspaceId']
                                  #user_id = workspace['UserName']
                                  #bundle_id = workspace['BundleId']
                                  LOGGER.info("Running Task for:")
                                  LOGGER.info(workspace['WorkspaceId'])
                                  tags = CLIENT.describe_tags(ResourceId=workspace['WorkspaceId'])
                                  tag_data += tags['TagList']
                                  LOGGER.info(tag_data)
                                  for taginfo in tag_data:
                                      SSMCLIENT.add_tags_to_resource(
                                          ResourceType='ManagedInstance',
                                          ResourceId=ssminfo['InstanceId'],
                                          Tags=[taginfo])
                                  tag_data = []
                                  for ds_id in ds_data:
                                      if ds_id['DirectoryId'] == workspace['DirectoryId']:
                                          #directory_id = ds_id['DirectoryId']
                                          #directory_name = ds_id['DirectoryName']
                                          #registration_code = ds_id['RegistrationCode']
                                          SSMCLIENT.add_tags_to_resource(
                                              ResourceType='ManagedInstance',
                                              ResourceId=ssminfo['InstanceId'],
                                              Tags=[
                                                  {
                                                      'Key': 'DirectoryID',
                                                      'Value': ds_id['DirectoryId']
                                                  },
                                                  {
                                                      'Key': 'DirectoryName',
                                                      'Value': ds_id['DirectoryName']
                                                  },
                                                  {
                                                      'Key': 'RegistrationCode',
                                                      'Value': ds_id['RegistrationCode']
                                                  },
                                              ])
                                      SSMCLIENT.add_tags_to_resource(
                                          ResourceType='ManagedInstance',
                                          ResourceId=ssminfo['InstanceId'],
                                          Tags=[
                                              {
                                                  'Key': 'Name',
                                                  'Value': workspace['ComputerName']
                                              },
                                              {
                                                  'Key': 'WorkspaceID',
                                                  'Value': workspace['WorkspaceId']
                                              },
                                              {
                                                  'Key': 'UserID',
                                                  'Value': workspace['UserName']
                                              },
                                              {
                                                  'Key': 'BundleID',
                                                  'Value': workspace['BundleId']
                                              },
                                          ])
              
          Runtime: python3.11
          Timeout: !Ref Timeout
  3. Save notepad file with YAML file extension.(<filename>.yaml).
  4. Open the AWS CloudFormation console.
  5. Select the AWS Region of your WorkSpaces deployment.
  6. In the navigation pane, choose Stacks.
  7. Choose Create stack, then choose With new resources (standard).
  8. On the Create stack page, select Upload a Template File.
  9. Select Choose File, choose template file that you saved in step 3.
  10. Choose Next.
  11. In the Stack name section, enter a stack name.
  12. In the Parameters section, enter the following values:
    • For EventBridgeRuleName, enter a unique name for the EventBridge rule.
    • For IAMRoleName, enter a unique name for the IAM Role.
    • For IntervalRate, enter an interval rate in the Rate Expression format. Amazon EventBridge will run this automation at interval rate specified.
    • For LambdaFunctionName, enter a unique name for the Lambda function.
  13. Choose Next
  14. On the Configure stack options page, leave all the defaults, and choose Next.
  15. Review the configuration options and acknowledge the IAM checkbox.
  16. Choose Submit.
  17. Verify that the stack has a status of CREATE_COMPLETE.

The stack deploys in approximately 2 minutes and creates the following resources:

  • AWS Lambda function
  • Amazon EventBridge Rule
  • IAM Role

Step 2: Validate the creation of new Tags for hybrid activated managed node in AWS Systems Manager.

You now have an Amazon EventBridge Rule and AWS Lambda function configured. Tags are created for the managed nodes after the interval specified during the creation of the AWS CloudFormation Stack. Use the following steps to validate the tags in Systems Manager:

  1. Open the AWS Systems Manager console.
  2. Select the AWS Region of your managed nodes.
  3. In the navigation pane, choose Fleet Manager from Node Management.
  4. Select any of the Managed Nodes created for WorkSpaces.
  5. Verify the created tags in Tags section.

Cleaning up

It is important to clean up unused resource to avoid unexpected usage fees. To clean up the environment, delete AWS CloudFormation stacks you created in the walkthrough. Deletion of AWS CloudFormation will delete Lambda function, EventBridge rules and associated IAM roles. You must manually delete the tags created for managed nodes in Systems Manager via console or API.

Conclusion

In this blog, you configured Amazon EventBridge with AWS Lambda functions to automatically copy AWS WorkSpace’s tags to their corresponding Systems Manager managed nodes.

To learn more about Amazon WorkSpaces, please review the administration guide. You can get more information about hybrid activation in AWS Systems Manager using this link.

If you would like to discuss how to configure this solution described in this blog for your specific use case, we would love to hear from you. Please reach out to your account team.

  1. Ajay Saini Ajay Saini is a End User Compute Specialist Solution Architect. He works with his customer to help them understand the best practices, accelerate their architecture design, migrate and modernize their existing Virtual Desktop Infrastructure (VDI) to AWS. In his spare time, he enjoys travel and spending time with his family
    Brandon Mahtani Brandon Mahtani is an EUC Specialist Solutions Architect who joined AWS in December of 2018 with over 20 years experience deploying desktop virtualization solutions within Higher Education as well as the Life Sciences industries.