Exemplo n.º 1
0
def _delete_data_from_registry(login_server, path, username, password, retry_times=3, retry_interval=5):
    for i in range(0, retry_times):
        errorMessage = None
        try:
            response = requests.delete(
                'https://{}{}'.format(login_server, path),
                headers=_get_authorization_header(username, password),
                verify=(not should_disable_connection_verify())
            )
            log_registry_response(response)

            if response.status_code == 200 or response.status_code == 202:
                return
            elif response.status_code == 401:
                raise CLIError(_parse_error_message('Authentication required.', response))
            elif response.status_code == 404:
                raise CLIError(_parse_error_message('The requested data does not exist.', response))
            else:
                raise Exception(_parse_error_message('Could not delete the requested data.', response))
        except CLIError:
            raise
        except Exception as e:  # pylint: disable=broad-except
            errorMessage = str(e)
            logger.debug('Retrying %s with exception %s', i + 1, errorMessage)
            time.sleep(retry_interval)
    if errorMessage:
        raise CLIError(errorMessage)
Exemplo n.º 2
0
def load_images_from_aliases_doc(cli_ctx, publisher=None, offer=None, sku=None):
    import requests
    from azure.cli.core.cloud import CloudEndpointNotSetException
    from azure.cli.core.util import should_disable_connection_verify
    try:
        target_url = cli_ctx.cloud.endpoints.vm_image_alias_doc
    except CloudEndpointNotSetException:
        raise CLIError("'endpoint_vm_image_alias_doc' isn't configured. Please invoke 'az cloud update' to configure "
                       "it or use '--all' to retrieve images from server")
    # under hack mode(say through proxies with unsigned cert), opt out the cert verification
    response = requests.get(target_url, verify=(not should_disable_connection_verify()))
    if response.status_code != 200:
        raise CLIError("Failed to retrieve image alias doc '{}'. Error: '{}'".format(target_url, response))
    dic = json.loads(response.content.decode())
    try:
        all_images = []
        result = (dic['outputs']['aliases']['value'])
        for v in result.values():  # loop around os
            for alias, vv in v.items():  # loop around distros
                all_images.append({
                    'urnAlias': alias,
                    'publisher': vv['publisher'],
                    'offer': vv['offer'],
                    'sku': vv['sku'],
                    'version': vv['version']
                })

        all_images = [i for i in all_images if (_matched(publisher, i['publisher']) and
                                                _matched(offer, i['offer']) and
                                                _matched(sku, i['sku']))]
        return all_images
    except KeyError:
        raise CLIError('Could not retrieve image list from {}'.format(target_url))
Exemplo n.º 3
0
def _get_manifest_digest(login_server, repository, tag, username, password, retry_times=3, retry_interval=5):
    url = 'https://{}/v2/{}/manifests/{}'.format(login_server, repository, tag)
    headers = get_authorization_header(username, password)
    headers.update(MANIFEST_V2_HEADER)

    for i in range(0, retry_times):
        errorMessage = None
        try:
            response = requests.get(
                url=url,
                headers=headers,
                verify=(not should_disable_connection_verify())
            )
            log_registry_response(response)

            if response.status_code == 200 and response.headers and 'Docker-Content-Digest' in response.headers:
                return response.headers['Docker-Content-Digest']
            elif response.status_code == 401:
                raise CLIError(parse_error_message('Authentication required.', response))
            elif response.status_code == 404:
                raise CLIError(parse_error_message('The manifest does not exist.', response))
            else:
                raise Exception(parse_error_message('Could not get manifest digest.', response))
        except CLIError:
            raise
        except Exception as e:  # pylint: disable=broad-except
            errorMessage = str(e)
            logger.debug('Retrying %s with exception %s', i + 1, errorMessage)
            time.sleep(retry_interval)

    raise CLIError(errorMessage)
Exemplo n.º 4
0
def _whl_download_from_url(url_parse_result, ext_file):
    from azure.cli.core.util import should_disable_connection_verify
    url = url_parse_result.geturl()
    r = requests.get(url, stream=True, verify=(not should_disable_connection_verify()))
    if r.status_code != 200:
        raise CLIError("Request to {} failed with {}".format(url, r.status_code))
    with open(ext_file, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024):
            if chunk:  # ignore keep-alive new chunks
                f.write(chunk)
Exemplo n.º 5
0
def _obtain_data_from_registry(login_server,
                               path,
                               username,
                               password,
                               result_index,
                               retry_times=3,
                               retry_interval=5,
                               pagination=20):
    resultList = []
    executeNextHttpCall = True

    while executeNextHttpCall:
        executeNextHttpCall = False
        for i in range(0, retry_times):
            errorMessage = None
            try:
                response = requests.get(
                    'https://{}{}'.format(login_server, path),
                    headers=_get_authorization_header(username, password),
                    params=_get_pagination_params(pagination),
                    verify=(not should_disable_connection_verify())
                )
                log_registry_response(response)

                if response.status_code == 200:
                    result = response.json()[result_index]
                    if result:
                        resultList += response.json()[result_index]
                    if 'link' in response.headers and response.headers['link']:
                        linkHeader = response.headers['link']
                        # The registry is telling us there's more items in the list,
                        # and another call is needed. The link header looks something
                        # like `Link: </v2/_catalog?last=hello-world&n=1>; rel="next"`
                        # we should follow the next path indicated in the link header
                        path = linkHeader[(linkHeader.index('<') + 1):linkHeader.index('>')]
                        executeNextHttpCall = True
                    break
                elif response.status_code == 401:
                    raise CLIError(_parse_error_message('Authentication required.', response))
                elif response.status_code == 404:
                    raise CLIError(_parse_error_message('The requested data does not exist.', response))
                else:
                    raise Exception(_parse_error_message('Could not get the requested data.', response))
            except CLIError:
                raise
            except Exception as e:  # pylint: disable=broad-except
                errorMessage = str(e)
                logger.debug('Retrying %s with exception %s', i + 1, errorMessage)
                time.sleep(retry_interval)
        if errorMessage:
            raise CLIError(errorMessage)

    return resultList
Exemplo n.º 6
0
def get_index(index_url=None):
    from azure.cli.core.util import should_disable_connection_verify
    index_url = index_url or DEFAULT_INDEX_URL
    try:
        response = requests.get(index_url, verify=(not should_disable_connection_verify()))
        if response.status_code == 200:
            return response.json()
        else:
            msg = ERR_TMPL_NON_200.format(response.status_code, index_url)
            raise CLIError(msg)
    except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as err:
        msg = ERR_TMPL_NO_NETWORK.format(str(err))
        raise CLIError(msg)
    except ValueError as err:
        msg = ERR_TMPL_BAD_JSON.format(str(err))
        raise CLIError(msg)
Exemplo n.º 7
0
def get_index(index_url=None):
    from azure.cli.core.util import should_disable_connection_verify
    index_url = index_url or DEFAULT_INDEX_URL

    for try_number in range(TRIES):
        try:
            response = requests.get(index_url, verify=(not should_disable_connection_verify()))
            if response.status_code == 200:
                return response.json()
            msg = ERR_TMPL_NON_200.format(response.status_code, index_url)
            raise CLIError(msg)
        except (requests.exceptions.ConnectionError, requests.exceptions.HTTPError) as err:
            msg = ERR_TMPL_NO_NETWORK.format(str(err))
            raise CLIError(msg)
        except ValueError as err:
            # Indicates that url is not redirecting properly to intended index url, we stop retrying after TRIES calls
            if try_number == TRIES - 1:
                msg = ERR_TMPL_BAD_JSON.format(str(err))
                raise CLIError(msg)
            import time
            time.sleep(0.5)
            continue
Exemplo n.º 8
0
    def _get_auth_token(self):
        profile = Profile(cli_ctx=self.cli_ctx)
        # Generate an Azure token with the VSTS resource app id
        auth_token, _, _ = profile.get_raw_token()
        content = {
            'resourceId': self.remote_host,
            'protocol': 'tcptunnel',
            'workloadHostPort': self.remote_port,
            'aztoken': auth_token[1],
            'token': self.last_token,
        }
        if self.node_id:
            custom_header = {'X-Node-Id': self.node_id}
        else:
            custom_header = {}

        web_address = 'https://{}/api/tokens'.format(self.bastion.dns_name)
        response = requests.post(
            web_address,
            data=content,
            headers=custom_header,
            verify=(not should_disable_connection_verify()))
        response_json = None

        if response.content is not None:
            response_json = json.loads(response.content.decode("utf-8"))

        if response.status_code not in [200]:
            if response_json is not None and response_json[
                    "message"] is not None:
                exp = CloudError(response, error=response_json["message"])
            else:
                exp = CloudError(response)
            raise exp

        self.last_token = response_json["authToken"]
        self.node_id = response_json["nodeId"]
        return self.last_token
Exemplo n.º 9
0
def get_index(index_url=None, cli_ctx=None):
    import requests
    from azure.cli.core.util import should_disable_connection_verify
    index_url = index_url or get_index_url(cli_ctx=cli_ctx)

    for try_number in range(TRIES):
        try:
            response = requests.get(
                index_url, verify=(not should_disable_connection_verify()))
            if response.status_code == 200:
                return response.json()
            msg = ERR_TMPL_NON_200.format(response.status_code, index_url)
            raise CLIError(msg)
        except (requests.exceptions.ConnectionError,
                requests.exceptions.HTTPError, ValueError) as err:
            if try_number == TRIES - 1:
                # ValueError indicates that shortlink url is not redirecting properly to intended index url
                msg = ERR_TMPL_BAD_JSON.format(str(err)) if isinstance(err, ValueError) else \
                    ERR_TMPL_NO_NETWORK.format(str(err))
                raise CLIError(msg)
            import time
            time.sleep(0.5)
            continue
Exemplo n.º 10
0
def keyvault_data_plane_factory(cli_ctx, _):
    from azure.keyvault import KeyVaultAuthentication, KeyVaultClient
    from azure.cli.core.util import should_disable_connection_verify

    version = str(get_api_version(cli_ctx, ResourceType.DATA_KEYVAULT))

    def get_token(server, resource, scope):  # pylint: disable=unused-argument
        import adal
        try:
            return Profile(cli_ctx=cli_ctx).get_raw_token(resource)[0]
        except adal.AdalError as err:
            # pylint: disable=no-member
            if (hasattr(err, 'error_response')
                    and ('error_description' in err.error_response) and
                ('AADSTS70008:' in err.error_response['error_description'])):
                raise CLIError(
                    "Credentials have expired due to inactivity. Please run 'az login'"
                )
            raise CLIError(err)

    client = KeyVaultClient(KeyVaultAuthentication(get_token),
                            api_version=version)

    # HACK, work around the fact that KeyVault library does't take confiuration object on constructor
    # which could be used to turn off the verifiaction. Remove this once we migrate to new data plane library
    # pylint: disable=protected-access
    if hasattr(client, '_client') and hasattr(client._client, 'config'):
        verify = not should_disable_connection_verify()
        client._client.config.connection.verify = verify
    else:
        from knack.log import get_logger
        logger = get_logger(__name__)
        logger.info(
            'Could not find the configuration object to turn off the verification if needed'
        )

    return client
Exemplo n.º 11
0
def _get_registry_status(login_server, registry_name, ignore_errors):
    import socket

    registry_ip = None

    try:
        registry_ip = socket.gethostbyname(login_server)
    except socket.gaierror:
        pass

    if not registry_ip:
        from ._errors import CONNECTIVITY_DNS_ERROR
        _handle_error(CONNECTIVITY_DNS_ERROR.format_error_message(login_server), ignore_errors)
        return False

    print_pass("DNS lookup to {} at IP {}".format(login_server, registry_ip))

    import requests
    from requests.exceptions import SSLError, RequestException
    from azure.cli.core.util import should_disable_connection_verify

    try:
        challenge = requests.get('https://' + login_server + '/v2/', verify=(not should_disable_connection_verify()))
    except SSLError:
        from ._errors import CONNECTIVITY_SSL_ERROR
        _handle_error(CONNECTIVITY_SSL_ERROR.format_error_message(login_server), ignore_errors)
        return False
    except RequestException:
        from ._errors import CONNECTIVITY_CHALLENGE_ERROR
        _handle_error(CONNECTIVITY_CHALLENGE_ERROR.format_error_message(login_server), ignore_errors)
        return False

    if challenge.status_code == 403:
        from ._errors import CONNECTIVITY_FORBIDDEN_ERROR
        _handle_error(CONNECTIVITY_FORBIDDEN_ERROR.format_error_message(login_server, registry_name), ignore_errors)
        return False
    return True
Exemplo n.º 12
0
def get_pool_manager(url):
    proxies = urllib.request.getproxies()
    bypass_proxy = urllib.request.proxy_bypass(
        urllib.parse.urlparse(url).hostname)

    if 'https' in proxies and not bypass_proxy:
        proxy = urllib.parse.urlparse(proxies['https'])

        if proxy.username and proxy.password:
            proxy_headers = urllib3.util.make_headers(
                proxy_basic_auth='{0}:{1}'.format(proxy.username,
                                                  proxy.password))
            logger.debug('Setting proxy-authorization header for basic auth')
        else:
            proxy_headers = None

        logger.info('Using proxy for app service tunnel connection')
        http = urllib3.ProxyManager(proxy.geturl(),
                                    proxy_headers=proxy_headers)
    else:
        http = urllib3.PoolManager()

    if should_disable_connection_verify():
        http.connection_pool_kw['cert_reqs'] = 'CERT_NONE'
    else:
        http.connection_pool_kw['cert_reqs'] = 'CERT_REQUIRED'
        if REQUESTS_CA_BUNDLE in os.environ:
            ca_bundle_file = os.environ[REQUESTS_CA_BUNDLE]
            logger.debug("Using CA bundle file at '%s'.", ca_bundle_file)
            if not os.path.isfile(ca_bundle_file):
                raise ValidationError(
                    'REQUESTS_CA_BUNDLE environment variable is specified with an invalid file path'
                )
        else:
            ca_bundle_file = certifi.where()
        http.connection_pool_kw['ca_certs'] = ca_bundle_file
    return http
Exemplo n.º 13
0
def _delete_data_from_registry(login_server,
                               path,
                               username,
                               password,
                               retry_times=3,
                               retry_interval=5):
    for i in range(0, retry_times):
        errorMessage = None
        try:
            response = requests.delete(
                'https://{}{}'.format(login_server, path),
                headers=_get_authorization_header(username, password),
                verify=(not should_disable_connection_verify()))
            log_registry_response(response)

            if response.status_code == 200 or response.status_code == 202:
                return
            elif response.status_code == 401:
                raise CLIError(
                    _parse_error_message('Authentication required.', response))
            elif response.status_code == 404:
                raise CLIError(
                    _parse_error_message('The requested data does not exist.',
                                         response))
            else:
                raise Exception(
                    _parse_error_message(
                        'Could not delete the requested data.', response))
        except CLIError:
            raise
        except Exception as e:  # pylint: disable=broad-except
            errorMessage = str(e)
            logger.debug('Retrying %s with exception %s', i + 1, errorMessage)
            time.sleep(retry_interval)

    raise CLIError(errorMessage)
Exemplo n.º 14
0
def _get_aad_token_after_challenge(cli_ctx,
                                   token_params,
                                   login_server,
                                   only_refresh_token,
                                   repository,
                                   artifact_repository,
                                   permission,
                                   is_diagnostics_context):
    authurl = urlparse(token_params['realm'])
    authhost = urlunparse((authurl[0], authurl[1], '/oauth2/exchange', '', '', ''))

    from azure.cli.core._profile import Profile
    profile = Profile(cli_ctx=cli_ctx)

    # this might be a cross tenant scenario, so pass subscription to get_raw_token
    subscription = get_subscription_id(cli_ctx)
    creds, _, tenant = profile.get_raw_token(subscription=subscription)

    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    content = {
        'grant_type': 'access_token',
        'service': token_params['service'],
        'tenant': tenant,
        'access_token': creds[1]
    }

    response = requests.post(authhost, urlencode(content), headers=headers,
                             verify=(not should_disable_connection_verify()))

    if response.status_code not in [200]:
        from ._errors import CONNECTIVITY_REFRESH_TOKEN_ERROR
        if is_diagnostics_context:
            return CONNECTIVITY_REFRESH_TOKEN_ERROR.format_error_message(login_server, response.status_code)
        raise CLIError(CONNECTIVITY_REFRESH_TOKEN_ERROR.format_error_message(login_server, response.status_code)
                       .get_error_message())

    refresh_token = loads(response.content.decode("utf-8"))["refresh_token"]
    if only_refresh_token:
        return refresh_token

    authhost = urlunparse((authurl[0], authurl[1], '/oauth2/token', '', '', ''))

    if repository:
        scope = 'repository:{}:{}'.format(repository, permission)
    elif artifact_repository:
        scope = 'artifact-repository:{}:{}'.format(artifact_repository, permission)
    else:
        # catalog only has * as permission, even for a read operation
        scope = 'registry:catalog:*'

    content = {
        'grant_type': 'refresh_token',
        'service': login_server,
        'scope': scope,
        'refresh_token': refresh_token
    }
    response = requests.post(authhost, urlencode(content), headers=headers,
                             verify=(not should_disable_connection_verify()))

    if response.status_code not in [200]:
        from ._errors import CONNECTIVITY_ACCESS_TOKEN_ERROR
        if is_diagnostics_context:
            return CONNECTIVITY_ACCESS_TOKEN_ERROR.format_error_message(login_server, response.status_code)
        raise CLIError(CONNECTIVITY_ACCESS_TOKEN_ERROR.format_error_message(login_server, response.status_code)
                       .get_error_message())

    return loads(response.content.decode("utf-8"))["access_token"]
