hiera-eyaml-sshagent 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []