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
working-directory: tests
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
- 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 urllib3
import uuid
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from textwrap import dedent
from typing import List
from .util import alloc_port
from .test_http_common import http_common_wg_port, echo_server_port # noqa
from .util import alloc_port, wait_port
from .test_http_common import echo_server_port # noqa
cargo_root = Path(os.getcwd()).parent
@ -33,6 +34,15 @@ class Child:
stop_timeout: float
@dataclass
class WarpgateProcess:
config_path: Path
process: subprocess.Popen
http_port: int
ssh_port: int
mysql_port: int
class ProcessManager:
children: List[Child]
@ -67,14 +77,14 @@ class ProcessManager:
def start_ssh_server(self, trusted_keys=[]):
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)
authorized_keys_path = data_dir / 'authorized_keys'
authorized_keys_path.write_text('\n'.join(trusted_keys))
config_path = data_dir / 'sshd_config'
authorized_keys_path = data_dir / "authorized_keys"
authorized_keys_path.write_text("\n".join(trusted_keys))
config_path = data_dir / "sshd_config"
config_path.write_text(
dedent(
f'''\
f"""\
Port 22
AuthorizedKeysFile {authorized_keys_path}
AllowAgentForwarding yes
@ -87,7 +97,7 @@ class ProcessManager:
PermitRootLogin yes
HostKey /ssh-keys/id_ed25519
Subsystem sftp /usr/lib/ssh/sftp-server
'''
"""
)
)
data_dir.chmod(0o700)
@ -96,17 +106,17 @@ class ProcessManager:
self.start(
[
'docker',
'run',
'--rm',
'-p',
f'{port}:22',
'-v',
f'{data_dir}:{data_dir}',
'-v',
f'{os.getcwd()}/ssh-keys:/ssh-keys',
'warpgate-e2e-ssh-server',
'-f',
"docker",
"run",
"--rm",
"-p",
f"{port}:22",
"-v",
f"{data_dir}:{data_dir}",
"-v",
f"{os.getcwd()}/ssh-keys:/ssh-keys",
"warpgate-e2e-ssh-server",
"-f",
str(config_path),
]
)
@ -115,99 +125,120 @@ class ProcessManager:
def start_mysql_server(self):
port = alloc_port()
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
def start_wg(self, config='', args=None):
ssh_port = alloc_port()
http_port = alloc_port()
mysql_port = alloc_port()
data_dir = self.ctx.tmpdir / f'wg-data-{uuid.uuid4()}'
data_dir.mkdir(parents=True)
keys_dir = data_dir / 'keys'
keys_dir.mkdir(parents=True)
for k in [
Path('ssh-keys/wg/client-ed25519'),
Path('ssh-keys/wg/client-rsa'),
Path('ssh-keys/wg/host-ed25519'),
Path('ssh-keys/wg/host-rsa'),
Path('certs/tls.certificate.pem'),
Path('certs/tls.key.pem'),
]:
shutil.copy(k, keys_dir / k.name)
config_path = data_dir / 'warpgate.yaml'
config_path.write_text(
dedent(
f'''\
ssh:
enable: true
listen: 0.0.0.0:{ssh_port}
keys: {keys_dir}
host_key_verification: auto_accept
http:
enable: true
listen: 0.0.0.0:{http_port}
certificate: {keys_dir}/tls.certificate.pem
key: {keys_dir}/tls.key.pem
mysql:
enable: true
listen: 0.0.0.0:{mysql_port}
certificate: {keys_dir}/tls.certificate.pem
key: {keys_dir}/tls.key.pem
recordings:
enable: false
roles:
- name: role
- name: admin
- name: warpgate:admin
'''
) + config
def start_wg(
self,
config="",
args=None,
share_with: WarpgateProcess = None,
stderr=None,
stdout=None,
) -> WarpgateProcess:
args = args or ["run"]
if share_with:
config_path = share_with.config_path
ssh_port = share_with.ssh_port
mysql_port = share_with.mysql_port
http_port = share_with.http_port
else:
ssh_port = alloc_port()
http_port = alloc_port()
mysql_port = alloc_port()
data_dir = self.ctx.tmpdir / f"wg-data-{uuid.uuid4()}"
data_dir.mkdir(parents=True)
keys_dir = data_dir / "ssh-keys"
keys_dir.mkdir(parents=True)
for k in [
Path("ssh-keys/wg/client-ed25519"),
Path("ssh-keys/wg/client-rsa"),
Path("ssh-keys/wg/host-ed25519"),
Path("ssh-keys/wg/host-rsa"),
]:
shutil.copy(k, keys_dir / k.name)
for k in [
Path("certs/tls.certificate.pem"),
Path("certs/tls.key.pem"),
]:
shutil.copy(k, data_dir / k.name)
config_path = data_dir / "warpgate.yaml"
def run(args, env={}):
return 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",
**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):
preargs = []
if password:
preargs = ['sshpass', '-p', password]
preargs = ["sshpass", "-p", password]
p = self.start(
[
*preargs,
'ssh',
"ssh",
# '-v',
'-o',
'IdentitiesOnly=yes',
'-o',
'StrictHostKeychecking=no',
'-o',
'UserKnownHostsFile=/dev/null',
"-o",
"IdentitiesOnly=yes",
"-o",
"StrictHostKeychecking=no",
"-o",
"UserKnownHostsFile=/dev/null",
*args,
],
stdout=subprocess.PIPE,
@ -217,24 +248,26 @@ class ProcessManager:
def start(self, args, stop_timeout=3, stop_signal=signal.SIGTERM, **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
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def timeout():
t = os.getenv('TIMEOUT', '10')
t = os.getenv("TIMEOUT", "10")
return int(t)
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def ctx():
with tempfile.TemporaryDirectory() as tmpdir:
ctx = Context(tmpdir=Path(tmpdir))
yield ctx
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def processes(ctx, report_generation):
mgr = ProcessManager(ctx)
try:
@ -243,38 +276,48 @@ def processes(ctx, report_generation):
mgr.stop()
@pytest.fixture(scope='session', autouse=True)
@pytest.fixture(scope="session", autouse=True)
def report_generation():
# 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
# 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():
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():
return 'Isj0ekwF1YsKW8VUUQiU4awp/9dMnyMcTPH9rlr1OsE='
return "Isj0ekwF1YsKW8VUUQiU4awp/9dMnyMcTPH9rlr1OsE="
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
def otp_key_base32():
return 'ELEPI6SMAXKYWCS3YVKFCCEU4GWCT76XJSPSGHCM6H624WXVHLAQ'
return "ELEPI6SMAXKYWCS3YVKFCCEU4GWCT76XJSPSGHCM6H624WXVHLAQ"
@pytest.fixture(scope='session')
@pytest.fixture(scope="session")
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)
requests.packages.urllib3.disable_warnings()
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)"]
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]]
name = "certifi"
version = "2022.6.15"
@ -230,6 +253,14 @@ category = "dev"
optional = false
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]]
name = "packaging"
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)"]
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]]
name = "pluggy"
version = "1.0.0"
@ -375,6 +426,14 @@ tomli = ">=1.0.0"
[package.extras]
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]]
name = "requests"
version = "2.28.1"
@ -420,6 +479,14 @@ category = "main"
optional = false
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]]
name = "typing-extensions"
version = "4.3.0"
@ -494,7 +561,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "227309a9ca00d2440517db21c07a19c67e657eb43ce7cd0264d5cd07f6138d39"
content-hash = "3a5e8c2efbb8a9dbd12ebfbcd4216085d17f66692f64e81f61f3feb4d3214680"
[metadata.files]
atomicwrites = [
@ -517,6 +584,29 @@ bcrypt = [
{file = "bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129"},
{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 = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{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.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 = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{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.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 = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{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.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 = [
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
{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.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 = [
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
{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"
flask-sock = "^0.5.2"
websocket-client = "^1.3.3"
PyYAML = "^6.0"
[tool.poetry.dev-dependencies]
flake8 = "^5.0.2"
pytest = "^7.1.2"
black = "^22.10.0"
[tool.pytest.ini_options]
minversion = "6.0"

View file

@ -5,5 +5,5 @@ rm target/llvm-cov-target/* || true
cargo llvm-cov clean --workspace
cargo llvm-cov --no-report --workspace --all-features -- --skip agent
cd tests
RUST_BACKTRACE=1 poetry run pytest -s $@
RUST_BACKTRACE=1 poetry run pytest $@
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
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:
def test_basic(
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.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
redirect = response.headers['location']
assert redirect == '/@warpgate#/login?next=%2F%3Fwarpgate%2Dtarget%3Decho'
redirect = response.headers["location"]
print(unquote(redirect))
assert (
unquote(redirect)
== f"/@warpgate#/login?next=/?warpgate-target={echo_target['name']}"
)
response = session.get(f'{url}/@warpgate/api/info').json()
assert response['username'] is None
response = session.get(f"{url}/@warpgate/api/info").json()
assert response["username"] is None
response = session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'user',
'password': '123',
"username": user["username"],
"password": "123",
},
)
assert response.status_code == 201
response = session.get(f'{url}/@warpgate/api/info').json()
assert response['username'] == 'user'
response = session.get(f"{url}/@warpgate/api/info").json()
assert response["username"] == user["username"]
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.json()['method'] == 'GET'
assert response.json()['path'] == '/some/path'
assert response.json()['args']['a'] == 'b'
assert response.json()['args']['c'] == 'd'
assert response.json()["method"] == "GET"
assert response.json()["path"] == "/some/path"
assert response.json()["args"]["a"] == "b"
assert response.json()["args"]["c"] == "d"

View file

@ -1,80 +1,39 @@
import pytest
import threading
from textwrap import dedent
from .util import alloc_port
@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')
@pytest.fixture(scope="session")
def echo_server_port():
from flask import Flask, request, jsonify, redirect
from flask_sock import Sock
app = Flask(__name__)
sock = Sock(app)
@app.route('/set-cookie')
@app.route("/set-cookie")
def set_cookie():
response = jsonify({})
response.set_cookie('cookie', 'value')
response.set_cookie("cookie", "value")
return response
@app.route('/redirect/<path:url>')
@app.route("/redirect/<path:url>")
def r(url):
return redirect(url)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def echo(path):
return jsonify({
'method': request.method,
'args': request.args,
'path': request.path,
})
return jsonify(
{
"method": request.method,
"args": request.args,
"path": request.path,
}
)
@sock.route('/socket')
@sock.route("/socket")
def ws_echo(ws):
while True:
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:
def test_success(
self,
processes,
processes: ProcessManager,
echo_server_port,
timeout,
shared_wg: WarpgateProcess,
):
proc, _ = processes.start_wg(
config=dedent(
f'''\
users: []
targets:
- name: target
allow_roles: [role]
http:
url: http://localhost:{echo_server_port}
'''
),
args=['test-target', 'target'],
)
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
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,
},
},
},
)
proc = processes.start_wg(
share_with=shared_wg,
args=["test-target", echo_target["name"]],
).process
proc.wait(timeout=timeout)
assert proc.returncode == 0
def test_fail_no_connection(self, processes, timeout):
proc, _ = processes.start_wg(
config=dedent(
'''\
users: []
targets:
- name: target
allow_roles: [role]
http:
url: http://localhostbaddomain
'''
),
args=['test-target', 'target'],
)
def test_fail_no_connection(
self, processes: ProcessManager, timeout, shared_wg: WarpgateProcess
):
url = f"https://localhost:{shared_wg.http_port}"
with api_admin_session(url) as session:
echo_target = api_create_target(
url,
session,
{
"name": f"echo-{uuid4()}",
"options": {
"kind": "Http",
"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)
assert proc.returncode != 0

View file

@ -1,31 +1,75 @@
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 TestHTTPCookies:
def test(
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.verify = False
url = f'https://localhost:{http_common_wg_port}'
headers = {'Host': f'localhost:{http_common_wg_port}'}
headers = {"Host": f"localhost:{shared_wg.http_port}"}
session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'user',
'password': '123',
"username": user["username"],
"password": "123",
},
headers=headers,
)
response = session.get(f'{url}/set-cookie?warpgate-target=echo', headers=headers)
print(response.headers)
session.get(
f"{url}/set-cookie?warpgate-target={echo_target['name']}", headers=headers
)
cookies = session.cookies.get_dict()
assert cookies['cookie'] == 'value'
assert cookies["cookie"] == "value"

View file

@ -1,30 +1,75 @@
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:
def test(
self,
http_common_wg_port,
shared_wg: WarpgateProcess,
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.verify = False
url = f'https://localhost:{http_common_wg_port}'
headers = {'Host': f'localhost:{http_common_wg_port}'}
headers = {"Host": f"localhost:{shared_wg.http_port}"}
session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'user',
'password': '123',
"username": user["username"],
"password": "123",
},
headers=headers,
)
response = session.get(f'{url}/redirect/http://localhost:{echo_server_port}/test?warpgate-target=echo', headers=headers, allow_redirects=False)
print(response.headers)
response = session.get(
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
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 Test:
def test(
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.verify = False
url = f'https://localhost:{http_common_wg_port}'
response = session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'user',
'password': '123',
"username": user["username"],
"password": "123",
},
)
assert response.status_code // 100 == 2
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.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

View file

@ -1,78 +1,173 @@
import requests
import pyotp
from base64 import b64decode
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 TestHTTPUserAuthOTP:
def test_auth_otp_success(
self,
http_common_wg_port,
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.verify = False
url = f'https://localhost:{http_common_wg_port}'
totp = pyotp.TOTP(otp_key_base32)
response = session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'userwithotp',
'password': '123',
"username": user["username"],
"password": "123",
},
)
assert response.status_code // 100 != 2
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
response = session.post(
f'{url}/@warpgate/api/auth/otp',
f"{url}/@warpgate/api/auth/otp",
json={
'otp': totp.now(),
"otp": totp.now(),
},
)
assert response.status_code // 100 == 2
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.json()['path'] == '/some/path'
assert response.json()["path"] == "/some/path"
def test_auth_otp_fail(
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.verify = False
url = f'https://localhost:{http_common_wg_port}'
response = session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'userwithotp',
'password': '123',
"username": user["username"],
"password": "123",
},
)
assert response.status_code // 100 != 2
response = session.post(
f'{url}/@warpgate/api/auth/otp',
f"{url}/@warpgate/api/auth/otp",
json={
'otp': '00000000',
"otp": "00000000",
},
)
assert response.status_code // 100 != 2
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

View file

@ -1,56 +1,135 @@
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 TestHTTPUserAuthPassword:
def test_auth_password_success(
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.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
response = session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'user',
'password': '123',
"username": user["username"],
"password": "123",
},
)
assert response.status_code // 100 == 2
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.json()['path'] == '/some/path'
assert response.json()["path"] == "/some/path"
def test_auth_password_fail(
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.verify = False
url = f'https://localhost:{http_common_wg_port}'
response = session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'user',
'password': '321321',
"username": user["username"],
"password": "321321",
},
)
assert response.status_code // 100 != 2
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

View file

@ -1,17 +1,76 @@
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:
def test_auth_password_success(
self,
http_common_wg_port,
echo_server_port,
shared_wg: WarpgateProcess,
):
wait_port(http_common_wg_port, recv=False)
url = f'https://localhost:{http_common_wg_port}'
secret = create_ticket(url, 'user', 'echo')
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,
},
},
},
)
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
response = session.get(
f'{url}/some/path?warpgate-target=echo',
f"{url}/some/path?warpgate-target={echo_target['name']}",
allow_redirects=False,
)
assert response.status_code // 100 != 2
# Ticket as a header
response = session.get(
f'{url}/some/path?warpgate-target=echo',
f"{url}/some/path?warpgate-target={echo_target['name']}",
allow_redirects=False,
headers={
'Authorization': f'Warpgate {secret}',
"Authorization": f"Warpgate {secret}",
},
)
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
session = requests.Session()
session.verify = False
response = session.get(
f'{url}/some/path?warpgate-ticket={secret}',
f"{url}/some/path?warpgate-ticket={secret}",
allow_redirects=False,
)
assert response.status_code // 100 == 2
assert response.json()['path'] == '/some/path'
assert response.json()["path"] == "/some/path"
# Ensure no access to other targets
session = requests.Session()
session.verify = False
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,
)
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 requests
from websocket import create_connection
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 TestHTTPWebsocket:
def test_basic(
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.verify = False
url = f'https://localhost:{http_common_wg_port}'
session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'user',
'password': '123',
"username": user["username"],
"password": "123",
},
)
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(
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,
sslopt={"cert_reqs": ssl.CERT_NONE},
)
ws.send('test')
assert ws.recv() == 'test'
ws.send_binary(b'test')
assert ws.recv() == b'test'
ws.send("test")
assert ws.recv() == "test"
ws.send_binary(b"test")
assert ws.recv() == b"test"
ws.ping()
ws.close()

View file

@ -1,74 +1,104 @@
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
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()
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(
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}'
'''
),
)
time.sleep(15)
wait_mysql_port(db_port)
wait_port(wg_ports['mysql'])
wait_port(shared_wg.mysql_port, recv=False)
client = processes.start(
[
'mysql',
'--user',
'user#db',
'-p123',
'--host',
'127.0.0.1',
'--port',
str(wg_ports["mysql"]),
"mysql",
"--user",
f"{user['username']}#{target['name']}",
"-p123",
"--host",
"127.0.0.1",
"--port",
str(shared_wg.mysql_port),
*mysql_client_opts,
mysql_client_ssl_opt,
'db',
"db",
],
stdin=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
client = processes.start(
[
'mysql',
'--user',
'user:db',
'-pwrong',
'--host',
'127.0.0.1',
'--port',
str(wg_ports["mysql"]),
"mysql",
"--user",
f"{user['username']}#{target['name']}",
"-pwrong",
"--host",
"127.0.0.1",
"--port",
str(shared_wg.mysql_port),
*mysql_client_opts,
mysql_client_ssl_opt,
'db',
"db",
],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
)
client.communicate(b'show schemas;', timeout=timeout)
client.communicate(b"show schemas;", timeout=timeout)
assert client.returncode != 0

View file

@ -1,50 +1,81 @@
from pathlib import Path
import subprocess
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
class Test:
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(
trusted_keys=[wg_c_ed25519_pubkey.read_text()]
)
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):
ssh_port = alloc_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'],
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(
share_with=shared_wg,
args=["test-target", ssh_target["name"]],
)
proc.wait(timeout=timeout)
assert proc.returncode != 0
wg.process.wait(timeout=timeout)
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 subprocess
import tempfile
@ -5,192 +6,242 @@ import time
import pytest
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
@pytest.fixture(scope='class')
@pytest.fixture(scope="session")
def ssh_port(processes, wg_c_ed25519_pubkey):
yield processes.start_ssh_server(trusted_keys=[wg_c_ed25519_pubkey.read_text()])
@pytest.fixture(scope='class')
def wg_port(processes, ssh_port, password_123_hash):
_, wg_ports = processes.start_wg(
dedent(
f'''\
targets:
- name: ssh
allow_roles: [role]
ssh:
host: 127.0.0.1
port: {ssh_port}
- name: ssh-bad-domain
allow_roles: [role]
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()}
'''
),
common_args = [
"-i",
"/dev/null",
"-o",
"PreferredAuthentications=password",
]
def setup_user_and_target(
processes: ProcessManager, wg: WarpgateProcess, wg_c_ed25519_pubkey
):
ssh_port = processes.start_ssh_server(
trusted_keys=[wg_c_ed25519_pubkey.read_text()]
)
wait_port(ssh_port)
wait_port(wg_ports['ssh'])
yield wg_ports['ssh']
common_args = [
'user:ssh@localhost',
'-i',
'/dev/null',
'-o',
'PreferredAuthentications=password',
]
url = f"https://localhost:{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": "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:
def test_stdout_stderr(
self,
processes: ProcessManager,
wg_port,
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(
'-p',
str(wg_port),
f"{user['username']}:{ssh_target['name']}@localhost",
"-p",
str(shared_wg.ssh_port),
*common_args,
'sh',
'-c',
"sh",
"-c",
'"echo -n stdout; echo -n stderr >&2"',
password='123',
password="123",
stderr=subprocess.PIPE,
)
stdout, stderr = ssh_client.communicate(timeout=timeout)
assert b'stdout' == stdout
assert stderr.endswith(b'stderr')
assert b"stdout" == stdout
assert stderr.endswith(b"stderr")
def test_pty(
self,
processes: ProcessManager,
wg_port,
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(
'-p',
str(wg_port),
'-tt',
f"{user['username']}:{ssh_target['name']}@localhost",
"-p",
str(shared_wg.ssh_port),
"-tt",
*common_args,
'echo',
'hello',
password='123',
"echo",
"hello",
password="123",
)
output = ssh_client.communicate(timeout=timeout)[0]
assert b'Warpgate' in output
assert b'Selected target:' in output
assert b'hello\r\n' in output
assert b"Warpgate" in output
assert b"Selected target:" in output
assert b"hello\r\n" in output
def test_signals(
self,
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(
'-p',
str(wg_port),
'-v',
f"{user['username']}:{ssh_target['name']}@localhost",
"-p",
str(shared_wg.ssh_port),
"-v",
*common_args,
'sh', '-c',
"sh",
"-c",
'"pkill -9 sh"',
password='123',
password="123",
)
assert ssh_client.returncode != 0
# def test_direct_tcpip(
# self,
# processes: ProcessManager,
# wg_port,
# timeout,
# ):
# local_port = alloc_port()
# wait_port(wg_port)
# ssh_client = processes.start_ssh_client(
# '-p',
# str(wg_port),
# '-v',
# *common_args,
# '-L', f'{local_port}:neverssl.com:80',
# '-N',
# password='123',
# )
def test_direct_tcpip(
self,
processes: ProcessManager,
wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
timeout,
):
user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
local_port = alloc_port()
ssh_client = processes.start_ssh_client(
f"{user['username']}:{ssh_target['name']}@localhost",
"-p",
str(shared_wg.ssh_port),
"-v",
*common_args,
"-L",
f"{local_port}:neverssl.com:80",
"-N",
password="123",
)
# wait_port(local_port, recv=False)
# 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
time.sleep(10)
# s = requests.Session()
# retries = requests.adapters.Retry(total=5, backoff_factor=1)
# s.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries))
# response = s.get(f'http://localhost:{local_port}', timeout=timeout)
# assert response.status_code == 200
# ssh_client.kill()
wait_port(local_port, recv=False)
s = requests.Session()
retries = requests.adapters.Retry(total=5, backoff_factor=1)
s.mount("http://", requests.adapters.HTTPAdapter(max_retries=retries))
response = s.get(f"http://localhost:{local_port}", timeout=timeout)
assert response.status_code == 200
ssh_client.kill()
def test_tcpip_forward(
self,
processes: ProcessManager,
wg_port,
wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
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(
'-p',
str(wg_port),
'-v',
f"{user['username']}:{ssh_target['name']}@localhost",
"-p",
str(shared_wg.ssh_port),
"-v",
*common_args,
'-R', '1234:neverssl.com:80',
'-N',
password='123',
"-R",
"1234:neverssl.com:80",
"-N",
password="123",
)
time.sleep(5)
ssh_client = processes.start_ssh_client(
'-p',
str(wg_port),
'-v',
f"{user['username']}:{ssh_target['name']}@localhost",
"-p",
str(shared_wg.ssh_port),
"-v",
*common_args,
'curl', '-v', 'http://localhost:1234',
password='123',
"curl",
"-v",
"http://localhost:1234",
password="123",
)
output = ssh_client.communicate(timeout=timeout)[0]
print(output)
assert ssh_client.returncode == 0
assert b'<html>' in output
assert b"<html>" in output
pf_client.kill()
def test_shell(
self,
processes: ProcessManager,
wg_port,
wg_c_ed25519_pubkey,
shared_wg: WarpgateProcess,
timeout,
):
user, ssh_target = setup_user_and_target(
processes, shared_wg, wg_c_ed25519_pubkey
)
script = dedent(
f'''
f"""
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:"
sleep 0.5
@ -207,11 +258,11 @@ class Test:
}}
exit 1
'''
"""
)
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]
@ -220,48 +271,58 @@ class Test:
def test_connection_error(
self,
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(
'-p',
str(wg_port),
'-tt',
'user:ssh-bad-domain@localhost',
'-i',
'/dev/null',
'-o',
'PreferredAuthentications=password',
password='123',
f"{user['username']}:{ssh_target['name']}@localhost",
"-p",
str(shared_wg.ssh_port),
"-tt",
"user:ssh-bad-domain@localhost",
"-i",
"/dev/null",
"-o",
"PreferredAuthentications=password",
password="123",
)
assert ssh_client.returncode != 0
def test_sftp(
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:
subprocess.check_call(
[
'sftp',
'-P',
str(wg_port),
'-o',
'User=user:ssh',
'-o',
'IdentitiesOnly=yes',
'-o',
'IdentityFile=ssh-keys/id_ed25519',
'-o',
'PreferredAuthentications=publickey',
'-o',
'StrictHostKeychecking=no',
'-o',
'UserKnownHostsFile=/dev/null',
'localhost:/etc/passwd',
"sftp",
"-P",
str(shared_wg.ssh_port),
"-o",
f"User={user['username']}:{ssh_target['name']}",
"-o",
"IdentitiesOnly=yes",
"-o",
"IdentityFile=ssh-keys/id_ed25519",
"-o",
"PreferredAuthentications=publickey",
"-o",
"StrictHostKeychecking=no",
"-o",
"UserKnownHostsFile=/dev/null",
"localhost:/etc/passwd",
f,
],
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 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
class Test:
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(
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(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(
'-t',
'user:badtarget@localhost',
'-p',
str(wg_ports['ssh']),
'-i',
'/dev/null',
'-o',
'PreferredAuthentications=password',
'echo',
'hello',
password='123',
"-t",
f"{user['username']}:badtarget@localhost",
"-p",
str(shared_wg.ssh_port),
"-i",
"/dev/null",
"-o",
"PreferredAuthentications=password",
"echo",
"hello",
stderr=subprocess.PIPE,
password="123",
)
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 base64 import b64decode
from uuid import uuid4
import pyotp
from pathlib import Path
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
@ -15,44 +25,61 @@ class Test:
otp_key_base32: str,
otp_key_base64: str,
timeout,
shared_wg: WarpgateProcess,
):
ssh_port = processes.start_ssh_server(
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(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)
script = dedent(
f'''
f"""
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"
sleep 0.5
@ -62,21 +89,24 @@ class Test:
"/bin/sh" {{ exit 0; }}
eof {{ exit 1; }}
}}
'''
"""
)
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)
assert ssh_client.returncode == 0, output + stderr
script = dedent(
f'''
f"""
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"
sleep 0.5
@ -87,11 +117,11 @@ class Test:
"Two-factor authentication" {{ exit 1; }}
eof {{ exit 1; }}
}}
'''
"""
)
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]

View file

@ -1,67 +1,92 @@
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
class 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(
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(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(
'user:ssh@localhost',
'-v',
'-p',
str(wg_ports['ssh']),
'-i',
'/dev/null',
'-o',
'PreferredAuthentications=password',
'ls',
'/bin/sh',
password='123',
f'{user["username"]}:{ssh_target["name"]}@localhost',
"-v",
"-p",
str(shared_wg.ssh_port),
"-i",
"/dev/null",
"-o",
"PreferredAuthentications=password",
"ls",
"/bin/sh",
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
ssh_client = processes.start_ssh_client(
'user:ssh@localhost',
'-p',
str(wg_ports['ssh']),
'-i',
'/dev/null',
'-o',
'PreferredAuthentications=password',
'ls',
'/bin/sh',
password='321',
f'{user["username"]}:{ssh_target["name"]}@localhost',
"-p",
str(shared_wg.ssh_port),
"-i",
"/dev/null",
"-o",
"PreferredAuthentications=password",
"ls",
"/bin/sh",
password="321",
)
ssh_client.communicate(timeout=timeout)
assert ssh_client.returncode != 0

View file

@ -1,125 +1,169 @@
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
class Test:
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(
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(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(
'user:ssh@localhost',
'-p',
str(wg_ports['ssh']),
'-o',
'IdentityFile=ssh-keys/id_ed25519',
'-o',
'PreferredAuthentications=publickey',
f'{user["username"]}:{ssh_target["name"]}@localhost',
"-p",
str(shared_wg.ssh_port),
"-o",
"IdentityFile=ssh-keys/id_ed25519",
"-o",
"PreferredAuthentications=publickey",
# 'sh', '-c', '"ls /bin/sh;sleep 1"',
'ls',
'/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
ssh_client = processes.start_ssh_client(
'user:ssh@localhost',
'-p',
str(wg_ports['ssh']),
'-o',
'IdentityFile=ssh-keys/id_rsa',
'-o',
'PreferredAuthentications=publickey',
'ls',
'/bin/sh',
f'{user["username"]}:{ssh_target["name"]}@localhost',
"-p",
str(shared_wg.ssh_port),
"-o",
"IdentityFile=ssh-keys/id_rsa",
"-o",
"PreferredAuthentications=publickey",
"ls",
"/bin/sh",
)
assert ssh_client.communicate(timeout=timeout)[0] == b''
assert ssh_client.communicate(timeout=timeout)[0] == b""
assert ssh_client.returncode != 0
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(
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(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(
'user:ssh@localhost',
'-v',
'-p',
str(wg_ports['ssh']),
'-o',
'IdentityFile=ssh-keys/id_rsa',
'-o',
'PreferredAuthentications=publickey',
'-o', 'PubkeyAcceptedKeyTypes=+ssh-rsa',
'ls',
'/bin/sh',
f'{user["username"]}:{ssh_target["name"]}@localhost',
"-v",
"-p",
str(shared_wg.ssh_port),
"-o",
"IdentityFile=ssh-keys/id_rsa",
"-o",
"PreferredAuthentications=publickey",
"-o",
"PubkeyAcceptedKeyTypes=+ssh-rsa",
"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
ssh_client = processes.start_ssh_client(
'user:ssh@localhost',
'-p',
str(wg_ports['ssh']),
'-o',
'IdentityFile=ssh-keys/id_ed25519',
'-o',
'PreferredAuthentications=publickey',
'-o', 'PubkeyAcceptedKeyTypes=+ssh-rsa',
'ls',
'/bin/sh',
f'{user["username"]}:{ssh_target["name"]}@localhost',
"-p",
str(shared_wg.ssh_port),
"-o",
"IdentityFile=ssh-keys/id_ed25519",
"-o",
"PreferredAuthentications=publickey",
"-o",
"PubkeyAcceptedKeyTypes=+ssh-rsa",
"ls",
"/bin/sh",
)
assert ssh_client.communicate(timeout=timeout)[0] == b''
assert ssh_client.communicate(timeout=timeout)[0] == b""
assert ssh_client.returncode != 0

View file

@ -1,63 +1,80 @@
from pathlib import Path
from textwrap import dedent
from uuid import uuid4
from .conftest import ProcessManager
from .util import create_ticket, wait_port
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_ticket,
api_create_user,
)
from .conftest import ProcessManager, WarpgateProcess
from .util import wait_port
class 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(
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(wg_ports['ssh'])
wait_port(wg_ports['http'], recv=False)
url = f'https://localhost:{wg_ports["http"]}'
secret = create_ticket(url, 'user', '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"])
secret = api_create_ticket(
url, session, user["username"], ssh_target["name"]
)
ssh_client = processes.start_ssh_client(
f'ticket-{secret}@localhost',
'-p',
str(wg_ports['ssh']),
'-i',
'/dev/null',
'-o',
'PreferredAuthentications=password',
'ls',
'/bin/sh',
password='123',
)
assert ssh_client.communicate(timeout=timeout)[0] == b'/bin/sh\n'
assert ssh_client.returncode == 0
ssh_client = processes.start_ssh_client(
f"ticket-{secret}@localhost",
"-p",
str(shared_wg.ssh_port),
"-i",
"/dev/null",
"-o",
"PreferredAuthentications=password",
"ls",
"/bin/sh",
password="123",
)
assert ssh_client.communicate(timeout=timeout)[0] == b"/bin/sh\n"
assert ssh_client.returncode == 0

View file

@ -9,12 +9,12 @@ import time
last_port = 1234
mysql_client_ssl_opt = '--ssl'
mysql_client_ssl_opt = "--ssl"
mysql_client_opts = []
if 'GITHUB_ACTION' in os.environ:
if "GITHUB_ACTION" in os.environ:
# Github uses MySQL instead of MariaDB
mysql_client_ssl_opt = '--ssl-mode=REQUIRED'
mysql_client_opts = ['--enable-cleartext-plugin']
mysql_client_ssl_opt = "--ssl-mode=REQUIRED"
mysql_client_opts = ["--enable-cleartext-plugin"]
def alloc_port():
@ -23,46 +23,55 @@ def alloc_port():
return last_port
def wait_port(port, recv=True, timeout=60):
logging.debug(f'Waiting for port {port}')
def wait_port(port, recv=True, timeout=60, for_process: subprocess.Popen = None):
logging.debug(f"Waiting for port {port}")
data = b''
data = b""
def wait():
nonlocal data
while True:
try:
s = socket.create_connection(('localhost', port), timeout=5)
s = socket.create_connection(("localhost", port), timeout=5)
if recv:
while True:
data = s.recv(100)
if data:
break
else:
data = b''
data = b""
s.close()
logging.debug(f'Port {port} is up')
logging.debug(f"Port {port} is up")
return data
except socket.error:
time.sleep(0.1)
continue
if for_process:
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.start()
t.join(timeout=timeout)
if t.is_alive():
raise Exception(f'Port {port} is not up')
raise Exception(f"Port {port} is not up")
return data
def wait_mysql_port(port):
logging.debug(f'Waiting for MySQL port {port}')
logging.debug(f"Waiting for MySQL port {port}")
def wait():
while True:
try:
subprocess.check_call(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')
subprocess.check_call(
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
except subprocess.CalledProcessError:
time.sleep(1)
@ -72,26 +81,26 @@ def wait_mysql_port(port):
t.start()
t.join(timeout=60)
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):
session = requests.Session()
session.verify = False
response = session.post(
f'{url}/@warpgate/api/auth/login',
f"{url}/@warpgate/api/auth/login",
json={
'username': 'admin',
'password': '123',
"username": "admin",
"password": "123",
},
)
assert response.status_code // 100 == 2
response = session.post(
f'{url}/@warpgate/admin/api/tickets',
f"{url}/@warpgate/admin/api/tickets",
json={
'username': username,
'target_name': target_name,
"username": username,
"target_name": target_name,
},
)
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 {
id: Set(Uuid::new_v4()),
username: Set(body.username.clone()),
credentials: Set(
serde_json::to_value(body.credentials.clone()).map_err(WarpgateError::from)?
),
credentials: Set(serde_json::to_value(process_credentials(&body.credentials))
.map_err(WarpgateError::from)?),
credential_policy: Set(serde_json::to_value(body.credential_policy.clone())
.map_err(WarpgateError::from)?),
};
@ -167,7 +166,8 @@ impl DetailApi {
let mut model: User::ActiveModel = user.into();
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 =
Set(serde_json::to_value(body.credential_policy.clone())
.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)]
enum GetUserRolesResponse {
#[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 Some(ssh_port) = ssh_port {
store.ssh.enable = true;
store.ssh.listen = ListenEndpoint(SocketAddr::from(([0, 0, 0, 0], *ssh_port)));
if let Commands::UnattendedSetup { mysql_port, .. } = &cli.command {
if let Some(mysql_port) = mysql_port {
store.mysql.enable = true;
store.mysql.listen = ListenEndpoint(SocketAddr::from(([0, 0, 0, 0], *mysql_port)));
}
} else {
if !is_docker() {