Exemplo n.º 15
0
def _get_aad_token(cli_ctx,
                   login_server,
                   only_refresh_token,
                   repository=None,
                   artifact_repository=None,
                   permission=None):
    """Obtains refresh and access tokens for an AAD-enabled registry.
    :param str login_server: The registry login server URL to log in to
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str artifact_repository: Artifact repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """
    if repository and artifact_repository:
        raise ValueError(
            "Only one of repository and artifact_repository can be provided.")

    if (repository or
            artifact_repository) and permission not in ACCESS_TOKEN_PERMISSION:
        raise ValueError(
            "Permission is required for a repository or artifact_repository. Allowed access token permission: {}"
            .format(ACCESS_TOKEN_PERMISSION))

    login_server = login_server.rstrip('/')

    challenge = requests.get('https://' + login_server + '/v2/',
                             verify=(not should_disable_connection_verify()))
    if challenge.status_code not in [
            401
    ] or 'WWW-Authenticate' not in challenge.headers:
        raise CLIError(
            "Registry '{}' did not issue a challenge.".format(login_server))

    authenticate = challenge.headers['WWW-Authenticate']

    tokens = authenticate.split(' ', 2)
    if len(tokens) < 2 or tokens[0].lower() != 'bearer':
        raise CLIError(
            "Registry '{}' does not support AAD login.".format(login_server))

    params = {
        y[0]: y[1].strip('"')
        for y in (x.strip().split('=', 2) for x in tokens[1].split(','))
    }
    if 'realm' not in params or 'service' not in params:
        raise CLIError(
            "Registry '{}' does not support AAD login.".format(login_server))

    authurl = urlparse(params['realm'])
    authhost = urlunparse(
        (authurl[0], authurl[1], '/oauth2/exchange', '', '', ''))

    from azure.cli.core._profile import Profile
    profile = Profile(cli_ctx=cli_ctx)
    creds, _, tenant = profile.get_raw_token()

    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    content = {
        'grant_type': 'access_token',
        'service': params['service'],
        'tenant': tenant,
        'access_token': creds[1]
    }

    response = requests.post(authhost,
                             urlencode(content),
                             headers=headers,
                             verify=(not should_disable_connection_verify()))

    if response.status_code not in [200]:
        raise CLIError(
            "Access to registry '{}' was denied. Response code: {}.".format(
                login_server, response.status_code))

    refresh_token = loads(response.content.decode("utf-8"))["refresh_token"]
    if only_refresh_token:
        return refresh_token

    authhost = urlunparse(
        (authurl[0], authurl[1], '/oauth2/token', '', '', ''))

    if repository:
        scope = 'repository:{}:{}'.format(repository, permission)
    elif artifact_repository:
        scope = 'artifact-repository:{}:{}'.format(artifact_repository,
                                                   permission)
    else:
        # catalog only has * as permission, even for a read operation
        scope = 'registry:catalog:*'

    content = {
        'grant_type': 'refresh_token',
        'service': login_server,
        'scope': scope,
        'refresh_token': refresh_token
    }
    response = requests.post(authhost,
                             urlencode(content),
                             headers=headers,
                             verify=(not should_disable_connection_verify()))

    if response.status_code not in [200]:
        raise CLIError(
            "Access to registry '{}' was denied. Response code: {}.".format(
                login_server, response.status_code))

    return loads(response.content.decode("utf-8"))["access_token"]
Exemplo n.º 16
0
def _get_credentials(cli_ctx,
                     registry_name,
                     username,
                     password,
                     only_refresh_token,
                     repository=None,
                     artifact_repository=None,
                     permission=None):
    """Try to get AAD authorization tokens or admin user credentials.
    :param str registry_name: The name of container registry
    :param str username: The username used to log into the container registry
    :param str password: The password used to log into the container registry
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str artifact_repository: Artifact repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """
    try:
        registry, resource_group_name = get_registry_by_name(
            cli_ctx, registry_name)
        login_server = registry.login_server
    except ResourceNotFound as e:
        # Try to use the pre-defined login server suffix to construct login server from registry name.
        login_server_suffix = get_login_server_suffix(cli_ctx)
        if not login_server_suffix:
            raise
        registry = None
        login_server = '{}{}'.format(registry_name,
                                     login_server_suffix).lower()
        resource_not_found = str(e)

    # Validate the login server is reachable
    try:
        requests.get('https://' + login_server + '/v2/',
                     verify=(not should_disable_connection_verify()))
    except RequestException:
        raise CLIError(
            "Could not connect to the registry '{}'. ".format(login_server) +
            "Please verify if the registry exists.")

    # 1. if username was specified, verify that password was also specified
    if username:
        if not password:
            try:
                password = prompt_pass(msg='Password: '******'Please specify both username and password in non-interactive mode.'
                )

        return login_server, username, password

    # 2. if we don't yet have credentials, attempt to get a refresh token
    if not password and (not registry
                         or registry.sku.name in MANAGED_REGISTRY_SKU):
        try:
            password = _get_aad_token(cli_ctx, login_server,
                                      only_refresh_token, repository,
                                      artifact_repository, permission)
            return login_server, EMPTY_GUID, password
        except CLIError as e:
            logger.warning(
                "Unable to get AAD authorization tokens with message: %s",
                str(e))

    # 3. if we still don't have credentials, attempt to get the admin credentials (if enabled)
    if not password:
        error_message = "Unable to get admin user credentials with message"
        if registry:
            if registry.admin_user_enabled:
                try:
                    cred = cf_acr_registries(cli_ctx).list_credentials(
                        resource_group_name, registry_name)
                    username = cred.username
                    password = cred.passwords[0].value
                    return login_server, username, password
                except CLIError as e:
                    logger.warning("%s: %s", error_message, str(e))
            else:
                logger.warning("%s: %s", error_message,
                               "Admin user is disabled.")
        else:
            logger.warning("%s: %s", error_message, resource_not_found)

    # 4. if we still don't have credentials, prompt the user
    if not password:
        try:
            username = prompt('Username: '******'Password: '******'Unable to authenticate using AAD or admin login credentials. '
                +
                'Please specify both username and password in non-interactive mode.'
            )

    return login_server, None, None
Exemplo n.º 17
0
def _get_aad_token(cli_ctx,
                   login_server,
                   only_refresh_token,
                   repository=None,
                   permission='*'):
    """Obtains refresh and access tokens for an AAD-enabled registry.
    :param str login_server: The registry login server URL to log in to
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """
    login_server = login_server.rstrip('/')

    challenge = requests.get('https://' + login_server + '/v2/',
                             verify=(not should_disable_connection_verify()))
    if challenge.status_code not in [
            401
    ] or 'WWW-Authenticate' not in challenge.headers:
        raise CLIError(
            "Registry '{}' did not issue a challenge.".format(login_server))

    authenticate = challenge.headers['WWW-Authenticate']

    tokens = authenticate.split(' ', 2)
    if len(tokens) < 2 or tokens[0].lower() != 'bearer':
        raise CLIError(
            "Registry '{}' does not support AAD login.".format(login_server))

    params = {
        y[0]: y[1].strip('"')
        for y in (x.strip().split('=', 2) for x in tokens[1].split(','))
    }
    if 'realm' not in params or 'service' not in params:
        raise CLIError(
            "Registry '{}' does not support AAD login.".format(login_server))

    authurl = urlparse(params['realm'])
    authhost = urlunparse(
        (authurl[0], authurl[1], '/oauth2/exchange', '', '', ''))

    from azure.cli.core._profile import Profile
    profile = Profile(cli_ctx=cli_ctx)
    sp_id, refresh, access, tenant = profile.get_refresh_token()

    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    if not sp_id:
        if not refresh:
            content = {
                'grant_type': 'access_token',
                'service': params['service'],
                'tenant': tenant,
                'access_token': access
            }
        else:
            content = {
                'grant_type': 'access_token_refresh_token',
                'service': params['service'],
                'tenant': tenant,
                'access_token': access,
                'refresh_token': refresh
            }
    else:
        content = {
            'grant_type': 'spn',
            'service': params['service'],
            'tenant': tenant,
            'username': sp_id,
            'password': refresh
        }

    response = requests.post(authhost,
                             urlencode(content),
                             headers=headers,
                             verify=(not should_disable_connection_verify()))

    if response.status_code not in [200]:
        raise CLIError(
            "Access to registry '{}' was denied. Response code: {}.".format(
                login_server, response.status_code))

    refresh_token = loads(response.content.decode("utf-8"))["refresh_token"]
    if only_refresh_token:
        return refresh_token

    authhost = urlunparse(
        (authurl[0], authurl[1], '/oauth2/token', '', '', ''))

    if repository is None:
        scope = 'registry:catalog:*'
    else:
        scope = 'repository:{}:{}'.format(repository, permission)

    content = {
        'grant_type': 'refresh_token',
        'service': login_server,
        'scope': scope,
        'refresh_token': refresh_token
    }
    response = requests.post(authhost,
                             urlencode(content),
                             headers=headers,
                             verify=(not should_disable_connection_verify()))
    access_token = loads(response.content.decode("utf-8"))["access_token"]

    return access_token
