Skip to content

Commit

Permalink
chore(integration-tests): add testing for user creation with password
Browse files Browse the repository at this point in the history
Signed-off-by: djach7 <djachimo@redhat.com>
  • Loading branch information
djach7 committed Jun 12, 2023
1 parent 045a20a commit a9943aa
Show file tree
Hide file tree
Showing 10 changed files with 472 additions and 29 deletions.
32 changes: 32 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions client-linuxapp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ secrecy = "0.8"
devicemapper = "0.33"
openssl = "0.10.45"
sha-crypt = "0.5.0"
logtest = "2.0.0"

fdo-data-formats = { path = "../data-formats", version = "0.4.10" }
fdo-http-wrapper = { path = "../http-wrapper", version = "0.4.10", features = ["client"] }
Expand Down
110 changes: 85 additions & 25 deletions client-linuxapp/src/serviceinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use fdo_data_formats::{
};
use fdo_http_wrapper::client::{RequestResult, ServiceClient};

use sha_crypt::{Sha256Params, sha256_simple, sha256_check};
use sha_crypt::{sha256_check, sha256_simple, Sha256Params};

const MAX_SERVICE_INFO_LOOPS: u32 = 1000;

Expand Down Expand Up @@ -65,19 +65,26 @@ fn create_user(user: &str) -> Result<()> {
}
// Creates new user if user not present
log::info!("Creating user: {user}");
Command::new("useradd")
let status = Command::new("useradd")
.arg("-m")
.arg(user)
.spawn()
.context("Error spawning new user command")?
.wait()
.context("Error creating new user")?;
Ok(())

if status.success() {
log::info!("User {user} created successfully");
Ok(())
} else {
bail!(format!("User creation failed. Exit Status: {status}"));
}
}

