From ac1c568777eecd684e2030fd42b4f46ff31e76f9 Mon Sep 17 00:00:00 2001
From: Volodymyr Stoiko <me@volodymyrstoiko.com>
Date: Tue, 10 Sep 2024 03:34:46 +0300
Subject: [PATCH] Get Sentry DSN from API (#92)

* Read sentry DSN from API

* naming

* fix

* fix

* Add option to enabled/disable sentry via env var (#93)
---
 main.go              | 40 ++++++++++++--------
 pkg/sentry/sentry.go | 88 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 112 insertions(+), 16 deletions(-)
 create mode 100644 pkg/sentry/sentry.go

diff --git a/main.go b/main.go
index cb4ebc6..fa2919f 100644
--- a/main.go
+++ b/main.go
@@ -23,6 +23,8 @@ import (
 	"k8s.io/client-go/rest"
 
 	"github.com/kubeshark/tracer/pkg/health"
+
+	sentrypkg "github.com/kubeshark/tracer/pkg/sentry"
 )
 
 const (
@@ -41,8 +43,6 @@ var globCbuf = flag.Int("cbuf", 0, fmt.Sprintf("Keep last N packets in circular
 var disableEbpfCapture = flag.Bool("disable-ebpf", false, "Disable capture packet via eBPF")
 var disableTlsLog = flag.Bool("disable-tls-log", false, "Disable tls logging")
 
-const sentryDsn = "https://c0b7399e76173c4601a82aab28eb4be8@o4507855877505024.ingest.us.sentry.io/4507886789263360"
-
 type sslListArray []string
 
 func (i *sslListArray) String() string {
@@ -58,25 +58,33 @@ var sslLibsGlobal sslListArray
 var tracer *Tracer
 
 func main() {
-	// To initialize Sentry's handler, you need to initialize Sentry itself beforehand
-	if err := sentry.Init(sentry.ClientOptions{
-		Dsn:           sentryDsn,
-		EnableTracing: true,
-		// Set TracesSampleRate to 1.0 to capture 100%
-		// of transactions for tracing.
-		// We recommend adjusting this value in production,
-		TracesSampleRate: 1.0,
-		Release:          version.Ver,
-	}); err != nil {
-		log.Error().Err(err).Msg("Sentry initialization failed:")
-	} else {
-		defer sentry.Flush(2 * time.Second)
+	var sentryDSN string
+	if sentrypkg.IsSentryEnabled() {
+		sentryDSN, error := sentrypkg.GetDSN(context.Background())
+		if error != nil {
+			log.Error().Err(error).Msg("Failed to get Sentry DSN")
+		}
+
+		// To initialize Sentry's handler, you need to initialize Sentry itself beforehand
+		if err := sentry.Init(sentry.ClientOptions{
+			Dsn:           sentryDSN,
+			EnableTracing: true,
+			// Set TracesSampleRate to 1.0 to capture 100%
+			// of transactions for tracing.
+			// We recommend adjusting this value in production,
+			TracesSampleRate: 1.0,
+			Release:          version.Ver,
+		}); err != nil {
+			log.Error().Err(err).Msg("Sentry initialization failed:")
+		} else {
+			defer sentry.Flush(2 * time.Second)
+		}
 	}
 
 	zerolog.SetGlobalLevel(zerolog.InfoLevel)
 
 	w, err := zlogsentry.New(
-		sentryDsn,
+		sentryDSN,
 	)
 	if err != nil {
 		stdlog.Fatal(err)
diff --git a/pkg/sentry/sentry.go b/pkg/sentry/sentry.go
new file mode 100644
index 0000000..9c26f8f
--- /dev/null
+++ b/pkg/sentry/sentry.go
@@ -0,0 +1,88 @@
+package sentry
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+
+	"github.com/hashicorp/go-retryablehttp"
+	"github.com/kubeshark/tracer/pkg/version"
+	"github.com/rs/zerolog/log"
+)
+
+type Request struct {
+	Service string `json:"service"`
+	Version string `json:"version"`
+}
+
+type DSNResponse struct {
+	DSN string `json:"dsn"`
+}
+
+func IsSentryEnabled() bool {
+	return os.Getenv("SENTRY_ENABLED") == "true"
+}
+
+func GetDSN(ctx context.Context) (string, error) {
+
+	retryClient := retryablehttp.NewClient()
+	retryClient.RetryMax = 3 // Max retry attempts
+
+	client := retryClient.StandardClient()
+
+	endpoint := getDSNEndpoint()
+
+	reqBody := Request{
+		Service: "tracer",
+		Version: version.Ver,
+	}
+
+	jsonData, err := json.Marshal(reqBody)
+	if err != nil {
+		return "", fmt.Errorf("error marshalling request body: %v", err)
+	}
+
+	req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(jsonData))
+	if err != nil {
+		return "", fmt.Errorf("error creating POST request: %v", err)
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return "", fmt.Errorf("error making POST request: %v", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		return "", nil // Return empty string if not 200
+	}
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", fmt.Errorf("error reading response body: %v", err)
+	}
+
+	var dsnResp DSNResponse
+	err = json.Unmarshal(body, &dsnResp)
+	if err != nil {
+		return "", fmt.Errorf("error unmarshalling response body: %v", err)
+	}
+
+	return dsnResp.DSN, nil
+}
+
+func getDSNEndpoint() string {
+	apiUrl, ok := os.LookupEnv("KUBESHARK_CLOUD_API_URL")
+	if !ok {
+		log.Info().Msg("KUBESHARK_CLOUD_API_URL wasn't found. Defaulting to https://api.kubeshark.co")
+		apiUrl = "https://api.kubeshark.co"
+	}
+
+	return fmt.Sprintf("%s/sentry", apiUrl)
+}