use crate::{
InternalError, InternalErrorOrigin,
config::schema::DelegatedTokenConfig,
domain::auth::DelegatedAuthNetwork,
ids::BuildNetwork,
ops::{
auth::{AuthOps, PrepareChainKeyRootDelegationBatchInput},
config::ConfigOps,
ic::IcOps,
runtime::{env::EnvOps, metrics::delegated_auth::DelegatedAuthMetrics, timer::TimerId},
},
workflow::{
config::{WORKFLOW_AUTH_RENEWAL_INTERVAL, WORKFLOW_INIT_DELAY},
prelude::*,
runtime::{auth::provisioning, timer::TimerWorkflow},
},
};
use std::{cell::RefCell, time::Duration};
thread_local! {
static RENEWAL_TIMER: RefCell<Option<TimerId>> = const { RefCell::new(None) };
}
const DEFAULT_DELEGATED_TOKEN_MAX_TTL_SECS: u64 = 24 * 60 * 60;
const RENEWAL_INTERVAL: Duration = WORKFLOW_AUTH_RENEWAL_INTERVAL;
pub(super) struct RootDelegationRenewalWorkflow;
impl RootDelegationRenewalWorkflow {
pub(super) fn start_if_configured() -> Result<(), InternalError> {
Self::start_if_configured_after(WORKFLOW_INIT_DELAY)
}
pub(super) fn start_soon_if_configured() -> Result<(), InternalError> {
Self::start_if_configured_after(Duration::ZERO)
}
fn start_if_configured_after(init_delay: Duration) -> Result<(), InternalError> {
if !EnvOps::is_root() {
return Ok(());
}
if !AuthOps::has_enabled_root_issuer_renewal_templates() {
return Ok(());
}
let config = ConfigOps::delegated_tokens_config()?;
if !config.enabled {
log!(
Topic::Auth,
Warn,
"root delegated-proof renewal timer skipped: delegated-token auth is disabled"
);
return Ok(());
}
require_chain_key_root_proof_mode(&config)?;
let _ = TimerWorkflow::set_guarded_interval(
&RENEWAL_TIMER,
init_delay,
"auth_renewal:init",
|| async {
let _ = Self::sweep().await;
},
RENEWAL_INTERVAL,
"auth_renewal:interval",
|| async {
let _ = Self::sweep().await;
},
);
Ok(())
}
pub(super) async fn sweep() -> Result<bool, InternalError> {
if !AuthOps::has_enabled_root_issuer_renewal_templates() {
return Ok(false);
}
let config = ConfigOps::delegated_tokens_config()?;
if !config.enabled {
return Ok(false);
}
require_chain_key_root_proof_mode(&config)?;
let build_network = build_network_from_delegated_auth_config(&config)?;
let min_accepted_proof_epoch = chain_key_min_accepted_proof_epoch(&config)?;
let max_cert_ttl_ns = delegated_token_max_ttl_ns()?;
let now_ns = IcOps::now_nanos();
DelegatedAuthMetrics::record_renewal_sweep_started();
let prepared = match AuthOps::prepare_due_chain_key_root_delegation_batch(
PrepareChainKeyRootDelegationBatchInput {
build_network,
max_cert_ttl_ns,
min_accepted_proof_epoch,
now_ns,
},
) {
Ok(result) => result,
Err(err) => {
DelegatedAuthMetrics::record_renewal_sweep_failed();
return Err(err);
}
};
let signed =
match AuthOps::sign_next_chain_key_root_delegation_batch(build_network, now_ns).await {
Ok(result) => result,
Err(err) => {
DelegatedAuthMetrics::record_renewal_sweep_failed();
return Err(err);
}
};
let installed = match AuthOps::start_next_chain_key_root_delegation_batch_install(now_ns)? {
Some(request) => {
provisioning::install_chain_key_delegation_proof_batch(request, now_ns).await?
}
None => false,
};
DelegatedAuthMetrics::record_renewal_sweep_completed();
if let Some(batch_id) = prepared.batch_id {
log!(
Topic::Auth,
Info,
"root chain-key delegated-proof renewal prepared batch_id={:?} issuers={} skipped={}",
batch_id,
prepared.prepared_issuers,
prepared.skipped_templates
);
}
Ok(prepared.batch_id.is_some() || signed.signed || signed.reused_signed || installed)
}
}
fn require_chain_key_root_proof_mode(config: &DelegatedTokenConfig) -> Result<(), InternalError> {
if config.root_proof_mode.trim() == "chain_key_batch" {
return Ok(());
}
Err(InternalError::invariant(
InternalErrorOrigin::Workflow,
"0.76 delegated-auth renewal requires root_proof_mode=\"chain_key_batch\"",
))
}
fn build_network_from_delegated_auth_config(
config: &DelegatedTokenConfig,
) -> Result<BuildNetwork, InternalError> {
let network = DelegatedAuthNetwork::parse(config.network.trim()).ok_or_else(|| {
InternalError::invalid_input(
"auth.delegated_tokens.network must be one of mainnet, local, pocketic, testnet",
)
})?;
if network.is_mainnet() {
Ok(BuildNetwork::Ic)
} else {
Ok(BuildNetwork::Local)
}
}
fn chain_key_min_accepted_proof_epoch(config: &DelegatedTokenConfig) -> Result<u64, InternalError> {
config
.chain_key_root_proof
.min_accepted_proof_epoch
.ok_or_else(|| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
"auth.delegated_tokens.chain_key_root_proof.min_accepted_proof_epoch is required for chain-key renewal",
)
})
}
fn delegated_token_max_ttl_ns() -> Result<u64, InternalError> {
let cfg = ConfigOps::delegated_tokens_config()?;
let max_ttl_secs = cfg
.max_ttl_secs
.unwrap_or(DEFAULT_DELEGATED_TOKEN_MAX_TTL_SECS);
max_ttl_secs.checked_mul(1_000_000_000).ok_or_else(|| {
InternalError::invalid_input("auth.delegated_tokens.max_ttl_secs overflows nanoseconds")
})
}