Skip to content

Commit 66b5b25

Browse files
committed
Make the nonce stable when generating delete records
1 parent 4eb7e75 commit 66b5b25

File tree

4 files changed

+68
-13
lines changed

4 files changed

+68
-13
lines changed

endpoint/crypto.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,32 @@ import (
2929
log "github.com/sirupsen/logrus"
3030
)
3131

32+
const standardGcmNonceSize = 12
33+
34+
// GenerateNonce creates a random nonce of a fixed size
35+
func GenerateNonce() ([]byte, error) {
36+
nonce := make([]byte, standardGcmNonceSize)
37+
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
38+
return nil, err
39+
}
40+
return []byte(base64.StdEncoding.EncodeToString(nonce)), nil
41+
}
42+
3243
// EncryptText gzip input data and encrypts it using the supplied AES key
3344
func EncryptText(text string, aesKey []byte, nonceEncoded []byte) (string, error) {
3445
block, err := aes.NewCipher(aesKey)
3546
if err != nil {
3647
return "", err
3748
}
3849

39-
gcm, err := cipher.NewGCM(block)
50+
gcm, err := cipher.NewGCMWithNonceSize(block, standardGcmNonceSize)
4051
if err != nil {
4152
return "", err
4253
}
4354

44-
nonce := make([]byte, gcm.NonceSize())
45-
if nonceEncoded == nil {
46-
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
47-
return "", err
48-
}
49-
} else {
50-
if _, err = base64.StdEncoding.Decode(nonce, nonceEncoded); err != nil {
51-
return "", err
52-
}
55+
nonce := make([]byte, standardGcmNonceSize)
56+
if _, err = base64.StdEncoding.Decode(nonce, nonceEncoded); err != nil {
57+
return "", err
5358
}
5459

5560
data, err := compressData([]byte(text))

endpoint/labels.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ func (l Labels) SerializePlain(withQuotes bool) string {
119119
sort.Strings(keys) // sort for consistency
120120

121121
for _, key := range keys {
122+
if key == txtEncryptionNonce {
123+
continue
124+
}
122125
tokens = append(tokens, fmt.Sprintf("%s/%s=%s", heritage, key, l[key]))
123126
}
124127
if withQuotes {
@@ -133,10 +136,16 @@ func (l Labels) Serialize(withQuotes bool, txtEncryptEnabled bool, aesKey []byte
133136
return l.SerializePlain(withQuotes)
134137
}
135138

136-
var encryptionNonce []byte = nil
139+
var encryptionNonce []byte
137140
if extractedNonce, nonceExists := l[txtEncryptionNonce]; nonceExists {
138141
encryptionNonce = []byte(extractedNonce)
139-
delete(l, txtEncryptionNonce)
142+
} else {
143+
var err error
144+
encryptionNonce, err = GenerateNonce()
145+
if err != nil {
146+
log.Fatalf("Failed to generate cryptographic nonce %#v.", err)
147+
}
148+
l[txtEncryptionNonce] = string(encryptionNonce)
140149
}
141150

142151
text := l.SerializePlain(false)

registry/txt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func (im *TXTRegistry) generateTXTRecord(r *endpoint.Endpoint) []*endpoint.Endpo
220220

221221
endpoints := make([]*endpoint.Endpoint, 0)
222222

223-
if !im.mapper.recordTypeInAffix() && r.RecordType != endpoint.RecordTypeAAAA {
223+
if !im.txtEncryptEnabled && !im.mapper.recordTypeInAffix() && r.RecordType != endpoint.RecordTypeAAAA {
224224
// old TXT record format
225225
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), endpoint.RecordTypeTXT, r.Labels.Serialize(true, im.txtEncryptEnabled, im.txtEncryptAESKey))
226226
if txt != nil {

registry/txt_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,47 @@ func TestFailGenerateTXT(t *testing.T) {
15271527
assert.Equal(t, expectedTXT, gotTXT)
15281528
}
15291529

1530+
func TestTXTRegistryApplyChangesEncrypt(t *testing.T) {
1531+
p := inmemory.NewInMemoryProvider()
1532+
p.CreateZone(testZone)
1533+
ctxEndpoints := []*endpoint.Endpoint{}
1534+
ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
1535+
1536+
p.ApplyChanges(ctx, &plan.Changes{
1537+
Create: []*endpoint.Endpoint{
1538+
newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
1539+
newEndpointWithOwnerAndOwnedRecord("txt.cname-foobar.test-zone.example.org", "\"h8UQ6jelUFUsEIn7SbFktc2MYXPx/q8lySqI4VwfVtVaIbb2nkHWV/88KKbuLtu7fJNzMir8ELVeVnRSY01KdiIuj7ledqZe5ailEjQaU5Z6uEKd5pgs6sH8\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
1540+
},
1541+
})
1542+
1543+
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte("12345678901234567890123456789012"))
1544+
records, _ := r.Records(ctx)
1545+
changes := &plan.Changes{
1546+
Delete: records,
1547+
}
1548+
1549+
// ensure that encryption nonce gets reused when deleting records
1550+
expected := &plan.Changes{
1551+
Delete: []*endpoint.Endpoint{
1552+
newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
1553+
newEndpointWithOwnerAndOwnedRecord("txt.cname-foobar.test-zone.example.org", "\"h8UQ6jelUFUsEIn7SbFktc2MYXPx/q8lySqI4VwfVtVaIbb2nkHWV/88KKbuLtu7fJNzMir8ELVeVnRSY01KdiIuj7ledqZe5ailEjQaU5Z6uEKd5pgs6sH8\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
1554+
},
1555+
}
1556+
1557+
p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
1558+
mExpected := map[string][]*endpoint.Endpoint{
1559+
"Delete": expected.Delete,
1560+
}
1561+
mGot := map[string][]*endpoint.Endpoint{
1562+
"Delete": got.Delete,
1563+
}
1564+
assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
1565+
assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
1566+
}
1567+
err := r.ApplyChanges(ctx, changes)
1568+
require.NoError(t, err)
1569+
}
1570+
15301571
// TestMultiClusterDifferentRecordTypeOwnership validates the registry handles environments where the same zone is managed by
15311572
// external-dns in different clusters and the ingress record type is different. For example one uses A records and the other
15321573
// uses CNAME. In this environment the first cluster that establishes the owner record should maintain ownership even

0 commit comments

Comments
 (0)