Skip to content

Commit

Permalink
Abstract away service name (opensearch-project#268)
Browse files Browse the repository at this point in the history
* Abstract away service name

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Compuute x-amz-content-256 header

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Fix async signing

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Adds types-six to dependencies

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Optionally remove Content-Length

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Fix dict typo

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Remove requirement for x-amz-content-sha256 header

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Remove deletion of content-length

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Fix capitalization

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

* Adding unit tests

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>

Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>
  • Loading branch information
harshavamsi authored Jan 18, 2023
1 parent c73e463 commit 5c1c890
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Updated getting started to user guide ([#233](https://github.com/opensearch-project/opensearch-py/pull/233))
- Updated CA certificate handling to check OpenSSL environment variables before defaulting to certifi ([#196](https://github.com/opensearch-project/opensearch-py/pull/196))
- Updates `master` to `cluster_manager` to be inclusive ([#242](https://github.com/opensearch-project/opensearch-py/pull/242))
- Support a custom signing service name for AWS SigV4 ([#268](https://github.com/opensearch-project/opensearch-py/pull/268))
### Deprecated

### Removed
Expand Down
6 changes: 4 additions & 2 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,9 @@ import boto3

host = '' # cluster endpoint, for example: my-test-domain.us-east-1.es.amazonaws.com
region = 'us-west-2'
service = 'es' # 'aoss' for OpenSearch Serverless
credentials = boto3.Session().get_credentials()
auth = AWSV4SignerAuth(credentials, region)
auth = AWSV4SignerAuth(credentials, region, service)
index_name = 'python-test-index3'

client = OpenSearch(
Expand Down Expand Up @@ -450,8 +451,9 @@ import boto3

host = '' # cluster endpoint, for example: my-test-domain.us-east-1.es.amazonaws.com
region = 'us-west-2'
service = 'es' # 'aoss' for OpenSearch Serverless
credentials = boto3.Session().get_credentials()
auth = AWSV4SignerAsyncAuth(credentials, region)
auth = AWSV4SignerAsyncAuth(credentials, region, service)
index_name = 'python-test-index3'

client = OpenSearch(
Expand Down
12 changes: 6 additions & 6 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

# -- Project information -----------------------------------------------------

project = 'OpenSearch Python Client'
copyright = 'OpenSearch Project Contributors'
author = 'OpenSearch Project Contributors'
project = "OpenSearch Python Client"
copyright = "OpenSearch Project Contributors"
author = "OpenSearch Project Contributors"


# -- General configuration ---------------------------------------------------
Expand All @@ -38,7 +38,7 @@
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
Expand All @@ -51,12 +51,12 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
html_theme = "sphinx_rtd_theme"

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]

# -- additional settings -------------------------------------------------
intersphinx_mapping = {
Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def format(session):

@nox.session()
def lint(session):
session.install("flake8", "black", "mypy", "isort", "types-requests")
session.install("flake8", "black", "mypy", "isort", "types-requests", "types-six")

session.run("isort", "--check", "--profile=black", *SOURCE_FILES)
session.run("black", "--target-version=py33", "--check", *SOURCE_FILES)
Expand Down
4 changes: 2 additions & 2 deletions opensearchpy/_async/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ async def map_actions():
raise_on_error,
ignore_status,
*args,
**kwargs
**kwargs,
),
):

Expand Down Expand Up @@ -471,5 +471,5 @@ async def _change_doc_index(hits, index):
target_client,
_change_doc_index(docs, target_index),
chunk_size=chunk_size,
**kwargs
**kwargs,
)
14 changes: 8 additions & 6 deletions opensearchpy/helpers/asyncsigner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

import sys

OPENSEARCH_SERVICE = "es"

PY3 = sys.version_info[0] == 3


Expand All @@ -19,7 +17,7 @@ class AWSV4SignerAsyncAuth:
AWS V4 Request Signer for Async Requests.
"""

def __init__(self, credentials, region): # type: ignore
def __init__(self, credentials, region, service="es"): # type: ignore
if not credentials:
raise ValueError("Credentials cannot be empty")
self.credentials = credentials
Expand All @@ -28,6 +26,10 @@ def __init__(self, credentials, region): # type: ignore
raise ValueError("Region cannot be empty")
self.region = region

if not service:
raise ValueError("Service name cannot be empty")
self.service = service

def __call__(self, method, url, query_string, body): # type: ignore
return self._sign_request(method, url, query_string, body) # type: ignore

Expand All @@ -37,17 +39,17 @@ def _sign_request(self, method, url, query_string, body):
:param prepared_request: unsigned headers
:return: signed headers
"""

from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest

# create an AWS request object and sign it using SigV4Auth
print("".join([url, query_string]))
aws_request = AWSRequest(
method=method,
url="".join([url, query_string]),
data=body,
)
sig_v4_auth = SigV4Auth(self.credentials, OPENSEARCH_SERVICE, self.region)

sig_v4_auth = SigV4Auth(self.credentials, self.service, self.region)
sig_v4_auth.add_auth(aws_request)

# copy the headers from AWS request object into the prepared_request
Expand Down
12 changes: 7 additions & 5 deletions opensearchpy/helpers/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

import requests

OPENSEARCH_SERVICE = "es"

PY3 = sys.version_info[0] == 3

if PY3:
Expand Down Expand Up @@ -50,7 +48,7 @@ class AWSV4SignerAuth(requests.auth.AuthBase):
AWS V4 Request Signer for Requests.
"""

def __init__(self, credentials, region): # type: ignore
def __init__(self, credentials, region, service="es"): # type: ignore
if not credentials:
raise ValueError("Credentials cannot be empty")
self.credentials = credentials
Expand All @@ -59,6 +57,10 @@ def __init__(self, credentials, region): # type: ignore
raise ValueError("Region cannot be empty")
self.region = region

if not service:
raise ValueError("Service name cannot be empty")
self.service = service

def __call__(self, request): # type: ignore
return self._sign_request(request) # type: ignore

Expand All @@ -78,9 +80,9 @@ def _sign_request(self, prepared_request): # type: ignore
aws_request = AWSRequest(
method=prepared_request.method.upper(),
url=url,
data=prepared_request.body,
)
sig_v4_auth = SigV4Auth(self.credentials, OPENSEARCH_SERVICE, self.region)

sig_v4_auth = SigV4Auth(self.credentials, self.service, self.region)
sig_v4_auth.add_auth(aws_request)

# copy the headers from AWS request object into the prepared_request
Expand Down
2 changes: 1 addition & 1 deletion out/opensearchpy/plugins/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
# compatible open source license.
#
# Modifications Copyright OpenSearch Contributors. See
# GitHub history for details.
# GitHub history for details.
74 changes: 61 additions & 13 deletions out/opensearchpy/plugins/alerting.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,67 @@
# Modifications Copyright OpenSearch Contributors. See
# GitHub history for details.

from ..client.utils import NamespacedClient as NamespacedClient, query_params as query_params
from ..client.utils import (
NamespacedClient as NamespacedClient,
query_params as query_params,
)
from typing import Any, Union

class AlertingClient(NamespacedClient):
def search_monitor(self, body, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def get_monitor(self, monitor_id, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def run_monitor(self, monitor_id, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def create_monitor(self, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def update_monitor(self, monitor_id, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def delete_monitor(self, monitor_id, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def get_destination(self, destination_id: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def create_destination(self, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def update_destination(self, destination_id, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def delete_destination(self, destination_id, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def get_alerts(self, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def acknowledge_alert(self, monitor_id, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
def search_monitor(
self, body, params: Any | None = ..., headers: Any | None = ...
) -> Union[bool, Any]: ...
def get_monitor(
self, monitor_id, params: Any | None = ..., headers: Any | None = ...
) -> Union[bool, Any]: ...
def run_monitor(
self, monitor_id, params: Any | None = ..., headers: Any | None = ...
) -> Union[bool, Any]: ...
def create_monitor(
self,
body: Any | None = ...,
params: Any | None = ...,
headers: Any | None = ...,
) -> Union[bool, Any]: ...
def update_monitor(
self,
monitor_id,
body: Any | None = ...,
params: Any | None = ...,
headers: Any | None = ...,
) -> Union[bool, Any]: ...
def delete_monitor(
self, monitor_id, params: Any | None = ..., headers: Any | None = ...
) -> Union[bool, Any]: ...
def get_destination(
self,
destination_id: Any | None = ...,
params: Any | None = ...,
headers: Any | None = ...,
) -> Union[bool, Any]: ...
def create_destination(
self,
body: Any | None = ...,
params: Any | None = ...,
headers: Any | None = ...,
) -> Union[bool, Any]: ...
def update_destination(
self,
destination_id,
body: Any | None = ...,
params: Any | None = ...,
headers: Any | None = ...,
) -> Union[bool, Any]: ...
def delete_destination(
self, destination_id, params: Any | None = ..., headers: Any | None = ...
) -> Union[bool, Any]: ...
def get_alerts(
self, params: Any | None = ..., headers: Any | None = ...
) -> Union[bool, Any]: ...
def acknowledge_alert(
self,
monitor_id,
body: Any | None = ...,
params: Any | None = ...,
headers: Any | None = ...,
) -> Union[bool, Any]: ...
15 changes: 15 additions & 0 deletions test_opensearchpy/test_async/test_asyncsigner.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,18 @@ async def test_aws_signer_async_when_credentials_is_null(self):

with pytest.raises(ValueError) as e:
assert str(e.value) == "Credentials cannot be empty"

@pytest.mark.skipif(
sys.version_info < (3, 6), reason="AWSV4SignerAsyncAuth requires python3.6+"
)
async def test_aws_signer_async_when_service_is_specified(self):
region = "us-west-2"
service = "aoss"

from opensearchpy.helpers.asyncsigner import AWSV4SignerAsyncAuth

auth = AWSV4SignerAsyncAuth(self.mock_session(), region, service)
headers = auth("GET", "http://localhost")
self.assertIn("Authorization", headers)
self.assertIn("X-Amz-Date", headers)
self.assertIn("X-Amz-Security-Token", headers)
26 changes: 26 additions & 0 deletions test_opensearchpy/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ def test_aws_signer_as_http_auth(self):
self.assertIn("X-Amz-Date", prepared_request.headers)
self.assertIn("X-Amz-Security-Token", prepared_request.headers)

@pytest.mark.skipif(
sys.version_info < (3, 6), reason="AWSV4SignerAuth requires python3.6+"
)
def test_aws_signer_when_region_is_null(self):
session = self.mock_session()

Expand All @@ -346,6 +349,9 @@ def test_aws_signer_when_region_is_null(self):
AWSV4SignerAuth(session, "")
assert str(e.value) == "Region cannot be empty"

@pytest.mark.skipif(
sys.version_info < (3, 6), reason="AWSV4SignerAuth requires python3.6+"
)
def test_aws_signer_when_credentials_is_null(self):
region = "us-west-1"

Expand All @@ -359,6 +365,26 @@ def test_aws_signer_when_credentials_is_null(self):
AWSV4SignerAuth("", region)
assert str(e.value) == "Credentials cannot be empty"

@pytest.mark.skipif(
sys.version_info < (3, 6), reason="AWSV4SignerAuth requires python3.6+"
)
def test_aws_signer_when_service_is_specified(self):
region = "us-west-1"
service = "aoss"

import requests

from opensearchpy.helpers.signer import AWSV4SignerAuth

auth = AWSV4SignerAuth(self.mock_session(), region, service)
con = RequestsHttpConnection(http_auth=auth)
prepared_request = requests.Request("GET", "http://localhost").prepare()
auth(prepared_request)
self.assertEqual(auth, con.session.auth)
self.assertIn("Authorization", prepared_request.headers)
self.assertIn("X-Amz-Date", prepared_request.headers)
self.assertIn("X-Amz-Security-Token", prepared_request.headers)

def mock_session(self):
access_key = uuid.uuid4().hex
secret_key = uuid.uuid4().hex
Expand Down

0 comments on commit 5c1c890

Please sign in to comment.