Exemplo n.º 18
0
def enable_one_deploy(cmd,
                      resource_group_name,
                      name,
                      src,
                      deploy_type=None,
                      is_async=None,
                      target_path=None,
                      timeout=None,
                      slot=None):
    import ntpath
    logger.info("Preparing for deployment")
    user_name, password = _get_site_credential(cmd.cli_ctx,
                                               resource_group_name, name, slot)

    try:
        scm_url = _get_scm_url(cmd, resource_group_name, name, slot)
    except ValueError:
        raise CLIError('Failed to fetch scm url for for app {}'.format(name))

    # Interpret deployment type from the file extension if the type parameter is not passed
    if deploy_type is None:
        fileName = ntpath.basename(src)
        fileExtension = fileName.split(".", 1)[1]
        if fileExtension in ('war', 'jar', 'zip', 'ear'):
            deploy_type = fileExtension
        elif fileExtension in ('sh', 'bat'):
            deploy_type = 'startup'
        else:
            deploy_type = 'static'

    logger.warning(
        "Deployment type: %s. To override deloyment type, please specify the --type parameter. Possible values: static, zip, war, jar, ear, startup",
        deploy_type)
    deploy_url = scm_url + '/api/publish?type=' + deploy_type

    if is_async is not None:
        deploy_url = deploy_url + '&async=true'

    if target_path is not None:
        deploy_url = deploy_url + '&path=' + target_path

    deployment_status_url = scm_url + '/api/deployments/latest'

    from azure.cli.core.util import (should_disable_connection_verify,
                                     get_az_user_agent)

    import urllib3
    authorization = urllib3.util.make_headers(
        basic_auth='{0}:{1}'.format(user_name, password))
    headers = authorization
    headers['Content-Type'] = 'application/octet-stream'
    headers['Cache-Control'] = 'no-cache'
    headers['User-Agent'] = get_az_user_agent()

    import requests
    import os

    # Read file content
    with open(os.path.realpath(os.path.expanduser(src)), 'rb') as fs:
        artifact_content = fs.read()
        logger.warning("Starting deployment...")
        res = requests.post(deploy_url,
                            data=artifact_content,
                            headers=headers,
                            verify=not should_disable_connection_verify())

    # check if an error occured during deployment
    if res.status_code == 400:
        raise CLIError("An error occured durng deployment: {}".format(
            res.text))

    # check if there's an ongoing process
    if res.status_code == 409:
        raise CLIError(
            "There may be an ongoing deployment or your app setting has WEBSITE_RUN_FROM_PACKAGE. "
            "Please track your deployment in {} and ensure the WEBSITE_RUN_FROM_PACKAGE app setting "
            "is removed.".format(deployment_status_url))

    # check the status of async deployment
    if res.status_code == 202:
        logger.warning("Asynchronous deployment request has been recieved")
        response = _check_zip_deployment_status(cmd, resource_group_name, name,
                                                deployment_status_url,
                                                authorization, timeout)
        return response

    return logger.warning("Deployment has completed successfully")
Exemplo n.º 19
0
def github_release_version_exists(version, repo, org='microsoft'):
    version_url = f'https://api.github.com/repos/{org}/{repo}/releases/tags/{version}'
    version_res = requests.get(version_url, verify=not should_disable_connection_verify())
    return version_res.status_code < 400
Exemplo n.º 20
0
def _get_aad_token(cli_ctx,
                   login_server,
                   only_refresh_token,
                   repository=None,
                   artifact_repository=None,
                   permission=None):
    """Obtains refresh and access tokens for an AAD-enabled registry.
    :param str login_server: The registry login server URL to log in to
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str artifact_repository: Artifact repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """
    if repository and artifact_repository:
        raise ValueError("Only one of repository and artifact_repository can be provided.")

    if (repository or artifact_repository) and permission not in ACCESS_TOKEN_PERMISSION:
        raise ValueError(
            "Permission is required for a repository or artifact_repository. Allowed access token permission: {}"
            .format(ACCESS_TOKEN_PERMISSION))

    login_server = login_server.rstrip('/')

    challenge = requests.get('https://' + login_server + '/v2/', verify=(not should_disable_connection_verify()))
    if challenge.status_code not in [401] or 'WWW-Authenticate' not in challenge.headers:
        raise CLIError("Registry '{}' did not issue a challenge.".format(login_server))

    authenticate = challenge.headers['WWW-Authenticate']

    tokens = authenticate.split(' ', 2)
    if len(tokens) < 2 or tokens[0].lower() != 'bearer':
        raise CLIError("Registry '{}' does not support AAD login.".format(login_server))

    params = {y[0]: y[1].strip('"') for y in
              (x.strip().split('=', 2) for x in tokens[1].split(','))}
    if 'realm' not in params or 'service' not in params:
        raise CLIError("Registry '{}' does not support AAD login.".format(login_server))

    authurl = urlparse(params['realm'])
    authhost = urlunparse((authurl[0], authurl[1], '/oauth2/exchange', '', '', ''))

    from azure.cli.core._profile import Profile
    profile = Profile(cli_ctx=cli_ctx)
    creds, _, tenant = profile.get_raw_token()

    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    content = {
        'grant_type': 'access_token',
        'service': params['service'],
        'tenant': tenant,
        'access_token': creds[1]
    }

    response = requests.post(authhost, urlencode(content), headers=headers,
                             verify=(not should_disable_connection_verify()))

    if response.status_code not in [200]:
        raise CLIError("Access to registry '{}' was denied. Response code: {}.".format(
            login_server, response.status_code))

    refresh_token = loads(response.content.decode("utf-8"))["refresh_token"]
    if only_refresh_token:
        return refresh_token

    authhost = urlunparse((authurl[0], authurl[1], '/oauth2/token', '', '', ''))

    if repository:
        scope = 'repository:{}:{}'.format(repository, permission)
    elif artifact_repository:
        scope = 'artifact-repository:{}:{}'.format(artifact_repository, permission)
    else:
        # catalog only has * as permission, even for a read operation
        scope = 'registry:catalog:*'

    content = {
        'grant_type': 'refresh_token',
        'service': login_server,
        'scope': scope,
        'refresh_token': refresh_token
    }
    response = requests.post(authhost, urlencode(content), headers=headers,
                             verify=(not should_disable_connection_verify()))

    if response.status_code not in [200]:
        raise CLIError("Access to registry '{}' was denied. Response code: {}.".format(
            login_server, response.status_code))

    return loads(response.content.decode("utf-8"))["access_token"]
Exemplo n.º 21
0
def _get_credentials(
        cmd,  # pylint: disable=too-many-statements
        registry_name,
        tenant_suffix,
        username,
        password,
        only_refresh_token,
        repository=None,
        artifact_repository=None,
        permission=None):
    """Try to get AAD authorization tokens or admin user credentials.
    :param str registry_name: The name of container registry
    :param str tenant_suffix: The registry login server tenant suffix
    :param str username: The username used to log into the container registry
    :param str password: The password used to log into the container registry
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str artifact_repository: Artifact repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """
    # Raise an error if password is specified but username isn't
    if not username and password:
        raise CLIError(
            'Please also specify username if password is specified.')

    cli_ctx = cmd.cli_ctx
    resource_not_found, registry = None, None
    try:
        registry, resource_group_name = get_registry_by_name(
            cli_ctx, registry_name)
        login_server = registry.login_server
        if tenant_suffix:
            logger.warning(
                "Obtained registry login server '%s' from service. The specified suffix '%s' is ignored.",
                login_server, tenant_suffix)
    except (ResourceNotFound, CLIError) as e:
        resource_not_found = str(e)
        logger.debug("Could not get registry from service. Exception: %s",
                     resource_not_found)
        if not isinstance(e, ResourceNotFound
                          ) and _AZ_LOGIN_MESSAGE not in resource_not_found:
            raise
        # Try to use the pre-defined login server suffix to construct login server from registry name.
        login_server_suffix = get_login_server_suffix(cli_ctx)
        if not login_server_suffix:
            raise
        login_server = '{}{}{}'.format(
            registry_name,
            '-{}'.format(tenant_suffix) if tenant_suffix else '',
            login_server_suffix).lower()

    # Validate the login server is reachable
    challenge = 'https://' + login_server + '/v2/'
    try:
        requests.get(challenge,
                     verify=(not should_disable_connection_verify()))
    except RequestException as e:
        logger.debug(
            "Could not connect to registry login server. Exception: %s",
            str(e))
        if resource_not_found:
            logger.warning(
                "%s\nUsing '%s' as the default registry login server.",
                resource_not_found, login_server)
        raise CLIError("Could not connect to the registry login server '{}'. ".
                       format(login_server) +
                       "Please verify that the registry exists and " +
                       "the URL '{}' is reachable from your environment.".
                       format(challenge))

    # 1. if username was specified, verify that password was also specified
    if username:
        if not password:
            try:
                password = prompt_pass(msg='Password: '******'Please specify both username and password in non-interactive mode.'
                )

        return login_server, username, password

    # 2. if we don't yet have credentials, attempt to get a refresh token
    if not registry or registry.sku.name in get_managed_sku(cmd):
        try:
            return login_server, EMPTY_GUID, _get_aad_token(
                cli_ctx, login_server, only_refresh_token, repository,
                artifact_repository, permission)
        except CLIError as e:
            logger.warning("%s: %s", AAD_TOKEN_BASE_ERROR_MESSAGE, str(e))

    # 3. if we still don't have credentials, attempt to get the admin credentials (if enabled)
    if registry:
        if registry.admin_user_enabled:
            try:
                cred = cf_acr_registries(cli_ctx).list_credentials(
                    resource_group_name, registry_name)
                return login_server, cred.username, cred.passwords[0].value
            except CLIError as e:
                logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE, str(e))
        else:
            logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE,
                           "Admin user is disabled.")
    else:
        logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE,
                       resource_not_found)

    # 4. if we still don't have credentials, prompt the user
    try:
        username = prompt('Username: '******'Password: '******'Unable to authenticate using AAD or admin login credentials. ' +
            'Please specify both username and password in non-interactive mode.'
        )

    return login_server, None, None
Exemplo n.º 22
0
def request_data_from_registry(http_method,
                               login_server,
                               path,
                               username,
                               password,
                               result_index=None,
                               json_payload=None,
                               file_payload=None,
                               params=None,
                               manifest_headers=False,
                               raw=False,
                               retry_times=3,
                               retry_interval=5,
                               timeout=300):
    if http_method not in ALLOWED_HTTP_METHOD:
        raise ValueError("Allowed http method: {}".format(ALLOWED_HTTP_METHOD))

    if json_payload and file_payload:
        raise ValueError("One of json_payload and file_payload can be specified.")

    if http_method in ['get', 'delete'] and (json_payload or file_payload):
        raise ValueError("Empty payload is required for http method: {}".format(http_method))

    if http_method in ['patch', 'put'] and not (json_payload or file_payload):
        raise ValueError("Non-empty payload is required for http method: {}".format(http_method))

    url = 'https://{}{}'.format(login_server, path)

    if manifest_headers:
        headers = get_manifest_authorization_header(username, password)
    else:
        headers = get_authorization_header(username, password)

    for i in range(0, retry_times):
        errorMessage = None
        try:
            if file_payload:
                with open(file_payload, 'rb') as data_payload:
                    response = requests.request(
                        method=http_method,
                        url=url,
                        headers=headers,
                        params=params,
                        data=data_payload,
                        timeout=timeout,
                        verify=(not should_disable_connection_verify())
                    )
            else:
                response = requests.request(
                    method=http_method,
                    url=url,
                    headers=headers,
                    params=params,
                    json=json_payload,
                    timeout=timeout,
                    verify=(not should_disable_connection_verify())
                )

            log_registry_response(response)

            if manifest_headers and raw and response.status_code == 200:
                return response.content.decode('utf-8'), None
            if response.status_code == 200:
                result = response.json()[result_index] if result_index else response.json()
                next_link = response.headers['link'] if 'link' in response.headers else None
                return result, next_link
            if response.status_code == 201 or response.status_code == 202:
                result = None
                try:
                    result = response.json()[result_index] if result_index else response.json()
                except ValueError as e:
                    logger.debug('Response is empty or is not a valid json. Exception: %s', str(e))
                return result, None
            if response.status_code == 204:
                return None, None
            if response.status_code == 401:
                raise RegistryException(
                    parse_error_message('Authentication required.', response),
                    response.status_code)
            if response.status_code == 404:
                raise RegistryException(
                    parse_error_message('The requested data does not exist.', response),
                    response.status_code)
            if response.status_code == 405:
                raise RegistryException(
                    parse_error_message('This operation is not supported.', response),
                    response.status_code)
            if response.status_code == 409:
                raise RegistryException(
                    parse_error_message('Failed to request data due to a conflict.', response),
                    response.status_code)
            raise Exception(parse_error_message('Could not {} the requested data.'.format(http_method), response))
        except CLIError:
            raise
        except Exception as e:  # pylint: disable=broad-except
            errorMessage = str(e)
            logger.debug('Retrying %s with exception %s', i + 1, errorMessage)
            time.sleep(retry_interval)

    raise CLIError(errorMessage)
Exemplo n.º 23
0
def _get_token_with_username_and_password(login_server,
                                          username,
                                          password,
                                          repository=None,
                                          artifact_repository=None,
                                          permission=None,
                                          is_login_context=False,
                                          is_diagnostics_context=False):
    """Decides and obtains credentials for a registry using username and password.
       To be used for scoped access credentials.
    :param str login_server: The registry login server URL to log in to
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str artifact_repository: Artifact repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """

    if is_login_context:
        return username, password

    token_params = _handle_challenge_phase(
        login_server, repository, artifact_repository, permission, False, is_diagnostics_context
    )

    from ._errors import ErrorClass
    if isinstance(token_params, ErrorClass):
        if is_diagnostics_context:
            return token_params
        raise CLIError(token_params.get_error_message())

    if ALLOWS_BASIC_AUTH in token_params:
        return username, password

    if repository:
        scope = 'repository:{}:{}'.format(repository, permission)
    elif artifact_repository:
        scope = 'artifact-repository:{}:{}'.format(artifact_repository, permission)
    else:
        # catalog only has * as permission, even for a read operation
        scope = 'registry:catalog:*'

    authurl = urlparse(token_params['realm'])
    authhost = urlunparse((authurl[0], authurl[1], '/oauth2/token', '', '', ''))
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    content = {
        'service': token_params['service'],
        'grant_type': 'password',
        'username': username,
        'password': password,
        'scope': scope
    }

    response = requests.post(authhost, urlencode(content), headers=headers,
                             verify=(not should_disable_connection_verify()))

    if response.status_code != 200:
        from ._errors import CONNECTIVITY_ACCESS_TOKEN_ERROR
        if is_diagnostics_context:
            return CONNECTIVITY_ACCESS_TOKEN_ERROR.format_error_message(login_server, response.status_code)
        raise CLIError(CONNECTIVITY_ACCESS_TOKEN_ERROR.format_error_message(login_server, response.status_code)
                       .get_error_message())

    access_token = loads(response.content.decode("utf-8"))["access_token"]

    return EMPTY_GUID, access_token
Exemplo n.º 24
0
def _get_aad_token(cli_ctx,
                   login_server,
                   only_refresh_token,
                   repository=None,
                   artifact_repository=None,
                   permission=None,
                   is_diagnostics_context=False):
    """Obtains refresh and access tokens for an AAD-enabled registry.
    :param str login_server: The registry login server URL to log in to
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str artifact_repository: Artifact repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """
    if repository and artifact_repository:
        raise ValueError(
            "Only one of repository and artifact_repository can be provided.")

    if (repository or
            artifact_repository) and permission not in ACCESS_TOKEN_PERMISSION:
        raise ValueError(
            "Permission is required for a repository or artifact_repository. Allowed access token permission: {}"
            .format(ACCESS_TOKEN_PERMISSION))

    login_server = login_server.rstrip('/')

    challenge = requests.get('https://' + login_server + '/v2/',
                             verify=(not should_disable_connection_verify()))
    if challenge.status_code not in [
            401
    ] or 'WWW-Authenticate' not in challenge.headers:
        from ._errors import CONNECTIVITY_CHALLENGE_ERROR
        if is_diagnostics_context:
            return CONNECTIVITY_CHALLENGE_ERROR.format_error_message(
                login_server)
        raise CLIError(
            CONNECTIVITY_CHALLENGE_ERROR.format_error_message(
                login_server).get_error_message())

    authenticate = challenge.headers['WWW-Authenticate']

    tokens = authenticate.split(' ', 2)
    if len(tokens) < 2 or tokens[0].lower() != 'bearer':
        from ._errors import CONNECTIVITY_AAD_LOGIN_ERROR
        if is_diagnostics_context:
            return CONNECTIVITY_AAD_LOGIN_ERROR.format_error_message(
                login_server)
        raise CLIError(
            CONNECTIVITY_AAD_LOGIN_ERROR.format_error_message(
                login_server).get_error_message())

    token_params = {
        y[0]: y[1].strip('"')
        for y in (x.strip().split('=', 2) for x in tokens[1].split(','))
    }
    if 'realm' not in token_params or 'service' not in token_params:
        from ._errors import CONNECTIVITY_AAD_LOGIN_ERROR
        if is_diagnostics_context:
            return CONNECTIVITY_AAD_LOGIN_ERROR.format_error_message(
                login_server)
        raise CLIError(
            CONNECTIVITY_AAD_LOGIN_ERROR.format_error_message(
                login_server).get_error_message())

    return _get_aad_token_after_challenge(cli_ctx, token_params, login_server,
                                          only_refresh_token, repository,
                                          artifact_repository, permission,
                                          is_diagnostics_context)
Exemplo n.º 25
0
def _handle_challenge_phase(login_server,
                            repository,
                            artifact_repository,
                            permission,
                            is_aad_token=True,
                            is_diagnostics_context=False):

    if repository and artifact_repository:
        raise ValueError(
            "Only one of repository and artifact_repository can be provided.")

    repo_permissions = {
        permission.value
        for permission in RepoAccessTokenPermission
    }
    if repository and permission not in repo_permissions:
        raise ValueError(
            "Permission is required for a repository. Allowed access token permission: {}"
            .format(repo_permissions))

    helm_permissions = {
        permission.value
        for permission in HelmAccessTokenPermission
    }
    if artifact_repository and permission not in helm_permissions:
        raise ValueError(
            "Permission is required for an artifact_repository. Allowed access token permission: {}"
            .format(helm_permissions))

    login_server = login_server.rstrip('/')

    challenge = requests.get('https://' + login_server + '/v2/',
                             verify=(not should_disable_connection_verify()))
    if challenge.status_code != 401 or 'WWW-Authenticate' not in challenge.headers:
        from ._errors import CONNECTIVITY_CHALLENGE_ERROR
        if is_diagnostics_context:
            return CONNECTIVITY_CHALLENGE_ERROR.format_error_message(
                login_server)
        raise CLIError(
            CONNECTIVITY_CHALLENGE_ERROR.format_error_message(
                login_server).get_error_message())

    authenticate = challenge.headers['WWW-Authenticate']

    tokens = authenticate.split(' ', 2)

    if not is_aad_token and tokens[0].lower() == 'basic':
        return {ALLOWS_BASIC_AUTH: True}

    token_params = {y[0]: y[1].strip('"') for y in (x.strip().split('=', 2) for x in tokens[1].split(','))} \
        if len(tokens) >= 2 and tokens[0].lower() == 'bearer' else None

    if not token_params or 'realm' not in token_params or 'service' not in token_params:
        from ._errors import CONNECTIVITY_AAD_LOGIN_ERROR, CONNECTIVITY_WWW_AUTHENTICATE_ERROR
        error = CONNECTIVITY_AAD_LOGIN_ERROR if is_aad_token else CONNECTIVITY_WWW_AUTHENTICATE_ERROR

        if is_diagnostics_context:
            return error.format_error_message(login_server)
        raise CLIError(
            error.format_error_message(login_server).get_error_message())

    return token_params
Exemplo n.º 26
0
def github_release_version_exists(cli_ctx, version, repo, org='microsoft'):
    import requests

    version_url = 'https://api.github.com/repos/{}/{}/releases/tags/{}'.format(org, repo, version)
    version_res = requests.get(version_url, verify=not should_disable_connection_verify())
    return version_res.status_code < 400
Exemplo n.º 27
0
def _obtain_data_from_registry(login_server,
                               path,
                               username,
                               password,
                               result_index,
                               retry_times=3,
                               retry_interval=5,
                               top=None,
                               orderby=None):
    resultList = []
    executeNextHttpCall = True

    url = 'https://{}{}'.format(login_server, path)
    headers = _get_authorization_header(username, password)
    params = {'n': DEFAULT_PAGINATION, 'orderby': orderby}

    while executeNextHttpCall:
        executeNextHttpCall = False
        for i in range(0, retry_times):
            errorMessage = None
            try:
                # Override the default page size if top is provided
                if top is not None:
                    params[
                        'n'] = DEFAULT_PAGINATION if top > DEFAULT_PAGINATION else top
                    top -= params['n']

                response = requests.get(
                    url=url,
                    headers=headers,
                    params=params,
                    verify=(not should_disable_connection_verify()))
                log_registry_response(response)

                if response.status_code == 200:
                    result = response.json()[result_index]
                    if result:
                        resultList += result
                    if top is not None and top <= 0:
                        break
                    if 'link' in response.headers and response.headers['link']:
                        linkHeader = response.headers['link']
                        # The registry is telling us there's more items in the list,
                        # and another call is needed. The link header looks something
                        # like `Link: </v2/_catalog?last=hello-world&n=1>; rel="next"`
                        # we should follow the next path indicated in the link header
                        path = linkHeader[(linkHeader.index('<') +
                                           1):linkHeader.index('>')]
                        tokens = path.split('?', 2)
                        params = {
                            y[0]: unquote(y[1])
                            for y in (x.split('=', 2)
                                      for x in tokens[1].split('&'))
                        }
                        executeNextHttpCall = True
                    break
                elif response.status_code == 401:
                    raise CLIError(
                        _parse_error_message('Authentication required.',
                                             response))
                elif response.status_code == 404:
                    raise CLIError(
                        _parse_error_message(
                            'The requested data does not exist.', response))
                else:
                    raise Exception(
                        _parse_error_message(
                            'Could not get the requested data.', response))
            except CLIError:
                raise
            except Exception as e:  # pylint: disable=broad-except
                errorMessage = str(e)
                logger.debug('Retrying %s with exception %s', i + 1,
                             errorMessage)
                time.sleep(retry_interval)
        if errorMessage:
            raise CLIError(errorMessage)

    return resultList
Exemplo n.º 28
0
def request_data_from_registry(http_method,
                               login_server,
                               path,
                               username,
                               password,
                               result_index=None,
                               json_payload=None,
                               data_payload=None,
                               params=None,
                               retry_times=3,
                               retry_interval=5):
    if http_method not in ALLOWED_HTTP_METHOD:
        raise ValueError("Allowed http method: {}".format(ALLOWED_HTTP_METHOD))

    if json_payload and data_payload:
        raise ValueError(
            "One of json_payload and data_payload can be specified.")

    if http_method in ['get', 'delete'] and (json_payload or data_payload):
        raise ValueError(
            "Empty payload is required for http method: {}".format(
                http_method))

    if http_method in ['patch', 'put'] and not (json_payload or data_payload):
        raise ValueError(
            "Non-empty payload is required for http method: {}".format(
                http_method))

    url = 'https://{}{}'.format(login_server, path)
    headers = get_authorization_header(username, password)

    for i in range(0, retry_times):
        errorMessage = None
        try:
            response = requests.request(
                method=http_method,
                url=url,
                headers=headers,
                params=params,
                json=json_payload,
                data=data_payload,
                verify=(not should_disable_connection_verify()))
            log_registry_response(response)

            if response.status_code == 200:
                result = response.json(
                )[result_index] if result_index else response.json()
                next_link = response.headers[
                    'link'] if 'link' in response.headers else None
                return result, next_link
            elif response.status_code == 201 or response.status_code == 202:
                result = None
                try:
                    result = response.json(
                    )[result_index] if result_index else response.json()
                except ValueError:
                    logger.debug('Response is empty or is not a valid json.')
                return result, None
            elif response.status_code == 204:
                return None, None
            elif response.status_code == 401:
                raise CLIError(
                    parse_error_message('Authentication required.', response))
            elif response.status_code == 404:
                raise CLIError(
                    parse_error_message('The requested data does not exist.',
                                        response))
            else:
                raise Exception(
                    parse_error_message(
                        'Could not {} the requested data.'.format(http_method),
                        response))
        except CLIError:
            raise
        except Exception as e:  # pylint: disable=broad-except
            errorMessage = str(e)
            logger.debug('Retrying %s with exception %s', i + 1, errorMessage)
            time.sleep(retry_interval)

    raise CLIError(errorMessage)
Exemplo n.º 29
0
def _get_credentials(cmd,  # pylint: disable=too-many-statements
                     registry_name,
                     tenant_suffix,
                     username,
                     password,
                     only_refresh_token,
                     repository=None,
                     artifact_repository=None,
                     permission=None,
                     is_login_context=False):
    """Try to get AAD authorization tokens or admin user credentials.
    :param str registry_name: The name of container registry
    :param str tenant_suffix: The registry login server tenant suffix
    :param str username: The username used to log into the container registry
    :param str password: The password used to log into the container registry
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str artifact_repository: Artifact repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """
    # Raise an error if password is specified but username isn't
    if not username and password:
        raise CLIError('Please also specify username if password is specified.')

    cli_ctx = cmd.cli_ctx
    resource_not_found, registry = None, None
    try:
        registry, resource_group_name = get_registry_by_name(cli_ctx, registry_name)
        login_server = registry.login_server
        if tenant_suffix:
            logger.warning(
                "Obtained registry login server '%s' from service. The specified suffix '%s' is ignored.",
                login_server, tenant_suffix)
    except (ResourceNotFound, CLIError) as e:
        resource_not_found = str(e)
        logger.debug("Could not get registry from service. Exception: %s", resource_not_found)
        if not isinstance(e, ResourceNotFound) and _AZ_LOGIN_MESSAGE not in resource_not_found:
            raise
        # Try to use the pre-defined login server suffix to construct login server from registry name.
        login_server_suffix = get_login_server_suffix(cli_ctx)
        if not login_server_suffix:
            raise
        login_server = '{}{}{}'.format(
            registry_name, '-{}'.format(tenant_suffix) if tenant_suffix else '', login_server_suffix).lower()

    # Validate the login server is reachable
    url = 'https://' + login_server + '/v2/'
    try:
        challenge = requests.get(url, verify=(not should_disable_connection_verify()))
        if challenge.status_code == 403:
            raise CLIError("Looks like you don't have access to registry '{}'. "
                           "To see configured firewall rules, run 'az acr show --query networkRuleSet --name {}'. "
                           "To see if public network access is enabled, run 'az acr show --query publicNetworkAccess'."
                           "Please refer to https://aka.ms/acr/errors#connectivity_forbidden_error for more information."  # pylint: disable=line-too-long
                           .format(login_server, registry_name))
    except RequestException as e:
        logger.debug("Could not connect to registry login server. Exception: %s", str(e))
        if resource_not_found:
            logger.warning("%s\nUsing '%s' as the default registry login server.", resource_not_found, login_server)

        from .check_health import ACR_CHECK_HEALTH_MSG
        check_health_msg = ACR_CHECK_HEALTH_MSG.format(registry_name)
        raise CLIError("Could not connect to the registry login server '{}'. "
                       "Please verify that the registry exists and the URL '{}' is reachable from your environment.\n{}"
                       .format(login_server, url, check_health_msg))

    # 1. if username was specified, verify that password was also specified
    if username:
        if not password:
            try:
                password = prompt_pass(msg='Password: '******'Please specify both username and password in non-interactive mode.')

        username, password = _get_token_with_username_and_password(
            login_server, username, password, repository, artifact_repository, permission, is_login_context
        )
        return login_server, username, password

    # 2. if we don't yet have credentials, attempt to get a refresh token
    if not registry or registry.sku.name in get_managed_sku(cmd):
        logger.info("Attempting to retrieve AAD refresh token...")
        try:
            return login_server, EMPTY_GUID, _get_aad_token(
                cli_ctx, login_server, only_refresh_token, repository, artifact_repository, permission)
        except CLIError as e:
            logger.warning("%s: %s", AAD_TOKEN_BASE_ERROR_MESSAGE, str(e))

    # 3. if we still don't have credentials, attempt to get the admin credentials (if enabled)
    if registry:
        if registry.admin_user_enabled:
            logger.info("Attempting with admin credentials...")
            try:
                cred = cf_acr_registries(cli_ctx).list_credentials(resource_group_name, registry_name)
                return login_server, cred.username, cred.passwords[0].value
            except CLIError as e:
                logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE, str(e))
        else:
            logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE, "Admin user is disabled.")
    else:
        logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE, resource_not_found)

    # 4. if we still don't have credentials, prompt the user
    try:
        username = prompt('Username: '******'Password: '******'Unable to authenticate using AAD or admin login credentials. ' +
            'Please specify both username and password in non-interactive mode.')

    return login_server, None, None
