source: cred_printer/cred_server.py @ 25ae6a3

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

Patch from Tom Mitchell: Add tail and head structs and pretty printing
them. cred_client.py in 811bc273bdd1b0b00512202c019614adb7a32977
includes pretty printing changes (from faber)

  • Property mode set to 100755
File size: 6.7 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                # Process the attribute head
137                head = creds[0].head()
138                a['head'] = dict()
139                a['head']['principal'] = head.principal()
140                a['head']['role'] = head.role_name()
141                a['head']['pretty_principal'] = self.repl_IDs(head.principal(),
142                                                              id_dict)
143                # Process the attribute tail
144                tail = creds[0].tail()
145                a['tail'] = dict()
146                a['tail']['principal'] = tail.principal()
147                a['tail']['pretty_principal'] = self.repl_IDs(tail.principal(),
148                                                              id_dict)
149                if tail.is_role():
150                    a['tail']['role'] = tail.role_name()
151                elif tail.is_linking():
152                    a['tail']['role'] = tail.role_name()
153                    a['tail']['linked_role'] = tail.linked_role()
154            else:
155                raise service_error(service_error.server_config, 
156                        'Multiple credentials in Context!?')
157        else:
158            # Fail, clear the keys
159            a['str'] = ''
160            a['auxstr']= ''
161            a['type'] = 'unknown'
162        a['errcode'] = errcode
163
164
165    def translate_creds(self, req, fid):
166        """
167        Base translation routine: split the credential dicts into IDs and
168        attributes, initialize an ABAC context with all the known IDs, and
169        parse out each attribute.  Return a single list of all the modified
170        certificate dicts.  The server will encode that and return it or
171        convert any service_errors raised into an XMLRPC Fault.
172        """
173        ids, attrs = self.split_certs(req[0])
174        ctxt = ABAC.Context()
175        id_dict = { }   # Used to create auxstrs.  It maps ID cert keyid->CN
176        for i in ids:
177            if 'chunk' in i: 
178                errcode = ctxt.load_id_chunk(i['chunk'])
179                del i['chunk']
180            else: 
181                errcode = ABAC.ABAC_CERT_INVALID
182
183            if errcode == ABAC.ABAC_CERT_SUCCESS:
184                id_dict[i['str']] = i['auxstr']
185            i['errcode'] = errcode
186        for a in attrs:
187            self.translate_attr(a, ctxt, id_dict)
188        return ids + attrs
189
190
191def shutdown(sig, frame):
192    """
193    Signal handler.  Set the global active variable false if a terminating
194    signal is received.
195    """
196    global active
197    active = False
198
199# Main code
200
201# Parse args
202parser = OptParser()
203opts, args = parser.parse_args()
204
205if opts.cert:
206    # There's a certificate specified, set up as an SSL server.
207    try:
208        ctx = ssl_context(opts.cert)
209    except SSLError, e:
210        sys.exit("Cannot load %s: %s" % (opts.cert, e))
211
212    s = server(('localhost', opts.port), xmlrpc_handler, ctx, 
213            credential_printer(), True)
214else:
215    # No certificate.  Be an open server
216    s = simpleServer(('localhost', opts.port), xmlrpc_handler, 
217            credential_printer(), True)
218
219# Catch SIGINT and SIGTERM
220signal(SIGINT, shutdown)
221signal(SIGTERM, shutdown)
222
223# Do it.
224active = True
225while active:
226    try:
227        # When a request comes in handle it.  This extra selecting gives us
228        # space to catch terminating signals.
229        i, o, e = select.select((s,), (), (), 1.0)
230        if s in i: s.handle_request()
231    except select.error, e:
232        if e[0] == 4: pass
233        else: sys.exit("Unexpected error: %s" % e)
234
235sys.exit(0)
Note: See TracBrowser for help on using the repository browser.