1
0
forked from clan/clan-core

Compare commits

...

3 Commits

Author SHA1 Message Date
246457ffa2 vm-manager: fix test discover 2024-04-12 13:01:49 +02:00
bd5fd8b7b1 clan-vm-manager: Removing exit 2024-04-12 12:58:46 +02:00
54e9439f0a clan-vm-manager: Add working vnc_client.py 2024-04-12 12:52:28 +02:00
25 changed files with 3244 additions and 32 deletions

View File

@ -1,4 +1,7 @@
{ {
lib,
stdenv,
weston,
python3, python3,
runCommand, runCommand,
setuptools, setuptools,
@ -14,8 +17,7 @@
libadwaita, libadwaita,
pytest, # Testing framework pytest, # Testing framework
pytest-cov, # Generate coverage reports pytest-cov, # Generate coverage reports
pytest-subprocess, # fake the real subprocess behavior to make your tests more independent. pytest-subprocess, # fake the real subprocess behavior to make your tests more independent. # Run tests in parallel on multiple cores
pytest-xdist, # Run tests in parallel on multiple cores
pytest-timeout, # Add timeouts to your tests pytest-timeout, # Add timeouts to your tests
}: }:
let let
@ -52,9 +54,9 @@ let
pytest # Testing framework pytest # Testing framework
pytest-cov # Generate coverage reports pytest-cov # Generate coverage reports
pytest-subprocess # fake the real subprocess behavior to make your tests more independent. pytest-subprocess # fake the real subprocess behavior to make your tests more independent.
pytest-xdist # Run tests in parallel on multiple cores
pytest-timeout # Add timeouts to your tests pytest-timeout # Add timeouts to your tests
]; ]
++ (lib.optionals stdenv.isLinux [ weston ]);
# Dependencies required for running tests # Dependencies required for running tests
testDependencies = runtimeDependencies ++ allPythonDeps ++ externalTestDeps; testDependencies = runtimeDependencies ++ allPythonDeps ++ externalTestDeps;

View File

@ -19,7 +19,7 @@ testpaths = "tests"
faulthandler_timeout = 60 faulthandler_timeout = 60
log_level = "DEBUG" log_level = "DEBUG"
log_format = "%(levelname)s: %(message)s\n %(pathname)s:%(lineno)d::%(funcName)s" log_format = "%(levelname)s: %(message)s\n %(pathname)s:%(lineno)d::%(funcName)s"
addopts = "--cov . --cov-report term --cov-report html:.reports/html --no-cov-on-fail --durations 5 --color=yes --new-first" # Add --pdb for debugging addopts = "--cov . --cov-report term --cov-report html:.reports/html --no-cov-on-fail --durations 5 --color=yes -vv --new-first" # Add --pdb for debugging
norecursedirs = "tests/helpers" norecursedirs = "tests/helpers"
markers = ["impure"] markers = ["impure"]
@ -29,11 +29,17 @@ warn_redundant_casts = true
disallow_untyped_calls = true disallow_untyped_calls = true
disallow_untyped_defs = true disallow_untyped_defs = true
no_implicit_optional = true no_implicit_optional = true
exclude = '^tests/helpers/libvncclient.py$'
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = "clan_cli.*" module = "clan_cli.*"
ignore_missing_imports = true ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "libvncclient.*"
ignore_missing_imports = true
[tool.ruff] [tool.ruff]
target-version = "py311" target-version = "py311"
line-length = 88 line-length = 88

View File

@ -10,6 +10,8 @@
python3, python3,
gtk4, gtk4,
libadwaita, libadwaita,
tigervnc,
libvncserver,
}: }:
let let
@ -31,6 +33,9 @@ mkShell {
ruff ruff
gtk4.dev # has the demo called 'gtk4-widget-factory' gtk4.dev # has the demo called 'gtk4-widget-factory'
libadwaita.devdoc # has the demo called 'adwaita-1-demo' libadwaita.devdoc # has the demo called 'adwaita-1-demo'
tigervnc
libvncserver.dev
libvncserver
] ]
++ devshellTestDeps ++ devshellTestDeps
@ -40,14 +45,28 @@ mkShell {
desktop-file-utils # verify desktop files desktop-file-utils # verify desktop files
]); ]);
# Use ipdb as the default debugger for python
PYTHONBREAKPOINT = "ipdb.set_trace";
hardeningDisabled = "all";
shellHook = '' shellHook = ''
export LIBVNC_INCLUDE=${libvncserver.dev}/include
export LIBVNC_LIB=${libvncserver}/lib
export GIT_ROOT=$(git rev-parse --show-toplevel) export GIT_ROOT=$(git rev-parse --show-toplevel)
export PKG_ROOT=$GIT_ROOT/pkgs/clan-vm-manager
# Add clan-vm-manager command to PATH # Add clan-vm-manager command to PATH
export PATH="$PKG_ROOT/bin":"$PATH" export PATH="$GIT_ROOT/pkgs/clan-vm-manager/bin":"$PATH"
# Add clan-vm-manager to the python path so that we can
# import it in the tests
export PYTHONPATH="$GIT_ROOT/pkgs/clan-vm-manager":"$PYTHONPATH"
# Add clan-cli to the python path so that we can import it without building it in nix first # Add clan-cli to the python path so that we can import it without building it in nix first
export PYTHONPATH="$GIT_ROOT/pkgs/clan-cli":"$PYTHONPATH" export PYTHONPATH="$GIT_ROOT/pkgs/clan-cli":"$PYTHONPATH"
# Add clan-cli to the PATH
export PATH="$GIT_ROOT/pkgs/clan-cli/bin":"$PATH"
''; '';
} }

