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:
Will Browne
2024-03-22 13:52:24 +01:00
committed by GitHub
parent 0b4c81158e
commit b765c21d4c
8 changed files with 198 additions and 71 deletions

View File

@@ -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
}

View File

@@ -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
}