Drop for ManagedPointer {
+ }
+ }
+
++impl<'a, P: Pointer> From<&'a ManagedPointer
> for ConstPointer<'a, P::T> {
++ fn from(ptr: &'a ManagedPointer
) -> ConstPointer<'a, P::T> {
++ ConstPointer {
++ ptr: ptr.pointer.as_const_ptr(),
++ _lifetime: PhantomData,
++ }
++ }
++}
++
+ impl ManagedPointer {
+ #[inline]
+ pub fn as_const(&self) -> ConstPointer {
+- ConstPointer {
+- ptr: self.pointer.as_const_ptr(),
++ self.into()
++ }
++
++ pub fn project_const_lifetime<'a, C>(
++ &'a self,
++ f: unsafe fn(&'a Self) -> *const C,
++ ) -> Result, ()> {
++ let ptr = unsafe { f(self) };
++ if ptr.is_null() {
++ return Err(());
+ }
++ Ok(ConstPointer {
++ ptr,
++ _lifetime: PhantomData,
++ })
+ }
+
+ #[inline]
+@@ -133,20 +154,40 @@ impl Drop for DetachablePointer {
+ }
+
+ #[derive(Debug)]
+-pub(crate) struct ConstPointer {
++pub(crate) struct ConstPointer<'a, T> {
+ ptr: *const T,
++ _lifetime: PhantomData<&'a T>,
++}
++
++impl ConstPointer<'static, T> {
++ pub unsafe fn new_static(ptr: *const T) -> Result {
++ if ptr.is_null() {
++ return Err(());
++ }
++ Ok(ConstPointer {
++ ptr,
++ _lifetime: PhantomData,
++ })
++ }
+ }
+
+-impl ConstPointer {
+- pub fn new(ptr: *const T) -> Result, ()> {
++impl ConstPointer<'_, T> {
++ pub fn project_const_lifetime<'a, C>(
++ &'a self,
++ f: unsafe fn(&'a Self) -> *const C,
++ ) -> Result, ()> {
++ let ptr = unsafe { f(self) };
+ if ptr.is_null() {
+ return Err(());
+ }
+- Ok(ConstPointer { ptr })
++ Ok(ConstPointer {
++ ptr,
++ _lifetime: PhantomData,
++ })
+ }
+ }
+
+-impl Deref for ConstPointer {
++impl Deref for ConstPointer<'_, T> {
+ type Target = *const T;
+
+ fn deref(&self) -> &Self::Target {
+diff --git a/src/rsa/encoding.rs b/src/rsa/encoding.rs
+index 11538c87cd9..193596386f5 100644
+--- a/src/rsa/encoding.rs
++++ b/src/rsa/encoding.rs
+@@ -21,7 +21,11 @@ pub(in crate::rsa) mod rfc8017 {
+ let mut pubkey_bytes = null_mut::();
+ let mut outlen: usize = 0;
+ if 1 != unsafe {
+- RSA_public_key_to_bytes(&mut pubkey_bytes, &mut outlen, *pubkey.get_rsa()?)
++ RSA_public_key_to_bytes(
++ &mut pubkey_bytes,
++ &mut outlen,
++ *pubkey.as_const().get_rsa()?,
++ )
+ } {
+ return Err(Unspecified);
+ }
+@@ -85,7 +89,7 @@ pub(in crate::rsa) mod rfc5280 {
+ pub(in crate::rsa) fn encode_public_key_der(
+ key: &LcPtr,
+ ) -> Result, Unspecified> {
+- let der = key.marshal_rfc5280_public_key()?;
++ let der = key.as_const().marshal_rfc5280_public_key()?;
+ Ok(PublicKeyX509Der::from(Buffer::new(der)))
+ }
+
+diff --git a/src/rsa/encryption.rs b/src/rsa/encryption.rs
+index d31187754e3..3dc58b3316f 100644
+--- a/src/rsa/encryption.rs
++++ b/src/rsa/encryption.rs
+@@ -44,7 +44,7 @@ impl PrivateDecryptingKey {
+ if !is_rsa_key(key) {
+ return Err(Unspecified);
+ }
+- match key.key_size_bits() {
++ match key.as_const().key_size_bits() {
+ 2048..=8192 => Ok(()),
+ _ => Err(Unspecified),
+ }
+@@ -105,13 +105,13 @@ impl PrivateDecryptingKey {
+ /// Returns the RSA signature size in bytes.
+ #[must_use]
+ pub fn key_size_bytes(&self) -> usize {
+- self.0.signature_size_bytes()
++ self.0.as_const().signature_size_bytes()
+ }
+
+ /// Returns the RSA key size in bits.
+ #[must_use]
+ pub fn key_size_bits(&self) -> usize {
+- self.0.key_size_bits()
++ self.0.as_const().key_size_bits()
+ }
+
+ /// Retrieves the `PublicEncryptingKey` corresponding with this `PrivateDecryptingKey`.
+@@ -133,7 +133,7 @@ impl Debug for PrivateDecryptingKey {
+ impl AsDer> for PrivateDecryptingKey {
+ fn as_der(&self) -> Result, Unspecified> {
+ Ok(Pkcs8V1Der::new(
+- self.0.marshal_rfc5208_private_key(Version::V1)?,
++ self.0.as_const().marshal_rfc5208_private_key(Version::V1)?,
+ ))
+ }
+ }
+@@ -157,7 +157,7 @@ impl PublicEncryptingKey {
+ if !is_rsa_key(key) {
+ return Err(Unspecified);
+ }
+- match key.key_size_bits() {
++ match key.as_const().key_size_bits() {
+ 2048..=8192 => Ok(()),
+ _ => Err(Unspecified),
+ }
+@@ -174,13 +174,13 @@ impl PublicEncryptingKey {
+ /// Returns the RSA signature size in bytes.
+ #[must_use]
+ pub fn key_size_bytes(&self) -> usize {
+- self.0.signature_size_bytes()
++ self.0.as_const().signature_size_bytes()
+ }
+
+ /// Returns the RSA key size in bits.
+ #[must_use]
+ pub fn key_size_bits(&self) -> usize {
+- self.0.key_size_bits()
++ self.0.as_const().key_size_bits()
+ }
+ }
+
+diff --git a/src/rsa/key.rs b/src/rsa/key.rs
+index 1abc483f88e..b43c5d4a6e2 100644
+--- a/src/rsa/key.rs
++++ b/src/rsa/key.rs
+@@ -16,8 +16,6 @@ use crate::encoding::{AsDer, Pkcs8V1Der};
+ use crate::error::{KeyRejected, Unspecified};
+ #[cfg(feature = "ring-io")]
+ use crate::io;
+-#[cfg(feature = "ring-io")]
+-use crate::ptr::ConstPointer;
+ use crate::ptr::{DetachableLcPtr, LcPtr};
+ use crate::rsa::PublicEncryptingKey;
+ use crate::sealed::Sealed;
+@@ -176,7 +174,7 @@ impl KeyPair {
+ if !is_rsa_key(key) {
+ return Err(KeyRejected::unspecified());
+ }
+- match key.key_size_bits() {
++ match key.as_const().key_size_bits() {
+ 2048..=8192 => Ok(()),
+ _ => Err(KeyRejected::unspecified()),
+ }
+@@ -231,7 +229,7 @@ impl KeyPair {
+ #[must_use]
+ pub fn public_modulus_len(&self) -> usize {
+ // This was already validated to be an RSA key so this can't fail
+- match self.evp_pkey.get_rsa() {
++ match self.evp_pkey.as_const().get_rsa() {
+ Ok(rsa) => {
+ // https://github.com/awslabs/aws-lc/blob/main/include/openssl/rsa.h#L99
+ unsafe { RSA_size(*rsa) as usize }
+@@ -261,7 +259,9 @@ impl crate::signature::KeyPair for KeyPair {
+ impl AsDer> for KeyPair {
+ fn as_der(&self) -> Result, Unspecified> {
+ Ok(Pkcs8V1Der::new(
+- self.evp_pkey.marshal_rfc5208_private_key(Version::V1)?,
++ self.evp_pkey
++ .as_const()
++ .marshal_rfc5208_private_key(Version::V1)?,
+ ))
+ }
+ }
+@@ -292,10 +292,13 @@ impl PublicKey {
+ let key = encoding::rfc8017::encode_public_key_der(evp_pkey)?;
+ #[cfg(feature = "ring-io")]
+ {
++ let evp_pkey = evp_pkey.as_const();
+ let pubkey = evp_pkey.get_rsa()?;
+- let modulus = ConstPointer::new(unsafe { RSA_get0_n(*pubkey) })?;
++ let modulus =
++ pubkey.project_const_lifetime(unsafe { |pubkey| RSA_get0_n(**pubkey) })?;
+ let modulus = modulus.to_be_bytes().into_boxed_slice();
+- let exponent = ConstPointer::new(unsafe { RSA_get0_e(*pubkey) })?;
++ let exponent =
++ pubkey.project_const_lifetime(unsafe { |pubkey| RSA_get0_e(**pubkey) })?;
+ let exponent = exponent.to_be_bytes().into_boxed_slice();
+ Ok(PublicKey {
+ key,
+@@ -478,12 +481,13 @@ pub(super) fn generate_rsa_key(size: c_int) -> Result, Unspecifi
+ #[must_use]
+ pub(super) fn is_valid_fips_key(key: &LcPtr) -> bool {
+ // This should always be an RSA key and must-never panic.
+- let rsa_key = key.get_rsa().expect("RSA EVP_PKEY");
++ let evp_pkey = key.as_const();
++ let rsa_key = evp_pkey.get_rsa().expect("RSA EVP_PKEY");
+
+ 1 == unsafe { RSA_check_fips(*rsa_key as *mut RSA) }
+ }
+
+ pub(super) fn is_rsa_key(key: &LcPtr) -> bool {
+- let id = key.id();
++ let id = key.as_const().id();
+ id == EVP_PKEY_RSA || id == EVP_PKEY_RSA_PSS
+ }
+diff --git a/src/rsa/signature.rs b/src/rsa/signature.rs
+index d1b18edf78d..164386c5b8f 100644
+--- a/src/rsa/signature.rs
++++ b/src/rsa/signature.rs
+@@ -109,7 +109,7 @@ impl RsaParameters {
+ /// `error::Unspecified` on parse error.
+ pub fn public_modulus_len(public_key: &[u8]) -> Result {
+ let rsa = encoding::rfc8017::decode_public_key_der(public_key)?;
+- Ok(unsafe { RSA_bits(*rsa.get_rsa()?) })
++ Ok(unsafe { RSA_bits(*rsa.as_const().get_rsa()?) })
+ }
+
+ #[must_use]
+@@ -222,7 +222,7 @@ pub(crate) fn verify_rsa_signature(
+ signature: &[u8],
+ allowed_bit_size: &RangeInclusive,
+ ) -> Result<(), Unspecified> {
+- if !allowed_bit_size.contains(&public_key.key_size_bits().try_into()?) {
++ if !allowed_bit_size.contains(&public_key.as_const().key_size_bits().try_into()?) {
+ return Err(Unspecified);
+ }
+
diff --git a/ci/env.py b/ci/env.py
index 6652d8138..7c191b05a 100755
--- a/ci/env.py
+++ b/ci/env.py
@@ -3,14 +3,15 @@
import os
LIBTELIO_ENV_MOOSE_RELEASE_TAG = "v17.0.0-libtelioApp"
-LIBTELIO_ENV_NAT_LAB_DEPS_TAG = "v0.0.32"
+LIBTELIO_ENV_NAT_LAB_DEPS_TAG = "v0.3.0"
LIBTELIO_ENV_ANDROID_BUILDER_TAG = "v7.2.2"
LIBTELIO_ENV_LINUX_BUILDER_TAG = "v7.2.3"
LIBTELIO_ENV_WINDOWS_BUILDER_TAG = "v7.2.2"
LIBTELIO_ENV_UNIFFI_GENERATORS_TAG = "v0.28.3-4"
-LIBTELIO_ENV_NAT_LAB_WINDOWS_VM_TAG = "v0.0.7"
-LIBTELIO_ENV_NAT_LAB_MACOS_VM_TAG = "v0.0.8"
-LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG = "v0.0.6"
+LIBTELIO_ENV_NAT_LAB_WINDOWS_VM_TAG = "v0.0.8"
+LIBTELIO_ENV_NAT_LAB_MACOS_VM_TAG = "v0.0.9"
+LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG = "v0.2.2"
+LIBTELIO_ENV_OPENWRT_BUILDER_TAG = "24.10.4"
def set_sh():
diff --git a/ci/fetch_artifacts.py b/ci/fetch_artifacts.py
index f687158ef..e1e744959 100644
--- a/ci/fetch_artifacts.py
+++ b/ci/fetch_artifacts.py
@@ -104,7 +104,7 @@ def _get_latest_tag(self):
raise Exception("No suitable build tag was found")
def _get_remote_path(self) -> str:
- LIBTELIO_BUILD_PROJECT_ID = 6299
+ LIBTELIO_BUILD_PROJECT_ID = 2386
libtelio_env_sec_gitlab_repository = os.environ.get(
"LIBTELIO_ENV_SEC_GITLAB_REPOSITORY", None
)
diff --git a/ci/moose_utils.py b/ci/moose_utils.py
index 95be71031..54c357e02 100644
--- a/ci/moose_utils.py
+++ b/ci/moose_utils.py
@@ -116,7 +116,7 @@ def set_cargo_dependencies():
MOOSELIBTELIOAPP_DEP = (
r"\nmooselibtelioapp = { "
- f'git = "https://{libtelio_env_sec_gitlab_repository}/low-level-hacks/moose/moose-events",'
+ f'git = "https://{libtelio_env_sec_gitlab_repository}/nord-projects/nordvpn/infra/llt/moose/moose-events",'
f' tag = "{LIBTELIO_ENV_MOOSE_RELEASE_TAG}" }}'
)
diff --git a/clis/tcli/src/nord.rs b/clis/tcli/src/nord.rs
index a27f10f33..bee210d5f 100644
--- a/clis/tcli/src/nord.rs
+++ b/clis/tcli/src/nord.rs
@@ -1,3 +1,4 @@
+use base64::prelude::{Engine, BASE64_STANDARD};
use reqwest::{
blocking::{Client, Response},
header,
@@ -92,6 +93,11 @@ struct MeshDev {
}
impl Nord {
+ fn format_auth_token(token: &String) -> String {
+ let creds = format!("token:{}", token);
+ format!("Basic {}", BASE64_STANDARD.encode(creds))
+ }
+
pub fn start_login() -> Result {
let chalenge = b"asdfasdf".to_vec();
let sha256 = hex::encode(Sha256::digest(&chalenge));
@@ -120,7 +126,7 @@ impl Nord {
.json()?;
let creds: Creds = client
.get(&format!("{}/users/services/credentials", API_BASE))
- .header(header::AUTHORIZATION, format!("token:{}", &login.token))
+ .header(header::AUTHORIZATION, Self::format_auth_token(&login.token))
.send()?
.checked()?
.json()?;
@@ -143,7 +149,7 @@ impl Nord {
let creds: Creds = client
.get(&format!("{}/users/services/credentials", API_BASE))
- .header(header::AUTHORIZATION, format!("token:{}", &login.token))
+ .header(header::AUTHORIZATION, Self::format_auth_token(&login.token))
.send()?
.checked()?
.json()?;
@@ -159,10 +165,11 @@ impl Nord {
let login = LoginInfo {
token: token.to_string(),
};
+
let client = Client::new();
let creds: Creds = client
.get(&format!("{}/users/services/credentials", API_BASE))
- .header(header::AUTHORIZATION, format!("token:{}", &login.token))
+ .header(header::AUTHORIZATION, Self::format_auth_token(&login.token))
.send()?
.checked()?
.json()?;
@@ -221,7 +228,7 @@ impl Nord {
.post(&format!("{}/meshnet/machines", API_BASE))
.header(
header::AUTHORIZATION,
- format!("Bearer token:{}", &self.login.token),
+ Self::format_auth_token(&self.login.token),
)
.header(header::CONTENT_TYPE, "application/json")
.header(header::ACCEPT, "application/json")
@@ -245,7 +252,7 @@ impl Nord {
.get(&format!("{}/meshnet/machines/{}/map", API_BASE, id))
.header(
header::AUTHORIZATION,
- format!("Bearer token:{}", &self.login.token),
+ Self::format_auth_token(&self.login.token),
)
.header(header::ACCEPT, "application/json")
.send()?
diff --git a/crates/telio-firewall/src/firewall.rs b/crates/telio-firewall/src/firewall.rs
index 2a3456add..506df14dc 100644
--- a/crates/telio-firewall/src/firewall.rs
+++ b/crates/telio-firewall/src/firewall.rs
@@ -883,8 +883,10 @@ impl Firewall for StatefullFirewall {
}
fn set_ip_addresses(&self, ip_addrs: Vec) {
- self.ip_addresses.write().extend_from_slice(&ip_addrs);
- self.recreate_chain();
+ if ip_addrs != *self.ip_addresses.read() {
+ *self.ip_addresses.write().as_mut() = ip_addrs;
+ self.recreate_chain();
+ }
}
}
@@ -1450,6 +1452,33 @@ pub mod tests {
}
}
+ fn ip_address_set_multiple_times() {
+ let ip_vec = vec![
+ StdIpAddr::V4(StdIpv4Addr::new(127, 0, 0, 1)),
+ StdIpAddr::V6(StdIpv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
+ ];
+ let new_ip_vec = vec![
+ StdIpAddr::V4(StdIpv4Addr::new(192, 168, 1, 1)),
+ StdIpAddr::V6(StdIpv6Addr::new(0xfd74, 0x656c, 0x696f, 0, 0, 0, 0, 1)),
+ ];
+
+ let fw = StatefullFirewall::new(false, &FeatureFirewall::default());
+
+ assert_eq!(fw.ip_addresses.read().len(), 0);
+
+ fw.set_ip_addresses(ip_vec.clone());
+ assert_eq!(fw.ip_addresses.read().len(), 2);
+ assert_eq!(*fw.ip_addresses.read(), ip_vec);
+
+ fw.set_ip_addresses(ip_vec.clone());
+ assert_eq!(fw.ip_addresses.read().len(), 2);
+ assert_eq!(*fw.ip_addresses.read(), ip_vec);
+
+ fw.set_ip_addresses(new_ip_vec.clone());
+ assert_eq!(fw.ip_addresses.read().len(), 2);
+ assert_eq!(*fw.ip_addresses.read(), new_ip_vec);
+ }
+
#[rustfmt::skip]
#[test]
fn ipv6_blocked() {
diff --git a/deny.toml b/deny.toml
index 656fb2f85..348783a58 100644
--- a/deny.toml
+++ b/deny.toml
@@ -5,15 +5,19 @@ targets = []
[advisories]
db-path = "~/.cargo/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]
-vulnerability = "deny"
-unmaintained = "warn"
yanked = "warn"
-notice = "warn"
-ignore = []
+ignore = [
+ "RUSTSEC-2024-0370", # proc-macro-error is unmaintained
+ "RUSTSEC-2024-0381", # pqcrypto-kyber is unmaintained
+ "RUSTSEC-2024-0436", # paste is unmaintained
+ "RUSTSEC-2024-0375", # atty is unmaintained
+ "RUSTSEC-2025-0052", # async-std is unmaintained
+ "RUSTSEC-2021-0145", # atty has potential unaligned read
+ "RUSTSEC-2025-0141", # bincode is unmaintained
+]
[licenses]
-unlicensed = "deny"
allow = [
"BSD-2-Clause",
"MPL-2.0",
@@ -21,15 +25,16 @@ allow = [
"Unicode-DFS-2016",
"Unicode-3.0",
"OpenSSL",
+ "Apache-2.0",
+ "MIT",
+ "ISC",
+ "BSD-3-Clause",
+ "Zlib"
]
-deny = []
-copyleft = "deny"
-allow-osi-fsf-free = "both"
-default = "deny"
confidence-threshold = 0.8
exceptions = [
- { allow = ["GPL-3.0"], name = "telio", version = "*" },
- { allow = ["GPL-3.0"], name = "llt-proto", version = "*" },
+ { allow = ["GPL-3.0-only"], name = "telio", version = "*" },
+ { allow = ["GPL-3.0-only"], name = "llt-proto", version = "*" },
]
[licenses.private]
diff --git a/nat-lab/Dockerfile b/nat-lab/Dockerfile
index 4b129e537..24b5a2fb1 100644
--- a/nat-lab/Dockerfile
+++ b/nat-lab/Dockerfile
@@ -1,14 +1,13 @@
ARG LIBTELIO_ENV_SEC_CONTAINER_REGISTRY
ARG LIBTELIO_ENV_NAT_LAB_DEPS_TAG
-FROM ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/vpn/client/libtelio-build/natlab-deps-common:${LIBTELIO_ENV_NAT_LAB_DEPS_TAG}
+FROM ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/nat_lab:${LIBTELIO_ENV_NAT_LAB_DEPS_TAG}
LABEL org.opencontainers.image.authors="info@nordsec.com"
LABEL org.opencontainers.image.source="https://github.com/NordSecurity/libtelio/blob/main/nat-lab/Dockerfile"
COPY --chmod=0755 bin/ /opt/bin/
COPY --chmod=0755 data/custom_debs /opt/custom_debs
-RUN curl -LsSf https://astral.sh/uv/install.sh | sh
RUN sh -c ". ~/.bashrc && uv add --directory /opt/bin grpcio==1.73.1 protobuf==6.30.2 aiohttp==3.12.14 cryptography==45.0.5 blake3==1.0.5"
# If custom deb is found, install it. This command doesn't fail if it doesn't find anything.
diff --git a/nat-lab/bin/nordlynx b/nat-lab/bin/nordlynx
index b37cc3498..786e4f105 100755
--- a/nat-lab/bin/nordlynx
+++ b/nat-lab/bin/nordlynx
@@ -2,64 +2,34 @@
set -euxo pipefail
+up() {
+ local service="$1"
+ echo "$service: enable + start"
+ if systemctl enable --now "$service" && systemctl is-active --quiet "$service"; then
+ echo "OK: $service"
+ else
+ echo "FAIL: $service"
+ systemctl status "$s" --no-pager || true
+ exit 1
+ fi
+}
+
# This is a workaround for 6.x kernel bug, which can cause NULL-deref when
# iptables are executed too early at boot.
# More details:
# https://lore.kernel.org/netdev/20240731213046.6194-3-pablo@netfilter.org/T/#m597226ed32420a20bca5be58cf1c21073f06083b
sleep 5
-sysctl net.ipv4.ip_forward=1
-sysctl net.ipv4.conf.all.rp_filter=0
-sysctl net.ipv4.conf.default.rp_filter=0
-sysctl net.ipv6.conf.all.disable_ipv6=0
-sysctl net.ipv6.conf.default.disable_ipv6=0
-sysctl net.ipv6.conf.all.forwarding=1
-
-mkdir -p /etc/nordlynx
-ip link add dev nordlynx0 type nordlynx
-ip addr add 10.5.0.1/16 dev nordlynx0
-ip addr add 100.64.0.1/10 dev nordlynx0
-ip link set mtu 1420 dev nordlynx0
-ip link set dev nordlynx0 up
-
-nlx set nordlynx0 listen-port 1023
-
-iptables -A FORWARD -i nordlynx0 -j ACCEPT;
-iptables -A FORWARD -o nordlynx0 -j ACCEPT;
-iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE
-
# Backup IP tables
iptables-save -f iptables_backup
ip6tables-save -f ip6tables_backup
-# courtesy sleep so it would be really all up and running
-sleep 2
-
-ip addr s > /var/log/ipaddr.show
-nlx > /var/log/nlx.log
-/opt/fakefm/fakefm/main -s testing123 > /var/log/fakefm.log 2>&1 &
-/opt/nlx-radius/nlx-radius/target/release/nlx-radius > /var/log/nordlynx.log 2>&1 &
-
-# run PQ upgrader
-cat > config.yml << EOL
-bind:
- port: 6480
-logging:
- output: file
- level: Debug
- file: /var/log/pq-upgrader.log
-dynamic_configuration:
- enabled: false
- port: 7771
-limits:
- peer:
- session_length: 5
- rate: 10
- burst: 5
-EOL
-
-pushd /opt/pq-upgrader/pq-upgr/upgrader
-./upgrader > /var/log/upgrader.log 2>&1 &
-popd
+echo "Setting up services..."
+systemctl daemon-reload
+up nlx-quick@nordlynx0
+up nlx-radius
+up fakefm.service
+up pq-upgrader
+up nlx-ns
touch /ready
diff --git a/nat-lab/bin/windows-client b/nat-lab/bin/windows-client
index 2cb57bc70..3cc99946e 100755
--- a/nat-lab/bin/windows-client
+++ b/nat-lab/bin/windows-client
@@ -13,6 +13,15 @@ last_rc=1
while [ $SECONDS -lt $end ]; do
if output=$(python3 /run/qga.py powershell -Command '$svc = Get-Service -Name "sshd" -ErrorAction SilentlyContinue; if ($null -eq $svc) { Write-Output "OpenSSH SSH Server (sshd) service not found"; exit 3 } elseif ($svc.Status -eq "Running") { Write-Output "OpenSSH SSH Server is running"; exit 0 } else { Write-Output ("OpenSSH SSH Server status: " + $svc.Status); exit 2 }'); then
echo "$output"
+
+ # Enable Windows Error Reporting crash dumps
+ echo "Configuring WER crash dumps..."
+ python3 /run/qga.py powershell -Command 'New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" -Force'
+ python3 /run/qga.py powershell -Command 'Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" -Name "DumpFolder" -Value "C:\CrashDumps" -Type ExpandString'
+ python3 /run/qga.py powershell -Command 'Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" -Name "DumpType" -Value 2 -Type DWord'
+ python3 /run/qga.py powershell -Command 'New-Item -Path "C:\CrashDumps" -ItemType Directory -Force'
+ echo "WER crash dumps configured"
+
exit 0
else
last_rc=$?
diff --git a/nat-lab/docker-compose.yml b/nat-lab/docker-compose.yml
index 631b7c221..24131da7c 100644
--- a/nat-lab/docker-compose.yml
+++ b/nat-lab/docker-compose.yml
@@ -14,7 +14,7 @@ services:
nlx-01: &common-nlx
hostname: nlx-01
privileged: true
- image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/dockur_linux:${LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG}
+ image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/dockur_linux:${LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG}
devices:
- "/dev/net/tun"
- "/dev/kvm"
@@ -107,7 +107,7 @@ services:
windows-client-01: &windows-client
hostname: windows-client-01
- image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/dockur_windows:${LIBTELIO_ENV_NAT_LAB_WINDOWS_VM_TAG}
+ image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/dockur_windows:${LIBTELIO_ENV_NAT_LAB_WINDOWS_VM_TAG}
privileged: true
devices:
- "/dev/net/tun"
@@ -191,7 +191,7 @@ services:
mac-client-01: &mac-client
hostname: mac-client-01
- image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/dockur_macos:${LIBTELIO_ENV_NAT_LAB_MACOS_VM_TAG}
+ image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/dockur_macos:${LIBTELIO_ENV_NAT_LAB_MACOS_VM_TAG}
privileged: true
devices:
- "/dev/net/tun"
@@ -483,7 +483,7 @@ services:
fullcone-gw-01: &common-fullcone-gw
hostname: fullcone-gw-01
privileged: true
- image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/dockur_linux:${LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG}
+ image: ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/automation/docker/libtelio/dockur_linux:${LIBTELIO_ENV_NAT_LAB_LINUX_VM_TAG}
devices:
- "/dev/net/tun"
- "/dev/kvm"
@@ -665,8 +665,7 @@ services:
context: .
dockerfile: openwrt.Dockerfile
args:
- LIBTELIO_ENV_SEC_CONTAINER_REGISTRY: $LIBTELIO_ENV_SEC_CONTAINER_REGISTRY
- LIBTELIO_ENV_NAT_LAB_DEPS_TAG: $LIBTELIO_ENV_NAT_LAB_DEPS_TAG
+ SEC_CONTAINER_REGISTRY: $LIBTELIO_ENV_SEC_CONTAINER_REGISTRY
cap_drop:
- ALL
cap_add:
diff --git a/nat-lab/openwrt.Dockerfile b/nat-lab/openwrt.Dockerfile
index e340229a8..ac0d9cebb 100644
--- a/nat-lab/openwrt.Dockerfile
+++ b/nat-lab/openwrt.Dockerfile
@@ -1,6 +1,6 @@
-ARG LIBTELIO_ENV_SEC_CONTAINER_REGISTRY
+ARG SEC_CONTAINER_REGISTRY
-FROM ${LIBTELIO_ENV_SEC_CONTAINER_REGISTRY}/low-level-hacks/third-party-build/openwrt_image/natlab-openwrt-24.10.2-x86-64:v0.0.2
+FROM ${SEC_CONTAINER_REGISTRY}/nord-projects/nordvpn/infra/llt/third-party-build/openwrt_image/natlab-openwrt-24.10.4-x86-64:v0.3.0
ENV QEMU_CONFIG_TIMEOUT="300"
@@ -13,7 +13,7 @@ RUN mkdir -p /var/lib/qemu-image
WORKDIR /var/lib/qemu-image
RUN mkdir -p /var/lib/qemu && \
- gunzip -c openwrt-24.10.2-x86-64-generic-ext4-combined.img.gz > /var/lib/qemu/image.raw
+ gunzip -c openwrt-24.10.4-x86-64-generic-ext4-combined.img.gz > /var/lib/qemu/image.raw
RUN mkdir -p /usr/local/share/vmconfig/container.d /usr/local/share/vmconfig/vm.d
RUN mkdir -p /var/lib/vmconfig/container.d /var/lib/vmconfig/vm.d
diff --git a/nat-lab/tests/helpers.py b/nat-lab/tests/helpers.py
index 4214b10a6..d0f2a64ce 100644
--- a/nat-lab/tests/helpers.py
+++ b/nat-lab/tests/helpers.py
@@ -604,10 +604,12 @@ async def print_network_state(connection: Connection) -> None:
ip_r,
)
- ip_tables_log = await connection.create_process(["iptables", "-L"]).execute()
+ ip_tables_log = await connection.create_process(
+ ["iptables", "--wait", "--list"]
+ ).execute()
ip_tables = ip_tables_log.get_stdout().strip()
log.debug(
- "--- Log of iptables -L command ---\n %s",
+ "--- Log of iptables --list command ---\n %s",
ip_tables,
)
diff --git a/nat-lab/tests/nordvpnlite.py b/nat-lab/tests/nordvpnlite.py
index 206edc2d4..f2e742c2e 100644
--- a/nat-lab/tests/nordvpnlite.py
+++ b/nat-lab/tests/nordvpnlite.py
@@ -114,7 +114,7 @@ async def assert_match_daemon_start(self, stdout: str):
class NordVpnLite:
- START_TIMEOUT_S = 3
+ START_TIMEOUT_S = 10
SOCKET_CHECK_INTERVAL_S = 0.5
NORDVPNLITE_CMD_CHECK_INTERVAL_S = 10 # TODO (LLT-6693): revert back to 1
diff --git a/nat-lab/tests/telio.py b/nat-lab/tests/telio.py
index 6e4b18624..1e3414d30 100644
--- a/nat-lab/tests/telio.py
+++ b/nat-lab/tests/telio.py
@@ -514,7 +514,7 @@ async def on_stderr(stderr: str) -> None:
async with AsyncExitStack() as exit_stack:
await exit_stack.enter_async_context(make_tcpdump([self._connection]))
- if isinstance(self._connection, DockerConnection):
+ if isinstance(self._connection, DockerConnection) or self._connection.target_os == TargetOS.Windows:
await self.clear_core_dumps()
await self.clear_system_log()
@@ -569,7 +569,7 @@ async def on_stderr(stderr: str) -> None:
"[%s] Test cleanup: Stopping tcpdump and collecting core dumps",
self._node.name,
)
- if isinstance(self._connection, DockerConnection):
+ if isinstance(self._connection, DockerConnection) or self._connection.target_os == TargetOS.Windows:
await self.collect_core_dumps()
log.info(
@@ -1381,18 +1381,28 @@ async def flush_logs(self) -> None:
# - have set the NATLAB_SAVE_LOGS environment variable
# - want to have natlab automatically collect core dumps for you
def get_coredump_folder(self) -> tuple[str, str]:
+ if self._connection.target_os == TargetOS.Windows:
+ return "C:\\CrashDumps", ""
return "/var/crash", "core-"
def should_skip_core_dump_collection(self) -> bool:
return (
os.environ.get("NATLAB_SAVE_LOGS") is None
- or self._connection.target_os != TargetOS.Linux
+ or self._connection.target_os not in (TargetOS.Linux, TargetOS.Windows)
)
async def clear_core_dumps(self):
if self.should_skip_core_dump_collection():
return
+ if self._connection.target_os == TargetOS.Windows:
+ await self._connection.create_process(
+ ["powershell", "-Command",
+ "Remove-Item -Path 'C:\\CrashDumps\\*' -Force -ErrorAction SilentlyContinue"],
+ quiet=True,
+ ).execute()
+ return
+
coredump_folder, _ = self.get_coredump_folder()
# clear the existing system core dumps
@@ -1408,21 +1418,43 @@ async def collect_core_dumps(self):
if self.should_skip_core_dump_collection():
return
+ coredump_dir = "coredumps"
+ os.makedirs(coredump_dir, exist_ok=True)
+ test_name = get_current_test_case_and_parameters()[0] or ""
+
+ if self._connection.target_os == TargetOS.Windows:
+ try:
+ process = await self._connection.create_process(
+ ["powershell", "-Command",
+ "Get-ChildItem -Path 'C:\\CrashDumps' -Filter '*.dmp' | Select-Object -ExpandProperty FullName"],
+ quiet=True,
+ ).execute()
+ dump_files = [f.strip() for f in process.get_stdout().strip().splitlines() if f.strip()]
+ except ProcessExecError as e:
+ log.warning("[%s] Failed to list Windows crash dumps: %s", self._node.name, e)
+ dump_files = []
+
+ for i, dump_path in enumerate(dump_files):
+ file_name = dump_path.rsplit("\\", 1)[-1]
+ log.info("[%s] Collecting Windows crash dump: %s", self._node.name, dump_path)
+ await self._connection.download(dump_path, coredump_dir)
+ # Rename to include test name
+ downloaded = os.path.join(coredump_dir, file_name)
+ if os.path.exists(downloaded):
+ os.rename(downloaded, os.path.join(coredump_dir, f"{test_name}_{file_name}_{i}.dmp"))
+ return
+
coredump_folder, file_prefix = self.get_coredump_folder()
dump_files = await find_files(
self._connection, coredump_folder, f"{file_prefix}*"
)
- coredump_dir = "coredumps"
- os.makedirs(coredump_dir, exist_ok=True)
-
should_copy_coredumps = len(dump_files) > 0
# if we collected some core dumps, copy them
if isinstance(self._connection, DockerConnection) and should_copy_coredumps:
container_name = container_id(self._connection.tag)
- test_name = get_current_test_case_and_parameters()[0] or ""
for i, file_path in enumerate(dump_files):
file_name = file_path.rsplit("/", 1)[-1]
core_dump_destination = (
diff --git a/nat-lab/tests/test_core_api.py b/nat-lab/tests/test_core_api.py
index 5e81c2d65..beec4b35c 100644
--- a/nat-lab/tests/test_core_api.py
+++ b/nat-lab/tests/test_core_api.py
@@ -106,15 +106,18 @@ async def clean_up_machines(connection: Connection):
authorization_header=BEARER_AUTHORIZATION_HEADER,
)
- for machine in machines:
- await send_https_request(
- connection,
- f"{CORE_API_URL}/v1/meshnet/machines/{machine['identifier']}",
- "DELETE",
- CORE_API_CA_CERTIFICATE_PATH,
- expect_response=False,
- authorization_header=BEARER_AUTHORIZATION_HEADER,
- )
+ # Check if machines is a valid list response
+ if isinstance(machines, list):
+ for machine in machines:
+ if "identifier" in machine:
+ await send_https_request(
+ connection,
+ f"{CORE_API_URL}/v1/meshnet/machines/{machine['identifier']}",
+ "DELETE",
+ CORE_API_CA_CERTIFICATE_PATH,
+ expect_response=False,
+ authorization_header=BEARER_AUTHORIZATION_HEADER,
+ )
async def register_vpn_server_key(
@@ -239,7 +242,10 @@ async def test_get_all_machines(registered_machines, machine_data):
assert isinstance(response_data, list)
assert len(response_data) == 2
- for machine, data in zip(registered_machines, machine_data):
+ for machine, data in zip(response_data, machine_data):
+ assert (
+ "identifier" in machine
+ ), f"Machine response missing 'identifier': {machine}"
verify_uuid(machine["identifier"])
assert machine["public_key"] == data.public_key
assert machine["os"] == data.os
@@ -299,6 +305,9 @@ async def test_delete_registered_machine(registered_machines, machine_data):
)
machine = registered_machines[0]
+ assert (
+ "identifier" in machine
+ ), f"Machine response missing 'identifier': {machine}"
await send_https_request(
connection,
diff --git a/nat-lab/tests/test_events_link_state.py b/nat-lab/tests/test_events_link_state.py
index 976b3881b..f170d5bf4 100644
--- a/nat-lab/tests/test_events_link_state.py
+++ b/nat-lab/tests/test_events_link_state.py
@@ -391,13 +391,13 @@ def __init__(self, conn: Connection):
async def __aenter__(self):
proc = self._conn.create_process([
"iptables",
- "-I",
+ "--insert",
"INPUT",
- "-p",
+ "--protocol",
"icmp",
"--icmp-type",
"echo-request",
- "-j",
+ "--jump",
"DROP",
])
await proc.execute()
@@ -405,13 +405,13 @@ async def __aenter__(self):
async def __aexit__(self, *_):
proc = self._conn.create_process([
"iptables",
- "-D",
+ "--delete",
"INPUT",
- "-p",
+ "--protocol",
"icmp",
"--icmp-type",
"echo-request",
- "-j",
+ "--jump",
"DROP",
])
await proc.execute()
diff --git a/nat-lab/tests/test_notification_center.py b/nat-lab/tests/test_notification_center.py
index 18797c9f5..61e403edb 100644
--- a/nat-lab/tests/test_notification_center.py
+++ b/nat-lab/tests/test_notification_center.py
@@ -178,6 +178,13 @@ async def test_nc_register():
payload,
authorization_header=BEARER_AUTHORIZATION_HEADER,
)
+
+ # Check if the response contains an error
+ if isinstance(https_process_stdout, dict) and "errors" in https_process_stdout:
+ raise AssertionError(
+ f"API returned error: {https_process_stdout['errors']}"
+ )
+
response = MachineResponse(**https_process_stdout)
identifier = response.identifier
diff --git a/nat-lab/tests/utils/router/linux_router.py b/nat-lab/tests/utils/router/linux_router.py
index 98ff9bca7..5fd148786 100644
--- a/nat-lab/tests/utils/router/linux_router.py
+++ b/nat-lab/tests/utils/router/linux_router.py
@@ -251,16 +251,17 @@ async def create_exit_node_route(self) -> None:
await self._connection.create_process(
[
"iptables",
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"nat",
- "-A",
+ "--append",
"POSTROUTING",
- "-s",
+ "--source",
"100.64.0.0/10",
"!",
- "-o",
+ "--out-interface",
self._interface_name,
- "-j",
+ "--jump",
"MASQUERADE",
],
quiet=True,
@@ -270,16 +271,17 @@ async def create_exit_node_route(self) -> None:
await self._connection.create_process(
[
"ip6tables",
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"nat",
- "-A",
+ "--append",
"POSTROUTING",
- "-s",
+ "--source",
config.LIBTELIO_IPV6_WG_SUBNET + "::/64",
"!",
- "-o",
+ "--out-interface",
self._interface_name,
- "-j",
+ "--jump",
"MASQUERADE",
],
quiet=True,
@@ -291,16 +293,17 @@ async def delete_exit_node_route(self) -> None:
await self._connection.create_process(
[
"iptables",
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"nat",
- "-D",
+ "--delete",
"POSTROUTING",
- "-s",
+ "--source",
"100.64.0.0/10",
"!",
- "-o",
+ "--out-interface",
self._interface_name,
- "-j",
+ "--jump",
"MASQUERADE",
],
quiet=True,
@@ -315,16 +318,17 @@ async def delete_exit_node_route(self) -> None:
await self._connection.create_process(
[
"ip6tables",
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"nat",
- "-D",
+ "--delete",
"POSTROUTING",
- "-s",
+ "--source",
config.LIBTELIO_IPV6_WG_SUBNET + "::/64",
"!",
- "-o",
+ "--out-interface",
self._interface_name,
- "-j",
+ "--jump",
"MASQUERADE",
],
quiet=True,
@@ -349,13 +353,14 @@ async def disable_path(self, address: str) -> AsyncIterator:
await self._connection.create_process(
[
iptables_string,
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"filter",
- "-A",
+ "--append",
"INPUT",
- "-s",
+ "--source",
address,
- "-j",
+ "--jump",
"DROP",
],
quiet=True,
@@ -363,13 +368,14 @@ async def disable_path(self, address: str) -> AsyncIterator:
await self._connection.create_process(
[
iptables_string,
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"filter",
- "-A",
+ "--append",
"OUTPUT",
- "-d",
+ "--destination",
address,
- "-j",
+ "--jump",
"DROP",
],
quiet=True,
@@ -381,13 +387,14 @@ async def disable_path(self, address: str) -> AsyncIterator:
await self._connection.create_process(
[
iptables_string,
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"filter",
- "-D",
+ "--delete",
"INPUT",
- "-s",
+ "--source",
address,
- "-j",
+ "--jump",
"DROP",
],
quiet=True,
@@ -395,13 +402,14 @@ async def disable_path(self, address: str) -> AsyncIterator:
await self._connection.create_process(
[
iptables_string,
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"filter",
- "-D",
+ "--delete",
"OUTPUT",
- "-d",
+ "--destination",
address,
- "-j",
+ "--jump",
"DROP",
],
quiet=True,
@@ -419,15 +427,16 @@ async def break_tcp_conn_to_host(self, address: str) -> AsyncIterator:
await self._connection.create_process(
[
iptables_string,
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"filter",
- "-A",
+ "--append",
"OUTPUT",
"--destination",
address,
- "-p",
+ "--protocol",
"tcp",
- "-j",
+ "--jump",
"REJECT",
"--reject-with",
"tcp-reset",
@@ -441,15 +450,16 @@ async def break_tcp_conn_to_host(self, address: str) -> AsyncIterator:
await self._connection.create_process(
[
iptables_string,
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"filter",
- "-D",
+ "--delete",
"OUTPUT",
"--destination",
address,
- "-p",
+ "--protocol",
"tcp",
- "-j",
+ "--jump",
"REJECT",
"--reject-with",
"tcp-reset",
@@ -469,15 +479,16 @@ async def break_udp_conn_to_host(self, address: str) -> AsyncIterator:
await self._connection.create_process(
[
iptables_string,
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"filter",
- "-A",
+ "--append",
"OUTPUT",
"--destination",
address,
- "-p",
+ "--protocol",
"udp",
- "-j",
+ "--jump",
"REJECT",
"--reject-with",
"icmp-host-unreachable",
@@ -491,15 +502,16 @@ async def break_udp_conn_to_host(self, address: str) -> AsyncIterator:
await self._connection.create_process(
[
iptables_string,
- "-t",
+ "--wait", # Wait for xtables lock
+ "--table",
"filter",
- "-D",
+ "--delete",
"OUTPUT",
"--destination",
address,
- "-p",
+ "--protocol",
"udp",
- "-j",
+ "--jump",
"REJECT",
"--reject-with",
"icmp-host-unreachable",
@@ -514,13 +526,14 @@ async def block_udp_port(self, port: int) -> AsyncIterator:
await self._connection.create_process(
[
"iptables",
- "-A",
+ "--wait", # Wait for xtables lock
+ "--append",
"OUTPUT",
- "-p",
+ "--protocol",
"udp",
"--sport",
str(port),
- "-j",
+ "--jump",
"DROP",
],
quiet=True,
@@ -532,13 +545,14 @@ async def block_udp_port(self, port: int) -> AsyncIterator:
await self._connection.create_process(
[
"iptables",
- "-D",
+ "--wait", # Wait for xtables lock
+ "--delete",
"OUTPUT",
- "-p",
+ "--protocol",
"udp",
"--sport",
str(port),
- "-j",
+ "--jump",
"DROP",
],
quiet=True,
@@ -549,13 +563,14 @@ async def block_tcp_port(self, port: int) -> AsyncIterator:
await self._connection.create_process(
[
"iptables",
- "-A",
+ "--wait", # Wait for xtables lock
+ "--append",
"OUTPUT",
- "-p",
+ "--protocol",
"tcp",
"--dport",
str(port),
- "-j",
+ "--jump",
"DROP",
],
quiet=True,
@@ -567,13 +582,14 @@ async def block_tcp_port(self, port: int) -> AsyncIterator:
await self._connection.create_process(
[
"iptables",
- "-D",
+ "--wait", # Wait for xtables lock
+ "--delete",
"OUTPUT",
- "-p",
+ "--protocol",
"tcp",
"--dport",
str(port),
- "-j",
+ "--jump",
"DROP",
],
quiet=True,
diff --git a/nat-lab/tests/utils/tcpdump.py b/nat-lab/tests/utils/tcpdump.py
index 5362eb1c6..08a9c94ef 100644
--- a/nat-lab/tests/utils/tcpdump.py
+++ b/nat-lab/tests/utils/tcpdump.py
@@ -19,7 +19,7 @@
TargetOS.Mac: "/var/root/dump.pcap",
TargetOS.Windows: "C:\\workspace\\dump.pcap",
}
-TCPDUMP_START_EVENT_TIMEOUT_S = 3
+TCPDUMP_START_EVENT_TIMEOUT_S = 10
class TcpDump:
diff --git a/nat-lab/tests/utils/vm/openwrt_vm_util.py b/nat-lab/tests/utils/vm/openwrt_vm_util.py
index a7dc9b948..a870a8555 100644
--- a/nat-lab/tests/utils/vm/openwrt_vm_util.py
+++ b/nat-lab/tests/utils/vm/openwrt_vm_util.py
@@ -1,9 +1,10 @@
import asyncssh
+import os
from config import get_root_path
from utils.connection import Connection
from utils.process import ProcessExecError
-DIST_PATH = "dist/openwrt/"
+DIST_PATH = f"dist/openwrt/{os.getenv('TELIO_BIN_PROFILE')}/x86_64/"
NATLAB_DATA_PATH = "nat-lab/data/"
LOCAL_BIN_DIR = "/tmp/"
OWR_CERT_PATH = "/etc/ssl/server_certificate/"