amarillo 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a94e70709b0f4147b7c04711d1e1f58a54eeea7a743364ee4e8dc2d25984b83a
4
- data.tar.gz: 1f179ac5eea2d4919e22d09a44b491cbed3e2704128d9da71a9c00a10c177275
3
+ metadata.gz: b53b0e0803cc4bd89185cbfc67aeb3d468ab7f34988073d7c405f6e88f38e6fe
4
+ data.tar.gz: ef2b8ec288bae0acd3924aa61ded5ccde2f9915bb15ec664316693641d4391e8
5
5
  SHA512:
6
- metadata.gz: 3acd69905f4138ab59a2de3a1cb8493a6e1ea9363ccaaa622c8d565112850e78ce11ea6fad940052c7a4795a3e19980bb2f24f392775ac4aeab463f19ef6d013
7
- data.tar.gz: 0025b7ca2705e934750ca1bf267644ddc9fd610072ab64a2d5fc7c762b1c16d200b6ea1c6f74b87723c2a75fe6251982478ee72cfc9bc7c5b11581211b2d1a9d
6
+ metadata.gz: 16eb56677f677b3b752de0af6e1b67dc6495b01a494e84e8c935ea19db84f3b16a2bf71cbc67893025388b4289b39d9ebf84f86b2e81b0abf0742c7707bd2e33
7
+ data.tar.gz: 94af28ad358c6313fa4a43c00b0b31fc9f01d577556c90e8bf49c9396c06946c7d689abdd6fcd826b558fbcf74b3767b93c183055c2db7514510369033a8c1f6
data/bin/amarillo CHANGED
@@ -23,9 +23,18 @@
23
23
  require 'optparse'
24
24
  require 'fileutils'
25
25
  require 'amarillo'
26
+ require 'amarillo/environment'
26
27
 
27
28
  options = {}
28
29
  OptionParser.new do |opts|
30
+ opts.on("-i", "--initialize", "Initialize amarillo defaults") do |i|
31
+ options[:initialize] = i
32
+ end
33
+
34
+ opts.on("-r", "--renew", "Renew certificates") do |r|
35
+ options[:renew] = r
36
+ end
37
+
29
38
  opts.on("-z", "--zone ZONE", "Hosted zone") do |z|
30
39
  options[:zone] = z
31
40
  end
@@ -38,8 +47,8 @@ OptionParser.new do |opts|
38
47
  options[:name] = n
39
48
  end
40
49
 
41
- opts.on("-o", "--output-directory OUTPUT_PATH", "Output directory of certificates and keys") do |o|
42
- options[:certificate_path] = o
50
+ opts.on("-a", "--amarillo-home AMARILLO_HOME", "Home directory for configuration, keys, and certificates") do |o|
51
+ options[:amarillo_home] = a
43
52
  end
44
53
 
45
54
  opts.on("-h", "--help") do |h|
@@ -55,57 +64,56 @@ Usage: amarillo --zone ZONE --name COMMONNAME --email EMAIL [--output-path OUTP
55
64
  exit 0
56
65
  end
57
66
 
58
- if options[:zone].nil? or
59
- options[:email].nil? or
60
- options[:name].nil? then
67
+ if options[:initialize] then
68
+ e = Amarillo::Environment.new
61
69
 
62
- puts "Usage: amarillo --zone ZONE --name COMMONNAME --email EMAIL [--output-directory OUTPUT_PATH]"
70
+ e.init options[:zone], options[:email]
63
71
 
64
- exit -1
72
+ exit 0
65
73
  end
66
74
 
67
- if options[:output_path].nil?
68
- certificate_path = "/etc/ssl/amarillo"
69
- key_path = "/etc/ssl/amarillo/private"
75
+ e = Amarillo::Environment.new
76
+ e.load_config
77
+
78
+ if options[:zone].nil?
79
+ zone = e.config["defaults"]["zone"]
80
+ if zone.nil? or zone == '' then
81
+ puts "Error: Specify a default zone in config.yml or use --zone"
82
+ exit -1
83
+ end
70
84
  else
71
- certificate_path = options[:output_path]
72
- key_path = "#{certificate_path}/private"
85
+ zone = options[:zone]
73
86
  end
74
87
 
75
- # Try to create the certificate_path and key_path
76
- keypath_dirname = File.dirname(key_path)
77
- unless File.directory?(keypath_dirname)
78
- begin
79
- FileUtils.mkdir_p(keypath_dirname)
80
- rescue
81
- writableMessage = <<-"HEREDOC"
82
- Error: #{certificate_path} and #{key_path} are not writable directories to store keys and certificates.
83
- HEREDOC
84
- print writableMessage
85
- exit -1
86
- end
88
+ if options[:email].nil?
89
+ email = e.config["defaults"]["email"]
90
+ if email.nil? or email == '' then
91
+ puts "Error: Specify a default e-mail address in config.yml or use --email"
92
+ exit -1
93
+ end
94
+ else
95
+ email = options[:email]
87
96
  end
88
97
 
89
- # Check for existense of aws.env
90
- awsEnvPath = Pathname.new("/etc/amarillo/aws.env")
91
- if not awsEnvPath.exist? then
92
- awsEnvMessage = <<-"HEREDOC"
93
- Error: /etc/amarillo/aws.env AWS credentials file not found.
94
-
95
- /etc/amarillo/aws.env must exist and set both aws_access_key_id and aws_secret_access_key for an IAM user with AmazonRoute53FullAccess permissions.
96
-
97
- Example:
98
+ if options[:name].nil? and options[:renew].nil? then
99
+ puts "Usage: amarillo --name COMMONNAME [--zone ZONE] [--email EMAIL] [--amarillo-home AMARILLO_HOME]"
100
+ exit -1
101
+ else
102
+ name = options[:name]
103
+ end
98
104
 
99
- [default]
100
- aws_access_key_id = your_access_key_id
101
- aws_secret_access_key = your_secret_access_key
102
- HEREDOC
105
+ if options[:amarillo_home].nil?
106
+ amarillo_home = "/usr/local/etc/amarillo"
107
+ else
108
+ amarillo_home = options[:amarillo_home]
109
+ end
103
110
 
104
- print awsEnvMessage
111
+ y = Amarillo.new amarillo_home
105
112
 
106
- exit -1
113
+ if options[:renew] then
114
+ y.renewCertificates
115
+ else
116
+ y.requestCertificate zone, name, email, nil
107
117
  end
108
118
 
109
119
 
110
- y = Amarillo.new(certificate_path, key_path, awsEnvPath)
111
- y.requestCertificate(options[:zone], options[:name], options[:email])
data/lib/amarillo.rb CHANGED
@@ -29,14 +29,23 @@ require 'openssl' # Key Generation
29
29
  require 'aws-sdk-core' # Credentials
30
30
  require 'aws-sdk-route53' # Route 53
31
31
  require 'resolv' # DNS Resolvers
32
+ require 'yaml' # YAML
32
33
 
33
34
  class Amarillo
34
35
 
35
- def initialize(certificatePath, keyPath, awsEnvPath)
36
+ def initialize(amarilloHome)
36
37
 
37
- @certificatePath = certificatePath
38
- @keyPath = keyPath
39
- @awsEnvPath = awsEnvPath
38
+ @environment = Amarillo::Environment.new(amarilloHome: amarilloHome)
39
+
40
+ if not @environment.verify then raise "Cannot initialize amarillo" end
41
+
42
+ @environment.load_config
43
+
44
+ @certificatePath = @environment.certificatePath
45
+ @keyPath = @environment.keyPath
46
+ @config = @environment.config
47
+ @awsEnvFile = @environment.awsEnvFile
48
+ @configsPath = @environment.configsPath
40
49
 
41
50
  @logger = Logger.new(STDOUT)
42
51
  @logger.level = Logger::INFO
@@ -65,19 +74,35 @@ class Amarillo
65
74
  valid
66
75
  end
67
76
 
68
- def requestCertificate(zone, commonName, email)
77
+ def get_route53
78
+ shared_creds = Aws::SharedCredentials.new(path: "#{@awsEnvFile}")
79
+
80
+ Aws.config.update(credentials: shared_creds)
81
+
82
+ region = @config["defaults"]["region"] ? @config["defaults"]["region"] : 'us-east-2'
83
+ @route53 = Aws::Route53::Client.new(region: region)
84
+ @hzone = @route53.list_hosted_zones(max_items: 100).hosted_zones.detect { |z| z.name == "#{@zone}." }
85
+
86
+ end
87
+
88
+ def requestCertificate(zone, commonName, email, key_type)
89
+
90
+ @zone = zone
91
+
92
+ acmeUrl = @config["defaults"]["acme_url"] ? @config["defaults"]["acme_url"] : 'https://acme-v02.api.letsencrypt.org/directory'
93
+
94
+ # Load private key
69
95
 
70
- @logger.info "Generating 4096-bit RSA private key"
71
- key = OpenSSL::PKey::RSA.new(4096)
72
- client = Acme::Client.new(
73
- private_key: key,
74
- directory: 'https://acme-v02.api.letsencrypt.org/directory'
75
- )
96
+ @logger.info "Loading 4096-bit RSA private key for Let's Encrypt account"
97
+ @logger.info "Let's Encrypt directory set to #{acmeUrl}"
76
98
 
77
- account = client.new_account(
78
- contact: "mailto:#{email}",
79
- terms_of_service_agreed: true
80
- )
99
+ key = OpenSSL::PKey::RSA.new File.read "#{@keyPath}/letsencrypt.key"
100
+
101
+ client = Acme::Client.new private_key: key,
102
+ directory: acmeUrl
103
+
104
+ account = client.new_account contact: "mailto:#{email}",
105
+ terms_of_service_agreed: true
81
106
 
82
107
  # Generate a certificate order
83
108
  @logger.info "Creating certificate order request for #{commonName}"
@@ -90,16 +115,7 @@ class Amarillo
90
115
 
91
116
  @logger.info "Challenge value for #{commonName} in #{zone} zone is #{challengeValue}"
92
117
 
93
- # Update Route 53
94
-
95
- shared_creds = Aws::SharedCredentials.new(path: "#{@awsEnvPath}")
96
- Aws.config.update(credentials: shared_creds)
97
-
98
- # TODO: Allow the user to set the region
99
- route53 = Aws::Route53::Client.new(region: 'us-east-2')
100
- hzone = route53.list_hosted_zones(max_items: 100)
101
- .hosted_zones
102
- .detect { |z| z.name == "#{zone}." }
118
+ self.get_route53
103
119
 
104
120
  change = {
105
121
  action: 'UPSERT',
@@ -114,30 +130,20 @@ class Amarillo
114
130
  }
115
131
 
116
132
  options = {
117
- hosted_zone_id: hzone.id,
133
+ hosted_zone_id: @hzone.id,
118
134
  change_batch: {
119
135
  changes: [change]
120
136
  }
121
137
  }
122
138
 
123
- route53.change_resource_record_sets(options)
124
-
125
- nameservers = []
139
+ @route53.change_resource_record_sets(options)
126
140
 
127
- @logger.info "Looking up nameservers for #{zone}"
141
+ nameservers = @environment.get_zone_nameservers
128
142
 
129
- Resolv::DNS.open(nameserver: '9.9.9.9') do |dns|
130
- while nameservers.length == 0
131
- nameservers = dns.getresources(
132
- zone,
133
- Resolv::DNS::Resource::IN::NS
134
- ).map(&:name).map(&:to_s)
135
- end
136
- end
137
-
138
- @logger.info "Waiting for DNS record to propogate"
143
+ @logger.info "Waiting for DNS record to propagate"
139
144
  while !check_dns(commonName, nameservers, challengeValue)
140
- sleep 1
145
+ sleep 2
146
+ @logger.info "Still waiting..."
141
147
  end
142
148
 
143
149
  authorization.dns.request_validation
@@ -150,15 +156,40 @@ class Amarillo
150
156
  authorization.dns.reload
151
157
  end
152
158
 
153
- @logger.info "Requesting certificate..."
159
+ @logger.info "Generating key"
160
+
161
+ # Create certificate yml
162
+ certConfig = {
163
+ "commonName" => commonName,
164
+ "email" => email,
165
+ "zone" => zone
166
+ }
154
167
 
155
- cert_key = OpenSSL::PKey::RSA.new(4096)
156
- csr = Acme::Client::CertificateRequest.new(
157
- private_key: cert_key,
158
- names: [commonName]
159
- )
168
+ if key_type
169
+ certConfig["key_type"] = key_type
170
+ else
171
+ key_type = @config["defaults"]["key_type"]
172
+ certConfig["key_type"] = key_type
173
+ end
160
174
 
161
- order.finalize(csr: csr)
175
+ type, args = key_type.split(',')
176
+
177
+ if type == 'ec' then
178
+ certPrivateKey = OpenSSL::PKey::EC.new(args).generate_key
179
+ elsif type == 'rsa' then
180
+ certPrivateKey = OpenSSL::PKey::RSA.new(args)
181
+ end
182
+
183
+ @logger.info "Requesting certificate..."
184
+ csr = Acme::Client::CertificateRequest.new private_key: certPrivateKey,
185
+ names: [commonName]
186
+
187
+ begin
188
+ order.finalize(csr: csr)
189
+ rescue
190
+ @logger.error("ERROR")
191
+ self.cleanup label, record_type, challengeValue
192
+ end
162
193
 
163
194
  sleep(1) while order.status == 'processing'
164
195
 
@@ -168,8 +199,9 @@ class Amarillo
168
199
  @logger.info "Saving private key to #{keyOutputPath}"
169
200
 
170
201
  File.open(keyOutputPath, "w") do |f|
171
- f.puts cert_key.to_pem.to_s
202
+ f.puts certPrivateKey.to_pem.to_s
172
203
  end
204
+ File.chmod(0600, keyOutputPath)
173
205
 
174
206
  @logger.info "Saving certificate to #{certOutputPath}"
175
207
 
@@ -177,6 +209,14 @@ class Amarillo
177
209
  f.puts order.certificate
178
210
  end
179
211
 
212
+ certConfigFile = "#{@configsPath}/#{commonName}.yml"
213
+ File.write(certConfigFile, certConfig.to_yaml)
214
+
215
+ self.cleanup label, record_type, challengeValue
216
+
217
+ end
218
+
219
+ def cleanup(label, record_type, challengeValue)
180
220
  @logger.info "Cleaning up..."
181
221
 
182
222
  change = {
@@ -192,12 +232,45 @@ class Amarillo
192
232
  }
193
233
 
194
234
  options = {
195
- hosted_zone_id: hzone.id,
235
+ hosted_zone_id: @hzone.id,
196
236
  change_batch: {
197
237
  changes: [change]
198
238
  }
199
239
  }
200
240
 
201
- route53.change_resource_record_sets(options)
241
+ @route53.change_resource_record_sets(options)
242
+ end
243
+
244
+ def renewCertificate(zone, commonName, email)
245
+
246
+ end
247
+
248
+ def renewCertificates
249
+ t = Time.now
250
+ @logger.info "Renewing certificates"
251
+
252
+ Dir["#{@configsPath}/*.yml"].each do |c|
253
+ config = YAML.load(File.read(c))
254
+
255
+ cn = config["commonName"]
256
+ email = config["email"]
257
+ zone = config["zone"]
258
+ key_type = config["key_type"]
259
+
260
+ certificatePath = "#{@certificatePath}/#{cn}.crt"
261
+ raw = File.read certificatePath
262
+ certificate = OpenSSL::X509::Certificate.new raw
263
+ daysToExpiration = (certificate.not_after - t).to_i / (24 * 60 * 60)
264
+
265
+ if daysToExpiration < 30 then
266
+ @logger.info "#{cn} certificate needs to be renewed"
267
+ self.requestCertificate zone, cn, email, key_type
268
+ else
269
+ @logger.info "#{cn} certificate does not need to be renewed"
270
+ end
271
+ end
202
272
  end
203
273
  end
274
+
275
+
276
+ require 'amarillo/environment'
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Inspired by Pete Keen's (pete@petekeen.net) post
4
+ # https://www.petekeen.net/lets-encrypt-without-certbot
5
+ #
6
+ # Copyright 2021 iAchieved.it LLC
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ # of this software and associated documentation files (the "Software"), to deal
10
+ # in the Software without restriction, including without limitation the rights
11
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ # copies of the Software, and to permit persons to whom the Software is
13
+ # furnished to do so, subject to the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included in
16
+ # all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ # SOFTWARE.
25
+
26
+ require 'logger'
27
+ require 'yaml'
28
+ require 'aws-sdk-core'
29
+
30
+ DefaultAmarilloHome = "/usr/local/etc/amarillo/"
31
+
32
+ class Amarillo::Environment
33
+
34
+ attr_reader :certificatePath, :keyPath, :configPath, :configsPath, :config, :awsEnvFile
35
+
36
+ def initialize(amarilloHome: DefaultAmarilloHome)
37
+
38
+ @logger = Logger.new(STDOUT)
39
+ @logger.level = Logger::INFO
40
+
41
+ @amarilloHome = amarilloHome
42
+ @certificatePath = amarilloHome + "/certificates"
43
+ @keyPath = amarilloHome + "/keys"
44
+ @configPath = amarilloHome
45
+ @configsPath = amarilloHome + "/configs"
46
+ @configFile = amarilloHome + "/config.yml"
47
+ @awsEnvFile = amarilloHome + "/aws.env"
48
+
49
+ end
50
+
51
+ # Public method to create default configuration files
52
+ def init(zone = nil, email = nil)
53
+
54
+ unless File.exist?(@configsPath) and File.directory?(@configsPath)
55
+ begin
56
+ @logger.info "Creating #{@configsPath} directory"
57
+ FileUtils.mkpath(@configsPath)
58
+ rescue
59
+ @logger.error("Cannot create #{@configsPath} directory")
60
+ return false
61
+ end
62
+ end
63
+
64
+ unless File.exist?(@certificatePath) and File.directory?(@certificatePath)
65
+ begin
66
+ @logger.info "Creating #{@certificatePath} directory"
67
+ FileUtils.mkpath(@certificatePath)
68
+ rescue
69
+ @logger.error("Cannot create #{@certificatePath} directory")
70
+ return false
71
+ end
72
+ end
73
+
74
+ unless File.exist?(@keyPath) and File.directory?(@keyPath)
75
+ begin
76
+ @logger.info "Creating #{@keyPath} directory"
77
+ FileUtils.mkpath(@keyPath)
78
+ rescue
79
+ @logger.error("Cannot create #{@keyPath} directory")
80
+ return false
81
+ end
82
+ end
83
+
84
+ # Create aws.env
85
+ unless File.exist?(@awsEnvFile) then
86
+ awsEnv = <<-HEREDOC
87
+ [default]
88
+ aws_access_key_id =
89
+ aws_secret_access_key =
90
+ HEREDOC
91
+ @logger.info("Creating blank #{@awsEnvFile}")
92
+ @logger.warn("NOTE: aws_access_key_id and aws_secret_access_key must be specified in this file.")
93
+ File.write(@awsEnvFile, awsEnv)
94
+ else
95
+ @logger.info("Refusing to overwrite #{@awsEnvFile}")
96
+ end
97
+
98
+ # Create config.yml
99
+ unless File.exist?(@configFile) then
100
+ @logger.info("Creating default configuration #{@configFile}")
101
+ config = {
102
+ "defaults" => {
103
+ "region" => 'us-east-2',
104
+ "profile" => 'default',
105
+ "email" => email,
106
+ "zone" => zone,
107
+ "nameservers" => ['208.67.222.222', '9.9.9.9'],
108
+ "key_type" => 'ec,secp384r1'
109
+ }}
110
+ File.write(@configFile, config.to_yaml)
111
+ else
112
+ @logger.info("Refusing to overwrite #{@configFile}")
113
+ end
114
+
115
+ # Create RSA private key for Let's Encrypt account
116
+ privateKeyPath = "#{@keyPath}/letsencrypt.key"
117
+
118
+ unless File.exist? privateKeyPath then
119
+ @logger.info "Generating 4096-bit RSA private key for Let's Encrypt account"
120
+
121
+ privateKey = OpenSSL::PKey::RSA.new(4096)
122
+
123
+
124
+ File.open(privateKeyPath, "w") do |f|
125
+ f.puts privateKey.to_pem.to_s
126
+ end
127
+ File.chmod(0400, privateKeyPath)
128
+ end
129
+ end
130
+
131
+ #
132
+ # Verify paths exist and are writable
133
+ # Verify aws.env exists and is formatted correctly
134
+ # Verify config.yml exists and is formatted correctly
135
+ #
136
+ def verify
137
+ @logger.info "Verifying amarillo environment"
138
+ if not verify_env() then return false end
139
+ if not verify_awsenv() then return false end
140
+ if not verify_config() then return false end
141
+ return true
142
+ end
143
+
144
+ def verify_env
145
+ unless File.stat(@certificatePath).writable? then
146
+ @logger.error(@certificatePath + " is not writable")
147
+ return false
148
+ end
149
+
150
+ unless File.stat(@keyPath).writable? then
151
+ @logger.error(@keyPath + " is not writable")
152
+ return false
153
+ end
154
+
155
+ return true
156
+ end
157
+
158
+ def verify_awsenv()
159
+ awsEnvFile = Pathname.new(@awsEnvFile)
160
+ if not awsEnvFile.exist? then
161
+ @logger.error("#{awsEnvFile} does not exist")
162
+ return false
163
+ end
164
+
165
+ awsCredentials = Aws::SharedCredentials.new(path: "#{@awsEnvFile}")
166
+
167
+ if awsCredentials.credentials.access_key_id.length != 20 then
168
+ @logger.error("#{@awsEnvFile} aws_access_key_id does not appear to be valid")
169
+ return false
170
+ end
171
+
172
+ if awsCredentials.credentials.secret_access_key.length != 40 then
173
+ @logger.error("#{@awsEnvFile} aws_secret_access_key does not appear to be valid")
174
+ return false
175
+ end
176
+
177
+ return true
178
+ end
179
+
180
+ def verify_config()
181
+ if not File.exist?(@configFile) then
182
+ @logger.error("#{@configFile} does not exist")
183
+ return false
184
+ end
185
+
186
+ begin
187
+ YAML.load(File.read(@configFile))
188
+ rescue
189
+ @logger.error("Unable to load configuration file")
190
+ return false
191
+ end
192
+
193
+ return true
194
+ end
195
+
196
+ def load_config()
197
+ if verify_config() then
198
+ @config = YAML.load(File.read(@configFile))
199
+ end
200
+ end
201
+
202
+ def get_zone_nameservers
203
+
204
+ self.load_config
205
+
206
+ nameservers = @config["defaults"]["nameservers"]
207
+ zone = @config["defaults"]["zone"]
208
+
209
+ @logger.info "Looking up nameservers for #{zone}"
210
+
211
+ zone_nameservers = []
212
+ Resolv::DNS.open(nameserver: nameservers) do |dns|
213
+ while zone_nameservers.length == 0
214
+ zone_nameservers = dns.getresources(
215
+ zone,
216
+ Resolv::DNS::Resource::IN::NS
217
+ ).map(&:name).map(&:to_s)
218
+ end
219
+ end
220
+
221
+ @logger.info "Found #{zone_nameservers.length} nameservers for zone #{zone}: #{zone_nameservers}"
222
+
223
+ return zone_nameservers
224
+ end
225
+
226
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amarillo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - iAchieved.it LLC
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-28 00:00:00.000000000 Z
11
+ date: 2021-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acme-client
@@ -75,11 +75,12 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - bin/amarillo
77
77
  - lib/amarillo.rb
78
+ - lib/amarillo/environment.rb
78
79
  homepage: https://github.com/iachievedit/amarillo
79
80
  licenses:
80
81
  - MIT
81
82
  metadata: {}
82
- post_install_message:
83
+ post_install_message:
83
84
  rdoc_options: []
84
85
  require_paths:
85
86
  - lib
@@ -94,8 +95,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
95
  - !ruby/object:Gem::Version
95
96
  version: '0'
96
97
  requirements: []
97
- rubygems_version: 3.0.3
98
- signing_key:
98
+ rubygems_version: 3.2.3
99
+ signing_key:
99
100
  specification_version: 4
100
101
  summary: Amarillo
101
102
  test_files: []