Browse Source

graylog: Add script to map users to their roles

Philipp Fromme 1 year ago
parent
commit
a5824dc3ed

+ 10 - 0
graylog/graylog-api-scripts.conf.tmpl

@@ -0,0 +1,10 @@
+[DEFAULTS]
+token={{ graylog_config['api_token'] }}
+
+[LDAP]
+server_uri={{ graylog_config['server_uri'] }}
+bind_dn={{ graylog_config['bind_dn'] }}
+bind_passwd={{ graylog_config['bind_passwd'] }}
+search_base_dn={{ graylog_config['search_base_dn'] }}
+ldap_group_search={{ graylog_config['ldap_group_search'] }}
+search_attribute={{ graylog_config['search_attribute'] }}

+ 145 - 0
graylog/graylog-group-mapping

@@ -0,0 +1,145 @@
+#!/usr/bin/python3
+# graylog group mapping script
+# Copyright (C) 2022 Philipp Fromme
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import argparse
+import configparser
+import logging
+import json
+import requests
+import ldap
+
+general_config_path = "/etc/graylog-api-scripts.conf"
+general_config = configparser.ConfigParser()
+general_config.read(general_config_path)
+api_token = general_config['DEFAULTS']['token']
+api_token_password = "token"
+api_url_base = "http://127.0.0.1:9000/api/"
+headers = {"Content-Type": "application/json", "X-Requested-By": "cli"}
+
+server_uri = general_config['LDAP']['server_uri']
+bind_dn = general_config['LDAP']['bind_dn']
+bind_passwd = general_config['LDAP']['bind_passwd']
+search_base_dn = general_config['LDAP']['search_base_dn']
+ldap_group_search = general_config['LDAP']['ldap_group_search']
+search_attribute = general_config['LDAP']['search_attribute']
+
+logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%b %d %H:%M:%S',
+                    level='WARNING')
+LOGGER = logging.getLogger()
+
+def connect_to_ldap(server_uri):
+    try:
+        conn = ldap.initialize(server_uri)
+        conn.protocol_version = ldap.VERSION3
+        conn.set_option(ldap.OPT_REFERRALS, 0)
+        conn.set_option(ldap.OPT_DEBUG_LEVEL, 255)
+        conn.simple_bind_s(bind_dn, bind_passwd)
+        return conn
+    except ldap.LDAPError as e:
+        raise Exception("Failed to connet to server {}: {}".format(server_uri, e))
+
+def get_ldap_groups(conn):
+    ldap_result_id = conn.search(search_base_dn, ldap.SCOPE_SUBTREE, ldap_group_search, [search_attribute])
+    result_type, result_data = conn.result(ldap_result_id, 1)
+    return result_data
+
+def get_users():
+    api_url = '{}users'.format(api_url_base)
+    response = requests.get(api_url, headers=headers, auth=(api_token, api_token_password))
+    if response.status_code == 200:
+        return json.loads(response.content.decode('utf-8'))
+    else:
+        return None
+
+def change_user_roles(user_id, roles):
+    api_url = '{}users/{}'.format(api_url_base, user_id)
+    json_data = {"roles": roles}
+    response = requests.put(api_url, json=json_data, headers=headers, auth=(api_token, api_token_password))
+    if response.status_code == 204:
+        return True
+    else:
+        return False
+
+def delete_user(user_id):
+    api_url = '{}users/id/{}'.format(api_url_base, user_id)
+    response = requests.delete(api_url, headers=headers, auth=(api_token, api_token_password))
+    if response.status_code == 204:
+        return True
+    else:
+        return False
+
+def main():
+    parser = argparse.ArgumentParser(description="Map LDAP Groups to Graylog Groups")
+    parser.add_argument("--level", "-l", help="Set the log level", default="WARNING")
+    args = parser.parse_args()
+
+    LOGGER.setLevel(args.level)
+
+    config_path = "/etc/graylog-group-mapping.conf"
+    config = configparser.ConfigParser()
+    config.read(config_path)
+    default_role = config['DEFAULTS']['default-role']
+    role_mapping = {}
+    # create a mapping from the config file to later give users their new_roles
+    for mapping in config['GROUP-MAPPING']:
+        role_mapping[mapping] = config['GROUP-MAPPING'][mapping]
+    groupMembers = {}
+    conn = connect_to_ldap(server_uri)
+    groups = get_ldap_groups(conn)
+    # sort what we found in ldap by throwing away everything
+    # besides groups and who is a member in them
+    for group in groups:
+        name = group[0].split(",")[0].split("=")[1]
+        members = group[1]
+        groupMembers[name] = []
+        if search_attribute in members:
+            members = members[search_attribute]
+            for member in members:
+                groupMembers[name].append(member.decode().split(",")[0].split("=")[1])
+    # get users in graylog and iterate over them
+    user_list = get_users()
+    if user_list is not None:
+        for user in user_list['users']:
+            if user['external'] == False:
+                continue
+            user_id = user['id']
+            username = user['username']
+            roles = user['roles']
+            # check first if user is member of any specified group
+            in_config_group = False
+            for group in groupMembers:
+                if username in groupMembers[group]:
+                    in_config_group = True
+                    break
+            if in_config_group:
+                new_roles = [default_role]
+                for group in role_mapping:
+                    if username in groupMembers[group]:
+                        new_roles.append(role_mapping[group])
+                new_roles = set(new_roles)
+                if new_roles != set(roles):
+                    new_roles = list(new_roles)
+                    LOGGER.warning("%s has roles %s and gets new roles %s", username, roles, new_roles)
+                    change_user_roles(user_id, new_roles)
+                else:
+                    LOGGER.info("%s: nothing changed", username)
+            else:
+                LOGGER.warning("%s not in any config group, therefore deleting this graylog user", username)
+                delete_user(user_id)
+
+if __name__ == "__main__":
+    main()

+ 8 - 0
graylog/graylog-group-mapping.conf.tmpl

@@ -0,0 +1,8 @@
+[DEFAULTS]
+default-role={{ graylog_config['default_role'] }}
+
+[GROUP-MAPPING]
+{%- for key, value in graylog_config['role_mapping'].items() %}
+{{ key }}={{ value }}
+{%- endfor %}
+

+ 1 - 0
graylog/graylog-group-mapping.cron

@@ -0,0 +1 @@
+*/5 * * * * root /usr/local/sbin/graylog-group-mapping

+ 31 - 1
graylog/init.sls

@@ -21,7 +21,7 @@ graylog-server:
   pkg.installed:
     - pkgs:
       - graylog-server
-      - graylog-enterprise-plugins
+      - python3-ldap
     - require:
       - pkgrepo: graylog-repo
       - service: mongodb
@@ -43,6 +43,15 @@ graylog-server:
     - require:
       - pkg: graylog-server
 
+# Default connection config for graylog api scripts
+/etc/graylog-api-scripts.conf:
+  file.managed:
+    - source: salt://graylog/graylog-api-scripts.conf.tmpl
+    - mode: 600
+    - template: jinja
+    - context:
+      graylog_config: {{ graylog_config }}
+
 # Install cronjob and notification script
 /etc/cron.d/graylog-system-notifications:
   file.managed:
@@ -55,3 +64,24 @@ graylog-server:
     - template: jinja
     - context:
       graylog_config: {{ graylog_config }}
+
+# Install cronjob, group mapping script and config files
+/etc/graylog-group-mapping.conf:
+  file.managed:
+    - source: salt://graylog/graylog-group-mapping.conf.tmpl
+    - mode: 600
+    - template: jinja
+    - context:
+      graylog_config: {{ graylog_config }}
+
+/etc/cron.d/graylog-group-mapping:
+  file.managed:
+    - source: salt://graylog/graylog-group-mapping.cron
+
+/usr/local/sbin/graylog-group-mapping:
+  file.managed:
+    - source: salt://graylog/graylog-group-mapping
+    - mode: 700
+    - template: jinja
+    - context:
+      graylog_config: {{ graylog_config }}