Tutorial · Cloud Storage

One S3 bucket, many webQsee teams

Keep one S3 bucket for your whole organisation and run multiple webQsee teams inside it using prefix isolation. Each team gets its own folder root, its own IAM policy and its own group of IAM users.

Estimated time: 30–45 min · Last reviewed: 2026-05 · Difficulty: Intermediate–Advanced

Before you start

The pattern

Use a single bucket (e.g. webqsee-org) and partition it by a top-level prefix per team:

webqsee-org/
  team-a/
    shared/
    protected/
    private/<userId>/
  team-b/
    shared/
    protected/
    private/<userId>/
  team-c/
    ...

webQsee already supports a prefix field in the Cloud Gallery's Connection settings JSON. When set, all paths the extension reads/writes are transparently prepended with that prefix.

Each team gets its own IAM policy that's identical in shape to the webqsee-user / webqsee-admin policies, but scoped to that team's prefix. Members of team A literally cannot list, read or write objects under team-b/ at the AWS level.

This page walks through setting up team A; repeat for team B, C, and so on.

1. Create the shared bucket (once)

  1. Create the bucket

    S3 → Create bucket. Name: webqsee-org (or whatever fits your naming convention). Region: e.g. eu-central-1. Block all public access: ON. Versioning: Disabled. Encryption: SSE-S3.

    Sanity check: Green toast "Successfully created bucket". The bucket appears in S3's bucket list.
  2. Apply CORS once for the whole bucket

    Permissions tab → CORS → Edit, paste:

    [
      {
        "AllowedOrigins": [
          "https://view.webqsee.com",
          "https://app.webqsee.com",
          "chrome-extension://*"
        ],
        "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
        "AllowedHeaders": ["*"],
        "ExposeHeaders": ["ETag", "x-amz-request-id", "x-amz-version-id"],
        "MaxAgeSeconds": 3000
      }
    ]

    Save changes.

    Sanity check: CORS is saved (green toast). All teams will share this one CORS config, you don't need to redo it per team.

2. Create the per-team policies (repeat per team)

The example below is for "team A", whose prefix is team-a/. For team B, find-and-replace team-a with team-b everywhere and rename to webqsee-team-b-user / webqsee-team-b-admin.

  1. Create policy webqsee-team-a-admin

    IAM → Policies → Create policy → JSON tab. Paste:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "TeamAAdminListBucket",
          "Effect": "Allow",
          "Action": [
            "s3:ListBucket",
            "s3:GetBucketLocation",
            "s3:GetBucketCors"
          ],
          "Resource": "arn:aws:s3:::webqsee-org",
          "Condition": {
            "StringLike": {
              "s3:prefix": [
                "team-a/shared/*", "team-a/shared/",
                "team-a/protected/*", "team-a/protected/",
                "team-a/private/*", "team-a/private/",
                "team-a/", "team-a"
              ]
            }
          }
        },
        {
          "Sid": "TeamAAdminFullObjects",
          "Effect": "Allow",
          "Action": [
            "s3:GetObject", "s3:GetObjectTagging",
            "s3:PutObject", "s3:PutObjectTagging",
            "s3:DeleteObject",
            "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts"
          ],
          "Resource": [
            "arn:aws:s3:::webqsee-org/team-a/shared/*",
            "arn:aws:s3:::webqsee-org/team-a/protected/*",
            "arn:aws:s3:::webqsee-org/team-a/private/*"
          ]
        }
      ]
    }

    Save as webqsee-team-a-admin.

    Sanity check: Policy webqsee-team-a-admin appears in Customer managed policies.
  2. Create policy webqsee-team-a-user

    Create another policy. Paste:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "TeamAUserListBucket",
          "Effect": "Allow",
          "Action": [
            "s3:ListBucket",
            "s3:GetBucketLocation",
            "s3:GetBucketCors"
          ],
          "Resource": "arn:aws:s3:::webqsee-org",
          "Condition": {
            "StringLike": {
              "s3:prefix": [
                "team-a/shared/*", "team-a/shared/",
                "team-a/protected/*", "team-a/protected/",
                "team-a/private/${aws:PrincipalTag/webqsee-user-id}/*",
                "team-a/private/${aws:PrincipalTag/webqsee-user-id}/",
                "team-a/", "team-a"
              ]
            }
          }
        },
        {
          "Sid": "TeamAUserSharedRW",
          "Effect": "Allow",
          "Action": [
            "s3:GetObject", "s3:GetObjectTagging",
            "s3:PutObject", "s3:PutObjectTagging",
            "s3:DeleteObject",
            "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts"
          ],
          "Resource": "arn:aws:s3:::webqsee-org/team-a/shared/*"
        },
        {
          "Sid": "TeamAUserPrivateRW",
          "Effect": "Allow",
          "Action": [
            "s3:GetObject", "s3:GetObjectTagging",
            "s3:PutObject", "s3:PutObjectTagging",
            "s3:DeleteObject",
            "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts"
          ],
          "Resource": "arn:aws:s3:::webqsee-org/team-a/private/${aws:PrincipalTag/webqsee-user-id}/*"
        },
        {
          "Sid": "TeamAUserProtectedReadOnly",
          "Effect": "Allow",
          "Action": ["s3:GetObject", "s3:GetObjectTagging"],
          "Resource": "arn:aws:s3:::webqsee-org/team-a/protected/*"
        }
      ]
    }

    Save as webqsee-team-a-user.

    Sanity check: Two team-A policies are now in the Customer managed list. The user policy uses the tag condition for per-member /private/ isolation.

