Skip to content

Commit

Permalink
Merge pull request #266 from amney/appuser
Browse files Browse the repository at this point in the history
Further certificate authentication changes
  • Loading branch information
michsmit99 committed Dec 15, 2016
2 parents 2355c57 + 0ddfa7b commit 55205bd
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 13 deletions.
2 changes: 1 addition & 1 deletion acitoolkit/aciphysobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -1436,7 +1436,7 @@ def get(cls, session, parent=None, node_id=None):
working_data = WorkingData(session, Node, base_url)

else:
class_url = '/api/node/class/fabricNode.json?'
class_url = '/api/node/class/fabricNode.json'
ret = session.get(class_url)
ret._content = ret._content.decode().replace('\n', '').encode()
data = ret.json()['imdata']
Expand Down
35 changes: 28 additions & 7 deletions acitoolkit/acisession.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import base64
import requests
from collections import namedtuple
from urllib import unquote
try:
from requests.packages.urllib3.exceptions import InsecureRequestWarning
except ImportError:
Expand Down Expand Up @@ -422,7 +423,7 @@ class Session(object):
This class is responsible for all communication with the APIC.
"""
def __init__(self, url, uid, pwd=None, cert_name=None, key=None, verify_ssl=False,
subscription_enabled=True, proxies=None):
appcenter_user=False, subscription_enabled=True, proxies=None):
"""
:param url: String containing the APIC URL such as ``https://1.2.3.4``
:param uid: String containing the username that will be used as\
Expand All @@ -436,6 +437,8 @@ def __init__(self, url, uid, pwd=None, cert_name=None, key=None, verify_ssl=Fals
:param verify_ssl: Used only for SSL connections with the APIC.\
Indicates whether SSL certificates must be verified. Possible\
values are True and False with the default being False.
:param appcenter_user: Set True when using certificate authentication from\
the context of an APIC appcenter app
:param proxies: Optional dictionary containing the proxies passed\
directly to the Requests library
Expand Down Expand Up @@ -472,6 +475,7 @@ def __init__(self, url, uid, pwd=None, cert_name=None, key=None, verify_ssl=Fals
self.pwd = pwd
self.key = key
self.cert_name = cert_name
self.appcenter_user = appcenter_user
if key and cert_name:
if NO_OPENSSL:
raise ImportError('Cannot use certificate authentication because pyopenssl is not available.\n\
Expand Down Expand Up @@ -538,25 +542,40 @@ def _prep_x509_header(self, method, url, data=None):
if not self.cert_auth:
return {}

logging.debug("Preparing certificate based authentication with cert name {} \
key file {} for request {} {} with data {}".format(self.cert_name, self.key, method, url, data))

if not self.session:
self.session = requests.Session()

cert_dn = 'uni/userext/user-{0}/usercert-{1}'.format(self.uid, self.cert_name)
if self.appcenter_user:
cert_dn = 'uni/userext/appuser-{0}/usercert-{1}'.format(self.uid, self.cert_name)
else:
cert_dn = 'uni/userext/user-{0}/usercert-{1}'.format(self.uid, self.cert_name)

url = unquote(url)

logging.debug((
"Preparing certificate based authentication with:"
"\n Cert DN: {}"
"\n Key file: {} "
"\n Request: {} {}"
"\n Data: {}").format(
cert_dn,
self.key,
method,
url,
data)
)

payload = '{}{}'.format(method, url)
if data:
payload += data
#logging.debug('Payload: ', payload)

signature = base64.b64encode(sign(self._x509Key, payload, 'sha256'))
cookie = {'APIC-Request-Signature': signature,
'APIC-Certificate-Algorithm': 'v1.0',
'APIC-Certificate-Fingerprint': 'fingerprint',
'APIC-Certificate-DN': cert_dn}

logging.debug('Authentication cookie', cookie)
logging.debug('Authentication cookie %s' % cookie)
return cookie

def _send_login(self, timeout=None):
Expand Down Expand Up @@ -726,6 +745,7 @@ def push_to_apic(self, url, data, timeout=None):
timeout=timeout, proxies=self._proxies, cookies=cookies)
if resp.status_code == 403:
logging.error('Certificate authentication failed. Please check all settings are correct.')
resp.raise_for_status()
else:
resp = self.session.post(post_url, data=json.dumps(data, sort_keys=True), verify=self.verify_ssl,
timeout=timeout, proxies=self._proxies)
Expand Down Expand Up @@ -759,6 +779,7 @@ def get(self, url, timeout=None):
if resp.status_code == 403:
if self.cert_auth:
logging.error('Certificate authentication failed. Please check all settings are correct.')
resp.raise_for_status()
else:
logging.error(resp.text)
logging.error('Trying to login again....')
Expand Down
8 changes: 5 additions & 3 deletions acitoolkit/acitoolkitlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ def set_default(key):
DEFAULT_URL = set_default('url')
DEFAULT_LOGIN = set_default('login')
DEFAULT_PASSWORD = set_default('password')
if DEFAULT_PASSWORD is not None:
DEFAULT_CERT_NAME = set_default('cert_name')
DEFAULT_KEY = set_default('key')
DEFAULT_CERT_NAME = set_default('cert_name')
DEFAULT_KEY = set_default('key')
#if DEFAULT_PASSWORD is not None:
# DEFAULT_CERT_NAME = set_default('cert_name')
# DEFAULT_KEY = set_default('key')
# else:
# DEFAULT_CERT_NAME = None
# DEFAULT_KEY = None
Expand Down
6 changes: 6 additions & 0 deletions docs/source/tutorialsimpleconfig.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,12 @@ installed using pip
# Example
session = Session('https://1.1.1.1', 'userabc', cert_name='userabc.crt', key='userabc.key')
.. note:: If using the acitoolkit from the context of an APIC App Center app, make sure to pass the extra
parameter ``appcenter_user=True``. App Center apps are provided a user that belongs to a different class
of users.


You do not need to explicitly call the ``login()`` method when using certificate authentication.

After this point, you can continue to use all of the acitoolkit methods to get and push configuration from the APIC securely and without logging in.
Expand Down
26 changes: 24 additions & 2 deletions tests/acitoolkit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3192,11 +3192,33 @@ class TestLiveCertAuth(TestLiveAPIC):
"""
Certificate auth tests with a live APIC
"""
def test_login_to_apic_with_cert(self):
session = Session(URL, LOGIN, cert_name=CERT_NAME, key=KEY)
def login_to_apic(self):
"""Login to the APIC using Certificate auth
RETURNS: Instance of class Session
"""
session = Session(URL, LOGIN, cert_name=CERT_NAME, key=KEY, subscription_enabled=False)
return session

def test_get_tenants(self):
"""
Test that cert auth can get Tenants
"""
session = self.login_to_apic()
tenants = Tenant.get(session)
self.assertTrue(len(tenants) > 0)

def test_get_with_params(self):
"""
Test that URL encoded parameters do not break cert auth
"""
session = self.login_to_apic()
tenants = Tenant.get_deep(
session,
names=['mgmt', 'common'],
limit_to=['fvTenant', 'fvAp']
)
self.assertTrue(len(tenants) > 0)


class TestLiveTenant(TestLiveAPIC):
"""
Expand Down

0 comments on commit 55205bd

Please sign in to comment.