Commit Graph

848 Commits

Author SHA1 Message Date
Stanislas Lange
796f35fd05 fix: show revoked clients in listClients() for fingerprint mode
In fingerprint mode, revoked clients are removed from the peer-fingerprint
block but their cert files still exist. Now we scan all certs in pki/issued/
and mark those not in the fingerprint block as 'revoked'.
2025-12-29 12:00:11 +01:00
Stanislas Lange
257b3ed8fc test: update shared client config after server renewal 2025-12-29 11:43:09 +01:00
Stanislas Lange
1bbf6cc1f7 refactor: address Copilot review feedback
- Remove redundant --days param (EASYRSA_CERT_EXPIRE is sufficient)
- Use writeClientConfig for proper file handling during server renewal
2025-12-29 11:30:05 +01:00
Stanislas Lange
3ed7237aac fix: copy server key + add nopass for server renewal in fingerprint mode 2025-12-29 11:26:35 +01:00
Stanislas Lange
80778b925b fix: remove all old cert files before renewal in fingerprint mode 2025-12-29 11:12:19 +01:00
Stanislas Lange
a3d720c593 test: update duplicate client error message check 2025-12-29 11:01:18 +01:00
Stanislas Lange
d85120e488 style: fix shfmt formatting (remove space before <<<) 2025-12-29 10:55:22 +01:00
Stanislas Lange
9e280e8cdd Merge CI fix for fingerprint mode testing 2025-12-29 10:50:33 +01:00
Stanislas Lange
a8c53cf5be fix: support fingerprint mode in client management operations
In fingerprint mode (OpenVPN 2.6+ peer-fingerprint authentication),
easyrsa self-sign-* commands don't create/maintain index.txt, but
several client management functions depended on it.

This commit fixes:
- selectClient(): now uses getClientsFromFingerprints() helper
- listClients(): properly lists clients from server.conf fingerprints
- newClient(): duplicate check now works in fingerprint mode
- renewClient(): uses self-sign-client instead of easyrsa renew
- renewServer(): uses self-sign-server and regenerates all client
  configs with the new server fingerprint

New helper functions:
- getAuthMode(): determines if PKI or fingerprint mode is active
- getClientsFromFingerprints(): parses <peer-fingerprint> block
- clientExistsInFingerprints(): checks if client exists

Fixes #1444
2025-12-29 10:47:38 +01:00
Stanislas Lange
cc1b2fafc8 fix(test): pass Docker env vars to systemd test service
The fingerprint mode CI test was not actually testing fingerprint mode
because Docker environment variables (-e AUTH_MODE=fingerprint) were not
being inherited by the systemd service running the tests.

Add PassEnvironment directive to pass AUTH_MODE and other test config
env vars from Docker to the systemd service.

