Skip to content

Commit

Permalink
Merge pull request #35 from insight-platform/issue-29
Browse files Browse the repository at this point in the history
Issue 29. Add basic authentication quarantine
  • Loading branch information
ksenia-vazhdaeva authored Aug 14, 2024
2 parents cb08bf4 + a34aa1c commit f17cbfe
Show file tree
Hide file tree
Showing 9 changed files with 837 additions and 64 deletions.
18 changes: 17 additions & 1 deletion docs/source/cookbook/2_basic_auth.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
HTTP Basic authentication
=========================

Media Gateway can control access to the server via a username/password authentication (HTTP Basic Authentication). HTTP Basic authentication should be used with HTTPS (see :doc:`0_https`) to provide confidentiality. The only endpoint that is available to anyone is :ref:`a health endpoint <health endpoint>`. Usernames and passwords are taken from `etcd <https://etcd.io/>`__.
Media Gateway can control access to the server via a username/password authentication (HTTP Basic Authentication). HTTP Basic authentication should be used with HTTPS (see :doc:`0_https`) to provide confidentiality. The only endpoint that is available to anyone is :ref:`a health endpoint <health endpoint>`. Usernames and passwords are taken from `etcd <https://etcd.io/>`__. An optional quarantine feature is available. A user is quarantined for a period if the amount of failed attempts to authenticate reaches the maximum. An error response without password checks is returned if the user is quarantined which reduces risks of attacks (e.g. password hacking, DoS).

`Savant messages <https://github.com/insight-platform/savant-rs/blob/main/savant_core/src/message.rs>`__ contain routing labels. Media Gateway server can be configured to accept messages from a user only if routing labels are allowed. Allowed labels in the form of `a label filter rule <https://github.com/insight-platform/savant-rs/blob/main/savant_core/src/message/label_filter.rs>`__ are taken from `etcd`.

Expand Down Expand Up @@ -68,6 +68,13 @@ To use HTTP Basic authentication update server and client configurations. Exampl
},
"evicted_threshold": 10
}
},
"quarantine": {
"failed_attempt_limit": 3,
"period": {
"secs": 60,
"nanos": 0
}
}
}
}
Expand Down Expand Up @@ -352,6 +359,13 @@ To test the server only prepare a configuration file. The configuration below do
},
"evicted_threshold": 10
}
},
"quarantine": {
"failed_attempt_limit": 3,
"period": {
"secs": 60,
"nanos": 0
}
}
}
},
Expand Down Expand Up @@ -418,6 +432,8 @@ Send a request with an invalid user name and password.
HTTP response with ``401 Unauthorized`` status code should be returned. It means that authentication fails.
Send the last request two more times. Each time HTTP response with ``401 Unauthorized`` status code should be returned. After that send the request with the valid password. HTTP response with ``401 Unauthorized`` status code should be returned during 1 minute, after 1 minute - HTTP response with ``400 Bad Request`` status code.
Add a new user `user2` with a password `password2` and send a request using it to test that new users are loaded.
.. code-block:: bash
Expand Down
33 changes: 28 additions & 5 deletions docs/source/miscellaneous/2_caching.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,48 @@ User data cache

User data such as usernames, passwords and allowed routing labels are required for HTTP Basic authentication and authorization and stored in `etcd` (see :doc:`/cookbook/2_basic_auth`). User data is cached and automatically reloaded from `etcd` when its checksum is changed.

Authentication cache
--------------------
Authentication caching structures
---------------------------------

Check result cache
^^^^^^^^^^^^^^^^^^

A separate cache is used to decrease cryptographic costs for HTTP Basic authentication (see :doc:`/cookbook/2_basic_auth`). It holds results of authentication checks which are used for subsequent requests if provided credentials are the same and the user's password is not changed.

Failed attempt cache
^^^^^^^^^^^^^^^^^^^^

If a quarantine feature is enabled a separate cache to track failed attempts to authenticate by users is used. It uses the same configuration as authentication check result cache.

Quarantine
^^^^^^^^^^

