Class: Puppet::SSL::SSLProvider Private

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet/ssl/ssl_provider.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

SSL Provider creates ‘SSLContext` objects that can be used to create secure connections.

Examples:

To load an SSLContext from an existing private key and related certs/crls:

ssl_context = provider.load_context

To load an SSLContext from an existing password-protected private key and related certs/crls:

ssl_context = provider.load_context(password: 'opensesame')

To create an SSLContext from in-memory certs and keys:

cacerts = [<OpenSSL::X509::Certificate>]
crls = [<OpenSSL::X509::CRL>]
key = <OpenSSL::X509::PKey>
cert = <OpenSSL::X509::Certificate>
ssl_context = provider.create_context(cacerts: cacerts, crls: crls, private_key: key, client_cert: cert)

To create an SSLContext to connect to non-puppet HTTPS servers:

cacerts = [<OpenSSL::X509::Certificate>]
ssl_context = provider.create_root_context(cacerts: cacerts)

Instance Method Summary collapse

Instance Method Details

#create_context(cacerts:, crls:, private_key:, client_cert:, revocation: , include_system_store: false) ⇒ Puppet::SSL::SSLContext

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create an ‘SSLContext` using the trusted `cacerts`, `crls`, `private_key`, `client_cert`, and `revocation` mode. Connections made from the returned context will be mutually authenticated.

The ‘crls` parameter must contain CRLs corresponding to each CA in `cacerts` depending on the `revocation` mode:

  • ‘:chain` - `crls` must contain a CRL for every CA in `cacerts`

  • ‘:leaf` - `crls` must contain (at least) the CRL for the leaf CA in `cacerts`

  • ‘false` - `crls` can be empty

The ‘private_key` and public key from the `client_cert` must match.

Parameters:

  • cacerts (Array<OpenSSL::X509::Certificate>)

    Array of trusted CA certs

  • crls (Array<OpenSSL::X509::CRL>)

    Array of CRLs

  • private_key (OpenSSL::PKey::RSA, OpenSSL::PKey::EC)

    client’s private key

  • client_cert (OpenSSL::X509::Certificate)

    client’s cert whose public key matches the ‘private_key`

  • revocation (:chain, :leaf, false) (defaults to: )

    revocation mode

  • include_system_store (true, false) (defaults to: false)

    Also trust system CA

Returns:

Raises:



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/puppet/ssl/ssl_provider.rb', line 148

def create_context(cacerts:, crls:, private_key:, client_cert:, revocation: Puppet[:certificate_revocation], include_system_store: false)
  raise ArgumentError, _("CA certs are missing") unless cacerts
  raise ArgumentError, _("CRLs are missing") unless crls
  raise ArgumentError, _("Private key is missing") unless private_key
  raise ArgumentError, _("Client cert is missing") unless client_cert

  store = create_x509_store(cacerts, crls, revocation, include_system_store: include_system_store)
  client_chain = resolve_client_chain(store, client_cert, private_key)

  Puppet::SSL::SSLContext.new(
    store: store, cacerts: cacerts, crls: crls,
    private_key: private_key, client_cert: client_cert, client_chain: client_chain,
    revocation: revocation
  ).freeze
end

#create_insecure_contextPuppet::SSL::SSLContext

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create an insecure ‘SSLContext`. Connections made from the returned context will not authenticate the server, i.e. `VERIFY_NONE`, and are vulnerable to MITM. Do not call this method.

Returns:



33
34
35
36
37
# File 'lib/puppet/ssl/ssl_provider.rb', line 33

def create_insecure_context
  store = create_x509_store([], [], false)

  Puppet::SSL::SSLContext.new(store: store, verify_peer: false).freeze
end

#create_root_context(cacerts:, crls: [], revocation: ) ⇒ Puppet::SSL::SSLContext

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create an ‘SSLContext` using the trusted `cacerts` and optional `crls`. Connections made from the returned context will authenticate the server, i.e. `VERIFY_PEER`, but will not use a client certificate.

The ‘crls` parameter must contain CRLs corresponding to each CA in `cacerts` depending on the `revocation` mode. See #create_context.

Parameters:

  • cacerts (Array<OpenSSL::X509::Certificate>)

    Array of trusted CA certs

  • crls (Array<OpenSSL::X509::CRL>) (defaults to: [])

    Array of CRLs

  • revocation (:chain, :leaf, false) (defaults to: )

    revocation mode

Returns:

Raises:



52
53
54
55
56
# File 'lib/puppet/ssl/ssl_provider.rb', line 52

def create_root_context(cacerts:, crls: [], revocation: Puppet[:certificate_revocation])
  store = create_x509_store(cacerts, crls, revocation)

  Puppet::SSL::SSLContext.new(store: store, cacerts: cacerts, crls: crls, revocation: revocation).freeze
end

#create_system_context(cacerts:, path: , include_client_cert: false) ⇒ Puppet::SSL::SSLContext

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create an ‘SSLContext` using the trusted `cacerts` and any certs in OpenSSL’s default verify path locations. When running puppet as a gem, the location is system dependent. When running puppet from puppet-agent packages, the location refers to the cacerts bundle in the puppet-agent package.

