Skip to content

Commit 48065a2

Browse files
committed
azure-init: Accept providing user groups via the CLI
Debian uses "sudo" as the group for having do-anything sudo permissions, where-as Fedora uses "wheel". Otherwise the same binary works fine for both. I don't see an advantage to baking the groups into the binary, so this is a take on runtime configuration. Accept a list of supplementary groups to use when provisioning the user so the same binary can be used for both. Values can be provided using the "-g" or "--groups" argument, or by setting the "AZURE_INIT_USER_GROUPS" environment variable. If no groups are provided, the default remains "wheel". I found this helpful when testing Azure#105. We could expand this to allow more runtime tweaks to, for example, the backend in use if folks like this.
1 parent 3ac3f41 commit 48065a2

File tree

3 files changed

+59
-1
lines changed

3 files changed

+59
-1
lines changed

Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ anyhow = "1.0.81"
1414
tokio = { version = "1", features = ["full"] }
1515
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
1616
tracing = "0.1.40"
17+
# We work fine with any version of 4, but 4.5 bumped MSRV to 1.74
18+
clap = { version = "<=4.4", features = ["derive", "cargo", "env"] }
19+
20+
[dev-dependencies]
21+
# Purely for the MSRV requirement.
22+
assert_cmd = "<=2.0.13"
23+
predicates = "3"
1724

1825
[dependencies.libazureinit]
1926
path = "libazureinit"

src/main.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use std::process::ExitCode;
55

66
use anyhow::Context;
7+
use clap::Parser;
78
use libazureinit::imds::InstanceMetadata;
89
use libazureinit::User;
910
use libazureinit::{
@@ -20,6 +21,24 @@ use tracing_subscriber::EnvFilter;
2021

2122
const VERSION: &str = env!("CARGO_PKG_VERSION");
2223

24+
/// Minimal provisioning agent for Azure
25+
///
26+
/// Create a user, add SSH public keys, and set the hostname.
27+
#[derive(Parser, Debug)]
28+
struct Cli {
29+
/// List of supplementary groups of the provisioned user account.
30+
///
31+
/// Values can be comma-separated and the argument can be provided multiple times.
32+
#[arg(
33+
long,
34+
short,
35+
env = "AZURE_INIT_USER_GROUPS",
36+
value_delimiter = ',',
37+
default_value = "wheel"
38+
)]
39+
groups: Vec<String>,
40+
}
41+
2342
#[instrument]
2443
fn get_environment() -> Result<Environment, anyhow::Error> {
2544
let ovf_devices = get_mount_device(None)?;
@@ -96,6 +115,8 @@ async fn main() -> ExitCode {
96115

97116
#[instrument]
98117
async fn provision() -> Result<(), anyhow::Error> {
118+
let opts = Cli::parse();
119+
99120
let mut default_headers = header::HeaderMap::new();
100121
let user_agent = header::HeaderValue::from_str(
101122
format!("azure-init v{VERSION}").as_str(),
@@ -124,7 +145,8 @@ async fn provision() -> Result<(), anyhow::Error> {
124145
.clone()
125146
.ok_or::<LibError>(LibError::InstanceMetadataFailure)?;
126147

127-
let user = User::new(username, im.compute.public_keys);
148+
let user =
149+
User::new(username, im.compute.public_keys).with_groups(opts.groups);
128150

129151
Provision::new(im.compute.os_profile.computer_name, user)
130152
.hostname_provisioners([

tests/cli.rs

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use std::process::Command;
2+
3+
use assert_cmd::prelude::*;
4+
use predicates::prelude::*;
5+
6+
// Assert help text includes the --groups flag
7+
#[test]
8+
fn help_groups() -> Result<(), Box<dyn std::error::Error>> {
9+
let mut command = Command::cargo_bin("azure-init")?;
10+
command.arg("--help");
11+
command
12+
.assert()
13+
.success()
14+
.stdout(predicate::str::contains("-g, --groups <GROUPS>"));
15+
16+
Ok(())
17+
}
18+
19+
// Assert we can't run without elevated privileges to create users.
20+
#[test]
21+
fn permission_denied() -> Result<(), Box<dyn std::error::Error>> {
22+
let mut command = Command::cargo_bin("azure-init")?;
23+
command
24+
.assert()
25+
.failure()
26+
.stderr(predicate::str::contains("Permission denied"));
27+
28+
Ok(())
29+
}

0 commit comments

Comments
 (0)