Skip to content

Commit

Permalink
DNS CAA records
Browse files Browse the repository at this point in the history
  • Loading branch information
nt0xa committed Jun 25, 2024
1 parent e65b958 commit 2990734
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 30 deletions.
2 changes: 1 addition & 1 deletion cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func main() {

go func() {
srv := dnsx.New(
":53",
":1053",
dnsHandler,
dnsx.NotifyStartedFunc(waitDNS.Done),
)
Expand Down
9 changes: 9 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[context]
server = 'test'

[servers]
[servers.test]
token = '04a752d6c759f59978755f554bffa08c'
url = 'https://sonar.test:31337'
insecure = true
proxy = 'http://localhost:8080'
2 changes: 2 additions & 0 deletions internal/actionsdb/dns_records.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func (act *dbactions) DNSRecordsCreate(ctx context.Context, p actions.DNSRecords
payload, err := act.db.PayloadsGetByUserAndName(u.ID, p.PayloadName)
if err == sql.ErrNoRows {
return nil, errors.NotFoundf("payload with name %q not found", p.PayloadName)
} else if err != nil {
return nil, errors.Internal(err)
}

if _, err := act.db.DNSRecordsGetByPayloadNameAndType(payload.ID, p.Name, strings.ToUpper(p.Type)); err != sql.ErrNoRows {
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/server/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ var dnsTemplate = tpl.MustParse(`
@ 60 IN MX 10 mx
* 60 IN MX 10 mx
@ 60 IN CAA 60 issue "letsencrypt.org"
@ 60 IN CAA 0 issue "letsencrypt.org"
@ SOA ns1 hostmaster 1337 86400 7200 4000000 11200
* SOA ns1 hostmaster 1337 86400 7200 4000000 11200
`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DELETE FROM dns_records WHERE type = 'CAA'::dns_record_type;
ALTER TABLE dns_records ALTER type TYPE text;
ALTER TYPE dns_record_type RENAME TO dns_record_type_old;
CREATE TYPE dns_record_type AS ENUM('A', 'AAAA', 'MX', 'TXT', 'CNAME', 'NS');
ALTER TABLE dns_records ALTER type TYPE dns_record_type USING type::dns_record_type;
DROP TYPE dns_record_type_old;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TYPE dns_record_type ADD VALUE 'CAA';
4 changes: 4 additions & 0 deletions internal/database/models/dns_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
DNSTypeTXT = "TXT"
DNSTypeCNAME = "CNAME"
DNSTypeNS = "NS"
DNSTypeCAA = "CAA"
)

var DNSTypesAll = []string{
Expand All @@ -23,6 +24,7 @@ var DNSTypesAll = []string{
DNSTypeTXT,
DNSTypeCNAME,
DNSTypeNS,
DNSTypeCAA,
}

const (
Expand Down Expand Up @@ -65,6 +67,8 @@ func (r *DNSRecord) Qtype() uint16 {
return dns.TypeCNAME
case DNSTypeNS:
return dns.TypeNS
case DNSTypeCAA:
return dns.TypeCAA
}
panic("unsupported dns query type")
}
2 changes: 2 additions & 0 deletions internal/database/models/dns_record_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func TestDNSRecordsQtype(t *testing.T) {
{"MX", dns.TypeMX},
{"CNAME", dns.TypeCNAME},
{"TXT", dns.TypeTXT},
{"NS", dns.TypeNS},
{"CAA", dns.TypeCAA},
}

for _, tt := range tests {
Expand Down
21 changes: 19 additions & 2 deletions internal/utils/valid/valid.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,24 @@ func MX(value interface{}) error {
return errors.New("invalid mx record")
}

func DNSRecord(typ string) validation.Rule {
func CAA(value interface{}) error {
v, _ := value.(string)

var (
flag uint8
tag string
val string
)
_, err := fmt.Sscanf(v, "%d %s %q", &flag, &tag, &val)
if err != nil {
return fmt.Errorf("invalid caa record: %w", err)
}

switch typ {
return nil
}

func DNSRecord(typ string) validation.Rule {
switch typ {
case "A":
return is.IPv4

Expand All @@ -95,6 +109,9 @@ func DNSRecord(typ string) validation.Rule {

case "CNAME":
return validation.By(FQDN)

case "CAA":
return validation.By(CAA)
}

return validation.Required
Expand Down
55 changes: 31 additions & 24 deletions pkg/dnsx/dnsx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,30 +88,32 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}

var tests = []struct {
name string
qtype uint16
results [][]string
}{
// Static
{"test.sonar.test.", dns.TypeMX, [][]string{
{"10 mx.sonar.test"},
}},
{"test.sonar.test.", dns.TypeA, [][]string{
{"1.1.1.1"},
}},
{"test.sonar.test.", dns.TypeAAAA, [][]string{
{"1.1.1.1"},
}},
{"c1da9f3d.sonar.test.", dns.TypeA, [][]string{
{"2.2.2.2"},
}},
{"ns.sonar.test.", dns.TypeNS, [][]string{
{"ns1.example.com."},
}},
}

func TestDNS(t *testing.T) {
t.Parallel()

var tests = []struct {
name string
qtype uint16
results [][]string
}{
// Static
{"test.sonar.test.", dns.TypeMX, [][]string{
{"10 mx.sonar.test"},
}},
{"test.sonar.test.", dns.TypeA, [][]string{
{"1.1.1.1"},
}},
{"test.sonar.test.", dns.TypeAAAA, [][]string{
{"1.1.1.1"},
}},
{"c1da9f3d.sonar.test.", dns.TypeA, [][]string{
{"2.2.2.2"},
}},
{"ns.sonar.test.", dns.TypeNS, [][]string{
{"ns1.example.com."},
}},
}

for _, tt := range tests {
tname := fmt.Sprintf("%s/%s", tt.name, dns.Type(tt.qtype).String())

Expand Down Expand Up @@ -209,7 +211,12 @@ func TestProvider(t *testing.T) {
err = handlerProvider.CleanUp("sonar.test", "", "")
require.NoError(t, err)

notifier.On("Notify", mock.Anything, mock.Anything, mock.Anything).Return()
notifier.On(
"Notify",
mock.Anything,
mock.Anything,
mock.Anything,
).Return()

in, _, err = c.Exchange(msg, "127.0.0.1:1053")
require.NoError(t, err)
Expand Down
24 changes: 23 additions & 1 deletion pkg/dnsx/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,27 @@ func NewRR(name string, qtype uint16, ttl int, value string) dns.RR {
},
Ns: value,
}

case dns.TypeCAA:
var (
flag uint8
tag string
val string
)

_, _ = fmt.Sscanf(value, "%d %s %q", &flag, &tag, &val)

return &dns.CAA{
Hdr: dns.RR_Header{
Name: name,
Rrtype: dns.TypeCAA,
Class: dns.ClassINET,
Ttl: uint32(ttl),
},
Flag: flag,
Tag: tag,
Value: val,
}
}

return nil
Expand All @@ -146,7 +167,6 @@ func QtypeString(qtype uint16) string {

// RRToString returns string representation of dns.RR value.
func RRToString(rr dns.RR) string {

switch r := rr.(type) {
case *dns.A:
return r.A.String()
Expand All @@ -160,6 +180,8 @@ func RRToString(rr dns.RR) string {
return r.Target
case *dns.NS:
return r.Ns
case *dns.CAA:
return fmt.Sprintf("%d %s %q", r.Flag, r.Tag, r.Value)
}

panic("unsupported dns record type")
Expand Down
101 changes: 100 additions & 1 deletion pkg/dnsx/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import (
"github.com/miekg/dns"
"github.com/nt0xa/sonar/pkg/dnsx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRRToString(t *testing.T) {
t.Parallel()

tests := []struct {
rec dns.RR
res string
Expand Down Expand Up @@ -75,6 +78,32 @@ func TestRRToString(t *testing.T) {
},
"example.com.",
},
{
&dns.NS{
Hdr: dns.RR_Header{
Name: "sonar.test",
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
Ttl: uint32(60),
},
Ns: "ns.example.com.",
},
"ns.example.com.",
},
{
&dns.CAA{
Hdr: dns.RR_Header{
Name: "sonar.test",
Rrtype: dns.TypeCAA,
Class: dns.ClassINET,
Ttl: uint32(60),
},
Flag: 1,
Tag: "issue",
Value: "letsencrypt.org",
},
`1 issue "letsencrypt.org"`,
},
}

for _, tt := range tests {
Expand All @@ -87,6 +116,8 @@ func TestRRToString(t *testing.T) {
}

func TestDNSStringToRR(t *testing.T) {
t.Parallel()

tests := []struct {
value string
qtype uint16
Expand Down Expand Up @@ -158,11 +189,79 @@ func TestDNSStringToRR(t *testing.T) {
Target: "example.com.",
},
},
{
"ns.example.com.",
dns.TypeNS,
&dns.NS{
Hdr: dns.RR_Header{
Name: "test.sonar.test.",
Rrtype: dns.TypeNS,
Class: dns.ClassINET,
Ttl: uint32(60),
},
Ns: "ns.example.com.",
},
},
{
"0 issue \"letsencrypt.org\"",
dns.TypeCAA,
&dns.CAA{
Hdr: dns.RR_Header{
Name: "test.sonar.test.",
Rrtype: dns.TypeCAA,
Class: dns.ClassINET,
Ttl: uint32(60),
},
Flag: 0,
Tag: "issue",
Value: "letsencrypt.org",
},
},
}

for _, tt := range tests {
t.Run(tt.value, func(st *testing.T) {
assert.Equal(t, tt.res, dnsx.NewRR("test.sonar.test.", tt.qtype, 60, tt.value))
assert.Equal(
t,
tt.res,
dnsx.NewRR("test.sonar.test.", tt.qtype, 60, tt.value),
)
})
}
}

func TestParseRecords(t *testing.T) {
t.Parallel()

rrs, err := dnsx.ParseRecords(`
@ IN 60 NS ns
@ IN 60 A 127.0.0.1
@ IN 60 AAAA 2001:0db8:85a3:0000:0000:8a2e:0370:7334
@ 60 IN MX 10 mx
@ 60 IN CAA 0 issue "letsencrypt.org"
@ SOA ns1 hostmaster 1337 86400 7200 4000000 11200
`, "sonar.test")
require.NoError(t, err)

assert.Equal(t, rrs[0].Header().Rrtype, dns.TypeNS)
assert.Equal(t, rrs[0].(*dns.NS).Ns, "ns.sonar.test.")

assert.Equal(t, rrs[1].Header().Rrtype, dns.TypeA)
assert.Equal(t, rrs[1].(*dns.A).A, net.ParseIP("127.0.0.1"))

assert.Equal(t, rrs[2].Header().Rrtype, dns.TypeAAAA)
assert.Equal(t, rrs[2].(*dns.AAAA).AAAA, net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"))

assert.Equal(t, rrs[3].Header().Rrtype, dns.TypeMX)
assert.Equal(t, rrs[3].(*dns.MX).Mx, "mx.sonar.test.")
assert.EqualValues(t, rrs[3].(*dns.MX).Preference, 10)

assert.Equal(t, rrs[4].Header().Rrtype, dns.TypeCAA)
assert.EqualValues(t, rrs[4].(*dns.CAA).Flag, 0)
assert.Equal(t, rrs[4].(*dns.CAA).Tag, "issue")
assert.Equal(t, rrs[4].(*dns.CAA).Value, "letsencrypt.org")

assert.Equal(t, rrs[5].Header().Rrtype, dns.TypeSOA)
assert.Equal(t, rrs[5].(*dns.SOA).Ns, "ns1.sonar.test.")
assert.Equal(t, rrs[5].(*dns.SOA).Mbox, "hostmaster.sonar.test.")
}

0 comments on commit 2990734

Please sign in to comment.