View File

@ -7,9 +7,6 @@ from clan_cli.custom_logger import setup_logging
from clan_cli.nix import nix_shell from clan_cli.nix import nix_shell
sys.path.append(str(Path(__file__).parent / "helpers")) sys.path.append(str(Path(__file__).parent / "helpers"))
sys.path.append(
str(Path(__file__).parent.parent)
) # Also add clan vm manager to PYTHONPATH
pytest_plugins = [ pytest_plugins = [
"temporary_dir", "temporary_dir",

View File

@ -0,0 +1,12 @@
-----BEGIN X509 CRL-----
MIIB2zCBxAIBATANBgkqhkiG9w0BAQsFADCBgDELMAkGA1UEBhMCVVMxEjAQBgNV
BAgMCVlvdXJTdGF0ZTERMA8GA1UEBwwIWW91ckNpdHkxGzAZBgNVBAoMEllvdXJD
QU9yZ2FuaXphdGlvbjEZMBcGA1UECwwQWW91ckNBRGVwYXJ0bWVudDESMBAGA1UE
AwwJMTI3LjAuMC4xFw0yNDA0MDExMzQ4MTBaFw0yNDA1MDExMzQ4MTBaoA8wDTAL
BgNVHRQEBAICEAAwDQYJKoZIhvcNAQELBQADggEBAKlXZYq3euvI5jdDreK6o9YQ
VtdLzdf/J0kQF8MVFjPo+x585xKYu2YgFLBmvZ5Jfpi6MjYH6E/liJdT1be16lDo
/CYy+55UdUgm61rordF+ddWQBcr4+Nw9k9ZQIdOMtHbleLRwk/hLvJ3d/3A+emVb
vs2I07tG+hfqkR2NlY5ff4cutd2WdVCcHYw0cF2S785cY+LfDz92bT03lswfm0qz
PoJbKtk2FYRcpddC3KxQl4gSDzmyiWEMuzOHDflMJO0K5OPvuzTc9JChS0n+Qb2y
klFtCZskKDy1NpM+pLH/RgFmivs8xiLAOsyk5/Ln5itHH1lm/pSaaj2XtI4aTSk=
-----END X509 CRL-----

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID9DCCAtygAwIBAgIURrbmCTlyih5uvTVoknewtlE0M6QwDQYJKoZIhvcNAQEL
BQAwgYAxCzAJBgNVBAYTAlVTMRIwEAYDVQQIDAlZb3VyU3RhdGUxETAPBgNVBAcM
CFlvdXJDaXR5MRswGQYDVQQKDBJZb3VyQ0FPcmdhbml6YXRpb24xGTAXBgNVBAsM
EFlvdXJDQURlcGFydG1lbnQxEjAQBgNVBAMMCTEyNy4wLjAuMTAeFw0yNDAzMjgx
MTUwMDFaFw0zNDAzMjYxMTUwMDFaMIGAMQswCQYDVQQGEwJVUzESMBAGA1UECAwJ
WW91clN0YXRlMREwDwYDVQQHDAhZb3VyQ2l0eTEbMBkGA1UECgwSWW91ckNBT3Jn
YW5pemF0aW9uMRkwFwYDVQQLDBBZb3VyQ0FEZXBhcnRtZW50MRIwEAYDVQQDDAkx
MjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDcb6ZpdEVi
D/TAdMtpI/lLOLBnBuA5+5+SEbq2M3U+7tmZ1GxhuJhFddP+JbLq84X5BzFYS3Be
Hn9W+iHOr58BvAdFi6lS1H7w5ZwRPXsZ+/HlydWBszKmTi7h8ByB5UnOezduRc26
gT6zPLtMXuUCAN9dAmThCA2G74EwUTJyZYOql+8wM3BbfE6wcnVdkADQcqFs86C4
0o9qitOoCXmtdd9TVDkyLcQMgxEx1W3LHvH2lNpp60T/FetilrdqsICaon/IbiYf
XeUtqUE2L61Os8Y0FqRxquMIgYm+vPtTOQk8v4slNmsnl/veFY+SpNt3pY1FNK6b
n1XaZop9p2pLAgMBAAGjZDBiMB0GA1UdDgQWBBReyxFuGLwIL/Q/dJXUSv51s4my
6TAfBgNVHSMEGDAWgBReyxFuGLwIL/Q/dJXUSv51s4my6TAPBgNVHRMBAf8EBTAD
AQH/MA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBAHC+9xYh9P5v
wGr1HY+tQdDzc0xpsXGtsHbUK859OJXI8HWew6+iAefMrnKBYBXphnmp5IolHlP+
qaUs2Y7Lih+z28skQh2kpJHnJMKbAN0MLucA6NTbRdQD7EyAO91cuZQ7PS3DyAvy
DosjgK4aMvpd07cbYLCTqaczz4KN/EdjMcvK2QelqtzZphGq+5meQDafLXBtf+CG
MmTh9szwYzzk7dGurNdsgugNNvk/X0p074SWjLvsK5FCuRJEh23Wx3bWBDMiVUcr
RrgVjJ47Jyeozto4whOglQRk9hc2VBjmeXBYlmi+PorcRFZrvAdWilBCsi37ki2G
qTrAwVx06AE=
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcb6ZpdEViD/TA
dMtpI/lLOLBnBuA5+5+SEbq2M3U+7tmZ1GxhuJhFddP+JbLq84X5BzFYS3BeHn9W
+iHOr58BvAdFi6lS1H7w5ZwRPXsZ+/HlydWBszKmTi7h8ByB5UnOezduRc26gT6z
PLtMXuUCAN9dAmThCA2G74EwUTJyZYOql+8wM3BbfE6wcnVdkADQcqFs86C40o9q
itOoCXmtdd9TVDkyLcQMgxEx1W3LHvH2lNpp60T/FetilrdqsICaon/IbiYfXeUt
qUE2L61Os8Y0FqRxquMIgYm+vPtTOQk8v4slNmsnl/veFY+SpNt3pY1FNK6bn1Xa
Zop9p2pLAgMBAAECggEAOHy77iYHaZuhInhLb8PyLB54xc3zQ6iBOZMlf28sSlY2
mL7gjyIYkyQgkO3kLWT+HdSEBpY+U0AJbaZnZ2mFm5ItYtrpJvqhFOYh2iEhHIV9
dV8FQVlET22VgfxfscGp6imVCMMGdxaLlK6paGag1KSYmGL2qtu/a6aQOmt0O+/h
iWYSpl2Rgdwi85fyEay/ziccmPKbkJEt4yjqhcEjzxZi+NDYvqEkdCa47pKZB6mY
5E8Eh+HdMaBNx1tVcs5gclQBm4PjTLFPpZohbVZiv88hvP79RYlIGHvUiWkEN4A7
6uGgJEcCH85SHNEE6Bfg9kD9nW2Lq4jvDJ0tMhibQQKBgQD+E13YmewzzROy8UKY
FxN8gqHWSkRJ5v1vP/z7WSibGOpkhLwRL2p4HHPvpU6F1272E3cPSHEDZ7QbrBn1
gWfCFk8zyj/LxEtvDsfEYL/ShKvDVXBaKmb8llILIR1LbtqEKu+hkuEDSmaFv9Gw
nGcfj3B2RN4LlZmmRQE0Adf4UQKBgQDeGw8ZyA9yd1XYsdHoh+YxZVZv+mxxU1SO
AYlmEBSFbrzl+zMg+exgb5GRmYWKSqhSvw0yGjcy37SAXDK4+faXyZ2YR2NjVzDh
4Odqu4aYwE7HU5Y/9zpsn40qc9UylRDBkcX9Dekj1b9hRzj19nFNVBJGjgZRLgMV
57UgGxPt2wKBgHMzLsLuD4XxPzRMZch19hTnWh/CbrIfdNvDZJ5Gb73bDzPiZy9X
k2vAYuTOzAqtgpc6fipEy1Ei7Sv63Y5OTVBYMzMlScXHS/if9/3XbEI0e3jGvXl0
bluqgKqhKhowug1hNmPJKBMI4fFU5uuwDqXlsLU/Rnp0K0WTVhdRmq3xAoGAQTpz
KeAuYTCY3qYCfqcCvLkFNKe4F2Qgrf/XiUjprfJCucwXTPT5La02dCtBI8cfPgXr
6y31zhQS36u0Hc0TVaqZhPJaRv+BVKUHcboXIl9AA5wRwUFrQCFvhOs1zsAmhqK4
IcRnFuYcaYZQPTQePFaXc28cfdTkhRdig0ZQiQcCgYEAtUGFZHsKP+Ft23m4X9KR
nYBehkG/L3S9PinXIBQU/32R1lc2G/ziMYijh8p9fYlzYm3AkkFURfBPHvCm8nl8
xduLA2D/CPb1HelwcuPNSrK6bJbu/Knn73ki1DUDtOQI/MhUc83SudGYzkPJvZTU
r5QeLVBf34I4meNOzOOXmag=
-----END PRIVATE KEY-----

View File

@ -0,0 +1 @@
070517FB67B9D171836447F4C188BB6C1F52E712

View File

@ -0,0 +1 @@
1001

View File

@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
# Check if openssl is available
if ! command -v openssl &> /dev/null; then
echo "openssl is not installed. Please install openssl to generate the certificates."
exit 1
fi
# Step 0: Define CA subject details
CA_SUBJECT="/C=US/ST=YourState/L=YourCity/O=YourCAOrganization/OU=YourCADepartment/CN=127.0.0.1"
# Step 1: Generate the CA's private key
openssl genpkey -algorithm RSA -out ca.key -pkeyopt rsa_keygen_bits:2048
# Step 2: Create a self-signed CA certificate
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "$CA_SUBJECT" -addext "subjectAltName = IP:127.0.0.1"
# Step 3: Generate a private key for the TLS certificate
openssl genpkey -algorithm RSA -out tls.key -pkeyopt rsa_keygen_bits:2048
# Step 4: Create a Certificate Signing Request (CSR) for the TLS certificate
TLS_SUBJECT="/C=US/ST=YourState/L=YourCity/O=YourOrganization/OU=YourDepartment/CN=127.0.0.1"
openssl req -new -key tls.key -out tls.csr -subj "$TLS_SUBJECT" -addext "subjectAltName = IP:127.0.0.1"
# Step 5: Sign the CSR with your CA to generate the TLS certificate
openssl x509 -req -in tls.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt -days 365 -sha256 -extfile <(printf "subjectAltName=IP:127.0.0.1")
# Step 6: Generate a ca.crl file needed for libvncserver
openssl ca -config ./openssl.conf -gencrl -keyfile ca.key -cert ca.crt -out ca.crl
echo "CA and TLS certificate, CSR, and key have been generated."

View File

@ -0,0 +1,65 @@
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = . # Where everything is stored
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # Database index file
new_certs_dir = $dir/newcerts
certificate = $dir/cacert.pem # The CA certificate
serial = $dir/serial # The current serial number
crlnumber = $dir/crlnumber # The current crl number
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/cakey.pem
RANDFILE = $dir/private/.rand
x509_extensions = usr_cert # The extensions to add to the cert
crlnumber = $dir/crlnumber # the current crl number must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/cakey.pem
RANDFILE = $dir/private/.rand
default_days = 365 # How long to certify for
default_crl_days = 30 # How long before next CRL
default_md = sha256 # Which md to use.
preserve = no # Keep passed DN ordering
policy = policy_match
[ policy_match ]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 2048
default_md = sha256
prompt = no
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
[ req_distinguished_name ]
countryName = US
stateOrProvinceName = California
localityName = San Francisco
0.organizationName = My Organization
organizationalUnitName = My Organizational Unit
commonName = My CA
emailAddress = email@example.com
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ usr_cert ]
basicConstraints = CA:FALSE
nsComment = "OpenSSL Generated Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer

View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID3jCCAsagAwIBAgIUBwUX+2e50XGDZEf0wYi7bB9S5xIwDQYJKoZIhvcNAQEL
BQAwgYAxCzAJBgNVBAYTAlVTMRIwEAYDVQQIDAlZb3VyU3RhdGUxETAPBgNVBAcM
CFlvdXJDaXR5MRswGQYDVQQKDBJZb3VyQ0FPcmdhbml6YXRpb24xGTAXBgNVBAsM
EFlvdXJDQURlcGFydG1lbnQxEjAQBgNVBAMMCTEyNy4wLjAuMTAeFw0yNDAzMjgx
MTUwMDJaFw0yNTAzMjgxMTUwMDJaMHwxCzAJBgNVBAYTAlVTMRIwEAYDVQQIDAlZ
b3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MRkwFwYDVQQKDBBZb3VyT3JnYW5p
emF0aW9uMRcwFQYDVQQLDA5Zb3VyRGVwYXJ0bWVudDESMBAGA1UEAwwJMTI3LjAu
MC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwYSlR4nEj4LuNclm
QFfqei1FU+bvLggzWzNNGfhGiqArzV8F7snUGaGymA6ZbXAj//Z+XXDQ5tE4DTDP
fU2SD38VpswioCtco/9ke3zZXXvZqnUhryWMfNjpxNF4E3UVkOsWwvkLV5qODsGr
K9+0oXxUYHIfcryF1O7F2Kf0HeHdEP15EB6pwb9qW/YcH/+M06P8zOS4dv7etZKv
ntGQevlF3Xw6KNy7PZ3iQgmigctA/MlYHUoNXCRNL2Wq5/QnVTSJbf9p+WN5DHEn
p662DRZT/K0pzJETB3/Sqswi86S/+bnggEegPeRzVDz0k3qXgia2S254mxibkD9R
7yTYiwIDAQABo1MwUTAPBgNVHREECDAGhwR/AAABMB0GA1UdDgQWBBSoZWaF9pFA
JEKZcTGJYnUkf/kFjjAfBgNVHSMEGDAWgBReyxFuGLwIL/Q/dJXUSv51s4my6TAN
BgkqhkiG9w0BAQsFAAOCAQEAicFkgSl+smSTM3SK9O4f8OOBtcX1aF1XkLvXHXpt
JIXyPkJbW+44Vb8q/ARI/4Nss4AlWuCWy0vHAhhPmvqBl0sr7CL/jAfJTSq++7P3
XjfQ/l7hT26DejmqXMZxIF89EwZha0DmetEIYnowvp6oiFluuMGY2qOzF0NXZTAK
NvHrj5yT1bpq6qdPLcKszaR9+MYmVC0r4pAargfpjCr01lBRH1h+p5QEEf/tYmQP
BY0pWbgI3wnMPtFCD0fPLHhCJXnWTpGeFsAczOaF3YJicCKg+3EusiazwJihzYfY
FdTnJlD2KkP2ipIia5q7wja7SeWBV8hzPvEuqvlUW0BCKQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIC4zCCAcsCAQAwfDELMAkGA1UEBhMCVVMxEjAQBgNVBAgMCVlvdXJTdGF0ZTER
MA8GA1UEBwwIWW91ckNpdHkxGTAXBgNVBAoMEFlvdXJPcmdhbml6YXRpb24xFzAV
BgNVBAsMDllvdXJEZXBhcnRtZW50MRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBhKVHicSPgu41yWZAV+p6LUVT5u8u
CDNbM00Z+EaKoCvNXwXuydQZobKYDpltcCP/9n5dcNDm0TgNMM99TZIPfxWmzCKg
K1yj/2R7fNlde9mqdSGvJYx82OnE0XgTdRWQ6xbC+QtXmo4Owasr37ShfFRgch9y
vIXU7sXYp/Qd4d0Q/XkQHqnBv2pb9hwf/4zTo/zM5Lh2/t61kq+e0ZB6+UXdfDoo
3Ls9neJCCaKBy0D8yVgdSg1cJE0vZarn9CdVNIlt/2n5Y3kMcSenrrYNFlP8rSnM
kRMHf9KqzCLzpL/5ueCAR6A95HNUPPSTepeCJrZLbnibGJuQP1HvJNiLAgMBAAGg
IjAgBgkqhkiG9w0BCQ4xEzARMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEL
BQADggEBAH8RwLJXJZWq4Q91+/cLP/s8aKge3CK09laDAcWMNDWfvR8+mNlSte7P
EwVVL1GJN6ywwy0xCLKCY2Ixb66jfaW6lcphVgWiP0+8l8t/7p0MVRD9Uogf3nut
Sj4cyzjUcqR6zK7MYwBEHy+PFURjhpvYcskClmZYSP2WQIzt7ZyYBO95NZ8nTM3t
Fr6VUIqs8aiLevQbLhjU8eJCyo20WF2/XiDRyub6tEbz1onU1WwTXuEMsWdL4M78
/jbB9GQ+XK48kNluw3URz8sPBU6iilgj4xoEcR/A31ORnaEULL5udLr2akq3sOm8
CeibewRVc1nnivi0d+WUi0NvSgPdeuA=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBhKVHicSPgu41
yWZAV+p6LUVT5u8uCDNbM00Z+EaKoCvNXwXuydQZobKYDpltcCP/9n5dcNDm0TgN
MM99TZIPfxWmzCKgK1yj/2R7fNlde9mqdSGvJYx82OnE0XgTdRWQ6xbC+QtXmo4O
wasr37ShfFRgch9yvIXU7sXYp/Qd4d0Q/XkQHqnBv2pb9hwf/4zTo/zM5Lh2/t61
kq+e0ZB6+UXdfDoo3Ls9neJCCaKBy0D8yVgdSg1cJE0vZarn9CdVNIlt/2n5Y3kM
cSenrrYNFlP8rSnMkRMHf9KqzCLzpL/5ueCAR6A95HNUPPSTepeCJrZLbnibGJuQ
P1HvJNiLAgMBAAECggEAIJ5Bl53Onlv01+cTD5xh/ub7jQlbXlhug5xRjiONjFc0
GuE96EJnuExLhJrNXKduwfmj0g8ufwFb38lO5/F3wZnrpdo5qeK1MkVdg/0GzF2Q
Uk18+H8tP2v2d0DRawIsuOkPRJzivwjjkfQt7G7ADQoeVMVXrKi/LCV0/rBMku6Q
oIjgY/odhX6w+Xb1RNqvtgvI2VpIQFTrb17GDF+RUVBADjoSNQyeqg/LsNvDne0N
7VitPJsdwNjgt6SmSBp/R0rBI3LHgSuk/2/ca9tFbBMmI9PYKQhUbXod14D8i6WH
jMsaBKXNeqYkkrXUQFKo2YmIfb5RHMRgxv/CYdxT4QKBgQDhvG9yPPLwfj3Ni76Q
4PyQtuHdpD0H35D4pnLQqv18Ec5aIu7sCjG+EJSBLRWD2PPAVieGX5/AyS31pBf3
6MUaqISrfGMVtU4egTIoK5fbJDy9w4da/McjxbIIE+UK/hhvOYn6CeVtC9v2VW1i
hZlWkyiIlMwcV4QNR6i9aFRJ4QKBgQDbdnKixXCKinNuKLAsu3MXWVBk3YT4u16P
+D8FCWm859nV2i69+FFVG7zTvzsvjK41Ya9OGggaK3jrLbEwsNu0R4FVaqSLs34c
fdq2ZegU/TUTZvnkWAiQuFd9CEcSGr3qnLEC3nFCuUFLyYLZ80zuYWjOE4vZdrQ8
Eu0bHHvn6wKBgBMIpoUFap6opmFshRcGQYWaRhVAQf0l9r1gm5HIuTL69WFYTLkO
av9RupPhz0ycwIDZQt/rtDa3P+7UdUjsEaKbzwP+qwQrk3izAB2u/1D1D0IY+JLN
eaUkiExyEQAKSNkoCuBQcU3ukA+HSH/kL/fC1Mofcc55+qJ8BlhiMalBAoGASMUu
3+A+IAImollliX+iexSHftqhM+TVR0HWi7ICWLw8VBfjteQ3+9OVulTHqE2qmlLI
0Un6c8sEbl8ZSP7r6wxmy07wPs6Gu6XTtvV1jjgjuEpGBDxYorwtbm0nO86YOMo6
O6xMvAY3q4ynEeQGF2k/Wk3K6pHc06qm6n14bH8CgYEAn86QvwgBRT7zoVO1CPRu
hwkaxYctJq72skNzsiNn/uC8GtxQcUWyqIU4Sc3pNDpLYPLeMpTD9E//BCUUAum1
kD0LB8e48Lua72LF3fy0XZGKUsmKm5YXqtDf2lh1mPt47c4pMooJrn2f9RBP2cdW
7R2cJtcxu+Y4Sgxp2N9EcfE=
-----END PRIVATE KEY-----

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,312 @@
#!/usr/bin/env python3
import os
import threading
from ctypes import (
CDLL,
POINTER,
Structure,
addressof,
c_char_p,
c_int,
c_size_t,
c_uint8,
c_void_p,
cast,
memmove,
sizeof,
)
from pathlib import Path
from typing import TypeVar
import libvncclient
from libvncclient import (
GetCredentialProc,
GotCursorShapeProc,
GotFrameBufferUpdateProc,
GotXCutTextProc,
HandleKeyboardLedStateProc,
HandleRFBServerMessage,
MallocFrameBufferProc,
SendFramebufferUpdateRequest,
SetFormatAndEncodings,
String,
WaitForMessage,
rfbClient,
rfbClientCleanup,
rfbCredential,
rfbCredentialTypeUser,
rfbCredentialTypeX509,
rfbGetClient,
rfbInitClient,
struct__rfbClient,
)
path_to_lib = libvncclient._libs["libvncclient.so"].access["cdecl"]._name
if path_to_lib.startswith("/nix/store/"):
print("Using libvncclient from nix store")
libc = CDLL("libc.so.6") # Use the correct path for your libc
libc.malloc.argtypes = [c_size_t]
libc.malloc.restype = c_void_p
def alloc_str(data: str) -> c_char_p:
bdata = data.encode("ascii") + b"\0"
data_buf = libc.malloc(len(bdata)) # +1 for null terminator
memmove(data_buf, c_char_p(bdata), len(bdata))
return data_buf
StructType = TypeVar("StructType", bound="Structure")
def got_cursor_shape(
cl: rfbClient,
width: int,
height: int,
xhot: int,
yhot: int,
data: POINTER(c_uint8), # type: ignore[valid-type]
) -> None:
print(f"got_cursor_shape: {width} {height} {xhot} {yhot}")
return
def alloc_struct(data: StructType) -> int:
data_buf = libc.malloc(sizeof(data))
memmove(data_buf, addressof(data), sizeof(data))
return data_buf
def get_credential(
rfb_client: POINTER(struct__rfbClient), # type: ignore[valid-type]
credential_type: c_int,
) -> int | None:
print(f"==> get_credential: {credential_type}")
if credential_type == rfbCredentialTypeUser:
creds = rfbCredential()
username = os.environ.get("USER")
if not username:
print("ERROR: USER environment variable is not set")
return None
creds.userCredential.username = alloc_str(username)
creds.userCredential.password = None
creds_buf = alloc_struct(creds)
# Return a integer to the creds obj
return creds_buf
if credential_type == rfbCredentialTypeX509:
ca_dir = (
Path(os.environ.get("GIT_ROOT", ""))
/ "pkgs"
/ "clan-vm-manager"
/ "tests"
/ "data"
/ "vnc-security"
)
ca_cert = ca_dir / "ca.crt"
if not ca_cert.exists():
print(f"ERROR: ca_cert does not exist: {ca_cert}")
return None
ca_crl = ca_dir / "ca.key"
if not ca_crl.exists():
print(f"ERROR: ca_crl does not exist: {ca_crl}")
return None
# Instantiate the credential union and populate it
creds = rfbCredential()
creds.x509Credential.x509CACertFile = alloc_str(str(ca_cert))
creds.x509Credential.x509CrlVerifyMode = False
print("===> Alloc struct")
creds_buf = alloc_struct(creds)
print("====> Done alloc struct")
# Return a integer to the creds obj
return creds_buf
print(f"ERROR: Unknown credential type: {credential_type}")
return None
def got_selection(cl: rfbClient, text: str, text_len: int) -> None:
print(f"got_selection: {text}")
def resize(client: rfbClient) -> bool:
width = client.contents.width
height = client.contents.height
bits_per_pixel = client.contents.format.bitsPerPixel
print(f"Size: {width}x{height}")
if client.contents.frameBuffer:
libc.free(client.contents.frameBuffer)
client.contents.frameBuffer = None
new_buf = libc.malloc(int(width * height * bits_per_pixel / 8))
if not new_buf:
print("malloc failed")
return False
casted_buf = cast(new_buf, POINTER(c_uint8))
client.contents.frameBuffer = casted_buf
request = SendFramebufferUpdateRequest(client, 0, 0, width, height, False)
if not request:
print("SendFramebufferUpdateRequest failed")
return True
def update(cl: rfbClient, x: int, y: int, w: int, h: int) -> None:
print(f"update: {x} {y} {w} {h}")
return
def kbd_leds(cl: rfbClient, value: int, pad: int) -> None:
print(f"kbd_leds: {value} {pad}")
# /*****************************************************************************
# *
# * Encoding types
# *
# *****************************************************************************/
# #define rfbEncodingRaw 0
# #define rfbEncodingCopyRect 1
# #define rfbEncodingRRE 2
# #define rfbEncodingCoRRE 4
# #define rfbEncodingHextile 5
# #define rfbEncodingZlib 6
# #define rfbEncodingTight 7
# #define rfbEncodingTightPng 0xFFFFFEFC /* -260 */
# #define rfbEncodingZlibHex 8
# #define rfbEncodingUltra 9
# #define rfbEncodingTRLE 15
# #define rfbEncodingZRLE 16
# #define rfbEncodingZYWRLE 17
# #define rfbEncodingH264 0x48323634
# /* Cache & XOR-Zlib - rdv@2002 */
# #define rfbEncodingCache 0xFFFF0000
# #define rfbEncodingCacheEnable 0xFFFF0001
# #define rfbEncodingXOR_Zlib 0xFFFF0002
# #define rfbEncodingXORMonoColor_Zlib 0xFFFF0003
# #define rfbEncodingXORMultiColor_Zlib 0xFFFF0004
# #define rfbEncodingSolidColor 0xFFFF0005
# #define rfbEncodingXOREnable 0xFFFF0006
# #define rfbEncodingCacheZip 0xFFFF0007
# #define rfbEncodingSolMonoZip 0xFFFF0008
# #define rfbEncodingUltraZip 0xFFFF0009
class VncError(Exception):
pass
class VncClient:
client: rfbClient
stop_event: threading.Event
thread: threading.Thread | None
def __init__(self) -> None:
bits_per_sample = 8
samples_per_pixel = 3
bytes_per_pixel = 4
self.client: rfbClient = rfbGetClient(
bits_per_sample, samples_per_pixel, bytes_per_pixel
)
if not self.client:
raise VncError("rfbGetClient failed")
self.stop_event = threading.Event() # Initialize the stop event.
self.thread = None
self._client_settings()
self._set_color()
self._set_encoding()
def _client_settings(self) -> None:
# client settings
self.client.contents.MallocFrameBuffer = MallocFrameBufferProc(resize)
self.client.contents.canHandleNewFBSize = True
self.client.contents.GotFrameBufferUpdate = GotFrameBufferUpdateProc(update)
self.client.contents.HandleKeyboardLedState = HandleKeyboardLedStateProc(
kbd_leds
)
self.client.contents.GotXCutText = GotXCutTextProc(got_selection)
self.client.contents.GotCursorShape = GotCursorShapeProc(got_cursor_shape)
self.client.contents.GetCredential = GetCredentialProc(get_credential)
self.client.contents.listenPort = 5900
self.client.contents.listenAddress = String.from_param("127.0.0.1")
def _set_color(self) -> None:
# Set client encoding to (equal to remmina)
# TRUE colour: max red 255 green 255 blue 255, shift red 16 green 8 blue 0
self.client.contents.format.bitsPerPixel = 32
self.client.contents.format.depth = 24
self.client.contents.format.redShift = 16
self.client.contents.format.blueShift = 0
self.client.contents.format.greenShift = 8
self.client.contents.format.blueMax = 0xFF
self.client.contents.format.redMax = 0xFF
self.client.contents.format.greenMax = 0xFF
SetFormatAndEncodings(self.client)
def _set_encoding(self) -> None:
# Set client compression to remminas quality 9 (best) and compress level 1 (lowest)
# BUG: tight encoding is crashing (looks exploitable)
self.client.contents.appData.shareDesktop = True
self.client.contents.appData.useBGR233 = False
self.client.contents.appData.encodingsString = String.from_param(
"copyrect zlib hextile raw"
)
self.client.contents.appData.compressLevel = 1
self.client.contents.appData.qualityLevel = 9
SetFormatAndEncodings(self.client)
def start_blocking(self) -> None:
print("Initializing connection")
argc = c_int(0)
argv = None
if not rfbInitClient(self.client, argc, argv):
raise VncError("rfbInitClient failed")
# Main loop
while not self.stop_event.is_set():
res = WaitForMessage(self.client, 500)
if res < 0:
rfbClientCleanup(self.client)
raise VncError("WaitForMessage failed")
if res > 0:
if not HandleRFBServerMessage(self.client):
rfbClientCleanup(self.client)
raise VncError("HandleRFBServerMessage failed")
rfbClientCleanup(self.client)
def start(self) -> None:
self.thread = threading.Thread(target=self.start_blocking)
self.thread.start()
def stop(self) -> None:
self.stop_event.set() # Signal the thread to stop.
if self.thread is not None:
self.thread.join() # Wait for the thread to finish.
print("VNC client stopped.")
if __name__ == "__main__":
vnc = VncClient()
vnc.start()
assert vnc.thread is not None
vnc.thread.join()

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
PYTHON_DIR=$(dirname "$(which python3)")/..
gdb --quiet -ex "source $PYTHON_DIR/share/gdb/libpython.py" -ex "run" --args python "$1"

View File

@ -16,12 +16,21 @@ def temporary_home(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]:
path = Path(env_dir).resolve() path = Path(env_dir).resolve()
log.debug("Temp HOME directory: %s", str(path)) log.debug("Temp HOME directory: %s", str(path))
monkeypatch.setenv("HOME", str(path)) monkeypatch.setenv("HOME", str(path))
monkeypatch.setenv("XDG_CONFIG_HOME", str(path / ".config"))
runtime_dir = path / "xdg-runtime-dir"
runtime_dir.mkdir()
runtime_dir.chmod(0o700)
monkeypatch.setenv("XDG_RUNTIME_DIR", str(runtime_dir))
monkeypatch.chdir(str(path)) monkeypatch.chdir(str(path))
yield path yield path
else: else:
with tempfile.TemporaryDirectory(prefix="pytest-") as dirpath: with tempfile.TemporaryDirectory(prefix="pytest-") as dirpath:
monkeypatch.setenv("HOME", str(dirpath)) monkeypatch.setenv("HOME", str(dirpath))
monkeypatch.setenv("XDG_CONFIG_HOME", str(Path(dirpath) / ".config")) monkeypatch.setenv("XDG_CONFIG_HOME", str(Path(dirpath) / ".config"))
runtime_dir = Path(dirpath) / "xdg-runtime-dir"
runtime_dir.mkdir()
runtime_dir.chmod(0o700)
monkeypatch.setenv("XDG_RUNTIME_DIR", str(runtime_dir))
monkeypatch.chdir(str(dirpath)) monkeypatch.chdir(str(dirpath))
log.debug("Temp HOME directory: %s", str(dirpath)) log.debug("Temp HOME directory: %s", str(dirpath))
yield Path(dirpath) yield Path(dirpath)

View File

@ -1,5 +1,5 @@
import pytest import pytest
from cli import Cli from helpers.cli import Cli
def test_help(capfd: pytest.CaptureFixture) -> None: def test_help(capfd: pytest.CaptureFixture) -> None:

View File

@ -1,8 +1,9 @@
import time import time
from wayland import GtkProc from wayland import GtkApp
def test_open(app: GtkProc) -> None: def test_join(app: GtkApp) -> None:
time.sleep(0.5) while app.poll() is None:
assert app.poll() is None time.sleep(0.1)
assert True

View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
# For vnc debugging
export VNC_USERNAME="$USER"
export VNC_PASSWORD=""
CA_CRT=$GIT_ROOT/pkgs/clan-vm-manager/tests/data/vnc-security/ca.crt
vncviewer 127.0.0.1 -Shared -X509CA "$CA_CRT"

View File

@ -1,27 +1,137 @@
# Import necessary modules
import os
import shlex
import subprocess
import sys import sys
import time
from collections.abc import Generator from collections.abc import Generator
from subprocess import Popen from pathlib import Path
from typing import NewType
# Assuming NewType is already imported or defined somewhere
import pytest import pytest
from helpers.vnc_client import VncClient
@pytest.fixture(scope="session") def write_script(cmd: list[str], new_env: dict[str, str], name: str) -> None:
def wayland_compositor() -> Generator[Popen, None, None]: # Create the bash script content
# Start the Wayland compositor (e.g., Weston) script_content = "#!/bin/bash\n"
# compositor = Popen(["weston", "--backend=headless-backend.so"]) for key, value in new_env.items():
compositor = Popen(["weston"]) if '"' in value:
yield compositor value = value.replace('"', '\\"')
# Cleanup: Terminate the compositor if "'" in value:
compositor.terminate() value = value.replace("'", "\\'")
if "`" in value:
value = value.replace("`", "\\`")
script_content += f'export {key}="{value}"\n'
strace_cmd = ["strace", "-f", "-o", "file-access.log", "-e", "trace=file", *cmd]
script_content += shlex.join(strace_cmd)
# Write the bash script to a file
script_filename = name
with open(script_filename, "w") as script_file:
script_file.write(script_content)
print(f"You can find the script at {os.getcwd()}/{script_filename}")
# Make the script executable
os.chmod(script_filename, 0o755)
GtkProc = NewType("GtkProc", Popen) class Compositor:
def __init__(self, proc: subprocess.Popen, env: dict[str, str]) -> None:
self.proc = proc
self.env = env
@pytest.fixture(scope="function") @pytest.fixture
def app() -> Generator[GtkProc, None, None]: def weston_override_lib(
rapp = Popen([sys.executable, "-m", "clan_vm_manager"], text=True) temporary_home: Path, test_root: Path
yield GtkProc(rapp) ) -> Generator[Path, None, None]:
# Cleanup: Terminate your application # with TemporaryDirectory() as tmpdir:
rapp.terminate() # This enforces a login shell by overriding the login shell of `getpwnam(3)`
lib_path = Path(temporary_home) / "libweston_auth_override.so"
subprocess.run(
[
os.environ.get("CC", "cc"),
"-shared",
"-fPIC",
"-o",
lib_path,
str(test_root / "weston_auth_override.c"),
],
check=True,
)
yield lib_path
@pytest.fixture
def wayland_comp(
test_root: Path, weston_override_lib: Path
) -> Generator[Compositor, None, None]:
tls_key = test_root / "data" / "vnc-security" / "tls.key"
tls_cert = test_root / "data" / "vnc-security" / "tls.crt"
wayland_display = "wayland-1" # Define a unique WAYLAND_DISPLAY value
new_env = os.environ.copy()
new_env["WAYLAND_DISPLAY"] = wayland_display
# Uncomment the next line if you need to force software rendering
new_env["LIBGL_ALWAYS_SOFTWARE"] = "true"
new_env["LD_PRELOAD"] = str(weston_override_lib)
cmd = [
"weston",
"--debug",
"--width=1920",
"--height=1080",
"--port=5900",
f"--vnc-tls-key={tls_key}",
f"--vnc-tls-cert={tls_cert}",
"--backend=vnc",
f"--socket={wayland_display}",
]
compositor = subprocess.Popen(cmd, env=new_env, text=True)
time.sleep(0.4)
if compositor.poll() is not None:
raise Exception(f"Failed to start {cmd}")
client = VncClient()
client.start()
yield Compositor(compositor, new_env)
compositor.kill()
client.stop()
class GtkApp:
def __init__(self, proc: subprocess.Popen) -> None:
self.proc = proc
def kill(self) -> None:
self.proc.kill()
def wait(self) -> None:
self.proc.wait()
def poll(self) -> int | None:
return self.proc.poll()
@pytest.fixture
def app(wayland_comp: Compositor) -> Generator[GtkApp, None, None]:
# Define the new environment variables
new_env = wayland_comp.env
new_env["GDK_BACKEND"] = "wayland"
# Define the command to be executed
cmd = [f"{sys.executable}", "-m", "clan_vm_manager"]
write_script(cmd, new_env, "weston.sh")
breakpoint()
# Execute the script
rapp = subprocess.Popen(
cmd,
text=True,
env=new_env,
stdin=sys.stdin,
)
time.sleep(0.4)
if rapp.poll() is not None:
raise Exception(f"Failed to start {cmd}")
yield GtkApp(rapp)
rapp.kill()

View File

@ -0,0 +1,12 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdio.h>
#include <dlfcn.h>
// Overriding the weston_authenticate_user function
bool weston_authenticate_user(const char *username, const char *password) {
printf("=====>Overridden weston_authenticate_user called with username: %s\n", username);
return true; // Always return true
}