Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions api/v1alpha1/task_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const (
CredentialTypeAPIKey CredentialType = "api-key"
// CredentialTypeOAuth uses OAuth for authentication.
CredentialTypeOAuth CredentialType = "oauth"
// CredentialTypeBedrock uses AWS credentials for Bedrock authentication.
CredentialTypeBedrock CredentialType = "bedrock"
)

// TaskPhase represents the current phase of a Task.
Expand All @@ -39,12 +41,26 @@ type SecretReference struct {

// Credentials defines how to authenticate with the AI agent.
type Credentials struct {
// Type specifies the credential type (api-key or oauth).
// +kubebuilder:validation:Enum=api-key;oauth
// Type specifies the credential type.
// +kubebuilder:validation:Enum=api-key;oauth;bedrock
Type CredentialType `json:"type"`

// SecretRef references the Secret containing credentials.
SecretRef SecretReference `json:"secretRef"`
// Required for api-key and oauth types. Optional for bedrock
// when using IAM Roles for Service Accounts (IRSA).
// +optional
SecretRef *SecretReference `json:"secretRef,omitempty"`

// Region specifies the cloud provider region (e.g. AWS region for Bedrock).
// Used with bedrock credentials when secretRef is omitted (IRSA mode).
// +optional
Region string `json:"region,omitempty"`

// ServiceAccountName overrides the pod's service account.
// Use with IAM Roles for Service Accounts (IRSA) on EKS to let
// the pod assume an IAM role without static credentials.
// +optional
ServiceAccountName string `json:"serviceAccountName,omitempty"`
}

// PodOverrides defines optional overrides for the agent pod.
Expand Down Expand Up @@ -84,6 +100,7 @@ type TaskSpec struct {

// Credentials specifies how to authenticate with the agent.
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self.type == 'bedrock' || has(self.secretRef)",message="secretRef is required for api-key and oauth credential types"
Credentials Credentials `json:"credentials"`

// Model optionally overrides the default model.
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/taskspawner_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ type TaskTemplate struct {

// Credentials specifies how to authenticate with the agent.
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self.type == 'bedrock' || has(self.secretRef)",message="secretRef is required for api-key and oauth credential types"
Credentials Credentials `json:"credentials"`

// Model optionally overrides the default model.
Expand Down
10 changes: 7 additions & 3 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions cmd/kelos-spawner/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func newTaskSpawner(name, namespace string, maxConcurrency *int32) *kelosv1alpha
Type: "claude-code",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeOAuth,
SecretRef: kelosv1alpha1.SecretReference{Name: "creds"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "creds"},
},
WorkspaceRef: &kelosv1alpha1.WorkspaceReference{Name: "test-ws"},
},
Expand All @@ -100,7 +100,7 @@ func newTask(name, namespace, spawnerName string, phase kelosv1alpha1.TaskPhase)
Prompt: "test",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeOAuth,
SecretRef: kelosv1alpha1.SecretReference{Name: "creds"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "creds"},
},
},
Status: kelosv1alpha1.TaskStatus{
Expand Down Expand Up @@ -212,7 +212,7 @@ func TestBuildSource_Jira(t *testing.T) {
Type: "claude-code",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeOAuth,
SecretRef: kelosv1alpha1.SecretReference{Name: "creds"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "creds"},
},
},
},
Expand Down Expand Up @@ -1186,7 +1186,7 @@ func newCompletedTask(name, namespace, spawnerName string, phase kelosv1alpha1.T
Prompt: "test",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeOAuth,
SecretRef: kelosv1alpha1.SecretReference{Name: "creds"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "creds"},
},
},
Status: kelosv1alpha1.TaskStatus{
Expand Down Expand Up @@ -1520,7 +1520,7 @@ func TestRunCycleWithSource_PropagatesUpstreamRepo(t *testing.T) {
Type: "claude-code",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeAPIKey,
SecretRef: kelosv1alpha1.SecretReference{Name: "my-secret"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "my-secret"},
},
},
},
Expand Down Expand Up @@ -1563,7 +1563,7 @@ func TestRunCycleWithSource_ExplicitUpstreamRepoTakesPrecedence(t *testing.T) {
Type: "claude-code",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeAPIKey,
SecretRef: kelosv1alpha1.SecretReference{Name: "my-secret"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "my-secret"},
},
UpstreamRepo: "explicit-org/explicit-repo",
},
Expand Down Expand Up @@ -1843,7 +1843,7 @@ func TestRunReportingCycle_ReportsForAnnotatedTasks(t *testing.T) {
Prompt: "test",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeOAuth,
SecretRef: kelosv1alpha1.SecretReference{Name: "creds"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "creds"},
},
},
Status: kelosv1alpha1.TaskStatus{
Expand Down Expand Up @@ -1904,7 +1904,7 @@ func TestRunReportingCycle_SkipsTasksWithoutReporting(t *testing.T) {
Prompt: "test",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeOAuth,
SecretRef: kelosv1alpha1.SecretReference{Name: "creds"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "creds"},
},
},
Status: kelosv1alpha1.TaskStatus{
Expand Down
90 changes: 90 additions & 0 deletions examples/09-bedrock-credentials/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Bedrock Credentials

This example demonstrates running a Claude Code task using AWS Bedrock instead of the Anthropic API directly.

## Prerequisites

- AWS account with Bedrock access enabled for Claude models
- AWS IAM credentials with `bedrock:InvokeModel` permissions

## Option 1: Static Credentials (Secret)

1. Create the Secret with your AWS credentials:

```bash
kubectl create secret generic bedrock-credentials \
--from-literal=AWS_ACCESS_KEY_ID=<your-access-key> \
--from-literal=AWS_SECRET_ACCESS_KEY=<your-secret-key> \
--from-literal=AWS_REGION=us-east-1
```

2. Create the Task:

```bash
kubectl apply -f task.yaml
```

### Optional Secret Keys

- `AWS_SESSION_TOKEN`: Required when using temporary credentials (e.g. from STS AssumeRole)
- `ANTHROPIC_BEDROCK_BASE_URL`: Custom Bedrock endpoint URL

### CLI with Static Credentials

```yaml
# ~/.kelos/config.yaml
bedrock:
accessKeyID: AKIAIOSFODNN7EXAMPLE
secretAccessKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
region: us-east-1
```
```bash
kelos run -p "Fix the bug"
```

Or with a pre-created secret:

```bash
kelos run -p "Fix the bug" --credential-type bedrock --secret bedrock-credentials
```

## Option 2: IAM Roles for Service Accounts (IRSA)

On EKS, you can use IRSA instead of static credentials. The AWS SDK automatically picks up credentials from the projected service account token — no Secret needed.

### Prerequisites

1. Create an IAM role with `bedrock:InvokeModel` permissions
2. Create a Kubernetes ServiceAccount annotated with the IAM role:

```bash
kubectl create serviceaccount bedrock-agent-sa
kubectl annotate serviceaccount bedrock-agent-sa \
eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/bedrock-agent-role
```

3. Create the Task with `region` and `serviceAccountName` (no `secretRef`):

```bash
kubectl apply -f task-irsa.yaml
```

### CLI with IRSA

```yaml
# ~/.kelos/config.yaml
bedrock:
region: us-east-1
serviceAccountName: bedrock-agent-sa
```
```bash
kelos run -p "Fix the bug"
```

Or with flags:

```bash
kelos run -p "Fix the bug" --credential-type bedrock --region us-east-1 --service-account bedrock-agent-sa
```
14 changes: 14 additions & 0 deletions examples/09-bedrock-credentials/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Secret
metadata:
name: bedrock-credentials
type: Opaque
stringData:
# TODO: Replace with your AWS credentials
AWS_ACCESS_KEY_ID: "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
AWS_REGION: "us-east-1"
# Optional: uncomment if using temporary credentials (e.g. STS AssumeRole)
# AWS_SESSION_TOKEN: "your-session-token"
# Optional: uncomment to use a custom Bedrock endpoint
# ANTHROPIC_BEDROCK_BASE_URL: "https://bedrock-runtime.us-east-1.amazonaws.com"
11 changes: 11 additions & 0 deletions examples/09-bedrock-credentials/task-irsa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: kelos.dev/v1alpha1
kind: Task
metadata:
name: bedrock-irsa-task
spec:
type: claude-code
prompt: "Write a Python script that prints the first 20 Fibonacci numbers."
credentials:
type: bedrock
region: us-east-1
serviceAccountName: bedrock-agent-sa
11 changes: 11 additions & 0 deletions examples/09-bedrock-credentials/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: kelos.dev/v1alpha1
kind: Task
metadata:
name: bedrock-task
spec:
type: claude-code
prompt: "Write a Python script that prints the first 20 Fibonacci numbers."
credentials:
type: bedrock
secretRef:
name: bedrock-credentials
13 changes: 13 additions & 0 deletions internal/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ type Config struct {
Namespace string `json:"namespace,omitempty"`
Workspace WorkspaceConfig `json:"workspace,omitempty"`
AgentConfig string `json:"agentConfig,omitempty"`
Bedrock *BedrockConfig `json:"bedrock,omitempty"`
}

// BedrockConfig holds AWS credentials for Bedrock authentication.
// For IRSA mode, omit accessKeyID and secretAccessKey and set only region
// and serviceAccountName.
type BedrockConfig struct {
AccessKeyID string `json:"accessKeyID,omitempty"`
SecretAccessKey string `json:"secretAccessKey,omitempty"`
Region string `json:"region"`
SessionToken string `json:"sessionToken,omitempty"`
BaseURL string `json:"baseURL,omitempty"`
ServiceAccountName string `json:"serviceAccountName,omitempty"`
}

// WorkspaceConfig holds workspace-related configuration.
Expand Down
4 changes: 3 additions & 1 deletion internal/cli/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ func printTaskDetail(w io.Writer, t *kelosv1alpha1.Task) {
printField(w, "Type", t.Spec.Type)
printField(w, "Phase", string(t.Status.Phase))
printField(w, "Prompt", t.Spec.Prompt)
printField(w, "Secret", t.Spec.Credentials.SecretRef.Name)
if t.Spec.Credentials.SecretRef != nil {
printField(w, "Secret", t.Spec.Credentials.SecretRef.Name)
}
printField(w, "Credential Type", string(t.Spec.Credentials.Type))
if t.Spec.Model != "" {
printField(w, "Model", t.Spec.Model)
Expand Down
4 changes: 2 additions & 2 deletions internal/cli/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ func TestPrintTaskDetail(t *testing.T) {
Prompt: "Fix the bug",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeAPIKey,
SecretRef: kelosv1alpha1.SecretReference{Name: "my-secret"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "my-secret"},
},
Model: "claude-sonnet-4-20250514",
Image: "custom-image:latest",
Expand Down Expand Up @@ -993,7 +993,7 @@ func TestPrintTaskDetailMinimal(t *testing.T) {
Prompt: "Do something",
Credentials: kelosv1alpha1.Credentials{
Type: kelosv1alpha1.CredentialTypeAPIKey,
SecretRef: kelosv1alpha1.SecretReference{Name: "secret"},
SecretRef: &kelosv1alpha1.SecretReference{Name: "secret"},
},
},
Status: kelosv1alpha1.TaskStatus{
Expand Down
Loading
Loading