keyman 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +2 -0
- data/Gemfile.lock +16 -0
- data/README.md +80 -0
- data/bin/keyman +39 -0
- data/keyman.gemspec +20 -0
- data/lib/keyman/group.rb +24 -0
- data/lib/keyman/keyfile.rb +19 -0
- data/lib/keyman/server.rb +81 -0
- data/lib/keyman/user.rb +21 -0
- data/lib/keyman.rb +104 -0
- metadata +71 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Keyman
|
2
|
+
|
3
|
+
This simple little utility allows you to manage the authorized_keys files for a
|
4
|
+
number of servers & users. It is designed to provide easy access to ensure that
|
5
|
+
you can revoke & grant access to appropriate people on multiple servers.
|
6
|
+
|
7
|
+
**Please Note: this utility is somewhat un-tested and not currently used in any
|
8
|
+
production environment. Your mileage may vary and we recommend testing in a
|
9
|
+
non-production environment prior to use.**
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
To install, just install the Rubygem.
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ gem install keyman
|
17
|
+
```
|
18
|
+
|
19
|
+
Once installed, you will need to create yourself a **manifest directory**. This
|
20
|
+
directory will contain all your configuration for your key manager. You should
|
21
|
+
create an empty directory and add two files, a `servers.rb` and a `users.rb` file.
|
22
|
+
|
23
|
+
## Example Users/Groups Manifest File
|
24
|
+
|
25
|
+
The below file is an example of a `users.rb` manifest file.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
group :admins do
|
29
|
+
user :adam, 'ssh-rsa AAAAB3NzaC1yc2EAAAA[...]=='
|
30
|
+
user :charlie, 'ssh-rsa AAAAB3NzaC1yc2EAAAA[...]=='
|
31
|
+
user :nathan, 'ssh-rsa AAAAB3NzaC1yc2EAAAA[...]=='
|
32
|
+
end
|
33
|
+
|
34
|
+
group :staff do
|
35
|
+
user :jack, 'ssh-rsa AAAAB3NzaC1yc2EAAAA[...]=='
|
36
|
+
user :dan, 'ssh-rsa AAAAB3NzaC1yc2EAAAA[...]=='
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
## Example Server Manifest File
|
41
|
+
|
42
|
+
The below file is an example of a `servers.rb` file.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
# An example configuration for a server where all admin users have
|
46
|
+
# access as 'root' and all staff users have access as 'app'.
|
47
|
+
server do
|
48
|
+
host 'app01.myapplication.com'
|
49
|
+
user 'root', :admins
|
50
|
+
user 'app', :admins, :staff
|
51
|
+
end
|
52
|
+
|
53
|
+
# An example configuration for a server where admins plus one other user
|
54
|
+
# have access as root only.
|
55
|
+
server do
|
56
|
+
host 'database01.myapplication.com'
|
57
|
+
user 'root', :admins, :dan
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
## Pushing files to servers
|
62
|
+
|
63
|
+
In order to push files to the server, you must already have YOUR key on the
|
64
|
+
machine in order to authenticate. If you do not, you will not have access
|
65
|
+
and will therefore be unable to push configuration.
|
66
|
+
|
67
|
+
```bash
|
68
|
+
$ cd path/to/manifest
|
69
|
+
# to push configuration to all servers
|
70
|
+
$ keyman push
|
71
|
+
# to push configuration to a specific server
|
72
|
+
$ keyman push database01.myapplication.com
|
73
|
+
```
|
74
|
+
|
75
|
+
There are other commands available within the app, you can view these by
|
76
|
+
viewing the inline help.
|
77
|
+
|
78
|
+
```bash
|
79
|
+
$ keyman help
|
80
|
+
```
|
data/bin/keyman
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'keyman'
|
3
|
+
begin
|
4
|
+
|
5
|
+
# Convert command line arguments into the appropriate ruby objects
|
6
|
+
args = ARGV.dup
|
7
|
+
final_args = []
|
8
|
+
options = {}
|
9
|
+
until args.empty?
|
10
|
+
arg = args.shift
|
11
|
+
if arg =~ /\A\-/
|
12
|
+
options[arg.gsub(/\A\-+/, '').to_sym] = args.shift
|
13
|
+
else
|
14
|
+
final_args << arg
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
command = final_args.first
|
19
|
+
case command
|
20
|
+
when 'help', nil
|
21
|
+
puts "This is the Keyman utility. You can use this to execute your key manifest"
|
22
|
+
puts "commands. In order to use this, you must be currently within your"
|
23
|
+
puts "manifest directory."
|
24
|
+
puts
|
25
|
+
puts " keys {server} {user} - displays the authorized keys file for a server's user"
|
26
|
+
puts " push - pushes the latest files to all servers"
|
27
|
+
puts " push {server} - pushes the latest file to the specified server"
|
28
|
+
puts " servers - displays a list of all servers"
|
29
|
+
puts " permissions {server} - displays the permissions for given server"
|
30
|
+
puts " users - displays a list of all users & groups"
|
31
|
+
puts
|
32
|
+
else
|
33
|
+
Keyman.run(final_args, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
rescue Keyman::Error => e
|
37
|
+
puts "\e[31m" + e.message + "\e[0m"
|
38
|
+
Process.exit(1)
|
39
|
+
end
|
data/keyman.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
|
2
|
+
require 'keyman'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'keyman'
|
6
|
+
s.version = Keyman::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.summary = "A simple library for managing distributed SSH keys"
|
9
|
+
s.description = s.summary
|
10
|
+
s.files = Dir["**/*"]
|
11
|
+
s.bindir = "bin"
|
12
|
+
s.executables << 'keyman'
|
13
|
+
s.require_path = 'lib'
|
14
|
+
s.has_rdoc = false
|
15
|
+
s.author = "Adam Cooke"
|
16
|
+
s.email = "adam@atechmedia.com"
|
17
|
+
s.homepage = "http://atechmedia.com"
|
18
|
+
s.add_dependency('net-ssh', '~> 2.6.3')
|
19
|
+
end
|
20
|
+
|
data/lib/keyman/group.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Keyman
|
2
|
+
class Group
|
3
|
+
|
4
|
+
attr_accessor :name, :users
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@users = []
|
8
|
+
end
|
9
|
+
|
10
|
+
# Add a new group with the given name
|
11
|
+
def self.add(name, &block)
|
12
|
+
g = Group.new
|
13
|
+
g.name = name
|
14
|
+
g.instance_eval(&block)
|
15
|
+
Keyman.groups << g
|
16
|
+
end
|
17
|
+
|
18
|
+
# Add a new user to the group
|
19
|
+
def user(*args)
|
20
|
+
@users << User.add(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Keyman
|
2
|
+
class Keyfile
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def group(name, &users_block)
|
6
|
+
Group.add(name, &users_block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def server(&block)
|
10
|
+
Server.add(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def user(username, key)
|
14
|
+
User.add(username, key)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Keyman
|
2
|
+
class Server
|
3
|
+
|
4
|
+
attr_accessor :host, :users, :location
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@users = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Adds a new server by accepting a block of objects
|
11
|
+
def self.add(&block)
|
12
|
+
s = self.new
|
13
|
+
s.instance_eval(&block)
|
14
|
+
Keyman.servers << s
|
15
|
+
s
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns or sets the hostname of the server which should be used when connecting
|
19
|
+
# and identifying this server
|
20
|
+
def host(host = nil)
|
21
|
+
host ? @host = host : @host
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets a user on the server along with the access objects which should be
|
25
|
+
# granted access
|
26
|
+
def user(name, *access_objects)
|
27
|
+
@users[name] = access_objects
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns or sets the location of the server
|
31
|
+
def location(location = nil)
|
32
|
+
location ? @location = location : @location
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns an array of users who have access to this server. Includes
|
36
|
+
# all objects from within the server
|
37
|
+
def authorized_users(username)
|
38
|
+
@users[username].map do |k|
|
39
|
+
obj = Keyman.user_or_group_for(k)
|
40
|
+
obj.is_a?(Group) ? obj.users : obj
|
41
|
+
end.flatten.uniq
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a full string output for the authorized_keys file. Passes
|
45
|
+
# the user who's file you wish to generate.
|
46
|
+
def authorized_keys(username)
|
47
|
+
Array.new.tap do |a|
|
48
|
+
a << "# SSH Authorized Keys file generated automatically by Keyman"
|
49
|
+
a << "# Generated at: #{Time.now.utc} for #{@host}"
|
50
|
+
a << nil
|
51
|
+
authorized_users(username).each do |u|
|
52
|
+
a << "# #{u.name}"
|
53
|
+
a << u.key + "\n"
|
54
|
+
end
|
55
|
+
end.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Push the authorized keys file to the appropriate server for the users
|
59
|
+
# configured here. This will not succeed if the current user does not
|
60
|
+
# already have a key on the server.
|
61
|
+
def push!
|
62
|
+
@users.each do |user, objects|
|
63
|
+
begin
|
64
|
+
Timeout.timeout(10) do |t|
|
65
|
+
Net::SSH.start(self.host, user) do |ssh|
|
66
|
+
ssh.exec!("mkdir -p ~/.ssh")
|
67
|
+
file = authorized_keys(user).gsub("\n", "\\n").gsub("\t", "\\t")
|
68
|
+
ssh.exec!("echo -e '#{file}' > ~/.ssh/authorized_keys")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
puts "\e[32mPushed authorized_keys to #{user}@#{self.host}\e[0m"
|
72
|
+
rescue Timeout::Error
|
73
|
+
puts "\e[31mTimed out while uploading authorized_keys to #{user}@#{self.host}\e[0m"
|
74
|
+
rescue
|
75
|
+
puts "\e[31mFailed to upload authorized_keys to #{user}@#{self.host}\e[0m"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
data/lib/keyman/user.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Keyman
|
2
|
+
class User
|
3
|
+
|
4
|
+
attr_accessor :name, :key
|
5
|
+
|
6
|
+
# Add a new user with the given details
|
7
|
+
def self.add(name, key)
|
8
|
+
existing = Keyman.users.select { |u| u.name == name.to_sym }.first
|
9
|
+
if existing
|
10
|
+
existing
|
11
|
+
else
|
12
|
+
u = self.new
|
13
|
+
u.name = name.to_sym
|
14
|
+
u.key = key
|
15
|
+
Keyman.users << u
|
16
|
+
u
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/keyman.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
require 'keyman/user'
|
4
|
+
require 'keyman/group'
|
5
|
+
require 'keyman/server'
|
6
|
+
require 'keyman/keyfile'
|
7
|
+
|
8
|
+
|
9
|
+
module Keyman
|
10
|
+
|
11
|
+
# The current version of the keyman system
|
12
|
+
VERSION = '1.0.0'
|
13
|
+
|
14
|
+
# An error which will be raised
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Storage for all the users, groups & servers which are loaded
|
19
|
+
# from the manifest
|
20
|
+
attr_accessor :users
|
21
|
+
attr_accessor :groups
|
22
|
+
attr_accessor :servers
|
23
|
+
|
24
|
+
# Load a manifest from the given folder
|
25
|
+
def load(directory)
|
26
|
+
self.users = []
|
27
|
+
self.groups = []
|
28
|
+
self.servers = []
|
29
|
+
if File.directory?(directory)
|
30
|
+
['groups.rb', 'servers.rb'].each do |file|
|
31
|
+
path = File.join(directory, file)
|
32
|
+
if File.exist?(path)
|
33
|
+
Keyman::Keyfile.class_eval(File.read(path))
|
34
|
+
else
|
35
|
+
raise Error, "No '#{file}' was found in your manifest directory. Abandoning..."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
else
|
39
|
+
raise Error, "No folder found at '#{directory}'"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return a user or a group for the given name
|
44
|
+
def user_or_group_for(name)
|
45
|
+
self.users.select { |u| u.name == name.to_sym }.first || self.groups.select { |u| u.name == name.to_sym }.first
|
46
|
+
end
|
47
|
+
|
48
|
+
# Execute a CLI command
|
49
|
+
def run(args, options = {})
|
50
|
+
load('./')
|
51
|
+
case args.first
|
52
|
+
when 'keys'
|
53
|
+
if server = self.servers.select { |s| s.host == args[1] }.first
|
54
|
+
if server.users[args[2]]
|
55
|
+
puts server.authorized_keys(args[2])
|
56
|
+
else
|
57
|
+
raise Error, "'#{args[2]}' is not a valid user for this host"
|
58
|
+
end
|
59
|
+
else
|
60
|
+
raise Error, "No server found with the hostname '#{args[1]}'"
|
61
|
+
end
|
62
|
+
when 'permissions'
|
63
|
+
if server = self.servers.select { |s| s.host == args[1] }.first
|
64
|
+
server.users.each do |username, objects|
|
65
|
+
puts '-' * 80
|
66
|
+
puts "\e[32m#{username}\e[0m can be used by:"
|
67
|
+
puts '-' * 80
|
68
|
+
server.authorized_users(username).each do |o|
|
69
|
+
puts " * #{o.name}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
else
|
73
|
+
raise Error, "No server found with the hostname '#{args[1]}'"
|
74
|
+
end
|
75
|
+
when 'push'
|
76
|
+
if args[1]
|
77
|
+
# push single server
|
78
|
+
if server = self.servers.select { |s| s.host == args[1] }.first
|
79
|
+
server.push!
|
80
|
+
else
|
81
|
+
raise Error, "No server found with the hostname '#{args[1]}'"
|
82
|
+
end
|
83
|
+
else
|
84
|
+
self.servers.each(&:push!)
|
85
|
+
end
|
86
|
+
when 'servers'
|
87
|
+
self.servers.each do |server|
|
88
|
+
puts " * " + server.host
|
89
|
+
end
|
90
|
+
when 'users'
|
91
|
+
self.groups.each do |group|
|
92
|
+
puts "-" * 80
|
93
|
+
puts group.name
|
94
|
+
puts "-" * 80
|
95
|
+
group.users.each do |u|
|
96
|
+
puts "\e[32m#{u.name}\e[0m"
|
97
|
+
puts "\e[37m#{u.key}\e[0m"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: keyman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Cooke
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: net-ssh
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.6.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.6.3
|
30
|
+
description: A simple library for managing distributed SSH keys
|
31
|
+
email: adam@atechmedia.com
|
32
|
+
executables:
|
33
|
+
- keyman
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- bin/keyman
|
38
|
+
- Gemfile
|
39
|
+
- Gemfile.lock
|
40
|
+
- keyman.gemspec
|
41
|
+
- lib/keyman/group.rb
|
42
|
+
- lib/keyman/keyfile.rb
|
43
|
+
- lib/keyman/server.rb
|
44
|
+
- lib/keyman/user.rb
|
45
|
+
- lib/keyman.rb
|
46
|
+
- README.md
|
47
|
+
homepage: http://atechmedia.com
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.23
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: A simple library for managing distributed SSH keys
|
71
|
+
test_files: []
|