Skip to content

Commit 01cd9b2

Browse files
authored
feat(bugfix): better sentry implementation, async vault, contextId on… (#29)
* feat(bugfix): better sentry implementation, async vault, contextId on logs, reduce vault api call in parallel * feat(log): traduce in english
1 parent 24794f7 commit 01cd9b2

File tree

12 files changed

+253
-79
lines changed

12 files changed

+253
-79
lines changed

main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func main() {
2828
}
2929
logger.Initialize(*cfg)
3030
log := logger.GetLogger()
31-
sentryService := sentry.NewSentry(cfg.SentryDsn, cfg.Sentry)
31+
sentryService := sentry.NewSentry(cfg.SentryDsn, cfg.Sentry, cfg.SentryEnvironment)
3232
sentryService.StartSentry()
3333

3434
k8sClient := k8s.NewClient()
@@ -40,7 +40,7 @@ func main() {
4040
ctx, cancel := context.WithCancel(context.Background())
4141
defer cancel()
4242

43-
c := controller.NewController(cfg, clientset)
43+
c := controller.NewController(cfg, clientset, sentryService)
4444

4545
switch cfg.Mode {
4646
case "injector":

pkg/config/config.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,24 @@ import (
1111
)
1212

1313
type Config struct {
14-
CertFile string `yaml:"certFile" envconfig:"cert_file"`
15-
KeyFile string `yaml:"keyFile" envconfig:"key_file"`
16-
VaultAddress string `yaml:"vaultAddress" envconfig:"vault_address"`
17-
VaultAuthPath string `yaml:"vaultAuthPath" envconfig:"vault_auth_path"`
18-
LogLevel string `yaml:"logLevel" envconfig:"log_level"`
19-
KubeRole string `yaml:"kubeRole" envconfig:"kube_role"`
20-
TokenTTL string `yaml:"tokenTTL" envconfig:"token_ttl"`
21-
VaultSecretName string `yaml:"vaultSecretName" envconfig:"vault_secret_name"`
22-
VaultSecretPrefix string `yaml:"vaultSecretPrefix" envconfig:"vault_secret_prefix"`
23-
Mode string `yaml:"mode" envconfig:"mode"`
24-
Sentry bool `yaml:"sentry" envconfig:"sentry"`
25-
SentryDsn string `yaml:"sentryDsn" envconfig:"sentry_dsn"`
26-
SyncTTLSecond int `yaml:"syncTTLSecond" envconfig:"sync_ttl_second"`
27-
InjectorLabel string `yaml:"injectorLabel" envconfig:"injector_label"`
28-
DefaultEngine string `yaml:"defaultEngine" envconfig:"default_engine"`
29-
VaultRateLimit int `yaml:"vaultRateLimit" envconfig:"vault_rate_limit"`
14+
CertFile string `yaml:"certFile" envconfig:"cert_file"`
15+
KeyFile string `yaml:"keyFile" envconfig:"key_file"`
16+
VaultAddress string `yaml:"vaultAddress" envconfig:"vault_address"`
17+
VaultAuthPath string `yaml:"vaultAuthPath" envconfig:"vault_auth_path"`
18+
LogLevel string `yaml:"logLevel" envconfig:"log_level"`
19+
KubeRole string `yaml:"kubeRole" envconfig:"kube_role"`
20+
TokenTTL string `yaml:"tokenTTL" envconfig:"token_ttl"`
21+
VaultSecretName string `yaml:"vaultSecretName" envconfig:"vault_secret_name"`
22+
VaultSecretPrefix string `yaml:"vaultSecretPrefix" envconfig:"vault_secret_prefix"`
23+
Mode string `yaml:"mode" envconfig:"mode"`
24+
Sentry bool `yaml:"sentry" envconfig:"sentry"`
25+
SentryDsn string `yaml:"sentryDsn" envconfig:"sentry_dsn"`
26+
SentryEnvironment string `yaml:"sentryEnvironment" envconfig:"sentry_environment"`
27+
SentrySampleRate float64 `yaml:"sentrySampleRate" envconfig:"sentry_sample_rate"`
28+
SyncTTLSecond int `yaml:"syncTTLSecond" envconfig:"sync_ttl_second"`
29+
InjectorLabel string `yaml:"injectorLabel" envconfig:"injector_label"`
30+
DefaultEngine string `yaml:"defaultEngine" envconfig:"default_engine"`
31+
VaultRateLimit int `yaml:"vaultRateLimit" envconfig:"vault_rate_limit"`
3032
}
3133

3234
func NewConfig(configFile string) (*Config, error) {
@@ -43,10 +45,12 @@ func NewConfig(configFile string) (*Config, error) {
4345
Mode: "all",
4446
Sentry: false,
4547
SentryDsn: "",
48+
SentryEnvironment: "production",
49+
SentrySampleRate: 1.0,
4650
SyncTTLSecond: 300,
4751
InjectorLabel: "vault-db-injector",
4852
DefaultEngine: "databases",
49-
VaultRateLimit: 50,
53+
VaultRateLimit: 30,
5054
}
5155
if configFile != "" {
5256
data, err := os.ReadFile(configFile)

pkg/controller/controller.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/numberly/vault-db-injector/pkg/prometheus"
1313
"github.com/numberly/vault-db-injector/pkg/renewer"
1414
"github.com/numberly/vault-db-injector/pkg/revoker"
15+
"github.com/numberly/vault-db-injector/pkg/sentry"
1516
"k8s.io/client-go/kubernetes"
1617
"k8s.io/client-go/tools/leaderelection/resourcelock"
1718
)
@@ -24,19 +25,21 @@ type Controller struct {
2425
Lock *resourcelock.LeaseLock
2526
PodName string
2627
log logger.Logger
28+
sentry sentry.SentryService
2729
}
2830

29-
func NewController(cfg *config.Config, Clientset *kubernetes.Clientset) *Controller {
31+
func NewController(cfg *config.Config, Clientset *kubernetes.Clientset, sentrySvc sentry.SentryService) *Controller {
3032
return &Controller{
3133
Cfg: cfg,
3234
Clientset: Clientset,
3335
log: logger.GetLogger(),
36+
sentry: sentrySvc,
3437
}
3538
}
3639

3740
func (c *Controller) RunInjector(ctx context.Context, errChan chan<- error, runSuccess chan<- bool) {
3841
c.log.Info("Starting server in mode injector")
39-
is := injector.NewWebhookStartor(c.Cfg, errChan, runSuccess)
42+
is := injector.NewWebhookStartor(c.Cfg, errChan, runSuccess, c.sentry)
4043
hcService := healthcheck.NewService()
4144
hcService.RegisterHandlers()
4245
go hcService.Start(ctx, stopChan)

pkg/injector/injector.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package injector
33
import (
44
"context"
55
"crypto/tls"
6+
"fmt"
67
"net/http"
78
"os"
89
"time"
@@ -14,6 +15,7 @@ import (
1415
"github.com/numberly/vault-db-injector/pkg/k8smutator"
1516
"github.com/numberly/vault-db-injector/pkg/logger"
1617
promInjector "github.com/numberly/vault-db-injector/pkg/prometheus"
18+
"github.com/numberly/vault-db-injector/pkg/sentry"
1719
"github.com/prometheus/client_golang/prometheus"
1820
"github.com/prometheus/client_golang/prometheus/promhttp"
1921
kwhhttp "github.com/slok/kubewebhook/v2/pkg/http"
@@ -34,14 +36,16 @@ type starterImpl struct {
3436
errChan chan<- error
3537
successChan chan<- bool
3638
log logger.Logger
39+
sentry sentry.SentryService
3740
}
3841

39-
func NewWebhookStartor(cfg *config.Config, errChan chan<- error, successChan chan<- bool) Startor {
42+
func NewWebhookStartor(cfg *config.Config, errChan chan<- error, successChan chan<- bool, sentrySvc sentry.SentryService) Startor {
4043
return &starterImpl{
4144
cfg: cfg,
4245
errChan: errChan,
4346
successChan: successChan,
4447
log: logger.GetLogger(),
48+
sentry: sentrySvc,
4549
}
4650
}
4751

@@ -58,6 +62,7 @@ func (s *starterImpl) StartWebhook(ctx context.Context, stopChan chan struct{})
5862
metricsRec, err := kwhprometheus.NewRecorder(kwhprometheus.RecorderConfig{Registry: reg})
5963
if err != nil {
6064
close(stopChan)
65+
s.sentry.CaptureError(err)
6166
s.log.Fatalf("could not create Prometheus metrics recorder: %v", err)
6267
}
6368

@@ -70,23 +75,27 @@ func (s *starterImpl) StartWebhook(ctx context.Context, stopChan chan struct{})
7075
wh, err := kwhmutating.NewWebhook(mcfg)
7176
if err != nil {
7277
close(stopChan)
78+
s.sentry.CaptureError(err)
7379
return errors.Newf("error creating webhook: %w", err)
7480
}
7581

7682
serverCert, err := tls.LoadX509KeyPair(s.cfg.CertFile, s.cfg.KeyFile)
7783
if err != nil {
7884
close(stopChan)
85+
s.sentry.CaptureError(err)
7986
s.log.Fatalf("Failed to load server certificate: %v", err)
8087
}
8188

8289
caCertPool, err := k8sClient.GetKubernetesCACert()
8390
if err != nil {
8491
close(stopChan)
92+
s.sentry.CaptureError(err)
8593
s.log.Fatalf("Failed to get Kubernetes CA certificate: %v", err)
8694
}
8795

8896
certByte, err := os.ReadFile(s.cfg.CertFile)
8997
if err != nil {
98+
s.sentry.CaptureError(err)
9099
logger.Errorf(err.Error())
91100
}
92101
caCertPool.AppendCertsFromPEM(certByte)
@@ -98,9 +107,13 @@ func (s *starterImpl) StartWebhook(ctx context.Context, stopChan chan struct{})
98107
})
99108
if err != nil {
100109
close(stopChan)
110+
s.sentry.CaptureError(err)
101111
return errors.Newf("error creating webhook handler: %w", err)
102112
}
103113

114+
// Add Sentry recovery middleware
115+
wrappedHandler := SentryRecoveryMiddleware(s.sentry)(whHandler)
116+
104117
// Configurer mTLS
105118
tlsConfig := &tls.Config{
106119
Certificates: []tls.Certificate{serverCert},
@@ -112,7 +125,7 @@ func (s *starterImpl) StartWebhook(ctx context.Context, stopChan chan struct{})
112125
ReadTimeout: 10 * time.Second,
113126
WriteTimeout: 10 * time.Second,
114127
TLSConfig: tlsConfig,
115-
Handler: whHandler,
128+
Handler: wrappedHandler,
116129
}
117130

118131
s.successChan <- true
@@ -123,6 +136,7 @@ func (s *starterImpl) StartWebhook(ctx context.Context, stopChan chan struct{})
123136
logger.Infof("Listening on :8443")
124137
err = httpServer.ListenAndServeTLS("", "")
125138
if err != nil {
139+
s.sentry.CaptureError(err)
126140
errCh <- errors.Newf("error serving webhook: %w", err)
127141
close(stopChan)
128142
}
@@ -134,6 +148,7 @@ func (s *starterImpl) StartWebhook(ctx context.Context, stopChan chan struct{})
134148
logger.Infof("Listening metrics on :8080")
135149
err = http.ListenAndServe(":8080", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
136150
if err != nil {
151+
s.sentry.CaptureError(err)
137152
errCh <- errors.Newf("error serving webhook metrics: %w", err)
138153
close(stopChan)
139154
}
@@ -144,16 +159,34 @@ func (s *starterImpl) StartWebhook(ctx context.Context, stopChan chan struct{})
144159
select {
145160
case err := <-errCh:
146161
if err != nil {
162+
s.sentry.CaptureError(err)
147163
s.log.Errorf("Server error: %v", err)
148164
close(stopChan)
149165
s.errChan <- err
150166
}
151167
case <-ctx.Done():
152-
s.log.Info("Shutting down servers due to context cancellation")
168+
shutdownMess := "Shutting down servers due to context cancellation"
169+
s.sentry.CaptureMessage(shutdownMess)
170+
s.log.Info(shutdownMess)
153171
httpServer.Shutdown(ctx)
154172
close(stopChan)
155173
// Shutdown metrics server as well
156174
}
157175
}()
158176
return nil
159177
}
178+
179+
// Add this recovery middleware function
180+
func SentryRecoveryMiddleware(sentrySvc sentry.SentryService) func(http.Handler) http.Handler {
181+
return func(next http.Handler) http.Handler {
182+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
183+
defer func() {
184+
if err := recover(); err != nil {
185+
sentrySvc.CaptureError(fmt.Errorf("panic in webhook handler: %v", err))
186+
w.WriteHeader(http.StatusInternalServerError)
187+
}
188+
}()
189+
next.ServeHTTP(w, r)
190+
})
191+
}
192+
}

pkg/k8s/parse_annotations.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func NewService(cfg config.Config, pod *corev1.Pod) *ParserService {
5151
}
5252
}
5353

54-
func (s *ParserService) GetPodDbConfig() (*podDbConfig, error) {
54+
func (s *ParserService) GetPodDbConfig(contextId string) (*podDbConfig, error) {
5555
estimatedSize := len(s.pod.Annotations)
5656
dbConfigurations := make([]DbConfiguration, 0, estimatedSize)
5757
vaultDbPath, ok := s.pod.Annotations[ANNOTATION_VAULT_DB_PATH]
@@ -67,12 +67,12 @@ func (s *ParserService) GetPodDbConfig() (*podDbConfig, error) {
6767
// Extract the database name and configuration type (e.g., dbname, dbaddress) from the key
6868
keyParts := strings.SplitN(key, "/", 2)
6969
if (len(keyParts) < 2 && key != "role") || (len(keyParts) < 2 && key != "cluster") {
70-
s.log.Printf("Warning: Annotation '%s' does not follow the expected format 'db-creds-injector.numberly.io/dbname.configtype'", key)
70+
s.log.Printf("%s: Warning: Annotation '%s' does not follow the expected format 'db-creds-injector.numberly.io/dbname.configtype'", contextId, key)
7171
continue // Skip if the annotation doesn't follow the expected format
7272
}
7373
dbConfigKeyParts := strings.SplitN(keyParts[1], ".", 2)
7474
if (len(dbConfigKeyParts) < 2 && key != "role") || (len(dbConfigKeyParts) < 2 && key != "cluster") {
75-
s.log.Printf("Warning: Configuration for '%s' does not include a database name and type", key)
75+
s.log.Printf("%s: Warning: Configuration for '%s' does not include a database name and type", contextId, key)
7676
continue // Skip if the configuvaultConnation doesn't include a database name and type
7777
}
7878
dbName := dbConfigKeyParts[0]
@@ -98,7 +98,7 @@ func (s *ParserService) GetPodDbConfig() (*podDbConfig, error) {
9898

9999
}
100100

101-
s.log.Infof("La valeur du role est : %s", dbc.Role)
101+
s.log.Infof("%s: The role value is : %s", contextId, dbc.Role)
102102

103103
// Assign the configuration value based on the type
104104
switch configType {
@@ -115,7 +115,7 @@ func (s *ParserService) GetPodDbConfig() (*podDbConfig, error) {
115115
case "role":
116116
dbc.Role = value
117117
default:
118-
s.log.Infof("db configuration is not handled : %s", configType)
118+
s.log.Infof("%s db configuration is not handled : %s", contextId, configType)
119119
}
120120

121121
}

pkg/k8s/parse_annotations_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestGetPodDbConfigWithoutAnnotations(t *testing.T) {
1919
pod := &corev1.Pod{} // Mock pod without any annotations
2020

2121
service := k8s.NewService(cfg, pod)
22-
podDbConfig, err := service.GetPodDbConfig()
22+
podDbConfig, err := service.GetPodDbConfig("id-1")
2323

2424
assert.NoError(t, err)
2525
// The VaultDbPath should default to cfg.DefaultEngine when no annotation is present.
@@ -53,7 +53,7 @@ func TestGetPodDbConfigWithAnnotationsModeURI(t *testing.T) {
5353
}
5454

5555
service := k8s.NewService(cfg, pod)
56-
podDbConfig, err := service.GetPodDbConfig()
56+
podDbConfig, err := service.GetPodDbConfig("id-1")
5757

5858
assert.NoError(t, err)
5959
assert.Equal(t, "custom-engine-path", podDbConfig.VaultDbPath)
@@ -84,7 +84,7 @@ func TestGetPodDbConfigWithAnnotationsModeClassic(t *testing.T) {
8484
}
8585

8686
service := k8s.NewService(cfg, pod)
87-
podDbConfig, err := service.GetPodDbConfig()
87+
podDbConfig, err := service.GetPodDbConfig("id-1")
8888

8989
assert.NoError(t, err)
9090
assert.Equal(t, "custom-engine-path", podDbConfig.VaultDbPath)
@@ -114,7 +114,7 @@ func TestGetPodDbConfigWithAnnotationsModeClassicWithoutDbPath(t *testing.T) {
114114
}
115115

116116
service := k8s.NewService(cfg, pod)
117-
podDbConfig, err := service.GetPodDbConfig()
117+
podDbConfig, err := service.GetPodDbConfig("id-1")
118118

119119
assert.NoError(t, err)
120120
assert.NotEmpty(t, podDbConfig.DbConfigurations)

0 commit comments

Comments
 (0)