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