Merge pull request #105 from openziti-test-kitchen/v0.3.0

v0.3.0_rc1
This commit is contained in:
Michael Quigley 2023-01-13 13:34:43 -05:00 committed by GitHub
commit 0d43b5566b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
349 changed files with 24714 additions and 5363 deletions

View File

@ -1,3 +1,42 @@
# v0.3.0 (WiP)
## CLI/zrok Client Changes
### Versioning
The `zrok` client now checks the version of the configured API endpoint before attempting to connect. This means a `zrok` client will only work with the same major/minor versions.
This means that a `v0.3` client will NOT work with a `v0.2` service. This also means that breaking API changes will require a minor revision change. A breaking change made in `v0.3` will provoke a new `v0.4` series to begin.
## API Changes
Naming has been streamlined:
* The `tunnel` operations are all tagged with `service`.
* `tunnel.Tunnel` becomes `service.Share`
* `tunnel.Untunnel` becomes `service.Unshare`
* `TunnelRequest` and `TunnelResponse` become `ShareRequest` and `ShareResponse`
* `UntunnelRequest` becomes `UnshareRequest`.
Sharing now includes the new mode options:
* `ShareRequest` now includes a `ShareMode` enum which includes `public` and `private` values
* `ShareRequest` now includes a `BackendMode` enum which includes `proxy`, `web`, and `dav` values
## Frontend Selection; Private Shares
The `zrok` model has been extended to include support for both a "public share" (exposing a backend through the globally-available `frontend` instances), and also a "private share" (exposing a backend service to a user who instantiates a private, local `frontend`).
### Underlying Schema Changes
* Added new `frontends` table
* Added new `availability_type` enumeration for use in the new `frontends` table
* Made the `account_id` column of the `environments` table `NULL`-able; a `NULL` value in the `account_id` column signifies an "ephemeral" environment
## Loop Test Shutdown Hook
The `zrok test loop` command now includes a shutdown hook to allow premature cancellation of a running test.
# v0.2.18 # v0.2.18
* DEFECT: Token generation has been improved to use an alphabet consisting of `[a-zA-Z0-9]`. Service token generation continues to use a case-insensitive alphabet consisting of `[a-z0-9]` to be DNS-safe. * DEFECT: Token generation has been improved to use an alphabet consisting of `[a-zA-Z0-9]`. Service token generation continues to use a case-insensitive alphabet consisting of `[a-z0-9]` to be DNS-safe.

View File

@ -26,3 +26,5 @@ swagger generate client -P rest_model_zrok.Principal -f "$zrokSpec" -c rest_clie
echo "...generating js client" echo "...generating js client"
openapi -s specs/zrok.yml -o ui/src/api -l js openapi -s specs/zrok.yml -o ui/src/api -l js
git checkout rest_server_zrok/configure_zrok.go

View File

@ -1,21 +0,0 @@
#!/bin/bash
# ctrl-01.zrok.io
ssh -i ~/.ssh/nf-zrok-ubuntu ctrl-01.zrok.io sudo systemctl stop zrok-ctrl
scp -i ~/.ssh/nf-zrok-ubuntu ~/local/zrok/bin/zrok ctrl-01.zrok.io:local/zrok/bin/zrok
ssh -i ~/.ssh/nf-zrok-ubuntu ctrl-01.zrok.io sudo systemctl start zrok-ctrl
# ctrl-02.zrok.io
ssh -i ~/.ssh/nf-zrok-ubuntu ctrl-02.zrok.io sudo systemctl stop zrok-ctrl
scp -i ~/.ssh/nf-zrok-ubuntu ~/local/zrok/bin/zrok ctrl-02.zrok.io:local/zrok/bin/zrok
ssh -i ~/.ssh/nf-zrok-ubuntu ctrl-02.zrok.io sudo systemctl start zrok-ctrl
# in-01.zrok.io
ssh -i ~/.ssh/nf-zrok-ubuntu in-01.zrok.io sudo systemctl stop zrok-http-frontend
scp -i ~/.ssh/nf-zrok-ubuntu ~/local/zrok/bin/zrok in-01.zrok.io:local/zrok/bin/zrok
ssh -i ~/.ssh/nf-zrok-ubuntu in-01.zrok.io sudo systemctl start zrok-http-frontend
# in-02.zrok.io
ssh -i ~/.ssh/nf-zrok-ubuntu in-02.zrok.io sudo systemctl stop zrok-http-frontend
scp -i ~/.ssh/nf-zrok-ubuntu ~/local/zrok/bin/zrok in-02.zrok.io:local/zrok/bin/zrok
ssh -i ~/.ssh/nf-zrok-ubuntu in-02.zrok.io sudo systemctl start zrok-http-frontend

View File

@ -5,10 +5,12 @@ import "fmt"
var Version string var Version string
var Hash string var Hash string
const Series = "v0.3"
func String() string { func String() string {
if Version != "" { if Version != "" {
return fmt.Sprintf("%v [%v]", Version, Hash) return fmt.Sprintf("%v [%v]", Version, Hash)
} else { } else {
return "<developer_build>" return Series + ".x [developer build]"
} }
} }

152
cmd/zrok/accessPrivate.go Normal file
View File

@ -0,0 +1,152 @@
package main
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/endpoints/privateFrontend"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net/url"
"os"
"os/signal"
"syscall"
)
func init() {
accessCmd.AddCommand(newAccessPrivateCommand().cmd)
}
type accessPrivateCommand struct {
cmd *cobra.Command
bindAddress string
}
func newAccessPrivateCommand() *accessPrivateCommand {
cmd := &cobra.Command{
Use: "private <shareToken>",
Short: "Create a private frontend to access a share",
Args: cobra.ExactArgs(1),
}
command := &accessPrivateCommand{cmd: cmd}
cmd.Run = command.run
cmd.Flags().StringVarP(&command.bindAddress, "bind", "b", "127.0.0.1:9191", "The address to bind the private frontend")
return command
}
func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
shrToken := args[0]
endpointUrl, err := url.Parse("http://" + cmd.bindAddress)
if err != nil {
if !panicInstead {
tui.Error("invalid endpoint address", err)
}
panic(err)
}
zrd, err := zrokdir.Load()
if err != nil {
tui.Error("unable to load zrokdir", err)
}
if zrd.Env == nil {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
tui.Error("unable to create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := share.NewAccessParams()
req.Body = &rest_model_zrok.AccessRequest{
ShrToken: shrToken,
EnvZID: zrd.Env.ZId,
}
accessResp, err := zrok.Share.Access(req, auth)
if err != nil {
if !panicInstead {
tui.Error("unable to access", err)
}
panic(err)
}
logrus.Infof("allocated frontend '%v'", accessResp.Payload.FrontendToken)
cfg := privateFrontend.DefaultConfig("backend")
cfg.ShrToken = shrToken
cfg.Address = cmd.bindAddress
cfg.RequestsChan = make(chan *endpoints.Request, 1024)
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cmd.destroy(accessResp.Payload.FrontendToken, zrd.Env.ZId, shrToken, zrok, auth)
os.Exit(0)
}()
frontend, err := privateFrontend.NewHTTP(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create private frontend", err)
}
panic(err)
}
go func() {
if err := frontend.Run(); err != nil {
if !panicInstead {
tui.Error("unable to run frontend", err)
}
}
}()
mdl := newAccessModel(shrToken, endpointUrl.String())
logrus.SetOutput(mdl)
prg := tea.NewProgram(mdl, tea.WithAltScreen())
mdl.prg = prg
go func() {
for {
select {
case req := <-cfg.RequestsChan:
if req != nil {
prg.Send(req)
}
}
}
}()
if _, err := prg.Run(); err != nil {
tui.Error("An error occurred", err)
}
close(cfg.RequestsChan)
cmd.destroy(accessResp.Payload.FrontendToken, zrd.Env.ZId, shrToken, zrok, auth)
}
func (cmd *accessPrivateCommand) destroy(frotendName, envZId, shrToken string, zrok *rest_client_zrok.Zrok, auth runtime.ClientAuthInfoWriter) {
logrus.Debugf("shutting down '%v'", shrToken)
req := share.NewUnaccessParams()
req.Body = &rest_model_zrok.UnaccessRequest{
FrontendToken: frotendName,
ShrToken: shrToken,
EnvZID: envZId,
}
if _, err := zrok.Share.Unaccess(req, auth); err == nil {
logrus.Debugf("shutdown complete")
} else {
logrus.Errorf("error shutting down: %v", err)
}
}

56
cmd/zrok/accessPublic.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"fmt"
"github.com/michaelquigley/cf"
"github.com/openziti-test-kitchen/zrok/endpoints/publicFrontend"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
accessCmd.AddCommand(newAccessPublicCommand().cmd)
}
type accessPublicCommand struct {
cmd *cobra.Command
}
func newAccessPublicCommand() *accessPublicCommand {
cmd := &cobra.Command{
Use: "public [<configPath>]",
Aliases: []string{"fe"},
Short: "Create a public access HTTP frontend",
Args: cobra.RangeArgs(0, 1),
}
command := &accessPublicCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (self *accessPublicCommand) run(_ *cobra.Command, args []string) {
cfg := publicFrontend.DefaultConfig()
if len(args) == 1 {
if err := cfg.Load(args[0]); err != nil {
if !panicInstead {
tui.Error(fmt.Sprintf("unable to load configuration '%v'", args[0]), err)
}
panic(err)
}
}
logrus.Infof(cf.Dump(cfg, cf.DefaultOptions()))
frontend, err := publicFrontend.NewHTTP(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create http frontend", err)
}
panic(err)
}
if err := frontend.Run(); err != nil {
if !panicInstead {
tui.Error("unable to run http frontend", err)
}
panic(err)
}
}

202
cmd/zrok/accessTui.go Normal file
View File

@ -0,0 +1,202 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/wordwrap"
"github.com/openziti-test-kitchen/zrok/endpoints"
"strings"
"time"
)
const accessTuiBacklog = 256
type accessModel struct {
shrToken string
localEndpoint string
requests []*endpoints.Request
log []string
showLog bool
width int
height int
prg *tea.Program
}
type accessLogLine string
func newAccessModel(shrToken, localEndpoint string) *accessModel {
return &accessModel{
shrToken: shrToken,
localEndpoint: localEndpoint,
}
}
func (m *accessModel) Init() tea.Cmd { return nil }
func (m *accessModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case *endpoints.Request:
m.requests = append(m.requests, msg)
if len(m.requests) > accessTuiBacklog {
m.requests = m.requests[1:]
}
case accessLogLine:
m.showLog = true
m.adjustPaneHeights()
m.log = append(m.log, string(msg))
if len(m.log) > accessTuiBacklog {
m.log = m.log[1:]
}
case tea.WindowSizeMsg:
m.width = msg.Width
accessHeaderStyle.Width(m.width - 2)
accessRequestsStyle.Width(m.width - 2)
accessLogStyle.Width(m.width - 2)
m.height = msg.Height
m.adjustPaneHeights()
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "ctrl+l":
return m, tea.ClearScreen
case "l":
m.showLog = !m.showLog
m.adjustPaneHeights()
}
}
return m, nil
}
func (m *accessModel) View() string {
var panes string
if m.showLog {
panes = lipgloss.JoinVertical(lipgloss.Left,
accessRequestsStyle.Render(m.renderRequests()),
accessLogStyle.Render(m.renderLog()),
)
} else {
panes = accessRequestsStyle.Render(m.renderRequests())
}
return lipgloss.JoinVertical(
lipgloss.Left,
accessHeaderStyle.Render(fmt.Sprintf("%v -> %v", m.localEndpoint, m.shrToken)),
panes,
)
}
func (m *accessModel) adjustPaneHeights() {
if !m.showLog {
accessRequestsStyle.Height(m.height - 5)
} else {
splitHeight := m.height - 5
accessRequestsStyle.Height(splitHeight/2 - 1)
accessLogStyle.Height(splitHeight/2 - 1)
}
}
func (m *accessModel) renderRequests() string {
var requestLines []string
for _, req := range m.requests {
reqLine := fmt.Sprintf("%v %v -> %v %v",
timeStyle.Render(req.Stamp.Format(time.RFC850)),
addressStyle.Render(req.RemoteAddr),
m.renderMethod(req.Method),
req.Path,
)
reqLineWrapped := wordwrap.String(reqLine, m.width-2)
splitWrapped := strings.Split(reqLineWrapped, "\n")
for _, splitLine := range splitWrapped {
splitLine := strings.ReplaceAll(splitLine, "\n", "")
if splitLine != "" {
requestLines = append(requestLines, splitLine)
}
}
}
maxRows := accessRequestsStyle.GetHeight()
startRow := 0
if len(requestLines) > maxRows {
startRow = len(requestLines) - maxRows
}
out := ""
for i := startRow; i < len(requestLines); i++ {
outLine := requestLines[i]
if i < len(requestLines)-1 {
outLine += "\n"
}
out += outLine
}
return out
}
func (m *accessModel) renderMethod(method string) string {
switch strings.ToLower(method) {
case "get":
return getStyle.Render(method)
case "post":
return postStyle.Render(method)
default:
return otherMethodStyle.Render(method)
}
}
func (m *accessModel) renderLog() string {
var splitLines []string
for _, line := range m.log {
wrapped := wordwrap.String(line, m.width-2)
wrappedLines := strings.Split(wrapped, "\n")
for _, wrappedLine := range wrappedLines {
splitLine := strings.ReplaceAll(wrappedLine, "\n", "")
if splitLine != "" {
splitLines = append(splitLines, splitLine)
}
}
}
maxRows := accessLogStyle.GetHeight()
startRow := 0
if len(splitLines) > maxRows {
startRow = len(splitLines) - maxRows
}
out := ""
for i := startRow; i < len(splitLines); i++ {
outLine := splitLines[i]
if i < len(splitLines)-1 {
outLine += "\n"
}
out += outLine
}
return out
}
func (m *accessModel) Write(p []byte) (n int, err error) {
in := string(p)
lines := strings.Split(in, "\n")
for _, line := range lines {
cleanLine := strings.ReplaceAll(line, "\n", "")
if cleanLine != "" {
m.prg.Send(accessLogLine(cleanLine))
}
}
return len(p), nil
}
var accessHeaderStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63")).
Align(lipgloss.Center)
var accessRequestsStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63"))
var accessLogStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63"))

View File

@ -0,0 +1,44 @@
package main
import (
"github.com/michaelquigley/cf"
"github.com/openziti-test-kitchen/zrok/controller"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
adminCmd.AddCommand(newAdminBootstrap().cmd)
}
type adminBootstrap struct {
cmd *cobra.Command
skipCtrl bool
skipFrontend bool
}
func newAdminBootstrap() *adminBootstrap {
cmd := &cobra.Command{
Use: "bootstrap <configPath>",
Short: "Bootstrap the underlying Ziti network for zrok",
Args: cobra.ExactArgs(1),
}
command := &adminBootstrap{cmd: cmd}
cmd.Run = command.run
cmd.Flags().BoolVar(&command.skipCtrl, "skip-ctrl", false, "Skip controller (ctrl) identity bootstrapping")
cmd.Flags().BoolVar(&command.skipFrontend, "skip-frontend", false, "Slip frontend identity bootstrapping")
return command
}
func (cmd *adminBootstrap) run(_ *cobra.Command, args []string) {
configPath := args[0]
inCfg, err := controller.LoadConfig(configPath)
if err != nil {
panic(err)
}
logrus.Infof(cf.Dump(inCfg, cf.DefaultOptions()))
if err := controller.Bootstrap(cmd.skipCtrl, cmd.skipFrontend, inCfg); err != nil {
panic(err)
}
logrus.Info("bootstrap complete!")
}

View File

@ -0,0 +1,59 @@
package main
import (
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/admin"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
adminCreateCmd.AddCommand(newAdminCreateFrontendCommand().cmd)
}
type adminCreateFrontendCommand struct {
cmd *cobra.Command
}
func newAdminCreateFrontendCommand() *adminCreateFrontendCommand {
cmd := &cobra.Command{
Use: "frontend <zitiId> <publicName> <urlTemplate>",
Aliases: []string{"fe"},
Short: "Create a global public frontend",
Args: cobra.ExactArgs(3),
}
command := &adminCreateFrontendCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *adminCreateFrontendCommand) run(_ *cobra.Command, args []string) {
zId := args[0]
publicName := args[1]
urlTemplate := args[2]
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
zrok, err := zrd.Client()
if err != nil {
panic(err)
}
req := admin.NewCreateFrontendParams()
req.Body = &rest_model_zrok.CreateFrontendRequest{
ZID: zId,
PublicName: publicName,
URLTemplate: urlTemplate,
}
resp, err := zrok.Admin.CreateFrontend(req, mustGetAdminAuth())
if err != nil {
panic(err)
}
logrus.Infof("created global public frontend '%v'", resp.Payload.Token)
}

View File

@ -0,0 +1,67 @@
package main
import (
"fmt"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/admin"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"os"
)
func init() {
adminCreateCmd.AddCommand(newAdminCreateIdentity().cmd)
}
type adminCreateIdentity struct {
cmd *cobra.Command
}
func newAdminCreateIdentity() *adminCreateIdentity {
cmd := &cobra.Command{
Use: "identity <name>",
Aliases: []string{"id"},
Short: "Create an identity and policies for a public frontend",
Args: cobra.ExactArgs(1),
}
command := &adminCreateIdentity{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *adminCreateIdentity) run(_ *cobra.Command, args []string) {
name := args[0]
zif, err := zrokdir.ZitiIdentityFile(name)
if err != nil {
panic(err)
}
if _, err := os.Stat(zif); err == nil {
logrus.Errorf("identity '%v' already exists at '%v'", name, zif)
os.Exit(1)
}
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
zrok, err := zrd.Client()
if err != nil {
panic(err)
}
req := admin.NewCreateIdentityParams()
req.Body.Name = name
resp, err := zrok.Admin.CreateIdentity(req, mustGetAdminAuth())
if err != nil {
panic(err)
}
if err := zrokdir.SaveZitiIdentity(name, resp.Payload.Cfg); err != nil {
panic(err)
}
fmt.Printf("zrok identity '%v' created with ziti id '%v'\n", name, resp.Payload.Identity)
}

View File

@ -0,0 +1,53 @@
package main
import (
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/admin"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
adminDeleteCmd.AddCommand(newAdminDeleteFrontendCommand().cmd)
}
type adminDeleteFrontendCommand struct {
cmd *cobra.Command
}
func newAdminDeleteFrontendCommand() *adminDeleteFrontendCommand {
cmd := &cobra.Command{
Use: "frontend <frontendToken>",
Aliases: []string{"fe"},
Short: "Delete a global public frontend",
Args: cobra.ExactArgs(1),
}
command := &adminDeleteFrontendCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *adminDeleteFrontendCommand) run(_ *cobra.Command, args []string) {
feToken := args[0]
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
zrok, err := zrd.Client()
if err != nil {
panic(err)
}
req := admin.NewDeleteFrontendParams()
req.Body = &rest_model_zrok.DeleteFrontendRequest{FrontendToken: feToken}
_, err = zrok.Admin.DeleteFrontend(req, mustGetAdminAuth())
if err != nil {
panic(err)
}
logrus.Infof("deleted global frontend '%v'", feToken)
}

View File

@ -8,25 +8,25 @@ import (
) )
func init() { func init() {
rootCmd.AddCommand(newGcCmd().cmd) adminCmd.AddCommand(newAdminGcCommand().cmd)
} }
type gcCmd struct { type adminGcCommand struct {
cmd *cobra.Command cmd *cobra.Command
} }
func newGcCmd() *gcCmd { func newAdminGcCommand() *adminGcCommand {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "gc <configPath>", Use: "gc <configPath>",
Short: "Garbage collect a zrok instance", Short: "Garbage collect a zrok instance",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
} }
c := &gcCmd{cmd: cmd} command := &adminGcCommand{cmd: cmd}
cmd.Run = c.run cmd.Run = command.run
return c return command
} }
func (gc *gcCmd) run(_ *cobra.Command, args []string) { func (gc *adminGcCommand) run(_ *cobra.Command, args []string) {
cfg, err := controller.LoadConfig(args[0]) cfg, err := controller.LoadConfig(args[0])
if err != nil { if err != nil {
panic(err) panic(err)

79
cmd/zrok/adminGenerate.go Normal file
View File

@ -0,0 +1,79 @@
package main
import (
"fmt"
"github.com/jaevor/go-nanoid"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/admin"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
adminCmd.AddCommand(newAdminGenerateCommand().cmd)
}
type adminGenerateCommand struct {
cmd *cobra.Command
amount int
}
func newAdminGenerateCommand() *adminGenerateCommand {
cmd := &cobra.Command{
Use: "generate",
Short: "Generate invite tokens (default: 5)",
Args: cobra.ExactArgs(0),
}
command := &adminGenerateCommand{cmd: cmd}
cmd.Run = command.run
cmd.Flags().IntVar(&command.amount, "amount", 5, "Amount of tokens to generate")
return command
}
func (cmd *adminGenerateCommand) run(_ *cobra.Command, args []string) {
var err error
tokens := make([]string, cmd.amount)
for i := 0; i < int(cmd.amount); i++ {
tokens[i], err = createToken()
if err != nil {
logrus.Error("error creating token", err)
}
}
zrd, err := zrokdir.Load()
if err != nil {
logrus.Error("error loading zrokdir", err)
}
zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
logrus.Error("error creating zrok api client", err)
}
panic(err)
}
req := admin.NewInviteTokenGenerateParams()
req.Body = &rest_model_zrok.InviteTokenGenerateRequest{
Tokens: tokens,
}
_, err = zrok.Admin.InviteTokenGenerate(req, mustGetAdminAuth())
if err != nil {
if !panicInstead {
logrus.Error("error creating invite tokens", err)
}
panic(err)
}
fmt.Printf("generated %d tokens\n", len(tokens))
}
func createToken() (string, error) {
gen, err := nanoid.CustomASCII("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 12)
if err != nil {
return "", err
}
return gen(), nil
}

View File

@ -0,0 +1,67 @@
package main
import (
"fmt"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/admin"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/spf13/cobra"
"os"
"time"
)
func init() {
adminListCmd.AddCommand(newAdminListFrontendsCommand().cmd)
}
type adminListFrontendsCommand struct {
cmd *cobra.Command
}
func newAdminListFrontendsCommand() *adminListFrontendsCommand {
cmd := &cobra.Command{
Use: "frontends",
Aliases: []string{"fes"},
Short: "List global public frontends",
Args: cobra.ExactArgs(0),
}
command := &adminListFrontendsCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *adminListFrontendsCommand) run(_ *cobra.Command, _ []string) {
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
zrok, err := zrd.Client()
if err != nil {
panic(err)
}
req := admin.NewListFrontendsParams()
resp, err := zrok.Admin.ListFrontends(req, mustGetAdminAuth())
if err != nil {
panic(err)
}
fmt.Println()
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleColoredDark)
t.AppendHeader(table.Row{"Token", "zId", "Public Name", "Url Template", "Created At", "Updated At"})
for _, pfe := range resp.Payload {
t.AppendRow(table.Row{
pfe.Token,
pfe.ZID,
pfe.PublicName,
pfe.URLTemplate,
time.UnixMilli(pfe.CreatedAt),
time.UnixMilli(pfe.UpdatedAt),
})
}
t.Render()
fmt.Println()
}

View File

@ -0,0 +1,64 @@
package main
import (
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/admin"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
adminUpdateCmd.AddCommand(newAdminUpdateFrontendCommand().cmd)
}
type adminUpdateFrontendCommand struct {
cmd *cobra.Command
newPublicName string
newUrlTemplate string
}
func newAdminUpdateFrontendCommand() *adminUpdateFrontendCommand {
cmd := &cobra.Command{
Use: "frontend <frontendToken>",
Aliases: []string{"fe"},
Short: "Update a global public frontend",
Args: cobra.ExactArgs(1),
}
command := &adminUpdateFrontendCommand{cmd: cmd}
cmd.Flags().StringVar(&command.newPublicName, "public-name", "", "Specify a new value for the public name")
cmd.Flags().StringVar(&command.newUrlTemplate, "url-template", "", "Specify a new value for the url template")
cmd.Run = command.run
return command
}
func (cmd *adminUpdateFrontendCommand) run(_ *cobra.Command, args []string) {
feToken := args[0]
if cmd.newPublicName == "" && cmd.newUrlTemplate == "" {
panic("must specify at least one of public name or url template")
}
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
zrok, err := zrd.Client()
if err != nil {
panic(err)
}
req := admin.NewUpdateFrontendParams()
req.Body = &rest_model_zrok.UpdateFrontendRequest{
FrontendToken: feToken,
PublicName: cmd.newPublicName,
URLTemplate: cmd.newUrlTemplate,
}
_, err = zrok.Admin.UpdateFrontend(req, mustGetAdminAuth())
if err != nil {
panic(err)
}
logrus.Infof("updated global frontend '%v'", feToken)
}

46
cmd/zrok/configGet.go Normal file
View File

@ -0,0 +1,46 @@
package main
import (
"fmt"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/spf13/cobra"
)
func init() {
configCmd.AddCommand(newConfigGetCommand().cmd)
}
type configGetCommand struct {
cmd *cobra.Command
}
func newConfigGetCommand() *configGetCommand {
cmd := &cobra.Command{
Use: "get <configName>",
Short: "Get a value from the environment config",
Args: cobra.ExactArgs(1),
}
command := &configGetCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *configGetCommand) run(_ *cobra.Command, args []string) {
configName := args[0]
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
switch configName {
case "apiEndpoint":
if zrd.Cfg != nil && zrd.Cfg.ApiEndpoint != "" {
fmt.Printf("apiEndpoint = %v\n", zrd.Cfg.ApiEndpoint)
} else {
fmt.Println("apiEndpoint = <unset>")
}
default:
fmt.Printf("unknown config name '%v'\n", configName)
}
}

58
cmd/zrok/configSet.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/spf13/cobra"
"os"
)
func init() {
configCmd.AddCommand(newConfigSetCommand().cmd)
}
type configSetCommand struct {
cmd *cobra.Command
}
func newConfigSetCommand() *configSetCommand {
cmd := &cobra.Command{
Use: "set <configName> <value>",
Short: "Set a value into the environment config",
Args: cobra.ExactArgs(2),
}
command := &configSetCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *configSetCommand) run(_ *cobra.Command, args []string) {
configName := args[0]
value := args[1]
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
modified := false
switch configName {
case "apiEndpoint":
if zrd.Cfg == nil {
zrd.Cfg = &zrokdir.Config{}
}
zrd.Cfg.ApiEndpoint = value
modified = true
default:
fmt.Printf("unknown config name '%v'\n", configName)
os.Exit(1)
}
if modified {
if err := zrd.Save(); err != nil {
panic(err)
}
fmt.Println("zrok configuration updated")
}
}

View File

@ -3,8 +3,9 @@ package main
import ( import (
"fmt" "fmt"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/identity" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/environment"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir" "github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -29,40 +30,45 @@ func newDisableCommand() *disableCommand {
return command return command
} }
func (cmd *disableCommand) run(_ *cobra.Command, args []string) { func (cmd *disableCommand) run(_ *cobra.Command, _ []string) {
env, err := zrokdir.LoadEnvironment() zrd, err := zrokdir.Load()
if err != nil { if err != nil {
if !panicInstead { if !panicInstead {
showError("could not load environment; not active?", err) tui.Error("unable to load zrokdir", err)
} }
panic(err) panic(err)
} }
zrok, err := zrokdir.ZrokClient(env.ApiEndpoint)
if zrd.Env == nil {
tui.Error("no environment found; nothing to disable!", nil)
}
zrok, err := zrd.Client()
if err != nil { if err != nil {
if !panicInstead { if !panicInstead {
showError("could not create zrok service client", err) tui.Error("could not create zrok client", err)
} }
panic(err) panic(err)
} }
auth := httptransport.APIKeyAuth("X-TOKEN", "header", env.Token) auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := identity.NewDisableParams() req := environment.NewDisableParams()
req.Body = &rest_model_zrok.DisableRequest{ req.Body = &rest_model_zrok.DisableRequest{
Identity: env.ZId, Identity: zrd.Env.ZId,
} }
_, err = zrok.Identity.Disable(req, auth) _, err = zrok.Environment.Disable(req, auth)
if err != nil { if err != nil {
logrus.Warnf("service cleanup failed (%v); will clean up local environment", err) logrus.Warnf("share cleanup failed (%v); will clean up local environment", err)
} }
if err := zrokdir.DeleteEnvironment(); err != nil { if err := zrokdir.DeleteEnvironment(); err != nil {
if !panicInstead { if !panicInstead {
showError("error removing zrok environment", err) tui.Error("error removing zrok environment", err)
} }
panic(err) panic(err)
} }
if err := zrokdir.DeleteZitiIdentity("backend"); err != nil { if err := zrokdir.DeleteZitiIdentity("backend"); err != nil {
if !panicInstead { if !panicInstead {
showError("error removing zrok backend identity", err) tui.Error("error removing zrok backend identity", err)
} }
} }
fmt.Printf("zrok environment '%v' disabled for '%v'\n", env.ZId, env.Token) fmt.Println("zrok environment disabled...")
} }

View File

@ -2,16 +2,18 @@ package main
import ( import (
"fmt" "fmt"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/identity" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/environment"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir" "github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/host"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
user2 "os/user" user2 "os/user"
"strings" "time"
) )
func init() { func init() {
@ -36,11 +38,10 @@ func newEnableCommand() *enableCommand {
} }
func (cmd *enableCommand) run(_ *cobra.Command, args []string) { func (cmd *enableCommand) run(_ *cobra.Command, args []string) {
env, err := zrokdir.LoadEnvironment() zrd, err := zrokdir.Load()
if err == nil { if err != nil {
showError(fmt.Sprintf("you already have an environment '%v' for '%v'", env.ZId, env.Token), nil) panic(err)
} }
token := args[0] token := args[0]
hostName, hostDetail, err := getHost() hostName, hostDetail, err := getHost()
@ -55,38 +56,70 @@ func (cmd *enableCommand) run(_ *cobra.Command, args []string) {
if cmd.description == "<user>@<hostname>" { if cmd.description == "<user>@<hostname>" {
cmd.description = fmt.Sprintf("%v@%v", user.Username, hostName) cmd.description = fmt.Sprintf("%v@%v", user.Username, hostName)
} }
zrok, err := zrd.Client()
zrok, err := zrokdir.ZrokClient(apiEndpoint)
if err != nil { if err != nil {
panic(err) cmd.endpointError(zrd.ApiEndpoint())
tui.Error("error creating service client", err)
} }
auth := httptransport.APIKeyAuth("X-TOKEN", "header", token) auth := httptransport.APIKeyAuth("X-TOKEN", "header", token)
req := identity.NewEnableParams() req := environment.NewEnableParams()
req.Body = &rest_model_zrok.EnableRequest{ req.Body = &rest_model_zrok.EnableRequest{
Description: cmd.description, Description: cmd.description,
Host: hostDetail, Host: hostDetail,
} }
resp, err := zrok.Identity.Enable(req, auth)
var prg *tea.Program
var mdl enableTuiModel
var done = make(chan struct{})
go func() {
mdl = newEnableTuiModel()
mdl.msg = "contacting the zrok service..."
prg = tea.NewProgram(mdl)
if _, err := prg.Run(); err != nil {
fmt.Println(err)
}
close(done)
if mdl.quitting {
os.Exit(1)
}
}()
resp, err := zrok.Environment.Enable(req, auth)
if err != nil { if err != nil {
if !panicInstead { time.Sleep(250 * time.Millisecond)
showError("the zrok service returned an error", err) prg.Send(fmt.Sprintf("the zrok service returned an error: %v\n", err))
} prg.Quit()
panic(err) <-done
cmd.endpointError(zrd.ApiEndpoint())
os.Exit(1)
} }
if err := zrokdir.SaveEnvironment(&zrokdir.Environment{Token: token, ZId: resp.Payload.Identity, ApiEndpoint: apiEndpoint}); err != nil { prg.Send("writing the environment details...")
if !panicInstead { apiEndpoint, _ := zrd.ApiEndpoint()
showError("there was an error saving the new environment", err) zrd.Env = &zrokdir.Environment{Token: token, ZId: resp.Payload.Identity, ApiEndpoint: apiEndpoint}
} if err := zrd.Save(); err != nil {
panic(err) prg.Send(fmt.Sprintf("there was an error saving the new environment: %v", err))
prg.Quit()
<-done
os.Exit(1)
} }
if err := zrokdir.SaveZitiIdentity("backend", resp.Payload.Cfg); err != nil { if err := zrokdir.SaveZitiIdentity("backend", resp.Payload.Cfg); err != nil {
if !panicInstead { prg.Send(fmt.Sprintf("there was an error writing the environment: %v", err))
showError("there was an error writing the environment file", err) prg.Quit()
} <-done
panic(err) os.Exit(1)
} }
fmt.Printf("zrok environment '%v' enabled for '%v'\n", resp.Payload.Identity, token) prg.Send(fmt.Sprintf("the zrok environment was successfully enabled..."))
prg.Quit()
<-done
}
func (cmd *enableCommand) endpointError(apiEndpoint, _ string) {
fmt.Printf("%v\n\n", tui.ErrorStyle.Render("there was a problem enabling your environment!"))
fmt.Printf("you are trying to use the zrok service at: %v\n\n", tui.CodeStyle.Render(apiEndpoint))
fmt.Printf("you can change your zrok service endpoint using this command:\n\n")
fmt.Printf("%v\n\n", tui.CodeStyle.Render("$ zrok config set apiEndpoint <newEndpoint>"))
fmt.Printf("(where newEndpoint is something like: %v)\n\n", tui.CodeStyle.Render("https://some.zrok.io"))
} }
func getHost() (string, string, error) { func getHost() (string, string, error) {
@ -99,14 +132,51 @@ func getHost() (string, string, error) {
return info.Hostname, thisHost, nil return info.Hostname, thisHost, nil
} }
var errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#D90166")).Background(lipgloss.Color("0")) type enableTuiModel struct {
spinner spinner.Model
func showError(msg string, err error) { msg string
errorLabel := errorStyle.Render("ERROR:") quitting bool
if err != nil { }
_, _ = fmt.Fprintf(os.Stderr, "%v %v (%v)\n", errorLabel, msg, strings.TrimSpace(err.Error()))
} else { func newEnableTuiModel() enableTuiModel {
_, _ = fmt.Fprintf(os.Stderr, "%v %v\n", errorLabel, msg) s := spinner.New()
} s.Spinner = spinner.Dot
os.Exit(1) s.Style = tui.WarningStyle
return enableTuiModel{spinner: s}
}
func (m enableTuiModel) Init() tea.Cmd { return m.spinner.Tick }
func (m enableTuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case string:
m.msg = msg
return m, nil
case tea.KeyMsg:
switch msg.String() {
case "q", "esc", "ctrl+c":
m.quitting = true
return m, tea.Quit
default:
return m, nil
}
case struct{}:
return m, tea.Quit
default:
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}
}
func (m enableTuiModel) View() string {
str := fmt.Sprintf("%s %s\n", m.spinner.View(), m.msg)
if m.quitting {
return str
}
return str
} }

View File

@ -1,4 +1,4 @@
package endpoint_ui package endpointUi
import "embed" import "embed"

View File

@ -388,6 +388,8 @@
</div> </div>
<div id="container"> <div id="container">
<div id="info"> <div id="info">
<h1>{{ .RequestedPath }}</h1>
<h2>{{ .Now }}</h2> <h2>{{ .Now }}</h2>
<h3>At This Endpoint:</h3> <h3>At This Endpoint:</h3>

View File

@ -1,238 +0,0 @@
package main
import (
"fmt"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
tb "github.com/nsf/termbox-go"
"github.com/openziti-test-kitchen/zrok/endpoints/backend"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/tunnel"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"
)
func init() {
httpCmd.AddCommand(newHttpBackendCommand().cmd)
}
type httpBackendCommand struct {
quiet bool
basicAuth []string
cmd *cobra.Command
}
func newHttpBackendCommand() *httpBackendCommand {
cmd := &cobra.Command{
Use: "backend <targetEndpoint>",
Aliases: []string{"be"},
Short: "Create an HTTP binding",
Args: cobra.ExactArgs(1),
}
command := &httpBackendCommand{cmd: cmd}
cmd.Flags().BoolVarP(&command.quiet, "quiet", "q", false, "Disable TUI 'chrome' for quiet operation")
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...")
cmd.Run = command.run
return command
}
func (self *httpBackendCommand) run(_ *cobra.Command, args []string) {
targetEndpoint, err := url.Parse(args[0])
if err != nil {
if !panicInstead {
showError("invalid target endpoint URL", err)
}
panic(err)
}
if targetEndpoint.Scheme == "" {
targetEndpoint.Scheme = "https"
}
if !self.quiet {
if err := ui.Init(); err != nil {
if !panicInstead {
showError("unable to initialize user interface", err)
}
panic(err)
}
defer ui.Close()
tb.SetInputMode(tb.InputEsc)
}
env, err := zrokdir.LoadEnvironment()
if err != nil {
ui.Close()
if !panicInstead {
showError("unable to load environment; did you 'zrok enable'?", err)
}
panic(err)
}
zif, err := zrokdir.ZitiIdentityFile("backend")
if err != nil {
ui.Close()
if !panicInstead {
showError("unable to load ziti identity configuration", err)
}
panic(err)
}
cfg := &backend.Config{
IdentityPath: zif,
EndpointAddress: targetEndpoint.String(),
}
zrok, err := zrokdir.ZrokClient(env.ApiEndpoint)
if err != nil {
ui.Close()
if !panicInstead {
showError("unable to create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", env.Token)
req := tunnel.NewTunnelParams()
req.Body = &rest_model_zrok.TunnelRequest{
ZID: env.ZId,
Endpoint: cfg.EndpointAddress,
AuthScheme: string(model.None),
}
if len(self.basicAuth) > 0 {
logrus.Infof("configuring basic auth")
req.Body.AuthScheme = string(model.Basic)
for _, pair := range self.basicAuth {
tokens := strings.Split(pair, ":")
if len(tokens) == 2 {
req.Body.AuthUsers = append(req.Body.AuthUsers, &rest_model_zrok.AuthUser{Username: strings.TrimSpace(tokens[0]), Password: strings.TrimSpace(tokens[1])})
} else {
panic(errors.Errorf("invalid username:password pair '%v'", pair))
}
}
}
resp, err := zrok.Tunnel.Tunnel(req, auth)
if err != nil {
ui.Close()
if !panicInstead {
showError("unable to create tunnel", err)
}
panic(err)
}
cfg.Service = resp.Payload.SvcName
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
self.destroy(env.ZId, cfg, zrok, auth)
os.Exit(0)
}()
httpProxy, err := backend.NewHTTP(cfg)
if err != nil {
ui.Close()
if !panicInstead {
showError("unable to create http backend", err)
}
panic(err)
}
go func() {
if err := httpProxy.Run(); err != nil {
if !panicInstead {
showError("unable to run http proxy", err)
}
panic(err)
}
}()
if !self.quiet {
ui.Clear()
w, h := ui.TerminalDimensions()
p := widgets.NewParagraph()
p.Border = true
p.Title = " access your zrok service "
p.Text = fmt.Sprintf("%v%v", strings.Repeat(" ", (((w-12)-len(resp.Payload.ProxyEndpoint))/2)-1), resp.Payload.ProxyEndpoint)
p.TextStyle = ui.Style{Fg: ui.ColorWhite}
p.PaddingTop = 1
p.SetRect(5, 5, w-10, 10)
lastRequests := float64(0)
var requestData []float64
spk := widgets.NewSparkline()
spk.Title = " requests "
spk.Data = requestData
spk.LineColor = ui.ColorCyan
slg := widgets.NewSparklineGroup(spk)
slg.SetRect(5, 11, w-10, h-5)
ui.Render(p, slg)
ticker := time.NewTicker(time.Second).C
uiEvents := ui.PollEvents()
for {
select {
case e := <-uiEvents:
switch e.Type {
case ui.ResizeEvent:
ui.Clear()
w, h = ui.TerminalDimensions()
p.SetRect(5, 5, w-10, 10)
slg.SetRect(5, 11, w-10, h-5)
ui.Render(p, slg)
case ui.KeyboardEvent:
switch e.ID {
case "q", "<C-c>":
ui.Close()
self.destroy(env.ZId, cfg, zrok, auth)
os.Exit(0)
}
}
case <-ticker:
currentRequests := float64(httpProxy.Requests())
deltaRequests := currentRequests - lastRequests
requestData = append(requestData, deltaRequests)
lastRequests = currentRequests
requestData = append(requestData, deltaRequests)
for len(requestData) > w-17 {
requestData = requestData[1:]
}
spk.Title = fmt.Sprintf(" requests (%d) ", int(currentRequests))
spk.Data = requestData
ui.Render(p, slg)
}
}
} else {
logrus.Infof("access your zrok service: %v", resp.Payload.ProxyEndpoint)
for {
time.Sleep(30 * time.Second)
}
}
}
func (self *httpBackendCommand) destroy(id string, cfg *backend.Config, zrok *rest_client_zrok.Zrok, auth runtime.ClientAuthInfoWriter) {
logrus.Debugf("shutting down '%v'", cfg.Service)
req := tunnel.NewUntunnelParams()
req.Body = &rest_model_zrok.UntunnelRequest{
ZID: id,
SvcName: cfg.Service,
}
if _, err := zrok.Tunnel.Untunnel(req, auth); err == nil {
logrus.Debugf("shutdown complete")
} else {
logrus.Errorf("error shutting down: %v", err)
}
}

View File

@ -1,55 +0,0 @@
package main
import (
"fmt"
"github.com/michaelquigley/cf"
"github.com/openziti-test-kitchen/zrok/endpoints/frontend"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
httpCmd.AddCommand(newHttpFrontendCommand().cmd)
}
type httpFrontendCommand struct {
cmd *cobra.Command
}
func newHttpFrontendCommand() *httpFrontendCommand {
cmd := &cobra.Command{
Use: "frontend [<configPath>]",
Aliases: []string{"fe"},
Short: "Create an HTTP frontend",
Args: cobra.RangeArgs(0, 1),
}
command := &httpFrontendCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (self *httpFrontendCommand) run(_ *cobra.Command, args []string) {
cfg := frontend.DefaultConfig()
if len(args) == 1 {
if err := cfg.Load(args[0]); err != nil {
if !panicInstead {
showError(fmt.Sprintf("unable to load configuration '%v'", args[0]), err)
}
panic(err)
}
}
logrus.Infof(cf.Dump(cfg, cf.DefaultOptions()))
httpListener, err := frontend.NewHTTP(cfg)
if err != nil {
if !panicInstead {
showError("unable to create http frontend", err)
}
panic(err)
}
if err := httpListener.Run(); err != nil {
if !panicInstead {
showError("unable to run http frontend", err)
}
panic(err)
}
}

View File

@ -2,10 +2,17 @@ package main
import ( import (
"fmt" "fmt"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/identity" "os"
"strings"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/account"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/util"
"github.com/openziti-test-kitchen/zrok/zrokdir" "github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/openziti/foundation/v2/term"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -14,7 +21,9 @@ func init() {
} }
type inviteCommand struct { type inviteCommand struct {
cmd *cobra.Command cmd *cobra.Command
token string
tui inviteTui
} }
func newInviteCommand() *inviteCommand { func newInviteCommand() *inviteCommand {
@ -23,42 +32,200 @@ func newInviteCommand() *inviteCommand {
Short: "Invite a new user to zrok", Short: "Invite a new user to zrok",
Args: cobra.ExactArgs(0), Args: cobra.ExactArgs(0),
} }
command := &inviteCommand{cmd: cmd} command := &inviteCommand{
cmd: cmd,
tui: newInviteTui(),
}
cmd.Run = command.run cmd.Run = command.run
cmd.Flags().StringVar(&command.token, "token", "", "Invite token required when zrok running in token store mode")
return command return command
} }
func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) { func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) {
email, err := term.Prompt("New Email: ") zrd, err := zrokdir.Load()
if err != nil { if err != nil {
panic(err) tui.Error("error loading zrokdir", err)
}
confirm, err := term.Prompt("Confirm Email: ")
if err != nil {
panic(err)
}
if confirm != email {
showError("entered emails do not match... aborting!", nil)
} }
zrok, err := zrokdir.ZrokClient(apiEndpoint) zrok, err := zrd.Client()
if err != nil { if err != nil {
if !panicInstead { if !panicInstead {
showError("error creating zrok api client", err) cmd.endpointError(zrd.ApiEndpoint())
} tui.Error("error creating zrok api client", err)
panic(err)
}
req := identity.NewCreateAccountParams()
req.Body = &rest_model_zrok.AccountRequest{
Email: email,
}
_, err = zrok.Identity.CreateAccount(req)
if err != nil {
if !panicInstead {
showError("error creating account", err)
} }
panic(err) panic(err)
} }
fmt.Printf("registration invitation sent to '%v'!\n", email) if _, err := tea.NewProgram(&cmd.tui).Run(); err != nil {
tui.Error("unable to run interface", err)
os.Exit(1)
}
if cmd.tui.done {
email := cmd.tui.inputs[0].Value()
req := account.NewInviteParams()
req.Body = &rest_model_zrok.InviteRequest{
Email: email,
Token: cmd.token,
}
_, err = zrok.Account.Invite(req)
if err != nil {
cmd.endpointError(zrd.ApiEndpoint())
tui.Error("error creating invitation", err)
}
fmt.Printf("invitation sent to '%v'!\n", email)
}
}
func (cmd *inviteCommand) endpointError(apiEndpoint, _ string) {
fmt.Printf("%v\n\n", tui.ErrorStyle.Render("there was a problem creating an invitation!"))
fmt.Printf("you are trying to use the zrok service at: %v\n\n", tui.CodeStyle.Render(apiEndpoint))
fmt.Printf("you can change your zrok service endpoint using this command:\n\n")
fmt.Printf("%v\n\n", tui.CodeStyle.Render("$ zrok config set apiEndpoint <newEndpoint>"))
fmt.Printf("(where newEndpoint is something like: %v)\n\n", tui.CodeStyle.Render("https://some.zrok.io"))
}
type inviteTui struct {
focusIndex int
msg string
inputs []textinput.Model
cursorMode textinput.CursorMode
done bool
msgOk string
msgMismatch string
focusedStyle lipgloss.Style
blurredStyle lipgloss.Style
errorStyle lipgloss.Style
cursorStyle lipgloss.Style
noStyle lipgloss.Style
helpStyle lipgloss.Style
focusedButton string
blurredButton string
}
func newInviteTui() inviteTui {
m := inviteTui{
inputs: make([]textinput.Model, 2),
}
m.focusedStyle = tui.WarningStyle.Copy()
m.blurredStyle = tui.CodeStyle.Copy()
m.errorStyle = tui.ErrorStyle.Copy()
m.cursorStyle = m.focusedStyle.Copy()
m.noStyle = lipgloss.NewStyle()
m.helpStyle = m.blurredStyle.Copy()
m.focusedButton = m.focusedStyle.Copy().Render("[ Submit ]")
m.blurredButton = fmt.Sprintf("[ %v ]", m.blurredStyle.Render("Submit"))
m.msgOk = m.noStyle.Render("enter and confirm your email address...")
m.msg = m.msgOk
m.msgMismatch = m.errorStyle.Render("email is invalid or does not match confirmation...")
var t textinput.Model
for i := range m.inputs {
t = textinput.New()
t.CursorStyle = m.cursorStyle
t.CharLimit = 96
switch i {
case 0:
t.Placeholder = "Email Address"
t.Focus()
t.PromptStyle = m.focusedStyle
t.TextStyle = m.focusedStyle
case 1:
t.Placeholder = "Confirm Email"
}
m.inputs[i] = t
}
return m
}
func (m inviteTui) Init() tea.Cmd { return textinput.Blink }
func (m *inviteTui) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc":
return m, tea.Quit
case "tab", "shift+tab", "enter", "up", "down":
s := msg.String()
if s == "enter" && m.focusIndex == len(m.inputs) {
if util.IsValidEmail(m.inputs[0].Value()) && m.inputs[0].Value() == m.inputs[1].Value() {
m.done = true
return m, tea.Quit
}
m.msg = m.msgMismatch
return m, nil
}
if s == "up" || s == "shift+tab" {
m.msg = m.msgOk
m.focusIndex--
} else {
m.msg = m.msgOk
m.focusIndex++
}
if m.focusIndex > len(m.inputs) {
m.focusIndex = 0
} else if m.focusIndex < 0 {
m.focusIndex = len(m.inputs)
}
cmds := make([]tea.Cmd, len(m.inputs))
for i := 0; i <= len(m.inputs)-1; i++ {
if i == m.focusIndex {
cmds[i] = m.inputs[i].Focus()
m.inputs[i].PromptStyle = m.focusedStyle
m.inputs[i].TextStyle = m.focusedStyle
continue
}
m.inputs[i].Blur()
m.inputs[i].PromptStyle = m.noStyle
m.inputs[i].TextStyle = m.noStyle
}
return m, tea.Batch(cmds...)
}
}
cmd := m.updateInputs(msg)
return m, cmd
}
func (m *inviteTui) updateInputs(msg tea.Msg) tea.Cmd {
cmds := make([]tea.Cmd, len(m.inputs))
for i := range m.inputs {
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
}
return tea.Batch(cmds...)
}
func (m inviteTui) View() string {
var b strings.Builder
b.WriteString(fmt.Sprintf("\n%v\n\n", m.msg))
for i := range m.inputs {
b.WriteString(m.inputs[i].View())
if i < len(m.inputs)-1 {
b.WriteRune('\n')
}
}
button := &m.blurredButton
if m.focusIndex == len(m.inputs) {
button = &m.focusedButton
}
_, _ = fmt.Fprintf(&b, "\n\n%s\n\n", *button)
return b.String()
} }

View File

@ -8,8 +8,9 @@ import (
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/model" "github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok" "github.com/openziti-test-kitchen/zrok/rest_client_zrok"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/tunnel" "github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/util" "github.com/openziti-test-kitchen/zrok/util"
"github.com/openziti-test-kitchen/zrok/zrokdir" "github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/openziti/sdk-golang/ziti" "github.com/openziti/sdk-golang/ziti"
@ -20,6 +21,9 @@ import (
"io" "io"
"math/rand" "math/rand"
"net/http" "net/http"
"os"
"os/signal"
"syscall"
"time" "time"
) )
@ -28,17 +32,18 @@ func init() {
} }
type loopCmd struct { type loopCmd struct {
cmd *cobra.Command cmd *cobra.Command
loopers int loopers int
iterations int iterations int
statusEvery int statusEvery int
timeoutSeconds int timeoutSeconds int
minPayload int minPayload int
maxPayload int maxPayload int
minDwellMs int minDwellMs int
maxDwellMs int maxDwellMs int
minPacingMs int minPacingMs int
maxPacingMs int maxPacingMs int
frontendSelection []string
} }
func newLoopCmd() *loopCmd { func newLoopCmd() *loopCmd {
@ -59,6 +64,7 @@ func newLoopCmd() *loopCmd {
cmd.Flags().IntVar(&r.maxDwellMs, "max-dwell-ms", 1000, "Maximum dwell time in milliseconds") cmd.Flags().IntVar(&r.maxDwellMs, "max-dwell-ms", 1000, "Maximum dwell time in milliseconds")
cmd.Flags().IntVar(&r.minPacingMs, "min-pacing-ms", 0, "Minimum pacing in milliseconds") cmd.Flags().IntVar(&r.minPacingMs, "min-pacing-ms", 0, "Minimum pacing in milliseconds")
cmd.Flags().IntVar(&r.maxPacingMs, "max-pacing-ms", 0, "Maximum pacing in milliseconds") cmd.Flags().IntVar(&r.maxPacingMs, "max-pacing-ms", 0, "Maximum pacing in milliseconds")
cmd.Flags().StringArrayVar(&r.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
return r return r
} }
@ -69,21 +75,32 @@ func (r *loopCmd) run(_ *cobra.Command, _ []string) {
loopers = append(loopers, l) loopers = append(loopers, l)
go l.run() go l.run()
} }
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
for _, looper := range loopers {
looper.stop = true
}
}()
for _, l := range loopers { for _, l := range loopers {
<-l.done <-l.done
} }
totalMismatches := 0 totalMismatches := 0
totalXfer := int64(0) totalXfer := int64(0)
totalLoops := int64(0)
for _, l := range loopers { for _, l := range loopers {
deltaSeconds := l.stopTime.Sub(l.startTime).Seconds() deltaSeconds := l.stopTime.Sub(l.startTime).Seconds()
xfer := int64(float64(l.bytes) / deltaSeconds) xfer := int64(float64(l.bytes) / deltaSeconds)
totalXfer += xfer totalXfer += xfer
totalMismatches += l.mismatches totalMismatches += l.mismatches
xferSec := util.BytesToSize(xfer) xferSec := util.BytesToSize(xfer)
logrus.Infof("looper #%d: %d mismatches, %s/sec", l.id, l.mismatches, xferSec) totalLoops += l.loops
logrus.Infof("looper #%d: %d loops, %d mismatches, %s/sec", l.id, l.loops, l.mismatches, xferSec)
} }
totalXferSec := util.BytesToSize(totalXfer) totalXferSec := util.BytesToSize(totalXfer)
logrus.Infof("total: %d mismatches, %s/sec", totalMismatches, totalXferSec) logrus.Infof("total: %d loops, %d mismatches, %s/sec", totalLoops, totalMismatches, totalXferSec)
os.Exit(0)
} }
type looper struct { type looper struct {
@ -94,13 +111,15 @@ type looper struct {
listener edge.Listener listener edge.Listener
zif string zif string
zrok *rest_client_zrok.Zrok zrok *rest_client_zrok.Zrok
service string shrToken string
proxyEndpoint string proxyEndpoint string
auth runtime.ClientAuthInfoWriter auth runtime.ClientAuthInfoWriter
mismatches int mismatches int
bytes int64 bytes int64
loops int64
startTime time.Time startTime time.Time
stopTime time.Time stopTime time.Time
stop bool
} }
func newLooper(id int, cmd *loopCmd) *looper { func newLooper(id int, cmd *loopCmd) *looper {
@ -116,7 +135,7 @@ func (l *looper) run() {
defer logrus.Infof("stopping #%d", l.id) defer logrus.Infof("stopping #%d", l.id)
l.startup() l.startup()
logrus.Infof("looper #%d, service: %v, frontend: %v", l.id, l.service, l.proxyEndpoint) logrus.Infof("looper #%d, shrToken: %v, frontend: %v", l.id, l.shrToken, l.proxyEndpoint)
go l.serviceListener() go l.serviceListener()
l.dwell() l.dwell()
l.iterate() l.iterate()
@ -134,7 +153,7 @@ func (l *looper) serviceListener() {
ConnectTimeout: 5 * time.Minute, ConnectTimeout: 5 * time.Minute,
MaxConnections: 10, MaxConnections: 10,
} }
if l.listener, err = ziti.NewContextWithConfig(zcfg).ListenWithOptions(l.service, &opts); err == nil { if l.listener, err = ziti.NewContextWithConfig(zcfg).ListenWithOptions(l.shrToken, &opts); err == nil {
if err := http.Serve(l.listener, l); err != nil { if err := http.Serve(l.listener, l); err != nil {
logrus.Errorf("looper #%d, error serving: %v", l.id, err) logrus.Errorf("looper #%d, error serving: %v", l.id, err)
} }
@ -152,33 +171,41 @@ func (l *looper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (l *looper) startup() { func (l *looper) startup() {
logrus.Infof("starting #%d", l.id) logrus.Infof("starting #%d", l.id)
var err error zrd, err := zrokdir.Load()
l.env, err = zrokdir.LoadEnvironment()
if err != nil { if err != nil {
panic(err) panic(err)
} }
if zrd.Env == nil {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
l.env = zrd.Env
l.zif, err = zrokdir.ZitiIdentityFile("backend") l.zif, err = zrokdir.ZitiIdentityFile("backend")
if err != nil { if err != nil {
panic(err) panic(err)
} }
l.zrok, err = zrokdir.ZrokClient(l.env.ApiEndpoint) l.zrok, err = zrd.Client()
if err != nil { if err != nil {
panic(err) panic(err)
} }
l.auth = httptransport.APIKeyAuth("x-token", "header", l.env.Token) l.auth = httptransport.APIKeyAuth("x-token", "header", l.env.Token)
tunnelReq := tunnel.NewTunnelParams() tunnelReq := share.NewShareParams()
tunnelReq.Body = &rest_model_zrok.TunnelRequest{ tunnelReq.Body = &rest_model_zrok.ShareRequest{
ZID: l.env.ZId, EnvZID: l.env.ZId,
Endpoint: fmt.Sprintf("looper#%d", l.id), ShareMode: "public",
AuthScheme: string(model.None), FrontendSelection: l.cmd.frontendSelection,
BackendMode: "proxy",
BackendProxyEndpoint: fmt.Sprintf("looper#%d", l.id),
AuthScheme: string(model.None),
} }
tunnelReq.SetTimeout(60 * time.Second) tunnelReq.SetTimeout(60 * time.Second)
tunnelResp, err := l.zrok.Tunnel.Tunnel(tunnelReq, l.auth) tunnelResp, err := l.zrok.Share.Share(tunnelReq, l.auth)
if err != nil { if err != nil {
panic(err) panic(err)
} }
l.service = tunnelResp.Payload.SvcName l.shrToken = tunnelResp.Payload.ShrToken
l.proxyEndpoint = tunnelResp.Payload.ProxyEndpoint l.proxyEndpoint = tunnelResp.Payload.FrontendProxyEndpoints[0]
} }
func (l *looper) dwell() { func (l *looper) dwell() {
@ -193,7 +220,7 @@ func (l *looper) iterate() {
l.startTime = time.Now() l.startTime = time.Now()
defer func() { l.stopTime = time.Now() }() defer func() { l.stopTime = time.Now() }()
for i := 0; i < l.cmd.iterations; i++ { for i := 0; i < l.cmd.iterations && !l.stop; i++ {
if i > 0 && i%l.cmd.statusEvery == 0 { if i > 0 && i%l.cmd.statusEvery == 0 {
logrus.Infof("looper #%d: iteration #%d", l.id, i) logrus.Infof("looper #%d: iteration #%d", l.id, i)
} }
@ -228,6 +255,7 @@ func (l *looper) iterate() {
pacingMs = rand.Intn(l.cmd.maxPacingMs-l.cmd.minPacingMs) + l.cmd.minPacingMs pacingMs = rand.Intn(l.cmd.maxPacingMs-l.cmd.minPacingMs) + l.cmd.minPacingMs
time.Sleep(time.Duration(pacingMs) * time.Millisecond) time.Sleep(time.Duration(pacingMs) * time.Millisecond)
} }
l.loops++
} }
} }
@ -238,12 +266,12 @@ func (l *looper) shutdown() {
} }
} }
untunnelReq := tunnel.NewUntunnelParams() untunnelReq := share.NewUnshareParams()
untunnelReq.Body = &rest_model_zrok.UntunnelRequest{ untunnelReq.Body = &rest_model_zrok.UnshareRequest{
ZID: l.env.ZId, EnvZID: l.env.ZId,
SvcName: l.service, ShrToken: l.shrToken,
} }
if _, err := l.zrok.Tunnel.Untunnel(untunnelReq, l.auth); err != nil { if _, err := l.zrok.Share.Unshare(untunnelReq, l.auth); err != nil {
logrus.Errorf("error shutting down looper #%d: %v", l.id, err) logrus.Errorf("error shutting down looper #%d: %v", l.id, err)
} }
} }

View File

@ -2,7 +2,6 @@ package main
import ( import (
"github.com/michaelquigley/pfxlog" "github.com/michaelquigley/pfxlog"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
@ -14,8 +13,15 @@ func init() {
pfxlog.GlobalInit(logrus.InfoLevel, pfxlog.DefaultOptions().SetTrimPrefix("github.com/openziti-test-kitchen/")) pfxlog.GlobalInit(logrus.InfoLevel, pfxlog.DefaultOptions().SetTrimPrefix("github.com/openziti-test-kitchen/"))
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")
rootCmd.PersistentFlags().BoolVarP(&panicInstead, "panic", "p", false, "Panic instead of showing pretty errors") rootCmd.PersistentFlags().BoolVarP(&panicInstead, "panic", "p", false, "Panic instead of showing pretty errors")
zrokdir.AddZrokApiEndpointFlag(&apiEndpoint, rootCmd.PersistentFlags()) rootCmd.AddCommand(accessCmd)
rootCmd.AddCommand(httpCmd) adminCmd.AddCommand(adminCreateCmd)
adminCmd.AddCommand(adminDeleteCmd)
adminCmd.AddCommand(adminListCmd)
adminCmd.AddCommand(adminUpdateCmd)
rootCmd.AddCommand(adminCmd)
rootCmd.AddCommand(configCmd)
rootCmd.AddCommand(shareCmd)
rootCmd.AddCommand(testCmd)
} }
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@ -29,11 +35,50 @@ var rootCmd = &cobra.Command{
} }
var verbose bool var verbose bool
var panicInstead bool var panicInstead bool
var apiEndpoint string
var httpCmd = &cobra.Command{ var accessCmd = &cobra.Command{
Use: "http", Use: "access",
Short: "HTTP endpoint operations", Short: "Create frontend access for shares",
}
var adminCmd = &cobra.Command{
Use: "admin",
Short: "Administration and operations functions",
}
var adminCreateCmd = &cobra.Command{
Use: "create",
Short: "Create global resources",
}
var adminDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete global resources",
}
var adminListCmd = &cobra.Command{
Use: "list",
Short: "List global resources",
}
var adminUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update global resources",
}
var configCmd = &cobra.Command{
Use: "config",
Short: "Configure your zrok environment",
}
var shareCmd = &cobra.Command{
Use: "share",
Short: "Create backend access for shares",
}
var testCmd = &cobra.Command{
Use: "test",
Short: "Utilities for testing zrok deployments",
} }
func main() { func main() {

69
cmd/zrok/release.go Normal file
View File

@ -0,0 +1,69 @@
package main
import (
httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(newReleaseCommand().cmd)
}
type releaseCommand struct {
cmd *cobra.Command
}
func newReleaseCommand() *releaseCommand {
cmd := &cobra.Command{
Use: "release <shareToken>",
Short: "Release a reserved share",
Args: cobra.ExactArgs(1),
}
command := &releaseCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *releaseCommand) run(_ *cobra.Command, args []string) {
shrToken := args[0]
zrd, err := zrokdir.Load()
if err != nil {
if !panicInstead {
tui.Error("unable to load zrokdir", err)
}
panic(err)
}
if zrd.Env == nil {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
tui.Error("unable to create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := share.NewUnshareParams()
req.Body = &rest_model_zrok.UnshareRequest{
EnvZID: zrd.Env.ZId,
ShrToken: shrToken,
Reserved: true,
}
if _, err := zrok.Share.Unshare(req, auth); err != nil {
if !panicInstead {
tui.Error("error releasing share", err)
}
panic(err)
}
logrus.Infof("reserved share '%v' released", shrToken)
}

124
cmd/zrok/reserve.go Normal file
View File

@ -0,0 +1,124 @@
package main
import (
httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net/url"
"strings"
)
func init() {
rootCmd.AddCommand(newReserveCommand().cmd)
}
type reserveCommand struct {
basicAuth []string
frontendSelection []string
backendMode string
cmd *cobra.Command
}
func newReserveCommand() *reserveCommand {
cmd := &cobra.Command{
Use: "reserve <public|private> <target>",
Short: "Create a reserved share",
Args: cobra.ExactArgs(2),
}
command := &reserveCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}")
cmd.Run = command.run
return command
}
func (cmd *reserveCommand) run(_ *cobra.Command, args []string) {
shareMode := args[0]
if shareMode != "public" && shareMode != "private" {
tui.Error("invalid sharing mode; expecting 'public' or 'private'", nil)
}
var target string
switch cmd.backendMode {
case "proxy":
targetEndpoint, err := url.Parse(args[1])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
if targetEndpoint.Scheme == "" {
targetEndpoint.Scheme = "https"
}
target = targetEndpoint.String()
case "web":
target = args[1]
}
zrd, err := zrokdir.Load()
if err != nil {
if !panicInstead {
tui.Error("error loading zrokdir", err)
}
panic(err)
}
if zrd.Env == nil {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
tui.Error("unable to create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := share.NewShareParams()
req.Body = &rest_model_zrok.ShareRequest{
EnvZID: zrd.Env.ZId,
ShareMode: shareMode,
BackendMode: cmd.backendMode,
BackendProxyEndpoint: target,
AuthScheme: string(model.None),
Reserved: true,
}
if shareMode == "public" {
req.Body.FrontendSelection = cmd.frontendSelection
}
if len(cmd.basicAuth) > 0 {
logrus.Infof("configuring basic auth")
req.Body.AuthScheme = string(model.Basic)
for _, pair := range cmd.basicAuth {
tokens := strings.Split(pair, ":")
if len(tokens) == 2 {
req.Body.AuthUsers = append(req.Body.AuthUsers, &rest_model_zrok.AuthUser{Username: strings.TrimSpace(tokens[0]), Password: strings.TrimSpace(tokens[1])})
} else {
panic(errors.Errorf("invalid username:password pair '%v'", pair))
}
}
}
resp, err := zrok.Share.Share(req, auth)
if err != nil {
if !panicInstead {
tui.Error("unable to create tunnel", err)
}
panic(err)
}
logrus.Infof("your reserved share token is '%v'", resp.Payload.ShrToken)
for _, fpe := range resp.Payload.FrontendProxyEndpoints {
logrus.Infof("reserved frontend endpoint: %v", fpe)
}
}

253
cmd/zrok/sharePrivate.go Normal file
View File

@ -0,0 +1,253 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend"
"github.com/openziti-test-kitchen/zrok/endpoints/webBackend"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
)
func init() {
shareCmd.AddCommand(newSharePrivateCommand().cmd)
}
type sharePrivateCommand struct {
basicAuth []string
backendMode string
headless bool
cmd *cobra.Command
}
func newSharePrivateCommand() *sharePrivateCommand {
cmd := &cobra.Command{
Use: "private <target>",
Short: "Share a target resource privately",
Args: cobra.ExactArgs(1),
}
command := &sharePrivateCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...")
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Run = command.run
return command
}
func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
var target string
switch cmd.backendMode {
case "proxy":
targetEndpoint, err := url.Parse(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
if targetEndpoint.Scheme == "" {
targetEndpoint.Scheme = "https"
}
target = targetEndpoint.String()
case "web":
target = args[0]
default:
tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web}", cmd.backendMode), nil)
}
zrd, err := zrokdir.Load()
if err != nil {
if !panicInstead {
tui.Error("unable to load zrokdir", err)
}
panic(err)
}
if zrd.Env == nil {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
zif, err := zrokdir.ZitiIdentityFile("backend")
if err != nil {
if !panicInstead {
tui.Error("unable to load ziti identity configuration", err)
}
panic(err)
}
zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
tui.Error("unable to create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := share.NewShareParams()
req.Body = &rest_model_zrok.ShareRequest{
EnvZID: zrd.Env.ZId,
ShareMode: "private",
BackendMode: cmd.backendMode,
BackendProxyEndpoint: target,
AuthScheme: string(model.None),
}
if len(cmd.basicAuth) > 0 {
logrus.Infof("configuring basic auth")
req.Body.AuthScheme = string(model.Basic)
for _, pair := range cmd.basicAuth {
tokens := strings.Split(pair, ":")
if len(tokens) == 2 {
req.Body.AuthUsers = append(req.Body.AuthUsers, &rest_model_zrok.AuthUser{Username: strings.TrimSpace(tokens[0]), Password: strings.TrimSpace(tokens[1])})
} else {
panic(errors.Errorf("invalid username:password pair '%v'", pair))
}
}
}
resp, err := zrok.Share.Share(req, auth)
if err != nil {
if !panicInstead {
tui.Error("unable to create share", err)
}
panic(err)
}
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cmd.destroy(zrd.Env.ZId, resp.Payload.ShrToken, zrok, auth)
os.Exit(0)
}()
requestsChan := make(chan *endpoints.Request, 1024)
switch cmd.backendMode {
case "proxy":
cfg := &proxyBackend.Config{
IdentityPath: zif,
EndpointAddress: target,
ShrToken: resp.Payload.ShrToken,
RequestsChan: requestsChan,
}
_, err = cmd.proxyBackendMode(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create proxy backend handler", err)
}
panic(err)
}
case "web":
cfg := &webBackend.Config{
IdentityPath: zif,
WebRoot: target,
ShrToken: resp.Payload.ShrToken,
RequestsChan: requestsChan,
}
_, err = cmd.webBackendMode(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create web backend handler", err)
}
panic(err)
}
default:
tui.Error("invalid backend mode", nil)
}
if cmd.headless {
logrus.Infof("allow other to access your share with the following command:\nzrok access private %v", resp.Payload.ShrToken)
for {
select {
case req := <-requestsChan:
logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path)
}
}
} else {
shareDescription := fmt.Sprintf("access your share with: %v", tui.CodeStyle.Render(fmt.Sprintf("zrok access private %v", resp.Payload.ShrToken)))
mdl := newShareModel(resp.Payload.ShrToken, []string{shareDescription}, "private", cmd.backendMode)
logrus.SetOutput(mdl)
prg := tea.NewProgram(mdl, tea.WithAltScreen())
mdl.prg = prg
go func() {
for {
select {
case req := <-requestsChan:
prg.Send(req)
}
}
}()
if _, err := prg.Run(); err != nil {
tui.Error("An error occurred", err)
}
close(requestsChan)
cmd.destroy(zrd.Env.ZId, resp.Payload.ShrToken, zrok, auth)
}
}
func (cmd *sharePrivateCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
be, err := proxyBackend.NewBackend(cfg)
if err != nil {
return nil, errors.Wrap(err, "error creating http proxy backend")
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running http proxy backend: %v", err)
}
}()
return be, nil
}
func (cmd *sharePrivateCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
be, err := webBackend.NewBackend(cfg)
if err != nil {
return nil, errors.Wrap(err, "error creating http web backend")
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running http web backend: %v", err)
}
}()
return be, nil
}
func (cmd *sharePrivateCommand) destroy(id string, shrToken string, zrok *rest_client_zrok.Zrok, auth runtime.ClientAuthInfoWriter) {
logrus.Debugf("shutting down '%v'", shrToken)
req := share.NewUnshareParams()
req.Body = &rest_model_zrok.UnshareRequest{
EnvZID: id,
ShrToken: shrToken,
}
if _, err := zrok.Share.Unshare(req, auth); err == nil {
logrus.Debugf("shutdown complete")
} else {
logrus.Errorf("error shutting down: %v", err)
}
}

