use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use workload_spec::{
EnvValue, EnvVar, ExposeSpec, HealthProbe, Healthcheck, ImageRef, LifecycleArchetype,
MeshExpose, MeshIdent, Millis, NamespaceId, ResourceLimits, RestartPolicy, SchemaVersion,
SecretMount, SecretRef, SecretTarget, StopPolicy, TenantId, TierTag, VolumeMount, Workload,
WorkloadSpec, HOST_NETWORK_ANNOTATION, HOST_NETWORK_VALUE, PUBLIC_IP_TAINT,
REQUIRES_TAINT_ANNOTATION,
};
pub const INGRESS_WORKLOAD_NAME: &str = "cloudflared-ingress";
const TOKEN_MOUNT_PATH: &str = "/run/secrets/tunnel-token";
const METRICS_LISTEN: &str = "127.0.0.1:20241";
const METRICS_PORT: u16 = 20241;
const READY_PATH: &str = "/ready";
const DEFAULT_BIN: &str = "/usr/local/bin/cloudflared";
pub const DEFAULT_TOKEN_SECRET: &str = "cloudflare-tunnel-token";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloudflaredIngressSpec {
pub tunnel_id: String,
#[serde(default = "default_token_secret")]
pub token_secret: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub command: Option<Vec<String>>,
}
fn default_token_secret() -> String {
DEFAULT_TOKEN_SECRET.to_string()
}
impl CloudflaredIngressSpec {
pub fn into_container_workload(&self, image: ImageRef) -> Workload {
let env = vec![
literal_env("TUNNEL_TOKEN_FILE", TOKEN_MOUNT_PATH.into()),
];
let secrets = vec![SecretMount {
source: SecretRef::Cluster {
name: self.token_secret.clone(),
},
target: SecretTarget::File {
path: TOKEN_MOUNT_PATH.into(),
mode: 0o400,
},
}];
let mut annotations = HashMap::new();
annotations.insert(
HOST_NETWORK_ANNOTATION.to_string(),
HOST_NETWORK_VALUE.to_string(),
);
annotations.insert(
REQUIRES_TAINT_ANNOTATION.to_string(),
PUBLIC_IP_TAINT.to_string(),
);
let mut labels = HashMap::new();
labels.insert("yah.ingress.tunnel-id".to_string(), self.tunnel_id.clone());
let spec = WorkloadSpec {
schema_version: SchemaVersion::V1,
name: INGRESS_WORKLOAD_NAME.into(),
image,
tier: TierTag("infra".into()),
tenant: TenantId::singleton(),
namespace: NamespaceId::singleton(),
replicas: 1,
command: Some(self.command.clone().unwrap_or_else(default_command)),
entrypoint: None,
workdir: None,
user: None,
env,
secrets,
volumes: Vec::<VolumeMount>::new(),
resources: ResourceLimits {
memory_mb: 256,
cpu_millis: 512,
ephemeral_storage_mb: 128,
},
depends_on: vec![],
healthcheck: Some(Healthcheck {
probe: HealthProbe::HttpGet {
path: READY_PATH.into(),
port: METRICS_PORT,
expect_status: None,
},
interval: Millis::from_secs(10),
timeout: Millis::from_secs(2),
initial_delay: Millis::from_secs(10),
failure_threshold: 3,
}),
restart_policy: RestartPolicy::Always,
archetype: Some(LifecycleArchetype::Appliance),
stop_policy: StopPolicy {
signal: 15,
grace_period: Millis::from_secs(5),
},
expose: ExposeSpec {
mesh: MeshExpose {
identity: MeshIdent(INGRESS_WORKLOAD_NAME.into()),
ports: vec![],
allow_from: vec![],
},
public: None,
operator: None,
},
labels,
annotations,
};
Workload::Container(spec)
}
}
fn default_command() -> Vec<String> {
vec![
DEFAULT_BIN.into(),
"tunnel".into(),
"--no-autoupdate".into(),
"--metrics".into(),
METRICS_LISTEN.into(),
"run".into(),
]
}
fn literal_env(name: &str, value: String) -> EnvVar {
EnvVar {
name: name.into(),
value: EnvValue::Literal { value },
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_image() -> ImageRef {
ImageRef {
registry: "docker.io".into(),
repository: "cloudflare/cloudflared".into(),
tag: "2026.1.0".into(),
digest: workload_spec::testing::test_digest(),
}
}
fn sample_spec() -> CloudflaredIngressSpec {
CloudflaredIngressSpec {
tunnel_id: "abc123".into(),
token_secret: DEFAULT_TOKEN_SECRET.into(),
command: None,
}
}
fn lower(spec: &CloudflaredIngressSpec) -> WorkloadSpec {
match spec.into_container_workload(sample_image()) {
Workload::Container(s) => s,
other => panic!("expected Container, got {other:?}"),
}
}
#[test]
fn appliance_is_pinned_and_always_restarted() {
let w = lower(&sample_spec());
assert_eq!(w.archetype, Some(LifecycleArchetype::Appliance));
assert_eq!(w.restart_policy, RestartPolicy::Always);
}
#[test]
fn placement_and_networking_match_the_passway_appliance() {
let w = lower(&sample_spec());
assert_eq!(w.requires_taint(), Some(PUBLIC_IP_TAINT));
assert!(w.wants_host_network());
assert_eq!(w.tier.0, "infra");
}
#[test]
fn token_rides_a_file_mount_and_never_appears_in_the_spec() {
let spec = sample_spec();
let w = lower(&spec);
assert_eq!(w.secrets.len(), 1);
assert_eq!(
w.secrets[0].source,
SecretRef::Cluster {
name: DEFAULT_TOKEN_SECRET.into()
}
);
assert!(matches!(
&w.secrets[0].target,
SecretTarget::File { path, mode } if path.as_os_str() == TOKEN_MOUNT_PATH && *mode == 0o400
));
let token_env = w
.env
.iter()
.find(|e| e.name == "TUNNEL_TOKEN_FILE")
.expect("TUNNEL_TOKEN_FILE env");
assert_eq!(
token_env.value,
EnvValue::Literal {
value: TOKEN_MOUNT_PATH.into()
}
);
let json = serde_json::to_string(&w).unwrap();
assert!(!json.contains("--token"), "token flag leaked into argv");
assert!(
!json.contains("TUNNEL_TOKEN\""),
"bare TUNNEL_TOKEN env leaked: {json}"
);
}
#[test]
fn command_runs_the_tunnel_with_the_metrics_listener() {
let w = lower(&sample_spec());
let cmd = w.command.expect("command is spelled out, not inherited");
assert_eq!(
cmd,
vec![
"/usr/local/bin/cloudflared",
"tunnel",
"--no-autoupdate",
"--metrics",
"127.0.0.1:20241",
"run",
]
);
}
#[test]
fn readiness_probe_targets_cloudflared_ready_not_a_bare_port() {
let w = lower(&sample_spec());
let hc = w.healthcheck.expect("healthcheck");
assert_eq!(
hc.probe,
HealthProbe::HttpGet {
path: "/ready".into(),
port: 20241,
expect_status: None,
}
);
}
#[test]
fn declares_no_inbound_listener() {
let w = lower(&sample_spec());
assert!(w.expose.public.is_none(), "a connector dials out");
assert!(w.expose.mesh.ports.is_empty());
}
#[test]
fn tunnel_id_is_labelled_for_the_rule_publisher() {
let w = lower(&sample_spec());
assert_eq!(
w.labels.get("yah.ingress.tunnel-id").map(String::as_str),
Some("abc123")
);
}
#[test]
fn does_not_collide_with_the_passway_appliance_name() {
assert_ne!(
INGRESS_WORKLOAD_NAME,
super::super::passway_ingress::INGRESS_WORKLOAD_NAME
);
}
#[test]
fn passes_shape_validation() {
let w = lower(&sample_spec());
workload_spec::validate::shape(&w).expect("cloudflared appliance is a valid spec");
}
#[test]
fn token_secret_defaults_when_absent_from_json() {
let spec: CloudflaredIngressSpec =
serde_json::from_str(r#"{"tunnel_id":"t1"}"#).expect("minimal JSON parses");
assert_eq!(spec.token_secret, DEFAULT_TOKEN_SECRET);
assert!(spec.command.is_none());
}
}