mirror of
https://github.com/warp-tech/warpgate.git
synced 2024-09-20 06:46:17 +08:00
updated tests
This commit is contained in:
parent
8087179ea0
commit
b7a08996d8
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -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
92
tests/api_client.py
Normal 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"]
|
|
@ -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
172
tests/poetry.lock
generated
|
@ -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"},
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue