Loading vault url from kubernetes vault auth file

This commit is contained in:
Jefferson Girao 2017-07-25 09:08:15 +02:00
parent f8eebde14f
commit b12944d8a7
17 changed files with 286 additions and 63 deletions

View file

@ -40,21 +40,17 @@ func NewAppRolePlugin(client *api.Client) AuthInterface {
} }
// Create a approle plugin with the secret id and role id provided in the file // Create a approle plugin with the secret id and role id provided in the file
func (r authAppRolePlugin) Create(cfg map[string]string) (string, error) { func (r authAppRolePlugin) Create(cfg *vaultAuthOptions) (string, error) {
// step: extract the options if cfg.RoleID == "" {
roleID, _ := cfg["role_id"] cfg.RoleID = os.Getenv("VAULT_SIDEKICK_ROLE_ID")
secretID, _ := cfg["secret_id"]
if roleID == "" {
roleID = os.Getenv("VAULT_SIDEKICK_ROLE_ID")
} }
if secretID == "" { if cfg.SecretID == "" {
secretID = os.Getenv("VAULT_SIDEKICK_SECRET_ID") cfg.SecretID = os.Getenv("VAULT_SIDEKICK_SECRET_ID")
} }
// step: create the token request // step: create the token request
request := r.client.NewRequest("POST", "/v1/auth/approle/login") request := r.client.NewRequest("POST", "/v1/auth/approle/login")
login := appRoleLogin{SecretID: secretID, RoleID: roleID} login := appRoleLogin{SecretID: cfg.SecretID, RoleID: cfg.RoleID}
if err := request.SetJSONBody(login); err != nil { if err := request.SetJSONBody(login); err != nil {
return "", err return "", err
} }

View file

@ -37,18 +37,16 @@ func NewUserTokenPlugin(client *api.Client) AuthInterface {
} }
// Create retrieves the token from an environment variable or file // Create retrieves the token from an environment variable or file
func (r authTokenPlugin) Create(cfg map[string]string) (string, error) { func (r authTokenPlugin) Create(cfg *vaultAuthOptions) (string, error) {
filename, _ := cfg["filename"] if cfg.FileName != "" {
fileFormat, _ := cfg["fileFormat"] content, err := readConfigFile(cfg.FileName, cfg.FileFormat)
if filename != "" {
content, err := readConfigFile(filename, fileFormat)
if err != nil { if err != nil {
return "", err return "", err
} }
// check: ensure we have a token in the file // check: ensure we have a token in the file
token, found := content["token"] token := content.Token
if !found { if token == "" {
return "", fmt.Errorf("the auth file: %s does not contain a token", filename) return "", fmt.Errorf("the auth file: %s does not contain a token", cfg.FileName)
} }
return token, nil return token, nil

View file

@ -41,21 +41,18 @@ func NewUserPassPlugin(client *api.Client) AuthInterface {
} }
// Create a userpass plugin with the username and password provide in the file // Create a userpass plugin with the username and password provide in the file
func (r authUserPassPlugin) Create(cfg map[string]string) (string, error) { func (r authUserPassPlugin) Create(cfg *vaultAuthOptions) (string, error) {
// step: extract the options // step: extract the options
username, _ := cfg["username"] if cfg.Username == "" {
password, _ := cfg["password"] cfg.Username = os.Getenv("VAULT_SIDEKICK_USERNAME")
if username == "" {
username = os.Getenv("VAULT_SIDEKICK_USERNAME")
} }
if password == "" { if cfg.Password == "" {
password = os.Getenv("VAULT_SIDEKICK_PASSWORD") cfg.Password = os.Getenv("VAULT_SIDEKICK_PASSWORD")
} }
// step: create the token request // step: create the token request
request := r.client.NewRequest("POST", fmt.Sprintf("/v1/auth/userpass/login/%s", username)) request := r.client.NewRequest("POST", fmt.Sprintf("/v1/auth/userpass/login/%s", cfg.Username))
if err := request.SetJSONBody(userPassLogin{Password: password}); err != nil { if err := request.SetJSONBody(userPassLogin{Password: cfg.Password}); err != nil {
return "", err return "", err
} }
// step: make the request // step: make the request

View file

@ -20,9 +20,25 @@ import (
"flag" "flag"
"fmt" "fmt"
"net/url" "net/url"
"os"
"time" "time"
) )
type vaultAuthOptions struct {
ClientToken string
Token string
LeaseDuration int
Renewable bool
Method string
VaultURL string `json:"vaultAddr"`
RoleID string `json:"role_id" yaml:"role_id"`
SecretID string `json:"secret_id" yaml:"secret_id"`
FileName string
FileFormat string
Username string
Password string
}
type config struct { type config struct {
// the url for th vault server // the url for th vault server
vaultURL string vaultURL string
@ -31,7 +47,7 @@ type config struct {
// whether or not the auth file format is default // whether or not the auth file format is default
vaultAuthFileFormat string vaultAuthFileFormat string
// the authentication options // the authentication options
vaultAuthOptions map[string]string vaultAuthOptions *vaultAuthOptions
// the vault ca file // the vault ca file
vaultCaFile string vaultCaFile string
// the place to write the resources // the place to write the resources
@ -59,7 +75,9 @@ var (
func init() { func init() {
// step: setup some defaults // step: setup some defaults
options.resources = new(VaultResources) options.resources = new(VaultResources)
options.vaultAuthOptions = map[string]string{VaultAuth: "token"} options.vaultAuthOptions = &vaultAuthOptions{
Method: "token",
}
flag.StringVar(&options.vaultURL, "vault", getEnv("VAULT_ADDR", "https://127.0.0.1:8200"), "url the vault service or VAULT_ADDR") flag.StringVar(&options.vaultURL, "vault", getEnv("VAULT_ADDR", "https://127.0.0.1:8200"), "url the vault service or VAULT_ADDR")
flag.StringVar(&options.vaultAuthFile, "auth", getEnv("AUTH_FILE", ""), "a configuration file in json or yaml containing authentication arguments") flag.StringVar(&options.vaultAuthFile, "auth", getEnv("AUTH_FILE", ""), "a configuration file in json or yaml containing authentication arguments")
@ -83,20 +101,33 @@ func parseOptions() error {
// validateOptions parses and validates the command line options // validateOptions parses and validates the command line options
func validateOptions(cfg *config) (err error) { func validateOptions(cfg *config) (err error) {
// step: validate the vault url
if _, err = url.Parse(cfg.vaultURL); err != nil {
return fmt.Errorf("invalid vault url: '%s' specified", cfg.vaultURL)
}
// step: read in the token if required // step: read in the token if required
if cfg.vaultAuthFile != "" { if cfg.vaultAuthFile != "" {
if exists, _ := fileExists(cfg.vaultAuthFile); !exists { if exists, _ := fileExists(cfg.vaultAuthFile); !exists {
return fmt.Errorf("the token file: %s does not exists, please check", cfg.vaultAuthFile) return fmt.Errorf("the token file: %s does not exists, please check", cfg.vaultAuthFile)
} }
options.vaultAuthOptions, err = readConfigFile(options.vaultAuthFile, options.vaultAuthFileFormat)
cfg.vaultAuthOptions, err = readConfigFile(cfg.vaultAuthFile, cfg.vaultAuthFileFormat)
if err != nil { if err != nil {
return fmt.Errorf("unable to read in authentication options from: %s, error: %s", cfg.vaultAuthFile, err) return fmt.Errorf("unable to read in authentication options from: %s, error: %s", cfg.vaultAuthFile, err)
} }
if cfg.vaultAuthOptions.VaultURL != "" {
cfg.vaultURL = cfg.vaultAuthOptions.VaultURL
}
}
if cfg.vaultURL == "" {
cfg.vaultURL = os.Getenv("VAULT_ADDR")
}
if cfg.vaultURL == "" {
return fmt.Errorf("VAULT_ADDR is unset")
}
// step: validate the vault url
if _, err = url.Parse(cfg.vaultURL); err != nil {
return fmt.Errorf("invalid vault url: '%s' specified", cfg.vaultURL)
} }
if cfg.vaultCaFile != "" { if cfg.vaultCaFile != "" {

77
config_test.go Normal file
View file

@ -0,0 +1,77 @@
package main
import (
"os"
"testing"
)
func TestValidateOptionsWithoutVaultURL(t *testing.T) {
os.Setenv("VAULT_ADDR", "")
cfg := &config{}
err := validateOptions(cfg)
if err == nil {
t.Errorf("should have raised error: %v", err)
}
}
func TestValidateOptionsWithEnvFallback(t *testing.T) {
os.Setenv("VAULT_ADDR", "http://testurl:8080")
cfg := &config{}
err := validateOptions(cfg)
if err != nil {
t.Errorf("raised an error: %v", err)
}
actual := cfg.vaultURL
expected := "http://testurl:8080"
if actual != expected {
t.Errorf("Expected Vault URL to be %s got %s", expected, actual)
}
}
func TestValidateOptionsWithInvalidVaultURL(t *testing.T) {
cfg := &config{
vaultURL: "%invalid_url",
}
err := validateOptions(cfg)
if err == nil {
t.Errorf("should have raised error")
}
}
func TestValidateOptionsWithInvalidVaultURLFromAuthFile(t *testing.T) {
cfg := &config{
vaultAuthFile: "tests/invalid_kubernetes_vault_auth_file.json",
}
err := validateOptions(cfg)
if err == nil {
t.Errorf("should have raised error")
}
}
func TestValidateOptionsWithVaultURLFromAuthFile(t *testing.T) {
cfg := &config{
vaultAuthFile: "tests/kubernetes_vault_auth_file.json",
}
err := validateOptions(cfg)
if err != nil {
t.Errorf("raising an error %v", err)
}
actual := cfg.vaultURL
expected := "http://testurl:8080"
if actual != expected {
t.Errorf("Expected Vault URL to be %s got %s", expected, actual)
}
}

View file

@ -0,0 +1 @@
{"method": "approle", "role_id": "admin", "secret_id": "foobar"}

View file

@ -0,0 +1,3 @@
method: approle
role_id: admin
secret_id: foobar

1
tests/auth_file.json Normal file
View file

@ -0,0 +1 @@
{"method": "userpass", "username": "admin", "password": "foobar"}

View file

@ -0,0 +1 @@
{"renewable": true, "leaseDuration": 60, "vaultAddr": "%invalid_url", "token": "foobar"}

View file

@ -0,0 +1 @@
{"renewable": true, "leaseDuration": 60, "vaultAddr": "http://testurl:8080", "token": "foobar"}

View file

@ -0,0 +1 @@
{"method": "token", "token": "foobar"}

View file

@ -0,0 +1,2 @@
method: token
token: foobar

View file

@ -0,0 +1 @@
{"method": "userpass", "username": "admin", "password": "foobar"}

View file

@ -0,0 +1,3 @@
method: userpass
username: admin
password: foobar

View file

@ -70,7 +70,7 @@ func getKeys(data map[string]interface{}) []string {
// readConfigFile read in a configuration file // readConfigFile read in a configuration file
// filename : the path to the file // filename : the path to the file
func readConfigFile(filename, fileFormat string) (map[string]string, error) { func readConfigFile(filename, fileFormat string) (*vaultAuthOptions, error) {
// step: check the file exists // step: check the file exists
if exists, err := fileExists(filename); !exists { if exists, err := fileExists(filename); !exists {
return nil, fmt.Errorf("the file: %s does not exist", filename) return nil, fmt.Errorf("the file: %s does not exist", filename)
@ -91,44 +91,44 @@ func readConfigFile(filename, fileFormat string) (map[string]string, error) {
// readJsonFile read in and unmarshall the data into a map // readJsonFile read in and unmarshall the data into a map
// filename : the path to the file container the json data // filename : the path to the file container the json data
func readJSONFile(filename, format string) (map[string]string, error) { func readJSONFile(filename, format string) (*vaultAuthOptions, error) {
data := make(map[string]string, 0) opts := &vaultAuthOptions{}
content, err := ioutil.ReadFile(filename) content, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return data, err return nil, err
} }
// unmarshall the data // unmarshall the data
err = json.Unmarshal(content, &data) err = json.Unmarshal(content, &opts)
if err != nil && format == "default" { if err != nil && format == "default" {
return data, err return nil, err
} }
if err != nil && format == "kubernetes-vault" { if err != nil && format == "kubernetes-vault" {
if data["clientToken"] != "" { if opts.ClientToken != "" {
data[VaultAuth] = "token" opts.Method = "token"
data["token"] = data["clientToken"] opts.Token = opts.ClientToken
return data, nil return opts, nil
} }
return data, err return nil, err
} }
return data, nil return opts, nil
} }
// readYAMLFile read in and unmarshall the data into a map // readYAMLFile read in and unmarshall the data into a map
// filename : the path to the file container the yaml data // filename : the path to the file container the yaml data
func readYAMLFile(filename string) (map[string]string, error) { func readYAMLFile(filename string) (*vaultAuthOptions, error) {
data := make(map[string]string, 0) o := &vaultAuthOptions{}
content, err := ioutil.ReadFile(filename) content, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return data, err return nil, err
} }
err = yaml.Unmarshal(content, data) err = yaml.Unmarshal(content, o)
if err != nil { if err != nil {
return data, err return nil, err
} }
return data, nil return o, nil
} }
// getDurationWithin generate a random integer between min and max // getDurationWithin generate a random integer between min and max

115
utils_test.go Normal file
View file

@ -0,0 +1,115 @@
package main
import (
"testing"
)
func TestReadConfigFileKubernetesVault(t *testing.T) {
o, err := readConfigFile("tests/kubernetes_vault_auth_file.json", "kubernetes-vault")
if err != nil {
t.Errorf("raising an error: %v", err)
}
tokenExpected := "foobar"
if o.Token != tokenExpected {
t.Errorf("Expected user %s got %s", tokenExpected, o.Token)
}
}
func TestReadConfigUserPassJSON(t *testing.T) {
o, err := readConfigFile("tests/userpass_auth_file.json", "default")
if err != nil {
t.Errorf("raising an error: %v", err)
}
userExpected := "admin"
passwordExpected := "foobar"
if o.Username != userExpected {
t.Errorf("Expected user %s got %s", userExpected, o.Username)
}
if o.Password != passwordExpected {
t.Errorf("Expected user %s got %s", passwordExpected, o.Password)
}
}
func TestReadConfigUserPassYAML(t *testing.T) {
o, err := readConfigFile("tests/userpass_auth_file.yml", "default")
if err != nil {
t.Errorf("raising an error: %v", err)
}
userExpected := "admin"
passwordExpected := "foobar"
if o.Username != userExpected {
t.Errorf("Expected user %s got %s", userExpected, o.Username)
}
if o.Password != passwordExpected {
t.Errorf("Expected user %s got %s", passwordExpected, o.Password)
}
}
func TestReadConfigAppRoleJSON(t *testing.T) {
o, err := readConfigFile("tests/approle_auth_file.json", "default")
if err != nil {
t.Errorf("raising an error: %v", err)
}
roleIDExpected := "admin"
secretIDExpected := "foobar"
if o.RoleID != roleIDExpected {
t.Errorf("Expected roleID %s got %s", roleIDExpected, o.RoleID)
}
if o.SecretID != secretIDExpected {
t.Errorf("Expected secretID %s got %s", secretIDExpected, o.SecretID)
}
}
func TestReadConfigAppRoleYAML(t *testing.T) {
o, err := readConfigFile("tests/approle_auth_file.yml", "default")
if err != nil {
t.Errorf("raising an error: %v", err)
}
roleIDExpected := "admin"
secretIDExpected := "foobar"
if o.RoleID != roleIDExpected {
t.Errorf("Expected roleID %s got %s", roleIDExpected, o.RoleID)
}
if o.SecretID != secretIDExpected {
t.Errorf("Expected secretID %s got %s", secretIDExpected, o.SecretID)
}
}
func TestReadConfigTokenJSON(t *testing.T) {
o, err := readConfigFile("tests/token_auth_file.json", "default")
if err != nil {
t.Errorf("raising an error: %v", err)
}
expected := "foobar"
if o.Token != expected {
t.Errorf("Expected token %s got %s", expected, o.Token)
}
}
func TestReadConfigTokenYAML(t *testing.T) {
o, err := readConfigFile("tests/token_auth_file.yml", "default")
if err != nil {
t.Errorf("raising an error: %v", err)
}
expected := "foobar"
if o.Token != expected {
t.Errorf("Expected token %s got %s", expected, o.Token)
}
}

View file

@ -31,15 +31,10 @@ import (
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
) )
const (
// VaultAuth the method to use when authenticating to vault
VaultAuth = "method"
)
// AuthInterface is the authentication interface // AuthInterface is the authentication interface
type AuthInterface interface { type AuthInterface interface {
// Create and handle renewals of the token // Create and handle renewals of the token
Create(map[string]string) (string, error) Create(*vaultAuthOptions) (string, error)
} }
// VaultService is the main interface into the vault API - placing into a structure // VaultService is the main interface into the vault API - placing into a structure
@ -425,15 +420,15 @@ func newVaultClient(opts *config) (*api.Client, error) {
return nil, err return nil, err
} }
plugin, _ := opts.vaultAuthOptions[VaultAuth] plugin := opts.vaultAuthOptions.Method
switch plugin { switch plugin {
case "userpass": case "userpass":
token, err = NewUserPassPlugin(client).Create(opts.vaultAuthOptions) token, err = NewUserPassPlugin(client).Create(opts.vaultAuthOptions)
case "approle": case "approle":
token, err = NewAppRolePlugin(client).Create(opts.vaultAuthOptions) token, err = NewAppRolePlugin(client).Create(opts.vaultAuthOptions)
case "token": case "token":
opts.vaultAuthOptions["filename"] = options.vaultAuthFile opts.vaultAuthOptions.FileName = options.vaultAuthFile
opts.vaultAuthOptions["fileFormat"] = options.vaultAuthFileFormat opts.vaultAuthOptions.FileFormat = options.vaultAuthFileFormat
token, err = NewUserTokenPlugin(client).Create(opts.vaultAuthOptions) token, err = NewUserTokenPlugin(client).Create(opts.vaultAuthOptions)
default: default:
return nil, fmt.Errorf("unsupported authentication plugin: %s", plugin) return nil, fmt.Errorf("unsupported authentication plugin: %s", plugin)