Authentication using zCompute APIs

Symp CLI commands can be run by authenticating, without requiring a token.

However, it is advisable to authenticate and then generate a token for use in any automated Symp CLI or API process. This will prevent possible connection reset by peer errors when rate limits are exceeded.

API Endpoints

The list of a cluster’s API endpoints is available in the UI.

To view the cluster’s API endpoints:

  1. In the UI, in the upper right corner near the username, click Help (the ? icon).

    The Help dropdown menu opens.

  2. In the dropdown menu, select API Endpoints.

    The API Endpoints dialog opens, listing the cluster’s API endpoints.

    help-about-dropdown api-endpoints-list

API Explorer

zCompute provides an interactive Swagger-based API Explorer.

API operations require authorization with a token.

Obtaining a token in the UI

To obtain a token in the UI:

  1. In the UI, in the upper right corner near the username, click Help (the ? icon), and in the dropdown menu, select API Explorer.

    The Open API Explorer dialog opens.

    api-explorer-open

  2. Click the Open API Explorer button.

    • A token is generated and automatically copied to the clipboard.

    • The zCompute Symp API Explorer opens, listing the zCompute APIs.

      api-explorer

Note

The token in the clipboard can be pasted and used in the API Explorer, or for running API operations using the CLI.

Running API operations in the API Explorer

  1. After Obtaining a token in the UI, click an API in the zCompute Symp API Explorer list.

    A Swagger UI opens, displaying the selected API’s REST methods.

  2. Scroll down the list to locate the desired operation to run.

  3. Click the selected REST method or its description, to expand the view to display its parameters.

    api-explorer-operation-run

  4. Click the Lock icon on the right, to submit the authorization token.

    Note

    Alternatively, click the Authorize button in the screen header.

    The Available Authorizations dialog opens.

    api-explorer-token

    1. In the token (apiKey) > Value field, paste the token from your clipboard.

    2. Click Authorize.

      Note

      After the token is authorized and while it is still valid, REST methods for all APIs can be run.

      It is not necessary to repeat this authorization step.

    3. Click Close to close the Available Authorizations dialog and return to the selected API REST method screen.

  5. Click Try it out.

  6. Enter parameters for the REST method, and click the Execute bar.

    The Responses pane expands, displaying:

    • The Curl command that was executed.

    • The Request URL.

    • The Server response, comprising the return code, response body and response headers.

    api-explorer-operation-response

Generating a token at a bash prompt

Since some commands are limited to the scope of a project, while others have the scope of an entire account (domain), it’s necessary to generate a token for the account (domain) scope, and a separate token for the project scope.

Using curl on the /api/v2/identity/auth endpoint, to generate authentication tokens involves two steps:

First, generating an account (domain) token, and then using the domain token to generate a project token.

  1. Generating a domain token:

    The /api/v2/identity/auth endpoint’s authentication token request data structure that defines the scope of an entire account (domain):

    {
      "auth": {
        "scope": {
          "domain": {
            "name": "<ACCOUNT_NAME>"
          }
        },
        "identity": {
          "password": {
            "user": {
              "domain": {
                "name": "<ACCOUNT_NAME>"
              },
              "password": "<PASSWORD>",
              "name": "<USER_NAME>"
            }
          },
          "methods": [
            "password"
          ]
        }
      }
    }
    
    1. The curl command at the bash prompt for generating an account (domain) authentication token:

      curl -D - -k -H "Content-Type: application/json" -X POST https://<cluster IP address>/api/v2/identity/auth -d \
      '{"auth": {"scope": {"domain": {"name": "<account>"}}, "identity": {"password": {"user": {"domain": {"name": "<account>"}, "password": "<password>", "name": "<user>"}}, "methods": ["password"]}}}'
      

      For example:

      curl -D - -k -H "Content-Type: application/json" -X POST https://10.11.12.13/api/v2/identity/auth -d \
      '{"auth": {"scope": {"domain": {"name": "acc1"}}, "identity": {"password": {"user": {"domain": {"name": "acc1"}, "password": "Mypass1!", "name": "user1"}}, "methods": ["password"]}}}'
      
    2. The domain token is returned in the x-subject-token header, and can be copy/pasted to assign it to an environment variable for later use.

      For example:

      export ZC_DOMAIN_TOKEN=MIISUgYJKoZIhvcNAQcCoIISQzCC...<2K+ character string>...ngpXjEqQtxlRGuCEIvo46sGMfedc=
      
  2. Using the previously generated domain token to generate a project token:

    The /api/v2/identity/auth endpoint’s authentication token request data structure that defines the scope as limited to a project, and the identity method as a token:

    {
      "auth": {
        "scope": {
          "project": {
            "domain": {
              "name": "<ACCOUNT_NAME>"
            },
            "name": "<PROJECT_NAME>"
          }
        },
        "identity": {
          "token": {
            "id": "'<DOMAIN_TOKEN>'"
          },
          "methods": [
            "token"
          ]
        }
      }
    }
    
    1. The curl command at the bash prompt for generating a project authentication token:

      curl -D - -k -H "Content-Type: application/json" -X POST https:<cluster IP address>/api/v2/identity/auth -d \
      '{"auth": {"scope": {"project": {"domain": {"name": "<account>"}, "name": "<project>"}}, "identity": {"token": {"id": "'<domain token string>'"}, "methods": ["token"]}}}'
      

      For example:

      curl -D - -k -H "Content-Type: application/json" -X POST https://10.11.12.13/api/v2/identity/auth -d \
      '{"auth": {"scope": {"project": {"domain": {"name": "acc1"}, "name": "vpcproj1"}}, "identity": {"token": {"id": "'$ZC_DOMAIN_TOKEN'"}, "methods": ["token"]}}}'
      
    2. The project token is returned in the x-subject-token header, and can be copy/pasted to assign it to an environment variable for later use.

      For example:

      export ZC_PROJECT_TOKEN=MIIScwYJKoZIhvcNAQcCoIISZDCC...<2K+ character string>...VpADxR3RDrScUbgSwMAaZ8zCSrow=
      
    3. To use curl to call a REST method on an endpoint, provide the token string in the X-Auth-Token header:

      curl -k -L -X <REST method> "https://<cluster IP address>/api-explorer/api/v2/<endpoint>/" -H "accept: application/json" -H "X-Auth-Token: <token string>"
      

      For example, to retrieve the project’s tags, use the GET method on the /api-explorer/api/v2/tags/ endpoint, and the project authentication token (assigned in the previous step to the environment variable $ZC_PROJECT_TOKEN). This example uses sed and awk to format the output:

      curl -k -L -X GET "https://10.11.12.13/api-explorer/api/v2/tags/" -H "accept: application/json" -H "X-Auth-Token: $ZC_PROJECT_TOKEN" | awk 'BEGIN{FS=",";OFS="\n"} FNR==1{$1=$1;print;exit}' | sed '/{/{x;p;x;}'
        % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                       Dload  Upload   Total   Spent    Left  Speed
      100   116  100   116    0     0  11756      0 --:--:-- --:--:-- --:--:-- 12888
      100  2302  100  2302    0     0  59137      0 --:--:-- --:--:-- --:--:--     0
      
      [{"description": "Tag2"
       "updated-at": "2023-10-31T08:44:01Z"
        "created-at": "2023-10-31T08:44:01Z"
        "value": ""
        "scope": "public"
        "project-id": "7ff34832b6754f7d91a918302af3e77f"
        "name": "vpctag2"}
      
        {"description": "Tag 1"
        "updated-at": "2023-10-31T08:10:49Z"
        "created-at": "2023-10-31T08:10:49Z"
        "value": ""
        "scope": "public"
        "project-id": "7ff34832b6754f7d91a918302af3e77f"
        "name": "vpctag1"}]
      

Generating a token in Symp CLI

  1. To generate an authentication token via the Symp CLI shell, use the Symp login command:

    login <user> <password> <domain> <project> -f value
    

    For example:

    login user1 Mypass1! acc1 vpcproj1 -f value
    
    MIISUgYJKoZIhvcNAQcCoIISQzCC...<2K+ character string>...ngpXjEqQtxlRGuCEIvo46sGMfedc=
    

    Alternatively, an example of a single command at the bash prompt to generate an authentication token and assign it to an environment variable:

    export ZC_PROJECT_TOKEN=`symp -k --url https://10.11.12.13 -d acc1 -u user1 --project vpcproj1 -p Mypass1! login user1 Mypass1! acc1 vpcproj1 -f value`
    
    echo $ZC_PROJECT_TOKEN
    
    MIISUgYJKoZIhvcNAQcCoIISQzCC...<2K+ character string>...ngpXjEqQtxlRGuCEIvo46sGMfedc=
    
  2. Using the token to sign on to Symp from the bash prompt:

    symp -k --url https://<cluster IP address> --project-token <token string>
    

    For example, generating the token (as in the previous step), and using it to sign on to Symp:

    export ZC_PROJECT_TOKEN=`symp -k --url https://10.11.12.13 -d acc1 -u user1 --project vpcproj1 -p Mypass1! login user1 Mypass1! acc1 vpcproj1 -f value`
    
    symp -k --url https://10.11.12.13 --project-token $ZC_PROJECT_TOKEN
    
    Starting new HTTPS connection (1): 10.16.145.114
    Connecting in insecure mode!
    Starting new HTTPS connection (1): 10.16.145.114
    Starting new HTTPS connection (1): 10.16.145.114
    
     d88888b  dP    dP 8888ba.88ba   888888ba  dP     dP   .88888.  888888ba  dP    dP
    88.     "' Y8.  .8P 88  `8b  `8b  88    `8b 88     88  d8'   `8b 88    `8b Y8.  .8P
    `Y88888b.  Y8aa8P  88   88   88 a88aaaa8P' 88aaaaa88a 88     88 88     88  Y8aa8P
          `8b    88    88   88   88  88        88     88  88     88 88     88    88
    d8'   .8P    88    88   88   88  88        88     88  Y8.   .8P 88     88    88
     Y88888P     dP    dP   dP   dP  dP        dP     dP   `8888P'  dP     dP    dP
    
    Tap <TAB> twice to get list of available commands.
    Type --help to get help with any command
    Symphony >
    

Python authentication example

When using a zCompute API, the user is required to pass a valid token in an X-Auth-Token HTTP header.

Users can generate a valid token by logging into the system using the identity/auth API.

The following Python script is an example of this process:

#!/usr/bin/python
from __future__ import print_function
import copy
import getpass
import os
import sys
import argparse
import requests
try:
    input = raw_input
except NameError:
    pass

IDENTITY_API_SUFFIX = "identity"
VPC_API_SUFFIX = "vpc"
DEFAULT_HEADERS = {
    "Accept": "application/json",
    "Content-Type": "application/json"
}
_debug = False


def _debug_msg(msg, *args, **kwargs):
    global _debug
    if _debug:
        print(msg.format(*args, **kwargs), file=sys.stderr)


def generate_totp_passcode(secret):
    """Generate TOTP passcode.
    :param bytes secret: A base32 encoded secret for the TOTP authentication
    :returns: totp passcode as bytes
    """
    try:
        import mintotp
    except:
        sys.exit("TOTP code generation library is not present. Please install mintotp using 'pip install mintotp'")
    return mintotp.totp(secret)


class ZComputeApi(object):

    def __init__(self, api_base_url, account_name, project_name, user, password,
                 insecure=False, totp_code=None, mfa_secret=None, debug=False):
        self._api_base_url = '{}/api/v2'.format(api_base_url)
        self._account_name = account_name
        self._project_name = project_name
        self._user = user
        self._password = password
        self._insecure = insecure
        self._token = None
        self._totp_code = totp_code
        self._mfa_secret = mfa_secret
        self._debug = debug

    def reset_api_params(self, token=None, project_name=None):
        if token:
            self._token = token
        if project_name:
            self._project_name = project_name

    def send_request(self, method='GET', api_path='', api_body=None, headers=None, raise_on_status=True):
        api_url = '{}/{}'.format(self._api_base_url, api_path)
        if headers:
            headers = copy.deepcopy(headers)
            headers.update(DEFAULT_HEADERS)
        else:
            headers = copy.deepcopy(DEFAULT_HEADERS)
        if self._token:
            headers['X-Auth-Token'] = self._token
        try:
            response = requests.request(
                method=method,
                url=api_url,
                headers=headers,
                json=api_body,
                verify=not self._insecure
            )
            if raise_on_status:
                response.raise_for_status()
        except Exception:
            _debug_msg("Failed sending {} {} reuquest".format(method, api_url))
            raise
        return response

    def send_api_request(self, method='GET', api_path='', api_body=None):
        return self.send_request(method=method, api_path=api_path, api_body=api_body).json()

    def _get_project_scope(self):
        return {"project": {"name": self._project_name, "domain": {"name": self._account_name}}}

    def _get_domain_scope(self):
        return {"domain": {"name": self._account_name}}

    def _get_password_auth(self):
        return {
            "methods": [
                "password"
            ],
            "password": {
                "user": {
                    "name": self._user,
                    "password": self._password,
                    "domain": {
                        "name": self._account_name
                    }
                }
            }
        }

    def _add_totp_code(self, identity_auth, totp_code):
        if identity_auth.get('methods'):
            identity_auth['methods'].append('totp')
        else:
            identity_auth['methods'] = ['totp']
        identity_auth['totp'] = {
            "user": {
                "name": self._user,
                "passcode": totp_code,
                "domain": {
                    "name": self._account_name
                }
            }
        }
        return identity_auth

    def get_project_token(self):
        identity_auth = self._get_password_auth()
        if self._mfa_secret:
            self._totp_code = generate_totp_passcode(self._mfa_secret)
        if self._totp_code:
            self._add_totp_code(identity_auth, self._totp_code)
        auth_json = {
            "auth": {
                "identity": identity_auth,
                "scope": self._get_project_scope()
            }
        }
        response = self.send_request('POST', '{}/auth'.format(IDENTITY_API_SUFFIX), auth_json, raise_on_status=False)
        if response.status_code == requests.codes.UNAUTHORIZED and response.json().get('receipt'):
            os_receipt = response.headers.get('openstack-auth-receipt')
            if self._mfa_secret:
                _debug_msg("Generating MFA secret")
                totp_code = generate_totp_passcode(self._mfa_secret)
            else:
                totp_code = str(input('MFA Code: ')).lower().strip()
            identity_auth = self._add_totp_code({}, totp_code)
            auth_json = {
                "auth": {
                    "identity": identity_auth,
                    "scope": self._get_project_scope()
                }
            }
            response = self.send_request('POST', '{}/auth'.format(IDENTITY_API_SUFFIX),
                                         auth_json,
                                         headers={"openstack-auth-receipt": os_receipt})
        else:
            response.raise_for_status()

        return response.headers['x-subject-token']

    def get_account_token(self):
        auth_json = {
            "auth": {
                "identity": self._get_password_auth(),
                "scope": self._get_domain_scope()
            }
        }
        auth_response = self.send_request('POST', '{}/auth'.format(IDENTITY_API_SUFFIX), auth_json)
        return auth_response.headers['x-subject-token']

    def get_user_default_project(self):
        details_api_uri = '{}/users/myself/projects'.format(IDENTITY_API_SUFFIX)
        projects = self.send_api_request('GET', details_api_uri)
        if len(projects) > 1:
            sys.exit("There are {} projects: {}\n"
                     "please select one using --project flag".format(
                          len(projects), ', '.join([p['name'] for p in projects])))
        return projects[0]['name']


def ask_for_value(parameter, current, hide=False):
    if hide:
        value = getpass.getpass("{} []: ".format(parameter))
    else:
        value = input("{} [{}]: ".format(parameter, current or '')).strip()
    if value:
        return value
    return current


def main():
    global _debug
    parser = argparse.ArgumentParser(description="Obtain zCompute API Token using user credentials")
    parser.add_argument("cluster", help="Cluster API endpoint IP or host name")
    parser.add_argument("--interactive",
                        help="Will get login credentials interactively",
                        action='store_true',
                        default=False)
    parser.add_argument("--account",
                        help="Account name (will use environment variable ZCOMPUTE_ACCOUNT if not provided)",
                        default=os.environ.get('ZCOMPUTE_ACCOUNT', None),
                        required=False)
    parser.add_argument("--username",
                        help="User name (will use environment variable ZCOMPUTE_USER if not provided)",
                        default=os.environ.get('ZCOMPUTE_USER', None),
                        required=False)
    parser.add_argument("--mfa-secret",
                        help="MFA secret to generate totp code MFA enabled login "
                             "(will use environment variable ZCOMPUTE_MFASECRET if not provided)",
                        default=os.environ.get('ZCOMPUTE_MFASECRET', None),
                        required=False)
    parser.add_argument("--totp-code",
                        help="TOTP code to use for MFA enabled login "
                             "(will use environment variable ZCOMPUTE_TOTP if not provided)",
                        default=os.environ.get('ZCOMPUTE_TOTP', None),
                        required=False)
    parser.add_argument("--project",
                        dest='project_name',
                        help="Project name (will use environment variable ZCOMPUTE_PROJECT if not provided)"
                             " if not provided and only one exists in the account will it",
                        default=os.environ.get('ZCOMPUTE_PROJECT', None),
                        required=False)
    parser.add_argument("--password",
                        help="Password - will use environment variable ZCOMPUTE_PASSWORD if not provided",
                        default=os.environ.get('ZCOMPUTE_PASSWORD', None))
    parser.add_argument("--debug", help="Print debug messages", default=False, action='store_true')
    parser.add_argument("-k", "--insecure", action='store_true', help="Do not verify cluster certificate", default=False)
    args = parser.parse_args()
    if args.debug:
        _debug = True
    if args.interactive:
        args.account = ask_for_value('account', args.account)
        args.project_name = ask_for_value('project name', args.project_name)
        args.username = ask_for_value('User name', args.username)
        args.mfa_secret = ask_for_value('MFA secret', args.mfa_secret, hide=True)
        args.totp_code = ask_for_value('TOTP code', args.totp_code)
        args.password = ask_for_value('Password', args.password, hide=True)

    url = 'https://{}'.format(args.cluster)
    if args.cluster.startswith('https://'):
        url = args.cluster
    api = ZComputeApi(url, args.account, args.project_name, args.username, args.password,
                      insecure=args.insecure, totp_code=args.totp_code, mfa_secret=args.mfa_secret, debug=args.debug)
    if not args.password:
        sys.exit("Please provide a password")
    if not args.project_name:
        if args.account is None:
            sys.exit("Please provide the account name to login into")
        if args.username is None:
            sys.exit("Please provide the user name to login with")
        api.reset_api_params(token=api.get_account_token())
        project_name = api.get_user_default_project()
    else:
        if args.project_name is None:
            sys.exit("Please provide the project name to login into")
        project_name = args.project_name
    api.reset_api_params(project_name=project_name)
    print(api.get_project_token())


if __name__ == '__main__':
    main()

|api-endpoints-list|