3. Create the per-team groups

  1. Create group webqsee-team-a-admin

    IAM → User groups → Create group. Attach webqsee-team-a-admin policy. Save.

    Sanity check: Group appears with 1 attached policy.
  2. Create group webqsee-team-a-user

    Same drill, attach the user policy.

    Sanity check: Both team-A groups exist with one policy each.

4. Create IAM users for team A

  1. Look up each member's webQsee user ID

    In webQsee → switch to team A using the team-switcher button in the toolbar → Settings → Current User and Team. The roster shows User IDs for that team's members.

    Sanity check: You have the (name, webQsee user ID) list for everyone in team A.
  2. Create one IAM user per team-A member

    IAM → Users → Create user. Username pattern: <first-name>-team-a. Programmatic only. Group: webqsee-team-a-user (or -admin). Tag: key webqsee-user-id, value = their webQsee user ID for team A. Generate access key, save securely.

    Sanity check: Each team-A member has an IAM user in the right group with the matching tag. Access keys are in your password manager.

5. Configure Cloud Gallery for team A

  1. Switch webQsee to team A

    In the webQsee toolbar use the team-switcher to make team A the active team. The Cloud Gallery settings you'll edit only apply to that team.

    Sanity check: The top of the main window shows "Team: Team A" (or whatever you named it).
  2. Set team-A team-wide cloud settings

    Gallery → Cloud Gallery → Edit Settings. Fill in:

    • Storage Type: S3 compatible storage, KEY-Auth
    • Authentication mode: Per-user credentials (multi-user)
    • Bucket name: webqsee-org
    • Connection settings:
      {
        "region": "eu-central-1",
        "prefix": "team-a/"
      }
      The trailing slash matters, webQsee concatenates it with internal paths.

    Save team settings.

    Sanity check: Toast "Team cloud settings saved". The Cloud Gallery panel reads "Cloud storage requires personal credentials", expected because per-user mode is on.
  3. Enter your personal credentials

    Paste your team-A IAM access key + secret. Click Save personal credentials.

    Sanity check: "Cloud Storage connected ✓" in green.
  4. Ask team-A members to do the same

    Each enters their own access key + secret while their active team in webQsee is team A.

    Sanity check: Every team-A member reports "Cloud Storage connected ✓".

6. Repeat for team B (and so on)

  1. Repeat steps 2–5 with team-b

    Same shape, different prefix. Create webqsee-team-b-admin and webqsee-team-b-user policies (ARNs use webqsee-org/team-b/…), matching groups, IAM users tagged with their team-B webQsee user IDs, and set the Connection settings JSON to { "region": "eu-central-1", "prefix": "team-b/" }.

    Sanity check: Switching webQsee from team A to team B (toolbar team-switcher) toggles which Cloud Gallery content you see; team-A members see only team-A content, team-B members see only team-B content.
Final sanity check, cross-team isolation

Have an A-only member upload a screenshot to Shared while their active team is A. In the S3 console you should see it appear under team-a/shared/…. Switch a B-only member's webQsee to team B and open Cloud Gallery → Shared, they should not see the team-A object. Even better: ask them to try aws s3 ls s3://webqsee-org/team-a/ from the CLI with their team-B credentials, they should get AccessDenied.

If they can see team-A content from team B, the IAM policy ARNs include the wrong prefix; re-check step 2.

Troubleshooting

  • Member sees an empty Shared tab even though uploads worked: the prefix in the Connection settings JSON is missing or has no trailing slash, so the list call goes to shared/ not team-a/shared/. Re-save with "prefix": "team-a/".
  • 403 on ListBucket: the policy's s3:prefix condition must include the team prefix exactly, e.g. "team-a/shared/*". Watch for trailing-slash mismatches.
  • A user can read team-B content: their IAM user is in the wrong group, or there's an extra inline policy attached directly to the user. Open IAM → User → Permissions and verify only the team-A policy applies.
  • "prefix not allowed" warning in webQsee: the prefix string must end with / and may not start with /. Valid: team-a/. Invalid: /team-a, team-a.
  • CORS error: CORS is bucket-wide so once applied (step 1) it covers all teams. If you see a CORS error, re-check the bucket Permissions tab.

When to choose the alternative (separate buckets)

Prefix-based isolation is great for organisations where:

  • You want one billing line in AWS for all webQsee storage
  • Cross-team analytics or backup tooling can target one bucket
  • You trust the IAM policy boundary to enforce separation

Choose one bucket per team when:

  • Compliance or audit requires hard bucket-level separation
  • Per-team data retention or lifecycle rules differ
  • You want to be able to hand a bucket over to a team without slicing it out of a shared one

Related tutorials

One bucket per team

The stricter alternative with bucket-level isolation between teams.

Open tutorial

Create multiple webQsee teams

Set up several webQsee teams under one organisation and administrate them centrally.

Open tutorial

Multi-user S3 with RBAC

The single-team multi-user reference setup this guide is built on top of.

Open tutorial

Simplified S3 integration

The single-user starter setup, useful as a reference.

Open tutorial

Stop guessing. Start webQseeing.

Add webQsee to Chrome or Edge in one click. Most features are free, forever, no signup needed. Upgrade only if your team needs cloud sharing, S3 storage and Pro-grade tooling.

Works in Chrome 103+, Edge 103+ and most Chromium-based browsers. Install instructions.