255
cmd/zrok/sharePublic.go Normal file
View File

@ -0,0 +1,255 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend"
"github.com/openziti-test-kitchen/zrok/endpoints/webBackend"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
)
func init() {
shareCmd.AddCommand(newSharePublicCommand().cmd)
}
type sharePublicCommand struct {
basicAuth []string
frontendSelection []string
backendMode string
headless bool
cmd *cobra.Command
}
func newSharePublicCommand() *sharePublicCommand {
cmd := &cobra.Command{
Use: "public <target>",
Short: "Share a target resource publicly",
Args: cobra.ExactArgs(1),
}
command := &sharePublicCommand{cmd: cmd}
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}")
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
cmd.Run = command.run
return command
}
func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
var target string
switch cmd.backendMode {
case "proxy":
targetEndpoint, err := url.Parse(args[0])
if err != nil {
if !panicInstead {
tui.Error("invalid target endpoint URL", err)
}
panic(err)
}
if targetEndpoint.Scheme == "" {
targetEndpoint.Scheme = "https"
}
target = targetEndpoint.String()
case "web":
target = args[0]
default:
tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web}", cmd.backendMode), nil)
}
zrd, err := zrokdir.Load()
if err != nil {
if !panicInstead {
tui.Error("unable to load zrokdir", err)
}
panic(err)
}
if zrd.Env == nil {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
zif, err := zrokdir.ZitiIdentityFile("backend")
if err != nil {
if !panicInstead {
tui.Error("unable to load ziti identity configuration", err)
}
panic(err)
}
zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
tui.Error("unable to create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := share.NewShareParams()
req.Body = &rest_model_zrok.ShareRequest{
EnvZID: zrd.Env.ZId,
ShareMode: "public",
FrontendSelection: cmd.frontendSelection,
BackendMode: cmd.backendMode,
BackendProxyEndpoint: target,
AuthScheme: string(model.None),
}
if len(cmd.basicAuth) > 0 {
logrus.Infof("configuring basic auth")
req.Body.AuthScheme = string(model.Basic)
for _, pair := range cmd.basicAuth {
tokens := strings.Split(pair, ":")
if len(tokens) == 2 {
req.Body.AuthUsers = append(req.Body.AuthUsers, &rest_model_zrok.AuthUser{Username: strings.TrimSpace(tokens[0]), Password: strings.TrimSpace(tokens[1])})
} else {
panic(errors.Errorf("invalid username:password pair '%v'", pair))
}
}
}
resp, err := zrok.Share.Share(req, auth)
if err != nil {
if !panicInstead {
tui.Error("unable to create share", err)
}
panic(err)
}
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cmd.destroy(zrd.Env.ZId, resp.Payload.ShrToken, zrok, auth)
os.Exit(0)
}()
requestsChan := make(chan *endpoints.Request, 1024)
switch cmd.backendMode {
case "proxy":
cfg := &proxyBackend.Config{
IdentityPath: zif,
EndpointAddress: target,
ShrToken: resp.Payload.ShrToken,
RequestsChan: requestsChan,
}
_, err = cmd.proxyBackendMode(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create proxy backend handler", err)
}
panic(err)
}
case "web":
cfg := &webBackend.Config{
IdentityPath: zif,
WebRoot: target,
ShrToken: resp.Payload.ShrToken,
RequestsChan: requestsChan,
}
_, err = cmd.webBackendMode(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create web backend handler", err)
}
panic(err)
}
default:
tui.Error("invalid backend mode", nil)
}
if cmd.headless {
logrus.Infof("access your zrok share at the following endpoints:\n %v", strings.Join(resp.Payload.FrontendProxyEndpoints, "\n"))
for {
select {
case req := <-requestsChan:
logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path)
}
}
} else {
mdl := newShareModel(resp.Payload.ShrToken, resp.Payload.FrontendProxyEndpoints, "public", cmd.backendMode)
logrus.SetOutput(mdl)
prg := tea.NewProgram(mdl, tea.WithAltScreen())
mdl.prg = prg
go func() {
for {
select {
case req := <-requestsChan:
prg.Send(req)
}
}
}()
if _, err := prg.Run(); err != nil {
tui.Error("An error occurred", err)
}
close(requestsChan)
cmd.destroy(zrd.Env.ZId, resp.Payload.ShrToken, zrok, auth)
}
}
func (cmd *sharePublicCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
be, err := proxyBackend.NewBackend(cfg)
if err != nil {
return nil, errors.Wrap(err, "error creating http proxy backend")
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running http proxy backend: %v", err)
}
}()
return be, nil
}
func (cmd *sharePublicCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
be, err := webBackend.NewBackend(cfg)
if err != nil {
return nil, errors.Wrap(err, "error creating http web backend")
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running http web backend: %v", err)
}
}()
return be, nil
}
func (cmd *sharePublicCommand) destroy(id string, shrToken string, zrok *rest_client_zrok.Zrok, auth runtime.ClientAuthInfoWriter) {
logrus.Debugf("shutting down '%v'", shrToken)
req := share.NewUnshareParams()
req.Body = &rest_model_zrok.UnshareRequest{
EnvZID: id,
ShrToken: shrToken,
}
if _, err := zrok.Share.Unshare(req, auth); err == nil {
logrus.Debugf("shutdown complete")
} else {
logrus.Errorf("error shutting down: %v", err)
}
}

215
cmd/zrok/shareReserved.go Normal file
View File

@ -0,0 +1,215 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti-test-kitchen/zrok/endpoints"
"github.com/openziti-test-kitchen/zrok/endpoints/proxyBackend"
"github.com/openziti-test-kitchen/zrok/endpoints/webBackend"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/metadata"
"github.com/openziti-test-kitchen/zrok/rest_client_zrok/share"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
shareCmd.AddCommand(newShareReservedCommand().cmd)
}
type shareReservedCommand struct {
overrideEndpoint string
headless bool
cmd *cobra.Command
}
func newShareReservedCommand() *shareReservedCommand {
cmd := &cobra.Command{
Use: "reserved <shareToken>",
Short: "Start a backend for a reserved share",
}
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.Run = command.run
return command
}
func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
shrToken := args[0]
var target string
zrd, err := zrokdir.Load()
if err != nil {
if !panicInstead {
tui.Error("error loading zrokdir", err)
}
panic(err)
}
if zrd.Env == nil {
tui.Error("unable to load environment; did you 'zrok enable'?", nil)
}
zrok, err := zrd.Client()
if err != nil {
if !panicInstead {
tui.Error("unable to create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := metadata.NewGetShareDetailParams()
req.ShrToken = shrToken
resp, err := zrok.Metadata.GetShareDetail(req, auth)
if err != nil {
if !panicInstead {
tui.Error("unable to retrieve reserved share", err)
}
panic(err)
}
if target == "" {
target = resp.Payload.BackendProxyEndpoint
}
zif, err := zrokdir.ZitiIdentityFile("backend")
if err != nil {
if !panicInstead {
tui.Error("unable to load ziti identity configuration", err)
}
panic(err)
}
logrus.Infof("sharing target: '%v'", target)
if resp.Payload.BackendProxyEndpoint != target {
upReq := share.NewUpdateShareParams()
upReq.Body = &rest_model_zrok.UpdateShareRequest{
ShrToken: shrToken,
BackendProxyEndpoint: target,
}
if _, err := zrok.Share.UpdateShare(upReq, auth); err != nil {
if !panicInstead {
tui.Error("unable to update backend proxy endpoint", err)
}
panic(err)
}
logrus.Infof("updated backend proxy endpoint to: %v", target)
} else {
logrus.Infof("using existing backend proxy endpoint: %v", target)
}
requestsChan := make(chan *endpoints.Request, 1024)
switch resp.Payload.BackendMode {
case "proxy":
cfg := &proxyBackend.Config{
IdentityPath: zif,
EndpointAddress: target,
ShrToken: shrToken,
RequestsChan: requestsChan,
}
_, err := cmd.proxyBackendMode(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create proxy backend handler", err)
}
panic(err)
}
case "web":
cfg := &webBackend.Config{
IdentityPath: zif,
WebRoot: target,
ShrToken: shrToken,
RequestsChan: requestsChan,
}
_, err := cmd.webBackendMode(cfg)
if err != nil {
if !panicInstead {
tui.Error("unable to create web backend handler", err)
}
panic(err)
}
default:
tui.Error("invalid backend mode", nil)
}
if cmd.headless {
switch resp.Payload.ShareMode {
case "public":
logrus.Infof("access your zrok share: %v", resp.Payload.FrontendEndpoint)
case "private":
logrus.Infof("use this command to access your zrok share: 'zrok access private %v'", shrToken)
}
for {
select {
case req := <-requestsChan:
logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path)
}
}
} else {
var shareDescription string
switch resp.Payload.ShareMode {
case "public":
shareDescription = resp.Payload.FrontendEndpoint
case "private":
shareDescription = fmt.Sprintf("access your share with: %v", tui.CodeStyle.Render(fmt.Sprintf("zrok access private %v", shrToken)))
}
mdl := newShareModel(shrToken, []string{shareDescription}, resp.Payload.ShareMode, resp.Payload.BackendMode)
logrus.SetOutput(mdl)
prg := tea.NewProgram(mdl, tea.WithAltScreen())
mdl.prg = prg
go func() {
for {
select {
case req := <-requestsChan:
prg.Send(req)
}
}
}()
if _, err := prg.Run(); err != nil {
tui.Error("An error occurred", err)
}
close(requestsChan)
}
}
func (cmd *shareReservedCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
be, err := proxyBackend.NewBackend(cfg)
if err != nil {
return nil, errors.Wrap(err, "error creating http proxy backend")
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running http proxy backend: %v", err)
}
}()
return be, nil
}
func (cmd *shareReservedCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
be, err := webBackend.NewBackend(cfg)
if err != nil {
return nil, errors.Wrap(err, "error creating http web backend")
}
go func() {
if err := be.Run(); err != nil {
logrus.Errorf("error running http web backend: %v", err)
}
}()
return be, nil
}

240
cmd/zrok/shareTui.go Normal file
View File

@ -0,0 +1,240 @@
package main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/reflow/wordwrap"
"github.com/openziti-test-kitchen/zrok/endpoints"
"strings"
"time"
)
const shareTuiBacklog = 256
type shareModel struct {
shrToken string
frontendDescriptions []string
shareMode string
backendMode string
requests []*endpoints.Request
log []string
showLog bool
width int
height int
headerHeight int
prg *tea.Program
}
type shareLogLine string
func newShareModel(shrToken string, frontendEndpoints []string, shareMode, backendMode string) *shareModel {
return &shareModel{
shrToken: shrToken,
frontendDescriptions: frontendEndpoints,
shareMode: shareMode,
backendMode: backendMode,
}
}
func (m *shareModel) Init() tea.Cmd { return nil }
func (m *shareModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case *endpoints.Request:
m.requests = append(m.requests, msg)
if len(m.requests) > shareTuiBacklog {
m.requests = m.requests[1:]
}
case shareLogLine:
m.showLog = true
m.adjustPaneHeights()
m.log = append(m.log, string(msg))
if len(m.log) > shareTuiBacklog {
m.log = m.log[1:]
}
case tea.WindowSizeMsg:
m.width = msg.Width
shareHeaderStyle.Width(m.width - 30)
shareConfigStyle.Width(26)
shareRequestsStyle.Width(m.width - 2)
shareLogStyle.Width(m.width - 2)
m.height = msg.Height
m.headerHeight = len(m.frontendDescriptions) + 4
m.adjustPaneHeights()
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "ctrl+l":
return m, tea.ClearScreen
case "l":
m.showLog = !m.showLog
m.adjustPaneHeights()
}
}
return m, nil
}
func (m *shareModel) View() string {
topRow := lipgloss.JoinHorizontal(lipgloss.Top,
shareHeaderStyle.Render(strings.Join(m.frontendDescriptions, "\n")),
shareConfigStyle.Render(m.renderConfig()),
)
var panes string
if m.showLog {
panes = lipgloss.JoinVertical(lipgloss.Left,
shareRequestsStyle.Render(m.renderRequests()),
shareLogStyle.Render(m.renderLog()),
)
} else {
panes = shareRequestsStyle.Render(m.renderRequests())
}
return lipgloss.JoinVertical(lipgloss.Left, topRow, panes)
}
func (m *shareModel) adjustPaneHeights() {
if !m.showLog {
shareRequestsStyle.Height(m.height - m.headerHeight)
} else {
splitHeight := m.height - m.headerHeight
shareRequestsStyle.Height(splitHeight/2 - 1)
shareLogStyle.Height(splitHeight/2 - 1)
}
}
func (m *shareModel) renderConfig() string {
out := "["
if m.shareMode == "public" {
out += shareModePublicStyle.Render(strings.ToUpper(m.shareMode))
} else {
out += shareModePrivateStyle.Render(strings.ToUpper(m.shareMode))
}
out += "] ["
if m.backendMode == "proxy" {
out += backendModeProxyStyle.Render(strings.ToUpper(m.backendMode))
} else {
out += backendModeWebStyle.Render(strings.ToUpper(m.backendMode))
}
out += "]"
return out
}
func (m *shareModel) renderRequests() string {
var requestLines []string
for _, req := range m.requests {
reqLine := fmt.Sprintf("%v %v -> %v %v",
timeStyle.Render(req.Stamp.Format(time.RFC850)),
addressStyle.Render(req.RemoteAddr),
m.renderMethod(req.Method),
req.Path,
)
reqLineWrapped := wordwrap.String(reqLine, m.width-2)
splitWrapped := strings.Split(reqLineWrapped, "\n")
for _, splitLine := range splitWrapped {
splitLine := strings.ReplaceAll(splitLine, "\n", "")
if splitLine != "" {
requestLines = append(requestLines, splitLine)
}
}
}
maxRows := shareRequestsStyle.GetHeight()
startRow := 0
if len(requestLines) > maxRows {
startRow = len(requestLines) - maxRows
}
out := ""
for i := startRow; i < len(requestLines); i++ {
outLine := requestLines[i]
if i < len(requestLines)-1 {
outLine += "\n"
}
out += outLine
}
return out
}
func (m *shareModel) renderMethod(method string) string {
switch strings.ToLower(method) {
case "get":
return getStyle.Render(method)
case "post":
return postStyle.Render(method)
default:
return otherMethodStyle.Render(method)
}
}
func (m *shareModel) renderLog() string {
var splitLines []string
for _, line := range m.log {
wrapped := wordwrap.String(line, m.width-2)
wrappedLines := strings.Split(wrapped, "\n")
for _, wrappedLine := range wrappedLines {
splitLine := strings.ReplaceAll(wrappedLine, "\n", "")
if splitLine != "" {
splitLines = append(splitLines, splitLine)
}
}
}
maxRows := shareLogStyle.GetHeight()
startRow := 0
if len(splitLines) > maxRows {
startRow = len(splitLines) - maxRows
}
out := ""
for i := startRow; i < len(splitLines); i++ {
outLine := splitLines[i]
if i < len(splitLines)-1 {
outLine += "\n"
}
out += outLine
}
return out
}
func (m *shareModel) Write(p []byte) (n int, err error) {
in := string(p)
lines := strings.Split(in, "\n")
for _, line := range lines {
cleanLine := strings.ReplaceAll(line, "\n", "")
if cleanLine != "" {
m.prg.Send(shareLogLine(cleanLine))
}
}
return len(p), nil
}
var shareHeaderStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63")).
Align(lipgloss.Center)
var shareConfigStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63")).
Align(lipgloss.Center)
var shareRequestsStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63"))
var shareLogStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63"))
var shareModePublicStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#0F0"))
var shareModePrivateStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F00"))
var backendModeProxyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
var backendModeWebStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#0CC"))
var timeStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#444"))
var addressStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
var getStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("98"))
var postStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("101"))
var otherMethodStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("166"))

65
cmd/zrok/status.go Normal file
View File

@ -0,0 +1,65 @@
package main
import (
"fmt"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/spf13/cobra"
"os"
)
func init() {
rootCmd.AddCommand(newStatusCommand().cmd)
}
type statusCommand struct {
cmd *cobra.Command
}
func newStatusCommand() *statusCommand {
cmd := &cobra.Command{
Use: "status",
Short: "Show the current environment status",
Aliases: []string{"st"},
Args: cobra.ExactArgs(0),
}
command := &statusCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *statusCommand) run(_ *cobra.Command, _ []string) {
_, _ = fmt.Fprintf(os.Stderr, "\n")
zrd, err := zrokdir.Load()
if err != nil {
tui.Error("unable to load zrokdir", err)
}
_, _ = fmt.Fprintf(os.Stdout, tui.CodeStyle.Render("Config")+":\n\n")
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleColoredDark)
t.AppendHeader(table.Row{"Config", "Value", "Source"})
apiEndpoint, from := zrd.ApiEndpoint()
t.AppendRow(table.Row{"apiEndpoint", apiEndpoint, from})
t.Render()
_, _ = fmt.Fprintf(os.Stderr, "\n")
if zrd.Env == nil {
tui.Warning("Unable to load your local environment!\n")
_, _ = fmt.Fprintf(os.Stderr, "To create a local environment use the %v command.\n", tui.CodeStyle.Render("zrok enable"))
} else {
_, _ = fmt.Fprintf(os.Stdout, tui.CodeStyle.Render("Environment")+":\n\n")
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleColoredDark)
t.AppendHeader(table.Row{"Property", "Value"})
t.AppendRow(table.Row{"Secret Token", zrd.Env.Token})
t.AppendRow(table.Row{"Ziti Identity", zrd.Env.ZId})
t.Render()
}
_, _ = fmt.Fprintf(os.Stdout, "\n")
}

View File

@ -3,7 +3,8 @@ package main
import ( import (
"fmt" "fmt"
"github.com/opentracing/opentracing-go/log" "github.com/opentracing/opentracing-go/log"
"github.com/openziti-test-kitchen/zrok/cmd/zrok/endpoint_ui" "github.com/openziti-test-kitchen/zrok/cmd/zrok/endpointUi"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"html/template" "html/template"
@ -14,12 +15,6 @@ import (
func init() { func init() {
testCmd.AddCommand(newTestEndpointCommand().cmd) testCmd.AddCommand(newTestEndpointCommand().cmd)
rootCmd.AddCommand(testCmd)
}
var testCmd = &cobra.Command{
Use: "test",
Short: "Utilities used for testing zrok",
} }
type testEndpointCommand struct { type testEndpointCommand struct {
@ -37,13 +32,13 @@ func newTestEndpointCommand() *testEndpointCommand {
} }
command := &testEndpointCommand{cmd: cmd} command := &testEndpointCommand{cmd: cmd}
var err error var err error
if command.t, err = template.ParseFS(endpoint_ui.FS, "index.gohtml"); err != nil { if command.t, err = template.ParseFS(endpointUi.FS, "index.gohtml"); err != nil {
if !panicInstead { if !panicInstead {
showError("unable to parse index template", err) tui.Error("unable to parse index template", err)
} }
panic(err) panic(err)
} }
cmd.Flags().StringVarP(&command.address, "address", "a", "0.0.0.0", "The address for the HTTP listener") cmd.Flags().StringVarP(&command.address, "address", "a", "127.0.0.1", "The address for the HTTP listener")
cmd.Flags().Uint16VarP(&command.port, "port", "P", 9090, "The port for the HTTP listener") cmd.Flags().Uint16VarP(&command.port, "port", "P", 9090, "The port for the HTTP listener")
cmd.Run = command.run cmd.Run = command.run
return command return command
@ -53,35 +48,37 @@ func (cmd *testEndpointCommand) run(_ *cobra.Command, _ []string) {
http.HandleFunc("/", cmd.serveIndex) http.HandleFunc("/", cmd.serveIndex)
if err := http.ListenAndServe(fmt.Sprintf("%v:%d", cmd.address, cmd.port), nil); err != nil { if err := http.ListenAndServe(fmt.Sprintf("%v:%d", cmd.address, cmd.port), nil); err != nil {
if !panicInstead { if !panicInstead {
showError("unable to start http listener", err) tui.Error("unable to start http listener", err)
} }
panic(err) panic(err)
} }
} }
func (cmd *testEndpointCommand) serveIndex(w http.ResponseWriter, r *http.Request) { func (cmd *testEndpointCommand) serveIndex(w http.ResponseWriter, r *http.Request) {
logrus.Infof("%v {%v} -> /index.gohtml", r.RemoteAddr, r.Host) logrus.Infof("%v {%v} | %v -> /index.gohtml", r.RemoteAddr, r.Host, r.RequestURI)
if err := cmd.t.Execute(w, newEndpointData(r)); err != nil { if err := cmd.t.Execute(w, newEndpointData(r)); err != nil {
log.Error(err) log.Error(err)
} }
} }
type endpointData struct { type endpointData struct {
Now time.Time RequestedPath string
RemoteAddr string Now time.Time
Host string RemoteAddr string
HostDetail string Host string
Ips string HostDetail string
HostHeader string Ips string
Headers map[string][]string HostHeader string
Headers map[string][]string
} }
func newEndpointData(r *http.Request) *endpointData { func newEndpointData(r *http.Request) *endpointData {
ed := &endpointData{ ed := &endpointData{
Now: time.Now(), RequestedPath: r.RequestURI,
HostHeader: r.Host, Now: time.Now(),
Headers: r.Header, HostHeader: r.Host,
RemoteAddr: r.RemoteAddr, Headers: r.Header,
RemoteAddr: r.RemoteAddr,
} }
ed.getHostInfo() ed.getHostInfo()
ed.getIps() ed.getIps()

15
cmd/zrok/util.go Normal file
View File

@ -0,0 +1,15 @@
package main
import (
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"os"
)
func mustGetAdminAuth() runtime.ClientAuthInfoWriter {
adminToken := os.Getenv("ZROK_ADMIN_TOKEN")
if adminToken == "" {
panic("please set ZROK_ADMIN_TOKEN to a valid admin token for your zrok instance")
}
return httptransport.APIKeyAuth("X-TOKEN", "header", adminToken)
}

View File

@ -2,7 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"github.com/charmbracelet/lipgloss"
"github.com/openziti-test-kitchen/zrok/build" "github.com/openziti-test-kitchen/zrok/build"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -25,5 +27,6 @@ func newVersionCommand() *versionCommand {
} }
func (cmd *versionCommand) run(_ *cobra.Command, _ []string) { func (cmd *versionCommand) run(_ *cobra.Command, _ []string) {
fmt.Println(" _ \n _____ __ ___ | | __\n|_ / '__/ _ \\| |/ /\n / /| | | (_) | < \n/___|_| \\___/|_|\\_\\\n\n" + build.String() + "\n") zrokStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FF00EE"))
fmt.Println(zrokStyle.Render(" _ \n _____ __ ___ | | __\n|_ / '__/ _ \\| |/ /\n / /| | | (_) | < \n/___|_| \\___/|_|\\_\\") + "\n\n" + tui.CodeStyle.Render(build.String()) + "\n")
} }

90
controller/access.go Normal file
View File

@ -0,0 +1,90 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/share"
"github.com/sirupsen/logrus"
)
type accessHandler struct{}
func newAccessHandler() *accessHandler {
return &accessHandler{}
}
func (h *accessHandler) Handle(params share.AccessParams, principal *rest_model_zrok.Principal) middleware.Responder {
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return share.NewAccessInternalServerError()
}
defer func() { _ = tx.Rollback() }()
envZId := params.Body.EnvZID
envId := 0
if envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx); err == nil {
found := false
for _, env := range envs {
if env.ZId == envZId {
logrus.Debugf("found identity '%v' for user '%v'", envZId, principal.Email)
envId = env.Id
found = true
break
}
}
if !found {
logrus.Errorf("environment '%v' not found for user '%v'", envZId, principal.Email)
return share.NewAccessUnauthorized()
}
} else {
logrus.Errorf("error finding environments for account '%v'", principal.Email)
return share.NewAccessNotFound()
}
shrToken := params.Body.ShrToken
sshr, err := str.FindShareWithToken(shrToken, tx)
if err != nil {
logrus.Errorf("error finding share")
return share.NewAccessNotFound()
}
if sshr == nil {
logrus.Errorf("unable to find share '%v' for user '%v'", shrToken, principal.Email)
return share.NewAccessNotFound()
}
feToken, err := createToken()
if err != nil {
logrus.Error(err)
return share.NewAccessInternalServerError()
}
if _, err := str.CreateFrontend(envId, &store.Frontend{Token: feToken, ZId: envZId}, tx); err != nil {
logrus.Errorf("error creating frontend record: %v", err)
return share.NewAccessInternalServerError()
}
edge, err := edgeClient()
if err != nil {
logrus.Error(err)
return share.NewAccessInternalServerError()
}
addlTags := map[string]interface{}{
"zrokEnvironmentZId": envZId,
"zrokFrontendToken": feToken,
"zrokShareToken": shrToken,
}
if err := zrokEdgeSdk.CreateServicePolicyDial(envZId+"-"+sshr.ZId+"-dial", sshr.ZId, []string{envZId}, addlTags, edge); err != nil {
logrus.Errorf("unable to create dial policy: %v", err)
return share.NewAccessInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing frontend record: %v", err)
return share.NewAccessInternalServerError()
}
return share.NewAccessCreated().WithPayload(&rest_model_zrok.AccessResponse{FrontendToken: feToken})
}

View File

@ -1,76 +0,0 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity"
"github.com/sirupsen/logrus"
)
type createAccountHandler struct {
}
func newCreateAccountHandler() *createAccountHandler {
return &createAccountHandler{}
}
func (self *createAccountHandler) Handle(params identity.CreateAccountParams) middleware.Responder {
if params.Body == nil || params.Body.Email == "" {
logrus.Errorf("missing email")
return identity.NewCreateAccountBadRequest().WithPayload("missing email")
}
logrus.Infof("received account request for email '%v'", params.Body.Email)
token, err := createToken()
if err != nil {
logrus.Error(err)
return identity.NewCreateAccountInternalServerError()
}
ar := &store.AccountRequest{
Token: token,
Email: params.Body.Email,
SourceAddress: params.HTTPRequest.RemoteAddr,
}
tx, err := str.Begin()
if err != nil {
logrus.Error(err)
return identity.NewCreateAccountInternalServerError()
}
defer func() { _ = tx.Rollback() }()
if _, err := str.FindAccountWithEmail(params.Body.Email, tx); err == nil {
logrus.Errorf("found account for '%v', cannot process account request", params.Body.Email)
return identity.NewCreateAccountBadRequest()
} else {
logrus.Infof("no account found for '%v': %v", params.Body.Email, err)
}
if oldAr, err := str.FindAccountRequestWithEmail(params.Body.Email, tx); err == nil {
logrus.Warnf("found previous account request for '%v', removing", params.Body.Email)
if err := str.DeleteAccountRequest(oldAr.Id, tx); err != nil {
logrus.Errorf("error deleteing previous account request for '%v': %v", params.Body.Email, err)
return identity.NewCreateAccountInternalServerError()
}
} else {
logrus.Warnf("error finding previous account request for '%v': %v", params.Body.Email, err)
}
if _, err := str.CreateAccountRequest(ar, tx); err != nil {
logrus.Errorf("error creating account request for '%v': %v", params.Body.Email, err)
return identity.NewCreateAccountInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing account request for '%v': %v", params.Body.Email, err)
return identity.NewCreateAccountInternalServerError()
}
if err := sendVerificationEmail(params.Body.Email, token); err != nil {
logrus.Errorf("error sending verification email for '%v': %v", params.Body.Email, err)
return identity.NewCreateAccountInternalServerError()
}
logrus.Infof("account request for '%v' has registration token '%v'", params.Body.Email, ar.Token)
return identity.NewCreateAccountCreated()
}

347
controller/bootstrap.go Normal file
View File

@ -0,0 +1,347 @@
package controller
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/config"
"github.com/openziti/edge/rest_management_api_client/edge_router_policy"
"github.com/openziti/edge/rest_management_api_client/identity"
"github.com/openziti/edge/rest_management_api_client/service"
"github.com/openziti/edge/rest_management_api_client/service_edge_router_policy"
"github.com/openziti/edge/rest_management_api_client/service_policy"
"github.com/openziti/edge/rest_model"
rest_model_edge "github.com/openziti/edge/rest_model"
"github.com/openziti/sdk-golang/ziti"
config2 "github.com/openziti/sdk-golang/ziti/config"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"time"
)
func Bootstrap(skipCtrl, skipFrontend bool, inCfg *Config) error {
cfg = inCfg
if v, err := store.Open(cfg.Store); err == nil {
str = v
} else {
return errors.Wrap(err, "error opening store")
}
logrus.Info("connecting to the ziti edge management api")
edge, err := edgeClient()
if err != nil {
return errors.Wrap(err, "error connecting to the ziti edge management api")
}
var ctrlZId string
if !skipCtrl {
logrus.Info("creating identity for controller ziti access")
if ctrlZId, err = getIdentityId("ctrl"); err == nil {
logrus.Infof("controller identity: %v", ctrlZId)
} else {
ctrlZId, err = bootstrapIdentity("ctrl", edge)
if err != nil {
panic(err)
}
}
if err := assertIdentity(ctrlZId, edge); err != nil {
panic(err)
}
if err := assertErpForIdentity("ctrl", ctrlZId, edge); err != nil {
panic(err)
}
}
var frontendZId string
if !skipFrontend {
logrus.Info("creating identity for frontend ziti access")
if frontendZId, err = getIdentityId("frontend"); err == nil {
logrus.Infof("frontend identity: %v", frontendZId)
} else {
frontendZId, err = bootstrapIdentity("frontend", edge)
if err != nil {
panic(err)
}
}
if err := assertIdentity(frontendZId, edge); err != nil {
panic(err)
}
if err := assertErpForIdentity("frontend", frontendZId, edge); err != nil {
panic(err)
}
tx, err := str.Begin()
if err != nil {
panic(err)
}
defer func() { _ = tx.Rollback() }()
publicFe, err := str.FindFrontendWithZId(frontendZId, tx)
if err != nil {
logrus.Warnf("missing public frontend for ziti id '%v'; please use 'zrok admin create frontend %v public https://{token}.your.dns.name' to create a frontend instance", frontendZId, frontendZId)
} else {
if publicFe.PublicName != nil && publicFe.UrlTemplate != nil {
logrus.Infof("found public frontend entry '%v' (%v) for ziti identity '%v'", *publicFe.PublicName, publicFe.Token, frontendZId)
} else {
logrus.Warnf("found frontend entry for ziti identity '%v'; missing either public name or url template", frontendZId)
}
}
}
if err := assertZrokProxyConfigType(edge); err != nil {
return err
}
var metricsSvcZId string
if metricsSvcZId, err = assertMetricsService(cfg, edge); err != nil {
return err
}
if err := assertMetricsSerp(metricsSvcZId, cfg, edge); err != nil {
return err
}
if !skipCtrl {
if err := assertCtrlMetricsBind(ctrlZId, metricsSvcZId, edge); err != nil {
return err
}
}
if !skipFrontend {
if err := assertFrontendMetricsDial(frontendZId, metricsSvcZId, edge); err != nil {
return err
}
}
return nil
}
func assertZrokProxyConfigType(edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("name=\"%v\"", model.ZrokProxyConfig)
limit := int64(100)
offset := int64(0)
listReq := &config.ListConfigTypesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.Config.ListConfigTypes(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) < 1 {
name := model.ZrokProxyConfig
ct := &rest_model.ConfigTypeCreate{Name: &name}
createReq := &config.CreateConfigTypeParams{ConfigType: ct}
createReq.SetTimeout(30 * time.Second)
createResp, err := edge.Config.CreateConfigType(createReq, nil)
if err != nil {
return err
}
logrus.Infof("created '%v' config type with id '%v'", model.ZrokProxyConfig, createResp.Payload.Data.ID)
} else if len(listResp.Payload.Data) > 1 {
return errors.Errorf("found %d '%v' config types; expected 0 or 1", len(listResp.Payload.Data), model.ZrokProxyConfig)
} else {
logrus.Infof("found '%v' config type with id '%v'", model.ZrokProxyConfig, *(listResp.Payload.Data[0].ID))
}
return nil
}
func getIdentityId(identityName string) (string, error) {
zif, err := zrokdir.ZitiIdentityFile(identityName)
if err != nil {
return "", errors.Wrapf(err, "error opening identity '%v' from zrokdir", identityName)
}
zcfg, err := config2.NewFromFile(zif)
if err != nil {
return "", errors.Wrapf(err, "error loading ziti config from file '%v'", zif)
}
zctx := ziti.NewContextWithConfig(zcfg)
id, err := zctx.GetCurrentIdentity()
if err != nil {
return "", errors.Wrapf(err, "error getting current identity from '%v'", zif)
}
return id.Id, nil
}
func assertIdentity(zId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("id=\"%v\"", zId)
limit := int64(0)
offset := int64(0)
listReq := &identity.ListIdentitiesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.Identity.ListIdentities(listReq, nil)
if err != nil {
return errors.Wrapf(err, "error listing identities for '%v'", zId)
}
if len(listResp.Payload.Data) != 1 {
return errors.Wrapf(err, "found %d identities for '%v'", len(listResp.Payload.Data), zId)
}
logrus.Infof("asserted identity '%v'", zId)
return nil
}
func bootstrapIdentity(name string, edge *rest_management_api_client.ZitiEdgeManagement) (string, error) {
idc, err := zrokEdgeSdk.CreateIdentity(name, rest_model_edge.IdentityTypeDevice, nil, edge)
if err != nil {
return "", errors.Wrapf(err, "error creating '%v' identity", name)
}
zId := idc.Payload.Data.ID
cfg, err := zrokEdgeSdk.EnrollIdentity(zId, edge)
if err != nil {
return "", errors.Wrapf(err, "error enrolling '%v' identity", name)
}
var out bytes.Buffer
enc := json.NewEncoder(&out)
enc.SetEscapeHTML(false)
err = enc.Encode(&cfg)
if err != nil {
return "", errors.Wrapf(err, "error encoding identity config '%v'", name)
}
if err := zrokdir.SaveZitiIdentity(name, out.String()); err != nil {
return "", errors.Wrapf(err, "error saving identity config '%v'", name)
}
return zId, nil
}
func assertErpForIdentity(name, zId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("name=\"%v\" and tags.zrok != null", name)
limit := int64(0)
offset := int64(0)
listReq := &edge_router_policy.ListEdgeRouterPoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.EdgeRouterPolicy.ListEdgeRouterPolicies(listReq, nil)
if err != nil {
return errors.Wrapf(err, "error listing edge router policies for '%v' (%v)", name, zId)
}
if len(listResp.Payload.Data) != 1 {
logrus.Infof("creating erp for '%v' (%v)", name, zId)
if err := zrokEdgeSdk.CreateEdgeRouterPolicy(name, zId, edge); err != nil {
return errors.Wrapf(err, "error creating erp for '%v' (%v)", name, zId)
}
}
logrus.Infof("asserted erps for '%v' (%v)", name, zId)
return nil
}
func assertMetricsService(cfg *Config, edge *rest_management_api_client.ZitiEdgeManagement) (string, error) {
filter := fmt.Sprintf("name=\"%v\" and tags.zrok != null", cfg.Metrics.ServiceName)
limit := int64(0)
offset := int64(0)
listReq := &service.ListServicesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.Service.ListServices(listReq, nil)
if err != nil {
return "", errors.Wrapf(err, "error listing '%v' service", cfg.Metrics.ServiceName)
}
var svcZId string
if len(listResp.Payload.Data) != 1 {
logrus.Infof("creating '%v' service", cfg.Metrics.ServiceName)
svcZId, err = zrokEdgeSdk.CreateService("metrics", nil, nil, edge)
if err != nil {
return "", errors.Wrapf(err, "error creating '%v' service", cfg.Metrics.ServiceName)
}
} else {
svcZId = *listResp.Payload.Data[0].ID
}
logrus.Infof("asserted '%v' service (%v)", cfg.Metrics.ServiceName, svcZId)
return svcZId, nil
}
func assertMetricsSerp(metricsSvcZId string, cfg *Config, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("allOf(serviceRoles) = \"@%v\" and allOf(edgeRouterRoles) = \"#all\" and tags.zrok != null", metricsSvcZId)
limit := int64(0)
offset := int64(0)
listReq := &service_edge_router_policy.ListServiceEdgeRouterPoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.ServiceEdgeRouterPolicy.ListServiceEdgeRouterPolicies(listReq, nil)
if err != nil {
return errors.Wrapf(err, "error listing '%v' serps", cfg.Metrics.ServiceName)
}
if len(listResp.Payload.Data) != 1 {
logrus.Infof("creating '%v' serp", cfg.Metrics.ServiceName)
_, err := zrokEdgeSdk.CreateServiceEdgeRouterPolicy(cfg.Metrics.ServiceName, metricsSvcZId, nil, edge)
if err != nil {
return errors.Wrapf(err, "error creating '%v' serp", cfg.Metrics.ServiceName)
}
}
logrus.Infof("asserted '%v' serp", cfg.Metrics.ServiceName)
return nil
}
func assertCtrlMetricsBind(ctrlZId, metricsSvcZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("allOf(serviceRoles) = \"@%v\" and allOf(identityRoles) = \"@%v\" and type = 2 and tags.zrok != null", metricsSvcZId, ctrlZId)
limit := int64(0)
offset := int64(0)
listReq := &service_policy.ListServicePoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.ServicePolicy.ListServicePolicies(listReq, nil)
if err != nil {
return errors.Wrapf(err, "error listing 'ctrl-metrics-bind' service policy")
}
if len(listResp.Payload.Data) != 1 {
logrus.Info("creating 'ctrl-metrics-bind' service policy")
if err = zrokEdgeSdk.CreateServicePolicyBind("ctrl-metrics-bind", metricsSvcZId, ctrlZId, nil, edge); err != nil {
return errors.Wrap(err, "error creating 'ctrl-metrics-bind' service policy")
}
}
logrus.Infof("asserted 'ctrl-metrics-bind' service policy")
return nil
}
func assertFrontendMetricsDial(frontendZId, metricsSvcZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("allOf(serviceRoles) = \"@%v\" and allOf(identityRoles) = \"@%v\" and type = 1 and tags.zrok != null", metricsSvcZId, frontendZId)
limit := int64(0)
offset := int64(0)
listReq := &service_policy.ListServicePoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.ServicePolicy.ListServicePolicies(listReq, nil)
if err != nil {
return errors.Wrapf(err, "error listing 'frontend-metrics-dial' service policy")
}
if len(listResp.Payload.Data) != 1 {
logrus.Info("creating 'frontend-metrics-dial' service policy")
if err = zrokEdgeSdk.CreateServicePolicyDial("frontend-metrics-dial", metricsSvcZId, []string{frontendZId}, nil, edge); err != nil {
return errors.Wrap(err, "error creating 'frontend-metrics-dial' service policy")
}
}
logrus.Infof("asserted 'frontend-metrics-dial' service policy")
return nil
}

View File

@ -4,17 +4,27 @@ import (
"github.com/michaelquigley/cf" "github.com/michaelquigley/cf"
"github.com/openziti-test-kitchen/zrok/controller/store" "github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/pkg/errors" "github.com/pkg/errors"
"time"
) )
const ConfigVersion = 1
type Config struct { type Config struct {
V int
Admin *AdminConfig
Endpoint *EndpointConfig Endpoint *EndpointConfig
Proxy *ProxyConfig
Email *EmailConfig Email *EmailConfig
Influx *InfluxConfig
Limits *LimitsConfig
Maintenance *MaintenanceConfig
Metrics *MetricsConfig
Registration *RegistrationConfig Registration *RegistrationConfig
Store *store.Config Store *store.Config
Ziti *ZitiConfig Ziti *ZitiConfig
Metrics *MetricsConfig }
Influx *InfluxConfig
type AdminConfig struct {
Secrets []string `cf:"+secret"`
} }
type EndpointConfig struct { type EndpointConfig struct {
@ -22,11 +32,6 @@ type EndpointConfig struct {
Port int Port int
} }
type ProxyConfig struct {
UrlTemplate string
Identities []string
}
type EmailConfig struct { type EmailConfig struct {
Host string Host string
Port int Port int
@ -37,6 +42,7 @@ type EmailConfig struct {
type RegistrationConfig struct { type RegistrationConfig struct {
EmailFrom string EmailFrom string
RegistrationUrlTemplate string RegistrationUrlTemplate string
TokenStrategy string
} }
type ZitiConfig struct { type ZitiConfig struct {
@ -56,10 +62,49 @@ type InfluxConfig struct {
Token string `cf:"+secret"` Token string `cf:"+secret"`
} }
type MaintenanceConfig struct {
Registration *RegistrationMaintenanceConfig
}
type RegistrationMaintenanceConfig struct {
ExpirationTimeout time.Duration
CheckFrequency time.Duration
BatchLimit int
}
const Unlimited = -1
type LimitsConfig struct {
Environments int
Shares int
}
func DefaultConfig() *Config {
return &Config{
Limits: &LimitsConfig{
Environments: Unlimited,
Shares: Unlimited,
},
Metrics: &MetricsConfig{
ServiceName: "metrics",
},
Maintenance: &MaintenanceConfig{
Registration: &RegistrationMaintenanceConfig{
ExpirationTimeout: time.Hour * 24,
CheckFrequency: time.Hour,
BatchLimit: 500,
},
},
}
}
func LoadConfig(path string) (*Config, error) { func LoadConfig(path string) (*Config, error) {
cfg := &Config{} cfg := DefaultConfig()
if err := cf.BindYaml(cfg, path, cf.DefaultOptions()); err != nil { if err := cf.BindYaml(cfg, path, cf.DefaultOptions()); err != nil {
return nil, errors.Wrapf(err, "error loading controller config '%v'", path) return nil, errors.Wrapf(err, "error loading controller config '%v'", path)
} }
if cfg.V != ConfigVersion {
return nil, errors.Errorf("expecting configuration version '%v', your configuration is version '%v'; please see zrok.io for changelog and configuration documentation", ConfigVersion, cfg.V)
}
return cfg, nil return cfg, nil
} }

View File

@ -1,12 +1,14 @@
package controller package controller
import ( import (
"context"
"github.com/go-openapi/loads" "github.com/go-openapi/loads"
influxdb2 "github.com/influxdata/influxdb-client-go/v2" influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/openziti-test-kitchen/zrok/controller/store" "github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok" "github.com/openziti-test-kitchen/zrok/rest_server_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/metadata" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/metadata"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -25,17 +27,28 @@ func Run(inCfg *Config) error {
} }
api := operations.NewZrokAPI(swaggerSpec) api := operations.NewZrokAPI(swaggerSpec)
api.KeyAuth = ZrokAuthenticate api.KeyAuth = newZrokAuthenticator(cfg).authenticate
api.IdentityCreateAccountHandler = newCreateAccountHandler() api.AccountInviteHandler = newInviteHandler(cfg)
api.IdentityEnableHandler = newEnableHandler() api.AccountLoginHandler = account.LoginHandlerFunc(loginHandler)
api.IdentityDisableHandler = newDisableHandler() api.AccountRegisterHandler = newRegisterHandler()
api.IdentityLoginHandler = identity.LoginHandlerFunc(loginHandler) api.AccountVerifyHandler = newVerifyHandler()
api.IdentityRegisterHandler = newRegisterHandler() api.AdminCreateFrontendHandler = newCreateFrontendHandler()
api.IdentityVerifyHandler = newVerifyHandler() api.AdminCreateIdentityHandler = newCreateIdentityHandler()
api.AdminDeleteFrontendHandler = newDeleteFrontendHandler()
api.AdminInviteTokenGenerateHandler = newInviteTokenGenerateHandler()
api.AdminListFrontendsHandler = newListFrontendsHandler()
api.AdminUpdateFrontendHandler = newUpdateFrontendHandler()
api.EnvironmentEnableHandler = newEnableHandler(cfg.Limits)
api.EnvironmentDisableHandler = newDisableHandler()
api.MetadataGetEnvironmentDetailHandler = newEnvironmentDetailHandler()
api.MetadataGetShareDetailHandler = newShareDetailHandler()
api.MetadataOverviewHandler = metadata.OverviewHandlerFunc(overviewHandler) api.MetadataOverviewHandler = metadata.OverviewHandlerFunc(overviewHandler)
api.MetadataVersionHandler = metadata.VersionHandlerFunc(versionHandler) api.MetadataVersionHandler = metadata.VersionHandlerFunc(versionHandler)
api.TunnelTunnelHandler = newTunnelHandler() api.ShareAccessHandler = newAccessHandler()
api.TunnelUntunnelHandler = newUntunnelHandler() api.ShareShareHandler = newShareHandler(cfg.Limits)
api.ShareUnaccessHandler = newUnaccessHandler()
api.ShareUnshareHandler = newUnshareHandler()
api.ShareUpdateShareHandler = newUpdateShareHandler()
if err := controllerStartup(); err != nil { if err := controllerStartup(); err != nil {
return err return err
@ -60,6 +73,15 @@ func Run(inCfg *Config) error {
}() }()
} }
ctx, cancel := context.WithCancel(context.Background())
defer func() {
cancel()
}()
if cfg.Maintenance != nil && cfg.Maintenance.Registration != nil {
go newMaintenanceAgent(ctx, cfg.Maintenance).run()
}
server := rest_server_zrok.NewServer(api) server := rest_server_zrok.NewServer(api)
defer func() { _ = server.Shutdown() }() defer func() { _ = server.Shutdown() }()
server.Host = cfg.Endpoint.Host server.Host = cfg.Endpoint.Host

View File

@ -0,0 +1,75 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/admin"
"github.com/sirupsen/logrus"
)
type createFrontendHandler struct{}
func newCreateFrontendHandler() *createFrontendHandler {
return &createFrontendHandler{}
}
func (h *createFrontendHandler) Handle(params admin.CreateFrontendParams, principal *rest_model_zrok.Principal) middleware.Responder {
if !principal.Admin {
logrus.Errorf("invalid admin principal")
return admin.NewCreateFrontendUnauthorized()
}
client, err := edgeClient()
if err != nil {
logrus.Errorf("error getting edge client: %v", err)
return admin.NewCreateFrontendInternalServerError()
}
zId := params.Body.ZID
detail, err := zrokEdgeSdk.GetIdentityByZId(zId, client)
if err != nil {
logrus.Errorf("error getting identity details for '%v': %v", zId, err)
return admin.NewCreateFrontendInternalServerError()
}
if len(detail.Payload.Data) != 1 {
logrus.Errorf("expected a single identity to be returned for '%v'", zId)
return admin.NewCreateFrontendNotFound()
}
logrus.Infof("found frontend identity '%v'", *detail.Payload.Data[0].Name)
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return admin.NewCreateFrontendInternalServerError()
}
defer func() { _ = tx.Rollback() }()
feToken, err := createToken()
if err != nil {
logrus.Errorf("error creating frontend token: %v", err)
return admin.NewCreateFrontendInternalServerError()
}
fe := &store.Frontend{
Token: feToken,
ZId: params.Body.ZID,
PublicName: &params.Body.PublicName,
UrlTemplate: &params.Body.URLTemplate,
Reserved: true,
}
if _, err := str.CreateGlobalFrontend(fe, tx); err != nil {
logrus.Errorf("error creating frontend record: %v", err)
return admin.NewCreateFrontendInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing frontend record: %v", err)
return admin.NewCreateFrontendInternalServerError()
}
logrus.Infof("created global frontend '%v' with public name '%v'", fe.Token, *fe.PublicName)
return admin.NewCreateFrontendCreated().WithPayload(&rest_model_zrok.CreateFrontendResponse{Token: feToken})
}

View File

@ -0,0 +1,94 @@
package controller
import (
"bytes"
"encoding/json"
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/admin"
"github.com/openziti/edge/rest_management_api_client/service"
rest_model_edge "github.com/openziti/edge/rest_model"
"github.com/sirupsen/logrus"
"time"
)
type createIdentityHandler struct{}
func newCreateIdentityHandler() *createIdentityHandler {
return &createIdentityHandler{}
}
func (h *createIdentityHandler) Handle(params admin.CreateIdentityParams, principal *rest_model_zrok.Principal) middleware.Responder {
name := params.Body.Name
if !principal.Admin {
logrus.Errorf("invalid admin principal")
return admin.NewCreateIdentityUnauthorized()
}
edge, err := edgeClient()
if err != nil {
logrus.Errorf("error getting edge client: %v", err)
return admin.NewCreateIdentityInternalServerError()
}
idc, err := zrokEdgeSdk.CreateIdentity(name, rest_model_edge.IdentityTypeService, nil, edge)
if err != nil {
logrus.Errorf("error creating identity: %v", err)
return admin.NewCreateIdentityInternalServerError()
}
zId := idc.Payload.Data.ID
idCfg, err := zrokEdgeSdk.EnrollIdentity(zId, edge)
if err != nil {
logrus.Errorf("error enrolling identity: %v", err)
return admin.NewCreateIdentityInternalServerError()
}
if err := zrokEdgeSdk.CreateEdgeRouterPolicy(name, zId, edge); err != nil {
logrus.Errorf("error creating edge router policy for identity: %v", err)
return admin.NewCreateIdentityInternalServerError()
}
filter := fmt.Sprintf("name=\"%v\" and tags.zrok != null", cfg.Metrics.ServiceName)
limit := int64(0)
offset := int64(0)
listSvcReq := &service.ListServicesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
}
listSvcReq.SetTimeout(30 * time.Second)
listSvcResp, err := edge.Service.ListServices(listSvcReq, nil)
if err != nil {
logrus.Errorf("error listing metrics service: %v", err)
return admin.NewCreateIdentityInternalServerError()
}
if len(listSvcResp.Payload.Data) != 1 {
logrus.Errorf("could not find metrics service")
return admin.NewCreateIdentityInternalServerError()
}
svcZId := *listSvcResp.Payload.Data[0].ID
spName := fmt.Sprintf("%v-%v-dial", name, cfg.Metrics.ServiceName)
if err := zrokEdgeSdk.CreateServicePolicyDial(spName, svcZId, []string{zId}, nil, edge); err != nil {
logrus.Errorf("error creating named dial service policy '%v': %v", spName, err)
return admin.NewCreateIdentityInternalServerError()
}
var out bytes.Buffer
enc := json.NewEncoder(&out)
enc.SetEscapeHTML(false)
err = enc.Encode(&idCfg)
if err != nil {
logrus.Errorf("error encoding identity config: %v", err)
return admin.NewCreateFrontendInternalServerError()
}
return admin.NewCreateIdentityCreated().WithPayload(&admin.CreateIdentityCreatedBody{
Identity: zId,
Cfg: out.String(),
})
}

View File

@ -0,0 +1,49 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/admin"
"github.com/sirupsen/logrus"
)
type deleteFrontendHandler struct{}
func newDeleteFrontendHandler() *deleteFrontendHandler {
return &deleteFrontendHandler{}
}
func (h *deleteFrontendHandler) Handle(params admin.DeleteFrontendParams, principal *rest_model_zrok.Principal) middleware.Responder {
feToken := params.Body.FrontendToken
if !principal.Admin {
logrus.Errorf("invalid admin principal")
return admin.NewDeleteFrontendUnauthorized()
}
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return admin.NewDeleteFrontendInternalServerError()
}
defer func() { _ = tx.Rollback() }()
fe, err := str.FindFrontendWithToken(feToken, tx)
if err != nil {
logrus.Errorf("error finding frontend with token '%v': %v", feToken, err)
return admin.NewDeleteFrontendNotFound()
}
if err := str.DeleteFrontend(fe.Id, tx); err != nil {
logrus.Errorf("error deleting frontend '%v': %v", feToken, err)
return admin.NewDeleteFrontendInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error commiting frontend '%v' deletion: %v", feToken, err)
return admin.NewDeleteFrontendInternalServerError()
}
return admin.NewDeleteFrontendOK()
}

View File

