Reject invalid deployment configuration early

This commit is contained in:
ms2k 2026-07-19 02:01:37 +09:00
commit 0e351c3640
7 changed files with 543 additions and 55 deletions

View file

@ -104,7 +104,7 @@ impl ApiApplicationConfig {
let lease_ttl = env_duration("REMORA_LEASE_TTL_SECONDS", 30)?; let lease_ttl = env_duration("REMORA_LEASE_TTL_SECONDS", 30)?;
let target = TargetStoreConfig::from_env().map_err(runtime_error)?; let target = TargetStoreConfig::from_env().map_err(runtime_error)?;
let vault = VaultConfig::from_env().map_err(runtime_error)?; let vault = VaultConfig::from_env().map_err(runtime_error)?;
Ok(Self { let config = Self {
public_bind: env_parse("REMORA_PUBLIC_BIND", "0.0.0.0:8080")?, public_bind: env_parse("REMORA_PUBLIC_BIND", "0.0.0.0:8080")?,
worker_bind: env_parse("REMORA_WORKER_BIND", "0.0.0.0:8443")?, worker_bind: env_parse("REMORA_WORKER_BIND", "0.0.0.0:8443")?,
management: ManagementConfig::from_env(crate::COMPONENT_NAME)?, management: ManagementConfig::from_env(crate::COMPONENT_NAME)?,
@ -156,10 +156,54 @@ impl ApiApplicationConfig {
private_key: PathBuf::from(required_env("REMORA_WORKER_TLS_KEY_FILE")?), private_key: PathBuf::from(required_env("REMORA_WORKER_TLS_KEY_FILE")?),
client_ca: PathBuf::from(required_env("REMORA_WORKER_TLS_CLIENT_CA_FILE")?), client_ca: PathBuf::from(required_env("REMORA_WORKER_TLS_CLIENT_CA_FILE")?),
}, },
}) };
validate_api_limits(
config.database_connections,
&config.coordination,
&config.attempt,
&config.recovery,
&config.public_api,
)?;
Ok(config)
} }
} }
fn validate_api_limits(
database_connections: u32,
coordination: &StoreConfig,
attempt: &AttemptConfig,
recovery: &RecoveryConfig,
public_api: &PublicApiConfig,
) -> Result<(), RuntimeError> {
if database_connections == 0
|| coordination.global_limit == 0
|| coordination.lease_ttl.is_zero()
|| coordination.reserve_ttl.is_zero()
|| coordination.progress_ttl.is_zero()
|| attempt.max_attempts == 0
|| recovery.retry_delay.is_zero()
|| recovery.cleanup_claim_ttl.is_zero()
|| recovery.batch_size == 0
|| public_api.cursor_secret.is_empty()
|| public_api.member_access_ttl.is_zero()
|| public_api.event_retention.is_zero()
|| public_api.event_poll_interval.is_zero()
|| public_api.event_heartbeat_interval.is_zero()
|| public_api.event_ticket_ttl.is_zero()
|| public_api.event_ticket_ttl > Duration::from_secs(60)
|| public_api.event_allowed_origins.is_empty()
|| public_api
.event_allowed_origins
.iter()
.any(|origin| origin.is_empty() || origin.trim() != origin)
{
return Err(RuntimeError::message(
"API limits and timing must be positive and event ticket expiry must not exceed 60 seconds",
));
}
Ok(())
}
impl ApiApplication { impl ApiApplication {
#[must_use] #[must_use]
pub fn new(config: ApiApplicationConfig) -> Self { pub fn new(config: ApiApplicationConfig) -> Self {
@ -657,4 +701,33 @@ mod tests {
"cannot read REMORA_CURSOR_SECRET_FILE: entity not found" "cannot read REMORA_CURSOR_SECRET_FILE: entity not found"
); );
} }
#[test]
fn validates_api_limits_before_external_connections() {
let coordination = StoreConfig {
global_limit: 1,
lease_ttl: Duration::from_secs(30),
reserve_ttl: Duration::from_secs(30),
progress_ttl: Duration::from_secs(60),
cancel_ttl: None,
};
let attempt = AttemptConfig { max_attempts: 1 };
let recovery = RecoveryConfig {
retry_delay: Duration::from_secs(1),
cleanup_claim_ttl: Duration::from_secs(30),
batch_size: 1,
};
let mut public_api = PublicApiConfig {
cursor_secret: vec![1; 32],
member_access_ttl: Duration::from_secs(60),
event_retention: Duration::from_secs(3600),
event_poll_interval: Duration::from_millis(250),
event_heartbeat_interval: Duration::from_secs(15),
event_ticket_ttl: Duration::from_secs(30),
event_allowed_origins: vec!["https://remora.example".into()],
};
assert!(validate_api_limits(1, &coordination, &attempt, &recovery, &public_api).is_ok());
public_api.event_ticket_ttl = Duration::from_secs(61);
assert!(validate_api_limits(1, &coordination, &attempt, &recovery, &public_api).is_err());
}
} }

