mirror of
https://github.com/flant/ovpn-admin.git
synced 2026-02-04 09:12:13 -08:00
Compare commits
16 Commits
bde25f690e
...
6389cd860a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6389cd860a | ||
|
|
59a8a91bd7 | ||
|
|
69fdc6b094 | ||
|
|
75a7385d59 | ||
|
|
1191bf7f9d | ||
|
|
a2c41756a5 | ||
|
|
ac96942e1d | ||
|
|
39f95e3d2c | ||
|
|
0680b4ff05 | ||
|
|
a7aab7cb6a | ||
|
|
3674d003c9 | ||
|
|
8fc518dba8 | ||
|
|
7134815ce6 | ||
|
|
0c881c81e7 | ||
|
|
699cddc908 | ||
|
|
c83c581e21 |
25
.github/dependabot.yml
vendored
Normal file
25
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Dependencies listed in go.mod
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
|
# Dependencies listed in .github/workflows/*.yml
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
|
# Dependencies listed in Dockerfile
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
|
# Dependencies listed in frontend/package.json
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
17
.github/release.yml
vendored
Normal file
17
.github/release.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
changelog:
|
||||||
|
exclude:
|
||||||
|
labels:
|
||||||
|
- ignore
|
||||||
|
categories:
|
||||||
|
- title: Enhancements 🚀
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
- title: Bug Fixes 🐛
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
- title: Dependency Updates ⬆️
|
||||||
|
labels:
|
||||||
|
- dependencies
|
||||||
|
- title: Other Changes
|
||||||
|
labels:
|
||||||
|
- "*"
|
||||||
35
.github/workflows/chart-release.yml
vendored
Normal file
35
.github/workflows/chart-release.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Release Charts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- 'charts/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
chart-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name "$GITHUB_ACTOR"
|
||||||
|
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v4
|
||||||
|
with:
|
||||||
|
version: v3.7.1
|
||||||
|
|
||||||
|
- name: Run chart-releaser
|
||||||
|
uses: helm/chart-releaser-action@v1.6.0
|
||||||
|
with:
|
||||||
|
charts_dir: charts
|
||||||
|
config: charts/cr.yaml
|
||||||
|
env:
|
||||||
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
107
.github/workflows/chart-test.yaml
vendored
Normal file
107
.github/workflows/chart-test.yaml
vendored
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
name: Chart Test
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
chart:
|
||||||
|
name: Chart
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
changed: ${{ steps.changes.outputs.changed }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4
|
||||||
|
with:
|
||||||
|
version: v3.10.3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2.6.1
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: ct lint
|
||||||
|
|
||||||
|
- name: Check generated docs
|
||||||
|
run: |
|
||||||
|
make docs
|
||||||
|
test "$(git diff --name-only)" == "" \
|
||||||
|
|| ( printf >&2 "\nREADME files are not up to date (run 'make docs'), differences:\n\n%s\n\n" "$(git diff)" ; exit 1 ; )
|
||||||
|
|
||||||
|
- name: Detect changes
|
||||||
|
id: changes
|
||||||
|
run: |
|
||||||
|
changed=$(ct list-changed)
|
||||||
|
if [[ -n "$changed" ]]; then
|
||||||
|
echo "changed=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
chart-test:
|
||||||
|
name: Chart Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: chart
|
||||||
|
if: needs.chart.outputs.changed == 'true'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
kube: ["1.25", "1.29", "1.31"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Helm
|
||||||
|
uses: azure/setup-helm@v4
|
||||||
|
with:
|
||||||
|
version: v3.10.3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
|
- name: Set up chart-testing
|
||||||
|
uses: helm/chart-testing-action@v2.6.1
|
||||||
|
|
||||||
|
# See https://github.com/kubernetes-sigs/kind/releases/tag/v0.17.0
|
||||||
|
- name: Determine KinD node image version
|
||||||
|
id: node_image
|
||||||
|
run: |
|
||||||
|
case ${{ matrix.kube }} in
|
||||||
|
1.25)
|
||||||
|
NODE_IMAGE=kindest/node:v1.25.3@sha256:f52781bc0d7a19fb6c405c2af83abfeb311f130707a0e219175677e366cc45d1 ;;
|
||||||
|
1.29)
|
||||||
|
NODE_IMAGE=kindest/node:v1.29.12@sha256:62c0672ba99a4afd7396512848d6fc382906b8f33349ae68fb1dbfe549f70dec ;;
|
||||||
|
1.31)
|
||||||
|
NODE_IMAGE=kindest/node:v1.31.2@sha256:0526eb5cd8d892ed79b56feb48d17eeee1f719f55d5c35cef468f053caffad32 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "image=$NODE_IMAGE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create KinD cluster
|
||||||
|
uses: helm/kind-action@v1.12.0
|
||||||
|
with:
|
||||||
|
version: v0.17.0
|
||||||
|
node_image: ${{ steps.node_image.outputs.image }}
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: ct install
|
||||||
35
.github/workflows/publish-latest.yaml
vendored
35
.github/workflows/publish-latest.yaml
vendored
@ -1,35 +0,0 @@
|
|||||||
name: Build and publish latest tag to Docker Hub (releases only)
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: build latest images for release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
|
||||||
password: ${{ secrets.DOCKER_PASS }}
|
|
||||||
- name: Push openvpn image to Docker Hub
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
tags: flant/ovpn-admin:openvpn-latest
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm
|
|
||||||
file: Dockerfile.openvpn
|
|
||||||
push: true
|
|
||||||
- name: Push ovpn-admin image to Docker Hub
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
tags: flant/ovpn-admin:latest
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm
|
|
||||||
file: Dockerfile
|
|
||||||
push: true
|
|
||||||
63
.github/workflows/publish-tag.yaml
vendored
63
.github/workflows/publish-tag.yaml
vendored
@ -1,39 +1,52 @@
|
|||||||
name: Build and publish tags to Docker Hub (tags only)
|
name: Build and publish tags to ghcr.io
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- v*
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
WERF_STAGED_DOCKERFILE_VERSION: v2
|
||||||
|
# WERF_BUILDAH_MODE: auto
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: build images for tag
|
name: build images for tag
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get the version
|
|
||||||
id: get_version
|
- uses: werf/actions/install@v1.2
|
||||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
|
||||||
- name: Set up Docker Buildx
|
- name: Login into ghcr.io
|
||||||
uses: docker/setup-buildx-action@v2
|
shell: bash
|
||||||
- name: Login to Docker Hub
|
run: werf cr login -u ${{ github.actor }} -p ${{ github.token }} ghcr.io/${{ github.repository }}
|
||||||
uses: docker/login-action@v2
|
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
uses: docker/metadata-action@v5.6.1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
images: ghcr.io/${{ github.repository }}/${{ matrix.name }}
|
||||||
password: ${{ secrets.DOCKER_PASS }}
|
|
||||||
- name: Push openvpn image to Docker Hub
|
- name: Build Image
|
||||||
uses: docker/build-push-action@v4
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
with:
|
run: |
|
||||||
tags: flant/ovpn-admin:openvpn-${{ steps.get_version.outputs.VERSION }}
|
source "$(werf ci-env github --as-file)"
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm
|
source <(jq -r '.labels | to_entries | to_entries[] | "export WERF_EXPORT_ADD_LABEL_\(.key)=\"\(.value.key)=\(.value.value)\""' <<< $DOCKER_METADATA_OUTPUT_JSON)
|
||||||
file: Dockerfile.openvpn
|
|
||||||
push: true
|
werf build --repo='' --final-repo=''
|
||||||
- name: Push ovpn-admin image to Docker Hub
|
|
||||||
uses: docker/build-push-action@v4
|
- name: Build and Push Image
|
||||||
with:
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: flant/ovpn-admin:${{ steps.get_version.outputs.VERSION }}
|
run: |
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm
|
source "$(werf ci-env github --as-file)"
|
||||||
file: Dockerfile
|
source <(jq -r '.labels | to_entries | to_entries[] | "export WERF_EXPORT_ADD_LABEL_\(.key)=\"\(.value.key)=\(.value.value)\""' <<< $DOCKER_METADATA_OUTPUT_JSON)
|
||||||
push: true
|
|
||||||
|
werf export --tag ghcr.io/${{ github.repository }}/%image%:${{ github.ref_name }}
|
||||||
|
|||||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: checkout code
|
- name: checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: build binaries
|
- name: build binaries
|
||||||
uses: wangyoucao577/go-release-action@v1.40
|
uses: wangyoucao577/go-release-action@v1.53
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
goversion: 1.17
|
goversion: 1.17
|
||||||
|
|||||||
2
.github/workflows/release_arm.yaml
vendored
2
.github/workflows/release_arm.yaml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: checkout code
|
- name: checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: build binaries
|
- name: build binaries
|
||||||
uses: wangyoucao577/go-release-action@v1.40
|
uses: wangyoucao577/go-release-action@v1.53
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
goversion: 1.17
|
goversion: 1.17
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,13 +1,11 @@
|
|||||||
|
bin/
|
||||||
|
|
||||||
easyrsa
|
easyrsa
|
||||||
easyrsa_master
|
easyrsa_master
|
||||||
easyrsa_slave
|
easyrsa_slave
|
||||||
ccd
|
ccd
|
||||||
ccd_master
|
ccd_master
|
||||||
ccd_slave
|
ccd_slave
|
||||||
openvpn-web-ui*
|
|
||||||
openvpn-ui*
|
|
||||||
openvpn-admin*
|
|
||||||
ovpn-admin*
|
|
||||||
frontend/node_modules
|
frontend/node_modules
|
||||||
|
|
||||||
main-packr.go
|
main-packr.go
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.16
|
FROM alpine:3.21
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
RUN apk add --update bash openvpn easy-rsa iptables && \
|
RUN apk add --update bash openvpn easy-rsa iptables && \
|
||||||
ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \
|
ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
FROM node:16-alpine3.15 AS frontend-builder
|
FROM node:16-alpine3.15 AS frontend-builder
|
||||||
COPY frontend/ /app
|
COPY ../frontend /app
|
||||||
RUN apk add --update python3 make g++ && cd /app && npm install && npm run build
|
RUN apk add --update python3 make g++ && cd /app && npm install && npm run build
|
||||||
|
|
||||||
FROM golang:1.17.3-buster AS backend-builder
|
FROM golang:1.17.3-buster AS backend-builder
|
||||||
RUN go install github.com/gobuffalo/packr/v2/packr2@latest
|
RUN go install github.com/gobuffalo/packr/v2/packr2@latest
|
||||||
COPY --from=frontend-builder /app/static /app/frontend/static
|
COPY --from=frontend-builder /app/static /app/frontend/static
|
||||||
COPY . /app
|
COPY .. /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
RUN cd /app && packr2 && env CGO_ENABLED=1 GOOS=linux GOARCH=${TARGETARCH} go build -a -tags netgo -ldflags '-linkmode external -extldflags -static -s -w' -o ovpn-admin && packr2 clean
|
RUN cd /app && packr2 && env CGO_ENABLED=1 GOOS=linux GOARCH=${TARGETARCH} go build -a -tags netgo -ldflags '-linkmode external -extldflags -static -s -w' -o ovpn-admin && packr2 clean
|
||||||
|
|
||||||
FROM alpine:3.16
|
FROM alpine:3.21
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=backend-builder /app/ovpn-admin /app
|
COPY --from=backend-builder /app/ovpn-admin /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
54
Makefile
Normal file
54
Makefile
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
export PATH := $(abspath bin/protoc/bin/):$(abspath bin/):${PATH}
|
||||||
|
export SHELL := env PATH=$(PATH) /bin/sh
|
||||||
|
|
||||||
|
GOOS?=$(shell go env GOOS)
|
||||||
|
GOARCH?=$(shell go env GOARCH)
|
||||||
|
GOLANGCI_VERSION = 1.55.2
|
||||||
|
HELM_DOCS_VERSION = 1.11.0
|
||||||
|
|
||||||
|
ifeq ($(GOARCH),arm)
|
||||||
|
ARCH=armv7
|
||||||
|
else
|
||||||
|
ARCH=$(GOARCH)
|
||||||
|
endif
|
||||||
|
|
||||||
|
COMMIT=$(shell git rev-parse --verify HEAD)
|
||||||
|
|
||||||
|
###########
|
||||||
|
# BUILDING
|
||||||
|
###########
|
||||||
|
|
||||||
|
###########
|
||||||
|
# LINTING
|
||||||
|
###########
|
||||||
|
bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
|
||||||
|
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
|
||||||
|
|
||||||
|
bin/golangci-lint-${GOLANGCI_VERSION}:
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION}
|
||||||
|
@mv bin/golangci-lint $@
|
||||||
|
|
||||||
|
###########
|
||||||
|
# HELM
|
||||||
|
###########
|
||||||
|
|
||||||
|
bin/helm-docs: bin/helm-docs-${HELM_DOCS_VERSION}
|
||||||
|
@ln -sf helm-docs-${HELM_DOCS_VERSION} bin/helm-docs
|
||||||
|
bin/helm-docs-${HELM_DOCS_VERSION}:
|
||||||
|
@mkdir -p bin
|
||||||
|
curl -L https://github.com/norwoodj/helm-docs/releases/download/v${HELM_DOCS_VERSION}/helm-docs_${HELM_DOCS_VERSION}_$(shell uname)_x86_64.tar.gz | tar -zOxf - helm-docs > ./bin/helm-docs-${HELM_DOCS_VERSION} && chmod +x ./bin/helm-docs-${HELM_DOCS_VERSION}
|
||||||
|
|
||||||
|
.PHONY: lint fix
|
||||||
|
lint: bin/golangci-lint
|
||||||
|
bin/golangci-lint run
|
||||||
|
|
||||||
|
fix: bin/golangci-lint
|
||||||
|
bin/golangci-lint run --fix
|
||||||
|
|
||||||
|
.PHONY: docs
|
||||||
|
docs: bin/helm-docs
|
||||||
|
bin/helm-docs -s file -c charts/ -t README.md.gotmpl
|
||||||
|
|
||||||
|
###########
|
||||||
|
# TESTING
|
||||||
|
###########
|
||||||
42
README.md
42
README.md
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
Simple web UI to manage OpenVPN users, their certificates & routes in Linux. While backend is written in Go, frontend is based on Vue.js.
|
Simple web UI to manage OpenVPN users, their certificates & routes in Linux. While backend is written in Go, frontend is based on Vue.js.
|
||||||
|
|
||||||
Originally created in [Flant](https://flant.com/) for internal needs & used for years, then updated to be more modern and [publicly released](https://blog.flant.com/introducing-ovpn-admin-web-interface-for-openvpn/) in March'21. Your contributions are welcome!
|
|
||||||
|
|
||||||
***DISCLAIMER!** This project was created for experienced users (system administrators) and private (e.g., protected by network policies) environments only. Thus, it is not implemented with security in mind (e.g., it doesn't strictly check all parameters passed by users, etc.). It also relies heavily on files and fails if required files aren't available.*
|
***DISCLAIMER!** This project was created for experienced users (system administrators) and private (e.g., protected by network policies) environments only. Thus, it is not implemented with security in mind (e.g., it doesn't strictly check all parameters passed by users, etc.). It also relies heavily on files and fails if required files aren't available.*
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@ -21,16 +19,16 @@ Originally created in [Flant](https://flant.com/) for internal needs & used for
|
|||||||
### Screenshots
|
### Screenshots
|
||||||
|
|
||||||
Managing users in ovpn-admin:
|
Managing users in ovpn-admin:
|
||||||

|

|
||||||
|
|
||||||
An example of dashboard made using ovpn-admin metrics:
|
An example of dashboard made using ovpn-admin metrics:
|
||||||

|

|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### 1. Docker
|
### 1. Docker
|
||||||
|
|
||||||
There is a ready-to-use [docker-compose.yaml](https://github.com/flant/ovpn-admin/blob/master/docker-compose.yaml), so you can just change/add values you need and start it with [start.sh](https://github.com/flant/ovpn-admin/blob/master/start.sh).
|
There is a ready-to-use [docker-compose.yaml](https://github.com/palark/ovpn-admin/blob/master/docker-compose.yaml), so you can just change/add values you need and start it with [start.sh](https://github.com/palark/ovpn-admin/blob/master/start.sh).
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
You need [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) installed.
|
You need [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) installed.
|
||||||
@ -38,7 +36,7 @@ You need [Docker](https://docs.docker.com/get-docker/) and [docker-compose](http
|
|||||||
Commands to execute:
|
Commands to execute:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/flant/ovpn-admin.git
|
git clone https://github.com/palark/ovpn-admin.git
|
||||||
cd ovpn-admin
|
cd ovpn-admin
|
||||||
./start.sh
|
./start.sh
|
||||||
```
|
```
|
||||||
@ -56,7 +54,7 @@ Requirements. You need Linux with the following components installed:
|
|||||||
Commands to execute:
|
Commands to execute:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/flant/ovpn-admin.git
|
git clone https://github.com/palark/ovpn-admin.git
|
||||||
cd ovpn-admin
|
cd ovpn-admin
|
||||||
./bootstrap.sh
|
./bootstrap.sh
|
||||||
./build.sh
|
./build.sh
|
||||||
@ -67,18 +65,18 @@ cd ovpn-admin
|
|||||||
|
|
||||||
### 3. Prebuilt binary
|
### 3. Prebuilt binary
|
||||||
|
|
||||||
You can also download and use prebuilt binaries from the [releases](https://github.com/flant/ovpn-admin/releases/latest) page — just choose a relevant tar.gz file.
|
You can also download and use prebuilt binaries from the [releases](https://github.com/palark/ovpn-admin/releases/latest) page — just choose a relevant tar.gz file.
|
||||||
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
* this tool uses external calls for `bash`, `coreutils` and `easy-rsa`, thus **Linux systems only are supported** at the moment.
|
* This tool uses external calls for `bash`, `coreutils` and `easy-rsa`, thus **Linux systems only are supported** at the moment.
|
||||||
* to enable additional password authentication provide `--auth` and `--auth.db="/etc/easyrsa/pki/users.db`" flags and install [openvpn-user](https://github.com/pashcovich/openvpn-user/releases/latest). This tool should be available in your `$PATH` and its binary should be executable (`+x`).
|
* To enable additional password authentication, provide `--auth` and `--auth.db="/etc/easyrsa/pki/users.db`" flags and install [openvpn-user](https://github.com/pashcovich/openvpn-user/releases/latest). This tool should be available in your `$PATH` and its binary should be executable (`+x`).
|
||||||
* master-replica synchronization does not work with `--storage.backend=kubernetes.secrets` - **WIP**
|
* If you use `--ccd` and `--ccd.path="/etc/openvpn/ccd"` and plan to use static address setup for users, do not forget to provide `--ovpn.network="172.16.100.0/24"` with valid openvpn-server network.
|
||||||
* additional password authentication does not work with `--storage.backend=kubernetes.secrets` - **WIP**
|
* If you want to pass all the traffic generated by the user, you need to edit `ovpn-admin/templates/client.conf.tpl` and uncomment `redirect-gateway def1`.
|
||||||
* if you use `--ccd` and `--ccd.path="/etc/openvpn/ccd"` abd plan to use static address setup for users do not forget to provide `--ovpn.network="172.16.100.0/24"` with valid openvpn-server network
|
* Tested with openvpn-server versions 2.4 and 2.5 and with tls-auth mode only.
|
||||||
* tested only with Openvpn-server versions 2.4 and 2.5 with only tls-auth mode
|
* Not tested with Easy-RSA version > 3.0.8.
|
||||||
* not tested with EasyRsa version > 3.0.8
|
* Status of user connections update every 28 seconds.
|
||||||
* status of users connections update every 28 second(*no need to ask why =)*)
|
* Master-replica synchronization and additional password authentication do not work with `--storage.backend=kubernetes.secrets` - **WIP**
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -160,6 +158,9 @@ Flags:
|
|||||||
--auth.db="./easyrsa/pki/users.db"
|
--auth.db="./easyrsa/pki/users.db"
|
||||||
(or OVPN_AUTH_DB_PATH) database path for password authorization
|
(or OVPN_AUTH_DB_PATH) database path for password authorization
|
||||||
|
|
||||||
|
--auth.db-init
|
||||||
|
(or OVPN_AUTH_DB_INIT) enable database init if user db not exists or size is 0
|
||||||
|
|
||||||
--log.level set log level: trace, debug, info, warn, error (default info)
|
--log.level set log level: trace, debug, info, warn, error (default info)
|
||||||
(or LOG_LEVEL)
|
(or LOG_LEVEL)
|
||||||
|
|
||||||
@ -172,6 +173,11 @@ Flags:
|
|||||||
--version show application version
|
--version show application version
|
||||||
```
|
```
|
||||||
|
|
||||||
## Further information
|
## Authors
|
||||||
|
|
||||||
Please feel free to use [issues](https://github.com/flant/ovpn-admin/issues) and [discussions](https://github.com/flant/ovpn-admin/discussions) to get help from maintainers & community.
|
ovpn-admin was originally created in [Flant](https://github.com/flant/) and used internally for years.
|
||||||
|
|
||||||
|
In March 2021, it [went public](https://medium.com/flant-com/introducing-ovpn-admin-a-web-interface-to-manage-openvpn-users-d81705ad8f23) and was still developed in Flant.
|
||||||
|
Namely, [@vitaliy-sn](https://github.com/vitaliy-sn) created its first version in Python, and [@pashcovich](https://github.com/pashcovich) rewrote it in Go.
|
||||||
|
|
||||||
|
In November 2024, this project was moved to [Palark](https://github.com/palark/), which is currently responsible for its maintenance and development.
|
||||||
|
|||||||
@ -8,7 +8,9 @@ import (
|
|||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -60,7 +62,6 @@ func genPrivKey() (privKeyPEM *bytes.Buffer, err error) {
|
|||||||
Bytes: privKeyPKCS8,
|
Bytes: privKeyPKCS8,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +134,17 @@ func genClientCert(privKey, caPrivKey *rsa.PrivateKey, ca *x509.Certificate, cn
|
|||||||
serialNumberRange := new(big.Int).Lsh(big.NewInt(1), 128)
|
serialNumberRange := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
serial, err := rand.Int(rand.Reader, serialNumberRange)
|
serial, err := rand.Int(rand.Reader, serialNumberRange)
|
||||||
|
|
||||||
|
certLifetimeDays, err := strconv.Atoi(*clientCertExpirationDays)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't get client certificate expiration value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
notBefore := time.Now()
|
||||||
|
notAfter := notBefore.Add(time.Duration(certLifetimeDays) * 24 * time.Hour)
|
||||||
|
if notAfter.After(ca.NotAfter) {
|
||||||
|
notAfter = ca.NotAfter
|
||||||
|
}
|
||||||
|
|
||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
DNSNames: []string{cn},
|
DNSNames: []string{cn},
|
||||||
@ -142,8 +154,8 @@ func genClientCert(privKey, caPrivKey *rsa.PrivateKey, ca *x509.Certificate, cn
|
|||||||
},
|
},
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||||
NotBefore: time.Now(),
|
NotBefore: notBefore,
|
||||||
NotAfter: ca.NotAfter,
|
NotAfter: notAfter,
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerBytes, err := x509.CreateCertificate(rand.Reader, &template, ca, &privKey.PublicKey, caPrivKey)
|
issuerBytes, err := x509.CreateCertificate(rand.Reader, &template, ca, &privKey.PublicKey, caPrivKey)
|
||||||
|
|||||||
2
charts/cr.yaml
Normal file
2
charts/cr.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
owner: palark
|
||||||
|
git-base-url: https://api.github.com/
|
||||||
15
charts/openvpn-admin/Chart.yaml
Normal file
15
charts/openvpn-admin/Chart.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
appVersion: "2.0.2"
|
||||||
|
description: Simple web UI to manage OpenVPN users, their certificates & routes in Linux. While backend is written in Go, frontend is based on Vue.js.
|
||||||
|
name: openvpn-admin
|
||||||
|
version: "0.0.2"
|
||||||
|
kubeVersion: ">=1.14.0-0"
|
||||||
|
maintainers:
|
||||||
|
- name: nabokihms
|
||||||
|
email: max.nabokih@gmail.com
|
||||||
|
url: github.com/nabokihms
|
||||||
|
sources:
|
||||||
|
- https://github.com/palark/openvpn-admin
|
||||||
|
keywords:
|
||||||
|
- kubernetes
|
||||||
|
- openvpn
|
||||||
40
charts/openvpn-admin/README.md
Normal file
40
charts/openvpn-admin/README.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# openvpn-admin
|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
Simple web UI to manage OpenVPN users, their certificates & routes in Linux. While backend is written in Go, frontend is based on Vue.js.
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
| Name | Email | Url |
|
||||||
|
| ---- | ------ | --- |
|
||||||
|
| nabokihms | <max.nabokih@gmail.com> | <github.com/nabokihms> |
|
||||||
|
|
||||||
|
## Source Code
|
||||||
|
|
||||||
|
* <https://github.com/palark/openvpn-admin>
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Kubernetes: `>=1.14.0-0`
|
||||||
|
|
||||||
|
## Values
|
||||||
|
|
||||||
|
| Key | Type | Default | Description |
|
||||||
|
|-----|------|---------|-------------|
|
||||||
|
| domain | string | `"changeme"` | |
|
||||||
|
| ovpnAdmin.repo | string | `"ghcr.io/palark/ovpn-admin/ovpn-admin"` | |
|
||||||
|
| ovpnAdmin.basicAuth.user | string | `"admin"` | |
|
||||||
|
| ovpnAdmin.basicAuth.password | string | `"changeme"` | |
|
||||||
|
| openvpn.repo | string | `"ghcr.io/palark/ovpn-admin/openvpn"` | |
|
||||||
|
| openvpn.subnet | string | `"172.16.200.0/255.255.255.0"` | |
|
||||||
|
| openvpn.inlet | string | `"HostPort"` | |
|
||||||
|
| openvpn.hostPort | int | `1194` | |
|
||||||
|
| nodeSelector | object | `{}` | [Node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) configuration. |
|
||||||
|
| tolerations | list | `[]` | [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for node taints. See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) for details. |
|
||||||
|
| ingress.enabled | bool | `false` | Enable [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/). |
|
||||||
|
| ingress.className | string | `""` | Ingress [class name](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class). |
|
||||||
|
| ingress.annotations | object | `{}` | Annotations to be added to the ingress. |
|
||||||
|
|
||||||
|
----------------------------------------------
|
||||||
|
Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0)
|
||||||
@ -12,19 +12,19 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: openvpn
|
app: openvpn
|
||||||
spec:
|
spec:
|
||||||
{{- if .Values.openvpn.nodeSelector }}
|
{{- if .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
{{- .Values.openvpn.nodeSelector | toYaml | indent 8 | printf "\n%s" }}
|
{{- .Values.nodeSelector | toYaml | indent 8 | printf "\n%s" }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.openvpn.tolerations }}
|
{{- if .Values.tolerations }}
|
||||||
tolerations:
|
tolerations:
|
||||||
{{- .Values.openvpn.tolerations | toYaml | indent 8 | printf "\n%s" }}
|
{{- .Values.tolerations | toYaml | indent 8 | printf "\n%s" }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
terminationGracePeriodSeconds: 0
|
terminationGracePeriodSeconds: 0
|
||||||
serviceAccountName: openvpn
|
serviceAccountName: openvpn
|
||||||
containers:
|
containers:
|
||||||
- name: ovpn-admin
|
- name: ovpn-admin
|
||||||
image: {{ .Values.ovpnAdmin.image }}
|
image: {{ .Values.ovpnAdmin.repo }}:master
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
@ -60,7 +60,7 @@ spec:
|
|||||||
- name: ccd
|
- name: ccd
|
||||||
mountPath: /mnt/ccd
|
mountPath: /mnt/ccd
|
||||||
- name: openvpn
|
- name: openvpn
|
||||||
image: {{ .Values.openvpn.image }}
|
image: {{ .Values.ovpnAdmin.repo }}:master
|
||||||
command: [ '/entrypoint.sh' ]
|
command: [ '/entrypoint.sh' ]
|
||||||
# imagePullPolicy: Always
|
# imagePullPolicy: Always
|
||||||
securityContext:
|
securityContext:
|
||||||
@ -4,12 +4,17 @@ kind: Ingress
|
|||||||
metadata:
|
metadata:
|
||||||
name: ovpn-admin
|
name: ovpn-admin
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: nginx
|
|
||||||
nginx.ingress.kubernetes.io/backend-protocol: HTTP
|
nginx.ingress.kubernetes.io/backend-protocol: HTTP
|
||||||
nginx.ingress.kubernetes.io/auth-type: basic
|
nginx.ingress.kubernetes.io/auth-type: basic
|
||||||
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
|
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
|
||||||
nginx.ingress.kubernetes.io/auth-secret: basic-auth
|
nginx.ingress.kubernetes.io/auth-secret: basic-auth
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
|
{{- with .Values.ingress.className }}
|
||||||
|
ingressClassName: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- {{ .Values.domain }}
|
- {{ .Values.domain }}
|
||||||
@ -25,15 +30,3 @@ spec:
|
|||||||
name: ovpn-admin
|
name: ovpn-admin
|
||||||
port:
|
port:
|
||||||
name: http
|
name: http
|
||||||
---
|
|
||||||
apiVersion: cert-manager.io/v1
|
|
||||||
kind: Certificate
|
|
||||||
metadata:
|
|
||||||
name: ovpn-admin
|
|
||||||
spec:
|
|
||||||
secretName: ingress-tls
|
|
||||||
dnsNames:
|
|
||||||
- {{ .Values.domain }}
|
|
||||||
issuerRef:
|
|
||||||
name: letsencrypt
|
|
||||||
kind: ClusterIssuer
|
|
||||||
39
charts/openvpn-admin/values.yaml
Normal file
39
charts/openvpn-admin/values.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
domain: changeme
|
||||||
|
ovpnAdmin:
|
||||||
|
repo: ghcr.io/palark/ovpn-admin/ovpn-admin
|
||||||
|
basicAuth:
|
||||||
|
user: admin
|
||||||
|
password: changeme
|
||||||
|
openvpn:
|
||||||
|
repo: ghcr.io/palark/ovpn-admin/openvpn
|
||||||
|
subnet: 172.16.200.0/255.255.255.0
|
||||||
|
# LoadBalancer or ExternalIP or HostPort
|
||||||
|
inlet: HostPort
|
||||||
|
#
|
||||||
|
# If inlet: ExternalIP
|
||||||
|
# externalIP: 1.2.3.4
|
||||||
|
# externalPort: 1194
|
||||||
|
#
|
||||||
|
# If inlet: HostPort
|
||||||
|
hostPort: 1194
|
||||||
|
# Domain or ip for connect to OpenVPN server
|
||||||
|
# externalHost: 1.2.3.4
|
||||||
|
|
||||||
|
# -- [Node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) configuration.
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for node taints.
|
||||||
|
# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) for details.
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
# -- Enable [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/).
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# -- Ingress [class name](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class).
|
||||||
|
className: ""
|
||||||
|
|
||||||
|
# -- Annotations to be added to the ingress.
|
||||||
|
annotations: {}
|
||||||
|
# kubernetes.io/ingress.class: nginx
|
||||||
|
# kubernetes.io/tls-acme: "true"
|
||||||
@ -1,2 +0,0 @@
|
|||||||
name: ovpn-admin
|
|
||||||
version: 1.0.0
|
|
||||||
@ -1 +0,0 @@
|
|||||||
helm chart example
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
domain: changeme
|
|
||||||
ovpnAdmin:
|
|
||||||
image: changeme
|
|
||||||
basicAuth:
|
|
||||||
user: admin
|
|
||||||
password: changeme
|
|
||||||
openvpn:
|
|
||||||
image: changeme
|
|
||||||
subnet: 172.16.200.0/255.255.255.0
|
|
||||||
# nodeSelector:
|
|
||||||
# node-role.kubernetes.io/master: ""
|
|
||||||
# tolerations:
|
|
||||||
# - effect: NoSchedule
|
|
||||||
# key: node-role.kubernetes.io/master
|
|
||||||
#
|
|
||||||
# // LoadBalancer or ExternalIP or HostPort
|
|
||||||
inlet: HostPort
|
|
||||||
#
|
|
||||||
# If inlet: ExternalIP
|
|
||||||
# externalIP: 1.2.3.4
|
|
||||||
# externalPort: 1194
|
|
||||||
#
|
|
||||||
# If inlet: HostPort
|
|
||||||
hostPort: 1194
|
|
||||||
# Domain or ip for connect to OpenVPN server
|
|
||||||
# externalHost: 1.2.3.4
|
|
||||||
76
main.go
76
main.go
@ -9,11 +9,7 @@ import (
|
|||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -25,6 +21,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
|
||||||
"github.com/gobuffalo/packr/v2"
|
"github.com/gobuffalo/packr/v2"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
@ -34,7 +35,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
usernameRegexp = `^([a-zA-Z0-9_.-@])+$`
|
usernameRegexp = `^([a-zA-Z0-9_.\-@])+$`
|
||||||
passwordMinLength = 6
|
passwordMinLength = 6
|
||||||
certsArchiveFileName = "certs.tar.gz"
|
certsArchiveFileName = "certs.tar.gz"
|
||||||
ccdArchiveFileName = "ccd.tar.gz"
|
ccdArchiveFileName = "ccd.tar.gz"
|
||||||
@ -49,7 +50,7 @@ const (
|
|||||||
var (
|
var (
|
||||||
listenHost = kingpin.Flag("listen.host", "host for ovpn-admin").Default("0.0.0.0").Envar("OVPN_LISTEN_HOST").String()
|
listenHost = kingpin.Flag("listen.host", "host for ovpn-admin").Default("0.0.0.0").Envar("OVPN_LISTEN_HOST").String()
|
||||||
listenPort = kingpin.Flag("listen.port", "port for ovpn-admin").Default("8080").Envar("OVPN_LISTEN_PORT").String()
|
listenPort = kingpin.Flag("listen.port", "port for ovpn-admin").Default("8080").Envar("OVPN_LISTEN_PORT").String()
|
||||||
listenBaseUrl = kingpin.Flag("listen.base-url", "base url for ovpn-admin").Default("/").Envar("OVPN_LISTEN_BASE_URL").String()
|
listenBaseUrl = kingpin.Flag("listen.base-url", "base url for ovpn-admin").Default("/").Envar("OVPN_LISTEN_BASE_URL").String()
|
||||||
serverRole = kingpin.Flag("role", "server role, master or slave").Default("master").Envar("OVPN_ROLE").HintOptions("master", "slave").String()
|
serverRole = kingpin.Flag("role", "server role, master or slave").Default("master").Envar("OVPN_ROLE").HintOptions("master", "slave").String()
|
||||||
masterHost = kingpin.Flag("master.host", "URL for the master server").Default("http://127.0.0.1").Envar("OVPN_MASTER_HOST").String()
|
masterHost = kingpin.Flag("master.host", "URL for the master server").Default("http://127.0.0.1").Envar("OVPN_MASTER_HOST").String()
|
||||||
masterBasicAuthUser = kingpin.Flag("master.basic-auth.user", "user for master server's Basic Auth").Default("").Envar("OVPN_MASTER_USER").String()
|
masterBasicAuthUser = kingpin.Flag("master.basic-auth.user", "user for master server's Basic Auth").Default("").Envar("OVPN_MASTER_USER").String()
|
||||||
@ -64,16 +65,18 @@ var (
|
|||||||
metricsPath = kingpin.Flag("metrics.path", "URL path for exposing collected metrics").Default("/metrics").Envar("OVPN_METRICS_PATH").String()
|
metricsPath = kingpin.Flag("metrics.path", "URL path for exposing collected metrics").Default("/metrics").Envar("OVPN_METRICS_PATH").String()
|
||||||
easyrsaDirPath = kingpin.Flag("easyrsa.path", "path to easyrsa dir").Default("./easyrsa").Envar("EASYRSA_PATH").String()
|
easyrsaDirPath = kingpin.Flag("easyrsa.path", "path to easyrsa dir").Default("./easyrsa").Envar("EASYRSA_PATH").String()
|
||||||
indexTxtPath = kingpin.Flag("easyrsa.index-path", "path to easyrsa index file").Default("").Envar("OVPN_INDEX_PATH").String()
|
indexTxtPath = kingpin.Flag("easyrsa.index-path", "path to easyrsa index file").Default("").Envar("OVPN_INDEX_PATH").String()
|
||||||
easyrsaBinPath = kingpin.Flag("easyrsa.bin-path", "path to easyrsa script").Default("easyrsa").Envar("EASYRSA_BIN_PATH").String()
|
easyrsaBinPath = kingpin.Flag("easyrsa.bin-path", "path to easyrsa script").Default("easyrsa").Envar("EASYRSA_BIN_PATH").String()
|
||||||
ccdEnabled = kingpin.Flag("ccd", "enable client-config-dir").Default("false").Envar("OVPN_CCD").Bool()
|
ccdEnabled = kingpin.Flag("ccd", "enable client-config-dir").Default("false").Envar("OVPN_CCD").Bool()
|
||||||
ccdDir = kingpin.Flag("ccd.path", "path to client-config-dir").Default("./ccd").Envar("OVPN_CCD_PATH").String()
|
ccdDir = kingpin.Flag("ccd.path", "path to client-config-dir").Default("./ccd").Envar("OVPN_CCD_PATH").String()
|
||||||
clientConfigTemplatePath = kingpin.Flag("templates.clientconfig-path", "path to custom client.conf.tpl").Default("").Envar("OVPN_TEMPLATES_CC_PATH").String()
|
clientConfigTemplatePath = kingpin.Flag("templates.clientconfig-path", "path to custom client.conf.tpl").Default("").Envar("OVPN_TEMPLATES_CC_PATH").String()
|
||||||
ccdTemplatePath = kingpin.Flag("templates.ccd-path", "path to custom ccd.tpl").Default("").Envar("OVPN_TEMPLATES_CCD_PATH").String()
|
ccdTemplatePath = kingpin.Flag("templates.ccd-path", "path to custom ccd.tpl").Default("").Envar("OVPN_TEMPLATES_CCD_PATH").String()
|
||||||
authByPassword = kingpin.Flag("auth.password", "enable additional password authentication").Default("false").Envar("OVPN_AUTH").Bool()
|
authByPassword = kingpin.Flag("auth.password", "enable additional password authentication").Default("false").Envar("OVPN_AUTH").Bool()
|
||||||
authDatabase = kingpin.Flag("auth.db", "database path for password authentication").Default("./easyrsa/pki/users.db").Envar("OVPN_AUTH_DB_PATH").String()
|
authDatabase = kingpin.Flag("auth.db", "database path for password authentication").Default("./easyrsa/pki/users.db").Envar("OVPN_AUTH_DB_PATH").String()
|
||||||
|
authDataBaseInit = kingpin.Flag("auth.db-init", "enable database initialization if db user not exists or size is 0").Default("false").Envar("OVPN_AUTH_DB_INIT").Bool()
|
||||||
logLevel = kingpin.Flag("log.level", "set log level: trace, debug, info, warn, error (default info)").Default("info").Envar("LOG_LEVEL").String()
|
logLevel = kingpin.Flag("log.level", "set log level: trace, debug, info, warn, error (default info)").Default("info").Envar("LOG_LEVEL").String()
|
||||||
logFormat = kingpin.Flag("log.format", "set log format: text, json (default text)").Default("text").Envar("LOG_FORMAT").String()
|
logFormat = kingpin.Flag("log.format", "set log format: text, json (default text)").Default("text").Envar("LOG_FORMAT").String()
|
||||||
storageBackend = kingpin.Flag("storage.backend", "storage backend: filesystem, kubernetes.secrets (default filesystem)").Default("filesystem").Envar("STORAGE_BACKEND").String()
|
storageBackend = kingpin.Flag("storage.backend", "storage backend: filesystem, kubernetes.secrets (default filesystem)").Default("filesystem").Envar("STORAGE_BACKEND").String()
|
||||||
|
clientCertExpirationDays = kingpin.Flag("client-cert.expiration-days", "Expiration period of OpenVPN client certificates in days, the period will shrink automatically to the CA expiration period").Default("3650").Envar("CLIENT_CERT_EXPIRATION_DAYS").String()
|
||||||
|
|
||||||
certsArchivePath = "/tmp/" + certsArchiveFileName
|
certsArchivePath = "/tmp/" + certsArchiveFileName
|
||||||
ccdArchivePath = "/tmp/" + ccdArchiveFileName
|
ccdArchivePath = "/tmp/" + ccdArchiveFileName
|
||||||
@ -504,6 +507,10 @@ func main() {
|
|||||||
*indexTxtPath = *easyrsaDirPath + "/pki/index.txt"
|
*indexTxtPath = *easyrsaDirPath + "/pki/index.txt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *authDataBaseInit {
|
||||||
|
ovpnUserInitDb()
|
||||||
|
}
|
||||||
|
|
||||||
ovpnAdmin := new(OvpnAdmin)
|
ovpnAdmin := new(OvpnAdmin)
|
||||||
|
|
||||||
ovpnAdmin.lastSyncTime = "unknown"
|
ovpnAdmin.lastSyncTime = "unknown"
|
||||||
@ -558,27 +565,27 @@ func main() {
|
|||||||
static := CacheControlWrapper(http.FileServer(staticBox))
|
static := CacheControlWrapper(http.FileServer(staticBox))
|
||||||
|
|
||||||
http.Handle(*listenBaseUrl, http.StripPrefix(strings.TrimRight(*listenBaseUrl, "/"), static))
|
http.Handle(*listenBaseUrl, http.StripPrefix(strings.TrimRight(*listenBaseUrl, "/"), static))
|
||||||
http.HandleFunc(*listenBaseUrl + "api/server/settings", ovpnAdmin.serverSettingsHandler)
|
http.HandleFunc(*listenBaseUrl+"api/server/settings", ovpnAdmin.serverSettingsHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/users/list", ovpnAdmin.userListHandler)
|
http.HandleFunc(*listenBaseUrl+"api/users/list", ovpnAdmin.userListHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/create", ovpnAdmin.userCreateHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/create", ovpnAdmin.userCreateHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/change-password", ovpnAdmin.userChangePasswordHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/change-password", ovpnAdmin.userChangePasswordHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/rotate", ovpnAdmin.userRotateHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/rotate", ovpnAdmin.userRotateHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/delete", ovpnAdmin.userDeleteHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/delete", ovpnAdmin.userDeleteHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/revoke", ovpnAdmin.userRevokeHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/revoke", ovpnAdmin.userRevokeHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/unrevoke", ovpnAdmin.userUnrevokeHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/unrevoke", ovpnAdmin.userUnrevokeHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/config/show", ovpnAdmin.userShowConfigHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/config/show", ovpnAdmin.userShowConfigHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/disconnect", ovpnAdmin.userDisconnectHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/disconnect", ovpnAdmin.userDisconnectHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/statistic", ovpnAdmin.userStatisticHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/statistic", ovpnAdmin.userStatisticHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/ccd", ovpnAdmin.userShowCcdHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/ccd", ovpnAdmin.userShowCcdHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/user/ccd/apply", ovpnAdmin.userApplyCcdHandler)
|
http.HandleFunc(*listenBaseUrl+"api/user/ccd/apply", ovpnAdmin.userApplyCcdHandler)
|
||||||
|
|
||||||
http.HandleFunc(*listenBaseUrl + "api/sync/last/try", ovpnAdmin.lastSyncTimeHandler)
|
http.HandleFunc(*listenBaseUrl+"api/sync/last/try", ovpnAdmin.lastSyncTimeHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + "api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler)
|
http.HandleFunc(*listenBaseUrl+"api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler)
|
http.HandleFunc(*listenBaseUrl+downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler)
|
||||||
http.HandleFunc(*listenBaseUrl + downloadCcdApiUrl, ovpnAdmin.downloadCcdHandler)
|
http.HandleFunc(*listenBaseUrl+downloadCcdApiUrl, ovpnAdmin.downloadCcdHandler)
|
||||||
|
|
||||||
http.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{}))
|
http.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{}))
|
||||||
http.HandleFunc(*listenBaseUrl + "ping", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc(*listenBaseUrl+"ping", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "pong")
|
fmt.Fprintf(w, "pong")
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -854,7 +861,7 @@ func (oAdmin *OvpnAdmin) getCcd(username string) Ccd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkStaticAddressIsFree(staticAddress string, username string) bool {
|
func checkStaticAddressIsFree(staticAddress string, username string) bool {
|
||||||
o := runBash(fmt.Sprintf("grep -rl ' %s ' %s | grep -vx %s/%s | wc -l", staticAddress, *ccdDir, *ccdDir, username))
|
o := runBash(fmt.Sprintf("grep -rl ' %[1]s ' %[2]s | grep -vx %[2]s/%[3]s | wc -l", staticAddress, *ccdDir, username))
|
||||||
|
|
||||||
if strings.TrimSpace(o) == "0" {
|
if strings.TrimSpace(o) == "0" {
|
||||||
return true
|
return true
|
||||||
@ -1004,7 +1011,7 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (bool, string) {
|
|||||||
func (oAdmin *OvpnAdmin) userChangePassword(username, password string) (error, string) {
|
func (oAdmin *OvpnAdmin) userChangePassword(username, password string) (error, string) {
|
||||||
|
|
||||||
if checkUserExist(username) {
|
if checkUserExist(username) {
|
||||||
o := runBash(fmt.Sprintf("openvpn-user check --db.path %s --user %s | grep %s | wc -l", *authDatabase, username, username))
|
o := runBash(fmt.Sprintf("openvpn-user check --db.path %[1]s --user %[2]s | grep %[2]s | wc -l", *authDatabase, username))
|
||||||
log.Debug(o)
|
log.Debug(o)
|
||||||
|
|
||||||
if err := validatePassword(password); err != nil {
|
if err := validatePassword(password); err != nil {
|
||||||
@ -1048,12 +1055,12 @@ func (oAdmin *OvpnAdmin) userRevoke(username string) (error, string) {
|
|||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
o := runBash(fmt.Sprintf("cd %s && echo yes | easyrsa revoke %s 1>/dev/null && %s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath, username))
|
o := runBash(fmt.Sprintf("cd %[1]s && echo yes | %[2]s revoke %[3]s 1>/dev/null && %[2]s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath, username))
|
||||||
log.Debugln(o)
|
log.Debugln(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *authByPassword {
|
if *authByPassword {
|
||||||
o := runBash(fmt.Sprintf("openvpn-user revoke --db-path %s --user %s", *authDatabase, username))
|
o := runBash(fmt.Sprintf("openvpn-user revoke --db.path %s --user %s", *authDatabase, username))
|
||||||
log.Debug(o)
|
log.Debug(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1115,7 +1122,7 @@ func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) {
|
|||||||
_ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath))
|
_ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath))
|
||||||
|
|
||||||
if *authByPassword {
|
if *authByPassword {
|
||||||
o := runBash(fmt.Sprintf("openvpn-user restore --db-path %s --user %s", *authDatabase, username))
|
o := runBash(fmt.Sprintf("openvpn-user restore --db.path %s --user %s", *authDatabase, username))
|
||||||
log.Debug(o)
|
log.Debug(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1340,7 +1347,7 @@ func (oAdmin *OvpnAdmin) mgmtGetActiveClients() []clientStatus {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
oAdmin.mgmtRead(conn) // read welcome message
|
oAdmin.mgmtRead(conn) // read welcome message
|
||||||
conn.Write([]byte("status\n"))
|
conn.Write([]byte("status 1\n"))
|
||||||
activeClients = append(activeClients, oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn), srv)...)
|
activeClients = append(activeClients, oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn), srv)...)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
@ -1501,6 +1508,13 @@ func unArchiveCcd() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ovpnUserInitDb() {
|
||||||
|
if fi, err := os.Stat(*authDatabase); errors.Is(err, os.ErrNotExist) || fi.Size() == 0 {
|
||||||
|
i := runBash(fmt.Sprintf("openvpn-user --db.path %[1]s db-init && openvpn-user --db.path %[1]s db-migrate", *authDatabase))
|
||||||
|
log.Debug(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (oAdmin *OvpnAdmin) syncDataFromMaster() {
|
func (oAdmin *OvpnAdmin) syncDataFromMaster() {
|
||||||
retryCountMax := 3
|
retryCountMax := 3
|
||||||
certsDownloadFailed := true
|
certsDownloadFailed := true
|
||||||
|
|||||||
@ -48,7 +48,7 @@ if [ ${OVPN_PASSWD_AUTH} = "true" ]; then
|
|||||||
echo "auth-user-pass-verify /etc/openvpn/scripts/auth.sh via-file" | tee -a /etc/openvpn/openvpn.conf
|
echo "auth-user-pass-verify /etc/openvpn/scripts/auth.sh via-file" | tee -a /etc/openvpn/openvpn.conf
|
||||||
echo "script-security 2" | tee -a /etc/openvpn/openvpn.conf
|
echo "script-security 2" | tee -a /etc/openvpn/openvpn.conf
|
||||||
echo "verify-client-cert require" | tee -a /etc/openvpn/openvpn.conf
|
echo "verify-client-cert require" | tee -a /etc/openvpn/openvpn.conf
|
||||||
openvpn-user db-init --db.path=$EASY_RSA_LOC/pki/users.db
|
openvpn-user db-init --db.path=$EASY_RSA_LOC/pki/users.db && openvpn-user db-migrate --db.path=$EASY_RSA_LOC/pki/users.db
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ -d $EASY_RSA_LOC/pki ] && chmod 755 $EASY_RSA_LOC/pki
|
[ -d $EASY_RSA_LOC/pki ] && chmod 755 $EASY_RSA_LOC/pki
|
||||||
|
|||||||
2
start.sh
2
start.sh
@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
docker-compose -p openvpn-master up -d --build
|
docker compose -p openvpn-master up -d --build
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
project: ovpn-admin
|
project: ovpn-admin
|
||||||
configVersion: 1
|
configVersion: 1
|
||||||
|
|
||||||
---
|
---
|
||||||
image: ovpn-admin
|
image: ovpn-admin
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile.ovpn-admin
|
||||||
|
context: .
|
||||||
---
|
---
|
||||||
image: openvpn
|
image: openvpn
|
||||||
dockerfile: Dockerfile.openvpn
|
dockerfile: Dockerfile.openvpn
|
||||||
|
context: .
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user