Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
785adc5
draft console server
david-crespo Nov 12, 2021
e83e39c
stub out asset and console_page routes
david-crespo Nov 12, 2021
490a1e5
Cookies extractor
david-crespo Nov 12, 2021
9476cfd
logout is closer to correct, add fake login, session cookie value helper
david-crespo Nov 17, 2021
e020e46
working tests for session create and session auth, that's cool
david-crespo Nov 17, 2021
3bc6880
fix config files
david-crespo Nov 18, 2021
ae1a19d
logout makes session token stop working
david-crespo Nov 18, 2021
5fae933
file server and login redirect pretty much work
david-crespo Nov 18, 2021
fdc0d31
fix confusing variable names
david-crespo Nov 18, 2021
b765a4d
move session cookie header generator into session cookie auth scheme
david-crespo Nov 18, 2021
18802ef
clean up file serving
david-crespo Nov 18, 2021
b864e64
Merge main into console-api
david-crespo Nov 18, 2021
af72045
stop using local dropshot
david-crespo Nov 18, 2021
98d5f89
make console_api match the new dir structure
david-crespo Nov 18, 2021
efe80eb
real internal error message for file not found errors
david-crespo Nov 18, 2021
356cf7c
make assets directory configurable
david-crespo Nov 18, 2021
87a8800
lovely unit tests for find_file, integration test for symlink
david-crespo Nov 19, 2021
7de6ffc
take out comment wondering whether to auth static files
david-crespo Nov 19, 2021
be44ef5
Merge main into console-api
david-crespo Nov 19, 2021
5b6b028
use new helpers in tests, response_body() -> parsed_body()
david-crespo Nov 19, 2021
a43dd44
add expect_response_header and use it
david-crespo Nov 19, 2021
98e95bd
render html from console pages route
david-crespo Nov 22, 2021
7c58bea
serve console routes from external_api dropshots server to avoid cors
david-crespo Nov 22, 2021
227ddf7
put expects on all the tests
david-crespo Nov 22, 2021
ac6ef6d
Merge branch 'main' into console-api
david-crespo Nov 22, 2021
7a533e2
whoops
david-crespo Nov 22, 2021
c3bff0c
premature optimization
david-crespo Nov 22, 2021
89fdc9a
extract common header parsing code in test helpers
david-crespo Nov 23, 2021
33ec251
handle nonexistent assets dir at startup with error instead of panic
david-crespo Nov 23, 2021
9332861
login form, shenanigans necessary to serve console
david-crespo Nov 23, 2021
3c0a6a3
Merge main into console-api
david-crespo Nov 23, 2021
ddf642d
response_body -> parsed_body merge issue
david-crespo Nov 23, 2021
92a19be
include login and logout post endpoint in openapi spec
david-crespo Nov 23, 2021
eeaefc4
update tests
david-crespo Nov 24, 2021
6500f8c
we don't need the /c/ prefix! all the console routes start with /orgs !
david-crespo Nov 24, 2021
c50476a
cache-control headers, comment why we're not doing asset checks on start
david-crespo Nov 24, 2021
5e022ca
Merge branch 'main' into console-api
david-crespo Nov 24, 2021
1a5eefe
update comments
david-crespo Nov 24, 2021
24604a1
make cache-control header tunable, rearrange config a bit
david-crespo Nov 24, 2021
159c4d0
assets dir relative to CARGO_MANIFEST_DIR for more stable results
david-crespo Nov 24, 2021
a1ab95d
Merge branch 'main' into console-api
david-crespo Nov 24, 2021
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
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions nexus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ipnetwork = "0.18"
lazy_static = "1.4.0"
libc = "0.2.108"
macaddr = { version = "1.0.1", features = [ "serde_std" ]}
mime_guess = "2.0.3"
newtype_derive = "0.1.6"
oso = "0.23"
oximeter-client = { path = "../oximeter-client" }
Expand Down
9 changes: 7 additions & 2 deletions nexus/examples/config-file.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
# Identifier for this instance of Nexus
id = "e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c"

[console]
# Directory of static assets for the console. Relative to nexus/.
assets_directory = "tests/fixtures" # TODO: figure out value
cache_control_max_age_minutes = 10
session_idle_timeout_minutes = 60
session_absolute_timeout_minutes = 480

