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