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.
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.
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" ] } } }
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"]}}}'
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=
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" ] } } }
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"]}}}'
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=
To use
curl
to call a REST method on an endpoint, provide the token string in theX-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 usessed
andawk
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¶
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=
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()