Skip to content

Instantly share code, notes, and snippets.

@gmcmillan
Created July 26, 2012 22:25
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save gmcmillan/3184964 to your computer and use it in GitHub Desktop.
Save gmcmillan/3184964 to your computer and use it in GitHub Desktop.
Simple Ruby class for manually querying the Chef REST API (using Net::HTTP instead of Chef's REST resources)
require 'base64'
require 'time'
require 'digest/sha1'
require 'openssl'
require 'net/https'
require 'json'
class ChefAPI
# Public: Gets/Sets the http object.
attr_accessor :http
# Public: Gets/Sets the String path for the HTTP request.
attr_accessor :path
# Public: Gets/Sets the String client_name containing the Chef client name.
attr_accessor :client_name
# Public: Gets/Sets the String key_file that is path to the Chef client PEM file.
attr_accessor :key_file
# Public: Initialize a Chef API call.
#
# opts - A Hash containing the settings desired for the HTTP session and auth.
# :server - The String server that is the Chef server name (required).
# :port - The String port for the Chef server (default: 443).
# :use_ssl - The Boolean use_ssl to use Net::HTTP SSL
# functionality or not (default: true).
# :ssl_insecure - The Boolean ssl_insecure to skip strict SSL cert
# checking (default: OpenSSL::SSL::VERIFY_PEER).
# :client_name - The String client_name that is the name of the Chef
# client (required).
# :key_file - The String key_file that is the path to the Chef client
# PEM file (required).
def initialize(opts={})
server = opts[:server]
port = opts.fetch(:port, 443)
use_ssl = opts.fetch(:use_ssl, true)
ssl_insecure = opts[:ssl_insecure] ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
@client_name = opts[:client_name]
@key_file = opts[:key_file]
@http = Net::HTTP.new(server, port)
@http.use_ssl = use_ssl
@http.verify_mode = ssl_insecure
end
# Public: Make the actual GET request to the Chef server.
#
# req_path - A String containing the server path you want to send with your
# GET request (required).
#
# Examples
#
# get_request('/environments/_default/nodes')
# # => ["server1.com","server2.com","server3.com"]
#
# Returns different Object type depending on request.
def get_request(req_path)
@path = req_path
begin
request = Net::HTTP::Get.new(path, headers)
response = http.request(request)
JSON.parse(response.body).keys
rescue OpenSSL::SSL::SSLError => e
raise "SSL error: #{e.message}."
end
end
private
# Private: Encode a String with SHA1.digest and then Base64.encode64 it.
#
# string - The String you want to encode.
#
# Examples
#
# encode('hello')
# # => "qvTGHdzF6KLavt4PO0gs2a6pQ00="
#
# Returns the hashed String.
def encode(string)
::Base64.encode64(Digest::SHA1.digest(string)).chomp
end
# Private: Forms the HTTP headers required to authenticate and query data
# via Chef's REST API.
#
# Examples
#
# headers
# # => {
# "Accept" => "application/json",
# "X-Ops-Sign" => "version=1.0",
# "X-Ops-Userid" => "client-name",
# "X-Ops-Timestamp" => "2012-07-27T20:09:25Z",
# "X-Ops-Content-Hash" => "JJKXjxksmsKXM=",
# "X-Ops-Authorization-1" => "JFKXjkmdkDMKCMDKd+",
# "X-Ops-Authorization-2" => "JFJXjxjJXXJ/FFjxjd",
# "X-Ops-Authorization-3" => "FFJfXffffhhJjxFJff",
# "X-Ops-Authorization-4" => "Fjxaaj2drg5wcZ8I7U",
# "X-Ops-Authorization-5" => "ffjXeiiiaHskkflllA",
# "X-Ops-Authorization-6" => "FjxJfjkskqkfjghAjQ=="
# }
#
# Returns a Hash with the necessary headers.
def headers
body = ""
timestamp = Time.now.utc.iso8601
key = OpenSSL::PKey::RSA.new(File.read(key_file))
canonical = "Method:GET\nHashed Path:#{encode(path)}\nX-Ops-Content-Hash:#{encode(body)}\nX-Ops-Timestamp:#{timestamp}\nX-Ops-UserId:#{client_name}"
header_hash = {
'Accept' => 'application/json',
'X-Ops-Sign' => 'version=1.0',
'X-Ops-Userid' => client_name,
'X-Ops-Timestamp' => timestamp,
'X-Ops-Content-Hash' => encode(body)
}
signature = Base64.encode64(key.private_encrypt(canonical))
signature_lines = signature.split(/\n/)
signature_lines.each_index do |idx|
key = "X-Ops-Authorization-#{idx + 1}"
header_hash[key] = signature_lines[idx]
end
header_hash
end
end
@suizhihao
Copy link

well done , very impressive!!

@suizhihao
Copy link

well done , very impressive!!

@drewlander
Copy link

This was very helpful. Modified it a little but to work with httparty and orgs but this saved me a TON of work, thank you!

@prakash1243
Copy link

Hi @gmcmillan
Thanks for the script. I have executed the above snippet and I think I could able to make the authorization successful. But when I pass a Get request, I get a html code as attached not the valid response. Could you please let me know what could be the issue. Thanks !
error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment