--- a/src/brand/attach Thu May 07 16:50:17 2009 -0700
+++ b/src/brand/attach Thu May 07 18:45:52 2009 -0700
@@ -58,6 +58,9 @@
f_reset_pub=$(gettext "Failed to reset publisher to %s %s")
f_no_pref_publisher=$(gettext "Unable to get preferred publisher information for zone '%s'.")
f_pkg_list=$(gettext "Unable to get zone package list.")
+f_get_secinfo=$(gettext "Failed to get key/cert information for publisher %s")
+f_nosuch_key=$(gettext "Failed to find key %s for global zone publisher")
+f_nosuch_cert=$(gettext "Failed to find cert %s for global zone publisher")
#set -o xtrace
@@ -68,7 +71,17 @@
TEXTDOMAIN="SUNW_OST_OSCMD"
export TEXTDOMAIN
-PKG="LC_ALL=C /usr/bin/pkg"
+PKG="/usr/bin/pkg"
+KEYDIR=/var/pkg/ssl
+
+#
+# Resetting GZ_IMAGE to something besides slash allows for simplified
+# debugging of various global zone image configurations-- simply make
+# an image somewhere with the appropriate interesting parameters.
+#
+GZ_IMAGE=${GZ_IMAGE:-/}
+PKG_IMAGE=$GZ_IMAGE
+export PKG_IMAGE
aarg=0
darg=0
@@ -412,6 +425,7 @@
#
trap "/usr/sbin/umount $zoneroot > /dev/null 2>&1" EXIT
+
#
# Look for the 'entire' incorporation's FMRI in the current image; due to users
# doing weird machinations with their publishers, we strip off the publisher
@@ -428,8 +442,21 @@
[[ -z $gz_publisher ]] && fail_usage "$f_no_pref_publisher" "global"
[[ -z $gz_publisher_url ]] && fail_usage "$f_no_pref_publisher" "global"
+get_pub_secinfo $gz_publisher | read gzkeyfile gzcertfile
+if [[ $? -ne 0 ]]; then
+ fail_usage "$f_get_secinfo" $gz_publisher
+fi
+[[ $gzkeyfile != "None" && ! -f $gzkeyfile ]] && \
+ fail_usage "$f_nosuch_key" $gzkeyfile
+[[ $gzcertfile != "None" && ! -f $gzcertfile ]] && \
+ fail_usage "$f_nosuch_cert" $gzcertfile
+
+
+#
+# We're done with the global zone: switch images to the non-global
+# zone.
+#
PKG_IMAGE="$zoneroot"
-export PKG_IMAGE
#
# Get publisher information for non global zone.
@@ -477,7 +504,7 @@
# See if the zone knows about the gz entire fmri in question. If yes,
# we'll try using that.
#
-$PKG list --no-refresh -a $gz_entire_fmri > /dev/null 2>&1
+LC_ALL=C $PKG list --no-refresh -a $gz_entire_fmri > /dev/null 2>&1
#
# If this doesn't exist, then we reset the preferred pub for
@@ -488,9 +515,37 @@
printf "$m_resetpub2\n" $gz_entire_fmri
printf "$m_resetpub3\n"
+ safe_dir $zoneroot/var
+ safe_dir $zoneroot/var/pkg
+
+ # Copy credentials from global zone.
+ secinfo=""
+ if [[ $gzkeyfile != "None" || $gzcertfile != "None" ]]; then
+ if [[ -e $zoneroot/$KEYDIR ]]; then
+ safe_dir $zoneroot/$KEYDIR
+ else
+ mkdir -m 755 $zoneroot/$KEYDIR
+ fi
+ fi
+
+ if [[ $gzkeyfile != None ]]; then
+ newlocation="$KEYDIR/attach_$(basename $gzkeyfile)"
+ safe_copy $gzkeyfile $zoneroot/$newlocation
+ chmod 644 $zoneroot/$newkeylocation
+ chown -h root:root $zoneroot/$newkeylocation
+ secinfo="$secinfo -k $newlocation"
+ fi
+ if [[ $gzcertfile != None ]]; then
+ newlocation="$KEYDIR/attach_$(basename $gzcertfile)"
+ safe_copy $gzcertfile $zoneroot/$newlocation
+ chmod 644 $zoneroot/$newkeylocation
+ chown -h root:root $zoneroot/$newkeylocation
+ secinfo="$secinfo -c $newlocation"
+ fi
+
# Note that we do cause a refresh here-- at some point we need the
# catalog to be updated.
- pkg -R $zoneroot set-publisher -P -O $gz_publisher_url $gz_publisher
+ $PKG set-publisher -P -O $gz_publisher_url $secinfo $gz_publisher
if [[ $? -ne 0 ]]; then
fail_fatal "$f_reset_pub"
fi
@@ -527,7 +582,7 @@
# First, list all the packages for the preferred publisher, then
# do an 'install' on those.
#
-zone_pkgs=$($PKG list --no-refresh -H "pkg://$zone_publisher/*" | \
+zone_pkgs=$(LC_ALL=C $PKG list --no-refresh -H "pkg://$zone_publisher/*" | \
awk '{print $1}' | egrep -v '^entire$')
if [[ $? -ne 0 ]]; then
fail_fatal "$f_pkg_list"
--- a/src/brand/common.ksh Thu May 07 16:50:17 2009 -0700
+++ b/src/brand/common.ksh Thu May 07 18:45:52 2009 -0700
@@ -40,6 +40,10 @@
f_no_active_ds=$(gettext "Error: no active dataset.")
f_zfs_mount=$(gettext "Unable to mount the zone's ZFS dataset.")
+f_safedir=$(gettext "Expected %s to be a directory.")
+f_cp=$(gettext "Failed to cp %s %s.")
+f_cp_unsafe=$(gettext "Failed to safely copy %s to %s.")
+
fail_incomplete() {
printf "ERROR: " 1>&2
printf "$@" 1>&2
@@ -151,6 +155,43 @@
# Emits to stdout the preferred publisher and its URL.
#
get_preferred_publisher() {
- $PKG publisher -PH | nawk '$2 == "origin" && $3 == "online" \
+ LC_ALL=C $PKG publisher -PH | nawk '$2 == "origin" && $3 == "online" \
{printf "%s %s\n", $1, $4; exit 0;}'
}
+
+#
+# Emit to stdout the key and cert associated with the publisher
+# name provided. Returns 'None' if no information is present.
+#
+get_pub_secinfo() {
+ key=$( LC_ALL=C $PKG publisher $1 | egrep '^ *SSL Key:' |
+ awk '{print $3}' )
+ [[ $? -ne 0 ]] && return 1
+ cert=$( LC_ALL=C $PKG publisher $1 | egrep '^ *SSL Cert:' |
+ awk '{print $3}' )
+ [[ $? -ne 0 ]] && return 1
+ print $key $cert
+}
+
+# Validate that the directory is safe.
+# n.b.: this is diverged from the shared/common.ksh version.
+safe_dir()
+{
+ typeset dir="$1"
+
+ [[ -h $dir || ! -d $dir ]] && fail_fatal "$f_safedir"
+}
+
+# Make a copy even if the destination already exists.
+# n.b.: this is diverged from the shared/common.ksh version.
+safe_copy()
+{
+ typeset src="$1"
+ typeset dst="$2"
+
+ if [[ ! -h $src && ! -h $dst && ! -d $dst ]]; then
+ /usr/bin/cp -p $src $dst || fail_fatal "$f_cp" "$src" "$dst"
+ else
+ fail_fatal "$f_cp_unsafe" "$src" "$dst"
+ fi
+}
--- a/src/brand/config.xml Thu May 07 16:50:17 2009 -0700
+++ b/src/brand/config.xml Thu May 07 18:45:52 2009 -0700
@@ -38,7 +38,7 @@
<!-- We may not be able to do the create in pkg(1) proper. -->
<install>/usr/lib/brand/ipkg/pkgcreatezone -z %z -R %R</install>
- <installopts>a:p:e:h</installopts>
+ <installopts>a:P:e:c:k:h</installopts>
<boot></boot>
<halt></halt>
<verify_cfg></verify_cfg>
--- a/src/brand/pkgcreatezone Thu May 07 16:50:17 2009 -0700
+++ b/src/brand/pkgcreatezone Thu May 07 18:45:52 2009 -0700
@@ -24,6 +24,15 @@
# Use is subject to license terms.
#
+#
+# Resetting GZ_IMAGE to something besides slash allows for simplified
+# debugging of various global zone image configurations-- simply make
+# an image somewhere with the appropriate interesting parameters.
+#
+GZ_IMAGE=${GZ_IMAGE:-/}
+PKG_IMAGE=$GZ_IMAGE
+export PKG_IMAGE
+
. /usr/lib/brand/ipkg/common.ksh
f_pkg5_missing=$(gettext "pkg(5) does not seem to be present on this system.\n")
@@ -34,12 +43,18 @@
f_bad_publisher=$(gettext "Syntax error in publisher information.\n")
f_no_entire=$(gettext "Unable to find 'entire' incorporation in the global zone image.\n")
f_no_entire_in_pref=$(gettext "Unable to locate the incorporation '%s' in the preferred publisher '%s'.\nUse -P to supply a publisher which contains this package.\n")
-
+f_key_prop=$(gettext "Unable to propagate key %s to %s")
+f_cert_prop=$(gettext "Unable to propagate cert %s to %s")
+f_get_secinfo=$(gettext "Failed to get key/cert information for publisher %s")
+f_nosuch_key=$(gettext "Failed to find key %s")
+f_nosuch_cert=$(gettext "Failed to find cert %s")
m_publisher=$(gettext " Publisher: Using %s (%s).")
m_cache=$(gettext " Cache: Using %s.")
m_image=$(gettext " Image: Preparing at %s.")
m_incorp=$(gettext "Sanity Check: Looking for 'entire' incorporation.\n")
+m_key_prop=$(gettext " Credentials: Propagating %s\n")
+m_cert_prop=$(gettext " Credentials: Propagating %s\n")
m_core=$(gettext " Installing: Core System (output follows)\n")
m_more=$(gettext " Installing: Additional Packages (output follows)\n")
m_smf=$(gettext " Postinstall: Copying SMF seed repository ...")
@@ -77,13 +92,17 @@
TEXTDOMAIN="SUNW_OST_OSCMD"
export TEXTDOMAIN
+KEYDIR=/var/pkg/ssl
PKG=/usr/bin/pkg
+
#
# Just in case. This should probably be removed later.
#
[[ ! -x $PKG ]] && fail_incomplete "$f_pkg5_missing"
-while getopts "a:P:z:R:h:e:" opt; do
+certfile="None"
+keyfile="None"
+while getopts "a:P:z:R:h:e:c:k:" opt; do
case $opt in
h) fail_usage "$m_usage";;
R) zonepath="$OPTARG" ;;
@@ -92,6 +111,8 @@
print -u2 \
"WARNING: -a is deprecated. Use -P instead." ;;
P) pub_and_url="$OPTARG" ;;
+ c) certfile="$OPTARG" ;;
+ k) keyfile="$OPTARG" ;;
e) extra_packages="$extra_packages $OPTARG" ;;
*) fail_usage "$m_usage";;
esac
@@ -106,20 +127,31 @@
is_system_labeled
zoneroot=$zonepath/root
+secinfo=""
#
# If the user didn't give us a publisher, and there's a preferred publisher set
# for the system, set that as the default.
#
+propagate_secinfo=
if [[ -z $pub_and_url ]]; then
+ if [[ $keyfile != "None" ]]; then
+ fail_usage "Key file not allowed without -P"
+ fi
+ if [[ $certfile != "None" ]]; then
+ fail_usage "Cert file not allowed without -P"
+ fi
#
# We look for a preferred online origin.
#
- tpub_and_url=`LC_ALL=C $PKG -R / publisher -PH | \
+ tpub_and_url=`LC_ALL=C $PKG publisher -PH | \
awk '$2 == "origin" && $3 == "online" \
{printf "%s=%s\n", $1, $4; exit 0;}'`
[[ $? -eq 0 && -n $tpub_and_url ]] && pub_and_url="$tpub_and_url"
+
+ # Note that later we need to propagate key & cert.
+ propagate_secinfo=1
fi
#
@@ -137,6 +169,24 @@
fail_usage "$f_bad_publisher"
fi
+if [[ -n $propagate_secinfo ]]; then
+ #
+ # Get the global zone's cert and key (if any) so that we can propagate
+ # them into the new image.
+ #
+ get_pub_secinfo $publisher | read keyfile certfile
+ if [[ $? -ne 0 ]]; then
+ fail_usage "$f_get_secinfo" $publisher
+ fi
+fi
+#
+# Do some sanity checks on key and cert.
+#
+[[ $keyfile != "None" && ! -f $keyfile ]] && \
+ fail_usage "$f_nosuch_key" $keyfile
+[[ $certfile != "None" && ! -f $certfile ]] && \
+ fail_usage "$f_nosuch_cert" $certfile
+
#
# Look for the 'entire' incorporation's FMRI in the current image; due to users
# doing weird machinations with their publishers, we strip off the publisher
@@ -210,9 +260,40 @@
#
printf "$m_publisher" $publisher $publisherurl
printf "\n$m_image\n" $zoneroot
-$PKG image-create --zone --full -p "$pub_and_url" $zoneroot || \
- fail_incomplete "$f_img"
+#
+# We copy the credentials from the global zone into the new image
+# we're about to create.
+#
+if [[ $keyfile != "None" ]]; then
+ newkeylocation="$KEYDIR/$(basename $keyfile)"
+ secinfo="$secinfo -k $newkeylocation"
+ printf "$m_key_prop\n" $(basename $keyfile)
+ mkdir -p -m 755 $zoneroot/$KEYDIR || fail_fatal "$f_key_prop"
+ cp $keyfile $zoneroot/$newkeylocation || fail_fatal "$f_key_prop"
+ chmod 644 $zoneroot/$newkeylocation
+ chown -h root:root $zoneroot/$newkeylocation
+fi
+if [[ $certfile != "None" ]]; then
+ newcertlocation="$KEYDIR/$(basename $certfile)"
+ secinfo="$secinfo -c $newcertlocation"
+ printf "$m_cert_prop\n" $(basename $certfile)
+ mkdir -p -m 755 $zoneroot/$KEYDIR || fail_fatal "$f_cert_prop"
+ cp $certfile $zoneroot/$newcertlocation || fail_fatal "$f_cert_prop"
+ chmod 644 $zoneroot/$newkeylocation
+ chown -h root:root $zoneroot/$newkeylocation
+fi
+
+#
+# Regrettably, since we already copied the key information into place,
+# we must pass the -f (force) option to image-create, since it thinks that
+# something must be wrong, as the image exists.
+#
+# XXX in the future, we can create the image --no-refresh, then
+# set the publisher. But image-create --no-refresh is broken right now.
+#
+$PKG image-create -f --zone --full -p "$pub_and_url" $secinfo $zoneroot || \
+ fail_incomplete "$f_img"
PKG_IMAGE="$zoneroot"
export PKG_IMAGE
--- a/src/client.py Thu May 07 16:50:17 2009 -0700
+++ b/src/client.py Thu May 07 18:45:52 2009 -0700
@@ -1662,7 +1662,7 @@
else:
return 0
-def publisher_set(img_dir, args):
+def publisher_set(img, img_dir, args):
"""pkg set-publisher [-Ped] [-k ssl_key] [-c ssl_cert] [--reset-uuid]
[-O origin_url] [-m mirror to add] [-M mirror to remove]
[--enable] [--disable] [--no-refresh] publisher"""
@@ -1786,6 +1786,18 @@
# None is checked for here so that a client can unset a ssl_cert or
# ssl_key by using -k "" or -c "".
if ssl_cert is not None or ssl_key is not None:
+ #
+ # In the case of zones, the ssl cert given is assumed to
+ # be relative to the root of the image, not truly absolute.
+ #
+ if img.is_zone():
+ if ssl_cert is not None:
+ ssl_cert = os.path.abspath(
+ img.get_root() + os.sep + ssl_cert)
+ if ssl_key is not None:
+ ssl_key = os.path.abspath(
+ img.get_root() + os.sep + ssl_key)
+
# Assume the user wanted to update the ssl_cert or ssl_key
# information for *all* of the currently selected
# repository's origins and mirrors.
@@ -2184,16 +2196,27 @@
if len(pargs) != 1:
usage(_("image-create requires a single image directory path"))
+ image_dir = pargs[0]
if ssl_key:
- ssl_key = os.path.abspath(ssl_key)
+ # When creating zones, the path is image-root-relative.
+ if is_zone:
+ ssl_key = os.path.normpath(image_dir + os.sep + \
+ ssl_key)
+ else:
+ ssl_key = os.path.abspath(ssl_key)
if not os.path.exists(ssl_key):
msg(_("pkg: set-publisher: SSL key file '%s' does " \
"not exist") % ssl_key)
return 1
if ssl_cert:
- ssl_cert = os.path.abspath(ssl_cert)
+ # When creating zones, the path is image-root-relative.
+ if is_zone:
+ ssl_cert = os.path.normpath(image_dir + os.sep + \
+ ssl_cert)
+ else:
+ ssl_cert = os.path.abspath(ssl_cert)
if not os.path.exists(ssl_cert):
msg(_("pkg: set-publisher: SSL key cert '%s' does " \
"not exist") % ssl_cert)
@@ -2216,8 +2239,6 @@
"characters"))
return 1
- image_dir = pargs[0]
-
# Bail if there is already an image there
if img.image_type(image_dir) != None and not force:
error(_("there is already an image at: %s") % image_dir)
@@ -2535,7 +2556,7 @@
elif subcommand == "verify":
return verify_image(img, pargs)
elif subcommand in ("set-authority", "set-publisher"):
- return publisher_set(mydir, pargs)
+ return publisher_set(img, mydir, pargs)
elif subcommand in ("unset-authority", "unset-publisher"):
return publisher_unset(mydir, pargs)
elif subcommand in ("authority", "publisher"):
--- a/src/modules/client/image.py Thu May 07 16:50:17 2009 -0700
+++ b/src/modules/client/image.py Thu May 07 18:45:52 2009 -0700
@@ -265,35 +265,45 @@
if self.root == None:
raise RuntimeError, "self.root must be set"
- ic = imageconfig.ImageConfig()
+ ic = imageconfig.ImageConfig(self.root)
ic.read(self.imgdir)
- self.cfg_cache = ic
-
# make sure we define architecture variant; upgrade config
# file if possible.
- if "variant.arch" not in self.cfg_cache.variants:
- self.cfg_cache.variants["variant.arch"] = \
- platform.processor()
+ changed = False
+ if "variant.arch" not in ic.variants:
+ ic.variants["variant.arch"] = platform.processor()
try:
- self.save_config()
+ ic.write(self.imgdir)
+ changed = True
except api_errors.PermissionsException:
pass
# make sure we define zone variant; upgrade config if possible
- if "variant.opensolaris.zone" not in self.cfg_cache.variants:
- zone = self.cfg_cache.filters.get("opensolaris.zone",
- "")
+ if "variant.opensolaris.zone" not in ic.variants:
+ zone = ic.filters.get("opensolaris.zone", "")
if zone == "nonglobal":
- self.cfg_cache.variants[
+ ic.variants[
"variant.opensolaris.zone"] = "nonglobal"
else:
- self.cfg_cache.variants[
+ ic.variants[
"variant.opensolaris.zone"] = "global"
try:
- self.save_config()
+ ic.write(self.imgdir)
+ changed = True
except api_errors.PermissionsException:
pass
+ #
+ # If we made changes to the configuration, reload it;
+ # this lets any processing which is a side-effect of
+ # these changes take place.
+ #
+ if changed:
+ ic = imageconfig.ImageConfig(self.root)
+ ic.read(self.imgdir)
+
+ self.cfg_cache = ic
+
def save_config(self):
self.cfg_cache.write(self.imgdir)
@@ -347,7 +357,7 @@
repositories=[repo])
# Initialize and store the configuration object.
- self.cfg_cache = imageconfig.ImageConfig()
+ self.cfg_cache = imageconfig.ImageConfig(self.root)
# ...so that if creation of the Publisher object fails, an
# empty, useless image won't be left behind.
--- a/src/modules/client/imageconfig.py Thu May 07 16:50:17 2009 -0700
+++ b/src/modules/client/imageconfig.py Thu May 07 18:45:52 2009 -0700
@@ -107,7 +107,8 @@
# XXX Use of ConfigParser is convenient and at most speculative--and
# definitely not interface.
- def __init__(self):
+ def __init__(self, imgroot):
+ self.__imgroot = imgroot
self.publishers = {}
self.publisher_status = {}
self.mirror_status = {}
@@ -151,6 +152,14 @@
# The root directory for publisher metadata.
pmroot = os.path.join(path, PUB_META_DIR)
+ #
+ # Must load variants first, since in the case of zones,
+ # the variant can impact the processing of publishers.
+ #
+ if cp.has_section("variant"):
+ for o in cp.options("variant"):
+ self.variants[o] = cp.get("variant", o)
+
for s in cp.sections():
if re.match("authority_.*", s):
k, a = self.read_publisher(pmroot, cp, s)
@@ -183,10 +192,6 @@
for o in cp.options("filter"):
self.filters[o] = cp.get("filter", o)
- if cp.has_section("variant"):
- for o in cp.options("variant"):
- self.variants[o] = cp.get("variant", o)
-
try:
self.preferred_publisher = \
self.properties["preferred-publisher"]
@@ -265,9 +270,27 @@
c.set(section, "mirrors",
str([u.uri for u in repo.mirrors]))
+ #
+ # For zones, where the reachability of an absolute path
+ # changes depending on whether you're in the zone or
+ # not. So we have a different policy: ssl_key and
+ # ssl_cert are treated as zone root relative.
+ #
+ ngz = self.variants.get("variant.opensolaris.zone",
+ "global") == "nonglobal"
+ p = str(pub["ssl_key"])
+ if ngz and self.__imgroot != os.sep and p != "None":
+ # Trim the imageroot from the path.
+ if p.startswith(self.__imgroot):
+ p = p[len(self.__imgroot):]
# XXX this should be per origin or mirror
- c.set(section, "ssl_key", str(pub["ssl_key"]))
- c.set(section, "ssl_cert", str(pub["ssl_cert"]))
+ c.set(section, "ssl_key", p)
+ p = str(pub["ssl_cert"])
+ if ngz and self.__imgroot != os.sep and p != "None":
+ if p.startswith(self.__imgroot):
+ p = p[len(self.__imgroot):]
+ # XXX this should be per origin or mirror
+ c.set(section, "ssl_cert", p)
# XXX this should really be client_uuid, but is being
# left with this name for compatibility with older
@@ -434,8 +457,21 @@
ssl_key = None
ssl_cert = None
+ #
+ # For zones, where the reachability of an absolute path
+ # changes depending on whether you're in the zone or not. So
+ # we have a different policy: ssl_key and ssl_cert are treated
+ # as zone root relative.
+ #
+ ngz = self.variants.get("variant.opensolaris.zone",
+ "global") == "nonglobal"
+
if ssl_key:
- ssl_key = os.path.abspath(ssl_key)
+ if ngz:
+ ssl_key = os.path.normpath(self.__imgroot +
+ os.sep + ssl_key)
+ else:
+ ssl_key = os.path.abspath(ssl_key)
if not os.path.exists(ssl_key):
# XXX need client messaging framework
emsg(api_errors.NoSuchCertificate(ssl_key,
@@ -443,7 +479,11 @@
ssl_key = None
if ssl_cert:
- ssl_cert = os.path.abspath(ssl_cert)
+ if ngz:
+ ssl_cert = os.path.normpath(self.__imgroot +
+ os.sep + ssl_cert)
+ else:
+ ssl_cert = os.path.abspath(ssl_cert)
if not os.path.exists(ssl_cert):
# XXX need client messaging framework
emsg(api_errors.NoSuchCertificate(ssl_cert,
--- a/src/tests/api/t_imageconfig.py Thu May 07 16:50:17 2009 -0700
+++ b/src/tests/api/t_imageconfig.py Thu May 07 18:45:52 2009 -0700
@@ -20,7 +20,7 @@
# CDDL HEADER END
#
-# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
import unittest
@@ -66,7 +66,7 @@
sort_policy: priority
""")
f.close()
- self.ic = imageconfig.ImageConfig()
+ self.ic = imageconfig.ImageConfig(self.sample_dir)
def tearDown(self):
try:
@@ -104,7 +104,7 @@
self.ic.properties['name'] = ustr
newdir = tempfile.mkdtemp()
self.ic.write(newdir)
- ic2 = imageconfig.ImageConfig()
+ ic2 = imageconfig.ImageConfig(newdir)
ic2.read(newdir)
ustr2 = ic2.properties['name']
shutil.rmtree(newdir)