From cec8e203d287ea51942131e5fbbf175d2344c8e5 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 21 Mar 2023 17:35:51 +0000 Subject: [PATCH 1/2] hardening: Enable setting templated file permissions This adds the ability to specify the permission mode of files created from a template. This enables the ability to have file-by-file control for sensitive files to enforce more restrictive permissions. Signed-off-by: Sean McGinnis --- .../api/apiserver/src/server/controller.rs | 1 + sources/api/thar-be-settings/src/config.rs | 53 +++++++++++++++---- sources/api/thar-be-settings/src/error.rs | 8 +++ .../shared-defaults/kubernetes-services.toml | 6 +++ sources/models/src/lib.rs | 2 + 5 files changed, 60 insertions(+), 10 deletions(-) diff --git a/sources/api/apiserver/src/server/controller.rs b/sources/api/apiserver/src/server/controller.rs index c472b2f36e6..bd1d8e0743c 100644 --- a/sources/api/apiserver/src/server/controller.rs +++ b/sources/api/apiserver/src/server/controller.rs @@ -737,6 +737,7 @@ mod test { hashmap!("foo".to_string() => ConfigurationFile { path: "file".try_into().unwrap(), template_path: "template".try_into().unwrap(), + mode: None, }) ); diff --git a/sources/api/thar-be-settings/src/config.rs b/sources/api/thar-be-settings/src/config.rs index 3057bdfede7..da791de419b 100644 --- a/sources/api/thar-be-settings/src/config.rs +++ b/sources/api/thar-be-settings/src/config.rs @@ -3,11 +3,14 @@ use crate::{error, Result}; use itertools::join; use snafu::{ensure, ResultExt}; use std::collections::HashSet; -use std::fs; +use std::fs::{self, OpenOptions}; +use std::io::prelude::*; +use std::os::unix::fs::OpenOptionsExt; use std::path::{Path, PathBuf}; use std::process::Command; const SYSTEMCTL_DAEMON_RELOAD: &str = "systemctl daemon-reload"; +const DEFAULT_FILE_MODE: u32 = 0o644; /// Query the API for ConfigurationFile data #[allow(clippy::implicit_hasher)] @@ -63,12 +66,18 @@ pub fn render_config_files( let try_rendered = registry.render(&name, &settings); if strict { let rendered = try_rendered.context(error::TemplateRenderSnafu { template: name })?; - rendered_configs.push(RenderedConfigFile::new(&metadata.path, rendered)); + rendered_configs.push(RenderedConfigFile::new( + &metadata.path, + rendered, + &metadata.mode, + )); } else { match try_rendered { - Ok(rendered) => { - rendered_configs.push(RenderedConfigFile::new(&metadata.path, rendered)) - } + Ok(rendered) => rendered_configs.push(RenderedConfigFile::new( + &metadata.path, + rendered, + &metadata.mode, + )), Err(err) => warn!("Unable to render template '{}': {}", &name, err), } } @@ -129,13 +138,15 @@ pub fn reload_config_files(rendered_configs: &[RenderedConfigFile]) -> Result<() pub struct RenderedConfigFile { path: PathBuf, rendered: String, + mode: Option, } impl RenderedConfigFile { - fn new(path: &str, rendered: String) -> RenderedConfigFile { + fn new(path: &str, rendered: String, mode: &Option) -> RenderedConfigFile { RenderedConfigFile { path: PathBuf::from(&path), rendered, + mode: mode.to_owned(), } } @@ -148,10 +159,32 @@ impl RenderedConfigFile { })?; }; - fs::write(&self.path, self.rendered.as_bytes()).context(error::TemplateWriteSnafu { - path: &self.path, - pathtype: "file", - }) + let mut binding = OpenOptions::new(); + let options = binding.write(true).create(true).mode(DEFAULT_FILE_MODE); + + // See if this file has a config setting for a specific mode, but don't error if we are + // not able to honor that request. + if let Some(mode) = &self.mode { + let mode_int = + u32::from_str_radix(mode.as_str(), 8).context(error::TemplateModeSnafu { + path: &self.path, + mode, + })?; + options.mode(mode_int); + } + + let mut file = options + .open(&self.path) + .context(error::TemplateWriteSnafu { + path: &self.path, + pathtype: "file", + })?; + + file.write_all(self.rendered.as_bytes()) + .context(error::TemplateWriteSnafu { + path: &self.path, + pathtype: "file", + }) } /// Checks whether the config file needs `systemd` to reload. diff --git a/sources/api/thar-be-settings/src/error.rs b/sources/api/thar-be-settings/src/error.rs index 178f0b67166..e05033734c0 100644 --- a/sources/api/thar-be-settings/src/error.rs +++ b/sources/api/thar-be-settings/src/error.rs @@ -1,3 +1,4 @@ +use core::num; use http::StatusCode; use snafu::Snafu; use std::io; @@ -27,6 +28,13 @@ pub enum Error { source: io::Error, }, + #[snafu(display("Failed to set template {} to mode {}: {}", path.display(), mode, source))] + TemplateMode { + path: PathBuf, + mode: String, + source: num::ParseIntError, + }, + #[snafu(display("Failed to run restart command - '{}': {}", command, source))] CommandExecutionFailure { command: String, source: io::Error }, diff --git a/sources/models/shared-defaults/kubernetes-services.toml b/sources/models/shared-defaults/kubernetes-services.toml index 113ca7c1aba..a31b389d7a6 100644 --- a/sources/models/shared-defaults/kubernetes-services.toml +++ b/sources/models/shared-defaults/kubernetes-services.toml @@ -22,18 +22,22 @@ template-path = "/usr/share/templates/kubelet-env" [configuration-files.kubelet-config] path = "/etc/kubernetes/kubelet/config" template-path = "/usr/share/templates/kubelet-config" +mode = "0600" [configuration-files.kubelet-kubeconfig] path = "/etc/kubernetes/kubelet/kubeconfig" template-path = "/usr/share/templates/kubelet-kubeconfig" +mode = "0600" [configuration-files.kubelet-bootstrap-kubeconfig] path = "/etc/kubernetes/kubelet/bootstrap-kubeconfig" template-path = "/usr/share/templates/kubelet-bootstrap-kubeconfig" +mode = "0600" [configuration-files.kubernetes-ca-crt] path = "/etc/kubernetes/pki/ca.crt" template-path = "/usr/share/templates/kubernetes-ca-crt" +mode = "0600" [configuration-files.kubelet-server-crt] path = "/etc/kubernetes/pki/kubelet-server.crt" @@ -46,10 +50,12 @@ template-path = "/usr/share/templates/kubelet-server-key" [configuration-files.kubelet-exec-start-conf] path = "/etc/systemd/system/kubelet.service.d/exec-start.conf" template-path = "/usr/share/templates/kubelet-exec-start-conf" +mode = "0600" [configuration-files.credential-provider-config-yaml] path = "/etc/kubernetes/kubelet/credential-provider-config.yaml" template-path = "/usr/share/templates/credential-provider-config-yaml" +mode = "0600" [services.static-pods] configuration-files = [] diff --git a/sources/models/src/lib.rs b/sources/models/src/lib.rs index 7b731fc538e..c712dc4d304 100644 --- a/sources/models/src/lib.rs +++ b/sources/models/src/lib.rs @@ -454,6 +454,8 @@ pub type ConfigurationFiles = HashMap; struct ConfigurationFile { path: SingleLineString, template_path: SingleLineString, + #[serde(skip_serializing_if = "Option::is_none")] + mode: Option, } ///// Metadata From 450b56c2a83971e328ced4503db6609be2aa6ded Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Thu, 23 Mar 2023 21:29:54 +0000 Subject: [PATCH 2/2] Add migration for kubernetes-services file modes This adds a migration for the addition of the mode property for a few of the Kubernetes template files. Signed-off-by: Sean McGinnis --- Release.toml | 1 + sources/Cargo.lock | 8 +++++++ sources/Cargo.toml | 1 + .../v1.14.0/k8s-services-mode/Cargo.toml | 13 +++++++++++ .../v1.14.0/k8s-services-mode/src/main.rs | 23 +++++++++++++++++++ sources/api/thar-be-settings/src/config.rs | 3 +-- 6 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 sources/api/migration/migrations/v1.14.0/k8s-services-mode/Cargo.toml create mode 100644 sources/api/migration/migrations/v1.14.0/k8s-services-mode/src/main.rs diff --git a/Release.toml b/Release.toml index 4fb9c4f2673..b5223b2eeb8 100644 --- a/Release.toml +++ b/Release.toml @@ -204,4 +204,5 @@ version = "1.14.0" "(1.13.5, 1.14.0)" = [ "migrate_v1.14.0_kubernetes-gc-percent-type-change.lz4", "migrate_v1.14.0_kubelet-config-settings.lz4", + "migrate_v1.14.0_k8s-services-mode.lz4", ] diff --git a/sources/Cargo.lock b/sources/Cargo.lock index 257a7a3459f..f519a6a3888 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -2233,6 +2233,14 @@ dependencies = [ "migration-helpers", ] +[[package]] +name = "k8s-services-mode" +version = "0.1.0" +dependencies = [ + "migration-helpers", + "serde_json", +] + [[package]] name = "kubelet-config-settings" version = "0.1.0" diff --git a/sources/Cargo.toml b/sources/Cargo.toml index 0aa074bd4fb..f369ae3b757 100644 --- a/sources/Cargo.toml +++ b/sources/Cargo.toml @@ -46,6 +46,7 @@ members = [ "api/migration/migrations/v1.13.4/add-hostname-override-metadata", "api/migration/migrations/v1.14.0/kubernetes-gc-percent-type-change", "api/migration/migrations/v1.14.0/kubelet-config-settings", + "api/migration/migrations/v1.14.0/k8s-services-mode", "bottlerocket-release", diff --git a/sources/api/migration/migrations/v1.14.0/k8s-services-mode/Cargo.toml b/sources/api/migration/migrations/v1.14.0/k8s-services-mode/Cargo.toml new file mode 100644 index 00000000000..6edec9e6e26 --- /dev/null +++ b/sources/api/migration/migrations/v1.14.0/k8s-services-mode/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "k8s-services-mode" +version = "0.1.0" +authors = ["Sean McGinnis "] +license = "Apache-2.0 OR MIT" +edition = "2021" +publish = false +# Don't rebuild crate just because of changes to README. +exclude = ["README.md"] + +[dependencies] +migration-helpers = { path = "../../../migration-helpers", version = "0.1.0"} +serde_json = "1.0" diff --git a/sources/api/migration/migrations/v1.14.0/k8s-services-mode/src/main.rs b/sources/api/migration/migrations/v1.14.0/k8s-services-mode/src/main.rs new file mode 100644 index 00000000000..da41d1adee9 --- /dev/null +++ b/sources/api/migration/migrations/v1.14.0/k8s-services-mode/src/main.rs @@ -0,0 +1,23 @@ +use migration_helpers::{common_migrations::AddSettingsMigration, migrate, Result}; +use std::process; + +/// Mode settings were added for a handful of the templated kubelet configuration files. +fn run() -> Result<()> { + migrate(AddSettingsMigration(&[ + "configuration-files.kubelet-config.mode", + "configuration-files.kubelet-kubeconfig.mode", + "configuration-files.kubelet-bootstrap-kubeconfig.mode", + "configuration-files.kubelet-exec-start-conf.mode", + "configuration-files.credential-provider-config-yaml.mode", + ])) +} + +// Returning a Result from main makes it print a Debug representation of the error, but with Snafu +// we have nice Display representations of the error, so we wrap "main" (run) and print any error. +// https://github.com/shepmaster/snafu/issues/110 +fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + process::exit(1); + } +} diff --git a/sources/api/thar-be-settings/src/config.rs b/sources/api/thar-be-settings/src/config.rs index da791de419b..d2590d56a7b 100644 --- a/sources/api/thar-be-settings/src/config.rs +++ b/sources/api/thar-be-settings/src/config.rs @@ -162,8 +162,7 @@ impl RenderedConfigFile { let mut binding = OpenOptions::new(); let options = binding.write(true).create(true).mode(DEFAULT_FILE_MODE); - // See if this file has a config setting for a specific mode, but don't error if we are - // not able to honor that request. + // See if this file has a config setting for a specific mode if let Some(mode) = &self.mode { let mode_int = u32::from_str_radix(mode.as_str(), 8).context(error::TemplateModeSnafu {