A common use case for EUDataVault Storage is a shared bucket where each user (or team) can only see and work with their own folder. This guide shows the two policy patterns that handle this, both verified end-to-end.
The patterns rely on string conditions and the s3:prefix context key. For the operator reference, see String Conditions and s3:prefix.
Both patterns expose the user's full subtree under the allowed folder, including nested objects, and deny everything outside it.
Pattern A - Hardcoded prefix
Attach this inline policy to a user who should only see team-data/projectA/:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListProjectA",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {
"StringLike": { "s3:prefix": "team-data/projectA/*" }
}
},
{
"Sid": "AllowReadWriteProjectA",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/team-data/projectA/*"
}
]
}The first statement scopes listing to the team-data/projectA/ folder. The second statement permits read and write on the objects inside it. Replace my-bucket with your bucket name and team-data/projectA/ with your folder.
Attach the policy via AWS CLI
aws iam put-user-policy \
--user-name alice@example.com \
--policy-name AllowProjectAOnly \
--policy-document file://policy.json \
--endpoint-url https://iam.eudatavault.eu \
--region eu-central-2Wait up to 90 seconds for the policy to propagate before testing.
Test the policy
List inside the allowed folder:
aws s3api list-objects-v2 \
--bucket my-bucket \
--prefix "team-data/projectA/" \
--endpoint-url https://eu-central-2.storage.eudatavault.eu \
--region eu-central-2The response includes every object under team-data/projectA/, including nested keys such as team-data/projectA/sub/file.txt and team-data/projectA/deep/nested/file.txt. The result is paginated when there are more than 1000 keys.
Download an object:
aws s3 cp s3://my-bucket/team-data/projectA/notes.txt ./notes.txt \
--endpoint-url https://eu-central-2.storage.eudatavault.eu \
--region eu-central-2Upload an object:
aws s3 cp ./report.pdf s3://my-bucket/team-data/projectA/report.pdf \
--endpoint-url https://eu-central-2.storage.eudatavault.eu \
--region eu-central-2Confirm that listing a different folder is denied:
aws s3api list-objects-v2 \
--bucket my-bucket \
--prefix "team-data/projectB/" \
--endpoint-url https://eu-central-2.storage.eudatavault.eu \
--region eu-central-2The response is 403 AccessDenied.
Pattern B - Per-user folder via ${aws:username}
Attach this single inline policy to every user who should be confined to user-data/<their-username>/:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListOwnFolder",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {
"StringLike": { "s3:prefix": "user-data/${aws:username}/*" }
}
},
{
"Sid": "AllowReadWriteOwnFolder",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/user-data/${aws:username}/*"
}
]
}${aws:username} resolves at request time to the caller's IAM username. On EUDataVault, the username is the email address used to create the user, so for a user alice@example.com the pattern expands to user-data/alice@example.com/*.
Both statements use the same variable, so each user automatically reads and writes only their own folder.
Test from each user
Sign in as alice@example.com and run:
aws s3api list-objects-v2 \
--bucket my-bucket \
--prefix "user-data/alice@example.com/" \
--endpoint-url https://eu-central-2.storage.eudatavault.eu \
--region eu-central-2The response lists alice's full subtree.
Now try to list bob's folder from alice's credentials:
aws s3api list-objects-v2 \
--bucket my-bucket \
--prefix "user-data/bob@example.com/" \
--endpoint-url https://eu-central-2.storage.eudatavault.eu \
--region eu-central-2The response is 403 AccessDenied. Bob's folder is invisible to alice.
Combining with an IP restriction
To require both a specific source network and the prefix, add an IpAddress condition inside the same Condition block. Both conditions then must hold (AND).
"Condition": {
"IpAddress": { "aws:SourceIp": "203.0.113.0/24" },
"StringLike": { "s3:prefix": "user-data/${aws:username}/*" }
}A request from outside 203.0.113.0/24 is denied even if the prefix matches. See Policy Conditions for the full IP condition reference.
Block a sub-folder with explicit Deny
To allow a parent folder but block a sensitive sub-folder, add a second statement with Effect: Deny. Explicit Deny always wins over Allow.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListTeamData",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {
"StringLike": { "s3:prefix": "team-data/*" }
}
},
{
"Sid": "DenyListSecret",
"Effect": "Deny",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {
"StringLike": { "s3:prefix": "team-data/secret/*" }
}
}
]
}Listing team-data/public/ succeeds. Listing team-data/secret/ returns 403 AccessDenied.
Gotchas
The matcher compares the request's --prefix parameter against the policy pattern as literal strings. The following situations catch first-time users.
The trailing slash matters
StringLike { s3:prefix: "team-data/projectA/*" } matches --prefix "team-data/projectA/" but does not match --prefix "team-data/projectA" (no trailing slash). The pattern requires the literal team-data/projectA/ prefix in the request value.
Always include the trailing slash on the request when the policy pattern includes it.
The user must always send --prefix
A request without --prefix is denied because s3:prefix is treated as absent and StringLike without IfExists denies on absent keys. For example:
aws s3api list-objects-v2 \
--bucket my-bucket \
--endpoint-url https://eu-central-2.storage.eudatavault.eu \
--region eu-central-2The call above returns 403 AccessDenied.
If you need to allow listing without a prefix, use StringLikeIfExists:
"Condition": {
"StringLikeIfExists": { "s3:prefix": "team-data/projectA/*" }
}The condition then passes when --prefix is missing, and the request is allowed.
Empty prefix is treated as absent
--prefix "" is the same as omitting the parameter. Non-IfExists conditions deny; IfExists conditions allow.
Nested keys are visible
Listing team-data/projectA/ returns every object below it, regardless of depth. To restrict a user to a single sub-folder, the policy pattern must target that sub-folder, for example team-data/projectA/public/*.
Usernames are emails
${aws:username} resolves to the full email used at user creation, including @ and any + tag. The pattern user-data/${aws:username}/* therefore expands to user-data/alice@example.com/*. The matcher does not URL-decode the request value, so the request must use the same literal characters.