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

Commit ecf33a1

Browse files
committed
Dataclass for passing around test config data
1 parent ee0f218 commit ecf33a1

7 files changed

Lines changed: 199 additions & 196 deletions

tests/bot-entrypoint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def load_env():
2828
"bin/keybaseca service &"
2929
) % (shlex.quote(path)))
3030
# Sleep so keybaseca has time to start
31-
time.sleep(2)
31+
time.sleep(3)
3232
return "OK"
3333

3434
if __name__ == '__main__':

tests/tests/conftest.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
import pytest
22

3-
from lib import UtilitiesLib
4-
from lib import SUBTEAM, SUBTEAM_SECONDARY, USERNAME, BOT_USERNAME, EXPECTED_HASH
3+
from lib import TestConfig, run_command, clear_keys, clear_local_config
54

65
@pytest.fixture(autouse=True)
76
def run_around_tests():
8-
lib = UtilitiesLib(SUBTEAM, SUBTEAM_SECONDARY, USERNAME, BOT_USERNAME, EXPECTED_HASH)
9-
lib.clear_keys()
10-
lib.clear_local_config()
7+
clear_keys()
8+
clear_local_config()
119
# Calling yield triggers the test execution
1210
yield
1311

1412
def pytest_sessionfinish(session, exitstatus):
1513
# Automatically run after all tests in order to ensure that no kssh-client config files stick around
16-
lib = UtilitiesLib(SUBTEAM, SUBTEAM_SECONDARY, USERNAME, BOT_USERNAME, EXPECTED_HASH)
17-
lib.run_command(f"keybase fs rm /keybase/team/{lib.subteam}.ssh/kssh-client.config || true" )
18-
lib.run_command(f"keybase fs rm /keybase/team/{lib.subteam}.ssh.staging/kssh-client.config || true" )
19-
lib.run_command(f"keybase fs rm /keybase/team/{lib.subteam}.ssh.prod/kssh-client.config || true" )
20-
lib.run_command(f"keybase fs rm /keybase/team/{lib.subteam}.ssh.root_everywhere/kssh-client.config || true" )
21-
lib.run_command(f"keybase fs rm /keybase/team/{lib.subteam_secondary}/kssh-client.config || true" )
14+
tc = TestConfig.getDefaultTestConfig()
15+
run_command(f"keybase fs rm /keybase/team/{tc.subteam}.ssh/kssh-client.config || true" )
16+
run_command(f"keybase fs rm /keybase/team/{tc.subteam}.ssh.staging/kssh-client.config || true" )
17+
run_command(f"keybase fs rm /keybase/team/{tc.subteam}.ssh.prod/kssh-client.config || true" )
18+
run_command(f"keybase fs rm /keybase/team/{tc.subteam}.ssh.root_everywhere/kssh-client.config || true" )
19+
run_command(f"keybase fs rm /keybase/team/{tc.subteam_secondary}/kssh-client.config || true" )

tests/tests/lib.py

Lines changed: 100 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,113 +4,123 @@
44
import signal
55
import subprocess
66
import time
7+
from typing import List
78

89
import requests
910

10-
SUBTEAM = os.environ['SUBTEAM']
11-
SUBTEAM_SECONDARY = os.environ['SUBTEAM_SECONDARY']
12-
USERNAME = os.environ['KSSH_USERNAME']
13-
BOT_USERNAME = os.environ['BOT_USERNAME']
11+
def getDefaultExpectedHash() -> bytes:
12+
# "uniquestring" is stored in /etc/unique of the SSH server. We then run the command `sha1sum /etc/unique` via kssh
13+
# and assert that the output contains the sha1 hash of uniquestring. This checks to make sure the command given to
14+
# kssh is actually executing on the remote server.
15+
return hashlib.sha1(b"uniquestring").hexdigest().encode('utf-8')
1416

15-
# "uniquestring" is stored in /etc/unique of the SSH server. We then run the command `sha1sum /etc/unique` via kssh
16-
# and assert that the output contains the sha1 hash of uniquestring. This checks to make sure the command given to
17-
# kssh is actually executing on the remote server.
18-
EXPECTED_HASH = hashlib.sha1(b"uniquestring").hexdigest().encode('utf-8')
17+
class TestConfig:
18+
# Not actually a test class so mark it to be skipped
19+
__test__ = False
1920

20-
class UtilitiesLib:
2121
def __init__(self, subteam, subteam_secondary, username, bot_username, expected_hash):
2222
self.subteam = subteam
2323
self.subteam_secondary = subteam_secondary
2424
self.username = username
2525
self.bot_username = bot_username
2626
self.expected_hash = expected_hash
2727

