Merge pull request #7 from Peltarion/parmus/whoami

feat: fetch account ID from DNSimple API
This commit is contained in:
Arne Diekmann 2021-03-25 10:09:27 +01:00 committed by GitHub
commit 865b72e083
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 38 additions and 27 deletions

View file

@ -10,14 +10,13 @@ A [cert-manager][2] ACME DNS01 solver webhook for [DNSimple][1].
## Quickstart
Take note of your DNSimple API token and account ID from the account settings in the automation tab. Run the following commands replacing the account ID, API token placeholders and email address:
Take note of your DNSimple API token from the account settings in the automation tab. Run the following commands replacing the API token placeholders and email address:
```bash
$ helm repo add neoskop https://charts.neoskop.dev
$ helm install cert-manager-webhook-dnsimple \
--namespace cert-manager \
--dry-run \
--set dnsimple.account='<DNSIMPLE_ACCOUNT_ID>' \
--set dnsimple.token='<DNSIMPLE_API_TOKEN>' \
--set clusterIssuer.production.enabled=true \
--set clusterIssuer.staging.enabled=true \
@ -52,7 +51,6 @@ The Helm chart accepts the following values:
| name | required | description | default value |
| ---------------------------------- | -------- | ----------------------------------------------- | --------------------------------------- |
| `dnsimple.account` | ✔️ | DNSimple Account ID | _empty_ |
| `dnsimple.token` | ✔️ | DNSimple API Token | _empty_ |
| `clusterIssuer.email` | | LetsEncrypt Admin Email | `name@example.com` |
| `clusterIssuer.production.enabled` | | Create a production `ClusterIssuer` | `false` |

View file

@ -18,7 +18,6 @@ spec:
- dns01:
webhook:
config:
account: {{ .Values.dnsimple.account | quote }}
tokenSecretRef:
key: token
name: {{ include "dnsimple-webhook.fullname" . }}

View file

@ -18,7 +18,6 @@ spec:
- dns01:
webhook:
config:
account: {{ .Values.dnsimple.account | quote }}
tokenSecretRef:
key: token
name: {{ include "dnsimple-webhook.fullname" . }}

View file

@ -12,7 +12,6 @@ certManager:
serviceAccountName: cert-manager
# logLevel: 3
dnsimple:
account: ""
token: ""
clusterIssuer:
email: name@example.com

49
main.go
View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
@ -71,7 +72,6 @@ type dnsimpleDNSProviderConfig struct {
// These fields will be set by users in the
// `issuer.spec.acme.dns01.providers.webhook.config` field.
Account string `json:"account"`
TokenSecretRef cmmeta.SecretKeySelector `json:"tokenSecretRef"`
}
@ -118,15 +118,15 @@ func (c *dnsimpleDNSProviderSolver) getDomainAndEntry(ch *v1alpha1.ChallengeRequ
return entry, domain
}
func (c *dnsimpleDNSProviderSolver) getExistingRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, zoneName string, entry string, key string) (*dnsimple.ZoneRecord, error) {
zone, err := client.Zones.GetZone(context.Background(), cfg.Account, zoneName)
func (c *dnsimpleDNSProviderSolver) getExistingRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, accountID string, zoneName string, entry string, key string) (*dnsimple.ZoneRecord, error) {
zone, err := client.Zones.GetZone(context.Background(), accountID, zoneName)
if err != nil {
return nil, fmt.Errorf("unable to get zone: %s", err)
}
// Look for existing TXT records.
records, err := client.Zones.ListRecords(context.Background(), cfg.Account, zone.Data.Name, &dnsimple.ZoneRecordListOptions{Type: dnsimple.String("TXT"), Name: dnsimple.String(entry)})
records, err := client.Zones.ListRecords(context.Background(), accountID, zone.Data.Name, &dnsimple.ZoneRecordListOptions{Type: dnsimple.String("TXT"), Name: dnsimple.String(entry)})
if err != nil {
return nil, fmt.Errorf("unable to get resource records: %s", err)
@ -141,9 +141,9 @@ func (c *dnsimpleDNSProviderSolver) getExistingRecord(cfg *dnsimpleDNSProviderCo
return nil, nil
}
func (c *dnsimpleDNSProviderSolver) updateRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, record *dnsimple.ZoneRecord, key string) (*dnsimple.ZoneRecord, error) {
func (c *dnsimpleDNSProviderSolver) updateRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, accountID string, record *dnsimple.ZoneRecord, key string) (*dnsimple.ZoneRecord, error) {
attributes := dnsimple.ZoneRecordAttributes{Content: key}
updatedRecord, err := client.Zones.UpdateRecord(context.Background(), cfg.Account, record.ZoneID, record.ID, attributes)
updatedRecord, err := client.Zones.UpdateRecord(context.Background(), accountID, record.ZoneID, record.ID, attributes)
if err != nil {
return nil, fmt.Errorf("unable to update record: %s", err)
@ -152,9 +152,9 @@ func (c *dnsimpleDNSProviderSolver) updateRecord(cfg *dnsimpleDNSProviderConfig,
return updatedRecord.Data, nil
}
func (c *dnsimpleDNSProviderSolver) createRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, entry *string, zoneName string, key string) (*dnsimple.ZoneRecord, error) {
func (c *dnsimpleDNSProviderSolver) createRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, accountID string, entry *string, zoneName string, key string) (*dnsimple.ZoneRecord, error) {
attributes := dnsimple.ZoneRecordAttributes{Name: entry, Type: "TXT", Content: key, TTL: 60}
createdRecord, err := client.Zones.CreateRecord(context.Background(), cfg.Account, zoneName, attributes)
createdRecord, err := client.Zones.CreateRecord(context.Background(), accountID, zoneName, attributes)
if err != nil {
return nil, fmt.Errorf("unable to create record: %s", err)
@ -163,8 +163,8 @@ func (c *dnsimpleDNSProviderSolver) createRecord(cfg *dnsimpleDNSProviderConfig,
return createdRecord.Data, nil
}
func (c *dnsimpleDNSProviderSolver) deleteRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, zoneName string, record *dnsimple.ZoneRecord) (*dnsimple.ZoneRecord, error) {
createdRecord, err := client.Zones.DeleteRecord(context.Background(), cfg.Account, zoneName, record.ID)
func (c *dnsimpleDNSProviderSolver) deleteRecord(cfg *dnsimpleDNSProviderConfig, client *dnsimple.Client, accountID string, zoneName string, record *dnsimple.ZoneRecord) (*dnsimple.ZoneRecord, error) {
createdRecord, err := client.Zones.DeleteRecord(context.Background(), accountID, zoneName, record.ID)
if err != nil {
return nil, fmt.Errorf("unable to delete record: %s", err)
@ -173,6 +173,15 @@ func (c *dnsimpleDNSProviderSolver) deleteRecord(cfg *dnsimpleDNSProviderConfig,
return createdRecord.Data, nil
}
func Whoami(client *dnsimple.Client) (string, error) {
whoamiResponse, err := client.Identity.Whoami(context.Background())
if err != nil {
return "", err
}
return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil
}
// Present is responsible for actually presenting the DNS record with the
// DNS provider.
// This method should tolerate being called multiple times with the same value.
@ -190,23 +199,28 @@ func (c *dnsimpleDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error
return fmt.Errorf("unable to get client: %s", err)
}
accountID, err := Whoami(client)
if err != nil {
return fmt.Errorf("unable to fetch account ID: %s", err)
}
entry, domain := c.getDomainAndEntry(ch)
klog.V(6).Infof("present for entry=%s, domain=%s", entry, domain)
existingRecord, err := c.getExistingRecord(&cfg, client, domain, entry, ch.Key)
existingRecord, err := c.getExistingRecord(&cfg, client, accountID, domain, entry, ch.Key)
if err != nil {
return fmt.Errorf("unable to find txt records: %s", err)
}
if existingRecord != nil {
_, err = c.updateRecord(&cfg, client, existingRecord, ch.Key)
_, err = c.updateRecord(&cfg, client, accountID, existingRecord, ch.Key)
if err != nil {
return fmt.Errorf("unable to update record: %s", err)
}
} else {
_, err = c.createRecord(&cfg, client, &entry, domain, ch.Key)
_, err = c.createRecord(&cfg, client, accountID, &entry, domain, ch.Key)
if err != nil {
return fmt.Errorf("unable to create record: %s", err)
@ -234,13 +248,18 @@ func (c *dnsimpleDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error
return fmt.Errorf("unable to get client: %s", err)
}
accountID, err := Whoami(client)
if err != nil {
return fmt.Errorf("unable to fetch account ID: %s", err)
}
entry, domain := c.getDomainAndEntry(ch)
klog.V(6).Infof("present for entry=%s, domain=%s", entry, domain)
existingRecord, err := c.getExistingRecord(&cfg, client, domain, entry, ch.Key)
existingRecord, err := c.getExistingRecord(&cfg, client, accountID, domain, entry, ch.Key)
if existingRecord != nil {
_, err = c.deleteRecord(&cfg, client, domain, existingRecord)
_, err = c.deleteRecord(&cfg, client, accountID, domain, existingRecord)
if err != nil {
return fmt.Errorf("unable to delete record: %s", err)

View file

@ -1,2 +1 @@
config.json
dnsimple-token.yaml

View file

@ -1,10 +1,9 @@
# Solver testdata directory
Copy the example files removing the `.example` suffix:
Copy the `dnsimple-token.yaml.example` example file removing the `.example` suffix:
```bash
$ cp config.json{.example,}
$ cp dnsimple-token.yaml{.example,}
```
Replace the placeholders for the account ID in `config.json` and for the API token in `dnsimple-token.yaml`. Both can be found/generate in you DNSimple account settings in the automation tab.
Replace the placeholders for the API token in `dnsimple-token.yaml`. The API token can be generated in your DNSimple account settings in the automation tab.

View file

@ -2,6 +2,5 @@
"tokenSecretRef": {
"name": "dnsimple-token",
"key": "token"
},
"account": "<Account-ID>"
}
}