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

Add support for using Local UniFi Api Keys #85

Merged
merged 11 commits into from
Jan 10, 2025
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@

## ⛵ Deployment

1. Create a local user with a password in your UniFi OS, this user only needs read/write access to the UniFi Network appliance.
1. Open your UniFi Console's Network Settings and go to `Settings > Control Plane > Admins & Users`.

2. Add the ExternalDNS Helm repository to your cluster.
2a. If you are running `UniFi Network v9.0.0` or greater, you can create an `Api Key` by selecting your user, going under `Control Plane API Key` and clicking `Create New`. Set the name to whatever you want, and the expiration to whatever you feel like commiting to. You can set `UNIFI_KEY` to this key.

2b. Otherwise, create a local user with a password in your UniFi OS, this user only needs read/write access to the UniFi Network appliance.

3. Add the ExternalDNS Helm repository to your cluster.

```sh
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
```

3. Create a Kubernetes secret called `external-dns-unifi-secret` that holds `username` and `password` with their respected values from step 1.
4. Create a Kubernetes secret called `external-dns-unifi-secret` that holds `username` and `password` with their respected values from step 1.

4. Create the helm values file, for example `external-dns-unifi-values.yaml`:
5. Create the helm values file, for example `external-dns-unifi-values.yaml`:

```yaml
fullnameOverride: external-dns-unifi
Expand Down Expand Up @@ -80,7 +84,7 @@
domainFilters: ["example.com"] # replace with your domain
```

5. Install the Helm chart
6. Install the Helm chart

```sh
helm install external-dns-unifi external-dns/external-dns -f external-dns-unifi-values.yaml --version 1.14.3 -n external-dns
Expand All @@ -92,10 +96,11 @@

| Environment Variable | Description | Default Value |
|------------------------------|--------------------------------------------------------------|---------------|
| `UNIFI_KEY` | The local api key provided for your user | N/A |
kashalls marked this conversation as resolved.
Show resolved Hide resolved
| `UNIFI_USER` | Username for the Unifi Controller (must be provided). | N/A |
| `UNIFI_PASS` | Password for the Unifi Controller (must be provided). | N/A |
kashalls marked this conversation as resolved.
Show resolved Hide resolved
| `UNIFI_SKIP_TLS_VERIFY` | Whether to skip TLS verification (true or false). | `true` |
| `UNIFI_SITE` | Unifi Site Identifier (used in multi-site installations) | `default` |
| `UNIFI_PASS` | Password for the Unifi Controller (must be provided). | N/A |
| `UNIFI_HOST` | Host of the Unifi Controller (must be provided). | N/A |
| `UNIFI_EXTERNAL_CONTROLLER`* | Toggles support for non-UniFi Hardware | `false` |
| `LOG_LEVEL` | Change the verbosity of logs (used when making a bug report) | `info` |
Expand Down
48 changes: 30 additions & 18 deletions internal/unifi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,15 @@ func newUnifiClient(config *Config) (*httpClient, error) {
},
}

if config.ExternalController {
if client.Config.ExternalController {
client.ClientURLs.Login = unifiLoginPathExternal
client.ClientURLs.Records = unifiRecordPathExternal
}

if client.Config.ApiKey != "" {
return client, nil
}

if err := client.login(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -126,21 +130,25 @@ func (c *httpClient) doRequest(method, path string, body io.Reader) (*http.Respo

// If the status code is 401, re-login and retry the request
if resp.StatusCode == http.StatusUnauthorized {
log.Debug("received 401 unauthorized, attempting to re-login")
if err := c.login(); err != nil {
log.Error("re-login failed", zap.Error(err))
return nil, err
}
// Update the headers with new CSRF token
c.setHeaders(req)

// Retry the request
log.Debug("retrying request after re-login")

resp, err = c.Client.Do(req)
if err != nil {
log.Error("Retry request failed", zap.Error(err))
return nil, err
if c.Config.ApiKey == "" {
log.Debug("received 401 unauthorized, attempting to re-login")
if err := c.login(); err != nil {
log.Error("re-login failed", zap.Error(err))
return nil, err
}
// Update the headers with new CSRF token
c.setHeaders(req)

// Retry the request
log.Debug("retrying request after re-login")

resp, err = c.Client.Do(req)
if err != nil {
log.Error("Retry request failed", zap.Error(err))
return nil, err
}
} else {
log.Error("recieved 401 unauthorized, cannot attempt request again")
}
}

Expand Down Expand Up @@ -286,8 +294,12 @@ func (c *httpClient) lookupIdentifier(key, recordType string) (*DNSRecord, error

// setHeaders sets the headers for the HTTP request.
func (c *httpClient) setHeaders(req *http.Request) {
// Add the saved CSRF header.
req.Header.Set("X-CSRF-Token", c.csrf)
if c.Config.ApiKey != "" {
req.Header.Set("X-API-KEY", c.Config.ApiKey)
} else {
req.Header.Set("X-CSRF-Token", c.csrf)
}

req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json; charset=utf-8")
}
5 changes: 3 additions & 2 deletions internal/unifi/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
// Config represents the configuration for the UniFi API.
type Config struct {
Host string `env:"UNIFI_HOST,notEmpty"`
User string `env:"UNIFI_USER,notEmpty"`
Password string `env:"UNIFI_PASS,notEmpty"`
ApiKey string `env:"UNIFI_KEY" envDefault:""`
User string `env:"UNIFI_USER" envDefault:""`
Password string `env:"UNIFI_PASS" envDefault:""`
Site string `env:"UNIFI_SITE" envDefault:"default"`
ExternalController bool `env:"UNIFI_EXTERNAL_CONTROLLER" envDefault:"false"`
SkipTLSVerify bool `env:"UNIFI_SKIP_TLS_VERIFY" envDefault:"true"`
Expand Down
Loading