smart_proxy_chef 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|