add keycloak integration

chrislusf
2025-09-11 10:06:23 -07:00
parent 51574f2436
commit d231a0f9fd
2 changed files with 188 additions and 0 deletions
+187
@@ -0,0 +1,187 @@
# Keycloak Integration (OIDC) with SeaweedFS S3 Gateway
This guide shows how to integrate Keycloak (OpenID Connect) with SeaweedFS S3 Gateway using the advanced IAM and STS configuration. It supports both:
- Direct OIDC authentication to S3 with Bearer tokens
- OIDC to STS role assumption using trust policies and role mapping
## Prerequisites
- A running Keycloak server and a realm (e.g. `seaweedfs`)
- A Keycloak client (e.g. `seaweedfs-s3`) created in that realm
- SeaweedFS with advanced IAM enabled via `-iam.config`
## Step 1: Configure Keycloak
1) Create a client
- Client ID: `seaweedfs-s3`
- Access Type: public or confidential (confidential requires a client secret)
- Standard Flow: enabled (for browser login flows)
2) Add role/group claims to tokens
- Add a mapper of type "Group Membership" or "User Realm Role" that puts roles/groups into a top-level claim:
- Claim name: `groups` (recommended) or `roles`
- Add to ID token: true
- Add to Access token: true
3) Confirm OIDC discovery
- Open your realm discovery document and note the issuer and certs endpoints (Keycloak Quarkus defaults):
- Issuer: `https://KEYCLOAK/realms/<realm>`
- JWKS (certs): `https://KEYCLOAK/realms/<realm>/protocol/openid-connect/certs`
- UserInfo: `https://KEYCLOAK/realms/<realm>/protocol/openid-connect/userinfo`
Note: For older Keycloak distributions, issuer may include `/auth` in the path.
## Step 2: Prepare SeaweedFS IAM config
Create an IAM configuration JSON file (e.g. `/etc/seaweed/iam_keycloak.json`) and reference it with `weed s3 -iam.config=...`.
Minimal example with Keycloak OIDC provider, role mapping, roles, and policies:
```json
{
"sts": {
"tokenDuration": "1h",
"maxSessionLength": "12h",
"issuer": "seaweedfs-sts",
"signingKey": "c2Vhd2VlZGZzLXNpZ25pbmcta2V5LTMyLWNoYXJzLWxvbmc="
},
"providers": [
{
"name": "keycloak",
"type": "oidc",
"enabled": true,
"config": {
"issuer": "https://KEYCLOAK/realms/seaweedfs",
"clientId": "seaweedfs-s3",
"clientSecret": "<optional-if-confidential>",
"jwksUri": "https://KEYCLOAK/realms/seaweedfs/protocol/openid-connect/certs",
"userInfoUri": "https://KEYCLOAK/realms/seaweedfs/protocol/openid-connect/userinfo",
"scopes": ["openid", "profile", "email", "roles", "groups"],
"roleMapping": {
"rules": [
{ "claim": "groups", "value": "admins", "role": "arn:seaweed:iam::role/S3AdminRole" },
{ "claim": "groups", "value": "developers", "role": "arn:seaweed:iam::role/S3WriteRole" }
],
"defaultRole": "arn:seaweed:iam::role/S3ReadOnlyRole"
}
}
}
],
"policies": [
{
"name": "S3ReadOnlyPolicy",
"document": {
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["s3:List*", "s3:Get*"], "Resource": ["*"] }
]
}
},
{
"name": "S3WritePolicy",
"document": {
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["s3:List*", "s3:Get*", "s3:Put*", "s3:DeleteObject"], "Resource": ["*"] }
]
}
},
{
"name": "S3AdminPolicy",
"document": {
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["s3:*"] , "Resource": ["*"] }
]
}
}
],
"roles": [
{
"roleName": "S3ReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/S3ReadOnlyRole",
"attachedPolicies": ["S3ReadOnlyPolicy"],
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Federated": "*" },
"Action": ["sts:AssumeRoleWithWebIdentity"],
"Condition": {
"StringEquals": { "seaweed:Issuer": "https://KEYCLOAK/realms/seaweedfs" }
}
}
]
}
},
{
"roleName": "S3WriteRole",
"roleArn": "arn:seaweed:iam::role/S3WriteRole",
"attachedPolicies": ["S3WritePolicy"],
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Federated": "*" },
"Action": ["sts:AssumeRoleWithWebIdentity"],
"Condition": {
"StringEquals": { "seaweed:Issuer": "https://KEYCLOAK/realms/seaweedfs" }
}
}
]
}
},
{
"roleName": "S3AdminRole",
"roleArn": "arn:seaweed:iam::role/S3AdminRole",
"attachedPolicies": ["S3AdminPolicy"],
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Federated": "*" },
"Action": ["sts:AssumeRoleWithWebIdentity"],
"Condition": {
"StringEquals": { "seaweed:Issuer": "https://KEYCLOAK/realms/seaweedfs" }
}
}
]
}
}
]
}
```
Notes:
- Set `signingKey` to a strong random secret (base64-encoded 32+ bytes). All S3 gateway instances must share the same STS `issuer` and `signingKey`.
- Explicit `jwksUri` and `userInfoUri` are recommended for Keycloak.
- Ensure your Keycloak mappers populate a top-level `groups` (or `roles`) claim.
## Step 3: Start the S3 Gateway
```bash
weed s3 -filer=filer:8888 -port=8333 -iam.config=/etc/seaweed/iam_keycloak.json
```
For multi-instance deployments, use the same IAM config on each instance.
## Using It
- Direct OIDC to S3 (Bearer): obtain a Keycloak access or ID token for client `seaweedfs-s3` and call S3 with:
```bash
curl -H "Authorization: Bearer $KEYCLOAK_TOKEN" http://s3-gateway:8333/
```
- Role selection: SeaweedFS maps OIDC claims via `roleMapping`. With the example above, users in `admins` get `S3AdminRole`, `developers` get `S3WriteRole`, others default to `S3ReadOnlyRole`.
## Troubleshooting
- Invalid token: verify token `iss` equals the configured provider `issuer` and `aud` or `azp` equals the client ID.
- JWKS errors: ensure `jwksUri` is reachable from the S3 gateway. For Keycloak, use the `.../protocol/openid-connect/certs` endpoint.
- No roles applied: confirm Keycloak mapper emits `groups` (or `roles`) at top-level in the token, and adjust `roleMapping` rules accordingly.
- Trust policy denied: ensure `seaweed:Issuer` in the trust policy matches your Keycloak realm issuer exactly.
+1
@@ -91,6 +91,7 @@
### AWS IAM
* [[Amazon IAM API]]
* [[AWS IAM CLI]]
* [[Keycloak Integration]]
### Machine Learning
* [[TensorFlow with SeaweedFS]]