summaryrefslogtreecommitdiff
path: root/lib/chef/http/ssl_policies.rb
blob: bc688f13a6a8f6059f668bc3423b3bf7eb0443ba (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#--
# Author:: Adam Jacob (<adam@chef.io>)
# Author:: Thom May (<thom@clearairturbulence.org>)
# Author:: Nuo Yan (<nuo@chef.io>)
# Author:: Christopher Brown (<cb@chef.io>)
# Author:: Christopher Walters (<cw@chef.io>)
# Author:: Daniel DeLeo (<dan@chef.io>)
# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

autoload :OpenSSL, "openssl"
require_relative "../util/path_helper"

class Chef
  class HTTP

    # == Chef::HTTP::DefaultSSLPolicy
    # Configures SSL behavior on an HTTP object via visitor pattern.
    class DefaultSSLPolicy

      def self.apply_to(http_client)
        new(http_client).apply
        http_client
      end

      attr_reader :http_client

      def initialize(http_client)
        @http_client = http_client
      end

      def apply
        set_verify_mode
        set_ca_store
        set_custom_certs
        set_client_credentials
      end

      def set_verify_mode
        if config[:ssl_verify_mode] == :verify_none
          http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
        elsif config[:ssl_verify_mode] == :verify_peer
          http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
        end
      end

      def set_ca_store
        if config[:ssl_ca_path]
          unless ::File.exist?(config[:ssl_ca_path])
            raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist"
          end

          http_client.ca_path = config[:ssl_ca_path]
        elsif config[:ssl_ca_file]
          unless ::File.exist?(config[:ssl_ca_file])
            raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist"
          end

          http_client.ca_file = config[:ssl_ca_file]
        elsif ENV["SSL_CERT_FILE"]
          unless ::File.exist?(ENV["SSL_CERT_FILE"])
            raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{ENV["SSL_CERT_FILE"]} does not exist"
          end

          http_client.ca_file = ENV["SSL_CERT_FILE"]
        end
      end

      def set_custom_certs
        unless http_client.cert_store
          http_client.cert_store = OpenSSL::X509::Store.new
          http_client.cert_store.set_default_paths
        end
        if config.trusted_certs_dir
          certs = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob_dir(config.trusted_certs_dir), "*.{crt,pem}"))
          certs.each do |cert_file|
            cert = begin
              OpenSSL::X509::Certificate.new(::File.binread(cert_file))
                   rescue OpenSSL::X509::CertificateError => e
                     raise Chef::Exceptions::ConfigurationError, "Error reading cert file '#{cert_file}', original error '#{e.class}: #{e.message}'"
            end
            add_trusted_cert(cert)
          end
        end
      end

      def set_client_credentials
        return unless config[:ssl_client_cert] || config[:ssl_client_key]

        unless config[:ssl_client_cert] && config[:ssl_client_key]
          raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together"
        end
        unless ::File.exists?(config[:ssl_client_cert])
          raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist"
        end
        unless ::File.exists?(config[:ssl_client_key])
          raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist"
        end

        begin
          http_client.cert = OpenSSL::X509::Certificate.new(::File.binread(config[:ssl_client_cert]))
        rescue OpenSSL::X509::CertificateError => e
          raise Chef::Exceptions::ConfigurationError, "Error reading cert file '#{config[:ssl_client_cert]}', original error '#{e.class}: #{e.message}'"
        end

        begin
          http_client.key = OpenSSL::PKey::RSA.new(::File.binread(config[:ssl_client_key]))
        rescue OpenSSL::PKey::RSAError => e
          raise Chef::Exceptions::ConfigurationError, "Error reading key file '#{config[:ssl_client_key]}', original error '#{e.class}: #{e.message}'"
        end
      end

      def config
        Chef::Config
      end

      private

      def add_trusted_cert(cert)
        http_client.cert_store.add_cert(cert)
      rescue OpenSSL::X509::StoreError => e
        raise e unless e.message == "cert already in hash table"
      end

    end

    class APISSLPolicy < DefaultSSLPolicy

      def set_verify_mode
        if config[:ssl_verify_mode] == :verify_peer || config[:verify_api_cert]
          http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
        elsif config[:ssl_verify_mode] == :verify_none
          http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
        end
      end
    end

    # This policy is used when we want to explicitly turn on verification
    # for a specific request regardless of the API Policy. For example, when
    # doing a `remote_file` where the user specified `verify_mode :verify_peer`
    class VerifyPeerSSLPolicy < DefaultSSLPolicy
      def set_verify_mode
        http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
      end
    end

    # This policy is used when we want to explicitly turn off verification
    # for a specific request regardless of the API Policy. For example, when
    # doing a `remote_file` where the user specified `verify_mode :verify_none`
    class VerifyNoneSSLPolicy < DefaultSSLPolicy
      def set_verify_mode
        http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
      end
    end

  end
end