Skip to content

Commit 14d3cff

Browse files
committed
Revert "Revert "feat: Sigstore verification of policies on download""
This reverts commit dec0a7a.
1 parent adea208 commit 14d3cff

File tree

6 files changed

+170
-27
lines changed

6 files changed

+170
-27
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ name = "policy-server"
33
version = "0.2.5"
44
authors = [
55
"Flavio Castelli <fcastelli@suse.com>",
6-
"Rafael Fernández López <rfernandezlopez@suse.com>"
6+
"Rafael Fernández López <rfernandezlopez@suse.com>",
7+
"Víctor Cuadrado Juan <vcuadradojuan@suse.de>"
78
]
89
edition = "2018"
910

README.md

+5-6
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ For example, given the configuration file from above, the `namespace_simple` pol
6060
will be invoked with the `valid_namespace` parameter set to `kubewarden-approved`.
6161

6262
Note well: it's possible to expose the same policy multiple times, each time with
63-
a different set of paraments.
63+
a different set of parameters.
6464

6565
The Wasm file providing the Kubewarden Policy can be either loaded from
6666
the local filesystem or it can be fetched from a remote location. The behaviour
@@ -76,7 +76,7 @@ depends on the URL format provided by the user:
7676

7777
The verbosity of policy-server can be configured via the `--log-level` flag.
7878
The default log level used is `info`, but `trace`, `debug`, `warn` and `error`
79-
levels are availble too.
79+
levels are available too.
8080

8181
Policy server can produce logs events using different formats. The `--log-fmt`
8282
flag is used to choose the format to be used.
@@ -111,12 +111,11 @@ our [official docs](https://docs.kubewarden.io/operator-manual/tracing/01-quicks
111111

112112
# Building
113113

114-
You can either build `kubewarden-admission` from sources (see below) or you can
115-
use the container image we maintain inside of our
114+
You can use the container image we maintain inside of our
116115
[GitHub Container Registry](https://github.com/orgs/kubewarden/packages/container/package/policy-server).
117116

118-
The `policy-server` binary can be built in this way:
117+
Alternatively, the `policy-server` binary can be built in this way:
119118

120119
```shell
121-
$ cargo build
120+
$ make build
122121
```

src/cli.rs

+37-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::settings::{read_policies_file, Policy};
1+
use crate::settings::{read_policies_file, read_verification_file, Policy, VerificationSettings};
22
use anyhow::{anyhow, Result};
33
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
44
use itertools::Itertools;
@@ -109,6 +109,13 @@ pub(crate) fn build_cli() -> App<'static, 'static> {
109109
.env("KUBEWARDEN_SOURCES_PATH")
110110
.help("YAML file holding source information (https, registry insecure hosts, custom CA's...)"),
111111
)
112+
.arg(
113+
Arg::with_name("verification-path")
114+
.env("KUBEWARDEN_VERIFICATION_CONFIG_PATH")
115+
.long("verification-path")
116+
.default_value("verification.yml")
117+
.help("YAML file holding verification information (URIs, keys, annotations...)"),
118+
)
112119
.arg(
113120
Arg::with_name("docker-config-json-path")
114121
.env("KUBEWARDEN_DOCKER_CONFIG_JSON_PATH")
@@ -121,7 +128,14 @@ pub(crate) fn build_cli() -> App<'static, 'static> {
121128
.long("enable-metrics")
122129
.required(false)
123130
.takes_value(false)
124-
.help("Enable metrics"),
131+
.help("Enable metrics [env: KUBEWARDEN_ENABLE_METRICS=]"),
132+
)
133+
.arg(
134+
Arg::with_name("enable-verification")
135+
.long("enable-verification")
136+
.required(false)
137+
.takes_value(false)
138+
.help("Enable Sigstore verification [env: KUBEWARDEN_ENABLE_VERIFICATION=]"),
125139
)
126140
.long_version(VERSION_AND_BUILTINS.as_str())
127141
}
@@ -157,6 +171,27 @@ pub(crate) fn policies(matches: &clap::ArgMatches) -> Result<HashMap<String, Pol
157171
})
158172
}
159173

