keyble 0.1.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+
3
+ gem "net-scp"
4
+
5
+ group :development do
6
+ gem "bundler"
7
+ gem "jeweler"
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Scott Tadman
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,19 @@
1
+ # keyble
2
+
3
+ SSH keychain management for servers.
4
+
5
+ ## Usage
6
+
7
+ ```
8
+ keyble --import keyfile
9
+ keyble --import keyfile server [server [...]]
10
+ keyble --add user@host server [server [...]]
11
+ keyble --remove user@host server [server [...]]
12
+ keyble --list
13
+ keyble --list server [server [...]]
14
+ ```
15
+
16
+ ## Copyright
17
+
18
+ Copyright (c) 2012 Scott Tadman, The Working Group Inc.
19
+ See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ require 'rake'
15
+ require 'jeweler'
16
+
17
+ Jeweler::Tasks.new do |gem|
18
+ gem.name = "keyble"
19
+ gem.homepage = "http://github.com/twg/keyble"
20
+ gem.license = "MIT"
21
+ gem.summary = %Q{SSH Key Management}
22
+ gem.description = %Q{Unubtrusive SSH key management}
23
+ gem.email = "scott@twg.ca"
24
+ gem.authors = [ "Scott Tadman" ]
25
+ # Dependencies defined in Gemfile
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+
31
+ Rake::TestTask.new(:test) do |test|
32
+ test.libs << 'lib' << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+
37
+ task :default => :test
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/keyble ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.expand_path('../lib', File.dirname(__FILE__))
4
+
5
+ require 'keyble'
6
+ require 'optparse'
7
+ require 'net/scp'
8
+
9
+ method = :list
10
+ key = nil
11
+
12
+ parser = OptionParser.new do |parser|
13
+ parser.on("-c", "--cache=s") do |s|
14
+ Keyble.cache_file_path = s
15
+ end
16
+ parser.on("-a", "--add=s") do |s|
17
+ method = :add
18
+ key = s
19
+ end
20
+ parser.on("-r", "--remove=s") do |s|
21
+ method = :remove
22
+ key = s
23
+ end
24
+ parser.on("-i", "--import") do |s|
25
+ method = :import
26
+ end
27
+ parser.on("-l", "--list") do
28
+ method = :list
29
+ end
30
+ end
31
+
32
+ args = parser.parse(*ARGV)
33
+
34
+ case (method)
35
+ when :add
36
+ if (File.exist?(key))
37
+ imported = Keyble.keys_import(key)
38
+
39
+ if (args.any?)
40
+ Keyble.keys_add(imported, args)
41
+ else
42
+ STDERR.puts "No servers specified."
43
+ exit(-11)
44
+ end
45
+ elsif (args.any?)
46
+ if (entry = Keyble.cache[key])
47
+ Keyble.keys_add({ key => entry }, args)
48
+ else
49
+ STDERR.puts "Could not locate key for #{key}"
50
+ end
51
+ else
52
+ puts parser
53
+ exit(-1)
54
+ end
55
+ when :remove
56
+ if (File.exist?(key))
57
+ imported = Keyble.keys_import(key)
58
+
59
+ if (args.any?)
60
+ Keyble.keys_remove(imported, args)
61
+ else
62
+ STDERR.puts "No servers specified."
63
+ exit(-11)
64
+ end
65
+ elsif (args.any?)
66
+ Keyble.keys_remove([ key ], args)
67
+ else
68
+ puts parser
69
+ exit(-1)
70
+ end
71
+ when :import
72
+ if (args.any?)
73
+ imported = { }
74
+
75
+ servers = args.reject do |path|
76
+ if (File.exist?(path))
77
+ imported.merge!(Keyble.read(path))
78
+
79
+ true
80
+ else
81
+ false
82
+ end
83
+ end
84
+
85
+ if (servers.any?)
86
+ server_keys = Keyble.keys_get(servers)
87
+
88
+ server_keys.each do |server, keys|
89
+ imported.merge!(keys)
90
+ end
91
+ end
92
+
93
+ Keyble.cache_merge!(imported)
94
+ Keyble.cache_save!
95
+
96
+ Keyble.keys_display(imported)
97
+ else
98
+ STDERR.puts "No servers specified"
99
+ exit(-11)
100
+ end
101
+ when :list
102
+ if (args.any?)
103
+ Keyble.keys_get(args).each do |server, keys|
104
+ if (args.length > 1)
105
+ puts server
106
+ puts '-' * 78
107
+ end
108
+
109
+ Keyble.keys_display(keys)
110
+ end
111
+ else
112
+ if (Keyble.cache.any?)
113
+ Keyble.keys_display(Keyble.cache)
114
+ else
115
+ puts "No keys in #{Keyble.cache_file_path}"
116
+ end
117
+ end
118
+ end
data/keyble.gemspec ADDED
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "keyble"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Scott Tadman"]
12
+ s.date = "2013-02-27"
13
+ s.description = "Unubtrusive SSH key management"
14
+ s.email = "scott@twg.ca"
15
+ s.executables = ["keyble"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ "Gemfile",
23
+ "LICENSE.txt",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "bin/keyble",
28
+ "keyble.gemspec",
29
+ "lib/keyble.rb",
30
+ "test/helper.rb",
31
+ "test/test_keyble.rb"
32
+ ]
33
+ s.homepage = "http://github.com/twg/keyble"
34
+ s.licenses = ["MIT"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = "1.8.23"
37
+ s.summary = "SSH Key Management"
38
+
39
+ if s.respond_to? :specification_version then
40
+ s.specification_version = 3
41
+
42
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
+ s.add_runtime_dependency(%q<net-scp>, [">= 0"])
44
+ s.add_development_dependency(%q<bundler>, [">= 0"])
45
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
46
+ else
47
+ s.add_dependency(%q<net-scp>, [">= 0"])
48
+ s.add_dependency(%q<bundler>, [">= 0"])
49
+ s.add_dependency(%q<jeweler>, [">= 0"])
50
+ end
51
+ else
52
+ s.add_dependency(%q<net-scp>, [">= 0"])
53
+ s.add_dependency(%q<bundler>, [">= 0"])
54
+ s.add_dependency(%q<jeweler>, [">= 0"])
55
+ end
56
+ end
57
+
data/lib/keyble.rb ADDED
@@ -0,0 +1,133 @@
1
+ module Keyble
2
+ def parse(data)
3
+ result = { }
4
+
5
+ data.split(/\r?\n/).collect do |line|
6
+ type, key, comment = line.split(/\s+/)
7
+
8
+ if (comment and !comment.empty? and comment.match(/\S+\@\S+/))
9
+ result[comment] = line
10
+ end
11
+ end
12
+
13
+ result
14
+ end
15
+
16
+ def read(path)
17
+ result = { }
18
+
19
+ File.open(path) do |f|
20
+ result = parse(f.read)
21
+ end
22
+
23
+ result
24
+ end
25
+
26
+ def write(path, data)
27
+ File.open(path, 'w') do |f|
28
+ data.each do |key, line|
29
+ f.puts(line)
30
+ end
31
+ end
32
+ end
33
+
34
+ def ssh_authorized_keys_path
35
+ ".ssh/authorized_keys"
36
+ end
37
+
38
+ def cache_file_path=(value)
39
+ @cache_file_path = value
40
+ end
41
+
42
+ def cache_file_path
43
+ @cache_file_path or File.expand_path(".keyble", ENV['HOME'])
44
+ end
45
+
46
+ def cache
47
+ @cache ||=
48
+ if (File.exist?(cache_file_path))
49
+ read(cache_file_path)
50
+ else
51
+ { }
52
+ end
53
+ end
54
+
55
+ def cache_merge!(data)
56
+ cache.merge!(data)
57
+
58
+ cache
59
+ end
60
+
61
+ def cache_save!
62
+ return unless (@cache)
63
+
64
+ write(cache_file_path, @cache)
65
+ end
66
+
67
+ def keys_get(servers)
68
+ result = { }
69
+
70
+ servers.each do |server|
71
+ Net::SCP.start(server, nil) do |scp|
72
+ result[server] = parse(scp.download!(ssh_authorized_keys_path))
73
+ end
74
+ end
75
+
76
+ result
77
+ end
78
+
79
+ def keys_reassign(servers)
80
+ servers.each do |server|
81
+ Net::SCP.start(server, nil) do |scp|
82
+ existing = parse(scp.download!(ssh_authorized_keys_path))
83
+
84
+ merged = yield(server, existing.dup)
85
+
86
+ if (merged != existing)
87
+ authorized_keys = StringIO.new(merged.values.join("\n"))
88
+
89
+ scp.upload!(authorized_keys, ssh_authorized_keys_path)
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def keys_add(keys, servers)
96
+ keys_reassign(servers) do |server, existing_keys|
97
+ existing_keys.merge(keys)
98
+ end
99
+ end
100
+
101
+ def keys_remove(keys, servers)
102
+ if (keys.respond_to?(:keys))
103
+ keys = keys.keys
104
+ end
105
+
106
+ keys_reassign(servers) do |server, existing_keys|
107
+ keys.each do |key|
108
+ existing_keys.delete(key)
109
+ end
110
+
111
+ existing_keys
112
+ end
113
+ end
114
+
115
+ def keys_import(file)
116
+ imported = read(file)
117
+
118
+ cache_merge!(imported)
119
+ cache_save!
120
+
121
+ imported
122
+ end
123
+
124
+ def keys_display(keys)
125
+ longest_key = keys.keys.collect { |k| k.to_s.length }.sort[-1]
126
+
127
+ keys.sort.each do |key, line|
128
+ puts "%-#{longest_key}s %s" % [ key, line.split(/\s+/)[1][-20, 20] ]
129
+ end
130
+ end
131
+
132
+ extend self
133
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'keyble'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,4 @@
1
+ require File.expand_path('helper', File.dirname(__FILE__))
2
+
3
+ class TestKeyble < Test::Unit::TestCase
4
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: keyble
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Scott Tadman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-scp
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: jeweler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Unubtrusive SSH key management
63
+ email: scott@twg.ca
64
+ executables:
65
+ - keyble
66
+ extensions: []
67
+ extra_rdoc_files:
68
+ - LICENSE.txt
69
+ - README.md
70
+ files:
71
+ - .document
72
+ - Gemfile
73
+ - LICENSE.txt
74
+ - README.md
75
+ - Rakefile
76
+ - VERSION
77
+ - bin/keyble
78
+ - keyble.gemspec
79
+ - lib/keyble.rb
80
+ - test/helper.rb
81
+ - test/test_keyble.rb
82
+ homepage: http://github.com/twg/keyble
83
+ licenses:
84
+ - MIT
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.23
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: SSH Key Management
107
+ test_files: []