acmesmith 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.
@@ -0,0 +1,176 @@
1
+ require 'aws-sdk'
2
+
3
+ require 'acmesmith/storages/base'
4
+ require 'acmesmith/account_key'
5
+ require 'acmesmith/certificate'
6
+
7
+ module Acmesmith
8
+ module Storages
9
+ class S3 < Base
10
+ def initialize(aws_access_key: nil, bucket:, prefix: nil, region:, use_kms: true, kms_key_id: nil, kms_key_id_account: nil, kms_key_id_certificate_key: nil)
11
+ @region = region
12
+ @bucket = bucket
13
+ @prefix = prefix
14
+ if @prefix && !@prefix.end_with?('/')
15
+ @prefix += '/'
16
+ end
17
+
18
+ @use_kms = use_kms
19
+ @kms_key_id = kms_key_id
20
+ @kms_key_id_account = kms_key_id_account
21
+ @kms_key_id_certificate_key = kms_key_id_certificate_key
22
+
23
+ @s3 = Aws::S3::Client.new({region: region, signature_version: 'v4'}.tap do |opt|
24
+ opt[:credentials] = Aws::Credentials.new(aws_access_key['access_key_id'], aws_access_key['secret_access_key'], aws_access_key['session_token']) if aws_access_key
25
+ end)
26
+ end
27
+
28
+ attr_reader :region, :bucket, :prefix, :use_kms, :kms_key_id, :kms_key_id_account, :kms_key_id_certificate_key
29
+
30
+ def get_account_key
31
+ obj = @s3.get_object(bucket: bucket, key: account_key_key)
32
+ AccountKey.new obj.body.read
33
+ rescue Aws::S3::Errors::NoSuchKey
34
+ raise NotExist
35
+ end
36
+
37
+ def account_key_exist?
38
+ begin
39
+ get_account_key
40
+ rescue NotExist
41
+ return false
42
+ else
43
+ return true
44
+ end
45
+ end
46
+
47
+ def put_account_key(key, passphrase = nil)
48
+ raise AlreadyExist if account_key_exist?
49
+ params = {
50
+ bucket: bucket,
51
+ key: account_key_key,
52
+ body: key.export(passphrase),
53
+ content_type: 'application/x-pem-file',
54
+ }
55
+ if use_kms
56
+ params[:server_side_encryption] = 'aws:kms'
57
+ key_id = kms_key_id_account || kms_key_id
58
+ params[:ssekms_key_id] = key_id if key_id
59
+ end
60
+
61
+ @s3.put_object(params)
62
+ end
63
+
64
+ def put_certificate(cert, passphrase = nil, update_current: true)
65
+ h = cert.export(passphrase)
66
+
67
+ put = -> (key, body, kms) do
68
+ params = {
69
+ bucket: bucket,
70
+ key: key,
71
+ body: body,
72
+ content_type: 'application/x-pem-file',
73
+ }
74
+ if kms
75
+ params[:server_side_encryption] = 'aws:kms'
76
+ key_id = kms_key_id_certificate_key || kms_key_id
77
+ params[:ssekms_key_id] = key_id if key_id
78
+ end
79
+ @s3.put_object(params)
80
+ end
81
+
82
+ put.call certificate_key(cert.common_name, cert.version), "#{h[:certificate].rstrip}\n", false
83
+ put.call chain_key(cert.common_name, cert.version), "#{h[:chain].rstrip}\n", false
84
+ put.call fullchain_key(cert.common_name, cert.version), "#{h[:fullchain].rstrip}\n", false
85
+ put.call private_key_key(cert.common_name, cert.version), "#{h[:private_key].rstrip}\n", true
86
+
87
+ if update_current
88
+ @s3.put_object(
89
+ bucket: bucket,
90
+ key: certificate_current_key(cert.common_name),
91
+ content_type: 'text/plain',
92
+ body: cert.version,
93
+ )
94
+ end
95
+ end
96
+
97
+ def get_certificate(common_name, version: 'current')
98
+ version = certificate_current(common_name) if version == 'current'
99
+
100
+ certificate = @s3.get_object(bucket: bucket, key: certificate_key(common_name, version)).body.read
101
+ chain = @s3.get_object(bucket: bucket, key: chain_key(common_name, version)).body.read
102
+ private_key = @s3.get_object(bucket: bucket, key: private_key_key(common_name, version)).body.read
103
+ Certificate.new(certificate, chain, private_key)
104
+ rescue Aws::S3::Errors::NoSuchKey
105
+ raise NotExist
106
+ end
107
+
108
+ def list_certificates
109
+ certs_prefix = "#{prefix}certs/"
110
+ @s3.list_objects(
111
+ bucket: bucket,
112
+ delimiter: '/',
113
+ prefix: certs_prefix,
114
+ ).each.flat_map do |page|
115
+ regexp = /\A#{Regexp.escape(certs_prefix)}/
116
+ page.common_prefixes.map { |_| _.sub(regexp, '').sub(/\/.+\z/, '') }.uniq
117
+ end
118
+ end
119
+
120
+ def list_certificate_versions(common_name)
121
+ cert_ver_prefix = "#{prefix}certs/#{common_name}/"
122
+ @s3.list_objects(
123
+ bucket: bucket,
124
+ delimiter: '/',
125
+ prefix: cert_ver_prefix,
126
+ ).each.flat_map do |page|
127
+ regexp = /\A#{Regexp.escape(cert_ver_prefix)}/
128
+ page.common_prefixes.map { |_| _.sub(regexp, '').sub(/\/.+\z/, '') }.uniq
129
+ end.reject { |_| _ == 'current' }
130
+ end
131
+
132
+ def get_current_certificate_version(common_name)
133
+ certificate_current(common_name)
134
+ end
135
+
136
+ private
137
+
138
+ def account_key_key
139
+ "#{prefix}account.pem"
140
+ end
141
+
142
+ def certificate_base_key(cn, ver)
143
+ "#{prefix}certs/#{cn}/#{ver}"
144
+ end
145
+
146
+ def certificate_current_key(cn)
147
+ certificate_base_key(cn, 'current')
148
+ end
149
+
150
+ def certificate_current(cn)
151
+ @s3.get_object(
152
+ bucket: bucket,
153
+ key: certificate_current_key(cn),
154
+ ).body.read.chomp
155
+ rescue Aws::S3::Errors::NoSuchKey
156
+ raise NotExist
157
+ end
158
+
159
+ def certificate_key(cn, ver)
160
+ "#{certificate_base_key(cn, ver)}/cert.pem"
161
+ end
162
+
163
+ def private_key_key(cn, ver)
164
+ "#{certificate_base_key(cn, ver)}/key.pem"
165
+ end
166
+
167
+ def chain_key(cn, ver)
168
+ "#{certificate_base_key(cn, ver)}/chain.pem"
169
+ end
170
+
171
+ def fullchain_key(cn, ver)
172
+ "#{certificate_base_key(cn, ver)}/fullchain.pem"
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,26 @@
1
+ module Acmesmith
2
+ module Utils
3
+ module Finder
4
+ def self.find(const, prefix, name)
5
+ retried = false
6
+ constant_name = name.to_s.gsub(/\A.|_./) { |s| s[-1].upcase }
7
+
8
+ begin
9
+ const.const_get constant_name, false
10
+ rescue NameError
11
+ unless retried
12
+ begin
13
+ require "#{prefix}/#{name}"
14
+ rescue LoadError
15
+ end
16
+
17
+ retried = true
18
+ retry
19
+ end
20
+
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Acmesmith
2
+ VERSION = "0.1.0"
3
+ end
data/script/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "acmesmith"
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/script/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acmesmith
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - sorah (Shota Fukumori)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: acme-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: 'Acmesmith is an [ACME (Automatic Certificate Management Environment)](https://github.com/ietf-wg-acme/acme)
98
+ client that works perfect on environment with multiple servers. This client saves
99
+ certificate and keys on cloud services (e.g. AWS S3) securely, then allow to deploy
100
+ issued certificates onto your servers smoothly. This works well on [Let''s encrypt](https://letsencrypt.org).
101
+
102
+ '
103
+ email:
104
+ - her@sorah.jp
105
+ executables:
106
+ - acmesmith
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - ".gitignore"
111
+ - ".rspec"
112
+ - ".travis.yml"
113
+ - Gemfile
114
+ - LICENSE.txt
115
+ - README.md
116
+ - Rakefile
117
+ - acmesmith.gemspec
118
+ - bin/acmesmith
119
+ - config.sample.yml
120
+ - lib/acmesmith.rb
121
+ - lib/acmesmith/account_key.rb
122
+ - lib/acmesmith/certificate.rb
123
+ - lib/acmesmith/challenge_responders.rb
124
+ - lib/acmesmith/challenge_responders/base.rb
125
+ - lib/acmesmith/challenge_responders/route53.rb
126
+ - lib/acmesmith/command.rb
127
+ - lib/acmesmith/config.rb
128
+ - lib/acmesmith/storages.rb
129
+ - lib/acmesmith/storages/base.rb
130
+ - lib/acmesmith/storages/filesystem.rb
131
+ - lib/acmesmith/storages/s3.rb
132
+ - lib/acmesmith/utils/finder.rb
133
+ - lib/acmesmith/version.rb
134
+ - script/console
135
+ - script/setup
136
+ homepage: https://github.com/sorah/acmesmith
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.5.1
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: ACME client (Let's encrypt client) to manage certificate in multi server
160
+ environment with cloud services (e.g. AWS)
161
+ test_files: []