summaryrefslogtreecommitdiffstats
path: root/lib/puppet/sslcertificates/support.rb
blob: 7d67081247e2a6d3896d7297ba3cf849462217e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
require 'puppet/sslcertificates'

# A module to handle reading of certificates.
module Puppet::SSLCertificates::Support
  class MissingCertificate < Puppet::Error; end
  class InvalidCertificate < Puppet::Error; end

  attr_reader :cacert

  # Some metaprogramming to create methods for retrieving and creating keys.
  # This probably isn't fewer lines than defining each separately...
  def self.keytype(name, options, &block)
    var = "@#{name}"

    maker = "mk_#{name}"
    reader = "read_#{name}"

    unless param = options[:param]
      raise ArgumentError, "You must specify the parameter for the key"
    end

    unless klass = options[:class]
      raise ArgumentError, "You must specify the class for the key"
    end

    # Define the method that creates it.
    define_method(maker, &block)

    # Define the reading method.
    define_method(reader) do
      return nil unless FileTest.exists?(Puppet[param]) or rename_files_with_uppercase(Puppet[param])

      begin
        instance_variable_set(var, klass.new(File.read(Puppet[param])))
      rescue => detail
        raise InvalidCertificate, "Could not read #{param}: #{detail}"
      end
    end

    # Define the overall method, which just calls the reader and maker
    # as appropriate.
    define_method(name) do
      unless cert = instance_variable_get(var)
        unless cert = send(reader)
          cert = send(maker)
          Puppet.settings.write(param) { |f| f.puts cert.to_pem }
        end
        instance_variable_set(var, cert)
      end
      cert
    end
  end

  # The key pair.
  keytype :key, :param => :hostprivkey, :class => OpenSSL::PKey::RSA do
    Puppet.info "Creating a new SSL key at #{Puppet[:hostprivkey]}"
    key = OpenSSL::PKey::RSA.new(Puppet[:keylength])

    # Our key meta programming can only handle one file, so we have
    # to separately write out the public key.
    Puppet.settings.write(:hostpubkey) do |f|
      f.print key.public_key.to_pem
    end
    return key
  end

  # Our certificate request
  keytype :csr, :param => :hostcsr, :class => OpenSSL::X509::Request do
    Puppet.info "Creating a new certificate request for #{Puppet[:certname]}"

    csr = OpenSSL::X509::Request.new
    csr.version = 0
    csr.subject = OpenSSL::X509::Name.new([["CN", Puppet[:certname]]])
    csr.public_key = key.public_key
    csr.sign(key, OpenSSL::Digest::MD5.new)

    return csr
  end

  keytype :cert, :param => :hostcert, :class => OpenSSL::X509::Certificate do
    raise MissingCertificate, "No host certificate"
  end

  keytype :ca_cert, :param => :localcacert, :class => OpenSSL::X509::Certificate do
    raise MissingCertificate, "No CA certificate"
  end

  # Request a certificate from the remote system.  This does all of the work
  # of creating the cert request, contacting the remote system, and
  # storing the cert locally.
  def requestcert
    begin
      cert, cacert = caclient.getcert(@csr.to_pem)
    rescue => detail
      puts detail.backtrace if Puppet[:trace]
      raise Puppet::Error.new("Certificate retrieval failed: #{detail}")
    end

    if cert.nil? or cert == ""
      return nil
    end
    Puppet.settings.write(:hostcert) do |f| f.print cert end
    Puppet.settings.write(:localcacert) do |f| f.print cacert end
    #File.open(@certfile, "w", 0644) { |f| f.print cert }
    #File.open(@cacertfile, "w", 0644) { |f| f.print cacert }
    begin
      @cert = OpenSSL::X509::Certificate.new(cert)
      @cacert = OpenSSL::X509::Certificate.new(cacert)
      retrieved = true
    rescue => detail
      raise Puppet::Error.new(
        "Invalid certificate: #{detail}"
      )
    end

    raise Puppet::DevError, "Received invalid certificate" unless @cert.check_private_key(@key)
    retrieved
  end

  # A hack method to deal with files that exist with a different case.
  # Just renames it; doesn't read it in or anything.
  def rename_files_with_uppercase(file)
    dir = File.dirname(file)
    short = File.basename(file)

    # If the dir isn't present, we clearly don't have the file.
    #return nil unless FileTest.directory?(dir)

    raise ArgumentError, "Tried to fix SSL files to a file containing uppercase" unless short.downcase == short

    return false unless File.directory?(dir)

    real_file = Dir.entries(dir).reject { |f| f =~ /^\./ }.find do |other|
      other.downcase == short
    end

    return nil unless real_file

    full_file = File.join(dir, real_file)

    Puppet.notice "Fixing case in #{full_file}; renaming to #{file}"
    File.rename(full_file, file)

    true
  end
end