Skip to main content

Restricted Bucket Access in Storin (Write-Only and Read-Only)

warning

⚠️ All configurations in this guide are examples. Adapt bucket names, profiles, and User IDs to your environment.

📌 Overview​

This guide shows how to share a Storin bucket with restricted access, for example:

  • a credential that can only upload files (write-only), useful for applications that upload backups or logs;
  • a credential that can only read and list files (read-only), useful for auditing, querying, or processing.

Access control is done through a Bucket Policy.

Other tools

The examples in this guide use the AWS CLI, but the same procedure can be done via the S3 API, MinIO Client (mc), or any other S3-compatible tool. The Bucket Policy (JSON) is the same; only the command used to apply it changes.


🧠 How Storin applies permissions​

In Storin, permissions are not applied to the Access Key or Secret Key individually. They are applied to the User ID (S3 identity) that the credential belongs to.

In practice:

  • Each project in the panel has one S3 identity, with its own User ID.
  • All Access Keys generated within the same project use the same User ID and therefore have exactly the same permissions.
  • Keys are only used to authenticate the identity. What can or cannot be done is defined by the User ID referenced in the policy.
info

To have different access profiles (owner, write-only, read-only), each profile needs a different User ID, which means a different project.

Generating a new Access Key/Secret Key in the same project does not create a new identity.

Example structure​

ProjectUser IDPurpose
Main projectUSER_OWNERBucket owner
Writer projectUSER_WRITERWrite-only
Reader projectUSER_READERRead-only

The Bucket Policy is set on the bucket of the Main project, referencing USER_WRITER and USER_READER.


🧰 Prerequisites​

  • A bucket already created in the main project (the bucket owner).
  • An S3-compatible tool. This guide uses the AWS CLI; if you haven't set it up yet, follow the guide Configuring the AWS CLI with Storin.
  • jq installed (optional, used to validate the policy JSON before applying it).

🆔 Part 1 – Creating the identities (User IDs)​

Repeat the steps below once for each restricted profile (one project for the Writer and another for the Reader).

Step 1 – Create a new project​

In the panel, create a new project. It can be in the same account or in a different account from the one that holds the bucket.

Creating a new project

Step 2 – Initialize the S3 identity​

The project's S3 identity is generated the first time Object Storage is used. To initialize it, create a temporary bucket in this project and then delete it.

Creating a temporary bucket

tip

The temporary bucket is only used to generate the identity. It can be deleted right away with no impact on the User ID.

Step 3 – Copy the User ID​

Go to the project's Access tab and copy the User ID shown.

Access tab with the User ID

Step 4 – Generate the Access Key and Secret Key​

Still in the Access tab, generate the project's Access Key and Secret Key and store them in a safe place. They will be used by the application or person who will have the restricted access.

Generating Access Key and Secret Key

danger

The Secret Key is only shown at creation time. Store it immediately in a safe place, such as a password manager.

At the end of this part, you will have:

ProfileUser IDCredentials
Writer<WRITER_ID>Access Key + Secret Key
Reader<READER_ID>Access Key + Secret Key

⚙️ Part 2 – Configuring the AWS CLI​

Configure one profile for each identity (owner, writer, and reader).

~/.aws/config file​

[profile storin-owner]
region = sp1
output = json
request_checksum_calculation = when_required
response_checksum_validation = when_required
endpoint_url = https://sp1-s3.saveincloud.io

[profile storin-writer]
region = sp1
output = json
request_checksum_calculation = when_required
response_checksum_validation = when_required
endpoint_url = https://sp1-s3.saveincloud.io

[profile storin-reader]
region = sp1
output = json
request_checksum_calculation = when_required
response_checksum_validation = when_required
endpoint_url = https://sp1-s3.saveincloud.io

~/.aws/credentials file​

[storin-owner]
aws_access_key_id = <ACCESS_KEY_OWNER>
aws_secret_access_key = <SECRET_KEY_OWNER>

[storin-writer]
aws_access_key_id = <ACCESS_KEY_WRITER>
aws_secret_access_key = <SECRET_KEY_WRITER>

[storin-reader]
aws_access_key_id = <ACCESS_KEY_READER>
aws_secret_access_key = <SECRET_KEY_READER>
warning

The request_checksum_calculation and response_checksum_validation lines are required on AWS CLI 2.25 or later. Without them, every upload fails with ValidationError.

info

Adjust the endpoint_url according to your bucket's region (for example, https://sp1-s3.saveincloud.io or https://bsb1-s3.saveincloud.io).


📝 Part 3 – Creating and applying the Bucket Policy​

Step 1 – Create the policy file​

Create a file named policy.json with the content below:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowWrite",
"Effect": "Allow",
"Principal": { "CanonicalUser": "<WRITER_ID>" },
"Action": [
"s3:PutObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": "arn:aws:s3:::<BUCKET>/*"
},
{
"Sid": "DenyReadForWriter",
"Effect": "Deny",
"Principal": { "CanonicalUser": "<WRITER_ID>" },
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:DeleteObject",
"s3:DeleteObjectVersion"
],
"Resource": "arn:aws:s3:::<BUCKET>/*"
},
{
"Sid": "AllowReadObjects",
"Effect": "Allow",
"Principal": { "CanonicalUser": "<READER_ID>" },
"Action": ["s3:GetObject", "s3:GetObjectVersion"],
"Resource": "arn:aws:s3:::<BUCKET>/*"
},
{
"Sid": "AllowListForReader",
"Effect": "Allow",
"Principal": { "CanonicalUser": "<READER_ID>" },
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::<BUCKET>", "arn:aws:s3:::<BUCKET>/*"]
}
]
}

Replace:

VariableValue
<WRITER_ID>User ID of the Writer project
<READER_ID>User ID of the Reader project
<BUCKET>Name of the bucket being shared

What each block does​

SidProfileEffect
AllowWriteWriterAllows uploading objects, including multipart uploads
DenyReadForWriterWriterBlocks reading and deleting, including objects it uploaded itself
AllowReadObjectsReaderAllows downloading objects and their versions
AllowListForReaderReaderAllows listing the bucket contents

Step 2 – Apply the policy​

The policy must be applied with the bucket owner's credential. AWS CLI example:

jq . policy.json && aws --profile storin-owner s3api put-bucket-policy \
--bucket <BUCKET> \
--policy file://policy.json

jq validates the JSON before sending it. If there is a syntax error, the command stops before applying the policy.

To check the applied policy:

aws --profile storin-owner s3api get-bucket-policy --bucket <BUCKET>

❗ Mandatory rules​

These rules apply to any tool used to apply the policy.

Principal must use CanonicalUser

The Principal must use CanonicalUser with the hexadecimal User ID. The forms below do not work in Storin and may return 403:

"Principal": { "AWS": "<id>" }
"Principal": { "AWS": "arn:aws:iam:::user/<id>" }
The DenyReadForWriter block is mandatory

Whoever performs the PUT of an object becomes the owner of that object and can read it through ACL. Without the explicit Deny, the Writer can read everything it uploaded itself.

The bucket owner cannot be restricted

The bucket owner always has full access, because ownership takes precedence over the policy. That is why the restricted credential must belong to an identity different from the owner's, that is, to another project.


✅ Part 4 – Validating the permissions​

The tests below use the AWS CLI. With another tool, run the equivalent operations (upload, download, list, and delete) with each credential and compare against the expected results table.

Create a test file:

echo "test" > /tmp/t.txt

Writer (write-only)​

# Should SUCCEED
aws --profile storin-writer s3api put-object --bucket <BUCKET> --key wr.txt --body /tmp/t.txt

# Should return 403 (AccessDenied)
aws --profile storin-writer s3api get-object --bucket <BUCKET> --key wr.txt /tmp/x.txt
aws --profile storin-writer s3api list-objects-v2 --bucket <BUCKET>
aws --profile storin-writer s3api delete-object --bucket <BUCKET> --key wr.txt
tip

Test the Writer's GET on an object it created itself (like wr.txt above). This is the scenario that proves DenyReadForWriter is working.

Reader (read-only)​

# Should SUCCEED
aws --profile storin-reader s3api get-object --bucket <BUCKET> --key wr.txt /tmp/y.txt
aws --profile storin-reader s3api list-objects-v2 --bucket <BUCKET>

# Should return 403 (AccessDenied)
aws --profile storin-reader s3api put-object --bucket <BUCKET> --key ot.txt --body /tmp/t.txt
aws --profile storin-reader s3api delete-object --bucket <BUCKET> --key wr.txt

Expected results​

IdentityPUTGETLISTDELETE
Owner✅✅✅✅
Writer✅❌❌❌
Reader❌✅✅❌

🛠️ Troubleshooting – Common Issues​

❌ 403 AccessDenied when applying or using the policy​

  • Check that the Principal uses the format "CanonicalUser": "<USER_ID>".
  • Confirm the copied User ID belongs to the correct project (Access tab).
  • Confirm the policy was applied with the bucket owner's credential.

❌ ValidationError on every upload (AWS CLI)​

The checksum lines are missing from ~/.aws/config. Add them to the profile:

request_checksum_calculation = when_required
response_checksum_validation = when_required

❌ The Writer can read its own files​

The DenyReadForWriter block is missing from the policy or has the wrong User ID. Review the policy and apply it again.

❌ The "restricted" credential has full access​

The Access Key was generated in the same project as the bucket owner. Keys from the same project share the same User ID. Create a separate project for the restricted profile, as described in Part 1.

❌ The Reader cannot list the bucket​

The s3:ListBucket action requires both resources: arn:aws:s3:::<BUCKET> and arn:aws:s3:::<BUCKET>/*.

About overwriting objects

The Writer can upload an object with an existing key, replacing the previous content. If this is a risk in your case, use unique keys when uploading (for example, with the date and time in the name) or enable versioning on the bucket.


📘 Related documentation: Configuring the AWS CLI with Storin