Skip to content

Commit

Permalink
Log warning if secrets file is world readable
Browse files Browse the repository at this point in the history
  • Loading branch information
johannaengland committed Aug 21, 2024
1 parent 168286a commit 1ba3a5b
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ old-events/
polldevs.cf
zino-state.json
zino.toml

# avoid accidental commit of the secrets file
secrets
1 change: 1 addition & 0 deletions changelog.d/280.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Log warning if `secrets` file is world-readable
9 changes: 9 additions & 0 deletions src/zino/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import asyncio
import logging
import os
import stat
from functools import wraps
from ipaddress import ip_address
from time import time
Expand Down Expand Up @@ -63,3 +65,10 @@ def wrapper(*args, **kwargs):
return wrapper

return actual_decorator


def file_is_world_readable(file: str) -> bool:
"""Returns a boolean value indicating if a file is readable by other users than its owner"""
st_mode = getattr(os.stat(path=file), "st_mode", None)

return bool(st_mode & stat.S_IROTH)
12 changes: 12 additions & 0 deletions src/zino/zino.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
link_traps,
logged_traps,
)
from zino.utils import file_is_world_readable

STATE_DUMP_JOB_ID = "zino.dump_state"
# Never try to dump state more often than this:
Expand All @@ -46,6 +47,17 @@ def main():
)
state.config = load_config(args)
apply_logging_config(state.config.logging)

try:
secrets_file = state.config.authentication.file
if file_is_world_readable(secrets_file):
_log.warning(
f"Secrets file {secrets_file} is world-readable. Please ensure that it is only readable by the user that runs the zino process."
)
except OSError as e:
_log.fatal(e)
sys.exit(1)

state.state = state.ZinoState.load_state_from_file(state.config.persistence.file) or state.ZinoState()
init_event_loop(args)

Expand Down
17 changes: 16 additions & 1 deletion tests/utils_test.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import logging
import os
import stat
from ipaddress import IPv4Address, IPv6Address
from unittest.mock import AsyncMock, MagicMock

import aiodns
import pytest

from zino.utils import log_time_spent, parse_ip, reverse_dns
from zino.utils import file_is_world_readable, log_time_spent, parse_ip, reverse_dns


class TestParseIP:
Expand Down Expand Up @@ -102,3 +104,16 @@ def mock_dnsresolver(monkeypatch) -> AsyncMock:
mock_dnsresolver = AsyncMock()
monkeypatch.setattr("zino.utils.aiodns.DNSResolver", lambda loop: mock_dnsresolver)
return mock_dnsresolver


class TestFileIsReadableByOthers:
def test_return_true_if_file_is_world_readable(self, secrets_file):
assert file_is_world_readable(secrets_file)

def test_return_if_file_is_only_readable_by_owner(self, tmp_path):
name = tmp_path / "owner-secrets"
with open(name, "w") as conf:
conf.write("""user1 password123""")
os.chmod(name, mode=stat.S_IRWXU)

assert not file_is_world_readable(name)

0 comments on commit 1ba3a5b

Please sign in to comment.