7
\$\begingroup\$

Here is a shell script which installs and configure owncloud on a Debian server. It also installs apache2 and MySQL as dependencies and fail2ban with a specific rule for owncloud. Apache2 is also configured to use a self signed SSL certificate.

The script seems to work properly (everything is installed and works fine) my question is about the possible security issues that could be created:

  • Is the ssl certificate correctly issued and installed?
  • Is there a potential issue with the handling of mysql?
  • Is there something else that I should see?

The official Github repo of this file is here.

#!/bin/bash
#Some bash script optimization for robustness (More information: www.davidpashley.com/articles/writing-robust-shell-scripts/)
#Break if the script uses unset variables
set -o nounset
#Break if a command has a non-true return value
set -o errexit
echo_info () {
 echo "--------------------------------------"
 echo "| INFO: 1ドル"
 echo "--------------------------------------"
}
echo_error () {
 echo "--------------------------------------"
 echo "| ERROR: 1ドル"
 echo "--------------------------------------"
}
is_installed () {
 #Check if a package is installed (More information: https://askubuntu.com/questions/319307/reliably-check-if-a-package-is-installed-or-not)
 if dpkg --get-selections | grep -q "^1ドル[[:space:]]*install$" >/dev/null; then
 echo_error "1ドル is already installed"
 exit 1
 fi
}
check_not_installed () {
 echo_info "Check if some package is already installed. If this is the case the script stops because it could not be performed without risc."
 is_installed apache2
 is_installed mysql-server-5.5
 is_installed owncloud
 is_installed fail2ban 
}
check_root () {
 if [ "$(id -u)" != "0" ]; then
 echo_error "The script must be called as root user!"
 exit 1
 fi
}
install_owncloud () {
 cd /tmp
 #Automatic installation of owncloud (More information: https://software.opensuse.org/download/package?project=isv:ownCloud:community&package=owncloud)
 echo_info "Add package repository"
 echo 'deb http://download.owncloud.org/download/repositories/8.2/Debian_8.0/ /' >> /etc/apt/sources.list.d/owncloud.list
 wget -nv https://download.owncloud.org/download/repositories/8.2/Debian_8.0/Release.key -O Release.key
 apt-key add - < Release.key 
 rm Release.key 
 echo_info "Install owncloud"
 #Because a dependency the package mysql-server-5.5 is installed too. During the installation a prompt ask for the root password. The next lines set it with the value of the variable. (More information: http://www.microhowto.info/howto/perform_an_unattended_installation_of_a_debian_package.html)
 echo "mysql-server-5.5 mysql-server/root_password password $mysqlRootPw" | debconf-set-selections
 echo "mysql-server-5.5 mysql-server/root_password_again password $mysqlRootPw" | debconf-set-selections
 apt-get update
 apt-get --assume-yes install owncloud
}
create_mysql_db () {
 echo_info "Create mysql database for owncloud"
 #Create a new database and user for owncloud (More information: http://www.bluepiccadilly.com/2011/12/creating-mysql-database-and-user-command-line-and-bash-script-automate-process)
 mysql=`which mysql`
 Q1="CREATE DATABASE IF NOT EXISTS $ocDb;"
 Q2="GRANT USAGE ON *.* TO $ocDbUser@localhost IDENTIFIED BY '$ocDbUserPw';"
 Q3="GRANT ALL PRIVILEGES ON $ocDb.* TO $ocDbUser@localhost;"
 Q4="FLUSH PRIVILEGES;"
 SQL="${Q1}${Q2}${Q3}${Q4}"
 $mysql -uroot -p$mysqlRootPw -e "$SQL"
}
patch_apache_filesize () {
 echo_info "Patch apache configuration"
 #Patch filesize in the htaccess (More Information: http://blog.webernetz.net/2015/07/15/yet-another-owncloud-installation-guide/)
 sed -i "s/php_value upload_max_filesize .*/php_value upload_max_filesize $maxFileSize/" /var/www/owncloud/.htaccess
 sed -i "s/php_value post_max_size .*/php_value post_max_size $maxFileSize/" /var/www/owncloud/.htaccess
 sed -i "s/php_value memory_limit .*/php_value memory_limit $maxFileSize/" /var/www/owncloud/.htaccess
 /etc/init.d/apache2 restart
}
configure_owncloud () {
 echo_info "Configure owncloud"
 #The data dir should not be under /var/www for security reasons, so the dir must be created (More information: https://doc.owncloud.org/server/8.1/admin_manual/configuration_server/harden_server.html)
 mkdir $ocDataDir
 chown -R ${htuser}:${htgroup} ${ocDataDir}/
 #Use the cli for first configuration (More information: https://doc.owncloud.org/server/8.1/admin_manual/installation/command_line_installation.html)
 cd /var/www/owncloud
 sudo -u $htuser php occ maintenance:install --database "mysql" --database-name "$ocDb" --database-user "$ocDbUser" --database-pass "$ocDbUserPw" --admin-user "$ocAdminUser" --admin-pass "$ocAdminUserPw" --data-dir "$ocDataDir"
 #Add the hostname and ip to the trusted domains, so that it could be reached from outside (More Information: https://doc.owncloud.org/server/8.1/admin_manual/installation/installation_wizard.html?highlight=trusted_domains#label-trusted-domains)
 sed -i "/.*0 => 'localhost',/a \\ 1 => '$hostname',\n 2 => '$ip'," /var/www/owncloud/config/config.php
 /etc/init.d/apache2 restart
}
install_fail2ban () {
 echo_info "Install fail2ban"
 apt-get --assume-yes install fail2ban
 #First configure the owncloud logfile
 logFileMasked=$(echo $logFile | sed 's/\//\\\//g')
 logTimezoneMasked=$(echo $logTimeZone | sed 's/\//\\\//g')
 sed -i "s/ 'logtimezone' => 'UTC',/ 'logtimezone' => '$logTimezoneMasked',\n 'logfile' => '$logFileMasked',\n 'loglevel' => '2',/" /var/www/owncloud/config/config.php
 touch $logFile
 chown ${htuser}:${htgroup} $logFile
 #Now configure fail2ban (More Information: http://www.rojtberg.net/711/secure-owncloud-server/, https://got-tty.org/archives/owncloud-6-sicherheit-durch-fail2ban.html)
 echo -e "[Definition]\nfailregex={\"app\":\"core\",\"message\":\"Login failed: user '.*' , wrong password, IP:<HOST>\",\"level\":2,\"time\":\".*\"}\n {\"app\":\"core\",\"message\":\"Login failed: '.*' \(Remote IP: '<HOST>', X-Forwarded-For: '.*'\)\",\"level\":2,\"time\":\".*\"}\n {\"reqId\":\".*\",\"remoteAddr\":\"<HOST>\",\"app\":\"core\",\"message\":\"Login failed: .*\",\"level\":2,\"time\":\".*\"}" > /etc/fail2ban/filter.d/owncloud.conf
 echo -e "[owncloud]\nenabled = true\nfilter = owncloud\nport = http,https\nmaxretry = $maxRetry\nlogpath = $logFile" >> /etc/fail2ban/jail.local
 /etc/init.d/fail2ban restart
}
enable_apache_ssl () {
 echo_info "Enable and compel apache ssl with default self-signed certifiacte of debian"
 #Uses the default self-signed certificate of debian (More information: https://doc.owncloud.org/server/8.1/admin_manual/installation/source_installation.html#enabling-ssl)
 a2enmod ssl
 a2ensite default-ssl
 #Force https for every connection (More information: https://doc.owncloud.org/server/8.1/admin_manual/configuration_server/harden_server.html)
 a2enmod headers
 sed -i "/.*<VirtualHost.*/a \\\tServerName $hostname\n\tRedirect permanent \/ https:\/\/$hostname\/" /etc/apache2/sites-available/000-default.conf
 sed -i "/.*<VirtualHost.*/a \\\t\tServerName $hostname\n\t\tHeader always add Strict-Transport-Security \"max-age=15768000\"" /etc/apache2/sites-available/default-ssl.conf
 /etc/init.d/apache2 restart
}
enable_apc_cache () {
 echo_info "Enable apc cache"
 #Install and configure apcu (More information: https://owncloud.org/blog/making-owncloud-faster-through-caching/)
 apt-get --assume-yes install php-apc
 sed -i "s/);/ 'memcache.local' => '\\\OC\\\Memcache\\\APCu',\n);/" /var/www/owncloud/config/config.php
 /etc/init.d/apache2 restart
}
#Read in the variables in an interacive mode. Too make it a little more comfortable, the following functions need to be defined.
#Read a value and set the default value as input (More Information: http://stackoverflow.com/questions/2642585/read-a-variable-in-bash-with-a-default-value)
read_value () {
 unset value
 read -e -i 1ドル value
}
#Hide the input and mask it with stars (More Information: http://stackoverflow.com/questions/1923435/how-do-i-echo-stars-when-reading-password-with-read) (Part of the read_pw function)
read_pw_loop_masked () {
 unset password
 while IFS= read -p "$prompt" -r -s -n 1 char
 do
 if [[ $char == $'0円' ]]
 then
 break
 fi
 prompt='*'
 password+="$char"
 done
 echo
}
#Ask for the password two times (Part of the read_pw function)
read_pw_loop_compare () {
 prompt="Enter Password:"
 read_pw_loop_masked
 password1=$password
 prompt="Reenter Password:"
 read_pw_loop_masked
 password2=$password
}
#Loop the password question until the two values match (Part of the read_pw function)
read_pw_loop () {
 read_pw_loop_compare
 while [ "$password1" != "$password2" ] ; do
 echo "Please retype, because the passwords did not match."
 read_pw_loop_compare 
 done
 password=$password1
}
#Main function for reading a password
read_pw () {
 echo $*
 read_pw_loop
}
ask_for_values () {
 echo_info "The script now ask for some values that are necessary for the installation."
 #You can customize the default values of the variables here or set a static value
 #Hostname and IP (the command retrieve this information automatically in the case that only the standard ethernet interface is installed)
 hostname=`hostname`
 echo "Please enter the hostname (The automatically indentified value is filled in, but you can easily change it if it is wrong.):"
 read_value $hostname
 hostname=$value
 ip=`hostname -I`
 echo "Please enter the ip of the outside interface (The automatically indentified value is filled in, but you can easily change it if it is wrong.):"
 read_value $ip
 ip=$value
 #Mysql configuration
 read_pw "Please enter a password for the root user of mysql:"
 mysqlRootPw=$password
 echo "Please enter the name of the mysql database for owncloud (The default value is filled in, but you can easily change it.):"
 read_value owncloud
 ocDb=$value
 echo "Please enter the name of the mysql user for owncloud (The default value is filled in, but you can easily change it.):"
 read_value owncloud
 ocDbUser=$value
 read_pw "Please enter a password for the owncloud user of mysql:"
 ocDbUserPw=$password
 #Apache2 configuration
 echo "Please enter the maximal size of files that could be uploaded to owncloud (The default value is filled in, but you can easily change it.):"
 read_value 1024M
 maxFileSize=$value
 #Typically user and group filled static
 htuser='www-data'
 htgroup='www-data'
 #Owncloud configuration
 echo "Please enter the name of the owncloud administrator(The default value is filled in, but you can easily change it.):"
 read_value admin
 ocAdminUser=$value
 read_pw "Please enter a password for the owncloud administrator:"
 ocAdminUserPw=$password
 echo "Please enter the path to the folder for files of owncloud (The default value is filled in, but you can easily change it.):"
 read_value /home/owncloud
 ocDataDir=$value
 #Fail2Ban
 logTimeZone=`cat /etc/timezone`
 echo "Please enter the time zone for the owncloud log (The default value is filled in, but you can easily change it.):"
 read_value $logTimeZone
 logTimeZone=$value
 echo "Please enter the path where owncloud log should be saved (The default value is filled in, but you can easily change it.):"
 read_value /var/log/owncloud.log
 logFile=$value
 echo "Please enter the max fails until fail2ban ban an ip (The default value is filled in, but you can easily change it.):"
 read_value 3
 maxRetry=$value
}
generate_self_signed_certificate () {
 echo_info "The script now generate a self signed certificate with a self created ca. For it the script ask for many values. The most necessary you can fill with a dot if you do not have a valid value. The 'extra' attributes you could skip with enter. Only the pass phrase for own-ca.key and the common name are important. The first value is the password for your ca, please type in a strong password and take a note of it. You will need it at least some seconds later. The common name must befit the hostname and you must type it in two times."
 #Generate a self signed certificate with a self created ca (More Information: https://thomas-leister.de/internet/eine-eigene-openssl-ca-erstellen-und-self-signed-certe-ausstellen/)
 # This was necessary because otherwise Apps like DAVdroid didn't work with the owncloud (More Information: https://davdroid.bitfire.at/faq/entry/importing-a-certificate)
 #Create a new ca
 mkdir /etc/ssl/ownca/
 cd /etc/ssl/ownca/
 openssl genrsa -aes256 -out own-ca.key 2048
 #Create the root certificate that is valid for 10 years
 openssl req -x509 -new -nodes -extensions v3_ca -key own-ca.key -days 3650 -out own-ca-root.pem -sha512
 #Create a client certificate that is valid for 10 years and sign it
 openssl genrsa -out self-signed-cert.key 4096
 openssl req -new -key self-signed-cert.key -out self-signed-cert.csr -sha512
 openssl x509 -req -in self-signed-cert.csr -CA own-ca-root.pem -CAkey own-ca.key -CAcreateserial -out self-signed-cert.pem -days 3650 -sha512
 rm self-signed-cert.csr
 mv ./self-signed-cert.pem ../certs
 mv ./self-signed-cert.key ../private
 mv ./own-ca-root.pem ../certs
}
install_self_signed_certificate () {
 sed -i "s/ SSLCertificateFile \/etc\/ssl\/certs\/ssl-cert-snakeoil.pem/ SSLCertificateFile \/etc\/ssl\/certs\/self-signed-cert.pem/" /etc/apache2/sites-available/default-ssl.conf
 sed -i "s/ SSLCertificateKeyFile \/etc\/ssl\/private\/ssl-cert-snakeoil.key/ SSLCertificateKeyFile \/etc\/ssl\/private\/self-signed-cert.key/" /etc/apache2/sites-available/default-ssl.conf
 sed -i "s/ #SSLCertificateChainFile \/etc\/apache2\/ssl.crt\/server-ca.crt/ SSLCertificateChainFile \/etc\/ssl\/certs\/own-ca-root.pem/" /etc/apache2/sites-available/default-ssl.conf
 /etc/init.d/apache2 restart
}
#You can customize the called functions here (you are responsible for looking for dependencies between them)
check_root
check_not_installed
ask_for_values
generate_self_signed_certificate
echo_info "Now the unattended part of the setup is started."
install_owncloud
create_mysql_db
patch_apache_filesize
configure_owncloud
install_fail2ban
enable_apache_ssl
enable_apc_cache
install_self_signed_certificate
echo_info "FINISH"
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Jan 26, 2016 at 15:15
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Naming

Some of the function names could be improved, for example this one:

is_installed () {
 #Check if a package is installed (More information: https://askubuntu.com/questions/319307/reliably-check-if-a-package-is-installed-or-not)
 if dpkg --get-selections | grep -q "^1ドル[[:space:]]*install$" >/dev/null; then
 echo_error "1ドル is already installed"
 exit 1
 fi
}

I expect a function named is_X to behave like a boolean, so that I can build conditional statements with it. But this is not that kind of function, it validates something or else exits the program. I'd name this verify_not_installed or similar.

Other minor issues

if dpkg --get-selections | grep -q "^1ドル[[:space:]]*install$" >/dev/null; then

You don't need to redirect the output of grep -q, the -q flag already takes care of that.


echo_error "1ドル is already installed"
exit 1

Every time you echo_error, you also exit 1. So you could move the exit 1 inside the function, and then probably rename it (I usually call it fatal).


echo 'deb http://download.owncloud.org/download/repositories/8.2/Debian_8.0/ /' >> /etc/apt/sources.list.d/owncloud.list
wget -nv https://download.owncloud.org/download/repositories/8.2/Debian_8.0/Release.key -O Release.key

The base URL is common in both of these commands, so I would put it in a variable to make it easy to change later if needed.


mysql=`which mysql`

Backticks are archaic, use $(...) instead.


Q1="CREATE DATABASE IF NOT EXISTS $ocDb;"
Q2="GRANT USAGE ON *.* TO $ocDbUser@localhost IDENTIFIED BY '$ocDbUserPw';"
Q3="GRANT ALL PRIVILEGES ON $ocDb.* TO $ocDbUser@localhost;"
Q4="FLUSH PRIVILEGES;"
SQL="${Q1}${Q2}${Q3}${Q4}"

This is a tedious and fragile way to construct a longer string. You could pass a here-document directly to mysql:

mysql -uroot -p$mysqlRootPw <<EOF
CREATE DATABASE IF NOT EXISTS $ocDb;
GRANT USAGE ON *.* TO $ocDbUser@localhost IDENTIFIED BY '$ocDbUserPw';
...
EOF

sed -i "s/php_value upload_max_filesize .*/php_value upload_max_filesize $maxFileSize/" /var/www/owncloud/.htaccess
sed -i "s/php_value post_max_size .*/php_value post_max_size $maxFileSize/" /var/www/owncloud/.htaccess
sed -i "s/php_value memory_limit .*/php_value memory_limit $maxFileSize/" /var/www/owncloud/.htaccess

This overwrites the /var/www/owncloud/.htaccess three times. You can perform multiple replacements in a single sed call, and overwrite the file only once:

sed -i \
 -e "s/php_value upload_max_filesize .*/php_value upload_max_filesize $maxFileSize/" \
 -e "s/php_value post_max_size .*/php_value post_max_size $maxFileSize/" \
 -e "s/php_value memory_limit .*/php_value memory_limit $maxFileSize/" \
 /var/www/owncloud/.htaccess

echo -e "[Definition]\nfailregex={\"app\":\"core\",\"message\":\"Login failed: user '.*' , wrong password, IP:<HOST>\",\"level\":2,\"time\":\".*\"}\n {\"app\":\"core\",\"message\":\"Login failed: '.*' \(Remote IP: '<HOST>', X-Forwarded-For: '.*'\)\",\"level\":2,\"time\":\".*\"}\n {\"reqId\":\".*\",\"remoteAddr\":\"<HOST>\",\"app\":\"core\",\"message\":\"Login failed: .*\",\"level\":2,\"time\":\".*\"}" > /etc/fail2ban/filter.d/owncloud.conf

This extremely long line is unreadable. Consider using a here-document instead:

cat <<"EOF" > /etc/fail2ban/filter.d/owncloud.conf
[Definition]
failregex={"app":"core","message":"Login failed: user '.*' , wrong password, IP:<HOST>","level":2,"time":".*"}
 {"app":"core","message":"Login failed: '.*' \(Remote IP: '<HOST>', X-Forwarded-For: '.*'\)","level":2,"time":".*"}
 {"reqId":".*","remoteAddr":"<HOST>","app":"core","message":"Login failed: .*","level":2,"time":".*"}
EOF
answered Sep 14, 2017 at 18:44
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.