174+
pub(crate) fn verification_settings(matches: &clap::ArgMatches) -> Result<VerificationSettings> {
175+
let verification_file = Path::new(matches.value_of("verification-path").unwrap_or("."));
176+
match read_verification_file(verification_file) {
177+
Err(e) => Err(anyhow!(
178+
"error while loading verification info from {:?}: {}",
179+
verification_file,
180+
e
181+
)),
182+
Ok(vs) => {
183+
if vs.verification_keys.is_empty() {
184+
Err(anyhow!(
185+
"error while loading verification info from {:?}: contains 0 verification keys",
186+
verification_file,
187+
))
188+
} else {
189+
Ok(vs)
190+
}
191+
}
192+
}
193+
}
194+
160195
// Setup the tracing system. This MUST be done inside of a tokio Runtime
161196
// because some collectors rely on it and would panic otherwise.
162197
pub(crate) fn setup_tracing(matches: &clap::ArgMatches) -> Result<()> {

src/main.rs

+99-18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use anyhow::Result;
66
use opentelemetry::global::shutdown_tracer_provider;
77
use policy_evaluator::callback_handler::CallbackHandlerBuilder;
88
use policy_evaluator::policy_metadata::Metadata;
9+
use settings::VerificationSettings;
910
use std::path::PathBuf;
1011
use std::{process, thread};
1112
use tokio::{runtime::Runtime, sync::mpsc, sync::oneshot};
@@ -40,6 +41,35 @@ fn main() -> Result<()> {
4041
.expect("error parsing the number of workers")
4142
});
4243

44+
// As of clap version 2, we have to do this check in separate
45+
// steps.
46+
//
47+
// `--enable-metrics` does not take a value. However, using
48+
// `env` from clap to link the `KUBEWARDEN_ENABLE_METRICS`
49+
// envvar to this flag forcefully sets `takes_value` on the
50+
// flag, making the usage of `--enable-metrics` argument weird
51+
// (this should not take a value).
52+
//
53+
// The answer is therefore, to just set up the
54+
// `--enable-metrics` flag in clap, and check manually whether
55+
// the environment variable is set.
56+
//
57+
// The same is true for `--verify-policies` flag, and
58+
// KUBEWARDEN_ENABLE_VERIFICATION env var.
59+
//
60+
// Check https://github.com/clap-rs/clap/issues/1476 for
61+
// further details.
62+
let metrics_enabled = matches.is_present("enable-metrics")
63+
|| std::env::var_os("KUBEWARDEN_ENABLE_METRICS").is_some();
64+
let verify_enabled = matches.is_present("enable-verification")
65+
|| std::env::var_os("KUBEWARDEN_ENABLE_VERIFICATION").is_some();
66+
67+
let verification_settings: Option<VerificationSettings> = if verify_enabled {
68+
Some(cli::verification_settings(&matches)?)
69+
} else {
70+
None
71+
};
72+
4373
////////////////////////////////////////////////////////////////////////////
4474
// //
4575
// Phase 1: setup the CallbackHandler. This is used by the synchronous //
@@ -149,24 +179,6 @@ fn main() -> Result<()> {
149179
fatal_error(err.to_string());
150180
}
151181

