Skip to content

Commit 73dd109

Browse files
committed
Name the Cloudflare token for role management, not as a generic API token
The secret and Pulumi config key are dedicated to Access policy role management (Account > Access: Apps and Policies > Edit only), so name them CLOUDFLARE_ROLE_MANAGEMENT_TOKEN / cloudflare:roleManagementToken. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBarRtzEtwT2HvfcEvRbW7
1 parent 79f0eaa commit 73dd109

5 files changed

Lines changed: 20 additions & 17 deletions

File tree

‎.github/workflows/deploy.yml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,16 @@ jobs:
6666
ORG_BILLING_EMAIL: ${{ secrets.ORG_BILLING_EMAIL }}
6767
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
6868
DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }}
69-
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
69+
CLOUDFLARE_ROLE_MANAGEMENT_TOKEN: ${{ secrets.CLOUDFLARE_ROLE_MANAGEMENT_TOKEN }}
7070
run: |
7171
echo "$PULUMI_PASSPHRASE" > passphrase.prod.txt
7272
export PULUMI_CONFIG_PASSPHRASE_FILE=passphrase.prod.txt
7373
pulumi login gs://mcp-access-prod-pulumi-state
7474
pulumi config set discord:guildId "$DISCORD_GUILD_ID" --stack prod
7575
pulumi config set discord:botToken "$DISCORD_BOT_TOKEN" --secret --stack prod
7676
pulumi config set githubBillingEmail "$ORG_BILLING_EMAIL" --secret --stack prod
77-
# Cloudflare Access is optional until the CLOUDFLARE_API_TOKEN secret exists
78-
if [ -n "$CLOUDFLARE_API_TOKEN" ]; then
79-
pulumi config set cloudflare:apiToken "$CLOUDFLARE_API_TOKEN" --secret --stack prod
77+
# Cloudflare Access is optional until the CLOUDFLARE_ROLE_MANAGEMENT_TOKEN secret exists
78+
if [ -n "$CLOUDFLARE_ROLE_MANAGEMENT_TOKEN" ]; then
79+
pulumi config set cloudflare:roleManagementToken "$CLOUDFLARE_ROLE_MANAGEMENT_TOKEN" --secret --stack prod
8080
fi
8181
make up

‎.github/workflows/preview.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
ORG_BILLING_EMAIL: ${{ secrets.ORG_BILLING_EMAIL }}
6767
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
6868
DISCORD_GUILD_ID: ${{ secrets.DISCORD_GUILD_ID }}
69-
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
69+
CLOUDFLARE_ROLE_MANAGEMENT_TOKEN: ${{ secrets.CLOUDFLARE_ROLE_MANAGEMENT_TOKEN }}
7070
run: |
7171
echo "$PULUMI_PASSPHRASE" > passphrase.prod.txt
7272
pulumi login gs://mcp-access-prod-pulumi-state
@@ -82,8 +82,8 @@ jobs:
8282
if [ -n "$ORG_BILLING_EMAIL" ]; then
8383
CONFIG_FLAGS="$CONFIG_FLAGS --config githubBillingEmail=$ORG_BILLING_EMAIL"
8484
fi
85-
if [ -n "$CLOUDFLARE_API_TOKEN" ]; then
86-
CONFIG_FLAGS="$CONFIG_FLAGS --config cloudflare:apiToken=$CLOUDFLARE_API_TOKEN"
85+
if [ -n "$CLOUDFLARE_ROLE_MANAGEMENT_TOKEN" ]; then
86+
CONFIG_FLAGS="$CONFIG_FLAGS --config cloudflare:roleManagementToken=$CLOUDFLARE_ROLE_MANAGEMENT_TOKEN"
8787
fi
8888
8989
# Run preview and capture output

‎Pulumi.prod.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ config:
55
secure: v1:Ntr2su0kgwp0oAh3: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
66
github:owner: modelcontextprotocol
77
# Cloudflare Access (see README "Cloudflare Access (security-room)").
8-
# cloudflare:apiToken is set by the deploy workflow from the CLOUDFLARE_API_TOKEN secret.
8+
# cloudflare:roleManagementToken is set by the deploy workflow from the CLOUDFLARE_ROLE_MANAGEMENT_TOKEN secret.
99
cloudflare:accountId: c39d6379ccfdb20a3627eeddffa80ec9
1010
cloudflare:githubIdentityProviderId: 1e53bc11-a504-4572-92d1-4c8d56fc198b
1111
# Adopt the existing dashboard-created policies on the first deploy. Set to "false"

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ Once merged, Pulumi provisions the account. An admin will share your initial pas
4343

4444
### One-time setup
4545