28-
def run_command(self, cmd, timeout=10):
29-
# In order to properly run a command with a timeout and shell=True, we use Popen with a shell and group all child
30-
# processes so we can kill all of them. See:
31-
# - https://stackoverflow.com/questions/36952245/subprocess-timeout-failure
32-
# - https://stackoverflow.com/questions/4789837/how-to-terminate-a-python-subprocess-launched-with-shell-true
33-
with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, preexec_fn=os.setsid) as process:
34-
try:
35-
stdout, stderr = process.communicate(timeout=timeout)
36-
if process.returncode != 0:
37-
raise subprocess.CalledProcessError(process.returncode, cmd, stdout, stderr)
38-
return stdout
39-
except subprocess.TimeoutExpired as e:
40-
os.killpg(process.pid, signal.SIGINT)
41-
print(f"Output before timeout: {process.communicate()[0]}")
42-
raise e
43-
44-
def read_file(self, filename):
45-
"""
46-
Read the contents of the given filename to a list of strings. If it is a normal file,
47-
uses the standard open() function. Otherwise, uses `keybase fs read`.
48-
:param filename: The name of the file to read
49-
:return: A list of lines in the file
50-
"""
51-
if filename.startswith("/keybase/"):
52-
return self.run_command(f"keybase fs read {filename}").splitlines()
53-
with open(filename, 'rb') as f:
54-
return f.readlines()
55-
56-
def clear_keys(self):
57-
# Clear all keys generated by kssh
28+
@staticmethod
29+
def getDefaultTestConfig():
30+
return TestConfig(
31+
os.environ['SUBTEAM'],
32+
os.environ['SUBTEAM_SECONDARY'],
33+
os.environ['KSSH_USERNAME'],
34+
os.environ['BOT_USERNAME'],
35+
getDefaultExpectedHash()
36+
)
37+
38+
def run_command(cmd: str, timeout: int=10) -> bytes:
39+
# In order to properly run a command with a timeout and shell=True, we use Popen with a shell and group all child
40+
# processes so we can kill all of them. See:
41+
# - https://stackoverflow.com/questions/36952245/subprocess-timeout-failure
42+
# - https://stackoverflow.com/questions/4789837/how-to-terminate-a-python-subprocess-launched-with-shell-true
43+
with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, preexec_fn=os.setsid) as process:
5844
try:
59-
self.run_command("rm -rf ~/.ssh/keybase-signed-key*")
60-
except subprocess.CalledProcessError:
61-
pass
62-
63-
def clear_local_config(self):
64-
# Clear kssh's local config file
65-
try:
66-
self.run_command("rm -rf ~/.ssh/kssh.config")
67-
except subprocess.CalledProcessError:
68-
pass
69-
70-
def load_env(self, filename):
71-
# Load the environment based off of the given filename which is the path to the python test script
72-
env_name = os.path.basename(filename).split(".")[0]
73-
return requests.get(f"http://ca-bot:8080/load_env?filename={env_name}").content == b"OK"
74-
75-
def assert_contains_hash(self, output):
76-
assert EXPECTED_HASH in output
77-
78-
@contextmanager
79-
def simulate_two_teams(self):
80-
# A context manager that simulates running the given function in an environment with two teams set up
81-
self.run_command(f"keybase fs read /keybase/team/{self.subteam}.ssh.staging/kssh-client.config | "
82-
f"sed 's/{self.subteam}.ssh.staging/{self.subteam_secondary}/g' | "
83-
f"sed 's/{self.bot_username}/otherbotname/g' | "
84-
f"keybase fs write /keybase/team/{self.subteam_secondary}/kssh-client.config")
85-
try:
86-
yield
87-
finally:
88-
self.run_command(f"keybase fs rm /keybase/team/{self.subteam_secondary}/kssh-client.config")
45+
stdout, stderr = process.communicate(timeout=timeout)
46+
if process.returncode != 0:
47+
raise subprocess.CalledProcessError(process.returncode, cmd, stdout, stderr)
48+
return stdout
49+
except subprocess.TimeoutExpired as e:
50+
os.killpg(process.pid, signal.SIGINT)
51+
print(f"Output before timeout: {process.communicate()[0]}")
52+
raise e
53+
54+
def read_file(filename: str) -> List[bytes]:
55+
"""
56+
Read the contents of the given filename to a list of strings. If it is a normal file,
57+
uses the standard open() function. Otherwise, uses `keybase fs read`.
58+
:param filename: The name of the file to read
59+
:return: A list of lines in the file
60+
"""
61+
if filename.startswith("/keybase/"):
62+
return run_command(f"keybase fs read {filename}").splitlines()
63+
with open(filename, 'rb') as f:
64+
return f.readlines()
65+
66+
def clear_keys():
67+
# Clear all keys generated by kssh
68+
try:
69+
run_command("rm -rf ~/.ssh/keybase-signed-key*")
70+
except subprocess.CalledProcessError:
71+
pass
72+
73+
def clear_local_config():
74+
# Clear kssh's local config file
75+
try:
76+
run_command("rm -rf ~/.ssh/kssh.config")
77+
except subprocess.CalledProcessError:
78+
pass
79+
80+
def load_env(filename: str):
81+
# Load the environment based off of the given filename which is the path to the python test script
82+
env_name = os.path.basename(filename).split(".")[0]
83+
return requests.get(f"http://ca-bot:8080/load_env?filename={env_name}").content == b"OK"
84+
85+
def assert_contains_hash(hash: bytes, output: bytes):
86+
assert hash in output
87+
88+
@contextmanager
89+
def simulate_two_teams(tc: TestConfig):
90+
# A context manager that simulates running the given function in an environment with two teams set up
91+
run_command(f"keybase fs read /keybase/team/{tc.subteam}.ssh.staging/kssh-client.config | "
92+
f"sed 's/{tc.subteam}.ssh.staging/{tc.subteam_secondary}/g' | "
93+
f"sed 's/{tc.bot_username}/otherbotname/g' | "
94+
f"keybase fs write /keybase/team/{tc.subteam_secondary}/kssh-client.config")
95+
try:
96+
yield
97+
finally:
98+
run_command(f"keybase fs rm /keybase/team/{tc.subteam_secondary}/kssh-client.config")
8999

