Skip to content
This repository was archived by the owner on Jan 5, 2024. It is now read-only.

Commit 10aae1e

Browse files
committed
Adds initial scripts to use rsyslog for logging in workstation
`sd-rsyslog` is the output plugin of rsyslog to be installed in /usr/sbin `sdlog.conf` is the configuration of rsyslog in /etc/rsyslog.d/ `securedrop-redis-log` is part of the Qrexec service inside of sd-log vm. This will receive the messages from any other vm, and add them into a queue in Redis. `securedrop-log-saver` will be the service inside `sd-log` VM, this will monitor the queue, and save any incoming message to the `syslog.log` file of the respective directory for each VM. It also has the Makefile for the project.
1 parent daaa709 commit 10aae1e

18 files changed

Lines changed: 420 additions & 25 deletions

MANIFEST.in

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ include README.md
33
include changelog.md
44
include build-requirements.txt
55
include requirements.txt
6-
include securedrop_log/*.py
7-
include securedrop_log/VERSION
8-
include setup.py
9-
include securedrop-log
6+
include securedrop-log*
7+
include securedrop-redis-log
108
include securedrop.Log
9+
include sd-rsyslog*
10+
include sdlog.conf
11+
include VERSION

Makefile

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
DEFAULT_GOAL: help
2+
SHELL := /bin/bash
3+
4+
# Bandit is a static code analysis tool to detect security vulnerabilities in Python applications
5+
# https://wiki.openstack.org/wiki/Security/Projects/Bandit
6+
.PHONY: bandit
7+
bandit: ## Run bandit with medium level excluding test-related folders
8+
pip install --upgrade pip && \
9+
pip install --upgrade bandit!=1.6.0 && \
10+
bandit -ll --recursive . --exclude tests,.venv
11+
12+
.PHONY: safety
13+
safety: ## Runs `safety check` to check python dependencies for vulnerabilities
14+
pip install --upgrade safety && \
15+
for req_file in `find . -type f -name '*requirements.txt'`; do \
16+
echo "Checking file $$req_file" \
17+
&& safety check --full-report -r $$req_file \
18+
&& echo -e '\n' \
19+
|| exit 1; \
20+
done
21+
22+
.PHONY: update-pip-requirements
23+
update-pip-requirements: ## Updates all Python requirements files via pip-compile.
24+
pip-compile --generate-hashes --output-file requirements.txt requirements.in
25+
26+
27+
# Explaination of the below shell command should it ever break.
28+
# 1. Set the field separator to ": ##" and any make targets that might appear between : and ##
29+
# 2. Use sed-like syntax to remove the make targets
30+
# 3. Format the split fields into $$1) the target name (in blue) and $$2) the target descrption
31+
# 4. Pass this file as an arg to awk
32+
# 5. Sort it alphabetically
33+
# 6. Format columns with colon as delimiter.
34+
.PHONY: help
35+
help: ## Print this message and exit.
36+
@printf "Makefile for developing and testing the SecureDrop Logging system.\n"
37+
@printf "Subcommands:\n\n"
38+
@awk 'BEGIN {FS = ":.*?## "} /^[0-9a-zA-Z_-]+:.*?## / {printf "\033[36m%s\033[0m : %s\n", $$1, $$2}' $(MAKEFILE_LIST) \
39+
| sort \
40+
| column -s ':' -t

README.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,47 @@ Add the following content to `/etc/qubes-rpc/securedrop.Log`
2424
/usr/sbin/securedrop-log
2525
```
2626

27-
and then place `securedrop-log` script to `/usr/sbin/` directory and make sure that
28-
it is executable.
27+
and then place `securedrop-redis-log` and `securedrop-log-saver` scripts to the
28+
virtualenv at `/opt/venvs/securedrop-log` and create links to `/usr/sbin/`
29+
directory and make sure that they are executable. This step will be automated via
30+
the Debian package.
31+
32+
33+
Copy `securedrop-log.service` file to `/usr/systemd/system` and then
34+
35+
```
36+
sudo systemctl daemon-reload
37+
sudo systemctl start redis
38+
sudo systemctl start securedrop-log
39+
```
40+
41+
To test the logging, make sure to execute `securedrop-log-saver` from a terminal in `sd-log`
42+
and check the ~/QubesIncomingLogs/vmname/syslog.log file via **tail -f**.
43+
2944

3045
### To use from any Python code in workvm
3146

47+
Put `sd-rsyslog-example.conf` file to `/etc/sd-rsyslog.conf`, make sure update
48+
it so that is shows the right **localvm** name.
49+
50+
Copy `sd-rsyslog` executable to **/usr/sbin**, and remember to `chmod +x`
51+
the binary.
52+
53+
Next, restart the rsyslog service.
54+
55+
```
56+
systemctl restart rsyslog
57+
```
58+
59+
3260
Here is an example code using Python logging
3361

