diff --git a/build_debian.sh b/build_debian.sh index 7b215772e77d..83d9a687cd4c 100755 --- a/build_debian.sh +++ b/build_debian.sh @@ -596,6 +596,14 @@ export built_by="$USER@$BUILD_HOSTNAME" export sonic_os_version="${SONIC_OS_VERSION}" j2 files/build_templates/sonic_version.yml.j2 | sudo tee $FILESYSTEM_ROOT/etc/sonic/sonic_version.yml +# Default users info +export password_expire="$( [[ "$CHANGE_DEFAULT_PASSWORD" == "y" ]] && echo true || echo false )" +export username="${USERNAME}" +export password="$(sudo grep ^${USERNAME} $FILESYSTEM_ROOT/etc/shadow | cut -d: -f2)" +j2 files/build_templates/default_users.json.j2 | sudo tee $FILESYSTEM_ROOT/etc/sonic/default_users.json +sudo LANG=c chroot $FILESYSTEM_ROOT chmod 600 /etc/sonic/default_users.json +sudo LANG=c chroot $FILESYSTEM_ROOT chown root:shadow /etc/sonic/default_users.json + ## Copy over clean-up script sudo cp ./files/scripts/core_cleanup.py $FILESYSTEM_ROOT/usr/bin/core_cleanup.py diff --git a/files/build_templates/default_users.json.j2 b/files/build_templates/default_users.json.j2 new file mode 100644 index 000000000000..3e2e9a48e29b --- /dev/null +++ b/files/build_templates/default_users.json.j2 @@ -0,0 +1,8 @@ +{%- set users_dict = { + username: { + "password": password, + "expire": password_expire + } + } +-%} +{{ users_dict | tojson(indent=4) }} \ No newline at end of file diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index 6723670490c3..29510542a2f2 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -601,12 +601,17 @@ sudo bash -c "echo enabled=false > $FILESYSTEM_ROOT/etc/sonic/updategraph.conf" # Generate initial SONiC configuration file j2 files/build_templates/init_cfg.json.j2 | sudo tee $FILESYSTEM_ROOT/etc/sonic/init_cfg.json -# Copy config-setup script and service file +# Copy config-setup script, conf file and service file j2 files/build_templates/config-setup.service.j2 | sudo tee $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_SYSTEM/config-setup.service sudo cp $IMAGE_CONFIGS/config-setup/config-setup $FILESYSTEM_ROOT/usr/bin/config-setup +sudo mkdir -p $FILESYSTEM_ROOT/etc/config-setup +sudo cp $IMAGE_CONFIGS/config-setup/config-setup.conf $FILESYSTEM_ROOT/etc/config-setup/config-setup.conf echo "config-setup.service" | sudo tee -a $GENERATED_SERVICE_FILE sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable config-setup.service +# Copy reset-factory script and service +sudo cp $IMAGE_CONFIGS/reset-factory/reset-factory $FILESYSTEM_ROOT/usr/bin/reset-factory + # Add delayed tacacs application service sudo cp files/build_templates/tacacs-config.timer $FILESYSTEM_ROOT_USR_LIB_SYSTEMD_SYSTEM/ echo "tacacs-config.timer" | sudo tee -a $GENERATED_SERVICE_FILE diff --git a/files/image_config/config-setup/config-setup b/files/image_config/config-setup/config-setup index 28a3a4373a42..b23d84cf2607 100755 --- a/files/image_config/config-setup/config-setup +++ b/files/image_config/config-setup/config-setup @@ -40,6 +40,7 @@ CONFIG_SETUP_VAR_DIR=/var/lib/config-setup CONFIG_SETUP_PRE_MIGRATION_FLAG=${CONFIG_SETUP_VAR_DIR}/pending_pre_migration CONFIG_SETUP_POST_MIGRATION_FLAG=${CONFIG_SETUP_VAR_DIR}/pending_post_migration CONFIG_SETUP_INITIALIZATION_FLAG=${CONFIG_SETUP_VAR_DIR}/pending_initialization +CONFIG_SETUP_CONF=/etc/config-setup/config-setup.conf TACACS_JSON_BACKUP=tacacs.json @@ -56,17 +57,31 @@ usage() EOF } +# Factory command usage and help +usage_factory() +{ + cat << EOF + Usage: config-setup factory < keep-basic > + + Create factory default configuration and save it to + to ${CONFIG_DB_JSON}. + + keep-basic - Preserves basic configurations only. +EOF +} + # run given script run_hook() { local script="$1" + local script_param="$2" local exit_status=0 if [ -f $script ]; then # Check hook for syntactical correctness before executing it - /bin/bash -n $script + /bin/bash -n $script $script_param exit_status=$? if [ "$exit_status" -eq 0 ]; then - . $script + . $script $script_param fi exit_status=$? fi @@ -82,6 +97,7 @@ run_hook() { run_hookdir() { local dir="$1" local progress_file="$2" + local script_param="$3" local exit_status=0 if [ -d "$dir" ]; then @@ -94,7 +110,7 @@ run_hookdir() { fi for script in $SCRIPT_LIST; do - run_hook $script + run_hook $script $script_param exit_status=$((exit_status|$?)) script_name=$(basename $script) sed -i "/$script_name/d" $progress_file @@ -215,10 +231,33 @@ generate_config() if [ "$1" = "ztp" ]; then /usr/lib/ztp/ztp-profile.sh create ${DEST_FILE} elif [ "$1" = "factory" ]; then + FACTORY_TYPE=$3 rv=1 + if [ "$FACTORY_TYPE" = "keep-basic" ]; then + TMP_FILE="/tmp/tmp_keep_basic.$$.json" + # Verify the DEST_FILE exists and KEEP_BASIC_TABLES was defined in CONFIG_SETUP_CONF + if [ ! -f ${DEST_FILE} ] || [ -z "${KEEP_BASIC_TABLES}" ]; then + # Create empty valid json file + echo {} > ${TMP_FILE} + else + # Create filtered json file with keep-basic tables only + jq 'with_entries(select([.key] | inside($tables)))' --argjson tables "$KEEP_BASIC_TABLES" ${DEST_FILE} > ${TMP_FILE} + fi + # Create factory default + sonic-cfggen -H -k ${HW_KEY} --preset ${DEFAULT_PRESET} > ${DEST_FILE} + rv=$? + if [ $rv -ne 0 ]; then + rm -f ${TMP_FILE} + return $rv + fi + # Merge factory default config with filtered json + jq --indent 4 -s '.[0] * .[1]' ${DEST_FILE} ${TMP_FILE} > tmp.$$.json && mv tmp.$$.json ${DEST_FILE} + rm -f ${TMP_FILE} + fi + # Execute config initialization hooks - run_hookdir ${FACTORY_DEFAULT_HOOKS} ${CONFIG_SETUP_INITIALIZATION_FLAG} + run_hookdir ${FACTORY_DEFAULT_HOOKS} ${CONFIG_SETUP_INITIALIZATION_FLAG} ${FACTORY_TYPE} # Use preset defined in default_sku if [ ! -e ${DEST_FILE} ]; then @@ -415,6 +454,9 @@ boot_config() # read SONiC immutable variables [ -f /etc/sonic/sonic-environment ] && . /etc/sonic/sonic-environment +# read config-setup.conf +[ -f $CONFIG_SETUP_CONF ] && . $CONFIG_SETUP_CONF + ### Execution starts here ### PLATFORM=${PLATFORM:-`sonic-cfggen -H -v DEVICE_METADATA.localhost.platform`} # Parse the device specific asic conf file, if it exists @@ -439,7 +481,13 @@ fi # Process factory default configuration creation request if [ "$CMD" = "factory" ]; then - generate_config factory ${CONFIG_DB_JSON} + FACTORY_TYPE=$2 + if [ "$FACTORY_TYPE" = "help" ] || [ "$FACTORY_TYPE" = "-h" ] || \ + [ "$FACTORY_TYPE" = "--help" ]; then + usage_factory + exit 1 + fi + generate_config factory ${CONFIG_DB_JSON} ${FACTORY_TYPE} fi # Take a backup of current configuration diff --git a/files/image_config/config-setup/config-setup.conf b/files/image_config/config-setup/config-setup.conf new file mode 100644 index 000000000000..f5122f3399fb --- /dev/null +++ b/files/image_config/config-setup/config-setup.conf @@ -0,0 +1,4 @@ +# conf file for config-setup +# file: /etc/config-setup/config-setup.conf +# +KEEP_BASIC_TABLES='["MGMT_PORT","MGMT_INTERFACE","MGMT_VRF_CONFIG","PASSW_HARDENING"]' \ No newline at end of file diff --git a/files/image_config/reset-factory/reset-factory b/files/image_config/reset-factory/reset-factory new file mode 100755 index 000000000000..fc86737c9d07 --- /dev/null +++ b/files/image_config/reset-factory/reset-factory @@ -0,0 +1,197 @@ +#!/bin/bash +########################################################################### +# SONIC Factory reset script # +# /usr/bin/reset-factory # +# This script is used to reset the system to factory settings. # +# It creates factory default configuration and save it to config_db.json. # +# Also, it clear logs, tech-support, reboot-cause files, warmboot files, # +# docker containers non-default users, users history files and # +# home directories. # +########################################################################### + +# Initialize constants +CONFIG_DB_JSON=/etc/sonic/config_db.json +DEFAULT_USERS_FILE=/etc/sonic/default_users.json +PERMLOG=/var/log/systemlog +SONIC_VERSION=$(sonic-cfggen -y /etc/sonic/sonic_version.yml -v build_version) +SONIC_OVERLAY_UPPERDIR="/host/image-$SONIC_VERSION/rw/etc/sonic" + +SERVICES_STOPPED=0 +trap "error_cleanup" HUP INT QUIT PIPE TERM + +# Command usage and help +usage() +{ + cat << EOF + Usage: reset-factory < keep-all-config | only-config | keep-basic > + + Create factory default configuration and save it to + to ${CONFIG_DB_JSON}. + Clears logs, system files and reboot the system. + + Default - Reset configurations to factory default. Logs and files will be deleted. + keep-all-config - Preserves all configurations after boot. Logs and files will be deleted. + only-config - Reset configurations to factory default. Logs and files will be preserved. + keep-basic - Preserves basic configurations only after boot. Logs and files will be deleted. +EOF +} + +run_reboot() +{ + reboot + # If for any reason we reach this code, then force reboot + rc=$? + if [ $rc -ne 0 ]; then + # Force reboot + reboot -f + fi +} + +error_cleanup() +{ + if [ ! -z "${TEMP_CFG}" ]; then + # Recover config_db.json file + mv ${TEMP_CFG} ${CONFIG_DB_JSON} + fi + + if [ $SERVICES_STOPPED -eq 0 ]; then + ERRMSG="reset-factory: halted with error before stopping critical services; exiting" + logger $ERRMSG + echo $ERRMSG + exit 1 + else + ERRMSG="reset-factory: halted with error after stopping critical services; rebooting" + logger $ERRMSG + echo $ERRMSG + run_reboot + fi +} + +# Restore original /etc/sonic folder by clearing the folder in overlayfs upperdir +clear_sonic_dir() +{ + EXCLUDE_LIST="${CONFIG_DB_JSON}\|/etc/sonic/sonic-environment" + find $SONIC_OVERLAY_UPPERDIR -type f | grep -ve ${EXCLUDE_LIST} | xargs rm -rf + # remount root + mount -o remount / +} + +# Get list of defaults users names and passwords from DEFAULT_USERS_FILE +# Delete non-default users and restore default password of default users +reset_users() +{ + if [ ! -f "${DEFAULT_USERS_FILE}" ]; then + echo "Error: Failed to get default users information" + return + fi + # Get default user accounts + default_users=$(jq -r '. | keys[]' $DEFAULT_USERS_FILE) + EXCLUDE_LIST=$(echo $default_users | tr ' ' '|') + # Get non-default user accounts + other_users=$(getent passwd | awk -F: '($3>=1000 && $3<=60000) {print $1}' | grep -E -v $EXCLUDE_LIST) + echo "Remove non-default users" + for user in ${other_users[@]} + do + # avoid printing home directory and mail spool errors + userdel -rf $user 2> /dev/null + done + echo "Restore default users passwords" + for user in ${default_users[@]} + do + # Restore default password + user_pass=$(jq -r '.[$user].password' --arg user "${user}" $DEFAULT_USERS_FILE) + echo "$user:$user_pass" | chpasswd -e + # Check if we need to expire password + expire=$(jq -r '.[$user].expire' --arg user "${user}" $DEFAULT_USERS_FILE) + [ "${expire}" == "true" ] && passwd -e ${user} + done +} + +# Only root can run reset factory +if [ $UID != 0 ]; then + echo "You must be root to reset system to factory settings" + exit 1 +fi + +CMD=$1 +FACTORY_TYPE= + +if [ "$CMD" = "keep-all-config" ] || [ "$CMD" = "only-config" ] || \ + [ "$CMD" = "keep-basic" ] || [ -z "$CMD" ]; then + FACTORY_TYPE=$CMD +else + usage + exit 1 +fi + +SERVICES_STOPPED=1 +echo "Stop critical services" +monit unmonitor container_checker +systemctl stop sonic.target --job-mode replace-irreversibly + +rc=$? +if [ $rc -ne 0 ]; then + error_cleanup +fi + +DATE=$(date "+%Y/%m/%d %H:%M:%S") +HOSTNAME=$(hostname | sed 's/\./ /' | awk '{print $1}') +printf "%s %s reset-factory: resetting system to factory defaults\n" "$DATE" "$HOSTNAME" >> $PERMLOG + +# Backup and delete config_db.json +TEMP_CFG="/tmp/temp_config_db.$$" +cp ${CONFIG_DB_JSON} ${TEMP_CFG} +if [ "$FACTORY_TYPE" != "keep-basic" ] && [ "$FACTORY_TYPE" != "keep-all-config" ]; then + rm -f ${CONFIG_DB_JSON} +fi + +echo "Call config-setup factory" +config-setup factory $FACTORY_TYPE +rc=$? +if [ $rc -ne 0 ]; then + error_cleanup +fi + +if [ "$FACTORY_TYPE" != "only-config" ]; then + + if [ "$FACTORY_TYPE" != "keep-basic" ]; then + + # Delete non-default users and restore default users passwords + reset_users + + echo "Delete bash, python and vim history files" + find /home /root -type f -name ".bash_history" -o -name ".python_history" -o -name ".viminfo" | xargs rm -rf + + echo "Delete any non-dotfiles in users home directories" + find /home/ /root -type f ! -iname ".*" -delete + fi + + echo "Remove all docker containers except the database" + database_pattern=($(docker ps -a -q -f "name=database" | paste -sd '|' -)) + docker rm -f $(docker ps -a -q | egrep -v ${database_pattern}) > /dev/null + + echo "Clear sonic directory" + clear_sonic_dir + + echo "Clear warmboot folder" + find /host/warmboot/ -type f -delete + + echo "Delete reboot-cause files and symlinks" + find /host/reboot-cause/ -type l,f -delete + + echo "Delete tech-support files" + rm -rf /var/dump/* + + echo "Delete logs files" + find /var/log/ -type f ! -iname "wtmp" ! -iname "btmp" ! -iname "lastlog" ! -iname "systemlog" -delete + + # Clear wtmp, utmp and lastlog files + rm -rf /var/log/btmp.* + cat /dev/null > /var/log/btmp + rm -rf /var/log/wtmp.* + cat /dev/null > /var/log/wtmp + rm -rf /var/log/lastlog.* + cat /dev/null > /var/log/lastlog +fi + +run_reboot