route/cmd/route-cli/main.go

268 lines
7.8 KiB
Go

package main
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"go.uber.org/atomic"
"git.xeserv.us/xena/route/internal/database"
"git.xeserv.us/xena/route/internal/routecrypto"
proto "git.xeserv.us/xena/route/proto"
"github.com/Xe/ln"
"github.com/Xe/uuid"
jwtcreds "github.com/Xe/x/tools/svc/credentials/jwt"
"github.com/dickeyxxx/netrc"
"github.com/kr/pretty"
"github.com/olekukonko/tablewriter"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"gopkg.in/alecthomas/kingpin.v2"
)
var allScopes = []string{
"token:get", "token:getall", "token:put", "token:delete", "token:deactivate",
"route:get", "route:getall", "route:put", "route:delete",
"connect",
}
var whoami string
func userHomeDir() string {
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
return home
}
return os.Getenv("HOME")
}
var (
app = kingpin.New("route", "An interface to routed")
grpcServer = app.Flag("routed-addr", "routed grpc address").Default("127.0.0.1:7268").String()
netrcPath = app.Flag("netrc", "netrc path").Default(filepath.Join(userHomeDir(), ".netrc")).String()
generateKey = app.Command("generate-key", "generate SSL cert crypto key")
backends = app.Command("backend", "backend management")
backendList = backends.Command("list", "list connected backends")
backendListDomain = backendList.Flag("domain", "if set, match backends against this domain name").String()
backendListUser = backendList.Flag("user", "if set, match backends against this username").String()
backendKill = backends.Command("kill", "forcibly disconnect a given backend")
backendKillID = backendKill.Flag("id", "the ID of the backend to remove").Required().String()
routes = app.Command("route", "route management")
routesCreate = routes.Command("create", "create a new route")
routesCreateDomain = routesCreate.Flag("domain", "domain for the route (if not given one will be generated for you)").String()
routesInspect = routes.Command("inspect", "inspect one route")
routesInspectDomain = routesInspect.Flag("domain", "domain to inspect").Required().String()
routesList = routes.Command("list", "list all routes owned by you")
routesRm = routes.Command("rm", "remove a route")
routesRmID = routesRm.Flag("id", "route ID to remove").Required().String()
testServer = app.Command("test-server", "spawn a simple HTTP test server on a TCP address")
testServerAddr = testServer.Flag("addr", "TCP address").Default(":9090").String()
token = app.Command("token", "token management")
tokenCreate = token.Command("create", "create a new token")
tokenCreateScopes = tokenCreate.Flag("scope", "token scopes").Default(allScopes...).Strings()
tokenGenerate = token.Command("generate-root", "generate a root token")
tokenGenerateKey = tokenGenerate.Flag("key", "token crypto key").Required().String()
tokenGenerateScopes = tokenGenerate.Flag("scopes", "token scopes").Default(allScopes...).Strings()
tokenGenerateUsername = tokenGenerate.Flag("username", "token username").Required().String()
tokenGenerateDatabasePath = tokenGenerate.Flag("db", "database file to add the root token to").Required().String()
tokenInspect = token.Command("inspect", "inspect a token")
tokenInspectID = tokenInspect.Flag("token-id", "token id").Required().String()
tokenList = token.Command("list", "list all tokens belonging to you")
tokenListDeactivated = tokenList.Flag("deactivated", "list deactivated tokens?").Default("false").Bool()
tokenRm = token.Command("rm", "remove a token")
tokenRmHard = tokenRm.Flag("hard", "hard-delete the token instead of deactivating it").Default("false").Bool()
tokenRmID = tokenRm.Flag("id", "token id").Required().String()
)
var (
hits *atomic.Int64
)
func init() {
hits = atomic.NewInt64(0)
}
func handle(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Route is go!")
fmt.Fprintf(w, "%s\n", pretty.Sprintf("%s", r.Header))
hn, _ := os.Hostname()
fmt.Fprintf(w, "Served by %s running %s\n", hn, runtime.GOOS)
fmt.Fprintf(w, "Hit count: %d", hits.Inc())
ip := r.Header.Get("X-Remote-Ip")
if ip != "" {
log.Printf("Hit from %s: %s", ip, r.RequestURI)
}
}
func main() {
cmdline := kingpin.MustParse(app.Parse(os.Args[1:]))
n, err := netrc.Parse(*netrcPath)
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "netrc.Parse"})
}
_ = n
switch cmdline {
case "test-server":
http.HandleFunc("/", handle)
ln.Fatal(ln.F{"err": http.ListenAndServe(*testServerAddr, nil), "action": "test_server"})
case "generate-key":
key, err := routecrypto.GenerateKey()
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "routecrypto.GenerateKey"})
}
fmt.Println("Your key is:", routecrypto.ShowKey(key))
case "token generate-root":
key, err := routecrypto.ParseKey(*tokenGenerateKey)
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "routecrypto.ParseKey"})
}
db, err := database.NewBoltStorage(*tokenGenerateDatabasePath, key)
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "database.NewBoltStorage"})
}
tBody := uuid.New()
_, err = db.PutToken(context.Background(), tBody, *tokenGenerateUsername, *tokenGenerateScopes)
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "db.PutToken"})
}
defer db.Close()
fmt.Println("Your token is:", tBody)
n.AddMachine(*grpcServer, *tokenGenerateUsername, tBody)
err = n.Save()
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "n.Save"})
}
return
}
m := n.Machine(*grpcServer)
connCreds := credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})
creds := jwtcreds.NewFromToken(m.Get("password"))
conn, err := grpc.Dial(*grpcServer,
grpc.WithTransportCredentials(connCreds),
grpc.WithPerRPCCredentials(creds))
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "grpc.Dial"})
}
rc := proto.NewRoutesClient(conn)
tc := proto.NewTokensClient(conn)
bc := proto.NewBackendsClient(conn)
_ = rc
_ = tc
_ = bc
switch cmdline {
case "route create":
idr, err := rc.Put(context.Background(), &proto.Route{Host: *routesCreateDomain})
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "rc.Put"})
}
fmt.Println(idr.Id)
case "route inspect":
r, err := rc.Get(context.Background(), &proto.GetRouteRequest{
Host: *routesCreateDomain,
})
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "rc.Get"})
}
json.NewEncoder(os.Stdout).Encode(r)
fmt.Println()
return
case "route list":
rts, err := rc.GetAll(context.Background(), &proto.Nil{})
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "rc.GetAll"})
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Host"})
for _, r := range rts.Routes {
table.Append([]string{r.Id, r.Host})
}
table.Render()
return
case "route rm":
_, err := rc.Delete(context.Background(), &proto.Route{Id: *routesRmID})
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "rc.Delete"})
}
case "backend list":
bkds, err := bc.List(context.Background(), &proto.BackendSelector{
Domain: *backendListDomain,
User: *backendListUser,
})
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "list backends"})
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Proto", "User", "Domain", "Failure Chance", "Host"})
for _, bknd := range bkds.Backends {
table.Append([]string{bknd.Id, bknd.Proto, bknd.User, bknd.Domain, fmt.Sprint(bknd.Phi), bknd.Host})
}
table.Render()
case "backend kill":
_, err := bc.Kill(context.Background(), &proto.BackendID{Id: *backendKillID})
if err != nil {
ln.Fatal(ln.F{"err": err, "action": "attempt to kill backend", "backend_id": *backendKillID})
}
fmt.Println("killed backend " + *backendKillID)
}
}