Tutorial · Cloud Storage

One S3 bucket per webQsee team

The cleaner alternative for stricter isolation: each webQsee team owns its own S3 bucket, its own IAM policies and its own users. More buckets to manage, harder, simpler boundaries.

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

Before you start

Why per-team buckets?

  • Bucket-level boundary, the strongest isolation S3 offers; impossible to accidentally read across teams via a typo in a path
  • Per-team lifecycle / retention, one team can keep recordings 90 days, another forever
  • Per-team billing using AWS cost allocation tags on the bucket
  • Easy hand-off, transfer a single bucket to a team's own AWS account if they spin out

Trade-off: more S3 buckets and IAM resources to manage. If you only have two or three teams that's no problem. For ten or more teams consider prefix-based isolation in one bucket.

Naming convention

Pick a stable prefix you can extend per team:

  • Buckets: webqsee-team-a, webqsee-team-b, …
  • Policies: webqsee-team-a-admin, webqsee-team-a-user, …
  • Groups: webqsee-team-a-admin, webqsee-team-a-user, …
  • Users: <person>-team-a (if a person is in multiple teams they get one IAM user per team)

The walkthrough below is for "team A"; repeat the whole pattern for each additional team.

1. Create the team-A bucket

  1. Open S3 → Create bucket

    Pick a region (eu-central-1, etc.).

    Sanity check: You're on the Create bucket form with your chosen region in the top-right.
  2. Configure the bucket

    Name: webqsee-team-a. Block all public access: ON. Versioning: Disabled. Encryption: SSE-S3. ACLs: disabled. Optionally add an AWS cost-allocation tag webqsee-team=team-a for per-team billing reports.

    Sanity check: Review summary confirms: public access blocked, versioning disabled, SSE-S3 encryption on, optional tag visible.
  3. Click Create bucket
    Sanity check: Green toast "Successfully created bucket 'webqsee-team-a'". The bucket appears in the list with the right region.
  4. Apply CORS to this bucket

    Bucket → Permissions → CORS → Edit:

    [
      {
        "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: Toast "Successfully edited CORS". CORS must be applied to every team bucket individually, unlike the shared-bucket pattern.

2. Create the team-A policies

  1. Create policy webqsee-team-a-admin

    IAM → Policies → Create policy → JSON. Paste:

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

    Save as webqsee-team-a-admin.

    Sanity check: The policy is in the Customer managed list. Note the ARNs target webqsee-team-a, not a generic shared bucket.
  2. Create policy webqsee-team-a-user

    The per-user policy with the aws:PrincipalTag/webqsee-user-id condition. Paste:

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

    Save as webqsee-team-a-user.

    Sanity check: Both team-A policies are in the Customer managed list. Each ARN starts with arn:aws:s3:::webqsee-team-a, no other team's bucket name appears.

3. Create team-A groups and users

  1. Create two groups

    IAM → User groups → Create group, twice:

    • webqsee-team-a-admin, attach the admin policy
    • webqsee-team-a-user, attach the user policy
    Sanity check: Both groups appear in User groups with 1 attached policy each.
  2. Collect team-A webQsee user IDs

    In webQsee, use the team-switcher to make team A the active team. Open Settings → Current User and Team. Note each member's User ID.

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

    IAM → Users → Create user. For each:

    • Username: <first-name>-team-a
    • Programmatic only (no console access)
    • Group: webqsee-team-a-user (or -admin)
    • Tag: key webqsee-user-id, value = their webQsee user ID in team A (exact case)
    • Generate one access key, save the Access Key ID + Secret immediately

    Important: If a person is also in team B, they need a separate IAM user <first-name>-team-b. Don't reuse one IAM user across teams, the IAM tag can only have one value.

    Sanity check: IAM → Users shows one IAM user per team-A member, all Active, all in the right group, all carrying the matching tag. Access keys are in your password manager.

4. Configure webQsee team A

  1. Switch to team A in webQsee

    Use the team-switcher in the toolbar. The Cloud Gallery settings below apply only to the currently active team.

    Sanity check: Top of the main window shows "Team: Team A".
  2. Team-wide settings

    Gallery → Cloud Gallery → Edit Settings:

    • Storage Type: S3 compatible storage, KEY-Auth
    • Authentication mode: Per-user credentials (multi-user)
    • Bucket name: webqsee-team-a
    • Connection settings:
      {
        "region": "eu-central-1"
      }
      No prefix field is needed because the bucket itself already isolates the team.

    Save team settings.

    Sanity check: Toast "Team cloud settings saved" appears.
  3. Each team-A member enters their personal credentials

    Personal section: paste own Access Key ID and Secret. Save.

    Sanity check: Every team-A member sees "Cloud Storage connected ✓" in their webQsee.

5. Repeat for team B (and so on)

  1. Repeat steps 1–4 for each additional team

    For team B: create bucket webqsee-team-b, apply CORS, create webqsee-team-b-admin / webqsee-team-b-user policies (ARNs targeting webqsee-team-b), groups, per-member IAM users with their webqsee-user-id tag, distribute keys, configure webQsee. The settings are exactly the same shape; only the bucket name and policy/group names differ.

    Sanity check: S3 bucket list shows webqsee-team-a, webqsee-team-b, etc. IAM Policies list shows admin/user pairs for each team. Each team's members see "Cloud Storage connected ✓" when their team is active.
Final sanity check, hard isolation

Open the AWS CLI with a team-A IAM access key:

aws s3 ls s3://webqsee-team-a/   # should list shared/ protected/ private/ ...
aws s3 ls s3://webqsee-team-b/   # should return AccessDenied

If team-A credentials can read team B, an IAM policy mistakenly references the wrong bucket name, re-check step 2. In webQsee itself: switching from team A to team B should switch the Cloud Gallery's entire content; you'll never see a team-A item while team B is active.

Troubleshooting

  • "NoSuchBucket": the bucket name in webQsee's team-wide settings doesn't match an actual bucket. Make sure you've selected the right team in webQsee before editing settings.
  • 403 on every operation: the policy ARNs are still pointing at a different bucket (e.g. you copy-pasted the team-A policy when creating team B and forgot to swap webqsee-team-awebqsee-team-b). Edit the customer-managed policy.
  • 403 on PUT to private/: the IAM user's webqsee-user-id tag is missing or doesn't match. Re-check step 3 step 3.
  • CORS error after creating a new bucket: CORS is per-bucket. You must apply step 1 step 4 to every new team bucket.
  • One human in two teams cannot upload to team B: they probably tried to reuse their team-A access key. Each team needs its own IAM user (separate access key) because the IAM tag is per-user. Generate a second key under <name>-team-b.
  • "Bucket name already exists": S3 bucket names are globally unique across all AWS customers. Add a discriminator: webqsee-team-a-acme.

Related tutorials

One bucket, many teams

The lighter alternative using prefix-based isolation in a single bucket.

Open tutorial

Create multiple webQsee teams

Spin up several teams under one organisation and administrate them centrally.

Open tutorial

Multi-user S3 with RBAC

The single-team reference setup these patterns extend.

Open tutorial

Simplified S3 integration

For solo users and small teams who want the quick path.

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.