11# -*- coding: utf-8 -*-
22"""
3- © 2019 Sebastian Wagner <wagner @cert.at>
3+ © 2019-2021 nic.at GmbH <intelmq-team @cert.at>
44
5- SPDX-License-Identifier: AGPL-3.0
5+ SPDX-License-Identifier: AGPL-3.0-only
66
77Sets up an intelmq environment after installation or upgrade by
88 * creating needed directories
99 * set intelmq as owner for those
1010 * providing example configuration files if not already existing
1111
12+ If intelmq-api is installed, the similar steps are performed:
13+ * creates needed directories
14+ * sets the webserver as group for them
15+ * sets group write permissions
16+
1217Reasoning:
1318Pip does not (and cannot) create `/opt/intelmq`/user-given ROOT_DIR, as described in
1419https://github.com/certtools/intelmq/issues/819
1520"""
1621import argparse
17- import glob
1822import os
1923import shutil
24+ import stat
2025import sys
2126import pkg_resources
2227
23- from pwd import getpwuid
28+ from grp import getgrnam
29+ from pathlib import Path
30+ from pwd import getpwnam
31+ from typing import Optional
32+
33+ try :
34+ import intelmq_api
35+ except ImportError :
36+ intelmq_api = None
2437
38+ from termstyle import red
2539from intelmq import (CONFIG_DIR , DEFAULT_LOGGING_PATH , ROOT_DIR , VAR_RUN_PATH ,
2640 VAR_STATE_PATH , BOTS_FILE , STATE_FILE_PATH )
2741from intelmq .bin .intelmqctl import IntelMQController
2842
2943
30- def intelmqsetup (ownership = True , state_file = STATE_FILE_PATH ):
31- if os .geteuid () != 0 and ownership :
32- sys .exit ('You need to run this program as root (for setting file ownership)' )
44+ MANAGER_CONFIG_DIR = Path (CONFIG_DIR ) / 'manager/'
45+ FILE_OUTPUT_PATH = Path (VAR_STATE_PATH ) / 'file-output/'
3346
47+
48+ def basic_checks (skip_ownership ):
49+ if os .geteuid () != 0 and not skip_ownership :
50+ sys .exit (red ('You need to run this program as root for setting file ownership!' ))
3451 if not ROOT_DIR :
35- sys .exit ('Not a pip-installation of IntelMQ, nothing to initialize.' )
36-
37- create_dirs = ('%s/file-output' % VAR_STATE_PATH ,
38- VAR_RUN_PATH ,
39- DEFAULT_LOGGING_PATH ,
40- CONFIG_DIR )
41- for create_dir in create_dirs :
42- if not os .path .isdir (create_dir ):
43- os .makedirs (create_dir , mode = 0o755 ,
44- exist_ok = True )
45- print ('Created directory %r.' % create_dir )
46-
47- example_confs = glob .glob (pkg_resources .resource_filename ('intelmq' , 'etc/*.conf' ))
52+ sys .exit (red ('Not a pip-installation of IntelMQ, nothing to initialize.' ))
53+
54+ if skip_ownership :
55+ return
56+ try :
57+ getpwnam ('intelmq' )
58+ except KeyError :
59+ sys .exit (red ("User 'intelmq' does not exist. Please create it and then re-run this program." ))
60+ try :
61+ getgrnam ('intelmq' )
62+ except KeyError :
63+ sys .exit (red ("Group 'intelmq' does not exist. Please create it and then re-run this program." ))
64+
65+
66+ def create_directory (directory : str , octal_mode : int ):
67+ directory = Path (directory )
68+ readable_mode = stat .filemode (octal_mode )
69+ if not directory .is_dir ():
70+ directory .mkdir (mode = octal_mode , exist_ok = True , parents = True )
71+ print (f'Created directory { directory !s} with permissions { readable_mode } .' )
72+ else :
73+ current_mode = directory .stat ().st_mode
74+ if current_mode != octal_mode :
75+ current_mode_readable = stat .filemode (current_mode )
76+ print (f'Fixed wrong permissions of { directory !s} : { current_mode_readable !r} -> { readable_mode !r} .' )
77+ directory .chmod (octal_mode )
78+
79+
80+ def change_owner (file : str , owner = None , group = None , log : bool = True ):
81+ if owner and Path (file ).owner () != owner :
82+ if log :
83+ print (f'Fixing owner of { file !s} .' )
84+ shutil .chown (file , user = owner )
85+ if group and Path (file ).group () != group :
86+ if log :
87+ print (f'Fixing group of { file !s} .' )
88+ shutil .chown (file , group = group )
89+
90+
91+ def find_webserver_user ():
92+ candidates = ('www-data' , 'wwwrun' , 'httpd' , 'apache' )
93+ for candidate in candidates :
94+ try :
95+ getpwnam (candidate )
96+ except KeyError :
97+ pass
98+ else :
99+ print (f'Detected webserver username { candidate !r} .' )
100+ return candidate
101+
102+
103+ def intelmqsetup_core (ownership = True , state_file = STATE_FILE_PATH ):
104+ create_directory (FILE_OUTPUT_PATH , 0o40755 )
105+ create_directory (VAR_RUN_PATH , 0o40755 )
106+ create_directory (DEFAULT_LOGGING_PATH , 0o40755 )
107+ create_directory (CONFIG_DIR , 0o40775 )
108+
109+ example_confs = Path (pkg_resources .resource_filename ('intelmq' , 'etc' )).glob ('*.conf' )
48110 for example_conf in example_confs :
49- fname = os .path .split (example_conf )[- 1 ]
50- if os .path .exists (os .path .join (CONFIG_DIR , fname )):
51- print ('Not overwriting existing %r with example.' % fname )
111+ fname = Path (example_conf ).name
112+ destination_file = Path (CONFIG_DIR ) / fname
113+ if destination_file .exists ():
114+ print (f'Not overwriting existing { fname !r} with example.' )
115+ log_ownership_change = True
52116 else :
53117 shutil .copy (example_conf , CONFIG_DIR )
54- print ('Use example %r.' % fname )
55-
56- print ('Writing BOTS file.' )
57- shutil .copy (pkg_resources .resource_filename ('intelmq' , 'bots/BOTS' ),
58- BOTS_FILE )
118+ print (f'Installing example { fname !r} to { CONFIG_DIR } .' )
119+ log_ownership_change = False # For installing the new files, we don't need to inform the admin that the permissions have been "fixed"
120+ if ownership :
121+ change_owner (destination_file , owner = 'intelmq' , group = 'intelmq' , log = log_ownership_change )
122+
123+ if Path (BOTS_FILE ).is_symlink ():
124+ print ('Skip writing BOTS file as it is a link.' )
125+ else :
126+ print ('Writing BOTS file.' )
127+ shutil .copy (pkg_resources .resource_filename ('intelmq' , 'bots/BOTS' ),
128+ BOTS_FILE )
59129
60130 if ownership :
61131 print ('Setting intelmq as owner for it\' s directories.' )
62132 for obj in (CONFIG_DIR , DEFAULT_LOGGING_PATH , ROOT_DIR , VAR_RUN_PATH ,
63- VAR_STATE_PATH , VAR_STATE_PATH + 'file-output' ):
64- if getpwuid (os .stat (obj ).st_uid ).pw_name != 'intelmq' :
65- shutil .chown (obj , user = 'intelmq' )
133+ VAR_STATE_PATH , FILE_OUTPUT_PATH ):
134+ change_owner (obj , owner = 'intelmq' )
66135
67- print ('Calling `intelmqctl upgrade-config to update/create state file' )
136+ print ('Calling `intelmqctl upgrade-config` to update/create state file. ' )
68137 controller = IntelMQController (interactive = False , no_file_logging = True ,
69138 drop_privileges = False )
70139 controller .upgrade_conf (state_file = state_file , no_backup = True )
71140
72141
142+ def intelmqsetup_api (ownership : bool = True , webserver_user : Optional [str ] = None ):
143+ if ownership :
144+ change_owner (CONFIG_DIR , group = 'intelmq' )
145+
146+ # Manager configuration directory
147+ create_directory (MANAGER_CONFIG_DIR , 0o40775 )
148+ if ownership :
149+ change_owner (MANAGER_CONFIG_DIR , group = 'intelmq' )
150+
151+ intelmq_group = getgrnam ('intelmq' )
152+ webserver_user = webserver_user or find_webserver_user ()
153+ if webserver_user not in intelmq_group .gr_mem :
154+ sys .exit (red (f"Webserver user { webserver_user } is not a member of the 'intelmq' group. "
155+ f"Please add it with: 'usermod -aG intelmq { webserver_user } '." ))
156+
157+ print ('Setup of intelmq-api successful.' )
158+
159+
73160def main ():
74161 parser = argparse .ArgumentParser ("Set's up directories and example "
75162 "configurations for IntelMQ." )
@@ -78,9 +165,17 @@ def main():
78165 parser .add_argument ('--state-file' ,
79166 help = 'The state file location to use.' ,
80167 default = STATE_FILE_PATH )
168+ parser .add_argument ('--webserver-user' ,
169+ help = 'The webserver to use instead of auto-detection.' )
81170 args = parser .parse_args ()
82- intelmqsetup (ownership = not args .skip_ownership ,
83- state_file = args .state_file )
171+
172+ basic_checks (skip_ownership = args .skip_ownership )
173+ intelmqsetup_core (ownership = not args .skip_ownership ,
174+ state_file = args .state_file )
175+ if intelmq_api :
176+ print ('Running setup for intelmq-api.' )
177+ intelmqsetup_api (ownership = not args .skip_ownership ,
178+ webserver_user = args .webserver_user )
84179
85180
86181if __name__ == '__main__' :
0 commit comments