Class: PuppetX::VaultLookup::Lookup
- Inherits:
-
Object
- Object
- PuppetX::VaultLookup::Lookup
- Defined in:
- lib/puppet_x/vault_lookup/lookup.rb
Overview
Internal class for looking up data from Vault.
Class Method Summary collapse
- .append_api_errors(message, response) ⇒ Object
- .auth_login_body(cert_role) ⇒ Object
- .get_approle_auth_token(client, vault_addr, path_segment, role_id, secret_id, namespace) ⇒ Object
- .get_cert_auth_token(client, vault_addr, cert_path_segment, cert_role, namespace) ⇒ Object
- .get_secret(client:, uri:, token:, namespace:, key:) ⇒ Object
- .get_token(client, login_url, request_data, namespace) ⇒ Object
- .json_parse(response, field) ⇒ Object
- .lookup(cache:, path:, vault_addr: nil, cert_path_segment: nil, cert_role: nil, namespace: nil, field: nil, auth_method: nil, role_id: nil, secret_id: nil, approle_path_segment: nil, agent_sink_file: nil) ⇒ Object
- .read_token_from_sink(sink:) ⇒ Object
Class Method Details
.append_api_errors(message, response) ⇒ Object
185 186 187 188 189 190 191 192 193 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 185 def self.append_api_errors(, response) errors = json_parse(response, 'errors') warnings = json_parse(response, 'warnings') # Can't modify frozen String, so we copy. copy = .dup copy << " (api errors: #{errors})" if errors copy << " (api warnings: #{warnings})" if warnings copy end |
.auth_login_body(cert_role) ⇒ Object
109 110 111 112 113 114 115 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 109 def self.auth_login_body(cert_role) if cert_role { name: cert_role }.to_json else '' end end |
.get_approle_auth_token(client, vault_addr, path_segment, role_id, secret_id, namespace) ⇒ Object
153 154 155 156 157 158 159 160 161 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 153 def self.get_approle_auth_token(client, vault_addr, path_segment, role_id, secret_id, namespace) vault_request_data = { role_id: role_id, secret_id: secret_id }.to_json login_url = vault_addr + path_segment + 'login' # rubocop:disable Style/StringConcatenation get_token(client, login_url, vault_request_data, namespace) end |
.get_cert_auth_token(client, vault_addr, cert_path_segment, cert_role, namespace) ⇒ Object
142 143 144 145 146 147 148 149 150 151 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 142 def self.get_cert_auth_token(client, vault_addr, cert_path_segment, cert_role, namespace) role_data = auth_login_body(cert_role) segment = if cert_path_segment.end_with?('/') cert_path_segment else "#{cert_path_segment}/" end login_url = vault_addr + segment + 'login' # rubocop:disable Style/StringConcatenation get_token(client, login_url, role_data, namespace) end |
.get_secret(client:, uri:, token:, namespace:, key:) ⇒ Object
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 117 def self.get_secret(client:, uri:, token:, namespace:, key:) headers = { 'X-Vault-Token' => token, 'X-Vault-Namespace' => namespace }.delete_if { |_key, value| value.nil? } secret_response = client.get(uri, headers: headers, options: { include_system_store: true }) unless secret_response.success? = "Received #{secret_response.code} response code from vault at #{uri} for secret lookup" raise Puppet::Error, append_api_errors(, secret_response) end begin json_data = JSON.parse(secret_response.body) if key.nil? && json_data['data'].key?('data') json_data['data']['data'] elsif key.nil? json_data['data'] elsif json_data['data'].key?('data') json_data['data']['data'][key] else json_data['data'][key] end rescue StandardError raise Puppet::Error, 'Error parsing json secret data from vault response' end end |
.get_token(client, login_url, request_data, namespace) ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 163 def self.get_token(client, login_url, request_data, namespace) headers = { 'Content-Type' => 'application/json', 'X-Vault-Namespace' => namespace }.delete_if { |_key, value| value.nil? } response = client.post(login_url, request_data, headers: headers, options: { include_system_store: true }) unless response.success? = "Received #{response.code} response code from vault at #{login_url} for authentication" raise Puppet::Error, append_api_errors(, response) end begin token = JSON.parse(response.body)['auth']['client_token'] rescue StandardError raise Puppet::Error, 'Unable to parse client_token from vault response' end raise Puppet::Error, 'No client_token found' if token.nil? token end |
.json_parse(response, field) ⇒ Object
195 196 197 198 199 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 195 def self.json_parse(response, field) JSON.parse(response.body)[field] rescue StandardError nil end |
.lookup(cache:, path:, vault_addr: nil, cert_path_segment: nil, cert_role: nil, namespace: nil, field: nil, auth_method: nil, role_id: nil, secret_id: nil, approle_path_segment: nil, agent_sink_file: nil) ⇒ Object
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 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 9 def self.lookup(cache:, path:, vault_addr: nil, cert_path_segment: nil, cert_role: nil, namespace: nil, field: nil, auth_method: nil, role_id: nil, secret_id: nil, approle_path_segment: nil, agent_sink_file: nil) if vault_addr.nil? Puppet.debug 'No Vault address was set on function, defaulting to value from VAULT_ADDR env value' vault_addr = ENV['VAULT_ADDR'] raise Puppet::Error, 'No vault_addr given and VAULT_ADDR env variable not set' if vault_addr.nil? end if namespace.nil? Puppet.debug 'No Vault namespace was set on function, defaulting to value from VAULT_NAMESPACE env value' namespace = ENV['VAULT_NAMESPACE'] end # Check the cache. # The path, vault_addr, and namepsace fields could result in a different # secret value, so use them for the cache key. cache_key = [path, vault_addr, namespace, field] cache_hash = cache.retrieve(self) prior_result = cache_hash[cache_key] unless prior_result.nil? Puppet.debug "Using cached result for #{path}: #{prior_result}" return prior_result end auth_method = ENV['VAULT_AUTH_METHOD'] || 'cert' if auth_method.nil? role_id = ENV['VAULT_ROLE_ID'] if role_id.nil? secret_id = ENV['VAULT_SECRET_ID'] if secret_id.nil? cert_path_segment = 'v1/auth/cert/' if cert_path_segment.nil? approle_path_segment = 'v1/auth/approle' if approle_path_segment.nil? vault_base_uri = URI(vault_addr) # URI is used here to parse the vault_addr into a host string # and port; it's possible to generate a URI::Generic when a scheme # is not defined, so double check here to make sure at least # host is defined. raise Puppet::Error, "Unable to parse a hostname from #{vault_addr}" unless vault_base_uri.hostname client = Puppet.runtime[:http] case auth_method when 'cert' token = get_cert_auth_token(client, vault_base_uri, cert_path_segment, cert_role, namespace) when 'approle' raise Puppet::Error, 'role_id and VAULT_ROLE_ID are both nil' if role_id.nil? raise Puppet::Error, 'secret_id and VAULT_SECRET_ID are both nil' if secret_id.nil? token = get_approle_auth_token(client, vault_base_uri, approle_path_segment, role_id, secret_id, namespace) when 'agent' # Setting the token to nil causes the 'X-Vault-Token' header to not be # added by this function when making requests to Vault. Instead, we're # relying on the local Vault agent's cache to add the token into the # headers of our request. This assumes that 'use_auto_auth_token = true' # is in the Vault agent's cache config. # @see https://developer.hashicorp.com/vault/docs/agent/caching#using-auto-auth-token token = nil when 'agent_sink' # This assumes the token is availble in a sink file populated by the Vault Agent. # @see https://developer.hashicorp.com/vault/docs/agent/autoauth/sinks/file if agent_sink_file.nil? Puppet.debug "No agent sink file was set on function, defaulting to VAULT_AGENT_SINK_FILE env var: #{ENV['VAULT_AGENT_SINK_FILE']}" agent_sink_file = ENV['VAULT_AGENT_SINK_FILE'] end raise Puppet::Error, 'agent_sink_file must be defined when using the agent_sink auth method' if agent_sink_file.nil? token = read_token_from_sink(sink: agent_sink_file) end secret_uri = vault_base_uri + "/v1/#{path.delete_prefix('/')}" data = get_secret(client: client, uri: secret_uri, token: token, namespace: namespace, key: field) sensitive_data = Puppet::Pops::Types::PSensitiveType::Sensitive.new(data) Puppet.debug "Caching found data for #{path}" cache_hash[cache_key] = sensitive_data sensitive_data end |
.read_token_from_sink(sink:) ⇒ Object
201 202 203 204 205 |
# File 'lib/puppet_x/vault_lookup/lookup.rb', line 201 def self.read_token_from_sink(sink:) raise Puppet::Error, "The agent_sink_file does not exist or is not readable: #{sink}" unless Puppet::FileSystem.exist?(sink) Puppet::FileSystem.read(sink).chomp end |