Skip to content

Commit

Permalink
logdog: switch to use apiclient cli to get settings
Browse files Browse the repository at this point in the history
This changes logdog to fetch settings using the apiclient. This creates two breaking changes.
First logdog will now include sections such as 'os' in the log. This provides more information
in the log. Secondly instead of removing the existence of secret settings their values are replaced
with REDACTED. This allows the user to see the setting was there while maintaining the scrubbing of
secrets.
  • Loading branch information
jmt-lab committed May 6, 2024
1 parent f9e9591 commit 3715c2d
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 47 deletions.
5 changes: 0 additions & 5 deletions sources/Cargo.lock

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

5 changes: 0 additions & 5 deletions sources/logdog/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@ publish = false
exclude = ["README.md"]

[dependencies]
apiclient = { path = "../api/apiclient", version = "0.1" }
constants = { path = "../constants", version = "0.1" }
datastore = { path = "../api/datastore", version = "0.1" }
flate2 = "1"
glob = "0.3"
models = { path = "../models", version = "0.1" }
reqwest = { version = "0.11", default-features = false, features = ["blocking", "rustls-tls-native-roots"] }
serde_json = "1"
shell-words = "1"
Expand All @@ -26,5 +22,4 @@ url = "2"
walkdir = "2"

[build-dependencies]
bottlerocket-variant = { version = "0.1", path = "../bottlerocket-variant" }
generate-readme = { version = "0.1", path = "../generate-readme" }
19 changes: 7 additions & 12 deletions sources/logdog/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ use std::io;
use std::path::PathBuf;

use crate::log_request::REQUESTS_DIR;
use datastore::{deserialization, serialization};
use reqwest::Url;
use snafu::{Backtrace, Snafu};

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
#[allow(clippy::enum_variant_names)]
pub(crate) enum Error {
#[snafu(display("Error calling Bottlerocket API '{}': {}", uri, source))]
ApiClient {
#[snafu(source(from(apiclient::Error, Box::new)))]
source: Box<apiclient::Error>,
uri: String,
#[snafu(display("Error calling apiclient: {}", source))]
ApiCommandFailure {
#[snafu(source(from(std::io::Error, Box::new)))]
source: Box<std::io::Error>,
},

#[snafu(display("Error fetching settings via apiclient: {}", reason))]
ApiExecutionFailure { reason: String },

#[snafu(display("Error creating the command stderr file '{}': {}", path.display(), source))]
CommandErrFile {
source: io::Error,
Expand Down Expand Up @@ -67,9 +68,6 @@ pub(crate) enum Error {
backtrace: Backtrace,
},

#[snafu(display("Error deserializing Settings: {} ", source))]
DeserializeSettings { source: deserialization::Error },

#[snafu(display("Error creating the error file '{}': {}", path.display(), source))]
ErrorFile {
source: io::Error,
Expand Down Expand Up @@ -167,9 +165,6 @@ pub(crate) enum Error {
#[snafu(display("Cannot write to / as a file."))]
RootAsFile { backtrace: Backtrace },

#[snafu(display("Error serializing Settings: {} ", source))]
SerializeSettings { source: serialization::Error },

#[snafu(display("Unable to deserialize Bottlerocket settings: {}", source))]
SettingsJson { source: serde_json::Error },

Expand Down
74 changes: 49 additions & 25 deletions sources/logdog/src/log_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
//! these provide the list of log requests that `logdog` will run.

use crate::error::{self, Result};
use datastore::deserialization::from_map;
use datastore::serialization::to_pairs;
use glob::{glob, Pattern};
use glob::glob;
use reqwest::blocking::{Client, Response};
use snafu::{ensure, OptionExt, ResultExt};
use std::collections::HashSet;
Expand All @@ -28,15 +26,14 @@ pub(crate) const REQUESTS_DIR: &str = "/usr/share/logdog.d";
/// Patterns to filter from settings output. These follow the Unix shell style pattern outlined
/// here: https://docs.rs/glob/0.3.0/glob/struct.Pattern.html.
const SENSITIVE_SETTINGS_PATTERNS: &[&str] = &[
"*.user-data",
"settings.kubernetes.bootstrap-token",
"/settings/kubernetes/bootstrap-token",
// Can contain a username:password component
"settings.network.https-proxy",
"settings.kubernetes.server-key",
"settings.container-registry.credentials",
"/settings/network/https-proxy",
"/settings/kubernetes/server-key",
"/settings/container-registry/credentials",
// Can be stored in settings.aws.credentials, but user can also add creds here
"settings.aws.config",
"settings.aws.credentials",
"/settings/aws/config",
"/settings/aws/credentials",
];

/// Returns the list of log requests to run by combining `VARIANT_REQUESTS` and `COMMON_REQUESTS`.
Expand Down Expand Up @@ -166,39 +163,66 @@ where
Ok(())
}

/// Strips user-data out of the settings model
fn strip_user_data(input: &mut serde_json::Value) {
let redacted = serde_json::Value::String("REDACTED".into());
match input {
serde_json::Value::Object(map) => {
for (key, value) in map.iter_mut() {
if key == "user-data" {
*value = redacted.clone();
} else {
strip_user_data(value)
}
}
}
serde_json::Value::Array(array) => {
for child in array {
strip_user_data(child)
}
}
_ => {}
}
}

/// Requests settings from the API, filters them, and writes the output to `tempdir`
async fn handle_settings_request<P>(request: &LogRequest<'_>, tempdir: P) -> Result<()>
where
P: AsRef<Path>,
{
let settings = get_settings().await?;
let mut settings_map = to_pairs(&settings).context(error::SerializeSettingsSnafu)?;
let mut settings = get_settings().await?;

// Filter all settings that match any of the "sensitive" patterns
let redacted = serde_json::Value::String("REDACTED".into());
for pattern in SENSITIVE_SETTINGS_PATTERNS {
let pattern =
Pattern::new(pattern).context(error::ParseGlobPatternSnafu { pattern: *pattern })?;
settings_map.retain(|k, _| !pattern.matches(k.name().as_str()))
if let Some(found) = settings.pointer_mut(pattern) {
*found = redacted.clone();
}
}
strip_user_data(&mut settings);

// Serialize the map back to a `Settings` to remove the escaping so it writes nicely to file
let settings: model::Settings =
from_map(&settings_map).context(error::DeserializeSettingsSnafu)?;
let outpath = tempdir.as_ref().join(request.filename);
let outfile = File::create(&outpath).context(error::FileCreateSnafu { path: &outpath })?;
serde_json::to_writer_pretty(&outfile, &settings)
.context(error::FileWriteSnafu { path: &outpath })?;
Ok(())
}

/// Uses `apiclient` to request all settings from the apiserver and deserializes into a `Settings`
async fn get_settings() -> Result<model::Settings> {
let uri = constants::API_SETTINGS_URI;
let (_status, response_body) = apiclient::raw_request(constants::API_SOCKET, uri, "GET", None)
.await
.context(error::ApiClientSnafu { uri })?;
/// Uses `apiclient` to request all settings from the apiserver and deserializes into a `json::Value`
async fn get_settings() -> Result<serde_json::Value> {
let result = Command::new("/usr/bin/apiclient")
.arg("get")
.output()
.context(error::ApiCommandFailureSnafu)?;

ensure!(
result.status.success(),
error::ApiExecutionFailureSnafu {
reason: String::from_utf8_lossy(&result.stderr)
}
);

serde_json::from_str(&response_body).context(error::SettingsJsonSnafu)
serde_json::from_slice(result.stdout.as_slice()).context(error::SettingsJsonSnafu)
}

/// Runs an `exec` `LogRequest`'s `instructions` and writes its output to to `tempdir`.
Expand Down

0 comments on commit 3715c2d

Please sign in to comment.