Skip to content

Commit

Permalink
initial checkin
Browse files Browse the repository at this point in the history
  • Loading branch information
sorah committed Jan 31, 2016
0 parents commit 702b162
Show file tree
Hide file tree
Showing 28 changed files with 1,230 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
config.yml
config.dev.yml
/storage/
2 changes: 2 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--format documentation
--color
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: ruby
rvm:
- 2.4.0
before_install: gem install bundler -v 1.10.6
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in acmesmith.gemspec
gemspec
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 sorah (Shota Fukumori)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
250 changes: 250 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# Acmesmith: An effective ACME client to operate on multiple servers environment with the cloud

Acmesmith is an [ACME (Automatic Certificate Management Environment)](https://github.com/ietf-wg-acme/acme) client that works perfect on environment with multiple servers. This client saves certificate and keys on cloud services (e.g. AWS S3) securely, then allow to deploy issued certificates onto your servers smoothly. This works well on [Let's encrypt](https://letsencrypt.org).

This tool is written in Ruby, but this saves certificates in simple scheme, so you can fetch certificate by your own simple scripts.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'acmesmith'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install acmesmith

## Usage

```
$ acmesmith register CONTACT # Create account key (contact e.g. mailto:xxx@example.org)
```

```
$ acmesmith authorize DOMAIN # Get authz for DOMAIN.
$ acmesmith request COMMON_NAME [SAN] # request certificate for CN +COMMON_NAME+ with SANs +SAN+
```

```
$ acmesmith list [COMMON_NAME] # list certificates or its versions
$ acmesmith current COMMON_NAME # show current version for certificate
$ acmesmith show-certificate COMMON_NAME # show certificate
$ acmesmith show-private-key COMMON_NAME # show private key
```

## Configuration

See [config.sample.yml](./config.sample.yml) to start.

``` yaml
endpoint: https://acme-staging.api.letsencrypt.org/
# endpoint: https://acme-v01.api.letsencrypt.org/ # productilon

storage:
# configure where to store keys and certificates; described later
challenge_responders:
# configure how to respond ACME challenges; described later

account_key_passphrase: password
certificate_key_passphrase: secret
```
### Storage
#### S3
```
storage:
type: s3
region:
bucket:
# prefix:
# aws_access_key: # aws credentials (optional); If omit, default configuration of aws-sdk use will be used.
# access_key_id:
# secret_access_key:
# session_token:
# use_kms: true
# kms_key_id: # KMS key id (optional); if omit, default AWS managed key for S3 will be used
# kms_key_id_account: # KMS key id for account key (optional); This overrides kms_key_id
# kms_key_id_certificate_key: # KMS key id for private keys for certificates (optional); This oveerides kms_key_id
```

This saves certificates and keys in the following S3 keys:

- `{prefix}/account.pem`: Account private key in pem
- `{prefix}/certs/{common_name}/current`: text file contains current version name
- `{prefix}/certs/{common_name}/{version}/cert.pem`: certificate in pem
- `{prefix}/certs/{common_name}/{version}/key.pem`: private key in pem
- `{prefix}/certs/{common_name}/{version}/chain.pem`: CA chain in pem
- `{prefix}/certs/{common_name}/{version}/fullchain.pem`: certificate + CA chain in pem. This is suitable for some server softwares like nginx.

#### Filesystem

This is not recommended. If you're planning to use this, make sure backing up the keys.

```
storage:
type: filesystem
path: /path/to/directory/to/store/keys
```

### Challenge Responders

Challenge responders responds to ACME challenges to prove domain ownership to CA.

#### Route53

Route53 responder supports `dns-01` challenge type. This assumes domain NS are managed under Route53 hosted zone.

```
challenge_responders:
- route53:
# aws_access_key: # aws credentials (optional); If omit, default configuration of aws-sdk use will be used.
# access_key_id:
# secret_access_key:
# session_token:
# hosted_zone_map: # hosted zone map (optional); This is to specify exactly one hosted zone to use. This will be required when there are multiple hosted zone with same domain name. Usually
# "example.org.": "/hostedzone/DEADBEEF"
```

## Vendor dependent notes

### AWS

#### IAM policy

##### All access (S3 + Route53 setup)

``` json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::{BUCKET-NAME}", "arn:aws:s3:::{BUCKET-NAME}/*"]
},
{
"Effect": "Allow",
"Action": "route53:ListHostedZones",
"Resource": "*"
}
{
"Effect": "Allow",
"Action": "route53:ChangeResourceRecordSets",
"Resource": ["arn:aws:route53:::hostedzone/*"]
}
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "*"
}
]
}
```

Note: You can limit allowed hosted zone by modifying `Resource` of `route53:ChangeResourceRecordSets`

##### Only fetching certificates

``` json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::{BUCKET-NAME}/certs/*"]
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::{BUCKET-NAME}"],
"Condition": {
"StringEquals": {
"s3:delimiter": "/"
},
"StringLike": {
"s3:prefix": "certs/*",
}
}
}
]
}
```

#### AWS KMS key policy for customer managed keys

If you're going to use `aws_kms_id` option to use customer managed keys instead of AWS managed default KMS key for Amazon S3, use the following policy as base:

Be sure to replace `{S3-REGION}` and `{YOUR-AWS-ACCOUNT-ID}` before applying it.

``` json
{
"Version": "2012-10-17",
"Id": "kms-acmesmith-s3-policy",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": "s3.{S3-REGION}.amazonaws.com",
"kms:CallerAccount": "{YOUR-AWS-ACCOUNT-ID}"
}
}
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{YOUR-AWS-ACCOUNT-ID}:root"
},
"Action": [
"kms:Describe*",
"kms:Get*",
"kms:List*",
"kms:Put*"
],
"Resource": "*"
}
]
}
```

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

### Todos

- Tests
- Support post actions (notifying servers, deploying to somewhere, etc...)
- Automated renewal command (request new certificates for existing certificates that expires soon)

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/acmesmith.


## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).

6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
28 changes: 28 additions & 0 deletions acmesmith.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'acmesmith/version'

Gem::Specification.new do |spec|
spec.name = "acmesmith"
spec.version = Acmesmith::VERSION
spec.authors = ["sorah (Shota Fukumori)"]
spec.email = ["her@sorah.jp"]

spec.summary = %q{ACME client to manage certificate in multi server environment with cloud services (e.g. AWS)}
spec.homepage = "https://github.com/sorah/acmesmith"
spec.license = "MIT"

spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "bin"
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency "acme-client"
spec.add_dependency "aws-sdk"
spec.add_dependency "thor"

spec.add_development_dependency "bundler", "~> 1.10"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec"
end
4 changes: 4 additions & 0 deletions bin/acmesmith
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env ruby
require 'acmesmith/command'

Acmesmith::Command.start
18 changes: 18 additions & 0 deletions config.sample.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
endpoint: https://acme-staging.api.letsencrypt.org/
# endpoint: https://acme-v01.api.letsencrypt.org/
storage:
type: s3
region: 'ap-northeast-1'
bucket: '...'
# prefix: '...'
# kms_key_id: 'arn:aws:kms:...'

# storage:
# type: filesystem
# path: ./storage

challenge_responders:
- route53: {}

account_key_passphrase: password
certificate_key_passphrase: secret
5 changes: 5 additions & 0 deletions lib/acmesmith.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require "acmesmith/version"

module Acmesmith
# Your code goes here...
end
Loading

0 comments on commit 702b162

Please sign in to comment.