diff --git a/CHANGELOG.md b/CHANGELOG.md index 05147e46..755e46de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ -# Changelog +# CHANGELOG ## v0.4.14 +FEATURE: `zrok` Drives "Phase 1" (`p1`) functionality included in this release. This includes new `--backend-mode drive`, which accepts a folder path as a target. A `drive` share can be mounted as a network drive on Windows, macOS, and Linux, allowing full read/write access from all applications on those systems (https://github.com/openziti/zrok/issues/218) Subsequent releases will address CLI use cases and provide further refinements to the overall approach. + FEATURE: Docker Compose project for a reserved public share in docker/compose/zrok-public-share-reserved/compose.yml is described in the [public share guide](https://docs.zrok.io/docs/guides/docker-share/docker_public_share_guide/). ## v0.4.13 @@ -138,7 +140,7 @@ CHANGE: `zrok test loop` has been moved to `zrok test loop public`, making way f ## v0.3.2 -FEATURE: New docker infrastructure, including `compose.yml` examples (and documentation) illustrating how to deploy `zrok` in `docker`-based environments +FEATURE: New docker infrastructure, including `docker-compose.yml` examples (and documentation) illustrating how to deploy `zrok` in `docker`-based environments CHANGE: Include missing `--headless` flag for `zrok enable` and `zrok access private` (https://github.com/openziti/zrok/issues/246) @@ -146,8 +148,7 @@ CHANGE: Fix for `zrok enable` error path handling (https://github.com/openziti/z FEATURE: `zrok controller validate` and `zrok access public validate` will both perform a quick syntax validation on controller and public frontend configuration documents (https://github.com/openziti/zrok/issues/238) - $ zrok controller validate etc/dev.yml - + $ zrok controller validate etc/dev.yml [ERROR]: controller config validation failed (error loading controller config 'etc/dev.yml': field 'maintenance': field 'registration': field 'expiration_timeout': got [bool], expected [time.Duration]) CHANGE: `zrok status` no longer shows secrets (secret token, ziti identity) unless the `--secrets` flag is passed (https://github.com/openziti/zrok/issues/243) diff --git a/cmd/zrok/reserve.go b/cmd/zrok/reserve.go index b6be848d..4b029f11 100644 --- a/cmd/zrok/reserve.go +++ b/cmd/zrok/reserve.go @@ -34,7 +34,7 @@ func newReserveCommand() *reserveCommand { } command := &reserveCommand{cmd: cmd} cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share") - cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, , caddy}") + cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, , caddy, drive}") cmd.Flags().BoolVarP(&command.jsonOutput, "json-output", "j", false, "Emit JSON describing the created reserved share") cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (,...)") cmd.Flags().StringVar(&command.oauthProvider, "oauth-provider", "", "Enable OAuth provider [google, github]") @@ -73,8 +73,11 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) { case "caddy": target = args[1] + case "drive": + target = args[1] + default: - tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web, tcpTunnel, udpTunnel, caddy}", cmd.backendMode), nil) + tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web, tcpTunnel, udpTunnel, caddy, drive}", cmd.backendMode), nil) } env, err := environment.LoadRoot() diff --git a/cmd/zrok/sharePrivate.go b/cmd/zrok/sharePrivate.go index 3c6de78b..8a25a7e9 100644 --- a/cmd/zrok/sharePrivate.go +++ b/cmd/zrok/sharePrivate.go @@ -4,6 +4,7 @@ import ( "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/openziti/zrok/endpoints" + "github.com/openziti/zrok/endpoints/drive" "github.com/openziti/zrok/endpoints/proxy" "github.com/openziti/zrok/endpoints/tcpTunnel" "github.com/openziti/zrok/endpoints/udpTunnel" @@ -72,8 +73,11 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) { target = args[0] cmd.headless = true + case "drive": + target = args[0] + default: - tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web, tcpTunnel, udpTunnel, caddy}", cmd.backendMode), nil) + tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web, tcpTunnel, udpTunnel, caddy, drive}", cmd.backendMode), nil) } root, err := environment.LoadRoot() @@ -238,6 +242,28 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) { } }() + case "drive": + cfg := &drive.BackendConfig{ + IdentityPath: zif, + DriveRoot: target, + ShrToken: shr.Token, + Requests: requests, + } + + be, err := drive.NewBackend(cfg) + if err != nil { + if !panicInstead { + tui.Error("error creating drive backend", err) + } + panic(err) + } + + go func() { + if err := be.Run(); err != nil { + logrus.Errorf("error running drive backend: %v", err) + } + }() + default: tui.Error("invalid backend mode", nil) } diff --git a/cmd/zrok/sharePublic.go b/cmd/zrok/sharePublic.go index b0181be3..5aa1b0e1 100644 --- a/cmd/zrok/sharePublic.go +++ b/cmd/zrok/sharePublic.go @@ -4,6 +4,7 @@ import ( "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/openziti/zrok/endpoints" + drive "github.com/openziti/zrok/endpoints/drive" "github.com/openziti/zrok/endpoints/proxy" "github.com/openziti/zrok/environment" "github.com/openziti/zrok/environment/env_core" @@ -42,7 +43,7 @@ func newSharePublicCommand() *sharePublicCommand { } command := &sharePublicCommand{cmd: cmd} cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share") - cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, caddy}") + 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 ") @@ -77,6 +78,9 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { target = args[0] cmd.headless = true + case "drive": + target = args[0] + default: tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web}", cmd.backendMode), nil) } @@ -204,6 +208,28 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { } }() + case "drive": + cfg := &drive.BackendConfig{ + IdentityPath: zif, + DriveRoot: target, + ShrToken: shr.Token, + Requests: requests, + } + + be, err := drive.NewBackend(cfg) + if err != nil { + if !panicInstead { + tui.Error("error creating drive backend", err) + } + panic(err) + } + + go func() { + if err := be.Run(); err != nil { + logrus.Errorf("error running drive backend: %v", err) + } + }() + default: tui.Error("invalid backend mode", nil) } diff --git a/cmd/zrok/shareReserved.go b/cmd/zrok/shareReserved.go index c4e33bb7..1a74d0ff 100644 --- a/cmd/zrok/shareReserved.go +++ b/cmd/zrok/shareReserved.go @@ -5,6 +5,7 @@ import ( tea "github.com/charmbracelet/bubbletea" httptransport "github.com/go-openapi/runtime/client" "github.com/openziti/zrok/endpoints" + "github.com/openziti/zrok/endpoints/drive" "github.com/openziti/zrok/endpoints/proxy" "github.com/openziti/zrok/endpoints/tcpTunnel" "github.com/openziti/zrok/endpoints/udpTunnel" @@ -123,7 +124,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { proxy.SetCaddyLoggingWriter(mdl) } - requestsChan := make(chan *endpoints.Request, 1024) + requests := make(chan *endpoints.Request, 1024) switch resp.Payload.BackendMode { case "proxy": cfg := &proxy.BackendConfig{ @@ -131,7 +132,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { EndpointAddress: target, ShrToken: shrToken, Insecure: cmd.insecure, - Requests: requestsChan, + Requests: requests, } be, err := proxy.NewBackend(cfg) @@ -153,7 +154,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { IdentityPath: zif, WebRoot: target, ShrToken: shrToken, - Requests: requestsChan, + Requests: requests, } be, err := proxy.NewCaddyWebBackend(cfg) @@ -175,7 +176,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { IdentityPath: zif, EndpointAddress: target, ShrToken: shrToken, - RequestsChan: requestsChan, + RequestsChan: requests, } be, err := tcpTunnel.NewBackend(cfg) @@ -197,7 +198,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { IdentityPath: zif, EndpointAddress: target, ShrToken: shrToken, - RequestsChan: requestsChan, + RequestsChan: requests, } be, err := udpTunnel.NewBackend(cfg) @@ -218,7 +219,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { cfg := &proxy.CaddyfileBackendConfig{ CaddyfilePath: target, Shr: &sdk.Share{Token: shrToken, FrontendEndpoints: []string{resp.Payload.FrontendEndpoint}}, - Requests: requestsChan, + Requests: requests, } be, err := proxy.NewCaddyfileBackend(cfg) @@ -235,6 +236,28 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { } }() + case "drive": + cfg := &drive.BackendConfig{ + IdentityPath: zif, + DriveRoot: target, + ShrToken: shrToken, + Requests: requests, + } + + be, err := drive.NewBackend(cfg) + if err != nil { + if !panicInstead { + tui.Error("error creating drive backend", err) + } + panic(err) + } + + go func() { + if err := be.Run(); err != nil { + logrus.Errorf("error running drive backend: %v", err) + } + }() + default: tui.Error("invalid backend mode", nil) } @@ -249,7 +272,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { } for { select { - case req := <-requestsChan: + case req := <-requests: logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path) } } @@ -261,7 +284,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { go func() { for { select { - case req := <-requestsChan: + case req := <-requests: prg.Send(req) } } @@ -271,6 +294,6 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) { tui.Error("An error occurred", err) } - close(requestsChan) + close(requests) } } diff --git a/controller/share.go b/controller/share.go index 860c106b..5a6b31a8 100644 --- a/controller/share.go +++ b/controller/share.go @@ -19,8 +19,6 @@ func newShareHandler() *shareHandler { } func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zrok.Principal) middleware.Responder { - logrus.Info("handling") - trx, err := str.Begin() if err != nil { logrus.Errorf("error starting transaction: %v", err) diff --git a/controller/store/sql/postgresql/014_v0_4_9_backend_mode_drive.sql b/controller/store/sql/postgresql/014_v0_4_9_backend_mode_drive.sql new file mode 100644 index 00000000..dce7a1e6 --- /dev/null +++ b/controller/store/sql/postgresql/014_v0_4_9_backend_mode_drive.sql @@ -0,0 +1,3 @@ +-- +migrate Up + +alter type backend_mode add value 'drive'; \ No newline at end of file diff --git a/controller/store/sql/sqlite3/014_v0_4_9_backend_mode_drive.sql b/controller/store/sql/sqlite3/014_v0_4_9_backend_mode_drive.sql new file mode 100644 index 00000000..9a78ea27 --- /dev/null +++ b/controller/store/sql/sqlite3/014_v0_4_9_backend_mode_drive.sql @@ -0,0 +1,54 @@ +-- +migrate Up + +alter table shares rename to shares_old; +create table shares ( + id integer primary key, + environment_id integer constraint fk_environments_shares references environments on delete cascade, + z_id string not null unique, + token string not null unique, + share_mode string not null, + backend_mode string not null, + frontend_selection string, + frontend_endpoint string, + backend_proxy_endpoint string, + reserved boolean not null default(false), + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), deleted boolean not null default(false), + + constraint chk_z_id check (z_id <> ''), + constraint chk_token check (token <> ''), + constraint chk_share_mode check (share_mode == 'public' or share_mode == 'private'), + constraint chk_backend_mode check (backend_mode == 'proxy' or backend_mode == 'web' or backend_mode == 'tcpTunnel' or backend_mode == 'udpTunnel' or backend_mode == 'caddy' or backend_mode == 'drive') +); +insert into shares select * from shares_old; +drop table shares_old; + +alter table frontends rename to frontends_old; +create table frontends ( + id integer primary key, + environment_id integer references environments(id), + token varchar(32) not null unique, + z_id varchar(32) not null, + public_name varchar(64) unique, + url_template varchar(1024), + reserved boolean not null default(false), + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + deleted boolean not null default(false), + private_share_id integer references shares(id) +); +insert into frontends select * from frontends_old; +drop table frontends_old; + +alter table share_limit_journal rename to share_limit_journal_old; +create table share_limit_journal ( + id integer primary key, + share_id integer references shares(id), + rx_bytes bigint not null, + tx_bytes bigint not null, + action limit_action_type not null, + created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')), + updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')) +); +insert into share_limit_journal select * from share_limit_journal_old; +drop table share_limit_journal_old; \ No newline at end of file diff --git a/endpoints/drive/backend.go b/endpoints/drive/backend.go new file mode 100644 index 00000000..f9147e8c --- /dev/null +++ b/endpoints/drive/backend.go @@ -0,0 +1,72 @@ +package drive + +import ( + "fmt" + "github.com/openziti/sdk-golang/ziti" + "github.com/openziti/sdk-golang/ziti/edge" + "github.com/openziti/zrok/endpoints" + "github.com/pkg/errors" + "golang.org/x/net/webdav" + "net/http" + "time" +) + +type BackendConfig struct { + IdentityPath string + DriveRoot string + ShrToken string + Requests chan *endpoints.Request +} + +type Backend struct { + cfg *BackendConfig + listener edge.Listener + handler http.Handler +} + +func NewBackend(cfg *BackendConfig) (*Backend, error) { + options := ziti.ListenOptions{ + ConnectTimeout: 5 * time.Minute, + MaxConnections: 64, + } + zcfg, err := ziti.NewConfigFromFile(cfg.IdentityPath) + if err != nil { + return nil, errors.Wrap(err, "error loading ziti identity") + } + zctx, err := ziti.NewContext(zcfg) + if err != nil { + return nil, errors.Wrap(err, "error loading ziti context") + } + listener, err := zctx.ListenWithOptions(cfg.ShrToken, &options) + if err != nil { + return nil, err + } + + handler := &webdav.Handler{ + FileSystem: webdav.Dir(cfg.DriveRoot), + LockSystem: webdav.NewMemLS(), + Logger: func(r *http.Request, err error) { + if cfg.Requests != nil { + cfg.Requests <- &endpoints.Request{ + Stamp: time.Now(), + RemoteAddr: fmt.Sprintf("%v", r.Header["X-Real-Ip"]), + Method: r.Method, + Path: r.URL.String(), + } + } + }, + } + + return &Backend{ + cfg: cfg, + listener: listener, + handler: handler, + }, nil +} + +func (b *Backend) Run() error { + if err := http.Serve(b.listener, b.handler); err != nil { + return err + } + return nil +} diff --git a/endpoints/proxy/backend.go b/endpoints/proxy/backend.go index 8b8b62e1..06858ada 100644 --- a/endpoints/proxy/backend.go +++ b/endpoints/proxy/backend.go @@ -25,7 +25,6 @@ type BackendConfig struct { type Backend struct { cfg *BackendConfig - requests func() int32 listener edge.Listener handler http.Handler } @@ -56,7 +55,6 @@ func NewBackend(cfg *BackendConfig) (*Backend, error) { handler := util.NewProxyHandler(proxy) return &Backend{ cfg: cfg, - requests: handler.Requests, listener: listener, handler: handler, }, nil @@ -69,10 +67,6 @@ func (b *Backend) Run() error { return nil } -func (b *Backend) Requests() func() int32 { - return b.requests -} - func newReverseProxy(cfg *BackendConfig) (*httputil.ReverseProxy, error) { targetURL, err := url.Parse(cfg.EndpointAddress) if err != nil { diff --git a/endpoints/proxy/caddyfileBackend.go b/endpoints/proxy/caddyfileBackend.go index 30b98abb..c69d93aa 100644 --- a/endpoints/proxy/caddyfileBackend.go +++ b/endpoints/proxy/caddyfileBackend.go @@ -56,10 +56,6 @@ func (b *CaddyfileBackend) Run() error { return nil } -func (b *CaddyfileBackend) Requests() func() int32 { - return nil -} - func preprocessCaddyfile(inF string, shr *sdk.Share) (string, error) { input, err := os.ReadFile(inF) if err != nil { diff --git a/endpoints/requests.go b/endpoints/requests.go index 883a8d58..6eed6013 100644 --- a/endpoints/requests.go +++ b/endpoints/requests.go @@ -2,10 +2,6 @@ package endpoints import "time" -type RequestHandler interface { - Requests() func() int32 -} - type Request struct { Stamp time.Time RemoteAddr string diff --git a/rest_model_zrok/share_request.go b/rest_model_zrok/share_request.go index c2c1d012..8fddf367 100644 --- a/rest_model_zrok/share_request.go +++ b/rest_model_zrok/share_request.go @@ -28,7 +28,7 @@ type ShareRequest struct { AuthUsers []*AuthUser `json:"authUsers"` // backend mode - // Enum: [proxy web tcpTunnel udpTunnel caddy] + // Enum: [proxy web tcpTunnel udpTunnel caddy drive] BackendMode string `json:"backendMode,omitempty"` // backend proxy endpoint @@ -114,7 +114,7 @@ var shareRequestTypeBackendModePropEnum []interface{} func init() { var res []string - if err := json.Unmarshal([]byte(`["proxy","web","tcpTunnel","udpTunnel","caddy"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["proxy","web","tcpTunnel","udpTunnel","caddy","drive"]`), &res); err != nil { panic(err) } for _, v := range res { @@ -138,6 +138,9 @@ const ( // ShareRequestBackendModeCaddy captures enum value "caddy" ShareRequestBackendModeCaddy string = "caddy" + + // ShareRequestBackendModeDrive captures enum value "drive" + ShareRequestBackendModeDrive string = "drive" ) // prop value enum diff --git a/rest_server_zrok/embedded_spec.go b/rest_server_zrok/embedded_spec.go index ee155ad8..67e51899 100644 --- a/rest_server_zrok/embedded_spec.go +++ b/rest_server_zrok/embedded_spec.go @@ -1472,7 +1472,8 @@ func init() { "web", "tcpTunnel", "udpTunnel", - "caddy" + "caddy", + "drive" ] }, "backendProxyEndpoint": { @@ -3088,7 +3089,8 @@ func init() { "web", "tcpTunnel", "udpTunnel", - "caddy" + "caddy", + "drive" ] }, "backendProxyEndpoint": { diff --git a/sdk/model.go b/sdk/model.go index 4c27ecd9..4eed621b 100644 --- a/sdk/model.go +++ b/sdk/model.go @@ -10,6 +10,7 @@ const ( TcpTunnelBackendMode BackendMode = "tcpTunnel" UdpTunnelBackendMode BackendMode = "udpTunnel" CaddyBackendMode BackendMode = "caddy" + DriveBackendMode BackendMode = "drive" ) type ShareMode string diff --git a/specs/zrok.yml b/specs/zrok.yml index 88539ac1..1f5ebeb8 100644 --- a/specs/zrok.yml +++ b/specs/zrok.yml @@ -971,7 +971,7 @@ definitions: type: string backendMode: type: string - enum: ["proxy", "web", "tcpTunnel", "udpTunnel", "caddy"] + enum: ["proxy", "web", "tcpTunnel", "udpTunnel", "caddy", "drive"] backendProxyEndpoint: type: string authScheme: