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 FIPS report to the API #3894

Merged
merged 4 commits into from
Apr 23, 2024
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
5 changes: 5 additions & 0 deletions packages/os/bottlerocket-fips-checks-metadata-json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "FIPS Security Policy",
"version": "v1.0.0",
"url": "https://csrc.nist.gov/"
}
61 changes: 43 additions & 18 deletions packages/os/os.spec
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,16 @@ Source7: host-ctr-toml
Source8: oci-default-hooks-json
Source9: cfsignal-toml
Source10: warm-pool-wait-toml
Source11: cis-checks-bottlerocket-metadata-json
Source11: bottlerocket-cis-checks-metadata-json
Source12: 00-resolved.conf
%if %{with k8s_runtime}
Source13: cis-checks-k8s-metadata-json
%endif
Source13: kubernetes-cis-checks-metadata-json
Source14: certdog-toml
Source15: prairiedog-toml
Source16: thar-be-updates-toml
Source17: corndog-toml
Source18: bootstrap-containers-toml
Source19: host-containers-toml
Source20: bottlerocket-fips-checks-metadata-json

# 1xx sources: systemd units
Source100: apiserver.service
Expand Down Expand Up @@ -284,9 +283,23 @@ Summary: Manages bootstrap-containers

%package -n %{_cross_os}bloodhound
Summary: Compliance check framework
Requires: (%{_cross_os}bloodhound-k8s if %{_cross_os}variant-runtime(k8s))
Requires: (%{_cross_os}bloodhound-fips if %{_cross_os}image-feature(fips))
%description -n %{_cross_os}bloodhound
%{summary}.

%package -n %{_cross_os}bloodhound-k8s
Summary: Compliance checks for Kubernetes
Requires: (%{_cross_os}bloodhound and %{_cross_os}variant-runtime(k8s))
%description -n %{_cross_os}bloodhound-k8s
%{summary}.

%package -n %{_cross_os}bloodhound-fips
Summary: Compliance checks for FIPS
Requires: (%{_cross_os}bloodhound and %{_cross_os}image-feature(fips))
%description -n %{_cross_os}bloodhound-fips
%{summary}.

%package -n %{_cross_os}xfscli
Summary: XFS progs cli
%description -n %{_cross_os}xfscli
Expand Down Expand Up @@ -416,7 +429,10 @@ for p in \
migrator prairiedog certdog \
signpost updog metricdog logdog \
ghostdog bootstrap-containers \
shimpei bloodhound bottlerocket-checks \
shimpei bloodhound \
bottlerocket-cis-checks \
bottlerocket-fips-checks \
kubernetes-cis-checks \
%{?with_aws_platform: shibaken} \
%{?with_k8s_runtime: static-pods} \
%{?with_nvidia_flavor: driverdog} \
Expand All @@ -433,9 +449,6 @@ for p in \
done
%endif

%if %{with k8s_runtime}
install -p -m 0755 ${HOME}/.cache/%{__cargo_target}/release/kubernetes-checks %{buildroot}%{_cross_bindir}
%endif
install -d %{buildroot}%{_cross_sbindir}
for p in \
xfs_admin xfs_info \
Expand All @@ -454,12 +467,21 @@ for p in \
br03020300 br03020400 br03020500 br03020600 br03020700 br03030100 \
br03040101 br03040102 br03040201 br03040202 br04010101 br04010200 \
; do
ln -rs %{buildroot}%{_cross_bindir}/bottlerocket-checks %{buildroot}%{_cross_libexecdir}/cis-checks/bottlerocket/${p}
ln -rs %{buildroot}%{_cross_bindir}/bottlerocket-cis-checks \
%{buildroot}%{_cross_libexecdir}/cis-checks/bottlerocket/${p}
done
install -m 0644 %{S:11} %{buildroot}%{_cross_libexecdir}/cis-checks/bottlerocket/metadata.json

mkdir -p %{buildroot}%{_cross_libexecdir}/fips-checks/bottlerocket
for p in \
fips01000000 fips01010000 fips01020000 fips01030000 \
; do
ln -rs %{buildroot}%{_cross_bindir}/bottlerocket-fips-checks \
%{buildroot}%{_cross_libexecdir}/fips-checks/bottlerocket/${p}
done
install -m 0644 %{S:20} %{buildroot}%{_cross_libexecdir}/fips-checks/bottlerocket/metadata.json

# Only add the k8s checks if it is a k8s variant
%if %{with k8s_runtime}
mkdir -p %{buildroot}%{_cross_libexecdir}/cis-checks/kubernetes
for p in \
k8s04010300 k8s04010400 k8s04020700 k8s04020800 \
Expand All @@ -468,10 +490,10 @@ for p in \
k8s04020300 k8s04020400 k8s04020500 k8s04020600 k8s04020900 \
k8s04021000 k8s04021100 k8s04021200 k8s04021300 \
; do
ln -rs %{buildroot}%{_cross_bindir}/kubernetes-checks %{buildroot}%{_cross_libexecdir}/cis-checks/kubernetes/${p}
ln -rs %{buildroot}%{_cross_bindir}/kubernetes-cis-checks \
%{buildroot}%{_cross_libexecdir}/cis-checks/kubernetes/${p}
done
install -m 0644 %{S:13} %{buildroot}%{_cross_libexecdir}/cis-checks/kubernetes/metadata.json
%endif

for p in apiclient ; do
install -p -m 0755 ${HOME}/.cache/.static/%{__cargo_target_static}/release/${p} %{buildroot}%{_cross_bindir}
Expand Down Expand Up @@ -725,17 +747,20 @@ install -p -m 0644 %{S:400} %{S:401} %{S:402} %{buildroot}%{_cross_licensedir}

%files -n %{_cross_os}bloodhound
%{_cross_bindir}/bloodhound
%{_cross_bindir}/bottlerocket-checks
%{_cross_bindir}/bottlerocket-cis-checks
%{_cross_libexecdir}/cis-checks/bottlerocket

%files -n %{_cross_os}bloodhound-k8s
%{_cross_bindir}/kubernetes-cis-checks
%{_cross_libexecdir}/cis-checks/kubernetes

%files -n %{_cross_os}bloodhound-fips
%{_cross_bindir}/bottlerocket-fips-checks
%{_cross_libexecdir}/fips-checks/bottlerocket

%files -n %{_cross_os}xfscli
%{_cross_sbindir}/xfs_admin
%{_cross_sbindir}/xfs_info
%{_cross_sbindir}/fsck.xfs

%if %{with k8s_runtime}
%{_cross_bindir}/kubernetes-checks
%{_cross_libexecdir}/cis-checks/kubernetes
%endif

%changelog
13 changes: 13 additions & 0 deletions sources/api/apiclient/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,19 @@ Refer to the [Kubernetes CIS Benchmark] for detailed audit and remediation steps
[Bottlerocket CIS Benchmark]: https://www.cisecurity.org/benchmark/bottlerocket
[Kubernetes CIS Benchmark]: https://www.cisecurity.org/benchmark/kubernetes

#### FIPS Security Policy reports

This command can be used to evaluate the current system state and settings for compliance with the requirements of the FIPS Security Policy.

```shell
apiclient report fips
```

The results from each item in the report will be one of:

- **PASS**: The system has been evaluated to be in compliance with the requirements of the FIPS Security Policy.
- **FAIL**: The system has been evaluated to not be in compliance with the requirements of the FIPS Security Policy.

## apiclient library

The apiclient library provides high-level methods to interact with the Bottlerocket API. See
Expand Down
13 changes: 13 additions & 0 deletions sources/api/apiclient/README.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,19 @@ Refer to the [Kubernetes CIS Benchmark] for detailed audit and remediation steps
[Bottlerocket CIS Benchmark]: https://www.cisecurity.org/benchmark/bottlerocket
[Kubernetes CIS Benchmark]: https://www.cisecurity.org/benchmark/kubernetes

#### FIPS Security Policy reports

This command can be used to evaluate the current system state and settings for compliance with the requirements of the FIPS Security Policy.

```shell
apiclient report fips
```

The results from each item in the report will be one of:

- **PASS**: The system has been evaluated to be in compliance with the requirements of the FIPS Security Policy.
- **FAIL**: The system has been evaluated to not be in compliance with the requirements of the FIPS Security Policy.

## apiclient library

