File: base.rb

package info (click to toggle)
puppet-agent 8.10.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,404 kB
  • sloc: ruby: 286,820; sh: 492; xml: 116; makefile: 88; cs: 68
file content (152 lines) | stat: -rw-r--r-- 4,345 bytes parent folder | download | duplicates (2)
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
147
148
149
150
151
152
# frozen_string_literal: true

require_relative '../../puppet/ssl/openssl_loader'
require_relative '../../puppet/ssl'
require_relative '../../puppet/ssl/digest'

# The base class for wrapping SSL instances.
class Puppet::SSL::Base
  # For now, use the YAML separator.
  SEPARATOR = "\n---\n"

  # Only allow printing ascii characters, excluding /
  VALID_CERTNAME = /\A[ -.0-~]+\Z/

  def self.from_multiple_s(text)
    text.split(SEPARATOR).collect { |inst| from_s(inst) }
  end

  def self.to_multiple_s(instances)
    instances.collect(&:to_s).join(SEPARATOR)
  end

  def self.wraps(klass)
    @wrapped_class = klass
  end

  def self.wrapped_class
    raise(Puppet::DevError, _("%{name} has not declared what class it wraps") % { name: self }) unless defined?(@wrapped_class)

    @wrapped_class
  end

  def self.validate_certname(name)
    raise _("Certname %{name} must not contain unprintable or non-ASCII characters") % { name: name.inspect } unless name =~ VALID_CERTNAME
  end

  attr_accessor :name, :content

  def generate
    raise Puppet::DevError, _("%{class_name} did not override 'generate'") % { class_name: self.class }
  end

  def initialize(name)
    @name = name.to_s.downcase
    self.class.validate_certname(@name)
  end

  ##
  # name_from_subject extracts the common name attribute from the subject of an
  # x.509 certificate certificate
  #
  # @api private
  #
  # @param [OpenSSL::X509::Name] subject The full subject (distinguished name) of the x.509
  #   certificate.
  #
  # @return [String] the name (CN) extracted from the subject.
  def self.name_from_subject(subject)
    if subject.respond_to? :to_a
      (subject.to_a.assoc('CN') || [])[1]
    end
  end

  # Create an instance of our Puppet::SSL::* class using a given instance of the wrapped class
  def self.from_instance(instance, name = nil)
    unless instance.is_a?(wrapped_class)
      raise ArgumentError, _("Object must be an instance of %{class_name}, %{actual_class} given") %
                           { class_name: wrapped_class, actual_class: instance.class }
    end
    if name.nil? and !instance.respond_to?(:subject)
      raise ArgumentError, _("Name must be supplied if it cannot be determined from the instance")
    end

    name ||= name_from_subject(instance.subject)
    result = new(name)
    result.content = instance
    result
  end

  # Convert a string into an instance
  def self.from_s(string, name = nil)
    instance = wrapped_class.new(string)
    from_instance(instance, name)
  end

  # Read content from disk appropriately.
  def read(path)
    # applies to Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest
    # nothing derives from Puppet::SSL::Certificate, but it is called by a number of other SSL Indirectors:
    # Puppet::Indirector::CertificateStatus::File (.indirection.find)
    # Puppet::Network::HTTP::WEBrick (.indirection.find)
    # Puppet::Network::HTTP::RackREST (.from_instance)
    # Puppet::Network::HTTP::WEBrickREST (.from_instance)
    # Puppet::SSL::Inventory (.indirection.search, implements its own add / rebuild / serials with encoding UTF8)
    @content = wrapped_class.new(Puppet::FileSystem.read(path, :encoding => Encoding::ASCII))
  end

  # Convert our thing to pem.
  def to_s
    return "" unless content

    content.to_pem
  end

  def to_data_hash
    to_s
  end

  # Provide the full text of the thing we're dealing with.
  def to_text
    return "" unless content

    content.to_text
  end

  def fingerprint(md = :SHA256)
    mds = md.to_s.upcase
    digest(mds).to_hex
  end

  def digest(algorithm = nil)
    algorithm ||= digest_algorithm

    Puppet::SSL::Digest.new(algorithm, content.to_der)
  end

  def digest_algorithm
    # The signature_algorithm on the X509 cert is a combination of the digest
    # algorithm and the encryption algorithm
    # e.g. md5WithRSAEncryption, sha256WithRSAEncryption
    # Unfortunately there isn't a consistent pattern
    # See RFCs 3279, 5758
    digest_re = Regexp.union(
      /ripemd160/i,
      /md[245]/i,
      /sha\d*/i
    )
    ln = content.signature_algorithm
    match = digest_re.match(ln)
    if match
      match[0].downcase
    else
      raise Puppet::Error, _("Unknown signature algorithm '%{ln}'") % { ln: ln }
    end
  end

  private

  def wrapped_class
    self.class.wrapped_class
  end
end