View file

@ -61,7 +61,7 @@ impl WorkerApplicationConfig {
endpoint: target.endpoint.clone(), endpoint: target.endpoint.clone(),
region: target.region.clone(), region: target.region.clone(),
}); });
Ok(Self { let config = Self {
management: ManagementConfig::from_env(crate::COMPONENT_NAME)?, management: ManagementConfig::from_env(crate::COMPONENT_NAME)?,
database_url: required_env("DATABASE_URL")?, database_url: required_env("DATABASE_URL")?,
database_connections: env_parse("REMORA_DATABASE_MAX_CONNECTIONS", "16")?, database_connections: env_parse("REMORA_DATABASE_MAX_CONNECTIONS", "16")?,
@ -124,7 +124,9 @@ impl WorkerApplicationConfig {
"REMORA_HUGGING_FACE_ENDPOINT", "REMORA_HUGGING_FACE_ENDPOINT",
"https://huggingface.co", "https://huggingface.co",
), ),
}) };
validate_worker_timing(&config.coordination, &config.worker)?;
Ok(config)
} }
} }
@ -324,6 +326,27 @@ fn parse_scheme_ports(input: &str) -> Result<Vec<(String, u16)>, RuntimeError> {
.collect() .collect()
} }
fn validate_worker_timing(
coordination: &StoreConfig,
worker: &WorkerConfig,
) -> Result<(), RuntimeError> {
if coordination.global_limit == 0
|| coordination.lease_ttl.is_zero()
|| coordination.reserve_ttl.is_zero()
|| coordination.progress_ttl.is_zero()
|| worker.local_limit == 0
|| worker.idle_delay.is_zero()
|| worker.renew_interval.is_zero()
|| worker.renew_interval >= coordination.lease_ttl
|| worker.cancel_poll_interval.is_zero()
{
return Err(RuntimeError::message(
"worker limits and timing must be positive, and renewal must precede lease expiry",
));
}
Ok(())
}
async fn wait_for_shutdown() -> Result<(), RuntimeError> { async fn wait_for_shutdown() -> Result<(), RuntimeError> {
#[cfg(unix)] #[cfg(unix)]
{ {
@ -387,4 +410,19 @@ mod tests {
assert!(parse_scheme_ports("https").is_err()); assert!(parse_scheme_ports("https").is_err());
assert!(parse_cidrs("private").is_err()); assert!(parse_cidrs("private").is_err());
} }
#[test]
fn validates_worker_timing_before_external_connections() {
let coordination = StoreConfig {
global_limit: 1,
lease_ttl: Duration::from_secs(30),
reserve_ttl: Duration::from_secs(30),
progress_ttl: Duration::from_secs(60),
cancel_ttl: None,
};
let mut worker = WorkerConfig::default();
assert!(validate_worker_timing(&coordination, &worker).is_ok());
worker.renew_interval = coordination.lease_ttl;
assert!(validate_worker_timing(&coordination, &worker).is_err());
}
} }

View file

@ -2,6 +2,36 @@
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}} {{- end -}}
{{- define "remora.validateValues" -}}
{{- if ge (int .Values.runtime.workerRenewSeconds) (int .Values.coordination.leaseTtlSeconds) -}}
{{- fail "runtime.workerRenewSeconds must be less than coordination.leaseTtlSeconds" -}}
{{- end -}}
{{- range $entry := splitList "," .Values.outbound.allowedSchemePorts -}}
{{- $parts := splitList ":" $entry -}}
{{- $port := int (index $parts 1) -}}
{{- if or (lt $port 1) (gt $port 65535) -}}
{{- fail "outbound.allowedSchemePorts contains a port outside 1..65535" -}}
{{- end -}}
{{- end -}}
{{- if and .Values.networkPolicy.enabled .Values.outbound.allowedCidrs -}}
{{- range $cidr := splitList "," .Values.outbound.allowedCidrs -}}
{{- if not (has $cidr $.Values.networkPolicy.externalEgressCidrs) -}}
{{- fail "every outbound.allowedCidrs entry must appear in networkPolicy.externalEgressCidrs" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if .Values.podDisruptionBudget.enabled -}}
{{- $minimum := toString .Values.podDisruptionBudget.minAvailable -}}
{{- if not (hasSuffix "%" $minimum) -}}
{{- range $component := list "api" "worker" "web" -}}
{{- if gt (int $minimum) (int (get $.Values $component).replicas) -}}
{{- fail "podDisruptionBudget.minAvailable cannot exceed a workload replica count" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- define "remora.fullname" -}} {{- define "remora.fullname" -}}
{{- if .Values.fullnameOverride -}} {{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}

View file

@ -1,3 +1,4 @@
{{- include "remora.validateValues" . }}
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
@ -24,6 +25,7 @@ data:
REMORA_RESERVED_TARGET_SEGMENTS: {{ required "runtime.reservedTargetSegments is required" .Values.runtime.reservedTargetSegments | quote }} REMORA_RESERVED_TARGET_SEGMENTS: {{ required "runtime.reservedTargetSegments is required" .Values.runtime.reservedTargetSegments | quote }}
REMORA_ADMISSION_POLICY_VERSION: {{ required "runtime.admissionPolicyVersion is required" .Values.runtime.admissionPolicyVersion | quote }} REMORA_ADMISSION_POLICY_VERSION: {{ required "runtime.admissionPolicyVersion is required" .Values.runtime.admissionPolicyVersion | quote }}
REMORA_MAX_ATTEMPTS: {{ .Values.runtime.maxAttempts | quote }} REMORA_MAX_ATTEMPTS: {{ .Values.runtime.maxAttempts | quote }}
REMORA_WORKER_RENEW_SECONDS: {{ .Values.runtime.workerRenewSeconds | quote }}
REMORA_RECOVERY_INTERVAL_SECONDS: {{ .Values.runtime.recoveryIntervalSeconds | quote }} REMORA_RECOVERY_INTERVAL_SECONDS: {{ .Values.runtime.recoveryIntervalSeconds | quote }}
REMORA_EVENT_ALLOWED_ORIGINS: {{ required "runtime.eventAllowedOrigins is required" .Values.runtime.eventAllowedOrigins | quote }} REMORA_EVENT_ALLOWED_ORIGINS: {{ required "runtime.eventAllowedOrigins is required" .Values.runtime.eventAllowedOrigins | quote }}
REMORA_EVENT_RETENTION_SECONDS: {{ .Values.runtime.eventRetentionSeconds | quote }} REMORA_EVENT_RETENTION_SECONDS: {{ .Values.runtime.eventRetentionSeconds | quote }}

View file

@ -1,98 +1,423 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object", "type": "object",
"required": ["api", "worker", "web", "database", "coordination", "oidc", "target", "vault"], "additionalProperties": false,
"required": [
"nameOverride",
"fullnameOverride",
"imagePullSecrets",
"podDnsPolicy",
"podDnsConfig",
"api",
"worker",
"web",
"database",
"coordination",
"oidc",
"target",
"vault",
"runtime",
"outbound",
"ingress",
"networkPolicy",
"observability",
"podDisruptionBudget"
],
"properties": { "properties": {
"api": {"$ref": "#/$defs/workload"}, "nameOverride": {"$ref": "#/$defs/dnsLabelOrEmpty"},
"worker": {"$ref": "#/$defs/workload"}, "fullnameOverride": {"$ref": "#/$defs/dnsLabelOrEmpty"},
"podDnsPolicy": {"enum": ["ClusterFirst", "ClusterFirstWithHostNet", "Default", "None"]}, "imagePullSecrets": {
"podDnsConfig": {"type": "object"}, "type": "array",
"web": { "items": {
"allOf": [{"$ref": "#/$defs/workload"}], "type": "object",
"type": "object", "additionalProperties": false,
"required": ["publicOrigin", "oidcClientId"], "required": ["name"],
"properties": { "properties": {"name": {"$ref": "#/$defs/nonEmpty"}}
"publicOrigin": {"type": "string", "format": "uri"},
"oidcClientId": {"type": "string", "minLength": 1}
} }
}, },
"podDnsPolicy": {"enum": ["ClusterFirst", "ClusterFirstWithHostNet", "Default", "None"]},
"podDnsConfig": {"$ref": "#/$defs/podDnsConfig"},
"api": {"$ref": "#/$defs/workload"},
"worker": {"$ref": "#/$defs/workload"},
"web": {"$ref": "#/$defs/webWorkload"},
"database": { "database": {
"type": "object", "type": "object",
"required": ["urlSecret"], "additionalProperties": false,
"properties": {"urlSecret": {"$ref": "#/$defs/secretRef"}} "required": ["urlSecret", "maxApiConnections", "maxWorkerConnections"],
"properties": {
"urlSecret": {"$ref": "#/$defs/secretRef"},
"maxApiConnections": {"$ref": "#/$defs/positiveInteger"},
"maxWorkerConnections": {"$ref": "#/$defs/positiveInteger"}
}
}, },
"coordination": { "coordination": {
"type": "object", "type": "object",
"required": ["urlSecret", "namespace", "globalAttemptLimit"], "additionalProperties": false,
"required": [
"urlSecret",
"namespace",
"globalAttemptLimit",
"leaseTtlSeconds",
"reserveTtlSeconds",
"progressTtlSeconds"
],
"properties": { "properties": {
"urlSecret": {"$ref": "#/$defs/secretRef"}, "urlSecret": {"$ref": "#/$defs/secretRef"},
"namespace": {"type": "string", "minLength": 1}, "namespace": {"$ref": "#/$defs/nonEmpty"},
"globalAttemptLimit": {"type": "integer", "minimum": 1} "globalAttemptLimit": {"$ref": "#/$defs/positiveInteger"},
"leaseTtlSeconds": {"$ref": "#/$defs/positiveInteger"},
"reserveTtlSeconds": {"$ref": "#/$defs/positiveInteger"},
"progressTtlSeconds": {"$ref": "#/$defs/positiveInteger"}
} }
}, },
"oidc": { "oidc": {
"type": "object", "type": "object",
"required": ["issuer", "audience", "resourceClient"], "additionalProperties": false,
"required": ["issuer", "audience", "resourceClient", "projectRoleClaims"],
"properties": { "properties": {
"issuer": {"type": "string", "format": "uri"}, "issuer": {"$ref": "#/$defs/httpUrl"},
"audience": {"type": "string", "minLength": 1}, "audience": {"$ref": "#/$defs/nonEmpty"},
"resourceClient": {"type": "string", "minLength": 1} "resourceClient": {"$ref": "#/$defs/nonEmpty"},
"projectRoleClaims": {"$ref": "#/$defs/commaList"}
} }
}, },
"target": { "target": {
"type": "object", "type": "object",
"required": ["storeId", "endpoint", "bucket", "exclusiveWriter", "credentialsSecret"], "additionalProperties": false,
"required": [
"storeId",
"endpoint",
"bucket",
"region",
"root",
"defaultPrefix",
"exclusiveWriter",
"credentialsSecret"
],
"properties": { "properties": {
"storeId": {"type": "string", "minLength": 1}, "storeId": {"$ref": "#/$defs/nonEmpty"},
"endpoint": {"type": "string", "format": "uri"}, "endpoint": {"$ref": "#/$defs/httpUrl"},
"bucket": {"type": "string", "minLength": 1}, "bucket": {
"type": "string",
"allOf": [
{"pattern": "^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$"},
{"not": {"pattern": "\\.\\."}}
]
},
"region": {"$ref": "#/$defs/nonEmpty"},
"root": {"type": "string", "maxLength": 1024},
"defaultPrefix": {"type": "string", "maxLength": 1024},
"exclusiveWriter": {"const": true}, "exclusiveWriter": {"const": true},
"credentialsSecret": { "credentialsSecret": {
"type": "object", "type": "object",
"additionalProperties": false,
"required": ["name", "accessKey", "secretKey"], "required": ["name", "accessKey", "secretKey"],
"properties": { "properties": {
"name": {"type": "string", "minLength": 1}, "name": {"$ref": "#/$defs/nonEmpty"},
"accessKey": {"type": "string", "minLength": 1}, "accessKey": {"$ref": "#/$defs/nonEmpty"},
"secretKey": {"type": "string", "minLength": 1} "secretKey": {"$ref": "#/$defs/nonEmpty"}
} }
} }
} }
}, },
"vault": { "vault": {
"type": "object", "type": "object",
"required": ["address", "path", "apiRole", "workerRole"], "additionalProperties": false,
"required": [
"image",
"address",
"path",
"kubernetesAuthMount",
"audience",
"apiRole",
"workerRole",
"caSecret"
],
"properties": { "properties": {
"address": {"type": "string", "format": "uri"}, "image": {"$ref": "#/$defs/nonEmpty"},
"path": {"type": "string", "minLength": 1}, "address": {"$ref": "#/$defs/httpUrl"},
"apiRole": {"type": "string", "minLength": 1}, "path": {"$ref": "#/$defs/nonEmpty"},
"workerRole": {"type": "string", "minLength": 1} "kubernetesAuthMount": {"$ref": "#/$defs/nonEmpty"},
} "audience": {"$ref": "#/$defs/nonEmpty"},
} "apiRole": {"$ref": "#/$defs/nonEmpty"},
}, "workerRole": {"$ref": "#/$defs/nonEmpty"},
"$defs": { "caSecret": {
"workload": {
"type": "object",
"required": ["replicas", "image"],
"properties": {
"replicas": {"type": "integer", "minimum": 1},
"image": {
"type": "object", "type": "object",
"required": ["repository", "tag", "pullPolicy"], "additionalProperties": false,
"required": ["name", "key"],
"properties": { "properties": {
"repository": {"type": "string", "minLength": 1}, "name": {"type": "string"},
"tag": {"type": "string", "minLength": 1}, "key": {"$ref": "#/$defs/nonEmpty"}
"digest": {"type": "string", "pattern": "^(|sha256:[0-9a-f]{64})$"},
"pullPolicy": {"enum": ["Always", "IfNotPresent", "Never"]}
} }
} }
} }
}, },
"runtime": {
"type": "object",
"additionalProperties": false,
"required": [
"reservedTargetSegments",
"admissionPolicyVersion",
"maxAttempts",
"workerLocalAttemptLimit",
"workerRenewSeconds",
"recoveryIntervalSeconds",
"eventAllowedOrigins",
"eventRetentionSeconds",
"logFilter"
],
"properties": {
"reservedTargetSegments": {"$ref": "#/$defs/commaList"},
"admissionPolicyVersion": {"$ref": "#/$defs/nonEmpty"},
"maxAttempts": {"$ref": "#/$defs/positiveInteger"},
"workerLocalAttemptLimit": {"$ref": "#/$defs/positiveInteger"},
"workerRenewSeconds": {"$ref": "#/$defs/positiveInteger"},
"recoveryIntervalSeconds": {"$ref": "#/$defs/positiveInteger"},
"eventAllowedOrigins": {"$ref": "#/$defs/httpUrlList"},
"eventRetentionSeconds": {"$ref": "#/$defs/positiveInteger"},
"logFilter": {"$ref": "#/$defs/nonEmpty"}
}
},
"outbound": {
"type": "object",
"additionalProperties": false,
"required": ["allowedSchemePorts", "allowedCidrs", "maxRedirects"],
"properties": {
"allowedSchemePorts": {
"type": "string",
"pattern": "^[a-z][a-z0-9+.-]*:[1-9][0-9]{0,4}(,[a-z][a-z0-9+.-]*:[1-9][0-9]{0,4})*$"
},
"allowedCidrs": {"$ref": "#/$defs/cidrListOrEmpty"},
"maxRedirects": {"type": "integer", "minimum": 0, "maximum": 100}
}
},
"ingress": {
"type": "object",
"additionalProperties": false,
"required": ["enabled", "className", "annotations", "host", "tlsSecretName"],
"properties": {
"enabled": {"type": "boolean"},
"className": {"type": "string"},
"annotations": {"$ref": "#/$defs/stringMap"},
"host": {
"type": "string",
"pattern": "^[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$"
},
"tlsSecretName": {"type": "string"}
}
},
"networkPolicy": {
"type": "object",
"additionalProperties": false,
"required": [
"enabled",
"ingressNamespaceSelector",
"ingressPodSelector",
"monitoringNamespaceSelector",
"monitoringPodSelector",
"dnsPodLabels",
"dependencyPodLabels",
"externalEgressCidrs",
"allowPublicSourceEgress"
],
"properties": {
"enabled": {"type": "boolean"},
"ingressNamespaceSelector": {"$ref": "#/$defs/labelSelector"},
"ingressPodSelector": {"$ref": "#/$defs/labelSelector"},
"monitoringNamespaceSelector": {"$ref": "#/$defs/labelSelector"},
"monitoringPodSelector": {"$ref": "#/$defs/labelSelector"},
"dnsPodLabels": {"$ref": "#/$defs/labelMapList"},
"dependencyPodLabels": {"$ref": "#/$defs/labelMapList"},
"externalEgressCidrs": {
"type": "array",
"uniqueItems": true,
"items": {"$ref": "#/$defs/cidr"}
},
"allowPublicSourceEgress": {"type": "boolean"}
}
},
"observability": {
"type": "object",
"additionalProperties": false,
"required": ["serviceMonitor", "prometheusRule"],
"properties": {
"serviceMonitor": {
"type": "object",
"additionalProperties": false,
"required": ["enabled", "interval"],
"properties": {
"enabled": {"type": "boolean"},
"interval": {"type": "string", "pattern": "^[1-9][0-9]*(ms|s|m|h)$"}
}
},
"prometheusRule": {
"type": "object",
"additionalProperties": false,
"required": ["enabled"],
"properties": {"enabled": {"type": "boolean"}}
}
}
},
"podDisruptionBudget": {
"type": "object",
"additionalProperties": false,
"required": ["enabled", "minAvailable"],
"properties": {
"enabled": {"type": "boolean"},
"minAvailable": {
"oneOf": [
{"type": "integer", "minimum": 0},
{"type": "string", "pattern": "^(100|[0-9]{1,2})%$"}
]
}
}
}
},
"$defs": {
"nonEmpty": {"type": "string", "minLength": 1},
"positiveInteger": {"type": "integer", "minimum": 1, "maximum": 2147483647},
"dnsLabelOrEmpty": {
"type": "string",
"pattern": "^(|[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)$",
"maxLength": 63
},
"httpUrl": {"type": "string", "pattern": "^https?://[^[:space:]]+$"},
"httpUrlList": {
"type": "string",
"pattern": "^https?://[^,[:space:]]+(,https?://[^,[:space:]]+)*$"
},
"commaList": {"type": "string", "pattern": "^[^,[:space:]]+(,[^,[:space:]]+)*$"},
"cidr": {"type": "string", "pattern": "^[0-9A-Fa-f:.]+/[0-9]{1,3}$"},
"cidrListOrEmpty": {
"type": "string",
"pattern": "^(|[0-9A-Fa-f:.]+/[0-9]{1,3}(,[0-9A-Fa-f:.]+/[0-9]{1,3})*)$"
},
"stringMap": {
"type": "object",
"additionalProperties": {"type": "string"}
},
"labelMapList": {
"type": "array",
"items": {"$ref": "#/$defs/stringMap"}
},
"labelSelector": {
"type": "object",
"additionalProperties": false,
"properties": {
"matchLabels": {"$ref": "#/$defs/stringMap"},
"matchExpressions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["key", "operator"],
"properties": {
"key": {"$ref": "#/$defs/nonEmpty"},
"operator": {"enum": ["In", "NotIn", "Exists", "DoesNotExist"]},
"values": {
"type": "array",
"items": {"type": "string"}
}
}
}
}
}
},
"podDnsConfig": {
"type": "object",
"additionalProperties": false,
"properties": {
"nameservers": {
"type": "array",
"maxItems": 3,
"items": {"type": "string", "minLength": 1}
},
"searches": {
"type": "array",
"maxItems": 32,
"items": {"type": "string", "minLength": 1}
},
"options": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": {"$ref": "#/$defs/nonEmpty"},
"value": {"type": "string"}
}
}
}
}
},
"image": {
"type": "object",
"additionalProperties": false,
"required": ["repository", "tag", "digest", "pullPolicy"],
"properties": {
"repository": {"$ref": "#/$defs/nonEmpty"},
"tag": {"$ref": "#/$defs/nonEmpty"},
"digest": {"type": "string", "pattern": "^(|sha256:[0-9a-f]{64})$"},
"pullPolicy": {"enum": ["Always", "IfNotPresent", "Never"]}
}
},
"resources": {
"type": "object",
"additionalProperties": false,
"required": ["requests", "limits"],
"properties": {
"requests": {"$ref": "#/$defs/resourceMap"},
"limits": {"$ref": "#/$defs/resourceMap"}
}
},
"resourceMap": {
"type": "object",
"additionalProperties": {"type": ["string", "number"]}
},
"workload": {
"type": "object",
"additionalProperties": false,
"required": ["replicas", "image", "resources"],
"properties": {
"replicas": {"$ref": "#/$defs/positiveInteger"},
"image": {"$ref": "#/$defs/image"},
"resources": {"$ref": "#/$defs/resources"}
}
},
"webWorkload": {
"type": "object",
"additionalProperties": false,
"required": [
"replicas",
"image",
"publicOrigin",
"apiUrl",
"eventsUrl",
"oidcClientId",
"oidcScope",
"cspConnectSrc",
"resources"
],
"properties": {
"replicas": {"$ref": "#/$defs/positiveInteger"},
"image": {"$ref": "#/$defs/image"},
"publicOrigin": {"$ref": "#/$defs/httpUrl"},
"apiUrl": {"type": "string", "pattern": "^(https?://|/).+"},
"eventsUrl": {"type": "string", "pattern": "^(|https?://.+|wss?://.+|/.+)$"},
"oidcClientId": {"$ref": "#/$defs/nonEmpty"},
"oidcScope": {"$ref": "#/$defs/nonEmpty"},
"cspConnectSrc": {"$ref": "#/$defs/nonEmpty"},
"resources": {"$ref": "#/$defs/resources"}
}
},
"secretRef": { "secretRef": {
"type": "object", "type": "object",
"additionalProperties": false,
"required": ["name", "key"], "required": ["name", "key"],
"properties": { "properties": {
"name": {"type": "string", "minLength": 1}, "name": {"$ref": "#/$defs/nonEmpty"},
"key": {"type": "string", "minLength": 1} "key": {"$ref": "#/$defs/nonEmpty"}
} }
} }
} }

