Table of Contents
- Kubernetes ServiceAccount Authentication (IRSA-style)
- How it works
- Prerequisites
- Part 1: Expose the cluster's OIDC discovery
- Part 2: Project a token into the pod
- Part 3: Configure SeaweedFS
- Part 4: Consume the credentials in the pod
- Option A — AWS SDK / CLI web-identity provider (recommended)
- Option B — call AssumeRoleWithWebIdentity directly
- Option C — automatic injection across many pods
- Trust policy condition keys
- Troubleshooting
- See also
Kubernetes ServiceAccount Authentication (IRSA-style)
This page covers advanced IAM features using the
-s3.iam.config/-iam.configoption.It builds on OIDC Integration — read that first for the general STS/OIDC model. This page is the Kubernetes-specific recipe.
A Kubernetes ServiceAccount (SA) token is just an OIDC JWT. That means SeaweedFS can accept it directly through AssumeRoleWithWebIdentity and hand back temporary S3 credentials — the same pattern AWS calls IRSA (IAM Roles for Service Accounts), but vendor-neutral.
This works on any cluster — managed or on-prem, kind/k3s/kubeadm — because SeaweedFS only needs to reach the cluster's OIDC discovery (JWKS) endpoint to verify token signatures. There is no dependency on EKS or AWS STS.
The result: pods authenticate to the S3 gateway with a short-lived, auto-rotated token instead of a static access key + secret.
How it works
Pod kube-apiserver / JWKS SeaweedFS S3 gateway
| | |
| 1. kubelet projects a SA token | |
| (OIDC JWT, audience=X) | |
|<---------------------------------| |
| |
| 2. AssumeRoleWithWebIdentity(RoleArn, WebIdentityToken=JWT) |
|------------------------------------------------------------------->|
| | 3. fetch JWKS, verify
| | signature + iss + aud
| | 4. match role trust policy
| | (oidc:sub = the SA)
| 5. temporary {AccessKeyId, SecretAccessKey, SessionToken} |
|<-------------------------------------------------------------------|
| |
| 6. normal S3 calls signed with the temporary credentials |
|------------------------------------------------------------------->|
SeaweedFS plays both the relying party (validating the cluster as an OIDC provider) and the STS service (issuing the temporary credentials). The credentials are stateless JWTs, so any S3 gateway instance can issue and validate them — no shared session store. See OIDC Integration for the distributed-deployment notes.
Prerequisites
- A Kubernetes cluster whose ServiceAccount issuer is a URL with a reachable OIDC discovery / JWKS endpoint (see Part 1).
- SeaweedFS S3 gateway started with advanced IAM enabled (
-iam.config/-s3.iam.config). - Network path from the S3 gateway to the cluster's JWKS endpoint.
Part 1: Expose the cluster's OIDC discovery
SeaweedFS verifies the token signature by fetching the cluster's public keys (JWKS). Find your cluster's issuer and JWKS URI:
kubectl get --raw /.well-known/openid-configuration | jq
# {
# "issuer": "https://kubernetes.default.svc.cluster.local",
# "jwks_uri": "https://kubernetes.default.svc.cluster.local/openid/v1/jwks",
# ...
# }
kubectl get --raw /openid/v1/jwks | jq
The issuer value is set by the kube-apiserver --service-account-issuer flag and is the iss claim on every SA token. SeaweedFS's issuer config must equal it exactly.
Two deployment shapes:
A. The issuer is a public HTTPS URL. Many clusters publish the discovery document and JWKS to a public location (an object-store bucket or a static HTTPS endpoint) precisely so external services can validate tokens. If {issuer}/.well-known/openid-configuration is reachable from the S3 gateway, SeaweedFS performs discovery automatically and you do not need to set jwksUri.
B. The issuer is the in-cluster API server (e.g. https://kubernetes.default.svc.cluster.local). The apiserver serves discovery and JWKS, but the endpoints normally require authentication and the URL is only resolvable inside the cluster. Make them work for SeaweedFS:
-
Allow unauthenticated reads of the discovery + JWKS endpoints (these expose public keys only):
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: service-account-issuer-discovery-unauthenticated roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:service-account-issuer-discovery subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: system:unauthenticated -
If the S3 gateway runs inside the cluster, point
jwksUriat the in-cluster service. If it runs outside, expose the JWKS through an ingress / published mirror and setjwksUrito that reachable URL.
If you control the apiserver, the cleanest setup is to give it an externally reachable issuer URL via
--service-account-issuer=https://<public-host>(and--service-account-jwks-uriif the JWKS is served from a different host). Then SeaweedFS discovery just works.
Part 2: Project a token into the pod
Do not use the default token at /var/run/secrets/kubernetes.io/serviceaccount/token — its audience targets the API server. Project a separate token with an audience that matches what SeaweedFS expects (its clientId). The kubelet rotates this token automatically.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
serviceAccountName: my-app # subject becomes system:serviceaccount:<ns>:my-app
containers:
- name: app
image: my-app:latest
env:
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: /var/run/secrets/seaweedfs/token
- name: AWS_ROLE_ARN
value: arn:aws:iam::role/K8sAppRole
volumeMounts:
- name: seaweedfs-token
mountPath: /var/run/secrets/seaweedfs
readOnly: true
volumes:
- name: seaweedfs-token
projected:
sources:
- serviceAccountToken:
path: token
audience: seaweedfs-s3 # must equal clientId in the SeaweedFS provider config
expirationSeconds: 3600
The token's sub claim is system:serviceaccount:<namespace>:<serviceaccount-name> — this is what you scope the role's trust policy to.
Part 3: Configure SeaweedFS
Register the cluster as an OIDC provider and create a role whose trust policy allows that provider's SA to assume it. No clientSecret is needed — web-identity validation only checks the signature, issuer, and audience.
/etc/seaweed/iam.json:
{
"sts": {
"tokenDuration": "1h",
"maxSessionLength": "12h",
"issuer": "seaweedfs-sts",
"signingKey": "c2Vhd2VlZGZzLXNpZ25pbmcta2V5LTMyLWNoYXJzLWxvbmc="
},
"providers": [
{
"name": "k8s",
"type": "oidc",
"enabled": true,
"config": {
"issuer": "https://kubernetes.default.svc.cluster.local",
"clientId": "seaweedfs-s3",
"jwksUri": "https://kubernetes.default.svc.cluster.local/openid/v1/jwks"
}
}
],
"policies": [
{
"name": "AppBucketPolicy",
"document": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::app-bucket", "arn:aws:s3:::app-bucket/*"]
}
]
}
}
],
"roles": [
{
"roleName": "K8sAppRole",
"roleArn": "arn:aws:iam::role/K8sAppRole",
"attachedPolicies": ["AppBucketPolicy"],
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Federated": "k8s" },
"Action": ["sts:AssumeRoleWithWebIdentity"],
"Condition": {
"StringEquals": {
"oidc:sub": "system:serviceaccount:default:my-app"
}
}
}
]
}
}
]
}
Notes:
clientId(seaweedfs-s3) must match the projected tokenaudience. Theaudclaim on a Kubernetes token is an array; SeaweedFS accepts the token if any entry matchesclientId(orclientIdsfor a list).- Set
jwksUrionly for case B above; omit it when discovery is reachable. - The trust policy is scoped to one ServiceAccount via
oidc:sub. To allow a whole namespace, useStringLikewithsystem:serviceaccount:default:*. See the condition key reference below. signingKeymust be a strong random base64 value (32+ bytes), identical across all S3 gateway instances.
Start the gateway:
weed s3 -filer=filer:8888 -port=8333 -iam.config=/etc/seaweed/iam.json
Part 4: Consume the credentials in the pod
The pod has a token file and a role ARN. Three ways to turn that into S3 access:
Option A — AWS SDK / CLI web-identity provider (recommended)
The standard AWS SDKs already implement the AssumeRoleWithWebIdentity credential provider and refresh it automatically. With the env vars from Part 2 set, point the STS calls at SeaweedFS:
export AWS_ENDPOINT_URL_STS=http://seaweedfs-s3:8333 # SeaweedFS handles STS on the S3 port
export AWS_ENDPOINT_URL=http://seaweedfs-s3:8333 # and the S3 data calls
export AWS_REGION=us-east-1
aws s3 ls s3://app-bucket/ --endpoint-url http://seaweedfs-s3:8333
The SDK reads AWS_ROLE_ARN + AWS_WEB_IDENTITY_TOKEN_FILE, calls SeaweedFS to assume the role, caches the temporary credentials, and re-assumes when they near expiry.
AWS_ENDPOINT_URL_STSis honored by AWS CLI v2 and recent SDKs. Older SDKs may ignore it — in that case set the STS endpoint in code (e.g. a customendpoint_urlon the STS client) instead of relying on the env var.
Option B — call AssumeRoleWithWebIdentity directly
Any HTTP client works. Read the projected token and POST to the gateway:
TOKEN=$(cat /var/run/secrets/seaweedfs/token)
curl -s "http://seaweedfs-s3:8333/?Action=AssumeRoleWithWebIdentity&Version=2011-06-15&RoleArn=arn:aws:iam::role/K8sAppRole&RoleSessionName=my-app&WebIdentityToken=${TOKEN}"
Response (abridged):
<AssumeRoleWithWebIdentityResponse>
<AssumeRoleWithWebIdentityResult>
<Credentials>
<AccessKeyId>...</AccessKeyId>
<SecretAccessKey>...</SecretAccessKey>
<SessionToken>eyJ...</SessionToken>
<Expiration>2026-01-01T12:00:00Z</Expiration>
</Credentials>
</AssumeRoleWithWebIdentityResult>
</AssumeRoleWithWebIdentityResponse>
Recognized parameters: WebIdentityToken (required), RoleArn, RoleSessionName, DurationSeconds, Policy (an inline session policy that further restricts the role). Your client is responsible for re-assuming before Expiration.
Option C — automatic injection across many pods
To avoid editing every Deployment, run the open-source amazon-eks-pod-identity-webhook (it works on non-EKS clusters too). It watches for a ServiceAccount annotation and injects the projected token volume and the AWS_ROLE_ARN / AWS_WEB_IDENTITY_TOKEN_FILE env vars into matching pods:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::role/K8sAppRole
eks.amazonaws.com/audience: seaweedfs-s3
You still override the STS endpoint as in Option A so the SDK targets SeaweedFS rather than the public AWS STS endpoint.
Trust policy condition keys
During AssumeRoleWithWebIdentity the following keys are available to the trust policy (see S3 Policy Conditions):
| Key | Value for a Kubernetes token |
|---|---|
oidc:sub |
system:serviceaccount:<namespace>:<name> — the ServiceAccount identity |
oidc:iss |
the cluster issuer URL |
aws:FederatedProvider |
the provider name from config (e.g. k8s) when the token issuer matches it, else the issuer URL |
aws:userid |
same as oidc:sub |
oidc:<claim> |
any other top-level claim on the token |
Scope on oidc:sub to pin a role to a specific ServiceAccount, or StringLike on system:serviceaccount:<ns>:* for a whole namespace. Audience is already enforced by the provider clientId, so you normally do not condition on it — and note oidc:aud is not populated for Kubernetes tokens because their aud is an array rather than a string.
Troubleshooting
| Problem | Solution |
|---|---|
| Invalid issuer | SeaweedFS issuer must exactly equal the token iss (kubectl get --raw /.well-known/openid-configuration). |
| JWKS errors / signature verify fails | The gateway can't reach the JWKS URI. Confirm discovery is reachable, or set jwksUri to a URL the gateway can fetch (Part 1, case B). |
| Audience matches none of the configured client IDs | The projected token audience must equal the provider clientId (or be in clientIds). |
| Trust policy denies web identity assumption | oidc:sub in the trust policy must match system:serviceaccount:<ns>:<sa> exactly; for a namespace use StringLike. |
| Credentials expire mid-run | Use the SDK web-identity provider (Option A) which auto-refreshes; if calling STS manually, re-assume before Expiration. |
| SDK still hits real AWS STS | The STS endpoint override didn't take effect; set AWS_ENDPOINT_URL_STS or configure the STS client endpoint in code. |
See also
- OIDC Integration — general OIDC/STS setup and the Keycloak walkthrough
- S3 Policy Conditions — full condition-key reference
- S3 Policy Variables — using claims like
${oidc:sub}inside policies - S3 Configuration —
-s3.configvs-s3.iam.config - Deployment to Kubernetes and Minikube — running SeaweedFS itself on Kubernetes
Introduction
- Quick Start with weed mini
- Simplest S3 Bucket and User Setup
- Components
- Getting Started
- Production Setup
- A typical step‐by‐step example
- Benchmarks
- FAQ
- Applications
API
Configuration
- Replication
- Store file with a Time To Live
- Failover Master Server
- Erasure coding for warm storage
- EC Bitrot Detection
- Server Startup via Systemd
- Environment Variables
Filer
- Filer Setup
- Directories and Files
- File Operations Quick Reference
- Data Structure for Large Files
- Filer Data Encryption
- Filer Commands and Operations
- Filer JWT Use
- TUS Resumable Uploads
Filer Stores
- Filer Cassandra Setup
- Filer Redis Setup
- Super Large Directories
- Path-Specific Filer Store
- Choosing a Filer Store
- Customize Filer Store
Management
Advanced Filer Configurations
- Migrate to Filer Store
- Add New Filer Store
- Filer Store Replication
- Filer Active Active cross cluster continuous synchronization
- Filer as a Key-Large-Value Store
- Path Specific Configuration
- Filer Change Data Capture
- Filer Operation Serialization
FUSE Mount
- FIO benchmark
- fstab and systemd mount
- POSIX Compliance
- Distributed POSIX Locks
- P2P reading in weed mount
WebDAV
SFTP Server
Cloud Drive
- Cloud Drive Benefits
- Cloud Drive Architecture
- Configure Remote Storage
- Mount Remote Storage
- Cache Remote Storage
- Cloud Drive Quick Setup
- Gateway to Remote Object Storage
AWS S3 API
- Amazon S3 API
- Supported APIs vs Minio
- S3 Lifecycle
- S3 Lifecycle vs Volume TTL
- S3 Conditional Operations
- S3 CORS
- S3 Object Lock and Retention
- S3 Object Versioning
- S3 API Benchmark
- S3 API FAQ
- S3 Bucket Quota
- S3 Rate Limiting
- S3 API Audit log
- S3 Nginx Proxy
- Docker Compose for S3
S3 Table Bucket
- S3 Table Bucket
- S3 Table Bucket Commands
- S3 Tables Security
- SeaweedFS Iceberg Catalog
- Iceberg Table Maintenance
Iceberg Integrations
- Spark Iceberg Integration
- Trino Iceberg Integration
- Dremio Iceberg Integration
- DuckDB Iceberg Integration
- Doris Iceberg Integration
- RisingWave Iceberg Integration
- Lakekeeper Iceberg Integration
S3 Authentication & IAM
- S3 Configuration - Start Here
- S3 Credentials (
-s3.config) - OIDC Integration (
-s3.iam.config) - Kubernetes ServiceAccount Authentication (IRSA-style)
- S3 Policy Variables
- S3 Policy Conditions
- S3 Bucket Policies
- Amazon IAM API
- AWS IAM CLI
- weed shell - Shell IAM Commands
Server-Side Encryption
S3 Client Tools
- AWS CLI with SeaweedFS
- s3cmd with SeaweedFS
- rclone with SeaweedFS
- restic with SeaweedFS
- nodejs with Seaweed S3
Machine Learning
HDFS
- Hadoop Compatible File System
- run Spark on SeaweedFS
- run HBase on SeaweedFS
- run Presto on SeaweedFS
- Hadoop Benchmark
- HDFS via S3 connector
Replication and Backup
- Async Replication to another Filer [Deprecated]
- Async Backup
- Async Filer Metadata Backup
- Async Replication to Cloud [Deprecated]
- Kubernetes Backups and Recovery with K8up
Metadata Change Events
Messaging
- Structured Data Lake with SMQ and SQL
- Seaweed Message Queue
- SQL Queries on Message Queue
- SQL Quick Reference
- PostgreSQL-compatible Server weed db
- Pub-Sub to SMQ to SQL
- Kafka to Kafka Gateway to SMQ to SQL
Use Cases
Operations
- System Metrics
- weed shell
- Data Backup
- Deployment to Kubernetes and Minikube
- Deployment with seaweed-up
Rust Volume Server
Advanced
- Large File Handling
- Optimization
- Optimization for Many Small Buckets
- Volume Management
- Tiered Storage
- Cloud Tier
- Cloud Monitoring
- Load Command Line Options from a file
- SRV Service Discovery
- Volume Files Structure
Security
- Security Overview
- Security Configuration
- Cryptography and FIPS Compliance
- Run Blob Storage on Public Internet