Merge pull request #739 from openziti/daemon_mode

zrok Agent (#463)
This commit is contained in:
Michael Quigley 2024-09-18 13:19:30 -04:00 committed by GitHub
commit 5af4aa6a8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 3944 additions and 30 deletions

View File

@ -4,6 +4,8 @@
MAJOR RELEASE: zrok reaches version 1.0.0!
FEATURE: New "zrok Agent", a background manager process for your zrok environments, which allows you to easily manage and work with multiple `zrok share` and `zrok access` processes (https://github.com/openziti/zrok/issues/463)
## v0.4.41
FIX: Fixed crash when invoking `zrok share reserved` with no arguments (https://github.com/openziti/zrok/issues/740)

76
agent/access.go Normal file
View File

@ -0,0 +1,76 @@
package agent
import (
"bytes"
"encoding/json"
"errors"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/zrok/agent/proctree"
"github.com/sirupsen/logrus"
"strings"
)
type access struct {
frontendToken string
token string
bindAddress string
responseHeaders []string
process *proctree.Child
readBuffer bytes.Buffer
booted bool
bootComplete chan struct{}
bootErr error
a *Agent
}
func (a *access) monitor() {
if err := proctree.WaitChild(a.process); err != nil {
pfxlog.ChannelLogger(a.token).Error(err)
}
a.a.outAccesses <- a
}
func (a *access) tail(data []byte) {
defer func() {
if r := recover(); r != nil {
logrus.Errorf("recovering: %v", r)
}
}()
a.readBuffer.Write(data)
if line, err := a.readBuffer.ReadString('\n'); err == nil {
line = strings.Trim(line, "\n")
if !a.booted {
in := make(map[string]interface{})
if err := json.Unmarshal([]byte(line), &in); err == nil {
if v, found := in["frontend_token"]; found {
if str, ok := v.(string); ok {
a.frontendToken = str
}
}
if v, found := in["bind_address"]; found {
if str, ok := v.(string); ok {
a.bindAddress = str
}
}
a.booted = true
} else {
a.bootErr = errors.New(line)
}
close(a.bootComplete)
} else {
if strings.HasPrefix(line, "{") {
in := make(map[string]interface{})
if err := json.Unmarshal([]byte(line), &in); err == nil {
pfxlog.ChannelLogger(a.token).Info(in)
}
} else {
pfxlog.ChannelLogger(a.token).Info(strings.Trim(line, "\n"))
}
}
} else {
a.readBuffer.WriteString(line)
}
}

48
agent/accessPrivate.go Normal file
View File

@ -0,0 +1,48 @@
package agent
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) AccessPrivate(_ context.Context, req *agentGrpc.AccessPrivateRequest) (*agentGrpc.AccessPrivateResponse, error) {
root, err := environment.LoadRoot()
if err != nil {
return nil, err
}
if !root.IsEnabled() {
return nil, errors.New("unable to load environment; did you 'zrok enable'?")
}
accCmd := []string{os.Args[0], "access", "private", "--agent", "-b", req.BindAddress, req.Token}
acc := &access{
token: req.Token,
bindAddress: req.BindAddress,
responseHeaders: req.ResponseHeaders,
bootComplete: make(chan struct{}),
a: i.a,
}
logrus.Infof("executing '%v'", accCmd)
acc.process, err = proctree.StartChild(acc.tail, accCmd...)
if err != nil {
return nil, err
}
go acc.monitor()
<-acc.bootComplete
if acc.bootErr == nil {
i.a.inAccesses <- acc
return &agentGrpc.AccessPrivateResponse{FrontendToken: acc.frontendToken}, nil
}
return nil, acc.bootErr
}

131
agent/agent.go Normal file
View File

@ -0,0 +1,131 @@
package agent
import (
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/agent/proctree"
"github.com/openziti/zrok/environment/env_core"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"net"
"os"
)
type Agent struct {
root env_core.Root
agentSocket string
shares map[string]*share
inShares chan *share
outShares chan *share
accesses map[string]*access
inAccesses chan *access
outAccesses chan *access
}
func NewAgent(root env_core.Root) (*Agent, error) {
if !root.IsEnabled() {
return nil, errors.Errorf("unable to load environment; did you 'zrok enable'?")
}
return &Agent{
root: root,
shares: make(map[string]*share),
inShares: make(chan *share),
outShares: make(chan *share),
accesses: make(map[string]*access),
inAccesses: make(chan *access),
outAccesses: make(chan *access),
}, nil
}
func (a *Agent) Run() error {
logrus.Infof("started")
if err := proctree.Init("zrok Agent"); err != nil {
return err
}
go a.manager()
agentSocket, err := a.root.AgentSocket()
if err != nil {
return err
}
l, err := net.Listen("unix", agentSocket)
if err != nil {
return err
}
a.agentSocket = agentSocket
srv := grpc.NewServer()
agentGrpc.RegisterAgentServer(srv, &agentGrpcImpl{a: a})
if err := srv.Serve(l); err != nil {
return err
}
return nil
}
func (a *Agent) Shutdown() {
logrus.Infof("stopping")
if err := os.Remove(a.agentSocket); err != nil {
logrus.Warnf("unable to remove agent socket: %v", err)
}
for _, shr := range a.shares {
logrus.Debugf("stopping share '%v'", shr.token)
a.outShares <- shr
}
for _, acc := range a.accesses {
logrus.Debugf("stopping access '%v'", acc.token)
a.outAccesses <- acc
}
}
func (a *Agent) manager() {
logrus.Info("started")
defer logrus.Warn("exited")
for {
select {
case inShare := <-a.inShares:
logrus.Infof("adding new share '%v'", inShare.token)
a.shares[inShare.token] = inShare
case outShare := <-a.outShares:
if outShare.token != "" {
logrus.Infof("removing share '%v'", outShare.token)
if err := proctree.StopChild(outShare.process); err != nil {
logrus.Errorf("error stopping share '%v': %v", outShare.token, err)
}
if err := proctree.WaitChild(outShare.process); err != nil {
logrus.Errorf("error joining share '%v': %v", outShare.token, err)
}
delete(a.shares, outShare.token)
} else {
logrus.Debug("skipping unidentified (orphaned) share removal")
}
case inAccess := <-a.inAccesses:
logrus.Infof("adding new access '%v'", inAccess.frontendToken)
a.accesses[inAccess.frontendToken] = inAccess
case outAccess := <-a.outAccesses:
if outAccess.frontendToken != "" {
logrus.Infof("removing access '%v'", outAccess.frontendToken)
if err := proctree.StopChild(outAccess.process); err != nil {
logrus.Errorf("error stopping access '%v': %v", outAccess.frontendToken, err)
}
if err := proctree.WaitChild(outAccess.process); err != nil {
logrus.Errorf("error joining access '%v': %v", outAccess.frontendToken, err)
}
delete(a.accesses, outAccess.frontendToken)
} else {
logrus.Debug("skipping unidentified (orphaned) access removal")
}
}
}
}
type agentGrpcImpl struct {
agentGrpc.UnimplementedAgentServer
a *Agent
}

View File

@ -0,0 +1,33 @@
package agentClient
import (
"context"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/environment/env_core"
"github.com/openziti/zrok/tui"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"net"
)
func NewClient(root env_core.Root) (client agentGrpc.AgentClient, conn *grpc.ClientConn, err error) {
agentSocket, err := root.AgentSocket()
if err != nil {
tui.Error("error getting agent socket", err)
}
opts := []grpc.DialOption{
grpc.WithContextDialer(func(_ context.Context, addr string) (net.Conn, error) {
return net.Dial("unix", addr)
}),
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
resolver.SetDefaultScheme("passthrough")
conn, err = grpc.NewClient(agentSocket, opts...)
if err != nil {
tui.Error("error connecting to agent socket", err)
}
return agentGrpc.NewAgentClient(conn), conn, nil
}

1558
agent/agentGrpc/agent.pb.go Normal file

File diff suppressed because it is too large Load Diff

115
agent/agentGrpc/agent.proto Normal file
View File

@ -0,0 +1,115 @@
syntax = "proto3";
option go_package = "github.com/openziti/zrok/agent/agentGrpc";
service Agent {
rpc AccessPrivate(AccessPrivateRequest) returns (AccessPrivateResponse) {}
rpc ReleaseAccess(ReleaseAccessRequest) returns (ReleaseAccessResponse) {}
rpc ReleaseShare(ReleaseShareRequest) returns (ReleaseShareResponse) {}
rpc ShareReserved(ShareReservedRequest) returns (ShareReservedResponse) {}
rpc SharePrivate(SharePrivateRequest) returns (SharePrivateResponse) {}
rpc SharePublic(SharePublicRequest) returns (SharePublicResponse) {}
rpc Status(StatusRequest) returns (StatusResponse) {}
rpc Version(VersionRequest) returns (VersionResponse) {}
}
message AccessDetail {
string frontendToken = 1;
string token = 2;
string bindAddress = 3;
repeated string responseHeaders = 4;
}
message AccessPrivateResponse{
string frontendToken = 1;
}
message AccessPrivateRequest{
string token = 1;
string bindAddress = 2;
repeated string responseHeaders = 3;
}
message ReleaseAccessRequest {
string frontendToken = 1;
}
message ReleaseAccessResponse {
}
message ReleaseShareRequest {
string token = 1;
}
message ReleaseShareResponse {
}
message ShareDetail {
string token = 1;
string shareMode = 2;
string backendMode = 3;
bool reserved = 4;
repeated string frontendEndpoint = 5;
string backendEndpoint = 6;
bool closed = 7;
string status = 8;
}
message SharePrivateRequest {
string target = 1;
string backendMode = 2;
bool insecure = 3;
bool closed = 4;
repeated string accessGrants = 5;
}
message SharePrivateResponse {
string token = 1;
}
message SharePublicRequest {
string target = 1;
repeated string basicAuth = 2;
repeated string frontendSelection = 3;
string backendMode = 4;
bool insecure = 5;
string oauthProvider = 6;
repeated string oauthEmailAddressPatterns = 7;
string oauthCheckInterval = 8;
bool closed = 9;
repeated string accessGrants = 10;
}
message SharePublicResponse {
string token = 1;
repeated string frontendEndpoints = 2;
}
message ShareReservedRequest {
string token = 1;
string overrideEndpoint = 2;
bool insecure = 3;
}
message ShareReservedResponse {
string token = 1;
string backendMode = 2;
string shareMode = 3;
repeated string frontendEndpoints = 4;
string target = 5;
}
message StatusRequest {
}
message StatusResponse {
repeated AccessDetail accesses = 1;
repeated ShareDetail shares = 2;
}
message VersionRequest {
}
message VersionResponse {
string v = 1;
}

View File

@ -0,0 +1,387 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.27.3
// source: agent/agentGrpc/agent.proto
package agentGrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Agent_AccessPrivate_FullMethodName = "/Agent/AccessPrivate"
Agent_ReleaseAccess_FullMethodName = "/Agent/ReleaseAccess"
Agent_ReleaseShare_FullMethodName = "/Agent/ReleaseShare"
Agent_ShareReserved_FullMethodName = "/Agent/ShareReserved"
Agent_SharePrivate_FullMethodName = "/Agent/SharePrivate"
Agent_SharePublic_FullMethodName = "/Agent/SharePublic"
Agent_Status_FullMethodName = "/Agent/Status"
Agent_Version_FullMethodName = "/Agent/Version"
)
// AgentClient is the client API for Agent service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AgentClient interface {
AccessPrivate(ctx context.Context, in *AccessPrivateRequest, opts ...grpc.CallOption) (*AccessPrivateResponse, error)
ReleaseAccess(ctx context.Context, in *ReleaseAccessRequest, opts ...grpc.CallOption) (*ReleaseAccessResponse, error)
ReleaseShare(ctx context.Context, in *ReleaseShareRequest, opts ...grpc.CallOption) (*ReleaseShareResponse, error)
ShareReserved(ctx context.Context, in *ShareReservedRequest, opts ...grpc.CallOption) (*ShareReservedResponse, error)
SharePrivate(ctx context.Context, in *SharePrivateRequest, opts ...grpc.CallOption) (*SharePrivateResponse, error)
SharePublic(ctx context.Context, in *SharePublicRequest, opts ...grpc.CallOption) (*SharePublicResponse, error)
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
Version(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*VersionResponse, error)
}
type agentClient struct {
cc grpc.ClientConnInterface
}
func NewAgentClient(cc grpc.ClientConnInterface) AgentClient {
return &agentClient{cc}
}
func (c *agentClient) AccessPrivate(ctx context.Context, in *AccessPrivateRequest, opts ...grpc.CallOption) (*AccessPrivateResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AccessPrivateResponse)
err := c.cc.Invoke(ctx, Agent_AccessPrivate_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *agentClient) ReleaseAccess(ctx context.Context, in *ReleaseAccessRequest, opts ...grpc.CallOption) (*ReleaseAccessResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ReleaseAccessResponse)
err := c.cc.Invoke(ctx, Agent_ReleaseAccess_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *agentClient) ReleaseShare(ctx context.Context, in *ReleaseShareRequest, opts ...grpc.CallOption) (*ReleaseShareResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ReleaseShareResponse)
err := c.cc.Invoke(ctx, Agent_ReleaseShare_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *agentClient) ShareReserved(ctx context.Context, in *ShareReservedRequest, opts ...grpc.CallOption) (*ShareReservedResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ShareReservedResponse)
err := c.cc.Invoke(ctx, Agent_ShareReserved_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *agentClient) SharePrivate(ctx context.Context, in *SharePrivateRequest, opts ...grpc.CallOption) (*SharePrivateResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SharePrivateResponse)
err := c.cc.Invoke(ctx, Agent_SharePrivate_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *agentClient) SharePublic(ctx context.Context, in *SharePublicRequest, opts ...grpc.CallOption) (*SharePublicResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SharePublicResponse)
err := c.cc.Invoke(ctx, Agent_SharePublic_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *agentClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StatusResponse)
err := c.cc.Invoke(ctx, Agent_Status_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *agentClient) Version(ctx context.Context, in *VersionRequest, opts ...grpc.CallOption) (*VersionResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(VersionResponse)
err := c.cc.Invoke(ctx, Agent_Version_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// AgentServer is the server API for Agent service.
// All implementations must embed UnimplementedAgentServer
// for forward compatibility.
type AgentServer interface {
AccessPrivate(context.Context, *AccessPrivateRequest) (*AccessPrivateResponse, error)
ReleaseAccess(context.Context, *ReleaseAccessRequest) (*ReleaseAccessResponse, error)
ReleaseShare(context.Context, *ReleaseShareRequest) (*ReleaseShareResponse, error)
ShareReserved(context.Context, *ShareReservedRequest) (*ShareReservedResponse, error)
SharePrivate(context.Context, *SharePrivateRequest) (*SharePrivateResponse, error)
SharePublic(context.Context, *SharePublicRequest) (*SharePublicResponse, error)
Status(context.Context, *StatusRequest) (*StatusResponse, error)
Version(context.Context, *VersionRequest) (*VersionResponse, error)
mustEmbedUnimplementedAgentServer()
}
// UnimplementedAgentServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedAgentServer struct{}
func (UnimplementedAgentServer) AccessPrivate(context.Context, *AccessPrivateRequest) (*AccessPrivateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AccessPrivate not implemented")
}
func (UnimplementedAgentServer) ReleaseAccess(context.Context, *ReleaseAccessRequest) (*ReleaseAccessResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReleaseAccess not implemented")
}
func (UnimplementedAgentServer) ReleaseShare(context.Context, *ReleaseShareRequest) (*ReleaseShareResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReleaseShare not implemented")
}
func (UnimplementedAgentServer) ShareReserved(context.Context, *ShareReservedRequest) (*ShareReservedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ShareReserved not implemented")
}
func (UnimplementedAgentServer) SharePrivate(context.Context, *SharePrivateRequest) (*SharePrivateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SharePrivate not implemented")
}
func (UnimplementedAgentServer) SharePublic(context.Context, *SharePublicRequest) (*SharePublicResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SharePublic not implemented")
}
func (UnimplementedAgentServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Status not implemented")
}
func (UnimplementedAgentServer) Version(context.Context, *VersionRequest) (*VersionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Version not implemented")
}
func (UnimplementedAgentServer) mustEmbedUnimplementedAgentServer() {}
func (UnimplementedAgentServer) testEmbeddedByValue() {}
// UnsafeAgentServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AgentServer will
// result in compilation errors.
type UnsafeAgentServer interface {
mustEmbedUnimplementedAgentServer()
}
func RegisterAgentServer(s grpc.ServiceRegistrar, srv AgentServer) {
// If the following call pancis, it indicates UnimplementedAgentServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Agent_ServiceDesc, srv)
}
func _Agent_AccessPrivate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AccessPrivateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServer).AccessPrivate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Agent_AccessPrivate_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServer).AccessPrivate(ctx, req.(*AccessPrivateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Agent_ReleaseAccess_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReleaseAccessRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServer).ReleaseAccess(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Agent_ReleaseAccess_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServer).ReleaseAccess(ctx, req.(*ReleaseAccessRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Agent_ReleaseShare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReleaseShareRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServer).ReleaseShare(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Agent_ReleaseShare_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServer).ReleaseShare(ctx, req.(*ReleaseShareRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Agent_ShareReserved_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ShareReservedRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServer).ShareReserved(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Agent_ShareReserved_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServer).ShareReserved(ctx, req.(*ShareReservedRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Agent_SharePrivate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SharePrivateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServer).SharePrivate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Agent_SharePrivate_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServer).SharePrivate(ctx, req.(*SharePrivateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Agent_SharePublic_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SharePublicRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServer).SharePublic(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Agent_SharePublic_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServer).SharePublic(ctx, req.(*SharePublicRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Agent_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServer).Status(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Agent_Status_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServer).Status(ctx, req.(*StatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Agent_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(VersionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServer).Version(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Agent_Version_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServer).Version(ctx, req.(*VersionRequest))
}
return interceptor(ctx, in, info, handler)
}
// Agent_ServiceDesc is the grpc.ServiceDesc for Agent service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Agent_ServiceDesc = grpc.ServiceDesc{
ServiceName: "Agent",
HandlerType: (*AgentServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "AccessPrivate",
Handler: _Agent_AccessPrivate_Handler,
},
{
MethodName: "ReleaseAccess",
Handler: _Agent_ReleaseAccess_Handler,
},
{
MethodName: "ReleaseShare",
Handler: _Agent_ReleaseShare_Handler,
},
{
MethodName: "ShareReserved",
Handler: _Agent_ShareReserved_Handler,
},
{
MethodName: "SharePrivate",
Handler: _Agent_SharePrivate_Handler,
},
{
MethodName: "SharePublic",
Handler: _Agent_SharePublic_Handler,
},
{
MethodName: "Status",
Handler: _Agent_Status_Handler,
},
{
MethodName: "Version",
Handler: _Agent_Version_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "agent/agentGrpc/agent.proto",
}

View File

@ -0,0 +1,59 @@
//go:build !windows
package proctree
import (
"os/exec"
"sync"
)
func Init(_ string) error {
return nil
}
func StartChild(tail TailFunction, args ...string) (*Child, error) {
cmd := exec.Command(args[0], args[1:]...)
cld := &Child{
TailFunction: tail,
cmd: cmd,
outStream: make(chan []byte),
errStream: make(chan []byte),
wg: new(sync.WaitGroup),
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
cld.wg.Add(3)
go reader(stdout, cld.outStream, cld.wg)
go reader(stderr, cld.errStream, cld.wg)
go cld.combiner(cld.wg)
return cld, nil
}
func WaitChild(c *Child) error {
c.wg.Wait()
if err := c.cmd.Wait(); err != nil {
return err
}
return nil
}
func StopChild(c *Child) error {
if err := c.cmd.Process.Kill(); err != nil {
return err
}
return nil
}

79
agent/proctree/impl_windows.go Executable file
View File

@ -0,0 +1,79 @@
//go:build windows
package proctree
import (
"github.com/kolesnikovae/go-winjob"
"golang.org/x/sys/windows"
"os/exec"
"sync"
)
var job *winjob.JobObject
func Init(name string) error {
var err error
if job == nil {
job, err = winjob.Create(name, winjob.LimitKillOnJobClose, winjob.LimitBreakawayOK)
if err != nil {
return err
}
}
return nil
}
func StartChild(tail TailFunction, args ...string) (*Child, error) {
cmd := exec.Command(args[0], args[1:]...)
cmd.SysProcAttr = &windows.SysProcAttr{CreationFlags: windows.CREATE_SUSPENDED}
cld := &Child{
TailFunction: tail,
cmd: cmd,
outStream: make(chan []byte),
errStream: make(chan []byte),
wg: new(sync.WaitGroup),
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
if err := job.Assign(cmd.Process); err != nil {
return nil, err
}
if err := winjob.ResumeProcess(cmd.Process.Pid); err != nil {
return nil, err
}
cld.wg.Add(3)
go reader(stdout, cld.outStream, cld.wg)
go reader(stderr, cld.errStream, cld.wg)
go cld.combiner(cld.wg)
return cld, nil
}
func WaitChild(c *Child) error {
c.wg.Wait()
if err := c.cmd.Wait(); err != nil {
return err
}
return nil
}
func StopChild(c *Child) error {
if err := c.cmd.Process.Kill(); err != nil {
return err
}
return nil
}

66
agent/proctree/proctree.go Executable file
View File

@ -0,0 +1,66 @@
package proctree
import (
"fmt"
"io"
"os/exec"
"sync"
)
type Child struct {
TailFunction TailFunction
cmd *exec.Cmd
outStream chan []byte
errStream chan []byte
wg *sync.WaitGroup
}
type TailFunction func(data []byte)
func (c *Child) combiner(wg *sync.WaitGroup) {
defer wg.Done()
outDone := false
errDone := false
for {
select {
case data := <-c.outStream:
if data != nil {
if c.TailFunction != nil {
c.TailFunction(data)
}
} else {
outDone = true
}
case data := <-c.errStream:
if data != nil {
if c.TailFunction != nil {
c.TailFunction(data)
}
} else {
errDone = true
}
}
if outDone && errDone {
return
}
}
}
func reader(r io.ReadCloser, o chan []byte, wg *sync.WaitGroup) {
defer close(o)
defer wg.Done()
buf := make([]byte, 64*1024)
for {
n, err := r.Read(buf)
if err != nil {
if err == io.EOF {
return
}
fmt.Printf("error reading: %v", err)
return
}
o <- buf[:n]
}
}

29
agent/releaseAccess.go Normal file
View File

@ -0,0 +1,29 @@
package agent
import (
"context"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/agent/proctree"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
func (i *agentGrpcImpl) ReleaseAccess(_ context.Context, req *agentGrpc.ReleaseAccessRequest) (*agentGrpc.ReleaseAccessResponse, error) {
if acc, found := i.a.accesses[req.FrontendToken]; found {
logrus.Infof("stopping access '%v'", acc.frontendToken)
if err := proctree.StopChild(acc.process); err != nil {
logrus.Error(err)
}
if err := proctree.WaitChild(acc.process); err != nil {
logrus.Error(err)
}
delete(i.a.accesses, acc.frontendToken)
logrus.Infof("released access '%v'", acc.frontendToken)
} else {
return nil, errors.Errorf("agent has no access with frontend token '%v'", req.FrontendToken)
}
return nil, nil
}

29
agent/releaseShare.go Executable file
View File

@ -0,0 +1,29 @@
package agent
import (
"context"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/agent/proctree"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
func (i *agentGrpcImpl) ReleaseShare(_ context.Context, req *agentGrpc.ReleaseShareRequest) (*agentGrpc.ReleaseShareResponse, error) {
if shr, found := i.a.shares[req.Token]; found {
logrus.Infof("stopping share '%v'", shr.token)
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)
logrus.Infof("released share '%v'", shr.token)
} else {
return nil, errors.Errorf("agent has no share with token '%v'", req.Token)
}
return nil, nil
}

107
agent/share.go Normal file
View File

@ -0,0 +1,107 @@
package agent
import (
"bytes"
"encoding/json"
"errors"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/zrok/agent/proctree"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/sirupsen/logrus"
"strings"
"time"
)
type share struct {
token string
frontendEndpoints []string
target string
basicAuth []string
frontendSelection []string
shareMode sdk.ShareMode
backendMode sdk.BackendMode
reserved bool
insecure bool
oauthProvider string
oauthEmailAddressPatterns []string
oauthCheckInterval time.Duration
closed bool
accessGrants []string
process *proctree.Child
readBuffer bytes.Buffer
booted bool
bootComplete chan struct{}
bootErr error
a *Agent
}
func (s *share) monitor() {
if err := proctree.WaitChild(s.process); err != nil {
pfxlog.ChannelLogger(s.token).Error(err)
}
s.a.outShares <- s
}
func (s *share) tail(data []byte) {
defer func() {
if r := recover(); r != nil {
logrus.Errorf("recovering: %v", r)
}
}()
s.readBuffer.Write(data)
if line, err := s.readBuffer.ReadString('\n'); err == nil {
line = strings.Trim(line, "\n")
if !s.booted {
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["backend_mode"]; found {
if str, ok := v.(string); ok {
s.backendMode = sdk.BackendMode(str)
}
}
if v, found := in["share_mode"]; found {
if str, ok := v.(string); ok {
s.shareMode = sdk.ShareMode(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)
}
}
}
}
if v, found := in["target"]; found {
if str, ok := v.(string); ok {
s.target = str
}
}
s.booted = true
} else {
s.bootErr = errors.New(line)
}
close(s.bootComplete)
} else {
if strings.HasPrefix(line, "{") {
in := make(map[string]interface{})
if err := json.Unmarshal([]byte(line), &in); err == nil {
pfxlog.ChannelLogger(s.token).Info(in)
}
} else {
pfxlog.ChannelLogger(s.token).Info(strings.Trim(line, "\n"))
}
}
} else {
s.readBuffer.WriteString(line)
}
}

66
agent/sharePrivate.go Normal file
View File

@ -0,0 +1,66 @@
package agent
import (
"context"
"errors"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/agent/proctree"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/sirupsen/logrus"
"os"
)
func (i *agentGrpcImpl) SharePrivate(_ context.Context, req *agentGrpc.SharePrivateRequest) (*agentGrpc.SharePrivateResponse, error) {
root, err := environment.LoadRoot()
if err != nil {
return nil, err
}
if !root.IsEnabled() {
return nil, errors.New("unable to load environment; did you 'zrok enable'?")
}
shrCmd := []string{os.Args[0], "share", "private", "--agent", "-b", req.BackendMode}
shr := &share{
shareMode: sdk.PrivateShareMode,
backendMode: sdk.BackendMode(req.BackendMode),
bootComplete: make(chan struct{}),
a: i.a,
}
if req.Insecure {
shrCmd = append(shrCmd, "--insecure")
}
shr.insecure = req.Insecure
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
}
go shr.monitor()
<-shr.bootComplete
if shr.bootErr == nil {
i.a.inShares <- shr
return &agentGrpc.SharePrivateResponse{Token: shr.token}, nil
}
return nil, shr.bootErr
}

93
agent/sharePublic.go Normal file
View File

@ -0,0 +1,93 @@
package agent
import (
"context"
"errors"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/agent/proctree"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/sirupsen/logrus"
"os"
)
func (i *agentGrpcImpl) SharePublic(_ context.Context, req *agentGrpc.SharePublicRequest) (*agentGrpc.SharePublicResponse, error) {
root, err := environment.LoadRoot()
if err != nil {
return nil, err
}
if !root.IsEnabled() {
return nil, errors.New("unable to load environment; did you 'zrok enable'?")
}
shrCmd := []string{os.Args[0], "share", "public", "--agent", "-b", req.BackendMode}
shr := &share{
shareMode: sdk.PublicShareMode,
backendMode: sdk.BackendMode(req.BackendMode),
bootComplete: make(chan struct{}),
a: i.a,
}
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
}
go shr.monitor()
<-shr.bootComplete
if shr.bootErr == nil {
i.a.inShares <- shr
return &agentGrpc.SharePublicResponse{
Token: shr.token,
FrontendEndpoints: shr.frontendEndpoints,
}, nil
}
return nil, shr.bootErr
}

61
agent/shareReserved.go Normal file
View File

@ -0,0 +1,61 @@
package agent
import (
"context"
"errors"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/agent/proctree"
"github.com/openziti/zrok/environment"
"os"
)
func (i *agentGrpcImpl) ShareReserved(_ context.Context, req *agentGrpc.ShareReservedRequest) (*agentGrpc.ShareReservedResponse, error) {
root, err := environment.LoadRoot()
if err != nil {
return nil, err
}
if !root.IsEnabled() {
return nil, errors.New("unable to load environment; did you 'zrok enable'?")
}
shrCmd := []string{os.Args[0], "share", "reserved", "--agent"}
shr := &share{
reserved: true,
bootComplete: make(chan struct{}),
a: i.a,
}
if req.OverrideEndpoint != "" {
shrCmd = append(shrCmd, "--override-endpoint", req.OverrideEndpoint)
}
if req.Insecure {
shrCmd = append(shrCmd, "--insecure")
}
shr.insecure = req.Insecure
shrCmd = append(shrCmd, req.Token)
shr.token = req.Token
shr.process, err = proctree.StartChild(shr.tail, shrCmd...)
if err != nil {
return nil, err
}
go shr.monitor()
<-shr.bootComplete
if shr.bootErr == nil {
i.a.inShares <- shr
return &agentGrpc.ShareReservedResponse{
Token: shr.token,
BackendMode: string(shr.backendMode),
ShareMode: string(shr.shareMode),
FrontendEndpoints: shr.frontendEndpoints,
Target: shr.target,
}, nil
}
return nil, shr.bootErr
}

33
agent/status.go Normal file
View File

@ -0,0 +1,33 @@
package agent
import (
"context"
"github.com/openziti/zrok/agent/agentGrpc"
)
func (i *agentGrpcImpl) Status(_ context.Context, _ *agentGrpc.StatusRequest) (*agentGrpc.StatusResponse, error) {
var accesses []*agentGrpc.AccessDetail
for feToken, acc := range i.a.accesses {
accesses = append(accesses, &agentGrpc.AccessDetail{
FrontendToken: feToken,
Token: acc.token,
BindAddress: acc.bindAddress,
ResponseHeaders: acc.responseHeaders,
})
}
var shares []*agentGrpc.ShareDetail
for token, shr := range i.a.shares {
shares = append(shares, &agentGrpc.ShareDetail{
Token: token,
ShareMode: string(shr.shareMode),
BackendMode: string(shr.backendMode),
Reserved: shr.reserved,
FrontendEndpoint: shr.frontendSelection,
BackendEndpoint: shr.target,
Closed: shr.closed,
})
}
return &agentGrpc.StatusResponse{Accesses: accesses, Shares: shares}, nil
}

14
agent/version.go Normal file
View File

@ -0,0 +1,14 @@
package agent
import (
"context"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/build"
"github.com/sirupsen/logrus"
)
func (i *agentGrpcImpl) Version(_ context.Context, _ *agentGrpc.VersionRequest) (*agentGrpc.VersionResponse, error) {
v := build.String()
logrus.Infof("responding to version inquiry with '%v'", v)
return &agentGrpc.VersionResponse{V: v}, nil
}

7
bin/generate_pb.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
protoc --go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
agent/agentGrpc/agent.proto

View File

@ -1,6 +1,8 @@
package main
import (
"encoding/json"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
@ -30,6 +32,7 @@ func init() {
type accessPrivateCommand struct {
bindAddress string
headless bool
agent bool
responseHeaders []string
cmd *cobra.Command
}
@ -42,6 +45,8 @@ func newAccessPrivateCommand() *accessPrivateCommand {
}
command := &accessPrivateCommand{cmd: cmd}
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().StringVarP(&command.bindAddress, "bind", "b", "127.0.0.1:9191", "The address to bind the private frontend")
cmd.Flags().StringArrayVar(&command.responseHeaders, "response-header", []string{}, "Add a response header ('key:value')")
cmd.Run = command.run
@ -81,7 +86,19 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
}
panic(err)
}
logrus.Infof("allocated frontend '%v'", accessResp.Payload.FrontendToken)
if cmd.agent {
data := make(map[string]interface{})
data["frontend_token"] = accessResp.Payload.FrontendToken
data["bind_address"] = cmd.bindAddress
jsonData, err := json.Marshal(data)
if err != nil {
panic(err)
}
fmt.Println(string(jsonData))
} else {
logrus.Infof("allocated frontend '%v'", accessResp.Payload.FrontendToken)
}
protocol := "http://"
switch accessResp.Payload.BackendMode {
@ -231,6 +248,21 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
}
}
} else if cmd.agent {
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 {
mdl := newAccessModel(shrToken, endpointUrl.String())
logrus.SetOutput(mdl)

View File

@ -0,0 +1,65 @@
package main
import (
"context"
"fmt"
"github.com/openziti/zrok/agent/agentClient"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
)
func init() {
agentAccessCmd.AddCommand(newAgentAccessPrivateCommand().cmd)
}
type agentAccessPrivateCommand struct {
bindAddress string
responseHeaders []string
cmd *cobra.Command
}
func newAgentAccessPrivateCommand() *agentAccessPrivateCommand {
cmd := &cobra.Command{
Use: "private <token>",
Short: "Bind a private access in the zrok Agent",
Args: cobra.ExactArgs(1),
}
command := &agentAccessPrivateCommand{cmd: cmd}
cmd.Flags().StringVarP(&command.bindAddress, "bind", "b", "127.0.0.1:9191", "The address to bind the private frontend")
cmd.Flags().StringArrayVar(&command.responseHeaders, "response-header", []string{}, "Add a response header ('key:value')")
cmd.Run = command.run
return command
}
func (cmd *agentAccessPrivateCommand) run(_ *cobra.Command, args []string) {
root, err := environment.LoadRoot()
if err != nil {
if !panicInstead {
tui.Error("unable to load environment", err)
}
panic(err)
}
if !root.IsEnabled() {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
client, conn, err := agentClient.NewClient(root)
if err != nil {
tui.Error("error connecting to agent", err)
}
defer conn.Close()
acc, err := client.AccessPrivate(context.Background(), &agentGrpc.AccessPrivateRequest{
Token: args[0],
BindAddress: cmd.bindAddress,
ResponseHeaders: cmd.responseHeaders,
})
if err != nil {
tui.Error("error creating access", err)
}
fmt.Println(acc)
}

View File

@ -0,0 +1,55 @@
package main
import (
"context"
"fmt"
"github.com/openziti/zrok/agent/agentClient"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
)
func init() {
agentReleaseCmd.AddCommand(newAgentReleaseAccessCommand().cmd)
}
type agentReleaseAccessCommand struct {
cmd *cobra.Command
}
func newAgentReleaseAccessCommand() *agentReleaseAccessCommand {
cmd := &cobra.Command{
Use: "access <frontendToken>",
Short: "Unbind an access from the zrok Agent",
Args: cobra.ExactArgs(1),
}
command := &agentReleaseAccessCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *agentReleaseAccessCommand) run(_ *cobra.Command, args []string) {
root, err := environment.LoadRoot()
if err != nil {
if !panicInstead {
tui.Error("unable to load environment", err)
}
panic(err)
}
client, conn, err := agentClient.NewClient(root)
if err != nil {
tui.Error("error connecting to agent", err)
}
defer conn.Close()
_, err = client.ReleaseAccess(context.Background(), &agentGrpc.ReleaseAccessRequest{
FrontendToken: args[0],
})
if err != nil {
tui.Error("error releasing access", err)
}
fmt.Println("success.")
}

55
cmd/zrok/agentReleaseShare.go Executable file
View File

@ -0,0 +1,55 @@
package main
import (
"context"
"fmt"
"github.com/openziti/zrok/agent/agentClient"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
)
func init() {
agentReleaseCmd.AddCommand(newAgentReleaseShareCommand().cmd)
}
type agentReleaseShareCommand struct {
cmd *cobra.Command
}
func newAgentReleaseShareCommand() *agentReleaseShareCommand {
cmd := &cobra.Command{
Use: "share <token>",
Short: "Release a share from the zrok Agent",
Args: cobra.ExactArgs(1),
}
command := &agentReleaseShareCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *agentReleaseShareCommand) run(_ *cobra.Command, args []string) {
root, err := environment.LoadRoot()
if err != nil {
if !panicInstead {
tui.Error("unable to load environment", err)
}
panic(err)
}
client, conn, err := agentClient.NewClient(root)
if err != nil {
tui.Error("error connecting to agent", err)
}
defer conn.Close()
_, err = client.ReleaseShare(context.Background(), &agentGrpc.ReleaseShareRequest{
Token: args[0],
})
if err != nil {
tui.Error("error releasing share", err)
}
fmt.Println("success.")
}

View File

@ -0,0 +1,162 @@
package main
import (
"context"
"fmt"
"github.com/openziti/zrok/agent/agentClient"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/endpoints/vpn"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
"net"
"path/filepath"
)
func init() {
agentShareCmd.AddCommand(newAgentSharePrivateCommand().cmd)
}
type agentSharePrivateCommand struct {
backendMode string
insecure bool
closed bool
accessGrants []string
cmd *cobra.Command
}
func newAgentSharePrivateCommand() *agentSharePrivateCommand {
cmd := &cobra.Command{
Use: "private <target>",
Short: "Create a private share in the zrok Agent",
Args: cobra.RangeArgs(0, 1),
}
command := &agentSharePrivateCommand{cmd: cmd}
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, tcpTunnel, udpTunnel, caddy, drive, socks, vpn}")
cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for <target>")
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.Run = command.run
return command
}
func (cmd *agentSharePrivateCommand) run(_ *cobra.Command, args []string) {
var target string
switch cmd.backendMode {
case "proxy":
if len(args) != 1 {
tui.Error("the 'proxy' backend mode expects a <target>", nil)
}
v, err := parseUrl(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
target = v
case "web":
if len(args) != 1 {
tui.Error("the 'web' backend mode expects a <target>", nil)
}
v, err := filepath.Abs(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
target = v
case "tcpTunnel":
if len(args) != 1 {
tui.Error("the 'tcpTunnel' backend mode expects a <target>", nil)
}
target = args[0]
case "udpTunnel":
if len(args) != 1 {
tui.Error("the 'udpTunnel' backend mode expects a <target>", nil)
}
target = args[0]
case "caddy":
if len(args) != 1 {
tui.Error("the 'caddy' backend mode expects a <target>", nil)
}
v, err := filepath.Abs(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
target = v
case "drive":
if len(args) != 1 {
tui.Error("the 'drive' backend mode expects a <target>", nil)
}
v, err := filepath.Abs(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
target = v
case "socks":
if len(args) != 0 {
tui.Error("the 'socks' backend mode does not expect <target>", nil)
}
target = "socks"
case "vpn":
if len(args) == 1 {
_, _, err := net.ParseCIDR(args[0])
if err != nil {
tui.Error("the 'vpn' backend expect valid CIDR <target>", err)
}
target = args[0]
} else {
target = vpn.DefaultTarget()
}
default:
tui.Error(fmt.Sprintf("invalid backend mode '%v'", cmd.backendMode), nil)
}
root, err := environment.LoadRoot()
if err != nil {
if !panicInstead {
tui.Error("unable to load environment", err)
}
panic(err)
}
if !root.IsEnabled() {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
client, conn, err := agentClient.NewClient(root)
if err != nil {
tui.Error("error connecting to agent", err)
}
defer conn.Close()
shr, err := client.SharePrivate(context.Background(), &agentGrpc.SharePrivateRequest{
Target: target,
BackendMode: cmd.backendMode,
Insecure: cmd.insecure,
Closed: cmd.closed,
AccessGrants: cmd.accessGrants,
})
if err != nil {
tui.Error("error creating share", err)
}
fmt.Println(shr)
}

View File

@ -0,0 +1,144 @@
package main
import (
"context"
"fmt"
"github.com/openziti/zrok/agent/agentClient"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
"path/filepath"
"time"
)
func init() {
agentShareCmd.AddCommand(newAgentSharePublicCommand().cmd)
}
type agentSharePublicCommand struct {
basicAuth []string
frontendSelection []string
backendMode string
headless bool
insecure bool
oauthProvider string
oauthEmailAddressPatterns []string
oauthCheckInterval time.Duration
closed bool
accessGrants []string
cmd *cobra.Command
}
func newAgentSharePublicCommand() *agentSharePublicCommand {
cmd := &cobra.Command{
Use: "public <target>",
Short: "Create a public share in the zrok Agent",
Args: cobra.ExactArgs(1),
}
command := &agentSharePublicCommand{cmd: cmd}
defaultFrontends := []string{"public"}
if root, err := environment.LoadRoot(); err == nil {
defaultFrontend, _ := root.DefaultFrontend()
defaultFrontends = []string{defaultFrontend}
}
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.insecure, "insecure", false, "Enable insecure TLS certificate validation for <target>")
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 (<username:password>,...)")
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")
cmd.Flags().DurationVar(&command.oauthCheckInterval, "oauth-check-interval", 3*time.Hour, "Maximum lifetime for OAuth authentication; reauthenticate after expiry")
cmd.MarkFlagsMutuallyExclusive("basic-auth", "oauth-provider")
cmd.Run = command.run
return command
}
func (cmd *agentSharePublicCommand) run(_ *cobra.Command, args []string) {
var target string
switch cmd.backendMode {
case "proxy":
v, err := parseUrl(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
target = v
case "web":
v, err := filepath.Abs(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
target = v
case "caddy":
v, err := filepath.Abs(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
target = v
case "drive":
v, err := filepath.Abs(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
target = v
default:
tui.Error(fmt.Sprintf("invalid backend mode '%v'", cmd.backendMode), nil)
}
root, err := environment.LoadRoot()
if err != nil {
if !panicInstead {
tui.Error("unable to load environment", err)
}
panic(err)
}
if !root.IsEnabled() {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
client, conn, err := agentClient.NewClient(root)
if err != nil {
tui.Error("error connecting to agent", err)
}
defer conn.Close()
shr, err := client.SharePublic(context.Background(), &agentGrpc.SharePublicRequest{
Target: target,
BasicAuth: cmd.basicAuth,
FrontendSelection: cmd.frontendSelection,
BackendMode: cmd.backendMode,
Insecure: cmd.insecure,
OauthProvider: cmd.oauthProvider,
OauthEmailAddressPatterns: cmd.oauthEmailAddressPatterns,
OauthCheckInterval: cmd.oauthCheckInterval.String(),
Closed: cmd.closed,
AccessGrants: cmd.accessGrants,
})
if err != nil {
tui.Error("error creating share", err)
}
fmt.Println(shr)
}

View File

@ -0,0 +1,65 @@
package main
import (
"context"
"fmt"
"github.com/openziti/zrok/agent/agentClient"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
)
func init() {
agentShareCmd.AddCommand(newAgentShareReservedCommand().cmd)
}
type agentShareReservedCommand struct {
overrideEndpoint string
insecure bool
cmd *cobra.Command
}
func newAgentShareReservedCommand() *agentShareReservedCommand {
cmd := &cobra.Command{
Use: "reserved <token>",
Short: "Share an existing reserved share in the zrok Agent",
Args: cobra.ExactArgs(1),
}
command := &agentShareReservedCommand{cmd: cmd}
cmd.Flags().StringVar(&command.overrideEndpoint, "override-endpoint", "", "Override the stored target endpoint with a replacement")
cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation")
cmd.Run = command.run
return command
}
func (cmd *agentShareReservedCommand) run(_ *cobra.Command, args []string) {
root, err := environment.LoadRoot()
if err != nil {
if !panicInstead {
tui.Error("unable to load environment", err)
}
panic(err)
}
if !root.IsEnabled() {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
client, conn, err := agentClient.NewClient(root)
if err != nil {
tui.Error("error connecting to agent", err)
}
defer conn.Close()
shr, err := client.ShareReserved(context.Background(), &agentGrpc.ShareReservedRequest{
Token: args[0],
OverrideEndpoint: cmd.overrideEndpoint,
Insecure: cmd.insecure,
})
if err != nil {
tui.Error("error sharing reserved share", err)
}
fmt.Println(shr)
}

62
cmd/zrok/agentStart.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"github.com/openziti/zrok/agent"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
"os"
"os/signal"
"syscall"
)
func init() {
agentCmd.AddCommand(newAgentStartCommand().cmd)
}
type agentStartCommand struct {
cmd *cobra.Command
}
func newAgentStartCommand() *agentStartCommand {
cmd := &cobra.Command{
Use: "start",
Short: "Start a zrok agent",
Args: cobra.NoArgs,
}
command := &agentStartCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *agentStartCommand) run(_ *cobra.Command, _ []string) {
root, err := environment.LoadRoot()
if err != nil {
tui.Error("error loading zrokdir", err)
}
if !root.IsEnabled() {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
a, err := agent.NewAgent(root)
if err != nil {
tui.Error("error creating agent", err)
}
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cmd.shutdown(a)
os.Exit(0)
}()
if err := a.Run(); err != nil {
tui.Error("agent aborted", err)
}
}
func (cmd *agentStartCommand) shutdown(a *agent.Agent) {
a.Shutdown()
}

74
cmd/zrok/agentStatus.go Normal file
View File

@ -0,0 +1,74 @@
package main
import (
"context"
"fmt"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/openziti/zrok/agent/agentClient"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
"os"
)
func init() {
agentCmd.AddCommand(newAgentStatusCommand().cmd)
}
type agentStatusCommand struct {
cmd *cobra.Command
}
func newAgentStatusCommand() *agentStatusCommand {
cmd := &cobra.Command{
Use: "status",
Short: "Show the status of the running zrok Agent",
Args: cobra.NoArgs,
}
command := &agentStatusCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *agentStatusCommand) run(_ *cobra.Command, _ []string) {
root, err := environment.LoadRoot()
if err != nil {
tui.Error("error loading zrokdir", err)
}
client, conn, err := agentClient.NewClient(root)
if err != nil {
tui.Error("error connecting to agent", err)
}
defer conn.Close()
status, err := client.Status(context.Background(), &agentGrpc.StatusRequest{})
if err != nil {
tui.Error("error getting status", err)
}
fmt.Println()
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleColoredDark)
t.AppendHeader(table.Row{"Frontend Token", "Token", "Bind Address"})
for _, access := range status.GetAccesses() {
t.AppendRow(table.Row{access.FrontendToken, access.Token, access.BindAddress})
}
t.Render()
fmt.Printf("%d accesses in agent\n", len(status.GetAccesses()))
fmt.Println()
t = table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleColoredDark)
t.AppendHeader(table.Row{"Token", "Reserved", "Share Mode", "Backend Mode", "Target"})
for _, share := range status.GetShares() {
t.AppendRow(table.Row{share.Token, share.Reserved, share.ShareMode, share.BackendMode, share.BackendEndpoint})
}
t.Render()
fmt.Printf("%d shares in agent\n", len(status.GetShares()))
fmt.Println()
}

49
cmd/zrok/agentVersion.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"context"
"github.com/openziti/zrok/agent/agentClient"
"github.com/openziti/zrok/agent/agentGrpc"
"github.com/openziti/zrok/environment"
"github.com/openziti/zrok/tui"
"github.com/spf13/cobra"
)
func init() {
agentCmd.AddCommand(newAgentVersionCommand().cmd)
}
type agentVersionCommand struct {
cmd *cobra.Command
}
func newAgentVersionCommand() *agentVersionCommand {
cmd := &cobra.Command{
Use: "version",
Short: "Retrieve the running zrok Agent version",
Args: cobra.NoArgs,
}
command := &agentVersionCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *agentVersionCommand) run(_ *cobra.Command, _ []string) {
root, err := environment.LoadRoot()
if err != nil {
tui.Error("error loading zrokdir", err)
}
client, conn, err := agentClient.NewClient(root)
if err != nil {
tui.Error("error connecting to agent", err)
}
defer conn.Close()
v, err := client.Version(context.Background(), &agentGrpc.VersionRequest{})
if err != nil {
tui.Error("error getting agent version", err)
}
println(v.GetV())
}

View File

@ -24,6 +24,10 @@ func init() {
adminCmd.AddCommand(adminDeleteCmd)
adminCmd.AddCommand(adminListCmd)
adminCmd.AddCommand(adminUpdateCmd)
rootCmd.AddCommand(agentCmd)
agentCmd.AddCommand(agentAccessCmd)
agentCmd.AddCommand(agentShareCmd)
agentCmd.AddCommand(agentReleaseCmd)
testCmd.AddCommand(loopCmd)
rootCmd.AddCommand(adminCmd)
rootCmd.AddCommand(configCmd)
@ -77,6 +81,27 @@ var adminUpdateCmd = &cobra.Command{
Short: "Update global resources",
}
var agentAccessCmd = &cobra.Command{
Use: "access",
Short: "zrok Agent access commands",
}
var agentCmd = &cobra.Command{
Use: "agent",
Short: "zrok Agent commands",
Aliases: []string{"daemon"},
}
var agentShareCmd = &cobra.Command{
Use: "share",
Short: "zrok Agent sharing commands",
}
var agentReleaseCmd = &cobra.Command{
Use: "release",
Short: "zrok Agent release commands",
}
var configCmd = &cobra.Command{
Use: "config",
Short: "Configure your zrok environment",

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/openziti/zrok/endpoints"
@ -27,9 +28,9 @@ func init() {
}
type sharePrivateCommand struct {
basicAuth []string
backendMode string
headless bool
agent bool
insecure bool
closed bool
accessGrants []string
@ -43,9 +44,10 @@ func newSharePrivateCommand() *sharePrivateCommand {
Args: cobra.RangeArgs(0, 1),
}
command := &sharePrivateCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...")
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, tcpTunnel, udpTunnel, caddy, drive, socks, vpn}")
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 <target>")
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)")
@ -145,7 +147,6 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
req := &sdk.ShareRequest{
BackendMode: sdk.BackendMode(cmd.backendMode),
ShareMode: sdk.PrivateShareMode,
BasicAuth: cmd.basicAuth,
Target: target,
}
if cmd.closed {
@ -160,9 +161,20 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
panic(err)
}
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))
}
shareDescription := fmt.Sprintf("access your share with: %v", tui.Code.Render(fmt.Sprintf("zrok access private %v", shr.Token)))
mdl := newShareModel(shr.Token, []string{shareDescription}, sdk.PrivateShareMode, sdk.BackendMode(cmd.backendMode))
if !cmd.headless {
if !cmd.headless && !cmd.agent {
proxy.SetCaddyLoggingWriter(mdl)
}
@ -366,6 +378,22 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
}
}
} else if cmd.agent {
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())

View File

@ -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 <target>")
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 (<username:password>,...)")
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")
@ -149,8 +152,19 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
panic(err)
}
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))
}
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 +281,22 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
}
}
} else if cmd.agent {
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())

