Configure AWS WorkSpaces Directories

The aws:workspaces/directory:Directory resource, part of the Pulumi AWS provider, registers a directory with AWS WorkSpaces, enabling virtual desktop provisioning for directory users. This guide focuses on three capabilities: personal vs pooled directory types, Active Directory and SAML authentication integration, and device access controls and IP restrictions.

WorkSpaces directories depend on AWS Directory Service directories or Active Directory domains, VPC subnets, and the workspaces_DefaultRole IAM role. The examples are intentionally small. Combine them with your own directory infrastructure, VPC configuration, and IAM roles.

Register a directory with WorkSpaces for personal desktops

Most deployments register an existing AWS Directory Service directory with WorkSpaces, enabling users to launch persistent virtual desktops tied to their directory accounts.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const exampleVpc = new aws.ec2.Vpc("example", {cidrBlock: "10.0.0.0/16"});
const exampleA = new aws.ec2.Subnet("example_a", {
    vpcId: exampleVpc.id,
    availabilityZone: "us-east-1a",
    cidrBlock: "10.0.0.0/24",
});
const exampleB = new aws.ec2.Subnet("example_b", {
    vpcId: exampleVpc.id,
    availabilityZone: "us-east-1b",
    cidrBlock: "10.0.1.0/24",
});
const exampleDirectory = new aws.directoryservice.Directory("example", {
    name: "corp.example.com",
    password: "#S1ncerely",
    size: "Small",
    vpcSettings: {
        vpcId: exampleVpc.id,
        subnetIds: [
            exampleA.id,
            exampleB.id,
        ],
    },
});
const workspaces = aws.iam.getPolicyDocument({
    statements: [{
        actions: ["sts:AssumeRole"],
        principals: [{
            type: "Service",
            identifiers: ["workspaces.amazonaws.com"],
        }],
    }],
});
const workspacesDefault = new aws.iam.Role("workspaces_default", {
    name: "workspaces_DefaultRole",
    assumeRolePolicy: workspaces.then(workspaces => workspaces.json),
});
const workspacesDefaultServiceAccess = new aws.iam.RolePolicyAttachment("workspaces_default_service_access", {
    role: workspacesDefault.name,
    policyArn: "arn:aws:iam::aws:policy/AmazonWorkSpacesServiceAccess",
});
const workspacesDefaultSelfServiceAccess = new aws.iam.RolePolicyAttachment("workspaces_default_self_service_access", {
    role: workspacesDefault.name,
    policyArn: "arn:aws:iam::aws:policy/AmazonWorkSpacesSelfServiceAccess",
});
const exampleC = new aws.ec2.Subnet("example_c", {
    vpcId: exampleVpc.id,
    availabilityZone: "us-east-1c",
    cidrBlock: "10.0.2.0/24",
});
const exampleD = new aws.ec2.Subnet("example_d", {
    vpcId: exampleVpc.id,
    availabilityZone: "us-east-1d",
    cidrBlock: "10.0.3.0/24",
});
const example = new aws.workspaces.Directory("example", {
    directoryId: exampleDirectory.id,
    subnetIds: [
        exampleC.id,
        exampleD.id,
    ],
    tags: {
        Example: "true",
    },
    certificateBasedAuthProperties: {
        certificateAuthorityArn: "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012",
        status: "ENABLED",
    },
    samlProperties: {
        userAccessUrl: "https://sso.example.com/",
        status: "ENABLED",
    },
    selfServicePermissions: {
        changeComputeType: true,
        increaseVolumeSize: true,
        rebuildWorkspace: true,
        restartWorkspace: true,
        switchRunningMode: true,
    },
    workspaceAccessProperties: {
        deviceTypeAndroid: "ALLOW",
        deviceTypeChromeos: "ALLOW",
        deviceTypeIos: "ALLOW",
        deviceTypeLinux: "DENY",
        deviceTypeOsx: "ALLOW",
        deviceTypeWeb: "DENY",
        deviceTypeWindows: "DENY",
        deviceTypeZeroclient: "DENY",
    },
    workspaceCreationProperties: {
        customSecurityGroupId: exampleAwsSecurityGroup.id,
        defaultOu: "OU=AWS,DC=Workgroup,DC=Example,DC=com",
        enableInternetAccess: true,
        enableMaintenanceMode: true,
        userEnabledAsLocalAdministrator: true,
    },
}, {
    dependsOn: [
        workspacesDefaultServiceAccess,
        workspacesDefaultSelfServiceAccess,
    ],
});
import pulumi
import pulumi_aws as aws

example_vpc = aws.ec2.Vpc("example", cidr_block="10.0.0.0/16")
example_a = aws.ec2.Subnet("example_a",
    vpc_id=example_vpc.id,
    availability_zone="us-east-1a",
    cidr_block="10.0.0.0/24")
example_b = aws.ec2.Subnet("example_b",
    vpc_id=example_vpc.id,
    availability_zone="us-east-1b",
    cidr_block="10.0.1.0/24")
example_directory = aws.directoryservice.Directory("example",
    name="corp.example.com",
    password="#S1ncerely",
    size="Small",
    vpc_settings={
        "vpc_id": example_vpc.id,
        "subnet_ids": [
            example_a.id,
            example_b.id,
        ],
    })
workspaces = aws.iam.get_policy_document(statements=[{
    "actions": ["sts:AssumeRole"],
    "principals": [{
        "type": "Service",
        "identifiers": ["workspaces.amazonaws.com"],
    }],
}])
workspaces_default = aws.iam.Role("workspaces_default",
    name="workspaces_DefaultRole",
    assume_role_policy=workspaces.json)
workspaces_default_service_access = aws.iam.RolePolicyAttachment("workspaces_default_service_access",
    role=workspaces_default.name,
    policy_arn="arn:aws:iam::aws:policy/AmazonWorkSpacesServiceAccess")
workspaces_default_self_service_access = aws.iam.RolePolicyAttachment("workspaces_default_self_service_access",
    role=workspaces_default.name,
    policy_arn="arn:aws:iam::aws:policy/AmazonWorkSpacesSelfServiceAccess")
example_c = aws.ec2.Subnet("example_c",
    vpc_id=example_vpc.id,
    availability_zone="us-east-1c",
    cidr_block="10.0.2.0/24")
example_d = aws.ec2.Subnet("example_d",
    vpc_id=example_vpc.id,
    availability_zone="us-east-1d",
    cidr_block="10.0.3.0/24")
example = aws.workspaces.Directory("example",
    directory_id=example_directory.id,
    subnet_ids=[
        example_c.id,
        example_d.id,
    ],
    tags={
        "Example": "true",
    },
    certificate_based_auth_properties={
        "certificate_authority_arn": "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012",
        "status": "ENABLED",
    },
    saml_properties={
        "user_access_url": "https://sso.example.com/",
        "status": "ENABLED",
    },
    self_service_permissions={
        "change_compute_type": True,
        "increase_volume_size": True,
        "rebuild_workspace": True,
        "restart_workspace": True,
        "switch_running_mode": True,
    },
    workspace_access_properties={
        "device_type_android": "ALLOW",
        "device_type_chromeos": "ALLOW",
        "device_type_ios": "ALLOW",
        "device_type_linux": "DENY",
        "device_type_osx": "ALLOW",
        "device_type_web": "DENY",
        "device_type_windows": "DENY",
        "device_type_zeroclient": "DENY",
    },
    workspace_creation_properties={
        "custom_security_group_id": example_aws_security_group["id"],
        "default_ou": "OU=AWS,DC=Workgroup,DC=Example,DC=com",
        "enable_internet_access": True,
        "enable_maintenance_mode": True,
        "user_enabled_as_local_administrator": True,
    },
    opts = pulumi.ResourceOptions(depends_on=[
            workspaces_default_service_access,
            workspaces_default_self_service_access,
        ]))
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/directoryservice"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/workspaces"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		exampleVpc, err := ec2.NewVpc(ctx, "example", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.0.0.0/16"),
		})
		if err != nil {
			return err
		}
		exampleA, err := ec2.NewSubnet(ctx, "example_a", &ec2.SubnetArgs{
			VpcId:            exampleVpc.ID(),
			AvailabilityZone: pulumi.String("us-east-1a"),
			CidrBlock:        pulumi.String("10.0.0.0/24"),
		})
		if err != nil {
			return err
		}
		exampleB, err := ec2.NewSubnet(ctx, "example_b", &ec2.SubnetArgs{
			VpcId:            exampleVpc.ID(),
			AvailabilityZone: pulumi.String("us-east-1b"),
			CidrBlock:        pulumi.String("10.0.1.0/24"),
		})
		if err != nil {
			return err
		}
		exampleDirectory, err := directoryservice.NewDirectory(ctx, "example", &directoryservice.DirectoryArgs{
			Name:     pulumi.String("corp.example.com"),
			Password: pulumi.String("#S1ncerely"),
			Size:     pulumi.String("Small"),
			VpcSettings: &directoryservice.DirectoryVpcSettingsArgs{
				VpcId: exampleVpc.ID(),
				SubnetIds: pulumi.StringArray{
					exampleA.ID(),
					exampleB.ID(),
				},
			},
		})
		if err != nil {
			return err
		}
		workspaces, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
			Statements: []iam.GetPolicyDocumentStatement{
				{
					Actions: []string{
						"sts:AssumeRole",
					},
					Principals: []iam.GetPolicyDocumentStatementPrincipal{
						{
							Type: "Service",
							Identifiers: []string{
								"workspaces.amazonaws.com",
							},
						},
					},
				},
			},
		}, nil)
		if err != nil {
			return err
		}
		workspacesDefault, err := iam.NewRole(ctx, "workspaces_default", &iam.RoleArgs{
			Name:             pulumi.String("workspaces_DefaultRole"),
			AssumeRolePolicy: pulumi.String(workspaces.Json),
		})
		if err != nil {
			return err
		}
		workspacesDefaultServiceAccess, err := iam.NewRolePolicyAttachment(ctx, "workspaces_default_service_access", &iam.RolePolicyAttachmentArgs{
			Role:      workspacesDefault.Name,
			PolicyArn: pulumi.String("arn:aws:iam::aws:policy/AmazonWorkSpacesServiceAccess"),
		})
		if err != nil {
			return err
		}
		workspacesDefaultSelfServiceAccess, err := iam.NewRolePolicyAttachment(ctx, "workspaces_default_self_service_access", &iam.RolePolicyAttachmentArgs{
			Role:      workspacesDefault.Name,
			PolicyArn: pulumi.String("arn:aws:iam::aws:policy/AmazonWorkSpacesSelfServiceAccess"),
		})
		if err != nil {
			return err
		}
		exampleC, err := ec2.NewSubnet(ctx, "example_c", &ec2.SubnetArgs{
			VpcId:            exampleVpc.ID(),
			AvailabilityZone: pulumi.String("us-east-1c"),
			CidrBlock:        pulumi.String("10.0.2.0/24"),
		})
		if err != nil {
			return err
		}
		exampleD, err := ec2.NewSubnet(ctx, "example_d", &ec2.SubnetArgs{
			VpcId:            exampleVpc.ID(),
			AvailabilityZone: pulumi.String("us-east-1d"),
			CidrBlock:        pulumi.String("10.0.3.0/24"),
		})
		if err != nil {
			return err
		}
		_, err = workspaces.NewDirectory(ctx, "example", &workspaces.DirectoryArgs{
			DirectoryId: exampleDirectory.ID(),
			SubnetIds: pulumi.StringArray{
				exampleC.ID(),
				exampleD.ID(),
			},
			Tags: pulumi.StringMap{
				"Example": pulumi.String("true"),
			},
			CertificateBasedAuthProperties: &workspaces.DirectoryCertificateBasedAuthPropertiesArgs{
				CertificateAuthorityArn: pulumi.String("arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012"),
				Status:                  pulumi.String("ENABLED"),
			},
			SamlProperties: &workspaces.DirectorySamlPropertiesArgs{
				UserAccessUrl: pulumi.String("https://sso.example.com/"),
				Status:        pulumi.String("ENABLED"),
			},
			SelfServicePermissions: &workspaces.DirectorySelfServicePermissionsArgs{
				ChangeComputeType:  pulumi.Bool(true),
				IncreaseVolumeSize: pulumi.Bool(true),
				RebuildWorkspace:   pulumi.Bool(true),
				RestartWorkspace:   pulumi.Bool(true),
				SwitchRunningMode:  pulumi.Bool(true),
			},
			WorkspaceAccessProperties: &workspaces.DirectoryWorkspaceAccessPropertiesArgs{
				DeviceTypeAndroid:    pulumi.String("ALLOW"),
				DeviceTypeChromeos:   pulumi.String("ALLOW"),
				DeviceTypeIos:        pulumi.String("ALLOW"),
				DeviceTypeLinux:      pulumi.String("DENY"),
				DeviceTypeOsx:        pulumi.String("ALLOW"),
				DeviceTypeWeb:        pulumi.String("DENY"),
				DeviceTypeWindows:    pulumi.String("DENY"),
				DeviceTypeZeroclient: pulumi.String("DENY"),
			},
			WorkspaceCreationProperties: &workspaces.DirectoryWorkspaceCreationPropertiesArgs{
				CustomSecurityGroupId:           pulumi.Any(exampleAwsSecurityGroup.Id),
				DefaultOu:                       pulumi.String("OU=AWS,DC=Workgroup,DC=Example,DC=com"),
				EnableInternetAccess:            pulumi.Bool(true),
				EnableMaintenanceMode:           pulumi.Bool(true),
				UserEnabledAsLocalAdministrator: pulumi.Bool(true),
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			workspacesDefaultServiceAccess,
			workspacesDefaultSelfServiceAccess,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var exampleVpc = new Aws.Ec2.Vpc("example", new()
    {
        CidrBlock = "10.0.0.0/16",
    });

    var exampleA = new Aws.Ec2.Subnet("example_a", new()
    {
        VpcId = exampleVpc.Id,
        AvailabilityZone = "us-east-1a",
        CidrBlock = "10.0.0.0/24",
    });

    var exampleB = new Aws.Ec2.Subnet("example_b", new()
    {
        VpcId = exampleVpc.Id,
        AvailabilityZone = "us-east-1b",
        CidrBlock = "10.0.1.0/24",
    });

    var exampleDirectory = new Aws.DirectoryService.Directory("example", new()
    {
        Name = "corp.example.com",
        Password = "#S1ncerely",
        Size = "Small",
        VpcSettings = new Aws.DirectoryService.Inputs.DirectoryVpcSettingsArgs
        {
            VpcId = exampleVpc.Id,
            SubnetIds = new[]
            {
                exampleA.Id,
                exampleB.Id,
            },
        },
    });

    var workspaces = Aws.Iam.GetPolicyDocument.Invoke(new()
    {
        Statements = new[]
        {
            new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
            {
                Actions = new[]
                {
                    "sts:AssumeRole",
                },
                Principals = new[]
                {
                    new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
                    {
                        Type = "Service",
                        Identifiers = new[]
                        {
                            "workspaces.amazonaws.com",
                        },
                    },
                },
            },
        },
    });

    var workspacesDefault = new Aws.Iam.Role("workspaces_default", new()
    {
        Name = "workspaces_DefaultRole",
        AssumeRolePolicy = workspaces.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
    });

    var workspacesDefaultServiceAccess = new Aws.Iam.RolePolicyAttachment("workspaces_default_service_access", new()
    {
        Role = workspacesDefault.Name,
        PolicyArn = "arn:aws:iam::aws:policy/AmazonWorkSpacesServiceAccess",
    });

    var workspacesDefaultSelfServiceAccess = new Aws.Iam.RolePolicyAttachment("workspaces_default_self_service_access", new()
    {
        Role = workspacesDefault.Name,
        PolicyArn = "arn:aws:iam::aws:policy/AmazonWorkSpacesSelfServiceAccess",
    });

    var exampleC = new Aws.Ec2.Subnet("example_c", new()
    {
        VpcId = exampleVpc.Id,
        AvailabilityZone = "us-east-1c",
        CidrBlock = "10.0.2.0/24",
    });

    var exampleD = new Aws.Ec2.Subnet("example_d", new()
    {
        VpcId = exampleVpc.Id,
        AvailabilityZone = "us-east-1d",
        CidrBlock = "10.0.3.0/24",
    });

    var example = new Aws.Workspaces.Directory("example", new()
    {
        DirectoryId = exampleDirectory.Id,
        SubnetIds = new[]
        {
            exampleC.Id,
            exampleD.Id,
        },
        Tags = 
        {
            { "Example", "true" },
        },
        CertificateBasedAuthProperties = new Aws.Workspaces.Inputs.DirectoryCertificateBasedAuthPropertiesArgs
        {
            CertificateAuthorityArn = "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012",
            Status = "ENABLED",
        },
        SamlProperties = new Aws.Workspaces.Inputs.DirectorySamlPropertiesArgs
        {
            UserAccessUrl = "https://sso.example.com/",
            Status = "ENABLED",
        },
        SelfServicePermissions = new Aws.Workspaces.Inputs.DirectorySelfServicePermissionsArgs
        {
            ChangeComputeType = true,
            IncreaseVolumeSize = true,
            RebuildWorkspace = true,
            RestartWorkspace = true,
            SwitchRunningMode = true,
        },
        WorkspaceAccessProperties = new Aws.Workspaces.Inputs.DirectoryWorkspaceAccessPropertiesArgs
        {
            DeviceTypeAndroid = "ALLOW",
            DeviceTypeChromeos = "ALLOW",
            DeviceTypeIos = "ALLOW",
            DeviceTypeLinux = "DENY",
            DeviceTypeOsx = "ALLOW",
            DeviceTypeWeb = "DENY",
            DeviceTypeWindows = "DENY",
            DeviceTypeZeroclient = "DENY",
        },
        WorkspaceCreationProperties = new Aws.Workspaces.Inputs.DirectoryWorkspaceCreationPropertiesArgs
        {
            CustomSecurityGroupId = exampleAwsSecurityGroup.Id,
            DefaultOu = "OU=AWS,DC=Workgroup,DC=Example,DC=com",
            EnableInternetAccess = true,
            EnableMaintenanceMode = true,
            UserEnabledAsLocalAdministrator = true,
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            workspacesDefaultServiceAccess,
            workspacesDefaultSelfServiceAccess,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.Subnet;
import com.pulumi.aws.ec2.SubnetArgs;
import com.pulumi.aws.directoryservice.inputs.DirectoryVpcSettingsArgs;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import com.pulumi.aws.iam.RolePolicyAttachment;
import com.pulumi.aws.iam.RolePolicyAttachmentArgs;
import com.pulumi.aws.workspaces.inputs.DirectoryCertificateBasedAuthPropertiesArgs;
import com.pulumi.aws.workspaces.inputs.DirectorySamlPropertiesArgs;
import com.pulumi.aws.workspaces.inputs.DirectorySelfServicePermissionsArgs;
import com.pulumi.aws.workspaces.inputs.DirectoryWorkspaceAccessPropertiesArgs;
import com.pulumi.aws.workspaces.inputs.DirectoryWorkspaceCreationPropertiesArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var exampleVpc = new Vpc("exampleVpc", VpcArgs.builder()
            .cidrBlock("10.0.0.0/16")
            .build());

        var exampleA = new Subnet("exampleA", SubnetArgs.builder()
            .vpcId(exampleVpc.id())
            .availabilityZone("us-east-1a")
            .cidrBlock("10.0.0.0/24")
            .build());

        var exampleB = new Subnet("exampleB", SubnetArgs.builder()
            .vpcId(exampleVpc.id())
            .availabilityZone("us-east-1b")
            .cidrBlock("10.0.1.0/24")
            .build());

        var exampleDirectory = new com.pulumi.aws.directoryservice.Directory("exampleDirectory", com.pulumi.aws.directoryservice.DirectoryArgs.builder()
            .name("corp.example.com")
            .password("#S1ncerely")
            .size("Small")
            .vpcSettings(DirectoryVpcSettingsArgs.builder()
                .vpcId(exampleVpc.id())
                .subnetIds(                
                    exampleA.id(),
                    exampleB.id())
                .build())
            .build());

        final var workspaces = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
            .statements(GetPolicyDocumentStatementArgs.builder()
                .actions("sts:AssumeRole")
                .principals(GetPolicyDocumentStatementPrincipalArgs.builder()
                    .type("Service")
                    .identifiers("workspaces.amazonaws.com")
                    .build())
                .build())
            .build());

        var workspacesDefault = new Role("workspacesDefault", RoleArgs.builder()
            .name("workspaces_DefaultRole")
            .assumeRolePolicy(workspaces.json())
            .build());

        var workspacesDefaultServiceAccess = new RolePolicyAttachment("workspacesDefaultServiceAccess", RolePolicyAttachmentArgs.builder()
            .role(workspacesDefault.name())
            .policyArn("arn:aws:iam::aws:policy/AmazonWorkSpacesServiceAccess")
            .build());

        var workspacesDefaultSelfServiceAccess = new RolePolicyAttachment("workspacesDefaultSelfServiceAccess", RolePolicyAttachmentArgs.builder()
            .role(workspacesDefault.name())
            .policyArn("arn:aws:iam::aws:policy/AmazonWorkSpacesSelfServiceAccess")
            .build());

        var exampleC = new Subnet("exampleC", SubnetArgs.builder()
            .vpcId(exampleVpc.id())
            .availabilityZone("us-east-1c")
            .cidrBlock("10.0.2.0/24")
            .build());

        var exampleD = new Subnet("exampleD", SubnetArgs.builder()
            .vpcId(exampleVpc.id())
            .availabilityZone("us-east-1d")
            .cidrBlock("10.0.3.0/24")
            .build());

        var example = new com.pulumi.aws.workspaces.Directory("example", com.pulumi.aws.workspaces.DirectoryArgs.builder()
            .directoryId(exampleDirectory.id())
            .subnetIds(            
                exampleC.id(),
                exampleD.id())
            .tags(Map.of("Example", "true"))
            .certificateBasedAuthProperties(DirectoryCertificateBasedAuthPropertiesArgs.builder()
                .certificateAuthorityArn("arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012")
                .status("ENABLED")
                .build())
            .samlProperties(DirectorySamlPropertiesArgs.builder()
                .userAccessUrl("https://sso.example.com/")
                .status("ENABLED")
                .build())
            .selfServicePermissions(DirectorySelfServicePermissionsArgs.builder()
                .changeComputeType(true)
                .increaseVolumeSize(true)
                .rebuildWorkspace(true)
                .restartWorkspace(true)
                .switchRunningMode(true)
                .build())
            .workspaceAccessProperties(DirectoryWorkspaceAccessPropertiesArgs.builder()
                .deviceTypeAndroid("ALLOW")
                .deviceTypeChromeos("ALLOW")
                .deviceTypeIos("ALLOW")
                .deviceTypeLinux("DENY")
                .deviceTypeOsx("ALLOW")
                .deviceTypeWeb("DENY")
                .deviceTypeWindows("DENY")
                .deviceTypeZeroclient("DENY")
                .build())
            .workspaceCreationProperties(DirectoryWorkspaceCreationPropertiesArgs.builder()
                .customSecurityGroupId(exampleAwsSecurityGroup.id())
                .defaultOu("OU=AWS,DC=Workgroup,DC=Example,DC=com")
                .enableInternetAccess(true)
                .enableMaintenanceMode(true)
                .userEnabledAsLocalAdministrator(true)
                .build())
            .build(), CustomResourceOptions.builder()
                .dependsOn(                
                    workspacesDefaultServiceAccess,
                    workspacesDefaultSelfServiceAccess)
                .build());

    }
}
resources:
  example:
    type: aws:workspaces:Directory
    properties:
      directoryId: ${exampleDirectory.id}
      subnetIds:
        - ${exampleC.id}
        - ${exampleD.id}
      tags:
        Example: true
      certificateBasedAuthProperties:
        certificateAuthorityArn: arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/12345678-1234-1234-1234-123456789012
        status: ENABLED
      samlProperties:
        userAccessUrl: https://sso.example.com/
        status: ENABLED
      selfServicePermissions:
        changeComputeType: true
        increaseVolumeSize: true
        rebuildWorkspace: true
        restartWorkspace: true
        switchRunningMode: true
      workspaceAccessProperties:
        deviceTypeAndroid: ALLOW
        deviceTypeChromeos: ALLOW
        deviceTypeIos: ALLOW
        deviceTypeLinux: DENY
        deviceTypeOsx: ALLOW
        deviceTypeWeb: DENY
        deviceTypeWindows: DENY
        deviceTypeZeroclient: DENY
      workspaceCreationProperties:
        customSecurityGroupId: ${exampleAwsSecurityGroup.id}
        defaultOu: OU=AWS,DC=Workgroup,DC=Example,DC=com
        enableInternetAccess: true
        enableMaintenanceMode: true
        userEnabledAsLocalAdministrator: true
    options:
      dependsOn:
        - ${workspacesDefaultServiceAccess}
        - ${workspacesDefaultSelfServiceAccess}
  exampleDirectory:
    type: aws:directoryservice:Directory
    name: example
    properties:
      name: corp.example.com
      password: '#S1ncerely'
      size: Small
      vpcSettings:
        vpcId: ${exampleVpc.id}
        subnetIds:
          - ${exampleA.id}
          - ${exampleB.id}
  workspacesDefault:
    type: aws:iam:Role
    name: workspaces_default
    properties:
      name: workspaces_DefaultRole
      assumeRolePolicy: ${workspaces.json}
  workspacesDefaultServiceAccess:
    type: aws:iam:RolePolicyAttachment
    name: workspaces_default_service_access
    properties:
      role: ${workspacesDefault.name}
      policyArn: arn:aws:iam::aws:policy/AmazonWorkSpacesServiceAccess
  workspacesDefaultSelfServiceAccess:
    type: aws:iam:RolePolicyAttachment
    name: workspaces_default_self_service_access
    properties:
      role: ${workspacesDefault.name}
      policyArn: arn:aws:iam::aws:policy/AmazonWorkSpacesSelfServiceAccess
  exampleVpc:
    type: aws:ec2:Vpc
    name: example
    properties:
      cidrBlock: 10.0.0.0/16
  exampleA:
    type: aws:ec2:Subnet
    name: example_a
    properties:
      vpcId: ${exampleVpc.id}
      availabilityZone: us-east-1a
      cidrBlock: 10.0.0.0/24
  exampleB:
    type: aws:ec2:Subnet
    name: example_b
    properties:
      vpcId: ${exampleVpc.id}
      availabilityZone: us-east-1b
      cidrBlock: 10.0.1.0/24
  exampleC:
    type: aws:ec2:Subnet
    name: example_c
    properties:
      vpcId: ${exampleVpc.id}
      availabilityZone: us-east-1c
      cidrBlock: 10.0.2.0/24
  exampleD:
    type: aws:ec2:Subnet
    name: example_d
    properties:
      vpcId: ${exampleVpc.id}
      availabilityZone: us-east-1d
      cidrBlock: 10.0.3.0/24
variables:
  workspaces:
    fn::invoke:
      function: aws:iam:getPolicyDocument
      arguments:
        statements:
          - actions:
              - sts:AssumeRole
            principals:
              - type: Service
                identifiers:
                  - workspaces.amazonaws.com

When you register a directory, WorkSpaces creates the infrastructure to provision personal desktops for directory users. The directoryId references your Directory Service directory; subnetIds specify where WorkSpaces places network interfaces (must be in different availability zones). The workspaceCreationProperties block controls defaults like internet access and organizational unit placement. The selfServicePermissions block determines which actions users can perform on their own WorkSpaces (restart, rebuild, resize). The workspaceAccessProperties block restricts which device types can connect. The certificateBasedAuthProperties and samlProperties blocks enable certificate-based and SAML authentication respectively.

Configure pooled WorkSpaces with Active Directory

Organizations needing non-persistent desktops for shift workers or temporary access use pooled WorkSpaces, where users connect to available desktops from a shared pool.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.workspaces.Directory("example", {
    subnetIds: [
        exampleC.id,
        exampleD.id,
    ],
    workspaceType: "POOLS",
    workspaceDirectoryName: "Pool directory",
    workspaceDirectoryDescription: "WorkSpaces Pools directory",
    userIdentityType: "CUSTOMER_MANAGED",
    activeDirectoryConfig: {
        domainName: "example.internal",
        serviceAccountSecretArn: exampleAwsSecretsmanagerSecret.arn,
    },
    workspaceAccessProperties: {
        deviceTypeAndroid: "ALLOW",
        deviceTypeChromeos: "ALLOW",
        deviceTypeIos: "ALLOW",
        deviceTypeLinux: "DENY",
        deviceTypeOsx: "ALLOW",
        deviceTypeWeb: "DENY",
        deviceTypeWindows: "DENY",
        deviceTypeZeroclient: "DENY",
    },
    workspaceCreationProperties: {
        customSecurityGroupId: exampleAwsSecurityGroup.id,
        defaultOu: "OU=AWS,DC=Workgroup,DC=Example,DC=com",
        enableInternetAccess: true,
    },
    samlProperties: {
        relayStateParameterName: "RelayState",
        userAccessUrl: "https://sso.example.com/",
        status: "ENABLED",
    },
});
import pulumi
import pulumi_aws as aws

example = aws.workspaces.Directory("example",
    subnet_ids=[
        example_c["id"],
        example_d["id"],
    ],
    workspace_type="POOLS",
    workspace_directory_name="Pool directory",
    workspace_directory_description="WorkSpaces Pools directory",
    user_identity_type="CUSTOMER_MANAGED",
    active_directory_config={
        "domain_name": "example.internal",
        "service_account_secret_arn": example_aws_secretsmanager_secret["arn"],
    },
    workspace_access_properties={
        "device_type_android": "ALLOW",
        "device_type_chromeos": "ALLOW",
        "device_type_ios": "ALLOW",
        "device_type_linux": "DENY",
        "device_type_osx": "ALLOW",
        "device_type_web": "DENY",
        "device_type_windows": "DENY",
        "device_type_zeroclient": "DENY",
    },
    workspace_creation_properties={
        "custom_security_group_id": example_aws_security_group["id"],
        "default_ou": "OU=AWS,DC=Workgroup,DC=Example,DC=com",
        "enable_internet_access": True,
    },
    saml_properties={
        "relay_state_parameter_name": "RelayState",
        "user_access_url": "https://sso.example.com/",
        "status": "ENABLED",
    })
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/workspaces"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := workspaces.NewDirectory(ctx, "example", &workspaces.DirectoryArgs{
			SubnetIds: pulumi.StringArray{
				exampleC.Id,
				exampleD.Id,
			},
			WorkspaceType:                 pulumi.String("POOLS"),
			WorkspaceDirectoryName:        pulumi.String("Pool directory"),
			WorkspaceDirectoryDescription: pulumi.String("WorkSpaces Pools directory"),
			UserIdentityType:              pulumi.String("CUSTOMER_MANAGED"),
			ActiveDirectoryConfig: &workspaces.DirectoryActiveDirectoryConfigArgs{
				DomainName:              pulumi.String("example.internal"),
				ServiceAccountSecretArn: pulumi.Any(exampleAwsSecretsmanagerSecret.Arn),
			},
			WorkspaceAccessProperties: &workspaces.DirectoryWorkspaceAccessPropertiesArgs{
				DeviceTypeAndroid:    pulumi.String("ALLOW"),
				DeviceTypeChromeos:   pulumi.String("ALLOW"),
				DeviceTypeIos:        pulumi.String("ALLOW"),
				DeviceTypeLinux:      pulumi.String("DENY"),
				DeviceTypeOsx:        pulumi.String("ALLOW"),
				DeviceTypeWeb:        pulumi.String("DENY"),
				DeviceTypeWindows:    pulumi.String("DENY"),
				DeviceTypeZeroclient: pulumi.String("DENY"),
			},
			WorkspaceCreationProperties: &workspaces.DirectoryWorkspaceCreationPropertiesArgs{
				CustomSecurityGroupId: pulumi.Any(exampleAwsSecurityGroup.Id),
				DefaultOu:             pulumi.String("OU=AWS,DC=Workgroup,DC=Example,DC=com"),
				EnableInternetAccess:  pulumi.Bool(true),
			},
			SamlProperties: &workspaces.DirectorySamlPropertiesArgs{
				RelayStateParameterName: pulumi.String("RelayState"),
				UserAccessUrl:           pulumi.String("https://sso.example.com/"),
				Status:                  pulumi.String("ENABLED"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Workspaces.Directory("example", new()
    {
        SubnetIds = new[]
        {
            exampleC.Id,
            exampleD.Id,
        },
        WorkspaceType = "POOLS",
        WorkspaceDirectoryName = "Pool directory",
        WorkspaceDirectoryDescription = "WorkSpaces Pools directory",
        UserIdentityType = "CUSTOMER_MANAGED",
        ActiveDirectoryConfig = new Aws.Workspaces.Inputs.DirectoryActiveDirectoryConfigArgs
        {
            DomainName = "example.internal",
            ServiceAccountSecretArn = exampleAwsSecretsmanagerSecret.Arn,
        },
        WorkspaceAccessProperties = new Aws.Workspaces.Inputs.DirectoryWorkspaceAccessPropertiesArgs
        {
            DeviceTypeAndroid = "ALLOW",
            DeviceTypeChromeos = "ALLOW",
            DeviceTypeIos = "ALLOW",
            DeviceTypeLinux = "DENY",
            DeviceTypeOsx = "ALLOW",
            DeviceTypeWeb = "DENY",
            DeviceTypeWindows = "DENY",
            DeviceTypeZeroclient = "DENY",
        },
        WorkspaceCreationProperties = new Aws.Workspaces.Inputs.DirectoryWorkspaceCreationPropertiesArgs
        {
            CustomSecurityGroupId = exampleAwsSecurityGroup.Id,
            DefaultOu = "OU=AWS,DC=Workgroup,DC=Example,DC=com",
            EnableInternetAccess = true,
        },
        SamlProperties = new Aws.Workspaces.Inputs.DirectorySamlPropertiesArgs
        {
            RelayStateParameterName = "RelayState",
            UserAccessUrl = "https://sso.example.com/",
            Status = "ENABLED",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.workspaces.Directory;
import com.pulumi.aws.workspaces.DirectoryArgs;
import com.pulumi.aws.workspaces.inputs.DirectoryActiveDirectoryConfigArgs;
import com.pulumi.aws.workspaces.inputs.DirectoryWorkspaceAccessPropertiesArgs;
import com.pulumi.aws.workspaces.inputs.DirectoryWorkspaceCreationPropertiesArgs;
import com.pulumi.aws.workspaces.inputs.DirectorySamlPropertiesArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Directory("example", DirectoryArgs.builder()
            .subnetIds(            
                exampleC.id(),
                exampleD.id())
            .workspaceType("POOLS")
            .workspaceDirectoryName("Pool directory")
            .workspaceDirectoryDescription("WorkSpaces Pools directory")
            .userIdentityType("CUSTOMER_MANAGED")
            .activeDirectoryConfig(DirectoryActiveDirectoryConfigArgs.builder()
                .domainName("example.internal")
                .serviceAccountSecretArn(exampleAwsSecretsmanagerSecret.arn())
                .build())
            .workspaceAccessProperties(DirectoryWorkspaceAccessPropertiesArgs.builder()
                .deviceTypeAndroid("ALLOW")
                .deviceTypeChromeos("ALLOW")
                .deviceTypeIos("ALLOW")
                .deviceTypeLinux("DENY")
                .deviceTypeOsx("ALLOW")
                .deviceTypeWeb("DENY")
                .deviceTypeWindows("DENY")
                .deviceTypeZeroclient("DENY")
                .build())
            .workspaceCreationProperties(DirectoryWorkspaceCreationPropertiesArgs.builder()
                .customSecurityGroupId(exampleAwsSecurityGroup.id())
                .defaultOu("OU=AWS,DC=Workgroup,DC=Example,DC=com")
                .enableInternetAccess(true)
                .build())
            .samlProperties(DirectorySamlPropertiesArgs.builder()
                .relayStateParameterName("RelayState")
                .userAccessUrl("https://sso.example.com/")
                .status("ENABLED")
                .build())
            .build());

    }
}
resources:
  example:
    type: aws:workspaces:Directory
    properties:
      subnetIds:
        - ${exampleC.id}
        - ${exampleD.id}
      workspaceType: POOLS
      workspaceDirectoryName: Pool directory
      workspaceDirectoryDescription: WorkSpaces Pools directory
      userIdentityType: CUSTOMER_MANAGED
      activeDirectoryConfig:
        domainName: example.internal
        serviceAccountSecretArn: ${exampleAwsSecretsmanagerSecret.arn}
      workspaceAccessProperties:
        deviceTypeAndroid: ALLOW
        deviceTypeChromeos: ALLOW
        deviceTypeIos: ALLOW
        deviceTypeLinux: DENY
        deviceTypeOsx: ALLOW
        deviceTypeWeb: DENY
        deviceTypeWindows: DENY
        deviceTypeZeroclient: DENY
      workspaceCreationProperties:
        customSecurityGroupId: ${exampleAwsSecurityGroup.id}
        defaultOu: OU=AWS,DC=Workgroup,DC=Example,DC=com
        enableInternetAccess: true
      samlProperties:
        relayStateParameterName: RelayState
        userAccessUrl: https://sso.example.com/
        status: ENABLED

Setting workspaceType to POOLS changes the directory behavior: instead of provisioning individual desktops per user, WorkSpaces maintains a pool of desktops that users claim temporarily. The userIdentityType determines how users authenticate (CUSTOMER_MANAGED for your own Active Directory). The activeDirectoryConfig block provides domain credentials via Secrets Manager, allowing WorkSpaces to join desktops to your domain. Unlike personal WorkSpaces, pooled directories don’t reference a Directory Service directory; WorkSpaces generates the directoryId automatically.

Restrict access with IP control groups

Security policies often require limiting WorkSpaces access to specific IP ranges, such as corporate networks or VPN endpoints.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const exampleIpGroup = new aws.workspaces.IpGroup("example", {name: "example"});
const example = new aws.workspaces.Directory("example", {
    directoryId: exampleAwsDirectoryServiceDirectory.id,
    ipGroupIds: [exampleIpGroup.id],
});
import pulumi
import pulumi_aws as aws

example_ip_group = aws.workspaces.IpGroup("example", name="example")
example = aws.workspaces.Directory("example",
    directory_id=example_aws_directory_service_directory["id"],
    ip_group_ids=[example_ip_group.id])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/workspaces"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		exampleIpGroup, err := workspaces.NewIpGroup(ctx, "example", &workspaces.IpGroupArgs{
			Name: pulumi.String("example"),
		})
		if err != nil {
			return err
		}
		_, err = workspaces.NewDirectory(ctx, "example", &workspaces.DirectoryArgs{
			DirectoryId: pulumi.Any(exampleAwsDirectoryServiceDirectory.Id),
			IpGroupIds: pulumi.StringArray{
				exampleIpGroup.ID(),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var exampleIpGroup = new Aws.Workspaces.IpGroup("example", new()
    {
        Name = "example",
    });

    var example = new Aws.Workspaces.Directory("example", new()
    {
        DirectoryId = exampleAwsDirectoryServiceDirectory.Id,
        IpGroupIds = new[]
        {
            exampleIpGroup.Id,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.workspaces.IpGroup;
import com.pulumi.aws.workspaces.IpGroupArgs;
import com.pulumi.aws.workspaces.Directory;
import com.pulumi.aws.workspaces.DirectoryArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var exampleIpGroup = new IpGroup("exampleIpGroup", IpGroupArgs.builder()
            .name("example")
            .build());

        var example = new Directory("example", DirectoryArgs.builder()
            .directoryId(exampleAwsDirectoryServiceDirectory.id())
            .ipGroupIds(exampleIpGroup.id())
            .build());

    }
}
resources:
  example:
    type: aws:workspaces:Directory
    properties:
      directoryId: ${exampleAwsDirectoryServiceDirectory.id}
      ipGroupIds:
        - ${exampleIpGroup.id}
  exampleIpGroup:
    type: aws:workspaces:IpGroup
    name: example
    properties:
      name: example

The ipGroupIds property associates IP control groups with the directory, restricting which source IPs can connect to WorkSpaces. You create IP groups separately using the aws.workspaces.IpGroup resource, defining allowed CIDR ranges. This provides network-based access control independent of user authentication.

Beyond these examples

These snippets focus on specific directory-level features: personal and pooled WorkSpaces directory types, Active Directory and SAML authentication, and device access controls and IP restrictions. They’re intentionally minimal rather than full WorkSpaces deployments.

The examples rely on pre-existing infrastructure such as AWS Directory Service directories or Active Directory domains, VPC subnets in multiple availability zones, the IAM role (workspaces_DefaultRole) with required policies, and Secrets Manager secrets for service account credentials in pooled mode. They focus on directory registration rather than provisioning the underlying infrastructure.

To keep things focused, common directory patterns are omitted, including:

  • IAM role creation and policy attachment (workspaces_DefaultRole)
  • Directory Service directory provisioning
  • VPC and subnet configuration
  • IP group rule definitions
  • Custom security group configuration
  • Organizational unit (OU) structure in Active Directory

These omissions are intentional: the goal is to illustrate how each directory feature is wired, not provide drop-in WorkSpaces modules. See the WorkSpaces Directory resource reference for all available configuration options.

Let's configure AWS WorkSpaces Directories

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

IAM & Prerequisites
Why do I need the workspaces_DefaultRole IAM role?
AWS WorkSpaces service requires the workspaces_DefaultRole IAM role to operate normally. Without it, the service cannot make calls to other AWS services like EC2 on your behalf.
How do I set up the required IAM role for WorkSpaces?
Create an IAM role named workspaces_DefaultRole with a trust policy allowing workspaces.amazonaws.com to assume it. Attach two managed policies: AmazonWorkSpacesServiceAccess and AmazonWorkSpacesSelfServiceAccess. Use dependsOn to ensure the role and policy attachments are created before the WorkSpaces directory.
Workspace Types & Configuration
What's the difference between PERSONAL and POOLS workspace types?
PERSONAL workspaces (the default) require you to specify directoryId and support selfServicePermissions. POOLS workspaces auto-generate directoryId, require activeDirectoryConfig for Active Directory integration, and use workspaceDirectoryName and workspaceDirectoryDescription instead of a directory service directory.
Can I specify directoryId when creating POOLS workspaces?
No. When workspaceType is set to POOLS, the directoryId is automatically generated and cannot be manually set. Omit this property and let AWS generate it.
What are the valid values for userIdentityType?
Valid values are CUSTOMER_MANAGED, AWS_DIRECTORY_SERVICE, and AWS_IAM_IDENTITY_CENTER. This property is immutable after creation.
Do selfServicePermissions apply to POOLS workspaces?
No. The selfServicePermissions property only applies when workspaceType is set to PERSONAL.
Immutability & Updates
What properties can't be changed after creating a WorkSpaces directory?
The following properties are immutable and will force resource replacement if changed: directoryId, subnetIds, tenancy, userIdentityType, activeDirectoryConfig, workspaceDirectoryDescription, workspaceDirectoryName, and workspaceType.
Authentication & Access Control
How do I enable certificate-based authentication?
Configure certificateBasedAuthProperties with a certificate authority ARN and status. Note that certificate-based authentication requires SAML authentication to be enabled first via samlProperties.
How do I control which devices can access WorkSpaces?
Use workspaceAccessProperties to specify access for each device type: Android, ChromeOS, iOS, Linux, macOS, web, Windows, and zero client. Set each to ALLOW or DENY.
How do I associate IP access control groups with my directory?
Set the ipGroupIds property to an array of IP group identifiers. You can create IP groups using the aws.workspaces.IpGroup resource.

Using a different cloud?

Explore compute guides for other cloud providers: