> For the complete documentation index, see [llms.txt](https://docs.impossiblecloud.com/impossible-cloud-help/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.impossiblecloud.com/impossible-cloud-help/security/identity-access-management-iam/managing-policies/restrict-iam-user-to-a-folder.md).

# Restrict an IAM user to a folder

A common use case for Impossible Cloud 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](/impossible-cloud-help/security/identity-access-management-iam/managing-policies/string-conditions-and-prefix.md).

## Choose a pattern

| Pattern                                   | Use when                                                                                                              |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Hardcoded prefix per user or role**     | Each user or team has a fixed folder name that does not match their username. Easiest to read; one policy per folder. |
| **Per-user folder via `${aws:username}`** | Every user gets a folder named after their username (their email). One policy serves all users.                       |

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/`:

```json
{
    "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

```bash
aws iam put-user-policy \
    --user-name alice@example.com \
    --policy-name AllowProjectAOnly \
    --policy-document file://policy.json \
    --endpoint-url https://iam.impossibleapi.net \
    --region eu-central-2
```

Wait up to 90 seconds for the policy to propagate before testing.

### Test the policy

List inside the allowed folder:

```bash
aws s3api list-objects-v2 \
    --bucket my-bucket \
    --prefix "team-data/projectA/" \
    --endpoint-url https://eu-central-2.storage.impossibleapi.net \
    --region eu-central-2
```

The 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:

```bash
aws s3 cp s3://my-bucket/team-data/projectA/notes.txt ./notes.txt \
    --endpoint-url https://eu-central-2.storage.impossibleapi.net \
    --region eu-central-2
```

Upload an object:

```bash
aws s3 cp ./report.pdf s3://my-bucket/team-data/projectA/report.pdf \
    --endpoint-url https://eu-central-2.storage.impossibleapi.net \
    --region eu-central-2
```

Confirm that listing a different folder is denied:

```bash
aws s3api list-objects-v2 \
    --bucket my-bucket \
    --prefix "team-data/projectB/" \
    --endpoint-url https://eu-central-2.storage.impossibleapi.net \
    --region eu-central-2
```

The 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>/`:

```json
{
    "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 Impossible Cloud, 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:

```bash
aws s3api list-objects-v2 \
    --bucket my-bucket \
    --prefix "user-data/alice@example.com/" \
    --endpoint-url https://eu-central-2.storage.impossibleapi.net \
    --region eu-central-2
```

The response lists alice's full subtree.

Now try to list bob's folder from alice's credentials:

```bash
aws s3api list-objects-v2 \
    --bucket my-bucket \
    --prefix "user-data/bob@example.com/" \
    --endpoint-url https://eu-central-2.storage.impossibleapi.net \
    --region eu-central-2
```

The 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).

```json
"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](/impossible-cloud-help/security/identity-access-management-iam/managing-policies/policy-conditions.md) 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`.

```json
{
    "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:

```bash
aws s3api list-objects-v2 \
    --bucket my-bucket \
    --endpoint-url https://eu-central-2.storage.impossibleapi.net \
    --region eu-central-2
```

The call above returns `403 AccessDenied`.

If you need to allow listing without a prefix, use `StringLikeIfExists`:

```json
"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.

## Common mistakes

| Symptom                                                                                   | Cause                                                                                                         | Fix                                                                                                                                                                                                                                                                              |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| All requests return 403, even the allowed prefix.                                         | Operator name typo (for example `StringLikes` or `StringLIKE`). The policy parses but never matches.          | Use the exact operator names listed in [String Conditions and s3:prefix](/impossible-cloud-help/security/identity-access-management-iam/managing-policies/string-conditions-and-prefix.md#supported-string-operators).                                                           |
| The first list works, the next one is denied.                                             | The first request included `--prefix`, the next did not. `s3:prefix` is then absent and the condition denies. | Always pass `--prefix` or switch the condition to `StringLikeIfExists`.                                                                                                                                                                                                          |
| `StringEquals { s3:prefix: "Team-Data/" }` denies a request with `--prefix "team-data/"`. | `StringEquals` is case-sensitive.                                                                             | Use `StringEqualsIgnoreCase` if the case should not matter.                                                                                                                                                                                                                      |
| Adding an `IpAddress` condition broke an `s3:ListBucket` policy that was working.         | Conditions inside one `Condition` block AND together. The request must now satisfy both.                      | Remove the IP condition, split into two statements, or update the allowed CIDR.                                                                                                                                                                                                  |
| A policy using `NumericEquals` or `DateGreaterThan` denies every request.                 | These operators are not yet evaluated and behave as always-false.                                             | Rewrite using a supported string or IP operator. See the operators-not-yet-evaluated section of [String Conditions and s3:prefix](/impossible-cloud-help/security/identity-access-management-iam/managing-policies/string-conditions-and-prefix.md#operators-not-yet-evaluated). |

## See also

* [String Conditions and s3:prefix](/impossible-cloud-help/security/identity-access-management-iam/managing-policies/string-conditions-and-prefix.md) - reference for operators, modifiers, and variables.
* [Policy Conditions](/impossible-cloud-help/security/identity-access-management-iam/managing-policies/policy-conditions.md) - IP address conditions.
* [Managing Policies](/impossible-cloud-help/security/identity-access-management-iam/managing-policies.md) - creating, updating, and deleting policies in the Storage Console.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.impossiblecloud.com/impossible-cloud-help/security/identity-access-management-iam/managing-policies/restrict-iam-user-to-a-folder.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
