From 8f6848e2f6be4cde36f1e3599de0e7ef0457a30b Mon Sep 17 00:00:00 2001 From: Mark Wooding Date: Thu, 5 May 2016 10:27:24 +0100 Subject: [PATCH] Found in crybaby's working tree. --- backend.py | 38 ++++++++++--- chpwd.rst | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 8 deletions(-) create mode 100644 chpwd.rst diff --git a/backend.py b/backend.py index fda32e0..2596306 100644 --- a/backend.py +++ b/backend.py @@ -44,7 +44,8 @@ CONF.DEFAULTS.update( ###-------------------------------------------------------------------------- ### Utilities. -def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args): +def fill_in_fields(fno_user, fno_passwd, fno_map, + user, passwd, args, defaults = None): """ Return a vector of filled-in fields. @@ -53,6 +54,10 @@ def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args): is a sequence of (NAME, POS) pairs. The USER and PASSWD arguments give the actual user name and password values; ARGS are the remaining arguments, maybe in the form `NAME=VALUE'. + + If DEFAULTS is given, it is a fully-filled-in sequence of records. The + user name and password are taken from the DEFAULTS if USER and PASSWD are + `None' on entry; they cannot be set from ARGS. """ ## Prepare the result vector, and set up some data structures. @@ -63,7 +68,8 @@ def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args): if fno_user >= n or fno_passwd >= n: ok = False for k, i in fno_map: fmap[k] = i - rmap[i] = "`%s'" % k + if i in rmap: ok = False + else: rmap[i] = "`%s'" % k if i >= n: ok = False if not ok: raise U.ExpectedError, \ @@ -71,8 +77,12 @@ def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args): ## Prepare the new record's fields. f = [None]*n - f[fno_user] = user - f[fno_passwd] = passwd + if user is not None: f[fno_user] = user + elif defaults is not None: f[no_user] = defaults[no_user] + else: raise U.ExpectedError, (500, "No user name given") + if passwd is not None: f[fno_passwd] = passwd + elif defaults is not None: f[no_passwd] = defaults[no_passwd] + else: raise U.ExpectedError, (500, "No password given") for a in args: if '=' in a: @@ -89,10 +99,12 @@ def fill_in_fields(fno_user, fno_passwd, fno_map, user, passwd, args): raise U.ExpectedError, (400, "Field %s is already set" % rmap[i]) f[i] = v - ## Check that the vector of fields is properly set up. + ## Check that the vector of fields is properly set up. Copy unset values + ## from the defaults if they're available. for i in xrange(n): - if f[i] is None: - raise U.ExpectedError, (500, "Field %s is unset" % rmap[i]) + if f[i] is not None: pass + elif defaults is not None: f[i] = defaults[i] + else: raise U.ExpectedError, (500, "Field %s is unset" % rmap[i]) ## Done. return f @@ -165,7 +177,7 @@ class FlatFileRecord (BasicRecord): is careful to do this). """ - def __init__(me, line, delim, fmap, *args, **kw): + def __init__(me, line, delim, fno_user, fno_passwd, fmap, *args, **kw): """ Initialize the record, splitting the LINE into fields separated by DELIM, and setting attributes under control of FMAP. @@ -174,6 +186,8 @@ class FlatFileRecord (BasicRecord): line = line.rstrip('\n') fields = line.split(delim) me._delim = delim + me._fno_user = fno_user + me._fno_passwd = fno_passwd me._fmap = fmap me._raw = fields for k, v in fmap.iteritems(): @@ -199,6 +213,14 @@ class FlatFileRecord (BasicRecord): fields[v] = val return me._delim.join(fields) + '\n' + def edit(me, args): + """ + Modify the record as described by the ARGS. + + Fields other than the user name and password can be modified. + """ + ff = fill_in_fields( + class FlatFileBackend (object): """ Password storage in a flat passwd(5)-style file. diff --git a/chpwd.rst b/chpwd.rst new file mode 100644 index 0000000..909576b --- /dev/null +++ b/chpwd.rst @@ -0,0 +1,185 @@ +======================================= + Chopwood: a password changing service +======================================= + +:Author: Mark Wooding + +.. sectnum:: + :depth: 3 + +.. contents:: + +Introduction +############ + + +Congratulations. You've just installed Squid, or nginx, or Exim, or +Dovecot, or, well, something. And it has a marvellous access control +system with users and passwords and stuff. Yet another little file with +*user*\ **:**\ *password* lines, or maybe a database or something. +Which will all be great. Just as soon as your users can actually set +passwords, or change them. Chopwood is a service for letting them do +that. + +Of course, you could set up some single sign-on system. They're quite +complicated. Also, they probably aren't really what you wanted, because +these various services that you've set up probably have rather different +security levels and it's just not a good plan to feed high-value +credentials to a low-security service. + +Chopwood isn't a single sign-on system: in fact it's the opposite. It's +a tool to let users manage a number of passwords on the same system. + +Chopwood has three user interfaces. + + * Local users can communicate with it as a `GNU Userv`_ service. Most + of the necessary plumbing is provided. Local users don't need to do + authenticate explicitly via Userv, so this is simple, convenient, + reliable, and relatively pain free. The only downside is that you + have to give users shell access to your server. + + * Remote users can reach it using SSH, using OpenSSH_\ 's forced + command feature. Maintaining the ``authorized_keys`` file is a bit + of a nuisance, especially if they need to keep changing their keys + [#keymgmt]_. + + * Remote users can also get to it with a web browser, using good + old-fashioned CGI. Unfortunately, we don't have a better approach + for authenticating users than more passwords. Of course, Chopwood + can manage its own password, but that's not really the point. + +.. [#keymgmt] Maybe someone should build a similar tool for managing + authorized keys for SSH services. + +.. _GNU Userv: http://www.gnu.org/software/userv/ + +.. _OpenSSH: http://www.openssh.org/ + + + +Installation +############ + + +Chopwood is written in Python, and doesn't depend on any additional +libraries. The program is tested with Python 2.7, but I expect that it +works with Python 2.6, and it's intended to work with 2.5 too. + +You'll also need GNU Make and at least of + + * GNU Userv; + + * OpenSSH, or some other SSH server with a forced-command feature; + + * a web server which can run CGI scripts as some other user; or + + * some willingness to get your hands dirty hooking Chopwood up to some + other interface. + +Chopwood is designed to be run from a Git working tree. You can clone +it from + + * + * + * + +I recommend that you use the HTTPS transport if you can; unfortunately, +at the moment, that server doesn't have a certificate from a well-known +CA. Please check the certificate against the TLSA record, which is +secured using DNSsec. + +The working tree needs a very quick build step, which you can accomplish +by running :: + + $ make + +This does three things: + + * it generates the ``auto.py`` file which tells Chopwood its + installation directory, version number, and various other + automatically discovered things; and + + * it generates the static files for the web interface; and + + * it causes Python to byte-compile the various Python modules. + + +Basic configuration +------------------- + +Great so far. Now it's time to shut up the warning about not having a +configuration file. + + +Configuration parameters +------------------------ + +**ALLOWOP**: + An object holding three boolean attributes. If **ALLOWOP.set** + is true then users are allowed to set passwords to values of + their choosing; if **ALLOWOP.reset** is true then they're + allowed to ask for passwords to be reset to a value chosen by + Chopwood; and if **ALLOWOP.clear** is true then they're allowed + to clear passwords, leaving them unable to log into the + applicable service. + +**AUTHHASH**: + The hash function to use when making HTTP authentication + cookies. This should be a function (or other callable) which + behaves like one of the **hashlib** hash functions. The default + is **hashlib**.\ **sha256**, which is likely to be good enough. + +**DB**: + The database which Chopwood should use to store its state. This + should be a pair (*module*, *modargs*), where *module* names a + PEP 249 database module and *modargs* is a sequence or + dictionary of arguments to pass to its **connect** function. + The default is to create a SQLite3 database in Chopwood's + working directory. + +**HASH**: + The password hash to use for Chopwood's own user database. See + `Chopwood password hashes`_ for more information about the + password hashes provided and the protocol in which hash objects + participate. The default is to use a **crypt**\ (3)-style hash + with MD5. + +**LOCKDIR**: + A string naming the directory in which Chopwood should store + its lockfiles. + +**RQCLASS** and **RQMIXIN**: + Classes used to determine password policies. See `Applying + local policy`_ for details. The default is to allow everything. + +**SCRIPT_NAME**: + A (possibly relative) URL referring to Chopwood's CGI script. + This will usually be set appropriately by the webserver, but + this setting exists so that you can override it if it's wrong. + +**SECRETFRESH**: + The time in seconds for which a cookie authentication key is + used for issuing fresh cookies. After this, a new key is + generated, and the old ones kept and used to validate existing + cookies until their lifetime expires (see **SECRETLIFE** below). + The default is five minutes. + +**SECRETLIFE**: + The total lifetime, in seconds, of a key used to ensure + cryptographically the integrity of session cookies. See + **SECRETFRESH** above. Users' browsers are instructed to + discard the cookie after `**SECRETLIFE** - **SECRETFRESH**`:m: + seconds. The default is half an hour. + +**STATIC**: + The URL prefix for static content: the location of static + resource *file* is reported to the user agent as *STATIC*\ + **/**\ *file*. The default is to attempt (see **SCRIPT_NAME** + above) to link back to Chopwood's CGI script to generate the + content dynamically. You can improve responsiveness by + configuring Chopwood and your webserver to serve actual static + files instead. + + +.. LocalWords: nginx www userv CGI keymgmt https TLSA py ALLOWOP sha +.. LocalWords: AUTHHASH RQCLASS RQMIXIN modargs -- 2.11.0