add some basic logging output to certmaster
[certmaster.git] / certmaster / minion / server.py
1 """
2 func
3
4 Copyright 2007, Red Hat, Inc
5 see AUTHORS
6
7 This software may be freely redistributed under the terms of the GNU
8 general public license.
9
10 You should have received a copy of the GNU General Public License
11 along with this program; if not, write to the Free Software
12 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
13 """
14
15 # standard modules
16 import SimpleXMLRPCServer
17 import string
18 import sys
19 import traceback
20 import socket
21 import fnmatch
22
23 from gettext import textdomain
24 I18N_DOMAIN = "func"
25
26
27 from func.config import read_config
28 from func.commonconfig import FuncdConfig
29 from func import logger
30 from func import certs
31 import func.jobthing as jobthing
32 import utils
33
34 # our modules
35 import AuthedXMLRPCServer
36 import codes
37 import module_loader
38 import func.utils as futils
39
40
41
42 class XmlRpcInterface(object):
43
44 def __init__(self):
45
46 """
47 Constructor.
48 """
49
50 config_file = '/etc/func/minion.conf'
51 self.config = read_config(config_file, FuncdConfig)
52 self.logger = logger.Logger().logger
53 self.audit_logger = logger.AuditLogger()
54 self.__setup_handlers()
55
56 # need a reference so we can log ip's, certs, etc
57 # self.server = server
58
59 def __setup_handlers(self):
60
61 """
62 Add RPC functions from each class to the global list so they can be called.
63 """
64
65 self.handlers = {}
66 for x in self.modules.keys():
67 try:
68 self.modules[x].register_rpc(self.handlers, x)
69 self.logger.debug("adding %s" % x)
70 except AttributeError, e:
71 self.logger.warning("module %s not loaded, missing register_rpc method" % self.modules[x])
72
73
74 # internal methods that we do instead of spreading internal goo
75 # all over the modules. For now, at lest -akl
76
77
78 # system.listMethods os a quasi stanard xmlrpc method, so
79 # thats why it has a odd looking name
80 self.handlers["system.listMethods"] = self.list_methods
81 self.handlers["system.list_methods"] = self.list_methods
82 self.handlers["system.list_modules"] = self.list_modules
83
84 def list_modules(self):
85 modules = self.modules.keys()
86 modules.sort()
87 return modules
88
89 def list_methods(self):
90 methods = self.handlers.keys()
91 methods.sort()
92 return methods
93
94 def get_dispatch_method(self, method):
95
96 if method in self.handlers:
97 return FuncApiMethod(self.logger, method, self.handlers[method])
98
99 else:
100 self.logger.info("Unhandled method call for method: %s " % method)
101 raise codes.InvalidMethodException
102
103
104 class FuncApiMethod:
105
106 """
107 Used to hold a reference to all of the registered functions.
108 """
109
110 def __init__(self, logger, name, method):
111
112 self.logger = logger
113 self.__method = method
114 self.__name = name
115
116 def __log_exc(self):
117
118 """
119 Log an exception.
120 """
121
122 (t, v, tb) = sys.exc_info()
123 self.logger.info("Exception occured: %s" % t )
124 self.logger.info("Exception value: %s" % v)
125 self.logger.info("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb))))
126
127 def __call__(self, *args):
128
129 self.logger.debug("(X) -------------------------------------------")
130
131 try:
132 rc = self.__method(*args)
133 except codes.FuncException, e:
134 self.__log_exc()
135 (t, v, tb) = sys.exc_info()
136 rc = futils.nice_exception(t,v,tb)
137 except:
138 self.__log_exc()
139 (t, v, tb) = sys.exc_info()
140 rc = futils.nice_exception(t,v,tb)
141 self.logger.debug("Return code for %s: %s" % (self.__name, rc))
142
143 return rc
144
145
146 def serve():
147
148 """
149 Code for starting the XMLRPC service.
150 """
151 server =FuncSSLXMLRPCServer(('', 51234))
152 server.logRequests = 0 # don't print stuff to console
153 server.serve_forever()
154
155
156
157 class FuncXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer, XmlRpcInterface):
158
159 def __init__(self, args):
160
161 self.allow_reuse_address = True
162
163 self.modules = module_loader.load_modules()
164 SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, args)
165 XmlRpcInterface.__init__(self)
166
167
168 class FuncSSLXMLRPCServer(AuthedXMLRPCServer.AuthedSSLXMLRPCServer,
169 XmlRpcInterface):
170 def __init__(self, args):
171 self.allow_reuse_address = True
172 self.modules = module_loader.load_modules()
173
174 XmlRpcInterface.__init__(self)
175 hn = utils.get_hostname()
176 self.key = "%s/%s.pem" % (self.config.cert_dir, hn)
177 self.cert = "%s/%s.cert" % (self.config.cert_dir, hn)
178 self.ca = "%s/ca.cert" % self.config.cert_dir
179
180 self._our_ca = certs.retrieve_cert_from_file(self.ca)
181
182 AuthedXMLRPCServer.AuthedSSLXMLRPCServer.__init__(self, ("", 51234),
183 self.key, self.cert,
184 self.ca)
185
186 def _dispatch(self, method, params):
187
188 """
189 the SimpleXMLRPCServer class will call _dispatch if it doesn't
190 find a handler method
191 """
192 # take _this_request and hand it off to check out the acls of the method
193 # being called vs the requesting host
194
195 if not hasattr(self, '_this_request'):
196 raise codes.InvalidMethodException
197
198 r,a = self._this_request
199 peer_cert = r.get_peer_certificate()
200 ip = a[0]
201
202
203 # generally calling conventions are: hardware.info
204 # async convention is async.hardware.info
205 # here we parse out the async to decide how to invoke it.
206 # see the async docs on the Wiki for further info.
207 async_dispatch = False
208 if method.startswith("async."):
209 async_dispatch = True
210 method = method.replace("async.","",1)
211
212 if not self._check_acl(peer_cert, ip, method, params):
213 raise codes.AccessToMethodDenied
214
215 # Recognize ipython's tab completion calls
216 if method == 'trait_names' or method == '_getAttributeNames':
217 return self.handlers.keys()
218
219 cn = peer_cert.get_subject().CN
220 sub_hash = peer_cert.subject_name_hash()
221 self.audit_logger.log_call(ip, cn, sub_hash, method, params)
222
223 try:
224 if not async_dispatch:
225 return self.get_dispatch_method(method)(*params)
226 else:
227 return jobthing.minion_async_run(self.get_dispatch_method, method, params)
228 except:
229 (t, v, tb) = sys.exc_info()
230 rc = futils.nice_exception(t, v, tb)
231 return rc
232
233 def auth_cb(self, request, client_address):
234 peer_cert = request.get_peer_certificate()
235 return peer_cert.get_subject().CN
236
237 def _check_acl(self, cert, ip, method, params):
238 acls = utils.get_acls_from_config(acldir=self.config.acl_dir)
239
240 # certmaster always gets to run things
241 ca_cn = self._our_ca.get_subject().CN
242 ca_hash = self._our_ca.subject_name_hash()
243 ca_key = '%s-%s' % (ca_cn, ca_hash)
244 acls[ca_key] = ['*']
245
246 cn = cert.get_subject().CN
247 sub_hash = cert.subject_name_hash()
248 if acls:
249 allow_list = []
250 hostkey = '%s-%s' % (cn, sub_hash)
251 # search all the keys, match to 'cn-subhash'
252 for hostmatch in acls.keys():
253 if fnmatch.fnmatch(hostkey, hostmatch):
254 allow_list.extend(acls[hostmatch])
255 # go through the allow_list and make sure this method is in there
256 for methodmatch in allow_list:
257 if fnmatch.fnmatch(method, methodmatch):
258 return True
259
260 return False
261
262
263 def main(argv):
264
265 """
266 Start things up.
267 """
268
269 if "daemon" in sys.argv or "--daemon" in sys.argv:
270 futils.daemonize("/var/run/funcd.pid")
271 else:
272 print "serving...\n"
273
274 try:
275 utils.create_minion_keys()
276 serve()
277 except codes.FuncException, e:
278 print >> sys.stderr, 'error: %s' % e
279 sys.exit(1)
280
281
282 # ======================================================================================
283 if __name__ == "__main__":
284 textdomain(I18N_DOMAIN)
285 main(sys.argv)