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