267 lines
7.4 KiB
Go
267 lines
7.4 KiB
Go
//
|
|
// Copyright (c) 2012-2018 Red Hat, Inc.
|
|
// This program and the accompanying materials are made
|
|
// available under the terms of the Eclipse Public License 2.0
|
|
// which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
//
|
|
// SPDX-License-Identifier: EPL-2.0
|
|
//
|
|
// Contributors:
|
|
// Red Hat, Inc. - initial API and implementation
|
|
//
|
|
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/eclipse/che/agents/go-agents/core/auth"
|
|
"github.com/eclipse/che/agents/go-agents/core/jsonrpc"
|
|
"github.com/eclipse/che/agents/go-agents/core/jsonrpc/jsonrpcws"
|
|
"github.com/eclipse/che/agents/go-agents/core/process"
|
|
"github.com/eclipse/che/agents/go-agents/core/rest"
|
|
"github.com/eclipse/che/agents/go-agents/core/rest/restutil"
|
|
"github.com/eclipse/che/agents/go-agents/exec-agent/exec"
|
|
)
|
|
|
|
var (
|
|
config = &execAgentConfig{}
|
|
)
|
|
|
|
func init() {
|
|
config.init()
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
log.SetOutput(os.Stdout)
|
|
|
|
config.printAll()
|
|
|
|
process.SetLogsDir(config.processLogsDir)
|
|
process.SetShellInterpreter(config.processShellInterpreter)
|
|
|
|
// remove old logs
|
|
if err := process.WipeLogs(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// start cleaner routine
|
|
if config.processCleanupPeriodInMinutes > 0 {
|
|
if config.processCleanupThresholdInMinutes < 0 {
|
|
log.Fatal("Expected process cleanup threshold to be non negative value")
|
|
}
|
|
cleaner := process.NewCleaner(config.processCleanupPeriodInMinutes, config.processCleanupThresholdInMinutes)
|
|
go cleaner.CleanPeriodically()
|
|
}
|
|
|
|
appHTTPRoutes := []rest.RoutesGroup{
|
|
exec.HTTPRoutes,
|
|
{
|
|
Name: "Exec-Agent WebSocket routes",
|
|
Items: []rest.Route{
|
|
{
|
|
Method: "GET",
|
|
Path: "/connect",
|
|
Name: "Connect to Exec-Agent(websocket)",
|
|
HandleFunc: func(w http.ResponseWriter, r *http.Request, _ rest.Params) error {
|
|
conn, err := jsonrpcws.Upgrade(w, r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tunnel := jsonrpc.NewManagedTunnel(conn)
|
|
tunnel.SayHello()
|
|
return nil
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Exec-Agent liveness route",
|
|
Items: []rest.Route{
|
|
{
|
|
Method: "GET",
|
|
Path: "/liveness",
|
|
Name: "Check Exec-Agent liveness",
|
|
HandleFunc: restutil.OKRespondingFunc,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
appOpRoutes := []jsonrpc.RoutesGroup{
|
|
exec.RPCRoutes,
|
|
}
|
|
|
|
// register routes and http handlers
|
|
r := rest.NewDefaultRouter(config.basePath, appHTTPRoutes)
|
|
rest.PrintRoutes(appHTTPRoutes)
|
|
jsonrpc.RegRoutesGroups(appOpRoutes)
|
|
jsonrpc.PrintRoutes(appOpRoutes)
|
|
|
|
// do not protect liveness check endpoint
|
|
var handler = wrapWithAuth(r, "/liveness")
|
|
|
|
server := &http.Server{
|
|
Handler: handler,
|
|
Addr: config.serverAddress,
|
|
WriteTimeout: 10 * time.Second,
|
|
ReadTimeout: 10 * time.Second,
|
|
}
|
|
log.Fatal(server.ListenAndServe())
|
|
}
|
|
|
|
func wrapWithAuth(h http.Handler, ignoreMapping string) http.Handler {
|
|
// required authentication for all the requests that match mappings, if auth is configured
|
|
if !config.authEnabled {
|
|
return h
|
|
}
|
|
|
|
ignorePattern := regexp.MustCompile(ignoreMapping)
|
|
cache := auth.NewCache(time.Minute*time.Duration(config.tokensExpirationTimeoutInMinutes), time.Minute*5)
|
|
return auth.NewCachingHandler(h, config.apiEndpoint, droppingRPCChannelsUnauthorizedHandler, cache, ignorePattern)
|
|
}
|
|
|
|
func droppingRPCChannelsUnauthorizedHandler(w http.ResponseWriter, req *http.Request, err error) {
|
|
token := req.URL.Query().Get("token")
|
|
for _, tun := range jsonrpc.GetTunnels() {
|
|
if wsTun, ok := tun.Conn().(*jsonrpcws.NativeConnAdapter); ok {
|
|
if u, err1 := url.ParseRequestURI(wsTun.RequestURI); err1 != nil {
|
|
log.Printf("Couldn't parse the RequestURI '%s' of channel '%s'", wsTun.RequestURI, tun.ID())
|
|
} else if u.Query().Get("token") == token {
|
|
log.Printf("Token for channel '%s' is expired, trying to drop the channel", tun.ID())
|
|
if dropped, ok := jsonrpc.Rm(tun.ID()); ok {
|
|
dropped.Close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
http.Error(w, err.Error(), http.StatusUnauthorized)
|
|
}
|
|
|
|
type execAgentConfig struct {
|
|
serverAddress string
|
|
basePath string
|
|
apiEndpoint string
|
|
|
|
authEnabled bool
|
|
tokensExpirationTimeoutInMinutes uint
|
|
|
|
processShellInterpreter string
|
|
processLogsDir string
|
|
processCleanupThresholdInMinutes int
|
|
processCleanupPeriodInMinutes int
|
|
}
|
|
|
|
func (cfg *execAgentConfig) init() {
|
|
// server configuration
|
|
flag.StringVar(
|
|
&cfg.serverAddress,
|
|
"addr",
|
|
":9000",
|
|
"IP:PORT or :PORT the address to start the server on",
|
|
)
|
|
flag.StringVar(
|
|
&cfg.basePath,
|
|
"path",
|
|
"",
|
|
`the base path for all the rpc & rest routes, so route paths are treated not
|
|
as 'server_address + route_path' but 'server_address + path + route_path'.
|
|
For example for the server address 'localhost:9000', route path '/connect' and
|
|
configured path '/api/' exec-agent server will serve the following route:
|
|
'localhost:9000/api/connect'.
|
|
Regexp syntax is supported`,
|
|
)
|
|
|
|
// workspace master server configuration
|
|
flag.StringVar(
|
|
&cfg.apiEndpoint,
|
|
"api-endpoint",
|
|
os.Getenv("CHE_API"),
|
|
`api-endpoint used by exec-agent modules(such as authentication)
|
|
to request workspace master. By default the value from 'CHE_API' environment variable is used`,
|
|
)
|
|
|
|
// auth configuration
|
|
defaultAuthEnabled := false
|
|
authEnabledEnv := os.Getenv("CHE_AUTH_ENABLED")
|
|
b, e := strconv.ParseBool(authEnabledEnv)
|
|
if e == nil {
|
|
defaultAuthEnabled = b
|
|
}
|
|
flag.BoolVar(
|
|
&cfg.authEnabled,
|
|
"enable-auth",
|
|
defaultAuthEnabled,
|
|
"whether authenticate requests on workspace master before allowing them to proceed."+
|
|
"By default the value from 'CHE_AUTH_ENABLED' environment variable is used or `false` if it is missing",
|
|
)
|
|
flag.UintVar(
|
|
&cfg.tokensExpirationTimeoutInMinutes,
|
|
"tokens-expiration-timeout",
|
|
auth.DefaultTokensExpirationTimeoutInMinutes,
|
|
"how much time machine tokens stay in cache(if auth is enabled)",
|
|
)
|
|
|
|
// process executor configuration
|
|
flag.StringVar(
|
|
&cfg.processShellInterpreter,
|
|
"cmd",
|
|
process.DefaultShellInterpreter,
|
|
"shell interpreter",
|
|
)
|
|
flag.IntVar(
|
|
&cfg.processCleanupPeriodInMinutes,
|
|
"process-cleanup-period",
|
|
-1,
|
|
"how often processs cleanup job will be executed(in minutes)",
|
|
)
|
|
flag.IntVar(&cfg.processCleanupThresholdInMinutes,
|
|
"process-cleanup-threshold",
|
|
-1,
|
|
`how much time will dead and unused process stay(in minutes),
|
|
if -1 passed then processes won't be cleaned at all. Please note that the time
|
|
of real cleanup is between configured threshold and threshold + process-cleanup-period.`,
|
|
)
|
|
curDir, err := os.Getwd()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
curDir += string(os.PathSeparator) + "logs"
|
|
flag.StringVar(
|
|
&cfg.processLogsDir,
|
|
"logs-dir",
|
|
curDir,
|
|
"base directory for process logs",
|
|
)
|
|
}
|
|
|
|
func (cfg *execAgentConfig) printAll() {
|
|
log.Println("Exec-agent configuration")
|
|
log.Println(" Server")
|
|
log.Printf(" - Address: %s\n", cfg.serverAddress)
|
|
log.Printf(" - Base path: '%s'\n", cfg.basePath)
|
|
if cfg.authEnabled {
|
|
log.Println(" Authentication")
|
|
log.Printf(" - Enabled: %t\n", cfg.authEnabled)
|
|
log.Printf(" - Tokens expiration timeout: %dm\n", cfg.tokensExpirationTimeoutInMinutes)
|
|
log.Println(" Workspace master server")
|
|
log.Printf(" - API endpoint: %s\n", cfg.apiEndpoint)
|
|
}
|
|
log.Println(" Process executor")
|
|
log.Printf(" - Logs dir: %s\n", cfg.processLogsDir)
|
|
if cfg.processCleanupPeriodInMinutes > 0 {
|
|
log.Printf(" - Cleanup job period: %dm\n", cfg.processCleanupPeriodInMinutes)
|
|
log.Printf(" - Not used & dead processes stay for: %dm\n", cfg.processCleanupThresholdInMinutes)
|
|
}
|
|
log.Println()
|
|
}
|