Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
"high_availability_config": "high_availability_config.yml",
"build_stream_config": "build_stream_config.yml",
"gitlab_config": "gitlab_config.yml",
"discovery_config": "discovery_config.yml"
"discovery_config": "discovery_config.yml",
"dns_config": "dns_config.yml"
# "additional_software": "additional_software.json"
}

Expand All @@ -78,6 +79,7 @@
files["provision_config"],
files["network_spec"],
files["software_config"],
files["dns_config"],
# files["high_availability_config"]
],
"security": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,40 @@ def json_file_mandatory(file_path):
"another additional subnet's dynamic_range."
)

# dns_config
DNS_DOMAIN_INVALID_MSG = (
"dns_domain must be a valid DNS domain name (RFC 1035). "
"Use lowercase alphanumeric characters, hyphens, and dots only. "
"Example: hpc.cluster"
)
DNS_DOMAIN_RESERVED_MSG = (
"dns_domain must not use a reserved domain. "
"The following are not permitted: cluster.local, localhost, "
"com, net, org, edu, gov, io."
)
DNS_TTL_RANGE_MSG = (
"dns_ttl must be an integer between 60 and 86400 (seconds)."
)
DNS_CACHE_TTL_RANGE_MSG = (
"dns_cache_ttl must be an integer between 10 and 3600 (seconds)."
)
DNS_CACHE_TTL_EXCEEDS_TTL_MSG = (
"dns_cache_ttl must be less than or equal to dns_ttl. "
"Cache TTL cannot exceed the record TTL."
)
DNS_FABRIC_SUFFIX_FORMAT_MSG = (
"each dns_fabric_suffix must begin with a hyphen and contain "
"only lowercase alphanumeric characters and hyphens. "
"Example: -ib, -stor"
)
DNS_SOA_POSITIVE_INT_MSG = (
"dns_soa values (refresh, retry, expire) must be positive integers."
)
DNS_REVERSE_DISABLED_WARNING_MSG = (
"dns_reverse_enabled is false. MPI and Slurm may require "
"reverse DNS (PTR records) for security validation."
)

# telemetry
MANDATORY_FIELD_FAIL_MSG = "must not be empty"
MYSQLDB_USER_FAIL_MSG = "username should not be kept 'root'."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["dns_config"],
"properties": {
"dns_config": {
"type": "object",
"required": [
"dns_enabled",
"dns_domain",
"dns_ttl",
"dns_reverse_enabled",
"dns_cache_ttl"
],
"properties": {
"dns_enabled": { "type": "boolean" },
"dns_domain": {
"type": "string",
"minLength": 1,
"pattern": "^[a-z0-9]([a-z0-9\\-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9\\-]*[a-z0-9])?)*$"
},
"dns_ttl": {
"type": "integer",
"minimum": 60,
"maximum": 86400
},
"dns_reverse_enabled": { "type": "boolean" },
"dns_fabric_suffixes": {
"type": "array",
"items": {
"type": "string",
"pattern": "^-[a-z0-9][a-z0-9\\-]*$"
}
},
"dns_cache_ttl": {
"type": "integer",
"minimum": 10,
"maximum": 3600
},
"dns_soa": {
"type": "object",
"properties": {
"refresh": { "type": "integer", "minimum": 1 },
"retry": { "type": "integer", "minimum": 1 },
"expire": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
}
},
"additionalProperties": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1442,3 +1442,118 @@ def _ranges_overlap(range_a, range_b):
return a_start <= b_end and b_start <= a_end
except (ValueError, TypeError):
return False


# Reserved domains that must not be used as dns_domain
_RESERVED_DOMAINS = frozenset([
"cluster.local", "localhost",
"com", "net", "org", "edu", "gov", "io",
])

# Regex for a valid DNS label (RFC 1035)
_DNS_LABEL_RE = re.compile(r'^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$')


def validate_dns_config(data):
"""
Validates dns_config input parameters.

Checks:
- dns_domain is a valid RFC 1035 domain name and not reserved.
- dns_ttl is in valid range (60-86400).
- dns_cache_ttl is in valid range (10-3600) and <= dns_ttl.
- dns_fabric_suffixes format (hyphen-prefixed, lowercase alphanumeric).
- dns_soa values are positive integers.

Args:
data (dict): The dns_config dict from dns_config.yml.

Returns:
list: Validation error messages.
"""
errors = []
cfg = data.get("dns_config", {})
if not cfg or not cfg.get("dns_enabled", False):
return errors

# --- dns_domain ---
domain = cfg.get("dns_domain", "")
if domain:
labels = domain.split(".")
valid_domain = all(_DNS_LABEL_RE.match(label) for label in labels) and len(domain) <= 253
if not valid_domain:
errors.append(
create_error_msg(
"dns_config.dns_domain", domain,
en_us_validation_msg.DNS_DOMAIN_INVALID_MSG,
)
)
if domain in _RESERVED_DOMAINS or any(
domain.endswith(f".{rd}") for rd in _RESERVED_DOMAINS
):
errors.append(
create_error_msg(
"dns_config.dns_domain", domain,
en_us_validation_msg.DNS_DOMAIN_RESERVED_MSG,
)
)
else:
errors.append(
create_error_msg(
"dns_config.dns_domain", domain,
en_us_validation_msg.DNS_DOMAIN_INVALID_MSG,
)
)

# --- dns_ttl ---
ttl = cfg.get("dns_ttl", 300)
if not isinstance(ttl, int) or ttl < 60 or ttl > 86400:
errors.append(
create_error_msg(
"dns_config.dns_ttl", str(ttl),
en_us_validation_msg.DNS_TTL_RANGE_MSG,
)
)

# --- dns_cache_ttl ---
cache_ttl = cfg.get("dns_cache_ttl", 60)
if not isinstance(cache_ttl, int) or cache_ttl < 10 or cache_ttl > 3600:
errors.append(
create_error_msg(
"dns_config.dns_cache_ttl", str(cache_ttl),
en_us_validation_msg.DNS_CACHE_TTL_RANGE_MSG,
)
)
elif isinstance(ttl, int) and cache_ttl > ttl:
errors.append(
create_error_msg(
"dns_config.dns_cache_ttl", str(cache_ttl),
en_us_validation_msg.DNS_CACHE_TTL_EXCEEDS_TTL_MSG,
)
)

# --- dns_fabric_suffixes ---
suffix_re = re.compile(r'^-[a-z0-9][a-z0-9\-]*$')
for suffix in cfg.get("dns_fabric_suffixes", []):
if not isinstance(suffix, str) or not suffix_re.match(suffix):
errors.append(
create_error_msg(
"dns_config.dns_fabric_suffixes", str(suffix),
en_us_validation_msg.DNS_FABRIC_SUFFIX_FORMAT_MSG,
)
)

# --- dns_soa ---
soa = cfg.get("dns_soa", {})
if soa:
for field in ("refresh", "retry", "expire"):
val = soa.get(field)
if val is not None and (not isinstance(val, int) or val < 1):
errors.append(
create_error_msg(
f"dns_config.dns_soa.{field}", str(val),
en_us_validation_msg.DNS_SOA_POSITIVE_INT_MSG,
)
)

return errors
Loading