nethsm/
test.rs

1#![doc = include_str!("../README.md")]
2
3use std::fs::File;
4use std::path::PathBuf;
5
6use chrono::Utc;
7use rstest::fixture;
8// Publicly re-export, so that consumers do not have to rely on rustainers directly .
9pub use rustainers::Container;
10use rustainers::runner::{RunOption, Runner};
11use testresult::TestResult;
12
13use crate::{
14    Connection,
15    ConnectionSecurity,
16    Credentials,
17    KeyId,
18    KeyMechanism,
19    KeyType,
20    NetHsm,
21    Passphrase,
22    Url,
23    UserRole,
24};
25
26/// Identifier for an admin user.
27pub static ADMIN_USER_ID: &str = "admin";
28
29/// Sample admin passphrase.
30pub static ADMIN_USER_PASSPHRASE: &str = "just-an-admin-passphrase";
31
32/// Sample unlock passphrase.
33pub static UNLOCK_PASSPHRASE: &str = "just-an-unlock-passphrase";
34
35/// Default user ID for an operator.
36pub static DEFAULT_OPERATOR_USER_ID: &str = "operator1";
37
38/// Default real name for an operator.
39pub static DEFAULT_OPERATOR_USER_REAL_NAME: &str = "Some Operator";
40
41/// Sample operator passphrase.
42pub static DEFAULT_OPERATOR_USER_PASSPHRASE: &str = "just-an-operator-passphrase";
43
44/// User ID for a different user.
45pub static OTHER_OPERATOR_USER_ID: &str = "operator2";
46
47/// Real name for a different user.
48pub static OTHER_OPERATOR_USER_REAL_NAME: &str = "Some Other Operator";
49
50/// Sample passphrase for a different user.
51pub static OTHER_OPERATOR_USER_PASSPHRASE: &str = "just-another-operator-passphrase";
52
53/// User ID for backup purposes.
54pub static BACKUP_USER_ID: &str = "backup1";
55
56/// Real name for the backup user.
57pub static BACKUP_USER_REAL_NAME: &str = "Some Backup";
58
59/// Sample passphrase for the backup user.
60pub static BACKUP_USER_PASSPHRASE: &str = "just-a-backup-passphrase";
61
62/// User ID for the metrics user.
63pub static METRICS_USER_ID: &str = "metrics1";
64
65/// Real name for the metrics user.
66pub static METRICS_USER_REAL_NAME: &str = "Some Metrics";
67
68/// Sample passphrase for the metrics user.
69pub static METRICS_USER_PASSPHRASE: &str = "just-a-metrics-passphrase";
70
71/// Default size of the RSA key in bits.
72pub static DEFAULT_RSA_BITS: u32 = 2048;
73
74/// Default ID for a key.
75pub static DEFAULT_KEY_ID: &str = "key1";
76
77/// Default ID for a different key.
78pub static OTHER_KEY_ID: &str = "key2";
79
80/// Default tag.
81pub static DEFAULT_TAG: &str = "tag1";
82
83/// Different tag.
84pub static OTHER_TAG: &str = "tag2";
85
86/// Default ID for the encryption key.
87pub static ENC_KEY_ID: &str = "enckey1";
88
89/// Default tag for the encryption key.
90pub static ENC_TAG: &str = "enctag1";
91
92/// User ID for the operator user who can access the encryption key.
93pub static ENC_OPERATOR_USER_ID: &str = "encoperator1";
94
95/// Real name for the operator user who can access the encryption key.
96pub static ENC_OPERATOR_USER_REAL_NAME: &str = "Some Encryption Operator";
97
98/// Sample passphrase for the operator user who can access the encryption key.
99pub static ENC_OPERATOR_USER_PASSPHRASE: &str = "just-an-encryption-passphrase";
100
101/// Default size for the AES key in bits.
102pub static DEFAULT_AES_BITS: u32 = 128;
103
104/// Sample namespace.
105pub static NAMESPACE1: &str = "namespace1";
106
107/// Administrator's user ID for `namespace1`.
108pub static NAMESPACE1_ADMIN_USER_ID: &str = "namespace1~admin";
109
110/// Sample passphrase for `namespace1`'s administrator.
111pub static NAMESPACE1_ADMIN_USER_PASSPHRASE: &str = "just-a-namespace-admin-passphrase";
112
113/// Real name for `namespace1`'s administrator.
114pub static NAMESPACE1_ADMIN_REAL_NAME: &str = "Namespace1 Admin";
115
116/// User ID of an operator in `namespace1`.
117pub static NAMESPACE1_OPERATOR_USER_ID: &str = "namespace1~operator";
118
119/// Sample passphrase of an operator in `namespace1`.
120pub static NAMESPACE1_OPERATOR_USER_PASSPHRASE: &str = "just-a-namespace-operator-passphrase";
121
122/// Real name of an operator in `namespace1`.
123pub static NAMESPACE1_OPERATOR_REAL_NAME: &str = "Namespace1 Operator";
124
125/// Second namespace.
126pub static NAMESPACE2: &str = "namespace2";
127
128/// Administrator's user ID for `namespace2`.
129pub static NAMESPACE2_ADMIN_USER_ID: &str = "namespace2~admin";
130
131/// Sample passphrase for `namespace2`'s administrator.
132pub static NAMESPACE2_ADMIN_USER_PASSPHRASE: &str = "just-a-namespace2-admin-passphrase";
133
134/// Real name for `namespace2`'s administrator.
135pub static NAMESPACE2_ADMIN_REAL_NAME: &str = "Namespace2 Admin";
136
137/// User ID of an operator in `namespace2`.
138pub static NAMESPACE2_OPERATOR_USER_ID: &str = "namespace2~operator";
139
140/// Sample passphrase of an operator in `namespace2`.
141pub static NAMESPACE2_OPERATOR_USER_PASSPHRASE: &str = "just-a-namespace2-operator-passphrase";
142
143/// Real name of an operator in `namespace2`.
144pub static NAMESPACE2_OPERATOR_REAL_NAME: &str = "Namespace2 Operator";
145
146mod container;
147pub use container::NetHsmImage;
148
149/// Creates and starts a new NetHSM container.
150pub async fn create_container() -> TestResult<Container<NetHsmImage>> {
151    let runner = Runner::podman()?;
152    let image = NetHsmImage::default();
153    println!("image: {:#?}", image.image);
154    let run_options = RunOption::builder().with_remove(true).build();
155    let container = runner.start_with_options(image, run_options).await?;
156    println!("serving URL: {}", container.url().await?);
157    Ok(container)
158}
159
160/// Creates a new [NetHsm] object configured with administrator credentials.
161pub fn create_nethsm(url: Url) -> TestResult<NetHsm> {
162    Ok(NetHsm::new(
163        Connection::new(url, ConnectionSecurity::Unsafe),
164        Some(Credentials::new(
165            ADMIN_USER_ID.parse()?,
166            Some(Passphrase::new(ADMIN_USER_PASSPHRASE.to_string())),
167        )),
168        None,
169        None,
170    )?)
171}
172
173/// Returns a new [NetHsm] object pointing to an unprovisioned NetHSM.
174#[fixture]
175pub async fn unprovisioned_nethsm() -> TestResult<(NetHsm, rustainers::Container<NetHsmImage>)> {
176    let container = create_container().await?;
177
178    Ok((create_nethsm(container.url().await?)?, container))
179}
180
181fn provision_nethsm(nethsm: &NetHsm) -> TestResult {
182    nethsm.provision(
183        Passphrase::new(UNLOCK_PASSPHRASE.to_string()),
184        Passphrase::new(ADMIN_USER_PASSPHRASE.to_string()),
185        Utc::now(),
186    )?;
187    Ok(())
188}
189
190fn add_users_to_nethsm(nethsm: &NetHsm) -> TestResult {
191    let users = [
192        (
193            UserRole::Operator,
194            DEFAULT_OPERATOR_USER_ID,
195            DEFAULT_OPERATOR_USER_PASSPHRASE,
196            DEFAULT_OPERATOR_USER_REAL_NAME,
197        ),
198        (
199            UserRole::Operator,
200            OTHER_OPERATOR_USER_ID,
201            OTHER_OPERATOR_USER_PASSPHRASE,
202            OTHER_OPERATOR_USER_REAL_NAME,
203        ),
204        (
205            UserRole::Operator,
206            ENC_OPERATOR_USER_ID,
207            ENC_OPERATOR_USER_PASSPHRASE,
208            ENC_OPERATOR_USER_REAL_NAME,
209        ),
210        (
211            UserRole::Metrics,
212            METRICS_USER_ID,
213            METRICS_USER_PASSPHRASE,
214            METRICS_USER_REAL_NAME,
215        ),
216        (
217            UserRole::Backup,
218            BACKUP_USER_ID,
219            BACKUP_USER_PASSPHRASE,
220            BACKUP_USER_REAL_NAME,
221        ),
222        (
223            UserRole::Administrator,
224            NAMESPACE1_ADMIN_USER_ID,
225            NAMESPACE1_ADMIN_USER_PASSPHRASE,
226            NAMESPACE1_ADMIN_REAL_NAME,
227        ),
228        (
229            UserRole::Operator,
230            NAMESPACE1_OPERATOR_USER_ID,
231            NAMESPACE1_OPERATOR_USER_PASSPHRASE,
232            NAMESPACE1_OPERATOR_REAL_NAME,
233        ),
234        (
235            UserRole::Administrator,
236            NAMESPACE2_ADMIN_USER_ID,
237            NAMESPACE2_ADMIN_USER_PASSPHRASE,
238            NAMESPACE2_ADMIN_REAL_NAME,
239        ),
240        (
241            UserRole::Operator,
242            NAMESPACE2_OPERATOR_USER_ID,
243            NAMESPACE2_OPERATOR_USER_PASSPHRASE,
244            NAMESPACE2_OPERATOR_REAL_NAME,
245        ),
246    ];
247
248    println!("Adding users to NetHSM...");
249    for (role, user_id, passphrase, real_name) in users.into_iter() {
250        println!("Adding user: {user_id}");
251        nethsm.add_user(
252            real_name.to_string(),
253            role,
254            Passphrase::new(passphrase.to_string()),
255            Some(user_id.parse()?),
256        )?;
257    }
258    println!("users: {:?}", nethsm.get_users()?);
259    println!("Creating namespaces...");
260    for namespace in [NAMESPACE1, NAMESPACE2] {
261        println!("Creating namespace: {namespace}");
262        nethsm.add_namespace(&namespace.parse()?)?;
263    }
264    println!("namespaces: {:?}", nethsm.get_namespaces()?);
265    Ok(())
266}
267
268fn add_keys_to_nethsm(nethsm: &NetHsm) -> TestResult {
269    let keys = [
270        (
271            vec![KeyMechanism::EdDsaSignature],
272            KeyType::Curve25519,
273            None,
274            DEFAULT_KEY_ID,
275            DEFAULT_TAG,
276            DEFAULT_OPERATOR_USER_ID,
277        ),
278        (
279            vec![
280                KeyMechanism::RsaSignaturePkcs1,
281                KeyMechanism::RsaDecryptionPkcs1,
282            ],
283            KeyType::Rsa,
284            Some(DEFAULT_RSA_BITS),
285            OTHER_KEY_ID,
286            OTHER_TAG,
287            OTHER_OPERATOR_USER_ID,
288        ),
289        (
290            vec![
291                KeyMechanism::AesDecryptionCbc,
292                KeyMechanism::AesEncryptionCbc,
293            ],
294            KeyType::Generic,
295            Some(DEFAULT_AES_BITS),
296            ENC_KEY_ID,
297            ENC_TAG,
298            ENC_OPERATOR_USER_ID,
299        ),
300    ];
301
302    println!("Adding keys to NetHSM...");
303    for (mechanisms, key_type, length, key_id, tag, user_id) in keys {
304        let key_id: &KeyId = &key_id.parse()?;
305        nethsm.generate_key(key_type, mechanisms, length, Some((*key_id).clone()), None)?;
306        nethsm.add_key_tag(key_id, tag)?;
307        nethsm.add_user_tag(&user_id.parse()?, tag)?;
308        // skip symmetric keys, as for those we do not have a public key
309        if key_type != KeyType::Generic {
310            nethsm.import_key_certificate(key_id, nethsm.get_public_key(key_id)?.into_bytes())?;
311        }
312    }
313
314    println!("users: {:?}", nethsm.get_users()?);
315    println!("keys: {:?}", nethsm.get_keys(None)?);
316    Ok(())
317}
318
319/// Creates a new [NetHsm] object pointing at a provisioned NetHSM container.
320#[fixture]
321pub async fn provisioned_nethsm() -> TestResult<(NetHsm, Container<NetHsmImage>)> {
322    let container = create_container().await?;
323    let nethsm = create_nethsm(container.url().await?)?;
324    println!("Provisioning container...");
325    provision_nethsm(&nethsm)?;
326
327    Ok((nethsm, container))
328}
329
330/// Creates a new [NetHsm] object pointing at a NetHSM container with users.
331#[fixture]
332pub async fn nethsm_with_users() -> TestResult<(NetHsm, Container<NetHsmImage>)> {
333    let container = create_container().await?;
334    let nethsm = create_nethsm(container.url().await?)?;
335    println!("Provisioning container...");
336    provision_nethsm(&nethsm)?;
337    println!("Adding users to container...");
338    add_users_to_nethsm(&nethsm)?;
339
340    Ok((nethsm, container))
341}
342
343/// Adds users and keys to an already provisioned NetHSM container.
344#[fixture]
345pub async fn nethsm_with_keys(
346    #[future] provisioned_nethsm: TestResult<(NetHsm, Container<NetHsmImage>)>,
347) -> TestResult<(NetHsm, Container<NetHsmImage>)> {
348    let (nethsm, container) = provisioned_nethsm.await?;
349
350    println!("Adding users and keys to container...");
351    add_users_to_nethsm(&nethsm)?;
352    add_keys_to_nethsm(&nethsm)?;
353
354    Ok((nethsm, container))
355}
356
357/// Downloads an update file if it's not already present.
358#[fixture]
359pub fn update_file() -> TestResult<PathBuf> {
360    let file_name = "update.img.bin";
361    let update_link =
362        format!("https://raw.githubusercontent.com/Nitrokey/nethsm-sdk-py/main/tests/{file_name}");
363    let download_dir = PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap_or("/tmp".into()));
364    let file = download_dir.join(file_name);
365
366    if !file.exists() {
367        let mut file_bytes = ureq::get(&update_link).call()?.into_reader();
368        let mut file_writer = File::create(&file)?;
369        std::io::copy(&mut file_bytes, &mut file_writer)?;
370        assert!(file.exists());
371    }
372
373    println!("Update file downloaded: {file:?}");
374    Ok(file)
375}