Skip to content

Commit 7a750f4

Browse files
author
Sebastian Wagner
committed
Merge remote-tracking branch 'upstream/pr/1787' into maintenance
2 parents 9d024f3 + e5c1713 commit 7a750f4

File tree

3 files changed

+136
-36
lines changed

3 files changed

+136
-36
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ CHANGELOG
3636
- Add missing newlines at end of various test input files (PR#1785 by Sebastian Wagner, fixes #1777).
3737

3838
### Tools
39+
- `intelmqsetup`:
40+
- Also cover required directory layout and file permissions for `intelmq-api` (PR#1787 by Sebastian Wagner, fixes #1783).
41+
- `intelmqctl`:
42+
- Do not log an error message if logging to file is explicitly disabled, e.g. in calls from `intelmsetup`. The error message would not be useful for the user and is not necessary.
3943

4044
### Contrib
4145

intelmq/bin/intelmqctl.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ def __init__(self, interactive: bool = False, return_type: str = "python", quiet
710710

711711
try:
712712
if no_file_logging:
713-
raise FileNotFoundError
713+
raise FileNotFoundError('Logging to file disabled.')
714714
logger = utils.log('intelmqctl', log_level=self.logging_level,
715715
log_format_stream=utils.LOG_FORMAT_SIMPLE,
716716
logging_level_stream=logging_level_stream,
@@ -720,7 +720,8 @@ def __init__(self, interactive: bool = False, return_type: str = "python", quiet
720720
logger = utils.log('intelmqctl', log_level=self.logging_level, log_path=False,
721721
log_format_stream=utils.LOG_FORMAT_SIMPLE,
722722
logging_level_stream=logging_level_stream)
723-
logger.error('Not logging to file: %s', exc)
723+
if not isinstance(exc, FileNotFoundError) and exc.args[0] != 'Logging to file disabled.':
724+
logger.error('Not logging to file: %s', exc)
724725
self.logger = logger
725726
if defaults_loading_exc:
726727
self.logger.exception('Loading the defaults configuration failed!',

intelmq/bin/intelmqsetup.py

Lines changed: 129 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,162 @@
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
77
Sets 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+
1217
Reasoning:
1318
Pip does not (and cannot) create `/opt/intelmq`/user-given ROOT_DIR, as described in
1419
https://github.com/certtools/intelmq/issues/819
1520
"""
1621
import argparse
17-
import glob
1822
import os
1923
import shutil
24+
import stat
2025
import sys
2126
import 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
2539
from intelmq import (CONFIG_DIR, DEFAULT_LOGGING_PATH, ROOT_DIR, VAR_RUN_PATH,
2640
VAR_STATE_PATH, BOTS_FILE, STATE_FILE_PATH)
2741
from 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+
73160
def 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

86181
if __name__ == '__main__':

0 commit comments

Comments
 (0)