smart_proxy_chef 0.1.2 → 0.1.3
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.
- checksums.yaml +4 -4
- data/lib/smart_proxy_chef_plugin/authentication.rb +46 -36
- data/lib/smart_proxy_chef_plugin/chef_api.rb +37 -35
- data/lib/smart_proxy_chef_plugin/chef_resource_api.rb +88 -0
- data/lib/smart_proxy_chef_plugin/connection_helper.rb +20 -0
- data/lib/smart_proxy_chef_plugin/foreman_api.rb +23 -6
- data/lib/smart_proxy_chef_plugin/version.rb +1 -1
- data/settings.d/chef.yml.example +1 -1
- metadata +5 -6
- data/lib/smart_proxy_chef_plugin/resources/base.rb +0 -20
- data/lib/smart_proxy_chef_plugin/resources/client.rb +0 -18
- data/lib/smart_proxy_chef_plugin/resources/node.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1c5fd05ae72a1469295dedb30ea5b17d6a31c8e
|
4
|
+
data.tar.gz: 2831d4c31cf71833fc813b6dfda6b8f2217cc020
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5219d453c2eabc2ac58187c021b12e05ad689eddeab17c636cd5f8cd0d2ac5a70e5468b9162b6741a4bb6d35f150c6382b66bcdf76ae34152f3b1ef898e43d6
|
7
|
+
data.tar.gz: f133518caf0024b07d9ebd9b36b8c054012550186b29909b784a6710ee7178caa441717a9ad7bfbd53ee3af7ad71b2b567697ece4fb39ec3991099537b5cd5a8
|
@@ -1,47 +1,57 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
require 'base64'
|
6
|
-
require 'openssl'
|
7
|
-
|
8
|
-
def verify_signature_request(client_name,signature,body)
|
9
|
-
#We need to retrieve client public key
|
10
|
-
#to verify signature
|
11
|
-
begin
|
12
|
-
client = Resources::Client.new.show(client_name)
|
13
|
-
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
|
14
|
-
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
15
|
-
Errno::ECONNREFUSED, OpenSSL::SSL::SSLError => e
|
16
|
-
raise Proxy::Error::Unauthorized, "Failed to authenticate node: "+e.message
|
17
|
-
end
|
1
|
+
require 'smart_proxy_chef_plugin/connection_helper'
|
2
|
+
require 'digest/sha2'
|
3
|
+
require 'base64'
|
4
|
+
require 'openssl'
|
18
5
|
|
19
|
-
|
20
|
-
|
6
|
+
module ChefPlugin
|
7
|
+
module Authentication
|
8
|
+
def authenticate_with_chef_signature
|
9
|
+
helpers ConnectionHelper, InstanceMethods, ::Proxy::Helpers
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
public_key.verify(OpenSSL::Digest::SHA256.new, decoded_signature, hash_body)
|
11
|
+
before do
|
12
|
+
authenticate_chef_signature(request)
|
13
|
+
end
|
26
14
|
end
|
27
15
|
|
28
|
-
|
29
|
-
|
16
|
+
module InstanceMethods
|
17
|
+
def verify_signature_request(client_name, signature,body)
|
18
|
+
#We need to retrieve client public key to verify signature
|
19
|
+
begin
|
20
|
+
client = get_connection.clients.fetch(client_name)
|
21
|
+
rescue StandardError => e
|
22
|
+
log_halt 401, "Failed to authenticate node: " + e.message + "\n#{e.backtrace.join("\n")}"
|
23
|
+
end
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
client_name = request.env['HTTP_X_FOREMAN_CLIENT']
|
34
|
-
signature = request.env['HTTP_X_FOREMAN_SIGNATURE']
|
25
|
+
log_halt 401, "Could not find client with name #{client_name}" if client.nil?
|
26
|
+
public_key = OpenSSL::PKey::RSA.new(client.public_key)
|
35
27
|
|
36
|
-
|
37
|
-
|
28
|
+
#signature is base64 encoded
|
29
|
+
decoded_signature = Base64.decode64(signature)
|
30
|
+
hash_body = Digest::SHA256.hexdigest(body)
|
31
|
+
public_key.verify(OpenSSL::Digest::SHA256.new, decoded_signature, hash_body)
|
38
32
|
end
|
39
33
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
34
|
+
def authenticate_chef_signature(request)
|
35
|
+
logger.debug('starting chef signature authentication')
|
36
|
+
content = request.env["rack.input"].read
|
37
|
+
|
38
|
+
auth = true
|
39
|
+
if ChefPlugin::Plugin.settings.chef_authenticate_nodes
|
40
|
+
client_name = request.env['HTTP_X_FOREMAN_CLIENT']
|
41
|
+
logger.debug("header HTTP_X_FOREMAN_CLIENT: #{client_name}")
|
42
|
+
signature = request.env['HTTP_X_FOREMAN_SIGNATURE']
|
43
|
+
|
44
|
+
log_halt 401, "Failed to authenticate node #{client_name}. Missing some headers" if client_name.nil? or signature.nil?
|
45
|
+
auth = verify_signature_request(client_name, signature, content)
|
46
|
+
end
|
47
|
+
|
48
|
+
if auth
|
49
|
+
log_halt 406, "Body is empty for node #{client_name}" if content.nil?
|
50
|
+
logger.debug("#{client_name} authenticated successfully")
|
51
|
+
return true
|
52
|
+
else
|
53
|
+
log_halt 401, "Failed to authenticate node #{client_name}"
|
54
|
+
end
|
45
55
|
end
|
46
56
|
end
|
47
57
|
end
|
@@ -1,50 +1,52 @@
|
|
1
|
-
require 'smart_proxy_chef_plugin/
|
2
|
-
require 'smart_proxy_chef_plugin/
|
1
|
+
require 'smart_proxy_chef_plugin/connection_helper'
|
2
|
+
require 'smart_proxy_chef_plugin/chef_resource_api'
|
3
3
|
|
4
4
|
module ChefPlugin
|
5
5
|
class ChefApi < ::Sinatra::Base
|
6
|
-
helpers ::Proxy::Helpers
|
7
|
-
|
8
|
-
|
9
|
-
logger.debug "Showing node #{params[:fqdn]}"
|
6
|
+
helpers ::Proxy::Helpers, ConnectionHelper
|
7
|
+
extend ChefResourceApi
|
8
|
+
authorize_with_trusted_hosts
|
10
9
|
|
10
|
+
before do
|
11
11
|
content_type :json
|
12
|
-
|
13
|
-
node.to_json
|
14
|
-
else
|
15
|
-
log_halt 404, "Node #{params[:fqdn]} not found"
|
16
|
-
end
|
12
|
+
@connection = get_connection
|
17
13
|
end
|
18
14
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
content_type :json
|
23
|
-
if (node = Resources::Client.new.show(params[:fqdn]))
|
24
|
-
node.to_json
|
25
|
-
else
|
26
|
-
log_halt 404, "Client #{params[:fqdn]} not found"
|
27
|
-
end
|
15
|
+
error ChefAPI::Error::UnknownAttribute do
|
16
|
+
log_halt 400, {:result => false, :errors => [env['sinatra.error'].message]}.to_json
|
28
17
|
end
|
29
18
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
result = Resources::Node.new.delete(params[:fqdn])
|
34
|
-
log_halt 400, "Node #{params[:fqdn]} could not be deleteded" unless result
|
35
|
-
|
36
|
-
logger.debug "Node #{params[:fqdn]} deleted"
|
37
|
-
{ :result => result }.to_json
|
19
|
+
error ChefAPI::Error::ResourceNotFound do
|
20
|
+
log_halt 404, {:result => false, :errors => [env['sinatra.error'].message]}.to_json
|
38
21
|
end
|
39
22
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
log_halt
|
23
|
+
resource :client
|
24
|
+
put "/clients/:id/regenerate_keys" do
|
25
|
+
logger.debug "Regenerating client #{params[:id]} keys"
|
26
|
+
client = @connection.clients.fetch(params[:id])
|
27
|
+
log_halt 404, "Client #{params[:id]} not found" if client.nil?
|
45
28
|
|
46
|
-
|
47
|
-
|
29
|
+
if client.regenerate_keys
|
30
|
+
logger.debug "Client #{params[:id]} keys regenerated"
|
31
|
+
client.to_json
|
32
|
+
else
|
33
|
+
log_halt 400, { :errors => 'Unable to regenerate keys' }.to_json
|
34
|
+
end
|
48
35
|
end
|
36
|
+
|
37
|
+
# commented resources do not work since they are scoped under other resource are not standard in other way
|
38
|
+
# resource :collection_proxy, :plural_name => 'collection_proxies'
|
39
|
+
resource :cookbook
|
40
|
+
# resource :cookbook_version
|
41
|
+
resource :data_bag
|
42
|
+
# resource :data_bag_item
|
43
|
+
resource :environment
|
44
|
+
resource :node
|
45
|
+
# resource :organization
|
46
|
+
# resource :partial_search, :plural_name => 'partial_searches'
|
47
|
+
# resource :principal
|
48
|
+
resource :role
|
49
|
+
# resource :search, :plural_name => 'searches'
|
50
|
+
resource :user
|
49
51
|
end
|
50
52
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module ChefPlugin
|
2
|
+
module ChefResourceApi
|
3
|
+
|
4
|
+
def resource(name, options = {})
|
5
|
+
@name = name
|
6
|
+
@options = options
|
7
|
+
plural = get_plural_name
|
8
|
+
|
9
|
+
show_action(name, plural) if actions.include?(:show)
|
10
|
+
create_action(name, plural) if actions.include?(:create)
|
11
|
+
update_action(name, plural) if actions.include?(:update)
|
12
|
+
delete_action(name, plural) if actions.include?(:delete)
|
13
|
+
list_action(plural) if actions.include?(:list)
|
14
|
+
end
|
15
|
+
|
16
|
+
def list_action(plural)
|
17
|
+
get "/#{plural}" do
|
18
|
+
logger.debug "Listing #{plural}"
|
19
|
+
|
20
|
+
# to workaround chef-api issue, see https://github.com/sethvargo/chef-api/pull/34 for more details
|
21
|
+
resources = get_connection.send(plural).all
|
22
|
+
resources.map(&:to_hash).to_json
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete_action(name, plural)
|
27
|
+
delete "/#{plural}/:id" do
|
28
|
+
logger.debug "Starting deletion of #{name} #{params[:id]}"
|
29
|
+
|
30
|
+
if (result = get_connection.send(plural).delete(params[:id]))
|
31
|
+
logger.debug "#{name.capitalize} #{params[:id]} deleted"
|
32
|
+
else
|
33
|
+
log_halt 400, "#{name.capitalize} #{params[:id]} could not be deleted" unless result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# currently broken at least for clients - see https://github.com/sethvargo/chef-api/issues/33
|
39
|
+
def update_action(name, plural)
|
40
|
+
put "/#{plural}/:id" do
|
41
|
+
logger.debug "Updating #{name} with parameters: " + params.inspect
|
42
|
+
|
43
|
+
if (object = get_connection.send(plural).update(params[:id], params[name]))
|
44
|
+
logger.debug "#{name.capitalize} #{params[:id]} updated"
|
45
|
+
object.to_json
|
46
|
+
else
|
47
|
+
log_halt 400, {:errors => object.errors}.to_json
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_action(name, plural)
|
53
|
+
post "/#{plural}" do
|
54
|
+
logger.debug "Creating #{name} with parameters: " + params.inspect
|
55
|
+
|
56
|
+
object = get_connection.send(plural).new(params[name])
|
57
|
+
if object.save
|
58
|
+
logger.debug "#{name.capitalize} #{params[:id]} created"
|
59
|
+
object.to_json
|
60
|
+
else
|
61
|
+
log_halt 400, {:errors => object.errors}.to_json
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def show_action(name, plural)
|
67
|
+
get "/#{plural}/:id" do
|
68
|
+
logger.debug "Showing #{name} #{params[:id]}"
|
69
|
+
|
70
|
+
if (object = get_connection.send(plural).fetch(params[:id]))
|
71
|
+
object.to_json
|
72
|
+
else
|
73
|
+
log_halt 404, "#{name.capitalize} #{params[:id]} not found"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def actions
|
81
|
+
@options[:actions].nil? ? [:create, :show, :list, :update, :delete] : @options[:actions]
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_plural_name
|
85
|
+
@options[:plural_name].nil? ? "#{@name}s" : @options[:plural_name]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'chef-api'
|
2
|
+
|
3
|
+
module ChefPlugin
|
4
|
+
module ConnectionHelper
|
5
|
+
def get_connection
|
6
|
+
connection = ::ChefAPI::Connection.new(
|
7
|
+
:endpoint => ChefPlugin::Plugin.settings.chef_server_url,
|
8
|
+
:client => ChefPlugin::Plugin.settings.chef_smartproxy_clientname,
|
9
|
+
:key => ChefPlugin::Plugin.settings.chef_smartproxy_privatekey,
|
10
|
+
)
|
11
|
+
connection.ssl_verify = ChefPlugin::Plugin.settings.chef_ssl_verify
|
12
|
+
self_signed = ChefPlugin::Plugin.settings.chef_ssl_pem_file
|
13
|
+
if !self_signed.nil? && !self_signed.empty?
|
14
|
+
connection.ssl_pem_file = self_signed
|
15
|
+
end
|
16
|
+
|
17
|
+
connection
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -2,9 +2,11 @@ require 'proxy/request'
|
|
2
2
|
require 'smart_proxy_chef_plugin/authentication'
|
3
3
|
|
4
4
|
module ChefPlugin
|
5
|
+
::Sinatra::Base.register Authentication
|
6
|
+
|
5
7
|
class ForemanApi < ::Sinatra::Base
|
6
8
|
helpers ::Proxy::Helpers
|
7
|
-
|
9
|
+
authenticate_with_chef_signature
|
8
10
|
|
9
11
|
error Proxy::Error::BadRequest do
|
10
12
|
log_halt(400, "Bad request : " + env['sinatra.error'].message )
|
@@ -16,15 +18,30 @@ module ChefPlugin
|
|
16
18
|
|
17
19
|
post "/hosts/facts" do
|
18
20
|
logger.debug 'facts upload request received'
|
19
|
-
|
20
|
-
|
21
|
-
end
|
21
|
+
foreman_response = Proxy::HttpRequest::Facts.new.post_facts(get_content)
|
22
|
+
log_result(foreman_response)
|
22
23
|
end
|
23
24
|
|
24
25
|
post "/reports" do
|
25
26
|
logger.debug 'report upload request received'
|
26
|
-
|
27
|
-
|
27
|
+
foreman_response = Proxy::HttpRequest::Reports.new.post_report(get_content)
|
28
|
+
log_result(foreman_response)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def get_content
|
34
|
+
input = request.env['rack.input']
|
35
|
+
input.rewind
|
36
|
+
input.read
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_result(foreman_response)
|
40
|
+
code = foreman_response.code.to_i
|
41
|
+
if code >= 200 && code < 300
|
42
|
+
logger.debug "upload forwarded to Foreman successfully, response was #{code}"
|
43
|
+
else
|
44
|
+
logger.error "forwarding failed, Foreman responded with #{code}, check Foreman access and production logs for more details"
|
28
45
|
end
|
29
46
|
end
|
30
47
|
end
|
data/settings.d/chef.yml.example
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# :chef_smartproxy_clientname: 'host.example.net'
|
9
9
|
# :chef_smartproxy_privatekey: '/etc/chef/client.pem'
|
10
10
|
|
11
|
-
# by default ssl verification of request to
|
11
|
+
# by default ssl verification of request to your chef server is enabled,
|
12
12
|
# you're supposed to install CA certificate yourself
|
13
13
|
# this usually consist of two steps
|
14
14
|
# download the CA cert to /etc/pki/tls/certs/, e.g. ca-cert-root.pem
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_proxy_chef
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marek Hulan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -134,17 +134,16 @@ files:
|
|
134
134
|
- lib/smart_proxy_chef_plugin/http_config.ru
|
135
135
|
- lib/smart_proxy_chef_plugin/chef_api.rb
|
136
136
|
- lib/smart_proxy_chef_plugin/foreman_api.rb
|
137
|
+
- lib/smart_proxy_chef_plugin/connection_helper.rb
|
138
|
+
- lib/smart_proxy_chef_plugin/chef_resource_api.rb
|
137
139
|
- lib/smart_proxy_chef_plugin/authentication.rb
|
138
|
-
- lib/smart_proxy_chef_plugin/resources/client.rb
|
139
|
-
- lib/smart_proxy_chef_plugin/resources/base.rb
|
140
|
-
- lib/smart_proxy_chef_plugin/resources/node.rb
|
141
140
|
- lib/smart_proxy_chef_plugin/chef_plugin.rb
|
142
141
|
- lib/smart_proxy_chef_plugin/version.rb
|
143
142
|
- lib/smart_proxy_chef.rb
|
144
143
|
- settings.d/chef.yml.example
|
145
144
|
- LICENSE
|
146
145
|
- Gemfile
|
147
|
-
homepage: https://github.com/theforeman/
|
146
|
+
homepage: https://github.com/theforeman/smart_proxy_chef
|
148
147
|
licenses:
|
149
148
|
- GPLv3
|
150
149
|
metadata: {}
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'chef-api'
|
2
|
-
|
3
|
-
module ChefPlugin
|
4
|
-
module Resources
|
5
|
-
class Base
|
6
|
-
def initialize
|
7
|
-
@connection = ChefAPI::Connection.new(
|
8
|
-
:endpoint => ChefPlugin::Plugin.settings.chef_server_url,
|
9
|
-
:client => ChefPlugin::Plugin.settings.chef_smartproxy_clientname,
|
10
|
-
:key => ChefPlugin::Plugin.settings.chef_smartproxy_privatekey,
|
11
|
-
)
|
12
|
-
@connection.ssl_verify = ChefPlugin::Plugin.settings.chef_ssl_verify
|
13
|
-
self_signed = ChefPlugin::Plugin.settings.chef_ssl_pem_file
|
14
|
-
if !self_signed.nil? && !self_signed.empty?
|
15
|
-
@connection.ssl_pem_file = self_signed
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'smart_proxy_chef_plugin/resources/base'
|
2
|
-
|
3
|
-
module ChefPlugin::Resources
|
4
|
-
class Client < Base
|
5
|
-
def initialize
|
6
|
-
super
|
7
|
-
@base = @connection.clients
|
8
|
-
end
|
9
|
-
|
10
|
-
def delete(fqdn)
|
11
|
-
@base.delete(fqdn)
|
12
|
-
end
|
13
|
-
|
14
|
-
def show(fqdn)
|
15
|
-
@base.fetch(fqdn)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'smart_proxy_chef_plugin/resources/base'
|
2
|
-
|
3
|
-
module ChefPlugin::Resources
|
4
|
-
class Node < Base
|
5
|
-
def initialize
|
6
|
-
super
|
7
|
-
@base = @connection.nodes
|
8
|
-
end
|
9
|
-
|
10
|
-
def delete(fqdn)
|
11
|
-
@base.delete(fqdn)
|
12
|
-
end
|
13
|
-
|
14
|
-
def show(fqdn)
|
15
|
-
@base.fetch(fqdn)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|