Skip to content

Commit

Permalink
feat(cli): allow host removal and token creation
Browse files Browse the repository at this point in the history
  • Loading branch information
gnur committed Sep 17, 2020
1 parent c007878 commit de47952
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 117 deletions.
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,49 @@ loglevel = "debug" #or info, warning, error
databasepath = "./tobab.db"
```

## cli
```
Usage: tobab <command>
Flags:
-h, --help Show context-sensitive help.
--debug
-c, --config=STRING config location
Commands:
run
start tobab server
validate
validate tobab config
host list
list all hosts
host add --hostname=STRING --backend=STRING --type=STRING
add a new proxy host
host delete --hostname=STRING
delete a host
version
print tobab version
Run "tobab <command> --help" for more information on a command.
```

### examples
```shell
# add a host
tobab host add --hostname=test.example.com --backend=127.0.0.1:8080 --type=http --public
# list hosts
tobab host list
# delete a host
tobab host delete --hostname=test.example.com
```

## api calls

### example api call to add a route that only allows signed in users with a example.com email address

```http
Expand Down Expand Up @@ -94,3 +137,13 @@ Cookie: X-Tobab-Token=<token>
}
###
```

### example api call to delete a route
```http
# @name delHost
DELETE /v1/api/host/prom.tobab.erwin.land
User-Agent: curl/7.64.1
Accept: */*
Cookie: X-Tobab-Token=<token>
###
```
28 changes: 27 additions & 1 deletion clirpc/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package clirpc

import "github.com/gnur/tobab"
import (
"time"

"github.com/gnur/tobab"
"github.com/o1egl/paseto/v2"
)

type Empty struct{}
type GetHostsOut struct {
Expand All @@ -9,3 +14,24 @@ type GetHostsOut struct {
type AddHostIn struct {
Host tobab.Host
}

type DeleteHostIn struct {
Hostname string
}

type CreateTokenIn struct {
Email string
TTL time.Duration
}

type CreateTokenOut struct {
Token string
}

type ValidateTokenIn struct {
Token string
}

type ValidateTokenOut struct {
Token paseto.JSONToken
}
105 changes: 94 additions & 11 deletions cmd/tobab/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"net/rpc"
"time"

"github.com/alecthomas/kong"
"github.com/gnur/tobab"
Expand Down Expand Up @@ -37,15 +38,37 @@ func (r *ValidateCmd) Run(ctx *Globals) error {
}

type HostCmd struct {
List HostListCmd `cmd help:"list all hosts"`
Add AddHostCmd `cmd help:"add a new proxy host"`
List HostListCmd `cmd:"" help:"list all hosts"`
Add AddHostCmd `cmd:"" help:"add a new proxy host"`
Delete DeleteHostCmd `cmd:"" help:"delete a host"`
}

type DeleteHostCmd struct {
Hostname string `help:"hostname to remove" kong:"required" short:"h"`
}

func (r *DeleteHostCmd) Run(ctx *Globals) error {
client, err := rpc.DialHTTP("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
in := &clirpc.DeleteHostIn{
Hostname: r.Hostname,
}
var out clirpc.Empty
err = client.Call("Tobab.DeleteHost", in, &out)
if err != nil {
log.Fatal("tobab error:", err)
}
fmt.Println("host deleted")
return nil
}

type AddHostCmd struct {
Hostname string `help:"hostname to listen on"`
Backend string `help:"Backend to connect to"`
Hostname string `help:"hostname to listen on" kong:"required"`
Backend string `help:"Backend to connect to" kong:"required"`
Public bool `help:"allows all connections"`
Type string `help:"type of proxy"`
Type string `help:"type of proxy" kong:"required"`
Globs []tobab.Glob `help:"if host is not public, globs of email addresses to allow access"`
}

Expand Down Expand Up @@ -100,18 +123,78 @@ func (r *VersionCmd) Run(ctx *Globals) error {
return nil
}

type TokenCmd struct {
Create CreateTokenCmd `cmd:"" help:"generate a new token"`
Validate ValidateTokenCmd `cmd:"" help:"Get fields from a token"`
}

type CreateTokenCmd struct {
Email string `help:"email address to issue token to" kong:"required" short:"e"`
TTL string `help:"max age of token" kong:"required" short:"t"`
}

func (r *CreateTokenCmd) Run(ctx *Globals) error {
ttl, err := time.ParseDuration(r.TTL)
if err != nil {
return fmt.Errorf("Invalid duration provided: %w", err)
}
client, err := rpc.DialHTTP("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
in := &clirpc.CreateTokenIn{
Email: r.Email,
TTL: ttl,
}
var out clirpc.CreateTokenOut
err = client.Call("Tobab.CreateToken", in, &out)
if err != nil {
log.Fatal("tobab error:", err)
}
fmt.Println("token created")
fmt.Println(out.Token)
return nil
}

type ValidateTokenCmd struct {
Token string `help:"plain text token" kong:"required" short:"t"`
}

func (r *ValidateTokenCmd) Run(ctx *Globals) error {
client, err := rpc.DialHTTP("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
in := &clirpc.ValidateTokenIn{
Token: r.Token,
}
var out clirpc.ValidateTokenOut
err = client.Call("Tobab.ValidateToken", in, &out)
if err != nil {
log.Fatal("tobab error:", err)
}
t := out.Token
fmt.Printf(`
Issuer: %s
Subject: %s
Issued at: %s
Expires at: %s
`, t.Issuer, t.Subject, t.IssuedAt, t.Expiration)
return nil
}

var cli struct {
Globals

Run RunCmd `cmd help:"start tobab server"`
Validate ValidateCmd `cmd help:"validate tobab config"`
Host HostCmd `cmd help:"various host related commands"`
Version VersionCmd `cmd help:"print tobab version"`
Run RunCmd `cmd:"" help:"start tobab server"`
Validate ValidateCmd `cmd:"" help:"validate tobab config"`
Host HostCmd `cmd:"" help:"various host related commands"`
Version VersionCmd `cmd:"" help:"print tobab version"`
Token TokenCmd `cmd:"" help:"various token related commands"`
}

func main() {
ctx := kong.Parse(&cli)
// Call the Run() method of the selected parsed command.
ctx := kong.Parse(&cli, kong.UsageOnError())
err := ctx.Run(&Globals{
Debug: cli.Debug,
Config: cli.Config,
Expand Down
15 changes: 7 additions & 8 deletions cmd/tobab/paseto.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"net/http"
"time"

"github.com/o1egl/paseto"
"github.com/o1egl/paseto/v2"
)

var ErrUnauthenticatedRequest = errors.New("No user information in request")
Expand Down Expand Up @@ -45,23 +45,22 @@ func (app *Tobab) decryptToken(t string) (*paseto.JSONToken, error) {
return &token, nil
}

func (app *Tobab) newToken(u string, claims map[string]string) (string, error) {
func (app *Tobab) newToken(u, issuer string, TTL time.Duration) (string, error) {
now := time.Now()
exp := now.Add(app.maxAge)
if TTL > app.maxAge {
return "", errors.New("Provided ttl is too long")
}
exp := now.Add(TTL)
nbt := now

jsonToken := paseto.JSONToken{
Issuer: app.fqdn,
Issuer: issuer,
Subject: u,
IssuedAt: now,
Expiration: exp,
NotBefore: nbt,
}

for k, v := range claims {
jsonToken.Set(k, v)
}

token, err := v2.Encrypt(app.key, jsonToken, footer)
if err != nil {
return "", err
Expand Down
65 changes: 23 additions & 42 deletions cmd/tobab/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"context"
"fmt"
"html/template"
"net"
"net/http"
Expand All @@ -11,7 +10,6 @@ import (
"net/url"
"os"
"os/signal"
"strings"
"time"

"github.com/caddyserver/certmagic"
Expand All @@ -27,15 +25,16 @@ import (
var version = "manual build"

type Tobab struct {
fqdn string
key []byte
config tobab.Config
logger *logrus.Entry
maxAge time.Duration
templates *template.Template
confLoc string
db tobab.Database
server *http.Server
fqdn string
key []byte
config tobab.Config
logger *logrus.Entry
maxAge time.Duration
defaultAge time.Duration
templates *template.Template
confLoc string
db tobab.Database
server *http.Server
}

func run(confLoc string) {
Expand Down Expand Up @@ -80,6 +79,7 @@ func run(confLoc string) {
if err != nil {
logger.WithError(err).WithField("location", cfg.DatabasePath).Fatal("Unable to initialize database")
}
defer db.Close()

app := Tobab{
key: key,
Expand All @@ -91,6 +91,18 @@ func run(confLoc string) {
db: db,
}

if age, err := time.ParseDuration(cfg.DefaultTokenAge); err != nil {
app.defaultAge = 720 * time.Hour
} else {
app.defaultAge = age
}

if age, err := time.ParseDuration(cfg.MaxTokenAge); err != nil {
app.maxAge = 24 * 365 * time.Hour
} else {
app.maxAge = age
}

app.templates, err = loadTemplates()
if err != nil {
logger.WithError(err).Fatal("unable to load templates")
Expand Down Expand Up @@ -176,37 +188,6 @@ func (app *Tobab) startServer() {
r.Use(handlers.CompressHandler)
r.Use(app.getRBACMiddleware())

if strings.EqualFold(app.config.Loglevel, "debug") {
err = r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
pathTemplate, err := route.GetPathTemplate()
if err == nil {
fmt.Println("ROUTE:", pathTemplate)
}
pathRegexp, err := route.GetPathRegexp()
if err == nil {
fmt.Println("Path regexp:", pathRegexp)
}
queriesTemplates, err := route.GetQueriesTemplates()
if err == nil {
fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
}
queriesRegexps, err := route.GetQueriesRegexp()
if err == nil {
fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
}
methods, err := route.GetMethods()
if err == nil {
fmt.Println("Methods:", strings.Join(methods, ","))
}
fmt.Println()
return nil
})

if err != nil {
app.logger.WithError(err).Error("failed dumping routes")
}
}

magicListener, err := certmagic.Listen(certHosts)
if err != nil {
app.logger.WithError(err).Fatal("Failed getting certmagic listener")
Expand Down
Loading

0 comments on commit de47952

Please sign in to comment.