From f61b2c908654f83b1304e50fd2420050a6921c58 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Fri, 8 Dec 2023 12:03:07 -0500 Subject: [PATCH] basic unique name implementation (#123) --- cmd/zrok/reserve.go | 8 ++++++++ controller/share.go | 17 +++++++++++++---- ...15_v0_4_19_share_unique_name_constraint.sql | 7 +++++++ go.mod | 3 ++- go.sum | 6 ++++-- sdk/golang/sdk/model.go | 1 + sdk/golang/sdk/share.go | 3 +++ sdk/python/sdk/zrok/zrok/share.py | 2 ++ util/uniqueName.go | 18 ++++++++++++++++++ 9 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 controller/store/sql/postgresql/015_v0_4_19_share_unique_name_constraint.sql create mode 100644 util/uniqueName.go diff --git a/cmd/zrok/reserve.go b/cmd/zrok/reserve.go index 5eab436b..cb1ae2d8 100644 --- a/cmd/zrok/reserve.go +++ b/cmd/zrok/reserve.go @@ -6,6 +6,7 @@ import ( "github.com/openziti/zrok/environment" "github.com/openziti/zrok/sdk/golang/sdk" "github.com/openziti/zrok/tui" + "github.com/openziti/zrok/util" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "slices" @@ -17,6 +18,7 @@ func init() { } type reserveCommand struct { + uniqueName string basicAuth []string frontendSelection []string backendMode string @@ -34,6 +36,7 @@ func newReserveCommand() *reserveCommand { Args: cobra.ExactArgs(2), } command := &reserveCommand{cmd: cmd} + cmd.Flags().StringVarP(&command.uniqueName, "unique-name", "n", "", "A unique name for the reserved share (defaults to generated identifier)") 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 (public|private: proxy, web, caddy, drive) (private: tcpTunnel, udpTunnel)") cmd.Flags().BoolVarP(&command.jsonOutput, "json-output", "j", false, "Emit JSON describing the created reserved share") @@ -56,6 +59,10 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) { tui.Error(fmt.Sprintf("invalid sharing mode for a %s share: %s", sdk.PublicShareMode, cmd.backendMode), nil) } + if cmd.uniqueName != "" && !util.IsValidUniqueName(cmd.uniqueName) { + tui.Error("invalid unique name; must be lowercase alphanumeric, between 4 and 32 characters in length, screened for profanity", nil) + } + var target string switch cmd.backendMode { case "proxy": @@ -95,6 +102,7 @@ func (cmd *reserveCommand) run(_ *cobra.Command, args []string) { req := &sdk.ShareRequest{ Reserved: true, + UniqueName: cmd.uniqueName, BackendMode: sdk.BackendMode(cmd.backendMode), ShareMode: shareMode, BasicAuth: cmd.basicAuth, diff --git a/controller/share.go b/controller/share.go index bcf36360..dbb22c2e 100644 --- a/controller/share.go +++ b/controller/share.go @@ -8,6 +8,7 @@ import ( "github.com/openziti/zrok/rest_model_zrok" "github.com/openziti/zrok/rest_server_zrok/operations/share" "github.com/openziti/zrok/sdk/golang/sdk" + "github.com/openziti/zrok/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -33,14 +34,14 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr found := false for _, env := range envs { if env.ZId == envZId { - logrus.Debugf("found identity '%v' for user '%v'", envZId, principal.Email) + logrus.Debugf("found identity '%v' for account '%v'", envZId, principal.Email) envId = env.Id found = true break } } if !found { - logrus.Errorf("environment '%v' not found for user '%v'", envZId, principal.Email) + logrus.Errorf("environment '%v' not found for account '%v'", envZId, principal.Email) return share.NewShareUnauthorized() } } else { @@ -58,11 +59,21 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr logrus.Error(err) return share.NewShareInternalServerError() } + + reserved := params.Body.Reserved + uniqueName := params.Body.UniqueName shrToken, err := createShareToken() if err != nil { logrus.Error(err) return share.NewShareInternalServerError() } + if reserved && uniqueName != "" { + if !util.IsValidUniqueName(uniqueName) { + logrus.Errorf("invalid unique name '%v' for account '%v'", uniqueName, principal.Email) + return share.NewShareUnprocessableEntity() + } + shrToken = uniqueName + } var shrZId string var frontendEndpoints []string @@ -94,7 +105,6 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr } case string(sdk.PrivateShareMode): - logrus.Info("doing private") shrZId, frontendEndpoints, err = newPrivateResourceAllocator().allocate(envZId, shrToken, params, edge) if err != nil { logrus.Error(err) @@ -108,7 +118,6 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr logrus.Debugf("allocated share '%v'", shrToken) - reserved := params.Body.Reserved sshr := &store.Share{ ZId: shrZId, Token: shrToken, diff --git a/controller/store/sql/postgresql/015_v0_4_19_share_unique_name_constraint.sql b/controller/store/sql/postgresql/015_v0_4_19_share_unique_name_constraint.sql new file mode 100644 index 00000000..42f01743 --- /dev/null +++ b/controller/store/sql/postgresql/015_v0_4_19_share_unique_name_constraint.sql @@ -0,0 +1,7 @@ +-- +migrate Up + +-- remove the old unique index (which did not respect the deleted flag) +ALTER TABLE shares DROP CONSTRAINT shares_token_key; + +-- add a new unique index which only constrains uniqueness for not-deleted rows +CREATE UNIQUE INDEX shares_token_idx ON shares(token) WHERE deleted is false; diff --git a/go.mod b/go.mod index ab9fdf36..41b833c5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/openziti/zrok go 1.20 require ( + github.com/TwiN/go-away v1.6.12 github.com/caddyserver/caddy/v2 v2.7.5-0.20230829153420-ed8bb13c5df7 github.com/charmbracelet/bubbles v0.14.0 github.com/charmbracelet/bubbletea v0.23.1 @@ -226,7 +227,7 @@ require ( golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/term v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.10.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect diff --git a/go.sum b/go.sum index c1454c49..65c9312d 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/TwiN/go-away v1.6.12 h1:80AjDyeTjfQaSFYbALzRcDKMAmxKW0a5PoxwXKZlW2A= +github.com/TwiN/go-away v1.6.12/go.mod h1:MpvIC9Li3minq+CGgbgUDvQ9tDaeW35k5IXZrF9MVas= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= @@ -1792,8 +1794,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/sdk/golang/sdk/model.go b/sdk/golang/sdk/model.go index 4eed621b..4289b84e 100644 --- a/sdk/golang/sdk/model.go +++ b/sdk/golang/sdk/model.go @@ -22,6 +22,7 @@ const ( type ShareRequest struct { Reserved bool + UniqueName string BackendMode BackendMode ShareMode ShareMode Target string diff --git a/sdk/golang/sdk/share.go b/sdk/golang/sdk/share.go index 45feb57f..49fc5606 100644 --- a/sdk/golang/sdk/share.go +++ b/sdk/golang/sdk/share.go @@ -26,6 +26,9 @@ func CreateShare(root env_core.Root, request *ShareRequest) (*Share, error) { return nil, errors.Errorf("unknown share mode '%v'", request.ShareMode) } out.Body.Reserved = request.Reserved + if request.Reserved { + out.Body.UniqueName = request.UniqueName + } if len(request.BasicAuth) > 0 { out.Body.AuthScheme = string(Basic) diff --git a/sdk/python/sdk/zrok/zrok/share.py b/sdk/python/sdk/zrok/zrok/share.py index 602fe442..52f135af 100644 --- a/sdk/python/sdk/zrok/zrok/share.py +++ b/sdk/python/sdk/zrok/zrok/share.py @@ -27,6 +27,8 @@ def CreateShare(root: Root, request: model.ShareRequest) -> model.Share: if request.OauthProvider != "": out.auth_scheme = model.AUTH_SCHEME_OAUTH + + try: zrok = root.Client() except Exception as e: diff --git a/util/uniqueName.go b/util/uniqueName.go new file mode 100644 index 00000000..419f8f1d --- /dev/null +++ b/util/uniqueName.go @@ -0,0 +1,18 @@ +package util + +import ( + goaway "github.com/TwiN/go-away" + "regexp" +) + +// IsValidUniqueName ensures that the string represents a valid unique name. Lowercase alphanumeric only. 4-32 characters. +func IsValidUniqueName(uniqueName string) bool { + match, err := regexp.Match("^[a-z0-9]{4,32}$", []byte(uniqueName)) + if err != nil { + return false + } + if match && goaway.IsProfane(uniqueName) { + return false + } + return match +}