Skip to content

Commit 3ae9834

Browse files
committed
fix normalizing dns record values for mx, srv and other type
1 parent 883dc32 commit 3ae9834

File tree

7 files changed

+335
-119
lines changed

7 files changed

+335
-119
lines changed

api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmd.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ public class CreateDnsRecordCmd extends BaseAsyncCmd {
5050
description = "ID of the DNS zone")
5151
private Long dnsZoneId;
5252

53-
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Record name")
53+
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "DNS record name")
5454
private String name;
5555

56-
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "Record type (A, CNAME)")
56+
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "DNS record type (e.g., A, AAAA, CNAME, MX, TXT, etc.)")
5757
private String type;
5858

5959
@Parameter(name = ApiConstants.CONTENTS, type = CommandType.LIST, collectionType = CommandType.STRING, required = true,

plugins/dns/powerdns/pom.xml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,4 @@
2727
<version>4.23.0.0-SNAPSHOT</version>
2828
<relativePath>../../pom.xml</relativePath>
2929
</parent>
30-
31-
<properties>
32-
<maven.compiler.source>11</maven.compiler.source>
33-
<maven.compiler.target>11</maven.compiler.target>
34-
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
35-
</properties>
36-
3730
</project>

plugins/dns/powerdns/src/test/java/org/apache/cloudstack/dns/DnsProviderUtilTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
package org.apache.cloudstack.dns;
1919

2020
import static org.apache.cloudstack.dns.DnsProviderUtil.appendPublicSuffixToZone;
21-
import static org.apache.cloudstack.dns.DnsProviderUtil.normalizeDomain;
21+
import static org.apache.cloudstack.dns.DnsProviderUtil.normalizeDomainForDb;
2222
import static org.junit.Assert.assertEquals;
2323
import static org.junit.Assert.fail;
2424

@@ -89,13 +89,13 @@ public void testAppendPublicSuffix() {
8989
if (Strings.isNotBlank(publicSuffix)) {
9090
result = executeAppendSuffixTest(userZoneName, publicSuffix);
9191
} else {
92-
result = appendPublicSuffixToZone(normalizeDomain(userZoneName), publicSuffix);
92+
result = appendPublicSuffixToZone(normalizeDomainForDb(userZoneName), publicSuffix);
9393
}
9494
assertEquals(expectedResult, result);
9595
}
9696
}
9797

