source: cred_printer/cred_server.py

abac0-leakabac0-meimei-idmei-rt0-nmei_rt0tvf-new-xml
Last change on this file was 619702a, checked in by Ted Faber <faber@…>, 6 years ago

Update credential printer ro loss of Creddy

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