hiera-eyaml-sshagent 0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7e18fd54c6a2bcd67245f1a2ed67d3cd7cfd5264a8726579e7a8d4d18177702e
4
+ data.tar.gz: 83ef75d39dc548306fc58c4b6fec4c9f7c3c67c08ec90335eae2931940cc58fc
5
+ SHA512:
6
+ metadata.gz: d4cccba756eaa6295ecacd19604febeb6aafda03e1b35383c495c141b40c9b5a286472c778af7cc02d4039e97d521bd8520b3376d11c2781313dfbd9bec349cb
7
+ data.tar.gz: 0e59f1211ec5697cd9dc8775b97315ac9dee04b9e114fcb7ccbf05e8d5db225dbce5b31753fc1ac0d6cde4cc29d7b26b1ec927e78cd77808b76e344c0362980d
@@ -0,0 +1,16 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v2.1.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: requirements-txt-fixer
9
+ - repo: local
10
+ hooks:
11
+ - id: rubocop
12
+ name: rubocop
13
+ entry: rubocop --auto-correct
14
+ types: [ruby]
15
+ language: ruby
16
+ additional_dependencies: ['rubocop:0.63.1']
@@ -0,0 +1,12 @@
1
+ Metrics/AbcSize:
2
+ Max: 30
3
+ Metrics/BlockLength:
4
+ Max: 30
5
+ Metrics/CyclomaticComplexity:
6
+ Max: 10
7
+ Metrics/MethodLength:
8
+ Max: 30
9
+ Metrics/LineLength:
10
+ IgnoreCopDirectives: true
11
+ Style/Documentation:
12
+ Enabled: false
@@ -0,0 +1,8 @@
1
+ language: python
2
+ python: 3.6
3
+ install: pip install pre-commit
4
+ script: pre-commit run --all-files
5
+ cache:
6
+ directories:
7
+ - $HOME/.cache/pip
8
+ - $HOME/.cache/pre-commit
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gem 'fernet', '>=2'
4
+ gem 'hiera-eyaml', '>=1.3.8'
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2019 Anthony Sottile
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,83 @@
1
+ hiera-eyaml-sshagent
2
+ ====================
3
+
4
+ A [hiera-eyaml] plugin which uses the ssh agent connected to `SSH_AUTH_SOCK`
5
+ to encrypt / decrypt values.
6
+
7
+ ### installation
8
+
9
+ ```bash
10
+ gem install hiera-eyaml-sshagent
11
+ ```
12
+
13
+ ### configuring
14
+
15
+ The plugin takes a single option `sshagent_keyid`:
16
+
17
+ ```yaml
18
+ version: 5
19
+ hierarchy:
20
+ - name: "Common secret data"
21
+ lookup_key: eyaml_lookup_key
22
+ path: common.eyaml
23
+ options:
24
+ sshagent_keyid: /home/asottile/.ssh/id_rsa
25
+ - name: "Common data"
26
+ path: common.yaml
27
+ ```
28
+
29
+ The `keyid` should match what is printed from `ssh-add -l`
30
+
31
+ ### how it works
32
+
33
+ It is based on code / ideas from the following:
34
+
35
+ - [blog post demoing ssh agent api in python][blog-post]
36
+ - [initial demo implementation in python][ssh-agent-python]
37
+ - [cryptography stackexchange: Is it safe to derive a password from a signature provided by ssh-agent?][se-is-it-safe]
38
+ - [security stackexchange: Is it possible to use SSH agent for generic data encryption?][se-ssh-agent]
39
+ - [sshcrypt]
40
+
41
+ #### retrieve symmetric key
42
+
43
+ This procedure takes a keyid, a 64 byte challenge, and a 16 byte salt.
44
+
45
+ 1. list ssh identities by querying `SSH_AUTH_SOCK`
46
+ 2. find the identity matching `keyid`
47
+ 3. sign the `challenge` using that identity
48
+ 4. use the response blob as a "password" with pbkdf2_hmac (using the salt)
49
+ 5. the result is a 32 byte key which will be used with fernet
50
+
51
+ #### `encrypt(keyid, blob)`
52
+
53
+ 1. generate a 64 byte "challenge" and 16 byte salt
54
+ 2. retrieve symmetric key
55
+ 3. encrypt with the symmetric key
56
+ 4. store a blob of `{challenge, salt, payload}`
57
+
58
+ #### `decrypt(keyid, blob)`
59
+
60
+ 1. load the stored blob `{challenge, salt, payload}`
61
+ 2. retrieve symmetric key
62
+ 3. decrypt with symmetric key
63
+
64
+ ### why?
65
+
66
+ I use a [masterless puppet setup][personal-puppet] to manage my machines.
67
+
68
+ My current bootstrapping process is:
69
+
70
+ 1. place ssh key on machine
71
+ 2. clone the repo
72
+ 3. `./run-puppet`
73
+
74
+ As such, I wanted a `hiera-eyaml` backend which didn't involve typing in more
75
+ passwords or copying around more keys (since I'm already using my ssh key).
76
+
77
+ [hiera-eyaml]: https://github.com/voxpupuli/hiera-eyaml
78
+ [blog-post]: http://ptspts.blogspot.com/2010/06/how-to-use-ssh-agent-programmatically.html
79
+ [ssh-agent-python]: https://github.com/asottile/ssh-agent-python
80
+ [se-is-it-safe]: https://crypto.stackexchange.com/q/19631/65568
81
+ [se-ssh-agent]: https://security.stackexchange.com/q/55757/197558
82
+ [ssh-crypt]: https://github.com/leighmcculloch/sshcrypt
83
+ [personal-puppet]: https://github.com/asottile/personal-puppet
@@ -0,0 +1,20 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'hiera/backend/eyaml/encryptors/sshagent/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'hiera-eyaml-sshagent'
7
+ gem.version = Hiera::Backend::Eyaml::Encryptors::SSHAgent::VERSION
8
+ gem.description = 'SSH_AUTH_SOCK encryptor for use with hiera-eyaml'
9
+ gem.summary = 'Encryption plugin for hiera-eyaml backend for Hiera'
10
+ gem.author = 'Anthony Sottile'
11
+ gem.license = 'MIT'
12
+
13
+ gem.homepage = 'http://github.com/asottile/hiera-eyaml-sshagent'
14
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.require_paths = ['lib']
17
+
18
+ gem.add_dependency('fernet', '>=2')
19
+ gem.add_dependency('hiera-eyaml', '>=1.3.8')
20
+ end
@@ -0,0 +1,156 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'socket'
4
+ require 'stringio'
5
+
6
+ require 'fernet'
7
+
8
+ class Hiera
9
+ module Backend
10
+ module Eyaml
11
+ module Encryptors
12
+ class SSHAgent < Encryptor
13
+ self.tag = 'SSHAGENT'
14
+
15
+ SSH2_AGENTC_REQUEST_IDENTITIES = "\x0b".freeze
16
+ SSH2_AGENT_IDENTITIES_ANSWER = "\x0c".freeze
17
+ SSH2_AGENTC_SIGN_REQUEST = "\x0d".freeze
18
+ SSH2_AGENT_SIGN_RESPONSE = "\x0e".freeze
19
+
20
+ def self.read_u32(sock)
21
+ sock.read(4).unpack('L>')[0]
22
+ end
23
+
24
+ def self.read_s(sock)
25
+ sock.read(read_u32(sock))
26
+ end
27
+
28
+ def self.encode_s(str)
29
+ [str.size].pack('I>') + str
30
+ end
31
+
32
+ def self.sign(sock, key_blob, challenge)
33
+ request = (
34
+ SSH2_AGENTC_SIGN_REQUEST +
35
+ encode_s(key_blob) +
36
+ encode_s(challenge) +
37
+ [0].pack('L>')
38
+ )
39
+ sock.write(encode_s(request))
40
+
41
+ sio = StringIO.new(read_s(sock))
42
+ if sio.read(1) != SSH2_AGENT_SIGN_RESPONSE
43
+ raise 'Expected SSH2_AGENT_SIGN_RESPONSE'
44
+ end
45
+
46
+ sio = StringIO.new(read_s(sio))
47
+ raise 'Expected ssh-rsa' if read_s(sio) != 'ssh-rsa'
48
+
49
+ read_s(sio)
50
+ end
51
+
52
+ def self.get_key_blob(sock, keyid)
53
+ sock.write(encode_s(SSH2_AGENTC_REQUEST_IDENTITIES))
54
+
55
+ sio = StringIO.new(read_s(sock))
56
+ if sio.read(1) != SSH2_AGENT_IDENTITIES_ANSWER
57
+ raise 'expected SSH2_AGENT_IDENTITIES_ANSWER'
58
+ end
59
+
60
+ (0...read_u32(sio)).each do
61
+ key_blob = read_s(sio)
62
+ key_comment = read_s(sio)
63
+ break key_blob if key_comment == keyid
64
+ end
65
+ end
66
+
67
+ class Encrypted
68
+ attr_reader :challenge
69
+ attr_reader :salt
70
+ attr_reader :payload
71
+
72
+ def initialize(challenge, salt, payload)
73
+ @challenge = challenge
74
+ @salt = salt
75
+ @payload = payload
76
+ end
77
+
78
+ def to_dct
79
+ {
80
+ 'challenge' => Base64.strict_encode64(@challenge),
81
+ 'salt' => Base64.strict_encode64(@salt),
82
+ 'payload' => @payload
83
+ }
84
+ end
85
+
86
+ def self.from_dct(dct)
87
+ Encrypted.new(
88
+ Base64.decode64(dct['challenge']),
89
+ Base64.decode64(dct['salt']),
90
+ dct['payload']
91
+ )
92
+ end
93
+ end
94
+
95
+ def self.get_key(keyid, challenge, salt)
96
+ signature_blob = Socket.unix(ENV['SSH_AUTH_SOCK']) do |sock|
97
+ key_blob = get_key_blob(sock, keyid)
98
+ break sign(sock, key_blob, challenge)
99
+ end
100
+
101
+ kdf = OpenSSL::PKCS5.pbkdf2_hmac(
102
+ signature_blob,
103
+ salt,
104
+ 100_000,
105
+ 32,
106
+ OpenSSL::Digest::SHA256.new
107
+ )
108
+ Base64.encode64(kdf)
109
+ end
110
+
111
+ def self.encrypt_contents(keyid, contents)
112
+ challenge = Random.urandom(64)
113
+ salt = Random.urandom(16)
114
+ key = get_key(keyid, challenge, salt)
115
+ Encrypted.new(challenge, salt, Fernet.generate(key, contents))
116
+ end
117
+
118
+ def self.decrypt_contents(keyid, contents)
119
+ enc = Encrypted.from_dct(JSON.parse(contents))
120
+ key = get_key(keyid, enc.challenge, enc.salt)
121
+ Fernet.verifier(key, enc.payload, enforce_ttl: false).message
122
+ end
123
+
124
+ self.options = {
125
+ keyid: {
126
+ desc: 'Key location -- it is the file path from `ssh-add -l`',
127
+ type: :string
128
+ }
129
+ }
130
+
131
+ def self.keyid
132
+ keyid = option :keyid
133
+ if keyid.nil? || keyid.empty?
134
+ raise ArgumentError, 'No keyid configured!'
135
+ end
136
+
137
+ keyid
138
+ end
139
+
140
+ def self.encrypt(plaintext)
141
+ enc = encrypt_contents(keyid, plaintext)
142
+ JSON.generate(enc.to_dct)
143
+ end
144
+
145
+ def self.decrypt(ciphertext)
146
+ decrypt_contents(keyid, ciphertext)
147
+ end
148
+
149
+ def self.create_keys
150
+ STDERR.puts 'This encryptor does not support creation of keys'
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,3 @@
1
+ require 'hiera/backend/eyaml/encryptors/sshagent'
2
+
3
+ Hiera::Backend::Eyaml::Encryptors::SSHAgent.register
@@ -0,0 +1,11 @@
1
+ class Hiera
2
+ module Backend
3
+ module Eyaml
4
+ module Encryptors
5
+ module SSHAgent
6
+ VERSION = '0.1'.freeze
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hiera-eyaml-sshagent
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Anthony Sottile
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-02-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fernet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hiera-eyaml
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.8
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.3.8
41
+ description: SSH_AUTH_SOCK encryptor for use with hiera-eyaml
42
+ email:
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - ".pre-commit-config.yaml"
48
+ - ".rubocop.yml"
49
+ - ".travis.yml"
50
+ - Gemfile
51
+ - LICENSE
52
+ - README.md
53
+ - hiera-eyaml-sshagent.gemspec
54
+ - lib/hiera/backend/eyaml/encryptors/sshagent.rb
55
+ - lib/hiera/backend/eyaml/encryptors/sshagent/eyaml_init.rb
56
+ - lib/hiera/backend/eyaml/encryptors/sshagent/version.rb
57
+ homepage: http://github.com/asottile/hiera-eyaml-sshagent
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.7.6
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Encryption plugin for hiera-eyaml backend for Hiera
81
+ test_files: []