Skip to content

Commit

Permalink
Merge branch 'dev' into oauth2proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
t-aleksander committed Nov 15, 2024
2 parents 4d63298 + 8baf58e commit 78a5c66
Show file tree
Hide file tree
Showing 36 changed files with 483 additions and 87 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
container: rust:1-slim
steps:
- name: Install packages
run: apt-get update && apt install -y git protobuf-compiler libssl-dev
run: apt-get update && apt install -y git protobuf-compiler libssl-dev pkg-config curl

- name: Checkout
uses: actions/checkout@v4
Expand Down

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

37 changes: 19 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,13 @@
<img alt="GitHub commits since latest release" src="https://img.shields.io/github/commits-since/defguard/defguard/latest/dev?style=for-the-badge&label=COMMITS%20SINCE%20LATEST%20RELEASE">
</p>

[Website](https://defguard.net) | [Getting Started](https://docs.defguard.net/#what-is-defguard) | [Features](https://github.com/defguard/defguard#features) | [Roadmap](https://github.com/orgs/defguard/projects/5) | [Support ❤](https://github.com/defguard/defguard#support-)
[Website](https://defguard.net) | [Getting Started](https://docs.defguard.net/#what-is-defguard) | [Features](https://github.com/defguard/defguard#features) | [Roadmap](https://github.com/orgs/defguard/projects/5) | [Support ❤](https://github.com/defguard/defguard#support)

## Enterprise features are here!

🛑 We encourge to test the [pre-release](https://docs.defguard.net/admin-and-features/setting-up-your-instance/pre-production-and-development-releases) of the new **Open Source Open Core** & **Enterprise features** (like external OpenID (Google/Microsoft/Custom), real time client sync and more!) published! 🛑

All currently available enterprise features are in [enterprise documentation section](https://docs.defguard.net/enterprise/all-enteprise-features) as well as information about [enterprise license](https://docs.defguard.net/enterprise/license).
</div>

### Unique value proposition
### Comprehensive Access Control

- **Comprehensive [WireGuard® 2FA/MFA](https://docs.defguard.net/admin-and-features/wireguard/multi-factor-authentication-mfa-2fa/architecture)** - not 2FA to "access application" like most solutions
- **[WireGuard® VPN with 2FA/MFA](https://docs.defguard.net/admin-and-features/wireguard/multi-factor-authentication-mfa-2fa/architecture)** - not 2FA to "access application" like most solutions
- The only solution with [automatic and real-time synchronization](https://docs.defguard.net/enterprise/automatic-real-time-desktop-client-configuration) for users' desktop client settings (including all VPNs/locations).
- Control users [ability to manage devices and VPN options](https://docs.defguard.net/enterprise/behavior-customization)
- [Integrated SSO based on OpenID Connect](https://docs.defguard.net/admin-and-features/openid-connect):
Expand All @@ -31,7 +26,9 @@ All currently available enterprise features are in [enterprise documentation sec
- Built on WireGuard® protocol which is faster than IPSec, and significantly faster than OpenVPN
- Built with Rust for speed and security

See below [full list of features](https://github.com/defguard/defguard#features)
See:
- [full list of features](https://github.com/defguard/defguard#features)
- [enterprise only features](https://docs.defguard.net/enterprise/all-enteprise-features)

#### Video introduction

Expand Down Expand Up @@ -65,6 +62,8 @@ Better quality video can [be viewed here](https://github.com/DefGuard/docs/raw/d

[Desktop client](https://github.com/DefGuard/client):
- **2FA / Multi-Factor Authentication** with TOTP or email based tokens & WireGuard PSK
- [automatic and real-time synchronization](https://docs.defguard.net/enterprise/automatic-real-time-desktop-client-configuration) for users' desktop client settings (including all VPNs/locations).
- Control users [ability to manage devices and VPN options](https://docs.defguard.net/enterprise/behavior-customization)
- Defguard instances as well as **any WireGuard tunnel** - just import your tunnels - one client for all WireGuard connections
- Secure and remote user enrollment - setting up password, automatically configuring the client for all VPN Locations/Networks
- Onboarding - displaying custom onboarding messages, with templates, links ...
Expand Down Expand Up @@ -117,7 +116,7 @@ The story and motivation behind defguard [can be found here: https://teonite.com

## Features

* [WireGuard®](https://www.wireguard.com/) VPN server with:
* Remote Access: [WireGuard® VPN](https://www.wireguard.com/) server with:
- [Multi-Factor Authentication](https://docs.defguard.net/help/desktop-client/multi-factor-authentication-mfa-2fa) with TOTP/Email & Pre-Shared Session Keys
- multiple VPN Locations (networks/sites) - with defined access (all users or only Admin group)
- multiple [Gateways](https://github.com/DefGuard/gateway) for each VPN Location (**high availability/failover**) - supported on a cluster of routers/firewalls for Linux, FreeBSD/PFSense/OPNSense
Expand All @@ -129,18 +128,20 @@ The story and motivation behind defguard [can be found here: https://teonite.com
- kernel (Linux, FreeBSD/OPNSense/PFSense) & userspace WireGuard® support with [our Rust library](https://github.com/defguard/wireguard-rs)
- dashboard and statistics overview of connected users/devices for admins
- *defguard is not an official WireGuard® project, and WireGuard is a registered trademark of Jason A. Donenfeld.*
* Integrated SSO: [OpenID Connect provider](https://openid.net/developers/how-connect-works/) - with **unique features**:
- Secure remote (over the internet) [user enrollment](https://docs.defguard.net/help/remote-user-enrollment)
- User [onboarding after enrollment](https://docs.defguard.net/help/remote-user-enrollment/user-onboarding-after-enrollment)
- LDAP (tested on [OpenLDAP](https://www.openldap.org/)) synchronization
- [forward auth](https://docs.defguard.net/features/forward-auth) for reverse proxies (tested with Traefik and Caddy)
- nice UI to manage users
- Users **self-service** (besides typical data management, users can revoke access to granted apps, MFA, WireGuard®, etc.)
* Identity & Account Management:
- SSO based on OpenID Connect](https://openid.net/developers/how-connect-works/)
- Extenal SSO: [external OpenID provider support](https://docs.defguard.net/enterprise/external-openid-providers)
- [Multi-Factor/2FA](https://en.wikipedia.org/wiki/Multi-factor_authentication) Authentication:
- [Time-based One-Time Password Algorithm](https://en.wikipedia.org/wiki/Time-based_one-time_password) (TOTP - e.g. Google Authenticator)
- WebAuthn / FIDO2 - for hardware key authentication support (eg. YubiKey, FaceID, TouchID, ...)
- Email based TOTP
* Extenal SSO: [external OpenID provider support](https://docs.defguard.net/enterprise/external-openid-providers)
- LDAP (tested on [OpenLDAP](https://www.openldap.org/)) synchronization
- [forward auth](https://docs.defguard.net/features/forward-auth) for reverse proxies (tested with Traefik and Caddy)
- nice UI to manage users
- Users **self-service** (besides typical data management, users can revoke access to granted apps, MFA, WireGuard®, etc.)
* Account Lifecycle Management:
- Secure remote (over the Internet) [user enrollment](https://docs.defguard.net/help/remote-user-enrollment) - on public web / Desktop Client
- User [onboarding after enrollment](https://docs.defguard.net/help/remote-user-enrollment/user-onboarding-after-enrollment)
* SSH & GPG public key management in user profile - with [SSH keys authentication for servers](https://docs.defguard.net/admin-and-features/ssh-authentication)
* [Yubikey hardware keys](https://www.yubico.com/) provisioning for users by *one click*
* [Email/SMTP support](https://docs.defguard.net/help/setting-up-smtp-for-email-notifications) for notifications, remote enrollment and onboarding
Expand Down
2 changes: 2 additions & 0 deletions migrations/20241108110157_add_on_delete.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE token DROP CONSTRAINT enrollment_admin_id_fkey;
ALTER TABLE token ADD CONSTRAINT enrollment_admin_id_fkey FOREIGN KEY(admin_id) REFERENCES "user"(id);
2 changes: 2 additions & 0 deletions migrations/20241108110157_add_on_delete.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE token DROP CONSTRAINT enrollment_admin_id_fkey;
ALTER TABLE token ADD CONSTRAINT enrollment_admin_id_fkey FOREIGN KEY(admin_id) REFERENCES "user"(id) ON DELETE CASCADE;
7 changes: 6 additions & 1 deletion src/bin/defguard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use defguard::{
auth::failed_login::FailedLoginMap,
config::{Command, DefGuardConfig},
db::{init_db, AppEvent, GatewayEvent, Settings, User},
enterprise::license::{run_periodic_license_check, set_cached_license, License},
enterprise::{
license::{run_periodic_license_check, set_cached_license, License},
limits::update_counts,
},
grpc::{run_grpc_bidi_stream, run_grpc_server, GatewayMap, WorkerState},
init_dev_env, init_vpn_location,
mail::{run_mail_handler, Mail},
Expand Down Expand Up @@ -99,6 +102,8 @@ async fn main() -> Result<(), anyhow::Error> {
let failed_logins = FailedLoginMap::new();
let failed_logins = Arc::new(Mutex::new(failed_logins));

update_counts(&pool).await?;

debug!("Checking enterprise license status");
match License::load_or_renew(&pool).await {
Ok(license) => {
Expand Down
8 changes: 2 additions & 6 deletions src/enterprise/db/models/enterprise_settings.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use sqlx::{query, query_as, PgExecutor};
use struct_patch::Patch;

use crate::enterprise::license::{get_cached_license, validate_license};
use crate::enterprise::is_enterprise_enabled;

#[derive(Debug, Deserialize, Patch, Serialize)]
#[patch(attribute(derive(Deserialize, Serialize)))]
Expand Down Expand Up @@ -34,11 +34,7 @@ impl EnterpriseSettings {
{
// avoid holding the rwlock across await, makes the future !Send
// and therefore unusable in axum handlers
let is_valid = {
let license = get_cached_license();
validate_license(license.as_ref()).is_ok()
};
if is_valid {
if is_enterprise_enabled() {
let settings = query_as!(
Self,
"SELECT admin_device_management, \
Expand Down
6 changes: 3 additions & 3 deletions src/enterprise/grpc/polling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use tonic::Status;

use crate::{
db::{models::polling_token::PollingToken, Device, Id, User},
enterprise::license::{get_cached_license, validate_license},
enterprise::is_enterprise_enabled,
grpc::{
proto::{InstanceInfoRequest, InstanceInfoResponse},
utils::build_device_config_response,
Expand All @@ -25,8 +25,8 @@ impl PollingServer {
debug!("Validating polling token. Token: {token}");

// Polling service is enterprise-only, check the lincense
if validate_license(get_cached_license().as_ref()).is_err() {
debug!("No valid license, denying instance polling info");
if !is_enterprise_enabled() {
debug!("Instance has enterprise features disabled, denying instance polling info");
return Err(Status::failed_precondition("no valid license"));
}

Expand Down
27 changes: 16 additions & 11 deletions src/enterprise/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::{
auth::SessionInfo,
enterprise::license::validate_license,
handlers::{ApiResponse, ApiResult},
};

Expand All @@ -14,7 +13,10 @@ use axum::{
http::{request::Parts, StatusCode},
};

use super::{db::models::enterprise_settings::EnterpriseSettings, license::get_cached_license};
use super::{
db::models::enterprise_settings::EnterpriseSettings, is_enterprise_enabled,
license::get_cached_license, needs_enterprise_license,
};
use crate::{appstate::AppState, error::WebError};

pub struct LicenseInfo {
Expand All @@ -33,19 +35,20 @@ where
type Rejection = WebError;

async fn from_request_parts(_parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let license = get_cached_license();

match validate_license(license.as_ref()) {
// Useless struct, but may come in handy later
Ok(()) => Ok(LicenseInfo { valid: true }),
Err(e) => Err(WebError::Forbidden(e.to_string())),
if is_enterprise_enabled() {
Ok(LicenseInfo { valid: true })
} else {
Err(WebError::Forbidden(
"Enterprise features are disabled".into(),
))
}
}
}

pub async fn check_enterprise_status() -> ApiResult {
let enterprise_enabled = is_enterprise_enabled();
let needs_license = needs_enterprise_license();
let license = get_cached_license();
let valid = validate_license((license).as_ref()).is_ok();
let license_info = license.as_ref().map(|license| {
serde_json::json!(
{
Expand All @@ -55,8 +58,10 @@ pub async fn check_enterprise_status() -> ApiResult {
)
});
Ok(ApiResponse {
json: serde_json::json!({ "enabled": valid,
"license_info": license_info
json: serde_json::json!({
"enabled": enterprise_enabled,
"needs_license": needs_license,
"license_info": license_info
}),
status: StatusCode::OK,
})
Expand Down
142 changes: 142 additions & 0 deletions src/enterprise/limits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use sqlx::{error::Error as SqlxError, query_as, PgPool};
use std::sync::{RwLock, RwLockReadGuard};

#[derive(Debug)]
pub(crate) struct Counts {
user: i64,
device: i64,
wireguard_network: i64,
}

static COUNTS: RwLock<Counts> = RwLock::new(Counts {
user: 0,
device: 0,
wireguard_network: 0,
});

fn set_counts(new_counts: Counts) {
*COUNTS
.write()
.expect("Failed to acquire lock on the enterprise limit counts.") = new_counts;
}

pub(crate) fn get_counts() -> RwLockReadGuard<'static, Counts> {
COUNTS
.read()
.expect("Failed to acquire lock on the enterprise limit counts.")
}

/// Update the counts of users, devices, and wireguard networks stored in the memory.
// TODO: Use it with database triggers when they are implemented
pub async fn update_counts(pool: &PgPool) -> Result<(), SqlxError> {
debug!("Updating device, user, and wireguard network counts.");
let counts = query_as!(
Counts,
"SELECT \
(SELECT count(*) FROM \"user\") \"user!\", \
(SELECT count(*) FROM device) \"device!\", \
(SELECT count(*) FROM wireguard_network) \"wireguard_network!\"
"
)
.fetch_one(pool)
.await?;

set_counts(counts);
debug!(
"Updated device, user, and wireguard network counts stored in memory, new counts: {:?}",
get_counts()
);

Ok(())
}

impl Counts {
pub(crate) fn is_over_limit(&self) -> bool {
self.user > 5 || self.device > 10 || self.wireguard_network > 1
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_counts() {
let counts = Counts {
user: 1,
device: 2,
wireguard_network: 3,
};

set_counts(counts);

let counts = get_counts();

assert_eq!(counts.user, 1);
assert_eq!(counts.device, 2);
assert_eq!(counts.wireguard_network, 3);
}

#[test]
fn test_is_over_limit() {
// User limit
{
let counts = Counts {
user: 6,
device: 1,
wireguard_network: 1,
};
set_counts(counts);
let counts = get_counts();
assert!(counts.is_over_limit());
}

// Device limit
{
let counts = Counts {
user: 1,
device: 11,
wireguard_network: 1,
};
set_counts(counts);
let counts = get_counts();
assert!(counts.is_over_limit());
}

// Wireguard network limit
{
let counts = Counts {
user: 1,
device: 1,
wireguard_network: 2,
};
set_counts(counts);
let counts = get_counts();
assert!(counts.is_over_limit());
}

// No limit
{
let counts = Counts {
user: 1,
device: 1,
wireguard_network: 1,
};
set_counts(counts);
let counts = get_counts();
assert!(!counts.is_over_limit());
}

// All limits
{
let counts = Counts {
user: 6,
device: 11,
wireguard_network: 2,
};
set_counts(counts);
let counts = get_counts();
assert!(counts.is_over_limit());
}
}
}
Loading

0 comments on commit 78a5c66

Please sign in to comment.