Merge pull request #1 from pashcovich/draft-password-auth
Additional password auth Multiple mgmt interface usgae Layout changes Small fixes
This commit is contained in:
commit
3db757659a
16 changed files with 367 additions and 228 deletions
|
@ -16,3 +16,6 @@ frontend/node_modules
|
|||
openvpn-web-ui
|
||||
openvpn-ui
|
||||
openvpn-admin
|
||||
|
||||
docker-compose.yaml
|
||||
docker-compose-slave.yaml
|
11
Dockerfile
11
Dockerfile
|
@ -1,19 +1,18 @@
|
|||
FROM golang:1.14.2-alpine3.11 AS backend-builder
|
||||
FROM golang:1.14.2-buster AS backend-builder
|
||||
COPY . /app
|
||||
#RUN apk --no-cache add build-base git gcc
|
||||
RUN cd /app && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags='-extldflags "-static" -s -w' -o openvpn-admin
|
||||
RUN cd /app && env CGO_ENABLED=./1 GOOS=linux GOARCH=amd64 go build -ldflags='-linkmode external -extldflags "-static" -s -w' -o openvpn-admin
|
||||
|
||||
FROM node:14.2-alpine3.11 AS frontend-builder
|
||||
COPY frontend/ /app
|
||||
RUN cd /app && npm install && npm run build
|
||||
|
||||
FROM alpine:3.11
|
||||
FROM alpine:3.13
|
||||
WORKDIR /app
|
||||
COPY --from=backend-builder /app/openvpn-admin /app
|
||||
COPY --from=frontend-builder /app/static /app/static
|
||||
COPY client.conf.tpl /app/client.conf.tpl
|
||||
COPY ccd.tpl /app/ccd.tpl
|
||||
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing/" >> /etc/apk/repositories && \
|
||||
apk add --update bash easy-rsa && \
|
||||
RUN apk add --update bash easy-rsa && \
|
||||
ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \
|
||||
wget https://github.com/pashcovich/openvpn-user/releases/download/v1.0.3-rc.1/openvpn-user-linux-amd64.tar.gz -O - | tar xz -C /usr/local/bin && \
|
||||
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
FROM alpine:3.11
|
||||
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing/" >> /etc/apk/repositories && \
|
||||
apk add --update bash openvpn easy-rsa && \
|
||||
FROM alpine:3.13
|
||||
RUN apk add --update bash openvpn easy-rsa && \
|
||||
ln -s /usr/share/easy-rsa/easyrsa /usr/local/bin && \
|
||||
wget https://github.com/pashcovich/openvpn-user/releases/download/v1.0.3-rc.1/openvpn-user-linux-amd64.tar.gz -O - | tar xz -C /usr/local/bin && \
|
||||
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
|
||||
COPY .werffiles /etc/openvpn/setup
|
||||
COPY setup/ /etc/openvpn/setup
|
||||
RUN chmod +x /etc/openvpn/setup/configure.sh
|
||||
|
|
2
build.sh
2
build.sh
|
@ -1,3 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags='-extldflags "-static" -s -w' -o openvpn-admin
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "-linkmode external -extldflags -static -s -w" -o openvpn-admin
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{- range $server := .Hosts }}
|
||||
remote {{ $server.Host }} {{ $server.Port }} tcp
|
||||
remote {{ $server.Host }} {{ $server.Port }} {{ $server.Protocol }}
|
||||
{{- end }}
|
||||
|
||||
verb 4
|
||||
|
@ -11,10 +11,19 @@ key-direction 1
|
|||
#redirect-gateway def1
|
||||
tls-client
|
||||
remote-cert-tls server
|
||||
# for update resolv.conf on ubuntu
|
||||
#script-security 2 system
|
||||
# uncomment needed below lines for use with linux
|
||||
#script-security 2
|
||||
# if use use resolved
|
||||
#up /etc/openvpn/update-resolv-conf
|
||||
#down /etc/openvpn/update-resolv-conf
|
||||
# if you use systemd-resolved first install and openvpn-systemd-resolved package
|
||||
#up /etc/openvpn/update-systemd-resolved
|
||||
#down /etc/openvpn/update-systemd-resolved
|
||||
|
||||
{{- if .PasswdAuth }}
|
||||
auth-user-pass
|
||||
{{- end }}
|
||||
|
||||
<cert>
|
||||
{{ .Cert -}}
|
||||
</cert>
|
||||
|
|
|
@ -8,6 +8,7 @@ services:
|
|||
image: openvpn:local
|
||||
command: /etc/openvpn/setup/configure.sh
|
||||
environment:
|
||||
- OPVN_PASSWD_AUTH=true
|
||||
- OPVN_ROLE=slave
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
|
@ -21,7 +22,7 @@ services:
|
|||
build:
|
||||
context: .
|
||||
image: openvpn-admin:local
|
||||
command: /app/openvpn-admin --debug --ovpn.network="172.16.100.0/22" --master.sync-token="TOKEN" --master.host="http://172.20.0.1:8080" --role="slave" --ovpn.host="127.0.0.1:7744" --ovpn.host="127.0.0.1:7778"
|
||||
command: /app/openvpn-admin --debug --ovpn.network="172.16.100.0/22" --master.sync-token="TOKEN" --master.host="http://172.20.0.1:8080" --role="slave" --ovpn.server="127.0.0.1:7744" --ovpn.server="127.0.0.1:7778" --auth.password
|
||||
environment:
|
||||
- OPVN_SLAVE=1
|
||||
network_mode: service:openvpn
|
||||
|
|
|
@ -7,6 +7,8 @@ services:
|
|||
dockerfile: Dockerfile.openvpn
|
||||
image: openvpn:local
|
||||
command: /etc/openvpn/setup/configure.sh
|
||||
environment:
|
||||
- OPVN_PASSWD_AUTH=true
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
|
@ -19,7 +21,7 @@ services:
|
|||
build:
|
||||
context: .
|
||||
image: openvpn-admin:local
|
||||
command: /app/openvpn-admin --debug --ovpn.network="172.16.100.0/22" --master.sync-token="TOKEN"
|
||||
command: /app/openvpn-admin --debug --ovpn.network="172.16.100.0/22" --master.sync-token="TOKEN" --auth.password
|
||||
network_mode: service:openvpn
|
||||
volumes:
|
||||
- ./easyrsa_master:/mnt/easyrsa
|
||||
|
|
5
frontend/package-lock.json
generated
5
frontend/package-lock.json
generated
|
@ -7838,6 +7838,11 @@
|
|||
"vue-style-loader": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"vue-notification": {
|
||||
"version": "1.3.20",
|
||||
"resolved": "https://registry.npmjs.org/vue-notification/-/vue-notification-1.3.20.tgz",
|
||||
"integrity": "sha512-vPj67Ah72p8xvtyVE8emfadqVWguOScAjt6OJDEUdcW5hW189NsqvfkOrctxHUUO9UYl9cTbIkzAEcPnHu+zBQ=="
|
||||
},
|
||||
"vue-style-loader": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
"vue": "^2.6.12",
|
||||
"vue-clipboard2": "^0.2.1",
|
||||
"vue-cookies": "^1.7.4",
|
||||
"vue-good-table": "^2.21.1"
|
||||
"vue-good-table": "^2.21.1",
|
||||
"vue-notification": "^1.3.20"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
|
|
@ -3,12 +3,14 @@ import axios from 'axios';
|
|||
import VueCookies from 'vue-cookies'
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
import VueGoodTablePlugin from 'vue-good-table'
|
||||
import Notifications from 'vue-notification'
|
||||
|
||||
import 'vue-good-table/dist/vue-good-table.css'
|
||||
|
||||
Vue.use(VueClipboard)
|
||||
Vue.use(VueGoodTablePlugin)
|
||||
Vue.use(VueCookies)
|
||||
Vue.use(Notifications)
|
||||
|
||||
var axios_cfg = function(url, data='', type='form') {
|
||||
if (data == '') {
|
||||
|
@ -54,6 +56,11 @@ new Vue({
|
|||
field: 'AccountStatus',
|
||||
filterable: true,
|
||||
},
|
||||
{
|
||||
label: 'Connection Server',
|
||||
field: 'ConnectionServer',
|
||||
filterable: true,
|
||||
},
|
||||
{
|
||||
label: 'Expiration Date',
|
||||
field: 'ExpirationDate',
|
||||
|
@ -84,39 +91,52 @@ new Vue({
|
|||
],
|
||||
rows: [],
|
||||
actions: [
|
||||
{
|
||||
name: 'u-change-password',
|
||||
label: 'Change password',
|
||||
class: 'btn-warning',
|
||||
showWhenStatus: 'Active',
|
||||
showForServerRole: ['master']
|
||||
},
|
||||
{
|
||||
name: 'u-revoke',
|
||||
label: 'Revoke',
|
||||
class: 'btn-warning',
|
||||
showWhenStatus: 'Active',
|
||||
showForServerRole: ['master']
|
||||
},
|
||||
{
|
||||
name: 'u-unrevoke',
|
||||
label: 'Unrevoke',
|
||||
class: 'btn-primary',
|
||||
showWhenStatus: 'Revoked',
|
||||
showForServerRole: ['master']
|
||||
},
|
||||
{
|
||||
name: 'u-show-config',
|
||||
label: 'Show config',
|
||||
showWhenStatus: 'Active',
|
||||
showForServerRole: ['master', 'slave']
|
||||
},
|
||||
// {
|
||||
// name: 'u-show-config',
|
||||
// label: 'Show config',
|
||||
// class: 'btn-primary',
|
||||
// showWhenStatus: 'Active',
|
||||
// showForServerRole: ['master', 'slave']
|
||||
// },
|
||||
{
|
||||
name: 'u-download-config',
|
||||
label: 'Download config',
|
||||
class: 'btn-info',
|
||||
showWhenStatus: 'Active',
|
||||
showForServerRole: ['master', 'slave']
|
||||
},
|
||||
{
|
||||
name: 'u-edit-ccd',
|
||||
label: 'Edit routes',
|
||||
class: 'btn-primary',
|
||||
showWhenStatus: 'Active',
|
||||
showForServerRole: ['master']
|
||||
},
|
||||
{
|
||||
name: 'u-edit-ccd',
|
||||
label: 'Show routes',
|
||||
class: 'btn-primary',
|
||||
showWhenStatus: 'Active',
|
||||
showForServerRole: ['slave']
|
||||
}
|
||||
|
@ -128,11 +148,15 @@ new Vue({
|
|||
lastSync: "unknown",
|
||||
u: {
|
||||
newUserName: '',
|
||||
// newUserPassword: 'nopass',
|
||||
newUserPassword: '',
|
||||
newUserCreateError: '',
|
||||
newPassword: '',
|
||||
passwordChangeStatus: '',
|
||||
passwordChangeMessage: '',
|
||||
modalNewUserVisible: false,
|
||||
modalShowConfigVisible: false,
|
||||
modalShowCcdVisible: false,
|
||||
modalChangePasswordVisible: false,
|
||||
openvpnConfig: '',
|
||||
ccd: {
|
||||
Name: '',
|
||||
|
@ -160,6 +184,7 @@ new Vue({
|
|||
axios.request(axios_cfg('api/user/revoke', data, 'form'))
|
||||
.then(function(response) {
|
||||
_this.getUserData();
|
||||
_this.$notify({title: 'User ' + _this.username + ' revoked!', type: 'warn'})
|
||||
});
|
||||
})
|
||||
_this.$root.$on('u-unrevoke', function () {
|
||||
|
@ -168,6 +193,7 @@ new Vue({
|
|||
axios.request(axios_cfg('api/user/unrevoke', data, 'form'))
|
||||
.then(function(response) {
|
||||
_this.getUserData();
|
||||
_this.$notify({title: 'User ' + _this.username + ' unrevoked!', type: 'success'})
|
||||
});
|
||||
})
|
||||
_this.$root.$on('u-show-config', function () {
|
||||
|
@ -210,6 +236,11 @@ new Vue({
|
|||
console.log(response.data);
|
||||
});
|
||||
})
|
||||
_this.$root.$on('u-change-password', function () {
|
||||
_this.u.modalChangePasswordVisible = true;
|
||||
var data = new URLSearchParams();
|
||||
data.append('username', _this.username);
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
customAddressDisabled: function () {
|
||||
|
@ -218,6 +249,9 @@ new Vue({
|
|||
ccdApplyStatusCssClass: function () {
|
||||
return this.u.ccdApplyStatus == 200 ? "alert-success" : "alert-danger"
|
||||
},
|
||||
passwordChangeStatusCssClass: function () {
|
||||
return this.u.passwordChangeStatus == 200 ? "alert-success" : "alert-danger"
|
||||
},
|
||||
modalNewUserDisplay: function () {
|
||||
return this.u.modalNewUserVisible ? {display: 'flex'} : {}
|
||||
},
|
||||
|
@ -227,6 +261,9 @@ new Vue({
|
|||
modalShowCcdDisplay: function () {
|
||||
return this.u.modalShowCcdVisible ? {display: 'flex'} : {}
|
||||
},
|
||||
modalChangePasswordDisplay: function () {
|
||||
return this.u.modalChangePasswordVisible ? {display: 'flex'} : {}
|
||||
},
|
||||
revokeFilterText: function() {
|
||||
return this.filters.hideRevoked ? "Show revoked" : "Hide revoked"
|
||||
},
|
||||
|
@ -256,6 +293,15 @@ new Vue({
|
|||
_this.rows = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
staticAddrCheckboxOnChange: function() {
|
||||
var staticAddrInput = document.getElementById('static-address');
|
||||
var staticAddrEnable = document.getElementById('enable-static');
|
||||
|
||||
staticAddrInput.disabled = !staticAddrEnable.checked;
|
||||
staticAddrInput.value == "dynamic" ? staticAddrInput.value = "" : staticAddrInput.value = "dynamic";
|
||||
},
|
||||
|
||||
getServerRole: function() {
|
||||
var _this = this;
|
||||
axios.request(axios_cfg('api/server/role'))
|
||||
|
@ -269,6 +315,7 @@ new Vue({
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
createUser: function() {
|
||||
var _this = this;
|
||||
|
||||
|
@ -276,19 +323,23 @@ new Vue({
|
|||
|
||||
var data = new URLSearchParams();
|
||||
data.append('username', _this.u.newUserName);
|
||||
// data.append('password', this.u.newUserPassword);
|
||||
data.append('password', _this.u.newUserPassword);
|
||||
|
||||
axios.request(axios_cfg('api/user/create', data, 'form'))
|
||||
.then(function(response) {
|
||||
_this.getUserData();
|
||||
_this.u.modalNewUserVisible = false;
|
||||
_this.u.newUserName = '';
|
||||
// _this.u.newUserPassword = 'nopass';
|
||||
_this.u.newUserPassword = '';
|
||||
_this.getUserData();
|
||||
_this.$notify({title: 'New user ' + _this.username + ' created', type: 'success'})
|
||||
})
|
||||
.catch(function(error) {
|
||||
_this.u.newUserCreateError = error.response.data;
|
||||
_this.$notify({title: 'New user ' + _this.username + ' creation failed.', type: 'error'})
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
ccdApply: function() {
|
||||
var _this = this;
|
||||
|
||||
|
@ -299,11 +350,38 @@ new Vue({
|
|||
.then(function(response) {
|
||||
_this.u.ccdApplyStatus = 200;
|
||||
_this.u.ccdApplyStatusMessage = response.data;
|
||||
_this.$notify({title: 'Ccd for user ' + _this.username + ' applied', type: 'success'})
|
||||
})
|
||||
.catch(function(error) {
|
||||
_this.u.ccdApplyStatus = error.response.status;
|
||||
_this.u.ccdApplyStatusMessage = error.response.data;
|
||||
_this.$notify({title: 'Ccd for user ' + _this.username + ' apply failed ', type: 'error'})
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
changeUserPassword: function(user) {
|
||||
var _this = this;
|
||||
|
||||
_this.u.passwordChangeMessage = "";
|
||||
|
||||
var data = new URLSearchParams();
|
||||
data.append('username', user);
|
||||
data.append('password', _this.u.newPassword);
|
||||
|
||||
axios.request(axios_cfg('api/user/change-password', data, 'form'))
|
||||
.then(function(response) {
|
||||
_this.u.passwordChangeStatus = 200;
|
||||
_this.u.newPassword = '';
|
||||
_this.getUserData();
|
||||
_this.u.modalChangePasswordVisible = false;
|
||||
_this.$notify({title: 'Password for user ' + _this.username + ' changed!', type: 'success'})
|
||||
})
|
||||
.catch(function(error) {
|
||||
_this.u.passwordChangeStatus = error.response.status;
|
||||
_this.u.passwordChangeMessage = error.response.data.message;
|
||||
_this.$notify({title: 'Changing password for user ' + _this.username + ' failed!', type: 'error'})
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
})
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<template slot="table-row" slot-scope="props">
|
||||
<span v-if="props.column.field == 'actions'">
|
||||
<button
|
||||
class="btn btn-sm btn-success el-square modal-el-margin"
|
||||
class="btn btn-sm el-square modal-el-margin"
|
||||
type="button"
|
||||
:title="action.label"
|
||||
:data-username="props.row.Identity"
|
||||
|
@ -36,6 +36,7 @@
|
|||
:data-text="action.label"
|
||||
@click.left.stop="rowActionFn"
|
||||
v-for="action in actions"
|
||||
v-bind:class="action.class"
|
||||
v-if="action.showWhenStatus == props.row.AccountStatus && action.showForServerRole.includes(serverRole)">
|
||||
{{ action.label }}
|
||||
</button>
|
||||
|
@ -55,7 +56,7 @@
|
|||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="text" class="form-control el-square modal-el-margin" placeholder="Username [_a-zA-Z0-9\.-]" v-model="u.newUserName">
|
||||
<!-- <input type="password" class="form-control el-square modal-el-margin" minlength="6" autocomplete="off" placeholder="Password [_a-zA-Z0-9\.-]" v-model="u.newUserPassword">-->
|
||||
<input type="password" class="form-control el-square modal-el-margin" minlength="6" autocomplete="off" placeholder="Password [_a-zA-Z0-9\.-]" v-model="u.newUserPassword">
|
||||
</div>
|
||||
|
||||
<div class="modal-footer justify-content-center" v-if="u.newUserCreateError.length > 0">
|
||||
|
@ -64,18 +65,41 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success el-square modal-el-margin" v-on:click.stop="createUser();">Create</button>
|
||||
<button type="button" class="btn btn-success el-square modal-el-margin" v-on:click.stop="createUser()">Create</button>
|
||||
<button type="button" class="btn btn-primary el-square d-flex justify-content-sm-end modal-el-margin" v-on:click.stop="u.newUserName='';u.newUserPassword='nopass';u.modalNewUserVisible=false">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-wrapper" v-if="u.modalChangePasswordVisible" v-bind:style="modalChangePasswordDisplay">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>Change password for: <strong>{{ username }}</strong></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="password" class="form-control el-square modal-el-margin" minlength="6" autocomplete="off" placeholder="Password [_a-zA-Z0-9\.-]" v-model="u.newPassword">
|
||||
</div>
|
||||
|
||||
<div class="modal-footer justify-content-center" v-if="u.passwordChangeMessage.length > 0">
|
||||
<div class="alert" v-bind:class="passwordChangeStatusCssClass" role="alert" >
|
||||
{{ u.passwordChangeMessage }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-success el-square modal-el-margin" v-on:click.stop="changeUserPassword(username)">Change password</button>
|
||||
<button type="button" class="btn btn-primary el-square d-flex justify-content-sm-end modal-el-margin" v-on:click.stop="u.newPassword='';u.passwordChangeMessage='';u.modalChangePasswordVisible=false">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-wrapper" v-if="u.modalShowConfigVisible" v-bind:style="modalShowConfigDisplay">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>ovpn config for {{ username }}</h4>
|
||||
<h4>ovpn config for: <strong>{{ username }}</strong></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex">
|
||||
|
@ -96,16 +120,18 @@
|
|||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="static-address-label ">Routes table for: <strong>{{ username }}</strong></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="input-group">
|
||||
<h4 class="static-address-label ">Client "{{ username }}" static address</h4>
|
||||
<h5 class="static-address-label ">Static address:</h5>
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<input id="enable-static" type="checkbox" onchange="document.getElementById('staticAddress').disabled=!this.checked;" v-if="serverRole == 'master'" v-bind:checked="customAddressDisabled">
|
||||
<input id="enable-static" type="checkbox" @change="staticAddrCheckboxOnChange()" v-if="serverRole == 'master'" v-bind:checked="!customAddressDisabled">
|
||||
</div>
|
||||
</div>
|
||||
<input id="staticAddress" type="text" class="form-control" v-model="u.ccd.ClientAddress" placeholder="127.0.0.1" v-bind:disabled="customAddressDisabled">
|
||||
<input id="static-address" type="text" class="form-control" v-model="u.ccd.ClientAddress" placeholder="127.0.0.1" v-bind:disabled="customAddressDisabled">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex ">
|
||||
|
@ -167,6 +193,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<notifications position="bottom left" :speed="900" />
|
||||
</div>
|
||||
<script src="dist/build.js"></script>
|
||||
</body>
|
||||
|
|
195
main.go
195
main.go
|
@ -20,7 +20,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
usernameRegexp = `^([a-zA-Z0-9_.-])+$`
|
||||
usernameRegexp = `^([a-zA-Z0-9_.-@])+$`
|
||||
passwordRegexp = `^([a-zA-Z0-9_.-@])+$`
|
||||
passwordMinLength = 6
|
||||
downloadCertsApiUrl = "/api/data/certs/download"
|
||||
downloadCcdApiUrl = "/api/data/ccd/download"
|
||||
certsArchiveFileName = "certs.tar.gz"
|
||||
|
@ -28,10 +30,10 @@ const (
|
|||
indexTxtDateLayout = "060102150405Z"
|
||||
stringDateFormat = "2006-01-02 15:04:05"
|
||||
ovpnStatusDateLayout = "Mon Jan 2 15:04:05 2006"
|
||||
version = "1.5.0"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
listenHost = kingpin.Flag("listen.host","host for openvpn-admin").Default("0.0.0.0").String()
|
||||
listenPort = kingpin.Flag("listen.port","port for openvpn-admin").Default("8080").String()
|
||||
serverRole = kingpin.Flag("role","server role master or slave").Default("master").HintOptions("master", "slave").String()
|
||||
|
@ -40,16 +42,18 @@ var (
|
|||
masterBasicAuthPassword = kingpin.Flag("master.basic-auth.password","password for basic auth on master server url").Default("").String()
|
||||
masterSyncFrequency = kingpin.Flag("master.sync-frequency", "master host data sync frequency in seconds.").Default("600").Int()
|
||||
masterSyncToken = kingpin.Flag("master.sync-token", "master host data sync security token").Default("justasimpleword").PlaceHolder("TOKEN").String()
|
||||
openvpnServer = kingpin.Flag("ovpn.host","host(s) for openvpn server").Default("127.0.0.1:7777").PlaceHolder("HOST:PORT").Strings()
|
||||
openvpnServer = kingpin.Flag("ovpn.server","comma separated addresses for openvpn servers").Default("127.0.0.1:7777").PlaceHolder("HOST:PORT").Strings()
|
||||
openvpnNetwork = kingpin.Flag("ovpn.network","network for openvpn server").Default("172.16.100.0/24").String()
|
||||
mgmtListenHost = kingpin.Flag("mgmt.host","host for openvpn server mgmt interface").Default("127.0.0.1").String()
|
||||
mgmtListenPort = kingpin.Flag("mgmt.port","port for openvpn server mgmt interface").Default("8989").String()
|
||||
mgmtAddress = kingpin.Flag("mgmt","comma separated (alias=address) for openvpn servers mgmt interfaces").Default("main=127.0.0.1:8989").Strings()
|
||||
metricsPath = kingpin.Flag("metrics.path", "URL path for surfacing collected metrics").Default("/metrics").String()
|
||||
easyrsaDirPath = kingpin.Flag("easyrsa.path", "path to easyrsa dir").Default("/mnt/easyrsa").String()
|
||||
indexTxtPath = kingpin.Flag("easyrsa.index-path", "path to easyrsa index file.").Default("/mnt/easyrsa/pki/index.txt").String()
|
||||
ccdDir = kingpin.Flag("ccd.path", "path to client-config-dir").Default("/mnt/ccd").String()
|
||||
authByPassword = kingpin.Flag("auth.password", "Enable additional password authorization.").Default("false").Bool()
|
||||
authDatabase = kingpin.Flag("auth.db", "Database path fort password authorization.").Default("/mnt/easyrsa/pki/users.db").String()
|
||||
staticPath = kingpin.Flag("static.path", "path to static dir").Default("./static").String()
|
||||
debug = kingpin.Flag("debug", "Enable debug mode.").Default("false").Bool()
|
||||
verbose = kingpin.Flag("verbose", "Enable verbose mode.").Default("false").Bool()
|
||||
|
||||
certsArchivePath = "/tmp/" + certsArchiveFileName
|
||||
ccdArchivePath = "/tmp/" + ccdArchiveFileName
|
||||
|
@ -103,7 +107,7 @@ var (
|
|||
|
||||
ovpnClientConnectionInfo = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "ovpn_client_connection_info",
|
||||
Help: "openvpn user connection info. ip - assigned address from opvn network. value - last time when connection was refreshed in unix format",
|
||||
Help: "openvpn user connection info. ip - assigned address from ovpn network. value - last time when connection was refreshed in unix format",
|
||||
},
|
||||
[]string{"client", "ip"},
|
||||
)
|
||||
|
@ -140,11 +144,14 @@ type OpenvpnAdmin struct {
|
|||
clients []OpenvpnClient
|
||||
activeClients []clientStatus
|
||||
promRegistry *prometheus.Registry
|
||||
mgmtInterfaces map[string]string
|
||||
}
|
||||
|
||||
|
||||
type OpenvpnServer struct {
|
||||
Host string
|
||||
Port string
|
||||
Protocol string
|
||||
}
|
||||
|
||||
type openvpnClientConfig struct {
|
||||
|
@ -153,6 +160,7 @@ type openvpnClientConfig struct {
|
|||
Cert string
|
||||
Key string
|
||||
TLS string
|
||||
PasswdAuth bool
|
||||
}
|
||||
|
||||
type OpenvpnClient struct {
|
||||
|
@ -161,6 +169,7 @@ type OpenvpnClient struct {
|
|||
ExpirationDate string `json:"ExpirationDate"`
|
||||
RevocationDate string `json:"RevocationDate"`
|
||||
ConnectionStatus string `json:"ConnectionStatus"`
|
||||
ConnectionServer string `json:"ConnectionServer"`
|
||||
}
|
||||
|
||||
type ccdRoute struct {
|
||||
|
@ -195,6 +204,7 @@ type clientStatus struct {
|
|||
LastRef string
|
||||
ConnectedSinceFormatted string
|
||||
LastRefFormatted string
|
||||
ConnectedTo string
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) userListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -214,7 +224,7 @@ func (oAdmin *OpenvpnAdmin) userCreateHandler(w http.ResponseWriter, r *http.Req
|
|||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
userCreated, userCreateStatus := oAdmin.userCreate(r.FormValue("username"))
|
||||
userCreated, userCreateStatus := oAdmin.userCreate(r.FormValue("username"), r.FormValue("password"))
|
||||
|
||||
if userCreated {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -244,6 +254,25 @@ func (oAdmin *OpenvpnAdmin) userUnrevokeHandler(w http.ResponseWriter, r *http.R
|
|||
fmt.Fprintf(w, "%s", oAdmin.userUnrevoke(r.FormValue("username")))
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) userChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
if *authByPassword {
|
||||
passwordChanged, passwordChangeMessage := oAdmin.userChangePassword(r.FormValue("username"), r.FormValue("password"))
|
||||
if passwordChanged {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, `{"status":"ok", "message": "%s"}`, passwordChangeMessage)
|
||||
return
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, `{"status":"error", "message": "%s"}`, passwordChangeMessage)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
http.Error(w, `{"status":"error"}`, http.StatusNotImplemented )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) userShowConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
fmt.Fprintf(w, "%s", oAdmin.renderClientConfig(r.FormValue("username")))
|
||||
|
@ -318,7 +347,7 @@ func (oAdmin *OpenvpnAdmin) downloadCertsHandler(w http.ResponseWriter, r *http.
|
|||
http.ServeFile(w,r, certsArchivePath)
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) downloadCddHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func (oAdmin *OpenvpnAdmin) downloadCcdHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if oAdmin.role == "slave" {
|
||||
http.Error(w, `{"status":"error"}`, http.StatusLocked)
|
||||
return
|
||||
|
@ -337,8 +366,10 @@ func (oAdmin *OpenvpnAdmin) downloadCddHandler(w http.ResponseWriter, r *http.Re
|
|||
}
|
||||
|
||||
func main() {
|
||||
kingpin.Version(version)
|
||||
kingpin.Parse()
|
||||
|
||||
|
||||
ovpnAdmin := new(OpenvpnAdmin)
|
||||
ovpnAdmin.lastSyncTime = "unknown"
|
||||
ovpnAdmin.role = *serverRole
|
||||
|
@ -346,6 +377,13 @@ func main() {
|
|||
ovpnAdmin.masterSyncToken = *masterSyncToken
|
||||
ovpnAdmin.promRegistry = prometheus.NewRegistry()
|
||||
|
||||
ovpnAdmin.mgmtInterfaces = make(map[string]string)
|
||||
|
||||
for _, mgmtInterface := range *mgmtAddress {
|
||||
parts := strings.SplitN(mgmtInterface, "=",2)
|
||||
ovpnAdmin.mgmtInterfaces[parts[0]] = parts[len(parts)-1]
|
||||
}
|
||||
|
||||
ovpnAdmin.registerMetrics()
|
||||
ovpnAdmin.setState()
|
||||
|
||||
|
@ -368,6 +406,7 @@ func main() {
|
|||
http.HandleFunc("/api/server/role", ovpnAdmin.serverRoleHandler)
|
||||
http.HandleFunc("/api/users/list", ovpnAdmin.userListHandler)
|
||||
http.HandleFunc("/api/user/create", ovpnAdmin.userCreateHandler)
|
||||
http.HandleFunc("/api/user/change-password", ovpnAdmin.userChangePasswordHandler)
|
||||
http.HandleFunc("/api/user/revoke", ovpnAdmin.userRevokeHandler)
|
||||
http.HandleFunc("/api/user/unrevoke", ovpnAdmin.userUnrevokeHandler)
|
||||
http.HandleFunc("/api/user/config/show", ovpnAdmin.userShowConfigHandler)
|
||||
|
@ -379,7 +418,7 @@ func main() {
|
|||
http.HandleFunc("/api/sync/last/try", ovpnAdmin.lastSyncTimeHandler)
|
||||
http.HandleFunc("/api/sync/last/successful", ovpnAdmin.lastSuccessfulSyncTimeHandler)
|
||||
http.HandleFunc(downloadCertsApiUrl, ovpnAdmin.downloadCertsHandler)
|
||||
http.HandleFunc(downloadCcdApiUrl, ovpnAdmin.downloadCddHandler)
|
||||
http.HandleFunc(downloadCcdApiUrl, ovpnAdmin.downloadCcdHandler)
|
||||
|
||||
http.Handle(*metricsPath, promhttp.HandlerFor(ovpnAdmin.promRegistry, promhttp.HandlerOpts{}))
|
||||
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -415,7 +454,7 @@ func (oAdmin *OpenvpnAdmin) setState() {
|
|||
oAdmin.activeClients = oAdmin.mgmtGetActiveClients()
|
||||
oAdmin.clients = oAdmin.usersList()
|
||||
|
||||
ovpnServerCaCertExpire.Set(float64((getOpvnCaCertExpireDate().Unix() - time.Now().Unix()) / 3600 / 24))
|
||||
ovpnServerCaCertExpire.Set(float64((getOvpnCaCertExpireDate().Unix() - time.Now().Unix()) / 3600 / 24))
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) updateState() {
|
||||
|
@ -469,8 +508,8 @@ func (oAdmin *OpenvpnAdmin) renderClientConfig(username string) string {
|
|||
var hosts []OpenvpnServer
|
||||
|
||||
for _, server := range *openvpnServer {
|
||||
parts := strings.SplitN(server, ":",2)
|
||||
hosts = append(hosts, OpenvpnServer{Host: parts[0], Port: parts[1]})
|
||||
parts := strings.SplitN(server, ":",3)
|
||||
hosts = append(hosts, OpenvpnServer{Host: parts[0], Port: parts[1], Protocol: parts[2]})
|
||||
}
|
||||
|
||||
conf := openvpnClientConfig{}
|
||||
|
@ -479,6 +518,7 @@ func (oAdmin *OpenvpnAdmin) renderClientConfig(username string) string {
|
|||
conf.Cert = fRead(*easyrsaDirPath + "/pki/issued/" + username + ".crt")
|
||||
conf.Key = fRead(*easyrsaDirPath + "/pki/private/" + username + ".key")
|
||||
conf.TLS = fRead(*easyrsaDirPath + "/pki/ta.key")
|
||||
conf.PasswdAuth = *authByPassword
|
||||
|
||||
t, _ := template.ParseFiles("client.conf.tpl")
|
||||
var tmp bytes.Buffer
|
||||
|
@ -624,6 +664,14 @@ func validateUsername(username string) bool {
|
|||
return validUsername.MatchString(username)
|
||||
}
|
||||
|
||||
func validatePassword(password string) bool {
|
||||
if len(password) < passwordMinLength {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func checkUserExist(username string) bool {
|
||||
for _, u := range indexTxtParser(fRead(*indexTxtPath)) {
|
||||
if u.DistinguishedName == ("/CN=" + username) {
|
||||
|
@ -663,8 +711,12 @@ func (oAdmin *OpenvpnAdmin) usersList() []OpenvpnClient {
|
|||
expiredCerts += 1
|
||||
}
|
||||
|
||||
if isUserConnected(line.Identity, oAdmin.activeClients) {
|
||||
ovpnClient.ConnectionServer = ""
|
||||
|
||||
userConnected, userConnectedTo := isUserConnected(line.Identity, oAdmin.activeClients)
|
||||
if userConnected {
|
||||
ovpnClient.ConnectionStatus = "Connected"
|
||||
ovpnClient.ConnectionServer = userConnectedTo
|
||||
connectedUsers += 1
|
||||
}
|
||||
|
||||
|
@ -689,16 +741,9 @@ func (oAdmin *OpenvpnAdmin) usersList() []OpenvpnClient {
|
|||
return users
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) userCreate(username string) (bool, string) {
|
||||
func (oAdmin *OpenvpnAdmin) userCreate(username, password string) (bool, string) {
|
||||
ucErr := fmt.Sprintf("User \"%s\" created", username)
|
||||
// TODO: add password for user cert . priority=low
|
||||
if validateUsername(username) == false {
|
||||
ucErr = fmt.Sprintf("Username \"%s\" incorrect, you can use only %s\n", username, usernameRegexp)
|
||||
if *debug {
|
||||
log.Printf("ERROR: userCreate: %s", ucErr)
|
||||
}
|
||||
return false, ucErr
|
||||
}
|
||||
|
||||
if checkUserExist(username) {
|
||||
ucErr = fmt.Sprintf("User \"%s\" already exists\n", username)
|
||||
if *debug {
|
||||
|
@ -706,15 +751,72 @@ func (oAdmin *OpenvpnAdmin) userCreate(username string) (bool, string) {
|
|||
}
|
||||
return false, ucErr
|
||||
}
|
||||
|
||||
if ! validateUsername(username) {
|
||||
ucErr = fmt.Sprintf("Username \"%s\" incorrect, you can use only %s\n", username, usernameRegexp)
|
||||
if *debug {
|
||||
log.Printf("ERROR: userCreate: %s", ucErr)
|
||||
}
|
||||
return false, ucErr
|
||||
}
|
||||
|
||||
if ! validatePassword(password) {
|
||||
ucErr = fmt.Sprintf("Password too short, password length must be greater or equal %d", passwordMinLength)
|
||||
if *debug {
|
||||
log.Printf("ERROR: userCreate: %s\n", ucErr)
|
||||
}
|
||||
return false, ucErr
|
||||
}
|
||||
|
||||
o := runBash(fmt.Sprintf("date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && easyrsa build-client-full %s nopass", *easyrsaDirPath, username))
|
||||
fmt.Println(o)
|
||||
if *debug {
|
||||
|
||||
if *authByPassword {
|
||||
o = runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password))
|
||||
fmt.Println(o)
|
||||
}
|
||||
|
||||
if *verbose {
|
||||
log.Printf("INFO: user created: %s", username)
|
||||
}
|
||||
|
||||
oAdmin.clients = oAdmin.usersList()
|
||||
|
||||
return true, ucErr
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) userChangePassword(username, password string) (bool, string) {
|
||||
|
||||
if checkUserExist(username) {
|
||||
o := runBash(fmt.Sprintf("openvpn-user check --db.path %s --user %s | grep %s | wc -l", *authDatabase, username, username))
|
||||
fmt.Println(o)
|
||||
|
||||
if ! validatePassword(password) {
|
||||
ucpErr := fmt.Sprintf("Password for too short, password length must be greater or equal %d", passwordMinLength)
|
||||
if *debug {
|
||||
log.Printf("ERROR: userChangePassword: %s\n", ucpErr)
|
||||
}
|
||||
return false, ucpErr
|
||||
}
|
||||
|
||||
if strings.TrimSpace(o) == "0" {
|
||||
fmt.Println("Creating user in users.db")
|
||||
o = runBash(fmt.Sprintf("openvpn-user create --db.path %s --user %s --password %s", *authDatabase, username, password))
|
||||
fmt.Println(o)
|
||||
}
|
||||
|
||||
o = runBash(fmt.Sprintf("openvpn-user change-password --db.path %s --user %s --password %s", *authDatabase, username, password))
|
||||
fmt.Println(o)
|
||||
|
||||
if *verbose {
|
||||
log.Printf("INFO: password for user %s was changed", username)
|
||||
}
|
||||
return true, "Password changed"
|
||||
}
|
||||
|
||||
return false, "User does not exist"
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) getUserStatistic(username string) clientStatus {
|
||||
for _, u := range oAdmin.activeClients {
|
||||
if u.CommonName == username {
|
||||
|
@ -728,6 +830,10 @@ func (oAdmin *OpenvpnAdmin) userRevoke(username string) string {
|
|||
if checkUserExist(username) {
|
||||
// check certificate valid flag 'V'
|
||||
o := runBash(fmt.Sprintf("date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && echo yes | easyrsa revoke %s && easyrsa gen-crl", *easyrsaDirPath, username))
|
||||
if *authByPassword {
|
||||
o = runBash(fmt.Sprintf("openvpn-user revoke --db-path %s --user %s", *authDatabase, username))
|
||||
//fmt.Println(o)
|
||||
}
|
||||
crlFix()
|
||||
oAdmin.clients = oAdmin.usersList()
|
||||
return fmt.Sprintln(o)
|
||||
|
@ -757,6 +863,10 @@ func (oAdmin *OpenvpnAdmin) userUnrevoke(username string) string {
|
|||
//fmt.Print(renderIndexTxt(usersFromIndexTxt))
|
||||
o = runBash(fmt.Sprintf("cd %s && easyrsa gen-crl", *easyrsaDirPath))
|
||||
//fmt.Println(o)
|
||||
if *authByPassword {
|
||||
o = runBash(fmt.Sprintf("openvpn-user restore --db-path %s --user %s", *authDatabase, username))
|
||||
//fmt.Println(o)
|
||||
}
|
||||
crlFix()
|
||||
o = ""
|
||||
fmt.Println(o)
|
||||
|
@ -773,11 +883,6 @@ func (oAdmin *OpenvpnAdmin) userUnrevoke(username string) string {
|
|||
return fmt.Sprintf("{\"msg\":\"User \"%s\" not found\"}", username)
|
||||
}
|
||||
|
||||
// TODO: add ability to change password for user cert . priority=low
|
||||
func userChangePassword(username string, newPassword string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) mgmtRead(conn net.Conn) string {
|
||||
buf := make([]byte, 32768)
|
||||
|
@ -786,7 +891,7 @@ func (oAdmin *OpenvpnAdmin) mgmtRead(conn net.Conn) string {
|
|||
return s
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text string) []clientStatus {
|
||||
func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text, serverName string) []clientStatus {
|
||||
var u []clientStatus
|
||||
isClientList := false
|
||||
isRouteTable := false
|
||||
|
@ -814,14 +919,14 @@ func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text string) []clientStatus
|
|||
|
||||
userName := user[0]
|
||||
userAddress := user[1]
|
||||
userBytesRecieved:= user[2]
|
||||
userBytesReceived:= user[2]
|
||||
userBytesSent:= user[3]
|
||||
userConnectedSince := user[4]
|
||||
|
||||
userStatus := clientStatus{CommonName: userName, RealAddress: userAddress, BytesReceived: userBytesRecieved, BytesSent: userBytesSent, ConnectedSince: userConnectedSince}
|
||||
userStatus := clientStatus{CommonName: userName, RealAddress: userAddress, BytesReceived: userBytesReceived, BytesSent: userBytesSent, ConnectedSince: userConnectedSince, ConnectedTo: serverName}
|
||||
u = append(u, userStatus)
|
||||
bytesSent, _ := strconv.Atoi(userBytesSent)
|
||||
bytesReceive, _ := strconv.Atoi(userBytesRecieved)
|
||||
bytesReceive, _ := strconv.Atoi(userBytesReceived)
|
||||
ovpnClientConnectionFrom.WithLabelValues(userName, userAddress).Set(float64(parseDateToUnix(ovpnStatusDateLayout, userConnectedSince)))
|
||||
ovpnClientBytesSent.WithLabelValues(userName).Set(float64(bytesSent))
|
||||
ovpnClientBytesReceived.WithLabelValues(userName).Set(float64(bytesReceive))
|
||||
|
@ -841,10 +946,10 @@ func (oAdmin *OpenvpnAdmin) mgmtConnectedUsersParser(text string) []clientStatus
|
|||
return u
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) mgmtKillUserConnection(username string) {
|
||||
conn, err := net.Dial("tcp", *mgmtListenHost+":"+*mgmtListenPort)
|
||||
func (oAdmin *OpenvpnAdmin) mgmtKillUserConnection(username, serverName string) {
|
||||
conn, err := net.Dial("tcp", oAdmin.mgmtInterfaces[serverName])
|
||||
if err != nil {
|
||||
log.Println("ERROR: openvpn mgmt interface is not reachable")
|
||||
log.Printf("WARNING: openvpn mgmt interface for %s is not reachable by addr %s\n", serverName, oAdmin.mgmtInterfaces[serverName])
|
||||
return
|
||||
}
|
||||
oAdmin.mgmtRead(conn) // read welcome message
|
||||
|
@ -854,25 +959,29 @@ func (oAdmin *OpenvpnAdmin) mgmtKillUserConnection(username string) {
|
|||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) mgmtGetActiveClients() []clientStatus {
|
||||
conn, err := net.Dial("tcp", *mgmtListenHost+":"+*mgmtListenPort)
|
||||
var activeClients []clientStatus
|
||||
|
||||
for srv, addr := range oAdmin.mgmtInterfaces {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
log.Println("ERROR: openvpn mgmt interface is not reachable")
|
||||
return []clientStatus{}
|
||||
log.Printf("WARNING: openvpn mgmt interface for %s is not reachable by addr %s\n", srv, addr)
|
||||
break
|
||||
}
|
||||
oAdmin.mgmtRead(conn) // read welcome message
|
||||
conn.Write([]byte("status\n"))
|
||||
activeClients := oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn))
|
||||
activeClients = append(activeClients, oAdmin.mgmtConnectedUsersParser(oAdmin.mgmtRead(conn), srv)...)
|
||||
conn.Close()
|
||||
}
|
||||
return activeClients
|
||||
}
|
||||
|
||||
func isUserConnected(username string, connectedUsers []clientStatus) bool {
|
||||
func isUserConnected(username string, connectedUsers []clientStatus) (bool, string) {
|
||||
for _, connectedUser := range connectedUsers {
|
||||
if connectedUser.CommonName == username {
|
||||
return true
|
||||
return true, connectedUser.ConnectedTo
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (oAdmin *OpenvpnAdmin) downloadCerts() bool {
|
||||
|
@ -968,7 +1077,7 @@ func (oAdmin *OpenvpnAdmin) syncWithMaster() {
|
|||
}
|
||||
}
|
||||
|
||||
func getOpvnCaCertExpireDate() time.Time {
|
||||
func getOvpnCaCertExpireDate() time.Time {
|
||||
caCertPath := *easyrsaDirPath + "/pki/ca.crt"
|
||||
caCertExpireDate := runBash(fmt.Sprintf("openssl x509 -in %s -noout -enddate | awk -F \"=\" {'print $2'}", caCertPath))
|
||||
|
||||
|
|
16
setup/auth.sh
Normal file
16
setup/auth.sh
Normal file
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
PATH=$PATH:/usr/local/bin
|
||||
set -e
|
||||
|
||||
env
|
||||
|
||||
auth_usr=$(head -1 $1)
|
||||
auth_passwd=$(tail -1 $1)
|
||||
|
||||
if [ $common_name = $username ]; then
|
||||
openvpn-user auth --db.path /etc/openvpn/easyrsa/pki/users.db --user ${auth_usr} --password ${auth_passwd}
|
||||
else
|
||||
echo "Authorization failed"
|
||||
exit 1
|
||||
fi
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
set -x
|
||||
set -ex
|
||||
|
||||
EASY_RSA_LOC="/etc/openvpn/easyrsa"
|
||||
SERVER_CERT="${EASY_RSA_LOC}/pki/issued/server.crt"
|
||||
|
@ -34,10 +34,20 @@ fi
|
|||
|
||||
cp -f /etc/openvpn/setup/openvpn.conf /etc/openvpn/openvpn.conf
|
||||
|
||||
if [ ${OPVN_PASSWD_AUTH} = "true" ]; then
|
||||
mkdir -p /etc/openvpn/scripts/
|
||||
cp -f /etc/openvpn/setup/auth.sh /etc/openvpn/scripts/auth.sh
|
||||
chmod +x /etc/openvpn/scripts/auth.sh
|
||||
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 "verify-client-cert require" | tee -a /etc/openvpn/openvpn.conf
|
||||
openvpn-user db-init --db.path=$EASY_RSA_LOC/pki/users.db
|
||||
fi
|
||||
|
||||
[ -d $EASY_RSA_LOC/pki ] && chmod 755 $EASY_RSA_LOC/pki
|
||||
[ -f $EASY_RSA_LOC/pki/crl.pem ] && chmod 644 $EASY_RSA_LOC/pki/crl.pem
|
||||
|
||||
mkdir -p /etc/openvpn/ccd
|
||||
|
||||
openvpn --config /etc/openvpn/openvpn.conf --client-config-dir /etc/openvpn/ccd
|
||||
openvpn --config /etc/openvpn/openvpn.conf --client-config-dir /etc/openvpn/ccd --port 1194 --proto tcp --management 127.0.0.1 8989
|
||||
|
|
@ -8,15 +8,14 @@ dh /etc/openvpn/easyrsa/pki/dh.pem
|
|||
crl-verify /etc/openvpn/easyrsa/pki/crl.pem
|
||||
tls-auth /etc/openvpn/easyrsa/pki/ta.key
|
||||
key-direction 0
|
||||
duplicate-cn
|
||||
cipher AES-128-CBC
|
||||
management 127.0.0.1 8989
|
||||
#management 127.0.0.1 8989
|
||||
keepalive 10 60
|
||||
persist-key
|
||||
persist-tun
|
||||
topology subnet
|
||||
proto tcp
|
||||
port 1194
|
||||
#proto tcp
|
||||
#port 1194
|
||||
dev tun0
|
||||
status /tmp/openvpn-status.log
|
||||
user nobody
|
120
werf.yaml
120
werf.yaml
|
@ -1,120 +0,0 @@
|
|||
project: openvpn-admin
|
||||
configVersion: 1
|
||||
deploy:
|
||||
helmRelease: "[[ project ]]-[[ env ]]"
|
||||
namespace: "[[ project ]]-[[ env ]]"
|
||||
|
||||
---
|
||||
artifact: backend-builder
|
||||
from: golang:1.14.2-alpine3.11
|
||||
git:
|
||||
- add: /
|
||||
to: /app
|
||||
stageDependencies:
|
||||
install:
|
||||
- "*.go"
|
||||
excludePaths:
|
||||
- .helm
|
||||
- .werf
|
||||
- frontend
|
||||
- werf.yaml
|
||||
- Dockerfile
|
||||
ansible:
|
||||
install:
|
||||
- name: Install packages
|
||||
apk:
|
||||
name:
|
||||
- build-base
|
||||
- gcc
|
||||
- name: Build backend
|
||||
command: go build -ldflags='-extldflags "-static" -s -w' -o openvpn-admin
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
args:
|
||||
chdir: /app
|
||||
|
||||
---
|
||||
artifact: frontend-builder
|
||||
from: node:14.2-alpine3.11
|
||||
git:
|
||||
- add: /frontend
|
||||
to: /app
|
||||
stageDependencies:
|
||||
install:
|
||||
- "**/*"
|
||||
excludePaths:
|
||||
- Dockerfile
|
||||
- build.sh
|
||||
- werf.yaml
|
||||
ansible:
|
||||
setup:
|
||||
- name: install deps
|
||||
command: npm install
|
||||
args:
|
||||
chdir: /app
|
||||
- name: Build app
|
||||
command: npm run build
|
||||
args:
|
||||
chdir: /app
|
||||
|
||||
---
|
||||
image: openvpn-admin
|
||||
from: alpine:3.11
|
||||
import:
|
||||
- artifact: backend-builder
|
||||
add: /app/openvpn-admin
|
||||
to: /usr/bin/openvpn-admin
|
||||
before: setup
|
||||
- artifact: frontend-builder
|
||||
add: /app/static
|
||||
to: /app/static
|
||||
before: setup
|
||||
git:
|
||||
- add: /client.conf.tpl
|
||||
to: /app/client.conf.tpl
|
||||
stageDependencies:
|
||||
setup:
|
||||
- "*"
|
||||
- add: /ccd.tpl
|
||||
to: /app/ccd.tpl
|
||||
stageDependencies:
|
||||
setup:
|
||||
- "*"
|
||||
ansible:
|
||||
install:
|
||||
- name: Install packages
|
||||
apk:
|
||||
name:
|
||||
- easy-rsa
|
||||
- bash
|
||||
- name: Create symbolic link for easy-rsa
|
||||
file:
|
||||
src: "/usr/share/easy-rsa/easyrsa"
|
||||
dest: "/usr/local/bin/easyrsa"
|
||||
state: link
|
||||
|
||||
---
|
||||
image: openvpn
|
||||
from: alpine:3.11
|
||||
git:
|
||||
- add: /.werffiles/
|
||||
to: /etc/openvpn/setup/
|
||||
stageDependencies:
|
||||
install:
|
||||
- "*"
|
||||
ansible:
|
||||
install:
|
||||
- name: Install packages
|
||||
apk:
|
||||
name:
|
||||
- openvpn
|
||||
- easy-rsa
|
||||
- name: Create symbolic link for easy-rsa
|
||||
file:
|
||||
src: "/usr/share/easy-rsa/easyrsa"
|
||||
dest: "/usr/local/bin/easyrsa"
|
||||
state: link
|
||||
|
||||
|
Loading…
Reference in a new issue