Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GANDI: Gandi v5 auth changes #2726

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 47 additions & 7 deletions documentation/providers/gandi_v5.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
`GANDI_V5` uses the v5 API and can act as a registrar provider
or a DNS provider. It is only able to work with domains
migrated to the new LiveDNS API, which should be all domains.
API keys are assigned to particular users. Go to User Settings,
"Manage the user account and security settings", the "Security"
tab, then regenerate the "Production API key".

* API Documentation: https://api.gandi.net/docs
* API Endpoint: https://api.gandi.net/
* Sandbox API Documentation: https://api.sandbox.gandi.net/docs/
* Sandbox API Endpoint: https://api.sandbox.gandi.net/

## Configuration

To use this provider, add an entry to `creds.json` with `TYPE` set to `GANDI_V5`
along your Gandi.net API key. The [sharing_id](https://api.gandi.net/docs/reference/) is optional.
along with other settings:

The `sharing_id` selects between different organizations which your account is
* (mandatory, string) your Gandi.net access credentials (see below) - one of:
* `token`: Personal Access Token (PAT)
* `apikey` API Key (deprecated)
* `apiurl`: (optional, string) the endpoint of the API. When empty or absent the production
endpoint is used (default) ; you can use it to select the Sandbox API Endpoint instead.
* `sharing_id`: (optional, string) let you scope to a specific organization. When empty or absent
calls are not scoped to a specific organization.

When both `token` and `apikey` are defined, the priority is given to `token` which will
be used for API communication (as if `apikey` was not set).
See [the Authentication section](#authentication) for details on obtaining these credentials.


The [sharing_id](https://api.gandi.net/docs/reference/#Sharing-ID) selects between different organizations which your account is
a member of; to manage domains in multiple organizations, you can use multiple
`creds.json` entries.

Expand All @@ -33,13 +45,32 @@ Example:
{
"gandi": {
"TYPE": "GANDI_V5",
"apikey": "your-gandi-key",
"token": "your-gandi-personal-access-token",
"sharing_id": "your-sharing_id"
}
}
```
{% endcode %}

## Authentication

(Cf [official documentation of the API](https://api.gandi.net/docs/authentication/)
The **Personal Access Token** (PAT) is configured in the [Account Settings of the
Gandi Admin application](https://admin.gandi.net/organizations/account/pat), then
click on "Create a token" button.
Choose an organisation (if your account happens to have multiple ones).
Then, choose a name (limited to 42 chars), an expiration date.
You can choose to limit the scope to a select number of products (domain names).
Finally, choose the permissions : the needed one is "Manage domain name technical configurations"
(in French: "Gérer la configuration technique des domaines"), which automatically
implies "See and renew domain names" (in French: "Voir et renouveler les domaines").
You then have only one (1) chance to copy and save the token somewhere.

The **API Key** is the previous (deprecated) mechanism used to do api calls.
To generate or delete your API key, go to User Settings,
"Manage the user account and security settings", the "Authentication options"
tab, then regenerate the "Production API key" under "Developer access"

## Metadata
This provider does not recognize any special metadata fields unique to Gandi.

Expand Down Expand Up @@ -80,7 +111,7 @@ If a domain does not exist in your Gandi account, DNSControl will *not* automati
Error getting corrections: 401: The server could not verify that you authorized to access the document you requested. Either you supplied the wrong credentials (e.g., bad api key), or your access token has expired
```

This is the error you'll see if your `apikey` in `creds.json` is wrong or invalid.
This is the error you'll see if your `token` (or (deprecated) `apikey`) in `creds.json` is wrong or invalid.

#### Domain does not exist in profile

Expand All @@ -97,3 +128,12 @@ If a `dnscontrol get-zones --format=nameonly CredId - all` returns nothing,
this is usually because your `creds.json` information is pointing at an empty
organization or no organization. The solution is to set `sharing_id` in
`creds.json`.


## Development

### Debugging
Set `GANDI_V5_DEBUG` environment variable to a [boolean-compatible](https://pkg.go.dev/strconv#ParseBool) value to dump all API calls made by this provider.

### Testing
Set `apiurl` key to the endpoint url for the sandbox (https://api.sandbox.gandi.net/), along with corresponding `token` (or (deprecated) `apikey`) created in this sandbox environment (Cf https://api.sandbox.gandi.net/docs/sandbox/) to make all API calls against Gandi sandbox environment.
3 changes: 2 additions & 1 deletion integrationTest/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@
"GANDI_V5": {
"TYPE": "GANDI_V5",
"apikey": "$GANDI_V5_APIKEY",
"domain": "$GANDI_V5_DOMAIN"
"domain": "$GANDI_V5_DOMAIN",
"token": "$GANDI_V5_TOKEN"
},
"GCLOUD": {
"TYPE": "GCLOUD",
Expand Down
54 changes: 29 additions & 25 deletions providers/gandiv5/gandi_v5Provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/StackExchange/dnscontrol/v4/providers"
"github.com/go-gandi/go-gandi"
"github.com/go-gandi/go-gandi/config"
"github.com/go-gandi/go-gandi/livedns"
"github.com/miekg/dns/dnsutil"
)

Expand Down Expand Up @@ -67,8 +68,10 @@ var features = providers.DocumentationNotes{
// gandiv5Provider is the gandiv5Provider handle used to store any client-related state.
type gandiv5Provider struct {
apikey string
token string
sharingid string
debug bool
apiurl string
}

// newDsp generates a DNS Service Provider client handle.
Expand All @@ -85,10 +88,12 @@ func newReg(conf map[string]string) (providers.Registrar, error) {
func newHelper(m map[string]string, metadata json.RawMessage) (*gandiv5Provider, error) {
api := &gandiv5Provider{}
api.apikey = m["apikey"]
if api.apikey == "" {
return nil, fmt.Errorf("missing Gandi apikey")
api.token = m["token"]
if (api.apikey == "") && (api.token == "") {
return nil, fmt.Errorf("missing Gandi personal access token (or apikey - deprecated)")
}
api.sharingid = m["sharing_id"]
api.apiurl = m["apiurl"]
debug, err := strconv.ParseBool(os.Getenv("GANDI_V5_DEBUG"))
if err == nil {
api.debug = debug
Expand All @@ -99,15 +104,24 @@ func newHelper(m map[string]string, metadata json.RawMessage) (*gandiv5Provider,

// Section 3: Domain Service Provider (DSP) related functions

// newLiveDNSClient returns a client to the Gandi Domains API
// It expects an API key, available from https://account.gandi.net/en/
func newLiveDNSClient(client *gandiv5Provider) *livedns.LiveDNS {
g := gandi.NewLiveDNSClient(config.Config{
APIKey: client.apikey,
PersonalAccessToken: client.token,
SharingID: client.sharingid,
Debug: client.debug,
APIURL: client.apiurl,
})
return g
}

// // ListZones lists the zones on this account.
// This no longer works. Until we can figure out why, we're removing this
// feature for Gandi.
// func (client *gandiv5Provider) ListZones() ([]string, error) {
// g := gandi.NewLiveDNSClient(config.Config{
// APIKey: client.apikey,
// SharingID: client.sharingid,
// Debug: client.debug,
// })
// g := newLiveDNSClient(client)

// listResp, err := g.ListDomains()
// if err != nil {
Expand All @@ -128,11 +142,7 @@ func newHelper(m map[string]string, metadata json.RawMessage) (*gandiv5Provider,
// GetZoneRecords gathers the DNS records and converts them to
// dnscontrol's format.
func (client *gandiv5Provider) GetZoneRecords(domain string, meta map[string]string) (models.Records, error) {
g := gandi.NewLiveDNSClient(config.Config{
APIKey: client.apikey,
SharingID: client.sharingid,
Debug: client.debug,
})
g := newLiveDNSClient(client)

// Get all the existing records:
records, err := g.GetDomainRecords(domain)
Expand Down Expand Up @@ -195,11 +205,7 @@ func (client *gandiv5Provider) GetZoneRecordsCorrections(dc *models.DomainConfig

PrepDesiredRecords(dc)

g := gandi.NewLiveDNSClient(config.Config{
APIKey: client.apikey,
SharingID: client.sharingid,
Debug: client.debug,
})
g := newLiveDNSClient(client)

// Gandi is a "ByLabel" API with the odd exception that changes must be
// done one label:rtype at a time.
Expand Down Expand Up @@ -296,11 +302,7 @@ func debugRecords(note string, recs []*models.RecordConfig) {

// GetNameservers returns a list of nameservers for domain.
func (client *gandiv5Provider) GetNameservers(domain string) ([]*models.Nameserver, error) {
g := gandi.NewLiveDNSClient(config.Config{
APIKey: client.apikey,
SharingID: client.sharingid,
Debug: client.debug,
})
g := newLiveDNSClient(client)
nameservers, err := g.GetDomainNS(domain)
if err != nil {
return nil, err
Expand All @@ -311,9 +313,11 @@ func (client *gandiv5Provider) GetNameservers(domain string) ([]*models.Nameserv
// GetRegistrarCorrections returns a list of corrections for this registrar.
func (client *gandiv5Provider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
gd := gandi.NewDomainClient(config.Config{
APIKey: client.apikey,
SharingID: client.sharingid,
Debug: client.debug,
APIKey: client.apikey,
PersonalAccessToken: client.token,
SharingID: client.sharingid,
Debug: client.debug,
APIURL: client.apiurl,
})

existingNs, err := gd.GetNameServers(dc.Name)
Expand Down