View file

@ -109,6 +109,7 @@ runtime:
admissionPolicyVersion: deployment-v1 admissionPolicyVersion: deployment-v1
maxAttempts: 3 maxAttempts: 3
workerLocalAttemptLimit: 4 workerLocalAttemptLimit: 4
workerRenewSeconds: 10
recoveryIntervalSeconds: 2 recoveryIntervalSeconds: 2
eventAllowedOrigins: https://remora.example.com eventAllowedOrigins: https://remora.example.com
eventRetentionSeconds: 3600 eventRetentionSeconds: 3600

View file

@ -376,6 +376,17 @@ rollout_remora() {
done done
} }
reject_chart_values() {
local reason="$1"
shift
if helm template remora "$ROOT/deploy/charts/remora" \
--namespace "$FIXTURE_NAMESPACE" \
"$@" >/dev/null 2>&1; then
printf 'chart accepted %s\n' "$reason" >&2
return 1
fi
}
validate_deployment_assets() { validate_deployment_assets() {
local chart="$ROOT/deploy/charts/remora" local chart="$ROOT/deploy/charts/remora"
helm lint "$chart" helm lint "$chart"
@ -392,12 +403,20 @@ validate_deployment_assets() {
grep -q 'absent(remora_ready{component="remora-worker"})' "$RUNTIME/deployment-policy-rendered.yaml" grep -q 'absent(remora_ready{component="remora-worker"})' "$RUNTIME/deployment-policy-rendered.yaml"
grep -q 'automountServiceAccountToken: false' "$RUNTIME/deployment-policy-rendered.yaml" grep -q 'automountServiceAccountToken: false' "$RUNTIME/deployment-policy-rendered.yaml"
grep -q 'readOnlyRootFilesystem: true' "$RUNTIME/deployment-policy-rendered.yaml" grep -q 'readOnlyRootFilesystem: true' "$RUNTIME/deployment-policy-rendered.yaml"
if helm template remora "$chart" \ reject_chart_values "an unprotected target namespace" --set target.exclusiveWriter=false
--namespace "$FIXTURE_NAMESPACE" \ reject_chart_values "an unknown top-level value" --set unexpected=true
--set target.exclusiveWriter=false >/dev/null 2>&1; then reject_chart_values "an unknown nested value" --set runtime.unexpected=true
printf 'chart accepted an unprotected target namespace\n' >&2 reject_chart_values "a zero lease TTL" --set coordination.leaseTtlSeconds=0
return 1 reject_chart_values "an invalid OIDC issuer" --set-string oidc.issuer=identity.invalid
fi reject_chart_values "an invalid target bucket" --set-string target.bucket=two..dots
reject_chart_values "an out-of-range source port" \
--set-string outbound.allowedSchemePorts=https:70000
reject_chart_values "renewal at lease expiry" \
--set runtime.workerRenewSeconds=30
reject_chart_values "application-only CIDR reachability" \
--set-string outbound.allowedCidrs=10.0.0.0/8
reject_chart_values "an impossible disruption budget" \
--set podDisruptionBudget.minAvailable=3
if grep -R -E \ if grep -R -E \
'(BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|postgres://[^[:space:]]+:[^[:space:]]+@|VAULT_TOKEN[=:][^$])' \ '(BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|postgres://[^[:space:]]+:[^[:space:]]+@|VAULT_TOKEN[=:][^$])' \
"$chart" "$ROOT/docs/spec" >/dev/null; then "$chart" "$ROOT/docs/spec" >/dev/null; then