use crate::constants::MAX_CLIENT_TOKEN_LEN;
use crate::entropy::Entropy;
use crate::error::Error;
const TOKEN_NONCE_BYTES: usize = 8;
#[cfg(test)]
const TOKEN_NONCE_HEX_LEN: usize = TOKEN_NONCE_BYTES * 2;
const MAX_TOKEN_SCOPE_LEN: usize = 64;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Verb {
Create,
Run,
}
impl Verb {
fn as_str(self) -> &'static str {
match self {
Verb::Create => "create",
Verb::Run => "run",
}
}
}
pub fn create_token_with(scope: &str, entropy: &dyn Entropy) -> Result<String, Error> {
Ok(mint(Verb::Create, scope, &nonce_from(entropy)?))
}
pub fn run_token_with(scope: &str, entropy: &dyn Entropy) -> Result<String, Error> {
Ok(mint(Verb::Run, scope, &nonce_from(entropy)?))
}
pub(crate) fn require_run_token(token: &str) -> Result<(), crate::Error> {
if token.is_empty()
|| token.len() > MAX_CLIENT_TOKEN_LEN
|| !token.bytes().all(|byte| byte.is_ascii_graphic())
{
return Err(crate::Error::invalid_arg(format!(
"client_token must contain 1..={MAX_CLIENT_TOKEN_LEN} printable ASCII characters without whitespace",
)));
}
Ok(())
}
pub(crate) fn resolve_log_stream(prefix: &str, entropy: &dyn Entropy) -> Result<String, Error> {
let nonce = nonce_from(entropy)?;
let mut resolved = String::with_capacity(prefix.len() + 1 + nonce.len() * 2);
resolved.push_str(prefix);
resolved.push('/');
resolved.push_str(&const_hex::encode(nonce));
debug_assert!(
resolved.len() <= crate::constants::MAX_LOG_STREAM_LEN,
"resolved a {}-character stream name, over the {} ceiling: {resolved}",
resolved.len(),
crate::constants::MAX_LOG_STREAM_LEN,
);
Ok(resolved)
}
fn mint(verb: Verb, scope: &str, nonce: &[u8]) -> String {
let label = tail(scope, MAX_TOKEN_SCOPE_LEN);
let mut token = String::with_capacity(verb.as_str().len() + 2 + label.len() + nonce.len() * 2);
token.push_str(verb.as_str());
token.push('-');
token.push_str(label);
token.push('-');
token.push_str(&const_hex::encode(nonce));
debug_assert!(
token.len() <= MAX_CLIENT_TOKEN_LEN,
"minted a {}-character token, over the {MAX_CLIENT_TOKEN_LEN} ceiling: {token}",
token.len()
);
token
}
fn tail(text: &str, max_bytes: usize) -> &str {
if text.len() <= max_bytes {
return text;
}
let cut = text.len() - max_bytes;
match text.char_indices().find(|(index, _)| *index >= cut) {
Some((index, _)) => &text[index..],
None => "",
}
}
fn nonce_from(entropy: &dyn Entropy) -> Result<[u8; TOKEN_NONCE_BYTES], Error> {
let mut bytes = [0u8; TOKEN_NONCE_BYTES];
entropy.fill(&mut bytes)?;
Ok(bytes)
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::sync::LazyLock;
use proptest::prelude::*;
use super::*;
use crate::entropy::testing::SequenceEntropy;
static SOURCE: LazyLock<SequenceEntropy> = LazyLock::new(SequenceEntropy::new);
fn create_token(scope: &str) -> String {
create_token_with(scope, &*SOURCE).expect("scripted")
}
fn run_token(scope: &str) -> String {
run_token_with(scope, &*SOURCE).expect("scripted")
}
fn worst_legal_scope() -> String {
format!(
"arn:aws:lambda:ap-northeast-1:123456789012:microvm-image:{}",
"a".repeat(crate::constants::MAX_IMAGE_NAME_LEN)
)
}
#[test]
fn a_token_is_the_verb_the_label_and_sixteen_hex_characters() {
let token = mint(
Verb::Create,
"agentd-conformance",
&[0xde, 0xad, 0xbe, 0xef, 0, 1, 2, 3],
);
assert_eq!(token, "create-agentd-conformance-deadbeef00010203");
let token = mint(Verb::Run, "vm-7", &[0xff; TOKEN_NONCE_BYTES]);
assert_eq!(token, "run-vm-7-ffffffffffffffff");
}
#[test]
fn the_verb_prefix_distinguishes_a_create_from_a_run() {
assert!(create_token("img").starts_with("create-"));
assert!(run_token("img").starts_with("run-"));
}
#[test]
fn two_hundred_create_tokens_for_one_scope_are_two_hundred_distinct_values() {
let scope = "agentd-conformance";
let minted: HashSet<String> = (0..200).map(|_| create_token(scope)).collect();
assert_eq!(minted.len(), 200, "a repeated token is a wedged image");
}
#[test]
fn create_and_run_tokens_never_collide_with_each_other() {
let scope = "arn:aws:lambda:us-east-1:123456789012:microvm-image:img";
let mut minted = HashSet::new();
for _ in 0..200 {
minted.insert(create_token(scope));
minted.insert(run_token(scope));
}
assert_eq!(minted.len(), 400);
}
#[test]
fn the_worst_legal_scope_fits_the_hundred_twenty_eight_character_ceiling() {
let scope = worst_legal_scope();
assert!(
scope.len() > MAX_TOKEN_SCOPE_LEN,
"the point of this scope is that it is longer than the label cap"
);
for token in [create_token(&scope), run_token(&scope)] {
assert!(
token.len() <= MAX_CLIENT_TOKEN_LEN,
"{} characters, over the {MAX_CLIENT_TOKEN_LEN} ceiling: {token}",
token.len()
);
}
}
#[test]
fn the_label_cap_leaves_room_for_the_longest_verb_and_the_whole_nonce() {
let longest_verb = Verb::Create.as_str().len();
let worst = longest_verb + 1 + MAX_TOKEN_SCOPE_LEN + 1 + TOKEN_NONCE_HEX_LEN;
assert!(
worst <= MAX_CLIENT_TOKEN_LEN,
"the worst legal token is {worst} characters against a ceiling of {MAX_CLIENT_TOKEN_LEN}"
);
assert_eq!(TOKEN_NONCE_HEX_LEN, TOKEN_NONCE_BYTES * 2);
}
#[test]
fn truncation_keeps_the_tail_so_two_arns_stay_distinguishable() {
let prefix = "arn:aws:lambda:ap-northeast-1:123456789012:microvm-image:";
let first_scope = format!("{prefix}alpha-conformance-image-name");
let second_scope = format!("{prefix}omega-conformance-image-name");
assert!(
first_scope.len() > MAX_TOKEN_SCOPE_LEN,
"these scopes must actually be truncated or the test proves nothing"
);
let first = mint(Verb::Run, &first_scope, &[0; TOKEN_NONCE_BYTES]);
let second = mint(Verb::Run, &second_scope, &[0; TOKEN_NONCE_BYTES]);
assert_ne!(first, second, "the distinguishing part is at the end");
assert!(first.contains("alpha-conformance-image-name"), "{first}");
assert!(second.contains("omega-conformance-image-name"), "{second}");
assert!(
!first.contains("arn:aws"),
"the shared prefix is what got cut: {first}"
);
}
#[test]
fn the_nonce_is_never_the_part_that_gets_cut() {
let scope = "z".repeat(4096);
let token = mint(Verb::Create, &scope, &[0xab; TOKEN_NONCE_BYTES]);
assert!(
token.ends_with("abababababababab"),
"the whole nonce must survive: {token}"
);
assert_eq!(
token.len(),
"create".len() + 1 + MAX_TOKEN_SCOPE_LEN + 1 + TOKEN_NONCE_HEX_LEN
);
}
#[test]
fn a_multibyte_scope_cuts_on_a_character_boundary_rather_than_panicking() {
for scope in ["日本語".repeat(64), "é".repeat(200), "🔥".repeat(50)] {
let token = create_token(&scope);
assert!(token.len() <= MAX_CLIENT_TOKEN_LEN, "{token}");
assert!(token.is_char_boundary(token.len()));
}
}
#[test]
fn an_empty_scope_still_mints_a_usable_token() {
let token = create_token("");
assert_eq!(token, format!("create--{}", &token[8..]));
assert_eq!(
token.len(),
"create".len() + 2 + TOKEN_NONCE_HEX_LEN,
"the model's clientToken min is 1 and this clears it: {token}"
);
}
proptest! {
#[test]
fn every_scope_mints_a_token_that_fits_and_keeps_its_whole_nonce(scope: String) {
let token = mint(Verb::Create, &scope, &[0x5a; TOKEN_NONCE_BYTES]);
prop_assert!(token.len() <= MAX_CLIENT_TOKEN_LEN, "{}", token);
prop_assert!(token.ends_with("5a5a5a5a5a5a5a5a"), "{}", token);
prop_assert!(token.starts_with("create-"), "{}", token);
}
#[test]
fn no_scope_makes_two_attempts_produce_the_same_token(scope: String) {
prop_assert_ne!(create_token(&scope), create_token(&scope));
prop_assert_ne!(run_token(&scope), run_token(&scope));
}
}
#[test]
fn a_resolved_log_stream_carries_the_prefix_a_slash_and_sixteen_fresh_hex() {
let first = resolve_log_stream("ci-image", &*SOURCE).expect("the pool is available");
let second = resolve_log_stream("ci-image", &*SOURCE).expect("the pool is available");
for resolved in [&first, &second] {
assert!(resolved.starts_with("ci-image/"), "{resolved}");
let suffix = &resolved["ci-image/".len()..];
assert_eq!(suffix.len(), TOKEN_NONCE_HEX_LEN, "{resolved}");
assert!(
suffix.bytes().all(|b| b.is_ascii_hexdigit()),
"the discriminator is hex: {resolved}"
);
assert_ne!(resolved.as_str(), "ci-image", "never the prefix verbatim");
}
assert_ne!(
first, second,
"two creates from one request must resolve to two streams, or concurrent \
builds collapse into one"
);
}
#[test]
fn the_longest_legal_prefix_resolves_to_exactly_the_shapes_ceiling() {
let prefix = "s".repeat(crate::constants::MAX_USER_LOG_STREAM_LEN);
let resolved = resolve_log_stream(&prefix, &*SOURCE).expect("the pool is available");
assert_eq!(resolved.len(), crate::constants::MAX_LOG_STREAM_LEN);
}
#[test]
fn the_nonce_source_does_not_repeat_itself() {
let drawn: HashSet<[u8; TOKEN_NONCE_BYTES]> = (0..200)
.map(|_| nonce_from(&*SOURCE).expect("the pool is available"))
.collect();
assert_eq!(drawn.len(), 200);
assert!(
!drawn.contains(&[0u8; TOKEN_NONCE_BYTES]),
"an all-zero draw is the signature of a read that silently did nothing"
);
}
#[test]
fn the_nonce_is_the_source_s_next_draw() {
let entropy = SequenceEntropy::new();
let hex = |n| const_hex::encode(SequenceEntropy::draw(n, TOKEN_NONCE_BYTES));
assert_eq!(
create_token_with("img", &entropy).expect("scripted"),
format!("create-img-{}", hex(1))
);
assert_eq!(
run_token_with("img", &entropy).expect("scripted"),
format!("run-img-{}", hex(2))
);
assert_eq!(
resolve_log_stream("ci", &entropy).expect("scripted"),
format!("ci/{}", hex(3))
);
}
#[test]
fn an_unavailable_source_refuses_every_token() {
let entropy = SequenceEntropy::unavailable();
assert!(create_token_with("img", &entropy).is_err());
assert!(run_token_with("img", &entropy).is_err());
assert!(resolve_log_stream("ci", &entropy).is_err());
}
}