3462
```Python
3563
import logging
36-
from securedrop_log import SecureDropLog
64+
import logging.handlers
3765

3866
def main():
39-
handler = SecureDropLog("workvm", "proxy-debian")
67+
handler = logging.handlers.SysLogHandler(address="/dev/log")
4068
logging.basicConfig(level=logging.DEBUG, handlers=[handler])
4169
logger = logging.getLogger("example")
4270

@@ -48,8 +76,9 @@ if __name__ == "__main__":
4876

4977
```
5078

51-
## The journalctl example
79+
Or use the logger command.
5280

53-
You will need `python3-systemd` package for the same.
81+
```
82+
logger This line should show in the syslog.log file in the sd-log file.
83+
```
5484

55-
The code is in `journal-example.py` file.

VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.0.4

build-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
redis==3.3.11 --hash=sha256:022f124431ae16ee3a3a69c8016e3e2b057b4f4e0bfa7787b6271d893890c3cc

changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 0.0.4
4+
5+
* Converts into rsyslog based logging system.
6+
37
## 0.0.3
48

59
* Fixes typos MANIFEST.in and setup.py

requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
redis==3.3.11

requirements.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#
2+
# This file is autogenerated by pip-compile
3+
# To update, run:
4+
#
5+
# pip-compile --generate-hashes --output-file=requirements.txt requirements.in
6+
#
7+
redis==3.3.11 \
8+
--hash=sha256:3613daad9ce5951e426f460deddd5caf469e08a3af633e9578fc77d362becf62 \
9+
--hash=sha256:8d0fc278d3f5e1249967cba2eb4a5632d19e45ce5c09442b8422d15ee2c22cc2

