diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 71eb49d..d70ab12 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,4 +19,4 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose + run: cargo test --verbose -- --test-threads=1 diff --git a/Cargo.lock b/Cargo.lock index f9e965e..aa5bcc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,6 +137,12 @@ dependencies = [ "libc", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.137" @@ -166,18 +172,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -264,6 +270,7 @@ dependencies = [ "clap", "error-chain", "fs2", + "lazy_static", "openssh-keys", "users", ] diff --git a/Cargo.toml b/Cargo.toml index 1ac2081..8f35cf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ repository = "https://github.com/coreos/update-ssh-keys" documentation = "https://docs.rs/update-ssh-keys" description = "A tool for managing authorized SSH keys" version = "0.4.2-alpha.0" +edition = "2021" [dependencies] # Private dependencies. @@ -16,6 +17,7 @@ fs2 = "0.4" error-chain = { version = "0.12", default-features = false } openssh-keys = { git = "https://github.com/pothos/openssh-keys", branch = "add-sk-keys" } users = "0.9" +lazy_static = "1.4.0" [[bin]] name = "update-ssh-keys" diff --git a/tests/compat_python.rs b/tests/compat_python.rs deleted file mode 100644 index a319ea1..0000000 --- a/tests/compat_python.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::env; -use std::process::Command; - -// This runs the old python integration test-suite to ensure -// retro-compatibility. -#[test] -fn test_compat_python_suite() { - let pytests = env::current_dir() - .unwrap() - .join("tests") - .join("test_update_ssh_keys.py"); - let result = Command::new(pytests).output().unwrap(); - if !result.status.success() { - panic!( - "\nstdout: {}\nstderr: {}", - String::from_utf8_lossy(&result.stdout), - String::from_utf8_lossy(&result.stderr) - ); - }; - assert!(result.status.success()); -} diff --git a/tests/test_update_ssh_keys.py b/tests/test_update_ssh_keys.py deleted file mode 100755 index 353d00b..0000000 --- a/tests/test_update_ssh_keys.py +++ /dev/null @@ -1,240 +0,0 @@ -#!/usr/bin/env python2 -# Copyright 2017 CoreOS, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import pwd -import shutil -import subprocess -import tempfile -import unittest - -script_path = os.path.abspath('%s/../../target/debug/update-ssh-keys' % __file__) - -test_keys = { - 'valid1': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDULTftpWMj4nD+7Ps' - 'B8itam2T6Aqm9Z+ursQG1SRiK4ie5rHGJoteGnbH91Uix/HDE5GC3Hz' - 'ICQVOnQay4hwJUKRfEUEWj1Sncer/BL2igDquABlcXNl2dgOlfJ8a3q' - '6IZnQpdEe6Vrqg/Ui082UxuZ08pNV94M/5IhR2fx0EbY66PQ97o+ywH' - 'sB7oXDO8p/+mGL+h7cxFY7hILXTa5/3TGBEgcA65Rrmq22eiRt97RGh' - 'DjfzIqTqb8gwuhTSNN7FWDLrEyRwJMbaTgDSoMIZdLtndVrGEqFHUO+' - 'WzinSiEQCs2MDDnTk29bleHAEktu1x68GYhg9S7O/gZq8/swAV ' - 'core@valid1', - 'valid2': 'command="echo \\"test\\"" ssh-dss AAAAB3NzaC1kc3MAAACBAJA94Sqw80BSKjVTNZD6570nXIN' - 'hP8R2UhbBuydT+GI6CfA9Dw7O0udJQUfrqARFcRQR/syc72CO6jaKNE' - '3/A5E+8uVmRZt7s9VtA47s1qxqHswth74m1Nb86n2OTB0HcW63FsXo2' - 'cJF+r+l6F3IcRPi4z/eaEKG7uhAS59TjH2tAAAAFQC0I9kL3oceMT1O' - '44WPe6NZ8w8CMwAAAIABGm2Yg8nGFZbo/W8njuM79w0W2P1NBVNWzBH' - 'WQqVbr4i1bWTSSc9X+itQUpeF6zAUDsUoprhNise2NLrMYCLFo9JxhE' - 'iYAcEJ/YbKEnjtJzaAmQNpyh3rCWuOcGPTevjAZIkl+zEc+/N7tCW1e' - 'uDYm6IXZ8LEQyTUQUdU4pZ2OgAAAIABk1ZA3+TiCMaoAafNVUZ7zwqk' - '888yVOgsJ7HGGDGRMo5ytr2SUJB7QWsLX6Un/Zbu32nXsAqtqagxd6F' - 'Ies98TSekMh/hAv9uK92mEsXSINXOeIMKRedqOyPgk5IEOsFpxAUO4T' - 'xpYToeuM8HRemecxw2eIFHnax+mQqCsi7FgQ== core@valid2', - 'valid3': 'sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9w' - 'ZW5zc2guY29tAAAAIEX/dQ0v4127bEo8eeG1EV0ApO2lWbSnN6RWusn' - '/NjqIAAAABHNzaDo= demos@siril', - 'bad': 'ssh-bad this-not-a-key core@bad', -} - -fingerprints = { - 'valid1': 'SHA256:yZ+o48h6quk9c+JVgJ/Zq4S5u4LUk6TSpneHKkmM9KY', - 'valid2': 'SHA256:RP5k1AybZ1kollIAnpUavr1v1nfZ0yloKvI46AMDPkM ', - 'valid3': 'SHA256:U8IKRkIHed6vFMTflwweA3HhIf2DWgZ8EFTm9fgwOUk', -} - -class UpdateSshKeysTestCase(unittest.TestCase): - - def setUp(self): - user_info = pwd.getpwuid(os.getuid()) - self.user = user_info.pw_name - self.ssh_dir = tempfile.mkdtemp(prefix='test_update_ssh_keys') - self.env = os.environ.copy() - self.pub_files = {} - - for name, text in test_keys.iteritems(): - pub_path = '%s/%s.pub' % (self.ssh_dir, name) - self.pub_files[name] = pub_path - with open(pub_path, 'w') as pub_fd: - pub_fd.write('%s\n' % text) - - def tearDown(self): - shutil.rmtree(self.ssh_dir) - - def assertHasKeys(self, *keys): - with open('%s/authorized_keys' % self.ssh_dir, 'r') as fd: - text = fd.read() - self.assertTrue(text.startswith('# auto-generated')) - for key in keys: - self.assertIn(test_keys[key], text) - for key in test_keys: - if key in keys: - continue - self.assertNotIn(test_keys[key], text) - - def run_script(self, *args, **kwargs): - cmd = [script_path, '-u', self.user, '--ssh-dir', self.ssh_dir] - cmd.extend(args) - return subprocess.Popen(cmd, env=self.env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - **kwargs) - - def test_usage(self): - proc = self.run_script('-h') - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertTrue(out.startswith('Usage: ')) - self.assertEquals(err, '') - - def test_no_keys(self): - proc = self.run_script() - out, err = proc.communicate() - self.assertEquals(proc.returncode, 1) - self.assertEquals(out, '') - self.assertIn('no keys found', err) - self.assertTrue(os.path.isdir('%s/authorized_keys.d' % self.ssh_dir)) - self.assertFalse(os.path.exists('%s/authorized_keys' % self.ssh_dir)) - - def test_first_run(self): - with open('%s/authorized_keys' % self.ssh_dir, 'w') as fd: - fd.write('%s\n' % test_keys['valid1']) - fd.write('%s\n' % test_keys['valid2']) - fd.write('%s\n' % test_keys['valid3']) - proc = self.run_script() - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertTrue(out.startswith('Updated ')) - self.assertEquals(err, '') - self.assertTrue(os.path.exists( - '%s/authorized_keys.d/old_authorized_keys' % self.ssh_dir)) - self.assertHasKeys('valid1', 'valid2', 'valid3') - - def test_add_one_file(self): - proc = self.run_script('-a', 'one', self.pub_files['valid1']) - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertTrue(out.startswith('Adding')) - self.assertIn(fingerprints['valid1'], out) - self.assertIn('\nUpdated ', out) - self.assertEquals(err, '') - self.assertTrue(os.path.exists( - '%s/authorized_keys.d/one' % self.ssh_dir)) - self.assertHasKeys('valid1') - - def test_add_one_stdin(self): - proc = self.run_script('-a', 'one', stdin=subprocess.PIPE) - out, err = proc.communicate(test_keys['valid1']) - self.assertEquals(proc.returncode, 0) - self.assertTrue(out.startswith('Adding')) - self.assertIn(fingerprints['valid1'], out) - self.assertIn('\nUpdated ', out) - self.assertEquals(err, '') - self.assertTrue(os.path.exists( - '%s/authorized_keys.d/one' % self.ssh_dir)) - self.assertHasKeys('valid1') - - def test_replace_one(self): - self.test_add_one_file() - proc = self.run_script('-a', 'one', self.pub_files['valid2']) - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertIn(fingerprints['valid2'], out) - self.assertEquals(err, '') - self.assertHasKeys('valid2') - - def test_no_replace(self): - self.test_add_one_file() - proc = self.run_script('-n', '-a', 'one', self.pub_files['valid2']) - out, err = proc.communicate() - self.assertTrue(out.startswith('Skipping')) - self.assertEquals(proc.returncode, 0) - self.assertEquals(err, '') - self.assertHasKeys('valid1') - - proc = self.run_script('-n', '-A', 'one', self.pub_files['valid2']) - out, err = proc.communicate() - self.assertTrue(out.startswith('Skipping')) - self.assertEquals(proc.returncode, 0) - self.assertEquals(err, '') - self.assertHasKeys('valid1') - - def test_add_two(self): - self.test_add_one_file() - proc = self.run_script('-a', 'two', self.pub_files['valid2']) - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertIn(fingerprints['valid2'], out) - self.assertEquals(err, '') - self.assertHasKeys('valid1', 'valid2') - - def test_del_one(self): - self.test_add_one_file() - proc = self.run_script('-d', 'one') - out, err = proc.communicate() - self.assertEquals(proc.returncode, 1) - self.assertIn(fingerprints['valid1'], out) - self.assertIn('no keys found', err) - # Removed from authorized_keys.d but not authorized_keys - self.assertFalse(os.path.exists( - '%s/authorized_keys.d/one' % self.ssh_dir)) - self.assertHasKeys('valid1') - - def test_del_two(self): - self.test_add_two() - proc = self.run_script('-d', 'two') - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertIn(fingerprints['valid2'], out) - self.assertEquals(err, '') - self.assertHasKeys('valid1') - - def test_disable(self): - self.test_add_two() - proc = self.run_script('-D', 'two') - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertTrue(out.startswith('Disabling')) - self.assertIn(fingerprints['valid2'], out) - self.assertEquals(err, '') - self.assertHasKeys('valid1') - - proc = self.run_script('-a', 'two', self.pub_files['valid2']) - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertTrue(out.startswith('Skipping')) - self.assertEquals(err, '') - self.assertHasKeys('valid1') - - def test_enable(self): - self.test_disable() - proc = self.run_script('-A', 'two', self.pub_files['valid2']) - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertTrue(out.startswith('Adding')) - self.assertEquals(err, '') - self.assertHasKeys('valid1', 'valid2') - - def test_add_bad(self): - self.test_add_one_file() - proc = self.run_script('-a', 'bad', self.pub_files['bad']) - out, err = proc.communicate() - self.assertEquals(proc.returncode, 0) - self.assertIn('warning', out) - self.assertIn('failed to parse public key', out) - self.assertHasKeys('valid1') - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_update_ssh_keys.rs b/tests/test_update_ssh_keys.rs new file mode 100644 index 0000000..d0c60f8 --- /dev/null +++ b/tests/test_update_ssh_keys.rs @@ -0,0 +1,444 @@ +// Copyright 2017-2023 Flatcar Authors + +extern crate update_ssh_keys; + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::path::PathBuf; + use std::fs; + use std::fs::File; + use std::io::{Read, Write}; + + use update_ssh_keys::{AuthorizedKeys, AuthorizedKeyEntry}; + + const SSH_DIR: &str = "/tmp/test_update_ssh_keys"; + + lazy_static::lazy_static! { + static ref TEST_KEYS: HashMap<&'static str, &'static str> = + [ + ( + "valid1", + "\ + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDULTftpWMj4nD+7Ps\ + B8itam2T6Aqm9Z+ursQG1SRiK4ie5rHGJoteGnbH91Uix/HDE5GC3Hz\ + ICQVOnQay4hwJUKRfEUEWj1Sncer/BL2igDquABlcXNl2dgOlfJ8a3q\ + 6IZnQpdEe6Vrqg/Ui082UxuZ08pNV94M/5IhR2fx0EbY66PQ97o+ywH\ + sB7oXDO8p/+mGL+h7cxFY7hILXTa5/3TGBEgcA65Rrmq22eiRt97RGh\ + DjfzIqTqb8gwuhTSNN7FWDLrEyRwJMbaTgDSoMIZdLtndVrGEqFHUO+\ + WzinSiEQCs2MDDnTk29bleHAEktu1x68GYhg9S7O/gZq8/swAV", + ), + ( + "valid2", + "\ + command=\"echo \\\"test\\\"\" ssh-dss AAAAB3NzaC1kc3MAAACBAJA94Sqw80BSKjVTNZD6570nXIN\ + hP8R2UhbBuydT+GI6CfA9Dw7O0udJQUfrqARFcRQR/syc72CO6jaKNE\ + 3/A5E+8uVmRZt7s9VtA47s1qxqHswth74m1Nb86n2OTB0HcW63FsXo2\ + cJF+r+l6F3IcRPi4z/eaEKG7uhAS59TjH2tAAAAFQC0I9kL3oceMT1O\ + 44WPe6NZ8w8CMwAAAIABGm2Yg8nGFZbo/W8njuM79w0W2P1NBVNWzBH\ + WQqVbr4i1bWTSSc9X+itQUpeF6zAUDsUoprhNise2NLrMYCLFo9JxhE\ + iYAcEJ/YbKEnjtJzaAmQNpyh3rCWuOcGPTevjAZIkl+zEc+/N7tCW1e\ + uDYm6IXZ8LEQyTUQUdU4pZ2OgAAAIABk1ZA3+TiCMaoAafNVUZ7zwqk\ + 888yVOgsJ7HGGDGRMo5ytr2SUJB7QWsLX6Un/Zbu32nXsAqtqagxd6F\ + Ies98TSekMh/hAv9uK92mEsXSINXOeIMKRedqOyPgk5IEOsFpxAUO4T\ + xpYToeuM8HRemecxw2eIFHnax+mQqCsi7FgQ== core@valid2", + ), + ( + "valid3", + "\ + sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9w\ + ZW5zc2guY29tAAAAIEX/dQ0v4127bEo8eeG1EV0ApO2lWbSnN6RWusn\ + /NjqIAAAABHNzaDo= demos@siril", + ), + ( + "bad", + "ssh-bad this-not-a-key core@bad", + ), + ].iter().cloned().collect(); + + static ref FINGERPRINTS: HashMap<&'static str, &'static str> = + [ + ( + "valid1", + "SHA256:yZ+o48h6quk9c+JVgJ/Zq4S5u4LUk6TSpneHKkmM9KY", + ), + ( + "valid2", + "SHA256:RP5k1AybZ1kollIAnpUavr1v1nfZ0yloKvI46AMDPkM", + ), + ( + "valid3", + "SHA256:U8IKRkIHed6vFMTflwweA3HhIf2DWgZ8EFTm9fgwOUk", + ), + ].iter().cloned().collect(); + } + + fn assert_has_keys(keys: Vec<&str>) { + let authkeyspath: PathBuf = PathBuf::from(format!("{}/authorized_keys", SSH_DIR)); + let authkeysfile = File::open(authkeyspath); + assert!(authkeysfile.is_ok()); + let mut authkeystext = String::new(); + authkeysfile.unwrap().read_to_string(&mut authkeystext).expect("unable to read a file"); + assert!(authkeystext.starts_with("# auto-generated")); + + for key in &keys { + assert!(authkeystext.contains(&*TEST_KEYS[key])); + } + + // for (key, _) in &*TEST_KEYS { + // if keys.contains(&key) { + // continue; + // } + // assert!(!authkeystext.contains(&*TEST_KEYS[key])); + // } + } + + fn add_key_check_results(aks: AuthorizedKeys, keys: Vec, sshkeyname: &str, testkeyname: &str, assert_keys: Vec<&str>) { + for key in &keys { + if let AuthorizedKeyEntry::Valid { + ref key, + } = *key + { + assert!(key.to_fingerprint_string().contains(&*FINGERPRINTS[sshkeyname])); + + aks.write().expect("failed to update authorized keys directory"); + _ = aks.sync(); + + let authkeyone: PathBuf = PathBuf::from(format!("{}/authorized_keys.d/{}", SSH_DIR, testkeyname)); + + assert!(authkeyone.exists()); + + assert_has_keys(assert_keys.clone()); + } + } + } + + fn add_one_ssh_key(sshkeyname: &str, testkeyname: &str, is_force: bool, is_replace: bool, is_stdin: bool, is_expected_success: bool, assert_keys: Vec<&str>) { + let ssh_dir: PathBuf = PathBuf::from(SSH_DIR); + let unamebind = users::get_current_username().expect("no username"); + let unamestr = unamebind.to_str().unwrap(); + let user = users::get_user_by_name(&unamestr).ok_or_else(|| format!("failed to find user with name '{}'", unamestr)).unwrap(); + + let mut aks = AuthorizedKeys::open(user, true, Some(ssh_dir)).expect(format!("failed to open authorized_keys directory for user '{}'", unamestr).as_str()); + + let keyfiles = [format!("{}/{}.pub", SSH_DIR, sshkeyname)]; + + let keys = if is_stdin { + // read the keys from stdin + AuthorizedKeys::read_keys(std::io::stdin()).unwrap() + } else { + let mut fkeys = vec![]; + for keyfile in keyfiles { + let file = File::open(&keyfile).expect(format!("failed to open keyfile '{:?}'", keyfile).as_str()); + fkeys.append(&mut AuthorizedKeys::read_keys(file).unwrap()); + } + fkeys + }; + + let res = aks.add_keys(testkeyname, keys.clone(), is_replace, is_force); + + match res { + Ok(keys) => { + if is_expected_success { + add_key_check_results(aks, keys, sshkeyname, testkeyname, assert_keys); + } else { + panic!("failed to add keys"); + } + } + Err(err) => { + if is_expected_success { + panic!("failed to add keys, {}", err); + } else { + add_key_check_results(aks, keys, sshkeyname, testkeyname, assert_keys); + } + } + } + } + + fn del_one_ssh_key(sshkeyname: &str, testkeyname: &str) { + let ssh_dir: PathBuf = PathBuf::from(SSH_DIR); + let unamebind = users::get_current_username().expect("no username"); + let unamestr = unamebind.to_str().unwrap(); + let user = users::get_user_by_name(&unamestr).ok_or_else(|| format!("failed to find user with name '{}'", unamestr)).unwrap(); + + let mut aks = AuthorizedKeys::open(user, true, Some(ssh_dir)).expect(format!("failed to open authorized_keys directory for user '{}'", unamestr).as_str()); + + let keyfiles = [format!("{}/{}.pub", SSH_DIR, sshkeyname)]; + + let mut keys = vec![]; + for keyfile in keyfiles { + let file = File::open(&keyfile).expect(format!("failed to open keyfile '{:?}'", keyfile).as_str()); + keys.append(&mut AuthorizedKeys::read_keys(file).unwrap()); + } + + for key in aks.remove_keys(testkeyname) { + if let AuthorizedKeyEntry::Valid { + ref key, + } = key + { + assert!(key.to_fingerprint_string().contains(&*FINGERPRINTS[sshkeyname])); + + aks.write().expect("failed to update authorized keys directory"); + _ = aks.sync(); + + let authkeyone: PathBuf = PathBuf::from(format!("{}/authorized_keys.d/{}", SSH_DIR, testkeyname)); + assert!(!authkeyone.exists()); + + let mut svec: Vec<&str> = Vec::new(); + svec.push(sshkeyname); + assert_has_keys(svec); + } + } + } + + #[test] + fn test001_setup_tests() { + for (name, text) in &*TEST_KEYS { + _ = fs::create_dir_all(SSH_DIR); + let pub_path: PathBuf = PathBuf::from(format!("{}/{}.pub", SSH_DIR, name)); + + let mut pubfile = File::create(pub_path).expect("unable to create a file"); + let _ = pubfile.write_all(text.as_bytes()); + } + } + + #[test] + fn test002_no_keys() { + let ssh_dir: PathBuf = PathBuf::from(SSH_DIR); + + let unamebind = users::get_current_username().expect("no username"); + let unamestr = unamebind.to_str().unwrap(); + let user = users::get_user_by_name(&unamestr).ok_or_else(|| format!("failed to find user with name '{}'", unamestr)).unwrap(); + + let aks = AuthorizedKeys::open(user, true, Some(ssh_dir)).expect(format!("failed to open authorized_keys directory for user '{}'", unamestr).as_str()); + + aks.write().expect("failed to update authorized keys directory"); + + let authkeysdir: PathBuf = PathBuf::from(format!("{}/authorized_keys.d", SSH_DIR)); + assert!(authkeysdir.is_dir()); + + let authkeys: PathBuf = PathBuf::from(format!("{}/authorized_keys", SSH_DIR)); + assert!(!authkeys.exists()); + } + + #[test] + fn test003_first_run() { + // It is necessary to clean up before running the first run test, + // because old_authorized_keys will be only created when authorized_keys.d + // does not exist. + _ = fs::remove_dir_all(format!("{}/authorized_keys.d", SSH_DIR)); + + let ssh_dir: PathBuf = PathBuf::from(SSH_DIR); + let authkeys: PathBuf = PathBuf::from(format!("{}/authorized_keys", SSH_DIR)); + let mut authkeysfile = File::options().create(true).append(true).write(true).open(authkeys.clone()).expect("unable to create a file"); + for (_, text) in &*TEST_KEYS { + _ = authkeysfile.write_all(format!("{}\n", text).as_bytes()); + } + + let unamebind = users::get_current_username().expect("no username"); + let unamestr = unamebind.to_str().unwrap(); + let user = users::get_user_by_name(&unamestr).ok_or_else(|| format!("failed to find user with name '{}'", unamestr)).unwrap(); + + let aks = AuthorizedKeys::open(user, true, Some(ssh_dir)).expect(format!("failed to open authorized_keys directory for user '{}'", unamestr).as_str()); + + aks.write().expect("failed to update authorized keys directory"); + _ = aks.sync(); + + let authkeys: PathBuf = PathBuf::from(format!("{}/authorized_keys.d/old_authorized_keys", SSH_DIR)); + assert!(authkeys.exists()); + assert_has_keys(["valid1", "valid2", "valid3"].to_vec()); + } + + #[test] + fn test004_add_one_file() { + add_one_ssh_key( + "valid1", // sshkeyname + "one", // testkeyname + false, // is_force + true, // is_replace + false, // is_stdin + true, // is_expected_success + ["valid1"].to_vec(), // assert_keys + ); + } + + #[test] + #[ignore] + fn test005_add_one_stdin() { + add_one_ssh_key( + "valid1", // sshkeyname + "one", // testkeyname + false, // is_force + false, // is_replace + true, // is_stdin + true, // is_expected_success + ["valid1"].to_vec(), // assert_keys + ); + } + + #[test] + fn test006_replace_one() { + test004_add_one_file(); + + add_one_ssh_key( + "valid2", // sshkeyname + "one", // testkeyname + false, // is_force + true, // is_replace + false, // is_stdin + true, // is_expected_success + ["valid2"].to_vec(), // assert_keys + ); + } + + #[test] + fn test007_no_replace() { + del_one_ssh_key("valid2", "one"); + test004_add_one_file(); + + add_one_ssh_key( + "valid2", // sshkeyname + "one", // testkeyname + false, // is_force + false, // is_replace + false, // is_stdin + false, // is_expected_success + ["valid1"].to_vec(), // assert_keys + ); + + add_one_ssh_key( + "valid2", // sshkeyname + "one", // testkeyname + true, // is_force + false, // is_replace + false, // is_stdin + false, // is_expected_success + ["valid1"].to_vec(), // assert_keys + ); + } + + #[test] + fn test008_add_two() { + test004_add_one_file(); + + add_one_ssh_key( + "valid2", // sshkeyname + "two", // testkeyname + false, // is_force + false, // is_replace + false, // is_stdin + true, // is_expected_success + ["valid1", "valid2"].to_vec(), // assert_keys + ); + } + + #[test] + fn test009_del_one() { + test004_add_one_file(); + + del_one_ssh_key("valid1", "one"); + } + + #[test] + fn test010_del_two() { + del_one_ssh_key("valid1", "one"); + del_one_ssh_key("valid2", "two"); + test008_add_two(); + + del_one_ssh_key("valid2", "two"); + } + + #[test] + #[ignore] + fn test_disable() { + del_one_ssh_key("valid1", "one"); + del_one_ssh_key("valid2", "two"); + + test008_add_two(); + + let sshkeyname1 = "valid1"; + let sshkeyname2 = "valid2"; + + let ssh_dir: PathBuf = PathBuf::from(SSH_DIR); + + let unamebind = users::get_current_username().expect("no username"); + let unamestr = unamebind.to_str().unwrap(); + let user = users::get_user_by_name(&unamestr).ok_or_else(|| format!("failed to find user with name '{}'", unamestr)).unwrap(); + + let mut aks = AuthorizedKeys::open(user, true, Some(ssh_dir)).expect(format!("failed to open authorized_keys directory for user '{}'", unamestr).as_str()); + + let keyfiles = [format!("{}/{}.pub", SSH_DIR, sshkeyname2)]; + + let mut keys = vec![]; + for keyfile in keyfiles { + let file = File::open(&keyfile).expect(format!("failed to open keyfile '{:?}'", keyfile).as_str()); + keys.append(&mut AuthorizedKeys::read_keys(file).unwrap()); + } + + for key in aks.disable_keys("two") { + if let AuthorizedKeyEntry::Valid { + ref key, + } = key + { + assert!(key.to_fingerprint_string().contains(&*FINGERPRINTS[sshkeyname2])); + + aks.write().expect("failed to update authorized keys directory"); + _ = aks.sync(); + + let authkeyone: PathBuf = PathBuf::from(format!("{}/authorized_keys.d/{}", SSH_DIR, "two")); + assert!(authkeyone.exists()); + + let mut svec: Vec<&str> = Vec::new(); + svec.push(sshkeyname1); + assert_has_keys(svec); + } + } + + // add two again + // add_one_ssh_key( + // "valid2", // sshkeyname + // "two", // testkeyname + // false, // is_force + // false, // is_replace + // false, // is_stdin + // false, // is_expected_success + // ["valid1"].to_vec(), // assert_keys + // ); + } + + #[test] + #[ignore] + fn test012_enable() { + test_disable(); + + add_one_ssh_key( + "valid2", // sshkeyname + "two", // testkeyname + true, // is_force + false, // is_replace + false, // is_stdin + true, // is_expected_success + ["valid1", "valid2"].to_vec(), // assert_keys + ); + } + + #[test] + fn test013_add_bad() { + test004_add_one_file(); + + add_one_ssh_key( + "bad", // sshkeyname + "bad", // testkeyname + true, // is_force + false, // is_replace + false, // is_stdin + true, // is_expected_success + ["valid1"].to_vec(), // assert_keys + ); + } + + #[test] + fn test014_teardown_tests() { + _ = fs::remove_dir_all(SSH_DIR); + } +}