Skip to content

Commit 2b6d45f

Browse files
authored
Merge pull request #195 from agesta23/feat/account-scoped-tokens
feat: add support for account-scoped API tokens
2 parents 045b164 + 2488488 commit 2b6d45f

3 files changed

Lines changed: 59 additions & 4 deletions

File tree

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ Authentication towards the Cloudflare API can be done in two ways:
2626
The preferred way of authenticating is with an API token, for which the scope can be configured at the Cloudflare
2727
dashboard.
2828

29+
**Important**: Cloudflare supports two types of API tokens:
30+
- **User-level tokens**: Can access all accounts the user has permissions for. These tokens auto-discover all accessible accounts.
31+
- **Account-scoped tokens**: Scoped to a specific account. When using account-scoped tokens, you **must** set the `CF_ACCOUNTS` environment variable with your account ID(s).
32+
2933
Required authentication scopes:
3034

3135
- `Zone/Analytics:Read` is required for zone-level metrics
@@ -37,9 +41,15 @@ Required authentication scopes:
3741
- `Account:Load Balancing: Monitors and Pools:Read` is required to fetch pools origin health status `cloudflare_pool_origin_health_status` metric
3842
- `Cloudflare Tunnel Read` is required to fetch Cloudflare Tunnel (Cloudflare Zero Trust) metrics
3943

40-
To authenticate this way, only set `CF_API_TOKEN` (omit `CF_API_EMAIL` and `CF_API_KEY`)
44+
**To authenticate with a user-level token**:
45+
- Set `CF_API_TOKEN` (omit `CF_API_EMAIL` and `CF_API_KEY`)
46+
- The exporter will auto-discover all accounts you have access to
47+
- [Shortcut to create the API token](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_analytics%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22analytics%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22firewall_services%22%2C%22type%22%3A%22read%22%7D%5D&name=Cloudflare+Exporter&accountId=*&zoneId=all)
4148

42-
[Shortcut to create the API token](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_analytics%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22analytics%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22firewall_services%22%2C%22type%22%3A%22read%22%7D%5D&name=Cloudflare+Exporter&accountId=*&zoneId=all)
49+
**To authenticate with an account-scoped token**:
50+
- Set `CF_API_TOKEN` with your account-scoped token
51+
- Set `CF_ACCOUNTS` with your account ID (find it in the Cloudflare dashboard URL: `https://dash.cloudflare.com/<ACCOUNT_ID>/...`)
52+
- Example: `CF_ACCOUNTS=abc123def456` or for multiple accounts: `CF_ACCOUNTS=abc123,def456`
4353

4454
### User email + API key
4555

@@ -57,6 +67,7 @@ The exporter can be configured using env variables or command flags.
5767
| `CF_API_EMAIL` | user email (see <https://support.cloudflare.com/hc/en-us/articles/200167836-Managing-API-Tokens-and-Keys>) |
5868
| `CF_API_KEY` | API key associated with email (`CF_API_EMAIL` is required if this is set)|
5969
| `CF_API_TOKEN` | API authentication token (recommended before API key + email. Version 0.0.5+. see <https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/api-token-auth>) |
70+
| `CF_ACCOUNTS` | (Required for account-scoped tokens) Cloudflare account IDs to monitor, comma delimited list. When using account-scoped API tokens, this must be set. User-level tokens can omit this to auto-discover all accessible accounts. |
6071
| `CF_ZONES` | (Optional) cloudflare zones to export, comma delimited list of zone ids. If not set, all zones from account are exported |
6172
| `CF_EXCLUDE_ZONES` | (Optional) cloudflare zones to exclude, comma delimited list of zone ids. If not set, no zones from account are excluded |
6273
| `CF_TIMEOUT` | Set cloudflare request timeout. Default 10 seconds |
@@ -77,6 +88,7 @@ Corresponding flags:
7788
-cf_api_email="": cloudflare api email, works with api_key flag
7889
-cf_api_key="": cloudflare api key, works with api_email flag
7990
-cf_api_token="": cloudflare api token (version 0.0.5+, preferred)
91+
-cf_accounts="": cloudflare accounts to monitor, comma delimited list (required for account-scoped API tokens)
8092
-cf_zones="": cloudflare zones to export, comma delimited list
8193
-cf_exclude_zones="": cloudflare zones to exclude, comma delimited list
8294
-cf_timeout="10s": cloudflare request timeout, default 10 seconds

cloudflare.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,37 @@ func fetchFirewallRules(zoneID string) map[string]string {
458458
return firewallRulesMap
459459
}
460460

461-
func fetchAccounts() []cfaccounts.Account {
461+
func fetchAccounts(targetAccountIDs []string) []cfaccounts.Account {
462462
var cfAccounts []cfaccounts.Account
463+
464+
if len(targetAccountIDs) > 0 {
465+
log.Info("Using provided account IDs (account-scoped token mode)")
466+
for _, accountID := range targetAccountIDs {
467+
accountID = strings.TrimSpace(accountID)
468+
if accountID == "" {
469+
continue
470+
}
471+
472+
ctx, cancel := context.WithTimeout(context.Background(), cftimeout)
473+
account, err := cfclient.Accounts.Get(ctx, cfaccounts.AccountGetParams{
474+
AccountID: cf.F(accountID),
475+
})
476+
cancel()
477+
478+
if err != nil {
479+
log.Warnf("Account %s details unavailable, using ID only: %v", accountID, err)
480+
cfAccounts = append(cfAccounts, cfaccounts.Account{
481+
ID: accountID,
482+
})
483+
} else {
484+
cfAccounts = append(cfAccounts, *account)
485+
}
486+
}
487+
log.Infof("Loaded %d account(s) from CF_ACCOUNTS", len(cfAccounts))
488+
return cfAccounts
489+
}
490+
491+
log.Info("Listing all accessible accounts (user-level token mode)")
463492
ctx, cancel := context.WithTimeout(context.Background(), cftimeout)
464493
defer cancel()
465494
page := cfclient.Accounts.ListAutoPaging(ctx,

main.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ var (
3939
// cfgMetricsDenylist = ""
4040
// )
4141

42+
func getTargetAccounts() []string {
43+
var accountIDs []string
44+
45+
if len(viper.GetString("cf_accounts")) > 0 {
46+
accountIDs = strings.Split(viper.GetString("cf_accounts"), ",")
47+
}
48+
return accountIDs
49+
}
50+
4251
func getTargetZones() []string {
4352
var zoneIDs []string
4453

@@ -105,7 +114,8 @@ func filterExcludedZones(all []cfzones.Zone, exclude []string) []cfzones.Zone {
105114

106115
func fetchMetrics() {
107116
var wg sync.WaitGroup
108-
accounts := fetchAccounts()
117+
targetAccounts := getTargetAccounts()
118+
accounts := fetchAccounts(targetAccounts)
109119

110120
for _, a := range accounts {
111121
wg.Add(1)
@@ -257,6 +267,10 @@ func main() {
257267
flags.String("cf_api_token", "", "cloudflare api token (preferred)")
258268
viper.BindEnv("cf_api_token")
259269

270+
flags.String("cf_accounts", "", "cloudflare accounts to monitor, comma delimited list of account ids (required for account-scoped API tokens)")
271+
viper.BindEnv("cf_accounts")
272+
viper.SetDefault("cf_accounts", "")
273+
260274
flags.String("cf_zones", "", "cloudflare zones to export, comma delimited list of zone ids")
261275
viper.BindEnv("cf_zones")
262276
viper.SetDefault("cf_zones", "")

0 commit comments

Comments
 (0)