sd-rsyslog

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/opt/venvs/securedrop-log/bin/python3
2+
"""A skeleton for a Python rsyslog output plugin with error handling.
3+
Requires Python 3.
4+
5+
To integrate a plugin based on this skeleton with rsyslog, configure an
6+
'omprog' action like the following:
7+
action(type="omprog"
8+
binary="/usr/bin/myplugin.py"
9+
output="/var/log/myplugin.log"
10+
confirmMessages="on"
11+
...)
12+
13+
Licensed under the Apache License, Version 2.0 (the "License");
14+
you may not use this file except in compliance with the License.
15+
You may obtain a copy of the License at
16+
17+
http://www.apache.org/licenses/LICENSE-2.0
18+
-or-
19+
see COPYING.ASL20 in the source distribution
20+
21+
Unless required by applicable law or agreed to in writing, software
22+
distributed under the License is distributed on an "AS IS" BASIS,
23+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24+
See the License for the specific language governing permissions and
25+
limitations under the License.
26+
"""
27+
28+
import sys
29+
import os
30+
import logging
31+
import configparser
32+
from subprocess import Popen, PIPE
33+
34+
# Global definitions specific to your plugin
35+
process = None
36+
37+
class RecoverableError(Exception):
38+
"""An error that has caused the processing of the current message to
39+
fail, but does not require restarting the plugin.
40+
41+
An example of such an error would be a temporary loss of connection to
42+
a database or a server. If such an error occurs in the onMessage function,
43+
your plugin should wrap it in a RecoverableError before raising it.
44+
For example:
45+
46+
try:
47+
# code that connects to a database
48+
except DbConnectionError as e:
49+
raise RecoverableError from e
50+
51+
Recoverable errors will cause the 'omprog' action to be temporarily
52+
suspended by rsyslog, during a period that can be configured using the
53+
"action.resumeInterval" action parameter. When the action is resumed,
54+
rsyslog will resend the failed message to your plugin.
55+
"""
56+
57+
58+
def onInit():
59+
"""Do everything that is needed to initialize processing (e.g. open files,
60+
create handles, connect to systems...).
61+
"""
62+
# Apart from processing the logs received from rsyslog, you want your plugin
63+
# to be able to report its own logs in some way. This will facilitate
64+
# diagnosing problems and debugging your code. Here we set up the standard
65+
# Python logging system to output the logs to stderr. In the rsyslog
66+
# configuration, you can configure the 'omprog' action to capture the stderr
67+
# of your plugin by specifying the action's "output" parameter.
68+
logging.basicConfig(stream=sys.stderr,
69+
level=logging.WARNING,
70+
format='%(asctime)s %(levelname)s %(message)s')
71+
72+
# This is an example of a debug log. (Note that for debug logs to be
73+
# emitted you must set 'level' to logging.DEBUG above.)
74+
logging.debug("onInit called")
75+
76+
77+
global process
78+
if not os.path.exists("/etc/sd-rsyslog.conf"):
79+
print("Please create the configuration file at /etc/sd-rsyslog.conf", file=sys.stderr)
80+
sys.exit(1)
81+
config = configparser.ConfigParser()
82+
config.read('/etc/sd-rsyslog.conf')
83+
logvmname = config['sd-rsyslog']['remotevm']
84+
localvmname = config['sd-rsyslog']['localvm']
85+
process = Popen(
86+
["/usr/lib/qubes/qrexec-client-vm", logvmname, "securedrop.Log"],
87+
stdin=PIPE,
88+
stdout=PIPE,
89+
stderr=PIPE,
90+
)
91+
process.stdin.write(localvmname.encode("utf-8"))
92+
process.stdin.write(b"\n")
93+
process.stdin.flush()
94+
95+
96+
def onMessage(msg):
97+
"""Process one log message received from rsyslog (e.g. send it to a
98+
database). If this function raises an error, the message will be retried
99+
by rsyslog.
100+
101+
Args:
102+
msg (str): the log message. Does NOT include a trailing newline.
103+
104+
Raises:
105+
RecoverableError: If a recoverable error occurs. The message will be
106+
retried without restarting the plugin.
107+
Exception: If a non-recoverable error occurs. The plugin will be
108+
restarted before retrying the message.
109+
"""
110+
logging.debug("onMessage called")
111+
112+
# For illustrative purposes, this plugin skeleton appends the received logs
113+
# to a file. When implementing your plugin, remove the following code.
114+
global process
115+
process.stdin.write(msg.encode("utf-8"))
116+
process.stdin.write(b"\n")
117+
process.stdin.flush()
118+
119+
120+
def onExit():
121+
"""Do everything that is needed to finish processing (e.g. close files,
122+
handles, disconnect from systems...). This is being called immediately
123+
before exiting.
124+
125+
This function should not raise any error. If it does, the error will be
126+
logged as a warning and ignored.
127+
"""
128+
logging.debug("onExit called")
129+
130+
# For illustrative purposes, this plugin skeleton appends the received logs
131+
# to a file. When implementing your plugin, remove the following code.
132+
global process
133+
process.stdin.flush()
134+
135+
136+
"""
137+
-------------------------------------------------------
138+
This is plumbing that DOES NOT need to be CHANGED
139+
-------------------------------------------------------
140+
This is the main loop that receives messages from rsyslog via stdin,
141+
invokes the above entrypoints, and provides status codes to rsyslog
142+
via stdout. In most cases, modifying this code should not be necessary.
143+
"""
144+
try:
145+
onInit()
146+
except Exception as e:
147+
# If an error occurs during initialization, log it and terminate. The
148+
# 'omprog' action will eventually restart the program.
149+
logging.exception("Initialization error, exiting program")
150+
sys.exit(1)
151+
152+
# Tell rsyslog we are ready to start processing messages:
153+
print("OK", flush=True)
154+
155+
endedWithError = False
156+
try:
157+
line = sys.stdin.readline()
158+
while line:
159+
line = line.rstrip('\n')
160+
try:
161+
onMessage(line)
162+
status = "OK"
163+
except RecoverableError as e:
164+
# Any line written to stdout that is not a status code will be
165+
# treated as a recoverable error by 'omprog', and cause the action
166+
# to be temporarily suspended. In this skeleton, we simply return
167+
# a one-line representation of the Python exception. (If debugging
168+
# is enabled in rsyslog, this line will appear in the debug logs.)
169+
status = repr(e)
170+
# We also log the complete exception to stderr (or to the logging
171+
# handler(s) configured in doInit, if any).
172+
logging.exception(e)
173+
174+
# Send the status code (or the one-line error message) to rsyslog:
175+
print(status, flush=True)
176+
line = sys.stdin.readline()
177+
178+
except Exception:
179+
# If a non-recoverable error occurs, log it and terminate. The 'omprog'
180+
# action will eventually restart the program.
181+
logging.exception("Unrecoverable error, exiting program")
182+
endedWithError = True
183+
184+
try:
185+
onExit()
186+
except Exception:
187+
logging.warning("Exception ignored in onExit", exc_info=True)
188+
189+
if endedWithError:
190+
sys.exit(1)
191+
else:
192+
sys.exit(0)
193+

sd-rsyslog-example.conf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[sd-rsyslog]
2+
remotevm = sd-log
3+
localvm = sd-svs

0 commit comments

Comments
 (0)