Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework data source editor to address SSL / auth issues #47

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 13 additions & 27 deletions grafana/rmf-app/pkg/plugin/http_helper/http_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,13 @@ import (
typ "github.com/IBM/RMF/grafana/rmf-app/pkg/plugin/types"
)

const DefaultHttpTimeout = 60

type HttpHelper struct {
}

func (h *HttpHelper) GetBaseUrl(em *typ.DatasourceEndpointModel) string {
protocol := "http"
if em.SSL {
protocol = "https"
}
return strings.TrimSpace(protocol) + "://" + strings.TrimSpace(em.Server) + ":" + strings.TrimSpace(em.Port)
return em.URL
}

func (h *HttpHelper) GetQueryType(qm *typ.QueryModel) string {
Expand Down Expand Up @@ -119,7 +117,6 @@ func executeHttpGetRequest(queryURL string, em *typ.DatasourceEndpointModel) (*h

func executeHttpGetRequestInternal(queryURL string, em *typ.DatasourceEndpointModel, isXmlFileRequest bool) (*http.Response, error) {
var client *http.Client
const DdsTimeout time.Duration = 30 * time.Second

req, err := http.NewRequest(http.MethodGet, queryURL, http.NoBody)
if err != nil {
Expand All @@ -132,27 +129,16 @@ func executeHttpGetRequestInternal(queryURL string, em *typ.DatasourceEndpointMo
req.Header.Add("Accept", "application/json")
}

// Get the client reference with timeout
client = &http.Client{Timeout: DdsTimeout}

// Set basic auth (if required)
if em.SSL {
if strings.TrimSpace(em.UserName) != "" {
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
// InsecureSkipVerify controls whether a client verifies the server's certificate chain and host name.
// If InsecureSkipVerify is true, crypto/tls accepts any certificate presented by the server and
// any host name in that certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks
// unless custom verification is used. This should be used only for testing or in combination with VerifyConnection or VerifyPeerCertificate.
InsecureSkipVerify: !em.VerifyInsecureCert,
},
},
Timeout: DdsTimeout,
}

req.SetBasicAuth(em.UserName, em.Password)
}
client = &http.Client{
Timeout: time.Duration(em.IntTimeout) * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: em.TlsSkipVerify,
},
},
}
if strings.TrimSpace(em.UserName) != "" {
req.SetBasicAuth(em.UserName, em.Password)
}

res, err := client.Do(req)
Expand Down
20 changes: 14 additions & 6 deletions grafana/rmf-app/pkg/plugin/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,20 @@ import (
)