View File

@ -1,6 +1,7 @@
package main
import (
"encoding/json"
"fmt"
tea "github.com/charmbracelet/bubbletea"
httptransport "github.com/go-openapi/runtime/client"
@ -28,6 +29,7 @@ func init() {
type shareReservedCommand struct {
overrideEndpoint string
headless bool
agent bool
insecure bool
cmd *cobra.Command
}
@ -41,6 +43,8 @@ func newShareReservedCommand() *shareReservedCommand {
command := &shareReservedCommand{cmd: cmd}
cmd.Flags().StringVar(&command.overrideEndpoint, "override-endpoint", "", "Override the stored target endpoint with a replacement")
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")
cmd.Run = command.run
return command
@ -96,7 +100,9 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
}
if resp.Payload.BackendMode != "socks" {
logrus.Infof("sharing target: '%v'", target)
if !cmd.agent {
logrus.Infof("sharing target: '%v'", target)
}
if resp.Payload.BackendProxyEndpoint != target {
upReq := share.NewUpdateShareParams()
@ -110,9 +116,13 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
}
panic(err)
}
logrus.Infof("updated backend target to: %v", target)
if !cmd.agent {
logrus.Infof("updated backend target to: %v", target)
}
} else {
logrus.Infof("using existing backend target: %v", target)
if !cmd.agent {
logrus.Infof("using existing backend target: %v", target)
}
}
}
@ -124,8 +134,26 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
shareDescription = fmt.Sprintf("access your share with: %v", tui.Code.Render(fmt.Sprintf("zrok access private %v", shrToken)))
}
if cmd.agent {
data := make(map[string]interface{})
data["token"] = resp.Payload.Token
data["backend_mode"] = resp.Payload.BackendMode
data["share_mode"] = resp.Payload.ShareMode
if resp.Payload.FrontendEndpoint != "" {
data["frontend_endpoints"] = resp.Payload.FrontendEndpoint
}
if resp.Payload.BackendProxyEndpoint != "" {
data["target"] = resp.Payload.BackendProxyEndpoint
}
jsonData, err := json.Marshal(data)
if err != nil {
panic(err)
}
fmt.Println(string(jsonData))
}
mdl := newShareModel(shrToken, []string{shareDescription}, sdk.ShareMode(resp.Payload.ShareMode), sdk.BackendMode(resp.Payload.BackendMode))
if !cmd.headless {
if !cmd.headless && !cmd.agent {
proxy.SetCaddyLoggingWriter(mdl)
}
@ -324,6 +352,23 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path)
}
}
} else if cmd.agent {
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())

