Skip to content

Commit 9f1b0d8

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 37d5783 commit 9f1b0d8

File tree

8 files changed

+291
-148
lines changed

8 files changed

+291
-148
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-libs && \
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: 72 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,25 @@
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_tls::HttpsConnector;
6+
use hyper_util::client::legacy::connect::HttpConnector;
57
use log::{debug, info, warn};
8+
use native_tls::{Certificate, Identity};
9+
use openssl::{ec::EcKey, pkey::PKey};
610
use tokio::{
11+
fs,
712
sync::{broadcast, watch},
813
time::sleep,
914
};
1015
use tokio_stream::{
1116
wrappers::{errors::BroadcastStreamRecvError, BroadcastStream},
1217
StreamExt,
1318
};
14-
use tonic::{
15-
metadata::MetadataValue,
16-
service::Interceptor,
17-
transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity},
18-
};
19+
use tonic::transport::Channel;
1920

2021
use crate::{config::GrpcConfig, event::Event, metrics::EventCounter};
2122

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-
5523
pub struct Client {
5624
rx: broadcast::Receiver<Arc<Event>>,
5725
running: watch::Receiver<bool>,
@@ -89,46 +57,88 @@ impl Client {
8957
info!("Stopping gRPC output...");
9058
break;
9159
}
92-
Err(e) => warn!("gRPC error: {e}"),
60+
Err(e) => warn!("gRPC error: {e:?}"),
9361
}
9462
}
9563
});
9664
}
9765

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

116126
async fn run(&mut self) -> anyhow::Result<bool> {
117-
let channel = self.create_channel()?;
127+
let tls_connector = self.get_tls_connector().await?;
128+
let connector = self.get_https_connector(tls_connector);
118129
loop {
119130
info!("Attempting to connect to gRPC server...");
120-
let channel = match channel.connect().await {
131+
let channel = match self.create_channel(connector.clone()).await {
121132
Ok(channel) => channel,
122133
Err(e) => {
123-
debug!("Failed to connect to server: {e}");
124-
sleep(Duration::new(1, 0)).await;
134+
debug!("Failed to connect to server: {e:?}");
135+
sleep(Duration::from_secs(1)).await;
125136
continue;
126137
}
127138
};
128139
info!("Successfully connected to gRPC server");
129140

130-
let mut client =
131-
FileActivityServiceClient::with_interceptor(channel, UserAgentInterceptor {});
141+
let mut client = FileActivityServiceClient::new(channel);
132142

133143
let metrics = self.metrics.clone();
134144
let rx =
@@ -149,7 +159,7 @@ impl Client {
149159
res = client.communicate(rx) => {
150160
match res {
151161
Ok(_) => info!("gRPC stream ended"),
152-
Err(e) => warn!("gRPC stream error: {e}"),
162+
Err(e) => warn!("gRPC stream error: {e:?}"),
153163
}
154164
}
155165
_ = 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-libs && \
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-libs
9+
- openssl-devel
810
- protobuf-compiler
911
- protobuf-devel
1012
- rust

rpms.lock.yaml

Lines changed: 14 additions & 0 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
@@ -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)