Tuesday, October 7, 2025

Debian: Configuring for Active Directory

Follow these steps to set up your Debian Linux OS to work with Active Directory:

1. Pre-configuration of network, DNS, full and short hostname.
Ensure the server has correct network settings and can resolve domain names:
ping dc.YOUR_FQDN
nslookup YOUR_FQDN


Replace the local domain name "localdomain" with your domain name YOUR_FQDN in the "/etc/hosts" file using the command:
sudo sed -i 's/localdomain/YOUR_FQDN/g' /etc/hosts

2. Installation of necessary packages.
sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt -y install realmd \
sssd sssd-tools libnss-sss libpam-sss adcli samba-common-bin \
oddjob oddjob-mkhomedir packagekit krb5-user


3. Domain discovery and configuration of Kerberos and realmd.
Discover the domain:
sudo realm discover YOUR_FQDN

To configure Kerberos, edit the file "/etc/krb5.conf":

[libdefaults]
    default_realm = YOUR_FQDN
    dns_lookup_realm = true
    dns_lookup_kdc = true


To configure realmd, create the file "/etc/realmd.conf" with the following content:

[service]
automatic-install = yes

[active-directory]
os-name = Debian
os-version = 13


These parameters will be reflected in the computer account properties in Active Directory after joining the host to the domain.

4. Pre-configuration of SSSD.
Since the "/etc/sssd/sssd.conf" file will be overwritten when joining or leaving the domain using the "realm join" command, the best option is to create a new SSSD configuration file with custom parameters.
sudo nano /etc/sssd/conf.d/10-mysettings.conf

[domain/YOUR_FQDN]
# Add authentication provider
auth_provider = ad
# Add password change provider
chpass_provider = ad
# Short username mode, when set to "false"
use_fully_qualified_names = true
# Default shell setting
default_shell = /bin/bash
# Home directory formation rule
fallback_homedir = /home/%u@%d
# Add to ignore group policies that are inaccessible
ad_gpo_ignore_unreadable = true
# Add dynamic DNS settings
dyndns_update = true
dyndns_refresh_interval = 1800
dyndns_ttl = 1800
dyndns_update_ptr = true


Set correct permissions on the configuration file:
sudo chmod 600 /etc/sssd/conf.d/10-mysettings.conf

5. Joining the domain.
sudo realm join YOUR_FQDN
\
--membership-software=adcli \
--user=DomainAdmin


Enter the user password when prompted.

Check successful join:
id DomainAdmin@YOUR_FQDN

6. Configuring automatic home directory creation.
Edit the file "/etc/pam.d/common-session":
sudo nano /etc/pam.d/common-session

Add to the end of the file:
session optional pam_mkhomedir.so skel=/etc/skel umask=077

7. Configuring access rights.

Managing console access to the server:

Deny login to all domain users:
sudo realm deny --all
Allow login only to specific groups:
sudo realm permit -g 'Linux_Console_Users'@YOUR_FQDN
For groups with spaces in the name:
sudo realm permit -g '"Domain Linux Users"'@YOUR_FQDN
Check settings:
sudo realm list

Managing SSH access:

Create a new file in the "/etc/ssh/sshd_config.d" directory:
sudo nano /etc/ssh/sshd_config.d/10-admins.conf

Add parameters to restrict access by groups:
AllowGroups
Linux_SSH_Users@YOUR_FQDN "Linux SSH Admins"@YOUR_FQDN

IMPORTANT!!!!
If you use the parameter value "use_fully_qualified_names = false" in the "/etc/sssd/sssd.conf" file, then when configuring SSH access, specify the short username or group name without the domain name, for example:
AllowGroups Linux_SSH_Users "Linux SSH Admins"

Also, the group names (Linux_SSH_Users and "Linux SSH Admins") and domain name (YOUR_FQDN) must be STRICTLY IN LOWERCASE!!!

Set correct permissions on the file and restart the SSH service:
sudo chmod 600 /etc/ssh/sshd_config.d/10-admins.conf
sudo systemctl restart ssh

8. Configuring sudo rights for domain groups.
Create a sudo rules file:
sudo visudo -f /etc/sudoers.d/domain_admins
Always use the "visudo" command, which prevents saving configuration with syntax errors.

Add necessary rules:

Full administrator rights (example):
%Linux_Sudoers@YOUR_FQDN ALL=(ALL:ALL) ALL
Members of the Linux_Sudoers group can now run commands with "sudo" by entering their Active Directory account password.

Limited rights (example):
%Linux_Sudoers@YOUR_FQDNALL=(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx

For groups with spaces in the name:
%Linux\ Admins@YOUR_FQDN ALL=(ALL:ALL) ALL

Set file permissions:
sudo chmod 440 /etc/sudoers.d/domain_admins

IMPORTANT!!!!
If you use the parameter value "use_fully_qualified_names = false" in the "/etc/sssd/sssd.conf" file, then when configuring sudoers, specify the short username or group name without the domain name, for example:
%Linux_Sudoers ALL=(ALL:ALL) ALL

9. Testing the configuration.
Try logging in with a domain account:
ssh linux_admin@YOUR_FQDN@server_ip_address
Check home directory creation:
pwd
Check "sudo" functionality for users from allowed groups.
Ensure the server time is synchronized with the domain (use NTP).
Regularly check authentication logs: /var/log/auth.log.
Use SSSD logs for troubleshooting: /var/log/sssd/
Check connectivity with the domain controller:
klist
Check the ability to obtain a Kerberos ticket:
kinit


Removing a host from the domain, renaming a domain machine.

To remove your host from the domain, execute:
sudo realm leave YOUR_FQDN -v --user=DomainAdmin
This will reset all settings made in the "/etc/sssd/sssd.conf" file.

The renaming operation for Linux boils down to the following steps:
1. Remove the host from the domain.
2. Rename using the command:
sudo hostnamectl set-hostname NEW-HOSTNAME
Also change the name in the "/etc/hosts" file:
sudo sed -i 's/OLD-HOSTNAME/NEW-HOSTNAME/g' /etc/hosts
Restart the service:
sudo systemctl restart systemd-hostnamed
3. Re-join the domain with the new name:
sudo realm join YOUR_FQDN \
--membership-software=adcli \
--user=DomainAdmin

After joining the domain, you must reconfigure access using the "sudo realm permit" command, as the "/etc/sssd/sssd.conf" file, which stored the previous settings, was reset by the SSSD service when leaving the domain.

Wednesday, October 1, 2025

Windows: Installing Windows 11 bypassing TPM verification and Secure Boot

If you need to deploy Microsoft Windows 11 on older hardware or a virtual machine without a TPM module and Secure Boot feature for specific reasons, you can bypass this restriction during installation as follows.

Once the graphical setup wizard loads, press the "Shift+F10" key combination: a Windows command prompt window will appear. You can now type the "regedit" command and launch the Registry Editor.

Next, add the following parameters:
1. Create a new key named "HKEY_LOCAL_MACHINE\SYSTEM\Setup\LabConfig".
2. Inside the newly created key, add two parameters of the "DWORD" type:
"BypassTPMCheck" and "BypassSecureBootCheck".
3. Set the value of these parameters to "1".

After modifying the registry, close the editor and continue with the Windows 11 OS installation.

Monday, September 22, 2025

Debian: Setting up a full Debian repository mirror

Creating a full mirror of the Debian repository is a task that requires significant resources and a reliable internet connection. A full mirror includes all packages, updates, backports, and security updates for a specific Debian release.
Debmirror is a specialized Perl utility designed for creating and maintaining Debian and Ubuntu repository mirrors. Unlike standard "rsync" methods, it offers a simpler and more reliable way to synchronize, especially for selective mirroring.

Below are the steps for setting up a Debian 13 ("trixie") repository mirror using "Debmirror":

1. Installing Debmirror and Apache2 web server:
sudo apt-get update
sudo apt-get install debmirror apache2 -y


The basic syntax of the debmirror command is as follows:

debmirror [OPTIONS] /path/to/local/mirror

Key debmirror parameters:
-a, --arch : Architectures (e.g., amd64,i386);
-s, --section : Repository sections (main, contrib, non-free for Debian; main, restricted, universe, multiverse for Ubuntu);
-d, --dist : Distributions (code names, e.g., bullseye, bookworm, trixie for Debian; focal, jammy for Ubuntu). Multiple can be specified separated by commas, including updates (bullseye-updates, bullseye-security);
-h, --host : Source mirror URL (e.g., ftp.debian.org, archive.ubuntu.com or a local Nexus repository);
-r, --root : Root path on the remote server (e.g., /debian for Debian, /ubuntu for Ubuntu);
-e, --method : Download protocol (http, https, ftp, rsync). http or https are often the most reliable.
--progress : Show download progress.
--nosource : Do not download package source code (significantly saves space).
--ignore-release-gpg : Ignore GPG signature verification of Release files. Use with caution as this reduces security.
--ignore-missing-release : Continue synchronization even if the Release file is missing on the server.


2. Test synchronization:

Create repository storage space on disk:
sudo mkdir -p /srv/mirror/debian
sudo mkdir -p /srv/mirror/debian-security
sudo chown -R nobody:nogroup /srv/mirror/debian
sudo chown -R nobody:nogroup /srv/mirror/debian-security


Then run synchronization commands as a test:

Synchronization of the main Debian repository (trixie,trixie-updates,trixie-backports):
debmirror \
-a amd64 \
--nosource \
-s main,contrib,non-free,non-free-firmware \
-d trixie,trixie-updates,trixie-backports \
-h ftp.debian.org \
-r /debian \
-e https \
--progress \
--ignore-release-gpg \
--no-check-gpg \
--rsync-extra=none \
/srv/mirror/debian


Synchronization of Debian security patches (trixie-security):
debmirror \
-a amd64 \
--nosource \
-s main,contrib,non-free,non-free-firmware \
-d trixie-security \
-h security.debian.org \
-r /debian-security \
-e https \
--progress \
--ignore-release-gpg \
--no-check-gpg \
--rsync-extra=none \
/srv/mirror/debian-security


If everything completed without errors, proceed to the next step.


3. Configuring the web server (Apache2) for access to the local repository:

To make the mirror available via HTTP create a file:
sudo nano /etc/apache2/sites-available/debian-mirror.conf

with the following content:

<VirtualHost *:80>
    ServerName your-mirror-domain.com #Replace with your server name or IP
    DocumentRoot /srv/mirror
    <Directory "/srv/mirror">
        Options +Indexes +FollowSymLinks
        IndexOptions NameWidth=* +SuppressDescription FancyIndexing
        Require all granted
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/debian-mirror_error.log
    CustomLog ${APACHE_LOG_DIR}/debian-mirror_access.log combined
</VirtualHost>


If HTTPS support is needed:

Create directories for the key pair (if they don't exist):
sudo mkdir -p /etc/ssl/private /etc/ssl/certs

Then generate a certificate:
sudo openssl req -x509 -nodes -days 730 -newkey rsa:2048 \ -keyout /etc/ssl/private/your-mirror.key \ -out /etc/ssl/certs/your-mirror.crt

During generation, specify Common Name: your-mirror-domain.com. Other fields can be filled as desired or left blank.

Set correct permissions for the key:
sudo chmod 600 /etc/ssl/private/your-mirror.key

And create a file:
sudo nano /etc/apache2/sites-available/debian-mirror-ssl.conf

with the following content:

<VirtualHost *:443>
    ServerName your-mirror-domain.com #Replace with your server name or IP
    DocumentRoot /srv/mirror
    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/your-mirror.crt
    SSLCertificateKeyFile /etc/ssl/private/your-mirror.key
    <Directory "/srv/mirror">
        Options +Indexes +FollowSymLinks
        IndexOptions NameWidth=* +SuppressDescription FancyIndexing
        Require all granted
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/debian-mirror_ssl_error.log
    CustomLog ${APACHE_LOG_DIR}/debian-mirror_ssl_access.log combined
</VirtualHost>


Activate the configuration and restart Apache:
sudo a2ensite debian-mirror
sudo a2ensite debian-mirror-ssl
#For HTTPS support
sudo apachectl configtest
sudo a2enmod rewrite
sudo a2enmod ssl
#For HTTPS support
sudo systemctl restart apache2


Check accessibility: Open "http://your-server-ip/debian" in a browser. The repository directory structure should be displayed.


4. Creating a script to automate the process and monitoring:

The following script can not only perform synchronization of the required repository branches using the "debmirror" command but also log activities, check if the script was previously running, and send reports via email. Create a file:
sudo nano /usr/local/bin/update-mirror.sh

Add the script text to it and make it executable:
sudo chmod +x /usr/local/bin/update-mirror.sh

#!/bin/bash

# Script settings
LOG_FILE="/var/log/mirror_update.log"
LOCK_FILE="/var/run/mirror_update.lock"
MIRROR_BASE="/srv/mirror"

# Email notifications
EMAIL_RECIPIENTS="admin@example.com,devops@example.com"  # Replace with real addresses
EMAIL_SUBJECT_PREFIX="[Mirror Update] "
SMTP_SERVER="Input_IP_of_SMTP_Server"  # SMTP server address
FROM_EMAIL="mirror-update@$(hostname)"  # Sender address

# Mirror configurations
declare -A MIRRORS=(
    ["main"]="
        -a amd64
        --nosource
        -s main,contrib,non-free,non-free-firmware
        -d trixie,trixie-updates,trixie-backports
        -h ftp.by.debian.org
        -r /debian
        -e rsync
        --progress
        --ignore-release-gpg
        --no-check-gpg
        ${MIRROR_BASE}/debian
    "
    ["security"]="
        -a amd64
        --nosource
        -s main,contrib,non-free,non-free-firmware
        -d trixie-security
        -h ftp.by.debian.org
        -r /debian-security
        -e rsync
        --progress
        --ignore-release-gpg
        --no-check-gpg
        ${MIRROR_BASE}/debian-security
    "
)

# Logging function
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}

# Email sending function using curl and SMTP
send_email() {
    local subject="$1"
    local body="$2"

    # Check if curl is installed
    if command -v curl &> /dev/null; then
        # Form email in MIME format
        local email_content=$(cat <<EOF
From: ${FROM_EMAIL}
To: ${EMAIL_RECIPIENTS}
Subject: ${EMAIL_SUBJECT_PREFIX}${subject}
Date: $(date -R)
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"

${body}
EOF
)

        # Send via SMTP using curl
        echo "$email_content" | curl -s --url "smtp://${SMTP_SERVER}" \
            --mail-from "${FROM_EMAIL}" \
            --mail-rcpt "${EMAIL_RECIPIENTS}" \
            --upload-file - \
            --insecure

        if [ $? -eq 0 ]; then
            log "Email sent successfully: ${subject}"
        else
            log "ERROR: Failed to send email: ${subject}"
        fi
    elif command -v sendmail &> /dev/null; then
        # Alternative method using sendmail
        (
            echo "Subject: ${EMAIL_SUBJECT_PREFIX}${subject}"
            echo "From: ${FROM_EMAIL}"
            echo "To: ${EMAIL_RECIPIENTS}"
            echo
            echo "${body}"
        ) | sendmail -t
    else
        log "ERROR: No email client found. Cannot send notification: $subject"
    fi
}

# Lock check function
check_lock() {
    if [ -e "$LOCK_FILE" ]; then
        local pid=$(cat "$LOCK_FILE")
        if kill -0 "$pid" 2>/dev/null; then
            log "ERROR: Script is already running with PID $pid"
            send_email "Script already running" "Mirror update script is already running with PID $pid. Current execution blocked."
            exit 1
        else
            log "WARNING: Lock file found but process does not exist. Removing lock file."
            rm -f "$LOCK_FILE"
        fi
    fi
}

# Lock creation function
create_lock() {
    echo $$ > "$LOCK_FILE"
}

# Lock removal function
remove_lock() {
    rm -f "$LOCK_FILE"
}

# Debmirror execution function
run_mirror() {
    local name="$1"
    local options="$2"

    log "Starting mirror update: $name"
    log "Command: debmirror $options"

    # Command execution
    debmirror $options

    local exit_code=$?
    if [ $exit_code -eq 0 ]; then
        log "Update successful: $name"
        return 0
    else
        log "Update failed with exit code $exit_code: $name"
        return $exit_code
    fi
}

# Main logic
main() {
    log "=== Starting mirror update process ==="

    # Check lock
    check_lock

    # Create lock
    create_lock

    # Add cleanup handler on exit
    trap 'remove_lock; log "Script interrupted"; exit 1' INT TERM
    trap 'remove_lock' EXIT

    local overall_success=true

    for mirror_name in "${!MIRRORS[@]}"; do
        if ! run_mirror "$mirror_name" "${MIRRORS[$mirror_name]}"; then
            overall_success=false
            log "ERROR: Mirror $mirror_name failed with exit code $?"
            send_email "Update failed: $mirror_name" "Mirror update for $mirror_name failed. Check $LOG_FILE for details."
        fi
    done

    if [ "$overall_success" = true ]; then
        log "=== All mirror updates completed successfully ==="
        send_email "All updates completed" "All mirror updates completed successfully. Check $LOG_FILE for details."
    else
        log "=== Some mirror updates failed ==="
        # Error notifications were already sent for each specific failure
    fi
}

# Error handling
set -euo pipefail

# Launch main function
main "$@"


5. Setting up log rotation:

Create a file /etc/logrotate.d/update_mirror with the following content:

/var/log/mirror_update.log {
weekly
missingok
rotate 26
compress
delaycompress
notifempty
create 644 root root
dateext
dateformat -%Y-%m-%d
}


Explanation of directives:
weekly - rotation occurs once a week;
missingok - skip rotation if the file is missing;
rotate 26 - keep 26 archive copies (half a year ≈ 26 weeks);
compress - compress archive logs using gzip;
delaycompress - delay compression until the next rotation (convenient for debugging);
notifempty - do not rotate empty files;
create 644 root root - create a new log file with permissions 644 and owner root:root after rotation;
dateext - add date to the archive file name;
dateformat -%Y-%m-%d - date format in the file name (YYYY-MM-DD).

Configuration check:
sudo logrotate -d /etc/logrotate.d/update_mirror
The -d key enables debug mode, which will show potential problems without performing rotation.

Forced rotation for testing:
sudo logrotate -vf /etc/logrotate.d/update_mirror
The -v key enables verbose output, and -f forcibly starts rotation.


6. Setting up automatic synchronization via cron

To keep the mirror up to date, it needs to be regularly synchronized. It is recommended to do this at least 4 times a day.
Create a cron job (sudo crontab -e):
Synchronization every 6 hours (at random minutes for even load distribution):
0 */6 * * * /usr/local/bin/update-mirror.sh

or synchronization daily at 2 AM:
0 2 * * * /usr/local/bin/update-mirror.sh

Do not synchronize the mirror exactly at 3:00, 9:00, 15:00, and 21:00 UTC, as this is when the main mirrors are updated. Add a random offset of a few minutes.


7. Testing and using the mirror:

Test your mirror by configuring sources.list on another machine:
sudo nano /etc/apt/sources.list

Add the lines:
deb http://your-mirror-ip-or-domain/debian/ trixie main contrib non-free non-free-firmware
deb http://your-mirror-ip-or-domain/debian/ trixie-updates main contrib non-free non-free-firmware
deb http://your-mirror-ip-or-domain/debian/ trixie-backports main contrib non-free non-free-firmware
deb http://your-mirror-ip-or-domain/debian-security/ trixie-security main contrib non-free non-free-firmware


Update package indexes:
sudo apt update
sudo apt upgrade


Monday, September 8, 2025

ALT Linux: Configuring VMware VM Customization Specifications for ALT Linux OS

Unfortunately, when using "VM Customization Specifications" when deploying ALT Linux OS, "open-vm-toos" scripts cannot correctly configure parameters such as host name, network settings, etc. for this system. To solve this problem, we will use a script that will take over the functionality that does not work correctly in the tools themselves.

Let's perform the following steps to configure "VM Customization Specifications":

1. Prepare an ATL Linux OS template with the installation of the required software, which should already be included with new hosts.
2. Be sure to install "open-vm-tools" and check that the "vmtoolsd" service is in autorun:
sudo apt-get install open-vm-tools
sudo systemctl enable vmtoolsd && sudo systemctl start vmtoolsd


3. Enable support for customization scripts:
sudo vmware-toolbox-cmd config set deployPkg enable-custom-scripts true

To check the current state of the parameter, run:
sudo vmware-toolbox-cmd config get deployPkg enable-custom-scripts

4. Next, configure "VM Customization Specifications" on vCenter Server. When setting up, we specify: OS type - Linux, host name generation rule, time zone, TCP/IP settings and add the following script:

#!/bin/bash

# Function to convert subnet mask to CIDR prefix
mask2cidr() {
    local mask=$1
    local n=0
    IFS=.
    for byte in $mask; do
        case $byte in
            255) n=$((n+8));;
            254) n=$((n+7));;
            252) n=$((n+6));;
            248) n=$((n+5));;
            240) n=$((n+4));;
            224) n=$((n+3));;
            192) n=$((n+2));;
            128) n=$((n+1));;
            0);;
            *) echo 24; return 1;;
        esac
    done
    echo $n
}

# Function to determine network management method
get_network_manager() {
    local interface=$1
    local options_file="/etc/net/ifaces/$interface/options"

    # Check if configuration file exists
    if [ -f "$options_file" ]; then
        local nm_controlled=$(grep -E "^NM_CONTROLLED=" "$options_file" | cut -d= -f2)
        local disabled=$(grep -E "^DISABLED=" "$options_file" | cut -d= -f2)
        local systemd_controlled=$(grep -E "^SYSTEMD_CONTROLLED=" "$options_file" | cut -d= -f2)

        if [ "$nm_controlled" = "yes" ]; then
            echo "NetworkManager"
        elif [ "$disabled" = "no" ]; then
            echo "EtcNet"
        elif [ "$systemd_controlled" = "yes" ]; then
            echo "SystemD"
        else
            echo "Unknown"
        fi
    else
        # If file doesn't exist, check active services
        if systemctl is-active NetworkManager >/dev/null 2>&1; then
            echo "NetworkManager"
        elif systemctl is-active systemd-networkd >/dev/null 2>&1 && \
             systemctl is-active systemd-resolved >/dev/null 2>&1; then
            echo "SystemD"
        elif systemctl is-active network >/dev/null 2>&1; then
            echo "EtcNet"
        else
            echo "Unknown"
        fi
    fi
}

# Function to configure NetworkManager
configure_networkmanager() {
    local interface=$1
    local bootproto=$2
    local ipaddr=$3
    local netmask=$4
    local gateway=$5
    local dns_servers=$6
    local domain=$7

    # Create or modify connection
    if [ "$bootproto" = "static" ]; then
        local prefix_length=$(mask2cidr "$netmask")
        nmcli con add type ethernet con-name "$interface" ifname "$interface" ip4 "$ipaddr/$prefix_length" gw4 "$gateway"
        if [ -n "$dns_servers" ]; then
            nmcli con mod "$interface" ipv4.dns "$dns_servers"
        fi
        if [ -n "$domain" ]; then
            nmcli con mod "$interface" ipv4.dns-search "$domain"
        fi
    else
        nmcli con add type ethernet con-name "$interface" ifname "$interface" ipv4.method auto
    fi

    # Activate connection
    nmcli con up "$interface"
}

# Function to configure EtcNet
configure_etcnet() {
    local interface=$1
    local bootproto=$2
    local ipaddr=$3
    local netmask=$4
    local gateway=$5
    local dns_servers=$6
    local domain=$7

    local iface_dir="/etc/net/ifaces/$interface"

    mkdir -p "$iface_dir"

    # Update options file based on configuration type
    if [ -f "$iface_dir/options" ]; then
        # Update existing options file, preserving other parameters
        if [ "$bootproto" = "static" ]; then
            # Remove old BOOTPROTO and SYSTEMD_BOOTPROTO parameters if they exist
            sed -i '/^BOOTPROTO=/d' "$iface_dir/options"
            sed -i '/^SYSTEMD_BOOTPROTO=/d' "$iface_dir/options"
            # Add new parameters
            echo "BOOTPROTO=static" >> "$iface_dir/options"
            echo "SYSTEMD_BOOTPROTO=static" >> "$iface_dir/options"
        else
            # Remove old BOOTPROTO and SYSTEMD_BOOTPROTO parameters if they exist
            sed -i '/^BOOTPROTO=/d' "$iface_dir/options"
            sed -i '/^SYSTEMD_BOOTPROTO=/d' "$iface_dir/options"
            # Add new parameters
            echo "BOOTPROTO=dhcp" >> "$iface_dir/options"
            echo "SYSTEMD_BOOTPROTO=dhcp4" >> "$iface_dir/options"
        fi
    else
        # Create new options file
        if [ "$bootproto" = "static" ]; then
            cat > "$iface_dir/options" <<EOF
BOOTPROTO=static
SYSTEMD_BOOTPROTO=static
EOF
        else
            cat > "$iface_dir/options" <<EOF
BOOTPROTO=dhcp
SYSTEMD_BOOTPROTO=dhcp4
EOF
        fi
    fi

    if [ "$bootproto" = "static" ]; then
        # Write IP address with CIDR prefix
        PREFIX_LENGTH=$(mask2cidr "$netmask")
        if [ -z "$PREFIX_LENGTH" ]; then
            PREFIX_LENGTH=24
        fi
        echo "$ipaddr/$PREFIX_LENGTH" > "$iface_dir/ipv4address"

        # Write default route
        if [ -n "$gateway" ]; then
            echo "default via $gateway" > "$iface_dir/ipv4route"
        fi

        # Configure DNS via resolv.conf in interface directory
        if [ -n "$dns_servers" ]; then
            # Create resolv.conf in interface directory
            echo "# Generated by network configuration script" > "$iface_dir/resolv.conf"
            if [ -n "$domain" ]; then
                echo "domain $domain" >> "$iface_dir/resolv.conf"
                echo "search $domain" >> "$iface_dir/resolv.conf"
            fi

            # Add DNS servers
            for dns in $dns_servers; do
                echo "nameserver $dns" >> "$iface_dir/resolv.conf"
            done
        fi

        # Release DHCP lease for interface
        if command -v /sbin/dhcpcd >/dev/null 2>&1; then
            /sbin/dhcpcd -k "$interface" 2>/dev/null || true
        fi
    else
        # For DHCP, clear possible previous static configurations
        rm -f "$iface_dir/ipv4address"
        rm -f "$iface_dir/ipv4route"
        rm -f "$iface_dir/resolv.conf"

        # Don't create type file as specified in requirements
    fi

    # Add interface to processing order
    local order_file="/etc/net/ifaces.order"
    if ! grep -q "^$interface$" "$order_file" 2>/dev/null; then
        echo "$interface" >> "$order_file"
    fi

    # Restart network
    systemctl restart network
}

# --- Function to clean logs and temporary files ---
clean_system() {
    # Clean logs (preserving directory structure)
    find /var/log -type f -name "*.log" -exec truncate -s 0 {} \;
    find /var/log -type f -name "*.gz" -delete;
    find /var/log -type f -name "*.old" -delete;
    find /var/log -type f -name "lastlog" -exec rm -f {} \;

    # Clean temporary files
    rm -rf /tmp/*
    rm -rf /var/tmp/*

    # Clean command history and user data
    for user_home in /home/*; do
        if [[ -d "$user_home" ]]; then
            user=$(basename "$user_home")
            # Clean bash_history and other histories
            truncate -s 0 "$user_home/.bash_history" 2>/dev/null || true
            # Clean application caches
            rm -rf "$user_home/.cache/*" 2>/dev/null || true
        fi
    done
    # Clean root
    truncate -s 0 /root/.bash_history 2>/dev/null || true
    rm -rf /root/.cache/* 2>/dev/null || true

    # Remove random seed files
    rm -f /var/lib/systemd/random-seed
}

# --- Function to remove SSH host keys ---
reset_ssh_keys() {
    rm -f /etc/ssh/ssh_host_*
    # Keys will be generated on next SSH server start
}

if [ "$1" = "precustomization" ]; then
    # Perform system cleanup
    clean_system
    reset_ssh_keys

    # Check and create /etc/sysconfig/network-scripts directory if needed
    if [ ! -d "/etc/sysconfig/network-scripts" ]; then
        mkdir -p /etc/sysconfig/network-scripts
    fi
    # Pre-customization stage
    VMCUST_DIR=$(ls -d /var/run/.vmware-imgcust* 2>/dev/null | head -n 1)
    if [ -n "$VMCUST_DIR" ]; then
        CUST_CFG_PATH="$VMCUST_DIR/cust.cfg"
        if [ -f "$CUST_CFG_PATH" ]; then
            cp "$CUST_CFG_PATH" "/root/cust.cfg"
        fi
    fi
elif [ "$1" = "postcustomization" ]; then
    # Post-customization stage
    if [ ! -f "/root/cust.cfg" ]; then
        exit 1
    fi

    CFG_FILE="/root/cust.cfg"

    # Parse configuration parameters
    BOOTPROTO=$(awk -F' = ' '/^BOOTPROTO/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    IPADDR=$(awk -F' = ' '/^IPADDR/ {line=$2} END{print line}' "$CFG_FILE")
    NETMASK=$(awk -F' = ' '/^NETMASK/ {line=$2} END{print line}' "$CFG_FILE")
    GATEWAY=$(awk -F' = ' '/^GATEWAY/ {line=$2} END{print line}' "$CFG_FILE")
    HOSTNAME=$(awk -F' = ' '/^HOSTNAME/ {line=$2} END{print line}' "$CFG_FILE")
    DOMAIN=$(awk -F' = ' '/^DOMAINNAME/ {line=$2} END{print line}' "$CFG_FILE")
    MACADDR=$(awk -F' = ' '/^MACADDR/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    DNS_SERVERS=$(awk -F' = ' '/^NAMESERVER\|[0-9]/ {print $2}' "$CFG_FILE" | tr '\n' ' ')
    DNS_FROM_DHCP=$(awk -F' = ' '/^DNSFROMDHCP/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    TIMEZONE=$(awk -F' = ' '/^TIMEZONE/ {line=$2} END{print line}' "$CFG_FILE")
    UTC=$(awk -F' = ' '/^UTC/ {line=$2} END{print tolower(line)}' "$CFG_FILE")

    # Determine network interface
    INTERFACE=$(ip -o link | awk -v mac="$MACADDR" 'tolower($0) ~ mac {gsub(":", "", $2); print $2}')
    if [ -z "$INTERFACE" ]; then
        INTERFACE=$(ip route | awk '/default/ {print $5; exit}')
    fi

    # Determine network management method
    NET_MGR=$(get_network_manager "$INTERFACE")

    case "$NET_MGR" in
        "NetworkManager")
            configure_networkmanager "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" "$GATEWAY" "$DNS_SERVERS" "$DOMAIN"
            ;;
        "EtcNet")
            configure_etcnet "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" "$GATEWAY" "$DNS_SERVERS" "$DOMAIN"
            ;;
        "SystemD")
            # Existing SystemD configuration
            rm -f /etc/systemd/network/*.network
            rm -f /etc/systemd/resolved.conf.d/*.conf

            # Create base configuration for loopback interface
            cat > /etc/systemd/network/00-loopback.network <<EOF
[Match]
Name=lo

[Network]
Address=127.0.0.1/8
Address=::1/128
EOF

            # Create configuration for main interface
            CONFIG_FILE="/etc/systemd/network/alterator-${INTERFACE}.network"

            if [ "$BOOTPROTO" = "static" ]; then
                if [ -z "$IPADDR" ] || [ -z "$NETMASK" ]; then
                    exit 1
                fi

                PREFIX_LENGTH=$(mask2cidr "$NETMASK")
                if [ -z "$PREFIX_LENGTH" ]; then
                    PREFIX_LENGTH=24
                fi

                # Create config for static IP with DNS
                cat > "$CONFIG_FILE" <<EOF
[Match]
Name=$INTERFACE

[Network]
Address=$IPADDR/$PREFIX_LENGTH
Gateway=$GATEWAY
EOF

                # Add DNS servers if needed
                if { [ "$BOOTPROTO" = "static" ] || [ "$DNS_FROM_DHCP" = "no" ]; } && [ -n "$DNS_SERVERS" ]; then
                    for dns in $DNS_SERVERS; do
                        echo "DNS=$dns" >> "$CONFIG_FILE"
                    done
                fi
            else
                # Create config for DHCP
                cat > "$CONFIG_FILE" <<EOF
[Match]
Name=$INTERFACE

[Network]
DHCP=ipv4

[DHCPv4]
UseDomains=true
UseDNS=yes
EOF

                # Add DNS servers if not using DNS from DHCP
                if [ "$DNS_FROM_DHCP" = "no" ] && [ -n "$DNS_SERVERS" ]; then
                    for dns in $DNS_SERVERS; do
                        echo "DNS=$dns" >> "$CONFIG_FILE"
                    done
                fi
            fi

            systemctl restart systemd-networkd
            systemctl restart systemd-resolved
            ;;
        *)
            echo "Unknown network management method"
            exit 1
            ;;
    esac

    # Common settings
    hostnamectl set-hostname "$HOSTNAME"
    sed -i "/127\.0\.1\.1/d" /etc/hosts
    echo "127.0.1.1 $HOSTNAME.$DOMAIN $HOSTNAME" >> /etc/hosts

     # Timezone configuration
    if [ -n "$TIMEZONE" ]; then
        timedatectl set-timezone "$TIMEZONE"
    fi

    # BIOS time format configuration (UTC or local)
    if [ -n "$UTC" ]; then
        if [ "$UTC" = "yes" ]; then
            # Set UTC time in BIOS
            timedatectl set-local-rtc 0
        else
            # Set local time in BIOS
            timedatectl set-local-rtc 1
        fi
    fi

    # Save configuration
    {
        echo "CONFIG_SOURCE=$CFG_FILE"
        echo "BOOTPROTO=$BOOTPROTO"
        echo "INTERFACE=$INTERFACE"
        echo "HOSTNAME=$HOSTNAME"
        echo "DOMAIN=$DOMAIN"
        echo "TIMEZONE=$TIMEZONE"
        echo "UTC=$UTC"
        [ -n "$DNS_FROM_DHCP" ] && echo "DNS_FROM_DHCP=$DNS_FROM_DHCP"
        [ -n "$DNS_SERVERS" ] && echo "DNS_SERVERS=\"$DNS_SERVERS\""
    }

    if [ "$BOOTPROTO" = "static" ]; then
        {
            echo "IP_ADDRESS=$IPADDR"
            echo "NETMASK=$NETMASK"
            echo "PREFIX_LENGTH=$PREFIX_LENGTH"
            [ -n "$GATEWAY" ] && echo "GATEWAY=$GATEWAY"
        }
    fi
    rm -f "/root/cust.cfg"
##############################################################################################
    # PLACE FOR ADDITIONAL SYSTEM CONFIGURATION
    # Here you can add commands for final machine setup:
    # - Installation of required software
    # - Configuration via Ansible or other configuration management systems
    # - Execution of custom scripts
    # - Setup of monitoring, logging and other services
##############################################################################################
fi

This script covers all possible network setup options: EtcNet, NetworkManager, Systemd-Networkd, both with static IP and with DHCP.

Wednesday, June 18, 2025

Windows: Enable LDAP over SSL (LDAPS) using a third-party certificate authority (CA)

Let's consider the option of configuring the LDAPS (LDAP over SSL) protocol using a third-party certification authority for the mydomain.local domain with three controllers: DC1, DC2, DC3:

1. Create a certificate request template in the form of a <name>.inf file for each domain controller. Replace <name> with the FQDN of the controller (for example, DC1.mydomain.local):

[Version]
Signature="$Windows NT$"

[NewRequest]
Subject = "CN=DC1.mydomain.local" ; FQDN of the current controller
KeySpec = 1
KeyLength = 2048
Exportable = TRUE
MachineKeySet = TRUE
SMIME = False
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0

[EnhancedKeyUsageExtension]
OID=1.3.6.1.5.5.7.3.1 ; Server Authentication

[Extensions]
2.5.29.17 = "{text}"
_continue_ = "dns=DC1.mydomain.local&" ; FQDN of the current controller
_continue_ = "dns=DC1&" ; short name of the current controller
_continue_ = "dns=DC2.mydomain.local&" ; FQDN of other controllers
_continue_ = "dns=DC2&"
_continue_ = "dns=DC3.mydomain.local&"
_continue_ = "dns=DC3&"
_continue_ = "dns=ldap.mydomain.local&" ; virtual name for switching
_continue_ = "dns=mydomain.local&" ; FQDN
_continue_ = "dns=MYDOMAIN" ; NetBIOS domain name

Important:
For balancing, you need to add the FQDN of the domain and all domain controllers, as well as their NetBIOS names, to the [Extensions] section. You can also add fault-tolerant DNS records, such as "ldap.mydomain.local".

Save the file on each controller (for example, C:\Cert\DC1.inf).

2. Generate a CSR for each controller. To do this, create certificate requests via the command line (on each controller):
certreq -new C:\Cert\<name>.inf C:\Cert\<name>.csr

3. Now you need to send the CSR to a third-party CA. Transfer the DC1.csr, DC2.csr, DC3.csr files to the administrator of the external CA and get back the signed certificates (DC1.cer, DC2.cer, DC3.cer or DC1.p7b, DC2.p7b, DC3.p7b) from the CA.

4. Install the certificates on the domain controllers. To do this, run the import command on each domain controller:
certreq -accept C:\Cert\<name>.cer

To check the installed certificate, open "mmc", add the snap-in "Certificates (Local Computer)". Make sure that the certificate is displayed in Personal → Certificates and has a private key.

5. To complete the LDAPS configuration on the controllers with the firewall enabled, open port 636:
New-NetFirewallRule -DisplayName "LDAPS" -Direction Inbound -Protocol TCP -LocalPort 636 -Action Allow

6. Restart the LDAP service or reboot the servers:
Restart-Service NTDS -Force

7. Check the operation of LDAPS using "ldp.exe" - connect to the domain controller, specifying port 636 and checking the "SSL" box.

Disabling unencrypted LDAP (optional):

Open "Group Policy Management" and select "Default Domain Controllers Policy".
Go to: "Computer Configuration → Policies → Windows Settings → Security Settings → Domain Controller: Require digital signature for LDAP server".
Set the parameter: "Require digital signature".
Restart the domain controllers to apply the changes.

Monday, May 26, 2025

Windows: Remove recovery partition from disk

Sometimes such a partition prevents you from expanding the system disk with Windows OS in a virtual environment. To remove it, run the console with elevated privileges and run:

1. First, disable the recovery environment:
reagentc /disable

2. Then run:
diskpart
list volume


Pay attention to the volume number for the recovery partition, remember it.

3. Delete the partition:
select volume N
delete volume override
exit


where N - the number of your recovery partition.

As a result of these actions, the recovery partition will be deleted and the space it occupied will be freed up.

Friday, May 23, 2025

Windows: Disabling Azure Arc Setup on Windows Server 2022 and later

Since the end of 2024, a new component "Azure Arc Setup" has been coming with Windows updates, designed to manage Azure resources. If you do not use this functionality, it is recommended to disable it. To do this, open the PowerShell console with elevated privileges and run:

For Windows 2022:
Disable-WindowsOptionalFeature -Online -FeatureName AzureArcSetup
(Also for Windows 2022, you can disable this feature by disabling components using the "Server Manager")

For Windows 2025:
DISM /online /Remove-Capability /CapabilityName:AzureArcSetup~~~~

After this, you will need to reboot the host.