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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7d2d28589334b1dd4348fdb64179ee703847ff63
4
- data.tar.gz: 887c69f465dbf1b6da515f0c9fafcc881077e3a7
3
+ metadata.gz: d1c5fd05ae72a1469295dedb30ea5b17d6a31c8e
4
+ data.tar.gz: 2831d4c31cf71833fc813b6dfda6b8f2217cc020
5
5
  SHA512:
6
- metadata.gz: b04a508b2a54db80f5b5e6106636cb444495d90a74235e16f784ac07cff0626bbe87d1516aa0c5847d3c560975d1f472109d30d3566a695a6be2897b13782832
7
- data.tar.gz: 9d6b04059fb1bd4eb3be73fcc9f92008c951c97f2742a656d5c7f7717ce06c3ca723af0ca2668575b04c847aa5d7bf9496f92efe1e480ab89586289db33fbec5
6
+ metadata.gz: e5219d453c2eabc2ac58187c021b12e05ad689eddeab17c636cd5f8cd0d2ac5a70e5468b9162b6741a4bb6d35f150c6382b66bcdf76ae34152f3b1ef898e43d6
7
+ data.tar.gz: f133518caf0024b07d9ebd9b36b8c054012550186b29909b784a6710ee7178caa441717a9ad7bfbd53ee3af7ad71b2b567697ece4fb39ec3991099537b5cd5a8
@@ -1,47 +1,57 @@
1
- module ChefPlugin
2
- class Authentication
3
- require 'smart_proxy_chef_plugin/resources/client'
4
- require 'digest/sha2'
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
- raise Proxy::Error::Unauthorized, "Could not find client with name #{client_name}" if client.nil?
20
- public_key = OpenSSL::PKey::RSA.new(client.public_key)
6
+ module ChefPlugin
7
+ module Authentication
8
+ def authenticate_with_chef_signature
9
+ helpers ConnectionHelper, InstanceMethods, ::Proxy::Helpers
21
10
 
22
- #signature is base64 encoded
23
- decoded_signature = Base64.decode64(signature)
24
- hash_body = Digest::SHA256.hexdigest(body)
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
- def authenticated(request, &block)
29
- content = request.env["rack.input"].read
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
- auth = true
32
- if ChefPlugin::Plugin.settings.chef_authenticate_nodes
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
- raise Proxy::Error::Unauthorized, "Failed to authenticate node #{client_name}. Missing some headers" if client_name.nil? or signature.nil?
37
- auth = verify_signature_request(client_name, signature, content)
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
- if auth
41
- raise Proxy::Error::BadRequest, "Body is empty for node #{client_name}" if content.nil?
42
- block.call(content)
43
- else
44
- raise Proxy::Error::Unauthorized, "Failed to authenticate node #{client_name}"
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/resources/node'
2
- require 'smart_proxy_chef_plugin/resources/client'
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
- get "/nodes/:fqdn" do
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
- if (node = Resources::Node.new.show(params[:fqdn]))
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
- get "/clients/:fqdn" do
20
- logger.debug "Showing client #{params[:fqdn]}"
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
- delete "/nodes/:fqdn" do
31
- logger.debug "Starting deletion of node #{params[:fqdn]}"
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
- delete "/clients/:fqdn" do
41
- logger.debug "Starting deletion of client #{params[:fqdn]}"
42
-
43
- result = Resources::Client.new.delete(params[:fqdn])
44
- log_halt 400, "Client #{params[:fqdn]} could not be deleted" unless result
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
- logger.debug "Client #{params[:fqdn]} deleted"
47
- { :result => result }.to_json
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
- authorize_with_trusted_hosts
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
- ChefPlugin::Authentication.new.authenticated(request) do |content|
20
- Proxy::HttpRequest::Facts.new.post_facts(content)
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
- ChefPlugin::Authentication.new.authenticated(request) do |content|
27
- Proxy::HttpRequest::Reports.new.post_report(content)
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
@@ -1,3 +1,3 @@
1
1
  module ChefPlugin
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.3'
3
3
  end
@@ -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 you chef server is enabled,
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.2
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-01-13 00:00:00.000000000 Z
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/smart-proxy-chef-plugin
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