Skip to content

Commit

Permalink
feat(config): accept CLOUDFLARE_* and all compatible token settings
Browse files Browse the repository at this point in the history
  • Loading branch information
favonia committed Sep 27, 2024
1 parent 9307c28 commit f408d31
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 38 deletions.
132 changes: 108 additions & 24 deletions internal/config/env_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,132 @@ import (

var oauthBearerRegex = regexp.MustCompile(`^[-a-zA-Z0-9._~+/]+=*$`)

func readAuthToken(ppfmt pp.PP) (string, bool) {
var (
token = Getenv("CF_API_TOKEN")
tokenFile = Getenv("CF_API_TOKEN_FILE")
)
var ok bool
// Keys of environment variables.
const (
TokenKey1 string = "CLOUDFLARE_API_TOKEN" //nolint:gosec
TokenKey2 string = "CF_API_TOKEN" //nolint:gosec
TokenFileKey1 string = "CLOUDFLARE_API_TOKEN_FILE" //nolint:gosec
TokenFileKey2 string = "CF_API_TOKEN_FILE" //nolint:gosec
)

// HintAuthTokenNewPrefix contains the hint about the transition from
// CF_* to CLOUDFLARE_*.
const HintAuthTokenNewPrefix string = "Cloudflare is transitioning its tools to use the prefix CLOUDFLARE instead of CF. To align with this change, it is recommended to use CLOUDFLARE_API_TOKEN (or CLOUDFLARE_API_TOKEN_FILE) instead of CF_API_TOKEN (or CF_API_TOKEN_FILE) moving forward. All options will be fully supported until version 2.0." //nolint:lll,gosec

// foolproof checks
func readPlainAuthTokens(ppfmt pp.PP) (string, string, bool) {
token1 := Getenv(TokenKey1)
token2 := Getenv(TokenKey2)

var token, tokenKey string
switch {
case token1 == "" && token2 == "":
return "", "", true
case token1 != "" && token2 != "" && token1 != token2:
ppfmt.Noticef(pp.EmojiUserError, "The values of %s and %s do not match", TokenKey1, TokenKey2)
return "", "", false
case token1 != "":
token, tokenKey = token1, TokenKey1
case token2 != "":
ppfmt.Hintf(pp.HintAuthTokenNewPrefix, HintAuthTokenNewPrefix)
token, tokenKey = token2, TokenKey2
}

// foolproof check: the sample value in README
if token == "YOUR-CLOUDFLARE-API-TOKEN" {
ppfmt.Noticef(pp.EmojiUserError, "You need to provide a real API token as CF_API_TOKEN")
ppfmt.Noticef(pp.EmojiUserError, "You need to provide a real API token as %s", tokenKey)
return "", "", false
}

return token, tokenKey, true
}

func readAuthTokenFile(ppfmt pp.PP, key string) (string, bool) {
tokenFile := Getenv(key)
if tokenFile == "" {
return "", true
}

token, ok := file.ReadString(ppfmt, tokenFile)
if !ok {
return "", false
}

if token == "" {
ppfmt.Noticef(pp.EmojiUserError, "The file specified by %s does not contain an API token", key)
return "", false
}

return token, true
}

func readAuthTokenFiles(ppfmt pp.PP) (string, string, bool) {
token1, ok := readAuthTokenFile(ppfmt, TokenFileKey1)
if !ok {
return "", "", false
}

token2, ok := readAuthTokenFile(ppfmt, TokenFileKey2)
if !ok {
return "", "", false
}

switch {
case token1 != "" && token2 != "" && token1 != token2:
ppfmt.Noticef(pp.EmojiUserError,
"The files specified by %s and %s contain conflicting tokens", TokenFileKey1, TokenFileKey2)
return "", "", false
case token1 != "":
return token1, TokenFileKey1, true
case token2 != "":
ppfmt.Hintf(pp.HintAuthTokenNewPrefix, HintAuthTokenNewPrefix)
return token2, TokenFileKey2, true
default:
return "", "", true
}
}

func readAuthToken(ppfmt pp.PP) (string, bool) {
//default:
// ppfmt.Noticef(pp.EmojiUserError, "Needs either CF_API_TOKEN or CF_API_TOKEN_FILE")
// return "", false
//}

tokenPlain, tokenPlainKey, ok := readPlainAuthTokens(ppfmt)
if !ok {
return "", false
}

tokenFile, tokenFileKey, ok := readAuthTokenFiles(ppfmt)
if !ok {
return "", false
}

var token string
switch {
case token != "" && tokenFile != "":
ppfmt.Noticef(pp.EmojiUserError, "Cannot have both CF_API_TOKEN and CF_API_TOKEN_FILE set")
case tokenPlain != "" && tokenFile != "" && tokenPlain != tokenFile:
ppfmt.Noticef(pp.EmojiUserError,
"The value of %s does not match the token found in the file specified by %s", tokenPlainKey, tokenFileKey)
return "", false
case token != "":
case tokenPlain != "":
token = tokenPlain
case tokenFile != "":
token, ok = file.ReadString(ppfmt, tokenFile)
if !ok {
return "", false
}

if token == "" {
ppfmt.Noticef(pp.EmojiUserError, "The token in the file specified by CF_API_TOKEN_FILE is empty")
return "", false
}
token = tokenFile
default:
ppfmt.Noticef(pp.EmojiUserError, "Needs either CF_API_TOKEN or CF_API_TOKEN_FILE")
ppfmt.Noticef(pp.EmojiUserError, "Needs either %s or %s", TokenKey1, TokenFileKey1)
return "", false
}

if !oauthBearerRegex.MatchString(token) {
ppfmt.Noticef(pp.EmojiUserWarning, "The API token does not look like a valid OAuth2 bearer token")
ppfmt.Noticef(pp.EmojiUserWarning,
"The provided API token appears to be invalid. "+
"It does not follow the OAuth2 bearer token format.")
}

return token, true
}

// ReadAuth reads environment variables CF_API_TOKEN, CF_API_TOKEN_FILE, and CF_ACCOUNT_ID
// and creates an [api.CloudflareAuth].
// ReadAuth reads environment variables CLOUDFLARE_API_TOKEN, CLOUDFLARE_API_TOKEN_FILE,
// CF_API_TOKEN, CF_API_TOKEN_FILE, and CF_ACCOUNT_ID and creates an [api.CloudflareAuth].
func ReadAuth(ppfmt pp.PP, field *api.Auth) bool {
token, ok := readAuthToken(ppfmt)
if !ok {
Expand Down
29 changes: 15 additions & 14 deletions internal/pp/hint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ type Hint int

// All the registered hints.
const (
HintUpdateDockerTemplate Hint = iota
HintIP4DetectionFails
HintIP6DetectionFails
HintIP4MappedIP6Address
HintDetectionTimeouts
HintUpdateTimeouts
HintRecordPermission
HintWAFListPermission
HintMismatchedRecordAttributes
HintMismatchedWAFListAttributes
Hint1111Blockage
HintExperimentalShoutrrr // introduced in 1.12.0
HintExperimentalWAF // introduced in 1.14.0
HintExperimentalLocalWithInterface // introduced in 1.15.0
HintUpdateDockerTemplate Hint = iota // PUID or PGID was used
HintAuthTokenNewPrefix // "CF_*" to "CLOUDFLARE_*"
HintIP4DetectionFails // How to turn off IPv4
HintIP6DetectionFails // How to set up IPv6 or turn it off
HintIP4MappedIP6Address // IPv4-mapped IPv6 addresses are bad for AAAA records
HintDetectionTimeouts // Longer detection timeout
HintUpdateTimeouts // Longer update timeout
HintRecordPermission // Permissions to update DNS tokens
HintWAFListPermission // Permissions to update WAF lists
HintMismatchedRecordAttributes // Attributes of DNS records have been changed
HintMismatchedWAFListAttributes // Attributes of WAF lists have been changed
Hint1111Blockage // 1.1.1.1 is blocked
HintExperimentalShoutrrr // New feature introduced in 1.12.0 on 2024/6/28
HintExperimentalWAF // New feature introduced in 1.14.0 on 2024/8/25
HintExperimentalLocalWithInterface // New feature introduced in 1.15.0
)

0 comments on commit f408d31

Please sign in to comment.