def get_web_service_config(args: argparse.Namespace): """ Gets url, username, and password for the Tortuga web service. :param argparse.Namespace args: argparse Namespace instance :return tuple: (url, username, password) """ username = password = url = None cfg_file = os.path.join(os.path.expanduser('~'), '.local', 'tortuga', 'credentials') if os.path.exists(cfg_file): cfg = configparser.ConfigParser() cfg.read(cfg_file) username = cfg.get('default', 'username') \ if cfg.has_section('default') and \ cfg.has_option('default', 'username') else None password = cfg.get('default', 'password') \ if cfg.has_section('default') and \ cfg.has_option('default', 'password') else None url = cfg.get('default', 'url') \ if cfg.has_section('default') and \ cfg.has_option('default', 'url') else None if args.url: url = args.url elif os.getenv('TORTUGA_WS_URL'): url = os.getenv('TORTUGA_WS_URL') if args.username: username = args.username elif os.getenv('TORTUGA_WS_USERNAME'): username = os.getenv('TORTUGA_WS_USERNAME') if args.password: password = args.password elif os.getenv('TORTUGA_WS_PASSWORD'): password = os.getenv('TORTUGA_WS_PASSWORD') # # CLI arguments should override the environment variable # if os.getenv('TORTUGA_WS_NO_VERIFY'): verify = False else: verify = args.verify if username is None and password is None: cm = ConfigManager() username = cm.getCfmUser() password = cm.getCfmPassword() return url, username, password, verify
class TortugaWsApi(RestApiClient): """ Base tortuga ws api class. """ def __init__(self, username: Optional[str] = None, password: Optional[str] = None, baseurl: Optional[str] = None, verify: bool = True): self._cm = ConfigManager() if not baseurl: baseurl = '{}://{}:{}'.format( self._cm.getAdminScheme(), self._cm.getInstaller(), self._cm.getAdminPort() ) if username is None and password is None: logger.debug('Using built-in user credentials') username = self._cm.getCfmUser() password = self._cm.getCfmPassword() super().__init__(username, password, baseurl, verify) self.baseurl = '{}/{}'.format(self.baseurl, WS_API_VERSION) def process_response(self, response: requests.Response): check_status(response.headers) return super().process_response(response)
class TortugaWsApiClient: """ Tortuga ws api client class. """ def __init__(self, endpoint: str, username: Optional[str] = None, password: Optional[str] = None, base_url: Optional[str] = None, verify: bool = True) -> None: self._cm = ConfigManager() if not base_url: base_url = '{}://{}:{}'.format( self._cm.getAdminScheme(), self._cm.getInstaller(), self._cm.getAdminPort() ) if username is None and password is None: logger.debug('Using built-in user credentials') username = self._cm.getCfmUser() password = self._cm.getCfmPassword() self._client = RestApiClient( username=username, password=password, baseurl=base_url, verify=verify ) self._client.baseurl = '{}/{}/{}/'.format(base_url, WS_API_VERSION, endpoint) def _build_query_string(self, params: dict) -> str: \ # pylint: disable=no-self-use return '&'.join([f'{k}={v}' for k, v in params.items()]) def list(self, **params) -> list: path = '/' query_string = self._build_query_string(params) if query_string: path += '?{}'.format(query_string) return self._client.get(path) def get(self, id_: str) -> dict: path = '/{}'.format(id_) return self._client.get(path)
class AuthManager(TortugaObjectManager, Singleton): def __init__(self): super(AuthManager, self).__init__() self._configManager = ConfigManager() self.__principals = {} self.__loadPrincipals() def cryptPassword(self, cleartext, salt="$1$"): \ # pylint: disable=no-self-use """ Return crypted password.... """ return crypt.crypt(cleartext, salt) def reloadPrincipals(self): """ This is used to reload the principals in auth manager """ self.__principals.clear() self.__loadPrincipals() def __loadPrincipals(self): """ Load principals for config manager and datastore """ # Create builtin cfm principal cfmUser = AuthPrincipal( self._configManager.getCfmUser(), self.cryptPassword(self._configManager.getCfmPassword()), {'roles': 'cfm'}) # Add cfm user self.__principals[cfmUser.getName()] = cfmUser # Add users from DB if self._configManager.isInstaller(): for admin in getAdminApi().getAdminList(): self.__principals[admin.getUsername()] = AuthPrincipal( admin.getUsername(), admin.getPassword(), attributeDict={'id': admin.getId()}) def getPrincipal(self, username, password): """ Get a principal based on a username and password """ principal = self.__principals.get(username) if principal and principal.getPassword() == crypt.crypt( password, principal.getPassword()): return principal return None
class TortugaWsApiClient: """ Tortuga ws api client class. """ def __init__(self, endpoint: str, token: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None, base_url: Optional[str] = None, verify: bool = True) -> None: self._cm = ConfigManager() if not base_url: base_url = '{}://{}:{}'.format(self._cm.getAdminScheme(), self._cm.getInstaller(), self._cm.getAdminPort()) if not token: if username is None and password is None: logger.debug('Using built-in user credentials') username = self._cm.getCfmUser() password = self._cm.getCfmPassword() self._client = RestApiClient(token=token, username=username, password=password, baseurl=base_url, verify=verify) self._client.baseurl = '{}/{}/{}/'.format(base_url, WS_API_VERSION, endpoint) def _build_query_string(self, params: dict) -> str: return '&'.join([f'{k}={v}' for k, v in params.items()]) def list(self, **params) -> list: path = '/' query_string = self._build_query_string(params) if query_string: path += '?{}'.format(query_string) return self._client.get(path) def get(self, id_: str) -> dict: path = '/{}'.format(id_) return self._client.get(path) def post(self, data: dict) -> dict: path = '/' return self._client.post(path, data=data) def put(self, data: dict) -> dict: if not data or 'id' not in data.keys(): raise Exception('Object does not have an id field') id_ = data['id'] if not id_: raise Exception('Object id is invalid') path = '/{}'.format(id_) return self._client.put(path, data=data) def delete(self, id_: str): path = '/{}'.format(id_) return self._client.delete(path)
class TortugaScriptConfig(Config): AUTH_METHOD_PASSWORD = '******' AUTH_METHOD_TOKEN = 'token' NAVOPS_CLI = '/opt/navops-launch/bin/navopsctl' DEFAULT_FILENAME = os.path.join(os.path.expanduser('~'), '.tortuga', 'config') def __init__(self, **kwargs): # # Internal properties # self._filename = None self._cm = ConfigManager() # # Determine the username/password to use as default # default_username = self._cm.getCfmUser() default_password = self._cm.getCfmPassword() if default_password == 'not-set': default_username = None default_password = None # # Check for default navops cli location # default_navops_cli = self.NAVOPS_CLI if not os.path.exists(default_navops_cli): default_navops_cli = None # # Configuration settings # self.url = kwargs.get('url', self._cm.getInstallerUrl()) self.token = kwargs.get('token', None) self.navops_cli = kwargs.get('navops_cli', default_navops_cli) self.username = kwargs.get('username', default_username) self.password = kwargs.get('password', default_password) self.verify = kwargs.get('verify', True) def _load_from_environment(self): if os.getenv('TORTUGA_WS_URL'): self.url = os.getenv('TORTUGA_WS_URL') if os.getenv('TORTUGA_WS_USERNAME'): self.username = os.getenv('TORTUGA_WS_USERNAME') if os.getenv('TORTUGA_WS_PASSWORD'): self.password = os.getenv('TORTUGA_WS_PASSWORD') if os.getenv('TORTUGA_WS_TOKEN'): self.token = os.getenv('TORTUGA_WS_TOKEN') if os.getenv('TORTUGA_WS_NO_VERIFY'): self.verify = False @classmethod def load(cls, filename: str = None) -> 'TortugaScriptConfig': # # If a file name is provided, then we try to load that first # if filename: config = cls._load_from_file(filename) # # If no filename is provided, then we have to figure out where to # get a configuration # else: # # First, check if the user has a config in their home directory # if os.path.exists(cls.DEFAULT_FILENAME): config = cls._load_from_file(cls.DEFAULT_FILENAME) # # Otherwise, create a new config from scratch # else: config = cls() # # Override the config with any settings provided from the # environment # config._load_from_environment() return config @classmethod def _load_from_file(cls, filename) -> 'TortugaScriptConfig': if not os.path.exists(filename): raise ConfigFileNotFoundException( 'Config file not found: {}'.format(filename)) with open(filename) as fp: try: config_data = json.load(fp) except json.JSONDecodeError: raise ConfigException( 'Invalid config file: {}'.format(filename)) try: unmarshalled = TortugaScriptConfigSchema().load(config_data) except ValidationError: raise ConfigException('Invalid config file: {}'.format(filename)) return TortugaScriptConfig(**unmarshalled.data) def save(self, filename: str = None): if not filename: if self._filename: filename = filename else: filename = TortugaScriptConfig.DEFAULT_FILENAME if not os.path.exists(filename): os.makedirs(os.path.dirname(filename), exist_ok=True, mode=0o700) marshalled = TortugaScriptConfigSchema().dump(self) with open(filename, 'w') as fp: json.dump(marshalled.data, fp, indent=4) def get_auth_method(self) -> str: """ Gets the authentication method that should be used. :return str: token or password :raises ConfigException: if no auth method is configured """ # # For the CFM user, always use password authentication # if self.username == self._cm.getCfmUser() and self.password: return self.AUTH_METHOD_PASSWORD # # For all other cases, if the navops CLI is present, or there # is a token, use token-based authentication # if self.navops_cli or self.token: return self.AUTH_METHOD_TOKEN # # Otherwise, fall back to password authentication # if self.username and self.password: return self.AUTH_METHOD_PASSWORD raise ConfigException('Authentication required. Use "tortuga login".') def get_token(self) -> str: """ Gets the current authentication token. :return str: the token :raises ConfigException: if token is unavailable """ if self.navops_cli: try: return self._get_navops_token() except ConfigException: pass if self.token: return self.token raise ConfigException('Authentication required. Use "tortuga login".') def _get_navops_token(self) -> str: cmd = '{} token'.format(self.navops_cli) try: p = executeCommand(cmd) except Exception as ex: logger.info(str(ex)) raise ConfigException(str(ex)) if p.getExitStatus() != 0: raise ConfigException(p.getStdErr()) return p.getStdOut().decode()
class TortugaWsApi: """ Base tortuga ws api class. """ def __init__(self, username=None, password=None): self._logger = logging.getLogger('tortuga.wsapi.{0}'.format( self.__class__.__name__)) self._logger.addHandler(logging.NullHandler()) self._cm = ConfigManager() if username is None and password is None: self._logger.debug('[%s] Using built-in user credentials' % (self.__module__)) username = self._cm.getCfmUser() password = self._cm.getCfmPassword() self._username = username self._password = password self._sm = None def _getWsUrl(self, url): """Extract scheme and net location from provided url. Use defaults if none exist.""" result = urlparse(url) scheme = result.scheme if result.scheme else \ self._cm.getAdminScheme() netloc = result.netloc if result.netloc else \ '{0}:{1}'.format(self._cm.getInstaller(), self._cm.getAdminPort()) return '{0}://{1}'.format(scheme, netloc) def _getSessionManager(self): if not self._sm: self._sm = sessionManager.createSession() return self._sm def getLogger(self): """ Get logger for this class. """ return self._logger def getConfigManager(self): """ Return configmanager reference """ return self._cm def sendSessionRequest(self, url, method='GET', contentType='application/json', data='', acceptType='application/json'): """ Send authorized session request Raises: UserNotAuthorized """ sm = self._getSessionManager() if not sm.hasSession(): if self._username is None: raise UserNotAuthorized('Username not supplied') if self._password is None: raise UserNotAuthorized('Password not supplied') wsUrl = self._getWsUrl(url) # establishSession() sets the 'wsUrl' so the explicit call # to setHost() is not required sm.establishSession(wsUrl, self._username, self._password) return sm.sendRequest(url, method, contentType, data, acceptType=acceptType) def sendRequest(self, url, method='GET', contentType='application/json', data='', acceptType='application/json'): """ Send unauthorized request. """ sm = self._getSessionManager() # Because there's no call to establishSession(), explicitly call # setHost() sm.setHost(self._getWsUrl(url)) return self._getSessionManager().sendRequest(url, method, contentType, data, acceptType)
class AuthManager(TortugaObjectManager): def __init__(self, *, session: Session): super(AuthManager, self).__init__() self.session = session self._configManager = ConfigManager() self.__principals = {} self.__loadPrincipals() def cryptPassword(self, cleartext): \ # pylint: disable=no-self-use """ Return crypted password """ return pbkdf2_sha256.hash(cleartext) def reloadPrincipals(self): """ This is used to reload the principals in auth manager """ self.__principals.clear() self.__loadPrincipals() def __loadPrincipals(self): """ Load principals for config manager and datastore """ from tortuga.admin.api import AdminApi # Create built-in cfm principal cfmUser = AuthPrincipal( self._configManager.getCfmUser(), self.cryptPassword(self._configManager.getCfmPassword()), {'roles': 'cfm'}) # Add cfm user self.__principals[cfmUser.get_name()] = cfmUser # Add users from DB if self._configManager.isInstaller(): for admin in AdminApi().getAdminList(self.session): self.__principals[admin.getUsername()] = AuthPrincipal( admin.getUsername(), admin.getPassword(), attributes={'id': admin.getId()}) def get_principal(self, username: str) -> AuthPrincipal: """ Get a principal by username. :param str username: the username of the principal to lookup :return AuthPrincipal: the principal, if found, otherwise None """ principal: AuthPrincipal = self.__principals.get(username) if not principal: principal = None return principal