class Vcert::CloudConnection

Constants

CERT_STATUS_FAILED
CERT_STATUS_ISSUED
CERT_STATUS_PENDING
CERT_STATUS_REQUESTED
CHAIN_OPTION_ROOT_FIRST
CHAIN_OPTION_ROOT_LAST
CLOUD_PREFIX
TOKEN_HEADER_NAME
URL_APPLICATION_BY_NAME
URL_CERTIFICATE_REQUESTS
URL_CERTIFICATE_RETRIEVE
URL_CERTIFICATE_STATUS
URL_CIT_BY_APP_NAME_CIT_ALIAS

Public Class Methods

new(url, apikey) click to toggle source
# File lib/cloud/cloud.rb, line 8
def initialize(url, apikey)
  @url = if url.nil?
           'https://api.venafi.cloud'.freeze
         else
           url
         end
  @apikey = apikey
end

Public Instance Methods

policy(zone_id) click to toggle source
# File lib/cloud/cloud.rb, line 168
def policy(zone_id)
  unless zone_id
    raise Vcert::ClientBadDataError, "Zone should be not nil"
  end
  arr = zone_id.split("\\", 2)

  app_name = arr[0]
  cit_alias = arr[1]

  if app_name.to_s.strip.empty? || cit_alias.to_s.strip.empty?
    raise Vcert::ClientBadDataError, "The parameters: app_name, cit_alias or both are empty"
  end

  app_name =  Addressable::URI.encode_component(app_name, Addressable::URI::CharacterClasses::QUERY)
  cit_alias =  Addressable::URI.encode_component(cit_alias, Addressable::URI::CharacterClasses::QUERY)
  status, data = get(URL_CIT_BY_APP_NAME_CIT_ALIAS % [app_name, cit_alias])
  puts data
  if status != 200
    raise Vcert::ServerUnexpectedBehaviorError, "Invalid status getting issuing template: %s for zone %s" % status, zone_id
  end
  parse_policy_responce_to_object(data)
end
renew(request, generate_new_key: true) click to toggle source
# File lib/cloud/cloud.rb, line 60
def renew(request, generate_new_key: true)
  puts("Trying to renew certificate")
  if request.id == nil && request.thumbprint == nil
    raise Vcert::ClientBadDataError, "Either request ID or certificate thumbprint is required to renew the certificate"
  end
  if request.thumbprint != nil
    cert_id, request_id = search_by_thumbprint(request.thumbprint)
  end
  if request.id != nil
    prev_request = get_cert_status(request)
    request_id = request.id
    zone = prev_request[:zoneId]
  end
  if request_id == nil
    raise Vcert::VcertError, "Can't find the existing certificate request id"
  end

  status, data = get(URL_CERTIFICATE_STATUS % request_id)

  if status == 200
    request.id = data['id']
    cert_id = data['certificateIds'][0]
  else
    raise Vcert::ServerUnexpectedBehaviorError, "Status #{status}"
  end


  if prev_request == nil
    prev_request = get_cert_status(request)
  end


  d = {existingCertificateId: cert_id,
       applicationId: data["applicationId"],
       certificateIssuingTemplateId: data["certificateIssuingTemplateId"],
       apiClientInformation: getApiClientInformation

  }
  if request.csr?
    d.merge!(certificateSigningRequest: request.csr)
    d.merge!(reuseCSR: false)
  elsif generate_new_key
    parsed_csr = parse_csr_fields(prev_request[:csr])
    renew_request = Vcert::Request.new(
        common_name: parsed_csr[:CN],
        san_dns: parsed_csr[:DNS],
        country: parsed_csr[:C],
        province: parsed_csr[:ST],
        locality: parsed_csr[:L],
        organization: parsed_csr[:O],
        organizational_unit: parsed_csr[:OU])
    d.merge!(certificateSigningRequest: renew_request.csr)
  else
    raise Vcert::VcertError, "This operation is not yet supported"
    #d.merge!(reuseCSR: true)
  end

  status, data = post(URL_CERTIFICATE_REQUESTS, data = d)
  if status == 201
    if generate_new_key
      return data['certificateRequests'][0]['id'], renew_request.private_key
    else
      return data['certificateRequests'][0]['id'], nil
    end

  else
    raise Vcert::ServerUnexpectedBehaviorError, "Status: #{status} Message: #{data}"
  end

end
request(zone_tag, request) click to toggle source
# File lib/cloud/cloud.rb, line 18
def request(zone_tag, request)
  zone_config = zone_configuration(zone_tag)
  _, data = post(URL_CERTIFICATE_REQUESTS, {:applicationId => zone_config.app_id,
                                            :certificateIssuingTemplateId=>zone_config.cit_id,
                                            :certificateSigningRequest => request.csr,
                                            :apiClientInformation => getApiClientInformation
  })
  LOG.debug("Raw response to certificate request:")
  LOG.debug(JSON.pretty_generate(data))
  request.id = data['certificateRequests'][0]["id"]
  request
end
retrieve(request) click to toggle source
# File lib/cloud/cloud.rb, line 31
def retrieve(request)
  LOG.info(("Getting certificate status for ID %s" % request.id))
  status, data = get(URL_CERTIFICATE_STATUS % request.id)
  if [200, 409].include? status
    case data['status']
    when CERT_STATUS_PENDING, CERT_STATUS_REQUESTED
      LOG.info(("Certificate status is: %s" % data['status']))
      return nil
    when CERT_STATUS_FAILED
      raise Vcert::ServerUnexpectedBehaviorError, "Certificate issue status is FAILED"
    when CERT_STATUS_ISSUED
      cert_arr = data["certificateIds"]
      status, full_chain = get(URL_CERTIFICATE_RETRIEVE % cert_arr[0] + "?chainOrder=#{CHAIN_OPTION_ROOT_LAST}&format=PEM")
      if status == 200
        cert = parse_full_chain full_chain
        if cert.private_key == nil
          cert.private_key = request.private_key
        end
        return cert
      else
        LOG.error("Can't issue certificate: #{full_chain}")
        raise Vcert::ServerUnexpectedBehaviorError, "Status #{status}"
      end
    else
      raise Vcert::ServerUnexpectedBehaviorError, "Unknown certificate status #{data['status']}"
    end
  end
end
zone_configuration(tag) click to toggle source
# File lib/cloud/cloud.rb, line 131
def zone_configuration(tag)
  if tag.to_s.strip.empty?
    raise Vcert::ClientBadDataError, "Zone should not be empty"
  end
  LOG.info("Getting configuration for zone #{tag}")
  arr = tag.split("\\", 2)

  app_name = arr[0]
  cit_alias = arr[1]

  if app_name.to_s.strip.empty? || cit_alias.to_s.strip.empty?
    raise Vcert::ClientBadDataError, "The parameters: app_name, cit_alias or both are empty"
  end
  app_name =  Addressable::URI.encode_component(app_name, Addressable::URI::CharacterClasses::QUERY)
  cit_alias =  Addressable::URI.encode_component(cit_alias, Addressable::URI::CharacterClasses::QUERY)

  #get cit
  _, data = get(URL_CIT_BY_APP_NAME_CIT_ALIAS % [app_name, cit_alias])

  #get app info
  _, app = get(URL_APPLICATION_BY_NAME % app_name)

  kt = Vcert::KeyType.new data['keyTypes'][0]["keyType"], data['keyTypes'][0]["keyLengths"][0].to_i
  z = Vcert::ZoneConfiguration.new(
      country: Vcert::CertField.new(""),
      province: Vcert::CertField.new(""),
      locality: Vcert::CertField.new(""),
      organization: Vcert::CertField.new(""),
      organizational_unit: Vcert::CertField.new(""),
      key_type: Vcert::CertField.new(kt, locked: true),
  )
  z.app_id = app["id"]
  z.cit_id = data["id"]

  return z
end

Private Instance Methods

get(url) click to toggle source
# File lib/cloud/cloud.rb, line 208
def get(url)
  uri = URI.parse(@url)
  request = Net::HTTP.new(uri.host, uri.port)
  request.use_ssl = true
  url = uri.path + "/" + url


  LOG.info("#{CLOUD_PREFIX} GET #{url}")
  response = request.get(url, { TOKEN_HEADER_NAME => @apikey })
  case response.code.to_i
  when 200, 201, 202, 409
    LOG.info("#{CLOUD_PREFIX} GET HTTP status OK")
  when 403
    raise Vcert::AuthenticationError
  else
    raise Vcert::ServerUnexpectedBehaviorError, "Unexpected code #{response.code} for URL #{url}. Message: #{response.body}"
  end
  case response.header['content-type']
  when "application/json"
    begin
      data = JSON.parse(response.body)
    rescue JSON::ParserError
      raise Vcert::ServerUnexpectedBehaviorError, "Invalid JSON"
    end
  when "text/plain"
    data = response.body
  else
    raise Vcert::ServerUnexpectedBehaviorError, "Unexpected content-type #{response.header['content-type']}"
  end
  # rescue *ALL_NET_HTTP_ERRORS
  return response.code.to_i, data
  # end
end
get_cert_status(request) click to toggle source
# File lib/cloud/cloud.rb, line 303
def get_cert_status(request)
  status, d = get(URL_CERTIFICATE_STATUS % request.id)
  if status == 200
    request_status = Hash.new
    request_status[:status] = d['status']
    request_status[:subject] = d['subjectDN'] or d['subjectCN'][0]
    request_status[:subject_alt_names] = d['subjectAlternativeNamesByType']
    request_status[:zoneId] = d['zoneId']
    request_status[:manage_id] = d['managedCertificateId']
    request_status[:csr] = d['certificateSigningRequest']
    request_status[:key_lenght] = d['keyLength']
    request_status[:key_type] = d['keyType']
    return request_status
  else
    raise Vcert::ServerUnexpectedBehaviorError, "status: #{status}"
  end
end
parse_full_chain(full_chain) click to toggle source
# File lib/cloud/cloud.rb, line 262
def parse_full_chain(full_chain)
  pems = parse_pem_list(full_chain)
  Vcert::Certificate.new(
      cert: pems[0],
      chain: pems[1..-1]
  )
end
parse_policy_responce_to_object(d) click to toggle source
# File lib/cloud/cloud.rb, line 270
def parse_policy_responce_to_object(d)
  key_types = []
  d['keyTypes'].each { |kt| key_types.push(['keyType']) }
  Vcert::Policy.new(policy_id: d['id'],
                             name: d['name'],
                             system_generated: d['systemGenerated'],
                             creation_date: d['creationDate'],
                             subject_cn_regexes: d['subjectCNRegexes'],
                             subject_o_regexes: d['subjectORegexes'],
                             subject_ou_regexes: d['subjectOURegexes'],
                             subject_st_regexes: d['subjectSTRegexes'],
                             subject_l_regexes: d['subjectLRegexes'],
                             subject_c_regexes: d['subjectCValues'],
                             san_regexes: d['sanRegexes'],
                             key_types: key_types)
end
post(url, data) click to toggle source
# File lib/cloud/cloud.rb, line 242
def post(url, data)
  uri = URI.parse(@url)
  request = Net::HTTP.new(uri.host, uri.port)
  request.use_ssl = true
  url = uri.path + "/" + url
  encoded_data = JSON.generate(data)
  LOG.info("#{CLOUD_PREFIX} POST #{url}")
  response = request.post(url, encoded_data, { TOKEN_HEADER_NAME => @apikey, "Content-Type" => "application/json", "Accept" => "application/json" })
  case response.code.to_i
  when 200, 201, 202, 409
    LOG.info("#{CLOUD_PREFIX} POST HTTP status OK")
  when 403
    raise Vcert::AuthenticationError
  else
    raise Vcert::ServerUnexpectedBehaviorError, "Unexpected code #{response.code} for URL #{url}. Message: #{response.body}"
  end
  data = JSON.parse(response.body)
  return response.code.to_i, data
end
search_by_thumbprint(thumbprint) click to toggle source
# File lib/cloud/cloud.rb, line 287
def search_by_thumbprint(thumbprint)
  # thumbprint = re.sub(r'[^\dabcdefABCDEF]', "", thumbprint)
  thumbprint = thumbprint.upcase
  status, data = post(URL_CERTIFICATE_SEARCH, data = {"expression": {operands: [
      {field: "fingerprint", operator: "MATCH", value: thumbprint}]}})
  # TODO: check that data have valid certificate in it
  if status != 200
    raise Vcert::ServerUnexpectedBehaviorError, "Status: #{status}. Message: #{data.body.to_s}"
  end
  # TODO: check data
  certId = data['certificates'][0]['id']
  certReqId = data['certificates'][0]['certificateRequestId']
  LOG.info("Found existing certificate with ID #{certId}")
  return certId, certReqId
end