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

View File

@ -19,7 +19,7 @@ testpaths = "tests"
faulthandler_timeout = 60
log_level = "DEBUG"
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"
markers = ["impure"]
@ -29,11 +29,17 @@ warn_redundant_casts = true
disallow_untyped_calls = true
disallow_untyped_defs = true
no_implicit_optional = true
exclude = '^tests/helpers/libvncclient.py$'
[[tool.mypy.overrides]]
module = "clan_cli.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "libvncclient.*"
ignore_missing_imports = true
[tool.ruff]
target-version = "py311"
line-length = 88

View File

@ -10,6 +10,8 @@
python3,
gtk4,
libadwaita,
tigervnc,
libvncserver,
}:
let
@ -31,6 +33,9 @@ mkShell {
ruff
gtk4.dev # has the demo called 'gtk4-widget-factory'
libadwaita.devdoc # has the demo called 'adwaita-1-demo'
tigervnc
libvncserver.dev
libvncserver
]
++ devshellTestDeps
@ -40,14 +45,28 @@ mkShell {
desktop-file-utils # verify desktop files
]);
# Use ipdb as the default debugger for python
PYTHONBREAKPOINT = "ipdb.set_trace";
hardeningDisabled = "all";
shellHook = ''
export LIBVNC_INCLUDE=${libvncserver.dev}/include
export LIBVNC_LIB=${libvncserver}/lib
export GIT_ROOT=$(git rev-parse --show-toplevel)
export PKG_ROOT=$GIT_ROOT/pkgs/clan-vm-manager
# 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
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
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 = [
"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()
log.debug("Temp HOME directory: %s", 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))
yield path
else:
with tempfile.TemporaryDirectory(prefix="pytest-") as dirpath:
monkeypatch.setenv("HOME", str(dirpath))
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))
log.debug("Temp HOME directory: %s", str(dirpath))
yield Path(dirpath)

View File

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

View File

@ -1,8 +1,9 @@
import time
from wayland import GtkProc
from wayland import GtkApp
def test_open(app: GtkProc) -> None:
time.sleep(0.5)
assert app.poll() is None
def test_join(app: GtkApp) -> None:
while 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 time
from collections.abc import Generator
from subprocess import Popen
from typing import NewType
from pathlib import Path
# Assuming NewType is already imported or defined somewhere
import pytest
from helpers.vnc_client import VncClient
@pytest.fixture(scope="session")
def wayland_compositor() -> Generator[Popen, None, None]:
# Start the Wayland compositor (e.g., Weston)
# compositor = Popen(["weston", "--backend=headless-backend.so"])
compositor = Popen(["weston"])
yield compositor
# Cleanup: Terminate the compositor
compositor.terminate()
def write_script(cmd: list[str], new_env: dict[str, str], name: str) -> None:
# Create the bash script content
script_content = "#!/bin/bash\n"
for key, value in new_env.items():
if '"' in value:
value = value.replace('"', '\\"')
if "'" in value:
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")
def app() -> Generator[GtkProc, None, None]:
rapp = Popen([sys.executable, "-m", "clan_vm_manager"], text=True)
yield GtkProc(rapp)
# Cleanup: Terminate your application
rapp.terminate()
@pytest.fixture
def weston_override_lib(
temporary_home: Path, test_root: Path
) -> Generator[Path, None, None]:
# with TemporaryDirectory() as tmpdir:
# 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
}