diff --git a/.github/workflows/chart.yml b/.github/workflows/chart.yml index ecb7ba3..4844f1f 100644 --- a/.github/workflows/chart.yml +++ b/.github/workflows/chart.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event_name == 'pull_request' }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 @@ -35,7 +35,7 @@ jobs: # ct lint requires Python 3.x to run following packages: # - yamale (https://github.com/23andMe/Yamale) # - yamllint (https://github.com/adrienverge/yamllint) - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.x' @@ -60,9 +60,9 @@ jobs: permissions: packages: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 542b924..d66756b 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -18,13 +18,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Get Version shell: bash @@ -32,14 +32,14 @@ jobs: - name: Login to Docker Hub if: github.event_name != 'pull_request' - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GitHub Container Registry if: github.event_name != 'pull_request' - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -47,7 +47,7 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: images: | wgportal/wg-portal @@ -68,7 +68,7 @@ jobs: type=semver,pattern=v{{major}} - name: Build and push Docker image - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: context: . push: ${{ github.event_name != 'pull_request' }} @@ -80,7 +80,7 @@ jobs: BUILD_VERSION=${{ env.BUILD_VERSION }} - name: Export binaries from images - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: context: . platforms: linux/amd64,linux/arm64,linux/arm/v7 @@ -96,7 +96,7 @@ jobs: done - name: Upload binaries - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: binaries path: binaries/wg-portal_linux* @@ -110,12 +110,12 @@ jobs: contents: write steps: - name: Download binaries - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: binaries - name: Create GitHub Release - uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 + uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: files: 'wg-portal_linux*' generate_release_notes: true diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 37fd61f..40c03f3 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -15,11 +15,11 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: 3.x diff --git a/Dockerfile b/Dockerfile index cee602a..c34d996 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN npm run build ###### # Build backend ###### -FROM --platform=${BUILDPLATFORM} golang:1.25-alpine AS builder +FROM --platform=${BUILDPLATFORM} golang:1.26-alpine AS builder # Set the working directory WORKDIR /build # Download dependencies @@ -50,7 +50,7 @@ COPY --from=builder /build/dist/wg-portal / ###### # Final image ###### -FROM alpine:3.22 +FROM alpine:3.23 # Install OS-level dependencies RUN apk add --no-cache bash curl iptables nftables openresolv wireguard-tools tzdata # Setup timezone diff --git a/README.md b/README.md index 9bc044f..c7eda8d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Pos * Docker ready * Can be used with existing WireGuard setups * Support for multiple WireGuard interfaces -* Supports multiple WireGuard backends (wgctrl or MikroTik) +* Supports multiple WireGuard backends (wgctrl, MikroTik, or pfSense) * Peer Expiry Feature * Handles route and DNS settings like wg-quick does * Exposes Prometheus metrics for monitoring and alerting diff --git a/cmd/wg-portal/main.go b/cmd/wg-portal/main.go index cd5fd7e..0592b82 100644 --- a/cmd/wg-portal/main.go +++ b/cmd/wg-portal/main.go @@ -47,7 +47,7 @@ func main() { rawDb, err := adapters.NewDatabase(cfg.Database) internal.AssertNoError(err) - database, err := adapters.NewSqlRepository(rawDb) + database, err := adapters.NewSqlRepository(rawDb, cfg) internal.AssertNoError(err) wireGuard, err := wireguard.NewControllerManager(cfg) @@ -84,7 +84,7 @@ func main() { internal.AssertNoError(err) userManager.StartBackgroundJobs(ctx) - authenticator, err := auth.NewAuthenticator(&cfg.Auth, cfg.Web.ExternalUrl, eventBus, userManager) + authenticator, err := auth.NewAuthenticator(&cfg.Auth, cfg.Web.ExternalUrl, cfg.Web.BasePath, eventBus, userManager) internal.AssertNoError(err) authenticator.StartBackgroundJobs(ctx) @@ -135,6 +135,7 @@ func main() { apiV0EndpointPeers := handlersV0.NewPeerEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendPeers) apiV0EndpointConfig := handlersV0.NewConfigEndpoint(cfg, apiV0Auth, wireGuard) apiV0EndpointTest := handlersV0.NewTestEndpoint(apiV0Auth) + apiV0EndpointWebsocket := handlersV0.NewWebsocketEndpoint(cfg, apiV0Auth, eventBus) apiFrontend := handlersV0.NewRestApi(apiV0Session, apiV0EndpointAuth, @@ -144,6 +145,7 @@ func main() { apiV0EndpointPeers, apiV0EndpointConfig, apiV0EndpointTest, + apiV0EndpointWebsocket, ) // endregion API v0 (SPA frontend) diff --git a/config.yml.sample b/config.yml.sample index 039d034..5d4c594 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -11,7 +11,12 @@ core: web: external_url: http://localhost:8888 + base_path: "" request_logging: true + frontend_filepath: "" + +mail: + templates_path: "" webhook: url: "" @@ -93,4 +98,16 @@ auth: admin_value_regex: ^true$ admin_group_regex: ^admin-group-name$ registration_enabled: true - log_user_info: true \ No newline at end of file + log_user_info: true + +backend: + default: local + pfsense: + - id: pfsense1 + display_name: "Main pfSense Firewall" + api_url: "https://pfsense.example.com" # Base URL without /api/v2 (endpoints already include it) + api_key: "your-api-key" # Generate in pfSense under 'System' -> 'REST API' -> 'Keys' + api_verify_tls: true + api_timeout: 30s + concurrency: 5 + debug: false \ No newline at end of file diff --git a/docs/documentation/configuration/overview.md b/docs/documentation/configuration/overview.md index cad0040..295e362 100644 --- a/docs/documentation/configuration/overview.md +++ b/docs/documentation/configuration/overview.md @@ -74,6 +74,7 @@ mail: from: Wireguard Portal link_only: false allow_peer_email: false + templates_path: "" auth: oidc: [] @@ -87,6 +88,7 @@ auth: web: listening_address: :8888 external_url: http://localhost:8888 + base_path: "" site_company_name: WireGuard Portal site_title: WireGuard Portal session_identifier: wgPortalSession @@ -96,6 +98,7 @@ web: expose_host_info: false cert_file: "" key_File: "" + frontend_filepath: "" webhook: url: "" @@ -154,12 +157,14 @@ More advanced options are found in the subsequent `Advanced` section. ### `create_default_peer` - **Default:** `false` - **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER` -- **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for **all** server interfaces. +- **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set. +- **Important:** This option is only effective for interfaces where the "Create default peer" flag is set (via the UI). ### `create_default_peer_on_creation` - **Default:** `false` - **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION` -- **Description:** If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for **all** server interfaces. +- **Description:** If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set. +- **Important:** This option requires [create_default_peer](#create_default_peer) to be enabled. ### `re_enable_peer_after_user_enable` - **Default:** `true` @@ -485,6 +490,11 @@ To send emails to all peers that have a valid email-address as user-identifier, If false, and the peer has no valid user record linked, emails will not be sent. If a peer has linked a valid user, the email address is always taken from the user record. +### `templates_path` +- **Default:** *(empty)* +- **Environment Variable:** `WG_PORTAL_MAIL_TEMPLATES_PATH` +- **Description:** Path to the email template files that override embedded templates. Check [usage documentation](../usage/mail-templates.md) for an example.` + --- ## Auth @@ -793,9 +803,16 @@ Without a valid `external_url`, the login process may fail due to CSRF protectio ### `external_url` - **Default:** `http://localhost:8888` - **Environment Variable:** `WG_PORTAL_WEB_EXTERNAL_URL` -- **Description:** The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects. +- **Description:** The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects. + The external URL must not contain a path component or trailing slash. If you want to serve WireGuard Portal on a subpath, use the `base_path` setting. **Important:** If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server. +### `base_path` +- **Default:** *(empty)* +- **Environment Variable:** `WG_PORTAL_WEB_BASE_PATH` +- **Description:** The base path for the web server (e.g., `/wgportal`). + By default (meaning an empty value), the portal will be served from the root path `/`. + ### `site_company_name` - **Default:** `WireGuard Portal` - **Environment Variable:** `WG_PORTAL_WEB_SITE_COMPANY_NAME` @@ -841,6 +858,14 @@ Without a valid `external_url`, the login process may fail due to CSRF protectio - **Environment Variable:** `WG_PORTAL_WEB_KEY_FILE` - **Description:** (Optional) Path to the TLS certificate key file. +### `frontend_filepath` +- **Default:** *(empty)* +- **Environment Variable:** `WG_PORTAL_WEB_FRONTEND_FILEPATH` +- **Description:** Optional base directory from which the web frontend is served. Check out the [building](../getting-started/sources.md) documentation for more information on how to compile the frontend assets. + - If the directory contains at least one file (recursively), these files are served at `/app`, overriding the embedded frontend assets. + - If the directory is empty or does not exist on startup, the embedded frontend is copied into this directory automatically and then served. + - If left empty, the embedded frontend is served and no files are written to disk. + --- ## Webhook diff --git a/docs/documentation/getting-started/binaries.md b/docs/documentation/getting-started/binaries.md index 5aed082..54eda62 100644 --- a/docs/documentation/getting-started/binaries.md +++ b/docs/documentation/getting-started/binaries.md @@ -9,6 +9,11 @@ Make sure that you download the correct binary for your architecture. The availa - `wg-portal_linux_arm64` - Linux ARM 64-bit - `wg-portal_linux_arm_v7` - Linux ARM 32-bit +### Released versions + +To download a specific version, replace `${WG_PORTAL_VERSION}` with the desired version (or set an environment variable). +All official release versions can be found on the [GitHub Releases Page](https://github.com/h44z/wg-portal/releases). + With `curl`: ```shell @@ -27,16 +32,74 @@ with `gh cli`: gh release download ${WG_PORTAL_VERSION} --repo h44z/wg-portal --output wg-portal --pattern '*amd64' ``` +The downloaded file will be named `wg-portal` and can be moved to a directory of your choice, see [Install](#install) for more information. + +### Unreleased versions (master branch builds) + +Unreleased versions can be fetched directly from the artifacts section of the [GitHub Workflow](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml?query=branch%3Amaster). ## Install +The following command can be used to install the downloaded binary (`wg-portal`) to `/opt/wg-portal/wg-portal`. It ensures that the binary is executable. + ```shell sudo mkdir -p /opt/wg-portal sudo install wg-portal /opt/wg-portal/ ``` -## Unreleased versions (master branch builds) +To handle tasks such as restarting the service or configuring automatic startup, it is recommended to use a process manager like [systemd](https://systemd.io/). +Refer to [Systemd Service Setup](#systemd-service-setup) for instructions. -Unreleased versions can be fetched directly from the artifacts section of the [GitHub Workflow](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml?query=branch%3Amaster). +## Systemd Service Setup +> **Note:** To run WireGuard Portal as systemd service, you need to download the binary for your architecture beforehand. +> +> The following examples assume that you downloaded the binary to `/opt/wg-portal/wg-portal`. +> The configuration file is expected to be located at `/opt/wg-portal/config.yml`. + +To run WireGuard Portal as a systemd service, you can create a service unit file. The easiest way to do this is by using `systemctl edit`: + +```shell +sudo systemctl edit --force --full wg-portal.service +``` + +Paste the following content into the editor and adjust the variables to your needs: + +```ini +[Unit] +Description=WireGuard Portal +ConditionPathExists=/opt/wg-portal/wg-portal +After=network.target + +[Service] +Type=simple +User=root +Group=root +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW + +Restart=on-failure +RestartSec=10 + +WorkingDirectory=/opt/wg-portal +Environment=WG_PORTAL_CONFIG=/opt/wg-portal/config.yml +ExecStart=/opt/wg-portal/wg-portal + +[Install] +WantedBy=multi-user.target +``` + +Alternatively, you can create or modify the file manually in `/etc/systemd/system/wg-portal.service`. +For systemd to pick up the changes, you need to reload the daemon: + +```shell +sudo systemctl daemon-reload +``` + +After creating the service file, you can enable and start the service: + +```shell +sudo systemctl enable --now wg-portal.service +``` + +To check status and log output, use: `sudo systemctl status wg-portal.service` or `sudo journalctl -u wg-portal.service`. diff --git a/docs/documentation/getting-started/reverse-proxy.md b/docs/documentation/getting-started/reverse-proxy.md index e1fad58..4bf07ae 100644 --- a/docs/documentation/getting-started/reverse-proxy.md +++ b/docs/documentation/getting-started/reverse-proxy.md @@ -84,6 +84,16 @@ web: external_url: https://wg.domain.com ``` +If you want to serve the web interface on a different base-path, you can also set the `web.base_path` option: + +```yaml +web: + external_url: https://wg.domain.com + base_path: /subpath +``` + +The WireGuard Portal will then be available at `https://wg.domain.com/subpath`. + ### Built-in TLS If you prefer to let WireGuard Portal handle TLS itself, you can use the built-in TLS support. diff --git a/docs/documentation/getting-started/sources.md b/docs/documentation/getting-started/sources.md index 36e9b71..9b3d3d6 100644 --- a/docs/documentation/getting-started/sources.md +++ b/docs/documentation/getting-started/sources.md @@ -4,7 +4,7 @@ To build the application from source files, use the Makefile provided in the rep - [Git](https://git-scm.com/downloads) - [Make](https://www.gnu.org/software/make/) -- [Go](https://go.dev/dl/): `>=1.24.0` +- [Go](https://go.dev/dl/): `>=1.25.0` - [Node.js with npm](https://nodejs.org/en/download): `node>=18, npm>=9` ## Build diff --git a/docs/documentation/rest-api/swagger.yaml b/docs/documentation/rest-api/swagger.yaml index 20f5c87..9b3b0ac 100644 --- a/docs/documentation/rest-api/swagger.yaml +++ b/docs/documentation/rest-api/swagger.yaml @@ -443,6 +443,18 @@ definitions: maxLength: 64 minLength: 32 type: string + AuthSources: + description: The source of the user. This field is optional. + example: + - db + items: + enum: + - db + - ldap + - oauth + type: string + readOnly: true + type: array Department: description: The department of the user. This field is optional. example: Software Development @@ -503,19 +515,6 @@ definitions: description: The phone number of the user. This field is optional. example: "+1234546789" type: string - ProviderName: - description: The name of the authentication provider. This field is read-only. - example: "" - readOnly: true - type: string - Source: - description: The source of the user. This field is optional. - enum: - - db - - ldap - - oauth - example: db - type: string required: - Identifier type: object diff --git a/docs/documentation/usage/authentication.md b/docs/documentation/usage/authentication.md new file mode 100644 index 0000000..76a9d67 --- /dev/null +++ b/docs/documentation/usage/authentication.md @@ -0,0 +1,152 @@ +WireGuard Portal supports multiple authentication mechanisms to manage user access. This includes + + - Local user accounts + - LDAP authentication + - OAuth2 and OIDC authentication + - Passkey authentication (WebAuthn) + +Users can have two roles which limit their permissions in WireGuard Portal: + +- **User**: Can manage their own account and peers. +- **Admin**: Can manage all users and peers, including the ability to manage WireGuard interfaces. + +In general, each user is identified by a _unique identifier_. If the same user identifier exists across multiple authentication sources, WireGuard Portal automatically merges those accounts into a single user record. +When a user is associated with multiple authentication sources, their information in WireGuard Portal is updated based on the most recently logged-in source. For more details, see [User Synchronization](./user-sync.md) documentation. + +## Password Authentication + +WireGuard Portal supports username and password authentication for both local and LDAP-backed accounts. +Local users are stored in the database, while LDAP users are authenticated against an external LDAP server. + +On initial startup, WireGuard Portal automatically creates a local admin account with the password `wgportal-default`. +> :warning: This password must be changed immediately after the first login. + +The minimum password length for all local users can be configured in the [`auth`](../configuration/overview.md#auth) +section of the configuration file. The default value is **16** characters, see [`min_password_length`](../configuration/overview.md#min_password_length). +The minimum password length is also enforced for the default admin user. + + +## Passkey (WebAuthn) Authentication + +Besides the standard authentication mechanisms, WireGuard Portal supports Passkey authentication. +This feature is enabled by default and can be configured in the [`webauthn`](../configuration/overview.md#webauthn-passkeys) section of the configuration file. + +Users can register multiple Passkeys to their account. These Passkeys can be used to log in to the web UI as long as the user is not locked. +> :warning: Passkey authentication does not disable password authentication. The password can still be used to log in (e.g., as a fallback). + +To register a Passkey, open the settings page *(1)* in the web UI and click on the "Register Passkey" *(2)* button. + +![Passkey UI](../../assets/images/passkey_setup.png) + + +## OAuth2 and OIDC Authentication + +WireGuard Portal supports OAuth2 and OIDC authentication. You can use any OAuth2 or OIDC provider that supports the authorization code flow, +such as Google, GitHub, or Keycloak. + +For OAuth2 or OIDC to work, you need to configure the [`external_url`](../configuration/overview.md#external_url) property in the [`web`](../configuration/overview.md#web) section of the configuration file. +If you are planning to expose the portal to the internet, make sure that the `external_url` is configured to use HTTPS. + +To add OIDC or OAuth2 authentication to WireGuard Portal, create a Client-ID and Client-Secret in your OAuth2 provider and +configure a new authentication provider in the [`auth`](../configuration/overview.md#auth) section of the configuration file. +Make sure that each configured provider has a unique `provider_name` property set. Samples can be seen [here](../configuration/examples.md). + +#### Limiting Login to Specific Domains + +You can limit the login to specific domains by setting the `allowed_domains` property for OAuth2 or OIDC providers. +This property is a comma-separated list of domains that are allowed to log in. The user's email address is checked against this list. +For example, if you want to allow only users with an email address ending in `outlook.com` to log in, set the property as follows: + +```yaml +auth: + oidc: + - provider_name: "oidc1" + # ... other settings + allowed_domains: + - "outlook.com" +``` + +#### Limit Login to Existing Users + +You can limit the login to existing users only by setting the `registration_enabled` property to `false` for OAuth2 or OIDC providers. +If registration is enabled, new users will be created in the database when they log in for the first time. + +#### Admin Mapping + +You can map users to admin roles based on their attributes in the OAuth2 or OIDC provider. To do this, set the `admin_mapping` property for the provider. +Administrative access can either be mapped by a specific attribute or by group membership. + +**Attribute specific mapping** can be achieved by setting the `admin_value_regex` and the `is_admin` property. +The `admin_value_regex` property is a regular expression that is matched against the value of the `is_admin` attribute. +The user is granted admin access if the regex matches the attribute value. + +Example: +```yaml +auth: + oidc: + - provider_name: "oidc1" + # ... other settings + field_map: + is_admin: "wg_admin_prop" + admin_mapping: + admin_value_regex: "^true$" +``` +The example above will grant admin access to users with the `wg_admin_prop` attribute set to `true`. + +**Group membership mapping** can be achieved by setting the `admin_group_regex` and `user_groups` property. +The `admin_group_regex` property is a regular expression that is matched against the group names of the user. +The user is granted admin access if the regex matches any of the group names. + +Example: +```yaml +auth: + oidc: + - provider_name: "oidc1" + # ... other settings + field_map: + user_groups: "groups" + admin_mapping: + admin_group_regex: "^the-admin-group$" +``` +The example above will grant admin access to users who are members of the `the-admin-group` group. + + +## LDAP Authentication + +WireGuard Portal supports LDAP authentication. You can use any LDAP server that supports the LDAP protocol, such as Active Directory or OpenLDAP. +Multiple LDAP servers can be configured in the [`auth`](../configuration/overview.md#auth) section of the configuration file. +WireGuard Portal remembers the authentication provider of the user and therefore avoids conflicts between multiple LDAP providers. + +To configure LDAP authentication, create a new [`ldap`](../configuration/overview.md#ldap) authentication provider in the [`auth`](../configuration/overview.md#auth) section of the configuration file. + +### Limiting Login to Specific Users + +You can limit the login to specific users by setting the `login_filter` property for LDAP provider. This filter uses the LDAP search filter syntax. +The username can be inserted into the query by placing the `{{login_identifier}}` placeholder in the filter. This placeholder will then be replaced with the username entered by the user during login. + +For example, if you want to allow only users with the `objectClass` attribute set to `organizationalPerson` to log in, set the property as follows: + +```yaml +auth: + ldap: + - provider_name: "ldap1" + # ... other settings + login_filter: "(&(objectClass=organizationalPerson)(uid={{login_identifier}}))" +``` + +The `login_filter` should always be designed to return at most one user. + +### Limit Login to Existing Users + +You can limit the login to existing users only by setting the `registration_enabled` property to `false` for LDAP providers. +If registration is enabled, new users will be created in the database when they log in for the first time. + +### Admin Mapping + +You can map users to admin roles based on their group membership in the LDAP server. To do this, set the `admin_group` and `memberof` property for the provider. +The `admin_group` property defines the distinguished name of the group that is allowed to log in as admin. +All groups that are listed in the `memberof` attribute of the user will be checked against this group. If one of the groups matches, the user is granted admin access. + + +## User Synchronization + diff --git a/docs/documentation/usage/backends.md b/docs/documentation/usage/backends.md index e891d95..aeac9d6 100644 --- a/docs/documentation/usage/backends.md +++ b/docs/documentation/usage/backends.md @@ -8,6 +8,7 @@ A global default backend determines where newly created interfaces go (unless yo **Supported backends:** - **Local** (default): Manages interfaces on the host running WireGuard Portal (Linux WireGuard via wgctrl). Use this when the portal should directly configure wg devices on the same server. - **MikroTik** RouterOS (_beta_): Manages interfaces and peers on MikroTik devices via the RouterOS REST API. Use this to control WG interfaces on RouterOS v7+. +- **pfSense** (_alpha_): Manages interfaces and peers on pfSense firewalls via the pfSense REST API. How backend selection works: - The default backend is configured at `backend.default` (_local_ or the id of a defined MikroTik backend). @@ -54,4 +55,37 @@ backend: ### Known limitations: - The MikroTik backend is still in beta. Some features may not work as expected. -- Not all WireGuard Portal features are supported yet (e.g., no support for interface hooks) \ No newline at end of file +- Not all WireGuard Portal features are supported yet (e.g., no support for interface hooks) + +## Configuring pfSense backends + +> :warning: The pfSense backend is currently **alpha**. Only basic interface and peer CRUD are supported. Traffic statistics (rx/tx, last handshake) are not exposed by the pfSense REST API and will show as empty. + +The pfSense backend talks to the pfSense REST API (pfSense Plus / CE with the REST API package installed). Point the backend at the appliance hostname without appending `/api/v2` — the portal appends `/api/v2` automatically. + +### Prerequisites on pfSense: +- pfSense with the REST API package enabled (`System -> API`) and WireGuard configured. +- An API key with permissions for WireGuard endpoints. If you use a read-only key, set `core.restore_state: false` in `config.yml` to avoid write attempts at startup. +- HTTPS recommended; set `api_verify_tls: false` only for lab/self-signed setups. + +Example WireGuard Portal configuration: + +```yaml +backend: + # default backend decides where new interfaces are created + default: pfsense1 + + pfsense: + - id: pfsense1 # unique id, not "local" + display_name: Main pfSense # optional nice name + api_url: https://pfsense.example.com # no trailing /api/v2 + api_key: your-api-key + api_verify_tls: true + api_timeout: 30s + concurrency: 5 + debug: false +``` + +### Known limitations: +- Alpha quality: behavior and API coverage may change. +- Statistics (rx/tx bytes, last handshake) are not available from the pfSense REST API today. diff --git a/docs/documentation/usage/general.md b/docs/documentation/usage/general.md index 6ebf5be..37b2038 100644 --- a/docs/documentation/usage/general.md +++ b/docs/documentation/usage/general.md @@ -14,7 +14,7 @@ WireGuard Interfaces can be categorized into three types: ## Accessing the Web UI The web UI should be accessed via the URL specified in the `external_url` property of the configuration file. -By default, WireGuard Portal listens on port `8888` for HTTP connections. Check the [Security](security.md) section for more information on securing the web UI. +By default, WireGuard Portal listens on port `8888` for HTTP connections. Check the [Security](security.md) or [Authentication](authentication.md) sections for more information on securing the web UI. So the default URL to access the web UI is: diff --git a/docs/documentation/usage/ldap.md b/docs/documentation/usage/ldap.md deleted file mode 100644 index d0ebc77..0000000 --- a/docs/documentation/usage/ldap.md +++ /dev/null @@ -1,37 +0,0 @@ -WireGuard Portal lets you hook up any LDAP server such as Active Directory or OpenLDAP for both authentication and user sync. -You can even register multiple LDAP servers side-by-side. When someone logs in via LDAP, their specific provider is remembered, -so there's no risk of cross-provider conflicts. Details on the log-in process can be found in the [Security](security.md#ldap-authentication) documentation. - -If you enable LDAP synchronization, all users within the LDAP directory will be created automatically in the WireGuard Portal database if they do not exist. -If a user is disabled or deleted in LDAP, the user will be disabled in WireGuard Portal as well. -The synchronization process can be fine-tuned by multiple parameters, which are described below. - -## LDAP Synchronization - -WireGuard Portal can automatically synchronize users from LDAP to the database. -To enable this feature, set the `sync_interval` property in the LDAP provider configuration to a value greater than "0". -The value is a string representing a duration, such as "15m" for 15 minutes or "1h" for 1 hour (check the [exact format definition](https://pkg.go.dev/time#ParseDuration) for details). -The synchronization process will run in the background and synchronize users from LDAP to the database at the specified interval. -Also make sure that the `sync_filter` property is a well-formed LDAP filter, or synchronization will fail. - -### Limiting Synchronization to Specific Users - -Use the `sync_filter` property in your LDAP provider block to restrict which users get synchronized. -It accepts any valid LDAP search filter, only entries matching that filter will be pulled into the portal's database. - -For example, to import only users with a `mail` attribute: -```yaml -auth: - ldap: - - id: ldap - # ... other settings - sync_filter: (mail=*) -``` - -### Disable Missing Users - -If you set the `disable_missing` property to `true`, any user that is not found in LDAP during synchronization will be disabled in WireGuard Portal. -All peers associated with that user will also be disabled. - -If you want a user and its peers to be automatically re-enabled once they are found in LDAP again, set the `auto_re_enable` property to `true`. -This will only re-enable the user if they where disabled by the synchronization process. Manually disabled users will not be re-enabled. \ No newline at end of file diff --git a/docs/documentation/usage/mail-templates.md b/docs/documentation/usage/mail-templates.md new file mode 100644 index 0000000..b61150e --- /dev/null +++ b/docs/documentation/usage/mail-templates.md @@ -0,0 +1,49 @@ +WireGuard Portal sends emails when you share a configuration with a user. +By default, the application uses embedded templates. You can fully customize these emails by pointing the Portal +to a folder containing your own templates. If the folder is empty on startup, the default embedded templates +are written there to get you started. + +## Configuration + +To enable custom templates, set the `mail.templates_path` option in the application configuration file +or the `WG_PORTAL_MAIL_TEMPLATES_PATH` environment variable to a valid folder path. + +For example: + +```yaml +mail: + # ... other mail options ... + # Path where custom email templates (.gotpl and .gohtml) are stored. + # If the directory is empty on startup, the default embedded templates + # will be written there so you can modify them. + # Leave empty to use embedded templates only. + templates_path: "/opt/wg-portal/mail-templates" +``` + +## Template files and names + +The system expects the following template names. Place files with these names in your `templates_path` to override the defaults. +You do not need to override all templates, only the ones you want to customize should be present. + +- Text templates (`.gotpl`): + - `mail_with_link.gotpl` + - `mail_with_attachment.gotpl` +- HTML templates (`.gohtml`): + - `mail_with_link.gohtml` + - `mail_with_attachment.gohtml` + +Both [text](https://pkg.go.dev/text/template) and [HTML templates](https://pkg.go.dev/html/template) are standard Go +templates and receive the following data fields, depending on the email type: + +- Common fields: + - `PortalUrl` (string) - external URL of the Portal + - `PortalName` (string) - site title/company name + - `User` (*domain.User) - the recipient user (may be partially populated when sending to a peer email) +- Link email (`mail_with_link.*`): + - `Link` (string) - the download link +- Attachment email (`mail_with_attachment.*`): + - `ConfigFileName` (string) - filename of the attached WireGuard config + - `QrcodePngName` (string) - CID content-id of the embedded QR code image + +Tip: You can inspect the embedded templates in the repository under [`internal/app/mail/tpl_files/`](https://github.com/h44z/wg-portal/tree/master/internal/app/mail/tpl_files) for reference. +When the directory at `templates_path` is empty, these files are copied to your folder so you can edit them in place. diff --git a/docs/documentation/usage/security.md b/docs/documentation/usage/security.md index 41f36aa..08024b2 100644 --- a/docs/documentation/usage/security.md +++ b/docs/documentation/usage/security.md @@ -1,153 +1,12 @@ This section describes the security features available to administrators for hardening WireGuard Portal and protecting its data. -## Authentication +## Database Encryption -WireGuard Portal supports multiple authentication methods, including: - -- Local user accounts -- LDAP authentication -- OAuth and OIDC authentication -- Passkey authentication (WebAuthn) - -Users can have two roles which limit their permissions in WireGuard Portal: - -- **User**: Can manage their own account and peers. -- **Admin**: Can manage all users and peers, including the ability to manage WireGuard interfaces. - -### Password Security - -WireGuard Portal supports username and password authentication for both local and LDAP-backed accounts. -Local users are stored in the database, while LDAP users are authenticated against an external LDAP server. - -On initial startup, WireGuard Portal automatically creates a local admin account with the password `wgportal-default`. -> :warning: This password must be changed immediately after the first login. - -The minimum password length for all local users can be configured in the [`auth`](../configuration/overview.md#auth) -section of the configuration file. The default value is **16** characters, see [`min_password_length`](../configuration/overview.md#min_password_length). -The minimum password length is also enforced for the default admin user. - - -### Passkey (WebAuthn) Authentication - -Besides the standard authentication mechanisms, WireGuard Portal supports Passkey authentication. -This feature is enabled by default and can be configured in the [`webauthn`](../configuration/overview.md#webauthn-passkeys) section of the configuration file. - -Users can register multiple Passkeys to their account. These Passkeys can be used to log in to the web UI as long as the user is not locked. -> :warning: Passkey authentication does not disable password authentication. The password can still be used to log in (e.g., as a fallback). - -To register a Passkey, open the settings page *(1)* in the web UI and click on the "Register Passkey" *(2)* button. - -![Passkey UI](../../assets/images/passkey_setup.png) - - -### OAuth and OIDC Authentication - -WireGuard Portal supports OAuth and OIDC authentication. You can use any OAuth or OIDC provider that supports the authorization code flow, -such as Google, GitHub, or Keycloak. - -For OAuth or OIDC to work, you need to configure the [`external_url`](../configuration/overview.md#external_url) property in the [`web`](../configuration/overview.md#web) section of the configuration file. -If you are planning to expose the portal to the internet, make sure that the `external_url` is configured to use HTTPS. - -To add OIDC or OAuth authentication to WireGuard Portal, create a Client-ID and Client-Secret in your OAuth provider and -configure a new authentication provider in the [`auth`](../configuration/overview.md#auth) section of the configuration file. -Make sure that each configured provider has a unique `provider_name` property set. Samples can be seen [here](../configuration/examples.md). - -#### Limiting Login to Specific Domains - -You can limit the login to specific domains by setting the `allowed_domains` property for OAuth or OIDC providers. -This property is a comma-separated list of domains that are allowed to log in. The user's email address is checked against this list. -For example, if you want to allow only users with an email address ending in `outlook.com` to log in, set the property as follows: - -```yaml -auth: - oidc: - - provider_name: "oidc1" - # ... other settings - allowed_domains: - - "outlook.com" -``` - -#### Limit Login to Existing Users - -You can limit the login to existing users only by setting the `registration_enabled` property to `false` for OAuth or OIDC providers. -If registration is enabled, new users will be created in the database when they log in for the first time. - -#### Admin Mapping - -You can map users to admin roles based on their attributes in the OAuth or OIDC provider. To do this, set the `admin_mapping` property for the provider. -Administrative access can either be mapped by a specific attribute or by group membership. - -**Attribute specific mapping** can be achieved by setting the `admin_value_regex` and the `is_admin` property. -The `admin_value_regex` property is a regular expression that is matched against the value of the `is_admin` attribute. -The user is granted admin access if the regex matches the attribute value. - -Example: -```yaml -auth: - oidc: - - provider_name: "oidc1" - # ... other settings - field_map: - is_admin: "wg_admin_prop" - admin_mapping: - admin_value_regex: "^true$" -``` -The example above will grant admin access to users with the `wg_admin_prop` attribute set to `true`. - -**Group membership mapping** can be achieved by setting the `admin_group_regex` and `user_groups` property. -The `admin_group_regex` property is a regular expression that is matched against the group names of the user. -The user is granted admin access if the regex matches any of the group names. - -Example: -```yaml -auth: - oidc: - - provider_name: "oidc1" - # ... other settings - field_map: - user_groups: "groups" - admin_mapping: - admin_group_regex: "^the-admin-group$" -``` -The example above will grant admin access to users who are members of the `the-admin-group` group. - - -### LDAP Authentication - -WireGuard Portal supports LDAP authentication. You can use any LDAP server that supports the LDAP protocol, such as Active Directory or OpenLDAP. -Multiple LDAP servers can be configured in the [`auth`](../configuration/overview.md#auth) section of the configuration file. -WireGuard Portal remembers the authentication provider of the user and therefore avoids conflicts between multiple LDAP providers. - -To configure LDAP authentication, create a new [`ldap`](../configuration/overview.md#ldap) authentication provider in the [`auth`](../configuration/overview.md#auth) section of the configuration file. - -#### Limiting Login to Specific Users - -You can limit the login to specific users by setting the `login_filter` property for LDAP provider. This filter uses the LDAP search filter syntax. -The username can be inserted into the query by placing the `{{login_identifier}}` placeholder in the filter. This placeholder will then be replaced with the username entered by the user during login. - -For example, if you want to allow only users with the `objectClass` attribute set to `organizationalPerson` to log in, set the property as follows: - -```yaml -auth: - ldap: - - provider_name: "ldap1" - # ... other settings - login_filter: "(&(objectClass=organizationalPerson)(uid={{login_identifier}}))" -``` - -The `login_filter` should always be designed to return at most one user. - -#### Limit Login to Existing Users - -You can limit the login to existing users only by setting the `registration_enabled` property to `false` for LDAP providers. -If registration is enabled, new users will be created in the database when they log in for the first time. - -#### Admin Mapping - -You can map users to admin roles based on their group membership in the LDAP server. To do this, set the `admin_group` and `memberof` property for the provider. -The `admin_group` property defines the distinguished name of the group that is allowed to log in as admin. -All groups that are listed in the `memberof` attribute of the user will be checked against this group. If one of the groups matches, the user is granted admin access. +WireGuard Portal supports multiple database backends. To reduce the risk of data exposure, sensitive information stored in the database can be encrypted. +To enable encryption, set the [`encryption_passphrase`](../configuration/overview.md#database) in the database configuration section. +> :warning: Important: Once encryption is enabled, it cannot be disabled, and the passphrase cannot be changed! +> Only new or updated records will be encrypted; existing data remains in plaintext until it’s next modified. ## UI and API Access @@ -157,4 +16,9 @@ WireGuard Portal provides a web UI and a REST API for user interaction. It is im It is recommended to use HTTPS for all communication with the portal to prevent eavesdropping. Event though, WireGuard Portal supports HTTPS out of the box, it is recommended to use a reverse proxy like Nginx or Traefik to handle SSL termination and other security features. -A detailed explanation is available in the [Reverse Proxy](../getting-started/reverse-proxy.md) section. \ No newline at end of file +A detailed explanation is available in the [Reverse Proxy](../getting-started/reverse-proxy.md) section. + +### Secure Authentication +To prevent unauthorized access, WireGuard Portal supports integrating with secure authentication providers such as LDAP, OAuth2, or Passkeys, see [Authentication](./authentication.md) for more details. +When possible, use centralized authentication and enforce multi-factor authentication (MFA) at the provider level for enhanced account security. +For local accounts, administrators should enforce strong password requirements. \ No newline at end of file diff --git a/docs/documentation/usage/user-sync.md b/docs/documentation/usage/user-sync.md new file mode 100644 index 0000000..b248ffb --- /dev/null +++ b/docs/documentation/usage/user-sync.md @@ -0,0 +1,46 @@ +For all external authentication providers (LDAP, OIDC, OAuth2), WireGuard Portal can automatically create a local user record upon the user's first successful login. +This behavior is controlled by the `registration_enabled` setting in each authentication provider's configuration. + +User information from external authentication sources is merged into the corresponding local WireGuard Portal user record whenever the user logs in. +Additionally, WireGuard Portal supports periodic synchronization of user data from an LDAP directory. + +To prevent overwriting local changes, WireGuard Portal allows you to set a per-user flag that disables synchronization of external attributes. +When this flag is set, the user in WireGuard Portal will not be updated automatically during log-ins or LDAP synchronization. + +### LDAP Synchronization + +WireGuard Portal lets you hook up any LDAP server such as Active Directory or OpenLDAP for both authentication and user sync. +You can even register multiple LDAP servers side-by-side. Details on the log-in process can be found in the [LDAP Authentication](./authentication.md#ldap-authentication) section. + +If you enable LDAP synchronization, all users within the LDAP directory will be created automatically in the WireGuard Portal database if they do not exist. +If a user is disabled or deleted in LDAP, the user will be disabled in WireGuard Portal as well. +The synchronization process can be fine-tuned by multiple parameters, which are described below. + +#### Synchronization Parameters + +To enable the LDAP sycnhronization this feature, set the `sync_interval` property in the LDAP provider configuration to a value greater than "0". +The value is a string representing a duration, such as "15m" for 15 minutes or "1h" for 1 hour (check the [exact format definition](https://pkg.go.dev/time#ParseDuration) for details). +The synchronization process will run in the background and synchronize users from LDAP to the database at the specified interval. +Also make sure that the `sync_filter` property is a well-formed LDAP filter, or synchronization will fail. + +##### Limiting Synchronization to Specific Users + +Use the `sync_filter` property in your LDAP provider block to restrict which users get synchronized. +It accepts any valid LDAP search filter, only entries matching that filter will be pulled into the portal's database. + +For example, to import only users with a `mail` attribute: +```yaml +auth: + ldap: + - id: ldap + # ... other settings + sync_filter: (mail=*) +``` + +##### Disable Missing Users + +If you set the `disable_missing` property to `true`, any user that is not found in LDAP during synchronization will be disabled in WireGuard Portal. +All peers associated with that user will also be disabled. + +If you want a user and its peers to be automatically re-enabled once they are found in LDAP again, set the `auto_re_enable` property to `true`. +This will only re-enable the user if they were disabled by the synchronization process. Manually disabled users will not be re-enabled. \ No newline at end of file diff --git a/docs/documentation/usage/webhooks.md b/docs/documentation/usage/webhooks.md index 1d0c692..8933840 100644 --- a/docs/documentation/usage/webhooks.md +++ b/docs/documentation/usage/webhooks.md @@ -68,26 +68,32 @@ All payload models are encoded as JSON objects. Fields with empty values might b #### User Payload (entity: `user`) -| JSON Field | Type | Description | -|----------------|-------------|-----------------------------------| -| CreatedBy | string | Creator identifier | -| UpdatedBy | string | Last updater identifier | -| CreatedAt | time.Time | Time of creation | -| UpdatedAt | time.Time | Time of last update | -| Identifier | string | Unique user identifier | -| Email | string | User email | -| Source | string | Authentication source | -| ProviderName | string | Name of auth provider | -| IsAdmin | bool | Whether user has admin privileges | -| Firstname | string | User's first name (optional) | -| Lastname | string | User's last name (optional) | -| Phone | string | Contact phone number (optional) | -| Department | string | User's department (optional) | -| Notes | string | Additional notes (optional) | -| Disabled | *time.Time | When user was disabled | -| DisabledReason | string | Reason for deactivation | -| Locked | *time.Time | When user account was locked | -| LockedReason | string | Reason for being locked | +| JSON Field | Type | Description | +|----------------|---------------|-----------------------------------| +| CreatedBy | string | Creator identifier | +| UpdatedBy | string | Last updater identifier | +| CreatedAt | time.Time | Time of creation | +| UpdatedAt | time.Time | Time of last update | +| Identifier | string | Unique user identifier | +| Email | string | User email | +| AuthSources | []AuthSource | Authentication sources | +| IsAdmin | bool | Whether user has admin privileges | +| Firstname | string | User's first name (optional) | +| Lastname | string | User's last name (optional) | +| Phone | string | Contact phone number (optional) | +| Department | string | User's department (optional) | +| Notes | string | Additional notes (optional) | +| Disabled | *time.Time | When user was disabled | +| DisabledReason | string | Reason for deactivation | +| Locked | *time.Time | When user account was locked | +| LockedReason | string | Reason for being locked | + +`AuthSource`: + +| JSON Field | Type | Description | +|--------------|---------------|-----------------------------------------------------| +| Source | string | The authentication source (e.g. LDAP, OAuth, or DB) | +| ProviderName | string | The identifier of the authentication provider | #### Peer Payload (entity: `peer`) diff --git a/frontend/index.html b/frontend/index.html index 287ef99..8aed737 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -9,6 +9,7 @@ - - - + + + + \ No newline at end of file diff --git a/internal/app/api/core/assets/tpl/prt_nav.gohtml b/internal/app/api/core/assets/tpl/prt_nav.gohtml index 9337daf..2ebd535 100644 --- a/internal/app/api/core/assets/tpl/prt_nav.gohtml +++ b/internal/app/api/core/assets/tpl/prt_nav.gohtml @@ -3,7 +3,7 @@ - Prolicht + Prolicht