Skip to content

Commit

Permalink
src/commands/exec: Add --as option to set user to ssh as, and default…
Browse files Browse the repository at this point in the history
… to root

Closes #10

Signed-off-by: Alberto Faria <[email protected]>
  • Loading branch information
albertofaria committed Mar 21, 2024
1 parent 066501d commit 1c173e4
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 65 deletions.
48 changes: 32 additions & 16 deletions docs/2-podman-docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ if the VM uses Ignition.
## SSH'ing into the VM

Assuming the VM supports cloud-init or Ignition and exposes an SSH server on
port 22, you can `ssh` into it using podman-exec as the VMs default user:
port 22, you can `ssh` into it as root using podman-exec:

> For this command to work with Docker, you must replace the `--latest` flag
> with the container's name or ID.
Expand All @@ -172,27 +172,43 @@ $ podman run \
""
8068a2c180e0f4bf494f5e0baa37d9f13a9810f76b361c0771b73666e47ec383

$ podman exec --latest fedora whoami
$ podman exec --latest whoami
Please login as the user "fedora" rather than the user "root".
```

This particular VM image does not allow logging in as root. To `ssh` into the VM
as a different user, specify its username using the `--as` option immediately
before the command (if any). You may need to pass in `--` before this option to
prevent podman-exec from trying to interpret it:

```console
$ podman exec --latest -- --as fedora whoami
fedora
```

$ podman exec -it --latest fedora
[fedora@8068a2c180e0 ~]$
If you just want a login shell, pass in an empty string as the command. The
following would be the output if this VM image allowed logging in as root:

```
$ podman exec -it --latest ""
[root@8068a2c180e0 ~]$
```

With cloud-init, the default user can vary between VM images. With Ignition,
`core` is considered to be the default user. In both cases, if the SSH server
allows password authentication, you should also be able to log in as any other
user.
You can also log in as a specific user:

```
$ podman exec -it --latest -- --as fedora
[fedora@8068a2c180e0 ~]$
```

The `fedora` argument to podman-exec above, which would typically correspond to
the command to be executed, determines instead the name of the user to `ssh`
into the VM as. A command can optionally be specified with further arguments. If
no command is specified, a login shell is initiated. In this case, you probably
also want to pass flags `-it` to podman-exec.
When the VM supports cloud-init, `authorized_keys` is automatically set up to
allow SSH access by podman-exec for users `root` and the default user as set in
the image's cloud-init configuration. With Ignition, this is set up for users
`root` and `core`.

If you actually just want to exec into the container in which the VM is running
(probably to debug some problem with crun-vm itself), pass in `-` as the
username.
> If you want to exec into the container in which the VM is running (probably to
> debug some problem with crun-vm itself), pass in the `--container` flag
> immediately before the command (if any).
## Port forwarding

Expand Down
8 changes: 3 additions & 5 deletions docs/3-kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,13 @@ $ kubectl logs my-vm
### SSH'ing into the pod/VM

Assuming the VM supports cloud-init or Ignition, you can also SSH into it using
`kubectl exec`, with the caveat that the user to SSH as is passed in place of
the command (this is the same behavior as with `podman exec` or `docker exec`;
see [SSH'ing into the VM]):
`kubectl exec`:

```console
$ kubectl exec my-vm -- fedora whoami
$ kubectl exec my-vm -- --as fedora whoami
fedora
$ kubectl exec -it my-vm -- fedora
$ kubectl exec -it my-vm -- --as fedora bash
[fedora@my-vm ~]$
```

Expand Down
68 changes: 40 additions & 28 deletions src/commands/create/first_boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ pub struct FirstBootConfig<'a> {
}

impl FirstBootConfig<'_> {
/// Returns `true` if a cloud-init config should be passed to the VM.
pub fn apply_to_cloud_init_config(
&self,
in_config_dir_path: Option<impl AsRef<Utf8Path>>,
Expand Down Expand Up @@ -132,11 +131,38 @@ impl FirstBootConfig<'_> {

ssh_authorized_keys.push(self.container_public_key.into());

let write_files = match user_data_mapping
.entry("write_files".into())
.or_insert_with(|| serde_yaml::Value::Sequence(vec![]))
{
serde_yaml::Value::Sequence(v) => v,
_ => bail!("invalid user-data file"),
};

write_files.push({
let mut m = serde_yaml::Mapping::new();
m.insert("path".into(), "/root/.ssh/authorized_keys".into());
m.insert(
"content".into(),
("\n".to_string() + self.container_public_key).into(),
);
m.insert("append".into(), true.into());
m.into()
});

// create block device symlinks and udev rules

let block_device_symlinks = self.get_block_device_symlinks();
let block_device_udev_rules = self.get_block_device_udev_rules();

if let Some(rules) = &block_device_udev_rules {
let mut mapping = serde_yaml::Mapping::new();
mapping.insert("path".into(), "/etc/udev/rules.d/99-crun-vm.rules".into());
mapping.insert("content".into(), rules.to_string().into());

write_files.push(mapping.into());
}

if !block_device_symlinks.is_empty() || block_device_udev_rules.is_some() {
let runcmd = match user_data_mapping
.entry("runcmd".into())
Expand Down Expand Up @@ -166,22 +192,6 @@ impl FirstBootConfig<'_> {
}
}

if let Some(rules) = block_device_udev_rules {
let write_files = match user_data_mapping
.entry("write_files".into())
.or_insert_with(|| serde_yaml::Value::Sequence(vec![]))
{
serde_yaml::Value::Sequence(v) => v,
_ => bail!("invalid user-data file"),
};

let mut mapping = serde_yaml::Mapping::new();
mapping.insert("path".into(), "/etc/udev/rules.d/99-crun-vm.rules".into());
mapping.insert("content".into(), rules.into());

write_files.push(mapping.into());
}

// generate iso

{
Expand Down Expand Up @@ -263,15 +273,17 @@ impl FirstBootConfig<'_> {
_ => bail!("invalid config file"),
};

let users_contains_core = users.iter().any(|u| match u {
serde_json::Value::Object(m) => m.get("name") == Some(&"core".into()),
_ => false,
});
for user in ["root", "core"] {
let user_exists = users.iter().any(|u| match u {
serde_json::Value::Object(m) => m.get("name") == Some(&user.into()),
_ => false,
});

if !users_contains_core {
users.push(serde_json::json!({
"name": "core",
}));
if !user_exists {
users.push(serde_json::json!({
"name": user,
}));
}
}

for user in users {
Expand All @@ -280,7 +292,9 @@ impl FirstBootConfig<'_> {
_ => bail!("invalid config file"),
};

if map.get("name") == Some(&"core".into()) {
let name = map.get("name");

if name == Some(&"root".into()) || name == Some(&"core".into()) {
let keys = match map
.entry("sshAuthorizedKeys")
.or_insert_with(|| serde_json::json!([]))
Expand All @@ -290,8 +304,6 @@ impl FirstBootConfig<'_> {
};

keys.push(self.container_public_key.into());

break;
}
}

Expand Down
52 changes: 36 additions & 16 deletions src/commands/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{
};

use anyhow::Result;
use clap::Parser;

use crate::crun::crun_exec;

Expand All @@ -18,22 +19,7 @@ pub fn exec(global_args: &liboci_cli::GlobalOpts, args: &liboci_cli::Exec) -> Re

let command = process.args().as_ref().expect("command specified");

let ssh_user = command
.first()
.expect("first command argument is user to ssh as into the vm");

let mut new_command = vec![];

if ssh_user != "-" {
new_command.extend(["/crun-vm/exec.sh".to_string(), ssh_user.clone()]);
}

new_command.extend(command.iter().skip(1).cloned());

if ssh_user == "-" && new_command.is_empty() {
new_command.push("/bin/bash".to_string());
}

let new_command = build_command(command);
process.set_args(Some(new_command));

serde_json::to_writer(
Expand All @@ -45,3 +31,37 @@ pub fn exec(global_args: &liboci_cli::GlobalOpts, args: &liboci_cli::Exec) -> Re

Ok(())
}

#[derive(Parser, Debug)]
#[clap(no_binary_name = true, disable_help_flag = true)]
struct ExecArgs {
#[clap(long = "as", default_value = "root")]
user: String,

#[clap(long, conflicts_with = "user")]
container: bool,

#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
command: Vec<String>,
}

fn build_command(original_command: &Vec<String>) -> Vec<String> {
let mut args: ExecArgs = ExecArgs::parse_from(original_command);

if args.command.starts_with(&["".to_string()]) {
args.command.remove(0);
}

if args.container {
if args.command.is_empty() {
vec!["/bin/bash".to_string()]
} else {
args.command
}
} else {
["/crun-vm/exec.sh".to_string(), args.user]
.into_iter()
.chain(args.command)
.collect()
}
}

0 comments on commit 1c173e4

Please sign in to comment.