9898
String executeAppendSuffixTest(String zoneName, String domainSuffix) {
99-
return appendPublicSuffixToZone(normalizeDomain(zoneName), domainSuffix);
99+
return appendPublicSuffixToZone(normalizeDomainForDb(zoneName), domainSuffix);
100100
}
101101
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.dns;
19+
20+
import static org.apache.cloudstack.dns.DnsRecord.RecordType.A;
21+
import static org.apache.cloudstack.dns.DnsRecord.RecordType.AAAA;
22+
import static org.apache.cloudstack.dns.DnsRecord.RecordType.CNAME;
23+
import static org.apache.cloudstack.dns.DnsRecord.RecordType.MX;
24+
import static org.apache.cloudstack.dns.DnsRecord.RecordType.NS;
25+
import static org.apache.cloudstack.dns.DnsRecord.RecordType.PTR;
26+
import static org.apache.cloudstack.dns.DnsRecord.RecordType.SRV;
27+
import static org.apache.cloudstack.dns.DnsRecord.RecordType.TXT;
28+
import static org.junit.Assert.assertEquals;
29+
import static org.junit.Assert.fail;
30+
31+
import java.util.Arrays;
32+
import java.util.Collection;
33+
34+
import org.junit.Test;
35+
import org.junit.runner.RunWith;
36+
import org.junit.runners.Parameterized;
37+
38+
@RunWith(Parameterized.class)
39+
public class NormalizeDnsRecordValueTest {
40+
41+
private final String description;
42+
private final String input;
43+
private final DnsRecord.RecordType recordType;
44+
private final String expected;
45+
private final boolean expectException;
46+
47+
public NormalizeDnsRecordValueTest(String description, String input,
48+
DnsRecord.RecordType recordType,
49+
String expected, boolean expectException) {
50+
this.description = description;
51+
this.input = input;
52+
this.recordType = recordType;
53+
this.expected = expected;
54+
this.expectException = expectException;
55+
}
56+
57+
@Parameterized.Parameters(name = "{0}")
58+
public static Collection<Object[]> data() {
59+
return Arrays.asList(new Object[][] {
60+
61+
// ----------------------------------------------------------------
62+
// Guard: blank/null value — all record types should throw
63+
// ----------------------------------------------------------------
64+
{"null value, A record", null, A, null, true},
65+
{"empty value, A record", "", A, null, true},
66+
{"blank value, A record", " ", A, null, true},
67+
68+
{"null value, AAAA record", null, AAAA, null, true},
69+
{"null value, CNAME record", null, CNAME, null, true},
70+
{"null value, MX record", null, MX, null, true},
71+
{"null value, TXT record", null, TXT, null, true},
72+
{"null value, SRV record", null, SRV, null, true},
73+
{"null value, NS record", null, NS, null, true},
74+
{"null value, PTR record", null, PTR, null, true},
75+
76+
// ----------------------------------------------------------------
77+
// A record
78+
// ----------------------------------------------------------------
79+
{"A: valid IPv4", "93.184.216.34", A, "93.184.216.34", false},
80+
{"A: valid IPv4 with whitespace", " 93.184.216.34 ", A, "93.184.216.34", false},
81+
{"A: loopback", "127.0.0.1", A, "127.0.0.1", false},
82+
{"A: all-zeros", "0.0.0.0", A, "0.0.0.0", false},
83+
{"A: broadcast", "255.255.255.255", A, "255.255.255.255", false},
84+
{"A: private 10.x", "10.0.0.1", A, "10.0.0.1", false},
85+
{"A: private 192.168.x", "192.168.1.1", A, "192.168.1.1", false},
86+
87+
{"A: IPv6 rejected", "2001:db8::1", A, null, true},
88+
{"A: domain rejected", "example.com", A, null, true},
89+
{"A: partial IP rejected", "192.168.1", A, null, true},
90+
{"A: trailing dot rejected", "93.184.216.34.", A, null, true},
91+
{"A: octet out of range rejected", "256.0.0.1", A, null, true},
92+
93+
// ----------------------------------------------------------------
94+
// AAAA record
95+
// ----------------------------------------------------------------
96+
{"AAAA: full IPv6", "2001:0db8:0000:0000:0000:0000:0000:0001", AAAA,
97+
"2001:0db8:0000:0000:0000:0000:0000:0001", false},
98+
99+
{"AAAA: compressed IPv6", "2001:db8::1", AAAA, "2001:db8::1", false},
100+
{"AAAA: loopback", "::1", AAAA, "::1", false},
101+
{"AAAA: all zeros", "::", AAAA, "::", false},
102+
{"AAAA: whitespace", " 2001:db8::1 ", AAAA, "2001:db8::1", false},
103+
104+
{"AAAA: IPv4 rejected", "93.184.216.34", AAAA, null, true},
105+
{"AAAA: domain rejected", "example.com", AAAA, null, true},
106+
{"AAAA: invalid hex rejected", "2001:db8::xyz", AAAA, null, true},
107+
108+
// ----------------------------------------------------------------
109+
// CNAME record
110+
// ----------------------------------------------------------------
111+
{"CNAME: basic", "target.example.com", CNAME, "target.example.com.", false},
112+
{"CNAME: uppercase", "TARGET.EXAMPLE.COM", CNAME, "target.example.com.", false},
113+
{"CNAME: trailing dot", "target.example.com.", CNAME, "target.example.com.", false},
114+
{"CNAME: whitespace", " target.example.com ", CNAME, "target.example.com.", false},
115+
{"CNAME: subdomain", "sub.target.example.com", CNAME, "sub.target.example.com.", false},
116+
117+
{"CNAME: IP rejected", "192.168.1.1", CNAME, null, true},
118+
{"CNAME: invalid label", "-bad.example.com", CNAME, null, true},
119+
120+
// ----------------------------------------------------------------
121+
// NS record
122+
// ----------------------------------------------------------------
123+
{"NS: basic", "ns1.example.com", NS, "ns1.example.com.", false},
124+
{"NS: uppercase", "NS1.EXAMPLE.COM", NS, "ns1.example.com.", false},
125+
{"NS: trailing dot", "ns1.example.com.", NS, "ns1.example.com.", false},
126+
{"NS: subdomain", "ns1.sub.example.com", NS, "ns1.sub.example.com.", false},
127+
128+
{"NS: IP rejected", "8.8.8.8", NS, null, true},
129+
{"NS: invalid label", "ns1-.example.com", NS, null, true},
130+
131+
// ----------------------------------------------------------------
132+
// PTR record
133+
// ----------------------------------------------------------------
134+
{"PTR: basic", "host.example.com", PTR, "host.example.com.", false},
135+
{"PTR: in-addr.arpa", "1.168.192.in-addr.arpa", PTR, "1.168.192.in-addr.arpa.", false},
136+
{"PTR: uppercase", "HOST.EXAMPLE.COM", PTR, "host.example.com.", false},
137+
{"PTR: trailing dot", "host.example.com.", PTR, "host.example.com.", false},
138+
139+
{"PTR: IP rejected", "192.168.1.1", PTR, null, true},
140+
{"PTR: invalid label", "-host.example.com", PTR, null, true},
141+
142+
// ----------------------------------------------------------------
143+
// MX record
144+
// ----------------------------------------------------------------
145+
{"MX: standard", "10 mail.example.com", MX, "10 mail.example.com.", false},
146+
{"MX: zero priority", "0 mail.example.com", MX, "0 mail.example.com.", false},
147+
{"MX: max priority", "65535 mail.example.com", MX, "65535 mail.example.com.", false},
148+
{"MX: uppercase", "10 MAIL.EXAMPLE.COM", MX, "10 mail.example.com.", false},
149+
{"MX: trailing dot", "10 mail.example.com.", MX, "10 mail.example.com.", false},
150+
{"MX: extra whitespace", "10 mail.example.com", MX, "10 mail.example.com.", false},
151+
152+
{"MX: missing domain", "10", MX, null, true},
153+
{"MX: priority out of range", "65536 mail.example.com", MX, null, true},
154+
{"MX: non-numeric priority", "abc mail.example.com", MX, null, true},
155+
{"MX: IP rejected", "10 192.168.1.1", MX, null, true},
156+
157+
// ----------------------------------------------------------------
158+
// SRV record
159+
// ----------------------------------------------------------------
160+
{"SRV: standard", "10 20 443 target.example.com", SRV, "10 20 443 target.example.com.", false},
161+
{"SRV: zeros", "0 0 1 target.example.com", SRV, "0 0 1 target.example.com.", false},
162+
{"SRV: max values", "65535 65535 65535 target.example.com", SRV, "65535 65535 65535 target.example.com.", false},
163+
{"SRV: uppercase", "10 20 443 TARGET.EXAMPLE.COM", SRV, "10 20 443 target.example.com.", false},
164+
{"SRV: trailing dot", "10 20 443 target.example.com.", SRV, "10 20 443 target.example.com.", false},
165+
166+
{"SRV: missing target", "10 20 443", SRV, null, true},
167+
{"SRV: port 0", "10 20 0 target.example.com", SRV, null, true},
168+
{"SRV: priority out of range", "65536 20 443 target.example.com", SRV, null, true},
169+
{"SRV: IP rejected", "10 20 443 192.168.1.1", SRV, null, true},
170+
171+
// ----------------------------------------------------------------
172+
// TXT record
173+
// ----------------------------------------------------------------
174+
{"TXT: trim", " hello world ", TXT, "hello world", false},
175+
{"TXT: already clean", "v=spf1 include:example.com ~all", TXT, "v=spf1 include:example.com ~all", false},
176+
{"TXT: special chars", "v=DKIM1; k=rsa; p=MIGf", TXT, "v=DKIM1; k=rsa; p=MIGf", false},
177+
{"TXT: unicode", "héllo wörld", TXT, "héllo wörld", false},
178+
{"TXT: multiple spaces", "key=value with spaces", TXT, "key=value with spaces", false},
179+
{"TXT: quoted", "\"quoted value\"", TXT, "\"quoted value\"", false},
180+
{"TXT: blank", " ", TXT, null, true},
181+
{"TXT: newline", "\n", TXT, null, true},
182+
});
183+
}
184+
185+
@Test
186+
public void testNormalizeDnsRecordValue() {
187+
if (expectException) {
188+
try {
189+
DnsProviderUtil.normalizeDnsRecordValue(input, recordType);
190+
fail("Expected IllegalArgumentException for [" + description + "] input='" + input + "'");
191+
} catch (IllegalArgumentException ignored) {}
192+
} else {
193+
String result = DnsProviderUtil.normalizeDnsRecordValue(input, recordType);
194+
assertEquals(description, expected, result);
195+
}
196+
}
197+
}

server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public DnsServer addDnsServer(AddDnsServerCmd cmd) {
172172
}
173173

174174
if (StringUtils.isNotBlank(publicDomainSuffix)) {
175-
publicDomainSuffix = DnsProviderUtil.normalizeDomain(publicDomainSuffix);
175+
publicDomainSuffix = DnsProviderUtil.normalizeDomainForDb(publicDomainSuffix);
176176
}
177177

178178
DnsProviderType type = cmd.getProvider();
@@ -263,7 +263,7 @@ public DnsServer updateDnsServer(UpdateDnsServerCmd cmd) {
263263
}
264264

265265
if (cmd.getPublicDomainSuffix() != null) {
266-
dnsServer.setPublicDomainSuffix(DnsProviderUtil.normalizeDomain(cmd.getPublicDomainSuffix()));
266+
dnsServer.setPublicDomainSuffix(DnsProviderUtil.normalizeDomainForDb(cmd.getPublicDomainSuffix()));
267267
}
268268

269269
if (cmd.getNameServers() != null) {
@@ -552,7 +552,7 @@ public DnsZone allocateDnsZone(CreateDnsZoneCmd cmd) {
552552
throw new InvalidParameterValueException("DNS zone name cannot be empty");
553553
}
554554

555-
String dnsZoneName = DnsProviderUtil.normalizeDomain(cmd.getName());
555+
String dnsZoneName = DnsProviderUtil.normalizeDomainForDb(cmd.getName());
556556
DnsServerVO server = dnsServerDao.findById(cmd.getDnsServerId());
557557
if (server == null) {
558558
throw new InvalidParameterValueException(String.format("DNS server not found for the given ID: %s", cmd.getDnsServerId()));

0 commit comments

Comments
 (0)