Skip to content

Commit 20d2d6c

Browse files
committed
Don't enforce container sigpolicy by default
Closes: #218 Basically this effort has not been really successful and adds more pain than it solves. We need to have a solution that works for podman too. In many scenarios, TLS is sufficient - or at least, we're far from the only thing that if fetched from a compromised server would result in a compromised system (e.g. privileged containers). Signed-off-by: Colin Walters <[email protected]>
1 parent 9138153 commit 20d2d6c

File tree

11 files changed

+172
-24
lines changed

11 files changed

+172
-24
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ jobs:
136136
run: |
137137
set -xeuo pipefail
138138
sudo podman run --rm -ti --privileged -v /:/target -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \
139-
quay.io/centos-bootc/fedora-bootc-dev:eln bootc install to-filesystem --target-no-signature-verification \
139+
quay.io/centos-bootc/fedora-bootc-dev:eln bootc install to-filesystem \
140140
--karg=foo=bar --disable-selinux --replace=alongside /target
141141
ls -al /boot/loader/
142142
sudo grep foo=bar /boot/loader/entries/*.conf

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ For more information, please see [install.md](install.md).
106106
If you have [an operating system already using ostree](https://ostreedev.github.io/ostree/#operating-systems-and-distributions-using-ostree) then you can use `bootc switch`:
107107

108108
```
109-
$ bootc switch --no-signature-verification quay.io/examplecorp/custom:latest
109+
$ bootc switch quay.io/examplecorp/custom:latest
110110
```
111111

112112
This will preserve existing state in `/etc` and `/var` - for example,

docs/install.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ to an existing system and install your container image. Failure to run
5858
Here's an example of using `bootc install` (root/elevated permission required):
5959

6060
```bash
61-
podman run --rm --privileged --pid=host --security-opt label=type:unconfined_t <image> bootc install to-disk --target-no-signature-verification /path/to/disk
61+
podman run --rm --privileged --pid=host --security-opt label=type:unconfined_t <image> bootc install to-disk /path/to/disk
6262
```
6363

6464
Note that while `--privileged` is used, this command will not perform any
@@ -85,10 +85,7 @@ This can be overridden via `--target_imgref`; this is handy in cases like perfor
8585
installation in a manufacturing environment from a mirrored registry.
8686

8787
By default, the installation process will verify that the container (representing the target OS)
88-
can fetch its own updates. A common cause of failure here is not changing the security settings
89-
in `/etc/containers/policy.json` in the target OS to verify signatures.
90-
91-
If you are pushing an unsigned image, you **MUST** specify `bootc install --target-no-signature-verification`.
88+
can fetch its own updates.
9289

9390
Additionally note that to perform an install with a target image reference set to an
9491
authenticated registry, you must provide a pull secret. One path is to embed the pull secret into

lib/src/cli.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,18 @@ pub(crate) struct SwitchOpts {
5555
#[clap(long, default_value = "registry")]
5656
pub(crate) transport: String,
5757

58-
/// Explicitly opt-out of requiring any form of signature verification.
59-
#[clap(long)]
58+
/// This argument is deprecated and does nothing.
59+
#[clap(long, hide = true)]
6060
pub(crate) no_signature_verification: bool,
6161

62+
/// This is the inverse of the previous `--target-no-signature-verification` (which is now
63+
/// a no-op).
64+
///
65+
/// Enabling this option enforces that `/etc/containers/policy.json` includes a
66+
/// default policy which requires signatures.
67+
#[clap(long)]
68+
pub(crate) enforce_container_sigpolicy: bool,
69+
6270
/// Enable verification via an ostree remote
6371
#[clap(long)]
6472
pub(crate) ostree_remote: Option<String>,
@@ -363,7 +371,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
363371
name: opts.target.to_string(),
364372
};
365373
let sigverify = sigpolicy_from_opts(
366-
opts.no_signature_verification,
374+
!opts.enforce_container_sigpolicy,
367375
opts.ostree_remote.as_deref(),
368376
);
369377
let target = ostree_container::OstreeImageReference { sigverify, imgref };
@@ -475,3 +483,22 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
475483
Opt::Man(manopts) => crate::docgen::generate_manpages(&manopts.directory),
476484
}
477485
}
486+
487+
#[test]
488+
fn test_parse_install_args() {
489+
// Verify we still process the legacy --target-no-signature-verification
490+
let o = Opt::try_parse_from([
491+
"bootc",
492+
"install",
493+
"to-filesystem",
494+
"--target-no-signature-verification",
495+
"/target",
496+
])
497+
.unwrap();
498+
let o = match o {
499+
Opt::Install(InstallOpts::ToFilesystem(fsopts)) => fsopts,
500+
o => panic!("Expected filesystem opts, not {o:?}"),
501+
};
502+
assert!(o.target_opts.target_no_signature_verification);
503+
assert_eq!(o.filesystem_opts.root_path.as_str(), "/target");
504+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# This one drops the now-optional signature schema
2+
apiVersion: org.containers.bootc/v1alpha1
3+
kind: BootcHost
4+
metadata:
5+
name: host
6+
spec:
7+
image:
8+
image: quay.io/fedora/fedora-coreos:stable
9+
transport: registry
10+
signature: !ostreeRemote "fedora"
11+
status:
12+
booted:
13+
image:
14+
image:
15+
image: quay.io/otherexample/otherimage:latest
16+
transport: registry
17+
version: 20231230.1
18+
timestamp: 2023-12-30T16:10:11Z
19+
imageDigest: sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
20+
incompatible: false
21+
pinned: false
22+
ostree:
23+
checksum: 41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3
24+
deploySerial: 0
25+
rollback: null
26+
isContainer: false

lib/src/fixtures/spec-v1.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# This one drops the now-optional signature schema
2+
apiVersion: org.containers.bootc/v1alpha1
3+
kind: BootcHost
4+
metadata:
5+
name: host
6+
spec:
7+
image:
8+
image: quay.io/otherexample/otherimage:latest
9+
transport: registry
10+
status:
11+
booted:
12+
image:
13+
image:
14+
image: quay.io/otherexample/otherimage:latest
15+
transport: registry
16+
version: 20231230.1
17+
timestamp: 2023-12-30T16:10:11Z
18+
imageDigest: sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
19+
incompatible: false
20+
pinned: false
21+
ostree:
22+
checksum: 41af286dc0b172ed2f1ca934fd2278de4a1192302ffa07087cea2682e7d372e3
23+
deploySerial: 0
24+
rollback: null
25+
isContainer: false

lib/src/install.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,26 @@ pub(crate) struct InstallTargetOpts {
6767
#[clap(long)]
6868
pub(crate) target_imgref: Option<String>,
6969

70-
/// Explicitly opt-out of requiring any form of signature verification.
71-
#[clap(long)]
70+
/// This command line argument does nothing; it exists for compatibility.
71+
///
72+
/// As of newer versions of bootc, this value is enabled by default,
73+
/// i.e. it is not enforced that a signature
74+
/// verification policy is enabled. Hence to enable it, one can specify
75+
/// `--target-no-signature-verification=false`.
76+
///
77+
/// It is likely that the functionality here will be replaced with a different signature
78+
/// enforcement scheme in the future that integrates with `podman`.
79+
#[clap(long, hide = true)]
7280
#[serde(default)]
7381
pub(crate) target_no_signature_verification: bool,
7482

83+
/// This is the inverse of the previous `--target-no-signature-verification` (which is now
84+
/// a no-op). Enabling this option enforces that `/etc/containers/policy.json` includes a
85+
/// default policy which requires signatures.
86+
#[clap(long)]
87+
#[serde(default)]
88+
pub(crate) enforce_container_sigpolicy: bool,
89+
7590
/// Enable verification via an ostree remote
7691
#[clap(long)]
7792
pub(crate) target_ostree_remote: Option<String>,
@@ -80,10 +95,8 @@ pub(crate) struct InstallTargetOpts {
8095
/// Specifying this option suppresses the check; use this when you know the issues it might find
8196
/// are addressed.
8297
///
83-
/// Two main reasons this might fail:
84-
///
85-
/// - Forgetting `--target-no-signature-verification` if needed
86-
/// - Using a registry which requires authentication, but not embedding the pull secret in the image.
98+
/// A common reason this may fail is when one is using an image which requires registry authentication,
99+
/// but not embedding the pull secret in the image so that updates can be fetched by the installed OS "day 2".
87100
#[clap(long)]
88101
#[serde(default)]
89102
pub(crate) skip_fetch_check: bool,
@@ -916,8 +929,14 @@ async fn prepare_install(
916929

917930
// Parse the target CLI image reference options and create the *target* image
918931
// reference, which defaults to pulling from a registry.
932+
if target_opts.target_no_signature_verification {
933+
// Perhaps log this in the future more prominently, but no reason to annoy people.
934+
tracing::debug!(
935+
"Use of --target-no-signature-verification flag which is enabled by default"
936+
);
937+
}
919938
let target_sigverify = sigpolicy_from_opts(
920-
target_opts.target_no_signature_verification,
939+
!target_opts.enforce_container_sigpolicy,
921940
target_opts.target_ostree_remote.as_deref(),
922941
);
923942
let target_imgname = target_opts

lib/src/privtests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ fn test_install_filesystem(image: &str, blockdev: &Utf8Path) -> Result<()> {
152152
let mountpoint: &Utf8Path = mountpoint_dir.path().try_into().unwrap();
153153

154154
// And run the install
155-
cmd!(sh, "podman run --rm --privileged --pid=host --env=RUST_LOG -v /usr/bin/bootc:/usr/bin/bootc -v {mountpoint}:/target-root {image} bootc install to-filesystem --target-no-signature-verification /target-root").run()?;
155+
cmd!(sh, "podman run --rm --privileged --pid=host --env=RUST_LOG -v /usr/bin/bootc:/usr/bin/bootc -v {mountpoint}:/target-root {image} bootc install to-filesystem /target-root").run()?;
156156

157157
cmd!(sh, "umount -R {mountpoint}").run()?;
158158

lib/src/spec.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ pub struct ImageReference {
5151
pub image: String,
5252
/// The container image transport
5353
pub transport: String,
54-
/// Disable signature verification
55-
pub signature: ImageSignature,
54+
/// Signature verification type
55+
#[serde(skip_serializing_if = "Option::is_none")]
56+
pub signature: Option<ImageSignature>,
5657
}
5758

5859
/// The status of the booted image
@@ -132,12 +133,33 @@ mod tests {
132133
use super::*;
133134

134135
#[test]
135-
fn test_parse_spec() {
136+
fn test_parse_spec_v0() {
136137
const SPEC_FIXTURE: &str = include_str!("fixtures/spec.yaml");
137138
let host: Host = serde_yaml::from_str(SPEC_FIXTURE).unwrap();
138139
assert_eq!(
139140
host.spec.image.as_ref().unwrap().image.as_str(),
140141
"quay.io/example/someimage:latest"
141142
);
142143
}
144+
145+
#[test]
146+
fn test_parse_spec_v1() {
147+
const SPEC_FIXTURE: &str = include_str!("fixtures/spec-v1.yaml");
148+
let host: Host = serde_yaml::from_str(SPEC_FIXTURE).unwrap();
149+
assert_eq!(
150+
host.spec.image.as_ref().unwrap().image.as_str(),
151+
"quay.io/otherexample/otherimage:latest"
152+
);
153+
assert_eq!(host.spec.image.as_ref().unwrap().signature, None);
154+
}
155+
156+
#[test]
157+
fn test_parse_ostreeremote() {
158+
const SPEC_FIXTURE: &str = include_str!("fixtures/spec-ostree-remote.yaml");
159+
let host: Host = serde_yaml::from_str(SPEC_FIXTURE).unwrap();
160+
assert_eq!(
161+
host.spec.image.as_ref().unwrap().signature,
162+
Some(ImageSignature::OstreeRemote("fedora".into()))
163+
);
164+
}
143165
}

lib/src/status.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@ fn transport_to_string(transport: ostree_container::Transport) -> String {
5151

5252
impl From<OstreeImageReference> for ImageReference {
5353
fn from(imgref: OstreeImageReference) -> Self {
54+
let signature = match imgref.sigverify {
55+
ostree_container::SignatureSource::ContainerPolicyAllowInsecure => None,
56+
v => Some(v.into()),
57+
};
5458
Self {
55-
signature: imgref.sigverify.into(),
59+
signature,
5660
transport: transport_to_string(imgref.imgref.transport),
5761
image: imgref.imgref.name,
5862
}
@@ -61,8 +65,12 @@ impl From<OstreeImageReference> for ImageReference {
6165

6266
impl From<ImageReference> for OstreeImageReference {
6367
fn from(img: ImageReference) -> Self {
68+
let sigverify = match img.signature {
69+
Some(v) => v.into(),
70+
None => ostree_container::SignatureSource::ContainerPolicyAllowInsecure,
71+
};
6472
Self {
65-
sigverify: img.signature.into(),
73+
sigverify,
6674
imgref: ostree_container::ImageReference {
6775
/// SAFETY: We validated the schema in kube-rs
6876
transport: img.transport.as_str().try_into().unwrap(),
@@ -284,3 +292,27 @@ pub(crate) async fn status(opts: super::cli::StatusOpts) -> Result<()> {
284292

285293
Ok(())
286294
}
295+
296+
#[test]
297+
fn test_convert_signatures() {
298+
use std::str::FromStr;
299+
let ir_unverified = &OstreeImageReference::from_str(
300+
"ostree-unverified-registry:quay.io/someexample/foo:latest",
301+
)
302+
.unwrap();
303+
let ir_ostree = &OstreeImageReference::from_str(
304+
"ostree-remote-registry:fedora:quay.io/fedora/fedora-coreos:stable",
305+
)
306+
.unwrap();
307+
308+
let ir = ImageReference::from(ir_unverified.clone());
309+
assert_eq!(ir.image, "quay.io/someexample/foo:latest");
310+
assert_eq!(ir.signature, None);
311+
312+
let ir = ImageReference::from(ir_ostree.clone());
313+
assert_eq!(ir.image, "quay.io/fedora/fedora-coreos:stable");
314+
assert_eq!(
315+
ir.signature,
316+
Some(ImageSignature::OstreeRemote("fedora".into()))
317+
);
318+
}

0 commit comments

Comments
 (0)