Skip to content

Commit

Permalink
Merge torrust#988: Overhaul tracker keys: option to disable auth key …
Browse files Browse the repository at this point in the history
…expiration

d7dfc3b feat: [torrust#978] add semantic validation for configuration (Jose Celano)
e8e935c feat: [torrust#978] add a config option to disable cheking keys' expiration (Jose Celano)

Pull request description:

  When the tracker is running in private mode you can disable checking keys' expiration in the configuration with:

  ```toml
  [core]
  private = false

  [core.private_mode]
  check_keys_expiration = false
  ```

  All keys will be valid as long as they exist in the database.

ACKs for top commit:
  josecelano:
    ACK d7dfc3b

Tree-SHA512: 0605a43f2c9962efece92327261c2cc7448a35e8961260b14164162408288d87e7055ba1764778ba94d1849f575fe7110328d95830f8a5fa8ea062d186a52066
  • Loading branch information
josecelano committed Aug 1, 2024
2 parents 680f642 + d7dfc3b commit e00feef
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 8 deletions.
1 change: 1 addition & 0 deletions packages/configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//!
//! The current version for configuration is [`v2`].
pub mod v2;
pub mod validator;

use std::collections::HashMap;
use std::env;
Expand Down
50 changes: 50 additions & 0 deletions packages/configuration/src/v2/core.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use derive_more::{Constructor, Display};
use serde::{Deserialize, Serialize};

use super::network::Network;
use crate::v2::database::Database;
use crate::validator::{SemanticValidationError, Validator};
use crate::{AnnouncePolicy, TrackerPolicy};

#[allow(clippy::struct_excessive_bools)]
Expand Down Expand Up @@ -32,6 +34,10 @@ pub struct Core {
#[serde(default = "Core::default_private")]
pub private: bool,

// Configuration specific when the tracker is running in private mode.
#[serde(default = "Core::default_private_mode")]
pub private_mode: Option<PrivateMode>,

// Tracker policy configuration.
#[serde(default = "Core::default_tracker_policy")]
pub tracker_policy: TrackerPolicy,
Expand All @@ -54,6 +60,7 @@ impl Default for Core {
listed: Self::default_listed(),
net: Self::default_network(),
private: Self::default_private(),
private_mode: Self::default_private_mode(),
tracker_policy: Self::default_tracker_policy(),
tracker_usage_statistics: Self::default_tracker_usage_statistics(),
}
Expand Down Expand Up @@ -85,10 +92,53 @@ impl Core {
false
}

fn default_private_mode() -> Option<PrivateMode> {
if Self::default_private() {
Some(PrivateMode::default())
} else {
None
}
}

fn default_tracker_policy() -> TrackerPolicy {
TrackerPolicy::default()
}
fn default_tracker_usage_statistics() -> bool {
true
}
}

/// Configuration specific when the tracker is running in private mode.
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy, Constructor, Display)]
pub struct PrivateMode {
/// A flag to disable expiration date for peer keys.
///
/// When true, if the keys is not permanent the expiration date will be
/// ignored. The key will be accepted even if it has expired.
#[serde(default = "PrivateMode::default_check_keys_expiration")]
pub check_keys_expiration: bool,
}

impl Default for PrivateMode {
fn default() -> Self {
Self {
check_keys_expiration: Self::default_check_keys_expiration(),
}
}
}

impl PrivateMode {
fn default_check_keys_expiration() -> bool {
true
}
}

impl Validator for Core {
fn validate(&self) -> Result<(), SemanticValidationError> {
if self.private_mode.is_some() && !self.private {
return Err(SemanticValidationError::UselessPrivateModeSection);
}

Ok(())
}
}
7 changes: 7 additions & 0 deletions packages/configuration/src/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ use self::health_check_api::HealthCheckApi;
use self::http_tracker::HttpTracker;
use self::tracker_api::HttpApi;
use self::udp_tracker::UdpTracker;
use crate::validator::{SemanticValidationError, Validator};
use crate::{Error, Info, Metadata, Version};

/// This configuration version
Expand Down Expand Up @@ -394,6 +395,12 @@ impl Configuration {
}
}

impl Validator for Configuration {
fn validate(&self) -> Result<(), SemanticValidationError> {
self.core.validate()
}
}

#[cfg(test)]
mod tests {

Expand Down
19 changes: 19 additions & 0 deletions packages/configuration/src/validator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Trait to validate semantic errors.
//!
//! Errors could involve more than one configuration option. Some configuration
//! combinations can be incompatible.
use thiserror::Error;

/// Errors that can occur validating the configuration.
#[derive(Error, Debug)]
pub enum SemanticValidationError {
#[error("Private mode section in configuration can only be included when the tracker is running in private mode.")]
UselessPrivateModeSection,
}

pub trait Validator {
/// # Errors
///
/// Will return an error if the configuration is invalid.
fn validate(&self) -> Result<(), SemanticValidationError>;
}
9 changes: 9 additions & 0 deletions src/bootstrap/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use std::sync::Arc;

use torrust_tracker_clock::static_time;
use torrust_tracker_configuration::validator::Validator;
use torrust_tracker_configuration::Configuration;
use tracing::info;

Expand All @@ -24,10 +25,18 @@ use crate::core::Tracker;
use crate::shared::crypto::ephemeral_instance_keys;

/// It loads the configuration from the environment and builds the main domain [`Tracker`] struct.
///
/// # Panics
///
/// Setup can file if the configuration is invalid.
#[must_use]
pub fn setup() -> (Configuration, Arc<Tracker>) {
let configuration = initialize_configuration();

if let Err(e) = configuration.validate() {
panic!("Configuration error: {e}");
}

let tracker = initialize_with_configuration(&configuration);

info!("Configuration:\n{}", configuration.clone().mask_secrets().to_json());
Expand Down
12 changes: 6 additions & 6 deletions src/core/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! Tracker keys are tokens used to authenticate the tracker clients when the tracker runs
//! in `private` or `private_listed` modes.
//!
//! There are services to [`generate_key`] and [`verify_key`] authentication keys.
//! There are services to [`generate_key`] and [`verify_key_expiration`] authentication keys.
//!
//! Authentication keys are used only by [`HTTP`](crate::servers::http) trackers. All keys have an expiration time, that means
//! they are only valid during a period of time. After that time the expiring key will no longer be valid.
Expand Down Expand Up @@ -33,7 +33,7 @@
//!
//! // And you can later verify it with:
//!
//! assert!(auth::verify_key(&expiring_key).is_ok());
//! assert!(auth::verify_key_expiration(&expiring_key).is_ok());
//! ```
use std::panic::Location;
Expand Down Expand Up @@ -106,7 +106,7 @@ pub fn generate_key(lifetime: Option<Duration>) -> PeerKey {
///
/// - `Error::KeyExpired` if `auth_key.valid_until` is past the `current_time`.
/// - `Error::KeyInvalid` if `auth_key.valid_until` is past the `None`.
pub fn verify_key(auth_key: &PeerKey) -> Result<(), Error> {
pub fn verify_key_expiration(auth_key: &PeerKey) -> Result<(), Error> {
let current_time: DurationSinceUnixEpoch = CurrentClock::now();

match auth_key.valid_until {
Expand Down Expand Up @@ -322,7 +322,7 @@ mod tests {
fn should_be_generated_with_a_expiration_time() {
let expiring_key = auth::generate_key(Some(Duration::new(9999, 0)));

assert!(auth::verify_key(&expiring_key).is_ok());
assert!(auth::verify_key_expiration(&expiring_key).is_ok());
}

#[test]
Expand All @@ -336,12 +336,12 @@ mod tests {
// Mock the time has passed 10 sec.
clock::Stopped::local_add(&Duration::from_secs(10)).unwrap();

assert!(auth::verify_key(&expiring_key).is_ok());
assert!(auth::verify_key_expiration(&expiring_key).is_ok());

// Mock the time has passed another 10 sec.
clock::Stopped::local_add(&Duration::from_secs(10)).unwrap();

assert!(auth::verify_key(&expiring_key).is_err());
assert!(auth::verify_key_expiration(&expiring_key).is_err());
}
}
}
32 changes: 30 additions & 2 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,16 @@ impl Tracker {
location: Location::caller(),
key: Box::new(key.clone()),
}),
Some(key) => auth::verify_key(key),
Some(key) => match self.config.private_mode {
Some(private_mode) => {
if private_mode.check_keys_expiration {
return auth::verify_key_expiration(key);
}

Ok(())
}
None => auth::verify_key_expiration(key),
},
}
}

Expand Down Expand Up @@ -1779,8 +1788,9 @@ mod tests {
use std::time::Duration;

use torrust_tracker_clock::clock::Time;
use torrust_tracker_configuration::v2::core::PrivateMode;

use crate::core::auth;
use crate::core::auth::{self, Key};
use crate::core::tests::the_tracker::private_tracker;
use crate::CurrentClock;

Expand Down Expand Up @@ -1829,6 +1839,24 @@ mod tests {
assert!(tracker.verify_auth_key(&expiring_key.key()).await.is_ok());
}

#[tokio::test]
async fn it_should_accept_an_expired_key_when_checking_expiration_is_disabled_in_configuration() {
let mut tracker = private_tracker();

tracker.config.private_mode = Some(PrivateMode {
check_keys_expiration: false,
});

let past_time = Some(Duration::ZERO);

let expiring_key = tracker
.add_auth_key(Key::new("YZSl4lMZupRuOpSRC3krIKR5BPB14nrJ").unwrap(), past_time)
.await
.unwrap();

assert!(tracker.authenticate(&expiring_key.key()).await.is_ok());
}

#[tokio::test]
async fn it_should_fail_verifying_an_unregistered_authentication_key() {
let tracker = private_tracker();
Expand Down

0 comments on commit e00feef

Please sign in to comment.