Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add kernel lockdown setting #1223

Merged
merged 4 commits into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ These settings can be changed at any time.

#### Kernel settings

* `settings.kernel.lockdown`: This allows further restrictions on what the Linux kernel will allow, for example preventing the loading of unsigned modules.
May be set to "none" (the default), "integrity", or "confidentiality".
**Important note:** this setting cannot be lowered (toward 'none') at runtime.
You must reboot for a change to a lower level to take effect.
* `settings.kernel.sysctl`: Key/value pairs representing Linux kernel parameters.
Remember to quote keys (since they often contain ".") and to quote all values.
* Example user data for setting up sysctl:
Expand Down
1 change: 1 addition & 0 deletions Release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ version = "1.0.4"
"(1.0.1, 1.0.2)" = ["migrate_v1.0.2_add-enable-spot-instance-draining.lz4"]
"(1.0.2, 1.0.3)" = ["migrate_v1.0.3_add-sysctl.lz4"]
"(1.0.3, 1.0.4)" = []
"(1.0.4, 1.0.5)" = ["migrate_v1.0.5_add-lockdown.lz4", "migrate_v1.0.5_sysctl-subcommand.lz4"]
3 changes: 3 additions & 0 deletions packages/kernel/config-bottlerocket
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ CONFIG_IKHEADERS=y

# BTF debug info at /sys/kernel/btf/vmlinux
CONFIG_DEBUG_INFO_BTF=y

# Enable support for the kernel lockdown security module.
CONFIG_SECURITY_LOCKDOWN_LSM=y
14 changes: 14 additions & 0 deletions sources/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions sources/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ members = [
"api/migration/migrations/v1.0.0/ecr-helper-control",
"api/migration/migrations/v1.0.2/add-enable-spot-instance-draining",
"api/migration/migrations/v1.0.3/add-sysctl",
"api/migration/migrations/v1.0.5/add-lockdown",
"api/migration/migrations/v1.0.5/sysctl-subcommand",

"bottlerocket-release",

Expand Down
4 changes: 3 additions & 1 deletion sources/api/corndog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
Current version: 0.1.0

corndog is a delicious way to get at the meat inside the kernels.
It sets kernel sysctl values based on key/value pairs in `settings.kernel.sysctl`.
It sets kernel-related settings, for example:
* sysctl values, based on key/value pairs in `settings.kernel.sysctl`
* lockdown mode, based on the value of `settings.kernel.lockdown`

## Colophon

Expand Down
137 changes: 126 additions & 11 deletions sources/api/corndog/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/*!
corndog is a delicious way to get at the meat inside the kernels.
It sets kernel sysctl values based on key/value pairs in `settings.kernel.sysctl`.
It sets kernel-related settings, for example:
* sysctl values, based on key/value pairs in `settings.kernel.sysctl`
* lockdown mode, based on the value of `settings.kernel.lockdown`
*/

#![deny(rust_2018_idioms)]

use log::{debug, error, trace};
use log::{debug, error, info, trace, warn};
use simplelog::{Config as LogConfig, LevelFilter, TermLogger, TerminalMode};
use snafu::ResultExt;
use std::collections::HashMap;
Expand All @@ -17,9 +19,11 @@ use std::{env, process};

const DEFAULT_API_SOCKET: &str = "/run/api.sock";
const SYSCTL_PATH_PREFIX: &str = "/proc/sys";
const LOCKDOWN_PATH: &str = "/sys/kernel/security/lockdown";

/// Store the args we receive on the command line.
struct Args {
subcommand: String,
log_level: LevelFilter,
socket_path: String,
}
Expand All @@ -32,20 +36,33 @@ async fn run() -> Result<()> {
TermLogger::init(args.log_level, LogConfig::default(), TerminalMode::Mixed)
.context(error::Logger)?;

// If the user has sysctl settings, apply them.
// If the user has kernel settings, apply them.
let model = get_model(args.socket_path).await?;
if let Some(settings) = model.settings {
if let Some(kernel) = settings.kernel {
if let Some(sysctls) = kernel.sysctl {
debug!("Applying sysctls: {:#?}", sysctls);
set_sysctls(sysctls)?;
match args.subcommand.as_ref() {
"sysctl" => {
if let Some(sysctls) = kernel.sysctl {
debug!("Applying sysctls: {:#?}", sysctls);
set_sysctls(sysctls);
}
}
"lockdown" => {
if let Some(lockdown) = kernel.lockdown {
debug!("Setting lockdown: {:#?}", lockdown);
set_lockdown(&lockdown)?;
}
}
_ => usage_msg(format!("Unknown subcommand '{}'", args.subcommand)), // should be unreachable
}
}
}

Ok(())
}

// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=

/// Retrieve the current model from the API.
async fn get_model<P>(socket_path: P) -> Result<model::Model>
where
Expand Down Expand Up @@ -85,7 +102,7 @@ where

/// Applies the requested sysctls to the system. The keys are used to generate the appropriate
/// path, and the value its contents.
fn set_sysctls<K>(sysctls: HashMap<K, String>) -> Result<()>
fn set_sysctls<K>(sysctls: HashMap<K, String>)
where
K: AsRef<str>,
{
Expand All @@ -100,16 +117,70 @@ where
error!("Failed to write sysctl value '{}': {}", key, e);
}
}
Ok(())
}

/// Sets the requested lockdown mode in the kernel.
///
/// The Linux kernel won't allow lowering the lockdown setting, but we want to allow users to
/// change the Bottlerocket setting and reboot for it to take effect. Changing the Bottlerocket
/// setting means this code will run to write it out, but it wouldn't be able to convince the
/// kernel. So, we just warn the user rather than trying to write and causing a failure that could
/// prevent the rest of a settings-changing transaction from going through. We'll run again after
/// reboot to set lockdown as it was requested.
fn set_lockdown(lockdown: &str) -> Result<()> {
let current_raw = fs::read_to_string(LOCKDOWN_PATH).unwrap_or_else(|_| "unknown".to_string());
let current = parse_kernel_setting(&current_raw);
trace!("Parsed lockdown setting '{}' to '{}'", current_raw, current);

// The kernel doesn't allow rewriting the current value.
if current == lockdown {
info!("Requested lockdown setting is already in effect.");
return Ok(());
// As described above, the kernel doesn't allow lowering the value.
} else if current == "confidentiality" || (current == "integrity" && lockdown == "none") {
warn!("Can't lower lockdown setting at runtime; please reboot for it to take effect.",);
return Ok(());
}

fs::write(LOCKDOWN_PATH, lockdown).context(error::Lockdown { current, lockdown })
}

/// The Linux kernel provides human-readable output like `[none] integrity confidentiality` when
/// you read settings from virtual files like /sys/kernel/security/lockdown. This parses out the
/// current value of the setting from that human-readable output.
///
/// There are also some files that only output the current value without the other options, so we
/// return the output as-is (except for trimming whitespace) if there are no brackets.
fn parse_kernel_setting(setting: &str) -> &str {
let mut setting = setting.trim();
// Take after the '['
if let Some(idx) = setting.find('[') {
if setting.len() > idx + 1 {
setting = &setting[idx + 1..];
}
}
// Take before the ']'
if let Some(idx) = setting.find(']') {
setting = &setting[..idx];
}
setting
}

// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=

/// Print a usage message in the event a bad argument is given.
fn usage() -> ! {
let program_name = env::args().next().unwrap_or_else(|| "program".to_string());
eprintln!(
r"Usage: {}
[ --socket-path PATH ]
[ --log-level trace|debug|info|warn|error ]
r"Usage: {} SUBCOMMAND [ ARGUMENTS... ]

Subcommands:
sysctl
lockdown

Global arguments:
--socket-path PATH
--log-level trace|debug|info|warn|error

Socket path defaults to {}",
program_name, DEFAULT_API_SOCKET,
Expand All @@ -127,6 +198,7 @@ fn usage_msg<S: AsRef<str>>(msg: S) -> ! {
fn parse_args(args: env::Args) -> Args {
let mut log_level = None;
let mut socket_path = None;
let mut subcommand = None;

let mut iter = args.skip(1);
while let Some(arg) = iter.next() {
Expand All @@ -147,11 +219,14 @@ fn parse_args(args: env::Args) -> Args {
)
}

"sysctl" | "lockdown" => subcommand = Some(arg),

_ => usage(),
}
}

Args {
subcommand: subcommand.unwrap_or_else(|| usage_msg("Must specify a subcommand.")),
log_level: log_level.unwrap_or_else(|| LevelFilter::Info),
socket_path: socket_path.unwrap_or_else(|| DEFAULT_API_SOCKET.to_string()),
}
Expand All @@ -168,9 +243,12 @@ async fn main() {
}
}

// =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=

mod error {
use http::StatusCode;
use snafu::Snafu;
use std::io;

#[derive(Debug, Snafu)]
#[snafu(visibility = "pub(super)")]
Expand All @@ -190,6 +268,18 @@ mod error {
response_body: String,
},

#[snafu(display(
"Failed to change lockdown from '{}' to '{}': {}",
current,
lockdown,
source
))]
Lockdown {
current: String,
lockdown: String,
source: io::Error,
},

#[snafu(display("Logger setup error: {}", source))]
Logger { source: simplelog::TermLogError },

Expand Down Expand Up @@ -219,4 +309,29 @@ mod test {
format!("{}/root/file", SYSCTL_PATH_PREFIX)
);
}

#[test]
fn brackets() {
assert_eq!(
"none",
parse_kernel_setting("[none] integrity confidentiality")
);
assert_eq!(
"integrity",
parse_kernel_setting("none [integrity] confidentiality\n")
);
assert_eq!(
"confidentiality",
parse_kernel_setting("none integrity [confidentiality]")
);
}

#[test]
fn no_brackets() {
assert_eq!("none", parse_kernel_setting("none"));
assert_eq!(
"none integrity confidentiality",
parse_kernel_setting("none integrity confidentiality\n")
);
}
}
Loading