{{readme}}
Expand Down
45 changes: 45 additions & 0 deletions sources/api/apiclient/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ enum UpdateSubcommand {
enum ReportSubcommand {
Cis(CisReportArgs),
CisK8s(CisReportArgs),
Fips(FipsReportArgs),
}

/// Stores common user-supplied arguments for the cis report subcommand.
Expand All @@ -113,6 +114,12 @@ struct CisReportArgs {
format: Option<String>,
}

/// Stores common user-supplied arguments for the fips report subcommand.
#[derive(Debug)]
struct FipsReportArgs {
format: Option<String>,
}

/// Stores user-supplied arguments for the 'update check' subcommand.
#[derive(Debug)]
struct UpdateCheckArgs {}
Expand Down Expand Up @@ -153,6 +160,7 @@ fn usage() -> ! {
exec Execute a command in a host container.
report cis Retrieve a Bottlerocket CIS benchmark compliance report.
report cis-k8s Retrieve a Kubernetes CIS benchmark compliance report.
report fips Retrieve a FIPS Security Policy compliance report.

raw options:
-u, --uri URI Required; URI to request from the server, e.g. /tx
Expand Down Expand Up @@ -586,6 +594,7 @@ fn parse_report_args(args: Vec<String>) -> Subcommand {
// Subcommands
"cis" if subcommand.is_none() && !arg.starts_with('-') => subcommand = Some(arg),
"cis-k8s" if subcommand.is_none() && !arg.starts_with('-') => subcommand = Some(arg),
"fips" if subcommand.is_none() && !arg.starts_with('-') => subcommand = Some(arg),

// Other arguments are passed to the subcommand parser
_ => subcommand_args.push(arg),
Expand All @@ -595,6 +604,7 @@ fn parse_report_args(args: Vec<String>) -> Subcommand {
let report_type = match subcommand.as_deref() {
Some("cis") => parse_report_cis_args(subcommand_args),
Some("cis-k8s") => parse_report_cis_k8s_args(subcommand_args),
Some("fips") => parse_report_fips_args(subcommand_args),
_ => usage_msg("Missing or unknown subcommand for 'report'"),
};

Expand Down Expand Up @@ -642,6 +652,31 @@ fn parse_cis_arguments(args: Vec<String>) -> CisReportArgs {
CisReportArgs { level, format }
}

/// Parses arguments for the 'report' fips subcommand.
fn parse_report_fips_args(args: Vec<String>) -> ReportSubcommand {
ReportSubcommand::Fips(parse_fips_arguments(args))
}

fn parse_fips_arguments(args: Vec<String>) -> FipsReportArgs {
let mut format = None;

let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_ref() {
"-f" | "--format" => {
format = Some(
iter.next()
.unwrap_or_else(|| usage_msg("Did not give argument to -f | --format")),
)
}

x => usage_msg(format!("Unknown argument '{}'", x)),
}
}

FipsReportArgs { format }
}

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

Expand Down Expand Up @@ -858,6 +893,16 @@ async fn run() -> Result<()> {
print!("{}", body);
}
}

ReportSubcommand::Fips(fips_args) => {
let body = report::get_fips_report(&args.socket_path, fips_args.format)
.await
.context(error::ReportSnafu)?;

if !body.is_empty() {
print!("{}", body);
}
}
},
}

Expand Down
21 changes: 21 additions & 0 deletions sources/api/apiclient/src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@ where
Ok(body)
}

/// Handles requesting a FIPS report.
pub async fn get_fips_report<P>(socket_path: P, format: Option<String>) -> Result<String>
where
P: AsRef<Path>,
{
let method = "GET";

let mut query = Vec::new();
if let Some(query_format) = format {
query.push(format!("format={}", query_format));
}

let uri = format!("/report/fips?{}", query.join("&"));

let (_status, body) = crate::raw_request(&socket_path, &uri, method, None)
.await
.context(error::RequestSnafu { uri, method })?;

Ok(body)
}

mod error {
use snafu::Snafu;

Expand Down
31 changes: 30 additions & 1 deletion sources/api/apiserver/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use tokio::process::Command as AsyncCommand;

const BLOODHOUND_BIN: &str = "/usr/bin/bloodhound";
const BLOODHOUND_K8S_CHECKS: &str = "/usr/libexec/cis-checks/kubernetes";
const BLOODHOUND_FIPS_CHECKS: &str = "/usr/libexec/fips-checks/bottlerocket";

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

Expand Down Expand Up @@ -128,7 +129,8 @@ where
.service(
web::scope("/report")
.route("", web::get().to(list_reports))
.route("/cis", web::get().to(get_cis_report)),
.route("/cis", web::get().to(get_cis_report))
.route("/fips", web::get().to(get_fips_report)),
)
})
.workers(threads)
Expand Down Expand Up @@ -598,6 +600,33 @@ async fn get_cis_report(query: web::Query<HashMap<String, String>>) -> Result<Ht
.body(String::from_utf8_lossy(&output.stdout).to_string()))
}

/// Gets the FIPS Security Policy report.
async fn get_fips_report(query: web::Query<HashMap<String, String>>) -> Result<HttpResponse> {
let mut cmd = AsyncCommand::new(BLOODHOUND_BIN);

// Check for requested format, default is text
if let Some(format) = query.get("format") {
cmd.arg("-f").arg(format);
}

cmd.arg("-c").arg(BLOODHOUND_FIPS_CHECKS);

let output = cmd.output().await.context(error::ReportExecSnafu)?;
ensure!(
output.status.success(),
error::ReportResultSnafu {
exit_code: match output.status.code() {
Some(code) => code,
None => output.status.signal().unwrap_or(1),
},
stderr: String::from_utf8_lossy(&output.stderr),
}
);
Ok(HttpResponse::Ok()
.content_type("application/text")
.body(String::from_utf8_lossy(&output.stdout).to_string()))
}

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

// Helpers for handler methods called by the router
Expand Down
25 changes: 25 additions & 0 deletions sources/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -682,3 +682,28 @@ paths:
description: "Unprocessable request"
500:
description: "Server error"

/report/fips:
get:
summary: "Get FIPS Security Policy report"
operationId: "fips-report"
parameters:
- in: query
name: format
description: "The FIPS Security Policy report format (text or json). Default format is text."
schema:
type: string
required: false
responses:
200:
description: "Successful request"
content:
application/json:
schema:
type: string
400:
description: "Bad request input"
422:
description: "Unprocessable request"
500:
description: "Server error"
Loading