diff --git a/artifact/facade.go b/artifact/facade.go index 9f1fb6f..00ba50a 100644 --- a/artifact/facade.go +++ b/artifact/facade.go @@ -5,6 +5,8 @@ import ( "errors" "net/http" + "github.com/deepsourcecorp/runner/middleware" + "github.com/labstack/echo/v4" ) @@ -25,12 +27,12 @@ var ( type Facade struct { ArtifactHandler *Handler - CORSMiddleware echo.MiddlewareFunc + allowedOrigin string } type Opts struct { + AllowedOrigin string // For CORS Bucket string - AllowedOrigin string Storage StorageClient } @@ -39,30 +41,19 @@ func New(ctx context.Context, opts *Opts) (*Facade, error) { return nil, ErrMissingOpts } - cors := corsMiddleware(opts.AllowedOrigin) - return &Facade{ + allowedOrigin: opts.AllowedOrigin, ArtifactHandler: NewHandler(opts.Storage, opts.Bucket), - CORSMiddleware: cors, }, nil } -func (f *Facade) AddRoutes(router Router, middleware []echo.MiddlewareFunc) Router { - middleware = append([]echo.MiddlewareFunc{f.CORSMiddleware}, middleware...) - router.AddRoute(http.MethodOptions, "apps/:app_id/artifacts/*", f.ArtifactHandler.HandleOptions, middleware...) - router.AddRoute(http.MethodPost, "apps/:app_id/artifacts/analysis", f.ArtifactHandler.HandleAnalysis, middleware...) - router.AddRoute(http.MethodPost, "apps/:app_id/artifacts/autofix", f.ArtifactHandler.HandleAutofix, middleware...) - return router -} +func (f *Facade) AddRoutes(router Router, m []echo.MiddlewareFunc) Router { + cors := middleware.CorsMiddleware(f.allowedOrigin) + router.AddRoute(http.MethodOptions, "apps/:app_id/artifacts", func(c echo.Context) error { return c.NoContent(http.StatusOK) }, cors) -func corsMiddleware(origin string) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - c.Response().Header().Set(HeaderAccessControlAllowOrigin, origin) - c.Response().Header().Set(HeaderAccessControlAllowMethods, "GET, POST, OPTIONS") - c.Response().Header().Set(HeaderAccessControlAllowHeaders, "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cache-Control, Pragma") - c.Response().Header().Set(HeaderAccessControlAllowCredentials, "true") - return next(c) - } - } + m = append([]echo.MiddlewareFunc{cors}, m...) + router.AddRoute(http.MethodPost, "apps/:app_id/artifacts/analysis", f.ArtifactHandler.HandleAnalysis, m...) + router.AddRoute(http.MethodPost, "apps/:app_id/artifacts/autofix", f.ArtifactHandler.HandleAutofix, m...) + + return router } diff --git a/artifact/handler.go b/artifact/handler.go index 59d4559..4eff938 100644 --- a/artifact/handler.go +++ b/artifact/handler.go @@ -3,7 +3,6 @@ package artifact import ( "fmt" "io" - "net/http" "github.com/labstack/echo/v4" "golang.org/x/exp/slog" @@ -120,7 +119,3 @@ func (h *Handler) HandleAutofix(c echo.Context) error { } return c.JSON(200, autofixArtifactsResponse) } - -func (*Handler) HandleOptions(c echo.Context) error { - return c.NoContent(http.StatusOK) -} diff --git a/artifact/middleware.go b/artifact/middleware.go deleted file mode 100644 index 12df6ab..0000000 --- a/artifact/middleware.go +++ /dev/null @@ -1,15 +0,0 @@ -package artifact - -import "github.com/labstack/echo/v4" - -func CORSMiddleware(allowedOrigin string) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - c.Response().Header().Set("Access-Control-Allow-Origin", allowedOrigin) - c.Response().Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - c.Response().Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cache-Control, Pragma") - c.Response().Header().Set("Access-Control-Allow-Credentials", "true") - return next(c) - } - } -} diff --git a/auth/auth.go b/auth/auth.go index 861ce50..1a918cc 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -13,6 +13,7 @@ import ( "github.com/deepsourcecorp/runner/auth/store" "github.com/deepsourcecorp/runner/auth/token" "github.com/deepsourcecorp/runner/httperror" + "github.com/deepsourcecorp/runner/middleware" "github.com/labstack/echo/v4" "golang.org/x/exp/slog" ) @@ -27,14 +28,16 @@ type Facade struct { SAMLHandlers *saml.Handler TokenMiddleware echo.MiddlewareFunc SessionMiddleware echo.MiddlewareFunc + allowedOrigin string } type Opts struct { - Runner *model.Runner - DeepSource *model.DeepSource - Apps map[string]*oauth.App - SAML *saml.Opts - Store store.Store + Runner *model.Runner + DeepSource *model.DeepSource + Apps map[string]*oauth.App + SAML *saml.Opts + Store store.Store + AllowedOrigin string // For CORS } func New(ctx context.Context, opts *Opts, client *http.Client) (*Facade, error) { @@ -80,11 +83,13 @@ func New(ctx context.Context, opts *Opts, client *http.Client) (*Facade, error) TokenMiddleware: tokenMiddleware, SessionMiddleware: sessionMiddleware, SAMLHandlers: samlHandlers, + allowedOrigin: opts.AllowedOrigin, }, nil } func (f *Facade) AddRoutes(r Router) Router { - r.AddRoute(http.MethodPost, "/refresh", f.TokenHandlers.HandleRefresh) + cors := middleware.CorsMiddleware(f.allowedOrigin) + r.AddRoute(http.MethodPost, "/refresh", f.TokenHandlers.HandleRefresh, cors) r.AddRoute(http.MethodPost, "/logout", f.TokenHandlers.HandleLogout) r.AddRoute(http.MethodGet, "/apps/:app_id/auth/authorize", f.OAuthHandlers.HandleAuthorize) diff --git a/auth/oauth/handler.go b/auth/oauth/handler.go index 22bfca2..29a61cc 100644 --- a/auth/oauth/handler.go +++ b/auth/oauth/handler.go @@ -15,10 +15,6 @@ import ( "golang.org/x/oauth2" ) -const ( - ExpiryAccessToken = 15 * time.Minute -) - type Handler struct { runner *model.Runner deepsource *model.DeepSource @@ -97,7 +93,7 @@ func (h *Handler) HandleCallback(c echo.Context) error { return c.JSON(500, err.Error()) } - accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser, token.ScopeCodeRead}, user) + accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser, token.ScopeCodeRead}, user, token.ExpiryAccessToken) if err != nil { return c.JSON(500, err.Error()) } @@ -111,7 +107,7 @@ func (h *Handler) HandleCallback(c echo.Context) error { HttpOnly: true, }) - refreshToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeRefresh}, user) + refreshToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeRefresh}, user, token.ExpiryRefreshToken) if err != nil { return c.JSON(500, err.Error()) } @@ -185,11 +181,11 @@ func (h *Handler) HandleToken(c echo.Context) error { return c.JSON(http.StatusForbidden, err.Error()) } - accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user) + accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user, token.ExpiryAccessToken) if err != nil { return c.JSON(500, err.Error()) } - refreshtToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user) + refreshtToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user, token.ExpiryRefreshToken) if err != nil { return c.JSON(500, err.Error()) } @@ -272,7 +268,7 @@ func (h *Handler) HandleRefresh(c echo.Context) error { return c.JSON(http.StatusUnauthorized, err.Error()) } - accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user) + accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user, token.ExpiryAccessToken) if err != nil { return c.JSON(500, err.Error()) } diff --git a/auth/saml/handler.go b/auth/saml/handler.go index 55191f0..7347bc6 100644 --- a/auth/saml/handler.go +++ b/auth/saml/handler.go @@ -89,7 +89,7 @@ func (h *Handler) AuthorizationHandler() echo.HandlerFunc { Email: attr.Get("email"), Name: attr.Get("first_name") + " " + attr.Get("last_name"), } - accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser, token.ScopeCodeRead}, user) + accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser, token.ScopeCodeRead}, user, token.ExpiryAccessToken) if err != nil { w.WriteHeader(http.StatusInternalServerError) if _, err := w.Write([]byte(err.Error())); err != nil { @@ -108,7 +108,7 @@ func (h *Handler) AuthorizationHandler() echo.HandlerFunc { HttpOnly: true, }) - refreshToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeRefresh}, user) + refreshToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeRefresh}, user, token.ExpiryRefreshToken) if err != nil { w.WriteHeader(http.StatusInternalServerError) if _, err := w.Write([]byte(err.Error())); err != nil { @@ -187,12 +187,12 @@ func (h *Handler) HandleToken(c echo.Context) error { return c.JSON(http.StatusForbidden, err.Error()) } - accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user) + accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user, token.ExpiryAccessToken) if err != nil { return c.JSON(http.StatusInternalServerError, err.Error()) } - refreshtoken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeRefresh}, user) + refreshtoken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeRefresh}, user, token.ExpiryRefreshToken) if err != nil { return c.JSON(http.StatusInternalServerError, err.Error()) } @@ -227,7 +227,7 @@ func (h *Handler) HandleRefresh(c echo.Context) error { return c.JSON(http.StatusUnauthorized, err.Error()) } - accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user) + accessToken, err := h.tokenService.GenerateToken(h.runner.ID, []string{token.ScopeUser}, user, token.ExpiryAccessToken) if err != nil { return c.JSON(500, err.Error()) } diff --git a/auth/token/handler.go b/auth/token/handler.go index 6e951cd..a88d193 100644 --- a/auth/token/handler.go +++ b/auth/token/handler.go @@ -15,11 +15,14 @@ type Handler struct { func NewHandler(runner *model.Runner, service *Service) *Handler { return &Handler{ service: service, + runner: runner, } } func (h *Handler) HandleRefresh(c echo.Context) error { - referrer := c.Request().Referer() + + referrer := c.QueryParam("redirect") + cookie, err := c.Cookie("refresh") if err != nil { return c.JSON(http.StatusUnauthorized, err.Error()) @@ -33,10 +36,11 @@ func (h *Handler) HandleRefresh(c echo.Context) error { return c.JSON(http.StatusUnauthorized, err.Error()) } - accessToken, err := h.service.GenerateToken(h.runner.ID, []string{ScopeUser, ScopeCodeRead}, user) + accessToken, err := h.service.GenerateToken(h.runner.ID, []string{ScopeUser, ScopeCodeRead}, user, ExpiryAccessToken) if err != nil { return c.JSON(500, err.Error()) } + c.SetCookie(&http.Cookie{ Name: "session", Value: accessToken, diff --git a/auth/token/middleware.go b/auth/token/middleware.go index ccd94cf..3753daf 100644 --- a/auth/token/middleware.go +++ b/auth/token/middleware.go @@ -9,16 +9,19 @@ import ( func SessionAuthMiddleware(runnerID string, service *Service) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { + referer := c.Request().URL.String() + c.Response().Header().Set("referer", referer) + cookie, err := c.Cookie("session") if err != nil { - return c.Redirect(http.StatusTemporaryRedirect, "/refresh") + return c.Redirect(http.StatusTemporaryRedirect, "/refresh?redirect="+referer) } if cookie.Value == "" { - return c.Redirect(http.StatusTemporaryRedirect, "/refresh") + return c.Redirect(http.StatusTemporaryRedirect, "/refresh?redirect="+referer) } _, err = service.ReadToken(runnerID, ScopeCodeRead, cookie.Value) if err != nil { - return c.Redirect(http.StatusTemporaryRedirect, "/refresh") + return c.Redirect(http.StatusTemporaryRedirect, "/refresh?redirect="+referer) } return next(c) } diff --git a/auth/token/middleware_test.go b/auth/token/middleware_test.go index 8023f1f..7110fa2 100644 --- a/auth/token/middleware_test.go +++ b/auth/token/middleware_test.go @@ -76,7 +76,7 @@ func TestSessionAuthMiddleware(t *testing.T) { t.Run("token expired", func(t *testing.T) { ExpiryAccessToken = -1 * time.Minute - token, err := service.GenerateToken("runner-id", []string{ScopeUser}, user) + token, err := service.GenerateToken("runner-id", []string{ScopeUser}, user, ExpiryAccessToken) require.NoError(t, err) req := httptest.NewRequest("GET", "/", nil) req.AddCookie(&http.Cookie{Name: "session", Value: token}) @@ -93,7 +93,7 @@ func TestSessionAuthMiddleware(t *testing.T) { t.Run("valid token", func(t *testing.T) { ExpiryAccessToken = 10 * time.Minute - token, err := service.GenerateToken("runner-id", []string{ScopeCodeRead}, user) + token, err := service.GenerateToken("runner-id", []string{ScopeCodeRead}, user, ExpiryAccessToken) require.NoError(t, err) req := httptest.NewRequest("GET", "/", nil) req.AddCookie(&http.Cookie{Name: "session", Value: token}) diff --git a/auth/token/service.go b/auth/token/service.go index 286cc54..2cec7ef 100644 --- a/auth/token/service.go +++ b/auth/token/service.go @@ -17,7 +17,8 @@ const ( ) var ( - ExpiryAccessToken = 15 * time.Minute + ExpiryAccessToken = 15 * time.Minute + ExpiryRefreshToken = 15 * 24 * time.Hour ) type Service struct { @@ -32,8 +33,8 @@ func NewService(signer *jwtutil.Signer, verifier *jwtutil.Verifier) *Service { } } -func (s *Service) GenerateToken(issuer string, scopes []string, user *model.User) (string, error) { - return s.signer.GenerateToken(issuer, scopes, user.Claims(), ExpiryAccessToken) +func (s *Service) GenerateToken(issuer string, scopes []string, user *model.User, expiry time.Duration) (string, error) { + return s.signer.GenerateToken(issuer, scopes, user.Claims(), expiry) } func (s *Service) ReadToken(issuer string, scope string, token string) (*model.User, error) { @@ -69,56 +70,3 @@ func (s *Service) ReadToken(issuer string, scope string, token string) (*model.U Provider: claims["provider"].(string), }, nil } - -// func (s *Service) ReadAccessToken(issuer string, token string) (*model.User, error) { -// claims, err := s.verifier.Verify(token) -// if err != nil { -// return nil, err -// } -// for _, v := range []string{"id", "name", "email", "login", "provider"} { -// if _, ok := claims[v]; !ok { -// return nil, errors.New("invalid claims") -// } -// } - -// if claims["iss"] != issuer { -// return nil, errors.New("invalid issuer") -// } - -// if claims["scp"] != ScopeCodeRead { -// return nil, errors.New("invalid scope") -// } - -// return &model.User{ -// ID: claims["id"].(string), -// Name: claims["name"].(string), -// Email: claims["email"].(string), -// Login: claims["login"].(string), -// Provider: claims["provider"].(string), -// }, nil -// } - -// func (s *Service) ReadRefreshToken(issuer string, token string) (*model.User, error) { -// claims, err := s.verifier.Verify(token) -// if err != nil { -// return nil, err -// } -// for _, v := range []string{"id", "name", "email", "login", "provider"} { -// if _, ok := claims[v]; !ok { -// return nil, errors.New("invalid claims") -// } -// } -// if claims["iss"] != issuer { -// return nil, errors.New("invalid issuer") -// } -// if claims["scp"] != ScopeRefresh { -// return nil, errors.New("invalid scope") -// } -// return &model.User{ -// ID: claims["id"].(string), -// Name: claims["name"].(string), -// Email: claims["email"].(string), -// Login: claims["login"].(string), -// Provider: claims["provider"].(string), -// }, nil -// } diff --git a/cmd/runner/auth.go b/cmd/runner/auth.go index 915542b..07b6d83 100644 --- a/cmd/runner/auth.go +++ b/cmd/runner/auth.go @@ -39,11 +39,12 @@ func GetAuthentiacator(ctx context.Context, c *config.Config) (*auth.Facade, err } opts := &auth.Opts{ - Runner: runner, - DeepSource: deepsource, - Apps: apps, - Store: store, - SAML: samlOpts, + Runner: runner, + DeepSource: deepsource, + Apps: apps, + Store: store, + SAML: samlOpts, + AllowedOrigin: c.DeepSource.Host.String(), } app, err := auth.New(ctx, opts, http.DefaultClient) diff --git a/cmd/runner/cors.go b/cmd/runner/cors.go new file mode 100644 index 0000000..62003d5 --- /dev/null +++ b/cmd/runner/cors.go @@ -0,0 +1,22 @@ +package main + +import "github.com/labstack/echo/v4" + +const ( + HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" + HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" +) + +func CorsMiddleware(origin string) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + c.Response().Header().Set(HeaderAccessControlAllowOrigin, origin) + c.Response().Header().Set(HeaderAccessControlAllowMethods, "GET, POST, OPTIONS") + c.Response().Header().Set(HeaderAccessControlAllowHeaders, "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cache-Control, Pragma") + c.Response().Header().Set(HeaderAccessControlAllowCredentials, "true") + return next(c) + } + } +} diff --git a/cmd/runner/main.go b/cmd/runner/main.go index f5b0647..5be3637 100644 --- a/cmd/runner/main.go +++ b/cmd/runner/main.go @@ -46,7 +46,6 @@ func LoadConfig() (*config.Config, error) { if err != nil { return nil, fmt.Errorf("failed to load config: %w", err) } - slog.Info("config loaded successfully") return cfg, nil } @@ -59,14 +58,16 @@ func SetLogLevel() { func main() { ParseFlags() SetLogLevel() - ctx := context.Background() - c, err := LoadConfig() if err != nil { slog.Error("failed to load config", slog.Any("err", err)) os.Exit(1) } + s := NewServer(c) + if !HideBanner { + s.PrintBanner() + } err = Migrate(c.RQLite) if err != nil { @@ -80,7 +81,6 @@ func main() { slog.Warn("failed to sync upstream", slog.Any("err", err)) } - s := NewServer(c) r, err := s.Router() if err != nil { slog.Error("failed to initialize router", slog.Any("err", err)) @@ -108,6 +108,13 @@ func main() { } orchestrator.AddRoutes(r, []echo.MiddlewareFunc{auth.TokenMiddleware}) + artifacts, err := GetArtifacts(ctx, c) + if err != nil { + slog.Error("failed to initialize artifacts app", slog.Any("err", err)) + os.Exit(1) + } + artifacts.AddRoutes(r, []echo.MiddlewareFunc{auth.SessionMiddleware}) + go orchestrator.Cleaner.Start(ctx) r.Setup() diff --git a/cmd/runner/runner.go b/cmd/runner/runner.go index e6dcb62..6279b9d 100644 --- a/cmd/runner/runner.go +++ b/cmd/runner/runner.go @@ -34,6 +34,7 @@ type Server struct { *echo.Echo *config.Config *http.Client + cors echo.MiddlewareFunc } func NewServer(c *config.Config) *Server { @@ -44,13 +45,11 @@ func NewServer(c *config.Config) *Server { e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "time=${time_rfc3339_nano} level=INFO method=${method}, uri=${uri}, status=${status}\n", })) - return &Server{Echo: e, Config: c} + cors := CorsMiddleware(c.DeepSource.Host.String()) + return &Server{Echo: e, Config: c, cors: cors} } func (s *Server) Start() error { - if !HideBanner { - s.PrintBanner() - } err := s.Echo.Start(fmt.Sprintf(":%d", RunnerPort)) if err != nil { slog.Error("failed to start server", slog.Any("err", err)) @@ -70,6 +69,9 @@ func (s *Server) Router() (*Router, error) { { Method: http.MethodGet, Path: "/readyz", HandlerFunc: func(c echo.Context) error { return c.JSON(http.StatusOK, map[string]interface{}{"status": "ok"}) }, }, + { + Method: http.MethodOptions, Path: "/*", HandlerFunc: func(c echo.Context) error { return c.NoContent(http.StatusOK) }, Middleware: []echo.MiddlewareFunc{s.cors}, + }, }, } return router, nil diff --git a/cmd/runner/sync.go b/cmd/runner/sync.go index f676d1e..578f8ef 100644 --- a/cmd/runner/sync.go +++ b/cmd/runner/sync.go @@ -4,10 +4,15 @@ import ( "context" "net/http" + "github.com/deepsourcecorp/runner/auth/jwtutil" "github.com/deepsourcecorp/runner/config" "github.com/deepsourcecorp/runner/sync" ) +var providers = map[string]string{ + "github": "gh", +} + func GetSyncer(_ context.Context, c *config.Config, client *http.Client) *sync.Syncer { deepsource := &sync.DeepSource{ Host: c.DeepSource.Host, @@ -25,8 +30,10 @@ func GetSyncer(_ context.Context, c *config.Config, client *http.Client) *sync.S apps = append(apps, sync.App{ ID: a.ID, Name: a.Name, - Provider: a.Provider, + Provider: providers[a.Provider], }) } - return sync.New(deepsource, runner, apps, client) + + signer := jwtutil.NewSigner(c.Runner.PrivateKey) + return sync.New(deepsource, runner, apps, signer, client) } diff --git a/middleware/cors.go b/middleware/cors.go new file mode 100644 index 0000000..3db6d47 --- /dev/null +++ b/middleware/cors.go @@ -0,0 +1,22 @@ +package middleware + +import "github.com/labstack/echo/v4" + +const ( + HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" + HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" +) + +func CorsMiddleware(origin string) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + c.Response().Header().Set(HeaderAccessControlAllowOrigin, origin) + c.Response().Header().Set(HeaderAccessControlAllowMethods, "GET, POST, OPTIONS") + c.Response().Header().Set(HeaderAccessControlAllowHeaders, "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cache-Control, Pragma") + c.Response().Header().Set(HeaderAccessControlAllowCredentials, "true") + return next(c) + } + } +} diff --git a/orchestrator/handler.go b/orchestrator/handler.go index 79589a1..8037849 100644 --- a/orchestrator/handler.go +++ b/orchestrator/handler.go @@ -6,7 +6,6 @@ import ( artifact "github.com/deepsourcelabs/artifacts/types" "github.com/labstack/echo/v4" - "golang.org/x/exp/slog" ) type Handler struct { @@ -24,7 +23,6 @@ func NewHandler( signer Signer, runner *Runner, ) *Handler { - slog.Info("initializing orchestrator handler", slog.Any("opts", opts)) return &Handler{ analysisTask: NewAnalysisTask(runner, opts, driver, provider, signer), autofixTask: NewAutofixTask(runner, opts, driver, provider, signer), diff --git a/sync/sync.go b/sync/sync.go index c077691..9d5422a 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -4,10 +4,16 @@ import ( "bytes" "encoding/json" "fmt" + "io" "net/http" "net/url" + "time" ) +type Signer interface { + GenerateToken(issuer string, scope []string, claims map[string]interface{}, expiry time.Duration) (string, error) +} + type App struct { ID string `json:"app_id"` Name string `json:"name"` @@ -36,38 +42,46 @@ type Payload struct { } type Syncer struct { - client *http.Client - payload *Payload - target string + client *http.Client + deepsource *DeepSource + apps []App + runner *Runner + signer Signer } -func New(deepsource *DeepSource, runner *Runner, apps []App, client *http.Client) *Syncer { +func New(deepsource *DeepSource, runner *Runner, apps []App, signer Signer, client *http.Client) *Syncer { if client == nil { client = http.DefaultClient } - payload := &Payload{ - RunnerID: runner.ID, - BaseURL: runner.Host.String(), - ClientID: runner.ClientID, - ClientSecret: runner.ClientSecret, - WebhookSecret: runner.WebhookSecret, - Apps: apps, - } - target := deepsource.Host.JoinPath("/api/runners/").String() - return &Syncer{client: client, payload: payload, target: target} + return &Syncer{client: client, runner: runner, deepsource: deepsource, apps: apps, signer: signer} } func (s *Syncer) Sync() error { - data, err := json.Marshal(s.payload) + payload := &Payload{ + BaseURL: s.runner.Host.String(), + ClientSecret: s.runner.ClientSecret, + WebhookSecret: s.runner.WebhookSecret, + Apps: s.apps, + } + + data, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("failed to sync to DeepSource: %w", err) + } + + target := s.deepsource.Host.JoinPath("/api/runner/").String() + request, err := http.NewRequest(http.MethodPut, target, bytes.NewReader(data)) if err != nil { return fmt.Errorf("failed to sync to DeepSource: %w", err) } - request, err := http.NewRequest(http.MethodPut, s.target, bytes.NewReader(data)) + + token, err := s.signer.GenerateToken(s.runner.ID, []string{"sync"}, nil, 5*time.Minute) if err != nil { return fmt.Errorf("failed to sync to DeepSource: %w", err) } + request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) request.Header.Set("Content-Type", "application/json") response, err := s.client.Do(request) @@ -76,7 +90,11 @@ func (s *Syncer) Sync() error { } if !(response.StatusCode == http.StatusOK || response.StatusCode == http.StatusCreated) { - return fmt.Errorf("failed to sync to DeepSource: status=%d", response.StatusCode) + body, err := io.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("failed to sync to DeepSource: %w", err) + } + return fmt.Errorf("failed to sync to DeepSource: code=%d, body=%s", response.StatusCode, string(body)) } return response.Body.Close() diff --git a/sync/sync_test.go b/sync/sync_test.go index a381738..827cb23 100644 --- a/sync/sync_test.go +++ b/sync/sync_test.go @@ -1,12 +1,15 @@ package sync import ( + "crypto/rand" + "crypto/rsa" "encoding/json" "net/http" "net/http/httptest" "net/url" "testing" + "github.com/deepsourcecorp/runner/auth/jwtutil" "github.com/stretchr/testify/assert" ) @@ -29,7 +32,7 @@ func TestSyncer_Sync(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() assert.Equal(t, http.MethodPut, r.Method) - assert.Equal(t, "/api/runners/", r.URL.Path) + assert.Equal(t, "/api/runner/", r.URL.Path) payload := &Payload{} err := json.NewDecoder(r.Body).Decode(payload) @@ -51,7 +54,10 @@ func TestSyncer_Sync(t *testing.T) { Host: *deepsourceHost, } - syncer := New(deepsource, runner, apps, nil) + privateKey, _ := rsa.GenerateKey(rand.Reader, 2048) + signer := jwtutil.NewSigner(privateKey) + + syncer := New(deepsource, runner, apps, signer, nil) err := syncer.Sync() assert.NoError(t, err) server.Close()