diff --git a/CHANGELOG.md b/CHANGELOG.md index cf1cdabc..d39ad869 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Fix sync method in Batman module (3c68cb8, PLUM Sprint 230210) - Fix cookie client session flow (#155, PLUM Sprint 230210) - Renaming resources without description (#158, PLUM Sprint 230210) +- Batman does not add nonexistent roles to Kibana users (#159, PLUM Sprint 230210) ### Features - Allow unsetting some client features (#148, PLUM Sprint 230113) diff --git a/seacatauth/batman/elk.py b/seacatauth/batman/elk.py index ec5b606c..a835b219 100644 --- a/seacatauth/batman/elk.py +++ b/seacatauth/batman/elk.py @@ -25,20 +25,22 @@ class ELKIntegration(asab.config.Configurable): """ ConfigDefaults = { - 'url': 'http://localhost:9200/', - 'username': 'elastic', - 'password': 'elastic', + "url": "http://localhost:9200/", + "username": "elastic", + "password": "elastic", - 'local_users': 'elastic kibana logstash_system beats_system remote_monitoring_user', - - 'mapped_roles_prefixes': '*/elk:', # Prefix of roles that will be transfered to Kibana + # List of elasticsearch system users + # If Seacat Auth has users with one of these usernames, it will not sync them + # to avoid interfering with kibana system users + "local_users": "elastic kibana logstash_system beats_system remote_monitoring_user", # Resources with this prefix will be mapped to Kibana users as roles # E.g.: Resource "elk:kibana-analyst" will be mapped to role "kibana-analyst" "resource_prefix": "elk:", - 'managed_role': 'seacat_managed', # 'flags' users in ElasticSearch/Kibana that is managed by us, + # This role 'flags' users in ElasticSearch/Kibana that is managed by Seacat Auth # There should be a role created in the ElasticSearch that grants no rights + "seacat_user_flag": "seacat_managed", } @@ -50,22 +52,18 @@ def __init__(self, batman_svc, config_section_name="batman:elk", config=None): self.RoleService = self.BatmanService.App.get_service("seacatauth.RoleService") self.ResourceService = self.BatmanService.App.get_service("seacatauth.ResourceService") - username = self.Config.get('username') - password = self.Config.get('password') + username = self.Config.get("username") + password = self.Config.get("password") self.BasicAuth = aiohttp.BasicAuth(username, password) - self.URL = self.Config.get('url').rstrip('/') + self.URL = self.Config.get("url").rstrip("/") self.ResourcePrefix = self.Config.get("resource_prefix") self.ELKResourceRegex = re.compile("^{}".format( re.escape(self.Config.get("resource_prefix")) )) - self.ELKSeacatFlagRole = self.Config.get("managed_role") - - # TODO: Obsolete, back compat only. Use resources instead of roles. - # - self.RolePrefixes = re.split(r"\s+", self.Config.get("mapped_roles_prefixes")) + self.ELKSeacatFlagRole = self.Config.get("seacat_user_flag") - lu = re.split(r'\s+', self.Config.get('local_users'), flags=re.MULTILINE) + lu = re.split(r"\s+", self.Config.get("local_users"), flags=re.MULTILINE) lu.append(username) self.LocalUsers = frozenset(lu) @@ -81,6 +79,7 @@ async def initialize(self): await self.sync_all() async def _initialize_resources(self): + # TODO: Remove resource if its respective kibana role has been removed """ Fetches roles from ELK and creates a Seacat Auth resource for each one of them. """ @@ -110,7 +109,7 @@ async def _initialize_resources(self): if resource_id not in existing_elk_resources: await self.ResourceService.create( resource_id, - description="Grants access to ELK role '{}.".format(role) + description="Grants access to ELK role {!r}.".format(role) ) async def sync_all(self): @@ -124,7 +123,7 @@ async def sync_all(self): async def sync(self, cred: dict, elk_resources: typing.Iterable): - username = cred.get('username') + username = cred.get("username") if username is None: # Be defensive L.info("Cannot create user: No username", struct_data={"cid": cred["_id"]}) @@ -135,29 +134,27 @@ async def sync(self, cred: dict, elk_resources: typing.Iterable): return json = { - 'enabled': cred.get('suspended', False) is not True, + "enabled": cred.get("suspended", False) is not True, # Generate technical password - 'password': self.BatmanService.generate_password(cred['_id']), + "password": self.BatmanService.generate_password(cred["_id"]), - 'metadata': { + "metadata": { # We are managed by SeaCat Auth - 'seacatauth': True + "seacatauth": True }, } - v = cred.get('email') + v = cred.get("email") if v is not None: - json['email'] = v + json["email"] = v - v = cred.get('full_name') + v = cred.get("full_name") if v is not None: - json['full_name'] = v + json["full_name"] = v - elk_roles = set( - self.ELKSeacatFlagRole, # Add a role that marks users managed by Seacat Auth - ) + elk_roles = {self.ELKSeacatFlagRole} # Add a role that marks users managed by Seacat Auth # Get authz dict authz = await build_credentials_authz(self.TenantService, self.RoleService, cred["_id"]) @@ -176,17 +173,11 @@ async def sync(self, cred: dict, elk_resources: typing.Iterable): for resource in user_resources.intersection(elk_resources) ) - # ELK roles from tenants - for tenant in authz: - if tenant == "*": - continue - elk_roles.add("tenant_{}".format(tenant)) - json["roles"] = list(elk_roles) try: async with aiohttp.ClientSession(auth=self.BasicAuth) as session: - async with session.post('{}/_xpack/security/user/{}'.format(self.URL, username), json=json) as resp: + async with session.post("{}/_xpack/security/user/{}".format(self.URL, username), json=json) as resp: if resp.status == 200: # Everything is alright here pass