View File

@ -67,6 +67,10 @@ func (b *Backend) Run() error {
return nil
}
func (b *Backend) Stop() error {
return b.listener.Close()
}
func newReverseProxy(cfg *BackendConfig) (*httputil.ReverseProxy, error) {
targetURL, err := url.Parse(cfg.EndpointAddress)
if err != nil {

View File

@ -81,6 +81,10 @@ func (c *CaddyWebBackend) Run() error {
return caddy.Run(c.caddyCfg)
}
func (c *CaddyWebBackend) Stop() error {
return caddy.Stop()
}
func (c *CaddyWebBackend) Requests() func() int32 {
return func() int32 { return 0 }
}

View File

@ -26,6 +26,8 @@ type Root interface {
ZitiIdentityNamed(name string) (string, error)
SaveZitiIdentityNamed(name, data string) error
DeleteZitiIdentityNamed(name string) error
AgentSocket() (string, error)
}
type Environment struct {

View File

@ -174,6 +174,10 @@ func (r *Root) DeleteZitiIdentityNamed(name string) error {
return nil
}
func (r *Root) AgentSocket() (string, error) {
return "", errors.Errorf("this environment version does not support agent sockets; please 'zrok update' this environment")
}
func (r *Root) Obliterate() error {
zrd, err := rootDir()
if err != nil {

View File

@ -174,6 +174,10 @@ func (r *Root) DeleteZitiIdentityNamed(name string) error {
return nil
}
func (r *Root) AgentSocket() (string, error) {
return agentSocket()
}
func (r *Root) Obliterate() error {
zrd, err := rootDir()
if err != nil {

View File

@ -53,3 +53,11 @@ func identityFile(name string) (string, error) {
}
return filepath.Join(idd, fmt.Sprintf("%v.json", name)), nil
}
func agentSocket() (string, error) {
zrd, err := rootDir()
if err != nil {
return "", err
}
return filepath.Join(zrd, "agent.socket"), nil
}

15
go.mod
View File

@ -27,6 +27,7 @@ require (
github.com/jedib0t/go-pretty/v6 v6.4.3
github.com/jessevdk/go-flags v1.6.1
github.com/jmoiron/sqlx v1.3.5
github.com/kolesnikovae/go-winjob v1.0.0
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.16
github.com/michaelquigley/cf v0.0.13
@ -57,7 +58,10 @@ require (
golang.org/x/crypto v0.25.0
golang.org/x/net v0.27.0
golang.org/x/oauth2 v0.21.0
golang.org/x/sys v0.24.0
golang.org/x/time v0.5.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
nhooyr.io/websocket v1.8.11
)
@ -85,7 +89,7 @@ require (
github.com/caddyserver/certmagic v0.20.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
@ -126,7 +130,7 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.2.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/glog v1.2.0 // indirect
github.com/golang/glog v1.2.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/cel-go v0.15.1 // indirect
@ -253,17 +257,14 @@ require (
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect

27
go.sum
View File

@ -119,8 +119,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
@ -298,8 +298,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -551,6 +551,8 @@ github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kolesnikovae/go-winjob v1.0.0 h1:OKEtCHB3sYNAiqNwGDhf08Y6luM7C8mP+42rp1N6SeE=
github.com/kolesnikovae/go-winjob v1.0.0/go.mod h1:k0joOLP3/NBrRmDQjPV2+oN1TPmEWt6arTNtFjVeQuM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@ -1182,6 +1184,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1401,12 +1404,12 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg=
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY=
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4 h1:ZcOkrmX74HbKFYnpPY8Qsw93fC29TbJXspYKaBkSXDQ=
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -1427,8 +1430,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=