#!/usr/bin/env python3 """ Load service account credentials into nscli's credential store. For use in a testing environment. """ import argparse from collections import namedtuple from functools import lru_cache import os import sys import requests from nspylib.clients import IdentityAPI, PWSafeAPI from nspylib.util.passkey import PassKey from nscli.config import settings CREDPATH = os.environ.get("CREDPATH") or "/srv/config/credentials.json" class NSCLITACACS: """NSCLI TACACS object""" pass class NSCLIPassKey(PassKey): """NSCLI version of PassKey as the NSCLI TACACS credentials are in their own PWSafe project per standards""" _attrs_nscli_acs = { 236987: "NSCLITACACS" } def __init__(self, *args, **kwargs): super(NSCLIPassKey, self).__init__(*args, **kwargs) self._nscli_acs = None @property def nscli_acs(self): """A NSCLITACACS object for nscli-uat-svc_a/b TACACS credentials""" if self._nscli_acs is None: self._nscli_acs = NSCLITACACS() pw = PWSafeAPI(identity_token=self.identity_token) r = pw.get_project(project_id=29700) r.raise_for_status() j = r.json() for cred in j: if cred["credential"]["id"] in list(self._attrs_nscli_acs.keys()): setattr(self._nscli_acs, self._attrs_nscli_acs[cred["credential"]["id"]], cred["credential"]) return self._nscli_acs class Credentials: """Service account credentials""" Cred = namedtuple("Cred", field_names=["username", "password"]) def __init__(self, path): self.passkey = NSCLIPassKey(full_path=path) self.passkey._attrs_service_accounts.update({ 198920: "NSCLISSHPubkey", 198921: "NSCLISSHPrivkey", }) attrs = { "sso": "sso username/password from passwordsafe", "identity": "identity token from identity API", "identity_api_response": "identity token from identity API", "core": "core token from CTKAPI", "acs": "acs username/password from passwordsafe", "ssh_pub_key": "ssh public key from passwordsafe", "ssh_priv_key": "ssh private key from passwordsafe", } for attr, msg in attrs.items(): try: assert getattr(self, attr) is not None except (AssertionError, Exception): print("ERROR: Failed to gather {}".format(msg), file=sys.stderr) raise else: print("Gathered {}".format(msg)) @property @lru_cache() def acs(self): return self.Cred( self.passkey.nscli_acs.NSCLITACACS["username"], self.passkey.nscli_acs.NSCLITACACS["password"] ) @property @lru_cache() def sso(self): return self.Cred( self.passkey.service.NSCLI["username"], self.passkey.service.NSCLI["password"] ) @property @lru_cache() def identity(self): token = self.identity_api_response["access"]["token"]["id"] timestamp = self.identity_api_response["access"]["token"]["expires"] return "{} {}".format(timestamp, token) @property @lru_cache() def identity_api_response(self): api = IdentityAPI() r = api.get_internal_token(username=self.sso.username, password=self.sso.password) r.raise_for_status() return r.json() @property @lru_cache() def core(self): r = requests.post( "https://ws.core.rackspace.com/ctkapi/login/{}".format(self.sso.username), json={"password": self.sso.password} ) r.raise_for_status() return r.json()["authtoken"] @property @lru_cache() def ssh_pub_key(self): return self.passkey.service.NSCLISSHPubkey["password"] @property @lru_cache() def ssh_priv_key(self): return self.passkey.service.NSCLISSHPrivkey["password"] def load_nscli(credentials, import_path="."): """Set the given credentials in an nscli installation found at the import path :param credentials: NSCLI credentials to set :type credentials: Credentials :param import_path: The folder where the nscli installation is located. This location is searched first. \ See https://docs.python.org/3/tutorial/modules.html#the-module-search-path :type import_path: str :return: None :rtype: None """ sys.path.insert(0, os.path.abspath(import_path)) from nscli.auth.ns_auth import Token t = Token() # t.sso_user can't be directly assigned. It comes from a settings file or env var. # The username for sso_user, sso_password, and identity_token are expected to match. assert t.sso_user == credentials.sso.username t.sso_password = credentials.sso.password t.core_token = credentials.core t.identity_token = credentials.identity t.ldap_roles = t.roles_from_ident_api_resp(credentials.identity_api_response) # t.acs_user can't be directly assigned. It comes from a settings file. # The username for acs_user and acs_password should match. assert t.acs_user == credentials.acs.username t.acs_password = credentials.acs.password ssh_dir = os.path.expanduser("~/.ssh") os.makedirs(ssh_dir, exist_ok=True) os.chmod(ssh_dir, 0o700) for filename, data, mode in [ (os.path.join(ssh_dir, "id_rsa.pub"), credentials.ssh_pub_key, 0o644), (os.path.join(ssh_dir, "id_rsa"), credentials.ssh_priv_key, 0o600) ]: with open(filename, "wt", encoding="utf-8") as f: f.write(data) print("Wrote {}".format(filename)) os.chmod(filename, mode) def build_cli_parser(): parser = argparse.ArgumentParser(description="Load Service Account Credentials") parser.add_argument( "--credpath", help="path to credential file", default=CREDPATH ) parser.add_argument( "--path", help="import path for nscli", default="." ) return parser def set_acs_username(acs_username): """Sets the current active TACACS username in the settings file""" # Since TACACS usernames rotate (nscli-uat-svc_a / nsclit-uat-svc_b) we need to set it upon load settings.set_value('auth', 'acs_user', acs_username) def main(args=sys.argv): parser = build_cli_parser() args = parser.parse_args(args[1:]) path, credpath = args.path, args.credpath credentials = Credentials(credpath) set_acs_username(credentials.acs.username) load_nscli(credentials, path) if __name__ == '__main__': sys.exit(main(sys.argv))