diff --git a/projects/vdk-control-cli/CHANGELOG.md b/projects/vdk-control-cli/CHANGELOG.md index d1da78898f..d5ed9a479f 100644 --- a/projects/vdk-control-cli/CHANGELOG.md +++ b/projects/vdk-control-cli/CHANGELOG.md @@ -10,6 +10,8 @@ VDK Control CLI 1.1 Introduced vdk create --local to create job only locally from sample vdk create without arguments would create job locally and try to detect if it can created in cloud vdk create --cloud - always created in cloud only and fail if it cannot + * Auto-detect if authentication is necessary + This remove the need of explicit variable to set/unset authentication. Now vdkcli would detect automatically. * **Bug Fixes** diff --git a/projects/vdk-control-cli/README.md b/projects/vdk-control-cli/README.md index 6eb93290db..5c3583d973 100644 --- a/projects/vdk-control-cli/README.md +++ b/projects/vdk-control-cli/README.md @@ -23,15 +23,20 @@ use `vdkcli` command instead of `vdk`. ### Environment variables: -* VDK_AUTHENTICATION_DISABLE - disables security (vdk login will not be required). See Security section. -* VDK_BASE_CONFIG_FOLDER - Override local base configuration folder (by default $HOME folder) . Use in case multiple users need to login (e.g in case of automation) on same machine. +* VDK_BASE_CONFIG_FOLDER - Override local base configuration folder (by default in $HOME folder). Inside it will create folder .vdk.internal. + CLI state may be kept there (login info). Use in case multiple users need to login (e.g in case of automation) on same machine. + +* VDK_CONTROL_SERVICE_REST_API_URL - Default Control Service URL to use if not specified as command line argument +* VDK_API_TOKEN - Default API Token to use if another authentication has not been used with vdk login +* VDK_API_TOKEN_AUTHORIZATION_URL - Default API token URL to use if another authentication has not been used with vdk login. ### Security -By default, all operation require authentication: vdk login must have finished successfully. -You can disable it with environment variable `VDK_AUTHENTICATION_DISABLE=true` -This would only work if Control Service which VDK CLI uses also has security disabled. +If Control Service configured require authentication: vdk login must have finished successfully. +Or alternatively correct VDK_API_TOKEN_AUTHORIZATION_URL and VDK_API_TOKEN must be set correctly and will behave same as `vdk login -t api-token`. +If vdk login is used - it take priority over environment variables set VDK_API_TOKEN_AUTHORIZATION_URL and VDK_API_TOKEN +To clear previous login info (aka logout) use `vdk logout`. -In case of credentials type login flow we start a process on port `31113` to receive the credentials. +In case of credentials type vdk login flow we start a process on port `31113` to receive the credentials. If you already have process running on `31113` you can override the value. To override the port set environmental variable `OAUTH_PORT` with free port which the client can use. diff --git a/projects/vdk-control-cli/src/vdk/internal/control/auth/apikey_auth.py b/projects/vdk-control-cli/src/vdk/internal/control/auth/apikey_auth.py new file mode 100644 index 0000000000..60d5dc80fb --- /dev/null +++ b/projects/vdk-control-cli/src/vdk/internal/control/auth/apikey_auth.py @@ -0,0 +1,39 @@ +# Copyright 2021 VMware, Inc. +# SPDX-License-Identifier: Apache-2.0 +from typing import Optional + +from vdk.internal.control.auth.auth import Authentication +from vdk.internal.control.auth.login_types import LoginTypes +from vdk.internal.control.configuration.vdk_config import VDKConfig + + +class ApiKeyAuthentication: + """ + Class that execute authentication process using API token. + It will use the API token to get temporary access token using api token authorization URL. + See Authentication class as well. + """ + + def __init__( + self, + api_token_authorization_url: Optional[str] = None, + api_token: Optional[str] = None, + ): + """ + :param api_token_authorization_url: Authorization URL - Same as login --api-token-authorization-server-url. + :param api_token: API Token - Same as login --api-token. + """ + self.__api_token = api_token + self.__api_token_authorization_url = api_token_authorization_url + self.__auth = Authentication() + + def authentication_process(self) -> None: + """ + Executes the authentication process and caches the generated access token so it can be used during REST calls. + """ + self.__auth.update_api_token_authorization_url( + self.__api_token_authorization_url + ) + self.__auth.update_api_token(self.__api_token) + self.__auth.update_auth_type(LoginTypes.API_TOKEN.value) + self.__auth.acquire_and_cache_access_token() diff --git a/projects/vdk-control-cli/src/vdk/internal/control/auth/auth.py b/projects/vdk-control-cli/src/vdk/internal/control/auth/auth.py index 8b9a1da057..1882e73ddb 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/auth/auth.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/auth/auth.py @@ -3,12 +3,14 @@ import json import logging import time +from typing import Optional from requests import post from requests.auth import HTTPBasicAuth from requests_oauthlib import OAuth2Session from vdk.internal.control.auth.auth_request_values import AuthRequestValues from vdk.internal.control.auth.login_types import LoginTypes +from vdk.internal.control.configuration.vdk_config import VDKConfig from vdk.internal.control.configuration.vdk_config import VDKConfigFolder from vdk.internal.control.exception.vdk_exception import VDKException @@ -82,8 +84,9 @@ def deserialize(content: str): class Authentication: REFRESH_TOKEN_GRANT_TYPE = "refresh_token" # nosec - def __init__(self, conf=VDKConfigFolder()): + def __init__(self, conf=VDKConfigFolder(), vdk_config=VDKConfig()): self._conf = conf + self.__vdk_config = vdk_config self._cache = self.__load_cache() def __load_cache(self): @@ -129,12 +132,12 @@ def __exchange_api_for_access_token(self): + int(token_response.get(AuthRequestValues.EXPIRATION_TIME_KEY.value, "0")) ) - def read_access_token(self): + def read_access_token(self) -> Optional[str]: """ Read access token from _cache or fetch it from Authorization server. If not available in _cache it will get it using provided configuration during VDK CLI login to fetch it. If it detects that token is about to expire it will try to refresh it. - :return: the access token + :return: the access token or None if it cannot detect any credentials. """ if ( not self._cache.access_token @@ -159,12 +162,19 @@ def acquire_and_cache_access_token(self): and self._configured_refresh_token() ): self.__exchange_refresh_for_access_token() + elif ( + self.__vdk_config.api_token + and self.__vdk_config.api_token_authorization_url + ): + self.update_api_token(self.__vdk_config.api_token) + self.update_api_token_authorization_url( + self.__vdk_config.api_token_authorization_url + ) + self.__exchange_api_for_access_token() else: - raise VDKException( - what="Not authenticated.", - why="Most likely VDK CLI login need to be completed first.", - consequence="Operation that require authorization will not work.", - countermeasure="Please execute login using VDK CLI login command", + log.debug( + "No authentication mechanism found. Will not cache access token." + "If Control Service authentication is enabled, API calls will fail." ) def __exchange_refresh_for_access_token(self): diff --git a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/login_group/login.py b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/login_group/login.py index 3d170b8045..665e1351b0 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/command_groups/login_group/login.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/command_groups/login_group/login.py @@ -1,9 +1,11 @@ # Copyright 2021 VMware, Inc. # SPDX-License-Identifier: Apache-2.0 import click +from vdk.internal.control.auth.apikey_auth import ApiKeyAuthentication from vdk.internal.control.auth.auth import Authentication from vdk.internal.control.auth.login_types import LoginTypes from vdk.internal.control.auth.redirect_auth import RedirectAuthentication +from vdk.internal.control.configuration.vdk_config import VDKConfig from vdk.internal.control.exception.vdk_exception import VDKException from vdk.internal.control.utils import cli_utils from vdk.internal.control.utils.cli_utils import extended_option @@ -131,11 +133,8 @@ def login( countermeasure="Please login providing correct API Token. ", ) else: - auth = Authentication() - auth.update_api_token_authorization_url(api_token_authorization_url) - auth.update_api_token(api_token) - auth.update_auth_type(auth_type) - auth.acquire_and_cache_access_token() + apikey_auth = ApiKeyAuthentication(api_token_authorization_url, api_token) + apikey_auth.authentication_process() click.echo("Login Successful") else: click.echo(f"Login type: {auth_type} not supported") diff --git a/projects/vdk-control-cli/src/vdk/internal/control/configuration/vdk_config.py b/projects/vdk-control-cli/src/vdk/internal/control/configuration/vdk_config.py index 231aed29ef..05623ebad2 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/configuration/vdk_config.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/configuration/vdk_config.py @@ -55,25 +55,17 @@ def local_config_folder(self) -> str: """ return os.getenv("VDK_BASE_CONFIG_FOLDER", str(Path.home())) - @property - def authentication_disabled(self) -> bool: - return os.getenv("VDK_AUTHENTICATION_DISABLE", "False").lower() in ( - "true", - "1", - "t", - ) - @property def control_service_rest_api_url(self) -> str: return os.getenv("VDK_CONTROL_SERVICE_REST_API_URL", None) @property - def api_token_authorization_server_url(self) -> str: + def api_token_authorization_url(self) -> str: """ Location of the API Token OAuth2 provider. Same as login --api-token-authorization-server-url This is used as default. """ - return os.getenv("VDK_API_TOKEN_AUTHORIZATION_SERVER_URL", None) + return os.getenv("VDK_API_TOKEN_AUTHORIZATION_URL", None) @property def api_token(self) -> str: diff --git a/projects/vdk-control-cli/src/vdk/internal/control/rest_lib/factory.py b/projects/vdk-control-cli/src/vdk/internal/control/rest_lib/factory.py index 32606d14ae..79e737abf1 100644 --- a/projects/vdk-control-cli/src/vdk/internal/control/rest_lib/factory.py +++ b/projects/vdk-control-cli/src/vdk/internal/control/rest_lib/factory.py @@ -43,16 +43,11 @@ def __init__(self, rest_api_url): ) self.config.client_side_validation = False self.config.verify_ssl = self.vdk_config.http_verify_ssl - if ( - self.vdk_config.authentication_disabled - or load_default_authentication_disable() - ): - log.info("Authentication is disabled.") - else: - auth = Authentication() - # For now there's no need to add auto-update since this is called usually in a shell script - # and each command will have short execution life even when multiple requests to API are made. - self.config.access_token = auth.read_access_token() + + auth = Authentication() + # For now there's no need to add auto-update since this is called usually in a shell script + # and each command will have short execution life even when multiple requests to API are made. + self.config.access_token = auth.read_access_token() def _new_api_client(self): api_client = ApiClient(self.config)