diff --git a/docs/tutorials/cloudflare.md b/docs/tutorials/cloudflare.md index 211dbf72c..79deffeff 100644 --- a/docs/tutorials/cloudflare.md +++ b/docs/tutorials/cloudflare.md @@ -20,6 +20,8 @@ Snippet from [Cloudflare - Getting Started](https://api.cloudflare.com/#getting- API Token will be preferred for authentication if `CF_API_TOKEN` environment variable is set. Otherwise `CF_API_KEY` and `CF_API_EMAIL` should be set to run ExternalDNS with Cloudflare. +You may provide the Cloudflare API token through a file by setting the +`CF_API_TOKEN="file:/path/to/token"`. When using API Token authentication, the token should be granted Zone `Read`, DNS `Edit` privileges, and access to `All zones`. diff --git a/provider/cloudflare/cloudflare.go b/provider/cloudflare/cloudflare.go index 0f7bdf0cc..3bfc12c0c 100644 --- a/provider/cloudflare/cloudflare.go +++ b/provider/cloudflare/cloudflare.go @@ -19,8 +19,10 @@ package cloudflare import ( "context" "fmt" + "io/ioutil" "os" "strconv" + "strings" cloudflare "github.com/cloudflare/cloudflare-go" log "github.com/sirupsen/logrus" @@ -155,7 +157,15 @@ func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter prov err error ) if os.Getenv("CF_API_TOKEN") != "" { - config, err = cloudflare.NewWithAPIToken(os.Getenv("CF_API_TOKEN")) + token := os.Getenv("CF_API_TOKEN") + if strings.HasPrefix(token, "file:") { + tokenBytes, err := ioutil.ReadFile(strings.TrimPrefix(token, "file:")) + if err != nil { + return nil, fmt.Errorf("failed to read CF_API_TOKEN from file: %v", err) + } + token = string(tokenBytes) + } + config, err = cloudflare.NewWithAPIToken(token) } else { config, err = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL")) } diff --git a/provider/cloudflare/cloudflare_test.go b/provider/cloudflare/cloudflare_test.go index 3755c8c9b..6d1d47b40 100644 --- a/provider/cloudflare/cloudflare_test.go +++ b/provider/cloudflare/cloudflare_test.go @@ -677,6 +677,23 @@ func TestCloudflareProvider(t *testing.T) { if err != nil { t.Errorf("should not fail, %s", err) } + + _ = os.Unsetenv("CF_API_TOKEN") + tokenFile := "/tmp/cf_api_token" + if err := os.WriteFile(tokenFile, []byte("abc123def"), 0644); err != nil { + t.Errorf("failed to write token file, %s", err) + } + _ = os.Setenv("CF_API_TOKEN", tokenFile) + _, err = NewCloudFlareProvider( + endpoint.NewDomainFilter([]string{"bar.com"}), + provider.NewZoneIDFilter([]string{""}), + false, + true, + 5000) + if err != nil { + t.Errorf("should not fail, %s", err) + } + _ = os.Unsetenv("CF_API_TOKEN") _ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx") _ = os.Setenv("CF_API_EMAIL", "test@test.com") @@ -689,6 +706,7 @@ func TestCloudflareProvider(t *testing.T) { if err != nil { t.Errorf("should not fail, %s", err) } + _ = os.Unsetenv("CF_API_KEY") _ = os.Unsetenv("CF_API_EMAIL") _, err = NewCloudFlareProvider(