Skip to content

Commit f514292

Browse files
ldezdmke
andauthored
rfc2136: add support for tsig-keygen generated file (#2330)
Co-authored-by: Dominik Menke <git@dmke.org>
1 parent f8db554 commit f514292

File tree

15 files changed

+432
-35
lines changed

15 files changed

+432
-35
lines changed

cmd/zz_gen_cmd_dnshelp.go

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/content/dns/zz_gen_rfc2136.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,18 @@ Here is an example bash command using the RFC2136 provider:
2727

2828
```bash
2929
RFC2136_NAMESERVER=127.0.0.1 \
30-
RFC2136_TSIG_KEY=lego \
30+
RFC2136_TSIG_KEY=example.com \
3131
RFC2136_TSIG_ALGORITHM=hmac-sha256. \
3232
RFC2136_TSIG_SECRET=YWJjZGVmZGdoaWprbG1ub3BxcnN0dXZ3eHl6MTIzNDU= \
3333
lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run
3434

3535
## ---
3636

37-
keyname=lego; keyfile=lego.key; tsig-keygen $keyname > $keyfile
37+
keyname=example.com; keyfile=example.com.key; tsig-keygen $keyname > $keyfile
3838

3939
RFC2136_NAMESERVER=127.0.0.1 \
40-
RFC2136_TSIG_KEY="$keyname" \
41-
RFC2136_TSIG_ALGORITHM="$( awk -F'[ ";]' '/algorithm/ { print $2 }' $keyfile )." \
42-
RFC2136_TSIG_SECRET="$( awk -F'[ ";]' '/secret/ { print $3 }' $keyfile )" \
43-
lego --email you@example.com --dns rfc2136 d "*.example.com" -d example.com run
40+
RFC2136_TSIG_FILE="$keyfile" \
41+
lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run
4442
```
4543

4644

@@ -51,9 +49,9 @@ lego --email you@example.com --dns rfc2136 d "*.example.com" -d example.com run
5149
| Environment Variable Name | Description |
5250
|-----------------------|-------------|
5351
| `RFC2136_NAMESERVER` | Network address in the form "host" or "host:port" |
54-
| `RFC2136_TSIG_ALGORITHM` | TSIG algorithm. See [miekg/dns#tsig.go](https://github.com/miekg/dns/blob/master/tsig.go) for supported values. To disable TSIG authentication, leave the `RFC2136_TSIG*` variables unset. |
55-
| `RFC2136_TSIG_KEY` | Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the `RFC2136_TSIG*` variables unset. |
56-
| `RFC2136_TSIG_SECRET` | Secret key payload. To disable TSIG authentication, leave the` RFC2136_TSIG*` variables unset. |
52+
| `RFC2136_TSIG_ALGORITHM` | TSIG algorithm. See [miekg/dns#tsig.go](https://github.com/miekg/dns/blob/master/tsig.go) for supported values. To disable TSIG authentication, leave the `RFC2136_TSIG_KEY` or `RFC2136_TSIG_SECRET` variables unset. |
53+
| `RFC2136_TSIG_KEY` | Name of the secret key as defined in DNS server configuration. To disable TSIG authentication, leave the `RFC2136_TSIG_KEY` variable unset. |
54+
| `RFC2136_TSIG_SECRET` | Secret key payload. To disable TSIG authentication, leave the `RFC2136_TSIG_SECRET` variable unset. |
5755

5856
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
5957
More information [here]({{% ref "dns#configuration-and-credentials" %}}).
@@ -67,6 +65,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}).
6765
| `RFC2136_POLLING_INTERVAL` | Time between DNS propagation check |
6866
| `RFC2136_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
6967
| `RFC2136_SEQUENCE_INTERVAL` | Time between sequential requests |
68+
| `RFC2136_TSIG_FILE` | Path to a key file generated by tsig-keygen |
7069
| `RFC2136_TTL` | The TTL of the TXT record used for the DNS challenge |
7170

7271
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
key "example.com" {
2+
algorithm;
3+
secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg=";
4+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
key {
2+
algorithm hmac-sha256;
3+
secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg=";
4+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
key "example.com" {
2+
secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg=";
3+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
key "example.com" {
2+
algorithm hmac-sha256;
3+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
key "example.com" {
2+
algorithm hmac-sha256;
3+
secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg=";
4+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
key "example.com" {
2+
algorithm hmac-sha256;
3+
secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg=";
4+
};
5+
6+
key "example.org" {
7+
algorithm hmac-sha512;
8+
secret "v6CkK3gop6HXj4+dcWiLXLGSYKVY5J1cTMjDsdl/Ah9B8aWfTgjwFBoHHyiHWSyvwWPDuEIRs2Pqm8nedca4+g==";
9+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
foo {
2+
bar example;
3+
};
4+
5+
key "example.com" {
6+
algorithm hmac-sha256;
7+
secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg=";
8+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# TSIG Key File
2+
3+
How to generate example:
4+
5+
```console
6+
$ docker run --rm -it -v $(pwd):/app -w /app alpine sh
7+
/app # apk add bind
8+
/app # tsig-keygen example.com > sample1.conf
9+
/app # tsig-keygen -a hmac-sha512 example.com > sample2.conf
10+
```
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package internal
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"os"
7+
"strings"
8+
)
9+
10+
type Key struct {
11+
Name string
12+
Algorithm string
13+
Secret string
14+
}
15+
16+
// ReadTSIGFile reads TSIG key file generated with `tsig-keygen`.
17+
func ReadTSIGFile(filename string) (*Key, error) {
18+
file, err := os.Open(filename)
19+
if err != nil {
20+
return nil, fmt.Errorf("open file: %w", err)
21+
}
22+
23+
defer func() { _ = file.Close() }()
24+
25+
key := &Key{}
26+
27+
var read bool
28+
29+
scanner := bufio.NewScanner(file)
30+
for scanner.Scan() {
31+
line := strings.TrimSpace(strings.TrimSuffix(scanner.Text(), ";"))
32+
33+
if line == "" {
34+
continue
35+
}
36+
37+
if read && line == "}" {
38+
break
39+
}
40+
41+
fields := strings.Fields(line)
42+
43+
switch {
44+
case fields[0] == "key":
45+
read = true
46+
47+
if len(fields) != 3 {
48+
return nil, fmt.Errorf("invalid key line: %s", line)
49+
}
50+
51+
key.Name = safeUnquote(fields[1])
52+
53+
case !read:
54+
continue
55+
56+
default:
57+
if len(fields) != 2 {
58+
continue
59+
}
60+
61+
v := safeUnquote(fields[1])
62+
63+
switch safeUnquote(fields[0]) {
64+
case "algorithm":
65+
key.Algorithm = v
66+
case "secret":
67+
key.Secret = v
68+
default:
69+
continue
70+
}
71+
}
72+
}
73+
74+
return key, nil
75+
}
76+
77+
func safeUnquote(v string) string {
78+
if len(v) < 2 {
79+
// empty or single character string
80+
return v
81+
}
82+
83+
if v[0] == '"' && v[len(v)-1] == '"' {
84+
// string wrapped in quotes
85+
return v[1 : len(v)-1]
86+
}
87+
88+
return v
89+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package internal
2+
3+
import (
4+
"path/filepath"
5+
"runtime"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestReadTSIGFile(t *testing.T) {
13+
testCases := []struct {
14+
desc string
15+
filename string
16+
expected *Key
17+
}{
18+
{
19+
desc: "basic",
20+
filename: "sample.conf",
21+
expected: &Key{Name: "example.com", Algorithm: "hmac-sha256", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
22+
},
23+
{
24+
desc: "data before the key",
25+
filename: "text_before.conf",
26+
expected: &Key{Name: "example.com", Algorithm: "hmac-sha256", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
27+
},
28+
{
29+
desc: "data after the key",
30+
filename: "text_after.conf",
31+
expected: &Key{Name: "example.com", Algorithm: "hmac-sha256", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
32+
},
33+
{
34+
desc: "ignore missing secret",
35+
filename: "missing_secret.conf",
36+
expected: &Key{Name: "example.com", Algorithm: "hmac-sha256"},
37+
},
38+
{
39+
desc: "ignore missing algorithm",
40+
filename: "mising_algo.conf",
41+
expected: &Key{Name: "example.com", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
42+
},
43+
{
44+
desc: "ignore invalid field format",
45+
filename: "invalid_field.conf",
46+
expected: &Key{Name: "example.com", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
47+
},
48+
}
49+
50+
for _, test := range testCases {
51+
t.Run(test.desc, func(t *testing.T) {
52+
t.Parallel()
53+
54+
key, err := ReadTSIGFile(filepath.Join("fixtures", test.filename))
55+
require.NoError(t, err)
56+
57+
assert.Equal(t, test.expected, key)
58+
})
59+
}
60+
}
61+
62+
func TestReadTSIGFile_error(t *testing.T) {
63+
if runtime.GOOS != "linux" {
64+
// Because error messages are different on Windows.
65+
t.Skip("only for UNIX systems")
66+
}
67+
68+
testCases := []struct {
69+
desc string
70+
filename string
71+
expected string
72+
}{
73+
{
74+
desc: "missing file",
75+
filename: "missing.conf",
76+
expected: "open file: open fixtures/missing.conf: no such file or directory",
77+
},
78+
{
79+
desc: "invalid key format",
80+
filename: "invalid_key.conf",
81+
expected: "invalid key line: key {",
82+
},
83+
}
84+
85+
for _, test := range testCases {
86+
t.Run(test.desc, func(t *testing.T) {
87+
t.Parallel()
88+
89+
_, err := ReadTSIGFile(filepath.Join("fixtures", test.filename))
90+
require.Error(t, err)
91+
92+
require.EqualError(t, err, test.expected)
93+
})
94+
}
95+
}

0 commit comments

Comments
 (0)