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
* 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"
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 Hash string
const Series = "v0.3"
func String() string {
if Version != "" {
return fmt.Sprintf("%v [%v]", Version, Hash)
} 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() {
rootCmd.AddCommand(newGcCmd().cmd)
adminCmd.AddCommand(newAdminGcCommand().cmd)
}
type gcCmd struct {
type adminGcCommand struct {
cmd *cobra.Command
}
func newGcCmd() *gcCmd {
func newAdminGcCommand() *adminGcCommand {
cmd := &cobra.Command{
Use: "gc <configPath>",
Short: "Garbage collect a zrok instance",
Args: cobra.ExactArgs(1),
}
c := &gcCmd{cmd: cmd}
cmd.Run = c.run
return c
command := &adminGcCommand{cmd: cmd}
cmd.Run = command.run
return command
}
func (gc *gcCmd) run(_ *cobra.Command, args []string) {
func (gc *adminGcCommand) run(_ *cobra.Command, args []string) {
cfg, err := controller.LoadConfig(args[0])
if err != nil {
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 (
"fmt"
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/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@ -29,40 +30,45 @@ func newDisableCommand() *disableCommand {
return command
}
func (cmd *disableCommand) run(_ *cobra.Command, args []string) {
env, err := zrokdir.LoadEnvironment()
func (cmd *disableCommand) run(_ *cobra.Command, _ []string) {
zrd, err := zrokdir.Load()
if err != nil {
if !panicInstead {
showError("could not load environment; not active?", err)
tui.Error("unable to load zrokdir", 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 !panicInstead {
showError("could not create zrok service client", err)
tui.Error("could not create zrok client", err)
}
panic(err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", env.Token)
req := identity.NewDisableParams()
auth := httptransport.APIKeyAuth("X-TOKEN", "header", zrd.Env.Token)
req := environment.NewDisableParams()
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 {
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 !panicInstead {
showError("error removing zrok environment", err)
tui.Error("error removing zrok environment", err)
}
panic(err)
}
if err := zrokdir.DeleteZitiIdentity("backend"); err != nil {
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 (
"fmt"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
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/tui"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/shirou/gopsutil/v3/host"
"github.com/spf13/cobra"
"os"
user2 "os/user"
"strings"
"time"
)
func init() {
@ -36,11 +38,10 @@ func newEnableCommand() *enableCommand {
}
func (cmd *enableCommand) run(_ *cobra.Command, args []string) {
env, err := zrokdir.LoadEnvironment()
if err == nil {
showError(fmt.Sprintf("you already have an environment '%v' for '%v'", env.ZId, env.Token), nil)
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
token := args[0]
hostName, hostDetail, err := getHost()
@ -55,38 +56,70 @@ func (cmd *enableCommand) run(_ *cobra.Command, args []string) {
if cmd.description == "<user>@<hostname>" {
cmd.description = fmt.Sprintf("%v@%v", user.Username, hostName)
}
zrok, err := zrokdir.ZrokClient(apiEndpoint)
zrok, err := zrd.Client()
if err != nil {
panic(err)
cmd.endpointError(zrd.ApiEndpoint())
tui.Error("error creating service client", err)
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", token)
req := identity.NewEnableParams()
req := environment.NewEnableParams()
req.Body = &rest_model_zrok.EnableRequest{
Description: cmd.description,
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 !panicInstead {
showError("the zrok service returned an error", err)
time.Sleep(250 * time.Millisecond)
prg.Send(fmt.Sprintf("the zrok service returned an error: %v\n", err))
prg.Quit()
<-done
cmd.endpointError(zrd.ApiEndpoint())
os.Exit(1)
}
panic(err)
}
if err := zrokdir.SaveEnvironment(&zrokdir.Environment{Token: token, ZId: resp.Payload.Identity, ApiEndpoint: apiEndpoint}); err != nil {
if !panicInstead {
showError("there was an error saving the new environment", err)
}
panic(err)
prg.Send("writing the environment details...")
apiEndpoint, _ := zrd.ApiEndpoint()
zrd.Env = &zrokdir.Environment{Token: token, ZId: resp.Payload.Identity, ApiEndpoint: apiEndpoint}
if err := zrd.Save(); err != nil {
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 !panicInstead {
showError("there was an error writing the environment file", err)
}
panic(err)
prg.Send(fmt.Sprintf("there was an error writing the environment: %v", err))
prg.Quit()
<-done
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) {
@ -99,14 +132,51 @@ func getHost() (string, string, error) {
return info.Hostname, thisHost, nil
}
var errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#D90166")).Background(lipgloss.Color("0"))
func showError(msg string, err error) {
errorLabel := errorStyle.Render("ERROR:")
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v %v (%v)\n", errorLabel, msg, strings.TrimSpace(err.Error()))
} else {
_, _ = fmt.Fprintf(os.Stderr, "%v %v\n", errorLabel, msg)
}
os.Exit(1)
type enableTuiModel struct {
spinner spinner.Model
msg string
quitting bool
}
func newEnableTuiModel() enableTuiModel {
s := spinner.New()
s.Spinner = spinner.Dot
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"

View File

@ -388,6 +388,8 @@
</div>
<div id="container">
<div id="info">
<h1>{{ .RequestedPath }}</h1>
<h2>{{ .Now }}</h2>
<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 (
"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/tui"
"github.com/openziti-test-kitchen/zrok/util"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/openziti/foundation/v2/term"
"github.com/spf13/cobra"
)
@ -15,6 +22,8 @@ func init() {
type inviteCommand struct {
cmd *cobra.Command
token string
tui inviteTui
}
func newInviteCommand() *inviteCommand {
@ -23,42 +32,200 @@ func newInviteCommand() *inviteCommand {
Short: "Invite a new user to zrok",
Args: cobra.ExactArgs(0),
}
command := &inviteCommand{cmd: cmd}
command := &inviteCommand{
cmd: cmd,
tui: newInviteTui(),
}
cmd.Run = command.run
cmd.Flags().StringVar(&command.token, "token", "", "Invite token required when zrok running in token store mode")
return command
}
func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) {
email, err := term.Prompt("New Email: ")
zrd, err := zrokdir.Load()
if err != nil {
panic(err)
}
confirm, err := term.Prompt("Confirm Email: ")
if err != nil {
panic(err)
}
if confirm != email {
showError("entered emails do not match... aborting!", nil)
tui.Error("error loading zrokdir", err)
}
zrok, err := zrokdir.ZrokClient(apiEndpoint)
zrok, err := zrd.Client()
if err != nil {
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{
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.Identity.CreateAccount(req)
_, err = zrok.Account.Invite(req)
if err != nil {
if !panicInstead {
showError("error creating account", err)
}
panic(err)
cmd.endpointError(zrd.ApiEndpoint())
tui.Error("error creating invitation", err)
}
fmt.Printf("registration invitation sent to '%v'!\n", email)
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"
"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_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/util"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/openziti/sdk-golang/ziti"
@ -20,6 +21,9 @@ import (
"io"
"math/rand"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
@ -39,6 +43,7 @@ type loopCmd struct {
maxDwellMs int
minPacingMs int
maxPacingMs int
frontendSelection []string
}
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.minPacingMs, "min-pacing-ms", 0, "Minimum 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
}
@ -69,21 +75,32 @@ func (r *loopCmd) run(_ *cobra.Command, _ []string) {
loopers = append(loopers, l)
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 {
<-l.done
}
totalMismatches := 0
totalXfer := int64(0)
totalLoops := int64(0)
for _, l := range loopers {
deltaSeconds := l.stopTime.Sub(l.startTime).Seconds()
xfer := int64(float64(l.bytes) / deltaSeconds)
totalXfer += xfer
totalMismatches += l.mismatches
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)
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 {
@ -94,13 +111,15 @@ type looper struct {
listener edge.Listener
zif string
zrok *rest_client_zrok.Zrok
service string
shrToken string
proxyEndpoint string
auth runtime.ClientAuthInfoWriter
mismatches int
bytes int64
loops int64
startTime time.Time
stopTime time.Time
stop bool
}
func newLooper(id int, cmd *loopCmd) *looper {
@ -116,7 +135,7 @@ func (l *looper) run() {
defer logrus.Infof("stopping #%d", l.id)
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()
l.dwell()
l.iterate()
@ -134,7 +153,7 @@ func (l *looper) serviceListener() {
ConnectTimeout: 5 * time.Minute,
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 {
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() {
logrus.Infof("starting #%d", l.id)
var err error
l.env, err = zrokdir.LoadEnvironment()
zrd, err := zrokdir.Load()
if err != nil {
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")
if err != nil {
panic(err)
}
l.zrok, err = zrokdir.ZrokClient(l.env.ApiEndpoint)
l.zrok, err = zrd.Client()
if err != nil {
panic(err)
}
l.auth = httptransport.APIKeyAuth("x-token", "header", l.env.Token)
tunnelReq := tunnel.NewTunnelParams()
tunnelReq.Body = &rest_model_zrok.TunnelRequest{
ZID: l.env.ZId,
Endpoint: fmt.Sprintf("looper#%d", l.id),
tunnelReq := share.NewShareParams()
tunnelReq.Body = &rest_model_zrok.ShareRequest{
EnvZID: l.env.ZId,
ShareMode: "public",
FrontendSelection: l.cmd.frontendSelection,
BackendMode: "proxy",
BackendProxyEndpoint: fmt.Sprintf("looper#%d", l.id),
AuthScheme: string(model.None),
}
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 {
panic(err)
}
l.service = tunnelResp.Payload.SvcName
l.proxyEndpoint = tunnelResp.Payload.ProxyEndpoint
l.shrToken = tunnelResp.Payload.ShrToken
l.proxyEndpoint = tunnelResp.Payload.FrontendProxyEndpoints[0]
}
func (l *looper) dwell() {
@ -193,7 +220,7 @@ func (l *looper) iterate() {
l.startTime = 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 {
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
time.Sleep(time.Duration(pacingMs) * time.Millisecond)
}
l.loops++
}
}
@ -238,12 +266,12 @@ func (l *looper) shutdown() {
}
}
untunnelReq := tunnel.NewUntunnelParams()
untunnelReq.Body = &rest_model_zrok.UntunnelRequest{
ZID: l.env.ZId,
SvcName: l.service,
untunnelReq := share.NewUnshareParams()
untunnelReq.Body = &rest_model_zrok.UnshareRequest{
EnvZID: l.env.ZId,
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)
}
}

View File

@ -2,7 +2,6 @@ package main
import (
"github.com/michaelquigley/pfxlog"
"github.com/openziti-test-kitchen/zrok/zrokdir"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"os"
@ -14,8 +13,15 @@ func init() {
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(&panicInstead, "panic", "p", false, "Panic instead of showing pretty errors")
zrokdir.AddZrokApiEndpointFlag(&apiEndpoint, rootCmd.PersistentFlags())
rootCmd.AddCommand(httpCmd)
rootCmd.AddCommand(accessCmd)
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{
@ -29,11 +35,50 @@ var rootCmd = &cobra.Command{
}
var verbose bool
var panicInstead bool
var apiEndpoint string
var httpCmd = &cobra.Command{
Use: "http",
Short: "HTTP endpoint operations",
var accessCmd = &cobra.Command{
Use: "access",
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() {

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 (
"fmt"
"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/spf13/cobra"
"html/template"
@ -14,12 +15,6 @@ import (
func init() {
testCmd.AddCommand(newTestEndpointCommand().cmd)
rootCmd.AddCommand(testCmd)
}
var testCmd = &cobra.Command{
Use: "test",
Short: "Utilities used for testing zrok",
}
type testEndpointCommand struct {
@ -37,13 +32,13 @@ func newTestEndpointCommand() *testEndpointCommand {
}
command := &testEndpointCommand{cmd: cmd}
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 {
showError("unable to parse index template", err)
tui.Error("unable to parse index template", 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.Run = command.run
return command
@ -53,20 +48,21 @@ func (cmd *testEndpointCommand) run(_ *cobra.Command, _ []string) {
http.HandleFunc("/", cmd.serveIndex)
if err := http.ListenAndServe(fmt.Sprintf("%v:%d", cmd.address, cmd.port), nil); err != nil {
if !panicInstead {
showError("unable to start http listener", err)
tui.Error("unable to start http listener", err)
}
panic(err)
}
}
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 {
log.Error(err)
}
}
type endpointData struct {
RequestedPath string
Now time.Time
RemoteAddr string
Host string
@ -78,6 +74,7 @@ type endpointData struct {
func newEndpointData(r *http.Request) *endpointData {
ed := &endpointData{
RequestedPath: r.RequestURI,
Now: time.Now(),
HostHeader: r.Host,
Headers: r.Header,

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 (
"fmt"
"github.com/charmbracelet/lipgloss"
"github.com/openziti-test-kitchen/zrok/build"
"github.com/openziti-test-kitchen/zrok/tui"
"github.com/spf13/cobra"
)
@ -25,5 +27,6 @@ func newVersionCommand() *versionCommand {
}
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/openziti-test-kitchen/zrok/controller/store"
"github.com/pkg/errors"
"time"
)
const ConfigVersion = 1
type Config struct {
V int
Admin *AdminConfig
Endpoint *EndpointConfig
Proxy *ProxyConfig
Email *EmailConfig
Influx *InfluxConfig
Limits *LimitsConfig
Maintenance *MaintenanceConfig
Metrics *MetricsConfig
Registration *RegistrationConfig
Store *store.Config
Ziti *ZitiConfig
Metrics *MetricsConfig
Influx *InfluxConfig
}
type AdminConfig struct {
Secrets []string `cf:"+secret"`
}
type EndpointConfig struct {
@ -22,11 +32,6 @@ type EndpointConfig struct {
Port int
}
type ProxyConfig struct {
UrlTemplate string
Identities []string
}
type EmailConfig struct {
Host string
Port int
@ -37,6 +42,7 @@ type EmailConfig struct {
type RegistrationConfig struct {
EmailFrom string
RegistrationUrlTemplate string
TokenStrategy string
}
type ZitiConfig struct {
@ -56,10 +62,49 @@ type InfluxConfig struct {
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) {
cfg := &Config{}
cfg := DefaultConfig()
if err := cf.BindYaml(cfg, path, cf.DefaultOptions()); err != nil {
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
}

View File

@ -1,12 +1,14 @@
package controller
import (
"context"
"github.com/go-openapi/loads"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"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/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/pkg/errors"
)
@ -25,17 +27,28 @@ func Run(inCfg *Config) error {
}
api := operations.NewZrokAPI(swaggerSpec)
api.KeyAuth = ZrokAuthenticate
api.IdentityCreateAccountHandler = newCreateAccountHandler()
api.IdentityEnableHandler = newEnableHandler()
api.IdentityDisableHandler = newDisableHandler()
api.IdentityLoginHandler = identity.LoginHandlerFunc(loginHandler)
api.IdentityRegisterHandler = newRegisterHandler()
api.IdentityVerifyHandler = newVerifyHandler()
api.KeyAuth = newZrokAuthenticator(cfg).authenticate
api.AccountInviteHandler = newInviteHandler(cfg)
api.AccountLoginHandler = account.LoginHandlerFunc(loginHandler)
api.AccountRegisterHandler = newRegisterHandler()
api.AccountVerifyHandler = newVerifyHandler()
api.AdminCreateFrontendHandler = newCreateFrontendHandler()
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.MetadataVersionHandler = metadata.VersionHandlerFunc(versionHandler)
api.TunnelTunnelHandler = newTunnelHandler()
api.TunnelUntunnelHandler = newUntunnelHandler()
api.ShareAccessHandler = newAccessHandler()
api.ShareShareHandler = newShareHandler(cfg.Limits)
api.ShareUnaccessHandler = newUnaccessHandler()
api.ShareUnshareHandler = newUnshareHandler()
api.ShareUpdateShareHandler = newUpdateShareHandler()
if err := controllerStartup(); err != nil {
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)
defer func() { _ = server.Shutdown() }()
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 (
"github.com/go-openapi/runtime/middleware"
"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_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/sirupsen/logrus"
@ -17,51 +18,51 @@ func newDisableHandler() *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()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return identity.NewDisableInternalServerError()
return environment.NewDisableInternalServerError()
}
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 {
logrus.Errorf("identity check failed: %v", err)
return identity.NewDisableUnauthorized()
return environment.NewDisableUnauthorized()
}
env, err := str.GetEnvironment(envId, tx)
if err != nil {
logrus.Errorf("error getting environment: %v", err)
return identity.NewDisableInternalServerError()
return environment.NewDisableInternalServerError()
}
edge, err := edgeClient()
if err != nil {
logrus.Errorf("error getting edge client: %v", err)
return identity.NewDisableInternalServerError()
return environment.NewDisableInternalServerError()
}
if err := self.removeServicesForEnvironment(envId, tx, edge); err != nil {
logrus.Errorf("error removing services for environment: %v", err)
return identity.NewDisableInternalServerError()
if err := h.removeSharesForEnvironment(envId, tx, edge); err != nil {
logrus.Errorf("error removing shares for environment: %v", err)
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)
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)
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)
return identity.NewDisableInternalServerError()
return environment.NewDisableInternalServerError()
}
if err := tx.Commit(); err != nil {
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)
if err != nil {
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)
}
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)
if err != nil {
return err
}
svcs, err := str.FindServicesForEnvironment(envId, tx)
shrs, err := str.FindSharesForEnvironment(envId, tx)
if err != nil {
return err
}
for _, svc := range svcs {
svcName := svc.Name
logrus.Infof("garbage collecting service '%v' for environment '%v'", svcName, env.ZId)
if err := deleteServiceEdgeRouterPolicy(env.ZId, svcName, edge); err != nil {
for _, shr := range shrs {
shrToken := shr.Token
logrus.Infof("garbage collecting share '%v' for environment '%v'", shrToken, env.ZId)
if err := zrokEdgeSdk.DeleteServiceEdgeRouterPolicy(env.ZId, shrToken, edge); err != nil {
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)
}
if err := deleteServicePolicyBind(env.ZId, svcName, edge); err != nil {
if err := zrokEdgeSdk.DeleteServicePolicyBind(env.ZId, shrToken, edge); err != nil {
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)
}
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.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
}
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 {
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"

View File

@ -2,77 +2,89 @@ package controller
import (
"bytes"
"context"
"encoding/json"
"fmt"
"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/zrokEdgeSdk"
"github.com/openziti-test-kitchen/zrok/rest_model_zrok"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/identity"
"github.com/openziti/edge/rest_management_api_client"
"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/openziti-test-kitchen/zrok/rest_server_zrok/operations/environment"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"time"
)
type enableHandler struct {
cfg *LimitsConfig
}
func newEnableHandler() *enableHandler {
return &enableHandler{}
func newEnableHandler(cfg *LimitsConfig) *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
tx, err := str.Begin()
if err != nil {
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()
if err != nil {
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 {
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 {
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)
return identity.NewEnableInternalServerError()
return environment.NewEnableInternalServerError()
}
envId, err := str.CreateEnvironment(int(principal.ID), &store.Environment{
Description: params.Body.Description,
Host: params.Body.Host,
Address: realRemoteAddress(params.HTTPRequest),
ZId: ident.Payload.Data.ID,
ZId: envZId,
}, tx)
if err != nil {
logrus.Errorf("error storing created identity: %v", err)
_ = tx.Rollback()
return identity.NewCreateAccountInternalServerError()
return environment.NewEnableInternalServerError()
}
if err := tx.Commit(); err != nil {
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)
resp := identity.NewEnableCreated().WithPayload(&rest_model_zrok.EnableResponse{
Identity: ident.Payload.Data.ID,
resp := environment.NewEnableCreated().WithPayload(&rest_model_zrok.EnableResponse{
Identity: envZId,
})
var out bytes.Buffer
@ -87,86 +99,15 @@ func (self *enableHandler) Handle(params identity.EnableParams, principal *rest_
return resp
}
func (self *enableHandler) createIdentity(email string, client *rest_management_api_client.ZitiEdgeManagement) (*identity_edge.CreateIdentityCreated, error) {
iIsAdmin := false
name, err := createToken()
func (h *enableHandler) checkLimits(principal *rest_model_zrok.Principal, tx *sqlx.Tx) error {
if !principal.Limitless && h.cfg.Environments > Unlimited {
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
if err != nil {
return nil, err
return errors.Errorf("unable to find environments for account '%v': %v", principal.Email, err)
}
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,
if len(envs)+1 > h.cfg.Environments {
return errors.Errorf("would exceed environments limit of %d for '%v'", h.cfg.Environments, principal.Email)
}
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
}
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"
"fmt"
"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/config"
"github.com/openziti/edge/rest_management_api_client/service"
@ -36,13 +37,13 @@ func GC(inCfg *Config) error {
return err
}
defer func() { _ = tx.Rollback() }()
dbSvcs, err := str.GetAllServices(tx)
sshrs, err := str.GetAllShares(tx)
if err != nil {
return err
}
liveMap := make(map[string]struct{})
for _, dbSvc := range dbSvcs {
liveMap[dbSvc.Name] = struct{}{}
for _, sshr := range sshrs {
liveMap[sshr.Token] = struct{}{}
}
if err := gcServices(edge, liveMap); err != nil {
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 {
if _, found := liveMap[*svc.Name]; !found {
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)
}
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)
}
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)
}
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)
}
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)
}
} else {
@ -108,7 +109,7 @@ func gcServiceEdgeRouterPolicies(edge *rest_management_api_client.ZitiEdgeManage
for _, serp := range listResp.Payload.Data {
if _, found := liveMap[*serp.Name]; !found {
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)
}
} else {
@ -135,7 +136,7 @@ func gcServicePolicies(edge *rest_management_api_client.ZitiEdgeManagement, live
if _, found := liveMap[spName]; !found {
logrus.Infof("garbage collecting, svcId='%v'", spName)
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)
}
} 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 {
for _, c := range listResp.Payload.Data {
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)
}
} 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 (
"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/identity"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"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 == "" {
logrus.Errorf("missing email or password")
return identity.NewLoginUnauthorized()
return account.NewLoginUnauthorized()
}
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()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return identity.NewLoginUnauthorized()
return account.NewLoginUnauthorized()
}
defer func() { _ = tx.Rollback() }()
a, err := str.FindAccountWithEmail(params.Body.Email, tx)
if err != nil {
logrus.Errorf("error finding account '%v': %v", params.Body.Email, err)
return identity.NewLoginUnauthorized()
return account.NewLoginUnauthorized()
}
if a.Password != hashPassword(params.Body.Password) {
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 {
if ma.writeApi != nil {
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},
time.UnixMilli(v.LastUpdate))
pts = append(pts, pt)

View File

@ -1,10 +1,7 @@
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/metadata"
"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)
return metadata.NewOverviewInternalServerError()
}
var out rest_model_zrok.EnvironmentServicesList
var out rest_model_zrok.EnvironmentSharesList
for _, env := range envs {
svcs, err := str.FindServicesForEnvironment(env.Id, tx)
shrs, err := str.FindSharesForEnvironment(env.Id, tx)
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()
}
es := &rest_model_zrok.EnvironmentServices{
es := &rest_model_zrok.EnvironmentShares{
Environment: &rest_model_zrok.Environment{
Address: env.Address,
CreatedAt: env.CreatedAt.UnixMilli(),
@ -39,74 +36,34 @@ func overviewHandler(_ metadata.OverviewParams, principal *rest_model_zrok.Princ
ZID: env.ZId,
},
}
sparkData, err := sparkDataForServices(svcs)
if err != nil {
logrus.Errorf("error querying spark data for services: %v", err)
return metadata.NewOverviewInternalServerError()
for _, shr := range shrs {
feEndpoint := ""
if shr.FrontendEndpoint != nil {
feEndpoint = *shr.FrontendEndpoint
}
for _, svc := range svcs {
es.Services = append(es.Services, &rest_model_zrok.Service{
CreatedAt: svc.CreatedAt.UnixMilli(),
Frontend: svc.Frontend,
Backend: svc.Backend,
UpdatedAt: svc.UpdatedAt.UnixMilli(),
ZID: svc.ZId,
Name: svc.Name,
Metrics: sparkData[svc.Name],
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,
CreatedAt: shr.CreatedAt.UnixMilli(),
UpdatedAt: shr.UpdatedAt.UnixMilli(),
})
}
out = append(out, es)
}
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/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/identity"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"github.com/sirupsen/logrus"
)
@ -13,30 +13,30 @@ type registerHandler struct{}
func newRegisterHandler() *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 == "" {
logrus.Error("missing token or password")
return identity.NewRegisterNotFound()
return account.NewRegisterNotFound()
}
logrus.Infof("received register request for token '%v'", params.Body.Token)
tx, err := str.Begin()
if err != nil {
logrus.Error(err)
return identity.NewRegisterInternalServerError()
return account.NewRegisterInternalServerError()
}
defer func() { _ = tx.Rollback() }()
ar, err := str.FindAccountRequestWithToken(params.Body.Token, tx)
if err != nil {
logrus.Error(err)
return identity.NewRegisterNotFound()
return account.NewRegisterNotFound()
}
token, err := createToken()
if err != nil {
logrus.Error(err)
return identity.NewRegisterInternalServerError()
return account.NewRegisterInternalServerError()
}
a := &store.Account{
Email: ar.Email,
@ -45,20 +45,20 @@ func (self *registerHandler) Handle(params identity.RegisterParams) middleware.R
}
if _, err := str.CreateAccount(a, tx); err != nil {
logrus.Error(err)
return identity.NewRegisterInternalServerError()
return account.NewRegisterInternalServerError()
}
if err := str.DeleteAccountRequest(ar.Id, tx); err != nil {
logrus.Error(err)
return identity.NewRegisterInternalServerError()
return account.NewRegisterInternalServerError()
}
if err := tx.Commit(); err != nil {
logrus.Error(err)
return identity.NewCreateAccountInternalServerError()
return account.NewRegisterInternalServerError()
}
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/edge/rest_management_api_client"
"github.com/openziti/edge/rest_management_api_client/config"
"github.com/openziti/edge/rest_model"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"time"
@ -28,14 +27,14 @@ func inspectZiti() error {
if err != nil {
return errors.Wrap(err, "error getting ziti edge client")
}
if err := ensureZrokProxyConfigType(edge); err != nil {
if err := findZrokProxyConfigType(edge); err != nil {
return err
}
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)
limit := int64(100)
offset := int64(0)
@ -50,22 +49,11 @@ func ensureZrokProxyConfigType(edge *rest_management_api_client.ZitiEdgeManageme
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
if len(listResp.Payload.Data) != 1 {
return errors.Errorf("expected 1 zrok proxy config type, found %d", len(listResp.Payload.Data))
}
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)
}
return nil
}

View File

@ -10,15 +10,16 @@ type Account struct {
Email string
Password string
Token string
Limitless bool
}
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 {
return 0, errors.Wrap(err, "error preparing accounts insert statement")
}
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 id, nil

View File

@ -1,6 +1,10 @@
package store
import (
"fmt"
"strings"
"time"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
@ -40,6 +44,34 @@ func (self *Store) FindAccountRequestWithToken(token string, tx *sqlx.Tx) (*Acco
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) {
ar := &AccountRequest{}
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
}
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 {
Model
AccountId int
AccountId *int
Description string
Host string
Address string
@ -26,6 +26,18 @@ func (self *Store) CreateEnvironment(accountId int, i *Environment, tx *sqlx.Tx)
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) {
i := &Environment{}
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
}
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 {
stmt, err := tx.Prepare("delete from environments where id = $1")
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"
)
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()
if err != nil {
return nil, err
}
defer func() { _ = tx.Rollback() }()
if a, err := str.FindAccountWithToken(token, tx); err == nil {
principal := rest_model_zrok.Principal{
principal := &rest_model_zrok.Principal{
ID: int64(a.Id),
Token: a.Token,
Email: a.Email,
Limitless: a.Limitless,
}
return &principal, nil
return principal, nil
} 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")
}
}
@ -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)
}
func createServiceName() (string, error) {
func createShareToken() (string, error) {
gen, err := nanoid.CustomASCII("abcdefghijklmnopqrstuvwxyz0123456789", 12)
if err != nil {
return "", err
@ -78,3 +102,7 @@ func realRemoteAddress(req *http.Request) string {
}
return ip
}
func proxyUrl(shrToken, template string) string {
return strings.Replace(template, "{token}", shrToken, -1)
}

View File

@ -3,7 +3,7 @@ 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/identity"
"github.com/openziti-test-kitchen/zrok/rest_server_zrok/operations/account"
"github.com/sirupsen/logrus"
)
@ -14,25 +14,24 @@ func newVerifyHandler() *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 {
logrus.Debugf("received verify request for token '%v'", params.Body.Token)
tx, err := str.Begin()
if err != nil {
logrus.Errorf("error starting transaction: %v", err)
return identity.NewVerifyInternalServerError()
return account.NewVerifyInternalServerError()
}
defer func() { _ = tx.Rollback() }()
ar, err := str.FindAccountRequestWithToken(params.Body.Token, tx)
if err != nil {
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})
} else {
logrus.Error("empty verification request")
return identity.NewVerifyInternalServerError().WithPayload(rest_model_zrok.ErrorMessage("empty verification request"))
return account.NewVerifyOK().WithPayload(&rest_model_zrok.VerifyResponse{Email: ar.Email})
}
logrus.Error("empty verification request")
return account.NewVerifyInternalServerError()
}

View File

@ -2,7 +2,7 @@ package controller
import (
"bytes"
"github.com/openziti-test-kitchen/zrok/controller/email_ui"
"github.com/openziti-test-kitchen/zrok/controller/emailUi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/wneessen/go-mail"
@ -63,7 +63,7 @@ func sendVerificationEmail(emailAddress, token 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 {
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