Last active
June 15, 2024 11:11
-
-
Save mrizvic/ce6353ff46d9b852500a823b004329ac to your computer and use it in GitHub Desktop.
Static password and OTP authentication for OpenVPN in with custom python scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import os | |
import sys | |
import datetime | |
import pyotp | |
import hashlib | |
### TO ALLOW ACCSSS CALL sys.exit(0) | |
### TO DENY ACCESS CALL sys.exit(1) | |
### PUT THIS IN OPENVPN SERVER CONFIG TO AUTHENTICATE AGAINST THIS SCRIPT | |
### READ MAN PAGE FOR OPENVPN TO UNDERSTAND IT! | |
#auth-user-pass-verify /etc/openvpn/auth.py via-env | |
#client-cert-not-required | |
#verify-client-cert none | |
### GENERATE PASSWORDS WITH hashlib.sha512('YourSecretPassword').hexdigest() | |
static_users={ | |
'user1':'f0e1ee12360a3b2970df88460c19f62e222040d3f376cf239d76d627ab569d6e2e77e7435fd7bc376ab97d9b5066af9041ee347ec1f1d12cee3e4af68d2e2ae5', | |
'user2':'8fcba3a34bfa1d7c8ad1af6b09c2d20d0edf4ace9042ca761f9d369b7257ab491ab3cd9a34434b54ea6057975f1a0e8466385b4e741201b659f0e9e57559a281', | |
'user3':'15c8b15e99212cc292a1bc8fd936b1e4b99fd36716e6736072e5aafaeaca2af1ffa8ba01a41e593543da5bf8c67f1ad9e2b2a31fb94a6731af0bb43fe04bc8ad' | |
} | |
### GENERATE OTP PASSWORDS WITH base64.b32encode('OTPSecretString') | |
### THE SAME ENCODED STRING MUST BE PROVIDED TO OTP GENERATOR | |
otp_users={'user@otp':'J5KFAU3FMNZGK5CTORZGS3TH'} | |
### IF DEBUGGING IS ON THEN WE DENY ACCESS TO ALL USERS! | |
debug = 0 | |
def mylogger(message): | |
timestamp=datetime.datetime.now().strftime('%a %b %e %H:%M:%S %Y us=%f') | |
processname=__file__ | |
usrname=os.getenv('username') | |
untrusted_ip = os.getenv('untrusted_ip') | |
untrusted_port = os.getenv('untrusted_port') | |
### OPENVPN LOGGING FORMAT | |
print('{0} {4}:{5} cmd={1} username={2} {3}'.format(timestamp, processname, usrname, message, untrusted_ip, untrusted_port)) | |
return | |
### CE PREVERJANJE USERJA USPE POTEM VRNI sys.exit(0) | |
### CE NE USPE VRNI sys.exit(1) | |
try: | |
usrname=os.getenv('username') | |
passwd =os.getenv('password') | |
if usrname is None: | |
myerr=('username is missing') | |
raise Exception(myerr) | |
if passwd is None: | |
myerr=('password is missing') | |
raise Exception(myerr) | |
### DENY ACCESS WHEN DEBUGGING | |
if debug == 1: | |
mylogger(os.getresuid()) | |
for variable in os.environ: | |
value = os.getenv(variable) | |
mylogger('ENV {0}={1}'.format(variable,value)) | |
sys.exit(1) | |
except Exception as e: | |
mylogger(e) | |
exit_val=1 | |
else: | |
### IS OTP USER? | |
if usrname in otp_users: | |
### CONVERT STRING TO INT | |
try: | |
otpcode=int(passwd) | |
except ValueError: | |
myerr=('password not numeric') | |
raise Exception(myerr) | |
totp = pyotp.TOTP(otp_users[usrname]) | |
if totp.verify(otpcode): | |
exit_val=0 | |
mylogger('OTP auth success') | |
else: | |
exit_val=1 | |
mylogger('OTP auth failed') | |
### IS USER WITH PASSWORD? | |
else: | |
try: | |
stored_passwd = static_users[usrname] | |
except KeyError: | |
exit_val=1 | |
mylogger('username not configured in auth script') | |
else: | |
computed_passwd = hashlib.sha512(str(passwd).encode('utf-8')).hexdigest() | |
if computed_passwd == stored_passwd: | |
exit_val=0 | |
mylogger('HASH auth success') | |
else: | |
exit_val=1 | |
mylogger('HASH auth failed') | |
sys.exit(exit_val) | |
### WE REALLY SHOULDNT BE HERE - DENY ACCESS | |
mylogger('ERROR: unexpected situation') | |
sys.exit(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import sys | |
import json | |
import datetime | |
def mylogger(message): | |
timestamp=datetime.datetime.now().strftime('%a %b %e %H:%M:%S %Y us=%f') | |
processname=__file__ | |
usrname=os.getenv('username') | |
untrusted_ip = os.getenv('untrusted_ip') | |
untrusted_port = os.getenv('untrusted_port') | |
### OPENVPN LOGGING FORMAT | |
print '{4}:{5} cmd={1} username={2} {3}'.format(timestamp, processname, usrname, message, untrusted_ip, untrusted_port) | |
return | |
script_type = os.getenv('script_type') | |
if script_type == 'client-connect': | |
mylogger('CLIENT CONNECTED') | |
elif script_type == 'client-disconnect': | |
mylogger('CLIENT DISCONNECTED') | |
CLIENT_DATA = {} | |
for key in os.environ: | |
value = os.getenv(key) | |
#mylogger('ENV {0}={1}'.format(key,value)) | |
CLIENT_DATA[key] = value | |
CLIENT_JSON = json.dumps(CLIENT_DATA, sort_keys=True) | |
mylogger('CLIENT_DATA={0}'.format(CLIENT_JSON)) | |
sys.exit(0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
client | |
proto udp | |
dev tun | |
<ca> | |
-----BEGIN CERTIFICATE----- | |
{{ vpn_srv_cert }} | |
-----END CERTIFICATE----- | |
</ca> | |
remote {{ vpn_srv_host }} {{ vpn_srv_port }} | |
#cipher none | |
auth-user-pass | |
auth-nocache | |
user {{ unprivileged_user }} | |
group {{ unprivileged_group }} | |
verb 4 | |
keepalive 10 120 | |
persist-key | |
persist-tun | |
float | |
resolv-retry infinite | |
nobind | |
comp-lzo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
### PARSE OPENVPN STATUS LOG FILE | |
### EXTRACT AND PRINT CLIENT LIST AND ROUTING TABLE | |
def sizeof_fmt(num, suffix='B'): | |
num=float(num) | |
for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: | |
if abs(num) < 1024.0: | |
return "%3.1f%s%s" % (num, unit, suffix) | |
num /= 1024.0 | |
return "%.1f%s%s" % (num, 'Yi', suffix) | |
cl_fmt = '{:10}\t{:22}\t{:16}\t{:39}\t{:9}\t{:9}\t{}' | |
route_fmt = '{:10}\t\t{:22}\t{:39}\t{}' | |
file=open("/tmp/systemd-private-2f36e5d00c9549a9ad271e5a48ea025a-openvpn@otp.service-cThXQz/tmp/openvpn-status.log","r") | |
for line in file: | |
if line.startswith('HEADER\tCLIENT_LIST'): | |
print("CLIENT LIST:") | |
print(cl_fmt).format('COMMON NAME', 'REAL ADDRESS', 'VIRTUAL ADDRESS', 'VIRTUAL IPV6 ADDRESS', 'BYTES RECEIVED', 'BYTES SENT', 'CONNECTED SINCE') | |
if line.startswith('CLIENT_LIST'): | |
line2=line.rstrip().split('\t') | |
cn = line2[1] | |
real_addr = line2[2] | |
virt_addr = line2[3] | |
virt_addr6 = line2[4] or 'None' | |
b_rcvd = line2[5] | |
b_sent = line2[6] | |
con_since = line2[7] | |
con_since_time = line2[8] | |
username = line2[9] | |
client_id = line2[10] | |
peer_id = line2[11] | |
print(cl_fmt).format(cn, real_addr, virt_addr, virt_addr6, sizeof_fmt(b_rcvd), sizeof_fmt(b_sent), con_since) | |
if line.startswith('HEADER\tROUTING_TABLE'): | |
print("") | |
print("ROUTING TABLE:") | |
print(route_fmt).format('COMMON NAME', 'REAL ADDRESS', 'VIRTUAL ADDRESS', 'LAST REF') | |
if line.startswith('ROUTING_TABLE'): | |
line2=line.rstrip().split('\t') | |
virt_addr = line2[1] | |
cn = line2[2] | |
real_addr = line2[3] | |
last_ref = line2[4] | |
print(route_fmt).format(cn, real_addr, virt_addr, last_ref) | |
file.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mode server | |
port 1194 | |
proto udp | |
dev tun0 | |
ca /etc/openvpn/ca.crt | |
cert /etc/openvpn/openvpnserver.crt | |
key /etc/openvpn/openvpnserver.key | |
dh /etc/openvpn/dh4096.pem | |
topology subnet | |
push "topology subnet" | |
server 10.1.1.0 255.255.255.0 | |
#ifconfig 10.1.1.0 255.255.255.0 | |
#ifconfig-pool 10.1.1.32 10.1.1.128 255.255.255.0 | |
server-ipv6 2db8::1/64 | |
push "route-ipv6 2000::/3" | |
cipher AES-256-CBC | |
comp-lzo adaptive | |
#push "comp-lzo yes" | |
user openvpn | |
group openvpn | |
verb 4 | |
max-clients 8 | |
keepalive 10 120 | |
persist-key | |
persist-tun | |
script-security 3 | |
reneg-sec 90000 | |
### DONT CHECK CERTIFICATE, JUST USER/PASS | |
client-cert-not-required | |
verify-client-cert none | |
### EXTERNAL SCRIPT FOR STATIC PASSWORD AND OTP AUTHENTICATION | |
auth-user-pass-verify /etc/openvpn/auth.py via-env | |
### CUSTOM CONNECT/DISCONNECT EVENT LOGGER | |
client-connect /etc/openvpn/client-handler.py | |
client-disconnect /etc/openvpn/client-handler.py | |
### CCD STUFF | |
client-config-dir /etc/openvpn/ccd | |
### USER NEEDS CONFIG IN ccd DIR | |
#ccd-exclusive | |
### READ SETTINGS FROM FILE IN ccd DIR | |
username-as-common-name | |
### WE HAVE SYSTEMD | |
suppress-timestamps | |
### STATUS FILE | |
status /tmp/openvpn-status.log 10 | |
status-version 3 |
@mrizvic Absolutely! Again, so very grateful to have found this gem. The code is solid, and your files were enlightening. Y'all rock!!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@virtualizer117 I really appreciate your comment! It really sparks the motivation to sharing is caring spirit when one puts so much effort in joining github.com just to show their gratitude to another stranger :)