forked from TrueCloudLab/lego
Add DNS Provider for VegaDNS (#553)
This commit is contained in:
parent
9bb5589e17
commit
3f09ecc0b1
11 changed files with 857 additions and 1 deletions
8
Gopkg.lock
generated
8
Gopkg.lock
generated
|
@ -34,6 +34,12 @@
|
||||||
revision = "fa1c0367800db75e4d10d0ec90c49a8731670224"
|
revision = "fa1c0367800db75e4d10d0ec90c49a8731670224"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/OpenDNS/vegadns2client"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "a3fa4a771d87bda2514a90a157e1fed1b6897d2e"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/akamai/AkamaiOPEN-edgegrid-golang"
|
name = "github.com/akamai/AkamaiOPEN-edgegrid-golang"
|
||||||
packages = [
|
packages = [
|
||||||
|
@ -374,6 +380,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "d93e6f0825f1f7c1c3c2ccd421af4d99b77b390f3346ca751968af0609119c96"
|
inputs-digest = "68128f1cf61f649cdac1483e18c54541782cbd344605cab3cf6c2b448a86bda0"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
1
cli.go
1
cli.go
|
@ -221,6 +221,7 @@ Here is an example bash command using the CloudFlare DNS provider:
|
||||||
fmt.Fprintln(w, "\trfc2136:\tRFC2136_TSIG_KEY, RFC2136_TSIG_SECRET,\n\t\tRFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER")
|
fmt.Fprintln(w, "\trfc2136:\tRFC2136_TSIG_KEY, RFC2136_TSIG_SECRET,\n\t\tRFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER")
|
||||||
fmt.Fprintln(w, "\troute53:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_HOSTED_ZONE_ID")
|
fmt.Fprintln(w, "\troute53:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_HOSTED_ZONE_ID")
|
||||||
fmt.Fprintln(w, "\tdyn:\tDYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD")
|
fmt.Fprintln(w, "\tdyn:\tDYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD")
|
||||||
|
fmt.Fprintln(w, "\tvegadns:\tSECRET_VEGADNS_KEY, SECRET_VEGADNS_SECRET, VEGADNS_URL")
|
||||||
fmt.Fprintln(w, "\tvultr:\tVULTR_API_KEY")
|
fmt.Fprintln(w, "\tvultr:\tVULTR_API_KEY")
|
||||||
fmt.Fprintln(w, "\tovh:\tOVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY")
|
fmt.Fprintln(w, "\tovh:\tOVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY")
|
||||||
fmt.Fprintln(w, "\tpdns:\tPDNS_API_KEY, PDNS_API_URL")
|
fmt.Fprintln(w, "\tpdns:\tPDNS_API_KEY, PDNS_API_URL")
|
||||||
|
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/xenolf/lego/providers/dns/rfc2136"
|
"github.com/xenolf/lego/providers/dns/rfc2136"
|
||||||
"github.com/xenolf/lego/providers/dns/route53"
|
"github.com/xenolf/lego/providers/dns/route53"
|
||||||
"github.com/xenolf/lego/providers/dns/sakuracloud"
|
"github.com/xenolf/lego/providers/dns/sakuracloud"
|
||||||
|
"github.com/xenolf/lego/providers/dns/vegadns"
|
||||||
"github.com/xenolf/lego/providers/dns/vultr"
|
"github.com/xenolf/lego/providers/dns/vultr"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,6 +108,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
|
||||||
return otc.NewDNSProvider()
|
return otc.NewDNSProvider()
|
||||||
case "exec":
|
case "exec":
|
||||||
return exec.NewDNSProvider()
|
return exec.NewDNSProvider()
|
||||||
|
case "vegadns":
|
||||||
|
return vegadns.NewDNSProvider()
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unrecognised DNS provider: %s", name)
|
return nil, fmt.Errorf("unrecognised DNS provider: %s", name)
|
||||||
}
|
}
|
||||||
|
|
85
providers/dns/vegadns/vegadns.go
Normal file
85
providers/dns/vegadns/vegadns.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
// Package vegadns implements a DNS provider for solving the DNS-01
|
||||||
|
// challenge using VegaDNS.
|
||||||
|
package vegadns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
vegaClient "github.com/OpenDNS/vegadns2client"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/platform/config/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSProvider describes a provider for VegaDNS
|
||||||
|
type DNSProvider struct {
|
||||||
|
client vegaClient.VegaDNSClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSProvider returns a DNSProvider instance configured for VegaDNS.
|
||||||
|
// Credentials must be passed in the environment variables:
|
||||||
|
// VEGADNS_URL, SECRET_VEGADNS_KEY, SECRET_VEGADNS_SECRET.
|
||||||
|
func NewDNSProvider() (*DNSProvider, error) {
|
||||||
|
values, err := env.Get("VEGADNS_URL")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("VegaDNS: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := os.Getenv("SECRET_VEGADNS_KEY")
|
||||||
|
secret := os.Getenv("SECRET_VEGADNS_SECRET")
|
||||||
|
|
||||||
|
return NewDNSProviderCredentials(values["VEGADNS_URL"], key, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||||
|
// DNSProvider instance configured for VegaDNS.
|
||||||
|
func NewDNSProviderCredentials(vegaDNSURL string, key string, secret string) (*DNSProvider, error) {
|
||||||
|
vega := vegaClient.NewVegaDNSClient(vegaDNSURL)
|
||||||
|
vega.APIKey = key
|
||||||
|
vega.APISecret = secret
|
||||||
|
|
||||||
|
return &DNSProvider{
|
||||||
|
client: vega,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout returns the timeout and interval to use when checking for DNS
|
||||||
|
// propagation. Adjusting here to cope with spikes in propagation times.
|
||||||
|
func (r *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
|
timeout = 12 * time.Minute
|
||||||
|
interval = 1 * time.Minute
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present creates a TXT record to fulfil the dns-01 challenge
|
||||||
|
func (r *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
|
_, domainID, err := r.client.GetAuthZone(fqdn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't find Authoritative Zone for %s in Present: %v", fqdn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.client.CreateTXT(domainID, fqdn, value, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp removes the TXT record matching the specified parameters
|
||||||
|
func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
|
_, domainID, err := r.client.GetAuthZone(fqdn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't find Authoritative Zone for %s in CleanUp: %v", fqdn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txt := strings.TrimSuffix(fqdn, ".")
|
||||||
|
|
||||||
|
recordID, err := r.client.GetRecordID(domainID, txt, "TXT")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("couldn't get Record ID in CleanUp: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.client.DeleteRecord(recordID)
|
||||||
|
}
|
391
providers/dns/vegadns/vegadns_test.go
Normal file
391
providers/dns/vegadns/vegadns_test.go
Normal file
|
@ -0,0 +1,391 @@
|
||||||
|
// Package vegadns implements a DNS provider for solving the DNS-01
|
||||||
|
// challenge using VegaDNS.
|
||||||
|
package vegadns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ipPort = "127.0.0.1:2112"
|
||||||
|
|
||||||
|
var jsonMap = map[string]string{
|
||||||
|
"token": `
|
||||||
|
{
|
||||||
|
"access_token":"699dd4ff-e381-46b8-8bf8-5de49dd56c1f",
|
||||||
|
"token_type":"bearer",
|
||||||
|
"expires_in":3600
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"domains": `
|
||||||
|
{
|
||||||
|
"domains":[
|
||||||
|
{
|
||||||
|
"domain_id":1,
|
||||||
|
"domain":"example.com",
|
||||||
|
"status":"active",
|
||||||
|
"owner_id":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"records": `
|
||||||
|
{
|
||||||
|
"status":"ok",
|
||||||
|
"total_records":2,
|
||||||
|
"domain":{
|
||||||
|
"status":"active",
|
||||||
|
"domain":"example.com",
|
||||||
|
"owner_id":0,
|
||||||
|
"domain_id":1
|
||||||
|
},
|
||||||
|
"records":[
|
||||||
|
{
|
||||||
|
"retry":"2048",
|
||||||
|
"minimum":"2560",
|
||||||
|
"refresh":"16384",
|
||||||
|
"email":"hostmaster.example.com",
|
||||||
|
"record_type":"SOA",
|
||||||
|
"expire":"1048576",
|
||||||
|
"ttl":86400,
|
||||||
|
"record_id":1,
|
||||||
|
"nameserver":"ns1.example.com",
|
||||||
|
"domain_id":1,
|
||||||
|
"serial":""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"example.com",
|
||||||
|
"value":"ns1.example.com",
|
||||||
|
"record_type":"NS",
|
||||||
|
"ttl":3600,
|
||||||
|
"record_id":2,
|
||||||
|
"location_id":null,
|
||||||
|
"domain_id":1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"_acme-challenge.example.com",
|
||||||
|
"value":"my_challenge",
|
||||||
|
"record_type":"TXT",
|
||||||
|
"ttl":3600,
|
||||||
|
"record_id":3,
|
||||||
|
"location_id":null,
|
||||||
|
"domain_id":1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"recordCreated": `
|
||||||
|
{
|
||||||
|
"status":"ok",
|
||||||
|
"record":{
|
||||||
|
"name":"_acme-challenge.example.com",
|
||||||
|
"value":"my_challenge",
|
||||||
|
"record_type":"TXT",
|
||||||
|
"ttl":3600,
|
||||||
|
"record_id":3,
|
||||||
|
"location_id":null,
|
||||||
|
"domain_id":1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"recordDeleted": `{"status": "ok"}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
type muxCallback func() *http.ServeMux
|
||||||
|
|
||||||
|
func TestVegaDNSNewDNSProviderFail(t *testing.T) {
|
||||||
|
os.Setenv("VEGADNS_URL", "")
|
||||||
|
_, err := NewDNSProvider()
|
||||||
|
assert.Error(t, err, "VEGADNS_URL env missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVegaDNSTimeoutSuccess(t *testing.T) {
|
||||||
|
ts, err := startTestServer(vegaDNSMuxSuccess)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer ts.Close()
|
||||||
|
defer os.Clearenv()
|
||||||
|
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
timeout, interval := provider.Timeout()
|
||||||
|
assert.Equal(t, timeout, time.Duration(720000000000))
|
||||||
|
assert.Equal(t, interval, time.Duration(60000000000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVegaDNSPresentSuccess(t *testing.T) {
|
||||||
|
ts, err := startTestServer(vegaDNSMuxSuccess)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer ts.Close()
|
||||||
|
defer os.Clearenv()
|
||||||
|
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.Present("example.com", "token", "keyAuth")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVegaDNSPresentFailToFindZone(t *testing.T) {
|
||||||
|
ts, err := startTestServer(vegaDNSMuxFailToFindZone)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer ts.Close()
|
||||||
|
defer os.Clearenv()
|
||||||
|
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.Present("example.com", "token", "keyAuth")
|
||||||
|
assert.EqualError(t, err, "can't find Authoritative Zone for _acme-challenge.example.com. in Present: Unable to find auth zone for fqdn _acme-challenge.example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVegaDNSPresentFailToCreateTXT(t *testing.T) {
|
||||||
|
ts, err := startTestServer(vegaDNSMuxFailToCreateTXT)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer ts.Close()
|
||||||
|
defer os.Clearenv()
|
||||||
|
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.Present("example.com", "token", "keyAuth")
|
||||||
|
assert.EqualError(t, err, "Got bad answer from VegaDNS on CreateTXT. Code: 400. Message: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVegaDNSCleanUpSuccess(t *testing.T) {
|
||||||
|
ts, err := startTestServer(vegaDNSMuxSuccess)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer ts.Close()
|
||||||
|
defer os.Clearenv()
|
||||||
|
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.CleanUp("example.com", "token", "keyAuth")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVegaDNSCleanUpFailToFindZone(t *testing.T) {
|
||||||
|
ts, err := startTestServer(vegaDNSMuxFailToFindZone)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer ts.Close()
|
||||||
|
defer os.Clearenv()
|
||||||
|
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.CleanUp("example.com", "token", "keyAuth")
|
||||||
|
assert.EqualError(t, err, "can't find Authoritative Zone for _acme-challenge.example.com. in CleanUp: Unable to find auth zone for fqdn _acme-challenge.example.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVegaDNSCleanUpFailToGetRecordID(t *testing.T) {
|
||||||
|
ts, err := startTestServer(vegaDNSMuxFailToGetRecordID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer ts.Close()
|
||||||
|
defer os.Clearenv()
|
||||||
|
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.CleanUp("example.com", "token", "keyAuth")
|
||||||
|
assert.EqualError(t, err, "couldn't get Record ID in CleanUp: Got bad answer from VegaDNS on GetRecordID. Code: 404. Message: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func vegaDNSMuxSuccess() *http.ServeMux {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/token", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["token"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/domains", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Query().Get("search") == "example.com" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["domains"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/records", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
if r.URL.Query().Get("domain_id") == "1" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["records"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
case http.MethodPost:
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
fmt.Fprintf(w, jsonMap["recordCreated"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/records/3", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodDelete:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["recordDeleted"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Printf("Not Found for Request: (%+v)\n\n", r)
|
||||||
|
})
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
func vegaDNSMuxFailToFindZone() *http.ServeMux {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/token", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["token"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/domains", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
func vegaDNSMuxFailToCreateTXT() *http.ServeMux {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/token", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["token"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/domains", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Query().Get("search") == "example.com" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["domains"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/records", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
if r.URL.Query().Get("domain_id") == "1" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["records"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
case http.MethodPost:
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
func vegaDNSMuxFailToGetRecordID() *http.ServeMux {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/token", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["token"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/domains", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Query().Get("search") == "example.com" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, jsonMap["domains"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/1.0/records", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts and returns a test server using a custom ip/port. Defer close() afterwards.
|
||||||
|
func startTestServer(callback muxCallback) (*httptest.Server, error) {
|
||||||
|
err := os.Setenv("SECRET_VEGADNS_KEY", "key")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Setenv("SECRET_VEGADNS_SECRET", "secret")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Setenv("VEGADNS_URL", "http://"+ipPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := httptest.NewUnstartedServer(callback())
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", ipPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.Listener = l
|
||||||
|
ts.Start()
|
||||||
|
|
||||||
|
return ts, nil
|
||||||
|
}
|
13
vendor/github.com/OpenDNS/vegadns2client/LICENSE
generated
vendored
Normal file
13
vendor/github.com/OpenDNS/vegadns2client/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2018, Cisco Systems, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
71
vendor/github.com/OpenDNS/vegadns2client/client.go
generated
vendored
Normal file
71
vendor/github.com/OpenDNS/vegadns2client/client.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package vegadns2client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VegaDNSClient - Struct for holding VegaDNSClient specific attributes
|
||||||
|
type VegaDNSClient struct {
|
||||||
|
client http.Client
|
||||||
|
baseurl string
|
||||||
|
version string
|
||||||
|
User string
|
||||||
|
Pass string
|
||||||
|
APIKey string
|
||||||
|
APISecret string
|
||||||
|
token Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send - Central place for sending requests
|
||||||
|
// Input: method, endpoint, params
|
||||||
|
// Output: *http.Response
|
||||||
|
func (vega *VegaDNSClient) Send(method string, endpoint string, params map[string]string) (*http.Response, error) {
|
||||||
|
vegaURL := vega.getURL(endpoint)
|
||||||
|
|
||||||
|
p := url.Values{}
|
||||||
|
for k, v := range params {
|
||||||
|
p.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var req *http.Request
|
||||||
|
|
||||||
|
if (method == "GET") || (method == "DELETE") {
|
||||||
|
vegaURL = fmt.Sprintf("%s?%s", vegaURL, p.Encode())
|
||||||
|
req, err = http.NewRequest(method, vegaURL, nil)
|
||||||
|
} else {
|
||||||
|
req, err = http.NewRequest(method, vegaURL, strings.NewReader(p.Encode()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error preparing request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vega.User != "" && vega.Pass != "" {
|
||||||
|
// Basic Auth
|
||||||
|
req.SetBasicAuth(vega.User, vega.Pass)
|
||||||
|
} else if vega.APIKey != "" && vega.APISecret != "" {
|
||||||
|
// OAuth
|
||||||
|
vega.getAuthToken()
|
||||||
|
err = vega.token.valid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", vega.getBearer())
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
return vega.client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vega *VegaDNSClient) getURL(endpoint string) string {
|
||||||
|
return fmt.Sprintf("%s/%s/%s", vega.baseurl, vega.version, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vega *VegaDNSClient) stillAuthorized() error {
|
||||||
|
return vega.token.valid()
|
||||||
|
}
|
80
vendor/github.com/OpenDNS/vegadns2client/domains.go
generated
vendored
Normal file
80
vendor/github.com/OpenDNS/vegadns2client/domains.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package vegadns2client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Domain - struct containing a domain object
|
||||||
|
type Domain struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
DomainID int `json:"domain_id"`
|
||||||
|
OwnerID int `json:"owner_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainResponse - api response of a domain list
|
||||||
|
type DomainResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Total int `json:"total_domains"`
|
||||||
|
Domains []Domain `json:"domains"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainID - returns the id for a domain
|
||||||
|
// Input: domain
|
||||||
|
// Output: int, err
|
||||||
|
func (vega *VegaDNSClient) GetDomainID(domain string) (int, error) {
|
||||||
|
params := make(map[string]string)
|
||||||
|
params["search"] = domain
|
||||||
|
|
||||||
|
resp, err := vega.Send("GET", "domains", params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("Error sending GET to GetDomainID: %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("Error reading response from GET to GetDomainID: %s", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return -1, fmt.Errorf("Got bad answer from VegaDNS on GetDomainID. Code: %d. Message: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
answer := DomainResponse{}
|
||||||
|
if err := json.Unmarshal(body, &answer); err != nil {
|
||||||
|
return -1, fmt.Errorf("Error unmarshalling body from GetDomainID: %s", err)
|
||||||
|
}
|
||||||
|
log.Println(answer)
|
||||||
|
for _, d := range answer.Domains {
|
||||||
|
if d.Domain == domain {
|
||||||
|
return d.DomainID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, fmt.Errorf("Didnt find domain %s", domain)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthZone retrieves the closest match to a given
|
||||||
|
// domain. Example: Given an argument "a.b.c.d.e", and a VegaDNS
|
||||||
|
// hosted domain of "c.d.e", GetClosestMatchingDomain will return
|
||||||
|
// "c.d.e".
|
||||||
|
func (vega *VegaDNSClient) GetAuthZone(fqdn string) (string, int, error) {
|
||||||
|
fqdn = strings.TrimSuffix(fqdn, ".")
|
||||||
|
numComponents := len(strings.Split(fqdn, "."))
|
||||||
|
for i := 1; i < numComponents; i++ {
|
||||||
|
tmpHostname := strings.SplitN(fqdn, ".", i)[i-1]
|
||||||
|
log.Printf("tmpHostname for i = %d: %s\n", i, tmpHostname)
|
||||||
|
if domainID, err := vega.GetDomainID(tmpHostname); err == nil {
|
||||||
|
log.Printf("Found zone: %s\n\tShortened to %s\n", tmpHostname, strings.TrimSuffix(tmpHostname, "."))
|
||||||
|
return strings.TrimSuffix(tmpHostname, "."), domainID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println("Unable to find hosted zone in vegadns")
|
||||||
|
return "", -1, fmt.Errorf("Unable to find auth zone for fqdn %s", fqdn)
|
||||||
|
}
|
19
vendor/github.com/OpenDNS/vegadns2client/main.go
generated
vendored
Normal file
19
vendor/github.com/OpenDNS/vegadns2client/main.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package vegadns2client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewVegaDNSClient - helper to instantiate a client
|
||||||
|
// Input: url string
|
||||||
|
// Output: VegaDNSClient
|
||||||
|
func NewVegaDNSClient(url string) VegaDNSClient {
|
||||||
|
httpClient := http.Client{Timeout: 15 * time.Second}
|
||||||
|
return VegaDNSClient{
|
||||||
|
client: httpClient,
|
||||||
|
baseurl: url,
|
||||||
|
version: "1.0",
|
||||||
|
token: Token{},
|
||||||
|
}
|
||||||
|
}
|
113
vendor/github.com/OpenDNS/vegadns2client/records.go
generated
vendored
Normal file
113
vendor/github.com/OpenDNS/vegadns2client/records.go
generated
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package vegadns2client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Record - struct representing a Record object
|
||||||
|
type Record struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
RecordType string `json:"record_type"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
RecordID int `json:"record_id"`
|
||||||
|
LocationID string `json:"location_id"`
|
||||||
|
DomainID int `json:"domain_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordsResponse - api response list of records
|
||||||
|
type RecordsResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Total int `json:"total_records"`
|
||||||
|
Domain Domain `json:"domain"`
|
||||||
|
Records []Record `json:"records"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecordID - helper to get the id of a record
|
||||||
|
// Input: domainID, record, recordType
|
||||||
|
// Output: int
|
||||||
|
func (vega *VegaDNSClient) GetRecordID(domainID int, record string, recordType string) (int, error) {
|
||||||
|
params := make(map[string]string)
|
||||||
|
params["domain_id"] = fmt.Sprintf("%d", domainID)
|
||||||
|
|
||||||
|
resp, err := vega.Send("GET", "records", params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("Error sending GET to GetRecordID: %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("Error reading response from GetRecordID: %s", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return -1, fmt.Errorf("Got bad answer from VegaDNS on GetRecordID. Code: %d. Message: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
answer := RecordsResponse{}
|
||||||
|
if err := json.Unmarshal(body, &answer); err != nil {
|
||||||
|
return -1, fmt.Errorf("Error unmarshalling body from GetRecordID: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range answer.Records {
|
||||||
|
if r.Name == record && r.RecordType == recordType {
|
||||||
|
return r.RecordID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, errors.New("Couldnt find record")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTXT - Creates a TXT record
|
||||||
|
// Input: domainID, fqdn, value, ttl
|
||||||
|
// Output: nil or error
|
||||||
|
func (vega *VegaDNSClient) CreateTXT(domainID int, fqdn string, value string, ttl int) error {
|
||||||
|
params := make(map[string]string)
|
||||||
|
|
||||||
|
params["record_type"] = "TXT"
|
||||||
|
params["ttl"] = fmt.Sprintf("%d", ttl)
|
||||||
|
params["domain_id"] = fmt.Sprintf("%d", domainID)
|
||||||
|
params["name"] = strings.TrimSuffix(fqdn, ".")
|
||||||
|
params["value"] = value
|
||||||
|
|
||||||
|
resp, err := vega.Send("POST", "records", params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Send POST error in CreateTXT: %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading POST response in CreateTXT: %s", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusCreated {
|
||||||
|
return fmt.Errorf("Got bad answer from VegaDNS on CreateTXT. Code: %d. Message: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecord - deletes a record by id
|
||||||
|
// Input: recordID
|
||||||
|
// Output: nil or error
|
||||||
|
func (vega *VegaDNSClient) DeleteRecord(recordID int) error {
|
||||||
|
resp, err := vega.Send("DELETE", fmt.Sprintf("records/%d", recordID), nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Send DELETE error in DeleteTXT: %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading DELETE response in DeleteTXT: %s", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Got bad answer from VegaDNS on DeleteTXT. Code: %d. Message: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
74
vendor/github.com/OpenDNS/vegadns2client/tokens.go
generated
vendored
Normal file
74
vendor/github.com/OpenDNS/vegadns2client/tokens.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package vegadns2client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token - struct to hold token information
|
||||||
|
type Token struct {
|
||||||
|
Token string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
ExpiresAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) valid() error {
|
||||||
|
if time.Now().UTC().After(t.ExpiresAt) {
|
||||||
|
return errors.New("Token Expired")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vega *VegaDNSClient) getBearer() string {
|
||||||
|
if vega.token.valid() != nil {
|
||||||
|
vega.getAuthToken()
|
||||||
|
}
|
||||||
|
return vega.token.formatBearer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) formatBearer() string {
|
||||||
|
return fmt.Sprintf("Bearer %s", t.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vega *VegaDNSClient) getAuthToken() {
|
||||||
|
tokenEndpoint := vega.getURL("token")
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("grant_type", "client_credentials")
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", tokenEndpoint, strings.NewReader(v.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error forming POST to getAuthToken: %s", err)
|
||||||
|
}
|
||||||
|
req.SetBasicAuth(vega.APIKey, vega.APISecret)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
issueTime := time.Now().UTC()
|
||||||
|
resp, err := vega.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error sending POST to getAuthToken: %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error reading response from POST to getAuthToken: %s", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
log.Fatalf("Got bad answer from VegaDNS on getAuthToken. Code: %d. Message: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &vega.token); err != nil {
|
||||||
|
log.Fatalf("Error unmarshalling body of POST to getAuthToken: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vega.token.TokenType != "bearer" {
|
||||||
|
log.Fatal("We don't support anything except bearer tokens")
|
||||||
|
}
|
||||||
|
vega.token.ExpiresAt = issueTime.Add(time.Duration(vega.token.ExpiresIn) * time.Second)
|
||||||
|
}
|
Loading…
Reference in a new issue