initial commit
This commit is contained in:
		
							
								
								
									
										55
									
								
								cmd/getvaultpw/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								cmd/getvaultpw/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"getvaultpw/internal/config" | ||||||
|  | 	"getvaultpw/internal/protectString" | ||||||
|  | 	"getvaultpw/internal/vault" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	// initialize application configuration | ||||||
|  | 	cfg := config.Init() | ||||||
|  |  | ||||||
|  | 	defer func(cfg *config.Config) { | ||||||
|  | 		if err := cfg.WriteConfig(cfg.ConfigFile); err != nil { | ||||||
|  | 			log.Fatalf("[WARNING] Unable to update configuration file: %v", err) | ||||||
|  | 		} | ||||||
|  | 		log.Println("[DEBUG] shutdown sequence complete") | ||||||
|  | 	}(cfg) | ||||||
|  |  | ||||||
|  | 	// if we are passed a vault instance and a password we need to update the password for a environment | ||||||
|  | 	if len(cfg.VaultInstance) != 0 && len(cfg.VaultPass) != 0 { | ||||||
|  | 		cfg.ConfigFileData.VaultEnvironment[cfg.VaultInstance].EPass = protectString.Encrypt(cfg.VaultPass, cfg.ConfigFileData.Salt) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// get the password for a secret | ||||||
|  | 	if len(cfg.SecretID) != 0 { | ||||||
|  | 		for k, v := range cfg.ConfigFileData.Credential { | ||||||
|  | 			if k == cfg.SecretID { | ||||||
|  | 				if (v.TimeStamp+(60*60*2)) < time.Now().UTC().Unix() || len(cfg.ConfigFileData.Credential[k].Cache) == 0 { | ||||||
|  | 					o, err := vault.GetCredential(cfg.ConfigFileData.VaultEnvironment[v.VaultEnv].Host, | ||||||
|  | 						cfg.ConfigFileData.VaultEnvironment[v.VaultEnv].User, | ||||||
|  | 						protectString.Decrypt(cfg.ConfigFileData.VaultEnvironment[v.VaultEnv].EPass, cfg.ConfigFileData.Salt), | ||||||
|  | 						v.ID, | ||||||
|  | 						v.Path) | ||||||
|  |  | ||||||
|  | 					if err != nil { | ||||||
|  | 						log.Fatalf("[ERROR] %v", err) | ||||||
|  | 					} | ||||||
|  | 					cfg.ConfigFileData.Credential[k].Cache = protectString.Encrypt(o, cfg.ConfigFileData.Salt) | ||||||
|  | 					cfg.ConfigFileData.Credential[k].TimeStamp = time.Now().UTC().Unix() | ||||||
|  | 					fmt.Println(o) | ||||||
|  | 				} else { | ||||||
|  | 					o := protectString.Decrypt(cfg.ConfigFileData.Credential[k].Cache, cfg.ConfigFileData.Salt) | ||||||
|  | 					fmt.Println(o) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				log.Fatalf("[ERROR] Unable to find secret (%s) in config file: %s", cfg.SecretID, cfg.ConfigFile) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | module getvaultpw | ||||||
|  |  | ||||||
|  | go 1.19 | ||||||
|  |  | ||||||
|  | require ( | ||||||
|  | 	github.com/hashicorp/logutils v1.0.0 | ||||||
|  | 	gopkg.in/yaml.v2 v2.4.0 | ||||||
|  | ) | ||||||
							
								
								
									
										5
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= | ||||||
|  | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||||
|  | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||||
							
								
								
									
										109
									
								
								internal/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								internal/config/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | package config | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/logutils" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Config struct { | ||||||
|  | 	// time configuration | ||||||
|  | 	TimeFormat    string         `env:"TIME_FORMAT" default:"2006-01-02 15:04:05"` | ||||||
|  | 	TimeZoneLocal string         `env:"TIME_ZONE" default:"America/Chicago"` | ||||||
|  | 	TZoneLocal    *time.Location `ignored:"true"` | ||||||
|  | 	TZoneUTC      *time.Location `ignored:"true"` | ||||||
|  |  | ||||||
|  | 	// logging | ||||||
|  | 	LogLevel int                   `env:"LOG_LEVEL" default:"0"` | ||||||
|  | 	Log      *logutils.LevelFilter `ignored:"true"` | ||||||
|  |  | ||||||
|  | 	// configuration file | ||||||
|  | 	ConfigFile        string           `ignored:"true" env:"CONFIG_FILE" required:"false"` | ||||||
|  | 	ConfigFileExample string           `ignored:"true"` | ||||||
|  | 	ConfigFileData    ConfigFileStruct `ignored:"true"` | ||||||
|  |  | ||||||
|  | 	// misc | ||||||
|  | 	VaultUser     string `env:"VAULT_USER" required:"false" default:""` | ||||||
|  | 	VaultPass     string `env:"VAULT_PASS" required:"false" default:""` | ||||||
|  | 	SecretID      string `env:"VAULT_SECRET_ID" required:"false" default:""` | ||||||
|  | 	VaultInstance string `env:"VAULT_INSTANCE" required:"false" default:"dev"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DefaultConfig initializes the config variable for use with a prepared set of defaults. | ||||||
|  | func DefaultConfig() *Config { | ||||||
|  | 	home, err := os.UserHomeDir() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("[FATAL] Unable to determine user home directory for config file: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &Config{ | ||||||
|  | 		Log: &logutils.LevelFilter{ | ||||||
|  | 			Levels: []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARNING", "ERROR"}, | ||||||
|  | 			Writer: os.Stderr, | ||||||
|  | 		}, | ||||||
|  | 		ConfigFile: home + "/.getvaultpw.yml", | ||||||
|  | 		ConfigFileExample: `--- | ||||||
|  | salt: exampleSalt | ||||||
|  |  | ||||||
|  | vaultEnvironment: | ||||||
|  |   dev: | ||||||
|  |     host: vault.dev.example.com | ||||||
|  |     user: userName | ||||||
|  |   test: | ||||||
|  |     host: vault.test.example.com | ||||||
|  |     user: userName | ||||||
|  |   stage: | ||||||
|  |     host: vault.stage.example.com | ||||||
|  |     user: userName | ||||||
|  |   prod: | ||||||
|  |     host: vault.prod.example.com | ||||||
|  |     user: userName | ||||||
|  |  | ||||||
|  | credentials: | ||||||
|  |   serviceAccountNumberOne: | ||||||
|  |     vaultPath: /secrets/serviceAccountNumberOne/credentials | ||||||
|  |     vaultID: password | ||||||
|  | 		vaultInstance: dev | ||||||
|  |   serviceAccountNumberTwo: | ||||||
|  |     vaultPath: /secrets/serviceAccountNumberTwo/credentials | ||||||
|  |     vaultID: password | ||||||
|  | `, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (cfg *Config) setLogLevel() { | ||||||
|  | 	switch { | ||||||
|  | 	case cfg.LogLevel <= 20: | ||||||
|  | 		cfg.Log.SetMinLevel(logutils.LogLevel("ERROR")) | ||||||
|  | 	case cfg.LogLevel > 20 && cfg.LogLevel <= 40: | ||||||
|  | 		cfg.Log.SetMinLevel(logutils.LogLevel("WARNING")) | ||||||
|  | 	case cfg.LogLevel > 40 && cfg.LogLevel <= 60: | ||||||
|  | 		cfg.Log.SetMinLevel(logutils.LogLevel("INFO")) | ||||||
|  | 	case cfg.LogLevel > 60 && cfg.LogLevel <= 80: | ||||||
|  | 		cfg.Log.SetMinLevel(logutils.LogLevel("DEBUG")) | ||||||
|  | 	case cfg.LogLevel > 80: | ||||||
|  | 		cfg.Log.SetMinLevel(logutils.LogLevel("TRACE")) | ||||||
|  | 	} | ||||||
|  | 	log.SetOutput(cfg.Log) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (cfg *Config) printRunningConfig(cfgInfo []StructInfo) { | ||||||
|  | 	log.Printf("[DEBUG] Current Running Configuration Values:") | ||||||
|  | 	for _, info := range cfgInfo { | ||||||
|  | 		switch info.Type.String() { | ||||||
|  | 		case "string": | ||||||
|  | 			p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*string) | ||||||
|  | 			log.Printf("[DEBUG]\t%s\t\t= %s\n", info.Alt, *p) | ||||||
|  | 		case "bool": | ||||||
|  | 			p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*bool) | ||||||
|  | 			log.Printf("[DEBUG]\t%s\t\t= %s\n", info.Alt, strconv.FormatBool(*p)) | ||||||
|  | 		case "int": | ||||||
|  | 			p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int) | ||||||
|  | 			log.Printf("[DEBUG]\t%s\t\t= %s\n", info.Alt, strconv.FormatInt(int64(*p), 10)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								internal/config/configFile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								internal/config/configFile.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | package config | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	"gopkg.in/yaml.v2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ConfigFileStruct struct { | ||||||
|  | 	Salt             string                       `yaml:"salt"` | ||||||
|  | 	VaultEnvironment map[string]*VaultEnvStruct   `yaml:"vaultEnvironment"` | ||||||
|  | 	Credential       map[string]*CredentialStruct `yaml:"credentials"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type VaultEnvStruct struct { | ||||||
|  | 	Host  string `yaml:"host"` | ||||||
|  | 	User  string `yaml:"user"` | ||||||
|  | 	EPass string `yaml:"encryptedPassword"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type CredentialStruct struct { | ||||||
|  | 	Path      string `yaml:"vaultPath"` | ||||||
|  | 	ID        string `yaml:"vaultID"` | ||||||
|  | 	TimeStamp int64  `yaml:"timestamp"` | ||||||
|  | 	Cache     string `yaml:"cachedValue"` | ||||||
|  | 	VaultEnv  string `yaml:"vaultInstance"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (cfg *Config) WriteConfig(dest string) error { | ||||||
|  | 	file, err := os.OpenFile(dest, os.O_WRONLY, 0600) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("[ERROR] Unable to open the config file %s: %v", dest, err) | ||||||
|  | 	} | ||||||
|  | 	defer file.Close() | ||||||
|  |  | ||||||
|  | 	data, _ := yaml.Marshal(cfg.ConfigFileData) | ||||||
|  | 	if _, err = file.Write(data); err != nil { | ||||||
|  | 		log.Fatalf("[ERROR] Unable to update config file %s: %v", cfg.ConfigFile, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										111
									
								
								internal/config/envconfig.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								internal/config/envconfig.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | package config | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type StructInfo struct { | ||||||
|  | 	Name         string | ||||||
|  | 	Alt          string | ||||||
|  | 	Key          string | ||||||
|  | 	Field        reflect.Value | ||||||
|  | 	Tags         reflect.StructTag | ||||||
|  | 	Type         reflect.Type | ||||||
|  | 	DefaultValue interface{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getStructInfo(spec interface{}) ([]StructInfo, error) { | ||||||
|  | 	s := reflect.ValueOf(spec) | ||||||
|  |  | ||||||
|  | 	if s.Kind() != reflect.Pointer { | ||||||
|  | 		return []StructInfo{}, fmt.Errorf("getStructInfo() was sent a %s instead of a pointer to a struct.\n", s.Kind()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s = s.Elem() | ||||||
|  | 	if s.Kind() != reflect.Struct { | ||||||
|  | 		return []StructInfo{}, fmt.Errorf("getStructInfo() was sent a %s instead of a struct.\n", s.Kind()) | ||||||
|  | 	} | ||||||
|  | 	typeOfSpec := s.Type() | ||||||
|  |  | ||||||
|  | 	infos := make([]StructInfo, 0, s.NumField()) | ||||||
|  | 	for i := 0; i < s.NumField(); i++ { | ||||||
|  | 		f := s.Field(i) | ||||||
|  | 		ftype := typeOfSpec.Field(i) | ||||||
|  |  | ||||||
|  | 		ignored, _ := strconv.ParseBool(ftype.Tag.Get("ignored")) | ||||||
|  | 		if !f.CanSet() || ignored { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for f.Kind() == reflect.Pointer { | ||||||
|  | 			if f.IsNil() { | ||||||
|  | 				if f.Type().Elem().Kind() != reflect.Struct { | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 				f.Set(reflect.New(f.Type().Elem())) | ||||||
|  | 			} | ||||||
|  | 			f = f.Elem() | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		info := StructInfo{ | ||||||
|  | 			Name:  ftype.Name, | ||||||
|  | 			Alt:   strings.ToUpper(ftype.Tag.Get("env")), | ||||||
|  | 			Key:   ftype.Name, | ||||||
|  | 			Field: f, | ||||||
|  | 			Tags:  ftype.Tag, | ||||||
|  | 			Type:  ftype.Type, | ||||||
|  | 		} | ||||||
|  | 		if info.Alt != "" { | ||||||
|  | 			info.Key = info.Alt | ||||||
|  | 		} | ||||||
|  | 		info.Key = strings.ToUpper(info.Key) | ||||||
|  | 		if ftype.Tag.Get("default") != "" { | ||||||
|  | 			v, err := typeConversion(ftype.Type.String(), ftype.Tag.Get("default")) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return []StructInfo{}, err | ||||||
|  | 			} | ||||||
|  | 			info.DefaultValue = v | ||||||
|  | 		} | ||||||
|  | 		infos = append(infos, info) | ||||||
|  | 	} | ||||||
|  | 	return infos, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func typeConversion(t, v string) (interface{}, error) { | ||||||
|  | 	switch t { | ||||||
|  | 	case "string": | ||||||
|  | 		return v, nil | ||||||
|  | 	case "int": | ||||||
|  | 		return strconv.ParseInt(v, 10, 0) | ||||||
|  | 	case "int8": | ||||||
|  | 		return strconv.ParseInt(v, 10, 8) | ||||||
|  | 	case "int16": | ||||||
|  | 		return strconv.ParseInt(v, 10, 16) | ||||||
|  | 	case "int32": | ||||||
|  | 		return strconv.ParseInt(v, 10, 32) | ||||||
|  | 	case "int64": | ||||||
|  | 		return strconv.ParseInt(v, 10, 64) | ||||||
|  | 	case "uint": | ||||||
|  | 		return strconv.ParseUint(v, 10, 0) | ||||||
|  | 	case "uint16": | ||||||
|  | 		return strconv.ParseUint(v, 10, 16) | ||||||
|  | 	case "uint32": | ||||||
|  | 		return strconv.ParseUint(v, 10, 32) | ||||||
|  | 	case "uint64": | ||||||
|  | 		return strconv.ParseUint(v, 10, 64) | ||||||
|  | 	case "float32": | ||||||
|  | 		return strconv.ParseFloat(v, 32) | ||||||
|  | 	case "float64": | ||||||
|  | 		return strconv.ParseFloat(v, 64) | ||||||
|  | 	case "complex64": | ||||||
|  | 		return strconv.ParseComplex(v, 64) | ||||||
|  | 	case "complex128": | ||||||
|  | 		return strconv.ParseComplex(v, 128) | ||||||
|  | 	case "bool": | ||||||
|  | 		return strconv.ParseBool(v) | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("Unable to identify type.") | ||||||
|  | } | ||||||
							
								
								
									
										161
									
								
								internal/config/initialize.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								internal/config/initialize.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | |||||||
|  | package config | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"io/ioutil" | ||||||
|  |  | ||||||
|  | 	"gopkg.in/yaml.v2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // getEnvString returns string from environment variable | ||||||
|  | func getEnvString(env, def string) (val string) { //nolint:deadcode | ||||||
|  | 	val = os.Getenv(env) | ||||||
|  |  | ||||||
|  | 	if val == "" { | ||||||
|  | 		return def | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getEnvInt returns int from environment variable | ||||||
|  | func getEnvInt(env string, def int) (ret int) { | ||||||
|  | 	val := os.Getenv(env) | ||||||
|  |  | ||||||
|  | 	if val == "" { | ||||||
|  | 		return def | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret, err := strconv.Atoi(val) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("[ERROR] Environment variable is not numeric: %v\n", env) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getEnvBool returns boolean from environment variable | ||||||
|  | func getEnvBool(env string, def bool) bool { | ||||||
|  | 	var ( | ||||||
|  | 		err    error | ||||||
|  | 		retVal bool | ||||||
|  | 		val    = os.Getenv(env) | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	if len(val) == 0 { | ||||||
|  | 		return def | ||||||
|  | 	} else { | ||||||
|  | 		retVal, err = strconv.ParseBool(val) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("[ERROR] Environment variable is not boolean: %v\n", env) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return retVal | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Init initializes the application configuration by reading default values from the struct's tags | ||||||
|  | // and environment variables. Tags processed by this process are as follows: | ||||||
|  | // `ignored:"true" env:"ENVIRONMENT_VARIABLE" default:"default value"` | ||||||
|  | func Init() *Config { | ||||||
|  | 	var cryptovault string | ||||||
|  | 	cfg := DefaultConfig() | ||||||
|  |  | ||||||
|  | 	cfgInfo, err := getStructInfo(cfg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("[FATAL] %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, info := range cfgInfo { | ||||||
|  | 		switch info.Type.String() { | ||||||
|  | 		case "string": | ||||||
|  | 			var dv string | ||||||
|  |  | ||||||
|  | 			if info.DefaultValue != nil { | ||||||
|  | 				dv = info.DefaultValue.(string) | ||||||
|  | 			} | ||||||
|  | 			p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*string) | ||||||
|  | 			flag.StringVar(p, info.Name, getEnvString(info.Name, dv), "("+info.Key+")") | ||||||
|  | 		case "bool": | ||||||
|  | 			var dv bool | ||||||
|  |  | ||||||
|  | 			if info.DefaultValue != nil { | ||||||
|  | 				dv = info.DefaultValue.(bool) | ||||||
|  | 			} | ||||||
|  | 			p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*bool) | ||||||
|  | 			flag.BoolVar(p, info.Name, getEnvBool(info.Name, dv), "("+info.Key+")") | ||||||
|  | 		case "int": | ||||||
|  | 			var dv int | ||||||
|  |  | ||||||
|  | 			if info.DefaultValue != nil { | ||||||
|  | 				dv = int(info.DefaultValue.(int64)) | ||||||
|  | 			} | ||||||
|  | 			p := reflect.ValueOf(cfg).Elem().FieldByName(info.Name).Addr().Interface().(*int) | ||||||
|  | 			flag.IntVar(p, info.Name, getEnvInt(info.Name, dv), "("+info.Key+")") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	flag.StringVar(&cryptovault, "label", "", "Deprecated support feature. Please use -SecretID.\nCryptovault compatibility feature used to identify the ID of the secret") | ||||||
|  | 	flag.Parse() | ||||||
|  |  | ||||||
|  | 	if len(cryptovault) != 0 { | ||||||
|  | 		cfg.SecretID = cryptovault | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// set logging level | ||||||
|  | 	cfg.setLogLevel() | ||||||
|  |  | ||||||
|  | 	// check if configuration file is present, if not create the example config file | ||||||
|  | 	if _, err := os.Stat(cfg.ConfigFile); os.IsNotExist(err) { | ||||||
|  | 		log.Printf("[WARNING] No configuration file present. Creating example configuration file at %s", cfg.ConfigFile) | ||||||
|  | 		file, err := os.Create(cfg.ConfigFile) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("[ERROR] Unable to create the config file %s: %v", cfg.ConfigFile, err) | ||||||
|  | 		} | ||||||
|  | 		defer file.Close() | ||||||
|  |  | ||||||
|  | 		file.Write([]byte(cfg.ConfigFileExample)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// readConfiguration File | ||||||
|  | 	fileData, err := ioutil.ReadFile(cfg.ConfigFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("[ERROR] Unable to read the config file %s: %v", cfg.ConfigFile, err) | ||||||
|  | 	} | ||||||
|  | 	err = yaml.Unmarshal(fileData, &cfg.ConfigFileData) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("[ERROR] Unable to process the config file %s: %v", cfg.ConfigFile, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// make sure there is a secure salt | ||||||
|  | 	if cfg.ConfigFileData.Salt == "" || cfg.ConfigFileData.Salt == "exampleSalt" { | ||||||
|  | 		s := make([]byte, 32) | ||||||
|  | 		rand.Read(s) | ||||||
|  | 		cfg.ConfigFileData.Salt = hex.EncodeToString(s) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// timezone & format configuration | ||||||
|  | 	cfg.TZoneUTC, _ = time.LoadLocation("UTC") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("[ERROR] Unable to parse timezone string. Please use one of the timezone database values listed here: %s", "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones") | ||||||
|  | 	} | ||||||
|  | 	cfg.TZoneLocal, err = time.LoadLocation(cfg.TimeZoneLocal) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("[ERROR] Unable to parse timezone string. Please use one of the timezone database values listed here: %s", "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones") | ||||||
|  | 	} | ||||||
|  | 	time.Now().Format(cfg.TimeFormat) | ||||||
|  |  | ||||||
|  | 	// print running config | ||||||
|  | 	cfg.printRunningConfig(cfgInfo) | ||||||
|  |  | ||||||
|  | 	log.Println("[INFO] initialization complete") | ||||||
|  | 	return cfg | ||||||
|  | } | ||||||
							
								
								
									
										137
									
								
								internal/httpclient/httpclient.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								internal/httpclient/httpclient.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | package httpclient | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"errors" | ||||||
|  | 	"net" | ||||||
|  | 	"strconv" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // HTTPClient is an interface for initializing the http client library. | ||||||
|  | type HTTPClient struct { | ||||||
|  | 	Client  *http.Client | ||||||
|  | 	Data    *bytes.Buffer | ||||||
|  | 	Headers map[string]string | ||||||
|  |  | ||||||
|  | 	Username string | ||||||
|  | 	Password string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DefaultClient is a function for defining a basic HTTP client with standard timeouts. | ||||||
|  | func DefaultClient() *HTTPClient { | ||||||
|  | 	return &HTTPClient{ | ||||||
|  | 		Client: &http.Client{ | ||||||
|  | 			Timeout: 60 * time.Second, | ||||||
|  | 			Transport: &http.Transport{ | ||||||
|  | 				Dial: (&net.Dialer{ | ||||||
|  | 					Timeout: 5 * time.Second, | ||||||
|  | 				}).Dial, | ||||||
|  | 				TLSHandshakeTimeout: 5 * time.Second, | ||||||
|  | 				IdleConnTimeout:     300 * time.Second, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewClient Create an HTTPClient with a user-provided net/http.Client | ||||||
|  | func NewClient(httpClient *http.Client) *HTTPClient { | ||||||
|  | 	return &HTTPClient{Client: httpClient} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetBasicAuth is a chaining function to set the username and password for basic | ||||||
|  | // authentication | ||||||
|  | func (c *HTTPClient) SetBasicAuth(username, password string) *HTTPClient { | ||||||
|  | 	c.Username = username | ||||||
|  | 	c.Password = password | ||||||
|  |  | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetPostData is a chaining function to set POST/PUT/PATCH data | ||||||
|  | func (c *HTTPClient) SetPostData(data string) *HTTPClient { | ||||||
|  | 	c.Data = bytes.NewBufferString(data) | ||||||
|  |  | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetHeader is a chaining function to set arbitrary HTTP Headers | ||||||
|  | func (c *HTTPClient) SetHeader(label string, value string) *HTTPClient { | ||||||
|  | 	if c.Headers == nil { | ||||||
|  | 		c.Headers = map[string]string{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.Headers[label] = value | ||||||
|  |  | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get calls the net.http GET operation | ||||||
|  | func (c *HTTPClient) Get(url string) ([]byte, error) { | ||||||
|  | 	return c.do(url, http.MethodGet) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Patch calls the net.http PATCH operation | ||||||
|  | func (c *HTTPClient) Patch(url string) ([]byte, error) { | ||||||
|  | 	return c.do(url, http.MethodPatch) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Post calls the net.http POST operation | ||||||
|  | func (c *HTTPClient) Post(url string) ([]byte, error) { | ||||||
|  | 	return c.do(url, http.MethodPost) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Put calls the net.http PUT operation | ||||||
|  | func (c *HTTPClient) Put(url string) ([]byte, error) { | ||||||
|  | 	return c.do(url, http.MethodPut) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *HTTPClient) do(url string, method string) ([]byte, error) { | ||||||
|  | 	var ( | ||||||
|  | 		req    *http.Request | ||||||
|  | 		res    *http.Response | ||||||
|  | 		output []byte | ||||||
|  | 		err    error | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	// NewRequest knows that c.data is typed *bytes.Buffer and will SEGFAULT | ||||||
|  | 	// if c.data is nil. So we create a request using nil when c.data is nil | ||||||
|  | 	if c.Data != nil { | ||||||
|  | 		req, err = http.NewRequest(method, url, c.Data) | ||||||
|  | 	} else { | ||||||
|  | 		req, err = http.NewRequest(method, url, nil) | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (len(c.Username) > 0) && (len(c.Password) > 0) { | ||||||
|  | 		req.SetBasicAuth(c.Username, c.Password) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if c.Headers != nil { | ||||||
|  | 		for label, value := range c.Headers { | ||||||
|  | 			req.Header.Set(label, value) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if res, err = c.Client.Do(req); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	defer res.Body.Close() | ||||||
|  |  | ||||||
|  | 	if output, err = ioutil.ReadAll(res.Body); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// check status | ||||||
|  | 	if res.StatusCode < 200 || res.StatusCode >= 300 { | ||||||
|  | 		return nil, errors.New("non-successful status code received [" + strconv.Itoa(res.StatusCode) + "]") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return output, nil | ||||||
|  | } | ||||||
							
								
								
									
										281
									
								
								internal/httpclient/httpclient_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								internal/httpclient/httpclient_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,281 @@ | |||||||
|  | package httpclient | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/httptest" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Data struct { | ||||||
|  | 	Greeting string            `json:"greeting"` | ||||||
|  | 	Headers  map[string]string `json:"headers"` | ||||||
|  | 	Method   string            `json:"method"` | ||||||
|  | 	Username string            `json:"username"` | ||||||
|  | 	Password string            `json:"password"` | ||||||
|  | 	PostData string            `json:"postdata"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	greeting    = "Hello world" | ||||||
|  | 	postData    = "Test data" | ||||||
|  | 	authUser    = "testuser" | ||||||
|  | 	authPass    = "testpass" | ||||||
|  | 	headerLabel = "Test-Header" | ||||||
|  | 	headerValue = "Test-Value" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func httpTestHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	var ( | ||||||
|  | 		b    []byte | ||||||
|  | 		user string | ||||||
|  | 		pass string | ||||||
|  | 		body []byte | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	data := Data{ | ||||||
|  | 		Greeting: greeting, | ||||||
|  | 		Headers:  map[string]string{}, | ||||||
|  | 		Method:   r.Method, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	user, pass, ok := r.BasicAuth() | ||||||
|  | 	if ok { | ||||||
|  | 		data.Username = user | ||||||
|  | 		data.Password = pass | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	body, err := ioutil.ReadAll(r.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Fprint(w, "ioutil.ReadAll failed") | ||||||
|  | 	} | ||||||
|  | 	data.PostData = string(body) | ||||||
|  |  | ||||||
|  | 	for h := range r.Header { | ||||||
|  | 		data.Headers[h] = r.Header.Get(h) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b, err = json.MarshalIndent(data, "", "  ") | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Fprint(w, "Json marshal failed somehow") | ||||||
|  | 	} | ||||||
|  | 	fmt.Fprint(w, string(b)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func checkMethod(t *testing.T, data Data, method string) { | ||||||
|  | 	if data.Method != method { | ||||||
|  | 		t.Errorf("data.Method(%s) != method(%s)", data.Method, method) | ||||||
|  | 	} | ||||||
|  | 	t.Log("checkMethod() success") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func checkGreeting(t *testing.T, data Data) { | ||||||
|  | 	if data.Greeting != greeting { | ||||||
|  | 		t.Errorf("data.Greeting(%s) != greeting(%s)", data.Greeting, greeting) | ||||||
|  | 	} | ||||||
|  | 	t.Log("checkGreeting() success") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func checkBasicAuth(t *testing.T, data Data) { | ||||||
|  | 	if data.Username != authUser { | ||||||
|  | 		t.Errorf("data.Username(%s) != authUser(%s)", data.Username, authUser) | ||||||
|  | 	} | ||||||
|  | 	if data.Password != authPass { | ||||||
|  | 		t.Errorf("data.Password(%s) != authPass(%s)", data.Password, authPass) | ||||||
|  | 	} | ||||||
|  | 	t.Log("checkBasicAuth() success") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func checkPostData(t *testing.T, data Data) { | ||||||
|  | 	if data.PostData != postData { | ||||||
|  | 		t.Errorf("data.PostData(%s) != postData(%s)", data.PostData, postData) | ||||||
|  | 	} | ||||||
|  | 	t.Log("checkPostData() success") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGet(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().Get(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodGet) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGetAuth(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().SetBasicAuth(authUser, authPass).Get(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodGet) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | 	checkBasicAuth(t, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPut(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().SetPostData(postData).Put(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodPut) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | 	checkPostData(t, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPutAuth(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().SetBasicAuth(authUser, authPass).SetPostData(postData).Put(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodPut) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | 	checkBasicAuth(t, data) | ||||||
|  | 	checkPostData(t, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPost(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().SetPostData(postData).Post(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodPost) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | 	checkPostData(t, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPostAuth(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().SetBasicAuth(authUser, authPass).SetPostData(postData).Post(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodPost) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | 	checkBasicAuth(t, data) | ||||||
|  | 	checkPostData(t, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPatch(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().SetPostData(postData).Patch(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodPatch) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | 	checkPostData(t, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestPatchAuth(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().SetBasicAuth(authUser, authPass).SetPostData(postData).Patch(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodPatch) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | 	checkBasicAuth(t, data) | ||||||
|  | 	checkPostData(t, data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestSetHeader(t *testing.T) { | ||||||
|  | 	var data Data | ||||||
|  |  | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(httpTestHandler)) | ||||||
|  | 	defer ts.Close() | ||||||
|  |  | ||||||
|  | 	output, err := DefaultClient().SetHeader(headerLabel, headerValue).Get(ts.URL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = json.Unmarshal(output, &data); err != nil { | ||||||
|  | 		t.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkMethod(t, data, http.MethodGet) | ||||||
|  | 	checkGreeting(t, data) | ||||||
|  | 	if data.Headers[headerLabel] != headerValue { | ||||||
|  | 		t.Errorf("SetHeader values not set in header: %+v", data.Headers) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								internal/protectString/decrypt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								internal/protectString/decrypt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | package protectString | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/aes" | ||||||
|  | 	"crypto/cipher" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func Decrypt(encryptedString string, keyString string) (decryptedString string) { | ||||||
|  | 	key, _ := hex.DecodeString(keyString) | ||||||
|  | 	enc, _ := hex.DecodeString(encryptedString) | ||||||
|  |  | ||||||
|  | 	//Create a new Cipher Block from the key | ||||||
|  | 	block, err := aes.NewCipher(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Create a new GCM | ||||||
|  | 	aesGCM, err := cipher.NewGCM(block) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Get the nonce size | ||||||
|  | 	nonceSize := aesGCM.NonceSize() | ||||||
|  |  | ||||||
|  | 	//Extract the nonce from the encrypted data | ||||||
|  | 	nonce, ciphertext := enc[:nonceSize], enc[nonceSize:] | ||||||
|  |  | ||||||
|  | 	//Decrypt the data | ||||||
|  | 	plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return fmt.Sprintf("%s", plaintext) | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								internal/protectString/encrypt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								internal/protectString/encrypt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | package protectString | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/aes" | ||||||
|  | 	"crypto/cipher" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func Encrypt(stringToEncrypt string, keyString string) (encryptedString string) { | ||||||
|  | 	key, _ := hex.DecodeString(keyString) | ||||||
|  | 	plaintext := []byte(stringToEncrypt) | ||||||
|  |  | ||||||
|  | 	//Create a new Cipher Block from the key | ||||||
|  | 	block, err := aes.NewCipher(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Create a new GCM | ||||||
|  | 	aesGCM, err := cipher.NewGCM(block) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Create a nonce. Nonce should be from GCM | ||||||
|  | 	nonce := make([]byte, aesGCM.NonceSize()) | ||||||
|  | 	if _, err = io.ReadFull(rand.Reader, nonce); err != nil { | ||||||
|  | 		panic(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//Encrypt the data using aesGCM.Seal | ||||||
|  | 	ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil) | ||||||
|  | 	return fmt.Sprintf("%x", ciphertext) | ||||||
|  | } | ||||||
							
								
								
									
										116
									
								
								internal/vault/vault.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								internal/vault/vault.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | package vault | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"regexp" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"encoding/json" | ||||||
|  |  | ||||||
|  | 	"getvaultpw/internal/httpclient" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type authRespStruct struct { | ||||||
|  | 	Auth struct { | ||||||
|  | 		Accessor      string `json:"accessor"` | ||||||
|  | 		ClientToken   string `json:"client_token"` | ||||||
|  | 		EntityID      string `json:"entity_id"` | ||||||
|  | 		LeaseDuration int64  `json:"lease_duration"` | ||||||
|  | 		Metadata      struct { | ||||||
|  | 			Username string `json:"username"` | ||||||
|  | 		} `json:"metadata"` | ||||||
|  | 		MfaRequirement interface{} `json:"mfa_requirement"` | ||||||
|  | 		NumUses        int64       `json:"num_uses"` | ||||||
|  | 		Orphan         bool        `json:"orphan"` | ||||||
|  | 		Policies       []string    `json:"policies"` | ||||||
|  | 		Renewable      bool        `json:"renewable"` | ||||||
|  | 		TokenPolicies  []string    `json:"token_policies"` | ||||||
|  | 		TokenType      string      `json:"token_type"` | ||||||
|  | 	} `json:"auth"` | ||||||
|  | 	Data          struct{}    `json:"data"` | ||||||
|  | 	LeaseDuration int64       `json:"lease_duration"` | ||||||
|  | 	LeaseID       string      `json:"lease_id"` | ||||||
|  | 	Renewable     bool        `json:"renewable"` | ||||||
|  | 	RequestID     string      `json:"request_id"` | ||||||
|  | 	Warnings      interface{} `json:"warnings"` | ||||||
|  | 	WrapInfo      interface{} `json:"wrap_info"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type secretV2Struct struct { | ||||||
|  | 	RequestID     string `json:"request_id"` | ||||||
|  | 	LeaseID       string `json:"lease_id"` | ||||||
|  | 	Renewable     bool   `json:"renewable"` | ||||||
|  | 	LeaseDuration int    `json:"lease_duration"` | ||||||
|  | 	Data          struct { | ||||||
|  | 		Data     map[string]string `json:"data"` | ||||||
|  | 		Metadata struct { | ||||||
|  | 			CreatedTime    time.Time   `json:"created_time"` | ||||||
|  | 			CustomMetadata interface{} `json:"custom_metadata"` | ||||||
|  | 			DeletionTime   string      `json:"deletion_time"` | ||||||
|  | 			Destroyed      bool        `json:"destroyed"` | ||||||
|  | 			Version        int         `json:"version"` | ||||||
|  | 		} `json:"metadata"` | ||||||
|  | 	} `json:"data"` | ||||||
|  | 	WrapInfo interface{} `json:"wrap_info"` | ||||||
|  | 	Warnings interface{} `json:"warnings"` | ||||||
|  | 	Auth     interface{} `json:"auth"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func login(host, user, pass string) (string, error) { | ||||||
|  | 	c := httpclient.DefaultClient() | ||||||
|  | 	c.SetHeader("Accept", "application/json") | ||||||
|  | 	c.SetHeader("Content-Type", "application/json") | ||||||
|  | 	c.SetPostData(fmt.Sprintf("{ \"password\":\"%s\"}", pass)) | ||||||
|  |  | ||||||
|  | 	log.Printf("[TRACE] LOGIN URL   : %s", fmt.Sprintf("%s/v1/auth/ldap/login/%s", host, user)) | ||||||
|  | 	log.Printf("[TRACE] LOGIN USER  : %s", user) | ||||||
|  | 	log.Printf("[TRACE] LOGIN PASS  : %s", pass) | ||||||
|  | 	o, err := c.Post(fmt.Sprintf("%s/v1/auth/ldap/login/%s", host, user)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var output authRespStruct | ||||||
|  | 	if err := json.Unmarshal(o, &output); err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return output.Auth.ClientToken, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetCredential(host, user, pass, store, path string) (string, error) { | ||||||
|  | 	token, err := login(host, user, pass) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c := httpclient.DefaultClient() | ||||||
|  | 	c.SetHeader("Accept", "application/json") | ||||||
|  | 	c.SetHeader("Content-Type", "application/json") | ||||||
|  | 	c.SetHeader("X-Vault-Token", token) | ||||||
|  |  | ||||||
|  | 	log.Printf("[TRACE] SECRET URL  : %s", fmt.Sprintf("%s/v1/%s/data/%s", host, store, path)) | ||||||
|  | 	log.Printf("[TRACE] SECRET TOKEN: %s", token) | ||||||
|  | 	o, err := c.Get(fmt.Sprintf("%s/v1/%s/data/%s", host, store, path)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var output secretV2Struct | ||||||
|  | 	if err := json.Unmarshal(o, &output); err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for k, v := range output.Data.Data { | ||||||
|  | 		r, err := regexp.Compile(`(p|P)(a|A)(s|S)(s|S)((w|W)(o|O)(r|R)(d|D))?`) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return "", err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if r.Match([]byte(k)) { | ||||||
|  | 			return v, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return "", fmt.Errorf("no password credential located in secret store") | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user