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 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 expectedSettings SecureSocksDSProxySettings expectedErr error }{ { description: "default values", iniFile: mustNewIniFile(` [secure_socks_datasource_proxy] `), expectedSettings: SecureSocksDSProxySettings{ Enabled: false, ShowUI: true, AllowInsecure: false, ClientCertFilePath: "", ClientCert: "", ClientKey: "", ClientKeyFilePath: "", RootCAs: []string{}, RootCAFilePaths: []string{}, ProxyAddress: "", ServerName: "", }, }, { description: "one or more root ca is required", iniFile: mustNewIniFile(` [secure_socks_datasource_proxy] enabled = true proxy_address = address `), expectedErr: errors.New("one or more rootCA required"), }, { description: "client cert is required", iniFile: mustNewIniFile(` [secure_socks_datasource_proxy] enabled = true proxy_address = address root_ca_cert = cert `), expectedErr: errors.New("client key pair required"), }, { description: "client key is required", iniFile: mustNewIniFile(` [secure_socks_datasource_proxy] enabled = true proxy_address = address root_ca_cert = cert1 client_cert = cert2 `), expectedErr: errors.New("client key pair required"), }, { description: "server name is required", iniFile: mustNewIniFile(` [secure_socks_datasource_proxy] enabled = true proxy_address = address root_ca_cert = cert1 client_cert = cert2 client_key = key `), expectedErr: errors.New("server name required"), }, { description: "proxy address is required", iniFile: mustNewIniFile(` [secure_socks_datasource_proxy] enabled = true root_ca_cert = cert1 client_cert = cert2 client_key = key server_name = name `), expectedErr: errors.New("proxy address required"), }, { description: "root ca, client cert and client key are not required in insecure more", iniFile: mustNewIniFile(` [secure_socks_datasource_proxy] enabled = true proxy_address = address server_name = name allow_insecure = true `), expectedSettings: SecureSocksDSProxySettings{ Enabled: true, ProxyAddress: "address", ServerName: "name", ShowUI: true, AllowInsecure: true, RootCAFilePaths: []string{}, RootCAs: []string{}, }, }, { description: "custom values", iniFile: mustNewIniFile(fmt.Sprintf(` [secure_socks_datasource_proxy] enabled = true client_cert = %s client_key = %s root_ca_cert = %s proxy_address = proxy_address server_name = server_name show_ui = false allow_insecure = false `, testFilePath, testFilePath, rootCACertFilePath)), expectedSettings: SecureSocksDSProxySettings{ 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", }, }, } for _, tt := range cases { t.Run(tt.description, func(t *testing.T) { settings, err := readSecureSocksDSProxySettings(tt.iniFile) if tt.expectedErr != nil { assert.Equal(t, tt.expectedErr, err) } else { 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 }