-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial audit log endpoints, data model, tests
- Loading branch information
1 parent
d9a6a8b
commit ec3d782
Showing
26 changed files
with
1,058 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at https://mozilla.org/MPL/5.0/. | ||
|
||
// Copyright 2025 Oxide Computer Company | ||
|
||
use crate::schema::audit_log; | ||
use chrono::{DateTime, TimeDelta, Utc}; | ||
use diesel::prelude::*; | ||
use nexus_types::external_api::views; | ||
use uuid::Uuid; | ||
|
||
#[derive(Queryable, Insertable, Selectable, Clone, Debug)] | ||
#[diesel(table_name = audit_log)] | ||
pub struct AuditLogEntry { | ||
pub id: Uuid, | ||
pub timestamp: DateTime<Utc>, | ||
pub request_id: String, | ||
// TODO: this isn't in the RFD but it seems nice to have | ||
pub request_uri: String, | ||
pub operation_id: String, | ||
pub source_ip: String, | ||
pub resource_type: String, | ||
|
||
// TODO: we probably want a dedicated enum for these columns and for that | ||
// we need a fancier set of columns. For example, we may want to initialize | ||
// the row with a _potential_ actor (probably a different field), like the | ||
// username or whatever is being used for login. This should probably be | ||
// preserved even after authentication determines an actual actor ID. See | ||
// the Actor struct in nexus/auth/src/authn/mod.ts | ||
|
||
// these are optional because of requests like login attempts, where there | ||
// is no actor until after the operation. | ||
pub actor_id: Option<Uuid>, | ||
pub actor_silo_id: Option<Uuid>, | ||
|
||
/// The specific action that was attempted (create, delete, update, etc) | ||
pub action: String, // TODO: enum type? | ||
|
||
// TODO: we will need to add headers in the client to get this info | ||
// How the actor authenticated (api_key, console, etc) | ||
// pub access_method: String, | ||
|
||
// TODO: RFD 523 says: "Additionally, the response (or error) data should be | ||
// included in the same log entry as the original request data. Separating | ||
// the response from the request into two different log entries is extremely | ||
// expensive for customers to identify which requests correspond to which | ||
// responses." I guess the typical thing is to include a duration of the | ||
// request rather than a second timestamp. | ||
|
||
// Seems like it has to be optional because at the beginning of the | ||
// operation, we have not yet resolved the resource selector to an ID | ||
pub resource_id: Option<Uuid>, | ||
|
||
// Fields that are optional because they get filled in after the action completes | ||
/// Time in milliseconds between receiving request and responding | ||
pub duration: Option<TimeDelta>, | ||
|
||
// Error information if the action failed | ||
pub error_code: Option<String>, | ||
pub error_message: Option<String>, | ||
// TODO: including a real response complicates things | ||
// Response data on success (if applicable) | ||
// pub success_response: Option<Value>, | ||
} | ||
|
||
impl AuditLogEntry { | ||
pub fn new( | ||
request_id: String, | ||
operation_id: String, | ||
request_uri: String, | ||
actor_id: Option<Uuid>, | ||
actor_silo_id: Option<Uuid>, | ||
) -> Self { | ||
Self { | ||
id: Uuid::new_v4(), | ||
timestamp: Utc::now(), | ||
request_id, | ||
request_uri, | ||
operation_id, | ||
actor_id, | ||
actor_silo_id, | ||
|
||
// TODO: actually get all these values | ||
source_ip: String::new(), | ||
resource_type: String::new(), | ||
action: String::new(), | ||
|
||
// fields that can only be filled in after the operation | ||
resource_id: None, | ||
duration: None, | ||
error_code: None, | ||
error_message: None, | ||
} | ||
} | ||
} | ||
|
||
// TODO: Add a struct representing only the fields set at log entry init time, | ||
// use as an arg to the datastore init function to make misuse harder | ||
|
||
// TODO: AuditLogActor | ||
// pub enum AuditLogActor { | ||
// UserBuiltin { user_builtin_id: Uuid }, | ||
// TODO: include info about computed roles at runtime? | ||
// SiloUser { silo_user_id: Uuid, silo_id: Uuid }, | ||
// Unauthenticated, | ||
// } | ||
|
||
impl From<AuditLogEntry> for views::AuditLogEntry { | ||
fn from(entry: AuditLogEntry) -> Self { | ||
Self { | ||
id: entry.id, | ||
timestamp: entry.timestamp, | ||
request_id: entry.request_id, | ||
request_uri: entry.request_uri, | ||
operation_id: entry.operation_id, | ||
source_ip: entry.source_ip, | ||
resource_type: entry.resource_type, | ||
resource_id: entry.resource_id, | ||
actor_id: entry.actor_id, | ||
actor_silo_id: entry.actor_silo_id, | ||
action: entry.action, | ||
duration_ms: entry.duration.map(|d| d.num_milliseconds()), | ||
error_code: entry.error_code, | ||
error_message: entry.error_message, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.