Skip to content
Merged
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
37 changes: 27 additions & 10 deletions containers/api-proxy/providers/anthropic.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,34 @@ function createAnthropicAdapter(env, deps = {}) {
validationPath: '/v1/messages',
validationMethod: 'POST',
validationBody: '{}',
validationHeaders: () => ({
[authHeaderName]: apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
}),
validationSkip: () => (oidcConfigured
? { skip: true, reason: 'OIDC auth; validation via token acquisition' }
: null),
skipModelsFetch: () => oidcConfigured,
validationHeaders: () => {
if (oidcProvider && oidcProvider.isReady()) {
return {
'Authorization': `Bearer ${oidcProvider.getToken()}`,
'anthropic-version': '2023-06-01',
Comment on lines +124 to +128
'content-type': 'application/json',
};
}
return {
[authHeaderName]: apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
};
},
validationSkip: () => {
if (!oidcConfigured) return null;
// After OIDC init, validate using the acquired token
if (oidcProvider.isReady()) return null;
return { skip: true, reason: 'OIDC auth; token not yet available' };
},
skipModelsFetch: () => oidcConfigured && !oidcProvider?.isReady(),
modelsPath: '/v1/models',
modelsFetchHeaders: () => ({ [authHeaderName]: apiKey, 'anthropic-version': '2023-06-01' }),
modelsFetchHeaders: () => {
if (oidcProvider && oidcProvider.isReady()) {
return { 'Authorization': `Bearer ${oidcProvider.getToken()}`, 'anthropic-version': '2023-06-01' };
}
return { [authHeaderName]: apiKey, 'anthropic-version': '2023-06-01' };
},
reflectionConfigured: !!apiKey || oidcRequested,
reflectionExtra: () => ({
auth_type: oidcRequested ? 'github-oidc/anthropic' : 'static-key',
Expand Down
3 changes: 3 additions & 0 deletions containers/api-proxy/server.custom-auth-header.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ describe('createAnthropicAdapter — custom auth header', () => {
AWF_AUTH_PROVIDER: 'anthropic',
ACTIONS_ID_TOKEN_REQUEST_URL: 'http://localhost/token',
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'test-token',
AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID: 'fdrl_test',
AWF_AUTH_ANTHROPIC_ORGANIZATION_ID: 'org-uuid-test',
AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID: 'svac_test',
});

const provider = adapter.getOidcProvider();
Expand Down
2 changes: 1 addition & 1 deletion containers/api-proxy/server.lifecycle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ describe('provider adapter alwaysBind', () => {

expect(adapter.isEnabled()).toBe(false);
expect(adapter.getOidcProvider()).not.toBeNull();
expect(adapter.getValidationProbe()).toEqual({ skip: true, reason: 'OIDC auth; validation via token acquisition' });
expect(adapter.getValidationProbe()).toEqual({ skip: true, reason: 'OIDC auth; token not yet available' });
expect(adapter.getModelsFetchConfig()).toBeNull();
expect(adapter.getReflectionInfo().configured).toBe(true);
expect(adapter.getReflectionInfo().auth_type).toBe('github-oidc/anthropic');
Expand Down
4 changes: 4 additions & 0 deletions docs/awf-config-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ the corresponding CLI flag.
- `apiProxy.auth.gcpWorkloadIdentityProvider` → *(config-only; maps to `AWF_AUTH_GCP_WORKLOAD_IDENTITY_PROVIDER`)*
- `apiProxy.auth.gcpServiceAccount` → *(config-only; maps to `AWF_AUTH_GCP_SERVICE_ACCOUNT`)*
- `apiProxy.auth.gcpScope` → *(config-only; maps to `AWF_AUTH_GCP_SCOPE`)*
- `apiProxy.auth.anthropicFederationRuleId` → *(config-only; maps to `AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID`)*
- `apiProxy.auth.anthropicOrganizationId` → *(config-only; maps to `AWF_AUTH_ANTHROPIC_ORGANIZATION_ID`)*
- `apiProxy.auth.anthropicServiceAccountId` → *(config-only; maps to `AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID`)*
- `apiProxy.auth.anthropicWorkspaceId` → *(config-only; maps to `AWF_AUTH_ANTHROPIC_WORKSPACE_ID`)*
- `apiProxy.targets.<provider>.host` → `--<provider>-api-target` *(except `antigravity.host`, which maps to the Gemini flag below)*
- `apiProxy.targets.antigravity.host` → `--gemini-api-target`
- `apiProxy.targets.openai.basePath` → `--openai-api-base-path`
Expand Down
24 changes: 23 additions & 1 deletion docs/awf-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,22 @@
"type": "string",
"description": "OAuth2 scope for GCP token. Maps to AWF_AUTH_GCP_SCOPE.",
"default": "https://www.googleapis.com/auth/cloud-platform"
},
"anthropicFederationRuleId": {
"type": "string",
"description": "Anthropic federation rule ID (e.g. fdrl_...). Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID."
},
"anthropicOrganizationId": {
"type": "string",
"description": "Anthropic organization UUID. Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_ORGANIZATION_ID."
},
"anthropicServiceAccountId": {
"type": "string",
"description": "Anthropic service account ID (e.g. svac_...). Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID."
},
"anthropicWorkspaceId": {
"type": "string",
"description": "Anthropic workspace ID. Required when the federation rule covers multiple workspaces. Maps to AWF_AUTH_ANTHROPIC_WORKSPACE_ID."
}
},
"required": [
Expand Down Expand Up @@ -263,7 +279,13 @@
"provider"
]
},
"then": {},
"then": {
"required": [
"anthropicFederationRuleId",
"anthropicOrganizationId",
"anthropicServiceAccountId"
]
},
"else": {
"required": [
"azureTenantId",
Expand Down
24 changes: 23 additions & 1 deletion src/awf-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,22 @@
"type": "string",
"description": "OAuth2 scope for GCP token. Maps to AWF_AUTH_GCP_SCOPE.",
"default": "https://www.googleapis.com/auth/cloud-platform"
},
"anthropicFederationRuleId": {
"type": "string",
"description": "Anthropic federation rule ID (e.g. fdrl_...). Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_FEDERATION_RULE_ID."
},
"anthropicOrganizationId": {
"type": "string",
"description": "Anthropic organization UUID. Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_ORGANIZATION_ID."
},
"anthropicServiceAccountId": {
"type": "string",
"description": "Anthropic service account ID (e.g. svac_...). Required when provider is 'anthropic'. Maps to AWF_AUTH_ANTHROPIC_SERVICE_ACCOUNT_ID."
},
"anthropicWorkspaceId": {
"type": "string",
"description": "Anthropic workspace ID. Required when the federation rule covers multiple workspaces. Maps to AWF_AUTH_ANTHROPIC_WORKSPACE_ID."
}
},
"required": [
Expand Down Expand Up @@ -263,7 +279,13 @@
"provider"
]
},
"then": {},
"then": {
"required": [
"anthropicFederationRuleId",
"anthropicOrganizationId",
"anthropicServiceAccountId"
]
},
"else": {
"required": [
"azureTenantId",
Expand Down
33 changes: 33 additions & 0 deletions src/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,39 @@ describe('awf-config.schema.json', () => {
auth: {
type: 'github-oidc',
provider: 'anthropic',
anthropicFederationRuleId: 'fdrl_abc123',
anthropicOrganizationId: 'org-uuid-abc',
anthropicServiceAccountId: 'svac_abc123',
},
},
})
).toBe(true);
});

it('rejects apiProxy.auth anthropic without required fields', () => {
expect(
validate({
apiProxy: {
auth: {
type: 'github-oidc',
provider: 'anthropic',
},
},
})
).toBe(false);
});

it('accepts apiProxy.auth anthropic with optional workspaceId', () => {
expect(
validate({
apiProxy: {
auth: {
type: 'github-oidc',
provider: 'anthropic',
anthropicFederationRuleId: 'fdrl_abc123',
anthropicOrganizationId: 'org-uuid-abc',
anthropicServiceAccountId: 'svac_abc123',
anthropicWorkspaceId: 'ws_abc123',
},
},
})
Expand Down
Loading