mirror of
https://github.com/containers/bootc.git
synced 2026-02-05 15:45:53 +01:00
Drop systemd service
Fixes https://github.com/coreos/bootupd/issues/551 Get hints by https://github.com/coreos/bootupd/issues/551#issuecomment-2124477922, and copy the comment here: Basically we detect if we're running in systemd; if we're not, we re-exec ourselves via systemd-run. Then we can just directly run code in what is now the daemon. I think an important aspect of this is that we retain something like `--unit bootupd` which acts as a lock - only one unit with that name can run at a time to avoid two concurrent invocations breaking things.
This commit is contained in:
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -53,6 +53,7 @@ jobs:
|
||||
device=$(losetup --list --noheadings --output NAME,BACK-FILE | grep myimage.raw | awk '{print $1}')
|
||||
sudo mount "${device}p2" /mnt/
|
||||
sudo ls /mnt/EFI/centos/{grub.cfg,shimx64.efi}
|
||||
sudo umount /mnt
|
||||
sudo losetup -D "${device}"
|
||||
sudo rm -f myimage.raw
|
||||
- name: bootc install to filesystem
|
||||
|
||||
12
Makefile
12
Makefile
@@ -18,10 +18,8 @@ ifeq ($(CONTAINER_RUNTIME), podman)
|
||||
IMAGE_PREFIX = localhost/
|
||||
endif
|
||||
|
||||
units = $(addprefix systemd/, bootupd.service bootupd.socket)
|
||||
|
||||
.PHONY: all
|
||||
all: $(units)
|
||||
all:
|
||||
cargo build ${CARGO_ARGS}
|
||||
ln -f target/${PROFILE}/bootupd target/${PROFILE}/bootupctl
|
||||
|
||||
@@ -33,17 +31,11 @@ create-build-container:
|
||||
build-in-container: create-build-container
|
||||
${CONTAINER_RUNTIME} run -ti --rm -v .:/srv/bootupd:z ${IMAGE_PREFIX}${IMAGE_NAME} make
|
||||
|
||||
.PHONY: install-units
|
||||
install-units: $(units)
|
||||
for unit in $(units); do install -D -m 644 --target-directory=$(DESTDIR)$(PREFIX)/lib/systemd/system/ $$unit; done
|
||||
|
||||
.PHONY: install
|
||||
install: install-units
|
||||
install:
|
||||
mkdir -p "${DESTDIR}$(PREFIX)/bin" "${DESTDIR}$(LIBEXECDIR)"
|
||||
install -D -t "${DESTDIR}$(LIBEXECDIR)" target/${PROFILE}/bootupd
|
||||
ln -f ${DESTDIR}$(LIBEXECDIR)/bootupd ${DESTDIR}$(PREFIX)/bin/bootupctl
|
||||
install -d "${DESTDIR}$(PREFIX)/lib/systemd/system/multi-user.target.wants"
|
||||
ln -s ../bootupd.socket "${DESTDIR}$(PREFIX)/lib/systemd/system/multi-user.target.wants"
|
||||
|
||||
install-grub-static:
|
||||
install -m 644 -D -t ${DESTDIR}$(PREFIX)/lib/bootupd/grub2-static src/grub2/*.cfg
|
||||
|
||||
@@ -52,15 +52,6 @@ cargo build --release
|
||||
%make_install INSTALL="install -p -c"
|
||||
make install-grub-static DESTDIR=%{?buildroot} INSTALL="%{__install} -p"
|
||||
|
||||
%post -n %{crate}
|
||||
%systemd_post bootupd.service bootupd.socket
|
||||
|
||||
%preun -n %{crate}
|
||||
%systemd_preun bootupd.service bootupd.socket
|
||||
|
||||
%postun -n %{crate}
|
||||
%systemd_postun bootupd.service bootupd.socket
|
||||
|
||||
%changelog
|
||||
* Tue Oct 18 2022 Colin Walters <walters@verbum.org> - 0.2.8-3
|
||||
- Dummy changelog
|
||||
@@ -1,31 +1,18 @@
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "powerpc64"))]
|
||||
use crate::bios;
|
||||
use crate::component;
|
||||
use crate::component::{Component, ValidationResult};
|
||||
use crate::coreos;
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
use crate::efi;
|
||||
use crate::model::{ComponentStatus, ComponentUpdatable, ContentMetadata, SavedState, Status};
|
||||
use crate::util;
|
||||
use crate::{component, ipc};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
/// A message sent from client to server
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) enum ClientRequest {
|
||||
/// Update a component
|
||||
Update { component: String },
|
||||
/// Update a component via adoption
|
||||
AdoptAndUpdate { component: String },
|
||||
/// Validate a component
|
||||
Validate { component: String },
|
||||
/// Print the current state
|
||||
Status,
|
||||
}
|
||||
|
||||
pub(crate) enum ConfigMode {
|
||||
None,
|
||||
Static,
|
||||
@@ -408,8 +395,8 @@ pub(crate) fn print_status(status: &Status) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn client_run_update(c: &mut ipc::ClientToDaemonConnection) -> Result<()> {
|
||||
let status: Status = c.send(&ClientRequest::Status)?;
|
||||
pub(crate) fn client_run_update() -> Result<()> {
|
||||
let status: Status = status()?;
|
||||
if status.components.is_empty() && status.adoptable.is_empty() {
|
||||
println!("No components installed.");
|
||||
return Ok(());
|
||||
@@ -420,9 +407,7 @@ pub(crate) fn client_run_update(c: &mut ipc::ClientToDaemonConnection) -> Result
|
||||
ComponentUpdatable::Upgradable => {}
|
||||
_ => continue,
|
||||
};
|
||||
match c.send(&ClientRequest::Update {
|
||||
component: name.to_string(),
|
||||
})? {
|
||||
match update(name)? {
|
||||
ComponentUpdateResult::AtLatestVersion => {
|
||||
// Shouldn't happen unless we raced with another client
|
||||
eprintln!(
|
||||
@@ -450,9 +435,7 @@ pub(crate) fn client_run_update(c: &mut ipc::ClientToDaemonConnection) -> Result
|
||||
}
|
||||
for (name, adoptable) in status.adoptable.iter() {
|
||||
if adoptable.confident {
|
||||
let r: ContentMetadata = c.send(&ClientRequest::AdoptAndUpdate {
|
||||
component: name.to_string(),
|
||||
})?;
|
||||
let r: ContentMetadata = adopt_and_update(name)?;
|
||||
println!("Adopted and updated: {}: {}", name, r.version);
|
||||
updated = true;
|
||||
} else {
|
||||
@@ -465,32 +448,28 @@ pub(crate) fn client_run_update(c: &mut ipc::ClientToDaemonConnection) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn client_run_adopt_and_update(c: &mut ipc::ClientToDaemonConnection) -> Result<()> {
|
||||
let status: Status = c.send(&ClientRequest::Status)?;
|
||||
pub(crate) fn client_run_adopt_and_update() -> Result<()> {
|
||||
let status: Status = status()?;
|
||||
if status.adoptable.is_empty() {
|
||||
println!("No components are adoptable.");
|
||||
} else {
|
||||
for (name, _) in status.adoptable.iter() {
|
||||
let r: ContentMetadata = c.send(&ClientRequest::AdoptAndUpdate {
|
||||
component: name.to_string(),
|
||||
})?;
|
||||
let r: ContentMetadata = adopt_and_update(name)?;
|
||||
println!("Adopted and updated: {}: {}", name, r.version);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn client_run_validate(c: &mut ipc::ClientToDaemonConnection) -> Result<()> {
|
||||
let status: Status = c.send(&ClientRequest::Status)?;
|
||||
pub(crate) fn client_run_validate() -> Result<()> {
|
||||
let status: Status = status()?;
|
||||
if status.components.is_empty() {
|
||||
println!("No components installed.");
|
||||
return Ok(());
|
||||
}
|
||||
let mut caught_validation_error = false;
|
||||
for (name, _) in status.components.iter() {
|
||||
match c.send(&ClientRequest::Validate {
|
||||
component: name.to_string(),
|
||||
})? {
|
||||
match validate(name)? {
|
||||
ValidationResult::Valid => {
|
||||
println!("Validated: {}", name);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
use crate::bootupd;
|
||||
use crate::ipc::ClientToDaemonConnection;
|
||||
use crate::model::Status;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use log::LevelFilter;
|
||||
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
static SYSTEMD_ARGS_BOOTUPD: &[&str] = &[
|
||||
"--unit",
|
||||
"bootupd",
|
||||
"--property",
|
||||
"PrivateNetwork=yes",
|
||||
"--property",
|
||||
"ProtectHome=yes",
|
||||
"--property",
|
||||
"MountFlags=slave",
|
||||
"--pipe",
|
||||
];
|
||||
|
||||
/// `bootupctl` sub-commands.
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(name = "bootupctl", about = "Bootupd client application", version)]
|
||||
@@ -87,10 +100,8 @@ impl CtlCommand {
|
||||
|
||||
/// Runner for `status` verb.
|
||||
fn run_status(opts: StatusOpts) -> Result<()> {
|
||||
let mut client = ClientToDaemonConnection::new();
|
||||
client.connect()?;
|
||||
|
||||
let r: Status = client.send(&bootupd::ClientRequest::Status)?;
|
||||
ensure_running_in_systemd()?;
|
||||
let r = bootupd::status()?;
|
||||
if opts.json {
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
@@ -101,38 +112,54 @@ impl CtlCommand {
|
||||
bootupd::print_status(&r)?;
|
||||
}
|
||||
|
||||
client.shutdown()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runner for `update` verb.
|
||||
fn run_update() -> Result<()> {
|
||||
let mut client = ClientToDaemonConnection::new();
|
||||
client.connect()?;
|
||||
|
||||
bootupd::client_run_update(&mut client)?;
|
||||
|
||||
client.shutdown()?;
|
||||
Ok(())
|
||||
ensure_running_in_systemd()?;
|
||||
bootupd::client_run_update()
|
||||
}
|
||||
|
||||
/// Runner for `update` verb.
|
||||
fn run_adopt_and_update() -> Result<()> {
|
||||
let mut client = ClientToDaemonConnection::new();
|
||||
client.connect()?;
|
||||
|
||||
bootupd::client_run_adopt_and_update(&mut client)?;
|
||||
|
||||
client.shutdown()?;
|
||||
Ok(())
|
||||
ensure_running_in_systemd()?;
|
||||
bootupd::client_run_adopt_and_update()
|
||||
}
|
||||
|
||||
/// Runner for `validate` verb.
|
||||
fn run_validate() -> Result<()> {
|
||||
let mut client = ClientToDaemonConnection::new();
|
||||
client.connect()?;
|
||||
bootupd::client_run_validate(&mut client)?;
|
||||
client.shutdown()?;
|
||||
Ok(())
|
||||
ensure_running_in_systemd()?;
|
||||
bootupd::client_run_validate()
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the current process is (apparently at least)
|
||||
/// running under systemd.
|
||||
fn running_in_systemd() -> bool {
|
||||
std::env::var_os("INVOCATION_ID").is_some()
|
||||
}
|
||||
|
||||
/// Require root permission
|
||||
fn require_root_permission() -> Result<()> {
|
||||
if !nix::unistd::Uid::effective().is_root() {
|
||||
anyhow::bail!("This command requires root privileges")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Detect if we're running in systemd; if we're not, we re-exec ourselves via
|
||||
/// systemd-run. Then we can just directly run code in what is now the daemon.
|
||||
fn ensure_running_in_systemd() -> Result<()> {
|
||||
require_root_permission()?;
|
||||
let running_in_systemd = running_in_systemd();
|
||||
if !running_in_systemd {
|
||||
let r = Command::new("systemd-run")
|
||||
.args(SYSTEMD_ARGS_BOOTUPD)
|
||||
.args(std::env::args())
|
||||
.exec();
|
||||
// If we got here, it's always an error
|
||||
return Err(r.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@ impl DCommand {
|
||||
/// CLI sub-commands.
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum DVerb {
|
||||
#[clap(name = "daemon", about = "Run service logic")]
|
||||
Daemon,
|
||||
#[clap(name = "generate-update-metadata", about = "Generate metadata")]
|
||||
GenerateUpdateMetadata(GenerateOpts),
|
||||
#[clap(name = "install", about = "Install components")]
|
||||
@@ -88,7 +86,6 @@ impl DCommand {
|
||||
/// Run CLI application.
|
||||
pub fn run(self) -> Result<()> {
|
||||
match self.cmd {
|
||||
DVerb::Daemon => crate::daemon::run(),
|
||||
DVerb::Install(opts) => Self::run_install(opts),
|
||||
DVerb::GenerateUpdateMetadata(opts) => Self::run_generate_meta(opts),
|
||||
}
|
||||
|
||||
@@ -62,7 +62,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_multicall_dispatch() {
|
||||
{
|
||||
let d_argv = vec!["/usr/bin/bootupd".to_string(), "daemon".to_string()];
|
||||
let d_argv = vec![
|
||||
"/usr/bin/bootupd".to_string(),
|
||||
"generate-update-metadata".to_string(),
|
||||
];
|
||||
let cli = MultiCall::from_args(d_argv);
|
||||
match cli {
|
||||
MultiCall::Ctl(cmd) => panic!("{:?}", cmd),
|
||||
@@ -89,12 +92,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_verbosity() {
|
||||
let default = MultiCall::from_args(vec!["bootupd".to_string(), "daemon".to_string()]);
|
||||
let default = MultiCall::from_args(vec![
|
||||
"bootupd".to_string(),
|
||||
"generate-update-metadata".to_string(),
|
||||
]);
|
||||
assert_eq!(default.loglevel(), LevelFilter::Warn);
|
||||
|
||||
let info = MultiCall::from_args(vec![
|
||||
"bootupd".to_string(),
|
||||
"daemon".to_string(),
|
||||
"generate-update-metadata".to_string(),
|
||||
"-v".to_string(),
|
||||
]);
|
||||
assert_eq!(info.loglevel(), LevelFilter::Info);
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
//! Daemon logic.
|
||||
|
||||
use crate::component::ValidationResult;
|
||||
use crate::model::Status;
|
||||
use crate::{bootupd, ipc};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use nix::sys::socket as nixsocket;
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
/// Accept a single client and then exit; we don't want to
|
||||
/// persistently run as a daemon. The systemd unit is mostly
|
||||
/// and implementation detail - it lets us use things like
|
||||
/// systemd's built in sandboxing (ProtectHome=yes) etc. and also
|
||||
/// ensures that only a single bootupd instance is running at
|
||||
/// a time (i.e. don't support concurrent updates).
|
||||
pub fn run() -> Result<()> {
|
||||
let srvsock_fd = systemd_activation().context("systemd service activation error")?;
|
||||
|
||||
// Accept an incoming client.
|
||||
let client = match accept_authenticate_client(srvsock_fd) {
|
||||
Ok(auth_client) => auth_client,
|
||||
Err(e) => {
|
||||
log::error!("failed to authenticate client: {}", e);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// Process all requests from this client.
|
||||
if let Err(e) = process_client_requests(client) {
|
||||
log::error!("failed to process request from client: {}", e);
|
||||
}
|
||||
|
||||
// Sleep for a half second to avoid triggering systemd service
|
||||
// restart limits.
|
||||
std::thread::sleep(std::time::Duration::from_secs_f32(0.5));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform initialization steps required by systemd service activation.
|
||||
///
|
||||
/// This ensures that the system is running under systemd, then receives the
|
||||
/// socket-FD for main IPC logic, and notifies systemd about ready-state.
|
||||
fn systemd_activation() -> Result<RawFd> {
|
||||
use libsystemd::daemon::{self, NotifyState};
|
||||
use std::os::unix::io::IntoRawFd;
|
||||
|
||||
if !daemon::booted() {
|
||||
bail!("daemon is not running as a systemd service");
|
||||
}
|
||||
|
||||
let srvsock_fd = {
|
||||
let mut fds = libsystemd::activation::receive_descriptors(true)
|
||||
.map_err(|e| anyhow::anyhow!("failed to receive file-descriptors: {}", e))?;
|
||||
let srvsock_fd = if let Some(fd) = fds.pop() {
|
||||
fd
|
||||
} else {
|
||||
bail!("no socket-fd received on service activation");
|
||||
};
|
||||
srvsock_fd.into_raw_fd()
|
||||
};
|
||||
|
||||
let sent = daemon::notify(true, &[NotifyState::Ready])
|
||||
.map_err(|e| anyhow::anyhow!("failed to notify ready-state: {}", e))?;
|
||||
if !sent {
|
||||
log::warn!("failed to notify ready-state: service notifications not supported");
|
||||
}
|
||||
|
||||
Ok(srvsock_fd)
|
||||
}
|
||||
|
||||
/// Accept an incoming connection, then authenticate the client.
|
||||
fn accept_authenticate_client(srvsock_fd: RawFd) -> Result<ipc::AuthenticatedClient> {
|
||||
let accepted = nixsocket::accept4(srvsock_fd, nixsocket::SockFlag::SOCK_CLOEXEC)?;
|
||||
let client = ipc::UnauthenticatedClient::new(accepted);
|
||||
|
||||
let authed = client.authenticate()?;
|
||||
|
||||
Ok(authed)
|
||||
}
|
||||
|
||||
/// Process all requests from a given client.
|
||||
///
|
||||
/// This sequentially processes all requests from a client, until it
|
||||
/// disconnects or a connection error is encountered.
|
||||
fn process_client_requests(client: ipc::AuthenticatedClient) -> Result<()> {
|
||||
use crate::bootupd::ClientRequest;
|
||||
|
||||
let mut buf = [0u8; ipc::MSGSIZE];
|
||||
loop {
|
||||
let n = nixsocket::recv(client.fd, &mut buf, nixsocket::MsgFlags::MSG_CMSG_CLOEXEC)?;
|
||||
let buf = &buf[0..n];
|
||||
if buf.is_empty() {
|
||||
log::trace!("client disconnected");
|
||||
break;
|
||||
}
|
||||
|
||||
let msg = bincode::deserialize(buf)?;
|
||||
log::trace!("processing request: {:?}", &msg);
|
||||
let r = match msg {
|
||||
ClientRequest::Update { component } => {
|
||||
bincode::serialize(&match bootupd::update(component.as_str()) {
|
||||
Ok(v) => ipc::DaemonToClientReply::Success::<bootupd::ComponentUpdateResult>(v),
|
||||
Err(e) => ipc::DaemonToClientReply::Failure(format!("{:#}", e)),
|
||||
})?
|
||||
}
|
||||
ClientRequest::AdoptAndUpdate { component } => {
|
||||
bincode::serialize(&match bootupd::adopt_and_update(component.as_str()) {
|
||||
Ok(v) => ipc::DaemonToClientReply::Success::<crate::model::ContentMetadata>(v),
|
||||
Err(e) => ipc::DaemonToClientReply::Failure(format!("{:#}", e)),
|
||||
})?
|
||||
}
|
||||
ClientRequest::Validate { component } => {
|
||||
bincode::serialize(&match bootupd::validate(component.as_str()) {
|
||||
Ok(v) => ipc::DaemonToClientReply::Success::<ValidationResult>(v),
|
||||
Err(e) => ipc::DaemonToClientReply::Failure(format!("{:#}", e)),
|
||||
})?
|
||||
}
|
||||
ClientRequest::Status => bincode::serialize(&match bootupd::status() {
|
||||
Ok(v) => ipc::DaemonToClientReply::Success::<Status>(v),
|
||||
Err(e) => ipc::DaemonToClientReply::Failure(format!("{:#}", e)),
|
||||
})?,
|
||||
};
|
||||
let written = nixsocket::send(client.fd, &r, nixsocket::MsgFlags::MSG_CMSG_CLOEXEC)?;
|
||||
if written != r.len() {
|
||||
bail!("wrote {} bytes to client, expected {}", written, r.len());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
169
src/ipc.rs
169
src/ipc.rs
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Red Hat, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use fn_error_context::context;
|
||||
use nix::sys::socket as nixsocket;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
pub(crate) const BOOTUPD_SOCKET: &str = "/run/bootupd.sock";
|
||||
pub(crate) const MSGSIZE: usize = 1_048_576;
|
||||
/// Sent between processes along with SCM credentials
|
||||
pub(crate) const BOOTUPD_HELLO_MSG: &str = "bootupd-hello\n";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) enum DaemonToClientReply<T> {
|
||||
Success(T),
|
||||
Failure(String),
|
||||
}
|
||||
|
||||
pub(crate) struct ClientToDaemonConnection {
|
||||
fd: i32,
|
||||
}
|
||||
|
||||
impl Drop for ClientToDaemonConnection {
|
||||
fn drop(&mut self) {
|
||||
if self.fd != -1 {
|
||||
nix::unistd::close(self.fd).expect("close");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientToDaemonConnection {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { fd: -1 }
|
||||
}
|
||||
|
||||
#[context("connecting to {}", BOOTUPD_SOCKET)]
|
||||
pub(crate) fn connect(&mut self) -> Result<()> {
|
||||
use nix::sys::uio::IoVec;
|
||||
self.fd = nixsocket::socket(
|
||||
nixsocket::AddressFamily::Unix,
|
||||
nixsocket::SockType::SeqPacket,
|
||||
nixsocket::SockFlag::SOCK_CLOEXEC,
|
||||
None,
|
||||
)?;
|
||||
let addr = nixsocket::SockAddr::new_unix(BOOTUPD_SOCKET)?;
|
||||
nixsocket::connect(self.fd, &addr)?;
|
||||
let creds = libc::ucred {
|
||||
pid: nix::unistd::getpid().as_raw(),
|
||||
uid: nix::unistd::getuid().as_raw(),
|
||||
gid: nix::unistd::getgid().as_raw(),
|
||||
};
|
||||
let creds = nixsocket::UnixCredentials::from(creds);
|
||||
let creds = nixsocket::ControlMessage::ScmCredentials(&creds);
|
||||
let _ = nixsocket::sendmsg(
|
||||
self.fd,
|
||||
&[IoVec::from_slice(BOOTUPD_HELLO_MSG.as_bytes())],
|
||||
&[creds],
|
||||
nixsocket::MsgFlags::MSG_CMSG_CLOEXEC,
|
||||
None,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn send<S: serde::ser::Serialize, T: serde::de::DeserializeOwned>(
|
||||
&mut self,
|
||||
msg: &S,
|
||||
) -> Result<T> {
|
||||
{
|
||||
let serialized = bincode::serialize(msg)?;
|
||||
let _ = nixsocket::send(self.fd, &serialized, nixsocket::MsgFlags::MSG_CMSG_CLOEXEC)
|
||||
.context("client sending request")?;
|
||||
}
|
||||
let reply: DaemonToClientReply<T> = {
|
||||
let mut buf = [0u8; MSGSIZE];
|
||||
let n = nixsocket::recv(self.fd, &mut buf, nixsocket::MsgFlags::MSG_CMSG_CLOEXEC)
|
||||
.context("client recv")?;
|
||||
let buf = &buf[0..n];
|
||||
if buf.is_empty() {
|
||||
bail!("Server sent an empty reply");
|
||||
}
|
||||
bincode::deserialize(buf).context("client parsing reply")?
|
||||
};
|
||||
match reply {
|
||||
DaemonToClientReply::Success::<T>(r) => Ok(r),
|
||||
DaemonToClientReply::Failure(buf) => {
|
||||
// For now we just prefix server
|
||||
anyhow::bail!("internal error: {}", buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(&mut self) -> Result<()> {
|
||||
nixsocket::shutdown(self.fd, nixsocket::Shutdown::Both)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UnauthenticatedClient {
|
||||
fd: RawFd,
|
||||
}
|
||||
|
||||
impl UnauthenticatedClient {
|
||||
pub(crate) fn new(fd: RawFd) -> Self {
|
||||
Self { fd }
|
||||
}
|
||||
|
||||
pub(crate) fn authenticate(mut self) -> Result<AuthenticatedClient> {
|
||||
use nix::sys::uio::IoVec;
|
||||
let fd = self.fd;
|
||||
let mut buf = [0u8; 1024];
|
||||
|
||||
nixsocket::setsockopt(fd, nix::sys::socket::sockopt::PassCred, &true)?;
|
||||
let iov = IoVec::from_mut_slice(buf.as_mut());
|
||||
let mut cmsgspace = nix::cmsg_space!(nixsocket::UnixCredentials);
|
||||
let msg = nixsocket::recvmsg(
|
||||
fd,
|
||||
&[iov],
|
||||
Some(&mut cmsgspace),
|
||||
nixsocket::MsgFlags::MSG_CMSG_CLOEXEC,
|
||||
)?;
|
||||
let mut creds = None;
|
||||
for cmsg in msg.cmsgs() {
|
||||
if let nixsocket::ControlMessageOwned::ScmCredentials(c) = cmsg {
|
||||
creds = Some(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(creds) = creds {
|
||||
if creds.uid() != 0 {
|
||||
bail!("unauthorized pid:{} uid:{}", creds.pid(), creds.uid())
|
||||
}
|
||||
println!("Connection from pid:{}", creds.pid());
|
||||
} else {
|
||||
bail!("No SCM credentials provided");
|
||||
}
|
||||
let hello = String::from_utf8_lossy(&buf[0..msg.bytes]);
|
||||
if hello != BOOTUPD_HELLO_MSG {
|
||||
bail!("Didn't receive correct hello message, found: {:?}", &hello);
|
||||
}
|
||||
let r = AuthenticatedClient { fd: self.fd };
|
||||
self.fd = -1;
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UnauthenticatedClient {
|
||||
fn drop(&mut self) {
|
||||
if self.fd != -1 {
|
||||
nix::unistd::close(self.fd).expect("close");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AuthenticatedClient {
|
||||
pub(crate) fd: RawFd,
|
||||
}
|
||||
|
||||
impl Drop for AuthenticatedClient {
|
||||
fn drop(&mut self) {
|
||||
if self.fd != -1 {
|
||||
nix::unistd::close(self.fd).expect("close");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ mod bootupd;
|
||||
mod cli;
|
||||
mod component;
|
||||
mod coreos;
|
||||
mod daemon;
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
mod efi;
|
||||
mod filesystem;
|
||||
@@ -33,7 +32,6 @@ mod filetree;
|
||||
target_arch = "powerpc64"
|
||||
))]
|
||||
mod grubconfigs;
|
||||
mod ipc;
|
||||
mod model;
|
||||
mod model_legacy;
|
||||
mod ostreeutil;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
[Unit]
|
||||
Description=bootloader update daemon
|
||||
Documentation=https://github.com/coreos/bootupd
|
||||
# Because the daemon currently agressively auto-exits
|
||||
# and our test suite runs many requests, let's allow
|
||||
# a lot of restarts before failing.
|
||||
StartLimitIntervalSec=2s
|
||||
StartLimitBurst=10
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
Environment=BOOTUPD_VERBOSITY="-v"
|
||||
ExecStart=/usr/libexec/bootupd daemon $BOOTUPD_VERBOSITY
|
||||
# This way our working directory isn't writable by default.
|
||||
WorkingDirectory=/usr
|
||||
# Various hardening flags just on general principle. We need
|
||||
# to run as root, but let's avoid accidental damage.
|
||||
ProtectHome=yes
|
||||
ReadOnlyPaths=/usr
|
||||
PrivateTmp=yes
|
||||
PrivateNetwork=yes
|
||||
ProtectHostname=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
# So we can remount /boot writable
|
||||
MountFlags=slave
|
||||
@@ -1,6 +0,0 @@
|
||||
[Socket]
|
||||
ListenSequentialPacket=/run/bootupd.sock
|
||||
SocketMode=0600
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@@ -16,7 +16,8 @@ if test -z "${COSA_DIR:-}"; then
|
||||
fi
|
||||
# Validate source directory
|
||||
bootupd_git=$(cd ${dn} && git rev-parse --show-toplevel)
|
||||
test -f ${bootupd_git}/systemd/bootupd.service
|
||||
# https://github.com/coreos/bootupd/issues/551
|
||||
! test -f ${bootupd_git}/systemd/bootupd.service
|
||||
|
||||
testtmp=$(mktemp -d -p /var/tmp bootupd-e2e.XXXXXXX)
|
||||
export test_tmpdir=${testtmp}
|
||||
|
||||
@@ -47,18 +47,13 @@ assert_file_has_content_literal out.txt 'Update: At latest version'
|
||||
assert_file_has_content out.txt '^CoreOS aleph version:'
|
||||
ok status
|
||||
|
||||
# Validate we auto-exited
|
||||
sleep 2
|
||||
systemctl show -p ActiveState bootupd > out.txt
|
||||
assert_file_has_content_literal out.txt 'ActiveState=inactive'
|
||||
|
||||
bootupctl validate | tee out.txt
|
||||
ok validate
|
||||
|
||||
if env LANG=C.UTF-8 runuser -u bin bootupctl status 2>err.txt; then
|
||||
fatal "Was able to bootupctl status as non-root"
|
||||
fi
|
||||
assert_file_has_content err.txt 'error:.*: Permission denied'
|
||||
assert_file_has_content err.txt 'error: This command requires root privileges'
|
||||
|
||||
# From here we'll fake updates
|
||||
test -w /usr || rpm-ostree usroverlay
|
||||
|
||||
Reference in New Issue
Block a user