Repository: cloud-custodian/cloud-custodian

Issue:
aws - workspaces directory
### Describe the feature

Workspaces have to be associated with directories. Proposing addition of a new resource type called workspaces-directory and adding basic generic custodian filters like subnet, security-group.

Also, adding a filter to enforce certain behavior on workspace clients. Our use case:

Cloud Custodian will verify that all WorkSpace directories have credential caching disabled for WorkSpaces clients by validating that the "ReconnectEnabled" client property is disabled. WorkSpace resources detected as being non-compliant will take a correct action by setting directory ReconnectEnabled client property to false

### Extra information or context

_No response_


Discussion:

Resolving patch:
diff --git a/c7n/resources/resource_map.py b/c7n/resources/resource_map.py
--- a/c7n/resources/resource_map.py
+++ b/c7n/resources/resource_map.py
@@ -207,5 +207,6 @@
     "aws.waf": "c7n.resources.waf.WAF",
     "aws.waf-regional": "c7n.resources.waf.RegionalWAF",
     "aws.workspaces": "c7n.resources.workspaces.Workspace",
+    "aws.workspaces-directory": "c7n.resources.workspaces.WorkspaceDirectory",
     "aws.workspaces-image": "c7n.resources.workspaces.WorkspaceImage"
 }
diff --git a/c7n/resources/workspaces.py b/c7n/resources/workspaces.py
--- a/c7n/resources/workspaces.py
+++ b/c7n/resources/workspaces.py
@@ -2,6 +2,7 @@
 # SPDX-License-Identifier: Apache-2.0
 import functools
 import itertools
+import jmespath
 
 from c7n.actions import BaseAction
 from c7n.filters import ValueFilter
@@ -9,9 +10,11 @@
 from c7n.manager import resources
 from c7n.query import QueryResourceManager, TypeInfo
 from c7n.tags import universal_augment
+from c7n.exceptions import PolicyValidationError
 from c7n.utils import local_session, type_schema, chunks
 from c7n.filters.iamaccess import CrossAccountAccessFilter
 from c7n.resolver import ValuesFrom
+import c7n.filters.vpc as net_filters
 
 
 @resources.register('workspaces')
@@ -226,3 +229,138 @@ def process(self, resources):
                 continue
             except client.exceptions.ResourceNotFoundException:
                 continue
