agola/internal/services/gateway/gateway.go

368 lines
17 KiB
Go

// Copyright 2019 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.
package gateway
import (
"context"
"crypto/tls"
"io/ioutil"
"net/http"
scommon "agola.io/agola/internal/common"
"agola.io/agola/internal/objectstorage"
"agola.io/agola/internal/services/common"
"agola.io/agola/internal/services/config"
"agola.io/agola/internal/services/gateway/action"
"agola.io/agola/internal/services/gateway/api"
"agola.io/agola/internal/services/gateway/handlers"
"agola.io/agola/internal/util"
csclient "agola.io/agola/services/configstore/client"
rsclient "agola.io/agola/services/runservice/client"
"github.com/golang-jwt/jwt/v4"
ghandlers "github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
errors "golang.org/x/xerrors"
)
const (
maxRequestSize = 1024 * 1024
)
type Gateway struct {
log zerolog.Logger
c *config.Gateway
ost *objectstorage.ObjStorage
runserviceClient *rsclient.Client
configstoreClient *csclient.Client
ah *action.ActionHandler
sd *common.TokenSigningData
}
func NewGateway(ctx context.Context, log zerolog.Logger, gc *config.Config) (*Gateway, error) {
c := &gc.Gateway
if c.Debug {
log = log.Level(zerolog.DebugLevel)
}
if c.Web.ListenAddress == "" {
return nil, errors.Errorf("listen address undefined")
}
if c.Web.TLS {
if c.Web.TLSKeyFile == "" {
return nil, errors.Errorf("no tls key file specified")
}
if c.Web.TLSCertFile == "" {
return nil, errors.Errorf("no tls cert file specified")
}
}
sd := &common.TokenSigningData{Duration: c.TokenSigning.Duration}
switch c.TokenSigning.Method {
case "hmac":
sd.Method = jwt.SigningMethodHS256
if c.TokenSigning.Key == "" {
return nil, errors.Errorf("empty token signing key for hmac method")
}
sd.Key = []byte(c.TokenSigning.Key)
case "rsa":
if c.TokenSigning.PrivateKeyPath == "" {
return nil, errors.Errorf("token signing private key file for rsa method not defined")
}
if c.TokenSigning.PublicKeyPath == "" {
return nil, errors.Errorf("token signing public key file for rsa method not defined")
}
sd.Method = jwt.SigningMethodRS256
privateKeyData, err := ioutil.ReadFile(c.TokenSigning.PrivateKeyPath)
if err != nil {
return nil, errors.Errorf("error reading token signing private key: %w", err)
}
sd.PrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(privateKeyData)
if err != nil {
return nil, errors.Errorf("error parsing token signing private key: %w", err)
}
publicKeyData, err := ioutil.ReadFile(c.TokenSigning.PublicKeyPath)
if err != nil {
return nil, errors.Errorf("error reading token signing public key: %w", err)
}
sd.PublicKey, err = jwt.ParseRSAPublicKeyFromPEM(publicKeyData)
if err != nil {
return nil, errors.Errorf("error parsing token signing public key: %w", err)
}
case "":
return nil, errors.Errorf("missing token signing method")
default:
return nil, errors.Errorf("unknown token signing method: %q", c.TokenSigning.Method)
}
ost, err := scommon.NewObjectStorage(&c.ObjectStorage)
if err != nil {
return nil, err
}
configstoreClient := csclient.NewClient(c.ConfigstoreURL)
runserviceClient := rsclient.NewClient(c.RunserviceURL)
ah := action.NewActionHandler(log, sd, configstoreClient, runserviceClient, gc.ID, c.APIExposedURL, c.WebExposedURL)
return &Gateway{
log: log,
c: c,
ost: ost,
runserviceClient: runserviceClient,
configstoreClient: configstoreClient,
ah: ah,
sd: sd,
}, nil
}
func (g *Gateway) Run(ctx context.Context) error {
// noop coors handler
corsHandler := func(h http.Handler) http.Handler {
return h
}
if len(g.c.Web.AllowedOrigins) > 0 {
corsAllowedMethodsOptions := ghandlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE"})
corsAllowedHeadersOptions := ghandlers.AllowedHeaders([]string{"Accept", "Accept-Encoding", "Authorization", "Content-Length", "Content-Type", "X-CSRF-Token", "Authorization"})
corsAllowedOriginsOptions := ghandlers.AllowedOrigins(g.c.Web.AllowedOrigins)
corsHandler = ghandlers.CORS(corsAllowedMethodsOptions, corsAllowedHeadersOptions, corsAllowedOriginsOptions)
}
webhooksHandler := api.NewWebhooksHandler(g.log, g.ah, g.configstoreClient, g.runserviceClient, g.c.APIExposedURL)
projectGroupHandler := api.NewProjectGroupHandler(g.log, g.ah)
projectGroupSubgroupsHandler := api.NewProjectGroupSubgroupsHandler(g.log, g.ah)
projectGroupProjectsHandler := api.NewProjectGroupProjectsHandler(g.log, g.ah)
createProjectGroupHandler := api.NewCreateProjectGroupHandler(g.log, g.ah)
updateProjectGroupHandler := api.NewUpdateProjectGroupHandler(g.log, g.ah)
deleteProjectGroupHandler := api.NewDeleteProjectGroupHandler(g.log, g.ah)
projectHandler := api.NewProjectHandler(g.log, g.ah)
createProjectHandler := api.NewCreateProjectHandler(g.log, g.ah)
updateProjectHandler := api.NewUpdateProjectHandler(g.log, g.ah)
deleteProjectHandler := api.NewDeleteProjectHandler(g.log, g.ah)
projectReconfigHandler := api.NewProjectReconfigHandler(g.log, g.ah)
projectUpdateRepoLinkedAccountHandler := api.NewProjectUpdateRepoLinkedAccountHandler(g.log, g.ah)
projectCreateRunHandler := api.NewProjectCreateRunHandler(g.log, g.ah)
secretHandler := api.NewSecretHandler(g.log, g.ah)
createSecretHandler := api.NewCreateSecretHandler(g.log, g.ah)
updateSecretHandler := api.NewUpdateSecretHandler(g.log, g.ah)
deleteSecretHandler := api.NewDeleteSecretHandler(g.log, g.ah)
variableHandler := api.NewVariableHandler(g.log, g.ah)
createVariableHandler := api.NewCreateVariableHandler(g.log, g.ah)
updateVariableHandler := api.NewUpdateVariableHandler(g.log, g.ah)
deleteVariableHandler := api.NewDeleteVariableHandler(g.log, g.ah)
currentUserHandler := api.NewCurrentUserHandler(g.log, g.ah)
userHandler := api.NewUserHandler(g.log, g.ah)
usersHandler := api.NewUsersHandler(g.log, g.ah)
createUserHandler := api.NewCreateUserHandler(g.log, g.ah)
deleteUserHandler := api.NewDeleteUserHandler(g.log, g.ah)
userCreateRunHandler := api.NewUserCreateRunHandler(g.log, g.ah)
userOrgsHandler := api.NewUserOrgsHandler(g.log, g.ah)
createUserLAHandler := api.NewCreateUserLAHandler(g.log, g.ah)
deleteUserLAHandler := api.NewDeleteUserLAHandler(g.log, g.ah)
createUserTokenHandler := api.NewCreateUserTokenHandler(g.log, g.ah)
deleteUserTokenHandler := api.NewDeleteUserTokenHandler(g.log, g.ah)
remoteSourceHandler := api.NewRemoteSourceHandler(g.log, g.ah)
createRemoteSourceHandler := api.NewCreateRemoteSourceHandler(g.log, g.ah)
updateRemoteSourceHandler := api.NewUpdateRemoteSourceHandler(g.log, g.ah)
remoteSourcesHandler := api.NewRemoteSourcesHandler(g.log, g.ah)
deleteRemoteSourceHandler := api.NewDeleteRemoteSourceHandler(g.log, g.ah)
orgHandler := api.NewOrgHandler(g.log, g.ah)
orgsHandler := api.NewOrgsHandler(g.log, g.ah)
createOrgHandler := api.NewCreateOrgHandler(g.log, g.ah)
deleteOrgHandler := api.NewDeleteOrgHandler(g.log, g.ah)
orgMembersHandler := api.NewOrgMembersHandler(g.log, g.ah)
addOrgMemberHandler := api.NewAddOrgMemberHandler(g.log, g.ah)
removeOrgMemberHandler := api.NewRemoveOrgMemberHandler(g.log, g.ah)
runHandler := api.NewRunHandler(g.log, g.ah)
runsHandler := api.NewRunsHandler(g.log, g.ah)
runtaskHandler := api.NewRuntaskHandler(g.log, g.ah)
runActionsHandler := api.NewRunActionsHandler(g.log, g.ah)
runTaskActionsHandler := api.NewRunTaskActionsHandler(g.log, g.ah)
logsHandler := api.NewLogsHandler(g.log, g.ah)
logsDeleteHandler := api.NewLogsDeleteHandler(g.log, g.ah)
userRemoteReposHandler := api.NewUserRemoteReposHandler(g.log, g.ah, g.configstoreClient)
badgeHandler := api.NewBadgeHandler(g.log, g.ah)
versionHandler := api.NewVersionHandler(g.log, g.ah)
reposHandler := api.NewReposHandler(g.log, g.c.GitserverURL)
loginUserHandler := api.NewLoginUserHandler(g.log, g.ah)
authorizeHandler := api.NewAuthorizeHandler(g.log, g.ah)
registerHandler := api.NewRegisterUserHandler(g.log, g.ah)
oauth2callbackHandler := api.NewOAuth2CallbackHandler(g.log, g.ah)
router := mux.NewRouter()
reposRouter := mux.NewRouter()
apirouter := mux.NewRouter().PathPrefix("/api/v1alpha").Subrouter().UseEncodedPath()
authForcedHandler := handlers.NewAuthHandler(g.log, g.configstoreClient, g.c.AdminToken, g.sd, true)
authOptionalHandler := handlers.NewAuthHandler(g.log, g.configstoreClient, g.c.AdminToken, g.sd, false)
router.PathPrefix("/api/v1alpha").Handler(apirouter)
apirouter.Handle("/logs", authOptionalHandler(logsHandler)).Methods("GET")
apirouter.Handle("/logs", authForcedHandler(logsDeleteHandler)).Methods("DELETE")
//apirouter.Handle("/projectgroups", authForcedHandler(projectsHandler)).Methods("GET")
apirouter.Handle("/projectgroups/{projectgroupref}", authForcedHandler(projectGroupHandler)).Methods("GET")
apirouter.Handle("/projectgroups/{projectgroupref}/subgroups", authForcedHandler(projectGroupSubgroupsHandler)).Methods("GET")
apirouter.Handle("/projectgroups/{projectgroupref}/projects", authForcedHandler(projectGroupProjectsHandler)).Methods("GET")
apirouter.Handle("/projectgroups", authForcedHandler(createProjectGroupHandler)).Methods("POST")
apirouter.Handle("/projectgroups/{projectgroupref}", authForcedHandler(updateProjectGroupHandler)).Methods("PUT")
apirouter.Handle("/projectgroups/{projectgroupref}", authForcedHandler(deleteProjectGroupHandler)).Methods("DELETE")
apirouter.Handle("/projects/{projectref}", authOptionalHandler(projectHandler)).Methods("GET")
apirouter.Handle("/projects", authForcedHandler(createProjectHandler)).Methods("POST")
apirouter.Handle("/projects/{projectref}", authForcedHandler(updateProjectHandler)).Methods("PUT")
apirouter.Handle("/projects/{projectref}", authForcedHandler(deleteProjectHandler)).Methods("DELETE")
apirouter.Handle("/projects/{projectref}/reconfig", authForcedHandler(projectReconfigHandler)).Methods("PUT")
apirouter.Handle("/projects/{projectref}/updaterepolinkedaccount", authForcedHandler(projectUpdateRepoLinkedAccountHandler)).Methods("PUT")
apirouter.Handle("/projects/{projectref}/createrun", authForcedHandler(projectCreateRunHandler)).Methods("POST")
apirouter.Handle("/projectgroups/{projectgroupref}/secrets", authForcedHandler(secretHandler)).Methods("GET")
apirouter.Handle("/projects/{projectref}/secrets", authForcedHandler(secretHandler)).Methods("GET")
apirouter.Handle("/projectgroups/{projectgroupref}/secrets", authForcedHandler(createSecretHandler)).Methods("POST")
apirouter.Handle("/projects/{projectref}/secrets", authForcedHandler(createSecretHandler)).Methods("POST")
apirouter.Handle("/projectgroups/{projectgroupref}/secrets/{secretname}", authForcedHandler(updateSecretHandler)).Methods("PUT")
apirouter.Handle("/projects/{projectref}/secrets/{secretname}", authForcedHandler(updateSecretHandler)).Methods("PUT")
apirouter.Handle("/projectgroups/{projectgroupref}/secrets/{secretname}", authForcedHandler(deleteSecretHandler)).Methods("DELETE")
apirouter.Handle("/projects/{projectref}/secrets/{secretname}", authForcedHandler(deleteSecretHandler)).Methods("DELETE")
apirouter.Handle("/projectgroups/{projectgroupref}/variables", authForcedHandler(variableHandler)).Methods("GET")
apirouter.Handle("/projects/{projectref}/variables", authForcedHandler(variableHandler)).Methods("GET")
apirouter.Handle("/projectgroups/{projectgroupref}/variables", authForcedHandler(createVariableHandler)).Methods("POST")
apirouter.Handle("/projects/{projectref}/variables", authForcedHandler(createVariableHandler)).Methods("POST")
apirouter.Handle("/projectgroups/{projectgroupref}/variables/{variablename}", authForcedHandler(updateVariableHandler)).Methods("PUT")
apirouter.Handle("/projects/{projectref}/variables/{variablename}", authForcedHandler(updateVariableHandler)).Methods("PUT")
apirouter.Handle("/projectgroups/{projectgroupref}/variables/{variablename}", authForcedHandler(deleteVariableHandler)).Methods("DELETE")
apirouter.Handle("/projects/{projectref}/variables/{variablename}", authForcedHandler(deleteVariableHandler)).Methods("DELETE")
apirouter.Handle("/user", authForcedHandler(currentUserHandler)).Methods("GET")
apirouter.Handle("/users/{userref}", authForcedHandler(userHandler)).Methods("GET")
apirouter.Handle("/users", authForcedHandler(usersHandler)).Methods("GET")
apirouter.Handle("/users", authForcedHandler(createUserHandler)).Methods("POST")
apirouter.Handle("/users/{userref}", authForcedHandler(deleteUserHandler)).Methods("DELETE")
apirouter.Handle("/user/createrun", authForcedHandler(userCreateRunHandler)).Methods("POST")
apirouter.Handle("/user/orgs", authForcedHandler(userOrgsHandler)).Methods("GET")
apirouter.Handle("/users/{userref}/linkedaccounts", authForcedHandler(createUserLAHandler)).Methods("POST")
apirouter.Handle("/users/{userref}/linkedaccounts/{laid}", authForcedHandler(deleteUserLAHandler)).Methods("DELETE")
apirouter.Handle("/users/{userref}/tokens", authForcedHandler(createUserTokenHandler)).Methods("POST")
apirouter.Handle("/users/{userref}/tokens/{tokenname}", authForcedHandler(deleteUserTokenHandler)).Methods("DELETE")
apirouter.Handle("/remotesources/{remotesourceref}", authForcedHandler(remoteSourceHandler)).Methods("GET")
apirouter.Handle("/remotesources", authForcedHandler(createRemoteSourceHandler)).Methods("POST")
apirouter.Handle("/remotesources/{remotesourceref}", authForcedHandler(updateRemoteSourceHandler)).Methods("PUT")
apirouter.Handle("/remotesources", authOptionalHandler(remoteSourcesHandler)).Methods("GET")
apirouter.Handle("/remotesources/{remotesourceref}", authForcedHandler(deleteRemoteSourceHandler)).Methods("DELETE")
apirouter.Handle("/orgs/{orgref}", authForcedHandler(orgHandler)).Methods("GET")
apirouter.Handle("/orgs", authForcedHandler(orgsHandler)).Methods("GET")
apirouter.Handle("/orgs", authForcedHandler(createOrgHandler)).Methods("POST")
apirouter.Handle("/orgs/{orgref}", authForcedHandler(deleteOrgHandler)).Methods("DELETE")
apirouter.Handle("/orgs/{orgref}/members", authForcedHandler(orgMembersHandler)).Methods("GET")
apirouter.Handle("/orgs/{orgref}/members/{userref}", authForcedHandler(addOrgMemberHandler)).Methods("PUT")
apirouter.Handle("/orgs/{orgref}/members/{userref}", authForcedHandler(removeOrgMemberHandler)).Methods("DELETE")
apirouter.Handle("/runs/{runid}", authOptionalHandler(runHandler)).Methods("GET")
apirouter.Handle("/runs/{runid}/actions", authForcedHandler(runActionsHandler)).Methods("PUT")
apirouter.Handle("/runs/{runid}/tasks/{taskid}", authOptionalHandler(runtaskHandler)).Methods("GET")
apirouter.Handle("/runs/{runid}/tasks/{taskid}/actions", authForcedHandler(runTaskActionsHandler)).Methods("PUT")
apirouter.Handle("/runs", authForcedHandler(runsHandler)).Methods("GET")
apirouter.Handle("/user/remoterepos/{remotesourceref}", authForcedHandler(userRemoteReposHandler)).Methods("GET")
apirouter.Handle("/badges/{projectref}", badgeHandler).Methods("GET")
apirouter.Handle("/version", versionHandler).Methods("GET")
apirouter.Handle("/auth/login", loginUserHandler).Methods("POST")
apirouter.Handle("/auth/authorize", authorizeHandler).Methods("POST")
apirouter.Handle("/auth/register", registerHandler).Methods("POST")
apirouter.Handle("/auth/oauth2/callback", oauth2callbackHandler).Methods("GET")
// TODO(sgotti) add auth to these requests
reposRouter.Handle("/repos/{rest:.*}", reposHandler).Methods("GET", "POST")
router.Handle("/webhooks", webhooksHandler).Methods("POST")
router.PathPrefix("/").HandlerFunc(handlers.NewWebBundleHandlerFunc(g.c.APIExposedURL))
maxBytesHandler := handlers.NewMaxBytesHandler(router, maxRequestSize)
mainrouter := mux.NewRouter()
mainrouter.PathPrefix("/repos/").Handler(corsHandler(reposRouter))
mainrouter.PathPrefix("/").Handler(corsHandler(maxBytesHandler))
var tlsConfig *tls.Config
if g.c.Web.TLS {
var err error
tlsConfig, err = util.NewTLSConfig(g.c.Web.TLSCertFile, g.c.Web.TLSKeyFile, "", false)
if err != nil {
g.log.Err(err).Send()
return err
}
}
httpServer := http.Server{
Addr: g.c.Web.ListenAddress,
Handler: mainrouter,
TLSConfig: tlsConfig,
}
lerrCh := make(chan error)
go func() {
if !g.c.Web.TLS {
lerrCh <- httpServer.ListenAndServe()
} else {
lerrCh <- httpServer.ListenAndServeTLS("", "")
}
}()
select {
case <-ctx.Done():
log.Info().Msgf("configstore exiting")
httpServer.Close()
case err := <-lerrCh:
if err != nil {
log.Err(err).Msgf("http server listen error")
return err
}
}
return nil
}