updated tests

This commit is contained in:
Eugene Pankov 2022-11-11 17:00:12 +01:00
parent 8087179ea0
commit b7a08996d8
No known key found for this signature in database
GPG key ID: 5896FCBBDD1CF4F4
27 changed files with 1881 additions and 830 deletions

View file

@ -43,7 +43,7 @@ jobs:
- name: Run - name: Run
working-directory: tests working-directory: tests
run: | run: |
TIMEOUT=120 poetry run ./run.sh RUST_LOG=debug TIMEOUT=120 poetry run ./run.sh
cargo llvm-cov --no-run --hide-instantiations --lcov > coverage.lcov cargo llvm-cov --no-run --hide-instantiations --lcov > coverage.lcov
- name: SonarCloud Scan - name: SonarCloud Scan

92
tests/api_client.py Normal file
View file

@ -0,0 +1,92 @@
import requests
from contextlib import contextmanager
@contextmanager
def api_admin_session(url):
session = requests.Session()
session.verify = False
response = session.post(
f"{url}/@warpgate/api/auth/login",
json={
"username": "admin",
"password": "123",
},
)
assert response.status_code // 100 == 2
yield session
def assert_response(response, code):
if response.status_code != code:
print(response.text)
assert response.status_code == code
def api_list_users(url, session):
response = session.get(
f"{url}/@warpgate/admin/api/users",
)
assert_response(response, 200)
return response.json()
def api_create_target(url, session, config):
response = session.post(
f"{url}/@warpgate/admin/api/targets",
json=config,
)
assert_response(response, 201)
return response.json()
def api_create_role(url, session, config):
response = session.post(
f"{url}/@warpgate/admin/api/roles",
json=config,
)
assert_response(response, 201)
return response.json()
def api_create_user(url, session, config):
response = session.post(
f"{url}/@warpgate/admin/api/users",
json=config,
)
assert_response(response, 201)
return response.json()
def api_add_role_to_target(url, session, target_id, role_id):
response = session.post(
f"{url}/@warpgate/admin/api/targets/{target_id}/roles/{role_id}",
)
assert_response(response, 201)
def api_add_role_to_user(url, session, user_id, role_id):
response = session.post(
f"{url}/@warpgate/admin/api/users/{user_id}/roles/{role_id}",
)
assert_response(response, 201)
def api_create_ticket(url, session, username, target_name):
response = session.post(
f"{url}/@warpgate/api/auth/login",
json={
"username": "admin",
"password": "123",
},
)
assert response.status_code // 100 == 2
response = session.post(
f"{url}/@warpgate/admin/api/tickets",
json={
"username": username,
"target_name": target_name,
},
)
assert response.status_code == 201
return response.json()["secret"]

View file

@ -9,13 +9,14 @@ import subprocess
import tempfile import tempfile
import urllib3 import urllib3
import uuid import uuid
from contextlib import contextmanager
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from textwrap import dedent from textwrap import dedent
from typing import List from typing import List
from .util import alloc_port from .util import alloc_port, wait_port
from .test_http_common import http_common_wg_port, echo_server_port # noqa from .test_http_common import echo_server_port # noqa
cargo_root = Path(os.getcwd()).parent cargo_root = Path(os.getcwd()).parent
@ -33,6 +34,15 @@ class Child:
stop_timeout: float stop_timeout: float
@dataclass
class WarpgateProcess:
config_path: Path
process: subprocess.Popen
http_port: int
ssh_port: int
mysql_port: int
class ProcessManager: class ProcessManager:
children: List[Child] children: List[Child]
@ -67,14 +77,14 @@ class ProcessManager:
def start_ssh_server(self, trusted_keys=[]): def start_ssh_server(self, trusted_keys=[]):
port = alloc_port() port = alloc_port()
data_dir = self.ctx.tmpdir / f'sshd-{uuid.uuid4()}' data_dir = self.ctx.tmpdir / f"sshd-{uuid.uuid4()}"
data_dir.mkdir(parents=True) data_dir.mkdir(parents=True)
authorized_keys_path = data_dir / 'authorized_keys' authorized_keys_path = data_dir / "authorized_keys"
authorized_keys_path.write_text('\n'.join(trusted_keys)) authorized_keys_path.write_text("\n".join(trusted_keys))
config_path = data_dir / 'sshd_config' config_path = data_dir / "sshd_config"
config_path.write_text( config_path.write_text(
dedent( dedent(
f'''\ f"""\
Port 22 Port 22
AuthorizedKeysFile {authorized_keys_path} AuthorizedKeysFile {authorized_keys_path}
AllowAgentForwarding yes AllowAgentForwarding yes
@ -87,7 +97,7 @@ class ProcessManager:
PermitRootLogin yes PermitRootLogin yes
HostKey /ssh-keys/id_ed25519 HostKey /ssh-keys/id_ed25519
Subsystem sftp /usr/lib/ssh/sftp-server Subsystem sftp /usr/lib/ssh/sftp-server
''' """
) )
) )
data_dir.chmod(0o700) data_dir.chmod(0o700)
@ -96,17 +106,17 @@ class ProcessManager:
self.start( self.start(
[ [
'docker', "docker",
'run', "run",
'--rm', "--rm",
'-p', "-p",
f'{port}:22', f"{port}:22",
'-v', "-v",
f'{data_dir}:{data_dir}', f"{data_dir}:{data_dir}",
'-v', "-v",
f'{os.getcwd()}/ssh-keys:/ssh-keys', f"{os.getcwd()}/ssh-keys:/ssh-keys",
'warpgate-e2e-ssh-server', "warpgate-e2e-ssh-server",
'-f', "-f",
str(config_path), str(config_path),
] ]
) )
@ -115,99 +125,120 @@ class ProcessManager:
def start_mysql_server(self): def start_mysql_server(self):
port = alloc_port() port = alloc_port()
self.start( self.start(
[ ["docker", "run", "--rm", "-p", f"{port}:3306", "warpgate-e2e-mysql-server"]
'docker',
'run',
'--rm',
'-p',
f'{port}:3306',
'warpgate-e2e-mysql-server'
]
) )
return port return port
def start_wg(self, config='', args=None): def start_wg(
ssh_port = alloc_port() self,
http_port = alloc_port() config="",
mysql_port = alloc_port() args=None,
data_dir = self.ctx.tmpdir / f'wg-data-{uuid.uuid4()}' share_with: WarpgateProcess = None,
data_dir.mkdir(parents=True) stderr=None,
keys_dir = data_dir / 'keys' stdout=None,
keys_dir.mkdir(parents=True) ) -> WarpgateProcess:
for k in [ args = args or ["run"]
Path('ssh-keys/wg/client-ed25519'),
Path('ssh-keys/wg/client-rsa'), if share_with:
Path('ssh-keys/wg/host-ed25519'), config_path = share_with.config_path
Path('ssh-keys/wg/host-rsa'), ssh_port = share_with.ssh_port
Path('certs/tls.certificate.pem'), mysql_port = share_with.mysql_port
Path('certs/tls.key.pem'), http_port = share_with.http_port
]: else:
shutil.copy(k, keys_dir / k.name) ssh_port = alloc_port()
config_path = data_dir / 'warpgate.yaml' http_port = alloc_port()
config_path.write_text( mysql_port = alloc_port()
dedent( data_dir = self.ctx.tmpdir / f"wg-data-{uuid.uuid4()}"
f'''\ data_dir.mkdir(parents=True)
ssh:
enable: true keys_dir = data_dir / "ssh-keys"
listen: 0.0.0.0:{ssh_port} keys_dir.mkdir(parents=True)
keys: {keys_dir} for k in [
host_key_verification: auto_accept Path("ssh-keys/wg/client-ed25519"),
http: Path("ssh-keys/wg/client-rsa"),
enable: true Path("ssh-keys/wg/host-ed25519"),
listen: 0.0.0.0:{http_port} Path("ssh-keys/wg/host-rsa"),
certificate: {keys_dir}/tls.certificate.pem ]:
key: {keys_dir}/tls.key.pem shutil.copy(k, keys_dir / k.name)
mysql:
enable: true for k in [
listen: 0.0.0.0:{mysql_port} Path("certs/tls.certificate.pem"),
certificate: {keys_dir}/tls.certificate.pem Path("certs/tls.key.pem"),
key: {keys_dir}/tls.key.pem ]:
recordings: shutil.copy(k, data_dir / k.name)
enable: false
roles: config_path = data_dir / "warpgate.yaml"
- name: role
- name: admin def run(args, env={}):
- name: warpgate:admin return self.start(
''' [
) + config f"{cargo_root}/target/llvm-cov-target/debug/warpgate",
"--config",
str(config_path),
*args,
],
cwd=cargo_root,
env={
**os.environ,
"LLVM_PROFILE_FILE": f"{cargo_root}/target/llvm-cov-target/warpgate-%m.profraw",
**env,
},
stop_signal=signal.SIGINT,
stop_timeout=5,
stderr=stderr,
stdout=stdout,
)
if not share_with:
p = run(
[
"unattended-setup",
"--ssh-port",
str(ssh_port),
"--http-port",
str(http_port),
"--mysql-port",
str(mysql_port),
"--data-path",
data_dir,
],
env={"WARPGATE_ADMIN_PASSWORD": "123"},
)
p.communicate()
assert p.returncode == 0
import yaml
config = yaml.safe_load(config_path.open())
config["ssh"]["host_key_verification"] = "auto_accept"
with config_path.open("w") as f:
yaml.safe_dump(config, f)
p = run(args)
return WarpgateProcess(
process=p,
config_path=config_path,
ssh_port=ssh_port,
http_port=http_port,
mysql_port=mysql_port,
) )
args = args or ['run']
p = self.start(
[
f'{cargo_root}/target/llvm-cov-target/debug/warpgate',
'--config',
str(config_path),
*args,
],
cwd=cargo_root,
env={
**os.environ,
'LLVM_PROFILE_FILE': f'{cargo_root}/target/llvm-cov-target/warpgate-%m.profraw',
},
stop_signal=signal.SIGINT,
stop_timeout=5,
)
return p, {
'ssh': ssh_port,
'http': http_port,
'mysql': mysql_port,
}
def start_ssh_client(self, *args, password=None, **kwargs): def start_ssh_client(self, *args, password=None, **kwargs):
preargs = [] preargs = []
if password: if password:
preargs = ['sshpass', '-p', password] preargs = ["sshpass", "-p", password]
p = self.start( p = self.start(
[ [
*preargs, *preargs,
'ssh', "ssh",
# '-v', # '-v',
'-o', "-o",
'IdentitiesOnly=yes', "IdentitiesOnly=yes",
'-o', "-o",
'StrictHostKeychecking=no', "StrictHostKeychecking=no",
'-o', "-o",
'UserKnownHostsFile=/dev/null', "UserKnownHostsFile=/dev/null",
*args, *args,
], ],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -217,24 +248,26 @@ class ProcessManager:
def start(self, args, stop_timeout=3, stop_signal=signal.SIGTERM, **kwargs): def start(self, args, stop_timeout=3, stop_signal=signal.SIGTERM, **kwargs):
p = subprocess.Popen(args, **kwargs) p = subprocess.Popen(args, **kwargs)
self.children.append(Child(process=p, stop_signal=stop_signal, stop_timeout=stop_timeout)) self.children.append(
Child(process=p, stop_signal=stop_signal, stop_timeout=stop_timeout)
)
return p return p
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
def timeout(): def timeout():
t = os.getenv('TIMEOUT', '10') t = os.getenv("TIMEOUT", "10")
return int(t) return int(t)
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
def ctx(): def ctx():
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
ctx = Context(tmpdir=Path(tmpdir)) ctx = Context(tmpdir=Path(tmpdir))
yield ctx yield ctx
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
def processes(ctx, report_generation): def processes(ctx, report_generation):
mgr = ProcessManager(ctx) mgr = ProcessManager(ctx)
try: try:
@ -243,38 +276,48 @@ def processes(ctx, report_generation):
mgr.stop() mgr.stop()
@pytest.fixture(scope='session', autouse=True) @pytest.fixture(scope="session", autouse=True)
def report_generation(): def report_generation():
# subprocess.call(['cargo', 'llvm-cov', 'clean', '--workspace']) # subprocess.call(['cargo', 'llvm-cov', 'clean', '--workspace'])
subprocess.check_call(['cargo', 'llvm-cov', 'run', '--no-report', '--', '--version'], cwd=cargo_root) subprocess.check_call(
["cargo", "llvm-cov", "run", "--no-report", "--", "--version"], cwd=cargo_root
)
yield yield
# subprocess.check_call(['cargo', 'llvm-cov', '--no-run', '--hide-instantiations', '--html'], cwd=cargo_root) # subprocess.check_call(['cargo', 'llvm-cov', '--no-run', '--hide-instantiations', '--html'], cwd=cargo_root)
@pytest.fixture(scope="session")
def shared_wg(processes: ProcessManager):
wg = processes.start_wg()
wait_port(wg.http_port, for_process=wg.process, recv=False)
wait_port(wg.ssh_port, for_process=wg.process)
yield wg
# ---- # ----
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
def wg_c_ed25519_pubkey(): def wg_c_ed25519_pubkey():
return Path(os.getcwd()) / 'ssh-keys/wg/client-ed25519.pub' return Path(os.getcwd()) / "ssh-keys/wg/client-ed25519.pub"
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
def otp_key_base64(): def otp_key_base64():
return 'Isj0ekwF1YsKW8VUUQiU4awp/9dMnyMcTPH9rlr1OsE=' return "Isj0ekwF1YsKW8VUUQiU4awp/9dMnyMcTPH9rlr1OsE="
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
def otp_key_base32(): def otp_key_base32():
return 'ELEPI6SMAXKYWCS3YVKFCCEU4GWCT76XJSPSGHCM6H624WXVHLAQ' return "ELEPI6SMAXKYWCS3YVKFCCEU4GWCT76XJSPSGHCM6H624WXVHLAQ"
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
def password_123_hash(): def password_123_hash():
return '$argon2id$v=19$m=4096,t=3,p=1$cxT6YKZS7r3uBT4nPJXEJQ$GhjTXyGi5vD2H/0X8D3VgJCZSXM4I8GiXRzl4k5ytk0' return "$argon2id$v=19$m=4096,t=3,p=1$cxT6YKZS7r3uBT4nPJXEJQ$GhjTXyGi5vD2H/0X8D3VgJCZSXM4I8GiXRzl4k5ytk0"
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
subprocess.call('chmod 600 ssh-keys/id*', shell=True) subprocess.call("chmod 600 ssh-keys/id*", shell=True)

172
tests/poetry.lock generated
View file

@ -35,6 +35,29 @@ cffi = ">=1.1"
tests = ["pytest (>=3.2.1,!=3.3.0)"] tests = ["pytest (>=3.2.1,!=3.3.0)"]
typecheck = ["mypy"] typecheck = ["mypy"]
[[package]]
name = "black"
version = "22.10.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2022.6.15" version = "2022.6.15"
@ -230,6 +253,14 @@ category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "21.3" version = "21.3"
@ -261,6 +292,26 @@ ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"]
gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"]
invoke = ["invoke (>=1.3)"] invoke = ["invoke (>=1.3)"]
[[package]]
name = "pathspec"
version = "0.10.1"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "platformdirs"
version = "2.5.3"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.0.0" version = "1.0.0"
@ -375,6 +426,14 @@ tomli = ">=1.0.0"
[package.extras] [package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "pyyaml"
version = "6.0"
description = "YAML parser and emitter for Python"
category = "main"
optional = false
python-versions = ">=3.6"
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.28.1" version = "2.28.1"
@ -420,6 +479,14 @@ category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[[package]]
name = "typed-ast"
version = "1.5.4"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.3.0" version = "4.3.0"
@ -494,7 +561,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "227309a9ca00d2440517db21c07a19c67e657eb43ce7cd0264d5cd07f6138d39" content-hash = "3a5e8c2efbb8a9dbd12ebfbcd4216085d17f66692f64e81f61f3feb4d3214680"
[metadata.files] [metadata.files]
atomicwrites = [ atomicwrites = [
@ -517,6 +584,29 @@ bcrypt = [
{file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"}, {file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"},
{file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"}, {file = "bcrypt-3.2.2.tar.gz", hash = "sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb"},
] ]
black = [
{file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"},
{file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"},
{file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"},
{file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"},
{file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"},
{file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
{file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"},
{file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"},
{file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"},
{file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"},
{file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"},
{file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"},
{file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
{file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"},
{file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"},
{file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"},
{file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"},
{file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"},
{file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"},
{file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
]
certifi = [ certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
@ -705,6 +795,10 @@ mccabe = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
] ]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
packaging = [ packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
@ -713,6 +807,14 @@ paramiko = [
{file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"},
{file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"},
] ]
pathspec = [
{file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
{file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
]
platformdirs = [
{file = "platformdirs-2.5.3-py3-none-any.whl", hash = "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb"},
{file = "platformdirs-2.5.3.tar.gz", hash = "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"},
]
pluggy = [ pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
@ -791,6 +893,48 @@ pytest = [
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
] ]
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
{file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
{file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
{file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
requests = [ requests = [
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
@ -807,6 +951,32 @@ tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
] ]
typed-ast = [
{file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
{file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
{file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
{file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
{file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
{file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
{file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
{file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
{file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
{file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
{file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
{file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
{file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
{file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
{file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
typing-extensions = [ typing-extensions = [
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},

View file

@ -14,10 +14,12 @@ Flask = "^2.2.1"
requests = "^2.28.1" requests = "^2.28.1"
flask-sock = "^0.5.2" flask-sock = "^0.5.2"
websocket-client = "^1.3.3" websocket-client = "^1.3.3"
PyYAML = "^6.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
flake8 = "^5.0.2" flake8 = "^5.0.2"
pytest = "^7.1.2" pytest = "^7.1.2"
black = "^22.10.0"
[tool.pytest.ini_options] [tool.pytest.ini_options]
minversion = "6.0" minversion = "6.0"

View file

@ -5,5 +5,5 @@ rm target/llvm-cov-target/* || true
cargo llvm-cov clean --workspace cargo llvm-cov clean --workspace
cargo llvm-cov --no-report --workspace --all-features -- --skip agent cargo llvm-cov --no-report --workspace --all-features -- --skip agent
cd tests cd tests
RUST_BACKTRACE=1 poetry run pytest -s $@ RUST_BACKTRACE=1 poetry run pytest $@
cargo llvm-cov --no-run --hide-instantiations --html cargo llvm-cov --no-run --hide-instantiations --html

View file

@ -1,46 +1,96 @@
from urllib.parse import unquote
from uuid import uuid4
import requests import requests
from tests.conftest import WarpgateProcess
from .util import wait_port from .api_client import (
api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_add_role_to_target,
)
from .test_http_common import * # noqa
class Test: class Test:
def test_basic( def test_basic(
self, self,
http_common_wg_port, echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": "user",
"credentials": [
{
"kind": "Password",
"hash": "123",
}
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}'
response = session.get(f'{url}/?warpgate-target=echo', allow_redirects=False) response = session.get(
f"{url}/?warpgate-target={echo_target['name']}", allow_redirects=False
)
assert response.status_code == 307 assert response.status_code == 307
redirect = response.headers['location'] redirect = response.headers["location"]
assert redirect == '/@warpgate#/login?next=%2F%3Fwarpgate%2Dtarget%3Decho' print(unquote(redirect))
assert (
unquote(redirect)
== f"/@warpgate#/login?next=/?warpgate-target={echo_target['name']}"
)
response = session.get(f'{url}/@warpgate/api/info').json() response = session.get(f"{url}/@warpgate/api/info").json()
assert response['username'] is None assert response["username"] is None
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'user', "username": user["username"],
'password': '123', "password": "123",
}, },
) )
assert response.status_code == 201 assert response.status_code == 201
response = session.get(f'{url}/@warpgate/api/info').json() response = session.get(f"{url}/@warpgate/api/info").json()
assert response['username'] == 'user' assert response["username"] == user["username"]
response = session.get( response = session.get(
f'{url}/some/path?a=b&warpgate-target=echo&c=d', allow_redirects=False f"{url}/some/path?a=b&warpgate-target={echo_target['name']}&c=d",
allow_redirects=False,
) )
print(response)
print(response.text)
assert response.status_code == 200 assert response.status_code == 200
assert response.json()['method'] == 'GET' assert response.json()["method"] == "GET"
assert response.json()['path'] == '/some/path' assert response.json()["path"] == "/some/path"
assert response.json()['args']['a'] == 'b' assert response.json()["args"]["a"] == "b"
assert response.json()['args']['c'] == 'd' assert response.json()["args"]["c"] == "d"

View file

@ -1,80 +1,39 @@
import pytest import pytest
import threading import threading
from textwrap import dedent
from .util import alloc_port from .util import alloc_port
@pytest.fixture(scope='session') @pytest.fixture(scope="session")
def http_common_wg_port(processes, echo_server_port, password_123_hash, otp_key_base64):
_, wg_ports = processes.start_wg(
dedent(
f'''\
targets:
- name: echo
allow_roles: [role]
http:
url: http://localhost:{echo_server_port}
- name: baddomain
allow_roles: [role]
http:
url: http://localhostfoobar
- name: warpgate:admin
allow_roles: [admin]
web_admin: {{}}
users:
- username: admin
roles: [admin, warpgate:admin]
credentials:
- type: password
hash: '{password_123_hash}'
- username: user
roles: [role]
credentials:
- type: password
hash: '{password_123_hash}'
- username: userwithotp
roles: [role]
credentials:
- type: password
hash: '{password_123_hash}'
- type: otp
key: {otp_key_base64}
require:
http: [password, otp]
'''
),
)
yield wg_ports['http']
@pytest.fixture(scope='session')
def echo_server_port(): def echo_server_port():
from flask import Flask, request, jsonify, redirect from flask import Flask, request, jsonify, redirect
from flask_sock import Sock from flask_sock import Sock
app = Flask(__name__) app = Flask(__name__)
sock = Sock(app) sock = Sock(app)
@app.route('/set-cookie') @app.route("/set-cookie")
def set_cookie(): def set_cookie():
response = jsonify({}) response = jsonify({})
response.set_cookie('cookie', 'value') response.set_cookie("cookie", "value")
return response return response
@app.route('/redirect/<path:url>') @app.route("/redirect/<path:url>")
def r(url): def r(url):
return redirect(url) return redirect(url)
@app.route('/', defaults={'path': ''}) @app.route("/", defaults={"path": ""})
@app.route('/<path:path>') @app.route("/<path:path>")
def echo(path): def echo(path):
return jsonify({ return jsonify(
'method': request.method, {
'args': request.args, "method": request.method,
'path': request.path, "args": request.args,
}) "path": request.path,
}
)
@sock.route('/socket') @sock.route("/socket")
def ws_echo(ws): def ws_echo(ws):
while True: while True:
data = ws.receive() data = ws.receive()

View file

@ -1,42 +1,66 @@
from textwrap import dedent from uuid import uuid4
from .api_client import api_admin_session, api_create_target
from .conftest import ProcessManager, WarpgateProcess
class Test: class Test:
def test_success( def test_success(
self, self,
processes, processes: ProcessManager,
echo_server_port, echo_server_port,
timeout, timeout,
shared_wg: WarpgateProcess,
): ):
proc, _ = processes.start_wg( url = f"https://localhost:{shared_wg.http_port}"
config=dedent( with api_admin_session(url) as session:
f'''\ echo_target = api_create_target(
users: [] url,
targets: session,
- name: target {
allow_roles: [role] "name": f"echo-{uuid4()}",
http: "options": {
url: http://localhost:{echo_server_port} "kind": "Http",
''' "url": f"http://localhost:{echo_server_port}",
), "tls": {
args=['test-target', 'target'], "mode": "Disabled",
) "verify": False,
},
},
},
)
proc = processes.start_wg(
share_with=shared_wg,
args=["test-target", echo_target["name"]],
).process
proc.wait(timeout=timeout) proc.wait(timeout=timeout)
assert proc.returncode == 0 assert proc.returncode == 0
def test_fail_no_connection(self, processes, timeout): def test_fail_no_connection(
proc, _ = processes.start_wg( self, processes: ProcessManager, timeout, shared_wg: WarpgateProcess
config=dedent( ):
'''\ url = f"https://localhost:{shared_wg.http_port}"
users: [] with api_admin_session(url) as session:
targets: echo_target = api_create_target(
- name: target url,
allow_roles: [role] session,
http: {
url: http://localhostbaddomain "name": f"echo-{uuid4()}",
''' "options": {
), "kind": "Http",
args=['test-target', 'target'], "url": "http://localhostbaddomain",
) "tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
proc = processes.start_wg(
share_with=shared_wg,
args=["test-target", echo_target["name"]],
).process
proc.wait(timeout=timeout) proc.wait(timeout=timeout)
assert proc.returncode != 0 assert proc.returncode != 0

View file

@ -1,31 +1,75 @@
import requests import requests
from uuid import uuid4
from .api_client import (
from .util import wait_port api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_add_role_to_target,
)
from .conftest import WarpgateProcess
from .test_http_common import * # noqa
class TestHTTPCookies: class TestHTTPCookies:
def test( def test(
self, self,
http_common_wg_port, echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
}
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}' headers = {"Host": f"localhost:{shared_wg.http_port}"}
headers = {'Host': f'localhost:{http_common_wg_port}'}
session.post( session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'user', "username": user["username"],
'password': '123', "password": "123",
}, },
headers=headers, headers=headers,
) )
response = session.get(f'{url}/set-cookie?warpgate-target=echo', headers=headers) session.get(
print(response.headers) f"{url}/set-cookie?warpgate-target={echo_target['name']}", headers=headers
)
cookies = session.cookies.get_dict() cookies = session.cookies.get_dict()
assert cookies['cookie'] == 'value' assert cookies["cookie"] == "value"

View file

@ -1,30 +1,75 @@
import requests import requests
from uuid import uuid4
from .util import wait_port from .api_client import (
api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_add_role_to_target,
)
from .conftest import WarpgateProcess
from .test_http_common import * # noqa
class TestHTTPRedirects: class TestHTTPRedirects:
def test( def test(
self, self,
http_common_wg_port, shared_wg: WarpgateProcess,
echo_server_port, echo_server_port,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
}
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}' headers = {"Host": f"localhost:{shared_wg.http_port}"}
headers = {'Host': f'localhost:{http_common_wg_port}'}
session.post( session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'user', "username": user["username"],
'password': '123', "password": "123",
}, },
headers=headers, headers=headers,
) )
response = session.get(f'{url}/redirect/http://localhost:{echo_server_port}/test?warpgate-target=echo', headers=headers, allow_redirects=False) response = session.get(
print(response.headers) f"{url}/redirect/http://localhost:{echo_server_port}/test?warpgate-target={echo_target['name']}",
headers=headers,
allow_redirects=False,
)
assert response.headers['location'] == '/test' assert response.headers["location"] == "/test"

View file

@ -1,35 +1,80 @@
import requests import requests
from uuid import uuid4
from .api_client import (
from .util import wait_port api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_add_role_to_target,
)
from .conftest import WarpgateProcess
from .test_http_common import * # noqa
class Test: class Test:
def test( def test(
self, self,
http_common_wg_port, echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
}
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}'
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'user', "username": user["username"],
'password': '123', "password": "123",
}, },
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
response = session.get( response = session.get(
f'{url}/some/path?a=b&warpgate-target=echo&c=d', allow_redirects=False f"{url}/some/path?a=b&warpgate-target={echo_target['name']}&c=d",
allow_redirects=False,
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
assert response.json()['path'] == '/some/path' assert response.json()["path"] == "/some/path"
response = session.post(f'{url}/@warpgate/api/auth/logout') response = session.post(f"{url}/@warpgate/api/auth/logout")
response = session.get(f'{url}/?warpgate-target=echo', allow_redirects=False) response = session.get(
f"{url}/?warpgate-target={echo_target['name']}", allow_redirects=False
)
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2

View file

@ -1,78 +1,173 @@
import requests import requests
import pyotp import pyotp
from base64 import b64decode
from uuid import uuid4
from .api_client import (
from .util import wait_port api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_add_role_to_target,
)
from .conftest import WarpgateProcess
from .test_http_common import * # noqa
class TestHTTPUserAuthOTP: class TestHTTPUserAuthOTP:
def test_auth_otp_success( def test_auth_otp_success(
self, self,
http_common_wg_port,
otp_key_base32, otp_key_base32,
otp_key_base64,
echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
{
"kind": "Totp",
"key": list(b64decode(otp_key_base64)),
},
],
"credential_policy": {
"http": ["Password", "Totp"],
},
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}'
totp = pyotp.TOTP(otp_key_base32) totp = pyotp.TOTP(otp_key_base32)
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'userwithotp', "username": user["username"],
'password': '123', "password": "123",
}, },
) )
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2
response = session.get( response = session.get(
f'{url}/some/path?a=b&warpgate-target=echo&c=d', allow_redirects=False f"{url}/some/path?a=b&warpgate-target={echo_target['name']}&c=d",
allow_redirects=False,
) )
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/otp', f"{url}/@warpgate/api/auth/otp",
json={ json={
'otp': totp.now(), "otp": totp.now(),
}, },
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
response = session.get( response = session.get(
f'{url}/some/path?a=b&warpgate-target=echo&c=d', allow_redirects=False f"{url}/some/path?a=b&warpgate-target={echo_target['name']}&c=d",
allow_redirects=False,
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
assert response.json()['path'] == '/some/path' assert response.json()["path"] == "/some/path"
def test_auth_otp_fail( def test_auth_otp_fail(
self, self,
http_common_wg_port, otp_key_base64,
echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
{
"kind": "Totp",
"key": list(b64decode(otp_key_base64)),
},
],
"credential_policy": {
"http": ["PublicKey", "Totp"],
},
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}'
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'userwithotp', "username": user["username"],
'password': '123', "password": "123",
}, },
) )
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/otp', f"{url}/@warpgate/api/auth/otp",
json={ json={
'otp': '00000000', "otp": "00000000",
}, },
) )
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2
response = session.get( response = session.get(
f'{url}/some/path?a=b&warpgate-target=echo&c=d', allow_redirects=False f"{url}/some/path?a=b&warpgate-target={echo_target['name']}&c=d",
allow_redirects=False,
) )
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2

View file

@ -1,56 +1,135 @@
import requests import requests
from uuid import uuid4
from .api_client import (
from .util import wait_port api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_add_role_to_target,
)
from .conftest import WarpgateProcess
from .test_http_common import * # noqa
class TestHTTPUserAuthPassword: class TestHTTPUserAuthPassword:
def test_auth_password_success( def test_auth_password_success(
self, self,
http_common_wg_port, echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}'
response = session.get(f'{url}/?warpgate-target=echo', allow_redirects=False) response = session.get(
f"{url}/?warpgate-target={echo_target['name']}", allow_redirects=False
)
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'user', "username": user["username"],
'password': '123', "password": "123",
}, },
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
response = session.get( response = session.get(
f'{url}/some/path?a=b&warpgate-target=echo&c=d', allow_redirects=False f"{url}/some/path?a=b&warpgate-target={echo_target['name']}&c=d",
allow_redirects=False,
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
assert response.json()['path'] == '/some/path' assert response.json()["path"] == "/some/path"
def test_auth_password_fail( def test_auth_password_fail(
self, self,
http_common_wg_port, echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}'
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'user', "username": user["username"],
'password': '321321', "password": "321321",
}, },
) )
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2
response = session.get( response = session.get(
f'{url}/some/path?a=b&warpgate-target=echo&c=d', allow_redirects=False f"{url}/some/path?a=b&warpgate-target={echo_target['name']}&c=d",
allow_redirects=False,
) )
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2

View file

@ -1,17 +1,76 @@
import requests import requests
from uuid import uuid4
from .util import create_ticket, wait_port from .api_client import (
api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_create_ticket,
api_add_role_to_target,
)
from .conftest import WarpgateProcess
from .test_http_common import * # noqa
class TestHTTPUserAuthTicket: class TestHTTPUserAuthTicket:
def test_auth_password_success( def test_auth_password_success(
self, self,
http_common_wg_port, echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
url = f'https://localhost:{http_common_wg_port}' with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
secret = create_ticket(url, 'user', 'echo') user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
other_target = api_create_target(
url,
session,
{
"name": f"other-{uuid4()}",
"options": {
"kind": "Http",
"url": "http://badhost",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
secret = api_create_ticket(
url, session, user["username"], echo_target["name"]
)
# --- # ---
@ -19,38 +78,55 @@ class TestHTTPUserAuthTicket:
session.verify = False session.verify = False
response = session.get( response = session.get(
f'{url}/some/path?warpgate-target=echo', f"{url}/some/path?warpgate-target={echo_target['name']}",
allow_redirects=False, allow_redirects=False,
) )
assert response.status_code // 100 != 2 assert response.status_code // 100 != 2
# Ticket as a header # Ticket as a header
response = session.get( response = session.get(
f'{url}/some/path?warpgate-target=echo', f"{url}/some/path?warpgate-target={echo_target['name']}",
allow_redirects=False, allow_redirects=False,
headers={ headers={
'Authorization': f'Warpgate {secret}', "Authorization": f"Warpgate {secret}",
}, },
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
assert response.json()['path'] == '/some/path' assert response.json()["path"] == "/some/path"
# Bad ticket
response = session.get(
f"{url}/some/path?warpgate-target={echo_target['name']}",
allow_redirects=False,
headers={
"Authorization": f"Warpgate bad{secret}",
},
)
assert response.status_code // 100 != 2
# Ticket as a GET param # Ticket as a GET param
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
response = session.get( response = session.get(
f'{url}/some/path?warpgate-ticket={secret}', f"{url}/some/path?warpgate-ticket={secret}",
allow_redirects=False, allow_redirects=False,
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
assert response.json()['path'] == '/some/path' assert response.json()["path"] == "/some/path"
# Ensure no access to other targets # Ensure no access to other targets
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
response = session.get( response = session.get(
f'{url}/some/path?warpgate-ticket={secret}&warpgate-target=admin', f"{url}/some/path?warpgate-ticket={secret}&warpgate-target=admin",
allow_redirects=False, allow_redirects=False,
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
assert response.json()['path'] == '/some/path'
assert response.json()["path"] == "/some/path"
response = session.get(
f"{url}/some/path?warpgate-ticket={secret}&warpgate-target={other_target['name']}",
allow_redirects=False,
)
assert response.status_code // 100 == 2
assert response.json()["path"] == "/some/path"

View file

@ -1,39 +1,81 @@
import ssl import ssl
import requests import requests
from websocket import create_connection from websocket import create_connection
from uuid import uuid4
from .api_client import (
from .util import wait_port api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_add_role_to_target,
)
from .conftest import WarpgateProcess
from .test_http_common import * # noqa
class TestHTTPWebsocket: class TestHTTPWebsocket:
def test_basic( def test_basic(
self, self,
http_common_wg_port, echo_server_port,
shared_wg: WarpgateProcess,
): ):
wait_port(http_common_wg_port, recv=False) url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"url": f"http://localhost:{echo_server_port}",
"tls": {
"mode": "Disabled",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, echo_target["id"], role["id"])
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
url = f'https://localhost:{http_common_wg_port}'
session.post( session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'user', "username": user["username"],
'password': '123', "password": "123",
}, },
) )
cookies = session.cookies.get_dict() cookies = session.cookies.get_dict()
cookie = '; '.join([f'{k}={v}' for k, v in cookies.items()]) cookie = "; ".join([f"{k}={v}" for k, v in cookies.items()])
ws = create_connection( ws = create_connection(
f'wss://localhost:{http_common_wg_port}/socket?warpgate-target=echo', f"wss://localhost:{shared_wg.http_port}/socket?warpgate-target={echo_target['name']}",
cookie=cookie, cookie=cookie,
sslopt={"cert_reqs": ssl.CERT_NONE}, sslopt={"cert_reqs": ssl.CERT_NONE},
) )
ws.send('test') ws.send("test")
assert ws.recv() == 'test' assert ws.recv() == "test"
ws.send_binary(b'test') ws.send_binary(b"test")
assert ws.recv() == b'test' assert ws.recv() == b"test"
ws.ping() ws.ping()
ws.close() ws.close()

View file

@ -1,74 +1,104 @@
import subprocess import subprocess
from textwrap import dedent import time
from uuid import uuid4
from .conftest import ProcessManager from .api_client import (
api_admin_session,
api_create_target,
api_create_user,
api_create_role,
api_add_role_to_user,
api_add_role_to_target,
)
from .conftest import WarpgateProcess, ProcessManager
from .util import wait_port, wait_mysql_port, mysql_client_ssl_opt, mysql_client_opts from .util import wait_port, wait_mysql_port, mysql_client_ssl_opt, mysql_client_opts
class Test: class Test:
def test(self, processes: ProcessManager, password_123_hash, timeout): def test(
self,
processes: ProcessManager,
timeout,
shared_wg: WarpgateProcess,
):
db_port = processes.start_mysql_server() db_port = processes.start_mysql_server()
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
target = api_create_target(
url,
session,
{
"name": f"mysql-{uuid4()}",
"options": {
"kind": "MySql",
"host": "localhost",
"port": db_port,
"username": "root",
"password": "123",
"tls": {
"mode": "Preferred",
"verify": False,
},
},
},
)
api_add_role_to_target(url, session, target["id"], role["id"])
_, wg_ports = processes.start_wg( time.sleep(15)
dedent(
f'''\
targets:
- name: db
allow_roles: [role]
mysql:
host: localhost
port: {db_port}
user: root
password: '123'
users:
- username: user
roles: [role]
credentials:
- type: password
hash: '{password_123_hash}'
'''
),
)
wait_mysql_port(db_port) wait_mysql_port(db_port)
wait_port(wg_ports['mysql']) wait_port(shared_wg.mysql_port, recv=False)
client = processes.start( client = processes.start(
[ [
'mysql', "mysql",
'--user', "--user",
'user#db', f"{user['username']}#{target['name']}",
'-p123', "-p123",
'--host', "--host",
'127.0.0.1', "127.0.0.1",
'--port', "--port",
str(wg_ports["mysql"]), str(shared_wg.mysql_port),
*mysql_client_opts, *mysql_client_opts,
mysql_client_ssl_opt, mysql_client_ssl_opt,
'db', "db",
], ],
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
) )
assert b'\ndb\n' in client.communicate(b'show schemas;', timeout=timeout)[0] assert b"\ndb\n" in client.communicate(b"show schemas;", timeout=timeout)[0]
assert client.returncode == 0 assert client.returncode == 0
client = processes.start( client = processes.start(
[ [
'mysql', "mysql",
'--user', "--user",
'user:db', f"{user['username']}#{target['name']}",
'-pwrong', "-pwrong",
'--host', "--host",
'127.0.0.1', "127.0.0.1",
'--port', "--port",
str(wg_ports["mysql"]), str(shared_wg.mysql_port),
*mysql_client_opts, *mysql_client_opts,
mysql_client_ssl_opt, mysql_client_ssl_opt,
'db', "db",
], ],
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
) )
client.communicate(b'show schemas;', timeout=timeout) client.communicate(b"show schemas;", timeout=timeout)
assert client.returncode != 0 assert client.returncode != 0

View file

@ -1,50 +1,81 @@
from pathlib import Path from pathlib import Path
import subprocess
from textwrap import dedent from textwrap import dedent
from uuid import uuid4
from .conftest import ProcessManager from .api_client import (
api_admin_session,
api_create_target,
)
from .conftest import ProcessManager, WarpgateProcess
from .util import alloc_port, wait_port from .util import alloc_port, wait_port
class Test: class Test:
def test_success( def test_success(
self, processes: ProcessManager, wg_c_ed25519_pubkey: Path, timeout self,
processes: ProcessManager,
wg_c_ed25519_pubkey: Path,
timeout,
shared_wg: WarpgateProcess,
): ):
ssh_port = processes.start_ssh_server( ssh_port = processes.start_ssh_server(
trusted_keys=[wg_c_ed25519_pubkey.read_text()] trusted_keys=[wg_c_ed25519_pubkey.read_text()]
) )
wait_port(ssh_port) wait_port(ssh_port)
proc, _ = processes.start_wg(
config=dedent(
f'''\
users: []
targets:
- name: ssh
allow_roles: [role]
ssh:
host: localhost
port: {ssh_port}
'''
),
args=['test-target', 'ssh'],
)
proc.wait(timeout=timeout)
assert proc.returncode == 0
def test_fail(self, processes: ProcessManager, timeout): url = f"https://localhost:{shared_wg.http_port}"
ssh_port = alloc_port() with api_admin_session(url) as session:
proc, _ = processes.start_wg( ssh_target = api_create_target(
config=dedent( url,
f'''\ session,
users: [] {
targets: "name": f"ssh-{uuid4()}",
- name: ssh "options": {
allow_roles: [role] "kind": "Ssh",
ssh: "host": "localhost",
host: localhost "port": ssh_port,
port: {ssh_port} "username": "root",
''' "auth": {"kind": "PublicKey"},
), },
args=['test-target', 'ssh'], },
)
wg = processes.start_wg(
share_with=shared_wg,
args=["test-target", ssh_target["name"]],
) )
proc.wait(timeout=timeout) wg.process.wait(timeout=timeout)
assert proc.returncode != 0 assert wg.process.returncode == 0
def test_fail(
self,
processes: ProcessManager,
timeout,
shared_wg: WarpgateProcess,
):
ssh_port = alloc_port()
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
ssh_target = api_create_target(
url,
session,
{
"name": f"ssh-{uuid4()}",
"options": {
"kind": "Ssh",
"host": "localhost",
"port": ssh_port,
"username": "root",
"auth": {"kind": "PublicKey"},
},
},
)
wg = processes.start_wg(
args=["test-target", ssh_target["name"]],
share_with=shared_wg,
)
wg.process.wait(timeout=timeout)
assert wg.process.returncode != 0

View file

@ -1,3 +1,4 @@
from uuid import uuid4
import requests import requests
import subprocess import subprocess
import tempfile import tempfile
@ -5,192 +6,242 @@ import time
import pytest import pytest
from textwrap import dedent from textwrap import dedent
from .conftest import ProcessManager from tests.api_client import (
api_add_role_to_target,
api_add_role_to_user,
api_admin_session,
api_create_role,
api_create_target,
api_create_user,
)
from .conftest import ProcessManager, WarpgateProcess
from .util import wait_port, alloc_port from .util import wait_port, alloc_port
@pytest.fixture(scope='class') @pytest.fixture(scope="session")
def ssh_port(processes, wg_c_ed25519_pubkey): def ssh_port(processes, wg_c_ed25519_pubkey):
yield processes.start_ssh_server(trusted_keys=[wg_c_ed25519_pubkey.read_text()]) yield processes.start_ssh_server(trusted_keys=[wg_c_ed25519_pubkey.read_text()])
@pytest.fixture(scope='class') common_args = [
def wg_port(processes, ssh_port, password_123_hash): "-i",
_, wg_ports = processes.start_wg( "/dev/null",
dedent( "-o",
f'''\ "PreferredAuthentications=password",
targets: ]
- name: ssh
allow_roles: [role]
ssh: def setup_user_and_target(
host: 127.0.0.1 processes: ProcessManager, wg: WarpgateProcess, wg_c_ed25519_pubkey
port: {ssh_port} ):
- name: ssh-bad-domain ssh_port = processes.start_ssh_server(
allow_roles: [role] trusted_keys=[wg_c_ed25519_pubkey.read_text()]
ssh:
host: baddomainsomething
users:
- username: user
roles: [role]
credentials:
- type: password
hash: '{password_123_hash}'
- type: publickey
key: {open('ssh-keys/id_ed25519.pub').read().strip()}
'''
),
) )
wait_port(ssh_port) wait_port(ssh_port)
wait_port(wg_ports['ssh'])
yield wg_ports['ssh']
url = f"https://localhost:{wg.http_port}"
common_args = [ with api_admin_session(url) as session:
'user:ssh@localhost', role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
'-i', user = api_create_user(
'/dev/null', url,
'-o', session,
'PreferredAuthentications=password', {
] "username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
{
"kind": "PublicKey",
"key": open("ssh-keys/id_ed25519.pub").read().strip(),
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
ssh_target = api_create_target(
url,
session,
{
"name": f"ssh-{uuid4()}",
"options": {
"kind": "Ssh",
"host": "localhost",
"port": ssh_port,
"username": "root",
"auth": {"kind": "PublicKey"},
},
},
)
api_add_role_to_target(url, session, ssh_target["id"], role["id"])
return user, ssh_target
class Test: class Test:
def test_stdout_stderr( def test_stdout_stderr(
self, self,
processes: ProcessManager, processes: ProcessManager,
wg_port,
timeout, timeout,
wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
): ):
user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'-p', f"{user['username']}:{ssh_target['name']}@localhost",
str(wg_port), "-p",
str(shared_wg.ssh_port),
*common_args, *common_args,
'sh', "sh",
'-c', "-c",
'"echo -n stdout; echo -n stderr >&2"', '"echo -n stdout; echo -n stderr >&2"',
password='123', password="123",
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
) )
stdout, stderr = ssh_client.communicate(timeout=timeout) stdout, stderr = ssh_client.communicate(timeout=timeout)
assert b'stdout' == stdout assert b"stdout" == stdout
assert stderr.endswith(b'stderr') assert stderr.endswith(b"stderr")
def test_pty( def test_pty(
self, self,
processes: ProcessManager, processes: ProcessManager,
wg_port,
timeout, timeout,
wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
): ):
user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'-p', f"{user['username']}:{ssh_target['name']}@localhost",
str(wg_port), "-p",
'-tt', str(shared_wg.ssh_port),
"-tt",
*common_args, *common_args,
'echo', "echo",
'hello', "hello",
password='123', password="123",
) )
output = ssh_client.communicate(timeout=timeout)[0] output = ssh_client.communicate(timeout=timeout)[0]
assert b'Warpgate' in output assert b"Warpgate" in output
assert b'Selected target:' in output assert b"Selected target:" in output
assert b'hello\r\n' in output assert b"hello\r\n" in output
def test_signals( def test_signals(
self, self,
processes: ProcessManager, processes: ProcessManager,
wg_port, wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
): ):
user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'-p', f"{user['username']}:{ssh_target['name']}@localhost",
str(wg_port), "-p",
'-v', str(shared_wg.ssh_port),
"-v",
*common_args, *common_args,
'sh', '-c', "sh",
"-c",
'"pkill -9 sh"', '"pkill -9 sh"',
password='123', password="123",
) )
assert ssh_client.returncode != 0 assert ssh_client.returncode != 0
# def test_direct_tcpip( def test_direct_tcpip(
# self, self,
# processes: ProcessManager, processes: ProcessManager,
# wg_port, wg_c_ed25519_pubkey,
# timeout, shared_wg: WarpgateProcess,
# ): timeout,
# local_port = alloc_port() ):
# wait_port(wg_port) user, ssh_target = setup_user_and_target(
# ssh_client = processes.start_ssh_client( processes, shared_wg, wg_c_ed25519_pubkey
# '-p', )
# str(wg_port), local_port = alloc_port()
# '-v', ssh_client = processes.start_ssh_client(
# *common_args, f"{user['username']}:{ssh_target['name']}@localhost",
# '-L', f'{local_port}:neverssl.com:80', "-p",
# '-N', str(shared_wg.ssh_port),
# password='123', "-v",
# ) *common_args,
"-L",
f"{local_port}:neverssl.com:80",
"-N",
password="123",
)
# wait_port(local_port, recv=False) time.sleep(10)
# for _ in range(15):
# time.sleep(1)
# try:
# response = requests.get(f'http://localhost:{local_port}', timeout=timeout)
# except Exception:
# continue
# if response.status_code == 200:
# break
# s = requests.Session() wait_port(local_port, recv=False)
# retries = requests.adapters.Retry(total=5, backoff_factor=1)
# s.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries)) s = requests.Session()
# response = s.get(f'http://localhost:{local_port}', timeout=timeout) retries = requests.adapters.Retry(total=5, backoff_factor=1)
# assert response.status_code == 200 s.mount("http://", requests.adapters.HTTPAdapter(max_retries=retries))
# ssh_client.kill() response = s.get(f"http://localhost:{local_port}", timeout=timeout)
assert response.status_code == 200
ssh_client.kill()
def test_tcpip_forward( def test_tcpip_forward(
self, self,
processes: ProcessManager, processes: ProcessManager,
wg_port, wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
timeout, timeout,
): ):
wait_port(wg_port) user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
pf_client = processes.start_ssh_client( pf_client = processes.start_ssh_client(
'-p', f"{user['username']}:{ssh_target['name']}@localhost",
str(wg_port), "-p",
'-v', str(shared_wg.ssh_port),
"-v",
*common_args, *common_args,
'-R', '1234:neverssl.com:80', "-R",
'-N', "1234:neverssl.com:80",
password='123', "-N",
password="123",
) )
time.sleep(5) time.sleep(5)
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'-p', f"{user['username']}:{ssh_target['name']}@localhost",
str(wg_port), "-p",
'-v', str(shared_wg.ssh_port),
"-v",
*common_args, *common_args,
'curl', '-v', 'http://localhost:1234', "curl",
password='123', "-v",
"http://localhost:1234",
password="123",
) )
output = ssh_client.communicate(timeout=timeout)[0] output = ssh_client.communicate(timeout=timeout)[0]
print(output)
assert ssh_client.returncode == 0 assert ssh_client.returncode == 0
assert b'<html>' in output assert b"<html>" in output
pf_client.kill() pf_client.kill()
def test_shell( def test_shell(
self, self,
processes: ProcessManager, processes: ProcessManager,
wg_port, wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
timeout, timeout,
): ):
user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
script = dedent( script = dedent(
f''' f"""
set timeout {timeout - 5} set timeout {timeout - 5}
spawn ssh -tt user:ssh@localhost -p {wg_port} -o StrictHostKeychecking=no -o UserKnownHostsFile=/dev/null -o PreferredAuthentications=password spawn ssh -tt {user['username']}:{ssh_target['name']}@localhost -p {shared_wg.ssh_port} -o StrictHostKeychecking=no -o UserKnownHostsFile=/dev/null -o PreferredAuthentications=password
expect "password:" expect "password:"
sleep 0.5 sleep 0.5
@ -207,11 +258,11 @@ class Test:
}} }}
exit 1 exit 1
''' """
) )
ssh_client = processes.start( ssh_client = processes.start(
['expect', '-d'], stdin=subprocess.PIPE, stdout=subprocess.PIPE ["expect", "-d"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
) )
output = ssh_client.communicate(script.encode(), timeout=timeout)[0] output = ssh_client.communicate(script.encode(), timeout=timeout)[0]
@ -220,48 +271,58 @@ class Test:
def test_connection_error( def test_connection_error(
self, self,
processes: ProcessManager, processes: ProcessManager,
wg_port, wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
): ):
user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'-p', f"{user['username']}:{ssh_target['name']}@localhost",
str(wg_port), "-p",
'-tt', str(shared_wg.ssh_port),
'user:ssh-bad-domain@localhost', "-tt",
'-i', "user:ssh-bad-domain@localhost",
'/dev/null', "-i",
'-o', "/dev/null",
'PreferredAuthentications=password', "-o",
password='123', "PreferredAuthentications=password",
password="123",
) )
assert ssh_client.returncode != 0 assert ssh_client.returncode != 0
def test_sftp( def test_sftp(
self, self,
wg_port, processes: ProcessManager,
wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
): ):
user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
with tempfile.TemporaryDirectory() as f: with tempfile.TemporaryDirectory() as f:
subprocess.check_call( subprocess.check_call(
[ [
'sftp', "sftp",
'-P', "-P",
str(wg_port), str(shared_wg.ssh_port),
'-o', "-o",
'User=user:ssh', f"User={user['username']}:{ssh_target['name']}",
'-o', "-o",
'IdentitiesOnly=yes', "IdentitiesOnly=yes",
'-o', "-o",
'IdentityFile=ssh-keys/id_ed25519', "IdentityFile=ssh-keys/id_ed25519",
'-o', "-o",
'PreferredAuthentications=publickey', "PreferredAuthentications=publickey",
'-o', "-o",
'StrictHostKeychecking=no', "StrictHostKeychecking=no",
'-o', "-o",
'UserKnownHostsFile=/dev/null', "UserKnownHostsFile=/dev/null",
'localhost:/etc/passwd', "localhost:/etc/passwd",
f, f,
], ],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
) )
assert 'root:x:0:0:root' in open(f + '/passwd').read() assert "root:x:0:0:root" in open(f + "/passwd").read()

View file

@ -1,52 +1,78 @@
from pathlib import Path from pathlib import Path
from textwrap import dedent import subprocess
from uuid import uuid4
from .conftest import ProcessManager from .api_client import (
api_add_role_to_target,
api_add_role_to_user,
api_admin_session,
api_create_role,
api_create_target,
api_create_user,
)
from .conftest import ProcessManager, WarpgateProcess
from .util import wait_port from .util import wait_port
class Test: class Test:
def test_bad_target( def test_bad_target(
self, processes: ProcessManager, wg_c_ed25519_pubkey: Path, password_123_hash self,
processes: ProcessManager,
wg_c_ed25519_pubkey: Path,
shared_wg: WarpgateProcess,
): ):
ssh_port = processes.start_ssh_server( ssh_port = processes.start_ssh_server(
trusted_keys=[wg_c_ed25519_pubkey.read_text()] trusted_keys=[wg_c_ed25519_pubkey.read_text()]
) )
_, wg_ports = processes.start_wg(
dedent(
f'''\
targets:
- name: ssh
allow_roles: [role]
ssh:
host: localhost
port: {ssh_port}
users:
- username: user
roles: [role]
credentials:
- type: password
hash: '{password_123_hash}'
'''
),
)
wait_port(ssh_port) wait_port(ssh_port)
wait_port(wg_ports['ssh'])
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
ssh_target = api_create_target(
url,
session,
{
"name": f"ssh-{uuid4()}",
"options": {
"kind": "Ssh",
"host": "localhost",
"port": ssh_port,
"username": "root",
"auth": {"kind": "PublicKey"},
},
},
)
api_add_role_to_target(url, session, ssh_target["id"], role["id"])
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'-t', "-t",
'user:badtarget@localhost', f"{user['username']}:badtarget@localhost",
'-p', "-p",
str(wg_ports['ssh']), str(shared_wg.ssh_port),
'-i', "-i",
'/dev/null', "/dev/null",
'-o', "-o",
'PreferredAuthentications=password', "PreferredAuthentications=password",
'echo', "echo",
'hello', "hello",
password='123', stderr=subprocess.PIPE,
password="123",
) )
assert ssh_client.returncode != 0 assert ssh_client.returncode != 0
assert b"Permission denied" in ssh_client.stderr.read()

View file

@ -1,9 +1,19 @@
from asyncio import subprocess from asyncio import subprocess
from base64 import b64decode
from uuid import uuid4
import pyotp import pyotp
from pathlib import Path from pathlib import Path
from textwrap import dedent from textwrap import dedent
from .conftest import ProcessManager from .api_client import (
api_add_role_to_target,
api_add_role_to_user,
api_admin_session,
api_create_role,
api_create_target,
api_create_user,
)
from .conftest import ProcessManager, WarpgateProcess
from .util import wait_port from .util import wait_port
@ -15,44 +25,61 @@ class Test:
otp_key_base32: str, otp_key_base32: str,
otp_key_base64: str, otp_key_base64: str,
timeout, timeout,
shared_wg: WarpgateProcess,
): ):
ssh_port = processes.start_ssh_server( ssh_port = processes.start_ssh_server(
trusted_keys=[wg_c_ed25519_pubkey.read_text()] trusted_keys=[wg_c_ed25519_pubkey.read_text()]
) )
_, wg_ports = processes.start_wg(
dedent(
f'''\
targets:
- name: ssh
allow_roles: [role]
ssh:
host: localhost
port: {ssh_port}
users:
- username: user
roles: [role]
credentials:
- type: publickey
key: {open('ssh-keys/id_ed25519.pub').read().strip()}
- type: otp
key: {otp_key_base64}
require:
ssh: [publickey, otp]
'''
),
)
wait_port(ssh_port) wait_port(ssh_port)
wait_port(wg_ports['ssh'])
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "PublicKey",
"key": open("ssh-keys/id_ed25519.pub").read().strip(),
},
{
"kind": "Totp",
"key": list(b64decode(otp_key_base64)),
},
],
"credential_policy": {
"ssh": ["PublicKey", "Totp"],
},
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
ssh_target = api_create_target(
url,
session,
{
"name": f"ssh-{uuid4()}",
"options": {
"kind": "Ssh",
"host": "localhost",
"port": ssh_port,
"username": "root",
"auth": {"kind": "PublicKey"},
},
},
)
api_add_role_to_target(url, session, ssh_target["id"], role["id"])
totp = pyotp.TOTP(otp_key_base32) totp = pyotp.TOTP(otp_key_base32)
script = dedent( script = dedent(
f''' f"""
set timeout {timeout - 5} set timeout {timeout - 5}
spawn ssh user:ssh@localhost -p {wg_ports['ssh']} -o StrictHostKeychecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o IdentityFile=ssh-keys/id_ed25519 -o PreferredAuthentications=publickey,keyboard-interactive ls /bin/sh spawn ssh {user["username"]}:{ssh_target["name"]}@localhost -p {shared_wg.ssh_port} -o StrictHostKeychecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o IdentityFile=ssh-keys/id_ed25519 -o PreferredAuthentications=publickey,keyboard-interactive ls /bin/sh
expect "Two-factor authentication" expect "Two-factor authentication"
sleep 0.5 sleep 0.5
@ -62,21 +89,24 @@ class Test:
"/bin/sh" {{ exit 0; }} "/bin/sh" {{ exit 0; }}
eof {{ exit 1; }} eof {{ exit 1; }}
}} }}
''' """
) )
ssh_client = processes.start( ssh_client = processes.start(
['expect'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ["expect"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) )
output, stderr = ssh_client.communicate(script.encode(), timeout=timeout) output, stderr = ssh_client.communicate(script.encode(), timeout=timeout)
assert ssh_client.returncode == 0, output + stderr assert ssh_client.returncode == 0, output + stderr
script = dedent( script = dedent(
f''' f"""
set timeout {timeout - 5} set timeout {timeout - 5}
spawn ssh user:ssh@localhost -p {[wg_ports['ssh']]} -o StrictHostKeychecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o IdentityFile=ssh-keys/id_ed25519 -o PreferredAuthentications=publickey,keyboard-interactive ls /bin/sh spawn ssh {user["username"]}:{ssh_target["name"]}@localhost -p {[shared_wg.ssh_port]} -o StrictHostKeychecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o IdentityFile=ssh-keys/id_ed25519 -o PreferredAuthentications=publickey,keyboard-interactive ls /bin/sh
expect "Two-factor authentication" expect "Two-factor authentication"
sleep 0.5 sleep 0.5
@ -87,11 +117,11 @@ class Test:
"Two-factor authentication" {{ exit 1; }} "Two-factor authentication" {{ exit 1; }}
eof {{ exit 1; }} eof {{ exit 1; }}
}} }}
''' """
) )
ssh_client = processes.start( ssh_client = processes.start(
['expect'], stdin=subprocess.PIPE, stdout=subprocess.PIPE ["expect"], stdin=subprocess.PIPE, stdout=subprocess.PIPE
) )
output = ssh_client.communicate(script.encode(), timeout=timeout)[0] output = ssh_client.communicate(script.encode(), timeout=timeout)[0]

View file

@ -1,67 +1,92 @@
from pathlib import Path from pathlib import Path
from textwrap import dedent from uuid import uuid4
from .conftest import ProcessManager from .api_client import (
api_add_role_to_target,
api_add_role_to_user,
api_admin_session,
api_create_role,
api_create_target,
api_create_user,
)
from .conftest import ProcessManager, WarpgateProcess
from .util import wait_port from .util import wait_port
class Test: class Test:
def test( def test(
self, processes: ProcessManager, wg_c_ed25519_pubkey: Path, password_123_hash, timeout self,
processes: ProcessManager,
wg_c_ed25519_pubkey: Path,
timeout,
shared_wg: WarpgateProcess,
): ):
ssh_port = processes.start_ssh_server( ssh_port = processes.start_ssh_server(
trusted_keys=[wg_c_ed25519_pubkey.read_text()] trusted_keys=[wg_c_ed25519_pubkey.read_text()]
) )
_, wg_ports = processes.start_wg(
dedent(
f'''\
targets:
- name: ssh
allow_roles: [role]
ssh:
host: localhost
port: {ssh_port}
users:
- username: user
roles: [role]
credentials:
- type: password
hash: '{password_123_hash}'
'''
),
)
wait_port(ssh_port) wait_port(ssh_port)
wait_port(wg_ports['ssh'])
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
ssh_target = api_create_target(
url,
session,
{
"name": f"ssh-{uuid4()}",
"options": {
"kind": "Ssh",
"host": "localhost",
"port": ssh_port,
"username": "root",
"auth": {"kind": "PublicKey"},
},
},
)
api_add_role_to_target(url, session, ssh_target["id"], role["id"])
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'user:ssh@localhost', f'{user["username"]}:{ssh_target["name"]}@localhost',
'-v', "-v",
'-p', "-p",
str(wg_ports['ssh']), str(shared_wg.ssh_port),
'-i', "-i",
'/dev/null', "/dev/null",
'-o', "-o",
'PreferredAuthentications=password', "PreferredAuthentications=password",
'ls', "ls",
'/bin/sh', "/bin/sh",
password='123', password="123",
) )
assert ssh_client.communicate(timeout=timeout)[0] == b'/bin/sh\n' assert ssh_client.communicate(timeout=timeout)[0] == b"/bin/sh\n"
assert ssh_client.returncode == 0 assert ssh_client.returncode == 0
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'user:ssh@localhost', f'{user["username"]}:{ssh_target["name"]}@localhost',
'-p', "-p",
str(wg_ports['ssh']), str(shared_wg.ssh_port),
'-i', "-i",
'/dev/null', "/dev/null",
'-o', "-o",
'PreferredAuthentications=password', "PreferredAuthentications=password",
'ls', "ls",
'/bin/sh', "/bin/sh",
password='321', password="321",
) )
ssh_client.communicate(timeout=timeout) ssh_client.communicate(timeout=timeout)
assert ssh_client.returncode != 0 assert ssh_client.returncode != 0

View file

@ -1,125 +1,169 @@
from pathlib import Path from pathlib import Path
from textwrap import dedent from uuid import uuid4
from .conftest import ProcessManager from .api_client import (
api_add_role_to_target,
api_add_role_to_user,
api_admin_session,
api_create_role,
api_create_target,
api_create_user,
)
from .conftest import ProcessManager, WarpgateProcess
from .util import wait_port from .util import wait_port
class Test: class Test:
def test_ed25519( def test_ed25519(
self, processes: ProcessManager, wg_c_ed25519_pubkey: Path, timeout self,
processes: ProcessManager,
wg_c_ed25519_pubkey: Path,
timeout,
shared_wg: WarpgateProcess,
): ):
ssh_port = processes.start_ssh_server( ssh_port = processes.start_ssh_server(
trusted_keys=[wg_c_ed25519_pubkey.read_text()] trusted_keys=[wg_c_ed25519_pubkey.read_text()]
) )
_, wg_ports = processes.start_wg(
dedent(
f'''\
targets:
- name: ssh
allow_roles: [role]
ssh:
host: localhost
port: {ssh_port}
users:
- username: user
roles: [role]
credentials:
- type: publickey
key: {open('ssh-keys/id_ed25519.pub').read().strip()}
'''
),
)
wait_port(ssh_port) wait_port(ssh_port)
wait_port(wg_ports['ssh'])
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "PublicKey",
"key": open("ssh-keys/id_ed25519.pub").read().strip(),
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
ssh_target = api_create_target(
url,
session,
{
"name": f"ssh-{uuid4()}",
"options": {
"kind": "Ssh",
"host": "localhost",
"port": ssh_port,
"username": "root",
"auth": {"kind": "PublicKey"},
},
},
)
api_add_role_to_target(url, session, ssh_target["id"], role["id"])
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'user:ssh@localhost', f'{user["username"]}:{ssh_target["name"]}@localhost',
'-p', "-p",
str(wg_ports['ssh']), str(shared_wg.ssh_port),
'-o', "-o",
'IdentityFile=ssh-keys/id_ed25519', "IdentityFile=ssh-keys/id_ed25519",
'-o', "-o",
'PreferredAuthentications=publickey', "PreferredAuthentications=publickey",
# 'sh', '-c', '"ls /bin/sh;sleep 1"', # 'sh', '-c', '"ls /bin/sh;sleep 1"',
'ls', "ls",
'/bin/sh', "/bin/sh",
) )
assert ssh_client.communicate(timeout=timeout)[0] == b'/bin/sh\n' assert ssh_client.communicate(timeout=timeout)[0] == b"/bin/sh\n"
assert ssh_client.returncode == 0 assert ssh_client.returncode == 0
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'user:ssh@localhost', f'{user["username"]}:{ssh_target["name"]}@localhost',
'-p', "-p",
str(wg_ports['ssh']), str(shared_wg.ssh_port),
'-o', "-o",
'IdentityFile=ssh-keys/id_rsa', "IdentityFile=ssh-keys/id_rsa",
'-o', "-o",
'PreferredAuthentications=publickey', "PreferredAuthentications=publickey",
'ls', "ls",
'/bin/sh', "/bin/sh",
) )
assert ssh_client.communicate(timeout=timeout)[0] == b'' assert ssh_client.communicate(timeout=timeout)[0] == b""
assert ssh_client.returncode != 0 assert ssh_client.returncode != 0
def test_rsa( def test_rsa(
self, processes: ProcessManager, wg_c_ed25519_pubkey: Path, timeout self,
processes: ProcessManager,
wg_c_ed25519_pubkey: Path,
timeout,
shared_wg: WarpgateProcess,
): ):
ssh_port = processes.start_ssh_server( ssh_port = processes.start_ssh_server(
trusted_keys=[wg_c_ed25519_pubkey.read_text()] trusted_keys=[wg_c_ed25519_pubkey.read_text()]
) )
_, wg_ports = processes.start_wg(
dedent(
f'''\
targets:
- name: ssh
allow_roles: [role]
ssh:
host: localhost
port: {ssh_port}
users:
- username: user
roles: [role]
credentials:
- type: publickey
key: {open('ssh-keys/id_rsa.pub').read().strip()}
'''
),
)
wait_port(ssh_port) wait_port(ssh_port)
wait_port(wg_ports['ssh'])
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "PublicKey",
"key": open("ssh-keys/id_rsa.pub").read().strip(),
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
ssh_target = api_create_target(
url,
session,
{
"name": f"ssh-{uuid4()}",
"options": {
"kind": "Ssh",
"host": "localhost",
"port": ssh_port,
"username": "root",
"auth": {"kind": "PublicKey"},
},
},
)
api_add_role_to_target(url, session, ssh_target["id"], role["id"])
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'user:ssh@localhost', f'{user["username"]}:{ssh_target["name"]}@localhost',
'-v', "-v",
'-p', "-p",
str(wg_ports['ssh']), str(shared_wg.ssh_port),
'-o', "-o",
'IdentityFile=ssh-keys/id_rsa', "IdentityFile=ssh-keys/id_rsa",
'-o', "-o",
'PreferredAuthentications=publickey', "PreferredAuthentications=publickey",
'-o', 'PubkeyAcceptedKeyTypes=+ssh-rsa', "-o",
'ls', "PubkeyAcceptedKeyTypes=+ssh-rsa",
'/bin/sh', "ls",
"/bin/sh",
) )
assert ssh_client.communicate(timeout=timeout)[0] == b'/bin/sh\n' assert ssh_client.communicate(timeout=timeout)[0] == b"/bin/sh\n"
assert ssh_client.returncode == 0 assert ssh_client.returncode == 0
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
'user:ssh@localhost', f'{user["username"]}:{ssh_target["name"]}@localhost',
'-p', "-p",
str(wg_ports['ssh']), str(shared_wg.ssh_port),
'-o', "-o",
'IdentityFile=ssh-keys/id_ed25519', "IdentityFile=ssh-keys/id_ed25519",
'-o', "-o",
'PreferredAuthentications=publickey', "PreferredAuthentications=publickey",
'-o', 'PubkeyAcceptedKeyTypes=+ssh-rsa', "-o",
'ls', "PubkeyAcceptedKeyTypes=+ssh-rsa",
'/bin/sh', "ls",
"/bin/sh",
) )
assert ssh_client.communicate(timeout=timeout)[0] == b'' assert ssh_client.communicate(timeout=timeout)[0] == b""
assert ssh_client.returncode != 0 assert ssh_client.returncode != 0

View file

@ -1,63 +1,80 @@
from pathlib import Path from pathlib import Path
from textwrap import dedent from uuid import uuid4
from .conftest import ProcessManager from .api_client import (
from .util import create_ticket, wait_port api_add_role_to_target,
api_add_role_to_user,
api_admin_session,
api_create_role,
api_create_target,
api_create_ticket,
api_create_user,
)
from .conftest import ProcessManager, WarpgateProcess
from .util import wait_port
class Test: class Test:
def test( def test(
self, processes: ProcessManager, wg_c_ed25519_pubkey: Path, password_123_hash, timeout self,
processes: ProcessManager,
wg_c_ed25519_pubkey: Path,
timeout,
shared_wg: WarpgateProcess,
): ):
ssh_port = processes.start_ssh_server( ssh_port = processes.start_ssh_server(
trusted_keys=[wg_c_ed25519_pubkey.read_text()] trusted_keys=[wg_c_ed25519_pubkey.read_text()]
) )
_, wg_ports = processes.start_wg(
dedent(
f'''\
targets:
- name: ssh
allow_roles: [role]
ssh:
host: localhost
port: {ssh_port}
- name: warpgate:admin
allow_roles: [admin]
web_admin: {{}}
users:
- username: user
roles: [role]
credentials:
- type: password
hash: '{password_123_hash}'
- username: admin
roles: [warpgate:admin]
credentials:
- type: password
hash: '{password_123_hash}'
'''
),
)
wait_port(ssh_port) wait_port(ssh_port)
wait_port(wg_ports['ssh'])
wait_port(wg_ports['http'], recv=False)
url = f'https://localhost:{wg_ports["http"]}' url = f"https://localhost:{shared_wg.http_port}"
secret = create_ticket(url, 'user', 'ssh') with api_admin_session(url) as session:
role = api_create_role(url, session, {"name": f"role-{uuid4()}"})
user = api_create_user(
url,
session,
{
"username": f"user-{uuid4()}",
"credentials": [
{
"kind": "Password",
"hash": "123",
},
],
},
)
api_add_role_to_user(url, session, user["id"], role["id"])
ssh_target = api_create_target(
url,
session,
{
"name": f"ssh-{uuid4()}",
"options": {
"kind": "Ssh",
"host": "localhost",
"port": ssh_port,
"username": "root",
"auth": {"kind": "PublicKey"},
},
},
)
api_add_role_to_target(url, session, ssh_target["id"], role["id"])
secret = api_create_ticket(
url, session, user["username"], ssh_target["name"]
)
ssh_client = processes.start_ssh_client( ssh_client = processes.start_ssh_client(
f'ticket-{secret}@localhost', f"ticket-{secret}@localhost",
'-p', "-p",
str(wg_ports['ssh']), str(shared_wg.ssh_port),
'-i', "-i",
'/dev/null', "/dev/null",
'-o', "-o",
'PreferredAuthentications=password', "PreferredAuthentications=password",
'ls', "ls",
'/bin/sh', "/bin/sh",
password='123', password="123",
) )
assert ssh_client.communicate(timeout=timeout)[0] == b'/bin/sh\n' assert ssh_client.communicate(timeout=timeout)[0] == b"/bin/sh\n"
assert ssh_client.returncode == 0 assert ssh_client.returncode == 0

View file

@ -9,12 +9,12 @@ import time
last_port = 1234 last_port = 1234
mysql_client_ssl_opt = '--ssl' mysql_client_ssl_opt = "--ssl"
mysql_client_opts = [] mysql_client_opts = []
if 'GITHUB_ACTION' in os.environ: if "GITHUB_ACTION" in os.environ:
# Github uses MySQL instead of MariaDB # Github uses MySQL instead of MariaDB
mysql_client_ssl_opt = '--ssl-mode=REQUIRED' mysql_client_ssl_opt = "--ssl-mode=REQUIRED"
mysql_client_opts = ['--enable-cleartext-plugin'] mysql_client_opts = ["--enable-cleartext-plugin"]
def alloc_port(): def alloc_port():
@ -23,46 +23,55 @@ def alloc_port():
return last_port return last_port
def wait_port(port, recv=True, timeout=60): def wait_port(port, recv=True, timeout=60, for_process: subprocess.Popen = None):
logging.debug(f'Waiting for port {port}') logging.debug(f"Waiting for port {port}")
data = b'' data = b""
def wait(): def wait():
nonlocal data nonlocal data
while True: while True:
try: try:
s = socket.create_connection(('localhost', port), timeout=5) s = socket.create_connection(("localhost", port), timeout=5)
if recv: if recv:
while True: while True:
data = s.recv(100) data = s.recv(100)
if data: if data:
break break
else: else:
data = b'' data = b""
s.close() s.close()
logging.debug(f'Port {port} is up') logging.debug(f"Port {port} is up")
return data return data
except socket.error: except socket.error:
time.sleep(0.1) if for_process:
continue try:
for_process.wait(timeout=0.1)
raise Exception("Process exited while waiting for port")
except subprocess.TimeoutExpired:
continue
else:
time.sleep(0.1)
t = threading.Thread(target=wait, daemon=True) t = threading.Thread(target=wait, daemon=True)
t.start() t.start()
t.join(timeout=timeout) t.join(timeout=timeout)
if t.is_alive(): if t.is_alive():
raise Exception(f'Port {port} is not up') raise Exception(f"Port {port} is not up")
return data return data
def wait_mysql_port(port): def wait_mysql_port(port):
logging.debug(f'Waiting for MySQL port {port}') logging.debug(f"Waiting for MySQL port {port}")
def wait(): def wait():
while True: while True:
try: try:
subprocess.check_call(f'mysql --user=root --password=123 --host=127.0.0.1 --port={port} --execute="show schemas;"', shell=True) subprocess.check_call(
logging.debug(f'Port {port} is up') f'mysql --user=root --password=123 --host=127.0.0.1 --port={port} --execute="show schemas;"',
shell=True,
)
logging.debug(f"Port {port} is up")
break break
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
time.sleep(1) time.sleep(1)
@ -72,26 +81,26 @@ def wait_mysql_port(port):
t.start() t.start()
t.join(timeout=60) t.join(timeout=60)
if t.is_alive(): if t.is_alive():
raise Exception(f'Port {port} is not up') raise Exception(f"Port {port} is not up")
def create_ticket(url, username, target_name): def create_ticket(url, username, target_name):
session = requests.Session() session = requests.Session()
session.verify = False session.verify = False
response = session.post( response = session.post(
f'{url}/@warpgate/api/auth/login', f"{url}/@warpgate/api/auth/login",
json={ json={
'username': 'admin', "username": "admin",
'password': '123', "password": "123",
}, },
) )
assert response.status_code // 100 == 2 assert response.status_code // 100 == 2
response = session.post( response = session.post(
f'{url}/@warpgate/admin/api/tickets', f"{url}/@warpgate/admin/api/tickets",
json={ json={
'username': username, "username": username,
'target_name': target_name, "target_name": target_name,
}, },
) )
assert response.status_code == 201 assert response.status_code == 201
return response.json()['secret'] return response.json()["secret"]

View file

@ -76,9 +76,8 @@ impl ListApi {
let values = User::ActiveModel { let values = User::ActiveModel {
id: Set(Uuid::new_v4()), id: Set(Uuid::new_v4()),
username: Set(body.username.clone()), username: Set(body.username.clone()),
credentials: Set( credentials: Set(serde_json::to_value(process_credentials(&body.credentials))
serde_json::to_value(body.credentials.clone()).map_err(WarpgateError::from)? .map_err(WarpgateError::from)?),
),
credential_policy: Set(serde_json::to_value(body.credential_policy.clone()) credential_policy: Set(serde_json::to_value(body.credential_policy.clone())
.map_err(WarpgateError::from)?), .map_err(WarpgateError::from)?),
}; };
@ -167,7 +166,8 @@ impl DetailApi {
let mut model: User::ActiveModel = user.into(); let mut model: User::ActiveModel = user.into();
model.username = Set(body.username.clone()); model.username = Set(body.username.clone());
model.credentials = Set(serde_json::to_value(credentials).map_err(WarpgateError::from)?); model.credentials = Set(serde_json::to_value(process_credentials(&body.credentials))
.map_err(WarpgateError::from)?);
model.credential_policy = model.credential_policy =
Set(serde_json::to_value(body.credential_policy.clone()) Set(serde_json::to_value(body.credential_policy.clone())
.map_err(WarpgateError::from)?); .map_err(WarpgateError::from)?);
@ -209,6 +209,18 @@ impl DetailApi {
} }
} }
fn process_credentials(credentials: &Vec<UserAuthCredential>) -> Vec<UserAuthCredential> {
let mut credentials = credentials.clone();
for credential in credentials.iter_mut() {
if let UserAuthCredential::Password(ref mut c) = credential {
if parse_hash(&c.hash.expose_secret()).is_err() {
c.hash = hash_password(&c.hash.expose_secret()).into();
}
}
}
credentials
}
#[derive(ApiResponse)] #[derive(ApiResponse)]
enum GetUserRolesResponse { enum GetUserRolesResponse {
#[oai(status = 200)] #[oai(status = 200)]

View file

@ -172,10 +172,10 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
} }
} }
if let Commands::UnattendedSetup { ssh_port, .. } = &cli.command { if let Commands::UnattendedSetup { mysql_port, .. } = &cli.command {
if let Some(ssh_port) = ssh_port { if let Some(mysql_port) = mysql_port {
store.ssh.enable = true; store.mysql.enable = true;
store.ssh.listen = ListenEndpoint(SocketAddr::from(([0, 0, 0, 0], *ssh_port))); store.mysql.listen = ListenEndpoint(SocketAddr::from(([0, 0, 0, 0], *mysql_port)));
} }
} else { } else {
if !is_docker() { if !is_docker() {