diff --git a/chronograf.go b/chronograf.go index 89ed7bbc0..5e37ea8f2 100644 --- a/chronograf.go +++ b/chronograf.go @@ -557,13 +557,14 @@ type KapacitorProperty struct { // Server represents a proxy connection to an HTTP server type Server struct { - ID int // ID is the unique ID of the server - SrcID int // SrcID of the data source - Name string // Name is the user-defined name for the server - Username string // Username is the username to connect to the server - Password string // Password is in CLEARTEXT - URL string // URL are the connections to the server - Active bool // Is this the active server for the source? + ID int // ID is the unique ID of the server + SrcID int // SrcID of the data source + Name string // Name is the user-defined name for the server + Username string // Username is the username to connect to the server + Password string // Password is in CLEARTEXT + URL string // URL are the connections to the server + InsecureSkipVerify bool // InsecureSkipVerify as true means any certificate presented by the server is accepted. + Active bool // Is this the active server for the source? } // ServersStore stores connection information for a `Server` diff --git a/kapacitor/client.go b/kapacitor/client.go index 1e7fbf9f5..da6eaa8a5 100644 --- a/kapacitor/client.go +++ b/kapacitor/client.go @@ -19,12 +19,13 @@ const ( // Client communicates to kapacitor type Client struct { - URL string - Username string - Password string - ID chronograf.ID - Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + URL string + Username string + Password string + InsecureSkipVerify bool + ID chronograf.ID + Ticker chronograf.Ticker + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } // KapaClient represents a connection to a kapacitor instance @@ -37,14 +38,15 @@ type KapaClient interface { } // NewClient creates a client that interfaces with Kapacitor tasks -func NewClient(url, username, password string) *Client { +func NewClient(url, username, password string, insecureSkipVerify bool) *Client { return &Client{ - URL: url, - Username: username, - Password: password, - ID: &uuid.V4{}, - Ticker: &Alert{}, - kapaClient: NewKapaClient, + URL: url, + Username: username, + Password: password, + InsecureSkipVerify: insecureSkipVerify, + ID: &uuid.V4{}, + Ticker: &Alert{}, + kapaClient: NewKapaClient, } } @@ -121,7 +123,7 @@ func (c *Client) Create(ctx context.Context, rule chronograf.AlertRule) (*Task, return nil, err } - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -189,7 +191,7 @@ func (c *Client) createFromQueryConfig(rule chronograf.AlertRule) (*client.Creat // Delete removes tickscript task from kapacitor func (c *Client) Delete(ctx context.Context, href string) error { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return err } @@ -197,7 +199,7 @@ func (c *Client) Delete(ctx context.Context, href string) error { } func (c *Client) updateStatus(ctx context.Context, href string, status client.TaskStatus) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -235,7 +237,7 @@ func (c *Client) Status(ctx context.Context, href string) (string, error) { } func (c *Client) status(ctx context.Context, href string) (client.TaskStatus, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return 0, err } @@ -249,7 +251,7 @@ func (c *Client) status(ctx context.Context, href string) (client.TaskStatus, er // All returns all tasks in kapacitor func (c *Client) All(ctx context.Context) (map[string]*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -286,7 +288,7 @@ func (c *Client) Reverse(id string, script chronograf.TICKScript) chronograf.Ale // Get returns a single alert in kapacitor func (c *Client) Get(ctx context.Context, id string) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -301,7 +303,7 @@ func (c *Client) Get(ctx context.Context, id string) (*Task, error) { // Update changes the tickscript of a given id. func (c *Client) Update(ctx context.Context, href string, rule chronograf.AlertRule) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password) + kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) if err != nil { return nil, err } @@ -386,7 +388,7 @@ func toTask(q *chronograf.QueryConfig) client.TaskType { } // NewKapaClient creates a Kapacitor client connection -func NewKapaClient(url, username, password string) (KapaClient, error) { +func NewKapaClient(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { var creds *client.Credentials if username != "" { creds = &client.Credentials{ @@ -397,8 +399,9 @@ func NewKapaClient(url, username, password string) (KapaClient, error) { } clnt, err := client.New(client.Config{ - URL: url, - Credentials: creds, + URL: url, + Credentials: creds, + InsecureSkipVerify: insecureSkipVerify, }) if err != nil { diff --git a/kapacitor/client_test.go b/kapacitor/client_test.go index 1932b1a6c..29b780542 100644 --- a/kapacitor/client_test.go +++ b/kapacitor/client_test.go @@ -75,7 +75,7 @@ func TestClient_All(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -100,7 +100,7 @@ func TestClient_All(t *testing.T) { { name: "return no tasks", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -110,7 +110,7 @@ func TestClient_All(t *testing.T) { { name: "return a non-reversible task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -141,7 +141,7 @@ func TestClient_All(t *testing.T) { { name: "return a reversible task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -380,7 +380,7 @@ func TestClient_Get(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -406,7 +406,7 @@ func TestClient_Get(t *testing.T) { { name: "return no task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -423,7 +423,7 @@ func TestClient_Get(t *testing.T) { { name: "return non-reversible task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -465,7 +465,7 @@ func TestClient_Get(t *testing.T) { { name: "return reversible task", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, }, @@ -706,7 +706,7 @@ func TestClient_updateStatus(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -727,7 +727,7 @@ func TestClient_updateStatus(t *testing.T) { { name: "disable alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -777,7 +777,7 @@ func TestClient_updateStatus(t *testing.T) { { name: "fail to enable alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -797,7 +797,7 @@ func TestClient_updateStatus(t *testing.T) { { name: "enable alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -880,7 +880,7 @@ func TestClient_Update(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -902,7 +902,7 @@ func TestClient_Update(t *testing.T) { { name: "update alert rule error", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -936,7 +936,7 @@ func TestClient_Update(t *testing.T) { { name: "update alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -1000,7 +1000,7 @@ func TestClient_Update(t *testing.T) { { name: "stays disabled when already disabled", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -1099,7 +1099,7 @@ func TestClient_Create(t *testing.T) { Password string ID chronograf.ID Ticker chronograf.Ticker - kapaClient func(url, username, password string) (KapaClient, error) + kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) } type args struct { ctx context.Context @@ -1119,7 +1119,7 @@ func TestClient_Create(t *testing.T) { { name: "create alert rule", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, @@ -1185,7 +1185,7 @@ func TestClient_Create(t *testing.T) { { name: "create alert rule error", fields: fields{ - kapaClient: func(url, username, password string) (KapaClient, error) { + kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { return kapa, nil }, Ticker: &Alert{}, diff --git a/server/kapacitors.go b/server/kapacitors.go index e7982a8cb..fde467a74 100644 --- a/server/kapacitors.go +++ b/server/kapacitors.go @@ -12,11 +12,12 @@ import ( ) type postKapacitorRequest struct { - Name *string `json:"name"` // User facing name of kapacitor instance.; Required: true - URL *string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092);/ Required: true - Username string `json:"username,omitempty"` // Username for authentication to kapacitor - Password string `json:"password,omitempty"` - Active bool `json:"active"` + Name *string `json:"name"` // User facing name of kapacitor instance.; Required: true + URL *string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092);/ Required: true + Username string `json:"username,omitempty"` // Username for authentication to kapacitor + Password string `json:"password,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. + Active bool `json:"active"` } func (p *postKapacitorRequest) Valid() error { @@ -44,13 +45,14 @@ type kapaLinks struct { } type kapacitor struct { - ID int `json:"id,string"` // Unique identifier representing a kapacitor instance. - Name string `json:"name"` // User facing name of kapacitor instance. - URL string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092) - Username string `json:"username,omitempty"` // Username for authentication to kapacitor - Password string `json:"password,omitempty"` - Active bool `json:"active"` - Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor + ID int `json:"id,string"` // Unique identifier representing a kapacitor instance. + Name string `json:"name"` // User facing name of kapacitor instance. + URL string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092) + Username string `json:"username,omitempty"` // Username for authentication to kapacitor + Password string `json:"password,omitempty"` + InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. + Active bool `json:"active"` + Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor } // NewKapacitor adds valid kapacitor store store. @@ -79,12 +81,13 @@ func (h *Service) NewKapacitor(w http.ResponseWriter, r *http.Request) { } srv := chronograf.Server{ - SrcID: srcID, - Name: *req.Name, - Username: req.Username, - Password: req.Password, - URL: *req.URL, - Active: req.Active, + SrcID: srcID, + Name: *req.Name, + Username: req.Username, + Password: req.Password, + InsecureSkipVerify: req.InsecureSkipVerify, + URL: *req.URL, + Active: req.Active, } if srv, err = h.ServersStore.Add(ctx, srv); err != nil { @@ -101,11 +104,12 @@ func (h *Service) NewKapacitor(w http.ResponseWriter, r *http.Request) { func newKapacitor(srv chronograf.Server) kapacitor { httpAPISrcs := "/chronograf/v1/sources" return kapacitor{ - ID: srv.ID, - Name: srv.Name, - Username: srv.Username, - URL: srv.URL, - Active: srv.Active, + ID: srv.ID, + Name: srv.Name, + Username: srv.Username, + URL: srv.URL, + Active: srv.Active, + InsecureSkipVerify: srv.InsecureSkipVerify, Links: kapaLinks{ Self: fmt.Sprintf("%s/%d/kapacitors/%d", httpAPISrcs, srv.SrcID, srv.ID), Proxy: fmt.Sprintf("%s/%d/kapacitors/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), @@ -204,11 +208,12 @@ func (h *Service) RemoveKapacitor(w http.ResponseWriter, r *http.Request) { } type patchKapacitorRequest struct { - Name *string `json:"name,omitempty"` // User facing name of kapacitor instance. - URL *string `json:"url,omitempty"` // URL for the kapacitor - Username *string `json:"username,omitempty"` // Username for kapacitor auth - Password *string `json:"password,omitempty"` - Active *bool `json:"active"` + Name *string `json:"name,omitempty"` // User facing name of kapacitor instance. + URL *string `json:"url,omitempty"` // URL for the kapacitor + Username *string `json:"username,omitempty"` // Username for kapacitor auth + Password *string `json:"password,omitempty"` + InsecureSkipVerify *bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. + Active *bool `json:"active"` } func (p *patchKapacitorRequest) Valid() error { @@ -268,6 +273,9 @@ func (h *Service) UpdateKapacitor(w http.ResponseWriter, r *http.Request) { if req.Username != nil { srv.Username = *req.Username } + if req.InsecureSkipVerify != nil { + srv.InsecureSkipVerify = *req.InsecureSkipVerify + } if req.Active != nil { srv.Active = *req.Active } @@ -303,7 +311,7 @@ func (h *Service) KapacitorRulesPost(w http.ResponseWriter, r *http.Request) { return } - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) var req chronograf.AlertRule if err = json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -440,7 +448,7 @@ func (h *Service) KapacitorRulesPut(w http.ResponseWriter, r *http.Request) { } tid := httprouter.GetParamFromContext(ctx, "tid") - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) var req chronograf.AlertRule if err = json.NewDecoder(r.Body).Decode(&req); err != nil { invalidJSON(w, h.Logger) @@ -510,7 +518,7 @@ func (h *Service) KapacitorRulesStatus(w http.ResponseWriter, r *http.Request) { } tid := httprouter.GetParamFromContext(ctx, "tid") - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) var req KapacitorStatus if err = json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -570,7 +578,7 @@ func (h *Service) KapacitorRulesGet(w http.ResponseWriter, r *http.Request) { return } - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) tasks, err := c.All(ctx) if err != nil { Error(w, http.StatusInternalServerError, err.Error(), h.Logger) @@ -613,7 +621,7 @@ func (h *Service) KapacitorRulesID(w http.ResponseWriter, r *http.Request) { } tid := httprouter.GetParamFromContext(ctx, "tid") - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) // Check if the rule exists within scope task, err := c.Get(ctx, tid) @@ -651,7 +659,7 @@ func (h *Service) KapacitorRulesDelete(w http.ResponseWriter, r *http.Request) { return } - c := kapa.NewClient(srv.URL, srv.Username, srv.Password) + c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) tid := httprouter.GetParamFromContext(ctx, "tid") // Check if the rule is linked to this server and kapacitor diff --git a/server/kapacitors_test.go b/server/kapacitors_test.go index 2a55ea637..b6f475171 100644 --- a/server/kapacitors_test.go +++ b/server/kapacitors_test.go @@ -174,6 +174,14 @@ func Test_KapacitorRulesGet(t *testing.T) { // setup mock service and test logger testLogger := mocks.TestLogger{} svc := &server.Service{ + SourcesStore: &mocks.SourcesStore{ + GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { + return chronograf.Source{ + ID: ID, + InsecureSkipVerify: true, + }, nil + }, + }, ServersStore: &mocks.ServersStore{ GetF: func(ctx context.Context, ID int) (chronograf.Server, error) { return chronograf.Server{ diff --git a/server/swagger.json b/server/swagger.json index 11c40d906..58ebff54e 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -2357,6 +2357,7 @@ "name": "kapa", "url": "http://localhost:9092", "active": false, + "insecureSkipVerify": false, "links": { "proxy": "/chronograf/v1/sources/4/kapacitors/4/proxy", "self": "/chronograf/v1/sources/4/kapacitors/4", @@ -2387,6 +2388,11 @@ "description": "URL for the kapacitor backend (e.g. http://localhost:9092)" }, + "insecureSkipVerify": { + "type": "boolean", + "description": + "True means any certificate presented by the kapacitor is accepted. Typically used for self-signed certs. Probably should only be used for testing." + }, "active": { "type": "boolean", "description":