@@ -2,6 +2,8 @@ package handlers
22
33import (
44 "context"
5+ "crypto/tls"
6+ "crypto/x509"
57 "encoding/json"
68 "fmt"
79 "io"
@@ -57,6 +59,18 @@ const EnvLokiPassword = "LOKI_PASSWORD"
5759// Environment variable name for Loki Token
5860const EnvLokiToken = "LOKI_TOKEN"
5961
62+ // Environment variable name for Loki client certificate file
63+ const EnvLokiClientCert = "LOKI_CLIENT_CERT"
64+
65+ // Environment variable name for Loki client key file
66+ const EnvLokiClientKey = "LOKI_CLIENT_KEY"
67+
68+ // Environment variable name for Loki CA certificate file
69+ const EnvLokiCACert = "LOKI_CA_CERT"
70+
71+ // Environment variable name for TLS insecure skip verify
72+ const EnvLokiTLSInsecureSkipVerify = "LOKI_TLS_INSECURE_SKIP_VERIFY"
73+
6074// Default Loki URL when environment variable is not set
6175const DefaultLokiURL = "http://localhost:3100"
6276
@@ -74,6 +88,42 @@ type LokiLabelValuesResult struct {
7488 Error string `json:"error,omitempty"`
7589}
7690
91+ // createTLSConfig creates a TLS configuration for mTLS authentication
92+ func createTLSConfig (clientCertFile , clientKeyFile , caCertFile string , insecureSkipVerify bool ) (* tls.Config , error ) {
93+ tlsConfig := & tls.Config {
94+ InsecureSkipVerify : insecureSkipVerify ,
95+ }
96+
97+ // Load client certificate and key if provided
98+ if clientCertFile != "" && clientKeyFile != "" {
99+ cert , err := tls .LoadX509KeyPair (clientCertFile , clientKeyFile )
100+ if err != nil {
101+ return nil , fmt .Errorf ("failed to load client certificate: %v" , err )
102+ }
103+ tlsConfig .Certificates = []tls.Certificate {cert }
104+ }
105+
106+ // Load CA certificate if provided
107+ if caCertFile != "" {
108+ // Read the CA certificate file
109+ caCert , err := os .ReadFile (caCertFile )
110+ if err != nil {
111+ return nil , fmt .Errorf ("failed to read CA certificate file: %v" , err )
112+ }
113+
114+ // Create a certificate pool and add the CA certificate
115+ caCertPool := x509 .NewCertPool ()
116+ if ! caCertPool .AppendCertsFromPEM (caCert ) {
117+ return nil , fmt .Errorf ("failed to parse CA certificate" )
118+ }
119+
120+ // Set the CA certificate pool in the TLS configuration
121+ tlsConfig .RootCAs = caCertPool
122+ }
123+
124+ return tlsConfig , nil
125+ }
126+
77127// NewLokiQueryTool creates and returns a tool for querying Grafana Loki
78128func NewLokiQueryTool () mcp.Tool {
79129 // Get Loki URL from environment variable or use default
@@ -173,6 +223,15 @@ func HandleLokiQuery(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal
173223 orgID = os .Getenv (EnvLokiOrgID )
174224 }
175225
226+ // Extract mTLS certificate parameters from environment variables only
227+ clientCert := os .Getenv (EnvLokiClientCert )
228+ clientKey := os .Getenv (EnvLokiClientKey )
229+ caCert := os .Getenv (EnvLokiCACert )
230+
231+ // Extract TLS insecure skip verify from environment variable only
232+ envValue := os .Getenv (EnvLokiTLSInsecureSkipVerify )
233+ tlsInsecureSkipVerify := envValue == "true"
234+
176235 // Set defaults for optional parameters
177236 start := time .Now ().Add (- 1 * time .Hour ).Unix ()
178237 end := time .Now ().Unix ()
@@ -212,7 +271,7 @@ func HandleLokiQuery(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal
212271 }
213272
214273 // Execute query with authentication
215- result , err := executeLokiQuery (ctx , queryURL , username , password , token , orgID )
274+ result , err := executeLokiQuery (ctx , queryURL , username , password , token , orgID , clientCert , clientKey , caCert , tlsInsecureSkipVerify )
216275 if err != nil {
217276 return nil , fmt .Errorf ("query execution failed: %v" , err )
218277 }
@@ -309,7 +368,7 @@ func buildLokiQueryURL(baseURL, query string, start, end int64, limit int) (stri
309368}
310369
311370// executeLokiQuery sends the HTTP request to Loki
312- func executeLokiQuery (ctx context.Context , queryURL string , username , password , token , orgID string ) (* LokiResult , error ) {
371+ func executeLokiQuery (ctx context.Context , queryURL string , username , password , token , orgID , clientCert , clientKey , caCert string , insecureSkipVerify bool ) (* LokiResult , error ) {
313372 // Create HTTP request
314373 req , err := http .NewRequestWithContext (ctx , "GET" , queryURL , nil )
315374 if err != nil {
@@ -330,10 +389,25 @@ func executeLokiQuery(ctx context.Context, queryURL string, username, password,
330389 req .Header .Add ("X-Scope-OrgID" , orgID )
331390 }
332391
333- // Execute request
392+ // Create HTTP client with optional TLS configuration
334393 client := & http.Client {
335394 Timeout : 30 * time .Second ,
336395 }
396+
397+ // Configure TLS if mTLS certificates are provided or if we need to skip verification
398+ if (clientCert != "" && clientKey != "" ) || insecureSkipVerify {
399+ tlsConfig , err := createTLSConfig (clientCert , clientKey , caCert , insecureSkipVerify )
400+ if err != nil {
401+ return nil , fmt .Errorf ("failed to create TLS config: %v" , err )
402+ }
403+
404+ transport := & http.Transport {
405+ TLSClientConfig : tlsConfig ,
406+ }
407+ client .Transport = transport
408+ }
409+
410+ // Execute request
337411 resp , err := client .Do (req )
338412 if err != nil {
339413 return nil , err
@@ -598,6 +672,15 @@ func HandleLokiLabelNames(ctx context.Context, request mcp.CallToolRequest) (*mc
598672 orgID = os .Getenv (EnvLokiOrgID )
599673 }
600674
675+ // Extract mTLS certificate parameters from environment variables only
676+ clientCert := os .Getenv (EnvLokiClientCert )
677+ clientKey := os .Getenv (EnvLokiClientKey )
678+ caCert := os .Getenv (EnvLokiCACert )
679+
680+ // Extract TLS insecure skip verify from environment variable only
681+ envValue := os .Getenv (EnvLokiTLSInsecureSkipVerify )
682+ tlsInsecureSkipVerify := envValue == "true"
683+
601684 // Set defaults for optional parameters
602685 start := time .Now ().Add (- 1 * time .Hour ).Unix ()
603686 end := time .Now ().Unix ()
@@ -632,7 +715,7 @@ func HandleLokiLabelNames(ctx context.Context, request mcp.CallToolRequest) (*mc
632715 }
633716
634717 // Execute labels request
635- result , err := executeLokiLabelsQuery (ctx , labelsURL , username , password , token , orgID )
718+ result , err := executeLokiLabelsQuery (ctx , labelsURL , username , password , token , orgID , clientCert , clientKey , caCert , tlsInsecureSkipVerify )
636719 if err != nil {
637720 return nil , fmt .Errorf ("labels query execution failed: %v" , err )
638721 }
@@ -687,6 +770,15 @@ func HandleLokiLabelValues(ctx context.Context, request mcp.CallToolRequest) (*m
687770 orgID = os .Getenv (EnvLokiOrgID )
688771 }
689772
773+ // Extract mTLS certificate parameters from environment variables only
774+ clientCert := os .Getenv (EnvLokiClientCert )
775+ clientKey := os .Getenv (EnvLokiClientKey )
776+ caCert := os .Getenv (EnvLokiCACert )
777+
778+ // Extract TLS insecure skip verify from environment variable only
779+ envValue := os .Getenv (EnvLokiTLSInsecureSkipVerify )
780+ tlsInsecureSkipVerify := envValue == "true"
781+
690782 // Set defaults for optional parameters
691783 start := time .Now ().Add (- 1 * time .Hour ).Unix ()
692784 end := time .Now ().Unix ()
@@ -721,7 +813,7 @@ func HandleLokiLabelValues(ctx context.Context, request mcp.CallToolRequest) (*m
721813 }
722814
723815 // Execute label values request
724- result , err := executeLokiLabelValuesQuery (ctx , labelValuesURL , username , password , token , orgID )
816+ result , err := executeLokiLabelValuesQuery (ctx , labelValuesURL , username , password , token , orgID , clientCert , clientKey , caCert , tlsInsecureSkipVerify )
725817 if err != nil {
726818 return nil , fmt .Errorf ("label values query execution failed: %v" , err )
727819 }
@@ -796,7 +888,7 @@ func buildLokiLabelValuesURL(baseURL, labelName string, start, end int64) (strin
796888}
797889
798890// executeLokiLabelsQuery sends the HTTP request to Loki labels endpoint
799- func executeLokiLabelsQuery (ctx context.Context , queryURL string , username , password , token , orgID string ) (* LokiLabelsResult , error ) {
891+ func executeLokiLabelsQuery (ctx context.Context , queryURL string , username , password , token , orgID , clientCert , clientKey , caCert string , insecureSkipVerify bool ) (* LokiLabelsResult , error ) {
800892 // Create HTTP request
801893 req , err := http .NewRequestWithContext (ctx , "GET" , queryURL , nil )
802894 if err != nil {
@@ -815,10 +907,25 @@ func executeLokiLabelsQuery(ctx context.Context, queryURL string, username, pass
815907 req .Header .Add ("X-Scope-OrgID" , orgID )
816908 }
817909
818- // Execute request
910+ // Create HTTP client with optional TLS configuration
819911 client := & http.Client {
820912 Timeout : 30 * time .Second ,
821913 }
914+
915+ // Configure TLS if mTLS certificates are provided or if we need to skip verification
916+ if (clientCert != "" && clientKey != "" ) || insecureSkipVerify {
917+ tlsConfig , err := createTLSConfig (clientCert , clientKey , caCert , insecureSkipVerify ) // Pass false for insecureSkipVerify
918+ if err != nil {
919+ return nil , fmt .Errorf ("failed to create TLS config: %v" , err )
920+ }
921+
922+ transport := & http.Transport {
923+ TLSClientConfig : tlsConfig ,
924+ }
925+ client .Transport = transport
926+ }
927+
928+ // Execute request
822929 resp , err := client .Do (req )
823930 if err != nil {
824931 return nil , err
@@ -851,7 +958,7 @@ func executeLokiLabelsQuery(ctx context.Context, queryURL string, username, pass
851958}
852959
853960// executeLokiLabelValuesQuery sends the HTTP request to Loki label values endpoint
854- func executeLokiLabelValuesQuery (ctx context.Context , queryURL string , username , password , token , orgID string ) (* LokiLabelValuesResult , error ) {
961+ func executeLokiLabelValuesQuery (ctx context.Context , queryURL string , username , password , token , orgID , clientCert , clientKey , caCert string , insecureSkipVerify bool ) (* LokiLabelValuesResult , error ) {
855962 // Create HTTP request
856963 req , err := http .NewRequestWithContext (ctx , "GET" , queryURL , nil )
857964 if err != nil {
@@ -870,10 +977,25 @@ func executeLokiLabelValuesQuery(ctx context.Context, queryURL string, username,
870977 req .Header .Add ("X-Scope-OrgID" , orgID )
871978 }
872979
873- // Execute request
980+ // Create HTTP client with optional TLS configuration
874981 client := & http.Client {
875982 Timeout : 30 * time .Second ,
876983 }
984+
985+ // Configure TLS if mTLS certificates are provided
986+ if (clientCert != "" && clientKey != "" ) || insecureSkipVerify {
987+ tlsConfig , err := createTLSConfig (clientCert , clientKey , caCert , insecureSkipVerify ) // Pass false for insecureSkipVerify
988+ if err != nil {
989+ return nil , fmt .Errorf ("failed to create TLS config: %v" , err )
990+ }
991+
992+ transport := & http.Transport {
993+ TLSClientConfig : tlsConfig ,
994+ }
995+ client .Transport = transport
996+ }
997+
998+ // Execute request
877999 resp , err := client .Do (req )
8781000 if err != nil {
8791001 return nil , err
0 commit comments