Merge lp:~smoser/ubuntu/trusty/cloud-init/trusty-1461242 into lp:ubuntu/trusty/cloud-init

Proposed by Scott Moser on 2015-09-10
Status: Merged
Merged at revision: 351
Proposed branch: lp:~smoser/ubuntu/trusty/cloud-init/trusty-1461242
Merge into: lp:ubuntu/trusty/cloud-init
Diff against target: 411 lines (+294/-31)
6 files modified
.pc/applied-patches (+1/-0)
.pc/lp-1461242-generate-ed25519-host-keys.patch/cloudinit/config/cc_ssh.py (+138/-0)
cloudinit/config/cc_ssh.py (+33/-30)
debian/changelog (+6/-1)
debian/patches/lp-1461242-generate-ed25519-host-keys.patch (+115/-0)
debian/patches/series (+1/-0)
To merge this branch: bzr merge lp:~smoser/ubuntu/trusty/cloud-init/trusty-1461242
Reviewer Review Type Date Requested Status
Ben Howard (community) Approve on 2015-09-11
Dan Watkins 2015-09-10 Pending
Review via email: mp+270711@code.launchpad.net
To post a comment you must log in.
352. By Scott Moser on 2015-09-10

remove use of 'decode_binary'

there is no 'decode_binary' here.
the return values from subp in python2 are strings.

Tested and okay for merger. It works as expected by creating ed25519 keys in /etc/ssh.

I would merge it, but bzr is complaining of a "readonly transport."

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.pc/applied-patches'
2--- .pc/applied-patches 2015-09-10 16:24:44 +0000
3+++ .pc/applied-patches 2015-09-10 16:50:23 +0000
4@@ -14,3 +14,4 @@
5 lp-1470890-include-regions-in-dynamic-mirror-discovery.patch
6 lp-1490796-azure-fix-mount_cb-for-symlinks.patch
7 lp-1469260-fix-consumption-of-vendor-data.patch
8+lp-1461242-generate-ed25519-host-keys.patch
9
10=== added directory '.pc/lp-1461242-generate-ed25519-host-keys.patch'
11=== added directory '.pc/lp-1461242-generate-ed25519-host-keys.patch/cloudinit'
12=== added directory '.pc/lp-1461242-generate-ed25519-host-keys.patch/cloudinit/config'
13=== added file '.pc/lp-1461242-generate-ed25519-host-keys.patch/cloudinit/config/cc_ssh.py'
14--- .pc/lp-1461242-generate-ed25519-host-keys.patch/cloudinit/config/cc_ssh.py 1970-01-01 00:00:00 +0000
15+++ .pc/lp-1461242-generate-ed25519-host-keys.patch/cloudinit/config/cc_ssh.py 2015-09-10 16:50:23 +0000
16@@ -0,0 +1,138 @@
17+# vi: ts=4 expandtab
18+#
19+# Copyright (C) 2009-2010 Canonical Ltd.
20+# Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P.
21+#
22+# Author: Scott Moser <scott.moser@canonical.com>
23+# Author: Juerg Haefliger <juerg.haefliger@hp.com>
24+#
25+# This program is free software: you can redistribute it and/or modify
26+# it under the terms of the GNU General Public License version 3, as
27+# published by the Free Software Foundation.
28+#
29+# This program is distributed in the hope that it will be useful,
30+# but WITHOUT ANY WARRANTY; without even the implied warranty of
31+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32+# GNU General Public License for more details.
33+#
34+# You should have received a copy of the GNU General Public License
35+# along with this program. If not, see <http://www.gnu.org/licenses/>.
36+
37+import glob
38+import os
39+
40+# Ensure this is aliased to a name not 'distros'
41+# since the module attribute 'distros'
42+# is a list of distros that are supported, not a sub-module
43+from cloudinit import distros as ds
44+
45+from cloudinit import ssh_util
46+from cloudinit import util
47+
48+DISABLE_ROOT_OPTS = ("no-port-forwarding,no-agent-forwarding,"
49+"no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\" "
50+"rather than the user \\\"root\\\".\';echo;sleep 10\"")
51+
52+KEY_2_FILE = {
53+ "rsa_private": ("/etc/ssh/ssh_host_rsa_key", 0600),
54+ "rsa_public": ("/etc/ssh/ssh_host_rsa_key.pub", 0644),
55+ "dsa_private": ("/etc/ssh/ssh_host_dsa_key", 0600),
56+ "dsa_public": ("/etc/ssh/ssh_host_dsa_key.pub", 0644),
57+ "ecdsa_private": ("/etc/ssh/ssh_host_ecdsa_key", 0600),
58+ "ecdsa_public": ("/etc/ssh/ssh_host_ecdsa_key.pub", 0644),
59+}
60+
61+PRIV_2_PUB = {
62+ 'rsa_private': 'rsa_public',
63+ 'dsa_private': 'dsa_public',
64+ 'ecdsa_private': 'ecdsa_public',
65+}
66+
67+KEY_GEN_TPL = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"'
68+
69+GENERATE_KEY_NAMES = ['rsa', 'dsa', 'ecdsa']
70+
71+KEY_FILE_TPL = '/etc/ssh/ssh_host_%s_key'
72+
73+
74+def handle(_name, cfg, cloud, log, _args):
75+
76+ # remove the static keys from the pristine image
77+ if cfg.get("ssh_deletekeys", True):
78+ key_pth = os.path.join("/etc/ssh/", "ssh_host_*key*")
79+ for f in glob.glob(key_pth):
80+ try:
81+ util.del_file(f)
82+ except:
83+ util.logexc(log, "Failed deleting key file %s", f)
84+
85+ if "ssh_keys" in cfg:
86+ # if there are keys in cloud-config, use them
87+ for (key, val) in cfg["ssh_keys"].iteritems():
88+ if key in KEY_2_FILE:
89+ tgt_fn = KEY_2_FILE[key][0]
90+ tgt_perms = KEY_2_FILE[key][1]
91+ util.write_file(tgt_fn, val, tgt_perms)
92+
93+ for (priv, pub) in PRIV_2_PUB.iteritems():
94+ if pub in cfg['ssh_keys'] or not priv in cfg['ssh_keys']:
95+ continue
96+ pair = (KEY_2_FILE[priv][0], KEY_2_FILE[pub][0])
97+ cmd = ['sh', '-xc', KEY_GEN_TPL % pair]
98+ try:
99+ # TODO(harlowja): Is this guard needed?
100+ with util.SeLinuxGuard("/etc/ssh", recursive=True):
101+ util.subp(cmd, capture=False)
102+ log.debug("Generated a key for %s from %s", pair[0], pair[1])
103+ except:
104+ util.logexc(log, "Failed generated a key for %s from %s",
105+ pair[0], pair[1])
106+ else:
107+ # if not, generate them
108+ genkeys = util.get_cfg_option_list(cfg,
109+ 'ssh_genkeytypes',
110+ GENERATE_KEY_NAMES)
111+ for keytype in genkeys:
112+ keyfile = KEY_FILE_TPL % (keytype)
113+ util.ensure_dir(os.path.dirname(keyfile))
114+ if not os.path.exists(keyfile):
115+ cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile]
116+ try:
117+ # TODO(harlowja): Is this guard needed?
118+ with util.SeLinuxGuard("/etc/ssh", recursive=True):
119+ util.subp(cmd, capture=False)
120+ except:
121+ util.logexc(log, "Failed generating key type %s to "
122+ "file %s", keytype, keyfile)
123+
124+ try:
125+ (users, _groups) = ds.normalize_users_groups(cfg, cloud.distro)
126+ (user, _user_config) = ds.extract_default(users)
127+ disable_root = util.get_cfg_option_bool(cfg, "disable_root", True)
128+ disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts",
129+ DISABLE_ROOT_OPTS)
130+
131+ keys = cloud.get_public_ssh_keys() or []
132+ if "ssh_authorized_keys" in cfg:
133+ cfgkeys = cfg["ssh_authorized_keys"]
134+ keys.extend(cfgkeys)
135+
136+ apply_credentials(keys, user, disable_root, disable_root_opts)
137+ except:
138+ util.logexc(log, "Applying ssh credentials failed!")
139+
140+
141+def apply_credentials(keys, user, disable_root, disable_root_opts):
142+
143+ keys = set(keys)
144+ if user:
145+ ssh_util.setup_user_keys(keys, user)
146+
147+ if disable_root:
148+ if not user:
149+ user = "NONE"
150+ key_prefix = disable_root_opts.replace('$USER', user)
151+ else:
152+ key_prefix = ''
153+
154+ ssh_util.setup_user_keys(keys, 'root', options=key_prefix)
155
156=== modified file 'cloudinit/config/cc_ssh.py'
157--- cloudinit/config/cc_ssh.py 2013-07-10 13:35:59 +0000
158+++ cloudinit/config/cc_ssh.py 2015-09-10 16:50:23 +0000
159@@ -20,6 +20,7 @@
160
161 import glob
162 import os
163+import sys
164
165 # Ensure this is aliased to a name not 'distros'
166 # since the module attribute 'distros'
167@@ -33,27 +34,19 @@
168 "no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\" "
169 "rather than the user \\\"root\\\".\';echo;sleep 10\"")
170
171-KEY_2_FILE = {
172- "rsa_private": ("/etc/ssh/ssh_host_rsa_key", 0600),
173- "rsa_public": ("/etc/ssh/ssh_host_rsa_key.pub", 0644),
174- "dsa_private": ("/etc/ssh/ssh_host_dsa_key", 0600),
175- "dsa_public": ("/etc/ssh/ssh_host_dsa_key.pub", 0644),
176- "ecdsa_private": ("/etc/ssh/ssh_host_ecdsa_key", 0600),
177- "ecdsa_public": ("/etc/ssh/ssh_host_ecdsa_key.pub", 0644),
178-}
179+GENERATE_KEY_NAMES = ['rsa', 'dsa', 'ecdsa', 'ed25519']
180+KEY_FILE_TPL = '/etc/ssh/ssh_host_%s_key'
181
182-PRIV_2_PUB = {
183- 'rsa_private': 'rsa_public',
184- 'dsa_private': 'dsa_public',
185- 'ecdsa_private': 'ecdsa_public',
186-}
187+CONFIG_KEY_TO_FILE = {}
188+PRIV_TO_PUB = {}
189+for k in GENERATE_KEY_NAMES:
190+ CONFIG_KEY_TO_FILE.update({"%s_private" % k: (KEY_FILE_TPL % k, 0o600)})
191+ CONFIG_KEY_TO_FILE.update(
192+ {"%s_public" % k: (KEY_FILE_TPL % k + ".pub", 0o600)})
193+ PRIV_TO_PUB["%s_private" % k] = "%s_public" % k
194
195 KEY_GEN_TPL = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"'
196
197-GENERATE_KEY_NAMES = ['rsa', 'dsa', 'ecdsa']
198-
199-KEY_FILE_TPL = '/etc/ssh/ssh_host_%s_key'
200-
201
202 def handle(_name, cfg, cloud, log, _args):
203
204@@ -69,15 +62,15 @@
205 if "ssh_keys" in cfg:
206 # if there are keys in cloud-config, use them
207 for (key, val) in cfg["ssh_keys"].iteritems():
208- if key in KEY_2_FILE:
209- tgt_fn = KEY_2_FILE[key][0]
210- tgt_perms = KEY_2_FILE[key][1]
211+ if key in CONFIG_KEY_TO_FILE:
212+ tgt_fn = CONFIG_KEY_TO_FILE[key][0]
213+ tgt_perms = CONFIG_KEY_TO_FILE[key][1]
214 util.write_file(tgt_fn, val, tgt_perms)
215
216- for (priv, pub) in PRIV_2_PUB.iteritems():
217+ for (priv, pub) in PRIV_TO_PUB.items():
218 if pub in cfg['ssh_keys'] or not priv in cfg['ssh_keys']:
219 continue
220- pair = (KEY_2_FILE[priv][0], KEY_2_FILE[pub][0])
221+ pair = (CONFIG_KEY_TO_FILE[priv][0], CONFIG_KEY_TO_FILE[pub][0])
222 cmd = ['sh', '-xc', KEY_GEN_TPL % pair]
223 try:
224 # TODO(harlowja): Is this guard needed?
225@@ -92,18 +85,28 @@
226 genkeys = util.get_cfg_option_list(cfg,
227 'ssh_genkeytypes',
228 GENERATE_KEY_NAMES)
229+ lang_c = os.environ.copy()
230+ lang_c['LANG'] = 'C'
231 for keytype in genkeys:
232 keyfile = KEY_FILE_TPL % (keytype)
233+ if os.path.exists(keyfile):
234+ continue
235 util.ensure_dir(os.path.dirname(keyfile))
236- if not os.path.exists(keyfile):
237- cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile]
238+ cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile]
239+
240+ # TODO(harlowja): Is this guard needed?
241+ with util.SeLinuxGuard("/etc/ssh", recursive=True):
242 try:
243- # TODO(harlowja): Is this guard needed?
244- with util.SeLinuxGuard("/etc/ssh", recursive=True):
245- util.subp(cmd, capture=False)
246- except:
247- util.logexc(log, "Failed generating key type %s to "
248- "file %s", keytype, keyfile)
249+ out, err = util.subp(cmd, capture=True, env=lang_c)
250+ sys.stdout.write(out)
251+ except util.ProcessExecutionError as e:
252+ err = e.stderr.lower()
253+ if (e.exit_code == 1 and
254+ err.lower().startswith("unknown key")):
255+ log.debug("ssh-keygen: unknown key type '%s'", keytype)
256+ else:
257+ util.logexc(log, "Failed generating key type %s to "
258+ "file %s", keytype, keyfile)
259
260 try:
261 (users, _groups) = ds.normalize_users_groups(cfg, cloud.distro)
262
263=== modified file 'debian/changelog'
264--- debian/changelog 2015-09-10 16:24:44 +0000
265+++ debian/changelog 2015-09-10 16:50:23 +0000
266@@ -1,10 +1,15 @@
267 cloud-init (0.7.5-0ubuntu1.11) UNRELEASED; urgency=medium
268
269+ [ Felipe Reyes ]
270 * d/patches/fix-consumption-of-vendor-data.patch:
271 - Fix consumption of vendor-data in OpenStack to allow namespacing
272 (LP: #1469260).
273
274- -- Felipe Reyes <felipe.reyes@canonical.com> Thu, 10 Sep 2015 12:50:32 -0300
275+ [ Scott Moser ]
276+ * d/patches/lp-1461242-generate-ed25519-host-keys.patch:
277+ - ssh: generate ed25519 host keys if supported (LP: #1461242)
278+
279+ -- Scott Moser <smoser@ubuntu.com> Thu, 10 Sep 2015 12:36:14 -0400
280
281 cloud-init (0.7.5-0ubuntu1.10) trusty; urgency=medium
282
283
284=== added file 'debian/patches/lp-1461242-generate-ed25519-host-keys.patch'
285--- debian/patches/lp-1461242-generate-ed25519-host-keys.patch 1970-01-01 00:00:00 +0000
286+++ debian/patches/lp-1461242-generate-ed25519-host-keys.patch 2015-09-10 16:50:23 +0000
287@@ -0,0 +1,115 @@
288+Author: Scott Moser <smoser@ubuntu.com>
289+Bug: https://launchpad.net/bugs/1461242
290+Applied-Upstream: yes
291+Origin: http://bazaar.launchpad.net/~cloud-init-dev/cloud-init/trunk/revision/1125
292+Description: ssh: generate ed25519 host keys if supported
293+ .
294+ now we attempt to generate ed25519 host keys.
295+ If ssh-keygen does not support it, a debug log message will be written.
296+
297+=== modified file 'cloudinit/config/cc_ssh.py'
298+--- a/cloudinit/config/cc_ssh.py
299++++ b/cloudinit/config/cc_ssh.py
300+@@ -20,6 +20,7 @@
301+
302+ import glob
303+ import os
304++import sys
305+
306+ # Ensure this is aliased to a name not 'distros'
307+ # since the module attribute 'distros'
308+@@ -33,26 +34,18 @@ DISABLE_ROOT_OPTS = ("no-port-forwarding
309+ "no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\" "
310+ "rather than the user \\\"root\\\".\';echo;sleep 10\"")
311+
312+-KEY_2_FILE = {
313+- "rsa_private": ("/etc/ssh/ssh_host_rsa_key", 0600),
314+- "rsa_public": ("/etc/ssh/ssh_host_rsa_key.pub", 0644),
315+- "dsa_private": ("/etc/ssh/ssh_host_dsa_key", 0600),
316+- "dsa_public": ("/etc/ssh/ssh_host_dsa_key.pub", 0644),
317+- "ecdsa_private": ("/etc/ssh/ssh_host_ecdsa_key", 0600),
318+- "ecdsa_public": ("/etc/ssh/ssh_host_ecdsa_key.pub", 0644),
319+-}
320+-
321+-PRIV_2_PUB = {
322+- 'rsa_private': 'rsa_public',
323+- 'dsa_private': 'dsa_public',
324+- 'ecdsa_private': 'ecdsa_public',
325+-}
326+-
327+-KEY_GEN_TPL = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"'
328++GENERATE_KEY_NAMES = ['rsa', 'dsa', 'ecdsa', 'ed25519']
329++KEY_FILE_TPL = '/etc/ssh/ssh_host_%s_key'
330+
331+-GENERATE_KEY_NAMES = ['rsa', 'dsa', 'ecdsa']
332++CONFIG_KEY_TO_FILE = {}
333++PRIV_TO_PUB = {}
334++for k in GENERATE_KEY_NAMES:
335++ CONFIG_KEY_TO_FILE.update({"%s_private" % k: (KEY_FILE_TPL % k, 0o600)})
336++ CONFIG_KEY_TO_FILE.update(
337++ {"%s_public" % k: (KEY_FILE_TPL % k + ".pub", 0o600)})
338++ PRIV_TO_PUB["%s_private" % k] = "%s_public" % k
339+
340+-KEY_FILE_TPL = '/etc/ssh/ssh_host_%s_key'
341++KEY_GEN_TPL = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"'
342+
343+
344+ def handle(_name, cfg, cloud, log, _args):
345+@@ -69,15 +62,15 @@ def handle(_name, cfg, cloud, log, _args
346+ if "ssh_keys" in cfg:
347+ # if there are keys in cloud-config, use them
348+ for (key, val) in cfg["ssh_keys"].iteritems():
349+- if key in KEY_2_FILE:
350+- tgt_fn = KEY_2_FILE[key][0]
351+- tgt_perms = KEY_2_FILE[key][1]
352++ if key in CONFIG_KEY_TO_FILE:
353++ tgt_fn = CONFIG_KEY_TO_FILE[key][0]
354++ tgt_perms = CONFIG_KEY_TO_FILE[key][1]
355+ util.write_file(tgt_fn, val, tgt_perms)
356+
357+- for (priv, pub) in PRIV_2_PUB.iteritems():
358++ for (priv, pub) in PRIV_TO_PUB.items():
359+ if pub in cfg['ssh_keys'] or not priv in cfg['ssh_keys']:
360+ continue
361+- pair = (KEY_2_FILE[priv][0], KEY_2_FILE[pub][0])
362++ pair = (CONFIG_KEY_TO_FILE[priv][0], CONFIG_KEY_TO_FILE[pub][0])
363+ cmd = ['sh', '-xc', KEY_GEN_TPL % pair]
364+ try:
365+ # TODO(harlowja): Is this guard needed?
366+@@ -92,18 +85,28 @@ def handle(_name, cfg, cloud, log, _args
367+ genkeys = util.get_cfg_option_list(cfg,
368+ 'ssh_genkeytypes',
369+ GENERATE_KEY_NAMES)
370++ lang_c = os.environ.copy()
371++ lang_c['LANG'] = 'C'
372+ for keytype in genkeys:
373+ keyfile = KEY_FILE_TPL % (keytype)
374++ if os.path.exists(keyfile):
375++ continue
376+ util.ensure_dir(os.path.dirname(keyfile))
377+- if not os.path.exists(keyfile):
378+- cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile]
379++ cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile]
380++
381++ # TODO(harlowja): Is this guard needed?
382++ with util.SeLinuxGuard("/etc/ssh", recursive=True):
383+ try:
384+- # TODO(harlowja): Is this guard needed?
385+- with util.SeLinuxGuard("/etc/ssh", recursive=True):
386+- util.subp(cmd, capture=False)
387+- except:
388+- util.logexc(log, "Failed generating key type %s to "
389+- "file %s", keytype, keyfile)
390++ out, err = util.subp(cmd, capture=True, env=lang_c)
391++ sys.stdout.write(out)
392++ except util.ProcessExecutionError as e:
393++ err = e.stderr.lower()
394++ if (e.exit_code == 1 and
395++ err.lower().startswith("unknown key")):
396++ log.debug("ssh-keygen: unknown key type '%s'", keytype)
397++ else:
398++ util.logexc(log, "Failed generating key type %s to "
399++ "file %s", keytype, keyfile)
400+
401+ try:
402+ (users, _groups) = ds.normalize_users_groups(cfg, cloud.distro)
403
404=== modified file 'debian/patches/series'
405--- debian/patches/series 2015-09-10 16:24:44 +0000
406+++ debian/patches/series 2015-09-10 16:50:23 +0000
407@@ -14,3 +14,4 @@
408 lp-1470890-include-regions-in-dynamic-mirror-discovery.patch
409 lp-1490796-azure-fix-mount_cb-for-symlinks.patch
410 lp-1469260-fix-consumption-of-vendor-data.patch
411+lp-1461242-generate-ed25519-host-keys.patch

Subscribers

People subscribed via source and target branches

to all changes: