Skip to content

Commit

Permalink
Fixes #23211 - Token based PuppetCA autosigning
Browse files Browse the repository at this point in the history
Removes old autosigning endpoints and adds new ones
that take a incoming CSR from puppet, extract the token
and forward it to foreman for verfication.
  • Loading branch information
Julian Todt committed May 3, 2018
1 parent 8a4bd9c commit f693783
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 149 deletions.
6 changes: 6 additions & 0 deletions lib/proxy/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def create_get(path, query={}, headers={})
req
end

def create_delete(path, query={}, headers={})
req = Net::HTTP::Delete.new(uri(path).path + "/" + query[:id])
req = add_headers(req, headers)
req
end

def uri(path)
URI.join(@base_uri.to_s, path)
end
Expand Down
8 changes: 8 additions & 0 deletions modules/puppetca/dependency_injection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Proxy::PuppetCa
module DependencyInjection
include Proxy::DependencyInjection::Accessors
def container_instance
@container_instance ||= ::Proxy::Plugins.instance.find {|p| p[:name] == :puppetca }[:di_container]
end
end
end
13 changes: 13 additions & 0 deletions modules/puppetca/plugin_configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module ::Proxy::PuppetCa
class PluginConfiguration
def load_classes
require 'puppetca/puppetca_certmanager'
require 'puppetca/dependency_injection'
require 'puppetca/puppetca_api'
end

def load_dependency_injection_wirings(container_instance, settings)
container_instance.dependency :cert_manager, lambda { ::Proxy::PuppetCa::Certmanager.new }
end
end
end
3 changes: 1 addition & 2 deletions modules/puppetca/puppetca.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
require 'puppetca/plugin_configuration'
require 'puppetca/puppetca_plugin'

module Proxy::PuppetCa; end
41 changes: 10 additions & 31 deletions modules/puppetca/puppetca_api.rb
Original file line number Diff line number Diff line change
@@ -1,56 +1,35 @@
require 'puppetca/puppetca_main'

module Proxy::PuppetCa
class Api < ::Sinatra::Base
extend Proxy::PuppetCa::DependencyInjection
inject_attr :cert_manager, :cert_manager

helpers ::Proxy::Helpers
authorize_with_trusted_hosts
authorize_with_ssl_client

get "/?" do
content_type :json
begin
Proxy::PuppetCa.list.to_json
cert_manager.list.to_json
rescue => e
log_halt 406, "Failed to list certificates: #{e}"
end
end

get "/autosign" do
content_type :json
begin
Proxy::PuppetCa.autosign_list.to_json
rescue => e
log_halt 406, "Failed to list autosign entries: #{e}"
end
end

post "/autosign/:certname" do
content_type :json
certname = params[:certname]
post "/autosign" do
request.body.rewind
begin
Proxy::PuppetCa.autosign(certname)
rescue => e
log_halt 406, "Failed to enable autosign for #{certname}: #{e}"
end
end

delete "/autosign/:certname" do
content_type :json
certname = params[:certname]
begin
Proxy::PuppetCa.disable(certname)
rescue Proxy::PuppetCa::NotPresent => e
log_halt 404, e.to_s
cert_manager.autosign(request.body.read) ? 200 : 404
rescue => e
log_halt 406, "Failed to remove autosign for #{certname}: #{e}"
log_halt 406, "Failed to check autosigning for CSR: #{e}"
end
end

post "/:certname" do
content_type :json
certname = params[:certname]
begin
Proxy::PuppetCa.sign(certname)
cert_manager.sign(certname)
rescue => e
log_halt 406, "Failed to sign certificate(s) for #{certname}: #{e}"
end
Expand All @@ -60,7 +39,7 @@ class Api < ::Sinatra::Base
begin
content_type :json
certname = params[:certname]
Proxy::PuppetCa.clean(certname)
cert_manager.clean(certname)
rescue Proxy::PuppetCa::NotPresent => e
log_halt 404, e.to_s
rescue => e
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
require 'openssl'
require 'set'
require 'puppetca/puppetca_csr'
require 'proxy/request'

module Proxy::PuppetCa
extend ::Proxy::Log
extend ::Proxy::Util
module ::Proxy::PuppetCa

class NotPresent < RuntimeError; end

class << self
class Certmanager
include ::Proxy::Log
include ::Proxy::Util

def sign certname
puppetca("sign", certname)
end
Expand All @@ -16,51 +19,52 @@ def clean certname
puppetca("clean", certname)
end

#remove certname from autosign if exists
def disable certname
raise "No such file #{autosign_file}" unless File.exist?(autosign_file)

