Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/tx_indexer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func main() {
Enabled: true,
Host: cfg.Metrics.Host,
Port: cfg.Metrics.Port,
Token: cfg.Metrics.Token,
}
_ = internalMetrics.StartMetricsServer(metricsConfig, []string{internalMetrics.ServiceTxIndexer}, logger)

Expand Down
3 changes: 1 addition & 2 deletions cmd/verifier/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ func main() {
supportedChains,
)

// Initialize metrics based on configuration
var httpMetrics *internalMetrics.HTTPMetrics
var appStoreCollector *internalMetrics.AppStoreCollector
if cfg.Metrics.Enabled {
Expand All @@ -94,9 +93,9 @@ func main() {
Enabled: true,
Host: cfg.Metrics.Host,
Port: cfg.Metrics.Port,
Token: cfg.Metrics.Token,
}, services, logger)

// Create HTTP metrics implementation
httpMetrics = internalMetrics.NewHTTPMetrics()

appStoreMetrics := internalMetrics.NewAppStoreMetrics()
Expand Down
4 changes: 1 addition & 3 deletions cmd/worker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,18 @@ func main() {
vaultMgmService,
)

// Initialize metrics based on configuration
var workerMetrics internalMetrics.WorkerMetricsInterface
if cfg.Metrics.Enabled {
logger.Info("Metrics enabled, setting up Prometheus metrics")

// Start metrics HTTP server with worker metrics
services := []string{internalMetrics.ServiceWorker}
_ = internalMetrics.StartMetricsServer(internalMetrics.Config{
Enabled: true,
Host: cfg.Metrics.Host,
Port: cfg.Metrics.Port,
Token: cfg.Metrics.Token,
}, services, logger)

// Create worker metrics instance
workerMetrics = internalMetrics.NewWorkerMetrics()
} else {
logger.Info("Worker metrics disabled")
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type MetricsConfig struct {
Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"`
Host string `mapstructure:"host" json:"host,omitempty"`
Port int `mapstructure:"port" json:"port,omitempty"`
Token string `mapstructure:"token" json:"token,omitempty"`
}

type PluginAssetsConfig struct {
Expand Down
18 changes: 3 additions & 15 deletions internal/api/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,28 +71,21 @@ func (s *Server) CreatePluginPolicy(c echo.Context) error {
if policy.ID == uuid.Nil {
policy.ID = uuid.New()
}
publicKey, ok := c.Get("vault_public_key").(string)
if !ok || publicKey == "" {
return c.JSON(http.StatusInternalServerError, NewErrorResponseWithMessage(msgVaultPublicKeyGetFailed))
}
if policy.PublicKey != publicKey {
return c.JSON(http.StatusForbidden, NewErrorResponseWithMessage(msgPublicKeyMismatch))
}

var (
isTrialActive bool
err error
)
err = s.db.WithTransaction(c.Request().Context(), func(ctx context.Context, tx pgx.Tx) error {
isTrialActive, _, err = s.db.IsTrialActive(ctx, tx, publicKey)
isTrialActive, _, err = s.db.IsTrialActive(ctx, tx, policy.PublicKey)
return err
})
if err != nil {
s.logger.WithError(err).Warnf("Failed to check trial info")
}

if !isTrialActive {
filePathName := common.GetVaultBackupFilename(publicKey, vtypes.PluginVultisigFees_feee.String())
filePathName := common.GetVaultBackupFilename(policy.PublicKey, vtypes.PluginVultisigFees_feee.String())
exist, err := s.vaultStorage.Exist(filePathName)
if err != nil {
s.logger.WithError(err).Error("failed to check vault existence")
Expand Down Expand Up @@ -197,18 +190,13 @@ func (s *Server) UpdatePluginPolicyById(c echo.Context) error {
return c.JSON(http.StatusBadRequest, NewErrorResponseWithMessage(msgRequestParseFailed))
}

publicKey, ok := c.Get("vault_public_key").(string)
if !ok || publicKey == "" {
return c.JSON(http.StatusInternalServerError, NewErrorResponseWithMessage(msgVaultPublicKeyGetFailed))
}

oldPolicy, err := s.policyService.GetPluginPolicy(c.Request().Context(), policy.ID)
if err != nil {
s.logger.WithError(err).Errorf("failed to get plugin policy, id:%s", policy.ID)
return c.JSON(http.StatusInternalServerError, NewErrorResponseWithMessage(msgPolicyGetFailed))
}

if oldPolicy.PublicKey != publicKey || policy.PublicKey != publicKey {
if oldPolicy.PublicKey != policy.PublicKey {
return c.JSON(http.StatusForbidden, NewErrorResponseWithMessage(msgPublicKeyMismatch))
}

Expand Down
26 changes: 13 additions & 13 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,27 +160,27 @@ func (s *Server) StartServer() error {
tokenGroup.GET("", s.GetActiveTokens)

// Protected vault endpoints
vaultGroup := e.Group("/vault", s.VaultAuthMiddleware)
// Reshare vault endpoint, only user who already log in can request resharing
vaultGroup := e.Group("/vault")
// Reshare vault endpoint
vaultGroup.POST("/reshare", s.ReshareVault)
vaultGroup.GET("/get/:pluginId/:publicKeyECDSA", s.GetVault) // Get Vault Data
vaultGroup.GET("/exist/:pluginId/:publicKeyECDSA", s.ExistVault) // Check if Vault exists
vaultGroup.GET("/get/:pluginId/:publicKeyECDSA", s.GetVault, s.VaultAuthMiddleware) // Get Vault Data (secured)
vaultGroup.GET("/exist/:pluginId/:publicKeyECDSA", s.ExistVault) // Check if Vault exists

// Sign endpoint, plugin should authenticate themselves using the API Key issued by the Verifier
pluginSigner := e.Group("/plugin-signer", s.PluginAuthMiddleware)
pluginSigner.POST("/sign", s.SignPluginMessages) // Sign messages
pluginSigner.GET("/sign/response/:taskId", s.GetKeysignResult) // Get keysign result

pluginGroup := e.Group("/plugin", s.VaultAuthMiddleware)
pluginGroup.DELETE("/:pluginId", s.DeletePlugin) // Delete plugin
pluginGroup.POST("/policy", s.CreatePluginPolicy)
pluginGroup := e.Group("/plugin")
pluginGroup.DELETE("/:pluginId", s.DeletePlugin, s.VaultAuthMiddleware)
pluginGroup.POST("/policy", s.CreatePluginPolicy) // Every valid request should be signed
pluginGroup.PUT("/policy", s.UpdatePluginPolicyById)
pluginGroup.GET("/policies/:pluginId", s.GetAllPluginPolicies)
pluginGroup.GET("/policy/:policyId", s.GetPluginPolicyById)
pluginGroup.GET("/policy/:pluginId/total-count", s.GetPluginInstallationsCountByID)
pluginGroup.DELETE("/policy/:policyId", s.DeletePluginPolicyById)
pluginGroup.GET("/policies/:policyId/history", s.GetPluginPolicyTransactionHistory)
pluginGroup.GET("/transactions", s.GetPluginTransactionHistory)
pluginGroup.GET("/policies/:pluginId", s.GetAllPluginPolicies, s.VaultAuthMiddleware)
pluginGroup.GET("/policy/:policyId", s.GetPluginPolicyById, s.VaultAuthMiddleware)
pluginGroup.GET("/policy/:pluginId/total-count", s.GetPluginInstallationsCountByID, s.VaultAuthMiddleware)
pluginGroup.DELETE("/policy/:policyId", s.DeletePluginPolicyById, s.VaultAuthMiddleware)
pluginGroup.GET("/policies/:policyId/history", s.GetPluginPolicyTransactionHistory, s.VaultAuthMiddleware)
pluginGroup.GET("/transactions", s.GetPluginTransactionHistory, s.VaultAuthMiddleware)

// fee group. These should only be accessible by the plugin server
feeGroup := e.Group("/fees", s.PluginAuthMiddleware)
Expand Down
11 changes: 8 additions & 3 deletions internal/metrics/appstore_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package metrics

import (
"context"
"sync"
"time"

"github.com/sirupsen/logrus"
Expand All @@ -20,6 +21,7 @@ type AppStoreCollector struct {
interval time.Duration
stopCh chan struct{}
doneCh chan struct{}
stopOnce sync.Once
}

func NewAppStoreCollector(db DatabaseQuerier, metrics *AppStoreMetrics, logger *logrus.Logger, interval time.Duration) *AppStoreCollector {
Expand Down Expand Up @@ -57,12 +59,15 @@ func (c *AppStoreCollector) Start() {
}

func (c *AppStoreCollector) Stop() {
close(c.stopCh)
<-c.doneCh
c.stopOnce.Do(func() {
close(c.stopCh)
<-c.doneCh
})
}

func (c *AppStoreCollector) collect() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(context.Background(), c.interval*9/10)
defer cancel()

installations, err := c.db.GetInstallationsByPlugin(ctx)
if err != nil {
Expand Down
71 changes: 59 additions & 12 deletions internal/metrics/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package metrics

import (
"context"
"crypto/subtle"
"fmt"
"net/http"
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
Expand All @@ -16,6 +18,7 @@ type Config struct {
Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"`
Host string `mapstructure:"host" json:"host,omitempty"`
Port int `mapstructure:"port" json:"port,omitempty"`
Token string `mapstructure:"token" json:"token,omitempty"`
}

// DefaultConfig returns default metrics configuration
Expand All @@ -31,20 +34,33 @@ func DefaultConfig() Config {
type Server struct {
server *http.Server
logger *logrus.Logger
token string
}

// NewServer creates a new metrics server with a custom registry
func NewServer(host string, port int, logger *logrus.Logger, registry *prometheus.Registry) *Server {
func NewServer(host string, port int, token string, logger *logrus.Logger, registry *prometheus.Registry) *Server {
s := &Server{
logger: logger,
token: token,
}

mux := http.NewServeMux()

// Register the Prometheus metrics handler with custom registry
var metricsHandler http.Handler
if registry != nil {
mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
metricsHandler = promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
} else {
metricsHandler = promhttp.Handler()
}

if token != "" {
logger.Info("Metrics endpoint protected with Bearer token authentication")
mux.Handle("/metrics", s.authMiddleware(metricsHandler))
} else {
mux.Handle("/metrics", promhttp.Handler())
logger.Warn("Metrics endpoint is NOT protected - consider setting metrics.token in config")
mux.Handle("/metrics", metricsHandler)
}

// Add a health check endpoint
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
Expand All @@ -70,10 +86,42 @@ func NewServer(host string, port int, logger *logrus.Logger, registry *prometheu
IdleTimeout: 15 * time.Second,
}

return &Server{
server: server,
logger: logger,
}
s.server = server
return s
}

func (s *Server) authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "missing authorization header", http.StatusUnauthorized)
return
}

const prefix = "Bearer "
if !strings.HasPrefix(authHeader, prefix) {
http.Error(w, "invalid authorization header format", http.StatusUnauthorized)
return
}

token := authHeader[len(prefix):]
if token == "" {
http.Error(w, "missing token", http.StatusUnauthorized)
return
}

if !s.validateToken(token) {
http.Error(w, "invalid token", http.StatusUnauthorized)
return
}

next.ServeHTTP(w, r)
})
}

func (s *Server) validateToken(token string) bool {
tokenBytes := []byte(token)
return subtle.ConstantTimeCompare([]byte(s.token), tokenBytes) == 1
}

// Start starts the metrics server in a goroutine
Expand All @@ -99,11 +147,10 @@ func StartMetricsServer(cfg Config, services []string, logger *logrus.Logger) *S
return nil
}

// Create registry and register metrics for specified services
registry := prometheus.NewRegistry()
RegisterMetrics(services, registry, logger)

server := NewServer(cfg.Host, cfg.Port, logger, registry)
server := NewServer(cfg.Host, cfg.Port, cfg.Token, logger, registry)
server.Start()
return server
}
Expand All @@ -115,7 +162,7 @@ func StartMetricsServerWithRegistry(cfg Config, registry *prometheus.Registry, l
return nil
}

server := NewServer(cfg.Host, cfg.Port, logger, registry)
server := NewServer(cfg.Host, cfg.Port, cfg.Token, logger, registry)
server.Start()
return server
}
6 changes: 3 additions & 3 deletions plugin/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ func (s *Server) GetRouter() *echo.Echo {
e.GET("/healthz", s.handleHealthz)

vlt := e.Group("/vault")
vlt.POST("/reshare", s.handleReshareVault, s.VerifierAuthMiddleware)
vlt.POST("/reshare", s.handleReshareVault)
vlt.GET("/get/:pluginId/:publicKeyECDSA", s.handleGetVault)
vlt.GET("/exist/:pluginId/:publicKeyECDSA", s.handleExistVault)
vlt.GET("/sign/response/:taskId", s.handleGetKeysignResult)
vlt.DELETE("/:pluginId/:publicKeyECDSA", s.handleDeleteVault, s.VerifierAuthMiddleware)

plg := e.Group("/plugin")
plg.POST("/policy", s.handleCreatePluginPolicy, s.VerifierAuthMiddleware)
plg.PUT("/policy", s.handleUpdatePluginPolicyById, s.VerifierAuthMiddleware)
plg.POST("/policy", s.handleCreatePluginPolicy)
plg.PUT("/policy", s.handleUpdatePluginPolicyById)
plg.GET("/recipe-specification", s.handleGetRecipeSpecification)
plg.POST("/recipe-specification/suggest", s.handleGetRecipeSpecificationSuggest)
plg.DELETE("/policy/:policyId", s.handleDeletePluginPolicyById, s.VerifierAuthMiddleware)
Expand Down
1 change: 1 addition & 0 deletions plugin/tx_indexer/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ type MetricsConfig struct {
Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"`
Host string `mapstructure:"host" json:"host,omitempty"`
Port int `mapstructure:"port" json:"port,omitempty"`
Token string `mapstructure:"token" json:"token,omitempty"`
}