Couldn''t wait for ZFS delegation, so I cobbled something together; see attachment. Nico -- -------------- next part -------------- #!/bin/ksh 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 "${dirs[@]}" 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 "${zonenames[@]}" 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 "${profiles[@]}" do IFS=, set -A profs -- $val IFS="$OIFS" for j in "${profs[@]}" do [[ "$i" = "$j" ]] && return 0 done done [[ "$1" = *@* ]] && validate_object "${1%%@*}" && 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" zfs_args[${#zfs_args[@]}]=-o zfs_args[${#zfs_args[@]}]=$OPTARG ;; [?]) usage;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage # The user creating this should have delegated access to their new # dataset zfs_args[${#zfs_args[@]}]=-o zfs_args[${#zfs_args[@]}]=:owner_user_$username:=yes zfs get name "$1" >/dev/null 2>&1 && err "$1 exists" validate_object "${1%/*}" zfs_args[${#zfs_args[@]}]="$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;; *) zfs_args[${#zfs_args[@]}]=-opt;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage for arg in "$@" do validate_object "$1" zfs_args[${#zfs_args[@]}]="$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" zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-f;; [?]) usage;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage validate_object "$1" zfs_args[${#zfs_args[@]}]="$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" zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-d; dash_d=:;; *) zfs_args=[${#zfs_args[@]}]=-$opt;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage [[ -z "$dash_d" && "$1" != *@* ]] && \ err "$PROG receive requires -d or a snapshot name be given" for arg in "$@" do validate_object "$1" zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-$opt validate_object "$OPTARG" zfs_args=[${#zfs_args[@]}]=$OPTARG;; *) usage;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage for arg in "$@" do validate_object "$1" zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-$opt;; *) usage;; esac done shift $((OPTIND - 1)) [[ $# -gt 1 ]] || usage OPTIND=1 for arg in "$@" do if [[ $OPTIND -eq $# ]] then validate_object "$arg" zfs_args[${#zfs_args[@]}]="$arg" return 0 fi OPTIND=$((OPTIND + 1)) validate_prop "$1" zfs_args[${#zfs_args[@]}]="$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" zfs_args[${#zfs_args[@]}]="$1" if [[ "$2" = *@* ]] then validate_object "${2%%@*}" else validate_object "${2%/*}" fi zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-$opt;; *) usage;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 && "$1" = *@* ]] || usage OPTIND=1 validate_object "${1%@*}" zfs_args[${#zfs_args[@]}]="$1" return 0 } ## Prolog ## # Get uid, euid; needed for checking and dropping privs OPTIND=1 dry_runwhile 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 profiles[${#profiles[@]}]="$profname" [[ "$profname" = "$PROF_PREFIX "* ]] || continue zonenames[${#zonenames[@]}]=${profname#${PROF_PREFIX} } 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 args[${#args[@]}]=-$opt done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage validate_object "$1" $dry_run chown "${args[@]}" -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 "${zfs_args[@]}" || exit $? $dry_run chown "$username" "/${zfs_args[$((${#zfs_args[@]} - 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" != *@* ]] || usage fs=$1 validate_object "$fs" zfs get -H -o value origin "$1"|read origin if [[ "$origin" != - ]] then validate_object "${origin%%@*}" fi set -A zfs_args promote "$1" ;; esac $dry_run exec zfs "${zfs_args[@]}"
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... -------------- next part -------------- #!/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 "${dirs[@]}" 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 "${zonenames[@]}" 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 "${profiles[@]}" do IFS=, set -A profs -- $val IFS="$OIFS" for j in "${profs[@]}" do [[ "$i" = "$j" ]] && return 0 done done [[ "$1" = *@* ]] && validate_object "${1%%@*}" && 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" zfs_args[${#zfs_args[@]}]=-o zfs_args[${#zfs_args[@]}]=$OPTARG ;; [?]) usage;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage # The user creating this should have delegated access to their new # dataset zfs_args[${#zfs_args[@]}]=-o zfs_args[${#zfs_args[@]}]=:owner_user_$username:=yes zfs get name "$1" >/dev/null 2>&1 && err "$1 exists" validate_object "${1%/*}" zfs_args[${#zfs_args[@]}]="$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;; *) zfs_args[${#zfs_args[@]}]=-opt;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage for arg in "$@" do validate_object "$1" zfs_args[${#zfs_args[@]}]="$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" zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-f;; [?]) usage;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage validate_object "$1" zfs_args[${#zfs_args[@]}]="$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" zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-d; dash_d=:;; *) zfs_args=[${#zfs_args[@]}]=-$opt;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage [[ -z "$dash_d" && "$1" != *@* ]] && \ err "$PROG receive requires -d or a snapshot name be given" for arg in "$@" do validate_object "$1" zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-$opt validate_object "$OPTARG" zfs_args=[${#zfs_args[@]}]=$OPTARG;; *) usage;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage for arg in "$@" do validate_object "$1" zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-$opt;; *) usage;; esac done shift $((OPTIND - 1)) [[ $# -gt 1 ]] || usage OPTIND=1 for arg in "$@" do if [[ $OPTIND -eq $# ]] then validate_object "$arg" zfs_args[${#zfs_args[@]}]="$arg" return 0 fi OPTIND=$((OPTIND + 1)) validate_prop "$1" zfs_args[${#zfs_args[@]}]="$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" zfs_args[${#zfs_args[@]}]="$1" if [[ "$2" = *@* ]] then validate_object "${2%%@*}" else validate_object "${2%/*}" fi zfs_args[${#zfs_args[@]}]="$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) zfs_args=[${#zfs_args[@]}]=-$opt;; *) usage;; esac done shift $((OPTIND - 1)) [[ $# -eq 1 && "$1" = *@* ]] || usage OPTIND=1 validate_object "${1%@*}" zfs_args[${#zfs_args[@]}]="$1" return 0 } ## Prolog ## # Get uid, euid; needed for checking and dropping privs OPTIND=1 dry_runwhile 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 profiles[${#profiles[@]}]="$profname" [[ "$profname" = "$PROF_PREFIX "* ]] || continue zonenames[${#zonenames[@]}]=${profname#${PROF_PREFIX} } 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 args[${#args[@]}]=-$opt done shift $((OPTIND - 1)) [[ $# -eq 1 ]] || usage validate_object "$1" $dry_run chown "${args[@]}" -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 "${zfs_args[@]}" || exit $? $dry_run chown "$username" "/${zfs_args[$((${#zfs_args[@]} - 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" != *@* ]] || usage fs=$1 validate_object "$fs" zfs get -H -o value origin "$1"|read origin if [[ "$origin" != - ]] then validate_object "${origin%%@*}" fi set -A zfs_args promote "$1" ;; esac $dry_run exec zfs "${zfs_args[@]}"
On Sat, Jun 23, 2007 at 12:31:28PM -0500, Nicolas Williams wrote:> 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...And I forgot to add a -p option here:> #!/bin/kshThat should be:> #!/bin/ksh -pNote that this script is not intended to be secure, just to keep honest people honest and from making certain mistakes. Setuid-scripts (which this isn''t quite) are difficult to make secure. Nico --
Nicolas Williams wrote:> Couldn''t wait for ZFS delegation, so I cobbled something together; see > attachment. > > Nico >The *real* ZFS delegation code was integrated into Nevada this morning. I''ve placed a little overview in my blog. http://blogs.sun.com/marks -Mark
On Tue, Jun 26, 2007 at 04:19:03PM -0600, Mark Shellenbaum wrote:> Nicolas Williams wrote: > >Couldn''t wait for ZFS delegation, so I cobbled something together; see > >attachment. > > The *real* ZFS delegation code was integrated into Nevada this morning. > I''ve placed a little overview in my blog. > > http://blogs.sun.com/marksYup. I''d written my script a while back but had left it unfinished. Fortunately I only spent a couple of hours on Friday finishing it up, but I really should have checked when ZFS delegation was scheduled to integrate (actually, I did ask on #onnv, but the only answer I got was "not soon enough"! :) Perhaps folks may find this script useful for pre-updated systems. (Speaking of which, what S10 update will ZFS delegation be rolled into?) Nico --
Oh, and thanks! ZFS delegations rocks.
Nicolas Williams wrote:> On Sat, Jun 23, 2007 at 12:31:28PM -0500, Nicolas Williams wrote: > > 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... > > And I forgot to add a -p option here: > > > #!/bin/ksh > > That should be: > > > #!/bin/ksh -pUhm... that''s no longer needed for /usr/bin/ksh in Solaris 10 and ksh93 never needed it.> Note that this script is not intended to be secure, just to keep honest > people honest and from making certain mistakes. Setuid-scripts (which > this isn''t quite) are difficult to make secure.Uhm... why ? You only have to make sure the users can''t inject data/code. David Korn provided some guidelines for such cases, see http://mail.opensolaris.org/pipermail/shell-discuss/2007-June/000493.html (mainly avoid "eval", put all variable expensions in quotes, set IFS= at the beginning of the script and harden your script against unexpected input (classical example is $ myscript "$(cat /usr/bin/cat)" # (e.g. the attempt to pass a giant binary string as argument))) ... and I am currently working on a new shell code style guideline at http://www.opensolaris.org/os/project/shell/shellstyle/ with more stuff. ---- Bye, Roland -- __ . . __ (o.\ \/ /.o) roland.mainz at nrubsig.org \__\/\/__/ MPEG specialist, C&&JAVA&&Sun&&Unix programmer /O /==\ O\ TEL +49 641 7950090 (;O/ \/ \O;)
On Wed, Jun 27, 2007 at 12:55:15AM +0200, Roland Mainz wrote:> Nicolas Williams wrote: > > On Sat, Jun 23, 2007 at 12:31:28PM -0500, Nicolas Williams wrote: > > > 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... > > > > And I forgot to add a -p option here: > > > > > #!/bin/ksh > > > > That should be: > > > > > #!/bin/ksh -p > > Uhm... that''s no longer needed for /usr/bin/ksh in Solaris 10 and ksh93 > never needed it.But will ksh or ksh93 know that this script must not source $ENV? Apparently ksh won''t source it anyways; this was not clear from the man page. Note that in the RBAC profile for this script the script gets run with privs=all, not euid=0, so checking that euid == uid is not sufficient.> > Note that this script is not intended to be secure, just to keep honest > > people honest and from making certain mistakes. Setuid-scripts (which > > this isn''t quite) are difficult to make secure. > > Uhm... why ? You only have to make sure the users can''t inject > data/code. David Korn provided some guidelines for such cases, see > http://mail.opensolaris.org/pipermail/shell-discuss/2007-June/000493.html > (mainly avoid "eval", put all variable expensions in quotes, set IFS= at > the beginning of the script and harden your script against unexpected > input (classical example is $ myscript "$(cat /usr/bin/cat)" # (e.g. the > attempt to pass a giant binary string as argument))) ... and I am > currently working on a new shell code style guideline at > http://www.opensolaris.org/os/project/shell/shellstyle/ with more stuff.As you can see the script quotes user arguments throughout. It''s probably secure -- what I meant is that I make no guarantees about this script :) Nico --
Nicolas Williams wrote:> On Wed, Jun 27, 2007 at 12:55:15AM +0200, Roland Mainz wrote: > > Nicolas Williams wrote: > > > On Sat, Jun 23, 2007 at 12:31:28PM -0500, Nicolas Williams wrote: > > > > 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... > > > > > > And I forgot to add a -p option here: > > > > > > > #!/bin/ksh > > > > > > That should be: > > > > > > > #!/bin/ksh -p > > > > Uhm... that''s no longer needed for /usr/bin/ksh in Solaris 10 and ksh93 > > never needed it. > > But will ksh or ksh93 know that this script must not source $ENV?Erm, I don''t know what''s the correct behaviour for Solaris ksh88... but for ksh93 it''s clearly defined that ${ENV} and /etc/ksh.kshrc are only sourced for _interactive_ shell sessions by default - and that excludes non-interactive scripts.> Apparently ksh won''t source it anyways; this was not clear from the man > page. > > Note that in the RBAC profile for this script the script gets run with > privs=all, not euid=0, so checking that euid == uid is not sufficient.What do you mean with that ?> > > Note that this script is not intended to be secure, just to keep honest > > > people honest and from making certain mistakes. Setuid-scripts (which > > > this isn''t quite) are difficult to make secure. > > > > Uhm... why ? You only have to make sure the users can''t inject > > data/code. David Korn provided some guidelines for such cases, see > > http://mail.opensolaris.org/pipermail/shell-discuss/2007-June/000493.html > > (mainly avoid "eval", put all variable expensions in quotes, set IFS= at > > the beginning of the script and harden your script against unexpected > > input (classical example is $ myscript "$(cat /usr/bin/cat)" # (e.g. the > > attempt to pass a giant binary string as argument))) ... and I am > > currently working on a new shell code style guideline at > > http://www.opensolaris.org/os/project/shell/shellstyle/ with more stuff. > > As you can see the script quotes user arguments throughout. It''s > probably secure -- what I meant is that I make no guarantees about this > script :)Yes... I saw that... and I realised that the new ksh93 getopts, pattern matching (e.g. [[ "${pat}" == ~(Ei).*myregex.* ]] to replace something like [ "$(echo "${pat}" | egrep -i ".*myregex.*")" != "" ] ) and associative arrays (e.g. use string as index instead of numbers) would be usefull for this script. Anyway... the script looks good... I wish the script code in OS/Net Makefiles would have that quality... ;-/ ---- Bye, Roland -- __ . . __ (o.\ \/ /.o) roland.mainz at nrubsig.org \__\/\/__/ MPEG specialist, C&&JAVA&&Sun&&Unix programmer /O /==\ O\ TEL +49 641 7950090 (;O/ \/ \O;)
On Wed, Jun 27, 2007 at 01:45:07AM +0200, Roland Mainz wrote:> Nicolas Williams wrote: > > But will ksh or ksh93 know that this script must not source $ENV? > > Erm, I don''t know what''s the correct behaviour for Solaris ksh88... but > for ksh93 it''s clearly defined that ${ENV} and /etc/ksh.kshrc are only > sourced for _interactive_ shell sessions by default - and that excludes > non-interactive scripts.Right, and I''d forgotten that, and when I glanced at the manpage, nervous that I''d might have missed a ksh option that''s important for setuid scripts, it was not obvious that this was indeed the case.> > Apparently ksh won''t source it anyways; this was not clear from the man > > page. > > > > Note that in the RBAC profile for this script the script gets run with > > privs=all, not euid=0, so checking that euid == uid is not sufficient. > > What do you mean with that ?Read the part of the script that deals with the ''setup'' sub-command.> > As you can see the script quotes user arguments throughout. It''s > > probably secure -- what I meant is that I make no guarantees about this > > script :) > > Yes... I saw that... and I realised that the new ksh93 getopts, pattern > matching (e.g. [[ "${pat}" == ~(Ei).*myregex.* ]] to replace something > like [ "$(echo "${pat}" | egrep -i ".*myregex.*")" != "" ] ) and > associative arrays (e.g. use string as index instead of numbers) would > be usefull for this script.Indeed. I can''t tell you how many times I''ve wished that Solaris had had ksh93 back in, well, 1993 :) Although, I must say that I *like* KSH globs quite a bit, enough so that I''d not resort to regexps in a ksh93 script unless I had to match patterns that were not easily expressible as KSH globs. And I like KSH variable substitution transformations like ${var%<pattern>} and so on (though, again, I wish ksh88 had a few more extensions of that sort). Nico --