Module: ChefConfig::Mixin::Credentials
- Included in:
- TrainTransport, WorkstationConfigLoader
- Defined in:
- lib/chef-config/mixin/credentials.rb
Overview
Helper methods for working with credentials files.
Constant Summary collapse
- GLOBAL_CONFIG_HASHES =
%w{ default_secrets_provider }.freeze
- SUPPORTED_SECRETS_PROVIDERS =
%w{ hashicorp-vault }.freeze
Instance Attribute Summary collapse
- #credentials_config ⇒ Object readonly
Instance Method Summary collapse
-
#credentials_file_path ⇒ String
Compute the path to the credentials file.
-
#credentials_profile(profile = nil) ⇒ String
Compute the active credentials profile name.
- #default_secrets_provider ⇒ Object
-
#global_options ⇒ Hash
Extract global (non-profile) settings from credentials file.
-
#load_credentials(profile = nil) ⇒ void
Load and process the active credentials.
-
#parse_credentials_file ⇒ String?
Load and parse the credentials file.
-
#resolve_secret(secrets_config) ⇒ String
Resolve a specific secret.
-
#resolve_secret_hashicorp(secrets_config) ⇒ String
Resolver logic for Hashicorp Vault.
-
#resolve_secrets(profile) ⇒ Hash
Resolve all secrets in a credentials file.
-
#valid_secrets_provider?(secrets_config) ⇒ true, false
Check, if referenced secrets provider is supported.
Instance Attribute Details
#credentials_config ⇒ Object (readonly)
29 30 31 |
# File 'lib/chef-config/mixin/credentials.rb', line 29 def credentials_config @credentials_config end |
Instance Method Details
#credentials_file_path ⇒ String
Compute the path to the credentials file.
58 59 60 61 62 63 64 65 |
# File 'lib/chef-config/mixin/credentials.rb', line 58 def credentials_file_path return Chef::Config[:credentials] if defined?(Chef::Config) && Chef::Config.key?(:credentials) env_file = ENV["CHEF_CREDENTIALS_FILE"] return env_file if env_file && File.file?(env_file) PathHelper.home(ChefUtils::Dist::Infra::USER_CONF_DIR, "credentials").freeze end |
#credentials_profile(profile = nil) ⇒ String
Compute the active credentials profile name.
The lookup order is argument (from –profile), environment variable ($CHEF_PROFILE), context file (~/.chef/context), and then “default” as a fallback.
41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/chef-config/mixin/credentials.rb', line 41 def credentials_profile(profile = nil) context_file = PathHelper.home(ChefUtils::Dist::Infra::USER_CONF_DIR, "context").freeze if !profile.nil? profile elsif ENV.include?("CHEF_PROFILE") ENV["CHEF_PROFILE"] elsif File.file?(context_file) File.read(context_file).strip else "default" end end |
#default_secrets_provider ⇒ Object
167 168 169 |
# File 'lib/chef-config/mixin/credentials.rb', line 167 def default_secrets_provider ["default_secrets_provider"] end |
#global_options ⇒ Hash
Extract global (non-profile) settings from credentials file.
118 119 120 121 |
# File 'lib/chef-config/mixin/credentials.rb', line 118 def globals = credentials_config.filter { |_, v| v.is_a? String } globals.merge! credentials_config.filter { |k, _| GLOBAL_CONFIG_HASHES.include? k } end |
#load_credentials(profile = nil) ⇒ void
This method returns an undefined value.
Load and process the active credentials.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/chef-config/mixin/credentials.rb', line 93 def load_credentials(profile = nil) profile = credentials_profile(profile) parse_credentials_file return if credentials_config.nil? # No credentials, nothing to do here. if credentials_config[profile].nil? # Unknown profile name. For "default" just silently ignore, otherwise # raise an error. return if profile == "default" raise ChefConfig::ConfigurationError, "Profile #{profile} doesn't exist. Please add it to #{credentials_file_path}." end resolve_secrets(profile) apply_credentials(credentials_config[profile], profile) end |
#parse_credentials_file ⇒ String?
Load and parse the credentials file.
Returns ‘nil` if the credentials file is unavailable.
73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/chef-config/mixin/credentials.rb', line 73 def parse_credentials_file credentials_file = credentials_file_path return nil unless File.file?(credentials_file) begin @credentials_config = Tomlrb.load_file(credentials_file) rescue => e # TOML's error messages are mostly rubbish, so we'll just give a generic one = "Unable to parse Credentials file: #{credentials_file}\n" << e. raise ChefConfig::ConfigurationError, end end |
#resolve_secret(secrets_config) ⇒ String
Resolve a specific secret.
To be replaced later by a Train-like framework to support multiple backends.
178 179 180 |
# File 'lib/chef-config/mixin/credentials.rb', line 178 def resolve_secret(secrets_config) resolve_secret_hashicorp(secrets_config) end |
#resolve_secret_hashicorp(secrets_config) ⇒ String
Resolver logic for Hashicorp Vault.
Local lazy loading of Gems which are not part of chef-config or chef-utils, but chef itself to be switched by a unified secrets mechanism for credentials and Chef DSL later. Showstopper mitigation for 19 GA.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/chef-config/mixin/credentials.rb', line 191 def resolve_secret_hashicorp(secrets_config) vault_config = secrets_config.transform_keys(&:to_sym) vault_config[:address] = vault_config[:endpoint] # Lazy require due to Gem being part of Chef and rarely used functionality require "vault" unless defined? Vault @vault ||= Vault::Client.new(vault_config) secret = secrets_config["secret"] engine = vault_config[:engine] || "secret" engine_type = vault_config[:engine_type] || "kv2" secret_value = case engine_type when "kv", "kv1" @vault.logical.read("#{engine_type}/#{secret}") when "kv2" @vault.kv(engine).read(secret)&.data else raise UnsupportedSecretsProvider.new("No support for secrets engine #{engine_type}") end # Always JSON for Hashicorp Vault, but this is future compatible to other providers if secret_value.is_a?(Hash) require "jmespath" unless defined? ::JMESPath ::JMESPath.search(secrets_config["field"], secret_value) else secret_value end end |
#resolve_secrets(profile) ⇒ Hash
Resolve all secrets in a credentials file
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/chef-config/mixin/credentials.rb', line 130 def resolve_secrets(profile) return unless credentials_config raise NoCredentialsFound.new("No credentials found for profile '#{profile}'") unless credentials_config[profile] secrets = credentials_config[profile].filter { |k, v| v.is_a?(Hash) && v.keys.include?("secret") } return if secrets.empty? secrets.each do |option, secrets_config| unless valid_secrets_provider?(secrets_config) raise UnsupportedSecretsProvider.new("Unsupported credentials secrets provider on '#{option}' for profile '#{profile}'") end secrets_config.merge!(default_secrets_provider) logger.debug("Resolving credentials secret '#{option}' for profile '#{profile}'") begin resolved_value = resolve_secret(secrets_config) ensure raise UnresolvedSecret.new("Could not resolve secret '#{option}' for profile '#{profile}'") if resolved_value.nil? end credentials_config[profile][option] = resolved_value end end |
#valid_secrets_provider?(secrets_config) ⇒ true, false
Check, if referenced secrets provider is supported.
160 161 162 163 164 165 |
# File 'lib/chef-config/mixin/credentials.rb', line 160 def valid_secrets_provider?(secrets_config) provider_config = secrets_config["secrets_provider"] || default_secrets_provider provider = provider_config["name"] provider && SUPPORTED_SECRETS_PROVIDERS.include?(provider) end |