@ -3,8 +3,9 @@ package controller
import ( import (
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/environment"
"github.com/openziti/edge/rest_management_api_client" "github.com/openziti/edge/rest_management_api_client"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -17,51 +18,51 @@ func newDisableHandler() *disableHandler {
return &disableHandler{} return &disableHandler{}
} }
func (self *disableHandler) Handle(params identity.DisableParams, principal *rest_model_zrok.Principal) middleware.Responder { func (h *disableHandler) Handle(params environment.DisableParams, principal *rest_model_zrok.Principal) middleware.Responder {
tx, err := str.Begin() tx, err := str.Begin()
if err != nil { if err != nil {
logrus.Errorf("error starting transaction: %v", err) logrus.Errorf("error starting transaction: %v", err)
return identity.NewDisableInternalServerError() return environment.NewDisableInternalServerError()
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
envId, err := self.checkZitiIdentity(params.Body.Identity, principal, tx) envId, err := h.checkZitiIdentity(params.Body.Identity, principal, tx)
if err != nil { if err != nil {
logrus.Errorf("identity check failed: %v", err) logrus.Errorf("identity check failed: %v", err)
return identity.NewDisableUnauthorized() return environment.NewDisableUnauthorized()
} }
env, err := str.GetEnvironment(envId, tx) env, err := str.GetEnvironment(envId, tx)
if err != nil { if err != nil {
logrus.Errorf("error getting environment: %v", err) logrus.Errorf("error getting environment: %v", err)
return identity.NewDisableInternalServerError() return environment.NewDisableInternalServerError()
} }
edge, err := edgeClient() edge, err := edgeClient()
if err != nil { if err != nil {
logrus.Errorf("error getting edge client: %v", err) logrus.Errorf("error getting edge client: %v", err)
return identity.NewDisableInternalServerError() return environment.NewDisableInternalServerError()
} }
if err := self.removeServicesForEnvironment(envId, tx, edge); err != nil { if err := h.removeSharesForEnvironment(envId, tx, edge); err != nil {
logrus.Errorf("error removing services for environment: %v", err) logrus.Errorf("error removing shares for environment: %v", err)
return identity.NewDisableInternalServerError() return environment.NewDisableInternalServerError()
} }
if err := self.removeEnvironment(envId, tx); err != nil { if err := h.removeEnvironment(envId, tx); err != nil {
logrus.Errorf("error removing environment: %v", err) logrus.Errorf("error removing environment: %v", err)
return identity.NewDisableInternalServerError() return environment.NewDisableInternalServerError()
} }
if err := deleteEdgeRouterPolicy(env.ZId, params.Body.Identity, edge); err != nil { if err := zrokEdgeSdk.DeleteEdgeRouterPolicy(env.ZId, edge); err != nil {
logrus.Errorf("error deleting edge router policy: %v", err) logrus.Errorf("error deleting edge router policy: %v", err)
return identity.NewDisableInternalServerError() return environment.NewDisableInternalServerError()
} }
if err := deleteIdentity(params.Body.Identity, edge); err != nil { if err := zrokEdgeSdk.DeleteIdentity(params.Body.Identity, edge); err != nil {
logrus.Errorf("error deleting identity: %v", err) logrus.Errorf("error deleting identity: %v", err)
return identity.NewDisableInternalServerError() return environment.NewDisableInternalServerError()
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
logrus.Errorf("error committing: %v", err) logrus.Errorf("error committing: %v", err)
} }
return identity.NewDisableOK() return environment.NewDisableOK()
} }
func (self *disableHandler) checkZitiIdentity(id string, principal *rest_model_zrok.Principal, tx *sqlx.Tx) (int, error) { func (h *disableHandler) checkZitiIdentity(id string, principal *rest_model_zrok.Principal, tx *sqlx.Tx) (int, error) {
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx) envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
if err != nil { if err != nil {
return -1, err return -1, err
@ -74,39 +75,48 @@ func (self *disableHandler) checkZitiIdentity(id string, principal *rest_model_z
return -1, errors.Errorf("no such environment '%v'", id) return -1, errors.Errorf("no such environment '%v'", id)
} }
func (self *disableHandler) removeServicesForEnvironment(envId int, tx *sqlx.Tx, edge *rest_management_api_client.ZitiEdgeManagement) error { func (h *disableHandler) removeSharesForEnvironment(envId int, tx *sqlx.Tx, edge *rest_management_api_client.ZitiEdgeManagement) error {
env, err := str.GetEnvironment(envId, tx) env, err := str.GetEnvironment(envId, tx)
if err != nil { if err != nil {
return err return err
} }
svcs, err := str.FindServicesForEnvironment(envId, tx) shrs, err := str.FindSharesForEnvironment(envId, tx)
if err != nil { if err != nil {
return err return err
} }
for _, svc := range svcs { for _, shr := range shrs {
svcName := svc.Name shrToken := shr.Token
logrus.Infof("garbage collecting service '%v' for environment '%v'", svcName, env.ZId) logrus.Infof("garbage collecting share '%v' for environment '%v'", shrToken, env.ZId)
if err := deleteServiceEdgeRouterPolicy(env.ZId, svcName, edge); err != nil { if err := zrokEdgeSdk.DeleteServiceEdgeRouterPolicy(env.ZId, shrToken, edge); err != nil {
logrus.Error(err) logrus.Error(err)
} }
if err := deleteServicePolicyDial(env.ZId, svcName, edge); err != nil { if err := zrokEdgeSdk.DeleteServicePolicyDial(env.ZId, shrToken, edge); err != nil {
logrus.Error(err) logrus.Error(err)
} }
if err := deleteServicePolicyBind(env.ZId, svcName, edge); err != nil { if err := zrokEdgeSdk.DeleteServicePolicyBind(env.ZId, shrToken, edge); err != nil {
logrus.Error(err) logrus.Error(err)
} }
if err := deleteConfig(env.ZId, svcName, edge); err != nil { if err := zrokEdgeSdk.DeleteConfig(env.ZId, shrToken, edge); err != nil {
logrus.Error(err) logrus.Error(err)
} }
if err := deleteService(env.ZId, svc.ZId, edge); err != nil { if err := zrokEdgeSdk.DeleteService(env.ZId, shr.ZId, edge); err != nil {
logrus.Error(err) logrus.Error(err)
} }
logrus.Infof("removed service '%v' for environment '%v'", svc.Name, env.ZId) logrus.Infof("removed share '%v' for environment '%v'", shr.Token, env.ZId)
} }
return nil return nil
} }
func (self *disableHandler) removeEnvironment(envId int, tx *sqlx.Tx) error { func (h *disableHandler) removeEnvironment(envId int, tx *sqlx.Tx) error {
shrs, err := str.FindSharesForEnvironment(envId, tx)
if err != nil {
return errors.Wrapf(err, "error finding shares for environment '%d'", envId)
}
for _, shr := range shrs {
if err := str.DeleteShare(shr.Id, tx); err != nil {
return errors.Wrapf(err, "error deleting share '%d' for environment '%d'", shr.Id, envId)
}
}
if err := str.DeleteEnvironment(envId, tx); err != nil { if err := str.DeleteEnvironment(envId, tx); err != nil {
return errors.Wrapf(err, "error deleting environment '%d'", envId) return errors.Wrapf(err, "error deleting environment '%d'", envId)
} }

View File

@ -1,178 +0,0 @@
package controller
import (
"context"
"fmt"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/config"
"github.com/openziti/edge/rest_management_api_client/edge_router_policy"
identity_edge "github.com/openziti/edge/rest_management_api_client/identity"
"github.com/openziti/edge/rest_management_api_client/service"
"github.com/openziti/edge/rest_management_api_client/service_edge_router_policy"
"github.com/openziti/edge/rest_management_api_client/service_policy"
"github.com/sirupsen/logrus"
"time"
)
func deleteServiceEdgeRouterPolicy(envZId, svcName string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("name=\"%v\"", svcName)
limit := int64(1)
offset := int64(0)
listReq := &service_edge_router_policy.ListServiceEdgeRouterPoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.ServiceEdgeRouterPolicy.ListServiceEdgeRouterPolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) == 1 {
serpId := *(listResp.Payload.Data[0].ID)
req := &service_edge_router_policy.DeleteServiceEdgeRouterPolicyParams{
ID: serpId,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
_, err := edge.ServiceEdgeRouterPolicy.DeleteServiceEdgeRouterPolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted service edge router policy '%v' for environment '%v'", serpId, envZId)
} else {
logrus.Infof("did not find a service edge router policy")
}
return nil
}
func deleteServicePolicyBind(envZId, svcName string, edge *rest_management_api_client.ZitiEdgeManagement) error {
return deleteServicePolicy(envZId, fmt.Sprintf("name=\"%v-backend\"", svcName), edge)
}
func deleteServicePolicyDial(envZId, svcName string, edge *rest_management_api_client.ZitiEdgeManagement) error {
return deleteServicePolicy(envZId, fmt.Sprintf("name=\"%v-dial\"", svcName), edge)
}
func deleteServicePolicy(envZId, filter string, edge *rest_management_api_client.ZitiEdgeManagement) error {
limit := int64(1)
offset := int64(0)
listReq := &service_policy.ListServicePoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.ServicePolicy.ListServicePolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) == 1 {
spId := *(listResp.Payload.Data[0].ID)
req := &service_policy.DeleteServicePolicyParams{
ID: spId,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
_, err := edge.ServicePolicy.DeleteServicePolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted service policy '%v' for environment '%v'", spId, envZId)
} else {
logrus.Infof("did not find a service policy")
}
return nil
}
func deleteConfig(envZId, svcName string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("name=\"%v\"", svcName)
limit := int64(0)
offset := int64(0)
listReq := &config.ListConfigsParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.Config.ListConfigs(listReq, nil)
if err != nil {
return err
}
for _, cfg := range listResp.Payload.Data {
deleteReq := &config.DeleteConfigParams{
ID: *cfg.ID,
Context: context.Background(),
}
deleteReq.SetTimeout(30 * time.Second)
_, err := edge.Config.DeleteConfig(deleteReq, nil)
if err != nil {
return err
}
logrus.Infof("deleted config '%v' for '%v'", *cfg.ID, envZId)
}
return nil
}
func deleteService(envZId, svcId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
req := &service.DeleteServiceParams{
ID: svcId,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
_, err := edge.Service.DeleteService(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted service '%v' for environment '%v'", svcId, envZId)
return nil
}
func deleteEdgeRouterPolicy(envZId, id string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("name=\"%v\"", id)
limit := int64(0)
offset := int64(0)
listReq := &edge_router_policy.ListEdgeRouterPoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.EdgeRouterPolicy.ListEdgeRouterPolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) == 1 {
erpId := *(listResp.Payload.Data[0].ID)
req := &edge_router_policy.DeleteEdgeRouterPolicyParams{
ID: erpId,
Context: context.Background(),
}
_, err := edge.EdgeRouterPolicy.DeleteEdgeRouterPolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted edge router policy '%v' for environment '%v'", erpId, envZId)
} else {
logrus.Infof("found '%d' edge router policies, expected 1", len(listResp.Payload.Data))
}
return nil
}
func deleteIdentity(id string, edge *rest_management_api_client.ZitiEdgeManagement) error {
req := &identity_edge.DeleteIdentityParams{
ID: id,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
_, err := edge.Identity.DeleteIdentity(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted environment identity '%v'", id)
return nil
}

View File

@ -1,4 +1,4 @@
package email_ui package emailUi
import "embed" import "embed"

View File

@ -2,77 +2,89 @@ package controller
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/build" "github.com/jmoiron/sqlx"
"github.com/openziti-test-kitchen/zrok/controller/store" "github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/environment"
"github.com/openziti/edge/rest_management_api_client" "github.com/pkg/errors"
"github.com/openziti/edge/rest_management_api_client/edge_router_policy"
identity_edge "github.com/openziti/edge/rest_management_api_client/identity"
rest_model_edge "github.com/openziti/edge/rest_model"
sdk_config "github.com/openziti/sdk-golang/ziti/config"
"github.com/openziti/sdk-golang/ziti/enroll"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"time"
) )
type enableHandler struct { type enableHandler struct {
cfg *LimitsConfig
} }
func newEnableHandler() *enableHandler { func newEnableHandler(cfg *LimitsConfig) *enableHandler {
return &enableHandler{} return &enableHandler{cfg: cfg}
} }
func (self *enableHandler) Handle(params identity.EnableParams, principal *rest_model_zrok.Principal) middleware.Responder { func (h *enableHandler) Handle(params environment.EnableParams, principal *rest_model_zrok.Principal) middleware.Responder {
// start transaction early; if it fails, don't bother creating ziti resources // start transaction early; if it fails, don't bother creating ziti resources
tx, err := str.Begin() tx, err := str.Begin()
if err != nil { if err != nil {
logrus.Errorf("error starting transaction: %v", err) logrus.Errorf("error starting transaction: %v", err)
return identity.NewEnableInternalServerError() return environment.NewEnableInternalServerError()
}
defer func() { _ = tx.Rollback() }()
if err := h.checkLimits(principal, tx); err != nil {
logrus.Errorf("limits error: %v", err)
return environment.NewEnableUnauthorized()
} }
client, err := edgeClient() client, err := edgeClient()
if err != nil { if err != nil {
logrus.Errorf("error getting edge client: %v", err) logrus.Errorf("error getting edge client: %v", err)
return identity.NewEnableInternalServerError() return environment.NewEnableInternalServerError()
} }
ident, err := self.createIdentity(principal.Email, client)
uniqueToken, err := createShareToken()
if err != nil {
logrus.Errorf("error creating unique identity token: %v", err)
return environment.NewEnableInternalServerError()
}
ident, err := zrokEdgeSdk.CreateEnvironmentIdentity(uniqueToken, principal.Email, params.Body.Description, client)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewEnableInternalServerError() return environment.NewEnableInternalServerError()
} }
cfg, err := self.enrollIdentity(ident.Payload.Data.ID, client)
envZId := ident.Payload.Data.ID
cfg, err := zrokEdgeSdk.EnrollIdentity(envZId, client)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewEnableInternalServerError() return environment.NewEnableInternalServerError()
} }
if err := self.createEdgeRouterPolicy(ident.Payload.Data.ID, client); err != nil {
if err := zrokEdgeSdk.CreateEdgeRouterPolicy(envZId, envZId, client); err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewEnableInternalServerError() return environment.NewEnableInternalServerError()
} }
envId, err := str.CreateEnvironment(int(principal.ID), &store.Environment{ envId, err := str.CreateEnvironment(int(principal.ID), &store.Environment{
Description: params.Body.Description, Description: params.Body.Description,
Host: params.Body.Host, Host: params.Body.Host,
Address: realRemoteAddress(params.HTTPRequest), Address: realRemoteAddress(params.HTTPRequest),
ZId: ident.Payload.Data.ID, ZId: envZId,
}, tx) }, tx)
if err != nil { if err != nil {
logrus.Errorf("error storing created identity: %v", err) logrus.Errorf("error storing created identity: %v", err)
_ = tx.Rollback() _ = tx.Rollback()
return identity.NewCreateAccountInternalServerError() return environment.NewEnableInternalServerError()
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
logrus.Errorf("error committing: %v", err) logrus.Errorf("error committing: %v", err)
return identity.NewCreateAccountInternalServerError() return environment.NewEnableInternalServerError()
} }
logrus.Infof("created environment for '%v', with ziti identity '%v', and database id '%v'", principal.Email, ident.Payload.Data.ID, envId) logrus.Infof("created environment for '%v', with ziti identity '%v', and database id '%v'", principal.Email, ident.Payload.Data.ID, envId)
resp := identity.NewEnableCreated().WithPayload(&rest_model_zrok.EnableResponse{ resp := environment.NewEnableCreated().WithPayload(&rest_model_zrok.EnableResponse{
Identity: ident.Payload.Data.ID, Identity: envZId,
}) })
var out bytes.Buffer var out bytes.Buffer
@ -87,86 +99,15 @@ func (self *enableHandler) Handle(params identity.EnableParams, principal *rest_
return resp return resp
} }
func (self *enableHandler) createIdentity(email string, client *rest_management_api_client.ZitiEdgeManagement) (*identity_edge.CreateIdentityCreated, error) { func (h *enableHandler) checkLimits(principal *rest_model_zrok.Principal, tx *sqlx.Tx) error {
iIsAdmin := false if !principal.Limitless && h.cfg.Environments > Unlimited {
name, err := createToken() envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
if err != nil { if err != nil {
return nil, err return errors.Errorf("unable to find environments for account '%v': %v", principal.Email, err)
}
if len(envs)+1 > h.cfg.Environments {
return errors.Errorf("would exceed environments limit of %d for '%v'", h.cfg.Environments, principal.Email)
}
} }
identityType := rest_model_edge.IdentityTypeUser
tags := self.zrokTags()
tags.SubTags["zrokEmail"] = email
i := &rest_model_edge.IdentityCreate{
Enrollment: &rest_model_edge.IdentityCreateEnrollment{Ott: true},
IsAdmin: &iIsAdmin,
Name: &name,
RoleAttributes: nil,
ServiceHostingCosts: nil,
Tags: tags,
Type: &identityType,
}
req := identity_edge.NewCreateIdentityParams()
req.Identity = i
resp, err := client.Identity.CreateIdentity(req, nil)
if err != nil {
return nil, err
}
return resp, nil
}
func (_ *enableHandler) enrollIdentity(id string, client *rest_management_api_client.ZitiEdgeManagement) (*sdk_config.Config, error) {
p := &identity_edge.DetailIdentityParams{
Context: context.Background(),
ID: id,
}
p.SetTimeout(30 * time.Second)
resp, err := client.Identity.DetailIdentity(p, nil)
if err != nil {
return nil, err
}
tkn, _, err := enroll.ParseToken(resp.GetPayload().Data.Enrollment.Ott.JWT)
if err != nil {
return nil, err
}
flags := enroll.EnrollmentFlags{
Token: tkn,
KeyAlg: "RSA",
}
conf, err := enroll.Enroll(flags)
if err != nil {
return nil, err
}
return conf, nil
}
func (self *enableHandler) createEdgeRouterPolicy(id string, edge *rest_management_api_client.ZitiEdgeManagement) error {
edgeRouterRoles := []string{"#all"}
identityRoles := []string{fmt.Sprintf("@%v", id)}
semantic := rest_model_edge.SemanticAllOf
erp := &rest_model_edge.EdgeRouterPolicyCreate{
EdgeRouterRoles: edgeRouterRoles,
IdentityRoles: identityRoles,
Name: &id,
Semantic: &semantic,
Tags: self.zrokTags(),
}
req := &edge_router_policy.CreateEdgeRouterPolicyParams{
Policy: erp,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
resp, err := edge.EdgeRouterPolicy.CreateEdgeRouterPolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("created edge router policy '%v' for ziti identity '%v'", resp.Payload.Data.ID, id)
return nil return nil
} }
func (self *enableHandler) zrokTags() *rest_model_edge.Tags {
return &rest_model_edge.Tags{
SubTags: map[string]interface{}{
"zrok": build.String(),
},
}
}

View File

@ -0,0 +1,78 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/metadata"
"github.com/sirupsen/logrus"
)
type environmentDetailHandler struct{}
func newEnvironmentDetailHandler() *environmentDetailHandler {
return &environmentDetailHandler{}
}
func (h *environmentDetailHandler) Handle(params metadata.GetEnvironmentDetailParams, principal *rest_model_zrok.Principal) middleware.Responder {
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return metadata.NewGetEnvironmentDetailInternalServerError()
}
defer func() { _ = tx.Rollback() }()
senv, err := str.FindEnvironmentForAccount(params.EnvZID, int(principal.ID), tx)
if err != nil {
logrus.Errorf("environment '%v' not found for account '%v': %v", params.EnvZID, principal.Email, err)
return metadata.NewGetEnvironmentDetailNotFound()
}
es := &rest_model_zrok.EnvironmentShares{
Environment: &rest_model_zrok.Environment{
Address: senv.Address,
CreatedAt: senv.CreatedAt.UnixMilli(),
Description: senv.Description,
Host: senv.Host,
UpdatedAt: senv.UpdatedAt.UnixMilli(),
ZID: senv.ZId,
},
}
shrs, err := str.FindSharesForEnvironment(senv.Id, tx)
if err != nil {
logrus.Errorf("error finding shares for environment '%v': %v", senv.ZId, err)
return metadata.NewGetEnvironmentDetailInternalServerError()
}
var sparkData map[string][]int64
if cfg.Influx != nil {
sparkData, err = sparkDataForShares(shrs)
if err != nil {
logrus.Errorf("error querying spark data for shares: %v", err)
}
}
for _, shr := range shrs {
feEndpoint := ""
if shr.FrontendEndpoint != nil {
feEndpoint = *shr.FrontendEndpoint
}
feSelection := ""
if shr.FrontendSelection != nil {
feSelection = *shr.FrontendSelection
}
beProxyEndpoint := ""
if shr.BackendProxyEndpoint != nil {
beProxyEndpoint = *shr.BackendProxyEndpoint
}
es.Shares = append(es.Shares, &rest_model_zrok.Share{
Token: shr.Token,
ZID: shr.ZId,
ShareMode: shr.ShareMode,
BackendMode: shr.BackendMode,
FrontendSelection: feSelection,
FrontendEndpoint: feEndpoint,
BackendProxyEndpoint: beProxyEndpoint,
Reserved: shr.Reserved,
Metrics: sparkData[shr.Token],
CreatedAt: shr.CreatedAt.UnixMilli(),
UpdatedAt: shr.UpdatedAt.UnixMilli(),
})
}
return metadata.NewGetEnvironmentDetailOK().WithPayload(es)
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/openziti-test-kitchen/zrok/controller/store" "github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti/edge/rest_management_api_client" "github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/config" "github.com/openziti/edge/rest_management_api_client/config"
"github.com/openziti/edge/rest_management_api_client/service" "github.com/openziti/edge/rest_management_api_client/service"
@ -36,13 +37,13 @@ func GC(inCfg *Config) error {
return err return err
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
dbSvcs, err := str.GetAllServices(tx) sshrs, err := str.GetAllShares(tx)
if err != nil { if err != nil {
return err return err
} }
liveMap := make(map[string]struct{}) liveMap := make(map[string]struct{})
for _, dbSvc := range dbSvcs { for _, sshr := range sshrs {
liveMap[dbSvc.Name] = struct{}{} liveMap[sshr.Token] = struct{}{}
} }
if err := gcServices(edge, liveMap); err != nil { if err := gcServices(edge, liveMap); err != nil {
return errors.Wrap(err, "error garbage collecting services") return errors.Wrap(err, "error garbage collecting services")
@ -71,19 +72,19 @@ func gcServices(edge *rest_management_api_client.ZitiEdgeManagement, liveMap map
for _, svc := range listResp.Payload.Data { for _, svc := range listResp.Payload.Data {
if _, found := liveMap[*svc.Name]; !found { if _, found := liveMap[*svc.Name]; !found {
logrus.Infof("garbage collecting, zitiSvcId='%v', zrokSvcId='%v'", *svc.ID, *svc.Name) logrus.Infof("garbage collecting, zitiSvcId='%v', zrokSvcId='%v'", *svc.ID, *svc.Name)
if err := deleteServiceEdgeRouterPolicy("gc", *svc.Name, edge); err != nil { if err := zrokEdgeSdk.DeleteServiceEdgeRouterPolicy("gc", *svc.Name, edge); err != nil {
logrus.Errorf("error garbage collecting service edge router policy: %v", err) logrus.Errorf("error garbage collecting service edge router policy: %v", err)
} }
if err := deleteServicePolicyDial("gc", *svc.Name, edge); err != nil { if err := zrokEdgeSdk.DeleteServicePolicyDial("gc", *svc.Name, edge); err != nil {
logrus.Errorf("error garbage collecting service dial policy: %v", err) logrus.Errorf("error garbage collecting service dial policy: %v", err)
} }
if err := deleteServicePolicyBind("gc", *svc.Name, edge); err != nil { if err := zrokEdgeSdk.DeleteServicePolicyBind("gc", *svc.Name, edge); err != nil {
logrus.Errorf("error garbage collecting service bind policy: %v", err) logrus.Errorf("error garbage collecting service bind policy: %v", err)
} }
if err := deleteConfig("gc", *svc.Name, edge); err != nil { if err := zrokEdgeSdk.DeleteConfig("gc", *svc.Name, edge); err != nil {
logrus.Errorf("error garbage collecting config: %v", err) logrus.Errorf("error garbage collecting config: %v", err)
} }
if err := deleteService("gc", *svc.ID, edge); err != nil { if err := zrokEdgeSdk.DeleteService("gc", *svc.ID, edge); err != nil {
logrus.Errorf("error garbage collecting service: %v", err) logrus.Errorf("error garbage collecting service: %v", err)
} }
} else { } else {
@ -108,7 +109,7 @@ func gcServiceEdgeRouterPolicies(edge *rest_management_api_client.ZitiEdgeManage
for _, serp := range listResp.Payload.Data { for _, serp := range listResp.Payload.Data {
if _, found := liveMap[*serp.Name]; !found { if _, found := liveMap[*serp.Name]; !found {
logrus.Infof("garbage collecting, svcId='%v'", *serp.Name) logrus.Infof("garbage collecting, svcId='%v'", *serp.Name)
if err := deleteServiceEdgeRouterPolicy("gc", *serp.Name, edge); err != nil { if err := zrokEdgeSdk.DeleteServiceEdgeRouterPolicy("gc", *serp.Name, edge); err != nil {
logrus.Errorf("error garbage collecting service edge router policy: %v", err) logrus.Errorf("error garbage collecting service edge router policy: %v", err)
} }
} else { } else {
@ -135,7 +136,7 @@ func gcServicePolicies(edge *rest_management_api_client.ZitiEdgeManagement, live
if _, found := liveMap[spName]; !found { if _, found := liveMap[spName]; !found {
logrus.Infof("garbage collecting, svcId='%v'", spName) logrus.Infof("garbage collecting, svcId='%v'", spName)
deleteFilter := fmt.Sprintf("id=\"%v\"", *sp.ID) deleteFilter := fmt.Sprintf("id=\"%v\"", *sp.ID)
if err := deleteServicePolicy("gc", deleteFilter, edge); err != nil { if err := zrokEdgeSdk.DeleteServicePolicy("gc", deleteFilter, edge); err != nil {
logrus.Errorf("error garbage collecting service policy: %v", err) logrus.Errorf("error garbage collecting service policy: %v", err)
} }
} else { } else {
@ -159,7 +160,7 @@ func gcConfigs(edge *rest_management_api_client.ZitiEdgeManagement, liveMap map[
if listResp, err := edge.Config.ListConfigs(listReq, nil); err == nil { if listResp, err := edge.Config.ListConfigs(listReq, nil); err == nil {
for _, c := range listResp.Payload.Data { for _, c := range listResp.Payload.Data {
if _, found := liveMap[*c.Name]; !found { if _, found := liveMap[*c.Name]; !found {
if err := deleteConfig("gc", *c.Name, edge); err != nil { if err := zrokEdgeSdk.DeleteConfig("gc", *c.Name, edge); err != nil {
logrus.Errorf("error garbage collecting config: %v", err) logrus.Errorf("error garbage collecting config: %v", err)
} }
} else { } else {

102
controller/invite.go Normal file
View File

@ -0,0 +1,102 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"github.com/openziti-test-kitchen/zrok/util"
"github.com/sirupsen/logrus"
)
type inviteHandler struct {
cfg *Config
}
func newInviteHandler(cfg *Config) *inviteHandler {
return &inviteHandler{
cfg: cfg,
}
}
func (self *inviteHandler) Handle(params account.InviteParams) middleware.Responder {
if params.Body == nil || params.Body.Email == "" {
logrus.Errorf("missing email")
return account.NewInviteBadRequest()
}
if !util.IsValidEmail(params.Body.Email) {
logrus.Errorf("'%v' is not a valid email address", params.Body.Email)
return account.NewInviteBadRequest()
}
logrus.Infof("received account request for email '%v'", params.Body.Email)
var token string
tx, err := str.Begin()
if err != nil {
logrus.Error(err)
return account.NewInviteInternalServerError()
}
defer func() { _ = tx.Rollback() }()
if self.cfg.Registration != nil && self.cfg.Registration.TokenStrategy == "store" {
inviteToken, err := str.GetInviteTokenByToken(params.Body.Token, tx)
if err != nil {
logrus.Errorf("cannot get invite token '%v' for '%v': %v", params.Body.Token, params.Body.Email, err)
return account.NewInviteBadRequest()
}
if err := str.DeleteInviteToken(inviteToken.Id, tx); err != nil {
logrus.Error(err)
return account.NewInviteInternalServerError()
}
logrus.Infof("using invite token '%v' to process invite request for '%v'", inviteToken.Token, params.Body.Email)
}
token, err = createToken()
if err != nil {
logrus.Error(err)
return account.NewInviteInternalServerError()
}
ar := &store.AccountRequest{
Token: token,
Email: params.Body.Email,
SourceAddress: params.HTTPRequest.RemoteAddr,
}
if _, err := str.FindAccountWithEmail(params.Body.Email, tx); err == nil {
logrus.Errorf("found account for '%v', cannot process account request", params.Body.Email)
return account.NewInviteBadRequest()
} else {
logrus.Infof("no account found for '%v': %v", params.Body.Email, err)
}
if oldAr, err := str.FindAccountRequestWithEmail(params.Body.Email, tx); err == nil {
logrus.Warnf("found previous account request for '%v', removing", params.Body.Email)
if err := str.DeleteAccountRequest(oldAr.Id, tx); err != nil {
logrus.Errorf("error deleteing previous account request for '%v': %v", params.Body.Email, err)
return account.NewInviteInternalServerError()
}
} else {
logrus.Warnf("error finding previous account request for '%v': %v", params.Body.Email, err)
}
if _, err := str.CreateAccountRequest(ar, tx); err != nil {
logrus.Errorf("error creating account request for '%v': %v", params.Body.Email, err)
return account.NewInviteInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing account request for '%v': %v", params.Body.Email, err)
return account.NewInviteInternalServerError()
}
if cfg.Email != nil && cfg.Registration != nil {
if err := sendVerificationEmail(params.Body.Email, token); err != nil {
logrus.Errorf("error sending verification email for '%v': %v", params.Body.Email, err)
return account.NewInviteInternalServerError()
}
} else {
logrus.Errorf("'email' and 'registration' configuration missing; skipping registration email")
}
logrus.Infof("account request for '%v' has registration token '%v'", params.Body.Email, ar.Token)
return account.NewInviteCreated()
}

View File

@ -0,0 +1,55 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/admin"
"github.com/sirupsen/logrus"
)
type inviteTokenGenerateHandler struct {
}
func newInviteTokenGenerateHandler() *inviteTokenGenerateHandler {
return &inviteTokenGenerateHandler{}
}
func (handler *inviteTokenGenerateHandler) Handle(params admin.InviteTokenGenerateParams, principal *rest_model_zrok.Principal) middleware.Responder {
if !principal.Admin {
logrus.Errorf("invalid admin principal")
return admin.NewInviteTokenGenerateUnauthorized()
}
if params.Body == nil || len(params.Body.Tokens) == 0 {
logrus.Error("missing tokens")
return admin.NewInviteTokenGenerateBadRequest()
}
logrus.Infof("received invite generate request with %d tokens", len(params.Body.Tokens))
invites := make([]*store.InviteToken, len(params.Body.Tokens))
for i, token := range params.Body.Tokens {
invites[i] = &store.InviteToken{
Token: token,
}
}
tx, err := str.Begin()
if err != nil {
logrus.Error(err)
return admin.NewInviteTokenGenerateInternalServerError()
}
defer func() { _ = tx.Rollback() }()
if err := str.CreateInviteTokens(invites, tx); err != nil {
logrus.Error(err)
return admin.NewInviteTokenGenerateInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing inviteGenerate request: %v", err)
return account.NewInviteInternalServerError()
}
return admin.NewInviteTokenGenerateCreated()
}

View File

@ -0,0 +1,52 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/admin"
"github.com/sirupsen/logrus"
)
type listFrontendsHandler struct{}
func newListFrontendsHandler() *listFrontendsHandler {
return &listFrontendsHandler{}
}
func (h *listFrontendsHandler) Handle(params admin.ListFrontendsParams, principal *rest_model_zrok.Principal) middleware.Responder {
if !principal.Admin {
logrus.Errorf("invalid admin principal")
return admin.NewListFrontendsUnauthorized()
}
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return admin.NewListFrontendsInternalServerError()
}
defer func() { _ = tx.Rollback() }()
sfes, err := str.FindPublicFrontends(tx)
if err != nil {
logrus.Errorf("error finding public frontends: %v", err)
return admin.NewListFrontendsInternalServerError()
}
var frontends rest_model_zrok.PublicFrontendList
for _, sfe := range sfes {
frontend := &rest_model_zrok.PublicFrontend{
Token: sfe.Token,
ZID: sfe.ZId,
CreatedAt: sfe.CreatedAt.UnixMilli(),
UpdatedAt: sfe.UpdatedAt.UnixMilli(),
}
if sfe.UrlTemplate != nil {
frontend.URLTemplate = *sfe.UrlTemplate
}
if sfe.PublicName != nil {
frontend.PublicName = *sfe.PublicName
}
frontends = append(frontends, frontend)
}
return admin.NewListFrontendsOK().WithPayload(frontends)
}

View File

@ -3,14 +3,14 @@ package controller
import ( import (
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func loginHandler(params identity.LoginParams) middleware.Responder { func loginHandler(params account.LoginParams) middleware.Responder {
if params.Body == nil || params.Body.Email == "" || params.Body.Password == "" { if params.Body == nil || params.Body.Email == "" || params.Body.Password == "" {
logrus.Errorf("missing email or password") logrus.Errorf("missing email or password")
return identity.NewLoginUnauthorized() return account.NewLoginUnauthorized()
} }
logrus.Infof("received login request for email '%v'", params.Body.Email) logrus.Infof("received login request for email '%v'", params.Body.Email)
@ -18,18 +18,18 @@ func loginHandler(params identity.LoginParams) middleware.Responder {
tx, err := str.Begin() tx, err := str.Begin()
if err != nil { if err != nil {
logrus.Errorf("error starting transaction: %v", err) logrus.Errorf("error starting transaction: %v", err)
return identity.NewLoginUnauthorized() return account.NewLoginUnauthorized()
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
a, err := str.FindAccountWithEmail(params.Body.Email, tx) a, err := str.FindAccountWithEmail(params.Body.Email, tx)
if err != nil { if err != nil {
logrus.Errorf("error finding account '%v': %v", params.Body.Email, err) logrus.Errorf("error finding account '%v': %v", params.Body.Email, err)
return identity.NewLoginUnauthorized() return account.NewLoginUnauthorized()
} }
if a.Password != hashPassword(params.Body.Password) { if a.Password != hashPassword(params.Body.Password) {
logrus.Errorf("password mismatch for account '%v'", params.Body.Email) logrus.Errorf("password mismatch for account '%v'", params.Body.Email)
return identity.NewLoginUnauthorized() return account.NewLoginUnauthorized()
} }
return identity.NewLoginOK().WithPayload(rest_model_zrok.LoginResponse(a.Token)) return account.NewLoginOK().WithPayload(rest_model_zrok.LoginResponse(a.Token))
} }

78
controller/maintenance.go Normal file
View File

@ -0,0 +1,78 @@
package controller
import (
"context"
"fmt"
"strings"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type maintenanceAgent struct {
*MaintenanceConfig
ctx context.Context
}
func newMaintenanceAgent(ctx context.Context, cfg *MaintenanceConfig) *maintenanceAgent {
return &maintenanceAgent{
MaintenanceConfig: cfg,
ctx: ctx,
}
}
func (ma *maintenanceAgent) run() {
logrus.Info("starting")
defer logrus.Info("stopping")
ticker := time.NewTicker(ma.Registration.CheckFrequency)
for {
select {
case <-ma.ctx.Done():
{
ticker.Stop()
return
}
case <-ticker.C:
{
if err := ma.deleteExpiredAccountRequests(); err != nil {
logrus.Error(err)
}
}
}
}
}
func (ma *maintenanceAgent) deleteExpiredAccountRequests() error {
tx, err := str.Begin()
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
timeout := time.Now().UTC().Add(-ma.Registration.ExpirationTimeout)
accountRequests, err := str.FindExpiredAccountRequests(timeout, ma.Registration.BatchLimit, tx)
if err != nil {
return errors.Wrapf(err, "error finding expire account requests before %v", timeout)
}
if len(accountRequests) > 0 {
logrus.Infof("found %d expired account requests to remove", len(accountRequests))
acctStrings := make([]string, len(accountRequests))
ids := make([]int, len(accountRequests))
for i, acct := range accountRequests {
ids[i] = acct.Id
acctStrings[i] = fmt.Sprintf("{%d:%s}", acct.Id, acct.Email)
}
logrus.Infof("deleting expired account requests: %v", strings.Join(acctStrings, ","))
if err := str.DeleteMultipleAccountRequests(ids, tx); err != nil {
return errors.Wrapf(err, "error deleting expired account requests before %v", timeout)
}
if err := tx.Commit(); err != nil {
return errors.Wrapf(err, "error committing expired acount requests deletion")
}
}
return nil
}

View File

@ -119,7 +119,7 @@ func (ma *metricsAgent) processMetrics(m *model.Metrics) error {
for k, v := range m.Sessions { for k, v := range m.Sessions {
if ma.writeApi != nil { if ma.writeApi != nil {
pt := influxdb2.NewPoint("xfer", pt := influxdb2.NewPoint("xfer",
map[string]string{"namespace": m.Namespace, "service": k}, map[string]string{"namespace": m.Namespace, "share": k},
map[string]interface{}{"bytesRead": v.BytesRead, "bytesWritten": v.BytesWritten}, map[string]interface{}{"bytesRead": v.BytesRead, "bytesWritten": v.BytesWritten},
time.UnixMilli(v.LastUpdate)) time.UnixMilli(v.LastUpdate))
pts = append(pts, pt) pts = append(pts, pt)

View File

@ -1,10 +1,7 @@
package controller package controller
import ( import (
"context"
"fmt"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/metadata" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/metadata"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -22,14 +19,14 @@ func overviewHandler(_ metadata.OverviewParams, principal *rest_model_zrok.Princ
logrus.Errorf("error finding environments for '%v': %v", principal.Email, err) logrus.Errorf("error finding environments for '%v': %v", principal.Email, err)
return metadata.NewOverviewInternalServerError() return metadata.NewOverviewInternalServerError()
} }
var out rest_model_zrok.EnvironmentServicesList var out rest_model_zrok.EnvironmentSharesList
for _, env := range envs { for _, env := range envs {
svcs, err := str.FindServicesForEnvironment(env.Id, tx) shrs, err := str.FindSharesForEnvironment(env.Id, tx)
if err != nil { if err != nil {
logrus.Errorf("error finding services for environment '%v': %v", env.ZId, err) logrus.Errorf("error finding shares for environment '%v': %v", env.ZId, err)
return metadata.NewOverviewInternalServerError() return metadata.NewOverviewInternalServerError()
} }
es := &rest_model_zrok.EnvironmentServices{ es := &rest_model_zrok.EnvironmentShares{
Environment: &rest_model_zrok.Environment{ Environment: &rest_model_zrok.Environment{
Address: env.Address, Address: env.Address,
CreatedAt: env.CreatedAt.UnixMilli(), CreatedAt: env.CreatedAt.UnixMilli(),
@ -39,74 +36,34 @@ func overviewHandler(_ metadata.OverviewParams, principal *rest_model_zrok.Princ
ZID: env.ZId, ZID: env.ZId,
}, },
} }
sparkData, err := sparkDataForServices(svcs)
if err != nil { for _, shr := range shrs {
logrus.Errorf("error querying spark data for services: %v", err) feEndpoint := ""
return metadata.NewOverviewInternalServerError() if shr.FrontendEndpoint != nil {
} feEndpoint = *shr.FrontendEndpoint
for _, svc := range svcs { }
es.Services = append(es.Services, &rest_model_zrok.Service{ feSelection := ""
CreatedAt: svc.CreatedAt.UnixMilli(), if shr.FrontendSelection != nil {
Frontend: svc.Frontend, feSelection = *shr.FrontendSelection
Backend: svc.Backend, }
UpdatedAt: svc.UpdatedAt.UnixMilli(), beProxyEndpoint := ""
ZID: svc.ZId, if shr.BackendProxyEndpoint != nil {
Name: svc.Name, beProxyEndpoint = *shr.BackendProxyEndpoint
Metrics: sparkData[svc.Name], }
es.Shares = append(es.Shares, &rest_model_zrok.Share{
Token: shr.Token,
ZID: shr.ZId,
ShareMode: shr.ShareMode,
BackendMode: shr.BackendMode,
FrontendSelection: feSelection,
FrontendEndpoint: feEndpoint,
BackendProxyEndpoint: beProxyEndpoint,
Reserved: shr.Reserved,
CreatedAt: shr.CreatedAt.UnixMilli(),
UpdatedAt: shr.UpdatedAt.UnixMilli(),
}) })
} }
out = append(out, es) out = append(out, es)
} }
return metadata.NewOverviewOK().WithPayload(out) return metadata.NewOverviewOK().WithPayload(out)
} }
func sparkDataForServices(svcs []*store.Service) (map[string][]int64, error) {
out := make(map[string][]int64)
if len(svcs) > 0 {
qapi := idb.QueryAPI(cfg.Influx.Org)
result, err := qapi.Query(context.Background(), sparkFluxQuery(svcs))
if err != nil {
return nil, err
}
for result.Next() {
combinedRate := int64(0)
readRate := result.Record().ValueByKey("bytesRead")
if readRate != nil {
combinedRate += readRate.(int64)
}
writeRate := result.Record().ValueByKey("bytesWritten")
if writeRate != nil {
combinedRate += writeRate.(int64)
}
svcName := result.Record().ValueByKey("service").(string)
svcMetrics := out[svcName]
svcMetrics = append(svcMetrics, combinedRate)
out[svcName] = svcMetrics
}
}
return out, nil
}
func sparkFluxQuery(svcs []*store.Service) string {
svcFilter := "|> filter(fn: (r) =>"
for i, svc := range svcs {
if i > 0 {
svcFilter += " or"
}
svcFilter += fmt.Sprintf(" r[\"service\"] == \"%v\"", svc.Name)
}
svcFilter += ")"
query := "read = from(bucket: \"zrok\")" +
"|> range(start: -5m)" +
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")" +
"|> filter(fn: (r) => r[\"_field\"] == \"bytesRead\" or r[\"_field\"] == \"bytesWritten\")" +
"|> filter(fn: (r) => r[\"namespace\"] == \"frontend\")" +
svcFilter +
"|> aggregateWindow(every: 5s, fn: sum, createEmpty: true)\n" +
"|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")" +
"|> yield(name: \"last\")"
return query
}

View File

@ -4,7 +4,7 @@ import (
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store" "github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -13,30 +13,30 @@ type registerHandler struct{}
func newRegisterHandler() *registerHandler { func newRegisterHandler() *registerHandler {
return &registerHandler{} return &registerHandler{}
} }
func (self *registerHandler) Handle(params identity.RegisterParams) middleware.Responder { func (self *registerHandler) Handle(params account.RegisterParams) middleware.Responder {
if params.Body == nil || params.Body.Token == "" || params.Body.Password == "" { if params.Body == nil || params.Body.Token == "" || params.Body.Password == "" {
logrus.Error("missing token or password") logrus.Error("missing token or password")
return identity.NewRegisterNotFound() return account.NewRegisterNotFound()
} }
logrus.Infof("received register request for token '%v'", params.Body.Token) logrus.Infof("received register request for token '%v'", params.Body.Token)
tx, err := str.Begin() tx, err := str.Begin()
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewRegisterInternalServerError() return account.NewRegisterInternalServerError()
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
ar, err := str.FindAccountRequestWithToken(params.Body.Token, tx) ar, err := str.FindAccountRequestWithToken(params.Body.Token, tx)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewRegisterNotFound() return account.NewRegisterNotFound()
} }
token, err := createToken() token, err := createToken()
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewRegisterInternalServerError() return account.NewRegisterInternalServerError()
} }
a := &store.Account{ a := &store.Account{
Email: ar.Email, Email: ar.Email,
@ -45,20 +45,20 @@ func (self *registerHandler) Handle(params identity.RegisterParams) middleware.R
} }
if _, err := str.CreateAccount(a, tx); err != nil { if _, err := str.CreateAccount(a, tx); err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewRegisterInternalServerError() return account.NewRegisterInternalServerError()
} }
if err := str.DeleteAccountRequest(ar.Id, tx); err != nil { if err := str.DeleteAccountRequest(ar.Id, tx); err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewRegisterInternalServerError() return account.NewRegisterInternalServerError()
} }
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
logrus.Error(err) logrus.Error(err)
return identity.NewCreateAccountInternalServerError() return account.NewRegisterInternalServerError()
} }
logrus.Infof("created account '%v' with token '%v'", a.Email, a.Token) logrus.Infof("created account '%v' with token '%v'", a.Email, a.Token)
return identity.NewRegisterOK().WithPayload(&rest_model_zrok.RegisterResponse{Token: a.Token}) return account.NewRegisterOK().WithPayload(&rest_model_zrok.RegisterResponse{Token: a.Token})
} }

160
controller/share.go Normal file
View File

@ -0,0 +1,160 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/jmoiron/sqlx"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/share"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type shareHandler struct {
cfg *LimitsConfig
}
func newShareHandler(cfg *LimitsConfig) *shareHandler {
return &shareHandler{cfg: cfg}
}
func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zrok.Principal) middleware.Responder {
logrus.Infof("handling")
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return share.NewShareInternalServerError()
}
defer func() { _ = tx.Rollback() }()
envZId := params.Body.EnvZID
envId := 0
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
if err == nil {
found := false
for _, env := range envs {
if env.ZId == envZId {
logrus.Debugf("found identity '%v' for user '%v'", envZId, principal.Email)
envId = env.Id
found = true
break
}
}
if !found {
logrus.Errorf("environment '%v' not found for user '%v'", envZId, principal.Email)
return share.NewShareUnauthorized()
}
} else {
logrus.Errorf("error finding environments for account '%v'", principal.Email)
return share.NewShareInternalServerError()
}
if err := h.checkLimits(principal, envs, tx); err != nil {
logrus.Errorf("limits error: %v", err)
return share.NewShareUnauthorized()
}
edge, err := edgeClient()
if err != nil {
logrus.Error(err)
return share.NewShareInternalServerError()
}
shrToken, err := createShareToken()
if err != nil {
logrus.Error(err)
return share.NewShareInternalServerError()
}
var shrZId string
var frontendEndpoints []string
switch params.Body.ShareMode {
case "public":
if len(params.Body.FrontendSelection) < 1 {
logrus.Info("no frontend selection provided")
return share.NewShareNotFound()
}
var frontendZIds []string
var frontendTemplates []string
for _, frontendSelection := range params.Body.FrontendSelection {
sfe, err := str.FindFrontendPubliclyNamed(frontendSelection, tx)
if err != nil {
logrus.Error(err)
return share.NewShareNotFound()
}
if sfe != nil && sfe.UrlTemplate != nil {
frontendZIds = append(frontendZIds, sfe.ZId)
frontendTemplates = append(frontendTemplates, *sfe.UrlTemplate)
logrus.Infof("added frontend selection '%v' with ziti identity '%v' for share '%v'", frontendSelection, sfe.ZId, shrToken)
}
}
shrZId, frontendEndpoints, err = newPublicResourceAllocator().allocate(envZId, shrToken, frontendZIds, frontendTemplates, params, edge)
if err != nil {
logrus.Error(err)
return share.NewShareInternalServerError()
}
case "private":
shrZId, frontendEndpoints, err = newPrivateResourceAllocator().allocate(envZId, shrToken, params, edge)
if err != nil {
logrus.Error(err)
return share.NewShareInternalServerError()
}
default:
logrus.Errorf("unknown share mode '%v", params.Body.ShareMode)
return share.NewShareInternalServerError()
}
logrus.Debugf("allocated share '%v'", shrToken)
reserved := params.Body.Reserved
sshr := &store.Share{
ZId: shrZId,
Token: shrToken,
ShareMode: params.Body.ShareMode,
BackendMode: params.Body.BackendMode,
BackendProxyEndpoint: &params.Body.BackendProxyEndpoint,
Reserved: reserved,
}
if len(frontendEndpoints) > 0 {
sshr.FrontendEndpoint = &frontendEndpoints[0]
} else if sshr.ShareMode == "private" {
sshr.FrontendEndpoint = &sshr.ShareMode
}
sid, err := str.CreateShare(envId, sshr, tx)
if err != nil {
logrus.Errorf("error creating share record: %v", err)
return share.NewShareInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing share record: %v", err)
return share.NewShareInternalServerError()
}
logrus.Infof("recorded share '%v' with id '%v' for '%v'", shrToken, sid, principal.Email)
return share.NewShareCreated().WithPayload(&rest_model_zrok.ShareResponse{
FrontendProxyEndpoints: frontendEndpoints,
ShrToken: shrToken,
})
}
func (h *shareHandler) checkLimits(principal *rest_model_zrok.Principal, envs []*store.Environment, tx *sqlx.Tx) error {
if !principal.Limitless && h.cfg.Shares > Unlimited {
total := 0
for i := range envs {
shrs, err := str.FindSharesForEnvironment(envs[i].Id, tx)
if err != nil {
return errors.Errorf("unable to find shares for environment '%v': %v", envs[i].ZId, err)
}
total += len(shrs)
if total+1 > h.cfg.Shares {
return errors.Errorf("would exceed shares limit of %d for '%v'", h.cfg.Shares, principal.Email)
}
}
}
return nil
}

77
controller/shareDetail.go Normal file
View File

@ -0,0 +1,77 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/metadata"
"github.com/sirupsen/logrus"
)
type shareDetailHandler struct{}
func newShareDetailHandler() *shareDetailHandler {
return &shareDetailHandler{}
}
func (h *shareDetailHandler) Handle(params metadata.GetShareDetailParams, principal *rest_model_zrok.Principal) middleware.Responder {
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return metadata.NewGetShareDetailInternalServerError()
}
defer func() { _ = tx.Rollback() }()
shr, err := str.FindShareWithToken(params.ShrToken, tx)
if err != nil {
logrus.Errorf("error finding share '%v': %v", params.ShrToken, err)
return metadata.NewGetShareDetailNotFound()
}
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
if err != nil {
logrus.Errorf("error finding environments for account '%v': %v", principal.Email, err)
return metadata.NewGetShareDetailInternalServerError()
}
found := false
for _, env := range envs {
if shr.EnvironmentId == env.Id {
found = true
break
}
}
if !found {
logrus.Errorf("environment not matched for share '%v' for account '%v'", params.ShrToken, principal.Email)
return metadata.NewGetShareDetailNotFound()
}
var sparkData map[string][]int64
if cfg.Influx != nil {
sparkData, err = sparkDataForShares([]*store.Share{shr})
if err != nil {
logrus.Errorf("error querying spark data for share: %v", err)
}
}
feEndpoint := ""
if shr.FrontendEndpoint != nil {
feEndpoint = *shr.FrontendEndpoint
}
feSelection := ""
if shr.FrontendSelection != nil {
feSelection = *shr.FrontendSelection
}
beProxyEndpoint := ""
if shr.BackendProxyEndpoint != nil {
beProxyEndpoint = *shr.BackendProxyEndpoint
}
return metadata.NewGetShareDetailOK().WithPayload(&rest_model_zrok.Share{
Token: shr.Token,
ZID: shr.ZId,
ShareMode: shr.ShareMode,
BackendMode: shr.BackendMode,
FrontendSelection: feSelection,
FrontendEndpoint: feEndpoint,
BackendProxyEndpoint: beProxyEndpoint,
Reserved: shr.Reserved,
Metrics: sparkData[shr.Token],
CreatedAt: shr.CreatedAt.UnixMilli(),
UpdatedAt: shr.UpdatedAt.UnixMilli(),
})
}

View File

@ -0,0 +1,40 @@
package controller
import (
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/share"
"github.com/openziti/edge/rest_management_api_client"
)
type privateResourceAllocator struct{}
func newPrivateResourceAllocator() *privateResourceAllocator {
return &privateResourceAllocator{}
}
func (a *privateResourceAllocator) allocate(envZId, shrToken string, params share.ShareParams, edge *rest_management_api_client.ZitiEdgeManagement) (shrZId string, frontendEndpoints []string, err error) {
var authUsers []*model.AuthUser
for _, authUser := range params.Body.AuthUsers {
authUsers = append(authUsers, &model.AuthUser{authUser.Username, authUser.Password})
}
cfgZId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, edge)
if err != nil {
return "", nil, err
}
shrZId, err = zrokEdgeSdk.CreateShareService(envZId, shrToken, cfgZId, edge)
if err != nil {
return "", nil, err
}
if err := zrokEdgeSdk.CreateServicePolicyBind(envZId+"-"+shrZId+"-bind", shrZId, envZId, zrokEdgeSdk.ZrokShareTags(shrToken).SubTags, edge); err != nil {
return "", nil, err
}
if err := zrokEdgeSdk.CreateShareServiceEdgeRouterPolicy(envZId, shrToken, shrZId, edge); err != nil {
return "", nil, err
}
return shrZId, nil, nil
}

48
controller/sharePublic.go Normal file
View File

@ -0,0 +1,48 @@
package controller
import (
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/share"
"github.com/openziti/edge/rest_management_api_client"
)
type publicResourceAllocator struct{}
func newPublicResourceAllocator() *publicResourceAllocator {
return &publicResourceAllocator{}
}
func (a *publicResourceAllocator) allocate(envZId, shrToken string, frontendZIds, frontendTemplates []string, params share.ShareParams, edge *rest_management_api_client.ZitiEdgeManagement) (shrZId string, frontendEndpoints []string, err error) {
var authUsers []*model.AuthUser
for _, authUser := range params.Body.AuthUsers {
authUsers = append(authUsers, &model.AuthUser{authUser.Username, authUser.Password})
}
cfgId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, edge)
if err != nil {
return "", nil, err
}
shrZId, err = zrokEdgeSdk.CreateShareService(envZId, shrToken, cfgId, edge)
if err != nil {
return "", nil, err
}
if err := zrokEdgeSdk.CreateServicePolicyBind(envZId+"-"+shrZId+"-bind", shrZId, envZId, zrokEdgeSdk.ZrokShareTags(shrToken).SubTags, edge); err != nil {
return "", nil, err
}
if err := zrokEdgeSdk.CreateServicePolicyDial(envZId+"-"+shrZId+"-dial", shrZId, frontendZIds, zrokEdgeSdk.ZrokShareTags(shrToken).SubTags, edge); err != nil {
return "", nil, err
}
if err := zrokEdgeSdk.CreateShareServiceEdgeRouterPolicy(envZId, shrToken, shrZId, edge); err != nil {
return "", nil, err
}
for _, frontendTemplate := range frontendTemplates {
frontendEndpoints = append(frontendEndpoints, proxyUrl(shrToken, frontendTemplate))
}
return shrZId, frontendEndpoints, nil
}

58
controller/sparkData.go Normal file
View File

@ -0,0 +1,58 @@
package controller
import (
"context"
"fmt"
"github.com/openziti-test-kitchen/zrok/controller/store"
)
func sparkDataForShares(shrs []*store.Share) (map[string][]int64, error) {
out := make(map[string][]int64)
if len(shrs) > 0 {
qapi := idb.QueryAPI(cfg.Influx.Org)
result, err := qapi.Query(context.Background(), sparkFluxQuery(shrs))
if err != nil {
return nil, err
}
for result.Next() {
combinedRate := int64(0)
readRate := result.Record().ValueByKey("bytesRead")
if readRate != nil {
combinedRate += readRate.(int64)
}
writeRate := result.Record().ValueByKey("bytesWritten")
if writeRate != nil {
combinedRate += writeRate.(int64)
}
shrToken := result.Record().ValueByKey("share").(string)
shrMetrics := out[shrToken]
shrMetrics = append(shrMetrics, combinedRate)
out[shrToken] = shrMetrics
}
}
return out, nil
}
func sparkFluxQuery(shrs []*store.Share) string {
shrFilter := "|> filter(fn: (r) =>"
for i, shr := range shrs {
if i > 0 {
shrFilter += " or"
}
shrFilter += fmt.Sprintf(" r[\"share\"] == \"%v\"", shr.Token)
}
shrFilter += ")"
query := "read = from(bucket: \"zrok\")" +
"|> range(start: -5m)" +
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")" +
"|> filter(fn: (r) => r[\"_field\"] == \"bytesRead\" or r[\"_field\"] == \"bytesWritten\")" +
"|> filter(fn: (r) => r[\"namespace\"] == \"frontend\")" +
shrFilter +
"|> aggregateWindow(every: 5s, fn: sum, createEmpty: true)\n" +
"|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")" +
"|> yield(name: \"last\")"
return query
}

View File

@ -6,7 +6,6 @@ import (
"github.com/openziti-test-kitchen/zrok/model" "github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti/edge/rest_management_api_client" "github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/config" "github.com/openziti/edge/rest_management_api_client/config"
"github.com/openziti/edge/rest_model"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"time" "time"
@ -28,14 +27,14 @@ func inspectZiti() error {
if err != nil { if err != nil {
return errors.Wrap(err, "error getting ziti edge client") return errors.Wrap(err, "error getting ziti edge client")
} }
if err := ensureZrokProxyConfigType(edge); err != nil { if err := findZrokProxyConfigType(edge); err != nil {
return err return err
} }
return nil return nil
} }
func ensureZrokProxyConfigType(edge *rest_management_api_client.ZitiEdgeManagement) error { func findZrokProxyConfigType(edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("name=\"%v\"", model.ZrokProxyConfig) filter := fmt.Sprintf("name=\"%v\"", model.ZrokProxyConfig)
limit := int64(100) limit := int64(100)
offset := int64(0) offset := int64(0)
@ -50,22 +49,11 @@ func ensureZrokProxyConfigType(edge *rest_management_api_client.ZitiEdgeManageme
if err != nil { if err != nil {
return err return err
} }
if len(listResp.Payload.Data) < 1 { if len(listResp.Payload.Data) != 1 {
name := model.ZrokProxyConfig return errors.Errorf("expected 1 zrok proxy config type, found %d", len(listResp.Payload.Data))
ct := &rest_model.ConfigTypeCreate{Name: &name}
createReq := &config.CreateConfigTypeParams{ConfigType: ct}
createReq.SetTimeout(30 * time.Second)
createResp, err := edge.Config.CreateConfigType(createReq, nil)
if err != nil {
return err
}
logrus.Infof("created '%v' config type with id '%v'", model.ZrokProxyConfig, createResp.Payload.Data.ID)
zrokProxyConfigId = createResp.Payload.Data.ID
} else if len(listResp.Payload.Data) > 1 {
return errors.Errorf("found %d '%v' config types; expected 0 or 1", len(listResp.Payload.Data), model.ZrokProxyConfig)
} else {
logrus.Infof("found '%v' config type with id '%v'", model.ZrokProxyConfig, *(listResp.Payload.Data[0].ID))
zrokProxyConfigId = *(listResp.Payload.Data[0].ID)
} }
logrus.Infof("found '%v' config type with id '%v'", model.ZrokProxyConfig, *(listResp.Payload.Data[0].ID))
zrokProxyConfigId = *(listResp.Payload.Data[0].ID)
return nil return nil
} }

View File

@ -7,18 +7,19 @@ import (
type Account struct { type Account struct {
Model Model
Email string Email string
Password string Password string
Token string Token string
Limitless bool
} }
func (self *Store) CreateAccount(a *Account, tx *sqlx.Tx) (int, error) { func (self *Store) CreateAccount(a *Account, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into accounts (email, password, token) values ($1, $2, $3) returning id") stmt, err := tx.Prepare("insert into accounts (email, password, token, limitless) values ($1, $2, $3, $4) returning id")
if err != nil { if err != nil {
return 0, errors.Wrap(err, "error preparing accounts insert statement") return 0, errors.Wrap(err, "error preparing accounts insert statement")
} }
var id int var id int
if err := stmt.QueryRow(a.Email, a.Password, a.Token).Scan(&id); err != nil { if err := stmt.QueryRow(a.Email, a.Password, a.Token, a.Limitless).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing accounts insert statement") return 0, errors.Wrap(err, "error executing accounts insert statement")
} }
return id, nil return id, nil

View File

@ -1,6 +1,10 @@
package store package store
import ( import (
"fmt"
"strings"
"time"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -40,6 +44,34 @@ func (self *Store) FindAccountRequestWithToken(token string, tx *sqlx.Tx) (*Acco
return ar, nil return ar, nil
} }
func (self *Store) FindExpiredAccountRequests(before time.Time, limit int, tx *sqlx.Tx) ([]*AccountRequest, error) {
var sql string
switch self.cfg.Type {
case "postgres":
sql = "select * from account_requests where created_at < $1 limit %d for update"
case "sqlite3":
sql = "select * from account_requests where created_at < $1 limit %d"
default:
return nil, errors.Errorf("unknown database type '%v'", self.cfg.Type)
}
rows, err := tx.Queryx(fmt.Sprintf(sql, limit), before)
if err != nil {
return nil, errors.Wrap(err, "error selecting expired account_requests")
}
var ars []*AccountRequest
for rows.Next() {
ar := &AccountRequest{}
if err := rows.StructScan(ar); err != nil {
return nil, errors.Wrap(err, "error scanning account_request")
}
ars = append(ars, ar)
}
return ars, nil
}
func (self *Store) FindAccountRequestWithEmail(email string, tx *sqlx.Tx) (*AccountRequest, error) { func (self *Store) FindAccountRequestWithEmail(email string, tx *sqlx.Tx) (*AccountRequest, error) {
ar := &AccountRequest{} ar := &AccountRequest{}
if err := tx.QueryRowx("select * from account_requests where email = $1", email).StructScan(ar); err != nil { if err := tx.QueryRowx("select * from account_requests where email = $1", email).StructScan(ar); err != nil {
@ -59,3 +91,27 @@ func (self *Store) DeleteAccountRequest(id int, tx *sqlx.Tx) error {
} }
return nil return nil
} }
func (self *Store) DeleteMultipleAccountRequests(ids []int, tx *sqlx.Tx) error {
if len(ids) == 0 {
return nil
}
anyIds := make([]any, len(ids))
indexes := make([]string, len(ids))
for i, id := range ids {
anyIds[i] = id
indexes[i] = fmt.Sprintf("$%d", i+1)
}
stmt, err := tx.Prepare(fmt.Sprintf("delete from account_requests where id in (%s)", strings.Join(indexes, ",")))
if err != nil {
return errors.Wrap(err, "error preparing account_requests delete multiple statement")
}
_, err = stmt.Exec(anyIds...)
if err != nil {
return errors.Wrap(err, "error executing account_requests delete multiple statement")
}
return nil
}

View File

@ -7,7 +7,7 @@ import (
type Environment struct { type Environment struct {
Model Model
AccountId int AccountId *int
Description string Description string
Host string Host string
Address string Address string
@ -26,6 +26,18 @@ func (self *Store) CreateEnvironment(accountId int, i *Environment, tx *sqlx.Tx)
return id, nil return id, nil
} }
func (self *Store) CreateEphemeralEnvironment(i *Environment, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into environments (description, host, address, z_id) values ($1, $2, $3, $4) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing environments (ephemeral) insert statement")
}
var id int
if err := stmt.QueryRow(i.Description, i.Host, i.Address, i.ZId).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing environments (ephemeral) insert statement")
}
return id, nil
}
func (self *Store) GetEnvironment(id int, tx *sqlx.Tx) (*Environment, error) { func (self *Store) GetEnvironment(id int, tx *sqlx.Tx) (*Environment, error) {
i := &Environment{} i := &Environment{}
if err := tx.QueryRowx("select * from environments where id = $1", id).StructScan(i); err != nil { if err := tx.QueryRowx("select * from environments where id = $1", id).StructScan(i); err != nil {
@ -50,6 +62,14 @@ func (self *Store) FindEnvironmentsForAccount(accountId int, tx *sqlx.Tx) ([]*En
return is, nil return is, nil
} }
func (self *Store) FindEnvironmentForAccount(envZId string, accountId int, tx *sqlx.Tx) (*Environment, error) {
env := &Environment{}
if err := tx.QueryRowx("select environments.* from environments where z_id = $1 and account_id = $2", envZId, accountId).StructScan(env); err != nil {
return nil, errors.Wrap(err, "error finding environment by z_id and account_id")
}
return env, nil
}
func (self *Store) DeleteEnvironment(id int, tx *sqlx.Tx) error { func (self *Store) DeleteEnvironment(id int, tx *sqlx.Tx) error {
stmt, err := tx.Prepare("delete from environments where id = $1") stmt, err := tx.Prepare("delete from environments where id = $1")
if err != nil { if err != nil {

View File

@ -0,0 +1,60 @@
package store
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestEphemeralEnvironment(t *testing.T) {
str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"})
assert.Nil(t, err)
assert.NotNil(t, str)
tx, err := str.Begin()
assert.Nil(t, err)
assert.NotNil(t, tx)
envId, err := str.CreateEphemeralEnvironment(&Environment{
Description: "description",
Host: "host",
Address: "address",
ZId: "zId0",
}, tx)
assert.Nil(t, err)
env, err := str.GetEnvironment(envId, tx)
assert.Nil(t, err)
assert.NotNil(t, env)
assert.Nil(t, env.AccountId)
}
func TestEnvironment(t *testing.T) {
str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"})
assert.Nil(t, err)
assert.NotNil(t, str)
tx, err := str.Begin()
assert.Nil(t, err)
assert.NotNil(t, tx)
acctId, err := str.CreateAccount(&Account{
Email: "test@test.com",
Password: "password",
Token: "token",
}, tx)
assert.Nil(t, err)
envId, err := str.CreateEnvironment(acctId, &Environment{
Description: "description",
Host: "host",
Address: "address",
ZId: "zId0",
}, tx)
assert.Nil(t, err)
env, err := str.GetEnvironment(envId, tx)
assert.Nil(t, err)
assert.NotNil(t, env)
assert.NotNil(t, env.AccountId)
assert.Equal(t, acctId, *env.AccountId)
}

View File

@ -0,0 +1,130 @@
package store
import (
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
type Frontend struct {
Model
EnvironmentId *int
Token string
ZId string
PublicName *string
UrlTemplate *string
Reserved bool
}
func (str *Store) CreateFrontend(envId int, f *Frontend, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into frontends (environment_id, token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5, $6) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing frontends insert statement")
}
var id int
if err := stmt.QueryRow(envId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing frontends insert statement")
}
return id, nil
}
func (str *Store) CreateGlobalFrontend(f *Frontend, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into frontends (token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing global frontends insert statement")
}
var id int
if err := stmt.QueryRow(f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing global frontends insert statement")
}
return id, nil
}
func (str *Store) GetFrontend(id int, tx *sqlx.Tx) (*Frontend, error) {
i := &Frontend{}
if err := tx.QueryRowx("select * from frontends where id = $1", id).StructScan(i); err != nil {
return nil, errors.Wrap(err, "error selecting frontend by id")
}
return i, nil
}
func (str *Store) FindFrontendWithToken(token string, tx *sqlx.Tx) (*Frontend, error) {
i := &Frontend{}
if err := tx.QueryRowx("select frontends.* from frontends where token = $1", token).StructScan(i); err != nil {
return nil, errors.Wrap(err, "error selecting frontend by name")
}
return i, nil
}
func (str *Store) FindFrontendWithZId(zId string, tx *sqlx.Tx) (*Frontend, error) {
i := &Frontend{}
if err := tx.QueryRowx("select frontends.* from frontends where z_id = $1", zId).StructScan(i); err != nil {
return nil, errors.Wrap(err, "error selecting frontend by ziti id")
}
return i, nil
}
func (str *Store) FindFrontendPubliclyNamed(publicName string, tx *sqlx.Tx) (*Frontend, error) {
i := &Frontend{}
if err := tx.QueryRowx("select frontends.* from frontends where public_name = $1", publicName).StructScan(i); err != nil {
return nil, errors.Wrap(err, "error selecting frontend by public_name")
}
return i, nil
}
func (str *Store) FindFrontendsForEnvironment(envId int, tx *sqlx.Tx) ([]*Frontend, error) {
rows, err := tx.Queryx("select frontends.* from frontends where environment_id = $1", envId)
if err != nil {
return nil, errors.Wrap(err, "error selecting frontends by environment_id")
}
var is []*Frontend
for rows.Next() {
i := &Frontend{}
if err := rows.StructScan(i); err != nil {
return nil, errors.Wrap(err, "error scanning frontend")
}
is = append(is, i)
}
return is, nil
}
func (str *Store) FindPublicFrontends(tx *sqlx.Tx) ([]*Frontend, error) {
rows, err := tx.Queryx("select frontends.* from frontends where environment_id is null and reserved = true")
if err != nil {
return nil, errors.Wrap(err, "error selecting public frontends")
}
var frontends []*Frontend
for rows.Next() {
frontend := &Frontend{}
if err := rows.StructScan(frontend); err != nil {
return nil, errors.Wrap(err, "error scanning frontend")
}
frontends = append(frontends, frontend)
}
return frontends, nil
}
func (str *Store) UpdateFrontend(fe *Frontend, tx *sqlx.Tx) error {
sql := "update frontends set environment_id = $1, token = $2, z_id = $3, public_name = $4, url_template = $5, reserved = $6, updated_at = current_timestamp where id = $7"
stmt, err := tx.Prepare(sql)
if err != nil {
return errors.Wrap(err, "error preparing frontends update statement")
}
_, err = stmt.Exec(fe.EnvironmentId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.Id)
if err != nil {
return errors.Wrap(err, "error executing frontends update statement")
}
return nil
}
func (str *Store) DeleteFrontend(id int, tx *sqlx.Tx) error {
stmt, err := tx.Prepare("delete from frontends where id = $1")
if err != nil {
return errors.Wrap(err, "error preparing frontends delete statement")
}
_, err = stmt.Exec(id)
if err != nil {
return errors.Wrap(err, "error executing frontends delete statement")
}
return nil
}

View File

@ -0,0 +1,61 @@
package store
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestPublicFrontend(t *testing.T) {
str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"})
assert.Nil(t, err)
assert.NotNil(t, str)
tx, err := str.Begin()
assert.Nil(t, err)
assert.NotNil(t, tx)
acctId, err := str.CreateAccount(&Account{
Email: "test@test.com",
Password: "password",
Token: "token",
}, tx)
assert.Nil(t, err)
envId, err := str.CreateEnvironment(acctId, &Environment{
Description: "description",
Host: "host",
Address: "address",
ZId: "zId0",
}, tx)
assert.Nil(t, err)
feName := "public"
feId, err := str.CreateFrontend(envId, &Frontend{
Token: "token",
ZId: "zId0",
PublicName: &feName,
}, tx)
assert.Nil(t, err)
fe, err := str.GetFrontend(feId, tx)
assert.Nil(t, err)
assert.NotNil(t, fe)
assert.Equal(t, envId, *fe.EnvironmentId)
assert.Equal(t, feName, *fe.PublicName)
fe0, err := str.FindFrontendPubliclyNamed(feName, tx)
assert.Nil(t, err)
assert.NotNil(t, fe0)
assert.EqualValues(t, fe, fe0)
err = str.DeleteFrontend(fe.Id, tx)
assert.Nil(t, err)
fe0, err = str.FindFrontendWithToken(feName, tx)
assert.NotNil(t, err)
assert.Nil(t, fe0)
fe0, err = str.GetFrontend(fe.Id, tx)
assert.NotNil(t, err)
assert.Nil(t, fe0)
}

View File

@ -0,0 +1,52 @@
package store
import (
"fmt"
"strings"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
type InviteToken struct {
Model
Token string
}
func (str *Store) CreateInviteTokens(inviteTokens []*InviteToken, tx *sqlx.Tx) error {
sql := "insert into invite_tokens (token) values %s"
invs := make([]any, len(inviteTokens))
queries := make([]string, len(inviteTokens))
for i, inv := range inviteTokens {
invs[i] = inv.Token
queries[i] = fmt.Sprintf("($%d)", i+1)
}
stmt, err := tx.Prepare(fmt.Sprintf(sql, strings.Join(queries, ",")))
if err != nil {
return errors.Wrap(err, "error preparing invite_tokens insert statement")
}
if _, err := stmt.Exec(invs...); err != nil {
return errors.Wrap(err, "error executing invites_tokens insert statement")
}
return nil
}
func (str *Store) GetInviteTokenByToken(token string, tx *sqlx.Tx) (*InviteToken, error) {
inviteToken := &InviteToken{}
if err := tx.QueryRowx("select * from invite_tokens where token = $1", token).StructScan(inviteToken); err != nil {
return nil, errors.Wrap(err, "error getting unused invite_token")
}
return inviteToken, nil
}
func (str *Store) DeleteInviteToken(id int, tx *sqlx.Tx) error {
stmt, err := tx.Prepare("delete from invite_tokens where id = $1")
if err != nil {
return errors.Wrap(err, "error preparing invite_tokens delete statement")
}
_, err = stmt.Exec(id)
if err != nil {
return errors.Wrap(err, "error executing invite_tokens delete statement")
}
return nil
}

View File

@ -1,92 +0,0 @@
package store
import (
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
type Service struct {
Model
EnvironmentId int
ZId string
Name string
Frontend string
Backend string
}
func (self *Store) CreateService(envId int, svc *Service, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into services (environment_id, z_id, name, frontend, backend) values ($1, $2, $3, $4, $5) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing services insert statement")
}
var id int
if err := stmt.QueryRow(envId, svc.ZId, svc.Name, svc.Frontend, svc.Backend).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing services insert statement")
}
return id, nil
}
func (self *Store) GetService(id int, tx *sqlx.Tx) (*Service, error) {
svc := &Service{}
if err := tx.QueryRowx("select * from services where id = $1", id).StructScan(svc); err != nil {
return nil, errors.Wrap(err, "error selecting service by id")
}
return svc, nil
}
func (self *Store) GetAllServices(tx *sqlx.Tx) ([]*Service, error) {
rows, err := tx.Queryx("select * from services order by id")
if err != nil {
return nil, errors.Wrap(err, "error selecting all services")
}
var svcs []*Service
for rows.Next() {
svc := &Service{}
if err := rows.StructScan(svc); err != nil {
return nil, errors.Wrap(err, "error scanning service")
}
svcs = append(svcs, svc)
}
return svcs, nil
}
func (self *Store) FindServicesForEnvironment(envId int, tx *sqlx.Tx) ([]*Service, error) {
rows, err := tx.Queryx("select services.* from services where environment_id = $1", envId)
if err != nil {
return nil, errors.Wrap(err, "error selecting services by environment id")
}
var svcs []*Service
for rows.Next() {
svc := &Service{}
if err := rows.StructScan(svc); err != nil {
return nil, errors.Wrap(err, "error scanning service")
}
svcs = append(svcs, svc)
}
return svcs, nil
}
func (self *Store) UpdateService(svc *Service, tx *sqlx.Tx) error {
sql := "update services set z_id = $1, name = $2, frontend = $3, backend = $4, updated_at = strftime('%Y-%m-%d %H:%M:%f', 'now') where id = $5"
stmt, err := tx.Prepare(sql)
if err != nil {
return errors.Wrap(err, "error preparing services update statement")
}
_, err = stmt.Exec(svc.ZId, svc.Name, svc.Frontend, svc.Backend, svc.Id)
if err != nil {
return errors.Wrap(err, "error executing services update statement")
}
return nil
}
func (self *Store) DeleteService(id int, tx *sqlx.Tx) error {
stmt, err := tx.Prepare("delete from services where id = $1")
if err != nil {
return errors.Wrap(err, "error preparing services delete statement")
}
_, err = stmt.Exec(id)
if err != nil {
return errors.Wrap(err, "error executing services delete statement")
}
return nil
}

104
controller/store/share.go Normal file
View File

@ -0,0 +1,104 @@
package store
import (
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
type Share struct {
Model
EnvironmentId int
ZId string
Token string
ShareMode string
BackendMode string
FrontendSelection *string
FrontendEndpoint *string
BackendProxyEndpoint *string
Reserved bool
}
func (self *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error) {
stmt, err := tx.Prepare("insert into shares (environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, reserved) values ($1, $2, $3, $4, $5, $6, $7, $8, $9) returning id")
if err != nil {
return 0, errors.Wrap(err, "error preparing shares insert statement")
}
var id int
if err := stmt.QueryRow(envId, shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved).Scan(&id); err != nil {
return 0, errors.Wrap(err, "error executing shares insert statement")
}
return id, nil
}
func (self *Store) GetShare(id int, tx *sqlx.Tx) (*Share, error) {
shr := &Share{}
if err := tx.QueryRowx("select * from shares where id = $1", id).StructScan(shr); err != nil {
return nil, errors.Wrap(err, "error selecting share by id")
}
return shr, nil
}
func (self *Store) GetAllShares(tx *sqlx.Tx) ([]*Share, error) {
rows, err := tx.Queryx("select * from shares order by id")
if err != nil {
return nil, errors.Wrap(err, "error selecting all shares")
}
var shrs []*Share
for rows.Next() {
shr := &Share{}
if err := rows.StructScan(shr); err != nil {
return nil, errors.Wrap(err, "error scanning share")
}
shrs = append(shrs, shr)
}
return shrs, nil
}
func (self *Store) FindShareWithToken(shrToken string, tx *sqlx.Tx) (*Share, error) {
shr := &Share{}
if err := tx.QueryRowx("select * from shares where token = $1", shrToken).StructScan(shr); err != nil {
return nil, errors.Wrap(err, "error selecting share by token")
}
return shr, nil
}
func (self *Store) FindSharesForEnvironment(envId int, tx *sqlx.Tx) ([]*Share, error) {
rows, err := tx.Queryx("select shares.* from shares where environment_id = $1", envId)
if err != nil {
return nil, errors.Wrap(err, "error selecting shares by environment id")
}
var shrs []*Share
for rows.Next() {
shr := &Share{}
if err := rows.StructScan(shr); err != nil {
return nil, errors.Wrap(err, "error scanning share")
}
shrs = append(shrs, shr)
}
return shrs, nil
}
func (self *Store) UpdateShare(shr *Share, tx *sqlx.Tx) error {
sql := "update shares set z_id = $1, token = $2, share_mode = $3, backend_mode = $4, frontend_selection = $5, frontend_endpoint = $6, backend_proxy_endpoint = $7, reserved = $8, updated_at = current_timestamp where id = $9"
stmt, err := tx.Prepare(sql)
if err != nil {
return errors.Wrap(err, "error preparing shares update statement")
}
_, err = stmt.Exec(shr.ZId, shr.Token, shr.ShareMode, shr.BackendMode, shr.FrontendSelection, shr.FrontendEndpoint, shr.BackendProxyEndpoint, shr.Reserved, shr.Id)
if err != nil {
return errors.Wrap(err, "error executing shares update statement")
}
return nil
}
func (self *Store) DeleteShare(id int, tx *sqlx.Tx) error {
stmt, err := tx.Prepare("delete from shares where id = $1")
if err != nil {
return errors.Wrap(err, "error preparing shares delete statement")
}
_, err = stmt.Exec(id)
if err != nil {
return errors.Wrap(err, "error executing shares delete statement")
}
return nil
}

View File

@ -0,0 +1,17 @@
-- +migrate Up
create table frontends (
id serial primary key,
environment_id integer references environments(id),
token varchar(32) not null unique,
z_id varchar(32) not null,
url_template varchar(1024),
public_name varchar(64) unique,
reserved boolean not null default(false),
created_at timestamptz not null default(current_timestamp),
updated_at timestamptz not null default(current_timestamp)
);
-- environments.account_id should allow NULL; environments with NULL account_id are "ephemeral"
alter table environments drop constraint fk_accounts_identities;
alter table environments add constraint fk_accounts_id foreign key (account_id) references accounts(id);

View File

@ -0,0 +1,44 @@
-- +migrate Up
create type share_mode as enum ('public', 'private');
create type backend_mode as enum ('proxy', 'web', 'dav');
alter table services
add column frontend_selection varchar(64),
add column share_mode share_mode not null default 'public',
add column backend_mode backend_mode not null default 'proxy',
add column reserved boolean not null default false;
alter table services
alter column share_mode drop default;
alter table services
alter column backend_mode drop default;
alter table services rename frontend to frontend_endpoint;
alter table services rename backend to backend_proxy_endpoint;
alter table services rename name to token;
alter table services rename to services_old;
create table services (
id serial primary key,
environment_id integer not null references environments(id),
z_id varchar(32) not null unique,
token varchar(32) not null unique,
share_mode share_mode not null,
backend_mode backend_mode not null,
frontend_selection varchar(64),
frontend_endpoint varchar(1024),
backend_proxy_endpoint varchar(1024),
reserved boolean not null default(false),
created_at timestamptz not null default(current_timestamp),
updated_at timestamptz not null default(current_timestamp),
constraint chk_z_id check (z_id <> ''),
constraint chk_token check (token <> '')
);
insert into services (id, environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, created_at, updated_at)
select id, environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, created_at, updated_at from services_old;
drop table services_old;

View File

@ -0,0 +1,8 @@
-- +migrate Up
alter table services rename to shares;
alter sequence services_id_seq1 rename to shares_id_seq1;
alter index services_pkey1 rename to shares_pkey1;
alter index services_token_key rename to shares_token_key;
alter index services_z_id_key1 rename to shares_z_id_key1;
alter table shares rename constraint services_environment_id_fkey to shares_environment_id_fkey;

View File

@ -0,0 +1,14 @@
-- +migrate Up
--
-- invite_tokens
---
create table invite_tokens (
id serial primary key,
token varchar(32) not null unique,
created_at timestamptz not null default(current_timestamp),
updated_at timestamptz not null default(current_timestamp),
constraint chk_token check(token <> '')
);

View File

@ -0,0 +1,25 @@
-- +migrate Up
alter table accounts rename to accounts_old;
create table accounts (
id serial primary key,
email varchar(1024) not null unique,
password char(128) not null,
token varchar(32) not null unique,
limitless boolean not null default(false),
created_at timestamp not null default(current_timestamp),
updated_at timestamp not null default(current_timestamp),
constraint chk_email check (email <> ''),
constraint chk_password check (password <> ''),
constraint chk_token check(token <> '')
);
insert into accounts(id, email, password, token, created_at, updated_at)
select id, email, password, token, created_at, updated_at from accounts_old;
alter table environments drop constraint fk_accounts_id;
alter table environments add constraint fk_accounts_id foreign key (account_id) references accounts(id);
drop table accounts_old;

View File

@ -0,0 +1,30 @@
-- +migrate Up
-- environments.account_id should allow NULL; environments with NULL account_id are "ephemeral"
alter table environments rename to environments_old;
create table environments (
id integer primary key,
account_id integer references accounts(id) on delete cascade,
description string,
host string,
address string,
z_id string not null unique,
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')),
constraint chk_z_id check (z_id <> '')
);
insert into environments select * from environments_old;
drop table environments_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'))
);

View File

@ -0,0 +1,34 @@
-- +migrate Up
alter table services add column frontend_selection string;
alter table services add column share_mode string not null default 'public';
alter table services add column backend_mode string not null default 'proxy';
alter table services add column reserved boolean not null default false;
alter table services rename column name to token;
alter table services rename to services_old;
create table services (
id integer primary key,
environment_id integer constraint fk_environments_services 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')),
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 == 'dav')
);
insert into services (id, environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, created_at, updated_at)
select id, environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend, backend, created_at, updated_at from services_old;
drop table services_old;

View File

@ -0,0 +1,25 @@
-- +migrate Up
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')),
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 == 'dav')
);
insert into shares select * from services;
drop table services;

View File

@ -0,0 +1,14 @@
-- +migrate Up
--
-- invite_tokens
---
create table invite_tokens (
id integer primary key,
token string not null unique,
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')),
constraint chk_token check(token <> '')
);

View File

@ -0,0 +1,3 @@
-- +migrate Up
alter table accounts add column limitless boolean not null default(false);

View File

@ -1,270 +0,0 @@
package controller
import (
"context"
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/build"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/tunnel"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/config"
"github.com/openziti/edge/rest_management_api_client/service"
"github.com/openziti/edge/rest_management_api_client/service_edge_router_policy"
"github.com/openziti/edge/rest_management_api_client/service_policy"
"github.com/openziti/edge/rest_model"
"github.com/sirupsen/logrus"
"strings"
"time"
)
type tunnelHandler struct {
}
func newTunnelHandler() *tunnelHandler {
return &tunnelHandler{}
}
func (h *tunnelHandler) Handle(params tunnel.TunnelParams, principal *rest_model_zrok.Principal) middleware.Responder {
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return tunnel.NewTunnelInternalServerError()
}
defer func() { _ = tx.Rollback() }()
envZId := params.Body.ZID
envId := 0
if envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx); err == nil {
found := false
for _, env := range envs {
if env.ZId == envZId {
logrus.Debugf("found identity '%v' for user '%v'", envZId, principal.Email)
envId = env.Id
found = true
break
}
}
if !found {
logrus.Errorf("environment '%v' not found for user '%v'", envZId, principal.Email)
return tunnel.NewTunnelUnauthorized().WithPayload("bad environment identity")
}
} else {
logrus.Errorf("error finding environments for account '%v'", principal.Email)
return tunnel.NewTunnelInternalServerError()
}
edge, err := edgeClient()
if err != nil {
logrus.Error(err)
return tunnel.NewTunnelInternalServerError()
}
svcName, err := createServiceName()
if err != nil {
logrus.Error(err)
return tunnel.NewTunnelInternalServerError()
}
cfgId, err := h.createConfig(envZId, svcName, params, edge)
if err != nil {
logrus.Error(err)
return tunnel.NewTunnelInternalServerError()
}
svcZId, err := h.createService(envZId, svcName, cfgId, edge)
if err != nil {
logrus.Error(err)
return tunnel.NewTunnelInternalServerError()
}
if err := h.createServicePolicyBind(envZId, svcName, svcZId, envZId, edge); err != nil {
logrus.Error(err)
return tunnel.NewTunnelInternalServerError()
}
if err := h.createServicePolicyDial(envZId, svcName, svcZId, edge); err != nil {
logrus.Error(err)
return tunnel.NewTunnelInternalServerError()
}
if err := h.createServiceEdgeRouterPolicy(envZId, svcName, svcZId, edge); err != nil {
logrus.Error(err)
return tunnel.NewTunnelInternalServerError()
}
logrus.Debugf("allocated service '%v'", svcName)
frontendUrl := h.proxyUrl(svcName)
sid, err := str.CreateService(envId, &store.Service{
ZId: svcZId,
Name: svcName,
Frontend: frontendUrl,
Backend: params.Body.Endpoint,
}, tx)
if err != nil {
logrus.Errorf("error creating service record: %v", err)
_ = tx.Rollback()
return tunnel.NewUntunnelInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing service record: %v", err)
return tunnel.NewTunnelInternalServerError()
}
logrus.Infof("recorded service '%v' with id '%v' for '%v'", svcName, sid, principal.Email)
return tunnel.NewTunnelCreated().WithPayload(&rest_model_zrok.TunnelResponse{
ProxyEndpoint: frontendUrl,
SvcName: svcName,
})
}
func (h *tunnelHandler) createConfig(envZId, svcName string, params tunnel.TunnelParams, edge *rest_management_api_client.ZitiEdgeManagement) (cfgID string, err error) {
authScheme, err := model.ParseAuthScheme(params.Body.AuthScheme)
if err != nil {
return "", err
}
cfg := &model.ProxyConfig{
AuthScheme: authScheme,
}
if cfg.AuthScheme == model.Basic {
cfg.BasicAuth = &model.BasicAuth{}
for _, authUser := range params.Body.AuthUsers {
cfg.BasicAuth.Users = append(cfg.BasicAuth.Users, &model.AuthUser{Username: authUser.Username, Password: authUser.Password})
}
}
cfgCrt := &rest_model.ConfigCreate{
ConfigTypeID: &zrokProxyConfigId,
Data: cfg,
Name: &svcName,
Tags: h.zrokTags(svcName),
}
cfgReq := &config.CreateConfigParams{
Config: cfgCrt,
Context: context.Background(),
}
cfgReq.SetTimeout(30 * time.Second)
cfgResp, err := edge.Config.CreateConfig(cfgReq, nil)
if err != nil {
return "", err
}
logrus.Infof("created config '%v' for environment '%v'", cfgResp.Payload.Data.ID, envZId)
return cfgResp.Payload.Data.ID, nil
}
func (h *tunnelHandler) createService(envZId, svcName, cfgId string, edge *rest_management_api_client.ZitiEdgeManagement) (serviceId string, err error) {
configs := []string{cfgId}
encryptionRequired := true
svc := &rest_model.ServiceCreate{
Configs: configs,
EncryptionRequired: &encryptionRequired,
Name: &svcName,
Tags: h.zrokTags(svcName),
}
req := &service.CreateServiceParams{
Service: svc,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
resp, err := edge.Service.CreateService(req, nil)
if err != nil {
return "", err
}
logrus.Infof("created zrok service named '%v' (with ziti id '%v') for environment '%v'", svcName, resp.Payload.Data.ID, envZId)
return resp.Payload.Data.ID, nil
}
func (h *tunnelHandler) createServicePolicyBind(envZId, svcName, svcZId, envId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
semantic := rest_model.SemanticAllOf
identityRoles := []string{fmt.Sprintf("@%v", envId)}
name := fmt.Sprintf("%v-backend", svcName)
var postureCheckRoles []string
serviceRoles := []string{fmt.Sprintf("@%v", svcZId)}
dialBind := rest_model.DialBindBind
svcp := &rest_model.ServicePolicyCreate{
IdentityRoles: identityRoles,
Name: &name,
PostureCheckRoles: postureCheckRoles,
Semantic: &semantic,
ServiceRoles: serviceRoles,
Type: &dialBind,
Tags: h.zrokTags(svcName),
}
req := &service_policy.CreateServicePolicyParams{
Policy: svcp,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
resp, err := edge.ServicePolicy.CreateServicePolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("created bind service policy '%v' for service '%v' for environment '%v'", resp.Payload.Data.ID, svcZId, envZId)
return nil
}
func (h *tunnelHandler) createServicePolicyDial(envZId, svcName, svcZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
var identityRoles []string
for _, proxyIdentity := range cfg.Proxy.Identities {
identityRoles = append(identityRoles, "@"+proxyIdentity)
logrus.Infof("added proxy identity role '%v'", proxyIdentity)
}
name := fmt.Sprintf("%v-dial", svcName)
var postureCheckRoles []string
semantic := rest_model.SemanticAllOf
serviceRoles := []string{fmt.Sprintf("@%v", svcZId)}
dialBind := rest_model.DialBindDial
svcp := &rest_model.ServicePolicyCreate{
IdentityRoles: identityRoles,
Name: &name,
PostureCheckRoles: postureCheckRoles,
Semantic: &semantic,
ServiceRoles: serviceRoles,
Type: &dialBind,
Tags: h.zrokTags(svcName),
}
req := &service_policy.CreateServicePolicyParams{
Policy: svcp,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
resp, err := edge.ServicePolicy.CreateServicePolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("created dial service policy '%v' for service '%v' for environment '%v'", resp.Payload.Data.ID, svcZId, envZId)
return nil
}
func (h *tunnelHandler) createServiceEdgeRouterPolicy(envZId, svcName, svcZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
edgeRouterRoles := []string{"#all"}
semantic := rest_model.SemanticAllOf
serviceRoles := []string{fmt.Sprintf("@%v", svcZId)}
serp := &rest_model.ServiceEdgeRouterPolicyCreate{
EdgeRouterRoles: edgeRouterRoles,
Name: &svcName,
Semantic: &semantic,
ServiceRoles: serviceRoles,
Tags: h.zrokTags(svcName),
}
serpParams := &service_edge_router_policy.CreateServiceEdgeRouterPolicyParams{
Policy: serp,
Context: context.Background(),
}
serpParams.SetTimeout(30 * time.Second)
resp, err := edge.ServiceEdgeRouterPolicy.CreateServiceEdgeRouterPolicy(serpParams, nil)
if err != nil {
return err
}
logrus.Infof("created service edge router policy '%v' for service '%v' for environment '%v'", resp.Payload.Data.ID, svcZId, envZId)
return nil
}
func (h *tunnelHandler) proxyUrl(svcName string) string {
return strings.Replace(cfg.Proxy.UrlTemplate, "{svcName}", svcName, -1)
}
func (h *tunnelHandler) zrokTags(svcName string) *rest_model.Tags {
return &rest_model.Tags{
SubTags: map[string]interface{}{
"zrok": build.String(),
"zrok-service-name": svcName,
},
}
}

84
controller/unaccess.go Normal file
View File

@ -0,0 +1,84 @@
package controller
import (
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/share"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type unaccessHandler struct{}
func newUnaccessHandler() *unaccessHandler {
return &unaccessHandler{}
}
func (h *unaccessHandler) Handle(params share.UnaccessParams, principal *rest_model_zrok.Principal) middleware.Responder {
feToken := params.Body.FrontendToken
shrToken := params.Body.ShrToken
envZId := params.Body.EnvZID
logrus.Infof("processing unaccess request for frontend '%v' (share '%v', environment '%v')", feToken, shrToken, envZId)
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return share.NewUnaccessInternalServerError()
}
defer func() { _ = tx.Rollback() }()
edge, err := edgeClient()
if err != nil {
logrus.Error(err)
return share.NewUnaccessInternalServerError()
}
var senv *store.Environment
if envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx); err == nil {
for _, env := range envs {
if env.ZId == envZId {
senv = env
break
}
}
if senv == nil {
err := errors.Errorf("environment with id '%v' not found for '%v", envZId, principal.Email)
logrus.Error(err)
return share.NewUnaccessUnauthorized()
}
} else {
logrus.Errorf("error finding environments for account '%v': %v", principal.Email, err)
return share.NewUnaccessUnauthorized()
}
sfe, err := str.FindFrontendWithToken(feToken, tx)
if err != nil {
logrus.Error(err)
return share.NewUnaccessInternalServerError()
}
if sfe == nil || (sfe.EnvironmentId != nil && *sfe.EnvironmentId != senv.Id) {
logrus.Errorf("frontend named '%v' not found", feToken)
return share.NewUnaccessInternalServerError()
}
if err := str.DeleteFrontend(sfe.Id, tx); err != nil {
logrus.Errorf("error deleting frontend named '%v': %v", feToken, err)
return share.NewUnaccessNotFound()
}
if err := zrokEdgeSdk.DeleteServicePolicy(envZId, fmt.Sprintf("tags.zrokShareToken=\"%v\" and tags.zrokFrontendToken=\"%v\" and type=1", shrToken, feToken), edge); err != nil {
logrus.Errorf("error removing access to '%v' for '%v': %v", shrToken, envZId, err)
return share.NewUnaccessInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing frontend '%v' delete: %v", feToken, err)
return share.NewUnaccessInternalServerError()
}
return share.NewUnaccessOK()
}

142
controller/unshare.go Normal file
View File

@ -0,0 +1,142 @@
package controller
import (
"context"
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/controller/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/share"
"github.com/openziti/edge/rest_management_api_client"
edge_service "github.com/openziti/edge/rest_management_api_client/service"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"time"
)
type unshareHandler struct{}
func newUnshareHandler() *unshareHandler {
return &unshareHandler{}
}
func (h *unshareHandler) Handle(params share.UnshareParams, principal *rest_model_zrok.Principal) middleware.Responder {
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return share.NewUnshareInternalServerError()
}
defer func() { _ = tx.Rollback() }()
edge, err := edgeClient()
if err != nil {
logrus.Error(err)
return share.NewUnshareInternalServerError()
}
shrToken := params.Body.ShrToken
shrZId, err := h.findShareZId(shrToken, edge)
if err != nil {
logrus.Error(err)
return share.NewUnshareNotFound()
}
var senv *store.Environment
if envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx); err == nil {
for _, env := range envs {
if env.ZId == params.Body.EnvZID {
senv = env
break
}
}
if senv == nil {
err := errors.Errorf("environment with id '%v' not found for '%v", params.Body.EnvZID, principal.Email)
logrus.Error(err)
return share.NewUnshareNotFound()
}
} else {
logrus.Errorf("error finding environments for account '%v': %v", principal.Email, err)
return share.NewUnshareNotFound()
}
var sshr *store.Share
if shrs, err := str.FindSharesForEnvironment(senv.Id, tx); err == nil {
for _, shr := range shrs {
if shr.ZId == shrZId {
sshr = shr
break
}
}
if sshr == nil {
err := errors.Errorf("share with id '%v' not found for '%v'", shrZId, principal.Email)
logrus.Error(err)
return share.NewUnshareNotFound()
}
} else {
logrus.Errorf("error finding shares for account '%v': %v", principal.Email, err)
return share.NewUnshareInternalServerError()
}
if sshr.Reserved == params.Body.Reserved {
// single tag-based share deallocator; should work regardless of sharing mode
if err := h.deallocateResources(senv, shrToken, shrZId, edge); err != nil {
logrus.Errorf("error unsharing ziti resources for '%v': %v", sshr, err)
return share.NewUnshareInternalServerError()
}
logrus.Debugf("deallocated share '%v'", shrToken)
if err := str.DeleteShare(sshr.Id, tx); err != nil {
logrus.Errorf("error deactivating share '%v': %v", shrZId, err)
return share.NewUnshareInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing transaction for '%v': %v", shrZId, err)
return share.NewUnshareInternalServerError()
}
} else {
logrus.Infof("share '%v' is reserved, skipping deallocation", shrToken)
}
return share.NewUnshareOK()
}
func (h *unshareHandler) findShareZId(shrToken string, edge *rest_management_api_client.ZitiEdgeManagement) (string, error) {
filter := fmt.Sprintf("name=\"%v\"", shrToken)
limit := int64(1)
offset := int64(0)
listReq := &edge_service.ListServicesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.Service.ListServices(listReq, nil)
if err != nil {
return "", err
}
if len(listResp.Payload.Data) == 1 {
return *(listResp.Payload.Data[0].ID), nil
}
return "", errors.Errorf("share '%v' not found", shrToken)
}
func (h *unshareHandler) deallocateResources(senv *store.Environment, shrToken, shrZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
if err := zrokEdgeSdk.DeleteServiceEdgeRouterPolicy(senv.ZId, shrToken, edge); err != nil {
return err
}
if err := zrokEdgeSdk.DeleteServicePolicyDial(senv.ZId, shrToken, edge); err != nil {
return err
}
if err := zrokEdgeSdk.DeleteServicePolicyBind(senv.ZId, shrToken, edge); err != nil {
return err
}
if err := zrokEdgeSdk.DeleteConfig(senv.ZId, shrToken, edge); err != nil {
return err
}
if err := zrokEdgeSdk.DeleteService(senv.ZId, shrZId, edge); err != nil {
return err
}
return nil
}

View File

@ -1,133 +0,0 @@
package controller
import (
"context"
"fmt"
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/controller/store"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/tunnel"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/service"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"time"
)
type untunnelHandler struct {
}
func newUntunnelHandler() *untunnelHandler {
return &untunnelHandler{}
}
func (h *untunnelHandler) Handle(params tunnel.UntunnelParams, principal *rest_model_zrok.Principal) middleware.Responder {
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return tunnel.NewUntunnelInternalServerError()
}
defer func() { _ = tx.Rollback() }()
edge, err := edgeClient()
if err != nil {
logrus.Error(err)
return tunnel.NewUntunnelInternalServerError()
}
svcName := params.Body.SvcName
svcZId, err := h.findServiceZId(svcName, edge)
if err != nil {
logrus.Error(err)
return tunnel.NewUntunnelInternalServerError()
}
var senv *store.Environment
if envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx); err == nil {
for _, env := range envs {
if env.ZId == params.Body.ZID {
senv = env
break
}
}
if senv == nil {
err := errors.Errorf("environment with id '%v' not found for '%v", params.Body.ZID, principal.Email)
logrus.Error(err)
return tunnel.NewUntunnelNotFound()
}
} else {
logrus.Errorf("error finding environments for account '%v': %v", principal.Email, err)
return tunnel.NewUntunnelInternalServerError()
}
var ssvc *store.Service
if svcs, err := str.FindServicesForEnvironment(senv.Id, tx); err == nil {
for _, svc := range svcs {
if svc.ZId == svcZId {
ssvc = svc
break
}
}
if ssvc == nil {
err := errors.Errorf("service with id '%v' not found for '%v'", svcZId, principal.Email)
logrus.Error(err)
return tunnel.NewUntunnelNotFound()
}
} else {
logrus.Errorf("error finding services for account '%v': %v", principal.Email, err)
return tunnel.NewUntunnelInternalServerError()
}
if err := deleteServiceEdgeRouterPolicy(senv.ZId, svcName, edge); err != nil {
logrus.Error(err)
return tunnel.NewUntunnelInternalServerError()
}
if err := deleteServicePolicyDial(senv.ZId, svcName, edge); err != nil {
logrus.Error(err)
return tunnel.NewUntunnelInternalServerError()
}
if err := deleteServicePolicyBind(senv.ZId, svcName, edge); err != nil {
logrus.Error(err)
return tunnel.NewUntunnelInternalServerError()
}
if err := deleteConfig(senv.ZId, svcName, edge); err != nil {
logrus.Error(err)
return tunnel.NewTunnelInternalServerError()
}
if err := deleteService(senv.ZId, svcZId, edge); err != nil {
logrus.Error(err)
return tunnel.NewUntunnelInternalServerError()
}
logrus.Debugf("deallocated service '%v'", svcName)
if err := str.DeleteService(ssvc.Id, tx); err != nil {
logrus.Errorf("error deactivating service '%v': %v", svcZId, err)
return tunnel.NewUntunnelInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing: %v", err)
return tunnel.NewUntunnelInternalServerError()
}
return tunnel.NewUntunnelOK()
}
func (h *untunnelHandler) findServiceZId(svcName string, edge *rest_management_api_client.ZitiEdgeManagement) (string, error) {
filter := fmt.Sprintf("name=\"%v\"", svcName)
limit := int64(1)
offset := int64(0)
listReq := &service.ListServicesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.Service.ListServices(listReq, nil)
if err != nil {
return "", err
}
if len(listResp.Payload.Data) == 1 {
return *(listResp.Payload.Data[0].ID), nil
}
return "", errors.Errorf("service '%v' not found", svcName)
}

View File

@ -0,0 +1,66 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/admin"
"github.com/sirupsen/logrus"
)
type updateFrontendHandler struct{}
func newUpdateFrontendHandler() *updateFrontendHandler {
return &updateFrontendHandler{}
}
func (h *updateFrontendHandler) Handle(params admin.UpdateFrontendParams, principal *rest_model_zrok.Principal) middleware.Responder {
feToken := params.Body.FrontendToken
publicName := params.Body.PublicName
urlTemplate := params.Body.URLTemplate
if !principal.Admin {
logrus.Errorf("invalid admin principal")
return admin.NewUpdateFrontendUnauthorized()
}
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return admin.NewUpdateFrontendInternalServerError()
}
defer func() { _ = tx.Rollback() }()
fe, err := str.FindFrontendWithToken(feToken, tx)
if err != nil {
logrus.Errorf("error finding frontend with token '%v': %v", feToken, err)
return admin.NewUpdateFrontendNotFound()
}
doUpdate := false
if publicName != "" {
if fe.PublicName == nil || (fe.PublicName != nil && *fe.PublicName != publicName) {
fe.PublicName = &publicName
doUpdate = true
}
}
if urlTemplate != "" {
if fe.UrlTemplate == nil || (fe.UrlTemplate != nil && *fe.UrlTemplate != urlTemplate) {
fe.UrlTemplate = &urlTemplate
doUpdate = true
}
}
if doUpdate {
if err := str.UpdateFrontend(fe, tx); err != nil {
logrus.Errorf("error updating frontend: %v", err)
return admin.NewUpdateFrontendInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error commiting frontend update: %v", err)
return admin.NewUpdateFrontendInternalServerError()
}
}
return admin.NewUpdateFrontendOK()
}

63
controller/updateShare.go Normal file
View File

@ -0,0 +1,63 @@
package controller
import (
"github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/share"
"github.com/sirupsen/logrus"
)
type updateShareHandler struct{}
func newUpdateShareHandler() *updateShareHandler {
return &updateShareHandler{}
}
func (h *updateShareHandler) Handle(params share.UpdateShareParams, principal *rest_model_zrok.Principal) middleware.Responder {
shrToken := params.Body.ShrToken
backendProxyEndpoint := params.Body.BackendProxyEndpoint
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return share.NewUpdateShareInternalServerError()
}
defer func() { _ = tx.Rollback() }()
sshr, err := str.FindShareWithToken(shrToken, tx)
if err != nil {
logrus.Errorf("share '%v' not found: %v", shrToken, err)
return share.NewUpdateShareNotFound()
}
senvs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
if err != nil {
logrus.Errorf("error finding environments for account '%v': %v", principal.Email, err)
return share.NewUpdateShareInternalServerError()
}
envFound := false
for _, senv := range senvs {
if senv.Id == sshr.Id {
envFound = true
break
}
}
if !envFound {
logrus.Errorf("environment not found for share '%v'", shrToken)
return share.NewUpdateShareNotFound()
}
sshr.BackendProxyEndpoint = &backendProxyEndpoint
if err := str.UpdateShare(sshr, tx); err != nil {
logrus.Errorf("error updating share '%v': %v", shrToken, err)
return share.NewUpdateShareInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Errorf("error committing transaction for share '%v' update: %v", shrToken, err)
return share.NewUpdateShareInternalServerError()
}
return share.NewUpdateShareOK()
}

View File

@ -13,20 +13,44 @@ import (
"strings" "strings"
) )
func ZrokAuthenticate(token string) (*rest_model_zrok.Principal, error) { type zrokAuthenticator struct {
cfg *Config
}
func newZrokAuthenticator(cfg *Config) *zrokAuthenticator {
return &zrokAuthenticator{cfg}
}
func (za *zrokAuthenticator) authenticate(token string) (*rest_model_zrok.Principal, error) {
tx, err := str.Begin() tx, err := str.Begin()
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
if a, err := str.FindAccountWithToken(token, tx); err == nil { if a, err := str.FindAccountWithToken(token, tx); err == nil {
principal := rest_model_zrok.Principal{ principal := &rest_model_zrok.Principal{
ID: int64(a.Id), ID: int64(a.Id),
Token: a.Token, Token: a.Token,
Email: a.Email, Email: a.Email,
Limitless: a.Limitless,
} }
return &principal, nil return principal, nil
} else { } else {
// check for admin secret
if cfg.Admin != nil {
for _, secret := range cfg.Admin.Secrets {
if token == secret {
principal := &rest_model_zrok.Principal{
ID: int64(-1),
Admin: true,
}
return principal, nil
}
}
}
// no match
return nil, errors2.New(401, "invalid api key") return nil, errors2.New(401, "invalid api key")
} }
} }
@ -43,7 +67,7 @@ func edgeClient() (*rest_management_api_client.ZitiEdgeManagement, error) {
return rest_util.NewEdgeManagementClientWithUpdb(cfg.Ziti.Username, cfg.Ziti.Password, cfg.Ziti.ApiEndpoint, caPool) return rest_util.NewEdgeManagementClientWithUpdb(cfg.Ziti.Username, cfg.Ziti.Password, cfg.Ziti.ApiEndpoint, caPool)
} }
func createServiceName() (string, error) { func createShareToken() (string, error) {
gen, err := nanoid.CustomASCII("abcdefghijklmnopqrstuvwxyz0123456789", 12) gen, err := nanoid.CustomASCII("abcdefghijklmnopqrstuvwxyz0123456789", 12)
if err != nil { if err != nil {
return "", err return "", err
@ -78,3 +102,7 @@ func realRemoteAddress(req *http.Request) string {
} }
return ip return ip
} }
func proxyUrl(shrToken, template string) string {
return strings.Replace(template, "{token}", shrToken, -1)
}

View File

@ -3,7 +3,7 @@ package controller
import ( import (
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok" "github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity" "github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -14,25 +14,24 @@ func newVerifyHandler() *verifyHandler {
return &verifyHandler{} return &verifyHandler{}
} }
func (self *verifyHandler) Handle(params identity.VerifyParams) middleware.Responder { func (self *verifyHandler) Handle(params account.VerifyParams) middleware.Responder {
if params.Body != nil { if params.Body != nil {
logrus.Debugf("received verify request for token '%v'", params.Body.Token) logrus.Debugf("received verify request for token '%v'", params.Body.Token)
tx, err := str.Begin() tx, err := str.Begin()
if err != nil { if err != nil {
logrus.Errorf("error starting transaction: %v", err) logrus.Errorf("error starting transaction: %v", err)
return identity.NewVerifyInternalServerError() return account.NewVerifyInternalServerError()
} }
defer func() { _ = tx.Rollback() }() defer func() { _ = tx.Rollback() }()
ar, err := str.FindAccountRequestWithToken(params.Body.Token, tx) ar, err := str.FindAccountRequestWithToken(params.Body.Token, tx)
if err != nil { if err != nil {
logrus.Errorf("error finding account with token '%v': %v", params.Body.Token, err) logrus.Errorf("error finding account with token '%v': %v", params.Body.Token, err)
return identity.NewVerifyNotFound() return account.NewVerifyNotFound()
} }
return identity.NewVerifyOK().WithPayload(&rest_model_zrok.VerifyResponse{Email: ar.Email}) return account.NewVerifyOK().WithPayload(&rest_model_zrok.VerifyResponse{Email: ar.Email})
} else {
logrus.Error("empty verification request")
return identity.NewVerifyInternalServerError().WithPayload(rest_model_zrok.ErrorMessage("empty verification request"))
} }
logrus.Error("empty verification request")
return account.NewVerifyInternalServerError()
} }

View File

@ -2,7 +2,7 @@ package controller
import ( import (
"bytes" "bytes"
"github.com/openziti-test-kitchen/zrok/controller/email_ui" "github.com/openziti-test-kitchen/zrok/controller/emailUi"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/wneessen/go-mail" "github.com/wneessen/go-mail"
@ -63,7 +63,7 @@ func sendVerificationEmail(emailAddress, token string) error {
} }
func mergeTemplate(emailData *verificationEmail, filename string) (string, error) { func mergeTemplate(emailData *verificationEmail, filename string) (string, error) {
t, err := template.ParseFS(email_ui.FS, filename) t, err := template.ParseFS(emailUi.FS, filename)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "error parsing verification email template '%v'", filename) return "", errors.Wrapf(err, "error parsing verification email template '%v'", filename)
} }

View File

@ -0,0 +1,75 @@
package zrokEdgeSdk
import (
"context"
"fmt"
"github.com/openziti-test-kitchen/zrok/model"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/config"
"github.com/openziti/edge/rest_model"
"github.com/sirupsen/logrus"
"time"
)
func CreateConfig(cfgTypeZId, envZId, shrToken string, authSchemeStr string, authUsers []*model.AuthUser, edge *rest_management_api_client.ZitiEdgeManagement) (cfgZId string, err error) {
authScheme, err := model.ParseAuthScheme(authSchemeStr)
if err != nil {
return "", err
}
cfg := &model.ProxyConfig{
AuthScheme: authScheme,
}
if cfg.AuthScheme == model.Basic {
cfg.BasicAuth = &model.BasicAuth{}
for _, authUser := range authUsers {
cfg.BasicAuth.Users = append(cfg.BasicAuth.Users, &model.AuthUser{Username: authUser.Username, Password: authUser.Password})
}
}
cfgCrt := &rest_model.ConfigCreate{
ConfigTypeID: &cfgTypeZId,
Data: cfg,
Name: &shrToken,
Tags: ZrokShareTags(shrToken),
}
cfgReq := &config.CreateConfigParams{
Config: cfgCrt,
Context: context.Background(),
}
cfgReq.SetTimeout(30 * time.Second)
cfgResp, err := edge.Config.CreateConfig(cfgReq, nil)
if err != nil {
return "", err
}
logrus.Infof("created config '%v' for environment '%v'", cfgResp.Payload.Data.ID, envZId)
return cfgResp.Payload.Data.ID, nil
}
func DeleteConfig(envZId, shrToken string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("tags.zrokShareToken=\"%v\"", shrToken)
limit := int64(0)
offset := int64(0)
listReq := &config.ListConfigsParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.Config.ListConfigs(listReq, nil)
if err != nil {
return err
}
for _, cfg := range listResp.Payload.Data {
deleteReq := &config.DeleteConfigParams{
ID: *cfg.ID,
Context: context.Background(),
}
deleteReq.SetTimeout(30 * time.Second)
_, err := edge.Config.DeleteConfig(deleteReq, nil)
if err != nil {
return err
}
logrus.Infof("deleted config '%v' for '%v'", *cfg.ID, envZId)
}
return nil
}

View File

@ -0,0 +1,67 @@
package zrokEdgeSdk
import (
"context"
"fmt"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/edge_router_policy"
rest_model_edge "github.com/openziti/edge/rest_model"
"github.com/sirupsen/logrus"
"time"
)
func CreateEdgeRouterPolicy(name, zId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
edgeRouterRoles := []string{"#all"}
identityRoles := []string{fmt.Sprintf("@%v", zId)}
semantic := rest_model_edge.SemanticAllOf
erp := &rest_model_edge.EdgeRouterPolicyCreate{
EdgeRouterRoles: edgeRouterRoles,
IdentityRoles: identityRoles,
Name: &name,
Semantic: &semantic,
Tags: ZrokTags(),
}
req := &edge_router_policy.CreateEdgeRouterPolicyParams{
Policy: erp,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
resp, err := edge.EdgeRouterPolicy.CreateEdgeRouterPolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("created edge router policy '%v' for ziti identity '%v'", resp.Payload.Data.ID, zId)
return nil
}
func DeleteEdgeRouterPolicy(envZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("name=\"%v\"", envZId)
limit := int64(0)
offset := int64(0)
listReq := &edge_router_policy.ListEdgeRouterPoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.EdgeRouterPolicy.ListEdgeRouterPolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) == 1 {
erpId := *(listResp.Payload.Data[0].ID)
req := &edge_router_policy.DeleteEdgeRouterPolicyParams{
ID: erpId,
Context: context.Background(),
}
_, err := edge.EdgeRouterPolicy.DeleteEdgeRouterPolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted edge router policy '%v' for environment '%v'", erpId, envZId)
} else {
logrus.Infof("found '%d' edge router policies, expected 1", len(listResp.Payload.Data))
}
return nil
}

View File

@ -0,0 +1,101 @@
package zrokEdgeSdk
import (
"context"
"fmt"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/identity"
rest_model_edge "github.com/openziti/edge/rest_model"
"github.com/openziti/sdk-golang/ziti/config"
"github.com/openziti/sdk-golang/ziti/enroll"
"github.com/sirupsen/logrus"
"time"
)
func CreateEnvironmentIdentity(uniqueToken, accountEmail, envDescription string, edge *rest_management_api_client.ZitiEdgeManagement) (*identity.CreateIdentityCreated, error) {
identityType := rest_model_edge.IdentityTypeUser
moreTags := map[string]interface{}{"zrokEmail": accountEmail}
return CreateIdentity(accountEmail+"-"+uniqueToken+"-"+envDescription, identityType, moreTags, edge)
}
func CreateIdentity(name string, identityType rest_model_edge.IdentityType, addlTags map[string]interface{}, edge *rest_management_api_client.ZitiEdgeManagement) (*identity.CreateIdentityCreated, error) {
isAdmin := false
tags := ZrokTags()
for k, v := range addlTags {
tags.SubTags[k] = v
}
req := identity.NewCreateIdentityParams()
req.Identity = &rest_model_edge.IdentityCreate{
Enrollment: &rest_model_edge.IdentityCreateEnrollment{Ott: true},
IsAdmin: &isAdmin,
Name: &name,
RoleAttributes: nil,
ServiceHostingCosts: nil,
Tags: tags,
Type: &identityType,
}
req.SetTimeout(30 * time.Second)
resp, err := edge.Identity.CreateIdentity(req, nil)
if err != nil {
return nil, err
}
return resp, nil
}
func GetIdentityByZId(zId string, edge *rest_management_api_client.ZitiEdgeManagement) (*identity.ListIdentitiesOK, error) {
filter := fmt.Sprintf("id=\"%v\"", zId)
limit := int64(0)
offset := int64(0)
req := &identity.ListIdentitiesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
resp, err := edge.Identity.ListIdentities(req, nil)
if err != nil {
return nil, err
}
return resp, nil
}
func EnrollIdentity(zId string, edge *rest_management_api_client.ZitiEdgeManagement) (*config.Config, error) {
p := &identity.DetailIdentityParams{
Context: context.Background(),
ID: zId,
}
p.SetTimeout(30 * time.Second)
resp, err := edge.Identity.DetailIdentity(p, nil)
if err != nil {
return nil, err
}
tkn, _, err := enroll.ParseToken(resp.GetPayload().Data.Enrollment.Ott.JWT)
if err != nil {
return nil, err
}
flags := enroll.EnrollmentFlags{
Token: tkn,
KeyAlg: "RSA",
}
conf, err := enroll.Enroll(flags)
if err != nil {
return nil, err
}
logrus.Infof("enrolled ziti identity '%v'", zId)
return conf, nil
}
func DeleteIdentity(zId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
req := &identity.DeleteIdentityParams{
ID: zId,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
_, err := edge.Identity.DeleteIdentity(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted ziti identity '%v'", zId)
return nil
}

View File

@ -0,0 +1,81 @@
package zrokEdgeSdk
import (
"context"
"fmt"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/service_edge_router_policy"
"github.com/openziti/edge/rest_model"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"time"
)
func CreateShareServiceEdgeRouterPolicy(envZId, shrToken, shrZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
serpZId, err := CreateServiceEdgeRouterPolicy(shrToken, shrZId, ZrokShareTags(shrToken).SubTags, edge)
if err != nil {
return err
}
logrus.Infof("created service edge router policy '%v' for service '%v' for environment '%v'", serpZId, shrZId, envZId)
return nil
}
func CreateServiceEdgeRouterPolicy(name, shrZId string, moreTags map[string]interface{}, edge *rest_management_api_client.ZitiEdgeManagement) (string, error) {
edgeRouterRoles := []string{"#all"}
semantic := rest_model.SemanticAllOf
serviceRoles := []string{fmt.Sprintf("@%v", shrZId)}
tags := ZrokTags()
for k, v := range moreTags {
tags.SubTags[k] = v
}
serp := &rest_model.ServiceEdgeRouterPolicyCreate{
EdgeRouterRoles: edgeRouterRoles,
Name: &name,
Semantic: &semantic,
ServiceRoles: serviceRoles,
Tags: tags,
}
serpParams := &service_edge_router_policy.CreateServiceEdgeRouterPolicyParams{
Policy: serp,
Context: context.Background(),
}
serpParams.SetTimeout(30 * time.Second)
resp, err := edge.ServiceEdgeRouterPolicy.CreateServiceEdgeRouterPolicy(serpParams, nil)
if err != nil {
return "", errors.Wrapf(err, "error creating serp '%v' for service '%v'", name, shrZId)
}
return resp.Payload.Data.ID, nil
}
func DeleteServiceEdgeRouterPolicy(envZId, shrToken string, edge *rest_management_api_client.ZitiEdgeManagement) error {
filter := fmt.Sprintf("tags.zrokShareToken=\"%v\"", shrToken)
limit := int64(1)
offset := int64(0)
listReq := &service_edge_router_policy.ListServiceEdgeRouterPoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.ServiceEdgeRouterPolicy.ListServiceEdgeRouterPolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) == 1 {
serpId := *(listResp.Payload.Data[0].ID)
req := &service_edge_router_policy.DeleteServiceEdgeRouterPolicyParams{
ID: serpId,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
_, err := edge.ServiceEdgeRouterPolicy.DeleteServiceEdgeRouterPolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted service edge router policy '%v' for environment '%v'", serpId, envZId)
} else {
logrus.Infof("did not find a service edge router policy")
}
return nil
}

View File

@ -0,0 +1,56 @@
package zrokEdgeSdk
import (
"context"
"github.com/openziti/edge/rest_management_api_client"
edge_service "github.com/openziti/edge/rest_management_api_client/service"
"github.com/openziti/edge/rest_model"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"time"
)
func CreateShareService(envZId, shrToken, cfgZId string, edge *rest_management_api_client.ZitiEdgeManagement) (shrZId string, err error) {
shrZId, err = CreateService(shrToken, []string{cfgZId}, map[string]interface{}{"zrokShareToken": shrToken}, edge)
if err != nil {
return "", errors.Wrapf(err, "error creating share '%v'", shrToken)
}
logrus.Infof("created share '%v' (with ziti id '%v') for environment '%v'", shrToken, shrZId, envZId)
return shrZId, nil
}
func CreateService(name string, cfgZIds []string, addlTags map[string]interface{}, edge *rest_management_api_client.ZitiEdgeManagement) (shrZId string, err error) {
encryptionRequired := true
svc := &rest_model.ServiceCreate{
EncryptionRequired: &encryptionRequired,
Name: &name,
Tags: MergeTags(ZrokTags(), addlTags),
}
if cfgZIds != nil {
svc.Configs = cfgZIds
}
req := &edge_service.CreateServiceParams{
Service: svc,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
resp, err := edge.Service.CreateService(req, nil)
if err != nil {
return "", err
}
return resp.Payload.Data.ID, nil
}
func DeleteService(envZId, shrZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
req := &edge_service.DeleteServiceParams{
ID: shrZId,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
_, err := edge.Service.DeleteService(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted service '%v' for environment '%v'", shrZId, envZId)
return nil
}

View File

@ -0,0 +1,119 @@
package zrokEdgeSdk
import (
"context"
"fmt"
"github.com/openziti/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/service_policy"
"github.com/openziti/edge/rest_model"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"time"
)
const (
servicePolicyDial = 1
servicePolicyBind = 2
)
func CreateServicePolicyBind(name, shrZId, bindZId string, addlTags map[string]interface{}, edge *rest_management_api_client.ZitiEdgeManagement) error {
semantic := rest_model.SemanticAllOf
identityRoles := []string{"@" + bindZId}
serviceRoles := []string{"@" + shrZId}
spZId, err := createServicePolicy(name, semantic, identityRoles, serviceRoles, addlTags, servicePolicyBind, edge)
if err != nil {
return errors.Wrapf(err, "error creating bind service policy for service '%v' for identity '%v'", shrZId, bindZId)
}
logrus.Infof("created bind service policy '%v' for service '%v' for identity '%v'", spZId, shrZId, bindZId)
return nil
}
func CreateServicePolicyDial(name, shrZId string, dialZIds []string, addlTags map[string]interface{}, edge *rest_management_api_client.ZitiEdgeManagement) error {
semantic := rest_model.SemanticAllOf
var identityRoles []string
for _, zId := range dialZIds {
identityRoles = append(identityRoles, "@"+zId)
}
serviceRoles := []string{"@" + shrZId}
spZId, err := createServicePolicy(name, semantic, identityRoles, serviceRoles, addlTags, servicePolicyDial, edge)
if err != nil {
return errors.Wrapf(err, "error creating dial service policy for service '%v' for identities '%v'", shrZId, dialZIds)
}
logrus.Infof("created dial service policy '%v' for service '%v' for identities '%v'", spZId, shrZId, dialZIds)
return nil
}
func createServicePolicy(name string, semantic rest_model.Semantic, identityRoles, serviceRoles []string, addlTags map[string]interface{}, dialBind int, edge *rest_management_api_client.ZitiEdgeManagement) (spZId string, err error) {
var dialBindType rest_model.DialBind
switch dialBind {
case servicePolicyBind:
dialBindType = rest_model.DialBindBind
case servicePolicyDial:
dialBindType = rest_model.DialBindDial
default:
return "", errors.Errorf("invalid dial bind type")
}
spc := &rest_model.ServicePolicyCreate{
IdentityRoles: identityRoles,
Name: &name,
PostureCheckRoles: make([]string, 0),
Semantic: &semantic,
ServiceRoles: serviceRoles,
Tags: MergeTags(ZrokTags(), addlTags),
Type: &dialBindType,
}
req := &service_policy.CreateServicePolicyParams{
Policy: spc,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
resp, err := edge.ServicePolicy.CreateServicePolicy(req, nil)
if err != nil {
return "", errors.Wrap(err, "error creating service policy")
}
return resp.Payload.Data.ID, nil
}
func DeleteServicePolicyBind(envZId, shrToken string, edge *rest_management_api_client.ZitiEdgeManagement) error {
return DeleteServicePolicy(envZId, fmt.Sprintf("tags.zrokShareToken=\"%v\" and type=%d", shrToken, servicePolicyBind), edge)
}
func DeleteServicePolicyDial(envZId, shrToken string, edge *rest_management_api_client.ZitiEdgeManagement) error {
return DeleteServicePolicy(envZId, fmt.Sprintf("tags.zrokShareToken=\"%v\" and type=%d", shrToken, servicePolicyDial), edge)
}
func DeleteServicePolicy(envZId, filter string, edge *rest_management_api_client.ZitiEdgeManagement) error {
limit := int64(1)
offset := int64(0)
listReq := &service_policy.ListServicePoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listReq.SetTimeout(30 * time.Second)
listResp, err := edge.ServicePolicy.ListServicePolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) == 1 {
spId := *(listResp.Payload.Data[0].ID)
req := &service_policy.DeleteServicePolicyParams{
ID: spId,
Context: context.Background(),
}
req.SetTimeout(30 * time.Second)
_, err := edge.ServicePolicy.DeleteServicePolicy(req, nil)
if err != nil {
return err
}
logrus.Infof("deleted service policy '%v' for environment '%v'", spId, envZId)
} else {
logrus.Infof("did not find a service policy")
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More