Skip to content

Commit e56efdd

Browse files
committed
Add Corgid for new method of Inspector Integration
This binary does following: - This converts application inventory on the host to Cyclone Dx sbom - Send the Cyslone Dx sbom to the new telemetart API
1 parent e4a3a2d commit e56efdd

10 files changed

Lines changed: 1107 additions & 1 deletion

File tree

Dockerfile

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,45 @@ FROM public.ecr.aws/amazonlinux/amazonlinux:2023 AS builder
22

33
RUN dnf install -y \
44
'dnf-command(download)' \
5-
cpio
5+
cpio \
6+
gcc \
7+
pkg-config \
8+
tar \
9+
gzip \
10+
cmake \
11+
clang \
12+
clang-devel
13+
14+
# Install Rust toolchain
15+
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
16+
ENV PATH="/root/.cargo/bin:${PATH}"
17+
18+
# Add musl targets for static compilation on both architectures
19+
RUN rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl
20+
21+
# Install musl cross-compilation toolchain
22+
RUN curl -L https://musl.cc/x86_64-linux-musl-cross.tgz | tar -xz -C /opt && \
23+
curl -L https://musl.cc/aarch64-linux-musl-cross.tgz | tar -xz -C /opt
24+
25+
# Set up cross-compilation environment
26+
ENV CC_x86_64_unknown_linux_musl="/opt/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc" \
27+
CC_aarch64_unknown_linux_musl="/opt/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc" \
28+
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER="/opt/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc" \
29+
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER="/opt/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc" \
30+
BINDGEN_EXTRA_CLANG_ARGS_aarch64_unknown_linux_musl="--sysroot=/opt/aarch64-linux-musl-cross/aarch64-linux-musl" \
31+
BINDGEN_EXTRA_CLANG_ARGS_x86_64_unknown_linux_musl="--sysroot=/opt/x86_64-linux-musl-cross/x86_64-linux-musl"
632

733
WORKDIR /root/build/util-linux
834
RUN dnf download util-linux && \
935
rpm2cpio util-linux-*.rpm | cpio -idmv
1036

37+
# Build corgid binary for both architectures
38+
WORKDIR /root/build
39+
COPY ./sources/corgid ./corgid/
40+
WORKDIR /root/build/corgid
41+
RUN cargo build --release --target x86_64-unknown-linux-musl && \
42+
cargo build --release --target aarch64-unknown-linux-musl
43+
1144
FROM public.ecr.aws/amazonlinux/amazonlinux:2023
1245

1346
# IMAGE_VERSION is the assigned version from input for this image.
@@ -62,6 +95,18 @@ COPY --from=builder /root/build/util-linux/usr/share/licenses/util-linux/COPYING
6295
/usr/share/licenses/util-linux/
6396
RUN ln -s /opt/util-linux/bin/* /usr/bin
6497

98+
# Copy corgid binary for the target architecture
99+
COPY --from=builder /root/build/corgid/target/x86_64-unknown-linux-musl/release/corgid /tmp/corgid-x86_64
100+
COPY --from=builder /root/build/corgid/target/aarch64-unknown-linux-musl/release/corgid /tmp/corgid-aarch64
101+
RUN ARCH=$(uname -m) && \
102+
if [ "$ARCH" = "x86_64" ]; then \
103+
cp /tmp/corgid-x86_64 /usr/sbin/corgid; \
104+
elif [ "$ARCH" = "aarch64" ]; then \
105+
cp /tmp/corgid-aarch64 /usr/sbin/corgid; \
106+
fi && \
107+
chmod +x /usr/sbin/corgid && \
108+
rm -f /tmp/corgid-*
109+
65110
# Validate amazon-ssm-agent binary
66111
RUN /usr/bin/amazon-ssm-agent -version
67112
# Validate lscpu binary

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,42 @@ For example:
3838
# ex: echo '{"ssm":{"activation-id":"foo","activation-code":"bar","region":"us-west-2"}}' | base64
3939
user-data = "eyJzc20iOnsiYWN0aXZhdGlvbi1pZCI6ImZvbyIsImFjdGl2YXRpb24tY29kZSI6ImJhciIsInJlZ2lvbiI6InVzLXdlc3QtMiJ9fQo="
4040
```
41+
42+
## Inspector SBOM Upload (corgid)
43+
44+
This container includes `corgid`, a binary that collects the Bottlerocket package inventory, converts it to a [CycloneDX](https://cyclonedx.org/) SBOM, and sends it to the Amazon Inspector API for vulnerability scanning. It runs automatically in the background when the container starts.
45+
46+
### Disabling corgid
47+
48+
To disable Inspector SBOM upload, set `inspector-sbom-upload` to `"false"` in the control container's user data:
49+
50+
```json
51+
{
52+
"inspector": {
53+
"upload-sbom": "false"
54+
}
55+
}
56+
```
57+
58+
Base64-encode the JSON and set it in your instance user data:
59+
60+
```toml
61+
[settings.host-containers.control]
62+
# echo '{"inspector": {"upload-sbom": "false"}}' | base64
63+
user-data = "eyJpbnNwZWN0b3IiOiB7InVwbG9hZC1zYm9tIjogImZhbHNlIn19"
64+
```
65+
66+
This can be combined with SSM hybrid activation settings in the same JSON object:
67+
68+
```json
69+
{
70+
"ssm": {
71+
"activation-id": "foo",
72+
"activation-code": "bar",
73+
"region": "us-west-2"
74+
},
75+
"inspector": {
76+
"upload-sbom": "false"
77+
}
78+
}
79+
```

sources/corgid/Cargo.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "corgid"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[[bin]]
7+
name = "corgid"
8+
path = "src/main.rs"
9+
10+
[dependencies]
11+
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
12+
serde = { version = "1", features = ["derive"] }
13+
serde_json = "1"
14+
sha2 = "0.10"
15+
hex = "0.4"
16+
hmac = "0.12"
17+
flate2 = "1"
18+
chrono = { version = "0.4", features = ["now"] }
19+
uuid = { version = "1", features = ["v4"] }
20+
log = "0.4"
21+
simplelog = "0.12"
22+
snafu = "0.8"
23+
base64 = "0.22"
24+
rustls = { version = "0.23", features = ["aws-lc-rs"] }

sources/corgid/src/error.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use snafu::Snafu;
2+
3+
#[derive(Debug, Snafu)]
4+
#[snafu(visibility(pub))]
5+
pub enum Error {
6+
#[snafu(display("Failed to read application inventory file: {source}"))]
7+
ReadInventory { source: std::io::Error },
8+
9+
#[snafu(display("Failed to parse application inventory file: {source}"))]
10+
ParseInventory { source: serde_json::Error },
11+
12+
#[snafu(display("Failed to serialize SBOM: {source}"))]
13+
SerializeSbom { source: serde_json::Error },
14+
15+
#[snafu(display("Failed to fetch IMDS credentials: {source}"))]
16+
ImdsCredentials { source: reqwest::Error },
17+
18+
#[snafu(display("Failed to parse IMDS credentials: {source}"))]
19+
ParseCredentials { source: serde_json::Error },
20+
21+
#[snafu(display("HTTP request failed: {source}"))]
22+
HttpRequest { source: reqwest::Error },
23+
24+
#[snafu(display("Inspector API error {status}: {body}"))]
25+
Api { status: u16, body: String },
26+
27+
#[snafu(display("Failed to parse API response: {source}"))]
28+
ParseResponse { source: serde_json::Error },
29+
30+
#[snafu(display("Failed to compress SBOM: {source}"))]
31+
Compress { source: std::io::Error },
32+
33+
#[snafu(display("No IAM role found in IMDS"))]
34+
NoIamRole,
35+
36+
#[snafu(display("No instance ID found in IMDS"))]
37+
NoInstanceId,
38+
}
39+
40+
pub type Result<T> = std::result::Result<T, Error>;

sources/corgid/src/imds.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//! EC2 Instance Metadata Service (IMDS) client.
2+
//!
3+
//! Retrieves IAM credentials from IMDS using IMDSv2 session tokens.
4+
5+
use crate::error::{self, Result};
6+
use reqwest::blocking::Client;
7+
use serde::Deserialize;
8+
use snafu::{OptionExt, ResultExt};
9+
10+
const IMDS_BASE: &str = "http://169.254.169.254";
11+
const IMDS_TOKEN_PATH: &str = "/latest/api/token";
12+
const IMDS_ROLE_PATH: &str = "/latest/meta-data/iam/security-credentials/";
13+
14+
/// IAM credentials retrieved from IMDS for signing API requests.
15+
#[derive(Deserialize)]
16+
pub struct Credentials {
17+
#[serde(rename = "AccessKeyId")]
18+
pub access_key_id: String,
19+
#[serde(rename = "SecretAccessKey")]
20+
pub secret_access_key: String,
21+
#[serde(rename = "Token")]
22+
pub token: String,
23+
}
24+
25+
/// Retrieves IAM credentials from the EC2 Instance Metadata Service.
26+
///
27+
/// Uses IMDSv2 with a session token for security.
28+
pub fn get_credentials(client: &Client) -> Result<Credentials> {
29+
let token = client
30+
.put(format!("{}{}", IMDS_BASE, IMDS_TOKEN_PATH))
31+
.header("X-aws-ec2-metadata-token-ttl-seconds", "21600")
32+
.send()
33+
.context(error::ImdsCredentialsSnafu)?
34+
.text()
35+
.context(error::ImdsCredentialsSnafu)?;
36+
37+
let roles = client
38+
.get(format!("{}{}", IMDS_BASE, IMDS_ROLE_PATH))
39+
.header("X-aws-ec2-metadata-token", &token)
40+
.send()
41+
.context(error::ImdsCredentialsSnafu)?
42+
.text()
43+
.context(error::ImdsCredentialsSnafu)?;
44+
45+
let role = roles.lines().next().context(error::NoIamRoleSnafu)?;
46+
47+
let creds_json = client
48+
.get(format!("{}{}{}", IMDS_BASE, IMDS_ROLE_PATH, role))
49+
.header("X-aws-ec2-metadata-token", &token)
50+
.send()
51+
.context(error::ImdsCredentialsSnafu)?
52+
.text()
53+
.context(error::ImdsCredentialsSnafu)?;
54+
55+
serde_json::from_str(&creds_json).context(error::ParseCredentialsSnafu)
56+
}

0 commit comments

Comments
 (0)