sshakery 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sshakery.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'minitest'
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 hattb
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Sshakery
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'sshakery'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install sshakery
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'lib/sshakery'
6
+ t.test_files = FileList[
7
+ 'test/lib/*_test.rb',
8
+ 'test/lib/sshakery/*_test.rb'
9
+ ]
10
+ t.verbose = true
11
+ end
12
+ task :default => :test
@@ -0,0 +1,340 @@
1
+ module Sshakery
2
+
3
+ class AuthKeys
4
+ # atomic writes
5
+ require 'tempfile'
6
+ require 'fileutils'
7
+
8
+ # instance attributes
9
+ # Instance state attributed
10
+ INSTANCE_ATTRIBUTES = [ :errors,
11
+ :raw_line,
12
+ :path,
13
+ :saved
14
+ ]
15
+
16
+ # attr used for auth key line (listed in order of appearance)
17
+ KEY_ATTRIBUTES =[
18
+ :command,
19
+ :permitopen,
20
+ :tunnel,
21
+ :from,
22
+ :environment,
23
+ :no_agent_forwarding,
24
+ :no_port_forwarding,
25
+ :no_pty,
26
+ :no_user_rc,
27
+ :no_X11_forwarding,
28
+ :key_type,
29
+ :key_data,
30
+ :note
31
+ ]
32
+
33
+ ATTRIBUTES = INSTANCE_ATTRIBUTES+KEY_ATTRIBUTES
34
+
35
+ ATTRIBUTES.each do |attr|
36
+ attr_accessor attr
37
+ end
38
+
39
+ DEFAULTS={
40
+ :errors => []
41
+ }.freeze
42
+
43
+ # equal their name if true
44
+ BOOL_ATTRIBUTES = [
45
+ :no_agent_forwarding,
46
+ :no_port_forwarding,
47
+ :no_pty,
48
+ :no_user_rc,
49
+ :no_X11_forwarding
50
+ ]
51
+
52
+ # equal their value if set
53
+ STR_ATTRIBUTES = [
54
+ :key_type,
55
+ :key_data,
56
+ :note
57
+ ]
58
+
59
+ # each is equal to a joined string of their values
60
+ ARR_STR_ATTRIBUTES = [
61
+ :environment
62
+ ]
63
+
64
+ # add string and substitute attr value
65
+ SUB_STR_ATTRIBUTES = {
66
+ :command=>'command="%sub%"',
67
+ :permitopen=>'permitopen="%sub%"',
68
+ :tunnel=>'tunnel="%sub%"',
69
+ :from=>'from="%sub%"'
70
+ }
71
+
72
+ # regex for matching ssh keys imported from a pub key file
73
+ TYPE_REGEX = /ssh-dss|ssh-rsa/
74
+ B64_REGEX = /[A-Za-z0-9\/\+]+={0,3}/
75
+
76
+ # additional regex for loading from auth keys file
77
+ OPTS_REGEX = {
78
+ :key_type=> /(#{TYPE_REGEX}) (?:#{B64_REGEX})/,
79
+ :key_data=> /(?:#{TYPE_REGEX}) (#{B64_REGEX})/,
80
+ :note=>/([A-Za-z0-9_\/\+@]+)\s*$/,
81
+ :command=>/command="([^"]+)"(?: |,)/,
82
+ :environment=>/([A-Z0-9]+=[^\s]+)(?: |,)/,
83
+ :from=>/from="([^"])"(?: |,)/,
84
+ :no_agent_forwarding=>/(no-agent-forwarding)(?: |,)/,
85
+ :no_port_forwarding=>/(no-port-forwarding)(?: |,)/,
86
+ :no_pty=>/(no-pty)(?: |,)/,
87
+ :no_user_rc=>/(no-user-rc)(?: |,)/,
88
+ :no_X11_forwarding=>/(no-X11-forwarding)(?: |,)/,
89
+ :permitopen=>/permitopen="([a-z0-9.]+:[\d]+)"(?: |,)/,
90
+ :tunnel=>/tunnel="(\d+)"(?: |,)/
91
+ }
92
+
93
+ ERRORS = {
94
+ :data_modulus=> {:key_data=>'public key length is not a modulus of 4'},
95
+ :data_short => {:key_data=>'public key is too short'},
96
+ :data_long => {:key_data=>'public key is too long'},
97
+ :data_char => {:key_data=>'public key contains invalid base64 characters'},
98
+ :data_nil => {:key_data=>'public key is missing'},
99
+ :type_nil => {:key_type=>'missing key type'},
100
+ :bool => 'bad value for boolean field'
101
+ }
102
+ # class instance attributes
103
+ class << self; attr_accessor :path, :temp_path end
104
+
105
+ # class methods
106
+
107
+ #return array of keys matching field
108
+ def self.find_all_by(field,value, with_regex=false)
109
+ result = []
110
+ self.all.each do |auth_key|
111
+ if with_regex
112
+ result.push auth_key if auth_key.send(field).match(value)
113
+ else
114
+ result.push auth_key if auth_key.send(field) == value
115
+ end
116
+ end
117
+ return result
118
+ end
119
+
120
+ def self.destroy(auth_key)
121
+ self.write auth_key, destroy=true
122
+ end
123
+
124
+ def self.write(auth_key, destroy=false)
125
+ lines = []
126
+ FsUtils.atomic_lock(:path=>self.path) do |f|
127
+ f.each_line do |line|
128
+ key=self.new(:raw_line => line )
129
+
130
+ if key.key_data == auth_key.key_data
131
+ lines.push auth_key.gen_raw_line if destroy==false
132
+ else
133
+ lines.push line
134
+ end
135
+ end
136
+ f.rewind
137
+ f.truncate(0)
138
+ lines.each do |line|
139
+ f.puts line
140
+ end
141
+ end
142
+ end
143
+
144
+ # return array of all keys in this file
145
+ def self.all
146
+ result = []
147
+ File.readlines(self.path).each do |line|
148
+ result.push( self.new(:raw_line => line ))
149
+ end
150
+ return result
151
+ end
152
+
153
+
154
+ # method attributes
155
+ def initialize(args={})
156
+ ATTRIBUTES.each do |attr|
157
+ instance_variable_set("@#{attr}", args.has_key?( attr ) ? args[attr] : nil )
158
+ end
159
+
160
+ unless self.raw_line.nil?
161
+ self.load_raw_line
162
+ self.saved = true
163
+ end
164
+ end
165
+
166
+ # instantiate object based on contents of raw_line
167
+ def load_raw_line
168
+ self.raw_line.chomp!
169
+ OPTS_REGEX.each do |xfield,pattern|
170
+ field = "@#{xfield}"
171
+ m= self.raw_line.match pattern
172
+ next if m.nil?
173
+ #p "#{field} => #{m.inspect}"
174
+ if BOOL_ATTRIBUTES.include? xfield
175
+ self.instance_variable_set(field, true)
176
+ next
177
+ end
178
+
179
+ if STR_ATTRIBUTES.include? xfield
180
+ self.instance_variable_set(field, m[1])
181
+ next
182
+ end
183
+
184
+ if ARR_STR_ATTRIBUTES.include? xfield
185
+ self.instance_variable_set(field, m.to_a)
186
+ next
187
+ end
188
+
189
+ if SUB_STR_ATTRIBUTES.include? xfield
190
+ self.instance_variable_set(field, m[1])
191
+ next
192
+ end
193
+
194
+ end
195
+ end
196
+
197
+ def all_no=(val)
198
+ BOOL_ATTRIBUTES.each do |attr|
199
+ self.instance_variable_set("@#{attr}",val)
200
+ end
201
+ end
202
+
203
+
204
+ # return string representation of what attr will look like in auth file
205
+ def raw_getter field
206
+ val = self.instance_variable_get("@#{field}")
207
+ return nil if val.nil? == true || val == false
208
+
209
+ if BOOL_ATTRIBUTES.include? field
210
+ return field.to_s.gsub '_', '-'
211
+ end
212
+
213
+ if STR_ATTRIBUTES.include? field
214
+ return val
215
+ end
216
+
217
+ if ARR_STR_ATTRIBUTES.include? field && val.empty? == false
218
+ return val.join ' '
219
+ end
220
+
221
+ if SUB_STR_ATTRIBUTES.include? field
222
+ return SUB_STR_ATTRIBUTES[field].sub '%sub%', val
223
+ end
224
+ end
225
+
226
+ # validate and add a key to authorized keys file
227
+ def save
228
+ return false if not self.valid?
229
+ return self.class.write(self)
230
+ end
231
+
232
+ # Raise an error if save doest pass validations
233
+ def save!
234
+ unless self.save
235
+ raise Sshakery::Errors::RecordInvalid.new 'Errors preventing save'
236
+ end
237
+ end
238
+
239
+ def destroy
240
+ return false if not self.saved?
241
+ return self.class.destroy self
242
+ end
243
+
244
+ # construct line for file
245
+ def gen_raw_line
246
+ return nil unless self.valid?
247
+ line = ''
248
+ data = []
249
+ SUB_STR_ATTRIBUTES.each do |field,field_regex|
250
+ val = self.raw_getter field
251
+ data.push val if val.nil? == false
252
+ end
253
+ unless data.empty?
254
+ line = "#{data.join ' ,'}"
255
+ end
256
+
257
+ data = []
258
+ BOOL_ATTRIBUTES.each do |field|
259
+ val = self.raw_getter field
260
+ data.push val if val.nil? == false
261
+ end
262
+ unless data.empty?
263
+ if line == ''
264
+ line += "#{data.join ','} "
265
+ else
266
+ line += ",#{data.join ','} "
267
+ end
268
+ end
269
+
270
+ data = []
271
+ ARR_STR_ATTRIBUTES.each do |field|
272
+ val = self.raw_getter field
273
+ data.push val if val.nil? == false
274
+ end
275
+ unless data.empty?
276
+ if line == ''
277
+ line += "#{data.join ','} "
278
+ else
279
+ line += ", #{data.join ','} "
280
+ end
281
+ end
282
+
283
+ data = []
284
+ STR_ATTRIBUTES.each do |field|
285
+ val = self.raw_getter field
286
+ data.push val if val.nil? == false
287
+ end
288
+ line += data.join ' '
289
+ return line
290
+ end
291
+
292
+ def valid?
293
+ self.errors = []
294
+
295
+ BOOL_ATTRIBUTES.each do |field|
296
+ val = self.raw_getter field
297
+ unless val.nil? == true || val == true || val == false
298
+ self.errors.push field=>ERRORS[:bool]
299
+ end
300
+ end
301
+
302
+ if self.key_data.nil?:
303
+ self.errors.push ERRORS[:data_nil]
304
+ return false
305
+ end
306
+
307
+ if self.key_type.nil?:
308
+ self.errors.push ERRORS[:type_nil]
309
+ return false
310
+ end
311
+
312
+ if not self.key_data.match "^#{B64_REGEX}$":
313
+ self.errors.push ERRORS[:data_char]
314
+ end
315
+
316
+ if self.key_data.size < 30:
317
+ self.errors.push ERRORS[:data_short]
318
+ end
319
+
320
+ if self.key_data.size > 1000:
321
+ self.errors.push ERRORS[:data_long]
322
+ end
323
+
324
+ if self.key_data.size % 4 != 0:
325
+ self.errors.push ERRORS[:data_modulus]
326
+ end
327
+
328
+ return self.errors.empty?
329
+ end
330
+
331
+ def saved?
332
+ return false if not self.valid?
333
+ return self.saved
334
+ end
335
+
336
+ end
337
+
338
+
339
+
340
+ end
@@ -0,0 +1,4 @@
1
+ module Sshakery::Errors
2
+ class RecordInvalid < StandardError
3
+ end
4
+ end
@@ -0,0 +1,131 @@
1
+
2
+ module Sshakery::FsUtils
3
+ require 'tempfile' unless defined?(Tempfile)
4
+ require 'fileutils' unless defined?(FileUtils)
5
+
6
+ # Write to file atomically while maintaining an exclusive lock
7
+ # create unused lock file and lock it
8
+ # create temp file
9
+ # copy file to temp file and yield temp
10
+ # atomic write temp
11
+ # release lock file
12
+ # - cant lock actual file as mv operation breaks lock
13
+ # - exclusive lock only works if all processes use the same
14
+ # lock file (this will happen by default)
15
+ # - atomic writes by moving file
16
+ def self.atomic_lock(opts={:path=>nil,:lock_name=>nil}, &block)
17
+ file_name = opts[:path]
18
+ opts[:lock_name] = file_name+'.lockfile' unless opts[:lock_name]
19
+ lock_name = opts[:lock_name]
20
+
21
+ # create lock_file if it doesnt exist
22
+ FileUtils.touch(lock_name)
23
+ self.lock_file(lock_name) do |f|
24
+ # write details of lock
25
+ f.truncate 0
26
+ f.puts self.lock_info
27
+ f.flush
28
+
29
+ # yield for atomic writes
30
+ self.atomic_write(file_name) do |temp_file|
31
+ yield temp_file
32
+ end
33
+ end
34
+ end
35
+
36
+ # Lock a file for a block so only one thread/process can modify it at a time.
37
+ def self.lock_file(file_name, &block)
38
+ f = File.open(file_name, 'r+')
39
+ begin
40
+ f.flock File::LOCK_EX
41
+ yield f
42
+ ensure
43
+ f.flock File::LOCK_UN unless f.nil?
44
+ end
45
+ end
46
+
47
+ # aquire shared lock for reading a file
48
+ def self.read(file_name, &block)
49
+ f = File.open(file_name, 'r')
50
+ f.flock File::LOCK_SH
51
+ puts "sh locked #{file_name}"
52
+ yield f
53
+ ensure
54
+ puts "sh unlocked #{file_name}"
55
+ f.flock File::LOCK_UN
56
+ end
57
+
58
+ # copied from:
59
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/file/atomic.rb
60
+
61
+ # Write to a file atomically. Useful for situations where you don't
62
+ # want other processes or threads to see half-written files.
63
+ #
64
+ # File.atomic_write('important.file') do |file|
65
+ # file.write('hello')
66
+ # end
67
+ #
68
+ # If your temp directory is not on the same filesystem as the file you're
69
+ # trying to write, you can provide a different temporary directory.
70
+ #
71
+ # File.atomic_write('/data/something.important', '/data/tmp') do |file|
72
+ # file.write('hello')
73
+ # end
74
+ def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
75
+ temp_file = Tempfile.new(File.basename(file_name), temp_dir)
76
+ temp_file.binmode
77
+ FileUtils.cp(file_name,temp_file.path)
78
+ yield temp_file
79
+ temp_file.close
80
+
81
+ if File.exists?(file_name)
82
+ # Get original file permissions
83
+ old_stat = File.stat(file_name)
84
+ else
85
+ # If not possible, probe which are the default permissions in the
86
+ # destination directory.
87
+ old_stat = probe_stat_in(dirname(file_name))
88
+ end
89
+
90
+ # Overwrite original file with temp file
91
+ FileUtils.mv(temp_file.path, file_name)
92
+
93
+ # Set correct permissions on new file
94
+ begin
95
+ File.chown(old_stat.uid, old_stat.gid, file_name)
96
+ # This operation will affect filesystem ACL's
97
+ File.chmod(old_stat.mode, file_name)
98
+ rescue Errno::EPERM
99
+ # Changing file ownership failed, moving on.
100
+ end
101
+ end
102
+
103
+ # Private utility method.
104
+ def self.probe_stat_in(dir) #:nodoc:
105
+ basename = [
106
+ '.permissions_check',
107
+ Thread.current.object_id,
108
+ Process.pid,
109
+ rand(1000000)
110
+ ].join('.')
111
+
112
+ file_name = join(dir, basename)
113
+ FileUtils.touch(file_name)
114
+ File.stat(file_name)
115
+ ensure
116
+ FileUtils.rm_f(file_name) if file_name
117
+ end
118
+
119
+ # lock file details to write to disk
120
+ def self.lock_info
121
+ return [
122
+ 'Sshakery-lockfile',
123
+ Thread.current.object_id,
124
+ Process.pid,
125
+ Time.now.to_i,
126
+ rand(1000000)
127
+ ].join(' ')
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,3 @@
1
+ module Sshakery
2
+ VERSION = "0.0.2"
3
+ end
data/lib/sshakery.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "sshakery/version"
2
+ require "sshakery/fs_utils"
3
+ require "sshakery/auth_keys"
4
+ require "sshakery/errors"
5
+
6
+ module Sshakery
7
+ # instantiate a new Authkey class
8
+ def self.new path
9
+ new = Class.new(AuthKeys)
10
+ new.path = path
11
+ new.temp_path = 'sshakery.temp'
12
+ return new
13
+ end
14
+ end
data/sshakery.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sshakery/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "sshakery"
8
+ gem.version = Sshakery::VERSION
9
+ gem.authors = ["hattwj"]
10
+ gem.email = ["hattwj@yahoo.com"]
11
+ gem.description = %q{A ruby gem for manipulating OpenSSH authorized_keys files}
12
+ gem.summary = %q{SSHakery is a ruby gem for manipulating OpenSSH authorized_keys files. It features file locking, backups (todo), and atomic writes}
13
+ gem.homepage = "https://github.com/hattwj/sshakery"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency("minitest", "~> 4.1.0")
21
+ end
@@ -0,0 +1,3 @@
1
+ no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAAAB3NzaC1ycAAAADAQABAAAAgQDPnqYjTcgGQDeMfzSvfdxIop03nsL+2W9csY3AvyjZrPuoqzcw== minitron_rsa
2
+ command="ls" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCfrOeOZ0OynDubOdonHUmHZG18LA7PfauD6gL2sFHp2/RMG3IxjN7T5BswpzDcwDWec0W7gDThlBN7K28fGekSNwnXvyI1E4ORdJ+u51PAeTi+HdYCqVZok3aQoEW4Kx4RBSW47Me7UnsoMjtC44UfxGpxnvoAXEq/YyiYyIr8nsk98D8sHsgZGpvofwblKXM5nhTvtR/pZo7r49knQB6I+5rrDr ubuntu@localhost
3
+ command="fortune" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDsBWq5F1n4FcZILZt42B6NthVrfGo0whFfSbyPuc/wfQcACpOy8EeAsCIGw0m+pDF8KlmDhYJhC/DGv4zXqk6yO+X3n5x+zfJY4AL1bu72kBnOuXbPhiDfmoBmcApbHDVJnzhRzd8sWv6qLd7bF+Dd None
@@ -0,0 +1,106 @@
1
+ require 'test/test_helper'
2
+
3
+ describe Sshakery::AuthKeys do
4
+ # create a copy of test data to manipulate
5
+ before do
6
+ @errors = Sshakery::AuthKeys::ERRORS
7
+ @temp = Tempfile.new('nofail')
8
+ src = "#{$dir}/fixtures/sshakery_nofail_fixture.txt"
9
+ FileUtils.cp src, @temp.path
10
+ @keys = Sshakery.new(@temp.path)
11
+ @key = @keys.new
12
+ end
13
+
14
+ # close temp file (should autoremove)
15
+ after do
16
+ @temp.close
17
+ @temp.unlink
18
+ end
19
+
20
+ describe "class behavior" do
21
+ it "must be defined" do
22
+ Sshakery::AuthKeys.wont_be_nil
23
+ end
24
+ end
25
+
26
+ # instance behavior
27
+ it "must not validate when empty" do
28
+ key=@keys.new
29
+ key.valid?.must_equal false
30
+ key.errors.wont_be_empty
31
+ end
32
+
33
+ it "must not save when empty" do
34
+ key=@keys.new
35
+ key.save.must_equal false
36
+ end
37
+
38
+ it "must raise error on save! with invalid data" do
39
+ key = @keys.new
40
+ failed_val = lambda { key.save! }
41
+ failed_val.must_raise Sshakery::Errors::RecordInvalid
42
+ error = failed_val.call rescue $!
43
+ error.message.must_include "Errors preventing save"
44
+ end
45
+
46
+ it "must have errors when generated line is incomplete" do
47
+ key = @keys.new
48
+ key.all_no = true
49
+ key.gen_raw_line.must_be_nil
50
+ key.errors.wont_be_empty
51
+ end
52
+
53
+ it "must accept valid b64 key_data" do
54
+ key = @keys.new
55
+ key.key_type = 'ssh-rsa'
56
+ key.key_data = 'badfhk55'*20
57
+ key.valid?.must_equal true
58
+ end
59
+
60
+ it "must reject invalid b64 characters" do
61
+ key = @keys.new
62
+ key.key_type = 'ssh-rsa'
63
+ key.key_data = 'baaAa$@'*40
64
+ key.valid?.must_equal false
65
+ key.errors.include?(@errors[:data_char]).must_equal true
66
+ key.key_data = 'ba ,a$@'*40
67
+ key.valid?.must_equal false
68
+ key.errors.include?(@errors[:data_char]).must_equal true
69
+ end
70
+
71
+ it "must only accept b64 modulus 4 data" do
72
+ key = @keys.new
73
+ key.key_type = 'ssh-rsa'
74
+ key.key_data = 'baa'*40
75
+ key.valid?.must_equal true
76
+ key.key_data = 'baa'*41
77
+ key.valid?.must_equal false
78
+ key.errors.include?(@errors[:data_modulus]).must_equal true
79
+ end
80
+
81
+ it "must reject short key_data" do
82
+ key = @keys.all[0]
83
+ key.key_data = 'baaAa12'*4
84
+ key.valid?.must_equal false
85
+ key.errors.include?(@errors[:data_short]).must_equal true
86
+ end
87
+
88
+ it "must reject long key_data" do
89
+ key = @keys.all[0]
90
+ key.key_data = 'baaAa123'*400
91
+ key.valid?.must_equal false
92
+ key.errors.include?(@errors[:data_long]).must_equal true
93
+ end
94
+
95
+ it "must reject invalid boolean options" do
96
+ instance = @keys.new
97
+ Sshakery::AuthKeys::BOOL_ATTRIBUTES.each do |attr|
98
+ key = @keys.all[0]
99
+ puts key.key_data.size
100
+ key.instance_variable_set("@#{attr}",'bad_data')
101
+ key.valid?.must_equal false
102
+ key.errors.include?(attr=>@errors[:bool]).must_equal true
103
+ end
104
+ end
105
+ end
106
+
@@ -0,0 +1,36 @@
1
+ require 'test/test_helper'
2
+
3
+ describe Sshakery::FsUtils do
4
+ it "must lock for writes" do
5
+ # a large offset to create large writes
6
+ offset = 10**600
7
+
8
+ #test file for testing atomic writes and locking
9
+ temp = Tempfile.new 'flock'
10
+ ts=[]
11
+ (1..50).each do |i|
12
+ t =Thread.new{
13
+ sleep rand/3
14
+ # update a counter using atomic writes and a file lock
15
+ Sshakery::FsUtils.atomic_lock( :path=>temp.path, :lock_path=>'./hhh' ) do |f|
16
+ (1..50).each do |j|
17
+ f.rewind
18
+ val = f.read.to_i
19
+ #write number to file
20
+ val = val>0 ? val+1 : offset+1
21
+ f.rewind
22
+ f.truncate f.pos
23
+ f.write "#{val}\n"
24
+ f.flush
25
+ f.rewind
26
+ end
27
+ end
28
+ }
29
+ ts.push t
30
+ end
31
+ ts.each{|t| t.join}
32
+ File.open(temp.path).read.to_i.must_equal offset+2500
33
+ temp.close
34
+ temp.unlink
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ require 'test/test_helper'
2
+
3
+ describe Sshakery do
4
+ it "must be defined" do
5
+ Sshakery::VERSION.wont_be_nil
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ require 'test/test_helper'
2
+ require 'tempfile'
3
+ require 'fileutils'
4
+
5
+ describe Sshakery do
6
+
7
+ # create a copy of test data to manipulate
8
+ before do
9
+ @temp = Tempfile.new('nofail')
10
+ src = "#{$dir}/fixtures/sshakery_nofail_fixture.txt"
11
+ FileUtils.cp src, @temp.path
12
+ @keys = Sshakery.new(@temp.path)
13
+ end
14
+
15
+ # close temp file (should autoremove)
16
+ after do
17
+ @temp.close
18
+ @temp.unlink
19
+ end
20
+
21
+ it "must load an authorized_keys file" do
22
+ @keys.all.size.wont_be_nil
23
+ end
24
+
25
+
26
+ it "must be searchable" do
27
+ @keys.find_all_by(:command,'ls').size.must_equal 1
28
+ @keys.find_all_by(:no_X11_forwarding,true).size.must_equal 1
29
+ end
30
+ end
31
+
@@ -0,0 +1,7 @@
1
+
2
+ require 'rubygems'
3
+ require 'minitest/autorun'
4
+ require 'minitest/pride'
5
+ require File.expand_path('../../lib/sshakery.rb', __FILE__)
6
+
7
+ $dir = File.dirname(File.expand_path(__FILE__))
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sshakery
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - hattwj
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-11-02 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: minitest
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 59
29
+ segments:
30
+ - 4
31
+ - 1
32
+ - 0
33
+ version: 4.1.0
34
+ type: :development
35
+ version_requirements: *id001
36
+ description: A ruby gem for manipulating OpenSSH authorized_keys files
37
+ email:
38
+ - hattwj@yahoo.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - .gitignore
47
+ - Gemfile
48
+ - LICENSE.txt
49
+ - README.md
50
+ - Rakefile
51
+ - lib/sshakery.rb
52
+ - lib/sshakery/auth_keys.rb
53
+ - lib/sshakery/errors.rb
54
+ - lib/sshakery/fs_utils.rb
55
+ - lib/sshakery/version.rb
56
+ - sshakery.gemspec
57
+ - test/fixtures/sshakery_nofail_fixture.txt
58
+ - test/lib/sshakery/auth_keys_test.rb
59
+ - test/lib/sshakery/fs_utils_test.rb
60
+ - test/lib/sshakery/version_test.rb
61
+ - test/lib/sshakery_test.rb
62
+ - test/test_helper.rb
63
+ homepage: https://github.com/hattwj/sshakery
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.8.15
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: SSHakery is a ruby gem for manipulating OpenSSH authorized_keys files. It features file locking, backups (todo), and atomic writes
96
+ test_files:
97
+ - test/fixtures/sshakery_nofail_fixture.txt
98
+ - test/lib/sshakery/auth_keys_test.rb
99
+ - test/lib/sshakery/fs_utils_test.rb
100
+ - test/lib/sshakery/version_test.rb
101
+ - test/lib/sshakery_test.rb
102
+ - test/test_helper.rb