Skip to content

Commit 873db01

Browse files
committed
fix: Avllow trailing dots in DomainName
1 parent 29e920d commit 873db01

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

crates/stackable-operator/src/commons/networking.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ use crate::validation;
1111
Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, JsonSchema,
1212
)]
1313
#[serde(try_from = "String", into = "String")]
14-
pub struct DomainName(#[validate(regex(path = "validation::RFC_1123_SUBDOMAIN_REGEX"))] String);
14+
pub struct DomainName(#[validate(regex(path = "validation::FQDN_REGEX"))] String);
1515

1616
impl FromStr for DomainName {
1717
type Err = validation::Errors;
1818

1919
fn from_str(value: &str) -> Result<Self, Self::Err> {
20-
validation::is_rfc_1123_subdomain(value)?;
20+
validation::is_fqdn(value)?;
2121
Ok(DomainName(value.to_owned()))
2222
}
2323
}

crates/stackable-operator/src/validation.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,23 @@ use const_format::concatcp;
1515
use regex::Regex;
1616
use snafu::Snafu;
1717

18+
/// Minimal length required by RFC 1123 is 63. Up to 255 allowed, unsupported by k8s.
19+
const RFC_1123_LABEL_MAX_LENGTH: usize = 63;
1820
// FIXME: According to https://www.rfc-editor.org/rfc/rfc1035#section-2.3.1 domain names must start with a letter
1921
// (and not a number).
2022
const RFC_1123_LABEL_FMT: &str = "[a-z0-9]([-a-z0-9]*[a-z0-9])?";
23+
const RFC_1123_LABEL_ERROR_MSG: &str = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character";
24+
25+
/// This is a subdomain's max length in DNS (RFC 1123)
26+
const RFC_1123_SUBDOMAIN_MAX_LENGTH: usize = 253;
2127
const RFC_1123_SUBDOMAIN_FMT: &str =
2228
concatcp!(RFC_1123_LABEL_FMT, "(\\.", RFC_1123_LABEL_FMT, ")*");
2329
const RFC_1123_SUBDOMAIN_ERROR_MSG: &str = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character";
24-
const RFC_1123_LABEL_ERROR_MSG: &str = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character";
2530

26-
// This is a subdomain's max length in DNS (RFC 1123)
27-
const RFC_1123_SUBDOMAIN_MAX_LENGTH: usize = 253;
28-
// Minimal length required by RFC 1123 is 63. Up to 255 allowed, unsupported by k8s.
29-
const RFC_1123_LABEL_MAX_LENGTH: usize = 63;
31+
const FQDN_MAX_LENGTH: usize = RFC_1123_SUBDOMAIN_MAX_LENGTH;
32+
/// Same as [`RFC_1123_SUBDOMAIN_FMT`], but allows a trailing slash
33+
const FQDN_FMT: &str = concatcp!(RFC_1123_SUBDOMAIN_FMT, "\\.?");
34+
const FQDN_ERROR_MSG: &str = "a FQDN must consist of lower case alphanumeric characters, '-' or '.', and must start with an alphanumeric character and end with an alphanumeric character or '.'";
3035

3136
const RFC_1035_LABEL_FMT: &str = "[a-z]([-a-z0-9]*[a-z0-9])?";
3237
const RFC_1035_LABEL_ERROR_MSG: &str = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character";
@@ -46,6 +51,9 @@ const KERBEROS_REALM_NAME_ERROR_MSG: &str =
4651
"Kerberos realm name must only contain alphanumeric characters, '-', and '.'";
4752

4853
// Lazily initialized regular expressions
54+
pub(crate) static FQDN_REGEX: LazyLock<Regex> =
55+
LazyLock::new(|| Regex::new(&format!("^{FQDN_FMT}$")).expect("failed to compile FQDN regex"));
56+
4957
pub(crate) static RFC_1123_SUBDOMAIN_REGEX: LazyLock<Regex> = LazyLock::new(|| {
5058
Regex::new(&format!("^{RFC_1123_SUBDOMAIN_FMT}$"))
5159
.expect("failed to compile RFC 1123 subdomain regex")
@@ -178,6 +186,23 @@ fn validate_all(validations: impl IntoIterator<Item = Result<(), Error>>) -> Res
178186
}
179187
}
180188

189+
pub fn is_fqdn(value: &str) -> Result {
190+
validate_all([
191+
validate_str_length(value, FQDN_MAX_LENGTH),
192+
validate_str_regex(
193+
value,
194+
&FQDN_REGEX,
195+
FQDN_ERROR_MSG,
196+
&[
197+
"example.com",
198+
"example.com.",
199+
"cluster.local",
200+
"cluster.local.",
201+
],
202+
),
203+
])
204+
}
205+
181206
/// Tests for a string that conforms to the definition of a subdomain in DNS (RFC 1123).
182207
pub fn is_rfc_1123_subdomain(value: &str) -> Result {
183208
validate_all([
@@ -394,6 +419,8 @@ mod tests {
394419
#[case(&"a".repeat(253))]
395420
fn is_rfc_1123_subdomain_pass(#[case] value: &str) {
396421
assert!(is_rfc_1123_subdomain(value).is_ok());
422+
// Every valid RFC1123 is also a valid FQDN
423+
assert!(is_fqdn(value).is_ok());
397424
}
398425

399426
#[test]

0 commit comments

Comments
 (0)