Skip to content

Commit

Permalink
API key Authentication (#1010)
Browse files Browse the repository at this point in the history
  • Loading branch information
philkra authored and honzakral committed Sep 20, 2019
1 parent dd206ad commit c4bc2e4
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 0 deletions.
22 changes: 22 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,28 @@ description of the options.

.. _certifi: http://certifiio.readthedocs.io/en/latest/

APIKey Authentication
~~~~~~~~~~~~~~~~~~~~~~

You can configure the client to use Elasticsearch's `API Key`_ for connecting to your cluster.
Please note this authentication method has been introduced with release of Elasticsearch ``6.7.0``.

from elasticsearch import Elasticsearch

# you can use the api key tuple
es = Elasticsearch(
['node-1', 'node-2', 'node-3'],
api_key=('id', 'api_key'),
)

# or you pass the base 64 encoded token
es = Elasticsearch(
['node-1', 'node-2', 'node-3'],
api_key='base64encoded tuple',
)

.. _API Key: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html

Logging
~~~~~~~

Expand Down
12 changes: 12 additions & 0 deletions elasticsearch/connection/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import base64

from platform import python_version

Expand Down Expand Up @@ -183,3 +184,14 @@ def _raise_error(self, status_code, raw_data):

def _get_default_user_agent(self):
return "elasticsearch-py/%s (Python %s)" % (__versionstr__, python_version())

def _get_api_key_header_val(self, api_key):
"""
Check the type of the passed api_key and return the correct header value
for the `API Key authentication <https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html>`
:arg api_key, either a tuple or a base64 encoded string
"""
if isinstance(api_key, (tuple, list)):
s = "{0}:{1}".format(api_key[0], api_key[1]).encode('utf-8')
return "ApiKey " + base64.b64encode(s).decode('utf-8')
return "ApiKey " + api_key
4 changes: 4 additions & 0 deletions elasticsearch/connection/http_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class RequestsHttpConnection(Connection):
separate cert and key files (client_cert will contain only the cert)
:arg headers: any custom http headers to be add to requests
:arg cloud_id: The Cloud ID from ElasticCloud. Convient way to connect to cloud instances.
:arg api_key: optional API Key authentication as either base64 encoded string or a tuple.
Other host connection params will be ignored.
"""

Expand All @@ -52,6 +53,7 @@ def __init__(
client_key=None,
headers=None,
cloud_id=None,
api_key=None,
**kwargs
):
if not REQUESTS_AVAILABLE:
Expand Down Expand Up @@ -80,6 +82,8 @@ def __init__(
elif isinstance(http_auth, string_types):
http_auth = tuple(http_auth.split(":", 1))
self.session.auth = http_auth
if api_key is not None:
self.session.headers['authorization'] = self._get_api_key_header_val(api_key)
self.base_url = "http%s://%s:%d%s" % (
"s" if self.use_ssl else "",
host,
Expand Down
4 changes: 4 additions & 0 deletions elasticsearch/connection/http_urllib3.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class Urllib3HttpConnection(Connection):
:arg headers: any custom http headers to be add to requests
:arg http_compress: Use gzip compression
:arg cloud_id: The Cloud ID from ElasticCloud. Convient way to connect to cloud instances.
:arg api_key: optional API Key authentication as either base64 encoded string or a tuple.
Other host connection params will be ignored.
"""

Expand All @@ -96,6 +97,7 @@ def __init__(
ssl_context=None,
http_compress=False,
cloud_id=None,
api_key=None,
**kwargs
):

Expand Down Expand Up @@ -128,6 +130,8 @@ def __init__(

self.headers.setdefault("content-type", "application/json")
self.headers.setdefault("user-agent", self._get_default_user_agent())
if api_key is not None:
self.headers.setdefault('authorization', self._get_api_key_header_val(api_key))
pool_class = urllib3.HTTPConnectionPool
kw = {}

Expand Down
30 changes: 30 additions & 0 deletions test_elasticsearch/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ def test_http_cloud_id(self):
con.host, "https://0fd50f62320ed6539f6cb48e1b68.example.cloud.com:9243"
)

def test_api_key_auth(self):
# test with tuple
con = Urllib3HttpConnection(
cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n",
api_key=("elastic", "changeme1"),
)
self.assertEquals(con.headers["authorization"], "ApiKey ZWxhc3RpYzpjaGFuZ2VtZTE=")

# test with base64 encoded string
con = Urllib3HttpConnection(
cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n",
api_key="ZWxhc3RpYzpjaGFuZ2VtZTI=",
)
self.assertEquals(con.headers["authorization"], "ApiKey ZWxhc3RpYzpjaGFuZ2VtZTI=")

def test_http_compression(self):
con = Urllib3HttpConnection(http_compress=True)
self.assertTrue(con.http_compress)
Expand Down Expand Up @@ -181,6 +196,21 @@ def test_http_cloud_id(self):
con.host, "https://0fd50f62320ed6539f6cb48e1b68.example.cloud.com:9243"
)

def test_api_key_auth(self):
# test with tuple
con = RequestsHttpConnection(
cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n",
api_key=("elastic", "changeme1"),
)
self.assertEquals(con.session.headers["authorization"], "ApiKey ZWxhc3RpYzpjaGFuZ2VtZTE=")

# test with base64 encoded string
con = RequestsHttpConnection(
cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n",
api_key="ZWxhc3RpYzpjaGFuZ2VtZTI=",
)
self.assertEquals(con.session.headers["authorization"], "ApiKey ZWxhc3RpYzpjaGFuZ2VtZTI=")

def test_uses_https_if_verify_certs_is_off(self):
with warnings.catch_warnings(record=True) as w:
con = self._get_mock_connection(
Expand Down

0 comments on commit c4bc2e4

Please sign in to comment.