Changeset 8168 – NDG Security
Search: Login Preferences Help/Guide About Trac Wiki Timeline Roadmap Browse Source View Tickets Search Context Navigation ← Previous Changeset Next Changeset → Changeset 8168 View differences inline side by side Show lines around each change Show the changes in full context Ignore: Blank lines Case changes White space changes Timestamp: 04/10/12 17:18:32 (10 years ago) Author: pjkersha Message: Completed refactoring of middleware module subject to tests added factory module for instantiating CA class Location: trunk/online_ca_service/onlineca/server Files: 1 added 1 edited factory.py (added) wsgi/middleware.py (modified) (7 diffs) Legend: Unmodified Added Removed trunk/online_ca_service/onlineca/server/wsgi/middleware.py r8167 r8168 14 14 15 15 import httplib 16 import socket 16 import os 17 17 import base64 18 import traceback 19 18 20 19 from webob import Request 21 20 from OpenSSL import crypto 22 21 22 from onlineca.server.interfaces import OnlineCaInterface 23 from onlineca.server.factory import call_module_object 23 24 from onlineca.server.wsgi.httpbasicauth import HttpBasicAuthResponseException 24 25 … … 29 30 30 31 class OnlineCaMiddleware(object): 31 """Build on MyClientMiddleware to expose a special logon Web Service method 32 33 TODO: possible refactor to NOT inherit from MyProxyClientMiddleware but 34 instead receive a MyProxyClient instance via environ set from an upstream 35 MyProxyClientMiddleware object 36 37 @cvar LOGON_FUNC_ENV_KEYNAME_OPTNAME: ini file option name to set the key 38 name in WSGI environ dict to assign to the Logon function created by this 39 middleware 40 @type LOGON_FUNC_ENV_KEYNAME_OPTNAME: string 41 42 @cvar DEFAULT_LOGON_FUNC_ENV_KEYNAME: default value for the key name in 32 """Web service interface for issuing certificates and providing CA trust 33 roots 34 35 @cvar CA_CLASS_FACTORY_OPTNAME: config file option name for Python path to function 36 or class constructor to make a CA instance. CA instance must implement CA 37 interface class as defined in the interfaces module - 38 onlineca.server.interfaces import OnlineCaInterface 39 @type CA_CLASS_FACTORY_OPTNAME: string 40 41 @cvar DEFAULT_CA_CLASS_FACTORY: default value for the key name in 43 42 WSGI environ dict to assign to the Logon function created by this 44 43 middleware 45 @type DEFAULT_ LOGON_FUNC_ENV_KEYNAME: string 44 @type DEFAULT_ CA_CLASS_FACTORY: string 46 45 47 46 @cvar CERT_REQ_POST_PARAM_KEYNAME: HTTP POST field name for the … … 49 48 @type CERT_REQ_POST_PARAM_KEYNAME: string 50 49 51 @ivar __ logonFuncEnvironKeyName: 52 @type __ logonFuncEnvironKeyName: string 50 @ivar __ ca_class_factory_path: 51 @type __ ca_class_factory_path: string 53 52 54 53 @cvar PARAM_PREFIX: prefix for ini file option names … … 57 56 58 57 # Options for ini file 59 LOGON_FUNC_ENV_KEYNAME_OPTNAME = 'logonFuncEnvKeyName' 60 DEFAULT_GLOBAL_PASSWD_OPTNAME = 'global_passwd' 58 CA_CLASS_FACTORY_OPTNAME = 'ca_class_factory_path' 61 59 62 60 # Default environ key names 63 DEFAULT_LOGON_FUNC_ENV_KEYNAME = ('myproxy.server.wsgi.middleware.' 64 'MyProxyClientMiddleware.logon') 61 DEFAULT_CA_CLASS_FACTORY = 'ca.impl.CertificateAuthority' 65 62 66 63 CERT_REQ_POST_PARAM_KEYNAME = 'certificate_request' 67 64 68 65 __slots__ = ( 69 '__logonFuncEnvironKeyName', 70 '__global_passwd' 66 '__ca', 67 '__ca_class_factory_path', 68 '__issue_cert_path', 69 '__trustroots_path', 70 '__trustroots_dir' 71 71 ) 72 PARAM_PREFIX = 'myproxy.ws.server.logon.' 72 PARAM_PREFIX = 'onlineca.server.' 73 CA_PARAM_PREFIX = CA_CLASS_FACTORY_OPTNAME + '.' 73 74 74 75 def __init__(self, app): … … 78 79 @param app: WSGI callable for next application in stack 79 80 ''' 80 super(OnlineCaMiddleware, self).__init__(app) 81 self.__logonFuncEnvironKeyName = None 82 self.__global_passwd = None 81 self._app = None 82 self.__ca_class_factory_path = None 83 self.__issue_cert_path = None 84 self.__trustroots_path = None 85 self.__ca = None 83 86 84 def parse_config(self, prefix=PARAM_PREFIX, myProxyClientPrefix=None, 85 **app_conf): 87 def parse_config(self, prefix=PARAM_PREFIX, ca_prefix=CA_PARAM_PREFIX, 88 **app_conf): 86 89 """Parse dictionary of configuration items updating the relevant 87 90 attributes of this instance … … 97 100 """ 98 101 99 # Call parent version 100 super(OnlineCaMiddleware, self).parse_config(prefix=prefix, 101 myProxyClientPrefix=myProxyClientPrefix, **app_conf) 102 103 102 # Extract additional parameters 104 logonFuncEnvKeyOptName = prefix + \ 105 self.__class__.LOGON_FUNC_ENV_KEYNAME_OPTNAME 106 107 self.logonFuncEnvironKeyName = app_conf.get(logonFuncEnvKeyOptName, 108 self.__class__.DEFAULT_LOGON_FUNC_ENV_KEYNAME) 109 110 global_passwd_optname = prefix + \ 111 self.__class__.DEFAULT_GLOBAL_PASSWD_OPTNAME 112 113 self.__global_passwd = app_conf.get(global_passwd_optname) 114 115 @property 116 def logonFuncEnvironKeyName(self): 117 """Get MyProxyClient logon function environ key name 118 103 cls = self.__class__ 104 ca_class_factory_path_optname = prefix + cls.CA_CLASS_FACTORY_OPTNAME 105 106 self.ca_class_factory_path = app_conf.get(ca_class_factory_path_optname, 107 cls.DEFAULT_CA_CLASS_FACTORY) 108 109 ca_opt_prefix = prefix + ca_prefix 110 ca_opt_offset = len(ca_opt_prefix) 111 ca_opt = {} 112 for optname, optval in app_conf.items(): 113 if optname.startswith(ca_opt_prefix): 114 ca_optname = optname[ca_opt_offset:] 115 ca_opt[ca_optname] = optval 116 117 self.instantiate_ca(**ca_opt) 118 119 def instantiate_ca(self, **ca_object_kwargs): 120 '''Create CA class instance 121 @param ca_object_kwargs: keywords to CA class constructor 122 ''' 123 self.__ca = call_module_object(self.ca_class_factory_path, 124 object_properties=ca_object_kwargs) 125 if not isinstance(self.__ca, OnlineCaInterface): 126 raise TypeError('%r CA class factory must return a %r derived ' 127 'type' % (self.ca_class_factory_path, 128 type(OnlineCaInterface))) 129 130 @property 131 def ca_class_factory_path(self): 132 return self.__ca_class_factory_path 133 134 @ca_class_factory_path.setter 135 def ca_class_factory_path(self, value): 136 if not isinstance(value, basestring): 137 raise TypeError('Expecting string type for "ca_class_factory_path"' 138 '; got %r type' % type(value)) 139 140 self.__ca_class_factory_path = value 141 142 @property 143 def issue_cert_path(self): 144 """Get URI path for get trust roots method 119 145 @rtype: basestring 120 @return: MyProxyClient logon function environ key name 121 """ 122 return self.__logonFuncEnvironKeyName 123 124 @logonFuncEnvironKeyName.setter 125 def logonFuncEnvironKeyName(self, value): 126 """Set MyProxyClient environ key name 127 146 @return: path for get trust roots method 147 """ 148 return self.__issue_cert_path 149 150 @issue_cert_path.setter 151 def issue_cert_path(self, value): 152 """Set URI path for get trust roots method 128 153 @type value: basestring 129 @param value: MyProxyClient logon function environ key name 130 """ 131 if not isinstance(value, basestring): 132 raise TypeError('Expecting string type for ' 133 '"logonFuncEnvironKeyName"; got %r type' % 154 @param value: path for get trust roots method 155 """ 156 if not isinstance(value, basestring): 157 raise TypeError('Expecting string type for "path"; got %r' % 134 158 type(value)) 135 self.__logonFuncEnvironKeyName = value 136 159 160 self.__issue_cert_path = value 161 162 @property 163 def trustroots_path(self): 164 """Get URI path for get trust roots method 165 @rtype: basestring 166 @return: path for get trust roots method 167 """ 168 return self.__trustroots_path 169 170 @trustroots_path.setter 171 def trustroots_path(self, value): 172 """trust roots path 173 """ 174 if not isinstance(value, basestring): 175 raise TypeError('Expecting string type for "path"; got %r' % 176 type(value)) 177 178 self.__trustroots_path = value 179 180 @property 181 def trustroots_dir(self): 182 """Get trust roots dir 183 """ 184 return self.__trustroots_dir 185 186 @trustroots_dir.setter 187 def trustroots_dir(self, value): 188 """trust roots dir 189 """ 190 if not isinstance(value, basestring): 191 raise TypeError('Expecting string type for "path"; got %r' % 192 type(value)) 193 194 self.__trustroots_dir = value 195 137 196 def __call__(self, environ, start_response): 138 197 '''Set MyProxy logon method in environ … … 143 202 @param start_response: standard WSGI start response function 144 203 ''' 145 log.debug("MyProxyClientMiddleware.__call__ ...") 146 environ[self.logonFuncEnvironKeyName] = self.myProxyLogon 147 148 return super(OnlineCaMiddleware, self).__call__(environ, 149 start_response) 150 151 @property 152 def myProxyLogon(self): 153 """Return the MyProxy logon method wrapped as a HTTP Basic Auth 154 authenticate interface function 155 156 @rtype: function 157 @return: MyProxy logon HTTP Basic Auth Callback 158 """ 159 def _myProxylogon(environ, start_response, username, password): 160 """Wrap MyProxy logon method as a WSGI app 161 @type environ: dict 162 @param environ: WSGI environment variables dictionary 163 @type start_response: function 164 @param start_response: standard WSGI start response function 165 @type username: basestring 166 @param username: username credential to MyProxy logon 167 @type password: basestring 168 @param password: pass-phrase for MyProxy logon call 169 @raise HttpBasicAuthResponseException: invalid client request 170 @raise MyProxyClientMiddlewareError: socket error for backend 171 MyProxy server 172 """ 173 request = Request(environ) 174 175 requestMethod = environ.get('REQUEST_METHOD') 176 if requestMethod != 'POST': 177 response = "HTTP Request method not recognised" 178 log.error("HTTP Request method %r not recognised", 179 requestMethod) 180 raise HttpBasicAuthResponseException(response, 181 httplib.METHOD_NOT_ALLOWED) 182 183 # Extract cert request and convert to standard string - SSL library 184 # will not accept unicode 185 cert_req_key = self.__class__.CERT_REQ_POST_PARAM_KEYNAME 186 pem_cert_req = str(request.POST.get(cert_req_key)) 187 if pem_cert_req is None: 188 response = ("No %r form variable set in POST message" % 189 cert_req_key) 190 log.error(response) 191 raise HttpBasicAuthResponseException(response, 192 httplib.BAD_REQUEST) 193 194 log.debug("cert req = %r", pem_cert_req) 195 196 # Expecting PEM encoded request 197 try: 198 cert_req = crypto.load_certificate_request(crypto.FILETYPE_PEM, 199 pem_cert_req) 200 except crypto.Error, e: 201 log.error("Error loading input certificate request: %r", 202 pem_cert_req) 203 raise HttpBasicAuthResponseException("Error loading input " 204 "certificate request", 205 httplib.BAD_REQUEST) 206 207 # Convert to ASN1 format expect by logon client call 208 asn1CertReq = crypto.dump_certificate_request(crypto.FILETYPE_ASN1, 209 cert_req) 210 211 # A global password can be set for the MyProxy call. This is used 212 # for the special case where this service is providing delegation 213 # The MyProxyCA uses a special PAM with a single password set for 214 # all usernames. Clients to this service must be protected by 215 # SSL client authentication 216 if self.__global_passwd is not None: 217 password_ = self.__global_passwd 218 else: 219 password_ = password 220 221 try: 222 credentials = self.myProxyClient.logon(username, 223 password_, 224 certReq=asn1CertReq) 225 status = self.getStatusMessage(httplib.OK) 226 response = '\n'.join(credentials) 227 228 start_response(status, 229 [('Content-length', str(len(response))), 230 ('Content-type', 'text/plain')]) 231 return [response] 232 233 except MyProxyClientError, e: 234 raise HttpBasicAuthResponseException(str(e), 235 httplib.UNAUTHORIZED) 236 except socket.error, e: 237 raise OnlineCaMiddlewareError("Socket error " 238 "with MyProxy server %r: %s" % 239 (self.myProxyClient.hostname, e)) 240 except Exception, e: 241 log.error("MyProxyClient.logon raised an unknown exception " 242 "calling %r: %s", 243 self.myProxyClient.hostname, 244 traceback.format_exc()) 245 raise # Trigger 500 Internal Server Error 246 247 return _myProxylogon 248 249 250 class OnlineCaGetTrustRootsMiddlewareError(Exception): 251 """OnlineCaGetTrustRootsMiddleware exception class""" 252 253 254 class OnlineCaGetTrustRootsMiddleware(MyProxyClientMiddlewareBase): 255 """HTTP client interface for MyProxy server Get Trust Roots method 256 257 It relies on a myproxy.server.wsgi.MyProxyClientMiddleware instance called 258 upstream in the WSGI stack to set up a MyProxyClient instance and make it 259 available in the environ to call its getTrustRoots method. 260 261 @cvar PATH_OPTNAME: ini file option to set the URI path for this service 262 @type PATH_OPTNAME: string 263 264 @cvar DEFAULT_PATH: default URI path setting 265 @type DEFAULT_PATH: string 266 267 @cvar PARAM_PREFIX: prefix for ini file option names 268 @type PARAM_PREFIX: string 269 270 @ivar __path: URI path setting for this service 271 @type __path: basestring 272 """ 273 274 PATH_OPTNAME = 'path' 275 DEFAULT_PATH = '/myproxy/get-trustroots' 276 277 # Option prefixes 278 PARAM_PREFIX = 'myproxy.getTrustRoots.' 279 280 __slots__ = ( 281 '__path', 282 ) 283 284 def __init__(self, app): 285 '''Create attributes 286 287 @type app: function 288 @param app: WSGI callable for next application in stack 289 ''' 290 super(OnlineCaGetTrustRootsMiddleware, self).__init__(app) 291 self.__path = None 292 293 @classmethod 294 def filter_app_factory(cls, app, global_conf, prefix=PARAM_PREFIX, 295 **app_conf): 296 """Function following Paste filter app factory signature 297 298 @type app: callable following WSGI interface 299 @param app: next middleware/application in the chain 300 @type global_conf: dict 301 @param global_conf: PasteDeploy global configuration dictionary 302 @type prefix: basestring 303 @param prefix: prefix for configuration items 304 @type app_conf: dict 305 @param app_conf: PasteDeploy application specific configuration 306 dictionary 307 308 @rtype: myproxy.server.wsgi.middleware.OnlineCaGetTrustRootsMiddleware 309 @return: an instance of this middleware 310 """ 311 app = cls(app) 312 app.parse_config(prefix=prefix, **app_conf) 313 return app 314 315 def parse_config(self, prefix=PARAM_PREFIX, **app_conf): 316 """Parse dictionary of configuration items updating the relevant 317 attributes of this instance 318 319 @type prefix: basestring 320 @param prefix: prefix for configuration items 321 @type app_conf: dict 322 @param app_conf: PasteDeploy application specific configuration 323 dictionary 324 """ 325 clientEnvKeyOptName = prefix + self.__class__.CLIENT_ENV_KEYNAME_OPTNAME 326 327 self.clientEnvironKeyName = app_conf.get(clientEnvKeyOptName, 328 self.__class__.DEFAULT_CLIENT_ENV_KEYNAME) 329 330 pathOptName = prefix + self.__class__.PATH_OPTNAME 331 self.path = app_conf.get(pathOptName, self.__class__.DEFAULT_PATH) 332 333 def _getPath(self): 334 """Get URI path for get trust roots method 335 @rtype: basestring 336 @return: path for get trust roots method 337 """ 338 return self.__path 339 340 def _setPath(self, value): 341 """Set URI path for get trust roots method 342 @type value: basestring 343 @param value: path for get trust roots method 344 """ 345 if not isinstance(value, basestring): 346 raise TypeError('Expecting string type for "path"; got %r' % 347 type(value)) 348 349 self.__path = value 350 351 path = property(fget=_getPath, fset=_setPath, 352 doc="environ SCRIPT_NAME path which invokes the " 353 "getTrustRoots method on this middleware") 354 355 def __call__(self, environ, start_response): 356 '''Get MyProxyClient instance from environ and call MyProxy 357 getTrustRoots method returning the response. 358 359 MyProxyClientMiddleware must be in place upstream in the WSGI stack 360 361 @type environ: dict 362 @param environ: WSGI environment variables dictionary 363 @type start_response: function 364 @param start_response: standard WSGI start response function 365 366 @rtype: list 367 @return: get trust roots response 368 ''' 369 # Skip if path doesn't match 370 if environ['PATH_INFO'] != self.path: 371 return self.app(environ, start_response) 372 373 log.debug("OnlineCaGetTrustRootsMiddleware.__call__ ...") 374 375 # Check method 376 requestMethod = environ.get('REQUEST_METHOD') 377 if requestMethod != 'GET': 378 response = "HTTP Request method not recognised" 379 log.error("HTTP Request method %r not recognised", requestMethod) 380 status = self.__class__.getStatusMessage(httplib.BAD_REQUEST) 381 start_response(status, 382 [('Content-type', 'text/plain'), 383 ('Content-length', str(len(response)))]) 384 return [response] 385 386 myProxyClient = environ[self.clientEnvironKeyName] 387 if not isinstance(myProxyClient, MyProxyClient): 388 raise TypeError('Expecting %r type for "myProxyClient" environ[%r] ' 389 'attribute got %r' % (MyProxyClient, 390 self.clientEnvironKeyName, 391 type(myProxyClient))) 392 393 response = self._getTrustRoots(myProxyClient) 394 start_response(self.getStatusMessage(httplib.OK), 204 log.debug("OnlineCaMiddleware.__call__ ...") 205 206 path_info = environ['PATH_INFO'] 207 if path_info == self.__issue_cert_path: 208 response = self._issue_certificate(environ) 209 210 elif path_info == self.__trustroots_path: 211 response = self._get_trustroots() 212 213 else: 214 return self._app(environ, start_response) 215 216 start_response('200 OK', 395 217 [('Content-length', str(len(response))), 396 218 ('Content-type', 'text/plain')]) 397 398 219 return [response] 399 400 @classmethod 401 def _getTrustRoots(cls, myProxyClient): 220 221 def _issue_certificate(self, environ): 222 request = Request(environ) 223 224 request_method = environ.get('REQUEST_METHOD') 225 if request_method != 'POST': 226 response = "HTTP Request method not recognised" 227 log.error("HTTP Request method %r not recognised", request_method) 228 raise HttpBasicAuthResponseException(response, 229 httplib.METHOD_NOT_ALLOWED) 230 231 # Extract cert request and convert to standard string - SSL library 232 # will not accept unicode 233 cert_req_key = self.__class__.CERT_REQ_POST_PARAM_KEYNAME 234 pem_cert_req = str(request.POST.get(cert_req_key)) 235 if pem_cert_req is None: 236 response = ("No %r form variable set in POST message" % 237 cert_req_key) 238 log.error(response) 239 raise HttpBasicAuthResponseException(response, 240 httplib.BAD_REQUEST) 241 242 log.debug("certificate request = %r", pem_cert_req) 243 244 # Expecting PEM encoded request 245 try: 246 cert_req = crypto.load_certificate_request(crypto.FILETYPE_PEM, 247 pem_cert_req) 248 except crypto.Error: 249 log.error("Error loading input certificate request: %r", 250 pem_cert_req) 251 raise HttpBasicAuthResponseException("Error loading input " 252 "certificate request", 253 httplib.BAD_REQUEST) 254 255 cert = self.__ca.issue_certificate(cert_req) 256 return cert 257 258 def _get_trustroots(self): 402 259 """Call getTrustRoots method on MyProxyClient instance retrieved from 403 260 environ and format and return a HTTP response 404 261 405 @type myProxyClient: myproxy.client.MyProxyClient 406 @param myProxyClient: MyProxyClient instance on which to call 407 getTrustRoots method 408 409 262 @rtype: basestring 410 263 @return: trust roots base64 encoded and concatenated together 411 @raise OnlineCaGetTrustRootsMiddlewareError: socket error with backend 412 MyProxy server 413 @raise MyProxyClientError: error response received by MyProxyClient 414 instance 415 """ 416 try: 417 trustRoots = myProxyClient.getTrustRoots() 418 419 # Serialise dict response 420 response = "\n".join(["%s=%s" % (k, base64.b64encode(v)) 421 for k,v in trustRoots.items()]) 422 423 return response 424 425 except MyProxyClientError, e: 426 log.error("MyProxyClient.getTrustRoots raised an " 427 "MyProxyClientError exception calling %r: %s", 428 myProxyClient.hostname, 429 traceback.format_exc()) 430 raise 431 432 except socket.error, e: 433 raise OnlineCaGetTrustRootsMiddlewareError("Socket error with " 434 "MyProxy server %r: %s" % 435 (myProxyClient.hostname, 436 e)) 437 except Exception, e: 438 log.error("MyProxyClient.getTrustRoots raised an unknown exception " 439 "calling %r: %s", 440 myProxyClient.hostname, 441 traceback.format_exc()) 442 raise # Trigger 500 Internal Server Error 443 264 """ 265 trust_roots = '' 266 for filename in os.listdir(self.trustroots_dir): 267 file_content = open(filename).read() 268 269 trust_roots += "%s=%s\n" % (filename, 270 base64.b64encode(file_content)) 271 272 return trust_roots Note: See TracChangeset for help on using the changeset viewer. Download in other formats: Unified Diff Zip Archive Powered by Trac 0.12.2 By Edgewall Software. Visit the Trac open source project at http://trac.edgewall.org/