zool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Pascal Friederich
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ Zool
2
+ =================
3
+ zool (which is named after Zuul, "The Gatekeeper", from the movie [Ghostbusters](http://en.wikipedia.org/wiki/Ghostbusters)) is a gem to manage authorized_keys files on a set of n servers.
4
+ It comes with a command-line client named zool which gives you access to the common tasks.
5
+
6
+ The command-line client
7
+ -----------------------
8
+
9
+ the command-line client currently supports 3 commands:
10
+
11
+ * fetch<br>
12
+ fetches the authorized_keys files from a file (defaults to /etc/hosts) or a list of hosts (see zool -h for more info), splits them up, removes duplicates and saves them to a .pub file in the keys (will be configurable... later...) directory.
13
+ It tries to generate the name of the keyfile by parsing the key for a someuser@somehost value at the end of the key. It only uses the someuser value to generate the keyfile name. That may become configurable later
14
+ You can specify a user / password for the fetch and setup tasks. See zool -h for details.
15
+ * setup<br>
16
+ this task creates the keys directory, fetches the keys and naively creates a simple version of a zool.conf. That will experience some overhaul for sure because it is only capable to create server directives for every server and isn't smart enough to group keys.
17
+ You can specify a user / password for the fetch and setup tasks. See zool -h for details.
18
+ * apply<br>
19
+ reads the zool.conf and distributes the keys to the servers specified in the configuration file. <br>
20
+
21
+ The zool.conf
22
+ ---------------
23
+
24
+ The zool.conf describes which keys should be deployed to which servers. It supports group, role and server directives.
25
+
26
+ [group devs]
27
+ members = peter, paul, danny
28
+
29
+ [group sysadmins]
30
+ members = tony, frank
31
+
32
+ [role app]
33
+ servers = 10.12.11.1, 10.12.11.2
34
+ keys = &devs, tony
35
+ user : my_deploy_user
36
+ password : mypassword
37
+
38
+ [server 10.11.1.4]
39
+ keys = &sysadmins
40
+ user = adminuser
41
+
42
+ The members are specified as the name of the keyfile containing the key, without the succeeding .pub extension.
43
+ A _group_ groups several keys, a _role_ groups several _servers_. A server, well, is a single server. (*Note*: you can have servers in several groups and even in an additional server directive at once)
44
+ Roles and servers can have multiple _keys_. The keys can be supplied like in the _group_ directive or if you want to reference to a groups keys, by prepending a _&_ (if you would want to reference the group _devs_ you would use _&devs_).
45
+ You can specify the user/password to use to connect to servers / roles.
46
+
47
+ *NOTE* Currently the first appearance of a server in the key file sets its user/password. So it is not possible to have multiple key configurations with a different user for a single server. That might change soon!
48
+
49
+ Security?
50
+ ----------
51
+ When zool creates a authorized_keys file on a server, it always creates a backup of the existing one (it uses `authorized_keys_timestamp` as the backups filename).
52
+ It also opens a backup connection to the server before uploading the keyfiles and tries to open another one after uploading them. If it fails to open another conncetion it uses the backup connection to restore the original keyfile.
53
+ See how it looks like if that happens:
54
+
55
+ *In the logfile*
56
+
57
+ INFO -- : Fetching key from 13.11.2.200
58
+ INFO -- : Trying to connect to 13.11.2.200 to see if I still have access
59
+ WARN -- : !!!!!! Could not login to server after upload operation! Rolling back !!!!!!
60
+ INFO -- : Trying to connect to 13.11.2.200 to see if I still have access
61
+ INFO -- : Backup channel connection succeeded. Assuming everything went fine!
62
+
63
+ *At the command-line*
64
+
65
+ NOW pray to the gods...
66
+ Going to deploy to 13.11.2.200
67
+ Uploading... [FAIL]
68
+ Could not connect to a Server after updating the authorized_keys file. Tried to roll back!
69
+ Error after uploading the keyfile to 13.11.2.200
70
+
71
+ Known issues
72
+ ------------
73
+
74
+ __Bugs / Issues__
75
+
76
+ * numbering of "similar" keys is only done when generating the key files. when writing the config files it uses the unnumbered version all the time
77
+ * tests on the fallback mechanism are not present
78
+
79
+ __Feature Todos__
80
+
81
+ * generating the config from a serverpool / hostfile is pretty dump at the moment. is doesn't use the groups and roles directives, instead stupidly adds server directives with the appropriate keys. That could be made smarter...
82
+ * if keys are in subfolders, the subfolders could automatically act as usable groups, with the folder name as reference
83
+
84
+ __DONE__
85
+
86
+ * allow customizing the user for server/role directives
87
+
88
+ Running the tests
89
+ =================
90
+
91
+ To run the cucumber features you need to have an ssh server running on your machine and your own public key in your authorized_keys file.
92
+ The tests use your authorized_keys file only to login to _localhost_ and fake authorized_keys and key files for testing.
93
+
94
+ Copyright
95
+ ---------
96
+ Copyright (c) 2010 Pascal Friederich. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'spec/rake/spectask'
2
+ require 'cucumber/rake/task'
3
+
4
+ Cucumber::Rake::Task.new do |t|
5
+ t.cucumber_opts = "--format progress"
6
+ end
7
+
8
+ task :default do
9
+ %w(spec cucumber).each do |task|
10
+ Rake::Task[task].invoke
11
+ end
12
+ end
13
+
14
+ desc "Run all specs"
15
+ Spec::Rake::SpecTask.new('spec') do |t|
16
+ t.spec_files = FileList['spec/**/*_spec.rb']
17
+ end
18
+
19
+ begin
20
+ require 'jeweler'
21
+ Jeweler::Tasks.new do |s|
22
+ s.name = "zool"
23
+ s.summary = "Library and command-line client to manage authorized_keys files"
24
+ s.description = "Zool allows you to manage authorized_keys files on servers. It comes with a command-line client 'zool'. The configuration can be done in a pyconfig/gitosis like configuration file. See README.md for further details"
25
+ s.email = "paukul@gmail.com"
26
+ s.homepage = "http://github.com/paukul/zool"
27
+ s.authors = ["Pascal Friederich"]
28
+ s.version = ["0.1.0"]
29
+ s.files.exclude 'vendor', 'spec', 'features', '.gitignore', 'Gemfile'
30
+ s.test_files.include 'features/**/*'
31
+ s.test_files.exclude 'features/tmp'
32
+ s.add_dependency 'net-scp', '>=1.0.2'
33
+ s.add_dependency 'net-ssh', '>=2.0.17'
34
+ s.add_dependency 'net-scp', '>=1.0.2'
35
+ s.add_dependency 'net-ssh', '>=2.0.17'
36
+ s.add_dependency 'treetop', '>=1.4.3'
37
+ s.add_dependency 'polyglot', '>=0.2.9'
38
+ s.add_dependency 'highline', '>=1.5.1'
39
+
40
+ s.add_development_dependency 'builder', '>=2.1.2'
41
+ s.add_development_dependency 'columnize', '>=0.3.1'
42
+ s.add_development_dependency 'cucumber', '>=0.5.3'
43
+ s.add_development_dependency 'diff-lcs', '>=1.1.2'
44
+ s.add_development_dependency 'fakefs', '>=0.2.1'
45
+ s.add_development_dependency 'json_pure', '>=1.2.0'
46
+ s.add_development_dependency 'linecache', '>=0.43'
47
+ s.add_development_dependency 'rake', '>=0.8.7'
48
+ s.add_development_dependency 'rspec', '>=1.2.9'
49
+ s.add_development_dependency 'ruby-debug', '>=0.10.3'
50
+ s.add_development_dependency 'ruby-debug-base', '>=0.10.3'
51
+ s.add_development_dependency 'term-ansicolor', '>=1.0.4'
52
+
53
+ end
54
+ Jeweler::GemcutterTasks.new
55
+ rescue LoadError
56
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
57
+ end
data/Readme.md ADDED
@@ -0,0 +1,96 @@
1
+ Zool
2
+ =================
3
+ zool (which is named after Zuul, "The Gatekeeper", from the movie [Ghostbusters](http://en.wikipedia.org/wiki/Ghostbusters)) is a gem to manage authorized_keys files on a set of n servers.
4
+ It comes with a command-line client named zool which gives you access to the common tasks.
5
+
6
+ The command-line client
7
+ -----------------------
8
+
9
+ the command-line client currently supports 3 commands:
10
+
11
+ * fetch<br>
12
+ fetches the authorized_keys files from a file (defaults to /etc/hosts) or a list of hosts (see zool -h for more info), splits them up, removes duplicates and saves them to a .pub file in the keys (will be configurable... later...) directory.
13
+ It tries to generate the name of the keyfile by parsing the key for a someuser@somehost value at the end of the key. It only uses the someuser value to generate the keyfile name. That may become configurable later
14
+ You can specify a user / password for the fetch and setup tasks. See zool -h for details.
15
+ * setup<br>
16
+ this task creates the keys directory, fetches the keys and naively creates a simple version of a zool.conf. That will experience some overhaul for sure because it is only capable to create server directives for every server and isn't smart enough to group keys.
17
+ You can specify a user / password for the fetch and setup tasks. See zool -h for details.
18
+ * apply<br>
19
+ reads the zool.conf and distributes the keys to the servers specified in the configuration file. <br>
20
+
21
+ The zool.conf
22
+ ---------------
23
+
24
+ The zool.conf describes which keys should be deployed to which servers. It supports group, role and server directives.
25
+
26
+ [group devs]
27
+ members = peter, paul, danny
28
+
29
+ [group sysadmins]
30
+ members = tony, frank
31
+
32
+ [role app]
33
+ servers = 10.12.11.1, 10.12.11.2
34
+ keys = &devs, tony
35
+ user : my_deploy_user
36
+ password : mypassword
37
+
38
+ [server 10.11.1.4]
39
+ keys = &sysadmins
40
+ user = adminuser
41
+
42
+ The members are specified as the name of the keyfile containing the key, without the succeeding .pub extension.
43
+ A _group_ groups several keys, a _role_ groups several _servers_. A server, well, is a single server. (*Note*: you can have servers in several groups and even in an additional server directive at once)
44
+ Roles and servers can have multiple _keys_. The keys can be supplied like in the _group_ directive or if you want to reference to a groups keys, by prepending a _&_ (if you would want to reference the group _devs_ you would use _&devs_).
45
+ You can specify the user/password to use to connect to servers / roles.
46
+
47
+ *NOTE* Currently the first appearance of a server in the key file sets its user/password. So it is not possible to have multiple key configurations with a different user for a single server. That might change soon!
48
+
49
+ Security?
50
+ ----------
51
+ When zool creates a authorized_keys file on a server, it always creates a backup of the existing one (it uses `authorized_keys_timestamp` as the backups filename).
52
+ It also opens a backup connection to the server before uploading the keyfiles and tries to open another one after uploading them. If it fails to open another conncetion it uses the backup connection to restore the original keyfile.
53
+ See how it looks like if that happens:
54
+
55
+ *In the logfile*
56
+
57
+ INFO -- : Fetching key from 13.11.2.200
58
+ INFO -- : Trying to connect to 13.11.2.200 to see if I still have access
59
+ WARN -- : !!!!!! Could not login to server after upload operation! Rolling back !!!!!!
60
+ INFO -- : Trying to connect to 13.11.2.200 to see if I still have access
61
+ INFO -- : Backup channel connection succeeded. Assuming everything went fine!
62
+
63
+ *At the command-line*
64
+
65
+ NOW pray to the gods...
66
+ Going to deploy to 13.11.2.200
67
+ Uploading... [FAIL]
68
+ Could not connect to a Server after updating the authorized_keys file. Tried to roll back!
69
+ Error after uploading the keyfile to 13.11.2.200
70
+
71
+ Known issues
72
+ ------------
73
+
74
+ __Bugs / Issues__
75
+
76
+ * numbering of "similar" keys is only done when generating the key files. when writing the config files it uses the unnumbered version all the time
77
+ * tests on the fallback mechanism are not present
78
+
79
+ __Feature Todos__
80
+
81
+ * generating the config from a serverpool / hostfile is pretty dump at the moment. is doesn't use the groups and roles directives, instead stupidly adds server directives with the appropriate keys. That could be made smarter...
82
+ * if keys are in subfolders, the subfolders could automatically act as usable groups, with the folder name as reference
83
+
84
+ __DONE__
85
+
86
+ * allow customizing the user for server/role directives
87
+
88
+ Running the tests
89
+ =================
90
+
91
+ To run the cucumber features you need to have an ssh server running on your machine and your own public key in your authorized_keys file.
92
+ The tests use your authorized_keys file only to login to _localhost_ and fake authorized_keys and key files for testing.
93
+
94
+ Copyright
95
+ ---------
96
+ Copyright (c) 2010 Pascal Friederich. See LICENSE for details.
data/bin/zool ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'lib/zool'
4
+ require 'optparse'
5
+ require 'highline'
6
+
7
+ class ZoolClient
8
+ VALID_COMMANDS = %w(fetch setup fake apply)
9
+
10
+ def initialize
11
+ @hl = HighLine.new
12
+ @options = {
13
+ :keys_dir => 'keys',
14
+ :hosts_file => '/etc/hosts',
15
+ :hosts => [],
16
+ :config => 'zool.conf',
17
+ :user => 'root',
18
+ :prompt_for_password => false
19
+ }
20
+
21
+ parse_opts
22
+ command = ARGV[0]
23
+ abort(@parser.to_s) unless VALID_COMMANDS.include?(command)
24
+ self.send(command)
25
+ end
26
+
27
+ def fetch
28
+ pool = read_hostsfile
29
+ dump_keyfiles(pool)
30
+ end
31
+
32
+ def fake
33
+ puts @options.inspect
34
+ end
35
+
36
+ def apply
37
+ begin
38
+ configuration = Zool::Configuration.parse(File.read(@options[:config]))
39
+ rescue Zool::Configuration::ParseError => e
40
+ exit_with_help("Error parsing the configuration file. Error was: #{e.message}")
41
+ end
42
+ $stdout.puts "NOW pray to the gods... "
43
+ $stdout.puts "Going to deploy to #{configuration.servers.keys.join(",")}"
44
+ $stdout.print "Uploading..."
45
+ begin
46
+ configuration.upload_keys
47
+ rescue Net::SSH::AuthenticationFailed => e
48
+ $stdout.puts " [FAIL]"
49
+ $stdout.puts "Error connecting to a server with the user '#{e.message}'"
50
+ exit 1
51
+ rescue Zool::Server::ConnectionVerificationExecption => e
52
+ $stdout.puts " [FAIL]"
53
+ $stdout.puts "Could not connect to a Server after updating the authorized_keys file. Tried to roll back!"
54
+ $stdout.puts e.message
55
+ exit 1
56
+ end
57
+ $stdout.puts " [DONE]"
58
+ $stdout.puts "Go check if everything is fine!"
59
+ end
60
+
61
+ def setup
62
+ unless File.directory?('keys')
63
+ $stdout.print "Creating keys directory..."
64
+ FileUtils.mkdir('keys')
65
+ $stdout.puts " [DONE]"
66
+ end
67
+
68
+ pool = read_hostsfile
69
+ $stdout.print "Fetching keys from servers and writing config to zool.conf..."
70
+ config = Zool::Configuration.build(pool)
71
+ File.open('zool.conf', 'w+') do |file|
72
+ file.puts config
73
+ end
74
+ $stdout.puts " [DONE]"
75
+ dump_keyfiles(pool)
76
+ end
77
+
78
+ def method_missing(method, *args)
79
+ exit_with_help("That command isn't supportet!")
80
+ end
81
+
82
+ private
83
+ def dump_keyfiles(pool)
84
+ $stdout.print "Writing keyfiles..."
85
+ pool.dump_keyfiles
86
+ $stdout.puts " [DONE]"
87
+ end
88
+
89
+ def parse_opts
90
+ @parser = OptionParser.new do |o|
91
+ o.banner = "Usage: zool [options] command"
92
+ o.separator("")
93
+ o.separator("Valid commands: #{VALID_COMMANDS.join(', ')}")
94
+ o.separator("")
95
+ o.separator("Input Options:")
96
+
97
+ o.on("--hosts host1,host2,host3", Array, "A comma separated list of hostnames to use (when using fetch or setup)") do |hosts|
98
+ @options[:hosts] = hosts
99
+ end
100
+
101
+ o.on("--hostfile FILENAME", String, "The file to take the hosts from. Defaults to /etc/hosts (is ignored if the --hosts option is provided)") do |hostefile|
102
+ @options[:hosts_file] = hostefile
103
+ end
104
+
105
+ o.on("-u USER", "--user USER", String, "The username to use to connect to the servers (defaults to root)") do |user|
106
+ @options[:user] = user
107
+ end
108
+
109
+ o.on("-p", "Prompt for the users password") do |p|
110
+ @options[:prompt_for_password] = p
111
+ end
112
+
113
+ o.on("-h", "--help", "This help screen" ) do
114
+ puts o
115
+ abort
116
+ end
117
+ end
118
+
119
+ @parser.parse!(ARGV)
120
+ end
121
+
122
+ def read_hostsfile
123
+ if @options[:hosts] == []
124
+ hosts_source = "hostfile #{@options[:hosts_file]}"
125
+ begin
126
+ hostfile = File.read(@options[:hosts_file])
127
+ rescue Errno::ENOENT
128
+ exit_with_help("File #{@options[:hosts_file]} not found or invalid")
129
+ end
130
+ else
131
+ hosts_source = "supplied list #{@options[:hosts].join(', ')}"
132
+ hostfile = @options[:hosts].join(" dummyname\n")
133
+ hostfile << " dummyname\n"
134
+ end
135
+
136
+ password = if @options[:prompt_for_password]
137
+ @hl.ask("Enter your password: ") { |q| q.echo = false }
138
+ else
139
+ ''
140
+ end
141
+
142
+ $stdout.print "Reading hosts from #{hosts_source}..."
143
+
144
+ pool = Zool::ServerPool.from_hostfile(hostfile, :user => @options[:user], :password => password)
145
+ $stdout.puts " [DONE]"
146
+ exit_with_help("no Valid servers found") if pool.servers == []
147
+ pool
148
+ end
149
+
150
+ def exit_with_help(msg)
151
+ reason = "\n #{msg}\n\n#{@parser}\n"
152
+ abort(reason)
153
+ end
154
+ end
155
+
156
+ ZoolClient.new
@@ -0,0 +1,67 @@
1
+ Feature: Setting up server/key configurations in a config file
2
+ In order to easily manage which keys should be deployed to which server
3
+ As a whatever i have no idea srsly
4
+ I want to write a config file which gets parsed correctly to a working server/serverpool/key setup
5
+
6
+ Scenario: a config file with groups, servers and roles
7
+ Given the server "13.9.6.1"
8
+ And the server "13.9.6.2"
9
+ And the server "13.9.6.3"
10
+ And the local keyfiles
11
+ | name | key |
12
+ | key1 | ssh-rsa key1== foobar |
13
+ | key2 | ssh-rsa key2== something |
14
+ | key3 | ssh-rsa key3== snafu |
15
+ | key4 | ssh-dsa key4== bazz |
16
+ And the config
17
+ """
18
+ [group devs]
19
+ members = key1, key2, key3
20
+
21
+ [role app]
22
+ servers = 13.9.6.1, 13.9.6.2
23
+ keys = &devs, key4
24
+
25
+ [server 13.9.6.3]
26
+ keys = key2
27
+ """
28
+ When I parse the config and run the upload_keys command
29
+ Then the following keys should be on the servers
30
+ | server | key |
31
+ | 13.9.6.1 | ssh-rsa key1== foobar |
32
+ | 13.9.6.1 | ssh-rsa key2== something |
33
+ | 13.9.6.1 | ssh-rsa key3== snafu |
34
+ | 13.9.6.1 | ssh-dsa key4== bazz |
35
+ | 13.9.6.2 | ssh-rsa key1== foobar |
36
+ | 13.9.6.2 | ssh-rsa key2== something |
37
+ | 13.9.6.2 | ssh-rsa key3== snafu |
38
+ | 13.9.6.2 | ssh-dsa key4== bazz |
39
+ | 13.9.6.3 | ssh-rsa key2== something |
40
+
41
+ Scenario: creating a new config file from a serverpool
42
+ Given the following hosts
43
+ """
44
+ 13.9.1.41 preview_server
45
+ 13.9.1.42 edge_server
46
+ 10.53.1.41 production_server
47
+ """
48
+ And the following keys are on the servers
49
+ | server | key |
50
+ | 13.9.1.41 | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
51
+ | 13.9.1.41 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
52
+ | 13.9.1.41 | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
53
+ | 13.9.1.42 | ssh-rsa key3== lee.hambley@xing.com |
54
+ | 10.53.1.41 | ssh-rsa key5== pascal.friederich@nb-pfriederich.local |
55
+ When I build the config from scratch
56
+ Then I should have the following config
57
+ """
58
+ [server 13.9.1.41]
59
+ keys = adem_deliceoglu, abel_fernandez, christian_kvalheim
60
+
61
+ [server 13.9.1.42]
62
+ keys = lee_hambley
63
+
64
+ [server 10.53.1.41]
65
+ keys = pascal_friederich
66
+
67
+ """