From 56c58ee8876f9b61c041ab4c33a8e6005ce1d1c1 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Thu, 12 Sep 2024 14:05:17 -0400 Subject: [PATCH] minimal sub-process based agent implementatio (#748) --- agent/model.go | 46 +++++++++++++++++++++++---- agent/publicShare.go | 60 +++++++++++++++++++++++++++++++++++- agent/releaseShare.go | 10 ++++-- cmd/zrok/agentSharePublic.go | 1 - cmd/zrok/sharePublic.go | 32 +++++++++++++++++-- 5 files changed, 137 insertions(+), 12 deletions(-) diff --git a/agent/model.go b/agent/model.go index f2dcf093..1c3989a5 100644 --- a/agent/model.go +++ b/agent/model.go @@ -1,13 +1,18 @@ package agent import ( + "bytes" + "encoding/json" + "github.com/michaelquigley/pfxlog" "github.com/openziti/zrok/agent/agentGrpc" + "github.com/openziti/zrok/agent/proctree" "github.com/openziti/zrok/sdk/golang/sdk" "time" ) type share struct { token string + frontendEndpoints []string target string basicAuth []string frontendSelection []string @@ -21,7 +26,39 @@ type share struct { closed bool accessGrants []string - handler backendHandler + process *proctree.Child + readBuffer bytes.Buffer + ready chan struct{} +} + +func (s *share) tail(data []byte) { + s.readBuffer.Write(data) + if line, err := s.readBuffer.ReadString('\n'); err == nil { + if s.token == "" { + in := make(map[string]interface{}) + if err := json.Unmarshal([]byte(line), &in); err == nil { + if v, found := in["token"]; found { + if str, ok := v.(string); ok { + s.token = str + } + } + if v, found := in["frontend_endpoints"]; found { + if vArr, ok := v.([]interface{}); ok { + for _, v := range vArr { + if str, ok := v.(string); ok { + s.frontendEndpoints = append(s.frontendEndpoints, str) + } + } + } + } + } + close(s.ready) + } else { + pfxlog.ChannelLogger(s.token).Info(string(line)) + } + } else { + s.readBuffer.WriteString(line) + } } type access struct { @@ -29,14 +66,11 @@ type access struct { bindAddress string responseHeaders []string + + process *proctree.Child } type agentGrpcImpl struct { agentGrpc.UnimplementedAgentServer a *Agent } - -type backendHandler interface { - Run() error - Stop() error -} diff --git a/agent/publicShare.go b/agent/publicShare.go index 6bf8a18e..94c5113c 100644 --- a/agent/publicShare.go +++ b/agent/publicShare.go @@ -4,7 +4,10 @@ import ( "context" "errors" "github.com/openziti/zrok/agent/agentGrpc" + "github.com/openziti/zrok/agent/proctree" "github.com/openziti/zrok/environment" + "github.com/sirupsen/logrus" + "os" ) func (i *agentGrpcImpl) PublicShare(_ context.Context, req *agentGrpc.PublicShareRequest) (*agentGrpc.PublicShareReply, error) { @@ -17,5 +20,60 @@ func (i *agentGrpcImpl) PublicShare(_ context.Context, req *agentGrpc.PublicShar return nil, errors.New("unable to load environment; did you 'zrok enable'?") } - return &agentGrpc.PublicShareReply{}, nil + shr := &share{ready: make(chan struct{})} + shrCmd := []string{os.Args[0], "share", "public", "--agent", "-b", req.BackendMode} + + for _, basicAuth := range req.BasicAuth { + shrCmd = append(shrCmd, "--basic-auth", basicAuth) + } + shr.basicAuth = req.BasicAuth + + for _, frontendSelection := range req.FrontendSelection { + shrCmd = append(shrCmd, "--frontend", frontendSelection) + } + shr.frontendSelection = req.FrontendSelection + + if req.Insecure { + shrCmd = append(shrCmd, "--insecure") + } + shr.insecure = req.Insecure + + if req.OauthProvider != "" { + shrCmd = append(shrCmd, "--oauth-provider", req.OauthProvider) + } + shr.oauthProvider = req.OauthProvider + + for _, pattern := range req.OauthEmailAddressPatterns { + shrCmd = append(shrCmd, "--oauth-email-address-patterns", pattern) + } + shr.oauthEmailAddressPatterns = req.OauthEmailAddressPatterns + + if req.OauthCheckInterval != "" { + shrCmd = append(shrCmd, "--oauth-check-interval", req.OauthCheckInterval) + } + + if req.Closed { + shrCmd = append(shrCmd, "--closed") + } + shr.closed = req.Closed + + for _, grant := range req.AccessGrants { + shrCmd = append(shrCmd, "--access-grant", grant) + } + shr.accessGrants = req.AccessGrants + + shrCmd = append(shrCmd, req.Target) + shr.target = req.Target + + logrus.Infof("executing '%v'", shrCmd) + + shr.process, err = proctree.StartChild(shr.tail, shrCmd...) + if err != nil { + return nil, err + } + + <-shr.ready + i.a.shares[shr.token] = shr + + return &agentGrpc.PublicShareReply{Token: shr.token}, nil } diff --git a/agent/releaseShare.go b/agent/releaseShare.go index deb5af59..7dcd1d49 100755 --- a/agent/releaseShare.go +++ b/agent/releaseShare.go @@ -3,6 +3,7 @@ package agent import ( "context" "github.com/openziti/zrok/agent/agentGrpc" + "github.com/openziti/zrok/agent/proctree" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -10,8 +11,13 @@ import ( func (i *agentGrpcImpl) ReleaseShare(_ context.Context, req *agentGrpc.ReleaseShareRequest) (*agentGrpc.ReleaseShareReply, error) { if shr, found := i.a.shares[req.Token]; found { logrus.Infof("stopping share '%v'", shr.token) - if err := shr.handler.Stop(); err != nil { - logrus.Errorf("error stopping share '%v': %v", shr.token, err) + + if err := proctree.StopChild(shr.process); err != nil { + logrus.Error(err) + } + + if err := proctree.WaitChild(shr.process); err != nil { + logrus.Error(err) } delete(i.a.shares, shr.token) diff --git a/cmd/zrok/agentSharePublic.go b/cmd/zrok/agentSharePublic.go index 03da860f..ae0bbf1d 100644 --- a/cmd/zrok/agentSharePublic.go +++ b/cmd/zrok/agentSharePublic.go @@ -47,7 +47,6 @@ func newAgentSharePublicCommand() *agentSharePublicCommand { cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for ") cmd.Flags().BoolVar(&command.closed, "closed", false, "Enable closed permission mode (see --access-grant)") cmd.Flags().StringArrayVar(&command.accessGrants, "access-grant", []string{}, "zrok accounts that are allowed to access this share (see --closed)") - cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") cmd.Flags().StringVar(&command.oauthProvider, "oauth-provider", "", "Enable OAuth provider [google, github]") cmd.Flags().StringArrayVar(&command.oauthEmailAddressPatterns, "oauth-email-address-patterns", []string{}, "Allow only these email domain globs to authenticate via OAuth") diff --git a/cmd/zrok/sharePublic.go b/cmd/zrok/sharePublic.go index d66867df..ac566238 100644 --- a/cmd/zrok/sharePublic.go +++ b/cmd/zrok/sharePublic.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/gobwas/glob" @@ -29,6 +30,7 @@ type sharePublicCommand struct { frontendSelection []string backendMode string headless bool + agent bool insecure bool oauthProvider string oauthEmailAddressPatterns []string @@ -53,10 +55,11 @@ func newSharePublicCommand() *sharePublicCommand { cmd.Flags().StringArrayVar(&command.frontendSelection, "frontend", defaultFrontends, "Selected frontends to use for the share") cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, caddy, drive}") cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless") + cmd.Flags().BoolVar(&command.agent, "agent", false, "Enable agent mode") + cmd.MarkFlagsMutuallyExclusive("headless", "agent") cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for ") cmd.Flags().BoolVar(&command.closed, "closed", false, "Enable closed permission mode (see --access-grant)") cmd.Flags().StringArrayVar(&command.accessGrants, "access-grant", []string{}, "zrok accounts that are allowed to access this share (see --closed)") - cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") cmd.Flags().StringVar(&command.oauthProvider, "oauth-provider", "", "Enable OAuth provider [google, github]") cmd.Flags().StringArrayVar(&command.oauthEmailAddressPatterns, "oauth-email-address-patterns", []string{}, "Allow only these email domain globs to authenticate via OAuth") @@ -150,7 +153,7 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { } mdl := newShareModel(shr.Token, shr.FrontendEndpoints, sdk.PublicShareMode, sdk.BackendMode(cmd.backendMode)) - if !cmd.headless { + if !cmd.headless && !cmd.agent { proxy.SetCaddyLoggingWriter(mdl) } @@ -267,6 +270,31 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { } } + } else if cmd.agent { + data := make(map[string]interface{}) + data["token"] = shr.Token + data["frontend_endpoints"] = shr.FrontendEndpoints + jsonData, err := json.Marshal(data) + if err != nil { + panic(err) + } + fmt.Println(string(jsonData)) + + for { + select { + case req := <-requests: + data := make(map[string]interface{}) + data["remote-address"] = req.RemoteAddr + data["method"] = req.Method + data["path"] = req.Path + jsonData, err := json.Marshal(data) + if err != nil { + fmt.Println(err) + } + fmt.Println(string(jsonData)) + } + } + } else { logrus.SetOutput(mdl) prg := tea.NewProgram(mdl, tea.WithAltScreen())