If a quarantine feature is enabled a separate structure is used to hold names of users that are in quarantine for the specified duration. It uses the same configuration as authentication check result cache and an additional parameter - the duration.

Cache configuration
-------------------

Caches use LRU eviction policy. The maximum number of entries the cache may contain is specified in the configuration. The cache might be inefficient if its size is not suitable. To detect such cases cache usage tracking is supported. Cache usage statistics includes evicted entries per period metric. If the metric value exceeds the threshold a warning is reported to logs.
Caching structures use LRU eviction policy. The maximum number of entries the caching structure may contain is specified in the configuration. The caching structure might be inefficient if its size is not suitable. To detect such cases usage tracking is supported. Usage statistics includes evicted entries per period metric. If the metric value exceeds the threshold a warning is reported to logs.

.. code-block::
:caption: logs for the exceeded evicted entries threshold in user data cache
[2024-08-05T04:40:20Z WARN media_gateway_server::server::service::cache] Evicted entities threshold is exceeded for user: 7 per 60.001 seconds
.. code-block::
:caption: logs for the exceeded evicted entries threshold in authentication cache
:caption: logs for the exceeded evicted entries threshold in authentication check result cache
[2024-08-05T04:40:20Z WARN media_gateway_server::server::service::cache] Evicted entities threshold is exceeded for auth check result: 14 per 60.001 seconds
.. code-block::
:caption: logs for the exceeded evicted entries threshold in authentication failed attempt cache
[2024-08-05T04:40:20Z WARN media_gateway_server::server::service::cache] Evicted entities threshold is exceeded for auth failed attempt: 14 per 60.001 seconds
.. code-block::
:caption: logs for the exceeded evicted entries threshold in authentication quarantine
[2024-08-05T04:40:20Z WARN media_gateway_server::server::service::cache] Evicted entities threshold is exceeded for auth: 14 per 60.001 seconds
[2024-08-05T04:40:20Z WARN media_gateway_server::server::service::cache] Evicted entities threshold is exceeded for auth quarantine: 14 per 60.001 seconds
The period and the threshold are specified in the configuration (see :ref:`cache configuration <cache configuration>`).

Expand Down
20 changes: 19 additions & 1 deletion docs/source/reference/0_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,11 @@ Authentication settings for the server.
- etcd configuration. See below.
- true
* - cache
- Settings for authentication cache. See :ref:`cache configuration section <cache configuration>`.
- Settings for authentication caching structures. See :ref:`cache configuration section <cache configuration>`.
- true
* - quarantine
- Settings for authentication quarantine. See below.
- false

**etcd**

Expand Down Expand Up @@ -449,6 +452,21 @@ Authentication settings for the server.
- Settings for user data cache. See :ref:`cache configuration section <cache configuration>`.
- true

**authentication quarantine**

.. list-table::
:header-rows: 1

* - Field
- Description
- Mandatory
* - failed_attempt_limit
- A number of failed attempts after which a quarantine will start for a user.
- true
* - period
- A period to quarantine a user. See :ref:`duration configuration <duration configuration>`.
- true

.. _statistics configuration:

Statistics
Expand Down
86 changes: 48 additions & 38 deletions media_gateway_server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ use media_gateway_common::health::HealthService;
use server::configuration::GatewayConfiguration;

use crate::server::api::gateway;
use crate::server::security::{basic_auth_validator, BasicAuthCheckResult};
use crate::server::service::cache::{
Cache, CacheUsageFactory, CacheUsageTracker, NoOpCacheUsageTracker,
use crate::server::security::quarantine::{
AuthQuarantine, AuthQuarantineFactory, NoOpAuthQuarantine,
};
use crate::server::security::{basic_auth_validator, BasicAuthCheckResult};
use crate::server::service::cache::{Cache, CacheUsageFactory, NoOpCacheUsageTracker};
use crate::server::service::crypto::argon2::Argon2PasswordService;
use crate::server::service::crypto::PasswordService;
use crate::server::service::gateway::GatewayService;
Expand All @@ -84,8 +85,8 @@ mod server;

type AuthAppData = (
Box<dyn Storage<UserData> + Sync + Send>,
NonZeroUsize,
Arc<Box<dyn CacheUsageTracker + Sync + Send>>,
Cache<Credentials, BasicAuthCheckResult>,
Box<dyn AuthQuarantine + Sync + Send>,
);

fn main() -> Result<()> {
Expand All @@ -110,42 +111,50 @@ fn main() -> Result<()> {
let gateway_service = web::Data::new(Mutex::new(GatewayService::try_from(&conf)?));
let health_service = web::Data::new(HealthService::new());
let auth_enabled = conf.auth.is_some();
let (storage, cache_size, cache_usage_tracker): AuthAppData = if let Some(auth_conf) = conf.auth
{
let auth_cache_usage_tracker = CacheUsageFactory::from(
auth_conf.basic.cache.usage.as_ref(),
"auth".to_string(),
&runtime,
);
let storage_cache_usage_tracker = CacheUsageFactory::from(
auth_conf.basic.etcd.cache.usage.as_ref(),
"user".to_string(),
&runtime,
);
(
Box::new(
EtcdStorage::try_from((
&auth_conf.basic.etcd,
&runtime,
storage_cache_usage_tracker.clone(),
))
.unwrap(),
),
auth_conf.basic.cache.size,
auth_cache_usage_tracker,
)
} else {
(
Box::new(EmptyStorage {}),
NonZeroUsize::new(1).unwrap(),
Arc::new(Box::new(NoOpCacheUsageTracker {})),
)
};
let (user_storage, auth_cache, auth_quarantine): AuthAppData =
if let Some(auth_conf) = conf.auth {
let auth_check_result_cache_usage_tracker = CacheUsageFactory::from(
auth_conf.basic.cache.usage.as_ref(),
"auth check result".to_string(),
&runtime,
);
let auth_quarantine = AuthQuarantineFactory::from(&auth_conf.basic, &runtime)?;
let storage_cache_usage_tracker = CacheUsageFactory::from(
auth_conf.basic.etcd.cache.usage.as_ref(),
"user".to_string(),
&runtime,
);
(
Box::new(
EtcdStorage::try_from((
&auth_conf.basic.etcd,
&runtime,
storage_cache_usage_tracker.clone(),
))
.unwrap(),
),
Cache::new(
auth_conf.basic.cache.size,
auth_check_result_cache_usage_tracker,
),
auth_quarantine,
)
} else {
(
Box::new(EmptyStorage {}),
Cache::new(
NonZeroUsize::new(1).unwrap(),
Arc::new(Box::new(NoOpCacheUsageTracker {})),
),
Box::new(NoOpAuthQuarantine {}),
)
};
let basic_auth_cache: web::Data<Cache<Credentials, BasicAuthCheckResult>> =
web::Data::new(Cache::new(cache_size, cache_usage_tracker));
web::Data::new(auth_cache);
let basic_auth_quarantine = web::Data::new(auth_quarantine);
let password_service: web::Data<Box<dyn PasswordService + Sync + Send>> =
web::Data::new(Box::new(Argon2PasswordService {}));
let user_service = web::Data::new(UserService::new(storage));
let user_service = web::Data::new(UserService::new(user_storage));

let mut http_server = HttpServer::new(move || {
App::new()
Expand All @@ -155,6 +164,7 @@ fn main() -> Result<()> {
.app_data(user_service.clone())
.app_data(password_service.clone())
.app_data(basic_auth_cache.clone())
.app_data(basic_auth_quarantine.clone())
.route("", web::post().to(gateway))
.wrap(Condition::new(
auth_enabled,
Expand Down
7 changes: 7 additions & 0 deletions media_gateway_server/src/server/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ pub struct AuthConfiguration {
pub struct BasicAuthConfiguration {
pub etcd: EtcdConfiguration,
pub cache: CacheConfiguration,
pub quarantine: Option<AuthQuarantineConfiguration>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AuthQuarantineConfiguration {
pub period: Duration,
pub failed_attempt_limit: u32,
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
Loading

0 comments on commit f17cbfe

Please sign in to comment.