acmesmith 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []