Skip to content

Commit 9e479d3

Browse files
committed
ROX-31430: use FIPS mode for gRPC communication
With this patch, we move away from using the tonic provided TLS implementation to injecting a manually built native-tls configuration, then using that to create a hyper HttpsConnector and finally telling tonic to use that connector for handling the underlying HTTPs connections needed for gRPC. In case no TLS certificates are provided, plain HTTP is used.
1 parent 18bdcaa commit 9e479d3

File tree

8 files changed

+301
-153
lines changed

8 files changed

+301
-153
lines changed

Cargo.lock

Lines changed: 175 additions & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,28 @@ clap = { version = "4.5.41", features = ["derive", "env"] }
1818
env_logger = { version = "0.11.5", default-features = false, features = ["humantime"] }
1919
http-body-util = "0.1.3"
2020
hyper = { version = "1.6.0", default-features = false }
21+
hyper-tls = "0.6.0"
2122
hyper-util = { version = "0.1.16", default-features = false }
2223
libc = { version = "0.2.159", default-features = false }
2324
log = { version = "0.4.22", default-features = false }
25+
native-tls = { version = "0.2.14", features = ["alpn"] }
26+
openssl = "0.10.75"
2427
prometheus-client = { version = "0.24.0", default-features = false }
2528
prost = "0.14.0"
2629
prost-types = "0.14.0"
2730
serde = { version = "1.0.219", features = ["derive"] }
2831
serde_json = "1.0.142"
2932
tokio = { version = "1.40.0", default-features = false, features = [
33+
"fs",
3034
"macros",
3135
"rt",
3236
"rt-multi-thread",
3337
"net",
3438
"signal",
3539
] }
40+
tokio-native-tls = "0.3.1"
3641
tokio-stream = { version = "0.1.17", features = ["sync"] }
37-
tonic = { version = "0.14.0", features = ["tls-ring"] }
42+
tonic = { version = "0.14.0" }
3843
tonic-prost = "0.14.0"
3944
tonic-prost-build = "0.14.0"
4045
uuid = { version = "1.17.0", features = ["v4"] }

Containerfile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ FROM quay.io/centos/centos:stream9 AS builder
33
RUN dnf install --enablerepo=crb -y \
44
clang \
55
libbpf-devel \
6+
openssl-devel \
67
protobuf-compiler \
78
protobuf-devel && \
89
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
@@ -22,7 +23,14 @@ RUN --mount=type=cache,target=/root/.cargo/registry \
2223
cargo build --release && \
2324
cp target/release/fact fact
2425

25-
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
26+
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
27+
28+
RUN microdnf install -y openssl && \
29+
microdnf clean all && \
30+
rpm --verbose -e --nodeps $( \
31+
rpm -qa 'curl' '*rpm*' '*dnf*' '*libsolv*' '*hawkey*' 'yum*' 'libyaml*' 'libarchive*' \
32+
) && \
33+
rm -rf /var/cache/yum
2634

2735
COPY --from=build /app/fact /usr/local/bin
2836

fact/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ clap = { workspace = true }
1212
env_logger = { workspace = true }
1313
http-body-util = { workspace = true }
1414
hyper = { workspace = true }
15+
hyper-tls = { workspace = true }
1516
hyper-util = { workspace = true }
1617
libc = { workspace = true }
1718
log = { workspace = true }
19+
native-tls = { workspace = true }
20+
openssl = { workspace = true }
1821
tonic = { workspace = true }
1922
tokio = { workspace = true }
23+
tokio-native-tls = { workspace = true }
2024
tokio-stream = { workspace = true }
2125
prometheus-client = { workspace = true }
2226
prost = { workspace = true }

fact/src/output/grpc.rs

Lines changed: 77 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,26 @@
1-
use std::{fs::read_to_string, path::Path, sync::Arc, time::Duration};
1+
use std::{sync::Arc, time::Duration};
22

3-
use anyhow::bail;
3+
use anyhow::{bail, Context};
44
use fact_api::file_activity_service_client::FileActivityServiceClient;
5+
use hyper::Uri;
6+
use hyper_tls::HttpsConnector;
7+
use hyper_util::client::legacy::connect::HttpConnector;
58
use log::{debug, info, warn};
9+
use native_tls::{Certificate, Identity};
10+
use openssl::{ec::EcKey, pkey::PKey};
611
use tokio::{
12+
fs,
713
sync::{broadcast, watch},
814
time::sleep,
915
};
1016
use tokio_stream::{
1117
wrappers::{errors::BroadcastStreamRecvError, BroadcastStream},
1218
StreamExt,
1319
};
14-
use tonic::{
15-
metadata::MetadataValue,
16-
service::Interceptor,
17-
transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity},
18-
};
20+
use tonic::transport::Channel;
1921

2022
use crate::{config::GrpcConfig, event::Event, metrics::EventCounter};
2123

22-
struct Certs {
23-
pub ca: Certificate,
24-
pub identity: Identity,
25-
}
26-
27-
impl TryFrom<&Path> for Certs {
28-
type Error = anyhow::Error;
29-
30-
fn try_from(path: &Path) -> Result<Self, Self::Error> {
31-
let ca = read_to_string(path.join("ca.pem"))?;
32-
let ca = Certificate::from_pem(ca);
33-
let cert = read_to_string(path.join("cert.pem"))?;
34-
let key = read_to_string(path.join("key.pem"))?;
35-
let identity = Identity::from_pem(cert, key);
36-
37-
Ok(Self { ca, identity })
38-
}
39-
}
40-
41-
struct UserAgentInterceptor {}
42-
43-
impl Interceptor for UserAgentInterceptor {
44-
fn call(
45-
&mut self,
46-
mut request: tonic::Request<()>,
47-
) -> Result<tonic::Request<()>, tonic::Status> {
48-
request
49-
.metadata_mut()
50-
.insert("user-agent", MetadataValue::from_static("Rox SFA Agent"));
51-
Ok(request)
52-
}
53-
}
54-
5524
pub struct Client {
5625
rx: broadcast::Receiver<Arc<Event>>,
5726
running: watch::Receiver<bool>,
@@ -89,46 +58,92 @@ impl Client {
8958
info!("Stopping gRPC output...");
9059
break;
9160
}
92-
Err(e) => warn!("gRPC error: {e}"),
61+
Err(e) => warn!("gRPC error: {e:?}"),
9362
}
9463
}
9564
});
9665
}
9766

