mirror of
https://github.com/angristan/openvpn-install.git
synced 2025-12-19 10:17:01 +01:00
Merge origin/master into pr-962
This commit is contained in:
12
.github/workflows/docker-test.yml
vendored
12
.github/workflows/docker-test.yml
vendored
@@ -9,7 +9,7 @@ name: Docker Test
|
|||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -89,6 +89,15 @@ jobs:
|
|||||||
name: tls-auth
|
name: tls-auth
|
||||||
sig: "3"
|
sig: "3"
|
||||||
key_file: tls-auth.key
|
key_file: tls-auth.key
|
||||||
|
# Test firewalld support on Fedora
|
||||||
|
- os:
|
||||||
|
name: fedora-42-firewalld
|
||||||
|
image: fedora:42
|
||||||
|
enable_firewalld: true
|
||||||
|
tls:
|
||||||
|
name: tls-crypt-v2
|
||||||
|
sig: "1"
|
||||||
|
key_file: tls-crypt-v2.key
|
||||||
|
|
||||||
name: ${{ matrix.os.name }}
|
name: ${{ matrix.os.name }}
|
||||||
steps:
|
steps:
|
||||||
@@ -103,6 +112,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg BASE_IMAGE=${{ matrix.os.image }} \
|
--build-arg BASE_IMAGE=${{ matrix.os.image }} \
|
||||||
|
--build-arg ENABLE_FIREWALLD=${{ matrix.os.enable_firewalld && 'y' || 'n' }} \
|
||||||
-t openvpn-server \
|
-t openvpn-server \
|
||||||
-f test/Dockerfile.server .
|
-f test/Dockerfile.server .
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -8,7 +8,7 @@ name: Lint
|
|||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
3
.github/workflows/update-easyrsa-hash.yml
vendored
3
.github/workflows/update-easyrsa-hash.yml
vendored
@@ -61,10 +61,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
if: env.HASH_CHANGED == 'true'
|
if: env.HASH_CHANGED == 'true'
|
||||||
|
env:
|
||||||
|
PAT: ${{ secrets.PAT }}
|
||||||
run: |
|
run: |
|
||||||
if ! git diff --quiet openvpn-install.sh; then
|
if ! git diff --quiet openvpn-install.sh; then
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git remote set-url origin "https://x-access-token:${PAT}@github.com/${{ github.repository }}"
|
||||||
git add openvpn-install.sh
|
git add openvpn-install.sh
|
||||||
git commit -m "chore: update Easy-RSA SHA256 hash"
|
git commit -m "chore: update Easy-RSA SHA256 hash"
|
||||||
git push
|
git push
|
||||||
|
|||||||
4
FAQ.md
4
FAQ.md
@@ -87,9 +87,9 @@ If your client is <2.3.3, remove `tls-version-min 1.2` from your `/etc/openvpn/s
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Q:** What syctl and iptables changes are made by the script?
|
**Q:** What sysctl and firewall changes are made by the script?
|
||||||
|
|
||||||
**A:** Iptables rules are saved at `/etc/iptables/add-openvpn-rules.sh` and `/etc/iptables/rm-openvpn-rules.sh`. They are managed by the service `/etc/systemd/system/iptables-openvpn.service`
|
**A:** If firewalld is active, the script uses `firewall-cmd --permanent` to configure port, masquerade, and rich rules. Otherwise, iptables rules are saved at `/etc/iptables/add-openvpn-rules.sh` and `/etc/iptables/rm-openvpn-rules.sh`, managed by `/etc/systemd/system/iptables-openvpn.service`.
|
||||||
|
|
||||||
Sysctl options are at `/etc/sysctl.d/99-openvpn.conf`
|
Sysctl options are at `/etc/sysctl.d/99-openvpn.conf`
|
||||||
|
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -59,7 +59,8 @@ The first time you run it, you'll have to follow the assistant and answer a few
|
|||||||
When OpenVPN is installed, you can run the script again, and you will get the choice to:
|
When OpenVPN is installed, you can run the script again, and you will get the choice to:
|
||||||
|
|
||||||
- Add a client
|
- Add a client
|
||||||
- Remove a client
|
- List client certificates
|
||||||
|
- Revoke a client
|
||||||
- Renew certificates (client or server)
|
- Renew certificates (client or server)
|
||||||
- Uninstall OpenVPN
|
- Uninstall OpenVPN
|
||||||
|
|
||||||
@@ -95,10 +96,11 @@ If you want to customise your installation, you can export them or specify them
|
|||||||
- `COMPRESSION_ENABLED=n`
|
- `COMPRESSION_ENABLED=n`
|
||||||
- `CUSTOMIZE_ENC=n`
|
- `CUSTOMIZE_ENC=n`
|
||||||
- `CLIENT=clientname`
|
- `CLIENT=clientname`
|
||||||
- `PASS=1`
|
- `PASS=1` (set to `2` for password-protected clients, requires `PASSPHRASE`)
|
||||||
- `MULTI_CLIENT=n`
|
- `MULTI_CLIENT=n`
|
||||||
- `CLIENT_CERT_DURATION_DAYS=3650`
|
- `CLIENT_CERT_DURATION_DAYS=3650`
|
||||||
- `SERVER_CERT_DURATION_DAYS=3650`
|
- `SERVER_CERT_DURATION_DAYS=3650`
|
||||||
|
- `NEW_CLIENT=y` (set to `n` to skip client creation after installation)
|
||||||
- `CLIENT_FILEPATH=/custom/path/client.ovpn` (optional, overrides default output path)
|
- `CLIENT_FILEPATH=/custom/path/client.ovpn` (optional, overrides default output path)
|
||||||
|
|
||||||
The `.ovpn` file is saved to `CLIENT_FILEPATH` if defined, otherwise: the client's home directory if it exists (`/home/$CLIENT`), otherwise `SUDO_USER`'s home, otherwise `/root`.
|
The `.ovpn` file is saved to `CLIENT_FILEPATH` if defined, otherwise: the client's home directory if it exists (`/home/$CLIENT`), otherwise `SUDO_USER`'s home, otherwise `/root`.
|
||||||
@@ -107,8 +109,6 @@ If the server is behind NAT, you can specify its endpoint with the `ENDPOINT` va
|
|||||||
|
|
||||||
Other variables can be set depending on your choice (encryption, compression). You can search for them in the `installQuestions()` function of the script.
|
Other variables can be set depending on your choice (encryption, compression). You can search for them in the `installQuestions()` function of the script.
|
||||||
|
|
||||||
Password-protected clients are not supported by the headless installation method since user input is expected by Easy-RSA.
|
|
||||||
|
|
||||||
The headless install is more-or-less idempotent, in that it has been made safe to run multiple times with the same parameters, e.g. by a state provisioner like Ansible/Terraform/Salt/Chef/Puppet. It will only install and regenerate the Easy-RSA PKI if it doesn't already exist, and it will only install OpenVPN and other upstream dependencies if OpenVPN isn't already installed. It will recreate all local config and re-generate the client file on each headless run.
|
The headless install is more-or-less idempotent, in that it has been made safe to run multiple times with the same parameters, e.g. by a state provisioner like Ansible/Terraform/Salt/Chef/Puppet. It will only install and regenerate the Easy-RSA PKI if it doesn't already exist, and it will only install OpenVPN and other upstream dependencies if OpenVPN isn't already installed. It will recreate all local config and re-generate the client file on each headless run.
|
||||||
|
|
||||||
### Headless User Addition
|
### Headless User Addition
|
||||||
@@ -121,20 +121,42 @@ The following Bash script adds a new user `foo` to an existing OpenVPN configura
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export MENU_OPTION="1"
|
export MENU_OPTION="1"
|
||||||
export CLIENT="foo"
|
export CLIENT="foo"
|
||||||
export PASS="1"
|
export PASS="1" # set to "2" for a password-protected client, and set PASSPHRASE
|
||||||
# export CLIENT_FILEPATH="/etc/openvpn/clients/foo.ovpn"
|
# export CLIENT_FILEPATH="/etc/openvpn/clients/foo.ovpn"
|
||||||
./openvpn-install.sh
|
./openvpn-install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** When a client name matches a system user (e.g., `foo` and `/home/foo` exists), the script automatically sets proper ownership and permissions on the `.ovpn` file.
|
**Note:** When a client name matches a system user (e.g., `foo` and `/home/foo` exists), the script automatically sets proper ownership and permissions on the `.ovpn` file.
|
||||||
|
|
||||||
|
### Headless User Revocation
|
||||||
|
|
||||||
|
It's also possible to automate the revocation of an existing user. The key is to provide the `MENU_OPTION` variable set to `3` along with either `CLIENT` (client name) or `CLIENTNUMBER` (1-based index from the client list).
|
||||||
|
|
||||||
|
The following Bash script revokes the existing user `foo`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
export MENU_OPTION="3"
|
||||||
|
export CLIENT="foo"
|
||||||
|
./openvpn-install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can use the client number:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
export MENU_OPTION="3"
|
||||||
|
export CLIENTNUMBER="1" # Revokes the first client in the list
|
||||||
|
./openvpn-install.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Installs and configures a ready-to-use OpenVPN server
|
- Installs and configures a ready-to-use OpenVPN server
|
||||||
- Certificate renewal for both client and server certificates
|
- Certificate renewal for both client and server certificates
|
||||||
- Uses [official OpenVPN repositories](https://community.openvpn.net/openvpn/wiki/OpenvpnSoftwareRepos) when possible for the latest stable releases
|
- Uses [official OpenVPN repositories](https://community.openvpn.net/openvpn/wiki/OpenvpnSoftwareRepos) when possible for the latest stable releases
|
||||||
- Iptables rules and forwarding managed in a seamless way
|
- Firewall rules and forwarding managed seamlessly (native firewalld support, iptables fallback)
|
||||||
- If needed, the script can cleanly remove OpenVPN, including configuration and iptables rules
|
- If needed, the script can cleanly remove OpenVPN, including configuration and firewall rules
|
||||||
- Customisable encryption settings, enhanced default settings (see [Security and Encryption](#security-and-encryption) below)
|
- Customisable encryption settings, enhanced default settings (see [Security and Encryption](#security-and-encryption) below)
|
||||||
- OpenVPN 2.4 features, mainly encryption improvements (see [Security and Encryption](#security-and-encryption) below)
|
- OpenVPN 2.4 features, mainly encryption improvements (see [Security and Encryption](#security-and-encryption) below)
|
||||||
- Variety of DNS resolvers to be pushed to the clients
|
- Variety of DNS resolvers to be pushed to the clients
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
# Configuration constants
|
# Configuration constants
|
||||||
readonly DEFAULT_CERT_VALIDITY_DURATION_DAYS=3650 # 10 years
|
readonly DEFAULT_CERT_VALIDITY_DURATION_DAYS=3650 # 10 years
|
||||||
readonly DEFAULT_CRL_VALIDITY_DURATION_DAYS=5475 # 15 years
|
readonly DEFAULT_CRL_VALIDITY_DURATION_DAYS=5475 # 15 years
|
||||||
readonly EASYRSA_VERSION="3.2.4"
|
readonly EASYRSA_VERSION="3.2.5"
|
||||||
readonly EASYRSA_SHA256="ed65e88cea892268efa71eb1161ce13af3beded6754301e1e713e36ff3613cac"
|
readonly EASYRSA_SHA256="662ee3b453155aeb1dff7096ec052cd83176c460cfa82ac130ef8568ec4df490"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Logging Configuration
|
# Logging Configuration
|
||||||
@@ -671,7 +671,7 @@ function installQuestions() {
|
|||||||
log_menu " 12) NextDNS (Anycast: worldwide)"
|
log_menu " 12) NextDNS (Anycast: worldwide)"
|
||||||
log_menu " 13) Custom"
|
log_menu " 13) Custom"
|
||||||
until [[ $DNS =~ ^[0-9]+$ ]] && [ "$DNS" -ge 1 ] && [ "$DNS" -le 13 ]; do
|
until [[ $DNS =~ ^[0-9]+$ ]] && [ "$DNS" -ge 1 ] && [ "$DNS" -le 13 ]; do
|
||||||
read -rp "DNS [1-13]: " -e -i 11 DNS
|
read -rp "DNS [1-13]: " -e -i 3 DNS
|
||||||
if [[ $DNS == 2 ]] && [[ -e /etc/unbound/unbound.conf ]]; then
|
if [[ $DNS == 2 ]] && [[ -e /etc/unbound/unbound.conf ]]; then
|
||||||
log_menu ""
|
log_menu ""
|
||||||
log_prompt "Unbound is already installed."
|
log_prompt "Unbound is already installed."
|
||||||
@@ -981,7 +981,7 @@ function installOpenVPN() {
|
|||||||
IPV6_SUPPORT=${IPV6_SUPPORT:-n}
|
IPV6_SUPPORT=${IPV6_SUPPORT:-n}
|
||||||
PORT_CHOICE=${PORT_CHOICE:-1}
|
PORT_CHOICE=${PORT_CHOICE:-1}
|
||||||
PROTOCOL_CHOICE=${PROTOCOL_CHOICE:-1}
|
PROTOCOL_CHOICE=${PROTOCOL_CHOICE:-1}
|
||||||
DNS=${DNS:-1}
|
DNS=${DNS:-3}
|
||||||
COMPRESSION_ENABLED=${COMPRESSION_ENABLED:-n}
|
COMPRESSION_ENABLED=${COMPRESSION_ENABLED:-n}
|
||||||
MULTI_CLIENT=${MULTI_CLIENT:-n}
|
MULTI_CLIENT=${MULTI_CLIENT:-n}
|
||||||
CUSTOMIZE_ENC=${CUSTOMIZE_ENC:-n}
|
CUSTOMIZE_ENC=${CUSTOMIZE_ENC:-n}
|
||||||
@@ -990,6 +990,7 @@ function installOpenVPN() {
|
|||||||
CLIENT_CERT_DURATION_DAYS=${CLIENT_CERT_DURATION_DAYS:-$DEFAULT_CERT_VALIDITY_DURATION_DAYS}
|
CLIENT_CERT_DURATION_DAYS=${CLIENT_CERT_DURATION_DAYS:-$DEFAULT_CERT_VALIDITY_DURATION_DAYS}
|
||||||
SERVER_CERT_DURATION_DAYS=${SERVER_CERT_DURATION_DAYS:-$DEFAULT_CERT_VALIDITY_DURATION_DAYS}
|
SERVER_CERT_DURATION_DAYS=${SERVER_CERT_DURATION_DAYS:-$DEFAULT_CERT_VALIDITY_DURATION_DAYS}
|
||||||
CONTINUE=${CONTINUE:-y}
|
CONTINUE=${CONTINUE:-y}
|
||||||
|
NEW_CLIENT=${NEW_CLIENT:-y}
|
||||||
|
|
||||||
if [[ -z $ENDPOINT ]]; then
|
if [[ -z $ENDPOINT ]]; then
|
||||||
ENDPOINT=$(resolvePublicIP)
|
ENDPOINT=$(resolvePublicIP)
|
||||||
@@ -1083,11 +1084,6 @@ function installOpenVPN() {
|
|||||||
|
|
||||||
# Create the server directory (OpenVPN 2.4+ directory structure)
|
# Create the server directory (OpenVPN 2.4+ directory structure)
|
||||||
run_cmd_fatal "Creating server directory" mkdir -p /etc/openvpn/server
|
run_cmd_fatal "Creating server directory" mkdir -p /etc/openvpn/server
|
||||||
|
|
||||||
# An old version of easy-rsa was available by default in some openvpn packages
|
|
||||||
if [[ -d /etc/openvpn/server/easy-rsa/ ]]; then
|
|
||||||
run_cmd "Removing old Easy-RSA" rm -rf /etc/openvpn/server/easy-rsa/
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Determine which user/group OpenVPN should run as
|
# Determine which user/group OpenVPN should run as
|
||||||
@@ -1175,11 +1171,11 @@ function installOpenVPN() {
|
|||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
# Generate tls-crypt key
|
# Generate tls-crypt key
|
||||||
run_cmd_fatal "Generating tls-crypt key" openvpn --genkey --secret /etc/openvpn/server/tls-crypt.key
|
run_cmd_fatal "Generating tls-crypt key" openvpn --genkey secret /etc/openvpn/server/tls-crypt.key
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
# Generate tls-auth key
|
# Generate tls-auth key
|
||||||
run_cmd_fatal "Generating tls-auth key" openvpn --genkey --secret /etc/openvpn/server/tls-auth.key
|
run_cmd_fatal "Generating tls-auth key" openvpn --genkey secret /etc/openvpn/server/tls-auth.key
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
@@ -1423,48 +1419,66 @@ verb 3" >>/etc/openvpn/server/server.conf
|
|||||||
installUnbound
|
installUnbound
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add iptables rules in two scripts
|
# Configure firewall rules
|
||||||
log_info "Configuring firewall rules..."
|
log_info "Configuring firewall rules..."
|
||||||
run_cmd_fatal "Creating iptables directory" mkdir -p /etc/iptables
|
|
||||||
|
|
||||||
# Script to add rules
|
if systemctl is-active --quiet firewalld; then
|
||||||
echo "#!/bin/sh
|
# Use firewalld native commands for systems with firewalld active
|
||||||
|
log_info "firewalld detected, using firewall-cmd..."
|
||||||
|
run_cmd "Adding OpenVPN port to firewalld" firewall-cmd --permanent --add-port="$PORT/$PROTOCOL"
|
||||||
|
run_cmd "Adding masquerade to firewalld" firewall-cmd --permanent --add-masquerade
|
||||||
|
|
||||||
|
# Add rich rules for VPN traffic (source-based rules work reliably with dynamic tun0 interface)
|
||||||
|
run_cmd "Adding VPN subnet rule" firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.8.0.0/24" accept'
|
||||||
|
|
||||||
|
if [[ $IPV6_SUPPORT == 'y' ]]; then
|
||||||
|
run_cmd "Adding IPv6 source rule" firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source address="fd42:42:42:42::/112" accept'
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_cmd "Reloading firewalld" firewall-cmd --reload
|
||||||
|
else
|
||||||
|
# Use iptables for systems without firewalld
|
||||||
|
run_cmd_fatal "Creating iptables directory" mkdir -p /etc/iptables
|
||||||
|
|
||||||
|
# Script to add rules
|
||||||
|
echo "#!/bin/sh
|
||||||
iptables -t nat -I POSTROUTING 1 -s 10.8.0.0/24 -o $NIC -j MASQUERADE
|
iptables -t nat -I POSTROUTING 1 -s 10.8.0.0/24 -o $NIC -j MASQUERADE
|
||||||
iptables -I INPUT 1 -i tun0 -j ACCEPT
|
iptables -I INPUT 1 -i tun0 -j ACCEPT
|
||||||
iptables -I FORWARD 1 -i $NIC -o tun0 -j ACCEPT
|
iptables -I FORWARD 1 -i $NIC -o tun0 -j ACCEPT
|
||||||
iptables -I FORWARD 1 -i tun0 -o $NIC -j ACCEPT
|
iptables -I FORWARD 1 -i tun0 -o $NIC -j ACCEPT
|
||||||
iptables -I INPUT 1 -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >/etc/iptables/add-openvpn-rules.sh
|
iptables -I INPUT 1 -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >/etc/iptables/add-openvpn-rules.sh
|
||||||
|
|
||||||
if [[ $IPV6_SUPPORT == 'y' ]]; then
|
if [[ $IPV6_SUPPORT == 'y' ]]; then
|
||||||
echo "ip6tables -t nat -I POSTROUTING 1 -s fd42:42:42:42::/112 -o $NIC -j MASQUERADE
|
echo "ip6tables -t nat -I POSTROUTING 1 -s fd42:42:42:42::/112 -o $NIC -j MASQUERADE
|
||||||
ip6tables -I INPUT 1 -i tun0 -j ACCEPT
|
ip6tables -I INPUT 1 -i tun0 -j ACCEPT
|
||||||
ip6tables -I FORWARD 1 -i $NIC -o tun0 -j ACCEPT
|
ip6tables -I FORWARD 1 -i $NIC -o tun0 -j ACCEPT
|
||||||
ip6tables -I FORWARD 1 -i tun0 -o $NIC -j ACCEPT
|
ip6tables -I FORWARD 1 -i tun0 -o $NIC -j ACCEPT
|
||||||
ip6tables -I INPUT 1 -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >>/etc/iptables/add-openvpn-rules.sh
|
ip6tables -I INPUT 1 -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >>/etc/iptables/add-openvpn-rules.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Script to remove rules
|
# Script to remove rules
|
||||||
echo "#!/bin/sh
|
echo "#!/bin/sh
|
||||||
iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o $NIC -j MASQUERADE
|
iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o $NIC -j MASQUERADE
|
||||||
iptables -D INPUT -i tun0 -j ACCEPT
|
iptables -D INPUT -i tun0 -j ACCEPT
|
||||||
iptables -D FORWARD -i $NIC -o tun0 -j ACCEPT
|
iptables -D FORWARD -i $NIC -o tun0 -j ACCEPT
|
||||||
iptables -D FORWARD -i tun0 -o $NIC -j ACCEPT
|
iptables -D FORWARD -i tun0 -o $NIC -j ACCEPT
|
||||||
iptables -D INPUT -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >/etc/iptables/rm-openvpn-rules.sh
|
iptables -D INPUT -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >/etc/iptables/rm-openvpn-rules.sh
|
||||||
|
|
||||||
if [[ $IPV6_SUPPORT == 'y' ]]; then
|
if [[ $IPV6_SUPPORT == 'y' ]]; then
|
||||||
echo "ip6tables -t nat -D POSTROUTING -s fd42:42:42:42::/112 -o $NIC -j MASQUERADE
|
echo "ip6tables -t nat -D POSTROUTING -s fd42:42:42:42::/112 -o $NIC -j MASQUERADE
|
||||||
ip6tables -D INPUT -i tun0 -j ACCEPT
|
ip6tables -D INPUT -i tun0 -j ACCEPT
|
||||||
ip6tables -D FORWARD -i $NIC -o tun0 -j ACCEPT
|
ip6tables -D FORWARD -i $NIC -o tun0 -j ACCEPT
|
||||||
ip6tables -D FORWARD -i tun0 -o $NIC -j ACCEPT
|
ip6tables -D FORWARD -i tun0 -o $NIC -j ACCEPT
|
||||||
ip6tables -D INPUT -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >>/etc/iptables/rm-openvpn-rules.sh
|
ip6tables -D INPUT -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >>/etc/iptables/rm-openvpn-rules.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
run_cmd "Making add-openvpn-rules.sh executable" chmod +x /etc/iptables/add-openvpn-rules.sh
|
run_cmd "Making add-openvpn-rules.sh executable" chmod +x /etc/iptables/add-openvpn-rules.sh
|
||||||
run_cmd "Making rm-openvpn-rules.sh executable" chmod +x /etc/iptables/rm-openvpn-rules.sh
|
run_cmd "Making rm-openvpn-rules.sh executable" chmod +x /etc/iptables/rm-openvpn-rules.sh
|
||||||
|
|
||||||
# Handle the rules via a systemd script
|
# Handle the rules via a systemd script
|
||||||
echo "[Unit]
|
echo "[Unit]
|
||||||
Description=iptables rules for OpenVPN
|
Description=iptables rules for OpenVPN
|
||||||
|
After=firewalld.service
|
||||||
Before=network-online.target
|
Before=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
@@ -1477,10 +1491,11 @@ RemainAfterExit=yes
|
|||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target" >/etc/systemd/system/iptables-openvpn.service
|
WantedBy=multi-user.target" >/etc/systemd/system/iptables-openvpn.service
|
||||||
|
|
||||||
# Enable service and apply rules
|
# Enable service and apply rules
|
||||||
run_cmd "Reloading systemd" systemctl daemon-reload
|
run_cmd "Reloading systemd" systemctl daemon-reload
|
||||||
run_cmd "Enabling iptables service" systemctl enable iptables-openvpn
|
run_cmd "Enabling iptables service" systemctl enable iptables-openvpn
|
||||||
run_cmd "Starting iptables service" systemctl start iptables-openvpn
|
run_cmd "Starting iptables service" systemctl start iptables-openvpn
|
||||||
|
fi
|
||||||
|
|
||||||
# If the server is behind a NAT, use the correct IP address for the clients to connect to
|
# If the server is behind a NAT, use the correct IP address for the clients to connect to
|
||||||
if [[ $ENDPOINT != "" ]]; then
|
if [[ $ENDPOINT != "" ]]; then
|
||||||
@@ -1522,9 +1537,13 @@ verb 3" >>/etc/openvpn/server/client-template.txt
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate the custom client.ovpn
|
# Generate the custom client.ovpn
|
||||||
log_info "Generating first client certificate..."
|
if [[ $NEW_CLIENT == "n" ]]; then
|
||||||
newClient
|
log_info "No clients added. To add clients, simply run the script again."
|
||||||
log_success "If you want to add more clients, you simply need to run this script another time!"
|
else
|
||||||
|
log_info "Generating first client certificate..."
|
||||||
|
newClient
|
||||||
|
log_success "If you want to add more clients, you simply need to run this script another time!"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helper function to get the home directory for storing client configs
|
# Helper function to get the home directory for storing client configs
|
||||||
@@ -1651,6 +1670,15 @@ function selectClient() {
|
|||||||
log_fatal "You have no existing clients!"
|
log_fatal "You have no existing clients!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# If CLIENT is set, validate it exists as a valid client
|
||||||
|
if [[ -n $CLIENT ]]; then
|
||||||
|
if tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | grep -qx "$CLIENT"; then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
log_fatal "Client '$CLIENT' not found or not valid"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ $show_expiry == "true" ]]; then
|
if [[ $show_expiry == "true" ]]; then
|
||||||
local i=1
|
local i=1
|
||||||
while read -r client; do
|
while read -r client; do
|
||||||
@@ -1677,6 +1705,88 @@ function selectClient() {
|
|||||||
CLIENT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p)
|
CLIENT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listClients() {
|
||||||
|
log_header "Client Certificates"
|
||||||
|
|
||||||
|
local index_file="/etc/openvpn/server/easy-rsa/pki/index.txt"
|
||||||
|
local number_of_clients
|
||||||
|
# Exclude server certificates (CN starting with server_)
|
||||||
|
number_of_clients=$(tail -n +2 "$index_file" | grep "^[VR]" | grep -cv "/CN=server_")
|
||||||
|
|
||||||
|
if [[ $number_of_clients == '0' ]]; then
|
||||||
|
log_warn "You have no existing client certificates!"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Found $number_of_clients client certificate(s)"
|
||||||
|
log_menu ""
|
||||||
|
printf " %-25s %-10s %-12s %s\n" "Name" "Status" "Expiry" "Remaining"
|
||||||
|
printf " %-25s %-10s %-12s %s\n" "----" "------" "------" "---------"
|
||||||
|
|
||||||
|
local cert_dir="/etc/openvpn/server/easy-rsa/pki/issued"
|
||||||
|
|
||||||
|
# Parse index.txt and sort by expiry date (oldest first)
|
||||||
|
# Exclude server certificates (CN starting with server_)
|
||||||
|
{
|
||||||
|
while read -r line; do
|
||||||
|
local status="${line:0:1}"
|
||||||
|
local client_name
|
||||||
|
client_name=$(echo "$line" | sed 's/.*\/CN=//')
|
||||||
|
|
||||||
|
# Format status
|
||||||
|
local status_text
|
||||||
|
if [[ "$status" == "V" ]]; then
|
||||||
|
status_text="Valid"
|
||||||
|
elif [[ "$status" == "R" ]]; then
|
||||||
|
status_text="Revoked"
|
||||||
|
else
|
||||||
|
status_text="Unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get expiry date from certificate file
|
||||||
|
local cert_file="$cert_dir/$client_name.crt"
|
||||||
|
local expiry_date="unknown"
|
||||||
|
local relative="unknown"
|
||||||
|
|
||||||
|
if [[ -f "$cert_file" ]]; then
|
||||||
|
# Get expiry from certificate (format: notAfter=Mon DD HH:MM:SS YYYY GMT)
|
||||||
|
local enddate
|
||||||
|
enddate=$(openssl x509 -enddate -noout -in "$cert_file" 2>/dev/null | cut -d= -f2)
|
||||||
|
|
||||||
|
if [[ -n "$enddate" ]]; then
|
||||||
|
# Parse date and convert to epoch
|
||||||
|
local expiry_epoch
|
||||||
|
expiry_epoch=$(date -d "$enddate" +%s 2>/dev/null || date -j -f "%b %d %H:%M:%S %Y %Z" "$enddate" +%s 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -n "$expiry_epoch" ]]; then
|
||||||
|
# Format as YYYY-MM-DD
|
||||||
|
expiry_date=$(date -d "@$expiry_epoch" +%Y-%m-%d 2>/dev/null || date -r "$expiry_epoch" +%Y-%m-%d 2>/dev/null)
|
||||||
|
|
||||||
|
# Calculate days remaining
|
||||||
|
local now_epoch days_remaining
|
||||||
|
now_epoch=$(date +%s)
|
||||||
|
days_remaining=$(((expiry_epoch - now_epoch) / 86400))
|
||||||
|
|
||||||
|
if [[ $days_remaining -lt 0 ]]; then
|
||||||
|
relative="$((-days_remaining)) days ago"
|
||||||
|
elif [[ $days_remaining -eq 0 ]]; then
|
||||||
|
relative="today"
|
||||||
|
elif [[ $days_remaining -eq 1 ]]; then
|
||||||
|
relative="1 day"
|
||||||
|
else
|
||||||
|
relative="$days_remaining days"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf " %-25s %-10s %-12s %s\n" "$client_name" "$status_text" "$expiry_date" "$relative"
|
||||||
|
done < <(tail -n +2 "$index_file" | grep "^[VR]" | grep -v "/CN=server_" | sort -t$'\t' -k2)
|
||||||
|
}
|
||||||
|
|
||||||
|
log_menu ""
|
||||||
|
}
|
||||||
|
|
||||||
function newClient() {
|
function newClient() {
|
||||||
log_header "New Client Setup"
|
log_header "New Client Setup"
|
||||||
log_prompt "Tell me a name for the client."
|
log_prompt "Tell me a name for the client."
|
||||||
@@ -1717,10 +1827,18 @@ function newClient() {
|
|||||||
run_cmd_fatal "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass
|
run_cmd_fatal "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
log_warn "You will be asked for the client password below"
|
if [[ -z "$PASSPHRASE" ]]; then
|
||||||
# Run directly (not via run_cmd) so password prompt is visible to user
|
log_warn "You will be asked for the client password below"
|
||||||
if ! ./easyrsa --batch build-client-full "$CLIENT"; then
|
# Run directly (not via run_cmd) so password prompt is visible to user
|
||||||
log_fatal "Building client certificate failed"
|
if ! ./easyrsa --batch build-client-full "$CLIENT"; then
|
||||||
|
log_fatal "Building client certificate failed"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "Using provided passphrase for client certificate"
|
||||||
|
# Use env var to avoid exposing passphrase in install log
|
||||||
|
export EASYRSA_PASSPHRASE="$PASSPHRASE"
|
||||||
|
run_cmd_fatal "Building client certificate" ./easyrsa --batch --passin=env:EASYRSA_PASSPHRASE --passout=env:EASYRSA_PASSPHRASE build-client-full "$CLIENT"
|
||||||
|
unset EASYRSA_PASSPHRASE
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -2010,15 +2128,24 @@ function removeOpenVPN() {
|
|||||||
# Remove customised service
|
# Remove customised service
|
||||||
run_cmd "Removing service file" rm -f /etc/systemd/system/openvpn-server@.service
|
run_cmd "Removing service file" rm -f /etc/systemd/system/openvpn-server@.service
|
||||||
|
|
||||||
# Remove the iptables rules related to the script
|
# Remove firewall rules
|
||||||
log_info "Removing iptables rules..."
|
log_info "Removing firewall rules..."
|
||||||
run_cmd "Stopping iptables service" systemctl stop iptables-openvpn
|
if systemctl is-active --quiet firewalld && firewall-cmd --list-ports | grep -q "$PORT/$PROTOCOL"; then
|
||||||
# Cleanup
|
# firewalld was used
|
||||||
run_cmd "Disabling iptables service" systemctl disable iptables-openvpn
|
run_cmd "Removing OpenVPN port from firewalld" firewall-cmd --permanent --remove-port="$PORT/$PROTOCOL"
|
||||||
run_cmd "Removing iptables service file" rm /etc/systemd/system/iptables-openvpn.service
|
run_cmd "Removing masquerade from firewalld" firewall-cmd --permanent --remove-masquerade
|
||||||
run_cmd "Reloading systemd" systemctl daemon-reload
|
run_cmd "Removing VPN subnet rule" firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="10.8.0.0/24" accept' 2>/dev/null || true
|
||||||
run_cmd "Removing iptables add script" rm /etc/iptables/add-openvpn-rules.sh
|
run_cmd "Removing IPv6 source rule" firewall-cmd --permanent --remove-rich-rule='rule family="ipv6" source address="fd42:42:42:42::/112" accept' 2>/dev/null || true
|
||||||
run_cmd "Removing iptables rm script" rm /etc/iptables/rm-openvpn-rules.sh
|
run_cmd "Reloading firewalld" firewall-cmd --reload
|
||||||
|
elif [[ -f /etc/systemd/system/iptables-openvpn.service ]]; then
|
||||||
|
# iptables was used
|
||||||
|
run_cmd "Stopping iptables service" systemctl stop iptables-openvpn
|
||||||
|
run_cmd "Disabling iptables service" systemctl disable iptables-openvpn
|
||||||
|
run_cmd "Removing iptables service file" rm /etc/systemd/system/iptables-openvpn.service
|
||||||
|
run_cmd "Reloading systemd" systemctl daemon-reload
|
||||||
|
run_cmd "Removing iptables add script" rm -f /etc/iptables/add-openvpn-rules.sh
|
||||||
|
run_cmd "Removing iptables rm script" rm -f /etc/iptables/rm-openvpn-rules.sh
|
||||||
|
fi
|
||||||
|
|
||||||
# SELinux
|
# SELinux
|
||||||
if hash sestatus 2>/dev/null; then
|
if hash sestatus 2>/dev/null; then
|
||||||
@@ -2085,12 +2212,13 @@ function manageMenu() {
|
|||||||
log_menu ""
|
log_menu ""
|
||||||
log_prompt "What do you want to do?"
|
log_prompt "What do you want to do?"
|
||||||
log_menu " 1) Add a new user"
|
log_menu " 1) Add a new user"
|
||||||
log_menu " 2) Revoke existing user"
|
log_menu " 2) List client certificates"
|
||||||
log_menu " 3) Renew certificate"
|
log_menu " 3) Revoke existing user"
|
||||||
log_menu " 4) Remove OpenVPN"
|
log_menu " 4) Renew certificate"
|
||||||
log_menu " 5) Exit"
|
log_menu " 5) Remove OpenVPN"
|
||||||
until [[ ${MENU_OPTION:-$menu_option} =~ ^[1-5]$ ]]; do
|
log_menu " 6) Exit"
|
||||||
read -rp "Select an option [1-5]: " menu_option
|
until [[ ${MENU_OPTION:-$menu_option} =~ ^[1-6]$ ]]; do
|
||||||
|
read -rp "Select an option [1-6]: " menu_option
|
||||||
done
|
done
|
||||||
menu_option="${MENU_OPTION:-$menu_option}"
|
menu_option="${MENU_OPTION:-$menu_option}"
|
||||||
|
|
||||||
@@ -2099,15 +2227,18 @@ function manageMenu() {
|
|||||||
newClient
|
newClient
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
revokeClient
|
listClients
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
renewMenu
|
revokeClient
|
||||||
;;
|
;;
|
||||||
4)
|
4)
|
||||||
removeOpenVPN
|
renewMenu
|
||||||
;;
|
;;
|
||||||
5)
|
5)
|
||||||
|
removeOpenVPN
|
||||||
|
;;
|
||||||
|
6)
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ ARG BASE_IMAGE=ubuntu:24.04
|
|||||||
FROM ${BASE_IMAGE}
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
ARG BASE_IMAGE
|
ARG BASE_IMAGE
|
||||||
|
# Set to "y" to install and enable firewalld for testing
|
||||||
|
ARG ENABLE_FIREWALLD=n
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV ENABLE_FIREWALLD=${ENABLE_FIREWALLD}
|
||||||
|
|
||||||
# Install basic dependencies based on the OS
|
# Install basic dependencies based on the OS
|
||||||
# dnsutils/bind-utils provides dig for DNS testing with Unbound
|
# dnsutils/bind-utils provides dig for DNS testing with Unbound
|
||||||
@@ -16,10 +19,12 @@ RUN if command -v apt-get >/dev/null; then \
|
|||||||
elif command -v dnf >/dev/null; then \
|
elif command -v dnf >/dev/null; then \
|
||||||
dnf install -y --allowerasing \
|
dnf install -y --allowerasing \
|
||||||
iproute iptables curl procps-ng systemd tar gzip bind-utils \
|
iproute iptables curl procps-ng systemd tar gzip bind-utils \
|
||||||
|
&& if [ "$ENABLE_FIREWALLD" = "y" ]; then dnf install -y firewalld; fi \
|
||||||
&& dnf clean all; \
|
&& dnf clean all; \
|
||||||
elif command -v yum >/dev/null; then \
|
elif command -v yum >/dev/null; then \
|
||||||
yum install -y \
|
yum install -y \
|
||||||
iproute iptables curl procps-ng systemd tar gzip bind-utils \
|
iproute iptables curl procps-ng systemd tar gzip bind-utils \
|
||||||
|
&& if [ "$ENABLE_FIREWALLD" = "y" ]; then yum install -y firewalld; fi \
|
||||||
&& yum clean all; \
|
&& yum clean all; \
|
||||||
elif command -v pacman >/dev/null; then \
|
elif command -v pacman >/dev/null; then \
|
||||||
pacman -Syu --noconfirm \
|
pacman -Syu --noconfirm \
|
||||||
@@ -31,6 +36,11 @@ RUN if command -v apt-get >/dev/null; then \
|
|||||||
&& zypper clean -a; \
|
&& zypper clean -a; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Enable firewalld if requested (must be done after systemd is available)
|
||||||
|
RUN if [ "$ENABLE_FIREWALLD" = "y" ] && command -v firewall-cmd >/dev/null; then \
|
||||||
|
systemctl enable firewalld; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Create TUN device (will be mounted at runtime)
|
# Create TUN device (will be mounted at runtime)
|
||||||
RUN mkdir -p /dev/net
|
RUN mkdir -p /dev/net
|
||||||
|
|
||||||
|
|||||||
@@ -359,6 +359,86 @@ touch /shared/new-client-connected
|
|||||||
echo ""
|
echo ""
|
||||||
echo "=== Certificate Revocation E2E Tests PASSED ==="
|
echo "=== Certificate Revocation E2E Tests PASSED ==="
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# Test PASSPHRASE-protected client connection
|
||||||
|
# =====================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== Testing PASSPHRASE-protected Client Connection ==="
|
||||||
|
|
||||||
|
PASSPHRASE_CLIENT="passphrasetest"
|
||||||
|
|
||||||
|
# Wait for passphrase test client config
|
||||||
|
echo "Waiting for passphrase test client config..."
|
||||||
|
MAX_WAIT=120
|
||||||
|
WAITED=0
|
||||||
|
while [ ! -f /shared/passphrase-client-config-ready ] && [ $WAITED -lt $MAX_WAIT ]; do
|
||||||
|
sleep 2
|
||||||
|
WAITED=$((WAITED + 2))
|
||||||
|
echo "Waiting for passphrase test config... ($WAITED/$MAX_WAIT seconds)"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -f /shared/passphrase-client-config-ready ]; then
|
||||||
|
echo "FAIL: Passphrase test client config not ready in time"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "/shared/$PASSPHRASE_CLIENT.ovpn" ]; then
|
||||||
|
echo "FAIL: Passphrase test client config file not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "/shared/$PASSPHRASE_CLIENT.pass" ]; then
|
||||||
|
echo "FAIL: Passphrase file not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Passphrase test client config found!"
|
||||||
|
|
||||||
|
# Disconnect current VPN before connecting with passphrase client
|
||||||
|
echo "Disconnecting current VPN connection..."
|
||||||
|
pkill openvpn || true
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Connect with passphrase-protected client using --askpass
|
||||||
|
echo "Connecting with '$PASSPHRASE_CLIENT' certificate (passphrase-protected)..."
|
||||||
|
openvpn --config "/shared/$PASSPHRASE_CLIENT.ovpn" --askpass "/shared/$PASSPHRASE_CLIENT.pass" --daemon --log /var/log/openvpn-passphrase.log
|
||||||
|
|
||||||
|
# Wait for connection
|
||||||
|
echo "Waiting for VPN connection with passphrase-protected client..."
|
||||||
|
MAX_WAIT=60
|
||||||
|
WAITED=0
|
||||||
|
while ! ip addr show tun0 2>/dev/null | grep -q "inet " && [ $WAITED -lt $MAX_WAIT ]; do
|
||||||
|
sleep 2
|
||||||
|
WAITED=$((WAITED + 2))
|
||||||
|
echo "Waiting for tun0... ($WAITED/$MAX_WAIT seconds)"
|
||||||
|
if [ -f /var/log/openvpn-passphrase.log ]; then
|
||||||
|
tail -3 /var/log/openvpn-passphrase.log
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! ip addr show tun0 2>/dev/null | grep -q "inet "; then
|
||||||
|
echo "FAIL: VPN connection with passphrase-protected client failed"
|
||||||
|
cat /var/log/openvpn-passphrase.log || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PASS: Connected with passphrase-protected '$PASSPHRASE_CLIENT' certificate"
|
||||||
|
ip addr show tun0
|
||||||
|
|
||||||
|
# Verify connectivity
|
||||||
|
if ping -c 2 10.8.0.1 >/dev/null 2>&1; then
|
||||||
|
echo "PASS: Can ping VPN gateway with passphrase-protected client"
|
||||||
|
else
|
||||||
|
echo "FAIL: Cannot ping VPN gateway with passphrase-protected client"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Signal server that we connected with passphrase client
|
||||||
|
touch /shared/passphrase-client-connected
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== PASSPHRASE-protected Client Tests PASSED ==="
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " ALL TESTS PASSED!"
|
echo " ALL TESTS PASSED!"
|
||||||
|
|||||||
@@ -77,15 +77,22 @@ fi
|
|||||||
# Verify all expected files were created
|
# Verify all expected files were created
|
||||||
echo "Verifying installation..."
|
echo "Verifying installation..."
|
||||||
MISSING_FILES=0
|
MISSING_FILES=0
|
||||||
for f in \
|
# Build list of required files
|
||||||
/etc/openvpn/server/server.conf \
|
REQUIRED_FILES=(
|
||||||
/etc/openvpn/server/ca.crt \
|
/etc/openvpn/server/server.conf
|
||||||
/etc/openvpn/server/ca.key \
|
/etc/openvpn/server/ca.crt
|
||||||
"/etc/openvpn/server/$TLS_KEY_FILE" \
|
/etc/openvpn/server/ca.key
|
||||||
/etc/openvpn/server/crl.pem \
|
"/etc/openvpn/server/$TLS_KEY_FILE"
|
||||||
/etc/openvpn/server/easy-rsa/pki/ca.crt \
|
/etc/openvpn/server/crl.pem
|
||||||
/etc/iptables/add-openvpn-rules.sh \
|
/etc/openvpn/server/easy-rsa/pki/ca.crt
|
||||||
/root/testclient.ovpn; do
|
/root/testclient.ovpn
|
||||||
|
)
|
||||||
|
# Only check for iptables script if firewalld is not active
|
||||||
|
if ! systemctl is-active --quiet firewalld; then
|
||||||
|
REQUIRED_FILES+=(/etc/iptables/add-openvpn-rules.sh)
|
||||||
|
fi
|
||||||
|
|
||||||
|
for f in "${REQUIRED_FILES[@]}"; do
|
||||||
if [ ! -f "$f" ]; then
|
if [ ! -f "$f" ]; then
|
||||||
echo "ERROR: Missing file: $f"
|
echo "ERROR: Missing file: $f"
|
||||||
MISSING_FILES=$((MISSING_FILES + 1))
|
MISSING_FILES=$((MISSING_FILES + 1))
|
||||||
@@ -177,7 +184,7 @@ echo "Original client certificate serial: $ORIG_CERT_SERIAL"
|
|||||||
# Test client certificate renewal using the script
|
# Test client certificate renewal using the script
|
||||||
echo "Testing client certificate renewal..."
|
echo "Testing client certificate renewal..."
|
||||||
RENEW_OUTPUT="/tmp/renew-client-output.log"
|
RENEW_OUTPUT="/tmp/renew-client-output.log"
|
||||||
(MENU_OPTION=3 RENEW_OPTION=1 CLIENTNUMBER=1 CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RENEW_OUTPUT" || true
|
(MENU_OPTION=4 RENEW_OPTION=1 CLIENTNUMBER=1 CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RENEW_OUTPUT" || true
|
||||||
|
|
||||||
# Verify renewal succeeded
|
# Verify renewal succeeded
|
||||||
if grep -q "Certificate for client testclient renewed" "$RENEW_OUTPUT"; then
|
if grep -q "Certificate for client testclient renewed" "$RENEW_OUTPUT"; then
|
||||||
@@ -257,7 +264,7 @@ echo "Original server certificate serial: $ORIG_SERVER_SERIAL"
|
|||||||
# Test server certificate renewal
|
# Test server certificate renewal
|
||||||
echo "Testing server certificate renewal..."
|
echo "Testing server certificate renewal..."
|
||||||
RENEW_SERVER_OUTPUT="/tmp/renew-server-output.log"
|
RENEW_SERVER_OUTPUT="/tmp/renew-server-output.log"
|
||||||
(MENU_OPTION=3 RENEW_OPTION=2 CONTINUE=y SERVER_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RENEW_SERVER_OUTPUT" || true
|
(MENU_OPTION=4 RENEW_OPTION=2 CONTINUE=y SERVER_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RENEW_SERVER_OUTPUT" || true
|
||||||
|
|
||||||
# Verify renewal succeeded
|
# Verify renewal succeeded
|
||||||
if grep -q "Server certificate renewed successfully" "$RENEW_SERVER_OUTPUT"; then
|
if grep -q "Server certificate renewed successfully" "$RENEW_SERVER_OUTPUT"; then
|
||||||
@@ -380,21 +387,58 @@ echo ""
|
|||||||
# Verify OpenVPN server (started by systemd via install script)
|
# Verify OpenVPN server (started by systemd via install script)
|
||||||
echo "Verifying OpenVPN server..."
|
echo "Verifying OpenVPN server..."
|
||||||
|
|
||||||
# Verify iptables NAT rules exist (applied by iptables-openvpn service)
|
# Verify firewall rules exist
|
||||||
echo "Verifying iptables NAT rules..."
|
echo "Verifying firewall rules..."
|
||||||
for _ in $(seq 1 10); do
|
if systemctl is-active --quiet firewalld; then
|
||||||
if iptables -t nat -L POSTROUTING -n | grep -q "10.8.0.0"; then
|
# firewalld is active - verify masquerade is enabled
|
||||||
echo "PASS: NAT POSTROUTING rule for 10.8.0.0/24 exists"
|
echo "firewalld detected, checking masquerade..."
|
||||||
break
|
for _ in $(seq 1 10); do
|
||||||
|
if firewall-cmd --query-masquerade 2>/dev/null; then
|
||||||
|
echo "PASS: firewalld masquerade is enabled"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if ! firewall-cmd --query-masquerade 2>/dev/null; then
|
||||||
|
echo "FAIL: firewalld masquerade is not enabled"
|
||||||
|
echo "Current firewalld config:"
|
||||||
|
firewall-cmd --list-all 2>&1 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Verify port is open
|
||||||
|
if firewall-cmd --list-ports | grep -q "1194/udp"; then
|
||||||
|
echo "PASS: OpenVPN port is open in firewalld"
|
||||||
|
else
|
||||||
|
echo "FAIL: OpenVPN port not found in firewalld"
|
||||||
|
firewall-cmd --list-ports
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Verify VPN subnet rich rule exists
|
||||||
|
if firewall-cmd --list-rich-rules | grep -q 'source address="10.8.0.0/24"'; then
|
||||||
|
echo "PASS: VPN subnet rich rule is configured"
|
||||||
|
else
|
||||||
|
echo "FAIL: VPN subnet rich rule not found in firewalld"
|
||||||
|
echo "Current rich rules:"
|
||||||
|
firewall-cmd --list-rich-rules
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# iptables mode - verify NAT rules
|
||||||
|
echo "iptables mode, checking NAT rules..."
|
||||||
|
for _ in $(seq 1 10); do
|
||||||
|
if iptables -t nat -L POSTROUTING -n | grep -q "10.8.0.0"; then
|
||||||
|
echo "PASS: NAT POSTROUTING rule for 10.8.0.0/24 exists"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if ! iptables -t nat -L POSTROUTING -n | grep -q "10.8.0.0"; then
|
||||||
|
echo "FAIL: NAT POSTROUTING rule for 10.8.0.0/24 not found"
|
||||||
|
echo "Current NAT rules:"
|
||||||
|
iptables -t nat -L POSTROUTING -n -v
|
||||||
|
systemctl status iptables-openvpn 2>&1 || true
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
if ! iptables -t nat -L POSTROUTING -n | grep -q "10.8.0.0"; then
|
|
||||||
echo "FAIL: NAT POSTROUTING rule for 10.8.0.0/24 not found"
|
|
||||||
echo "Current NAT rules:"
|
|
||||||
iptables -t nat -L POSTROUTING -n -v
|
|
||||||
systemctl status iptables-openvpn 2>&1 || true
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify IP forwarding is enabled
|
# Verify IP forwarding is enabled
|
||||||
@@ -501,19 +545,11 @@ if [ ! -f /shared/revoke-client-disconnected ]; then
|
|||||||
fi
|
fi
|
||||||
echo "Client disconnected"
|
echo "Client disconnected"
|
||||||
|
|
||||||
# Now revoke the certificate
|
# Now revoke the certificate using the new CLIENT name feature
|
||||||
echo "Revoking certificate for '$REVOKE_CLIENT'..."
|
echo "Revoking certificate for '$REVOKE_CLIENT'..."
|
||||||
REVOKE_OUTPUT="/tmp/revoke-output.log"
|
REVOKE_OUTPUT="/tmp/revoke-output.log"
|
||||||
# MENU_OPTION=2 is revoke, CLIENTNUMBER is dynamically determined from index.txt
|
# MENU_OPTION=3 is revoke, CLIENT specifies the client name directly
|
||||||
# We need to find the client number for revoketest
|
(MENU_OPTION=3 CLIENT=$REVOKE_CLIENT bash /opt/openvpn-install.sh) 2>&1 | tee "$REVOKE_OUTPUT" || true
|
||||||
REVOKE_CLIENT_NUM=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | grep -n "CN=$REVOKE_CLIENT\$" | cut -d: -f1)
|
|
||||||
if [ -z "$REVOKE_CLIENT_NUM" ]; then
|
|
||||||
echo "ERROR: Could not find client number for '$REVOKE_CLIENT'"
|
|
||||||
cat /etc/openvpn/server/easy-rsa/pki/index.txt
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Revoke client number: $REVOKE_CLIENT_NUM"
|
|
||||||
(MENU_OPTION=2 CLIENTNUMBER=$REVOKE_CLIENT_NUM bash /opt/openvpn-install.sh) 2>&1 | tee "$REVOKE_OUTPUT" || true
|
|
||||||
|
|
||||||
if grep -q "Certificate for client $REVOKE_CLIENT revoked" "$REVOKE_OUTPUT"; then
|
if grep -q "Certificate for client $REVOKE_CLIENT revoked" "$REVOKE_OUTPUT"; then
|
||||||
echo "PASS: Certificate for '$REVOKE_CLIENT' revoked successfully"
|
echo "PASS: Certificate for '$REVOKE_CLIENT' revoked successfully"
|
||||||
@@ -553,6 +589,47 @@ echo "PASS: Connection with revoked certificate correctly rejected"
|
|||||||
|
|
||||||
echo "=== Certificate Revocation Tests PASSED ==="
|
echo "=== Certificate Revocation Tests PASSED ==="
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# Test listing client certificates
|
||||||
|
# =====================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== Testing List Client Certificates ==="
|
||||||
|
|
||||||
|
# At this point we have 3 client certificates:
|
||||||
|
# - testclient (Valid) - the renewed certificate
|
||||||
|
# - testclient (Revoked) - the old certificate revoked during renewal
|
||||||
|
# - revoketest (Revoked) - the revoked certificate
|
||||||
|
LIST_OUTPUT="/tmp/list-clients-output.log"
|
||||||
|
(MENU_OPTION=2 bash /opt/openvpn-install.sh) 2>&1 | tee "$LIST_OUTPUT" || true
|
||||||
|
|
||||||
|
# Verify list output contains expected clients
|
||||||
|
if grep -q "testclient" "$LIST_OUTPUT" && grep -q "Valid" "$LIST_OUTPUT"; then
|
||||||
|
echo "PASS: List shows testclient as Valid"
|
||||||
|
else
|
||||||
|
echo "FAIL: List does not show testclient correctly"
|
||||||
|
cat "$LIST_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "$REVOKE_CLIENT" "$LIST_OUTPUT" && grep -q "Revoked" "$LIST_OUTPUT"; then
|
||||||
|
echo "PASS: List shows $REVOKE_CLIENT as Revoked"
|
||||||
|
else
|
||||||
|
echo "FAIL: List does not show $REVOKE_CLIENT correctly"
|
||||||
|
cat "$LIST_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify certificate count (3 certs: testclient valid, testclient revoked from renewal, revoketest revoked)
|
||||||
|
if grep -q "Found 3 client certificate(s)" "$LIST_OUTPUT"; then
|
||||||
|
echo "PASS: List shows correct certificate count"
|
||||||
|
else
|
||||||
|
echo "FAIL: List does not show correct certificate count"
|
||||||
|
cat "$LIST_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== List Client Certificates Tests PASSED ==="
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# Test reusing revoked client name
|
# Test reusing revoked client name
|
||||||
# =====================================================
|
# =====================================================
|
||||||
@@ -618,6 +695,85 @@ fi
|
|||||||
echo "PASS: Client connected with new '$REVOKE_CLIENT' certificate"
|
echo "PASS: Client connected with new '$REVOKE_CLIENT' certificate"
|
||||||
|
|
||||||
echo "=== Reuse of Revoked Client Name Tests PASSED ==="
|
echo "=== Reuse of Revoked Client Name Tests PASSED ==="
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# Test PASSPHRASE support for headless client creation
|
||||||
|
# =====================================================
|
||||||
|
echo ""
|
||||||
|
echo "=== Testing PASSPHRASE Support ==="
|
||||||
|
|
||||||
|
PASSPHRASE_CLIENT="passphrasetest"
|
||||||
|
TEST_PASSPHRASE="TestP@ssw0rd#123"
|
||||||
|
echo "Creating client '$PASSPHRASE_CLIENT' with passphrase in headless mode..."
|
||||||
|
PASSPHRASE_OUTPUT="/tmp/passphrase-output.log"
|
||||||
|
(MENU_OPTION=1 CLIENT=$PASSPHRASE_CLIENT PASS=2 PASSPHRASE="$TEST_PASSPHRASE" CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$PASSPHRASE_OUTPUT" || true
|
||||||
|
|
||||||
|
# Verify client was created
|
||||||
|
if [ -f "/root/$PASSPHRASE_CLIENT.ovpn" ]; then
|
||||||
|
echo "PASS: Client '$PASSPHRASE_CLIENT' with passphrase created successfully"
|
||||||
|
else
|
||||||
|
echo "FAIL: Failed to create client '$PASSPHRASE_CLIENT' with passphrase"
|
||||||
|
cat "$PASSPHRASE_OUTPUT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify the passphrase is NOT leaked in the output
|
||||||
|
if grep -q "$TEST_PASSPHRASE" "$PASSPHRASE_OUTPUT"; then
|
||||||
|
echo "FAIL: Passphrase was leaked in command output!"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "PASS: Passphrase not leaked in command output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify the log file doesn't contain the passphrase
|
||||||
|
if [ -f /opt/openvpn-install.log ] && grep -q "$TEST_PASSPHRASE" /opt/openvpn-install.log; then
|
||||||
|
echo "FAIL: Passphrase was leaked in log file!"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "PASS: Passphrase not leaked in log file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify certificate was created with encryption (key should be encrypted)
|
||||||
|
CLIENT_KEY="/etc/openvpn/server/easy-rsa/pki/private/$PASSPHRASE_CLIENT.key"
|
||||||
|
if [ -f "$CLIENT_KEY" ]; then
|
||||||
|
if grep -q "ENCRYPTED" "$CLIENT_KEY"; then
|
||||||
|
echo "PASS: Client key is encrypted"
|
||||||
|
else
|
||||||
|
echo "FAIL: Client key is not encrypted"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "FAIL: Client key not found at $CLIENT_KEY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy config for passphrase client connectivity test
|
||||||
|
cp "/root/$PASSPHRASE_CLIENT.ovpn" "/shared/$PASSPHRASE_CLIENT.ovpn"
|
||||||
|
sed -i 's/^remote .*/remote openvpn-server 1194/' "/shared/$PASSPHRASE_CLIENT.ovpn"
|
||||||
|
# Write passphrase to a file for client to use with --askpass
|
||||||
|
echo "$TEST_PASSPHRASE" >"/shared/$PASSPHRASE_CLIENT.pass"
|
||||||
|
echo "Copied $PASSPHRASE_CLIENT config and passphrase to /shared/"
|
||||||
|
|
||||||
|
# Signal client that passphrase test config is ready
|
||||||
|
touch /shared/passphrase-client-config-ready
|
||||||
|
|
||||||
|
# Wait for client to confirm connection with passphrase client
|
||||||
|
echo "Waiting for client to connect with '$PASSPHRASE_CLIENT' certificate..."
|
||||||
|
MAX_WAIT=60
|
||||||
|
WAITED=0
|
||||||
|
while [ ! -f /shared/passphrase-client-connected ] && [ $WAITED -lt $MAX_WAIT ]; do
|
||||||
|
sleep 2
|
||||||
|
WAITED=$((WAITED + 2))
|
||||||
|
echo "Waiting for passphrase client connection... ($WAITED/$MAX_WAIT seconds)"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -f /shared/passphrase-client-connected ]; then
|
||||||
|
echo "FAIL: Client did not connect with passphrase-protected certificate"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "PASS: Client connected with passphrase-protected certificate"
|
||||||
|
|
||||||
|
echo "=== PASSPHRASE Support Tests PASSED ==="
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== All Revocation Tests PASSED ==="
|
echo "=== All Revocation Tests PASSED ==="
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user