-
-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathlib.rs
More file actions
140 lines (124 loc) · 5.46 KB
/
Copy pathlib.rs
File metadata and controls
140 lines (124 loc) · 5.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! API wrapper for accessing krb5-provision-keytab binary
use std::{
path::{Path, PathBuf},
process::Stdio,
};
use serde::{Deserialize, Serialize};
use snafu::{ResultExt, Snafu};
use stackable_secret_operator_utils::crd::SecretReference;
use tokio::{io::AsyncWriteExt, process::Command};
#[derive(Serialize, Deserialize)]
pub struct Request {
pub admin_keytab_path: PathBuf,
pub admin_principal_name: String,
pub pod_keytab_path: PathBuf,
pub principals: Vec<PrincipalRequest>,
pub admin_backend: AdminBackend,
}
#[derive(Serialize, Deserialize)]
pub struct PrincipalRequest {
pub name: String,
}
#[derive(Serialize, Deserialize)]
pub enum AdminBackend {
Mit,
ActiveDirectory {
ldap_server: String,
ldap_tls_ca_secret: SecretReference,
password_cache_secret: SecretReference,
user_distinguished_name: String,
schema_distinguished_name: String,
generate_sam_account_name: Option<ActiveDirectorySamAccountNameRules>,
},
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ActiveDirectorySamAccountNameRules {
pub prefix: String,
pub total_length: u8,
}
#[derive(Serialize, Deserialize)]
pub struct Response {}
#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("failed to serialize request"))]
SerializeRequest { source: serde_json::Error },
#[snafu(display("failed to deserialize response"))]
DeserializeResponse { source: serde_json::Error },
#[snafu(display("failed to start provisioner"))]
SpawnProvisioner { source: std::io::Error },
#[snafu(display("error waiting for provisioner to exit"))]
WaitProvisioner { source: std::io::Error },
#[snafu(display("failed to provision keytab: {msg}"))]
RunProvisioner { msg: String },
#[snafu(display("failed to write request"))]
WriteRequest { source: std::io::Error },
}
/// Provisions a Kerberos Keytab based on the [`Request`].
///
/// This function assumes that the binary produced by this crate is on the `$PATH`, and will fail otherwise.
pub async fn provision_keytab_file(
krb5_config_path: &Path,
req: &Request,
) -> Result<Response, Error> {
let req_str = serde_json::to_vec(&req).context(SerializeRequestSnafu)?;
let mut child = Command::new("stackable-krb5-provision-keytab")
// make sure the process is killed if we error out of this fn somewhere due to
// an error when writing to stdin or getting stdout
// Usually we'd expect the process to terminate on its own, this is a fail safe to ensure
// it gets killed in case it hangs for some reason.
.kill_on_drop(true)
.env("KRB5_CONFIG", krb5_config_path)
// ldap3 uses the default client keytab to authenticate to the LDAP server
.env("KRB5_CLIENT_KTNAME", &req.admin_keytab_path)
// avoid leaking credentials between secret volumes/secretclasses by only storing the
// TGT that is obtained for the operation in the memory of the short lives process
// spawned by `Command::new` above - this way it'll be wiped from memory once this exits
// With any shared or persistent ticket cache this might stick around and potentially be
// reused by other volumes (which could cause privilege escalations and similar fun issues)
.env("KRB5CCNAME", "MEMORY:")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.context(SpawnProvisionerSnafu)?;
// Get a `ChildStdin` object for the spawned process and write the serialized request
// for a Principal into it in order for the child process to deserialize it and
// process the request
let mut stdin = child
.stdin
.take()
// a failure here has some fundamental reason like us taking ownership of the pipe
// earlier, which is most probably more a coding than a runtime error - this is
// why this error is not handled here, but we panic instead
.expect(
"Failed to take ownership of stdin pipe of stackable-krb5-provision-keytab command! ",
);
stdin.write_all(&req_str).await.context(WriteRequestSnafu)?;
stdin.flush().await.context(WriteRequestSnafu)?;
drop(stdin);
// Wait for the process to finish and capture output
// This will always return Ok(...) regardless of exit code or output of the child process
// Failure here means that something went wrong with connecting to the process or obtaining
// exit code or output
let output = child
.wait_with_output()
.await
.context(WaitProvisionerSnafu)?;
// Check if the spawned command returned 0 as return code
if output.status.success() {
// Check for success of the operation by deserializing stdout of the process to a `Response`
// struct - since `Response` is an empty struct with no fields this effectively means that
// any output will fail to deserialize and cause an `Error::RunProvisioner` to be propagated
// with the output of the child process
serde_json::from_slice::<Result<Response, String>>(&output.stdout)
.context(DeserializeResponseSnafu)?
.map_err(|msg| Error::RunProvisioner { msg })
} else {
Err(Error::RunProvisioner {
msg: format!(
"Got non zero return code {:?} from stackable-krb5-provision-keytab: {:?}",
output.status.code(),
String::from_utf8(output.stderr).unwrap_or_else(|_| "<invalid utf-8>".to_owned()),
),
})
}
}