diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 9ea0244..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,59 +0,0 @@ -# ? docker-compose.yml for development environment - -# ! To start development you need to create a directory ./dummy_platform. -# ? Place files from the test platform into it: -# ? VM6: -# ? /opt/ispsystem/vm/config.json - configuration file -# ? /opt/ispsystem/vm/mysql - database directory -# ? DCI6: -# ? /opt/ispsystem/dci/config.json - configuration file -# ? /opt/ispsystem/dci/mysql - database directory - -# ? Create ./.env file and fill it with required vars: -# ? PLATFORM_TYPE='vm' -# ? Database container: -# ? MYSQL_DATABASE="database name" -# ? MYSQL_ROOT_PASSWORD="super secret password from config.json" - -# ? Launch: -# ? docker-compose up -d --force-recreate -# ? docker attach mgrctl - -services: - mgrctl: - container_name: mgrctl - restart: unless-stopped - build: - context: . - args: - - APP_VERSION=${APP_VERSION} - - APP_DIR=${APP_DIR} - - SRC_DIR=${SRC_DIR} - - PKG_NAME=${PKG_NAME} - - PKG_VERSION=${PKG_VERSION} - networks: - vm_box_net: null - volumes: - - type: bind - source: ./dummy_platform/opt/ispsystem/${PLATFORM_TYPE}/config.json - target: /opt/ispsystem/${PLATFORM_TYPE}/config.json - env_file: - - ./.env - tty: true - stdin_open: true - mysql: - container_name: mysql - image: docker-registry.ispsystem.com/mysql:5 - volumes: - - ./dummy_platform/opt/ispsystem/${PLATFORM_TYPE}/mysql:/var/lib/mysql - env_file: - - ./.env - labels: - autoconf_mysql: "true" - networks: - vm_box_net: null - command: --group-concat-max-len=131072 --max-connections=1000 --optimizer-search-depth=0 - -networks: - vm_box_net: - driver: bridge diff --git a/mgrctl/api/base.py b/mgrctl/api/base.py index bc3b00c..2a28b19 100644 --- a/mgrctl/api/base.py +++ b/mgrctl/api/base.py @@ -3,18 +3,52 @@ import json import urllib import requests -from mgrctl.settings.api import INPUT_HOSTNAME, INPUT_PORT, HEADERS +from mgrctl.settings.api import INPUT_URL, HEADERS +from mgrctl.settings.platform import ( + PLATFORM_TYPE, + PLATFORM_VERIFY_SSL, + PLATFORM_DUMMY, + PLATFORM_DUMMY_VM6_API_URL, + PLATFORM_DUMMY_VM6_EMAIL, + PLATFORM_DUMMY_VM6_PASSWORD, + PLATFORM_DUMMY_VM6_TOKEN, + PLATFORM_DUMMY_DCI6_API_URL, + PLATFORM_DUMMY_DCI6_EMAIL, + PLATFORM_DUMMY_DCI6_PASSWORD, + PLATFORM_DUMMY_DCI6_TOKEN +) class BaseAPI(object): - def __init__(self, api_url=None, verify_ssl=True): + def __init__(self): """Announces required parameters""" - internal_api_url = f'http://{INPUT_HOSTNAME}:{INPUT_PORT}' - self.API_URL = api_url if api_url else internal_api_url + if PLATFORM_TYPE == 'vm': + if PLATFORM_DUMMY: + self.API_URL = PLATFORM_DUMMY_VM6_API_URL + self.AUTH_TYPE = 'Public' + self.HEADERS = {'x-xsrf-token': PLATFORM_DUMMY_VM6_TOKEN} + self.EMAIL = PLATFORM_DUMMY_VM6_EMAIL + self.PASSWORD = PLATFORM_DUMMY_VM6_PASSWORD + else: + self.API_URL = INPUT_URL + self.AUTH_TYPE = 'Internal' + self.HEADERS = HEADERS + + if PLATFORM_TYPE == 'dci': + if PLATFORM_DUMMY: + self.API_URL = PLATFORM_DUMMY_DCI6_API_URL + self.AUTH_TYPE = 'Public' + self.HEADERS = {'x-xsrf-token': PLATFORM_DUMMY_DCI6_TOKEN} + self.EMAIL = PLATFORM_DUMMY_DCI6_EMAIL + self.PASSWORD = PLATFORM_DUMMY_DCI6_PASSWORD + else: + self.API_URL = INPUT_URL + self.AUTH_TYPE = 'Internal' + self.HEADERS = HEADERS + self.API_VERSION = 'v3' self.API_DEFINITION = 'api' - self.HEADERS = HEADERS - self.VERIFY_SSL = verify_ssl + self.VERIFY_SSL = PLATFORM_VERIFY_SSL def _gen_request_url(self, url): return f'{self.API_URL}/{self.API_DEFINITION}/{self.API_VERSION}{url}' @@ -61,69 +95,26 @@ class BaseAPI(object): print(response) raise sys.exit() - def get(self, url, headers={}, data={}): - return self.call_api(url, headers, data, method='GET') - - def post(self, url, headers={}, data={}): - return self.call_api(url, headers, data, method='POST') - class BaseAuthAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - """ - Init class for /auth/v4 requests - - Args: - api_url (str, optional): url api host. Defaults to None. - If None will use from settings.api.INPUT_HOSTNAME:INPUT_PORT - - verify_ssl (bool, optional): Isn't recommended. Defaults to True. - Use only for testing if you don't have root sign SSL cert. - """ - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_VERSION = 'v4' self.API_DEFINITION = 'auth' - def get_auth_token(self, email: str, password: str) -> dict: - """ - Get auth token for authentication - - Arg: - email (str): user email - password (str): user password - - Returns: - response (dict): { - "confirmed": true, - "expires_at": "date time", - "id": "int", - "token": "str" - } - """ + def get_auth_token(self, email=None, password=None) -> dict: + email = self.EMAIL if not email else email + password = self.PASSWORD if not password else password return self.call_api( url='/public/token', method='POST', data={'email': email, 'password': password} ) - def get_auth_key(self, token: str, user, auth_type='Internal') -> dict: - """ - Key authentication - - Args: - token (str): auth token - user (str or int): user id or email - auth_type (str, optional): May be Public for public auth. - Defaults to 'Internal'. - - Returns: - response (dict): { - "id": "int", - "key": "str" - } - """ + def get_auth_key(self, token=None, user=None) -> dict: headers = {} - if auth_type == 'Public': + user = self.EMAIL if not user else user + if token: headers = self.make_auth_header(token) return self.call_api( url=f'/user/{user}/key', @@ -132,32 +123,22 @@ class BaseAuthAPI(BaseAPI): ) def make_auth_header(self, token: str) -> dict: - """ - Generate dict for auth header - - Args: - token (str): auth token - - Returns: - dict: {'x-xsrf-token': token} use it for request headers - """ return {'x-xsrf-token': token} - def whoami(self, token: str) -> dict: + def whoami(self) -> dict: return self.call_api( url='/whoami', - method='GET', - headers=self.make_auth_header(token) + method='GET' ) class BaseIpAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_DEFINITION = 'ip' class BaseDnsProxyAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_DEFINITION = 'dnsproxy' diff --git a/mgrctl/api/dci6.py b/mgrctl/api/dci6.py index f5639de..6f19e7c 100644 --- a/mgrctl/api/dci6.py +++ b/mgrctl/api/dci6.py @@ -6,8 +6,8 @@ class AuthAPI(BaseAuthAPI): class BackupAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_VERSION = 'v4' self.API_DEFINITION = 'backup' @@ -17,20 +17,20 @@ class DnsProxyAPI(BaseDnsProxyAPI): class EserviceAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_DEFINITION = 'eservice' class IsoAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_DEFINITION = 'iso' class IpmiAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_DEFINITION = 'ipmiproxy' @@ -39,13 +39,13 @@ class IpAPI(BaseIpAPI): class ReportAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_VERSION = 'v4' self.API_DEFINITION = 'report' class UpdaterAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_DEFINITION = 'updater' diff --git a/mgrctl/api/vm6.py b/mgrctl/api/vm6.py index 3391717..b24bb55 100644 --- a/mgrctl/api/vm6.py +++ b/mgrctl/api/vm6.py @@ -14,6 +14,6 @@ class IpAPI(BaseIpAPI): class VmAPI(BaseAPI): - def __init__(self, api_url=None, verify_ssl=True): - super().__init__(api_url, verify_ssl) + def __init__(self): + super().__init__() self.API_DEFINITION = 'vm' diff --git a/mgrctl/apps/vm6/auth/commands.py b/mgrctl/apps/vm6/auth/commands.py index 8c49886..783e118 100644 --- a/mgrctl/apps/vm6/auth/commands.py +++ b/mgrctl/apps/vm6/auth/commands.py @@ -1,9 +1,11 @@ import click -from mgrctl.db.vm6.databases import isp_database -from mgrctl.db.vm6.models import AuthUser - from mgrctl.apps.vm6.auth import __version__ +from mgrctl.api.vm6 import AuthAPI +from mgrctl.utils.api_users import UserAPI + + +user_cursor = UserAPI(callback_class=AuthAPI) @click.group(help='auth cmd for auth in VMmanager 6') @@ -16,16 +18,79 @@ def cli(): pass -@cli.command(help='show all users and their roles on platform (DEMO EXAMPLE)') -def users(): - # check and init connection to db: - isp_database.connect() - # get all fields from auth_user table - # SELECT * FROM auth_user; - all_users = AuthUser.select() - # Iterate fields and print to console users' email and role - for user in all_users: - result = f'{user.email} | {user.roles[0]}' - click.echo(result) - # Close connection - isp_database.close() +@cli.group(help='Manage users') +def user(): + pass + + +@user.command(help='List users') +@click.option( + '--all', + is_flag=True, + required=False, + help='Show all users' +) +@click.option( + '--admins', + is_flag=True, + required=False, + help='Show all active admins', +) +def ls(all, admins): + if all: + user_cursor.echo_users(role='all') + elif admins: + user_cursor.echo_users(role='admin') + else: + user_cursor.echo_users(role='all') + + +@user.command(help='Generate access key and return auth link(s)') +@click.option( + '--id', + required=False, + type=int, + help='User id' +) +@click.option( + '--count', + required=False, + type=int, + help='Number of access keys generated', +) +@click.option( + '--random', + is_flag=True, + required=False, + help='Interactive mode, ignores other keys' +) +@click.option( + '--interactive', + is_flag=True, + required=False, + help='Interactive mode, ignores other keys' +) +def access(id, count, interactive, random): + if id and not count: + keys = user_cursor.get_access_keys(user=id, count=1) + links = user_cursor.gen_access_links(keys) + user_cursor.echo_access_links(links) + elif id and count: + keys = user_cursor.get_access_keys(user=id, count=count) + links = user_cursor.gen_access_links(keys) + user_cursor.echo_access_links(links) + elif interactive: + pass + elif random: + admin = user_cursor.get_first_random_admin() + keys = user_cursor.get_access_keys(user=admin.get('id', 3), count=1) + links = user_cursor.gen_access_links(keys) + user_cursor.echo_access_links(links) + else: + pass + + +@user.command(help='Generate API token for mgrctl user') +def token(): + token = user_cursor.gen_api_token() + user_cursor.echo_api_token(token) diff --git a/mgrctl/settings/api.py b/mgrctl/settings/api.py index b027494..0543ee7 100644 --- a/mgrctl/settings/api.py +++ b/mgrctl/settings/api.py @@ -1,4 +1,8 @@ -from mgrctl.settings.platform import PLATFORM_TYPE +from requests.packages import urllib3 +from mgrctl.settings.platform import ( + PLATFORM_TYPE, + PLATFORM_VERIFY_SSL_WARNING +) # Name of nginx container: INPUT_HOSTNAME = 'input' if PLATFORM_TYPE == 'vm' else 'dci_input_1' @@ -6,5 +10,16 @@ INPUT_HOSTNAME = 'input' if PLATFORM_TYPE == 'vm' else 'dci_input_1' # Port that nginx container is listening: INPUT_PORT = '1500' +# Internal API url: +INPUT_URL = f'http://{INPUT_HOSTNAME}:{INPUT_PORT}' + # Headers for internal auth: HEADERS = {"Internal-Auth": "on", "Accept": "application/json"} + +# Suppress warning from urllib3: +if not PLATFORM_VERIFY_SSL_WARNING: + # ! This is not recommended, + # ! the user will not see a message about an untrusted SSL connection + urllib3.disable_warnings( + category=urllib3.exceptions.InsecureRequestWarning + ) diff --git a/mgrctl/settings/platform.py b/mgrctl/settings/platform.py index 49420e1..b8cdfaf 100644 --- a/mgrctl/settings/platform.py +++ b/mgrctl/settings/platform.py @@ -4,6 +4,9 @@ from mgrctl.utils.helpers import parse_json_file PLATFORM_TYPE = env.str('PLATFORM_TYPE', 'vm') +PLATFORM_VERIFY_SSL = env.bool('PLATFORM_VERIFY_SSL', True) +PLATFORM_VERIFY_SSL_WARNING = env.bool('PLATFORM_VERIFY_SSL_WARNING', True) + PLATFORM_CONFIG = env.str( 'PLATFORM_CONFIG', f'/opt/ispsystem/{PLATFORM_TYPE}/config.json' @@ -11,7 +14,20 @@ PLATFORM_CONFIG = env.str( PLATFORM_CONFIG = parse_json_file(PLATFORM_CONFIG) -PLATFORM_URL = env.url( +PLATFORM_URL = env.str( 'PLATFORM_URL', f"https://{PLATFORM_CONFIG.get('DomainName' ,'replace.me')}" ) + +# Development mode: +PLATFORM_DUMMY = env.bool('PLATFORM_DUMMY', False) + +PLATFORM_DUMMY_VM6_API_URL = env.str('PLATFORM_DUMMY_VM6_API_URL', '') +PLATFORM_DUMMY_VM6_EMAIL = env.str('PLATFORM_DUMMY_VM6_EMAIL', '') +PLATFORM_DUMMY_VM6_PASSWORD = env.str('PLATFORM_DUMMY_VM6_PASSWORD', '') +PLATFORM_DUMMY_VM6_TOKEN = env.str('PLATFORM_DUMMY_VM6_TOKEN', '') + +PLATFORM_DUMMY_DCI6_API_URL = env.str('PLATFORM_DUMMY_DCI6_API_URL', '') +PLATFORM_DUMMY_DCI6_EMAIL = env.str('PLATFORM_DUMMY_DCI6_EMAIL', '') +PLATFORM_DUMMY_DCI6_PASSWORD = env.str('PLATFORM_DUMMY_DCI6_PASSWORD', '') +PLATFORM_DUMMY_DCI6_TOKEN = env.str('PLATFORM_DUMMY_DCI6_TOKEN', '') diff --git a/mgrctl/utils/api_users.py b/mgrctl/utils/api_users.py new file mode 100644 index 0000000..8a75e13 --- /dev/null +++ b/mgrctl/utils/api_users.py @@ -0,0 +1,74 @@ +import click +from tabulate import tabulate + +from mgrctl.settings.platform import PLATFORM_URL + + +class UserAPI(object): + def __init__(self, callback_class): + """Announces required parameters""" + self.callback_class = callback_class + self.callback = callback_class() + + def get_users(self, role: str) -> dict: + data = {} + if role == 'admin': + data = {"where": "((roles+CP+'%@admin%')+AND+(state+EQ+'active'))"} + return self.callback.call_api( + url='/user', + method='GET', + data=data + ) + + def _format_users(self, users: dict) -> list: + output = [] + for user in users.get('list', []): + output.append({ + 'id': user.get('id', ''), + 'email': user.get('email', ''), + 'roles': user.get('roles', []), + 'state': user.get('state', '') + }) + return output + + def get_first_random_admin(self): + users = self.get_users(role='admin') + admin = {} + for user in users.get('list', []): + if '@admin' in admin.get('roles', []): + admin = user + break + return admin + + def echo_users(self, role: str) -> None: + users = self.get_users(role) + output = self._format_users(users) + click.echo(tabulate(output, headers='keys')) + + def get_access_keys(self, user, count=1): + keys = [] + while count >= 1: + count -= 1 + key = self.callback.get_auth_key(user=user) + check = key.get('key', '') + if check: + keys.append(key) + return keys + + def gen_access_links(self, keys: list) -> list: + links = [] + for key in keys: + _id = key.get('id', '') + link = f"{PLATFORM_URL}/auth/key/{key.get('key', '')}" + links.append({'id': _id, 'link': link}) + return links + + def echo_access_links(self, links: list) -> None: + click.echo(tabulate(links, headers='keys')) + + def gen_api_token(self, email=None, password=None): + token = self.callback.get_auth_token(email, password) + return token + + def echo_api_token(self, token: dict) -> None: + click.echo(tabulate([token], headers='keys'))