Skip to content

Commit 4b34640

Browse files
committed
rfc2136: add support for tsig-keygen generated file
1 parent f8db554 commit 4b34640

File tree

12 files changed

+353
-10
lines changed

12 files changed

+353
-10
lines changed
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 "lego" {
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 "lego" {
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 "lego" {
2+
algorithm hmac-sha256;
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 "lego" {
2+
algorithm hmac-sha256;
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 "lego" {
2+
algorithm hmac-sha256;
3+
secret "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg=";
4+
};
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 lego > sample1.conf
9+
/app # tsig-keygen -a hmac-sha512 lego > sample2.conf
10+
```
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package internal
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"os"
8+
"strings"
9+
10+
"github.com/go-viper/mapstructure/v2"
11+
)
12+
13+
type Key struct {
14+
Name string `mapstructure:"name"`
15+
Algorithm string `mapstructure:"algorithm"`
16+
Secret string `mapstructure:"secret"`
17+
}
18+
19+
// ReadTSIGFile reads TSIG key file generated with `tsig-keygen`.
20+
func ReadTSIGFile(filename string) (*Key, error) {
21+
raw, err := os.ReadFile(filename)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
data := make(map[string]string)
27+
28+
var read bool
29+
30+
scanner := bufio.NewScanner(bytes.NewReader(raw))
31+
for scanner.Scan() {
32+
line := strings.TrimSuffix(scanner.Text(), ";")
33+
34+
if line == "}" {
35+
break
36+
}
37+
38+
switch {
39+
case strings.HasPrefix(line, "key "):
40+
read = true
41+
fields := strings.Fields(line)
42+
43+
if len(fields) != 3 {
44+
return nil, fmt.Errorf("invalid key line: %s", line)
45+
}
46+
47+
data["name"] = safeUnquote(fields[1])
48+
49+
case !read:
50+
continue
51+
52+
default:
53+
fields := strings.Fields(line)
54+
55+
if len(fields) != 2 {
56+
continue
57+
}
58+
59+
data[safeUnquote(fields[0])] = safeUnquote(fields[1])
60+
}
61+
}
62+
63+
key := &Key{}
64+
err = mapstructure.Decode(data, key)
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
if key.Algorithm != "" {
70+
// to be compatible with https://github.com/miekg/dns/blob/master/tsig.go
71+
key.Algorithm += "."
72+
}
73+
74+
return key, nil
75+
}
76+
77+
func safeUnquote(v string) string {
78+
if v == "" {
79+
return v
80+
}
81+
82+
if v[0] == '"' && v[len(v)-1] == '"' {
83+
return v[1 : len(v)-1]
84+
}
85+
86+
return v
87+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package internal
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestReadTSIGFile(t *testing.T) {
12+
testCases := []struct {
13+
desc string
14+
filename string
15+
expected *Key
16+
}{
17+
{
18+
desc: "basic",
19+
filename: "sample.conf",
20+
expected: &Key{Name: "lego", Algorithm: "hmac-sha256.", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
21+
},
22+
{
23+
desc: "data before the key",
24+
filename: "text_before.conf",
25+
expected: &Key{Name: "lego", Algorithm: "hmac-sha256.", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
26+
},
27+
{
28+
desc: "data after the key",
29+
filename: "text_after.conf",
30+
expected: &Key{Name: "lego", Algorithm: "hmac-sha256.", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
31+
},
32+
{
33+
desc: "missing secret",
34+
filename: "missing_secret.conf",
35+
expected: &Key{Name: "lego", Algorithm: "hmac-sha256."},
36+
},
37+
{
38+
desc: "missing algorithm",
39+
filename: "mising_algo.conf",
40+
expected: &Key{Name: "lego", Secret: "TCG5A6/lOHUGbW0e/9RYYbzWDFMlj1pIxCvybLBayBg="},
41+
},
42+
}
43+
44+
for _, test := range testCases {
45+
t.Run(test.desc, func(t *testing.T) {
46+
t.Parallel()
47+
48+
key, err := ReadTSIGFile(filepath.Join("fixtures", test.filename))
49+
require.NoError(t, err)
50+
51+
assert.Equal(t, test.expected, key)
52+
})
53+
}
54+
}
55+
56+
func TestReadTSIGFile_error(t *testing.T) {
57+
_, err := ReadTSIGFile(filepath.Join("fixtures", "invalid_key.conf"))
58+
require.Error(t, err)
59+
}

providers/dns/rfc2136/rfc2136.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,22 @@ import (
1010

1111
"github.com/go-acme/lego/v4/challenge/dns01"
1212
"github.com/go-acme/lego/v4/platform/config/env"
13+
"github.com/go-acme/lego/v4/providers/dns/rfc2136/internal"
1314
"github.com/miekg/dns"
1415
)
1516

1617
// Environment variables names.
1718
const (
1819
envNamespace = "RFC2136_"
1920

21+
EnvTSIGFile = envNamespace + "TSIG_FILE"
22+
2023
EnvTSIGKey = envNamespace + "TSIG_KEY"
2124
EnvTSIGSecret = envNamespace + "TSIG_SECRET"
2225
EnvTSIGAlgorithm = envNamespace + "TSIG_ALGORITHM"
23-
EnvNameserver = envNamespace + "NAMESERVER"
24-
EnvDNSTimeout = envNamespace + "DNS_TIMEOUT"
26+
27+
EnvNameserver = envNamespace + "NAMESERVER"
28+
EnvDNSTimeout = envNamespace + "DNS_TIMEOUT"
2529

2630
EnvTTL = envNamespace + "TTL"
2731
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
@@ -31,10 +35,14 @@ const (
3135

3236
// Config is used to configure the creation of the DNSProvider.
3337
type Config struct {
34-
Nameserver string
35-
TSIGAlgorithm string
36-
TSIGKey string
37-
TSIGSecret string
38+
Nameserver string
39+
40+
TSIGFile string
41+
42+
TSIGAlgorithm string
43+
TSIGKey string
44+
TSIGSecret string
45+
3846
PropagationTimeout time.Duration
3947
PollingInterval time.Duration
4048
TTL int
@@ -76,6 +84,9 @@ func NewDNSProvider() (*DNSProvider, error) {
7684

7785
config := NewDefaultConfig()
7886
config.Nameserver = values[EnvNameserver]
87+
88+
config.TSIGFile = env.GetOrDefaultString(EnvTSIGFile, "")
89+
7990
config.TSIGKey = env.GetOrFile(EnvTSIGKey)
8091
config.TSIGSecret = env.GetOrFile(EnvTSIGSecret)
8192

@@ -92,10 +103,28 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
92103
return nil, errors.New("rfc2136: nameserver missing")
93104
}
94105

106+
if config.TSIGFile != "" {
107+
key, err := internal.ReadTSIGFile(config.TSIGFile)
108+
if err != nil {
109+
return nil, fmt.Errorf("rfc2136: read TSIG file: %w", err)
110+
}
111+
112+
config.TSIGAlgorithm = key.Algorithm
113+
config.TSIGKey = key.Name
114+
config.TSIGSecret = key.Secret
115+
}
116+
95117
if config.TSIGAlgorithm == "" {
96118
config.TSIGAlgorithm = dns.HmacSHA1
97119
}
98120

121+
switch config.TSIGAlgorithm {
122+
case dns.HmacSHA1, dns.HmacSHA224, dns.HmacSHA256, dns.HmacSHA384, dns.HmacSHA512:
123+
// valid algorithm
124+
default:
125+
return nil, fmt.Errorf("rfc2136: unsupported TSIG algorithm: %s", config.TSIGAlgorithm)
126+
}
127+
99128
// Append the default DNS port if none is specified.
100129
if _, _, err := net.SplitHostPort(config.Nameserver); err != nil {
101130
if strings.Contains(err.Error(), "missing port") {

providers/dns/rfc2136/rfc2136.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@ lego --email [email protected] --dns rfc2136 -d '*.example.com' -d example.com run
1616
keyname=lego; keyfile=lego.key; tsig-keygen $keyname > $keyfile
1717
1818
RFC2136_NAMESERVER=127.0.0.1 \
19-
RFC2136_TSIG_KEY="$keyname" \
20-
RFC2136_TSIG_ALGORITHM="$( awk -F'[ ";]' '/algorithm/ { print $2 }' $keyfile )." \
21-
RFC2136_TSIG_SECRET="$( awk -F'[ ";]' '/secret/ { print $3 }' $keyfile )" \
22-
lego --email [email protected] --dns rfc2136 d "*.example.com" -d example.com run
19+
RFC2136_TSIG_FILE="$keyfile" \
20+
lego --email [email protected] --dns rfc2136 d '*.example.com' -d example.com run
2321
'''
2422

2523
[Configuration]
@@ -29,6 +27,7 @@ lego --email [email protected] --dns rfc2136 d "*.example.com" -d example.com run
2927
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."
3028
RFC2136_NAMESERVER = 'Network address in the form "host" or "host:port"'
3129
[Configuration.Additional]
30+
RFC2136_TSIG_FILE = "Path to a key file generated by tsig-keygen"
3231
RFC2136_POLLING_INTERVAL = "Time between DNS propagation check"
3332
RFC2136_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
3433
RFC2136_TTL = "The TTL of the TXT record used for the DNS challenge"

0 commit comments

Comments
 (0)