152-
// As of clap version 2, we have to do this check in separate
153-
// steps.
154-
//
155-
// `--enable-metrics` does not take a value. However, using
156-
// `env` from clap to link the `KUBEWARDEN_ENABLE_METRICS`
157-
// envvar to this flag forcefully sets `takes_value` on the
158-
// flag, making the usage of `--enable-metrics` argument weird
159-
// (this should not take a value).
160-
//
161-
// The answer is therefore, to just set up the
162-
// `--enable-metrics` flag in clap, and check manually whether
163-
// the environment variable is set.
164-
//
165-
// Check https://github.com/clap-rs/clap/issues/1476 for
166-
// further details.
167-
let metrics_enabled = matches.is_present("enable-metrics")
168-
|| std::env::var_os("KUBEWARDEN_ENABLE_METRICS").is_some();
169-
170182
// The unused variable is required so the meter is not dropped early and
171183
// lives for the whole block lifetime, exporting metrics
172184
let _meter = if metrics_enabled {
@@ -186,6 +198,48 @@ fn main() -> Result<()> {
186198
);
187199
for (name, policy) in policies.iter_mut() {
188200
debug!(policy = name.as_str(), "download");
201+
202+
let mut verifier = policy_fetcher::verify::Verifier::new(sources.clone());
203+
let mut verified_manifest_digest: Option<String> = None;
204+
205+
if verify_enabled {
206+
// verify policy prior to pulling for all keys, and keep the
207+
// verified manifest digest of last iteration, even if all are
208+
// the same:
209+
for key_value in verification_settings
210+
.as_ref()
211+
.unwrap()
212+
.verification_keys
213+
.values()
214+
{
215+
verified_manifest_digest = Some(
216+
verifier
217+
.verify(
218+
&policy.url,
219+
docker_config.clone(),
220+
verification_settings
221+
.as_ref()
222+
.unwrap()
223+
.verification_annotations
224+
.clone(),
225+
key_value,
226+
)
227+
.await
228+
.map_err(|e| fatal_error(e.to_string()))
229+
.unwrap(),
230+
);
231+
}
232+
info!(
233+
name = name.as_str(),
234+
sha256sum = verified_manifest_digest
235+
.as_ref()
236+
.unwrap_or(&"unknown".to_string())
237+
.as_str(),
238+
status = "verified-signatures",
239+
"policy download",
240+
);
241+
}
242+
189243
match policy_fetcher::fetch_policy(
190244
&policy.url,
191245
policy_fetcher::PullDestination::Store(PathBuf::from(policies_download_dir)),
@@ -195,6 +249,33 @@ fn main() -> Result<()> {
195249
.await
196250
{
197251
Ok(fetched_policy) => {
252+
if verify_enabled {
253+
if verified_manifest_digest.is_none() {
254+
// when deserializing keys we check that have keys to
255+
// verify. We will always have a digest manifest
256+
fatal_error("Verification of policy failed".to_string());
257+
unreachable!();
258+
}
259+
verifier
260+
.verify_local_file_checksum(
261+
&fetched_policy.uri,
262+
docker_config.clone(),
263+
verified_manifest_digest.as_ref().unwrap(),
264+
)
265+
.await
266+
.map_err(|e| fatal_error(e.to_string()))
267+
.unwrap();
268+
info!(
269+
name = name.as_str(),
270+
sha256sum = verified_manifest_digest
271+
.as_ref()
272+
.unwrap_or(&"unknown".to_string())
273+
.as_str(),
274+
status = "verified-local-checksum",
275+
"policy download",
276+
);
277+
}
278+
198279
if let Ok(Some(policy_metadata)) =
199280
Metadata::from_path(&fetched_policy.local_path)
200281
{

src/settings.rs

+12
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ pub fn read_policies_file(path: &Path) -> Result<HashMap<String, Policy>> {
3636
Ok(ps)
3737
}
3838

39+
#[derive(Deserialize, Debug, Clone)]
40+
pub struct VerificationSettings {
41+
pub verification_keys: HashMap<String, String>,
42+
pub verification_annotations: Option<HashMap<String, String>>,
43+
}
44+
45+
pub fn read_verification_file(path: &Path) -> Result<VerificationSettings> {
46+
let settings_file = File::open(path)?;
47+
let vs: VerificationSettings = serde_yaml::from_reader(&settings_file)?;
48+
Ok(vs)
49+
}
50+
3951
#[cfg(test)]
4052
mod tests {
4153
use super::*;

verification.yml.example

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
verification_keys:
3+
key-name-irrelevant: |
4+
-----BEGIN PUBLIC KEY-----
5+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0HFTtCfTtPmkx5p1RbtwDE1EVzu
6+
wjQs1cCRKb5Pz/yUspkQsN3FO4iyWodCy5j3o0CdIJD/1gvq98pf4IG9tA==
7+
-----END PUBLIC KEY-----
8+
another-key: |
9+
-----BEGIN PUBLIC KEY-----
10+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0HFTtCfTtPmkx5p1RbtwDE1EVzu
11+
wjQs1cCRKb5Pz/yUspkQsN3FO4iyWodCy5j3o0CdIJD/1gvq98pf4IG9tA==
12+
-----END PUBLIC KEY-----
13+
verification_annotations: # optional
14+
env: prod
15+
foo: bar

0 commit comments

Comments
 (0)