source: cred_printer/cred_server.py @ 811bc273

abac0-leakabac0-meicompt_changesgec13mei-idmei-rt0-nmei_rt0tvf-new-xml
Last change on this file since 811bc273 was 02dcba5, checked in by Ted Faber <faber@…>, 13 years ago

Add credential printer

  • Property mode set to 100755
File size: 5.8 KB
Line 
1#!/usr/local/bin/python
2
3import sys
4
5import re
6import select
7from signal import signal, SIGINT, SIGTERM
8
9from cred_printer.server import xmlrpc_handler, server, simpleServer
10from cred_printer.service_error import service_error
11from cred_printer.util import ssl_context
12
13from xmlrpclib import Binary
14from tempfile import NamedTemporaryFile
15from string import join
16
17from optparse import OptionParser
18
19import ABAC
20import Creddy
21
22class OptParser(OptionParser):
23    """
24    Option parsing for clients.  Should be self-explanatory.
25    """
26    def __init__(self):
27        OptionParser.__init__(self)
28        self.add_option('--port', dest='port', type='int', default=13232,
29                help='port to listen on')
30        self.add_option('--cert', dest='cert', default=None,
31                help='My identity certificate (and key)')
32
33
34class credential_printer:
35    """
36    The implementation passed to the server to handle incoming requests.
37    Specifically, translate_creds will be called when the translate method is
38    invoked on the server.
39    """
40    def __init__(self):
41        """
42        Initialize the method -> function dict.
43        """
44        self.xmlrpc_services = { 'translate': self.translate_creds }
45
46    @staticmethod
47    def get_cred_data(d):
48        """
49        Get the credential data from one of the aprameter dicts
50        """
51        return d.get('credential', Binary()).data
52
53    @staticmethod
54    def attr_cred_to_string(r):
55        """
56        Parse a an ABAC.Credential into a string representation.
57        """
58        return "%s <- %s" % (r.head().string(), r.tail().string())
59
60    @staticmethod
61    def repl_IDs(s, id_dict):
62        """
63        Replace all the keyids in the string with the name they map to in the
64        dict.  id_dict maps an ID certificate's keyid to a human-readable name.
65        """
66        for k, n in id_dict.items():
67            if re.search(k, s):
68                s = re.sub(k, n, s)
69        return s
70
71    def make_ID(self, cred):
72        """
73        Create a Creddy.ID from the given binary, if possible.  Because
74        Creddy.ID doesn't take a binary blob, this writes a temp file and
75        creates the ID from that.  If successful, the ID is returned.
76        Otherwise None is returned.
77        """
78        i = None
79        try:
80            f = NamedTemporaryFile()
81            f.write(cred)
82            f.flush()
83            i = Creddy.ID(f.name)
84            f.close()
85        except:
86            pass
87        finally:
88            return i
89
90    def split_certs(self, cred_dict):
91        """
92        A list of dicts of the form { id: identifier, credential: bits} is
93        divided into two such lists, one where the bits are attribute
94        certificates and one where they are IDs.  Dicts the ID cert list have
95        their 'type', 'str', 'auxstr' fields set to 'identity', the keyid, and
96        the common name in the certificate.  The temporary 'chunk' key is set
97        to the binary representation fo the credential.  The return value is a
98        tuple of ID dicts and attr dicts in that order.
99        """
100        ids = [ ]
101        attrs = [ ]
102        for e in cred_dict:
103            abac_ID = self.make_ID(self.get_cred_data(e))
104            if abac_ID:
105                id_name = abac_ID.cert_filename()
106                if id_name.endswith('_ID.pem'):
107                    id_name = id_name[0:-7]
108                e['type'] = 'identity'
109                e['str'] = abac_ID.keyid()
110                e['auxstr'] = id_name
111                e['chunk'] = abac_ID.cert_chunk()
112                ids.append(e)
113            else:
114                attrs.append(e)
115
116        return (ids, attrs)
117
118
119    def translate_attr(self, a, c, id_dict):
120        """
121        Set the 'str' and 'auxstr' fields in the given attribute dict (a) by
122        importing it into a clone of the given context and parsing the result.
123        The 'errcode' key is set as well.
124        """
125        cc = ABAC.Context(c)
126        errcode = cc.load_attribute_chunk(self.get_cred_data(a))
127        if errcode == ABAC.ABAC_CERT_SUCCESS:
128            # Success, pull it out and parse it.
129            creds = cc.credentials()
130            # There should only be one credential in the list.  If not, throw
131            # an error
132            if len(creds) == 1:
133                a['str'] = self.attr_cred_to_string(creds[0])
134                a['auxstr'] = self.repl_IDs(a['str'], id_dict)
135                a['type'] = 'attribute'
136            else:
137                raise service_error(service_error.server_config, 
138                        'Multiple credentials in Context!?')
139        else:
140            # Fail, clear the keys
141            a['str'] = ''
142            a['auxstr']= ''
143            a['type'] = 'unknown'
144        a['errcode'] = errcode
145
146
147    def translate_creds(self, req, fid):
148        """
149        Base translation routine: split the credential dicts into IDs and
150        attributes, initialize an ABAC context with all the known IDs, and
151        parse out each attribute.  Return a single list of all the modified
152        certificate dicts.  The server will encode that and return it or
153        convert any service_errors raised into an XMLRPC Fault.
154        """
155        ids, attrs = self.split_certs(req[0])
156        ctxt = ABAC.Context()
157        id_dict = { }   # Used to create auxstrs.  It maps ID cert keyid->CN
158        for i in ids:
159            if 'chunk' in i: 
160                errcode = ctxt.load_id_chunk(i['chunk'])
161                del i['chunk']
162            else: 
163                errcode = ABAC.ABAC_CERT_INVALID
164
165            if errcode == ABAC.ABAC_CERT_SUCCESS:
166                id_dict[i['str']] = i['auxstr']
167            i['errcode'] = errcode
168        for a in attrs:
169            self.translate_attr(a, ctxt, id_dict)
170        return ids + attrs
171
172
173def shutdown(sig, frame):
174    """
175    Signal handler.  Set the global active variable false if a terminating
176    signal is received.
177    """
178    global active
179    active = False
180
181# Main code
182
183# Parse args
184parser = OptParser()
185opts, args = parser.parse_args()
186
187if opts.cert:
188    # There's a certificate specified, set up as an SSL server.
189    try:
190        ctx = ssl_context(opts.cert)
191    except SSLError, e:
192        sys.exit("Cannot load %s: %s" % (opts.cert, e))
193
194    s = server(('localhost', opts.port), xmlrpc_handler, ctx, 
195            credential_printer(), True)
196else:
197    # No certificate.  Be an open server
198    s = simpleServer(('localhost', opts.port), xmlrpc_handler, 
199            credential_printer(), True)
200
201# Catch SIGINT and SIGTERM
202signal(SIGINT, shutdown)
203signal(SIGTERM, shutdown)
204
205# Do it.
206active = True
207while active:
208    try:
209        # When a request comes in handle it.  This extra selecting gives us
210        # space to catch terminating signals.
211        i, o, e = select.select((s,), (), (), 1.0)
212        if s in i: s.handle_request()
213    except select.error, e:
214        if e[0] == 4: pass
215        else: sys.exit("Unexpected error: %s" % e)
216
217sys.exit(0)
Note: See TracBrowser for help on using the repository browser.