Exercise nfs-crust against real AWS EFS
Total cost is pennies (t4g.nano + EFS for ~15 minutes). Every resource is
throwaway and torn down at the end — never skip teardown.
Prerequisites
- AWS credentials for a sandbox-grade account: set
AWS_PROFILE and
AWS_REGION from the environment or ask the user; never assume a
production account. Verify with aws sts get-caller-identity first.
session-manager-plugin on PATH. Without sudo, it can be installed by
extracting session-manager-plugin from the AWS sessionmanager-bundle.zip
for the local platform into ~/.local/bin.
Discover networking
Pick a VPC and a public subnet (MapPublicIpOnLaunch true, so SSM can reach
the instance) rather than hardcoding ids:
aws ec2 describe-vpcs (prefer the default VPC; otherwise ask the user
which VPC is safe to use)
aws ec2 describe-subnets --filters Name=vpc-id,Values=<vpc> and choose a
public subnet
- Resolve the AMI fresh:
aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64 --query Parameter.Value --output text
Setup
- Create a security group in the VPC allowing TCP 2049 from itself; attach
it to both the mount target and the instance. Tag everything created here
Name=nfs-crust-throwaway.
aws efs create-file-system --creation-token nfs-crust-throwaway-$(date +%s) --tags Key=Name,Value=nfs-crust-throwaway; wait for available; create a
mount target in the public subnet with the security group.
- Create IAM role + instance profile
nfs-crust-throwaway-ssm with
AmazonSSMManagedInstanceCore attached (trust ec2.amazonaws.com). Sleep
~10s after creation for instance-profile propagation.
- Launch a
t4g.nano with the AMI, subnet, security group, public IP, and
the instance profile. Wait for SSM PingStatus == Online (~1-2 min).
- Open the EFS root: a fresh EFS root is root-owned mode 755 and the
client authenticates as the local uid via AUTH_SYS, so writes would fail.
Via
aws ssm send-command (AWS-RunShellScript) on the instance:
mount nfs4 -o nfsvers=4.1 <mount-target-ip>:/, chmod 777 it, unmount.
- Tunnel, in the background:
aws ssm start-session --target <instance> --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"host":["<mount-target-ip>"],"portNumber":["2049"],"localPortNumber":["12049"]}'
then confirm with nc -z 127.0.0.1 12049.
Run
NFS_CRUST_EXTERNAL_ENDPOINT=127.0.0.1:12049 \
NFS_CRUST_EXTERNAL_OPERATION_TIMEOUT_SECONDS=120 \
NFS_CRUST_EXTERNAL_CONNECT_TIMEOUT_SECONDS=60 \
cargo test --test external_nfs -- --ignored --test-threads=1
Expect all tests to pass in roughly two minutes through the tunnel. The
fused-put test observes RPC compound tags through a local proxy, so it only
asserts on plaintext endpoints (it self-skips under TLS).
Teardown (always, in this order)
- Stop the tunnel session.
- Terminate the instance.
- Delete the mount target; wait until it is gone, then delete the file
system (deletion fails while mount targets exist).
- After the instance is terminated, delete the security group (it is
referenced by both instance and mount target until then).
- Remove role from instance profile, delete instance profile, detach the
policy, delete the role.
- Confirm:
aws efs describe-file-systems shows no nfs-crust-throwaway.
EFS behavior notes (verified July 2026)
- EFS accepts the NFSv4.1 special current stateid for
WRITE in a compound
but returns NFS4ERR_BAD_STATEID for CLOSE with it — that is why the
fused put closes via a detached follow-up compound.
- EFS accepts
VERIFY, SAVEFH/RESTOREFH, COMMIT before CLOSE, and
~12-op compounds.
- EFS accepts the fused temporary-file shape (
OPEN + WRITE via current
stateid + COMMIT + VERIFY + atomic publish) and the
COMMIT+VERIFY+CLOSE verified-close compound used for chunked writes.
- Per-operation latency through the tunnel is a few ms; the suite finishing
in ~2 minutes is normal.
1---2name: efs-test3description: Run the ignored external NFS interop suite against a throwaway AWS EFS file system, reached from the local machine through an SSM port-forward tunnel. Creates all AWS resources, runs the tests, and tears everything down. Use when a change touches compound shapes, the RPC transport, or anything else where embednfs coverage is not enough.4---56# Exercise nfs-crust against real AWS EFS78Total cost is pennies (t4g.nano + EFS for ~15 minutes). Every resource is9throwaway and torn down at the end — never skip teardown.1011## Prerequisites1213- AWS credentials for a sandbox-grade account: set `AWS_PROFILE` and14 `AWS_REGION` from the environment or ask the user; never assume a15 production account. Verify with `aws sts get-caller-identity` first.16- `session-manager-plugin` on PATH. Without sudo, it can be installed by17 extracting `session-manager-plugin` from the AWS `sessionmanager-bundle.zip`18 for the local platform into `~/.local/bin`.1920## Discover networking2122Pick a VPC and a public subnet (`MapPublicIpOnLaunch` true, so SSM can reach23the instance) rather than hardcoding ids:2425- `aws ec2 describe-vpcs` (prefer the default VPC; otherwise ask the user26 which VPC is safe to use)27- `aws ec2 describe-subnets --filters Name=vpc-id,Values=<vpc>` and choose a28 public subnet29- Resolve the AMI fresh:30 `aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64 --query Parameter.Value --output text`3132## Setup33341. Create a security group in the VPC allowing TCP 2049 from itself; attach35 it to both the mount target and the instance. Tag everything created here36 `Name=nfs-crust-throwaway`.372. `aws efs create-file-system --creation-token nfs-crust-throwaway-$(date +%s)38 --tags Key=Name,Value=nfs-crust-throwaway`; wait for `available`; create a39 mount target in the public subnet with the security group.403. Create IAM role + instance profile `nfs-crust-throwaway-ssm` with41 `AmazonSSMManagedInstanceCore` attached (trust `ec2.amazonaws.com`). Sleep42 ~10s after creation for instance-profile propagation.434. Launch a `t4g.nano` with the AMI, subnet, security group, public IP, and44 the instance profile. Wait for SSM `PingStatus == Online` (~1-2 min).455. **Open the EFS root**: a fresh EFS root is root-owned mode 755 and the46 client authenticates as the local uid via AUTH_SYS, so writes would fail.47 Via `aws ssm send-command` (AWS-RunShellScript) on the instance:48 mount `nfs4 -o nfsvers=4.1 <mount-target-ip>:/`, `chmod 777` it, unmount.496. Tunnel, in the background:50 `aws ssm start-session --target <instance> --document-name51 AWS-StartPortForwardingSessionToRemoteHost --parameters52 '{"host":["<mount-target-ip>"],"portNumber":["2049"],"localPortNumber":["12049"]}'`53 then confirm with `nc -z 127.0.0.1 12049`.5455## Run5657```58NFS_CRUST_EXTERNAL_ENDPOINT=127.0.0.1:12049 \59NFS_CRUST_EXTERNAL_OPERATION_TIMEOUT_SECONDS=120 \60NFS_CRUST_EXTERNAL_CONNECT_TIMEOUT_SECONDS=60 \61cargo test --test external_nfs -- --ignored --test-threads=162```6364Expect all tests to pass in roughly two minutes through the tunnel. The65fused-put test observes RPC compound tags through a local proxy, so it only66asserts on plaintext endpoints (it self-skips under TLS).6768## Teardown (always, in this order)69701. Stop the tunnel session.712. Terminate the instance.723. Delete the mount target; wait until it is gone, then delete the file73 system (deletion fails while mount targets exist).744. After the instance is terminated, delete the security group (it is75 referenced by both instance and mount target until then).765. Remove role from instance profile, delete instance profile, detach the77 policy, delete the role.786. Confirm: `aws efs describe-file-systems` shows no `nfs-crust-throwaway`.7980## EFS behavior notes (verified July 2026)8182- EFS accepts the NFSv4.1 special current stateid for `WRITE` in a compound83 but returns `NFS4ERR_BAD_STATEID` for `CLOSE` with it — that is why the84 fused put closes via a detached follow-up compound.85- EFS accepts `VERIFY`, `SAVEFH`/`RESTOREFH`, `COMMIT` before `CLOSE`, and86 ~12-op compounds.87- EFS accepts the fused temporary-file shape (`OPEN` + `WRITE` via current88 stateid + `COMMIT` + `VERIFY` + atomic publish) and the89 `COMMIT`+`VERIFY`+`CLOSE` verified-close compound used for chunked writes.90- Per-operation latency through the tunnel is a few ms; the suite finishing91 in ~2 minutes is normal.