secret 0.0.1 → 0.0.2
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 +23 -20
- data/Gemfile +4 -4
- data/LICENSE.txt +22 -22
- data/README.md +60 -52
- data/Rakefile +1 -1
- data/lib/secret/container.rb +82 -63
- data/lib/secret/encryption.rb +44 -44
- data/lib/secret/file.rb +178 -186
- data/lib/secret/version.rb +3 -3
- data/lib/secret.rb +34 -36
- data/secret.gemspec +23 -23
- data/test/Gemfile +6 -3
- data/test/spawn.rb +16 -0
- data/test/tester.rb +36 -10
- metadata +11 -24
- data/lib/secret/locking.rb +0 -142
- data/test/secrets/cert_key +0 -1
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a56b7cd7533a64c852d89901ba1460a2b487d498
|
4
|
+
data.tar.gz: 2993017272c967c3b9e7249f6be3eff1a9bc3e22
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f263a042a3fb1373b73a7548143b36fc1eb6147cbdbd5ce58e7f6aa34c22bfa40cb014d6934186b220ee08c305976b3b87df8ffb1721091c120243b363297fdd
|
7
|
+
data.tar.gz: a80eb4b104b6fde26085b253e9d6065aa145d9a6cf65d8a54041e98310f4dd69e7b29abd4a88b3f1b2fd9f0faab5ee5f7bc126d2d23b2a0cc9b850a3778e3b76
|
data/.gitignore
CHANGED
@@ -1,20 +1,23 @@
|
|
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
|
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
|
21
|
+
*.komodoproject
|
22
|
+
.komodotools
|
23
|
+
test/secrets/*
|
data/Gemfile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
|
3
|
-
# Specify your gem's dependencies in secret.gemspec
|
4
|
-
gemspec
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in secret.gemspec
|
4
|
+
gemspec
|
data/LICENSE.txt
CHANGED
@@ -1,22 +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.
|
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
CHANGED
@@ -1,52 +1,60 @@
|
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
+
# Support for nested directories
|
38
|
+
file = secret.file "certs/file.crt"
|
39
|
+
file.stash "Contents of CA File"
|
40
|
+
puts file.contents
|
41
|
+
|
42
|
+
# Also support for shorthand syntax
|
43
|
+
secret.stash "certs/file.key", "Contents of this key file!"
|
44
|
+
puts secret.contents "certs/file.key"
|
45
|
+
```
|
46
|
+
|
47
|
+
## How Secure is It?
|
48
|
+
Ths is only *somewhat* secure and will provide protection against:
|
49
|
+
|
50
|
+
* Someone who gains access to your server with non-root access
|
51
|
+
* Other non-root server processes
|
52
|
+
|
53
|
+
However, this will **not** protect you against:
|
54
|
+
|
55
|
+
* People with root access
|
56
|
+
* Arbitrary code execution attacks by the owner (i.e. an `eval()` gone wrong)
|
57
|
+
|
58
|
+
## Other Features
|
59
|
+
This gem also includes locking support, meaning that it will be resillient against multiple processes
|
60
|
+
writing to a file. This will **not** lock multiple threads from the same process.
|
data/Rakefile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/secret/container.rb
CHANGED
@@ -1,64 +1,83 @@
|
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
#
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
return
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
|
1
|
+
module Secret
|
2
|
+
class Container
|
3
|
+
|
4
|
+
attr_reader :directory, :files, :chmod_mode
|
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, chmod = Secret::CHMOD_MODE)
|
10
|
+
@directory = directory
|
11
|
+
@chmod_mode = chmod
|
12
|
+
|
13
|
+
@files = {}
|
14
|
+
|
15
|
+
# Do some checking about our directory
|
16
|
+
if ::File.exist?(directory)
|
17
|
+
raise ArgumentError, "Specified directory '#{directory}' is actually a file!" unless ::File.directory?(directory)
|
18
|
+
|
19
|
+
# Now make our directory if auto_create
|
20
|
+
else
|
21
|
+
raise ArgumentError, "Specified directory '#{directory}' does not exist!" unless auto_create
|
22
|
+
FileUtils.mkdir_p(directory, :mode => chmod_mode) # Only give read/write access to this user
|
23
|
+
end
|
24
|
+
raise ArgumentError, "Directory '#{directory}' is not writeable!" unless ::File.writable?(directory)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Stashes the contents of a file
|
28
|
+
def stash(path, contents)
|
29
|
+
file(path).stash(contents)
|
30
|
+
end
|
31
|
+
|
32
|
+
def contents(path)
|
33
|
+
file(path).contents
|
34
|
+
end
|
35
|
+
|
36
|
+
# Gets a file stored in the container.
|
37
|
+
# @param [Symbol] filename the name of the file.
|
38
|
+
# @return [Secret::File] a secret file
|
39
|
+
def file(filename)
|
40
|
+
fn = filename.to_s
|
41
|
+
f = files[fn]
|
42
|
+
return f unless f.nil?
|
43
|
+
|
44
|
+
d = ::File.dirname(fn)
|
45
|
+
container = d == "." ? self : dir(d)
|
46
|
+
|
47
|
+
f = Secret::File.new(container, ::File.basename(filename) + Secret::FILE_EXT)
|
48
|
+
files[fn] = f
|
49
|
+
return f
|
50
|
+
end
|
51
|
+
|
52
|
+
# Another container within the directory
|
53
|
+
def dir(name)
|
54
|
+
Container.new ::File.join(directory, name), true, chmod_mode
|
55
|
+
end
|
56
|
+
|
57
|
+
def method_missing(meth, *args, &block)
|
58
|
+
super(meth, *args, &block) if args.any? or block_given?
|
59
|
+
return file(meth)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# Deletes the cache of objects
|
64
|
+
def uncache!
|
65
|
+
@files = {}
|
66
|
+
end
|
67
|
+
|
68
|
+
# This should be called once in some sort of initializer.
|
69
|
+
def initialize_once!
|
70
|
+
destroy_all_locks!
|
71
|
+
end
|
72
|
+
|
73
|
+
# Viciously destroys all locks that the file and its containers may have. Use carefully!
|
74
|
+
# @return [Integer] the number of files destroyed.
|
75
|
+
def destroy_all_locks!
|
76
|
+
files = Dir[::File.join(directory, '*.lock')]
|
77
|
+
files.each{|f| ::File.delete(f) }
|
78
|
+
return files.count
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
end
|
64
83
|
end
|
data/lib/secret/encryption.rb
CHANGED
@@ -1,45 +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
|
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
45
|
end
|
data/lib/secret/file.rb
CHANGED
@@ -1,187 +1,179 @@
|
|
1
|
-
module Secret
|
2
|
-
|
3
|
-
# Handles file operations.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
# @
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Delete
|
122
|
-
::File.delete(backup_file_path)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
#
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
def backup_file_path
|
180
|
-
return file_path + '.bak'
|
181
|
-
end
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
end
|
1
|
+
module Secret
|
2
|
+
|
3
|
+
# Handles file operations. Uses Ruby's internal file locking mechanisms.
|
4
|
+
class File
|
5
|
+
# include Secret::Encryption
|
6
|
+
|
7
|
+
attr_reader :container, :identifier
|
8
|
+
|
9
|
+
# Creates a new secret file. The specified identifier doesn't already need to exist.
|
10
|
+
# @param [Secret::Container] container the container object
|
11
|
+
# @param [Symbol] identifier an unique identifier for this container
|
12
|
+
def initialize(container, identifier)
|
13
|
+
raise ArgumentError, "Container must be a Secret::Container object" unless container.is_a?(Secret::Container)
|
14
|
+
@container = container; @identifier = identifier
|
15
|
+
touch!
|
16
|
+
ensure_writeable!
|
17
|
+
end
|
18
|
+
|
19
|
+
# Checks whether this file actually exists or not
|
20
|
+
# @return [Boolean] true if the file exists (i.e. has content), false if otherwise.
|
21
|
+
def exist?
|
22
|
+
::File.exist?(file_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Gets a file stream of this file. If the file doesn't exist, then a blank file will be created. By default,
|
26
|
+
# this allows you to write to the file. However, please use the {#stash} command, as it accounts for mid-write
|
27
|
+
# crashes. Don't forget to close the file stream when you're done!
|
28
|
+
# @param [String] mode the mode for this file. Currently defaults to 'r+', which is read-write, with the
|
29
|
+
# file pointer at the beginning of the file.
|
30
|
+
# @return [IO] an IO stream to this file, if not using a block
|
31
|
+
# @note Uses an exclusive lock on this file
|
32
|
+
# @example
|
33
|
+
# file = container.some_file
|
34
|
+
#
|
35
|
+
# # Unsafe way!
|
36
|
+
# io = file.stream
|
37
|
+
# io.write "Hello World!"
|
38
|
+
# io.close
|
39
|
+
#
|
40
|
+
# # Safe way, with locking support
|
41
|
+
# file.stream do |f|
|
42
|
+
# f.write "Hello World!"
|
43
|
+
# end
|
44
|
+
def stream(mode = 'r', &block)
|
45
|
+
touch!
|
46
|
+
ensure_writeable!
|
47
|
+
return ::File.open(file_path, mode, container.chmod_mode) unless block_given?
|
48
|
+
::File.open(file_path, mode, container.chmod_mode) do |f|
|
49
|
+
begin
|
50
|
+
f.flock(::File::LOCK_EX) # Lock with exclusive mode
|
51
|
+
block.call(f)
|
52
|
+
ensure
|
53
|
+
f.flock(::File::LOCK_UN)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# Gets the contents of the file in a string format. Will return an empty string if the file doesn't exist, or the
|
60
|
+
# file just so happens to be empty.
|
61
|
+
# @return [String] the contents of the file
|
62
|
+
def contents
|
63
|
+
str = nil
|
64
|
+
stream 'r' do |f|
|
65
|
+
str = f.read
|
66
|
+
end
|
67
|
+
return str
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Creates a new file if it doesn't exist. Doesn't actually change the last updated timestamp.
|
72
|
+
# @return [Boolean] true if an empty file was created, false if the file already existed.
|
73
|
+
def touch!
|
74
|
+
unless exist?
|
75
|
+
::File.open(file_path, 'w', container.chmod_mode) {}
|
76
|
+
secure!
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
|
82
|
+
# Secures the file by chmoding it to 0700
|
83
|
+
# @raise [IOError] if the file doesn't exist on the server.
|
84
|
+
def secure!
|
85
|
+
raise IOError, "File doesn't exist" unless exist?
|
86
|
+
::File.chmod(container.chmod_mode, file_path)
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# Stashes some content into the file! This will write a temporary backup file before stashing, in order to prevent
|
91
|
+
# any partial writes if the server crashes. Once this finishes executing, you can be sure that contents have been
|
92
|
+
# written.
|
93
|
+
# @param [String] content the contents to stash. **Must be a string!**
|
94
|
+
# @raise [ArgumentError] if content is anything other than a String object!
|
95
|
+
def stash(content)
|
96
|
+
raise ArgumentError, "Content must be a String (was type of type #{content.class.name})" unless content.is_a?(String)
|
97
|
+
touch!
|
98
|
+
ensure_writeable!
|
99
|
+
|
100
|
+
# Think of this as a beginning of a transaction.
|
101
|
+
::File.open(file_path, 'a', container.chmod_mode) do |f|
|
102
|
+
begin
|
103
|
+
f.flock(::File::LOCK_EX)
|
104
|
+
|
105
|
+
# Open a temporary file for writing, and close it immediately
|
106
|
+
::File.open(tmp_file_path, "w", container.chmod_mode){|f| f.write content }
|
107
|
+
|
108
|
+
# Rename tmp file to backup file now we know contents are sane
|
109
|
+
::File.rename(tmp_file_path, backup_file_path)
|
110
|
+
|
111
|
+
# Truncate file contents to zero bytes
|
112
|
+
f.truncate 0
|
113
|
+
|
114
|
+
# Write content
|
115
|
+
f.write content
|
116
|
+
ensure
|
117
|
+
# Now unlock file!
|
118
|
+
f.flock(::File::LOCK_UN)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Delete backup file
|
122
|
+
::File.delete(backup_file_path)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Committed! Secure it just in case
|
126
|
+
secure!
|
127
|
+
end
|
128
|
+
|
129
|
+
def ensure_writeable!
|
130
|
+
unless ::File.writable?(file_path)
|
131
|
+
raise FileUnreadableError, "File is not writeable - perhaps it was created by a different process?"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Attempts to restore a backup (i.e. if the computer crashed while doing a stash command)
|
136
|
+
# @return [Boolean] true if the backup was successfully restored, false otherwise
|
137
|
+
def restore_backup!
|
138
|
+
return false unless ::File.exist?(backup_file_path)
|
139
|
+
|
140
|
+
# We know backup exists, so let's write to the file. We want to truncate file contents.
|
141
|
+
# Now copy file contents over from the backup file. We use this method to use locking.
|
142
|
+
::File.open(file_path, 'w', container.chmod_mode) do |f|
|
143
|
+
begin
|
144
|
+
f.flock ::File::LOCK_EX
|
145
|
+
::File.open(backup_file_path, 'r', container.chmod_mode) do |b|
|
146
|
+
f.write b.read
|
147
|
+
end
|
148
|
+
ensure
|
149
|
+
f.flock ::File::LOCK_UN
|
150
|
+
end
|
151
|
+
end
|
152
|
+
return true
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
# Gets the actual file path of this container. Intentionally made private (security through obscurity)
|
160
|
+
# @return [String] the absolute path to where this file is
|
161
|
+
def file_path
|
162
|
+
@the_file_path = ::File.join(container.directory, identifier.to_s) if @the_file_path.nil?
|
163
|
+
return @the_file_path
|
164
|
+
end
|
165
|
+
|
166
|
+
# The path of the temporary file. Used as a temporary container for stashing. This file will then be re-named.
|
167
|
+
# @return [String] the path to the temporary file
|
168
|
+
def tmp_file_path
|
169
|
+
return file_path + ".tmp"
|
170
|
+
end
|
171
|
+
|
172
|
+
# Gets the path of the backup file
|
173
|
+
# @return [String] the path of the backup file
|
174
|
+
def backup_file_path
|
175
|
+
return file_path + '.bak'
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
187
179
|
end
|
data/lib/secret/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module Secret
|
2
|
-
VERSION = "0.0.
|
3
|
-
end
|
1
|
+
module Secret
|
2
|
+
VERSION = "0.0.2"
|
3
|
+
end
|
data/lib/secret.rb
CHANGED
@@ -1,36 +1,34 @@
|
|
1
|
-
require "secret/version"
|
2
|
-
require "secret/
|
3
|
-
require "secret/
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
1
|
+
require "secret/version"
|
2
|
+
require "secret/file"
|
3
|
+
require "secret/container"
|
4
|
+
|
5
|
+
module Secret
|
6
|
+
|
7
|
+
# The chmod mode to use for files.
|
8
|
+
CHMOD_MODE = 0700
|
9
|
+
|
10
|
+
# The file extension for secret files
|
11
|
+
FILE_EXT = ".sfile"
|
12
|
+
|
13
|
+
# Gets the default container
|
14
|
+
# @return [Secret::Container] the default container
|
15
|
+
def self.default
|
16
|
+
raise ArgumentError, "Must call 'Secret.configure_default' before you can access the default container" unless @default
|
17
|
+
return @default
|
18
|
+
end
|
19
|
+
|
20
|
+
# Configures the default container once
|
21
|
+
def self.configure_default(directory, auto_create = true)
|
22
|
+
unless @default
|
23
|
+
@default = Secret::Container.new(directory, auto_create)
|
24
|
+
@default.initialize_once!
|
25
|
+
return true
|
26
|
+
else
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class FileUnreadableError < Exception; end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
data/secret.gemspec
CHANGED
@@ -1,23 +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
|
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
CHANGED
data/test/spawn.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'secret'
|
4
|
+
Secret.configure_default 'secrets'
|
5
|
+
container = Secret.default
|
6
|
+
|
7
|
+
f = "running.txt"
|
8
|
+
File.open(f, 'w'){|f| f.write "Running..." }
|
9
|
+
|
10
|
+
container.test1.stream 'w' do |f|
|
11
|
+
puts "Sleeping for 10 seconds..."
|
12
|
+
sleep(10)
|
13
|
+
f.write "New Contents"
|
14
|
+
end
|
15
|
+
|
16
|
+
File.delete(f)
|
data/test/tester.rb
CHANGED
@@ -1,10 +1,36 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'bundler/setup'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
container = Secret.default
|
9
|
-
|
10
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
# require 'win32/process'
|
4
|
+
|
5
|
+
require 'secret'
|
6
|
+
|
7
|
+
Secret.configure_default 'secrets'
|
8
|
+
container = Secret.default
|
9
|
+
|
10
|
+
container.stash "key.crt", "The contents of this key!"
|
11
|
+
|
12
|
+
puts "Contents of key: '#{container.contents 'key.crt'}'"
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
exit(0)
|
17
|
+
container.something.stash "My Secret Text!"
|
18
|
+
container.file(:someting_else).stash "More secret text"
|
19
|
+
container.dir('test')
|
20
|
+
|
21
|
+
puts "Testing some multi-process action"
|
22
|
+
container.test1.stash "Original Content"
|
23
|
+
|
24
|
+
# Windows - testing file locking
|
25
|
+
p = Process.create(
|
26
|
+
:app_name => 'ruby spawn.rb',
|
27
|
+
:creation_flags => Process::DETACHED_PROCESS,
|
28
|
+
:process_inherit => false,
|
29
|
+
:thread_inherit => true,
|
30
|
+
)
|
31
|
+
|
32
|
+
sleep(1)
|
33
|
+
puts p.inspect
|
34
|
+
|
35
|
+
puts "New contents:"
|
36
|
+
puts "'#{container.test1.contents}'"
|
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secret
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Christopher Thornton
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
11
|
+
date: 2013-05-14 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: bundler
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ~>
|
20
18
|
- !ruby/object:Gem::Version
|
@@ -22,7 +20,6 @@ dependencies:
|
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - ~>
|
28
25
|
- !ruby/object:Gem::Version
|
@@ -30,17 +27,15 @@ dependencies:
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rake
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '>='
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '>='
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
description: Keeps your files more secure by ensuring saved files are chmoded 0700
|
@@ -60,44 +55,36 @@ files:
|
|
60
55
|
- lib/secret/container.rb
|
61
56
|
- lib/secret/encryption.rb
|
62
57
|
- lib/secret/file.rb
|
63
|
-
- lib/secret/locking.rb
|
64
58
|
- lib/secret/version.rb
|
65
59
|
- secret.gemspec
|
66
60
|
- test/Gemfile
|
67
|
-
- test/
|
61
|
+
- test/spawn.rb
|
68
62
|
- test/tester.rb
|
69
63
|
homepage: https://github.com/cgthornt/secret
|
70
64
|
licenses:
|
71
65
|
- MIT
|
66
|
+
metadata: {}
|
72
67
|
post_install_message:
|
73
68
|
rdoc_options: []
|
74
69
|
require_paths:
|
75
70
|
- lib
|
76
71
|
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
-
none: false
|
78
72
|
requirements:
|
79
|
-
- -
|
73
|
+
- - '>='
|
80
74
|
- !ruby/object:Gem::Version
|
81
75
|
version: '0'
|
82
|
-
segments:
|
83
|
-
- 0
|
84
|
-
hash: 296992438860896651
|
85
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
-
none: false
|
87
77
|
requirements:
|
88
|
-
- -
|
78
|
+
- - '>='
|
89
79
|
- !ruby/object:Gem::Version
|
90
80
|
version: '0'
|
91
|
-
segments:
|
92
|
-
- 0
|
93
|
-
hash: 296992438860896651
|
94
81
|
requirements: []
|
95
82
|
rubyforge_project:
|
96
|
-
rubygems_version:
|
83
|
+
rubygems_version: 2.0.0
|
97
84
|
signing_key:
|
98
|
-
specification_version:
|
85
|
+
specification_version: 4
|
99
86
|
summary: Keeps files more secure on server environments
|
100
87
|
test_files:
|
101
88
|
- test/Gemfile
|
102
|
-
- test/
|
89
|
+
- test/spawn.rb
|
103
90
|
- test/tester.rb
|
data/lib/secret/locking.rb
DELETED
@@ -1,142 +0,0 @@
|
|
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
|
data/test/secrets/cert_key
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
Hello World!
|