Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Azure Linux support #87

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ description = "A reference implementation for provisioning Linux VMs on Azure."

[dependencies]
tokio = { version = "1", features = ["full"] }
os-release = "0.1.0"

[dependencies.libazureinit]
path = "libazureinit"
Expand Down
1 change: 1 addition & 0 deletions libazureinit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ xml-rs = "0.8.13"
serde_json = "1.0.96"
nix = "0.26.2"
libc = "0.2.146"
os-release = "0.1.0"

[lib]
name = "libazureinit"
Expand Down
80 changes: 80 additions & 0 deletions libazureinit/src/distro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub trait Distribution {
pub enum Distributions {
Debian,
Ubuntu,
AzureLinux,
}

impl Distribution for Distributions {
Expand All @@ -26,10 +27,78 @@ impl Distribution for Distributions {
password: &str,
) -> Result<i32, String> {
match self {
Distributions::AzureLinux => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As being implemented in the other PR #86, we should not check for each distro, to be flexible and scalable.
I will soon try to update the PR to make it reviewable. Once it got merged, I hope this PR could be rebased and written without the distro checks.

let mut home_path = "/home/".to_string();
home_path.push_str(username);
println!("Creating user in AzureLinux: {}", username);
match Command::new("useradd")
.arg(username)
.arg("--comment")
.arg(
"Provisioning agent created this user based on username provided in IMDS",
)
.arg("-U")
.arg("--groups")
.arg("wheel,sudo")
.arg("-d")
.arg(home_path.clone())
.arg("-m")
.status(){
Ok(_)=>(),
Err(err) => return Err(err.to_string()),
};

if password.is_empty() {
match Command::new("passwd")
.arg("-d")
.arg(username)
.status()
{
Ok(status_code) => {
if !status_code.success() {
return Err("Failed to create user".to_string());
}
}
Err(err) => return Err(err.to_string()),
};
println!("User created without password")
} else {
let input = format!("{}:{}", username, password);

let mut output = Command::new("chpasswd")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I am not an expert of Azure Linux, but is there a specific reason why you need to do password-based authentication?

Previously we had the password authentication in the code base, but the PR #57 removed that part mainly for security. Though there still seems to be cases where password authentication would be necessary. I am just curious what the situation of Azure Linux would be.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No actually, I believe we do want to eliminate password auth, this was a hold over from me re-implementing the debian logic of an older branch 👍

Thank you for the catch

.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::inherit())
.spawn()
.expect("Failed to run chpasswd.");

let mut stdin =
output.stdin.as_ref().ok_or("Failed to open stdin")?;

stdin.write_all(input.as_bytes()).map_err(|error| {
format!("Failed to write to stdin: {}", error)
})?;

let status = output.wait().map_err(|error| {
format!("Failed to wait for stdin command: {}", error)
})?;

if !status.success() {
return Err(format!(
"Chpasswd command failed with exit code {}",
status.code().unwrap_or(-1)
));
}
println!("User created with password")
}

Ok(0)
},
Distributions::Debian | Distributions::Ubuntu => {
let mut home_path = "/home/".to_string();
home_path.push_str(username);

println!("Creating user in Debian/Ubuntu: {}", username);
match Command::new("useradd")
.arg(username)
.arg("--comment")
Expand Down Expand Up @@ -94,6 +163,16 @@ impl Distribution for Distributions {
}
fn set_hostname(&self, hostname: &str) -> Result<i32, String> {
match self {
Distributions::AzureLinux => {
match Command::new("hostnamectl")
.arg("set-hostname")
.arg(hostname)
.status()
{
Ok(status_code) => Ok(status_code.code().unwrap_or(1)),
Err(err) => Err(err.to_string()),
}
},
Distributions::Debian | Distributions::Ubuntu => {
match Command::new("hostnamectl")
.arg("set-hostname")
Expand All @@ -112,6 +191,7 @@ impl From<&str> for Distributions {
match s {
"debian" => Distributions::Debian,
"ubuntu" => Distributions::Ubuntu,
"azurelinux" => Distributions::AzureLinux,
_ => panic!("Unknown distribution"),
}
}
Expand Down
18 changes: 8 additions & 10 deletions libazureinit/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub async fn set_ssh_keys(
let mut authorized_keys_path = file_path;
authorized_keys_path.push_str("/authorized_keys");

println!("authorized_keys_path: {:?}", authorized_keys_path);
let mut authorized_keys =
File::create(authorized_keys_path.clone()).unwrap();
for key in keys {
Expand All @@ -30,17 +31,17 @@ pub async fn set_ssh_keys(
let mut new_permissions = permissions.clone();
new_permissions.set_mode(0o600);
fs::set_permissions(authorized_keys_path.clone(), new_permissions).unwrap();

let uid_username = CString::new(username.clone()).unwrap();
let uid_passwd = unsafe { libc::getpwnam(uid_username.as_ptr()) };
let uid = unsafe { (*uid_passwd).pw_uid };
let new_uid = Uid::from_raw(uid);

let gid_groupname = CString::new(username.clone()).unwrap();
let gid_group = unsafe { libc::getgrnam(gid_groupname.as_ptr()) };
let gid = unsafe { (*gid_group).gr_gid };
let new_gid = Gid::from_raw(gid);

let _set_ownership = nix::unistd::chown(
authorized_keys_path.as_str(),
Some(new_uid),
Expand All @@ -54,27 +55,24 @@ pub async fn create_ssh_directory(
) -> Result<(), Box<dyn std::error::Error>> {
let mut file_path = home_path.to_owned();
file_path.push_str("/.ssh");

create_dir(file_path.clone())?;

let uid_username = CString::new(username).unwrap();
let uid_passwd = unsafe { libc::getpwnam(uid_username.as_ptr()) };
let uid = unsafe { (*uid_passwd).pw_uid };
let new_uid = Uid::from_raw(uid);

let gid_groupname = CString::new(username).unwrap();
let gid_group = unsafe { libc::getgrnam(gid_groupname.as_ptr()) };
let gid = unsafe { (*gid_group).gr_gid };
let new_gid = Gid::from_raw(gid);

let _set_ownership =
nix::unistd::chown(file_path.as_str(), Some(new_uid), Some(new_gid));

nix::unistd::chown(file_path.as_str(), Some(new_uid), Some(new_gid));
let metadata = fs::metadata(&file_path).unwrap();
let permissions = metadata.permissions();
let mut new_permissions = permissions.clone();
new_permissions.set_mode(0o700);
fs::set_permissions(&file_path, new_permissions).unwrap();

Ok(())
}
26 changes: 23 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,30 @@

use libazureinit::distro::{Distribution, Distributions};
use libazureinit::{goalstate, imds, media, user};
use os_release::OsRelease;

#[tokio::main]
async fn main() {
println!("Starting Provisioning...");
println!("Querying IMDS...");
let query_result = imds::query_imds().await;
let imds_body = match query_result {
Ok(imds_body) => imds_body,
Err(_err) => return,
};

println!("Getting provisioning details...");
let provision_with_password = imds::get_provision_with_password(&imds_body);
let disable_authentication = match provision_with_password {
Ok(disable_authentication) => disable_authentication,
Err(_err) => return,
};

println!("disable auth is set to: {}", disable_authentication);
let username;
let mut password = "".to_owned();

println!("Provisioning user...");
if !disable_authentication {
media::make_temp_directory().unwrap();

Expand All @@ -47,46 +53,60 @@ async fn main() {
Ok(username) => username,
Err(_err) => return,
};
println!("Getting username... {}", username.as_str());
}

let mut file_path = "/home/".to_string();
file_path.push_str(username.as_str());

Distributions::from("ubuntu")
let _os_release = match OsRelease::new() {
Ok(os_release) => os_release,
Err(_err) => return,
};
println!("Found OS: {}", _os_release.id.as_str());

Distributions::from(_os_release.id.as_str())
.create_user(username.as_str(), password.as_str())
.expect("Failed to create user");
let _create_directory =
user::create_ssh_directory(username.as_str(), &file_path).await;

println!("User's SSH directory was successfully created");
let get_ssh_key_result = imds::get_ssh_keys(imds_body.clone());
println!("Getting SSH keys...");
let keys = match get_ssh_key_result {
Ok(keys) => keys,
Err(_err) => return,
};

file_path.push_str("/.ssh");

println!("Setting SSH keys...");
user::set_ssh_keys(keys, username.to_string(), file_path.clone()).await;

println!("Setting hostname...");
let get_hostname_result = imds::get_hostname(imds_body.clone());
let hostname = match get_hostname_result {
Ok(hostname) => hostname,
Err(_err) => return,
};

Distributions::from("ubuntu")
println!("Setting hostname to: {}", hostname.as_str());
Distributions::from(_os_release.id.as_str())
.set_hostname(hostname.as_str())
.expect("Failed to set hostname");

println!("Getting goal state...");
let get_goalstate_result = goalstate::get_goalstate().await;
let vm_goalstate = match get_goalstate_result {
Ok(vm_goalstate) => vm_goalstate,
Err(_err) => return,
};

println!("Reporting health...");
let report_health_result = goalstate::report_health(vm_goalstate).await;
match report_health_result {
Ok(report_health) => report_health,
Err(_err) => return,
};
println!("Provisioning completed successfully.");
}