Skip to content

Commit dda10eb

Browse files
markturanskyAmbient Code Botclaude
authored
fix(cp): credential rolebinding and project delete (#1203)
## Summary - Seeds `credential:token-reader` and `credential:reader` roles via migration `202603311216` - Mounts runner BOT_TOKEN as file with 10-min background refresh loop in control-plane - Authorizes runner OIDC service account in `WatchSessionMessages` - Adds exponential backoff retry in informer error handler - Adds `acpctl apply -f` credential manifest support and role-binding commands to CLI ## Test plan - [ ] Deploy to OSD `ambient-s0` via ArgoCD (gitops MR \!94 already merged) - [ ] Verify `credential:token-reader` and `credential:reader` appear in `acpctl get roles` - [ ] Create role binding for `github-agent` in `credential-test` project - [ ] Start agent session and confirm runner pod retrieves token via `GET /credentials/{id}/token` 🤖 Generated with [Claude Code](https://claude.ai/code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added role seeding and management capabilities to credentials system. * Enhanced CLI with updated login, project context, and resource management commands. * Introduced declarative manifest application via `acpctl apply`. * Added agent creation and session messaging commands. * **Bug Fixes** * Strengthened authorization validation for session message access. * **Documentation** * Expanded CLI reference with comprehensive command examples and usage patterns. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Ambient Code Bot <bot@ambient-code.local> Co-authored-by: Claude <noreply@anthropic.com>
1 parent d0a19d9 commit dda10eb

5 files changed

Lines changed: 255 additions & 32 deletions

File tree

components/ambient-api-server/plugins/credentials/migration.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package credentials
22

33
import (
4+
"encoding/json"
5+
46
"gorm.io/gorm"
57

68
"github.com/go-gormigrate/gormigrate/v2"
9+
"github.com/openshift-online/rh-trex-ai/pkg/api"
710
"github.com/openshift-online/rh-trex-ai/pkg/db"
811
)
912

@@ -30,3 +33,67 @@ func migration() *gormigrate.Migration {
3033
},
3134
}
3235
}
36+
37+
func rolesMigration() *gormigrate.Migration {
38+
type roleRow struct {
39+
ID string
40+
Name string
41+
DisplayName string
42+
Description string
43+
Permissions string
44+
BuiltIn bool
45+
}
46+
47+
seed := []struct {
48+
name string
49+
displayName string
50+
description string
51+
permissions []string
52+
}{
53+
{
54+
name: "credential:token-reader",
55+
displayName: "Credential Token Reader",
56+
description: "Retrieve the raw token value for a credential",
57+
permissions: []string{"credential:token"},
58+
},
59+
{
60+
name: "credential:reader",
61+
displayName: "Credential Reader",
62+
description: "Read credential metadata (name, provider, description)",
63+
permissions: []string{"credential:read", "credential:list"},
64+
},
65+
}
66+
67+
return &gormigrate.Migration{
68+
ID: "202603311216",
69+
Migrate: func(tx *gorm.DB) error {
70+
for _, r := range seed {
71+
permsJSON, err := json.Marshal(r.permissions)
72+
if err != nil {
73+
return err
74+
}
75+
row := roleRow{
76+
ID: api.NewID(),
77+
Name: r.name,
78+
DisplayName: r.displayName,
79+
Description: r.description,
80+
Permissions: string(permsJSON),
81+
BuiltIn: true,
82+
}
83+
if err := tx.Table("roles").
84+
Where("name = ?", r.name).
85+
FirstOrCreate(&row).Error; err != nil {
86+
return err
87+
}
88+
}
89+
return nil
90+
},
91+
Rollback: func(tx *gorm.DB) error {
92+
names := make([]string, len(seed))
93+
for i, r := range seed {
94+
names[i] = r.name
95+
}
96+
return tx.Table("roles").Where("name IN ?", names).Delete(&roleRow{}).Error
97+
},
98+
}
99+
}

components/ambient-api-server/plugins/credentials/plugin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,5 @@ func init() {
8282
presenters.RegisterKind(&Credential{}, "Credential")
8383

8484
db.RegisterMigration(migration())
85+
db.RegisterMigration(rolesMigration())
8586
}

components/ambient-api-server/plugins/credentials/testmain_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ import (
99
"github.com/golang/glog"
1010

1111
"github.com/ambient-code/platform/components/ambient-api-server/test"
12+
13+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/agents"
14+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/inbox"
15+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/projectSettings"
16+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/projects"
17+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/rbac"
18+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/roleBindings"
19+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/roles"
20+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/sessions"
21+
_ "github.com/ambient-code/platform/components/ambient-api-server/plugins/users"
22+
_ "github.com/openshift-online/rh-trex-ai/plugins/events"
23+
_ "github.com/openshift-online/rh-trex-ai/plugins/generic"
1224
)
1325

1426
func TestMain(m *testing.M) {

components/ambient-api-server/plugins/sessions/grpc_handler.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@ func (h *sessionGRPCHandler) WatchSessionMessages(req *pb.WatchSessionMessagesRe
288288

289289
if !middleware.IsServiceCaller(ctx) {
290290
username := auth.GetUsernameFromContext(ctx)
291-
if username != "" && (h.grpcServiceAccount == "" || username != h.grpcServiceAccount) {
291+
if username == "" {
292+
return status.Error(codes.PermissionDenied, "not authorized to watch this session")
293+
}
294+
if h.grpcServiceAccount == "" || username != h.grpcServiceAccount {
292295
session, svcErr := h.service.Get(ctx, req.GetSessionId())
293296
if svcErr != nil {
294297
return grpcutil.ServiceErrorToGRPC(svcErr)

components/ambient-cli/README.md

Lines changed: 171 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,91 +21,231 @@ This produces an `acpctl` binary in the current directory with embedded version
2121

2222
```bash
2323
# With a token and API server URL
24-
./acpctl login --token <your-token> --url http://localhost:8000 --project myproject
24+
acpctl login <api-url> --token <your-token>
25+
26+
# Skip TLS verification (e.g. local Kind cluster)
27+
acpctl login <api-url> --token <your-token> --insecure-skip-tls-verify
28+
29+
# Use RH SSO
30+
acpctl login --use-auth-code --url https://ambient-api-server-ambient-code--ambient-s0.apps.int.spoke.dev.us-east-1.aws.paas.redhat.com
2531

2632
# Verify
27-
./acpctl whoami
33+
acpctl whoami
34+
# User: service-account-bob
35+
# Project: myproject
2836
```
2937

3038
### 2. Configure defaults
3139

3240
```bash
3341
# Set or change the default project
34-
./acpctl config set project myproject
42+
acpctl config set project myproject
43+
44+
# Switch active project context (shorthand)
45+
acpctl project myproject
46+
47+
# Show the currently active project
48+
acpctl project current
3549

3650
# View current settings
37-
./acpctl config get api_url
38-
./acpctl config get project
51+
acpctl config get api_url
52+
acpctl config get project
3953
```
4054

4155
### 3. List resources
4256

4357
```bash
44-
# List sessions (table format)
45-
./acpctl get sessions
58+
# Sessions
59+
acpctl get sessions
60+
acpctl get sessions -o json
4661

47-
# List projects
48-
./acpctl get projects
62+
# Single session by ID
63+
acpctl get session <session-id>
64+
acpctl get session <session-id> -o json
4965

50-
# JSON output
51-
./acpctl get sessions -o json
66+
# Projects
67+
acpctl get projects
68+
69+
# Agents
70+
acpctl get agents
71+
acpctl get agents -o json
5272

53-
# Single resource by ID
54-
./acpctl get session <session-id>
73+
# Credentials
74+
acpctl get credentials
75+
acpctl get credentials -o json
76+
77+
# Roles
78+
acpctl get roles
79+
acpctl get roles -o json
5580
```
5681

5782
### 4. Create resources
5883

5984
```bash
6085
# Create a project
61-
./acpctl create project --name my-project --display-name "My Project"
86+
acpctl create project --name my-project --display-name "My Project" --description "Demo project"
6287

6388
# Create a session
64-
./acpctl create session --name fix-bug-123 \
89+
acpctl create session --name fix-bug-123 \
6590
--prompt "Fix the null pointer in handler.go" \
6691
--repo-url https://github.com/org/repo \
6792
--model sonnet
6893

6994
# Create with all options
70-
./acpctl create session --name refactor-auth \
95+
acpctl create session --name refactor-auth \
7196
--prompt "Refactor the auth middleware" \
7297
--model sonnet \
7398
--max-tokens 4000 \
7499
--temperature 0.7 \
75100
--timeout 3600
101+
102+
# Create an agent
103+
acpctl agent create \
104+
--project-id my-project \
105+
--name my-agent \
106+
--prompt "You are a GitHub automation agent."
107+
108+
# Create a role binding (bind a credential role to an agent)
109+
acpctl create role-binding \
110+
--user-id <user-id> \
111+
--role-id <role-id> \
112+
--scope agent \
113+
--scope-id <agent-id>
76114
```
77115

78-
### 5. Session lifecycle
116+
### 5. Apply declarative manifests
117+
118+
`acpctl apply` creates or updates resources from YAML files. Token values can be
119+
injected via environment variables referenced in the manifest.
120+
121+
```bash
122+
# Apply a credential manifest
123+
cat > credential.yaml <<'EOF'
124+
kind: Credential
125+
name: my-github-pat
126+
provider: github
127+
token: $GITHUB_TOKEN
128+
description: GitHub PAT for CI
129+
EOF
130+
131+
GITHUB_TOKEN="ghp_..." acpctl apply -f credential.yaml
132+
```
133+
134+
Supported `kind` values: `Credential` (additional kinds vary by deployment).
135+
136+
### 6. Agent sessions
137+
138+
```bash
139+
# Start a session for a named agent with an initial prompt
140+
acpctl agent start <agent-name> \
141+
--project-id <project-name> \
142+
--prompt "Open a test issue in org/repo"
143+
144+
# Start and capture the session ID
145+
SESSION_JSON=$(acpctl agent start my-agent --project-id my-project --prompt "..." -o json)
146+
SESSION_ID=$(echo "$SESSION_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
147+
```
148+
149+
### 7. Session lifecycle
79150

80151
```bash
81152
# Start a session
82-
./acpctl start <session-id>
153+
acpctl start <session-id>
83154

84155
# Stop a session
85-
./acpctl stop <session-id>
156+
acpctl stop <session-id>
157+
```
158+
159+
### 8. Session messages
160+
161+
```bash
162+
# List all messages for a session (table format)
163+
acpctl session messages <session-id>
164+
165+
# JSON output
166+
acpctl session messages <session-id> -o json
167+
168+
# Stream new messages live (follow mode)
169+
acpctl session messages <session-id> -f
170+
171+
# Stream only messages after a known sequence number
172+
acpctl session messages <session-id> -f --after 42
173+
174+
# Send a user message to a running session (multi-turn)
175+
acpctl session send <session-id> "Please also update the test file."
86176
```
87177

88-
### 6. Inspect resources
178+
### 9. Inspect resources
89179

90180
```bash
91-
# Full JSON detail of a session
92-
./acpctl describe session <session-id>
181+
# Full detail of a session
182+
acpctl describe session <session-id>
93183

94-
# Full JSON detail of a project
95-
./acpctl describe project <project-id>
184+
# Full detail of a project
185+
acpctl describe project <project-id>
96186
```
97187

98-
### 7. Delete resources
188+
### 10. Delete resources
99189

100190
```bash
101-
./acpctl delete project <project-id>
102-
./acpctl delete project-settings <id>
191+
acpctl delete session <session-id> -y
192+
acpctl delete project <project-id> -y
193+
acpctl delete project-settings <id>
194+
acpctl credential delete <credential-id> --confirm
103195
```
104196

105-
### 8. Log out
197+
### 11. Log out
106198

107199
```bash
108-
./acpctl logout
200+
acpctl logout
201+
```
202+
203+
## Credentials
204+
205+
Credentials store secrets (e.g. GitHub PATs, API keys) that are injected into
206+
agent sessions at runtime. The runner retrieves the raw token via the
207+
credentials API, so the secret is never embedded in session configuration.
208+
209+
```bash
210+
# List credentials
211+
acpctl get credentials
212+
213+
# Create via apply (token injected from env var — never passed as a flag)
214+
GITHUB_TOKEN="ghp_..." acpctl apply -f credential.yaml
215+
216+
# Delete
217+
acpctl credential delete <credential-id> --confirm
218+
```
219+
220+
### Role bindings
221+
222+
Access to credentials is controlled by role bindings. The relevant roles are:
223+
224+
| Role | Permission |
225+
|---|---|
226+
| `credential:token-reader` | Retrieve the raw credential token via `GET /credentials/{id}/token` |
227+
| `credential:reader` | Read credential metadata (name, provider, description) |
228+
229+
```bash
230+
# Look up a role ID
231+
ROLE_ID=$(acpctl get roles -o json | python3 -c "
232+
import sys, json
233+
data = json.load(sys.stdin)
234+
items = data.get('items', []) if isinstance(data, dict) else data
235+
for r in items:
236+
if r.get('name') == 'credential:token-reader':
237+
print(r['id']); break
238+
")
239+
240+
# Get your user ID
241+
MY_USER_ID=$(acpctl whoami | awk '/^User:/{print $2}')
242+
243+
# Bind the role to an agent (agent can now retrieve the token)
244+
acpctl create role-binding \
245+
--user-id "${MY_USER_ID}" \
246+
--role-id "${ROLE_ID}" \
247+
--scope agent \
248+
--scope-id <agent-id>
109249
```
110250

111251
## Try It Now (No Server Required)
@@ -122,12 +262,12 @@ make build
122262
./acpctl create --help
123263

124264
# Login and config flow
125-
./acpctl login --token test-token --url http://localhost:8000 --project demo
265+
./acpctl login http://localhost:8000 --token test-token
126266
./acpctl whoami
127267
./acpctl config get api_url
128268
./acpctl config get project
129269
./acpctl config set project other-project
130-
./acpctl config get project
270+
./acpctl project current
131271

132272
# Shell completion
133273
./acpctl completion bash

0 commit comments

Comments
 (0)