From 2fcbf2ccb4cd3197c44ae069904a37eca6776b94 Mon Sep 17 00:00:00 2001 From: Felipe Garcia Bulsoni Date: Fri, 24 Nov 2017 15:49:10 -0200 Subject: [PATCH 1/2] Enable usage of a certificate file when connecting to OneView Appliance - Bumped up SDK versions to prepare for release - Enabled usage of ssl_certificate as ENV variable and inside Dict - Added certificate files to ignored list --- .gitignore | 3 ++ CHANGELOG.md | 6 +++- README.md | 28 +++++++++++++++++ docs/source/conf.py | 4 +-- hpOneView/__init__.py | 2 +- hpOneView/connection.py | 50 +++++++++++++++---------------- hpOneView/oneview_client.py | 8 +++-- setup.py | 4 +-- tests/unit/test_oneview_client.py | 3 ++ tox.ini | 2 +- 10 files changed, 75 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 45a5ffcb..2386fcf2 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ docs/source/* !docs/source/conf.py !docs/source/index.rst .vscode + +#certificate files +*.crt diff --git a/CHANGELOG.md b/CHANGELOG.md index 11595c29..5c5dd571 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ -# 4.4.0(Unreleased) +# 4.4.0 +#### Notes +Enabled usage of a CA Certificate file for establishing a SSL connection to the HPE OneView Appliance. + #### New Resources: - Version #### Bug fixes & Enhancements - [#332](https://github.com/HewlettPackard/python-hpOneView/issues/332) example scmb.py is broken with v4.x libray +- [#339](https://github.com/HewlettPackard/python-hpOneView/issues/339) Validate secure connection to OneView using a certificate file # 4.3.0 #### Notes diff --git a/README.md b/README.md index 090e7907..dc722a3c 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ export ONEVIEWSDK_SESSIONID='123' # Optional export ONEVIEWSDK_API_VERSION='300' export ONEVIEWSDK_AUTH_LOGIN_DOMAIN='authdomain' +export ONEVIEWSDK_SSL_CERTIFICATE='' export ONEVIEWSDK_PROXY=':' ``` @@ -140,6 +141,33 @@ oneview_client = OneViewClient(config) :lock: Tip: Check the file permissions because the password is stored in clear-text. +### SSL Server Certificate + +To enable the SDK to establish a SSL connection to the HPE OneView server, it is necessary to generate a CA Cert file containing the server credentials. + +1. Fetch the HPE OneView Appliance CA certificate + +Example: + +```bash +$ openssl s_client -showcerts -host -port 443 +``` + +Copy the server certificate content from `-----BEGIN CERTIFICATE-----` to `-----END CERTIFICATE-----` (inclusive) into a `.crt` file. + +2. Declare the CA Certificate location when creating a `config` dictionary + +```python +config = { + "ip": "172.16.102.82", + "credentials": { + "userName": "Administrator", + "password": "secret123" + }, + "ssl_certificate": "/home/python-hpOneView/my_ov_certificate.crt" +} +``` + ### Proxy If your environment requires a proxy, define the proxy properties in the JSON file using the following syntax: diff --git a/docs/source/conf.py b/docs/source/conf.py index bf145409..4bdae1b7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -71,9 +71,9 @@ # built documents. # # The short X.Y version. -version = u'4.3.0' +version = u'4.4.0' # The full version, including alpha/beta/rc tags. -release = u'4.3.0' +release = u'4.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/hpOneView/__init__.py b/hpOneView/__init__.py index 1e167423..23620341 100644 --- a/hpOneView/__init__.py +++ b/hpOneView/__init__.py @@ -14,7 +14,7 @@ standard_library.install_aliases() __title__ = 'hpOneView' -__version__ = '4.3.0' +__version__ = '4.4.0' __copyright__ = '(C) Copyright (2012-2017) Hewlett Packard Enterprise Development LP' __license__ = 'MIT' diff --git a/hpOneView/connection.py b/hpOneView/connection.py index 47282930..277fc4c0 100644 --- a/hpOneView/connection.py +++ b/hpOneView/connection.py @@ -1,24 +1,5 @@ # -*- coding: utf-8 -* -""" -connection.py -~~~~~~~~~~~~~~ - -This module maintains communication with the appliance. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from builtins import open -from builtins import str -from future import standard_library -from future.utils import raise_from - -standard_library.install_aliases() - - ### # (C) Copyright (2012-2017) Hewlett Packard Enterprise Development LP # @@ -41,6 +22,23 @@ # THE SOFTWARE. ### +""" +connection.py +~~~~~~~~~~~~~~ + +This module maintains communication with the appliance. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from builtins import open +from builtins import str +from future import standard_library + +standard_library.install_aliases() + import http.client import json import logging @@ -49,6 +47,7 @@ import os import ssl import time +import traceback from hpOneView.exceptions import HPOneViewException @@ -56,7 +55,7 @@ class connection(object): - def __init__(self, applianceIp, api_version=300): + def __init__(self, applianceIp, api_version=300, sslBundle=False): self._session = None self._host = applianceIp self._cred = None @@ -68,8 +67,8 @@ def __init__(self, applianceIp, api_version=300): self._proxyHost = None self._proxyPort = None self._doProxy = False - self._sslTrustedBundle = None self._sslTrustAll = True + self._sslTrustedBundle = self.set_trusted_ssl_bundle(sslBundle) self._nextPage = None self._prevPage = None self._numTotalRecords = 0 @@ -92,8 +91,9 @@ def set_proxy(self, proxyHost, proxyPort): self._doProxy = True def set_trusted_ssl_bundle(self, sslBundle): - self._sslTrustAll = False - self._sslTrustedBundle = sslBundle + if sslBundle is not False: + self._sslTrustAll = False + return sslBundle def get_session(self): return self._session @@ -434,8 +434,8 @@ def login(self, cred, verbose=False): try: if self._validateVersion is False: self.validateVersion() - except Exception as e: - raise_from(HPOneViewException('Failure during login attempt.'), e) + except Exception: + raise(HPOneViewException('Failure during login attempt.\n %s' % traceback.format_exc())) self._cred = cred try: diff --git a/hpOneView/oneview_client.py b/hpOneView/oneview_client.py index 012e8b66..03cf64fb 100755 --- a/hpOneView/oneview_client.py +++ b/hpOneView/oneview_client.py @@ -116,7 +116,7 @@ class OneViewClient(object): DEFAULT_API_VERSION = 300 def __init__(self, config): - self.__connection = connection(config["ip"], config.get('api_version', self.DEFAULT_API_VERSION)) + self.__connection = connection(config["ip"], config.get('api_version', self.DEFAULT_API_VERSION), config.get('ssl_certificate', False)) self.__image_streamer_ip = config.get("image_streamer_ip") self.__set_proxy(config) self.__connection.login(config["credentials"]) @@ -193,7 +193,6 @@ def __init__(self, config): self.__versions = None self.__backups = None self.__login_details = None - # TODO: Implement: con.set_trusted_ssl_bundle(args.cert) @classmethod def from_json_file(cls, file_name): @@ -217,7 +216,8 @@ def from_environment_variables(cls): Construct OneViewClient using environment variables. Allowed variables: ONEVIEWSDK_IP (required), ONEVIEWSDK_USERNAME (required), ONEVIEWSDK_PASSWORD (required), - ONEVIEWSDK_AUTH_LOGIN_DOMAIN, ONEVIEWSDK_API_VERSION, ONEVIEWSDK_IMAGE_STREAMER_IP, ONEVIEWSDK_SESSIONID and ONEVIEWSDK_PROXY. + ONEVIEWSDK_AUTH_LOGIN_DOMAIN, ONEVIEWSDK_API_VERSION, ONEVIEWSDK_IMAGE_STREAMER_IP, ONEVIEWSDK_SESSIONID, ONEVIEWSDK_SSL_CERTIFICATE + and ONEVIEWSDK_PROXY. Returns: OneViewClient: @@ -225,6 +225,7 @@ def from_environment_variables(cls): ip = os.environ.get('ONEVIEWSDK_IP', '') image_streamer_ip = os.environ.get('ONEVIEWSDK_IMAGE_STREAMER_IP', '') api_version = int(os.environ.get('ONEVIEWSDK_API_VERSION', OneViewClient.DEFAULT_API_VERSION)) + ssl_certificate = os.environ.get('ONEVIEWSDK_SSL_CERTIFICATE', '') username = os.environ.get('ONEVIEWSDK_USERNAME', '') auth_login_domain = os.environ.get('ONEVIEWSDK_AUTH_LOGIN_DOMAIN', '') password = os.environ.get('ONEVIEWSDK_PASSWORD', '') @@ -234,6 +235,7 @@ def from_environment_variables(cls): config = dict(ip=ip, image_streamer_ip=image_streamer_ip, api_version=api_version, + ssl_certificate=ssl_certificate, credentials=dict(userName=username, authLoginDomain=auth_login_domain, password=password, sessionID=sessionID), proxy=proxy) diff --git a/setup.py b/setup.py index 8c78a2c1..6d798d57 100644 --- a/setup.py +++ b/setup.py @@ -26,10 +26,10 @@ from setuptools import setup setup(name='hpOneView', - version='4.3.0', + version='4.4.0', description='HPE OneView Python Library', url='https://github.com/HewlettPackard/python-hpOneView', - download_url="https://github.com/HewlettPackard/python-hpOneView/tarball/v4.3.0", + download_url="https://github.com/HewlettPackard/python-hpOneView/tarball/v4.4.0", author='Hewlett Packard Enterprise Development LP', author_email='oneview-pythonsdk@hpe.com', license='MIT', diff --git a/tests/unit/test_oneview_client.py b/tests/unit/test_oneview_client.py index 66bcee0b..e9be14cc 100755 --- a/tests/unit/test_oneview_client.py +++ b/tests/unit/test_oneview_client.py @@ -297,6 +297,7 @@ def test_from_environment_variables_is_passing_right_arguments_to_the_constructo mock_cls.assert_called_once_with({'api_version': 201, 'proxy': '172.16.100.195:9999', 'ip': '172.16.100.199', + 'ssl_certificate': '', 'image_streamer_ip': '172.172.172.172', 'credentials': {'userName': 'admin', @@ -313,6 +314,7 @@ def test_from_environment_variables_is_passing_right_arguments_to_the_constructo 'proxy': '172.16.100.195:9999', 'ip': '172.16.100.199', 'image_streamer_ip': '172.172.172.172', + 'ssl_certificate': '', 'credentials': {'userName': 'admin', 'password': 'secret123', @@ -328,6 +330,7 @@ def test_from_environment_variables_is_passing_right_arguments_to_the_constructo 'proxy': '', 'ip': '172.16.100.199', 'image_streamer_ip': '', + 'ssl_certificate': '', 'credentials': {'userName': '', 'password': '', diff --git a/tox.ini b/tox.ini index ee8636dd..68c4de6c 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ skip_missing_interpreters = true [flake8] ignore = E402 -max-line-length = 140 +max-line-length = 160 exclude = hpOneView/__init__.py max-complexity = 14 From ed79be82429f4c454823c637e69bcf97cf8ab2fd Mon Sep 17 00:00:00 2001 From: Felipe Garcia Bulsoni Date: Mon, 27 Nov 2017 14:45:36 -0200 Subject: [PATCH 2/2] Updated ImageStreamer client to also accept a ca cert file - Updated README to include using a certificate file on Image Streamer - Updated config-rename.json example --- README.md | 24 +++++++++++++------ examples/config-rename.json | 3 ++- hpOneView/connection.py | 4 ++-- .../image_streamer/image_streamer_client.py | 4 ++-- hpOneView/oneview_client.py | 3 ++- tests/unit/test_connection.py | 4 ++-- 6 files changed, 27 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index dc722a3c..8da86743 100644 --- a/README.md +++ b/README.md @@ -145,18 +145,28 @@ oneview_client = OneViewClient(config) To enable the SDK to establish a SSL connection to the HPE OneView server, it is necessary to generate a CA Cert file containing the server credentials. -1. Fetch the HPE OneView Appliance CA certificate - -Example: - +1. Fetch the HPE OneView Appliance CA certificate. ```bash $ openssl s_client -showcerts -host -port 443 ``` -Copy the server certificate content from `-----BEGIN CERTIFICATE-----` to `-----END CERTIFICATE-----` (inclusive) into a `.crt` file. - -2. Declare the CA Certificate location when creating a `config` dictionary +2. Copy the server certificate wrapped with a header line and a footer line into a `.crt` file. +``` +-----BEGIN CERTIFICATE----- +... (HPE OneView Appliance certificate in base64 PEM encoding) ... +-----END CERTIFICATE----- +``` +When using HPE Image Streamer, the server certificate for the HPE Image Streamer should also be added to the certificates file. Example: +``` +-----BEGIN CERTIFICATE----- +... (HPE OneView Appliance certificate in base64 PEM encoding) ... +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +... (HPE Image Streamer Appliance certificate in base64 PEM encoding) ... +-----END CERTIFICATE----- +``` +3. Declare the CA Certificate location when creating a `config` dictionary. ```python config = { "ip": "172.16.102.82", diff --git a/examples/config-rename.json b/examples/config-rename.json index 68f02e11..28a8ebee 100644 --- a/examples/config-rename.json +++ b/examples/config-rename.json @@ -1,7 +1,8 @@ { "ip": "172.16.102.59", "image_streamer_ip": "172.16.102.60", - "api_version": 300, + "api_version": 500, + "ssl_certificate": "", "credentials": { "userName": "administrator", "authLoginDomain": "", diff --git a/hpOneView/connection.py b/hpOneView/connection.py index 277fc4c0..2d8e8301 100644 --- a/hpOneView/connection.py +++ b/hpOneView/connection.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -* - ### # (C) Copyright (2012-2017) Hewlett Packard Enterprise Development LP # @@ -68,6 +67,7 @@ def __init__(self, applianceIp, api_version=300, sslBundle=False): self._proxyPort = None self._doProxy = False self._sslTrustAll = True + self._sslBundle = sslBundle self._sslTrustedBundle = self.set_trusted_ssl_bundle(sslBundle) self._nextPage = None self._prevPage = None @@ -91,7 +91,7 @@ def set_proxy(self, proxyHost, proxyPort): self._doProxy = True def set_trusted_ssl_bundle(self, sslBundle): - if sslBundle is not False: + if sslBundle: self._sslTrustAll = False return sslBundle diff --git a/hpOneView/image_streamer/image_streamer_client.py b/hpOneView/image_streamer/image_streamer_client.py index 8e89bd04..e15d777a 100644 --- a/hpOneView/image_streamer/image_streamer_client.py +++ b/hpOneView/image_streamer/image_streamer_client.py @@ -44,8 +44,8 @@ class ImageStreamerClient(object): - def __init__(self, ip, session_id, api_version): - self.__connection = connection(ip, api_version) + def __init__(self, ip, session_id, api_version, sslBundle=False): + self.__connection = connection(ip, api_version, sslBundle) self.__connection.set_session_id(session_id) self.__golden_images = None self.__plan_scripts = None diff --git a/hpOneView/oneview_client.py b/hpOneView/oneview_client.py index 03cf64fb..50c4cec7 100755 --- a/hpOneView/oneview_client.py +++ b/hpOneView/oneview_client.py @@ -286,7 +286,8 @@ def create_image_streamer_client(self): """ image_streamer = ImageStreamerClient(self.__image_streamer_ip, self.__connection.get_session_id(), - self.__connection._apiVersion) + self.__connection._apiVersion, + self.__connection._sslBundle) return image_streamer diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 588644a5..22dc9762 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -984,7 +984,7 @@ def test_get_connection_ssl_trust_all_with_proxy(self): def test_get_connection_trusted_ssl_bundle_with_proxy(self, mock_lvl): self.connection.set_proxy('10.0.0.1', 3128) - self.connection.set_trusted_ssl_bundle(None) + self.connection.set_trusted_ssl_bundle('/test') conn = self.connection.get_connection() @@ -995,7 +995,7 @@ def test_get_connection_trusted_ssl_bundle_with_proxy(self, mock_lvl): @patch.object(ssl.SSLContext, 'load_verify_locations') def test_get_connection_trusted_ssl_bundle(self, mock_lvl): - self.connection.set_trusted_ssl_bundle(None) + self.connection.set_trusted_ssl_bundle('/test') conn = self.connection.get_connection()