Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
ai
autoca
Commits
9e47d2e4
Commit
9e47d2e4
authored
Dec 30, 2012
by
ale
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
allow multiple downloads of the same zip file (for a short time)
parent
70c717c8
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
162 additions
and
50 deletions
+162
-50
autovpn/templates/download.html
autovpn/templates/download.html
+1
-1
autovpn/vpn_app.py
autovpn/vpn_app.py
+160
-49
setup.py
setup.py
+1
-0
No files found.
autovpn/templates/download.html
View file @
9e47d2e4
{% extends "_base.html" %}
{% set dl_url = url_for('vpn_admin.new_cert_dl', _csrf=csrf_token()) %}
{% set dl_url = url_for('vpn_admin.new_cert_dl', _csrf=csrf_token()
, t=cn_token
) %}
{% block head %}
<meta
http-equiv=
"refresh"
content=
"2; url={{ dl_url }}"
>
{% endblock %}
...
...
autovpn/vpn_app.py
View file @
9e47d2e4
import
datetime
import
functools
import
itsdangerous
import
logging
import
os
import
shutil
import
subprocess
import
tempfile
import
threading
import
time
import
uuid
import
zipfile
from
cStringIO
import
StringIO
from
OpenSSL
import
crypto
from
flask
import
Blueprint
,
Flask
,
abort
,
redirect
,
request
,
make_response
,
\
render_template
,
session
,
g
,
current_app
,
url_for
from
autoca
import
ca
from
autoca
import
ca_app
from
autoca
import
ca_stub
from
flask
import
Blueprint
,
Flask
,
abort
,
redirect
,
request
,
make_response
,
\
render_template
,
session
,
g
,
current_app
,
url_for
vpn_admin
=
Blueprint
(
'vpn_admin'
,
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -136,6 +140,97 @@ Further info:
'''
class
Singleton
(
object
):
"""Singleton with deferred instantiation."""
def
__init__
(
self
,
cls
):
self
.
lock
=
threading
.
Lock
()
self
.
obj
=
None
self
.
cls
=
cls
def
__call__
(
self
,
*
args
):
with
self
.
lock
:
if
self
.
obj
is
None
:
self
.
obj
=
self
.
cls
(
*
args
)
return
self
.
obj
class
Signer
(
object
):
"""Generates secure download tokens that are valid for a few seconds."""
TIMEOUT
=
60
def
__init__
(
self
,
secret
):
self
.
l
=
threading
.
local
()
self
.
secret
=
secret
def
_signer
(
self
):
if
not
hasattr
(
self
.
l
,
'signer'
):
self
.
l
.
signer
=
itsdangerous
.
URLSafeTimedSerializer
(
self
.
secret
,
salt
=
'cn'
)
return
self
.
l
.
signer
def
encode
(
self
):
cn
=
str
(
uuid
.
uuid4
())
return
self
.
_signer
().
dumps
(
cn
)
def
decode
(
self
,
token
):
return
self
.
_signer
().
loads
(
token
,
max_age
=
self
.
TIMEOUT
)
class
ZipCacheCleanupThread
(
threading
.
Thread
):
EXPIRE_TIME
=
120
def
__init__
(
self
,
root
):
threading
.
Thread
.
__init__
(
self
)
self
.
root
=
root
self
.
setDaemon
(
True
)
self
.
start
()
def
purge_cached_files
(
self
):
cutoff
=
self
.
EXPIRE_TIME
now
=
time
.
time
()
for
f
in
os
.
listdir
(
self
.
root
):
if
f
.
startswith
(
'.'
)
or
not
f
.
endswith
(
'.zip'
):
continue
fp
=
os
.
path
.
join
(
self
.
root
,
f
)
mtime
=
os
.
path
.
getmtime
(
gp
)
if
(
now
-
mtime
)
>
cutoff
:
os
.
unlink
(
fp
)
def
run
(
self
):
while
True
:
time
.
sleep
(
60
)
try
:
self
.
purge_cached_files
()
except
:
pass
class
ZipCache
(
object
):
"""A simple on-disk cache of recent zip files."""
cleanup_thread
=
Singleton
(
ZipCacheCleanupThread
)
def
__init__
(
self
,
root
):
self
.
root
=
root
self
.
cleanup
=
self
.
cleanup_thread
(
root
)
def
_path
(
self
,
cn
):
return
os
.
path
.
join
(
self
.
root
,
cn
+
'.zip'
)
def
get
(
self
,
cn
):
try
:
with
open
(
self
.
_path
(
cn
),
'r'
)
as
fd
:
return
fd
.
read
()
except
:
return
None
def
put
(
self
,
cn
,
contents
):
with
open
(
self
.
_path
(
cn
),
'w'
)
as
fd
:
fd
.
write
(
contents
)
def
to_pkcs12
(
crt_pem
,
key_pem
,
ca_pem
):
"""Pack credentials into a PKCS12-format buffer."""
tmpdir
=
tempfile
.
mkdtemp
()
...
...
@@ -220,66 +315,78 @@ def logout():
@
auth
@
csrf
(
methods
=
[
'GET'
,
'POST'
])
def
new_cert
():
session
[
'dl_ok'
]
=
True
return
render_template
(
'download.html'
)
cn
=
str
(
uuid
.
uuid4
())
cn_token
=
current_app
.
signer
.
encode
(
cn
)
return
render_template
(
'download.html'
,
cn_token
=
cn_token
)
@
vpn_admin
.
route
(
'/newcertdl'
)
@
auth
@
csrf
(
methods
=
[
'GET'
])
def
new_cert_dl
():
if
not
session
.
pop
(
'dl_ok'
,
None
):
# Retrieve CN from the signed token.
try
:
cn
=
current_app
.
signer
.
decode
(
request
.
args
.
get
(
't'
))
except
:
return
render_template
(
'download_retry.html'
)
# Create and sign a certificate.
cn
=
str
(
uuid
.
uuid4
())
# Check if the certificate is new or not, so that the user can
# perform multiple downloads of the same certificate / private
# keypair until the token is valid.
zip_data
=
current_app
.
zipcache
.
get
(
cn
)
if
not
zip_data
:
validity
=
int
(
current_app
.
config
.
get
(
'VPN_CERT_VALIDITY'
,
7
))
expiry_date
=
datetime
.
date
.
today
()
+
datetime
.
timedelta
(
validity
)
# Create and sign the new certificate.
validity
=
int
(
current_app
.
config
.
get
(
'VPN_CERT_VALIDITY'
,
7
))
expiry_date
=
datetime
.
date
.
today
()
+
datetime
.
timedelta
(
validity
)
subject
=
current_app
.
config
.
get
(
'VPN_DEFAULT_SUBJECT_ATTRS'
,
{}).
copy
()
subject
[
'CN'
]
=
cn
subject
=
current_app
.
config
.
get
(
'VPN_DEFAULT_SUBJECT_ATTRS'
,
{}).
copy
()
subject
[
'CN'
]
=
cn
pkey
,
cert
=
g
.
ca
.
make_certificate
(
subject
,
days
=
validity
)
pkey
,
cert
=
g
.
ca
.
make_certificate
(
subject
,
days
=
validity
)
# Create the zipfile in-memory, with all the files the user needs.
vars
=
{
'cn'
:
cn
,
'bundle_identifier'
:
'.'
.
join
(
# Create the zipfile in-memory, with all the files the user needs.
vars
=
{
'cn'
:
cn
,
'bundle_identifier'
:
'.'
.
join
(
current_app
.
config
[
'VPN_ENDPOINT'
].
split
(
'.'
)[::
-
1
])
+
'.'
+
cn
,
'vpn_endpoint'
:
current_app
.
config
[
'VPN_ENDPOINT'
],
'vpn_site'
:
current_app
.
config
[
'VPN_SITE_URL'
],
'expiry_date'
:
expiry_date
.
strftime
(
'%Y/%m/%d'
)}
ca_pem
=
g
.
ca
.
get_ca
()
crt_pem
=
crypto
.
dump_certificate
(
crypto
.
FILETYPE_PEM
,
cert
)
key_pem
=
crypto
.
dump_privatekey
(
crypto
.
FILETYPE_PEM
,
pkey
)
pkcs12
=
to_pkcs12
(
crt_pem
,
key_pem
,
ca_pem
)
manifest
=
[
(
'ca.crt'
,
ca_pem
),
(
'crl.pem'
,
g
.
ca
.
get_crl
(
format
=
'pem'
)),
(
'%s.crt'
%
cn
,
crt_pem
),
(
'%s.key'
%
cn
,
key_pem
),
(
'%s.pfx'
%
cn
,
pkcs12
),
(
'tlsauth.key'
,
current_app
.
config
[
'TLS_AUTH_KEY'
]),
(
'openvpn-%s.conf'
%
cn
,
OPENVPN_CONFIG_TEMPLATE
%
vars
),
(
'README.txt'
,
README_TEMPLATE
%
vars
),
# Tunnelblick configuration for OSX
(
'%s.tblk/Info.plist'
%
cn
,
TBLK_PLIST_TEMPLATE
%
vars
),
(
'%s.tblk/config.ovpn'
%
cn
,
OPENVPN_CONFIG_TEMPLATE
%
vars
),
(
'%s.tblk/ca.crt'
%
cn
,
g
.
ca
.
get_ca
()),
(
'%s.tblk/crl.pem'
%
cn
,
g
.
ca
.
get_crl
(
format
=
'pem'
)),
(
'%s.tblk/%s.crt'
%
(
cn
,
cn
),
crt_pem
),
(
'%s.tblk/%s.key'
%
(
cn
,
cn
),
key_pem
),
(
'%s.tblk/tlsauth.key'
%
cn
,
current_app
.
config
[
'TLS_AUTH_KEY'
]),
]
zbuf
=
StringIO
()
zf
=
zipfile
.
ZipFile
(
zbuf
,
mode
=
'w'
,
compression
=
zipfile
.
ZIP_DEFLATED
)
for
filename
,
contents
in
manifest
:
zf
.
writestr
(
filename
,
contents
)
zf
.
close
()
response
=
make_response
(
zbuf
.
getvalue
())
'vpn_endpoint'
:
current_app
.
config
[
'VPN_ENDPOINT'
],
'vpn_site'
:
current_app
.
config
[
'VPN_SITE_URL'
],
'expiry_date'
:
expiry_date
.
strftime
(
'%Y/%m/%d'
)}
ca_pem
=
g
.
ca
.
get_ca
()
crt_pem
=
crypto
.
dump_certificate
(
crypto
.
FILETYPE_PEM
,
cert
)
key_pem
=
crypto
.
dump_privatekey
(
crypto
.
FILETYPE_PEM
,
pkey
)
pkcs12
=
to_pkcs12
(
crt_pem
,
key_pem
,
ca_pem
)
manifest
=
[
(
'ca.crt'
,
ca_pem
),
(
'crl.pem'
,
g
.
ca
.
get_crl
(
format
=
'pem'
)),
(
'%s.crt'
%
cn
,
crt_pem
),
(
'%s.key'
%
cn
,
key_pem
),
(
'%s.pfx'
%
cn
,
pkcs12
),
(
'tlsauth.key'
,
current_app
.
config
[
'TLS_AUTH_KEY'
]),
(
'openvpn-%s.conf'
%
cn
,
OPENVPN_CONFIG_TEMPLATE
%
vars
),
(
'README.txt'
,
README_TEMPLATE
%
vars
),
# Tunnelblick configuration for OSX
(
'%s.tblk/Info.plist'
%
cn
,
TBLK_PLIST_TEMPLATE
%
vars
),
(
'%s.tblk/config.ovpn'
%
cn
,
OPENVPN_CONFIG_TEMPLATE
%
vars
),
(
'%s.tblk/ca.crt'
%
cn
,
g
.
ca
.
get_ca
()),
(
'%s.tblk/crl.pem'
%
cn
,
g
.
ca
.
get_crl
(
format
=
'pem'
)),
(
'%s.tblk/%s.crt'
%
(
cn
,
cn
),
crt_pem
),
(
'%s.tblk/%s.key'
%
(
cn
,
cn
),
key_pem
),
(
'%s.tblk/tlsauth.key'
%
cn
,
current_app
.
config
[
'TLS_AUTH_KEY'
]),
]
zbuf
=
StringIO
()
zf
=
zipfile
.
ZipFile
(
zbuf
,
mode
=
'w'
,
compression
=
zipfile
.
ZIP_DEFLATED
)
for
filename
,
contents
in
manifest
:
zf
.
writestr
(
filename
,
contents
)
zf
.
close
()
zip_data
=
zbuf
.
getvalue
()
current_app
.
zipcache
.
put
(
cn
,
zip_data
)
response
=
make_response
(
zip_data
)
response
.
headers
[
'Content-Type'
]
=
'application/zip'
response
.
headers
[
'Content-Disposition'
]
=
(
'attachment; filename="%s.zip"'
%
cn
)
...
...
@@ -291,6 +398,8 @@ def make_app(config={}):
app
=
Flask
(
__name__
)
app
.
config
.
update
(
config
)
app
.
config
.
from_envvar
(
'VPN_APP_SETTINGS'
,
silent
=
True
)
app
.
signer
=
Signer
(
app
.
config
[
'SIGNER_SECRET'
])
app
.
zipcache
=
ZipCache
(
app
.
config
[
'CACHE_DIR'
])
app
.
register_blueprint
(
vpn_admin
)
# Figure out how to hook to the CA.
...
...
@@ -316,6 +425,7 @@ if __name__ == '__main__':
try
:
make_app
({
'DEBUG'
:
'true'
,
'SECRET_KEY'
:
'somesecret'
,
'SIGNER_SECRET'
:
'moresecrets'
,
'VPN_CA_ROOT'
:
ca_dir
,
'VPN_CA_SUBJECT'
:
{
'CN'
:
'test CA'
,
'O'
:
'test'
},
'VPN_ENDPOINT'
:
'vpn.example.com'
,
...
...
@@ -327,6 +437,7 @@ if __name__ == '__main__':
'''
,
'AUTH_ENABLE'
:
True
,
'AUTH_FUNCTION'
:
lambda
x
,
y
:
(
x
and
y
and
x
==
y
),
'CACHE_DIR'
:
'cache'
,
}).
run
(
port
=
4000
)
finally
:
shutil
.
rmtree
(
ca_dir
)
setup.py
View file @
9e47d2e4
...
...
@@ -12,6 +12,7 @@ setup(
license
=
"MIT"
,
packages
=
find_packages
(),
platforms
=
[
"any"
],
install_requires
=
[
"Flask"
,
"itsdangerous"
],
zip_safe
=
False
,
entry_points
=
{
"console_scripts"
:
[
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment