mgutt Posted November 28, 2021 Share Posted November 28, 2021 This script automatically grants write permissions for selected shares and users, after the root user logged into the WebGUI and removes it after 10800 seconds (3 hours) to protect the shares most of the time against ransomware attacks. Simply logout from the WebGUI and login again, to re-grant write permissions. Installation: Install the "User Scripts" plugin from "Apps" Add a new script and paste the following script Set the schedule to "Custom" and the value to * * * * * (this means the script is executed every minute) Note: As the cronjob runs "only" every minute, it takes up to one minute to get write permissions. #!/bin/bash # ##################################### # Script: SMB Share Auto Write Protection # Description: Selected SMB Shares get write access for a specific time after the root user logged in # and are reset to read-only afterwards to protect them against ransomware. # Author: Marc Gutt # Version: 0.3 # # Changelog: # 0.3 # - fixed wrong last login timestamp # 0.2 # - clean up smb config backups # - get most recent syslog file # ##################################### # ##################################### # Settings # ##################################### # Enable writing on this shares after root user logged in protect_shares="Movie,Music,Photo,TV,Video" # Set shares to read-only X seconds after root user logged in protect_shares_after_seconds="10800" # Which users are allowed to write users="marc" # Location of smb shares config file config_file="/etc/samba/smb-shares.conf" # ##################################### # Script # ##################################### # make script race condition safe if [[ -d "/tmp/${0//\//_}" ]] || ! mkdir "/tmp/${0//\//_}"; then echo "Script is already running!"; exit 1; fi; trap 'rmdir "/tmp/${0//\//_}"' EXIT; # check if smb config file exists if [[ ! -f "$config_file" ]]; then echo "$config_file not found!" exit 1 fi # check if nothing writes to the config file (avoids race conditions) if ! find "$config_file" -mmin +1 -exec true {} +; then echo "$config_file is not old enough" exit 0 fi # get last login timestamp syslog_file=$(ls /var/log/syslog /var/log/syslog.[0-9] 2>/dev/null | tail -n1) last_login=$(grep "webGUI: Successful login user root" "$syslog_file" | tail -n1) last_login_date=$(echo "$last_login" | grep -oP "^.*?:[0-9]+:[0-9]+") timestamp_last_login=$(date -d "$last_login_date" +%s) echo "Last login was: $last_login_date" # get current and expire timestamps timestamp_now=$(date +%s) timestamp_protect=$((timestamp_last_login + protect_shares_after_seconds)) # prepare input protect_shares=${protect_shares//,/|} users=(${users//,/ }) # read share configs into array shares_config=$(cat $config_file) shares_config=${shares_config//[/$'\f'[} # add feed-form as a delimiter IFS=$'\f' # set delimiter to feed-form shares_config=($shares_config) # fill array IFS=' ' # set delimiter to whitespace # loop through configs update_config="" for i in "${!shares_config[@]}"; do config="${shares_config[$i]}" if [[ $config =~ ^\[($protect_shares)\] ]]; then sharename=$(echo "$config" | head -n1) if [[ ! $config =~ "read list =" ]]; then echo "$sharename Does not contain \"read list\"!" continue fi if [[ ! $config =~ "write list =" ]]; then echo "$sharename Does not contain \"write list\"!" continue fi read_list=$(echo -e "$config" | grep -Po "(?<=read list = ).*") write_list=$(echo -e "$config" | grep -Po "(?<=write list = ).*") if [[ ! $read_list ]] && [[ ! $write_list ]]; then echo "$sharename Has no users in read AND write list!" continue fi config_before="$config" # set share to read-only if [[ timestamp_protect -lt timestamp_now ]]; then for user in "${users[@]}"; do # add user to read list if [[ ! $read_list =~ $user ]]; then echo "$sharename $user added to read list" read_list="$read_list,$user" fi # remove user from write list write_list_array=(${write_list//,/ }) for j in "${!write_list_array[@]}"; do if [[ ${write_list_array[$j]} == "$user" ]]; then echo "$sharename $user deleted from write list" unset "write_list_array[$j]" fi done write_list="$(IFS=, ; echo "${write_list_array[*]-}")" done echo "$sharename Grant $write_list write permissions and set $read_list to read-only" # make share writeable else for user in "${users[@]}"; do # add user to write list if [[ ! $write_list =~ $user ]]; then echo "$sharename $user added to write list" write_list="$write_list,$user" fi # remove user from read list read_list_array=(${read_list//,/ }) for j in "${!read_list_array[@]}"; do if [[ ${read_list_array[$j]} == "$user" ]]; then echo "$sharename $user deleted from read list" unset "read_list_array[$j]" fi done read_list="$(IFS=, ; echo "${read_list_array[*]-}")" done fi # remove comma(s) from beginning and end read_list=$( echo "$read_list" | sed 's/^,*//' | sed 's/,*$//' ) write_list=$( echo "$write_list" | sed 's/^,*//' | sed 's/,*$//' ) # set configs shopt -s extglob # needed for the following regex: config=${config/read list =*([ a-zA-Z0-9.-_,])/read list = $read_list} config=${config/write list =*([ a-zA-Z0-9.-_,])/write list = $write_list} if [[ "$config_before" != "$config" ]]; then update_config=1 shares_config[$i]="$config" fi fi done # write array to config file if [[ $update_config ]]; then # backup smb config file echo "Backup SMB config file" cp -v "$config_file" "/tmp/$(date +%FT%H-%M-%S)_$(basename "$config_file")" # overwrite smb config file echo "Update SMB config file" printf "%s" "${shares_config[@]}" > "$config_file" # add final new line echo "" >> "$config_file" # reload smb config smbcontrol all reload-config else echo "SMB config has not changed" fi # remove all smb config backups older than 2 days find /tmp -name "*_$(basename "$config_file")" -not -name "$(date +%F)*" -not -name "$(date +%F --date="yesterday")*" -delete Quote Link to comment
Squid Posted November 28, 2021 Share Posted November 28, 2021 Question: Do any / all streams in progress via SMB get dropped when reloading the config? Quote Link to comment
mgutt Posted November 28, 2021 Author Share Posted November 28, 2021 4 minutes ago, Squid said: Do any / all streams in progress via SMB get dropped when reloading the config? No. "smbcontrol all reload-config" works without interruption. Even if you remove writing permissions of a share. Only if you stop all transfers and close all SMB connections (for example closing all Windows Explorers and waiting ~ 30 seconds), the new permissions become active. So this is a "lazy" permission update which is based on SMB sessions. Quote Link to comment
mgutt Posted January 9, 2022 Author Share Posted January 9, 2022 Released version 0.3: Quote # - fixed wrong last login timestamp Quote Link to comment
jkexbx Posted October 30, 2023 Share Posted October 30, 2023 Thanks for the script! I'm trying to modify it so that I can create a restore point where my configuration is good and then a script to restore to that point every day. That way when I modify my SMB configuration to work on something, I don't have to worry about resetting my configuration back to normal once I'm done. Copying and creating the backups seems to be working fine, but I'm struggling to get the backup to reapply. ### Backup Script with Timestamp for smb-shares.conf #!/bin/bash timestamp=$(date +"%Y%m%d%H%M%S") if cp /etc/samba/smb-shares.conf /etc/samba/smb-shares.conf.bak && cp /etc/samba/smb-shares.conf "/mnt/user/Unraid Backups/SMB/smb-shares.conf_${timestamp}.bak"; then echo "$(date) - Backup successful." >> /var/log/smb_backup_restore.log else echo "$(date) - Backup failed." >> /var/log/smb_backup_restore.log fi ### Restore Script for smb-shares.conf with Reload Config #!/bin/bash if [ ! -f /etc/samba/smb-shares.conf.bak ]; then echo "$(date) - Backup file not found. Restore failed." >> /var/log/smb_backup_restore.log exit 1 fi if cp /etc/samba/smb-shares.conf.bak /etc/samba/smb-shares.conf; then echo "$(date) - Restore successful." >> /var/log/smb_backup_restore.log smbcontrol all reload-config else echo "$(date) - Restore failed." >> /var/log/smb_backup_restore.log fi Quote Link to comment
kilonde Posted July 3 Share Posted July 3 On 11/28/2021 at 7:15 PM, mgutt said: This script automatically grants write permissions for selected shares and users, after the root user logged into the WebGUI and removes it after 10800 seconds (3 hours) to protect the shares most of the time against ransomware attacks. Simply logout from the WebGUI and login again, to re-grant write permissions. Installation: Install the "User Scripts" plugin from "Apps" Add a new script and paste the following script Set the schedule to "Custom" and the value to * * * * * (this means the script is executed every minute) Note: As the cronjob runs "only" every minute, it takes up to one minute to get write permissions. #!/bin/bash # ##################################### # Script: SMB Share Auto Write Protection # Description: Selected SMB Shares get write access for a specific time after the root user logged in # and are reset to read-only afterwards to protect them against ransomware. # Author: Marc Gutt # Version: 0.3 # # Changelog: # 0.3 # - fixed wrong last login timestamp # 0.2 # - clean up smb config backups # - get most recent syslog file # ##################################### # ##################################### # Settings # ##################################### # Enable writing on this shares after root user logged in protect_shares="Movie,Music,Photo,TV,Video" # Set shares to read-only X seconds after root user logged in protect_shares_after_seconds="10800" # Which users are allowed to write users="marc" # Location of smb shares config file config_file="/etc/samba/smb-shares.conf" # ##################################### # Script # ##################################### # make script race condition safe if [[ -d "/tmp/${0//\//_}" ]] || ! mkdir "/tmp/${0//\//_}"; then echo "Script is already running!"; exit 1; fi; trap 'rmdir "/tmp/${0//\//_}"' EXIT; # check if smb config file exists if [[ ! -f "$config_file" ]]; then echo "$config_file not found!" exit 1 fi # check if nothing writes to the config file (avoids race conditions) if ! find "$config_file" -mmin +1 -exec true {} +; then echo "$config_file is not old enough" exit 0 fi # get last login timestamp syslog_file=$(ls /var/log/syslog /var/log/syslog.[0-9] 2>/dev/null | tail -n1) last_login=$(grep "webGUI: Successful login user root" "$syslog_file" | tail -n1) last_login_date=$(echo "$last_login" | grep -oP "^.*?:[0-9]+:[0-9]+") timestamp_last_login=$(date -d "$last_login_date" +%s) echo "Last login was: $last_login_date" # get current and expire timestamps timestamp_now=$(date +%s) timestamp_protect=$((timestamp_last_login + protect_shares_after_seconds)) # prepare input protect_shares=${protect_shares//,/|} users=(${users//,/ }) # read share configs into array shares_config=$(cat $config_file) shares_config=${shares_config//[/$'\f'[} # add feed-form as a delimiter IFS=$'\f' # set delimiter to feed-form shares_config=($shares_config) # fill array IFS=' ' # set delimiter to whitespace # loop through configs update_config="" for i in "${!shares_config[@]}"; do config="${shares_config[$i]}" if [[ $config =~ ^\[($protect_shares)\] ]]; then sharename=$(echo "$config" | head -n1) if [[ ! $config =~ "read list =" ]]; then echo "$sharename Does not contain \"read list\"!" continue fi if [[ ! $config =~ "write list =" ]]; then echo "$sharename Does not contain \"write list\"!" continue fi read_list=$(echo -e "$config" | grep -Po "(?<=read list = ).*") write_list=$(echo -e "$config" | grep -Po "(?<=write list = ).*") if [[ ! $read_list ]] && [[ ! $write_list ]]; then echo "$sharename Has no users in read AND write list!" continue fi config_before="$config" # set share to read-only if [[ timestamp_protect -lt timestamp_now ]]; then for user in "${users[@]}"; do # add user to read list if [[ ! $read_list =~ $user ]]; then echo "$sharename $user added to read list" read_list="$read_list,$user" fi # remove user from write list write_list_array=(${write_list//,/ }) for j in "${!write_list_array[@]}"; do if [[ ${write_list_array[$j]} == "$user" ]]; then echo "$sharename $user deleted from write list" unset "write_list_array[$j]" fi done write_list="$(IFS=, ; echo "${write_list_array[*]-}")" done echo "$sharename Grant $write_list write permissions and set $read_list to read-only" # make share writeable else for user in "${users[@]}"; do # add user to write list if [[ ! $write_list =~ $user ]]; then echo "$sharename $user added to write list" write_list="$write_list,$user" fi # remove user from read list read_list_array=(${read_list//,/ }) for j in "${!read_list_array[@]}"; do if [[ ${read_list_array[$j]} == "$user" ]]; then echo "$sharename $user deleted from read list" unset "read_list_array[$j]" fi done read_list="$(IFS=, ; echo "${read_list_array[*]-}")" done fi # remove comma(s) from beginning and end read_list=$( echo "$read_list" | sed 's/^,*//' | sed 's/,*$//' ) write_list=$( echo "$write_list" | sed 's/^,*//' | sed 's/,*$//' ) # set configs shopt -s extglob # needed for the following regex: config=${config/read list =*([ a-zA-Z0-9.-_,])/read list = $read_list} config=${config/write list =*([ a-zA-Z0-9.-_,])/write list = $write_list} if [[ "$config_before" != "$config" ]]; then update_config=1 shares_config[$i]="$config" fi fi done # write array to config file if [[ $update_config ]]; then # backup smb config file echo "Backup SMB config file" cp -v "$config_file" "/tmp/$(date +%FT%H-%M-%S)_$(basename "$config_file")" # overwrite smb config file echo "Update SMB config file" printf "%s" "${shares_config[@]}" > "$config_file" # add final new line echo "" >> "$config_file" # reload smb config smbcontrol all reload-config else echo "SMB config has not changed" fi # remove all smb config backups older than 2 days find /tmp -name "*_$(basename "$config_file")" -not -name "$(date +%F)*" -not -name "$(date +%F --date="yesterday")*" -delete I get the following error when I run the script: [appdata] Does not contain "read list"! SMB config has not changed root@Tower:~# cat /etc/samba/smb-shares.conf [appdata] path = /mnt/user/appdata comment = application data browseable = yes # Secure public = yes writeable = no write list = kilonde case sensitive = auto preserve case = yes short preserve case = yes I suspect I need to make some changes to the smb-shares.conf? Thank you! Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.