Skip to content

Commit

Permalink
refactoring (#1094)
Browse files Browse the repository at this point in the history
  • Loading branch information
yesoreyeram authored Dec 12, 2024
1 parent c3f8476 commit 42e54d4
Show file tree
Hide file tree
Showing 12 changed files with 460 additions and 415 deletions.
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ require (
go.opentelemetry.io/otel/metric v1.32.0 // indirect
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.24.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
Expand Down
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
Expand All @@ -342,8 +342,8 @@ golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -357,14 +357,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
Expand Down
219 changes: 219 additions & 0 deletions pkg/httpclient/httpclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package httpclient

import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"

"github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana-aws-sdk/pkg/sigv4"
"github.com/grafana/grafana-infinity-datasource/pkg/models"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
"github.com/icholy/digest"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
"golang.org/x/oauth2/jwt"
)

func GetHTTPClient(ctx context.Context, settings models.InfinitySettings) (*http.Client, error) {
httpClient, err := getBaseHTTPClient(ctx, settings)
if httpClient == nil {
return httpClient, errors.Join(models.ErrCreatingHTTPClient, err)
}
httpClient, err = applyDigestAuth(ctx, httpClient, settings)
if err != nil {
return httpClient, errors.Join(models.ErrCreatingHTTPClient, err)
}
httpClient, err = applyOAuthClientCredentials(ctx, httpClient, settings)
if err != nil {
return httpClient, errors.Join(models.ErrCreatingHTTPClient, err)
}
httpClient, err = applyOAuthJWT(ctx, httpClient, settings)
if err != nil {
return httpClient, errors.Join(models.ErrCreatingHTTPClient, err)
}
httpClient, err = applyAWSAuth(ctx, httpClient, settings)
if err != nil {
return httpClient, errors.Join(models.ErrCreatingHTTPClient, err)
}
httpClient, err = applySecureSocksProxyConfiguration(ctx, httpClient, settings)
if err != nil {
return httpClient, errors.Join(models.ErrCreatingHTTPClient, err)
}
return httpClient, nil
}

func getBaseHTTPClient(ctx context.Context, settings models.InfinitySettings) (*http.Client, error) {
logger := backend.Logger.FromContext(ctx)
tlsConfig, err := GetTLSConfigFromSettings(settings)
if err != nil {
return nil, err
}
transport := &http.Transport{TLSClientConfig: tlsConfig}
switch settings.ProxyType {
case models.ProxyTypeNone:
logger.Debug("proxy type is set to none. Not using the proxy")
case models.ProxyTypeUrl:
logger.Debug("proxy type is set to url. Using the proxy", "proxy_url", settings.ProxyUrl)
u, err := url.Parse(settings.ProxyUrl)
if err != nil {
logger.Error("error parsing proxy url", "err", err.Error(), "proxy_url", settings.ProxyUrl)
return nil, err
}
transport.Proxy = http.ProxyURL(u)
default:
transport.Proxy = http.ProxyFromEnvironment
}

return &http.Client{
Transport: transport,
Timeout: time.Second * time.Duration(settings.TimeoutInSeconds),
}, nil
}

func isDigestAuthConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodDigestAuth
}

func applyDigestAuth(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
_, span := tracing.DefaultTracer().Start(ctx, "ApplyDigestAuth")
defer span.End()
if isDigestAuthConfigured(settings) {
a := digest.Transport{Username: settings.UserName, Password: settings.Password, Transport: httpClient.Transport}
httpClient.Transport = &a
}
return httpClient, nil
}

func isOAuthCredentialsConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthTypeClientCredentials
}

func applyOAuthClientCredentials(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
_, span := tracing.DefaultTracer().Start(ctx, "ApplyOAuthClientCredentials")
defer span.End()
if isOAuthCredentialsConfigured(settings) {
oauthConfig := clientcredentials.Config{
ClientID: settings.OAuth2Settings.ClientID,
ClientSecret: settings.OAuth2Settings.ClientSecret,
TokenURL: settings.OAuth2Settings.TokenURL,
Scopes: []string{},
EndpointParams: url.Values{},
AuthStyle: settings.OAuth2Settings.AuthStyle,
}
for _, scope := range settings.OAuth2Settings.Scopes {
if scope != "" {
oauthConfig.Scopes = append(oauthConfig.Scopes, scope)
}
}
for k, v := range settings.OAuth2Settings.EndpointParams {
if k != "" && v != "" {
oauthConfig.EndpointParams.Set(k, v)
}
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
httpClient = oauthConfig.Client(ctx)
}
return httpClient, nil
}

func isOAuthJWTConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodOAuth && settings.OAuth2Settings.OAuth2Type == models.AuthOAuthJWT
}

func applyOAuthJWT(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
_, span := tracing.DefaultTracer().Start(ctx, "ApplyOAuthJWT")
defer span.End()
if isOAuthJWTConfigured(settings) {
jwtConfig := jwt.Config{
Email: settings.OAuth2Settings.Email,
TokenURL: settings.OAuth2Settings.TokenURL,
PrivateKey: []byte(strings.ReplaceAll(settings.OAuth2Settings.PrivateKey, "\\n", "\n")),
PrivateKeyID: settings.OAuth2Settings.PrivateKeyID,
Subject: settings.OAuth2Settings.Subject,
Scopes: []string{},
}
for _, scope := range settings.OAuth2Settings.Scopes {
if scope != "" {
jwtConfig.Scopes = append(jwtConfig.Scopes, scope)
}
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
httpClient = jwtConfig.Client(ctx)
}
return httpClient, nil
}

func isAwsAuthConfigured(settings models.InfinitySettings) bool {
return settings.AuthenticationMethod == models.AuthenticationMethodAWS
}

func applyAWSAuth(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
ctx, span := tracing.DefaultTracer().Start(ctx, "ApplyAWSAuth")
defer span.End()
if isAwsAuthConfigured(settings) {
tempHttpClient, err := getBaseHTTPClient(ctx, settings)
if err != nil {
return tempHttpClient, err
}
authType := settings.AWSSettings.AuthType
if authType == "" {
authType = models.AWSAuthTypeKeys
}
region := settings.AWSSettings.Region
if region == "" {
region = "us-east-2"
}
service := settings.AWSSettings.Service
if service == "" {
service = "monitoring"
}
conf := &sigv4.Config{
AuthType: string(authType),
Region: region,
Service: service,
AccessKey: settings.AWSAccessKey,
SecretKey: settings.AWSSecretKey,
}

authSettings := awsds.ReadAuthSettings(ctx)
rt, err := sigv4.New(conf, *authSettings, sigv4.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
req.Header.Add("Accept", "application/json")
return tempHttpClient.Do(req)
}))
if err != nil {
return httpClient, err
}
httpClient.Transport = rt
}
return httpClient, nil
}

func applySecureSocksProxyConfiguration(ctx context.Context, httpClient *http.Client, settings models.InfinitySettings) (*http.Client, error) {
logger := backend.Logger.FromContext(ctx)
if isAwsAuthConfigured(settings) {
return httpClient, nil
}
t := httpClient.Transport
if isDigestAuthConfigured(settings) {
// if we are using Digest, the Transport is 'digest.Transport' that wraps 'http.Transport'
t = t.(*digest.Transport).Transport
} else if isOAuthCredentialsConfigured(settings) || isOAuthJWTConfigured(settings) {
// if we are using Oauth, the Transport is 'oauth2.Transport' that wraps 'http.Transport'
t = t.(*oauth2.Transport).Base
}

// secure socks proxy configuration - checks if enabled inside the function
err := proxy.New(settings.ProxyOpts.ProxyOptions).ConfigureSecureSocksHTTPProxy(t.(*http.Transport))
if err != nil {
logger.Error("error configuring secure socks proxy", "err", err.Error())
return nil, fmt.Errorf("error configuring secure socks proxy. %s", err)
}
return httpClient, nil
}
35 changes: 35 additions & 0 deletions pkg/httpclient/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package httpclient

import (
"crypto/tls"
"crypto/x509"
"errors"

"github.com/grafana/grafana-infinity-datasource/pkg/models"
)

func GetTLSConfigFromSettings(settings models.InfinitySettings) (*tls.Config, error) {
tlsConfig := &tls.Config{
InsecureSkipVerify: settings.InsecureSkipVerify,
ServerName: settings.ServerName,
}
if settings.TLSClientAuth {
if settings.TLSClientCert == "" || settings.TLSClientKey == "" {
return nil, errors.New("invalid Client cert or key")
}
cert, err := tls.X509KeyPair([]byte(settings.TLSClientCert), []byte(settings.TLSClientKey))
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if settings.TLSAuthWithCACert && settings.TLSCACert != "" {
caPool := x509.NewCertPool()
ok := caPool.AppendCertsFromPEM([]byte(settings.TLSCACert))
if !ok {
return nil, errors.New("invalid TLS CA certificate")
}
tlsConfig.RootCAs = caPool
}
return tlsConfig, nil
}
Loading

0 comments on commit 42e54d4

Please sign in to comment.