secret 0.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.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+
19
+
20
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in secret.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Christopher Thornton
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ Secret Files
2
+ ============
3
+ Keeps your files more secure by ensuring saved files are chmoded 0700 by the same user who is running the process.
4
+ For example, this could be used by a Rails application to store certificates only readable by the www-data process.
5
+
6
+ The secret files gem includes some fun over-engineering with file locking!
7
+
8
+ ## Usage
9
+ Add to your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'secret'
13
+ ```
14
+
15
+ Now you should set up your "default" container in an initializer:
16
+
17
+ ```ruby
18
+ Secret.configure_default 'path/to/secret/dir'
19
+ ```
20
+
21
+ Finally, you should now be able to put stuff into the secret directory, or read contents!
22
+
23
+ ```ruby
24
+ secret = Secret.default
25
+
26
+ # Save the key
27
+ secret.some_key.stash "ThisIsSomeKey"
28
+
29
+ # Get the key
30
+ puts secret.some_key.contents
31
+
32
+ # Manually seek through the file
33
+ secret.some_key.stream do |f|
34
+ f.seek 1, IO::SEEK_CUR
35
+ end
36
+ ```
37
+
38
+ ## How Secure is It?
39
+ Ths is only *somewhat* secure and will provide protection against:
40
+
41
+ * Someone who gains access to your server with non-root access
42
+ * Other non-root server processes
43
+
44
+ However, this will **not** protect you against:
45
+
46
+ * People with root access
47
+ * Arbitrary code execution attacks by the owner (i.e. an `eval()` gone wrong)
48
+
49
+ ## Other Features
50
+ This gem also includes locking support, meaning that it should (hopefully) be resillient against multiple processes
51
+ writing to a file. The primary reason for this is because I really wanted to try file locking. However, don't do
52
+ anything too tricky and you should be okay.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,64 @@
1
+ module Secret
2
+ class Container
3
+
4
+ attr_reader :directory, :files
5
+
6
+ # Initializes a container. Does significant checking on the directory to ensure it is writeable and it exists.
7
+ # @param [String] directory the directory to the container
8
+ # @param [Boolean] auto_create if true, will attempt to create the directory if it does not exist.
9
+ def initialize(directory, auto_create = true)
10
+ @directory = directory
11
+
12
+ @files = {}
13
+
14
+ # Do some checking about our directory
15
+ if ::File.exist?(directory)
16
+ raise ArgumentError, "Specified directory '#{directory}' is actually a file!" unless ::File.directory?(directory)
17
+
18
+ # Now make our directory if auto_create
19
+ else
20
+ raise ArgumentError, "Specified directory '#{directory}' does not exist!" unless auto_create
21
+ Dir.mkdir(directory, Secret::CHMOD_MODE) # Only give read/write access to this user
22
+ end
23
+ raise ArgumentError, "Directory '#{directory}' is not writeable!" unless ::File.writable?(directory)
24
+ end
25
+
26
+ # Gets a file stored in the container.
27
+ # @param [Symbol] filename the name of the file.
28
+ # @return [Secret::File] a secret file
29
+ def file(filename)
30
+ fn = filename.to_sym
31
+ f = files[fn]
32
+ return f unless f.nil?
33
+ f = Secret::File.new(self, filename)
34
+ files[fn] = f
35
+ return f
36
+ end
37
+
38
+ def method_missing(meth, *args, &block)
39
+ super(meth, *args, &block) if args.any? or block_given?
40
+ return file(meth)
41
+ end
42
+
43
+
44
+ # Deletes the cache of objects
45
+ def uncache!
46
+ @files = {}
47
+ end
48
+
49
+ # This should be called once in some sort of initializer.
50
+ def initialize_once!
51
+ destroy_all_locks!
52
+ end
53
+
54
+ # Viciously destroys all locks that the file and its containers may have. Use carefully!
55
+ # @return [Integer] the number of files destroyed.
56
+ def destroy_all_locks!
57
+ files = Dir[::File.join(directory, '*.lock')]
58
+ files.each{|f| ::File.delete(f) }
59
+ return files.count
60
+ end
61
+
62
+
63
+ end
64
+ end
@@ -0,0 +1,45 @@
1
+ require 'openssl'
2
+
3
+ module Secret
4
+ module Encryption
5
+
6
+
7
+
8
+ def encrypt_basic(passphrase)
9
+ cipher = OpenSSL::Cipher.new('aes-256-cbc')
10
+ cipher.encrypt
11
+ key = passphrase
12
+ iv = cipher.random_iv
13
+
14
+ out = StringIO.new("", "wb") do |outf|
15
+ StringIO.new(contents, "rb") do |inf|
16
+ while inf.read(4096, buf)
17
+ outf << cipher.update(buf)
18
+ end
19
+ outf << cipher.final
20
+ end
21
+ end
22
+
23
+ return out.string
24
+ end
25
+
26
+ # Checks to see if the file is encrypted
27
+ def encrypted?
28
+ ::File.exist?(encrypted_meta_filename)
29
+ end
30
+
31
+
32
+ def stash(content); raise "Not Implemented"; end
33
+
34
+ def contents; raise "Not Implemented"; end
35
+
36
+
37
+
38
+ protected
39
+
40
+ def encrypted_meta_filename
41
+ raise NotImplementedError, "Must implement dis!"
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,187 @@
1
+ module Secret
2
+
3
+ # Handles file operations.
4
+ #
5
+ # Note that locking operations are not perfect!
6
+ class File
7
+ include Secret::Locking
8
+
9
+ # include Secret::Encryption
10
+
11
+ attr_reader :container, :identifier
12
+
13
+ # Lock timeout in MS. Defaults to 5000 MS
14
+ attr_accessor :lock_timeout_ms
15
+
16
+ # Whether this current object holds the lock over the file.
17
+ # @return [Boolean] TRUE if this object was the one that holds the lock (i.e. created the lock), FALSE if another
18
+ # object holds the lock, or the file simply isn't locked.
19
+ attr_reader :owns_lock
20
+
21
+ # Creates a new secret file. The specified identifier doesn't already need to exist.
22
+ # @param [Secret::Container] container the container object
23
+ # @param [Symbol] identifier an unique identifier for this container
24
+ def initialize(container, identifier)
25
+ raise ArgumentError, "Container must be a Secret::Container object" unless container.is_a?(Secret::Container)
26
+ @container = container; @identifier = identifier; @owns_lock = false
27
+ @lock_timeout_ms = Secret::Locking::DEFAULT_LOCK_WAIT_MS
28
+ end
29
+
30
+ # Checks whether this file actually exists or not
31
+ # @return [Boolean] true if the file exists (i.e. has content), false if otherwise.
32
+ def exist?
33
+ ::File.exist?(file_path)
34
+ end
35
+
36
+ # Gets a file stream of this file. If the file doesn't exist, then a blank file will be created. By default,
37
+ # this allows you to write to the file. However, please use the {#stash} command, as it accounts for mid-write
38
+ # crashes. Don't forget to close the file stream when you're done!
39
+ # @param [String] mode the mode for this file. Currently defaults to 'r+', which is read-write, with the
40
+ # file pointer at the beginning of the file.
41
+ # @return [IO] an IO stream to this file, if not using a block
42
+ # @note This completely bypasses any internal locking mechanisms if not using a block!
43
+ # @example
44
+ # file = container.some_file
45
+ #
46
+ # # Unsafe way!
47
+ # io = file.stream
48
+ # io.write "Hello World!"
49
+ # io.close
50
+ #
51
+ # # Safe way, with locking support
52
+ # file.stream do |f|
53
+ # f.write "Hello World!"
54
+ # end
55
+ def stream(mode = 'r+', &block)
56
+ touch!
57
+ return ::File.open(file_path, mode, Secret::CHMOD_MODE) unless block_given?
58
+ wait_for_unlock(true, lock_timeout_ms) do
59
+ io = ::File.open(file_path, mode, Secret::CHMOD_MODE)
60
+ block.call(io)
61
+ io.close unless io.closed?
62
+ end
63
+ end
64
+
65
+
66
+ # Gets the contents of the file in a string format. Will return an empty string if the file doesn't exist, or the
67
+ # file just so happens to be empty.
68
+ # @return [String] the contents of the file
69
+ def contents
70
+ wait_for_unlock(false, lock_timeout_ms) do
71
+ io = stream
72
+ str = io.read
73
+ io.close
74
+ end
75
+ return str
76
+ end
77
+
78
+
79
+ # Creates a new file if it doesn't exist. Doesn't actually change the last updated timestamp.
80
+ # @return [Boolean] true if an empty file was created, false if the file already existed.
81
+ def touch!
82
+ unless exist?
83
+ ::File.open(file_path, 'w') {}
84
+ secure!
85
+ return true
86
+ end
87
+ return false
88
+ end
89
+
90
+ # Secures the file by chmoding it to 0700
91
+ # @raise [IOError] if the file doesn't exist on the server.
92
+ def secure!
93
+ raise IOError, "File doesn't exist" unless exist?
94
+ ::File.chmod(Secret::CHMOD_MODE, file_path)
95
+ end
96
+
97
+
98
+ # Stashes some content into the file! This will write a temporary backup file before stashing, in order to prevent
99
+ # any partial writes if the server crashes. Once this finishes executing, you can be sure that contents have been
100
+ # written.
101
+ # @param [String] content the contents to stash. **Must be a string!**
102
+ # @raise [ArgumentError] if content is anything other than a String object!
103
+ def stash(content)
104
+ raise ArgumentError, "Content must be a String (was type of type #{content.class.name})" unless content.is_a?(String)
105
+
106
+ # Get an exclusive lock
107
+ wait_for_unlock(true, lock_timeout_ms) do
108
+ touch!
109
+
110
+ # Think of this as a beginning of a transaction.
111
+
112
+ # Open a temporary file for writing, and close it immediately
113
+ ::File.open(tmp_file_path, "w", Secret::CHMOD_MODE){|f| f.write content }
114
+
115
+ # Rename the existing file path
116
+ ::File.rename(file_path, backup_file_path)
117
+
118
+ # Now rename the temporary file to the correct file
119
+ ::File.rename(tmp_file_path, file_path)
120
+
121
+ # Delete the backup
122
+ ::File.delete(backup_file_path)
123
+
124
+ # Committed! Secure it just in case
125
+ secure!
126
+ end
127
+ end
128
+
129
+ # Attempts to restore a backup (i.e. if the computer crashed while doing a stash command)
130
+ # @return [Boolean] true if the backup was successfully restored, false otherwise
131
+ def restore_backup!
132
+ return false if locked?
133
+ return false unless ::File.exist?(backup_file_path)
134
+
135
+ # Ideally we want to get an exclusive lock when doing this!
136
+ wait_for_unlock(true, lock_timeout_ms) do
137
+ # If the file actually exists, then the backup file probably wasn't deleted, so just
138
+ # delete the backup file.
139
+ if ::File.exist?(file_path)
140
+ ::File.delete(backup_file_path)
141
+ return true
142
+
143
+ # Otherwise, the temporary file probably wasn't renamed, so restore the backup and delete the temporary file
144
+ # if it exists. It's possible the file was corrupted in the middle of writing, so it is better to resort
145
+ # to an old and complete file rather than a new and possibly corrupted file.
146
+ else
147
+ ::File.rename(backup_file_path, file_path)
148
+ ::File.delete(tmp_file_path) if ::File.exist?(tmp_file_path)
149
+ return true
150
+ end
151
+ end
152
+ end
153
+
154
+ protected
155
+
156
+ # Gets the path of the lock file
157
+ def lock_file_path
158
+ return file_path + '.lock'
159
+ end
160
+
161
+
162
+ private
163
+
164
+ # Gets the actual file path of this container. Intentionally made private (security through obscurity)
165
+ # @return [String] the absolute path to where this file is
166
+ def file_path
167
+ @the_file_path = ::File.join(container.directory, identifier.to_s) if @the_file_path.nil?
168
+ return @the_file_path
169
+ end
170
+
171
+ # The path of the temporary file. Used as a temporary container for stashing. This file will then be re-named.
172
+ # @return [String] the path to the temporary file
173
+ def tmp_file_path
174
+ return file_path + ".tmp"
175
+ end
176
+
177
+ # Gets the path of the backup file
178
+ # @return [String] the path of the backup file
179
+ def backup_file_path
180
+ return file_path + '.bak'
181
+ end
182
+
183
+
184
+
185
+
186
+ end
187
+ end
@@ -0,0 +1,142 @@
1
+ module Secret
2
+
3
+ # Locking utilities!
4
+ module Locking
5
+
6
+ # By default, wait for locks for 5 seconds
7
+ DEFAULT_LOCK_WAIT_MS = 5000
8
+
9
+ # Locks the current file. May prevent other object instances (or processes) from reading / writing to this file.
10
+ # You may use this to upgrade your lock if you wish.
11
+ # @param [Boolean] exclusive if TRUE, holds an exclusive lock meaning that other processes cannot read or write
12
+ # to the file. If FALSE, allows other files to read from the file, but not to write.
13
+ # @return [Boolean] true if the lock was created successfully, false if it was not (likely due to another process holding a lock,
14
+ # or we already hold the lock).
15
+ # @note Remember to unlock the file once you are done using it!
16
+ def lock!(exclusive = false)
17
+ return false if(locked? and !owns_lock?)
18
+
19
+ # Write
20
+ # The file contents will be of '<exclusive bit>:<process id>'
21
+ lock_string = (exclusive ? '0' : '1') + ":#{Process.pid}"
22
+ ::File.open(lock_file_path, 'w',Secret::CHMOD_MODE) { |f| f.write lock_string }
23
+ @owns_lock = true
24
+
25
+ return true
26
+ end
27
+
28
+ # Checks to see if this file is locked. Has no indication of whether we own the lock or not.
29
+ # @param [Boolean] exclusive if TRUE, only checks if it's an exclusive lock
30
+ # @return [Boolean] true if the file is locked, false otherwise.
31
+ def locked?(exclusive = false)
32
+ return false unless ::File.exist?(lock_file_path)
33
+ return true unless exclusive # Don't bother checking for non-exlusive
34
+ ex = false
35
+ begin
36
+ # Quickly peek at the file to see if it is exclusive
37
+ ::File.open(lock_file_path, 'r', Secret::CHMOD_MODE) do |f|
38
+ ex = f.seek(1, IO::SEEK_CUR) == '1'
39
+ f.close
40
+ end
41
+ return ex
42
+ rescue Exception => e
43
+ return false
44
+ end
45
+ end
46
+
47
+
48
+ # An alias for {#owns_lock}
49
+ def owns_lock?
50
+ return owns_lock
51
+ end
52
+
53
+ # Unlocks this file.
54
+ # @return [Boolean] true if unlock was successful, false if unlock was unsuccessful (i.e. the file wasn't locked,
55
+ # or we don't own the lock)
56
+ def unlock!
57
+ return false if !locked?
58
+ return false if !owns_lock?
59
+
60
+ ::File.delete(lock_file_path)
61
+ @owns_lock = false
62
+ return true
63
+ end
64
+
65
+ # Forcibly unlocks this file, regardless of whether the lock is owned
66
+ # @return [Boolean] true if unlock was successful, false if the file isn't locked.
67
+ def force_unlock!
68
+ return false if !locked?
69
+ ::File.delete(lock_file_path)
70
+ @owns_lock = false
71
+ return true
72
+ end
73
+
74
+
75
+ # Waits for the file to become unlocked for 'max_time_ms'. If file is locked for that long, then raises an
76
+ # {Secret::::FileLockedError}.
77
+ # @param [Boolean] exclusive if TRUE, waits for an exclusive lock or shared lock, and then locks with an
78
+ # exclusive lock if a block is given. If FALSE, then only waits for an exclusive lock.
79
+ # @param [Integer] max_time_ms the maximum number of MS to wait until raising an error. If negative, waits forever.
80
+ # @param [Integer] sleep_ms the number of MS to sleep before polling the lock again
81
+ # @param [Proc] block pass a block to then execute block while locked (i.e. {#lock_and_do})
82
+ def wait_for_unlock(exclusive = false, max_time_ms = DEFAULT_LOCK_WAIT_MS, sleep_ms = 100, &block)
83
+ ms_start = (Time.now.to_f * 1000.0).to_i
84
+ wait_exclusive = exclusive or block_given?
85
+ sleep_ms = sleep_ms / 1000.0
86
+ while locked?(!wait_exclusive)
87
+ break if owns_lock?
88
+ sleep(sleep_ms)
89
+ continue if max_time_ms < 0
90
+ ms_new = (Time.now.to_f * 1000.0).to_i
91
+ if (ms_new - ms_start) > max_time_ms
92
+ raise Secret::FileLockedError, "Timeout of #{max_time_ms} MS exceeded while waiting for lock!"
93
+ end
94
+ end
95
+
96
+ # If we are here, waiting has finished
97
+ lock_and_do(exclusive, &block) if block_given?
98
+ end
99
+
100
+ # Locks and executes a block, then unlocks upon block execution. This will always ensure that the lock
101
+ # is released upon block execution, regardless of error or no.
102
+ # @param [Boolean] exclusive if TRUE, uses an exclusive lock. If false, uses a shared lock
103
+ # @raise [ArgumentError] if a block is not provided
104
+ # @raise [FileLockedError] if the file is locked
105
+ def lock_and_do(exclusive = false, &block)
106
+ raise ArgumentError, 'Block not given' unless block_given?
107
+ raise Secret::FileLockedError, 'Cannot lock a file if it is already locked!' if locked?
108
+ lock!(exclusive)
109
+ begin
110
+ block.call
111
+ rescue Exception => e
112
+ raise e
113
+ ensure
114
+ unlock!
115
+ end
116
+ end
117
+
118
+
119
+ # Gets information about the current lock:
120
+ #
121
+ # {:pid => 123123, :exclusive => true }
122
+ #
123
+ # @return [Hash,nil] information about the current lock, or nil if no lock is present
124
+ def lock_info
125
+ return nil unless locked?
126
+ begin
127
+ str = nil
128
+ ::File.open(lock_file_path, 'r', Secret::CHMOD_MODE) {|f| str = f.read }
129
+ ex,pid = str.split ':'
130
+ return {:exclusive => (ex == "1"), :pid => pid.to_i}
131
+ rescue Exception => e
132
+ return nil
133
+ end
134
+ end
135
+
136
+ protected
137
+
138
+ def lock_file_path
139
+ raise NotImplementedError, "'lock_file_path' needs to be implemented"
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,3 @@
1
+ module Secret
2
+ VERSION = "0.0.1"
3
+ end
data/lib/secret.rb ADDED
@@ -0,0 +1,36 @@
1
+ require "secret/version"
2
+ require "secret/locking"
3
+ require "secret/file"
4
+ require "secret/container"
5
+
6
+ module Secret
7
+
8
+ # The chmod mode to use for files.
9
+ CHMOD_MODE = 0700
10
+
11
+ # Gets the UID of the current process
12
+ def self.uid
13
+ return Process.uid
14
+ end
15
+
16
+ # Gets the default container
17
+ # @return [Secret::Container] the default container
18
+ def self.default
19
+ raise ArgumentError, "Must call 'Secret.configure_default' before you can access the default container" unless @default
20
+ return @default
21
+ end
22
+
23
+ # Configures the default container once
24
+ def self.configure_default(directory, auto_create = true)
25
+ unless @default
26
+ @default = Secret::Container.new(directory, auto_create)
27
+ @default.initialize_once!
28
+ return true
29
+ else
30
+ return false
31
+ end
32
+ end
33
+
34
+ class FileLockedError < Exception; end
35
+
36
+ end
data/secret.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'secret/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "secret"
8
+ spec.version = Secret::VERSION
9
+ spec.authors = ["Christopher Thornton"]
10
+ spec.email = ["rmdirbin@gmail.com"]
11
+ spec.description = %q{Keeps your files more secure by ensuring saved files are chmoded 0700 by the same user who is running the process.}
12
+ spec.summary = %q{Keeps files more secure on server environments}
13
+ spec.homepage = "https://github.com/cgthornt/secret"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
data/test/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'secret', path: '../'
@@ -0,0 +1 @@
1
+ Hello World!
data/test/tester.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'secret'
5
+
6
+ Secret.configure_default 'secrets'
7
+
8
+ container = Secret.default
9
+ container.cert_key.stash "Hello World!"
10
+ puts container.cert_key.encrypt_basic "password"
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: secret
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Christopher Thornton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Keeps your files more secure by ensuring saved files are chmoded 0700
47
+ by the same user who is running the process.
48
+ email:
49
+ - rmdirbin@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - lib/secret.rb
60
+ - lib/secret/container.rb
61
+ - lib/secret/encryption.rb
62
+ - lib/secret/file.rb
63
+ - lib/secret/locking.rb
64
+ - lib/secret/version.rb
65
+ - secret.gemspec
66
+ - test/Gemfile
67
+ - test/secrets/cert_key
68
+ - test/tester.rb
69
+ homepage: https://github.com/cgthornt/secret
70
+ licenses:
71
+ - MIT
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ segments:
83
+ - 0
84
+ hash: 296992438860896651
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ segments:
92
+ - 0
93
+ hash: 296992438860896651
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.25
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Keeps files more secure on server environments
100
+ test_files:
101
+ - test/Gemfile
102
+ - test/secrets/cert_key
103
+ - test/tester.rb