Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.

The format is based on `Keep a Changelog <http://keepachangelog.com/>`_.

====================
2.1.2 - 2018-11-29
====================

Added
-----
* Support for getting bucket statistics in the Object Storage service
* Support for using FIPS compliant libcrypto library

Fixed
-----
* Block Storage service for copying volume backups across regions is now enabled


====================
2.1.1 - 2018-11-15
====================
Expand Down Expand Up @@ -49,10 +63,6 @@ Changed
-------
* database_edition field in Backup and model changed from a free format string to a validated string. It will only accept one of the following: “STANDARD_EDITION”, “ENTERPRISE_EDITION”, “ENTERPRISE_EDITION_HIGH_PERFORMANCE”, “ENTERPRISE_EDITION_EXTREME_PERFORMANCE”

Known issue
-----------
* Block Storage service for copying volume backups across regions is not enabled

Breaking
--------
* db_data_size_in_mbs field in Backup and BackupSummary models renamed to database_size_in_g_bs. The type changed from int to float.
Expand Down
44 changes: 44 additions & 0 deletions docs/fips-libraries.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.. _fips-libraries:

.. raw:: html

<script type='text/javascript'>
var oldDocsHost = 'oracle-bare-metal-cloud-services-python-sdk';
if (window.location.href.indexOf(oldDocsHost) != -1) {
window.location.href = 'https://oracle-bare-metal-cloud-services-python-sdk.readthedocs.io/en/latest/deprecation-notice.html';
}
</script>

Using FIPS-validated Libraries
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The SDK can be configured to use FIPS-validated libcrypto library. You can set it programmatically on a per session basis or persistently across the environment. Both approaches require the path to the libcrypto library on your system.

Enabling FIPS Mode Programmatically
------------------------------------

To configure the SDK to use a FIPS-validated libcrypto library, execute the following:

.. code-block:: python

oci.fips.enable_fips_mode('</path/to/libcrypto.x.x.x>')

Setting the Environment Variables
---------------------------------

If you do not want to run ``enable_fips_mode()`` for every session, you can set an environment variable so that the SDK uses the library every time.

Set the following environment variable to the path to the libcrypto library:

- FIPS_LIBCRYPTO_PATH

Verifying the Configuration
---------------------------

To verify that the SDK is using the libcrypto library that you specified, execute the following:

.. code-block:: python

oci.fips.is_fips_mode()

This should return True, indicating that the SDK is using the library specified by the environment variable.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ To get started, head over to the :ref:`installation instructions <install>` or s

installation
configuration
fips-libraries
forward-compatibility
backward-compatibility
quickstart
Expand Down
4 changes: 3 additions & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ This command instructs the `requests <https://pypi.python.org/pypi/requests>`_
library used by the Python SDK to use the version of OpenSSL that is bundled with the `cryptography <https://pypi.python.org/pypi/cryptography>`_
library used by the SDK.

**Note:**
If you don't want to use ``requests[security]`` you can update OpenSSL as you normally would. For example, on OS X, use Homebrew to update OpenSSL using the following commands::

brew update
brew install openssl
brew install python

.. note::
If you need to configure your environment for FIPS-compliance, see :doc:`fips-libraries`

=================
Troubleshooting
=================
Expand Down
5 changes: 3 additions & 2 deletions src/oci/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
# Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

from . import audit, container_engine, core, database, dns, email, file_storage, identity, key_management, load_balancer, object_storage, resource_search
from . import auth, config, constants, decorators, exceptions, regions, pagination, retry
from . import auth, config, constants, decorators, exceptions, regions, pagination, retry, fips
from .base_client import BaseClient
from .request import Request
from .response import Response
from .signer import Signer
from .version import __version__ # noqa
from .waiter import wait_until

fips.enable_fips_mode()

__all__ = [
"BaseClient", "Error", "Request", "Response", "Signer", "config", "constants", "decorators", "exceptions", "regions", "wait_until", "pagination", "auth", "retry",
"BaseClient", "Error", "Request", "Response", "Signer", "config", "constants", "decorators", "exceptions", "regions", "wait_until", "pagination", "auth", "retry", "fips",
"audit", "container_engine", "core", "database", "dns", "email", "file_storage", "identity", "key_management", "load_balancer", "object_storage", "resource_search"
]
2 changes: 2 additions & 0 deletions src/oci/auth/auth_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def get_tenancy_id_from_certificate(cert):
val = name_attribute.value
if val.startswith('opc-tenant:'):
return val[len('opc-tenant:'):]
if val.startswith('opc-identity:'):
return val[len('opc-identity:'):]

raise RuntimeError('The certificate does not contain a tenancy OCID')

Expand Down
9 changes: 5 additions & 4 deletions src/oci/auth/certificate_retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def __init__(self, **kwargs):
self.passphrase = self.passphrase.encode('ascii')

self._refresh_lock = threading.Lock()
self.requests_session = requests.Session()

self.refresh()

Expand Down Expand Up @@ -166,7 +167,7 @@ def _refresh_inner(self):
import oci.signer

downloaded_certificate = six.BytesIO()
response = requests.get(self.cert_url, stream=True)
response = self.requests_session.get(self.cert_url, stream=True, timeout=(10, 60))

response.raise_for_status()

Expand All @@ -182,7 +183,7 @@ def _refresh_inner(self):

if self.private_key_url:
downloaded_private_key_raw = six.BytesIO()
response = requests.get(self.private_key_url, stream=True)
response = self.requests_session.get(self.private_key_url, stream=True, timeout=(10, 60))

response.raise_for_status()

Expand Down Expand Up @@ -242,7 +243,7 @@ class PEMStringCertificateRetriever(AbstractCertificateRetriever):
def __init__(self, **kwargs):
import oci.signer

super(UrlBasedCertificateRetriever, self).__init__()
super(PEMStringCertificateRetriever, self).__init__()

if 'certificate_pem' not in kwargs:
raise TypeError('certificate_pem must be supplied as a keyword argument')
Expand Down Expand Up @@ -317,7 +318,7 @@ def __init__(self, **kwargs):

parent_class_kwargs = {
'certificate_pem': self._load_data_from_file(kwargs['certificate_file_path']),
'private_key_pem_file_path': private_key_pem,
'private_key_pem': private_key_pem,
'passphrase': kwargs.get('passphrase')
}

Expand Down
6 changes: 4 additions & 2 deletions src/oci/auth/federation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ def __init__(self, **kwargs):
else:
self.retry_strategy = oci.retry.DEFAULT_RETRY_STRATEGY

self.requests_session = requests.Session()

def refresh_security_token(self):
return self._refresh_security_token_inner()

Expand Down Expand Up @@ -142,9 +144,9 @@ def _get_security_token_from_auth_service(self):
signer = AuthTokenRequestSigner(self.tenancy_id, fingerprint, self.leaf_certificate_retriever)

if self.cert_bundle_verify:
response = requests.post(self.federation_endpoint, json=request_payload, auth=signer, verify=self.cert_bundle_verify)
response = self.requests_session.post(self.federation_endpoint, json=request_payload, auth=signer, verify=self.cert_bundle_verify, timeout=(10, 60))
else:
response = requests.post(self.federation_endpoint, json=request_payload, auth=signer)
response = self.requests_session.post(self.federation_endpoint, json=request_payload, auth=signer, timeout=(10, 60))

parsed_response = None
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def initialize_and_return_region(self):
if hasattr(self, 'region'):
return self.region

response = requests.get(self.GET_REGION_URL)
response = requests.get(self.GET_REGION_URL, timeout=(10, 60))
region_raw = response.text.strip().lower()

# The region can be something like "phx" but internally we expect "us-phoenix-1", "us-ashburn-1" etc.
Expand Down
112 changes: 112 additions & 0 deletions src/oci/fips.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# coding: utf-8
# Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

import sys
import ctypes
import logging
import os


class DevNull:
"""
Simple class to supress errors which may occur when importing hashlib
in FIPS mode.
"""
def write(self, msg):
pass


def override_libcrypto(fips_libcrypto_path):
"""
Override libcrypto and add FIPS_mode function to ssl if it is not there
"""

_bs_crypto = ctypes.CDLL(fips_libcrypto_path)
_bs_crypto.FIPS_mode_set(ctypes.c_int(1))
import ssl # noqa: E402
if not hasattr(ssl, 'FIPS_mode'):
ssl.FIPS_mode = _bs_crypto.FIPS_mode


def md5(intitial_message=''):
"""
Placeholder md5 function for hashlib so it won't segfault when called after
enabling FIPS mode.
"""
raise ValueError("md5 disabled for fips")
return None


def patch_hashlib_md5():
"""
hashlib.md5 is imported by urllib3, which is required by requests,
which is used by oci (python sdk). This will cause errors so we need to
patch hashlib.
"""

stderr = sys.stderr
try:
sys.stderr = DevNull()
import hashlib # noqa: E402
except (RuntimeError, ValueError):
pass
sys.stderr = stderr
hashlib.md5 = md5


def is_fips_mode():
"""
Verify that ssl.FIPS_mode() returns 1 and that using md5 raises an
exception
"""

import hashlib
import ssl

if not hasattr(ssl, 'FIPS_mode'):
return False
elif ssl.FIPS_mode() != 1:
return False

try:
digest = hashlib.md5(b"Hello World\n").hexdigest() # noqa: F841
return False
except ValueError:
# Expect to get this exception so do nothing
pass

return True


def enable_fips_mode(fips_libcrypto_path=None):
"""
Enable FIPS mode by overriding libcrypto and patching hashlib
"""

logger = logging.getLogger("{}.{}".format(__name__, id(enable_fips_mode)))
logger.addHandler(logging.NullHandler())

if not fips_libcrypto_path:
if 'FIPS_LIBCRYPTO_PATH' in os.environ:
fips_libcrypto_path = os.environ['FIPS_LIBCRYPTO_PATH']

if fips_libcrypto_path:
override_libcrypto(fips_libcrypto_path)

import hashlib
try:
digest = hashlib.md5(b"Hello World\n").hexdigest() # noqa: F841

# If the previous line did not raise an exception md5 needs to be
# patched
patch_hashlib_md5()

except ValueError:
# Expect to get this exception so do nothing
pass

logger.info("Using '{}' for libcypto".format(fips_libcrypto_path))
if is_fips_mode():
logger.info("FIPS mode is active")
else:
logger.error("Failed to enter FIPS mode")
Loading