Skip to content

Commit 192a8b1

Browse files
drebescatsby
authored andcommitted
provider/dns: DNS dynamic updates (RFC 2136)
1 parent b720ec3 commit 192a8b1

20 files changed

Lines changed: 1914 additions & 0 deletions

‎builtin/bins/provider-dns/main.go‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package main
2+
3+
import (
4+
"github.com/hashicorp/terraform/builtin/providers/dns"
5+
"github.com/hashicorp/terraform/plugin"
6+
)
7+
8+
func main() {
9+
plugin.Serve(&plugin.ServeOpts{
10+
ProviderFunc: dns.Provider,
11+
})
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package main
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/bash
2+
3+
set -eu
4+
set -x
5+
6+
# Test domains
7+
export DNS_DOMAIN_FORWARD="example.com."
8+
export DNS_DOMAIN_REVERSE="1.168.192.in-addr.arpa."
9+
10+
# Run with no authentication
11+
12+
export DNS_UPDATE_SERVER=127.0.0.1
13+
docker run -d -p 53:53/udp \
14+
-e BIND_DOMAIN_FORWARD=${DNS_DOMAIN_FORWARD} \
15+
-e BIND_DOMAIN_REVERSE=${DNS_DOMAIN_REVERSE} \
16+
-e BIND_INSECURE=true \
17+
--name bind_insecure drebes/bind
18+
make testacc TEST=./builtin/providers/dns
19+
docker stop bind_insecure
20+
docker rm bind_insecure
21+
22+
# Run with authentication
23+
24+
export DNS_UPDATE_KEYNAME=${DNS_DOMAIN_FORWARD}
25+
export DNS_UPDATE_KEYALGORITHM="hmac-md5"
26+
export DNS_UPDATE_KEYSECRET="c3VwZXJzZWNyZXQ="
27+
docker run -d -p 53:53/udp \
28+
-e BIND_DOMAIN_FORWARD=${DNS_DOMAIN_FORWARD} \
29+
-e BIND_DOMAIN_REVERSE=${DNS_DOMAIN_REVERSE} \
30+
-e BIND_KEY_NAME=${DNS_UPDATE_KEYNAME} \
31+
-e BIND_KEY_ALGORITHM=${DNS_UPDATE_KEYALGORITHM} \
32+
-e BIND_KEY_SECRET=${DNS_UPDATE_KEYSECRET} \
33+
--name bind_secure drebes/bind
34+
make testacc TEST=./builtin/providers/dns
35+
docker stop bind_secure
36+
docker rm bind_secure

‎builtin/providers/dns/config.go‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dns
2+
3+
import (
4+
"fmt"
5+
"github.com/miekg/dns"
6+
"log"
7+
)
8+
9+
type Config struct {
10+
server string
11+
port int
12+
keyname string
13+
keyalgo string
14+
keysecret string
15+
}
16+
17+
type DNSClient struct {
18+
c *dns.Client
19+
srv_addr string
20+
keyname string
21+
keysecret string
22+
keyalgo string
23+
}
24+
25+
// Configures and returns a fully initialized DNSClient
26+
func (c *Config) Client() (interface{}, error) {
27+
log.Println("[INFO] Building DNSClient config structure")
28+
29+
var client DNSClient
30+
client.srv_addr = fmt.Sprintf("%s:%d", c.server, c.port)
31+
authCfgOk := false
32+
if (c.keyname == "" && c.keysecret == "" && c.keyalgo == "") ||
33+
(c.keyname != "" && c.keysecret != "" && c.keyalgo != "") {
34+
authCfgOk = true
35+
}
36+
if !authCfgOk {
37+
return nil, fmt.Errorf("Error configuring provider: when using authentication, \"key_name\", \"key_secret\" and \"key_algorithm\" should be non empty")
38+
}
39+
client.c = new(dns.Client)
40+
if c.keyname != "" {
41+
client.keyname = c.keyname
42+
client.keysecret = c.keysecret
43+
keyalgo, err := convertHMACAlgorithm(c.keyalgo)
44+
if err != nil {
45+
return nil, fmt.Errorf("Error configuring provider: %s", err)
46+
}
47+
client.keyalgo = keyalgo
48+
client.c.TsigSecret = map[string]string{c.keyname: c.keysecret}
49+
}
50+
return &client, nil
51+
}
52+
53+
// Validates and converts HMAC algorithm
54+
func convertHMACAlgorithm(name string) (string, error) {
55+
switch name {
56+
case "hmac-md5":
57+
return dns.HmacMD5, nil
58+
case "hmac-sha1":
59+
return dns.HmacSHA1, nil
60+
case "hmac-sha256":
61+
return dns.HmacSHA256, nil
62+
case "hmac-sha512":
63+
return dns.HmacSHA512, nil
64+
default:
65+
return "", fmt.Errorf("Unknown HMAC algorithm: %s", name)
66+
}
67+
}

‎builtin/providers/dns/provider.go‎

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package dns
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/hashicorp/terraform/helper/schema"
8+
"github.com/hashicorp/terraform/terraform"
9+
"github.com/miekg/dns"
10+
)
11+
12+
// Provider returns a schema.Provider for DNS dynamic updates.
13+
func Provider() terraform.ResourceProvider {
14+
return &schema.Provider{
15+
Schema: map[string]*schema.Schema{
16+
"update": &schema.Schema{
17+
Type: schema.TypeList,
18+
MaxItems: 1,
19+
Optional: true,
20+
Elem: &schema.Resource{
21+
Schema: map[string]*schema.Schema{
22+
"server": &schema.Schema{
23+
Type: schema.TypeString,
24+
Required: true,
25+
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_SERVER", nil),
26+
},
27+
"port": &schema.Schema{
28+
Type: schema.TypeInt,
29+
Optional: true,
30+
Default: 53,
31+
},
32+
"key_name": &schema.Schema{
33+
Type: schema.TypeString,
34+
Optional: true,
35+
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYNAME", nil),
36+
},
37+
"key_algorithm": &schema.Schema{
38+
Type: schema.TypeString,
39+
Optional: true,
40+
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYALGORITHM", nil),
41+
},
42+
"key_secret": &schema.Schema{
43+
Type: schema.TypeString,
44+
Optional: true,
45+
DefaultFunc: schema.EnvDefaultFunc("DNS_UPDATE_KEYSECRET", nil),
46+
},
47+
},
48+
},
49+
},
50+
},
51+
52+
ResourcesMap: map[string]*schema.Resource{
53+
"dns_a_record_set": resourceDnsARecordSet(),
54+
"dns_aaaa_record_set": resourceDnsAAAARecordSet(),
55+
"dns_cname_record": resourceDnsCnameRecord(),
56+
"dns_ptr_record": resourceDnsPtrRecord(),
57+
},
58+
59+
ConfigureFunc: configureProvider,
60+
}
61+
}
62+
63+
func configureProvider(d *schema.ResourceData) (interface{}, error) {
64+
65+
var server, keyname, keyalgo, keysecret string
66+
var port int
67+
68+
// if the update block is missing, schema.EnvDefaultFunc is not called
69+
if v, ok := d.GetOk("update"); ok {
70+
update := v.([]interface{})[0].(map[string]interface{})
71+
if val, ok := update["port"]; ok {
72+
port = int(val.(int))
73+
}
74+
if val, ok := update["server"]; ok {
75+
server = val.(string)
76+
}
77+
if val, ok := update["key_name"]; ok {
78+
keyname = val.(string)
79+
}
80+
if val, ok := update["key_algorithm"]; ok {
81+
keyalgo = val.(string)
82+
}
83+
if val, ok := update["key_secret"]; ok {
84+
keysecret = val.(string)
85+
}
86+
} else {
87+
if len(os.Getenv("DNS_UPDATE_SERVER")) > 0 {
88+
server = os.Getenv("DNS_UPDATE_SERVER")
89+
} else {
90+
return nil, nil
91+
}
92+
port = 53
93+
if len(os.Getenv("DNS_UPDATE_KEYNAME")) > 0 {
94+
keyname = os.Getenv("DNS_UPDATE_KEYNAME")
95+
}
96+
if len(os.Getenv("DNS_UPDATE_KEYALGORITHM")) > 0 {
97+
keyalgo = os.Getenv("DNS_UPDATE_KEYALGORITHM")
98+
}
99+
if len(os.Getenv("DNS_UPDATE_KEYSECRET")) > 0 {
100+
keysecret = os.Getenv("DNS_UPDATE_KEYSECRET")
101+
}
102+
}
103+
104+
config := Config{
105+
server: server,
106+
port: port,
107+
keyname: keyname,
108+
keyalgo: keyalgo,
109+
keysecret: keysecret,
110+
}
111+
112+
return config.Client()
113+
}
114+
115+
func getAVal(record interface{}) (string, error) {
116+
117+
recstr := record.(*dns.A).String()
118+
var name, ttl, class, typ, addr string
119+
120+
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &addr)
121+
if err != nil {
122+
return "", fmt.Errorf("Error parsing record: %s", err)
123+
}
124+
125+
return addr, nil
126+
}
127+
128+
func getAAAAVal(record interface{}) (string, error) {
129+
130+
recstr := record.(*dns.AAAA).String()
131+
var name, ttl, class, typ, addr string
132+
133+
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &addr)
134+
if err != nil {
135+
return "", fmt.Errorf("Error parsing record: %s", err)
136+
}
137+
138+
return addr, nil
139+
}
140+
141+
func getCnameVal(record interface{}) (string, error) {
142+
143+
recstr := record.(*dns.CNAME).String()
144+
var name, ttl, class, typ, cname string
145+
146+
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &cname)
147+
if err != nil {
148+
return "", fmt.Errorf("Error parsing record: %s", err)
149+
}
150+
151+
return cname, nil
152+
}
153+
154+
func getPtrVal(record interface{}) (string, error) {
155+
156+
recstr := record.(*dns.PTR).String()
157+
var name, ttl, class, typ, ptr string
158+
159+
_, err := fmt.Sscanf(recstr, "%s\t%s\t%s\t%s\t%s", &name, &ttl, &class, &typ, &ptr)
160+
if err != nil {
161+
return "", fmt.Errorf("Error parsing record: %s", err)
162+
}
163+
164+
return ptr, nil
165+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package dns
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform/helper/schema"
8+
"github.com/hashicorp/terraform/terraform"
9+
)
10+
11+
var testAccProviders map[string]terraform.ResourceProvider
12+
var testAccProvider *schema.Provider
13+
14+
func init() {
15+
testAccProvider = Provider().(*schema.Provider)
16+
testAccProviders = map[string]terraform.ResourceProvider{
17+
"dns": testAccProvider,
18+
}
19+
}
20+
21+
func TestProvider(t *testing.T) {
22+
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
23+
t.Fatalf("err: %s", err)
24+
}
25+
}
26+
27+
func TestProvider_impl(t *testing.T) {
28+
var _ terraform.ResourceProvider = Provider()
29+
}
30+
31+
func testAccPreCheck(t *testing.T) {
32+
v := os.Getenv("DNS_UPDATE_SERVER")
33+
if v == "" {
34+
t.Fatal("DNS_UPDATE_SERVER must be set for acceptance tests")
35+
}
36+
}

0 commit comments

Comments
 (0)