ironclad 0.4.0 → 0.5.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 +9 -6
- data/lib/generators/ironclad/install_generator.rb +1 -0
- data/lib/ironclad/cache/keychain.rb +3 -1
- data/lib/ironclad/cache/keyctl.rb +34 -3
- data/lib/ironclad/cache/null.rb +1 -1
- data/lib/ironclad/cache.rb +4 -1
- data/lib/ironclad/cli.rb +41 -5
- data/lib/ironclad/key_store.rb +6 -1
- data/lib/ironclad/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a87bf5f6911710634655b6619b79f20feec36c8c91033e4174b84f6d51eb643
|
|
4
|
+
data.tar.gz: a594caae3ea3825b7fbd5984301ad90298b163174e7ec1378274c117e1c7c05e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e554621354d643f0ca215b07194774d2e634ad3a6d0730029ad492f10545de281c755535b74e944f44a0926727deeb9ea392dc9618acc93404bba1af74cc4b5
|
|
7
|
+
data.tar.gz: 79fb39bfd14bcd75f312193f60484cf970ea68fea9a1ce420302955f5062a671a99c676aa2b5c9b33994a0b0818b8b5a78dd37ed1cd136340d84e1007b058933
|
data/README.md
CHANGED
|
@@ -25,8 +25,9 @@ ships:
|
|
|
25
25
|
2. On a miss, read it from 1Password (`op read`) and seed the cache.
|
|
26
26
|
|
|
27
27
|
The cache never expires. After rotating a key, refresh it with
|
|
28
|
-
`bin/ironclad <env
|
|
29
|
-
|
|
28
|
+
`bin/ironclad refresh <env>`, or refresh every environment at once with
|
|
29
|
+
`bin/ironclad refresh --all`. Platforms with no supported keystore simply fetch
|
|
30
|
+
from 1Password every call.
|
|
30
31
|
|
|
31
32
|
## Requirements
|
|
32
33
|
|
|
@@ -81,10 +82,12 @@ keys:
|
|
|
81
82
|
### CLI
|
|
82
83
|
|
|
83
84
|
```sh
|
|
84
|
-
bin/ironclad
|
|
85
|
-
bin/ironclad production
|
|
86
|
-
bin/ironclad
|
|
87
|
-
bin/ironclad
|
|
85
|
+
bin/ironclad # print the development key
|
|
86
|
+
bin/ironclad production # print the production key
|
|
87
|
+
bin/ironclad refresh # re-cache the development key after a rotation
|
|
88
|
+
bin/ironclad refresh production # re-cache production's key
|
|
89
|
+
bin/ironclad refresh --all # re-cache every configured environment's key
|
|
90
|
+
bin/ironclad edit staging # edit staging credentials in your editor
|
|
88
91
|
```
|
|
89
92
|
|
|
90
93
|
### Capistrano
|
|
@@ -22,11 +22,13 @@ module Ironclad
|
|
|
22
22
|
def write(name, key)
|
|
23
23
|
# The key passes via argv (briefly visible to the same user's `ps`); the
|
|
24
24
|
# security tool has no stdin input mode. Acceptable for a local cache.
|
|
25
|
+
# system returns nil when `security` cannot be executed at all, which is
|
|
26
|
+
# a failure to cache just as much as a non-zero exit is.
|
|
25
27
|
system(
|
|
26
28
|
'security', 'add-generic-password', '-U',
|
|
27
29
|
'-a', @account, '-s', name, '-w', key,
|
|
28
30
|
out: File::NULL, err: File::NULL
|
|
29
|
-
)
|
|
31
|
+
) || false
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
end
|
|
@@ -15,15 +15,46 @@ module Ironclad
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def read(name)
|
|
18
|
-
id, status = Open3.
|
|
18
|
+
id, _error, status = Open3.capture3(
|
|
19
|
+
'keyctl', 'search', '@u', 'user', name
|
|
20
|
+
)
|
|
19
21
|
return unless status.success?
|
|
20
22
|
|
|
21
|
-
out, status = Open3.
|
|
23
|
+
out, _error, status = Open3.capture3('keyctl', 'pipe', id.chomp)
|
|
22
24
|
status.success? ? out : nil
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def write(name, key)
|
|
26
|
-
|
|
28
|
+
# `keyctl padd` creates the key with the kernel's default permissions
|
|
29
|
+
# and offers no way to set them at creation, so changing them afterwards
|
|
30
|
+
# needs setattr, which only a possessor has. @u grants the owning user
|
|
31
|
+
# link permission, so linking it into @s first guarantees possession.
|
|
32
|
+
Open3.capture3('keyctl', 'link', '@u', '@s')
|
|
33
|
+
|
|
34
|
+
id, error, status = Open3.capture3(
|
|
35
|
+
'keyctl', 'padd', 'user', name, '@u', stdin_data: key
|
|
36
|
+
)
|
|
37
|
+
return warn_failure(error, "cache #{name}") unless status.success?
|
|
38
|
+
|
|
39
|
+
# possessor 0x3f (all), user 0x2f (view, read, write, search, setattr),
|
|
40
|
+
# group and other none. A caller that does not possess the key gets only
|
|
41
|
+
# the user byte, which has to cover every operation performed here.
|
|
42
|
+
_out, error, status = Open3.capture3(
|
|
43
|
+
'keyctl', 'setperm', id.chomp, '0x3f2f0000'
|
|
44
|
+
)
|
|
45
|
+
return warn_failure(error, "set permissions on #{name}") unless status.success?
|
|
46
|
+
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def warn_failure(error, action)
|
|
53
|
+
detail = error.strip
|
|
54
|
+
message = "ironclad: could not #{action} in the Linux keyring"
|
|
55
|
+
message += ": #{detail}" unless detail.empty?
|
|
56
|
+
warn message
|
|
57
|
+
false
|
|
27
58
|
end
|
|
28
59
|
end
|
|
29
60
|
end
|
data/lib/ironclad/cache/null.rb
CHANGED
data/lib/ironclad/cache.rb
CHANGED
|
@@ -6,7 +6,10 @@ require_relative 'cache/keyctl'
|
|
|
6
6
|
require_relative 'cache/null'
|
|
7
7
|
|
|
8
8
|
module Ironclad
|
|
9
|
-
# Selects the OS keystore backend for the current platform.
|
|
9
|
+
# Selects the OS keystore backend for the current platform. A backend responds
|
|
10
|
+
# to #read(name), returning the cached string or nil, and #write(name, key),
|
|
11
|
+
# returning true when the key is cached and false when it is not. A backend
|
|
12
|
+
# with nowhere to store keys reports true: nothing to do is not a failure.
|
|
10
13
|
module Cache
|
|
11
14
|
module_function
|
|
12
15
|
|
data/lib/ironclad/cli.rb
CHANGED
|
@@ -6,7 +6,9 @@ module Ironclad
|
|
|
6
6
|
# Command-line entry point. Dependency-free arg parsing keeps boot cheap and
|
|
7
7
|
# avoids loading Rails just to print a key.
|
|
8
8
|
#
|
|
9
|
-
# ironclad [env]
|
|
9
|
+
# ironclad [env] print the credentials key (default env)
|
|
10
|
+
# ironclad refresh [env] re-cache one environment's key
|
|
11
|
+
# ironclad refresh --all re-cache every environment's key
|
|
10
12
|
# ironclad edit [env] edit Rails credentials for env
|
|
11
13
|
# ironclad diff <file> git textconv: decrypt a credentials file
|
|
12
14
|
class CLI
|
|
@@ -20,6 +22,9 @@ module Ironclad
|
|
|
20
22
|
|
|
21
23
|
def run
|
|
22
24
|
case @argv.first
|
|
25
|
+
when 'refresh'
|
|
26
|
+
@argv.shift
|
|
27
|
+
return refresh
|
|
23
28
|
when 'edit'
|
|
24
29
|
@argv.shift
|
|
25
30
|
edit(@argv.shift || 'default')
|
|
@@ -40,10 +45,39 @@ module Ironclad
|
|
|
40
45
|
private
|
|
41
46
|
|
|
42
47
|
def print_key
|
|
43
|
-
refresh = @argv.delete('--refresh') ? true : false
|
|
44
48
|
env = @argv.shift || 'default'
|
|
45
49
|
validate_env!(env)
|
|
46
|
-
puts Ironclad.key(env
|
|
50
|
+
puts Ironclad.key(env)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def refresh
|
|
54
|
+
return refresh_all if @argv.delete('--all')
|
|
55
|
+
|
|
56
|
+
env = @argv.shift || 'default'
|
|
57
|
+
validate_env!(env)
|
|
58
|
+
refresh_key(env) ? 0 : 1
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def refresh_all
|
|
62
|
+
environments = Ironclad.config.environments
|
|
63
|
+
if environments.empty?
|
|
64
|
+
raise Error, 'No environments are configured in config/ironclad.yml.'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
failed = environments.reject { |env| refresh_key(env) }
|
|
68
|
+
return 0 if failed.empty?
|
|
69
|
+
|
|
70
|
+
warn "Failed to refresh: #{failed.join(', ')}."
|
|
71
|
+
1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def refresh_key(env)
|
|
75
|
+
Ironclad.key(env, refresh: true)
|
|
76
|
+
puts "#{env}: refreshed"
|
|
77
|
+
true
|
|
78
|
+
rescue Error => e
|
|
79
|
+
warn "#{env}: #{e.message}"
|
|
80
|
+
false
|
|
47
81
|
end
|
|
48
82
|
|
|
49
83
|
def edit(env)
|
|
@@ -75,12 +109,14 @@ module Ironclad
|
|
|
75
109
|
ironclad — Rails credential keys from 1Password, cached locally.
|
|
76
110
|
|
|
77
111
|
Usage:
|
|
78
|
-
ironclad [env]
|
|
112
|
+
ironclad [env] print the credentials key (env: default)
|
|
79
113
|
ironclad edit [env] edit Rails credentials for env
|
|
80
114
|
ironclad diff <file> git textconv: decrypt a credentials file
|
|
115
|
+
ironclad refresh [env] re-cache one key (env: default)
|
|
116
|
+
ironclad refresh --all re-cache every environment's key
|
|
81
117
|
ironclad --help show this help
|
|
82
118
|
|
|
83
|
-
|
|
119
|
+
refresh re-reads from the source after a key rotation.
|
|
84
120
|
Environments are defined in config/ironclad.yml.
|
|
85
121
|
HELP
|
|
86
122
|
end
|
data/lib/ironclad/key_store.rb
CHANGED
|
@@ -22,7 +22,12 @@ module Ironclad
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
fetched = @source.read(@config.reference(environment))
|
|
25
|
-
@cache.write(name, fetched)
|
|
25
|
+
cached = @cache.write(name, fetched)
|
|
26
|
+
if refresh && !cached
|
|
27
|
+
raise Error,
|
|
28
|
+
"Could not cache refreshed credentials key for #{environment}"
|
|
29
|
+
end
|
|
30
|
+
|
|
26
31
|
fetched
|
|
27
32
|
end
|
|
28
33
|
end
|
data/lib/ironclad/version.rb
CHANGED