Skip to content

Commit

Permalink
Add iptable config and remove port config
Browse files Browse the repository at this point in the history
  • Loading branch information
hanneary committed Nov 29, 2023
1 parent 2dc3289 commit c4253f8
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 86 deletions.
137 changes: 81 additions & 56 deletions src/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,6 @@ async fn process_dockerfile<R: AsyncRead + std::marker::Unpin>(
|entrypoint| build_user_service(entrypoint, wait_for_env, last_user, user_env_vars),
)?;

if let Some(true) = exposed_port.map(|port| port == 443) {
return Err(DockerError::RestrictedPortExposed(exposed_port.unwrap()).into());
}

let ev_domain = std::env::var("EV_DOMAIN").unwrap_or_else(|_| String::from("evervault.com"));

let data_plane_url = format!(
Expand All @@ -234,35 +230,36 @@ async fn process_dockerfile<R: AsyncRead + std::marker::Unpin>(
data_plane_run_script = format!("{data_plane_run_script} {port}");
}

let bootstrap_script_content = r#"ifconfig lo 127.0.0.1\n echo \"enclave.local\" > /etc/hostname \n echo \"127.0.0.1 enclave.local\" >> /etc/hosts \n hostname -F /etc/hostname \necho \"Booting enclave...\"\nexec runsvdir /etc/service"#;

let installer_bundle_url = format!(
"https://cage-build-assets.{}/installer/{}.tar.gz",
ev_domain, installer_version
);
let installer_bundle = "runtime-dependencies.tar.gz";
let installer_destination = format!("{INSTALLER_DIRECTORY}/{installer_bundle}");

let egress = build_config.clone().egress;
let egress_settings = if egress.is_enabled() {
json!({
"ports": &egress.clone().get_ports(),
"allow_list": &egress.clone().get_destinations()
})
} else {
json!({})
};

let mut dataplane_info = json!({
"api_key_auth": &build_config.api_key_auth(),
"trx_logging_enabled": &build_config.trx_logging_enabled(),
"forward_proxy_protocol": build_config.forward_proxy_protocol(),
"trusted_headers": build_config.trusted_headers(),
});

if egress.enabled {
dataplane_info["egress"] = egress_settings;
}
let egress = build_config.clone().egress;
let egress_config = if egress.is_enabled() {
dataplane_info["egress"] = json!({
"allow_list": &egress.clone().get_destinations()
});
r#"iptables -A OUTPUT -t nat -p tcp --dport 443 ! -d 127.0.0.1 -j DNAT --to-destination 127.0.0.1:4444\nip route add default via 127.0.0.1 dev lo\niptables -t nat -A POSTROUTING -o lo -s 0.0.0.0 -j SNAT --to-source 127.0.0.1\n"#
} else {
""
};

let loopback_config = r#"ifconfig lo 127.0.0.1\n echo \"enclave.local\" > /etc/hostname \n echo \"127.0.0.1 enclave.local\" >> /etc/hosts \n hostname -F /etc/hostname \n"#;

let bootstrap_script = r#"echo \"Booting enclave...\"\nexec runsvdir /etc/service"#;

let bootstrap_script_content =
format!("{}{}{}", loopback_config, egress_config, bootstrap_script);

let installer_bundle_url = format!(
"https://cage-build-assets.{}/installer/{}.tar.gz",
ev_domain, installer_version
);
let installer_bundle = "runtime-dependencies.tar.gz";
let installer_destination = format!("{INSTALLER_DIRECTORY}/{installer_bundle}");

if let Some(healthcheck) = build_config.healthcheck.as_deref() {
dataplane_info["healthcheck"] = json!(healthcheck);
Expand Down Expand Up @@ -303,7 +300,7 @@ async fn process_dockerfile<R: AsyncRead + std::marker::Unpin>(
injected_directives,
vec![Directive::new_run(
crate::docker::utils::write_command_to_script(
bootstrap_script_content,
&bootstrap_script_content,
"/bootstrap",
&[],
),
Expand Down Expand Up @@ -428,7 +425,7 @@ pub fn build_user_service(

#[cfg(test)]
mod test {
use super::{process_dockerfile, BuildError};
use super::process_dockerfile;
use crate::cert::CertValidityPeriod;
use crate::config::EgressSettings;
use crate::config::ScalingSettings;
Expand All @@ -440,7 +437,7 @@ mod test {
use std::iter::zip;
use tempfile::TempDir;

fn get_config() -> ValidatedCageBuildConfig {
fn get_config(egress_enabled: bool) -> ValidatedCageBuildConfig {
ValidatedCageBuildConfig {
cage_name: "test".into(),
cage_uuid: "1234".into(),
Expand All @@ -449,9 +446,8 @@ mod test {
app_uuid: "3241".into(),
dockerfile: "".into(),
egress: EgressSettings {
enabled: false,
enabled: egress_enabled,
destinations: None,
ports: Some(vec!["433".to_string()]),
},
scaling: ScalingSettings {
desired_replicas: 2,
Expand Down Expand Up @@ -484,7 +480,7 @@ RUN touch /hello-script;\
ENTRYPOINT ["sh", "/hello-script"]"#;
let mut readable_contents = sample_dockerfile_contents.as_bytes();

let config = get_config();
let config = get_config(false);

let data_plane_version = "0.0.0".to_string();
let installer_version = "abcdef".to_string();
Expand Down Expand Up @@ -547,7 +543,7 @@ RUN touch /hello-script;\
ENTRYPOINT ["sh", "/hello-script"]"#;
let mut readable_contents = sample_dockerfile_contents.as_bytes();

let config = get_config();
let config = get_config(false);

let data_plane_version = "0.0.0".to_string();
let installer_version = "abcdef".to_string();
Expand Down Expand Up @@ -612,7 +608,7 @@ RUN touch /hello-script;\
ENTRYPOINT ["sh", "/hello-script"]"#;
let mut readable_contents = sample_dockerfile_contents.as_bytes();

let config = get_config();
let config = get_config(false);

let data_plane_version = "0.0.0".to_string();
let installer_version = "abcdef".to_string();
Expand Down Expand Up @@ -676,7 +672,7 @@ RUN touch /hello-script;\
ENTRYPOINT ["sh", "/hello-script"]"#;
let mut readable_contents = sample_dockerfile_contents.as_bytes();

let config = get_config();
let config = get_config(false);

let data_plane_version = "0.0.0".to_string();
let installer_version = "abcdef".to_string();
Expand Down Expand Up @@ -727,16 +723,17 @@ ENTRYPOINT ["/bootstrap", "1>&2"]
}

#[tokio::test]
async fn test_process_dockerfile_with_restricted_reserved_port() {
async fn test_process_dockerfile_with_valid_reserved_port() {
let sample_dockerfile_contents = r#"FROM alpine
RUN touch /hello-script;\
/bin/sh -c "echo -e '"'#!/bin/sh\nwhile true; do echo "hello"; sleep 2; done;\n'"' > /hello-script"
EXPOSE 443
EXPOSE 3443
ENTRYPOINT ["sh", "/hello-script"]"#;
let mut readable_contents = sample_dockerfile_contents.as_bytes();

let config = get_config();
let mut config: ValidatedCageBuildConfig = get_config(false);
config.healthcheck = Some("/health".into());

let data_plane_version = "0.0.0".to_string();
let installer_version = "abcdef".to_string();
Expand All @@ -748,28 +745,55 @@ ENTRYPOINT ["sh", "/hello-script"]"#;
false,
)
.await;
assert_eq!(processed_file.is_err(), true);
assert_eq!(processed_file.is_ok(), true);
let processed_file = processed_file.unwrap();

assert!(matches!(
processed_file,
Err(BuildError::DockerError(
crate::docker::error::DockerError::RestrictedPortExposed(443)
))
));
let expected_output_contents = r##"FROM alpine
RUN touch /hello-script;\
/bin/sh -c "echo -e '"'#!/bin/sh\nwhile true; do echo "hello"; sleep 2; done;\n'"' > /hello-script"
USER root
RUN mkdir -p /opt/evervault
ADD https://cage-build-assets.evervault.com/installer/abcdef.tar.gz /opt/evervault/runtime-dependencies.tar.gz
RUN cd /opt/evervault ; tar -xzf runtime-dependencies.tar.gz ; sh ./installer.sh ; rm runtime-dependencies.tar.gz
RUN echo {\"api_key_auth\":true,\"forward_proxy_protocol\":false,\"healthcheck\":\"/health\",\"trusted_headers\":[\"X-Evervault-*\"],\"trx_logging_enabled\":true} > /etc/dataplane-config.json
RUN mkdir -p /etc/service/user-entrypoint
RUN printf "#!/bin/sh\nsleep 5\necho \"Checking status of data-plane\"\nSVDIR=/etc/service sv check data-plane || exit 1\necho \"Data-plane up and running\"\nwhile ! grep -q \"EV_CAGE_INITIALIZED\" /etc/customer-env\n do echo \"Env not ready, sleeping user process for one second\"\n sleep 1\n done \n . /etc/customer-env\n\necho \"Booting user service...\"\ncd %s\nexec sh /hello-script\n" "$PWD" > /etc/service/user-entrypoint/run && chmod +x /etc/service/user-entrypoint/run
ADD https://cage-build-assets.evervault.com/runtime/0.0.0/data-plane/egress-disabled/tls-termination-enabled /opt/evervault/data-plane
RUN chmod +x /opt/evervault/data-plane
RUN mkdir -p /etc/service/data-plane
RUN printf "#!/bin/sh\necho \"Booting Evervault data plane...\"\nexec /opt/evervault/data-plane 3443\n" > /etc/service/data-plane/run && chmod +x /etc/service/data-plane/run
RUN printf "#!/bin/sh\nifconfig lo 127.0.0.1\n echo \"enclave.local\" > /etc/hostname \n echo \"127.0.0.1 enclave.local\" >> /etc/hosts \n hostname -F /etc/hostname \necho \"Booting enclave...\"\nexec runsvdir /etc/service\n" > /bootstrap && chmod +x /bootstrap
ENTRYPOINT ["/bootstrap", "1>&2"]
"##;

let expected_directives = docker::parse::DockerfileDecoder::decode_dockerfile_from_src(
expected_output_contents.as_bytes(),
)
.await
.unwrap();

assert_eq!(expected_directives.len(), processed_file.len());
for (expected_directive, processed_directive) in
zip(expected_directives.iter(), processed_file.iter())
{
let expected_directive = expected_directive.to_string();
let processed_directive = processed_directive.to_string();
assert_eq!(expected_directive, processed_directive);
}
}

#[tokio::test]
async fn test_process_dockerfile_with_valid_reserved_port() {
async fn test_process_dockerfile_with_user_directive() {
let sample_dockerfile_contents = r#"FROM alpine
USER someuser
RUN touch /hello-script;\
/bin/sh -c "echo -e '"'#!/bin/sh\nwhile true; do echo "hello"; sleep 2; done;\n'"' > /hello-script"
EXPOSE 3443
ENTRYPOINT ["sh", "/hello-script"]"#;
let mut readable_contents = sample_dockerfile_contents.as_bytes();

let mut config: ValidatedCageBuildConfig = get_config();
config.healthcheck = Some("/health".into());
let config = get_config(false);

let data_plane_version = "0.0.0".to_string();
let installer_version = "abcdef".to_string();
Expand All @@ -785,15 +809,16 @@ ENTRYPOINT ["sh", "/hello-script"]"#;
let processed_file = processed_file.unwrap();

let expected_output_contents = r##"FROM alpine
USER someuser
RUN touch /hello-script;\
/bin/sh -c "echo -e '"'#!/bin/sh\nwhile true; do echo "hello"; sleep 2; done;\n'"' > /hello-script"
USER root
RUN mkdir -p /opt/evervault
ADD https://cage-build-assets.evervault.com/installer/abcdef.tar.gz /opt/evervault/runtime-dependencies.tar.gz
RUN cd /opt/evervault ; tar -xzf runtime-dependencies.tar.gz ; sh ./installer.sh ; rm runtime-dependencies.tar.gz
RUN echo {\"api_key_auth\":true,\"forward_proxy_protocol\":false,\"healthcheck\":\"/health\",\"trusted_headers\":[\"X-Evervault-*\"],\"trx_logging_enabled\":true} > /etc/dataplane-config.json
RUN echo {\"api_key_auth\":true,\"forward_proxy_protocol\":false,\"trusted_headers\":[\"X-Evervault-*\"],\"trx_logging_enabled\":true} > /etc/dataplane-config.json
RUN mkdir -p /etc/service/user-entrypoint
RUN printf "#!/bin/sh\nsleep 5\necho \"Checking status of data-plane\"\nSVDIR=/etc/service sv check data-plane || exit 1\necho \"Data-plane up and running\"\nwhile ! grep -q \"EV_CAGE_INITIALIZED\" /etc/customer-env\n do echo \"Env not ready, sleeping user process for one second\"\n sleep 1\n done \n . /etc/customer-env\n\necho \"Booting user service...\"\ncd %s\nexec sh /hello-script\n" "$PWD" > /etc/service/user-entrypoint/run && chmod +x /etc/service/user-entrypoint/run
RUN printf "#!/bin/sh\nsleep 5\necho \"Checking status of data-plane\"\nSVDIR=/etc/service sv check data-plane || exit 1\necho \"Data-plane up and running\"\nwhile ! grep -q \"EV_CAGE_INITIALIZED\" /etc/customer-env\n do echo \"Env not ready, sleeping user process for one second\"\n sleep 1\n done \n . /etc/customer-env\n\necho \"Booting user service...\"\ncd %s\nsu someuser -c 'exec sh /hello-script'\n" "$PWD" > /etc/service/user-entrypoint/run && chmod +x /etc/service/user-entrypoint/run
ADD https://cage-build-assets.evervault.com/runtime/0.0.0/data-plane/egress-disabled/tls-termination-enabled /opt/evervault/data-plane
RUN chmod +x /opt/evervault/data-plane
RUN mkdir -p /etc/service/data-plane
Expand All @@ -819,7 +844,7 @@ ENTRYPOINT ["/bootstrap", "1>&2"]
}

#[tokio::test]
async fn test_process_dockerfile_with_user_directive() {
async fn test_process_dockerfile_with_egress_enabled() {
let sample_dockerfile_contents = r#"FROM alpine
USER someuser
Expand All @@ -829,7 +854,7 @@ EXPOSE 3443
ENTRYPOINT ["sh", "/hello-script"]"#;
let mut readable_contents = sample_dockerfile_contents.as_bytes();

let config = get_config();
let config = get_config(true);

let data_plane_version = "0.0.0".to_string();
let installer_version = "abcdef".to_string();
Expand All @@ -852,14 +877,14 @@ USER root
RUN mkdir -p /opt/evervault
ADD https://cage-build-assets.evervault.com/installer/abcdef.tar.gz /opt/evervault/runtime-dependencies.tar.gz
RUN cd /opt/evervault ; tar -xzf runtime-dependencies.tar.gz ; sh ./installer.sh ; rm runtime-dependencies.tar.gz
RUN echo {\"api_key_auth\":true,\"forward_proxy_protocol\":false,\"trusted_headers\":[\"X-Evervault-*\"],\"trx_logging_enabled\":true} > /etc/dataplane-config.json
RUN echo {\"api_key_auth\":true,\"egress\":{\"allow_list\":\"*\"},\"forward_proxy_protocol\":false,\"trusted_headers\":[\"X-Evervault-*\"],\"trx_logging_enabled\":true} > /etc/dataplane-config.json
RUN mkdir -p /etc/service/user-entrypoint
RUN printf "#!/bin/sh\nsleep 5\necho \"Checking status of data-plane\"\nSVDIR=/etc/service sv check data-plane || exit 1\necho \"Data-plane up and running\"\nwhile ! grep -q \"EV_CAGE_INITIALIZED\" /etc/customer-env\n do echo \"Env not ready, sleeping user process for one second\"\n sleep 1\n done \n . /etc/customer-env\n\necho \"Booting user service...\"\ncd %s\nsu someuser -c 'exec sh /hello-script'\n" "$PWD" > /etc/service/user-entrypoint/run && chmod +x /etc/service/user-entrypoint/run
ADD https://cage-build-assets.evervault.com/runtime/0.0.0/data-plane/egress-disabled/tls-termination-enabled /opt/evervault/data-plane
ADD https://cage-build-assets.evervault.com/runtime/0.0.0/data-plane/egress-enabled/tls-termination-enabled /opt/evervault/data-plane
RUN chmod +x /opt/evervault/data-plane
RUN mkdir -p /etc/service/data-plane
RUN printf "#!/bin/sh\necho \"Booting Evervault data plane...\"\nexec /opt/evervault/data-plane 3443\n" > /etc/service/data-plane/run && chmod +x /etc/service/data-plane/run
RUN printf "#!/bin/sh\nifconfig lo 127.0.0.1\n echo \"enclave.local\" > /etc/hostname \n echo \"127.0.0.1 enclave.local\" >> /etc/hosts \n hostname -F /etc/hostname \necho \"Booting enclave...\"\nexec runsvdir /etc/service\n" > /bootstrap && chmod +x /bootstrap
RUN printf "#!/bin/sh\nifconfig lo 127.0.0.1\n echo \"enclave.local\" > /etc/hostname \n echo \"127.0.0.1 enclave.local\" >> /etc/hosts \n hostname -F /etc/hostname \niptables -A OUTPUT -t nat -p tcp --dport 443 ! -d 127.0.0.1 -j DNAT --to-destination 127.0.0.1:4444\nip route add default via 127.0.0.1 dev lo\niptables -t nat -A POSTROUTING -o lo -s 0.0.0.0 -j SNAT --to-source 127.0.0.1\necho \"Booting enclave...\"\nexec runsvdir /etc/service\n" > /bootstrap && chmod +x /bootstrap
ENTRYPOINT ["/bootstrap", "1>&2"]
"##;

Expand Down Expand Up @@ -892,7 +917,7 @@ EXPOSE 3443
ENTRYPOINT ["sh", "/hello-script"]"#;
let mut readable_contents = sample_dockerfile_contents.as_bytes();

let config = get_config();
let config = get_config(false);

let data_plane_version = "0.0.0".to_string();
let installer_version = "abcdef".to_string();
Expand Down
12 changes: 1 addition & 11 deletions src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ pub struct InitArgs {
#[clap(long = "self-destruct")]
pub is_time_bound: bool,

/// Comma separated list of ports to allow egress on (e.g. 443,465,998), default port is 443 if none are supplied
#[clap(long = "egress-ports")]
pub egress_ports: Option<String>,

/// Comma separated list of destinations to allow traffic to from the enclave e.g api.evervault.com, default is allow all
#[clap(long = "egress-destinations")]
pub egress_destinations: Option<String>,
Expand Down Expand Up @@ -110,11 +106,7 @@ impl std::convert::From<InitArgs> for CageConfig {
app_uuid: None,
team_uuid: None,
debug: val.debug,
egress: EgressSettings::new(
convert_comma_list(val.egress_ports),
convert_comma_list(val.egress_destinations),
val.egress,
),
egress: EgressSettings::new(convert_comma_list(val.egress_destinations), val.egress),
scaling: Some(ScalingSettings {
desired_replicas: val.desired_replicas.unwrap_or(2),
}),
Expand Down Expand Up @@ -231,7 +223,6 @@ mod init_tests {
is_time_bound: false,
disable_api_key_auth: false,
trx_logging_disabled: false,
egress_ports: Some("443".to_string()),
egress_destinations: Some("evervault.com".to_string()),
forward_proxy_protocol: false,
trusted_headers: Some("X-Evervault-*".to_string()),
Expand All @@ -256,7 +247,6 @@ trusted_headers = ["X-Evervault-*"]
[egress]
enabled = true
destinations = ["evervault.com"]
ports = ["443"]
[scaling]
desired_replicas = 2
Expand Down
21 changes: 2 additions & 19 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,25 @@ use thiserror::Error;
pub struct EgressSettings {
pub enabled: bool,
pub destinations: Option<Vec<String>>,
pub ports: Option<Vec<String>>,
}

impl EgressSettings {
pub fn new(
ports: Option<Vec<String>>,
destinations: Option<Vec<String>>,
enabled: bool,
) -> EgressSettings {
let enabled = enabled || destinations.is_some() || ports.is_some();
pub fn new(destinations: Option<Vec<String>>, enabled: bool) -> EgressSettings {
let enabled = enabled || destinations.is_some();
let destinations = if enabled && destinations.is_none() {
Some(vec!["*".to_string()])
} else {
destinations.clone()
};
let ports = if enabled && ports.is_none() {
Some(vec!["443".to_string()])
} else {
ports.clone()
};
EgressSettings {
enabled,
destinations,
ports,
}
}

pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn get_ports(self) -> String {
self.ports
.map(|ports| ports.join(","))
.unwrap_or("443".to_string())
}
pub fn get_destinations(self) -> String {
self.destinations
.map(|destination| destination.join(","))
Expand Down Expand Up @@ -590,7 +574,6 @@ mod test {
egress: super::EgressSettings {
enabled: false,
destinations: None,
ports: Some(vec!["443".to_string()]),
},
scaling: Some(super::ScalingSettings {
desired_replicas: 2,
Expand Down

0 comments on commit c4253f8

Please sign in to comment.