1
0
Fork 0
mirror of synced 2024-06-25 18:31:10 -04:00

Compare commits

...

15 commits

Author SHA1 Message Date
Sprait 0c881c81e7
update start.sh
using compose v2
https://docs.docker.com/compose/migrate/#docker-compose-vs-docker-compose
2023-12-12 09:58:55 +03:00
Dmitry Shurupov 699cddc908
Fix the announcement blog link in README.md 2023-10-25 11:57:38 +07:00
Sprait c83c581e21
fix revoke user (#243)
* fix reuse argument multiple times

* replace whitespace with tab
2023-09-12 11:05:28 +03:00
Sprait 35f76ec3b6
Update actions build binary (#240)
* disable-386-build

* update versison go-release-action
2023-09-11 09:18:48 +03:00
Sprait 8a35f70364
add variable for prometheus datasource to dashboard (#239) 2023-09-08 17:45:34 +03:00
Sprait 4981dcb919
Multi-platform build (#234)
* add multi-platform build
2023-09-04 19:24:13 +03:00
Sprait dbc48ef3f1
Merge pull request #92 from strnk/master
listen.base-url parameter to support reverse-proxy setups
2023-08-22 13:40:46 +03:00
vitaliy-sn f4d0212bfc
Merge pull request #199 from flant/update-user-list-in-ha-mode
Fix update user list for kubernetes.secrets backend
2023-04-27 16:04:00 +03:00
Vitaliy Snurnitsin 9024405232 Force update user list for kubernetes.secrets backend
Signed-off-by: Vitaliy Snurnitsin <vitaliy.snurnitsin@flant.com>
2023-04-27 15:18:29 +03:00
Christophe Huriaux dfe2f3d756
Merge branch 'master' into master 2022-11-02 14:29:22 +01:00
Ilya Sosnovsky a973b88463
Update README.md
Fix notes due #165 #166
2022-11-02 14:41:30 +03:00
Ilya Sosnovsky 3f3976ff7a Add dashboard example 2022-09-06 09:55:02 +03:00
strnk 0ee9be5744 Fix certs and ccd slave download API endpoints 2021-12-07 16:12:57 +01:00
strnk f73626dd7b Add configuration parameter for the easyrsa script path 2021-12-07 15:57:31 +01:00
strnk 633ad79d6a Add base URL configuration to the webserver to support reverse-proxy setups 2021-12-07 15:44:52 +01:00
10 changed files with 1069 additions and 65 deletions

View file

@ -12,19 +12,24 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
fetch-depth: 0 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 - name: Push openvpn image to Docker Hub
uses: docker/build-push-action@v1 uses: docker/build-push-action@v4
with: with:
username: ${{ secrets.DOCKER_USER }} tags: flant/ovpn-admin:openvpn-latest
password: ${{ secrets.DOCKER_PASS }} platforms: linux/amd64,linux/arm64,linux/arm
repository: flant/ovpn-admin file: Dockerfile.openvpn
tags: openvpn-latest push: true
dockerfile: Dockerfile.openvpn
- name: Push ovpn-admin image to Docker Hub - name: Push ovpn-admin image to Docker Hub
uses: docker/build-push-action@v1 uses: docker/build-push-action@v4
with: with:
username: ${{ secrets.DOCKER_USER }} tags: flant/ovpn-admin:latest
password: ${{ secrets.DOCKER_PASS }} platforms: linux/amd64,linux/arm64,linux/arm
repository: flant/ovpn-admin file: Dockerfile
tags: latest push: true
dockerfile: Dockerfile

View file

@ -16,19 +16,24 @@ jobs:
- name: Get the version - name: Get the version
id: get_version id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- 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 - name: Push openvpn image to Docker Hub
uses: docker/build-push-action@v1 uses: docker/build-push-action@v4
with: with:
username: ${{ secrets.DOCKER_USER }} tags: flant/ovpn-admin:openvpn-${{ steps.get_version.outputs.VERSION }}
password: ${{ secrets.DOCKER_PASS }} platforms: linux/amd64,linux/arm64,linux/arm
repository: flant/ovpn-admin file: Dockerfile.openvpn
tags: openvpn-${{ steps.get_version.outputs.VERSION }} push: true
dockerfile: Dockerfile.openvpn
- name: Push ovpn-admin image to Docker Hub - name: Push ovpn-admin image to Docker Hub
uses: docker/build-push-action@v1 uses: docker/build-push-action@v4
with: with:
username: ${{ secrets.DOCKER_USER }} tags: flant/ovpn-admin:${{ steps.get_version.outputs.VERSION }}
password: ${{ secrets.DOCKER_PASS }} platforms: linux/amd64,linux/arm64,linux/arm
repository: flant/ovpn-admin file: Dockerfile
tags: ${{ steps.get_version.outputs.VERSION }} push: true
dockerfile: Dockerfile

View file

@ -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.28 uses: wangyoucao577/go-release-action@v1.40
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
goversion: 1.17 goversion: 1.17

View file

@ -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.28 uses: wangyoucao577/go-release-action@v1.40
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
goversion: 1.17 goversion: 1.17

View file

@ -1,17 +1,20 @@
FROM node:16-alpine3.15 AS frontend-builder FROM node:16-alpine3.15 AS frontend-builder
COPY frontend/ /app COPY frontend/ /app
RUN 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
RUN cd /app && packr2 && env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -a -tags netgo -ldflags '-linkmode external -extldflags -static -s -w' -o ovpn-admin && packr2 clean 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
FROM alpine:3.16 FROM alpine:3.16
WORKDIR /app WORKDIR /app
COPY --from=backend-builder /app/ovpn-admin /app COPY --from=backend-builder /app/ovpn-admin /app
ARG TARGETARCH
RUN apk add --update bash easy-rsa openssl openvpn coreutils && \ RUN apk add --update bash easy-rsa openssl openvpn coreutils && \
ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \ ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \
wget https://github.com/pashcovich/openvpn-user/releases/download/v1.0.4/openvpn-user-linux-amd64.tar.gz -O - | tar xz -C /usr/local/bin && \ wget https://github.com/pashcovich/openvpn-user/releases/download/v1.0.4/openvpn-user-linux-${TARGETARCH}.tar.gz -O - | tar xz -C /usr/local/bin && \
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/* rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
RUN if [ -f "/usr/local/bin/openvpn-user-${TARGETARCH}" ]; then ln -s /usr/local/bin/openvpn-user-${TARGETARCH} /usr/local/bin/openvpn-user; fi

View file

@ -1,7 +1,9 @@
FROM alpine:3.16 FROM alpine:3.16
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 && \
wget https://github.com/pashcovich/openvpn-user/releases/download/v1.0.4/openvpn-user-linux-amd64.tar.gz -O - | tar xz -C /usr/local/bin && \ wget https://github.com/pashcovich/openvpn-user/releases/download/v1.0.4/openvpn-user-linux-${TARGETARCH}.tar.gz -O - | tar xz -C /usr/local/bin && \
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/* rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
RUN if [ -f "/usr/local/bin/openvpn-user-${TARGETARCH}" ]; then ln -s /usr/local/bin/openvpn-user-${TARGETARCH} /usr/local/bin/openvpn-user; fi
COPY setup/ /etc/openvpn/setup COPY setup/ /etc/openvpn/setup
RUN chmod +x /etc/openvpn/setup/configure.sh RUN chmod +x /etc/openvpn/setup/configure.sh

View file

@ -2,7 +2,7 @@
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! Originally created in [Flant](https://flant.com/) for internal needs & used for years, then updated to be more modern and [publicly released](https://medium.com/flant-com/introducing-ovpn-admin-a-web-interface-to-manage-openvpn-users-d81705ad8f23) 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.*
@ -76,7 +76,8 @@ You can also download and use prebuilt binaries from the [releases](https://gith
* master-replica synchronization does not work with `--storage.backend=kubernetes.secrets` - **WIP** * master-replica synchronization does not work with `--storage.backend=kubernetes.secrets` - **WIP**
* additional password authentication does not work with `--storage.backend=kubernetes.secrets` - **WIP** * additional password authentication does not work with `--storage.backend=kubernetes.secrets` - **WIP**
* 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 * 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 only with Openvpn-server versions 2.4 and 2. * tested only with Openvpn-server versions 2.4 and 2.5 with only tls-auth mode
* not tested with EasyRsa version > 3.0.8
* status of users connections update every 28 second(*no need to ask why =)*) * status of users connections update every 28 second(*no need to ask why =)*)
## Usage ## Usage
@ -93,6 +94,9 @@ Flags:
--listen.port="8080" port for ovpn-admin --listen.port="8080" port for ovpn-admin
(or OVPN_LISTEN_PORT) (or OVPN_LISTEN_PORT)
--listen.base-url="/" base URL for ovpn-admin web files
(or $OVPN_LISTEN_BASE_URL)
--role="master" server role, master or slave --role="master" server role, master or slave
(or OVPN_ROLE) (or OVPN_ROLE)

974
dashboard/ovpn-admin.json Normal file
View file

@ -0,0 +1,974 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 54,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 1,
"mappings": [],
"thresholds": {
"mode": "percentage",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 10
}
]
},
"unit": "d"
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 7,
"x": 5,
"y": 0
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.5.2",
"targets": [
{
"expr": "ovpn_server_cert_expire",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"title": "Server cert valid time",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 1,
"mappings": [],
"thresholds": {
"mode": "percentage",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 10
}
]
},
"unit": "d"
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 7,
"x": 12,
"y": 0
},
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.5.2",
"targets": [
{
"expr": "ovpn_server_ca_cert_expire",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"title": "Server CA cert valid time",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "semi-dark-orange",
"value": 200
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 0,
"y": 5
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.5.2",
"targets": [
{
"expr": "ovpn_clients_total",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"title": "Total clients",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 6,
"y": 5
},
"id": 5,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.5.2",
"targets": [
{
"expr": "ovpn_clients_connected",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"title": "Connected clients",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "semi-dark-orange",
"value": 10
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 5
},
"id": 7,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.5.13",
"targets": [
{
"expr": "ovpn_clients_expired",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"title": "Revoked clients",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 5
},
"id": 6,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "8.5.2",
"targets": [
{
"expr": "ovpn_clients_expired",
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"title": "Expired clients",
"type": "stat"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"links": []
},
"overrides": []
},
"fill": 2,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 10
},
"hiddenSeries": false,
"id": 9,
"legend": {
"avg": false,
"current": false,
"hideEmpty": true,
"max": false,
"min": false,
"show": false,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "8.5.2",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "ovpn_client_bytes_received",
"interval": "",
"legendFormat": "{{ client }}",
"refId": "A"
}
],
"thresholds": [],
"timeRegions": [],
"title": "Сlient bytes received",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"format": "decbytes",
"logBase": 1,
"show": true
},
{
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"links": []
},
"overrides": []
},
"fill": 2,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 10
},
"hiddenSeries": false,
"id": 10,
"legend": {
"avg": false,
"current": false,
"hideEmpty": true,
"max": false,
"min": false,
"show": false,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "8.5.2",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "ovpn_client_bytes_sent",
"interval": "",
"legendFormat": "{{ client }}",
"refId": "A"
}
],
"thresholds": [],
"timeRegions": [],
"title": "Сlient bytes sent",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"format": "decbytes",
"logBase": 1,
"show": true
},
{
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 18
},
"hiddenSeries": false,
"id": 16,
"legend": {
"avg": false,
"current": false,
"hideEmpty": true,
"max": false,
"min": false,
"show": false,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "8.5.2",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "rate(ovpn_client_bytes_received[1m])",
"interval": "",
"legendFormat": "{{ client }}",
"refId": "A"
}
],
"thresholds": [],
"timeRegions": [],
"title": "Clients bytes received rate",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:93",
"format": "Bps",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:94",
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"links": []
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 18
},
"hiddenSeries": false,
"id": 17,
"legend": {
"avg": false,
"current": false,
"hideEmpty": true,
"max": false,
"min": false,
"show": false,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "8.5.2",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "rate(ovpn_client_bytes_sent[1m])",
"interval": "",
"legendFormat": "{{ client }}",
"refId": "A"
}
],
"thresholds": [],
"timeRegions": [],
"title": "Client bytes sent rate ",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "time",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:174",
"format": "Bps",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:175",
"format": "short",
"logBase": 1,
"show": false
}
],
"yaxis": {
"align": false
}
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"description": "value show last connection check time",
"fieldConfig": {
"defaults": {
"custom": {
"align": "center",
"displayMode": "auto",
"width": 20
},
"mappings": [],
"noValue": "Currently there are no connections",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
}
]
},
"unit": "dateTimeAsIso"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 26
},
"id": 12,
"maxDataPoints": 1,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
}
},
"pluginVersion": "7.0.6",
"targets": [
{
"expr": "ovpn_client_connection_info * 1000",
"format": "time_series",
"interval": "",
"legendFormat": "{{ client }}-{{ip}}",
"refId": "A"
}
],
"title": "Connection info",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"description": "value shows when connection was started",
"fieldConfig": {
"defaults": {
"custom": {
"align": "center",
"displayMode": "auto",
"width": 20
},
"mappings": [],
"noValue": "Currently there are no connections",
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
}
]
},
"unit": "dateTimeAsIso"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 26
},
"id": 13,
"maxDataPoints": 1,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
}
},
"pluginVersion": "7.0.6",
"targets": [
{
"expr": "ovpn_client_connection_from * 1000",
"format": "time_series",
"interval": "",
"legendFormat": "{{ client }}-{{ip}}",
"refId": "A"
}
],
"title": "Connection from",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "$ds_prometheus"
},
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green"
},
{
"color": "red",
"value": 7
},
{
"color": "dark-orange",
"value": 14
},
{
"color": "#EAB839",
"value": 30
},
{
"color": "green",
"value": 31
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 14,
"w": 24,
"x": 0,
"y": 34
},
"id": 19,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "center",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
}
},
"pluginVersion": "7.0.6",
"targets": [
{
"expr": "ovpn_client_cert_expire ",
"format": "time_series",
"interval": "",
"legendFormat": "{{ client }}",
"refId": "A"
}
],
"title": "Client cert valid days",
"type": "stat"
}
],
"refresh": false,
"schemaVersion": 36,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "default",
"value": "default"
},
"hide": 0,
"includeAll": false,
"multi": false,
"label": "Prometheus",
"name": "ds_prometheus",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-15m",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Ovpn-Admin",
"uid": "Z7qmFI0Gk",
"version": 1,
"weekStart": ""
}

73
main.go
View file

@ -36,12 +36,12 @@ import (
const ( const (
usernameRegexp = `^([a-zA-Z0-9_.-@])+$` usernameRegexp = `^([a-zA-Z0-9_.-@])+$`
passwordMinLength = 6 passwordMinLength = 6
downloadCertsApiUrl = "/api/data/certs/download"
downloadCcdApiUrl = "/api/data/ccd/download"
certsArchiveFileName = "certs.tar.gz" certsArchiveFileName = "certs.tar.gz"
ccdArchiveFileName = "ccd.tar.gz" ccdArchiveFileName = "ccd.tar.gz"
indexTxtDateLayout = "060102150405Z" indexTxtDateLayout = "060102150405Z"
stringDateFormat = "2006-01-02 15:04:05" stringDateFormat = "2006-01-02 15:04:05"
downloadCertsApiUrl = "api/data/certs/download"
downloadCcdApiUrl = "api/data/ccd/download"
kubeNamespaceFilePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" kubeNamespaceFilePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
) )
@ -49,6 +49,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()
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()
@ -63,6 +64,7 @@ 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()
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()
@ -248,6 +250,15 @@ type clientStatus struct {
func (oAdmin *OvpnAdmin) userListHandler(w http.ResponseWriter, r *http.Request) { func (oAdmin *OvpnAdmin) userListHandler(w http.ResponseWriter, r *http.Request) {
log.Info(r.RemoteAddr, " ", r.RequestURI) log.Info(r.RemoteAddr, " ", r.RequestURI)
if *storageBackend == "kubernetes.secrets" {
err := app.updateIndexTxtOnDisk()
if err != nil {
log.Errorln(err)
}
oAdmin.clients = oAdmin.usersList()
}
usersList, _ := json.Marshal(oAdmin.clients) usersList, _ := json.Marshal(oAdmin.clients)
fmt.Fprintf(w, "%s", usersList) fmt.Fprintf(w, "%s", usersList)
} }
@ -546,32 +557,32 @@ func main() {
staticBox := packr.New("static", "./frontend/static") staticBox := packr.New("static", "./frontend/static")
static := CacheControlWrapper(http.FileServer(staticBox)) static := CacheControlWrapper(http.FileServer(staticBox))
http.Handle("/", static) http.Handle(*listenBaseUrl, http.StripPrefix(strings.TrimRight(*listenBaseUrl, "/"), static))
http.HandleFunc("/api/server/settings", ovpnAdmin.serverSettingsHandler) http.HandleFunc(*listenBaseUrl + "api/server/settings", ovpnAdmin.serverSettingsHandler)
http.HandleFunc("/api/users/list", ovpnAdmin.userListHandler) http.HandleFunc(*listenBaseUrl + "api/users/list", ovpnAdmin.userListHandler)
http.HandleFunc("/api/user/create", ovpnAdmin.userCreateHandler) http.HandleFunc(*listenBaseUrl + "api/user/create", ovpnAdmin.userCreateHandler)
http.HandleFunc("/api/user/change-password", ovpnAdmin.userChangePasswordHandler) http.HandleFunc(*listenBaseUrl + "api/user/change-password", ovpnAdmin.userChangePasswordHandler)
http.HandleFunc("/api/user/rotate", ovpnAdmin.userRotateHandler) http.HandleFunc(*listenBaseUrl + "api/user/rotate", ovpnAdmin.userRotateHandler)
http.HandleFunc("/api/user/delete", ovpnAdmin.userDeleteHandler) http.HandleFunc(*listenBaseUrl + "api/user/delete", ovpnAdmin.userDeleteHandler)
http.HandleFunc("/api/user/revoke", ovpnAdmin.userRevokeHandler) http.HandleFunc(*listenBaseUrl + "api/user/revoke", ovpnAdmin.userRevokeHandler)
http.HandleFunc("/api/user/unrevoke", ovpnAdmin.userUnrevokeHandler) http.HandleFunc(*listenBaseUrl + "api/user/unrevoke", ovpnAdmin.userUnrevokeHandler)
http.HandleFunc("/api/user/config/show", ovpnAdmin.userShowConfigHandler) http.HandleFunc(*listenBaseUrl + "api/user/config/show", ovpnAdmin.userShowConfigHandler)
http.HandleFunc("/api/user/disconnect", ovpnAdmin.userDisconnectHandler) http.HandleFunc(*listenBaseUrl + "api/user/disconnect", ovpnAdmin.userDisconnectHandler)
http.HandleFunc("/api/user/statistic", ovpnAdmin.userStatisticHandler) http.HandleFunc(*listenBaseUrl + "api/user/statistic", ovpnAdmin.userStatisticHandler)
http.HandleFunc("/api/user/ccd", ovpnAdmin.userShowCcdHandler) http.HandleFunc(*listenBaseUrl + "api/user/ccd", ovpnAdmin.userShowCcdHandler)
http.HandleFunc("/api/user/ccd/apply", ovpnAdmin.userApplyCcdHandler) http.HandleFunc(*listenBaseUrl + "api/user/ccd/apply", ovpnAdmin.userApplyCcdHandler)
http.HandleFunc("/api/sync/last/try", ovpnAdmin.lastSyncTimeHandler) http.HandleFunc(*listenBaseUrl + "api/sync/last/try", ovpnAdmin.lastSyncTimeHandler)
http.HandleFunc("/api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler) http.HandleFunc(*listenBaseUrl + "api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler)
http.HandleFunc(downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler) http.HandleFunc(*listenBaseUrl + downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler)
http.HandleFunc(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("/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")
}) })
log.Printf("Bind: http://%s:%s", *listenHost, *listenPort) log.Printf("Bind: http://%s:%s%s", *listenHost, *listenPort, *listenBaseUrl)
log.Fatal(http.ListenAndServe(*listenHost+":"+*listenPort, nil)) log.Fatal(http.ListenAndServe(*listenHost+":"+*listenPort, nil))
} }
@ -843,7 +854,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
@ -974,7 +985,7 @@ func (oAdmin *OvpnAdmin) userCreate(username, password string) (bool, string) {
log.Error(err) log.Error(err)
} }
} else { } else {
o := runBash(fmt.Sprintf("cd %s && easyrsa build-client-full %s nopass 1>/dev/null", *easyrsaDirPath, username)) o := runBash(fmt.Sprintf("cd %s && %s build-client-full %s nopass 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath, username))
log.Debug(o) log.Debug(o)
} }
@ -993,7 +1004,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 {
@ -1037,7 +1048,7 @@ 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 && easyrsa gen-crl 1>/dev/null", *easyrsaDirPath, 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)
} }
@ -1101,7 +1112,7 @@ func (oAdmin *OvpnAdmin) userUnrevoke(username string) (error, string) {
log.Error(err) log.Error(err)
} }
_ = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl 1>/dev/null", *easyrsaDirPath)) _ = 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))
@ -1192,7 +1203,7 @@ func (oAdmin *OvpnAdmin) userRotate(username, newPassword string) (error, string
log.Error(err) log.Error(err)
} }
_ = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl 1>/dev/null", *easyrsaDirPath)) _ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null", *easyrsaDirPath, *easyrsaBinPath))
} }
crlFix() crlFix()
oAdmin.clients = oAdmin.usersList() oAdmin.clients = oAdmin.usersList()
@ -1224,7 +1235,7 @@ func (oAdmin *OvpnAdmin) userDelete(username string) (error, string) {
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
_ = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl 1>/dev/null ", *easyrsaDirPath)) _ = runBash(fmt.Sprintf("cd %s && %s gen-crl 1>/dev/null ", *easyrsaDirPath, *easyrsaBinPath))
} }
crlFix() crlFix()
oAdmin.clients = oAdmin.usersList() oAdmin.clients = oAdmin.usersList()
@ -1428,7 +1439,7 @@ func (oAdmin *OvpnAdmin) downloadCerts() bool {
} }
} }
err := fDownload(certsArchivePath, *masterHost+downloadCertsApiUrl+"?token="+oAdmin.masterSyncToken, oAdmin.masterHostBasicAuth) err := fDownload(certsArchivePath, *masterHost+*listenBaseUrl+downloadCertsApiUrl+"?token="+oAdmin.masterSyncToken, oAdmin.masterHostBasicAuth)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return false return false
@ -1445,7 +1456,7 @@ func (oAdmin *OvpnAdmin) downloadCcd() bool {
} }
} }
err := fDownload(ccdArchivePath, *masterHost+downloadCcdApiUrl+"?token="+oAdmin.masterSyncToken, oAdmin.masterHostBasicAuth) err := fDownload(ccdArchivePath, *masterHost+*listenBaseUrl+downloadCcdApiUrl+"?token="+oAdmin.masterSyncToken, oAdmin.masterHostBasicAuth)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return false return false

View file

@ -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