1package influxdb
2
3import (
4	"context"
5	"fmt"
6	"os"
7	"strconv"
8	"testing"
9	"time"
10
11	"github.com/hashicorp/errwrap"
12	"github.com/hashicorp/vault/helper/testhelpers/docker"
13	"github.com/hashicorp/vault/sdk/database/dbplugin"
14	influx "github.com/influxdata/influxdb/client/v2"
15	"github.com/ory/dockertest"
16)
17
18const testInfluxRole = `CREATE USER "{{username}}" WITH PASSWORD '{{password}}';GRANT ALL ON "vault" TO "{{username}}";`
19
20func prepareInfluxdbTestContainer(t *testing.T) (func(), string, int) {
21	if os.Getenv("INFLUXDB_HOST") != "" {
22		return func() {}, os.Getenv("INFLUXDB_HOST"), 0
23	}
24
25	pool, err := dockertest.NewPool("")
26	if err != nil {
27		t.Fatalf("Failed to connect to docker: %s", err)
28	}
29
30	ro := &dockertest.RunOptions{
31		Repository: "influxdb",
32		Tag:        "alpine",
33		Env:        []string{"INFLUXDB_DB=vault", "INFLUXDB_ADMIN_USER=influx-root", "INFLUXDB_ADMIN_PASSWORD=influx-root", "INFLUXDB_HTTP_AUTH_ENABLED=true"},
34	}
35	resource, err := pool.RunWithOptions(ro)
36	if err != nil {
37		t.Fatalf("Could not start local influxdb docker container: %s", err)
38	}
39
40	cleanup := func() {
41		docker.CleanupResource(t, pool, resource)
42	}
43
44	port, _ := strconv.Atoi(resource.GetPort("8086/tcp"))
45	address := "127.0.0.1"
46
47	// exponential backoff-retry
48	if err = pool.Retry(func() error {
49		cli, err := influx.NewHTTPClient(influx.HTTPConfig{
50			Addr:     fmt.Sprintf("http://%s:%d", address, port),
51			Username: "influx-root",
52			Password: "influx-root",
53		})
54		if err != nil {
55			return errwrap.Wrapf("Error creating InfluxDB Client: ", err)
56		}
57		defer cli.Close()
58		_, _, err = cli.Ping(1)
59		if err != nil {
60			return errwrap.Wrapf("error checking cluster status: {{err}}", err)
61		}
62		return nil
63	}); err != nil {
64		cleanup()
65		t.Fatalf("Could not connect to influxdb docker container: %s", err)
66	}
67	return cleanup, address, port
68}
69
70func TestInfluxdb_Initialize(t *testing.T) {
71	if os.Getenv("VAULT_ACC") == "" {
72		t.SkipNow()
73	}
74	cleanup, address, port := prepareInfluxdbTestContainer(t)
75	defer cleanup()
76
77	connectionDetails := map[string]interface{}{
78		"host":     address,
79		"port":     port,
80		"username": "influx-root",
81		"password": "influx-root",
82	}
83
84	db := new()
85	_, err := db.Init(context.Background(), connectionDetails, true)
86	if err != nil {
87		t.Fatalf("err: %s", err)
88	}
89
90	if !db.Initialized {
91		t.Fatal("Database should be initialized")
92	}
93
94	err = db.Close()
95	if err != nil {
96		t.Fatalf("err: %s", err)
97	}
98
99	// test a string protocol
100	connectionDetails = map[string]interface{}{
101		"host":     address,
102		"port":     strconv.Itoa(port),
103		"username": "influx-root",
104		"password": "influx-root",
105	}
106
107	_, err = db.Init(context.Background(), connectionDetails, true)
108	if err != nil {
109		t.Fatalf("err: %s", err)
110	}
111}
112
113func TestInfluxdb_CreateUser(t *testing.T) {
114	if os.Getenv("VAULT_ACC") == "" {
115		t.SkipNow()
116	}
117	cleanup, address, port := prepareInfluxdbTestContainer(t)
118	defer cleanup()
119
120	connectionDetails := map[string]interface{}{
121		"host":     address,
122		"port":     port,
123		"username": "influx-root",
124		"password": "influx-root",
125	}
126
127	db := new()
128	_, err := db.Init(context.Background(), connectionDetails, true)
129	if err != nil {
130		t.Fatalf("err: %s", err)
131	}
132
133	statements := dbplugin.Statements{
134		Creation: []string{testInfluxRole},
135	}
136
137	usernameConfig := dbplugin.UsernameConfig{
138		DisplayName: "test",
139		RoleName:    "test",
140	}
141
142	username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(time.Minute))
143	if err != nil {
144		t.Fatalf("err: %s", err)
145	}
146
147	if err := testCredsExist(t, address, port, username, password); err != nil {
148		t.Fatalf("Could not connect with new credentials: %s", err)
149	}
150}
151
152func TestMyInfluxdb_RenewUser(t *testing.T) {
153	if os.Getenv("VAULT_ACC") == "" {
154		t.SkipNow()
155	}
156	cleanup, address, port := prepareInfluxdbTestContainer(t)
157	defer cleanup()
158
159	connectionDetails := map[string]interface{}{
160		"host":     address,
161		"port":     port,
162		"username": "influx-root",
163		"password": "influx-root",
164	}
165
166	db := new()
167	_, err := db.Init(context.Background(), connectionDetails, true)
168	if err != nil {
169		t.Fatalf("err: %s", err)
170	}
171
172	statements := dbplugin.Statements{
173		Creation: []string{testInfluxRole},
174	}
175
176	usernameConfig := dbplugin.UsernameConfig{
177		DisplayName: "test",
178		RoleName:    "test",
179	}
180
181	username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(time.Minute))
182	if err != nil {
183		t.Fatalf("err: %s", err)
184	}
185
186	if err := testCredsExist(t, address, port, username, password); err != nil {
187		t.Fatalf("Could not connect with new credentials: %s", err)
188	}
189
190	err = db.RenewUser(context.Background(), statements, username, time.Now().Add(time.Minute))
191	if err != nil {
192		t.Fatalf("err: %s", err)
193	}
194}
195
196func TestInfluxdb_RevokeUser(t *testing.T) {
197	if os.Getenv("VAULT_ACC") == "" {
198		t.SkipNow()
199	}
200	cleanup, address, port := prepareInfluxdbTestContainer(t)
201	defer cleanup()
202
203	connectionDetails := map[string]interface{}{
204		"host":     address,
205		"port":     port,
206		"username": "influx-root",
207		"password": "influx-root",
208	}
209
210	db := new()
211	_, err := db.Init(context.Background(), connectionDetails, true)
212	if err != nil {
213		t.Fatalf("err: %s", err)
214	}
215
216	statements := dbplugin.Statements{
217		Creation: []string{testInfluxRole},
218	}
219
220	usernameConfig := dbplugin.UsernameConfig{
221		DisplayName: "test",
222		RoleName:    "test",
223	}
224
225	username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(time.Minute))
226	if err != nil {
227		t.Fatalf("err: %s", err)
228	}
229
230	if err = testCredsExist(t, address, port, username, password); err != nil {
231		t.Fatalf("Could not connect with new credentials: %s", err)
232	}
233
234	// Test default revoke statements
235	err = db.RevokeUser(context.Background(), statements, username)
236	if err != nil {
237		t.Fatalf("err: %s", err)
238	}
239
240	if err = testCredsExist(t, address, port, username, password); err == nil {
241		t.Fatal("Credentials were not revoked")
242	}
243}
244func TestInfluxdb_RotateRootCredentials(t *testing.T) {
245	if os.Getenv("VAULT_ACC") == "" {
246		t.SkipNow()
247	}
248	cleanup, address, port := prepareInfluxdbTestContainer(t)
249	defer cleanup()
250
251	connectionDetails := map[string]interface{}{
252		"host":     address,
253		"port":     port,
254		"username": "influx-root",
255		"password": "influx-root",
256	}
257
258	db := new()
259
260	connProducer := db.influxdbConnectionProducer
261
262	_, err := db.Init(context.Background(), connectionDetails, true)
263	if err != nil {
264		t.Fatalf("err: %s", err)
265	}
266
267	if !connProducer.Initialized {
268		t.Fatal("Database should be initialized")
269	}
270
271	newConf, err := db.RotateRootCredentials(context.Background(), nil)
272	if err != nil {
273		t.Fatalf("err: %v", err)
274	}
275	if newConf["password"] == "influx-root" {
276		t.Fatal("password was not updated")
277	}
278
279	err = db.Close()
280	if err != nil {
281		t.Fatalf("err: %s", err)
282	}
283}
284
285func testCredsExist(t testing.TB, address string, port int, username, password string) error {
286	cli, err := influx.NewHTTPClient(influx.HTTPConfig{
287		Addr:     fmt.Sprintf("http://%s:%d", address, port),
288		Username: username,
289		Password: password,
290	})
291	if err != nil {
292		return errwrap.Wrapf("Error creating InfluxDB Client: ", err)
293	}
294	defer cli.Close()
295	_, _, err = cli.Ping(1)
296	if err != nil {
297		return errwrap.Wrapf("error checking server ping: {{err}}", err)
298	}
299	q := influx.NewQuery("SHOW SERIES ON vault", "", "")
300	response, err := cli.Query(q)
301	if err != nil {
302		return errwrap.Wrapf("error querying influxdb server: {{err}}", err)
303	}
304	if response.Error() != nil {
305		return errwrap.Wrapf("error using the correct influx database: {{err}}", response.Error())
306	}
307	return nil
308}
309