From c8cca3cc82c3c446c49bf23ff6e2805f2aaeef48 Mon Sep 17 00:00:00 2001
From: Ben Howard <ben.howard@canonical.com>
Date: Mon, 27 Aug 2012 16:26:46 -0600
Subject: Fixed critical bug where user and group creation was being done after
 SSH configurations were applied. The result of this bug was that cloud-config
 supplied SSH public keys would fail to apply since the configured user may or
 may not exist. (LP: #1042459).

cloudinit/config/cc_ssh_import_id.py:
    ssh_import_id.py now handles all user SSH import IDs.

cloudinit/distros/ubuntu.py:
    Removed create_user class override as cruft, since ssh_import_id
    now handles all users.

config/cloud.cfg:
    Moved users_groups to run under cloud_init_modules.

doc/examples/cloud-config.txt:
    Added missing documentation on user and group creation.
---
 cloudinit/config/cc_ssh_import_id.py | 40 +++++++++++-----
 cloudinit/distros/ubuntu.py          | 18 --------
 config/cloud.cfg                     |  2 +-
 doc/examples/cloud-config.txt        | 90 ++++++++++++++++++++++++++++++++++++
 4 files changed, 120 insertions(+), 30 deletions(-)

diff --git a/cloudinit/config/cc_ssh_import_id.py b/cloudinit/config/cc_ssh_import_id.py
index cd97b99b..4a8849c8 100644
--- a/cloudinit/config/cc_ssh_import_id.py
+++ b/cloudinit/config/cc_ssh_import_id.py
@@ -19,6 +19,7 @@
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from cloudinit import util
+import pwd
 
 # The ssh-import-id only seems to exist on ubuntu (for now)
 # https://launchpad.net/ssh-import-id
@@ -26,30 +27,47 @@ distros = ['ubuntu']
 
 
 def handle(name, cfg, cloud, log, args):
+
+    # import for "user: XXXXX"
     if len(args) != 0:
         user = args[0]
         ids = []
         if len(args) > 1:
             ids = args[1:]
-    else:
-        user = cloud.distro.get_default_user()
 
-        if 'users' in cfg:
-            user_zero = cfg['users'].keys()[0]
+        import_ssh_ids(ids, user, log)
 
-            if user_zero != "default":
-                user = user_zero
+    # import for cloudinit created users
+    for user in cfg['users'].keys():
+        if user == "default":
+            distro_user = cloud.distro.get_default_user()
+            d_ids = util.get_cfg_option_list(cfg, "ssh_import_id", [])
+            import_ssh_ids(d_ids, distro_user, log)
 
-        ids = util.get_cfg_option_list(cfg, "ssh_import_id", [])
+        user_cfg = cfg['users'][user]
+        if not isinstance(user_cfg, dict):
+            user_cfg = None
 
-    if len(ids) == 0:
-        log.debug("Skipping module named %s, no ids found to import", name)
-        return
+        if user_cfg:
+            ids = util.get_cfg_option_list(user_cfg, "ssh_import_id", [])
+            import_ssh_ids(ids, user, log)
+
+
+def import_ssh_ids(ids, user):
 
     if not user:
-        log.debug("Skipping module named %s, no user found to import", name)
+        log.debug("Skipping ssh-import-ids, no user for ids")
         return
 
+    if len(ids) == 0:
+        log.debug("Skipping ssh-import-ids for %s, no ids to import" % user)
+        return
+
+    try:
+        check = pwd.getpwnam(user)
+    except KeyError:
+        log.debug("Skipping ssh-import-ids for %s, user not found" % user)
+
     cmd = ["sudo", "-Hu", user, "ssh-import-id"] + ids
     log.debug("Importing ssh ids for user %s.", user)
 
diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py
index 4b3f8572..cb93f971 100644
--- a/cloudinit/distros/ubuntu.py
+++ b/cloudinit/distros/ubuntu.py
@@ -32,21 +32,3 @@ class Distro(debian.Distro):
 
     distro_name = 'ubuntu'
     default_user = 'ubuntu'
-
-    def create_user(self, name, **kargs):
-
-        if not super(Distro, self).create_user(name, **kargs):
-            return False
-
-        if 'sshimportid' in kargs:
-            cmd = ["sudo", "-Hu", name, "ssh-import-id"] + kargs['sshimportid']
-            LOG.debug("Importing ssh ids for user %s, post user creation."
-                        % name)
-
-            try:
-                util.subp(cmd, capture=True)
-            except util.ProcessExecutionError as e:
-                util.logexc(LOG, "Failed to import %s ssh ids", name)
-                raise e
-
-        return True
diff --git a/config/cloud.cfg b/config/cloud.cfg
index 2744c940..9c475251 100644
--- a/config/cloud.cfg
+++ b/config/cloud.cfg
@@ -29,6 +29,7 @@ cloud_init_modules:
  - update_etc_hosts
  - ca-certs
  - rsyslog
+ - users-groups
  - ssh
 
 # The modules that run in the 'config' stage
@@ -37,7 +38,6 @@ cloud_config_modules:
 # this can be used by upstart jobs for 'start on cloud-config'.
  - emit_upstart
  - mounts
- - users-groups
  - ssh-import-id
  - locale
  - set-passwords
diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt
index 56a6c35a..9d073db5 100644
--- a/doc/examples/cloud-config.txt
+++ b/doc/examples/cloud-config.txt
@@ -167,6 +167,96 @@ mounts:
 # complete.  This must be an array, and must have 7 fields.
 mount_default_fields: [ None, None, "auto", "defaults,nobootwait", "0", "2" ]
 
+# add groups to the system
+# The following example adds the ubuntu group with members foo and bar and
+# the group cloud-users.
+groups:
+  ubuntu: [foo,bar]
+  cloud-users
+
+# add users to the system. Users are added after groups are added.
+users:
+  foobar:
+    gecos: Foo B. Bar
+    primary-group: foobar
+    groups: users
+    expiredate: 2012-09-01
+    ssh-import-id: foobar
+    lock-passwd: false
+    passwd: $6$j212wezy$7H/1LT4f9/N3wpgNunhsIqtMj62OKiS3nyNwuizouQc3u7MbYCarYeAHWYPYb2FT.lbioDm2RrkJPb9BZMN1O/
+  barfoo:
+    gecos: Bar B. Foo
+    sudo: ALL=(ALL) NOPASSWD:ALL
+    groups: users, admin
+    ssh-import-id: None
+    lock-passwd: true
+    ssh-authorized-keys:
+      - <ssh pub key 1>
+      - <ssh pub key 2>
+  cloudy:
+    gecos: Magic Cloud App Daemon User
+    inactive: true
+    system: true
+
+# Valid Values:
+#   gecos: The user name's real name, i.e. "Bob B. Smith"
+#   homedir: Optional. Set to the local path you want to use. Defaults to
+#           /home/<username>
+#   primary-group: define the primary group. Defaults to a new group created
+#           named after the user.
+#   groups:  Optional. Additional groups to add the user to. Defaults to none
+#   lock-passwd: Defaults to true. Lock the password to disable password login
+#   inactive: Create the user as inactive
+#   passwd: The hash -- not the password itself -- of the password you want
+#           to use for this user. You can generate a safe hash via:
+#               mkpasswd -m SHA-512 -s 4096
+#           (the above command would create a password SHA512 password hash
+#           with 4096 salt rounds)
+#
+#           Please note: while the use of a hashed password is better than
+#               plain text, the use of this feature is not ideal. Also,
+#               using a high number of salting rounds will help, but it should
+#               not be relied upon.
+#
+#               To highlight this risk, running John the Ripper against the
+#               example hash above, with a readily available wordlist, revealed
+#               the true password in 12 seconds on a i7-2620QM.
+#
+#               In other words, this feature is a potential security risk and is
+#               provided for your convenience only. If you do not fully trust the
+#               medium over which your cloud-config will be transmitted, then you
+#               should use SSH authentication only.
+#
+#               You have thus been warned.
+#
+#   no-create-home: When set to true, do not create home directory.
+#   no-user-group: When set to true, do not create a group named after the user.
+#   no-log-init: When set to true, do not initialize lastlog and faillog database.
+#   ssh-import-id: Optional. Import SSH ids
+#   ssh-authorized-key: Optional. Add key to user's ssh authorized keys file
+#   sudo: Defaults to none. Set to the sudo string you want to use, i.e.
+#           ALL=(ALL) NOPASSWD:ALL. To add multiple rules, use the following
+#           format.
+               sudo:
+                   - ALL=(ALL) NOPASSWD:/bin/mysql
+                   - ALL=(ALL) ALL
+#           Note: Please double check your syntax and make sure it is valid.
+#               cloud-init does not parse/check the syntax of the sudo
+#               directive.
+#   system: Create the user as a system user. This means no home directory.
+#
+# Default user creation: Ubuntu Only
+# Unless you define users, you will get a Ubuntu user on Ubuntu systems with the
+# legacy permission (no password sudo, locked user, etc). If however, you want
+# to have the ubuntu user in addition to other users, you need to instruct
+# cloud-init that you also want the default user. To do this use the following
+# syntax:
+users:
+  default: True
+  foobar: ...
+#
+# users[0] (the first user in users) overrides the user directive.
+
 # add each entry to ~/.ssh/authorized_keys for the configured user or the
 # first user defined in the user definition directive.
 ssh_authorized_keys:
-- 
cgit v1.2.3