Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions nexus/db-model/src/silo_auth_settings.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::SqlU32;
use chrono::{DateTime, Utc};
use nexus_db_schema::schema::silo_auth_settings;
use nexus_types::external_api::{params, views};
Expand All @@ -22,7 +23,7 @@ pub struct SiloAuthSettings {

/// Max token lifetime in seconds. Null means no max: users can create
/// tokens that never expire.
pub device_token_max_ttl_seconds: Option<i64>,
pub device_token_max_ttl_seconds: Option<SqlU32>,
}

impl SiloAuthSettings {
Expand All @@ -41,7 +42,8 @@ impl From<SiloAuthSettings> for views::SiloAuthSettings {
Self {
silo_id: silo_auth_settings.silo_id,
device_token_max_ttl_seconds: silo_auth_settings
.device_token_max_ttl_seconds,
.device_token_max_ttl_seconds
.map(|ttl| ttl.0),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion nexus/db-queries/src/db/datastore/silo_auth_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl DataStore {
.map(|_| ())
}

pub async fn silo_auth_settings_delete(
pub(crate) async fn silo_auth_settings_delete(
&self,
opctx: &OpContext,
conn: &async_bb8_diesel::Connection<DbConnection>,
Expand Down
2 changes: 1 addition & 1 deletion nexus/src/app/device_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl super::Nexus {
// build a way for the user to ask for a different TTL
silo_auth_settings
.device_token_max_ttl_seconds
.map(|ttl| Utc::now() + Duration::seconds(ttl)),
.map(|ttl| Utc::now() + Duration::seconds(ttl.0.into())),
);

if db_request.time_expires < Utc::now() {
Expand Down
5 changes: 2 additions & 3 deletions nexus/src/app/silo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,18 +226,17 @@ impl super::Nexus {
Ok(shared::Policy { role_assignments })
}

pub(crate) async fn silo_fetch_settings(
pub(crate) async fn silo_fetch_auth_settings(
&self,
opctx: &OpContext,
silo_lookup: &lookup::Silo<'_>,
) -> LookupResult<SiloAuthSettings> {
// TODO: can everyone view this on their own silo? why not, right?
let (.., authz_silo) =
silo_lookup.lookup_for(authz::Action::Read).await?;
self.db_datastore.silo_auth_settings_view(opctx, &authz_silo).await
}

pub(crate) async fn silo_update_settings(
pub(crate) async fn silo_update_auth_settings(
&self,
opctx: &OpContext,
silo_lookup: &lookup::Silo<'_>,
Expand Down
4 changes: 2 additions & 2 deletions nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ impl NexusExternalApi for NexusExternalApiImpl {

let silo_lookup = nexus.silo_lookup(&opctx, silo)?;
let settings =
nexus.silo_fetch_settings(&opctx, &silo_lookup).await?;
nexus.silo_fetch_auth_settings(&opctx, &silo_lookup).await?;
Ok(HttpResponseOk(settings.into()))
};
apictx
Expand Down Expand Up @@ -274,7 +274,7 @@ impl NexusExternalApi for NexusExternalApiImpl {
.into();
let silo_lookup = nexus.silo_lookup(&opctx, silo)?;
let settings = nexus
.silo_update_settings(&opctx, &silo_lookup, &new_settings)
.silo_update_auth_settings(&opctx, &silo_lookup, &new_settings)
.await?;
Ok(HttpResponseOk(settings.into()))
};
Expand Down
15 changes: 10 additions & 5 deletions nexus/tests/integration_tests/device_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ async fn test_device_auth_flow(cptestctx: &ControlPlaneTestContext) {
assert_eq!(token.access_token.len(), 52);
assert!(token.access_token.starts_with("oxide-token-"));

// now make a request with the token. it 403s because unpriv user has no roles
// now make a request with the token. it 403s because unpriv user has no
// roles
project_list(&testctx, &token.access_token, StatusCode::FORBIDDEN)
.await
.expect("projects list should 403 with no roles");
Expand Down Expand Up @@ -275,7 +276,8 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
StatusCode::BAD_REQUEST,
)
.await;
let msg = "unable to parse JSON body: device_token_max_ttl_seconds: invalid value";
let msg = "unable to parse JSON body: \
device_token_max_ttl_seconds: invalid value";
assert!(error.message.starts_with(&msg));
}
for value in [-3, 0] {
Expand All @@ -286,7 +288,8 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
StatusCode::BAD_REQUEST,
)
.await;
let msg = "unable to parse JSON body: device_token_max_ttl_seconds: invalid value";
let msg = "unable to parse JSON body: \
device_token_max_ttl_seconds: invalid value";
assert!(error.message.starts_with(&msg));
}

Expand All @@ -298,7 +301,9 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
StatusCode::BAD_REQUEST,
)
.await;
assert!(error.message.starts_with("unable to parse JSON body: missing field `device_token_max_ttl_seconds`"));
let msg = "unable to parse JSON body: \
missing field `device_token_max_ttl_seconds`";
assert!(error.message.starts_with(&msg));

// set token expiration on silo to 3 seconds
let settings: views::SiloAuthSettings = object_put(
Expand Down Expand Up @@ -333,7 +338,7 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) {
.await
.expect("expiring token should fail after expiration");

// original token should still work (it was created before the expiration setting)
// original token should still work (created before the expiration setting)
project_list(&testctx, &initial_token, StatusCode::OK)
.await
.expect("initial token should still work");
Expand Down
1 change: 0 additions & 1 deletion nexus/types/src/external_api/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ pub struct SiloQuotasUpdate {
pub struct SiloAuthSettingsUpdate {
/// Maximum lifetime of a device token in seconds. If set to null, users
/// will be able to create tokens that do not expire.
#[schemars(range(min = 1))]
pub device_token_max_ttl_seconds: Nullable<NonZeroU32>,
}

Expand Down
6 changes: 4 additions & 2 deletions nexus/types/src/external_api/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,13 @@ impl SimpleIdentityOrName for SiloUtilization {
}
}

/// A collection of resource counts used to set the virtual capacity of a silo
/// View of silo authentication settings
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct SiloAuthSettings {
pub silo_id: Uuid,
pub device_token_max_ttl_seconds: Option<i64>,
/// Maximum lifetime of a device token in seconds. If set to null, users
/// will be able to create tokens that do not expire.
pub device_token_max_ttl_seconds: Option<u32>,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with u32 here because it feels maybe overkill to have to validate that this is NonZero when it comes out of the DB.

}

// AFFINITY GROUPS
Expand Down
6 changes: 4 additions & 2 deletions openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -22607,13 +22607,15 @@
]
},
"SiloAuthSettings": {
"description": "A collection of resource counts used to set the virtual capacity of a silo",
"description": "View of silo authentication settings",
"type": "object",
"properties": {
"device_token_max_ttl_seconds": {
"nullable": true,
"description": "Maximum lifetime of a device token in seconds. If set to null, users will be able to create tokens that do not expire.",
"type": "integer",
"format": "int64"
"format": "uint32",
"minimum": 0
},
"silo_id": {
"type": "string",
Expand Down