46-
1. In the Cloudflare dashboard for the **MCP Domain Account**, create an API token with the permission **Account → Access: Apps and Policies → Edit** (scoped to that account only).
47-
2. Add it as the GitHub Actions secret `CLOUDFLARE_API_TOKEN` in the `production` environment (repository settings → Environments → production). Until the secret exists, the Cloudflare module logs "Cloudflare integration disabled" and creates nothing, so previews stay green.
46+
1. In the Cloudflare dashboard for the **MCP Domain Account**, create a token dedicated to Access policy role management. Give it a descriptive name so it does not read as a generic API token, e.g. `mcp-access: Access policy role management`, and scope it to only **Account → Access: Apps and Policies → Edit** on the MCP Domain Account. Do not reuse this token for anything else.
47+
2. Add it as the GitHub Actions secret `CLOUDFLARE_ROLE_MANAGEMENT_TOKEN` in the `production` environment (repository settings → Environments → production). The deploy workflow passes it to Pulumi as `cloudflare:roleManagementToken`. Until the secret exists, the Cloudflare module logs "Cloudflare integration disabled: roleManagementToken not configured" and creates nothing, so previews stay green.
4848
3. The account ID and GitHub identity-provider ID are non-secret and live in [`Pulumi.prod.yaml`](Pulumi.prod.yaml).
4949
4. **Adopting the existing policy.** Pulumi's `import` resource option only succeeds when the program's inputs match the live resource, so adoption is two deploys:
5050
- With `cloudflare:importExistingPolicies: "true"` in `Pulumi.prod.yaml`, the first deploy imports the existing `Maintainers` policy (by its `cloudflarePolicyId`) as-is, ignoring its rule lists.
@@ -113,7 +113,7 @@ The following secrets must be configured in GitHub Actions for automated deploym
113113
- Used to decrypt encrypted values in Pulumi stack configuration
114114
- Keep this secure - if lost, you cannot decrypt your Pulumi state
115115

116-
- **`CLOUDFLARE_API_TOKEN`** (optional, `production` environment): Cloudflare API token with **Account → Access: Apps and Policies → Edit** on the MCP Domain Account
116+
- **`CLOUDFLARE_ROLE_MANAGEMENT_TOKEN`** (optional, `production` environment): Cloudflare token dedicated to Access policy role management, scoped only to **Account → Access: Apps and Policies → Edit** on the MCP Domain Account (not a general-purpose API token)
117117
- Used to manage the Cloudflare Access policy for `securityroom.modelcontextprotocol.io` (see [Cloudflare Access (security-room)](#cloudflare-access-security-room))
118118

119119
## Initial Setup

‎src/cloudflare.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import * as cloudflare from '@pulumi/cloudflare';
33
import { ACCESS_POLICIES, GITHUB_ORG, getAccessPolicyTeams } from './config/accessPolicies';
44

55
const config = new pulumi.Config('cloudflare');
6-
// Cloudflare integration is optional - only enabled if apiToken is configured, so
7-
// previews stay green before the CLOUDFLARE_API_TOKEN secret exists.
8-
const CLOUDFLARE_API_TOKEN = config.getSecret('apiToken');
9-
const CLOUDFLARE_ENABLED = CLOUDFLARE_API_TOKEN !== undefined;
6+
// Cloudflare integration is optional - only enabled if roleManagementToken is configured,
7+
// so previews stay green before the CLOUDFLARE_ROLE_MANAGEMENT_TOKEN secret exists.
8+
// The token is scoped to Access policy role management only (Account > Access: Apps and
9+
// Policies > Edit); it is not a general-purpose Cloudflare API token.
10+
const CLOUDFLARE_ROLE_MANAGEMENT_TOKEN = config.getSecret('roleManagementToken');
11+
const CLOUDFLARE_ENABLED = CLOUDFLARE_ROLE_MANAGEMENT_TOKEN !== undefined;
1012

1113
if (!CLOUDFLARE_ENABLED) {
12-
pulumi.log.info('Cloudflare integration disabled: apiToken not configured');
14+
pulumi.log.info('Cloudflare integration disabled: roleManagementToken not configured');
1315
}
1416

1517
// Access policies keyed by policy id (accessPolicies.ts)
@@ -26,7 +28,8 @@ if (CLOUDFLARE_ENABLED) {
2628
const importExisting = config.getBoolean('importExistingPolicies') ?? false;
2729

2830
const provider = new cloudflare.Provider('cloudflare', {
29-
apiToken: CLOUDFLARE_API_TOKEN,
31+
// Provider argument name; the value is the role-management token above.
32+
apiToken: CLOUDFLARE_ROLE_MANAGEMENT_TOKEN,
3033
});
3134

3235
ACCESS_POLICIES.forEach((policy) => {

0 commit comments

Comments
 (0)