Unix Groups

From DreamHost
Jump to: navigation, search
The instructions provided in this article or section are considered advanced.

You are expected to be knowledgeable in the UNIX shell.
Support for these instructions is not available from DreamHost tech support.
Server changes performed by DreamHost may break your modifications. You, as the user, are responsible for troubleshooting and resolving issues with your customizations.

The instructions provided in this article or section require shell access unless otherwise stated.

You can use the PuTTY client on Windows, or SSH on UNIX and UNIX-like systems such as Linux or Mac OS X.
Your account must be configured for shell access in the Control Panel.
More information may be available on the article's talk page.


Overview

The primary use of Unix Groups is to assign “group ownership” of files and directories. You can configure this in the panel on the (Panel > 'Users' > 'Unix Groups') page. However, please note that only users with Enhanced User Security disabled can have their user group changed.

Creating a Unix Group

To use UNIX Groups to give individual FTP users access to specific subdirectories of a website:

  1. Navigate to the (Panel > 'Users' > 'Manage Users') page.
  2. Create a few users with Shell access. View the Enabling Shell Access article for instructions.
    • In this example, the new Shell users 'webmaster_unixgroup' and 'contributor_unixgroup' are named.
    Important icon.png Important: Make sure Enhanced User Security is disabled on each Shell user or the following steps will not function.


  3. Navigate to the (Panel > 'Domains' > 'Manage Domains') page.
  4. Assign your domain to the new Shell user you just created named 'webmaster'. View the following article for instructions:
  5. Navigate to the (Panel > 'Users' > 'Unix Groups') page.
    01 Unix Groups.fw.png
  6. Click the Add Custom Group button.
    02 Unix Groups.fw.png
  7. Create a group with both users in it. For example, you can name it "contributor_group".
    Note2 icon.png Note: In this example the following names are used:
    • Unix group name – 'contributor_group_test'.
    • The two users are named 'webmaster_unixgroup' and 'contributor_unixgroup'


    After clicking Add Custom Group! the following confirmation message appears:
    03 Unix Groups.fw.png
  8. SSH to the server as your 'webmaster' Shell user and then give the "contributor_group_test" access to whichever directory you want the user "contributor_unixgroup" to access. For example, give the user 'contributor_unixgroup' user access to the directory named '/contributor_dir'.
    chgrp -R contributor_group_test contributor_dir
    Note2 icon.png Note: If you only use the command 'chgrp' it only changes the permissions on the single directory named /contributor_dir. If you want all subdirectories to also change permissions, make sure to add the -R option.


  9. While still logged in as "webmaster", give the group (and all users in it) the desired level of access for that directory.
    • To provide full read/write/execute access to /contributor_dir and all of its subdirectories and files, run this command:
    chmod -R g+rwxs contributor_dir
  10. Exit the session and then log back in as 'contributor_unixgroup'.
  11. Make a symbolic link (i.e., a shortcut) to the directory with this command:
    ln -s /home/webmaster_unixgroup/example.com/contributor_dir /home/contributor_unixgroup/
    Note2 icon.png Note: Note the space after the remote path, but just before the local /home/<username>/ path, which creates a symbolic link called "contributor_dir" in the home directory of 'contributor_unixgroup'.


    04 Unix Groups.fw.png
Important icon.png Important: To upload or download files, 'contributor_unixgroup' must use SFTP, because FTP does not work with symbolic links.


Caveats

  • If you have any .cgi files, the above command makes them fail to run and generates HTTP 500 errors. Be sure that you leave the group for the .cgi and its parent directory as the default group. In other words, if your .cgi file is in /home/joe/example.com/cgi/fun.cgi then the following directories/files must be joe's default group: /home/joe/example.com/cgi, /home/joe/example.com/cgi/fun.cgi.
  • Some file servers have an internal limit of 16 groups. If you are in more than 16 groups, only the first 16 (as listed by the "groups" command) are considered.

Fundamentals of Unix Groups at DreamHost

  • Every DreamHost shared hosting account has a Unix Group in the form of pg####### (where each # is a [0-9] digit):
  • This group is called "a default group" and there is exactly 1 per account.
  • Every Unix User and every Mailbox belonging to the account is a member of this group. Your files and folders are not shared outside of your account.
  • Each Unix User's default Unix Group is entirely determined by the Unix User's Enhanced User Security check-box option.
- If Enhanced User Security is NOT selected, the default pg####### group is the user's group and its permissions are 751.
- If Enhanced User Security IS selected, the user's home directory has its permissions set to 710 and its group is changed to "adm".
  • None of the above can be changed except Enhanced User Security.
  • Every Unix Group of an account can contain any subset (combination) of Unix users which belong to that same DreamHost account.
  • Unix Groups cannot be nested.

Advanced: Protecting your files from other members of your group

The following sections are for advanced users with a solid knowledge of the Unix command-line.

Option one: chgrp

To keep other members of your group from accessing your files, you can create a new group in the panel and add only yourself to the group. Then, change the group of all of your files that you want to protect:

chgrp -R my_group /home/user/directory

The down side to this is that you have to chgrp any new files when they are created in order to continue protecting them.

Option two: chmod g-x

To keep everyone from your group out, you can also use this method. Simply remove execute permissions from the directory you want to protect:

chmod g-x /home/user/directory

The down side to this is that you have to remember:

  • to do this for every folder that you create, and
  • not to do it to any of your web folders.

Option three: SetGID bit

Set the GID bit on your root folder and any already existing folders:

chgrp -R <my_group> ~/
find ~/ -xdev -user <username> -group <my_group> -type d -print -exec chmod +s {} \;

Line #1 of this changes the group ownership of all 'files' and 'folders' (within your home folder ~/) to the specified <my_group>.

Line #2 'sets the GID bit' for all the 'folders' within your home folder and it's subfolders. In particular, it modifies all that are owned by the specified <my_group> which should be all of them if the command from line #1 was executed successfully. Setting the GID bit on a folder changes it such that subsequently any folders or files created within it will be owned by the specified <my_group>.

The down side to this is that if the group of a folder is ever changed, it loses the GID flag and anything under that folder is now created with your default (pg#######) group again.

WARNING: POSSIBLE OUTDATED INFORMATION!
DreamHost does not directly support any of the features described on this page and is not responsible for keeping this content updated or accurate. Use at your own risk!
There may be additional information on the talk page.

Option four: Cron job

To effectively change your default group and make sure that anything new created is in that group, create a cron job that runs the setperms.sh script every hour or so:

  1. Create your new group in the panel.
  2. Once your group has been created, SSH into your account and verify that you are a member of the group by typing:
    groups
  3. See if your new group is listed in the results.
  4. Protect your webfolder appropriately:
    chmod 751 <webfolder>
  5. Create the setperms.sh file in your home folder.

setperms.sh

#!/bin/bash

LOCKFILE=$HOME"/.set-permissions.lock"
ABORT=0
NEEDSHELP=0

Usage()
{
        echo "Usage: $0 -g <group> -p <group> -u <user>"
        echo "          -w <folder> [[-w <folder>] ...]"
        echo " -g|--group <group>       The group to chgroup your files to"
        echo " -p|--primary <group>     The group that files must have to be chgrouped"
        echo " -u|--user <user> The user you are running as. The user must"
        echo "                  be a member of both groups specified."
        echo " -w|--web <folder>        The foldername of your web directory that must be"
        echo "                  world readable. You must specify at least one folder,"
        echo "                  but may specify any number of folders to be"
        echo "                  accessable from the web"
        ABORT=1
}

ParseCommandLine()
{
        echo "Parsing..."
        # Parse the command line
        if [ $# -eq 0 ]; then
                NEEDSHELP=1
        fi

        i=0
        while [ -n "$1" ]; do
                case $1 in
                        "-g" | "--group")
                                [ -n "$2" ] || Usage
                                GROUP=$2
                                shift
                                ;;
                        "-h" | "--help")
                                NEEDSHELP=1
                                shift
                                ;;
                        "-p" | "--primary")
                                [ -n "$2" ] || Usage
                                PRIMARYGROUP=$2
                                shift
                                ;;
                        "-u" | "--user")
                                [ -n "$2" ] || Usage
                                USERNAME=$2
                                shift
                                ;;
                        "-w" | "--web")
                                [ -n "$2" ] || Usage
                                WEBFOLDER[$i]=$2
                                i=$i+1
                                shift
                                ;;
                        *)
                                Usage;;
                esac
                shift
                if [ $ABORT -eq 1 ]; then
                        return
                fi
        done

        if [ ${NEEDSHELP} -lt 1 ]; then
                if [ -z $GROUP ]; then
                        echo "You must specify a group (-g)"
                        NEEDSHELP=1
                fi

                if [ -z $PRIMARYGROUP ]; then
                        echo "You must specify a primary group (-p)"
                        NEEDSHELP=1
                fi

                if [ -z $USERNAME ]; then
                        echo "You must specify a user (-u)"
                        NEEDSHELP=1
                fi

                if [ -z $WEBFOLDER ]; then
                        echo "You must specify at least one webfolder (-w)"
                        NEEDSHELP=1
                fi
        fi
}

ValidateGroups()
{
        echo "Validating group memberships"
        # Check the primary group
        groups ${USERNAME} | grep "${PRIMARYGROUP}" 2>&1 > /dev/null
        if [ $? -gt 0 ]; then
                echo "User ${USERNAME} is not a member of ${PRIMARYGROUP}"
                ABORT=1
        fi

        # Check the target group
        groups ${USERNAME} | grep "${GROUP}" 2>&1 > /dev/null
        if [ $? -gt 0 ]; then
                echo "User ${USERNAME} is not a member of ${PRIMARYGROUP}"
                ABORT=1
        fi
}

RunCommands()
{
        # Deal with stuff in the webfolders first
        for W in ${WEBFOLDER[*]}
        do
                echo "Setting file permissions in web folder ${W}"
                find ~/$W -xdev -user ${USERNAME} -group ${PRIMARYGROUP} -not -type d -print -exec chmod o+r,o-w {} \; -exec chgrp ${GROUP} {} \;
                echo "Setting folder permissions in web folder ${W}"
                find ~/$W -xdev -user ${USERNAME} -group ${PRIMARYGROUP} -type d -print -exec chmod o+x,o-wr {} \; -exec chgrp ${GROUP} {} \;
        done

        # Change anything in the primary group to the new group
        echo "Setting group membership on items currently in group ${PRIMARYGROUP}"
        find ~/ -xdev -user ${USERNAME} -group ${PRIMARYGROUP} -not -type l -print -exec chmod o-rwx {} \; -exec chgrp ${GROUP} {} \;

        # Cleanup any messes we may have made
        chmod o+x .
        chmod u+x,g-wx,o-wx $0
        for W in ${WEBFOLDER[*]}
        do
                chmod o+x $W
        done
}

Main()
{
        # Make sure that we're the only instance of the script running
        if [ -e $LOCKFILE ]
        then
                echo "Either another instance of this script is running or you need to remove the $LOCKFILE file"
                exit 0
        fi
        touch $LOCKFILE

        ParseCommandLine "$@"

        if [ ${NEEDSHELP} -eq 0 ]; then
                # All the parameters were there, now make sure they're valid
                ValidateGroups
        fi

        if [ ${NEEDSHELP} -eq 0 ]; then
                # Run the commands
                echo "Running the commands"

                RunCommands $0
        elif [ ${ABORT} -lt 1 ]; then
                Usage
        fi

        # Remove the temporary lockfile
        rm $LOCKFILE
}

Main "$@"

Make sure that the file is in Unix mode (rather than DOS mode) so that weird things don't happen:

dos2unix setperms.sh

Chmod the setperms.sh script so that you can execute it and no one else can:

chmod 700 setperms.sh

Chgrp the setperms.sh script to something other than your default (pg#######) group:

chgrp <newgroup> setperms.sh

Run the script for the first time to get everything set up properly and make sure it's working. This will probably take a long while to run the first time as it will touch just about, and will print out, all of the files and folders that it is making changes to:

./setperms.sh -g <newgroup> -p <primary group, pg#######> -u <username> -w <webfolder>

For example:

./setperms.sh -g fred -p pg1234567 -u fred -w fred.com

If you have multiple web folders, you can put additional -w <webfolder> statements on the end.

Setting up the cron job

crontab -e

Install the script to run every hour or so, and to dump all of the output to the garbage:

0 * * * * /home/fred/setperms.sh -g fred -p pg1234567 -u fred -w fred.com >/dev/null 2>&1

If you want the results to be emailed to you every time the script runs:

MAILTO=<your@email.address>
0 * * * * /home/fred/setperms.sh -g fred -p pg1234567 -u fred -w fred.com

If you want the results to be emailed to you once a day:

MAILTO=<your@email.address>
0 * * * * /home/fred/setperms.sh -g fred -p pg1234567 -u fred -w fred.com >> /home/fred/setperms.log 2>&1
1 1 * * * cat /home/fred/setperms.log
2 1 * * * rm -rf /home/fred/setperms.log

Timings

Sample timings:

#files |    743   |   1961   |    6709   |   19878   |   31302   |   31477   |
-------|----------|----------|-----------|-----------|-----------|-----------|
  real | 0m2.600s | 0m6.427s | 0m48.321s | 1m48.668s | 4m39.025s | 4m41.896s |
  user | 0m0.050s | 0m0.120s |  0m0.090s |  0m0.170s |  0m0.460s |  0m0.570s |
   sys | 0m0.130s | 0m0.060s |  0m1.230s |  0m0.950s |  0m3.630s |  0m3.710s |
-----------------------------------------------------------------------------/

This is for runs with no files/folders that needed changing. As the number of files/folders that have to be modified increases, so will the timings.

Details

This script traverses your entire structure looking for and doing the following things:

Order | Looking for               | Action if found
------|---------------------------------------------------------------
  1   | In each webfolder, a      | Remove world write, add world
      | file, your username,      | read, make group the new group
      | primary group (pg####),   | 
------|---------------------------------------------------------------
  2   | In each webfolder, a      | Remove world read and write, add
      | folder, your username,    | world execute, make group the
      | primary group (pg####),   | new group.
------|---------------------------------------------------------------
  3   | From your home, a file    | Remove world read, write, execute,
      | or folder, your username, | world execute, make group the
      | primary group (pg####),   | new group.
------|---------------------------------------------------------------
  4   | On your home folder       | Add world execute
------|---------------------------------------------------------------
  5   | On the script             | Add owner execute, remove group
      |                           | and world write and execute
------|---------------------------------------------------------------
  6   | On each webfolder         | Add world execute
------|---------------------------------------------------------------

Pros and cons

Pros
  • Anything that is created by your user from anywhere (ssh, sftp, ftp, and so on) is treated as "new" by the script and processed. Anything that has a group membership other than your primary (pg#######) group is treated as "old" and left alone. This means that you know that anything new you create will within the hour be assigned the correct group and permissions to be secure, but that if you want one particular item (file or folder) to have a non-default group or permissions it won't be messed with.
  • Doesn't care what your umask is set at, it sets the permissions to properly protect the private files and properly serve the web-accessable ones.
Cons
  • This script has 3 find commands in it, two of which are executed once for each web folder that you have. If you have a lot of web folders, this could potentially add up and might not be a viable way to go.
  • The initial run takes quite a long time; even with under 800 files it still took close to a minute.
  • Ignores your umask (it fits on both sides).
  • Requires shell access for each account.

If a symbolic link is created, an SFTP user is also able to access shared sub-directories/files through an FTP client.

See also