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