90-
@contextmanager
91-
def outputs_audit_log(self, filename, expected_number):
92-
# A context manager that asserts that the given function triggers expected_number of audit logs to be added to '/keybase/team/team.ssh.prod/ca.log'
93-
# Note that fuse is not running in the container so this has to use `keybase fs read`
100+
@contextmanager
101+
def outputs_audit_log(tc: TestConfig, filename: str, expected_number: int):
102+
# A context manager that asserts that the given function triggers expected_number of audit logs to be added to '/keybase/team/team.ssh.prod/ca.log'
103+
# Note that fuse is not running in the container so this has to use `keybase fs read`
94104

95-
# Make a set of the lines in the audit log before we ran
96-
before_lines = set(self.read_file(filename))
105+
# Make a set of the lines in the audit log before we ran
106+
before_lines = set(read_file(filename))
97107

98-
# Then run the code inside the context manager
99-
yield
108+
# Then run the code inside the context manager
109+
yield
100110

101-
# And sleep to give KBFS some time
102-
time.sleep(1.5)
111+
# And sleep to give KBFS some time
112+
time.sleep(1.5)
103113

104-
# Then see if there are new lines using set difference. This is only safe/reasonable since we include a
105-
# timestamp in audit log lines.
106-
after_lines = set(self.read_file(filename))
107-
new_lines = after_lines - before_lines
114+
# Then see if there are new lines using set difference. This is only safe/reasonable since we include a
115+
# timestamp in audit log lines.
116+
after_lines = set(read_file(filename))
117+
new_lines = after_lines - before_lines
108118

109-
cnt = 0
110-
for line in new_lines:
111-
line = line.decode('utf-8')
112-
if line and f"Processing SignatureRequest from user={self.username}" in line and f"principals:{self.subteam}.ssh.staging,{self.subteam}.ssh.root_everywhere, expiration:+1h, pubkey:ssh-ed25519" in line:
113-
cnt += 1
119+
cnt = 0
120+
for line in new_lines:
121+
line = line.decode('utf-8')
122+
if line and f"Processing SignatureRequest from user={tc.username}" in line and f"principals:{tc.subteam}.ssh.staging,{tc.subteam}.ssh.root_everywhere, expiration:+1h, pubkey:ssh-ed25519" in line:
123+
cnt += 1
114124

115-
if cnt != expected_number:
116-
assert False, f"Found {cnt} audit log entries, expected {expected_number}! New audit logs: {new_lines}"
125+
if cnt != expected_number:
126+
assert False, f"Found {cnt} audit log entries, expected {expected_number}! New audit logs: {new_lines}"

0 commit comments

Comments
 (0)