Read about securing web services with Python using UserNameTokens
András Veres-Szentkirályi has started a series on securing web services with Python; the first part deals with UserNameTokens and mentions sec-wall, the security proxy, yay!
András Veres-Szentkirályi has started a series on securing web services with Python; the first part deals with UserNameTokens and mentions sec-wall, the security proxy, yay!
The unstoppable Miguel Landaeta did it again and I’m happy to let you all know that sec-wall, the security proxy, can be now installed on Debian wheezy/sid using nothing but DEBs, like below. The only prerequisite is that you need to visit http://alioth.debian.org/~nomadium-guest/debian/unstable/ to find and download the latest DEB – at the time of this writing it’s sec-wall_1.0.0-0miguel2_all.deb but it sure is going to change shortly because sec-wall 1.1 is about to be released soon.
First, the dependencies:
apt-get install python-springpython python-gevent apt-get install python-argparse python-lxml apt-get install python-pesto python-zdaemon apt-get install python-pkg-resources |
Now install sec-wall:
dpkg -i ./sec-wall_1.0.0-0miguel2_all.deb |
And that’s all!
Nice folks at www.linuxsecurity.com have just published a new article on using the sec-wall security proxy with cURL and PycURL, the Python interface to the libcurl library, so if you’re looking for information on how to test services secured using sec-wall, either with HTTP Basic/Digest Auth, custom HTTP headers, XPath-based authentication, WS-Security or SSL/TLS client certificates then you won’t be disappointed. Enjoy!
One of the things sec-wall, a featured-packed high performance security proxy, provides is the support for securing access to resources using arbitrary XPath expressions. What is currently missing in the documentation though is an explanation of how one should use XML namespaces. The thing can be done and there’s a bug report regarding it which I’m going to fix and close in a day or two but just thought that in the meantime I’d blog about it.
So how would one go about creating a sec-wall config.py file that should let in only clients that use credentials akin to what’s below?
<?xml version="1.0" encoding="utf-8"?> <a> <b> <username xmlns="http://example.com/myns1">foo</username> <c xmlns="http://example.com/myns2" password="bar" /> </b> </a> |
The answer is pretty simple – etree.XPath objects accept a namespaces argument which ought to be a mapping between prefixes used in expressions and actual namespaces, so the config file should read like below:
# -*- coding: utf-8 -*- # stdlib import uuid # lxml from lxml import etree # Don't share it with anyone. INSTANCE_SECRET = '7bcb90942d994440af05d02b691ae86d' # May be shared with the outside world. INSTANCE_UNIQUE = uuid.uuid4().hex # ############################################################################## def xpath(): username = 'foo' password = 'bar' xpath1 = "/a/b/myns1:username/text() = '{0}'".format(username) xpath2 = "//myns2:c/@password='{0}'".format(password) ns_dict = { 'myns1': 'http://example.com/myns1', 'myns2': 'http://example.com/myns2', } return { 'xpath': True, 'xpath-1': etree.XPath(xpath1, namespaces=ns_dict), 'xpath-2': etree.XPath(xpath2, namespaces=ns_dict), 'host': 'http://example.com/', } urls = [ ('/xpath', xpath()), ] |
Solid, eh?
One of the niceties of sec-wall, the security proxy, is that it’s written in Python which means not only that one doesn’t have to learn yet another obscure configuration format summoned from the deepest levels of R’lyeh but also that it’s very easy to extend the config.py file.
For instance, by default all of the secrets need to be given in clear text and that can probably be sub-optimal for some people, so why don’t we customize the config file and have it use GnuPG with symmetric decryption via the python-gnupg wrapper?
Let’s install python-gnupg first (piece of cake):
$ sudo pip install python-gnupg |
Let’s prepare the secret.ini file, an INI-formatted configuration file which contains all the secrets:
$ cat secret.ini [secret] INSTANCE_SECRET=756817c1e2b8443c9d6f728da9b2b5fd admin=MySecretPassword $ |
The file will be now encrypted using GnuPG (let’s say the password is ’123′) ..
$ gpg --symmetric secret.ini
$ |
.. the result of which is a new secret.ini.gpg file and secret.ini may now be removed:
$ rm secret.ini
$ |
OK, we need the accompanying config.py file now, just like the one below:
# -*- coding: utf-8 -*- # stdlib import cStringIO, getpass, sys, uuid from ConfigParser import ConfigParser # python-gnupg import gnupg # Where are we expecting the secrets to be. gpg_file = open('./secret.ini.gpg', "rb") passphrase = getpass.getpass('Enter passphrase (will not be echoed back): ') gpg = gnupg.GPG() ini_data = str(gpg.decrypt_file(gpg_file, passphrase=passphrase)) # Exit early on an incorrect passphrase. if not ini_data: print("Incorrect passphrase.") sys.exit(1) # The passphrase was OK, proceed on to parsing the INI data. config = ConfigParser() config.readfp(cStringIO.StringIO(ini_data)) # Use the GPG-encrypted data. INSTANCE_SECRET = config.get('secret', 'INSTANCE_SECRET') # May be shared with the outside world. INSTANCE_UNIQUE = uuid.uuid4().hex # ############################################################################## def admin(): return { 'basic-auth': True, 'basic-auth-username':'user', 'basic-auth-password':config.get('secret', 'admin'), # Uses GPG now. 'basic-auth-realm':'Secure area', 'host': 'http://example.com' } def default(): return { 'ssl': True, 'ssl-cert': True, 'ssl-cert-commonName':INSTANCE_SECRET, 'host': 'http://' + INSTANCE_SECRET } urls = [ ('/admin/', admin()), ('/*', default()), ] |
Try starting the proxy now. If everything’s OK, if the passphrase you’ve supplied has been the same as the one used for encrypting the original INI file, the sec-wall instance will start in background as usual:
$ sec-wall --start . Enter passphrase (will not be echoed back): $ echo $? 0 $ |
If on the other hand the password wasn’t ’123′, the proxy won’t start:
$ sec-wall --start . Enter passphrase (will not be echoed back): Incorrect passphrase. $ echo $? 1 $ |
And that’s it! Couldn’t have been simpler, eh?
This is mostly a copy of the announcement sent to the python-announce@ list, although I’ve added a couple more usage examples at the end of the post.
sec-wall is a feature packed high-performance security proxy which has many interesting features, including the support for SSL/TLS, WS-Security, HTTP Auth Basic/Digest, extensible authentication schemes based on custom HTTP headers and XPath expressions, powerful URL matching/rewriting and an optional headers enrichment.
sec-wall uses and is built on top of several fantastic Python open source technologies, such as gevent, Spring Python, Pesto, lxml, zdaemon or PyYAML and is meant to be highly customizable and easy to use. Good performance, tests, documentation and building an awesome community are at the very heart of the project.
Here are examples showing how little is needed to secure a backend server with HTTP Basic Auth, SSL/TLS client certificates and WS-Security.
# -*- coding: utf-8 -*- # stdlib import uuid # Don't share it with anyone. INSTANCE_SECRET = '5bf4e78c256746eda2ce3e0e73f256d0' # May be shared with the outside world. INSTANCE_UNIQUE = uuid.uuid4().hex def default(): return { 'basic-auth':True, 'basic-auth-username':'MyUser', 'basic-auth-password':'MySecret', 'basic-auth-realm':'Secure area', 'host': 'http://example.com' } urls = [ ('/*', default()), ] |
# -*- coding: utf-8 -*- # stdlib import os.path as path, uuid # Don't share it with anyone. INSTANCE_SECRET = '5bf4e78c256746eda2ce3e0e73f256d0' # May be shared with the outside world. INSTANCE_UNIQUE = uuid.uuid4().hex # Useful constants cur_dir = path.dirname(__file__) # Crypto keyfile = path.join(cur_dir, './crypto/server-priv.pem') certfile = path.join(cur_dir, './crypto/server-cert.pem') ca_certs = path.join(cur_dir, './crypto/ca-cert.pem') server_type = 'https' def default(): return { 'ssl': True, 'ssl-cert': True, 'ssl-cert-commonName': 'My Client', 'ssl-cert-organizationName': 'My Company', 'host': 'http://example.com' } urls = [ ('/*', default()), ] |
# -*- coding: utf-8 -*- # stdlib import uuid # Don't share it with anyone. INSTANCE_SECRET = '5bf4e78c256746eda2ce3e0e73f256d0' # May be shared with the outside world. INSTANCE_UNIQUE = uuid.uuid4().hex def default(): return { 'wsse-pwd': True, 'wsse-pwd-username': 'MyUser', 'wsse-pwd-password': 'MySecret', # Needs to be given in clear text 'wsse-pwd-reject-empty-nonce-creation': True, 'wsse-pwd-reject-stale-tokens': True, 'wsse-pwd-reject-expiry-limit': 120, 'wsse-pwd-nonce-freshness-time': 120, 'host': 'http://example.com' } urls = [ ('/*', default()), ] |
Links:
Project’s homepage: http://sec-wall.gefira.pl/
Getting started: http://sec-wall.gefira.pl/documentation/getting-started/index.html
Usage examples: http://sec-wall.gefira.pl/documentation/usage-examples/index.html
Twitter: https://twitter.com/fourthrealm
Blog: http://www.gefira.pl/blog
IRC: #sec-wall channel on Freenode network
Cheers!