Ori.livneh has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/165779

Change subject: add `keyholder` module for managing a shared ssh-agent
......................................................................

add `keyholder` module for managing a shared ssh-agent

The keyholder class provides a means of allowing a group of trusted
users to use a shared SSH identity without exposing the identity's
private key. This is accomplished by running a pair of SSH agents
as system services: `keyholder-agent` and `keyholder-proxy`:
`keyholder-agent` is the actual ssh-agent instance that holds the
private key. `keyholder-proxy` proxies requests to the agent via a
domain socket that is owned by the trusted user group. The proxy
implements a subset of the ssh-agent protocol, allowing users to list
identities and to use them to sign requests, but not to add or remove
identities.

The two services bind domain sockets at these addresses:

  /run/keyholder
  ├── agent.sock (0600)
  └── proxy.sock (0660)

Before the shared SSH agent can be used, it must be armed by a user
with access to the private key. This can be done by running:

 $ SSH_AUTH_SOCK=/run/keyholder/agent.sock ssh-add /path/to/key

Users in the trusted group can use the shared agent by running:

 $ SSH_AUTH_SOCK=/run/keyholder/proxy.sock ssh remote-host ...

Change-Id: Ic683f21d408cd2a7b3ddf3f15994b83b4dab761f
---
A modules/keyholder/files/keyholder-agent.conf
A modules/keyholder/files/keyholder-proxy.conf
A modules/keyholder/files/ssh-agent-proxy
A modules/keyholder/manifests/init.pp
4 files changed, 282 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/puppet 
refs/changes/79/165779/1

diff --git a/modules/keyholder/files/keyholder-agent.conf 
b/modules/keyholder/files/keyholder-agent.conf
new file mode 100644
index 0000000..dabd313
--- /dev/null
+++ b/modules/keyholder/files/keyholder-agent.conf
@@ -0,0 +1,31 @@
+# keyholder-agent - Shared SSH-agent
+#
+# Runs the ssh-agent(1) instance that holds shared identities.
+
+description "Shared SSH agent"
+
+start on (local-filesystems and net-device-up IFACE!=lo)
+
+pre-start script
+    [ ! -r /etc/default/keyholder ] && { stop; exit 0; }
+    . /etc/default/keyholder
+    /bin/rm -f /run/keyholder/agent.pid /run/keyholder/agent.sock
+    /usr/bin/install -m 0755 -g keyholder -o keyholder -d /run/keyholder
+end script
+
+script
+  /sbin/start-stop-daemon --quiet --start  \
+    --chuid "keyholder:keyholder"          \
+    --pidfile /run/keyholder/agent.pid     \
+    --startas /usr/bin/ssh-agent --        \
+      -d -a /run/keyholder/agent.sock
+end script
+
+post-start script
+    . /etc/default/keyholder
+    [ -S /run/keyholder/agent.sock ] || sleep 1
+end script
+
+post-stop exec /bin/rm -f /run/keyholder/agent.pid /run/keyholder/agent.sock
+
+# vim: set ft=upstart:
diff --git a/modules/keyholder/files/keyholder-proxy.conf 
b/modules/keyholder/files/keyholder-proxy.conf
new file mode 100644
index 0000000..0db3830
--- /dev/null
+++ b/modules/keyholder/files/keyholder-proxy.conf
@@ -0,0 +1,30 @@
+# keyholder-proxy - Filtering proxy for ssh-agent(1)
+#
+# The `keyholder-proxy` service runs the filtering ssh-agent proxy
+# that acts as an intermediary between users in the trusted group
+# and the backend ssh-agent that holds the shared key(s).
+
+description "Shared SSH agent proxy"
+
+start on started keyholder-agent
+stop on stopped keyholder-agent
+
+pre-start script
+    [ ! -S /run/keyholder/agent.sock ] && sleep 0.5
+    [ ! -r /etc/default/keyholder ] && { stop; exit 0; }
+    . /etc/default/keyholder
+    /bin/rm -f /run/keyholder/proxy.sock
+end script
+
+script
+  . /etc/default/keyholder
+  /sbin/start-stop-daemon --quiet --start    \
+    --chuid "keyholder:${KEYHOLDER_GROUP}"   \
+    --pidfile /run/keyholder/proxy.pid       \
+    --umask 007                              \
+    --startas /usr/local/bin/ssh-agent-proxy
+end script
+
+post-stop exec /bin/rm -f /run/keyholder/proxy.sock
+
+# vim: set ft=upstart:
diff --git a/modules/keyholder/files/ssh-agent-proxy 
b/modules/keyholder/files/ssh-agent-proxy
new file mode 100644
index 0000000..ecfad4a
--- /dev/null
+++ b/modules/keyholder/files/ssh-agent-proxy
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+  ssh-agent-proxy -- filtering proxy for ssh-agent
+
+  Creates a UNIX domain socket that proxies connections to an ssh-agent(1)
+  socket, disallowing any operations except listing identities and signing
+  requests.
+
+  usage: ssh-agent-proxy [--bind ADDRESS] [--connect ADDRESS]
+
+  Options:
+    --bind ADDRESS     Bind the proxy to the UNIX domain socket at this address
+    --connect ADDRESS  Proxy connects to the ssh-agent socket at this address
+
+
+  Copyright 2014 Ori Livneh <[email protected]>
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+"""
+import argparse
+import os
+import select
+import socket
+import socketserver
+import struct
+
+
+# See <http://api.libssh.org/rfc/PROTOCOL.agent>
+SSH_AGENTC_REQUEST_RSA_IDENTITIES = 1
+SSH2_AGENTC_REQUEST_IDENTITIES = 11
+SSH2_AGENTC_SIGN_REQUEST = 13
+SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9
+SSH_AGENT_FAILURE = 5
+SSH_AGENT_SUCCESS = 6
+
+
+class SshAgentProxyHandler(socketserver.BaseRequestHandler):
+
+    permitted_requests = (
+        SSH_AGENTC_REQUEST_RSA_IDENTITIES,
+        SSH2_AGENTC_REQUEST_IDENTITIES,
+        SSH2_AGENTC_SIGN_REQUEST,
+    )
+
+    def setup(self):
+        self.proxy = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        self.proxy.setblocking(0)
+        self.proxy.connect(self.connect)
+        self.sockets = [self.request, self.proxy]
+
+    def recv_message(self, socket):
+        header = socket.recv(5)
+        if not header:
+            return None, b''
+        size, kind = struct.unpack_from('!LB', header)
+        message = socket.recv(size - 1)
+        return kind, message
+
+    def send_message(self, socket, kind, message=b''):
+        header = struct.pack('!LB', len(message) + 1, kind)
+        socket.sendall(header + message)
+
+    def handle(self):
+        while 1:
+            readable, *_ = select.select(self.sockets, [], [], 1)
+            for socket in readable:
+                kind, message = self.recv_message(socket)
+                if kind is None:
+                    return
+
+                if socket == self.proxy:
+                    self.send_message(self.request, kind, message)
+                else:
+                    if kind == SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
+                        self.send_message(self.request, SSH_AGENT_SUCCESS)
+                    elif kind in self.permitted_requests:
+                        self.send_message(self.proxy, kind, message)
+                    else:
+                        self.send_message(self.request, SSH_AGENT_FAILURE)
+
+    def finish(self):
+        for socket in self.sockets:
+            socket.close()
+
+
+ap = argparse.ArgumentParser(description='Filtering proxy for ssh-agent(1)')
+ap.add_argument('--bind', default='/run/keyholder/proxy.sock',
+                help='Bind the proxy to the domain socket at this address')
+ap.add_argument('--connect', default='/run/keyholder/agent.sock',
+                help='Proxy connects to the ssh-agent socket at this address')
+args = ap.parse_args()
+
+SshAgentProxyHandler.connect = args.connect
+proxy = socketserver.ThreadingUnixStreamServer(args.bind, SshAgentProxyHandler)
+proxy.serve_forever()
diff --git a/modules/keyholder/manifests/init.pp 
b/modules/keyholder/manifests/init.pp
new file mode 100644
index 0000000..091c538
--- /dev/null
+++ b/modules/keyholder/manifests/init.pp
@@ -0,0 +1,114 @@
+# == Class: keyholder
+#
+# The keyholder class provides a means of allowing a group of trusted
+# users to use a shared SSH identity without exposing the identity's
+# private key. This is accomplished by running a pair of SSH agents
+# as system services: `keyholder-agent` and `keyholder-proxy`:
+# `keyholder-agent` is the actual ssh-agent instance that holds the
+# private key. `keyholder-proxy` proxies requests to the agent via a
+# domain socket that is owned by the trusted user group. The proxy
+# implements a subset of the ssh-agent protocol, allowing users to list
+# identities and to use them to sign requests, but not to add or remove
+# identities.
+#
+# The two services bind domain sockets at these addresses:
+#
+#   /run/keyholder
+#   ├── agent.sock (0600)
+#   └── proxy.sock (0660)
+#
+# Before the shared SSH agent can be used, it must be armed by a user
+# with access to the private key. This can be done by running:
+#
+#  $ SSH_AUTH_SOCK=/run/keyholder/agent.sock ssh-add /path/to/key
+#
+# Users in the trusted group can use the shared agent by running:
+#
+#  $ SSH_AUTH_SOCK=/run/keyholder/proxy.sock ssh remote-host ...
+#
+# === Parameters
+#
+# [*trusted_group*]
+#   The name or GID of the trusted user group with which the agent
+#   should be shared. It is the caller's responsibility to ensure
+#   the group exists.
+#
+# === Examples
+#
+#  class { 'keyholder':
+#      trusted_group => 'wikidev',
+#      require       => Group['wikidev'],
+#  }
+#
+# === Bugs
+#
+# It is currently only possible to have a single agent / proxy pair
+# (shared with just one group) on a particular node.
+#
+class keyholder( $trusted_group ) {
+    group { 'keyholder':
+        ensure => present,
+    }
+
+    user { 'keyholder':
+        ensure     => present,
+        gid        => 'keyholder',
+        shell      => '/bin/false',
+        home       => '/nonexistent',
+        system     => true,
+        managehome => false,
+    }
+
+    file { '/etc/default/keyholder':
+        content => "KEYHOLDER_GROUP=${trusted_group}\n",
+        owner   => 'root',
+        group   => 'root',
+        mode    => '0444',
+        notify  => Service['keyholder-agent'],
+    }
+
+    file { '/usr/local/bin/ssh-agent-proxy':
+        source => 'puppet:///modules/keyholder/ssh-agent-proxy',
+        owner  => 'root',
+        group  => 'root',
+        mode   => '0555',
+        notify => Service['keyholder-agent'],
+    }
+
+
+    # The `keyholder-agent` service is responsible for running
+    # the ssh-agent instance that will hold shared key(s).
+
+    file { '/etc/init/keyholder-agent.conf':
+        source => 'puppet:///modules/keyholder/keyholder-agent.conf',
+        owner  => 'root',
+        group  => 'root',
+        mode   => '0444',
+        notify => Service['keyholder-agent'],
+    }
+
+    service { 'keyholder-agent':
+        ensure   => running,
+        provider => 'upstart',
+        require  => User['keyholder'],
+    }
+
+
+    # The `keyholder-proxy` service runs the filtering ssh-agent proxy
+    # that acts as an intermediary between users in the trusted group
+    # and the backend ssh-agent that holds the shared key(s).
+
+    file { '/etc/init/keyholder-proxy.conf':
+        source => 'puppet:///modules/keyholder/keyholder-proxy.conf',
+        owner  => 'root',
+        group  => 'root',
+        mode   => '0444',
+        notify => Service['keyholder-proxy'],
+    }
+
+    service { 'keyholder-proxy':
+        ensure   => running,
+        provider => 'upstart',
+        require  => Service['keyholder-agent'],
+    }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/165779
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic683f21d408cd2a7b3ddf3f15994b83b4dab761f
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Ori.livneh <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to