-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
initial implementation of persistence [RFC/WIP] #63
Open
rmoriz
wants to merge
3
commits into
schubergphilis:master
Choose a base branch
from
rmoriz:rmoriz/persistence
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,12 @@ | ||
# | ||
# Author:: Thijs Houtenbos <[email protected]> | ||
# Author:: Roland Moriz <[email protected]> | ||
|
||
# Cookbook:: acme | ||
# Library:: matchers | ||
# | ||
# Copyright 2015-2017 Schuberg Philis | ||
# Copyright 2017 Moriz GmbH | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
|
@@ -21,6 +24,7 @@ | |
if defined?(ChefSpec) | ||
ChefSpec.define_matcher(:acme_certificate) | ||
ChefSpec.define_matcher(:acme_selfsigned) | ||
ChefSpec.define_matcher(:acme_persistence) | ||
|
||
def create_acme_selfsigned(resource_name) | ||
ChefSpec::Matchers::ResourceMatcher.new(:acme_selfsigned, :create, resource_name) | ||
|
@@ -29,4 +33,12 @@ def create_acme_selfsigned(resource_name) | |
def create_acme_certificate(resource_name) | ||
ChefSpec::Matchers::ResourceMatcher.new(:acme_certificate, :create, resource_name) | ||
end | ||
|
||
def save_acme_persistence(resource_name) | ||
ChefSpec::Matchers::ResourceMatcher.new(:acme_persistence, :save, resource_name) | ||
end | ||
|
||
def load_acme_persistence(resource_name) | ||
ChefSpec::Matchers::ResourceMatcher.new(:acme_persistence, :load, resource_name) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
resource_name :acme_persistence | ||
|
||
property :cn, String, name_property: true | ||
property :alt_names, Array | ||
property :key, String, required: true | ||
property :crt, String | ||
property :chain, String | ||
property :fullchain, String | ||
|
||
property :master, [TrueClass, FalseClass], default: false | ||
|
||
property :data_bag_name, String, required: true | ||
property :encrypt, [TrueClass, FalseClass], default: true | ||
property :secret, String | ||
|
||
property :owner, String, default: 'root' | ||
property :group, String, default: 'root' | ||
|
||
action :save do | ||
unless master | ||
Chef::Log.warn "master property not set, will not save #{cn} certificates" | ||
return | ||
end | ||
|
||
data = { | ||
'id' => cn, | ||
'alt_names' => alt_names, | ||
'created_by' => node['fqdn'], | ||
'created_at' => Time.now | ||
} | ||
|
||
# 'key', 'cert', 'chain' are also used in the data bag format used by | ||
# https://github.com/atomic-penguin/cookbook-certificate/blob/master/providers/manage.rb | ||
data['key'] = ::File.read(new_resource.key) if new_resource.key | ||
data['cert'] = ::File.read(new_resource.crt) if new_resource.crt | ||
data['chain'] = ::File.read(new_resource.chain) if new_resource.chain | ||
|
||
data['fullchain'] = ::File.read(new_resource.fullchain) if new_resource.fullchain | ||
|
||
chef_data_bag_item "#{data_bag_name}/#{cn}" do | ||
raw_data data | ||
if new_resource.encrypt && (new_resource.secret || default_data_bag_secret) | ||
encrypt true | ||
encryption_version 2 | ||
secret new_resource.secret || default_data_bag_secret | ||
end | ||
end | ||
end | ||
|
||
# Matrix: | ||
# | ||
# +------------------------+-----------------+--------------------------------------+ | ||
# | file | data bag item | action | | ||
# | ---------------------- | --------------- | ------------------------------------ | | ||
# | does not exist | exists | create from data bag item | | ||
# | does not exist | does not exist | nothing (-> acme_selfsigned) | | ||
# | exists (self-signed) | exists | create from data bag item | | ||
# | exists (self-signed) | does not exist | nothing (~> renew acme_certificate) | | ||
# | exists (valid) | is newer | create from data bag item | | ||
# | exists (valid) | is older | nothing (~> renew acme_certificate) | | ||
# | exists (expired) | is newer | create from data bag item | | ||
# | exists (expired) | does not exist | nothing (~> renew acme_certificate | | ||
# +------------------------+-----------------+--------------------------------------+ | ||
# | ||
action :load do | ||
begin | ||
existing_cert = ::OpenSSL::X509::Certificate.new(::File.read(crt || fullchain)) | ||
rescue Errno::ENOENT => e | ||
Chef::Log.warn("certificate file #{crt || fullchain} does not exist yet: #{e}") | ||
rescue OpenSSL::X509::CertificateError => e | ||
Chef::Log.error("certificate file #{crt || fullchain} exists but is broken: #{e}") | ||
end | ||
|
||
item = load_data_bag_item(data_bag_name, 'id:' + cn, secret) | ||
return unless item | ||
|
||
render_to_files(item) if !existing_cert || | ||
self_signed?(existing_cert) || | ||
item_newer?(item, existing_cert) | ||
end | ||
|
||
action_class do | ||
def load_data_bag_item(data_bag_name, _data_bag_item, secret = nil) | ||
item = search(data_bag_name, 'id:' + cn).first | ||
item = ::Chef::EncryptedDataBagItem.new(item, secret) if item && secret | ||
item | ||
end | ||
|
||
def self_signed?(cert) | ||
cert.issuer == cert.subject | ||
end | ||
|
||
def item_newer?(item, existing_cert) | ||
item_cert = ::OpenSSL::X509::Certificate.new item['cert'] if item['cert'] | ||
item_cert ||= ::OpenSSL::X509::Certificate.new item['fullchain'] if item['fullchain'] | ||
item_cert.not_before > existing_cert.not_before | ||
rescue OpenSSL::X509::CertificateError => e | ||
Chef::Log.error("data bag item #{new_resource.data_bag_name}/#{item['id']} is broken: #{e}") | ||
end | ||
|
||
def render_to_files(item) | ||
file "acme_store: #{new_resource.cn} SSL key" do | ||
path new_resource.key | ||
owner new_resource.owner | ||
group new_resource.group | ||
mode 00400 | ||
content item['key'] | ||
sensitive true | ||
action :create | ||
end | ||
|
||
file "acme_store: #{new_resource.cn} SSL crt" do | ||
path new_resource.crt | ||
owner new_resource.owner | ||
group new_resource.group | ||
mode 00644 | ||
content item['cert'] | ||
action :create | ||
|
||
only_if { !!item['cert'] } | ||
end | ||
|
||
file "acme_store: #{new_resource.cn} SSL fullchain" do | ||
path new_resource.fullchain | ||
owner new_resource.owner | ||
group new_resource.group | ||
mode 00644 | ||
content item['fullchain'] | ||
action :create | ||
|
||
only_if { !!item['fullchain'] } | ||
end | ||
|
||
file "acme_store: #{new_resource.cn} SSL chain" do | ||
path new_resource.chain | ||
owner new_resource.owner | ||
group new_resource.group | ||
mode 00644 | ||
content item['chain'] | ||
action :create | ||
|
||
only_if { !!item['chain'] } | ||
end | ||
end | ||
|
||
def default_data_bag_secret | ||
Chef::EncryptedDataBagItem.load_secret(Chef::Config[:encrypted_data_bag_secret]) | ||
rescue => e | ||
Chef::Log.error "property 'secret' is not provided and the default encrypted_data_bag_secret file does not exist: #{e}" | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you don’t need this custom method, and can instead use the built-in
data_bag_item()
method, since you’re in a scope that has it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My solution was supposed to support three cases:
using chef's
data_bag_item
without a secret automatically falls back to the secret defined inChef::Config[:encrypted_data_bag_secret]
in case of an encrypted data bag (no way to customize and iirc providingnil
as secret throws an exception, too)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rmoriz ah! I forgot about the
nil
throwing an exception. In that case, this is great.