| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | package setting | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"crypto/rand" | 
					
						
							|  |  |  | 	"crypto/rsa" | 
					
						
							|  |  |  | 	"crypto/x509" | 
					
						
							|  |  |  | 	"crypto/x509/pkix" | 
					
						
							|  |  |  | 	"encoding/pem" | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 	"math/big" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 	"gopkg.in/ini.v1" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestReadSecureSocksDSProxySettings(t *testing.T) { | 
					
						
							|  |  |  | 	t.Parallel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 	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) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 	cases := []struct { | 
					
						
							|  |  |  | 		description      string | 
					
						
							|  |  |  | 		iniFile          *ini.File | 
					
						
							|  |  |  | 		expectedSettings SecureSocksDSProxySettings | 
					
						
							|  |  |  | 		expectedErr      error | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			description: "default values", | 
					
						
							|  |  |  | 			iniFile: mustNewIniFile(` | 
					
						
							|  |  |  | 		[secure_socks_datasource_proxy] | 
					
						
							|  |  |  | 		`), | 
					
						
							|  |  |  | 			expectedSettings: SecureSocksDSProxySettings{ | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 				Enabled:            false, | 
					
						
							|  |  |  | 				ShowUI:             true, | 
					
						
							|  |  |  | 				AllowInsecure:      false, | 
					
						
							|  |  |  | 				ClientCertFilePath: "", | 
					
						
							|  |  |  | 				ClientCert:         "", | 
					
						
							|  |  |  | 				ClientKey:          "", | 
					
						
							|  |  |  | 				ClientKeyFilePath:  "", | 
					
						
							|  |  |  | 				RootCAs:            []string{}, | 
					
						
							|  |  |  | 				RootCAFilePaths:    []string{}, | 
					
						
							|  |  |  | 				ProxyAddress:       "", | 
					
						
							|  |  |  | 				ServerName:         "", | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 			description: "one or more root ca is required", | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 			iniFile: mustNewIniFile(` | 
					
						
							|  |  |  | [secure_socks_datasource_proxy] | 
					
						
							|  |  |  | enabled = true | 
					
						
							|  |  |  | proxy_address = address | 
					
						
							|  |  |  | `), | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 			expectedErr: errors.New("one or more rootCA required"), | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			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{ | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 				Enabled:         true, | 
					
						
							|  |  |  | 				ProxyAddress:    "address", | 
					
						
							|  |  |  | 				ServerName:      "name", | 
					
						
							|  |  |  | 				ShowUI:          true, | 
					
						
							|  |  |  | 				AllowInsecure:   true, | 
					
						
							|  |  |  | 				RootCAFilePaths: []string{}, | 
					
						
							|  |  |  | 				RootCAs:         []string{}, | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			description: "custom values", | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 			iniFile: mustNewIniFile(fmt.Sprintf(` | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 		[secure_socks_datasource_proxy] | 
					
						
							|  |  |  | 		enabled = true | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 		client_cert = %s | 
					
						
							|  |  |  | 		client_key = %s | 
					
						
							|  |  |  | 		root_ca_cert = %s | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 		proxy_address = proxy_address | 
					
						
							|  |  |  | 		server_name = server_name | 
					
						
							|  |  |  | 		show_ui = false | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 		allow_insecure = false | 
					
						
							|  |  |  | 		`, testFilePath, testFilePath, rootCACertFilePath)), | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 			expectedSettings: SecureSocksDSProxySettings{ | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 				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", | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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) | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 				assert.Equal(t, tt.expectedSettings, settings) | 
					
						
							| 
									
										
										
										
											2023-12-14 23:16:32 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-03-22 20:52:24 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							|  |  |  | } |