moranis 0.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
6
+ config/*
7
+ config
8
+ Capify
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in moranis.gemspec
4
+ gemspec
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,6 @@
1
+ require_relative 'moranis/key_master'
2
+ require_relative 'moranis/remote_run'
3
+
4
+ module Moranis
5
+
6
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Moranis
2
+ VERSION = "0.5"
3
+ 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: []