Skip to content

Commit

Permalink
Fix ipv6 input and database adminid constraints (#851)
Browse files Browse the repository at this point in the history
* fix building docs

* add libssl-dev dependency

* Update README.md

* add pkg-config dependency

* add curl dependency

* Update README.md

* Update README.md

* Update README.md

* add on delete cascade to adminid in the token table

* allow ipv6 input

---------

Co-authored-by: Robert Olejnik <[email protected]>
  • Loading branch information
t-aleksander and teon authored Nov 12, 2024
1 parent 9de1101 commit 0d58058
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 52 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
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;
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"get-text-width": "^1.0.3",
"hex-rgb": "^5.0.0",
"html-react-parser": "^5.1.1",
"ipaddr.js": "^2.2.0",
"itertools": "^2.2.3",
"lodash-es": "^4.17.21",
"numbro": "^2.4.0",
Expand Down
9 changes: 9 additions & 0 deletions web/pnpm-lock.yaml

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

1 change: 1 addition & 0 deletions web/src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,7 @@ const en: BaseTranslation = {
portMax: 'Maximum port is 65535.',
endpoint: 'Enter a valid endpoint.',
address: 'Enter a valid address.',
addressNetmask: 'Enter a valid address with a netmask.',
validPort: 'Enter a valid port.',
validCode: 'Code should have 6 digits.',
allowedIps: 'Only valid IP or domain is allowed.',
Expand Down
8 changes: 8 additions & 0 deletions web/src/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2101,6 +2101,10 @@ type RootTranslation = {
* E​n​t​e​r​ ​a​ ​v​a​l​i​d​ ​a​d​d​r​e​s​s​.
*/
address: string
/**
* E​n​t​e​r​ ​a​ ​v​a​l​i​d​ ​a​d​d​r​e​s​s​ ​w​i​t​h​ ​a​ ​n​e​t​m​a​s​k​.
*/
addressNetmask: string
/**
* E​n​t​e​r​ ​a​ ​v​a​l​i​d​ ​p​o​r​t​.
*/
Expand Down Expand Up @@ -6356,6 +6360,10 @@ export type TranslationFunctions = {
* Enter a valid address.
*/
address: () => LocalizedString
/**
* Enter a valid address with a netmask.
*/
addressNetmask: () => LocalizedString
/**
* Enter a valid port.
*/
Expand Down
3 changes: 2 additions & 1 deletion web/src/i18n/pl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -841,8 +841,9 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe
oneUppercase: 'Wymagana jedna duża litera.',
oneLowercase: 'Wymagana jedna mała litera.',
portMax: 'Maksymalny numer portu to 65535.',
endpoint: 'Wpisz prawidłowy punkt końcowy.',
endpoint: 'Wpisz poprawny adres.',
address: 'Wprowadź poprawny adres.',
addressNetmask: 'Wprowadź poprawny adres IP oraz maskę sieci.',
validPort: 'Wprowadź prawidłowy port.',
validCode: 'Kod powinien mieć 6 cyfr.',
allowedIps: 'Tylko poprawne adresy IP oraz domeny.',
Expand Down
47 changes: 35 additions & 12 deletions web/src/pages/network/NetworkEditForm/NetworkEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './style.scss';

import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import ipaddr from 'ipaddr.js';
import { isNull, omit, omitBy } from 'lodash-es';
import { useEffect, useMemo, useRef, useState } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
Expand All @@ -20,11 +21,7 @@ import { QueryKeys } from '../../../shared/queries';
import { Network } from '../../../shared/types';
import { titleCase } from '../../../shared/utils/titleCase';
import { trimObjectStrings } from '../../../shared/utils/trimObjectStrings.ts';
import {
validateIp,
validateIpOrDomain,
validateIpOrDomainList,
} from '../../../shared/validators';
import { validateIpOrDomain, validateIpOrDomainList } from '../../../shared/validators';
import { useNetworkPageStore } from '../hooks/useNetworkPageStore';

type FormFields = {
Expand Down Expand Up @@ -155,17 +152,43 @@ export const NetworkEditForm = () => {
if (!netmaskPresent) {
return false;
}
const ipValid = validateIp(value, true);
if (ipValid) {
const host = value.split('.')[3].split('/')[0];
if (host === '0') return false;
const ipValid = ipaddr.isValidCIDR(value);
if (!ipValid) {
return false;
}
const [address] = ipaddr.parseCIDR(value);
if (address.kind() === 'ipv6') {
const networkAddress = ipaddr.IPv6.networkAddressFromCIDR(value);
const broadcastAddress = ipaddr.IPv6.broadcastAddressFromCIDR(value);
if (
(address as ipaddr.IPv6).toNormalizedString() ===
networkAddress.toNormalizedString() ||
(address as ipaddr.IPv6).toNormalizedString() ===
broadcastAddress.toNormalizedString()
) {
return false;
}
} else {
const networkAddress = ipaddr.IPv4.networkAddressFromCIDR(value);
const broadcastAddress = ipaddr.IPv4.broadcastAddressFromCIDR(value);
if (
(address as ipaddr.IPv4).toNormalizedString() ===
networkAddress.toNormalizedString() ||
(address as ipaddr.IPv4).toNormalizedString() ===
broadcastAddress.toNormalizedString()
) {
return false;
}
}
return ipValid;
}, LL.form.error.address()),
}, LL.form.error.addressNetmask()),
endpoint: z
.string()
.min(1, LL.form.error.required())
.refine((val) => validateIpOrDomain(val), LL.form.error.endpoint()),
.refine(
(val) => validateIpOrDomain(val, false, true),
LL.form.error.endpoint(),
),
port: z
.number({
invalid_type_error: LL.form.error.required(),
Expand All @@ -179,7 +202,7 @@ export const NetworkEditForm = () => {
if (val === '' || !val) {
return true;
}
return validateIpOrDomainList(val, ',', true);
return validateIpOrDomainList(val, ',', false, true);
}, LL.form.error.allowedIps()),
allowed_groups: z.array(z.string().min(1, LL.form.error.minimumLength())),
mfa_enabled: z.boolean(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const SmtpSettingsForm = () => {
.string()
.min(1, LL.form.error.required())
.refine(
(val) => (!val ? true : validateIpOrDomain(val)),
(val) => (!val ? true : validateIpOrDomain(val, false, true)),
LL.form.error.endpoint(),
),
smtp_port: z
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { QueryKeys } from '../../../../shared/queries';
import { ModifyNetworkRequest } from '../../../../shared/types';
import { titleCase } from '../../../../shared/utils/titleCase';
import { trimObjectStrings } from '../../../../shared/utils/trimObjectStrings.ts';
import { validateIp, validateIpOrDomainList } from '../../../../shared/validators';
import { validateIpOrDomainList, validateIPv4 } from '../../../../shared/validators';
import { useWizardStore } from '../../hooks/useWizardStore';

type FormInputs = ModifyNetworkRequest['network'];
Expand Down Expand Up @@ -91,7 +91,7 @@ export const WizardNetworkConfiguration = () => {
if (!netmaskPresent) {
return false;
}
const ipValid = validateIp(value, true);
const ipValid = validateIPv4(value, true);
if (ipValid) {
const host = value.split('.')[3].split('/')[0];
if (host === '0') return false;
Expand Down
3 changes: 0 additions & 3 deletions web/src/shared/patterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ export const patternValidUrl = new RegExp(
export const patternValidDomain =
/^(?:(?:(?:[a-zA-z\-]+)\:\/{1,3})?(?:[a-zA-Z0-9])(?:[a-zA-Z0-9\-\.]){1,61}(?:\.[a-zA-Z]{2,})+|\[(?:(?:(?:[a-fA-F0-9]){1,4})(?::(?:[a-fA-F0-9]){1,4}){7}|::1|::)\]|(?:(?:[0-9]{1,3})(?:\.[0-9]{1,3}){3}))(?:\:[0-9]{1,5})?$/;

export const patternValidIp =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

export const patternSafeUsernameCharacters = /^[a-zA-Z0-9]+[a-zA-Z0-9.\-_]+$/;

export const patternSafePasswordCharacters =
Expand Down
47 changes: 33 additions & 14 deletions web/src/shared/validators.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { patternValidDomain, patternValidIp } from './patterns';
import ipaddr from 'ipaddr.js';

import { patternValidDomain } from './patterns';

// Returns flase when invalid
export const validateIpOrDomain = (val: string, allowMask = false): boolean => {
return validateIp(val, allowMask) || patternValidDomain.test(val);
export const validateIpOrDomain = (
val: string,
allowMask = false,
allowIPv6 = false,
): boolean => {
return (
(allowIPv6 && validateIPv6(val, allowMask)) ||
validateIPv4(val, allowMask) ||
patternValidDomain.test(val)
);
};

// Returns flase when invalid
Expand All @@ -14,7 +24,7 @@ export const validateIpList = (
const trimed = val.replace(' ', '');
const split = trimed.split(splitWith);
for (const value of split) {
if (!validateIp(value, allowMasks)) {
if (!validateIPv4(value, allowMasks)) {
return false;
}
}
Expand All @@ -26,31 +36,40 @@ export const validateIpOrDomainList = (
val: string,
splitWith = ',',
allowMasks = false,
allowIPv6 = false,
): boolean => {
const trimed = val.replace(' ', '');
const split = trimed.split(splitWith);
for (const value of split) {
if (!validateIp(value, allowMasks) && !patternValidDomain.test(value)) {
console.log(allowIPv6 && !validateIPv6(value, allowMasks));
if (
!validateIPv4(value, allowMasks) &&
!patternValidDomain.test(value) &&
(!allowIPv6 || !validateIPv6(value, allowMasks))
) {
return false;
}
}
return true;
};

// Returns flase when invalid
export const validateIp = (ip: string, allowMask = false): boolean => {
export const validateIPv4 = (ip: string, allowMask = false): boolean => {
if (allowMask) {
if (ip.includes('/')) {
ipaddr.IPv4.isValidCIDR(ip);
}
}
return ipaddr.IPv4.isValid(ip);
};

export const validateIPv6 = (ip: string, allowMask = false): boolean => {
if (allowMask) {
if (ip.includes('/')) {
const split = ip.split('/');
if (split.length !== 2) return true;
const ipValid = patternValidIp.test(split[0]);
if (split[1] === '') return false;
const mask = Number(split[1]);
const maskValid = mask >= 0 && mask <= 32;
return ipValid && maskValid;
ipaddr.IPv6.isValidCIDR(ip);
}
}
return patternValidIp.test(ip);
return ipaddr.IPv6.isValid(ip);
};

export const validatePort = (val: string) => {
Expand Down

0 comments on commit 0d58058

Please sign in to comment.