diff --git a/.dockerignore b/.dockerignore
index 47adc59..a80bc64 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -16,3 +16,6 @@ frontend/node_modules
openvpn-web-ui
openvpn-ui
openvpn-admin
+
+docker-compose.yaml
+docker-compose-slave.yaml
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index cd785fc..04d7a4b 100644
--- a/Dockerfile
+++ b/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/*
diff --git a/Dockerfile.openvpn b/Dockerfile.openvpn
index 94a783b..ee5f785 100644
--- a/Dockerfile.openvpn
+++ b/Dockerfile.openvpn
@@ -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
diff --git a/build.sh b/build.sh
index b17b614..ce6f2ff 100755
--- a/build.sh
+++ b/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
diff --git a/client.conf.tpl b/client.conf.tpl
index 4ee096e..7f29c47 100644
--- a/client.conf.tpl
+++ b/client.conf.tpl
@@ -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 -}}
diff --git a/docker-compose-slave.yaml b/docker-compose-slave.yaml
index 8169a87..d1d00ae 100644
--- a/docker-compose-slave.yaml
+++ b/docker-compose-slave.yaml
@@ -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
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 3cf4c36..3cb6fef 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -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
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 975817e..8a2d672 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -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",
diff --git a/frontend/package.json b/frontend/package.json
index ca0b2fa..0e04fe3 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -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%",
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 1d8a995..cbc2e12 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -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'})
+ });
+ },
}
+
})
diff --git a/frontend/static/index.html b/frontend/static/index.html
index 48dff1d..a317ed3 100644
--- a/frontend/static/index.html
+++ b/frontend/static/index.html
@@ -28,7 +28,7 @@
No users have been created yet.
@@ -51,11 +52,11 @@ No users have been created yet.
+
+
@@ -96,16 +120,18 @@
ovpn config for {{ username }}