Fixes the CI gap where fingerprint mode issues went undetected.
2025-12-29 10:34:13 +01:00
Stanislas Lange
62e98f79e0 Update links in README 2025-12-28 00:49:26 +01:00
renovate[bot]
cc4a92b55f chore(deps): update super-linter/super-linter action to v8.3.2 (#1440)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[super-linter/super-linter](https://redirect.github.com/super-linter/super-linter)
| action | patch | `v8.3.1` -> `v8.3.2` |

---

### Release Notes

<details>
<summary>super-linter/super-linter (super-linter/super-linter)</summary>

###
[`v8.3.2`](https://redirect.github.com/super-linter/super-linter/releases/tag/v8.3.2)

[Compare
Source](https://redirect.github.com/super-linter/super-linter/compare/v8.3.1...v8.3.2)

##### 🐛 Bugfixes

- centralize file array additions and fix type
([#&#8203;7323](https://redirect.github.com/super-linter/super-linter/issues/7323))
([ce80cf6](ce80cf6842)),
closes
[#&#8203;7302](https://redirect.github.com/super-linter/super-linter/issues/7302)
- create log groups for enabled languages only
([#&#8203;7329](https://redirect.github.com/super-linter/super-linter/issues/7329))
([7c85bf3](7c85bf3695)),
closes
[#&#8203;7307](https://redirect.github.com/super-linter/super-linter/issues/7307)
- initialize github\_before\_sha
([#&#8203;7120](https://redirect.github.com/super-linter/super-linter/issues/7120))
([a93b722](a93b722492)),
closes
[#&#8203;7118](https://redirect.github.com/super-linter/super-linter/issues/7118)
[#&#8203;7275](https://redirect.github.com/super-linter/super-linter/issues/7275)

##### ⬆️ Dependency updates

- **bundler:** bump rubocop in /dependencies in the rubocop group
([#&#8203;7313](https://redirect.github.com/super-linter/super-linter/issues/7313))
([7fab96c](7fab96c232))
- **docker:** bump clj-kondo/clj-kondo in the docker group
([#&#8203;7325](https://redirect.github.com/super-linter/super-linter/issues/7325))
([fa23c54](fa23c5433e))
- **docker:** bump the docker group with 4 updates
([#&#8203;7318](https://redirect.github.com/super-linter/super-linter/issues/7318))
([dc49a6d](dc49a6d6bd))
- **java:** bump com.puppycrawl.tools:checkstyle
([#&#8203;7312](https://redirect.github.com/super-linter/super-linter/issues/7312))
([ab58437](ab584378be))
- **npm:** bump next from 16.0.10 to 16.1.0 in /dependencies
([#&#8203;7316](https://redirect.github.com/super-linter/super-linter/issues/7316))
([a8572e2](a8572e292b))
- **npm:** bump renovate
([#&#8203;7300](https://redirect.github.com/super-linter/super-linter/issues/7300))
([191338a](191338acc8))
- **npm:** bump the npm group across 1 directory with 10 updates
([#&#8203;7322](https://redirect.github.com/super-linter/super-linter/issues/7322))
([24d9e00](24d9e00de9))
- **npm:** bump the npm group across 1 directory with 2 updates
([#&#8203;7296](https://redirect.github.com/super-linter/super-linter/issues/7296))
([0697485](069748517a))
- **npm:** bump the npm group across 1 directory with 2 updates
([#&#8203;7301](https://redirect.github.com/super-linter/super-linter/issues/7301))
([4b2bf76](4b2bf76ed4))
- **npm:** bump the npm group across 1 directory with 4 updates
([#&#8203;7327](https://redirect.github.com/super-linter/super-linter/issues/7327))
([07e73d6](07e73d6003))
- **python:** bump ansible-lint
([#&#8203;7326](https://redirect.github.com/super-linter/super-linter/issues/7326))
([47962ea](47962eae72))
- **python:** bump snakemake
([#&#8203;7295](https://redirect.github.com/super-linter/super-linter/issues/7295))
([3f92589](3f925892e7))
- **python:** bump the pip group across 1 directory with 2 updates
([#&#8203;7299](https://redirect.github.com/super-linter/super-linter/issues/7299))
([0ca0315](0ca0315180))
- **python:** bump the pip group across 1 directory with 6 updates
([#&#8203;7317](https://redirect.github.com/super-linter/super-linter/issues/7317))
([ae7e8d8](ae7e8d8e5c))

##### 🧰 Maintenance

- disable dependabot cooldown
([#&#8203;7311](https://redirect.github.com/super-linter/super-linter/issues/7311))
([e98f7d3](e98f7d3c79))
- **docs:** mention conflicting tools in upgrades
([#&#8203;7324](https://redirect.github.com/super-linter/super-linter/issues/7324))
([7afe608](7afe608fff)),
closes
[#&#8203;7298](https://redirect.github.com/super-linter/super-linter/issues/7298)
- **github-actions:** bump the dev-ci-tools group with 2 updates
([#&#8203;7315](https://redirect.github.com/super-linter/super-linter/issues/7315))
([4b07868](4b07868ae2))
- group eslint updates
([#&#8203;7321](https://redirect.github.com/super-linter/super-linter/issues/7321))
([20f25a3](20f25a3690))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/angristan/openvpn-install).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi41OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1hc3RlciIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-25 13:44:05 +01:00
renovate[bot]
46ac668bbc chore(deps): update docker/setup-buildx-action action to v3.12.0 (#1438)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[docker/setup-buildx-action](https://redirect.github.com/docker/setup-buildx-action)
| action | minor | `v3.11.1` -> `v3.12.0` |

---

### Release Notes

<details>
<summary>docker/setup-buildx-action
(docker/setup-buildx-action)</summary>

###
[`v3.12.0`](https://redirect.github.com/docker/setup-buildx-action/compare/v3.11.1...v3.12.0)

[Compare
Source](https://redirect.github.com/docker/setup-buildx-action/compare/v3.11.1...v3.12.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/angristan/openvpn-install).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi41OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1hc3RlciIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-25 13:43:54 +01:00
Stanislas
fd154b74e1 docs: add FAQ entry for server-side split-tunnel configuration (#1436)
Adds FAQ entry for server-side split-tunnel configuration.

Closes #443. The script is focused on the road warrior use case
(full-tunnel for privacy on untrusted networks), so split-tunnel is
documented as a manual post-install configuration rather than a built-in
feature.

Closes #547.
2025-12-18 17:34:05 +01:00
Stanislas
df242ee069 feat: add peer-fingerprint authentication mode (OpenVPN 2.6+) (#1437)
## Summary

Implements support for OpenVPN's `--peer-fingerprint` option, enabling
PKI-less authentication using SHA256 certificate fingerprints instead of
a CA chain.

Closes #1361

## Changes

- Add `--auth-mode` option (`pki` or `fingerprint`) for install command
- Use Easy-RSA's `self-sign-server` and `self-sign-client` commands for
fingerprint mode
- Server stores client fingerprints in `<peer-fingerprint>` block in
`server.conf`
- Clients verify server using `peer-fingerprint` directive instead of CA
- Revocation removes fingerprint from config and reloads OpenVPN
(instant effect)
- Version check ensures OpenVPN 2.6+ when fingerprint mode is selected

## Usage

```bash
# Interactive mode prompts for auth mode choice

# CLI mode
./openvpn-install.sh install --auth-mode fingerprint
```

## Comparison

| Aspect | PKI Mode | Fingerprint Mode |
|--------|----------|------------------|
| Server cert | CA-signed | Self-signed |
| Client cert | CA-signed | Self-signed |
| Revocation | CRL-based | Remove fingerprint |
| OpenVPN | Any version | 2.6.0+ required |
| Best for | Large deployments | Small/home setups |
2025-12-18 17:20:28 +01:00
Stanislas
13008ef45c docs: expand FAQ on accessing server's LAN (#1434)
## Summary
- Expands the FAQ entry about accessing computers on the server's LAN
- The previous answer only mentioned pushing a route, which is
insufficient for most setups
- Added explanation of the return routing requirement with two options:
  - Static route on router (recommended)
  - Masquerade rule (when router can't be modified)

Closes #1126
2025-12-18 12:56:47 +01:00
renovate[bot]
cf4a6a791a chore(deps): update super-linter/super-linter action to v8.3.1 (#1429)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[super-linter/super-linter](https://redirect.github.com/super-linter/super-linter)
| action | patch | `v8.3.0` -> `v8.3.1` |

---

### Release Notes

<details>
<summary>super-linter/super-linter (super-linter/super-linter)</summary>

###
[`v8.3.1`](https://redirect.github.com/super-linter/super-linter/releases/tag/v8.3.1)

[Compare
Source](https://redirect.github.com/super-linter/super-linter/compare/v8.3.0...v8.3.1)

##### 🐛 Bugfixes

- **docs:** ansible-lints lints the entire dir
([#&#8203;7272](https://redirect.github.com/super-linter/super-linter/issues/7272))
([b721f3c](b721f3c545)),
closes
[#&#8203;7263](https://redirect.github.com/super-linter/super-linter/issues/7263)
- handle paths with parentheses
([#&#8203;7273](https://redirect.github.com/super-linter/super-linter/issues/7273))
([d29d0d4](d29d0d4ffb))
- rollback to python 3.13
([#&#8203;7269](https://redirect.github.com/super-linter/super-linter/issues/7269))
([10265f1](10265f11c8))
- trivial log message bug when file does not exist
([#&#8203;7268](https://redirect.github.com/super-linter/super-linter/issues/7268))
([c6a7b38](c6a7b38567))

##### ⬆️ Dependency updates

- **bundler:** bump rubocop-rails in /dependencies in the rubocop group
([#&#8203;7251](https://redirect.github.com/super-linter/super-linter/issues/7251))
([d8a2032](d8a2032a5d))
- **java:** bump com.google.googlejavaformat:google-java-format
([#&#8203;7270](https://redirect.github.com/super-linter/super-linter/issues/7270))
([140a2e3](140a2e37bc))
- **java:** bump com.puppycrawl.tools:checkstyle
([#&#8203;7264](https://redirect.github.com/super-linter/super-linter/issues/7264))
([550df3c](550df3c97d))
- **java:** bump the java-gradle group across 3 directories with 3
updates
([#&#8203;7252](https://redirect.github.com/super-linter/super-linter/issues/7252))
([5306a0a](5306a0a618))
- **npm:** bump
[@&#8203;modelcontextprotocol/sdk](https://redirect.github.com/modelcontextprotocol/sdk)
in /dependencies
([#&#8203;7248](https://redirect.github.com/super-linter/super-linter/issues/7248))
([4d59852](4d59852bbc))
- **npm:** bump express from 5.1.0 to 5.2.1 in /dependencies
([#&#8203;7246](https://redirect.github.com/super-linter/super-linter/issues/7246))
([50462d3](50462d3ff8))
- **npm:** bump jws from 4.0.0 to 4.0.1 in /dependencies
([#&#8203;7260](https://redirect.github.com/super-linter/super-linter/issues/7260))
([cc90344](cc90344711))
- **npm:** bump next from 16.0.7 to 16.0.9 in /dependencies
([#&#8203;7277](https://redirect.github.com/super-linter/super-linter/issues/7277))
([b7cedfb](b7cedfbfe6))
- **npm:** bump the npm group across 1 directory with 3 updates
([#&#8203;7289](https://redirect.github.com/super-linter/super-linter/issues/7289))
([f65215e](f65215e93e))
- **npm:** bump the npm group across 1 directory with 5 updates
([#&#8203;7271](https://redirect.github.com/super-linter/super-linter/issues/7271))
([b4e616f](b4e616f557))
- **npm:** bump the npm group across 1 directory with 7 updates
([#&#8203;7259](https://redirect.github.com/super-linter/super-linter/issues/7259))
([0ab9ad4](0ab9ad4208))
- **npm:** bump the npm group across 1 directory with 8 updates
([#&#8203;7266](https://redirect.github.com/super-linter/super-linter/issues/7266))
([39e94f8](39e94f843c))
- **python:** bump the pip group across 1 directory with 2 updates
([#&#8203;7288](https://redirect.github.com/super-linter/super-linter/issues/7288))
([4559b6e](4559b6e55a))
- **python:** bump the pip group across 1 directory with 7 updates
([#&#8203;7265](https://redirect.github.com/super-linter/super-linter/issues/7265))
([026d3fe](026d3fe1ed))

##### 🧰 Maintenance

- add prettier and htmlhint to the npm group
([#&#8203;7257](https://redirect.github.com/super-linter/super-linter/issues/7257))
([4692c1c](4692c1cc3a))
- **deps:** update docker dependencies
([#&#8203;7285](https://redirect.github.com/super-linter/super-linter/issues/7285))
([f4d16d3](f4d16d3155)),
closes
[#&#8203;7244](https://redirect.github.com/super-linter/super-linter/issues/7244)
- fix docs typos and update next
([#&#8203;7284](https://redirect.github.com/super-linter/super-linter/issues/7284))
([0df9f3c](0df9f3cff2))
- update issue template and print graph
([#&#8203;7276](https://redirect.github.com/super-linter/super-linter/issues/7276))
([dfb728c](dfb728c158))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/angristan/openvpn-install).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi41OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1hc3RlciIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-18 12:46:41 +01:00
Stanislas
42c027c038 docs: add FAQ entry for port-share feature (#1435)
## Summary
- Adds FAQ entry explaining how to use OpenVPN's `port-share` feature to
run OpenVPN on port 443 alongside a web server

Closes #453
2025-12-18 12:34:45 +01:00
Stanislas
b443b75375 feat: disconnect clients immediately on certificate revocation (#1432)
## Summary

Adds immediate client disconnect when a certificate is revoked, via
OpenVPN management interface.

Previously, revoked clients stayed connected until they voluntarily
disconnected or the server restarted.

Fixes #1199

## Changes

- Enable management interface (Unix socket at
`/var/run/openvpn/server.sock`)
- Add `disconnectClient()` function to send `kill` command on revoke
- Add `socat` dependency for socket communication
2025-12-18 11:32:50 +01:00
Stanislas
9289770e8b refactor: eliminate CHOICE variables and centralize configuration (#1427)
## Summary

- Eliminate all CHOICE intermediary variables (~17 variables removed)
- Replace numeric values with descriptive string values throughout
- Centralize configuration defaults in a single function
- Add comprehensive validation for all configuration values
- Add reusable helper functions for interactive prompts
- Fix non-interactive mode to skip interactive prompts entirely

## Changes

### Configuration Values Now Use Strings

| Variable | Before | After |
|----------|--------|-------|
| TLS_SIG | 1, 2, 3 | "crypt-v2", "crypt", "auth" |
| CERT_TYPE | 1, 2 | "ecdsa", "rsa" |
| DNS | 1-13 | "cloudflare", "quad9", "custom", etc. |

### Code Reduction

- Removed ~80 lines of duplicate defaults from `installOpenVPN()`
- Removed ~60 lines of CHOICE variable assignments from `cmd_install()`
- Net change: cleaner structure with better separation of concerns

### New Functions

- `set_installation_defaults()`: Single source of truth for all defaults
- `validate_configuration()`: Validates all config values after defaults
applied
- `select_from_array()`: Generic menu selection helper
- `select_with_labels()`: Menu with display labels different from values
- `prompt_yes_no()`: Yes/no prompts with validation
- `prompt_validated()`: Custom value prompts with validation function
- `detect_server_ips()`: Detects server IPv4/IPv6 addresses
- `prepare_network_config()`: Calculates derived network config
(gateways, etc.)

### Configuration Constants

Added readonly arrays for valid options:
- `PROTOCOLS`, `DNS_PROVIDERS`, `CIPHERS`, `CERT_TYPES`
- `CERT_CURVES`, `RSA_KEY_SIZES`, `TLS_VERSIONS`
- `TLS13_CIPHERSUITES_OPTIONS`, `TLS_GROUPS_OPTIONS`
- `HMAC_ALGORITHMS`, `TLS_SIG_MODES`

### Non-Interactive Mode Fix

- `installQuestions()` is now only called in interactive mode
- IP detection and gateway calculations extracted to separate functions
- Renamed `AUTO_INSTALL` to `NON_INTERACTIVE_INSTALL` for clarity
- Non-interactive installs no longer hang waiting for user input

## Test plan

- [ ] Run `make test` (default Ubuntu)
- [ ] Test interactive installation
- [ ] Test non-interactive installation with CLI flags
- [ ] Test non-interactive installation with environment variables
- [ ] Test invalid configuration values are rejected
2025-12-17 23:48:10 +01:00
Stanislas
e273a77dcd fix: use source-based firewall rules with interface wildcard matching (#1426)
## Summary

- Fixes firewall rules that hardcode `tun0` interface, which fails when
OpenVPN uses `tun1`, `tun2`, etc. because another service already
occupies `tun0`
- Uses a defense-in-depth approach combining interface wildcard matching
with source-based rules to prevent IP spoofing

Fixes #1298

## Changes

| Backend | Before | After |
|---------|--------|-------|
| **iptables** | `-i tun0` | `-i tun+ -s $VPN_SUBNET` |
| **nftables** | `iifname "tun0"` | `iifname "tun*" ip saddr
$VPN_SUBNET` |
| **firewalld** | rich rules (source-based) | no change needed |

## Implementation Details

- **iptables/nftables**: Combined interface wildcard (`tun+`/`tun*`)
with source matching provides defense in depth - traffic must come from
both a tun interface AND the VPN subnet
- **firewalld**: Already used source-based rich rules, so no changes
required (rich rules work reliably across both iptables and nftables
backends)
2025-12-16 09:58:30 +01:00
Stanislas Lange
f7436ef2c1 docs: fix currency symbol formatting 2025-12-15 22:26:19 +01:00
Stanislas Lange
ea2faad268 docs: update recommended providers 2025-12-15 22:25:32 +01:00
Stanislas
6b07477dd9 feat: flexible IPv4/IPv6 support with independent endpoint and client addressing (#1419)
## Summary

Comprehensive IPv4/IPv6 overhaul that decouples server endpoint
addressing from client tunnel addressing, supporting all combinations
with automatic leak prevention.

### Supported Configurations

| Endpoint | Client Mode | Description |
|----------|-------------|-------------|
| IPv4 | IPv4-only | Traditional setup (4→4) |
| IPv4 | Dual-stack | IPv4 endpoint, clients get both (4→4/6) |
| IPv4 | IPv6-only | IPv4 endpoint, clients get IPv6 only (4→6) |
| IPv6 | IPv4-only | IPv6 endpoint, clients get IPv4 only (6→4) |
| IPv6 | Dual-stack | IPv6 endpoint, clients get both (6→4/6) |
| IPv6 | IPv6-only | Full IPv6 setup (6→6) |

### Leak Prevention

- **IPv4-only mode**: Pushes `block-ipv6` to clients, blocking all IPv6
traffic
- **IPv6-only mode**: Assigns IPv4 addresses and pushes
`redirect-gateway def1` to capture IPv4 traffic, which is then dropped
(no IPv4 NAT configured)
- **Dual-stack mode**: Both protocols tunneled normally

### New CLI Options

```
Network Options:
  --endpoint-type <4|6>     Endpoint IP version (default: 4)
  --client-ipv4             Enable IPv4 for VPN clients (default: enabled)
  --no-client-ipv4          Disable IPv4 for VPN clients
  --client-ipv6             Enable IPv6 for VPN clients (default: disabled)
  --no-client-ipv6          Disable IPv6 for VPN clients
  --subnet-ipv4 <x.x.x.0>   IPv4 VPN subnet (default: 10.8.0.0)
  --subnet-ipv6 <prefix>    IPv6 VPN subnet (default: fd42:42:42:42::)
```

### Usage Examples

```bash
# Dual-stack clients (IPv4 + IPv6)
./openvpn-install.sh install --client-ipv4 --client-ipv6

# IPv6-only clients (IPv4 traffic blocked)
./openvpn-install.sh install --no-client-ipv4 --client-ipv6

# IPv4-only clients (IPv6 traffic blocked) - default behavior
./openvpn-install.sh install --client-ipv4 --no-client-ipv6

# IPv6 server endpoint
./openvpn-install.sh install --endpoint-type 6 --endpoint 2001:db8::1

# Custom subnets
./openvpn-install.sh install --client-ipv6 --subnet-ipv4 10.9.0.0 --subnet-ipv6 fd00🔢5678::
```

### Implementation Details

**Core changes:**
- New `ENDPOINT_TYPE` variable (4 or 6) controls server listening
protocol
- New `CLIENT_IPV4`/`CLIENT_IPV6` variables control client tunnel
addressing
- Renamed `VPN_SUBNET` → `VPN_SUBNET_IPV4`, added `VPN_SUBNET_IPV6`
- Separate `resolvePublicIPv4()` and `resolvePublicIPv6()` functions
- New `validate_subnet_ipv6()` for ULA (fd00::/8) validation

**Protocol handling:**
- Uses `proto udp6`/`tcp6` when endpoint type is IPv6
- Firewall and SELinux commands handle both protocol variants

**Firewall updates:**
- firewalld: Conditional IPv6 masquerade and forwarding
- nftables: Separate ip/ip6 tables for NAT based on client config
- iptables: ip6tables rules only when IPv6 clients enabled

**DNS configuration:**
- Unbound listens on IPv4/IPv6 gateway addresses as needed
- All third-party DNS providers now include IPv6 servers:
  - Cloudflare: 2606:4700:4700::1111
  - Quad9: 2620:fe::fe
  - Google: 2001:4860:4860::8888
  - OpenDNS: 2620:119:35::35
  - AdGuard: 2a10:50c0::ad1:ff

**CI/Testing:**
- Added `ubuntu-24.04-dual-stack` test matrix entry
- Docker test container enables IPv6 forwarding
- `CLIENT_IPV6` environment variable passed to test container

## Test Plan

- [x] Shellcheck passes
- [x] CI Docker tests pass (including new dual-stack test)
- [x] Manual testing: IPv4-only mode blocks IPv6 traffic
- [x] Manual testing: IPv6-only mode blocks IPv4 traffic
- [x] Manual testing: Dual-stack mode tunnels both protocols

---

Closes #1317
Closes #1288
Closes #1084
Closes #701
Closes #350
2025-12-15 22:19:02 +01:00
Stanislas
61bd345014 fix: simplify e2e test wait loops to prevent flaky failures (#1425)
## Summary

- Replace all fixed-timeout wait loops with simple indefinite `while`
loops
- Add 5-second stabilization delay after VPN connection before running
ping tests
- Add server-side tun0 interface verification before signaling client
- Add wait for OpenVPN restart after server certificate renewal

## Problem

Tests fail randomly with errors like:
```
Test 2: Pinging VPN gateway (10.9.0.1)...
10 packets transmitted, 0 received, 100% packet loss
FAIL: Cannot ping VPN gateway
```

Example:
https://github.com/angristan/openvpn-install/actions/runs/20230801728/job/58072998112

## Solution

Instead of guessing timeout values that may be too short for slow CI
runners, all wait loops now run indefinitely and rely on the job-level
timeout to catch actual failures.

**Before:**
```bash
MAX_WAIT=60
WAITED=0
while [ condition ] && [ $WAITED -lt $MAX_WAIT ]; do
    sleep 2
    WAITED=$((WAITED + 2))
done
if [ condition ]; then exit 1; fi
```

**After:**
```bash
while [ condition ]; do
    sleep 2
done
```

This removes 83 lines of boilerplate timeout logic.
2025-12-15 21:22:22 +01:00
Stanislas
0473a35b97 fix: prevent duplicate install questions and MTU prompt in CLI mode (#1422)
## Summary

Fixed a bug where installation questions were asked twice in interactive
mode, and a related bug where the MTU choice prompt appeared in
non-interactive CLI mode.

**Root cause:** `installQuestions()` was being called both by the caller
(`cmd_install()` or `cmd_interactive()`) and again inside
`installOpenVPN()`.

**Changes:**
- Removed the redundant `installQuestions()` call from
`installOpenVPN()`
- Added `installQuestions()` call to the non-interactive branch of
`cmd_install()` (needed for IP detection and setup)
- Added `MTU_CHOICE` default to the non-interactive branch (was
previously set inside `installOpenVPN()` before calling
`installQuestions()`)
2025-12-15 12:42:59 +01:00
Stanislas
63eff762dd fix: use run_cmd_fatal for package list updates (#1423)
## Summary
- Changed `apt-get update` commands from `run_cmd` to `run_cmd_fatal`
- Package list updates are critical operations that should fail the
installation if they fail
- Affects 3 locations: initial update, post-repo-add update, and removal
cleanup
2025-12-15 12:36:18 +01:00
Stanislas
04f7178c80 feat: add TLS 1.3 support, replace ecdh-curve with tls-groups (#1421)
## Summary

- Add TLS 1.3 support with `--tls-version-min` and `--tls-ciphersuites`
- Replace deprecated `ecdh-curve` with `tls-groups`
- Remove traditional DH support (OpenVPN 2.7 defaults to ECDH)

## New options

| Option | Default |
|--------|---------|
| `--tls-version-min` | `1.2` |
| `--tls-ciphersuites` |
`TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256`
|
| `--tls-groups` | `X25519:prime256v1:secp384r1:secp521r1` |

## Removed

- `--dh-type`, `--dh-bits`, `--dh-curve`
- DH parameter generation

Closes https://github.com/angristan/openvpn-install/issues/1231
Closes https://github.com/angristan/openvpn-install/issues/637
Closes https://github.com/angristan/openvpn-install/issues/1362
2025-12-15 12:13:03 +01:00
Stanislas
2e0605e2eb fix: validate client name length to prevent invalid certificates (#1420)
## Summary
- Add `is_valid_client_name()` helper and `validate_client_name()`
function to enforce client name constraints
- Reject client names longer than 64 characters (OpenSSL CN limit)
- Apply validation at all entry points: interactive prompt, `client add`
CLI, and `--client` install option

## Problem
Client names longer than 64 bytes cause Easy-RSA/OpenSSL to silently
truncate or reject Common Names, resulting in `.ovpn` files with empty
`<cert>/<key>` sections. Users (especially in headless/automated
deployments) would see the script complete successfully but get
non-functional output.

Fixes #1306
2025-12-15 11:24:01 +01:00
Stanislas
8375af5452 feat: add configurable MTU support (#1417)
## Summary
- Add `--mtu <size>` CLI option to configure tunnel MTU (valid range:
576-65535)
- Add interactive prompt with user-friendly explanation for
non-technical users
- Write `tun-mtu` to server.conf and client template when custom value
is set
- OpenVPN auto-calculates MSSFIX based on the MTU value (no separate
option needed)

## Use cases
- PPPoE connections (typically need MTU ~1492)
- Mobile/cellular networks with variable MTU
- Networks with connectivity issues due to fragmentation

## Usage
```bash
# CLI mode
./openvpn-install.sh install --mtu 1400

# Interactive mode prompts with explanation:
# "MTU controls the maximum packet size. Lower values can help
#  with connectivity issues on some networks (e.g., PPPoE, mobile)."
```

Close https://github.com/angristan/openvpn-install/pull/1300

Co-authored-by: Fabian Druschke <fdruschke@outlook.com>
2025-12-15 10:53:15 +01:00
Stanislas
15ca74639c feat: remove compression support (#1418)
## Summary

- Remove compression support from the script (CLI option, interactive
prompts, config generation)
- Compression is unsafe due to the VORACLE attack and OpenVPN is
deprecating it
- Simplify DCO compatibility check (no longer needs compression
condition)

Closes https://github.com/angristan/openvpn-install/issues/872
2025-12-15 10:04:02 +01:00
Stanislas
898489f3c9 fix: exit non-zero when client name exists (#1407)
Fixes https://github.com/angristan/openvpn-install/issues/1194
2025-12-15 09:57:52 +01:00
Stanislas
ec3e80ac16 feat: add CLI interface with subcommands (#1398)
Replace environment variable-based configuration with a proper CLI
interface using subcommands and flags.

### Commands

```
openvpn-install <command> [options]

Commands:
  install       Install and configure OpenVPN server
  uninstall     Remove OpenVPN server
  client        Manage client certificates (add/list/revoke/renew)
  server        Server management (status/renew)
  interactive   Launch interactive menu
```

### Highlights

- **No args → help**: Running without arguments shows help instead of
interactive menu
- **JSON output**: `client list` and `server status` support `--format
json`
- **25+ install flags**: Network, DNS, security, and client options
- **Interactive mode preserved**: `install --interactive` or
`interactive` command

### Breaking Changes

Environment variables (`AUTO_INSTALL`, `MENU_OPTION`, `CLIENT`, etc.)
are no longer supported. Use CLI flags instead.

```bash
# Before
MENU_OPTION=1 CLIENT=foo PASS=1 ./openvpn-install.sh

# After
./openvpn-install.sh client add foo
```


Closes https://github.com/angristan/openvpn-install/issues/1202
2025-12-14 22:08:44 +01:00
Stanislas
648fe1ee0b feat: add option to list connected clients (#1396)
## Summary
- Add new menu option "3) List connected clients" to show currently
connected VPN clients
- Parses `/var/log/openvpn/status.log` and displays client name, real
IP, VPN IP, connection time, and transfer stats
- Human-readable byte formatting (K/M/G)

## Example output
```
   Name                 Real Address           VPN IP           Connected Since      Transfer
   ----                 ------------           ------           ---------------      --------
   stan                 123.45.211.11:28291    10.8.0.2         2025-12-14 10:13:22  ↓7.3M ↑123.5M
```

Closes https://github.com/angristan/openvpn-install/pull/863
2025-12-14 13:04:39 +01:00
Stanislas
e9deb4b8ab feat: add configurable VPN subnet (#1394)
Allow users to customize the VPN subnet during installation instead of
using the hardcoded `10.8.0.0/24`.

- Add subnet prompt during interactive installation (default or custom)
- Add `VPN_SUBNET` environment variable for headless installs
- Validate RFC1918 /24 networks (e.g., `10.9.0.0`, `172.16.0.0`,
`192.168.1.0`)

Closes https://github.com/angristan/openvpn-install/issues/153
Closes https://github.com/angristan/openvpn-install/pull/550
Closes https://github.com/angristan/openvpn-install/pull/1150
Closes https://github.com/angristan/openvpn-install/pull/952
Closes https://github.com/angristan/openvpn-install/pull/551

Co-authored-by: browningluke <lrbrowning6@gmail.com>
2025-12-14 10:54:52 +01:00
Stanislas
cb0ef7b1c2 fix: use /etc/openvpn/server/ for tls-crypt-v2 temp files (#1393)
## Summary

- Fix tls-crypt-v2 client key generation failing on Ubuntu 25.04+ with
"Permission denied"
- Add Ubuntu 25.10 to CI test matrix

## Root Cause

Ubuntu 25.04 introduced an AppArmor profile for openvpn
(`/etc/apparmor.d/openvpn`) that restricts where the binary can write.
The allowed paths are:
- `/etc/openvpn/{,**}`
- `@{HOME}/**` (owner only)

The script was using `mktemp` which creates files in `/tmp`, causing the
error:
```
Cannot open file '/tmp/tmp.XXX' for write: Permission denied (errno=13)
```

## Fix

Changed the temp file location from `/tmp` to `/etc/openvpn/server/`:
```bash
# Before
tls_crypt_v2_tmpfile=$(mktemp)

# After
tls_crypt_v2_tmpfile=$(mktemp /etc/openvpn/server/tls-crypt-v2-client.XXXXXX)
```

Fixes #1391
2025-12-14 00:23:43 +01:00
Stanislas
8ea2d1b5b2 feat: add native nftables support (#1389)
- Add nftables as a third firewall backend option alongside firewalld
and iptables
- Detection priority: firewalld → nftables → iptables (legacy fallback)
- Uses dedicated `openvpn` and `openvpn-nat` tables for clean isolation
- Integrates with native `nftables.service` via include in
`/etc/nftables.conf`


Closes https://github.com/angristan/openvpn-install/issues/530
2025-12-14 00:03:29 +01:00
Stanislas
a220d3a689 fix: improve CLIENT_FILEPATH handling and reduce code duplication (#1390)
## Summary

Follow-up improvements to #962:

- **Fix `getClientOwner()` edge case**: Now verifies the user actually
exists via `id` command before attempting to set ownership. Previously
only checked if `/home/$client` directory existed, which could fail if
the directory exists but the user doesn't.

- **Add directory creation for custom paths**: When `CLIENT_FILEPATH`
points to a non-existent directory, the script now creates it
automatically with `mkdir -p`.

- **Reduce code duplication**: Extract the repeated filepath/permission
logic from `newClient()` and `renewClient()` into a new
`writeClientConfig()` helper function, removing ~30 lines of duplicated
code.
2025-12-13 23:31:52 +01:00
Rémi Alvergnat
08f6f1e7cc feat: add CLIENT_FILEPATH env var and fix client file ownership (#962)
Fix #961

- Adds CLIENT_FILEPATH env var to specify custom output path for .ovpn files
- Automatically sets correct ownership (chown) and permissions (chmod go-rw) when client name matches a system user

---------

Co-authored-by: Stanislas Lange <git@slange.me>
2025-12-13 21:12:23 +01:00
Stanislas
d8aa625639 feat: add native firewalld support (#1388)
## Summary

- Add native firewalld support for RHEL/Fedora/CentOS systems
- When firewalld is active, use `firewall-cmd --permanent` instead of
raw iptables
- Rules persist across `firewall-cmd --reload`
- Fall back to iptables when firewalld is not active
- Add `After=firewalld.service` to iptables systemd unit for safety

## Changes

**Install:** Detect firewalld, use `firewall-cmd` to add port,
masquerade, and rich rules. Fall back to iptables if inactive.

**Uninstall:** Detect which method was used and clean up accordingly.

**Tests:** Add `fedora-42-firewalld` CI test with firewalld enabled.

---

Closes https://github.com/angristan/openvpn-install/issues/356
Closes https://github.com/angristan/openvpn-install/pull/1200
2025-12-13 20:49:40 +01:00
Stanislas
9175c2c221 feat: support headless client revocation by name (#1387)
Add support for revoking clients by setting the CLIENT environment
variable directly with the client name, in addition to the existing
CLIENTNUMBER support (from
https://github.com/angristan/openvpn-install/pull/1328)

This makes headless revocation more user-friendly as users no longer
need to know the client's index number.
2025-12-13 20:18:07 +01:00
Podesta
9fd183caed feat: add flag for creation or not of new client after install (#1010)
Add a flag `NEW_CLIENT` so that the user can choose whether or not he
wishes to create a new user after installation.
It is specially useful on headless installations, when upgrading to a
different server, but keeping old credentials.
It does not change any defaults, so if no flag is passed, it still
creates the new user.

---------

Co-authored-by: Stanislas Lange <git@slange.me>
2025-12-13 19:57:02 +01:00
renovate[bot]
be2a195bb5 chore(deps): update dependency openvpn/easy-rsa to v3.2.5 (#1381)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [OpenVPN/easy-rsa](https://redirect.github.com/OpenVPN/easy-rsa) |
patch | `3.2.4` -> `3.2.5` |

---

### Release Notes

<details>
<summary>OpenVPN/easy-rsa (OpenVPN/easy-rsa)</summary>

###
[`v3.2.5`](https://redirect.github.com/OpenVPN/easy-rsa/releases/tag/v3.2.5):
3.2.5

[Compare
Source](https://redirect.github.com/OpenVPN/easy-rsa/compare/v3.2.4...v3.2.5)

#### What's Changed

- Replace `local` / `global` `openssl-easyrsa.cnf` by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1394](https://redirect.github.com/OpenVPN/easy-rsa/pull/1394)
- init-pki: Introduce configurable cryptography by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1397](https://redirect.github.com/OpenVPN/easy-rsa/pull/1397)
- Drop x509 type kdc built-in by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1399](https://redirect.github.com/OpenVPN/easy-rsa/pull/1399)
- Always generate an `openssl-easyrsa.cnf` or `x509-types` tmp-file by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1401](https://redirect.github.com/OpenVPN/easy-rsa/pull/1401)
- Libressl use `$EASYRSA_FORCE_SAFE_SSL` by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1402](https://redirect.github.com/OpenVPN/easy-rsa/pull/1402)
- Update EasyRSA-Advanced.md by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1403](https://redirect.github.com/OpenVPN/easy-rsa/pull/1403)
- `source_vars()`: Add `grep` regex for assign by equal `=` by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1405](https://redirect.github.com/OpenVPN/easy-rsa/pull/1405)
- export\_pkcs(), PKCS12 inline: Respect $EASYRSA\_NO\_INLINE by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1407](https://redirect.github.com/OpenVPN/easy-rsa/pull/1407)
- Introduce peer-fingerprint inline lists by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1410](https://redirect.github.com/OpenVPN/easy-rsa/pull/1410)
- help: Add '-b' alias for --batch and correct default 'vars' file by
[@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1411](https://redirect.github.com/OpenVPN/easy-rsa/pull/1411)
- New function ssl\_cert\_sig\_digest(); Extract certificae digest name
by [@&#8203;TinCanTech](https://redirect.github.com/TinCanTech) in
[#&#8203;1414](https://redirect.github.com/OpenVPN/easy-rsa/pull/1414)
- Upgrading OpenSSL for Windows to 3.6.0 by
[@&#8203;ecrist](https://redirect.github.com/ecrist) in
[#&#8203;1416](https://redirect.github.com/OpenVPN/easy-rsa/pull/1416)

**Full Changelog**:
<https://github.com/OpenVPN/easy-rsa/compare/v3.2.4...v3.2.5>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/angristan/openvpn-install).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi40Mi4yIiwidXBkYXRlZEluVmVyIjoiNDIuNDIuMiIsInRhcmdldEJyYW5jaCI6Im1hc3RlciIsImxhYmVscyI6W119-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Stanislas Lange <git@slange.me>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-12-13 19:48:07 +01:00
Stanislas
0f2bd04447 feat: change default DNS resolver to Cloudflare (#1385)
- Change default DNS resolver from AdGuard DNS to Cloudflare (1.1.1.1)
- Applies to both interactive mode and AUTO_INSTALL mode
2025-12-13 19:32:07 +01:00
Stanislas
190e49ec33 feat: add list clients menu option (#1382)
## Summary

- Add new "List existing users" option to management menu (option 2)
- Displays all client certificates with status (Valid/Revoked),
expiration date, and days remaining
- Reads expiry directly from certificate files using openssl for
accurate 4-digit year dates
- Output sorted by expiration date (oldest first)
- Updates test MENU_OPTION values to match new menu numbering

Example output:
```
=== Existing Clients ===

Found 2 certificate(s)

   Name                      Status     Expiry       Remaining
   ----                      ------     ------       ---------
   user1                     Valid      2035-12-11   3649 days
   user2                     Revoked    unknown      unknown
```

Closes #567
Closes #563
Closes #587
2025-12-13 19:17:30 +01:00
Stanislas
90f2313ff3 fix: use non-deprecated --genkey syntax for tls-crypt and tls-auth (#1383)
## Summary

- Replace deprecated `--genkey --secret` syntax with `--genkey secret`
for tls-crypt and tls-auth key generation

The OpenVPN source explicitly warns about this:
```
WARNING: Using --genkey --secret filename is DEPRECATED. Use --genkey secret filename instead.
```

Closes #1256
Close https://github.com/angristan/openvpn-install/issues/1280
2025-12-13 18:59:40 +01:00
Siebren Kraak
cb2d67be74 Add PASSPHRASE support in headless mode (#1015)
Add support for a password protected user in headless mode

Fixes #389

---------

Co-authored-by: Siebren Kraak <siebren.kraak@secura.com>
Co-authored-by: Stanislas Lange <git@slange.me>
2025-12-13 15:42:43 +01:00
Stanislas
75ea8ef1c1 ci: only cancel in-progress jobs for pull requests (#1378)
- Only cancel in-progress CI jobs for pull requests, not for master
branch pushes
- Ensures all master branch jobs run to completion while still saving CI
resources on PRs
2025-12-13 15:14:15 +01:00
Cezar Lungu
99c74e5af4 Delete old easy-rsa remove (#655)
It isn't packaged anymore with openvpn in the supported distros

Co-authored-by: Stanislas Lange <git@slange.me>
2025-12-13 15:11:17 +01:00
Stanislas Lange
991c403d78 chore: fix AGENTS.md linting (GitHub/Docker capitalization) 2025-12-13 14:56:23 +01:00