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