class CSREndpoints(object): def __init__(self, core): self._core = weakref.ref(core) self._certs = Certs() self._auto_allow_csr = False @property def auto_allow_csr(self): return self._auto_allow_csr @auto_allow_csr.setter def auto_allow_csr(self, value): self._auto_allow_csr = value def get_routes(self): """ Returns a list of tuples with the routes for authentication. Tuple should have the following: - regular expression for calling the endpoint - 'callable' keyword specifying that a method is being specified - the method that should be used to call when the regular expression matches code: return [ (re.compile('^/csr/request_new$'), 'callable', self._csr_request_new) ] :return: """ return [(re.compile('^/csr/request_new$'), 'callable', self._csr_request_new)] def _csr_request_new(self, env, data): _log.debug("New csr request") if not isinstance(data, dict): try: request_data = jsonapi.loads(data) except: _log.error( "Invalid data for csr request. Must be json serializable") return Response() else: request_data = data.copy() csr = request_data.get('csr').encode("utf-8") identity = self._certs.get_csr_common_name(csr) # The identity must start with the current instances name or it is a failure. if not identity.startswith(get_platform_instance_name() + "."): json_response = dict( status="ERROR", message="CSR must start with instance name: {}".format( get_platform_instance_name())) Response(jsonapi.dumps(json_response), content_type='application/json', headers={'Content-type': 'application/json'}) csr_file = self._certs.save_pending_csr_request( env.get('REMOTE_ADDR'), identity, csr) if self._auto_allow_csr: _log.debug( "Creating cert and permissions for user: {}".format(identity)) status = self._certs.get_csr_status(identity) json_response = dict(status=status) if status == 'APPROVED': try: json_response['cert'] = self._certs.get_cert_from_csr( identity) except Exception as e: _log.error(f"Exception getting cert from csr {e}") else: try: cert = self._certs.approve_csr(identity) #permissions = self._core().rmq_mgmt.get_default_permissions(identity) _log.debug(f"CREATING NEW RMQ USER: {identity}") permissions = dict(configure=".*", read=".*", write=".*") self._core().rmq_mgmt.create_user_with_permissions( identity, permissions, True) json_response = dict(status="SUCCESSFUL", cert=cert) except BaseException as e: _log.error( f"Exception in approving csr/creating user in auto_allow_csr mode: {e}" ) else: status = self._certs.get_csr_status(identity) cert = self._certs.get_cert_from_csr(identity) json_response = dict(status=status) if status == "APPROVED": json_response['cert'] = self._certs.get_cert_from_csr(identity) elif status == "PENDING": json_response[ 'message'] = "The request is pending admininstrator approval." elif status == "DENIED": json_response[ 'message'] = "The request has been denied by the administrator." elif status == "UNKNOWN": json_response[ 'message'] = "An unknown common name was specified to the server {}".format( identity) else: json_response[ 'message'] = "An unkonwn error has occured during the respons phase" response = None try: if json_response.get('cert', None): json_response['cert'] = json_response['cert'].decode('utf-8') response = Response(jsonapi.dumps(json_response), content_type='application/json', headers={'Content-type': 'application/json'}) except BaseException as e: _log.error(f"Exception creating Response {e}") return response
class AdminEndpoints(object): def __init__(self, rmq_mgmt, ssl_public_key): self._rmq_mgmt = rmq_mgmt self._ssl_public_key = ssl_public_key self._userdict = None self.reload_userdict() self._observer = Observer() self._observer.schedule( FileReloader("web-users.json", self.reload_userdict), get_home()) self._observer.start() self._certs = Certs() def reload_userdict(self): webuserpath = os.path.join(get_home(), 'web-users.json') self._userdict = PersistentDict(webuserpath) def get_routes(self): """ Returns a list of tuples with the routes for the adminstration endpoints available in it. :return: """ return [(re.compile('^/admin.*'), 'callable', self.admin)] def admin(self, env, data): if len(self._userdict) == 0: if env.get('REQUEST_METHOD') == 'POST': decoded = dict((k, v if len(v) > 1 else v[0]) for k, v in urlparse.parse_qs(data).iteritems()) username = decoded.get('username') pass1 = decoded.get('password1') pass2 = decoded.get('password2') if pass1 == pass2 and pass1 is not None: _log.debug("Setting master password") self.add_user(username, pass1, groups=['admin']) return Response('', status='302', headers={'Location': '/admin/login.html'}) template = template_env(env).get_template('first.html') return Response(template.render()) if 'login.html' in env.get('PATH_INFO') or '/admin/' == env.get( 'PATH_INFO'): template = template_env(env).get_template('login.html') return Response(template.render()) return self.verify_and_dispatch(env, data) def verify_and_dispatch(self, env, data): """ Verify that the user is an admin and dispatch :param env: web environment :param data: data associated with a web form or json/xml request data :return: Response object. """ from volttron.platform.web import get_user_claims, NotAuthorized try: claims = get_user_claims(env) except NotAuthorized: _log.error("Unauthorized user attempted to connect to {}".format( env.get('PATH_INFO'))) return Response('<h1>Unauthorized User</h1>', status="401 Unauthorized") # Make sure we have only admins for viewing this. if 'admin' not in claims.get('groups'): return Response('<h1>Unauthorized User</h1>', status="401 Unauthorized") # Make sure we have only admins for viewing this. if 'admin' not in claims.get('groups'): return Response('<h1>Unauthorized User</h1>', status="401 Unauthorized") path_info = env.get('PATH_INFO') if path_info.startswith('/admin/api/'): return self.__api_endpoint(path_info[len('/admin/api/'):], data) if path_info.endswith('html'): page = path_info.split('/')[-1] try: template = template_env(env).get_template(page) except TemplateNotFound: return Response("<h1>404 Not Found</h1>", status="404 Not Found") if page == 'list_certs.html': html = template.render( certs=self._certs.get_all_cert_subjects()) elif page == 'pending_csrs.html': html = template.render( csrs=self._certs.get_pending_csr_requests()) else: # A template with no params. html = template.render() return Response(html) template = template_env(env).get_template('index.html') resp = template.render() return Response(resp) def __api_endpoint(self, endpoint, data): _log.debug("Doing admin endpoint {}".format(endpoint)) if endpoint == 'certs': response = self.__cert_list_api() elif endpoint == 'pending_csrs': response = self.__pending_csrs_api() elif endpoint.startswith('approve_csr/'): response = self.__approve_csr_api(endpoint.split('/')[1]) elif endpoint.startswith('deny_csr/'): response = self.__deny_csr_api(endpoint.split('/')[1]) elif endpoint.startswith('delete_csr/'): response = self.__delete_csr_api(endpoint.split('/')[1]) else: response = Response( '{"status": "Unknown endpoint {}"}'.format(endpoint), content_type="application/json") return response def __approve_csr_api(self, common_name): try: _log.debug("Creating cert and permissions for user: {}".format( common_name)) self._certs.approve_csr(common_name) permissions = self._rmq_mgmt.get_default_permissions(common_name) self._rmq_mgmt.create_user_with_permissions( common_name, permissions, True) data = dict(status=self._certs.get_csr_status(common_name), cert=self._certs.get_cert_from_csr(common_name)) except ValueError as e: data = dict(status="ERROR", message=e.message) return Response(json.dumps(data), content_type="application/json") def __deny_csr_api(self, common_name): try: self._certs.deny_csr(common_name) data = dict(status="DENIED", message="The administrator has denied the request") except ValueError as e: data = dict(status="ERROR", message=e.message) return Response(json.dumps(data), content_type="application/json") def __delete_csr_api(self, common_name): try: self._certs.delete_csr(common_name) data = dict(status="DELETED", message="The administrator has denied the request") except ValueError as e: data = dict(status="ERROR", message=e.message) return Response(json.dumps(data), content_type="application/json") def __pending_csrs_api(self): csrs = [c for c in self._certs.get_pending_csr_requests()] return Response(json.dumps(csrs), content_type="application/json") def __cert_list_api(self): subjects = [ dict(common_name=x.common_name) for x in self._certs.get_all_cert_subjects() ] return Response(json.dumps(subjects), content_type="application/json") def add_user(self, username, unencrypted_pw, groups=[], overwrite=False): if self._userdict.get(username): raise ValueError("Already exists!") if groups is None: groups = [] hashed_pass = argon2.hash(unencrypted_pw) self._userdict[username] = dict(hashed_password=hashed_pass, groups=groups) self._userdict.async_sync()