diff --git a/README.md b/README.md index ab431a13a15..6160c08e8cc 100644 --- a/README.md +++ b/README.md @@ -344,6 +344,27 @@ These settings can be changed at any time. * `settings.updates.version-lock`: Controls the version that will be selected when you issue an update request. Can be locked to a specific version like `v1.0.0`, or `latest` to take the latest available version. Defaults to `latest`. * `settings.updates.ignore-waves`: Updates are rolled out in waves to reduce the impact of issues. For testing purposes, you can set this to `true` to ignore those waves and update immediately. +#### Network settings + +##### Proxy settings + +These settings will configure the proxying behavior of the following services: +* For all variants: + * [containerd.service](packages/containerd/containerd.service) + * [host-containerd.service](packages/host-ctr/host-containerd.service) +* For Kubernetes variants: + * [kubelet.service](packages/kubernetes-1.18/kubelet.service) +* For the ECS variant: + * [docker.service](packages/docker-engine/docker.service) + * [ecs.service](packages/ecs-agent/ecs.service) + +* `settings.network.https-proxy`: The HTTPS proxy server to be used by services listed above. +* `settings.network.no-proxy`: A list of hosts that are excluded from proxying. + +The no-proxy list will automatically include entries for localhost. + +If you're running a Kubernetes variant, the no-proxy list will automatically include the Kubernetes API server endpoint and other commonly used Kubernetes DNS suffixes to facilitate intra-cluster networking. + #### Time settings * `settings.ntp.time-servers`: A list of NTP servers used to set and verify the system time. diff --git a/packages/containerd/containerd.service b/packages/containerd/containerd.service index 9b82300877b..9f47867d19d 100644 --- a/packages/containerd/containerd.service +++ b/packages/containerd/containerd.service @@ -5,6 +5,7 @@ After=network-online.target configured.target Wants=network-online.target configured.target [Service] +EnvironmentFile=/etc/network/proxy.env ExecStart=/usr/bin/containerd Type=notify Delegate=yes diff --git a/packages/docker-engine/docker.service b/packages/docker-engine/docker.service index 6fb7ba3271f..4efffa623c3 100644 --- a/packages/docker-engine/docker.service +++ b/packages/docker-engine/docker.service @@ -10,6 +10,7 @@ StartLimitIntervalSec=60s [Service] Type=notify +EnvironmentFile=/etc/network/proxy.env ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock ExecReload=/bin/kill -s HUP $MAINPID Delegate=yes diff --git a/packages/ecs-agent/ecs.service b/packages/ecs-agent/ecs.service index d1b2e0707cf..3e03c977785 100644 --- a/packages/ecs-agent/ecs.service +++ b/packages/ecs-agent/ecs.service @@ -11,6 +11,7 @@ Restart=on-failure RestartPreventExitStatus=5 RestartSec=1s EnvironmentFile=-/etc/ecs/ecs.config +EnvironmentFile=/etc/network/proxy.env Environment=ECS_CHECKPOINT=true ExecStartPre=/sbin/iptables -t nat -A PREROUTING -d 169.254.170.2/32 \ -p tcp -m tcp --dport 80 -j DNAT --to-destination 127.0.0.1:51679 diff --git a/packages/host-ctr/host-containerd.service b/packages/host-ctr/host-containerd.service index 5cd27c2e230..98c58d23747 100644 --- a/packages/host-ctr/host-containerd.service +++ b/packages/host-ctr/host-containerd.service @@ -5,6 +5,7 @@ After=network-online.target configured.target Wants=network-online.target configured.target [Service] +EnvironmentFile=/etc/network/proxy.env ExecStart=/usr/bin/containerd --config /etc/host-containerd/config.toml Type=notify Delegate=yes diff --git a/packages/kubernetes-1.15/kubelet.service b/packages/kubernetes-1.15/kubelet.service index bb6376d2cc9..fc9bcad9b46 100644 --- a/packages/kubernetes-1.15/kubelet.service +++ b/packages/kubernetes-1.15/kubelet.service @@ -7,6 +7,7 @@ BindsTo=containerd.service [Service] Type=notify +EnvironmentFile=/etc/network/proxy.env EnvironmentFile=/etc/kubernetes/kubelet/env ExecStartPre=/sbin/iptables -P FORWARD ACCEPT # Pull the pause container image before starting `kubelet` so `containerd/cri` wouldn't have to diff --git a/packages/kubernetes-1.16/kubelet.service b/packages/kubernetes-1.16/kubelet.service index bb6376d2cc9..fc9bcad9b46 100644 --- a/packages/kubernetes-1.16/kubelet.service +++ b/packages/kubernetes-1.16/kubelet.service @@ -7,6 +7,7 @@ BindsTo=containerd.service [Service] Type=notify +EnvironmentFile=/etc/network/proxy.env EnvironmentFile=/etc/kubernetes/kubelet/env ExecStartPre=/sbin/iptables -P FORWARD ACCEPT # Pull the pause container image before starting `kubelet` so `containerd/cri` wouldn't have to diff --git a/packages/kubernetes-1.17/kubelet.service b/packages/kubernetes-1.17/kubelet.service index bb6376d2cc9..fc9bcad9b46 100644 --- a/packages/kubernetes-1.17/kubelet.service +++ b/packages/kubernetes-1.17/kubelet.service @@ -7,6 +7,7 @@ BindsTo=containerd.service [Service] Type=notify +EnvironmentFile=/etc/network/proxy.env EnvironmentFile=/etc/kubernetes/kubelet/env ExecStartPre=/sbin/iptables -P FORWARD ACCEPT # Pull the pause container image before starting `kubelet` so `containerd/cri` wouldn't have to diff --git a/packages/kubernetes-1.18/kubelet.service b/packages/kubernetes-1.18/kubelet.service index bb6376d2cc9..fc9bcad9b46 100644 --- a/packages/kubernetes-1.18/kubelet.service +++ b/packages/kubernetes-1.18/kubelet.service @@ -7,6 +7,7 @@ BindsTo=containerd.service [Service] Type=notify +EnvironmentFile=/etc/network/proxy.env EnvironmentFile=/etc/kubernetes/kubelet/env ExecStartPre=/sbin/iptables -P FORWARD ACCEPT # Pull the pause container image before starting `kubelet` so `containerd/cri` wouldn't have to diff --git a/packages/release/proxy-env b/packages/release/proxy-env new file mode 100644 index 00000000000..754625ac5d6 --- /dev/null +++ b/packages/release/proxy-env @@ -0,0 +1,6 @@ +{{#if settings.network.https-proxy}} +HTTPS_PROXY={{settings.network.https-proxy}} +https_proxy={{settings.network.https-proxy}} +{{/if}} +NO_PROXY={{#each settings.network.no-proxy}}{{this}},{{/each}}localhost,127.0.0.1{{#if settings.kubernetes.api-server}},{{host settings.kubernetes.api-server}}{{/if}}{{#if settings.kubernetes.cluster-domain}},.{{settings.kubernetes.cluster-domain}}{{/if}} +no_proxy={{#each settings.network.no-proxy}}{{this}},{{/each}}localhost,127.0.0.1{{#if settings.kubernetes.api-server}},{{host settings.kubernetes.api-server}}{{/if}}{{#if settings.kubernetes.cluster-domain}},.{{settings.kubernetes.cluster-domain}}{{/if}} diff --git a/packages/release/release.spec b/packages/release/release.spec index 552ea9442fe..da99539b9e8 100644 --- a/packages/release/release.spec +++ b/packages/release/release.spec @@ -13,6 +13,7 @@ Source98: release-systemd-system.conf Source99: release-tmpfiles.conf Source200: motd.template +Source201: proxy-env Source1000: eth0.xml Source1002: configured.target @@ -122,6 +123,7 @@ install -p -m 0644 ${LICENSEPATH}.mount %{buildroot}%{_cross_unitdir} install -d %{buildroot}%{_cross_templatedir} install -p -m 0644 %{S:200} %{buildroot}%{_cross_templatedir}/motd +install -p -m 0644 %{S:201} %{buildroot}%{_cross_templatedir}/proxy-env %files %{_cross_factorydir}%{_cross_sysconfdir}/hosts @@ -142,5 +144,6 @@ install -p -m 0644 %{S:200} %{buildroot}%{_cross_templatedir}/motd %{_cross_unitdir}/var-lib-bottlerocket.mount %dir %{_cross_templatedir} %{_cross_templatedir}/motd +%{_cross_templatedir}/proxy-env %changelog diff --git a/sources/Cargo.lock b/sources/Cargo.lock index efb0e3f9e73..15918074347 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -975,6 +975,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "fs2" version = "0.4.3" @@ -2386,6 +2396,7 @@ dependencies = [ "serde_json", "snafu", "tokio", + "url", ] [[package]] @@ -3297,10 +3308,11 @@ dependencies = [ [[package]] name = "url" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" dependencies = [ + "form_urlencoded", "idna", "matches", "percent-encoding", diff --git a/sources/api/schnauzer/Cargo.toml b/sources/api/schnauzer/Cargo.toml index 6c5ca74daa2..2afa477c738 100644 --- a/sources/api/schnauzer/Cargo.toml +++ b/sources/api/schnauzer/Cargo.toml @@ -23,6 +23,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1" snafu = "0.6" tokio = { version = "0.2", default-features = false, features = ["macros", "rt-threaded"] } +url = "2.1" # When hyper updates to tokio 0.3: #tokio = { version = "0.3", default-features = false, features = ["macros", "rt-multi-thread"] } diff --git a/sources/api/schnauzer/src/helpers.rs b/sources/api/schnauzer/src/helpers.rs index 482687ef95c..9df73023d66 100644 --- a/sources/api/schnauzer/src/helpers.rs +++ b/sources/api/schnauzer/src/helpers.rs @@ -8,6 +8,7 @@ use serde_json::value::Value; use snafu::{OptionExt, ResultExt}; use std::borrow::Borrow; use std::collections::HashMap; +use url::Url; lazy_static! { /// A map to tell us which registry to pull ECR images from for a given region. @@ -132,6 +133,21 @@ mod error { template: String, source: std::io::Error, }, + + #[snafu(display( + "Expected an absolute URL, got '{}' in template '{}': '{}'", + url_str, + template, + source + ))] + UrlParse { + url_str: String, + template: String, + source: url::ParseError, + }, + + #[snafu(display("URL '{}' is missing host component", url_str))] + UrlHost { url_str: String }, } // Handlebars helpers are required to return a RenderError. @@ -463,6 +479,40 @@ pub fn ecr_prefix( Ok(()) } +/// `host` takes an absolute URL and trims it down and returns its host. +pub fn host( + helper: &Helper<'_, '_>, + _: &Handlebars, + _: &Context, + renderctx: &mut RenderContext<'_, '_>, + out: &mut dyn Output, +) -> Result<(), RenderError> { + trace!("Starting host helper"); + let template_name = template_name(renderctx); + check_param_count(helper, template_name, 1)?; + + let url_val = get_param(helper, 0)?; + let url_str = url_val + .as_str() + .with_context(|| error::InvalidTemplateValue { + expected: "string", + value: url_val.to_owned(), + template: template_name.to_owned(), + })?; + let url = Url::parse(url_str).context(error::UrlParse { + url_str, + template: template_name, + })?; + let url_host = url.host_str().context(error::UrlHost { url_str })?; + + // write it to the template + out.write(&url_host).with_context(|| error::TemplateWrite { + template: template_name.to_owned(), + })?; + + Ok(()) +} + // =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= // helpers to the helpers @@ -823,3 +873,62 @@ mod test_ecr_registry { assert_eq!(result, EXPECTED_URL_XY_ZTOWN_1); } } + +#[cfg(test)] +mod test_host { + use super::*; + use handlebars::TemplateRenderError; + use serde::Serialize; + use serde_json::json; + + // A thin wrapper around the handlebars render_template method that includes + // setup and registration of helpers + fn setup_and_render_template(tmpl: &str, data: &T) -> Result + where + T: Serialize, + { + let mut registry = Handlebars::new(); + registry.register_helper("host", Box::new(host)); + + registry.render_template(tmpl, data) + } + + #[test] + fn not_absolute_url() { + assert!(setup_and_render_template( + "{{ host url_setting }}", + &json!({"url_setting": "example.com"}), + ) + .is_err()); + } + + #[test] + fn https() { + let result = setup_and_render_template( + "{{ host url_setting }}", + &json!({"url_setting": "https://example.example.com/example/example"}), + ) + .unwrap(); + assert_eq!(result, "example.example.com"); + } + + #[test] + fn http() { + let result = setup_and_render_template( + "{{ host url_setting }}", + &json!({"url_setting": "http://example.com"}), + ) + .unwrap(); + assert_eq!(result, "example.com"); + } + + #[test] + fn unknown_scheme() { + let result = setup_and_render_template( + "{{ host url_setting }}", + &json!({"url_setting": "foo://example.com"}), + ) + .unwrap(); + assert_eq!(result, "example.com"); + } +} diff --git a/sources/api/schnauzer/src/lib.rs b/sources/api/schnauzer/src/lib.rs index 5551712a186..ed80ec3838c 100644 --- a/sources/api/schnauzer/src/lib.rs +++ b/sources/api/schnauzer/src/lib.rs @@ -122,6 +122,7 @@ pub fn build_template_registry() -> Result> { template_registry.register_helper("join_map", Box::new(helpers::join_map)); template_registry.register_helper("default", Box::new(helpers::default)); template_registry.register_helper("ecr-prefix", Box::new(helpers::ecr_prefix)); + template_registry.register_helper("host", Box::new(helpers::host)); Ok(template_registry) } diff --git a/sources/models/defaults.toml b/sources/models/defaults.toml index 5d6cb460a3e..392b9ab2d64 100644 --- a/sources/models/defaults.toml +++ b/sources/models/defaults.toml @@ -22,7 +22,7 @@ template-path = "/usr/share/templates/motd" # Container runtime. [services.containerd] -configuration-files = ["containerd-config-toml"] +configuration-files = ["containerd-config-toml", "proxy-env"] restart-commands = ["/bin/systemctl try-restart containerd.service"] [configuration-files.containerd-config-toml] @@ -32,7 +32,7 @@ template-path = "/usr/share/templates/containerd-config-toml" # Host-container runtime [services.host-containerd] -configuration-files = [] +configuration-files = ["proxy-env"] restart-commands = ["/bin/systemctl try-restart host-containerd.service"] # Updates. @@ -83,6 +83,15 @@ restart-commands = ["/usr/bin/host-containers"] [metadata.settings.host-containers] affected-services = ["host-containers"] +# Network + +[configuration-files.proxy-env] +path = "/etc/network/proxy.env" +template-path = "/usr/share/templates/proxy-env" + +[metadata.settings.network] +affected-services = ["containerd", "host-containerd"] + # NTP [settings.ntp] diff --git a/sources/models/src/aws-dev/mod.rs b/sources/models/src/aws-dev/mod.rs index 44f4af2e368..f3e1f8d0b06 100644 --- a/sources/models/src/aws-dev/mod.rs +++ b/sources/models/src/aws-dev/mod.rs @@ -3,7 +3,9 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use crate::modeled_types::Identifier; -use crate::{AwsSettings, ContainerImage, KernelSettings, NtpSettings, UpdatesSettings}; +use crate::{ + AwsSettings, ContainerImage, KernelSettings, NetworkSettings, NtpSettings, UpdatesSettings, +}; // Note: we have to use 'rename' here because the top-level Settings structure is the only one // that uses its name in serialization; internal structures use the field name that points to it @@ -13,6 +15,7 @@ struct Settings { updates: UpdatesSettings, host_containers: HashMap, ntp: NtpSettings, + network: NetworkSettings, kernel: KernelSettings, aws: AwsSettings, } diff --git a/sources/models/src/aws-dev/override-defaults.toml b/sources/models/src/aws-dev/override-defaults.toml index 3b3df6bf7a7..8e8648f59e3 100644 --- a/sources/models/src/aws-dev/override-defaults.toml +++ b/sources/models/src/aws-dev/override-defaults.toml @@ -5,4 +5,8 @@ template-path = "/usr/share/templates/containerd-config-toml_aws-dev" # Docker [services.docker] restart-commands = ["/bin/systemctl try-restart docker.service"] -configuration-files = [] +configuration-files = ["proxy-env"] + +# Network +[metadata.settings.network] +affected-services = ["containerd", "docker", "host-containerd"] diff --git a/sources/models/src/aws-ecs-1/mod.rs b/sources/models/src/aws-ecs-1/mod.rs index 30fae027919..a2c21627b09 100644 --- a/sources/models/src/aws-ecs-1/mod.rs +++ b/sources/models/src/aws-ecs-1/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::modeled_types::Identifier; use crate::{ - AwsSettings, ContainerImage, ECSSettings, KernelSettings, NtpSettings, UpdatesSettings, + AwsSettings, ContainerImage, ECSSettings, KernelSettings, NetworkSettings, NtpSettings, UpdatesSettings, }; // Note: we have to use 'rename' here because the top-level Settings structure is the only one @@ -15,6 +15,7 @@ struct Settings { updates: UpdatesSettings, host_containers: HashMap, ntp: NtpSettings, + network: NetworkSettings, kernel: KernelSettings, aws: AwsSettings, ecs: ECSSettings, diff --git a/sources/models/src/aws-ecs-1/override-defaults.toml b/sources/models/src/aws-ecs-1/override-defaults.toml index 91c9b12bc58..89fea159248 100644 --- a/sources/models/src/aws-ecs-1/override-defaults.toml +++ b/sources/models/src/aws-ecs-1/override-defaults.toml @@ -5,7 +5,7 @@ template-path = "/usr/share/templates/containerd-config-toml_aws-ecs-1" # Docker [services.docker] restart-commands = ["/bin/systemctl try-restart docker.service"] -configuration-files = [] +configuration-files = ["proxy-env"] # ECS [services.ecs] @@ -23,3 +23,7 @@ affected-services = ["ecs"] allow-privileged-containers = false logging-drivers = ["json-file", "awslogs", "none"] loglevel = "info" + +# Network +[metadata.settings.network] +affected-services = ["containerd", "docker", "ecs", "host-containerd"] diff --git a/sources/models/src/aws-k8s-1.15/mod.rs b/sources/models/src/aws-k8s-1.15/mod.rs index 1352ea0a765..59e77df51fc 100644 --- a/sources/models/src/aws-k8s-1.15/mod.rs +++ b/sources/models/src/aws-k8s-1.15/mod.rs @@ -4,7 +4,8 @@ use std::collections::HashMap; use crate::modeled_types::Identifier; use crate::{ - AwsSettings, ContainerImage, KernelSettings, KubernetesSettings, NtpSettings, UpdatesSettings, + AwsSettings, ContainerImage, KernelSettings, KubernetesSettings, NetworkSettings, NtpSettings, + UpdatesSettings, }; // Note: we have to use 'rename' here because the top-level Settings structure is the only one @@ -16,6 +17,7 @@ struct Settings { updates: UpdatesSettings, host_containers: HashMap, ntp: NtpSettings, + network: NetworkSettings, kernel: KernelSettings, aws: AwsSettings, } diff --git a/sources/models/src/aws-k8s-1.15/override-defaults.toml b/sources/models/src/aws-k8s-1.15/override-defaults.toml index 765254f7147..425b076b605 100644 --- a/sources/models/src/aws-k8s-1.15/override-defaults.toml +++ b/sources/models/src/aws-k8s-1.15/override-defaults.toml @@ -5,7 +5,7 @@ template-path = "/usr/share/templates/containerd-config-toml_aws-k8s" # Kubernetes. [services.kubernetes] -configuration-files = ["kubelet-env", "kubelet-config", "kubelet-kubeconfig", "kubernetes-ca-crt"] +configuration-files = ["kubelet-env", "kubelet-config", "kubelet-kubeconfig", "kubernetes-ca-crt", "proxy-env"] restart-commands = ["/bin/systemctl try-restart kubelet.service"] [configuration-files.kubelet-env] @@ -36,3 +36,8 @@ affected-services = ["kubernetes", "containerd"] [settings.kubernetes] cluster-domain = "cluster.local" + + +# Network +[metadata.settings.network] +affected-services = ["containerd", "kubernetes", "host-containerd"] diff --git a/sources/models/src/lib.rs b/sources/models/src/lib.rs index 6fd2de76863..653703fbc7d 100644 --- a/sources/models/src/lib.rs +++ b/sources/models/src/lib.rs @@ -140,6 +140,14 @@ struct ContainerImage { superpowered: bool, } +// Network settings. These settings will affect host service components' network behavior +#[model] +struct NetworkSettings { + https_proxy: Url, + // We allow some flexibility in NO_PROXY values because different services support different formats. + no_proxy: Vec, +} + // NTP settings #[model] struct NtpSettings { diff --git a/sources/models/src/modeled_types/shared.rs b/sources/models/src/modeled_types/shared.rs index fc398445969..0efb0670e72 100644 --- a/sources/models/src/modeled_types/shared.rs +++ b/sources/models/src/modeled_types/shared.rs @@ -240,6 +240,9 @@ mod test_url { "http://localhost", "localhost/path", "localhost", + "localhost:8080", + ".internal", + ".cluster.local" ] { Url::try_from(*ok).unwrap(); }