# List of authentication schemes to support.
#
# This is not fleshed out yet and the only reason to change it now is for
# working on authentication or authorization. Neither is really implemented
# yet.
[authn]
schemes_external = []
session_idle_timeout_minutes = 60
session_absolute_timeout_minutes = 480

[database]
# URL for connecting to the database
Expand Down
11 changes: 8 additions & 3 deletions nexus/examples/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@
# Identifier for this instance of Nexus
id = "e6bff1ff-24fb-49dc-a54e-c6a350cd4d6c"

[console]
# Directory of static assets for the console. Relative to nexus/.
assets_directory = "tests/fixtures" # TODO: figure out value
cache_control_max_age_minutes = 10
session_idle_timeout_minutes = 60
session_absolute_timeout_minutes = 480

# List of authentication schemes to support.
#
# This is not fleshed out yet and the only reason to change it now is for
# working on authentication or authorization. Neither is really implemented
# yet.
[authn]
# TODO(https://github.com/oxidecomputer/omicron/issues/372): Remove "spoof".
schemes_external = ["spoof"]
session_idle_timeout_minutes = 60
session_absolute_timeout_minutes = 480
schemes_external = ["spoof", "session_cookie"]

[database]
# URL for connecting to the database
Expand Down
25 changes: 25 additions & 0 deletions nexus/src/authn/external/cookies.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use anyhow::Context;
use async_trait::async_trait;
use cookie::{Cookie, CookieJar, ParseError};
use dropshot::{
Extractor, ExtractorMetadata, HttpError, RequestContext, ServerContext,
};
use std::sync::Arc;

pub fn parse_cookies(
headers: &http::HeaderMap<http::HeaderValue>,
Expand All @@ -19,6 +24,26 @@ pub fn parse_cookies(
}
Ok(cookies)
}
pub struct Cookies(pub CookieJar);

NewtypeFrom! { () pub struct Cookies(pub CookieJar); }
NewtypeDeref! { () pub struct Cookies(pub CookieJar); }

#[async_trait]
impl Extractor for Cookies {
async fn from_request<Context: ServerContext>(
rqctx: Arc<RequestContext<Context>>,
) -> Result<Self, HttpError> {
let request = &rqctx.request.lock().await;
let cookies = parse_cookies(request.headers())
.unwrap_or_else(|_| CookieJar::new());
Ok(cookies.into())
}

fn metadata() -> ExtractorMetadata {
ExtractorMetadata { paginated: false, parameters: vec![] }
}
}

#[cfg(test)]
mod test {
Expand Down
2 changes: 1 addition & 1 deletion nexus/src/authn/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::authn;
use async_trait::async_trait;
use authn::Reason;

mod cookies;
pub mod cookies;
pub mod session_cookie;
pub mod spoof;

Expand Down
38 changes: 36 additions & 2 deletions nexus/src/authn/external/session_cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ pub const SESSION_COOKIE_COOKIE_NAME: &str = "session";
pub const SESSION_COOKIE_SCHEME_NAME: authn::SchemeName =
authn::SchemeName("session_cookie");

/// Generate session cookie header
pub fn session_cookie_header_value(token: &str, max_age: Duration) -> String {
format!(
"{}={}; Secure; HttpOnly; SameSite=Lax; Max-Age={}",
SESSION_COOKIE_COOKIE_NAME,
token,
max_age.num_seconds()
)
}

/// Generate session cookie with empty token and max-age=0 so browser deletes it
pub fn clear_session_cookie_header_value() -> String {
session_cookie_header_value("", Duration::zero())
}

/// Implements an authentication scheme where we check the DB to see if we have
/// a session matching the token in a cookie ([`SESSION_COOKIE_COOKIE_NAME`]) on
/// the request. This is meant to be used by the web console.
Expand Down Expand Up @@ -148,8 +163,9 @@ fn get_token_from_cookie(
#[cfg(test)]
mod test {
use super::{
get_token_from_cookie, Details, HttpAuthnScheme,
HttpAuthnSessionCookie, Reason, SchemeResult, Session, SessionStore,
get_token_from_cookie, session_cookie_header_value, Details,
HttpAuthnScheme, HttpAuthnSessionCookie, Reason, SchemeResult, Session,
SessionStore,
};
use async_trait::async_trait;
use chrono::{DateTime, Duration, Utc};
Expand Down Expand Up @@ -355,4 +371,22 @@ mod test {
let token = get_token_from_cookie(&headers);
assert_eq!(token, None);
}

#[test]
fn test_session_cookie_value() {
assert_eq!(
session_cookie_header_value("abc", Duration::seconds(5)),
"session=abc; Secure; HttpOnly; SameSite=Lax; Max-Age=5"
);

assert_eq!(
session_cookie_header_value("abc", Duration::seconds(-5)),
"session=abc; Secure; HttpOnly; SameSite=Lax; Max-Age=-5"
);

assert_eq!(
session_cookie_header_value("", Duration::zero()),
"session=; Secure; HttpOnly; SameSite=Lax; Max-Age=0"
);
}
}
41 changes: 32 additions & 9 deletions nexus/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ use std::path::{Path, PathBuf};
pub struct AuthnConfig {
/** allowed authentication schemes for external HTTP server */
pub schemes_external: Vec<SchemeName>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ConsoleConfig {
pub assets_directory: PathBuf,
/** how long the browser can cache static assets */
pub cache_control_max_age_minutes: u32,
/** how long a session can be idle before expiring */
pub session_idle_timeout_minutes: u32,
/** how long a session can exist before expiring */
Expand All @@ -40,6 +47,8 @@ pub struct Config {
pub dropshot_internal: ConfigDropshot,
/** Identifier for this instance of Nexus */
pub id: uuid::Uuid,
/** Console-related tunables */
pub console: ConsoleConfig,
/** Server-wide logging configuration. */
pub log: ConfigLogging,
/** Database parameters */
Expand Down Expand Up @@ -149,7 +158,10 @@ impl Config {

#[cfg(test)]
mod test {
use super::{AuthnConfig, Config, LoadError, LoadErrorKind, SchemeName};
use super::{
AuthnConfig, Config, ConsoleConfig, LoadError, LoadErrorKind,
SchemeName,
};
use crate::db;
use dropshot::ConfigDropshot;
use dropshot::ConfigLogging;
Expand Down Expand Up @@ -257,10 +269,13 @@ mod test {
"valid",
r##"
id = "28b90dc4-c22a-65ba-f49a-f051fe01208f"
[authn]
schemes_external = []
[console]
assets_directory = "tests/fixtures"
cache_control_max_age_minutes = 10
session_idle_timeout_minutes = 60
session_absolute_timeout_minutes = 480
[authn]
schemes_external = []
[dropshot_external]
bind_address = "10.1.2.3:4567"
request_body_max_bytes = 1024
Expand All @@ -282,11 +297,13 @@ mod test {
config,
Config {
id: "28b90dc4-c22a-65ba-f49a-f051fe01208f".parse().unwrap(),
authn: AuthnConfig {
schemes_external: Vec::new(),
console: ConsoleConfig {
assets_directory: "tests/fixtures".parse().unwrap(),
cache_control_max_age_minutes: 10,
session_idle_timeout_minutes: 60,
session_absolute_timeout_minutes: 480
},
authn: AuthnConfig { schemes_external: Vec::new() },
dropshot_external: ConfigDropshot {
bind_address: "10.1.2.3:4567"
.parse::<SocketAddr>()
Expand Down Expand Up @@ -316,10 +333,13 @@ mod test {
"valid",
r##"
id = "28b90dc4-c22a-65ba-f49a-f051fe01208f"
[authn]
schemes_external = [ "spoof", "session_cookie" ]
[console]
assets_directory = "tests/fixtures"
cache_control_max_age_minutes = 10
session_idle_timeout_minutes = 60
session_absolute_timeout_minutes = 480
[authn]
schemes_external = [ "spoof", "session_cookie" ]
[dropshot_external]
bind_address = "10.1.2.3:4567"
request_body_max_bytes = 1024
Expand Down Expand Up @@ -351,10 +371,13 @@ mod test {
"bad authn.schemes_external",
r##"
id = "28b90dc4-c22a-65ba-f49a-f051fe01208f"
[authn]
schemes_external = ["trust-me"]
[console]
assets_directory = "tests/fixtures"
cache_control_max_age_minutes = 10
session_idle_timeout_minutes = 60
session_absolute_timeout_minutes = 480
[authn]
schemes_external = ["trust-me"]
[dropshot_external]
bind_address = "10.1.2.3:4567"
request_body_max_bytes = 1024
Expand Down
Loading