Class: Puppet::Network::HTTP::RackREST

Inherits:
Object
  • Object
show all
Includes:
Handler
Defined in:
lib/puppet/network/http/rack/rest.rb

Defined Under Namespace

Classes: RackFile

Constant Summary collapse

ContentType =
'Content-Type'.freeze
CHUNK_SIZE =
8192

Constants included from Handler

Handler::DISALLOWED_KEYS

Constants included from Issues

Issues::ENVIRONMENT_NOT_FOUND, Issues::FAILED_AUTHORIZATION, Issues::HANDLER_NOT_FOUND, Issues::MISSING_HEADER_FIELD, Issues::NO_INDIRECTION_REMOTE_REQUESTS, Issues::RESOURCE_NOT_FOUND, Issues::RUNTIME_ERROR, Issues::UNSUPPORTED_FORMAT, Issues::UNSUPPORTED_MEDIA_TYPE, Issues::UNSUPPORTED_METHOD

Instance Method Summary collapse

Methods included from Handler

#format_to_mime, #process, #register, #resolve_node

Constructor Details

#initialize(args = {}) ⇒ RackREST

Returns a new instance of RackREST.



30
31
32
33
34
35
# File 'lib/puppet/network/http/rack/rest.rb', line 30

def initialize(args={})
  super()
  register([Puppet::Network::HTTP::API.master_routes,
            Puppet::Network::HTTP::API.ca_routes,
            Puppet::Network::HTTP::API.not_found_upgrade])
end

Instance Method Details

#body(request) ⇒ Object

return the request body



96
97
98
# File 'lib/puppet/network/http/rack/rest.rb', line 96

def body(request)
  request.body.read
end

#cleanup(request) ⇒ Object

Passenger freaks out if we finish handling the request without reading any part of the body, so make sure we have.



129
130
131
132
# File 'lib/puppet/network/http/rack/rest.rb', line 129

def cleanup(request)
  request.body.read(1)
  nil
end

#client_cert(request) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/puppet/network/http/rack/rest.rb', line 100

def client_cert(request)
  # This environment variable is set by mod_ssl, note that it
  # requires the `+ExportCertData` option in the `SSLOptions` directive
  cert = request.env['SSL_CLIENT_CERT']
  # NOTE: The SSL_CLIENT_CERT environment variable will be the empty string
  # when Puppet agent nodes have not yet obtained a signed certificate.
  if cert.nil? || cert.empty?
    # When running with unicorn, the SSL_CLIENT_CERT variable is not available
    # in the environment, therefore we have to pass a header: 'X-SSL-Client-Cert'
    cert = request.env['HTTP_X_SSL_CLIENT_CERT']
    if cert.nil? || cert.empty?
      nil
    else
      # in contrast to the environment variable, the client cert is passed in
      # as single string, therefore restore the certificate to a valid pem
      # encoded certificate
      cert.gsub!(/ /, "\n")
      cert.gsub!(/BEGIN\nCERT/, "BEGIN CERT")
      cert.gsub!(/END\nCERT/, "END CERT")
      cert = Puppet::SSL::Certificate.from_instance(OpenSSL::X509::Certificate.new(cert))
      cert
    end
  else
    Puppet::SSL::Certificate.from_instance(OpenSSL::X509::Certificate.new(cert))
  end
end

#convert_singular_arrays_to_value(hash) ⇒ Object



155
156
157
158
159
160
161
# File 'lib/puppet/network/http/rack/rest.rb', line 155

def convert_singular_arrays_to_value(hash)
  hash.each do |key, value|
    if value.size == 1
      hash[key] = value.first
    end
  end
end

#extract_client_info(request) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/puppet/network/http/rack/rest.rb', line 134

def extract_client_info(request)
  result = {}
  result[:ip] = request.ip

  # if we find SSL info in the headers, use them to get a hostname from the CN.
  # try this with :ssl_client_header, which defaults should work for
  # Apache with StdEnvVars.
  subj_str = request.env[Puppet[:ssl_client_header]]
  subject = Puppet::Util::SSL.subject_from_dn(subj_str || "")

  if cn = Puppet::Util::SSL.cn_from_subject(subject)
    result[:node] = cn
    result[:authenticated] = (request.env[Puppet[:ssl_client_verify_header]] == 'SUCCESS')
  else
    result[:node] = resolve_node(result)
    result[:authenticated] = false
  end

  result
end

#headers(request) ⇒ Object

Retrieve all headers from the http request, as a map.



53
54
55
56
57
58
59
60
# File 'lib/puppet/network/http/rack/rest.rb', line 53

def headers(request)
  headers = request.env.select {|k,v| k.start_with? 'HTTP_'}.inject({}) do |m, (k,v)|
    m[k.sub(/^HTTP_/, '').gsub('_','-').downcase] = v
    m
  end
  headers['content-type'] = request.content_type
  headers
end

#http_method(request) ⇒ Object

Return which HTTP verb was used in this request.



63
64
65
# File 'lib/puppet/network/http/rack/rest.rb', line 63

def http_method(request)
  request.request_method
end

#params(request) ⇒ Object

Return the query params for this request.



68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/puppet/network/http/rack/rest.rb', line 68

def params(request)
  if request.post?
    params = request.params
  else
    # rack doesn't support multi-valued query parameters,
    # e.g. ignore, so parse them ourselves
    params = CGI.parse(request.query_string)
    convert_singular_arrays_to_value(params)
  end
  result = decode_params(params)
  result.merge(extract_client_info(request))
end

#path(request) ⇒ Object

what path was requested? (this is, without any query parameters)



82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/puppet/network/http/rack/rest.rb', line 82

def path(request)
  # The value that Passenger provides for 'path' is escaped
  # (URL percent-encoded), see
  # https://github.com/phusion/passenger/blob/release-5.0.26/src/apache2_module/Hooks.cpp#L885
  # for the implementation as hooked up to an Apache web server.  Code
  # in the indirector / HTTP layer which consumes this path, however, assumes
  # that it has already been unescaped, so it is unescaped here.
  if request.path
    # don't use CGI.unescape which mangles space handling
    URI.unescape(request.path.encode(Encoding::UTF_8))
  end
end

#set_content_type(response, format) ⇒ Object



37
38
39
# File 'lib/puppet/network/http/rack/rest.rb', line 37

def set_content_type(response, format)
  response[ContentType] = format
end

#set_response(response, result, status = 200) ⇒ Object

produce the body of the response



42
43
44
45
46
47
48
49
50
# File 'lib/puppet/network/http/rack/rest.rb', line 42

def set_response(response, result, status = 200)
  response.status = status
  unless result.is_a?(File)
    response.write result
  else
    response["Content-Length"] = result.stat.size.to_s
    response.body = RackFile.new(result)
  end
end