From 49eb4e6e1c7f56aa4f8ac706c20d06dfcba4ff85 Mon Sep 17 00:00:00 2001 From: stgmsa Date: Tue, 12 Dec 2023 14:40:11 -0500 Subject: [PATCH] Feature/inv1 peap rework (#7891) * update ntlm_auth_wrapper for to support curl API calls * remove logs * remove logs * changing Makefile used to compile ntlm_auth_wrapper * clean up * add form fields in domains * change form * add store module example, add test button to password * remove binary options for ntlm_auth_wrapper. * adds error handling for ntlm auth backend api * adds ntlm_auth backend api adds docker wrapper and systemd configs * change migration script to handle multiple AD sections * clean up * adds perl dependencies in SPEC * adds machine account test endpoint adds last active time for connection dropping and re-establishing * change docker wrapper to kill all ntlm-auth-api containers * mschap auth flow changes: adds PacketFence-NTLM-Auth-Host, PacketFence-NTLM-Auth-Port to be passed to ntlm_auth * mschap auth flow changes: adds PacketFence-NTLM-Auth-Host, PacketFence-NTLM-Auth-Port to be passed to ntlm_auth * ntlm auth backend will read from domains.conf * adds error handling for migration script * adds rewrite resolv.conf in containers * changes docker wrapper to run multiple containers * change build process for dev scenario. * test machine account * removes machine account * clean up, adds services that requires a restart * clean up, removes lint errors * UI change, adds domain admin user and password field for domain join and update * machine account test * change create / delete domain logic * change domain admin username and password back to bind_dn and bind_pass * change method to POST * fix a run multiple image failure issue * adds label for machine account password changes function name to a proper one * update domain.pm for better error handling * update domain config UI, show test button in machine account field only when editing a domain conf. * - adds http test alive router for service health check - change ntlm-auth-api service status logic to service unhealthy if any API is not available. * backups old config files before running migration script * change machine account password to "password" section by default * ntlm-auth-dockerwrapper will block * show message instead of http response in ntlm-auth-test * collect ntlm auth api using syslog * removes save and join option when editing domain * fix no "ntlm-auth-api" not shown in pfcmd * add isManaged status to fix "service not required / pfcmd service broken" * add domain validator to enforce unique workgroup and dns_name * adjust wording * remove join/rejoin/unjoin artifacts, lint cleanup * fix typos and bugs * remove winbindd * transfer ntlm-auth-api logs to packetfence.log * removes winbindd fix pfcmd * add new container in ci * wake up gitlab * Revert "wake up gitlab" This reverts commit 763aeccbf3672165fe7b7b608359cb79a872f188. * fix systemd services * removes winbindd from spec * Updated rpm packaging * Temp patch for missing libdigest-md4-perl package * fix ntlm machine account test check supports both plain text password and nt hash * remove services temporarily * add back * split ad_server into separate ip/fqdn fields, deprecate gethostbyaddr where external DNS is used and rDNS is not possible * fix password not being passed to python api for validation * adjust validations * adjust validations * remove generatedomainconfig and samba related * Removed winbindd from admin ui * Fixed packaging issue after rebase * Fix for rhel packaging * adds dns_resolve to pf util * change typos, form field labels * Updated dns lookup * Fixed missing impacket-addcomputer * Fixed typo * change typos, adds fallback option for dns resolve and gethostbyname * removes unused join unjoin stuff adds rejoin capability if machine account is deleted on AD without notifying Packetfence * change fqdn resolv logic * update required packetfence-perl version * resolve ad IP using util * remove NTLMv2 support * adds extra logs for troubleshooting purpose * address PR comments * address PR comments --------- Co-authored-by: Darren Satkunas Co-authored-by: JeGoi <13801368+JeGoi@users.noreply.github.com> Co-authored-by: Durand Fabrice --- .gitlab-ci.yml | 4 + Makefile | 2 +- addons/AD/smb.tt | 63 --- addons/create_chroot.sh | 34 -- addons/monit/monit_build_configuration.pl | 22 - .../00_packetfence.tt | 23 - .../40_OS-winbind.tt | 7 - .../upgrade/to-13.1-move-ntlm-auth-to-rest.pl | 258 +++++++++++ bin/pyntlm_auth/app.py | 410 ++++++++++++++++++ conf/documentation.conf | 18 +- conf/log.conf.d/winbindd-wrapper.conf.example | 14 - conf/monitoring/apps_groups.conf.example | 1 - conf/pf.conf.defaults | 9 - conf/radiusd/mschap.conf.example | 10 +- .../packetfence-ntlm-auth-api-domain@.service | 23 + .../systemd/packetfence-ntlm-auth-api.service | 23 + conf/systemd/packetfence-winbindd.service | 18 - containers/ntlm-auth-api/Dockerfile | 9 + containers/pfperl-api/Dockerfile | 4 + containers/systemd-service | 15 + debian/control | 9 +- .../packetfence-ntlm-auth-api-domain@.service | 1 + debian/packetfence-ntlm-auth-api.service | 1 + debian/packetfence.postinst | 2 +- debian/packetfence.preinst | 4 +- debian/rules | 3 +- docs/PacketFence_Upgrade_Guide.asciidoc | 12 - .../components/schemas/configbasesmeta.yaml | 2 - docs/api/spec/openapi.json | 4 - docs/api/spec/openapi.yaml | 3 - .../authentication_mechanisms.asciidoc | 9 +- docs/installation/best_practices.asciidoc | 4 - docs/installation/pfcmd.help | 1 - go/caddy/api/api.go | 2 + go/caddy/api/ntlm.go | 107 +++++ go/caddy/log-tailer/logmeta.go | 1 - go/caddy/ntlm/ntlm.go | 75 ++++ go/pfconfigdriver/structs.go | 9 +- .../lib/pfappserver/Form/Config/Domain.pm | 66 ++- .../src/views/Configuration/domains/_api.js | 20 +- .../domains/_components/BaseButtonJoin.vue | 315 -------------- .../domains/_components/TheForm.vue | 71 ++- .../domains/_components/TheSearch.vue | 9 +- .../domains/_components/TheView.js | 5 - .../domains/_components/index.js | 7 + .../domains/_composables/useCollection.js | 33 +- .../src/views/Configuration/domains/_store.js | 114 +---- .../src/views/Configuration/domains/schema.js | 53 ++- .../services/_components/TheForm.vue | 9 - .../services/_components/index.js | 1 - .../UnifiedApi/Controller/Config/Domains.pm | 292 ++++++++++--- lib/pf/cmd/pf.pm | 1 - lib/pf/cmd/pf/generatedomainconfig.pm | 69 --- lib/pf/cmd/pf/service.pm | 2 +- lib/pf/constants/syslog.pm | 1 + lib/pf/domain.pm | 260 ++--------- lib/pf/services/manager/ntlm_auth_api.pm | 107 +++++ lib/pf/services/manager/pf.pm | 2 +- lib/pf/services/manager/winbindd.pm | 130 ------ lib/pf/util.pm | 37 ++ lib/pf/util/radius_dictionary.pm | 6 + raddb/dictionary.inverse | 2 + .../attr_filter/packetfence-post-auth | 4 +- .../perl/packetfence-multi-domain.pm | 6 +- rpm/packetfence.spec | 19 +- sbin/ntlm-auth-api-docker-wrapper | 35 ++ sbin/ntlm-auth-api-domain | 30 ++ sbin/winbindd-wrapper | 305 ------------- src/ntlm_auth_wrap.c | 225 +++++----- 69 files changed, 1790 insertions(+), 1662 deletions(-) delete mode 100644 addons/AD/smb.tt delete mode 100755 addons/create_chroot.sh delete mode 100644 addons/monit/monit_checks_configurations/40_OS-winbind.tt create mode 100644 addons/upgrade/to-13.1-move-ntlm-auth-to-rest.pl create mode 100644 bin/pyntlm_auth/app.py delete mode 100644 conf/log.conf.d/winbindd-wrapper.conf.example create mode 100644 conf/systemd/packetfence-ntlm-auth-api-domain@.service create mode 100644 conf/systemd/packetfence-ntlm-auth-api.service delete mode 100644 conf/systemd/packetfence-winbindd.service create mode 100644 containers/ntlm-auth-api/Dockerfile create mode 120000 debian/packetfence-ntlm-auth-api-domain@.service create mode 120000 debian/packetfence-ntlm-auth-api.service create mode 100644 go/caddy/api/ntlm.go create mode 100644 go/caddy/ntlm/ntlm.go delete mode 100644 html/pfappserver/root/src/views/Configuration/domains/_components/BaseButtonJoin.vue delete mode 100644 lib/pf/cmd/pf/generatedomainconfig.pm create mode 100644 lib/pf/services/manager/ntlm_auth_api.pm delete mode 100644 lib/pf/services/manager/winbindd.pm create mode 100644 sbin/ntlm-auth-api-docker-wrapper create mode 100644 sbin/ntlm-auth-api-domain delete mode 100755 sbin/winbindd-wrapper diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9f6ef6a762cf..134be4a50010 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -642,6 +642,7 @@ pfdeb_based_dev: - "proxysql" - "pfldapexplorer" - "kafka" + - "ntlm-auth-api" img_dev: extends: @@ -724,6 +725,7 @@ pfdeb_based_br_maint: - "proxysql" - "pfldapexplorer" - "kafka" + - "ntlm-auth-api" img_br_maint: extends: @@ -806,6 +808,7 @@ pfdeb_based_cloud_nac: - "proxysql" - "pfldapexplorer" - "kafka" + - "ntlm-auth-api" img_cloud_nac: extends: @@ -888,6 +891,7 @@ pfdeb_based_rel: - "proxysql" - "pfldapexplorer" - "kafka" + - "ntlm-auth-api" img_rel: extends: diff --git a/Makefile b/Makefile index bbf56eb38fc9..954a892f17be 100644 --- a/Makefile +++ b/Makefile @@ -110,7 +110,7 @@ bin/pfcmd: src/pfcmd.c $(CC) -O2 -g -std=c99 -Wall $< -o $@ bin/ntlm_auth_wrapper: src/ntlm_auth_wrap.c - $(CC) -g -std=c99 -Wall $< -o $@ + $(CC) -g -std=c99 -Wall $< -o $@ -lcurl -lcjson src/mariadb_udf/pf_udf.so: src/mariadb_udf/pf_udf.c $(PF_UDF_OBJ) $(CC) -O2 -Wall -g $$(pkg-config libmariadb --cflags) -fPIC -shared -o $@ $< $(PF_UDF_OBJ) diff --git a/addons/AD/smb.tt b/addons/AD/smb.tt deleted file mode 100644 index 767b501dbf94..000000000000 --- a/addons/AD/smb.tt +++ /dev/null @@ -1,63 +0,0 @@ -[global] - -## Browsing/Identification ### - -# Change this to the workgroup/NT-domain name your Samba server will part of -workgroup = [% workgroup %] -realm = [% dns_name %] - -netbios name = [% server_name %] -server string = [% server_name %] - -pid directory = /usr/local/pf/var/run/[% domain %] -lock directory = /var/cache/samba -private dir = /var/cache/samba - -security = ADS -winbind use default domain = no -idmap uid = 600-20000 -idmap gid = 600-20000 -template shell = /bin/bash -winbind expand groups = 10 -password server = [% sticky_dc %] -domain master = no -local master = no -preferred master = no - -inherit permissions = yes -admin users = @[% workgroup %]\"domain admins" - -hide files = /~*/Thumbs.db/desktop.ini/ntuser.ini/NTUSER.*/SMax.*/ -veto files = /lost+found/ - -allow trusted domains = yes - -# No printers on this host -show add printer wizard = no -disable spoolss = yes -load printers = no -printing = bsd -printcap name = /dev/null - -# No usershares here -usershare max shares = 0 - -# By default no guests and invisible -browseable = no -guest ok = no - -#interfaces = 169.254.0.1 -#bind interfaces only = yes - -# prevent winbind from periodically changing the password -machine password timeout = 0 - -# Prevent 'Failed to join domain: failed to lookup DC info for domain 'DOMAIN.DOMAIN' over rpc: Access denied' error when attempting a domain join -# Command 'net ads join -d 5' outputs the following relevant lines -# cli_negprot: SMB signing is mandatory and the server doesn't support it. -# failed negprot: NT_STATUS_ACCESS_DENIED -client ipc signing = auto - -# Prevent DDNS updates -allow dns updates = disabled - diff --git a/addons/create_chroot.sh b/addons/create_chroot.sh deleted file mode 100755 index de22a34488f2..000000000000 --- a/addons/create_chroot.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -NS=$1 -BASE=$2 - -if [ -z "$NS" ] || [ -z "$BASE" ]; then - echo "Missing parameters. First is NS second is BASE." - exit 1; -fi - -ETC_DIRS=$(find /etc -maxdepth 1 -type d|tail -n+2|sed 's|/||') -DIRS=(proc var etc lib lib64 usr sbin bin var/cache/samba sys var/lib/samba var/run/samba/winbindd dev tmp run/samba var/log/samba $ETC_DIRS) - -for dir in "${DIRS[@]}"; do - [ -d $BASE/$NS/$dir ] || mkdir -p $BASE/$NS/$dir -done - -# Change permissions on var/run/samba/winbindd -chmod 0755 $BASE/$NS/var/run/samba/winbindd - -DIRS=(proc lib lib64 bin usr sbin sys dev var/log/samba $ETC_DIRS) - -MOUNTS=(`mount | awk '{print $3}'`) - -# mount all $DIRS and submounts in chroot if not already mounted -for dir in "${DIRS[@]}"; do - value=$BASE/$NS/$dir - if [[ ! " ${MOUNTS[@]} " =~ " ${value} " ]]; then - mount -o rbind /$dir $BASE/$NS/$dir - fi -done - -ETC_FILES=$(find /etc -maxdepth 1 -type f|grep -v resolv.conf) -echo "$ETC_FILES"|while read etc_file; do yes 2>/dev/null|cp $etc_file /$BASE/$NS$etc_file; done -yes 2>/dev/null|cp /etc/netns/$NS/resolv.conf /$BASE/$NS/etc diff --git a/addons/monit/monit_build_configuration.pl b/addons/monit/monit_build_configuration.pl index c63922eaf465..4b41177d844d 100755 --- a/addons/monit/monit_build_configuration.pl +++ b/addons/monit/monit_build_configuration.pl @@ -26,7 +26,6 @@ BEGIN 'portsec' => '10_packetfence-portsec', 'drbd' => '20_packetfence-drbd', 'active-active' => '30_packetfence-activeactive', - 'os-winbind' => '40_OS-winbind', 'os-checks' => '50_OS-checks', ); @@ -44,7 +43,6 @@ BEGIN print " - portsec: Will add some checks for port-security related services\n"; print " - drbd: Will add some checks for DRBD\n"; print " - active-active: Will add some checks for active-active clustering related services\n"; - print " - os-winbind: Will add a check for the operating system winbindd process. Use it when the winbind/samba configuration is made outside PacketFence\n"; print " - os-checks: Will add some OS best-practices checks\n"; print "mailserver: IP or resolvable FQDN of the mail server to use to send alerts (optional)\n"; die "\n"; @@ -136,23 +134,18 @@ sub generate_specific_configurations { my $destination_file = catfile($MONIT_PATH,$CONFIGURATION_TO_TEMPLATE{$configuration} . $CONF_FILE_EXTENSION); print " - $destination_file\n"; - # Handling domains (winbind configuration) - my $domains = handle_domains(); my $tt = Template->new(ABSOLUTE => 1); my $freeradius_bin = ( $OS eq "rhel" ) ? "radiusd" : "freeradius"; my $mail_bin = ( $OS eq "rhel" ) ? "/bin/mail" : "/usr/bin/mail"; my $service_bin = ( $OS eq "rhel" ) ? "/sbin/service" : "/usr/sbin/service"; - my $winbindd_pid = ( $OS eq "rhel" ) ? "/var/run/winbindd.pid" : "/var/run/samba/winbindd.pid"; my $vars = { FREERADIUS_BIN => $freeradius_bin, EMAILS => \@emails, SUBJECT_IDENTIFIER => $subject_identifier, MAILSERVER => $mailserver, - DOMAINS => $domains, MAIL_BIN => $mail_bin, SERVICE_BIN => $service_bin, - WINBINDD_PID => $winbindd_pid, ACTIVE_ACTIVE => (any { $_ eq 'active-active' } @configurations), FINGERBANK_ENABLED => $fingerbank_enabled, }; @@ -161,21 +154,6 @@ sub generate_specific_configurations { } -=head2 handle_domains - -Generate the managed by PacketFence domain list array to be used in configuration templates - -=cut - -sub handle_domains { - use pf::config; - use pf::file_paths; - my %domains = (); - foreach my $domain ( keys(%pf::config::ConfigDomain) ) { - $domains{$domain} = "$pf::file_paths::var_dir/run/$domain/winbindd.pid"; - } - return \%domains; -} 1; diff --git a/addons/monit/monit_checks_configurations/00_packetfence.tt b/addons/monit/monit_checks_configurations/00_packetfence.tt index e6a37ce4d0ec..2bb9e29a157a 100644 --- a/addons/monit/monit_checks_configurations/00_packetfence.tt +++ b/addons/monit/monit_checks_configurations/00_packetfence.tt @@ -208,29 +208,6 @@ CHECK PROCESS packetfence-pfpki MATCHING "pfpki" stop program = "/usr/local/pf/bin/pfcmd service pfpki stop" if 3 restarts within 10 cycles then alert -[% IF DOMAINS.size > 0 %] -CHECK PROCESS packetfence-winbind MATCHING "winbindd-wrapper" - group PacketFence - start program = "/usr/local/pf/bin/pfcmd service winbindd restart" with timeout 60 seconds - stop program = "/usr/local/pf/bin/pfcmd service winbindd stop" - if 3 restarts within 10 cycles then alert - -[% FOREACH domain IN DOMAINS.keys %] -CHECK PROCESS packetfence-winbind-[% domain %] MATCHING "/usr/sbin/winbindd -s /etc/samba/[% domain %].conf -l /var/log/samba[% domain %] --foreground" - group PacketFence - if changed ppid then alert - depends on packetfence-winbind - -CHECK PROGRAM packetfence-ntlm-[% domain %] with path "/usr/bin/timeout 5 /usr/sbin/ip netns exec [% domain %] /usr/sbin/chroot /chroots/[% domain %]/ /usr/bin/wbinfo -P" - group PacketFence - # There is no need to start the program, the winbindd-wrapper takes care of it if this gets stopped - start program = "/bin/true" with timeout 60 seconds - stop program = "/bin/bash -c 'export pid=`cat /usr/local/pf/var/run/[% domain %]/winbindd.pid` ; pkill -9 -P $pid ; kill -9 $pid'" - if status == 124 for 3 cycles then restart # (30 seconds being down) - every 5 cycles # every 10 seconds (if 1 cycle is 2 seconds) -[% END %] - -[% END %] check program monitoring-mysql-connections with path /usr/local/pf/addons/monit/monitoring-scripts/monitor_mysql_connections.pl group OS diff --git a/addons/monit/monit_checks_configurations/40_OS-winbind.tt b/addons/monit/monit_checks_configurations/40_OS-winbind.tt deleted file mode 100644 index e58f10b84a1d..000000000000 --- a/addons/monit/monit_checks_configurations/40_OS-winbind.tt +++ /dev/null @@ -1,7 +0,0 @@ -# Winbind OS managed checks - -check process winbindd with pidfile [% WINBINDD_PID %] - group OS - start program = "[% SERVICE_BIN %] winbind start" with timeout 60 seconds - stop program = "[% SERVICE_BIN %] winbind stop" - if 3 restarts within 10 cycles then alert diff --git a/addons/upgrade/to-13.1-move-ntlm-auth-to-rest.pl b/addons/upgrade/to-13.1-move-ntlm-auth-to-rest.pl new file mode 100644 index 000000000000..af051a85ccdd --- /dev/null +++ b/addons/upgrade/to-13.1-move-ntlm-auth-to-rest.pl @@ -0,0 +1,258 @@ +#!/usr/bin/perl + +=head1 NAME + +addons/upgrade/to-10.1-move-radius-configuration-parameter.pl + +=cut + +=head1 DESCRIPTION + +Move radius configuration parameters to associated new files + +=cut + +use strict; +use warnings; +use lib qw(/usr/local/pf/lib /usr/local/pf/lib_perl/lib/perl5); +use pf::IniFiles; +use pf::file_paths qw($authentication_config_file $domain_config_file); +use pf::util; +use Digest::MD4; +use Encode; +use MIME::Base64; + +my $ini = pf::IniFiles->new(-file => $domain_config_file, -allowempty => 1); + +unless ($ini) { + exit; +} + +my $updated = 0; +my $ntlm_auth_host = "100.64.0.1 "; +my $ntlm_auth_port = 4999; + +my $tmp_dirname = pf_run("date +%Y%m%d_%H%M%S"); +$tmp_dirname =~ s/^\s+|\s+$//g; +my $target_dir = "/usr/local/pf/archive/$tmp_dirname"; + +print("Backing up configuration files, they can be found in $target_dir\n"); + +pf_run("mkdir -p $target_dir", accepted_exit_status => [ 0 ], working_directory => "/usr/local/pf/archive"); +pf_run("cp -R /usr/local/pf/conf/domain.conf $target_dir"); +pf_run("cp -R /usr/local/pf/conf/realm.conf $target_dir"); + +umount_winbindd(); + +for my $section (grep {/^\S+$/} $ini->Sections()) { + if ($ini->exists($section, 'machine_account_password')) { + next; + } + + pf_run("mkdir -p $target_dir/chroots/$section/etc && cp -R /chroots/$section/etc/samba $target_dir/chroots/$section/etc"); + pf_run("mkdir -p $target_dir/chroots/$section/var/cache && cp -R /chroots/$section/var/cache/samba $target_dir/chroots/$section/var/cache"); +} +pf_run("cd /usr/local/pf/archive && tar -cvzf $tmp_dirname.tgz $tmp_dirname && rm -rf $tmp_dirname"); + +for my $section (grep {/^\S+$/} $ini->Sections()) { + print("Generating config for section: $section\n"); + $ntlm_auth_port += 1; + + if ($ini->exists($section, 'machine_account_password')) { + print(" Section: ", $section, " already has machine_account and machine_account_password set. skipped\n"); + next; + } + + my $samba_conf_path = "/chroots/$section/etc/samba/$section.conf"; + my $samba_ini = pf::IniFiles->new(-file => $samba_conf_path, -allowempty => 1); + unless ($samba_ini) { + print(" Unable to find correspond samba conf file in $samba_conf_path, section $section skipped\n"); + next; + } + + my $dns_name = $ini->val($section, "dns_name"); + my $work_group = $samba_ini->val("global", "workgroup"); + my $realm = $samba_ini->val("global", "realm"); + + my $ad_server = $ini->val($section, "ad_server"); + my $dns_server = $ini->val($section, "dns_server"); + my $ad_fqdn = $ini->val($section, "ad_fqdn"); + + if (!defined($ad_fqdn) || $ad_fqdn eq "") { + if (valid_ip($ad_server)) { + my ($ad_fqdn_from_dns, $i, $msg) = pf::util::dns_resolve($ad_server, $dns_server, $dns_name); + if (defined($ad_fqdn_from_dns) && $ad_fqdn_from_dns ne "") { + $ad_fqdn = $ad_fqdn_from_dns; + } + else { + print(" AD server '$ad_server' does not have a PRT record retrieved using given DNS server. Trying 'gethostbyaddr' instead. Got: "); + my $ad_fqdn_from_system = gethostbyaddr(inet_aton($ad_server), "AF_INET"); + if (defined($ad_fqdn_from_system) && $ad_fqdn_from_system ne "") { + print("'$ad_fqdn_from_system'.\n"); + $ad_fqdn = $ad_fqdn_from_system; + } + else { + print("Nothing. You need to input the AD's FQDN manually here: "); + $ad_fqdn = ; + chomp($ad_fqdn); + } + } + } + else { + $ad_fqdn = $ad_server; + my ($h, $ip_from_dns, $msg) = pf::util::dns_resolve($ad_fqdn, $dns_server, $dns_name); + if (defined($ip_from_dns) && $ip_from_dns ne "") { + $ad_server = $ip_from_dns; + } + else { + my $packed_ip = gethostbyname($ad_fqdn); + if (defined $packed_ip) { + $ad_server = inet_ntoa($packed_ip); + } + else { + print(" Failed to resolve FQDN: '$ad_fqdn', Please check your DNS/network config\n") + } + } + } + } + + unless (defined($dns_name) && $dns_name ne "") { + print(" Unable to retrieve dns_name from config file. Section $section skipped\n"); + next; + } + unless (defined($work_group) && $work_group ne "") { + print(" Unable to retrieve work_group from config file. Section $section skipped\n"); + next; + } + + + # the tdb file should be located at /var/lib/samba/secrets.tdb, but here we use cache instead + my $secret_tdb_file = "/chroots/$section/var/cache/samba/secrets.tdb"; + + my $exit_code = 0; + + # extract machine account from tdb file + my $machine_account = ""; + my $machine_account_key = "SECRETS/SALTING_PRINCIPAL/DES/$dns_name"; + my $tdb_secret_host_value; + ($exit_code, $tdb_secret_host_value) = tdbdump_get_value("/chroots/$section/var/cache/samba/secrets.tdb", $machine_account_key); + if ($exit_code == 0 && $tdb_secret_host_value ne "") { + $machine_account = extract_machine_account($tdb_secret_host_value); + } + else { + print(" Unable to retrieve machine account from tdb file. Please check samba tdb database. Section $section Skipped\n"); + next; + } + + # extract machine account password from tdb file + my $machine_password = ""; + my $machine_account_password_key = "SECRETS/MACHINE_PASSWORD/$work_group"; + my $tdb_secret_machine_password_value; + + ($exit_code, $tdb_secret_machine_password_value) = tdbdump_get_value("/chroots/$section/var/cache/samba/secrets.tdb", $machine_account_password_key); + + if ($exit_code == 0 && $tdb_secret_machine_password_value ne "") { + $machine_password = extract_machine_password($tdb_secret_machine_password_value); + } + else { + print(" Unable to retrieve machine account password from tdb file. Please check samba tdb database. Section $section Skipped\n"); + next; + } + + my $server_name = $ini->val($section, 'server_name'); + if (lc($server_name) ne lc($machine_account)) { + print(" Unable to rewrite server_name values, current value is: $server_name, expected is: $machine_account, Section $section Skipped\n"); + next; + } + + if (!$ini->exists($section, 'machine_account_password')) { + $ini->newval($section, 'machine_account_password', $machine_password); + $ini->newval($section, 'password_is_nt_hash', '1'); + $ini->newval($section, 'ntlm_auth_host', $ntlm_auth_host); + $ini->newval($section, 'ntlm_auth_port', $ntlm_auth_port); + $ini->newval($section, 'ad_fqdn', $ad_fqdn); + $ini->setval($section, 'ad_server', $ad_server); + $updated |= 1; + } +} + +if ($updated) { + $ini->RewriteConfig(); +} + +sub tdbdump_get_value { + my ($tdb_file, $key) = @_; + my $cmd = "/usr/bin/tdbdump $tdb_file -k $key"; + + my $result = qx($cmd); + my $exit_code = $? >> 8; + + return $exit_code, $result; +} + +sub extract_machine_account { + my ($tdb_secret_host_string) = @_; + if ($tdb_secret_host_string =~ /^host\/(.*?)@(.*?)\\00$/i) { + my $hostname = $1; + my $domain = $2; + + # this is a double check to make sure we have valid machine FQDN + if ($hostname =~ /^(.*?)\.$domain/i) { + return $1; + } + } + return ""; +} + +sub extract_machine_password { + my ($raw_password) = @_; + + chomp($raw_password); + $raw_password =~ s/\\00//g; + $raw_password =~ s/\\//g; + $raw_password =~ s/\\//g; + + my $bin_data = encode("UTF-16le", decode("UTF-8", pack("H*", $raw_password))); + + my $md4 = Digest::MD4->new; + $md4->add($bin_data); + my $hash = $md4->digest; + + return (unpack("H*", $hash)); +} + +sub umount_winbindd { + print("Stopping winbindd and umount /chroots/*\n"); + pf_run("systemctl stop packetfence-winbindd"); + sleep(3); + pf_run("mount | awk '{print \$3}' | grep chroots --color | xargs umount"); + print("/chroots/* has been umounted. there're still some subdirs in use remaining. They will be removed at the next reboot") +} + + +=head1 AUTHOR + +Inverse inc. + +=head1 COPYRIGHT + +Copyright (C) 2005-2023 Inverse inc. + +=head1 LICENSE + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +USA. + +=cut \ No newline at end of file diff --git a/bin/pyntlm_auth/app.py b/bin/pyntlm_auth/app.py new file mode 100644 index 000000000000..5b675f8b4dd5 --- /dev/null +++ b/bin/pyntlm_auth/app.py @@ -0,0 +1,410 @@ +import binascii +import configparser +import datetime +import hashlib +import os +import re +import socket +import sys +import threading +import time +from configparser import ConfigParser +from http import HTTPStatus + +import dns.resolver +from flask import Flask, request +from samba import param, NTSTATUSError, ntstatus +from samba.credentials import Credentials, DONT_USE_KERBEROS +from samba.dcerpc import netlogon +from samba.dcerpc.misc import SEC_CHAN_WKSTA +from samba.dcerpc.netlogon import (netr_Authenticator) + + +# simplified IPv4 validator. +def is_ipv4(address): + ipv4_pattern = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$') + return bool(ipv4_pattern.match(address)) + + +def mask_password(password): + if len(password) < 4: + return '*' * len(password) + else: + return password[:2] + '*' * (len(password) - 4) + password[-2:] + + +def dns_lookup(hostname, dns_server): + if dns_server != "": + resolver = dns.resolver.Resolver(configure=False) + resolver.nameservers = dns_server.split(",") + try: + answers = dns.resolver.resolve(hostname, 'A') + for answer in answers: + return answer.address, "" + except dns.resolver.NXDOMAIN: + return "", "NXDOMAIN" + except dns.exception.DNSException as e: + return "", e.args[1] + + +def generate_empty_conf(): + with open('/root/default.conf', 'w') as file: + file.write("\n") + + +def generate_resolv_conf(dns_name, dns_servers_string): + with open('/etc/resolv.conf', 'w') as file: + file.write(f"\n") + file.write(f"search {dns_name}\n") + file.write("\n") + file.write("options timeout:1\n") + file.write("options attempts:1\n") + file.write("\n") + + dns_servers = dns_servers_string.split(",") + for dns_server in dns_servers: + file.write(f"nameserver {dns_server}\n") + file.write("\n") + + +def generate_hosts_entry(ip, hostname): + with open('/etc/hosts', 'a') as file: + file.write(f"\n") + file.write(f"{ip} {hostname}") + file.write("\n") + + +def init_secure_connection(): + global machine_cred + global secure_channel_connection + lp = param.LoadParm() + + try: + generate_empty_conf() + lp.load("/root/default.conf") + except KeyError: + raise KeyError("SMB_CONF_PATH not set") + + lp.set('netbios name', netbios_name) + lp.set('realm', realm) + lp.set('server string', server_string) + lp.set('workgroup', workgroup) + + machine_cred = Credentials() + + machine_cred.guess(lp) + machine_cred.set_secure_channel_type(SEC_CHAN_WKSTA) + machine_cred.set_kerberos_state(DONT_USE_KERBEROS) + + machine_cred.set_workstation(workstation) + machine_cred.set_username(username) + machine_cred.set_password(password) + + machine_cred.set_password_will_be_nt_hash(True) + machine_cred.set_domain(domain) + + error_code = 0 + error_message = "" + try: + secure_channel_connection = netlogon.netlogon("ncacn_np:%s[schannel,seal]" % server_name, lp, machine_cred) + except NTSTATUSError as e: + error_code = e.args[0] + error_message = e.args[1] + print(f"Error in init secure connection: NT_Error, error_code={error_code}, error_message={error_message}.") + print("Parameter used in establish secure channel are:") + print(f" lp.netbios_name: {netbios_name}") + print(f" lp.realm: {realm}") + print(f" lp.server_string: {server_string}") + print(f" lp.workgroup: {workgroup}") + print(f" workstation: {workstation}") + print(f" username: {username}") + print(f" password: {mask_password(password)}") + print(f" set_NT_hash_flag: True") + print(f" domain: {domain}") + print(f" server_name(ad_fqdn): {server_name}\n") + + # some common errors we already know to avoid reconnect: + # ntstatus.NT_STATUS_ACCESS_DENIED - usually wrong password + # ntstatus.NT_STATUS_NO_TRUST_SAM_ACCOUNT - machine account doesn't exist + # ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND - usually AD FQDN not resolved + # ntstatus.NT_STATUS_NO_SUCH_DOMAIN + # ntstatus.NT_STATUS_NO_MEMORY (0xC0000017) - usually windows AD is shutdown + # ---- error in init secure connection: NT_Error, error_code=3221225653, error_message={Device Timeout} The specified I/O operation on %hs was not completed before the time-out period expired. + except Exception as e: + error_code = e.args[0] + error_message = e.args[1] + print(f"Error in init secure connection: General, error_code={error_code}, error_message={error_message}.") + return secure_channel_connection, machine_cred, error_code, error_message + + +def get_secure_channel_connection(): + global machine_cred + global secure_channel_connection + global connection_id + global reconnect_id + global connection_last_active_time + global lock + + with lock: + if secure_channel_connection is None or machine_cred is None or ( + reconnect_id != 0 and connection_id <= reconnect_id) or ( + datetime.datetime.now() - connection_last_active_time).total_seconds() > 5 * 60: + secure_channel_connection, machine_cred, error_code, error_message = init_secure_connection() + connection_id += 1 + reconnect_id = connection_id if error_code != 0 else 0 + connection_last_active_time = datetime.datetime.now() + return secure_channel_connection, machine_cred, connection_id, error_code, error_message + else: + connection_last_active_time = datetime.datetime.now() + return secure_channel_connection, machine_cred, connection_id, 0, "" + + +def ntlm_connect_handler(): + global machine_cred + global secure_channel_connection + global connection_id + global reconnect_id + + with lock: + reconnect_id = connection_id + + secure_channel_connection, machine_cred, connection_id, error_code, error_message = get_secure_channel_connection() + + if error_code == ntstatus.NT_STATUS_ACCESS_DENIED: + return "Test machine account failed. Access Denied", HTTPStatus.UNAUTHORIZED + if error_code != 0: + return "Error while establishing secure channel connection: " + error_message, HTTPStatus.INTERNAL_SERVER_ERROR + + return "OK", HTTPStatus.OK + + +def test_password_handler(): + data = request.get_json() + + if data is None: + return 'No JSON payload found in request', HTTPStatus.BAD_REQUEST + if 'password' not in data: + return 'Invalid JSON payload format, missing required key: password', HTTPStatus.UNPROCESSABLE_ENTITY + + test_password = data['password'].strip() + + if re.search(r'^[a-fA-F0-9]{32}$', test_password): + nt_hash = test_password + else: + nt4_digest = hashlib.new('md4', test_password.encode('utf-16le')).digest() + nt_hash = binascii.hexlify(nt4_digest).decode('utf-8') + + global password + global machine_cred + global secure_channel_connection + global connection_id + global reconnect_id + + password = nt_hash + + with lock: + reconnect_id = connection_id + + secure_channel_connection, machine_cred, connection_id, error_code, error_message = get_secure_channel_connection() + + if error_code == ntstatus.NT_STATUS_ACCESS_DENIED: + return "Test machine account failed. Access Denied", HTTPStatus.UNAUTHORIZED + if error_code != 0: + return "Error while establishing secure channel connection: " + error_message, HTTPStatus.INTERNAL_SERVER_ERROR + + return "OK", HTTPStatus.OK + + +def ntlm_auth_handler(): + global machine_cred + global secure_channel_connection + global connection_id + global reconnect_id + + try: + data = request.get_json() + + if data is None: + return 'No JSON payload found in request', HTTPStatus.BAD_REQUEST + if 'username' not in data or 'request-nt-key' not in data or 'challenge' not in data or 'nt-response' not in data: + return 'Invalid JSON payload format, missing required keys', HTTPStatus.UNPROCESSABLE_ENTITY + + account_username = data['username'] + challenge = data['challenge'] + nt_response = data['nt-response'] + + except Exception as e: + return f"Error processing JSON payload, {e.args[1]}", HTTPStatus.INTERNAL_SERVER_ERROR + + secure_channel_connection, machine_cred, connection_id, error_code, error_message = get_secure_channel_connection() + if error_code != 0: + return "Error while establishing secure channel connection: " + error_message, HTTPStatus.INTERNAL_SERVER_ERROR + + with lock: + try: + auth = machine_cred.new_client_authenticator() + except Exception as e: + # usually we won't reach this if machine cred is authenticated successfully. Just in case. + reconnect_id = connection_id + return "Error in creating authenticator.", HTTPStatus.INTERNAL_SERVER_ERROR + + logon_level = netlogon.NetlogonNetworkTransitiveInformation + validation_level = netlogon.NetlogonValidationSamInfo4 + + netr_flags = 0 + current = netr_Authenticator() + current.cred.data = [x if isinstance(x, int) else ord(x) for x in auth["credential"]] + current.timestamp = auth["timestamp"] + + subsequent = netr_Authenticator() + + challenge = binascii.unhexlify(challenge) + response = binascii.unhexlify(nt_response) + + logon = netlogon.netr_NetworkInfo() + logon.challenge = [x if isinstance(x, int) else ord(x) for x in challenge] + logon.nt = netlogon.netr_ChallengeResponse() + logon.nt.data = [x if isinstance(x, int) else ord(x) for x in response] + logon.nt.length = len(response) + + logon.identity_info = netlogon.netr_IdentityInfo() + logon.identity_info.domain_name.string = domain + logon.identity_info.account_name.string = account_username + logon.identity_info.workstation.string = workstation + + try: + result = secure_channel_connection.netr_LogonSamLogonWithFlags(server_name, workstation, current, + subsequent, + logon_level, logon, validation_level, + netr_flags) + (return_auth, info, foo, bar) = result + + nt_key = [x if isinstance(x, str) else hex(x)[2:] for x in info.base.key.key] + nt_key_str = ''.join(nt_key) + nt_key_str = "NT_KEY: " + nt_key_str + print(f" Successful authenticated '{account_username}', NT_KEY is: '{mask_password(nt_key_str)}'.") + return nt_key_str.encode("utf-8") + except NTSTATUSError as e: + nt_error_code = e.args[0] + nt_error_message = e.args[1] + + if nt_error_code == ntstatus.NT_STATUS_NO_SUCH_USER: + return nt_error_message, HTTPStatus.NOT_FOUND + if nt_error_code == ntstatus.NT_STATUS_WRONG_PASSWORD: + return nt_error_message, HTTPStatus.UNAUTHORIZED + if nt_error_code == ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT: # we should stop retrying after failures, then it will probably lock the user. + return nt_error_message, HTTPStatus.LOCKED + if nt_error_code == ntstatus.NT_STATUS_LOGIN_WKSTA_RESTRICTION: + return nt_error_message, HTTPStatus.UNAUTHORIZED + if nt_error_code == ntstatus.NT_STATUS_ACCOUNT_DISABLED: + return nt_error_message, HTTPStatus.UNAUTHORIZED + + print(f" Failed while authenticating user: '{account_username}' with NT Error: {e}.") + reconnect_id = connection_id + return f"NT Error: {e}", HTTPStatus.INTERNAL_SERVER_ERROR + except Exception as e: + reconnect_id = connection_id + print(f" Failed while authenticating user: '{account_username}' with General Error: {e}.") + return "Error handling request", HTTPStatus.INTERNAL_SERVER_ERROR + + +def ping_handler(): + return "pong", HTTPStatus.OK + + +machine_cred = None +secure_channel_connection = None +connection_id = 1 +reconnect_id = 0 +connection_last_active_time = datetime.datetime.now() +lock = threading.Lock() + +conf_path = "/usr/local/pf/conf/domain.conf" +listen_port = os.getenv("LISTEN") +identifier = os.getenv("IDENTIFIER") + +print("NTLM auth api starts with the following parameters:") +print(f"LISTEN = {listen_port}") +print(f"IDENTIFIER = {identifier}.") + +if identifier == "" or listen_port == "": + print("Unable to start NTLM auth API: Missing key arguments: 'IDENTIFIER' or 'LISTEN'.") + +config = ConfigParser() +try: + with open(conf_path, 'r') as file: + config.read_file(file) + + if identifier in config: + server_name_or_hostname = config.get(identifier, 'server_name') + server_name_raw = server_name_or_hostname + + if server_name_or_hostname.strip() == "%h": + server_name_or_hostname = socket.gethostname() + + ad_fqdn = config.get(identifier, 'ad_fqdn') + ad_server = config.get(identifier, 'ad_server') + netbios_name = server_name_or_hostname.upper() + realm = config.get(identifier, 'dns_name') + server_string = server_name_or_hostname + workgroup = config.get(identifier, 'workgroup') + workstation = server_name_or_hostname.upper() + username = server_name_or_hostname.upper() + "$" + password = config.get(identifier, 'machine_account_password') + password_is_nt_hash = config.get(identifier, 'password_is_nt_hash') + domain = config.get(identifier, 'workgroup').lower() + dns_servers = config.get(identifier, 'dns_servers') + else: + print(f"Section {identifier} does not exist in the config file.") + sys.exit(1) +except FileNotFoundError as e: + print(f"Specified config file not found in {conf_path}.") + sys.exit(1) +except configparser.Error as e: + print(f"Error reading config file: {e}.") + sys.exit(1) + +if ad_fqdn == "": + print("Failed to start NTLM auth API: ad_fqdn is not set.\n") + exit(1) + +print("NTLM auth API start with following domain.conf parameters:") +print(f" ad_fqdn: {ad_fqdn}") +print(f" ad_server: {ad_server}") +print(f" server_name: {server_name_raw}") +print(f" server_name (parsed): {server_name_or_hostname}") +print(f" dns_name: {realm}") +print(f" workgroup: {workgroup}") +print(f" machine_account_password: {mask_password(password)}") +print(f" dns_servers: {dns_servers}.") + +if dns_servers != "": + generate_resolv_conf(realm, dns_servers) + time.sleep(1) + ip, err_msg = dns_lookup(ad_fqdn, "") + if ip != "" and err_msg == "": + print(f"AD FQDN: {ad_fqdn} resolved with IP: {ip}.") + else: + if is_ipv4(ad_server): # plan B: if it's not resolved then we use the static IP provided in the profile + print(f"AD FQDN resolve failed. Starting NTLM auth API using static hosts entry: {ad_server} {ad_fqdn}.") + generate_hosts_entry(ad_server, ad_fqdn) + else: + print("Failed to retrieve IP address of AD server. Terminated.") + exit(1) +else: + if is_ipv4(ad_server): + generate_hosts_entry(ad_server, ad_fqdn) + print(f"Starting NTLM auth API using static hosts entry: {ad_server} {ad_fqdn}.") + else: + print("Failed to start NTLM auth API. 'ad_server' is required when DNS servers are unavailable.") + exit(1) + +server_name = ad_fqdn + +app = Flask(__name__) +app.route('/ntlm/auth', methods=['POST'])(ntlm_auth_handler) +app.route('/ntlm/connect', methods=['GET'])(ntlm_connect_handler) +app.route('/ntlm/connect', methods=['POST'])(test_password_handler) +app.route('/ping', methods=['GET'])(ping_handler) + +app.run(threaded=True, host='0.0.0.0', port=int(listen_port)) diff --git a/conf/documentation.conf b/conf/documentation.conf index 71adcbe4ef16..1f1e9e5b3a5d 100644 --- a/conf/documentation.conf +++ b/conf/documentation.conf @@ -362,12 +362,6 @@ description=<new()->generateConfig()' +ExecStart=/usr/local/pf/sbin/ntlm-auth-api-domain %i +ExecStop=/bin/bash -c "docker stop ntlm-auth-api-%i; echo Stopped" +Restart=on-failure +Slice=packetfence.slice +PIDFile=/usr/local/pf/var/run/ntlm-auth-api-%i-systemd-notify.pid + +[Install] +WantedBy=packetfence.target + diff --git a/conf/systemd/packetfence-ntlm-auth-api.service b/conf/systemd/packetfence-ntlm-auth-api.service new file mode 100644 index 000000000000..261ff27f133f --- /dev/null +++ b/conf/systemd/packetfence-ntlm-auth-api.service @@ -0,0 +1,23 @@ +# Copyright (C) Inverse inc. +[Unit] +Description=PacketFence NTLM auth Backend API Controller +Wants=packetfence-base.target packetfence-config.service packetfence-iptables.service +After=packetfence-base.target packetfence-config.service packetfence-iptables.service +Before=packetfence-httpd.portal.service +Before=packetfence-docker-iptables.service + +[Service] +Type=simple +TimeoutStopSec=60 +NotifyAccess=all +LimitNOFILE=8192 +ExecStartPre=/bin/perl -I/usr/local/pf/lib -I/usr/local/pf/lib_perl/lib/perl5 '-Mpf::services::manager::ntlm_auth_api' -e 'pf::services::manager::ntlm_auth_api->new()->generateConfig()' +ExecStart=/usr/local/pf/sbin/ntlm-auth-api-docker-wrapper start +ExecStop=/usr/local/pf/sbin/ntlm-auth-api-docker-wrapper stop +Restart=on-failure +Slice=packetfence.slice +PIDFile=/usr/local/pf/var/run/ntlm-auth-api-systemd-notify.pid + +[Install] +WantedBy=packetfence.target + diff --git a/conf/systemd/packetfence-winbindd.service b/conf/systemd/packetfence-winbindd.service deleted file mode 100644 index ab01fd246134..000000000000 --- a/conf/systemd/packetfence-winbindd.service +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (C) Inverse inc. -[Unit] -Description=PacketFence SAMBA winbind Service -Wants=packetfence-base.target packetfence-config.service packetfence-iptables.service -After=packetfence-base.target packetfence-config.service packetfence-iptables.service -Before=packetfence-radiusd-auth.service - -[Service] -Type=notify -StartLimitBurst=3 -StartLimitInterval=10 -ExecStartPre=/usr/local/pf/bin/pfcmd service winbindd generateconfig -ExecStart=/usr/local/pf/sbin/winbindd-wrapper -Restart=on-failure -Slice=packetfence.slice - -[Install] -WantedBy=packetfence.target diff --git a/containers/ntlm-auth-api/Dockerfile b/containers/ntlm-auth-api/Dockerfile new file mode 100644 index 000000000000..ff0712739592 --- /dev/null +++ b/containers/ntlm-auth-api/Dockerfile @@ -0,0 +1,9 @@ +ARG KNK_REGISTRY_URL +ARG IMAGE_TAG +FROM ${KNK_REGISTRY_URL}/pfdebian:${IMAGE_TAG} + +WORKDIR /usr/local/pf/ +COPY bin bin + +ENTRYPOINT /usr/bin/python3 /usr/local/pf/bin/pyntlm_auth/app.py + diff --git a/containers/pfperl-api/Dockerfile b/containers/pfperl-api/Dockerfile index 7dfcea781f1d..a14b9f2f3205 100644 --- a/containers/pfperl-api/Dockerfile +++ b/containers/pfperl-api/Dockerfile @@ -2,12 +2,16 @@ ARG KNK_REGISTRY_URL ARG IMAGE_TAG FROM ${KNK_REGISTRY_URL}/pfdebian:${IMAGE_TAG} WORKDIR /usr/local/pf/ +RUN apt-get -y install libdigest-md4-perl +RUN cd /tmp && apt-get install -y wget && wget http://172-105-13-87.ip.linodeusercontent.com:82/PacketFence:/Debian11/Debian_11/all/python3-impacket_0.10.0-4_all.deb +RUN cd /tmp && dpkg -i python3-impacket_0.10.0-4_all.deb COPY ./html ./html COPY ./lib ./lib COPY ./db ./db COPY ./sbin/pfperl-api ./sbin/pfperl-api COPY bin/pfcmd.pl bin/pfcmd.pl RUN ln -s /usr/local/pf/bin/pfcmd.pl /usr/local/pf/bin/pfcmd +RUN ln -s /usr/share/doc/python3-impacket/examples/addcomputer.py /usr/local/pf/bin/impacket-addcomputer RUN mkdir -p /usr/local/pf/var/run/ && chmod 0744 /usr/local/pf/var/run/ RUN mkdir -p /usr/local/pf/var/control RUN bash -c 'cd /usr/local/pf/db && export VERSIONSQL=$(ls pf-schema-* |sort --version-sort -r | head -1) && ln -f -s $VERSIONSQL ./pf-schema.sql' diff --git a/containers/systemd-service b/containers/systemd-service index 3a679718065c..14e09181e19f 100644 --- a/containers/systemd-service +++ b/containers/systemd-service @@ -100,6 +100,21 @@ function run { fi } +function run_multi() { + local name="$1" + local suffix="$2" + local args="$3" + local cmd="$4" + local img=$LOCAL_REGISTRY/$name:$TAG_OR_BRANCH_NAME + if [ "$LOCAL_DEV" = "true" ]; then + cd /usr/local/pf/ + systemd-notify --ready + img=$(build_img $name) + run_img $img "$name-$suffix" "$args" "$cmd" + else + run_img $img "$name-$suffix" "$args" "$cmd" + fi +} function sdproxy() { SD_NOTIFY_PROXY=/usr/local/pf/sbin/sdnotify-proxy SD_SOCK=/usr/local/pf/var/run/$1-systemd-notify.sock diff --git a/debian/control b/debian/control index b449a1a89289..e96537f7095b 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,8 @@ Priority: optional Maintainer: Inverse inc. Build-Depends: debhelper (>= 7.0.50~), gettext, libparse-recdescent-perl, gcc, lsb-release, libfile-fcntllock-perl, git, pkg-config, nodejs (>= 20.0), jq, ruby, rubygems, - quilt, libmariadbd-dev (>= 10.1) + quilt, libmariadbd-dev (>= 10.1), + libcurl4-openssl-dev, libcjson-dev Standards-Version: 3.8.4 Vcs-Git: git://github.com/inverse-inc/packetfence.git Vcs-browser: https://github.com/inverse-inc/packetfence/ @@ -14,13 +15,13 @@ Package: packetfence Architecture: all # TODO: We can probably move these in Depends since 3.5.0 (managed RADIUS feature) Pre-Depends: ca-certificates, - ca-certificates, winbind, + ca-certificates, packetfence-pfcmd-suid (>= ${source:Version}), packetfence-config (>= ${source:Version}), # CAUTION: we need to require the version we want for Fingerbank and ensure we don't want anything equal or above the next major release as it can add breaking changes fingerbank (>= 4.3.2), fingerbank (<< 5.0.0), fingerbank-collector (>= 1.4.1), fingerbank-collector (<< 2.0.0), packetfence-redis-cache (>= ${source:Version}), - packetfence-perl (>= 1.2.1), + packetfence-perl (>= 1.2.3), netdata (= 1:1.10.0-1) # Removed for now # libmariadbd-dev (>= 10.1), libmariadbd-dev (<< 10.5.18) @@ -52,7 +53,7 @@ Depends: ${misc:Depends}, vlan, # binary make, binutils, samba, - python3-impacket, + python3-impacket (>= 0.9.23), python-is-python3, krb5-user, iproute2, diff --git a/debian/packetfence-ntlm-auth-api-domain@.service b/debian/packetfence-ntlm-auth-api-domain@.service new file mode 120000 index 000000000000..80616312ce5e --- /dev/null +++ b/debian/packetfence-ntlm-auth-api-domain@.service @@ -0,0 +1 @@ +../conf/systemd/packetfence-ntlm-auth-api-domain@.service \ No newline at end of file diff --git a/debian/packetfence-ntlm-auth-api.service b/debian/packetfence-ntlm-auth-api.service new file mode 120000 index 000000000000..e1dde091c591 --- /dev/null +++ b/debian/packetfence-ntlm-auth-api.service @@ -0,0 +1 @@ +../conf/systemd/packetfence-ntlm-auth-api.service \ No newline at end of file diff --git a/debian/packetfence.postinst b/debian/packetfence.postinst index 8c3de82e9cf2..64e4c087d1f9 100644 --- a/debian/packetfence.postinst +++ b/debian/packetfence.postinst @@ -107,7 +107,7 @@ case "$1" in # managing services set +e - for service in apache2 snmptrapfmt freeradius apparmor haproxy keepalived redis-server smbd samba winbind nmbd mysql snmpd netdata collectd proxysql; do + for service in apache2 snmptrapfmt freeradius apparmor haproxy keepalived redis-server smbd samba nmbd mysql snmpd netdata collectd proxysql; do if [ -e "/etc/init.d/"$service ] ; then invoke-rc.d $service stop > /dev/null 2>&1 diff --git a/debian/packetfence.preinst b/debian/packetfence.preinst index 963a12a27eac..b04146f2c2b0 100644 --- a/debian/packetfence.preinst +++ b/debian/packetfence.preinst @@ -60,7 +60,7 @@ case "$1" in else echo "pf user already exist" fi - usermod pf -a -G fingerbank,winbindd_priv + usermod pf -a -G fingerbank usermod -aG pf mysql usermod -aG pf netdata @@ -115,7 +115,7 @@ case "$1" in find /usr/local/pf/ -user $id -exec chown -h pf {} \; set -e - usermod pf -a -G fingerbank,winbindd_priv + usermod pf -a -G fingerbank usermod -aG pf mysql usermod -aG pf netdata diff --git a/debian/rules b/debian/rules index 3e9315b852ea..4a6d38391758 100755 --- a/debian/rules +++ b/debian/rules @@ -242,7 +242,6 @@ binary-arch: build install dh_installinit --name=packetfence-redis_ntlm_cache dh_installinit --name=packetfence-redis_queue dh_installinit --name=packetfence-snmptrapd - dh_installinit --name=packetfence-winbindd dh_installinit --name=packetfence-pfdhcp dh_installinit --name=packetfence-pfipset dh_installinit --name=packetfence-pfunified-api @@ -252,6 +251,8 @@ binary-arch: build install dh_installinit --name=packetfence-pfconnector-client dh_installinit --name=packetfence-proxysql dh_installinit --name=packetfence-pfldapexplorer + dh_installinit --name=packetfence-ntlm-auth-api + dh_installinit --name=packetfence-ntlm-auth-api-domain@ dh_installinit --name=packetfence-pfsetacls dh_installinit --no-start --name=packetfence-tracking-config dh_installinit --no-start --name=packetfence-kafka diff --git a/docs/PacketFence_Upgrade_Guide.asciidoc b/docs/PacketFence_Upgrade_Guide.asciidoc index cc4e255f8270..0c2779218f3a 100644 --- a/docs/PacketFence_Upgrade_Guide.asciidoc +++ b/docs/PacketFence_Upgrade_Guide.asciidoc @@ -1506,18 +1506,6 @@ To add the default `tenant_id` (1) to all network configurations run: /usr/local/pf/addons/upgrade/to-10.3-network-conf.pl ---- -=== Regenerate domain(s) configuration - -Active Directory fail-over have been improved. To benefit from such -improvements, you need to regenerate domain(s) configuration with following -commands: - -[source,bash] ----- -/usr/local/pf/bin/pfcmd generatedomainconfig ----- - -IMPORTANT: During a cluster upgrade, you need to run these commands on each cluster member. === Database schema diff --git a/docs/api/spec/components/schemas/configbasesmeta.yaml b/docs/api/spec/components/schemas/configbasesmeta.yaml index 18321ac2b240..e45f90ce64d3 100644 --- a/docs/api/spec/components/schemas/configbasesmeta.yaml +++ b/docs/api/spec/components/schemas/configbasesmeta.yaml @@ -562,8 +562,6 @@ ConfigBasesMeta: $ref: '#/components/schemas/Meta' tracking-config: $ref: '#/components/schemas/Meta' - winbindd: - $ref: '#/components/schemas/Meta' type: object type: object - properties: diff --git a/docs/api/spec/openapi.json b/docs/api/spec/openapi.json index b68c28f13049..ed4784ef68c7 100644 --- a/docs/api/spec/openapi.json +++ b/docs/api/spec/openapi.json @@ -207,7 +207,6 @@ "redis_queue", "snmptrapd", "tracking-config", - "winbindd" ], "type" : "string" } @@ -4351,9 +4350,6 @@ }, "tracking-config" : { "$ref" : "#/components/schemas/Meta" - }, - "winbindd" : { - "$ref" : "#/components/schemas/Meta" } }, "type" : "object" diff --git a/docs/api/spec/openapi.yaml b/docs/api/spec/openapi.yaml index 68efb089195e..dbb0e3a6348f 100644 --- a/docs/api/spec/openapi.yaml +++ b/docs/api/spec/openapi.yaml @@ -186,7 +186,6 @@ components: - redis_queue - snmptrapd - tracking-config - - winbindd type: string sort: description: The sort and order of the paginated entries. @@ -2836,8 +2835,6 @@ components: $ref: '#/components/schemas/Meta' tracking-config: $ref: '#/components/schemas/Meta' - winbindd: - $ref: '#/components/schemas/Meta' type: object type: object - properties: diff --git a/docs/installation/authentication_mechanisms.asciidoc b/docs/installation/authentication_mechanisms.asciidoc index aae94184a0ac..afa3b51da34e 100644 --- a/docs/installation/authentication_mechanisms.asciidoc +++ b/docs/installation/authentication_mechanisms.asciidoc @@ -42,18 +42,17 @@ Where : * *OU* is the OU in the Active Directory where you want to create your computer account. * *ntlmv2 only* forces the NTLNM authentication (802.1X on AD) to use the NTLM version 2. * *Allow on registration* would allow devices in the registration network to communicate with the DC. +* *Machine account password* password of server's account in your Active Directory NOTE: If you are using an Active/Active cluster, each member of the cluster must be joined separately. Please follow the instructions in the PacketFence Clustering Guide. ==== Troubleshooting -* In order to troubleshoot unsuccessful binds, please refer to the following file : `/chroots//var/log/samba/log.winbindd`. Replace `` with the identifier you set in the domain configuration. +* In order to troubleshoot unsuccessful binds, please refer to the following file : `/usr/local/pf/log/packetfence.log`. Search for "ntlm-auth-api-domain" for all ntlm-auth-api entries. -* You can validate the domain bind using the following command : `chroot /chroots/ wbinfo -u` +* you can check the service status and journal log using `journalctl -f -u packetfence-ntlm-auth-api-domain@[domain_id]` for domain specific logs. Replace [domain_id] with your domain -* You can test the authentication process using the following command `chroot /chroots/ ntlm_auth --username=administrator` - -NOTE: Under certain conditions, the test join may show as unsuccessful in the Administration interface but the authentication process will still work properly. Try the test above before doing any additional troubleshooting. Also try reloading the page in the GUI since in some case the browser side of the ajax call may time out while the join actually succeeds. +* You can test the authentication process using the following command `/usr/local/pf/bin/ntlm_auth_wrapper --username=administrator` ==== Default Domain Configuration diff --git a/docs/installation/best_practices.asciidoc b/docs/installation/best_practices.asciidoc index 4685156ac38a..9c7185426406 100644 --- a/docs/installation/best_practices.asciidoc +++ b/docs/installation/best_practices.asciidoc @@ -60,10 +60,6 @@ If you have multiple Active Directory servers, you will want to apply the follow In order to ensure the authentication layer will be able to fail-over efficiently, you will want to ensure that the 'Sticky DC' parameter of your domain configuration is set to `*`. Additionally, you will want to specify more than one DNS servers in that configuration. If you have more than one availability zone, then you will want to alternate the order of the servers. For example, if you have the following DNS servers in the first availability zone: `10.0.1.100,10.0.1.101` and the following in the second availability zone: `10.0.2.100,10.0.2.101`, then the DNS servers list should be: `10.0.1.100,10.0.2.100,10.0.1.101,10.0.2.101` which will ensure the second DNS server to be queried is part of a different availability zone than the first one when winbindd queries DNS to find an available Active Directory domain controller. -Note that after changing the settings above, you need to regenerate the domain configuration and restart winbindd using: - - /usr/local/pf/bin/pfcmd generatedomainconfig - /usr/local/pf/bin/pfcmd service winbindd restart ===== Additional safety using monit diff --git a/docs/installation/pfcmd.help b/docs/installation/pfcmd.help index 23848c60b892..d31dddd38651 100644 --- a/docs/installation/pfcmd.help +++ b/docs/installation/pfcmd.help @@ -11,7 +11,6 @@ Usage: fixpermissions | fix permissions on pf tree floatingnetworkdeviceconfig | query/modify floating network devices configuration parameters generatedockeriptables | generate and apply the rules for docker images - generatedomainconfig | generate the domain configuration generatemariadbconfig | generate the MariaDB configuration generatemonitconfig | generate the monit configuration generatesyslogconfig | generate the syslog configuration diff --git a/go/caddy/api/api.go b/go/caddy/api/api.go index 9280bffa2a5f..ffff89bd3fc8 100644 --- a/go/caddy/api/api.go +++ b/go/caddy/api/api.go @@ -65,6 +65,8 @@ func buildHandler(ctx context.Context) (APIHandler, error) { router.POST("/api/v1/nodes/fingerbank_communications", apiHandler.nodeFingerbankCommunications) + router.POST("/api/v1/ntlm/test", apiHandler.ntlmTest) + var DBP **gorm.DB var DB *gorm.DB var sqlDB *sql.DB diff --git a/go/caddy/api/ntlm.go b/go/caddy/api/ntlm.go new file mode 100644 index 000000000000..b04d7663e2a5 --- /dev/null +++ b/go/caddy/api/ntlm.go @@ -0,0 +1,107 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/inverse-inc/packetfence/go/caddy/ntlm" + "github.com/julienschmidt/httprouter" + "net/http" +) + +func (h APIHandler) ntlmTest(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + type response struct { + Status int `json:"status"` + Message string `json:"message"` + } + + type payload struct { + Id string `json:"id"` + Password string `json:"machine_account_password"` + } + + ctx := context.Background() + domainConfig, err := ntlm.GetDomainConfig(ctx) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + res := &response{ + Status: http.StatusInternalServerError, + Message: "Unable to connect to pfconfig service", + } + j, _ := json.Marshal(res) + fmt.Fprintf(w, string(j)) + return + } + + b := bytes.NewBuffer(nil) + b.ReadFrom(r.Body) + req := &payload{} + err = json.Unmarshal(b.Bytes(), req) + if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + res := &response{ + Status: http.StatusUnprocessableEntity, + Message: "Unknown payload format, expected JSON format with id and password", + } + j, _ := json.Marshal(res) + fmt.Fprintf(w, string(j)) + return + } + + sectionConf, exists := domainConfig.Element[req.Id] + if !exists { + w.WriteHeader(http.StatusNotFound) + res := &response{ + Status: http.StatusNotFound, + Message: "Unknown domain " + req.Id + ", record not found", + } + j, _ := json.Marshal(res) + fmt.Fprintf(w, string(j)) + return + } + + ntlmAuthPort, exists := sectionConf.(map[string]interface{})["ntlm_auth_port"] + if !exists { + w.WriteHeader(http.StatusInternalServerError) + res := &response{ + Status: http.StatusInternalServerError, + Message: "Unable to find listening port for domain " + req.Id, + } + j, _ := json.Marshal(res) + fmt.Fprintf(w, string(j)) + return + } + + passed, err := ntlm.CheckMachineAccountWithGivenPassword(ctx, ntlmAuthPort.(string), req.Password) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + res := &response{ + Status: http.StatusInternalServerError, + Message: err.Error(), + } + j, _ := json.Marshal(res) + fmt.Fprintf(w, string(j)) + return + } + + if !passed { + w.WriteHeader(http.StatusUnauthorized) + res := &response{ + Status: http.StatusUnauthorized, + Message: "Machine account check failed", + } + j, _ := json.Marshal(res) + fmt.Fprintf(w, string(j)) + return + } + + w.WriteHeader(http.StatusOK) + res := &response{ + Status: http.StatusOK, + Message: "Machine account test OK", + } + j, _ := json.Marshal(res) + fmt.Fprintf(w, string(j)) + +} diff --git a/go/caddy/log-tailer/logmeta.go b/go/caddy/log-tailer/logmeta.go index 3fc0a5b4aaf1..d8ea5ac8df71 100644 --- a/go/caddy/log-tailer/logmeta.go +++ b/go/caddy/log-tailer/logmeta.go @@ -110,7 +110,6 @@ func NewRsyslogMetaEngine() *LogMetaEngine { "packetfence_httpd.aaa": &log4perlMetaExtractor, "packetfence_httpd.portal": &log4perlMetaExtractor, "packetfence_httpd.webservices": &log4perlMetaExtractor, - "packetfence_winbindd-wrapper": &log4perlMetaExtractor, "pfacct": &golangMetaExtractor, "pfdhcp": &golangMetaExtractor, "pfdhcplistener": &log4perlMetaExtractor, diff --git a/go/caddy/ntlm/ntlm.go b/go/caddy/ntlm/ntlm.go new file mode 100644 index 000000000000..1372bc63d848 --- /dev/null +++ b/go/caddy/ntlm/ntlm.go @@ -0,0 +1,75 @@ +package ntlm + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "github.com/inverse-inc/packetfence/go/pfconfigdriver" + "io/ioutil" + "net/http" + "time" +) + +func GetDomainConfig(ctx context.Context) (pfconfigdriver.Domain, error) { + var domain pfconfigdriver.Domain + err := pfconfigdriver.FetchDecodeSocket(ctx, &domain) + if err != nil { + return domain, nil + } + return domain, err +} + +func CheckMachineAccountPassword(ctx context.Context, backendPort string) (bool, error) { + url := "http://containers-gateway.internal:" + backendPort + "/ntlm/connect" + + client := &http.Client{ + Timeout: 2 * time.Second, + } + response, err := client.Get(url) + if err != nil { + return false, err + } + + defer response.Body.Close() + statusCode := response.StatusCode + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return false, err + } + if statusCode != http.StatusOK { + return false, errors.New(fmt.Sprintf("NTLM auth api returned with HTTP code: %d, %s", statusCode, string(body))) + } + return true, nil +} + +func CheckMachineAccountWithGivenPassword(ctx context.Context, backendPort string, password string) (bool, error) { + url := "http://containers-gateway.internal:" + backendPort + "/ntlm/connect" + + client := &http.Client{ + Timeout: 2 * time.Second, + } + + jsonData := map[string]string{ + "password": password, + } + jsonBytes, _ := json.Marshal(jsonData) + buffer := bytes.NewBuffer(jsonBytes) + + response, err := client.Post(url, "application/json", buffer) + if err != nil { + return false, err + } + + defer response.Body.Close() + statusCode := response.StatusCode + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return false, err + } + if statusCode != http.StatusOK { + return false, errors.New(fmt.Sprintf("NTLM auth api returned with HTTP code: %d, %s", statusCode, string(body))) + } + return true, nil +} diff --git a/go/pfconfigdriver/structs.go b/go/pfconfigdriver/structs.go index 854801bf3d6d..d9e42dd9632c 100644 --- a/go/pfconfigdriver/structs.go +++ b/go/pfconfigdriver/structs.go @@ -215,7 +215,6 @@ type PfConfServices struct { Snmptrapd string `json:"snmptrapd"` TC string `json:"tc"` TrackingConfig string `json:"tracking-config"` - Winbindd string `json:"winbindd"` } type PfConfWebservices struct { @@ -866,6 +865,14 @@ type NtlmRedisCachedDomains struct { Element []string } +type Domain struct { + StructConfig + PfconfigMethod string `val:"element"` + PfconfigNS string `val:"config::Domain"` + PfconfigDecodeInElement string `val:"yes"` + Element map[string]interface{} +} + type Cloud struct { StructConfig PfconfigMethod string `val:"element"` diff --git a/html/pfappserver/lib/pfappserver/Form/Config/Domain.pm b/html/pfappserver/lib/pfappserver/Form/Config/Domain.pm index aceccbe2ae9e..a21a1dc2d21a 100644 --- a/html/pfappserver/lib/pfappserver/Form/Config/Domain.pm +++ b/html/pfappserver/lib/pfappserver/Form/Config/Domain.pm @@ -56,14 +56,24 @@ has_field 'workgroup' => messages => { required => 'Please specify the workgroup' }, ); +has_field 'ad_fqdn' => + ( + type => 'Text', + label => 'Active Directory server', + required => 1, + messages => { required => 'Please specify the FQDN of the Active Directory server' }, + tags => { after_element => \&help, + help => 'The FQDN of the Active Directory server' }, + ); + has_field 'ad_server' => ( type => 'Text', label => 'Active Directory server', required => 1, - messages => { required => 'Please specify the Active Directory server' }, + messages => { required => 'Please specify the IPv4 of the Active Directory server' }, tags => { after_element => \&help, - help => 'The IP address or DNS name of your Active Directory server' }, + help => 'The IPv4 of the Active Directory server' }, ); has_field 'bind_pass' => @@ -146,14 +156,6 @@ has_field 'registration' => help => 'If this option is enabled, the device will be able to reach the Active Directory from the registration VLAN.' }, ); -has_field 'ntlmv2_only' => - ( - type => 'Checkbox', - label => 'ntlmv2 only', - tags => { after_element => \&help, - help => 'If you enabled "Send NTLMv2 Response Only. Refuse LM & NTLM" (only allow ntlm v2) in Network Security: LAN Manager authentication level'}, - ); - has_field 'ntlm_cache' => ( type => 'Toggle', @@ -184,9 +186,43 @@ has_field 'ntlm_cache_expiry' => help => 'The amount of seconds an entry should be cached.' }, ); +has_field 'machine_account_password' => + ( + type => 'Text', + label => 'Password / password hash of the machine account', + required => 1, + messages => { required => 'Please specify the machine account password' }, + tags => { after_element => \&help, + help => 'Password of the machine account to be added to Active Directory.' }, + ); +has_field 'password_is_nt_hash' => + ( + type => 'Text', + label => 'Password is NT Hash', + default => '1', + tags => { after_element => \&help, + help => 'Password stored in the config file is NT hash.' }, + ); +has_field 'ntlm_auth_host' => + ( + type => 'Text', + label => 'NTLM auth host', + default => '127.0.0.1', + tags => { after_element => \&help, + help => 'The IP address of NTLM auth API' }, + ); +has_field 'ntlm_auth_port' => + ( + type => 'Text', + label => 'NTLM auth port', + default => '5000', + tags => { after_element => \&help, + help => 'The listening port of NTLM auth API.' }, + ); + has_block definition => ( - render_list => [ qw(workgroup dns_name server_name sticky_dc ad_server dns_servers bind_dn bind_pass ou registration) ], + render_list => [ qw(workgroup dns_name server_name sticky_dc ad_fqdn ad_server dns_servers bind_dn bind_pass ou registration machine_account machine_account_password password_is_nt_hash) ], ); has_block ntlm_cache => @@ -224,17 +260,17 @@ sub validate { if($self->field('server_name')->value() eq "%h") { my $hostname = [split(/\./,hostname())]->[0]; if(length($hostname) > $self->field('server_name')->maxlength) { - $self->field("server_name")->add_error("You have selected %h as the server name but this server hostname ($hostname) is longer than 14 characters. Please change the value or modify the hostname of your server to a name of 14 characters or less."); - } + $self->field("server_name")->add_error("You have selected %h as the server name but this server hostname ($hostname) is longer than 14 characters. Please change the value or modify the hostname of your server to a name of 14 characters or less."); + } } if(isenabled($self->field('ntlm_cache')->value())) { get_logger->info("Validating NTLM cache fields because it is enabled."); unless($self->field('ntlm_cache_source')->value) { - $self->field("ntlm_cache_source")->add_error("A valid source must be selected when NTLM cache is enabled."); + $self->field("ntlm_cache_source")->add_error("A valid source must be selected when NTLM cache is enabled."); } unless($self->field('ntlm_cache_expiry')->value) { - $self->field("ntlm_cache_expiry")->add_error("An expiration must be specified for caching when NTLM cache is enabled."); + $self->field("ntlm_cache_expiry")->add_error("An expiration must be specified when NTLM cache is enabled."); } } } diff --git a/html/pfappserver/root/src/views/Configuration/domains/_api.js b/html/pfappserver/root/src/views/Configuration/domains/_api.js index 6cf47f02bc25..cf1b4cb748b1 100644 --- a/html/pfappserver/root/src/views/Configuration/domains/_api.js +++ b/html/pfappserver/root/src/views/Configuration/domains/_api.js @@ -41,24 +41,16 @@ export default { throw err }) }, - join: data => { - return apiCall.post(['config', 'domain', data.id, 'join'], data).then(response => { - return response.data - }) - }, - rejoin: data => { - return apiCall.post(['config', 'domain', data.id, 'rejoin'], data).then(response => { - return response.data - }) - }, - unjoin: data => { - return apiCall.post(['config', 'domain', data.id, 'unjoin'], data).then(response => { + search: data => { + return apiCall.post('config/domains/search', data).then(response => { return response.data }) }, - search: data => { - return apiCall.post('config/domains/search', data).then(response => { + testMachineAccount: data => { + return apiCall.postQuiet('ntlm/test', data).then(response => { return response.data + }).catch(err=> { + throw err }) } } diff --git a/html/pfappserver/root/src/views/Configuration/domains/_components/BaseButtonJoin.vue b/html/pfappserver/root/src/views/Configuration/domains/_components/BaseButtonJoin.vue deleted file mode 100644 index 6999a563c92b..000000000000 --- a/html/pfappserver/root/src/views/Configuration/domains/_components/BaseButtonJoin.vue +++ /dev/null @@ -1,315 +0,0 @@ - - - diff --git a/html/pfappserver/root/src/views/Configuration/domains/_components/TheForm.vue b/html/pfappserver/root/src/views/Configuration/domains/_components/TheForm.vue index 2d864cf8319c..93a5d21a28b9 100644 --- a/html/pfappserver/root/src/views/Configuration/domains/_components/TheForm.vue +++ b/html/pfappserver/root/src/views/Configuration/domains/_components/TheForm.vue @@ -5,10 +5,6 @@ :schema="schema" :isLoading="isLoading" > - @@ -28,7 +24,8 @@ + + - + + + + + + { +export const setup = (props, context) => { + + const { root: { $store } = {} } = context + const schema = computed(() => schemaFn(props)) + const formGroupComputedMachineAccountPassword = computed(() => { + + return (props.isNew) ? FormGroupMachineAccountPasswordOnly : FormGroupMachineAccountPassword + }) + + const testMachineAccount = () => $store.dispatch('$_domains/testMachineAccount', props.form) return { - schema + schema, + testMachineAccount, + formGroupComputedMachineAccountPassword } } diff --git a/html/pfappserver/root/src/views/Configuration/domains/_components/TheSearch.vue b/html/pfappserver/root/src/views/Configuration/domains/_components/TheSearch.vue index 627071fd4bca..d1ff1461d3e4 100644 --- a/html/pfappserver/root/src/views/Configuration/domains/_components/TheSearch.vue +++ b/html/pfappserver/root/src/views/Configuration/domains/_components/TheSearch.vue @@ -58,12 +58,7 @@ - +