Exemplo n.º 30
0
def _get_credentials(cmd,  # pylint: disable=too-many-statements
                     registry_name,
                     tenant_suffix,
                     username,
                     password,
                     only_refresh_token,
                     repository=None,
                     artifact_repository=None,
                     permission=None):
    """Try to get AAD authorization tokens or admin user credentials.
    :param str registry_name: The name of container registry
    :param str tenant_suffix: The registry login server tenant suffix
    :param str username: The username used to log into the container registry
    :param str password: The password used to log into the container registry
    :param bool only_refresh_token: Whether to ask for only refresh token, or for both refresh and access tokens
    :param str repository: Repository for which the access token is requested
    :param str artifact_repository: Artifact repository for which the access token is requested
    :param str permission: The requested permission on the repository, '*' or 'pull'
    """
    # Raise an error if password is specified but username isn't
    if not username and password:
        raise CLIError('Please also specify username if password is specified.')

    cli_ctx = cmd.cli_ctx
    resource_not_found, registry = None, None
    try:
        registry, resource_group_name = get_registry_by_name(cli_ctx, registry_name)
        login_server = registry.login_server
        if tenant_suffix:
            logger.warning(
                "Obtained registry login server '%s' from service. The specified suffix '%s' is ignored.",
                login_server, tenant_suffix)
    except (ResourceNotFound, CLIError) as e:
        resource_not_found = str(e)
        logger.debug("Could not get registry from service. Exception: %s", resource_not_found)
        if not isinstance(e, ResourceNotFound) and _AZ_LOGIN_MESSAGE not in resource_not_found:
            raise
        # Try to use the pre-defined login server suffix to construct login server from registry name.
        login_server_suffix = get_login_server_suffix(cli_ctx)
        if not login_server_suffix:
            raise
        login_server = '{}{}{}'.format(
            registry_name, '-{}'.format(tenant_suffix) if tenant_suffix else '', login_server_suffix).lower()

    # Validate the login server is reachable
    url = 'https://' + login_server + '/v2/'
    try:
        challenge = requests.get(url, verify=(not should_disable_connection_verify()))
        if challenge.status_code in [403]:
            raise CLIError("Looks like you don't have access to registry '{}'. "
                           "Are firewalls and virtual networks enabled?".format(login_server))
    except RequestException as e:
        logger.debug("Could not connect to registry login server. Exception: %s", str(e))
        if resource_not_found:
            logger.warning("%s\nUsing '%s' as the default registry login server.", resource_not_found, login_server)
        raise CLIError("Could not connect to the registry login server '{}'. ".format(login_server) +
                       "Please verify that the registry exists and " +
                       "the URL '{}' is reachable from your environment.".format(url))

    # 1. if username was specified, verify that password was also specified
    if username:
        if not password:
            try:
                password = prompt_pass(msg='Password: '******'Please specify both username and password in non-interactive mode.')

        return login_server, username, password

    # 2. if we don't yet have credentials, attempt to get a refresh token
    if not registry or registry.sku.name in get_managed_sku(cmd):
        try:
            return login_server, EMPTY_GUID, _get_aad_token(
                cli_ctx, login_server, only_refresh_token, repository, artifact_repository, permission)
        except CLIError as e:
            logger.warning("%s: %s", AAD_TOKEN_BASE_ERROR_MESSAGE, str(e))

    # 3. if we still don't have credentials, attempt to get the admin credentials (if enabled)
    if registry:
        if registry.admin_user_enabled:
            try:
                cred = cf_acr_registries(cli_ctx).list_credentials(resource_group_name, registry_name)
                return login_server, cred.username, cred.passwords[0].value
            except CLIError as e:
                logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE, str(e))
        else:
            logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE, "Admin user is disabled.")
    else:
        logger.warning("%s: %s", ADMIN_USER_BASE_ERROR_MESSAGE, resource_not_found)

    # 4. if we still don't have credentials, prompt the user
    try:
        username = prompt('Username: '******'Password: '******'Unable to authenticate using AAD or admin login credentials. ' +
            'Please specify both username and password in non-interactive mode.')

    return login_server, None, None
Exemplo n.º 31
0
    def test_send_raw_requests(self, send_mock, get_raw_token_mock):
        if 'AZURE_HTTP_USER_AGENT' in os.environ:
            del os.environ[
                'AZURE_HTTP_USER_AGENT']  # Clear env var possibly added by DevOps

        return_val = mock.MagicMock()
        return_val.is_ok = True
        send_mock.return_value = return_val
        get_raw_token_mock.return_value = ("Bearer", "eyJ0eXAiOiJKV1",
                                           None), None, None

        cli_ctx = DummyCli()
        cli_ctx.data = {'command': 'rest', 'safe_params': ['method', 'uri']}
        test_arm_active_directory_resource_id = 'https://management.core.windows.net/'
        test_arm_endpoint = 'https://management.azure.com/'
        subscription_id = '00000001-0000-0000-0000-000000000000'
        arm_resource_id = '/subscriptions/{}/resourcegroups/02?api-version=2019-07-01'.format(
            subscription_id)
        full_arm_rest_url = test_arm_endpoint.rstrip('/') + arm_resource_id
        test_body = '{"b1": "v1"}'

        expected_header = {
            'User-Agent': get_az_user_agent(),
            'Accept-Encoding': 'gzip, deflate',
            'Accept': '*/*',
            'Connection': 'keep-alive',
            'Content-Type': 'application/json',
            'CommandName': 'rest',
            'ParameterSetName': 'method uri',
            'Content-Length': '12'
        }
        expected_header_with_auth = expected_header.copy()
        expected_header_with_auth['Authorization'] = 'Bearer eyJ0eXAiOiJKV1'

        # Test basic usage
        # Mock Put Blob https://docs.microsoft.com/en-us/rest/api/storageservices/put-blob
        # Authenticate with service SAS https://docs.microsoft.com/en-us/rest/api/storageservices/create-service-sas
        sas_token = ['sv=2019-02-02', '{"srt": "s"}', "{'ss': 'bf'}"]
        send_raw_request(
            cli_ctx,
            'PUT',
            'https://myaccount.blob.core.windows.net/mycontainer/myblob?timeout=30',
            uri_parameters=sas_token,
            body=test_body,
            generated_client_request_id_name=None)

        get_raw_token_mock.assert_not_called()
        request = send_mock.call_args.args[1]
        self.assertEqual(request.method, 'PUT')
        self.assertEqual(
            request.url,
            'https://myaccount.blob.core.windows.net/mycontainer/myblob?timeout=30&sv=2019-02-02&srt=s&ss=bf'
        )
        self.assertEqual(request.body, '{"b1": "v1"}')
        # Verify no Authorization header
        self.assertDictEqual(dict(request.headers), expected_header)
        self.assertEqual(send_mock.call_args.kwargs["verify"],
                         not should_disable_connection_verify())

        # Test Authorization header is skipped
        send_raw_request(cli_ctx,
                         'GET',
                         full_arm_rest_url,
                         body=test_body,
                         skip_authorization_header=True,
                         generated_client_request_id_name=None)

        get_raw_token_mock.assert_not_called()
        request = send_mock.call_args.args[1]
        self.assertDictEqual(dict(request.headers), expected_header)

        # Test Authorization header is already provided
        send_raw_request(cli_ctx,
                         'GET',
                         full_arm_rest_url,
                         body=test_body,
                         headers={'Authorization=Basic ABCDE'},
                         generated_client_request_id_name=None)

        get_raw_token_mock.assert_not_called()
        request = send_mock.call_args.args[1]
        self.assertDictEqual(dict(request.headers), {
            **expected_header, 'Authorization': 'Basic ABCDE'
        })

        # Test Authorization header is auto appended
        send_raw_request(cli_ctx,
                         'GET',
                         full_arm_rest_url,
                         body=test_body,
                         generated_client_request_id_name=None)

        get_raw_token_mock.assert_called_with(
            mock.ANY,
            test_arm_active_directory_resource_id,
            subscription=subscription_id)
        request = send_mock.call_args.args[1]
        self.assertDictEqual(dict(request.headers), expected_header_with_auth)

        # Test ARM Subscriptions - List
        # https://docs.microsoft.com/en-us/rest/api/resources/subscriptions/list
        # /subscriptions?api-version=2020-01-01
        send_raw_request(cli_ctx,
                         'GET',
                         '/subscriptions?api-version=2020-01-01',
                         body=test_body,
                         generated_client_request_id_name=None)

        get_raw_token_mock.assert_called_with(
            mock.ANY, test_arm_active_directory_resource_id)
        request = send_mock.call_args.args[1]
        self.assertEqual(
            request.url,
            test_arm_endpoint.rstrip('/') +
            '/subscriptions?api-version=2020-01-01')
        self.assertDictEqual(dict(request.headers), expected_header_with_auth)

        # Test ARM Tenants - List
        # https://docs.microsoft.com/en-us/rest/api/resources/tenants/list
        # /tenants?api-version=2020-01-01
        send_raw_request(cli_ctx,
                         'GET',
                         '/tenants?api-version=2020-01-01',
                         body=test_body,
                         generated_client_request_id_name=None)

        get_raw_token_mock.assert_called_with(
            mock.ANY, test_arm_active_directory_resource_id)
        request = send_mock.call_args.args[1]
        self.assertEqual(
            request.url,
            test_arm_endpoint.rstrip('/') + '/tenants?api-version=2020-01-01')
        self.assertDictEqual(dict(request.headers), expected_header_with_auth)

        # Test ARM resource ID
        # /subscriptions/00000001-0000-0000-0000-000000000000/resourcegroups/02?api-version=2019-07-01
        send_raw_request(cli_ctx,
                         'GET',
                         arm_resource_id,
                         body=test_body,
                         generated_client_request_id_name=None)

        get_raw_token_mock.assert_called_with(
            mock.ANY,
            test_arm_active_directory_resource_id,
            subscription=subscription_id)
        request = send_mock.call_args.args[1]
        self.assertEqual(request.url, full_arm_rest_url)
        self.assertDictEqual(dict(request.headers), expected_header_with_auth)

        # Test full ARM URL
        # https://management.azure.com/subscriptions/00000001-0000-0000-0000-000000000000/resourcegroups/02?api-version=2019-07-01
        send_raw_request(cli_ctx, 'GET', full_arm_rest_url)

        get_raw_token_mock.assert_called_with(
            mock.ANY,
            test_arm_active_directory_resource_id,
            subscription=subscription_id)
        request = send_mock.call_args.args[1]
        self.assertEqual(request.url, full_arm_rest_url)

        # Test full ARM URL with port
        # https://management.azure.com:443/subscriptions/00000001-0000-0000-0000-000000000000/resourcegroups/02?api-version=2019-07-01
        test_arm_endpoint_with_port = 'https://management.azure.com:443/'
        full_arm_rest_url_with_port = test_arm_endpoint_with_port.rstrip(
            '/') + arm_resource_id
        send_raw_request(cli_ctx, 'GET', full_arm_rest_url_with_port)

        get_raw_token_mock.assert_called_with(
            mock.ANY,
            test_arm_active_directory_resource_id,
            subscription=subscription_id)
        request = send_mock.call_args.args[1]
        self.assertEqual(
            request.url,
            'https://management.azure.com:443/subscriptions/00000001-0000-0000-0000-000000000000/resourcegroups/02?api-version=2019-07-01'
        )

        # Test non-ARM APIs

        # Test AD Graph API https://graph.windows.net/
        url = 'https://graph.windows.net/00000002-0000-0000-0000-000000000000/applications/00000003-0000-0000-0000-000000000000?api-version=1.6'
        send_raw_request(cli_ctx,
                         'PATCH',
                         url,
                         body=test_body,
                         generated_client_request_id_name=None)
        get_raw_token_mock.assert_called_with(mock.ANY,
                                              'https://graph.windows.net/')
        request = send_mock.call_args.args[1]
        self.assertEqual(request.method, 'PATCH')
        self.assertEqual(request.url, url)

        # Test MS Graph API https://graph.microsoft.com/beta/appRoleAssignments/01
        url = 'https://graph.microsoft.com/beta/appRoleAssignments/01'
        send_raw_request(cli_ctx,
                         'PATCH',
                         url,
                         body=test_body,
                         generated_client_request_id_name=None)
        get_raw_token_mock.assert_called_with(mock.ANY,
                                              'https://graph.microsoft.com/')
        request = send_mock.call_args.args[1]
        self.assertEqual(request.method, 'PATCH')
        self.assertEqual(request.url, url)

        # Test custom case-insensitive User-Agent
        with mock.patch.dict('os.environ',
                             {'AZURE_HTTP_USER_AGENT': "env-ua"}):
            send_raw_request(cli_ctx,
                             'GET',
                             full_arm_rest_url,
                             headers={'user-agent=ARG-UA'})

            get_raw_token_mock.assert_called_with(
                mock.ANY,
                test_arm_active_directory_resource_id,
                subscription=subscription_id)
            request = send_mock.call_args.args[1]
            self.assertEqual(request.headers['User-Agent'],
                             get_az_user_agent() + ' env-ua ARG-UA')
Exemplo n.º 32
0
def request_data_from_registry(http_method,
                               login_server,
                               path,
                               username,
                               password,
                               result_index=None,
                               json_payload=None,
                               file_payload=None,
                               params=None,
                               retry_times=3,
                               retry_interval=5):
    if http_method not in ALLOWED_HTTP_METHOD:
        raise ValueError("Allowed http method: {}".format(ALLOWED_HTTP_METHOD))

    if json_payload and file_payload:
        raise ValueError("One of json_payload and file_payload can be specified.")

    if http_method in ['get', 'delete'] and (json_payload or file_payload):
        raise ValueError("Empty payload is required for http method: {}".format(http_method))

    if http_method in ['patch', 'put'] and not (json_payload or file_payload):
        raise ValueError("Non-empty payload is required for http method: {}".format(http_method))

    url = 'https://{}{}'.format(login_server, path)
    headers = get_authorization_header(username, password)

    for i in range(0, retry_times):
        errorMessage = None
        try:
            if file_payload:
                with open(file_payload, 'rb') as data_payload:
                    response = requests.request(
                        method=http_method,
                        url=url,
                        headers=headers,
                        params=params,
                        data=data_payload,
                        verify=(not should_disable_connection_verify())
                    )
            else:
                response = requests.request(
                    method=http_method,
                    url=url,
                    headers=headers,
                    params=params,
                    json=json_payload,
                    verify=(not should_disable_connection_verify())
                )

            log_registry_response(response)

            if response.status_code == 200:
                result = response.json()[result_index] if result_index else response.json()
                next_link = response.headers['link'] if 'link' in response.headers else None
                return result, next_link
            elif response.status_code == 201 or response.status_code == 202:
                result = None
                try:
                    result = response.json()[result_index] if result_index else response.json()
                except ValueError as e:
                    logger.debug('Response is empty or is not a valid json. Exception: %s', str(e))
                return result, None
            elif response.status_code == 204:
                return None, None
            elif response.status_code == 401:
                raise RegistryException(
                    parse_error_message('Authentication required.', response),
                    response.status_code)
            elif response.status_code == 404:
                raise RegistryException(
                    parse_error_message('The requested data does not exist.', response),
                    response.status_code)
            elif response.status_code == 405:
                raise RegistryException(
                    parse_error_message('This operation is not supported.', response),
                    response.status_code)
            elif response.status_code == 409:
                raise RegistryException(
                    parse_error_message('Failed to request data due to a conflict.', response),
                    response.status_code)
            else:
                raise Exception(parse_error_message('Could not {} the requested data.'.format(http_method), response))
        except CLIError:
            raise
        except Exception as e:  # pylint: disable=broad-except
            errorMessage = str(e)
            logger.debug('Retrying %s with exception %s', i + 1, errorMessage)
            time.sleep(retry_interval)

    raise CLIError(errorMessage)