summaryrefslogtreecommitdiff
path: root/cloudinit/ssh_util.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/ssh_util.py')
-rw-r--r--cloudinit/ssh_util.py105
1 files changed, 62 insertions, 43 deletions
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
index 93fd55dd..c97b3819 100644
--- a/cloudinit/ssh_util.py
+++ b/cloudinit/ssh_util.py
@@ -19,6 +19,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from StringIO import StringIO
+
+import csv
import os
import pwd
@@ -26,6 +29,7 @@ from cloudinit import log as logging
from cloudinit import util
LOG = logging.getLogger(__name__)
+DEF_SSHD_CFG = "/etc/ssh/sshd_config"
class AuthKeyEntry(object):
@@ -52,6 +56,40 @@ class AuthKeyEntry(object):
self.line = str(line)
(self.value, self.components) = self._parse(self.line, def_opt)
+ def _extract_options(self, ent):
+ """
+ The options (if present) consist of comma-separated option specifica-
+ tions. No spaces are permitted, except within double quotes.
+ Note that option keywords are case-insensitive.
+ """
+ quoted = False
+ i = 0
+ while (i < len(ent) and
+ ((quoted) or (ent[i] not in (" ", "\t")))):
+ curc = ent[i]
+ if i + 1 >= len(ent):
+ i = i + 1
+ break
+ nextc = ent[i + 1]
+ if curc == "\\" and nextc == '"':
+ i = i + 1
+ elif curc == '"':
+ quoted = not quoted
+ i = i + 1
+
+ options = ent[0:i]
+ options_lst = []
+ reader = csv.reader(StringIO(options), quoting=csv.QUOTE_NONE)
+ for row in reader:
+ for e in row:
+ e = e.strip()
+ if e:
+ options_lst.append(e)
+ toks = []
+ if i + 1 < len(ent):
+ toks = ent[i + 1:].split(None, 3)
+ return (options_lst, toks)
+
def _form_components(self, toks):
components = {}
if len(toks) == 1:
@@ -81,29 +119,10 @@ class AuthKeyEntry(object):
if len(toks) < 4:
tmp_components.update(self._form_components(toks))
else:
- # taken from auth_rsa_key_allowed in auth-rsa.c
- i = 0
- quoted = False
- try:
- while (i < len(ent) and
- ((quoted) or (ent[i] not in (" ", "\t")))):
- curc = ent[i]
- nextc = ent[i + 1]
- if curc == "\\" and nextc == '"':
- i = i + 1
- elif curc == '"':
- quoted = not quoted
- i = i + 1
- except IndexError:
- return (False, {})
- try:
- options = ent[0:i]
- toks = ent[i + 1:].split(None, 3)
- if options:
- tmp_components['options'] = options
- tmp_components.update(self._form_components(toks))
- except (IndexError, ValueError):
- return (False, {})
+ (options, toks) = self._extract_options(ent)
+ if options:
+ tmp_components['options'] = ",".join(options)
+ tmp_components.update(self._form_components(toks))
# We got some useful value!
return (True, tmp_components)
@@ -125,7 +144,7 @@ class AuthKeyEntry(object):
return ' '.join(toks)
-def update_authorized_keys(fname, keys):
+def _update_authorized_keys(fname, keys):
lines = []
try:
if os.path.isfile(fname):
@@ -159,9 +178,11 @@ def update_authorized_keys(fname, keys):
return '\n'.join(lines)
-def setup_user_keys(keys, user, key_prefix, sshd_config_fn="/etc/ssh/sshd_config"):
- pwent = pwd.getpwnam(user)
+def setup_user_keys(keys, user, key_prefix, sshd_config_fn=None):
+ if not sshd_config_fn:
+ sshd_config_fn = DEF_SSHD_CFG
+ pwent = pwd.getpwnam(user)
ssh_dir = os.path.join(pwent.pw_dir, '.ssh')
if not os.path.exists(ssh_dir):
util.ensure_dir(ssh_dir, mode=0700)
@@ -173,14 +194,12 @@ def setup_user_keys(keys, user, key_prefix, sshd_config_fn="/etc/ssh/sshd_config
with util.SeLinuxGuard(ssh_dir, recursive=True):
try:
- """
- AuthorizedKeysFile may contain tokens
- of the form %T which are substituted during connection set-up.
- The following tokens are defined: %% is replaced by a literal
- '%', %h is replaced by the home directory of the user being
- authenticated and %u is replaced by the username of that user.
- """
- ssh_cfg = parse_ssh_config(sshd_config_fn)
+ # AuthorizedKeysFile may contain tokens
+ # of the form %T which are substituted during connection set-up.
+ # The following tokens are defined: %% is replaced by a literal
+ # '%', %h is replaced by the home directory of the user being
+ # authenticated and %u is replaced by the username of that user.
+ ssh_cfg = _parse_ssh_config(sshd_config_fn)
akeys = ssh_cfg.get("authorizedkeysfile", '')
akeys = akeys.strip()
if not akeys:
@@ -193,22 +212,22 @@ def setup_user_keys(keys, user, key_prefix, sshd_config_fn="/etc/ssh/sshd_config
authorized_keys = akeys
except (IOError, OSError):
authorized_keys = os.path.join(ssh_dir, 'authorized_keys')
- LOG.exception(("Failed extracting 'AuthorizedKeysFile' in ssh config"
- " from %s, using 'AuthorizedKeysFile' file %s instead."),
+ LOG.exception(("Failed extracting 'AuthorizedKeysFile'"
+ " in ssh config"
+ " from %s, using 'AuthorizedKeysFile' file"
+ " %s instead"),
sshd_config_fn, authorized_keys)
- content = update_authorized_keys(authorized_keys, key_entries)
+ content = _update_authorized_keys(authorized_keys, key_entries)
util.ensure_dir(os.path.dirname(authorized_keys), mode=0700)
util.write_file(authorized_keys, content, mode=0600)
util.chownbyid(authorized_keys, pwent.pw_uid, pwent.pw_gid)
-def parse_ssh_config(fname):
- """
- The file contains keyword-argu-ment pairs, one per line.
- Lines starting with '#' and empty lines are interpreted as comments.
- Note: key-words are case-insensitive and arguments are case-sensitive
- """
+def _parse_ssh_config(fname):
+ # The file contains keyword-argument pairs, one per line.
+ # Lines starting with '#' and empty lines are interpreted as comments.
+ # Note: key-words are case-insensitive and arguments are case-sensitive
ret = {}
if not os.path.isfile(fname):
return ret