ansible-vault 0.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/Guardfile +40 -0
- data/LICENSE.txt +21 -0
- data/README.md +75 -0
- data/Rakefile +6 -0
- data/ansible-vault.gemspec +32 -0
- data/bin/console +14 -0
- data/bin/setup +30 -0
- data/lib/ansible/vault.rb +76 -0
- data/lib/ansible/vault/bin_ascii.rb +22 -0
- data/lib/ansible/vault/cryptor.rb +104 -0
- data/lib/ansible/vault/decryptor.rb +38 -0
- data/lib/ansible/vault/encryptor.rb +27 -0
- data/lib/ansible/vault/error.rb +7 -0
- data/lib/ansible/vault/file_reader.rb +58 -0
- data/lib/ansible/vault/file_writer.rb +51 -0
- data/lib/ansible/vault/version.rb +5 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c64c35022f30a4f3c587291beec7db13bbde45c8
|
4
|
+
data.tar.gz: a67022fa1b3d67df377f9eb1fdf926f428d67235
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d143eb2573573c5f83adf5e373f440c2d5207d2cc4933c13f1d58bcdd8a1a4f2cd60aa6f0babbafe60fc9487bde28207007a600cc25c686353f0ffcfb2b9b2b5
|
7
|
+
data.tar.gz: 696b412c876e520bba00d1884db687c5ad63f118cd23adfd624681e06095534c988ebdc422a07b6f503156b7298a4efb9fb183e2c8b2cefc62d6d203c424c8a2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at t.pickett66@gmail.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# RSpec files
|
32
|
+
rspec = dsl.rspec
|
33
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
34
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
35
|
+
watch(rspec.spec_files)
|
36
|
+
|
37
|
+
# Ruby files
|
38
|
+
ruby = dsl.ruby
|
39
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
40
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Tyler Pickett
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Ansible::Vault
|
2
|
+
[](https://travis-ci.org/tpickett66/ansible-vault-rb)
|
3
|
+
|
4
|
+
A ruby implementation of the Ansible vault file format for use in tooling that
|
5
|
+
needs to work with the vaults but doesn't want to shell out. The goal is to
|
6
|
+
provide an IO like API for interacting with the files, basic reading and
|
7
|
+
writing will be implemented first with a stream interface coming later if the
|
8
|
+
need arises. The API design is inspired by Ruby's IO class for ease of adoption
|
9
|
+
and [http://nacl.cr.yp.to/](NaCl's crypto_box) in that it takes care of doing
|
10
|
+
the right things for the user.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'ansible-vault'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
$ gem install ansible-vault
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
### Reading the contents of a vault
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'ansible/vault'
|
34
|
+
|
35
|
+
contents = Ansible::Vault.read(path: '/path/to/file', password: 'foobar')
|
36
|
+
# => 'These are the secrets that I keep.'
|
37
|
+
```
|
38
|
+
|
39
|
+
Yep, that's it! This call opens the vault file, verifies the included HMAC, and
|
40
|
+
(assuming the HMAC checks out) decrypts the contents of the file and returns
|
41
|
+
the String representation of the contents.
|
42
|
+
|
43
|
+
### Writing new contents to a vault
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
require 'ansible/vault'
|
47
|
+
|
48
|
+
Ansible::Vault.write({
|
49
|
+
path: '/path/to/file',
|
50
|
+
password: 'foobar',
|
51
|
+
plaintext: 'My secrets.'
|
52
|
+
}) # => #<File:(closed)>
|
53
|
+
```
|
54
|
+
|
55
|
+
This call overwrites anything at the path specified with the cyphertext of the
|
56
|
+
supplied contents. The plaintext is expected to have been cast to a string prior
|
57
|
+
to being passed to this function.
|
58
|
+
|
59
|
+
## Development
|
60
|
+
|
61
|
+
After checking out the repo, run `bin/setup` to check for the required
|
62
|
+
dependencies. Then, run `rake spec` to run the tests. You can also run
|
63
|
+
`bin/console` for an interactive prompt that will allow you to experiment.
|
64
|
+
|
65
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
66
|
+
release a new version, update the version number in `version.rb`, and then run
|
67
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
68
|
+
git commits and tags, and push the `.gem` file to
|
69
|
+
[rubygems.org](https://rubygems.org).
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
73
|
+
Bug reports and pull requests are welcome on GitHub at
|
74
|
+
https://github.com/tpickett66/ansible-vault-rb.
|
75
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ansible/vault/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ansible-vault"
|
8
|
+
spec.version = Ansible::Vault::VERSION
|
9
|
+
spec.authors = ["Tyler Pickett"]
|
10
|
+
spec.email = ["t.pickett66@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A ruby implementation of Ansible's vault utilities}
|
13
|
+
spec.description = "A ruby implementation of Ansible's vault utilities. " \
|
14
|
+
"Currently supports the AES256 variant, no support for the original AES" \
|
15
|
+
"format is planned."
|
16
|
+
spec.homepage = "https://github.com/tpickett66/ansible-vault-rb"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
spec.license = "MIT"
|
23
|
+
|
24
|
+
spec.add_dependency "oroku_saki", "~> 1.1"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
spec.add_development_dependency "guard-rspec", "~> 4.6"
|
30
|
+
spec.add_development_dependency "byebug", "~> 8.2"
|
31
|
+
spec.add_development_dependency "yard", "~> 0.8.7"
|
32
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "ansible/vault"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
|
5
|
+
red=$'\e[31m'
|
6
|
+
green=$'\e[32m'
|
7
|
+
yellow=$'\e[33m'
|
8
|
+
blue=$'\e[34m'
|
9
|
+
bold=$'\e[1m'
|
10
|
+
reset=$'\e[0m'
|
11
|
+
|
12
|
+
bundle install
|
13
|
+
|
14
|
+
if hash python 2>/dev/null; then
|
15
|
+
PYTHON_MISSING=0
|
16
|
+
else
|
17
|
+
echo "${bold}${yellow}Unable to locate python in \$PATH, please install it using your favorite package manager.${reset}"
|
18
|
+
PYTHON_MISSING=1
|
19
|
+
fi
|
20
|
+
|
21
|
+
if hash ansible-vault 2>/dev/null; then
|
22
|
+
ANSIBLE_MISSING=0
|
23
|
+
else
|
24
|
+
echo "${bold}${yellow}Unable to locate ansible-vault in \$PATH, please install using pip or your favorite package manager.${reset}"
|
25
|
+
ANSIBLE_MISSING=1
|
26
|
+
fi
|
27
|
+
|
28
|
+
if PYTHON_MISSING || ANSIBLE_MISSING; then
|
29
|
+
echo "${bold}${yellow}One or more optional dependencies are missing${reset}"
|
30
|
+
fi
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'oroku_saki'
|
2
|
+
|
3
|
+
require 'ansible/vault/bin_ascii'
|
4
|
+
require 'ansible/vault/encryptor'
|
5
|
+
require 'ansible/vault/decryptor'
|
6
|
+
require 'ansible/vault/file_reader'
|
7
|
+
require 'ansible/vault/file_writer'
|
8
|
+
require 'ansible/vault/version'
|
9
|
+
|
10
|
+
module Ansible
|
11
|
+
# The top level class for interacting with Vault files.
|
12
|
+
class Vault
|
13
|
+
# Read and decrypt the plaintext contents of a vault
|
14
|
+
#
|
15
|
+
# @param path [String] The path to the file to read
|
16
|
+
# @param password [String] The password for the file
|
17
|
+
# @return [String] The plaintext contents of the vault, this is marked for
|
18
|
+
# zeroing before the GC reaps the object. Any data extracted/parsed from
|
19
|
+
# this string should be similarly wiped from memory when no longer used.
|
20
|
+
def self.read(path:, password:)
|
21
|
+
new(path: path, password: password).read
|
22
|
+
end
|
23
|
+
|
24
|
+
# Encrypt plaintext using the supplied and write it to the specified location
|
25
|
+
#
|
26
|
+
# @param path [String] The path to the file to write, truncated before writing
|
27
|
+
# @param password [String] The password for the file
|
28
|
+
# @param plaintext [String] The secrets to be protected
|
29
|
+
# @return [File] The closed file handle the vault was written to
|
30
|
+
def self.write(path:, password:, plaintext:)
|
31
|
+
new(path: path, password: password, plaintext: plaintext).write
|
32
|
+
end
|
33
|
+
|
34
|
+
# Build a new Vault
|
35
|
+
#
|
36
|
+
# @param path [String] The path to the file to read
|
37
|
+
# @param password [String] The password for the file
|
38
|
+
def initialize(path:, password:, plaintext: :none)
|
39
|
+
@path = path
|
40
|
+
@password = password.shred_later
|
41
|
+
@plaintext = plaintext
|
42
|
+
@plaintext.shred_later if String === @plaintext
|
43
|
+
end
|
44
|
+
|
45
|
+
# Inspect this vault
|
46
|
+
#
|
47
|
+
# Overridden from the default implementation to prevent passwords from
|
48
|
+
# being leaked into logs.
|
49
|
+
#
|
50
|
+
# @return [String]
|
51
|
+
def inspect
|
52
|
+
%Q{#<Ansible::Vault:#{"0x00%x" % (object_id << 1)} @path="#{@path}", @password="#{@password[0,4]}...">}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Write the plaintext to the file specified
|
56
|
+
#
|
57
|
+
# @return [File] The closed file handle the vault was written to
|
58
|
+
def write
|
59
|
+
file = FileWriter.new(@path)
|
60
|
+
encryptor = Encryptor.new(password: @password, file: file)
|
61
|
+
encryptor.encrypt(@plaintext)
|
62
|
+
file.write
|
63
|
+
end
|
64
|
+
|
65
|
+
# Extract the plaintext from a previously written vault file
|
66
|
+
#
|
67
|
+
# @return [String] The plaintext contents of the vault, this is marked for
|
68
|
+
# zeroing before the GC reaps the object. Any data extracted/parsed from
|
69
|
+
# this string should be similarly wiped from memory when no longer used.
|
70
|
+
def read
|
71
|
+
file = FileReader.new(@path)
|
72
|
+
decryptor = Decryptor.new(password: @password, file: file)
|
73
|
+
decryptor.plaintext
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ansible
|
2
|
+
class Vault
|
3
|
+
# A Ruby implementation of part of Python's binascii module
|
4
|
+
module BinASCII
|
5
|
+
# Convert the supplied binary string to the hex representation
|
6
|
+
#
|
7
|
+
# @param [String] bin_data The binary data to encode
|
8
|
+
# @return [String] The hex encoded binary data.
|
9
|
+
def self.hexlify(bin_data)
|
10
|
+
bin_data.unpack('H*').first
|
11
|
+
end
|
12
|
+
|
13
|
+
# Convert the hexadecimal represenation of data back to binary
|
14
|
+
#
|
15
|
+
# @param [String] hex_data The hex data to convert back to binary
|
16
|
+
# @return [String] The binary representation of the supplied hex data
|
17
|
+
def self.unhexlify(hex_data)
|
18
|
+
[hex_data].pack('H*')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
require 'ansible/vault/error'
|
4
|
+
|
5
|
+
module Ansible
|
6
|
+
class Vault
|
7
|
+
# The base class for handling the en/decryption process
|
8
|
+
#
|
9
|
+
# This is here mostly to supply a consistent configuration for the various
|
10
|
+
# primitives in use.
|
11
|
+
#
|
12
|
+
# @!attribute [r] file
|
13
|
+
# @return [FileReader] The object handling manipulating data for the vault
|
14
|
+
# format.
|
15
|
+
class Cryptor
|
16
|
+
attr_reader :file
|
17
|
+
|
18
|
+
# The number of bytes in each key we need to generate
|
19
|
+
KEY_LENGTH = 32
|
20
|
+
# The number of bytes in the cipher's block
|
21
|
+
BLOCK_SIZE = IV_LENGTH = 16
|
22
|
+
# The number of iterations to use in the key derivation function, this
|
23
|
+
# was pulled from the Ansible source. Do not change.
|
24
|
+
KDF_ITERATIONS = 10_000
|
25
|
+
# The total number of bytes to be output by the key derivation function.
|
26
|
+
KDF_OUTPUT_LENGTH = (2 * KEY_LENGTH + IV_LENGTH)
|
27
|
+
# The hashing algorithm for use in the KDF and HMAC calculations
|
28
|
+
HASH_ALGORITHM = 'SHA256'.freeze
|
29
|
+
# The Cipher spec OpenSSL expects when building our cipher object.
|
30
|
+
CIPHER = 'AES-256-CTR'.freeze
|
31
|
+
|
32
|
+
# Build a new cryptor object
|
33
|
+
#
|
34
|
+
# @param password [String] The password to be fed into the KDF
|
35
|
+
# @param file [FileReader] The object for correctly reading/writing the
|
36
|
+
# Ansible vault file format.
|
37
|
+
def initialize(password:, file:)
|
38
|
+
@password = password
|
39
|
+
@file = file
|
40
|
+
end
|
41
|
+
|
42
|
+
# Inspect the cryptor object.
|
43
|
+
#
|
44
|
+
# Overridden from the default to prevent key/password leakage.
|
45
|
+
def inspect
|
46
|
+
"#<#{self.class.name}:#{"0x00%x" % (object_id << 1)}>"
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def calculated_hmac
|
52
|
+
return @calculated_hmac if defined?(@calculated_hmac)
|
53
|
+
digest = OpenSSL::Digest.new(HASH_ALGORITHM)
|
54
|
+
hmac_algorithm = OpenSSL::HMAC.new(hmac_key, digest)
|
55
|
+
hmac_algorithm << file.ciphertext
|
56
|
+
@calculated_hmac = hmac_algorithm.hexdigest
|
57
|
+
end
|
58
|
+
|
59
|
+
def cipher(mode: :decrypt)
|
60
|
+
@cipher ||= OpenSSL::Cipher.new(CIPHER).tap do |cipher|
|
61
|
+
cipher.public_send(mode)
|
62
|
+
cipher.key = cipher_key
|
63
|
+
cipher.iv = iv
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def cipher_key
|
68
|
+
return @cipher_key if defined?(@cipher_key)
|
69
|
+
derive_keys
|
70
|
+
@cipher_key
|
71
|
+
end
|
72
|
+
|
73
|
+
def hmac_key
|
74
|
+
return @hmac_key if defined?(@hmac_key)
|
75
|
+
derive_keys
|
76
|
+
@hmac_key
|
77
|
+
end
|
78
|
+
|
79
|
+
def iv
|
80
|
+
return @iv if defined?(@iv)
|
81
|
+
derive_keys
|
82
|
+
@iv
|
83
|
+
end
|
84
|
+
|
85
|
+
def derive_keys
|
86
|
+
if salt.nil? || salt.strip.empty?
|
87
|
+
raise MissingSalt, "Unable to derive keys, no salt available!"
|
88
|
+
end
|
89
|
+
key = OpenSSL::PKCS5.pbkdf2_hmac(
|
90
|
+
@password,
|
91
|
+
salt,
|
92
|
+
KDF_ITERATIONS,
|
93
|
+
KDF_OUTPUT_LENGTH,
|
94
|
+
HASH_ALGORITHM
|
95
|
+
)
|
96
|
+
@cipher_key = key[0,KEY_LENGTH].shred_later
|
97
|
+
@hmac_key = key[KEY_LENGTH, KEY_LENGTH].shred_later
|
98
|
+
@iv = key[KEY_LENGTH*2, IV_LENGTH].shred_later
|
99
|
+
key.shred!
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'ansible/vault/cryptor'
|
2
|
+
|
3
|
+
module Ansible
|
4
|
+
class Vault
|
5
|
+
# The class that handles decrypting an existing vault file
|
6
|
+
class Decryptor < Cryptor
|
7
|
+
# Decrypts the ciphertext from the file and strips any padding found.
|
8
|
+
#
|
9
|
+
# @return [String] The plaintext contents of the file, this is marked for
|
10
|
+
# zeroing before the GC reaps the object. Any data extracted/parsed
|
11
|
+
# from this string should be similarly wiped from memory when no longer
|
12
|
+
# used.
|
13
|
+
def plaintext
|
14
|
+
return @plaintext if defined?(@plaintext)
|
15
|
+
unless hmac_matches?
|
16
|
+
raise HMACMismatch, 'HMAC encoded in the file does not match calculated one!'
|
17
|
+
end
|
18
|
+
@plaintext = cipher(mode: :decrypt).update(file.ciphertext)
|
19
|
+
padding_length = @plaintext[-1].codepoints.first
|
20
|
+
@plaintext.sub!(/#{padding_length.chr}{#{padding_length}}\z/, '')
|
21
|
+
@plaintext.shred_later
|
22
|
+
end
|
23
|
+
|
24
|
+
# Indicates if the HMAC present in the file matches the calculated one
|
25
|
+
#
|
26
|
+
# @return [Boolean]
|
27
|
+
def hmac_matches?
|
28
|
+
OrokuSaki.secure_compare(calculated_hmac, file.hmac)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def salt
|
34
|
+
file.salt
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
require 'ansible/vault/cryptor'
|
4
|
+
|
5
|
+
module Ansible
|
6
|
+
class Vault
|
7
|
+
# The class that handles encrypting data to be written to a file.
|
8
|
+
class Encryptor < Cryptor
|
9
|
+
# Encrypt supplied plaintext, calculate HMAC, and pass to supplied {FileWriter}
|
10
|
+
#
|
11
|
+
# @param [String] plaintext The source data to be encrypted
|
12
|
+
def encrypt(plaintext)
|
13
|
+
padding_length = BLOCK_SIZE - plaintext.bytesize % BLOCK_SIZE
|
14
|
+
padded_plaintext = (plaintext + (padding_length.chr * padding_length)).shred_later
|
15
|
+
file.ciphertext = cipher(mode: :encrypt).update(padded_plaintext) + cipher.final
|
16
|
+
file.salt = salt
|
17
|
+
file.hmac = calculated_hmac
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def salt
|
23
|
+
@salt ||= SecureRandom.random_bytes(KEY_LENGTH)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Ansible
|
2
|
+
class Vault
|
3
|
+
# A class for reading the data encoded in an Ansible vault file.
|
4
|
+
#
|
5
|
+
# @!attribute [r] body
|
6
|
+
# The encoded body of the file.
|
7
|
+
# @!attribute [r] header
|
8
|
+
# The header of the file, not currently used.
|
9
|
+
# @!attribute [r] path
|
10
|
+
# The path of the file being read.
|
11
|
+
class FileReader
|
12
|
+
attr_reader :body, :header, :path
|
13
|
+
|
14
|
+
def initialize(path)
|
15
|
+
@path = path
|
16
|
+
::File.open(path, 'r') { |f|
|
17
|
+
@header = f.gets.chomp
|
18
|
+
@body = f.readlines.map(&:chomp).join
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Extracts and decodes the ciphertext from the file body
|
23
|
+
#
|
24
|
+
# @return [String] The raw binary representation of the ciphertext
|
25
|
+
def ciphertext
|
26
|
+
return @ciphertext if defined?(@ciphertext)
|
27
|
+
decode_body
|
28
|
+
@ciphertext
|
29
|
+
end
|
30
|
+
|
31
|
+
# Extracts the HMAC value from the file body
|
32
|
+
#
|
33
|
+
# @return [String] The hex representation of the HMAC
|
34
|
+
def hmac
|
35
|
+
return @hmac if defined?(@hmac)
|
36
|
+
decode_body
|
37
|
+
@hmac
|
38
|
+
end
|
39
|
+
|
40
|
+
# Extracts and decodes the salt from the file body
|
41
|
+
#
|
42
|
+
# @return [String] The raw binary representation of the salt
|
43
|
+
def salt
|
44
|
+
return @salt if defined?(@salt)
|
45
|
+
decode_body
|
46
|
+
@salt
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def decode_body
|
52
|
+
salt, @hmac, ciphertext = BinASCII.unhexlify(@body).split("\n")
|
53
|
+
@ciphertext = BinASCII.unhexlify(ciphertext)
|
54
|
+
@salt = BinASCII.unhexlify(salt)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Ansible
|
2
|
+
class Vault
|
3
|
+
# A class for writting the encrypted data into an Ansible formatted file
|
4
|
+
#
|
5
|
+
# @!attribute [r] path
|
6
|
+
# @return [String] The path to write the file to, truncated before writing
|
7
|
+
# @!attribute [rw] ciphertext
|
8
|
+
# @return [String] The encrypted contents of the file.
|
9
|
+
# @!attribute [rw] hmac
|
10
|
+
# @return [String] The HMAC value for the ciphertext, calculated by an
|
11
|
+
# instance of {Encryptor}
|
12
|
+
# @!attribute [rw] salt
|
13
|
+
# @return [String] The salt value for the ciphertext, generated by an
|
14
|
+
# instance of {Encryptor}
|
15
|
+
class FileWriter
|
16
|
+
attr_reader :path
|
17
|
+
attr_accessor :ciphertext, :hmac, :salt
|
18
|
+
|
19
|
+
# The standard header for Ansible's current vault format
|
20
|
+
HEADER = "$ANSIBLE_VAULT;1.1;AES256\n".freeze
|
21
|
+
|
22
|
+
# Construct a new FileWriter
|
23
|
+
#
|
24
|
+
# @param [String] path The path to write the file out to.
|
25
|
+
def initialize(path)
|
26
|
+
@path = path
|
27
|
+
end
|
28
|
+
|
29
|
+
# Write out the encrypted contents of the file in the correct format.
|
30
|
+
#
|
31
|
+
# @return [File] The closed file handle used to write the data out.
|
32
|
+
def write
|
33
|
+
File.open(path, 'w') { |file|
|
34
|
+
file.write(HEADER)
|
35
|
+
file.write(encoded_body)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def encoded_body
|
42
|
+
encoded_ciphertext = BinASCII.hexlify(@ciphertext)
|
43
|
+
encoded_salt = BinASCII.hexlify(@salt)
|
44
|
+
|
45
|
+
raw_body = [encoded_salt, @hmac, encoded_ciphertext].join("\n")
|
46
|
+
BinASCII.hexlify(raw_body).scan(/.{,80}/).join("\n")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ansible-vault
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tyler Pickett
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: oroku_saki
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '8.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '8.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.8.7
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.8.7
|
111
|
+
description: A ruby implementation of Ansible's vault utilities. Currently supports
|
112
|
+
the AES256 variant, no support for the original AESformat is planned.
|
113
|
+
email:
|
114
|
+
- t.pickett66@gmail.com
|
115
|
+
executables: []
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- ".rspec"
|
121
|
+
- ".travis.yml"
|
122
|
+
- CODE_OF_CONDUCT.md
|
123
|
+
- Gemfile
|
124
|
+
- Guardfile
|
125
|
+
- LICENSE.txt
|
126
|
+
- README.md
|
127
|
+
- Rakefile
|
128
|
+
- ansible-vault.gemspec
|
129
|
+
- bin/console
|
130
|
+
- bin/setup
|
131
|
+
- lib/ansible/vault.rb
|
132
|
+
- lib/ansible/vault/bin_ascii.rb
|
133
|
+
- lib/ansible/vault/cryptor.rb
|
134
|
+
- lib/ansible/vault/decryptor.rb
|
135
|
+
- lib/ansible/vault/encryptor.rb
|
136
|
+
- lib/ansible/vault/error.rb
|
137
|
+
- lib/ansible/vault/file_reader.rb
|
138
|
+
- lib/ansible/vault/file_writer.rb
|
139
|
+
- lib/ansible/vault/version.rb
|
140
|
+
homepage: https://github.com/tpickett66/ansible-vault-rb
|
141
|
+
licenses:
|
142
|
+
- MIT
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 2.4.5
|
161
|
+
signing_key:
|
162
|
+
specification_version: 4
|
163
|
+
summary: A ruby implementation of Ansible's vault utilities
|
164
|
+
test_files: []
|
165
|
+
has_rdoc:
|