zool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,72 @@
1
+ Feature: Fetching SSH Keys
2
+ In order to see a list of all used ssh servers
3
+ As a xing techie
4
+ I want to be able to fetch all authorized keys from remote servers
5
+
6
+ Scenario: Fetching all keys from a server
7
+ Given the following keys are on the servers
8
+ | server | key |
9
+ | preview_server | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
10
+ When I run the fetch_keys command for the server "preview_server"
11
+ Then It should fetch the following keys
12
+ | key |
13
+ | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
14
+
15
+ Scenario: Fetching all keys from all servers
16
+ Given the following hosts
17
+ """
18
+ 13.9.1.41 preview_server
19
+ 13.9.1.42 edge_server
20
+ 10.53.1.41 production_server
21
+ """
22
+ And the following keys are on the servers
23
+ | server | key |
24
+ | 13.9.1.41 | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
25
+ | 13.9.1.41 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
26
+ | 13.9.1.41 | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
27
+ | 13.9.1.42 | ssh-rsa key3== lee.hambley@xing.com |
28
+ | 10.53.1.41 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
29
+ | 10.53.1.41 | ssh-rsa key5== pascal.friederich@nb-pfriederich.local |
30
+ When I run the fetch_keys command
31
+ Then It should fetch the following keys
32
+ | key |
33
+ | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
34
+ | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
35
+ | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
36
+ | ssh-rsa key3== lee.hambley@xing.com |
37
+ | ssh-rsa key5== pascal.friederich@nb-pfriederich.local |
38
+
39
+ Scenario: Dumping a single servers keys to files
40
+ Given the following keys have been fetched
41
+ | key |
42
+ | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
43
+ | ssh-rsa key4== abelfernandez@nb-afernandez.local |
44
+ | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
45
+ | ssh-rsa key3== lee.hambley@xing.com |
46
+ | ssh-rsa key5== lee.hambley@private |
47
+ When I run the dump_keyfiles command
48
+ Then It should generate the following files
49
+ | name | key |
50
+ | abelfernandez.pub | ssh-rsa key4== abelfernandez@nb-afernandez.local |
51
+ | adem_deliceoglu.pub | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
52
+ | christian_kvalheim.pub | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
53
+ | lee_hambley.pub | ssh-rsa key3== lee.hambley@xing.com |
54
+ | lee_hambley_2.pub | ssh-rsa key5== lee.hambley@private |
55
+
56
+ Scenario: Dumping all servers keys to files
57
+ Given the following hosts
58
+ """
59
+ 13.9.1.41 preview_server
60
+ 10.53.1.42 production_server
61
+ """
62
+ And the following keys are on the servers
63
+ | server | key |
64
+ | 13.9.1.41 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
65
+ | 13.9.1.41 | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
66
+ | 10.53.1.42 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
67
+ When I run the fetch_keys command
68
+ And I run the dump_keyfiles command
69
+ Then It should generate the following files
70
+ | name | key |
71
+ | abel_fernandez.pub | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
72
+ | christian_kvalheim.pub | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
@@ -0,0 +1,136 @@
1
+ require 'fileutils'
2
+ require 'ruby-debug'
3
+ Debugger.start
4
+
5
+ #########
6
+ # GIVEN
7
+ #########
8
+
9
+ Given /^the local keyfiles$/ do |table|
10
+ writer = Zool::KeyfileWriter.new
11
+ table.hashes.each {|keyfile| writer.write(keyfile['key'], keyfile['name'])}
12
+ end
13
+
14
+ Given /^the config$/ do |string|
15
+ @config = string
16
+ end
17
+
18
+ Given /^the following hosts$/ do |string|
19
+ hosts = StringIO.new(string)
20
+ @zool = Zool::ServerPool.from_hostfile(hosts)
21
+ end
22
+
23
+ Given /^the following keys are on the servers$/ do |table|
24
+ keys = server_with_keys_from_table(table)
25
+
26
+ keys.each do |host, keys|
27
+ File.open(fake_server_dir!(host) + '/authorized_keys', 'w+') do |file|
28
+ file.write(keys.join("\n"))
29
+ end
30
+ end
31
+ end
32
+
33
+ Given /^the following keys have been fetched$/ do |table|
34
+ Given 'the server "localhost"'
35
+ @zool.keys = table.rows.flatten
36
+ end
37
+
38
+ Given /^the server "([^\"]*)" without a key file$/ do |servername|
39
+ Given "the server \"localhost\""
40
+ end
41
+
42
+ Given /^the server "([^\"]*)"$/ do |servername|
43
+ @zool = Zool::Server.new(servername)
44
+ end
45
+
46
+ #########
47
+ # WHEN
48
+ #########
49
+
50
+ When /^I parse the config and run the upload_keys command$/ do
51
+ @zool = Zool::Configuration.parse(@config)
52
+ @zool.upload_keys
53
+ end
54
+
55
+ When /^I build the config from scratch$/ do
56
+ @generated_config = Zool::Configuration.build(@zool)
57
+ end
58
+
59
+ When /^I run the fetch_keys command for the server "([^\"]*)"$/ do |hostname|
60
+ @zool = Zool::Server.new(hostname)
61
+ @zool.fetch_keys
62
+ end
63
+
64
+ When /^I add the key "([^\"]*)"$/ do |key|
65
+ @zool.keys << key
66
+ end
67
+
68
+ When /^I run the (.*) command$/ do |command|
69
+ @zool.send(command)
70
+ end
71
+
72
+ When /^I upload the keys to the server "([^\"]*)"$/ do |servername, table|
73
+ @zool = Zool::Server.new(servername)
74
+ @zool.keys = table.rows
75
+ @zool.upload_keys
76
+ end
77
+
78
+ #########
79
+ # THEN
80
+ #########
81
+
82
+ Then /^It should fetch the following keys$/ do |table|
83
+ actual_keys = [['key']] | @zool.keys.map {|key| [key] }
84
+ table.diff!(actual_keys)
85
+ end
86
+
87
+ Then /^It should generate the following files$/ do |keyfiles|
88
+ actual_keyfiles = [['name', 'key']]
89
+ Dir[TEST_TMP_PATH + '/keys/*.pub'].each do |keyfile|
90
+ File.open(keyfile) do |file|
91
+ actual_keyfiles << [File.basename(keyfile), file.read.strip]
92
+ end
93
+ end
94
+
95
+ keyfiles.diff!(actual_keyfiles)
96
+ end
97
+
98
+ Then /^the server "([^\"]*)" should have the authorized_keys file with the content$/ do |server, expected_content|
99
+ File.read(TEST_TMP_PATH + "/servers/#{server}/authorized_keys").should == expected_content
100
+ end
101
+
102
+ Then /^the following keys should be on the servers$/ do |table|
103
+ actual_keys_from_server = [['server', 'key']]
104
+ server_with_keys_from_table(table).each do |server, keys|
105
+ entries = File.read(TEST_TMP_PATH + "/servers/#{server}/authorized_keys").split("\n").map {|key| [server, key]}
106
+ actual_keys_from_server += entries
107
+ end
108
+ table.diff!(actual_keys_from_server)
109
+ end
110
+
111
+ Then /^I should have the following config$/ do |string|
112
+ @generated_config.should == string
113
+ end
114
+
115
+ #########
116
+ # HELPER
117
+ #########
118
+ def fake_server_dir!(server)
119
+ path = fake_server_dir(server)
120
+ return path if File.directory?(path)
121
+ FileUtils.mkdir_p path
122
+ path
123
+ end
124
+
125
+ def fake_server_dir(host)
126
+ TEST_TMP_PATH + "/servers/#{host}"
127
+ end
128
+
129
+ def server_with_keys_from_table(table)
130
+ keys = {}
131
+ table.hashes.each do |values|
132
+ keys[values["server"]] ||= []
133
+ keys[values["server"]] << values["key"]
134
+ end
135
+ keys
136
+ end
@@ -0,0 +1,62 @@
1
+ Feature: Store ssh keys on servers
2
+ In order to simplify uploading of keys to servers
3
+ As a xing techie
4
+ I want to be able to compose key lists and upload them to servers
5
+
6
+ Scenario: uploading keys to a server
7
+ Given the server "preview_server" without a key file
8
+ When I upload the keys to the server "preview_server"
9
+ | key |
10
+ | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
11
+ | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
12
+ | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
13
+ | ssh-rsa key3== lee.hambley@xing.com |
14
+ | ssh-rsa key5== pascal.friederich@nb-pfriederich.local |
15
+
16
+ Then the server "preview_server" should have the authorized_keys file with the content
17
+ """
18
+ ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO
19
+ ssh-rsa key4== abel.fernandez@nb-afernandez.local
20
+ ssh-dss key2== christian.kvalheim@nb-ckvalheim.local
21
+ ssh-rsa key3== lee.hambley@xing.com
22
+ ssh-rsa key5== pascal.friederich@nb-pfriederich.local
23
+ """
24
+
25
+ Scenario: adding a single key to a servers keyfile
26
+ Given the server "13.9.1.41"
27
+ And the following keys are on the servers
28
+ | server | key |
29
+ | 13.9.1.41 | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
30
+ | 13.9.1.41 | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
31
+ When I add the key "ssh-rsa key4== abel.fernandez@nb-afernandez.local"
32
+ And I run the upload_keys command
33
+ Then the server "13.9.1.41" should have the authorized_keys file with the content
34
+ """
35
+ ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO
36
+ ssh-dss key2== christian.kvalheim@nb-ckvalheim.local
37
+ ssh-rsa key4== abel.fernandez@nb-afernandez.local
38
+ """
39
+
40
+ Scenario: adding a single key to a serverpools keyfiles
41
+ Given the following hosts
42
+ """
43
+ 13.9.1.41 preview_server
44
+ 13.9.1.42 edge_server
45
+ """
46
+ And the following keys are on the servers
47
+ | server | key |
48
+ | 13.9.1.41 | ssh-rsa key1== some.key@somehost |
49
+ | 13.9.1.41 | ssh-rsa key4== anotherkey@ahost.local |
50
+ | 13.9.1.41 | ssh-dss key2== thiskey@thishost.local |
51
+ | 13.9.1.42 | ssh-rsa key3== snafu@bar.com |
52
+ When I add the key "ssh-rsa key5== additionalkey@host"
53
+ And I run the upload_keys command
54
+ Then the following keys should be on the servers
55
+ | server | key |
56
+ | 13.9.1.41 | ssh-rsa key1== some.key@somehost |
57
+ | 13.9.1.41 | ssh-rsa key4== anotherkey@ahost.local |
58
+ | 13.9.1.41 | ssh-dss key2== thiskey@thishost.local |
59
+ | 13.9.1.41 | ssh-rsa key5== additionalkey@host |
60
+ | 13.9.1.42 | ssh-rsa key3== snafu@bar.com |
61
+ | 13.9.1.42 | ssh-rsa key5== additionalkey@host |
62
+
@@ -0,0 +1,39 @@
1
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../..'))
2
+
3
+ require 'spec'
4
+ require 'lib/zool'
5
+
6
+ TEST_TMP_PATH = File.expand_path(File.dirname(__FILE__) + "/../tmp")
7
+
8
+ class Zool::Server
9
+ def initialize(host, options = {})
10
+ @options = {
11
+ :password => '',
12
+ :user => `whoami`.chomp
13
+ }
14
+ @hostname = 'localhost'
15
+ @fake_hostname = host
16
+
17
+ temp_server_path = TEST_TMP_PATH + "/servers/#{host}"
18
+ @keyfile_location = temp_server_path + '/authorized_keys'
19
+ FileUtils.mkdir_p temp_server_path unless File.directory? temp_server_path
20
+ end
21
+
22
+ def hostname
23
+ @fake_hostname
24
+ end
25
+ end
26
+
27
+ class Zool::KeyfileWriter
28
+ def initialize(out_directory = 'keys')
29
+ @out_directory = TEST_TMP_PATH + "/#{out_directory}"
30
+ FileUtils.mkdir_p @out_directory unless File.directory? @out_directory
31
+ end
32
+ end
33
+
34
+ Before do
35
+ FileUtils.rm_r TEST_TMP_PATH if File.directory? TEST_TMP_PATH
36
+ @zool = nil
37
+ @servers = nil
38
+ end
39
+
@@ -0,0 +1,85 @@
1
+ grammar PyConfig
2
+ rule main
3
+ section*
4
+ {
5
+ def build
6
+ @config = {}
7
+ elements.each do |section|
8
+ @config[section.name] = section.build
9
+ end
10
+ @config
11
+ end
12
+ }
13
+ end
14
+
15
+ rule section
16
+ space* '[' sectionname ']' eol
17
+ pairs:(sectionvalue eol?)+
18
+ white*
19
+ {
20
+ def name
21
+ sectionname.text_value
22
+ end
23
+
24
+ def build
25
+ return pairs.elements.inject({}) do |pairs, elt|
26
+ pairs.merge!(elt.sectionvalue.build)
27
+ pairs
28
+ end
29
+ end
30
+ }
31
+ end
32
+
33
+ rule sectionname
34
+ (string / space / '.')+
35
+ end
36
+
37
+ rule sectionvalue
38
+ space* key separator value
39
+ {
40
+ def build
41
+ separator.build(key, value)
42
+ end
43
+ }
44
+ end
45
+
46
+ rule separator
47
+ space* ':' space*
48
+ {
49
+ def build(key, value)
50
+ {key.text_value => value.text_value.strip}
51
+ end
52
+ }
53
+ /
54
+ space* '=' space*
55
+ {
56
+ def build(key, value)
57
+ {key.text_value => value.text_value.split(',').map {|val| val.strip }}
58
+ end
59
+ }
60
+ end
61
+
62
+ rule key
63
+ string
64
+ end
65
+
66
+ rule value
67
+ [^\n]*
68
+ end
69
+
70
+ rule string
71
+ [a-zA-Z_0-9]+
72
+ end
73
+
74
+ rule white
75
+ (space / eol)
76
+ end
77
+
78
+ rule space
79
+ [ \t]
80
+ end
81
+
82
+ rule eol
83
+ "\n" / ("\r" "\n"?)
84
+ end
85
+ end
data/lib/zool.rb ADDED
@@ -0,0 +1,24 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ begin
3
+ require File.expand_path(File.dirname(__FILE__) + '/../vendor/gems/environment')
4
+ rescue LoadError
5
+ # seems to be the gem version
6
+ end
7
+
8
+ require 'treetop'
9
+ require 'py_config_parser/py_config_parser'
10
+ require 'zool/server'
11
+ require 'zool/server_pool'
12
+ require 'zool/key_file_writer'
13
+ require 'zool/configuration'
14
+
15
+ module Zool
16
+ DEFAULT_LOGGER = begin
17
+ if defined?(Spec)
18
+ # we are in test environment
19
+ Logger.new('test.log')
20
+ else
21
+ Logger.new('zool.log')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,151 @@
1
+ module Zool
2
+ class Configuration
3
+ class ParseError < Exception; end
4
+ attr_reader :servers, :roles, :groups
5
+
6
+ def self.parse(configuration)
7
+ parser = PyConfigParser.new
8
+ if raw_config = parser.parse(configuration)
9
+ self.new(raw_config.build)
10
+ else
11
+ raise ParseError.new(parser.failure_reason)
12
+ end
13
+ end
14
+
15
+ def self.build(pool)
16
+ conf = ""
17
+ pool.servers.each do |server|
18
+ next if server.keys == []
19
+ conf << "\n" unless conf == ""
20
+ conf << "[server #{server.hostname}]\n"
21
+ keynames = server.keys.map {|key| KeyfileWriter.keyname_for_key(key)}
22
+ conf << " keys = #{keynames.join(', ')}\n"
23
+ end
24
+ conf
25
+ end
26
+
27
+ def initialize(raw_config)
28
+ @raw_config = raw_config
29
+ parse
30
+ end
31
+
32
+ def keys
33
+ @keys ||= read_keys
34
+ end
35
+
36
+ def upload_keys
37
+ @servers.each { |servername, server| server.upload_keys }
38
+ end
39
+
40
+ private
41
+ def parse
42
+ @roles = {}
43
+ @servers = {}
44
+ @groups = {}
45
+
46
+ parse_groups
47
+ parse_servers
48
+ parse_roles
49
+ end
50
+
51
+ def parse_groups
52
+ raw_groups.each do |raw_group|
53
+ @groups[raw_group[/^group\s(.*)/, 1]] = @raw_config[raw_group]['members']
54
+ end
55
+ end
56
+
57
+ def parse_servers
58
+ raw_servers.each do |raw_server|
59
+ server = server(raw_server[/^server\s(.*)/, 1], @raw_config[raw_server])
60
+ @raw_config[raw_server]['keys'].each do |key|
61
+ server.keys << fetch_key(key)
62
+ end
63
+ end
64
+ end
65
+
66
+ def raw_servers
67
+ raw(:server)
68
+ end
69
+
70
+ def raw_groups
71
+ raw(:group)
72
+ end
73
+
74
+ def parse_roles
75
+ raw_roles.each do |raw_role|
76
+ @roles[raw_role[/^role\s(.*)/, 1]] = server_pool(raw_role)
77
+ end
78
+ end
79
+
80
+ def raw_roles
81
+ raw(:role)
82
+ end
83
+
84
+ def raw(raw_type)
85
+ @raw_config.select {|k, v| k =~ /^#{raw_type}/}.map {|role_arrey| role_arrey[0]}
86
+ end
87
+
88
+ def server(hostname, raw_object)
89
+ return @servers[hostname] if @servers[hostname]
90
+ options = server_options_from_configuration(raw_object)
91
+
92
+ new_server = Server.new(hostname, options)
93
+ new_server.keys = []
94
+ @servers[hostname] = new_server
95
+ new_server
96
+ end
97
+
98
+ def server_options_from_configuration(raw_object)
99
+ user = raw_object['user']
100
+ password = raw_object['password']
101
+ options = {}
102
+ options.update({:user => user}) if user
103
+ options.update({:password => password}) if password
104
+ options
105
+ end
106
+
107
+ def read_keys
108
+ hash = {}
109
+ key_directory = KeyfileWriter.new.out_directory # FIXME: terrible and lazy hack!
110
+ keyfiles = Dir["#{key_directory}/*.pub"]
111
+ keyfiles.each do |keyfile|
112
+ keyname = File.basename(keyfile)[/(.*)\.pub/, 1]
113
+ hash[keyname] = File.read(keyfile).chomp
114
+ end
115
+ hash
116
+ end
117
+
118
+ def server_pool(raw_role)
119
+ pool = ServerPool.new()
120
+
121
+ @raw_config[raw_role]['servers'].each do |hostname|
122
+ pool << server(hostname, @raw_config[raw_role])
123
+ end
124
+
125
+ @raw_config[raw_role]['keys'].each do |key|
126
+ if key =~ /^&/
127
+ add_group_keys(key[1..-1], pool)
128
+ else
129
+ pool.keys << fetch_key(key)
130
+ end
131
+ end
132
+
133
+ pool
134
+ end
135
+
136
+ def fetch_key(key)
137
+ return keys[key] unless keys[key].nil?
138
+ raise ParseError.new("missing ssh key '#{key}'")
139
+ end
140
+
141
+ def add_group_keys(group, pool)
142
+ begin
143
+ @groups[group].each do |key|
144
+ pool.keys << fetch_key(key)
145
+ end
146
+ rescue NoMethodError => e
147
+ raise ParseError.new("missing referenced group '#{group}'")
148
+ end
149
+ end
150
+ end
151
+ end