Skip to content

Commit c19b489

Browse files
committed
Split API key into two parts per libdns#4
1 parent 5c8f490 commit c19b489

File tree

3 files changed

+54
-16
lines changed

3 files changed

+54
-16
lines changed

README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ This package implements the [libdns interfaces](https://github.com/libdns/libdns
99

1010
This package supports API **token** authentication.
1111

12-
You will need to create a token with the following permissions:
12+
You will need to create two tokens with the following permissions:
1313

14-
- Zone / Zone / Read
15-
- Zone / DNS / Edit
14+
1. Zone / Zone / Read
15+
2. Applicable Zone / DNS / Edit
1616

1717
The first permission is needed to get the zone ID, and the second permission is obviously necessary to edit the DNS records. If you're only using the `GetRecords()` method, you can change the second permission to Read to guarantee no changes will be made.
1818

19+
By using two API keys it allows for having a DNS Edit key which only allows editing a single Zone.
20+
1921
To clarify, do NOT use API keys, which are globally-scoped:
2022

2123
![Don't use API keys](https://user-images.githubusercontent.com/1128849/81196485-556aca00-8f7c-11ea-9e13-c6a8a966f689.png)

client.go

+37-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func (p *Provider) createRecord(ctx context.Context, zoneInfo cfZone, record lib
2929
req.Header.Set("Content-Type", "application/json")
3030

3131
var result cfDNSRecord
32-
_, err = p.doAPIRequest(req, &result)
32+
_, err = p.doDNSAPIRequest(req, &result)
3333
if err != nil {
3434
return cfDNSRecord{}, err
3535
}
@@ -54,7 +54,7 @@ func (p *Provider) updateRecord(ctx context.Context, oldRec, newRec cfDNSRecord)
5454
req.Header.Set("Content-Type", "application/json")
5555

5656
var result cfDNSRecord
57-
_, err = p.doAPIRequest(req, &result)
57+
_, err = p.doDNSAPIRequest(req, &result)
5858
return result, err
5959
}
6060

@@ -73,7 +73,7 @@ func (p *Provider) getDNSRecords(ctx context.Context, zoneInfo cfZone, rec libdn
7373
}
7474

7575
var results []cfDNSRecord
76-
_, err = p.doAPIRequest(req, &results)
76+
_, err = p.doDNSAPIRequest(req, &results)
7777
return results, err
7878
}
7979

@@ -99,7 +99,7 @@ func (p *Provider) getZoneInfo(ctx context.Context, zoneName string) (cfZone, er
9999
}
100100

101101
var zones []cfZone
102-
_, err = p.doAPIRequest(req, &zones)
102+
_, err = p.doZoneAPIRequest(req, &zones)
103103
if err != nil {
104104
return cfZone{}, err
105105
}
@@ -113,13 +113,43 @@ func (p *Provider) getZoneInfo(ctx context.Context, zoneName string) (cfZone, er
113113
return zones[0], nil
114114
}
115115

116-
// doAPIRequest authenticates the request req and does the round trip. It returns
117-
// the decoded response from Cloudflare if successful; otherwise it returns an
116+
func (p *Provider) doZoneAPIRequest(req *http.Request, result interface{}) (cfResponse, error) {
117+
var token string
118+
if p.APIToken != "" {
119+
token = p.APIToken
120+
} else if p.ZoneToken != "" {
121+
token = p.ZoneToken
122+
} else {
123+
return cfResponse{}, fmt.Errorf("you must set either api_token or zone_token")
124+
}
125+
126+
req.Header.Set("Authorization", "Bearer " + token)
127+
128+
return p.doAPIRequest(req, result)
129+
}
130+
131+
func (p *Provider) doDNSAPIRequest(req *http.Request, result interface{}) (cfResponse, error) {
132+
var token string
133+
if p.APIToken != "" {
134+
token = p.APIToken
135+
} else if p.DNSToken != "" {
136+
token = p.DNSToken
137+
} else {
138+
return cfResponse{}, fmt.Errorf("you must set either api_token or dns_token")
139+
}
140+
141+
req.Header.Set("Authorization", "Bearer " + token)
142+
143+
return p.doAPIRequest(req, result)
144+
}
145+
146+
// doAPIRequest does the round trip, using the authentication set in the above functions.
147+
// It returns the decoded response from Cloudflare if successful; otherwise it returns an
118148
// error including error information from the API if applicable. If result is a
119149
// non-nil pointer, the result field from the API response will be decoded into
120150
// it for convenience.
121151
func (p *Provider) doAPIRequest(req *http.Request, result interface{}) (cfResponse, error) {
122-
req.Header.Set("Authorization", "Bearer "+p.APIToken)
152+
123153

124154
resp, err := http.DefaultClient.Do(req)
125155
if err != nil {

provider.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ import (
1212
// Provider implements the libdns interfaces for Cloudflare.
1313
// TODO: Support pagination and retries, handle rate limits.
1414
type Provider struct {
15-
// API token is used for authentication. Make sure to use a
16-
// scoped API **token**, NOT a global API **key**. It will
17-
// need two permissions: Zone-Zone-Read and Zone-DNS-Edit,
18-
// unless you are only using `GetRecords()`, in which case
19-
// the second can be changed to Read.
20-
APIToken string `json:"api_token,omitempty"`
15+
// API tokens are used for authentication. Make sure to use
16+
// two scoped API **tokens**, NOT a global API **key**.
17+
// Key 1:
18+
// - Zone-Zone-Read (All Zones)
19+
// Key 2:
20+
// - Zone-DNS-Edit (Relevant Zone)
21+
// However, if you are only using `GetRecords()`, in which
22+
// case the second can be changed to Read.
23+
// This prevents having a single key which can edit ALL Zones
24+
APIToken string `json:"api_token,omitempty"` // Backwards compatibility
25+
ZoneToken string `json:"zone_token,omitempty"`
26+
DNSToken string `json:"dns_token,omitempty"`
2127

2228
zones map[string]cfZone
2329
zonesMu sync.Mutex

0 commit comments

Comments
 (0)