Ori.livneh has submitted this change and it was merged.

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 ...

A `keyholder` shell script provides a simplified command-line interface
for using the agent.

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

Approvals:
  Ori.livneh: Verified; Looks good to me, approved
  Faidon Liambotis: Looks good to me, approved



diff --git a/modules/keyholder/files/keyholder 
b/modules/keyholder/files/keyholder
new file mode 100755
index 0000000..499db3a
--- /dev/null
+++ b/modules/keyholder/files/keyholder
@@ -0,0 +1,49 @@
+#!/bin/bash
+# keyholder -- Manage shared SSH agent
+
+show_usage() {
+  /bin/echo >&2 "keyholder -- Manage shared SSH agent
+
+  keyholder add KEY
+    Add a private key identity to the agent
+
+  keyholder list
+    Lists fingerprints of all identities currently represented by the agent
+
+  keyholder list-proxy
+    Lists fingerprints of all identities currently represented by the proxy
+
+  keyholder clear
+    Deletes all identities from the agent
+
+  keyholder start/stop/restart
+    Start / stop / restart the keyholder service
+  "
+  exit 1
+}
+
+command=$1; shift
+case "$command" in
+    status)
+        /sbin/status keyholder-agent
+        /sbin/status keyholder-proxy
+        ;;
+    list)
+        SSH_AUTH_SOCK=/run/keyholder/agent.sock /usr/bin/ssh-add -l
+        ;;
+    list-proxy)
+        SSH_AUTH_SOCK=/run/keyholder/proxy.sock /usr/bin/ssh-add -l
+        ;;
+    add)
+        SSH_AUTH_SOCK=/run/keyholder/agent.sock /usr/bin/sudo -u keyholder -E 
/usr/bin/ssh-add "$@"
+        ;;
+    clear)
+        SSH_AUTH_SOCK=/run/keyholder/agent.sock /usr/bin/sudo -u keyholder -E 
/usr/bin/ssh-add -D
+        ;;
+    start|stop|restart)
+        "/sbin/${command}" keyholder-agent
+        ;;
+    *)
+        show_usage
+        ;;
+esac
diff --git a/modules/keyholder/files/keyholder-agent.conf 
b/modules/keyholder/files/keyholder-agent.conf
new file mode 100644
index 0000000..21a57ba
--- /dev/null
+++ b/modules/keyholder/files/keyholder-agent.conf
@@ -0,0 +1,16 @@
+# 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)
+
+setgid keyholder
+setuid keyholder
+
+exec /usr/bin/ssh-agent -d -a /run/keyholder/agent.sock
+post-start exec [ -S /run/keyholder/agent.sock ] || sleep 1
+post-stop exec /bin/rm -f /run/keyholder/agent.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..69d3049
--- /dev/null
+++ b/modules/keyholder/files/ssh-agent-proxy
@@ -0,0 +1,112 @@
+#!/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 CODE, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+"""
+import argparse
+import grp
+import os
+import pwd
+import select
+import socket
+import socketserver
+import struct
+import syslog
+
+
+SSH_AGENT_FAILURE = 5
+SO_PEERCRED = 17
+
+s_message_header = struct.Struct('!LB')
+s_ucred = struct.Struct('2Ii')
+
+syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_AUTH)
+
+
+class SshAgentProxyHandler(socketserver.BaseRequestHandler):
+    # See <http://api.libssh.org/rfc/PROTOCOL.agent>
+    permitted_requests = {
+        0x1: 'SSH_AGENTC_REQUEST_RSA_IDENTITIES',
+        0xb: 'SSH2_AGENTC_REQUEST_IDENTITIES',
+        0xd: 'SSH2_AGENTC_SIGN_REQUEST',
+    }
+
+    def get_peer_credentials(self, sock):
+        credentials = sock.getsockopt(
+            socket.SOL_SOCKET, SO_PEERCRED, s_ucred.size)
+        pid, uid, gid = s_ucred.unpack(credentials)
+        return pwd.getpwuid(uid).pw_name, grp.getgrgid(gid).gr_name
+
+    def setup(self):
+        self.proxy = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        self.proxy.setblocking(False)
+        self.proxy.connect(self.connect)
+        self.sockets = (self.request, self.proxy)
+
+    def recv_message(self, sock):
+        header = sock.recv(s_message_header.size, socket.MSG_WAITALL)
+        if len(header) < s_message_header.size:
+            return None, b''
+        size, code = s_message_header.unpack(header)
+        message = sock.recv(size - 1, socket.MSG_WAITALL)
+        return code, message
+
+    def send_message(self, sock, code, message=b''):
+        header = s_message_header.pack(len(message) + 1, code)
+        sock.sendall(header + message)
+
+    def handle(self):
+        while 1:
+            readable, *_ = select.select(self.sockets, (), (), 1)
+            if self.proxy in readable:
+                self.request.sendall(self.proxy.recv(64 * 1024))
+            if self.request in readable:
+                code, message = self.recv_message(self.request)
+                if code is None:
+                    return
+                user, group = self.get_peer_credentials(self.request)
+                req = self.permitted_requests.get(code, 'UNKNOWN (%s)' % code)
+                syslog.syslog('Received %s from %s:%s' % (req, user, group))
+                if code in self.permitted_requests:
+                    self.send_message(self.proxy, code, message)
+                else:
+                    self.send_message(self.request, SSH_AGENT_FAILURE)
+
+
+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
+syslog.syslog('Proxying %s -> %s' % (args.bind, 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..f19c4f5
--- /dev/null
+++ b/modules/keyholder/manifests/init.pp
@@ -0,0 +1,118 @@
+# == 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 { '/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':
+        content => template('keyholder/keyholder-proxy.conf.erb'),
+        owner   => 'root',
+        group   => 'root',
+        mode    => '0444',
+        notify  => Service['keyholder-proxy'],
+    }
+
+    service { 'keyholder-proxy':
+        ensure   => running,
+        provider => 'upstart',
+        require  => Service['keyholder-agent'],
+    }
+
+
+    # The `keyholder` script provides a simplified command-line
+    # interface for managing the agent. See `keyholder --help`.
+
+    file { '/usr/local/sbin/keyholder':
+        source => 'puppet:///modules/keyholder/keyholder',
+        owner  => 'root',
+        group  => 'root',
+        mode   => '0555',
+        notify => Service['keyholder-proxy'],
+    }
+}
diff --git a/modules/keyholder/templates/keyholder-proxy.conf.erb 
b/modules/keyholder/templates/keyholder-proxy.conf.erb
new file mode 100644
index 0000000..5271516
--- /dev/null
+++ b/modules/keyholder/templates/keyholder-proxy.conf.erb
@@ -0,0 +1,21 @@
+# 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
+
+setuid keyholder
+setgid <%= @trusted_group %>
+
+umask 007
+
+pre-start exec /bin/rm -f /run/keyholder/proxy.sock
+exec /usr/local/bin/ssh-agent-proxy
+post-stop exec /bin/rm -f /run/keyholder/proxy.sock
+
+# vim: set ft=upstart:

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ic683f21d408cd2a7b3ddf3f15994b83b4dab761f
Gerrit-PatchSet: 4
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Ori.livneh <[email protected]>
Gerrit-Reviewer: BryanDavis <[email protected]>
Gerrit-Reviewer: CSteipp <[email protected]>
Gerrit-Reviewer: Faidon Liambotis <[email protected]>
Gerrit-Reviewer: Ori.livneh <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to