kbsecret 0.9.5 → 0.9.6
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/lib/kbsecret/cli.rb +8 -5
- data/lib/kbsecret/cli/kbsecret-cp +44 -0
- data/lib/kbsecret/cli/kbsecret-new +5 -1
- data/lib/kbsecret/cli/kbsecret-session +2 -2
- data/lib/kbsecret/exceptions.rb +26 -0
- data/lib/kbsecret/session.rb +38 -1
- data/lib/version.rb +1 -1
- metadata +15 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95af6343b31d058c770ddc7c9ee0a82c3a5f984c
|
4
|
+
data.tar.gz: 6d07e7ea5d2a10b0d2f3a46979d447c3785642ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b60c937f2a8bf3c9271299f602727059af992b0bc2510040ce057fbcd08beae232af1c84d8fa1def9a0e32c362511ec7e6906772a655ed1e8c756f1c83025ba2
|
7
|
+
data.tar.gz: 1f11837a4cdd43d7f4439a48544bbf54a7e46715ef069495cf106469bebb65634d5948ee2b370d7d9c75bae960506c2482656321b96031642d8549122db26095
|
data/lib/kbsecret/cli.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "colored2"
|
4
3
|
require "slop"
|
5
4
|
require "dreck"
|
6
5
|
require "abbrev"
|
6
|
+
require "pastel"
|
7
7
|
|
8
8
|
module KBSecret
|
9
9
|
# An encapsulation of useful methods for kbsecret's CLI.
|
@@ -13,6 +13,9 @@ module KBSecret
|
|
13
13
|
# Abbreviations for record types (e.g., `env` for `environment`).
|
14
14
|
TYPE_ALIASES = Hash.new { |_, k| k }.update(Abbrev.abbrev(Record.record_types)).freeze
|
15
15
|
|
16
|
+
# ANSI color objects for prettifying output.
|
17
|
+
GREEN, YELLOW, RED = ->(p) { [p.green, p.yellow, p.red].map(&:detach) }.call(Pastel.new)
|
18
|
+
|
16
19
|
# @return [Slop::Result, nil] the result of option parsing, if requested
|
17
20
|
# via {#slop}
|
18
21
|
attr_reader :opts
|
@@ -155,7 +158,7 @@ module KBSecret
|
|
155
158
|
# @return [void]
|
156
159
|
def info(msg)
|
157
160
|
return unless @opts.verbose?
|
158
|
-
STDERR.puts "#{"Info"
|
161
|
+
STDERR.puts "#{GREEN["Info"]}: #{msg}"
|
159
162
|
end
|
160
163
|
|
161
164
|
# Print an informational message via {#info} and exit successfully.
|
@@ -172,7 +175,7 @@ module KBSecret
|
|
172
175
|
# @return [void]
|
173
176
|
def warn(msg)
|
174
177
|
return if @opts.no_warn?
|
175
|
-
STDERR.puts "#{"Warning"
|
178
|
+
STDERR.puts "#{YELLOW["Warning"]}: #{msg}"
|
176
179
|
end
|
177
180
|
|
178
181
|
# Print an error message and terminate.
|
@@ -180,7 +183,7 @@ module KBSecret
|
|
180
183
|
# @return [void]
|
181
184
|
# @note This method does not return!
|
182
185
|
def die(msg)
|
183
|
-
pretty = "#{"Fatal"
|
186
|
+
pretty = "#{RED["Fatal"]}: #{msg}"
|
184
187
|
abort pretty
|
185
188
|
end
|
186
189
|
|
@@ -190,7 +193,7 @@ module KBSecret
|
|
190
193
|
# @return [void]
|
191
194
|
# @note This method does not return!
|
192
195
|
def die(msg)
|
193
|
-
pretty = "#{"Fatal"
|
196
|
+
pretty = "#{RED["Fatal"]}: #{msg}"
|
194
197
|
abort pretty
|
195
198
|
end
|
196
199
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "kbsecret"
|
5
|
+
include KBSecret
|
6
|
+
|
7
|
+
cmd = CLI.create do |c|
|
8
|
+
c.slop do |o|
|
9
|
+
o.banner = <<~HELP
|
10
|
+
Usage:
|
11
|
+
kbsecret cp [options] <source> <destination> <record [record ...]>
|
12
|
+
HELP
|
13
|
+
|
14
|
+
o.bool "-f", "--force", "force copying (ignore overwrites)"
|
15
|
+
o.bool "-m", "--move", "delete the record after copying"
|
16
|
+
end
|
17
|
+
|
18
|
+
c.dreck do
|
19
|
+
string :src_sess
|
20
|
+
string :dst_sess
|
21
|
+
list :string, :labels
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
cmd.guard do
|
26
|
+
src_sess = Session.new label: cmd.args[:src_sess]
|
27
|
+
dst_sess = Session.new label: cmd.args[:dst_sess]
|
28
|
+
|
29
|
+
selected_records = src_sess.records.select { |r| cmd.args[:labels].include?(r.label) }
|
30
|
+
cmd.die "No such record(s)." if selected_records.empty?
|
31
|
+
|
32
|
+
overlaps = dst_sess.record_labels & selected_records.map(&:label)
|
33
|
+
|
34
|
+
# the code below actually handles the overwriting if necessary, but we fail early here for
|
35
|
+
# friendliness and to avoid half-copying the selected records
|
36
|
+
unless overlaps.empty? || cmd.opts.force?
|
37
|
+
cmd.die "Refusing to overwrite existing record(s) without --force."
|
38
|
+
end
|
39
|
+
|
40
|
+
selected_records.each do |record|
|
41
|
+
dst_sess.import_record(record, overwrite: cmd.opts.force?)
|
42
|
+
src_sess.delete_record(label) if cmd.opts.move?
|
43
|
+
end
|
44
|
+
end
|
@@ -38,6 +38,8 @@ type = cmd.args[:type]
|
|
38
38
|
label = cmd.args[:label]
|
39
39
|
resolved_type = CLI::TYPE_ALIASES[type]
|
40
40
|
|
41
|
+
# the code below actually handles the overwriting if necessary, but we fail early here
|
42
|
+
# for friendliness and to avoid prompting the user for input unnecessarily
|
41
43
|
if cmd.session.record?(label) && !cmd.opts.force?
|
42
44
|
cmd.die "Refusing to overwrite an existing record without --force."
|
43
45
|
end
|
@@ -61,4 +63,6 @@ fields = if cmd.opts.terse?
|
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
|
-
cmd.guard
|
66
|
+
cmd.guard do
|
67
|
+
cmd.session.add_record resolved_type, label, *fields, overwrite: cmd.opts.force?
|
68
|
+
end
|
@@ -11,9 +11,9 @@ def new_session(label, cmd)
|
|
11
11
|
end
|
12
12
|
|
13
13
|
if cmd.opts[:team]
|
14
|
-
teams = Keybase::Local::Team.
|
14
|
+
teams = Keybase::Local::Team.list_self_memberships.teams
|
15
15
|
|
16
|
-
unless teams.any? { |t| t
|
16
|
+
unless teams.any? { |t| t.fq_name == cmd.opts[:team] }
|
17
17
|
cmd.die "No such team (either nonexistent or non-member)."
|
18
18
|
end
|
19
19
|
|
data/lib/kbsecret/exceptions.rb
CHANGED
@@ -9,6 +9,7 @@ module KBSecret
|
|
9
9
|
|
10
10
|
# Raised during record loading if a particular file can't be loaded
|
11
11
|
class RecordLoadError < KBSecretError
|
12
|
+
# @param path [String] the path to the record
|
12
13
|
def initialize(path)
|
13
14
|
base = File.basename(path)
|
14
15
|
super "Failed to load record in file: '#{base}'"
|
@@ -17,6 +18,7 @@ module KBSecret
|
|
17
18
|
|
18
19
|
# Raised during record creation if an unknown record type is requested.
|
19
20
|
class RecordTypeUnknownError < KBSecretError
|
21
|
+
# @param type [String, Symbol] the record type
|
20
22
|
def initialize(type)
|
21
23
|
super "Unknown record type: '#{type}'"
|
22
24
|
end
|
@@ -24,13 +26,25 @@ module KBSecret
|
|
24
26
|
|
25
27
|
# Raised during record creation if too many/few arguments are given.
|
26
28
|
class RecordCreationArityError < KBSecretError
|
29
|
+
# @param exp [Integer] the number of expected arguments
|
30
|
+
# @param act [Integer] the number of actual arguments
|
27
31
|
def initialize(exp, act)
|
28
32
|
super "Needed #{exp} arguments for this record, got #{act}"
|
29
33
|
end
|
30
34
|
end
|
31
35
|
|
36
|
+
# Raised when record creation or import would cause an unintended overwrite.
|
37
|
+
class RecordOverwriteError < KBSecretError
|
38
|
+
# @param session [Session] the session being modified
|
39
|
+
# @param label [String] the label being overwritten in the session
|
40
|
+
def initialize(session, label)
|
41
|
+
super "Record '#{label}' already exists in '#{session.label}'"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
32
45
|
# Raised during session load if an error occurs.
|
33
46
|
class SessionLoadError < KBSecretError
|
47
|
+
# @param msg [String] the error message
|
34
48
|
def initialize(msg)
|
35
49
|
super "Session loading failure: #{msg}"
|
36
50
|
end
|
@@ -38,13 +52,23 @@ module KBSecret
|
|
38
52
|
|
39
53
|
# Raised during session lookup if an unknown session is requested.
|
40
54
|
class SessionUnknownError < KBSecretError
|
55
|
+
# @param sess [String, Symbol] the label of the session
|
41
56
|
def initialize(sess)
|
42
57
|
super "Unknown session: '#{sess}'"
|
43
58
|
end
|
44
59
|
end
|
45
60
|
|
61
|
+
# Raised during record import if the source is the same as the destination.
|
62
|
+
class SessionImportError < KBSecretError
|
63
|
+
# @param session [Session] the session being imported into
|
64
|
+
def initialize(session)
|
65
|
+
super "Session '#{session.label}' cannot import records from itself"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
46
69
|
# Raised during generator lookup if an unknown profile is requested.
|
47
70
|
class GeneratorUnknownError < KBSecretError
|
71
|
+
# @param gen [String, Symbol] the label of the generator
|
48
72
|
def initialize(gen)
|
49
73
|
super "Unknown generator profile: '#{gen}'"
|
50
74
|
end
|
@@ -52,6 +76,7 @@ module KBSecret
|
|
52
76
|
|
53
77
|
# Raised during generator creation if an unknown generator format is requested.
|
54
78
|
class GeneratorFormatError < KBSecretError
|
79
|
+
# @param fmt [String, Symbol] the format of the generator
|
55
80
|
def initialize(fmt)
|
56
81
|
super "Unknown generator format: '#{fmt}'"
|
57
82
|
end
|
@@ -59,6 +84,7 @@ module KBSecret
|
|
59
84
|
|
60
85
|
# Raised during generator creation if a non-positive generator length is requested.
|
61
86
|
class GeneratorLengthError < KBSecretError
|
87
|
+
# @param length [Integer] the length of the generator
|
62
88
|
def initialize(length)
|
63
89
|
super "Bad secret generator length (#{length}, must be positive)"
|
64
90
|
end
|
data/lib/kbsecret/session.rb
CHANGED
@@ -62,17 +62,25 @@ module KBSecret
|
|
62
62
|
# @param type [String, Symbol] the type of record (see {Record.record_types})
|
63
63
|
# @param label [String, Symbol] the new record's label
|
64
64
|
# @param args [Array<String>] the record-type specific arguments
|
65
|
+
# @param overwrite [Boolean] whether or not to overwrite an existing record if necessary
|
65
66
|
# @return [void]
|
66
67
|
# @raise [Exceptions::UnknownRecordTypeError] if the requested type does not exist
|
67
68
|
# in {Record.record_types}
|
68
69
|
# @raise [Exceptions::RecordCreationArityError] if the number of specified record
|
69
70
|
# arguments does not match the record type's constructor
|
70
|
-
|
71
|
+
# @raise [Exceptions::RecordOverwriteError] if the record addition would cause an
|
72
|
+
# unchecked overwrite
|
73
|
+
def add_record(type, label, *args, overwrite: false)
|
71
74
|
klass = Record.class_for(type.to_sym)
|
72
75
|
arity = klass.external_fields.length
|
73
76
|
|
74
77
|
raise Exceptions::RecordCreationArityError.new(arity, args.size) unless arity == args.size
|
75
78
|
|
79
|
+
if record? label
|
80
|
+
raise Exceptions::RecordOverwriteError.new(self, label) unless overwrite
|
81
|
+
delete_record label
|
82
|
+
end
|
83
|
+
|
76
84
|
body = klass.external_fields.zip(args).to_h
|
77
85
|
record = klass.new(self, label.to_s, **body)
|
78
86
|
|
@@ -80,6 +88,26 @@ module KBSecret
|
|
80
88
|
record.sync!
|
81
89
|
end
|
82
90
|
|
91
|
+
# Import an existing record from another session.
|
92
|
+
# @param record [Record] the record to import
|
93
|
+
# @param overwrite [Boolean] whether or not to overwrite an existing record if necessary
|
94
|
+
# @return [void]
|
95
|
+
# @raise [Exceptions::SessionImportError] if the record's source session is our session
|
96
|
+
# @raise [Exceptions::RecordOverwriteError] if record import would cause an unchecked overwrite
|
97
|
+
def import_record(record, overwrite: false)
|
98
|
+
raise Exceptions::SessionImportError, self if self == record.session
|
99
|
+
|
100
|
+
if record? record.label
|
101
|
+
raise Exceptions::RecordOverwriteError.new(self, record.label) unless overwrite
|
102
|
+
delete_record record.label
|
103
|
+
end
|
104
|
+
|
105
|
+
klass = record.class
|
106
|
+
imported_record = klass.load!(self, record.to_h)
|
107
|
+
records << imported_record
|
108
|
+
imported_record.sync!
|
109
|
+
end
|
110
|
+
|
83
111
|
# Delete a record from the session, if it exists. Does nothing if
|
84
112
|
# no such record can be found.
|
85
113
|
# @param label [String, Symbol] the label of the record to delete
|
@@ -113,6 +141,15 @@ module KBSecret
|
|
113
141
|
Dir[File.join(path, "*.json")]
|
114
142
|
end
|
115
143
|
|
144
|
+
# Compare two sessions for equality.
|
145
|
+
# @param other [Session] the other object to compare against
|
146
|
+
# @return [Boolean] whether or not the two sessions are equal
|
147
|
+
# @note The equality of two sessions is determined *solely* by them having the same
|
148
|
+
# session directory, *not* by having the same label *or* the same in-memory state.
|
149
|
+
def ==(other)
|
150
|
+
other.class == self.class && other.path == path
|
151
|
+
end
|
152
|
+
|
116
153
|
# Load all records associated with the session.
|
117
154
|
# @return [Array<Record::Abstract>] all records in the session
|
118
155
|
# @api private
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kbsecret
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Woodruff
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-11-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aruba
|
@@ -137,61 +137,61 @@ dependencies:
|
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '1.1'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
140
|
+
name: dreck
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
143
|
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
145
|
+
version: 0.2.1
|
146
146
|
type: :runtime
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
152
|
+
version: 0.2.1
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
154
|
+
name: inih
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version:
|
159
|
+
version: '1.1'
|
160
160
|
type: :runtime
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
|
-
version:
|
166
|
+
version: '1.1'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
|
-
name:
|
168
|
+
name: keybase-unofficial
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
170
170
|
requirements:
|
171
171
|
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: '1.
|
173
|
+
version: '1.0'
|
174
174
|
type: :runtime
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version: '1.
|
180
|
+
version: '1.0'
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
|
-
name:
|
182
|
+
name: pastel
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
184
184
|
requirements:
|
185
185
|
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: '0.
|
187
|
+
version: '0.7'
|
188
188
|
type: :runtime
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: '0.
|
194
|
+
version: '0.7'
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
196
|
name: slop
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -233,6 +233,7 @@ files:
|
|
233
233
|
- bin/kbsecret
|
234
234
|
- lib/kbsecret.rb
|
235
235
|
- lib/kbsecret/cli.rb
|
236
|
+
- lib/kbsecret/cli/kbsecret-cp
|
236
237
|
- lib/kbsecret/cli/kbsecret-dump-fields
|
237
238
|
- lib/kbsecret/cli/kbsecret-env
|
238
239
|
- lib/kbsecret/cli/kbsecret-generator
|