found = false
entries = File.readlines(autosign_file).collect do |l|
if l.chomp != certname
l
else
found = true
nil
end
end.uniq.compact
if found
open(autosign_file, File::TRUNC|File::RDWR) do |autosign|
autosign.write entries.join
end
logger.debug "Removed #{certname} from autosign"
else
logger.debug "Attempt to remove nonexistent client autosign for #{certname}"
raise NotPresent, "Attempt to remove nonexistent client autosign for #{certname}"
# decide if csr should be autosigned
# parameter is csr to use
def autosign csr
if csr.nil?
logger.warn "Request did not include a CSR."
return false
end
if sign_all
logger.warn "Signing CSR without token verification."
return true
end
begin
req = Proxy::PuppetCa::CSR.new csr
token = req.challenge_password
rescue
logger.warn "Invalid CSR"
return false
end
if token.nil?
logger.warn "CSR did not include a token."
return false
end
foreman_csr_validation token
end

# add certname to puppet autosign file
# parameter is certname to use
def autosign certname
FileUtils.touch(autosign_file) unless File.exist?(autosign_file)

open(autosign_file, File::RDWR) do |autosign|
# Check that we don't have that host already
found = autosign.readlines.find { |line| line.chomp == certname }
autosign.puts certname unless found
end
logger.debug "Added #{certname} to autosign"
def sign_all
Proxy::PuppetCa::Plugin.settings.sign_all
end

# list of hosts which are now allowed to be installed via autosign
def autosign_list
return [] unless File.exist?(autosign_file)
File.read(autosign_file).split("\n").reject do |v|
v =~ /^\s*#.*|^$/ ## Remove comments and empty lines
end.map do |v|
v.chomp ## Strip trailing spaces
# Verify token by trying to delete it from foreman
def foreman_csr_validation token
logger.debug "Calling foreman with token '#{token}'."
fr = Proxy::HttpRequest::ForemanRequest.new
req = fr.request_factory.create_delete("api/puppetca_token", :id => token)
begin
response = fr.send_request(req)
if response.code.start_with? "2"
logger.debug "Autosigning CSR with token " + token
return true
end
rescue => e
logger.error "Error while calling foreman: " + e.to_s
return false
end
logger.debug "Denied autosign request for token " + token
return false
end

# list of all certificates and their state/fingerprint
Expand Down Expand Up @@ -129,10 +133,6 @@ def ssldir
Proxy::PuppetCa::Plugin.settings.ssldir
end

def autosign_file
Proxy::PuppetCa::Plugin.settings.autosignfile
end

# parse the puppetca --list output
def certificate str
case str
Expand Down
25 changes: 25 additions & 0 deletions modules/puppetca/puppetca_csr.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Proxy::PuppetCa
class CSR
attr_reader :csr

def initialize(raw_csr)
@csr = OpenSSL::X509::Request.new(raw_csr)
end

def challenge_password
attribute = custom_attributes.detect do |attr|
['challengePassword', '1.2.840.113549.1.9.7'].include?(attr[:oid])
end
attribute ? attribute[:value] : nil
end

def custom_attributes
@csr.attributes.map do |attr|
{
oid: attr.oid,
value: attr.value.value.first.value
}
end
end
end
end
5 changes: 4 additions & 1 deletion modules/puppetca/puppetca_plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ class Plugin < ::Proxy::Plugin
http_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__))
https_rackup_path File.expand_path("http_config.ru", File.expand_path("../", __FILE__))

default_settings :ssldir => '/var/lib/puppet/ssl', :autosignfile => '/etc/puppet/autosign.conf'
default_settings :ssldir => '/var/lib/puppet/ssl', :sign_all => false

plugin :puppetca, ::Proxy::VERSION

load_classes ::Proxy::PuppetCa::PluginConfiguration
load_dependency_injection_wirings ::Proxy::PuppetCa::PluginConfiguration
end
end
4 changes: 2 additions & 2 deletions test/puppetca/puppetca_config_test.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
require 'test_helper'
require 'puppetca/puppetca_plugin'
require 'puppetca/puppetca'

class PuppetCAConfigTest < Test::Unit::TestCase
def test_omitted_settings_have_default_values
Proxy::PuppetCa::Plugin.load_test_settings({})
assert_equal '/var/lib/puppet/ssl', Proxy::PuppetCa::Plugin.settings.ssldir
assert_equal '/etc/puppet/autosign.conf', Proxy::PuppetCa::Plugin.settings.autosignfile
assert_equal false, Proxy::PuppetCa::Plugin.settings.sign_all
end
end
Loading

0 comments on commit f693783

Please sign in to comment.