Merge branch 'upstream'
This commit is contained in:
commit
bf5c737c8f
12
Cargo.toml
12
Cargo.toml
@ -1,12 +0,0 @@
|
||||
[workspace]
|
||||
exclude = [ "build", "common-src", "perl", "scripts" ]
|
||||
members = [
|
||||
"pve-rs",
|
||||
"pmg-rs",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
# proxmox-tfa requires -time and -uuid as well, so enable *all* or *none* of them
|
||||
#proxmox-tfa = { path = "../proxmox/proxmox-tfa" }
|
||||
#proxmox-time = { path = "../proxmox/proxmox-time" }
|
||||
#proxmox-uuid = { path = "../proxmox/proxmox-uuid" }
|
101
Makefile
101
Makefile
@ -1,33 +1,11 @@
|
||||
CARGO ?= cargo
|
||||
|
||||
define to_upper
|
||||
$(shell echo "$(1)" | tr '[:lower:]' '[:upper:]')
|
||||
endef
|
||||
|
||||
ifeq ($(BUILD_MODE), release)
|
||||
CARGO_BUILD_ARGS += --release --offline
|
||||
DEBUG_LIBPATH :=
|
||||
else
|
||||
DEBUG_LIBPATH := "-L./target/debug",
|
||||
endif
|
||||
|
||||
define package_template
|
||||
sed -r \
|
||||
-e 's/\{\{PRODUCT\}\}/$(1)/g;' \
|
||||
-e 's/\{\{LIBRARY\}\}/$(2)/g;' \
|
||||
-e 's|\{\{DEBUG_LIBPATH\}\}|$(DEBUG_LIBPATH)|g;' \
|
||||
Proxmox/Lib/template.pm \
|
||||
>Proxmox/Lib/$(1).pm
|
||||
endef
|
||||
|
||||
define upload_template
|
||||
cd build; \
|
||||
dcmd --deb lib$(1)-rs-perl*.changes \
|
||||
| grep -v '.changes$$' \
|
||||
| tar -cf "$@.tar" -T-; \
|
||||
cat "$@.tar" | ssh -X repoman@repo.proxmox.com upload --product $(2) --dist bullseye
|
||||
endef
|
||||
|
||||
.PHONY: all
|
||||
all:
|
||||
ifeq ($(BUILD_TARGET), pve)
|
||||
@ -40,74 +18,29 @@ else
|
||||
@echo " - make pmg"
|
||||
endif
|
||||
|
||||
.PHONY: pve pmg
|
||||
pve pmg:
|
||||
@PERLMOD_PRODUCT=$(call to_upper,$@) \
|
||||
RUSTFLAGS="-L/usr/lib64/perl5/CORE -lperl" $(CARGO) build $(CARGO_BUILD_ARGS) -p $@-rs
|
||||
|
||||
.PHONY: gen
|
||||
gen:
|
||||
$(call package_template,PMG,pmg_rs)
|
||||
$(call package_template,PVE,pve_rs)
|
||||
perl ./scripts/genpackage.pl Common \
|
||||
Proxmox::RS::APT::Repositories \
|
||||
Proxmox::RS::CalendarEvent \
|
||||
Proxmox::RS::Subscription
|
||||
perl ./scripts/genpackage.pl PVE \
|
||||
PVE::RS::APT::Repositories \
|
||||
PVE::RS::OpenId \
|
||||
PVE::RS::ResourceScheduling::Static \
|
||||
PVE::RS::TFA
|
||||
perl ./scripts/genpackage.pl PMG \
|
||||
PMG::RS::APT::Repositories \
|
||||
PMG::RS::Acme \
|
||||
PMG::RS::CSR \
|
||||
PMG::RS::OpenId \
|
||||
PMG::RS::TFA
|
||||
|
||||
build:
|
||||
rm -rf build
|
||||
mkdir build
|
||||
echo system >build/rust-toolchain
|
||||
cp -a ./scripts ./build
|
||||
cp -a ./Cargo.toml ./build
|
||||
cp -a ./common ./build
|
||||
cp -a ./pve-rs ./build
|
||||
cp -a ./pmg-rs ./build
|
||||
cp -a ./Proxmox ./build
|
||||
$(MAKE) BUILD_MODE=release -C build -f ../Makefile gen
|
||||
mkdir -p ./build/pve-rs/Proxmox/Lib
|
||||
mv ./build/Proxmox/Lib/PVE.pm ./build/pve-rs/Proxmox/Lib/PVE.pm
|
||||
mkdir -p ./build/pmg-rs/Proxmox/Lib
|
||||
mv ./build/Proxmox/Lib/PMG.pm ./build/pmg-rs/Proxmox/Lib/PMG.pm
|
||||
mv ./build/PVE ./build/pve-rs
|
||||
mv ./build/PMG ./build/pmg-rs
|
||||
mv ./build/Proxmox ./build/common/pkg
|
||||
# Replace the symlinks with copies of the common code in pve/pmg:
|
||||
cd build; for i in pve pmg; do \
|
||||
rm ./$$i-rs/common ; \
|
||||
mkdir ./$$i-rs/common ; \
|
||||
cp -R ./common/src ./$$i-rs/common/src ; \
|
||||
done
|
||||
# So the common packages end up in ./build, rather than ./build/common
|
||||
mv ./build/common/pkg ./build/common-pkg
|
||||
|
||||
pve-deb: build
|
||||
cd ./build/pve-rs && dpkg-buildpackage -b -uc -us
|
||||
touch $@
|
||||
|
||||
pmg-deb: build
|
||||
cd ./build/pmg-rs && dpkg-buildpackage -b -uc -us
|
||||
touch $@
|
||||
|
||||
common-deb: build
|
||||
cd ./build/common-pkg && dpkg-buildpackage -b -uc -us
|
||||
touch $@
|
||||
|
||||
pve-upload: pve-deb
|
||||
$(call upload_template,pve,pve)
|
||||
pmg-upload: pmg-deb
|
||||
$(call upload_template,pmg,pmg)
|
||||
|
||||
# need to put into variable to ensure comma isn't interpreted as param separator on call
|
||||
common_target=pve,pmg
|
||||
common-upload: common-deb
|
||||
$(call upload_template,proxmox,$(common_target))
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
cargo clean
|
||||
rm -rf ./build ./PVE ./PMG ./pve-deb ./pmg-deb ./common-deb
|
||||
# Copy the workspace root into the sources
|
||||
mkdir build/pve-rs/.workspace
|
||||
cp -t build/pve-rs/.workspace Cargo.toml
|
||||
sed -i -e '/\[package\]/a\workspace = ".workspace"' build/pve-rs/Cargo.toml
|
||||
# Clear the member array and replace it with ".."
|
||||
sed -i -e '/^members = \[/,/^]$$/d' build/pve-rs/.workspace/Cargo.toml
|
||||
sed -i -e '/^\[workspace\]/a\members = [ ".." ]' build/pve-rs/.workspace/Cargo.toml
|
||||
# Copy the cargo config
|
||||
mkdir build/pve-rs/.cargo
|
||||
cp -t build/pve-rs/.cargo .cargo/config
|
||||
|
@ -1,68 +0,0 @@
|
||||
package Proxmox::Lib::{{PRODUCT}};
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Proxmox::Lib::{{PRODUCT}} - base module for {{PRODUCT}} rust bindings
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
package {{PRODUCT}}::RS::SomeBindings;
|
||||
|
||||
use base 'Proxmox::Lib::{{PRODUCT}}';
|
||||
|
||||
BEGIN { __PACKAGE__->bootstrap(); }
|
||||
|
||||
1;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is the base module of all {{PRODUCT}} bindings.
|
||||
Its job is to ensure the 'lib{{LIBRARY}}.so' library is loaded and provide a 'bootstrap' class
|
||||
method to load the actual code.
|
||||
|
||||
=cut
|
||||
|
||||
use DynaLoader;
|
||||
|
||||
sub library {
|
||||
return '{{LIBRARY}}';
|
||||
}
|
||||
|
||||
sub load : prototype($) {
|
||||
my ($pkg) = @_;
|
||||
|
||||
my $mod_name = $pkg->library();
|
||||
|
||||
my @dirs = (map "-L$_/auto", @INC);
|
||||
my $mod_file = DynaLoader::dl_findfile({{DEBUG_LIBPATH}}@dirs, $mod_name);
|
||||
die "failed to locate shared library for $mod_name (lib${mod_name}.so)\n" if !$mod_file;
|
||||
|
||||
my $lib = DynaLoader::dl_load_file($mod_file)
|
||||
or die "failed to load library '$mod_file'\n";
|
||||
|
||||
my $data = ($::{'proxmox-rs-library'} //= {});
|
||||
$data->{$mod_name} = $lib;
|
||||
$data->{-current} //= $lib;
|
||||
$data->{-package} //= $pkg;
|
||||
}
|
||||
|
||||
sub bootstrap {
|
||||
my ($pkg) = @_;
|
||||
|
||||
my $mod_name = $pkg->library();
|
||||
|
||||
my $bootstrap_name = 'boot_' . ($pkg =~ s/::/__/gr);
|
||||
|
||||
my $lib = $::{'proxmox-rs-library'}
|
||||
or die "rust library not available for '{PRODUCT}'\n";
|
||||
$lib = $lib->{$mod_name};
|
||||
|
||||
my $sym = DynaLoader::dl_find_symbol($lib, $bootstrap_name);
|
||||
die "failed to locate '$bootstrap_name'\n" if !defined $sym;
|
||||
my $boot = DynaLoader::dl_install_xsub($bootstrap_name, $sym, "src/FIXME.rs");
|
||||
$boot->();
|
||||
}
|
||||
|
||||
BEGIN { __PACKAGE__->load(); }
|
||||
|
||||
1;
|
@ -1,4 +1,4 @@
|
||||
include /usr/share/dpkg/default.mk
|
||||
include /usr/share/dpkg/pkg-info.mk
|
||||
|
||||
PACKAGE=libproxmox-rs-perl
|
||||
|
||||
@ -8,22 +8,63 @@ export GITVERSION:=$(shell git rev-parse HEAD)
|
||||
PERL_INSTALLVENDORARCH != perl -MConfig -e 'print $$Config{installvendorarch};'
|
||||
PERL_INSTALLVENDORLIB != perl -MConfig -e 'print $$Config{installvendorlib};'
|
||||
|
||||
DEB=${PACKAGE}_${DEB_VERSION}_${ARCH}.deb
|
||||
DEB=$(PACKAGE)_$(DEB_VERSION)_$(ARCH).deb
|
||||
DSC=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION).dsc
|
||||
BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM)
|
||||
|
||||
DESTDIR=
|
||||
|
||||
all:
|
||||
PERLMOD_GENPACKAGE := /usr/lib/perlmod/genpackage.pl \
|
||||
--lib=- \
|
||||
--lib-tag=proxmox \
|
||||
--lib-package=Proxmox::Lib::Common \
|
||||
--lib-prefix=Proxmox
|
||||
|
||||
# Point to any generated pm file (Proxmox/ dir is already present in this package)
|
||||
Proxmox/RS/CalendarEvent.pm:
|
||||
$(PERLMOD_GENPACKAGE) \
|
||||
Proxmox::RS::APT::Repositories \
|
||||
Proxmox::RS::CalendarEvent \
|
||||
Proxmox::RS::Notify \
|
||||
Proxmox::RS::Subscription
|
||||
|
||||
all: Proxmox/RS/CalendarEvent.pm
|
||||
true
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
install: Proxmox/RS/CalendarEvent.pm
|
||||
install -d -m755 $(DESTDIR)$(PERL_INSTALLVENDORLIB)
|
||||
find PVE \! -type d -print -exec install -Dm644 '{}' $(DESTDIR)$(PERL_INSTALLVENDORLIB)'/{}' ';'
|
||||
find Proxmox \! -type d -print -exec install -Dm644 '{}' $(DESTDIR)$(PERL_INSTALLVENDORLIB)'/{}' ';'
|
||||
rm $(DESTDIR)$(PERL_INSTALLVENDORLIB)'/Proxmox/Lib/template.pm'
|
||||
|
||||
$(BUILDDIR): debian PVE Proxmox Makefile
|
||||
rm -rf $(BUILDDIR) $(BUILDDIR).tmp
|
||||
mkdir $(BUILDDIR).tmp
|
||||
cp -t $(BUILDDIR).tmp -a debian PVE Proxmox Makefile
|
||||
mv $(BUILDDIR).tmp $(BUILDDIR)
|
||||
|
||||
.PHONY: deb
|
||||
deb: $(DEB)
|
||||
$(DEB): build
|
||||
cd build; dpkg-buildpackage -b -us -uc --no-pre-clean
|
||||
lintian $(DEBS)
|
||||
$(DEB): $(BUILDDIR)
|
||||
cd $(BUILDDIR); dpkg-buildpackage -b -us -uc --no-pre-clean
|
||||
lintian $(DEB)
|
||||
|
||||
.PHONY: dsc
|
||||
dsc: $(DSC)
|
||||
$(DSC): $(BUILDDIR)
|
||||
cd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d
|
||||
lintian $(DSC)
|
||||
|
||||
sbuild: $(DSC)
|
||||
sbuild $(DSC)
|
||||
|
||||
.PHONY: upload
|
||||
upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION)
|
||||
upload: $(DEB)
|
||||
# check if working directory is clean
|
||||
git diff --exit-code --stat && git diff --exit-code --stat --staged
|
||||
tar cf - $(DEB) | ssh -X repoman@repo.proxmox.com upload --product pve,pmg --dist $(DEB_DISTRIBUTION)
|
||||
|
||||
clean:
|
||||
rm -f *.deb *.dsc *.tar.* *.build *.buildinfo *.changes
|
||||
rm -rf $(PACKAGE)-[0-9]*/
|
||||
|
100
common/pkg/Proxmox/Lib/SslProbe.pm
Normal file
100
common/pkg/Proxmox/Lib/SslProbe.pm
Normal file
@ -0,0 +1,100 @@
|
||||
package Proxmox::Lib::SslProbe;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
=head1 Environment Variable Safety
|
||||
|
||||
Perl's handling of environment variables was completely messed up until v5.38.
|
||||
Using `setenv` such as use din the `openssl-probe` crate would cause it to
|
||||
crash later on, therefore we provide a perl-version of env var probing instead,
|
||||
and override the crate with one that doesn't replace the variables if they are
|
||||
already set correctly.
|
||||
|
||||
=cut
|
||||
|
||||
BEGIN {
|
||||
# Copied from openssl-probe
|
||||
my @cert_dirs = (
|
||||
"/var/ssl",
|
||||
"/usr/share/ssl",
|
||||
"/usr/local/ssl",
|
||||
"/usr/local/openssl",
|
||||
"/usr/local/etc/openssl",
|
||||
"/usr/local/share",
|
||||
"/usr/lib/ssl",
|
||||
"/usr/ssl",
|
||||
"/etc/openssl",
|
||||
"/etc/pki/ca-trust/extracted/pem",
|
||||
"/etc/pki/tls",
|
||||
"/etc/ssl",
|
||||
"/etc/certs",
|
||||
"/opt/etc/ssl",
|
||||
"/data/data/com.termux/files/usr/etc/tls",
|
||||
"/boot/system/data/ssl",
|
||||
);
|
||||
|
||||
# Copied from openssl-probe
|
||||
my @cert_file_names = (
|
||||
"cert.pem",
|
||||
"certs.pem",
|
||||
"ca-bundle.pem",
|
||||
"cacert.pem",
|
||||
"ca-certificates.crt",
|
||||
"certs/ca-certificates.crt",
|
||||
"certs/ca-root-nss.crt",
|
||||
"certs/ca-bundle.crt",
|
||||
"CARootCertificates.pem",
|
||||
"tls-ca-bundle.pem",
|
||||
);
|
||||
|
||||
my $probed_ssl_vars = 0;
|
||||
|
||||
# The algorithm here is taken from the `openssl-probe` crate and should
|
||||
# produce the exact same result in order to ensure the rust code does not
|
||||
# call `setenv()`.
|
||||
my sub probe_ssl_vars : prototype() {
|
||||
return if $probed_ssl_vars;
|
||||
$probed_ssl_vars = 1;
|
||||
|
||||
my $result_file = $ENV{SSL_CERT_FILE};
|
||||
my $result_file_changed = 0;
|
||||
my $result_dir = $ENV{SSL_CERT_DIR};
|
||||
my $result_dir_changed = 0;
|
||||
|
||||
for my $certs_dir (@cert_dirs) {
|
||||
if (!defined($result_file)) {
|
||||
for my $file (@cert_file_names) {
|
||||
my $path = "$certs_dir/$file";
|
||||
if (-e $path) {
|
||||
$result_file = $path;
|
||||
$result_file_changed = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!defined($result_dir)) {
|
||||
for my $file (@cert_file_names) {
|
||||
my $path = "$certs_dir/certs";
|
||||
if (-d $path) {
|
||||
$result_dir = $path;
|
||||
$result_dir_changed = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
last if defined($result_file) && defined($result_dir);
|
||||
}
|
||||
|
||||
if ($result_file_changed && defined($result_file)) {
|
||||
$ENV{SSL_CERT_FILE} = $result_file;
|
||||
}
|
||||
if ($result_dir_changed && defined($result_dir)) {
|
||||
$ENV{SSL_CERT_DIR} = $result_dir;
|
||||
}
|
||||
}
|
||||
|
||||
probe_ssl_vars();
|
||||
}
|
||||
|
||||
1;
|
@ -1,3 +1,27 @@
|
||||
libproxmox-rs-perl (0.3.3) bookworm; urgency=medium
|
||||
|
||||
* move ssl var probing to Proxmox::Lib::SslProbe
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 07 Dec 2023 09:57:33 +0100
|
||||
|
||||
libproxmox-rs-perl (0.3.2) bookworm; urgency=medium
|
||||
|
||||
* add Proxmox::Lib::Common::probe_ssl_vars() helper
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 05 Dec 2023 10:46:39 +0100
|
||||
|
||||
libproxmox-rs-perl (0.3.1) bookworm; urgency=medium
|
||||
|
||||
* add Proxmox::RS::Notify module
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 24 Jul 2023 14:02:17 +0200
|
||||
|
||||
libproxmox-rs-perl (0.3.0) bookworm; urgency=medium
|
||||
|
||||
* rebuild for Debian 12 Bookworm based release series
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 17 May 2023 15:48:41 +0200
|
||||
|
||||
libproxmox-rs-perl (0.2.1) bullseye; urgency=medium
|
||||
|
||||
* update to proxmox-subscription 0.3 / proxmox-http 0.7
|
||||
|
@ -1 +0,0 @@
|
||||
12
|
@ -1,10 +1,9 @@
|
||||
Source: libproxmox-rs-perl
|
||||
Section: perl
|
||||
Priority: optional
|
||||
Build-Depends:
|
||||
debhelper (>= 12),
|
||||
Build-Depends: debhelper-compat (= 13), perlmod-bin,
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.5.1
|
||||
Standards-Version: 4.6.2
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox-perl-rs.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox-perl-rs.git
|
||||
Homepage: https://www.proxmox.com
|
||||
@ -14,15 +13,12 @@ Package: libproxmox-rs-perl
|
||||
Architecture: any
|
||||
# always bump both versioned Depends and Breaks, otherwise systems with both
|
||||
# libpmg-rs-perl and libpve-rs-perl might load an outdated lib and break
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
${perl:Depends},
|
||||
${shlibs:Depends},
|
||||
libpve-rs-perl (>= 0.7.2) | libpmg-rs-perl (>= 0.6.2),
|
||||
Breaks:
|
||||
libpve-rs-perl (<< 0.7.2),
|
||||
libpmg-rs-perl (<< 0.6.2),
|
||||
Replaces: libpve-rs-perl (<< 0.6.0)
|
||||
Description: PVE/PMG common parts which have been ported to Rust - Perl packages
|
||||
Contains the perl side of modules provided by the libraries of both libpve-rs-perl and
|
||||
libpmg-rs-perl, loading whichever is available.
|
||||
Depends: libpve-rs-perl (>= 0.8.5) | libpmg-rs-perl (>= 0.6.2),
|
||||
${misc:Depends},
|
||||
${perl:Depends},
|
||||
${shlibs:Depends},
|
||||
Breaks: libpmg-rs-perl (<< 0.6.2), libpve-rs-perl (<< 0.7.2),
|
||||
Replaces: libpve-rs-perl (<< 0.6.0),
|
||||
Description: PVE/PMG common perl parts for Rust perlmod bindings
|
||||
Contains the perl side of modules provided by the libraries of both
|
||||
libpve-rs-perl and libpmg-rs-perl, loading whichever is available.
|
||||
|
14
common/src/logger.rs
Normal file
14
common/src/logger.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use env_logger::{Builder, Env};
|
||||
use std::io::Write;
|
||||
|
||||
/// Initialize logging. Should only be called once
|
||||
pub fn init(env_var_name: &str, default_log_level: &str) {
|
||||
if let Err(e) = Builder::from_env(Env::new().filter_or(env_var_name, default_log_level))
|
||||
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
|
||||
.write_style(env_logger::WriteStyle::Never)
|
||||
.format_timestamp(None)
|
||||
.try_init()
|
||||
{
|
||||
eprintln!("could not set up env_logger: {e}");
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
pub mod apt;
|
||||
mod calendar_event;
|
||||
pub mod logger;
|
||||
pub mod notify;
|
||||
mod subscription;
|
||||
|
508
common/src/notify.rs
Normal file
508
common/src/notify.rs
Normal file
@ -0,0 +1,508 @@
|
||||
#[perlmod::package(name = "Proxmox::RS::Notify")]
|
||||
mod export {
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use anyhow::{bail, Error};
|
||||
use serde_json::Value as JSONValue;
|
||||
|
||||
use perlmod::Value;
|
||||
use proxmox_http_error::HttpError;
|
||||
use proxmox_notify::endpoints::gotify::{
|
||||
DeleteableGotifyProperty, GotifyConfig, GotifyConfigUpdater, GotifyPrivateConfig,
|
||||
GotifyPrivateConfigUpdater,
|
||||
};
|
||||
use proxmox_notify::endpoints::sendmail::{
|
||||
DeleteableSendmailProperty, SendmailConfig, SendmailConfigUpdater,
|
||||
};
|
||||
use proxmox_notify::endpoints::smtp::{
|
||||
DeleteableSmtpProperty, SmtpConfig, SmtpConfigUpdater, SmtpMode, SmtpPrivateConfig,
|
||||
SmtpPrivateConfigUpdater,
|
||||
};
|
||||
use proxmox_notify::matcher::{
|
||||
CalendarMatcher, DeleteableMatcherProperty, FieldMatcher, MatchModeOperator, MatcherConfig,
|
||||
MatcherConfigUpdater, SeverityMatcher,
|
||||
};
|
||||
use proxmox_notify::{api, Config, Notification, Severity};
|
||||
|
||||
pub struct NotificationConfig {
|
||||
config: Mutex<Config>,
|
||||
}
|
||||
|
||||
perlmod::declare_magic!(Box<NotificationConfig> : &NotificationConfig as "Proxmox::RS::Notify");
|
||||
|
||||
/// Support `dclone` so this can be put into the `ccache` of `PVE::Cluster`.
|
||||
#[export(name = "STORABLE_freeze", raw_return)]
|
||||
fn storable_freeze(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
cloning: bool,
|
||||
) -> Result<Value, Error> {
|
||||
if !cloning {
|
||||
bail!("freezing Notification config not supported!");
|
||||
}
|
||||
|
||||
let mut cloned = Box::new(NotificationConfig {
|
||||
config: Mutex::new(this.config.lock().unwrap().clone()),
|
||||
});
|
||||
let value = Value::new_pointer::<NotificationConfig>(&mut *cloned);
|
||||
let _perl = Box::leak(cloned);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Instead of `thaw` we implement `attach` for `dclone`.
|
||||
#[export(name = "STORABLE_attach", raw_return)]
|
||||
fn storable_attach(
|
||||
#[raw] class: Value,
|
||||
cloning: bool,
|
||||
#[raw] serialized: Value,
|
||||
) -> Result<Value, Error> {
|
||||
if !cloning {
|
||||
bail!("STORABLE_attach called with cloning=false");
|
||||
}
|
||||
let data = unsafe { Box::from_raw(serialized.pv_raw::<NotificationConfig>()?) };
|
||||
Ok(perlmod::instantiate_magic!(&class, MAGIC => data))
|
||||
}
|
||||
|
||||
#[export(raw_return)]
|
||||
fn parse_config(
|
||||
#[raw] class: Value,
|
||||
raw_config: &[u8],
|
||||
raw_private_config: &[u8],
|
||||
) -> Result<Value, Error> {
|
||||
let raw_config = std::str::from_utf8(raw_config)?;
|
||||
let raw_private_config = std::str::from_utf8(raw_private_config)?;
|
||||
|
||||
Ok(perlmod::instantiate_magic!(&class, MAGIC => Box::new(
|
||||
NotificationConfig {
|
||||
config: Mutex::new(Config::new(raw_config, raw_private_config)?)
|
||||
}
|
||||
)))
|
||||
}
|
||||
|
||||
#[export]
|
||||
fn write_config(#[try_from_ref] this: &NotificationConfig) -> Result<(String, String), Error> {
|
||||
Ok(this.config.lock().unwrap().write()?)
|
||||
}
|
||||
|
||||
#[export]
|
||||
fn digest(#[try_from_ref] this: &NotificationConfig) -> String {
|
||||
let config = this.config.lock().unwrap();
|
||||
hex::encode(config.digest())
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn send(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
severity: Severity,
|
||||
title: String,
|
||||
body: String,
|
||||
template_data: Option<JSONValue>,
|
||||
fields: Option<HashMap<String, String>>,
|
||||
) -> Result<(), HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
let notification = Notification::new_templated(
|
||||
severity,
|
||||
title,
|
||||
body,
|
||||
template_data.unwrap_or_default(),
|
||||
fields.unwrap_or_default(),
|
||||
);
|
||||
|
||||
api::common::send(&config, ¬ification)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn test_target(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
target: &str,
|
||||
) -> Result<(), HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::common::test_target(&config, target)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn get_sendmail_endpoints(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
) -> Result<Vec<SendmailConfig>, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::sendmail::get_endpoints(&config)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn get_sendmail_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
id: &str,
|
||||
) -> Result<SendmailConfig, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::sendmail::get_endpoint(&config, id)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn add_sendmail_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: String,
|
||||
mailto: Option<Vec<String>>,
|
||||
mailto_user: Option<Vec<String>>,
|
||||
from_address: Option<String>,
|
||||
author: Option<String>,
|
||||
comment: Option<String>,
|
||||
disable: Option<bool>,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
|
||||
api::sendmail::add_endpoint(
|
||||
&mut config,
|
||||
&SendmailConfig {
|
||||
name,
|
||||
mailto,
|
||||
mailto_user,
|
||||
from_address,
|
||||
author,
|
||||
comment,
|
||||
disable,
|
||||
filter: None,
|
||||
origin: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update_sendmail_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
mailto: Option<Vec<String>>,
|
||||
mailto_user: Option<Vec<String>>,
|
||||
from_address: Option<String>,
|
||||
author: Option<String>,
|
||||
comment: Option<String>,
|
||||
disable: Option<bool>,
|
||||
delete: Option<Vec<DeleteableSendmailProperty>>,
|
||||
digest: Option<&str>,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
let digest = decode_digest(digest)?;
|
||||
|
||||
api::sendmail::update_endpoint(
|
||||
&mut config,
|
||||
name,
|
||||
&SendmailConfigUpdater {
|
||||
mailto,
|
||||
mailto_user,
|
||||
from_address,
|
||||
author,
|
||||
comment,
|
||||
disable,
|
||||
},
|
||||
delete.as_deref(),
|
||||
digest.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn delete_sendmail_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
api::sendmail::delete_endpoint(&mut config, name)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn get_gotify_endpoints(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
) -> Result<Vec<GotifyConfig>, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::gotify::get_endpoints(&config)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn get_gotify_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
id: &str,
|
||||
) -> Result<GotifyConfig, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::gotify::get_endpoint(&config, id)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn add_gotify_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: String,
|
||||
server: String,
|
||||
token: String,
|
||||
comment: Option<String>,
|
||||
disable: Option<bool>,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
api::gotify::add_endpoint(
|
||||
&mut config,
|
||||
&GotifyConfig {
|
||||
name: name.clone(),
|
||||
server,
|
||||
comment,
|
||||
disable,
|
||||
filter: None,
|
||||
origin: None,
|
||||
},
|
||||
&GotifyPrivateConfig { name, token },
|
||||
)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update_gotify_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
server: Option<String>,
|
||||
token: Option<String>,
|
||||
comment: Option<String>,
|
||||
disable: Option<bool>,
|
||||
delete: Option<Vec<DeleteableGotifyProperty>>,
|
||||
digest: Option<&str>,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
let digest = decode_digest(digest)?;
|
||||
|
||||
api::gotify::update_endpoint(
|
||||
&mut config,
|
||||
name,
|
||||
&GotifyConfigUpdater {
|
||||
server,
|
||||
comment,
|
||||
disable,
|
||||
},
|
||||
&GotifyPrivateConfigUpdater { token },
|
||||
delete.as_deref(),
|
||||
digest.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn delete_gotify_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
api::gotify::delete_gotify_endpoint(&mut config, name)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn get_smtp_endpoints(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
) -> Result<Vec<SmtpConfig>, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::smtp::get_endpoints(&config)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn get_smtp_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
id: &str,
|
||||
) -> Result<SmtpConfig, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::smtp::get_endpoint(&config, id)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn add_smtp_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: String,
|
||||
server: String,
|
||||
port: Option<u16>,
|
||||
mode: Option<SmtpMode>,
|
||||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
mailto: Option<Vec<String>>,
|
||||
mailto_user: Option<Vec<String>>,
|
||||
from_address: String,
|
||||
author: Option<String>,
|
||||
comment: Option<String>,
|
||||
disable: Option<bool>,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
api::smtp::add_endpoint(
|
||||
&mut config,
|
||||
&SmtpConfig {
|
||||
name: name.clone(),
|
||||
server,
|
||||
port,
|
||||
mode,
|
||||
username,
|
||||
mailto,
|
||||
mailto_user,
|
||||
from_address,
|
||||
author,
|
||||
comment,
|
||||
disable,
|
||||
origin: None,
|
||||
},
|
||||
&SmtpPrivateConfig { name, password },
|
||||
)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update_smtp_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
server: Option<String>,
|
||||
port: Option<u16>,
|
||||
mode: Option<SmtpMode>,
|
||||
username: Option<String>,
|
||||
password: Option<String>,
|
||||
mailto: Option<Vec<String>>,
|
||||
mailto_user: Option<Vec<String>>,
|
||||
from_address: Option<String>,
|
||||
author: Option<String>,
|
||||
comment: Option<String>,
|
||||
disable: Option<bool>,
|
||||
delete: Option<Vec<DeleteableSmtpProperty>>,
|
||||
digest: Option<&str>,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
let digest = decode_digest(digest)?;
|
||||
|
||||
api::smtp::update_endpoint(
|
||||
&mut config,
|
||||
name,
|
||||
&SmtpConfigUpdater {
|
||||
server,
|
||||
port,
|
||||
mode,
|
||||
username,
|
||||
mailto,
|
||||
mailto_user,
|
||||
from_address,
|
||||
author,
|
||||
comment,
|
||||
disable,
|
||||
},
|
||||
&SmtpPrivateConfigUpdater { password },
|
||||
delete.as_deref(),
|
||||
digest.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn delete_smtp_endpoint(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
api::smtp::delete_endpoint(&mut config, name)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn get_matchers(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
) -> Result<Vec<MatcherConfig>, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::matcher::get_matchers(&config)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn get_matcher(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
id: &str,
|
||||
) -> Result<MatcherConfig, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::matcher::get_matcher(&config, id)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn add_matcher(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: String,
|
||||
target: Option<Vec<String>>,
|
||||
match_severity: Option<Vec<SeverityMatcher>>,
|
||||
match_field: Option<Vec<FieldMatcher>>,
|
||||
match_calendar: Option<Vec<CalendarMatcher>>,
|
||||
mode: Option<MatchModeOperator>,
|
||||
invert_match: Option<bool>,
|
||||
comment: Option<String>,
|
||||
disable: Option<bool>,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
api::matcher::add_matcher(
|
||||
&mut config,
|
||||
&MatcherConfig {
|
||||
name,
|
||||
match_severity,
|
||||
match_field,
|
||||
match_calendar,
|
||||
target,
|
||||
mode,
|
||||
invert_match,
|
||||
comment,
|
||||
disable,
|
||||
origin: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update_matcher(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
target: Option<Vec<String>>,
|
||||
match_severity: Option<Vec<SeverityMatcher>>,
|
||||
match_field: Option<Vec<FieldMatcher>>,
|
||||
match_calendar: Option<Vec<CalendarMatcher>>,
|
||||
mode: Option<MatchModeOperator>,
|
||||
invert_match: Option<bool>,
|
||||
comment: Option<String>,
|
||||
disable: Option<bool>,
|
||||
delete: Option<Vec<DeleteableMatcherProperty>>,
|
||||
digest: Option<&str>,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
let digest = decode_digest(digest)?;
|
||||
|
||||
api::matcher::update_matcher(
|
||||
&mut config,
|
||||
name,
|
||||
&MatcherConfigUpdater {
|
||||
match_severity,
|
||||
match_field,
|
||||
match_calendar,
|
||||
target,
|
||||
mode,
|
||||
invert_match,
|
||||
comment,
|
||||
disable,
|
||||
},
|
||||
delete.as_deref(),
|
||||
digest.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
#[export(serialize_error)]
|
||||
fn delete_matcher(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
) -> Result<(), HttpError> {
|
||||
let mut config = this.config.lock().unwrap();
|
||||
api::matcher::delete_matcher(&mut config, name)
|
||||
}
|
||||
|
||||
#[export]
|
||||
fn get_referenced_entities(
|
||||
#[try_from_ref] this: &NotificationConfig,
|
||||
name: &str,
|
||||
) -> Result<Vec<String>, HttpError> {
|
||||
let config = this.config.lock().unwrap();
|
||||
api::common::get_referenced_entities(&config, name)
|
||||
}
|
||||
|
||||
fn decode_digest(digest: Option<&str>) -> Result<Option<Vec<u8>>, HttpError> {
|
||||
digest
|
||||
.map(hex::decode)
|
||||
.transpose()
|
||||
.map_err(|e| api::http_err!(BAD_REQUEST, "invalid digest: {e}"))
|
||||
}
|
||||
}
|
5
pmg-rs/.cargo/config
Normal file
5
pmg-rs/.cargo/config
Normal file
@ -0,0 +1,5 @@
|
||||
[source]
|
||||
[source.debian-packages]
|
||||
directory = "/usr/share/cargo/registry"
|
||||
[source.crates-io]
|
||||
replace-with = "debian-packages"
|
@ -1,14 +1,13 @@
|
||||
[package]
|
||||
name = "pmg-rs"
|
||||
version = "0.6.2"
|
||||
authors = [
|
||||
"Proxmox Support Team <support@proxmox.com>",
|
||||
"Wolfgang Bumiller <w.bumiller@proxmox.com>",
|
||||
"Fabian Ebner <f.ebner@proxmox.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
license = "AGPL-3"
|
||||
version = "0.7.5"
|
||||
description = "PMG parts which have been ported to rust"
|
||||
homepage = "https://www.proxmox.com"
|
||||
authors = ["Proxmox Support Team <support@proxmox.com>"]
|
||||
edition = "2021"
|
||||
license = "AGPL-3"
|
||||
repository = "https://git.proxmox.com/?p=proxmox.git"
|
||||
|
||||
exclude = [
|
||||
"build",
|
||||
"debian",
|
||||
@ -20,22 +19,25 @@ crate-type = [ "cdylib" ]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
env_logger = "0.10"
|
||||
hex = "0.4"
|
||||
http = "0.2.7"
|
||||
libc = "0.2"
|
||||
nix = "0.24"
|
||||
nix = "0.26"
|
||||
openssl = "0.10.40"
|
||||
serde = "1.0"
|
||||
serde_bytes = "0.11.3"
|
||||
serde_bytes = "0.11"
|
||||
serde_json = "1.0"
|
||||
url = "2"
|
||||
|
||||
perlmod = { version = "0.13", features = [ "exporter" ] }
|
||||
perlmod = { version = "0.13.4", features = [ "exporter" ] }
|
||||
|
||||
proxmox-acme-rs = { version = "0.4", features = ["client"] }
|
||||
proxmox-apt = "0.9"
|
||||
proxmox-http = { version = "0.7", features = ["client-sync", "client-trait"] }
|
||||
proxmox-subscription = "0.3"
|
||||
proxmox-sys = "0.4"
|
||||
proxmox-tfa = { version = "2.1", features = ["api"] }
|
||||
proxmox-acme = { version = "0.5", features = ["client"] }
|
||||
proxmox-apt = "0.10"
|
||||
proxmox-http = { version = "0.9", features = ["client-sync", "client-trait"] }
|
||||
proxmox-http-error = "0.1.0"
|
||||
proxmox-notify = "0.3.1"
|
||||
proxmox-subscription = "0.4"
|
||||
proxmox-sys = "0.5"
|
||||
proxmox-tfa = { version = "4.0.4", features = ["api"] }
|
||||
proxmox-time = "1.1.3"
|
||||
|
4
pmg-rs/Fixup.pm
Normal file
4
pmg-rs/Fixup.pm
Normal file
@ -0,0 +1,4 @@
|
||||
# BEGIN Fixup.pm
|
||||
# This is prepended to the current PMG.pm to force-include the temporary `openssl-probe` fixup.
|
||||
use Proxmox::Lib::SslProbe;
|
||||
# END Fixup.pm
|
@ -1,4 +1,4 @@
|
||||
#include /usr/share/dpkg/default.mk
|
||||
#include /usr/share/dpkg/pkg-info.mk
|
||||
|
||||
PACKAGE=libpmg-rs-perl
|
||||
|
||||
@ -8,33 +8,46 @@ export GITVERSION:=$(shell git rev-parse HEAD)
|
||||
PERL_INSTALLVENDORARCH != perl -MConfig -e 'print $$Config{installvendorarch};'
|
||||
PERL_INSTALLVENDORLIB != perl -MConfig -e 'print $$Config{installvendorlib};'
|
||||
|
||||
MAIN_DEB=${PACKAGE}_${DEB_VERSION}_${ARCH}.deb
|
||||
DBGSYM_DEB=${PACKAGE}-dbgsym_${DEB_VERSION}_${ARCH}.deb
|
||||
MAIN_DEB=$(PACKAGE)_$(DEB_VERSION)_$(ARCH).deb
|
||||
DBGSYM_DEB=$(PACKAGE)-dbgsym_$(DEB_VERSION)_$(ARCH).deb
|
||||
DEBS=$(MAIN_DEB) $(DBGSYM_DEB)
|
||||
DSC=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION).dsc
|
||||
BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM)
|
||||
|
||||
DESTDIR=
|
||||
|
||||
PM_DIR := PMG
|
||||
|
||||
PERLMOD_GENPACKAGE := /usr/lib/perlmod/genpackage.pl \
|
||||
--lib=pmg_rs \
|
||||
--lib-tag=proxmox \
|
||||
--lib-package=Proxmox::Lib::PMG \
|
||||
--lib-prefix=PMG \
|
||||
--include-file=Fixup.pm
|
||||
|
||||
PERLMOD_PACKAGES := \
|
||||
PMG::RS::APT::Repositories \
|
||||
PMG::RS::Acme \
|
||||
PMG::RS::CSR \
|
||||
PMG::RS::OpenId \
|
||||
PMG::RS::TFA
|
||||
|
||||
ifeq ($(BUILD_MODE), release)
|
||||
CARGO_BUILD_ARGS += --release --offline
|
||||
TARGET_DIR=release
|
||||
else
|
||||
TARGET_DIR=debug
|
||||
endif
|
||||
|
||||
all:
|
||||
ifneq ($(BUILD_MODE), skip)
|
||||
all: PMG
|
||||
cargo build $(CARGO_BUILD_ARGS)
|
||||
endif
|
||||
|
||||
# always re-create this dir
|
||||
# but also copy the local target/ and PMG/ dirs as a build-cache
|
||||
.PHONY: build
|
||||
build:
|
||||
rm -rf build
|
||||
cargo build --release --offline
|
||||
rsync -a debian Makefile Cargo.toml Cargo.lock src target PMG build/
|
||||
Proxmox PMG: Proxmox/Lib/PMG.pm
|
||||
Proxmox/Lib/PMG.pm: Fixup.pm
|
||||
$(PERLMOD_GENPACKAGE) $(PERLMOD_PACKAGES)
|
||||
|
||||
.PHONY: install
|
||||
install: target/release/libpmg_rs.so
|
||||
install: target/release/libpmg_rs.so Proxmox/Lib/PMG.pm PMG
|
||||
install -d -m755 $(DESTDIR)$(PERL_INSTALLVENDORARCH)/auto
|
||||
install -m644 target/release/libpmg_rs.so $(DESTDIR)$(PERL_INSTALLVENDORARCH)/auto/libpmg_rs.so
|
||||
install -d -m755 $(DESTDIR)$(PERL_INSTALLVENDORLIB)
|
||||
@ -42,25 +55,43 @@ install: target/release/libpmg_rs.so
|
||||
install -m644 Proxmox/Lib/PMG.pm $(DESTDIR)$(PERL_INSTALLVENDORLIB)/Proxmox/Lib/PMG.pm
|
||||
find $(PM_DIR) \! -type d -print -exec install -Dm644 '{}' $(DESTDIR)$(PERL_INSTALLVENDORLIB)'/{}' ';'
|
||||
|
||||
.PHONY: deb
|
||||
deb: $(MAIN_DEB)
|
||||
$(MAIN_DEB): build
|
||||
cd build; dpkg-buildpackage -b -us -uc --no-pre-clean
|
||||
lintian $(DEBS)
|
||||
|
||||
distclean: clean
|
||||
|
||||
clean:
|
||||
rm -rf PMG Proxmox
|
||||
cargo clean
|
||||
rm -rf *.deb *.dsc *.tar.gz *.buildinfo *.changes Cargo.lock build
|
||||
find . -name '*~' -exec rm {} ';'
|
||||
rm -f *.deb *.dsc *.tar.* *.build *.buildinfo *.changes Cargo.lock
|
||||
rm -rf $(PACKAGE)-[0-9]*/
|
||||
|
||||
.PHONY: dinstall
|
||||
dinstall: ${DEBS}
|
||||
dpkg -i ${DEBS}
|
||||
dinstall: $(DEBS)
|
||||
dpkg -i $(DEBS)
|
||||
|
||||
.PHONY: upload
|
||||
upload: ${DEBS}
|
||||
upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION)
|
||||
upload: $(DEBS)
|
||||
# check if working directory is clean
|
||||
git diff --exit-code --stat && git diff --exit-code --stat --staged
|
||||
tar cf - ${DEBS} | ssh -X repoman@repo.proxmox.com upload --product pmg --dist bullseye
|
||||
tar cf - $(DEBS) | ssh -X repoman@repo.proxmox.com upload --product pmg --dist $(DEB_DISTRIBUTION)
|
||||
|
||||
$(BUILDDIR): src debian common/src Cargo.toml Makefile .cargo/config
|
||||
rm -rf $(BUILDDIR) $(BUILDDIR).tmp
|
||||
mkdir $(BUILDDIR).tmp
|
||||
mkdir $(BUILDDIR).tmp/common
|
||||
cp -a -t $(BUILDDIR).tmp src debian Cargo.toml Makefile .cargo Fixup.pm
|
||||
cp -a -t $(BUILDDIR).tmp/common common/src
|
||||
mv $(BUILDDIR).tmp $(BUILDDIR)
|
||||
|
||||
.PHONY: deb
|
||||
deb: $(DEBS)
|
||||
$(DEBS): $(BUILDDIR)
|
||||
cd $(BUILDDIR); PATH="/usr/local/bin:/usr/bin" dpkg-buildpackage -b -us -uc
|
||||
lintian $(DEBS)
|
||||
|
||||
.PHONY: dsc
|
||||
dsc: $(DSC)
|
||||
$(DSC): $(BUILDDIR)
|
||||
cd $(BUILDDIR); PATH="/usr/local/bin:/usr/bin" dpkg-buildpackage -S -us -uc -d
|
||||
lintian $(DSC)
|
||||
|
||||
sbuild: $(DSC)
|
||||
sbuild $(DSC)
|
||||
|
1
pmg-rs/common
Symbolic link
1
pmg-rs/common
Symbolic link
@ -0,0 +1 @@
|
||||
../common
|
@ -1,3 +1,49 @@
|
||||
libpmg-rs-perl (0.7.5) bookworm; urgency=medium
|
||||
|
||||
* add EAB binding support to ACME
|
||||
|
||||
* make Proxmox::Lib::PMG pull in Proxmox::Lib::SslProbe to work around
|
||||
an issue where the openssl-probe crate calls setenv() and messes up perl's
|
||||
view of the environment
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 07 Dec 2023 09:57:43 +0100
|
||||
|
||||
libpmg-rs-perl (0.7.4) bookworm; urgency=medium
|
||||
|
||||
* update to env logger 0.10
|
||||
|
||||
* use declare_magic for ACME
|
||||
|
||||
* add Promox::Lib::PMG::use_safe_putenv
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 06 Dec 2023 11:22:56 +0100
|
||||
|
||||
libpmg-rs-perl (0.7.3) bookworm; urgency=medium
|
||||
|
||||
* reset failure counts when unlocking second factors
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 05 Jul 2023 13:35:23 +0200
|
||||
|
||||
libpmg-rs-perl (0.7.2) bookworm; urgency=medium
|
||||
|
||||
* set default log level to 'info'
|
||||
|
||||
* introduce PMG_LOG environment variable to override log level
|
||||
|
||||
* add tfa_lock_status query sub
|
||||
|
||||
* add api_unlock_tfa sub
|
||||
|
||||
* bump proxmox-tfa to 4.0.2
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 27 Jun 2023 16:01:23 +0200
|
||||
|
||||
libpmg-rs-perl (0.7.1) bookworm; urgency=medium
|
||||
|
||||
* rebuild for Debian 12 Bookworm based release series
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 18 May 2023 12:01:08 +0200
|
||||
|
||||
libpmg-rs-perl (0.6.2) bullseye; urgency=medium
|
||||
|
||||
* update to proxmox-subscription 0.3 / proxmox-http 0.7
|
||||
|
@ -1 +0,0 @@
|
||||
12
|
@ -1,43 +1,51 @@
|
||||
Source: libpmg-rs-perl
|
||||
Section: perl
|
||||
Priority: optional
|
||||
Build-Depends: cargo:native <!nocheck>,
|
||||
debhelper-compat (= 13),
|
||||
librust-openssl-probe-dev (= 0.1.5-1~bpo12+pve1),
|
||||
dh-cargo (>= 25),
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-env-logger-0.10+default-dev,
|
||||
librust-hex-0.4+default-dev,
|
||||
librust-http-0.2+default-dev (>= 0.2.7-~~),
|
||||
librust-libc-0.2+default-dev,
|
||||
librust-nix-0.26+default-dev,
|
||||
librust-openssl-0.10+default-dev (>= 0.10.40-~~),
|
||||
librust-perlmod-0.13+default-dev (>= 0.13.4-~~),
|
||||
librust-perlmod-0.13+exporter-dev (>= 0.13.4-~~),
|
||||
librust-proxmox-acme-0.5+client-dev,
|
||||
librust-proxmox-acme-0.5+default-dev,
|
||||
librust-proxmox-apt-0.10+default-dev,
|
||||
librust-proxmox-http-0.9+client-sync-dev,
|
||||
librust-proxmox-http-0.9+client-trait-dev,
|
||||
librust-proxmox-http-0.9+default-dev,
|
||||
librust-proxmox-http-error-0.1+default-dev,
|
||||
librust-proxmox-notify-0.3+default-dev (>= 0.3.1-~~),
|
||||
librust-proxmox-subscription-0.4+default-dev,
|
||||
librust-proxmox-sys-0.5+default-dev,
|
||||
librust-proxmox-tfa-4+api-dev (>= 4.0.4-~~),
|
||||
librust-proxmox-tfa-4+default-dev (>= 4.0.4-~~),
|
||||
librust-proxmox-time-1+default-dev (>= 1.1.3-~~),
|
||||
librust-serde-1+default-dev,
|
||||
librust-serde-bytes-0.11+default-dev,
|
||||
librust-serde-json-1+default-dev,
|
||||
librust-url-2+default-dev,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
perlmod-bin (>= 0.2.0-3),
|
||||
rustc:native <!nocheck>,
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Build-Depends:
|
||||
debhelper (>= 12),
|
||||
dh-cargo (>= 24),
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-hex-0.4+default-dev,
|
||||
librust-http-0.2+default-dev (>= 0.2.7-~~),
|
||||
librust-libc-0.2+default-dev,
|
||||
librust-nix-0.24+default-dev,
|
||||
librust-openssl-0.10+default-dev (>= 0.10.40-~~),
|
||||
librust-perlmod-0.13+default-dev,
|
||||
librust-perlmod-0.13+exporter-dev,
|
||||
librust-proxmox-acme-rs-0.4+client-dev,
|
||||
librust-proxmox-acme-rs-0.4+default-dev,
|
||||
librust-proxmox-apt-0.9+default-dev,
|
||||
librust-proxmox-http-0.7+client-sync-dev,
|
||||
librust-proxmox-http-0.7+client-trait-dev,
|
||||
librust-proxmox-http-0.7+default-dev,
|
||||
librust-proxmox-subscription-0.3+default-dev,
|
||||
librust-proxmox-sys-0.4+default-dev,
|
||||
librust-proxmox-tfa-2+api-dev (>= 2.1-~~),
|
||||
librust-proxmox-tfa-2+default-dev (>= 2.1-~~),
|
||||
librust-proxmox-time-1+default-dev (>= 1.1.3-~~),
|
||||
librust-serde-1+default-dev,
|
||||
librust-serde-bytes-0.11+default-dev (>= 0.11.3-~~),
|
||||
librust-serde-json-1+default-dev,
|
||||
librust-url-2+default-dev,
|
||||
Standards-Version: 4.3.0
|
||||
Standards-Version: 4.6.1
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox-perl-rs.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox-perl-rs.git
|
||||
Homepage: https://www.proxmox.com
|
||||
|
||||
Package: libpmg-rs-perl
|
||||
Architecture: any
|
||||
Depends: ${perl:Depends},
|
||||
Depends: ${misc:Depends},
|
||||
${perl:Depends},
|
||||
${shlibs:Depends},
|
||||
libproxmox-rs-perl (>= 0.3.3),
|
||||
Description: Components of Proxmox Mail Gateway which have been ported to Rust.
|
||||
Contains parts of Proxmox Mail Gateway which have been ported to, or newly
|
||||
implemented in the Rust programming language.
|
||||
|
@ -1,10 +1,31 @@
|
||||
# WARNING: this is *NOT* use as canonical source for d/control, but rather occasionally used via
|
||||
# an invocation like:
|
||||
# make clean
|
||||
# rm debian/control
|
||||
# debcargo package --config debian/debcargo.toml --changelog-ready --no-overlay-write-back --directory libpmg-rs-perl-0.7.1 pmg-rs 0.7.1
|
||||
# mv libpmg-rs-perl-0.7.1/debian/control debian/control
|
||||
# to semi.manually refresh the control file
|
||||
#
|
||||
# NOTE: debcargo thinks this is a source package, but it isn't! Drop provides, the dependencies of
|
||||
# the binary package on rust source packages, Multi-Arch same, and other things that do not make
|
||||
# sense for a combined perl + arch-dependent library package.
|
||||
|
||||
overlay = "."
|
||||
crate_src_path = ".."
|
||||
maintainer = "Proxmox Support Team <support@proxmox.com>"
|
||||
|
||||
[source]
|
||||
section = "perl"
|
||||
vcs_git = "git://git.proxmox.com/git/proxmox.git"
|
||||
vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
|
||||
vcs_git = "git://git.proxmox.com/git/proxmox-perl-rs.git"
|
||||
vcs_browser = "https://git.proxmox.com/?p=proxmox-perl-rs.git"
|
||||
build_depends = [
|
||||
"perlmod-bin",
|
||||
]
|
||||
|
||||
[packages.libpmg-rs-perl]
|
||||
[packages.bin]
|
||||
name = "libpmg-rs-perl"
|
||||
summary = "Components of Proxmox Mail Gateway which have been ported to Rust."
|
||||
description = """
|
||||
Contains parts of Proxmox Mail Gateway which have been ported to, or newly
|
||||
implemented in the Rust programming language.
|
||||
"""
|
||||
|
@ -1,7 +1,25 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
include /usr/share/dpkg/pkg-info.mk
|
||||
include /usr/share/rustc/architecture.mk
|
||||
|
||||
#export DH_VERBOSE=1
|
||||
export BUILD_MODE=release
|
||||
|
||||
CARGO=/usr/share/cargo/bin/cargo
|
||||
|
||||
export CFLAGS CXXFLAGS CPPFLAGS LDFLAGS
|
||||
export DEB_HOST_RUST_TYPE DEB_HOST_GNU_TYPE
|
||||
export CARGO_HOME = $(CURDIR)/debian/cargo_home
|
||||
|
||||
export DEB_CARGO_CRATE=pmg-rs_$(DEB_VERSION_UPSTREAM)
|
||||
export DEB_CARGO_PACKAGE=pmg-rs
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure:
|
||||
@perl -ne 'if (/^version\s*=\s*"(\d+(?:\.\d+)+)"/) { my $$v_cargo = $$1; my $$v_deb = "$(DEB_VERSION_UPSTREAM)"; \
|
||||
die "ERROR: d/changelog <-> Cargo.toml version mismatch: $$v_cargo != $$v_deb\n" if $$v_cargo ne $$v_deb; exit(0); }' Cargo.toml
|
||||
$(CARGO) prepare-debian $(CURDIR)/debian/cargo_registry --link-from-system
|
||||
dh_auto_configure
|
||||
|
@ -9,8 +9,8 @@ use std::os::unix::fs::OpenOptionsExt;
|
||||
use anyhow::{format_err, Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use proxmox_acme_rs::account::AccountData as AcmeAccountData;
|
||||
use proxmox_acme_rs::{Account, Client};
|
||||
use proxmox_acme::account::AccountData as AcmeAccountData;
|
||||
use proxmox_acme::{Account, Client};
|
||||
|
||||
/// Our on-disk format inherited from PVE's proxmox-acme code.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
@ -79,6 +79,7 @@ impl Inner {
|
||||
tos_agreed: bool,
|
||||
contact: Vec<String>,
|
||||
rsa_bits: Option<u32>,
|
||||
eab_creds: Option<(String, String)>,
|
||||
) -> Result<(), Error> {
|
||||
self.tos = if tos_agreed {
|
||||
self.client.terms_of_service_url()?.map(str::to_owned)
|
||||
@ -86,7 +87,9 @@ impl Inner {
|
||||
None
|
||||
};
|
||||
|
||||
let _account = self.client.new_account(contact, tos_agreed, rsa_bits)?;
|
||||
let _account = self
|
||||
.client
|
||||
.new_account(contact, tos_agreed, rsa_bits, eab_creds)?;
|
||||
let file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
@ -182,67 +185,45 @@ impl Inner {
|
||||
#[perlmod::package(name = "PMG::RS::Acme")]
|
||||
pub mod export {
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use anyhow::Error;
|
||||
use serde_bytes::{ByteBuf, Bytes};
|
||||
|
||||
use perlmod::Value;
|
||||
use proxmox_acme_rs::directory::Meta;
|
||||
use proxmox_acme_rs::order::OrderData;
|
||||
use proxmox_acme_rs::{Authorization, Challenge, Order};
|
||||
use proxmox_acme::directory::Meta;
|
||||
use proxmox_acme::order::OrderData;
|
||||
use proxmox_acme::{Authorization, Challenge, Order};
|
||||
|
||||
use super::{AccountData, Inner};
|
||||
|
||||
const CLASSNAME: &str = "PMG::RS::Acme";
|
||||
perlmod::declare_magic!(Box<Acme> : &Acme as "PMG::RS::Acme");
|
||||
|
||||
/// An Acme client instance.
|
||||
pub struct Acme {
|
||||
inner: Mutex<Inner>,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a Value> for &'a Acme {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &'a Value) -> Result<&'a Acme, Error> {
|
||||
Ok(unsafe { value.from_blessed_box(CLASSNAME)? })
|
||||
}
|
||||
}
|
||||
|
||||
fn bless(class: Value, mut ptr: Box<Acme>) -> Result<Value, Error> {
|
||||
let value = Value::new_pointer::<Acme>(&mut *ptr);
|
||||
let value = Value::new_ref(&value);
|
||||
let this = value.bless_sv(&class)?;
|
||||
let _perl = Box::leak(ptr);
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
/// Create a new ACME client instance given an account path and an API directory URL.
|
||||
#[export(raw_return)]
|
||||
pub fn new(#[raw] class: Value, api_directory: String) -> Result<Value, Error> {
|
||||
bless(
|
||||
class,
|
||||
Box::new(Acme {
|
||||
Ok(perlmod::instantiate_magic!(
|
||||
&class,
|
||||
MAGIC => Box::new(Acme {
|
||||
inner: Mutex::new(Inner::new(api_directory)?),
|
||||
}),
|
||||
)
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
/// Load an existing account.
|
||||
#[export(raw_return)]
|
||||
pub fn load(#[raw] class: Value, account_path: String) -> Result<Value, Error> {
|
||||
bless(
|
||||
class,
|
||||
Box::new(Acme {
|
||||
Ok(perlmod::instantiate_magic!(
|
||||
&class,
|
||||
MAGIC => Box::new(Acme {
|
||||
inner: Mutex::new(Inner::load(account_path)?),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[export(name = "DESTROY")]
|
||||
fn destroy(#[raw] this: Value) {
|
||||
perlmod::destructor!(this, Acme: CLASSNAME);
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new account.
|
||||
@ -260,11 +241,16 @@ pub mod export {
|
||||
tos_agreed: bool,
|
||||
contact: Vec<String>,
|
||||
rsa_bits: Option<u32>,
|
||||
eab_kid: Option<String>,
|
||||
eab_hmac_key: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
this.inner
|
||||
.lock()
|
||||
.unwrap()
|
||||
.new_account(account_path, tos_agreed, contact, rsa_bits)
|
||||
this.inner.lock().unwrap().new_account(
|
||||
account_path,
|
||||
tos_agreed,
|
||||
contact,
|
||||
rsa_bits,
|
||||
eab_kid.zip(eab_hmac_key),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the directory's meta information.
|
||||
|
@ -5,7 +5,7 @@ pub mod export {
|
||||
use anyhow::Error;
|
||||
use serde_bytes::ByteBuf;
|
||||
|
||||
use proxmox_acme_rs::util::Csr;
|
||||
use proxmox_acme::util::Csr;
|
||||
|
||||
/// Generates a CSR and its accompanying private key.
|
||||
///
|
||||
|
@ -1,7 +1,25 @@
|
||||
#[path = "../../common/src/mod.rs"]
|
||||
#[path = "../common/src/mod.rs"]
|
||||
pub mod common;
|
||||
|
||||
pub mod acme;
|
||||
pub mod apt;
|
||||
pub mod csr;
|
||||
pub mod tfa;
|
||||
|
||||
#[perlmod::package(name = "Proxmox::Lib::PMG", lib = "pmg_rs")]
|
||||
mod export {
|
||||
use crate::common;
|
||||
|
||||
#[export]
|
||||
pub fn init() {
|
||||
common::logger::init("PMG_LOG", "info");
|
||||
}
|
||||
|
||||
/// CLI tools should call this very early. This is a workaround causing environment variable
|
||||
/// manipulation to leak instead of crash. Required when calling into rust code that causes
|
||||
/// `setenv` calls, particularly code using the openssl crate.
|
||||
#[export]
|
||||
pub fn use_safe_putenv() {
|
||||
perlmod::ffi::use_safe_putenv(true);
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,13 @@ use nix::errno::Errno;
|
||||
use nix::sys::stat::Mode;
|
||||
|
||||
pub(self) use proxmox_tfa::api::{
|
||||
RecoveryState, TfaChallenge, TfaConfig, TfaResponse, U2fConfig, WebauthnConfig,
|
||||
RecoveryState, TfaChallenge, TfaConfig, TfaResponse, U2fConfig, UserChallengeAccess,
|
||||
WebauthnConfig,
|
||||
};
|
||||
|
||||
#[perlmod::package(name = "PMG::RS::TFA")]
|
||||
mod export {
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Mutex;
|
||||
|
||||
@ -31,7 +33,7 @@ mod export {
|
||||
use url::Url;
|
||||
|
||||
use perlmod::Value;
|
||||
use proxmox_tfa::api::methods;
|
||||
use proxmox_tfa::api::{methods, TfaResult};
|
||||
|
||||
use super::{TfaConfig, UserAccess};
|
||||
|
||||
@ -105,7 +107,7 @@ mod export {
|
||||
) -> Result<String, Error> {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
inner.u2f_registration_challenge(UserAccess::new(&raw_this)?, userid, description)
|
||||
inner.u2f_registration_challenge(&UserAccess::new(&raw_this)?, userid, description)
|
||||
}
|
||||
|
||||
/// Finish a u2f registration. This updates temporary data in `/run` and therefore the config
|
||||
@ -120,7 +122,7 @@ mod export {
|
||||
) -> Result<String, Error> {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
inner.u2f_registration_finish(UserAccess::new(&raw_this)?, userid, challenge, response)
|
||||
inner.u2f_registration_finish(&UserAccess::new(&raw_this)?, userid, challenge, response)
|
||||
}
|
||||
|
||||
/// Check if a user has any TFA entries of a given type.
|
||||
@ -203,7 +205,7 @@ mod export {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
match inner.authentication_challenge(
|
||||
UserAccess::new(&raw_this)?,
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
origin.as_ref(),
|
||||
)? {
|
||||
@ -220,10 +222,7 @@ mod export {
|
||||
.unwrap()
|
||||
.users
|
||||
.get(userid)
|
||||
.and_then(|user| {
|
||||
let state = user.recovery_state();
|
||||
state.is_available().then(move || state)
|
||||
})
|
||||
.and_then(|user| user.recovery_state())
|
||||
}
|
||||
|
||||
/// Takes the TFA challenge string (which is a json object) and verifies ther esponse against
|
||||
@ -244,15 +243,78 @@ mod export {
|
||||
let challenge: super::TfaChallenge = serde_json::from_str(challenge)?;
|
||||
let response: super::TfaResponse = response.parse()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
inner
|
||||
.verify(
|
||||
UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
&challenge,
|
||||
response,
|
||||
origin.as_ref(),
|
||||
)
|
||||
.map(|save| save.needs_saving())
|
||||
let result = inner.verify(
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
&challenge,
|
||||
response,
|
||||
origin.as_ref(),
|
||||
);
|
||||
match result {
|
||||
TfaResult::Success { needs_saving } => Ok(needs_saving),
|
||||
_ => bail!("TFA authentication failed"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes the TFA challenge string (which is a json object) and verifies ther esponse against
|
||||
/// it.
|
||||
///
|
||||
/// Returns a result hash of the form:
|
||||
/// ```text
|
||||
/// {
|
||||
/// "result": bool, // whether TFA was successful
|
||||
/// "needs-saving": bool, // whether the user config needs saving
|
||||
/// "tfa-limit-reached": bool, // whether the TFA limit was reached (config needs saving)
|
||||
/// "totp-limit-reached": bool, // whether the TOTP limit was reached (config needs saving)
|
||||
/// }
|
||||
/// ```
|
||||
#[export]
|
||||
fn authentication_verify2(
|
||||
#[raw] raw_this: Value,
|
||||
//#[try_from_ref] this: &Tfa,
|
||||
userid: &str,
|
||||
challenge: &str, //super::TfaChallenge,
|
||||
response: &str,
|
||||
origin: Option<Url>,
|
||||
) -> Result<TfaReturnValue, Error> {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
let challenge: super::TfaChallenge = serde_json::from_str(challenge)?;
|
||||
let response: super::TfaResponse = response.parse()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
let result = inner.verify(
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
&challenge,
|
||||
response,
|
||||
origin.as_ref(),
|
||||
);
|
||||
Ok(match result {
|
||||
TfaResult::Success { needs_saving } => TfaReturnValue {
|
||||
result: true,
|
||||
needs_saving,
|
||||
..Default::default()
|
||||
},
|
||||
TfaResult::Locked => TfaReturnValue::default(),
|
||||
TfaResult::Failure {
|
||||
needs_saving,
|
||||
totp_limit_reached,
|
||||
tfa_limit_reached,
|
||||
} => TfaReturnValue {
|
||||
result: false,
|
||||
needs_saving,
|
||||
totp_limit_reached,
|
||||
tfa_limit_reached,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct TfaReturnValue {
|
||||
result: bool,
|
||||
needs_saving: bool,
|
||||
totp_limit_reached: bool,
|
||||
tfa_limit_reached: bool,
|
||||
}
|
||||
|
||||
/// DEBUG HELPER: Get the current TOTP value for a given TOTP URI.
|
||||
@ -314,7 +376,7 @@ mod export {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
methods::add_tfa_entry(
|
||||
&mut this.inner.lock().unwrap(),
|
||||
UserAccess::new(&raw_this)?,
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
description,
|
||||
totp,
|
||||
@ -375,6 +437,66 @@ mod export {
|
||||
Err(methods::EntryNotFound) => bail!("no such entry"),
|
||||
}
|
||||
}
|
||||
|
||||
#[export]
|
||||
fn api_unlock_tfa(#[raw] raw_this: Value, userid: &str) -> Result<bool, Error> {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
Ok(methods::unlock_and_reset_tfa(
|
||||
&mut this.inner.lock().unwrap(),
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct TfaLockStatus {
|
||||
/// Once a user runs into a TOTP limit they get locked out of TOTP until they successfully use
|
||||
/// a recovery key.
|
||||
#[serde(skip_serializing_if = "bool_is_false", default)]
|
||||
totp_locked: bool,
|
||||
|
||||
/// If a user hits too many 2nd factor failures, they get completely blocked for a while.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
#[serde(deserialize_with = "filter_expired_timestamp")]
|
||||
tfa_locked_until: Option<i64>,
|
||||
}
|
||||
|
||||
impl From<&proxmox_tfa::api::TfaUserData> for TfaLockStatus {
|
||||
fn from(data: &proxmox_tfa::api::TfaUserData) -> Self {
|
||||
Self {
|
||||
totp_locked: data.totp_locked,
|
||||
tfa_locked_until: data.tfa_locked_until,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bool_is_false(b: &bool) -> bool {
|
||||
!*b
|
||||
}
|
||||
|
||||
#[export]
|
||||
fn tfa_lock_status(
|
||||
#[try_from_ref] this: &Tfa,
|
||||
userid: Option<&str>,
|
||||
) -> Result<Option<perlmod::Value>, Error> {
|
||||
let this = this.inner.lock().unwrap();
|
||||
if let Some(userid) = userid {
|
||||
if let Some(user) = this.users.get(userid) {
|
||||
Ok(Some(perlmod::to_value(&TfaLockStatus::from(user))?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(Some(perlmod::to_value(
|
||||
&HashMap::<String, TfaLockStatus>::from_iter(
|
||||
this.users
|
||||
.iter()
|
||||
.map(|(uid, data)| (uid.clone(), TfaLockStatus::from(data))),
|
||||
),
|
||||
)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach the path to errors from [`nix::mkir()`].
|
||||
@ -440,9 +562,7 @@ fn challenge_data_path(userid: &str, debug: bool) -> PathBuf {
|
||||
}
|
||||
|
||||
impl proxmox_tfa::api::OpenUserChallengeData for UserAccess {
|
||||
type Data = UserChallengeData;
|
||||
|
||||
fn open(&self, userid: &str) -> Result<UserChallengeData, Error> {
|
||||
fn open(&self, userid: &str) -> Result<Box<dyn UserChallengeAccess>, Error> {
|
||||
if self.is_debug() {
|
||||
mkdir("./local-tfa-challenges", 0o700)?;
|
||||
} else {
|
||||
@ -485,15 +605,15 @@ impl proxmox_tfa::api::OpenUserChallengeData for UserAccess {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(UserChallengeData {
|
||||
Ok(Box::new(UserChallengeData {
|
||||
inner,
|
||||
path,
|
||||
lock: file,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// `open` without creating the file if it doesn't exist, to finish WA authentications.
|
||||
fn open_no_create(&self, userid: &str) -> Result<Option<UserChallengeData>, Error> {
|
||||
fn open_no_create(&self, userid: &str) -> Result<Option<Box<dyn UserChallengeAccess>>, Error> {
|
||||
let path = challenge_data_path(userid, self.is_debug());
|
||||
|
||||
let mut file = match std::fs::OpenOptions::new()
|
||||
@ -514,11 +634,11 @@ impl proxmox_tfa::api::OpenUserChallengeData for UserAccess {
|
||||
format_err!("failed to read challenge data for user {}: {}", userid, err)
|
||||
})?;
|
||||
|
||||
Ok(Some(UserChallengeData {
|
||||
Ok(Some(Box::new(UserChallengeData {
|
||||
inner,
|
||||
path,
|
||||
lock: file,
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
fn remove(&self, userid: &str) -> Result<bool, Error> {
|
||||
@ -529,6 +649,10 @@ impl proxmox_tfa::api::OpenUserChallengeData for UserAccess {
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_lockout(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Container of `TfaUserChallenges` with the corresponding file lock guard.
|
||||
@ -546,7 +670,7 @@ impl proxmox_tfa::api::UserChallengeAccess for UserChallengeData {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
fn save(self) -> Result<(), Error> {
|
||||
fn save(&mut self) -> Result<(), Error> {
|
||||
UserChallengeData::save(self)
|
||||
}
|
||||
}
|
||||
@ -591,7 +715,7 @@ impl UserChallengeData {
|
||||
///
|
||||
/// This currently consumes selfe as we never perform more than 1 insertion/removal, and this
|
||||
/// way also unlocks early.
|
||||
fn save(mut self) -> Result<(), Error> {
|
||||
fn save(&mut self) -> Result<(), Error> {
|
||||
self.rewind()?;
|
||||
|
||||
serde_json::to_writer(&mut &self.lock, &self.inner).map_err(|err| {
|
||||
|
@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "pve-rs"
|
||||
version = "0.7.3"
|
||||
authors = ["Proxmox Support Team <support@proxmox.com>"]
|
||||
edition = "2018"
|
||||
license = "AGPL-3"
|
||||
version = "0.8.8"
|
||||
description = "PVE parts which have been ported to Rust"
|
||||
homepage = "https://www.proxmox.com"
|
||||
authors = ["Proxmox Support Team <support@proxmox.com>"]
|
||||
edition = "2021"
|
||||
license = "AGPL-3"
|
||||
repository = "https://git.proxmox.com/?p=proxmox.git"
|
||||
|
||||
exclude = [
|
||||
"debian",
|
||||
@ -18,10 +19,12 @@ crate-type = [ "cdylib" ]
|
||||
anyhow = "1.0"
|
||||
base32 = "0.4"
|
||||
base64 = "0.13"
|
||||
env_logger = "0.10"
|
||||
hex = "0.4"
|
||||
http = "0.2.7"
|
||||
libc = "0.2"
|
||||
nix = "0.24"
|
||||
log = "0.4.17"
|
||||
nix = "0.26"
|
||||
openssl = "0.10.40"
|
||||
serde = "1.0"
|
||||
serde_bytes = "0.11"
|
||||
@ -30,11 +33,13 @@ url = "2"
|
||||
|
||||
perlmod = { version = "0.13", features = [ "exporter" ] }
|
||||
|
||||
proxmox-apt = "0.9"
|
||||
proxmox-http = { version = "0.7", features = ["client-sync", "client-trait"] }
|
||||
proxmox-openid = "0.9.5"
|
||||
proxmox-resource-scheduling = "0.1"
|
||||
proxmox-subscription = "0.3"
|
||||
proxmox-sys = "0.4"
|
||||
proxmox-tfa = { version = "2.1", features = ["api"] }
|
||||
proxmox-apt = "0.10.6"
|
||||
proxmox-http = { version = "0.9", features = ["client-sync", "client-trait"] }
|
||||
proxmox-http-error = "0.1.0"
|
||||
proxmox-notify = { version = "0.3.1", features = ["pve-context"] }
|
||||
proxmox-openid = "0.10"
|
||||
proxmox-resource-scheduling = "0.3.0"
|
||||
proxmox-subscription = "0.4"
|
||||
proxmox-sys = "0.5"
|
||||
proxmox-tfa = { version = "4.0.4", features = ["api"] }
|
||||
proxmox-time = "1.1.3"
|
||||
|
4
pve-rs/Fixup.pm
Normal file
4
pve-rs/Fixup.pm
Normal file
@ -0,0 +1,4 @@
|
||||
# BEGIN Fixup.pm
|
||||
# This is prepended to the current PMG.pm to force-include the temporary `openssl-probe` fixup.
|
||||
use Proxmox::Lib::SslProbe;
|
||||
# END Fixup.pm
|
@ -1,4 +1,4 @@
|
||||
#include /usr/share/dpkg/default.mk
|
||||
#include /usr/share/dpkg/pkg-info.mk
|
||||
|
||||
PACKAGE=libpve-rs-perl
|
||||
export PERLMOD_PRODUCT=PVE
|
||||
@ -9,33 +9,52 @@ export GITVERSION:=$(shell git rev-parse HEAD)
|
||||
PERL_INSTALLVENDORARCH != perl -MConfig -e 'print $$Config{installvendorarch};'
|
||||
PERL_INSTALLVENDORLIB != perl -MConfig -e 'print $$Config{installvendorlib};'
|
||||
|
||||
MAIN_DEB=${PACKAGE}_${DEB_VERSION}_${ARCH}.deb
|
||||
DBGSYM_DEB=${PACKAGE}-dbgsym_${DEB_VERSION}_${ARCH}.deb
|
||||
MAIN_DEB=$(PACKAGE)_$(DEB_VERSION)_$(ARCH).deb
|
||||
DBGSYM_DEB=$(PACKAGE)-dbgsym_$(DEB_VERSION)_$(ARCH).deb
|
||||
DEBS=$(MAIN_DEB) $(DBGSYM_DEB)
|
||||
DSC=$(PACKAGE)_$(DEB_VERSION_UPSTREAM_REVISION).dsc
|
||||
BUILDDIR ?= $(PACKAGE)-$(DEB_VERSION_UPSTREAM)
|
||||
|
||||
DESTDIR=
|
||||
|
||||
PM_DIR := PVE
|
||||
|
||||
PERLMOD_GENPACKAGE := /usr/lib/perlmod/genpackage.pl \
|
||||
--lib=pve_rs \
|
||||
--lib-tag=proxmox \
|
||||
--lib-package=Proxmox::Lib::PVE \
|
||||
--lib-prefix=PVE \
|
||||
--include-file=Fixup.pm
|
||||
|
||||
PERLMOD_PACKAGES := \
|
||||
PVE::RS::APT::Repositories \
|
||||
PVE::RS::OpenId \
|
||||
PVE::RS::ResourceScheduling::Static \
|
||||
PVE::RS::TFA
|
||||
|
||||
ifeq ($(BUILD_MODE), release)
|
||||
CARGO_BUILD_ARGS += --release --offline
|
||||
TARGET_DIR=release
|
||||
else
|
||||
TARGET_DIR=debug
|
||||
endif
|
||||
|
||||
all:
|
||||
ifneq ($(BUILD_MODE), skip)
|
||||
all: PVE
|
||||
cargo build $(CARGO_BUILD_ARGS)
|
||||
endif
|
||||
mkdir -p test/Proxmox/Lib
|
||||
sed -r -e \
|
||||
's@^sub libfile.*$$@sub libfile { "$(shell pwd)/target/$(TARGET_DIR)/libpve_rs.so"; }@' \
|
||||
Proxmox/Lib/PVE.pm >test/Proxmox/Lib/PVE.pm
|
||||
|
||||
# always re-create this dir
|
||||
# but also copy the local target/ and PVE/ dirs as a build-cache
|
||||
.PHONY: build
|
||||
build:
|
||||
rm -rf build
|
||||
cargo build --release --offline
|
||||
rsync -a debian Makefile Cargo.toml Cargo.lock src target PVE build/
|
||||
Proxmox PVE: Proxmox/Lib/PVE.pm
|
||||
Proxmox/Lib/PVE.pm: Fixup.pm
|
||||
$(PERLMOD_GENPACKAGE) $(PERLMOD_PACKAGES)
|
||||
|
||||
check: all
|
||||
$(MAKE) -C test test
|
||||
|
||||
.PHONY: install
|
||||
install: target/release/libpve_rs.so
|
||||
install: target/release/libpve_rs.so Proxmox/Lib/PVE.pm PVE
|
||||
install -d -m755 $(DESTDIR)$(PERL_INSTALLVENDORARCH)/auto
|
||||
install -m644 target/release/libpve_rs.so $(DESTDIR)$(PERL_INSTALLVENDORARCH)/auto/libpve_rs.so
|
||||
install -d -m755 $(DESTDIR)$(PERL_INSTALLVENDORLIB)
|
||||
@ -43,25 +62,42 @@ install: target/release/libpve_rs.so
|
||||
install -m644 Proxmox/Lib/PVE.pm $(DESTDIR)$(PERL_INSTALLVENDORLIB)/Proxmox/Lib/PVE.pm
|
||||
find $(PM_DIR) \! -type d -print -exec install -Dm644 '{}' $(DESTDIR)$(PERL_INSTALLVENDORLIB)'/{}' ';'
|
||||
|
||||
.PHONY: deb
|
||||
deb: $(MAIN_DEB)
|
||||
$(MAIN_DEB): build
|
||||
cd build; dpkg-buildpackage -b -us -uc --no-pre-clean
|
||||
lintian $(DEBS)
|
||||
|
||||
distclean: clean
|
||||
|
||||
clean:
|
||||
rm -rf PVE Proxmox
|
||||
cargo clean
|
||||
rm -rf *.deb *.dsc *.tar.gz *.buildinfo *.changes Cargo.lock build
|
||||
find . -name '*~' -exec rm {} ';'
|
||||
rm -f *.deb *.dsc *.tar.* *.build *.buildinfo *.changes Cargo.lock
|
||||
rm -rf $(PACKAGE)-[0-9]*/
|
||||
|
||||
.PHONY: dinstall
|
||||
dinstall: ${DEBS}
|
||||
dpkg -i ${DEBS}
|
||||
dinstall: $(DEBS)
|
||||
dpkg -i $(DEBS)
|
||||
|
||||
.PHONY: upload
|
||||
upload: ${DEBS}
|
||||
upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION)
|
||||
upload: $(DEBS)
|
||||
# check if working directory is clean
|
||||
git diff --exit-code --stat && git diff --exit-code --stat --staged
|
||||
tar cf - ${DEBS} | ssh -X repoman@repo.proxmox.com upload --product pve --dist bullseye
|
||||
tar cf - $(DEBS) | ssh -X repoman@repo.proxmox.com upload --product pve --dist $(DEB_DISTRIBUTION)
|
||||
|
||||
$(BUILDDIR): src debian test common/src Cargo.toml Makefile .cargo/config
|
||||
rm -rf $(BUILDDIR) $(BUILDDIR).tmp
|
||||
mkdir $(BUILDDIR).tmp
|
||||
mkdir $(BUILDDIR).tmp/common
|
||||
cp -a -t $(BUILDDIR).tmp src debian test Cargo.toml Makefile .cargo Fixup.pm
|
||||
cp -a -t $(BUILDDIR).tmp/common common/src
|
||||
mv $(BUILDDIR).tmp $(BUILDDIR)
|
||||
|
||||
.PHONY: deb
|
||||
deb: $(DEBS)
|
||||
$(DEBS): $(BUILDDIR)
|
||||
cd $(BUILDDIR); PATH="/usr/local/bin:/usr/bin" dpkg-buildpackage -b -us -uc
|
||||
lintian $(DEBS)
|
||||
|
||||
.PHONY: dsc
|
||||
dsc: $(DSC)
|
||||
$(DSC): $(BUILDDIR)
|
||||
cd $(BUILDDIR); PATH="/usr/local/bin:/usr/bin" dpkg-buildpackage -S -us -uc -d
|
||||
lintian $(DSC)
|
||||
|
||||
sbuild: $(DSC)
|
||||
sbuild $(DSC)
|
||||
|
1
pve-rs/common
Symbolic link
1
pve-rs/common
Symbolic link
@ -0,0 +1 @@
|
||||
../common
|
@ -1,3 +1,114 @@
|
||||
libpve-rs-perl (0.8.8) bookworm; urgency=medium
|
||||
|
||||
* notify: include 'hostname' and 'type' metadata fields for forwarded mails
|
||||
|
||||
* notify: smtp: forward original message instead of nesting
|
||||
|
||||
* notify: smtp: add 'Auto-Submitted' header to email body
|
||||
|
||||
* notify: api: allow resetting built-in targets if used by a matcher
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 10 Jan 2024 14:19:47 +0100
|
||||
|
||||
libpve-rs-perl (0.8.7) bookworm; urgency=medium
|
||||
|
||||
* notify: adapt to new matcher-based notification routing
|
||||
|
||||
* notify: add bindings for smtp API calls
|
||||
|
||||
* pve-rs: notify: remove notify_context for PVE
|
||||
|
||||
* notify: add 'disable' parameter
|
||||
|
||||
* notify: support 'origin' paramter
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 17 Nov 2023 13:41:17 +0100
|
||||
|
||||
libpve-rs-perl (0.8.6) bookworm; urgency=medium
|
||||
|
||||
* re-build with newer proxmox-apt depenceny to make Ceph Reef repo available
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 05 Sep 2023 15:37:44 +0200
|
||||
|
||||
libpve-rs-perl (0.8.5) bookworm; urgency=medium
|
||||
|
||||
* add PVE::RS::Notify module
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 24 Jul 2023 11:18:56 +0200
|
||||
|
||||
libpve-rs-perl (0.8.4) bookworm; urgency=medium
|
||||
|
||||
* reset failure counts when unlocking second factors
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 05 Jul 2023 13:30:17 +0200
|
||||
|
||||
libpve-rs-perl (0.8.3) bookworm; urgency=medium
|
||||
|
||||
* set default log level to 'info'
|
||||
|
||||
* introduce PVE_LOG environment variable to override log level
|
||||
|
||||
* add tfa_lock_status query sub
|
||||
|
||||
* bump proxmox-tfa to 4.0.2
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Mon, 05 Jun 2023 12:55:03 +0200
|
||||
|
||||
libpve-rs-perl (0.8.2) bookworm; urgency=medium
|
||||
|
||||
* update proxmox-apt which updated repositories info for bookworm
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Sun, 04 Jun 2023 18:33:42 +0200
|
||||
|
||||
libpve-rs-perl (0.8.1) bookworm; urgency=medium
|
||||
|
||||
* bump proxmox-apt,http,openid,subscription,sys crates to their bookworm
|
||||
versions
|
||||
|
||||
* bump proxmox-tfa to 4.0.1 to include the unlock API
|
||||
|
||||
* enable TFA lockout and provide the `api_unlock_tfa` call
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 31 May 2023 14:17:31 +0200
|
||||
|
||||
libpve-rs-perl (0.8.0) bookworm; urgency=medium
|
||||
|
||||
* rebuild for Debian 12 Bookworm based release series
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 16 May 2023 14:26:52 +0200
|
||||
|
||||
libpve-rs-perl (0.7.6) bullseye; urgency=medium
|
||||
|
||||
* update to new tfa crate
|
||||
|
||||
* introduce new authentication_verify2 call to utilize the totp/tfa locking
|
||||
capabilities of the TFA API
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 10 May 2023 10:54:10 +0200
|
||||
|
||||
libpve-rs-perl (0.7.5) bullseye; urgency=medium
|
||||
|
||||
* update proxmox-resource-scheduling dependency to 0.2.1 to pull in an
|
||||
improvement for with services where CPU should matter more if there is no
|
||||
memory load at all
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 21 Mar 2023 17:58:22 +0100
|
||||
|
||||
libpve-rs-perl (0.7.4) bullseye; urgency=medium
|
||||
|
||||
* initialize logging when shared library is loaded
|
||||
|
||||
* update to new TFA crate
|
||||
|
||||
* bump proxmox-resource-scheduling dependency to 0.2 to pull in a fix for
|
||||
usage calculation for homogeneous nodes
|
||||
|
||||
* pve: test: resource scheduling: add test with overcommitted node
|
||||
|
||||
* update nix to 0.26
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 21 Mar 2023 15:28:08 +0100
|
||||
|
||||
libpve-rs-perl (0.7.3) bullseye; urgency=medium
|
||||
|
||||
* add PVE::RS::ResourceScheduling::Static and tests
|
||||
|
@ -1 +0,0 @@
|
||||
10
|
@ -1,39 +1,44 @@
|
||||
Source: libpve-rs-perl
|
||||
Section: perl
|
||||
Priority: optional
|
||||
Build-Depends:
|
||||
debhelper (>= 12),
|
||||
dh-cargo (>= 24),
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-base32-0.4+default-dev,
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-hex-0.4+default-dev,
|
||||
librust-http-0.2+default-dev (>= 0.2.7-~~),
|
||||
librust-libc-0.2+default-dev,
|
||||
librust-nix-0.24+default-dev,
|
||||
librust-openssl-0.10+default-dev (>= 0.10.40-~~),
|
||||
librust-perlmod-0.13+default-dev,
|
||||
librust-perlmod-0.13+exporter-dev,
|
||||
librust-proxmox-apt-0.9+default-dev,
|
||||
librust-proxmox-http-0.7+client-sync-dev,
|
||||
librust-proxmox-http-0.7+client-trait-dev,
|
||||
librust-proxmox-http-0.7+default-dev,
|
||||
librust-proxmox-openid-0.9+default-dev (>= 0.9.5-~~),
|
||||
librust-proxmox-resource-scheduling-0.1+default-dev,
|
||||
librust-proxmox-subscription-0.3+default-dev,
|
||||
librust-proxmox-sys-0.4+default-dev,
|
||||
librust-proxmox-tfa-2+api-dev (>= 2.1-~~),
|
||||
librust-proxmox-tfa-2+default-dev (>= 2.1-~~),
|
||||
librust-proxmox-time-1+default-dev (>= 1.1.3-~~),
|
||||
librust-serde-1+default-dev,
|
||||
librust-serde-bytes-0.11+default-dev,
|
||||
librust-serde-json-1+default-dev,
|
||||
librust-url-2+default-dev,
|
||||
Build-Depends: cargo:native <!nocheck>,
|
||||
debhelper-compat (= 13),
|
||||
dh-cargo (>= 25),
|
||||
librust-anyhow-1+default-dev,
|
||||
librust-base32-0.4+default-dev,
|
||||
librust-base64-0.13+default-dev,
|
||||
librust-env-logger-0.10+default-dev,
|
||||
librust-hex-0.4+default-dev,
|
||||
librust-http-0.2+default-dev (>= 0.2.7-~~),
|
||||
librust-libc-0.2+default-dev,
|
||||
librust-log-0.4+default-dev (>= 0.4.17-~~),
|
||||
librust-nix-0.26+default-dev,
|
||||
librust-openssl-0.10+default-dev (>= 0.10.40-~~),
|
||||
librust-perlmod-0.13+default-dev,
|
||||
librust-perlmod-0.13+exporter-dev,
|
||||
librust-proxmox-apt-0.10+default-dev (>= 0.10.6-~~),
|
||||
librust-proxmox-http-0.9+client-sync-dev,
|
||||
librust-proxmox-http-0.9+client-trait-dev,
|
||||
librust-proxmox-http-0.9+default-dev,
|
||||
librust-proxmox-http-error-0.1+default-dev,
|
||||
librust-proxmox-notify-0.3+default-dev (>= 0.3.1-~~),
|
||||
librust-proxmox-notify-0.3+pve-context-dev (>= 0.3.1-~~),
|
||||
librust-proxmox-openid-0.10+default-dev,
|
||||
librust-proxmox-resource-scheduling-0.3+default-dev,
|
||||
librust-proxmox-subscription-0.4+default-dev,
|
||||
librust-proxmox-sys-0.5+default-dev,
|
||||
librust-proxmox-tfa-4+api-dev (>= 4.0.4-~~),
|
||||
librust-proxmox-tfa-4+default-dev (>= 4.0.4-~~),
|
||||
librust-proxmox-time-1+default-dev (>= 1.1.3-~~),
|
||||
librust-serde-1+default-dev,
|
||||
librust-serde-bytes-0.11+default-dev,
|
||||
librust-serde-json-1+default-dev,
|
||||
librust-url-2+default-dev,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
perlmod-bin (>= 0.2.0-3),
|
||||
rustc:native <!nocheck>,
|
||||
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||
Standards-Version: 4.5.1
|
||||
Standards-Version: 4.6.1
|
||||
Vcs-Git: git://git.proxmox.com/git/proxmox-perl-rs.git
|
||||
Vcs-Browser: https://git.proxmox.com/?p=proxmox-perl-rs.git
|
||||
Homepage: https://www.proxmox.com
|
||||
@ -41,14 +46,13 @@ Rules-Requires-Root: no
|
||||
|
||||
Package: libpve-rs-perl
|
||||
Architecture: any
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
${perl:Depends},
|
||||
${shlibs:Depends},
|
||||
Breaks:
|
||||
libpve-access-control (<< 7.1-3),
|
||||
libpve-common-perl (<< 7.1-4),
|
||||
pve-manager (<< 7.1-11),
|
||||
Depends: ${misc:Depends},
|
||||
${perl:Depends},
|
||||
${shlibs:Depends},
|
||||
libproxmox-rs-perl (>= 0.3.3),
|
||||
Breaks: libpve-access-control (<< 7.1-3),
|
||||
libpve-common-perl (<< 7.1-4),
|
||||
pve-manager (<< 7.1-11),
|
||||
Description: PVE parts which have been ported to Rust - Rust source code
|
||||
This package contains the source for the Rust pve-rs crate, packaged by
|
||||
debcargo for use with cargo and dh-cargo.
|
||||
|
@ -1,7 +1,25 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
include /usr/share/dpkg/pkg-info.mk
|
||||
include /usr/share/rustc/architecture.mk
|
||||
|
||||
#export DH_VERBOSE=1
|
||||
export BUILD_MODE=release
|
||||
|
||||
CARGO=/usr/share/cargo/bin/cargo
|
||||
|
||||
export CFLAGS CXXFLAGS CPPFLAGS LDFLAGS
|
||||
export DEB_HOST_RUST_TYPE DEB_HOST_GNU_TYPE
|
||||
export CARGO_HOME = $(CURDIR)/debian/cargo_home
|
||||
|
||||
export DEB_CARGO_CRATE=pve-rs_$(DEB_VERSION_UPSTREAM)
|
||||
export DEB_CARGO_PACKAGE=pve-rs
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure:
|
||||
@perl -ne 'if (/^version\s*=\s*"(\d+(?:\.\d+)+)"/) { my $$v_cargo = $$1; my $$v_deb = "$(DEB_VERSION_UPSTREAM)"; \
|
||||
die "ERROR: d/changelog <-> Cargo.toml version mismatch: $$v_cargo != $$v_deb\n" if $$v_cargo ne $$v_deb; exit(0); }' Cargo.toml
|
||||
$(CARGO) prepare-debian $(CURDIR)/debian/cargo_registry --link-from-system
|
||||
dh_auto_configure
|
||||
|
@ -1,9 +1,22 @@
|
||||
//! Rust library for the Proxmox VE code base.
|
||||
|
||||
#[path = "../../common/src/mod.rs"]
|
||||
#[path = "../common/src/mod.rs"]
|
||||
pub mod common;
|
||||
|
||||
//pub mod apt;
|
||||
pub mod openid;
|
||||
pub mod resource_scheduling;
|
||||
pub mod tfa;
|
||||
|
||||
#[perlmod::package(name = "Proxmox::Lib::PVE", lib = "pve_rs")]
|
||||
mod export {
|
||||
use proxmox_notify::context::pve::PVE_CONTEXT;
|
||||
|
||||
use crate::common;
|
||||
|
||||
#[export]
|
||||
pub fn init() {
|
||||
common::logger::init("PVE_LOG", "info");
|
||||
proxmox_notify::context::set_context(&PVE_CONTEXT)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#[perlmod::package(name = "PVE::RS::OpenId", lib = "pve_rs")]
|
||||
mod export {
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use anyhow::Error;
|
||||
@ -9,34 +8,13 @@ mod export {
|
||||
|
||||
use proxmox_openid::{OpenIdAuthenticator, OpenIdConfig, PrivateAuthState};
|
||||
|
||||
const CLASSNAME: &str = "PVE::RS::OpenId";
|
||||
perlmod::declare_magic!(Box<OpenId> : &OpenId as "PVE::RS::OpenId");
|
||||
|
||||
/// An OpenIdAuthenticator client instance.
|
||||
pub struct OpenId {
|
||||
inner: Mutex<OpenIdAuthenticator>,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a Value> for &'a OpenId {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &'a Value) -> Result<&'a OpenId, Error> {
|
||||
Ok(unsafe { value.from_blessed_box(CLASSNAME)? })
|
||||
}
|
||||
}
|
||||
|
||||
fn bless(class: Value, mut ptr: Box<OpenId>) -> Result<Value, Error> {
|
||||
let value = Value::new_pointer::<OpenId>(&mut *ptr);
|
||||
let value = Value::new_ref(&value);
|
||||
let this = value.bless_sv(&class)?;
|
||||
let _perl = Box::leak(ptr);
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
#[export(name = "DESTROY")]
|
||||
fn destroy(#[raw] this: Value) {
|
||||
perlmod::destructor!(this, OpenId: CLASSNAME);
|
||||
}
|
||||
|
||||
/// Create a new OpenId client instance
|
||||
#[export(raw_return)]
|
||||
pub fn discover(
|
||||
@ -45,12 +23,12 @@ mod export {
|
||||
redirect_url: &str,
|
||||
) -> Result<Value, Error> {
|
||||
let open_id = OpenIdAuthenticator::discover(&config, redirect_url)?;
|
||||
bless(
|
||||
class,
|
||||
Box::new(OpenId {
|
||||
Ok(perlmod::instantiate_magic!(
|
||||
&class,
|
||||
MAGIC => Box::new(OpenId {
|
||||
inner: Mutex::new(open_id),
|
||||
}),
|
||||
)
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
#[export]
|
||||
|
@ -21,11 +21,13 @@ use nix::sys::stat::Mode;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
pub(self) use proxmox_tfa::api::{
|
||||
RecoveryState, TfaChallenge, TfaConfig, TfaResponse, TfaUserData, U2fConfig, WebauthnConfig,
|
||||
RecoveryState, TfaChallenge, TfaConfig, TfaResponse, TfaUserData, U2fConfig,
|
||||
UserChallengeAccess, WebauthnConfig,
|
||||
};
|
||||
|
||||
#[perlmod::package(name = "PVE::RS::TFA")]
|
||||
mod export {
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Mutex;
|
||||
|
||||
@ -34,7 +36,7 @@ mod export {
|
||||
use url::Url;
|
||||
|
||||
use perlmod::Value;
|
||||
use proxmox_tfa::api::methods;
|
||||
use proxmox_tfa::api::{methods, TfaResult};
|
||||
|
||||
use super::{TfaConfig, UserAccess};
|
||||
|
||||
@ -173,7 +175,7 @@ mod export {
|
||||
) -> Result<String, Error> {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
inner.u2f_registration_challenge(UserAccess::new(&raw_this)?, userid, description)
|
||||
inner.u2f_registration_challenge(&UserAccess::new(&raw_this)?, userid, description)
|
||||
}
|
||||
|
||||
/// Finish a u2f registration. This updates temporary data in `/run` and therefore the config
|
||||
@ -188,7 +190,7 @@ mod export {
|
||||
) -> Result<String, Error> {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
inner.u2f_registration_finish(UserAccess::new(&raw_this)?, userid, challenge, response)
|
||||
inner.u2f_registration_finish(&UserAccess::new(&raw_this)?, userid, challenge, response)
|
||||
}
|
||||
|
||||
/// Check if a user has any TFA entries of a given type.
|
||||
@ -249,7 +251,7 @@ mod export {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
match inner.authentication_challenge(
|
||||
UserAccess::new(&raw_this)?,
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
origin.as_ref(),
|
||||
)? {
|
||||
@ -266,10 +268,7 @@ mod export {
|
||||
.unwrap()
|
||||
.users
|
||||
.get(userid)
|
||||
.and_then(|user| {
|
||||
let state = user.recovery_state();
|
||||
state.is_available().then(move || state)
|
||||
})
|
||||
.and_then(|user| user.recovery_state())
|
||||
}
|
||||
|
||||
/// Takes the TFA challenge string (which is a json object) and verifies ther esponse against
|
||||
@ -277,6 +276,8 @@ mod export {
|
||||
///
|
||||
/// NOTE: This returns a boolean whether the config data needs to be *saved* after this call
|
||||
/// (to use up recovery keys!).
|
||||
///
|
||||
/// WARNING: This method is now deprecated, as it failures were communicated via croaking.
|
||||
#[export]
|
||||
fn authentication_verify(
|
||||
#[raw] raw_this: Value,
|
||||
@ -290,15 +291,78 @@ mod export {
|
||||
let challenge: super::TfaChallenge = serde_json::from_str(challenge)?;
|
||||
let response: super::TfaResponse = response.parse()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
inner
|
||||
.verify(
|
||||
UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
&challenge,
|
||||
response,
|
||||
origin.as_ref(),
|
||||
)
|
||||
.map(|save| save.needs_saving())
|
||||
let result = inner.verify(
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
&challenge,
|
||||
response,
|
||||
origin.as_ref(),
|
||||
);
|
||||
match result {
|
||||
TfaResult::Success { needs_saving } => Ok(needs_saving),
|
||||
_ => bail!("TFA authentication failed"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes the TFA challenge string (which is a json object) and verifies ther esponse against
|
||||
/// it.
|
||||
///
|
||||
/// Returns a result hash of the form:
|
||||
/// ```text
|
||||
/// {
|
||||
/// "result": bool, // whether TFA was successful
|
||||
/// "needs-saving": bool, // whether the user config needs saving
|
||||
/// "tfa-limit-reached": bool, // whether the TFA limit was reached (config needs saving)
|
||||
/// "totp-limit-reached": bool, // whether the TOTP limit was reached (config needs saving)
|
||||
/// }
|
||||
/// ```
|
||||
#[export]
|
||||
fn authentication_verify2(
|
||||
#[raw] raw_this: Value,
|
||||
//#[try_from_ref] this: &Tfa,
|
||||
userid: &str,
|
||||
challenge: &str, //super::TfaChallenge,
|
||||
response: &str,
|
||||
origin: Option<Url>,
|
||||
) -> Result<TfaReturnValue, Error> {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
let challenge: super::TfaChallenge = serde_json::from_str(challenge)?;
|
||||
let response: super::TfaResponse = response.parse()?;
|
||||
let mut inner = this.inner.lock().unwrap();
|
||||
let result = inner.verify(
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
&challenge,
|
||||
response,
|
||||
origin.as_ref(),
|
||||
);
|
||||
Ok(match result {
|
||||
TfaResult::Success { needs_saving } => TfaReturnValue {
|
||||
result: true,
|
||||
needs_saving,
|
||||
..Default::default()
|
||||
},
|
||||
TfaResult::Locked => TfaReturnValue::default(),
|
||||
TfaResult::Failure {
|
||||
needs_saving,
|
||||
totp_limit_reached,
|
||||
tfa_limit_reached,
|
||||
} => TfaReturnValue {
|
||||
result: false,
|
||||
needs_saving,
|
||||
totp_limit_reached,
|
||||
tfa_limit_reached,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Default, serde::Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct TfaReturnValue {
|
||||
result: bool,
|
||||
needs_saving: bool,
|
||||
totp_limit_reached: bool,
|
||||
tfa_limit_reached: bool,
|
||||
}
|
||||
|
||||
/// DEBUG HELPER: Get the current TOTP value for a given TOTP URI.
|
||||
@ -360,7 +424,7 @@ mod export {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
methods::add_tfa_entry(
|
||||
&mut this.inner.lock().unwrap(),
|
||||
UserAccess::new(&raw_this)?,
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
description,
|
||||
totp,
|
||||
@ -421,6 +485,66 @@ mod export {
|
||||
Err(methods::EntryNotFound) => bail!("no such entry"),
|
||||
}
|
||||
}
|
||||
|
||||
#[export]
|
||||
fn api_unlock_tfa(#[raw] raw_this: Value, userid: &str) -> Result<bool, Error> {
|
||||
let this: &Tfa = (&raw_this).try_into()?;
|
||||
Ok(methods::unlock_and_reset_tfa(
|
||||
&mut this.inner.lock().unwrap(),
|
||||
&UserAccess::new(&raw_this)?,
|
||||
userid,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
struct TfaLockStatus {
|
||||
/// Once a user runs into a TOTP limit they get locked out of TOTP until they successfully use
|
||||
/// a recovery key.
|
||||
#[serde(skip_serializing_if = "bool_is_false", default)]
|
||||
totp_locked: bool,
|
||||
|
||||
/// If a user hits too many 2nd factor failures, they get completely blocked for a while.
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
#[serde(deserialize_with = "filter_expired_timestamp")]
|
||||
tfa_locked_until: Option<i64>,
|
||||
}
|
||||
|
||||
impl From<&proxmox_tfa::api::TfaUserData> for TfaLockStatus {
|
||||
fn from(data: &proxmox_tfa::api::TfaUserData) -> Self {
|
||||
Self {
|
||||
totp_locked: data.totp_locked,
|
||||
tfa_locked_until: data.tfa_locked_until,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bool_is_false(b: &bool) -> bool {
|
||||
!*b
|
||||
}
|
||||
|
||||
#[export]
|
||||
fn tfa_lock_status(
|
||||
#[try_from_ref] this: &Tfa,
|
||||
userid: Option<&str>,
|
||||
) -> Result<Option<perlmod::Value>, Error> {
|
||||
let this = this.inner.lock().unwrap();
|
||||
if let Some(userid) = userid {
|
||||
if let Some(user) = this.users.get(userid) {
|
||||
Ok(Some(perlmod::to_value(&TfaLockStatus::from(user))?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(Some(perlmod::to_value(
|
||||
&HashMap::<String, TfaLockStatus>::from_iter(
|
||||
this.users
|
||||
.iter()
|
||||
.map(|(uid, data)| (uid.clone(), TfaLockStatus::from(data))),
|
||||
),
|
||||
)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Version 1 format of `/etc/pve/priv/tfa.cfg`
|
||||
@ -514,6 +638,7 @@ fn decode_old_entry(ty: &[u8], data: &[u8], user: &str) -> Result<TfaUserData, E
|
||||
b"oath" => user_data.totp.extend(
|
||||
decode_old_oath_entry(value, user)?
|
||||
.into_iter()
|
||||
.map(proxmox_tfa::api::TotpEntry::new)
|
||||
.map(move |entry| proxmox_tfa::api::TfaEntry::from_parts(info.clone(), entry)),
|
||||
),
|
||||
b"yubico" => user_data.yubico.extend(
|
||||
@ -841,9 +966,7 @@ fn challenge_data_path(userid: &str, debug: bool) -> PathBuf {
|
||||
}
|
||||
|
||||
impl proxmox_tfa::api::OpenUserChallengeData for UserAccess {
|
||||
type Data = UserChallengeData;
|
||||
|
||||
fn open(&self, userid: &str) -> Result<UserChallengeData, Error> {
|
||||
fn open(&self, userid: &str) -> Result<Box<dyn UserChallengeAccess>, Error> {
|
||||
if self.is_debug() {
|
||||
mkdir("./local-tfa-challenges", 0o700)?;
|
||||
} else {
|
||||
@ -886,15 +1009,15 @@ impl proxmox_tfa::api::OpenUserChallengeData for UserAccess {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(UserChallengeData {
|
||||
Ok(Box::new(UserChallengeData {
|
||||
inner,
|
||||
path,
|
||||
lock: file,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// `open` without creating the file if it doesn't exist, to finish WA authentications.
|
||||
fn open_no_create(&self, userid: &str) -> Result<Option<UserChallengeData>, Error> {
|
||||
fn open_no_create(&self, userid: &str) -> Result<Option<Box<dyn UserChallengeAccess>>, Error> {
|
||||
let path = challenge_data_path(userid, self.is_debug());
|
||||
|
||||
let mut file = match std::fs::OpenOptions::new()
|
||||
@ -915,11 +1038,11 @@ impl proxmox_tfa::api::OpenUserChallengeData for UserAccess {
|
||||
format_err!("failed to read challenge data for user {}: {}", userid, err)
|
||||
})?;
|
||||
|
||||
Ok(Some(UserChallengeData {
|
||||
Ok(Some(Box::new(UserChallengeData {
|
||||
inner,
|
||||
path,
|
||||
lock: file,
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
fn remove(&self, userid: &str) -> Result<bool, Error> {
|
||||
@ -930,6 +1053,10 @@ impl proxmox_tfa::api::OpenUserChallengeData for UserAccess {
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_lockout(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Container of `TfaUserChallenges` with the corresponding file lock guard.
|
||||
@ -947,7 +1074,7 @@ impl proxmox_tfa::api::UserChallengeAccess for UserChallengeData {
|
||||
&mut self.inner
|
||||
}
|
||||
|
||||
fn save(self) -> Result<(), Error> {
|
||||
fn save(&mut self) -> Result<(), Error> {
|
||||
UserChallengeData::save(self)
|
||||
}
|
||||
}
|
||||
@ -992,7 +1119,7 @@ impl UserChallengeData {
|
||||
///
|
||||
/// This currently consumes selfe as we never perform more than 1 insertion/removal, and this
|
||||
/// way also unlocks early.
|
||||
fn save(mut self) -> Result<(), Error> {
|
||||
fn save(&mut self) -> Result<(), Error> {
|
||||
self.rewind()?;
|
||||
|
||||
serde_json::to_writer(&mut &self.lock, &self.inner).map_err(|err| {
|
||||
|
@ -1,4 +1,9 @@
|
||||
.PHONY: test
|
||||
test:
|
||||
test: Proxmox/Lib/PVE.pm
|
||||
@echo "-- running pve-rs tests --"
|
||||
./resource_scheduling.pl
|
||||
perl -I. -I.. -I../.. ./resource_scheduling.pl
|
||||
|
||||
# The test stub, we don't know where to look for the library from in here!
|
||||
Proxmox/Lib/PVE.pm:
|
||||
@echo "run 'make' in the pve-rs/ dir first"
|
||||
@exit 1
|
||||
|
@ -5,35 +5,73 @@ use warnings;
|
||||
|
||||
use Test::More;
|
||||
|
||||
# FIXME ensure that the just built library is loaded rather than the installed one and add a test
|
||||
# target to pve-rs/Makefile afterwards. Issue is that the loader looks into an $PATH/auto directory,
|
||||
# so it's not enough to use lib qw(../target/release)
|
||||
# Also might be a good idea to test for existence of the files to avoid surprises if the directory
|
||||
# structure changes in the future.
|
||||
#use lib qw(..);
|
||||
#use lib qw(../target/release);
|
||||
use PVE::RS::ResourceScheduling::Static;
|
||||
|
||||
my $static = PVE::RS::ResourceScheduling::Static->new();
|
||||
is(scalar($static->list_nodes()->@*), 0, 'node list empty');
|
||||
$static->add_node("A", 10, 100_000_000_000);
|
||||
is(scalar($static->list_nodes()->@*), 1, '1 node added');
|
||||
$static->add_node("B", 20, 200_000_000_000);
|
||||
is(scalar($static->list_nodes()->@*), 2, '2nd node');
|
||||
$static->add_node("C", 30, 300_000_000_000);
|
||||
is(scalar($static->list_nodes()->@*), 3, '3rd node');
|
||||
$static->remove_node("C");
|
||||
is(scalar($static->list_nodes()->@*), 2, '3rd removed should be 2');
|
||||
ok($static->contains_node("A"), 'should contain a node A');
|
||||
ok($static->contains_node("B"), 'should contain a node B');
|
||||
ok(!$static->contains_node("C"), 'should not contain a node C');
|
||||
sub test_basic {
|
||||
my $static = PVE::RS::ResourceScheduling::Static->new();
|
||||
is(scalar($static->list_nodes()->@*), 0, 'node list empty');
|
||||
$static->add_node("A", 10, 100_000_000_000);
|
||||
is(scalar($static->list_nodes()->@*), 1, '1 node added');
|
||||
$static->add_node("B", 20, 200_000_000_000);
|
||||
is(scalar($static->list_nodes()->@*), 2, '2nd node');
|
||||
$static->add_node("C", 30, 300_000_000_000);
|
||||
is(scalar($static->list_nodes()->@*), 3, '3rd node');
|
||||
$static->remove_node("C");
|
||||
is(scalar($static->list_nodes()->@*), 2, '3rd removed should be 2');
|
||||
ok($static->contains_node("A"), 'should contain a node A');
|
||||
ok($static->contains_node("B"), 'should contain a node B');
|
||||
ok(!$static->contains_node("C"), 'should not contain a node C');
|
||||
}
|
||||
|
||||
my $service = {
|
||||
maxcpu => 4,
|
||||
maxmem => 20_000_000_000,
|
||||
};
|
||||
sub test_balance {
|
||||
my $static = PVE::RS::ResourceScheduling::Static->new();
|
||||
$static->add_node("A", 10, 100_000_000_000);
|
||||
$static->add_node("B", 20, 200_000_000_000);
|
||||
|
||||
my $service = {
|
||||
maxcpu => 4,
|
||||
maxmem => 20_000_000_000,
|
||||
};
|
||||
|
||||
for (my $i = 0; $i < 15; $i++) {
|
||||
my $score_list = $static->score_nodes_to_start_service($service);
|
||||
|
||||
# imitate HA manager
|
||||
my $scores = { map { $_->[0] => -$_->[1] } $score_list->@* };
|
||||
my @nodes = sort {
|
||||
$scores->{$a} <=> $scores->{$b} || $a cmp $b
|
||||
} keys $scores->%*;
|
||||
|
||||
if ($i % 3 == 2) {
|
||||
is($nodes[0], "A", 'first should be A');
|
||||
is($nodes[1], "B", 'second should be A');
|
||||
} else {
|
||||
is($nodes[0], "B", 'first should be B');
|
||||
is($nodes[1], "A", 'second should be A');
|
||||
}
|
||||
|
||||
$static->add_service_usage_to_node($nodes[0], $service);
|
||||
}
|
||||
}
|
||||
|
||||
sub test_overcommitted {
|
||||
my $static = PVE::RS::ResourceScheduling::Static->new();
|
||||
$static->add_node("A", 4, 4_102_062_080);
|
||||
$static->add_node("B", 4, 4_102_062_080);
|
||||
$static->add_node("C", 4, 4_102_053_888);
|
||||
$static->add_node("D", 4, 4_102_053_888);
|
||||
|
||||
my $service = {
|
||||
maxcpu => 1,
|
||||
maxmem => 536_870_912,
|
||||
};
|
||||
|
||||
$static->add_service_usage_to_node("A", $service);
|
||||
$static->add_service_usage_to_node("A", $service);
|
||||
$static->add_service_usage_to_node("A", $service);
|
||||
$static->add_service_usage_to_node("B", $service);
|
||||
$static->add_service_usage_to_node("A", $service);
|
||||
|
||||
for (my $i = 0; $i < 15; $i++) {
|
||||
my $score_list = $static->score_nodes_to_start_service($service);
|
||||
|
||||
# imitate HA manager
|
||||
@ -42,15 +80,65 @@ for (my $i = 0; $i < 15; $i++) {
|
||||
$scores->{$a} <=> $scores->{$b} || $a cmp $b
|
||||
} keys $scores->%*;
|
||||
|
||||
if ($i % 3 == 2) {
|
||||
is($nodes[0], "A", 'first should be A');
|
||||
is($nodes[1], "B", 'second should be A');
|
||||
} else {
|
||||
is($nodes[0], "B", 'first should be B');
|
||||
is($nodes[1], "A", 'second should be A');
|
||||
}
|
||||
|
||||
$static->add_service_usage_to_node($nodes[0], $service);
|
||||
is($nodes[0], "C", 'first should be C');
|
||||
is($nodes[1], "D", 'second should be D');
|
||||
is($nodes[2], "B", 'third should be B');
|
||||
is($nodes[3], "A", 'fourth should be A');
|
||||
}
|
||||
|
||||
sub test_balance_small_memory_difference {
|
||||
my ($with_start_load) = @_;
|
||||
|
||||
my $static = PVE::RS::ResourceScheduling::Static->new();
|
||||
# Memory is different to avoid flaky results with what would otherwise be ties.
|
||||
$static->add_node("A", 8, 10_000_000_000);
|
||||
$static->add_node("B", 4, 9_000_000_000);
|
||||
$static->add_node("C", 4, 8_000_000_000);
|
||||
|
||||
if ($with_start_load) {
|
||||
$static->add_service_usage_to_node("A", { maxcpu => 4, maxmem => 1_000_000_000 });
|
||||
$static->add_service_usage_to_node("B", { maxcpu => 2, maxmem => 1_000_000_000 });
|
||||
$static->add_service_usage_to_node("C", { maxcpu => 2, maxmem => 1_000_000_000 });
|
||||
}
|
||||
|
||||
my $service = {
|
||||
maxcpu => 3,
|
||||
maxmem => 16_000_000,
|
||||
};
|
||||
|
||||
for (my $i = 0; $i < 20; $i++) {
|
||||
my $score_list = $static->score_nodes_to_start_service($service);
|
||||
|
||||
# imitate HA manager
|
||||
my $scores = { map { $_->[0] => -$_->[1] } $score_list->@* };
|
||||
my @nodes = sort {
|
||||
$scores->{$a} <=> $scores->{$b} || $a cmp $b
|
||||
} keys $scores->%*;
|
||||
|
||||
if ($i % 4 <= 1) {
|
||||
is($nodes[0], "A", 'first should be A');
|
||||
is($nodes[1], "B", 'second should be B');
|
||||
is($nodes[2], "C", 'third should be C');
|
||||
} elsif ($i % 4 == 2) {
|
||||
is($nodes[0], "B", 'first should be B');
|
||||
is($nodes[1], "C", 'second should be C');
|
||||
is($nodes[2], "A", 'third should be A');
|
||||
} elsif ($i % 4 == 3) {
|
||||
is($nodes[0], "C", 'first should be C');
|
||||
is($nodes[1], "A", 'second should be A');
|
||||
is($nodes[2], "B", 'third should be B');
|
||||
} else {
|
||||
die "internal error, got $i % 4 == " . ($i % 4) . "\n";
|
||||
}
|
||||
|
||||
$static->add_service_usage_to_node($nodes[0], $service);
|
||||
}
|
||||
}
|
||||
|
||||
test_basic();
|
||||
test_balance();
|
||||
test_overcommitted();
|
||||
test_balance_small_memory_difference(1);
|
||||
test_balance_small_memory_difference(0);
|
||||
|
||||
done_testing();
|
||||
|
@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
# Create a perl package given a product and package name.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
|
||||
my $product = shift @ARGV or die "missing product name (PVE, PMG or Common)\n";
|
||||
|
||||
die "missing package name\n" if !@ARGV;
|
||||
|
||||
for my $package (@ARGV) {
|
||||
my $path = ($package =~ s@::@/@gr) . ".pm";
|
||||
|
||||
print "Generating $path\n";
|
||||
|
||||
$path =~ m@^(.*)/[^/]+@;
|
||||
make_path($1, { mode => 0755 });
|
||||
|
||||
open(my $fh, '>', $path) or die "failed to open '$path' for writing: $!\n";
|
||||
|
||||
print {$fh} <<"EOF";
|
||||
package $package;
|
||||
use base 'Proxmox::Lib::$product';
|
||||
BEGIN { __PACKAGE__->bootstrap(); }
|
||||
1;
|
||||
EOF
|
||||
|
||||
close($fh);
|
||||
}
|
Loading…
Reference in New Issue
Block a user