Class: Bolt::Plugin::Vault

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt/plugin/vault.rb

Defined Under Namespace

Classes: VaultHTTPError

Constant Summary collapse

TOKEN_HEADER =

All requests for secrets must have a token in the request header

"X-Vault-Token"
DEFAULT_HEADER =

Default header for all requests, including auth methods

{
  "Content-Type" => "application/json",
  "Accept" => "application/json"
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config:, **_opts) ⇒ Vault

Returns a new instance of Vault.



61
62
63
64
65
# File 'lib/bolt/plugin/vault.rb', line 61

def initialize(config:, **_opts)
  validate_config(config)
  @config = config
  @logger = Logging.logger[self]
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



15
16
17
# File 'lib/bolt/plugin/vault.rb', line 15

def config
  @config
end

Instance Method Details

#auth_token(auth) ⇒ Object

Authenticate with Vault using the ‘Token’ auth method



190
191
192
193
# File 'lib/bolt/plugin/vault.rb', line 190

def auth_token(auth)
  validate_auth(auth, %w[token])
  auth['token']
end

#auth_userpass(auth, opts) ⇒ Object

Authenticate with Vault using the ‘Userpass’ auth method



196
197
198
199
200
201
202
203
# File 'lib/bolt/plugin/vault.rb', line 196

def auth_userpass(auth, opts)
  validate_auth(auth, %w[user pass])
  path = "auth/userpass/login/#{auth['user']}"
  uri = uri(opts, path)
  data = { "password" => auth['pass'] }.to_json

  request(:Post, uri, opts, data)['auth']['client_token']
end

#client(uri, opts) ⇒ Object

Configure the http/s client



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/bolt/plugin/vault.rb', line 95

def client(uri, opts)
  client = Net::HTTP.new(uri.host, uri.port)

  if uri.scheme == 'https'
    cacert = opts['cacert'] || config['cacert'] || ENV['VAULT_CACERT']

    unless cacert
      raise Bolt::ValidationError, "Expected cacert to be set when using https"
    end

    client.use_ssl = true
    client.ssl_version = :TLSv1_2
    client.ca_file = cacert
    client.verify_mode = OpenSSL::SSL::VERIFY_PEER
  end

  client
end

#hooksObject



57
58
59
# File 'lib/bolt/plugin/vault.rb', line 57

def hooks
  [:resolve_reference]
end

#nameObject



53
54
55
# File 'lib/bolt/plugin/vault.rb', line 53

def name
  'vault'
end

#parse_response(response, opts) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/bolt/plugin/vault.rb', line 152

def parse_response(response, opts)
  data = case opts['version']
         when 2
           response['data']['data']
         else
           response['data']
         end

  if opts['field']
    unless data[opts['field']]
      raise Bolt::ValidationError, "Unknown secrets field: #{opts['field']}"
    end
    data[opts['field']]
  else
    data
  end
end

#request(verb, uri, opts, data, header = {}) ⇒ Object



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
# File 'lib/bolt/plugin/vault.rb', line 123

def request(verb, uri, opts, data, header = {})
  # Add on any header options
  header = DEFAULT_HEADER.merge(header)

  # Create the HTTP request
  client = client(uri, opts)
  request = Net::HTTP.const_get(verb).new(uri.request_uri, header)

  # Attach any data
  request.body = data if data

  # Send the request
  begin
    response = client.request(request)
  rescue StandardError => e
    raise Bolt::Error.new(
      "Failed to connect to #{uri}: #{e.message}",
      'CONNECT_ERROR'
    )
  end

  case response
  when Net::HTTPOK
    JSON.parse(response.body)
  else
    raise VaultHTTPError, response
  end
end

#request_token(auth, opts) ⇒ Object

Request a token from Vault using one of the auth methods



171
172
173
174
175
176
177
178
179
180
# File 'lib/bolt/plugin/vault.rb', line 171

def request_token(auth, opts)
  case auth['method']
  when 'token'
    auth_token(auth)
  when 'userpass'
    auth_userpass(auth, opts)
  else
    raise Bolt::ValidationError, "Unknown auth method: #{auth['method']}"
  end
end

#resolve_reference(opts) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/bolt/plugin/vault.rb', line 67

def resolve_reference(opts)
  validate_options(opts)

  header = {
    TOKEN_HEADER => token(opts)
  }

  response = request(:Get, uri(opts), opts, nil, header)

  parse_response(response, opts)
end

#token(opts) ⇒ Object

Auth token to vault server



115
116
117
118
119
120
121
# File 'lib/bolt/plugin/vault.rb', line 115

def token(opts)
  if (auth = opts['auth'] || config['auth'])
    request_token(auth, opts)
  else
    ENV['VAULT_TOKEN']
  end
end

#uri(opts, path = nil) ⇒ Object

Request uri - built up from Vault server url and secrets path



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/bolt/plugin/vault.rb', line 80

def uri(opts, path = nil)
  url = opts['server_url'] || config['server_url'] || ENV['VAULT_ADDR']

  # Handle the different versions of the API
  if opts['version'] == 2
    mount, store = opts['path'].split('/', 2)
    opts['path'] = [mount, 'data', store].join('/')
  end

  path ||= opts['path']

  URI.parse(File.join(url, "v1", path))
end

#validate_auth(auth, required_keys) ⇒ Object



182
183
184
185
186
187
# File 'lib/bolt/plugin/vault.rb', line 182

def validate_auth(auth, required_keys)
  required_keys.each do |key|
    next if auth[key]
    raise Bolt::ValidationError, "Expected key in #{auth['method']} auth method: #{key}"
  end
end

#validate_config(config) ⇒ Object

Make sure no unexpected keys are in the config



27
28
29
30
31
32
33
34
# File 'lib/bolt/plugin/vault.rb', line 27

def validate_config(config)
  known_keys = %w[server_url auth cacert]

  config.each do |key, _v|
    next if known_keys.include?(key)
    raise Bolt::ValidationError, "Unexpected key in Vault plugin config: #{key}"
  end
end

#validate_options(opts) ⇒ Object

Make sure no unexpected keys are in the inventory config and that required keys are present



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/bolt/plugin/vault.rb', line 38

def validate_options(opts)
  known_keys = %w[_plugin server_url auth path field version cacert]
  required_keys = %w[path]

  opts.each do |key, _v|
    next if known_keys.include?(key)
    raise Bolt::ValidationError, "Unexpected key in inventory config: #{key}"
  end

  required_keys.each do |key|
    next if opts[key]
    raise Bolt::ValidationError, "Expected key in inventory config: #{key}"
  end
end