sshakery 0.0.2

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,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