SMB Share Auto Write Protection


Recommended Posts

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:

  1. Install the "User Scripts" plugin from "Apps"
  2. Add a new script and paste the following script
  3. Set the schedule to "Custom" and the value to * * * * *
    image.png.585ef2acdf878e264a906a1be6e4add2.png
    (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

 

Link to comment
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.

Link to comment
  • 1 year later...

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

 

Link to comment

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.