pkg/utils/vault/secret_file.go (86 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package vault
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"time"
)
const (
// InMemoryFile is the name of the file to use to not write it to disk and only keep it in memory
InMemoryFile = "in-memory"
// buildLicensePubKeyPrefixEnvVar allows to prefix the field to retrieve a specific license public key secret
buildLicensePubKeyPrefixEnvVar = "BUILD_LICENSE_PUBKEY"
retryTimeout = 10 * time.Second
retryInterval = 1 * time.Second
)
// SecretFile maps a Vault Secret into a file that is optionally written to disk.
type SecretFile struct {
// Name is the name of the file to optionaly write the secret to disk. If the value is 'in-memory' the file is not written to disk.
Name string
// Path is the Vault path to read the secret
Path string
// FormatJSON indicates if the secret needs to be printed in JSON format. It can be only true if FieldResolver is not set.
FormatJSON bool
// FieldResolver is a function to get only a given field of the secret. It is optional and can be nil.
// It can be only set if FormatJSON is false.
FieldResolver func() string
// Base64Encoded indicates if the secret needs to be decoded. It is only usable with FieldResolver is set.
Base64Encoded bool
}
// ReadFile reads the file if it exists or else reads the Secret from Vault and
// writes it to the file on disk.
func ReadFile(clientProvider ClientProvider, f SecretFile) ([]byte, error) {
if _, err := os.Stat(f.Name); err == nil {
return os.ReadFile(f.Name)
}
client, err := clientProvider()
if err != nil {
return nil, err
}
bytes, err := f.readFromVault(client)
if err != nil {
return nil, err
}
if f.Name != InMemoryFile {
err = os.WriteFile(f.Name, bytes, 0600)
if err != nil {
return nil, err
}
}
return bytes, nil
}
func (f SecretFile) readFromVault(c Client) ([]byte, error) {
log.Printf("Read %s from vault", f.Path)
secretPath := filepath.Join(rootPath(), f.Path)
secret, err := read(c, secretPath)
if err != nil {
return nil, err
}
// validate mutual exclusions
if f.FieldResolver != nil && f.FormatJSON {
return nil, fmt.Errorf("FieldResolver cannot be defined if FormatJSON is true")
}
if f.FieldResolver == nil && !f.FormatJSON {
return nil, fmt.Errorf("FieldResolver must be defined if FormatJSON is false")
}
if f.FieldResolver == nil && f.Base64Encoded {
return nil, fmt.Errorf("FieldResolver must be defined if Base64Encoded is true")
}
// encode to JSON and return
if f.FormatJSON {
return json.Marshal(secret.Data)
}
// get the field as string
field := f.FieldResolver()
var ok bool
val, ok := secret.Data[field]
if !ok {
return nil, fmt.Errorf("field %s not found at %s", field, secretPath)
}
stringVal, ok := val.(string)
if !ok {
return nil, fmt.Errorf("secret %s is not a string", secretPath)
}
// decode from Base64 encoded and return
if f.Base64Encoded {
return base64.StdEncoding.DecodeString(stringVal)
}
// or return as is
return []byte(stringVal), nil
}
// LicensePubKeyPrefix is a specific field resolver that prefixes the given field with the value of the build license public key environment variable.
func LicensePubKeyPrefix(field string) func() string {
return func() string {
prefix := os.Getenv(buildLicensePubKeyPrefixEnvVar)
if prefix != "" {
field = fmt.Sprintf("%s-%s", prefix, field)
}
return field
}
}