1package libkv 2 3import ( 4 "fmt" 5 "net/url" 6 "os" 7 "time" 8 9 "github.com/pkg/errors" 10 11 yaml "gopkg.in/yaml.v3" 12 13 "github.com/docker/libkv" 14 "github.com/docker/libkv/store" 15 "github.com/docker/libkv/store/consul" 16 "github.com/hairyhenderson/gomplate/v3/conv" 17 "github.com/hairyhenderson/gomplate/v3/env" 18 "github.com/hairyhenderson/gomplate/v3/vault" 19 consulapi "github.com/hashicorp/consul/api" 20) 21 22const ( 23 http = "http" 24 https = "https" 25) 26 27// NewConsul - instantiate a new Consul datasource handler 28func NewConsul(u *url.URL) (*LibKV, error) { 29 consul.Register() 30 c, err := consulURL(u) 31 if err != nil { 32 return nil, err 33 } 34 config, err := consulConfig(c.Scheme == https) 35 if err != nil { 36 return nil, err 37 } 38 if role := env.Getenv("CONSUL_VAULT_ROLE", ""); role != "" { 39 mount := env.Getenv("CONSUL_VAULT_MOUNT", "consul") 40 41 var client *vault.Vault 42 client, err = vault.New(nil) 43 if err != nil { 44 return nil, err 45 } 46 err = client.Login() 47 defer client.Logout() 48 if err != nil { 49 return nil, err 50 } 51 52 path := fmt.Sprintf("%s/creds/%s", mount, role) 53 54 var data []byte 55 data, err = client.Read(path) 56 if err != nil { 57 return nil, errors.Wrapf(err, "vault consul auth failed") 58 } 59 60 decoded := make(map[string]interface{}) 61 err = yaml.Unmarshal(data, &decoded) 62 if err != nil { 63 return nil, errors.Wrapf(err, "Unable to unmarshal object") 64 } 65 66 token := decoded["token"].(string) 67 68 // nolint: gosec 69 _ = os.Setenv("CONSUL_HTTP_TOKEN", token) 70 } 71 var kv store.Store 72 kv, err = libkv.NewStore(store.CONSUL, []string{c.String()}, config) 73 if err != nil { 74 return nil, errors.Wrapf(err, "Consul setup failed") 75 } 76 return &LibKV{kv}, nil 77} 78 79// -- converts a gomplate datasource URL into a usable Consul URL 80func consulURL(u *url.URL) (*url.URL, error) { 81 addrEnv := env.Getenv("CONSUL_HTTP_ADDR") 82 c, err := url.Parse(addrEnv) 83 if err != nil { 84 return nil, errors.Wrapf(err, "invalid URL '%s' in CONSUL_HTTP_ADDR", addrEnv) 85 } 86 if c.Scheme == "" { 87 c.Scheme = u.Scheme 88 } 89 switch c.Scheme { 90 case "consul+http", http: 91 c.Scheme = http 92 case "consul+https", https: 93 c.Scheme = https 94 case "consul": 95 if conv.Bool(env.Getenv("CONSUL_HTTP_SSL")) { 96 c.Scheme = https 97 } else { 98 c.Scheme = http 99 } 100 } 101 102 if c.Host == "" && u.Host == "" { 103 c.Host = "localhost:8500" 104 } else if c.Host == "" { 105 c.Host = u.Host 106 } 107 108 return c, nil 109} 110 111func consulConfig(useTLS bool) (*store.Config, error) { 112 t := conv.MustAtoi(env.Getenv("CONSUL_TIMEOUT")) 113 config := &store.Config{ 114 ConnectionTimeout: time.Duration(t) * time.Second, 115 } 116 if useTLS { 117 tconf := setupTLS("CONSUL") 118 var err error 119 config.TLS, err = consulapi.SetupTLSConfig(tconf) 120 if err != nil { 121 return nil, errors.Wrapf(err, "TLS Config setup failed") 122 } 123 } 124 return config, nil 125} 126 127func setupTLS(prefix string) *consulapi.TLSConfig { 128 tlsConfig := &consulapi.TLSConfig{ 129 Address: env.Getenv(prefix + "_TLS_SERVER_NAME"), 130 CAFile: env.Getenv(prefix + "_CACERT"), 131 CAPath: env.Getenv(prefix + "_CAPATH"), 132 CertFile: env.Getenv(prefix + "_CLIENT_CERT"), 133 KeyFile: env.Getenv(prefix + "_CLIENT_KEY"), 134 } 135 if v := env.Getenv(prefix + "_HTTP_SSL_VERIFY"); v != "" { 136 verify := conv.Bool(v) 137 tlsConfig.InsecureSkipVerify = !verify 138 } 139 return tlsConfig 140} 141