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
141
142
143
144
145
146
147
148
149
150
//! `acme-proxy nonce` — count the replay-nonce table, and sweep it by hand.
//!
//! The server already sweeps it on an interval; these exist to debug a table
//! that is growing. Nonce values are bearer credentials and are never listed.
use std::io::BufRead;
use std::sync::Arc;
use std::time::Duration;
use clap::Subcommand;
use crate::cli::CliError;
use acme_proxy_admin::admin;
use acme_proxy_core::config::Config;
use acme_proxy_jobs::auditor::admin as audit_admin;
use acme_proxy_store::db::Database;
use acme_proxy_store::nonce::Nonce;
#[derive(Subcommand)]
pub enum NonceCommand {
/// Delete nonces older than the TTL.
Cleanup {
/// The age past which a nonce is deleted. Defaults to `nonce.ttl_seconds`.
#[arg(long = "ttl-seconds")]
ttl_seconds: Option<u64>,
},
/// Print how many nonces the table holds, and the window they are fresh for.
Count {
/// Print it as JSON.
#[arg(long)]
json: bool,
},
}
pub async fn run_nonce_command(
command: NonceCommand,
yes: bool,
reader: &mut impl BufRead,
config: &Config,
database: Arc<Database>,
) -> Result<(), CliError> {
match command {
NonceCommand::Cleanup { ttl_seconds } => {
let ttl = Duration::from_secs(ttl_seconds.unwrap_or(config.nonce.ttl_seconds));
match admin::confirm_cleanup_nonces(ttl, yes, reader, database.clone()).await? {
None => println!("Cancelled."),
Some(removed) => {
// Only when it actually removed something, the rule
// `audit cleanup` already follows: a sweep that changed
// nothing is not an administrative action worth a row.
if removed > 0 {
audit_admin::record_cli_action(&database, |actor, client| {
audit_admin::nonce_cleanup_completed(actor, client, removed)
})
.await;
}
println!("Removed {removed} nonce(s).");
}
}
}
NonceCommand::Count { json } => {
// No `Palette`: a count is data, and the only thing here that could
// be painted -- "the reaper is not running" -- is a judgement this
// command deliberately leaves to the operator reading the two
// numbers together.
let count = Nonce::count(&database).await?;
let ttl = config.nonce.ttl_seconds;
if json {
println!("{}", admin::render_nonce_stats_json(count, ttl));
} else {
println!("{count} nonce(s), ttl {ttl}s.");
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
/// Both shapes, and the pair the count is only meaningful as: the number on
/// its own says nothing without the window it is a count over.
#[tokio::test]
async fn count_reports_the_table_and_the_configured_ttl() {
let database = Arc::new(Database::connect_in_memory().await.unwrap());
let mut config = Config::default();
config.nonce.ttl_seconds = 42;
for _ in 0..3 {
Nonce::new().save(&database).await.unwrap();
}
assert_eq!(Nonce::count(&database).await.unwrap(), 3);
assert_eq!(
admin::render_nonce_stats_json(3, config.nonce.ttl_seconds),
serde_json::json!({ "count": 3, "ttlSeconds": 42 }),
);
for json in [false, true] {
let mut reader: &[u8] = &[];
run_nonce_command(
NonceCommand::Count { json },
true,
&mut reader,
&config,
database.clone(),
)
.await
.unwrap();
}
}
/// Omitting `--ttl-seconds` falls back to `nonce.ttl_seconds`, and a
/// declined confirmation sweeps nothing without being a failure.
#[tokio::test]
async fn cleanup_honours_the_configured_ttl_and_the_prompt() {
let database = Arc::new(
acme_proxy_store::db::Database::connect_in_memory()
.await
.unwrap(),
);
let mut config = Config::default();
config.nonce.ttl_seconds = 1;
let mut declined: &[u8] = b"n\n";
run_nonce_command(
NonceCommand::Cleanup { ttl_seconds: None },
false,
&mut declined,
&config,
database.clone(),
)
.await
.unwrap();
let mut reader: &[u8] = &[];
run_nonce_command(
NonceCommand::Cleanup {
ttl_seconds: Some(60),
},
true,
&mut reader,
&config,
database,
)
.await
.unwrap();
}
}