Skip to content

Commit

Permalink
Add Cloudflare access token auth
Browse files Browse the repository at this point in the history
  • Loading branch information
alexrsagen committed Jun 12, 2023
1 parent d577fd6 commit 6007f0d
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 25 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ Info/debug-level output may be obtained by passing the `-verbose` flag.

Cleanup mode is set by passing the `-cleanup` flag. This should be used in the `--manual-cleanup-hook` of certbot.

### Example
### Example (API user key auth)
```
CF_API_EMAIL="[email protected]" CF_API_KEY="xxxxx" /opt/certbot/certbot-auto renew --server https://acme-v02.api.letsencrypt.org/directory --manual --manual-auth-hook="/path/to/go-certbot-cloudflare" --manual-cleanup-hook="/path/to/go-certbot-cloudflare -cleanup" --manual-public-ip-logging-ok --preferred-challenges dns
```

### Example (API access token auth)
```
CF_ACCESS_TOKEN="xxxxx" /opt/certbot/certbot-auto renew --server https://acme-v02.api.letsencrypt.org/directory --manual --manual-auth-hook="/path/to/go-certbot-cloudflare" --manual-cleanup-hook="/path/to/go-certbot-cloudflare -cleanup" --manual-public-ip-logging-ok --preferred-challenges dns
```
30 changes: 21 additions & 9 deletions cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ type cfCreateRecordResponse struct {
Result cfResponseRecord `json:"result"`
}

func cfGet(apiEmail, apiKey, urlExt string, v url.Values) (*http.Response, error) {
func cfGet(apiAccessToken, apiEmail, apiKey, urlExt string, v url.Values) (*http.Response, error) {
base, err := url.Parse(cfAPIBase)
if err != nil {
return nil, err
Expand All @@ -128,12 +128,16 @@ func cfGet(apiEmail, apiKey, urlExt string, v url.Values) (*http.Response, error
req.URL.RawQuery = v.Encode()
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Auth-Email", apiEmail)
req.Header.Set("X-Auth-Key", apiKey)
if apiAccessToken != "" {
req.Header.Set("Authorization", "Bearer "+apiAccessToken)
} else {
req.Header.Set("X-Auth-Email", apiEmail)
req.Header.Set("X-Auth-Key", apiKey)
}
return http.DefaultClient.Do(req)
}

func cfDelete(apiEmail, apiKey, urlExt string, v url.Values) (*http.Response, error) {
func cfDelete(apiAccessToken, apiEmail, apiKey, urlExt string, v url.Values) (*http.Response, error) {
base, err := url.Parse(cfAPIBase)
if err != nil {
return nil, err
Expand All @@ -150,12 +154,16 @@ func cfDelete(apiEmail, apiKey, urlExt string, v url.Values) (*http.Response, er
req.URL.RawQuery = v.Encode()
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Auth-Email", apiEmail)
req.Header.Set("X-Auth-Key", apiKey)
if apiAccessToken != "" {
req.Header.Set("Authorization", "Bearer "+apiAccessToken)
} else {
req.Header.Set("X-Auth-Email", apiEmail)
req.Header.Set("X-Auth-Key", apiKey)
}
return http.DefaultClient.Do(req)
}

func cfPostJSON(apiEmail, apiKey, urlExt string, v interface{}) (*http.Response, error) {
func cfPostJSON(apiAccessToken, apiEmail, apiKey, urlExt string, v interface{}) (*http.Response, error) {
base, err := url.Parse(cfAPIBase)
if err != nil {
return nil, err
Expand All @@ -173,7 +181,11 @@ func cfPostJSON(apiEmail, apiKey, urlExt string, v interface{}) (*http.Response,
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Auth-Email", apiEmail)
req.Header.Set("X-Auth-Key", apiKey)
if apiAccessToken != "" {
req.Header.Set("Authorization", "Bearer "+apiAccessToken)
} else {
req.Header.Set("X-Auth-Email", apiEmail)
req.Header.Set("X-Auth-Key", apiKey)
}
return http.DefaultClient.Do(req)
}
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module main

go 1.20

require github.com/go-ini/ini v1.67.0

require github.com/stretchr/testify v1.8.4 // indirect
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
42 changes: 27 additions & 15 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ func main() {
fmt.Println("[error] Environment variable CERTBOT_VALIDATION not set")
return
}
cfAPIAccessToken, ok := os.LookupEnv("CF_API_ACCESS_TOKEN")
if !ok && *verbose {
fmt.Println("[warning] Environment variable CF_API_ACCESS_TOKEN not set (this is OK if CF_API_EMAIL / CF_API_KEY is set or renew config contains auth)")
}
cfAPIEmail, ok := os.LookupEnv("CF_API_EMAIL")
if !ok && *verbose {
fmt.Println("[warning] Environment variable CF_API_EMAIL not set, now depending on renew config")
fmt.Println("[warning] Environment variable CF_API_EMAIL not set (this is OK if CF_API_ACCESS_TOKEN is set or renew config contains auth)")
}
cfAPIKey, ok := os.LookupEnv("CF_API_KEY")
if !ok && *verbose {
fmt.Println("[warning] Environment variable CF_API_KEY not set, now depending on renew config")
fmt.Println("[warning] Environment variable CF_API_KEY not set (this is OK if CF_API_ACCESS_TOKEN is set or renew config contains auth)")
}

// Get renewal file path
Expand Down Expand Up @@ -82,24 +86,32 @@ func main() {
fmt.Printf("[error] Could not find section \"go-certbot-cloudflare\" in file \"%s\"\n", renewFilePath)
return
}
if cfAPIAccessToken == "" {
keyAPIAccessToken := section.Key("cf_api_access_token")
if keyAPIAccessToken != nil {
cfAPIAccessToken = keyAPIAccessToken.String()
} else if *verbose {
fmt.Printf("[warning] Could not find key \"cf_api_access_token\" under section \"go-certbot-cloudflare\" in file \"%s\" (this is OK if cf_api_email / cf_api_key is set or environment variables provide auth)\n", renewFilePath)
}
}
if cfAPIEmail == "" {
keyAPIEmail := section.Key("cf_api_email")
if keyAPIEmail == nil {
fmt.Printf("[error] Could not find key \"cf_api_email\" under section \"go-certbot-cloudflare\" in file \"%s\"\n", renewFilePath)
return
if keyAPIEmail != nil {
cfAPIEmail = keyAPIEmail.String()
} else if *verbose {
fmt.Printf("[warning] Could not find key \"cf_api_email\" under section \"go-certbot-cloudflare\" in file \"%s\" (this is OK if cf_api_access_token is set or environment variables provide auth)\n", renewFilePath)
}
cfAPIEmail = keyAPIEmail.String()
}
if cfAPIKey == "" {
keyAPIKey := section.Key("cf_api_key")
if keyAPIKey == nil {
fmt.Printf("[error] Could not find key \"cf_api_key\" under section \"go-certbot-cloudflare\" in file \"%s\"\n", renewFilePath)
return
if keyAPIKey != nil {
cfAPIKey = keyAPIKey.String()
} else if *verbose {
fmt.Printf("[warning] Could not find key \"cf_api_key\" under section \"go-certbot-cloudflare\" in file \"%s\" (this is OK if cf_api_access_token is set or environment variables provide auth)\n", renewFilePath)
}
cfAPIKey = keyAPIKey.String()
}
}
if cfAPIEmail == "" || cfAPIKey == "" {
if cfAPIAccessToken == "" && (cfAPIEmail == "" || cfAPIKey == "") {
fmt.Println("[error] Cloudflare email or API key is empty")
return
}
Expand All @@ -111,7 +123,7 @@ func main() {
if *verbose {
fmt.Printf("[info] Looking up zone %s in Cloudflare account\n", zoneDomain)
}
httpRes, err := cfGet(cfAPIEmail, cfAPIKey, "zones", url.Values{
httpRes, err := cfGet(cfAPIAccessToken, cfAPIEmail, cfAPIKey, "zones", url.Values{
"name": []string{zoneDomain},
"status": []string{"active"},
"page": []string{"1"},
Expand Down Expand Up @@ -167,7 +179,7 @@ func main() {
if *verbose {
fmt.Println("[info] Looking up DNS ACME challenge records in Cloudflare zone")
}
httpRes, err := cfGet(cfAPIEmail, cfAPIKey, "zones/"+zonesRes.Result[0].ID+"/dns_records", url.Values{
httpRes, err := cfGet(cfAPIAccessToken, cfAPIEmail, cfAPIKey, "zones/"+zonesRes.Result[0].ID+"/dns_records", url.Values{
"type": []string{"TXT"},
"name": []string{subdomain},
"page": []string{"1"},
Expand Down Expand Up @@ -195,7 +207,7 @@ func main() {
if *verbose {
fmt.Printf("[info] Deleting challenge record TXT %s: \"%s\"\n", recordsRes.Result[i].Name, recordsRes.Result[i].Content)
}
httpRes, err := cfDelete(cfAPIEmail, cfAPIKey, "zones/"+zonesRes.Result[0].ID+"/dns_records/"+recordsRes.Result[i].ID, nil)
httpRes, err := cfDelete(cfAPIAccessToken, cfAPIEmail, cfAPIKey, "zones/"+zonesRes.Result[0].ID+"/dns_records/"+recordsRes.Result[i].ID, nil)
if err != nil {
fmt.Printf("[error] Cloudflare request failed\n%v\n", err)
return
Expand Down Expand Up @@ -261,7 +273,7 @@ func main() {
fmt.Println("[info] Challenge record not found on domain")
fmt.Printf("[info] Creating TXT record %s with content \"%s\"\n", subdomain, vt)
}
httpRes, err := cfPostJSON(cfAPIEmail, cfAPIKey, "zones/"+zonesRes.Result[0].ID+"/dns_records", &cfCreateDNSRecord{
httpRes, err := cfPostJSON(cfAPIAccessToken, cfAPIEmail, cfAPIKey, "zones/"+zonesRes.Result[0].ID+"/dns_records", &cfCreateDNSRecord{
Type: "TXT",
Name: subdomain,
Content: vt,
Expand Down

0 comments on commit 6007f0d

Please sign in to comment.