Browse Source

Add module to allow easily handle SSH access to machines.

  This module + template allows granting access to nodes based on a "global"
  switch, node_ids and roles. See the given pillar example for configuration.

Signed-off-by: Maximilian Wilhelm <max@rfc2324.org>
Maximilian Wilhelm 7 years ago
parent
commit
4a7019d5af
6 changed files with 104 additions and 6 deletions
  1. 53 0
      _modules/ffho_auth.py
  2. 4 0
      ssh/authorized_keys.tmpl
  3. 0 3
      ssh/authorized_keys_root
  4. 2 1
      ssh/init.sls
  5. 27 0
      zz_EXAMPLE_PILLAR/ssh.sls
  6. 18 2
      zz_EXAMPLE_PILLAR/top.sls

+ 53 - 0
_modules/ffho_auth.py

@@ -0,0 +1,53 @@
+#!/usr/bin/python
+#
+# Maximilian Wilhelm <max@rfc2324.org>
+#  --  Mon 23 Jan 2017 12:21:22 AM CET
+#
+
+import collections
+
+def _ssh_user_allowed (access_config, node_id, node_config, entry_name):
+	roles = node_config.get ('roles', [])
+
+	# Access config for the given user is the string "global"
+	if type (access_config) == str:
+		if access_config == "global":
+			return True
+
+	if type (access_config) not in [ dict, collections.OrderedDict ]:
+        	raise Exception ("SSH configuration for entry %s seems broken!" % (entry_name))
+
+	# String "global" found in the access config?
+	elif "global" in access_config:
+		return True
+
+	# Is there an entry for this node_id in the 'nodes' list?
+	elif node_id in access_config.get ('nodes', {}):
+		return True
+
+	# Should the key be allowed for any of the roles configured for this node?
+	for allowed_role in access_config.get ('roles', []):
+		if allowed_role in roles:
+			return True
+
+	return False
+
+
+def get_ssh_authkeys (ssh_config, node_config, node_id, username):
+	auth_keys = []
+
+	for entry_name, entry in ssh_config['keys'].items ():
+		access = entry.get ('access', {})
+		add_keys = False
+
+		# Skip this key if there's no entry for the given username
+		if username not in access:
+			continue
+
+		user_access = access.get (username)
+		if _ssh_user_allowed (user_access, node_id, node_config, entry_name):
+			for key in entry.get ('pubkeys', []):
+				if key not in auth_keys:
+					auth_keys.append (key)
+
+	return sorted (auth_keys)

+ 4 - 0
ssh/authorized_keys.tmpl

@@ -0,0 +1,4 @@
+{%- set ssh_config = salt['pillar.get']('ssh') -%}
+{%- set node_config = salt['pillar.get']('nodes:' ~ grains['id']) -%}
+{%- set auth_keys = salt['ffho_auth.get_ssh_authkeys'](ssh_config, node_config, grains['id'], username) -%}
+{{ "\n".join (auth_keys) }}

+ 0 - 3
ssh/authorized_keys_root

@@ -1,3 +0,0 @@
-{% include "ssh/authorized_keys_root.MASTER" %}
-# Special keys only allowed on this host
-{% include "ssh/authorized_keys_root.H_" ~ grains['id'] ignore missing %}

+ 2 - 1
ssh/init.sls

@@ -36,8 +36,9 @@ ssh:
 # Create authorized_keys for root (MASTER + host specific)
 /root/.ssh/authorized_keys:
   file.managed:
-    - source: salt://ssh/authorized_keys_root
+    - source: salt://ssh/authorized_keys.tmpl
     - template: jinja
+      username: root
     - user: root
     - group: root
     - mode: 644

+ 27 - 0
zz_EXAMPLE_PILLAR/ssh.sls

@@ -0,0 +1,27 @@
+ssh:
+  keys:
+    max:
+      pubkeys:
+        - "ssh-rsa ABC max@pandora"
+      access:
+        root: global
+
+    karsten:
+      pubkeys:
+        - "ssh-rsa ACBDE kb-light@leo-loewe"
+      access:
+        root:
+          global: true
+        build:
+          nodes:
+            - masterbuilder.in.ffho.net
+
+    webmaster:
+      pubkeys:
+        - "ssh-rsa AAAfoo webmaster@apache"
+      access:
+        root:
+          roles:
+            - webserver
+          nodes:
+            - fe01.in.ffho.net

+ 18 - 2
zz_EXAMPLE_PILLAR/top.sls

@@ -2,7 +2,23 @@ base:
   '*':
     - nodes
     - sites
+    - cert
     - ffho
-#    - cert
-#    - ovpn
+
+    #
+    # Role/Application specific stuff
+
+    # SSH authorized_keys configuration
+    - ssh
+
+    # Traffic engineering
     - te
+
+    # DNS server
+    - dns-server
+
+    # OpenVPN tunnels
+    - ovpn
+
+    # Anycast Healthchecker
+    - anycast-healthchecker