Citrix SecurSpaces™

Prepare the AWS resources

Citrix SecurSpaces™ does not create or validate any of the AWS resources on this page. Provision them before anyone uses the feature.

Prerequisites common to Amazon EFS and Amazon S3 Files

Create a mount target in every Availability Zone

Create a mount target in every Availability Zone in which the node group can place workspace nodes. Take the Availability Zone list from the node group’s autoscaling group, not from where nodes happen to run today. EFS allows one mount target per Availability Zone, shared by all Mount Points on that file system.

A workspace scheduled into an Availability Zone with no reachable mount target hangs in ContainerCreating, and Kubernetes cannot compensate. The mount target is not represented in Kubernetes, so this is the only symptom.

Mount target creation is slow. Wait until every mount target reports available before you create Mount Points.

Scope the NFS rule to a security group

The source of the mount target’s inbound NFS rule decides who can mount the file system. If you allow the whole VPC CIDR, every workload in the VPC can mount it, not only your workspace nodes.

Set the source of the inbound rule to the EKS worker node security group rather than an IP range:

aws ec2 authorize-security-group-ingress \
  --group-id <mount-target-sg> --protocol tcp --port 2049 \
  --source-group <eks-node-security-group-id>
<!--NeedCopy-->

Both halves of the rule are required:

  • Mount target security group, inbound — TCP 2049, source is the node security group.
  • Node security group, outbound — TCP 2049, destination is the mount target security group.

Most EKS node groups keep the default allow-all egress, so the outbound half is usually already satisfied. If your account restricts node egress, add it explicitly. A missing outbound rule produces the same symptom as a missing mount target: workspaces hang while starting, with no indication in Kubernetes.

Outbound rules on the mount target security group are not needed, because security groups are stateful.

Note

The source is always the node security group, including on clusters that use EKS security groups for pods. The mount is performed by the EFS CSI node DaemonSet, which runs with hostNetwork, and security groups for pods do not apply to hostNetwork pods. A SecurityGroupPolicy never matches the DaemonSet, so scoping the inbound rule to a pod security group leaves every workspace hanging while starting. If you must use a CIDR, scope it to the worker node subnet CIDRs, never the whole VPC. A security group limits reachability, not encryption. Keep tls on the mount regardless.

Attach the CSI driver policy

One CSI driver, efs.csi.aws.com, serves both Amazon EFS and Amazon S3 Files, so this step applies whichever backend you use. Attach AmazonEFSCSIDriverPolicy to the CSI driver role. It must include DescribeMountTargets, which the mount helper uses to resolve the Availability Zone local mount target.

Amazon EFS prerequisites

  • Create the file system first. The driver cannot create it. Create New carves a fresh isolated access point on the file system you provisioned. It never creates a file system.
  • Create it as a Regional file system, not a One Zone file system. A Regional file system allows one mount target in each Availability Zone, which is what the per-Availability-Zone requirement above needs. A One Zone file system allows a single mount target, so it cannot back a Mount Point. Workspaces scheduled into the Availability Zones it does not serve hang while starting, and there is no mount target you can add to fix them.
  • Enable encryption at rest when you create the file system. Mount Points work either way, so nothing warns you later, and encryption cannot be enabled after creation.

Enable automatic backups

EFS has no versioning, no snapshots, and no recycle bin. Without a recovery point that already existed when data was lost, a deletion is permanent.

A Regional file system, which is the only kind that can back a Mount Point, is created with automatic backups disabled when you use the CLI, the API, or Terraform. Only the EFS console enables them for you. Pass --backup at creation, or enable it afterwards:

aws efs put-backup-policy --file-system-id <fs-id> --backup-policy Status="ENABLED"
aws efs describe-backup-policy --file-system-id <fs-id>
<!--NeedCopy-->

This attaches the default AWS Backup plan for EFS: daily backups with 35-day retention. Confirm that AWS Backup is offered in your Region, and budget for it separately from EFS.

Choose Elastic throughput

The CLI and API default to Bursting throughput, whose baseline scales with stored bytes. A Mount Point file system starts empty, so it converges on the minimum and caps at 35,000 read and 7,000 write IOPS. Elastic throughput raises this to approximately 250,000 and 50,000, which is what the many small-file operations in a workspace actually need.

aws efs update-file-system --file-system-id <fs-id> --throughput-mode elastic
<!--NeedCopy-->

Throughput mode is a file system attribute. It cannot be set on the StorageClass, and SecurSpaces cannot see or warn about it.

Note

Elastic throughput meters every gigabyte read and written, with writes charged at roughly twice the rate of reads. Size the change from your CloudWatch read and write metrics first, and do not switch during business hours. Elastic throughput does not reduce per-operation latency, so it is not a fix for slow small-file work.

Amazon S3 Files prerequisites

Amazon S3 Files requires the common prerequisites described earlier — the per-Availability-Zone mount targets, the scoped NFS rule, and the CSI driver policy — plus the following.

Note

Do not confuse Amazon S3 Files with Mountpoint for Amazon S3 (s3.csi.aws.com), which is not POSIX compliant and is not a supported Mount Point backend.

Prepare the bucket

The S3 bucket must have versioning enabled and server-side encryption configured before you create the file system. Bucket versioning is also the only way to recover a deleted file on S3 Files.

The --accept-bucket-warning flag is not an “existing data” switch. S3 Files refuses to create a file system scoped to a prefix that holds a large number of objects, warning that recursive renames or moves on such a file system are slow and costly, because every file needs its own copy and delete against the bucket. Pass the flag to acknowledge the warning and let the creation proceed. It does nothing about versioning or encryption, which must still be set beforehand.

Create the service role

When you create the file system you pass an IAM role that S3 Files assumes to read and write your bucket and to manage the EventBridge rules that drive synchronization. The S3 console creates this role for you. From the CLI or Terraform you must author both the trust policy and the permissions policy.

A role with only a trust policy is accepted at creation time, but leaves synchronization unable to work.

The trust policy lets S3 Files assume the role, through the elasticfilesystem.amazonaws.com service principal. Keep the account and ARN conditions. They stop another AWS account using your role against your bucket:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "elasticfilesystem.amazonaws.com" },
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": { "aws:SourceAccount": "<account-id>" },
      "ArnLike": { "aws:SourceArn": "arn:aws:s3files:<region>:<account-id>:file-system/*" }
    }
  }]
}
<!--NeedCopy-->

The permissions policy must grant the following. Scope each statement to the resources it names, and keep the conditions: without them the role holds far more than this one file system needs.

  • s3:ListBucket and s3:ListBucketVersions on the bucket, conditioned on aws:ResourceAccount matching your account ID.
  • s3:AbortMultipartUpload, s3:DeleteObject*, s3:GetObject*, s3:List*, and s3:PutObject* on the bucket contents, with the same aws:ResourceAccount condition. The wildcard actions are deliberate: S3 Files uses the version-aware S3 operations that bucket versioning enables.
  • kms:GenerateDataKey, kms:Encrypt, kms:Decrypt, kms:ReEncryptFrom, and kms:ReEncryptTo on the ARN of the key that encrypts the bucket, conditioned on kms:ViaService matching s3.<region>.amazonaws.com and on kms:EncryptionContext:aws:s3:arn matching the bucket and its contents. Left without a resource and these conditions, the statement lets the role use every key in the account whose key policy delegates to IAM.
  • EventBridge rule management — events:PutRule, events:PutTargets, events:EnableRule, events:DisableRule, events:RemoveTargets, and events:DeleteRule — scoped to rule/DO-NOT-DELETE-S3-Files* and conditioned on events:ManagedBy matching elasticfilesystem.amazonaws.com, plus EventBridge read actions.

For the full policy documents, see the AWS documentation for working with Amazon S3 Files.

Warning

If the bucket uses SSE-KMS, the role cannot read or write a single object without the kms: actions. Reads fail and writes never reach the bucket, both silently. Keep the KMS statement even on an SSE-S3 bucket, so the role still works if the bucket is switched to SSE-KMS later. If the key is a customer managed key, its key policy must also allow this role. An IAM policy alone is not enough.

The EventBridge rules named DO-NOT-DELETE-S3-Files... are how changes made directly in the bucket reach mounted workspaces. Do not delete or disable them. Without them, cached files keep serving stale content.

Create the file system

aws s3files create-file-system --bucket <bucket-arn> [--prefix p/] --role-arn <service-role-arn>
<!--NeedCopy-->

The file system encrypts its own high-performance tier at rest by default. You can supply a customer managed key for that storage instead. This is a different key from the one that encrypts the bucket. If you supply one, it must be in the same Region as the file system, and its key policy must allow S3 Files kms:Decrypt, kms:GenerateDataKeyWithoutPlaintext, kms:CreateGrant, and kms:DescribeKey. The default key policy already does.

Warning

Disabling or deleting that key makes the file system, and every Mount Point on it, inaccessible. Disabling takes effect after a short delay and is undone by re-enabling the key; deleting it is permanent. Treat the key as part of the file system rather than as a key you can retire during routine key hygiene.

Check versions and platform support

  • In the cluster — the Amazon EFS CSI driver must be version 3.0.0 or later.
  • On any EC2 host you mount from directly — the amazon-efs-utils client must be version 3.0.0 or later.
  • AWS CLI — new enough to include the s3files commands. Verify with aws s3files help.
  • In your Region — confirm that Amazon S3 Files is offered in the Region your cluster runs in. It became generally available in April 2026 in 34 Regions, and coverage grows, so check the AWS regional services table for the current list.

Attach AmazonS3FilesCSIDriverPolicy and AmazonS3FilesClientFullAccess to the controller identity, and AmazonS3FilesClientFullAccess, AmazonElasticFileSystemsUtils, and S3 read on the bucket to the node identity. See Scope IAM permissions before you attach the account-wide managed policies.

Amazon S3 Files does not support cross-account static attach: the file system and bucket must be in the same AWS account as the cluster. It is not supported on EKS Fargate, EKS Hybrid Nodes, or Windows containers.

Scope IAM permissions

The EFS CSI driver authenticates through IAM roles for service accounts (IRSA).

Warning

Most AWS walkthroughs attach the node policies to the EC2 instance role of the managed node group. An instance role grant is not private to the CSI driver: any workload on that node that can reach the instance metadata service can use it. Because workspaces run customer-supplied code, treat every policy on the node role as one you have granted to your developers.

Give the node driver its own role bound to its service account rather than using the instance role:

  1. Enable the cluster OIDC provider.
  2. Create a role whose trust policy pins both the service account and the STS audience.
  3. Annotate efs-csi-node-sa with eks.amazonaws.com/role-arn. Do the same for efs-csi-controller-sa.

Use the exact system:serviceaccount:<namespace>:<name> value. A wildcard subject, or a missing audience condition, lets any pod in the cluster assume the role.

Scope client access to your file systems

All AWS managed S3 Files client policies use "Resource": "*" and cannot be limited to a file system. AmazonS3FilesClientFullAccess goes on both the controller identity and the node identity, so anything holding either one is authorized to mount every S3 Files file system in the account and to write to them as root.

Replace that managed policy on both identities with a customer managed policy that names your file systems and grants s3files:ClientMount and s3files:ClientWrite on them. Add s3files:ClientRootAccess only where a mount genuinely needs it.

Similarly, give the node identity S3 read scoped to the buckets that back Mount Points rather than an account-wide read-only policy such as AmazonS3ReadOnlyAccess. An inline policy granting s3:GetObject, s3:GetObjectVersion, and s3:ListBucket on those buckets is enough.

Note

A file system policy that omits a role does not revoke an account-wide identity grant. An allow in either the identity policy or the file system policy is sufficient. Either scope the identity policy, or add an explicit deny.

Harden instance metadata

Require IMDSv2 and set the metadata hop limit to 1 on the node group. Apply the steps in this order. Applied out of order, this silently breaks Mount Point provisioning.

  1. Confirm the driver uses its own role. Role credentials are injected when a pod is created, so annotating the service accounts is not enough. Until the pods restart, the driver falls back to the node role and appears to work. Restart the driver, confirm every pod reports a role ARN, and create one Mount Point successfully.
  2. Change the launch template, so replacement nodes are created hardened. Set HttpTokens: required, HttpPutResponseHopLimit: 1, and HttpEndpoint: enabled. Editing the template is not enough on its own: a managed node group stays pinned to the launch-template version it was created with, so update the node group to the new version as well. Otherwise every scale-out and every replaced node comes back on the pinned version, and the hardening quietly decays on a cluster whose workspace nodes come and go.
  3. Roll the change to running nodes one at a time, watching the kube-system namespace for about 15 minutes after the first before continuing:

    aws ec2 modify-instance-metadata-options --instance-id <id> \
      --http-tokens required --http-put-response-hop-limit 1 --http-endpoint enabled
    <!--NeedCopy-->
    

    To roll a node back, set --http-put-response-hop-limit 2 on that instance.

  4. Check what the node group will hand out. Read the metadata options back from the launch template, and confirm that the node group points at the version you checked:

    aws ec2 describe-launch-template-versions --launch-template-id <id> --versions '$Latest' \
      --query 'LaunchTemplateVersions[0].LaunchTemplateData.MetadataOptions'
    <!--NeedCopy-->
    

    "HttpPutResponseHopLimit": 2 means IMDSv2 is enforced but pod access to the metadata service is not blocked.

  5. Verify from an ordinary pod, not from a workspace terminal. Every workspace pod already drops traffic to 169.254.169.254 inside its own network namespace, so a probe run in a workspace terminal returns nothing whether or not the node was ever hardened. Run it from a pod that does not carry that block, and expect a timeout with no token returned:

    kubectl run imds-probe --rm -it --restart=Never --image=curlimages/curl -- \
      sh -c 'curl -s -m2 -X PUT http://169.254.169.254/latest/api/token \
        -H "X-aws-ec2-metadata-token-ttl-seconds: 60"'
    <!--NeedCopy-->
    

    Then run the same request on the host as a positive control. It must still succeed. If it does not, you have broken host agents rather than protected pods.

Metadata options are fixed when an instance launches. Changing the launch template does nothing to running nodes, and changing a running node does not survive its replacement.

Note

With eksctl, use disablePodIMDS: true on the node group. The related disableIMDSv1 key already defaults to true, so setting it alone changes nothing, and eksctl writes a hop limit of 2 without disablePodIMDS. That leaves every pod able to borrow the node role. disablePodIMDS cannot be combined with iam.withAddonPolicies, and is refused when a managed node group supplies its own launch template.

A hop limit of 1 stops ordinary pods from reaching the metadata service. Processes on the host and pods using hostNetwork are unaffected, including the CSI node DaemonSet. The CSI controller runs on the pod network and is affected: if it loses metadata access, new Mount Points fail while existing mounts keep working.

Next