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 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: []