+
+
+@resources.register('workspaces-directory')
+class WorkspaceDirectory(QueryResourceManager):
+
+    class resource_type(TypeInfo):
+        service = 'workspaces'
+        enum_spec = ('describe_workspace_directories', 'Directories', None)
+        arn_type = 'directory'
+        id = 'DirectoryId'
+        name = 'DirectoryName'
+        universal_taggable = True
+
+    augment = universal_augment
+
+
+@WorkspaceDirectory.filter_registry.register('security-group')
+class WorkspacesDirectorySG(net_filters.SecurityGroupFilter):
+
+    RelatedIdsExpression = ""
+    expressions = ("WorkspaceSecurityGroupId", "WorkspaceCreationProperties.CustomSecurityGroupId")
+
+    def get_related_ids(self, resources):
+        sg_ids = set()
+        for r in resources:
+            for exp in self.expressions:
+                id = jmespath.search(exp, r)
+                if id:
+                    sg_ids.add(id)
+        return list(sg_ids)
+
+
+@WorkspaceDirectory.filter_registry.register('subnet')
+class WorkSpacesDirectorySg(net_filters.SubnetFilter):
+
+    RelatedIdsExpression = "SubnetIds[]"
+
+
+@WorkspaceDirectory.filter_registry.register('client-properties')
+class WorkspacesDirectoryClientProperties(ValueFilter):
+    """Filter workspace directories based off workspace client properties.
+
+    :example:
+
+    .. code-block:: yaml
+
+       policies:
+         - name: workspace-client-credentials
+           resource: aws.workspaces-directory
+           filters:
+            - type: client-properties
+              key: ReconnectEnabled
+              value: ENABLED
+
+    """
+    permissions = ('workspaces:DescribeClientProperties',)
+
+    schema = type_schema('client-properties', rinherit=ValueFilter.schema)
+    annotation_key = 'c7n:client-properties'
+
+    def process(self, directories, event=None):
+        client = local_session(self.manager.session_factory).client('workspaces')
+        results = []
+        for directory in directories:
+            if self.annotation_key not in directory:
+                try:
+                    client_properties = client.describe_client_properties(
+                        ResourceIds=[directory['DirectoryId']]).get(
+                            'ClientPropertiesList')[0].get('ClientProperties')
+                except client.exceptions.ResourceNotFoundException:
+                    continue
+                directory[self.annotation_key] = client_properties
+
+            if self.match(directory[self.annotation_key]):
+                results.append(directory)
+        return results
+
+
+@WorkspaceDirectory.action_registry.register('modify-client-properties')
+class ModifyClientProperties(BaseAction):
+    """Action to enable/disable credential caching for Workspaces client.
+
+    :example:
+
+    .. code-block:: yaml
+
+            policies:
+              - name: workspace-directories-credentials-cache
+                resource: aws.workspaces-directory
+                filters:
+                  - type: client-properties
+                    key: ReconnectEnabled
+                    value: ENABLED
+                actions:
+                  - type: modify-client-properties
+                    attributes:
+                      ClientProperties:
+                        ReconnectEnabled: DISABLED
+
+    """
+    schema = type_schema(
+        'modify-client-properties',
+        required=['attributes'],
+        attributes={
+            'type': 'object',
+            'additionalProperties': False,
+            'properties': {
+                'ClientProperties': {
+                    'type': 'object',
+                    'additionalProperties': False,
+                    'required': ['ReconnectEnabled'],
+                    'properties': {
+                        'ReconnectEnabled': {'enum': ['DISABLED', 'ENABLED']}
+                    }
+                }
+            }})
+
+    permissions = ('workspaces:ModifyClientProperties',)
+
+    def validate(self):
+        for f in self.manager.iter_filters():
+            if isinstance(f, WorkspacesDirectoryClientProperties):
+                return self
+        raise PolicyValidationError(
+            '`modify-client-properties` may only be used in '
+            'conjunction with `client-properties` filter on %s' % (self.manager.data,))
+
+    def process(self, directories):
+        client = local_session(self.manager.session_factory).client('workspaces')
+        for directory in directories:
+            try:
+                client.modify_client_properties(
+                    ResourceId=directory['DirectoryId'], **self.data['attributes'])
+            except client.exceptions.ResourceNotFoundException:
+                continue


Test patch:
diff --git a/tests/data/placebo/test_workspaces_directory_client_properties/tagging.GetResources_1.json b/tests/data/placebo/test_workspaces_directory_client_properties/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_client_properties/tagging.GetResources_1.json
@@ -0,0 +1,22 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:workspaces:us-east-1:644160558196:directory/d-90677cc91c",
+                "Tags": []
+            },
+            {
+                "ResourceARN": "arn:aws:workspaces:us-east-1:644160558196:directory/d-90675153fc",
+                "Tags": [
+                    {
+                        "Key": "c7n",
+                        "Value": "test"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_1.json b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_1.json
@@ -0,0 +1,14 @@
+{
+    "status_code": 200,
+    "data": {
+        "ClientPropertiesList": [
+            {
+                "ResourceId": "d-90677cc91c",
+                "ClientProperties": {
+                    "ReconnectEnabled": "DISABLED"
+                }
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_2.json b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_2.json
@@ -0,0 +1,14 @@
+{
+    "status_code": 200,
+    "data": {
+        "ClientPropertiesList": [
+            {
+                "ResourceId": "d-90675153fc",
+                "ClientProperties": {
+                    "ReconnectEnabled": "ENABLED"
+                }
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_3.json b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeClientProperties_3.json
@@ -0,0 +1,14 @@
+{
+    "status_code": 200,
+    "data": {
+        "ClientPropertiesList": [
+            {
+                "ResourceId": "d-90675153fc",
+                "ClientProperties": {
+                    "ReconnectEnabled": "DISABLED"
+                }
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeWorkspaceDirectories_1.json b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeWorkspaceDirectories_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.DescribeWorkspaceDirectories_1.json
@@ -0,0 +1,95 @@
+{
+    "status_code": 200,
+    "data": {
+        "Directories": [
+            {
+                "DirectoryId": "d-90677cc91c",
+                "Alias": "d-90677cc91c",
+                "DirectoryName": "corp.amazonworkspaces.com",
+                "RegistrationCode": "SLiad+7UQFN3",
+                "SubnetIds": [
+                    "subnet-083ab4dd1a8b6eaa6",
+                    "subnet-0544e30702e2e1ad4"
+                ],
+                "DnsIpAddresses": [
+                    "172.16.0.18",
+                    "172.16.1.157"
+                ],
+                "CustomerUserName": "Administrator",
+                "IamRoleId": "arn:aws:iam::644160558196:role/workspaces_DefaultRole",
+                "DirectoryType": "SIMPLE_AD",
+                "WorkspaceSecurityGroupId": "sg-09c19e05bb8d7ece1",
+                "State": "REGISTERED",
+                "WorkspaceCreationProperties": {
+                    "EnableWorkDocs": true,
+                    "EnableInternetAccess": false,
+                    "CustomSecurityGroupId": "sg-0811d6a4d7c41b530",
+                    "UserEnabledAsLocalAdministrator": true,
+                    "EnableMaintenanceMode": true
+                },
+                "WorkspaceAccessProperties": {
+                    "DeviceTypeWindows": "ALLOW",
+                    "DeviceTypeOsx": "ALLOW",
+                    "DeviceTypeWeb": "DENY",
+                    "DeviceTypeIos": "ALLOW",
+                    "DeviceTypeAndroid": "ALLOW",
+                    "DeviceTypeChromeOs": "ALLOW",
+                    "DeviceTypeZeroClient": "ALLOW",
+                    "DeviceTypeLinux": "DENY"
+                },
+                "Tenancy": "SHARED",
+                "SelfservicePermissions": {
+                    "RestartWorkspace": "ENABLED",
+                    "IncreaseVolumeSize": "ENABLED",
+                    "ChangeComputeType": "ENABLED",
+                    "SwitchRunningMode": "ENABLED",
+                    "RebuildWorkspace": "ENABLED"
+                }
+            },
+            {
+                "DirectoryId": "d-90675153fc",
+                "Alias": "d-90675153fc",
+                "DirectoryName": "corp.amazonworkspaces.com",
+                "RegistrationCode": "SLiad+S7DNDA",
+                "SubnetIds": [
+                    "subnet-914763e7",
+                    "subnet-efbcccb7"
+                ],
+                "DnsIpAddresses": [
+                    "172.31.14.121",
+                    "172.31.21.194"
+                ],
+                "CustomerUserName": "Administrator",
+                "IamRoleId": "arn:aws:iam::644160558196:role/workspaces_DefaultRole",
+                "DirectoryType": "SIMPLE_AD",
+                "WorkspaceSecurityGroupId": "sg-07f76375bda506a4a",
+                "State": "REGISTERED",
+                "WorkspaceCreationProperties": {
+                    "EnableWorkDocs": false,
+                    "EnableInternetAccess": true,
+                    "UserEnabledAsLocalAdministrator": true,
+                    "EnableMaintenanceMode": true
+                },
+                "WorkspaceAccessProperties": {
+                    "DeviceTypeWindows": "ALLOW",
+                    "DeviceTypeOsx": "ALLOW",
+                    "DeviceTypeWeb": "DENY",
+                    "DeviceTypeIos": "ALLOW",
+                    "DeviceTypeAndroid": "ALLOW",
+                    "DeviceTypeChromeOs": "ALLOW",
+                    "DeviceTypeZeroClient": "ALLOW",
+                    "DeviceTypeLinux": "DENY"
+                },
+                "Tenancy": "SHARED",
+                "SelfservicePermissions": {
+                    "RestartWorkspace": "ENABLED",
+                    "IncreaseVolumeSize": "ENABLED",
+                    "ChangeComputeType": "ENABLED",
+                    "SwitchRunningMode": "ENABLED",
+                    "RebuildWorkspace": "ENABLED"
+                }
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.ModifyClientProperties_1.json b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.ModifyClientProperties_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_client_properties/workspaces.ModifyClientProperties_1.json
@@ -0,0 +1,6 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_subnet_sg/ec2.DescribeSecurityGroups_1.json b/tests/data/placebo/test_workspaces_directory_subnet_sg/ec2.DescribeSecurityGroups_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_subnet_sg/ec2.DescribeSecurityGroups_1.json
@@ -0,0 +1,39 @@
+{
+    "status_code": 200,
+    "data": {
+        "SecurityGroups": [
+            {
+                "Description": "Amazon WorkSpaces Security Group",
+                "GroupName": "d-90675153fc_workspacesMembers",
+                "IpPermissions": [],
+                "OwnerId": "644160558196",
+                "GroupId": "sg-07f76375bda506a4a",
+                "IpPermissionsEgress": [
+                    {
+                        "IpProtocol": "-1",
+                        "IpRanges": [
+                            {
+                                "CidrIp": "0.0.0.0/0"
+                            }
+                        ],
+                        "Ipv6Ranges": [],
+                        "PrefixListIds": [],
+                        "UserIdGroupPairs": []
+                    }
+                ],
+                "Tags": [
+                    {
+                        "Key": "NetworkLocation",
+                        "Value": "Private"
+                    },
+                    {
+                        "Key": "Created by Amazon WorkSpaces",
+                        "Value": "Amazon WorkSpaces"
+                    }
+                ],
+                "VpcId": "vpc-d2d616b5"
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_subnet_sg/ec2.DescribeSubnets_1.json b/tests/data/placebo/test_workspaces_directory_subnet_sg/ec2.DescribeSubnets_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_subnet_sg/ec2.DescribeSubnets_1.json
@@ -0,0 +1,108 @@
+{
+    "status_code": 200,
+    "data": {
+        "Subnets": [
+            {
+                "AvailabilityZone": "us-east-1b",
+                "AvailabilityZoneId": "use1-az6",
+                "AvailableIpAddressCount": 4084,
+                "CidrBlock": "172.31.16.0/20",
+                "DefaultForAz": true,
+                "MapPublicIpOnLaunch": true,
+                "MapCustomerOwnedIpOnLaunch": false,
+                "State": "available",
+                "SubnetId": "subnet-efbcccb7",
+                "VpcId": "vpc-d2d616b5",
+                "OwnerId": "644160558196",
+                "AssignIpv6AddressOnCreation": false,
+                "Ipv6CidrBlockAssociationSet": [],
+                "Tags": [
+                    {
+                        "Key": "Internal",
+                        "Value": "False"
+                    },
+                    {
+                        "Key": "Name",
+                        "Value": ""
+                    }
+                ],
+                "SubnetArn": "arn:aws:ec2:us-east-1:644160558196:subnet/subnet-efbcccb7"
+            },
+            {
+                "AvailabilityZone": "us-east-1a",
+                "AvailabilityZoneId": "use1-az4",
+                "AvailableIpAddressCount": 250,
+                "CidrBlock": "172.16.1.0/24",
+                "DefaultForAz": false,
+                "MapPublicIpOnLaunch": false,
+                "MapCustomerOwnedIpOnLaunch": false,
+                "State": "available",
+                "SubnetId": "subnet-083ab4dd1a8b6eaa6",
+                "VpcId": "vpc-0598080981e0332f9",
+                "OwnerId": "644160558196",
+                "AssignIpv6AddressOnCreation": false,
+                "Ipv6CidrBlockAssociationSet": [],
+                "Tags": [
+                    {
+                        "Key": "AWSServiceAccount",
+                        "Value": "644160558196"
+                    }
+                ],
+                "SubnetArn": "arn:aws:ec2:us-east-1:644160558196:subnet/subnet-083ab4dd1a8b6eaa6"
+            },
+            {
+                "AvailabilityZone": "us-east-1b",
+                "AvailabilityZoneId": "use1-az6",
+                "AvailableIpAddressCount": 250,
+                "CidrBlock": "172.16.0.0/24",
+                "DefaultForAz": false,
+                "MapPublicIpOnLaunch": false,
+                "MapCustomerOwnedIpOnLaunch": false,
+                "State": "available",
+                "SubnetId": "subnet-0544e30702e2e1ad4",
+                "VpcId": "vpc-0598080981e0332f9",
+                "OwnerId": "644160558196",
+                "AssignIpv6AddressOnCreation": false,
+                "Ipv6CidrBlockAssociationSet": [],
+                "Tags": [
+                    {
+                        "Key": "AWSServiceAccount",
+                        "Value": "644160558196"
+                    },
+                    {
+                        "Key": "c7n",
+                        "Value": "pratyush"
+                    }
+                ],
+                "SubnetArn": "arn:aws:ec2:us-east-1:644160558196:subnet/subnet-0544e30702e2e1ad4"
+            },
+            {
+                "AvailabilityZone": "us-east-1a",
+                "AvailabilityZoneId": "use1-az4",
+                "AvailableIpAddressCount": 4085,
+                "CidrBlock": "172.31.0.0/20",
+                "DefaultForAz": true,
+                "MapPublicIpOnLaunch": true,
+                "MapCustomerOwnedIpOnLaunch": false,
+                "State": "available",
+                "SubnetId": "subnet-914763e7",
+                "VpcId": "vpc-d2d616b5",
+                "OwnerId": "644160558196",
+                "AssignIpv6AddressOnCreation": false,
+                "Ipv6CidrBlockAssociationSet": [],
+                "Tags": [
+                    {
+                        "Key": "NetworkLocation",
+                        "Value": "Public"
+                    },
+                    {
+                        "Key": "TestTag",
+                        "Value": "TestValue"
+                    }
+                ],
+                "SubnetArn": "arn:aws:ec2:us-east-1:644160558196:subnet/subnet-914763e7"
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_subnet_sg/tagging.GetResources_1.json b/tests/data/placebo/test_workspaces_directory_subnet_sg/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_subnet_sg/tagging.GetResources_1.json
@@ -0,0 +1,17 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [
+            {
+                "ResourceARN": "arn:aws:workspaces:us-east-1:644160558196:directory/d-90677cc91c",
+                "Tags": []
+            },
+            {
+                "ResourceARN": "arn:aws:workspaces:us-east-1:644160558196:directory/d-90675153fc",
+                "Tags": []
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_subnet_sg/tagging.TagResources_1.json b/tests/data/placebo/test_workspaces_directory_subnet_sg/tagging.TagResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_subnet_sg/tagging.TagResources_1.json
@@ -0,0 +1,7 @@
+{
+    "status_code": 200,
+    "data": {
+        "FailedResourcesMap": {},
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_subnet_sg/workspaces.DescribeTags_1.json b/tests/data/placebo/test_workspaces_directory_subnet_sg/workspaces.DescribeTags_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_subnet_sg/workspaces.DescribeTags_1.json
@@ -0,0 +1,12 @@
+{
+    "status_code": 200,
+    "data": {
+        "TagList": [
+            {
+                "Key": "c7n",
+                "Value": "test"
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_workspaces_directory_subnet_sg/workspaces.DescribeWorkspaceDirectories_1.json b/tests/data/placebo/test_workspaces_directory_subnet_sg/workspaces.DescribeWorkspaceDirectories_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_workspaces_directory_subnet_sg/workspaces.DescribeWorkspaceDirectories_1.json
@@ -0,0 +1,95 @@
+{
+    "status_code": 200,
+    "data": {
+        "Directories": [
+            {
+                "DirectoryId": "d-90677cc91c",
+                "Alias": "d-90677cc91c",
+                "DirectoryName": "corp.amazonworkspaces.com",
+                "RegistrationCode": "SLiad+7UQFN3",
+                "SubnetIds": [
+                    "subnet-083ab4dd1a8b6eaa6",
+                    "subnet-0544e30702e2e1ad4"
+                ],
+                "DnsIpAddresses": [
+                    "172.16.0.18",
+                    "172.16.1.157"
+                ],
+                "CustomerUserName": "Administrator",
+                "IamRoleId": "arn:aws:iam::644160558196:role/workspaces_DefaultRole",
+                "DirectoryType": "SIMPLE_AD",
+                "WorkspaceSecurityGroupId": "sg-09c19e05bb8d7ece1",
+                "State": "REGISTERED",
+                "WorkspaceCreationProperties": {
+                    "EnableWorkDocs": true,
+                    "EnableInternetAccess": false,
+                    "CustomSecurityGroupId": "sg-0811d6a4d7c41b530",
+                    "UserEnabledAsLocalAdministrator": true,
+                    "EnableMaintenanceMode": true
+                },
+                "WorkspaceAccessProperties": {
+                    "DeviceTypeWindows": "ALLOW",
+                    "DeviceTypeOsx": "ALLOW",
+                    "DeviceTypeWeb": "DENY",
+                    "DeviceTypeIos": "ALLOW",
+                    "DeviceTypeAndroid": "ALLOW",
+                    "DeviceTypeChromeOs": "ALLOW",
+                    "DeviceTypeZeroClient": "ALLOW",
+                    "DeviceTypeLinux": "DENY"
+                },
+                "Tenancy": "SHARED",
+                "SelfservicePermissions": {
+                    "RestartWorkspace": "ENABLED",
+                    "IncreaseVolumeSize": "ENABLED",
+                    "ChangeComputeType": "ENABLED",
+                    "SwitchRunningMode": "ENABLED",
+                    "RebuildWorkspace": "ENABLED"
+                }
+            },
+            {
+                "DirectoryId": "d-90675153fc",
+                "Alias": "d-90675153fc",
+                "DirectoryName": "corp.amazonworkspaces.com",
+                "RegistrationCode": "SLiad+S7DNDA",
+                "SubnetIds": [
+                    "subnet-914763e7",
+                    "subnet-efbcccb7"
+                ],
+                "DnsIpAddresses": [
+                    "172.31.14.121",
+                    "172.31.21.194"
+                ],
+                "CustomerUserName": "Administrator",
+                "IamRoleId": "arn:aws:iam::644160558196:role/workspaces_DefaultRole",
+                "DirectoryType": "SIMPLE_AD",
+                "WorkspaceSecurityGroupId": "sg-07f76375bda506a4a",
+                "State": "REGISTERED",
+                "WorkspaceCreationProperties": {
+                    "EnableWorkDocs": false,
+                    "EnableInternetAccess": true,
+                    "UserEnabledAsLocalAdministrator": true,
+                    "EnableMaintenanceMode": true
+                },
+                "WorkspaceAccessProperties": {
+                    "DeviceTypeWindows": "ALLOW",
+                    "DeviceTypeOsx": "ALLOW",
+                    "DeviceTypeWeb": "DENY",
+                    "DeviceTypeIos": "ALLOW",
+                    "DeviceTypeAndroid": "ALLOW",
+                    "DeviceTypeChromeOs": "ALLOW",
+                    "DeviceTypeZeroClient": "ALLOW",
+                    "DeviceTypeLinux": "DENY"
+                },
+                "Tenancy": "SHARED",
+                "SelfservicePermissions": {
+                    "RestartWorkspace": "ENABLED",
+                    "IncreaseVolumeSize": "ENABLED",
+                    "ChangeComputeType": "ENABLED",
+                    "SwitchRunningMode": "ENABLED",
+                    "RebuildWorkspace": "ENABLED"
+                }
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/test_workspaces.py b/tests/test_workspaces.py
--- a/tests/test_workspaces.py
+++ b/tests/test_workspaces.py
@@ -200,3 +200,58 @@ def test_workspaces_image_delete_associated_error(self):
         client = session_factory().client('workspaces')
         call = client.describe_workspace_images(ImageIds=[imageId])
         self.assertTrue(call['Images'])
+
+    def test_workspaces_directory_subnet_sg(self):
+        factory = self.replay_flight_data("test_workspaces_directory_subnet_sg")
+        p = self.load_policy(
+            {
+                "name": "workspace-directory-sg-subnet",
+                "resource": "workspaces-directory",
+                "filters": [
+                    {'type': 'subnet',
+                     'key': 'tag:NetworkLocation',
+                     'value': 'Public'},
+                    {'type': 'security-group',
+                     'key': 'tag:NetworkLocation',
+                     'value': 'Private'}],
+                'actions': [{
+                    'type': 'tag',
+                    'key': 'c7n',
+                    'value': 'test'
+                }]
+            },
+            session_factory=factory,
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]['DirectoryId'], 'd-90675153fc')
+        client = factory().client('workspaces')
+        tags = client.describe_tags(ResourceId=resources[0]['DirectoryId'])
+        self.assertEqual({'Key': 'c7n', 'Value': 'test'}, tags['TagList'][0])
+
+    def test_workspaces_directory_client_properties(self):
+        factory = self.replay_flight_data("test_workspaces_directory_client_properties")
+        p = self.load_policy(
+            {
+                "name": "workspace-directory-sg-subnet",
+                "resource": "workspaces-directory",
+                "filters": [
+                    {'type': 'client-properties',
+                     'key': 'ReconnectEnabled',
+                     'value': 'ENABLED'}],
+                'actions': [{
+                    'type': 'modify-client-properties',
+                    'attributes': {
+                        'ClientProperties': {'ReconnectEnabled': 'DISABLED'}
+                    }
+                }]
+            },
+            session_factory=factory,
+        )
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]['DirectoryId'], 'd-90675153fc')
+        client = factory().client('workspaces')
+        cp = client.describe_client_properties(ResourceIds=['d-90675153fc'])
+        self.assertEqual({'ReconnectEnabled': 'DISABLED'}, cp.get(
+            'ClientPropertiesList')[0].get('ClientProperties'))