type DatasourceEndpointModel struct {
Server string `json:"path"`
Port string `json:"port"`
SSL bool `json:"ssl"`
UserName string `json:"userName"`
Password string `json:"password"`
VerifyInsecureCert bool `json:"skipVerify"`
// Conventional Grafana HTTP config (see the `DataSourceHttpSettings` UI element)
// TODO: the type is to get rid of. We need to re-use one HTTP client on DS level.
URL string
IntTimeout int
RawTimeout string `json:"timeout"`
TlsSkipVerify bool `json:"tlsSkipVerify"`
// Legacy: custom RMF settings. We should ge rid of it at some point.
Server string `json:"path"`
Port string `json:"port"`
SSL bool `json:"ssl"`
UserName string `json:"userName"`
Password string `json:"password"`
// NB: the meaning of JSON field is inverted. If set, we do verify certificates.
VerifyInsecureCert bool `json:"skipVerify"`
DatasourceUid string
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ package unmarshal_functions

import (
"encoding/json"

typ "github.com/IBM/RMF/grafana/rmf-app/pkg/plugin/types"
"fmt"
"strconv"
"strings"

"github.com/grafana/grafana-plugin-sdk-go/backend"

http "github.com/IBM/RMF/grafana/rmf-app/pkg/plugin/http_helper"
typ "github.com/IBM/RMF/grafana/rmf-app/pkg/plugin/types"
)

type UnmarshalFunctions struct {
Expand All @@ -45,21 +49,44 @@ func (u *UnmarshalFunctions) UnmarshalQueryModel(pCtx backend.PluginContext, que
}

func (u *UnmarshalFunctions) UnmarshalEndpointModel(pCtx backend.PluginContext) (*typ.DatasourceEndpointModel, error) {
// Unmarshal the pluginContext JSON into our endpointModel.
var endpointModel typ.DatasourceEndpointModel
// Unmarshal the pluginContext JSON into our em.
var em typ.DatasourceEndpointModel
dsSettings := pCtx.DataSourceInstanceSettings

err := json.Unmarshal(pCtx.DataSourceInstanceSettings.JSONData, &endpointModel)
err := json.Unmarshal(pCtx.DataSourceInstanceSettings.JSONData, &em)
if err != nil {
return nil, err
}
if pCtx.DataSourceInstanceSettings.DecryptedSecureJSONData != nil {
val, ok := pCtx.DataSourceInstanceSettings.DecryptedSecureJSONData["password"]
if ok {
endpointModel.Password = val
if em.Server != "" || em.Port != "" {
// Data source in legacy format
protocol := "http"
if em.SSL {
protocol = "https"
}
em.URL = fmt.Sprintf(
"%s://%s:%s", protocol, strings.TrimSpace(em.Server), strings.TrimSpace(em.Port))
em.IntTimeout = http.DefaultHttpTimeout
em.TlsSkipVerify = !em.VerifyInsecureCert
if pCtx.DataSourceInstanceSettings.DecryptedSecureJSONData != nil {
val, ok := pCtx.DataSourceInstanceSettings.DecryptedSecureJSONData["password"]
if ok {
em.Password = val
}
}
} else {
// Data source in conventional Grafana format
em.URL = dsSettings.URL
em.IntTimeout, err = strconv.Atoi(em.RawTimeout)
if err != nil {
em.IntTimeout = http.DefaultHttpTimeout
}
if dsSettings.BasicAuthEnabled {
em.UserName = dsSettings.BasicAuthUser
em.Password = dsSettings.DecryptedSecureJSONData["basicAuthPassword"]
}
}
endpointModel.DatasourceUid = pCtx.DataSourceInstanceSettings.UID
return &endpointModel, nil
em.DatasourceUid = pCtx.DataSourceInstanceSettings.UID
return &em, nil
}

func (u *UnmarshalFunctions) UnmarshalQueryAndEndpointModel(query backend.DataQuery, pCtx backend.PluginContext) (*typ.QueryModel, *typ.DatasourceEndpointModel, error) {
Expand Down
28 changes: 23 additions & 5 deletions grafana/rmf-app/src/datasources/rmf-datasource/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* (C) Copyright IBM Corp. 2023.
* (C) Copyright Rocket Software, Inc. 2023.
* (C) Copyright IBM Corp. 2023, 2024.
* (C) Copyright Rocket Software, Inc. 2023-2024.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,7 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DataQuery, DataSourceJsonData, NullValueMode, SelectableValue, ThresholdsMode } from '@grafana/data';
import {
DataQuery,
DataSourceJsonData,
DataSourceSettings,
NullValueMode,
SelectableValue,
ThresholdsMode
} from '@grafana/data';

export enum visualisationType {
auto = 'auto',
Expand Down Expand Up @@ -105,21 +112,32 @@ export const defaultQuery: Partial<RMFQuery> = {
* These are options configured for each DataSource instance
*/
export interface RMFDataSourceJsonData extends DataSourceJsonData {
// Conventional Grafana HTTP config (see the `DataSourceHttpSettings` UI element)
tlsSkipVerify?: boolean;
// We store it as a string because of complications of UI input validation
timeout?: string;
// Legacy: custom RMF settings. We should ge rid of it at some point.
path?: string;
port?: string;
ssl?: boolean;
userName?: string;
userName?: string;
// NB: the meaning of that is inverted. If set, we do verify certificates.
skipVerify?: boolean;
}

/**
* Value that is used in the backend, but never sent over HTTP to the frontend
*/
export interface RMFDatasourceSecureJsonData {
export interface RMFDataSourceSecureJsonData {
// Conventional Grafana HTTP config (see the `DataSourceHttpSettings` UI element)
basicAuthPassword?: string
// Legacy: custom RMF settings. We should ge rid of it at some point.
userName?: string;
password?: string;
}

export type RMFDataSourceSettings = DataSourceSettings<RMFDataSourceJsonData, RMFDataSourceSecureJsonData>;

export interface DashboardHistoryItem {
dashboardId?: number;
panelId?: number;
Expand Down
Loading
Loading