// Returns true if password is encrypted
// is_password_encrypted functionality is taken from osbuild-composer's crypt.go
fn is_password_encrypted(s: &str) -> bool {
// is_password_encrypted functionality is taken from osbuild-composer's crypt.go:
// https://github.com/osbuild/osbuild-composer/blob/main/internal/crypt/crypt.go
pub fn is_password_encrypted(s: &str) -> bool {
let prefixes = ["$2b$", "$6$", "$5$"];

for prefix in prefixes {
Expand Down Expand Up @@ -105,21 +112,24 @@ fn create_user_with_password(user: &str, password: &str) -> Result<()> {
str_encrypted_pw = sha256_simple(password, &default_params).expect("Hashing failed");
assert!(sha256_check(password, &str_encrypted_pw).is_ok());
}

// Creates new user if user not present
log::info!("Creating user {user} with password");
Command::new("useradd")
let status = Command::new("useradd")
.arg("-p")
.arg(str_encrypted_pw)
.arg(user)
.spawn()
.context("Error spawning new user command")?
.wait()
.context("Error creating new user")?;
Ok(())
}


if status.success() {
log::info!("User {user} created successfully with password");
Ok(())
} else {
bail!(format!("User creation failed. Exit Status: {status}"));
}
}
fn install_ssh_key(user: &str, key: &str) -> Result<()> {
let user_info = passwd::Passwd::from_name(user);
if user_info.is_none() {
Expand Down Expand Up @@ -457,21 +467,15 @@ async fn process_serviceinfo_in(si_in: &ServiceInfo, si_out: &mut ServiceInfo) -
}
if module == FedoraIotServiceInfoModule::SSHKey.into() {
if key == "username" {
let value = value
.as_str()
.context("Error parsing username value")?;
let value = value.as_str().context("Error parsing username value")?;
sshkey_user = Some(value.to_string());
log::info!("Username is: {value}");
} else if key == "password" {
let value = value
.as_str()
.context("Error parsing password value")?;
let value = value.as_str().context("Error parsing password value")?;
sshkey_password = Some(value.to_string());
log::info!("Password is present");
} else if key == "sshkeys" {
let value = value
.as_str()
.context("Error parsing sshkey value")?;
let value = value.as_str().context("Error parsing sshkey value")?;
sshkey_keys = Some(value.to_string());
log::info!("Keys are present");
}
Expand Down Expand Up @@ -686,25 +690,34 @@ async fn process_serviceinfo_in(si_in: &ServiceInfo, si_out: &mut ServiceInfo) -
if sshkey_user.is_none() {
bail!("SSHkey module missing username");
} else if sshkey_keys.is_none() && sshkey_password.is_none() {
bail!("SSHkey module missing password and key")
bail!("SSHkey module missing password and key")
}
if sshkey_password.is_some() {
log::info!("SSHkey module was active, creating user with password");
create_user_with_password(sshkey_user.as_ref().unwrap(), sshkey_password.as_ref().unwrap()).context(format!(
create_user_with_password(
sshkey_user.as_ref().unwrap(),
sshkey_password.as_ref().unwrap(),
)
.context(format!(
"Error creating new user with password: {}",
sshkey_user.as_ref().unwrap()
))?;
sshkey_user.as_ref().unwrap()
))?;
}
if sshkey_keys.is_some() {
log::info!("SSHkey module was active, installing SSH keys");
create_user(sshkey_user.as_ref().unwrap()).context(format!(
"Error creating new user: {}",
sshkey_user.as_ref().unwrap()
))?;
let sshkey_keys_v: Vec<String> = sshkey_keys.unwrap().split(" ").map(|s| s.to_string()).collect();
let sshkey_keys_v: Vec<String> = sshkey_keys
.unwrap()
.split(' ')
.map(|s| s.to_string())
.collect();
for key in sshkey_keys_v {
let key_s: String = key;
install_ssh_key(sshkey_user.as_ref().unwrap(), key_s.as_str()).context("Error installing SSH key")?;
install_ssh_key(sshkey_user.as_ref().unwrap(), key_s.as_str())
.context("Error installing SSH key")?;
log::info!("Installed sshkey: {key_s}");
}
}
Expand Down Expand Up @@ -808,6 +821,8 @@ mod test {

use super::BinaryFileInProgress;

use crate::serviceinfo::*;

#[test]
fn test_binaryfileinprogress_destination_path() {
assert_eq!(
Expand All @@ -823,4 +838,49 @@ mod test {
PathBuf::from("/etc/something")
);
}

#[test]
fn test_pw_encryption() {
let type_5_encryption = "$5$ML4hMHtER3/SY9D2$2eWHscoFbfVebDC32qA2dPo3pD6FFM6CRTrvAOMpwQ";
assert!(is_password_encrypted(type_5_encryption));
let type_2b_encryption = "$2b$ML4hMHtER3/SY9D2$2eWHscoFbfVebDC32qA2dPo3pD6FFM6CRTrvAOMpwQ";
assert!(is_password_encrypted(type_2b_encryption));
let type_6_encryption = "$6$ML4hMHtER3/SY9D2$2eWHscoFbfVebDC32qA2dPo3pD6FFM6CRTrvAOMpwQ";
assert!(is_password_encrypted(type_6_encryption));
let plaintext_encryption = "testpassword";
assert!(!is_password_encrypted(plaintext_encryption));
let empty_pw = "";
assert!(!is_password_encrypted(empty_pw));
}

#[test]
fn test_user_creation_no_pw() {
let test_user = "test";
assert!(create_user(test_user).is_ok());
let empty_user = "";
assert!(create_user(empty_user).is_err());
let at_user = "test@test";
assert!(create_user(at_user).is_err());
let dash_user = "-test";
assert!(create_user(dash_user).is_err());
let digits_user = "12345";
assert!(create_user(digits_user).is_err());
}

#[test]
fn test_user_creation_with_pw() {
let test_user = "testb";
let test_password = "password";
assert!(create_user_with_password(test_user, test_password).is_ok());
let empty_user = "";
assert!(create_user_with_password(empty_user, test_password).is_err());
let at_user = "testb@testb";
assert!(create_user_with_password(at_user, test_password).is_err());
let dash_user = "-testb";
assert!(create_user_with_password(dash_user, test_password).is_err());
let digits_user = "123456";
assert!(create_user_with_password(digits_user, test_password).is_err());
let empty_password = "";
assert!(create_user_with_password(test_user, empty_password).is_ok());
}
}
3 changes: 3 additions & 0 deletions integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ serde_json = "1.0"
pretty_assertions = "1.0.0"
paste = "1.0"
passwd = "0.0.1"
shadow = "0.0.1"
pem = "2.0"
users = "0.11.0"
sha-crypt = "0.5.0"

fdo-data-formats = { path = "../data-formats" }
fdo-util = { path = "../util" }
fdo-client-linuxapp = { path = "../client-linuxapp"}
2 changes: 1 addition & 1 deletion integration-tests/templates/serviceinfo-api-server.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ admin_auth_token: TestAdminToken
service_info:
initial_user:
username: {{ user }}
password: test
password: {{ password }}
sshkeys:
- {{ sshkey }}
files:
Expand Down
2 changes: 2 additions & 0 deletions integration-tests/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,13 +808,15 @@ impl<'a> TestServerConfigurator<'a> {
users::get_current_username().unwrap().to_str().unwrap(),
);
cfg.insert("sshkey", "sshkey_default");
cfg.insert("password", "testpassword");
} else {
L.l("per_device_serviceinfo is set, using device specific values");
cfg.insert(
"user",
users::get_current_username().unwrap().to_str().unwrap(),
);
cfg.insert("sshkey", "sshkey_per_device");
cfg.insert("password", "testpassword");
}

// TODO: Insert more defaults
Expand Down
14 changes: 13 additions & 1 deletion integration-tests/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use common::{Binary, LogSide, TestContext};

use anyhow::{bail, Context, Result};

use sha_crypt::sha256_check;

const L: LogSide = LogSide::Test;

#[tokio::test]
Expand Down Expand Up @@ -138,6 +140,7 @@ where
env::set_var("PER_DEVICE_SERVICEINFO", "false");
let mut ctx = TestContext::new().context("Error building test context")?;
let new_user: &str = "testuser"; // new user to be created during onboarding
let new_pw: &str = "testpassword"; // new password to accompany new user during onboarding
let encrypted_disk_loc = ctx.testpath().join("encrypted.img");
let rendezvous_server = ctx
.start_test_server(
Expand All @@ -156,7 +159,8 @@ where
&encrypted_disk_loc.to_string_lossy(),
);
if ci {
cfg.insert("user", new_user)
cfg.insert("user", new_user);
cfg.insert("password", new_pw);
};
Ok(())
})?)
Expand Down Expand Up @@ -402,6 +406,14 @@ sshkey_default
"User: {} is not created during onboarding",
&new_user
);
if let Some(test_user) = shadow::Shadow::from_name(new_user) {
pretty_assertions::assert_eq!(
test_user.password.is_empty(),
false,
"Password not created during onboarding"
);
assert!(sha256_check("testpassword", &test_user.password).is_ok());
}
} else {
L.l("Skipped create initial user validation
To validate set env variable FDO_PRIVILEGED and run test as superuser");
Expand Down
Loading

0 comments on commit a9943aa

Please sign in to comment.