98-
fn create_channel(&self) -> anyhow::Result<Endpoint> {
99-
let config = self.config.borrow();
100-
let Some(url) = config.url() else {
101-
bail!("Attempting to run gRPC client with no URL");
67+
async fn get_tls_connector(&self) -> anyhow::Result<Option<tokio_native_tls::TlsConnector>> {
68+
let certs = {
69+
let config = self.config.borrow();
70+
let Some(certs) = config.certs() else {
71+
return Ok(None);
72+
};
73+
certs.to_owned()
74+
};
75+
let ca = fs::read(certs.join("ca.pem")).await?;
76+
let ca = Certificate::from_pem(&ca).context("Failed to parse CA")?;
77+
let cert = fs::read(certs.join("cert.pem")).await?;
78+
79+
// The key is in PKCS#1 format using EC algorithm, we
80+
// need it in PKCS#8 format for native-tls, so we
81+
// convert it here
82+
let key = fs::read(certs.join("key.pem")).await?;
83+
let key = EcKey::private_key_from_pem(&key)?;
84+
let key = PKey::from_ec_key(key)?;
85+
let key = key.private_key_to_pem_pkcs8()?;
86+
87+
let id = match Identity::from_pkcs8(&cert, &key) {
88+
Ok(id) => id,
89+
Err(e) => bail!("Failed to create identity: {e:?}"),
90+
};
91+
let connector = native_tls::TlsConnector::builder()
92+
.add_root_certificate(ca)
93+
.identity(id)
94+
.request_alpns(&["h2"])
95+
.build()?;
96+
Ok(Some(connector.into()))
97+
}
98+
99+
fn get_https_connector(
100+
&self,
101+
connector: Option<tokio_native_tls::TlsConnector>,
102+
) -> anyhow::Result<Option<HttpsConnector<HttpConnector>>> {
103+
match connector {
104+
Some(connector) => {
105+
let mut http = HttpConnector::new();
106+
http.enforce_http(false);
107+
let mut connector = HttpsConnector::from((http, connector));
108+
connector.https_only(true);
109+
Ok(Some(connector))
110+
}
111+
None => Ok(None),
112+
}
113+
}
114+
115+
async fn create_channel(
116+
&self,
117+
connector: Option<HttpsConnector<HttpConnector>>,
118+
) -> anyhow::Result<Channel> {
119+
let url = match self.config.borrow().url() {
120+
Some(url) => url.to_string(),
121+
None => bail!("Attempting to run gRPC client with no URL"),
102122
};
103-
let url = url.to_string();
104-
let certs = config.certs().map(Certs::try_from).transpose()?;
105-
let mut channel = Channel::from_shared(url)?;
106-
if let Some(certs) = certs {
107-
let tls = ClientTlsConfig::new()
108-
.domain_name("sensor.stackrox.svc")
109-
.ca_certificate(certs.ca.clone())
110-
.identity(certs.identity.clone());
111-
channel = channel.tls_config(tls)?;
123+
let channel = Channel::from_shared(url)?;
124+
match connector {
125+
Some(connector) => Ok(channel.connect_with_connector(connector).await?),
126+
None => Ok(channel.connect().await?),
112127
}
113-
Ok(channel)
114128
}
115129

116130
async fn run(&mut self) -> anyhow::Result<bool> {
117-
let channel = self.create_channel()?;
131+
let tls_connector = self.get_tls_connector().await?;
132+
let connector = self.get_https_connector(tls_connector)?;
118133
loop {
119134
info!("Attempting to connect to gRPC server...");
120-
let channel = match channel.connect().await {
135+
let channel = match self.create_channel(connector.clone()).await {
121136
Ok(channel) => channel,
122137
Err(e) => {
123-
debug!("Failed to connect to server: {e}");
124-
sleep(Duration::new(1, 0)).await;
138+
debug!("Failed to connect to server: {e:?}");
139+
sleep(Duration::from_secs(1)).await;
125140
continue;
126141
}
127142
};
128143
info!("Successfully connected to gRPC server");
129144

130-
let mut client =
131-
FileActivityServiceClient::with_interceptor(channel, UserAgentInterceptor {});
145+
let uri = Uri::from_static("https://sensor.stackrox.svc");
146+
let mut client = FileActivityServiceClient::with_origin(channel, uri);
132147

133148
let metrics = self.metrics.clone();
134149
let rx =
@@ -149,7 +164,7 @@ impl Client {
149164
res = client.communicate(rx) => {
150165
match res {
151166
Ok(_) => info!("gRPC stream ended"),
152-
Err(e) => warn!("gRPC stream error: {e}"),
167+
Err(e) => warn!("gRPC stream error: {e:?}"),
153168
}
154169
}
155170
_ = self.config.changed() => return Ok(true),

konflux.Containerfile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ RUN echo "Checking required FACT_TAG"; [[ "${FACT_TAG}" != "" ]]
66
RUN dnf install -y \
77
clang \
88
libbpf-devel \
9+
openssl-devel \
910
protobuf-compiler \
1011
protobuf-devel \
1112
cargo \
@@ -17,7 +18,7 @@ COPY . .
1718

1819
RUN cargo build --release
1920

20-
FROM registry.access.redhat.com/ubi9/ubi-micro@sha256:f45ee3d1f8ea8cd490298769daac2ac61da902e83715186145ac2e65322ddfc8
21+
FROM registry.access.redhat.com/ubi9/ubi-minimal@sha256:6fc28bcb6776e387d7a35a2056d9d2b985dc4e26031e98a2bd35a7137cd6fd71
2122

2223
ARG FACT_TAG
2324

@@ -39,6 +40,13 @@ LABEL \
3940
# We also set it to not inherit one from a base stage in case it's RHEL or UBI.
4041
release="1"
4142

43+
RUN microdnf install -y openssl && \
44+
microdnf clean all && \
45+
rpm --verbose -e --nodeps $( \
46+
rpm -qa 'curl' '*rpm*' '*dnf*' '*libsolv*' '*hawkey*' 'yum*' 'libyaml*' 'libarchive*' \
47+
) && \
48+
rm -rf /var/cache/yum
49+
4250
COPY --from=builder /app/target/release/fact /usr/local/bin
4351

4452
ENTRYPOINT ["fact"]

rpms.in.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ packages:
55
- cargo
66
- clang
77
- libbpf-devel
8+
- openssl
9+
- openssl-devel
810
- protobuf-compiler
911
- protobuf-devel
1012
- rust

rpms.lock.yaml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ arches:
200200
name: llvm-libs
201201
evr: 20.1.8-3.el9
202202
sourcerpm: llvm-20.1.8-3.el9.src.rpm
203+
- url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/o/openssl-devel-3.5.1-4.el9_7.aarch64.rpm
204+
repoid: rhel-9-for-aarch64-appstream-rpms
205+
size: 4996902
206+
checksum: sha256:a250fae31cced54a51c0c4aacdd44855044652eb39f4141e23fe197d2528ff0b
207+
name: openssl-devel
208+
evr: 1:3.5.1-4.el9_7
209+
sourcerpm: openssl-3.5.1-4.el9_7.src.rpm
203210
- url: https://cdn.redhat.com/content/dist/rhel9/9/aarch64/appstream/os/Packages/p/policycoreutils-python-utils-3.6-3.el9.noarch.rpm
204211
repoid: rhel-9-for-aarch64-appstream-rpms
205212
size: 77697
@@ -1429,13 +1436,13 @@ arches:
14291436
name: glibc-headers
14301437
evr: 2.34-231.el9_7.2
14311438
sourcerpm: glibc-2.34-231.el9_7.2.src.rpm
1432-
- url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-611.13.1.el9_7.x86_64.rpm
1439+
- url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/k/kernel-headers-5.14.0-611.16.1.el9_7.x86_64.rpm
14331440
repoid: rhel-9-for-x86_64-appstream-rpms
1434-
size: 2991381
1435-
checksum: sha256:c144c57a9804a2a98f6f34f0769e0cfadfedd9d1f9a7b1e5ad0e548b108355ff
1441+
size: 2993337
1442+
checksum: sha256:3b3c3dd0e7e442f8d44d33e9c8c5d2989045626b53977de2c28ff174b3b26c5b
14361443
name: kernel-headers
1437-
evr: 5.14.0-611.13.1.el9_7
1438-
sourcerpm: kernel-5.14.0-611.13.1.el9_7.src.rpm
1444+
evr: 5.14.0-611.16.1.el9_7
1445+
sourcerpm: kernel-5.14.0-611.16.1.el9_7.src.rpm
14391446
- url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/l/libmpc-1.2.1-4.el9.x86_64.rpm
14401447
repoid: rhel-9-for-x86_64-appstream-rpms
14411448
size: 66075
@@ -1499,6 +1506,13 @@ arches:
14991506
name: llvm-libs
15001507
evr: 20.1.8-3.el9
15011508
sourcerpm: llvm-20.1.8-3.el9.src.rpm
1509+
- url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/o/openssl-devel-3.5.1-4.el9_7.x86_64.rpm
1510+
repoid: rhel-9-for-x86_64-appstream-rpms
1511+
size: 4997984
1512+
checksum: sha256:3aeba34c9a9c3313b16166111a1dfe61a29ffaff671bb8f0be95eb0e2dede860
1513+
name: openssl-devel
1514+
evr: 1:3.5.1-4.el9_7
1515+
sourcerpm: openssl-3.5.1-4.el9_7.src.rpm
15021516
- url: https://cdn.redhat.com/content/dist/rhel9/9/x86_64/appstream/os/Packages/p/policycoreutils-python-utils-3.6-3.el9.noarch.rpm
15031517
repoid: rhel-9-for-x86_64-appstream-rpms
15041518
size: 77697

0 commit comments

Comments
 (0)