Connections made from the returned context will authenticate the server, i.e. ‘VERIFY_PEER`, but will not use a client certificate (unless requested) and will not perform revocation checking.

Parameters:

  • cacerts (Array<OpenSSL::X509::Certificate>)

    Array of trusted CA certs

  • path (String, nil) (defaults to: )

    A file containing additional trusted CA certs.

  • include_client_cert (true, false) (defaults to: false)

    If true, the client cert will be added to the context allowing mutual TLS authentication. The default is false. If the client cert doesn’t exist then the option will be ignored.

Returns:

Raises:



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
# File 'lib/puppet/ssl/ssl_provider.rb', line 75

def create_system_context(cacerts:, path: Puppet[:ssl_trust_store], include_client_cert: false)
  store = create_x509_store(cacerts, [], false, include_system_store: true)

  if path
    stat = Puppet::FileSystem.stat(path)
    if stat
      if stat.ftype == 'file'
        # don't add empty files as ruby/openssl will raise
        if stat.size > 0
          begin
            store.add_file(path)
          rescue => e
            Puppet.err(_("Failed to add '%{path}' as a trusted CA file: %{detail}" % { path: path, detail: e.message }, e))
          end
        end
      else
        Puppet.warning(_("The 'ssl_trust_store' setting does not refer to a file and will be ignored: '%{path}'" % { path: path }))
      end
    end
  end

  if include_client_cert
    cert_provider = Puppet::X509::CertProvider.new
    private_key = cert_provider.load_private_key(Puppet[:certname], required: false)
    unless private_key
      Puppet.warning("Private key for '#{Puppet[:certname]}' does not exist")
    end

    client_cert = cert_provider.load_client_cert(Puppet[:certname], required: false)
    unless client_cert
      Puppet.warning("Client certificate for '#{Puppet[:certname]}' does not exist")
    end

    if private_key && client_cert
      client_chain = resolve_client_chain(store, client_cert, private_key)

      return Puppet::SSL::SSLContext.new(
        store: store, cacerts: cacerts, crls: [],
        private_key: private_key, client_cert: client_cert, client_chain: client_chain,
        revocation: false
      ).freeze
    end
  end

  Puppet::SSL::SSLContext.new(store: store, cacerts: cacerts, crls: [], revocation: false).freeze
end

#load_context(certname: , revocation: , password: nil, include_system_store: false) ⇒ Puppet::SSL::SSLContext

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Load an ‘SSLContext` using available certs and keys. An exception is raised if any component is missing or is invalid, such as a mismatched client cert and private key. Connections made from the returned context will be mutually authenticated.

Parameters:

  • certname (String) (defaults to: )

    Which cert & key to load

  • revocation (:chain, :leaf, false) (defaults to: )

    revocation mode

  • password (String, nil) (defaults to: nil)

    If the private key is encrypted, decrypt it using the password. If the key is encrypted, but a password is not specified, then the key cannot be loaded.

  • include_system_store (true, false) (defaults to: false)

    Also trust system CA

Returns:

Raises:



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/puppet/ssl/ssl_provider.rb', line 180

def load_context(certname: Puppet[:certname], revocation: Puppet[:certificate_revocation], password: nil, include_system_store: false)
  cert = Puppet::X509::CertProvider.new
  cacerts = cert.load_cacerts(required: true)
  crls = case revocation
         when :chain, :leaf
           cert.load_crls(required: true)
         else
           []
         end
  private_key = cert.load_private_key(certname, required: true, password: password)
  client_cert = cert.load_client_cert(certname, required: true)

  create_context(cacerts: cacerts, crls: crls,  private_key: private_key, client_cert: client_cert, revocation: revocation, include_system_store: include_system_store)
rescue OpenSSL::PKey::PKeyError => e
  raise Puppet::SSL::SSLError.new(_("Failed to load private key for host '%{name}': %{message}") % { name: certname, message: e.message }, e)
end

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/puppet/ssl/ssl_provider.rb', line 214

def print(ssl_context, alg = 'SHA256')
  if Puppet::Util::Log.sendlevel?(:debug)
    chain = ssl_context.client_chain
    # print from root to client
    chain.reverse.each_with_index do |cert, i|
      digest = Puppet::SSL::Digest.new(alg, cert.to_der)
      if i == chain.length - 1
        Puppet.debug(_("Verified client certificate '%{subject}' fingerprint %{digest}") % { subject: cert.subject.to_utf8, digest: digest })
      else
        Puppet.debug(_("Verified CA certificate '%{subject}' fingerprint %{digest}") % { subject: cert.subject.to_utf8, digest: digest })
      end
    end
    ssl_context.crls.each do |crl|
      oid_values = crl.extensions.to_h { |ext| [ext.oid, ext.value] }
      crlNumber = oid_values['crlNumber'] || 'unknown'
      authKeyId = (oid_values['authorityKeyIdentifier'] || 'unknown').chomp
      Puppet.debug("Using CRL '#{crl.issuer.to_utf8}' authorityKeyIdentifier '#{authKeyId}' crlNumber '#{crlNumber}'")
    end
  end
end

#verify_request(csr, public_key) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Verify the ‘csr` was signed with a private key corresponding to the `public_key`. This ensures the CSR was signed by someone in possession of the private key, and that it hasn’t been tampered with since.

Parameters:

Raises:

  • (Puppet::SSL:SSLError)

    The private_key for the given ‘public_key` was not used to sign the CSR.



206
207
208
209
210
211
212
# File 'lib/puppet/ssl/ssl_provider.rb', line 206

def verify_request(csr, public_key)
  unless csr.verify(public_key)
    raise Puppet::SSL::SSLError, _("The CSR for host '%{name}' does not match the public key") % { name: subject(csr) }
  end

  csr
end