summaryrefslogtreecommitdiff
path: root/lib/chef/knife/data_bag_secret_options.rb
blob: 19b0dfca38d9d13dfbce8e60c75bfa642d650361 (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
#
# Author:: Tyler Ball (<tball@chef.io>)
# Copyright:: Copyright 2014-2016, 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.
#

require "mixlib/cli"
require_relative "../config"
require_relative "../encrypted_data_bag_item/check_encrypted"

class Chef
  class Knife
    module DataBagSecretOptions
      include Mixlib::CLI
      include Chef::EncryptedDataBagItem::CheckEncrypted

      # The config object is populated by knife#merge_configs with knife.rb `knife[:*]` config values, but they do
      # not overwrite the command line properties.  It does mean, however, that `knife[:secret]` and `--secret-file`
      # passed at the same time populate both `config[:secret]` and `config[:secret_file]`.  We cannot differentiate
      # the valid case (`knife[:secret]` in config file and `--secret-file` on CL) and the invalid case (`--secret`
      # and `--secret-file` on the CL) - thats why I'm storing the CL options in a different config key if they
      # are provided.

      def self.included(base)
        base.option :secret,
               short: "-s SECRET",
               long: "--secret ",
               description: "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'.",
               # Need to store value from command line in separate variable - knife#merge_configs populates same keys
               # on config object from
               proc: Proc.new { |s| set_cl_secret(s) }

        base.option :secret_file,
               long: "--secret-file SECRET_FILE",
               description: "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'.",
               proc: Proc.new { |sf| set_cl_secret_file(sf) }

        base.option :encrypt,
               long: "--encrypt",
               description: "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it.",
               boolean: true,
               default: false
      end

      def encryption_secret_provided?
        base_encryption_secret_provided?
      end

      def encryption_secret_provided_ignore_encrypt_flag?
        base_encryption_secret_provided?(false)
      end

      def read_secret
        # Moving the non 'compile-time' requires into here to speed up knife command loading
        # IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item'
        require_relative "../encrypted_data_bag_item"

        if has_cl_secret?
          config[:secret]
        elsif has_cl_secret_file?
          Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
        elsif secret = knife_config[:secret]
          secret
        else
          secret_file = knife_config[:secret_file]
          Chef::EncryptedDataBagItem.load_secret(secret_file)
        end
      end

      def validate_secrets
        if has_cl_secret? && has_cl_secret_file?
          ui.fatal("Please specify only one of --secret, --secret-file")
          exit(1)
        end

        if knife_config[:secret] && knife_config[:secret_file]
          ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config file")
          exit(1)
        end
      end

      private

      ##
      # Determine if the user has specified an appropriate secret for encrypting data bag items.
      # @return boolean
      def base_encryption_secret_provided?(need_encrypt_flag = true)
        validate_secrets

        return true if has_cl_secret? || has_cl_secret_file?

        if need_encrypt_flag
          if config[:encrypt]
            unless knife_config[:secret] || knife_config[:secret_file]
              ui.fatal("No secret or secret_file specified in config, unable to encrypt item.")
              exit(1)
            end
            return true
          end
          return false
        elsif knife_config[:secret] || knife_config[:secret_file]
          # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret
          return true
        end
        false
      end

      def has_cl_secret?
        Chef::Config[:knife].key?(:cl_secret)
      end

      def self.set_cl_secret(s)
        Chef::Config[:knife][:cl_secret] = s
      end

      def has_cl_secret_file?
        Chef::Config[:knife].key?(:cl_secret_file)
      end

      def self.set_cl_secret_file(sf)
        Chef::Config[:knife][:cl_secret_file] = sf
      end

      def knife_config
        Chef::Config.key?(:knife) ? Chef::Config[:knife] : {}
      end

    end
  end
end