mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2026-01-23 02:14:29 +00:00
VNC-257 Implement env var config override
This commit is contained in:
parent
71bb0013eb
commit
6e3d2072f2
13 changed files with 483 additions and 129 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -21,6 +21,9 @@ builder/build/
|
|||
builder/www/
|
||||
spec/tmp
|
||||
|
||||
Benchmark.xml
|
||||
SelfBench.xml
|
||||
|
||||
# Deb building artefacts
|
||||
debian/.debhelper/
|
||||
debian/files
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
|
|
@ -15,6 +16,16 @@ config_filename = os.path.join(config_dir, "config.yaml")
|
|||
if os.getenv('KASMVNC_SPEC_DEBUG_OUTPUT'):
|
||||
debug_output = True
|
||||
|
||||
def run_vncserver_to_print_xvnc_cli_options(env=os.environ):
|
||||
return run_cmd(f'vncserver -dry-run -config {config_filename}', env=env)
|
||||
|
||||
def pick_cli_option(cli_option, xvnc_cmd):
|
||||
cli_option_regex = re.compile(f'\'?-{cli_option}\'?(?:\s+[^-][^\s]*|$)')
|
||||
results = cli_option_regex.findall(xvnc_cmd)
|
||||
if len(results) == 0:
|
||||
return None
|
||||
|
||||
return ' '.join(results)
|
||||
|
||||
def write_config(config_text):
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
|
|
|
|||
133
spec/vncserver_env_var_to_cli_spec.py
Normal file
133
spec/vncserver_env_var_to_cli_spec.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import os
|
||||
from mamba import description, context, fcontext, it, fit, _it, before, after
|
||||
from expects import expect, equal, contain, match
|
||||
|
||||
from helper.spec_helper import start_xvnc, kill_xvnc, run_cmd, clean_env, \
|
||||
add_kasmvnc_user_docker, clean_kasm_users, start_xvnc_pexpect, \
|
||||
write_config, config_filename, pick_cli_option, \
|
||||
run_vncserver_to_print_xvnc_cli_options
|
||||
|
||||
with description('Env var config override') as self:
|
||||
with context("env var override is turned off"):
|
||||
with it("doesn't override, when setting is defined in config"):
|
||||
write_config('''
|
||||
desktop:
|
||||
allow_resize: true
|
||||
server:
|
||||
allow_environment_variables_to_override_config_settings: false
|
||||
''')
|
||||
env = os.environ.copy()
|
||||
env["KVNC_DESKTOP_ALLOW_RESIZE"] = "false"
|
||||
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options(env=env)
|
||||
cli_option = pick_cli_option('AcceptSetDesktopSize',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-AcceptSetDesktopSize '1'"))
|
||||
|
||||
with it("doesn't override, when setting is not defined in config"):
|
||||
write_config('''
|
||||
desktop:
|
||||
allow_resize: true
|
||||
''')
|
||||
env = os.environ.copy()
|
||||
env["KVNC_DESKTOP_ALLOW_RESIZE"] = "false"
|
||||
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options(env=env)
|
||||
cli_option = pick_cli_option('AcceptSetDesktopSize',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-AcceptSetDesktopSize '1'"))
|
||||
|
||||
with context("env var override is turned on"):
|
||||
with it("converts env var to CLI option"):
|
||||
write_config('''
|
||||
desktop:
|
||||
allow_resize: true
|
||||
server:
|
||||
allow_environment_variables_to_override_config_settings: true
|
||||
''')
|
||||
env = os.environ.copy()
|
||||
env["KVNC_DESKTOP_ALLOW_RESIZE"] = "false"
|
||||
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options(env=env)
|
||||
cli_option = pick_cli_option('AcceptSetDesktopSize',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-AcceptSetDesktopSize '0'"))
|
||||
|
||||
with it("produces error message if env var has invalid value"):
|
||||
write_config('''
|
||||
desktop:
|
||||
allow_resize: true
|
||||
server:
|
||||
allow_environment_variables_to_override_config_settings: true
|
||||
''')
|
||||
env = os.environ.copy()
|
||||
env["KVNC_DESKTOP_ALLOW_RESIZE"] = "none"
|
||||
|
||||
completed_process = run_cmd(f'vncserver -dry-run -test-output-topic validation -config {config_filename}', print_stderr=False, env=env)
|
||||
expect(completed_process.stderr).to(contain("desktop.allow_resize 'none': must be true or false"))
|
||||
|
||||
with it("produces error message and exits if env var name is unsupported"):
|
||||
write_config('''
|
||||
foo: true
|
||||
desktop:
|
||||
allow_resize: true
|
||||
server:
|
||||
allow_environment_variables_to_override_config_settings: true
|
||||
''')
|
||||
env = os.environ.copy()
|
||||
env["KVNC_FOO"] = "none"
|
||||
|
||||
completed_process = run_cmd(f'vncserver -test-output-topic validation -config {config_filename}', print_stderr=False, env=env)
|
||||
expect(completed_process.stderr).to(contain("Unsupported config env vars found:\nKVNC_FOO"))
|
||||
|
||||
with context("config setting server.allow_environment_variables_to_override_config_settings"):
|
||||
with it("produces error message if config has invalid value"):
|
||||
write_config('''
|
||||
server:
|
||||
allow_environment_variables_to_override_config_settings: none
|
||||
''')
|
||||
|
||||
completed_process = run_cmd(f'vncserver -dry-run -test-output-topic validation -config {config_filename}', print_stderr=False)
|
||||
expect(completed_process.stderr).to(contain("server.allow_environment_variables_to_override_config_settings 'none': must be true or false"))
|
||||
|
||||
with it("doesn't interpolate env vars into value"):
|
||||
write_config('''
|
||||
server:
|
||||
allow_environment_variables_to_override_config_settings: ${ALLOW_OVERRIDE}
|
||||
''')
|
||||
|
||||
env = os.environ.copy()
|
||||
env["ALLOW_OVERRIDE"] = "true"
|
||||
|
||||
completed_process = run_cmd(f'vncserver -dry-run -test-output-topic validation -config {config_filename}',\
|
||||
print_stderr=False, env=env)
|
||||
expect(completed_process.stderr).\
|
||||
to(contain("server.allow_environment_variables_to_override_config_settings '${ALLOW_OVERRIDE}': must be true or false"))
|
||||
|
||||
with it("doesn't allow to override it with the corresponding env var if set to false"):
|
||||
write_config('''
|
||||
server:
|
||||
allow_environment_variables_to_override_config_settings: false
|
||||
''')
|
||||
|
||||
env = os.environ.copy()
|
||||
env["KVNC_SERVER_ALLOW_ENVIRONMENT_VARIABLES_TO_OVERRIDE_CONFIG_SETTINGS"] = "${ALLOW_OVERRIDE}"
|
||||
|
||||
completed_process = run_cmd(f'vncserver -dry-run -test-output-topic validation -config {config_filename}',\
|
||||
print_stderr=False, env=env)
|
||||
expect(completed_process.stderr).\
|
||||
not_to(contain("server.allow_environment_variables_to_override_config_settings '${ALLOW_OVERRIDE}': must be true or false"))
|
||||
|
||||
with it("doesn't allow to override it with the env var if set to true"):
|
||||
write_config('''
|
||||
server:
|
||||
allow_environment_variables_to_override_config_settings: true
|
||||
''')
|
||||
|
||||
env = os.environ.copy()
|
||||
env["KVNC_SERVER_ALLOW_ENVIRONMENT_VARIABLES_TO_OVERRIDE_CONFIG_SETTINGS"] = "${ALLOW_OVERRIDE}"
|
||||
|
||||
completed_process = run_cmd(f'vncserver -dry-run -test-output-topic validation -config {config_filename}',\
|
||||
print_stderr=False, env=env)
|
||||
expect(completed_process.stderr).\
|
||||
not_to(contain("server.allow_environment_variables_to_override_config_settings '${ALLOW_OVERRIDE}': must be true or false"))
|
||||
|
|
@ -1,48 +1,19 @@
|
|||
import os
|
||||
import re
|
||||
import shutil
|
||||
from os.path import expanduser
|
||||
from mamba import description, context, fcontext, it, fit, _it, before, after
|
||||
from expects import expect, equal, contain, match
|
||||
|
||||
from helper.spec_helper import start_xvnc, kill_xvnc, run_cmd, clean_env, \
|
||||
add_kasmvnc_user_docker, clean_kasm_users, start_xvnc_pexpect, \
|
||||
write_config, config_filename
|
||||
|
||||
home_dir = expanduser("~")
|
||||
vnc_dir = f'{home_dir}/.vnc'
|
||||
user_config = f'{vnc_dir}/kasmvnc.yaml'
|
||||
|
||||
|
||||
def run_vncserver():
|
||||
return run_cmd(f'vncserver -dry-run -config {config_filename}')
|
||||
|
||||
|
||||
def pick_cli_option(cli_option, xvnc_cmd):
|
||||
cli_option_regex = re.compile(f'\'?-{cli_option}\'?(?:\s+[^-][^\s]*|$)')
|
||||
results = cli_option_regex.findall(xvnc_cmd)
|
||||
if len(results) == 0:
|
||||
return None
|
||||
|
||||
return ' '.join(results)
|
||||
|
||||
|
||||
def prepare_env():
|
||||
os.makedirs(vnc_dir, exist_ok=True)
|
||||
shutil.copyfile('spec/kasmvnc.yaml', user_config)
|
||||
|
||||
write_config, config_filename, pick_cli_option, \
|
||||
run_vncserver_to_print_xvnc_cli_options
|
||||
|
||||
with description('YAML to CLI') as self:
|
||||
with before.all:
|
||||
prepare_env()
|
||||
|
||||
with context("convert a boolean key"):
|
||||
with it("convert true to 1"):
|
||||
write_config('''
|
||||
desktop:
|
||||
allow_resize: true
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('AcceptSetDesktopSize',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-AcceptSetDesktopSize '1'"))
|
||||
|
|
@ -52,7 +23,7 @@ with description('YAML to CLI') as self:
|
|||
desktop:
|
||||
allow_resize: false
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('AcceptSetDesktopSize',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-AcceptSetDesktopSize '0'"))
|
||||
|
|
@ -63,7 +34,7 @@ with description('YAML to CLI') as self:
|
|||
brute_force_protection:
|
||||
blacklist_threshold: 2
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('BlacklistThreshold',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-BlacklistThreshold '2'"))
|
||||
|
|
@ -74,7 +45,7 @@ with description('YAML to CLI') as self:
|
|||
ssl:
|
||||
pem_certificate: /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('cert',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(
|
||||
|
|
@ -87,7 +58,7 @@ with description('YAML to CLI') as self:
|
|||
- 0x22->0x40
|
||||
- 0x24->0x40
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('RemapKeys',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(
|
||||
|
|
@ -100,7 +71,7 @@ with description('YAML to CLI') as self:
|
|||
server_to_client:
|
||||
size: 20
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('DLP_ClipSendMax',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-DLP_ClipSendMax '20'"))
|
||||
|
|
@ -111,7 +82,7 @@ with description('YAML to CLI') as self:
|
|||
network:
|
||||
websocket_port: auto
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('websocketPort',
|
||||
completed_process.stdout)
|
||||
expect(["-websocketPort '8444'", "-websocketPort '8445'"]). \
|
||||
|
|
@ -122,7 +93,7 @@ with description('YAML to CLI') as self:
|
|||
network:
|
||||
websocket_port: 8555
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('websocketPort',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-websocketPort '8555'"))
|
||||
|
|
@ -130,7 +101,7 @@ with description('YAML to CLI') as self:
|
|||
with it("no key - no CLI option"):
|
||||
write_config('''
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('websocketPort',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal(None))
|
||||
|
|
@ -141,7 +112,7 @@ with description('YAML to CLI') as self:
|
|||
network:
|
||||
protocol: http
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('noWebsocket',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal(None))
|
||||
|
|
@ -151,7 +122,7 @@ with description('YAML to CLI') as self:
|
|||
network:
|
||||
protocol: vnc
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('noWebsocket',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-noWebsocket '1'"))
|
||||
|
|
@ -162,7 +133,7 @@ with description('YAML to CLI') as self:
|
|||
advanced:
|
||||
kasm_password_file: ${HOME}/.kasmpasswd
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('KasmPasswordFile',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-KasmPasswordFile '/home/docker/.kasmpasswd'"))
|
||||
|
|
@ -174,7 +145,7 @@ with description('YAML to CLI') as self:
|
|||
log_dest: logfile
|
||||
level: 40
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('Log',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-Log '*:stdout:40'"))
|
||||
|
|
@ -188,7 +159,7 @@ with description('YAML to CLI') as self:
|
|||
right: 40%
|
||||
bottom: 40
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('DLP_Region',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-DLP_Region '10,-10,40%,40'"))
|
||||
|
|
@ -200,7 +171,7 @@ with description('YAML to CLI') as self:
|
|||
advanced:
|
||||
x_font_path: auto
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('fp',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(match(r'/usr/share/fonts'))
|
||||
|
|
@ -208,7 +179,7 @@ with description('YAML to CLI') as self:
|
|||
with it("none specified"):
|
||||
write_config('''
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('fp',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(match(r'/usr/share/fonts'))
|
||||
|
|
@ -219,7 +190,7 @@ with description('YAML to CLI') as self:
|
|||
advanced:
|
||||
x_font_path: /src
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('fp', completed_process.stdout)
|
||||
expect(cli_option).to(equal("-fp '/src'"))
|
||||
|
||||
|
|
@ -239,7 +210,7 @@ with description('YAML to CLI') as self:
|
|||
network:
|
||||
interface: 0.0.0.0
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('interface',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-interface '0.0.0.0'"))
|
||||
|
|
@ -263,7 +234,7 @@ with description('YAML to CLI') as self:
|
|||
width: 1024
|
||||
height: 768
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('geometry',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-geometry '1024x768'"))
|
||||
|
|
@ -275,7 +246,7 @@ with description('YAML to CLI') as self:
|
|||
text:
|
||||
template: "星街すいせい"
|
||||
''')
|
||||
completed_process = run_vncserver()
|
||||
completed_process = run_vncserver_to_print_xvnc_cli_options()
|
||||
cli_option = pick_cli_option('DLP_WatermarkText',
|
||||
completed_process.stdout)
|
||||
expect(cli_option).to(equal("-DLP_WatermarkText '星街すいせい'"))
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ use Data::Dumper;
|
|||
|
||||
use KasmVNC::DataClumpValidator;
|
||||
use KasmVNC::Utils;
|
||||
use KasmVNC::SettingValidation;
|
||||
|
||||
our $fetchValueSub;
|
||||
$KasmVNC::CliOption::dataClumpValidator = KasmVNC::DataClumpValidator->new();
|
||||
@KasmVNC::CliOption::isActiveCallbacks = ();
|
||||
our @ISA = ('KasmVNC::SettingValidation');
|
||||
|
||||
our $ConfigValue;
|
||||
our $dataClumpValidator = KasmVNC::DataClumpValidator->new();
|
||||
our @isActiveCallbacks = ();
|
||||
|
||||
sub new {
|
||||
my ($class, $args) = @_;
|
||||
|
|
@ -56,20 +59,20 @@ sub activate {
|
|||
sub beforeIsActive {
|
||||
my $callback = shift;
|
||||
|
||||
push @KasmVNC::CliOption::isActiveCallbacks, $callback;
|
||||
push @isActiveCallbacks, $callback;
|
||||
}
|
||||
|
||||
sub isActiveByCallbacks {
|
||||
my $self = shift;
|
||||
|
||||
all { $_->($self) } @KasmVNC::CliOption::isActiveCallbacks;
|
||||
all { $_->($self) } @isActiveCallbacks;
|
||||
}
|
||||
|
||||
sub makeKeysWithValuesAccessible {
|
||||
my $self = shift;
|
||||
|
||||
foreach my $name (@{ $self->configKeyNames() }) {
|
||||
my $value = $self->fetchValue($name);
|
||||
my $value = $ConfigValue->($name);
|
||||
$self->{$name} = $value if defined($value);
|
||||
}
|
||||
}
|
||||
|
|
@ -100,39 +103,11 @@ sub deriveValue {
|
|||
my $self = shift;
|
||||
|
||||
my $value = $self->{deriveValueSub}->($self);
|
||||
$self->interpolateEnvVars($value);
|
||||
}
|
||||
|
||||
sub interpolateEnvVars {
|
||||
my $self = shift;
|
||||
my $value = shift;
|
||||
|
||||
return $value unless defined($value);
|
||||
|
||||
while ($value =~ /\$\{(\w+)\}/) {
|
||||
my $envValue = $ENV{$1};
|
||||
$value =~ s/\Q$&\E/$envValue/;
|
||||
}
|
||||
|
||||
$value;
|
||||
}
|
||||
|
||||
sub errorMessages {
|
||||
my $self = shift;
|
||||
|
||||
join "\n", @{ $self->{errors} };
|
||||
interpolateEnvVars($value);
|
||||
}
|
||||
|
||||
# private
|
||||
|
||||
sub isValid {
|
||||
my $self = shift;
|
||||
|
||||
$self->validate() unless $self->{validated};
|
||||
|
||||
$self->isNoErrorsPresent();
|
||||
}
|
||||
|
||||
sub validate {
|
||||
my $self = shift;
|
||||
|
||||
|
|
@ -142,22 +117,16 @@ sub validate {
|
|||
$self->{validated} = 1;
|
||||
}
|
||||
|
||||
sub isNoErrorsPresent {
|
||||
my $self = shift;
|
||||
|
||||
scalar @{ $self->{errors} } == 0;
|
||||
}
|
||||
|
||||
sub validateDataClump {
|
||||
my $self = shift;
|
||||
|
||||
$KasmVNC::CliOption::dataClumpValidator->validate($self);
|
||||
$dataClumpValidator->validate($self);
|
||||
}
|
||||
|
||||
sub configValues {
|
||||
my $self = shift;
|
||||
|
||||
map { $self->fetchValue($_->{name}) } @{ $self->{configKeys} };
|
||||
map { $ConfigValue->($_->{name}) } @{ $self->{configKeys} };
|
||||
}
|
||||
|
||||
sub configValue {
|
||||
|
|
@ -183,22 +152,10 @@ sub hasKey {
|
|||
first { $_ eq $configKey } @{ $self->configKeyNames() };
|
||||
}
|
||||
|
||||
sub addErrorMessage {
|
||||
my ($self, $errorMessage) = @_;
|
||||
|
||||
push @{ $self->{errors} }, $errorMessage;
|
||||
}
|
||||
|
||||
sub validateConfigValues {
|
||||
my $self = shift;
|
||||
|
||||
map { $_->validate($self) } @{ $self->{configKeys} };
|
||||
}
|
||||
|
||||
sub fetchValue {
|
||||
my $self = shift;
|
||||
|
||||
&$fetchValueSub(shift);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,14 @@ sub get {
|
|||
$value;
|
||||
}
|
||||
|
||||
sub set {
|
||||
my ($self, $absoluteKey, $value) = @_;
|
||||
my $path = absoluteKeyToHashPath($absoluteKey);
|
||||
my $config = $self->{data};
|
||||
|
||||
eval "\$config$path = \$value";
|
||||
}
|
||||
|
||||
sub exists {
|
||||
my ($self, $absoluteKey) = @_;
|
||||
my $path = absoluteKeyToHashPath($absoluteKey);
|
||||
|
|
|
|||
119
unix/KasmVNC/ConfigEnvVars.pm
Normal file
119
unix/KasmVNC/ConfigEnvVars.pm
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
package KasmVNC::ConfigEnvVars;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use v5.10;
|
||||
use Data::Dumper;
|
||||
|
||||
use Exporter;
|
||||
|
||||
@KasmVNC::ConfigEnvVars::ISA = qw(Exporter);
|
||||
|
||||
our @EXPORT = (
|
||||
'OverrideConfigWithConfigEnvVars',
|
||||
'CheckForUnsupportedConfigEnvVars'
|
||||
);
|
||||
|
||||
use constant ENV_VAR_OVERRIDE_SETTING => "server.allow_environment_variables_to_override_config_settings";
|
||||
|
||||
our @configKeyOverrideDenylist = (
|
||||
ENV_VAR_OVERRIDE_SETTING
|
||||
);
|
||||
|
||||
our $logger;
|
||||
our %prefixedEnvVars;
|
||||
our %envVarAllowlist;
|
||||
|
||||
our $ConfigValue;
|
||||
our $SetConfigValue;
|
||||
our $ShouldPrintTopic;
|
||||
our $SupportedAbsoluteKeys;
|
||||
|
||||
sub IsAllowEnvVarOverride {
|
||||
my $allowOverride = $ConfigValue->(ENV_VAR_OVERRIDE_SETTING) // "false";
|
||||
$allowOverride eq "true";
|
||||
}
|
||||
|
||||
sub OverrideConfigWithConfigEnvVars {
|
||||
return unless IsAllowEnvVarOverride();
|
||||
|
||||
%prefixedEnvVars = FetchPrefixedEnvVarsFromEnvironment();
|
||||
PrepareEnvVarAllowlist();
|
||||
|
||||
for my $envVarName (sort keys %prefixedEnvVars) {
|
||||
my $configKey = $envVarAllowlist{$envVarName};
|
||||
next unless defined($configKey);
|
||||
|
||||
my $envVarValue = GetEnvVarValue($envVarName);
|
||||
$logger->debug("Overriding $configKey with $envVarName=$envVarValue");
|
||||
$SetConfigValue->($configKey, $envVarValue);
|
||||
}
|
||||
}
|
||||
|
||||
sub GetEnvVarValue {
|
||||
my $envVarName = shift;
|
||||
|
||||
$prefixedEnvVars{$envVarName};
|
||||
}
|
||||
|
||||
sub PrepareEnvVarAllowlist {
|
||||
%envVarAllowlist = ();
|
||||
my %configKeyOverrideAllowlist = %{ ConfigKeyOverrideAllowlist() };
|
||||
|
||||
for my $configKey (keys %configKeyOverrideAllowlist) {
|
||||
my $allowedEnvVarName = ConvertConfigKeyToEnvVarName($configKey);
|
||||
$envVarAllowlist{$allowedEnvVarName} = $configKey;
|
||||
}
|
||||
}
|
||||
|
||||
sub ConfigKeyOverrideAllowlist {
|
||||
my %configKeyOverrideAllowlist = %{ $SupportedAbsoluteKeys->() };
|
||||
delete @configKeyOverrideAllowlist{@configKeyOverrideDenylist};
|
||||
|
||||
\%configKeyOverrideAllowlist;
|
||||
}
|
||||
|
||||
sub FetchPrefixedEnvVarsFromEnvironment {
|
||||
my %prefixedEnvVars = map { $_ => $ENV{$_} } grep { /^KVNC_/ } keys %ENV;
|
||||
PrintPrefixedEnvVars();
|
||||
|
||||
%prefixedEnvVars;
|
||||
}
|
||||
|
||||
sub ConvertConfigKeyToEnvVarName {
|
||||
my $configKey = shift;
|
||||
my $envVarName = $configKey;
|
||||
|
||||
$envVarName =~ s/\./_/g;
|
||||
$envVarName = "KVNC_$envVarName";
|
||||
$envVarName = uc $envVarName;
|
||||
$logger->debug("$configKey -> $envVarName");
|
||||
|
||||
$envVarName;
|
||||
}
|
||||
|
||||
sub PrintPrefixedEnvVars {
|
||||
$logger->debug("Found KVNC_ env vars:");
|
||||
for my $envVarName (sort keys %prefixedEnvVars) {
|
||||
$logger->debug("$envVarName=$prefixedEnvVars{$envVarName}");
|
||||
}
|
||||
}
|
||||
|
||||
sub CheckForUnsupportedConfigEnvVars {
|
||||
return unless IsAllowEnvVarOverride();
|
||||
|
||||
my @unsupportedEnvVars =
|
||||
grep(!defined($envVarAllowlist{$_}), keys %prefixedEnvVars);
|
||||
|
||||
return if (scalar @unsupportedEnvVars == 0);
|
||||
|
||||
if ($ShouldPrintTopic->("validation")) {
|
||||
$logger->warn("Unsupported config env vars found:");
|
||||
$logger->warn(join("\n", @unsupportedEnvVars));
|
||||
$logger->warn();
|
||||
}
|
||||
|
||||
exit 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -8,7 +8,7 @@ use Data::Dumper;
|
|||
|
||||
use KasmVNC::Utils;
|
||||
|
||||
our $fetchValueSub;
|
||||
our $ConfigValue;
|
||||
|
||||
use constant {
|
||||
INT => 0,
|
||||
|
|
@ -86,18 +86,12 @@ sub isValueBlank {
|
|||
!defined($value) || $value eq "";
|
||||
}
|
||||
|
||||
sub fetchValue {
|
||||
my $self = shift;
|
||||
|
||||
&$fetchValueSub(shift);
|
||||
}
|
||||
|
||||
sub constructErrorMessage {
|
||||
my $self = shift;
|
||||
my $staticErrorMessage = shift;
|
||||
|
||||
my $name = $self->{name};
|
||||
my $value = join ", ", @{ listify($self->fetchValue($name)) };
|
||||
my $value = join ", ", @{ listify($ConfigValue->($name)) };
|
||||
|
||||
"$name '$value': $staticErrorMessage";
|
||||
}
|
||||
|
|
@ -117,7 +111,7 @@ sub isValidBoolean {
|
|||
sub value {
|
||||
my $self = shift;
|
||||
|
||||
$self->fetchValue($self->{name});
|
||||
$ConfigValue->($self->{name});
|
||||
}
|
||||
|
||||
our @EXPORT_OK = ('INT', 'STRING', 'BOOLEAN');
|
||||
|
|
|
|||
57
unix/KasmVNC/ConfigSetting.pm
Normal file
57
unix/KasmVNC/ConfigSetting.pm
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package KasmVNC::ConfigSetting;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use v5.10;
|
||||
|
||||
use KasmVNC::SettingValidation;
|
||||
|
||||
our @ISA = ('KasmVNC::SettingValidation');
|
||||
|
||||
our $ConfigValue;
|
||||
|
||||
sub new {
|
||||
my ($class, $args) = @_;
|
||||
my $self = bless {
|
||||
configKey => $args->{configKey},
|
||||
errors => []
|
||||
}, $class;
|
||||
}
|
||||
|
||||
sub toValue {
|
||||
my $self = shift;
|
||||
|
||||
$self->deriveValue();
|
||||
}
|
||||
|
||||
sub deriveValue {
|
||||
my $self = shift;
|
||||
|
||||
my $configKeyName = $self->{configKey}->{name};
|
||||
my $value = $ConfigValue->($configKeyName);
|
||||
interpolateEnvVars($value);
|
||||
}
|
||||
|
||||
sub configKeyNames {
|
||||
my $self = shift;
|
||||
|
||||
[$self->{configKey}->{name}];
|
||||
}
|
||||
|
||||
# private
|
||||
|
||||
sub validate {
|
||||
my $self = shift;
|
||||
|
||||
$self->validateConfigValue();
|
||||
|
||||
$self->{validated} = 1;
|
||||
}
|
||||
|
||||
sub validateConfigValue {
|
||||
my $self = shift;
|
||||
|
||||
$self->{configKey}->validate($self);
|
||||
}
|
||||
|
||||
1;
|
||||
33
unix/KasmVNC/SettingValidation.pm
Normal file
33
unix/KasmVNC/SettingValidation.pm
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package KasmVNC::SettingValidation;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use v5.10;
|
||||
|
||||
sub isValid {
|
||||
my $self = shift;
|
||||
|
||||
$self->validate() unless $self->{validated};
|
||||
|
||||
$self->isNoErrorsPresent();
|
||||
}
|
||||
|
||||
sub errorMessages {
|
||||
my $self = shift;
|
||||
|
||||
join "\n", @{ $self->{errors} };
|
||||
}
|
||||
|
||||
sub isNoErrorsPresent {
|
||||
my $self = shift;
|
||||
|
||||
scalar @{ $self->{errors} } == 0;
|
||||
}
|
||||
|
||||
sub addErrorMessage {
|
||||
my ($self, $errorMessage) = @_;
|
||||
|
||||
push @{ $self->{errors} }, $errorMessage;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -11,7 +11,7 @@ use Exporter;
|
|||
@KasmVNC::Utils::ISA = qw(Exporter);
|
||||
|
||||
our @EXPORT = ('listify', 'flatten', 'isBlank', 'isPresent', 'deriveBoolean',
|
||||
'printStackTrace');
|
||||
'printStackTrace', 'interpolateEnvVars');
|
||||
|
||||
sub listify {
|
||||
# Implementation based on Hyper::Functions
|
||||
|
|
@ -73,4 +73,17 @@ sub containsWideSymbols {
|
|||
$string =~ /[^\x00-\xFF]/;
|
||||
}
|
||||
|
||||
sub interpolateEnvVars {
|
||||
my $value = shift;
|
||||
|
||||
return $value unless defined($value);
|
||||
|
||||
while ($value =~ /\$\{(\w+)\}/) {
|
||||
my $envValue = $ENV{$1};
|
||||
$value =~ s/\Q$&\E/$envValue/;
|
||||
}
|
||||
|
||||
$value;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ server:
|
|||
no_user_session_timeout: never
|
||||
active_user_session_timeout: never
|
||||
inactive_user_session_timeout: never
|
||||
allow_environment_variables_to_override_config_settings: false
|
||||
|
||||
command_line:
|
||||
prompt: true
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ use DateTime;
|
|||
use DateTime::TimeZone;
|
||||
|
||||
use KasmVNC::CliOption;
|
||||
use KasmVNC::ConfigSetting;
|
||||
use KasmVNC::ConfigKey;
|
||||
use KasmVNC::PatternValidator;
|
||||
use KasmVNC::EnumValidator;
|
||||
|
|
@ -54,6 +55,8 @@ use KasmVNC::TextUI;
|
|||
use KasmVNC::Utils;
|
||||
use KasmVNC::Logger;
|
||||
|
||||
use KasmVNC::ConfigEnvVars;
|
||||
|
||||
use constant {
|
||||
NO_ARG_VALUE => 0,
|
||||
REQUIRED_ARG_VALUE => 1,
|
||||
|
|
@ -71,9 +74,18 @@ ParseAndProcessCliOptions();
|
|||
|
||||
PrepareLoggingAndXvncKillingFramework();
|
||||
CreateUserConfigIfNeeded();
|
||||
DefineConfigToCLIConversion();
|
||||
|
||||
DefinePossibleConfigSettings();
|
||||
|
||||
LoadConfigs();
|
||||
OverrideConfigWithConfigEnvVars();
|
||||
InterpolateEnvVarsIntoConfigValues();
|
||||
ValidateConfigValues();
|
||||
CheckForUnsupportedConfigEnvVars();
|
||||
CheckForUnsupportedConfigKeys();
|
||||
|
||||
ActivateConfigToCLIConversion();
|
||||
|
||||
SetAppSettingsFromConfigAndCli();
|
||||
DisableLegacyVncAuth();
|
||||
AllowXProgramsToConnectToXvnc();
|
||||
|
|
@ -1178,6 +1190,11 @@ sub DefineFilePathsAndStuff {
|
|||
$KasmVNC::Users::vncPasswdBin = $exedir . "kasmvncpasswd";
|
||||
$KasmVNC::Users::logger = $logger;
|
||||
$KasmVNC::Config::logger = $logger;
|
||||
$KasmVNC::ConfigEnvVars::logger = $logger;
|
||||
$KasmVNC::ConfigEnvVars::SupportedAbsoluteKeys = \&SupportedAbsoluteKeys;
|
||||
$KasmVNC::ConfigEnvVars::ConfigValue = \&ConfigValue;
|
||||
$KasmVNC::ConfigEnvVars::SetConfigValue = \&SetConfigValue;
|
||||
$KasmVNC::ConfigEnvVars::ShouldPrintTopic = \&ShouldPrintTopic;
|
||||
|
||||
$vncSystemConfigDir = "/etc/kasmvnc";
|
||||
if ($ENV{KASMVNC_DEVELOPMENT}) {
|
||||
|
|
@ -1227,8 +1244,9 @@ sub limitVncModeOptions {
|
|||
}
|
||||
|
||||
sub DefineConfigToCLIConversion {
|
||||
$KasmVNC::CliOption::fetchValueSub = \&ConfigValue;
|
||||
$KasmVNC::ConfigKey::fetchValueSub = \&ConfigValue;
|
||||
$KasmVNC::CliOption::ConfigValue = \&ConfigValue;
|
||||
$KasmVNC::ConfigSetting::ConfigValue = \&ConfigValue;
|
||||
$KasmVNC::ConfigKey::ConfigValue = \&ConfigValue;
|
||||
|
||||
my $regionValidator = KasmVNC::PatternValidator->new({
|
||||
pattern => qr/^(-)?\d+(%)?$/,
|
||||
|
|
@ -2700,7 +2718,7 @@ sub ShouldPrintTopic {
|
|||
|
||||
sub SupportedAbsoluteKeys {
|
||||
my @supportedAbsoluteKeys =
|
||||
map { $_->configKeyNames() } @allCliOptions;
|
||||
map { $_->configKeyNames() } (@allCliOptions, @configSettings);
|
||||
@supportedAbsoluteKeys = flatten(@supportedAbsoluteKeys);
|
||||
my %result = map { $_ => 1 } @supportedAbsoluteKeys;
|
||||
|
||||
|
|
@ -2724,7 +2742,6 @@ sub SupportedSectionsFromAbsoluteKey {
|
|||
|
||||
sub StartXvncOrExit {
|
||||
$cmd = ConstructXvncCmd();
|
||||
CheckForUnsupportedConfigKeys();
|
||||
CheckSslCertReadable();
|
||||
say $cmd if ($debug || IsDryRun()) && ShouldPrintTopic("xvnc-cmd");
|
||||
|
||||
|
|
@ -2877,6 +2894,13 @@ sub ConfigValue {
|
|||
return $configRef->get($absoluteKey);
|
||||
}
|
||||
|
||||
sub SetConfigValue {
|
||||
my ($absoluteKey, $value, $configRef) = @_;
|
||||
$configRef ||= $mergedConfig;
|
||||
|
||||
$configRef->set($absoluteKey, $value);
|
||||
}
|
||||
|
||||
sub DerivedValue {
|
||||
my $absoluteKey = shift;
|
||||
|
||||
|
|
@ -2951,7 +2975,6 @@ sub ConstructOptFromConfig{
|
|||
}
|
||||
|
||||
sub ConfigToCmd {
|
||||
ValidateConfig();
|
||||
%optFromConfig = %{ ConstructOptFromConfig() };
|
||||
|
||||
my @cmd = map { $cliArgMap{$_}->toString() } (keys %optFromConfig);
|
||||
|
|
@ -2960,20 +2983,20 @@ sub ConfigToCmd {
|
|||
return $cmdStr;
|
||||
}
|
||||
|
||||
sub ValidateConfig {
|
||||
foreach my $cliOption (@allCliOptions) {
|
||||
ValidateCliOption($cliOption);
|
||||
sub ValidateConfigValues {
|
||||
foreach my $setting (@allCliOptions, @configSettings) {
|
||||
ValidateSetting($setting);
|
||||
}
|
||||
}
|
||||
|
||||
sub ValidateCliOption {
|
||||
my $cliOption = $_[0];
|
||||
sub ValidateSetting {
|
||||
my $setting = $_[0];
|
||||
|
||||
return if ($cliOption->isValid());
|
||||
return if ($setting->isValid());
|
||||
|
||||
if (ShouldPrintTopic("validation")) {
|
||||
$logger->warn("config errors:");
|
||||
$logger->warn($cliOption->errorMessages());
|
||||
$logger->warn($setting->errorMessages());
|
||||
$logger->warn();
|
||||
}
|
||||
|
||||
|
|
@ -3033,3 +3056,34 @@ sub InitLogger {
|
|||
sub UseUtfStdio {
|
||||
use open qw( :std :encoding(UTF-8) );
|
||||
}
|
||||
|
||||
sub DefinePossibleConfigSettings {
|
||||
DefineConfigToCLIConversion();
|
||||
DefineConfigSettings();
|
||||
}
|
||||
|
||||
sub DefineConfigSettings {
|
||||
@configSettings = (
|
||||
KasmVNC::ConfigSetting->new({
|
||||
configKey => KasmVNC::ConfigKey->new({
|
||||
name => "server.allow_environment_variables_to_override_config_settings",
|
||||
type => KasmVNC::ConfigKey::BOOLEAN
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
sub InterpolateEnvVarsIntoConfigValues {
|
||||
my @interpolationDenylist = (
|
||||
"server.allow_environment_variables_to_override_config_settings"
|
||||
);
|
||||
my %supportedAbsoluteKeys = %{ SupportedAbsoluteKeys() };
|
||||
|
||||
delete @supportedAbsoluteKeys{@interpolationDenylist};
|
||||
|
||||
for my $absoluteKey (keys %supportedAbsoluteKeys) {
|
||||
my $value = ConfigValue($absoluteKey);
|
||||
my $interpolatedValue = interpolateEnvVars($value);
|
||||
SetConfigValue($absoluteKey, $interpolatedValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue