def filter_command(command, rootwrap_config): # Load rootwrap configuration try: rawconfig = ConfigParser.RawConfigParser() rawconfig.read(rootwrap_config) rw_config = wrapper.RootwrapConfig(rawconfig) except ValueError as exc: LOG.error(_LE('Incorrect value in %(config)s: %(exc)s'), { 'config': rootwrap_config, 'exc': exc.message }) sys.exit(errno.EINVAL) except ConfigParser.Error: LOG.error(_LE('Incorrect configuration file: %(config)s'), {'config': rootwrap_config}) sys.exit(errno.EINVAL) # Check if command matches any of the loaded filters filters = wrapper.load_filters(rw_config.filters_path) try: wrapper.match_filter(filters, command, exec_dirs=rw_config.exec_dirs) except wrapper.FilterMatchNotExecutable as exc: LOG.error( _LE('Command %(command)s is not executable: ' '%(path)s (filter match = %(name)s)'), { 'command': command, 'path': exc.match.exec_path, 'name': exc.match.name }) sys.exit(errno.EINVAL) except wrapper.NoFilterMatched: LOG.error(_LE('Unauthorized command: %(cmd)s (no filter matched)'), {'cmd': command}) sys.exit(errno.EPERM)
def filter_command(command, rootwrap_config): # Load rootwrap configuration try: rawconfig = ConfigParser.RawConfigParser() rawconfig.read(rootwrap_config) rw_config = wrapper.RootwrapConfig(rawconfig) except ValueError as exc: LOG.error(_LE('Incorrect value in %(config)s: %(exc)s'), {'config': rootwrap_config, 'exc': exc.message}) sys.exit(errno.EINVAL) except ConfigParser.Error: LOG.error(_LE('Incorrect configuration file: %(config)s'), {'config': rootwrap_config}) sys.exit(errno.EINVAL) # Check if command matches any of the loaded filters filters = wrapper.load_filters(rw_config.filters_path) try: wrapper.match_filter(filters, command, exec_dirs=rw_config.exec_dirs) except wrapper.FilterMatchNotExecutable as exc: LOG.error(_LE('Command %(command)s is not executable: ' '%(path)s (filter match = %(name)s)'), {'command': command, 'path': exc.match.exec_path, 'name': exc.match.name}) sys.exit(errno.EINVAL) except wrapper.NoFilterMatched: LOG.error(_LE('Unauthorized command: %(cmd)s (no filter matched)'), {'cmd': command}) sys.exit(errno.EPERM)
def _request(self, method, url, **kwargs): """Perform REST request and save response info.""" try: LOG.debug( "%(method)s: Request for %(resource)s payload: " "%(payload)s", { 'method': method.upper(), 'resource': url, 'payload': kwargs.get('data') }) start_time = time.time() response = self.session.request(method, url, verify=False, timeout=self.timeout, **kwargs) LOG.debug("%(method)s Took %(time).2f seconds to process", { 'method': method.upper(), 'time': time.time() - start_time }) except (r_exc.Timeout, r_exc.SSLError) as te: # Should never see SSLError, unless requests package is old (<2.0) timeout_val = 0.0 if self.timeout is None else self.timeout LOG.warning( _LW("%(method)s: Request timeout%(ssl)s " "(%(timeout).3f sec) for CSR(%(host)s)"), { 'method': method, 'timeout': timeout_val, 'ssl': '(SSLError)' if isinstance(te, r_exc.SSLError) else '', 'host': self.host }) self.status = requests.codes.REQUEST_TIMEOUT except r_exc.ConnectionError: LOG.exception( _LE("%(method)s: Unable to connect to " "CSR(%(host)s)"), { 'method': method, 'host': self.host }) self.status = requests.codes.NOT_FOUND except Exception as e: LOG.error( _LE("%(method)s: Unexpected error for CSR (%(host)s): " "%(error)s"), { 'method': method, 'host': self.host, 'error': e }) self.status = requests.codes.INTERNAL_SERVER_ERROR else: self.status = response.status_code LOG.debug("%(method)s: Completed [%(status)s]", { 'method': method, 'status': self.status }) return self._response_info_for(response, method)
def execute_with_mount(): conf = setup_conf() conf() config.setup_logging() if not conf.cmd: LOG.error(_LE('No command provided, exiting')) return errno.EINVAL if not conf.mount_paths: LOG.error(_LE('No mount path provided, exiting')) return errno.EINVAL # Both sudoers and rootwrap.conf will not exist in the directory /etc # after bind-mount, so we can't use utils.execute(conf.cmd, # run_as_root=True). That's why we have to check here if cmd matches # CommandFilter filter_command(conf.cmd, conf.rootwrap_config) # Make sure the process is running in net namespace invoked by ip # netns exec(/proc/[pid]/ns/net) which is since Linux 3.0, # as we can't check mount namespace(/proc/[pid]/ns/mnt) # which is since Linux 3.8. For more detail please refer the link # http://man7.org/linux/man-pages/man7/namespaces.7.html if os.path.samefile(os.path.join('/proc/1/ns/net'), os.path.join('/proc', str(os.getpid()), 'ns/net')): LOG.error(_LE('Cannot run without netns, exiting')) return errno.EINVAL for path, new_path in conf.mount_paths.items(): if not os.path.isdir(new_path): # Sometimes all directories are not ready LOG.debug('%s is not directory', new_path) continue if os.path.isdir(path) and os.path.isabs(path): return_code = execute(['mount', '--bind', new_path, path]) if return_code == 0: LOG.info( _LI('%(new_path)s has been ' 'bind-mounted in %(path)s'), { 'new_path': new_path, 'path': path }) else: LOG.error( _LE('Failed to bind-mount ' '%(new_path)s in %(path)s'), { 'new_path': new_path, 'path': path }) return execute(conf.cmd)
def _do_request(self, method, resource, payload=None, more_headers=None, full_url=False): """Perform a REST request to a CSR resource. If this is the first time interacting with the CSR, a token will be obtained. If the request fails, due to an expired token, the token will be obtained and the request will be retried once more. """ if self.token is None: if not self.authenticate(): return if full_url: url = resource else: url = ('https://%(host)s/api/v1/%(resource)s' % {'host': self.host, 'resource': resource}) headers = {'Accept': 'application/json', 'X-auth-token': self.token} if more_headers: headers.update(more_headers) if payload: payload = jsonutils.dumps(payload) response = self._request(method, url, data=payload, headers=headers) if self.status == requests.codes.UNAUTHORIZED: if not self.authenticate(): return headers['X-auth-token'] = self.token response = self._request(method, url, data=payload, headers=headers) if self.status != requests.codes.REQUEST_TIMEOUT: return response LOG.error(_LE("%(method)s: Request timeout for CSR(%(host)s)"), {'method': method, 'host': self.host})
def authenticate(self): """Obtain a token to use for subsequent CSR REST requests. This is called when there is no token yet, or if the token has expired and attempts to use it resulted in an UNAUTHORIZED REST response. """ url = URL_BASE % {'host': self.host, 'resource': 'auth/token-services'} headers = {'Content-Length': '0', 'Accept': 'application/json'} headers.update(HEADER_CONTENT_TYPE_JSON) LOG.debug( "%(auth)s with CSR %(host)s", { 'auth': 'Authenticating' if self.token is None else 'Reauthenticating', 'host': self.host }) self.token = None response = self._request("POST", url, headers=headers, auth=self.auth) if response: self.token = response['token-id'] LOG.debug("Successfully authenticated with CSR %s", self.host) return True LOG.error(_LE("Failed authentication with CSR %(host)s [%(status)s]"), { 'host': self.host, 'status': self.status })
def disable(self): """Disabling the process.""" try: if self.active: self.stop() self.remove_config() except RuntimeError: LOG.exception(_LE("Failed to disable vpn process on router %s"), self.id)
def _resolve_fqdn(self, fqdn): # The first addrinfo member from the list returned by # socket.getaddrinfo is used for the address resolution. # The code doesn't filter for ipv4 or ipv6 address. try: addrinfo = socket.getaddrinfo(fqdn, None)[0] return addrinfo[-1][0] except socket.gaierror: LOG.exception(_LE("Peer address %s cannot be resolved"), fqdn)
def set_admin_state(self, is_up): """Change the admin state for the IPSec connection.""" self.csr.set_ipsec_connection_state(self.tunnel, admin_up=is_up) if self.csr.status != requests.codes.NO_CONTENT: state = "UP" if is_up else "DOWN" LOG.error(_LE("Unable to change %(tunnel)s admin state to " "%(state)s"), {'tunnel': self.tunnel, 'state': state}) raise CsrAdminStateChangeFailure(tunnel=self.tunnel, state=state)
def execute_with_mount(): conf = setup_conf() conf() config.setup_logging() if not conf.cmd: LOG.error(_LE('No command provided, exiting')) return errno.EINVAL if not conf.mount_paths: LOG.error(_LE('No mount path provided, exiting')) return errno.EINVAL # Both sudoers and rootwrap.conf will not exist in the directory /etc # after bind-mount, so we can't use utils.execute(conf.cmd, # run_as_root=True). That's why we have to check here if cmd matches # CommandFilter filter_command(conf.cmd, conf.rootwrap_config) # Make sure the process is running in net namespace invoked by ip # netns exec(/proc/[pid]/ns/net) which is since Linux 3.0, # as we can't check mount namespace(/proc/[pid]/ns/mnt) # which is since Linux 3.8. For more detail please refer the link # http://man7.org/linux/man-pages/man7/namespaces.7.html if os.path.samefile(os.path.join('/proc/1/ns/net'), os.path.join('/proc', str(os.getpid()), 'ns/net')): LOG.error(_LE('Cannot run without netns, exiting')) return errno.EINVAL for path, new_path in six.iteritems(conf.mount_paths): if not os.path.isdir(new_path): # Sometimes all directories are not ready LOG.debug('%s is not directory', new_path) continue if os.path.isdir(path) and os.path.isabs(path): return_code = execute(['mount', '--bind', new_path, path]) if return_code == 0: LOG.info(_LI('%(new_path)s has been ' 'bind-mounted in %(path)s'), {'new_path': new_path, 'path': path}) else: LOG.error(_LE('Failed to bind-mount ' '%(new_path)s in %(path)s'), {'new_path': new_path, 'path': path}) return execute(conf.cmd)
def disable(self): """Disabling the process.""" try: if self.active: self.stop() self.remove_config() except RuntimeError: LOG.exception( _LE("Failed to disable vpn process on router %s"), self.id)
def enable(self): """Enabling the process.""" try: self.ensure_configs() if self.active: self.restart() else: self.start() except RuntimeError: LOG.exception(_LE("Failed to enable vpn process on router %s"), self.id)
def do_create_action(self, action_suffix, info, resource_id, title): """Perform a single REST step for IPSec site connection create.""" create_action = 'create_%s' % action_suffix try: getattr(self.csr, create_action)(info) except AttributeError: LOG.exception(_LE("Internal error - '%s' is not defined"), create_action) raise CsrResourceCreateFailure(resource=title, which=resource_id) self._check_create(title, resource_id) self.steps.append(RollbackStep(action_suffix, resource_id, title))
def _check_create(self, resource, which): """Determine if REST create request was successful.""" if self.csr.status == requests.codes.CREATED: LOG.debug("%(resource)s %(which)s is configured", {'resource': resource, 'which': which}) return LOG.error(_LE("Unable to create %(resource)s %(which)s: " "%(status)d"), {'resource': resource, 'which': which, 'status': self.csr.status}) # ToDO(pcm): Set state to error raise CsrResourceCreateFailure(resource=resource, which=which)
def set_admin_state(self, is_up): """Change the admin state for the IPSec connection.""" self.csr.set_ipsec_connection_state(self.tunnel, admin_up=is_up) if self.csr.status != requests.codes.NO_CONTENT: state = "UP" if is_up else "DOWN" LOG.error( _LE("Unable to change %(tunnel)s admin state to " "%(state)s"), { 'tunnel': self.tunnel, 'state': state }) raise CsrAdminStateChangeFailure(tunnel=self.tunnel, state=state)
def enable(self): """Enabling the process.""" try: self.ensure_configs() if self.active: self.restart() else: self.start() except RuntimeError: LOG.exception( _LE("Failed to enable vpn process on router %s"), self.id)
def _request(self, method, url, **kwargs): """Perform REST request and save response info.""" try: LOG.debug("%(method)s: Request for %(resource)s payload: " "%(payload)s", {'method': method.upper(), 'resource': url, 'payload': kwargs.get('data')}) start_time = time.time() response = self.session.request(method, url, verify=False, timeout=self.timeout, **kwargs) LOG.debug("%(method)s Took %(time).2f seconds to process", {'method': method.upper(), 'time': time.time() - start_time}) except (r_exc.Timeout, r_exc.SSLError) as te: # Should never see SSLError, unless requests package is old (<2.0) timeout_val = 0.0 if self.timeout is None else self.timeout LOG.warning(_LW("%(method)s: Request timeout%(ssl)s " "(%(timeout).3f sec) for CSR(%(host)s)"), {'method': method, 'timeout': timeout_val, 'ssl': '(SSLError)' if isinstance(te, r_exc.SSLError) else '', 'host': self.host}) self.status = requests.codes.REQUEST_TIMEOUT except r_exc.ConnectionError: LOG.exception(_LE("%(method)s: Unable to connect to " "CSR(%(host)s)"), {'method': method, 'host': self.host}) self.status = requests.codes.NOT_FOUND except Exception as e: LOG.error(_LE("%(method)s: Unexpected error for CSR (%(host)s): " "%(error)s"), {'method': method, 'host': self.host, 'error': e}) self.status = requests.codes.INTERNAL_SERVER_ERROR else: self.status = response.status_code LOG.debug("%(method)s: Completed [%(status)s]", {'method': method, 'status': self.status}) return self._response_info_for(response, method)
def do_rollback(self): """Undo create steps that were completed successfully.""" for step in reversed(self.steps): delete_action = 'delete_%s' % step.action LOG.debug("Performing rollback action %(action)s for " "resource %(resource)s", {'action': delete_action, 'resource': step.title}) try: getattr(self.csr, delete_action)(step.resource_id) except AttributeError: LOG.exception(_LE("Internal error - '%s' is not defined"), delete_action) raise CsrResourceCreateFailure(resource=step.title, which=step.resource_id) self._verify_deleted(self.csr.status, step.title, step.resource_id) self.steps = []
def _check_create(self, resource, which): """Determine if REST create request was successful.""" if self.csr.status == requests.codes.CREATED: LOG.debug("%(resource)s %(which)s is configured", { 'resource': resource, 'which': which }) return LOG.error( _LE("Unable to create %(resource)s %(which)s: " "%(status)d"), { 'resource': resource, 'which': which, 'status': self.csr.status }) # ToDO(pcm): Set state to error raise CsrResourceCreateFailure(resource=resource, which=which)
def _cleanup_control_files(self): try: ctl_file = '%s.ctl' % self.pid_path LOG.debug('Removing %(pidfile)s and %(ctlfile)s', {'pidfile': self.pid_file, 'ctlfile': ctl_file}) if os.path.exists(self.pid_file): os.remove(self.pid_file) if os.path.exists(ctl_file): os.remove(ctl_file) except OSError as e: LOG.error(_LE('Unable to remove pluto control ' 'files for router %(router)s. %(msg)s'), {'router': self.id, 'msg': e})
def get_vpn_services_on_host(self, context, host): # make RPC call to neutron server cctxt = self.client.prepare() data = cctxt.call(context, 'get_vpn_services_on_host', host=host) vpn_services = list() for svc in data: try: for conn in svc[_KEY_CONNECTIONS]: vyatta_vpn_config.validate_svc_connection(conn) except v_exc.InvalidVPNServiceError: LOG.error(_LE('Invalid or incomplete VPN service data: ' 'id={id}').format(id=svc.get('id'))) continue vpn_services.append(svc) # return transformed data to caller return vpn_services
def _do_request(self, method, resource, payload=None, more_headers=None, full_url=False): """Perform a REST request to a CSR resource. If this is the first time interacting with the CSR, a token will be obtained. If the request fails, due to an expired token, the token will be obtained and the request will be retried once more. """ if self.token is None: if not self.authenticate(): return if full_url: url = resource else: url = ('https://%(host)s/api/v1/%(resource)s' % { 'host': self.host, 'resource': resource }) headers = {'Accept': 'application/json', 'X-auth-token': self.token} if more_headers: headers.update(more_headers) if payload: payload = jsonutils.dumps(payload) response = self._request(method, url, data=payload, headers=headers) if self.status == requests.codes.UNAUTHORIZED: if not self.authenticate(): return headers['X-auth-token'] = self.token response = self._request(method, url, data=payload, headers=headers) if self.status != requests.codes.REQUEST_TIMEOUT: return response LOG.error(_LE("%(method)s: Request timeout for CSR(%(host)s)"), { 'method': method, 'host': self.host })
def do_rollback(self): """Undo create steps that were completed successfully.""" for step in reversed(self.steps): delete_action = 'delete_%s' % step.action LOG.debug( "Performing rollback action %(action)s for " "resource %(resource)s", { 'action': delete_action, 'resource': step.title }) try: getattr(self.csr, delete_action)(step.resource_id) except AttributeError: LOG.exception(_LE("Internal error - '%s' is not defined"), delete_action) raise CsrResourceCreateFailure(resource=step.title, which=step.resource_id) self._verify_deleted(self.csr.status, step.title, step.resource_id) self.steps = []
def _process_running(self): """Checks if process is still running.""" # If no PID file, we assume the process is not running. if not os.path.exists(self.pid_file): return False try: # We take an ask-forgiveness-not-permission approach and rely # on throwing to tell us something. If the pid file exists, # delve into the process information and check if it matches # our expected command line. with open(self.pid_file, 'r') as f: pid = f.readline().strip() with open('/proc/%s/cmdline' % pid) as cmd_line_file: cmd_line = cmd_line_file.readline() if self.pid_path in cmd_line and 'pluto' in cmd_line: # Okay the process is probably a pluto process # and it contains the pid_path in the command # line... could be a race. Log to error and return # that it is *NOT* okay to clean up files. We are # logging to error instead of debug because it # indicates something bad has happened and this is # valuable information for figuring it out. LOG.error( _LE('Process %(pid)s exists with command ' 'line %(cmd_line)s.') % { 'pid': pid, 'cmd_line': cmd_line }) return True except IOError as e: # This is logged as "info" instead of error because it simply # means that we couldn't find the files to check on them. LOG.info( _LI('Unable to find control files on startup for ' 'router %(router)s: %(msg)s'), { 'router': self.id, 'msg': e }) return False
def authenticate(self): """Obtain a token to use for subsequent CSR REST requests. This is called when there is no token yet, or if the token has expired and attempts to use it resulted in an UNAUTHORIZED REST response. """ url = URL_BASE % {'host': self.host, 'resource': 'auth/token-services'} headers = {'Content-Length': '0', 'Accept': 'application/json'} headers.update(HEADER_CONTENT_TYPE_JSON) LOG.debug("%(auth)s with CSR %(host)s", {'auth': 'Authenticating' if self.token is None else 'Reauthenticating', 'host': self.host}) self.token = None response = self._request("POST", url, headers=headers, auth=self.auth) if response: self.token = response['token-id'] LOG.debug("Successfully authenticated with CSR %s", self.host) return True LOG.error(_LE("Failed authentication with CSR %(host)s [%(status)s]"), {'host': self.host, 'status': self.status})
def _process_running(self): """Checks if process is still running.""" # If no PID file, we assume the process is not running. if not os.path.exists(self.pid_file): return False try: # We take an ask-forgiveness-not-permission approach and rely # on throwing to tell us something. If the pid file exists, # delve into the process information and check if it matches # our expected command line. with open(self.pid_file, 'r') as f: pid = f.readline().strip() with open('/proc/%s/cmdline' % pid) as cmd_line_file: cmd_line = cmd_line_file.readline() if self.pid_path in cmd_line and 'pluto' in cmd_line: # Okay the process is probably a pluto process # and it contains the pid_path in the command # line... could be a race. Log to error and return # that it is *NOT* okay to clean up files. We are # logging to error instead of debug because it # indicates something bad has happened and this is # valuable information for figuring it out. LOG.error(_LE('Process %(pid)s exists with command ' 'line %(cmd_line)s.') % {'pid': pid, 'cmd_line': cmd_line}) return True except IOError as e: # This is logged as "info" instead of error because it simply # means that we couldn't find the files to check on them. LOG.info(_LI('Unable to find control files on startup for ' 'router %(router)s: %(msg)s'), {'router': self.id, 'msg': e}) return False