moranis 0.5
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/.gitignore +8 -0
- data/Gemfile +4 -0
- data/README.md +88 -0
- data/Rakefile +1 -0
- data/bin/key_master +23 -0
- data/lib/moranis.rb +6 -0
- data/lib/moranis/key_master.rb +81 -0
- data/lib/moranis/remote_run.rb +84 -0
- data/lib/moranis/version.rb +3 -0
- data/moranis.gemspec +24 -0
- metadata +56 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
#Moranis
|
2
|
+
|
3
|
+
Centralized Public Key management for small teams with lots of servers
|
4
|
+
|
5
|
+
##Why?
|
6
|
+
|
7
|
+
Because my team has many servers, many developers and few system administrators and LDAP is great but it adds more overhead.
|
8
|
+
This project currently only support SSH1/openSSH1 because in practice I have yet to need support for the second generation variants
|
9
|
+
and did not want to have to write out two different key file formats but adding support should not be difficult
|
10
|
+
|
11
|
+
##How?
|
12
|
+
|
13
|
+
The basic idea is that anyone on the team who already has key based access as a specific user can be trusted to grant that same access
|
14
|
+
to others. You or your system administrator may disagree but in practice this makes sense for small to medium sized teams.
|
15
|
+
|
16
|
+
The idea is pretty simple:
|
17
|
+
Keep a local list of users and public keys that can be sync'd to many hosts that contain those users.
|
18
|
+
|
19
|
+
I recommend not using this for root accounts. Basically always make sure there is an account that you can access to revert any
|
20
|
+
rogue changes that render an account non accessible. The revert feature will fall back to root if the original user is not accessible.
|
21
|
+
This means that a team member with root access may need to perform the revert for a user that does not have that access
|
22
|
+
|
23
|
+
* Connect to each host in the host list for a given user
|
24
|
+
* Write out the new authorized_keys.tmp file based on the locally enabled keys
|
25
|
+
* Test the new key file
|
26
|
+
* If any errors were encountered the old key file remains in place
|
27
|
+
|
28
|
+
##Usage
|
29
|
+
|
30
|
+
###Standalone
|
31
|
+
A binary called key_master is installed with the gem. The binary accepts two required and one optional paramters
|
32
|
+
The action you want to take for the group, The group you want to sync the keys for, and a config file that contains the users
|
33
|
+
hosts and keys.
|
34
|
+
|
35
|
+
The config file portion can be removed if you set MORANIS_CONFIG_PATH in your environment to the path to your config file or if your
|
36
|
+
present working directory is relative to the config file as ./config/moranis.yml
|
37
|
+
|
38
|
+
The config file format is as follows
|
39
|
+
|
40
|
+
```bash
|
41
|
+
key_master sync group_name_1 ./config/config.yml
|
42
|
+
|
43
|
+
key_master sync group_name_1
|
44
|
+
````
|
45
|
+
|
46
|
+
```haml
|
47
|
+
|
48
|
+
group_name_1:
|
49
|
+
hosts:
|
50
|
+
- host1.com
|
51
|
+
- host2.com
|
52
|
+
users:
|
53
|
+
- user1
|
54
|
+
- user2
|
55
|
+
keys:
|
56
|
+
- ssh-dss key1abcdeif....
|
57
|
+
- ssh-rsa key2abcdeif....
|
58
|
+
|
59
|
+
|
60
|
+
group_name_2:
|
61
|
+
hosts:
|
62
|
+
- host1a.com
|
63
|
+
- host2a.com
|
64
|
+
users:
|
65
|
+
- user1a
|
66
|
+
- user2a
|
67
|
+
keys:
|
68
|
+
- ...
|
69
|
+
|
70
|
+
````
|
71
|
+
###Use From your code
|
72
|
+
```ruby
|
73
|
+
require 'moranis'
|
74
|
+
|
75
|
+
key_master = Moranis::KeyMaster.new(config_path)
|
76
|
+
|
77
|
+
#sync the keys with the local configuration on the specified group
|
78
|
+
key_master.run_for_group(group)
|
79
|
+
|
80
|
+
#revert the keys fro the specified group to the most recent backup
|
81
|
+
key_master.revert_for_group(group)
|
82
|
+
````
|
83
|
+
##TODO
|
84
|
+
* Add support for a key database as well as the current yml file
|
85
|
+
* Add more fault tolerance and error handling, add checking to see if the root account is being synced and provide a warning
|
86
|
+
* Tests
|
87
|
+
* Support for ssh2/openssh2
|
88
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/key_master
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "moranis"
|
3
|
+
|
4
|
+
action = ARGV[0] || ""
|
5
|
+
group = ARGV[1] || ""
|
6
|
+
|
7
|
+
config_path = ARGV[2] || ENV["MORANIS_CONFIG_PATH"] || "#{Dir.pwd}/config/moranis.yml"
|
8
|
+
|
9
|
+
key_master = Moranis::KeyMaster.new(config_path) if config_path
|
10
|
+
|
11
|
+
case action
|
12
|
+
when "sync" ; key_master.run_for_group(group)
|
13
|
+
when "revert"; key_master.revert_for_group(group)
|
14
|
+
else
|
15
|
+
puts <<-HELP
|
16
|
+
====================================================================
|
17
|
+
Moranis: Clortho the key master
|
18
|
+
|
19
|
+
USAGE: key_master <sync|revert> <group> <optional config name>
|
20
|
+
|
21
|
+
====================================================================
|
22
|
+
HELP
|
23
|
+
end
|
data/lib/moranis.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module Moranis
|
2
|
+
class KeyMaster
|
3
|
+
|
4
|
+
#TODO: move this in to a configuration object
|
5
|
+
#typical file is either authorized_keys, #authorized_keys2 or authorized depending on ssh version
|
6
|
+
#I only needed ssh1 support for my situation but this is here for convenience and can be changed
|
7
|
+
#by setting Moranis::Keymaster::KEY_FILE = "authorized_keys2"
|
8
|
+
#given that we have not yet added support for ssh2 in the output file format this is probably not particularly useful yet
|
9
|
+
KEY_FILE = "authorized_keys"
|
10
|
+
|
11
|
+
def initialize(config_path)
|
12
|
+
@config = YAML::load(File.open(config_path))
|
13
|
+
end
|
14
|
+
|
15
|
+
def groups
|
16
|
+
@config.keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_for_group(group)
|
20
|
+
hosts = @config[group]["hosts"]
|
21
|
+
keys = @config[group]["keys"]
|
22
|
+
users = @config[group]["users"]
|
23
|
+
|
24
|
+
hosts.each do |host|
|
25
|
+
users.each do |user|
|
26
|
+
puts "Generating keys on #{host} for #{user}"
|
27
|
+
remote_run = Moranis::RemoteRun.new(host,user)
|
28
|
+
|
29
|
+
#make sure the temporary auth file exists
|
30
|
+
remote_run.commands << "touch ~/.ssh/#{KEY_FILE}.tmp"
|
31
|
+
|
32
|
+
#make sure the permissions are set up properly
|
33
|
+
remote_run.commands << "chmod 600 ~/.ssh/#{KEY_FILE}.tmp"
|
34
|
+
|
35
|
+
#clear the temporary key file
|
36
|
+
remote_run.commands << "cat /dev/null > ~/.ssh/#{KEY_FILE}.tmp"
|
37
|
+
|
38
|
+
#append the keys to the file
|
39
|
+
keys.each do |key|
|
40
|
+
remote_run.commands << "echo \"#{key}\" >> ~/.ssh/#{KEY_FILE}.tmp"
|
41
|
+
end
|
42
|
+
|
43
|
+
#swap the key files
|
44
|
+
remote_run.commands << "mv ~/.ssh/#{KEY_FILE} ~/.ssh/#{KEY_FILE}.bak"
|
45
|
+
remote_run.commands << "mv ~/.ssh/#{KEY_FILE}.tmp ~/.ssh/#{KEY_FILE}"
|
46
|
+
|
47
|
+
begin
|
48
|
+
remote_run.execute
|
49
|
+
rescue Net::SSH::AuthenticationFailed
|
50
|
+
puts "You do not have access to #{user} on #{host}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def revert_for_group(group)
|
57
|
+
hosts = @config[group]["hosts"]
|
58
|
+
keys = @config[group]["keys"]
|
59
|
+
users = @config[group]["users"]
|
60
|
+
|
61
|
+
hosts.each do |host|
|
62
|
+
users.each do |user|
|
63
|
+
remote_run = Moranis::RemoteRun.new(host,user)
|
64
|
+
begin
|
65
|
+
remote_run.commands << "if [ -f ~/.ssh/#{KEY_FILE}.bak ] then mv ~/.ssh/#{KEY_FILE}.bak ~/.ssh/#{KEY_FILE} fi"
|
66
|
+
remote_run.execute
|
67
|
+
rescue
|
68
|
+
#we tried as the user, the keys file may be messed up or our key does not exist in the list any longer
|
69
|
+
# lets try as root
|
70
|
+
root_remote_run = Moranis::RemoteRun.new(host,"root")
|
71
|
+
root_remote_run.commands << "su - #{user}"
|
72
|
+
root_remote_run.commands << "if [ -f ~/.ssh/#{KEY_FILE}.bak ] then mv ~/.ssh/#{KEY_FILE}.bak ~/.ssh/#{KEY_FILE} fi"
|
73
|
+
root_remote_run.execute rescue Net::SSH::AuthenticationFailed (puts "You may not have access to root on #{host}")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Execute commands on a remote server
|
2
|
+
#
|
3
|
+
# Usage:
|
4
|
+
#
|
5
|
+
# r = RemoteRun.new("qa-d0","webadmit_etl")
|
6
|
+
# r.commands << "source /etc/profile"
|
7
|
+
# r.commands << "cd ~/current"
|
8
|
+
# r.commands << "rake extractor:run cas=3 users=12550"
|
9
|
+
#
|
10
|
+
# r.execute
|
11
|
+
#
|
12
|
+
require 'net/ssh'
|
13
|
+
require 'net/ssh/gateway'
|
14
|
+
require 'yaml'
|
15
|
+
module Moranis
|
16
|
+
|
17
|
+
class RemoteRun
|
18
|
+
|
19
|
+
attr_accessor :hosts, :gateway_address, :gateway_user, :commands
|
20
|
+
|
21
|
+
def initialize(hosts,user, options={})
|
22
|
+
@hosts = [hosts].flatten #accept a single host or an array of hosts
|
23
|
+
@gateway_address = options[:gateway_address]
|
24
|
+
@gateway_user = options[:gateway_user]
|
25
|
+
@user = user
|
26
|
+
@commands = []
|
27
|
+
end
|
28
|
+
|
29
|
+
#expects the config file to be formatted as yaml as follows
|
30
|
+
#environment:
|
31
|
+
# group:
|
32
|
+
# user: username
|
33
|
+
# host: hostname
|
34
|
+
def self.initialize_from_config(config_file,environment,group,options={})
|
35
|
+
config_file = YAML.load_file(config_file)
|
36
|
+
env_config = config_file[environment][group]
|
37
|
+
options[:gateway_address] ||= env_config["gateway"]["host"] unless env_config["gateway"].nil?
|
38
|
+
options[:gateway_user] ||= env_config["gateway"]["user"] unless env_config["gateway"].nil?
|
39
|
+
new(env_config["host"],env_config["user"],options)
|
40
|
+
end
|
41
|
+
|
42
|
+
#gateway wrapper
|
43
|
+
def gateway_wrapper
|
44
|
+
@gateway_host ||= if(@gateway_addr && @gateway_user)
|
45
|
+
Net::SSH::Gateway.new(@gateway_addr, @gateway_user, {:forward_agent => true})
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def ssh_wrapper(host,command)
|
52
|
+
output = ""
|
53
|
+
Net::SSH.start(host, @user) do |ssh|
|
54
|
+
ssh.exec(command) do |channel,stream,data|
|
55
|
+
output = data
|
56
|
+
end
|
57
|
+
end
|
58
|
+
output
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def execute
|
63
|
+
run_me = @commands.join(" && ")
|
64
|
+
output = []
|
65
|
+
|
66
|
+
if @gateway_host
|
67
|
+
gateway_wrapper.ssh(host, @user, {:forward_agent => true}) do |gateway|
|
68
|
+
@hosts.uniq.each do |h|
|
69
|
+
gateway.ssh(host, options[:gateway_user], {:forward_agent => true}) do |ssh|
|
70
|
+
ssh.exec(run_me) do |channel,stream,data|
|
71
|
+
output << data
|
72
|
+
puts data
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
else
|
78
|
+
@hosts.each{ |h| output << ssh_wrapper(h,run_me) }
|
79
|
+
end
|
80
|
+
|
81
|
+
output.flatten
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/moranis.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "moranis/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "moranis"
|
7
|
+
s.version = Moranis::VERSION
|
8
|
+
s.authors = ["Bill Chapman"]
|
9
|
+
s.email = ["byllc@overnothing.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{ SSH Key management for teams}
|
12
|
+
s.description = %q{ Standalone centralized ssh key management for teams }
|
13
|
+
|
14
|
+
s.rubyforge_project = "moranis"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
end
|
24
|
+
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: moranis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.5'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bill Chapman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-29 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! ' Standalone centralized ssh key management for teams '
|
15
|
+
email:
|
16
|
+
- byllc@overnothing.com
|
17
|
+
executables:
|
18
|
+
- key_master
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- bin/key_master
|
27
|
+
- lib/moranis.rb
|
28
|
+
- lib/moranis/key_master.rb
|
29
|
+
- lib/moranis/remote_run.rb
|
30
|
+
- lib/moranis/version.rb
|
31
|
+
- moranis.gemspec
|
32
|
+
homepage: ''
|
33
|
+
licenses: []
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project: moranis
|
52
|
+
rubygems_version: 1.8.10
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: SSH Key management for teams
|
56
|
+
test_files: []
|