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 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