From db2fe12258e4fe2b70cab62c831f2970e9da4104 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 20:52:28 +0100 Subject: [PATCH 001/141] Update FUNDING.yml --- .github/FUNDING.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 42b3ecb..cda9719 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,5 +1,2 @@ -patreon: stanislas -liberapay: stanislas ko_fi: stanislas -github: angristan custom: https://coindrop.to/stanislas From 5fcce43d8d24aed7595916a9741503e5112af3ed Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:02:46 +0100 Subject: [PATCH 002/141] Update CI images --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a8a001..b62a5d8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,13 +16,10 @@ jobs: - debian-11-x64 - ubuntu-18-04-x64 - ubuntu-20-04-x64 - - ubuntu-21-04-x64 - ubuntu-21-10-x64 - - fedora-33-x64 - - fedora-34-x64 - fedora-35-x64 - centos-7-x64 - - centos-8-x64 + - centos-stream-8-x64 steps: - uses: actions/checkout@v2.4.0 From b4d6acac833d6846e93c2460f1d4868ab152ace8 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:04:46 +0100 Subject: [PATCH 003/141] Update feature-request.md --- .github/ISSUE_TEMPLATE/feature-request.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index e5dd037..d20e804 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -15,17 +15,9 @@ assignees: '' - [ ] My issue is about the script, and not OpenVPN itself -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. From 68f2080f0cf86479a5e5a8706c06ef6c4555faa3 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:15:10 +0100 Subject: [PATCH 004/141] Update bug-report-or-suport-request.md --- .../bug-report-or-suport-request.md | 90 +++++++++++-------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md b/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md index 93b6be9..c781d4d 100644 --- a/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md +++ b/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md @@ -1,35 +1,55 @@ ---- -name: Bug report / Support request -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**⚠️ Unless you are sure you find a bug with the script, please open a [discussion](https://github.com/angristan/openvpn-install/discussions) instead of an issue!** - -**Checklist** - -- [ ] I read the [README](https://github.com/angristan/openvpn-install/blob/master/README.md) -- [ ] I read the [FAQ](https://github.com/angristan/openvpn-install/blob/master/FAQ.md) -- [ ] I searched the [issues](https://github.com/angristan/openvpn-install/issues?q=is%3Aissue+) -- [ ] I searched the [discussion](https://github.com/angristan/openvpn-install/discussions) -- [ ] My issue is about the script, and not OpenVPN itself - - - -Pease include as much details as possible in your issue: - -- Description of the issue -- How to reproduce the issue -- What did you expected should happen -- Logs -- Server/Client versions (OS, OpenVPN, etc) -- Any context or information that could help - ---- - - +name: Bug Report +description: File a bug report +title: "[Bug]: " +labels: ["bug", "triage"] +assignees: + - octocat +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + PLEASE prioritise these communities before opening an issue. + - https://forums.openvpn.net + - https://stackoverflow.com/questions/tagged/openvpn + - https://github.com/angristan/openvpn-install/discussions + ANY ISSUE THAT IS NOT CLEARLY A BUG REPORT WILL BE CLOSED AND/OR CONVERTED TO A DISCUSSION. + - type: input + id: server + attributes: + label: Server OS + description: What OS is the OpenVPN server running on? + placeholder: Debian 10 + validations: + required: false + - type: input + id: openvpn + attributes: + label: OpenVPN version + description: What OpenVPN version is running on your server? + placeholder: 2.5.0 + validations: + required: false + - type: input + id: client + attributes: + label: Client + description: What OS and client are you using? Please specify the versions. + placeholder: Viscosity 1.10.1 on macOS 12.1 + validations: + required: false + - type: textarea + id: issue + attributes: + label: What is the bug? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell From ad9c06941154f6266c89d7cba2c079958a7e31aa Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:16:25 +0100 Subject: [PATCH 005/141] Update bug-report-or-suport-request.md --- .github/ISSUE_TEMPLATE/bug-report-or-suport-request.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md b/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md index c781d4d..3696200 100644 --- a/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md +++ b/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md @@ -1,9 +1,6 @@ name: Bug Report description: File a bug report title: "[Bug]: " -labels: ["bug", "triage"] -assignees: - - octocat body: - type: markdown attributes: @@ -14,6 +11,7 @@ body: - https://stackoverflow.com/questions/tagged/openvpn - https://github.com/angristan/openvpn-install/discussions ANY ISSUE THAT IS NOT CLEARLY A BUG REPORT WILL BE CLOSED AND/OR CONVERTED TO A DISCUSSION. + - type: input id: server attributes: @@ -22,6 +20,7 @@ body: placeholder: Debian 10 validations: required: false + - type: input id: openvpn attributes: @@ -30,6 +29,7 @@ body: placeholder: 2.5.0 validations: required: false + - type: input id: client attributes: @@ -38,6 +38,7 @@ body: placeholder: Viscosity 1.10.1 on macOS 12.1 validations: required: false + - type: textarea id: issue attributes: @@ -47,6 +48,7 @@ body: value: "A bug happened!" validations: required: true + - type: textarea id: logs attributes: From ee862414cdadf09d96372f214ce71e5f1e30a88d Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:17:17 +0100 Subject: [PATCH 006/141] Rename bug-report-or-suport-request.md to bug.yml --- .../ISSUE_TEMPLATE/{bug-report-or-suport-request.md => bug.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{bug-report-or-suport-request.md => bug.yml} (100%) diff --git a/.github/ISSUE_TEMPLATE/bug-report-or-suport-request.md b/.github/ISSUE_TEMPLATE/bug.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/bug-report-or-suport-request.md rename to .github/ISSUE_TEMPLATE/bug.yml From 194077bcf7525a3931476aab67ac0d7b7c7d9d61 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:20:00 +0100 Subject: [PATCH 007/141] Update bug.yml --- .github/ISSUE_TEMPLATE/bug.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 3696200..656e789 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -6,11 +6,21 @@ body: attributes: value: | Thanks for taking the time to fill out this bug report! - PLEASE prioritise these communities before opening an issue. + **PLEASE** prioritise these communities before opening an issue. - https://forums.openvpn.net - https://stackoverflow.com/questions/tagged/openvpn - https://github.com/angristan/openvpn-install/discussions - ANY ISSUE THAT IS NOT CLEARLY A BUG REPORT WILL BE CLOSED AND/OR CONVERTED TO A DISCUSSION. + + **ANY ISSUE THAT IS NOT CLEARLY A BUG REPORT WILL BE CLOSED AND/OR CONVERTED TO A DISCUSSION.** + + - type: checkboxes + id: pre + attributes: + label: Make sure your check these beforehand! + description: Search before opening an issue. + options: + - label: [Issues](https://github.com/angristan/openvpn-install/issues) + required: true - type: input id: server From 6be8f0aeb54b3a746b8c04636071724b532f1f64 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:20:28 +0100 Subject: [PATCH 008/141] Update bug.yml --- .github/ISSUE_TEMPLATE/bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 656e789..d68c09a 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -19,7 +19,7 @@ body: label: Make sure your check these beforehand! description: Search before opening an issue. options: - - label: [Issues](https://github.com/angristan/openvpn-install/issues) + - label: Issues: https://github.com/angristan/openvpn-install/issues required: true - type: input From 8349bf66d44907640e4abf61d9f12cb50c76cce6 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:20:41 +0100 Subject: [PATCH 009/141] Update bug.yml --- .github/ISSUE_TEMPLATE/bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index d68c09a..c4588fa 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -19,7 +19,7 @@ body: label: Make sure your check these beforehand! description: Search before opening an issue. options: - - label: Issues: https://github.com/angristan/openvpn-install/issues + - label: Issues - https://github.com/angristan/openvpn-install/issues required: true - type: input From 45f4ebcc6ead5cbf4c43ac0506c4f984bec13f2d Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 24 Jan 2022 21:22:17 +0100 Subject: [PATCH 010/141] Update bug.yml --- .github/ISSUE_TEMPLATE/bug.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index c4588fa..bb3ff34 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -6,12 +6,13 @@ body: attributes: value: | Thanks for taking the time to fill out this bug report! + **PLEASE** prioritise these communities before opening an issue. - https://forums.openvpn.net - https://stackoverflow.com/questions/tagged/openvpn - https://github.com/angristan/openvpn-install/discussions - **ANY ISSUE THAT IS NOT CLEARLY A BUG REPORT WILL BE CLOSED AND/OR CONVERTED TO A DISCUSSION.** + ⚠️ **ANY ISSUE THAT IS NOT CLEARLY A BUG REPORT WILL BE CLOSED AND/OR CONVERTED TO A DISCUSSION.** - type: checkboxes id: pre @@ -21,7 +22,13 @@ body: options: - label: Issues - https://github.com/angristan/openvpn-install/issues required: true - + - label: README and FAQ - https://github.com/angristan/openvpn-install + required: true + - label: Wiki - https://github.com/angristan/openvpn-install/wiki + required: true + - label: Discussions - https://github.com/angristan/openvpn-install/discussions + required: true + - type: input id: server attributes: From 6e12bf0b2cda78dfff46d1d91a0baae93851a507 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Wed, 26 Jan 2022 21:54:29 +0100 Subject: [PATCH 011/141] Fix linting --- .github/ISSUE_TEMPLATE/bug.yml | 12 ++++++------ .github/ISSUE_TEMPLATE/feature-request.md | 8 +++----- .github/linters/.markdown-lint.yml | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index bb3ff34..0eacdc9 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -6,14 +6,14 @@ body: attributes: value: | Thanks for taking the time to fill out this bug report! - + **PLEASE** prioritise these communities before opening an issue. - https://forums.openvpn.net - https://stackoverflow.com/questions/tagged/openvpn - https://github.com/angristan/openvpn-install/discussions - + ⚠️ **ANY ISSUE THAT IS NOT CLEARLY A BUG REPORT WILL BE CLOSED AND/OR CONVERTED TO A DISCUSSION.** - + - type: checkboxes id: pre attributes: @@ -37,7 +37,7 @@ body: placeholder: Debian 10 validations: required: false - + - type: input id: openvpn attributes: @@ -46,7 +46,7 @@ body: placeholder: 2.5.0 validations: required: false - + - type: input id: client attributes: @@ -55,7 +55,7 @@ body: placeholder: Viscosity 1.10.1 on macOS 12.1 validations: required: false - + - type: textarea id: issue attributes: diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index d20e804..dcc367d 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Checklist** @@ -20,4 +19,3 @@ If you need help with OpenVPN itself, please us the [community forums](https://f PLEASE OPEN A DISCUSSION INSTEAD: https://github.com/angristan/openvpn-install/discussions ⚠️ ---> - diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index 68d1fdf..962acbc 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -1 +1 @@ -{ 'MD013': null, 'MD045': null, 'MD040': null, 'MD036': null } +{ "MD013": null, "MD045": null, "MD040": null, "MD036": null } From f8ec7c39bea999ad4702f7644bfcfab73b76b47f Mon Sep 17 00:00:00 2001 From: Stanislas <11699655+angristan@users.noreply.github.com> Date: Mon, 31 Jan 2022 01:35:59 +0100 Subject: [PATCH 012/141] Update FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index cda9719..0ddf502 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,3 @@ ko_fi: stanislas custom: https://coindrop.to/stanislas +custom: https://saythanks.io/to/angristan From 15d8fb053d5b58e1b8a1696cfa5a404f9d10d638 Mon Sep 17 00:00:00 2001 From: Stanislas <11699655+angristan@users.noreply.github.com> Date: Mon, 31 Jan 2022 01:36:28 +0100 Subject: [PATCH 013/141] Update FUNDING.yml --- .github/FUNDING.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0ddf502..cda9719 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,2 @@ ko_fi: stanislas custom: https://coindrop.to/stanislas -custom: https://saythanks.io/to/angristan From fdc3f25987cbfdcaf7a16d9684437f297a959831 Mon Sep 17 00:00:00 2001 From: Stanislas <11699655+angristan@users.noreply.github.com> Date: Mon, 31 Jan 2022 01:36:59 +0100 Subject: [PATCH 014/141] Fix saythanks URL in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04333cc..4f88d7b 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ The script supports both and uses `tls-crypt` by default. ## Say thanks -You can [say thanks](https://saythanks.io/to/angristan%40pm.me) if you want! +You can [say thanks](https://saythanks.io/to/angristan) if you want! ## Credits & Licence From c2059d684d9101436c1c0bcb7ead83f07b7e55ff Mon Sep 17 00:00:00 2001 From: Stanislas <11699655+angristan@users.noreply.github.com> Date: Mon, 31 Jan 2022 01:40:10 +0100 Subject: [PATCH 015/141] Add SayThanks badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4f88d7b..c769172 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ![Test](https://github.com/angristan/openvpn-install/workflows/Test/badge.svg) ![Lint](https://github.com/angristan/openvpn-install/workflows/Lint/badge.svg) ![visitors](https://visitor-badge.glitch.me/badge?page_id=angristan.openvpn-install) +[![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/angristan) OpenVPN installer for Debian, Ubuntu, Fedora, CentOS, Arch Linux, Oracle Linux, Rocky Linux and AlmaLinux. From b3b7593b2d4dd146f9c9da810bcec9b07a69c026 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 May 2022 11:51:39 +0200 Subject: [PATCH 016/141] build(deps): bump actions/checkout from 2.4.0 to 3.0.2 (#996) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3.0.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3.0.2) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7ad096b..ea69759 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v2.4.0 + uses: actions/checkout@v3.0.2 - name: Lint Code Base uses: github/super-linter@v4.1.0 env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b62a5d8..e5a478b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: - centos-7-x64 - centos-stream-8-x64 steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3.0.2 - name: Setup doctl uses: digitalocean/action-doctl@v2 From deb5867979646091cc3cdea5f8974e200353e0aa Mon Sep 17 00:00:00 2001 From: Adiyat Mubarak Date: Tue, 2 Aug 2022 15:09:31 +0700 Subject: [PATCH 017/141] Update README.md OpenVPN client for macOS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c769172..4e81f28 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ More Q&A in [FAQ.md](FAQ.md). - Windows: [The official OpenVPN community client](https://openvpn.net/index.php/download/community-downloads.html). - Linux: The `openvpn` package from your distribution. There is an [official APT repository](https://community.openvpn.net/openvpn/wiki/OpenvpnSoftwareRepos) for Debian/Ubuntu based distributions. -- macOS: [Tunnelblick](https://tunnelblick.net/), [Viscosity](https://www.sparklabs.com/viscosity/). +- macOS: [Tunnelblick](https://tunnelblick.net/), [Viscosity](https://www.sparklabs.com/viscosity/), [OpenVPN for Mac](https://openvpn.net/client-connect-vpn-for-mac-os/). - Android: [OpenVPN for Android](https://play.google.com/store/apps/details?id=de.blinkt.openvpn). - iOS: [The official OpenVPN Connect client](https://itunes.apple.com/us/app/openvpn-connect/id590379981). From 4553dd9c2181bc086975fd5c8e4bc56ba332a3e7 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Wed, 17 Aug 2022 16:08:12 +0200 Subject: [PATCH 018/141] ci: remove ubuntu 21.10 as it is not supported by DO --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5a478b..a0b796e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,6 @@ jobs: - debian-11-x64 - ubuntu-18-04-x64 - ubuntu-20-04-x64 - - ubuntu-21-10-x64 - fedora-35-x64 - centos-7-x64 - centos-stream-8-x64 From a7e54a47ce74c598fceea0bbd9b95c5ed1f8a70e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 03:00:54 +0000 Subject: [PATCH 019/141] build(deps): bump appleboy/ssh-action from 0.1.4 to 0.1.5 Bumps [appleboy/ssh-action](https://github.com/appleboy/ssh-action) from 0.1.4 to 0.1.5. - [Release notes](https://github.com/appleboy/ssh-action/releases) - [Commits](https://github.com/appleboy/ssh-action/compare/v0.1.4...v0.1.5) --- updated-dependencies: - dependency-name: appleboy/ssh-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a0b796e..bd28f79 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,7 +50,7 @@ jobs: - name: Setup remote server (Debian/Ubuntu) if: steps.server_os.outputs.value == 'debian' || steps.server_os.outputs.value == 'ubuntu' - uses: appleboy/ssh-action@v0.1.4 + uses: appleboy/ssh-action@v0.1.5 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -59,7 +59,7 @@ jobs: - name: Setup remote server (Fedora) if: steps.server_os.outputs.value == 'fedora' - uses: appleboy/ssh-action@v0.1.4 + uses: appleboy/ssh-action@v0.1.5 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -68,7 +68,7 @@ jobs: - name: Setup remote server (CentOS) if: steps.server_os.outputs.value == 'centos' - uses: appleboy/ssh-action@v0.1.4 + uses: appleboy/ssh-action@v0.1.5 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -76,7 +76,7 @@ jobs: script: set -x && yum install -y git - name: Download repo and checkout current commit - uses: appleboy/ssh-action@v0.1.4 + uses: appleboy/ssh-action@v0.1.5 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -84,7 +84,7 @@ jobs: script: set -x && git clone https://github.com/angristan/openvpn-install.git && cd openvpn-install && git checkout ${{ github.event.pull_request.head.sha }} - name: Run openvpn-install.sh in headless mode - uses: appleboy/ssh-action@v0.1.4 + uses: appleboy/ssh-action@v0.1.5 with: host: ${{ steps.server_ip.outputs.value }} username: root From 83111c58e787313ebc3a1e85b782aa6c2286abba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 03:00:50 +0000 Subject: [PATCH 020/141] build(deps): bump actions/checkout from 3.0.2 to 3.1.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.0.2 to 3.1.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.0.2...v3.1.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ea69759..b53d67f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3.0.2 + uses: actions/checkout@v3.1.0 - name: Lint Code Base uses: github/super-linter@v4.1.0 env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bd28f79..22af688 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: - centos-7-x64 - centos-stream-8-x64 steps: - - uses: actions/checkout@v3.0.2 + - uses: actions/checkout@v3.1.0 - name: Setup doctl uses: digitalocean/action-doctl@v2 From 0de56f8f335c74ad7de99c6165d1a8bc1d2e3149 Mon Sep 17 00:00:00 2001 From: Padraig Doran Date: Mon, 21 Nov 2022 18:12:46 +0000 Subject: [PATCH 021/141] Use --genkey secret filename instead (#1059) Fix for: WARNING: Using --genkey --secret filename is DEPRECATED. Use --genkey secret filename instead. https://community.openvpn.net/openvpn/wiki/DeprecatedOptions#Option:--secret Status Removed Deprecated in: OpenVPN v2.4 Removed in: OpenVPN v2.5 Affects: --genkey Result if used: User Warning printed Replaced by: secret (No leading double dash) Examples: Use --genkey secret filename Notes: --- openvpn-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 64d8ed0..c905976 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -742,11 +742,11 @@ function installOpenVPN() { case $TLS_SIG in 1) # Generate tls-crypt key - openvpn --genkey --secret /etc/openvpn/tls-crypt.key + openvpn --genkey secret /etc/openvpn/tls-crypt.key ;; 2) # Generate tls-auth key - openvpn --genkey --secret /etc/openvpn/tls-auth.key + openvpn --genkey secret /etc/openvpn/tls-auth.key ;; esac else From d3751457ab6fb4e012764ed2d47ab9ab279f9675 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 21 Nov 2022 19:15:22 +0100 Subject: [PATCH 022/141] Remove Debian 9 in e2e tests as it has been removed by DO --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 22af688..d030f08 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,6 @@ jobs: strategy: matrix: os-image: - - debian-9-x64 - debian-10-x64 - debian-11-x64 - ubuntu-18-04-x64 From 86a6d2d3e7715fac982657826202518d29e8af95 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Mon, 21 Nov 2022 19:33:24 +0100 Subject: [PATCH 023/141] Revert "Use --genkey secret filename instead (#1059)" This reverts commit 0de56f8f335c74ad7de99c6165d1a8bc1d2e3149. --- openvpn-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index c905976..64d8ed0 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -742,11 +742,11 @@ function installOpenVPN() { case $TLS_SIG in 1) # Generate tls-crypt key - openvpn --genkey secret /etc/openvpn/tls-crypt.key + openvpn --genkey --secret /etc/openvpn/tls-crypt.key ;; 2) # Generate tls-auth key - openvpn --genkey secret /etc/openvpn/tls-auth.key + openvpn --genkey --secret /etc/openvpn/tls-auth.key ;; esac else From d629791a95e308171ec003424fc7c197bb9b1e0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 14:05:14 +0100 Subject: [PATCH 024/141] build(deps): bump actions/checkout from 3.1.0 to 3.2.0 (#1075) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.1.0...v3.2.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b53d67f..7301cdf 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3.1.0 + uses: actions/checkout@v3.2.0 - name: Lint Code Base uses: github/super-linter@v4.1.0 env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d030f08..a869a1c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: - centos-7-x64 - centos-stream-8-x64 steps: - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3.2.0 - name: Setup doctl uses: digitalocean/action-doctl@v2 From 638b51168a9726be3d72253abcd18a0ed3cd6878 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 14:05:27 +0100 Subject: [PATCH 025/141] build(deps): bump appleboy/ssh-action from 0.1.5 to 0.1.6 (#1074) Bumps [appleboy/ssh-action](https://github.com/appleboy/ssh-action) from 0.1.5 to 0.1.6. - [Release notes](https://github.com/appleboy/ssh-action/releases) - [Commits](https://github.com/appleboy/ssh-action/compare/v0.1.5...v0.1.6) --- updated-dependencies: - dependency-name: appleboy/ssh-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a869a1c..af95572 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: - name: Setup remote server (Debian/Ubuntu) if: steps.server_os.outputs.value == 'debian' || steps.server_os.outputs.value == 'ubuntu' - uses: appleboy/ssh-action@v0.1.5 + uses: appleboy/ssh-action@v0.1.6 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -58,7 +58,7 @@ jobs: - name: Setup remote server (Fedora) if: steps.server_os.outputs.value == 'fedora' - uses: appleboy/ssh-action@v0.1.5 + uses: appleboy/ssh-action@v0.1.6 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -67,7 +67,7 @@ jobs: - name: Setup remote server (CentOS) if: steps.server_os.outputs.value == 'centos' - uses: appleboy/ssh-action@v0.1.5 + uses: appleboy/ssh-action@v0.1.6 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -75,7 +75,7 @@ jobs: script: set -x && yum install -y git - name: Download repo and checkout current commit - uses: appleboy/ssh-action@v0.1.5 + uses: appleboy/ssh-action@v0.1.6 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -83,7 +83,7 @@ jobs: script: set -x && git clone https://github.com/angristan/openvpn-install.git && cd openvpn-install && git checkout ${{ github.event.pull_request.head.sha }} - name: Run openvpn-install.sh in headless mode - uses: appleboy/ssh-action@v0.1.5 + uses: appleboy/ssh-action@v0.1.6 with: host: ${{ steps.server_ip.outputs.value }} username: root From 8e427ee9c2ec47487fe12d0f11a2ff8ef0264c6f Mon Sep 17 00:00:00 2001 From: Stanislas Date: Tue, 3 Jan 2023 14:06:19 +0100 Subject: [PATCH 026/141] Delete .github/ISSUE_TEMPLATE directory --- .github/ISSUE_TEMPLATE/bug.yml | 74 ----------------------- .github/ISSUE_TEMPLATE/feature-request.md | 21 ------- 2 files changed, 95 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.md diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml deleted file mode 100644 index 0eacdc9..0000000 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: Bug Report -description: File a bug report -title: "[Bug]: " -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - - **PLEASE** prioritise these communities before opening an issue. - - https://forums.openvpn.net - - https://stackoverflow.com/questions/tagged/openvpn - - https://github.com/angristan/openvpn-install/discussions - - ⚠️ **ANY ISSUE THAT IS NOT CLEARLY A BUG REPORT WILL BE CLOSED AND/OR CONVERTED TO A DISCUSSION.** - - - type: checkboxes - id: pre - attributes: - label: Make sure your check these beforehand! - description: Search before opening an issue. - options: - - label: Issues - https://github.com/angristan/openvpn-install/issues - required: true - - label: README and FAQ - https://github.com/angristan/openvpn-install - required: true - - label: Wiki - https://github.com/angristan/openvpn-install/wiki - required: true - - label: Discussions - https://github.com/angristan/openvpn-install/discussions - required: true - - - type: input - id: server - attributes: - label: Server OS - description: What OS is the OpenVPN server running on? - placeholder: Debian 10 - validations: - required: false - - - type: input - id: openvpn - attributes: - label: OpenVPN version - description: What OpenVPN version is running on your server? - placeholder: 2.5.0 - validations: - required: false - - - type: input - id: client - attributes: - label: Client - description: What OS and client are you using? Please specify the versions. - placeholder: Viscosity 1.10.1 on macOS 12.1 - validations: - required: false - - - type: textarea - id: issue - attributes: - label: What is the bug? - description: Also tell us, what did you expect to happen? - placeholder: Tell us what you see! - value: "A bug happened!" - validations: - required: true - - - type: textarea - id: logs - attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. - render: shell diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md deleted file mode 100644 index dcc367d..0000000 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: "" -labels: "" -assignees: "" ---- - -**Checklist** - -- [ ] I read the [README](https://github.com/angristan/openvpn-install/blob/master/README.md) -- [ ] I read the [FAQ](https://github.com/angristan/openvpn-install/blob/master/FAQ.md) -- [ ] I searched the [issues](https://github.com/angristan/openvpn-install/issues?q=is%3Aissue+) -- [ ] My issue is about the script, and not OpenVPN itself - - From 039ebaafe1ab862b9a362df3456b57d25cac961e Mon Sep 17 00:00:00 2001 From: xumia <59720581+xumia@users.noreply.github.com> Date: Tue, 3 Jan 2023 21:06:59 +0800 Subject: [PATCH 027/141] Fix the wrong client config when the certificate contains END string (#1067) --- openvpn-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 64d8ed0..4bdc587 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1125,7 +1125,7 @@ function newClient() { echo "" echo "" - awk '/BEGIN/,/END/' "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt" + awk '/BEGIN/,/END CERTIFICATE/' "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt" echo "" echo "" From 9b5361d32d3677ee8c4c6ad1bcf0991b92772010 Mon Sep 17 00:00:00 2001 From: Christian Ramelow Date: Tue, 3 Jan 2023 14:09:37 +0100 Subject: [PATCH 028/141] Adds the `--no-same-owner` flag to tar command (#1070) Close #1069 --- openvpn-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 4bdc587..090c7a0 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -705,7 +705,7 @@ function installOpenVPN() { local version="3.0.7" wget -O ~/easy-rsa.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-${version}.tgz mkdir -p /etc/openvpn/easy-rsa - tar xzf ~/easy-rsa.tgz --strip-components=1 --directory /etc/openvpn/easy-rsa + tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/easy-rsa rm -f ~/easy-rsa.tgz cd /etc/openvpn/easy-rsa/ || return From 4ee44c8e468a9a7c7e6ea7fb9f0bb839e74bb73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20St=C3=A1rek?= Date: Fri, 6 Jan 2023 21:33:54 +0100 Subject: [PATCH 029/141] Use DNS fallback to ifconfig.co (#1066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan Stárek --- openvpn-install.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 090c7a0..434b707 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -627,9 +627,13 @@ function installOpenVPN() { # Behind NAT, we'll default to the publicly reachable IPv4/IPv6. if [[ $IPV6_SUPPORT == "y" ]]; then - PUBLIC_IP=$(curl --retry 5 --retry-connrefused https://ifconfig.co) + if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused https://ifconfig.co) ; then + PUBLIC_IP=$(dig -6 TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') + fi else - PUBLIC_IP=$(curl --retry 5 --retry-connrefused -4 https://ifconfig.co) + if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused -4 https://ifconfig.co) ; then + PUBLIC_IP=$(dig -4 TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') + fi fi ENDPOINT=${ENDPOINT:-$PUBLIC_IP} fi From 2a57e89489974e9ace314283248a918aed8d2dd4 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Fri, 6 Jan 2023 21:39:02 +0100 Subject: [PATCH 030/141] Public IP detection: change provider Close #1076 #924 #1039 #925 --- openvpn-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 434b707..7c6f741 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -627,11 +627,11 @@ function installOpenVPN() { # Behind NAT, we'll default to the publicly reachable IPv4/IPv6. if [[ $IPV6_SUPPORT == "y" ]]; then - if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused https://ifconfig.co) ; then + if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused https://ip.seeip.org); then PUBLIC_IP=$(dig -6 TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') fi else - if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused -4 https://ifconfig.co) ; then + if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused -4 https://ip.seeip.org); then PUBLIC_IP=$(dig -4 TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') fi fi From 38d3bf9afa0733245a6b2eb7e149aaed54cddc67 Mon Sep 17 00:00:00 2001 From: s7r Date: Wed, 18 Jan 2023 22:05:09 +0000 Subject: [PATCH 031/141] Update FAQ with IPv6 prefix policies for ULA addresses (#1083) * Update FAQ with IPv6 prefix policies for ULA addresses Most operating systems will prefer IPv4 rather than IPv6 ULA, which defeats the purpose of redirect-gateway ipv6. It's nothing the script can do to automate this. --- FAQ.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/FAQ.md b/FAQ.md index 825a9ba..5acd78b 100644 --- a/FAQ.md +++ b/FAQ.md @@ -151,3 +151,25 @@ route 10.0.0.0 255.0.0.0 ``` So for example - here it would route all traffic of `10.0.0.0/8` to the vpn. And the rest through the internet. + +--- + +**Q:** I have enabled IPv6 and my VPN client gets an IPv6 address. Why do I reach the websites or other dual-stacked destionations via IPv4 only? + +**A:** This is because inside the tunnel you don't get a publicly routable IPv6 address, instead you get an ULA (Unlique Local Lan) address. Operating systems don't prefer this all the time. You can fix this in your operating system policies as it's unrelated to the VPN itself: + +Windows (commands needs to run cmd.exe as Administrator): + +``` +netsh interface ipv6 add prefixpolicy fd00::/8 3 1 +``` + +Linux: + +edit `/etc/gai.conf` and uncomment the following line and also change its value to `1`: + +``` +label fc00::/7 1 +``` + +This will not work properly unless you add you your VPN server `server.conf` one or two lines to push at least 1 (one) IPv6 DNS server. Most providers have IPv6 servers as well, add two more lines of `push "dhcp-option DNS "` From 48f8c16d3a95a980329cf41b33f8f9e1605f8839 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 21 Jan 2023 23:00:23 +0100 Subject: [PATCH 032/141] Add issue and PR templates --- .github/issue_template.md | 10 ++++++++++ .github/pull_request_template.md | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 .github/issue_template.md create mode 100644 .github/pull_request_template.md diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..4d38a8d --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,10 @@ + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..d26ab9b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,8 @@ + From 2d0eca1a1d16e796be1b8338bc52f768b5b0a305 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 21 Jan 2023 23:06:44 +0100 Subject: [PATCH 033/141] Update issue/PR template + contributing guidelines --- .github/issue_template.md | 13 ++++++------- .github/pull_request_template.md | 11 +++++------ README.md | 4 ++++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/issue_template.md b/.github/issue_template.md index 4d38a8d..adfdb1a 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,10 +1,9 @@ diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d26ab9b..43bed85 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,7 @@ diff --git a/README.md b/README.md index 4e81f28..9fa1c18 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,10 @@ Solutions that provision a ready to use OpenVPN server based on this script in o ## Contributing +## Discuss changes + +Please open an issue before submitting a PR if you want to discuss a change, especially if it's a big one. + ### Code formatting We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https://github.com/mvdan/sh) to enforce bash styling guidelines and good practices. They are executed for each commit / PR with GitHub Actions, so you can check the configuration [here](https://github.com/angristan/openvpn-install/blob/master/.github/workflows/push.yml). From 580c617d9844fbba82eb0f3fb92bfe5289781102 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 21 Jan 2023 23:18:52 +0100 Subject: [PATCH 034/141] Update issue template --- .github/issue_template.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/issue_template.md b/.github/issue_template.md index adfdb1a..2166d42 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -5,5 +5,6 @@ 💡 It helps keep the issue tracker clean and focused on bugs and feature requests. 🙏 Please include as much information as possible, and make sure you're running the latest version of the script. +✍️ Please state the Linux distribution you're using and its version, as well as the OpenVPN version. ✋ For feature requests, remember that this script is meant to be simple and easy to use. If you want to add a lot of options, it's better to fork the project. ---> From 64cbbce0b7fd6c324c0d08c32ee961b9e8246f07 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 21 Jan 2023 23:40:01 +0100 Subject: [PATCH 035/141] Add OpenVPN 2.5 notice to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9fa1c18..7dbba89 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,9 @@ We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https:// ## Security and Encryption +> **Warning** +> This has not been updated for OpenVPN 2.5 and later. + OpenVPN's default settings are pretty weak regarding encryption. This script aims to improve that. OpenVPN 2.4 was a great update regarding encryption. It added support for ECDSA, ECDH, AES GCM, NCP and tls-crypt. From 0ac1bf4f51716516d2817998fbbda005c616a09d Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 21 Jan 2023 23:41:03 +0100 Subject: [PATCH 036/141] Format markdown table in readme --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7dbba89..f38c0d3 100644 --- a/README.md +++ b/README.md @@ -118,17 +118,17 @@ The script supports these OS and architectures: | | i386 | amd64 | armhf | arm64 | | --------------- | ---- | ----- | ----- | ----- | -| Amazon Linux 2 | ❔ | ✅ | ❔ | ❔ | -| Arch Linux | ❔ | ✅ | ❔ | ✅ | -| CentOS 7 | ✅ | ✅ | ✅ | ✅ | -| CentOS 8 | ❌ | ✅ | ❌ | ✅ | -| Debian >= 9 | ✅ | ✅ | ✅ | ✅ | -| Fedora >= 27 | ❔ | ✅ | ❔ | ❔ | -| Ubuntu 16.04 | ✅ | ✅ | ❌ | ❌ | -| Ubuntu >= 18.04 | ✅ | ✅ | ✅ | ✅ | -| Oracle Linux 8 | ❌ | ✅ | ❌ | ❔ | -| Rocky Linux 8 | ❔ | ✅ | ❔ | ❔ | -| AlmaLinux 8 | ❌ | ✅ | ❌ | ❔ | +| Amazon Linux 2 | ❔ | ✅ | ❔ | ❔ | +| Arch Linux | ❔ | ✅ | ❔ | ✅ | +| CentOS 7 | ✅ | ✅ | ✅ | ✅ | +| CentOS 8 | ❌ | ✅ | ❌ | ✅ | +| Debian >= 9 | ✅ | ✅ | ✅ | ✅ | +| Fedora >= 27 | ❔ | ✅ | ❔ | ❔ | +| Ubuntu 16.04 | ✅ | ✅ | ❌ | ❌ | +| Ubuntu >= 18.04 | ✅ | ✅ | ✅ | ✅ | +| Oracle Linux 8 | ❌ | ✅ | ❌ | ❔ | +| Rocky Linux 8 | ❔ | ✅ | ❔ | ❔ | +| AlmaLinux 8 | ❌ | ✅ | ❌ | ❔ | To be noted: From ca8d58d5f0090a8c43a2e1a3b6577263843ef88a Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sun, 22 Jan 2023 00:06:05 +0100 Subject: [PATCH 037/141] Fix checkout in test workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af95572..b9b7747 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,7 +80,7 @@ jobs: host: ${{ steps.server_ip.outputs.value }} username: root key: ${{ secrets.SSH_KEY }} - script: set -x && git clone https://github.com/angristan/openvpn-install.git && cd openvpn-install && git checkout ${{ github.event.pull_request.head.sha }} + script: set -x && git clone https://github.com/angristan/openvpn-install.git && cd openvpn-install && git checkout ${{ github.sha }} - name: Run openvpn-install.sh in headless mode uses: appleboy/ssh-action@v0.1.6 From 2f76bb5e40f6f41a5bb2928379499b74da93f653 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sun, 22 Jan 2023 00:06:51 +0100 Subject: [PATCH 038/141] Update easy-rsa to 3.1.2 and fix compatibility with Ubuntu 22.04 Based on this patch by @zerodivisi0n: https://github.com/angristan/openvpn-install/issues/1000#issuecomment-1283484772 --- .github/workflows/test.yml | 1 + openvpn-install.sh | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9b7747..4270574 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,7 @@ jobs: - debian-11-x64 - ubuntu-18-04-x64 - ubuntu-20-04-x64 + - ubuntu-22-04-x64 - fedora-35-x64 - centos-7-x64 - centos-stream-8-x64 diff --git a/openvpn-install.sh b/openvpn-install.sh index 7c6f741..6bb39c5 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -706,7 +706,7 @@ function installOpenVPN() { # Install the latest version of easy-rsa from source, if not already installed. if [[ ! -d /etc/openvpn/easy-rsa/ ]]; then - local version="3.0.7" + local version="3.1.2" wget -O ~/easy-rsa.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-${version}.tgz mkdir -p /etc/openvpn/easy-rsa tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/easy-rsa @@ -729,18 +729,16 @@ function installOpenVPN() { SERVER_NAME="server_$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)" echo "$SERVER_NAME" >SERVER_NAME_GENERATED - echo "set_var EASYRSA_REQ_CN $SERVER_CN" >>vars - # Create the PKI, set up the CA, the DH params and the server certificate ./easyrsa init-pki - ./easyrsa --batch build-ca nopass + ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass if [[ $DH_TYPE == "2" ]]; then # ECDH keys are generated on-the-fly so we don't need to generate them beforehand openssl dhparam -out dh.pem $DH_KEY_SIZE fi - ./easyrsa build-server-full "$SERVER_NAME" nopass + ./easyrsa --batch build-server-full "$SERVER_NAME" nopass EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl case $TLS_SIG in @@ -1087,11 +1085,11 @@ function newClient() { cd /etc/openvpn/easy-rsa/ || return case $PASS in 1) - ./easyrsa build-client-full "$CLIENT" nopass + ./easyrsa --batch build-client-full "$CLIENT" nopass ;; 2) echo "⚠️ You will be asked for the client password below ⚠️" - ./easyrsa build-client-full "$CLIENT" + ./easyrsa --batch build-client-full "$CLIENT" ;; esac echo "Client $CLIENT added." From d2556ff235e9daa5fa10245050c4432f03a0258f Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sun, 22 Jan 2023 00:57:26 +0100 Subject: [PATCH 039/141] Add support for CentOS 9 + update supported distributions --- .github/workflows/test.yml | 4 ++++ README.md | 33 +++++++++++++++++---------------- openvpn-install.sh | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4270574..4473eed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,8 +17,12 @@ jobs: - ubuntu-20-04-x64 - ubuntu-22-04-x64 - fedora-35-x64 + # - fedora-36-x64 + # - fedora-37-x64 + # dnf is broken: https://ask.fedoraproject.org/t/dnf-operations-use-large-amount-of-ram-and-may-fail-in-low-memory-environments/26427 - centos-7-x64 - centos-stream-8-x64 + - centos-stream-9-x64 steps: - uses: actions/checkout@v3.2.0 diff --git a/README.md b/README.md index f38c0d3..393b702 100644 --- a/README.md +++ b/README.md @@ -114,27 +114,28 @@ export PASS="1" ## Compatibility -The script supports these OS and architectures: +The script supports these Linux distributions: -| | i386 | amd64 | armhf | arm64 | -| --------------- | ---- | ----- | ----- | ----- | -| Amazon Linux 2 | ❔ | ✅ | ❔ | ❔ | -| Arch Linux | ❔ | ✅ | ❔ | ✅ | -| CentOS 7 | ✅ | ✅ | ✅ | ✅ | -| CentOS 8 | ❌ | ✅ | ❌ | ✅ | -| Debian >= 9 | ✅ | ✅ | ✅ | ✅ | -| Fedora >= 27 | ❔ | ✅ | ❔ | ❔ | -| Ubuntu 16.04 | ✅ | ✅ | ❌ | ❌ | -| Ubuntu >= 18.04 | ✅ | ✅ | ✅ | ✅ | -| Oracle Linux 8 | ❌ | ✅ | ❌ | ❔ | -| Rocky Linux 8 | ❔ | ✅ | ❔ | ❔ | -| AlmaLinux 8 | ❌ | ✅ | ❌ | ❔ | +| | Support | +| ------------------ | ------- | +| AlmaLinux 8 | ✅ | +| Amazon Linux 2 | ✅ | +| Arch Linux | ✅ | +| CentOS 7 | ✅ 🤖 | +| CentOS Stream >= 8 | ✅ 🤖 | +| Debian >= 10 | ✅ 🤖 | +| Fedora >= 35 | ✅ 🤖 | +| Oracle Linux 8 | ✅ | +| Rocky Linux 8 | ✅ | +| Ubuntu >= 18.04 | ✅ 🤖 | To be noted: -- It should work on Debian 8+ and Ubuntu 16.04+. But versions not in the table above are not officially supported. +- The script is regularly tested against the distributions marked with a 🤖 only. + - It's only test on `amd64` architecture. +- It should work on older versions such as Debian 8+, Ubuntu 16.04+ and previous Fedora releases. But versions not in the table above are not officially supported. + - It should also support versions between the LTS versions, but these are not tested. - The script requires `systemd`. -- The script is regularly tested against `amd64` only. ## Fork diff --git a/openvpn-install.sh b/openvpn-install.sh index 6bb39c5..c47e08f 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -57,7 +57,7 @@ function checkOS() { fi if [[ $ID == "centos" || $ID == "rocky" || $ID == "almalinux" ]]; then OS="centos" - if [[ ! $VERSION_ID =~ (7|8) ]]; then + if [[ $VERSION_ID -lt 7 ]]; then echo "⚠️ Your version of CentOS is not supported." echo "" echo "The script only support CentOS 7 and CentOS 8." From 33fe6af1315cf1f5a43397bf65341e683deac463 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sun, 22 Jan 2023 01:55:06 +0100 Subject: [PATCH 040/141] Update cloud providers --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 393b702..b856e95 100644 --- a/README.md +++ b/README.md @@ -151,10 +151,9 @@ More Q&A in [FAQ.md](FAQ.md). **A:** I recommend these: -- [Vultr](https://www.vultr.com/?ref=8537055-6G): Worldwide locations, IPv6 support, starting at \$3.50/month -- [Hetzner](https://hetzner.cloud/?ref=ywtlvZsjgeDq): Germany, IPv6, 20 TB of traffic, starting at €3/month -- [Digital Ocean](https://goo.gl/qXrNLK): Worldwide locations, IPv6 support, starting at \$5/month -- [PulseHeberg](https://goo.gl/76yqW5): France, unlimited bandwidth, starting at €3/month +- [Vultr](https://www.vultr.com/?ref=8948982-8H): Worldwide locations, IPv6 support, starting at \$5/month +- [Hetzner](https://hetzner.cloud/?ref=ywtlvZsjgeDq): Germany, Finland and USA. IPv6, 20 TB of traffic, starting at 4.5€/month +- [Digital Ocean](https://m.do.co/c/ed0ba143fe53): Worldwide locations, IPv6 support, starting at \$4/month --- From d096f7a3a24063cade1c9b0c0a4cb4d0e1aea457 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sun, 22 Jan 2023 16:42:48 +0100 Subject: [PATCH 041/141] Add star history --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b856e95..d3e3037 100644 --- a/README.md +++ b/README.md @@ -353,3 +353,7 @@ You can [say thanks](https://saythanks.io/to/angristan) if you want! Many thanks to the [contributors](https://github.com/Angristan/OpenVPN-install/graphs/contributors) and Nyr's original work. This project is under the [MIT Licence](https://raw.githubusercontent.com/Angristan/openvpn-install/master/LICENSE) + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=angristan/openvpn-install&type=Date)](https://star-history.com/#angristan/openvpn-install&Date) From 80feebed16b3baa5979f764ee3272443f2fe08e6 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sun, 22 Jan 2023 16:43:13 +0100 Subject: [PATCH 042/141] Remove visitors badge --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d3e3037..fba65c8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ ![Test](https://github.com/angristan/openvpn-install/workflows/Test/badge.svg) ![Lint](https://github.com/angristan/openvpn-install/workflows/Lint/badge.svg) -![visitors](https://visitor-badge.glitch.me/badge?page_id=angristan.openvpn-install) [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/angristan) OpenVPN installer for Debian, Ubuntu, Fedora, CentOS, Arch Linux, Oracle Linux, Rocky Linux and AlmaLinux. From 1a249c621d63aa16f95b59673df8ae5c64d3c002 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Mon, 20 Nov 2023 21:14:04 +0100 Subject: [PATCH 043/141] ci: test workflow server images update (#1183) * test ci * remove ubuntu 18.04 * remove fedora 35 and add 37 38 * disable centos stream 9, add debian 12 --- .github/workflows/test.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4473eed..71ea00a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,7 @@ on: push: branches: - master + - ci name: Test jobs: @@ -13,16 +14,14 @@ jobs: os-image: - debian-10-x64 - debian-11-x64 - - ubuntu-18-04-x64 + - debian-12-x64 - ubuntu-20-04-x64 - ubuntu-22-04-x64 - - fedora-35-x64 - # - fedora-36-x64 - # - fedora-37-x64 - # dnf is broken: https://ask.fedoraproject.org/t/dnf-operations-use-large-amount-of-ram-and-may-fail-in-low-memory-environments/26427 + - fedora-37-x64 + - fedora-38-x64 - centos-7-x64 - centos-stream-8-x64 - - centos-stream-9-x64 + # - centos-stream-9-x64 # yum oomkill steps: - uses: actions/checkout@v3.2.0 From 651e36c6cb0359ab7f4e699e2c71d3917acba69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Salbe=C3=AF?= Date: Mon, 20 Nov 2023 21:19:13 +0100 Subject: [PATCH 044/141] Fix syntax error on Rocky Linux version check (#1182) Co-authored-by: David Salbei --- openvpn-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index c47e08f..06ad608 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -57,7 +57,7 @@ function checkOS() { fi if [[ $ID == "centos" || $ID == "rocky" || $ID == "almalinux" ]]; then OS="centos" - if [[ $VERSION_ID -lt 7 ]]; then + if [[ ${VERSION_ID%.*} -lt 7 ]]; then echo "⚠️ Your version of CentOS is not supported." echo "" echo "The script only support CentOS 7 and CentOS 8." From 5a4b31bd0d711da5df5febc944167b3cdb0a28bf Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Mon, 20 Nov 2023 21:21:51 +0100 Subject: [PATCH 045/141] Fix typo in README --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fba65c8..dd83d6a 100644 --- a/README.md +++ b/README.md @@ -117,21 +117,21 @@ The script supports these Linux distributions: | | Support | | ------------------ | ------- | -| AlmaLinux 8 | ✅ | -| Amazon Linux 2 | ✅ | -| Arch Linux | ✅ | -| CentOS 7 | ✅ 🤖 | -| CentOS Stream >= 8 | ✅ 🤖 | -| Debian >= 10 | ✅ 🤖 | -| Fedora >= 35 | ✅ 🤖 | -| Oracle Linux 8 | ✅ | -| Rocky Linux 8 | ✅ | -| Ubuntu >= 18.04 | ✅ 🤖 | +| AlmaLinux 8 | ✅ | +| Amazon Linux 2 | ✅ | +| Arch Linux | ✅ | +| CentOS 7 | ✅ 🤖 | +| CentOS Stream >= 8 | ✅ 🤖 | +| Debian >= 10 | ✅ 🤖 | +| Fedora >= 35 | ✅ 🤖 | +| Oracle Linux 8 | ✅ | +| Rocky Linux 8 | ✅ | +| Ubuntu >= 18.04 | ✅ 🤖 | To be noted: - The script is regularly tested against the distributions marked with a 🤖 only. - - It's only test on `amd64` architecture. + - It's only tested on `amd64` architecture. - It should work on older versions such as Debian 8+, Ubuntu 16.04+ and previous Fedora releases. But versions not in the table above are not officially supported. - It should also support versions between the LTS versions, but these are not tested. - The script requires `systemd`. From 6a127fa2b633508528ed21ed754f8c1dcb973d71 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 16 May 2024 20:02:01 +0200 Subject: [PATCH 046/141] Enable manual trigger of actions --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7301cdf..3b75f34 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] name: Lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 71ea00a..07f0a12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,7 @@ on: branches: - master - ci + workflow_dispatch: name: Test jobs: From 305e9868cfc608ff547aed2a022d927df0c12d8e Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 16 May 2024 20:08:12 +0200 Subject: [PATCH 047/141] CI: update linux distributions used in end-to-end tests --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 07f0a12..d014be9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,8 +18,8 @@ jobs: - debian-12-x64 - ubuntu-20-04-x64 - ubuntu-22-04-x64 - - fedora-37-x64 - - fedora-38-x64 + - ubuntu-24-04-x64 + - fedora-39-x64 - centos-7-x64 - centos-stream-8-x64 # - centos-stream-9-x64 # yum oomkill From a2725d61a35eb97405af0df0ecfcb45dca372326 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 16 May 2024 20:13:47 +0200 Subject: [PATCH 048/141] CI: update actions/checkout to v4 --- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3b75f34..d1c6650 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v4 - name: Lint Code Base uses: github/super-linter@v4.1.0 env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d014be9..20cd584 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: - centos-stream-8-x64 # - centos-stream-9-x64 # yum oomkill steps: - - uses: actions/checkout@v3.2.0 + - uses: actions/checkout@v4 - name: Setup doctl uses: digitalocean/action-doctl@v2 From 0cc002e17dbb54bb3703469b15db085f48e9e8bc Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 16 May 2024 20:33:32 +0200 Subject: [PATCH 049/141] CI: wait for dpkg lock in debian/ubuntu setup step --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 20cd584..c73a553 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: host: ${{ steps.server_ip.outputs.value }} username: root key: ${{ secrets.SSH_KEY }} - script: set -x && apt-get update && apt-get install -y git + script: set -x && apt-get -o DPkg::Lock::Timeout=120 update && apt-get install -y git - name: Setup remote server (Fedora) if: steps.server_os.outputs.value == 'fedora' From 67701fac77d4d716cf2f4be8ac2c59db0e91f952 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 16 May 2024 20:37:23 +0200 Subject: [PATCH 050/141] CI: wait for dpkg lock in debian/ubuntu setup step --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c73a553..ef3f666 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: host: ${{ steps.server_ip.outputs.value }} username: root key: ${{ secrets.SSH_KEY }} - script: set -x && apt-get -o DPkg::Lock::Timeout=120 update && apt-get install -y git + script: set -x && apt-get update && apt-get -o DPkg::Lock::Timeout=120 install -y git - name: Setup remote server (Fedora) if: steps.server_os.outputs.value == 'fedora' From a189535563f1b48605115643585e036cd5631ac1 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Jul 2024 18:16:19 +0200 Subject: [PATCH 051/141] Set client and server certificates validity to 10 years (#1235) Prevent #974 --- openvpn-install.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 06ad608..6c6a647 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -731,14 +731,14 @@ function installOpenVPN() { # Create the PKI, set up the CA, the DH params and the server certificate ./easyrsa init-pki - ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass + EASYRSA_CA_EXPIRE=3650 ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass if [[ $DH_TYPE == "2" ]]; then # ECDH keys are generated on-the-fly so we don't need to generate them beforehand openssl dhparam -out dh.pem $DH_KEY_SIZE fi - ./easyrsa --batch build-server-full "$SERVER_NAME" nopass + EASYRSA_CERT_EXPIRE=3650 ./easyrsa --batch build-server-full "$SERVER_NAME" nopass EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl case $TLS_SIG in @@ -1085,11 +1085,11 @@ function newClient() { cd /etc/openvpn/easy-rsa/ || return case $PASS in 1) - ./easyrsa --batch build-client-full "$CLIENT" nopass + EASYRSA_CERT_EXPIRE=3650 ./easyrsa --batch build-client-full "$CLIENT" nopass ;; 2) echo "⚠️ You will be asked for the client password below ⚠️" - ./easyrsa --batch build-client-full "$CLIENT" + EASYRSA_CERT_EXPIRE=3650 ./easyrsa --batch build-client-full "$CLIENT" ;; esac echo "Client $CLIENT added." From 2ce1ee765eaef88f955ee3ba27ea42f6e085d8a7 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Fri, 12 Jul 2024 18:19:53 +0200 Subject: [PATCH 052/141] Remove centos-stream-8-x64 from test workflow Not available on DO anymore --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef3f666..ab3ecf5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,6 @@ jobs: - ubuntu-24-04-x64 - fedora-39-x64 - centos-7-x64 - - centos-stream-8-x64 # - centos-stream-9-x64 # yum oomkill steps: - uses: actions/checkout@v4 From 56660eefeb31293ef4c1a6c5c1e2984d874122d1 Mon Sep 17 00:00:00 2001 From: xiahare <45806405+xiahare@users.noreply.github.com> Date: Thu, 7 Nov 2024 11:39:28 -0800 Subject: [PATCH 053/141] Fix public IP detection: ip.seeip.org has been changed to api.seeip.org (#1252) --- openvpn-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 6c6a647..d9397b1 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -627,11 +627,11 @@ function installOpenVPN() { # Behind NAT, we'll default to the publicly reachable IPv4/IPv6. if [[ $IPV6_SUPPORT == "y" ]]; then - if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused https://ip.seeip.org); then + if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused https://api.seeip.org); then PUBLIC_IP=$(dig -6 TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') fi else - if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused -4 https://ip.seeip.org); then + if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused -4 https://api.seeip.org); then PUBLIC_IP=$(dig -4 TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') fi fi From 0d58ddcb8c5c3c096416a4b059972451f88ed369 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 7 Nov 2024 20:46:47 +0100 Subject: [PATCH 054/141] Update distribution matrix for end-to-end tests --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab3ecf5..198ff23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,13 +13,12 @@ jobs: strategy: matrix: os-image: - - debian-10-x64 - debian-11-x64 - debian-12-x64 - - ubuntu-20-04-x64 - ubuntu-22-04-x64 - ubuntu-24-04-x64 - fedora-39-x64 + - fedora-40-x64 - centos-7-x64 # - centos-stream-9-x64 # yum oomkill steps: From dc114f324374b15c337674bedc986e4f6248f08e Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 7 Nov 2024 20:49:42 +0100 Subject: [PATCH 055/141] Update distribution matrix for end-to-end tests --- .github/workflows/test.yml | 1 - README.md | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 198ff23..5579a11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,6 @@ jobs: - ubuntu-24-04-x64 - fedora-39-x64 - fedora-40-x64 - - centos-7-x64 # - centos-stream-9-x64 # yum oomkill steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index dd83d6a..0f3a539 100644 --- a/README.md +++ b/README.md @@ -117,16 +117,16 @@ The script supports these Linux distributions: | | Support | | ------------------ | ------- | -| AlmaLinux 8 | ✅ | -| Amazon Linux 2 | ✅ | -| Arch Linux | ✅ | -| CentOS 7 | ✅ 🤖 | -| CentOS Stream >= 8 | ✅ 🤖 | -| Debian >= 10 | ✅ 🤖 | -| Fedora >= 35 | ✅ 🤖 | -| Oracle Linux 8 | ✅ | -| Rocky Linux 8 | ✅ | -| Ubuntu >= 18.04 | ✅ 🤖 | +| AlmaLinux 8 | ✅ | +| Amazon Linux 2 | ✅ | +| Arch Linux | ✅ | +| CentOS 7 | ✅ | +| CentOS Stream >= 8 | ✅ 🤖 | +| Debian >= 10 | ✅ 🤖 | +| Fedora >= 35 | ✅ 🤖 | +| Oracle Linux 8 | ✅ | +| Rocky Linux 8 | ✅ | +| Ubuntu >= 18.04 | ✅ 🤖 | To be noted: From e1f19e0f246c4e429dbf32bdf0a0a507d8ac194b Mon Sep 17 00:00:00 2001 From: Raphael Pinto Date: Thu, 7 Nov 2024 19:55:14 +0000 Subject: [PATCH 056/141] Fix Public IP detection - Fix issue when seeip.org is unreachable #1241 (#1243) The script does work when seeip.org is unreachable, so I changed the policy to define the public IP. It solves the issue #1241 * Timeout limit on each try to solve the IP to avoid long waits; * Extra public IP providers as failovers; * the script only will try to solve an IP if the ENDPOINT is empty; Co-authored-by: Stanislas --- openvpn-install.sh | 58 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index d9397b1..a0e04a4 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -216,6 +216,45 @@ access-control: fd42:42:42:42::/112 allow' >>/etc/unbound/openvpn.conf systemctl restart unbound } +function resolvePublicIP() { + # IP version flags, we'll use as default the IPv4 + CURL_IP_VERSION_FLAG="-4" + DIG_IP_VERSION_FLAG="-4" + + # Behind NAT, we'll default to the publicly reachable IPv4/IPv6. + if [[ $IPV6_SUPPORT == "y" ]]; then + CURL_IP_VERSION_FLAG="" + DIG_IP_VERSION_FLAG="-6" + fi + + # If there is no public ip yet, we'll try to solve it using: https://api.seeip.org + if [[ -z $PUBLIC_IP ]]; then + PUBLIC_IP=$(curl -f -m 5 -sS --retry 2 --retry-connrefused "$CURL_IP_VERSION_FLAG" https://api.seeip.org 2>/dev/null) + fi + + # If there is no public ip yet, we'll try to solve it using: https://ifconfig.me + if [[ -z $PUBLIC_IP ]]; then + PUBLIC_IP=$(curl -f -m 5 -sS --retry 2 --retry-connrefused "$CURL_IP_VERSION_FLAG" https://ifconfig.me 2>/dev/null) + fi + + # If there is no public ip yet, we'll try to solve it using: https://api.ipify.org + if [[ -z $PUBLIC_IP ]]; then + PUBLIC_IP=$(curl -f -m 5 -sS --retry 2 --retry-connrefused "$CURL_IP_VERSION_FLAG" https://api.ipify.org 2>/dev/null) + fi + + # If there is no public ip yet, we'll try to solve it using: ns1.google.com + if [[ -z $PUBLIC_IP ]]; then + PUBLIC_IP=$(dig $DIG_IP_VERSION_FLAG TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') + fi + + if [[ -z $PUBLIC_IP ]]; then + echo >&2 echo "Couldn't solve the public IP" + exit 1 + fi + + echo "$PUBLIC_IP" +} + function installQuestions() { echo "Welcome to the OpenVPN installer!" echo "The git repository is available at: https://github.com/angristan/openvpn-install" @@ -244,9 +283,12 @@ function installQuestions() { echo "It seems this server is behind NAT. What is its public IPv4 address or hostname?" echo "We need it for the clients to connect to the server." - PUBLICIP=$(curl -s https://api.ipify.org) + if [[ -z $ENDPOINT ]]; then + DEFAULT_ENDPOINT=$(resolvePublicIP) + fi + until [[ $ENDPOINT != "" ]]; do - read -rp "Public IPv4 address or hostname: " -e -i "$PUBLICIP" ENDPOINT + read -rp "Public IPv4 address or hostname: " -e -i "$DEFAULT_ENDPOINT" ENDPOINT done fi @@ -625,17 +667,9 @@ function installOpenVPN() { PASS=${PASS:-1} CONTINUE=${CONTINUE:-y} - # Behind NAT, we'll default to the publicly reachable IPv4/IPv6. - if [[ $IPV6_SUPPORT == "y" ]]; then - if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused https://api.seeip.org); then - PUBLIC_IP=$(dig -6 TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') - fi - else - if ! PUBLIC_IP=$(curl -f --retry 5 --retry-connrefused -4 https://api.seeip.org); then - PUBLIC_IP=$(dig -4 TXT +short o-o.myaddr.l.google.com @ns1.google.com | tr -d '"') - fi + if [[ -z $ENDPOINT ]]; then + ENDPOINT=$(resolvePublicIP) fi - ENDPOINT=${ENDPOINT:-$PUBLIC_IP} fi # Run setup questions first, and set other variables if auto-install From e2d4990ae194e37fd5162168a8aac5e2d89e0e8d Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Mon, 6 Jan 2025 17:25:21 +0100 Subject: [PATCH 057/141] Improve README --- README.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0f3a539..e670650 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,25 @@ This script will let you setup your own secure VPN server in just a few seconds. You can also check out [wireguard-install](https://github.com/angristan/wireguard-install), a simple installer for a simpler, safer, faster and more modern VPN protocol. +## What is this? + +This script is meant to be run on your own server, whether it's a VPS or a dedicated server, or even a computer at home. + +Once set up, you will be able to generate client configuration files for every device you want to connect. + +Each client will be able to route its internet traffic through the server, fully encrypted. + +```mermaid +graph LR + A[Phone] --> VPN + B[Laptop] --> VPN + C[Computer] --> VPN + + VPN[OpenVPN Server] + + VPN -->|Encrypted Traffic| I[Internet] +``` + ## Usage First, get the script and make it executable: @@ -37,9 +56,7 @@ When OpenVPN is installed, you can run the script again, and you will get the ch In your home directory, you will have `.ovpn` files. These are the client configuration files. Download them from your server and connect using your favorite OpenVPN client. -If you have any question, head to the [FAQ](#faq) first. Please read everything before opening an issue. - -**PLEASE do not send me emails or private messages asking for help.** The only place to get help is the issues. Other people may be able to help and in the future, other users may also run into the same issue as you. My time is not available for free just for you, you're not special. +If you have any question, head to the [FAQ](#faq) first. And if you need help, you can open a [discussion](https://github.com/angristan/openvpn-install/discussions). Please search existing issues and dicussions first. ### Headless install From 399c3c87b97af75bf8d76006c785320b894e8ca4 Mon Sep 17 00:00:00 2001 From: Blake Fleischer Date: Mon, 10 Mar 2025 05:24:45 -0400 Subject: [PATCH 058/141] Add support for Amazon Linux 2023 out of the box (#1259) Co-authored-by: Stanislas Lange --- README.md | 25 +++++++++++++------------ openvpn-install.sh | 13 +++++++++---- 2 files changed, 22 insertions(+), 16 deletions(-) mode change 100755 => 100644 openvpn-install.sh diff --git a/README.md b/README.md index e670650..4d787ad 100644 --- a/README.md +++ b/README.md @@ -132,18 +132,19 @@ export PASS="1" The script supports these Linux distributions: -| | Support | -| ------------------ | ------- | -| AlmaLinux 8 | ✅ | -| Amazon Linux 2 | ✅ | -| Arch Linux | ✅ | -| CentOS 7 | ✅ | -| CentOS Stream >= 8 | ✅ 🤖 | -| Debian >= 10 | ✅ 🤖 | -| Fedora >= 35 | ✅ 🤖 | -| Oracle Linux 8 | ✅ | -| Rocky Linux 8 | ✅ | -| Ubuntu >= 18.04 | ✅ 🤖 | +| | Support | +| ---------------------- | ------- | +| AlmaLinux 8 | ✅ | +| Amazon Linux 2 | ✅ | +| Amazon Linux >= 2023.6 | ✅ | +| Arch Linux | ✅ | +| CentOS 7 | ✅ | +| CentOS Stream >= 8 | ✅ 🤖 | +| Debian >= 10 | ✅ 🤖 | +| Fedora >= 35 | ✅ 🤖 | +| Oracle Linux 8 | ✅ | +| Rocky Linux 8 | ✅ | +| Ubuntu >= 18.04 | ✅ 🤖 | To be noted: diff --git a/openvpn-install.sh b/openvpn-install.sh old mode 100755 new mode 100644 index a0e04a4..bf3171c --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -75,11 +75,14 @@ function checkOS() { fi fi if [[ $ID == "amzn" ]]; then - OS="amzn" - if [[ $VERSION_ID != "2" ]]; then + if [[ $VERSION_ID == "2" ]]; then + OS="amzn" + elif [[ "$(echo "$PRETTY_NAME" | cut -c 1-18)" == "Amazon Linux 2023." ]] && [[ "$(echo "$PRETTY_NAME" | cut -c 19)" -ge 6 ]]; then + OS="amzn2023" + else echo "⚠️ Your version of Amazon Linux is not supported." echo "" - echo "The script only support Amazon Linux 2." + echo "The script only support Amazon Linux 2 or Amazon Linux 2023.6+" echo "" exit 1 fi @@ -719,6 +722,8 @@ function installOpenVPN() { elif [[ $OS == 'amzn' ]]; then amazon-linux-extras install -y epel yum install -y openvpn iptables openssl wget ca-certificates curl + elif [[ $OS == 'amzn2023' ]]; then + dnf install -y openvpn iptables openssl wget ca-certificates elif [[ $OS == 'fedora' ]]; then dnf install -y openvpn iptables openssl wget ca-certificates curl policycoreutils-python-utils elif [[ $OS == 'arch' ]]; then @@ -958,7 +963,7 @@ verb 3" >>/etc/openvpn/server.conf fi # Finally, restart and enable OpenVPN - if [[ $OS == 'arch' || $OS == 'fedora' || $OS == 'centos' || $OS == 'oracle' ]]; then + if [[ $OS == 'arch' || $OS == 'fedora' || $OS == 'centos' || $OS == 'oracle' || $OS == 'amzn2023' ]]; then # Don't modify package-provided service cp /usr/lib/systemd/system/openvpn-server@.service /etc/systemd/system/openvpn-server@.service From 19e4b7961f7ad995dd4a2baa62853e125ac76ae8 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Mon, 10 Mar 2025 10:27:12 +0100 Subject: [PATCH 059/141] CI: add Fedora 41 and remove 39 for e2e workflow --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5579a11..00e3707 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,8 +17,8 @@ jobs: - debian-12-x64 - ubuntu-22-04-x64 - ubuntu-24-04-x64 - - fedora-39-x64 - fedora-40-x64 + - fedora-41-x64 # - centos-stream-9-x64 # yum oomkill steps: - uses: actions/checkout@v4 From 7e32f6ae8373fe66e657cb693518c45768d0ea6e Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 15 Mar 2025 22:52:11 +0100 Subject: [PATCH 060/141] Fix mermaid diagram in README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4d787ad..da654b0 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,13 @@ Each client will be able to route its internet traffic through the server, fully ```mermaid graph LR - A[Phone] --> VPN - B[Laptop] --> VPN - C[Computer] --> VPN + A[Phone] -->|Encrypted| VPN + B[Laptop] -->|Encrypted| VPN + C[Computer] -->|Encrypted| VPN VPN[OpenVPN Server] - VPN -->|Encrypted Traffic| I[Internet] + VPN --> I[Internet] ``` ## Usage From a680d1f7e30ce2e476589f405c841f713b8d3b24 Mon Sep 17 00:00:00 2001 From: mags0ft <113589312+mags0ft@users.noreply.github.com> Date: Thu, 1 May 2025 17:24:16 +0200 Subject: [PATCH 061/141] Correct numerous smaller spelling mistakes --- openvpn-install.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index bf3171c..ef3cd37 100644 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -25,7 +25,7 @@ function checkOS() { if [[ $VERSION_ID -lt 9 ]]; then echo "⚠️ Your version of Debian is not supported." echo "" - echo "However, if you're using Debian >= 9 or unstable/testing then you can continue, at your own risk." + echo "However, if you're using Debian >= 9 or unstable/testing, you can continue at your own risk." echo "" until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Continue? [y/n]: " -e CONTINUE @@ -40,7 +40,7 @@ function checkOS() { if [[ $MAJOR_UBUNTU_VERSION -lt 16 ]]; then echo "⚠️ Your version of Ubuntu is not supported." echo "" - echo "However, if you're using Ubuntu >= 16.04 or beta, then you can continue, at your own risk." + echo "However, if you're using Ubuntu >= 16.04 or beta, you can continue at your own risk." echo "" until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Continue? [y/n]: " -e CONTINUE @@ -60,7 +60,7 @@ function checkOS() { if [[ ${VERSION_ID%.*} -lt 7 ]]; then echo "⚠️ Your version of CentOS is not supported." echo "" - echo "The script only support CentOS 7 and CentOS 8." + echo "The script only supports CentOS 7 and CentOS 8." echo "" exit 1 fi @@ -70,7 +70,7 @@ function checkOS() { if [[ ! $VERSION_ID =~ (8) ]]; then echo "Your version of Oracle Linux is not supported." echo "" - echo "The script only support Oracle Linux 8." + echo "The script only supports Oracle Linux 8." exit 1 fi fi @@ -82,7 +82,7 @@ function checkOS() { else echo "⚠️ Your version of Amazon Linux is not supported." echo "" - echo "The script only support Amazon Linux 2 or Amazon Linux 2023.6+" + echo "The script only supports Amazon Linux 2 or Amazon Linux 2023.6+" echo "" exit 1 fi @@ -90,18 +90,18 @@ function checkOS() { elif [[ -e /etc/arch-release ]]; then OS=arch else - echo "Looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2, Oracle Linux 8 or Arch Linux system" + echo "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2, Oracle Linux 8 or Arch Linux system." exit 1 fi } function initialCheck() { if ! isRoot; then - echo "Sorry, you need to run this as root" + echo "Sorry, you need to run this script as root." exit 1 fi if ! tunAvailable; then - echo "TUN is not available" + echo "TUN is not available." exit 1 fi checkOS @@ -264,7 +264,7 @@ function installQuestions() { echo "" echo "I need to ask you a few questions before starting the setup." - echo "You can leave the default options and just press enter if you are ok with them." + echo "You can leave the default options and just press enter if you are okay with them." echo "" echo "I need to know the IPv4 address of the network interface you want OpenVPN listening to." echo "Unless your server is behind NAT, it should be your public IPv4 address." @@ -428,7 +428,7 @@ function installQuestions() { echo "" echo "Do you want to customize encryption settings?" echo "Unless you know what you're doing, you should stick with the default parameters provided by the script." - echo "Note that whatever you choose, all the choices presented in the script are safe. (Unlike OpenVPN's defaults)" + echo "Note that whatever you choose, all the choices presented in the script are safe (unlike OpenVPN's defaults)." echo "See https://github.com/angristan/openvpn-install#security-and-encryption to learn more." echo "" until [[ $CUSTOMIZE_ENC =~ (y|n) ]]; do @@ -687,7 +687,7 @@ function installOpenVPN() { # $NIC can not be empty for script rm-openvpn-rules.sh if [[ -z $NIC ]]; then echo - echo "Can not detect public interface." + echo "Could not detect public interface." echo "This needs for setup MASQUERADE." until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Continue? [y/n]: " -e CONTINUE From 74dcf678448f08a3b59965762129796aea3bbfc3 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:06:53 +0100 Subject: [PATCH 062/141] fix: remove duplicate echo in resolvePublicIP error message --- openvpn-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index ef3cd37..b5ca9fe 100644 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -251,7 +251,7 @@ function resolvePublicIP() { fi if [[ -z $PUBLIC_IP ]]; then - echo >&2 echo "Couldn't solve the public IP" + echo >&2 "Couldn't solve the public IP" exit 1 fi From 1c5381cc03e65a2363a082ad21f466dbcee0f28d Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:07:09 +0100 Subject: [PATCH 063/141] fix: correct DNS prompt range from [1-12] to [1-13] The prompt incorrectly showed [1-12] when option 13 (Custom DNS) is valid. --- openvpn-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index b5ca9fe..7bb0bd0 100644 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -371,7 +371,7 @@ function installQuestions() { echo " 12) NextDNS (Anycast: worldwide)" echo " 13) Custom" until [[ $DNS =~ ^[0-9]+$ ]] && [ "$DNS" -ge 1 ] && [ "$DNS" -le 13 ]; do - read -rp "DNS [1-12]: " -e -i 11 DNS + read -rp "DNS [1-13]: " -e -i 11 DNS if [[ $DNS == 2 ]] && [[ -e /etc/unbound/unbound.conf ]]; then echo "" echo "Unbound is already installed." From 94f096787803bffbebb7a41e1e2419e0c6d1b342 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:07:44 +0100 Subject: [PATCH 064/141] security: add SHA256 checksum verification for easy-rsa download Adds integrity verification to prevent supply chain attacks when downloading easy-rsa from GitHub releases. --- openvpn-install.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openvpn-install.sh b/openvpn-install.sh index 7bb0bd0..5c1f8b0 100644 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -746,7 +746,14 @@ function installOpenVPN() { # Install the latest version of easy-rsa from source, if not already installed. if [[ ! -d /etc/openvpn/easy-rsa/ ]]; then local version="3.1.2" + local easy_rsa_sha256="d63cf129490ffd6d8792ede7344806c506c82c32428b5bb609ad97ca6a6e4499" wget -O ~/easy-rsa.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-${version}.tgz + echo "${easy_rsa_sha256} ~/easy-rsa.tgz" | sha256sum -c + if [[ $? -ne 0 ]]; then + echo "SHA256 checksum verification failed for easy-rsa download!" + rm -f ~/easy-rsa.tgz + exit 1 + fi mkdir -p /etc/openvpn/easy-rsa tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/easy-rsa rm -f ~/easy-rsa.tgz From 960be1a65834aec8fb1e613e99f75cb844a64997 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:08:09 +0100 Subject: [PATCH 065/141] security: add validation for root.hints download Verify that the downloaded root.hints file is not empty and contains expected DNS root server content before using it. --- openvpn-install.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openvpn-install.sh b/openvpn-install.sh index 5c1f8b0..5feccfa 100644 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -147,6 +147,12 @@ prefetch: yes' >>/etc/unbound/unbound.conf # Get root servers list curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache + # Verify download was successful and file contains expected content + if [[ ! -s /etc/unbound/root.hints ]] || ! grep -q "ROOT-SERVERS" /etc/unbound/root.hints; then + echo "Failed to download root.hints or file is invalid!" + rm -f /etc/unbound/root.hints + exit 1 + fi if [[ ! -f /etc/unbound/unbound.conf.old ]]; then mv /etc/unbound/unbound.conf /etc/unbound/unbound.conf.old From d61b16f3b8822b5873009a50853c6174508a4564 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:08:35 +0100 Subject: [PATCH 066/141] ci: replace deprecated set-output with GITHUB_OUTPUT The set-output workflow command was deprecated in favor of environment files. See: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 00e3707..26e71e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: run: doctl compute droplet create openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }} --size s-1vcpu-1gb --image ${{ matrix.os-image }} --region lon1 --enable-ipv6 --ssh-keys be:66:76:61:a8:71:93:aa:e3:19:ba:d8:0d:d2:2d:d4 --wait - name: Get server ID - run: echo ::set-output name=value::$(doctl compute droplet list -o json | jq -r '.[] | select(.name == "'openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }}'").id') + run: echo "value=$(doctl compute droplet list -o json | jq -r '.[] | select(.name == "'openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }}'").id')" >> $GITHUB_OUTPUT id: server_id - name: Move server to dedicated project @@ -42,11 +42,11 @@ jobs: run: sleep 90 - name: Get server IP - run: echo ::set-output name=value::$(doctl compute droplet list -o json | jq -r '.[] | select(.name == "'openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }}'").networks.v4 | .[] | select(.type == "'public'").ip_address') + run: echo "value=$(doctl compute droplet list -o json | jq -r '.[] | select(.name == "'openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }}'").networks.v4 | .[] | select(.type == "'public'").ip_address')" >> $GITHUB_OUTPUT id: server_ip - name: Get server OS - run: echo ::set-output name=value::$(echo ${{ matrix.os-image }} | cut -d '-' -f1) + run: echo "value=$(echo ${{ matrix.os-image }} | cut -d '-' -f1)" >> $GITHUB_OUTPUT id: server_os - name: Setup remote server (Debian/Ubuntu) From 00f3cd16055acd2956a37c013c611a4cccda22c5 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:08:57 +0100 Subject: [PATCH 067/141] ci: update Super Linter from v4.1.0 to v7 The super-linter project has been moved to the super-linter org and significantly updated. v7 includes many improvements and bug fixes. --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d1c6650..4120775 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,6 +9,6 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - name: Lint Code Base - uses: github/super-linter@v4.1.0 + uses: super-linter/super-linter@v7 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 7c2c491fab93636fb1001a9f5259e86d76cd9ca0 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:09:17 +0100 Subject: [PATCH 068/141] ci: update appleboy/ssh-action from v0.1.6 to v1.2.0 Updates to a more recent stable version with bug fixes and improvements. --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26e71e0..385ed00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,7 +51,7 @@ jobs: - name: Setup remote server (Debian/Ubuntu) if: steps.server_os.outputs.value == 'debian' || steps.server_os.outputs.value == 'ubuntu' - uses: appleboy/ssh-action@v0.1.6 + uses: appleboy/ssh-action@v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -60,7 +60,7 @@ jobs: - name: Setup remote server (Fedora) if: steps.server_os.outputs.value == 'fedora' - uses: appleboy/ssh-action@v0.1.6 + uses: appleboy/ssh-action@v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -69,7 +69,7 @@ jobs: - name: Setup remote server (CentOS) if: steps.server_os.outputs.value == 'centos' - uses: appleboy/ssh-action@v0.1.6 + uses: appleboy/ssh-action@v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -77,7 +77,7 @@ jobs: script: set -x && yum install -y git - name: Download repo and checkout current commit - uses: appleboy/ssh-action@v0.1.6 + uses: appleboy/ssh-action@v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -85,7 +85,7 @@ jobs: script: set -x && git clone https://github.com/angristan/openvpn-install.git && cd openvpn-install && git checkout ${{ github.sha }} - name: Run openvpn-install.sh in headless mode - uses: appleboy/ssh-action@v0.1.6 + uses: appleboy/ssh-action@v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root From bf31e0ca644238ba5a53151956a245a43e64cd88 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:09:33 +0100 Subject: [PATCH 069/141] docs: fix broken workflow link (push.yml -> lint.yml) The workflow file was renamed but the README link was not updated. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da654b0..72fd4ce 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ Please open an issue before submitting a PR if you want to discuss a change, esp ### Code formatting -We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https://github.com/mvdan/sh) to enforce bash styling guidelines and good practices. They are executed for each commit / PR with GitHub Actions, so you can check the configuration [here](https://github.com/angristan/openvpn-install/blob/master/.github/workflows/push.yml). +We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https://github.com/mvdan/sh) to enforce bash styling guidelines and good practices. They are executed for each commit / PR with GitHub Actions, so you can check the configuration [here](https://github.com/angristan/openvpn-install/blob/master/.github/workflows/lint.yml). ## Security and Encryption From 46a295b5381e868fadf942e3428a2e0ba25f9d48 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:09:56 +0100 Subject: [PATCH 070/141] docs: update security section note for OpenVPN 2.5+ Replace the warning about outdated documentation with a note clarifying that TLS 1.2 is kept as minimum for client compatibility while acknowledging OpenVPN 2.5+ features. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 72fd4ce..75d449c 100644 --- a/README.md +++ b/README.md @@ -219,8 +219,8 @@ We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https:// ## Security and Encryption -> **Warning** -> This has not been updated for OpenVPN 2.5 and later. +> **Note** +> This section covers security features for OpenVPN 2.4+. OpenVPN 2.5+ introduced additional features like TLS 1.3 support, but this script maintains TLS 1.2 as the minimum for broader client compatibility. OpenVPN's default settings are pretty weak regarding encryption. This script aims to improve that. From bfcd62459257bcad557a05e1c3c44a9262d44a87 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:10:13 +0100 Subject: [PATCH 071/141] docs: fix sysctl config path in FAQ (20 -> 99) The script uses /etc/sysctl.d/99-openvpn.conf but the FAQ incorrectly referenced /etc/sysctl.d/20-openvpn.conf --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index 5acd78b..c7ca6c6 100644 --- a/FAQ.md +++ b/FAQ.md @@ -83,7 +83,7 @@ If your client is <2.3.3, remove `tls-version-min 1.2` from your `/etc/openvpn/s **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` -Sysctl options are at `/etc/sysctl.d/20-openvpn.conf` +Sysctl options are at `/etc/sysctl.d/99-openvpn.conf` --- From 7304dbaac80edd2c3193829471cd1312a2ca589f Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:11:11 +0100 Subject: [PATCH 072/141] style: reduce shellcheck disables and fix warnings - Remove unnecessary shellcheck disables (SC2164, SC1072, SC1073, SC1009) - Add explanatory comments for remaining disables - Fix SC2181: use direct exit code check instead of $? - Fix SC2086: quote DH_KEY_SIZE variable --- openvpn-install.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 5feccfa..7326326 100644 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1,5 +1,7 @@ #!/bin/bash -# shellcheck disable=SC1091,SC2164,SC2034,SC1072,SC1073,SC1009 +# shellcheck disable=SC1091,SC2034 +# SC1091: Not following /etc/os-release (sourced dynamically) +# SC2034: Variables used indirectly or exported for subprocesses # Secure OpenVPN server installer for Debian, Ubuntu, CentOS, Amazon Linux 2, Fedora, Oracle Linux 8, Arch Linux, Rocky Linux and AlmaLinux. # https://github.com/angristan/openvpn-install @@ -754,8 +756,7 @@ function installOpenVPN() { local version="3.1.2" local easy_rsa_sha256="d63cf129490ffd6d8792ede7344806c506c82c32428b5bb609ad97ca6a6e4499" wget -O ~/easy-rsa.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-${version}.tgz - echo "${easy_rsa_sha256} ~/easy-rsa.tgz" | sha256sum -c - if [[ $? -ne 0 ]]; then + if ! echo "${easy_rsa_sha256} ~/easy-rsa.tgz" | sha256sum -c; then echo "SHA256 checksum verification failed for easy-rsa download!" rm -f ~/easy-rsa.tgz exit 1 @@ -787,7 +788,7 @@ function installOpenVPN() { if [[ $DH_TYPE == "2" ]]; then # ECDH keys are generated on-the-fly so we don't need to generate them beforehand - openssl dhparam -out dh.pem $DH_KEY_SIZE + openssl dhparam -out dh.pem "$DH_KEY_SIZE" fi EASYRSA_CERT_EXPIRE=3650 ./easyrsa --batch build-server-full "$SERVER_NAME" nopass From b7557dd77f70976aeaad6eefa77cd3950bd7b68f Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:30:43 +0100 Subject: [PATCH 073/141] refactor: extract magic numbers to named constants Move hardcoded values to readonly constants at the top of the script: - CERT_VALIDITY_DAYS: certificate expiry (10 years) - CRL_VALIDITY_DAYS: CRL expiry (10 years) - EASYRSA_VERSION: easy-rsa version - EASYRSA_SHA256: easy-rsa checksum This improves maintainability and makes it easier to update these values in the future. --- openvpn-install.sh | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 7326326..1772287 100644 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -6,6 +6,12 @@ # Secure OpenVPN server installer for Debian, Ubuntu, CentOS, Amazon Linux 2, Fedora, Oracle Linux 8, Arch Linux, Rocky Linux and AlmaLinux. # https://github.com/angristan/openvpn-install +# Configuration constants +readonly CERT_VALIDITY_DAYS=3650 # 10 years +readonly CRL_VALIDITY_DAYS=3650 # 10 years +readonly EASYRSA_VERSION="3.1.2" +readonly EASYRSA_SHA256="d63cf129490ffd6d8792ede7344806c506c82c32428b5bb609ad97ca6a6e4499" + function isRoot() { if [ "$EUID" -ne 0 ]; then return 1 @@ -753,10 +759,8 @@ function installOpenVPN() { # Install the latest version of easy-rsa from source, if not already installed. if [[ ! -d /etc/openvpn/easy-rsa/ ]]; then - local version="3.1.2" - local easy_rsa_sha256="d63cf129490ffd6d8792ede7344806c506c82c32428b5bb609ad97ca6a6e4499" - wget -O ~/easy-rsa.tgz https://github.com/OpenVPN/easy-rsa/releases/download/v${version}/EasyRSA-${version}.tgz - if ! echo "${easy_rsa_sha256} ~/easy-rsa.tgz" | sha256sum -c; then + wget -O ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" + if ! echo "${EASYRSA_SHA256} ~/easy-rsa.tgz" | sha256sum -c; then echo "SHA256 checksum verification failed for easy-rsa download!" rm -f ~/easy-rsa.tgz exit 1 @@ -784,15 +788,15 @@ function installOpenVPN() { # Create the PKI, set up the CA, the DH params and the server certificate ./easyrsa init-pki - EASYRSA_CA_EXPIRE=3650 ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass + EASYRSA_CA_EXPIRE=$CERT_VALIDITY_DAYS ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass if [[ $DH_TYPE == "2" ]]; then # ECDH keys are generated on-the-fly so we don't need to generate them beforehand openssl dhparam -out dh.pem "$DH_KEY_SIZE" fi - EASYRSA_CERT_EXPIRE=3650 ./easyrsa --batch build-server-full "$SERVER_NAME" nopass - EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl + EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS ./easyrsa --batch build-server-full "$SERVER_NAME" nopass + EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS ./easyrsa gen-crl case $TLS_SIG in 1) @@ -1138,11 +1142,11 @@ function newClient() { cd /etc/openvpn/easy-rsa/ || return case $PASS in 1) - EASYRSA_CERT_EXPIRE=3650 ./easyrsa --batch build-client-full "$CLIENT" nopass + EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS ./easyrsa --batch build-client-full "$CLIENT" nopass ;; 2) echo "⚠️ You will be asked for the client password below ⚠️" - EASYRSA_CERT_EXPIRE=3650 ./easyrsa --batch build-client-full "$CLIENT" + EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS ./easyrsa --batch build-client-full "$CLIENT" ;; esac echo "Client $CLIENT added." @@ -1230,7 +1234,7 @@ function revokeClient() { CLIENT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p) cd /etc/openvpn/easy-rsa/ || return ./easyrsa --batch revoke "$CLIENT" - EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl + EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS ./easyrsa gen-crl rm -f /etc/openvpn/crl.pem cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem chmod 644 /etc/openvpn/crl.pem From 77f28d159599eb53c2094a8785d9d7eaf1adc0f0 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:30:49 +0100 Subject: [PATCH 074/141] ci: add fetch-depth: 0 for super-linter v7 compatibility Super-linter v7 requires full git history to find the default branch for comparison. Without fetch-depth: 0, it fails with 'master branch doesn't exist' error. --- .github/workflows/lint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4120775..ef6e5b1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,6 +8,8 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Lint Code Base uses: super-linter/super-linter@v7 env: From 3a0260e9b88170135bf6feeeb97c7d8f3178acaf Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:36:36 +0100 Subject: [PATCH 075/141] Make openvpn-install.sh executable --- openvpn-install.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 openvpn-install.sh diff --git a/openvpn-install.sh b/openvpn-install.sh old mode 100644 new mode 100755 From cad43ad99ec1a5857b68d9c5e06a6ca24f8452b8 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:36:48 +0100 Subject: [PATCH 076/141] Add permissions to lint.yml for security best practices --- .github/workflows/lint.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ef6e5b1..f7cf46d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,6 +2,9 @@ on: [push, pull_request, workflow_dispatch] name: Lint +permissions: + contents: read + jobs: super-linter: runs-on: ubuntu-latest From 62c336022ff36ce236e657be71a08dffbb3bffdd Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:37:01 +0100 Subject: [PATCH 077/141] Add permissions to test.yml for security best practices --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 385ed00..5ea4062 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,10 @@ on: workflow_dispatch: name: Test + +permissions: + contents: read + jobs: install: runs-on: ubuntu-latest From 6b92f8a61f7bb65157f759f0382cfec13d7bd6ac Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:37:48 +0100 Subject: [PATCH 078/141] Quote shell variables in test.yml to fix shellcheck warnings --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5ea4062..3b893ec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,10 +33,10 @@ jobs: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} - name: Create server - run: doctl compute droplet create openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }} --size s-1vcpu-1gb --image ${{ matrix.os-image }} --region lon1 --enable-ipv6 --ssh-keys be:66:76:61:a8:71:93:aa:e3:19:ba:d8:0d:d2:2d:d4 --wait + run: doctl compute droplet create "openvpn-action-${GITHUB_RUN_ID}-${GITHUB_RUN_NUMBER}-${{ matrix.os-image }}" --size s-1vcpu-1gb --image "${{ matrix.os-image }}" --region lon1 --enable-ipv6 --ssh-keys be:66:76:61:a8:71:93:aa:e3:19:ba:d8:0d:d2:2d:d4 --wait - name: Get server ID - run: echo "value=$(doctl compute droplet list -o json | jq -r '.[] | select(.name == "'openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }}'").id')" >> $GITHUB_OUTPUT + run: echo "value=$(doctl compute droplet list -o json | jq -r '.[] | select(.name == "'"openvpn-action-${GITHUB_RUN_ID}-${GITHUB_RUN_NUMBER}-${{ matrix.os-image }}"'").id')" >> "$GITHUB_OUTPUT" id: server_id - name: Move server to dedicated project @@ -46,11 +46,11 @@ jobs: run: sleep 90 - name: Get server IP - run: echo "value=$(doctl compute droplet list -o json | jq -r '.[] | select(.name == "'openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }}'").networks.v4 | .[] | select(.type == "'public'").ip_address')" >> $GITHUB_OUTPUT + run: echo "value=$(doctl compute droplet list -o json | jq -r '.[] | select(.name == "'"openvpn-action-${GITHUB_RUN_ID}-${GITHUB_RUN_NUMBER}-${{ matrix.os-image }}"'").networks.v4 | .[] | select(.type == "'"public"'").ip_address')" >> "$GITHUB_OUTPUT" id: server_ip - name: Get server OS - run: echo "value=$(echo ${{ matrix.os-image }} | cut -d '-' -f1)" >> $GITHUB_OUTPUT + run: echo "value=$(echo "${{ matrix.os-image }}" | cut -d '-' -f1)" >> "$GITHUB_OUTPUT" id: server_os - name: Setup remote server (Debian/Ubuntu) @@ -97,5 +97,5 @@ jobs: script: 'set -x && AUTO_INSTALL=y bash -x ~/openvpn-install/openvpn-install.sh && ps aux | grep openvpn | grep -v grep > /dev/null 2>&1 && echo "Success: OpenVPN is running" && exit 0 || echo "Failure: OpenVPN is not running" && exit 1' - name: Delete server - run: doctl compute droplet delete -f openvpn-action-$GITHUB_RUN_ID-$GITHUB_RUN_NUMBER-${{ matrix.os-image }} + run: doctl compute droplet delete -f "openvpn-action-${GITHUB_RUN_ID}-${GITHUB_RUN_NUMBER}-${{ matrix.os-image }}" if: always() From 7e9a7136577ddcb414f9b50d7c8fc4a04b8f631b Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:44:39 +0100 Subject: [PATCH 079/141] Fix shfmt formatting for constant comments --- openvpn-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 1772287..592f7d1 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -7,8 +7,8 @@ # https://github.com/angristan/openvpn-install # Configuration constants -readonly CERT_VALIDITY_DAYS=3650 # 10 years -readonly CRL_VALIDITY_DAYS=3650 # 10 years +readonly CERT_VALIDITY_DAYS=3650 # 10 years +readonly CRL_VALIDITY_DAYS=3650 # 10 years readonly EASYRSA_VERSION="3.1.2" readonly EASYRSA_SHA256="d63cf129490ffd6d8792ede7344806c506c82c32428b5bb609ad97ca6a6e4499" From fafd10687f8c78646ccd644ef3a1e916966de1b7 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:44:56 +0100 Subject: [PATCH 080/141] Disable MD041 rule for template files with HTML comments --- .github/linters/.markdown-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index 962acbc..695685c 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -1 +1 @@ -{ "MD013": null, "MD045": null, "MD040": null, "MD036": null } +{ "MD013": null, "MD045": null, "MD040": null, "MD036": null, "MD041": null } From 39dd034717cd38ed0a5bf957fe9d57d62cab08fb Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 18:45:12 +0100 Subject: [PATCH 081/141] Fix textlint terminology: websites -> sites --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index c7ca6c6..fa7209e 100644 --- a/FAQ.md +++ b/FAQ.md @@ -154,7 +154,7 @@ So for example - here it would route all traffic of `10.0.0.0/8` to the vpn. And --- -**Q:** I have enabled IPv6 and my VPN client gets an IPv6 address. Why do I reach the websites or other dual-stacked destionations via IPv4 only? +**Q:** I have enabled IPv6 and my VPN client gets an IPv6 address. Why do I reach the sites or other dual-stacked destionations via IPv4 only? **A:** This is because inside the tunnel you don't get a publicly routable IPv6 address, instead you get an ULA (Unlique Local Lan) address. Operating systems don't prefer this all the time. You can fix this in your operating system policies as it's unrelated to the VPN itself: From cc834519ff552e22892afb3c60e5340ebf943019 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 23:01:22 +0100 Subject: [PATCH 082/141] Fix path to easy-rsa tarball in checksum verification --- openvpn-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 592f7d1..9170b7e 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -760,7 +760,7 @@ function installOpenVPN() { # Install the latest version of easy-rsa from source, if not already installed. if [[ ! -d /etc/openvpn/easy-rsa/ ]]; then wget -O ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" - if ! echo "${EASYRSA_SHA256} ~/easy-rsa.tgz" | sha256sum -c; then + if ! echo "${EASYRSA_SHA256} $HOME/easy-rsa.tgz" | sha256sum -c; then echo "SHA256 checksum verification failed for easy-rsa download!" rm -f ~/easy-rsa.tgz exit 1 From 93284de7df6e9cc0f5401604bdc73690b4023d79 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Thu, 4 Dec 2025 23:01:56 +0100 Subject: [PATCH 083/141] Fix typo in FAQ Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index fa7209e..1f0b572 100644 --- a/FAQ.md +++ b/FAQ.md @@ -154,7 +154,7 @@ So for example - here it would route all traffic of `10.0.0.0/8` to the vpn. And --- -**Q:** I have enabled IPv6 and my VPN client gets an IPv6 address. Why do I reach the sites or other dual-stacked destionations via IPv4 only? +**Q:** I have enabled IPv6 and my VPN client gets an IPv6 address. Why do I reach the sites or other dual-stacked destinations via IPv4 only? **A:** This is because inside the tunnel you don't get a publicly routable IPv6 address, instead you get an ULA (Unlique Local Lan) address. Operating systems don't prefer this all the time. You can fix this in your operating system policies as it's unrelated to the VPN itself: From 469bc2f8839beb6d2d9f7e30d91b206e9175b879 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 23:12:06 +0100 Subject: [PATCH 084/141] Update OS images in CI workflow to include Debian 13 and remove 11 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3b893ec..368b506 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,8 +17,8 @@ jobs: strategy: matrix: os-image: - - debian-11-x64 - debian-12-x64 + - debian-13-x64 - ubuntu-22-04-x64 - ubuntu-24-04-x64 - fedora-40-x64 From f92582fb2f966b3e6cf962a2a475e965719f3679 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 23:15:24 +0100 Subject: [PATCH 085/141] Update Fedora OS images in CI workflow to include 42 and 43 --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 368b506..6f30aa4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,8 +21,8 @@ jobs: - debian-13-x64 - ubuntu-22-04-x64 - ubuntu-24-04-x64 - - fedora-40-x64 - - fedora-41-x64 + - fedora-42-x64 + - fedora-43-x64 # - centos-stream-9-x64 # yum oomkill steps: - uses: actions/checkout@v4 From 94c1af2b5d973de3e1388899dcee2b6fdd6a9a52 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 4 Dec 2025 23:18:15 +0100 Subject: [PATCH 086/141] Remove Fedora 43 OS image from CI workflow --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f30aa4..e823678 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,6 @@ jobs: - ubuntu-22-04-x64 - ubuntu-24-04-x64 - fedora-42-x64 - - fedora-43-x64 # - centos-stream-9-x64 # yum oomkill steps: - uses: actions/checkout@v4 From a3389c126c7daeaf05306f23949ec7be33f5a58d Mon Sep 17 00:00:00 2001 From: Stanislas Date: Sun, 7 Dec 2025 12:27:41 +0100 Subject: [PATCH 087/141] Add Docker-based E2E testing (#1320) ### Summary - Add automated end-to-end testing using Docker to verify the installation script works across 18 Linux distributions - Add Oracle Linux 9 support to the installation script - Drop support for EOL distributions (Debian 8/9/10, CentOS 7, Ubuntu 16.04) - Disable Digital Ocean droplets based end-to-end tests, let's use docker from now on ### Changes **New test infrastructure:** - `test/Dockerfile.server` - Multi-OS server image with `BASE_IMAGE` build arg - `test/Dockerfile.client` - Ubuntu 24.04 client for connectivity testing - `test/server-entrypoint.sh` - Runs install script, verifies files exist, asserts iptables NAT rules, starts OpenVPN - `test/client-entrypoint.sh` - Connects to VPN, verifies tun0 interface, pings gateway - `docker-compose.yml` - Orchestrates server + client with shared volume - `.github/workflows/docker-test.yml` - CI matrix testing 18 OS variants - `.github/workflows/test.yml` - Removed push/PR triggers, now manual only for DO tests - `Makefile` - Local testing commands (`make test`, `make test-ubuntu-24.04`, etc.) **Distributions tested (18 total):** | Family | Versions | |--------|----------| | Ubuntu | 18.04, 20.04, 22.04, 24.04 | | Debian | 11, 12 | | Fedora | 40, 41 | | Rocky Linux | 8, 9 | | AlmaLinux | 8, 9 | | Oracle Linux | 8, 9 | | Amazon Linux | 2, 2023 | | CentOS Stream | 9 | | Arch Linux | latest | --- .github/workflows/docker-test.yml | 166 ++++++++++++++++++++++++++++++ .github/workflows/test.yml | 7 +- Makefile | 136 ++++++++++++++++++++++++ README.md | 27 +++-- docker-compose.yml | 55 ++++++++++ openvpn-install.sh | 45 ++++---- test/Dockerfile.client | 24 +++++ test/Dockerfile.server | 42 ++++++++ test/client-entrypoint.sh | 90 ++++++++++++++++ test/server-entrypoint.sh | 109 ++++++++++++++++++++ 10 files changed, 655 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/docker-test.yml create mode 100644 Makefile create mode 100644 docker-compose.yml create mode 100644 test/Dockerfile.client create mode 100644 test/Dockerfile.server create mode 100755 test/client-entrypoint.sh create mode 100755 test/server-entrypoint.sh diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml new file mode 100644 index 0000000..7a16cf1 --- /dev/null +++ b/.github/workflows/docker-test.yml @@ -0,0 +1,166 @@ +--- +on: + push: + workflow_dispatch: + +name: Docker Test + +permissions: + contents: read + +jobs: + docker-test: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: + - name: ubuntu-18.04 + image: ubuntu:18.04 + - name: ubuntu-20.04 + image: ubuntu:20.04 + - name: ubuntu-22.04 + image: ubuntu:22.04 + - name: ubuntu-24.04 + image: ubuntu:24.04 + - name: debian-11 + image: debian:11 + - name: debian-12 + image: debian:12 + - name: centos-stream-9 + image: quay.io/centos/centos:stream9 + - name: fedora-40 + image: fedora:40 + - name: fedora-41 + image: fedora:41 + - name: rocky-8 + image: rockylinux:8 + - name: rocky-9 + image: rockylinux:9 + - name: almalinux-8 + image: almalinux:8 + - name: almalinux-9 + image: almalinux:9 + - name: archlinux + image: archlinux:latest + - name: oraclelinux-8 + image: oraclelinux:8 + - name: oraclelinux-9 + image: oraclelinux:9 + - name: amazonlinux-2 + image: amazonlinux:2 + - name: amazonlinux-2023 + image: amazonlinux:2023 + + name: ${{ matrix.os.name }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build server image + run: | + docker build \ + --build-arg BASE_IMAGE=${{ matrix.os.image }} \ + -t openvpn-server \ + -f test/Dockerfile.server . + + - name: Build client image + run: docker build -t openvpn-client -f test/Dockerfile.client . + + - name: Create Docker network + run: docker network create --subnet=172.28.0.0/24 vpn-test + + - name: Create shared volume + run: docker volume create shared-config + + - name: Start OpenVPN server + run: | + docker run -d \ + --name openvpn-server \ + --hostname openvpn-server \ + --cap-add=NET_ADMIN \ + --device=/dev/net/tun:/dev/net/tun \ + --sysctl net.ipv4.ip_forward=1 \ + --network vpn-test \ + --ip 172.28.0.10 \ + -v shared-config:/shared \ + openvpn-server + + - name: Wait for server installation and startup + run: | + echo "Waiting for OpenVPN server to install and start..." + for i in {1..60}; do + if docker exec openvpn-server pgrep openvpn > /dev/null 2>&1; then + echo "OpenVPN server is running!" + break + fi + echo "Waiting... ($i/60)" + sleep 5 + # Show logs for debugging + docker logs --tail 20 openvpn-server 2>&1 || true + done + + # Final check + if ! docker exec openvpn-server pgrep openvpn > /dev/null 2>&1; then + echo "ERROR: OpenVPN server failed to start" + docker logs openvpn-server + exit 1 + fi + + - name: Verify client config was generated + run: | + docker run --rm -v shared-config:/shared alpine \ + ls -la /shared/ + docker run --rm -v shared-config:/shared alpine \ + cat /shared/client.ovpn + + - name: Start OpenVPN client and run tests + run: | + docker run \ + --name openvpn-client \ + --hostname openvpn-client \ + --cap-add=NET_ADMIN \ + --device=/dev/net/tun:/dev/net/tun \ + --network vpn-test \ + --ip 172.28.0.20 \ + -v shared-config:/shared:ro \ + openvpn-client & + + # Wait for tests to complete (look for success message) + for i in {1..60}; do + if docker logs openvpn-client 2>&1 | grep -q "ALL TESTS PASSED" + then + echo "Tests passed!" + exit 0 + fi + if docker logs openvpn-client 2>&1 | grep -q "FAIL:"; then + echo "Tests failed!" + docker logs openvpn-client + exit 1 + fi + echo "Waiting for tests... ($i/60)" + sleep 2 + done + + echo "Timeout waiting for tests" + docker logs openvpn-client + exit 1 + + - name: Show server logs + if: always() + run: docker logs openvpn-server 2>&1 || true + + - name: Show client logs + if: always() + run: docker logs openvpn-client 2>&1 || true + + - name: Cleanup + if: always() + run: | + docker stop openvpn-server openvpn-client 2>/dev/null || true + docker rm openvpn-server openvpn-client 2>/dev/null || true + docker network rm vpn-test 2>/dev/null || true + docker volume rm shared-config 2>/dev/null || true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e823678..5d609f1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,7 @@ +# DigitalOcean E2E tests (manual trigger only) +# Primary CI testing is now done via Docker in docker-test.yml +# This workflow is kept for real-world VM testing when needed on: - push: - branches: - - master - - ci workflow_dispatch: name: Test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..89ec301 --- /dev/null +++ b/Makefile @@ -0,0 +1,136 @@ +.PHONY: test test-build test-up test-down test-logs test-clean + +# Run the full test suite +test: test-build test-up + @echo "Waiting for tests to complete..." + @for i in $$(seq 1 60); do \ + if docker logs openvpn-client 2>&1 | grep -q "ALL TESTS PASSED"; then \ + echo "✓ Tests passed!"; \ + $(MAKE) test-down; \ + exit 0; \ + fi; \ + if docker logs openvpn-client 2>&1 | grep -q "FAIL:"; then \ + echo "✗ Tests failed!"; \ + docker logs openvpn-client; \ + $(MAKE) test-down; \ + exit 1; \ + fi; \ + echo "Waiting... ($$i/60)"; \ + sleep 2; \ + done; \ + echo "Timeout waiting for tests"; \ + $(MAKE) test-down; \ + exit 1 + +# Build test containers +test-build: + BASE_IMAGE=$(BASE_IMAGE) docker compose build + +# Start test containers +test-up: + docker compose up -d + +# Stop and remove test containers +test-down: + docker compose down -v --remove-orphans + +# View logs +test-logs: + docker compose logs -f + +# View server logs only +test-logs-server: + docker logs -f openvpn-server + +# View client logs only +test-logs-client: + docker logs -f openvpn-client + +# Full cleanup +test-clean: test-down + docker rmi openvpn-install-openvpn-server openvpn-install-openvpn-client 2>/dev/null || true + docker volume prune -f + +# Interactive shell into server container +test-shell-server: + docker exec -it openvpn-server /bin/bash + +# Interactive shell into client container +test-shell-client: + docker exec -it openvpn-client /bin/bash + +# Test specific distributions +test-ubuntu-18.04: + $(MAKE) test BASE_IMAGE=ubuntu:18.04 + +test-ubuntu-20.04: + $(MAKE) test BASE_IMAGE=ubuntu:20.04 + +test-ubuntu-22.04: + $(MAKE) test BASE_IMAGE=ubuntu:22.04 + +test-ubuntu-24.04: + $(MAKE) test BASE_IMAGE=ubuntu:24.04 + +test-debian-11: + $(MAKE) test BASE_IMAGE=debian:11 + +test-debian-12: + $(MAKE) test BASE_IMAGE=debian:12 + +test-fedora-40: + $(MAKE) test BASE_IMAGE=fedora:40 + +test-fedora-41: + $(MAKE) test BASE_IMAGE=fedora:41 + +test-rocky-8: + $(MAKE) test BASE_IMAGE=rockylinux:8 + +test-rocky-9: + $(MAKE) test BASE_IMAGE=rockylinux:9 + +test-almalinux-8: + $(MAKE) test BASE_IMAGE=almalinux:8 + +test-almalinux-9: + $(MAKE) test BASE_IMAGE=almalinux:9 + +test-oracle-8: + $(MAKE) test BASE_IMAGE=oraclelinux:8 + +test-oracle-9: + $(MAKE) test BASE_IMAGE=oraclelinux:9 + +test-amazon-2: + $(MAKE) test BASE_IMAGE=amazonlinux:2 + +test-amazon-2023: + $(MAKE) test BASE_IMAGE=amazonlinux:2023 + +test-arch: + $(MAKE) test BASE_IMAGE=archlinux:latest + +test-centos-stream-9: + $(MAKE) test BASE_IMAGE=quay.io/centos/centos:stream9 + +# Test all distributions (runs sequentially) +test-all: + $(MAKE) test-ubuntu-18.04 + $(MAKE) test-ubuntu-20.04 + $(MAKE) test-ubuntu-22.04 + $(MAKE) test-ubuntu-24.04 + $(MAKE) test-debian-11 + $(MAKE) test-debian-12 + $(MAKE) test-fedora-40 + $(MAKE) test-fedora-41 + $(MAKE) test-rocky-8 + $(MAKE) test-rocky-9 + $(MAKE) test-almalinux-8 + $(MAKE) test-almalinux-9 + $(MAKE) test-oracle-8 + $(MAKE) test-oracle-9 + $(MAKE) test-amazon-2 + $(MAKE) test-amazon-2023 + $(MAKE) test-arch + $(MAKE) test-centos-stream-9 diff --git a/README.md b/README.md index 75d449c..6a58353 100644 --- a/README.md +++ b/README.md @@ -132,26 +132,23 @@ export PASS="1" The script supports these Linux distributions: -| | Support | -| ---------------------- | ------- | -| AlmaLinux 8 | ✅ | -| Amazon Linux 2 | ✅ | -| Amazon Linux >= 2023.6 | ✅ | -| Arch Linux | ✅ | -| CentOS 7 | ✅ | -| CentOS Stream >= 8 | ✅ 🤖 | -| Debian >= 10 | ✅ 🤖 | -| Fedora >= 35 | ✅ 🤖 | -| Oracle Linux 8 | ✅ | -| Rocky Linux 8 | ✅ | -| Ubuntu >= 18.04 | ✅ 🤖 | +| | Support | +| ------------------ | ------- | +| AlmaLinux >= 8 | ✅ 🤖 | +| Amazon Linux 2 | ✅ 🤖 | +| Amazon Linux 2023 | ✅ 🤖 | +| Arch Linux | ✅ 🤖 | +| CentOS Stream >= 8 | ✅ 🤖 | +| Debian >= 11 | ✅ 🤖 | +| Fedora >= 40 | ✅ 🤖 | +| Oracle Linux >= 8 | ✅ 🤖 | +| Rocky Linux >= 8 | ✅ 🤖 | +| Ubuntu >= 18.04 | ✅ 🤖 | To be noted: - The script is regularly tested against the distributions marked with a 🤖 only. - It's only tested on `amd64` architecture. -- It should work on older versions such as Debian 8+, Ubuntu 16.04+ and previous Fedora releases. But versions not in the table above are not officially supported. - - It should also support versions between the LTS versions, but these are not tested. - The script requires `systemd`. ## Fork diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..abce116 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,55 @@ +--- +services: + openvpn-server: + build: + context: . + dockerfile: test/Dockerfile.server + args: + BASE_IMAGE: ${BASE_IMAGE:-} + container_name: openvpn-server + hostname: openvpn-server + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + sysctls: + - net.ipv4.ip_forward=1 + volumes: + - shared-config:/shared + networks: + vpn-test: + ipv4_address: 172.28.0.10 + healthcheck: + test: ["CMD", "pgrep", "openvpn"] + interval: 5s + timeout: 3s + retries: 30 + + openvpn-client: + build: + context: . + dockerfile: test/Dockerfile.client + container_name: openvpn-client + hostname: openvpn-client + cap_add: + - NET_ADMIN + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - shared-config:/shared:ro + networks: + vpn-test: + ipv4_address: 172.28.0.20 + depends_on: + openvpn-server: + condition: service_healthy + +volumes: + shared-config: + +networks: + vpn-test: + driver: bridge + ipam: + config: + - subnet: 172.28.0.0/24 diff --git a/openvpn-install.sh b/openvpn-install.sh index 9170b7e..e6f0eca 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -3,7 +3,7 @@ # SC1091: Not following /etc/os-release (sourced dynamically) # SC2034: Variables used indirectly or exported for subprocesses -# Secure OpenVPN server installer for Debian, Ubuntu, CentOS, Amazon Linux 2, Fedora, Oracle Linux 8, Arch Linux, Rocky Linux and AlmaLinux. +# Secure OpenVPN server installer for Debian, Ubuntu, CentOS, Amazon Linux 2, Fedora, Oracle Linux, Arch Linux, Rocky Linux and AlmaLinux. # https://github.com/angristan/openvpn-install # Configuration constants @@ -30,10 +30,10 @@ function checkOS() { source /etc/os-release if [[ $ID == "debian" || $ID == "raspbian" ]]; then - if [[ $VERSION_ID -lt 9 ]]; then + if [[ $VERSION_ID -lt 11 ]]; then echo "⚠️ Your version of Debian is not supported." echo "" - echo "However, if you're using Debian >= 9 or unstable/testing, you can continue at your own risk." + echo "However, if you're using Debian >= 11 or unstable/testing, you can continue at your own risk." echo "" until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Continue? [y/n]: " -e CONTINUE @@ -45,10 +45,10 @@ function checkOS() { elif [[ $ID == "ubuntu" ]]; then OS="ubuntu" MAJOR_UBUNTU_VERSION=$(echo "$VERSION_ID" | cut -d '.' -f1) - if [[ $MAJOR_UBUNTU_VERSION -lt 16 ]]; then + if [[ $MAJOR_UBUNTU_VERSION -lt 18 ]]; then echo "⚠️ Your version of Ubuntu is not supported." echo "" - echo "However, if you're using Ubuntu >= 16.04 or beta, you can continue at your own risk." + echo "However, if you're using Ubuntu >= 18.04 or beta, you can continue at your own risk." echo "" until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Continue? [y/n]: " -e CONTINUE @@ -65,20 +65,20 @@ function checkOS() { fi if [[ $ID == "centos" || $ID == "rocky" || $ID == "almalinux" ]]; then OS="centos" - if [[ ${VERSION_ID%.*} -lt 7 ]]; then + if [[ ${VERSION_ID%.*} -lt 8 ]]; then echo "⚠️ Your version of CentOS is not supported." echo "" - echo "The script only supports CentOS 7 and CentOS 8." + echo "The script only supports CentOS Stream 8+ / Rocky Linux 8+ / AlmaLinux 8+." echo "" exit 1 fi fi if [[ $ID == "ol" ]]; then OS="oracle" - if [[ ! $VERSION_ID =~ (8) ]]; then + if [[ ! $VERSION_ID =~ ^(8|9) ]]; then echo "Your version of Oracle Linux is not supported." echo "" - echo "The script only supports Oracle Linux 8." + echo "The script only supports Oracle Linux 8 and 9." exit 1 fi fi @@ -98,7 +98,7 @@ function checkOS() { elif [[ -e /etc/arch-release ]]; then OS=arch else - echo "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2, Oracle Linux 8 or Arch Linux system." + echo "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2, Oracle Linux or Arch Linux system." exit 1 fi } @@ -718,20 +718,19 @@ function installOpenVPN() { if [[ $OS =~ (debian|ubuntu) ]]; then apt-get update apt-get -y install ca-certificates gnupg - # We add the OpenVPN repo to get the latest version. - if [[ $VERSION_ID == "16.04" ]]; then - echo "deb http://build.openvpn.net/debian/openvpn/stable xenial main" >/etc/apt/sources.list.d/openvpn.list - wget -O - https://swupdate.openvpn.net/repos/repo-public.gpg | apt-key add - - apt-get update - fi - # Ubuntu > 16.04 and Debian > 8 have OpenVPN >= 2.4 without the need of a third party repository. + # Ubuntu >= 18.04 and Debian >= 11 have OpenVPN >= 2.4 without the need of a third party repository. apt-get install -y openvpn iptables openssl wget ca-certificates curl elif [[ $OS == 'centos' ]]; then yum install -y epel-release yum install -y openvpn iptables openssl wget ca-certificates curl tar 'policycoreutils-python*' elif [[ $OS == 'oracle' ]]; then - yum install -y oracle-epel-release-el8 - yum-config-manager --enable ol8_developer_EPEL + if [[ $VERSION_ID =~ ^8 ]]; then + yum install -y oracle-epel-release-el8 + yum-config-manager --enable ol8_developer_EPEL + elif [[ $VERSION_ID =~ ^9 ]]; then + yum install -y oracle-epel-release-el9 + yum-config-manager --enable ol9_developer_EPEL + fi yum install -y openvpn iptables openssl wget ca-certificates curl tar policycoreutils-python-utils elif [[ $OS == 'amzn' ]]; then amazon-linux-extras install -y epel @@ -993,11 +992,6 @@ verb 3" >>/etc/openvpn/server.conf systemctl daemon-reload systemctl enable openvpn-server@server systemctl restart openvpn-server@server - elif [[ $OS == "ubuntu" ]] && [[ $VERSION_ID == "16.04" ]]; then - # On Ubuntu 16.04, we use the package from the OpenVPN repo - # This package uses a sysvinit service - systemctl enable openvpn - systemctl start openvpn else # Don't modify package-provided service cp /lib/systemd/system/openvpn\@.service /etc/systemd/system/openvpn\@.service @@ -1297,9 +1291,6 @@ function removeOpenVPN() { systemctl stop openvpn-server@server # Remove customised service rm /etc/systemd/system/openvpn-server@.service - elif [[ $OS == "ubuntu" ]] && [[ $VERSION_ID == "16.04" ]]; then - systemctl disable openvpn - systemctl stop openvpn else systemctl disable openvpn@server systemctl stop openvpn@server diff --git a/test/Dockerfile.client b/test/Dockerfile.client new file mode 100644 index 0000000..a8121e3 --- /dev/null +++ b/test/Dockerfile.client @@ -0,0 +1,24 @@ +# checkov:skip=CKV_DOCKER_2:Test container doesn't need healthcheck +# checkov:skip=CKV_DOCKER_3:OpenVPN client requires root for NET_ADMIN +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Install OpenVPN client and testing tools +RUN apt-get update && apt-get install -y \ + openvpn \ + iproute2 \ + iputils-ping \ + procps \ + && rm -rf /var/lib/apt/lists/* + +# Create TUN device directory (device will be mounted at runtime) +RUN mkdir -p /dev/net + +# Copy test scripts +COPY test/client-entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +WORKDIR /etc/openvpn + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/test/Dockerfile.server b/test/Dockerfile.server new file mode 100644 index 0000000..c79baef --- /dev/null +++ b/test/Dockerfile.server @@ -0,0 +1,42 @@ +# checkov:skip=CKV_DOCKER_2:Test container doesn't need healthcheck +# checkov:skip=CKV_DOCKER_3:OpenVPN server requires root for NET_ADMIN +# checkov:skip=CKV_DOCKER_7:Base image is parameterized, some use latest tag +ARG BASE_IMAGE=ubuntu:24.04 +FROM ${BASE_IMAGE} + +ARG BASE_IMAGE +ENV DEBIAN_FRONTEND=noninteractive + +# Install basic dependencies based on the OS +RUN if command -v apt-get >/dev/null; then \ + apt-get update && apt-get install -y \ + iproute2 iptables curl procps systemd systemd-sysv \ + && rm -rf /var/lib/apt/lists/*; \ + elif command -v dnf >/dev/null; then \ + dnf install -y --allowerasing \ + iproute iptables curl procps-ng systemd tar gzip \ + && dnf clean all; \ + elif command -v yum >/dev/null; then \ + yum install -y \ + iproute iptables curl procps-ng systemd tar gzip \ + && yum clean all; \ + elif command -v pacman >/dev/null; then \ + pacman -Syu --noconfirm \ + iproute2 iptables curl procps-ng \ + && pacman -Scc --noconfirm; \ + fi + +# Create TUN device (will be mounted at runtime) +RUN mkdir -p /dev/net + +# Copy the install script +COPY openvpn-install.sh /opt/openvpn-install.sh +RUN chmod +x /opt/openvpn-install.sh + +# Copy test scripts +COPY test/server-entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +WORKDIR /opt + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/test/client-entrypoint.sh b/test/client-entrypoint.sh new file mode 100755 index 0000000..c2ed605 --- /dev/null +++ b/test/client-entrypoint.sh @@ -0,0 +1,90 @@ +#!/bin/bash +set -e + +echo "=== OpenVPN Client Container ===" + +# Create TUN device if it doesn't exist +if [ ! -c /dev/net/tun ]; then + mkdir -p /dev/net + mknod /dev/net/tun c 10 200 + chmod 600 /dev/net/tun +fi + +echo "TUN device ready" + +# Wait for client config to be available +echo "Waiting for client config..." +MAX_WAIT=120 +WAITED=0 +while [ ! -f /shared/client.ovpn ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/client.ovpn ]; then + echo "ERROR: Client config not found after ${MAX_WAIT}s" + exit 1 +fi + +echo "Client config found!" +cat /shared/client.ovpn + +# Connect to VPN +echo "Connecting to OpenVPN server..." +openvpn --config /shared/client.ovpn --daemon --log /var/log/openvpn.log + +# Wait for connection +echo "Waiting for VPN connection..." +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)" + + # Check for errors + if [ -f /var/log/openvpn.log ]; then + tail -5 /var/log/openvpn.log + fi +done + +if ! ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "ERROR: VPN connection failed" + echo "=== OpenVPN log ===" + cat /var/log/openvpn.log || true + exit 1 +fi + +echo "=== VPN Connected! ===" +ip addr show tun0 + +# Run connectivity tests +echo "" +echo "=== Running connectivity tests ===" + +# Test 1: Check tun0 interface +echo "Test 1: Checking tun0 interface..." +if ip addr show tun0 | grep -q "10.8.0"; then + echo "PASS: tun0 interface has correct IP range (10.8.0.x)" +else + echo "FAIL: tun0 interface doesn't have expected IP" + exit 1 +fi + +# Test 2: Ping VPN gateway +echo "Test 2: Pinging VPN gateway (10.8.0.1)..." +if ping -c 3 10.8.0.1; then + echo "PASS: Can ping VPN gateway" +else + echo "FAIL: Cannot ping VPN gateway" + exit 1 +fi + +echo "" +echo "==========================================" +echo " ALL TESTS PASSED!" +echo "==========================================" + +# Keep container running for debugging if needed +exec tail -f /var/log/openvpn.log diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh new file mode 100755 index 0000000..5661e3f --- /dev/null +++ b/test/server-entrypoint.sh @@ -0,0 +1,109 @@ +#!/bin/bash +set -e + +echo "=== OpenVPN Server Container ===" + +# Create TUN device if it doesn't exist +if [ ! -c /dev/net/tun ]; then + mkdir -p /dev/net + mknod /dev/net/tun c 10 200 + chmod 600 /dev/net/tun +fi + +echo "TUN device ready" + +# Set up environment for auto-install +export AUTO_INSTALL=y +export APPROVE_INSTALL=y +export APPROVE_IP=y +export IPV6_SUPPORT=n +export PORT_CHOICE=1 +export PROTOCOL_CHOICE=1 +export DNS=9 # Google DNS (works in containers) +export COMPRESSION_ENABLED=n +export CUSTOMIZE_ENC=n +export CLIENT=testclient +export PASS=1 +export ENDPOINT=openvpn-server + +# Prepare script for container environment: +# - Replace systemctl calls with no-ops (systemd doesn't work in containers) +# This ensures the script won't fail silently on systemctl commands +sed 's/\bsystemctl /echo "[SKIPPED] systemctl " # /g' /opt/openvpn-install.sh >/tmp/openvpn-install.sh +chmod +x /tmp/openvpn-install.sh + +echo "Running OpenVPN install script..." +# Run in subshell because the script calls 'exit 0' after generating client config +# Use || true to prevent set -e from exiting on failure, then check exit code +(bash -x /tmp/openvpn-install.sh) && INSTALL_EXIT_CODE=0 || INSTALL_EXIT_CODE=$? + +echo "=== Installation complete (exit code: $INSTALL_EXIT_CODE) ===" + +if [ "$INSTALL_EXIT_CODE" -ne 0 ]; then + echo "ERROR: Install script failed with exit code $INSTALL_EXIT_CODE" + exit 1 +fi + +# Verify all expected files were created +echo "Verifying installation..." +MISSING_FILES=0 +for f in \ + /etc/openvpn/server.conf \ + /etc/openvpn/ca.crt \ + /etc/openvpn/ca.key \ + /etc/openvpn/tls-crypt.key \ + /etc/openvpn/crl.pem \ + /etc/openvpn/easy-rsa/pki/ca.crt \ + /etc/iptables/add-openvpn-rules.sh \ + /root/testclient.ovpn; do + if [ ! -f "$f" ]; then + echo "ERROR: Missing file: $f" + MISSING_FILES=$((MISSING_FILES + 1)) + fi +done + +if [ $MISSING_FILES -gt 0 ]; then + echo "ERROR: $MISSING_FILES required files are missing" + exit 1 +fi + +echo "All required files present" +echo "" +echo "Server config:" +cat /etc/openvpn/server.conf + +# Copy client config to shared volume +cp /root/testclient.ovpn /shared/client.ovpn +# Modify remote address to use container hostname +sed -i 's/^remote .*/remote openvpn-server 1194/' /shared/client.ovpn +echo "Client config copied to /shared/client.ovpn" + +# Start OpenVPN server manually (systemd doesn't work in containers) +echo "Starting OpenVPN server..." + +# Apply iptables rules manually (systemd not available in containers) +echo "Applying iptables rules..." +bash /etc/iptables/add-openvpn-rules.sh + +# Verify iptables NAT rules exist +echo "Verifying iptables NAT rules..." +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" +else + echo "FAIL: NAT POSTROUTING rule for 10.8.0.0/24 not found" + echo "Current NAT rules:" + iptables -t nat -L POSTROUTING -n -v + exit 1 +fi + +# Enable IP forwarding (may already be set via docker-compose sysctls) +if [ "$(cat /proc/sys/net/ipv4/ip_forward)" != "1" ]; then + echo 1 >/proc/sys/net/ipv4/ip_forward || { + echo "ERROR: Failed to enable IP forwarding" + exit 1 + } +fi + +# Start OpenVPN in foreground (run from /etc/openvpn so relative paths work) +cd /etc/openvpn +exec openvpn --config /etc/openvpn/server.conf From 0ed153b8ec49d4ebf7410d9012cba57f89eab9fc Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Tue, 9 Dec 2025 14:17:19 +0100 Subject: [PATCH 088/141] Update pull request template to clarify maintenance burden of added features --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 43bed85..d093642 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,5 +3,5 @@ ➡️ Please make sure you've followed the guidelines: https://github.com/angristan/openvpn-install#contributing ✅ Please make sure your changes are tested and working 🗣️ Please avoid large PRs, and discuss changes in a GitHub issue first -✋ If the changes are too big and not in line with the project, they will probably be rejected. Remember that this script is meant to be simple and easy to use. +✋ If the changes are too big and not in line with the project, they will probably be rejected. Remember that this script is meant to be simple and easy to use! And that added features increase maintenance burden. ---> From adc4c6d220e573c78a0a9721d8d2976cfde9153e Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Tue, 9 Dec 2025 15:02:23 +0100 Subject: [PATCH 089/141] Remove cloud provisioning solutions section from README I don't maintain them, so I can't vouch that they work. Close https://github.com/angristan/openvpn-install/pull/934 as well --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 6a58353..ccb0454 100644 --- a/README.md +++ b/README.md @@ -197,13 +197,6 @@ More Q&A in [FAQ.md](FAQ.md). More Q&A in [FAQ.md](FAQ.md). -## One-stop solutions for public cloud - -Solutions that provision a ready to use OpenVPN server based on this script in one go are available for: - -- AWS using Terraform at [`openvpn-terraform-install`](https://github.com/dumrauf/openvpn-terraform-install) -- Terraform AWS module [`openvpn-ephemeral`](https://registry.terraform.io/modules/paulmarsicloud/openvpn-ephemeral/aws/latest) - ## Contributing ## Discuss changes From 004fbb477a6049c5e3dbde035ab8c958cfe311ed Mon Sep 17 00:00:00 2001 From: Stanislas Date: Tue, 9 Dec 2025 15:52:37 +0100 Subject: [PATCH 090/141] Add structured logging system with color-coded output and file logging (#1321) ## Summary - Add comprehensive logging system with color-coded log levels ([INFO], [WARN], [ERROR], [OK]) - Wrap all command executions with `run_cmd()` to capture output and prevent leaks to stdout - Add file logging with timestamps (default: `openvpn-install.log`) - Suppress interactive prompts in auto-install mode for cleaner CI/scripted usage - Show log file location hint on errors for easier debugging ## Changes - **openvpn-install.sh**: New logging functions (`log_info`, `log_warn`, `log_error`, `log_fatal`, `log_success`, `log_prompt`, `log_header`, `log_menu`, `run_cmd`), all `echo` statements converted to use logging functions - **test/validate-output.sh**: New E2E validator that ensures all script output uses proper log formatting (catches raw echo leaks) - **test/server-entrypoint.sh**: Integrates output validation into Docker tests - **test/Dockerfile.server**: Copies validation script into container ## Configuration - `VERBOSE=1` - Show command output in terminal - `LOG_FILE=path` - Customize log location (default: `openvpn-install.log`) - `LOG_FILE=""` - Disable file logging - `FORCE_COLOR=1` - Force colored output in non-TTY environments --- docker-compose.yml | 2 +- openvpn-install.sh | 791 +++++++++++++++++++++++--------------- test/Dockerfile.server | 3 +- test/server-entrypoint.sh | 16 +- test/validate-output.sh | 87 +++++ 5 files changed, 579 insertions(+), 320 deletions(-) create mode 100755 test/validate-output.sh diff --git a/docker-compose.yml b/docker-compose.yml index abce116..094c96d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: context: . dockerfile: test/Dockerfile.server args: - BASE_IMAGE: ${BASE_IMAGE:-} + BASE_IMAGE: ${BASE_IMAGE:-ubuntu:24.04} container_name: openvpn-server hostname: openvpn-server cap_add: diff --git a/openvpn-install.sh b/openvpn-install.sh index e6f0eca..a407287 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -12,6 +12,142 @@ readonly CRL_VALIDITY_DAYS=3650 # 10 years readonly EASYRSA_VERSION="3.1.2" readonly EASYRSA_SHA256="d63cf129490ffd6d8792ede7344806c506c82c32428b5bb609ad97ca6a6e4499" +# ============================================================================= +# Logging Configuration +# ============================================================================= +# Set VERBOSE=1 to see command output, VERBOSE=0 (default) for quiet mode +# Set LOG_FILE to customize log location (default: openvpn-install.log in current dir) +# Set LOG_FILE="" to disable file logging +VERBOSE=${VERBOSE:-0} +LOG_FILE=${LOG_FILE:-openvpn-install.log} + +# Color definitions (disabled if not a terminal, unless FORCE_COLOR=1) +if [[ -t 1 ]] || [[ $FORCE_COLOR == "1" ]]; then + readonly COLOR_RESET='\033[0m' + readonly COLOR_RED='\033[0;31m' + readonly COLOR_GREEN='\033[0;32m' + readonly COLOR_YELLOW='\033[0;33m' + readonly COLOR_BLUE='\033[0;34m' + readonly COLOR_CYAN='\033[0;36m' + readonly COLOR_DIM='\033[0;90m' + readonly COLOR_BOLD='\033[1m' +else + readonly COLOR_RESET='' + readonly COLOR_RED='' + readonly COLOR_GREEN='' + readonly COLOR_YELLOW='' + readonly COLOR_BLUE='' + readonly COLOR_CYAN='' + readonly COLOR_DIM='' + readonly COLOR_BOLD='' +fi + +# Write to log file (no colors, with timestamp) +_log_to_file() { + if [[ -n "$LOG_FILE" ]]; then + echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >>"$LOG_FILE" + fi +} + +# Logging functions +log_info() { + echo -e "${COLOR_BLUE}[INFO]${COLOR_RESET} $*" + _log_to_file "[INFO] $*" +} + +log_warn() { + echo -e "${COLOR_YELLOW}[WARN]${COLOR_RESET} $*" + _log_to_file "[WARN] $*" +} + +log_error() { + echo -e "${COLOR_RED}[ERROR]${COLOR_RESET} $*" >&2 + _log_to_file "[ERROR] $*" + if [[ -n "$LOG_FILE" ]]; then + echo -e "${COLOR_YELLOW} Check the log file for details: ${LOG_FILE}${COLOR_RESET}" >&2 + fi +} + +log_fatal() { + echo -e "${COLOR_RED}[ERROR]${COLOR_RESET} $*" >&2 + _log_to_file "[FATAL] $*" + if [[ -n "$LOG_FILE" ]]; then + echo -e "${COLOR_YELLOW} Check the log file for details: ${LOG_FILE}${COLOR_RESET}" >&2 + _log_to_file "Script exited with error" + fi + exit 1 +} + +log_success() { + echo -e "${COLOR_GREEN}[OK]${COLOR_RESET} $*" + _log_to_file "[OK] $*" +} + +log_debug() { + if [[ $VERBOSE -eq 1 ]]; then + echo -e "${COLOR_DIM}[DEBUG]${COLOR_RESET} $*" + fi + _log_to_file "[DEBUG] $*" +} + +log_prompt() { + # For user-facing prompts/questions (no prefix, just cyan) + # Skip display in auto-install mode + if [[ $AUTO_INSTALL != "y" ]]; then + echo -e "${COLOR_CYAN}$*${COLOR_RESET}" + fi + _log_to_file "[PROMPT] $*" +} + +log_header() { + # For section headers + # Skip display in auto-install mode + if [[ $AUTO_INSTALL != "y" ]]; then + echo "" + echo -e "${COLOR_BOLD}${COLOR_BLUE}=== $* ===${COLOR_RESET}" + echo "" + fi + _log_to_file "=== $* ===" +} + +log_menu() { + # For menu options - only show in interactive mode + if [[ $AUTO_INSTALL != "y" ]]; then + echo "$@" + fi +} + +# Run a command with optional output suppression +# Usage: run_cmd "description" command [args...] +run_cmd() { + local desc="$1" + shift + if [[ $VERBOSE -eq 1 ]]; then + echo -e "${COLOR_DIM}> $*${COLOR_RESET}" + fi + _log_to_file "[CMD] $*" + if [[ $VERBOSE -eq 1 ]]; then + if [[ -n "$LOG_FILE" ]]; then + "$@" 2>&1 | tee -a "$LOG_FILE" + else + "$@" + fi + else + if [[ -n "$LOG_FILE" ]]; then + "$@" >>"$LOG_FILE" 2>&1 + else + "$@" >/dev/null 2>&1 + fi + fi + local ret=$? + if [[ $ret -eq 0 ]]; then + log_debug "$desc completed successfully" + else + log_error "$desc failed with exit code $ret" + fi + return $ret +} + function isRoot() { if [ "$EUID" -ne 0 ]; then return 1 @@ -31,10 +167,8 @@ function checkOS() { if [[ $ID == "debian" || $ID == "raspbian" ]]; then if [[ $VERSION_ID -lt 11 ]]; then - echo "⚠️ Your version of Debian is not supported." - echo "" - echo "However, if you're using Debian >= 11 or unstable/testing, you can continue at your own risk." - echo "" + log_warn "Your version of Debian is not supported." + log_info "However, if you're using Debian >= 11 or unstable/testing, you can continue at your own risk." until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Continue? [y/n]: " -e CONTINUE done @@ -46,10 +180,8 @@ function checkOS() { OS="ubuntu" MAJOR_UBUNTU_VERSION=$(echo "$VERSION_ID" | cut -d '.' -f1) if [[ $MAJOR_UBUNTU_VERSION -lt 18 ]]; then - echo "⚠️ Your version of Ubuntu is not supported." - echo "" - echo "However, if you're using Ubuntu >= 18.04 or beta, you can continue at your own risk." - echo "" + log_warn "Your version of Ubuntu is not supported." + log_info "However, if you're using Ubuntu >= 18.04 or beta, you can continue at your own risk." until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Continue? [y/n]: " -e CONTINUE done @@ -66,20 +198,15 @@ function checkOS() { if [[ $ID == "centos" || $ID == "rocky" || $ID == "almalinux" ]]; then OS="centos" if [[ ${VERSION_ID%.*} -lt 8 ]]; then - echo "⚠️ Your version of CentOS is not supported." - echo "" - echo "The script only supports CentOS Stream 8+ / Rocky Linux 8+ / AlmaLinux 8+." - echo "" - exit 1 + log_info "The script only supports CentOS Stream 8+ / Rocky Linux 8+ / AlmaLinux 8+." + log_fatal "Your version of CentOS is not supported." fi fi if [[ $ID == "ol" ]]; then OS="oracle" if [[ ! $VERSION_ID =~ ^(8|9) ]]; then - echo "Your version of Oracle Linux is not supported." - echo "" - echo "The script only supports Oracle Linux 8 and 9." - exit 1 + log_info "The script only supports Oracle Linux 8 and 9." + log_fatal "Your version of Oracle Linux is not supported." fi fi if [[ $ID == "amzn" ]]; then @@ -88,39 +215,34 @@ function checkOS() { elif [[ "$(echo "$PRETTY_NAME" | cut -c 1-18)" == "Amazon Linux 2023." ]] && [[ "$(echo "$PRETTY_NAME" | cut -c 19)" -ge 6 ]]; then OS="amzn2023" else - echo "⚠️ Your version of Amazon Linux is not supported." - echo "" - echo "The script only supports Amazon Linux 2 or Amazon Linux 2023.6+" - echo "" - exit 1 + log_info "The script only supports Amazon Linux 2 or Amazon Linux 2023.6+" + log_fatal "Your version of Amazon Linux is not supported." fi fi elif [[ -e /etc/arch-release ]]; then OS=arch else - echo "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2, Oracle Linux or Arch Linux system." - exit 1 + log_fatal "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2, Oracle Linux or Arch Linux system." fi } function initialCheck() { if ! isRoot; then - echo "Sorry, you need to run this script as root." - exit 1 + log_fatal "Sorry, you need to run this script as root." fi if ! tunAvailable; then - echo "TUN is not available." - exit 1 + log_fatal "TUN is not available." fi checkOS } function installUnbound() { + log_info "Installing Unbound DNS resolver..." # If Unbound isn't installed, install it if [[ ! -e /etc/unbound/unbound.conf ]]; then if [[ $OS =~ (debian|ubuntu) ]]; then - apt-get install -y unbound + run_cmd "Installing Unbound" apt-get install -y unbound # Configuration echo 'interface: 10.8.0.1 @@ -131,7 +253,7 @@ use-caps-for-id: yes prefetch: yes' >>/etc/unbound/unbound.conf elif [[ $OS =~ (centos|amzn|oracle) ]]; then - yum install -y unbound + run_cmd "Installing Unbound" yum install -y unbound # Configuration sed -i 's|# interface: 0.0.0.0$|interface: 10.8.0.1|' /etc/unbound/unbound.conf @@ -141,7 +263,7 @@ prefetch: yes' >>/etc/unbound/unbound.conf sed -i 's|use-caps-for-id: no|use-caps-for-id: yes|' /etc/unbound/unbound.conf elif [[ $OS == "fedora" ]]; then - dnf install -y unbound + run_cmd "Installing Unbound" dnf install -y unbound # Configuration sed -i 's|# interface: 0.0.0.0$|interface: 10.8.0.1|' /etc/unbound/unbound.conf @@ -151,19 +273,18 @@ prefetch: yes' >>/etc/unbound/unbound.conf sed -i 's|# use-caps-for-id: no|use-caps-for-id: yes|' /etc/unbound/unbound.conf elif [[ $OS == "arch" ]]; then - pacman -Syu --noconfirm unbound + run_cmd "Installing Unbound" pacman -Syu --noconfirm unbound # Get root servers list - curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache + run_cmd "Downloading root hints" curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache # Verify download was successful and file contains expected content if [[ ! -s /etc/unbound/root.hints ]] || ! grep -q "ROOT-SERVERS" /etc/unbound/root.hints; then - echo "Failed to download root.hints or file is invalid!" - rm -f /etc/unbound/root.hints - exit 1 + run_cmd "Cleaning up invalid file" rm -f /etc/unbound/root.hints + log_fatal "Failed to download root.hints or file is invalid!" fi if [[ ! -f /etc/unbound/unbound.conf.old ]]; then - mv /etc/unbound/unbound.conf /etc/unbound/unbound.conf.old + run_cmd "Backing up unbound.conf" mv /etc/unbound/unbound.conf /etc/unbound/unbound.conf.old fi echo 'server: @@ -229,8 +350,8 @@ access-control: fd42:42:42:42::/112 allow' >>/etc/unbound/openvpn.conf fi fi - systemctl enable unbound - systemctl restart unbound + run_cmd "Enabling Unbound service" systemctl enable unbound + run_cmd "Starting Unbound service" systemctl restart unbound } function resolvePublicIP() { @@ -265,23 +386,21 @@ function resolvePublicIP() { fi if [[ -z $PUBLIC_IP ]]; then - echo >&2 "Couldn't solve the public IP" - exit 1 + log_fatal "Couldn't solve the public IP" fi echo "$PUBLIC_IP" } function installQuestions() { - echo "Welcome to the OpenVPN installer!" - echo "The git repository is available at: https://github.com/angristan/openvpn-install" - echo "" + log_header "OpenVPN Installer" + log_prompt "The git repository is available at: https://github.com/angristan/openvpn-install" - echo "I need to ask you a few questions before starting the setup." - echo "You can leave the default options and just press enter if you are okay with them." - echo "" - echo "I need to know the IPv4 address of the network interface you want OpenVPN listening to." - echo "Unless your server is behind NAT, it should be your public IPv4 address." + log_prompt "I need to ask you a few questions before starting the setup." + log_prompt "You can leave the default options and just press enter if you are okay with them." + log_menu "" + log_prompt "I need to know the IPv4 address of the network interface you want OpenVPN listening to." + log_prompt "Unless your server is behind NAT, it should be your public IPv4 address." # Detect public IPv4 address and pre-fill for the user IP=$(ip -4 addr | sed -ne 's|^.* inet \([^/]*\)/.* scope global.*$|\1|p' | head -1) @@ -296,9 +415,9 @@ function installQuestions() { fi # If $IP is a private IP address, the server must be behind NAT if echo "$IP" | grep -qE '^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192\.168)'; then - echo "" - echo "It seems this server is behind NAT. What is its public IPv4 address or hostname?" - echo "We need it for the clients to connect to the server." + log_menu "" + log_prompt "It seems this server is behind NAT. What is its public IPv4 address or hostname?" + log_prompt "We need it for the clients to connect to the server." if [[ -z $ENDPOINT ]]; then DEFAULT_ENDPOINT=$(resolvePublicIP) @@ -309,9 +428,8 @@ function installQuestions() { done fi - echo "" - echo "Checking for IPv6 connectivity..." - echo "" + log_menu "" + log_prompt "Checking for IPv6 connectivity..." # "ping6" and "ping -6" availability varies depending on the distribution if type ping6 >/dev/null 2>&1; then PING6="ping6 -c3 ipv6.google.com > /dev/null 2>&1" @@ -319,22 +437,22 @@ function installQuestions() { PING6="ping -6 -c3 ipv6.google.com > /dev/null 2>&1" fi if eval "$PING6"; then - echo "Your host appears to have IPv6 connectivity." + log_prompt "Your host appears to have IPv6 connectivity." SUGGESTION="y" else - echo "Your host does not appear to have IPv6 connectivity." + log_prompt "Your host does not appear to have IPv6 connectivity." SUGGESTION="n" fi - echo "" + log_menu "" # Ask the user if they want to enable IPv6 regardless its availability. until [[ $IPV6_SUPPORT =~ (y|n) ]]; do read -rp "Do you want to enable IPv6 support (NAT)? [y/n]: " -e -i $SUGGESTION IPV6_SUPPORT done - echo "" - echo "What port do you want OpenVPN to listen to?" - echo " 1) Default: 1194" - echo " 2) Custom" - echo " 3) Random [49152-65535]" + log_menu "" + log_prompt "What port do you want OpenVPN to listen to?" + log_menu " 1) Default: 1194" + log_menu " 2) Custom" + log_menu " 3) Random [49152-65535]" until [[ $PORT_CHOICE =~ ^[1-3]$ ]]; do read -rp "Port choice [1-3]: " -e -i 1 PORT_CHOICE done @@ -350,14 +468,14 @@ function installQuestions() { 3) # Generate random number within private ports range PORT=$(shuf -i49152-65535 -n1) - echo "Random Port: $PORT" + log_info "Random Port: $PORT" ;; esac - echo "" - echo "What protocol do you want OpenVPN to use?" - echo "UDP is faster. Unless it is not available, you shouldn't use TCP." - echo " 1) UDP" - echo " 2) TCP" + log_menu "" + log_prompt "What protocol do you want OpenVPN to use?" + log_prompt "UDP is faster. Unless it is not available, you shouldn't use TCP." + log_menu " 1) UDP" + log_menu " 2) TCP" until [[ $PROTOCOL_CHOICE =~ ^[1-2]$ ]]; do read -rp "Protocol [1-2]: " -e -i 1 PROTOCOL_CHOICE done @@ -369,30 +487,30 @@ function installQuestions() { PROTOCOL="tcp" ;; esac - echo "" - echo "What DNS resolvers do you want to use with the VPN?" - echo " 1) Current system resolvers (from /etc/resolv.conf)" - echo " 2) Self-hosted DNS Resolver (Unbound)" - echo " 3) Cloudflare (Anycast: worldwide)" - echo " 4) Quad9 (Anycast: worldwide)" - echo " 5) Quad9 uncensored (Anycast: worldwide)" - echo " 6) FDN (France)" - echo " 7) DNS.WATCH (Germany)" - echo " 8) OpenDNS (Anycast: worldwide)" - echo " 9) Google (Anycast: worldwide)" - echo " 10) Yandex Basic (Russia)" - echo " 11) AdGuard DNS (Anycast: worldwide)" - echo " 12) NextDNS (Anycast: worldwide)" - echo " 13) Custom" + log_menu "" + log_prompt "What DNS resolvers do you want to use with the VPN?" + log_menu " 1) Current system resolvers (from /etc/resolv.conf)" + log_menu " 2) Self-hosted DNS Resolver (Unbound)" + log_menu " 3) Cloudflare (Anycast: worldwide)" + log_menu " 4) Quad9 (Anycast: worldwide)" + log_menu " 5) Quad9 uncensored (Anycast: worldwide)" + log_menu " 6) FDN (France)" + log_menu " 7) DNS.WATCH (Germany)" + log_menu " 8) OpenDNS (Anycast: worldwide)" + log_menu " 9) Google (Anycast: worldwide)" + log_menu " 10) Yandex Basic (Russia)" + log_menu " 11) AdGuard DNS (Anycast: worldwide)" + log_menu " 12) NextDNS (Anycast: worldwide)" + log_menu " 13) Custom" until [[ $DNS =~ ^[0-9]+$ ]] && [ "$DNS" -ge 1 ] && [ "$DNS" -le 13 ]; do read -rp "DNS [1-13]: " -e -i 11 DNS if [[ $DNS == 2 ]] && [[ -e /etc/unbound/unbound.conf ]]; then - echo "" - echo "Unbound is already installed." - echo "You can allow the script to configure it in order to use it from your OpenVPN clients" - echo "We will simply add a second server to /etc/unbound/unbound.conf for the OpenVPN subnet." - echo "No changes are made to the current configuration." - echo "" + log_menu "" + log_prompt "Unbound is already installed." + log_prompt "You can allow the script to configure it in order to use it from your OpenVPN clients" + log_prompt "We will simply add a second server to /etc/unbound/unbound.conf for the OpenVPN subnet." + log_prompt "No changes are made to the current configuration." + log_menu "" until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Apply configuration changes to Unbound? [y/n]: " -e CONTINUE @@ -414,18 +532,18 @@ function installQuestions() { done fi done - echo "" - echo "Do you want to use compression? It is not recommended since the VORACLE attack makes use of it." + log_menu "" + log_prompt "Do you want to use compression? It is not recommended since the VORACLE attack makes use of it." until [[ $COMPRESSION_ENABLED =~ (y|n) ]]; do - read -rp"Enable compression? [y/n]: " -e -i n COMPRESSION_ENABLED + read -rp "Enable compression? [y/n]: " -e -i n COMPRESSION_ENABLED done if [[ $COMPRESSION_ENABLED == "y" ]]; then - echo "Choose which compression algorithm you want to use: (they are ordered by efficiency)" - echo " 1) LZ4-v2" - echo " 2) LZ4" - echo " 3) LZ0" + log_prompt "Choose which compression algorithm you want to use: (they are ordered by efficiency)" + log_menu " 1) LZ4-v2" + log_menu " 2) LZ4" + log_menu " 3) LZ0" until [[ $COMPRESSION_CHOICE =~ ^[1-3]$ ]]; do - read -rp"Compression algorithm [1-3]: " -e -i 1 COMPRESSION_CHOICE + read -rp "Compression algorithm [1-3]: " -e -i 1 COMPRESSION_CHOICE done case $COMPRESSION_CHOICE in 1) @@ -439,12 +557,12 @@ function installQuestions() { ;; esac fi - echo "" - echo "Do you want to customize encryption settings?" - echo "Unless you know what you're doing, you should stick with the default parameters provided by the script." - echo "Note that whatever you choose, all the choices presented in the script are safe (unlike OpenVPN's defaults)." - echo "See https://github.com/angristan/openvpn-install#security-and-encryption to learn more." - echo "" + log_menu "" + log_prompt "Do you want to customize encryption settings?" + log_prompt "Unless you know what you're doing, you should stick with the default parameters provided by the script." + log_prompt "Note that whatever you choose, all the choices presented in the script are safe (unlike OpenVPN's defaults)." + log_prompt "See https://github.com/angristan/openvpn-install#security-and-encryption to learn more." + log_menu "" until [[ $CUSTOMIZE_ENC =~ (y|n) ]]; do read -rp "Customize encryption settings? [y/n]: " -e -i n CUSTOMIZE_ENC done @@ -459,14 +577,14 @@ function installQuestions() { HMAC_ALG="SHA256" TLS_SIG="1" # tls-crypt else - echo "" - echo "Choose which cipher you want to use for the data channel:" - echo " 1) AES-128-GCM (recommended)" - echo " 2) AES-192-GCM" - echo " 3) AES-256-GCM" - echo " 4) AES-128-CBC" - echo " 5) AES-192-CBC" - echo " 6) AES-256-CBC" + log_menu "" + log_prompt "Choose which cipher you want to use for the data channel:" + log_menu " 1) AES-128-GCM (recommended)" + log_menu " 2) AES-192-GCM" + log_menu " 3) AES-256-GCM" + log_menu " 4) AES-128-CBC" + log_menu " 5) AES-192-CBC" + log_menu " 6) AES-256-CBC" until [[ $CIPHER_CHOICE =~ ^[1-6]$ ]]; do read -rp "Cipher [1-6]: " -e -i 1 CIPHER_CHOICE done @@ -490,22 +608,22 @@ function installQuestions() { CIPHER="AES-256-CBC" ;; esac - echo "" - echo "Choose what kind of certificate you want to use:" - echo " 1) ECDSA (recommended)" - echo " 2) RSA" + log_menu "" + log_prompt "Choose what kind of certificate you want to use:" + log_menu " 1) ECDSA (recommended)" + log_menu " 2) RSA" until [[ $CERT_TYPE =~ ^[1-2]$ ]]; do - read -rp"Certificate key type [1-2]: " -e -i 1 CERT_TYPE + read -rp "Certificate key type [1-2]: " -e -i 1 CERT_TYPE done case $CERT_TYPE in 1) - echo "" - echo "Choose which curve you want to use for the certificate's key:" - echo " 1) prime256v1 (recommended)" - echo " 2) secp384r1" - echo " 3) secp521r1" + log_menu "" + log_prompt "Choose which curve you want to use for the certificate's key:" + log_menu " 1) prime256v1 (recommended)" + log_menu " 2) secp384r1" + log_menu " 3) secp521r1" until [[ $CERT_CURVE_CHOICE =~ ^[1-3]$ ]]; do - read -rp"Curve [1-3]: " -e -i 1 CERT_CURVE_CHOICE + read -rp "Curve [1-3]: " -e -i 1 CERT_CURVE_CHOICE done case $CERT_CURVE_CHOICE in 1) @@ -520,11 +638,11 @@ function installQuestions() { esac ;; 2) - echo "" - echo "Choose which size you want to use for the certificate's RSA key:" - echo " 1) 2048 bits (recommended)" - echo " 2) 3072 bits" - echo " 3) 4096 bits" + log_menu "" + log_prompt "Choose which size you want to use for the certificate's RSA key:" + log_menu " 1) 2048 bits (recommended)" + log_menu " 2) 3072 bits" + log_menu " 3) 4096 bits" until [[ $RSA_KEY_SIZE_CHOICE =~ ^[1-3]$ ]]; do read -rp "RSA key size [1-3]: " -e -i 1 RSA_KEY_SIZE_CHOICE done @@ -541,14 +659,14 @@ function installQuestions() { esac ;; esac - echo "" - echo "Choose which cipher you want to use for the control channel:" + log_menu "" + log_prompt "Choose which cipher you want to use for the control channel:" case $CERT_TYPE in 1) - echo " 1) ECDHE-ECDSA-AES-128-GCM-SHA256 (recommended)" - echo " 2) ECDHE-ECDSA-AES-256-GCM-SHA384" + log_menu " 1) ECDHE-ECDSA-AES-128-GCM-SHA256 (recommended)" + log_menu " 2) ECDHE-ECDSA-AES-256-GCM-SHA384" until [[ $CC_CIPHER_CHOICE =~ ^[1-2]$ ]]; do - read -rp"Control channel cipher [1-2]: " -e -i 1 CC_CIPHER_CHOICE + read -rp "Control channel cipher [1-2]: " -e -i 1 CC_CIPHER_CHOICE done case $CC_CIPHER_CHOICE in 1) @@ -560,10 +678,10 @@ function installQuestions() { esac ;; 2) - echo " 1) ECDHE-RSA-AES-128-GCM-SHA256 (recommended)" - echo " 2) ECDHE-RSA-AES-256-GCM-SHA384" + log_menu " 1) ECDHE-RSA-AES-128-GCM-SHA256 (recommended)" + log_menu " 2) ECDHE-RSA-AES-256-GCM-SHA384" until [[ $CC_CIPHER_CHOICE =~ ^[1-2]$ ]]; do - read -rp"Control channel cipher [1-2]: " -e -i 1 CC_CIPHER_CHOICE + read -rp "Control channel cipher [1-2]: " -e -i 1 CC_CIPHER_CHOICE done case $CC_CIPHER_CHOICE in 1) @@ -575,22 +693,22 @@ function installQuestions() { esac ;; esac - echo "" - echo "Choose what kind of Diffie-Hellman key you want to use:" - echo " 1) ECDH (recommended)" - echo " 2) DH" + log_menu "" + log_prompt "Choose what kind of Diffie-Hellman key you want to use:" + log_menu " 1) ECDH (recommended)" + log_menu " 2) DH" until [[ $DH_TYPE =~ [1-2] ]]; do - read -rp"DH key type [1-2]: " -e -i 1 DH_TYPE + read -rp "DH key type [1-2]: " -e -i 1 DH_TYPE done case $DH_TYPE in 1) - echo "" - echo "Choose which curve you want to use for the ECDH key:" - echo " 1) prime256v1 (recommended)" - echo " 2) secp384r1" - echo " 3) secp521r1" + log_menu "" + log_prompt "Choose which curve you want to use for the ECDH key:" + log_menu " 1) prime256v1 (recommended)" + log_menu " 2) secp384r1" + log_menu " 3) secp521r1" while [[ $DH_CURVE_CHOICE != "1" && $DH_CURVE_CHOICE != "2" && $DH_CURVE_CHOICE != "3" ]]; do - read -rp"Curve [1-3]: " -e -i 1 DH_CURVE_CHOICE + read -rp "Curve [1-3]: " -e -i 1 DH_CURVE_CHOICE done case $DH_CURVE_CHOICE in 1) @@ -605,11 +723,11 @@ function installQuestions() { esac ;; 2) - echo "" - echo "Choose what size of Diffie-Hellman key you want to use:" - echo " 1) 2048 bits (recommended)" - echo " 2) 3072 bits" - echo " 3) 4096 bits" + log_menu "" + log_prompt "Choose what size of Diffie-Hellman key you want to use:" + log_menu " 1) 2048 bits (recommended)" + log_menu " 2) 3072 bits" + log_menu " 3) 4096 bits" until [[ $DH_KEY_SIZE_CHOICE =~ ^[1-3]$ ]]; do read -rp "DH key size [1-3]: " -e -i 1 DH_KEY_SIZE_CHOICE done @@ -626,17 +744,17 @@ function installQuestions() { esac ;; esac - echo "" + log_menu "" # The "auth" options behaves differently with AEAD ciphers if [[ $CIPHER =~ CBC$ ]]; then - echo "The digest algorithm authenticates data channel packets and tls-auth packets from the control channel." + log_prompt "The digest algorithm authenticates data channel packets and tls-auth packets from the control channel." elif [[ $CIPHER =~ GCM$ ]]; then - echo "The digest algorithm authenticates tls-auth packets from the control channel." + log_prompt "The digest algorithm authenticates tls-auth packets from the control channel." fi - echo "Which digest algorithm do you want to use for HMAC?" - echo " 1) SHA-256 (recommended)" - echo " 2) SHA-384" - echo " 3) SHA-512" + log_prompt "Which digest algorithm do you want to use for HMAC?" + log_menu " 1) SHA-256 (recommended)" + log_menu " 2) SHA-384" + log_menu " 3) SHA-512" until [[ $HMAC_ALG_CHOICE =~ ^[1-3]$ ]]; do read -rp "Digest algorithm [1-3]: " -e -i 1 HMAC_ALG_CHOICE done @@ -651,18 +769,18 @@ function installQuestions() { HMAC_ALG="SHA512" ;; esac - echo "" - echo "You can add an additional layer of security to the control channel with tls-auth and tls-crypt" - echo "tls-auth authenticates the packets, while tls-crypt authenticate and encrypt them." - echo " 1) tls-crypt (recommended)" - echo " 2) tls-auth" + log_menu "" + log_prompt "You can add an additional layer of security to the control channel with tls-auth and tls-crypt" + log_prompt "tls-auth authenticates the packets, while tls-crypt authenticate and encrypt them." + log_menu " 1) tls-crypt (recommended)" + log_menu " 2) tls-auth" until [[ $TLS_SIG =~ [1-2] ]]; do read -rp "Control channel additional security mechanism [1-2]: " -e -i 1 TLS_SIG done fi - echo "" - echo "Okay, that was all I needed. We are ready to setup your OpenVPN server now." - echo "You will be able to generate a client at the end of the installation." + log_menu "" + log_prompt "Okay, that was all I needed. We are ready to setup your OpenVPN server now." + log_prompt "You will be able to generate a client at the end of the installation." APPROVE_INSTALL=${APPROVE_INSTALL:-n} if [[ $APPROVE_INSTALL =~ n ]]; then read -n1 -r -p "Press any key to continue..." @@ -687,6 +805,19 @@ function installOpenVPN() { if [[ -z $ENDPOINT ]]; then ENDPOINT=$(resolvePublicIP) fi + + # Log auto-install mode and parameters + log_info "=== OpenVPN Auto-Install ===" + log_info "Running in auto-install mode with the following settings:" + log_info " ENDPOINT=$ENDPOINT" + log_info " IPV6_SUPPORT=$IPV6_SUPPORT" + log_info " PORT_CHOICE=$PORT_CHOICE" + log_info " PROTOCOL_CHOICE=$PROTOCOL_CHOICE" + log_info " DNS=$DNS" + log_info " COMPRESSION_ENABLED=$COMPRESSION_ENABLED" + log_info " CUSTOMIZE_ENC=$CUSTOMIZE_ENC" + log_info " CLIENT=$CLIENT" + log_info " PASS=$PASS" fi # Run setup questions first, and set other variables if auto-install @@ -700,9 +831,8 @@ function installOpenVPN() { # $NIC can not be empty for script rm-openvpn-rules.sh if [[ -z $NIC ]]; then - echo - echo "Could not detect public interface." - echo "This needs for setup MASQUERADE." + log_warn "Could not detect public interface." + log_info "This needs for setup MASQUERADE." until [[ $CONTINUE =~ (y|n) ]]; do read -rp "Continue? [y/n]: " -e CONTINUE done @@ -715,37 +845,49 @@ function installOpenVPN() { # idempotent on multiple runs, but will only install OpenVPN from upstream # the first time. if [[ ! -e /etc/openvpn/server.conf ]]; then + log_header "Installing OpenVPN" if [[ $OS =~ (debian|ubuntu) ]]; then - apt-get update - apt-get -y install ca-certificates gnupg + log_info "Updating package lists..." + run_cmd "Update package lists" apt-get update + run_cmd "Installing prerequisites" apt-get -y install ca-certificates gnupg # Ubuntu >= 18.04 and Debian >= 11 have OpenVPN >= 2.4 without the need of a third party repository. - apt-get install -y openvpn iptables openssl wget ca-certificates curl + log_info "Installing OpenVPN and dependencies..." + run_cmd "Installing OpenVPN" apt-get install -y openvpn iptables openssl wget ca-certificates curl elif [[ $OS == 'centos' ]]; then - yum install -y epel-release - yum install -y openvpn iptables openssl wget ca-certificates curl tar 'policycoreutils-python*' + log_info "Installing EPEL repository..." + run_cmd "Installing EPEL" yum install -y epel-release + log_info "Installing OpenVPN and dependencies..." + run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl tar 'policycoreutils-python*' elif [[ $OS == 'oracle' ]]; then + log_info "Installing EPEL repository..." if [[ $VERSION_ID =~ ^8 ]]; then - yum install -y oracle-epel-release-el8 - yum-config-manager --enable ol8_developer_EPEL + run_cmd "Installing Oracle EPEL" yum install -y oracle-epel-release-el8 + run_cmd "Enabling EPEL repository" yum-config-manager --enable ol8_developer_EPEL elif [[ $VERSION_ID =~ ^9 ]]; then - yum install -y oracle-epel-release-el9 - yum-config-manager --enable ol9_developer_EPEL + run_cmd "Installing Oracle EPEL" yum install -y oracle-epel-release-el9 + run_cmd "Enabling EPEL repository" yum-config-manager --enable ol9_developer_EPEL fi - yum install -y openvpn iptables openssl wget ca-certificates curl tar policycoreutils-python-utils + log_info "Installing OpenVPN and dependencies..." + run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl tar policycoreutils-python-utils elif [[ $OS == 'amzn' ]]; then - amazon-linux-extras install -y epel - yum install -y openvpn iptables openssl wget ca-certificates curl + log_info "Installing EPEL repository..." + run_cmd "Installing EPEL" amazon-linux-extras install -y epel + log_info "Installing OpenVPN and dependencies..." + run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl elif [[ $OS == 'amzn2023' ]]; then - dnf install -y openvpn iptables openssl wget ca-certificates + log_info "Installing OpenVPN and dependencies..." + run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl wget ca-certificates elif [[ $OS == 'fedora' ]]; then - dnf install -y openvpn iptables openssl wget ca-certificates curl policycoreutils-python-utils + log_info "Installing OpenVPN and dependencies..." + run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl wget ca-certificates curl policycoreutils-python-utils elif [[ $OS == 'arch' ]]; then # Install required dependencies and upgrade the system - pacman --needed --noconfirm -Syu openvpn iptables openssl wget ca-certificates curl + log_info "Installing OpenVPN and dependencies..." + run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl wget ca-certificates curl fi # An old version of easy-rsa was available by default in some openvpn packages if [[ -d /etc/openvpn/easy-rsa/ ]]; then - rm -rf /etc/openvpn/easy-rsa/ + run_cmd "Removing old Easy-RSA" rm -rf /etc/openvpn/easy-rsa/ fi fi @@ -758,15 +900,17 @@ function installOpenVPN() { # Install the latest version of easy-rsa from source, if not already installed. if [[ ! -d /etc/openvpn/easy-rsa/ ]]; then - wget -O ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" - if ! echo "${EASYRSA_SHA256} $HOME/easy-rsa.tgz" | sha256sum -c; then - echo "SHA256 checksum verification failed for easy-rsa download!" - rm -f ~/easy-rsa.tgz - exit 1 - fi - mkdir -p /etc/openvpn/easy-rsa - tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/easy-rsa - rm -f ~/easy-rsa.tgz + run_cmd "Downloading Easy-RSA v${EASYRSA_VERSION}" wget -O ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" + log_info "Verifying Easy-RSA checksum..." + CHECKSUM_OUTPUT=$(echo "${EASYRSA_SHA256} $HOME/easy-rsa.tgz" | sha256sum -c 2>&1) || { + _log_to_file "[CHECKSUM] $CHECKSUM_OUTPUT" + run_cmd "Cleaning up failed download" rm -f ~/easy-rsa.tgz + log_fatal "SHA256 checksum verification failed for easy-rsa download!" + } + _log_to_file "[CHECKSUM] $CHECKSUM_OUTPUT" + run_cmd "Creating Easy-RSA directory" mkdir -p /etc/openvpn/easy-rsa + run_cmd "Extracting Easy-RSA" tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/easy-rsa + run_cmd "Cleaning up archive" rm -f ~/easy-rsa.tgz cd /etc/openvpn/easy-rsa/ || return case $CERT_TYPE in @@ -786,25 +930,32 @@ function installOpenVPN() { echo "$SERVER_NAME" >SERVER_NAME_GENERATED # Create the PKI, set up the CA, the DH params and the server certificate - ./easyrsa init-pki - EASYRSA_CA_EXPIRE=$CERT_VALIDITY_DAYS ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass + log_info "Initializing PKI..." + run_cmd "Initializing PKI" ./easyrsa init-pki + export EASYRSA_CA_EXPIRE=$CERT_VALIDITY_DAYS + log_info "Building CA..." + run_cmd "Building CA" ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass if [[ $DH_TYPE == "2" ]]; then # ECDH keys are generated on-the-fly so we don't need to generate them beforehand - openssl dhparam -out dh.pem "$DH_KEY_SIZE" + run_cmd "Generating DH parameters (this may take a while)" openssl dhparam -out dh.pem "$DH_KEY_SIZE" fi - EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS ./easyrsa --batch build-server-full "$SERVER_NAME" nopass - EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS ./easyrsa gen-crl + export EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS + log_info "Building server certificate..." + run_cmd "Building server certificate" ./easyrsa --batch build-server-full "$SERVER_NAME" nopass + export EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS + run_cmd "Generating CRL" ./easyrsa gen-crl + log_info "Generating TLS key..." case $TLS_SIG in 1) # Generate tls-crypt key - openvpn --genkey --secret /etc/openvpn/tls-crypt.key + run_cmd "Generating tls-crypt key" openvpn --genkey --secret /etc/openvpn/tls-crypt.key ;; 2) # Generate tls-auth key - openvpn --genkey --secret /etc/openvpn/tls-auth.key + run_cmd "Generating tls-auth key" openvpn --genkey --secret /etc/openvpn/tls-auth.key ;; esac else @@ -815,15 +966,17 @@ function installOpenVPN() { fi # Move all the generated files - cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn + log_info "Copying certificates..." + run_cmd "Copying certificates to /etc/openvpn" cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn if [[ $DH_TYPE == "2" ]]; then - cp dh.pem /etc/openvpn + run_cmd "Copying DH parameters" cp dh.pem /etc/openvpn fi # Make cert revocation list readable for non-root - chmod 644 /etc/openvpn/crl.pem + run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/crl.pem # Generate server.conf + log_info "Generating server configuration..." echo "port $PORT" >/etc/openvpn/server.conf if [[ $IPV6_SUPPORT == 'n' ]]; then echo "proto $PROTOCOL" >>/etc/openvpn/server.conf @@ -958,52 +1111,55 @@ status /var/log/openvpn/status.log verb 3" >>/etc/openvpn/server.conf # Create client-config-dir dir - mkdir -p /etc/openvpn/ccd + run_cmd "Creating client config directory" mkdir -p /etc/openvpn/ccd # Create log dir - mkdir -p /var/log/openvpn + run_cmd "Creating log directory" mkdir -p /var/log/openvpn # Enable routing + log_info "Enabling IP forwarding..." + run_cmd "Creating sysctl.d directory" mkdir -p /etc/sysctl.d echo 'net.ipv4.ip_forward=1' >/etc/sysctl.d/99-openvpn.conf if [[ $IPV6_SUPPORT == 'y' ]]; then echo 'net.ipv6.conf.all.forwarding=1' >>/etc/sysctl.d/99-openvpn.conf fi # Apply sysctl rules - sysctl --system + run_cmd "Applying sysctl rules" sysctl --system # If SELinux is enabled and a custom port was selected, we need this if hash sestatus 2>/dev/null; then if sestatus | grep "Current mode" | grep -qs "enforcing"; then if [[ $PORT != '1194' ]]; then - semanage port -a -t openvpn_port_t -p "$PROTOCOL" "$PORT" + run_cmd "Configuring SELinux port" semanage port -a -t openvpn_port_t -p "$PROTOCOL" "$PORT" fi fi fi # Finally, restart and enable OpenVPN + log_info "Configuring OpenVPN service..." if [[ $OS == 'arch' || $OS == 'fedora' || $OS == 'centos' || $OS == 'oracle' || $OS == 'amzn2023' ]]; then # Don't modify package-provided service - cp /usr/lib/systemd/system/openvpn-server@.service /etc/systemd/system/openvpn-server@.service + run_cmd "Copying OpenVPN service file" cp /usr/lib/systemd/system/openvpn-server@.service /etc/systemd/system/openvpn-server@.service # Workaround to fix OpenVPN service on OpenVZ - sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn-server@.service + run_cmd "Patching service file (LimitNPROC)" sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn-server@.service # Another workaround to keep using /etc/openvpn/ - sed -i 's|/etc/openvpn/server|/etc/openvpn|' /etc/systemd/system/openvpn-server@.service + run_cmd "Patching service file (paths)" sed -i 's|/etc/openvpn/server|/etc/openvpn|' /etc/systemd/system/openvpn-server@.service - systemctl daemon-reload - systemctl enable openvpn-server@server - systemctl restart openvpn-server@server + run_cmd "Reloading systemd" systemctl daemon-reload + run_cmd "Enabling OpenVPN service" systemctl enable openvpn-server@server + run_cmd "Starting OpenVPN service" systemctl restart openvpn-server@server else # Don't modify package-provided service - cp /lib/systemd/system/openvpn\@.service /etc/systemd/system/openvpn\@.service + run_cmd "Copying OpenVPN service file" cp /lib/systemd/system/openvpn\@.service /etc/systemd/system/openvpn\@.service # Workaround to fix OpenVPN service on OpenVZ - sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn\@.service + run_cmd "Patching service file (LimitNPROC)" sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn\@.service # Another workaround to keep using /etc/openvpn/ - sed -i 's|/etc/openvpn/server|/etc/openvpn|' /etc/systemd/system/openvpn\@.service + run_cmd "Patching service file (paths)" sed -i 's|/etc/openvpn/server|/etc/openvpn|' /etc/systemd/system/openvpn\@.service - systemctl daemon-reload - systemctl enable openvpn@server - systemctl restart openvpn@server + run_cmd "Reloading systemd" systemctl daemon-reload + run_cmd "Enabling OpenVPN service" systemctl enable openvpn@server + run_cmd "Starting OpenVPN service" systemctl restart openvpn@server fi if [[ $DNS == 2 ]]; then @@ -1011,7 +1167,8 @@ verb 3" >>/etc/openvpn/server.conf fi # Add iptables rules in two scripts - mkdir -p /etc/iptables + log_info "Configuring firewall rules..." + run_cmd "Creating iptables directory" mkdir -p /etc/iptables # Script to add rules echo "#!/bin/sh @@ -1045,8 +1202,8 @@ 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 fi - chmod +x /etc/iptables/add-openvpn-rules.sh - chmod +x /etc/iptables/rm-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 # Handle the rules via a systemd script echo "[Unit] @@ -1064,9 +1221,9 @@ RemainAfterExit=yes WantedBy=multi-user.target" >/etc/systemd/system/iptables-openvpn.service # Enable service and apply rules - systemctl daemon-reload - systemctl enable iptables-openvpn - systemctl start iptables-openvpn + run_cmd "Reloading systemd" systemctl daemon-reload + run_cmd "Enabling iptables service" systemctl enable iptables-openvpn + run_cmd "Starting iptables service" systemctl start iptables-openvpn # If the server is behind a NAT, use the correct IP address for the clients to connect to if [[ $ENDPOINT != "" ]]; then @@ -1074,6 +1231,7 @@ WantedBy=multi-user.target" >/etc/systemd/system/iptables-openvpn.service fi # client-template.txt is created so we have a template to add further users later + log_info "Creating client template..." echo "client" >/etc/openvpn/client-template.txt if [[ $PROTOCOL == 'udp' ]]; then echo "proto udp" >>/etc/openvpn/client-template.txt @@ -1104,24 +1262,25 @@ verb 3" >>/etc/openvpn/client-template.txt fi # Generate the custom client.ovpn + log_info "Generating first client certificate..." newClient - echo "If you want to add more clients, you simply need to run this script another time!" + log_success "If you want to add more clients, you simply need to run this script another time!" } function newClient() { - echo "" - echo "Tell me a name for the client." - echo "The name must consist of alphanumeric character. It may also include an underscore or a dash." + log_header "New Client Setup" + log_prompt "Tell me a name for the client." + log_prompt "The name must consist of alphanumeric character. It may also include an underscore or a dash." until [[ $CLIENT =~ ^[a-zA-Z0-9_-]+$ ]]; do read -rp "Client name: " -e CLIENT done - echo "" - echo "Do you want to protect the configuration file with a password?" - echo "(e.g. encrypt the private key with a password)" - echo " 1) Add a passwordless client" - echo " 2) Use a password for the client" + log_menu "" + log_prompt "Do you want to protect the configuration file with a password?" + log_prompt "(e.g. encrypt the private key with a password)" + log_menu " 1) Add a passwordless client" + log_menu " 2) Use a password for the client" until [[ $PASS =~ ^[1-2]$ ]]; do read -rp "Select an option [1-2]: " -e -i 1 PASS @@ -1129,21 +1288,22 @@ function newClient() { CLIENTEXISTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c -E "/CN=$CLIENT\$") if [[ $CLIENTEXISTS == '1' ]]; then - echo "" - echo "The specified client CN was already found in easy-rsa, please choose another name." + log_error "The specified client CN was already found in easy-rsa, please choose another name." exit else cd /etc/openvpn/easy-rsa/ || return + log_info "Generating client certificate..." + export EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS case $PASS in 1) - EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS ./easyrsa --batch build-client-full "$CLIENT" nopass + run_cmd "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass ;; 2) - echo "⚠️ You will be asked for the client password below ⚠️" - EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS ./easyrsa --batch build-client-full "$CLIENT" + log_warn "You will be asked for the client password below" + ./easyrsa --batch build-client-full "$CLIENT" ;; esac - echo "Client $CLIENT added." + log_success "Client $CLIENT added." fi # Home directory of the user, where the client configuration will be written @@ -1171,7 +1331,7 @@ function newClient() { fi # Generates the custom client.ovpn - cp /etc/openvpn/client-template.txt "$homeDir/$CLIENT.ovpn" + run_cmd "Creating client config" cp /etc/openvpn/client-template.txt "$homeDir/$CLIENT.ovpn" { echo "" cat "/etc/openvpn/easy-rsa/pki/ca.crt" @@ -1200,9 +1360,9 @@ function newClient() { esac } >>"$homeDir/$CLIENT.ovpn" - echo "" - echo "The configuration file has been written to $homeDir/$CLIENT.ovpn." - echo "Download the .ovpn file and import it in your OpenVPN client." + log_menu "" + log_success "The configuration file has been written to $homeDir/$CLIENT.ovpn." + log_info "Download the .ovpn file and import it in your OpenVPN client." exit 0 } @@ -1210,13 +1370,11 @@ function newClient() { function revokeClient() { NUMBEROFCLIENTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^V") if [[ $NUMBEROFCLIENTS == '0' ]]; then - echo "" - echo "You have no existing clients!" - exit 1 + log_fatal "You have no existing clients!" fi - echo "" - echo "Select the existing client certificate you want to revoke" + log_header "Revoke Client" + log_prompt "Select the existing client certificate you want to revoke" tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' until [[ $CLIENTNUMBER -ge 1 && $CLIENTNUMBER -le $NUMBEROFCLIENTS ]]; do if [[ $CLIENTNUMBER == '1' ]]; then @@ -1227,58 +1385,57 @@ function revokeClient() { done CLIENT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p) cd /etc/openvpn/easy-rsa/ || return - ./easyrsa --batch revoke "$CLIENT" - EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS ./easyrsa gen-crl - rm -f /etc/openvpn/crl.pem - cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem - chmod 644 /etc/openvpn/crl.pem - find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete - rm -f "/root/$CLIENT.ovpn" - sed -i "/^$CLIENT,.*/d" /etc/openvpn/ipp.txt - cp /etc/openvpn/easy-rsa/pki/index.txt{,.bk} + log_info "Revoking certificate for $CLIENT..." + run_cmd "Revoking certificate" ./easyrsa --batch revoke "$CLIENT" + export EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS + run_cmd "Regenerating CRL" ./easyrsa gen-crl + run_cmd "Removing old CRL" rm -f /etc/openvpn/crl.pem + run_cmd "Copying new CRL" cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem + run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/crl.pem + run_cmd "Removing client config from /home" find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete + run_cmd "Removing client config from /root" rm -f "/root/$CLIENT.ovpn" + run_cmd "Removing IP assignment" sed -i "/^$CLIENT,.*/d" /etc/openvpn/ipp.txt + run_cmd "Backing up index" cp /etc/openvpn/easy-rsa/pki/index.txt{,.bk} - echo "" - echo "Certificate for client $CLIENT revoked." + log_success "Certificate for client $CLIENT revoked." } function removeUnbound() { # Remove OpenVPN-related config - sed -i '/include: \/etc\/unbound\/openvpn.conf/d' /etc/unbound/unbound.conf - rm /etc/unbound/openvpn.conf + run_cmd "Removing Unbound include" sed -i '/include: \/etc\/unbound\/openvpn.conf/d' /etc/unbound/unbound.conf + run_cmd "Removing OpenVPN Unbound config" rm /etc/unbound/openvpn.conf until [[ $REMOVE_UNBOUND =~ (y|n) ]]; do - echo "" - echo "If you were already using Unbound before installing OpenVPN, I removed the configuration related to OpenVPN." + log_info "If you were already using Unbound before installing OpenVPN, I removed the configuration related to OpenVPN." read -rp "Do you want to completely remove Unbound? [y/n]: " -e REMOVE_UNBOUND done if [[ $REMOVE_UNBOUND == 'y' ]]; then + log_info "Removing Unbound..." # Stop Unbound - systemctl stop unbound + run_cmd "Stopping Unbound" systemctl stop unbound if [[ $OS =~ (debian|ubuntu) ]]; then - apt-get remove --purge -y unbound + run_cmd "Removing Unbound" apt-get remove --purge -y unbound elif [[ $OS == 'arch' ]]; then - pacman --noconfirm -R unbound + run_cmd "Removing Unbound" pacman --noconfirm -R unbound elif [[ $OS =~ (centos|amzn|oracle) ]]; then - yum remove -y unbound + run_cmd "Removing Unbound" yum remove -y unbound elif [[ $OS == 'fedora' ]]; then - dnf remove -y unbound + run_cmd "Removing Unbound" dnf remove -y unbound fi - rm -rf /etc/unbound/ + run_cmd "Removing Unbound config" rm -rf /etc/unbound/ - echo "" - echo "Unbound removed!" + log_success "Unbound removed!" else - systemctl restart unbound - echo "" - echo "Unbound wasn't removed." + run_cmd "Restarting Unbound" systemctl restart unbound + log_info "Unbound wasn't removed." fi } function removeOpenVPN() { - echo "" + log_header "Remove OpenVPN" read -rp "Do you really want to remove OpenVPN? [y/n]: " -e -i n REMOVE if [[ $REMOVE == 'y' ]]; then # Get OpenVPN port from the configuration @@ -1286,81 +1443,81 @@ function removeOpenVPN() { PROTOCOL=$(grep '^proto ' /etc/openvpn/server.conf | cut -d " " -f 2) # Stop OpenVPN + log_info "Stopping OpenVPN service..." if [[ $OS =~ (fedora|arch|centos|oracle) ]]; then - systemctl disable openvpn-server@server - systemctl stop openvpn-server@server + run_cmd "Disabling OpenVPN service" systemctl disable openvpn-server@server + run_cmd "Stopping OpenVPN service" systemctl stop openvpn-server@server # Remove customised service - rm /etc/systemd/system/openvpn-server@.service + run_cmd "Removing service file" rm /etc/systemd/system/openvpn-server@.service else - systemctl disable openvpn@server - systemctl stop openvpn@server + run_cmd "Disabling OpenVPN service" systemctl disable openvpn@server + run_cmd "Stopping OpenVPN service" systemctl stop openvpn@server # Remove customised service - rm /etc/systemd/system/openvpn\@.service + run_cmd "Removing service file" rm /etc/systemd/system/openvpn\@.service fi # Remove the iptables rules related to the script - systemctl stop iptables-openvpn + log_info "Removing iptables rules..." + run_cmd "Stopping iptables service" systemctl stop iptables-openvpn # Cleanup - systemctl disable iptables-openvpn - rm /etc/systemd/system/iptables-openvpn.service - systemctl daemon-reload - rm /etc/iptables/add-openvpn-rules.sh - rm /etc/iptables/rm-openvpn-rules.sh + 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 /etc/iptables/add-openvpn-rules.sh + run_cmd "Removing iptables rm script" rm /etc/iptables/rm-openvpn-rules.sh # SELinux if hash sestatus 2>/dev/null; then if sestatus | grep "Current mode" | grep -qs "enforcing"; then if [[ $PORT != '1194' ]]; then - semanage port -d -t openvpn_port_t -p "$PROTOCOL" "$PORT" + run_cmd "Removing SELinux port" semanage port -d -t openvpn_port_t -p "$PROTOCOL" "$PORT" fi fi fi + log_info "Removing OpenVPN package..." if [[ $OS =~ (debian|ubuntu) ]]; then - apt-get remove --purge -y openvpn + run_cmd "Removing OpenVPN" apt-get remove --purge -y openvpn if [[ -e /etc/apt/sources.list.d/openvpn.list ]]; then - rm /etc/apt/sources.list.d/openvpn.list - apt-get update + run_cmd "Removing OpenVPN repo" rm /etc/apt/sources.list.d/openvpn.list + run_cmd "Updating package lists" apt-get update fi elif [[ $OS == 'arch' ]]; then - pacman --noconfirm -R openvpn + run_cmd "Removing OpenVPN" pacman --noconfirm -R openvpn elif [[ $OS =~ (centos|amzn|oracle) ]]; then - yum remove -y openvpn + run_cmd "Removing OpenVPN" yum remove -y openvpn elif [[ $OS == 'fedora' ]]; then - dnf remove -y openvpn + run_cmd "Removing OpenVPN" dnf remove -y openvpn fi # Cleanup - find /home/ -maxdepth 2 -name "*.ovpn" -delete - find /root/ -maxdepth 1 -name "*.ovpn" -delete - rm -rf /etc/openvpn - rm -rf /usr/share/doc/openvpn* - rm -f /etc/sysctl.d/99-openvpn.conf - rm -rf /var/log/openvpn + run_cmd "Removing client configs from /home" find /home/ -maxdepth 2 -name "*.ovpn" -delete + run_cmd "Removing client configs from /root" find /root/ -maxdepth 1 -name "*.ovpn" -delete + run_cmd "Removing /etc/openvpn" rm -rf /etc/openvpn + run_cmd "Removing OpenVPN docs" rm -rf /usr/share/doc/openvpn* + run_cmd "Removing sysctl config" rm -f /etc/sysctl.d/99-openvpn.conf + run_cmd "Removing OpenVPN logs" rm -rf /var/log/openvpn # Unbound if [[ -e /etc/unbound/openvpn.conf ]]; then removeUnbound fi - echo "" - echo "OpenVPN removed!" + log_success "OpenVPN removed!" else - echo "" - echo "Removal aborted!" + log_info "Removal aborted!" fi } function manageMenu() { - echo "Welcome to OpenVPN-install!" - echo "The git repository is available at: https://github.com/angristan/openvpn-install" - echo "" - echo "It looks like OpenVPN is already installed." - echo "" - echo "What do you want to do?" - echo " 1) Add a new user" - echo " 2) Revoke existing user" - echo " 3) Remove OpenVPN" - echo " 4) Exit" + log_header "OpenVPN Management" + log_prompt "The git repository is available at: https://github.com/angristan/openvpn-install" + log_success "OpenVPN is already installed." + log_menu "" + log_prompt "What do you want to do?" + log_menu " 1) Add a new user" + log_menu " 2) Revoke existing user" + log_menu " 3) Remove OpenVPN" + log_menu " 4) Exit" until [[ $MENU_OPTION =~ ^[1-4]$ ]]; do read -rp "Select an option [1-4]: " MENU_OPTION done diff --git a/test/Dockerfile.server b/test/Dockerfile.server index c79baef..acf6f0f 100644 --- a/test/Dockerfile.server +++ b/test/Dockerfile.server @@ -35,7 +35,8 @@ RUN chmod +x /opt/openvpn-install.sh # Copy test scripts COPY test/server-entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +COPY test/validate-output.sh /opt/test/validate-output.sh +RUN chmod +x /entrypoint.sh /opt/test/validate-output.sh WORKDIR /opt diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 5661e3f..54e56ff 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -14,6 +14,7 @@ echo "TUN device ready" # Set up environment for auto-install export AUTO_INSTALL=y +export FORCE_COLOR=1 export APPROVE_INSTALL=y export APPROVE_IP=y export IPV6_SUPPORT=n @@ -34,11 +35,24 @@ chmod +x /tmp/openvpn-install.sh echo "Running OpenVPN install script..." # Run in subshell because the script calls 'exit 0' after generating client config +# Capture output to validate logging format, while still displaying it # Use || true to prevent set -e from exiting on failure, then check exit code -(bash -x /tmp/openvpn-install.sh) && INSTALL_EXIT_CODE=0 || INSTALL_EXIT_CODE=$? +INSTALL_OUTPUT="/tmp/install-output.log" +(bash /tmp/openvpn-install.sh) 2>&1 | tee "$INSTALL_OUTPUT" +INSTALL_EXIT_CODE=${PIPESTATUS[0]} echo "=== Installation complete (exit code: $INSTALL_EXIT_CODE) ===" +# Validate that all output uses proper logging format (ANSI color codes) +echo "Validating output format..." +if /opt/test/validate-output.sh "$INSTALL_OUTPUT"; then + echo "PASS: All script output uses proper log formatting" +else + echo "FAIL: Script output contains unformatted lines" + echo "This indicates echo statements that should use log_* functions" + exit 1 +fi + if [ "$INSTALL_EXIT_CODE" -ne 0 ]; then echo "ERROR: Install script failed with exit code $INSTALL_EXIT_CODE" exit 1 diff --git a/test/validate-output.sh b/test/validate-output.sh new file mode 100755 index 0000000..88c8bbd --- /dev/null +++ b/test/validate-output.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Validates that script output only contains properly formatted log messages +# All output from openvpn-install.sh should use logging functions +# +# Usage: ./validate-output.sh +# Or pipe: some_command | ./validate-output.sh + +set -euo pipefail + +INPUT_FILE="${1:-/dev/stdin}" + +# Valid output patterns: +# - Lines starting with ANSI escape codes (colored output) +# - Lines starting with our log prefixes (non-TTY mode) +# - Lines starting with > (command echo from run_cmd) +# - Empty lines + +# ANSI escape code pattern +ANSI_PATTERN=$'^\033\\[' + +# Log prefix patterns (for non-TTY mode where colors are disabled) +# These match: [INFO], [WARN], [ERROR], [OK], [DEBUG], or > (command line) +LOG_PREFIXES='^(\[INFO\]|\[WARN\]|\[ERROR\]|\[OK\]|\[DEBUG\]|> )' + +# Count issues +INVALID_LINES=0 +TOTAL_LINES=0 +LINE_NUM=0 + +echo "Validating script output for unformatted lines..." +echo "" + +while IFS= read -r line || [[ -n "$line" ]]; do + LINE_NUM=$((LINE_NUM + 1)) + + # Skip empty lines + if [[ -z "$line" ]]; then + continue + fi + + TOTAL_LINES=$((TOTAL_LINES + 1)) + + # Check if line starts with ANSI escape code (colored output from log functions) + if [[ "$line" =~ $ANSI_PATTERN ]]; then + continue + fi + + # Check if line starts with our log prefixes (non-TTY mode) + if [[ "$line" =~ $LOG_PREFIXES ]]; then + continue + fi + + # If we get here, the line doesn't match expected patterns - it's raw output + INVALID_LINES=$((INVALID_LINES + 1)) + # Truncate long lines for display + if [[ ${#line} -gt 100 ]]; then + DISPLAY_LINE="${line:0:100}..." + else + DISPLAY_LINE="$line" + fi + echo " [LEAK] Line $LINE_NUM: $DISPLAY_LINE" + +done <"$INPUT_FILE" + +echo "" +echo "----------------------------------------" +echo "Total lines checked: $TOTAL_LINES" +echo "Invalid lines found: $INVALID_LINES" + +if [[ $INVALID_LINES -gt 0 ]]; then + echo "" + echo "ERROR: Found $INVALID_LINES line(s) without proper log formatting." + echo "" + echo "All user-visible output should use log_* functions:" + echo " - log_info 'message' -> [INFO] message" + echo " - log_warn 'message' -> [WARN] message" + echo " - log_error 'message' -> [ERROR] message" + echo " - log_success 'message' -> [OK] message" + echo " - run_cmd 'desc' cmd -> > cmd" + echo "" + echo "Raw echo statements or command output should not leak to stdout." + exit 1 +fi + +echo "" +echo "All output is properly formatted!" +exit 0 From 8a133b7bedc6350bb5ff69775bd90a97a70ea059 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Tue, 9 Dec 2025 18:06:53 +0100 Subject: [PATCH 091/141] ci: run Docker e2e tests on pull requests --- .github/workflows/docker-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 7a16cf1..80df170 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -1,6 +1,7 @@ --- on: push: + pull_request: workflow_dispatch: name: Docker Test From 9e439b60ad0e2601d930da08dea0397317325601 Mon Sep 17 00:00:00 2001 From: Shahzain Ali Date: Tue, 9 Dec 2025 22:12:23 +0500 Subject: [PATCH 092/141] Add option to allow multiple devices per client profile (duplicate-cn) (#1278) Added duplicate-cn for connecting multiple clients using same .ovpn --------- Co-authored-by: Stanislas Lange --- openvpn-install.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index a407287..c325f73 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -413,7 +413,7 @@ function installQuestions() { if [[ $APPROVE_IP =~ n ]]; then read -rp "IP address: " -e -i "$IP" IP fi - # If $IP is a private IP address, the server must be behind NAT + # If $IP is a private IP address, the server must be behind NAT if echo "$IP" | grep -qE '^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192\.168)'; then log_menu "" log_prompt "It seems this server is behind NAT. What is its public IPv4 address or hostname?" @@ -533,6 +533,13 @@ function installQuestions() { fi done log_menu "" + read -rp "Allow a single .ovpn profile to be used on multiple devices simultaneously? [y/n]: " -e -i n MULTI_CLIENT_CHOICE + if [[ $MULTI_CLIENT_CHOICE =~ ^[Yy]$ ]]; then + MULTI_CLIENT="y" + else + MULTI_CLIENT="n" + fi + log_menu "" log_prompt "Do you want to use compression? It is not recommended since the VORACLE attack makes use of it." until [[ $COMPRESSION_ENABLED =~ (y|n) ]]; do read -rp "Enable compression? [y/n]: " -e -i n COMPRESSION_ENABLED @@ -984,6 +991,10 @@ function installOpenVPN() { echo "proto ${PROTOCOL}6" >>/etc/openvpn/server.conf fi + if [[ $MULTI_CLIENT == "y" ]]; then + echo "duplicate-cn" >>/etc/openvpn/server.conf + fi + echo "dev tun user nobody group $NOGROUP From cd0fc55bf7ec0706b0f5b2f12c9b0e1e0d6f2f56 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Tue, 9 Dec 2025 18:15:54 +0100 Subject: [PATCH 093/141] docs: add duplicate-cn feature to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ccb0454..d2e1124 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ export PASS="1" - Block DNS leaks on Windows 10 - Randomised server certificate name - Choice to protect clients with a password (private key encryption) +- Option to allow multiple devices to use the same client profile simultaneously - Many other little things! ## Compatibility From b23517dbb09554b7da5b2c0a87f1d5a99865b5e0 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Tue, 9 Dec 2025 18:30:57 +0100 Subject: [PATCH 094/141] Fix MULTI_CLIENT prompt blocking auto-install mode The duplicate-cn feature added an interactive prompt that wasn't following the auto-install pattern, causing the script to hang when running with AUTO_INSTALL=y. --- openvpn-install.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index c325f73..8295a2c 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -533,12 +533,10 @@ function installQuestions() { fi done log_menu "" - read -rp "Allow a single .ovpn profile to be used on multiple devices simultaneously? [y/n]: " -e -i n MULTI_CLIENT_CHOICE - if [[ $MULTI_CLIENT_CHOICE =~ ^[Yy]$ ]]; then - MULTI_CLIENT="y" - else - MULTI_CLIENT="n" - fi + log_prompt "Do you want to allow a single .ovpn profile to be used on multiple devices simultaneously?" + until [[ $MULTI_CLIENT =~ (y|n) ]]; do + read -rp "Allow multiple devices per client? [y/n]: " -e -i n MULTI_CLIENT + done log_menu "" log_prompt "Do you want to use compression? It is not recommended since the VORACLE attack makes use of it." until [[ $COMPRESSION_ENABLED =~ (y|n) ]]; do @@ -804,6 +802,7 @@ function installOpenVPN() { PROTOCOL_CHOICE=${PROTOCOL_CHOICE:-1} DNS=${DNS:-1} COMPRESSION_ENABLED=${COMPRESSION_ENABLED:-n} + MULTI_CLIENT=${MULTI_CLIENT:-n} CUSTOMIZE_ENC=${CUSTOMIZE_ENC:-n} CLIENT=${CLIENT:-client} PASS=${PASS:-1} @@ -822,6 +821,7 @@ function installOpenVPN() { log_info " PROTOCOL_CHOICE=$PROTOCOL_CHOICE" log_info " DNS=$DNS" log_info " COMPRESSION_ENABLED=$COMPRESSION_ENABLED" + log_info " MULTI_CLIENT=$MULTI_CLIENT" log_info " CUSTOMIZE_ENC=$CUSTOMIZE_ENC" log_info " CLIENT=$CLIENT" log_info " PASS=$PASS" From 8bd0c73f8ff424c2b83df8c7c5081e5f5f7098c4 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Tue, 9 Dec 2025 19:45:56 +0100 Subject: [PATCH 095/141] Use official OpenVPN repositories for latest stable versions (#1323) ## Summary - Install OpenVPN from official upstream repositories instead of distribution packages - Gets the latest stable releases with security fixes and new features - Properly cleans up repos and GPG keys on uninstall ## Repository sources | OS | Repository | |---|---| | Debian/Ubuntu | `build.openvpn.net/debian/openvpn/stable` | | CentOS/Oracle/Fedora | Fedora Copr `@OpenVPN/openvpn-release-2.6` | | Amazon Linux/Arch | Distribution packages (no official repo available) | ## Changes - Add `installOpenVPNRepo()` function to configure official repos before package installation - Remove duplicate package installations between repo setup and install functions - Clean up repos and GPG keys during uninstall - Standardize `log_success` (`[OK]`) for major milestones only --- Close https://github.com/angristan/openvpn-install/pull/1294 --- README.md | 1 + openvpn-install.sh | 111 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d2e1124..d8df6d0 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ export PASS="1" ## Features - Installs and configures a ready-to-use OpenVPN server +- 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 - If needed, the script can cleanly remove OpenVPN, including configuration and iptables rules - Customisable encryption settings, enhanced default settings (see [Security and Encryption](#security-and-encryption) below) diff --git a/openvpn-install.sh b/openvpn-install.sh index 8295a2c..c7a7bc3 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -236,6 +236,62 @@ function initialCheck() { checkOS } +function installOpenVPNRepo() { + log_info "Setting up official OpenVPN repository..." + + if [[ $OS =~ (debian|ubuntu) ]]; then + run_cmd "Update package lists" apt-get update + run_cmd "Installing prerequisites" apt-get install -y ca-certificates curl + + # Create keyrings directory + run_cmd "Creating keyrings directory" mkdir -p /etc/apt/keyrings + + # Download and install GPG key + if ! run_cmd "Downloading OpenVPN GPG key" curl -fsSL https://swupdate.openvpn.net/repos/repo-public.gpg -o /etc/apt/keyrings/openvpn-repo-public.asc; then + log_fatal "Failed to download OpenVPN repository GPG key" + fi + + # Add repository - using stable release + if [[ -z "${VERSION_CODENAME}" ]]; then + log_fatal "VERSION_CODENAME is not set. Unable to configure OpenVPN repository." + fi + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/openvpn-repo-public.asc] https://build.openvpn.net/debian/openvpn/stable ${VERSION_CODENAME} main" >/etc/apt/sources.list.d/openvpn-aptrepo.list + + log_info "Updating package lists with new repository..." + run_cmd "Update package lists" apt-get update + + log_info "OpenVPN official repository configured" + + elif [[ $OS =~ (centos|oracle) ]]; then + # For RHEL-based systems, use Fedora Copr (OpenVPN 2.6 stable) + # EPEL is required for pkcs11-helper dependency + log_info "Configuring OpenVPN Copr repository for RHEL-based system..." + + if ! command -v dnf &>/dev/null; then + run_cmd "Installing EPEL repository" yum install -y epel-release + run_cmd "Installing yum-plugin-copr" yum install -y yum-plugin-copr + run_cmd "Enabling OpenVPN Copr repo" yum copr enable -y @OpenVPN/openvpn-release-2.6 + else + run_cmd "Installing EPEL repository" dnf install -y epel-release + run_cmd "Installing dnf-plugins-core" dnf install -y dnf-plugins-core + run_cmd "Enabling OpenVPN Copr repo" dnf copr enable -y @OpenVPN/openvpn-release-2.6 + fi + + log_info "OpenVPN Copr repository configured" + + elif [[ $OS == "fedora" ]]; then + # Fedora already has recent OpenVPN, but we can use Copr for latest 2.6 + log_info "Configuring OpenVPN Copr repository for Fedora..." + run_cmd "Installing dnf-plugins-core" dnf install -y dnf-plugins-core + run_cmd "Enabling OpenVPN Copr repo" dnf copr enable -y @OpenVPN/openvpn-release-2.6 + + log_info "OpenVPN Copr repository configured" + + else + log_info "No official OpenVPN repository available for this OS, using distribution packages" + fi +} + function installUnbound() { log_info "Installing Unbound DNS resolver..." # If Unbound isn't installed, install it @@ -853,43 +909,26 @@ function installOpenVPN() { # the first time. if [[ ! -e /etc/openvpn/server.conf ]]; then log_header "Installing OpenVPN" + + # Setup official OpenVPN repository for latest versions + installOpenVPNRepo + + log_info "Installing OpenVPN and dependencies..." if [[ $OS =~ (debian|ubuntu) ]]; then - log_info "Updating package lists..." - run_cmd "Update package lists" apt-get update - run_cmd "Installing prerequisites" apt-get -y install ca-certificates gnupg - # Ubuntu >= 18.04 and Debian >= 11 have OpenVPN >= 2.4 without the need of a third party repository. - log_info "Installing OpenVPN and dependencies..." - run_cmd "Installing OpenVPN" apt-get install -y openvpn iptables openssl wget ca-certificates curl + run_cmd "Installing OpenVPN" apt-get install -y openvpn iptables openssl wget elif [[ $OS == 'centos' ]]; then - log_info "Installing EPEL repository..." - run_cmd "Installing EPEL" yum install -y epel-release - log_info "Installing OpenVPN and dependencies..." run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl tar 'policycoreutils-python*' elif [[ $OS == 'oracle' ]]; then - log_info "Installing EPEL repository..." - if [[ $VERSION_ID =~ ^8 ]]; then - run_cmd "Installing Oracle EPEL" yum install -y oracle-epel-release-el8 - run_cmd "Enabling EPEL repository" yum-config-manager --enable ol8_developer_EPEL - elif [[ $VERSION_ID =~ ^9 ]]; then - run_cmd "Installing Oracle EPEL" yum install -y oracle-epel-release-el9 - run_cmd "Enabling EPEL repository" yum-config-manager --enable ol9_developer_EPEL - fi - log_info "Installing OpenVPN and dependencies..." run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl tar policycoreutils-python-utils elif [[ $OS == 'amzn' ]]; then log_info "Installing EPEL repository..." run_cmd "Installing EPEL" amazon-linux-extras install -y epel - log_info "Installing OpenVPN and dependencies..." run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl elif [[ $OS == 'amzn2023' ]]; then - log_info "Installing OpenVPN and dependencies..." run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl wget ca-certificates elif [[ $OS == 'fedora' ]]; then - log_info "Installing OpenVPN and dependencies..." run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl wget ca-certificates curl policycoreutils-python-utils elif [[ $OS == 'arch' ]]; then - # Install required dependencies and upgrade the system - log_info "Installing OpenVPN and dependencies..." run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl wget ca-certificates curl fi # An old version of easy-rsa was available by default in some openvpn packages @@ -1489,16 +1528,34 @@ function removeOpenVPN() { log_info "Removing OpenVPN package..." if [[ $OS =~ (debian|ubuntu) ]]; then run_cmd "Removing OpenVPN" apt-get remove --purge -y openvpn - if [[ -e /etc/apt/sources.list.d/openvpn.list ]]; then - run_cmd "Removing OpenVPN repo" rm /etc/apt/sources.list.d/openvpn.list - run_cmd "Updating package lists" apt-get update + # Remove OpenVPN official repository and GPG key + if [[ -e /etc/apt/sources.list.d/openvpn-aptrepo.list ]]; then + run_cmd "Removing OpenVPN repo" rm /etc/apt/sources.list.d/openvpn-aptrepo.list fi + if [[ -e /etc/apt/keyrings/openvpn-repo-public.asc ]]; then + run_cmd "Removing OpenVPN GPG key" rm /etc/apt/keyrings/openvpn-repo-public.asc + fi + run_cmd "Updating package lists" apt-get update elif [[ $OS == 'arch' ]]; then run_cmd "Removing OpenVPN" pacman --noconfirm -R openvpn - elif [[ $OS =~ (centos|amzn|oracle) ]]; then + elif [[ $OS =~ (centos|oracle) ]]; then run_cmd "Removing OpenVPN" yum remove -y openvpn + # Disable Copr repo if it was enabled + if command -v dnf &>/dev/null; then + run_cmd "Disabling OpenVPN Copr repo" dnf copr disable -y @OpenVPN/openvpn-release-2.6 2>/dev/null || true + else + run_cmd "Disabling OpenVPN Copr repo" yum copr disable -y @OpenVPN/openvpn-release-2.6 2>/dev/null || true + fi + elif [[ $OS =~ (amzn|amzn2023) ]]; then + if [[ $OS == 'amzn2023' ]]; then + run_cmd "Removing OpenVPN" dnf remove -y openvpn + else + run_cmd "Removing OpenVPN" yum remove -y openvpn + fi elif [[ $OS == 'fedora' ]]; then run_cmd "Removing OpenVPN" dnf remove -y openvpn + # Disable Copr repo + run_cmd "Disabling OpenVPN Copr repo" dnf copr disable -y @OpenVPN/openvpn-release-2.6 2>/dev/null || true fi # Cleanup From 66890fb5d3003081748935cccf50981e8c4559b4 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Tue, 9 Dec 2025 19:47:02 +0100 Subject: [PATCH 096/141] ci: prevent duplicate workflow runs (#1324) ## Summary - Restrict `push` trigger to `master` branch only (feature branch pushes won't trigger CI) - Add concurrency groups to cancel redundant runs when new commits are pushed - Works correctly with fork PRs using standard `pull_request` event --- .github/workflows/docker-test.yml | 5 +++++ .github/workflows/lint.yml | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 80df170..86dc00d 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -1,11 +1,16 @@ --- on: push: + branches: [master] pull_request: workflow_dispatch: name: Docker Test +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f7cf46d..3093834 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,15 @@ -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: [master] + pull_request: + workflow_dispatch: name: Lint +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + permissions: contents: read From 625821dfd0af6697dff7d69e26003e998eeb2b9f Mon Sep 17 00:00:00 2001 From: Omid Shojaee <31741561+omidshojaee@users.noreply.github.com> Date: Tue, 9 Dec 2025 22:34:29 +0330 Subject: [PATCH 097/141] Allow custom certificate duration when creating clients (#1250) For those who need it, the script asks for how many days the new client should be valid. This defaults to 3650 days. Then it sets the ```EASYRSA_CERT_EXPIRE``` variable accordingly. This script is meant to be simple which means it is for those who are not tech-savvy to handle the complex task of installing and configuring OpenVPN. However if the user has a large number of clients and all of them are valid for 10 years, it is very hard to keep track of them. This PR helps them to set a reasonable validity period, while the default is the same. --------- Co-authored-by: Stanislas Lange --- openvpn-install.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index c7a7bc3..d1b99c0 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -862,6 +862,7 @@ function installOpenVPN() { CUSTOMIZE_ENC=${CUSTOMIZE_ENC:-n} CLIENT=${CLIENT:-client} PASS=${PASS:-1} + DAYS_VALID=${DAYS_VALID:-3650} CONTINUE=${CONTINUE:-y} if [[ -z $ENDPOINT ]]; then @@ -881,6 +882,7 @@ function installOpenVPN() { log_info " CUSTOMIZE_ENC=$CUSTOMIZE_ENC" log_info " CLIENT=$CLIENT" log_info " PASS=$PASS" + log_info " DAYS_VALID=$DAYS_VALID" fi # Run setup questions first, and set other variables if auto-install @@ -1326,6 +1328,12 @@ function newClient() { read -rp "Client name: " -e CLIENT done + if [[ -z $DAYS_VALID ]]; then + log_menu "" + log_prompt "How many days should the client certificate be valid for?" + read -rp "Certificate validity (days): " -e -i 3650 DAYS_VALID + fi + log_menu "" log_prompt "Do you want to protect the configuration file with a password?" log_prompt "(e.g. encrypt the private key with a password)" @@ -1343,7 +1351,7 @@ function newClient() { else cd /etc/openvpn/easy-rsa/ || return log_info "Generating client certificate..." - export EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS + export EASYRSA_CERT_EXPIRE=$DAYS_VALID case $PASS in 1) run_cmd "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass @@ -1353,7 +1361,7 @@ function newClient() { ./easyrsa --batch build-client-full "$CLIENT" ;; esac - log_success "Client $CLIENT added." + log_success "Client $CLIENT added and is valid for $DAYS_VALID days." fi # Home directory of the user, where the client configuration will be written From fb2041d9bb3ba8e9ca1f4cfee95f1fdf913b1e85 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Tue, 9 Dec 2025 21:41:08 +0100 Subject: [PATCH 098/141] Improve command logging in run_cmd function --- openvpn-install.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index d1b99c0..711f6ec 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -122,9 +122,8 @@ log_menu() { run_cmd() { local desc="$1" shift - if [[ $VERBOSE -eq 1 ]]; then - echo -e "${COLOR_DIM}> $*${COLOR_RESET}" - fi + # Display the command being run + echo -e "${COLOR_DIM}> $*${COLOR_RESET}" _log_to_file "[CMD] $*" if [[ $VERBOSE -eq 1 ]]; then if [[ -n "$LOG_FILE" ]]; then From 6b092703470f0826416a232a3d751483321f1c09 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Tue, 9 Dec 2025 21:49:19 +0100 Subject: [PATCH 099/141] feat: add certificate renewal functionality (#1328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add certificate renewal for both client and server certificates - Allow custom validity period during renewal (prompts user, defaults to 3650 days) - Show expiry info inline in menus (e.g., "Renew the server certificate (expires in 3542 days)") - Regenerate `.ovpn` files after client renewal - Restart OpenVPN service after server renewal - Extract reusable helper functions to reduce code duplication - Add robust input validation and error handling ## New menu option ``` What do you want to do? 1) Add a new user 2) Revoke existing user 3) Renew certificate ← NEW 4) Remove OpenVPN 5) Exit ``` ## Renewal submenu ``` What do you want to renew? 1) Renew a client certificate 2) Renew the server certificate (expires in 3542 days) 3) Back to main menu ``` Client list shows expiry for each: ``` Select the existing client certificate you want to renew 1) alice (expires in 3542 days) 2) bob (expires in 30 days) 3) charlie (EXPIRED 5 days ago) ``` ## Helper functions added Extracted common code into reusable functions: - `getHomeDir()` - home directory detection - `regenerateCRL()` - CRL regeneration after cert changes - `generateClientConfig()` - .ovpn file generation - `selectClient()` - client listing with optional expiry display - `getDaysUntilExpiry()` - certificate expiry calculation - `formatExpiry()` - human-readable expiry formatting ## Test plan - [x] Client certificate renewal tested in Docker CI - [x] Server certificate renewal tested in Docker CI - [x] Certificate validity verified after renewal (~3650 days) - [x] VPN connectivity tested with renewed certificate Closes #974 #1002 #1228 #1060 --- FAQ.md | 8 + README.md | 2 + openvpn-install.sh | 395 ++++++++++++++++++++++++++++++-------- test/server-entrypoint.sh | 151 +++++++++++++++ 4 files changed, 475 insertions(+), 81 deletions(-) diff --git a/FAQ.md b/FAQ.md index 1f0b572..cef7f2d 100644 --- a/FAQ.md +++ b/FAQ.md @@ -8,6 +8,14 @@ You can, of course, it's even recommended, update the `openvpn` package with you --- +**Q:** How do I renew certificates before they expire? + +**A:** Run the script again and select "Renew certificates" from the menu. You can renew either client certificates or the server certificate. The script will show you the current expiration date for each certificate and let you choose a new validity period (default: 3650 days / 10 years). + +For client renewals, a new `.ovpn` file will be generated that you need to distribute to the client. For server renewals, the OpenVPN service will need to be restarted (the script will prompt you). + +--- + **Q:** How do I check for DNS leaks? **A:** Go to [browserleaks.com](https://browserleaks.com/dns) or [ipleak.net](https://ipleak.net/) (both perform IPv4 and IPv6 check) with your browser. Your IP should not show up (test without and without the VPN). The DNS servers should be the ones you selected during the setup, not your IP address nor your ISP's DNS servers' addresses. diff --git a/README.md b/README.md index d8df6d0..3256f84 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ When OpenVPN is installed, you can run the script again, and you will get the ch - Add a client - Remove a client +- Renew certificates (client or server) - Uninstall OpenVPN In your home directory, you will have `.ovpn` files. These are the client configuration files. Download them from your server and connect using your favorite OpenVPN client. @@ -113,6 +114,7 @@ export PASS="1" ## Features - Installs and configures a ready-to-use OpenVPN server +- 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 - Iptables rules and forwarding managed in a seamless way - If needed, the script can cleanly remove OpenVPN, including configuration and iptables rules diff --git a/openvpn-install.sh b/openvpn-install.sh index 711f6ec..70ae989 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1318,6 +1318,116 @@ verb 3" >>/etc/openvpn/client-template.txt log_success "If you want to add more clients, you simply need to run this script another time!" } +# Helper function to get the home directory for storing client configs +function getHomeDir() { + local client="$1" + if [ -e "/home/${client}" ]; then + echo "/home/${client}" + elif [ "${SUDO_USER}" ]; then + if [ "${SUDO_USER}" == "root" ]; then + echo "/root" + else + echo "/home/${SUDO_USER}" + fi + else + echo "/root" + fi +} + +# Helper function to regenerate the CRL after certificate changes +function regenerateCRL() { + export EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS + run_cmd "Regenerating CRL" ./easyrsa gen-crl + run_cmd "Removing old CRL" rm -f /etc/openvpn/crl.pem + run_cmd "Copying new CRL" cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem + run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/crl.pem +} + +# Helper function to generate .ovpn client config file +function generateClientConfig() { + local client="$1" + local home_dir="$2" + + # Determine if we use tls-auth or tls-crypt + local tls_sig="" + if grep -qs "^tls-crypt" /etc/openvpn/server.conf; then + tls_sig="1" + elif grep -qs "^tls-auth" /etc/openvpn/server.conf; then + tls_sig="2" + fi + + # Generate the custom client.ovpn + run_cmd "Creating client config" cp /etc/openvpn/client-template.txt "$home_dir/$client.ovpn" + { + echo "" + cat "/etc/openvpn/easy-rsa/pki/ca.crt" + echo "" + + echo "" + awk '/BEGIN/,/END CERTIFICATE/' "/etc/openvpn/easy-rsa/pki/issued/$client.crt" + echo "" + + echo "" + cat "/etc/openvpn/easy-rsa/pki/private/$client.key" + echo "" + + case $tls_sig in + 1) + echo "" + cat /etc/openvpn/tls-crypt.key + echo "" + ;; + 2) + echo "key-direction 1" + echo "" + cat /etc/openvpn/tls-auth.key + echo "" + ;; + esac + } >>"$home_dir/$client.ovpn" +} + +# Helper function to list valid clients and select one +# Arguments: show_expiry (optional, "true" to show expiry info) +# Sets global variables: +# CLIENT - the selected client name +# CLIENTNUMBER - the selected client number (1-based index) +# NUMBEROFCLIENTS - total count of valid clients +function selectClient() { + local show_expiry="${1:-false}" + local client_number + + NUMBEROFCLIENTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^V") + if [[ $NUMBEROFCLIENTS == '0' ]]; then + log_fatal "You have no existing clients!" + fi + + if [[ $show_expiry == "true" ]]; then + local i=1 + while read -r client; do + local client_cert="/etc/openvpn/easy-rsa/pki/issued/$client.crt" + local days + days=$(getDaysUntilExpiry "$client_cert") + local expiry + expiry=$(formatExpiry "$days") + echo " $i) $client $expiry" + ((i++)) + done < <(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2) + else + tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' + fi + + until [[ ${CLIENTNUMBER:-$client_number} -ge 1 && ${CLIENTNUMBER:-$client_number} -le $NUMBEROFCLIENTS ]]; do + if [[ $NUMBEROFCLIENTS == '1' ]]; then + read -rp "Select one client [1]: " client_number + else + read -rp "Select one client [1-$NUMBEROFCLIENTS]: " client_number + fi + done + CLIENTNUMBER="${CLIENTNUMBER:-$client_number}" + CLIENT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p) +} + function newClient() { log_header "New Client Setup" log_prompt "Tell me a name for the client." @@ -1327,10 +1437,12 @@ function newClient() { read -rp "Client name: " -e CLIENT done - if [[ -z $DAYS_VALID ]]; then + if [[ -z $DAYS_VALID ]] || ! [[ $DAYS_VALID =~ ^[0-9]+$ ]] || [[ $DAYS_VALID -lt 1 ]]; then log_menu "" log_prompt "How many days should the client certificate be valid for?" - read -rp "Certificate validity (days): " -e -i 3650 DAYS_VALID + until [[ $DAYS_VALID =~ ^[0-9]+$ ]] && [[ $DAYS_VALID -ge 1 ]]; do + read -rp "Certificate validity (days): " -e -i 3650 DAYS_VALID + done fi log_menu "" @@ -1363,59 +1475,9 @@ function newClient() { log_success "Client $CLIENT added and is valid for $DAYS_VALID days." fi - # Home directory of the user, where the client configuration will be written - if [ -e "/home/${CLIENT}" ]; then - # if $1 is a user name - homeDir="/home/${CLIENT}" - elif [ "${SUDO_USER}" ]; then - # if not, use SUDO_USER - if [ "${SUDO_USER}" == "root" ]; then - # If running sudo as root - homeDir="/root" - else - homeDir="/home/${SUDO_USER}" - fi - else - # if not SUDO_USER, use /root - homeDir="/root" - fi - - # Determine if we use tls-auth or tls-crypt - if grep -qs "^tls-crypt" /etc/openvpn/server.conf; then - TLS_SIG="1" - elif grep -qs "^tls-auth" /etc/openvpn/server.conf; then - TLS_SIG="2" - fi - - # Generates the custom client.ovpn - run_cmd "Creating client config" cp /etc/openvpn/client-template.txt "$homeDir/$CLIENT.ovpn" - { - echo "" - cat "/etc/openvpn/easy-rsa/pki/ca.crt" - echo "" - - echo "" - awk '/BEGIN/,/END CERTIFICATE/' "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt" - echo "" - - echo "" - cat "/etc/openvpn/easy-rsa/pki/private/$CLIENT.key" - echo "" - - case $TLS_SIG in - 1) - echo "" - cat /etc/openvpn/tls-crypt.key - echo "" - ;; - 2) - echo "key-direction 1" - echo "" - cat /etc/openvpn/tls-auth.key - echo "" - ;; - esac - } >>"$homeDir/$CLIENT.ovpn" + # Generate the .ovpn config file + homeDir=$(getHomeDir "$CLIENT") + generateClientConfig "$CLIENT" "$homeDir" log_menu "" log_success "The configuration file has been written to $homeDir/$CLIENT.ovpn." @@ -1425,30 +1487,14 @@ function newClient() { } function revokeClient() { - NUMBEROFCLIENTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^V") - if [[ $NUMBEROFCLIENTS == '0' ]]; then - log_fatal "You have no existing clients!" - fi - log_header "Revoke Client" log_prompt "Select the existing client certificate you want to revoke" - tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' - until [[ $CLIENTNUMBER -ge 1 && $CLIENTNUMBER -le $NUMBEROFCLIENTS ]]; do - if [[ $CLIENTNUMBER == '1' ]]; then - read -rp "Select one client [1]: " CLIENTNUMBER - else - read -rp "Select one client [1-$NUMBEROFCLIENTS]: " CLIENTNUMBER - fi - done - CLIENT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | sed -n "$CLIENTNUMBER"p) + selectClient + cd /etc/openvpn/easy-rsa/ || return log_info "Revoking certificate for $CLIENT..." run_cmd "Revoking certificate" ./easyrsa --batch revoke "$CLIENT" - export EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS - run_cmd "Regenerating CRL" ./easyrsa gen-crl - run_cmd "Removing old CRL" rm -f /etc/openvpn/crl.pem - run_cmd "Copying new CRL" cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem - run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/crl.pem + regenerateCRL run_cmd "Removing client config from /home" find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete run_cmd "Removing client config from /root" rm -f "/root/$CLIENT.ovpn" run_cmd "Removing IP assignment" sed -i "/^$CLIENT,.*/d" /etc/openvpn/ipp.txt @@ -1457,6 +1503,186 @@ function revokeClient() { log_success "Certificate for client $CLIENT revoked." } +function renewClient() { + local homeDir days_valid + + log_header "Renew Client Certificate" + log_prompt "Select the existing client certificate you want to renew" + selectClient "true" + + # Allow user to specify renewal duration (use DAYS_VALID env var for headless mode) + if [[ -z $DAYS_VALID ]] || ! [[ $DAYS_VALID =~ ^[0-9]+$ ]] || [[ $DAYS_VALID -lt 1 ]]; then + log_menu "" + log_prompt "How many days should the renewed certificate be valid for?" + until [[ $days_valid =~ ^[0-9]+$ ]] && [[ $days_valid -ge 1 ]]; do + read -rp "Certificate validity (days): " -e -i 3650 days_valid + done + else + days_valid=$DAYS_VALID + fi + + cd /etc/openvpn/easy-rsa/ || return + log_info "Renewing certificate for $CLIENT..." + + # Backup the old certificate before renewal + run_cmd "Backing up old certificate" cp "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt" "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt.bak" + + # Renew the certificate (keeps the same private key) + export EASYRSA_CERT_EXPIRE=$days_valid + run_cmd "Renewing certificate" ./easyrsa --batch renew "$CLIENT" + + # Revoke the old certificate + run_cmd "Revoking old certificate" ./easyrsa --batch revoke-renewed "$CLIENT" + + # Regenerate the CRL + regenerateCRL + + # Regenerate the .ovpn file with the new certificate + homeDir=$(getHomeDir "$CLIENT") + generateClientConfig "$CLIENT" "$homeDir" + + log_menu "" + log_success "Certificate for client $CLIENT renewed and is valid for $days_valid days." + log_info "The new configuration file has been written to $homeDir/$CLIENT.ovpn." + log_info "Download the new .ovpn file and import it in your OpenVPN client." +} + +function renewServer() { + local server_name days_valid + + log_header "Renew Server Certificate" + + # Get the server name from the config + server_name=$(grep '^cert ' /etc/openvpn/server.conf | cut -d ' ' -f 2 | sed 's/\.crt$//') + if [[ -z "$server_name" ]]; then + log_fatal "Could not determine server certificate name from /etc/openvpn/server.conf" + fi + + log_prompt "This will renew the server certificate: $server_name" + log_warn "The OpenVPN service will be restarted after renewal." + if [[ -z $CONTINUE ]]; then + read -rp "Do you want to continue? [y/n]: " -e -i n CONTINUE + fi + if [[ $CONTINUE != "y" ]]; then + log_info "Renewal aborted." + return + fi + + # Allow user to specify renewal duration (use DAYS_VALID env var for headless mode) + if [[ -z $DAYS_VALID ]] || ! [[ $DAYS_VALID =~ ^[0-9]+$ ]] || [[ $DAYS_VALID -lt 1 ]]; then + log_menu "" + log_prompt "How many days should the renewed certificate be valid for?" + until [[ $days_valid =~ ^[0-9]+$ ]] && [[ $days_valid -ge 1 ]]; do + read -rp "Certificate validity (days): " -e -i 3650 days_valid + done + else + days_valid=$DAYS_VALID + fi + + cd /etc/openvpn/easy-rsa/ || return + log_info "Renewing server certificate..." + + # Backup the old certificate before renewal + run_cmd "Backing up old certificate" cp "/etc/openvpn/easy-rsa/pki/issued/$server_name.crt" "/etc/openvpn/easy-rsa/pki/issued/$server_name.crt.bak" + + # Renew the certificate (keeps the same private key) + export EASYRSA_CERT_EXPIRE=$days_valid + run_cmd "Renewing certificate" ./easyrsa --batch renew "$server_name" + + # Revoke the old certificate + run_cmd "Revoking old certificate" ./easyrsa --batch revoke-renewed "$server_name" + + # Regenerate the CRL + regenerateCRL + + # Copy the new certificate to /etc/openvpn/ + run_cmd "Copying new certificate" cp "/etc/openvpn/easy-rsa/pki/issued/$server_name.crt" /etc/openvpn/ + + # Restart OpenVPN + log_info "Restarting OpenVPN service..." + if [[ $OS =~ (fedora|arch|centos|oracle) ]]; then + run_cmd "Restarting OpenVPN" systemctl restart openvpn-server@server + elif [[ $OS == "ubuntu" ]] && [[ $VERSION_ID == "16.04" ]]; then + run_cmd "Restarting OpenVPN" systemctl restart openvpn + else + run_cmd "Restarting OpenVPN" systemctl restart openvpn@server + fi + + log_success "Server certificate renewed successfully and is valid for $days_valid days." +} + +function getDaysUntilExpiry() { + local cert_file="$1" + if [[ -f "$cert_file" ]]; then + local expiry_date + expiry_date=$(openssl x509 -in "$cert_file" -noout -enddate | cut -d= -f2) + local expiry_epoch + expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$expiry_date" +%s 2>/dev/null) + if [[ -z "$expiry_epoch" ]]; then + echo "?" + return + fi + local now_epoch + now_epoch=$(date +%s) + echo $(((expiry_epoch - now_epoch) / 86400)) + else + echo "?" + fi +} + +function formatExpiry() { + local days="$1" + if [[ "$days" == "?" ]]; then + echo "(unknown expiry)" + elif [[ $days -lt 0 ]]; then + echo "(EXPIRED $((-days)) days ago)" + elif [[ $days -eq 0 ]]; then + echo "(expires today)" + elif [[ $days -eq 1 ]]; then + echo "(expires in 1 day)" + else + echo "(expires in $days days)" + fi +} + +function renewMenu() { + local server_name server_cert server_days server_expiry renew_option + + log_header "Certificate Renewal" + + # Get server certificate expiry for menu display + server_name=$(grep '^cert ' /etc/openvpn/server.conf | cut -d ' ' -f 2 | sed 's/\.crt$//') + if [[ -z "$server_name" ]]; then + server_expiry="(unknown expiry)" + else + server_cert="/etc/openvpn/easy-rsa/pki/issued/$server_name.crt" + server_days=$(getDaysUntilExpiry "$server_cert") + server_expiry=$(formatExpiry "$server_days") + fi + + log_menu "" + log_prompt "What do you want to renew?" + log_menu " 1) Renew a client certificate" + log_menu " 2) Renew the server certificate $server_expiry" + log_menu " 3) Back to main menu" + until [[ ${RENEW_OPTION:-$renew_option} =~ ^[1-3]$ ]]; do + read -rp "Select an option [1-3]: " renew_option + done + renew_option="${RENEW_OPTION:-$renew_option}" + + case $renew_option in + 1) + renewClient + ;; + 2) + renewServer + ;; + 3) + manageMenu + ;; + esac +} + function removeUnbound() { # Remove OpenVPN-related config run_cmd "Removing Unbound include" sed -i '/include: \/etc\/unbound\/openvpn.conf/d' /etc/unbound/unbound.conf @@ -1584,6 +1810,8 @@ function removeOpenVPN() { } function manageMenu() { + local menu_option + log_header "OpenVPN Management" log_prompt "The git repository is available at: https://github.com/angristan/openvpn-install" log_success "OpenVPN is already installed." @@ -1591,13 +1819,15 @@ function manageMenu() { log_prompt "What do you want to do?" log_menu " 1) Add a new user" log_menu " 2) Revoke existing user" - log_menu " 3) Remove OpenVPN" - log_menu " 4) Exit" - until [[ $MENU_OPTION =~ ^[1-4]$ ]]; do - read -rp "Select an option [1-4]: " MENU_OPTION + log_menu " 3) Renew certificate" + log_menu " 4) Remove OpenVPN" + log_menu " 5) Exit" + until [[ ${MENU_OPTION:-$menu_option} =~ ^[1-5]$ ]]; do + read -rp "Select an option [1-5]: " menu_option done + menu_option="${MENU_OPTION:-$menu_option}" - case $MENU_OPTION in + case $menu_option in 1) newClient ;; @@ -1605,9 +1835,12 @@ function manageMenu() { revokeClient ;; 3) - removeOpenVPN + renewMenu ;; 4) + removeOpenVPN + ;; + 5) exit 0 ;; esac @@ -1617,7 +1850,7 @@ function manageMenu() { initialCheck # Check if OpenVPN is already installed -if [[ -e /etc/openvpn/server.conf && $AUTO_INSTALL != "y" ]]; then +if [[ -e /etc/openvpn/server.conf ]]; then manageMenu else installOpenVPN diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 54e56ff..d176fe1 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -92,6 +92,157 @@ cp /root/testclient.ovpn /shared/client.ovpn sed -i 's/^remote .*/remote openvpn-server 1194/' /shared/client.ovpn echo "Client config copied to /shared/client.ovpn" +# ===================================================== +# Test certificate renewal functionality +# ===================================================== +echo "" +echo "=== Testing Certificate Renewal ===" + +# Get the original certificate serial number for comparison +ORIG_CERT_SERIAL=$(openssl x509 -in /etc/openvpn/easy-rsa/pki/issued/testclient.crt -noout -serial | cut -d= -f2) +echo "Original client certificate serial: $ORIG_CERT_SERIAL" + +# Test client certificate renewal using the script +echo "Testing client certificate renewal..." +RENEW_OUTPUT="/tmp/renew-client-output.log" +(MENU_OPTION=3 RENEW_OPTION=1 CLIENTNUMBER=1 DAYS_VALID=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RENEW_OUTPUT" || true + +# Verify renewal succeeded +if grep -q "Certificate for client testclient renewed" "$RENEW_OUTPUT"; then + echo "PASS: Client renewal completed successfully" +else + echo "FAIL: Client renewal did not complete" + cat "$RENEW_OUTPUT" + exit 1 +fi + +# Verify new certificate has different serial +NEW_CERT_SERIAL=$(openssl x509 -in /etc/openvpn/easy-rsa/pki/issued/testclient.crt -noout -serial | cut -d= -f2) +echo "New client certificate serial: $NEW_CERT_SERIAL" +if [ "$ORIG_CERT_SERIAL" != "$NEW_CERT_SERIAL" ]; then + echo "PASS: Certificate serial changed (renewal created new cert)" +else + echo "FAIL: Certificate serial unchanged" + exit 1 +fi + +# Verify renewed certificate has correct validity period +# The default is 3650 days, so the cert should be valid for ~10 years from now +CLIENT_CERT_NOT_AFTER=$(openssl x509 -in /etc/openvpn/easy-rsa/pki/issued/testclient.crt -noout -enddate | cut -d= -f2) +CLIENT_CERT_NOT_BEFORE=$(openssl x509 -in /etc/openvpn/easy-rsa/pki/issued/testclient.crt -noout -startdate | cut -d= -f2) +echo "Client certificate valid from: $CLIENT_CERT_NOT_BEFORE" +echo "Client certificate valid until: $CLIENT_CERT_NOT_AFTER" + +# Calculate days until expiry (should be close to 3650) +CERT_END_EPOCH=$(date -d "$CLIENT_CERT_NOT_AFTER" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$CLIENT_CERT_NOT_AFTER" +%s 2>/dev/null) +NOW_EPOCH=$(date +%s) +DAYS_VALID_ACTUAL=$(((CERT_END_EPOCH - NOW_EPOCH) / 86400)) +echo "Client certificate validity: $DAYS_VALID_ACTUAL days" + +# Should be between 3640 and 3650 days (allowing some tolerance for timing) +if [ "$DAYS_VALID_ACTUAL" -ge 3640 ] && [ "$DAYS_VALID_ACTUAL" -le 3650 ]; then + echo "PASS: Client certificate validity is correct (~3650 days)" +else + echo "FAIL: Client certificate validity is unexpected: $DAYS_VALID_ACTUAL days (expected ~3650)" + exit 1 +fi + +# Verify new .ovpn file was generated +if [ -f /root/testclient.ovpn ]; then + echo "PASS: New .ovpn file generated" +else + echo "FAIL: .ovpn file not found after renewal" + exit 1 +fi + +# Verify CRL was updated +if [ -f /etc/openvpn/crl.pem ]; then + echo "PASS: CRL file exists" +else + echo "FAIL: CRL file missing after renewal" + exit 1 +fi + +# Update shared client config with renewed certificate +cp /root/testclient.ovpn /shared/client.ovpn +sed -i 's/^remote .*/remote openvpn-server 1194/' /shared/client.ovpn +echo "Updated client config with renewed certificate" + +echo "=== Client Certificate Renewal Tests PASSED ===" + +# ===================================================== +# Test server certificate renewal +# ===================================================== +echo "" +echo "=== Testing Server Certificate Renewal ===" + +# Get server certificate name and original serial +SERVER_NAME=$(grep '^cert ' /etc/openvpn/server.conf | cut -d ' ' -f 2 | sed 's/\.crt$//') +ORIG_SERVER_SERIAL=$(openssl x509 -in "/etc/openvpn/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) +echo "Server certificate: $SERVER_NAME" +echo "Original server certificate serial: $ORIG_SERVER_SERIAL" + +# Test server certificate renewal +echo "Testing server certificate renewal..." +RENEW_SERVER_OUTPUT="/tmp/renew-server-output.log" +(MENU_OPTION=3 RENEW_OPTION=2 CONTINUE=y DAYS_VALID=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RENEW_SERVER_OUTPUT" || true + +# Verify renewal succeeded +if grep -q "Server certificate renewed successfully" "$RENEW_SERVER_OUTPUT"; then + echo "PASS: Server renewal completed successfully" +else + echo "FAIL: Server renewal did not complete" + cat "$RENEW_SERVER_OUTPUT" + exit 1 +fi + +# Verify new certificate has different serial +NEW_SERVER_SERIAL=$(openssl x509 -in "/etc/openvpn/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) +echo "New server certificate serial: $NEW_SERVER_SERIAL" +if [ "$ORIG_SERVER_SERIAL" != "$NEW_SERVER_SERIAL" ]; then + echo "PASS: Server certificate serial changed (renewal created new cert)" +else + echo "FAIL: Server certificate serial unchanged" + exit 1 +fi + +# Verify renewed server certificate has correct validity period +SERVER_CERT_NOT_AFTER=$(openssl x509 -in "/etc/openvpn/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -enddate | cut -d= -f2) +SERVER_CERT_NOT_BEFORE=$(openssl x509 -in "/etc/openvpn/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -startdate | cut -d= -f2) +echo "Server certificate valid from: $SERVER_CERT_NOT_BEFORE" +echo "Server certificate valid until: $SERVER_CERT_NOT_AFTER" + +# Calculate days until expiry (should be close to 3650) +SERVER_END_EPOCH=$(date -d "$SERVER_CERT_NOT_AFTER" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$SERVER_CERT_NOT_AFTER" +%s 2>/dev/null) +SERVER_DAYS_VALID=$(((SERVER_END_EPOCH - NOW_EPOCH) / 86400)) +echo "Server certificate validity: $SERVER_DAYS_VALID days" + +if [ "$SERVER_DAYS_VALID" -ge 3640 ] && [ "$SERVER_DAYS_VALID" -le 3650 ]; then + echo "PASS: Server certificate validity is correct (~3650 days)" +else + echo "FAIL: Server certificate validity is unexpected: $SERVER_DAYS_VALID days (expected ~3650)" + exit 1 +fi + +# Verify the new certificate was copied to /etc/openvpn/ +if [ -f "/etc/openvpn/$SERVER_NAME.crt" ]; then + DEPLOYED_SERIAL=$(openssl x509 -in "/etc/openvpn/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) + if [ "$NEW_SERVER_SERIAL" = "$DEPLOYED_SERIAL" ]; then + echo "PASS: New server certificate deployed to /etc/openvpn/" + else + echo "FAIL: Deployed certificate doesn't match renewed certificate" + exit 1 + fi +else + echo "FAIL: Server certificate not found in /etc/openvpn/" + exit 1 +fi + +echo "=== Server Certificate Renewal Tests PASSED ===" +echo "" +echo "=== All Certificate Renewal Tests PASSED ===" +echo "" + # Start OpenVPN server manually (systemd doesn't work in containers) echo "Starting OpenVPN server..." From f9a544104e3d35a75760d7ff532de69784bb5604 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Tue, 9 Dec 2025 21:55:36 +0100 Subject: [PATCH 100/141] docs: add missing headless variables to README Add MULTI_CLIENT and DAYS_VALID to the documented headless install variables, matching what the script actually supports. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3256f84..4fe97ec 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,8 @@ If you want to customise your installation, you can export them or specify them - `CUSTOMIZE_ENC=n` - `CLIENT=clientname` - `PASS=1` +- `MULTI_CLIENT=n` +- `DAYS_VALID=3650` If the server is behind NAT, you can specify its endpoint with the `ENDPOINT` variable. If the endpoint is the public IP address which it is behind, you can use `ENDPOINT=$(curl -4 ifconfig.co)` (the script will default to this). The endpoint can be an IPv4 or a domain. From ffcffac0614d64f067e62bc7c5e7664a3412d96e Mon Sep 17 00:00:00 2001 From: Stanislas Date: Tue, 9 Dec 2025 23:33:57 +0100 Subject: [PATCH 101/141] refactor: improve certificate duration variable naming (#1329) ## Summary - Rename constants to `DEFAULT_CERT_VALIDITY_DURATION_DAYS` and `DEFAULT_CRL_VALIDITY_DURATION_DAYS` for clarity - Replace all hardcoded `3650` values with the constants - Split `DAYS_VALID` into `CLIENT_CERT_DURATION_DAYS` and `SERVER_CERT_DURATION_DAYS` for more granular control over client vs server certificate validity - Increase CRL validity to 15 years (5475 days) to provide a 5-year safety buffer over the default 10-year certificate validity - Update README with new headless install variables ## Breaking changes - `DAYS_VALID` environment variable is replaced by `CLIENT_CERT_DURATION_DAYS` and `SERVER_CERT_DURATION_DAYS` --- README.md | 3 +- openvpn-install.sh | 60 ++++++++++++++++++++------------------- test/server-entrypoint.sh | 4 +-- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 4fe97ec..78842af 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,8 @@ If you want to customise your installation, you can export them or specify them - `CLIENT=clientname` - `PASS=1` - `MULTI_CLIENT=n` -- `DAYS_VALID=3650` +- `CLIENT_CERT_DURATION_DAYS=3650` +- `SERVER_CERT_DURATION_DAYS=3650` If the server is behind NAT, you can specify its endpoint with the `ENDPOINT` variable. If the endpoint is the public IP address which it is behind, you can use `ENDPOINT=$(curl -4 ifconfig.co)` (the script will default to this). The endpoint can be an IPv4 or a domain. diff --git a/openvpn-install.sh b/openvpn-install.sh index 70ae989..0e507b0 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -7,8 +7,8 @@ # https://github.com/angristan/openvpn-install # Configuration constants -readonly CERT_VALIDITY_DAYS=3650 # 10 years -readonly CRL_VALIDITY_DAYS=3650 # 10 years +readonly DEFAULT_CERT_VALIDITY_DURATION_DAYS=3650 # 10 years +readonly DEFAULT_CRL_VALIDITY_DURATION_DAYS=5475 # 15 years readonly EASYRSA_VERSION="3.1.2" readonly EASYRSA_SHA256="d63cf129490ffd6d8792ede7344806c506c82c32428b5bb609ad97ca6a6e4499" @@ -861,7 +861,8 @@ function installOpenVPN() { CUSTOMIZE_ENC=${CUSTOMIZE_ENC:-n} CLIENT=${CLIENT:-client} PASS=${PASS:-1} - DAYS_VALID=${DAYS_VALID:-3650} + 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} CONTINUE=${CONTINUE:-y} if [[ -z $ENDPOINT ]]; then @@ -881,7 +882,8 @@ function installOpenVPN() { log_info " CUSTOMIZE_ENC=$CUSTOMIZE_ENC" log_info " CLIENT=$CLIENT" log_info " PASS=$PASS" - log_info " DAYS_VALID=$DAYS_VALID" + log_info " CLIENT_CERT_DURATION_DAYS=$CLIENT_CERT_DURATION_DAYS" + log_info " SERVER_CERT_DURATION_DAYS=$SERVER_CERT_DURATION_DAYS" fi # Run setup questions first, and set other variables if auto-install @@ -979,7 +981,7 @@ function installOpenVPN() { # Create the PKI, set up the CA, the DH params and the server certificate log_info "Initializing PKI..." run_cmd "Initializing PKI" ./easyrsa init-pki - export EASYRSA_CA_EXPIRE=$CERT_VALIDITY_DAYS + export EASYRSA_CA_EXPIRE=$DEFAULT_CERT_VALIDITY_DURATION_DAYS log_info "Building CA..." run_cmd "Building CA" ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass @@ -988,10 +990,10 @@ function installOpenVPN() { run_cmd "Generating DH parameters (this may take a while)" openssl dhparam -out dh.pem "$DH_KEY_SIZE" fi - export EASYRSA_CERT_EXPIRE=$CERT_VALIDITY_DAYS + export EASYRSA_CERT_EXPIRE=${SERVER_CERT_DURATION_DAYS:-$DEFAULT_CERT_VALIDITY_DURATION_DAYS} log_info "Building server certificate..." run_cmd "Building server certificate" ./easyrsa --batch build-server-full "$SERVER_NAME" nopass - export EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS + export EASYRSA_CRL_DAYS=$DEFAULT_CRL_VALIDITY_DURATION_DAYS run_cmd "Generating CRL" ./easyrsa gen-crl log_info "Generating TLS key..." @@ -1336,7 +1338,7 @@ function getHomeDir() { # Helper function to regenerate the CRL after certificate changes function regenerateCRL() { - export EASYRSA_CRL_DAYS=$CRL_VALIDITY_DAYS + export EASYRSA_CRL_DAYS=$DEFAULT_CRL_VALIDITY_DURATION_DAYS run_cmd "Regenerating CRL" ./easyrsa gen-crl run_cmd "Removing old CRL" rm -f /etc/openvpn/crl.pem run_cmd "Copying new CRL" cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem @@ -1437,11 +1439,11 @@ function newClient() { read -rp "Client name: " -e CLIENT done - if [[ -z $DAYS_VALID ]] || ! [[ $DAYS_VALID =~ ^[0-9]+$ ]] || [[ $DAYS_VALID -lt 1 ]]; then + if [[ -z $CLIENT_CERT_DURATION_DAYS ]] || ! [[ $CLIENT_CERT_DURATION_DAYS =~ ^[0-9]+$ ]] || [[ $CLIENT_CERT_DURATION_DAYS -lt 1 ]]; then log_menu "" log_prompt "How many days should the client certificate be valid for?" - until [[ $DAYS_VALID =~ ^[0-9]+$ ]] && [[ $DAYS_VALID -ge 1 ]]; do - read -rp "Certificate validity (days): " -e -i 3650 DAYS_VALID + until [[ $CLIENT_CERT_DURATION_DAYS =~ ^[0-9]+$ ]] && [[ $CLIENT_CERT_DURATION_DAYS -ge 1 ]]; do + read -rp "Certificate validity (days): " -e -i $DEFAULT_CERT_VALIDITY_DURATION_DAYS CLIENT_CERT_DURATION_DAYS done fi @@ -1462,7 +1464,7 @@ function newClient() { else cd /etc/openvpn/easy-rsa/ || return log_info "Generating client certificate..." - export EASYRSA_CERT_EXPIRE=$DAYS_VALID + export EASYRSA_CERT_EXPIRE=$CLIENT_CERT_DURATION_DAYS case $PASS in 1) run_cmd "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass @@ -1472,7 +1474,7 @@ function newClient() { ./easyrsa --batch build-client-full "$CLIENT" ;; esac - log_success "Client $CLIENT added and is valid for $DAYS_VALID days." + log_success "Client $CLIENT added and is valid for $CLIENT_CERT_DURATION_DAYS days." fi # Generate the .ovpn config file @@ -1504,21 +1506,21 @@ function revokeClient() { } function renewClient() { - local homeDir days_valid + local homeDir client_cert_duration_days log_header "Renew Client Certificate" log_prompt "Select the existing client certificate you want to renew" selectClient "true" - # Allow user to specify renewal duration (use DAYS_VALID env var for headless mode) - if [[ -z $DAYS_VALID ]] || ! [[ $DAYS_VALID =~ ^[0-9]+$ ]] || [[ $DAYS_VALID -lt 1 ]]; then + # Allow user to specify renewal duration (use CLIENT_CERT_DURATION_DAYS env var for headless mode) + if [[ -z $CLIENT_CERT_DURATION_DAYS ]] || ! [[ $CLIENT_CERT_DURATION_DAYS =~ ^[0-9]+$ ]] || [[ $CLIENT_CERT_DURATION_DAYS -lt 1 ]]; then log_menu "" log_prompt "How many days should the renewed certificate be valid for?" - until [[ $days_valid =~ ^[0-9]+$ ]] && [[ $days_valid -ge 1 ]]; do - read -rp "Certificate validity (days): " -e -i 3650 days_valid + until [[ $client_cert_duration_days =~ ^[0-9]+$ ]] && [[ $client_cert_duration_days -ge 1 ]]; do + read -rp "Certificate validity (days): " -e -i $DEFAULT_CERT_VALIDITY_DURATION_DAYS client_cert_duration_days done else - days_valid=$DAYS_VALID + client_cert_duration_days=$CLIENT_CERT_DURATION_DAYS fi cd /etc/openvpn/easy-rsa/ || return @@ -1528,7 +1530,7 @@ function renewClient() { run_cmd "Backing up old certificate" cp "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt" "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt.bak" # Renew the certificate (keeps the same private key) - export EASYRSA_CERT_EXPIRE=$days_valid + export EASYRSA_CERT_EXPIRE=$client_cert_duration_days run_cmd "Renewing certificate" ./easyrsa --batch renew "$CLIENT" # Revoke the old certificate @@ -1542,13 +1544,13 @@ function renewClient() { generateClientConfig "$CLIENT" "$homeDir" log_menu "" - log_success "Certificate for client $CLIENT renewed and is valid for $days_valid days." + log_success "Certificate for client $CLIENT renewed and is valid for $client_cert_duration_days days." log_info "The new configuration file has been written to $homeDir/$CLIENT.ovpn." log_info "Download the new .ovpn file and import it in your OpenVPN client." } function renewServer() { - local server_name days_valid + local server_name server_cert_duration_days log_header "Renew Server Certificate" @@ -1568,15 +1570,15 @@ function renewServer() { return fi - # Allow user to specify renewal duration (use DAYS_VALID env var for headless mode) - if [[ -z $DAYS_VALID ]] || ! [[ $DAYS_VALID =~ ^[0-9]+$ ]] || [[ $DAYS_VALID -lt 1 ]]; then + # Allow user to specify renewal duration (use SERVER_CERT_DURATION_DAYS env var for headless mode) + if [[ -z $SERVER_CERT_DURATION_DAYS ]] || ! [[ $SERVER_CERT_DURATION_DAYS =~ ^[0-9]+$ ]] || [[ $SERVER_CERT_DURATION_DAYS -lt 1 ]]; then log_menu "" log_prompt "How many days should the renewed certificate be valid for?" - until [[ $days_valid =~ ^[0-9]+$ ]] && [[ $days_valid -ge 1 ]]; do - read -rp "Certificate validity (days): " -e -i 3650 days_valid + until [[ $server_cert_duration_days =~ ^[0-9]+$ ]] && [[ $server_cert_duration_days -ge 1 ]]; do + read -rp "Certificate validity (days): " -e -i $DEFAULT_CERT_VALIDITY_DURATION_DAYS server_cert_duration_days done else - days_valid=$DAYS_VALID + server_cert_duration_days=$SERVER_CERT_DURATION_DAYS fi cd /etc/openvpn/easy-rsa/ || return @@ -1586,7 +1588,7 @@ function renewServer() { run_cmd "Backing up old certificate" cp "/etc/openvpn/easy-rsa/pki/issued/$server_name.crt" "/etc/openvpn/easy-rsa/pki/issued/$server_name.crt.bak" # Renew the certificate (keeps the same private key) - export EASYRSA_CERT_EXPIRE=$days_valid + export EASYRSA_CERT_EXPIRE=$server_cert_duration_days run_cmd "Renewing certificate" ./easyrsa --batch renew "$server_name" # Revoke the old certificate @@ -1608,7 +1610,7 @@ function renewServer() { run_cmd "Restarting OpenVPN" systemctl restart openvpn@server fi - log_success "Server certificate renewed successfully and is valid for $days_valid days." + log_success "Server certificate renewed successfully and is valid for $server_cert_duration_days days." } function getDaysUntilExpiry() { diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index d176fe1..2f3c38b 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -105,7 +105,7 @@ echo "Original client certificate serial: $ORIG_CERT_SERIAL" # Test client certificate renewal using the script echo "Testing client certificate renewal..." RENEW_OUTPUT="/tmp/renew-client-output.log" -(MENU_OPTION=3 RENEW_OPTION=1 CLIENTNUMBER=1 DAYS_VALID=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RENEW_OUTPUT" || true +(MENU_OPTION=3 RENEW_OPTION=1 CLIENTNUMBER=1 CLIENT_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RENEW_OUTPUT" || true # Verify renewal succeeded if grep -q "Certificate for client testclient renewed" "$RENEW_OUTPUT"; then @@ -185,7 +185,7 @@ echo "Original server certificate serial: $ORIG_SERVER_SERIAL" # Test server certificate renewal echo "Testing server certificate renewal..." RENEW_SERVER_OUTPUT="/tmp/renew-server-output.log" -(MENU_OPTION=3 RENEW_OPTION=2 CONTINUE=y DAYS_VALID=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RENEW_SERVER_OUTPUT" || true +(MENU_OPTION=3 RENEW_OPTION=2 CONTINUE=y SERVER_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RENEW_SERVER_OUTPUT" || true # Verify renewal succeeded if grep -q "Server certificate renewed successfully" "$RENEW_SERVER_OUTPUT"; then From c0fcf91972faf314ad462c4c75d446abb46e3ac7 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Wed, 10 Dec 2025 00:11:25 +0100 Subject: [PATCH 102/141] feat: add ChaCha20-Poly1305 cipher support (#1330) ## Summary - Add `CHACHA20-POLY1305` as a data channel cipher option - Add `ECDHE-*-CHACHA20-POLY1305` control channel cipher options - Add version check (requires OpenVPN 2.5+) - Update README documentation ChaCha20-Poly1305 is particularly useful on devices without hardware AES acceleration (AES-NI), such as ARM-based devices (Raspberry Pi, etc.) and older CPUs, where it can provide better performance than AES. Closes #1244 Closes #190 --- README.md | 5 ++++ openvpn-install.sh | 62 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 78842af..8cc096d 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,8 @@ AES-256 is 40% slower than AES-128, and there isn't any real reason to use a 256 AES-GCM is an [AEAD cipher](https://en.wikipedia.org/wiki/Authenticated_encryption) which means it simultaneously provides confidentiality, integrity, and authenticity assurances on the data. +ChaCha20-Poly1305 is another AEAD cipher that provides similar security to AES-GCM. It is particularly useful on devices without hardware AES acceleration (AES-NI), such as older CPUs and many ARM-based devices, where it can be significantly faster than AES. + The script supports the following ciphers: - `AES-128-GCM` @@ -283,6 +285,7 @@ The script supports the following ciphers: - `AES-128-CBC` - `AES-192-CBC` - `AES-256-CBC` +- `CHACHA20-POLY1305` (requires OpenVPN 2.5+) And defaults to `AES-128-GCM`. @@ -297,9 +300,11 @@ The script proposes the following options, depending on the certificate: - ECDSA: - `TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256` - `TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384` + - `TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256` (requires OpenVPN 2.5+) - RSA: - `TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256` - `TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384` + - `TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256` (requires OpenVPN 2.5+) It defaults to `TLS-ECDHE-*-WITH-AES-128-GCM-SHA256`. diff --git a/openvpn-install.sh b/openvpn-install.sh index 0e507b0..54909e6 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -235,6 +235,29 @@ function initialCheck() { checkOS } +# Check if OpenVPN version is at least the specified version +# Usage: openvpnVersionAtLeast "2.5" +# Returns 0 if version is >= specified, 1 otherwise +function openvpnVersionAtLeast() { + local required_version="$1" + local installed_version + + if ! command -v openvpn &>/dev/null; then + return 1 + fi + + installed_version=$(openvpn --version 2>/dev/null | head -1 | awk '{print $2}') + if [[ -z "$installed_version" ]]; then + return 1 + fi + + # Compare versions using sort -V + if [[ "$(printf '%s\n' "$required_version" "$installed_version" | sort -V | head -n1)" == "$required_version" ]]; then + return 0 + fi + return 1 +} + function installOpenVPNRepo() { log_info "Setting up official OpenVPN repository..." @@ -645,8 +668,9 @@ function installQuestions() { log_menu " 4) AES-128-CBC" log_menu " 5) AES-192-CBC" log_menu " 6) AES-256-CBC" - until [[ $CIPHER_CHOICE =~ ^[1-6]$ ]]; do - read -rp "Cipher [1-6]: " -e -i 1 CIPHER_CHOICE + log_menu " 7) CHACHA20-POLY1305 (requires OpenVPN 2.5+, good for devices without AES-NI)" + until [[ $CIPHER_CHOICE =~ ^[1-7]$ ]]; do + read -rp "Cipher [1-7]: " -e -i 1 CIPHER_CHOICE done case $CIPHER_CHOICE in 1) @@ -667,6 +691,9 @@ function installQuestions() { 6) CIPHER="AES-256-CBC" ;; + 7) + CIPHER="CHACHA20-POLY1305" + ;; esac log_menu "" log_prompt "Choose what kind of certificate you want to use:" @@ -725,8 +752,9 @@ function installQuestions() { 1) log_menu " 1) ECDHE-ECDSA-AES-128-GCM-SHA256 (recommended)" log_menu " 2) ECDHE-ECDSA-AES-256-GCM-SHA384" - until [[ $CC_CIPHER_CHOICE =~ ^[1-2]$ ]]; do - read -rp "Control channel cipher [1-2]: " -e -i 1 CC_CIPHER_CHOICE + log_menu " 3) ECDHE-ECDSA-CHACHA20-POLY1305 (requires OpenVPN 2.5+)" + until [[ $CC_CIPHER_CHOICE =~ ^[1-3]$ ]]; do + read -rp "Control channel cipher [1-3]: " -e -i 1 CC_CIPHER_CHOICE done case $CC_CIPHER_CHOICE in 1) @@ -735,13 +763,17 @@ function installQuestions() { 2) CC_CIPHER="TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384" ;; + 3) + CC_CIPHER="TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256" + ;; esac ;; 2) log_menu " 1) ECDHE-RSA-AES-128-GCM-SHA256 (recommended)" log_menu " 2) ECDHE-RSA-AES-256-GCM-SHA384" - until [[ $CC_CIPHER_CHOICE =~ ^[1-2]$ ]]; do - read -rp "Control channel cipher [1-2]: " -e -i 1 CC_CIPHER_CHOICE + log_menu " 3) ECDHE-RSA-CHACHA20-POLY1305 (requires OpenVPN 2.5+)" + until [[ $CC_CIPHER_CHOICE =~ ^[1-3]$ ]]; do + read -rp "Control channel cipher [1-3]: " -e -i 1 CC_CIPHER_CHOICE done case $CC_CIPHER_CHOICE in 1) @@ -750,6 +782,9 @@ function installQuestions() { 2) CC_CIPHER="TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384" ;; + 3) + CC_CIPHER="TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256" + ;; esac ;; esac @@ -805,10 +840,10 @@ function installQuestions() { ;; esac log_menu "" - # The "auth" options behaves differently with AEAD ciphers + # The "auth" options behaves differently with AEAD ciphers (GCM, ChaCha20-Poly1305) if [[ $CIPHER =~ CBC$ ]]; then log_prompt "The digest algorithm authenticates data channel packets and tls-auth packets from the control channel." - elif [[ $CIPHER =~ GCM$ ]]; then + elif [[ $CIPHER =~ GCM$ ]] || [[ $CIPHER == "CHACHA20-POLY1305" ]]; then log_prompt "The digest algorithm authenticates tls-auth packets from the control channel." fi log_prompt "Which digest algorithm do you want to use for HMAC?" @@ -934,6 +969,17 @@ function installOpenVPN() { elif [[ $OS == 'arch' ]]; then run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl wget ca-certificates curl fi + + # Verify ChaCha20-Poly1305 compatibility if selected + if [[ $CIPHER == "CHACHA20-POLY1305" ]] || [[ $CC_CIPHER =~ CHACHA20 ]]; then + local installed_version + installed_version=$(openvpn --version 2>/dev/null | head -1 | awk '{print $2}') + if ! openvpnVersionAtLeast "2.5"; then + log_fatal "ChaCha20-Poly1305 requires OpenVPN 2.5 or later. Installed version: $installed_version" + fi + log_info "OpenVPN version supports ChaCha20-Poly1305" + fi + # An old version of easy-rsa was available by default in some openvpn packages if [[ -d /etc/openvpn/easy-rsa/ ]]; then run_cmd "Removing old Easy-RSA" rm -rf /etc/openvpn/easy-rsa/ From bda450948a933224f4f779a24a44c6279e1574a1 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Wed, 10 Dec 2025 16:58:35 +0100 Subject: [PATCH 103/141] feat: update EasyRSA version and revoke command --- openvpn-install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 54909e6..9d70913 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -9,8 +9,8 @@ # Configuration constants readonly DEFAULT_CERT_VALIDITY_DURATION_DAYS=3650 # 10 years readonly DEFAULT_CRL_VALIDITY_DURATION_DAYS=5475 # 15 years -readonly EASYRSA_VERSION="3.1.2" -readonly EASYRSA_SHA256="d63cf129490ffd6d8792ede7344806c506c82c32428b5bb609ad97ca6a6e4499" +readonly EASYRSA_VERSION="3.2.3" +readonly EASYRSA_SHA256="577d9a0f5205ee74d818af163c2f3447fff8d3dd11b91523db2fe8d99cd851e3" # ============================================================================= # Logging Configuration @@ -1541,7 +1541,7 @@ function revokeClient() { cd /etc/openvpn/easy-rsa/ || return log_info "Revoking certificate for $CLIENT..." - run_cmd "Revoking certificate" ./easyrsa --batch revoke "$CLIENT" + run_cmd "Revoking certificate" ./easyrsa --batch revoke-issued "$CLIENT" regenerateCRL run_cmd "Removing client config from /home" find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete run_cmd "Removing client config from /root" rm -f "/root/$CLIENT.ovpn" From b9a16500275bff0d264c1546d6cd83ab001b9412 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Wed, 10 Dec 2025 17:54:00 +0100 Subject: [PATCH 104/141] feat: drop Amazon Linux 2 support (#1332) ## Summary - Remove Amazon Linux 2 support from the installer - Amazon Linux 2023 remains fully supported ## Motivation Amazon Linux 2 is reaching EOL. Additionally, Amazon Linux 2 ships with **OpenSSL 1.0.2k** (from 2017) which is incompatible with Easy-RSA 3.2.x. The newer Easy-RSA versions use `openssl x509 -ext` which doesn't exist in OpenSSL 1.0.x, causing certificate generation to fail. This blocks our ability to upgrade Easy-RSA: https://github.com/angristan/openvpn-install/commit/bda450948a933224f4f779a24a44c6279e1574a1 ## Changes - Updated OS detection to reject Amazon Linux 2 with a clear message - Removed Amazon Linux 2 specific code paths (EPEL installation, yum commands) - Removed from CI test matrix - Updated README supported distributions table - Updated Makefile test targets - Also, add Amazon Linux 2023 Unbound handling --- .github/workflows/docker-test.yml | 2 -- Makefile | 4 ---- README.md | 1 - openvpn-install.sh | 33 +++++++++++-------------------- 4 files changed, 12 insertions(+), 28 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 86dc00d..8899eaf 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -54,8 +54,6 @@ jobs: image: oraclelinux:8 - name: oraclelinux-9 image: oraclelinux:9 - - name: amazonlinux-2 - image: amazonlinux:2 - name: amazonlinux-2023 image: amazonlinux:2023 diff --git a/Makefile b/Makefile index 89ec301..7352a6b 100644 --- a/Makefile +++ b/Makefile @@ -102,9 +102,6 @@ test-oracle-8: test-oracle-9: $(MAKE) test BASE_IMAGE=oraclelinux:9 -test-amazon-2: - $(MAKE) test BASE_IMAGE=amazonlinux:2 - test-amazon-2023: $(MAKE) test BASE_IMAGE=amazonlinux:2023 @@ -130,7 +127,6 @@ test-all: $(MAKE) test-almalinux-9 $(MAKE) test-oracle-8 $(MAKE) test-oracle-9 - $(MAKE) test-amazon-2 $(MAKE) test-amazon-2023 $(MAKE) test-arch $(MAKE) test-centos-stream-9 diff --git a/README.md b/README.md index 8cc096d..c270bd3 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,6 @@ The script supports these Linux distributions: | | Support | | ------------------ | ------- | | AlmaLinux >= 8 | ✅ 🤖 | -| Amazon Linux 2 | ✅ 🤖 | | Amazon Linux 2023 | ✅ 🤖 | | Arch Linux | ✅ 🤖 | | CentOS Stream >= 8 | ✅ 🤖 | diff --git a/openvpn-install.sh b/openvpn-install.sh index 9d70913..eea5069 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -3,7 +3,7 @@ # SC1091: Not following /etc/os-release (sourced dynamically) # SC2034: Variables used indirectly or exported for subprocesses -# Secure OpenVPN server installer for Debian, Ubuntu, CentOS, Amazon Linux 2, Fedora, Oracle Linux, Arch Linux, Rocky Linux and AlmaLinux. +# Secure OpenVPN server installer for Debian, Ubuntu, CentOS, Amazon Linux 2023, Fedora, Oracle Linux, Arch Linux, Rocky Linux and AlmaLinux. # https://github.com/angristan/openvpn-install # Configuration constants @@ -209,19 +209,18 @@ function checkOS() { fi fi if [[ $ID == "amzn" ]]; then - if [[ $VERSION_ID == "2" ]]; then - OS="amzn" - elif [[ "$(echo "$PRETTY_NAME" | cut -c 1-18)" == "Amazon Linux 2023." ]] && [[ "$(echo "$PRETTY_NAME" | cut -c 19)" -ge 6 ]]; then + if [[ "$(echo "$PRETTY_NAME" | cut -c 1-18)" == "Amazon Linux 2023." ]] && [[ "$(echo "$PRETTY_NAME" | cut -c 19)" -ge 6 ]]; then OS="amzn2023" else - log_info "The script only supports Amazon Linux 2 or Amazon Linux 2023.6+" + log_info "The script only supports Amazon Linux 2023.6+" + log_info "Amazon Linux 2 is EOL and no longer supported." log_fatal "Your version of Amazon Linux is not supported." fi fi elif [[ -e /etc/arch-release ]]; then OS=arch else - log_fatal "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2, Oracle Linux or Arch Linux system." + log_fatal "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2023, Oracle Linux or Arch Linux system." fi } @@ -330,7 +329,7 @@ hide-version: yes use-caps-for-id: yes prefetch: yes' >>/etc/unbound/unbound.conf - elif [[ $OS =~ (centos|amzn|oracle) ]]; then + elif [[ $OS =~ (centos|oracle) ]]; then run_cmd "Installing Unbound" yum install -y unbound # Configuration @@ -340,7 +339,7 @@ prefetch: yes' >>/etc/unbound/unbound.conf sed -i 's|# hide-version: no|hide-version: yes|' /etc/unbound/unbound.conf sed -i 's|use-caps-for-id: no|use-caps-for-id: yes|' /etc/unbound/unbound.conf - elif [[ $OS == "fedora" ]]; then + elif [[ $OS =~ (fedora|amzn2023) ]]; then run_cmd "Installing Unbound" dnf install -y unbound # Configuration @@ -390,7 +389,7 @@ prefetch: yes' >>/etc/unbound/unbound.conf access-control: fd42:42:42:42::/112 allow' >>/etc/unbound/unbound.conf fi - if [[ ! $OS =~ (fedora|centos|amzn|oracle) ]]; then + if [[ ! $OS =~ (fedora|centos|oracle|amzn2023) ]]; then # DNS Rebinding fix echo "private-address: 10.0.0.0/8 private-address: fd42:42:42:42::/112 @@ -958,10 +957,6 @@ function installOpenVPN() { run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl tar 'policycoreutils-python*' elif [[ $OS == 'oracle' ]]; then run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl tar policycoreutils-python-utils - elif [[ $OS == 'amzn' ]]; then - log_info "Installing EPEL repository..." - run_cmd "Installing EPEL" amazon-linux-extras install -y epel - run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl elif [[ $OS == 'amzn2023' ]]; then run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl wget ca-certificates elif [[ $OS == 'fedora' ]]; then @@ -1750,9 +1745,9 @@ function removeUnbound() { run_cmd "Removing Unbound" apt-get remove --purge -y unbound elif [[ $OS == 'arch' ]]; then run_cmd "Removing Unbound" pacman --noconfirm -R unbound - elif [[ $OS =~ (centos|amzn|oracle) ]]; then + elif [[ $OS =~ (centos|oracle) ]]; then run_cmd "Removing Unbound" yum remove -y unbound - elif [[ $OS == 'fedora' ]]; then + elif [[ $OS =~ (fedora|amzn2023) ]]; then run_cmd "Removing Unbound" dnf remove -y unbound fi @@ -1827,12 +1822,8 @@ function removeOpenVPN() { else run_cmd "Disabling OpenVPN Copr repo" yum copr disable -y @OpenVPN/openvpn-release-2.6 2>/dev/null || true fi - elif [[ $OS =~ (amzn|amzn2023) ]]; then - if [[ $OS == 'amzn2023' ]]; then - run_cmd "Removing OpenVPN" dnf remove -y openvpn - else - run_cmd "Removing OpenVPN" yum remove -y openvpn - fi + elif [[ $OS == 'amzn2023' ]]; then + run_cmd "Removing OpenVPN" dnf remove -y openvpn elif [[ $OS == 'fedora' ]]; then run_cmd "Removing OpenVPN" dnf remove -y openvpn # Disable Copr repo From a4c51f9bf9607a0c108fc1086e3fed14f6933ec6 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Wed, 10 Dec 2025 18:08:54 +0100 Subject: [PATCH 105/141] ci: add Renovate for Easy-RSA version updates (#1333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Add Renovate configuration to automatically track Easy-RSA releases - Add GitHub Action to auto-update SHA256 hash on Renovate PRs ## How it works 1. **Renovate** detects a new Easy-RSA release → creates PR updating `EASYRSA_VERSION` 2. **GitHub Action** triggers on the PR → downloads tarball → computes SHA256 → commits fix 3. PR is ready to merge with both version and hash updated --- I intentionally updated to the second-to-last version in https://github.com/angristan/openvpn-install/commit/bda450948a933224f4f779a24a44c6279e1574a1 to test if this works. --- .github/workflows/update-easyrsa-hash.yml | 71 +++++++++++++++++++++++ renovate.json | 16 +++++ 2 files changed, 87 insertions(+) create mode 100644 .github/workflows/update-easyrsa-hash.yml create mode 100644 renovate.json diff --git a/.github/workflows/update-easyrsa-hash.yml b/.github/workflows/update-easyrsa-hash.yml new file mode 100644 index 0000000..283a180 --- /dev/null +++ b/.github/workflows/update-easyrsa-hash.yml @@ -0,0 +1,71 @@ +name: Update Easy-RSA SHA256 + +# Note: This workflow commits and pushes changes to openvpn-install.sh. +# Infinite recursion is prevented because pushes made with GITHUB_TOKEN do not trigger workflows. +# See: https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#preventing-workflow-runs-from-recursively-generating-new-workflow-runs + +on: + pull_request: + types: [opened, synchronize] + paths: + - "openvpn-install.sh" + +permissions: + contents: read + +jobs: + update-hash: + if: startsWith(github.head_ref, 'renovate/') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version and update SHA256 + run: | + VERSION=$(grep -oP 'EASYRSA_VERSION="\K[^"]+' openvpn-install.sh) + if [ -z "$VERSION" ]; then + echo "Error: Failed to extract EASYRSA_VERSION" + exit 1 + fi + echo "Easy-RSA version: $VERSION" + + CURRENT_SHA=$(grep -oP 'EASYRSA_SHA256="\K[^"]+' openvpn-install.sh) + if [ -z "$CURRENT_SHA" ]; then + echo "Error: Failed to extract EASYRSA_SHA256" + exit 1 + fi + echo "Current SHA256: $CURRENT_SHA" + + TARBALL_URL="https://github.com/OpenVPN/easy-rsa/releases/download/v${VERSION}/EasyRSA-${VERSION}.tgz" + if ! curl -fsSL "$TARBALL_URL" -o /tmp/easyrsa.tgz; then + echo "Error: Failed to download Easy-RSA tarball from $TARBALL_URL" + exit 1 + fi + NEW_SHA=$(sha256sum /tmp/easyrsa.tgz | cut -d' ' -f1) + echo "New SHA256: $NEW_SHA" + + if [ "$CURRENT_SHA" != "$NEW_SHA" ]; then + sed -i "s|EASYRSA_SHA256=\"$CURRENT_SHA\"|EASYRSA_SHA256=\"$NEW_SHA\"|" openvpn-install.sh + echo "SHA256 updated" + echo "HASH_CHANGED=true" >> "$GITHUB_ENV" + else + echo "SHA256 already correct" + fi + + - name: Commit changes + if: env.HASH_CHANGED == 'true' + run: | + if ! git diff --quiet openvpn-install.sh; then + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add openvpn-install.sh + git commit -m "chore: update Easy-RSA SHA256 hash" + git push + else + echo "No changes to commit" + fi diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..faa4ab7 --- /dev/null +++ b/renovate.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "customManagers": [ + { + "customType": "regex", + "managerFilePatterns": ["/^openvpn-install\\.sh$/"], + "matchStrings": [ + "readonly\\s+EASYRSA_VERSION=\"(?\\d+\\.\\d+\\.\\d+)\"" + ], + "depNameTemplate": "OpenVPN/easy-rsa", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v(?.*)$" + } + ] +} From 2f24d2aec72059255057624bb628e31a855a5a22 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Wed, 10 Dec 2025 18:13:57 +0100 Subject: [PATCH 106/141] Remove Dependabot configuration --- .github/dependabot.yml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 8ac6b8c..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" From a6154c2653afd8e2fab21615ca0664ff80be8f78 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Wed, 10 Dec 2025 18:14:57 +0100 Subject: [PATCH 107/141] Disable renovate check for disabled workflow --- .github/workflows/{test.yml => do-test.yml} | 0 renovate.json | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) rename .github/workflows/{test.yml => do-test.yml} (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/do-test.yml similarity index 100% rename from .github/workflows/test.yml rename to .github/workflows/do-test.yml diff --git a/renovate.json b/renovate.json index faa4ab7..7988e9c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,13 +1,12 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended"], + "ignorePaths": [".github/workflows/do-test.yml"], "customManagers": [ { "customType": "regex", "managerFilePatterns": ["/^openvpn-install\\.sh$/"], - "matchStrings": [ - "readonly\\s+EASYRSA_VERSION=\"(?\\d+\\.\\d+\\.\\d+)\"" - ], + "matchStrings": ["readonly\\s+EASYRSA_VERSION=\"(?\\d+\\.\\d+\\.\\d+)\""], "depNameTemplate": "OpenVPN/easy-rsa", "datasourceTemplate": "github-releases", "extractVersionTemplate": "^v(?.*)$" From ba1d0419a814de29825ef3c9dbccc4b92416513f Mon Sep 17 00:00:00 2001 From: Stanislas Date: Wed, 10 Dec 2025 18:23:58 +0100 Subject: [PATCH 108/141] fix: use PAT to trigger CI after hash update (#1337) - Commits made with `GITHUB_TOKEN` don't trigger workflows - Using a PAT allows the hash update commit to trigger CI checks - Fixes the issue where PR #1335 didn't have CI triggered after the hash update --- .github/workflows/update-easyrsa-hash.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-easyrsa-hash.yml b/.github/workflows/update-easyrsa-hash.yml index 283a180..7ae7028 100644 --- a/.github/workflows/update-easyrsa-hash.yml +++ b/.github/workflows/update-easyrsa-hash.yml @@ -1,8 +1,9 @@ name: Update Easy-RSA SHA256 # Note: This workflow commits and pushes changes to openvpn-install.sh. -# Infinite recursion is prevented because pushes made with GITHUB_TOKEN do not trigger workflows. -# See: https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#preventing-workflow-runs-from-recursively-generating-new-workflow-runs +# Uses PAT to trigger CI on the resulting commit. Infinite recursion is prevented +# by the 'renovate/' branch prefix check - CI commits don't re-trigger this workflow. +# Requires: Create a PAT with 'contents: write' scope and add as repository secret 'PAT' on: pull_request: @@ -23,7 +24,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.PAT }} - name: Extract version and update SHA256 run: | From 5d9687f8b008e916eae28aec7a050d1297eb6536 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Wed, 10 Dec 2025 18:32:03 +0100 Subject: [PATCH 109/141] style: format renovate.json with prettier --- renovate.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 7988e9c..ab80063 100644 --- a/renovate.json +++ b/renovate.json @@ -6,7 +6,9 @@ { "customType": "regex", "managerFilePatterns": ["/^openvpn-install\\.sh$/"], - "matchStrings": ["readonly\\s+EASYRSA_VERSION=\"(?\\d+\\.\\d+\\.\\d+)\""], + "matchStrings": [ + "readonly\\s+EASYRSA_VERSION=\"(?\\d+\\.\\d+\\.\\d+)\"" + ], "depNameTemplate": "OpenVPN/easy-rsa", "datasourceTemplate": "github-releases", "extractVersionTemplate": "^v(?.*)$" From 3e46cfb3bd3df1102dd537f1b7da84ea6b05279d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:39:39 +0100 Subject: [PATCH 110/141] chore(deps): update dependency openvpn/easy-rsa to v3.2.4 (#1335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Update | Change | |---|---|---| | [OpenVPN/easy-rsa](https://redirect.github.com/OpenVPN/easy-rsa) | patch | `3.2.3` -> `3.2.4` | --- ### Release Notes
OpenVPN/easy-rsa (OpenVPN/easy-rsa) ### [`v3.2.4`](https://redirect.github.com/OpenVPN/easy-rsa/releases/tag/v3.2.4): 3.2.4 [Compare Source](https://redirect.github.com/OpenVPN/easy-rsa/compare/v3.2.3...v3.2.4) #### What's Changed - export-p12: Move inline file to 'inline/private' folder by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1356](https://redirect.github.com/OpenVPN/easy-rsa/pull/1356) - Restructure help by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1363](https://redirect.github.com/OpenVPN/easy-rsa/pull/1363) - New global option: `--no-lockfile` = env-var: `$EASYRSA_NO_LOCKFILE` by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1364](https://redirect.github.com/OpenVPN/easy-rsa/pull/1364) - Restructure `verify_working_env()` by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1367](https://redirect.github.com/OpenVPN/easy-rsa/pull/1367) - Improve verbose by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1368](https://redirect.github.com/OpenVPN/easy-rsa/pull/1368) - Windows easyrsa-shell-init.sh: Replace 'read -p' by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1371](https://redirect.github.com/OpenVPN/easy-rsa/pull/1371) - mutual\_exclusions(): Include basic checks for --startdate/--enddate by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1372](https://redirect.github.com/OpenVPN/easy-rsa/pull/1372) - easyrsa-shell-init.sh: Allow Easy-RSA to use '\User$HOME' directory by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1374](https://redirect.github.com/OpenVPN/easy-rsa/pull/1374) - Remove 'easyrsa\_mkdir()', use only 'mkdir' by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1376](https://redirect.github.com/OpenVPN/easy-rsa/pull/1376) - revoke: Archive request and private key files and expand help by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1378](https://redirect.github.com/OpenVPN/easy-rsa/pull/1378) - set\_no\_clobber(): Add simple error detection by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1379](https://redirect.github.com/OpenVPN/easy-rsa/pull/1379) - random: Use verify\_working\_env() to configure EASYRSA\_OPENSSL by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1381](https://redirect.github.com/OpenVPN/easy-rsa/pull/1381) - self\_sign(): Force use of Easy-RSA X509-type file 'selfsign' by [@​TinCanTech](https://redirect.github.com/TinCanTech) in [#​1383](https://redirect.github.com/OpenVPN/easy-rsa/pull/1383) **Full Changelog**:
--- ### 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. --- - [ ] 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). --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] --- openvpn-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index eea5069..5a6010f 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -9,8 +9,8 @@ # Configuration constants readonly DEFAULT_CERT_VALIDITY_DURATION_DAYS=3650 # 10 years readonly DEFAULT_CRL_VALIDITY_DURATION_DAYS=5475 # 15 years -readonly EASYRSA_VERSION="3.2.3" -readonly EASYRSA_SHA256="577d9a0f5205ee74d818af163c2f3447fff8d3dd11b91523db2fe8d99cd851e3" +readonly EASYRSA_VERSION="3.2.4" +readonly EASYRSA_SHA256="ed65e88cea892268efa71eb1161ce13af3beded6754301e1e713e36ff3613cac" # ============================================================================= # Logging Configuration From 2ecd4bd6e49dadb51553d33c2938549f898e92ae Mon Sep 17 00:00:00 2001 From: Stanislas Date: Wed, 10 Dec 2025 18:53:45 +0100 Subject: [PATCH 111/141] feat: add Data Channel Offload (DCO) availability check (#1331) - Add detection and logging for OpenVPN Data Channel Offload (DCO) support during installation - DCO is a kernel acceleration feature (merged into Linux 6.16) that improves VPN performance - Add DCO documentation to README --- README.md | 20 +++++++++++++++++++ openvpn-install.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/README.md b/README.md index c270bd3..2861931 100644 --- a/README.md +++ b/README.md @@ -362,6 +362,26 @@ So both provide an additional layer of security and mitigate DoS attacks. They a The script supports both and uses `tls-crypt` by default. +### Data Channel Offload (DCO) + +[Data Channel Offload](https://openvpn.net/as-docs/openvpn-data-channel-offload.html) (DCO) is a kernel acceleration feature that significantly improves OpenVPN performance by keeping data channel encryption/decryption in kernel space, eliminating costly context switches between user and kernel space for each packet. + +DCO was merged into the Linux kernel 6.16 (April 2025). + +**Requirements:** + +- OpenVPN 2.6.0 or later +- Linux kernel 6.16+ (built-in) or `ovpn-dco` kernel module +- UDP protocol (TCP is not supported) +- AEAD cipher (`AES-128-GCM`, `AES-256-GCM`, or `CHACHA20-POLY1305`) +- Compression disabled + +The script's default settings (AES-128-GCM, UDP, no compression) are DCO-compatible. When DCO is available and the configuration is compatible, OpenVPN will automatically use it for improved performance. + +**Note:** DCO must be supported on both the server and the client for full acceleration. Client support is available in OpenVPN 2.6+ (Linux, Windows, FreeBSD) and OpenVPN Connect 3.4+ (Windows). macOS does not currently support DCO, but clients can still connect to DCO-enabled servers with partial performance benefits on the server-side. + +The script will display the DCO availability status during installation. + ## Say thanks You can [say thanks](https://saythanks.io/to/angristan) if you want! diff --git a/openvpn-install.sh b/openvpn-install.sh index 5a6010f..28e8040 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -257,6 +257,42 @@ function openvpnVersionAtLeast() { return 1 } +# Check if kernel version is at least the specified version +# Usage: kernelVersionAtLeast "6.16" +# Returns 0 if version is >= specified, 1 otherwise +function kernelVersionAtLeast() { + local required_version="$1" + local kernel_version + + kernel_version=$(uname -r | cut -d'-' -f1) + if [[ -z "$kernel_version" ]]; then + return 1 + fi + + if [[ "$(printf '%s\n' "$required_version" "$kernel_version" | sort -V | head -n1)" == "$required_version" ]]; then + return 0 + fi + return 1 +} + +# Check if Data Channel Offload (DCO) is available +# DCO requires: OpenVPN 2.6+, kernel support (Linux 6.16+ or ovpn-dco module) +# Returns 0 if DCO is available, 1 otherwise +function isDCOAvailable() { + # DCO requires OpenVPN 2.6+ + if ! openvpnVersionAtLeast "2.6"; then + return 1 + fi + + # DCO is built into Linux 6.16+, or available via ovpn-dco module + if kernelVersionAtLeast "6.16"; then + return 0 + elif lsmod 2>/dev/null | grep -q "^ovpn_dco" || modinfo ovpn-dco &>/dev/null; then + return 0 + fi + return 1 +} + function installOpenVPNRepo() { log_info "Setting up official OpenVPN repository..." @@ -975,6 +1011,18 @@ function installOpenVPN() { log_info "OpenVPN version supports ChaCha20-Poly1305" fi + # Check Data Channel Offload (DCO) availability + if isDCOAvailable; then + # Check if configuration is DCO-compatible + if [[ $PROTOCOL == "udp" ]] && [[ $COMPRESSION_ENABLED == "n" ]] && [[ $CIPHER =~ (GCM|CHACHA20-POLY1305) ]]; then + log_info "Data Channel Offload (DCO) is available and will be used for improved performance" + else + log_info "Data Channel Offload (DCO) is available but not enabled (requires UDP, AEAD cipher, no compression)" + fi + else + log_info "Data Channel Offload (DCO) is not available (requires OpenVPN 2.6+ and kernel support)" + fi + # An old version of easy-rsa was available by default in some openvpn packages if [[ -d /etc/openvpn/easy-rsa/ ]]; then run_cmd "Removing old Easy-RSA" rm -rf /etc/openvpn/easy-rsa/ From 9162924468d2c0c7ae0d91fad42059626e563d6a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:29:01 +0100 Subject: [PATCH 112/141] chore(deps): update actions/checkout action to v6 (#1338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [actions/checkout](https://redirect.github.com/actions/checkout) | action | major | `v4` -> `v6` | --- ### Release Notes
actions/checkout (actions/checkout) ### [`v6`](https://redirect.github.com/actions/checkout/compare/v5...v6) [Compare Source](https://redirect.github.com/actions/checkout/compare/v5...v6) ### [`v5`](https://redirect.github.com/actions/checkout/compare/v4...v5) [Compare Source](https://redirect.github.com/actions/checkout/compare/v4...v5)
--- ### 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. --- - [ ] 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). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docker-test.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/update-easyrsa-hash.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 8899eaf..bdb1c2a 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -59,7 +59,7 @@ jobs: name: ${{ matrix.os.name }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3093834..2eabb91 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Lint Code Base diff --git a/.github/workflows/update-easyrsa-hash.yml b/.github/workflows/update-easyrsa-hash.yml index 7ae7028..7245e7a 100644 --- a/.github/workflows/update-easyrsa-hash.yml +++ b/.github/workflows/update-easyrsa-hash.yml @@ -21,7 +21,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ github.head_ref }} token: ${{ secrets.PAT }} From 1aae852c607baa1eb9c704dbc4cd9d51a32ca371 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:12:00 +0100 Subject: [PATCH 113/141] chore(deps): update super-linter/super-linter action to v8 (#1339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [super-linter/super-linter](https://redirect.github.com/super-linter/super-linter) | action | major | `v7` -> `v8` | --- ### Release Notes
super-linter/super-linter (super-linter/super-linter) ### [`v8`](https://redirect.github.com/super-linter/super-linter/blob/HEAD/CHANGELOG.md#680-2024-07-31) [Compare Source](https://redirect.github.com/super-linter/super-linter/compare/v7...v8) ##### 🚀 Features - allow using both prettier and standardjs ([#​5679](https://redirect.github.com/super-linter/super-linter/issues/5679)) ([2daf461](https://redirect.github.com/super-linter/super-linter/commit/2daf461143778318ccc60ba59f175d385f222e4d)) - customize phpstan config file name ([#​5940](https://redirect.github.com/super-linter/super-linter/issues/5940)) ([20c4df5](https://redirect.github.com/super-linter/super-linter/commit/20c4df58c060cf122dce53b14ec578d48d921fb0)) - enable dotenv-linter for slim images ([#​5868](https://redirect.github.com/super-linter/super-linter/issues/5868)) ([c770a8d](https://redirect.github.com/super-linter/super-linter/commit/c770a8d253f8b8852fd3404b63b807d93cc3691a)) - remove no-eslintrc and simplify eslint conf ([#​5809](https://redirect.github.com/super-linter/super-linter/issues/5809)) ([5be4926](https://redirect.github.com/super-linter/super-linter/commit/5be49266330f237f4a8ab2acccffe93b69cf4b04)), closes [#​5688](https://redirect.github.com/super-linter/super-linter/issues/5688) - write github actions step summary ([#​5867](https://redirect.github.com/super-linter/super-linter/issues/5867)) ([57c8658](https://redirect.github.com/super-linter/super-linter/commit/57c86588c388a515ae31f498b4cc4a6337169c11)), closes [#​5650](https://redirect.github.com/super-linter/super-linter/issues/5650) ##### 🐛 Bugfixes - avoid duplicated content in summary ([#​5939](https://redirect.github.com/super-linter/super-linter/issues/5939)) ([ef57e13](https://redirect.github.com/super-linter/super-linter/commit/ef57e132e136ab9991a0cc1517236e827d27baf9)) - store outputs in the main output directory ([#​5899](https://redirect.github.com/super-linter/super-linter/issues/5899)) ([78ed3ef](https://redirect.github.com/super-linter/super-linter/commit/78ed3ef5fc8fe45290b530f94077281d2ac8e634)) - update pylint to ignore import-errors ([#​5927](https://redirect.github.com/super-linter/super-linter/issues/5927)) ([eec862d](https://redirect.github.com/super-linter/super-linter/commit/eec862d0ea33fe6a836dfd545eef1125e530c794)) - update the list of linters to remove ([#​5870](https://redirect.github.com/super-linter/super-linter/issues/5870)) ([6bd7659](https://redirect.github.com/super-linter/super-linter/commit/6bd76596f3f7df70a323eabb797f028293873424)) ##### ⬆️ Dependency updates - **bundler:** bump rubocop-minitest in /dependencies ([#​5875](https://redirect.github.com/super-linter/super-linter/issues/5875)) ([9751e62](https://redirect.github.com/super-linter/super-linter/commit/9751e62beed14780ff12fc073c7aa9eafbd449f7)) - **bundler:** bump rubocop-performance in /dependencies ([#​5777](https://redirect.github.com/super-linter/super-linter/issues/5777)) ([763dcc4](https://redirect.github.com/super-linter/super-linter/commit/763dcc4d456ae0c347801d30b5ec00dbfaa60ef2)) - **bundler:** bump rubocop-rspec from 2.30.0 to 3.0.3 in /dependencies ([#​5878](https://redirect.github.com/super-linter/super-linter/issues/5878)) ([592d903](https://redirect.github.com/super-linter/super-linter/commit/592d903c509a4c70c89cc009e74b63c3273c5e7b)) - **dev-docker:** bump node in /dev-dependencies ([#​5872](https://redirect.github.com/super-linter/super-linter/issues/5872)) ([587fe0a](https://redirect.github.com/super-linter/super-linter/commit/587fe0a8b9318324fbfd86b9cd87b0ac6b24ba42)) - **dev-npm:** bump release-please in /dev-dependencies ([#​5754](https://redirect.github.com/super-linter/super-linter/issues/5754)) ([6bb3f78](https://redirect.github.com/super-linter/super-linter/commit/6bb3f789bb5cacff35492a1245befb2fd13b97f5)) - **docker:** bump alpine/helm from 3.14.4 to 3.15.3 ([#​5882](https://redirect.github.com/super-linter/super-linter/issues/5882)) ([b5bf9f2](https://redirect.github.com/super-linter/super-linter/commit/b5bf9f297d13e9563bd708ae98e2e05b9b44698c)) - **docker:** bump alpine/terragrunt from 1.9.0 to 1.9.2 ([#​5883](https://redirect.github.com/super-linter/super-linter/issues/5883)) ([95feeac](https://redirect.github.com/super-linter/super-linter/commit/95feeacb0ab075e7e183c5f8ea1c515992f66bbf)) - **docker:** bump dart from 3.4.2-sdk to 3.4.4-sdk ([#​5764](https://redirect.github.com/super-linter/super-linter/issues/5764)) ([b75f1cf](https://redirect.github.com/super-linter/super-linter/commit/b75f1cfcefc1b62a81b916bd00d0b8dfa7ad69aa)) - **docker:** bump dotnet/sdk ([#​5873](https://redirect.github.com/super-linter/super-linter/issues/5873)) ([f068663](https://redirect.github.com/super-linter/super-linter/commit/f06866359b4158e04825a03d5852823d47d78b05)) - **docker:** bump golangci/golangci-lint from v1.59.0 to v1.59.1 ([#​5748](https://redirect.github.com/super-linter/super-linter/issues/5748)) ([81ab76d](https://redirect.github.com/super-linter/super-linter/commit/81ab76d00179b8d66e7f909c546cb7bfbde8ae9f)) - **docker:** bump goreleaser/goreleaser from v1.26.2 to v2.1.0 ([#​5881](https://redirect.github.com/super-linter/super-linter/issues/5881)) ([d84d439](https://redirect.github.com/super-linter/super-linter/commit/d84d4393933047f08abf0871aacefa04c63b5f32)) - **docker:** bump hashicorp/terraform from 1.8.4 to 1.9.2 ([#​5885](https://redirect.github.com/super-linter/super-linter/issues/5885)) ([d384e67](https://redirect.github.com/super-linter/super-linter/commit/d384e674c729dfd236ce888d04e4bdd24a85b630)) - **docker:** bump mstruebing/editorconfig-checker from v3.0.1 to v3.0.3 ([#​5856](https://redirect.github.com/super-linter/super-linter/issues/5856)) ([81196f4](https://redirect.github.com/super-linter/super-linter/commit/81196f4267199e63ec82dd0aaff9d1f400b38cac)) - **docker:** bump python from 3.12.3-alpine3.20 to 3.12.4-alpine3.20 ([#​5884](https://redirect.github.com/super-linter/super-linter/issues/5884)) ([8a044b5](https://redirect.github.com/super-linter/super-linter/commit/8a044b58dee1dd7a03fcaf2d7d6b58754f156be2)) - **docker:** bump scalameta/scalafmt from v3.8.1 to v3.8.2 ([#​5765](https://redirect.github.com/super-linter/super-linter/issues/5765)) ([4931da5](https://redirect.github.com/super-linter/super-linter/commit/4931da55da35a8b438354adc03d220f693458f75)) - **docker:** bump terraform-linters/tflint from v0.51.2 to v0.52.0 ([#​5858](https://redirect.github.com/super-linter/super-linter/issues/5858)) ([ae1dba5](https://redirect.github.com/super-linter/super-linter/commit/ae1dba53fdc862da0064f56382dd86cd273862f6)) - **docker:** bump yoheimuta/protolint from 0.50.2 to 0.50.3 ([#​5857](https://redirect.github.com/super-linter/super-linter/issues/5857)) ([913bd0d](https://redirect.github.com/super-linter/super-linter/commit/913bd0dd47d3a42d6f2d3acad8fb59c2f185337b)) - **docker:** bump zricethezav/gitleaks from v8.18.3 to v8.18.4 ([#​5768](https://redirect.github.com/super-linter/super-linter/issues/5768)) ([33bb4b4](https://redirect.github.com/super-linter/super-linter/commit/33bb4b46d473c80a82364c50746ed20d19231f90)) - **github-actions:** bump actions/download-artifact from 4.1.7 to 4.1.8 ([#​5861](https://redirect.github.com/super-linter/super-linter/issues/5861)) ([ed72e66](https://redirect.github.com/super-linter/super-linter/commit/ed72e66416df8df15b492f7df00b8621b556893f)) - **github-actions:** bump actions/upload-artifact from 4.3.3 to 4.3.4 ([#​5860](https://redirect.github.com/super-linter/super-linter/issues/5860)) ([dd4313c](https://redirect.github.com/super-linter/super-linter/commit/dd4313c9b3bee70bfd968923e77430f3f19785aa)) - **github-actions:** bump docker/build-push-action from 5 to 6 ([#​5770](https://redirect.github.com/super-linter/super-linter/issues/5770)) ([27170b8](https://redirect.github.com/super-linter/super-linter/commit/27170b8e9243df7e18e59e7c43163b142568ee22)) - **java:** bump com.pinterest.ktlint:ktlint-cli in /dependencies/ktlint ([#​5849](https://redirect.github.com/super-linter/super-linter/issues/5849)) ([19c5fce](https://redirect.github.com/super-linter/super-linter/commit/19c5fcea2ea6a72741e59a30cb815c5faac524be)) - **npm:** bump [@​babel/eslint-parser](https://redirect.github.com/babel/eslint-parser) in /dependencies ([#​5886](https://redirect.github.com/super-linter/super-linter/issues/5886)) ([387a2b5](https://redirect.github.com/super-linter/super-linter/commit/387a2b56261451b12b00fc37d2fb79766564044d)) - **npm:** bump [@​babel/preset-react](https://redirect.github.com/babel/preset-react) in /dependencies ([#​5740](https://redirect.github.com/super-linter/super-linter/issues/5740)) ([4eeb628](https://redirect.github.com/super-linter/super-linter/commit/4eeb62862e823fd431fefb0078e5605484934254)) - **npm:** bump [@​babel/preset-typescript](https://redirect.github.com/babel/preset-typescript) in /dependencies ([#​5734](https://redirect.github.com/super-linter/super-linter/issues/5734)) ([de4b193](https://redirect.github.com/super-linter/super-linter/commit/de4b1930064cb6b5f65625101be33892c50d400f)) - **npm:** bump [@​react-native/eslint-config](https://redirect.github.com/react-native/eslint-config) in /dependencies ([#​5835](https://redirect.github.com/super-linter/super-linter/issues/5835)) ([28c228d](https://redirect.github.com/super-linter/super-linter/commit/28c228dfc0b8279e28ca5bb72a34e9390eca81b9)) - **npm:** bump [@​typescript-eslint/eslint-plugin](https://redirect.github.com/typescript-eslint/eslint-plugin) in /dependencies ([#​5895](https://redirect.github.com/super-linter/super-linter/issues/5895)) ([7f5b018](https://redirect.github.com/super-linter/super-linter/commit/7f5b018fb7237ef0d6d1cb1578729065bacb4310)) - **npm:** bump eslint-plugin-jest in /dependencies ([#​5738](https://redirect.github.com/super-linter/super-linter/issues/5738)) ([1312398](https://redirect.github.com/super-linter/super-linter/commit/1312398b9c72138ce76f443dd933a8ff82f75fae)) - **npm:** bump eslint-plugin-jsx-a11y in /dependencies ([#​5797](https://redirect.github.com/super-linter/super-linter/issues/5797)) ([8972772](https://redirect.github.com/super-linter/super-linter/commit/89727727323246be93c3ed88c0b0fe1f07e25468)) - **npm:** bump eslint-plugin-react in /dependencies ([#​5890](https://redirect.github.com/super-linter/super-linter/issues/5890)) ([fe3e1f8](https://redirect.github.com/super-linter/super-linter/commit/fe3e1f83b7c7645640af9202bb7f4d8993f088fc)) - **npm:** bump eslint-plugin-vue from 9.26.0 to 9.27.0 in /dependencies ([#​5851](https://redirect.github.com/super-linter/super-linter/issues/5851)) ([c2e85a9](https://redirect.github.com/super-linter/super-linter/commit/c2e85a9f03ceccfe41107ea1e627c8c222b2154a)) - **npm:** bump jscpd from 4.0.4 to 4.0.5 in /dependencies ([#​5852](https://redirect.github.com/super-linter/super-linter/issues/5852)) ([042c6b1](https://redirect.github.com/super-linter/super-linter/commit/042c6b191708878b5db2b5096eadb1b4a6f4f27d)) - **npm:** bump next from 14.2.3 to 14.2.5 in /dependencies ([#​5887](https://redirect.github.com/super-linter/super-linter/issues/5887)) ([22b7ba9](https://redirect.github.com/super-linter/super-linter/commit/22b7ba91d0d273fdc5369d1ead66ae2cee451df0)) - **npm:** bump prettier from 3.3.2 to 3.3.3 in /dependencies ([#​5891](https://redirect.github.com/super-linter/super-linter/issues/5891)) ([b601212](https://redirect.github.com/super-linter/super-linter/commit/b6012126df99c9a8cdc5878bb9417ca7afee6809)) - **npm:** bump react-router-dom from 6.23.1 to 6.25.0 in /dependencies ([#​5897](https://redirect.github.com/super-linter/super-linter/issues/5897)) ([ef71e94](https://redirect.github.com/super-linter/super-linter/commit/ef71e944ab41a8f04ba143f44d4b8443d2d982b5)) - **npm:** bump renovate from 37.421.5 to 37.432.0 in /dependencies ([#​5896](https://redirect.github.com/super-linter/super-linter/issues/5896)) ([09a01eb](https://redirect.github.com/super-linter/super-linter/commit/09a01ebbe6dbb5e502817bb927191b8986c7c912)) - **npm:** bump textlint-rule-terminology in /dependencies ([#​5853](https://redirect.github.com/super-linter/super-linter/issues/5853)) ([55b065d](https://redirect.github.com/super-linter/super-linter/commit/55b065d3c2d512648c52d4d695761a7d274ae769)) - **npm:** bump typescript from 5.4.5 to 5.5.3 in /dependencies ([#​5832](https://redirect.github.com/super-linter/super-linter/issues/5832)) ([8605c2b](https://redirect.github.com/super-linter/super-linter/commit/8605c2b584b36201ac8d1acf98254c2d70776c75)) - **python:** bump ansible-lint in /dependencies/python ([#​5877](https://redirect.github.com/super-linter/super-linter/issues/5877)) ([e90ee32](https://redirect.github.com/super-linter/super-linter/commit/e90ee328a2f6eb42952eeda45929271fddaf0c77)) - **python:** bump cfn-lint from 1.4.2 to 1.6.1 in /dependencies/python ([#​5876](https://redirect.github.com/super-linter/super-linter/issues/5876)) ([ebf8cc8](https://redirect.github.com/super-linter/super-linter/commit/ebf8cc807a6c756755e790419fefcf9e5353b911)) - **python:** bump checkov in /dependencies/python ([#​5879](https://redirect.github.com/super-linter/super-linter/issues/5879)) ([47392ad](https://redirect.github.com/super-linter/super-linter/commit/47392ad6636919b4b21e6ef2c3e1418cb1e931f3)) - **python:** bump flake8 from 7.0.0 to 7.1.0 in /dependencies/python ([#​5780](https://redirect.github.com/super-linter/super-linter/issues/5780)) ([f019ee3](https://redirect.github.com/super-linter/super-linter/commit/f019ee34d21aa778d4b04217e7f717779ddfa767)) - **python:** bump ruff from 0.5.0 to 0.5.2 in /dependencies/python ([#​5880](https://redirect.github.com/super-linter/super-linter/issues/5880)) ([3fd69a1](https://redirect.github.com/super-linter/super-linter/commit/3fd69a107b05f04135f1c01544c50b940a3b1e99)) - **python:** bump snakemake in /dependencies/python ([#​5874](https://redirect.github.com/super-linter/super-linter/issues/5874)) ([2b6aa12](https://redirect.github.com/super-linter/super-linter/commit/2b6aa1290608468ee3828efdeabbeb467be43b80)) - **python:** bump sqlfluff from 3.0.7 to 3.1.0 in /dependencies/python ([#​5847](https://redirect.github.com/super-linter/super-linter/issues/5847)) ([31da61e](https://redirect.github.com/super-linter/super-linter/commit/31da61e1899ca3c9d26681c265b0928b559f1274)) ##### 🧰 Maintenance - add super-linter configuration in the bug template ([#​5910](https://redirect.github.com/super-linter/super-linter/issues/5910)) ([26ddd8b](https://redirect.github.com/super-linter/super-linter/commit/26ddd8b084201554e0351abf421d184864076a56)) - authenticate tflint init ([#​5894](https://redirect.github.com/super-linter/super-linter/issues/5894)) ([cc20e45](https://redirect.github.com/super-linter/super-linter/commit/cc20e4561e549c6f6609900ba68602930c01fb7d)) - bump alpine image to 3.20 and php to 8.3.x ([#​5863](https://redirect.github.com/super-linter/super-linter/issues/5863)) ([d9d1909](https://redirect.github.com/super-linter/super-linter/commit/d9d19095ecf48511e4c21a2e23f0def36187d141)) - enable dev-dependencies docker build checks ([#​5871](https://redirect.github.com/super-linter/super-linter/issues/5871)) ([12da497](https://redirect.github.com/super-linter/super-linter/commit/12da4973c69e6987217089a418199a8227f658d7)) - fix docker build warnings ([#​5862](https://redirect.github.com/super-linter/super-linter/issues/5862)) ([fc094cc](https://redirect.github.com/super-linter/super-linter/commit/fc094cc1a4be6a290c49c633fb857c454a98da47)) - print info about the environment, image size ([#​5869](https://redirect.github.com/super-linter/super-linter/issues/5869)) ([bcf8ca8](https://redirect.github.com/super-linter/super-linter/commit/bcf8ca82ad571bb0a04f028193b3f56486cfbeb4)) - remove duplicated configuration files ([#​5928](https://redirect.github.com/super-linter/super-linter/issues/5928)) ([70e0239](https://redirect.github.com/super-linter/super-linter/commit/70e02391170f6aaff4f0b4e80bf9bc8300940171)) - run docker build checks ([#​5864](https://redirect.github.com/super-linter/super-linter/issues/5864)) ([ce59f5c](https://redirect.github.com/super-linter/super-linter/commit/ce59f5c323a8e5038753582dc265ab172b654834)) - split validation logic in smaller functions ([#​5892](https://redirect.github.com/super-linter/super-linter/issues/5892)) ([d2d7334](https://redirect.github.com/super-linter/super-linter/commit/d2d73347d3e49fb28061a41df471afdcf0a579ba)) - update dependabot config for ci/dev updates ([#​5898](https://redirect.github.com/super-linter/super-linter/issues/5898)) ([e374e48](https://redirect.github.com/super-linter/super-linter/commit/e374e489339410662293e39f4147dc69d098077f)) - update release-please to the new workspace ([#​5901](https://redirect.github.com/super-linter/super-linter/issues/5901)) ([2ecf945](https://redirect.github.com/super-linter/super-linter/commit/2ecf94533920fb6a90ba5f029230cd4cdc69f855))
--- ### 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. --- - [ ] 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). --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Stanislas Lange --- .editorconfig | 7 +++++++ .github/linters/.markdown-lint.yml | 9 ++++++++- .github/workflows/do-test.yml | 19 ++++++++++++------- .github/workflows/docker-test.yml | 4 +++- .github/workflows/lint.yml | 3 ++- .github/workflows/update-easyrsa-hash.yml | 1 + .trivyignore | 8 ++++++++ FAQ.md | 10 +++++----- README.md | 2 +- biome.json | 7 +++++++ test/Dockerfile.client | 2 +- test/Dockerfile.server | 2 +- 12 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 .trivyignore create mode 100644 biome.json diff --git a/.editorconfig b/.editorconfig index b4d9ec4..2dc2678 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,10 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true + [*.sh] indent_style = tab indent_size = 4 diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index 695685c..2633810 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -1 +1,8 @@ -{ "MD013": null, "MD045": null, "MD040": null, "MD036": null, "MD041": null } +{ + "MD013": null, + "MD045": null, + "MD040": null, + "MD036": null, + "MD041": null, + "MD060": null, +} diff --git a/.github/workflows/do-test.yml b/.github/workflows/do-test.yml index 5d609f1..f176a04 100644 --- a/.github/workflows/do-test.yml +++ b/.github/workflows/do-test.yml @@ -24,9 +24,11 @@ jobs: # - centos-stream-9-x64 # yum oomkill steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup doctl - uses: digitalocean/action-doctl@v2 + uses: digitalocean/action-doctl@135ac0aa0eed4437d547c6f12c364d3006b42824 # v2.5.1 with: token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} @@ -38,7 +40,10 @@ jobs: id: server_id - name: Move server to dedicated project - run: doctl projects resources assign ${{ secrets.DIGITALOCEAN_PROJECT_ID }} --resource=do:droplet:${{ steps.server_id.outputs.value }} + run: doctl projects resources assign "$DIGITALOCEAN_PROJECT_ID" --resource=do:droplet:"$SERVER_ID" + env: + DIGITALOCEAN_PROJECT_ID: ${{ secrets.DIGITALOCEAN_PROJECT_ID }} + SERVER_ID: ${{ steps.server_id.outputs.value }} - name: Wait for server to boot run: sleep 90 @@ -53,7 +58,7 @@ jobs: - name: Setup remote server (Debian/Ubuntu) if: steps.server_os.outputs.value == 'debian' || steps.server_os.outputs.value == 'ubuntu' - uses: appleboy/ssh-action@v1.2.0 + uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -62,7 +67,7 @@ jobs: - name: Setup remote server (Fedora) if: steps.server_os.outputs.value == 'fedora' - uses: appleboy/ssh-action@v1.2.0 + uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -71,7 +76,7 @@ jobs: - name: Setup remote server (CentOS) if: steps.server_os.outputs.value == 'centos' - uses: appleboy/ssh-action@v1.2.0 + uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -79,7 +84,7 @@ jobs: script: set -x && yum install -y git - name: Download repo and checkout current commit - uses: appleboy/ssh-action@v1.2.0 + uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root @@ -87,7 +92,7 @@ jobs: script: set -x && git clone https://github.com/angristan/openvpn-install.git && cd openvpn-install && git checkout ${{ github.sha }} - name: Run openvpn-install.sh in headless mode - uses: appleboy/ssh-action@v1.2.0 + uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0 with: host: ${{ steps.server_ip.outputs.value }} username: root diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index bdb1c2a..215f7d3 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -60,9 +60,11 @@ jobs: name: ${{ matrix.os.name }} steps: - uses: actions/checkout@v6 + with: + persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Build server image run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2eabb91..6a04a2f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,7 +21,8 @@ jobs: uses: actions/checkout@v6 with: fetch-depth: 0 + persist-credentials: false - name: Lint Code Base - uses: super-linter/super-linter@v7 + uses: super-linter/super-linter@502f4fe48a81a392756e173e39a861f8c8efe056 # v8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-easyrsa-hash.yml b/.github/workflows/update-easyrsa-hash.yml index 7245e7a..d3cff5c 100644 --- a/.github/workflows/update-easyrsa-hash.yml +++ b/.github/workflows/update-easyrsa-hash.yml @@ -25,6 +25,7 @@ jobs: with: ref: ${{ github.head_ref }} token: ${{ secrets.PAT }} + persist-credentials: false - name: Extract version and update SHA256 run: | diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..69f9cb1 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,8 @@ +# Test containers require root for OpenVPN NET_ADMIN capability +AVD-DS-0002 + +# Test containers don't need healthcheck +AVD-DS-0026 + +# False positive: yum clean all is present in the conditional but Trivy doesn't detect it +AVD-DS-0015 diff --git a/FAQ.md b/FAQ.md index cef7f2d..12dc709 100644 --- a/FAQ.md +++ b/FAQ.md @@ -35,7 +35,7 @@ up /etc/openvpn/update-resolv-conf down /etc/openvpn/update-resolv-conf ``` -Centos 6, 7 +CentOS 6, 7 ``` script-security 2 @@ -43,7 +43,7 @@ up /usr/share/doc/openvpn-2.4.8/contrib/pull-resolv-conf/client.up down /usr/share/doc/openvpn-2.4.8/contrib/pull-resolv-conf/client.down ``` -Centos 8, Fedora 30, 31 +CentOS 8, Fedora 30, 31 ``` script-security 2 @@ -117,13 +117,13 @@ Sysctl options are at `/etc/sysctl.d/99-openvpn.conf` **Q:** How can I access computers the OpenVPN server's remote LAN? -**A:** Add a route with the subnet of the remote network to `/etc/openvpn/server.conf` and restart openvpn. Example: `push "route 192.168.1.0 255.255.255.0"` if the server's LAN is `192.168.1.0/24` +**A:** Add a route with the subnet of the remote network to `/etc/openvpn/server.conf` and restart OpenVPN. Example: `push "route 192.168.1.0 255.255.255.0"` if the server's LAN is `192.168.1.0/24` --- **Q:** How can I add multiple users in one go? -**A:** Here is a sample bash script to achieve this: +**A:** Here is a sample Bash script to achieve this: ```sh userlist=(user1 user2 user3) @@ -158,7 +158,7 @@ route-nopull route 10.0.0.0 255.0.0.0 ``` -So for example - here it would route all traffic of `10.0.0.0/8` to the vpn. And the rest through the internet. +So for example - here it would route all traffic of `10.0.0.0/8` to the VPN. And the rest through the internet. --- diff --git a/README.md b/README.md index 2861931..30aadbc 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,7 @@ Please open an issue before submitting a PR if you want to discuss a change, esp ### Code formatting -We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https://github.com/mvdan/sh) to enforce bash styling guidelines and good practices. They are executed for each commit / PR with GitHub Actions, so you can check the configuration [here](https://github.com/angristan/openvpn-install/blob/master/.github/workflows/lint.yml). +We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https://github.com/mvdan/sh) to enforce Bash styling guidelines and good practices. They are executed for each commit / PR with GitHub Actions, so you can check the [lint workflow configuration](https://github.com/angristan/openvpn-install/blob/master/.github/workflows/lint.yml). ## Security and Encryption diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..cd69666 --- /dev/null +++ b/biome.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "formatter": { + "indentStyle": "space", + "indentWidth": 2 + } +} diff --git a/test/Dockerfile.client b/test/Dockerfile.client index a8121e3..6ce945a 100644 --- a/test/Dockerfile.client +++ b/test/Dockerfile.client @@ -5,7 +5,7 @@ FROM ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive # Install OpenVPN client and testing tools -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --no-install-recommends \ openvpn \ iproute2 \ iputils-ping \ diff --git a/test/Dockerfile.server b/test/Dockerfile.server index acf6f0f..2cee391 100644 --- a/test/Dockerfile.server +++ b/test/Dockerfile.server @@ -9,7 +9,7 @@ ENV DEBIAN_FRONTEND=noninteractive # Install basic dependencies based on the OS RUN if command -v apt-get >/dev/null; then \ - apt-get update && apt-get install -y \ + apt-get update && apt-get install -y --no-install-recommends \ iproute2 iptables curl procps systemd systemd-sysv \ && rm -rf /var/lib/apt/lists/*; \ elif command -v dnf >/dev/null; then \ From 2374e4e81c05aaa59c2e8ffd0b0f74f63e5dbcea Mon Sep 17 00:00:00 2001 From: Stanislas Date: Thu, 11 Dec 2025 13:14:56 +0100 Subject: [PATCH 114/141] Refactor Unbound setup and add E2E tests (#1340) Refactor Unbound DNS installation to use modern `conf.d` pattern and add E2E testing. **Changes:** - Unified Unbound config across all distros using `/etc/unbound/unbound.conf.d/openvpn.conf` - Added startup validation with retry logic - Added `ip-freebind` to allow binding before tun interface exists - E2E tests now verify Unbound DNS resolution from VPN clients **Testing:** - Server: verifies config creation, interface binding, security options - Client: verifies DNS resolution through Unbound (10.8.0.1) --- Closes https://github.com/angristan/openvpn-install/issues/602 Closes https://github.com/angristan/openvpn-install/pull/604 Closes https://github.com/angristan/openvpn-install/issues/1189 Co-authored-by: Henry N --- openvpn-install.sh | 178 +++++++++++++++----------------------- test/Dockerfile.client | 2 + test/Dockerfile.server | 9 +- test/client-entrypoint.sh | 22 +++++ test/server-entrypoint.sh | 81 ++++++++++++++++- 5 files changed, 180 insertions(+), 112 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 28e8040..31087c1 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -351,120 +351,84 @@ function installOpenVPNRepo() { function installUnbound() { log_info "Installing Unbound DNS resolver..." - # If Unbound isn't installed, install it - if [[ ! -e /etc/unbound/unbound.conf ]]; then + # Install Unbound if not present + if [[ ! -e /etc/unbound/unbound.conf ]]; then if [[ $OS =~ (debian|ubuntu) ]]; then run_cmd "Installing Unbound" apt-get install -y unbound - - # Configuration - echo 'interface: 10.8.0.1 -access-control: 10.8.0.1/24 allow -hide-identity: yes -hide-version: yes -use-caps-for-id: yes -prefetch: yes' >>/etc/unbound/unbound.conf - elif [[ $OS =~ (centos|oracle) ]]; then run_cmd "Installing Unbound" yum install -y unbound - - # Configuration - sed -i 's|# interface: 0.0.0.0$|interface: 10.8.0.1|' /etc/unbound/unbound.conf - sed -i 's|# access-control: 127.0.0.0/8 allow|access-control: 10.8.0.1/24 allow|' /etc/unbound/unbound.conf - sed -i 's|# hide-identity: no|hide-identity: yes|' /etc/unbound/unbound.conf - sed -i 's|# hide-version: no|hide-version: yes|' /etc/unbound/unbound.conf - sed -i 's|use-caps-for-id: no|use-caps-for-id: yes|' /etc/unbound/unbound.conf - elif [[ $OS =~ (fedora|amzn2023) ]]; then run_cmd "Installing Unbound" dnf install -y unbound - - # Configuration - sed -i 's|# interface: 0.0.0.0$|interface: 10.8.0.1|' /etc/unbound/unbound.conf - sed -i 's|# access-control: 127.0.0.0/8 allow|access-control: 10.8.0.1/24 allow|' /etc/unbound/unbound.conf - sed -i 's|# hide-identity: no|hide-identity: yes|' /etc/unbound/unbound.conf - sed -i 's|# hide-version: no|hide-version: yes|' /etc/unbound/unbound.conf - sed -i 's|# use-caps-for-id: no|use-caps-for-id: yes|' /etc/unbound/unbound.conf - elif [[ $OS == "arch" ]]; then run_cmd "Installing Unbound" pacman -Syu --noconfirm unbound - - # Get root servers list - run_cmd "Downloading root hints" curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache - # Verify download was successful and file contains expected content - if [[ ! -s /etc/unbound/root.hints ]] || ! grep -q "ROOT-SERVERS" /etc/unbound/root.hints; then - run_cmd "Cleaning up invalid file" rm -f /etc/unbound/root.hints - log_fatal "Failed to download root.hints or file is invalid!" - fi - - if [[ ! -f /etc/unbound/unbound.conf.old ]]; then - run_cmd "Backing up unbound.conf" mv /etc/unbound/unbound.conf /etc/unbound/unbound.conf.old - fi - - echo 'server: - use-syslog: yes - do-daemonize: no - username: "unbound" - directory: "/etc/unbound" - trust-anchor-file: trusted-key.key - root-hints: root.hints - interface: 10.8.0.1 - access-control: 10.8.0.1/24 allow - port: 53 - num-threads: 2 - use-caps-for-id: yes - harden-glue: yes - hide-identity: yes - hide-version: yes - qname-minimisation: yes - prefetch: yes' >/etc/unbound/unbound.conf - fi - - # IPv6 DNS for all OS - if [[ $IPV6_SUPPORT == 'y' ]]; then - echo 'interface: fd42:42:42:42::1 -access-control: fd42:42:42:42::/112 allow' >>/etc/unbound/unbound.conf - fi - - if [[ ! $OS =~ (fedora|centos|oracle|amzn2023) ]]; then - # DNS Rebinding fix - echo "private-address: 10.0.0.0/8 -private-address: fd42:42:42:42::/112 -private-address: 172.16.0.0/12 -private-address: 192.168.0.0/16 -private-address: 169.254.0.0/16 -private-address: fd00::/8 -private-address: fe80::/10 -private-address: 127.0.0.0/8 -private-address: ::ffff:0:0/96" >>/etc/unbound/unbound.conf - fi - else # Unbound is already installed - echo 'include: /etc/unbound/openvpn.conf' >>/etc/unbound/unbound.conf - - # Add Unbound 'server' for the OpenVPN subnet - echo 'server: -interface: 10.8.0.1 -access-control: 10.8.0.1/24 allow -hide-identity: yes -hide-version: yes -use-caps-for-id: yes -prefetch: yes -private-address: 10.0.0.0/8 -private-address: fd42:42:42:42::/112 -private-address: 172.16.0.0/12 -private-address: 192.168.0.0/16 -private-address: 169.254.0.0/16 -private-address: fd00::/8 -private-address: fe80::/10 -private-address: 127.0.0.0/8 -private-address: ::ffff:0:0/96' >/etc/unbound/openvpn.conf - if [[ $IPV6_SUPPORT == 'y' ]]; then - echo 'interface: fd42:42:42:42::1 -access-control: fd42:42:42:42::/112 allow' >>/etc/unbound/openvpn.conf fi fi + # Configure Unbound for OpenVPN (runs whether freshly installed or pre-existing) + # Create conf.d directory (works on all distros) + run_cmd "Creating Unbound config directory" mkdir -p /etc/unbound/unbound.conf.d + + # Ensure main config includes conf.d directory + # Modern Debian/Ubuntu use include-toplevel, others need include directive + if ! grep -qE "include(-toplevel)?:\s*.*/etc/unbound/unbound.conf.d" /etc/unbound/unbound.conf 2>/dev/null; then + # Add include directive for conf.d if not present + echo 'include: "/etc/unbound/unbound.conf.d/*.conf"' >>/etc/unbound/unbound.conf + fi + + # Generate OpenVPN-specific Unbound configuration + # Using consistent best-practice settings across all distros + { + echo 'server:' + echo ' # OpenVPN DNS resolver configuration' + echo ' interface: 10.8.0.1' + echo ' access-control: 10.8.0.0/24 allow' + echo '' + echo ' # Security hardening' + echo ' hide-identity: yes' + echo ' hide-version: yes' + echo ' harden-glue: yes' + echo ' harden-dnssec-stripped: yes' + echo '' + echo ' # Performance optimizations' + echo ' prefetch: yes' + echo ' use-caps-for-id: yes' + echo ' qname-minimisation: yes' + echo '' + echo ' # Allow binding before tun interface exists' + echo ' ip-freebind: yes' + echo '' + echo ' # DNS rebinding protection' + echo ' private-address: 10.0.0.0/8' + echo ' private-address: 172.16.0.0/12' + echo ' private-address: 192.168.0.0/16' + echo ' private-address: 169.254.0.0/16' + echo ' private-address: 127.0.0.0/8' + echo ' private-address: fd00::/8' + echo ' private-address: fe80::/10' + echo ' private-address: ::ffff:0:0/96' + + # IPv6 support + if [[ $IPV6_SUPPORT == 'y' ]]; then + echo '' + echo ' # IPv6 VPN support' + echo ' interface: fd42:42:42:42::1' + echo ' access-control: fd42:42:42:42::/112 allow' + echo ' private-address: fd42:42:42:42::/112' + fi + } >/etc/unbound/unbound.conf.d/openvpn.conf + run_cmd "Enabling Unbound service" systemctl enable unbound run_cmd "Starting Unbound service" systemctl restart unbound + + # Validate Unbound is running + for i in {1..10}; do + if pgrep -x unbound >/dev/null; then + return 0 + fi + sleep 1 + done + log_fatal "Unbound failed to start. Check 'journalctl -u unbound' for details." } function resolvePublicIP() { @@ -1775,9 +1739,13 @@ function renewMenu() { } function removeUnbound() { - # Remove OpenVPN-related config - run_cmd "Removing Unbound include" sed -i '/include: \/etc\/unbound\/openvpn.conf/d' /etc/unbound/unbound.conf - run_cmd "Removing OpenVPN Unbound config" rm /etc/unbound/openvpn.conf + run_cmd "Removing OpenVPN Unbound config" rm -f /etc/unbound/unbound.conf.d/openvpn.conf + + # Clean up include directive if conf.d directory is now empty + if [[ -d /etc/unbound/unbound.conf.d ]] && [[ -z "$(ls -A /etc/unbound/unbound.conf.d)" ]]; then + run_cmd "Cleaning up Unbound include directive" \ + sed -i '/^include: "\/etc\/unbound\/unbound\.conf\.d\/\*\.conf"$/d' /etc/unbound/unbound.conf + fi until [[ $REMOVE_UNBOUND =~ (y|n) ]]; do log_info "If you were already using Unbound before installing OpenVPN, I removed the configuration related to OpenVPN." @@ -1786,7 +1754,6 @@ function removeUnbound() { if [[ $REMOVE_UNBOUND == 'y' ]]; then log_info "Removing Unbound..." - # Stop Unbound run_cmd "Stopping Unbound" systemctl stop unbound if [[ $OS =~ (debian|ubuntu) ]]; then @@ -1800,7 +1767,6 @@ function removeUnbound() { fi run_cmd "Removing Unbound config" rm -rf /etc/unbound/ - log_success "Unbound removed!" else run_cmd "Restarting Unbound" systemctl restart unbound @@ -1887,7 +1853,7 @@ function removeOpenVPN() { run_cmd "Removing OpenVPN logs" rm -rf /var/log/openvpn # Unbound - if [[ -e /etc/unbound/openvpn.conf ]]; then + if [[ -e /etc/unbound/unbound.conf.d/openvpn.conf ]]; then removeUnbound fi log_success "OpenVPN removed!" diff --git a/test/Dockerfile.client b/test/Dockerfile.client index 6ce945a..63b0cc6 100644 --- a/test/Dockerfile.client +++ b/test/Dockerfile.client @@ -5,11 +5,13 @@ FROM ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive # Install OpenVPN client and testing tools +# dnsutils provides dig for DNS testing with Unbound RUN apt-get update && apt-get install -y --no-install-recommends \ openvpn \ iproute2 \ iputils-ping \ procps \ + dnsutils \ && rm -rf /var/lib/apt/lists/* # Create TUN device directory (device will be mounted at runtime) diff --git a/test/Dockerfile.server b/test/Dockerfile.server index 2cee391..fee1c81 100644 --- a/test/Dockerfile.server +++ b/test/Dockerfile.server @@ -8,21 +8,22 @@ ARG BASE_IMAGE ENV DEBIAN_FRONTEND=noninteractive # Install basic dependencies based on the OS +# dnsutils/bind-utils provides dig for DNS testing with Unbound RUN if command -v apt-get >/dev/null; then \ apt-get update && apt-get install -y --no-install-recommends \ - iproute2 iptables curl procps systemd systemd-sysv \ + iproute2 iptables curl procps systemd systemd-sysv dnsutils \ && rm -rf /var/lib/apt/lists/*; \ elif command -v dnf >/dev/null; then \ dnf install -y --allowerasing \ - iproute iptables curl procps-ng systemd tar gzip \ + iproute iptables curl procps-ng systemd tar gzip bind-utils \ && dnf clean all; \ elif command -v yum >/dev/null; then \ yum install -y \ - iproute iptables curl procps-ng systemd tar gzip \ + iproute iptables curl procps-ng systemd tar gzip bind-utils \ && yum clean all; \ elif command -v pacman >/dev/null; then \ pacman -Syu --noconfirm \ - iproute2 iptables curl procps-ng \ + iproute2 iptables curl procps-ng bind \ && pacman -Scc --noconfirm; \ fi diff --git a/test/client-entrypoint.sh b/test/client-entrypoint.sh index c2ed605..69b779b 100755 --- a/test/client-entrypoint.sh +++ b/test/client-entrypoint.sh @@ -81,6 +81,28 @@ else exit 1 fi +# Test 3: DNS resolution through Unbound +echo "Test 3: Testing DNS resolution via Unbound (10.8.0.1)..." +DNS_SUCCESS=false +for i in 1 2 3 4 5; do + DIG_OUTPUT=$(dig @10.8.0.1 example.com +short +time=5 2>&1) + if [ -n "$DIG_OUTPUT" ] && ! echo "$DIG_OUTPUT" | grep -qi "timed out\|SERVFAIL\|connection refused"; then + DNS_SUCCESS=true + break + fi + echo "DNS attempt $i failed:" + echo "$DIG_OUTPUT" + sleep 2 +done +if [ "$DNS_SUCCESS" = true ]; then + echo "PASS: DNS resolution through Unbound works" + echo "Resolved example.com to: $(dig @10.8.0.1 example.com +short +time=5)" +else + echo "FAIL: DNS resolution through Unbound failed after 5 attempts" + dig @10.8.0.1 example.com +time=5 || true + exit 1 +fi + echo "" echo "==========================================" echo " ALL TESTS PASSED!" diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 2f3c38b..527c258 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -20,7 +20,7 @@ export APPROVE_IP=y export IPV6_SUPPORT=n export PORT_CHOICE=1 export PROTOCOL_CHOICE=1 -export DNS=9 # Google DNS (works in containers) +export DNS=2 # Self-hosted Unbound DNS resolver export COMPRESSION_ENABLED=n export CUSTOMIZE_ENC=n export CLIENT=testclient @@ -29,8 +29,11 @@ export ENDPOINT=openvpn-server # Prepare script for container environment: # - Replace systemctl calls with no-ops (systemd doesn't work in containers) +# - Skip Unbound startup validation (we start Unbound manually later) # This ensures the script won't fail silently on systemctl commands -sed 's/\bsystemctl /echo "[SKIPPED] systemctl " # /g' /opt/openvpn-install.sh >/tmp/openvpn-install.sh +sed -e 's/\bsystemctl /echo "[SKIPPED] systemctl " # /g' \ + -e 's/log_fatal "Unbound failed to start/return 0 # [SKIPPED] /g' \ + /opt/openvpn-install.sh >/tmp/openvpn-install.sh chmod +x /tmp/openvpn-install.sh echo "Running OpenVPN install script..." @@ -243,6 +246,80 @@ echo "" echo "=== All Certificate Renewal Tests PASSED ===" echo "" +# ===================================================== +# Start and verify Unbound DNS resolver +# ===================================================== +echo "=== Starting Unbound DNS Resolver ===" + +# Start Unbound manually (systemctl commands are no-ops in container) +if [ -f /etc/unbound/unbound.conf ]; then + echo "Starting Unbound DNS resolver..." + + # Create root key for DNSSEC if it doesn't exist + # Normally, unbound.service's ExecStartPre copies /usr/share/dns/root.key to /var/lib/unbound/root.key + # In Docker, policy-rc.d blocks service starts during apt install, so this never happens + if [ ! -f /var/lib/unbound/root.key ] && [ -f /usr/share/dns/root.key ]; then + mkdir -p /var/lib/unbound + cp /usr/share/dns/root.key /var/lib/unbound/root.key + chown -R unbound:unbound /var/lib/unbound 2>/dev/null || true + fi + + unbound + # Poll up to 10 seconds for Unbound to start + for _ in $(seq 1 10); do + if pgrep -x unbound >/dev/null; then + echo "PASS: Unbound is running" + break + fi + sleep 1 + done + if ! pgrep -x unbound >/dev/null; then + echo "FAIL: Unbound failed to start" + # Show debug info + unbound-checkconf /etc/unbound/unbound.conf 2>&1 || true + exit 1 + fi +else + echo "FAIL: /etc/unbound/unbound.conf not found" + exit 1 +fi + +echo "" +echo "=== Verifying Unbound Installation ===" + +# Verify Unbound config exists in conf.d directory +UNBOUND_OPENVPN_CONF="/etc/unbound/unbound.conf.d/openvpn.conf" +if [ -f "$UNBOUND_OPENVPN_CONF" ]; then + echo "PASS: Found Unbound config at $UNBOUND_OPENVPN_CONF" +else + echo "FAIL: OpenVPN Unbound config not found at $UNBOUND_OPENVPN_CONF" + echo "Contents of /etc/unbound/:" + ls -la /etc/unbound/ + ls -la /etc/unbound/unbound.conf.d/ 2>/dev/null || true + exit 1 +fi + +# Verify Unbound listens on VPN gateway +if grep -q "interface: 10.8.0.1" "$UNBOUND_OPENVPN_CONF"; then + echo "PASS: Unbound configured to listen on 10.8.0.1" +else + echo "FAIL: Unbound not configured for 10.8.0.1" + cat "$UNBOUND_OPENVPN_CONF" + exit 1 +fi + +# Verify OpenVPN pushes correct DNS +if grep -q 'push "dhcp-option DNS 10.8.0.1"' /etc/openvpn/server.conf; then + echo "PASS: OpenVPN configured to push Unbound DNS" +else + echo "FAIL: OpenVPN not configured to push Unbound DNS" + grep "dhcp-option DNS" /etc/openvpn/server.conf || echo "No DNS push found" + exit 1 +fi + +echo "=== Unbound Installation Verified ===" +echo "" + # Start OpenVPN server manually (systemd doesn't work in containers) echo "Starting OpenVPN server..." From 0f324ef3b9f50c765838a8cb626d2a7a6674ca1d Mon Sep 17 00:00:00 2001 From: Stanislas Date: Thu, 11 Dec 2025 13:45:53 +0100 Subject: [PATCH 115/141] docs: add "Why OpenVPN?" section to README (#1341) - Add a new "Why OpenVPN?" section explaining when OpenVPN is preferable over WireGuard - Move the wireguard-install link from the intro to this new section --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 30aadbc..692c559 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ OpenVPN installer for Debian, Ubuntu, Fedora, CentOS, Arch Linux, Oracle Linux, This script will let you setup your own secure VPN server in just a few seconds. -You can also check out [wireguard-install](https://github.com/angristan/wireguard-install), a simple installer for a simpler, safer, faster and more modern VPN protocol. - ## What is this? This script is meant to be run on your own server, whether it's a VPS or a dedicated server, or even a computer at home. @@ -29,6 +27,16 @@ graph LR VPN --> I[Internet] ``` +## Why OpenVPN? + +OpenVPN was the de facto standard for open-source VPNs when this script was created. WireGuard came later and is simpler and faster for most use cases. Check out [wireguard-install](https://github.com/angristan/wireguard-install). + +That said, OpenVPN still makes sense when you need: + +- **TCP support**: works in restrictive environments where UDP is blocked (corporate networks, airports, hotels, etc.) +- **Password-protected private keys**: WireGuard configs store the private key in plain text +- **Legacy compatibility**: clients exist for pretty much every platform, including older systems + ## Usage First, get the script and make it executable: From e06329c770cfce5435f93b1c43de6d4e64869d30 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 11 Dec 2025 13:49:05 +0100 Subject: [PATCH 116/141] docs: update README to include Amazon Linux in supported distributions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 692c559..3b6af46 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Lint](https://github.com/angristan/openvpn-install/workflows/Lint/badge.svg) [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/angristan) -OpenVPN installer for Debian, Ubuntu, Fedora, CentOS, Arch Linux, Oracle Linux, Rocky Linux and AlmaLinux. +OpenVPN installer for Debian, Ubuntu, Fedora, CentOS, Amazon Linux, Arch Linux, Oracle Linux, Rocky Linux and AlmaLinux. This script will let you setup your own secure VPN server in just a few seconds. From 77a025d042e09f1c075445223a6c159bd115eb84 Mon Sep 17 00:00:00 2001 From: Leo Wang Date: Thu, 11 Dec 2025 04:58:40 -0800 Subject: [PATCH 117/141] Improve wording of README (#1134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I know I’m setting up open VPN because google gave me this page, but I wasn’t sure if I should run this script on my Amazon Linux 2 server or my MacBook Pro client for a hot minute. --------- Co-authored-by: Stanislas --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b6af46..67690c9 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ That said, OpenVPN still makes sense when you need: ## Usage -First, get the script and make it executable: +First, on your server, get the script and make it executable: ```bash curl -O https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh From 599d1221132e5f6097a1710dce5130a2df086570 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 11 Dec 2025 16:26:45 +0100 Subject: [PATCH 118/141] fix: use pgrep -x to accurately check for OpenVPN process in docker-test workflow --- .github/workflows/docker-test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 215f7d3..debd6e4 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -99,7 +99,8 @@ jobs: run: | echo "Waiting for OpenVPN server to install and start..." for i in {1..60}; do - if docker exec openvpn-server pgrep openvpn > /dev/null 2>&1; then + # Use pgrep -x to match exactly "openvpn" process, not "apt-get install openvpn" + if docker exec openvpn-server pgrep -x openvpn > /dev/null 2>&1; then echo "OpenVPN server is running!" break fi @@ -110,7 +111,7 @@ jobs: done # Final check - if ! docker exec openvpn-server pgrep openvpn > /dev/null 2>&1; then + if ! docker exec openvpn-server pgrep -x openvpn > /dev/null 2>&1; then echo "ERROR: OpenVPN server failed to start" docker logs openvpn-server exit 1 From 65f4885c367a7fecadc8ef2aa28ee831d610864b Mon Sep 17 00:00:00 2001 From: Stanislas Date: Thu, 11 Dec 2025 17:04:44 +0100 Subject: [PATCH 119/141] refactor: replace wget with curl (#1343) - Replace `wget` with `curl` for downloading Easy-RSA - Remove `wget` from package dependencies across all distributions - Ensure `curl` and `ca-certificates` are installed on all distributions - Add `--retry 3` for automatic retries on transient network failures --- openvpn-install.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 31087c1..825d7b6 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -952,17 +952,17 @@ function installOpenVPN() { log_info "Installing OpenVPN and dependencies..." if [[ $OS =~ (debian|ubuntu) ]]; then - run_cmd "Installing OpenVPN" apt-get install -y openvpn iptables openssl wget + run_cmd "Installing OpenVPN" apt-get install -y openvpn iptables openssl curl ca-certificates elif [[ $OS == 'centos' ]]; then - run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl tar 'policycoreutils-python*' + run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar 'policycoreutils-python*' elif [[ $OS == 'oracle' ]]; then - run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl wget ca-certificates curl tar policycoreutils-python-utils + run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar policycoreutils-python-utils elif [[ $OS == 'amzn2023' ]]; then - run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl wget ca-certificates + run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl elif [[ $OS == 'fedora' ]]; then - run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl wget ca-certificates curl policycoreutils-python-utils + run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl policycoreutils-python-utils elif [[ $OS == 'arch' ]]; then - run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl wget ca-certificates curl + run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl ca-certificates curl fi # Verify ChaCha20-Poly1305 compatibility if selected @@ -1002,7 +1002,7 @@ function installOpenVPN() { # Install the latest version of easy-rsa from source, if not already installed. if [[ ! -d /etc/openvpn/easy-rsa/ ]]; then - run_cmd "Downloading Easy-RSA v${EASYRSA_VERSION}" wget -O ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" + run_cmd "Downloading Easy-RSA v${EASYRSA_VERSION}" curl -fL --retry 3 -o ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" log_info "Verifying Easy-RSA checksum..." CHECKSUM_OUTPUT=$(echo "${EASYRSA_SHA256} $HOME/easy-rsa.tgz" | sha256sum -c 2>&1) || { _log_to_file "[CHECKSUM] $CHECKSUM_OUTPUT" From 3c9580b5b4e2dabc789c92f3d15c8279d7d50be3 Mon Sep 17 00:00:00 2001 From: Joel Ramos <53574318+joelpramos@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:15:34 -0500 Subject: [PATCH 120/141] fix: allow reusing revoked client names (#1185) Close https://github.com/angristan/openvpn-install/pull/680, close https://github.com/angristan/openvpn-install/issues/652, close https://github.com/angristan/openvpn-install/issues/1024, close https://github.com/angristan/openvpn-install/issues/746 Co-authored-by: Stanislas --- openvpn-install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 825d7b6..5e0eeaa 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1510,8 +1510,8 @@ function newClient() { read -rp "Select an option [1-2]: " -e -i 1 PASS done - CLIENTEXISTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c -E "/CN=$CLIENT\$") - if [[ $CLIENTEXISTS == '1' ]]; then + CLIENTEXISTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -E "^V" | grep -c -E "/CN=$CLIENT\$") + if [[ $CLIENTEXISTS != '0' ]]; then log_error "The specified client CN was already found in easy-rsa, please choose another name." exit else From 6cca56f5b5f09daa89f174b65d6293641907819d Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 11 Dec 2025 16:41:53 +0100 Subject: [PATCH 121/141] ci: add install script log output in docker-test workflow Add step to display the install script log file (openvpn-install.log) which includes timestamps and all installation activity. This makes debugging CI failures easier by providing detailed logs directly in the workflow output. --- .github/workflows/docker-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index debd6e4..59218e7 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -160,6 +160,12 @@ jobs: if: always() run: docker logs openvpn-server 2>&1 || true + - name: Show install script log + if: always() + run: | + docker cp openvpn-server:/opt/openvpn-install.log /tmp/openvpn-install.log 2>/dev/null && \ + cat /tmp/openvpn-install.log || echo "No install log found" + - name: Show client logs if: always() run: docker logs openvpn-client 2>&1 || true From d9e11822dbdf709a5e9a36a5d3eb6023d88bf67f Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 11 Dec 2025 17:11:11 +0100 Subject: [PATCH 122/141] fix: use pgrep -f to detect OpenVPN server, not transient processes The previous check using `pgrep -x openvpn` was matching transient openvpn processes like `openvpn --genkey` that run during installation, causing false positives. This led to race conditions where the CI thought the server was running when it was actually still installing. Use `pgrep -f "openvpn.*server.conf"` to specifically match the actual OpenVPN server process running with the server configuration. --- .github/workflows/docker-test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 59218e7..2e1e321 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -99,8 +99,9 @@ jobs: run: | echo "Waiting for OpenVPN server to install and start..." for i in {1..60}; do - # Use pgrep -x to match exactly "openvpn" process, not "apt-get install openvpn" - if docker exec openvpn-server pgrep -x openvpn > /dev/null 2>&1; then + # Use pgrep -f to match openvpn running with server.conf, not transient + # processes like "openvpn --genkey" that run during installation + if docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then echo "OpenVPN server is running!" break fi @@ -111,7 +112,7 @@ jobs: done # Final check - if ! docker exec openvpn-server pgrep -x openvpn > /dev/null 2>&1; then + if ! docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then echo "ERROR: OpenVPN server failed to start" docker logs openvpn-server exit 1 From 690414a56ef8af69715284f5220ceccdd94de00f Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Thu, 11 Dec 2025 17:15:05 +0100 Subject: [PATCH 123/141] ci: update Fedora versions to 42 and 43 --- .github/workflows/docker-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 2e1e321..fa6a16d 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -36,10 +36,10 @@ jobs: image: debian:12 - name: centos-stream-9 image: quay.io/centos/centos:stream9 - - name: fedora-40 - image: fedora:40 - - name: fedora-41 - image: fedora:41 + - name: fedora-42 + image: fedora:42 + - name: fedora-43 + image: fedora:43 - name: rocky-8 image: rockylinux:8 - name: rocky-9 From 0d4d2229f40ae5856e1aca5eb0e2e206bd1f6ec1 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Thu, 11 Dec 2025 18:22:16 +0100 Subject: [PATCH 124/141] test: add e2e tests for certificate revocation (#1345) ## Summary - Add end-to-end tests for certificate revocation functionality - Test that a revoked client certificate cannot connect to the VPN - Test that a new certificate can be created with the same name as a revoked one (validating the fix from #1185) - Test that the new certificate can successfully connect ## Test Flow 1. **Initial connectivity tests** - existing tests pass 2. **Certificate revocation test**: - Create a new client `revoketest` - Connect with the certificate (verifies it works) - Disconnect the client - Revoke the certificate via the install script - Try to reconnect with revoked cert (verifies connection is rejected) 3. **Reuse revoked name test**: - Create a new certificate with the same name `revoketest` - Verify both revoked and valid entries exist in `index.txt` - Connect with the new certificate (verifies it works) ## Changes | File | Changes | |------|---------| | `test/server-entrypoint.sh` | Start OpenVPN in background, add revocation test orchestration | | `test/client-entrypoint.sh` | Add revocation test phases with signal file coordination | | `docker-compose.yml` | Remove read-only restriction on shared volume for client | | `Makefile` | Increase timeout from 60 to 180 iterations | | `.github/workflows/docker-test.yml` | Increase timeouts, fix shared volume | --- .github/workflows/docker-test.yml | 9 +- Makefile | 4 +- docker-compose.yml | 2 +- test/client-entrypoint.sh | 257 +++++++++++++++++++++++++++++- test/server-entrypoint.sh | 226 +++++++++++++++++++++++++- 5 files changed, 488 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index fa6a16d..2ac72d6 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -17,7 +17,7 @@ permissions: jobs: docker-test: runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 20 strategy: fail-fast: false matrix: @@ -134,11 +134,12 @@ jobs: --device=/dev/net/tun:/dev/net/tun \ --network vpn-test \ --ip 172.28.0.20 \ - -v shared-config:/shared:ro \ + -v shared-config:/shared \ openvpn-client & # Wait for tests to complete (look for success message) - for i in {1..60}; do + # Extended timeout for revocation e2e tests + for i in {1..180}; do if docker logs openvpn-client 2>&1 | grep -q "ALL TESTS PASSED" then echo "Tests passed!" @@ -149,7 +150,7 @@ jobs: docker logs openvpn-client exit 1 fi - echo "Waiting for tests... ($i/60)" + echo "Waiting for tests... ($i/180)" sleep 2 done diff --git a/Makefile b/Makefile index 7352a6b..c73f669 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Run the full test suite test: test-build test-up @echo "Waiting for tests to complete..." - @for i in $$(seq 1 60); do \ + @for i in $$(seq 1 180); do \ if docker logs openvpn-client 2>&1 | grep -q "ALL TESTS PASSED"; then \ echo "✓ Tests passed!"; \ $(MAKE) test-down; \ @@ -15,7 +15,7 @@ test: test-build test-up $(MAKE) test-down; \ exit 1; \ fi; \ - echo "Waiting... ($$i/60)"; \ + echo "Waiting... ($$i/180)"; \ sleep 2; \ done; \ echo "Timeout waiting for tests"; \ diff --git a/docker-compose.yml b/docker-compose.yml index 094c96d..1147a4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: devices: - /dev/net/tun:/dev/net/tun volumes: - - shared-config:/shared:ro + - shared-config:/shared networks: vpn-test: ipv4_address: 172.28.0.20 diff --git a/test/client-entrypoint.sh b/test/client-entrypoint.sh index 69b779b..4dba5bf 100755 --- a/test/client-entrypoint.sh +++ b/test/client-entrypoint.sh @@ -103,10 +103,265 @@ else exit 1 fi +echo "" +echo "=== Initial connectivity tests PASSED ===" + +# Signal server that initial tests passed +touch /shared/initial-tests-passed + +# ===================================================== +# Certificate Revocation E2E Tests +# ===================================================== +echo "" +echo "=== Starting Certificate Revocation E2E Tests ===" + +REVOKE_CLIENT="revoketest" + +# Wait for revoke test client config +echo "Waiting for revoke test client config..." +MAX_WAIT=120 +WAITED=0 +while [ ! -f /shared/revoke-client-config-ready ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for revoke test config... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/revoke-client-config-ready ]; then + echo "FAIL: Revoke test client config not ready in time" + exit 1 +fi + +if [ ! -f "/shared/$REVOKE_CLIENT.ovpn" ]; then + echo "FAIL: Revoke test client config file not found" + exit 1 +fi + +echo "Revoke test client config found!" + +# Disconnect current VPN (testclient) before connecting with revoke test client +echo "Disconnecting current VPN connection..." +pkill openvpn || true +sleep 2 + +# Connect with revoke test client +echo "Connecting with '$REVOKE_CLIENT' certificate..." +openvpn --config "/shared/$REVOKE_CLIENT.ovpn" --daemon --log /var/log/openvpn-revoke.log + +# Wait for connection +echo "Waiting for VPN connection with revoke test 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-revoke.log ]; then + tail -3 /var/log/openvpn-revoke.log + fi +done + +if ! ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: VPN connection with revoke test client failed" + cat /var/log/openvpn-revoke.log || true + exit 1 +fi + +echo "PASS: Connected with '$REVOKE_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 revoke test client" +else + echo "FAIL: Cannot ping VPN gateway with revoke test client" + exit 1 +fi + +# Signal server that we're connected with revoke test client +touch /shared/revoke-client-connected + +# Wait for server to signal us to disconnect +echo "Waiting for server to signal disconnect..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/revoke-client-disconnect ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) +done + +if [ ! -f /shared/revoke-client-disconnect ]; then + echo "FAIL: Server did not signal disconnect" + exit 1 +fi + +# Disconnect +echo "Disconnecting revoke test client..." +pkill openvpn || true + +# Wait for openvpn to fully exit and tun0 to be released +WAITED=0 +MAX_WAIT_DISCONNECT=10 +while (pgrep openvpn >/dev/null || ip addr show tun0 2>/dev/null | grep -q "inet ") && [ $WAITED -lt $MAX_WAIT_DISCONNECT ]; do + sleep 1 + WAITED=$((WAITED + 1)) +done + +# Verify disconnected +if ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: tun0 still has IP after disconnect" + exit 1 +fi +echo "PASS: Disconnected successfully" + +# Signal server that we're disconnected +touch /shared/revoke-client-disconnected + +# Wait for server to revoke the certificate and signal us to reconnect +echo "Waiting for server to revoke certificate and signal reconnect..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/revoke-try-reconnect ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) +done + +if [ ! -f /shared/revoke-try-reconnect ]; then + echo "FAIL: Server did not signal to try reconnect" + exit 1 +fi + +# Try to reconnect with the now-revoked certificate (should fail) +echo "Attempting to reconnect with revoked certificate (should fail)..." +rm -f /var/log/openvpn-revoke-fail.log +openvpn --config "/shared/$REVOKE_CLIENT.ovpn" --daemon --log /var/log/openvpn-revoke-fail.log + +# Wait and check if connection fails +# The connection should fail due to certificate being revoked +echo "Waiting to verify connection is rejected..." +CONNECT_FAILED=false +MAX_WAIT=30 +WAITED=0 +while [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + + # Check if tun0 came up (would mean revocation didn't work) + if ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: Connection succeeded with revoked certificate!" + cat /var/log/openvpn-revoke-fail.log || true + exit 1 + fi + + # Check for certificate verification failure in log + if [ -f /var/log/openvpn-revoke-fail.log ]; then + if grep -qi "certificate verify failed\|TLS Error\|AUTH_FAILED\|certificate revoked" /var/log/openvpn-revoke-fail.log; then + echo "Connection correctly rejected (certificate revoked)" + CONNECT_FAILED=true + break + fi + fi + + echo "Checking connection status... ($WAITED/$MAX_WAIT seconds)" + if [ -f /var/log/openvpn-revoke-fail.log ]; then + tail -3 /var/log/openvpn-revoke-fail.log + fi +done + +# Kill any remaining openvpn process +pkill openvpn 2>/dev/null || true +sleep 1 + +# Even if we didn't see explicit error, verify tun0 is not up +if ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: tun0 interface exists - revoked cert may have connected" + exit 1 +fi + +if [ "$CONNECT_FAILED" = true ]; then + echo "PASS: Connection with revoked certificate was correctly rejected" +else + echo "PASS: Connection with revoked certificate did not succeed (no tun0)" + echo "OpenVPN log:" + cat /var/log/openvpn-revoke-fail.log || true +fi + +# Signal server that reconnect with revoked cert failed +touch /shared/revoke-reconnect-failed + +# ===================================================== +# Test connecting with new certificate (same name) +# ===================================================== +echo "" +echo "=== Testing connection with recreated certificate ===" + +# Wait for server to create new cert and signal us +echo "Waiting for new client config with same name..." +MAX_WAIT=120 +WAITED=0 +while [ ! -f /shared/new-client-config-ready ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for new config... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/new-client-config-ready ]; then + echo "FAIL: New client config not ready in time" + exit 1 +fi + +if [ ! -f "/shared/$REVOKE_CLIENT-new.ovpn" ]; then + echo "FAIL: New client config file not found" + exit 1 +fi + +echo "New client config found!" + +# Connect with the new certificate +echo "Connecting with new '$REVOKE_CLIENT' certificate..." +rm -f /var/log/openvpn-new.log +openvpn --config "/shared/$REVOKE_CLIENT-new.ovpn" --daemon --log /var/log/openvpn-new.log + +# Wait for connection +echo "Waiting for VPN connection with new certificate..." +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-new.log ]; then + tail -3 /var/log/openvpn-new.log + fi +done + +if ! ip addr show tun0 2>/dev/null | grep -q "inet "; then + echo "FAIL: VPN connection with new certificate failed" + cat /var/log/openvpn-new.log || true + exit 1 +fi + +echo "PASS: Connected with new '$REVOKE_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 new certificate" +else + echo "FAIL: Cannot ping VPN gateway with new certificate" + exit 1 +fi + +# Signal server that we connected with new cert +touch /shared/new-client-connected + +echo "" +echo "=== Certificate Revocation E2E Tests PASSED ===" + echo "" echo "==========================================" echo " ALL TESTS PASSED!" echo "==========================================" # Keep container running for debugging if needed -exec tail -f /var/log/openvpn.log +exec tail -f /var/log/openvpn-new.log 2>/dev/null || tail -f /var/log/openvpn.log 2>/dev/null || sleep infinity diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 527c258..133f3c2 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -346,6 +346,228 @@ if [ "$(cat /proc/sys/net/ipv4/ip_forward)" != "1" ]; then } fi -# Start OpenVPN in foreground (run from /etc/openvpn so relative paths work) +# Start OpenVPN in background (run from /etc/openvpn so relative paths work) cd /etc/openvpn -exec openvpn --config /etc/openvpn/server.conf +openvpn --config /etc/openvpn/server.conf --log /var/log/openvpn-server.log & +OPENVPN_PID=$! + +# Wait for OpenVPN to start +echo "Waiting for OpenVPN server to start..." +for _ in $(seq 1 30); do + if pgrep -f "openvpn --config" >/dev/null; then + echo "OpenVPN server started (PID: $OPENVPN_PID)" + break + fi + sleep 1 +done + +if ! pgrep -f "openvpn --config" >/dev/null; then + echo "FAIL: OpenVPN server failed to start" + cat /var/log/openvpn-server.log || true + exit 1 +fi + +# ===================================================== +# Wait for initial client tests to complete +# ===================================================== +echo "" +echo "=== Waiting for initial client connectivity tests ===" +MAX_WAIT=120 +WAITED=0 +while [ ! -f /shared/initial-tests-passed ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for initial tests... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/initial-tests-passed ]; then + echo "ERROR: Initial client tests did not complete in time" + exit 1 +fi +echo "Initial client tests passed, proceeding with revocation tests" + +# ===================================================== +# Test certificate revocation functionality +# ===================================================== +echo "" +echo "=== Testing Certificate Revocation ===" + +# Create a new client for revocation testing +REVOKE_CLIENT="revoketest" +echo "Creating client '$REVOKE_CLIENT' for revocation testing..." +REVOKE_CREATE_OUTPUT="/tmp/revoke-create-output.log" +(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$REVOKE_CREATE_OUTPUT" || true + +if [ -f "/root/$REVOKE_CLIENT.ovpn" ]; then + echo "PASS: Client '$REVOKE_CLIENT' created successfully" +else + echo "FAIL: Failed to create client '$REVOKE_CLIENT'" + cat "$REVOKE_CREATE_OUTPUT" + exit 1 +fi + +# Copy config for revocation test client +cp "/root/$REVOKE_CLIENT.ovpn" "/shared/$REVOKE_CLIENT.ovpn" +sed -i 's/^remote .*/remote openvpn-server 1194/' "/shared/$REVOKE_CLIENT.ovpn" +echo "Copied $REVOKE_CLIENT config to /shared/" + +# Signal client that revoke test config is ready +touch /shared/revoke-client-config-ready + +# Wait for client to confirm connection with revoke test client +echo "Waiting for client to connect with '$REVOKE_CLIENT' certificate..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/revoke-client-connected ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for revoke test connection... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/revoke-client-connected ]; then + echo "ERROR: Client did not connect with revoke test certificate" + exit 1 +fi +echo "PASS: Client connected with '$REVOKE_CLIENT' certificate" + +# Signal client to disconnect before revocation +touch /shared/revoke-client-disconnect + +# Wait for client to disconnect +echo "Waiting for client to disconnect..." +MAX_WAIT=30 +WAITED=0 +while [ ! -f /shared/revoke-client-disconnected ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) +done + +if [ ! -f /shared/revoke-client-disconnected ]; then + echo "ERROR: Client did not signal disconnect" + exit 1 +fi +echo "Client disconnected" + +# Now revoke the certificate +echo "Revoking certificate for '$REVOKE_CLIENT'..." +REVOKE_OUTPUT="/tmp/revoke-output.log" +# MENU_OPTION=2 is revoke, CLIENTNUMBER is dynamically determined from index.txt +# We need to find the client number for revoketest +REVOKE_CLIENT_NUM=$(tail -n +2 /etc/openvpn/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/easy-rsa/pki/index.txt + exit 1 +fi +echo "Revoke client number: $REVOKE_CLIENT_NUM" +(MENU_OPTION=2 CLIENTNUMBER=$REVOKE_CLIENT_NUM bash /tmp/openvpn-install.sh) 2>&1 | tee "$REVOKE_OUTPUT" || true + +if grep -q "Certificate for client $REVOKE_CLIENT revoked" "$REVOKE_OUTPUT"; then + echo "PASS: Certificate for '$REVOKE_CLIENT' revoked successfully" +else + echo "FAIL: Failed to revoke certificate" + cat "$REVOKE_OUTPUT" + exit 1 +fi + +# Verify certificate is marked as revoked in index.txt +if tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -q "^R.*CN=$REVOKE_CLIENT\$"; then + echo "PASS: Certificate marked as revoked in index.txt" +else + echo "FAIL: Certificate not marked as revoked" + cat /etc/openvpn/easy-rsa/pki/index.txt + exit 1 +fi + +# Signal client to try reconnecting (should fail) +touch /shared/revoke-try-reconnect + +# Wait for client to confirm that connection with revoked cert failed +echo "Waiting for client to confirm revoked cert connection failure..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/revoke-reconnect-failed ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for reconnect failure confirmation... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/revoke-reconnect-failed ]; then + echo "ERROR: Client did not confirm that revoked cert connection failed" + exit 1 +fi +echo "PASS: Connection with revoked certificate correctly rejected" + +echo "=== Certificate Revocation Tests PASSED ===" + +# ===================================================== +# Test reusing revoked client name +# ===================================================== +echo "" +echo "=== Testing Reuse of Revoked Client Name ===" + +# Create a new certificate with the same name as the revoked one +echo "Creating new client with same name '$REVOKE_CLIENT'..." +RECREATE_OUTPUT="/tmp/recreate-output.log" +(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RECREATE_OUTPUT" || true + +if [ -f "/root/$REVOKE_CLIENT.ovpn" ]; then + echo "PASS: New client '$REVOKE_CLIENT' created successfully (reusing revoked name)" +else + echo "FAIL: Failed to create client with revoked name" + cat "$RECREATE_OUTPUT" + exit 1 +fi + +# Verify the new certificate is valid (V) in index.txt +if tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -q "^V.*CN=$REVOKE_CLIENT\$"; then + echo "PASS: New certificate is valid in index.txt" +else + echo "FAIL: New certificate not marked as valid" + cat /etc/openvpn/easy-rsa/pki/index.txt + exit 1 +fi + +# Verify there's also a revoked entry (both should exist) +REVOKED_COUNT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^R.*CN=$REVOKE_CLIENT\$") +VALID_COUNT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^V.*CN=$REVOKE_CLIENT\$") +echo "Certificates for '$REVOKE_CLIENT': $REVOKED_COUNT revoked, $VALID_COUNT valid" +if [ "$REVOKED_COUNT" -ge 1 ] && [ "$VALID_COUNT" -eq 1 ]; then + echo "PASS: Both revoked and new valid certificate entries exist" +else + echo "FAIL: Unexpected certificate state" + cat /etc/openvpn/easy-rsa/pki/index.txt + exit 1 +fi + +# Copy the new config +cp "/root/$REVOKE_CLIENT.ovpn" "/shared/$REVOKE_CLIENT-new.ovpn" +sed -i 's/^remote .*/remote openvpn-server 1194/' "/shared/$REVOKE_CLIENT-new.ovpn" +echo "Copied new $REVOKE_CLIENT config to /shared/" + +# Signal client that new config is ready +touch /shared/new-client-config-ready + +# Wait for client to confirm successful connection with new cert +echo "Waiting for client to connect with new '$REVOKE_CLIENT' certificate..." +MAX_WAIT=60 +WAITED=0 +while [ ! -f /shared/new-client-connected ] && [ $WAITED -lt $MAX_WAIT ]; do + sleep 2 + WAITED=$((WAITED + 2)) + echo "Waiting for new cert connection... ($WAITED/$MAX_WAIT seconds)" +done + +if [ ! -f /shared/new-client-connected ]; then + echo "ERROR: Client did not connect with new certificate" + exit 1 +fi +echo "PASS: Client connected with new '$REVOKE_CLIENT' certificate" + +echo "=== Reuse of Revoked Client Name Tests PASSED ===" +echo "" +echo "=== All Revocation Tests PASSED ===" + +# Keep server running for any remaining client tests +echo "Server waiting for client to complete all tests..." +wait $OPENVPN_PID From 4b00f44e8e68ae7e73b7a5eb6eaeef09aa6fc42a Mon Sep 17 00:00:00 2001 From: Stanislas Date: Thu, 11 Dec 2025 20:22:00 +0100 Subject: [PATCH 125/141] feat: add version 10 support for RHEL-based distributions (#1346) ## Summary - Add version 10 support for CentOS Stream, Rocky Linux, AlmaLinux, and Oracle Linux - Consolidate version check logic into a single check for all RHEL-based distributions - Fix Rocky Linux Docker image names to `rockylinux/rockylinux:tag` - Increase Easy-RSA download curl retry from 3 to 5 - Fail early if EPEL/Copr repository setup fails - Fix Oracle Linux EPEL package name (`oracle-epel-release-el*` instead of `epel-release`) ## Changes ### `openvpn-install.sh` - Combine version checks for CentOS/Rocky/AlmaLinux and Oracle Linux into one - Update error message to list supported distributions - Change Easy-RSA download `--retry 3` to `--retry 5` - Add `|| log_fatal` to EPEL and Copr setup commands to fail early on errors - Use `oracle-epel-release-el{8,9,10}` for Oracle Linux instead of `epel-release` ### `.github/workflows/docker-test.yml` - Add CentOS Stream 10 (`quay.io/centos/centos:stream10`) - Add Rocky Linux 10 (`rockylinux/rockylinux:10`) - Add AlmaLinux 10 (`almalinux:10`) - Add Oracle Linux 10 (`oraclelinux:10`) - Fix Rocky Linux image names from `rockylinux:X` to `rockylinux/rockylinux:X` ## Test plan - [ ] CI passes for existing distributions - [ ] CI passes for new version 10 distributions (where images are available) --- .github/workflows/docker-test.yml | 12 ++++++++-- openvpn-install.sh | 37 +++++++++++++++++-------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 2ac72d6..a2d2b5f 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -36,24 +36,32 @@ jobs: image: debian:12 - name: centos-stream-9 image: quay.io/centos/centos:stream9 + - name: centos-stream-10 + image: quay.io/centos/centos:stream10 - name: fedora-42 image: fedora:42 - name: fedora-43 image: fedora:43 - name: rocky-8 - image: rockylinux:8 + image: rockylinux/rockylinux:8 - name: rocky-9 - image: rockylinux:9 + image: rockylinux/rockylinux:9 + - name: rocky-10 + image: rockylinux/rockylinux:10 - name: almalinux-8 image: almalinux:8 - name: almalinux-9 image: almalinux:9 + - name: almalinux-10 + image: almalinux:10 - name: archlinux image: archlinux:latest - name: oraclelinux-8 image: oraclelinux:8 - name: oraclelinux-9 image: oraclelinux:9 + - name: oraclelinux-10 + image: oraclelinux:10 - name: amazonlinux-2023 image: amazonlinux:2023 diff --git a/openvpn-install.sh b/openvpn-install.sh index 5e0eeaa..8a22e93 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -196,17 +196,13 @@ function checkOS() { fi if [[ $ID == "centos" || $ID == "rocky" || $ID == "almalinux" ]]; then OS="centos" - if [[ ${VERSION_ID%.*} -lt 8 ]]; then - log_info "The script only supports CentOS Stream 8+ / Rocky Linux 8+ / AlmaLinux 8+." - log_fatal "Your version of CentOS is not supported." - fi fi if [[ $ID == "ol" ]]; then OS="oracle" - if [[ ! $VERSION_ID =~ ^(8|9) ]]; then - log_info "The script only supports Oracle Linux 8 and 9." - log_fatal "Your version of Oracle Linux is not supported." - fi + fi + if [[ $OS =~ (centos|oracle) ]] && [[ ${VERSION_ID%.*} -lt 8 ]]; then + log_info "The script only supports CentOS Stream / Rocky Linux / AlmaLinux / Oracle Linux version 8+." + log_fatal "Your version is not supported." fi if [[ $ID == "amzn" ]]; then if [[ "$(echo "$PRETTY_NAME" | cut -c 1-18)" == "Amazon Linux 2023." ]] && [[ "$(echo "$PRETTY_NAME" | cut -c 19)" -ge 6 ]]; then @@ -220,7 +216,7 @@ function checkOS() { elif [[ -e /etc/arch-release ]]; then OS=arch else - log_fatal "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2023, Oracle Linux or Arch Linux system." + log_fatal "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2023, Oracle Linux, Arch Linux, Rocky Linux or AlmaLinux system." fi } @@ -324,14 +320,21 @@ function installOpenVPNRepo() { # EPEL is required for pkcs11-helper dependency log_info "Configuring OpenVPN Copr repository for RHEL-based system..." - if ! command -v dnf &>/dev/null; then - run_cmd "Installing EPEL repository" yum install -y epel-release - run_cmd "Installing yum-plugin-copr" yum install -y yum-plugin-copr - run_cmd "Enabling OpenVPN Copr repo" yum copr enable -y @OpenVPN/openvpn-release-2.6 + # Oracle Linux uses oracle-epel-release-el* instead of epel-release + if [[ $OS == "oracle" ]]; then + EPEL_PACKAGE="oracle-epel-release-el${VERSION_ID%.*}" else - run_cmd "Installing EPEL repository" dnf install -y epel-release - run_cmd "Installing dnf-plugins-core" dnf install -y dnf-plugins-core - run_cmd "Enabling OpenVPN Copr repo" dnf copr enable -y @OpenVPN/openvpn-release-2.6 + EPEL_PACKAGE="epel-release" + fi + + if ! command -v dnf &>/dev/null; then + run_cmd "Installing EPEL repository" yum install -y "$EPEL_PACKAGE" || log_fatal "Failed to install EPEL repository" + run_cmd "Installing yum-plugin-copr" yum install -y yum-plugin-copr || log_fatal "Failed to install yum-plugin-copr" + run_cmd "Enabling OpenVPN Copr repo" yum copr enable -y @OpenVPN/openvpn-release-2.6 || log_fatal "Failed to enable OpenVPN Copr repo" + else + run_cmd "Installing EPEL repository" dnf install -y "$EPEL_PACKAGE" || log_fatal "Failed to install EPEL repository" + run_cmd "Installing dnf-plugins-core" dnf install -y dnf-plugins-core || log_fatal "Failed to install dnf-plugins-core" + run_cmd "Enabling OpenVPN Copr repo" dnf copr enable -y @OpenVPN/openvpn-release-2.6 || log_fatal "Failed to enable OpenVPN Copr repo" fi log_info "OpenVPN Copr repository configured" @@ -1002,7 +1005,7 @@ function installOpenVPN() { # Install the latest version of easy-rsa from source, if not already installed. if [[ ! -d /etc/openvpn/easy-rsa/ ]]; then - run_cmd "Downloading Easy-RSA v${EASYRSA_VERSION}" curl -fL --retry 3 -o ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" + run_cmd "Downloading Easy-RSA v${EASYRSA_VERSION}" curl -fL --retry 5 -o ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" log_info "Verifying Easy-RSA checksum..." CHECKSUM_OUTPUT=$(echo "${EASYRSA_SHA256} $HOME/easy-rsa.tgz" | sha256sum -c 2>&1) || { _log_to_file "[CHECKSUM] $CHECKSUM_OUTPUT" From bbf93a19d5ddeb8c06c8a5e0816cd7a1f3883534 Mon Sep 17 00:00:00 2001 From: Guo Yunhe Date: Fri, 12 Dec 2025 04:22:12 +0800 Subject: [PATCH 126/141] Add openSUSE Tumbleweed/Leap support (#1166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: Stanislas Lange --- .github/workflows/docker-test.yml | 4 ++++ Makefile | 8 ++++++++ README.md | 26 +++++++++++++----------- openvpn-install.sh | 33 +++++++++++++++++++++++++++++-- test/Dockerfile.server | 4 ++++ 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index a2d2b5f..f9325f2 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -56,6 +56,10 @@ jobs: image: almalinux:10 - name: archlinux image: archlinux:latest + - name: opensuse-leap-16.0 + image: opensuse/leap:16.0 + - name: opensuse-tumbleweed + image: opensuse/tumbleweed - name: oraclelinux-8 image: oraclelinux:8 - name: oraclelinux-9 diff --git a/Makefile b/Makefile index c73f669..8c3e871 100644 --- a/Makefile +++ b/Makefile @@ -111,6 +111,12 @@ test-arch: test-centos-stream-9: $(MAKE) test BASE_IMAGE=quay.io/centos/centos:stream9 +test-opensuse-leap: + $(MAKE) test BASE_IMAGE=opensuse/leap:16.0 + +test-opensuse-tumbleweed: + $(MAKE) test BASE_IMAGE=opensuse/tumbleweed + # Test all distributions (runs sequentially) test-all: $(MAKE) test-ubuntu-18.04 @@ -130,3 +136,5 @@ test-all: $(MAKE) test-amazon-2023 $(MAKE) test-arch $(MAKE) test-centos-stream-9 + $(MAKE) test-opensuse-leap + $(MAKE) test-opensuse-tumbleweed diff --git a/README.md b/README.md index 67690c9..5f002c1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Lint](https://github.com/angristan/openvpn-install/workflows/Lint/badge.svg) [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/angristan) -OpenVPN installer for Debian, Ubuntu, Fedora, CentOS, Amazon Linux, Arch Linux, Oracle Linux, Rocky Linux and AlmaLinux. +OpenVPN installer for Debian, Ubuntu, Fedora, openSUSE, CentOS, Amazon Linux, Arch Linux, Oracle Linux, Rocky Linux and AlmaLinux. This script will let you setup your own secure VPN server in just a few seconds. @@ -147,17 +147,19 @@ export PASS="1" The script supports these Linux distributions: -| | Support | -| ------------------ | ------- | -| AlmaLinux >= 8 | ✅ 🤖 | -| Amazon Linux 2023 | ✅ 🤖 | -| Arch Linux | ✅ 🤖 | -| CentOS Stream >= 8 | ✅ 🤖 | -| Debian >= 11 | ✅ 🤖 | -| Fedora >= 40 | ✅ 🤖 | -| Oracle Linux >= 8 | ✅ 🤖 | -| Rocky Linux >= 8 | ✅ 🤖 | -| Ubuntu >= 18.04 | ✅ 🤖 | +| | Support | +| ------------------- | ------- | +| AlmaLinux >= 8 | ✅ 🤖 | +| Amazon Linux 2023 | ✅ 🤖 | +| Arch Linux | ✅ 🤖 | +| CentOS Stream >= 8 | ✅ 🤖 | +| Debian >= 11 | ✅ 🤖 | +| Fedora >= 40 | ✅ 🤖 | +| openSUSE Leap >= 16 | ✅ 🤖 | +| openSUSE Tumbleweed | ✅ 🤖 | +| Oracle Linux >= 8 | ✅ 🤖 | +| Rocky Linux >= 8 | ✅ 🤖 | +| Ubuntu >= 18.04 | ✅ 🤖 | To be noted: diff --git a/openvpn-install.sh b/openvpn-install.sh index 8a22e93..2ed9da2 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -189,11 +189,21 @@ function checkOS() { fi fi fi - elif [[ -e /etc/system-release ]]; then + elif [[ -e /etc/os-release ]]; then source /etc/os-release if [[ $ID == "fedora" || $ID_LIKE == "fedora" ]]; then OS="fedora" fi + if [[ $ID == "opensuse-tumbleweed" ]]; then + OS="opensuse" + fi + if [[ $ID == "opensuse-leap" ]]; then + OS="opensuse" + if [[ ${VERSION_ID%.*} -lt 16 ]]; then + log_info "The script only supports openSUSE Leap 16+." + log_fatal "Your version of openSUSE Leap is not supported." + fi + fi if [[ $ID == "centos" || $ID == "rocky" || $ID == "almalinux" ]]; then OS="centos" fi @@ -213,10 +223,13 @@ function checkOS() { log_fatal "Your version of Amazon Linux is not supported." fi fi + if [[ $ID == "arch" ]]; then + OS="arch" + fi elif [[ -e /etc/arch-release ]]; then OS=arch else - log_fatal "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, CentOS, Amazon Linux 2023, Oracle Linux, Arch Linux, Rocky Linux or AlmaLinux system." + log_fatal "It looks like you aren't running this installer on a Debian, Ubuntu, Fedora, openSUSE, CentOS, Amazon Linux 2023, Oracle Linux, Arch Linux, Rocky Linux or AlmaLinux system." fi } @@ -363,6 +376,8 @@ function installUnbound() { run_cmd "Installing Unbound" yum install -y unbound elif [[ $OS =~ (fedora|amzn2023) ]]; then run_cmd "Installing Unbound" dnf install -y unbound + elif [[ $OS == "opensuse" ]]; then + run_cmd "Installing Unbound" zypper install -y unbound elif [[ $OS == "arch" ]]; then run_cmd "Installing Unbound" pacman -Syu --noconfirm unbound fi @@ -419,6 +434,13 @@ function installUnbound() { echo ' access-control: fd42:42:42:42::/112 allow' echo ' private-address: fd42:42:42:42::/112' fi + + # Disable remote-control (requires SSL certs on openSUSE) + if [[ $OS == "opensuse" ]]; then + echo '' + echo 'remote-control:' + echo ' control-enable: no' + fi } >/etc/unbound/unbound.conf.d/openvpn.conf run_cmd "Enabling Unbound service" systemctl enable unbound @@ -964,6 +986,8 @@ function installOpenVPN() { run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl elif [[ $OS == 'fedora' ]]; then run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl policycoreutils-python-utils + elif [[ $OS == 'opensuse' ]]; then + run_cmd "Installing OpenVPN" zypper install -y openvpn iptables openssl ca-certificates curl elif [[ $OS == 'arch' ]]; then run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl ca-certificates curl fi @@ -1767,6 +1791,8 @@ function removeUnbound() { run_cmd "Removing Unbound" yum remove -y unbound elif [[ $OS =~ (fedora|amzn2023) ]]; then run_cmd "Removing Unbound" dnf remove -y unbound + elif [[ $OS == 'opensuse' ]]; then + run_cmd "Removing Unbound" zypper remove -y unbound fi run_cmd "Removing Unbound config" rm -rf /etc/unbound/ @@ -1793,6 +1819,7 @@ function removeOpenVPN() { # Remove customised service run_cmd "Removing service file" rm /etc/systemd/system/openvpn-server@.service else + # Debian, Ubuntu, openSUSE use openvpn@server run_cmd "Disabling OpenVPN service" systemctl disable openvpn@server run_cmd "Stopping OpenVPN service" systemctl stop openvpn@server # Remove customised service @@ -1845,6 +1872,8 @@ function removeOpenVPN() { run_cmd "Removing OpenVPN" dnf remove -y openvpn # Disable Copr repo run_cmd "Disabling OpenVPN Copr repo" dnf copr disable -y @OpenVPN/openvpn-release-2.6 2>/dev/null || true + elif [[ $OS == 'opensuse' ]]; then + run_cmd "Removing OpenVPN" zypper remove -y openvpn fi # Cleanup diff --git a/test/Dockerfile.server b/test/Dockerfile.server index fee1c81..7f7dbf4 100644 --- a/test/Dockerfile.server +++ b/test/Dockerfile.server @@ -25,6 +25,10 @@ RUN if command -v apt-get >/dev/null; then \ pacman -Syu --noconfirm \ iproute2 iptables curl procps-ng bind \ && pacman -Scc --noconfirm; \ + elif command -v zypper >/dev/null; then \ + zypper install -y \ + iproute2 iptables curl procps systemd tar gzip bind-utils gawk \ + && zypper clean -a; \ fi # Create TUN device (will be mounted at runtime) From 04f2996c7951753ecda4ff10b92b34f9e37881ea Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Dec 2025 00:04:51 +0100 Subject: [PATCH 127/141] fix: disable ifconfig-pool-persist when duplicate-cn is enabled (#1354) ## Summary - Only add `ifconfig-pool-persist` to server.conf when `MULTI_CLIENT != y` - Add note in the installation prompt about this limitation - Update README to mention the trade-off First reported in https://github.com/angristan/openvpn-install/issues/440#issuecomment-2987417197 ## Background `ifconfig-pool-persist` is incompatible with `duplicate-cn`. When `duplicate-cn` is enabled, OpenVPN bypasses common name matching in the IP pool allocation, making the persistence file ineffective. From [OpenVPN source](https://github.com/OpenVPN/openvpn/blob/e5ff8247/src/openvpn/init.c#L3608-L3610): ```c if (o->duplicate_cn && o->ifconfig_pool_persist_filename) { msg(M_WARN, "WARNING: --ifconfig-pool-persist will not work with --duplicate-cn"); } ``` Previously, the script always added `ifconfig-pool-persist ipp.txt` regardless of whether `duplicate-cn` was enabled via `MULTI_CLIENT=y`. --- README.md | 2 +- openvpn-install.sh | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5f002c1..b8bd758 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ export PASS="1" - Block DNS leaks on Windows 10 - Randomised server certificate name - Choice to protect clients with a password (private key encryption) -- Option to allow multiple devices to use the same client profile simultaneously +- Option to allow multiple devices to use the same client profile simultaneously (disables persistent IP addresses) - Many other little things! ## Compatibility diff --git a/openvpn-install.sh b/openvpn-install.sh index 2ed9da2..1d86962 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -636,6 +636,7 @@ function installQuestions() { done log_menu "" log_prompt "Do you want to allow a single .ovpn profile to be used on multiple devices simultaneously?" + log_prompt "Note: Enabling this disables persistent IP addresses for clients." until [[ $MULTI_CLIENT =~ (y|n) ]]; do read -rp "Allow multiple devices per client? [y/n]: " -e -i n MULTI_CLIENT done @@ -1124,8 +1125,12 @@ persist-key persist-tun keepalive 10 120 topology subnet -server 10.8.0.0 255.255.255.0 -ifconfig-pool-persist ipp.txt" >>/etc/openvpn/server.conf +server 10.8.0.0 255.255.255.0" >>/etc/openvpn/server.conf + + # ifconfig-pool-persist is incompatible with duplicate-cn + if [[ $MULTI_CLIENT != "y" ]]; then + echo "ifconfig-pool-persist ipp.txt" >>/etc/openvpn/server.conf + fi # DNS resolvers case $DNS in From 79b276351410fd10055d2a79b832696ace54d917 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Dec 2025 00:47:10 +0100 Subject: [PATCH 128/141] feat: add remote-cert-tls client to server configuration (#1359) ## Summary - Add `remote-cert-tls client` directive to server config to ensure only certificates with "TLS Web Client Authentication" EKU can connect - Document the feature in the Security and Encryption section of README.md --- README.md | 6 ++++++ openvpn-install.sh | 1 + 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index b8bd758..f1dc142 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,12 @@ So both provide an additional layer of security and mitigate DoS attacks. They a The script supports both and uses `tls-crypt` by default. +### Certificate type verification (`remote-cert-tls`) + +The server is configured with `remote-cert-tls client`, which requires connecting peers to have a certificate with the "TLS Web Client Authentication" extended key usage. This prevents a server certificate from being used to impersonate a client. + +Similarly, clients are configured with `remote-cert-tls server` to ensure they only connect to servers presenting valid server certificates. This protects against an attacker with a valid client certificate setting up a rogue server. + ### Data Channel Offload (DCO) [Data Channel Offload](https://openvpn.net/as-docs/openvpn-data-channel-offload.html) (DCO) is a kernel acceleration feature that significantly improves OpenVPN performance by keeping data channel encryption/decryption in kernel space, eliminating costly context switches between user and kernel space for each packet. diff --git a/openvpn-install.sh b/openvpn-install.sh index 1d86962..ce28c18 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1243,6 +1243,7 @@ cipher $CIPHER ncp-ciphers $CIPHER tls-server tls-version-min 1.2 +remote-cert-tls client tls-cipher $CC_CIPHER client-config-dir /etc/openvpn/ccd status /var/log/openvpn/status.log From 693b4c31fc6fcac782a6dcd913841008c491dc99 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Dec 2025 01:12:30 +0100 Subject: [PATCH 129/141] docs: update Security and Encryption section for modern OpenVPN (#1360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modernise the Security and Encryption section to reflect OpenVPN's improved defaults over the years. - Add version-by-version changelog of security improvements (2.4, 2.5, 2.6) - Add `[!NOTE]` callouts for default changes in subsections - Update Easy-RSA link (v3.0.7 → v3.2.2) --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f1dc142..5b93a83 100644 --- a/README.md +++ b/README.md @@ -225,26 +225,34 @@ We use [shellcheck](https://github.com/koalaman/shellcheck) and [shfmt](https:// ## Security and Encryption -> **Note** -> This section covers security features for OpenVPN 2.4+. OpenVPN 2.5+ introduced additional features like TLS 1.3 support, but this script maintains TLS 1.2 as the minimum for broader client compatibility. +> [!NOTE] +> This script was created in 2016 when OpenVPN's defaults were quite weak. Back then, customising encryption settings was essential for a secure setup. Since then, OpenVPN has significantly improved its defaults, but the script still offers customisation options. -OpenVPN's default settings are pretty weak regarding encryption. This script aims to improve that. +OpenVPN 2.3 and earlier shipped with outdated defaults like Blowfish (BF-CBC), TLS 1.0, and SHA1. Each major release since has brought significant improvements: -OpenVPN 2.4 was a great update regarding encryption. It added support for ECDSA, ECDH, AES GCM, NCP and tls-crypt. +- **OpenVPN 2.4** (2016): Added ECDSA, ECDH, AES-GCM, NCP (cipher negotiation), and tls-crypt +- **OpenVPN 2.5** (2020): Default cipher changed from BF-CBC to AES-256-GCM:AES-128-GCM, added ChaCha20-Poly1305, tls-crypt-v2, and TLS 1.3 support +- **OpenVPN 2.6** (2023): TLS 1.2 minimum by default, compression blocked by default, `--peer-fingerprint` for PKI-less setups, and DCO kernel acceleration If you want more information about an option mentioned below, head to the [OpenVPN manual](https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage). It is very complete. -Most of OpenVPN's encryption-related stuff is managed by [Easy-RSA](https://github.com/OpenVPN/easy-rsa). Defaults parameters are in the [vars.example](https://github.com/OpenVPN/easy-rsa/blob/v3.0.7/easyrsa3/vars.example) file. +Certificate and PKI management is handled by [Easy-RSA](https://github.com/OpenVPN/easy-rsa). Default parameters are in the [vars.example](https://github.com/OpenVPN/easy-rsa/blob/v3.2.2/easyrsa3/vars.example) file. ### Compression +> [!NOTE] +> OpenVPN 2.6+ defaults `--allow-compression` to `no`, which blocks even server-pushed compression. Prior versions allowed servers to push compression settings to clients. + By default, OpenVPN doesn't enable compression. This script provides support for LZ0 and LZ4 (v1/v2) algorithms, the latter being more efficient. However, it is discouraged to use compression since the [VORACLE attack](https://protonvpn.com/blog/voracle-attack/) makes use of it. ### TLS version -OpenVPN accepts TLS 1.0 by default, which is nearly [20 years old](https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.0). +> [!NOTE] +> OpenVPN 2.6+ defaults to TLS 1.2 minimum. Prior versions accepted TLS 1.0 by default. + +OpenVPN 2.5 and earlier accepted TLS 1.0 by default, which is nearly [20 years old](https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_1.0). With `tls-version-min 1.2` we enforce TLS 1.2, which the best protocol available currently for OpenVPN. @@ -267,7 +275,10 @@ OpenVPN uses `SHA-256` as the signature hash by default, and so does the script. ### Data channel -By default, OpenVPN uses `BF-CBC` as the data channel cipher. Blowfish is an old (1993) and weak algorithm. Even the official OpenVPN documentation admits it. +> [!NOTE] +> The default data channel cipher changed in OpenVPN 2.5. Prior versions defaulted to `BF-CBC`, while OpenVPN 2.5+ defaults to `AES-256-GCM:AES-128-GCM`. OpenVPN 2.6+ also includes `CHACHA20-POLY1305` in the default cipher list when available. + +By default, OpenVPN 2.4 and earlier used `BF-CBC` as the data channel cipher. Blowfish is an old (1993) and weak algorithm. Even the official OpenVPN documentation admits it. > The default is BF-CBC, an abbreviation for Blowfish in Cipher Block Chaining mode. > From 3bc52d245be4a513dc9770c45000a2af09503448 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Dec 2025 10:23:36 +0100 Subject: [PATCH 130/141] feat: use modern data-ciphers naming while maintaining 2.4 compatibility (#1363) ## Summary - Add `data-ciphers` directive alongside `ncp-ciphers` for future-proofing - Server config now emits both `data-ciphers` and `ncp-ciphers` - Client config adds `ignore-unknown-option data-ciphers`, `data-ciphers`, and `ncp-ciphers` for full backward compatibility with OpenVPN 2.4 clients ## Context The `ncp-ciphers` option is a legacy alias of `data-ciphers` that is still accepted but deprecated in OpenVPN 2.5+. This change aligns with modern naming conventions while maintaining compatibility with older 2.4 clients. --- README.md | 2 +- openvpn-install.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b93a83..3ade0dc 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,7 @@ The script supports the following ciphers: And defaults to `AES-128-GCM`. -OpenVPN 2.4 added a feature called "NCP": _Negotiable Crypto Parameters_. It means you can provide a cipher suite like with HTTPS. It is set to `AES-256-GCM:AES-128-GCM` by default and overrides the `--cipher` parameter when used with an OpenVPN 2.4 client. For the sake of simplicity, the script set both the `--cipher` and `--ncp-cipher` to the cipher chosen above. +OpenVPN 2.4 added a feature called "NCP": _Negotiable Crypto Parameters_. It means you can provide a cipher suite like with HTTPS. It is set to `AES-256-GCM:AES-128-GCM` by default and overrides the `--cipher` parameter when used with an OpenVPN 2.4 client. For the sake of simplicity, the script sets `--cipher` (fallback for non-NCP clients), `--data-ciphers` (modern OpenVPN 2.5+ naming), and `--ncp-ciphers` (legacy alias for OpenVPN 2.4 compatibility) to the cipher chosen above. ### Control channel diff --git a/openvpn-install.sh b/openvpn-install.sh index ce28c18..c625ee6 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1240,6 +1240,8 @@ cert $SERVER_NAME.crt key $SERVER_NAME.key auth $HMAC_ALG cipher $CIPHER +ignore-unknown-option data-ciphers +data-ciphers $CIPHER ncp-ciphers $CIPHER tls-server tls-version-min 1.2 @@ -1389,6 +1391,9 @@ verify-x509-name $SERVER_NAME name auth $HMAC_ALG auth-nocache cipher $CIPHER +ignore-unknown-option data-ciphers +data-ciphers $CIPHER +ncp-ciphers $CIPHER tls-client tls-version-min 1.2 tls-cipher $CC_CIPHER From 44c995df8e9cdd018872cbba79bddb3164849de5 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Dec 2025 22:09:18 +0100 Subject: [PATCH 131/141] feat: migrate to OpenVPN 2.4+ directory structure and improve distro compatibility (#1364) ## Summary Migrates OpenVPN configuration to use the modern OpenVPN 2.4+ directory structure and improves compatibility across different Linux distributions. Close https://github.com/angristan/openvpn-install/issues/1307, close https://github.com/angristan/openvpn-install/issues/788, close https://github.com/angristan/openvpn-install/issues/605, close https://github.com/angristan/openvpn-install/pull/653, close https://github.com/angristan/openvpn-install/issues/1214 ### Directory Structure Changes - All server files now in `/etc/openvpn/server/` instead of `/etc/openvpn/` - Uses `openvpn-server@server.service` consistently across all distros - `server.conf` uses relative paths for portability ### Distro-Specific User/Group Handling Different distros configure OpenVPN differently: | Distro | User | Group | systemd handles user? | |--------|------|-------|----------------------| | Debian/Ubuntu | nobody | nogroup | No | | Fedora/RHEL/Amazon | openvpn | openvpn | No | | Arch | openvpn | network | **Yes** (via `User=` in service) | The script now: 1. Detects if an `openvpn` user exists and uses appropriate group 2. Checks if systemd service already has `User=` directive 3. Skips `user`/`group` in config when systemd handles it (avoids "double privilege drop" error on Arch) 4. Sets file ownership with `chown -R` for non-root OpenVPN users ### Other Changes - Updated FAQ.md with new paths - Added systemd service file validation in tests - Added CRL reload verification in tests --- FAQ.md | 8 +- openvpn-install.sh | 321 +++++++++++++++++++++----------------- test/server-entrypoint.sh | 131 +++++++++++----- 3 files changed, 273 insertions(+), 187 deletions(-) diff --git a/FAQ.md b/FAQ.md index 12dc709..f05bfdb 100644 --- a/FAQ.md +++ b/FAQ.md @@ -71,7 +71,7 @@ down /usr/share/openvpn/contrib/pull-resolv-conf/client.down - AES CBC - tls-auth -If your client is <2.3.3, remove `tls-version-min 1.2` from your `/etc/openvpn/server.conf` and `.ovpn` files. +If your client is <2.3.3, remove `tls-version-min 1.2` from your `/etc/openvpn/server/server.conf` and `.ovpn` files. --- @@ -117,7 +117,7 @@ Sysctl options are at `/etc/sysctl.d/99-openvpn.conf` **Q:** How can I access computers the OpenVPN server's remote LAN? -**A:** Add a route with the subnet of the remote network to `/etc/openvpn/server.conf` and restart OpenVPN. Example: `push "route 192.168.1.0 255.255.255.0"` if the server's LAN is `192.168.1.0/24` +**A:** Add a route with the subnet of the remote network to `/etc/openvpn/server/server.conf` and restart OpenVPN. Example: `push "route 192.168.1.0 255.255.255.0"` if the server's LAN is `192.168.1.0/24` --- @@ -145,13 +145,13 @@ done < users.txt **Q:** How do I change the default `.ovpn` file created for future clients? -**A:** You can edit the template out of which `.ovpn` files are created by editing `/etc/openvpn/client-template.txt` +**A:** You can edit the template out of which `.ovpn` files are created by editing `/etc/openvpn/server/client-template.txt` --- **Q:** For my clients - I want to set my internal network to pass through the VPN and the rest to go through my internet? -**A:** You would need to edit the `.ovpn` file. You can edit the template out of which those files are created by editing `/etc/openvpn/client-template.txt` file and adding +**A:** You would need to edit the `.ovpn` file. You can edit the template out of which those files are created by editing `/etc/openvpn/server/client-template.txt` file and adding ```sh route-nopull diff --git a/openvpn-install.sh b/openvpn-install.sh index c625ee6..a84bb06 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -970,7 +970,7 @@ function installOpenVPN() { # If OpenVPN isn't installed yet, install it. This script is more-or-less # idempotent on multiple runs, but will only install OpenVPN from upstream # the first time. - if [[ ! -e /etc/openvpn/server.conf ]]; then + if [[ ! -e /etc/openvpn/server/server.conf ]]; then log_header "Installing OpenVPN" # Setup official OpenVPN repository for latest versions @@ -1015,21 +1015,45 @@ function installOpenVPN() { log_info "Data Channel Offload (DCO) is not available (requires OpenVPN 2.6+ and kernel support)" fi + # Create the server directory (OpenVPN 2.4+ directory structure) + run_cmd "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/easy-rsa/ ]]; then - run_cmd "Removing old Easy-RSA" rm -rf /etc/openvpn/easy-rsa/ + if [[ -d /etc/openvpn/server/easy-rsa/ ]]; then + run_cmd "Removing old Easy-RSA" rm -rf /etc/openvpn/server/easy-rsa/ fi fi - # Find out if the machine uses nogroup or nobody for the permissionless group - if grep -qs "^nogroup:" /etc/group; then - NOGROUP=nogroup + # Determine which user/group OpenVPN should run as + # - Fedora/RHEL/Amazon create 'openvpn' user with 'openvpn' group + # - Arch creates 'openvpn' user with 'network' group + # - Debian/Ubuntu/openSUSE don't create a dedicated user, use 'nobody' + # + # Also check if the systemd service file already handles user/group switching. + # If so, we shouldn't add user/group to config (would cause double privilege drop). + SYSTEMD_HANDLES_USER=false + for service_file in /usr/lib/systemd/system/openvpn-server@.service /lib/systemd/system/openvpn-server@.service; do + if [[ -f "$service_file" ]] && grep -q "^User=" "$service_file"; then + SYSTEMD_HANDLES_USER=true + break + fi + done + + if id openvpn &>/dev/null; then + OPENVPN_USER=openvpn + # Get the openvpn user's primary group (e.g., 'openvpn' on Fedora, 'network' on Arch) + OPENVPN_GROUP=$(id -gn openvpn 2>/dev/null || echo openvpn) else - NOGROUP=nobody + OPENVPN_USER=nobody + if grep -qs "^nogroup:" /etc/group; then + OPENVPN_GROUP=nogroup + else + OPENVPN_GROUP=nobody + fi fi # Install the latest version of easy-rsa from source, if not already installed. - if [[ ! -d /etc/openvpn/easy-rsa/ ]]; then + if [[ ! -d /etc/openvpn/server/easy-rsa/ ]]; then run_cmd "Downloading Easy-RSA v${EASYRSA_VERSION}" curl -fL --retry 5 -o ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" log_info "Verifying Easy-RSA checksum..." CHECKSUM_OUTPUT=$(echo "${EASYRSA_SHA256} $HOME/easy-rsa.tgz" | sha256sum -c 2>&1) || { @@ -1038,11 +1062,11 @@ function installOpenVPN() { log_fatal "SHA256 checksum verification failed for easy-rsa download!" } _log_to_file "[CHECKSUM] $CHECKSUM_OUTPUT" - run_cmd "Creating Easy-RSA directory" mkdir -p /etc/openvpn/easy-rsa - run_cmd "Extracting Easy-RSA" tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/easy-rsa + run_cmd "Creating Easy-RSA directory" mkdir -p /etc/openvpn/server/easy-rsa + run_cmd "Extracting Easy-RSA" tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/server/easy-rsa run_cmd "Cleaning up archive" rm -f ~/easy-rsa.tgz - cd /etc/openvpn/easy-rsa/ || return + cd /etc/openvpn/server/easy-rsa/ || return case $CERT_TYPE in 1) echo "set_var EASYRSA_ALGO ec" >vars @@ -1081,55 +1105,58 @@ function installOpenVPN() { case $TLS_SIG in 1) # Generate tls-crypt key - run_cmd "Generating tls-crypt key" openvpn --genkey --secret /etc/openvpn/tls-crypt.key + run_cmd "Generating tls-crypt key" openvpn --genkey --secret /etc/openvpn/server/tls-crypt.key ;; 2) # Generate tls-auth key - run_cmd "Generating tls-auth key" openvpn --genkey --secret /etc/openvpn/tls-auth.key + run_cmd "Generating tls-auth key" openvpn --genkey --secret /etc/openvpn/server/tls-auth.key ;; esac else # If easy-rsa is already installed, grab the generated SERVER_NAME # for client configs - cd /etc/openvpn/easy-rsa/ || return + cd /etc/openvpn/server/easy-rsa/ || return SERVER_NAME=$(cat SERVER_NAME_GENERATED) fi # Move all the generated files log_info "Copying certificates..." - run_cmd "Copying certificates to /etc/openvpn" cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn + run_cmd "Copying certificates to /etc/openvpn/server" cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server if [[ $DH_TYPE == "2" ]]; then - run_cmd "Copying DH parameters" cp dh.pem /etc/openvpn + run_cmd "Copying DH parameters" cp dh.pem /etc/openvpn/server fi # Make cert revocation list readable for non-root - run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/crl.pem + run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/server/crl.pem # Generate server.conf log_info "Generating server configuration..." - echo "port $PORT" >/etc/openvpn/server.conf + echo "port $PORT" >/etc/openvpn/server/server.conf if [[ $IPV6_SUPPORT == 'n' ]]; then - echo "proto $PROTOCOL" >>/etc/openvpn/server.conf + echo "proto $PROTOCOL" >>/etc/openvpn/server/server.conf elif [[ $IPV6_SUPPORT == 'y' ]]; then - echo "proto ${PROTOCOL}6" >>/etc/openvpn/server.conf + echo "proto ${PROTOCOL}6" >>/etc/openvpn/server/server.conf fi if [[ $MULTI_CLIENT == "y" ]]; then - echo "duplicate-cn" >>/etc/openvpn/server.conf + echo "duplicate-cn" >>/etc/openvpn/server/server.conf fi - echo "dev tun -user nobody -group $NOGROUP -persist-key + echo "dev tun" >>/etc/openvpn/server/server.conf + # Only add user/group if systemd doesn't handle it (avoids double privilege drop) + if [[ $SYSTEMD_HANDLES_USER == "false" ]]; then + echo "user $OPENVPN_USER +group $OPENVPN_GROUP" >>/etc/openvpn/server/server.conf + fi + echo "persist-key persist-tun keepalive 10 120 topology subnet -server 10.8.0.0 255.255.255.0" >>/etc/openvpn/server.conf +server 10.8.0.0 255.255.255.0" >>/etc/openvpn/server/server.conf # ifconfig-pool-persist is incompatible with duplicate-cn if [[ $MULTI_CLIENT != "y" ]]; then - echo "ifconfig-pool-persist ipp.txt" >>/etc/openvpn/server.conf + echo "ifconfig-pool-persist ipp.txt" >>/etc/openvpn/server/server.conf fi # DNS resolvers @@ -1146,64 +1173,64 @@ server 10.8.0.0 255.255.255.0" >>/etc/openvpn/server.conf sed -ne 's/^nameserver[[:space:]]\+\([^[:space:]]\+\).*$/\1/p' $RESOLVCONF | while read -r line; do # Copy, if it's a IPv4 |or| if IPv6 is enabled, IPv4/IPv6 does not matter if [[ $line =~ ^[0-9.]*$ ]] || [[ $IPV6_SUPPORT == 'y' ]]; then - echo "push \"dhcp-option DNS $line\"" >>/etc/openvpn/server.conf + echo "push \"dhcp-option DNS $line\"" >>/etc/openvpn/server/server.conf fi done ;; 2) # Self-hosted DNS resolver (Unbound) - echo 'push "dhcp-option DNS 10.8.0.1"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 10.8.0.1"' >>/etc/openvpn/server/server.conf if [[ $IPV6_SUPPORT == 'y' ]]; then - echo 'push "dhcp-option DNS fd42:42:42:42::1"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS fd42:42:42:42::1"' >>/etc/openvpn/server/server.conf fi ;; 3) # Cloudflare - echo 'push "dhcp-option DNS 1.0.0.1"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 1.1.1.1"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 1.0.0.1"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 1.1.1.1"' >>/etc/openvpn/server/server.conf ;; 4) # Quad9 - echo 'push "dhcp-option DNS 9.9.9.9"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 149.112.112.112"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 9.9.9.9"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 149.112.112.112"' >>/etc/openvpn/server/server.conf ;; 5) # Quad9 uncensored - echo 'push "dhcp-option DNS 9.9.9.10"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 149.112.112.10"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 9.9.9.10"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 149.112.112.10"' >>/etc/openvpn/server/server.conf ;; 6) # FDN - echo 'push "dhcp-option DNS 80.67.169.40"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 80.67.169.12"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 80.67.169.40"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 80.67.169.12"' >>/etc/openvpn/server/server.conf ;; 7) # DNS.WATCH - echo 'push "dhcp-option DNS 84.200.69.80"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 84.200.70.40"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 84.200.69.80"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 84.200.70.40"' >>/etc/openvpn/server/server.conf ;; 8) # OpenDNS - echo 'push "dhcp-option DNS 208.67.222.222"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 208.67.220.220"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 208.67.222.222"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 208.67.220.220"' >>/etc/openvpn/server/server.conf ;; 9) # Google - echo 'push "dhcp-option DNS 8.8.8.8"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 8.8.4.4"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 8.8.8.8"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 8.8.4.4"' >>/etc/openvpn/server/server.conf ;; 10) # Yandex Basic - echo 'push "dhcp-option DNS 77.88.8.8"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 77.88.8.1"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 77.88.8.8"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 77.88.8.1"' >>/etc/openvpn/server/server.conf ;; 11) # AdGuard DNS - echo 'push "dhcp-option DNS 94.140.14.14"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 94.140.15.15"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 94.140.14.14"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 94.140.15.15"' >>/etc/openvpn/server/server.conf ;; 12) # NextDNS - echo 'push "dhcp-option DNS 45.90.28.167"' >>/etc/openvpn/server.conf - echo 'push "dhcp-option DNS 45.90.30.167"' >>/etc/openvpn/server.conf + echo 'push "dhcp-option DNS 45.90.28.167"' >>/etc/openvpn/server/server.conf + echo 'push "dhcp-option DNS 45.90.30.167"' >>/etc/openvpn/server/server.conf ;; 13) # Custom DNS - echo "push \"dhcp-option DNS $DNS1\"" >>/etc/openvpn/server.conf + echo "push \"dhcp-option DNS $DNS1\"" >>/etc/openvpn/server/server.conf if [[ $DNS2 != "" ]]; then - echo "push \"dhcp-option DNS $DNS2\"" >>/etc/openvpn/server.conf + echo "push \"dhcp-option DNS $DNS2\"" >>/etc/openvpn/server/server.conf fi ;; esac - echo 'push "redirect-gateway def1 bypass-dhcp"' >>/etc/openvpn/server.conf + echo 'push "redirect-gateway def1 bypass-dhcp"' >>/etc/openvpn/server/server.conf # IPv6 network settings if needed if [[ $IPV6_SUPPORT == 'y' ]]; then @@ -1211,26 +1238,26 @@ server 10.8.0.0 255.255.255.0" >>/etc/openvpn/server.conf tun-ipv6 push tun-ipv6 push "route-ipv6 2000::/3" -push "redirect-gateway ipv6"' >>/etc/openvpn/server.conf +push "redirect-gateway ipv6"' >>/etc/openvpn/server/server.conf fi if [[ $COMPRESSION_ENABLED == "y" ]]; then - echo "compress $COMPRESSION_ALG" >>/etc/openvpn/server.conf + echo "compress $COMPRESSION_ALG" >>/etc/openvpn/server/server.conf fi if [[ $DH_TYPE == "1" ]]; then - echo "dh none" >>/etc/openvpn/server.conf - echo "ecdh-curve $DH_CURVE" >>/etc/openvpn/server.conf + echo "dh none" >>/etc/openvpn/server/server.conf + echo "ecdh-curve $DH_CURVE" >>/etc/openvpn/server/server.conf elif [[ $DH_TYPE == "2" ]]; then - echo "dh dh.pem" >>/etc/openvpn/server.conf + echo "dh dh.pem" >>/etc/openvpn/server/server.conf fi case $TLS_SIG in 1) - echo "tls-crypt tls-crypt.key" >>/etc/openvpn/server.conf + echo "tls-crypt tls-crypt.key" >>/etc/openvpn/server/server.conf ;; 2) - echo "tls-auth tls-auth.key 0" >>/etc/openvpn/server.conf + echo "tls-auth tls-auth.key 0" >>/etc/openvpn/server/server.conf ;; esac @@ -1247,15 +1274,23 @@ tls-server tls-version-min 1.2 remote-cert-tls client tls-cipher $CC_CIPHER -client-config-dir /etc/openvpn/ccd +client-config-dir ccd status /var/log/openvpn/status.log -verb 3" >>/etc/openvpn/server.conf +verb 3" >>/etc/openvpn/server/server.conf # Create client-config-dir dir - run_cmd "Creating client config directory" mkdir -p /etc/openvpn/ccd + run_cmd "Creating client config directory" mkdir -p /etc/openvpn/server/ccd # Create log dir run_cmd "Creating log directory" mkdir -p /var/log/openvpn + # On distros that use a dedicated OpenVPN user (not "nobody"), e.g., Fedora, RHEL, Arch, + # set ownership so OpenVPN can read config/certs and write to log directory + if [[ $OPENVPN_USER != "nobody" ]]; then + log_info "Setting ownership for OpenVPN user..." + chown -R "$OPENVPN_USER:$OPENVPN_GROUP" /etc/openvpn/server + chown "$OPENVPN_USER:$OPENVPN_GROUP" /var/log/openvpn + fi + # Enable routing log_info "Enabling IP forwarding..." run_cmd "Creating sysctl.d directory" mkdir -p /etc/sysctl.d @@ -1276,33 +1311,41 @@ verb 3" >>/etc/openvpn/server.conf fi # Finally, restart and enable OpenVPN + # OpenVPN 2.4+ uses openvpn-server@.service with config in /etc/openvpn/server/ log_info "Configuring OpenVPN service..." - if [[ $OS == 'arch' || $OS == 'fedora' || $OS == 'centos' || $OS == 'oracle' || $OS == 'amzn2023' ]]; then - # Don't modify package-provided service - run_cmd "Copying OpenVPN service file" cp /usr/lib/systemd/system/openvpn-server@.service /etc/systemd/system/openvpn-server@.service - # Workaround to fix OpenVPN service on OpenVZ - run_cmd "Patching service file (LimitNPROC)" sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn-server@.service - # Another workaround to keep using /etc/openvpn/ - run_cmd "Patching service file (paths)" sed -i 's|/etc/openvpn/server|/etc/openvpn|' /etc/systemd/system/openvpn-server@.service - - run_cmd "Reloading systemd" systemctl daemon-reload - run_cmd "Enabling OpenVPN service" systemctl enable openvpn-server@server - run_cmd "Starting OpenVPN service" systemctl restart openvpn-server@server + # Find the service file (location and name vary by distro) + # Modern distros: openvpn-server@.service in /usr/lib/systemd/system/ or /lib/systemd/system/ + # openSUSE: openvpn@.service (old-style) that we need to adapt + if [[ -f /usr/lib/systemd/system/openvpn-server@.service ]]; then + SERVICE_SOURCE="/usr/lib/systemd/system/openvpn-server@.service" + elif [[ -f /lib/systemd/system/openvpn-server@.service ]]; then + SERVICE_SOURCE="/lib/systemd/system/openvpn-server@.service" + elif [[ -f /usr/lib/systemd/system/openvpn@.service ]]; then + # openSUSE uses old-style service, we'll create our own openvpn-server@.service + SERVICE_SOURCE="/usr/lib/systemd/system/openvpn@.service" + elif [[ -f /lib/systemd/system/openvpn@.service ]]; then + SERVICE_SOURCE="/lib/systemd/system/openvpn@.service" else - # Don't modify package-provided service - run_cmd "Copying OpenVPN service file" cp /lib/systemd/system/openvpn\@.service /etc/systemd/system/openvpn\@.service - - # Workaround to fix OpenVPN service on OpenVZ - run_cmd "Patching service file (LimitNPROC)" sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn\@.service - # Another workaround to keep using /etc/openvpn/ - run_cmd "Patching service file (paths)" sed -i 's|/etc/openvpn/server|/etc/openvpn|' /etc/systemd/system/openvpn\@.service - - run_cmd "Reloading systemd" systemctl daemon-reload - run_cmd "Enabling OpenVPN service" systemctl enable openvpn@server - run_cmd "Starting OpenVPN service" systemctl restart openvpn@server + log_fatal "Could not find openvpn-server@.service or openvpn@.service file" fi + # Don't modify package-provided service, copy to /etc/systemd/system/ + run_cmd "Copying OpenVPN service file" cp "$SERVICE_SOURCE" /etc/systemd/system/openvpn-server@.service + + # Workaround to fix OpenVPN service on OpenVZ + run_cmd "Patching service file (LimitNPROC)" sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn-server@.service + + # Ensure the service uses /etc/openvpn/server/ as working directory + # This is needed for openSUSE which uses old-style paths by default + if grep -q "cd /etc/openvpn/" /etc/systemd/system/openvpn-server@.service; then + run_cmd "Patching service file (paths)" sed -i 's|/etc/openvpn/|/etc/openvpn/server/|g' /etc/systemd/system/openvpn-server@.service + fi + + run_cmd "Reloading systemd" systemctl daemon-reload + run_cmd "Enabling OpenVPN service" systemctl enable openvpn-server@server + run_cmd "Starting OpenVPN service" systemctl restart openvpn-server@server + if [[ $DNS == 2 ]]; then installUnbound fi @@ -1373,12 +1416,12 @@ WantedBy=multi-user.target" >/etc/systemd/system/iptables-openvpn.service # client-template.txt is created so we have a template to add further users later log_info "Creating client template..." - echo "client" >/etc/openvpn/client-template.txt + echo "client" >/etc/openvpn/server/client-template.txt if [[ $PROTOCOL == 'udp' ]]; then - echo "proto udp" >>/etc/openvpn/client-template.txt - echo "explicit-exit-notify" >>/etc/openvpn/client-template.txt + echo "proto udp" >>/etc/openvpn/server/client-template.txt + echo "explicit-exit-notify" >>/etc/openvpn/server/client-template.txt elif [[ $PROTOCOL == 'tcp' ]]; then - echo "proto tcp-client" >>/etc/openvpn/client-template.txt + echo "proto tcp-client" >>/etc/openvpn/server/client-template.txt fi echo "remote $IP $PORT dev tun @@ -1399,10 +1442,10 @@ tls-version-min 1.2 tls-cipher $CC_CIPHER ignore-unknown-option block-outside-dns setenv opt block-outside-dns # Prevent Windows 10 DNS leak -verb 3" >>/etc/openvpn/client-template.txt +verb 3" >>/etc/openvpn/server/client-template.txt if [[ $COMPRESSION_ENABLED == "y" ]]; then - echo "compress $COMPRESSION_ALG" >>/etc/openvpn/client-template.txt + echo "compress $COMPRESSION_ALG" >>/etc/openvpn/server/client-template.txt fi # Generate the custom client.ovpn @@ -1431,9 +1474,9 @@ function getHomeDir() { function regenerateCRL() { export EASYRSA_CRL_DAYS=$DEFAULT_CRL_VALIDITY_DURATION_DAYS run_cmd "Regenerating CRL" ./easyrsa gen-crl - run_cmd "Removing old CRL" rm -f /etc/openvpn/crl.pem - run_cmd "Copying new CRL" cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem - run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/crl.pem + run_cmd "Removing old CRL" rm -f /etc/openvpn/server/crl.pem + run_cmd "Copying new CRL" cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem + run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/server/crl.pem } # Helper function to generate .ovpn client config file @@ -1443,37 +1486,37 @@ function generateClientConfig() { # Determine if we use tls-auth or tls-crypt local tls_sig="" - if grep -qs "^tls-crypt" /etc/openvpn/server.conf; then + if grep -qs "^tls-crypt" /etc/openvpn/server/server.conf; then tls_sig="1" - elif grep -qs "^tls-auth" /etc/openvpn/server.conf; then + elif grep -qs "^tls-auth" /etc/openvpn/server/server.conf; then tls_sig="2" fi # Generate the custom client.ovpn - run_cmd "Creating client config" cp /etc/openvpn/client-template.txt "$home_dir/$client.ovpn" + run_cmd "Creating client config" cp /etc/openvpn/server/client-template.txt "$home_dir/$client.ovpn" { echo "" - cat "/etc/openvpn/easy-rsa/pki/ca.crt" + cat "/etc/openvpn/server/easy-rsa/pki/ca.crt" echo "" echo "" - awk '/BEGIN/,/END CERTIFICATE/' "/etc/openvpn/easy-rsa/pki/issued/$client.crt" + awk '/BEGIN/,/END CERTIFICATE/' "/etc/openvpn/server/easy-rsa/pki/issued/$client.crt" echo "" echo "" - cat "/etc/openvpn/easy-rsa/pki/private/$client.key" + cat "/etc/openvpn/server/easy-rsa/pki/private/$client.key" echo "" case $tls_sig in 1) echo "" - cat /etc/openvpn/tls-crypt.key + cat /etc/openvpn/server/tls-crypt.key echo "" ;; 2) echo "key-direction 1" echo "" - cat /etc/openvpn/tls-auth.key + cat /etc/openvpn/server/tls-auth.key echo "" ;; esac @@ -1490,7 +1533,7 @@ function selectClient() { local show_expiry="${1:-false}" local client_number - NUMBEROFCLIENTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^V") + NUMBEROFCLIENTS=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V") if [[ $NUMBEROFCLIENTS == '0' ]]; then log_fatal "You have no existing clients!" fi @@ -1498,16 +1541,16 @@ function selectClient() { if [[ $show_expiry == "true" ]]; then local i=1 while read -r client; do - local client_cert="/etc/openvpn/easy-rsa/pki/issued/$client.crt" + local client_cert="/etc/openvpn/server/easy-rsa/pki/issued/$client.crt" local days days=$(getDaysUntilExpiry "$client_cert") local expiry expiry=$(formatExpiry "$days") echo " $i) $client $expiry" ((i++)) - done < <(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2) + done < <(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2) else - tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' + tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep "^V" | cut -d '=' -f 2 | nl -s ') ' fi until [[ ${CLIENTNUMBER:-$client_number} -ge 1 && ${CLIENTNUMBER:-$client_number} -le $NUMBEROFCLIENTS ]]; do @@ -1518,7 +1561,7 @@ function selectClient() { fi done CLIENTNUMBER="${CLIENTNUMBER:-$client_number}" - CLIENT=$(tail -n +2 /etc/openvpn/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 newClient() { @@ -1548,12 +1591,12 @@ function newClient() { read -rp "Select an option [1-2]: " -e -i 1 PASS done - CLIENTEXISTS=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -E "^V" | grep -c -E "/CN=$CLIENT\$") + CLIENTEXISTS=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -E "^V" | grep -c -E "/CN=$CLIENT\$") if [[ $CLIENTEXISTS != '0' ]]; then log_error "The specified client CN was already found in easy-rsa, please choose another name." exit else - cd /etc/openvpn/easy-rsa/ || return + cd /etc/openvpn/server/easy-rsa/ || return log_info "Generating client certificate..." export EASYRSA_CERT_EXPIRE=$CLIENT_CERT_DURATION_DAYS case $PASS in @@ -1584,14 +1627,14 @@ function revokeClient() { log_prompt "Select the existing client certificate you want to revoke" selectClient - cd /etc/openvpn/easy-rsa/ || return + cd /etc/openvpn/server/easy-rsa/ || return log_info "Revoking certificate for $CLIENT..." run_cmd "Revoking certificate" ./easyrsa --batch revoke-issued "$CLIENT" regenerateCRL run_cmd "Removing client config from /home" find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete run_cmd "Removing client config from /root" rm -f "/root/$CLIENT.ovpn" - run_cmd "Removing IP assignment" sed -i "/^$CLIENT,.*/d" /etc/openvpn/ipp.txt - run_cmd "Backing up index" cp /etc/openvpn/easy-rsa/pki/index.txt{,.bk} + run_cmd "Removing IP assignment" sed -i "/^$CLIENT,.*/d" /etc/openvpn/server/ipp.txt + run_cmd "Backing up index" cp /etc/openvpn/server/easy-rsa/pki/index.txt{,.bk} log_success "Certificate for client $CLIENT revoked." } @@ -1614,11 +1657,11 @@ function renewClient() { client_cert_duration_days=$CLIENT_CERT_DURATION_DAYS fi - cd /etc/openvpn/easy-rsa/ || return + cd /etc/openvpn/server/easy-rsa/ || return log_info "Renewing certificate for $CLIENT..." # Backup the old certificate before renewal - run_cmd "Backing up old certificate" cp "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt" "/etc/openvpn/easy-rsa/pki/issued/$CLIENT.crt.bak" + run_cmd "Backing up old certificate" cp "/etc/openvpn/server/easy-rsa/pki/issued/$CLIENT.crt" "/etc/openvpn/server/easy-rsa/pki/issued/$CLIENT.crt.bak" # Renew the certificate (keeps the same private key) export EASYRSA_CERT_EXPIRE=$client_cert_duration_days @@ -1645,10 +1688,10 @@ function renewServer() { log_header "Renew Server Certificate" - # Get the server name from the config - server_name=$(grep '^cert ' /etc/openvpn/server.conf | cut -d ' ' -f 2 | sed 's/\.crt$//') + # Get the server name from the config (extract basename since path may be relative) + server_name=$(basename "$(grep '^cert ' /etc/openvpn/server/server.conf | cut -d ' ' -f 2)" .crt) if [[ -z "$server_name" ]]; then - log_fatal "Could not determine server certificate name from /etc/openvpn/server.conf" + log_fatal "Could not determine server certificate name from /etc/openvpn/server/server.conf" fi log_prompt "This will renew the server certificate: $server_name" @@ -1672,11 +1715,11 @@ function renewServer() { server_cert_duration_days=$SERVER_CERT_DURATION_DAYS fi - cd /etc/openvpn/easy-rsa/ || return + cd /etc/openvpn/server/easy-rsa/ || return log_info "Renewing server certificate..." # Backup the old certificate before renewal - run_cmd "Backing up old certificate" cp "/etc/openvpn/easy-rsa/pki/issued/$server_name.crt" "/etc/openvpn/easy-rsa/pki/issued/$server_name.crt.bak" + run_cmd "Backing up old certificate" cp "/etc/openvpn/server/easy-rsa/pki/issued/$server_name.crt" "/etc/openvpn/server/easy-rsa/pki/issued/$server_name.crt.bak" # Renew the certificate (keeps the same private key) export EASYRSA_CERT_EXPIRE=$server_cert_duration_days @@ -1688,18 +1731,12 @@ function renewServer() { # Regenerate the CRL regenerateCRL - # Copy the new certificate to /etc/openvpn/ - run_cmd "Copying new certificate" cp "/etc/openvpn/easy-rsa/pki/issued/$server_name.crt" /etc/openvpn/ + # Copy the new certificate to /etc/openvpn/server/ + run_cmd "Copying new certificate" cp "/etc/openvpn/server/easy-rsa/pki/issued/$server_name.crt" /etc/openvpn/server/ # Restart OpenVPN log_info "Restarting OpenVPN service..." - if [[ $OS =~ (fedora|arch|centos|oracle) ]]; then - run_cmd "Restarting OpenVPN" systemctl restart openvpn-server@server - elif [[ $OS == "ubuntu" ]] && [[ $VERSION_ID == "16.04" ]]; then - run_cmd "Restarting OpenVPN" systemctl restart openvpn - else - run_cmd "Restarting OpenVPN" systemctl restart openvpn@server - fi + run_cmd "Restarting OpenVPN" systemctl restart openvpn-server@server log_success "Server certificate renewed successfully and is valid for $server_cert_duration_days days." } @@ -1743,12 +1780,12 @@ function renewMenu() { log_header "Certificate Renewal" - # Get server certificate expiry for menu display - server_name=$(grep '^cert ' /etc/openvpn/server.conf | cut -d ' ' -f 2 | sed 's/\.crt$//') + # Get server certificate expiry for menu display (extract basename since path may be relative) + server_name=$(basename "$(grep '^cert ' /etc/openvpn/server/server.conf | cut -d ' ' -f 2)" .crt) if [[ -z "$server_name" ]]; then server_expiry="(unknown expiry)" else - server_cert="/etc/openvpn/easy-rsa/pki/issued/$server_name.crt" + server_cert="/etc/openvpn/server/easy-rsa/pki/issued/$server_name.crt" server_days=$(getDaysUntilExpiry "$server_cert") server_expiry=$(formatExpiry "$server_days") fi @@ -1819,23 +1856,15 @@ function removeOpenVPN() { read -rp "Do you really want to remove OpenVPN? [y/n]: " -e -i n REMOVE if [[ $REMOVE == 'y' ]]; then # Get OpenVPN port from the configuration - PORT=$(grep '^port ' /etc/openvpn/server.conf | cut -d " " -f 2) - PROTOCOL=$(grep '^proto ' /etc/openvpn/server.conf | cut -d " " -f 2) + PORT=$(grep '^port ' /etc/openvpn/server/server.conf | cut -d " " -f 2) + PROTOCOL=$(grep '^proto ' /etc/openvpn/server/server.conf | cut -d " " -f 2) # Stop OpenVPN log_info "Stopping OpenVPN service..." - if [[ $OS =~ (fedora|arch|centos|oracle) ]]; then - run_cmd "Disabling OpenVPN service" systemctl disable openvpn-server@server - run_cmd "Stopping OpenVPN service" systemctl stop openvpn-server@server - # Remove customised service - run_cmd "Removing service file" rm /etc/systemd/system/openvpn-server@.service - else - # Debian, Ubuntu, openSUSE use openvpn@server - run_cmd "Disabling OpenVPN service" systemctl disable openvpn@server - run_cmd "Stopping OpenVPN service" systemctl stop openvpn@server - # Remove customised service - run_cmd "Removing service file" rm /etc/systemd/system/openvpn\@.service - fi + run_cmd "Disabling OpenVPN service" systemctl disable openvpn-server@server + run_cmd "Stopping OpenVPN service" systemctl stop openvpn-server@server + # Remove customised service + run_cmd "Removing service file" rm -f /etc/systemd/system/openvpn-server@.service # Remove the iptables rules related to the script log_info "Removing iptables rules..." @@ -1946,7 +1975,7 @@ function manageMenu() { initialCheck # Check if OpenVPN is already installed -if [[ -e /etc/openvpn/server.conf ]]; then +if [[ -e /etc/openvpn/server/server.conf ]]; then manageMenu else installOpenVPN diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 133f3c2..934447b 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -65,12 +65,12 @@ fi echo "Verifying installation..." MISSING_FILES=0 for f in \ - /etc/openvpn/server.conf \ - /etc/openvpn/ca.crt \ - /etc/openvpn/ca.key \ - /etc/openvpn/tls-crypt.key \ - /etc/openvpn/crl.pem \ - /etc/openvpn/easy-rsa/pki/ca.crt \ + /etc/openvpn/server/server.conf \ + /etc/openvpn/server/ca.crt \ + /etc/openvpn/server/ca.key \ + /etc/openvpn/server/tls-crypt.key \ + /etc/openvpn/server/crl.pem \ + /etc/openvpn/server/easy-rsa/pki/ca.crt \ /etc/iptables/add-openvpn-rules.sh \ /root/testclient.ovpn; do if [ ! -f "$f" ]; then @@ -85,9 +85,66 @@ if [ $MISSING_FILES -gt 0 ]; then fi echo "All required files present" + +# ===================================================== +# Verify systemd service file configuration +# ===================================================== +echo "" +echo "=== Verifying systemd service configuration ===" + +# Check that the correct service file was created +SERVICE_FILE="/etc/systemd/system/openvpn-server@.service" +if [ -f "$SERVICE_FILE" ]; then + echo "PASS: openvpn-server@.service exists at $SERVICE_FILE" +else + echo "FAIL: openvpn-server@.service not found at $SERVICE_FILE" + echo "Contents of /etc/systemd/system/:" + find /etc/systemd/system/ -maxdepth 1 -name '*openvpn*' -ls 2>/dev/null || echo "No openvpn service files found" + exit 1 +fi + +# Verify the service file points to /etc/openvpn/server/ (not patched back to /etc/openvpn/) +if grep -q "/etc/openvpn/server" "$SERVICE_FILE"; then + echo "PASS: Service file uses correct path /etc/openvpn/server/" +else + echo "FAIL: Service file does not reference /etc/openvpn/server/" + echo "Service file contents:" + cat "$SERVICE_FILE" + exit 1 +fi + +# Verify the service file syntax is valid (if systemd-analyze is available) +if command -v systemd-analyze >/dev/null 2>&1; then + echo "Validating service file syntax..." + if systemd-analyze verify "$SERVICE_FILE" 2>&1 | tee /tmp/service-verify.log; then + echo "PASS: Service file syntax is valid" + else + # systemd-analyze verify may return non-zero for warnings, check for actual errors + if grep -qi "error" /tmp/service-verify.log; then + echo "FAIL: Service file has syntax errors" + cat /tmp/service-verify.log + exit 1 + else + echo "PASS: Service file syntax is valid (warnings only)" + fi + fi +else + echo "SKIP: systemd-analyze not available, skipping syntax validation" +fi + +# Verify the old service file pattern (openvpn@.service) was NOT created +OLD_SERVICE_FILE="/etc/systemd/system/openvpn@.service" +if [ -f "$OLD_SERVICE_FILE" ]; then + echo "FAIL: Legacy openvpn@.service was created (should use openvpn-server@.service)" + exit 1 +else + echo "PASS: Legacy openvpn@.service not present (correct)" +fi + +echo "=== systemd service configuration verified ===" echo "" echo "Server config:" -cat /etc/openvpn/server.conf +cat /etc/openvpn/server/server.conf # Copy client config to shared volume cp /root/testclient.ovpn /shared/client.ovpn @@ -102,7 +159,7 @@ echo "" echo "=== Testing Certificate Renewal ===" # Get the original certificate serial number for comparison -ORIG_CERT_SERIAL=$(openssl x509 -in /etc/openvpn/easy-rsa/pki/issued/testclient.crt -noout -serial | cut -d= -f2) +ORIG_CERT_SERIAL=$(openssl x509 -in /etc/openvpn/server/easy-rsa/pki/issued/testclient.crt -noout -serial | cut -d= -f2) echo "Original client certificate serial: $ORIG_CERT_SERIAL" # Test client certificate renewal using the script @@ -120,7 +177,7 @@ else fi # Verify new certificate has different serial -NEW_CERT_SERIAL=$(openssl x509 -in /etc/openvpn/easy-rsa/pki/issued/testclient.crt -noout -serial | cut -d= -f2) +NEW_CERT_SERIAL=$(openssl x509 -in /etc/openvpn/server/easy-rsa/pki/issued/testclient.crt -noout -serial | cut -d= -f2) echo "New client certificate serial: $NEW_CERT_SERIAL" if [ "$ORIG_CERT_SERIAL" != "$NEW_CERT_SERIAL" ]; then echo "PASS: Certificate serial changed (renewal created new cert)" @@ -131,8 +188,8 @@ fi # Verify renewed certificate has correct validity period # The default is 3650 days, so the cert should be valid for ~10 years from now -CLIENT_CERT_NOT_AFTER=$(openssl x509 -in /etc/openvpn/easy-rsa/pki/issued/testclient.crt -noout -enddate | cut -d= -f2) -CLIENT_CERT_NOT_BEFORE=$(openssl x509 -in /etc/openvpn/easy-rsa/pki/issued/testclient.crt -noout -startdate | cut -d= -f2) +CLIENT_CERT_NOT_AFTER=$(openssl x509 -in /etc/openvpn/server/easy-rsa/pki/issued/testclient.crt -noout -enddate | cut -d= -f2) +CLIENT_CERT_NOT_BEFORE=$(openssl x509 -in /etc/openvpn/server/easy-rsa/pki/issued/testclient.crt -noout -startdate | cut -d= -f2) echo "Client certificate valid from: $CLIENT_CERT_NOT_BEFORE" echo "Client certificate valid until: $CLIENT_CERT_NOT_AFTER" @@ -159,7 +216,7 @@ else fi # Verify CRL was updated -if [ -f /etc/openvpn/crl.pem ]; then +if [ -f /etc/openvpn/server/crl.pem ]; then echo "PASS: CRL file exists" else echo "FAIL: CRL file missing after renewal" @@ -179,9 +236,9 @@ echo "=== Client Certificate Renewal Tests PASSED ===" echo "" echo "=== Testing Server Certificate Renewal ===" -# Get server certificate name and original serial -SERVER_NAME=$(grep '^cert ' /etc/openvpn/server.conf | cut -d ' ' -f 2 | sed 's/\.crt$//') -ORIG_SERVER_SERIAL=$(openssl x509 -in "/etc/openvpn/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) +# Get server certificate name and original serial (extract basename since path may be relative) +SERVER_NAME=$(basename "$(grep '^cert ' /etc/openvpn/server/server.conf | cut -d ' ' -f 2)" .crt) +ORIG_SERVER_SERIAL=$(openssl x509 -in "/etc/openvpn/server/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) echo "Server certificate: $SERVER_NAME" echo "Original server certificate serial: $ORIG_SERVER_SERIAL" @@ -200,7 +257,7 @@ else fi # Verify new certificate has different serial -NEW_SERVER_SERIAL=$(openssl x509 -in "/etc/openvpn/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) +NEW_SERVER_SERIAL=$(openssl x509 -in "/etc/openvpn/server/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) echo "New server certificate serial: $NEW_SERVER_SERIAL" if [ "$ORIG_SERVER_SERIAL" != "$NEW_SERVER_SERIAL" ]; then echo "PASS: Server certificate serial changed (renewal created new cert)" @@ -210,8 +267,8 @@ else fi # Verify renewed server certificate has correct validity period -SERVER_CERT_NOT_AFTER=$(openssl x509 -in "/etc/openvpn/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -enddate | cut -d= -f2) -SERVER_CERT_NOT_BEFORE=$(openssl x509 -in "/etc/openvpn/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -startdate | cut -d= -f2) +SERVER_CERT_NOT_AFTER=$(openssl x509 -in "/etc/openvpn/server/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -enddate | cut -d= -f2) +SERVER_CERT_NOT_BEFORE=$(openssl x509 -in "/etc/openvpn/server/easy-rsa/pki/issued/$SERVER_NAME.crt" -noout -startdate | cut -d= -f2) echo "Server certificate valid from: $SERVER_CERT_NOT_BEFORE" echo "Server certificate valid until: $SERVER_CERT_NOT_AFTER" @@ -227,17 +284,17 @@ else exit 1 fi -# Verify the new certificate was copied to /etc/openvpn/ -if [ -f "/etc/openvpn/$SERVER_NAME.crt" ]; then - DEPLOYED_SERIAL=$(openssl x509 -in "/etc/openvpn/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) +# Verify the new certificate was copied to /etc/openvpn/server/ +if [ -f "/etc/openvpn/server/$SERVER_NAME.crt" ]; then + DEPLOYED_SERIAL=$(openssl x509 -in "/etc/openvpn/server/$SERVER_NAME.crt" -noout -serial | cut -d= -f2) if [ "$NEW_SERVER_SERIAL" = "$DEPLOYED_SERIAL" ]; then - echo "PASS: New server certificate deployed to /etc/openvpn/" + echo "PASS: New server certificate deployed to /etc/openvpn/server/" else echo "FAIL: Deployed certificate doesn't match renewed certificate" exit 1 fi else - echo "FAIL: Server certificate not found in /etc/openvpn/" + echo "FAIL: Server certificate not found in /etc/openvpn/server/" exit 1 fi @@ -309,11 +366,11 @@ else fi # Verify OpenVPN pushes correct DNS -if grep -q 'push "dhcp-option DNS 10.8.0.1"' /etc/openvpn/server.conf; then +if grep -q 'push "dhcp-option DNS 10.8.0.1"' /etc/openvpn/server/server.conf; then echo "PASS: OpenVPN configured to push Unbound DNS" else echo "FAIL: OpenVPN not configured to push Unbound DNS" - grep "dhcp-option DNS" /etc/openvpn/server.conf || echo "No DNS push found" + grep "dhcp-option DNS" /etc/openvpn/server/server.conf || echo "No DNS push found" exit 1 fi @@ -346,9 +403,9 @@ if [ "$(cat /proc/sys/net/ipv4/ip_forward)" != "1" ]; then } fi -# Start OpenVPN in background (run from /etc/openvpn so relative paths work) -cd /etc/openvpn -openvpn --config /etc/openvpn/server.conf --log /var/log/openvpn-server.log & +# Start OpenVPN in background (run from /etc/openvpn/server so relative paths work) +cd /etc/openvpn/server +openvpn --config /etc/openvpn/server/server.conf --log /var/log/openvpn-server.log & OPENVPN_PID=$! # Wait for OpenVPN to start @@ -453,10 +510,10 @@ echo "Revoking certificate for '$REVOKE_CLIENT'..." REVOKE_OUTPUT="/tmp/revoke-output.log" # MENU_OPTION=2 is revoke, CLIENTNUMBER is dynamically determined from index.txt # We need to find the client number for revoketest -REVOKE_CLIENT_NUM=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep "^V" | grep -n "CN=$REVOKE_CLIENT\$" | cut -d: -f1) +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/easy-rsa/pki/index.txt + cat /etc/openvpn/server/easy-rsa/pki/index.txt exit 1 fi echo "Revoke client number: $REVOKE_CLIENT_NUM" @@ -471,11 +528,11 @@ else fi # Verify certificate is marked as revoked in index.txt -if tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -q "^R.*CN=$REVOKE_CLIENT\$"; then +if tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -q "^R.*CN=$REVOKE_CLIENT\$"; then echo "PASS: Certificate marked as revoked in index.txt" else echo "FAIL: Certificate not marked as revoked" - cat /etc/openvpn/easy-rsa/pki/index.txt + cat /etc/openvpn/server/easy-rsa/pki/index.txt exit 1 fi @@ -520,23 +577,23 @@ else fi # Verify the new certificate is valid (V) in index.txt -if tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -q "^V.*CN=$REVOKE_CLIENT\$"; then +if tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -q "^V.*CN=$REVOKE_CLIENT\$"; then echo "PASS: New certificate is valid in index.txt" else echo "FAIL: New certificate not marked as valid" - cat /etc/openvpn/easy-rsa/pki/index.txt + cat /etc/openvpn/server/easy-rsa/pki/index.txt exit 1 fi # Verify there's also a revoked entry (both should exist) -REVOKED_COUNT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^R.*CN=$REVOKE_CLIENT\$") -VALID_COUNT=$(tail -n +2 /etc/openvpn/easy-rsa/pki/index.txt | grep -c "^V.*CN=$REVOKE_CLIENT\$") +REVOKED_COUNT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^R.*CN=$REVOKE_CLIENT\$") +VALID_COUNT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c "^V.*CN=$REVOKE_CLIENT\$") echo "Certificates for '$REVOKE_CLIENT': $REVOKED_COUNT revoked, $VALID_COUNT valid" if [ "$REVOKED_COUNT" -ge 1 ] && [ "$VALID_COUNT" -eq 1 ]; then echo "PASS: Both revoked and new valid certificate entries exist" else echo "FAIL: Unexpected certificate state" - cat /etc/openvpn/easy-rsa/pki/index.txt + cat /etc/openvpn/server/easy-rsa/pki/index.txt exit 1 fi From 408d577461bcac3c7b0ab13dd4640feeb5721ea9 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Dec 2025 23:17:12 +0100 Subject: [PATCH 132/141] feat: add missing dependencies for all supported distros (#1368) ## Summary - Add `tar` and DNS utilities (`dnsutils`/`bind-utils`/`bind`) to all supported distros - Ensures the script works reliably on minimal system images where these tools may not be pre-installed ## Changes by distro | Distro | Packages added | |--------|----------------| | debian/ubuntu | `tar`, `dnsutils` | | centos | `bind-utils` | | oracle | `bind-utils` | | amzn2023 | `tar`, `bind-utils` | | fedora | `tar`, `bind-utils` | | opensuse | `tar`, `bind-utils` | | arch | `tar`, `bind` | ## Why these packages? - **tar**: Required for extracting Easy-RSA `.tgz` archive - **dnsutils/bind-utils/bind**: Provides `dig` command used as fallback for public IP detection --- openvpn-install.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index a84bb06..d3e3695 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -978,19 +978,19 @@ function installOpenVPN() { log_info "Installing OpenVPN and dependencies..." if [[ $OS =~ (debian|ubuntu) ]]; then - run_cmd "Installing OpenVPN" apt-get install -y openvpn iptables openssl curl ca-certificates + run_cmd "Installing OpenVPN" apt-get install -y openvpn iptables openssl curl ca-certificates tar dnsutils elif [[ $OS == 'centos' ]]; then - run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar 'policycoreutils-python*' + run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar bind-utils 'policycoreutils-python*' elif [[ $OS == 'oracle' ]]; then - run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar policycoreutils-python-utils + run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar bind-utils policycoreutils-python-utils elif [[ $OS == 'amzn2023' ]]; then - run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl + run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl tar bind-utils elif [[ $OS == 'fedora' ]]; then - run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl policycoreutils-python-utils + run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl tar bind-utils policycoreutils-python-utils elif [[ $OS == 'opensuse' ]]; then - run_cmd "Installing OpenVPN" zypper install -y openvpn iptables openssl ca-certificates curl + run_cmd "Installing OpenVPN" zypper install -y openvpn iptables openssl ca-certificates curl tar bind-utils elif [[ $OS == 'arch' ]]; then - run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl ca-certificates curl + run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl ca-certificates curl tar bind fi # Verify ChaCha20-Poly1305 compatibility if selected From 179cbc0c25e296a044b5bb12b44d03d83baf40a8 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Dec 2025 23:38:12 +0100 Subject: [PATCH 133/141] fix: increase DNS test retries and use seq for loop (#1370) - Increase DNS retry count from 5 to 10 for improved test reliability when Unbound needs more time to initialize - Refactor retry loop to use `seq` with a `DNS_MAX_RETRIES` to be cleaner --- test/client-entrypoint.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/client-entrypoint.sh b/test/client-entrypoint.sh index 4dba5bf..0e85117 100755 --- a/test/client-entrypoint.sh +++ b/test/client-entrypoint.sh @@ -84,7 +84,8 @@ fi # Test 3: DNS resolution through Unbound echo "Test 3: Testing DNS resolution via Unbound (10.8.0.1)..." DNS_SUCCESS=false -for i in 1 2 3 4 5; do +DNS_MAX_RETRIES=10 +for i in $(seq 1 $DNS_MAX_RETRIES); do DIG_OUTPUT=$(dig @10.8.0.1 example.com +short +time=5 2>&1) if [ -n "$DIG_OUTPUT" ] && ! echo "$DIG_OUTPUT" | grep -qi "timed out\|SERVFAIL\|connection refused"; then DNS_SUCCESS=true @@ -98,7 +99,7 @@ if [ "$DNS_SUCCESS" = true ]; then echo "PASS: DNS resolution through Unbound works" echo "Resolved example.com to: $(dig @10.8.0.1 example.com +short +time=5)" else - echo "FAIL: DNS resolution through Unbound failed after 5 attempts" + echo "FAIL: DNS resolution through Unbound failed after $DNS_MAX_RETRIES attempts" dig @10.8.0.1 example.com +time=5 || true exit 1 fi From 236e77af6826af82cf56eed2b725d7fcc2ce86d5 Mon Sep 17 00:00:00 2001 From: Stanislas Date: Fri, 12 Dec 2025 23:47:09 +0100 Subject: [PATCH 134/141] feat: add logging for system checks in initialCheck (#1371) Add debug and info logging to initialCheck for better troubleshooting visibility. --- openvpn-install.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openvpn-install.sh b/openvpn-install.sh index d3e3695..16ea068 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -234,13 +234,21 @@ function checkOS() { } function initialCheck() { + log_debug "Checking root privileges..." if ! isRoot; then log_fatal "Sorry, you need to run this script as root." fi + log_debug "Root check passed" + + log_debug "Checking TUN device availability..." if ! tunAvailable; then log_fatal "TUN is not available." fi + log_debug "TUN device available at /dev/net/tun" + + log_debug "Detecting operating system..." checkOS + log_info "Detected OS: $OS (${PRETTY_NAME:-unknown})" } # Check if OpenVPN version is at least the specified version From 9e1bb4b17526345194fff051f633db6ca1473dec Mon Sep 17 00:00:00 2001 From: Stanislas Date: Sat, 13 Dec 2025 01:14:54 +0100 Subject: [PATCH 135/141] feat: enable proper systemd support in Docker tests (#1373) - Replace the `sed` hack that disabled `systemctl` commands with proper systemd support in Docker containers - This allows testing the actual `systemctl` commands used by the install script - No more manual workarounds for starting OpenVPN/Unbound services --- .github/workflows/docker-test.yml | 54 +++++++++++--- docker-compose.yml | 9 ++- test/Dockerfile.server | 23 +++++- test/server-entrypoint.sh | 115 ++++++++++++------------------ 4 files changed, 118 insertions(+), 83 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index f9325f2..2afd63e 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -99,34 +99,56 @@ jobs: docker run -d \ --name openvpn-server \ --hostname openvpn-server \ - --cap-add=NET_ADMIN \ + --privileged \ + --cgroupns=host \ --device=/dev/net/tun:/dev/net/tun \ --sysctl net.ipv4.ip_forward=1 \ --network vpn-test \ --ip 172.28.0.10 \ -v shared-config:/shared \ + -v /sys/fs/cgroup:/sys/fs/cgroup:rw \ + --tmpfs /run \ + --tmpfs /run/lock \ + --stop-signal SIGRTMIN+3 \ openvpn-server - name: Wait for server installation and startup run: | - echo "Waiting for OpenVPN server to install and start..." - for i in {1..60}; do - # Use pgrep -f to match openvpn running with server.conf, not transient - # processes like "openvpn --genkey" that run during installation + echo "Waiting for OpenVPN server to install and client config to be ready..." + for i in {1..90}; do + # Check BOTH conditions: + # 1. OpenVPN server process is running + # 2. Client config file exists in shared volume + OPENVPN_RUNNING=false + CONFIG_EXISTS=false + if docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then - echo "OpenVPN server is running!" + OPENVPN_RUNNING=true + fi + + if docker exec openvpn-server test -f /shared/client.ovpn 2>/dev/null; then + CONFIG_EXISTS=true + fi + + if [ "$OPENVPN_RUNNING" = true ] && [ "$CONFIG_EXISTS" = true ]; then + echo "OpenVPN server is running and client config is ready!" break fi - echo "Waiting... ($i/60)" + + echo "Waiting... ($i/90) - OpenVPN running: $OPENVPN_RUNNING, Config exists: $CONFIG_EXISTS" sleep 5 - # Show logs for debugging - docker logs --tail 20 openvpn-server 2>&1 || true done # Final check if ! docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then echo "ERROR: OpenVPN server failed to start" - docker logs openvpn-server + docker exec openvpn-server systemctl status openvpn-server@server 2>&1 || true + exit 1 + fi + + if ! docker exec openvpn-server test -f /shared/client.ovpn 2>/dev/null; then + echo "ERROR: Client config not generated" + docker exec openvpn-server systemctl status openvpn-test.service 2>&1 || true exit 1 fi @@ -174,6 +196,18 @@ jobs: if: always() run: docker logs openvpn-server 2>&1 || true + - name: Show systemd journal logs + if: always() + run: | + echo "=== openvpn-test.service status ===" + docker exec openvpn-server systemctl status openvpn-test.service 2>&1 || true + echo "" + echo "=== openvpn-test.service journal ===" + docker exec openvpn-server journalctl -u openvpn-test.service --no-pager -n 100 2>&1 || true + echo "" + echo "=== openvpn-server@server.service journal ===" + docker exec openvpn-server journalctl -u openvpn-server@server.service --no-pager -n 50 2>&1 || true + - name: Show install script log if: always() run: | diff --git a/docker-compose.yml b/docker-compose.yml index 1147a4c..7b90969 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,17 +8,22 @@ services: BASE_IMAGE: ${BASE_IMAGE:-ubuntu:24.04} container_name: openvpn-server hostname: openvpn-server - cap_add: - - NET_ADMIN + privileged: true + cgroupns: host devices: - /dev/net/tun:/dev/net/tun sysctls: - net.ipv4.ip_forward=1 volumes: - shared-config:/shared + - /sys/fs/cgroup:/sys/fs/cgroup:rw + tmpfs: + - /run + - /run/lock networks: vpn-test: ipv4_address: 172.28.0.10 + stop_signal: SIGRTMIN+3 healthcheck: test: ["CMD", "pgrep", "openvpn"] interval: 5s diff --git a/test/Dockerfile.server b/test/Dockerfile.server index 7f7dbf4..6f9438c 100644 --- a/test/Dockerfile.server +++ b/test/Dockerfile.server @@ -43,6 +43,27 @@ COPY test/server-entrypoint.sh /entrypoint.sh COPY test/validate-output.sh /opt/test/validate-output.sh RUN chmod +x /entrypoint.sh /opt/test/validate-output.sh +# Create systemd service for the test script +RUN printf '%s\n' \ + '[Unit]' \ + 'Description=OpenVPN Installation Test' \ + 'After=network.target' \ + '' \ + '[Service]' \ + 'Type=oneshot' \ + 'Environment=HOME=/root' \ + 'WorkingDirectory=/root' \ + 'ExecStart=/entrypoint.sh' \ + 'RemainAfterExit=yes' \ + 'StandardOutput=journal+console' \ + 'StandardError=journal+console' \ + '' \ + '[Install]' \ + 'WantedBy=multi-user.target' \ + > /etc/systemd/system/openvpn-test.service \ + && systemctl enable openvpn-test.service + WORKDIR /opt -ENTRYPOINT ["/entrypoint.sh"] +STOPSIGNAL SIGRTMIN+3 +CMD ["/sbin/init"] diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 934447b..63260bb 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -27,21 +27,12 @@ export CLIENT=testclient export PASS=1 export ENDPOINT=openvpn-server -# Prepare script for container environment: -# - Replace systemctl calls with no-ops (systemd doesn't work in containers) -# - Skip Unbound startup validation (we start Unbound manually later) -# This ensures the script won't fail silently on systemctl commands -sed -e 's/\bsystemctl /echo "[SKIPPED] systemctl " # /g' \ - -e 's/log_fatal "Unbound failed to start/return 0 # [SKIPPED] /g' \ - /opt/openvpn-install.sh >/tmp/openvpn-install.sh -chmod +x /tmp/openvpn-install.sh - echo "Running OpenVPN install script..." # Run in subshell because the script calls 'exit 0' after generating client config # Capture output to validate logging format, while still displaying it # Use || true to prevent set -e from exiting on failure, then check exit code INSTALL_OUTPUT="/tmp/install-output.log" -(bash /tmp/openvpn-install.sh) 2>&1 | tee "$INSTALL_OUTPUT" +(bash /opt/openvpn-install.sh) 2>&1 | tee "$INSTALL_OUTPUT" INSTALL_EXIT_CODE=${PIPESTATUS[0]} echo "=== Installation complete (exit code: $INSTALL_EXIT_CODE) ===" @@ -86,6 +77,11 @@ fi echo "All required files present" +# Copy client config to shared volume for the client container +cp /root/testclient.ovpn /shared/client.ovpn +sed -i 's/^remote .*/remote openvpn-server 1194/' /shared/client.ovpn +echo "Client config copied to /shared/client.ovpn" + # ===================================================== # Verify systemd service file configuration # ===================================================== @@ -146,12 +142,6 @@ echo "" echo "Server config:" cat /etc/openvpn/server/server.conf -# Copy client config to shared volume -cp /root/testclient.ovpn /shared/client.ovpn -# Modify remote address to use container hostname -sed -i 's/^remote .*/remote openvpn-server 1194/' /shared/client.ovpn -echo "Client config copied to /shared/client.ovpn" - # ===================================================== # Test certificate renewal functionality # ===================================================== @@ -165,7 +155,7 @@ echo "Original client certificate serial: $ORIG_CERT_SERIAL" # Test client certificate renewal using the script echo "Testing client certificate renewal..." RENEW_OUTPUT="/tmp/renew-client-output.log" -(MENU_OPTION=3 RENEW_OPTION=1 CLIENTNUMBER=1 CLIENT_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RENEW_OUTPUT" || true +(MENU_OPTION=3 RENEW_OPTION=1 CLIENTNUMBER=1 CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RENEW_OUTPUT" || true # Verify renewal succeeded if grep -q "Certificate for client testclient renewed" "$RENEW_OUTPUT"; then @@ -245,7 +235,7 @@ echo "Original server certificate serial: $ORIG_SERVER_SERIAL" # Test server certificate renewal echo "Testing server certificate renewal..." RENEW_SERVER_OUTPUT="/tmp/renew-server-output.log" -(MENU_OPTION=3 RENEW_OPTION=2 CONTINUE=y SERVER_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RENEW_SERVER_OUTPUT" || true +(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 # Verify renewal succeeded if grep -q "Server certificate renewed successfully" "$RENEW_SERVER_OUTPUT"; then @@ -304,26 +294,14 @@ echo "=== All Certificate Renewal Tests PASSED ===" echo "" # ===================================================== -# Start and verify Unbound DNS resolver +# Verify Unbound DNS resolver (started by systemd via install script) # ===================================================== -echo "=== Starting Unbound DNS Resolver ===" +echo "=== Verifying Unbound DNS Resolver ===" -# Start Unbound manually (systemctl commands are no-ops in container) if [ -f /etc/unbound/unbound.conf ]; then - echo "Starting Unbound DNS resolver..." - - # Create root key for DNSSEC if it doesn't exist - # Normally, unbound.service's ExecStartPre copies /usr/share/dns/root.key to /var/lib/unbound/root.key - # In Docker, policy-rc.d blocks service starts during apt install, so this never happens - if [ ! -f /var/lib/unbound/root.key ] && [ -f /usr/share/dns/root.key ]; then - mkdir -p /var/lib/unbound - cp /usr/share/dns/root.key /var/lib/unbound/root.key - chown -R unbound:unbound /var/lib/unbound 2>/dev/null || true - fi - - unbound - # Poll up to 10 seconds for Unbound to start - for _ in $(seq 1 10); do + # Verify Unbound is running (started by systemctl in install script) + echo "Checking Unbound service status..." + for _ in $(seq 1 30); do if pgrep -x unbound >/dev/null; then echo "PASS: Unbound is running" break @@ -331,9 +309,9 @@ if [ -f /etc/unbound/unbound.conf ]; then sleep 1 done if ! pgrep -x unbound >/dev/null; then - echo "FAIL: Unbound failed to start" - # Show debug info - unbound-checkconf /etc/unbound/unbound.conf 2>&1 || true + echo "FAIL: Unbound is not running" + systemctl status unbound 2>&1 || true + journalctl -u unbound --no-pager -n 50 2>&1 || true exit 1 fi else @@ -377,50 +355,46 @@ fi echo "=== Unbound Installation Verified ===" echo "" -# Start OpenVPN server manually (systemd doesn't work in containers) -echo "Starting OpenVPN server..." +# Verify OpenVPN server (started by systemd via install script) +echo "Verifying OpenVPN server..." -# Apply iptables rules manually (systemd not available in containers) -echo "Applying iptables rules..." -bash /etc/iptables/add-openvpn-rules.sh - -# Verify iptables NAT rules exist +# Verify iptables NAT rules exist (applied by iptables-openvpn service) echo "Verifying iptables NAT rules..." -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" -else +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 -# Enable IP forwarding (may already be set via docker-compose sysctls) +# Verify IP forwarding is enabled if [ "$(cat /proc/sys/net/ipv4/ip_forward)" != "1" ]; then - echo 1 >/proc/sys/net/ipv4/ip_forward || { - echo "ERROR: Failed to enable IP forwarding" - exit 1 - } + echo "ERROR: IP forwarding is not enabled" + exit 1 fi -# Start OpenVPN in background (run from /etc/openvpn/server so relative paths work) -cd /etc/openvpn/server -openvpn --config /etc/openvpn/server/server.conf --log /var/log/openvpn-server.log & -OPENVPN_PID=$! - -# Wait for OpenVPN to start +# Wait for OpenVPN to start (started by systemctl in install script) echo "Waiting for OpenVPN server to start..." for _ in $(seq 1 30); do - if pgrep -f "openvpn --config" >/dev/null; then - echo "OpenVPN server started (PID: $OPENVPN_PID)" + if pgrep -f "openvpn.*server.conf" >/dev/null; then + echo "PASS: OpenVPN server is running" break fi sleep 1 done -if ! pgrep -f "openvpn --config" >/dev/null; then - echo "FAIL: OpenVPN server failed to start" - cat /var/log/openvpn-server.log || true +if ! pgrep -f "openvpn.*server.conf" >/dev/null; then + echo "FAIL: OpenVPN server is not running" + systemctl status openvpn-server@server 2>&1 || true + journalctl -u openvpn-server@server --no-pager -n 50 2>&1 || true exit 1 fi @@ -453,7 +427,7 @@ echo "=== Testing Certificate Revocation ===" REVOKE_CLIENT="revoketest" echo "Creating client '$REVOKE_CLIENT' for revocation testing..." REVOKE_CREATE_OUTPUT="/tmp/revoke-create-output.log" -(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$REVOKE_CREATE_OUTPUT" || true +(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$REVOKE_CREATE_OUTPUT" || true if [ -f "/root/$REVOKE_CLIENT.ovpn" ]; then echo "PASS: Client '$REVOKE_CLIENT' created successfully" @@ -517,7 +491,7 @@ if [ -z "$REVOKE_CLIENT_NUM" ]; then exit 1 fi echo "Revoke client number: $REVOKE_CLIENT_NUM" -(MENU_OPTION=2 CLIENTNUMBER=$REVOKE_CLIENT_NUM bash /tmp/openvpn-install.sh) 2>&1 | tee "$REVOKE_OUTPUT" || true +(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 echo "PASS: Certificate for '$REVOKE_CLIENT' revoked successfully" @@ -566,7 +540,7 @@ echo "=== Testing Reuse of Revoked Client Name ===" # Create a new certificate with the same name as the revoked one echo "Creating new client with same name '$REVOKE_CLIENT'..." RECREATE_OUTPUT="/tmp/recreate-output.log" -(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /tmp/openvpn-install.sh) 2>&1 | tee "$RECREATE_OUTPUT" || true +(MENU_OPTION=1 CLIENT=$REVOKE_CLIENT PASS=1 CLIENT_CERT_DURATION_DAYS=3650 bash /opt/openvpn-install.sh) 2>&1 | tee "$RECREATE_OUTPUT" || true if [ -f "/root/$REVOKE_CLIENT.ovpn" ]; then echo "PASS: New client '$REVOKE_CLIENT' created successfully (reusing revoked name)" @@ -625,6 +599,7 @@ echo "=== Reuse of Revoked Client Name Tests PASSED ===" echo "" echo "=== All Revocation Tests PASSED ===" -# Keep server running for any remaining client tests -echo "Server waiting for client to complete all tests..." -wait $OPENVPN_PID +# Server tests complete - systemd keeps the container running via /sbin/init +# OpenVPN service (openvpn-server@server) continues independently +echo "Server tests complete. Container will remain running via systemd." +echo "OpenVPN is managed by: systemctl status openvpn-server@server" From e7aa52b51f971af775d3f0bd156ceb292394642a Mon Sep 17 00:00:00 2001 From: Stanislas Date: Sat, 13 Dec 2025 10:55:36 +0100 Subject: [PATCH 136/141] fix(arch): detect pending kernel upgrades before installation (#1372) On Arch Linux, the script uses `pacman -Syu` which performs a full system upgrade. If a user's system is out of date and has pending kernel updates: 1. Script runs `pacman -Syu` to install OpenVPN 2. Kernel gets upgraded along with other packages 3. The TUN module for the **new** kernel isn't loaded (old kernel still running) 4. OpenVPN fails to start because TUN is unavailable 5. User has to reboot anyway, but now they're confused about why it broke So we check preventively now, and ask them to upgrade & reboot before running the script image --- openvpn-install.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/openvpn-install.sh b/openvpn-install.sh index 16ea068..0321cee 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -233,6 +233,57 @@ function checkOS() { fi } +function checkArchPendingKernelUpgrade() { + if [[ $OS != "arch" ]]; then + return 0 + fi + + # Check if running kernel's modules are available + # (detects if kernel was upgraded but system not rebooted) + # Skip this check in containers - they share host kernel but have their own /lib/modules + if [[ -f /.dockerenv ]] || grep -qE '(docker|lxc|containerd)' /proc/1/cgroup 2>/dev/null; then + log_info "Running in container, skipping kernel modules check" + else + local running_kernel + running_kernel=$(uname -r) + if [[ ! -d "/lib/modules/${running_kernel}" ]]; then + log_error "Kernel modules for running kernel ($running_kernel) not found!" + log_info "This usually means the kernel was upgraded but the system wasn't rebooted." + log_fatal "Please reboot your system and run this script again." + fi + fi + + log_info "Checking for pending kernel upgrades on Arch Linux..." + + # Sync package database to check for updates + if ! pacman -Sy &>/dev/null; then + log_warn "Failed to sync package database, skipping kernel upgrade check" + return 0 + fi + + # Check for pending linux kernel upgrades + local pending_kernels + pending_kernels=$(pacman -Qu 2>/dev/null | grep -E '^linux' || true) + + if [[ -n "$pending_kernels" ]]; then + log_warn "Linux kernel upgrade(s) pending:" + echo "$pending_kernels" | while read -r line; do + log_info " $line" + done + echo "" + log_info "This script uses 'pacman -Syu' which will upgrade your kernel." + log_info "After a kernel upgrade, the TUN module won't be available until you reboot." + echo "" + log_info "Please upgrade your system and reboot first:" + log_info " sudo pacman -Syu" + log_info " sudo reboot" + echo "" + log_fatal "Aborting. Run this script again after upgrading and rebooting." + fi + + log_success "No pending kernel upgrades" +} + function initialCheck() { log_debug "Checking root privileges..." if ! isRoot; then @@ -249,6 +300,7 @@ function initialCheck() { log_debug "Detecting operating system..." checkOS log_info "Detected OS: $OS (${PRETTY_NAME:-unknown})" + checkArchPendingKernelUpgrade } # Check if OpenVPN version is at least the specified version From a6c88ddfda2b0d4ad764c75ffb85d054445dd884 Mon Sep 17 00:00:00 2001 From: wid-get <129321765+wid-get@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:17:51 +0300 Subject: [PATCH 137/141] fix: use After=network-online.target for iptables service (#1140) fixes not executing add-openvpn-rules.sh after OS reboot. systemctl shows service as _running_ but no iptables rules added to list. Also this fixes issue https://github.com/angristan/openvpn-install/issues/1127 --- openvpn-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvpn-install.sh b/openvpn-install.sh index 0321cee..8b25730 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -1452,7 +1452,7 @@ ip6tables -D INPUT -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >>/etc/iptables # Handle the rules via a systemd script echo "[Unit] Description=iptables rules for OpenVPN -Before=network-online.target +After=network-online.target Wants=network-online.target [Service] From 2c53bc0f83588512bb1ca4110fb71e9fbfc0d0ad Mon Sep 17 00:00:00 2001 From: Stanislas Date: Sat, 13 Dec 2025 13:31:54 +0100 Subject: [PATCH 138/141] feat: add run_cmd_fatal, fix Fedora, improve CI (#1369) ## Summary This PR contains three related improvements: ### 1. Add `run_cmd_fatal` for critical operations - New helper function that wraps `run_cmd` and exits on failure - Converts critical operations (package installs, PKI setup, certificate generation) to fail fast - Non-critical operations (systemctl, cleanup) still use `run_cmd` - Password-protected client certs run directly to preserve interactive prompt ### 2. Fix Fedora installation - Skip Copr repository setup since Fedora already ships OpenVPN 2.6.x - Simplifies installation and removes external repository dependency ### 3. Improve CI test reliability - Fail fast when `openvpn-test.service` fails during startup - Add `journalctl` output to error diagnostics - Display service status in wait loop - Increase VPN gateway ping count from 3 to 10 for stability --- .github/workflows/docker-test.yml | 27 +++++-- openvpn-install.sh | 119 ++++++++++++++++-------------- test/client-entrypoint.sh | 2 +- 3 files changed, 85 insertions(+), 63 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index 2afd63e..d2113bd 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -116,9 +116,21 @@ jobs: run: | echo "Waiting for OpenVPN server to install and client config to be ready..." for i in {1..90}; do - # Check BOTH conditions: - # 1. OpenVPN server process is running - # 2. Client config file exists in shared volume + # Get service status (properly handle non-zero exit codes) + # systemctl is-active returns exit code 3 for "inactive"/"failed", so capture output without checking exit code + SERVICE_STATUS="$(docker exec openvpn-server systemctl is-active openvpn-test.service 2>/dev/null)" || true + [ -z "$SERVICE_STATUS" ] && SERVICE_STATUS="unknown" + + # Fail fast if service failed + if [ "$SERVICE_STATUS" = "failed" ]; then + echo "ERROR: openvpn-test.service failed during installation" + docker exec openvpn-server systemctl status openvpn-test.service 2>&1 || true + docker exec openvpn-server journalctl -u openvpn-test.service --no-pager -n 100 2>&1 || true + exit 1 + fi + + # Check if OpenVPN server is running and client config exists + # The service will be "activating" while waiting for client tests - that's expected OPENVPN_RUNNING=false CONFIG_EXISTS=false @@ -135,23 +147,26 @@ jobs: break fi - echo "Waiting... ($i/90) - OpenVPN running: $OPENVPN_RUNNING, Config exists: $CONFIG_EXISTS" + echo "Waiting... ($i/90) - Service: $SERVICE_STATUS, OpenVPN running: $OPENVPN_RUNNING, Config exists: $CONFIG_EXISTS" sleep 5 done - # Final check + # Final verification if ! docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then echo "ERROR: OpenVPN server failed to start" docker exec openvpn-server systemctl status openvpn-server@server 2>&1 || true + docker exec openvpn-server journalctl -u openvpn-test.service --no-pager -n 100 2>&1 || true exit 1 fi if ! docker exec openvpn-server test -f /shared/client.ovpn 2>/dev/null; then echo "ERROR: Client config not generated" - docker exec openvpn-server systemctl status openvpn-test.service 2>&1 || true + docker exec openvpn-server journalctl -u openvpn-test.service --no-pager -n 100 2>&1 || true exit 1 fi + echo "Server ready for client connection!" + - name: Verify client config was generated run: | docker run --rm -v shared-config:/shared alpine \ diff --git a/openvpn-install.sh b/openvpn-install.sh index 8b25730..d68fdd8 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -147,6 +147,16 @@ run_cmd() { return $ret } +# Run a command that must succeed, exit on failure +# Usage: run_cmd_fatal "description" command [args...] +run_cmd_fatal() { + local desc="$1" + shift + if ! run_cmd "$desc" "$@"; then + log_fatal "$desc failed" + fi +} + function isRoot() { if [ "$EUID" -ne 0 ]; then return 1 @@ -367,7 +377,7 @@ function installOpenVPNRepo() { if [[ $OS =~ (debian|ubuntu) ]]; then run_cmd "Update package lists" apt-get update - run_cmd "Installing prerequisites" apt-get install -y ca-certificates curl + run_cmd_fatal "Installing prerequisites" apt-get install -y ca-certificates curl # Create keyrings directory run_cmd "Creating keyrings directory" mkdir -p /etc/apt/keyrings @@ -401,24 +411,20 @@ function installOpenVPNRepo() { fi if ! command -v dnf &>/dev/null; then - run_cmd "Installing EPEL repository" yum install -y "$EPEL_PACKAGE" || log_fatal "Failed to install EPEL repository" - run_cmd "Installing yum-plugin-copr" yum install -y yum-plugin-copr || log_fatal "Failed to install yum-plugin-copr" - run_cmd "Enabling OpenVPN Copr repo" yum copr enable -y @OpenVPN/openvpn-release-2.6 || log_fatal "Failed to enable OpenVPN Copr repo" + run_cmd_fatal "Installing EPEL repository" yum install -y "$EPEL_PACKAGE" + run_cmd_fatal "Installing yum-plugin-copr" yum install -y yum-plugin-copr + run_cmd_fatal "Enabling OpenVPN Copr repo" yum copr enable -y @OpenVPN/openvpn-release-2.6 else - run_cmd "Installing EPEL repository" dnf install -y "$EPEL_PACKAGE" || log_fatal "Failed to install EPEL repository" - run_cmd "Installing dnf-plugins-core" dnf install -y dnf-plugins-core || log_fatal "Failed to install dnf-plugins-core" - run_cmd "Enabling OpenVPN Copr repo" dnf copr enable -y @OpenVPN/openvpn-release-2.6 || log_fatal "Failed to enable OpenVPN Copr repo" + run_cmd_fatal "Installing EPEL repository" dnf install -y "$EPEL_PACKAGE" + run_cmd_fatal "Installing dnf-plugins-core" dnf install -y dnf-plugins-core + run_cmd_fatal "Enabling OpenVPN Copr repo" dnf copr enable -y @OpenVPN/openvpn-release-2.6 fi log_info "OpenVPN Copr repository configured" elif [[ $OS == "fedora" ]]; then - # Fedora already has recent OpenVPN, but we can use Copr for latest 2.6 - log_info "Configuring OpenVPN Copr repository for Fedora..." - run_cmd "Installing dnf-plugins-core" dnf install -y dnf-plugins-core - run_cmd "Enabling OpenVPN Copr repo" dnf copr enable -y @OpenVPN/openvpn-release-2.6 - - log_info "OpenVPN Copr repository configured" + # Fedora already ships with recent OpenVPN 2.6.x, no Copr needed + log_info "Fedora already has recent OpenVPN packages, using distribution version" else log_info "No official OpenVPN repository available for this OS, using distribution packages" @@ -431,15 +437,15 @@ function installUnbound() { # Install Unbound if not present if [[ ! -e /etc/unbound/unbound.conf ]]; then if [[ $OS =~ (debian|ubuntu) ]]; then - run_cmd "Installing Unbound" apt-get install -y unbound + run_cmd_fatal "Installing Unbound" apt-get install -y unbound elif [[ $OS =~ (centos|oracle) ]]; then - run_cmd "Installing Unbound" yum install -y unbound + run_cmd_fatal "Installing Unbound" yum install -y unbound elif [[ $OS =~ (fedora|amzn2023) ]]; then - run_cmd "Installing Unbound" dnf install -y unbound + run_cmd_fatal "Installing Unbound" dnf install -y unbound elif [[ $OS == "opensuse" ]]; then - run_cmd "Installing Unbound" zypper install -y unbound + run_cmd_fatal "Installing Unbound" zypper install -y unbound elif [[ $OS == "arch" ]]; then - run_cmd "Installing Unbound" pacman -Syu --noconfirm unbound + run_cmd_fatal "Installing Unbound" pacman -Syu --noconfirm unbound fi fi @@ -1038,19 +1044,19 @@ function installOpenVPN() { log_info "Installing OpenVPN and dependencies..." if [[ $OS =~ (debian|ubuntu) ]]; then - run_cmd "Installing OpenVPN" apt-get install -y openvpn iptables openssl curl ca-certificates tar dnsutils + run_cmd_fatal "Installing OpenVPN" apt-get install -y openvpn iptables openssl curl ca-certificates tar dnsutils elif [[ $OS == 'centos' ]]; then - run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar bind-utils 'policycoreutils-python*' + run_cmd_fatal "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar bind-utils 'policycoreutils-python*' elif [[ $OS == 'oracle' ]]; then - run_cmd "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar bind-utils policycoreutils-python-utils + run_cmd_fatal "Installing OpenVPN" yum install -y openvpn iptables openssl ca-certificates curl tar bind-utils policycoreutils-python-utils elif [[ $OS == 'amzn2023' ]]; then - run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl tar bind-utils + run_cmd_fatal "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl tar bind-utils elif [[ $OS == 'fedora' ]]; then - run_cmd "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl tar bind-utils policycoreutils-python-utils + run_cmd_fatal "Installing OpenVPN" dnf install -y openvpn iptables openssl ca-certificates curl tar bind-utils policycoreutils-python-utils elif [[ $OS == 'opensuse' ]]; then - run_cmd "Installing OpenVPN" zypper install -y openvpn iptables openssl ca-certificates curl tar bind-utils + run_cmd_fatal "Installing OpenVPN" zypper install -y openvpn iptables openssl ca-certificates curl tar bind-utils elif [[ $OS == 'arch' ]]; then - run_cmd "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl ca-certificates curl tar bind + run_cmd_fatal "Installing OpenVPN" pacman --needed --noconfirm -Syu openvpn iptables openssl ca-certificates curl tar bind fi # Verify ChaCha20-Poly1305 compatibility if selected @@ -1076,7 +1082,7 @@ function installOpenVPN() { fi # Create the server directory (OpenVPN 2.4+ directory structure) - run_cmd "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 @@ -1114,7 +1120,7 @@ function installOpenVPN() { # Install the latest version of easy-rsa from source, if not already installed. if [[ ! -d /etc/openvpn/server/easy-rsa/ ]]; then - run_cmd "Downloading Easy-RSA v${EASYRSA_VERSION}" curl -fL --retry 5 -o ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" + run_cmd_fatal "Downloading Easy-RSA v${EASYRSA_VERSION}" curl -fL --retry 5 -o ~/easy-rsa.tgz "https://github.com/OpenVPN/easy-rsa/releases/download/v${EASYRSA_VERSION}/EasyRSA-${EASYRSA_VERSION}.tgz" log_info "Verifying Easy-RSA checksum..." CHECKSUM_OUTPUT=$(echo "${EASYRSA_SHA256} $HOME/easy-rsa.tgz" | sha256sum -c 2>&1) || { _log_to_file "[CHECKSUM] $CHECKSUM_OUTPUT" @@ -1122,8 +1128,8 @@ function installOpenVPN() { log_fatal "SHA256 checksum verification failed for easy-rsa download!" } _log_to_file "[CHECKSUM] $CHECKSUM_OUTPUT" - run_cmd "Creating Easy-RSA directory" mkdir -p /etc/openvpn/server/easy-rsa - run_cmd "Extracting Easy-RSA" tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/server/easy-rsa + run_cmd_fatal "Creating Easy-RSA directory" mkdir -p /etc/openvpn/server/easy-rsa + run_cmd_fatal "Extracting Easy-RSA" tar xzf ~/easy-rsa.tgz --strip-components=1 --no-same-owner --directory /etc/openvpn/server/easy-rsa run_cmd "Cleaning up archive" rm -f ~/easy-rsa.tgz cd /etc/openvpn/server/easy-rsa/ || return @@ -1145,31 +1151,31 @@ function installOpenVPN() { # Create the PKI, set up the CA, the DH params and the server certificate log_info "Initializing PKI..." - run_cmd "Initializing PKI" ./easyrsa init-pki + run_cmd_fatal "Initializing PKI" ./easyrsa init-pki export EASYRSA_CA_EXPIRE=$DEFAULT_CERT_VALIDITY_DURATION_DAYS log_info "Building CA..." - run_cmd "Building CA" ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass + run_cmd_fatal "Building CA" ./easyrsa --batch --req-cn="$SERVER_CN" build-ca nopass if [[ $DH_TYPE == "2" ]]; then # ECDH keys are generated on-the-fly so we don't need to generate them beforehand - run_cmd "Generating DH parameters (this may take a while)" openssl dhparam -out dh.pem "$DH_KEY_SIZE" + run_cmd_fatal "Generating DH parameters (this may take a while)" openssl dhparam -out dh.pem "$DH_KEY_SIZE" fi export EASYRSA_CERT_EXPIRE=${SERVER_CERT_DURATION_DAYS:-$DEFAULT_CERT_VALIDITY_DURATION_DAYS} log_info "Building server certificate..." - run_cmd "Building server certificate" ./easyrsa --batch build-server-full "$SERVER_NAME" nopass + run_cmd_fatal "Building server certificate" ./easyrsa --batch build-server-full "$SERVER_NAME" nopass export EASYRSA_CRL_DAYS=$DEFAULT_CRL_VALIDITY_DURATION_DAYS - run_cmd "Generating CRL" ./easyrsa gen-crl + run_cmd_fatal "Generating CRL" ./easyrsa gen-crl log_info "Generating TLS key..." case $TLS_SIG in 1) # Generate tls-crypt key - run_cmd "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 ;; 2) # Generate tls-auth key - run_cmd "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 else @@ -1181,9 +1187,9 @@ function installOpenVPN() { # Move all the generated files log_info "Copying certificates..." - run_cmd "Copying certificates to /etc/openvpn/server" cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server + run_cmd_fatal "Copying certificates to /etc/openvpn/server" cp pki/ca.crt pki/private/ca.key "pki/issued/$SERVER_NAME.crt" "pki/private/$SERVER_NAME.key" /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server if [[ $DH_TYPE == "2" ]]; then - run_cmd "Copying DH parameters" cp dh.pem /etc/openvpn/server + run_cmd_fatal "Copying DH parameters" cp dh.pem /etc/openvpn/server fi # Make cert revocation list readable for non-root @@ -1339,9 +1345,9 @@ status /var/log/openvpn/status.log verb 3" >>/etc/openvpn/server/server.conf # Create client-config-dir dir - run_cmd "Creating client config directory" mkdir -p /etc/openvpn/server/ccd + run_cmd_fatal "Creating client config directory" mkdir -p /etc/openvpn/server/ccd # Create log dir - run_cmd "Creating log directory" mkdir -p /var/log/openvpn + run_cmd_fatal "Creating log directory" mkdir -p /var/log/openvpn # On distros that use a dedicated OpenVPN user (not "nobody"), e.g., Fedora, RHEL, Arch, # set ownership so OpenVPN can read config/certs and write to log directory @@ -1353,7 +1359,7 @@ verb 3" >>/etc/openvpn/server/server.conf # Enable routing log_info "Enabling IP forwarding..." - run_cmd "Creating sysctl.d directory" mkdir -p /etc/sysctl.d + run_cmd_fatal "Creating sysctl.d directory" mkdir -p /etc/sysctl.d echo 'net.ipv4.ip_forward=1' >/etc/sysctl.d/99-openvpn.conf if [[ $IPV6_SUPPORT == 'y' ]]; then echo 'net.ipv6.conf.all.forwarding=1' >>/etc/sysctl.d/99-openvpn.conf @@ -1391,7 +1397,7 @@ verb 3" >>/etc/openvpn/server/server.conf fi # Don't modify package-provided service, copy to /etc/systemd/system/ - run_cmd "Copying OpenVPN service file" cp "$SERVICE_SOURCE" /etc/systemd/system/openvpn-server@.service + run_cmd_fatal "Copying OpenVPN service file" cp "$SERVICE_SOURCE" /etc/systemd/system/openvpn-server@.service # Workaround to fix OpenVPN service on OpenVZ run_cmd "Patching service file (LimitNPROC)" sed -i 's|LimitNPROC|#LimitNPROC|' /etc/systemd/system/openvpn-server@.service @@ -1412,7 +1418,7 @@ verb 3" >>/etc/openvpn/server/server.conf # Add iptables rules in two scripts log_info "Configuring firewall rules..." - run_cmd "Creating iptables directory" mkdir -p /etc/iptables + run_cmd_fatal "Creating iptables directory" mkdir -p /etc/iptables # Script to add rules echo "#!/bin/sh @@ -1452,7 +1458,7 @@ ip6tables -D INPUT -i $NIC -p $PROTOCOL --dport $PORT -j ACCEPT" >>/etc/iptables # Handle the rules via a systemd script echo "[Unit] Description=iptables rules for OpenVPN -After=network-online.target +Before=network-online.target Wants=network-online.target [Service] @@ -1533,9 +1539,9 @@ function getHomeDir() { # Helper function to regenerate the CRL after certificate changes function regenerateCRL() { export EASYRSA_CRL_DAYS=$DEFAULT_CRL_VALIDITY_DURATION_DAYS - run_cmd "Regenerating CRL" ./easyrsa gen-crl + run_cmd_fatal "Regenerating CRL" ./easyrsa gen-crl run_cmd "Removing old CRL" rm -f /etc/openvpn/server/crl.pem - run_cmd "Copying new CRL" cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem + run_cmd_fatal "Copying new CRL" cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem run_cmd "Setting CRL permissions" chmod 644 /etc/openvpn/server/crl.pem } @@ -1661,11 +1667,14 @@ function newClient() { export EASYRSA_CERT_EXPIRE=$CLIENT_CERT_DURATION_DAYS case $PASS in 1) - run_cmd "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass + run_cmd_fatal "Building client certificate" ./easyrsa --batch build-client-full "$CLIENT" nopass ;; 2) log_warn "You will be asked for the client password below" - ./easyrsa --batch build-client-full "$CLIENT" + # Run directly (not via run_cmd) so password prompt is visible to user + if ! ./easyrsa --batch build-client-full "$CLIENT"; then + log_fatal "Building client certificate failed" + fi ;; esac log_success "Client $CLIENT added and is valid for $CLIENT_CERT_DURATION_DAYS days." @@ -1689,7 +1698,7 @@ function revokeClient() { cd /etc/openvpn/server/easy-rsa/ || return log_info "Revoking certificate for $CLIENT..." - run_cmd "Revoking certificate" ./easyrsa --batch revoke-issued "$CLIENT" + run_cmd_fatal "Revoking certificate" ./easyrsa --batch revoke-issued "$CLIENT" regenerateCRL run_cmd "Removing client config from /home" find /home/ -maxdepth 2 -name "$CLIENT.ovpn" -delete run_cmd "Removing client config from /root" rm -f "/root/$CLIENT.ovpn" @@ -1725,10 +1734,10 @@ function renewClient() { # Renew the certificate (keeps the same private key) export EASYRSA_CERT_EXPIRE=$client_cert_duration_days - run_cmd "Renewing certificate" ./easyrsa --batch renew "$CLIENT" + run_cmd_fatal "Renewing certificate" ./easyrsa --batch renew "$CLIENT" # Revoke the old certificate - run_cmd "Revoking old certificate" ./easyrsa --batch revoke-renewed "$CLIENT" + run_cmd_fatal "Revoking old certificate" ./easyrsa --batch revoke-renewed "$CLIENT" # Regenerate the CRL regenerateCRL @@ -1783,16 +1792,16 @@ function renewServer() { # Renew the certificate (keeps the same private key) export EASYRSA_CERT_EXPIRE=$server_cert_duration_days - run_cmd "Renewing certificate" ./easyrsa --batch renew "$server_name" + run_cmd_fatal "Renewing certificate" ./easyrsa --batch renew "$server_name" # Revoke the old certificate - run_cmd "Revoking old certificate" ./easyrsa --batch revoke-renewed "$server_name" + run_cmd_fatal "Revoking old certificate" ./easyrsa --batch revoke-renewed "$server_name" # Regenerate the CRL regenerateCRL # Copy the new certificate to /etc/openvpn/server/ - run_cmd "Copying new certificate" cp "/etc/openvpn/server/easy-rsa/pki/issued/$server_name.crt" /etc/openvpn/server/ + run_cmd_fatal "Copying new certificate" cp "/etc/openvpn/server/easy-rsa/pki/issued/$server_name.crt" /etc/openvpn/server/ # Restart OpenVPN log_info "Restarting OpenVPN service..." @@ -1970,8 +1979,6 @@ function removeOpenVPN() { run_cmd "Removing OpenVPN" dnf remove -y openvpn elif [[ $OS == 'fedora' ]]; then run_cmd "Removing OpenVPN" dnf remove -y openvpn - # Disable Copr repo - run_cmd "Disabling OpenVPN Copr repo" dnf copr disable -y @OpenVPN/openvpn-release-2.6 2>/dev/null || true elif [[ $OS == 'opensuse' ]]; then run_cmd "Removing OpenVPN" zypper remove -y openvpn fi diff --git a/test/client-entrypoint.sh b/test/client-entrypoint.sh index 0e85117..648c285 100755 --- a/test/client-entrypoint.sh +++ b/test/client-entrypoint.sh @@ -74,7 +74,7 @@ fi # Test 2: Ping VPN gateway echo "Test 2: Pinging VPN gateway (10.8.0.1)..." -if ping -c 3 10.8.0.1; then +if ping -c 10 10.8.0.1; then echo "PASS: Can ping VPN gateway" else echo "FAIL: Cannot ping VPN gateway" From 3561d13389a03a6917574f69816b850c837ca85e Mon Sep 17 00:00:00 2001 From: Stanislas Date: Sat, 13 Dec 2025 14:32:38 +0100 Subject: [PATCH 139/141] feat: add tls-crypt-v2 support with per-client keys (#1377) ## Summary - Add support for OpenVPN's `tls-crypt-v2` feature (per-client TLS keys) - Set `tls-crypt-v2` as the new recommended default - Add CI tests for all 3 TLS key types Closes #983 Closes #758 Closes https://github.com/angristan/openvpn-install/pull/1257 ## What is tls-crypt-v2? Unlike `tls-crypt` (shared key), `tls-crypt-v2` generates unique keys per client: - **Better security**: Compromised client keys don't affect other clients - **Easier management**: Individual client key revocation without regenerating server key - **Scalability**: Better suited for large deployments Requires OpenVPN 2.5+ (released 2020). ## Menu options ``` 1) tls-crypt-v2 (recommended): Encrypts control channel, unique key per client 2) tls-crypt: Encrypts control channel, shared key for all clients 3) tls-auth: Authenticates control channel, no encryption ``` --- .github/workflows/docker-test.yml | 37 +++++++++++++++++++++-- README.md | 14 +++++++-- openvpn-install.sh | 49 +++++++++++++++++++++++-------- test/server-entrypoint.sh | 26 ++++++++++++++-- 4 files changed, 107 insertions(+), 19 deletions(-) diff --git a/.github/workflows/docker-test.yml b/.github/workflows/docker-test.yml index d2113bd..f1e83ab 100644 --- a/.github/workflows/docker-test.yml +++ b/.github/workflows/docker-test.yml @@ -68,6 +68,27 @@ jobs: image: oraclelinux:10 - name: amazonlinux-2023 image: amazonlinux:2023 + # Default TLS settings (tls-crypt-v2) + tls: + - name: tls-crypt-v2 + sig: "1" + key_file: tls-crypt-v2.key + # Additional TLS types tested on Ubuntu 24.04 only + include: + - os: + name: ubuntu-24.04-tls-crypt + image: ubuntu:24.04 + tls: + name: tls-crypt + sig: "2" + key_file: tls-crypt.key + - os: + name: ubuntu-24.04-tls-auth + image: ubuntu:24.04 + tls: + name: tls-auth + sig: "3" + key_file: tls-auth.key name: ${{ matrix.os.name }} steps: @@ -110,6 +131,8 @@ jobs: --tmpfs /run \ --tmpfs /run/lock \ --stop-signal SIGRTMIN+3 \ + -e TLS_SIG=${{ matrix.tls.sig }} \ + -e TLS_KEY_FILE=${{ matrix.tls.key_file }} \ openvpn-server - name: Wait for server installation and startup @@ -151,8 +174,18 @@ jobs: sleep 5 done - # Final verification - if ! docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then + # Final verification with retry (handles race condition during cert renewal restart) + OPENVPN_STARTED=false + for retry in {1..5}; do + if docker exec openvpn-server pgrep -f "openvpn.*server.conf" > /dev/null 2>&1; then + OPENVPN_STARTED=true + break + fi + echo "Waiting for OpenVPN process... (retry $retry/5)" + sleep 2 + done + + if [ "$OPENVPN_STARTED" = false ]; then echo "ERROR: OpenVPN server failed to start" docker exec openvpn-server systemctl status openvpn-server@server 2>&1 || true docker exec openvpn-server journalctl -u openvpn-test.service --no-pager -n 100 2>&1 || true diff --git a/README.md b/README.md index 3ade0dc..fd280c1 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,7 @@ The script provides the following choices: It defaults to `SHA256`. -### `tls-auth` and `tls-crypt` +### `tls-auth`, `tls-crypt`, and `tls-crypt-v2` From the OpenVPN wiki, about `tls-auth`: @@ -381,7 +381,17 @@ So both provide an additional layer of security and mitigate DoS attacks. They a `tls-crypt` is an OpenVPN 2.4 feature that provides encryption in addition to authentication (unlike `tls-auth`). It is more privacy-friendly. -The script supports both and uses `tls-crypt` by default. +`tls-crypt-v2` is an OpenVPN 2.5 feature that builds on `tls-crypt` by using **per-client keys** instead of a shared key. Each client receives a unique key derived from a server key. This provides: + +- **Better security**: If a client key is compromised, other clients are not affected +- **Easier key management**: Client keys can be revoked individually without regenerating the server key +- **Scalability**: Better suited for large deployments with many clients + +The script supports all three options: + +- `tls-crypt-v2` (default): Per-client keys for better security +- `tls-crypt`: Shared key for all clients, compatible with OpenVPN 2.4+ +- `tls-auth`: HMAC authentication only (no encryption), compatible with older clients ### Certificate type verification (`remote-cert-tls`) diff --git a/openvpn-install.sh b/openvpn-install.sh index d68fdd8..bda5b48 100755 --- a/openvpn-install.sh +++ b/openvpn-install.sh @@ -749,7 +749,7 @@ function installQuestions() { DH_TYPE="1" # ECDH DH_CURVE="prime256v1" HMAC_ALG="SHA256" - TLS_SIG="1" # tls-crypt + TLS_SIG="1" # tls-crypt-v2 else log_menu "" log_prompt "Choose which cipher you want to use for the data channel:" @@ -956,12 +956,12 @@ function installQuestions() { ;; esac log_menu "" - log_prompt "You can add an additional layer of security to the control channel with tls-auth and tls-crypt" - log_prompt "tls-auth authenticates the packets, while tls-crypt authenticate and encrypt them." - log_menu " 1) tls-crypt (recommended)" - log_menu " 2) tls-auth" - until [[ $TLS_SIG =~ [1-2] ]]; do - read -rp "Control channel additional security mechanism [1-2]: " -e -i 1 TLS_SIG + log_prompt "You can add an additional layer of security to the control channel." + log_menu " 1) tls-crypt-v2 (recommended): Encrypts control channel, unique key per client" + log_menu " 2) tls-crypt: Encrypts control channel, shared key for all clients" + log_menu " 3) tls-auth: Authenticates control channel, no encryption" + until [[ $TLS_SIG =~ ^[1-3]$ ]]; do + read -rp "Control channel additional security mechanism [1-3]: " -e -i 1 TLS_SIG done fi log_menu "" @@ -1170,10 +1170,14 @@ function installOpenVPN() { log_info "Generating TLS key..." case $TLS_SIG in 1) + # Generate tls-crypt-v2 server key + run_cmd_fatal "Generating tls-crypt-v2 server key" openvpn --genkey tls-crypt-v2-server /etc/openvpn/server/tls-crypt-v2.key + ;; + 2) # Generate tls-crypt key run_cmd_fatal "Generating tls-crypt key" openvpn --genkey --secret /etc/openvpn/server/tls-crypt.key ;; - 2) + 3) # Generate tls-auth key run_cmd_fatal "Generating tls-auth key" openvpn --genkey --secret /etc/openvpn/server/tls-auth.key ;; @@ -1320,9 +1324,12 @@ push "redirect-gateway ipv6"' >>/etc/openvpn/server/server.conf case $TLS_SIG in 1) - echo "tls-crypt tls-crypt.key" >>/etc/openvpn/server/server.conf + echo "tls-crypt-v2 tls-crypt-v2.key" >>/etc/openvpn/server/server.conf ;; 2) + echo "tls-crypt tls-crypt.key" >>/etc/openvpn/server/server.conf + ;; + 3) echo "tls-auth tls-auth.key 0" >>/etc/openvpn/server/server.conf ;; esac @@ -1550,12 +1557,14 @@ function generateClientConfig() { local client="$1" local home_dir="$2" - # Determine if we use tls-auth or tls-crypt + # Determine if we use tls-crypt-v2, tls-crypt, or tls-auth local tls_sig="" - if grep -qs "^tls-crypt" /etc/openvpn/server/server.conf; then + if grep -qs "^tls-crypt-v2" /etc/openvpn/server/server.conf; then tls_sig="1" - elif grep -qs "^tls-auth" /etc/openvpn/server/server.conf; then + elif grep -qs "^tls-crypt" /etc/openvpn/server/server.conf; then tls_sig="2" + elif grep -qs "^tls-auth" /etc/openvpn/server/server.conf; then + tls_sig="3" fi # Generate the custom client.ovpn @@ -1575,11 +1584,25 @@ function generateClientConfig() { case $tls_sig in 1) + # Generate per-client tls-crypt-v2 key using secure temp file + tls_crypt_v2_tmpfile=$(mktemp) + if ! openvpn --tls-crypt-v2 /etc/openvpn/server/tls-crypt-v2.key \ + --genkey tls-crypt-v2-client "$tls_crypt_v2_tmpfile"; then + rm -f "$tls_crypt_v2_tmpfile" + log_error "Failed to generate tls-crypt-v2 client key" + exit 1 + fi + echo "" + cat "$tls_crypt_v2_tmpfile" + echo "" + rm -f "$tls_crypt_v2_tmpfile" + ;; + 2) echo "" cat /etc/openvpn/server/tls-crypt.key echo "" ;; - 2) + 3) echo "key-direction 1" echo "" cat /etc/openvpn/server/tls-auth.key diff --git a/test/server-entrypoint.sh b/test/server-entrypoint.sh index 63260bb..83e5328 100755 --- a/test/server-entrypoint.sh +++ b/test/server-entrypoint.sh @@ -22,11 +22,33 @@ export PORT_CHOICE=1 export PROTOCOL_CHOICE=1 export DNS=2 # Self-hosted Unbound DNS resolver export COMPRESSION_ENABLED=n -export CUSTOMIZE_ENC=n export CLIENT=testclient export PASS=1 export ENDPOINT=openvpn-server +# TLS key type configuration (default: tls-crypt-v2) +# TLS_SIG: 1=tls-crypt-v2, 2=tls-crypt, 3=tls-auth +# TLS_KEY_FILE: the expected key file name for verification +TLS_SIG="${TLS_SIG:-1}" +TLS_KEY_FILE="${TLS_KEY_FILE:-tls-crypt-v2.key}" +export TLS_SIG + +# If using non-default TLS settings, enable encryption customization +if [ "$TLS_SIG" != "1" ]; then + export CUSTOMIZE_ENC=y + # Set other encryption defaults when customizing + export CIPHER_CHOICE=1 # AES-128-GCM + export CERT_TYPE=1 # ECDSA + export CERT_CURVE_CHOICE=1 # prime256v1 + export CC_CIPHER_CHOICE=1 # ECDHE-ECDSA-AES-128-GCM-SHA256 + export DH_TYPE=1 # ECDH + export DH_CURVE_CHOICE=1 # prime256v1 + export HMAC_ALG_CHOICE=1 # SHA-256 + echo "Testing TLS key type: $TLS_SIG (key file: $TLS_KEY_FILE)" +else + export CUSTOMIZE_ENC=n +fi + echo "Running OpenVPN install script..." # Run in subshell because the script calls 'exit 0' after generating client config # Capture output to validate logging format, while still displaying it @@ -59,7 +81,7 @@ for f in \ /etc/openvpn/server/server.conf \ /etc/openvpn/server/ca.crt \ /etc/openvpn/server/ca.key \ - /etc/openvpn/server/tls-crypt.key \ + "/etc/openvpn/server/$TLS_KEY_FILE" \ /etc/openvpn/server/crl.pem \ /etc/openvpn/server/easy-rsa/pki/ca.crt \ /etc/iptables/add-openvpn-rules.sh \ From 3afccf03516738de737115f161df57df0badd17b Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 13 Dec 2025 14:33:59 +0100 Subject: [PATCH 140/141] Add AGENTS.md --- AGENTS.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..16cca5e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,7 @@ +* Use gh CLI to interact with github +* Test locally using the docker setup when needed +* When doing changes, check if README/FAQ and tests needs to be updated +* Remember the script and documentation needs to be accessible to a moderately technical audience +* Keep PR description concise (no test plan) +* Don't use gh cli to post comments on the developer's behalf +* Don't amend commits and force push unless told otherwise From 991c403d78f4dc2150d98fd02448285ee503da66 Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 13 Dec 2025 14:56:23 +0100 Subject: [PATCH 141/141] chore: fix AGENTS.md linting (GitHub/Docker capitalization) --- AGENTS.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 16cca5e..b9510d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ -* Use gh CLI to interact with github -* Test locally using the docker setup when needed -* When doing changes, check if README/FAQ and tests needs to be updated -* Remember the script and documentation needs to be accessible to a moderately technical audience -* Keep PR description concise (no test plan) -* Don't use gh cli to post comments on the developer's behalf -* Don't amend commits and force push unless told otherwise +- Use gh CLI to interact with GitHub +- Test locally using the Docker setup when needed +- When doing changes, check if README/FAQ and tests needs to be updated +- Remember the script and documentation needs to be accessible to a moderately technical audience +- Keep PR description concise (no test plan) +- Don't use gh cli to post comments on the developer's behalf +- Don't amend commits and force push unless told otherwise