Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ resolver = "2"
anyhow = "1.0"
api_identity = { path = "api_identity" }
assert_matches = "1.5.0"
assert_cmd = "2.0.8"
async-bb8-diesel = { git = "https://github.com/oxidecomputer/async-bb8-diesel", rev = "7944dafc8a36dc6e20a1405eca59d04662de2bb7" }
async-trait = "0.1.60"
authz-macros = { path = "nexus/authz-macros" }
Expand Down Expand Up @@ -205,6 +206,7 @@ serde_urlencoded = "0.7.1"
serde_with = "2.2.0"
serial_test = "0.10"
sha3 = "0.10.6"
shell-words = "1.1.0"
signal-hook = "0.3"
signal-hook-tokio = { version = "0.3", features = [ "futures-v0_3" ] }
sled = "0.34"
Expand Down
4 changes: 4 additions & 0 deletions wicket/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ default-run = "wicket"

[dependencies]
anyhow.workspace = true
camino.workspace = true
clap.workspace = true
crossterm = { version = "0.25.0", features = ["event-stream"] }
futures.workspace = true
Expand All @@ -17,8 +18,10 @@ semver = { version = "1.0.16", features = ["std", "serde"] }
serde.workspace = true
serde_json.workspace = true
sha3.workspace = true
shell-words.workspace = true
slog.workspace = true
slog-async.workspace = true
slog-envlogger.workspace = true
slog-term.workspace = true
snafu.workspace = true
tar.workspace = true
Expand All @@ -29,6 +32,7 @@ tui = "0.19.0"
wicketd-client.workspace = true

[dev-dependencies]
assert_cmd.workspace = true
tempfile.workspace = true

[[bin]]
Expand Down
18 changes: 18 additions & 0 deletions wicket/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,21 @@ functionality implemented. All the inventory and power data shown in the
and RSS. Lastly, we don't have a way to take rack updates and install them, or
initialize the rack (including trust quorum). This is a lot of functionality
that will be implemented incrementally.

# Testing wicket as a login shell

Wicket is meant to be used as a login shell. To test the login shell on a local Unix machine:

1. Make the `wicket` available globally, at e.g. `/usr/local/bin/wicket`:
* If your build directory is globally readable, create a symlink to `wicket` in a well-known location. From omicron's root, run: `sudo ln -s $(readlink -f target/debug/wicket) /usr/local/bin/wicket`
* If it isn't globally accessible, run `sudo cp target/debug/wicket /usr/local/bin`. (You'll have to copy `wicket` each time you build it.)
2. Add a new user to test against, for example `wicket-test`:
1. Add a group for the new user: `groupadd wicket-test`.
2. Add the user: `sudo useradd -m -g wicket-test -s /usr/local/bin/wicket wicket-test`

At this point, you can use `sudo -u wicket-test -i` (Linux) or `pfexec su - wicket-test` (illumos) to test wicket as a login shell.

* A plain `sudo -u wicket-test -i` will show the TUI.
* `sudo -u wicket-test -i upload ...` will let you upload an artifact over stdin.

If you'd like to test connections over ssh, add your ssh key to the new user's `.ssh/authorized_keys`, then run `ssh wicket-test@localhost [upload ...]`.
13 changes: 6 additions & 7 deletions wicket/src/bin/wicket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::error::Error;
use wicket::Wizard;
use anyhow::Result;
use clap::Parser;
use wicket::WicketApp;

