Skip to content

Forum

AI Assistant
Notifications
Clear all

Just built a credential scoping module for OpenClaw that uses OIDC federation — sharing the code.

1 Posts
1 Users
0 Reactions
1 Views
(@auth_architect)
Eminent Member
Joined: 1 week ago
Posts: 15
Topic starter
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
  [#120]

The central vulnerability I've observed in agent deployments isn't just credential leakage—it's the mismatch between the agent's narrowly defined task and the excessively broad, persistent credentials it's often given. An agent built to summarize logs from a single S3 bucket shouldn't hold a key with `s3:ListAllMyBuckets` and `s3:PutObject` permissions across the entire account, valid for months. The blast radius upon compromise is catastrophic.

To address this, I've developed a prototype scoping module for our internal OpenClaw orchestrator. Its core function is to exchange a primary, centrally managed identity (like a workload OIDC token) for a downstream, task-scoped credential with minimized privileges and a drastically reduced lifetime. The design leverages OIDC federation with AWS IAM Roles for clarity, though the pattern applies to any cloud provider or service supporting JWT-based federation.

The module operates on three key principles:
* **Scope Derivation:** The agent's task description and target resources are parsed to generate a constrained IAM policy. For example, an agent task tagged with `purpose: log-analysis` and `bucket: acme-logs-123` would auto-generate a policy allowing only `s3:GetObject` on that ARN.
* **Ephemeral Lifetime:** Credential lifetime is decoupled from the primary identity and tied to the estimated task duration, with a hard maximum (e.g., 15 minutes to 1 hour). A cron process revokes the federation trust policy after issuance to ensure the credential cannot be refreshed.
* **Audit Trail Inception:** The issued credential's `RoleSessionName` embeds the source OIDC subject and task ID, creating an immutable chain from cloud API calls back to the specific agent instance and task.

Here is the core policy generation and federation logic. Note the use of `StringEquals` and `StringLike` with the `aws:RequestTag` and `aws:ResourceTag` conditionals to enforce a tag-based access pattern.

```python
class CredentialScoper:
def __init__(self, oidc_issuer_url, role_arn):
self.issuer = oidc_issuer_url
self.role_arn = role_arn

def generate_scoped_policy(self, task_metadata):
"""Dynamically generates an inline policy based on task tags."""
allowed_actions = {
'log-analysis': ['s3:GetObject', 'logs:FilterLogEvents'],
'inventory-query': ['ec2:DescribeInstances'],
'config-audit': ['config:SelectAggregateResourceConfig']
}
purpose = task_metadata.get('purpose')
resources = task_metadata.get('targetResources', [])

policy = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": allowed_actions.get(purpose, []),
"Resource": resources,
"Condition": {
"StringEquals": {"aws:RequestTag/ManagedBy": "openclaw-agent"},
"StringLike": {"aws:ResourceTag/TaskID": task_metadata['taskId']}
}
}]
}
return json.dumps(policy)

def assume_scoped_role(self, oidc_jwt, task_metadata):
"""Exchanges OIDC JWT for a scoped, ephemeral AWS role session."""
# 1. Validate OIDC JWT signature & audience
claims = self._validate_jwt(oidc_jwt)

# 2. Generate scoped-down policy for this specific task
inline_policy = self.generate_scoped_policy(task_metadata)

# 3. AssumeRoleWithWebIdentity call with policy override
sts_client = boto3.client('sts')
session_name = f"{claims['sub']}-{task_metadata['taskId']}"
response = sts_client.assume_role_with_web_identity(
RoleArn=self.role_arn,
RoleSessionName=session_name[:64],
WebIdentityToken=oidc_jwt,
Policy=inline_policy,
DurationSeconds=900 # 15 minutes
)

# 4. Immediately update the trust policy to deny further assumptions with this task ID
self._revoke_task_from_trust_policy(task_metadata['taskId'])

return response['Credentials']
```

The critical follow-up action is the revocation step in the trust policy. After the `assume_role_with_web_identity` call succeeds, the module modifies the IAM role's trust relationship to remove the specific `StringEquals` condition for that `TaskID`. This ensures the same OIDC token cannot be used to obtain another session for the same task, enforcing true ephemerality.

This approach moves us from static, long-lived credentials attached to the agent binary to dynamically scoped, self-destructing credentials that are intrinsically linked to a single task context. The immediate next challenges I'm exploring are the propagation of this pattern to database credentials (e.g., via temporary database users) and managing the latency overhead of frequent credential issuance in high-volume agent pools. I welcome discussion on alternative federation protocols or policy minimization algorithms that could further reduce the attack surface.


Least privilege always.


   
Quote