Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions crates/agent-identity-conformance/src/dvls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ pub(crate) struct CertificateRecord {
serial_number: Option<String>,
#[serde(rename = "validFrom", deserialize_with = "timestamp")]
pub(crate) not_before: OffsetDateTime,
#[serde(rename = "validUntil", deserialize_with = "timestamp")]
#[serde(rename = "validTo", deserialize_with = "timestamp")]
pub(crate) not_after: OffsetDateTime,
/// The issuing root's thumbprint.
#[serde(rename = "issuerThumbprint")]
Expand All @@ -447,7 +447,7 @@ pub(crate) struct CertificateRecord {
/// The current certificate, as an identity's summary shows it.
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub(crate) struct CurrentCertificate {
#[serde(rename = "validUntil", deserialize_with = "timestamp")]
#[serde(rename = "validTo", deserialize_with = "timestamp")]
pub(crate) not_after: OffsetDateTime,
/// The issuing root's thumbprint.
#[serde(rename = "issuerThumbprint")]
Expand Down Expand Up @@ -670,7 +670,7 @@ pub(crate) struct RootSummary {
#[serde(default, deserialize_with = "optional_timestamp")]
valid_from: Option<OffsetDateTime>,
#[serde(default, deserialize_with = "optional_timestamp")]
valid_until: Option<OffsetDateTime>,
valid_to: Option<OffsetDateTime>,
}

#[derive(Clone, Debug, PartialEq, Deserialize)]
Expand Down Expand Up @@ -1209,7 +1209,7 @@ mod tests {
"enrollmentTokenName": "test",
"createdOn": "2026-09-25T12:00:00Z",
"isRenewalRequested": false,
"certificate": { "issuerThumbprint": "root-id", "validUntil": "2026-12-24T12:00:00Z" }
"certificate": { "issuerThumbprint": "root-id", "validTo": "2026-12-24T12:00:00Z" }
})
}

Expand Down
6 changes: 3 additions & 3 deletions crates/agent-identity-mock/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Each operation requires the permission the table lists, and a session without it
Enums are PascalCase.
An identity's `status` is `Active` (an unexpired current or pending certificate), `Grace` (no unexpired certificate, but one within renewal grace), `Expired` (beyond renewal grace) or `Revoked`, as the DVLS intent's "Terminology" defines.
A certificate's `status` is `Pending`, `Current` or `Retired`, a token's `status` is `Active`, `Exhausted`, `Expired` or `Revoked` (each winning over those before it), and the rotation's `phase` is `Idle` or `Rotating`.
Timestamps are RFC 3339 UTC with `Z` and named `*On`; certificate and root validity is `validFrom` and `validUntil`.
Timestamps are RFC 3339 UTC with `Z` and named `*On`; certificate and root validity is `validFrom` and `validTo`.
A date-time parameter is UTC unless it carries an offset, and keeps its fraction of a second: token expiries, rotation deadlines and last-seen filters compare and return it exactly.
List operations take `pageNumber` (default 1), `pageSize` (default 25, at most 100 identities or 500 tokens), `sortOrder` (`1` ascending by default, `0` descending) and `sortField`, and return `{ currentPage, data, pageSize, totalCount, totalPage }`.
A page past the last is 400, and page 1 of an empty list is its last page.
Expand All @@ -134,14 +134,14 @@ An ID that is not a GUID is 404, like an unknown one.
| GET | `/api/v1/agent-identity/enrollment-token` | View | Page of every token, revoked ones included; `sortField` is `createdOn` (default), `name` or `expiresOn`, with ties in creation order. |
| GET | `/api/v1/agent-identity/enrollment-token/{id}` | View | `AgentIdentityEnrollmentToken`, without the full token; 404 when unknown. |
| POST | `/api/v1/agent-identity/enrollment-token/{id}/revoke` | Manage | 204, idempotent, or 404 when unknown. The token stays listed and readable, with `status` `Revoked`. It can still replay its own existing current key without side effects, while a new key gets `token_invalid`. |
| GET | `/api/v1/agent-identity` | View | Page of identities in creation order (`sortField` is `createdOn` only). Query: `view=Summary\|Full`, repeated `metadataKey`, repeated `status`, repeated `enrollmentTokenId`, `issuerThumbprint` (the current certificate's issuer), `lastSeenFrom` and `lastSeenTo` (inclusive), and `search` (case-insensitive friendly-name substring); filters combine with AND. The Summary view carries `id`, `friendlyName`, `status`, `isConnected`, `lastSeenOn`, `certificate { issuerThumbprint, validUntil }` while the identity has a current certificate, and the requested `metadata` keys, omitted when none match. |
| GET | `/api/v1/agent-identity` | View | Page of identities in creation order (`sortField` is `createdOn` only). Query: `view=Summary\|Full`, repeated `metadataKey`, repeated `status`, repeated `enrollmentTokenId`, `issuerThumbprint` (the current certificate's issuer), `lastSeenFrom` and `lastSeenTo` (inclusive), and `search` (case-insensitive friendly-name substring); filters combine with AND. The Summary view carries `id`, `friendlyName`, `status`, `isConnected`, `lastSeenOn`, `certificate { issuerThumbprint, validTo }` while the identity has a current certificate, and the requested `metadata` keys, omitted when none match. |
| GET | `/api/v1/agent-identity/{id}` | View | Full view: the Summary members, all `metadata`, `certificates` (newest first), `createdOn`, `enrollmentTokenId`, `enrollmentTokenName`, `isRenewalRequested`, and `revokedOn` once revoked. |
| PATCH | `/api/v1/agent-identity/{id}` | Manage | `{ friendlyName? }` (1..255 scalar values without leading or trailing white space); absent or `null` changes nothing; 200 → Full view. |
| POST | `/api/v1/agent-identity/{id}/revoke` | Revoke | 204, idempotent; closes live streams PERMISSION_DENIED + `identity_revoked`. |
| DELETE | `/api/v1/agent-identity/{id}` | Manage | 204, or 409 unless the identity is revoked. |
| POST | `/api/v1/agent-identity/{id}/request-renewal` | Manage | 202; sets the flag and pushes `RenewRequested{reason:"admin"}` to live streams; 409 when the identity is revoked. |
| POST | `/api/v1/agent-identity/rotation` | Rotate root | Body `{ deadlineOn? }`, `{}` for the rotation maximum: the latest `notAfter` among the old root's unexpired current or pending certificates of non-revoked identities, or now when there are none. A deadline at or before now is an emergency rotation. 202 → `AgentIdentityRotation`; 409 while rotating, without a root, or with `root_key_unavailable`; 422 for a deadline after the rotation maximum. |
| GET | `/api/v1/agent-identity/rotation` | View | `{ phase, startedOn?, deadlineOn?, oldRoot?, newRoot?, oldRootActiveIdentityCount }`, each root `{ thumbprint, validFrom, validUntil }`; the count includes each non-revoked identity with an unexpired current or pending old-root certificate, and is 0 while `Idle`. |
| GET | `/api/v1/agent-identity/rotation` | View | `{ phase, startedOn?, deadlineOn?, oldRoot?, newRoot?, oldRootActiveIdentityCount }`, each root `{ thumbprint, validFrom, validTo }`; the count includes each non-revoked identity with an unexpired current or pending old-root certificate, and is 0 while `Idle`. |
| GET | `/api/v1/agent-identity/settings` | View | `{ authorityId, isAgentChannelEnabled, isAgentChannelAvailable, certificateLifetimeDays, rotationNotificationsPerSecond }`, by default `true`, `true`, `90` and `10`. |
| PATCH | `/api/v1/agent-identity/settings` | Manage | `{ isAgentChannelEnabled?, certificateLifetimeDays? (1..365), rotationNotificationsPerSecond? (1..1000) }` → settings. An `isAgentChannelEnabled` that would grow a config past one channel envelope is 400 on that member, and nothing changes. |

Expand Down
6 changes: 3 additions & 3 deletions crates/agent-identity-mock/src/api_admin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ fn identity_view(state: &State, identity: &Identity, now: i64) -> Value {
if let Some(current) = identity.current_cert() {
view["certificate"] = json!({
"issuerThumbprint": current.issuer,
"validUntil": rfc3339(current.not_after),
"validTo": rfc3339(current.not_after),
});
}
view
Expand Down Expand Up @@ -696,7 +696,7 @@ fn certificate_view(cert: &Cert) -> Value {
},
"thumbprint": cert.thumbprint,
"validFrom": rfc3339(cert.not_before),
"validUntil": rfc3339(cert.not_after),
"validTo": rfc3339(cert.not_after),
})
}

Expand Down Expand Up @@ -875,7 +875,7 @@ fn rotation_view(state: &State, now: i64) -> Value {
json!({
"thumbprint": root.thumbprint,
"validFrom": rfc3339(root.not_before),
"validUntil": rfc3339(root.not_after),
"validTo": rfc3339(root.not_after),
})
})
};
Expand Down
16 changes: 8 additions & 8 deletions crates/agent-identity-mock/src/api_admin/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ async fn token_has_no_delete_operation() -> anyhow::Result<()> {

// ==== Identities ====

/// The `(thumbprint, serialNumber, validFrom, validUntil)` of a DER certificate, as `AgentIdentityCertificate` defines
/// The `(thumbprint, serialNumber, validFrom, validTo)` of a DER certificate, as `AgentIdentityCertificate` defines
/// them: the thumbprint is the base64url of the SHA-256 of the DER encoding, and the serial number is hexadecimal.
fn certificate_fields(der: &[u8]) -> anyhow::Result<(String, String, String, String)> {
use der::Decode as _;
Expand Down Expand Up @@ -1006,10 +1006,10 @@ fn certificate_fields(der: &[u8]) -> anyhow::Result<(String, String, String, Str
async fn get_identity_returns_the_full_view() -> anyhow::Result<()> {
let (app, now) = frozen_app()?;
let (id, chain) = enroll_with_chain(&app, json!({ "hostname": "host-a" })).await?;
let (thumbprint, serial, valid_from, valid_until) = certificate_fields(&chain[0])?;
let (thumbprint, serial, valid_from, valid_to) = certificate_fields(&chain[0])?;
let (root, ..) = certificate_fields(&chain[1])?;
// The certificate follows the default 90-day lifetime.
assert_eq!((&valid_from, &valid_until), (&rfc3339(now), &rfc3339(now + 90 * DAY)));
assert_eq!((&valid_from, &valid_to), (&rfc3339(now), &rfc3339(now + 90 * DAY)));
let token_id = app.state.lock().await.identities[&id].token_id;

let (status, view) = call(&app, Method::GET, &format!("/{id}"), None).await?;
Expand All @@ -1021,15 +1021,15 @@ async fn get_identity_returns_the_full_view() -> anyhow::Result<()> {
"status": "Active",
"isConnected": false,
"lastSeenOn": rfc3339(now),
"certificate": { "issuerThumbprint": root, "validUntil": valid_until },
"certificate": { "issuerThumbprint": root, "validTo": valid_to },
"metadata": { "hostname": "host-a" },
"certificates": [{
"issuerThumbprint": root,
"serialNumber": serial,
"status": "Current",
"thumbprint": thumbprint,
"validFrom": valid_from,
"validUntil": valid_until,
"validTo": valid_to,
}],
"createdOn": rfc3339(now),
"enrollmentTokenId": token_id,
Expand Down Expand Up @@ -1435,7 +1435,7 @@ async fn rotation_defaults_to_the_rotation_maximum() -> anyhow::Result<()> {
json!({
"thumbprint": root.thumbprint,
"validFrom": rfc3339(root.not_before),
"validUntil": rfc3339(root.not_after),
"validTo": rfc3339(root.not_after),
})
};
let new_root = state.issuing_root().thumbprint.clone();
Expand Down Expand Up @@ -1654,7 +1654,7 @@ async fn certificate_lifetime_applies_to_new_certificates() -> anyhow::Result<()
assert_eq!((status, &settings["certificateLifetimeDays"]), (200, &json!(7)));
let id = enroll(&app, json!({})).await?;
let (_, view) = call(&app, Method::GET, &format!("/{id}"), None).await?;
assert_eq!(view["certificate"]["validUntil"], rfc3339(now + 7 * DAY));
assert_eq!(view["certificate"]["validTo"], rfc3339(now + 7 * DAY));
Ok(())
}

Expand All @@ -1666,7 +1666,7 @@ async fn leaf_lifetime_fault_overrides_the_setting() -> anyhow::Result<()> {
app.state.lock().await.faults.leaf_lifetime_secs = Some(60);
let id = enroll(&app, json!({})).await?;
let (_, view) = call(&app, Method::GET, &format!("/{id}"), None).await?;
assert_eq!(view["certificate"]["validUntil"], rfc3339(now + 60));
assert_eq!(view["certificate"]["validTo"], rfc3339(now + 60));
Ok(())
}

Expand Down
Loading