fn main() -> Result<(), Box<dyn Error>> {
let mut wizard = Wizard::new();
wizard.run()?;

Ok(())
fn main() -> Result<()> {
let app: WicketApp = Parser::parse();
app.exec()
}
123 changes: 123 additions & 0 deletions wicket/src/dispatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Code that manages command dispatch for wicket.

use std::net::SocketAddrV6;

use anyhow::{bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use clap::Parser;
use slog::Drain;

use crate::{upload::UploadArgs, wizard::Wizard};

#[derive(Debug, Parser)]
#[command(version, author = "Oxide Computer Company")]
pub struct WicketApp {
/// Login shell arguments.
///
/// Wicket is designed to be a login shell for use over ssh. If no arguments are specified,
/// wicket behaves like a TUI. However, if arguments are specified with "-c" (as in other login
/// shells e.g. bash -c), wicketd accepts an upload command.
///
/// Login shell arguments are provided in a quoted form, so we expect a single String here.
/// This string is split using shell quoting logic to get the actual arguments.
#[arg(short = 'c', allow_hyphen_values = true)]
shell_args: Option<String>,
}

#[derive(Debug, Parser)]
enum ShellCommand {
/// Upload an artifact to wicketd.
Upload(UploadArgs),
}

impl WicketApp {
/// Executes the command.
pub fn exec(self) -> Result<()> {
// TODO: make this configurable?
let wicketd_addr: SocketAddrV6 = "[::1]:8000".parse().unwrap();

match self.shell_args {
Some(shell_args) => {
let args =
shell_words::split(&shell_args).with_context(|| {
format!("could not parse shell arguments from input {shell_args}")
})?;
let log = setup_log(&log_path()?, WithStderr::Yes)?;
// parse_from uses the the first argument as the command name. Insert "wicket" as
// the command name.
let args = ShellCommand::parse_from(
std::iter::once("wicket".to_owned()).chain(args),
);
match args {
ShellCommand::Upload(args) => args.exec(log, wicketd_addr),
}
}
None => {
// Do not expose standard error since it'll be on top of the TUI.
let log = setup_log(&log_path()?, WithStderr::No)?;
// Not invoked with "-c" -- run the TUI wizard.
Wizard::new(log, wicketd_addr).run()
}
}
}
}

fn setup_log(
path: &Utf8Path,
with_stderr: WithStderr,
) -> anyhow::Result<slog::Logger> {
let file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.with_context(|| format!("error opening log file {path}"))?;

let decorator = slog_term::PlainDecorator::new(file);
let drain = slog_term::FullFormat::new(decorator).build().fuse();

let drain = match with_stderr {
WithStderr::Yes => {
let stderr_drain = stderr_env_drain("RUST_LOG");
let drain = slog::Duplicate::new(drain, stderr_drain).fuse();
slog_async::Async::new(drain).build().fuse()
}
WithStderr::No => slog_async::Async::new(drain).build().fuse(),
};

Ok(slog::Logger::root(drain, slog::o!()))
}

#[derive(Copy, Clone, Debug)]
enum WithStderr {
Yes,
No,
}

fn log_path() -> Result<Utf8PathBuf> {
match std::env::var("WICKET_LOG_PATH") {
Ok(path) => Ok(path.into()),
Err(std::env::VarError::NotPresent) => Ok("/tmp/wicket.log".into()),
Err(std::env::VarError::NotUnicode(_)) => {
bail!("WICKET_LOG_PATH is not valid unicode");
}
}
}

fn stderr_env_drain(env_var: &str) -> impl Drain<Ok = (), Err = slog::Never> {
let stderr_decorator = slog_term::TermDecorator::new().build();
let stderr_drain =
slog_term::FullFormat::new(stderr_decorator).build().fuse();
let mut builder = slog_envlogger::LogBuilder::new(stderr_drain);
if let Ok(s) = std::env::var(env_var) {
builder = builder.parse(&s);
} else {
// Log at the info level by default.
builder = builder.filter(None, slog::FilterLevel::Info);
}
builder.build()
}
3 changes: 3 additions & 0 deletions wicket/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
//! in an intuitive manner.

pub(crate) mod defaults;
mod dispatch;
pub(crate) mod inventory;
mod screens;
pub mod update;
mod upload;
mod wicketd;
mod widgets;
mod wizard;

pub use crate::dispatch::*;
pub use crate::wizard::*;
Loading