Skip to content

Commit 420335d

Browse files
authored
Add workload identity federation support to base-action (#1378)
* Add workload identity federation support to base-action Move the workload identity module into base-action so the standalone action can fetch and refresh the GitHub OIDC identity token itself, and expose the same federation inputs as the outer action. Switch the base-action test workflows from the anthropic_api_key secret to the federation repo variables and grant them id-token: write. * Verify MCP test tool invocation instead of init connection status MCP servers can connect asynchronously, so the init event may report a server as pending. Check that the server is registered at init, then assert the test tool was actually called and returned its response. Also pass the MCP config through claude_args --mcp-config, replacing the removed mcp_config input.
1 parent 7f37f2e commit 420335d

15 files changed

Lines changed: 250 additions & 126 deletions

.github/workflows/ci-all.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,25 @@ on:
1111

1212
permissions:
1313
contents: read
14+
# Lets the test workflows mint the GitHub OIDC token they exchange for a
15+
# Claude API access token (workload identity federation). See docs/setup.md.
16+
id-token: write
1417

1518
jobs:
1619
ci:
1720
uses: ./.github/workflows/ci.yml
1821

1922
test-base-action:
2023
uses: ./.github/workflows/test-base-action.yml
21-
secrets: inherit # Required for ANTHROPIC_API_KEY
2224

2325
test-custom-executables:
2426
uses: ./.github/workflows/test-custom-executables.yml
25-
secrets: inherit
2627

2728
test-mcp-servers:
2829
uses: ./.github/workflows/test-mcp-servers.yml
29-
secrets: inherit
3030

3131
test-settings:
3232
uses: ./.github/workflows/test-settings.yml
33-
secrets: inherit
3433

3534
test-structured-output:
3635
uses: ./.github/workflows/test-structured-output.yml
37-
secrets: inherit

.github/workflows/test-base-action.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ on:
1010
default: "List the files in the current directory starting with 'package'"
1111
workflow_call:
1212

13+
# The Claude API is authenticated via workload identity federation: id-token
14+
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
15+
# access token. See docs/setup.md.
16+
permissions:
17+
contents: read
18+
id-token: write
19+
1320
jobs:
1421
test-inline-prompt:
1522
runs-on: ubuntu-latest
@@ -21,7 +28,9 @@ jobs:
2128
uses: ./base-action
2229
with:
2330
prompt: ${{ github.event.inputs.test_prompt || 'List the files in the current directory starting with "package"' }}
24-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
31+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
32+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
33+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
2534
allowed_tools: "LS,Read"
2635

2736
- name: Verify inline prompt output
@@ -78,7 +87,9 @@ jobs:
7887
uses: ./base-action
7988
with:
8089
prompt_file: "test-prompt.txt"
81-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
90+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
91+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
92+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
8293
allowed_tools: "LS,Read"
8394

8495
- name: Verify prompt file output

.github/workflows/test-custom-executables.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ on:
55
workflow_dispatch:
66
workflow_call:
77

8+
# The Claude API is authenticated via workload identity federation: id-token
9+
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
10+
# access token. See docs/setup.md.
11+
permissions:
12+
contents: read
13+
id-token: write
14+
815
jobs:
916
test-custom-executables:
1017
runs-on: ubuntu-latest
@@ -47,7 +54,9 @@ jobs:
4754
with:
4855
prompt: |
4956
List the files in the current directory starting with "package"
50-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
57+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
58+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
59+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
5160
path_to_claude_code_executable: /home/runner/.local/bin/claude
5261
path_to_bun_executable: /home/runner/.bun/bin/bun
5362
allowed_tools: "LS,Read"

.github/workflows/test-mcp-servers.yml

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ on:
55
workflow_dispatch:
66
workflow_call:
77

8+
# The Claude API is authenticated via workload identity federation: id-token
9+
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
10+
# access token. See docs/setup.md.
11+
permissions:
12+
contents: read
13+
id-token: write
14+
815
jobs:
916
test-mcp-integration:
1017
runs-on: ubuntu-latest
@@ -25,8 +32,11 @@ jobs:
2532
uses: ./base-action
2633
id: claude-test
2734
with:
28-
prompt: "List all available tools"
29-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
35+
prompt: "Call the test_tool tool and report its response."
36+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
37+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
38+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
39+
claude_args: --allowedTools mcp__test-server__test_tool
3040
env:
3141
# Change to test directory so it finds .mcp.json
3242
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
@@ -50,21 +60,29 @@ jobs:
5060
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers' "$OUTPUT_FILE" > /dev/null; then
5161
echo "✓ Found mcp_servers in output"
5262
53-
# Check if test-server is connected
54-
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers[] | select(.name == "test-server" and .status == "connected")' "$OUTPUT_FILE" > /dev/null; then
55-
echo "✓ test-server is connected"
63+
# MCP servers can connect asynchronously, so the init event may
64+
# report the server as pending — check registration there, then
65+
# verify the tool actually ran.
66+
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers[] | select(.name == "test-server")' "$OUTPUT_FILE" > /dev/null; then
67+
echo "✓ test-server is registered"
5668
else
57-
echo "✗ test-server not found or not connected"
69+
echo "✗ test-server not found"
5870
jq '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers' "$OUTPUT_FILE"
5971
exit 1
6072
fi
61-
62-
# Check if mcp tools are available
63-
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .tools[] | select(. == "mcp__test-server__test_tool")' "$OUTPUT_FILE" > /dev/null; then
64-
echo "✓ MCP test tool found"
73+
74+
if jq -e '.[] | select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "mcp__test-server__test_tool")' "$OUTPUT_FILE" > /dev/null; then
75+
echo "✓ MCP test tool was called"
76+
else
77+
echo "✗ MCP test tool was not called"
78+
jq '[.[] | select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | .name]' "$OUTPUT_FILE"
79+
exit 1
80+
fi
81+
82+
if jq -e '.[] | select(.type == "user") | .message.content[]? | select(.type == "tool_result") | select(.content | tostring | contains("Test tool response"))' "$OUTPUT_FILE" > /dev/null; then
83+
echo "✓ MCP test tool returned its response"
6584
else
66-
echo "✗ MCP test tool not found"
67-
jq '.[] | select(.type == "system" and .subtype == "init") | .tools' "$OUTPUT_FILE"
85+
echo "✗ MCP test tool response not found"
6886
exit 1
6987
fi
7088
else
@@ -106,9 +124,13 @@ jobs:
106124
uses: ./base-action
107125
id: claude-config-test
108126
with:
109-
prompt: "List all available tools"
110-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
111-
mcp_config: '{"mcpServers":{"test-server":{"type":"stdio","command":"bun","args":["simple-mcp-server.ts"],"env":{}}}}'
127+
prompt: "Call the test_tool tool and report its response."
128+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
129+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
130+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
131+
claude_args: |
132+
--allowedTools mcp__test-server__test_tool
133+
--mcp-config '{"mcpServers":{"test-server":{"type":"stdio","command":"bun","args":["simple-mcp-server.ts"],"env":{}}}}'
112134
env:
113135
# Change to test directory so bun can find the MCP server script
114136
CLAUDE_WORKING_DIR: ${{ github.workspace }}/base-action/test/mcp-test
@@ -132,21 +154,29 @@ jobs:
132154
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers' "$OUTPUT_FILE" > /dev/null; then
133155
echo "✓ Found mcp_servers in output"
134156
135-
# Check if test-server is connected
136-
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers[] | select(.name == "test-server" and .status == "connected")' "$OUTPUT_FILE" > /dev/null; then
137-
echo "✓ test-server is connected"
157+
# MCP servers can connect asynchronously, so the init event may
158+
# report the server as pending — check registration there, then
159+
# verify the tool actually ran.
160+
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers[] | select(.name == "test-server")' "$OUTPUT_FILE" > /dev/null; then
161+
echo "✓ test-server is registered"
138162
else
139-
echo "✗ test-server not found or not connected"
163+
echo "✗ test-server not found"
140164
jq '.[] | select(.type == "system" and .subtype == "init") | .mcp_servers' "$OUTPUT_FILE"
141165
exit 1
142166
fi
143-
144-
# Check if mcp tools are available
145-
if jq -e '.[] | select(.type == "system" and .subtype == "init") | .tools[] | select(. == "mcp__test-server__test_tool")' "$OUTPUT_FILE" > /dev/null; then
146-
echo "✓ MCP test tool found"
167+
168+
if jq -e '.[] | select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "mcp__test-server__test_tool")' "$OUTPUT_FILE" > /dev/null; then
169+
echo "✓ MCP test tool was called"
170+
else
171+
echo "✗ MCP test tool was not called"
172+
jq '[.[] | select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | .name]' "$OUTPUT_FILE"
173+
exit 1
174+
fi
175+
176+
if jq -e '.[] | select(.type == "user") | .message.content[]? | select(.type == "tool_result") | select(.content | tostring | contains("Test tool response"))' "$OUTPUT_FILE" > /dev/null; then
177+
echo "✓ MCP test tool returned its response"
147178
else
148-
echo "✗ MCP test tool not found"
149-
jq '.[] | select(.type == "system" and .subtype == "init") | .tools' "$OUTPUT_FILE"
179+
echo "✗ MCP test tool response not found"
150180
exit 1
151181
fi
152182
else

.github/workflows/test-settings.yml

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ on:
55
workflow_dispatch:
66
workflow_call:
77

8+
# The Claude API is authenticated via workload identity federation: id-token
9+
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
10+
# access token. See docs/setup.md.
11+
permissions:
12+
contents: read
13+
id-token: write
14+
815
jobs:
916
test-settings-inline-allow:
1017
runs-on: ubuntu-latest
@@ -17,7 +24,9 @@ jobs:
1724
with:
1825
prompt: |
1926
Use Bash to echo "Hello from settings test"
20-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
27+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
28+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
29+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
2130
settings: |
2231
{
2332
"permissions": {
@@ -66,7 +75,9 @@ jobs:
6675
with:
6776
prompt: |
6877
Run the command `echo $HOME` to check the home directory path
69-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
78+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
79+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
80+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
7081
settings: |
7182
{
7283
"permissions": {
@@ -108,7 +119,9 @@ jobs:
108119
with:
109120
prompt: |
110121
Use Bash to echo "Hello from settings file test"
111-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
122+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
123+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
124+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
112125
settings: "test-settings.json"
113126

114127
- name: Verify echo worked
@@ -162,7 +175,9 @@ jobs:
162175
with:
163176
prompt: |
164177
Run the command `echo $HOME` to check the home directory path
165-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
178+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
179+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
180+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
166181
settings: "test-settings.json"
167182

168183
- name: Verify echo was denied

.github/workflows/test-structured-output.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ on:
55
workflow_dispatch:
66
workflow_call:
77

8+
# The Claude API is authenticated via workload identity federation: id-token
9+
# lets the action mint the GitHub OIDC token it exchanges for a short-lived
10+
# access token. See docs/setup.md.
811
permissions:
912
contents: read
13+
id-token: write
1014

1115
jobs:
1216
test-basic-types:
@@ -28,7 +32,9 @@ jobs:
2832
- number_field: 42
2933
- boolean_true: true
3034
- boolean_false: false
31-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
35+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
36+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
37+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
3238
claude_args: |
3339
--allowedTools Bash
3440
--json-schema '{"type":"object","properties":{"text_field":{"type":"string"},"number_field":{"type":"number"},"boolean_true":{"type":"boolean"},"boolean_false":{"type":"boolean"}},"required":["text_field","number_field","boolean_true","boolean_false"]}'
@@ -86,7 +92,9 @@ jobs:
8692
- items: ["apple", "banana", "cherry"]
8793
- config: {"key": "value", "count": 3}
8894
- empty_array: []
89-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
95+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
96+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
97+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
9098
claude_args: |
9199
--allowedTools Bash
92100
--json-schema '{"type":"object","properties":{"items":{"type":"array","items":{"type":"string"}},"config":{"type":"object"},"empty_array":{"type":"array"}},"required":["items","config","empty_array"]}'
@@ -138,7 +146,9 @@ jobs:
138146
- empty_string: ""
139147
- negative: -5
140148
- decimal: 3.14
141-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
149+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
150+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
151+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
142152
claude_args: |
143153
--allowedTools Bash
144154
--json-schema '{"type":"object","properties":{"zero":{"type":"number"},"empty_string":{"type":"string"},"negative":{"type":"number"},"decimal":{"type":"number"}},"required":["zero","empty_string","negative","decimal"]}'
@@ -192,7 +202,9 @@ jobs:
192202
prompt: |
193203
Run: echo "test"
194204
Return EXACTLY: {test-result: "passed", item_count: 10}
195-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
205+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
206+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
207+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
196208
claude_args: |
197209
--allowedTools Bash
198210
--json-schema '{"type":"object","properties":{"test-result":{"type":"string"},"item_count":{"type":"number"}},"required":["test-result","item_count"]}'
@@ -230,7 +242,9 @@ jobs:
230242
uses: ./base-action
231243
with:
232244
prompt: "Run: echo 'complete'. Return: {done: true}"
233-
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
245+
anthropic_federation_rule_id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
246+
anthropic_organization_id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
247+
anthropic_service_account_id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
234248
claude_args: |
235249
--allowedTools Bash
236250
--json-schema '{"type":"object","properties":{"done":{"type":"boolean"}},"required":["done"]}'

0 commit comments

Comments
 (0)