mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Pass PDC file contents in requests (#84783)
* Plugins: Pass PDC file contents in requests * go mod tidy * undo go.mod changes * fix linter * fix tests * undo unnecessary changes * update dep * join with comma * update naming * bump SDK
This commit is contained in:
@@ -1,33 +1,41 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
type SecureSocksDSProxySettings struct {
|
||||
Enabled bool
|
||||
ShowUI bool
|
||||
AllowInsecure bool
|
||||
ClientCert string
|
||||
ClientKey string
|
||||
RootCA string
|
||||
ProxyAddress string
|
||||
ServerName string
|
||||
Enabled bool
|
||||
ShowUI bool
|
||||
AllowInsecure bool
|
||||
ClientCert string
|
||||
ClientCertFilePath string
|
||||
ClientKey string
|
||||
ClientKeyFilePath string
|
||||
RootCAs []string
|
||||
RootCAFilePaths []string
|
||||
ProxyAddress string
|
||||
ServerName string
|
||||
}
|
||||
|
||||
func readSecureSocksDSProxySettings(iniFile *ini.File) (SecureSocksDSProxySettings, error) {
|
||||
s := SecureSocksDSProxySettings{}
|
||||
s := SecureSocksDSProxySettings{
|
||||
RootCAs: []string{},
|
||||
RootCAFilePaths: []string{},
|
||||
}
|
||||
secureSocksProxySection := iniFile.Section("secure_socks_datasource_proxy")
|
||||
s.Enabled = secureSocksProxySection.Key("enabled").MustBool(false)
|
||||
s.ClientCert = secureSocksProxySection.Key("client_cert").MustString("")
|
||||
s.ClientKey = secureSocksProxySection.Key("client_key").MustString("")
|
||||
s.RootCA = secureSocksProxySection.Key("root_ca_cert").MustString("")
|
||||
s.ProxyAddress = secureSocksProxySection.Key("proxy_address").MustString("")
|
||||
s.ServerName = secureSocksProxySection.Key("server_name").MustString("")
|
||||
s.ShowUI = secureSocksProxySection.Key("show_ui").MustBool(true)
|
||||
s.AllowInsecure = secureSocksProxySection.Key("allow_insecure").MustBool(false)
|
||||
s.ClientCertFilePath = secureSocksProxySection.Key("client_cert").MustString("")
|
||||
s.ClientKeyFilePath = secureSocksProxySection.Key("client_key").MustString("")
|
||||
s.RootCAFilePaths = secureSocksProxySection.Key("root_ca_cert").Strings(" ")
|
||||
|
||||
if !s.Enabled {
|
||||
return s, nil
|
||||
@@ -40,14 +48,50 @@ func readSecureSocksDSProxySettings(iniFile *ini.File) (SecureSocksDSProxySettin
|
||||
// If the proxy is going to use TLS.
|
||||
if !s.AllowInsecure {
|
||||
// all fields must be specified to use the proxy
|
||||
if s.RootCA == "" {
|
||||
return s, errors.New("rootCA required")
|
||||
} else if s.ClientCert == "" || s.ClientKey == "" {
|
||||
if len(s.RootCAFilePaths) == 0 {
|
||||
return s, errors.New("one or more rootCA required")
|
||||
} else if s.ClientCertFilePath == "" || s.ClientKeyFilePath == "" {
|
||||
return s, errors.New("client key pair required")
|
||||
} else if s.ServerName == "" {
|
||||
return s, errors.New("server name required")
|
||||
}
|
||||
} else {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
if s.ClientCertFilePath != "" {
|
||||
certPEMBlock, err := os.ReadFile(s.ClientCertFilePath)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.ClientCert = string(certPEMBlock)
|
||||
}
|
||||
|
||||
if s.ClientKeyFilePath != "" {
|
||||
keyPEMBlock, err := os.ReadFile(s.ClientKeyFilePath)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.ClientKey = string(keyPEMBlock)
|
||||
}
|
||||
|
||||
var rootCAs []string
|
||||
for _, rootCAFile := range s.RootCAFilePaths {
|
||||
// nolint:gosec
|
||||
// The gosec G304 warning can be ignored because `rootCAFile` comes from config ini, and we check below if
|
||||
// it's the right file type.
|
||||
pemBytes, err := os.ReadFile(rootCAFile)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
pemDecoded, _ := pem.Decode(pemBytes)
|
||||
if pemDecoded == nil || pemDecoded.Type != "CERTIFICATE" {
|
||||
return s, errors.New("root ca is invalid")
|
||||
}
|
||||
rootCAs = append(rootCAs, string(pemBytes))
|
||||
}
|
||||
s.RootCAs = rootCAs
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -1,25 +1,46 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func mustNewIniFile(fileContents string) *ini.File {
|
||||
file, err := ini.Load([]byte(fileContents))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("creating ini file for test: %s", err))
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
func TestReadSecureSocksDSProxySettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
testFilePath := filepath.Join(tempDir, "test")
|
||||
testFileData := "foobar"
|
||||
err := os.WriteFile(testFilePath, []byte(testFileData), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
rootCACertFilePath := filepath.Join(tempDir, "ca.cert")
|
||||
// nolint:gosec
|
||||
caCertFile, err := os.Create(rootCACertFilePath)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
err = caCertFile.Close()
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
rootCaFileData := createTestRootCAFile(t, rootCACertFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
description string
|
||||
iniFile *ini.File
|
||||
@@ -32,24 +53,27 @@ func TestReadSecureSocksDSProxySettings(t *testing.T) {
|
||||
[secure_socks_datasource_proxy]
|
||||
`),
|
||||
expectedSettings: SecureSocksDSProxySettings{
|
||||
Enabled: false,
|
||||
ClientCert: "",
|
||||
ClientKey: "",
|
||||
RootCA: "",
|
||||
ProxyAddress: "",
|
||||
ServerName: "",
|
||||
ShowUI: true,
|
||||
AllowInsecure: false,
|
||||
Enabled: false,
|
||||
ShowUI: true,
|
||||
AllowInsecure: false,
|
||||
ClientCertFilePath: "",
|
||||
ClientCert: "",
|
||||
ClientKey: "",
|
||||
ClientKeyFilePath: "",
|
||||
RootCAs: []string{},
|
||||
RootCAFilePaths: []string{},
|
||||
ProxyAddress: "",
|
||||
ServerName: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "root ca is required",
|
||||
description: "one or more root ca is required",
|
||||
iniFile: mustNewIniFile(`
|
||||
[secure_socks_datasource_proxy]
|
||||
enabled = true
|
||||
proxy_address = address
|
||||
`),
|
||||
expectedErr: errors.New("rootCA required"),
|
||||
expectedErr: errors.New("one or more rootCA required"),
|
||||
},
|
||||
{
|
||||
description: "client cert is required",
|
||||
@@ -106,35 +130,40 @@ server_name = name
|
||||
allow_insecure = true
|
||||
`),
|
||||
expectedSettings: SecureSocksDSProxySettings{
|
||||
Enabled: true,
|
||||
ProxyAddress: "address",
|
||||
ServerName: "name",
|
||||
ShowUI: true,
|
||||
AllowInsecure: true,
|
||||
Enabled: true,
|
||||
ProxyAddress: "address",
|
||||
ServerName: "name",
|
||||
ShowUI: true,
|
||||
AllowInsecure: true,
|
||||
RootCAFilePaths: []string{},
|
||||
RootCAs: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "custom values",
|
||||
iniFile: mustNewIniFile(`
|
||||
iniFile: mustNewIniFile(fmt.Sprintf(`
|
||||
[secure_socks_datasource_proxy]
|
||||
enabled = true
|
||||
client_cert = cert
|
||||
client_key = key
|
||||
root_ca_cert = root_ca
|
||||
client_cert = %s
|
||||
client_key = %s
|
||||
root_ca_cert = %s
|
||||
proxy_address = proxy_address
|
||||
server_name = server_name
|
||||
show_ui = false
|
||||
allow_insecure = true
|
||||
`),
|
||||
allow_insecure = false
|
||||
`, testFilePath, testFilePath, rootCACertFilePath)),
|
||||
expectedSettings: SecureSocksDSProxySettings{
|
||||
Enabled: true,
|
||||
ClientCert: "cert",
|
||||
ClientKey: "key",
|
||||
RootCA: "root_ca",
|
||||
ProxyAddress: "proxy_address",
|
||||
ServerName: "server_name",
|
||||
ShowUI: false,
|
||||
AllowInsecure: true,
|
||||
Enabled: true,
|
||||
ShowUI: false,
|
||||
AllowInsecure: false,
|
||||
ClientCert: testFileData,
|
||||
ClientCertFilePath: testFilePath,
|
||||
ClientKey: testFileData,
|
||||
ClientKeyFilePath: testFilePath,
|
||||
RootCAs: []string{rootCaFileData},
|
||||
RootCAFilePaths: []string{rootCACertFilePath},
|
||||
ProxyAddress: "proxy_address",
|
||||
ServerName: "server_name",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -146,9 +175,56 @@ allow_insecure = true
|
||||
if tt.expectedErr != nil {
|
||||
assert.Equal(t, tt.expectedErr, err)
|
||||
} else {
|
||||
assert.Equal(t, tt.expectedSettings, settings)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedSettings, settings)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createTestRootCAFile(t *testing.T, path string) string {
|
||||
t.Helper()
|
||||
|
||||
ca := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(2019),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Grafana Labs"},
|
||||
CommonName: "Grafana",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||
IsCA: true,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
require.NoError(t, err)
|
||||
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
// nolint:gosec
|
||||
caCertFile, err := os.Create(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
block := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: caBytes,
|
||||
}
|
||||
err = pem.Encode(caCertFile, block)
|
||||
require.NoError(t, err)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err = pem.Encode(buf, block)
|
||||
require.NoError(t, err)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func mustNewIniFile(fileContents string) *ini.File {
|
||||
file, err := ini.Load([]byte(fileContents))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("creating ini file for test: %s", err))
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user