On Sat, Jun 23, 2007 at 12:18:05PM -0500, Nicolas Williams wrote:
> Couldn't wait for ZFS delegation, so I cobbled something together; see
> attachment.

I forgot to slap on the CDDL header...
#!/bin/ksh
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

ARG0=$0
PROG=${0##*/}
OIFS="$IFS"

# grep -q rocks, but it lives in xpg4...
OPATH=$PATH
PATH=/usr/xpg4/bin:/bin:/sbin

# Configuration (see usage message below)
#
# This is really based on how a particular server on SWAN is configured,
# with datasets named tank/<zone>-export that are intended to be
# administered by the zone admins, not just the global zone admins.
#
# Maybe it would be better to just used user props to track delegation.
#
USER_ZFS_BASE=tank/users
ZONE_ZFS_BASE=tank
ZONE_ZFS_SUFFIX=-export
PROF_PREFIX="Zoned NFS Mgmt Hack for"
DELEG_ZFS_PROF="ZFS Delegation Hack"

usage () {
    cat <<EOF
Usage: pfexec $PROG [-x] [-n] zfs <zfs arguments>
       pfexec $PROG [-x] [-n] chown <chown args> <dataset>
       pfexec $PROG [-x] [-n] add-zone-profile <zonename>
       pfexec $PROG [-x] [-n] setup

        Options:
                -x      debug
                -n      dry-run
EOF
    fmt <<EOF

        With this program you can execute with privilege any zfs command
        that operates on a filesystem or snapshot named
        $USER_ZFS_BASE/<username>[/*] or
        $ZONE_ZFS_BASE/<zonename>$ZONE_ZFS_SUFFIX[/*] where <username>
        is the user running $PROG or where <zonename> is the name of a
        zone for which the user has have administrative authority.

        You can also delegate administration of ZFS dataset by using
        properties called :owner_user_<username>: (any value will do) or
        :owner_profiles: with a comma-separated list of profiles as its
        value -- any user with one of those profiles can admin the given
        dataset.

        Users must have an RBAC profile granted which allows them to
        execute this command with all privileges (privs=all).

        Administrative authority for a zone is granted by granting a
        profile named

            "${PROF_PREFIX} <zonename>"

        The add-zone-profile adds such profiles.

        The chown sub-command allows users to chown to themselves any
        dataset for which they have authority.

        The setup sub-command creates a profile, "Delegated ZFS Hack"
        which you can grant to users (e.g., to all users via
        PROFS_GRANTED in policy.conf(4)).

        This script must be executed with euid=0 via pfexec(1) or a
        profiled shell.

        <dataset> is always a ZFS dataset name (i.e., no leading '/'!).
EOF
    exit 1
}

err () {
    print -u2 -- "Error: " "$@"
    exit 1
}

realpath () {
    typeset dir dirs
    if [[ "$1" != */* ]]
    then
        IFS=:
        set -A dirs -- $PATH
        IFS="$OIFS"
        for dir in "[EMAIL PROTECTED]"
        do
            if [[ -x "${dir}/$1" ]]
            then
                print -- "${dir}/$1"
                return 0
            fi
        done
    elif [[ "$1" = /* || "$1" = */* ]]
    then
        (cd "${1%/*}" > /dev/null && print -- "$(/bin/pwd)/${1##*/}")
        return $?
    fi
    err "Can't resolve path to $PROG"
}

validate_object () {
    typeset i j prop op val user zone profs


    if [[ "$1" = "${USER_ZFS_BASE}"/* ]]
    then
        # A user's dataset
        user=${1#$USER_ZFS_BASE/}
        user=${user%%/*}
        [[ "$username" = "$user" ]] && return 0
    elif [[ "$1" = "${ZONE_ZFS_BASE}"/* ]]
    then
        # A zone's dataset
        zone=${1#$ZONE_ZFS_BASE/}
        zone=${zone%${ZONE_ZFS_SUFFIX}*}
        for i in "[EMAIL PROTECTED]"
        do
            [[ "$zone" = "$i" ]] && return 0
        done
    fi
    # More fun: if the dataset has a property of the form
    # :owner_user_<username>: or :owner_profiles:, the latter having
    # a comma-separated list of profile names as a value
    zfs get -H -o value type "$1" 2>/dev/null|read val
    [[ -z "$val" ]] && err "Dataset $1 does not exist"
    zfs get -H -o value :owner_user_${username}: "$1"|read val
    [[ "$val" != - ]] && return 0
    zfs get -H -o value :owner_profiles: "$1"|read val
    for i in "[EMAIL PROTECTED]"
    do
        IFS=,
        set -A profs -- $val
        IFS="$OIFS"
        for j in "[EMAIL PROTECTED]"
        do
            [[ "$i" = "$j" ]] && return 0
        done
    done
    [[ "$1" = [EMAIL PROTECTED] ]] && validate_object "[EMAIL PROTECTED]" && 
return 0
    # usage() exits
    print FOO
    usage
}

validate_prop () {
    typeset prop

    prop=${1%%=*}

    case "$prop" in
        mountpoint|quota|zoned|reservation|volsize|devices|setuid|:owner_*)
            err "Cannot set $prop properties";;
        *) return 0;;
    esac
}

zfs_create_opts () {
    typeset opt OPTARG prop arg
    # KSH getopts bug workaround
    OPTIND=1
    set -A zfs_args create
    while getopts sb:o:V: opt
    do
        case $opt in
            s|b|V) err "$PROG does not support volumes";;
            o)  validate_prop "$OPTARG"
                [EMAIL PROTECTED]
                [EMAIL PROTECTED]
                ;;
            [?])        usage;;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -eq 1 ]] || usage

    # The user creating this should have delegated access to their new
    # dataset
    [EMAIL PROTECTED]
    [EMAIL PROTECTED]:owner_user_$username:=yes
    zfs get name "$1" >/dev/null 2>&1 && err "$1 exists"
    validate_object "${1%/*}"
    [EMAIL PROTECTED]"$1"
}

zfs_set_opts () {
    [[ $# -eq 2 ]] || usage
    validate_prop "$1"
    validate_object "$2"
    set -A zfs_args set "$@"
}

# Common for destroy and rollback
zfs_destroy_or_rollback_opts () {
    typeset opt OPTARG prop arg subcmd
    # KSH getopts bug workaround
    OPTIND=1
    set -A zfs_args -- "$1"
    shift
    while getopts rRf opt
    do
        case $opt in
            [?])        usage;;
            *)  [EMAIL PROTECTED];;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -eq 1 ]] || usage
    for arg in "$@"
    do
        validate_object "$1"
        [EMAIL PROTECTED]"$arg"
    done
}

zfs_mount_opts () {
    typeset opt OPTARG prop arg
    # KSH getopts bug workaround
    OPTIND=1
    set -A zfs_args mount
    while getopts Oao: opt
    do
        case $opt in
            o|O|a) err "$PROG does not support zfs mount -$opt";;
            *)  usage;;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -eq 1 ]] || usage
    for arg in "$@"
    do
        validate_object "$1"
        [EMAIL PROTECTED]"$arg"
    done
}

zfs_unmount_or_unshare_opts () {
    typeset opt OPTARG prop arg
    # KSH getopts bug workaround
    OPTIND=1
    set -A zfs_args -- "$1"
    shift
    while getopts fa opt
    do
        case $opt in
            a) err "$PROG does not support zfs mount -$opt";;
            f) [EMAIL PROTECTED];;
            [?])        usage;;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -eq 1 ]] || usage
    validate_object "$1"
    [EMAIL PROTECTED]"$1"
}

zfs_share_opts () {
    typeset opt OPTARG prop arg
    # KSH getopts bug workaround
    OPTIND=1
    set -A zfs_args share
    while getopts a opt
    do
        case $opt in
            a) err "$PROG does not support zfs mount -$opt";;
            [?])        usage;;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -eq 1 ]] || usage
    for arg in "$@"
    do
        validate_object "$1"
        [EMAIL PROTECTED]"$arg"
    done
}

zfs_receive_opts () {
    typeset opt OPTARG prop arg dash_d
    # KSH getopts bug workaround
    OPTIND=1
    dash_d=
    set -A zfs_args receive
    while getopts vnFd opt
    do
        case $opt in
            [?]) usage;;
            d) [EMAIL PROTECTED]; dash_d=:;;
            *) [EMAIL PROTECTED];;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -eq 1 ]] || usage
    [[ -z "$dash_d" && "$1" != [EMAIL PROTECTED] ]] && \
        err "$PROG receive requires -d or a snapshot name be given"
    for arg in "$@"
    do
        validate_object "$1"
        [EMAIL PROTECTED]"$arg"
    done
}

zfs_send_opts () {
    typeset opt OPTARG prop arg dash_d
    # KSH getopts bug workaround
    OPTIND=1
    dash_d=
    set -A zfs_args send
    while getopts i: opt
    do
        case $opt in
            i) [EMAIL PROTECTED]
               validate_object "$OPTARG"
               [EMAIL PROTECTED];;
            *) usage;;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -eq 1 ]] || usage
    for arg in "$@"
    do
        validate_object "$1"
        [EMAIL PROTECTED]"$arg"
    done
}

zfs_inherit_opts () {
    typeset opt OPTARG prop arg dash_d
    # KSH getopts bug workaround
    OPTIND=1
    dash_d=
    set -A zfs_args inherit
    while getopts r opt
    do
        case $opt in
            r) [EMAIL PROTECTED];;
            *) usage;;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -gt 1 ]] || usage
    OPTIND=1
    for arg in "$@"
    do
        if [[ $OPTIND -eq $# ]]
        then
            validate_object "$arg"
            [EMAIL PROTECTED]"$arg"
            return 0
        fi
        OPTIND=$((OPTIND + 1))
        validate_prop "$1"
        [EMAIL PROTECTED]"$arg"
    done
}

zfs_rename_or_clone_opts () {
    typeset opt OPTARG prop arg dash_d
    # KSH getopts bug workaround
    OPTIND=1
    dash_d=
    set -A zfs_args -- "$1"
    shift
    [[ $# -eq 2 ]] || usage
    OPTIND=1
    validate_object "$1"
    [EMAIL PROTECTED]"$1"
    if [[ "$2" = [EMAIL PROTECTED] ]]
    then
        validate_object "[EMAIL PROTECTED]"
    else
        validate_object "${2%/*}"
    fi
    [EMAIL PROTECTED]"$2"
}

zfs_snapshot_opts () {
    typeset opt OPTARG prop arg dash_d
    # KSH getopts bug workaround
    OPTIND=1
    dash_d=
    set -A zfs_args snapshot
    while getopts r opt
    do
        case $opt in
            r) [EMAIL PROTECTED];;
            *) usage;;
        esac
    done
    shift $((OPTIND - 1))
    [[ $# -eq 1 && "$1" = [EMAIL PROTECTED] ]] || usage
    OPTIND=1
    validate_object "[EMAIL PROTECTED]"
    [EMAIL PROTECTED]"$1"
    return 0
}

## Prolog
##
# Get uid, euid; needed for checking and dropping privs
OPTIND=1
dry_run=
while getopts xn opt
do
    case $opt in
        x)  typeset -ft $(typeset +f)
            set -x;;
        n)  dry_run=print;;
    esac
done

shift $((OPTIND - 1))

[[ $# -gt 0 ]] || usage

# find out who is running this and what profiles they have
id -nu|read username
[[ -z "$username" ]] && err "Can't determine username"
set -A profiles --
set -A zonenames --
profiles|while read profname
do
    [EMAIL PROTECTED]"$profname"
    [[ "$profname" = "$PROF_PREFIX "* ]] || continue
    [EMAIL PROTECTED] }
done

## Main
case "$1" in
    zfs) shift
        ppriv $$|grep E:|read junk privs
        [[ "$privs" != all ]] && err "Run '$PROG $1' via pfexec or pf shell"
        [[ "$username" = root ]] && err "Don't run $PROG zfs ... as root"
        [[ $# -ge 1 ]] || usage
        subcmd=$1
        shift
        # continue after esac
        ;;
    chown)
        [[ "$privs" != all ]] && err "Run '$PROG $1' via pfexec or pf shell"
        [[ "$username" = root ]] && err "Don't run $PROG chown ... as root"
        shift
        OPTIND=1
        set -A args --
        # Reject options -H and -L
        while getopts fhRP opt
        do
            [EMAIL PROTECTED]
        done
        shift $((OPTIND - 1))
        [[ $# -eq 1 ]] || usage
        validate_object "$1"
        $dry_run chown "[EMAIL PROTECTED]" -P "$username" "/$1"
        exit $?
        ;;
    add-zone-profile) 
        if grep -q "^${PROF_PREFIX} $2:" /etc/security/prof_attr
        then
            print "Profile already exists"
            exit 1
        fi
        if [[ -n "$dry_run" ]]
        then
            print "Would append to /etc/security/prof_attr:"
            print "${PROF_PREFIX} $2:::Hack for mgmt of NFS exports for zones:"
            print "Would append to /etc/security/exec_attr:"
            print "${PROF_PREFIX} $2:solaris:cmd:::$0:privs=all"
        else
            print "${PROF_PREFIX} $2:::Hack for mgmt of NFS exports for zones:" 
>> /etc/security/prof_attr
            print "${PROF_PREFIX} $2:solaris:cmd:::$0:privs=all" >> 
/etc/security/exec_attr
        fi
        exit $?
        ;;
    setup)
        realpath "$ARG0"|read ABS_PROG
        [[ -n "$ABS_PROG" ]] || exit 1
        if grep -q "^${DELEG_ZFS_PROF}:" /etc/security/prof_attr
        then
            if grep -q "^${DELEG_ZFS_PROF}:solaris:cmd:" /etc/security/exec_attr
            then
                print -u2 "The RBAC profile (${DELEG_ZFS_PROF}) is already 
setup"
                exit 0
            fi
            if [[ -n "$dry_run" ]]
            then
                print "Would append to /etc/security/exec_attr:"
                print "\t${DELEG_ZFS_PROF}:solaris:cmd:::${ABS_PROG}:privs=all"
                exit 0
            fi
            print "${DELEG_ZFS_PROF}:solaris:cmd:::${ABS_PROG}:privs=all" \
                >> /etc/security/exec_attr
            exit $?
        fi
        if [[ -n "$dry_run" ]]
        then
            print "Would append to /etc/security/prof_attr:"
            print "\t${DELEG_ZFS_PROF}:::Hack for ZFS delegation:"
        else
            print "${DELEG_ZFS_PROF}:::Hack for ZFS delegation:" \
                >> /etc/security/prof_attr || exit $?
        fi
        if grep -q "^${DELEG_ZFS_PROF}:solaris:cmd:" /etc/security/exec_attr
        then
            exit 0
        fi
        if [[ -n "$dry_run" ]]
        then
            print "Would append to /etc/security/exec_attr:"
            print "\t${DELEG_ZFS_PROF}:solaris:cmd:::${ABS_PROG}:privs=all"
            exit 0
        fi
        print "${DELEG_ZFS_PROF}:solaris:cmd:::${ABS_PROG}:privs=all" \
            >> /etc/security/exec_attr
        exit $?
        ;;
    unsetup)
        realpath "$ARG0"|read ABS_PROG
        [[ "$username" != root ]] && err "Run $PROG unsetup as root"
        [[ -n "$ABS_PROG" ]] || exit 1
        for i in exec_attr prof_attr
        do
            if grep -q "^${DELEG_ZFS_PROF}:" /etc/security/$i
            then
                if [[ -n "$dry_run" ]]
                then
                    print "Would remove from /etc/security/$i:"
                    grep "^${DELEG_ZFS_PROF}:" /etc/security/$i
                    exit 0
                else
                    cp -p /etc/security/$i /etc/security/$i.$$ || exit 1
                    grep -v "^${DELEG_ZFS_PROF}:" /etc/security/$i > \
                        /etc/security/$i.$$
                    mv /etc/security/$i.$$ /etc/security/$i
                fi
            else
                print "RBAC profile (${DELEG_ZFS_PROF}) not present in 
/etc/security/$i"
            fi
        done
        exit 0
        ;;
    *) usage;;
esac

# OK, we're doing a ZFS command.
case "$subcmd" in
    # get and list need no special treatment, no privs
    get|list) 
        # Not that we have to drop privs for this, but why not?
        ppriv -s A=basic $$
        $dry_run exec zfs $subcmd "$@"
        ;;
    # create is special: we chown after it
    create)
        zfs_create_opts "$@"
        pcred -u 0 $$
        $dry_run zfs "[EMAIL PROTECTED]" || exit $?
        $dry_run chown "$username" "/${zfs_args[$(([EMAIL PROTECTED] - 1))]}"
        exit $?
        ;;
    # Some sub-commands have much the same signature
    destroy|rollback)   zfs_destroy_or_rollback_opts $subcmd "$@" ;;
    rename|clone)       zfs_rename_or_clone_opts $subcmd "$@" ;;
    unmount|unshare)    zfs_unmount_or_unshare_opts $subcmd "$@" ;;
    # Others don't
    inherit)            zfs_inherit_opts "$@" ;;
    mount)              zfs_mount_opts "$@" ;;
    receive)            zfs_receive_opts "$@" ;;
    send)               zfs_send_opts "$@" ;;
    set)                zfs_set_opts "$@" ;;
    share)              zfs_share_opts "$@" ;;
    snapshot)           zfs_snapshot_opts "$@" ;;
    promote)
        [[ $# -eq 1 && "$1" != [EMAIL PROTECTED] ]] || usage
        fs=$1
        validate_object "$fs"
        zfs get -H -o value origin "$1"|read origin
        if [[ "$origin" != - ]]
        then
            validate_object "[EMAIL PROTECTED]"
        fi
        set -A zfs_args promote "$1"
        ;;
esac

$dry_run exec zfs "[EMAIL PROTECTED]"
_______________________________________________
zfs-discuss mailing list
zfs-discuss@opensolaris.org
http://mail.opensolaris.org/mailman/listinfo/zfs-discuss

Reply via email to