1212import uuid
1313from typing import List , Optional , Union
1414
15- from urllib .parse import urljoin
1615import requests
1716from elasticsearch import Elasticsearch
1817
1918_context = threading .local ()
2019
2120
22- class Kibana ( object ) :
21+ class Kibana :
2322 """Wrapper around the Kibana SIEM APIs."""
2423
25- CACHED = False
26-
27- def __init__ (self , cloud_id = None , kibana_url = None , verify = True , elasticsearch = None , space = None ):
24+ def __init__ (self , cloud_id = None , kibana_url = None , api_key = None , verify = True , elasticsearch = None , space = None ):
2825 """"Open a session to the platform."""
2926 self .authenticated = False
27+
3028 self .session = requests .Session ()
3129 self .session .verify = verify
30+
31+ if api_key :
32+ self .session .headers .update (
33+ {
34+ "kbn-xsrf" : "true" ,
35+ "Authorization" : f"ApiKey { api_key } " ,
36+ }
37+ )
38+
3239 self .verify = verify
3340
3441 self .cloud_id = cloud_id
@@ -37,9 +44,6 @@ def __init__(self, cloud_id=None, kibana_url=None, verify=True, elasticsearch=No
3744 self .space = space if space and space .lower () != 'default' else None
3845 self .status = None
3946
40- self .provider_name = None
41- self .provider_type = None
42-
4347 if self .cloud_id :
4448 self .cluster_name , cloud_info = self .cloud_id .split (":" )
4549 self .domain , self .es_uuid , self .kibana_uuid = \
@@ -50,18 +54,24 @@ def __init__(self, cloud_id=None, kibana_url=None, verify=True, elasticsearch=No
5054
5155 kibana_url_from_cloud = f"https://{ self .kibana_uuid } .{ self .domain } :9243"
5256 if self .kibana_url and self .kibana_url != kibana_url_from_cloud :
53- raise ValueError (f'kibana_url provided ({ self .kibana_url } ) does not match url derived from cloud_id '
54- f'{ kibana_url_from_cloud } ' )
57+ raise ValueError (
58+ f'kibana_url provided ({ self .kibana_url } ) does not match url derived from cloud_id '
59+ f'{ kibana_url_from_cloud } '
60+ )
5561 self .kibana_url = kibana_url_from_cloud
56-
5762 self .elastic_url = f"https://{ self .es_uuid } .{ self .domain } :9243"
5863
59- self .provider_name = 'cloud-basic'
60- self .provider_type = 'basic'
61-
6264 self .session .headers .update ({'Content-Type' : "application/json" , "kbn-xsrf" : str (uuid .uuid4 ())})
6365 self .elasticsearch = elasticsearch
6466
67+ if not self .elasticsearch and self .elastic_url :
68+ self .elasticsearch = Elasticsearch (
69+ hosts = [self .elastic_url ],
70+ api_key = api_key ,
71+ verify_certs = self .verify ,
72+ )
73+ self .elasticsearch .info ()
74+
6575 if not verify :
6676 from requests .packages .urllib3 .exceptions import \
6777 InsecureRequestWarning
@@ -75,7 +85,7 @@ def version(self):
7585 return self .status .get ("version" , {}).get ("number" )
7686
7787 @staticmethod
78- def ndjson_file_data_prep (lines : List [dict ], filename : str ) -> ( dict , str ) :
88+ def ndjson_file_data_prep (lines : List [dict ], filename : str ) -> tuple [ dict , str ] :
7989 """Prepare a request for an ndjson file upload to Kibana."""
8090 data = ('\n ' .join (json .dumps (r ) for r in lines ) + '\n ' )
8191 boundary = '----JustAnotherBoundary'
@@ -144,63 +154,6 @@ def delete(self, uri, params=None, error=True, **kwargs):
144154 """Perform an HTTP DELETE."""
145155 return self .request ('DELETE' , uri , params = params , error = error , ** kwargs )
146156
147- def login (self , kibana_username , kibana_password , provider_type = None , provider_name = None ):
148- """Authenticate to Kibana using the API to update our cookies."""
149- payload = {'username' : kibana_username , 'password' : kibana_password }
150- path = '/internal/security/login'
151-
152- try :
153- self .post (path , data = payload , error = True , verbose = False )
154- except requests .HTTPError as e :
155- # 7.10 changed the structure of the auth data
156- # providers dictated by Kibana configs in:
157- # https://www.elastic.co/guide/en/kibana/current/security-settings-kb.html#authentication-security-settings
158- # more details: https://discuss.elastic.co/t/kibana-7-10-login-issues/255201/2
159- if e .response .status_code == 400 and '[undefined]' in e .response .text :
160- provider_type = provider_type or self .provider_type or 'basic'
161- provider_name = provider_name or self .provider_name or 'basic'
162-
163- payload = {
164- 'params' : payload ,
165- 'currentURL' : '' ,
166- 'providerType' : provider_type ,
167- 'providerName' : provider_name
168- }
169- self .post (path , data = payload , error = True )
170- else :
171- raise
172-
173- # Kibana will authenticate against URLs which contain invalid spaces
174- if self .space :
175- self .verify_space (self .space )
176-
177- self .authenticated = True
178- self .status = self .get ("/api/status" )
179-
180- # create ES and force authentication
181- if self .elasticsearch is None and self .elastic_url is not None :
182- self .elasticsearch = Elasticsearch (hosts = [self .elastic_url ], http_auth = (kibana_username , kibana_password ),
183- verify_certs = self .verify )
184- self .elasticsearch .info ()
185-
186- # make chaining easier
187- return self
188-
189- def add_cookie (self , cookie ):
190- """Add cookie to be used for auth (such as from an SSO session)."""
191- # https://www.elastic.co/guide/en/kibana/7.10/security-settings-kb.html#security-session-and-cookie-settings
192- self .session .headers ['sid' ] = cookie
193- self .session .cookies .set ('sid' , cookie )
194- self .status = self .get ('/api/status' )
195- self .authenticated = True
196-
197- def add_api_key (self , api_key : str ) -> bool :
198- """Add an API key to be used for auth."""
199- self .session .headers ['Authorization' ] = f'ApiKey { api_key } '
200- self .status = self .get ('/api/status' )
201- self .authenticated = True
202- return bool (self .status )
203-
204157 def logout (self ):
205158 """Quit the current session."""
206159 try :
0 commit comments