rkerberos 0.2.3 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGES.md +15 -0
- data/EXAMPLES.md +1015 -0
- data/MANIFEST.md +17 -12
- data/README.md +5 -5
- data/ext/rkerberos/ccache.c +101 -26
- data/ext/rkerberos/config.c +53 -7
- data/ext/rkerberos/context.c +70 -7
- data/ext/rkerberos/kadm5.c +280 -87
- data/ext/rkerberos/keytab.c +210 -64
- data/ext/rkerberos/policy.c +25 -23
- data/ext/rkerberos/principal.c +108 -10
- data/ext/rkerberos/rkerberos.c +229 -28
- data/ext/rkerberos/rkerberos.h +8 -0
- data/rkerberos.gemspec +10 -7
- data/spec/config_spec.rb +33 -0
- data/spec/context_spec.rb +51 -0
- data/spec/credentials_cache_spec.rb +79 -26
- data/spec/kadm5_spec.rb +278 -5
- data/spec/krb5_keytab_spec.rb +224 -20
- data/spec/krb5_spec.rb +131 -20
- data/spec/policy_spec.rb +4 -0
- data/spec/principal_spec.rb +90 -10
- metadata +9 -8
data/spec/krb5_keytab_spec.rb
CHANGED
|
@@ -37,21 +37,49 @@ RSpec.describe Kerberos::Krb5::Keytab, :kadm5 do
|
|
|
37
37
|
subject(:keytab) { described_class.new }
|
|
38
38
|
|
|
39
39
|
describe 'constructor' do
|
|
40
|
-
it 'accepts an optional name' do
|
|
41
|
-
expect { described_class.new("FILE:/usr/local/var/keytab") }.not_to raise_error
|
|
42
|
-
expect { described_class.new("FILE:/bogus/keytab") }.not_to raise_error
|
|
40
|
+
it 'accepts an optional name keyword' do
|
|
41
|
+
expect { described_class.new(name: "FILE:/usr/local/var/keytab") }.not_to raise_error
|
|
42
|
+
expect { described_class.new(name: "FILE:/bogus/keytab") }.not_to raise_error
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
it 'raises error for invalid residual type' do
|
|
46
46
|
expect {
|
|
47
|
-
described_class.new("BOGUS:/tmp/keytab")
|
|
47
|
+
described_class.new(name: "BOGUS:/tmp/keytab")
|
|
48
48
|
}.to raise_error(Kerberos::Krb5::Keytab::Exception)
|
|
49
49
|
end
|
|
50
|
+
|
|
51
|
+
it 'accepts a context keyword argument' do
|
|
52
|
+
ctx = Kerberos::Krb5::Context.new
|
|
53
|
+
expect { described_class.new(name: @keytab_name, context: ctx) }.not_to raise_error
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'works with context and no name' do
|
|
57
|
+
ctx = Kerberos::Krb5::Context.new
|
|
58
|
+
expect { described_class.new(context: ctx) }.not_to raise_error
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'rejects positional arguments' do
|
|
62
|
+
expect { described_class.new(@keytab_name) }.to raise_error(ArgumentError)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'rejects unknown keywords' do
|
|
66
|
+
expect { described_class.new(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'raises TypeError for non-Context context argument' do
|
|
70
|
+
expect { described_class.new(context: "bad") }.to raise_error(TypeError)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'raises error for a closed context' do
|
|
74
|
+
ctx = Kerberos::Krb5::Context.new
|
|
75
|
+
ctx.close
|
|
76
|
+
expect { described_class.new(context: ctx) }.to raise_error(Kerberos::Krb5::Exception)
|
|
77
|
+
end
|
|
50
78
|
end
|
|
51
79
|
|
|
52
80
|
describe '#keytab_name and #keytab_type' do
|
|
53
81
|
it 'returns the underlying name and type strings' do
|
|
54
|
-
kt = described_class.new(@keytab_name)
|
|
82
|
+
kt = described_class.new(name: @keytab_name)
|
|
55
83
|
expect(kt).to respond_to(:keytab_name)
|
|
56
84
|
expect(kt).to respond_to(:keytab_type)
|
|
57
85
|
|
|
@@ -67,30 +95,30 @@ RSpec.describe Kerberos::Krb5::Keytab, :kadm5 do
|
|
|
67
95
|
|
|
68
96
|
describe '#close' do
|
|
69
97
|
it 'returns true' do
|
|
70
|
-
kt = described_class.new(@keytab_name)
|
|
98
|
+
kt = described_class.new(name: @keytab_name)
|
|
71
99
|
expect(kt.close).to eq(true)
|
|
72
100
|
end
|
|
73
101
|
|
|
74
102
|
it 'can be called multiple times without error' do
|
|
75
|
-
kt = described_class.new(@keytab_name)
|
|
103
|
+
kt = described_class.new(name: @keytab_name)
|
|
76
104
|
kt.close
|
|
77
105
|
expect { kt.close }.not_to raise_error
|
|
78
106
|
end
|
|
79
107
|
|
|
80
108
|
it 'raises an error when calling keytab_name after close' do
|
|
81
|
-
kt = described_class.new(@keytab_name)
|
|
109
|
+
kt = described_class.new(name: @keytab_name)
|
|
82
110
|
kt.close
|
|
83
111
|
expect { kt.keytab_name }.to raise_error(Kerberos::Krb5::Exception)
|
|
84
112
|
end
|
|
85
113
|
|
|
86
114
|
it 'raises an error when calling keytab_type after close' do
|
|
87
|
-
kt = described_class.new(@keytab_name)
|
|
115
|
+
kt = described_class.new(name: @keytab_name)
|
|
88
116
|
kt.close
|
|
89
117
|
expect { kt.keytab_type }.to raise_error(Kerberos::Krb5::Exception)
|
|
90
118
|
end
|
|
91
119
|
|
|
92
120
|
it 'does not segfault when garbage collected after close' do
|
|
93
|
-
kt = described_class.new(@keytab_name)
|
|
121
|
+
kt = described_class.new(name: @keytab_name)
|
|
94
122
|
kt.close
|
|
95
123
|
kt = nil
|
|
96
124
|
GC.start
|
|
@@ -152,7 +180,7 @@ RSpec.describe Kerberos::Krb5::Keytab, :kadm5 do
|
|
|
152
180
|
|
|
153
181
|
describe '#each' do
|
|
154
182
|
it 'yields keytab entries for a valid keytab' do
|
|
155
|
-
kt = described_class.new(@keytab_name)
|
|
183
|
+
kt = described_class.new(name: @keytab_name)
|
|
156
184
|
entries = []
|
|
157
185
|
kt.each { |entry| entries << entry }
|
|
158
186
|
expect(entries.length).to eq(2)
|
|
@@ -166,14 +194,14 @@ RSpec.describe Kerberos::Krb5::Keytab, :kadm5 do
|
|
|
166
194
|
end
|
|
167
195
|
|
|
168
196
|
it 'does not leak resources when the block raises' do
|
|
169
|
-
kt = described_class.new(@keytab_name)
|
|
197
|
+
kt = described_class.new(name: @keytab_name)
|
|
170
198
|
expect {
|
|
171
199
|
kt.each { |_| raise "boom" }
|
|
172
200
|
}.to raise_error(RuntimeError, "boom")
|
|
173
201
|
end
|
|
174
202
|
|
|
175
203
|
it 'does not leak resources when the block breaks' do
|
|
176
|
-
kt = described_class.new(@keytab_name)
|
|
204
|
+
kt = described_class.new(name: @keytab_name)
|
|
177
205
|
result = catch(:done) do
|
|
178
206
|
kt.each { |_| throw :done, :escaped }
|
|
179
207
|
:completed
|
|
@@ -184,7 +212,7 @@ RSpec.describe Kerberos::Krb5::Keytab, :kadm5 do
|
|
|
184
212
|
|
|
185
213
|
describe '#get_entry' do
|
|
186
214
|
it 'finds an entry by principal name' do
|
|
187
|
-
kt = described_class.new(@keytab_name)
|
|
215
|
+
kt = described_class.new(name: @keytab_name)
|
|
188
216
|
entry = kt.get_entry("testuser1@#{@realm}")
|
|
189
217
|
expect(entry).to be_a(Kerberos::Krb5::Keytab::Entry)
|
|
190
218
|
expect(entry.principal).to eq("testuser1@#{@realm}")
|
|
@@ -194,14 +222,14 @@ RSpec.describe Kerberos::Krb5::Keytab, :kadm5 do
|
|
|
194
222
|
end
|
|
195
223
|
|
|
196
224
|
it 'finds an entry filtering by vno' do
|
|
197
|
-
kt = described_class.new(@keytab_name)
|
|
225
|
+
kt = described_class.new(name: @keytab_name)
|
|
198
226
|
entry = kt.get_entry("testuser1@#{@realm}", 1)
|
|
199
227
|
expect(entry.principal).to eq("testuser1@#{@realm}")
|
|
200
228
|
expect(entry.vno).to eq(1)
|
|
201
229
|
end
|
|
202
230
|
|
|
203
231
|
it 'finds an entry filtering by vno and enctype' do
|
|
204
|
-
kt = described_class.new(@keytab_name)
|
|
232
|
+
kt = described_class.new(name: @keytab_name)
|
|
205
233
|
# aes128-cts-hmac-sha1-96 is enctype 17
|
|
206
234
|
entry = kt.get_entry("testuser1@#{@realm}", 1, 17)
|
|
207
235
|
expect(entry.principal).to eq("testuser1@#{@realm}")
|
|
@@ -210,21 +238,21 @@ RSpec.describe Kerberos::Krb5::Keytab, :kadm5 do
|
|
|
210
238
|
end
|
|
211
239
|
|
|
212
240
|
it 'raises an error for a non-existent principal' do
|
|
213
|
-
kt = described_class.new(@keytab_name)
|
|
241
|
+
kt = described_class.new(name: @keytab_name)
|
|
214
242
|
expect {
|
|
215
243
|
kt.get_entry("bogus@#{@realm}")
|
|
216
244
|
}.to raise_error(Kerberos::Krb5::Exception)
|
|
217
245
|
end
|
|
218
246
|
|
|
219
247
|
it 'is aliased as find' do
|
|
220
|
-
kt = described_class.new(@keytab_name)
|
|
248
|
+
kt = described_class.new(name: @keytab_name)
|
|
221
249
|
expect(kt.method(:find)).to eq(kt.method(:get_entry))
|
|
222
250
|
end
|
|
223
251
|
end
|
|
224
252
|
|
|
225
253
|
describe '#dup' do
|
|
226
254
|
it 'creates an independent handle referring to same keytab' do
|
|
227
|
-
kt1 = described_class.new(@keytab_name)
|
|
255
|
+
kt1 = described_class.new(name: @keytab_name)
|
|
228
256
|
kt2 = kt1.dup
|
|
229
257
|
expect(kt2).to be_a(described_class)
|
|
230
258
|
expect(kt2.keytab_name).to eq(kt1.keytab_name)
|
|
@@ -235,8 +263,184 @@ RSpec.describe Kerberos::Krb5::Keytab, :kadm5 do
|
|
|
235
263
|
end
|
|
236
264
|
|
|
237
265
|
it 'clone is an alias for dup' do
|
|
238
|
-
kt = described_class.new(@keytab_name)
|
|
266
|
+
kt = described_class.new(name: @keytab_name)
|
|
239
267
|
expect(kt.method(:clone)).to eq(kt.method(:dup))
|
|
240
268
|
end
|
|
241
269
|
end
|
|
270
|
+
|
|
271
|
+
describe '#have_content?' do
|
|
272
|
+
it 'returns true for a keytab with entries' do
|
|
273
|
+
kt = described_class.new(name: @keytab_name)
|
|
274
|
+
expect(kt.have_content?).to be true
|
|
275
|
+
kt.close
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it 'returns false for an empty keytab' do
|
|
279
|
+
empty_file = File.join(Dir.tmpdir, 'empty_test.keytab')
|
|
280
|
+
FileUtils.touch(empty_file)
|
|
281
|
+
kt = described_class.new(name: "FILE:#{empty_file}")
|
|
282
|
+
expect(kt.have_content?).to be false
|
|
283
|
+
kt.close
|
|
284
|
+
FileUtils.rm_f(empty_file)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it 'raises when the context is closed' do
|
|
288
|
+
kt = described_class.new(name: @keytab_name)
|
|
289
|
+
kt.close
|
|
290
|
+
expect { kt.have_content? }.to raise_error(Kerberos::Krb5::Exception)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
describe '#add_entry' do
|
|
295
|
+
let(:add_kt_file) { File.join(Dir.tmpdir, 'add_test.keytab') }
|
|
296
|
+
let(:add_kt_name) { "FILE:#{add_kt_file}" }
|
|
297
|
+
|
|
298
|
+
after(:each) { FileUtils.rm_f(add_kt_file) }
|
|
299
|
+
|
|
300
|
+
it 'adds an entry that is retrievable via get_entry' do
|
|
301
|
+
kt = described_class.new(name: add_kt_name)
|
|
302
|
+
kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret', vno: 1)
|
|
303
|
+
|
|
304
|
+
entry = kt.get_entry("testuser1@#{@realm}")
|
|
305
|
+
expect(entry).to be_a(Kerberos::Krb5::Keytab::Entry)
|
|
306
|
+
expect(entry.principal).to eq("testuser1@#{@realm}")
|
|
307
|
+
expect(entry.vno).to eq(1)
|
|
308
|
+
# default enctype is aes256-cts-hmac-sha1-96 = 18
|
|
309
|
+
expect(entry.key).to eq(18)
|
|
310
|
+
kt.close
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'accepts an explicit enctype' do
|
|
314
|
+
kt = described_class.new(name: add_kt_name)
|
|
315
|
+
# aes128-cts-hmac-sha1-96 = 17
|
|
316
|
+
kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret', vno: 2, enctype: 17)
|
|
317
|
+
|
|
318
|
+
entry = kt.get_entry("testuser1@#{@realm}", 2, 17)
|
|
319
|
+
expect(entry.vno).to eq(2)
|
|
320
|
+
expect(entry.key).to eq(17)
|
|
321
|
+
kt.close
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it 'can add multiple entries for the same principal' do
|
|
325
|
+
kt = described_class.new(name: add_kt_name)
|
|
326
|
+
kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret', vno: 1, enctype: 18)
|
|
327
|
+
kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret', vno: 1, enctype: 17)
|
|
328
|
+
|
|
329
|
+
entries = []
|
|
330
|
+
kt.each { |e| entries << e }
|
|
331
|
+
expect(entries.length).to eq(2)
|
|
332
|
+
kt.close
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it 'returns self for chaining' do
|
|
336
|
+
kt = described_class.new(name: add_kt_name)
|
|
337
|
+
result = kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret')
|
|
338
|
+
expect(result).to equal(kt)
|
|
339
|
+
kt.close
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it 'raises ArgumentError when principal is missing' do
|
|
343
|
+
kt = described_class.new(name: add_kt_name)
|
|
344
|
+
expect { kt.add_entry(password: 'secret') }.to raise_error(ArgumentError)
|
|
345
|
+
kt.close
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
it 'raises ArgumentError when password is missing' do
|
|
349
|
+
kt = described_class.new(name: add_kt_name)
|
|
350
|
+
expect { kt.add_entry(principal: "testuser1@#{@realm}") }.to raise_error(ArgumentError)
|
|
351
|
+
kt.close
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
it 'raises ArgumentError with no arguments' do
|
|
355
|
+
kt = described_class.new(name: add_kt_name)
|
|
356
|
+
expect { kt.add_entry }.to raise_error(ArgumentError)
|
|
357
|
+
kt.close
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
it 'rejects unknown keywords' do
|
|
361
|
+
kt = described_class.new(name: add_kt_name)
|
|
362
|
+
expect {
|
|
363
|
+
kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret', unknown: true)
|
|
364
|
+
}.to raise_error(ArgumentError, /unknown keyword/)
|
|
365
|
+
kt.close
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
it 'raises when the keytab is closed' do
|
|
369
|
+
kt = described_class.new(name: add_kt_name)
|
|
370
|
+
kt.close
|
|
371
|
+
expect {
|
|
372
|
+
kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret')
|
|
373
|
+
}.to raise_error(Kerberos::Krb5::Exception)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
describe '#remove_entry' do
|
|
378
|
+
let(:rm_kt_file) { File.join(Dir.tmpdir, 'remove_test.keytab') }
|
|
379
|
+
let(:rm_kt_name) { "FILE:#{rm_kt_file}" }
|
|
380
|
+
|
|
381
|
+
before(:each) do
|
|
382
|
+
@rm_kt = described_class.new(name: rm_kt_name)
|
|
383
|
+
@rm_kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret', vno: 1, enctype: 18)
|
|
384
|
+
@rm_kt.add_entry(principal: "testuser1@#{@realm}", password: 'secret', vno: 2, enctype: 17)
|
|
385
|
+
@rm_kt.add_entry(principal: "testuser2@#{@realm}", password: 'secret', vno: 1, enctype: 18)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
after(:each) do
|
|
389
|
+
@rm_kt.close rescue nil
|
|
390
|
+
FileUtils.rm_f(rm_kt_file)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
it 'removes all entries for a principal when vno and enctype are defaulted' do
|
|
394
|
+
@rm_kt.remove_entry(principal: "testuser1@#{@realm}")
|
|
395
|
+
entries = []
|
|
396
|
+
@rm_kt.each { |e| entries << e }
|
|
397
|
+
principals = entries.map(&:principal)
|
|
398
|
+
expect(principals).not_to include("testuser1@#{@realm}")
|
|
399
|
+
expect(principals).to include("testuser2@#{@realm}")
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it 'removes only the entry matching a specific vno' do
|
|
403
|
+
@rm_kt.remove_entry(principal: "testuser1@#{@realm}", vno: 1)
|
|
404
|
+
entry = @rm_kt.get_entry("testuser1@#{@realm}")
|
|
405
|
+
expect(entry.vno).to eq(2)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
it 'removes only the entry matching a specific enctype' do
|
|
409
|
+
@rm_kt.remove_entry(principal: "testuser1@#{@realm}", enctype: 17)
|
|
410
|
+
entry = @rm_kt.get_entry("testuser1@#{@realm}")
|
|
411
|
+
expect(entry.key).to eq(18)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
it 'returns self for chaining' do
|
|
415
|
+
result = @rm_kt.remove_entry(principal: "testuser2@#{@realm}")
|
|
416
|
+
expect(result).to equal(@rm_kt)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
it 'raises ArgumentError when principal is missing' do
|
|
420
|
+
expect { @rm_kt.remove_entry(vno: 1) }.to raise_error(ArgumentError)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
it 'raises ArgumentError with no arguments' do
|
|
424
|
+
expect { @rm_kt.remove_entry }.to raise_error(ArgumentError)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
it 'rejects unknown keywords' do
|
|
428
|
+
expect {
|
|
429
|
+
@rm_kt.remove_entry(principal: "testuser1@#{@realm}", unknown: true)
|
|
430
|
+
}.to raise_error(ArgumentError, /unknown keyword/)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it 'raises when the keytab is closed' do
|
|
434
|
+
@rm_kt.close
|
|
435
|
+
expect {
|
|
436
|
+
@rm_kt.remove_entry(principal: "testuser1@#{@realm}")
|
|
437
|
+
}.to raise_error(Kerberos::Krb5::Exception)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
it 'raises for a non-existent entry' do
|
|
441
|
+
expect {
|
|
442
|
+
@rm_kt.remove_entry(principal: "nobody@#{@realm}")
|
|
443
|
+
}.to raise_error(Kerberos::Krb5::Keytab::Exception)
|
|
444
|
+
end
|
|
445
|
+
end
|
|
242
446
|
end
|
data/spec/krb5_spec.rb
CHANGED
|
@@ -23,7 +23,7 @@ RSpec.describe Kerberos::Krb5 do
|
|
|
23
23
|
let(:service) { 'kadmin/admin' }
|
|
24
24
|
|
|
25
25
|
it 'has the correct version constant' do
|
|
26
|
-
expect(Kerberos::Krb5::VERSION).to eq('0.
|
|
26
|
+
expect(Kerberos::Krb5::VERSION).to eq('0.3.0')
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
it 'accepts a block and yields itself' do
|
|
@@ -31,6 +31,31 @@ RSpec.describe Kerberos::Krb5 do
|
|
|
31
31
|
described_class.new { |k| expect(k).to be_a(described_class) }
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
describe 'constructor' do
|
|
35
|
+
it 'accepts a context keyword argument' do
|
|
36
|
+
context = Kerberos::Krb5::Context.new
|
|
37
|
+
k = described_class.new(context: context)
|
|
38
|
+
expect(k).to be_a(described_class)
|
|
39
|
+
expect(k.get_default_realm).to eq(@realm)
|
|
40
|
+
k.close
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'raises TypeError if context is not a Kerberos::Krb5::Context' do
|
|
44
|
+
expect { described_class.new(context: 'bad') }.to raise_error(TypeError, /context must be/)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'raises an error if the context is closed' do
|
|
48
|
+
context = Kerberos::Krb5::Context.new
|
|
49
|
+
context.close
|
|
50
|
+
expect { described_class.new(context: context) }.to raise_error(Kerberos::Krb5::Exception, /context is closed/)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'rejects positional option hashes and unknown keywords' do
|
|
54
|
+
expect { described_class.new({context: nil}) }.to raise_error(ArgumentError)
|
|
55
|
+
expect { described_class.new(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
34
59
|
describe '#get_default_realm' do
|
|
35
60
|
it 'responds to get_default_realm' do
|
|
36
61
|
expect(krb5).to respond_to(:get_default_realm)
|
|
@@ -68,53 +93,74 @@ RSpec.describe Kerberos::Krb5 do
|
|
|
68
93
|
expect(krb5).to respond_to(:verify_init_creds)
|
|
69
94
|
end
|
|
70
95
|
|
|
96
|
+
it 'requires get_init_creds_password arguments as keywords' do
|
|
97
|
+
expect { krb5.get_init_creds_password(user, 'changeme') }.to raise_error(ArgumentError)
|
|
98
|
+
expect {
|
|
99
|
+
krb5.get_init_creds_password(principal: user, password: 'changeme', unknown: true)
|
|
100
|
+
}.to raise_error(ArgumentError, /unknown keyword/)
|
|
101
|
+
end
|
|
102
|
+
|
|
71
103
|
it 'raises when no credentials have been acquired' do
|
|
72
104
|
expect { krb5.verify_init_creds }.to raise_error(Kerberos::Krb5::Exception)
|
|
73
105
|
end
|
|
74
106
|
|
|
75
107
|
it 'validates argument types' do
|
|
76
|
-
expect { krb5.verify_init_creds(true) }.to raise_error(TypeError)
|
|
77
|
-
expect { krb5.verify_init_creds(
|
|
78
|
-
expect { krb5.verify_init_creds(
|
|
108
|
+
expect { krb5.verify_init_creds(server: true) }.to raise_error(TypeError)
|
|
109
|
+
expect { krb5.verify_init_creds(keytab: true) }.to raise_error(TypeError)
|
|
110
|
+
expect { krb5.verify_init_creds(ccache: true) }.to raise_error(TypeError)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'accepts only documented keyword arguments' do
|
|
114
|
+
expect { krb5.verify_init_creds(nil) }.to raise_error(ArgumentError)
|
|
115
|
+
expect { krb5.verify_init_creds(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
|
|
79
116
|
end
|
|
80
117
|
|
|
81
118
|
it 'verifies credentials obtained via password' do
|
|
82
|
-
krb5.get_init_creds_password(user, 'changeme')
|
|
119
|
+
krb5.get_init_creds_password(principal: user, password: 'changeme')
|
|
83
120
|
expect(krb5.verify_init_creds).to be true
|
|
84
121
|
end
|
|
85
122
|
|
|
86
123
|
it 'accepts a server principal string' do
|
|
87
|
-
krb5.get_init_creds_password(user, 'changeme')
|
|
88
|
-
expect(krb5.verify_init_creds("kadmin/admin@#{@realm}")).to be true
|
|
124
|
+
krb5.get_init_creds_password(principal: user, password: 'changeme')
|
|
125
|
+
expect(krb5.verify_init_creds(server: "kadmin/admin@#{@realm}")).to be true
|
|
89
126
|
end
|
|
90
127
|
|
|
91
128
|
it 'accepts a Keytab object' do
|
|
92
|
-
krb5.get_init_creds_password(user, 'changeme')
|
|
129
|
+
krb5.get_init_creds_password(principal: user, password: 'changeme')
|
|
93
130
|
kt = Kerberos::Krb5::Keytab.new
|
|
94
|
-
expect(krb5.verify_init_creds(
|
|
131
|
+
expect(krb5.verify_init_creds(keytab: kt)).to be true
|
|
95
132
|
end
|
|
96
133
|
|
|
97
134
|
it 'stores additional credentials in provided CredentialsCache' do
|
|
98
135
|
ccache = Kerberos::Krb5::CredentialsCache.new
|
|
99
|
-
krb5.get_init_creds_password(user, 'changeme')
|
|
100
|
-
expect(krb5.verify_init_creds(
|
|
136
|
+
krb5.get_init_creds_password(principal: user, password: 'changeme')
|
|
137
|
+
expect(krb5.verify_init_creds(ccache: ccache)).to be true
|
|
101
138
|
expect(ccache.primary_principal).to be_a(String)
|
|
102
139
|
expect(ccache.primary_principal).to include('@')
|
|
103
140
|
end
|
|
104
141
|
|
|
105
142
|
it 'provides authenticate! which acquires and verifies (Zanarotti mitigation)' do
|
|
106
143
|
expect(krb5).to respond_to(:authenticate!)
|
|
107
|
-
expect(krb5.authenticate!(user, 'changeme')).to be true
|
|
144
|
+
expect(krb5.authenticate!(principal: user, password: 'changeme')).to be true
|
|
108
145
|
expect(krb5.verify_init_creds).to be true
|
|
109
146
|
end
|
|
110
147
|
|
|
111
148
|
it 'accepts an optional service argument' do
|
|
112
|
-
expect {
|
|
149
|
+
expect {
|
|
150
|
+
krb5.authenticate!(principal: user, password: 'changeme', service: 'kadmin/changepw')
|
|
151
|
+
}.not_to raise_error
|
|
113
152
|
expect(krb5.verify_init_creds).to be true
|
|
114
153
|
end
|
|
115
154
|
|
|
116
155
|
it 'validates argument types for authenticate!' do
|
|
117
|
-
expect { krb5.authenticate!(true, true) }.to raise_error(TypeError)
|
|
156
|
+
expect { krb5.authenticate!(principal: true, password: true) }.to raise_error(TypeError)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'requires authenticate! arguments as keywords' do
|
|
160
|
+
expect { krb5.authenticate!(user, 'changeme') }.to raise_error(ArgumentError)
|
|
161
|
+
expect {
|
|
162
|
+
krb5.authenticate!(principal: user, password: 'changeme', unknown: true)
|
|
163
|
+
}.to raise_error(ArgumentError, /unknown keyword/)
|
|
118
164
|
end
|
|
119
165
|
end
|
|
120
166
|
|
|
@@ -154,16 +200,16 @@ RSpec.describe Kerberos::Krb5 do
|
|
|
154
200
|
end
|
|
155
201
|
|
|
156
202
|
it 'changes the password successfully' do
|
|
157
|
-
krb5.get_init_creds_password(user, 'changeme')
|
|
203
|
+
krb5.get_init_creds_password(principal: user, password: 'changeme')
|
|
158
204
|
expect(krb5.change_password('changeme', 'Newpass99!')).to be true
|
|
159
205
|
# Verify we can authenticate with the new password
|
|
160
206
|
krb5_check = described_class.new
|
|
161
|
-
expect(krb5_check.get_init_creds_password(user, 'Newpass99!')).to be true
|
|
207
|
+
expect(krb5_check.get_init_creds_password(principal: user, password: 'Newpass99!')).to be true
|
|
162
208
|
krb5_check.close
|
|
163
209
|
end
|
|
164
210
|
|
|
165
211
|
it 'raises with a meaningful message when the old password is wrong' do
|
|
166
|
-
krb5.get_init_creds_password(user, 'changeme')
|
|
212
|
+
krb5.get_init_creds_password(principal: user, password: 'changeme')
|
|
167
213
|
expect {
|
|
168
214
|
krb5.change_password('wrongpass', 'Newpass99!')
|
|
169
215
|
}.to raise_error(Kerberos::Krb5::Exception, /krb5_(get_init_creds_password|change_password)/)
|
|
@@ -180,7 +226,7 @@ RSpec.describe Kerberos::Krb5 do
|
|
|
180
226
|
let(:compliant_pw) { 'Changeme1!' }
|
|
181
227
|
|
|
182
228
|
it 'raises an exception with the KDC rejection reason' do
|
|
183
|
-
krb5.get_init_creds_password(policy_user, compliant_pw)
|
|
229
|
+
krb5.get_init_creds_password(principal: policy_user, password: compliant_pw)
|
|
184
230
|
expect {
|
|
185
231
|
krb5.change_password(compliant_pw, 'a')
|
|
186
232
|
}.to raise_error(Kerberos::Krb5::Exception, /krb5_change_password/)
|
|
@@ -208,18 +254,83 @@ RSpec.describe Kerberos::Krb5 do
|
|
|
208
254
|
expect(krb5).to respond_to(:get_init_creds_keytab)
|
|
209
255
|
end
|
|
210
256
|
|
|
257
|
+
it 'accepts only documented keyword arguments' do
|
|
258
|
+
expect { krb5.get_init_creds_keytab(user) }.to raise_error(ArgumentError)
|
|
259
|
+
expect { krb5.get_init_creds_keytab(unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
|
|
260
|
+
end
|
|
261
|
+
|
|
211
262
|
it 'acquires credentials for a principal from a supplied keytab file' do
|
|
212
263
|
kt_name = "FILE:#{@kt_file}"
|
|
213
|
-
expect { krb5.get_init_creds_keytab(user, kt_name) }.not_to raise_error
|
|
264
|
+
expect { krb5.get_init_creds_keytab(principal: user, keytab: kt_name) }.not_to raise_error
|
|
214
265
|
expect(krb5.verify_init_creds).to be true
|
|
215
266
|
end
|
|
216
267
|
|
|
217
268
|
it 'accepts a CredentialsCache to receive credentials' do
|
|
218
269
|
ccache = Kerberos::Krb5::CredentialsCache.new
|
|
219
270
|
kt_name = "FILE:#{@kt_file}"
|
|
220
|
-
expect { krb5.get_init_creds_keytab(user, kt_name,
|
|
271
|
+
expect { krb5.get_init_creds_keytab(principal: user, keytab: kt_name, ccache: ccache) }.not_to raise_error
|
|
221
272
|
expect(ccache.primary_principal).to be_a(String)
|
|
222
273
|
expect(ccache.primary_principal).to include('@')
|
|
223
274
|
end
|
|
224
275
|
end
|
|
276
|
+
|
|
277
|
+
describe '.thread_safe?' do
|
|
278
|
+
it 'returns a boolean' do
|
|
279
|
+
result = described_class.thread_safe?
|
|
280
|
+
expect([true, false]).to include(result)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it 'is callable without an instance' do
|
|
284
|
+
expect(described_class).to respond_to(:thread_safe?)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
describe '#get_host_realm' do
|
|
289
|
+
it 'responds to get_host_realm' do
|
|
290
|
+
expect(krb5).to respond_to(:get_host_realm)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
it 'returns an array of realm strings' do
|
|
294
|
+
result = krb5.get_host_realm('localhost')
|
|
295
|
+
expect(result).to be_a(Array)
|
|
296
|
+
result.each { |r| expect(r).to be_a(String) }
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
it 'returns a non-empty array for a known hostname' do
|
|
300
|
+
result = krb5.get_host_realm('localhost')
|
|
301
|
+
expect(result).not_to be_empty
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'raises TypeError for non-string argument' do
|
|
305
|
+
expect { krb5.get_host_realm(123) }.to raise_error(TypeError)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
it 'raises ArgumentError when called without arguments' do
|
|
309
|
+
expect { krb5.get_host_realm }.to raise_error(ArgumentError)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
describe '#expand_hostname' do
|
|
314
|
+
it 'responds to expand_hostname' do
|
|
315
|
+
expect(krb5).to respond_to(:expand_hostname)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it 'returns a string' do
|
|
319
|
+
result = krb5.expand_hostname('localhost')
|
|
320
|
+
expect(result).to be_a(String)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
it 'returns a non-empty string' do
|
|
324
|
+
result = krb5.expand_hostname('localhost')
|
|
325
|
+
expect(result.size).to be > 0
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it 'raises TypeError for non-string argument' do
|
|
329
|
+
expect { krb5.expand_hostname(123) }.to raise_error(TypeError)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
it 'raises ArgumentError when called without arguments' do
|
|
333
|
+
expect { krb5.expand_hostname }.to raise_error(ArgumentError)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
225
336
|
end
|
data/spec/policy_spec.rb
CHANGED
|
@@ -21,6 +21,10 @@ RSpec.describe 'Kerberos::Kadm5::Policy', :kadm5 do
|
|
|
21
21
|
it 'must be present' do
|
|
22
22
|
expect { klass.new(max_life: 10000) }.to raise_error(ArgumentError)
|
|
23
23
|
end
|
|
24
|
+
it 'rejects positional option hashes and unknown keywords' do
|
|
25
|
+
expect { klass.new({name: 'test'}) }.to raise_error(ArgumentError)
|
|
26
|
+
expect { klass.new(name: 'test', unknown: true) }.to raise_error(ArgumentError, /unknown keyword/)
|
|
27
|
+
end
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
describe 'min_life' do
|