2pass 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +7 -3
- data/bin/2pass +29 -17
- data/lib/2pass/version.rb +1 -1
- data/lib/2pass.rb +94 -4
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 135196a393e121fb8b71d5295fdedc19f3bccb24885ce81cf630be59d2b7c64e
|
|
4
|
+
data.tar.gz: 7f5684e81780732115fcf6b9d5a071cfd1e8a05eaa48246e35f7fd3841a702ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e1c997e62173b57c596adf21c9636bfd7ed3417a5f0611d497db1caa486a18e113ad37b234bec8b3334fcea282c5f99f2ec9dbd1b4e328222b1439f281629586
|
|
7
|
+
data.tar.gz: 9697b86fe839dc5d29f7786ab2e278986fbdb64bd2fc89e142f6e8bc812f919a66d3b57afcb97e71e099d806cc1411c56c12f5117facf6174ee780de71812b14
|
data/README.md
CHANGED
|
@@ -39,9 +39,13 @@ Then build the gem and install it.
|
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
41
|
2pass -h
|
|
42
|
-
2pass get <vault_name> <id>
|
|
43
|
-
2pass list <vault_name>
|
|
44
|
-
2pass
|
|
42
|
+
2pass get <vault_name> <id> [env]
|
|
43
|
+
2pass list <vault_name> [env]
|
|
44
|
+
2pass list <vault_name> [env] --json
|
|
45
|
+
2pass list <vault_name> [env] --dotenv
|
|
46
|
+
2pass add <vault_name> <id> <value> [env]
|
|
47
|
+
2pass link <vault_name> <target_path> [env]
|
|
48
|
+
2pass update
|
|
45
49
|
```
|
|
46
50
|
You can also pass an optional environment to most commands:
|
|
47
51
|
`2pass get myproject mykey production`
|
data/bin/2pass
CHANGED
|
@@ -5,13 +5,14 @@ require_relative "../lib/2pass"
|
|
|
5
5
|
def help_message
|
|
6
6
|
puts <<~HELP
|
|
7
7
|
Usage:
|
|
8
|
-
2pass add <vault_name> <id> <value>
|
|
9
|
-
2pass get <vault_name> <id>
|
|
10
|
-
2pass list <vault_name>
|
|
11
|
-
2pass list <vault_name>
|
|
12
|
-
2pass list <vault_name>
|
|
13
|
-
2pass link <vault_name> <target_path>
|
|
14
|
-
2pass -
|
|
8
|
+
2pass add <vault_name> <id> <value> [env] - Add new secret. env is optional
|
|
9
|
+
2pass get <vault_name> <id> [env] - Get content by ID from the specified vault. env is optional
|
|
10
|
+
2pass list <vault_name> [env] - List the content of the specified vault as key=value pairs. env is optional
|
|
11
|
+
2pass list <vault_name> [env] --json - List the content of the specified vault as JSON. env is optional
|
|
12
|
+
2pass list <vault_name> [env] --dotenv - List the content of the specified vault as key=value pairs. env is optional
|
|
13
|
+
2pass link <vault_name> <target_path> [env] - Create a symlink for an existing vault. Useful when the vault is stored in a synced place (iCloud, Dropbox, etc.). env is optional
|
|
14
|
+
2pass update - Update 2pass to the latest RubyGems release
|
|
15
|
+
2pass -h - Display this help message
|
|
15
16
|
HELP
|
|
16
17
|
end
|
|
17
18
|
|
|
@@ -43,31 +44,35 @@ if options[:version]
|
|
|
43
44
|
exit
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
if ARGV.
|
|
47
|
+
if ARGV.empty?
|
|
47
48
|
help_message
|
|
48
49
|
exit(1)
|
|
49
50
|
end
|
|
50
51
|
|
|
51
|
-
command,
|
|
52
|
+
command, *args = ARGV
|
|
52
53
|
|
|
53
54
|
begin
|
|
54
55
|
case command&.to_sym
|
|
55
56
|
when :get
|
|
56
|
-
if args.length <
|
|
57
|
+
if args.length < 2 || args.length > 3
|
|
57
58
|
help_message
|
|
58
59
|
exit(1)
|
|
59
60
|
end
|
|
60
|
-
id, env = args[0], args[1]
|
|
61
|
+
vault_name, id, env = args[0], args[1], args[2]
|
|
61
62
|
puts TwoPass.get_secret(vault_name, id, env: env)
|
|
62
63
|
when :add
|
|
63
|
-
if args.length
|
|
64
|
+
if args.length < 3 || args.length > 4
|
|
64
65
|
help_message
|
|
65
66
|
exit(1)
|
|
66
67
|
end
|
|
67
|
-
id, value, env = args[0], args[1], args[2]
|
|
68
|
+
vault_name, id, value, env = args[0], args[1], args[2], args[3]
|
|
68
69
|
TwoPass.add_secret(vault_name, id, value, env: env)
|
|
69
70
|
when :list
|
|
70
|
-
|
|
71
|
+
if args.length < 1 || args.length > 2
|
|
72
|
+
help_message
|
|
73
|
+
exit(1)
|
|
74
|
+
end
|
|
75
|
+
vault_name, env = args[0], args[1]
|
|
71
76
|
if options[:format] == :json
|
|
72
77
|
puts TwoPass.list_content(vault_name, env: env, format: :json)
|
|
73
78
|
elsif options[:format] == :dotenv
|
|
@@ -75,18 +80,25 @@ begin
|
|
|
75
80
|
else
|
|
76
81
|
puts TwoPass.list_content(vault_name, env: env)
|
|
77
82
|
end
|
|
78
|
-
exit(
|
|
83
|
+
exit(0)
|
|
79
84
|
when :link
|
|
80
|
-
if args.length <
|
|
85
|
+
if args.length < 2 || args.length > 3
|
|
81
86
|
help_message
|
|
82
87
|
exit(1)
|
|
83
88
|
end
|
|
84
|
-
target_path, env = args[0], args[1]
|
|
89
|
+
vault_name, target_path, env = args[0], args[1], args[2]
|
|
85
90
|
TwoPass.create_symlink(vault_name, target_path, env: env)
|
|
91
|
+
when :update
|
|
92
|
+
if args.any?
|
|
93
|
+
help_message
|
|
94
|
+
exit(1)
|
|
95
|
+
end
|
|
96
|
+
puts TwoPass.update_cli
|
|
86
97
|
when :help
|
|
87
98
|
help_message
|
|
88
99
|
else
|
|
89
100
|
help_message
|
|
101
|
+
exit(1)
|
|
90
102
|
end
|
|
91
103
|
rescue => e
|
|
92
104
|
STDERR.puts e.message
|
data/lib/2pass/version.rb
CHANGED
data/lib/2pass.rb
CHANGED
|
@@ -4,6 +4,9 @@ require "optparse"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "yaml"
|
|
6
6
|
require "fileutils"
|
|
7
|
+
require "net/http"
|
|
8
|
+
require "uri"
|
|
9
|
+
require "rubygems/version"
|
|
7
10
|
require "terminal-table"
|
|
8
11
|
|
|
9
12
|
require_relative "2pass/version"
|
|
@@ -15,13 +18,32 @@ module TwoPass
|
|
|
15
18
|
def create_symlink(vault_name, target_path, env: nil)
|
|
16
19
|
FileUtils.mkdir_p(VAULT_DIR)
|
|
17
20
|
symlink_path = vault_file_path(vault_name, env: env)
|
|
21
|
+
expanded_target_path = File.expand_path(target_path)
|
|
22
|
+
|
|
23
|
+
unless File.exist?(expanded_target_path)
|
|
24
|
+
raise "Target vault file does not exist: #{expanded_target_path}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
unless File.readable?(expanded_target_path)
|
|
28
|
+
raise "Target vault file is not readable: #{expanded_target_path}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if File.symlink?(symlink_path)
|
|
32
|
+
existing_target = File.expand_path(File.readlink(symlink_path), File.dirname(symlink_path))
|
|
33
|
+
if existing_target == expanded_target_path
|
|
34
|
+
puts "Symlink already exists: #{symlink_path} -> #{expanded_target_path}"
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
raise "A different symlink already exists at #{symlink_path}: #{existing_target}"
|
|
39
|
+
end
|
|
18
40
|
|
|
19
41
|
if File.exist?(symlink_path)
|
|
20
|
-
|
|
21
|
-
else
|
|
22
|
-
File.symlink(target_path, symlink_path)
|
|
23
|
-
puts "Created symlink: #{symlink_path} -> #{target_path}"
|
|
42
|
+
raise "A regular file already exists at #{symlink_path}. Remove it before creating a symlink."
|
|
24
43
|
end
|
|
44
|
+
|
|
45
|
+
File.symlink(expanded_target_path, symlink_path)
|
|
46
|
+
puts "Created symlink: #{symlink_path} -> #{expanded_target_path}"
|
|
25
47
|
end
|
|
26
48
|
|
|
27
49
|
def save_vault(vault_name, data, env: nil)
|
|
@@ -81,6 +103,22 @@ module TwoPass
|
|
|
81
103
|
end
|
|
82
104
|
end
|
|
83
105
|
|
|
106
|
+
def update_cli
|
|
107
|
+
latest_version = fetch_latest_version
|
|
108
|
+
current = Gem::Version.new(VERSION)
|
|
109
|
+
latest = Gem::Version.new(latest_version)
|
|
110
|
+
|
|
111
|
+
if current >= latest
|
|
112
|
+
return "2pass is already up to date (#{VERSION})."
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
unless run_update_command
|
|
116
|
+
raise "Failed to update 2pass. Try running: #{update_command.join(' ')}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
"Updated 2pass from #{VERSION} to #{latest_version}. Restart your shell session if needed."
|
|
120
|
+
end
|
|
121
|
+
|
|
84
122
|
private
|
|
85
123
|
|
|
86
124
|
def load_vault(vault_name, env: nil)
|
|
@@ -88,6 +126,8 @@ module TwoPass
|
|
|
88
126
|
return [] unless File.exist?(file_path)
|
|
89
127
|
|
|
90
128
|
YAML.load_file(file_path, symbolize_names: true) || []
|
|
129
|
+
rescue Errno::EACCES, Errno::EPERM => e
|
|
130
|
+
raise permission_error_message(file_path, e)
|
|
91
131
|
rescue Psych::SyntaxError => e
|
|
92
132
|
STDERR.puts "Error parsing YAML file: #{e.message}"
|
|
93
133
|
[]
|
|
@@ -99,5 +139,55 @@ module TwoPass
|
|
|
99
139
|
[vault_name, env].compact.join(".")
|
|
100
140
|
].join("/") + ".yml"
|
|
101
141
|
end
|
|
142
|
+
|
|
143
|
+
def permission_error_message(file_path, error)
|
|
144
|
+
message = +"Unable to read vault file: #{file_path}\n#{error.class}: #{error.message}"
|
|
145
|
+
symlink_target = nil
|
|
146
|
+
|
|
147
|
+
if File.symlink?(file_path)
|
|
148
|
+
symlink_target = File.expand_path(File.readlink(file_path), File.dirname(file_path))
|
|
149
|
+
message << "\nSymlink target: #{symlink_target}"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
path_to_check = symlink_target || file_path
|
|
153
|
+
if path_to_check.include?("/Library/Mobile Documents/")
|
|
154
|
+
message << "\nThe vault points to iCloud Drive. macOS may be blocking your terminal app."
|
|
155
|
+
message << "\nCheck System Settings > Privacy & Security > Files and Folders"
|
|
156
|
+
message << "\nfor your terminal app and enable iCloud Drive (and Full Disk Access if needed)."
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
message
|
|
160
|
+
rescue StandardError
|
|
161
|
+
"Unable to read vault file: #{file_path}\n#{error.class}: #{error.message}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def fetch_latest_version
|
|
165
|
+
uri = URI("https://rubygems.org/api/v1/gems/2pass.json")
|
|
166
|
+
response = Net::HTTP.start(
|
|
167
|
+
uri.host,
|
|
168
|
+
uri.port,
|
|
169
|
+
use_ssl: true,
|
|
170
|
+
open_timeout: 5,
|
|
171
|
+
read_timeout: 5
|
|
172
|
+
) do |http|
|
|
173
|
+
http.get(uri.request_uri)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
177
|
+
raise "Unable to check latest version (HTTP #{response.code})"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
JSON.parse(response.body).fetch("version")
|
|
181
|
+
rescue JSON::ParserError, KeyError => e
|
|
182
|
+
raise "Unable to parse latest version from RubyGems: #{e.message}"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def run_update_command
|
|
186
|
+
system(*update_command)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def update_command
|
|
190
|
+
[Gem.ruby, "-S", "gem", "update", "2pass"]
|
|
191
|
+
end
|
|
102
192
|
end
|
|
103
193
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: 2pass
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Olivier
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: bundler
|
|
@@ -105,7 +104,6 @@ homepage: https://2pass.xyz
|
|
|
105
104
|
licenses:
|
|
106
105
|
- MIT
|
|
107
106
|
metadata: {}
|
|
108
|
-
post_install_message:
|
|
109
107
|
rdoc_options: []
|
|
110
108
|
require_paths:
|
|
111
109
|
- lib
|
|
@@ -120,8 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
120
118
|
- !ruby/object:Gem::Version
|
|
121
119
|
version: '0'
|
|
122
120
|
requirements: []
|
|
123
|
-
rubygems_version:
|
|
124
|
-
signing_key:
|
|
121
|
+
rubygems_version: 4.0.3
|
|
125
122
|
specification_version: 4
|
|
126
123
|
summary: A CLI app for managing secrets
|
|
127
124
|
test_files: []
|