diff --git a/cmd/keyctl/main.go b/cmd/keyctl/main.go index 187ca19..fbbfb5f 100644 --- a/cmd/keyctl/main.go +++ b/cmd/keyctl/main.go @@ -17,7 +17,6 @@ import ( "git.giftfish.de/ston1th/keyctl/pkg/api" "git.giftfish.de/ston1th/keyctl/pkg/cli" "git.giftfish.de/ston1th/keyctl/pkg/db" - "git.giftfish.de/ston1th/keyctl/pkg/ssh" "github.com/go-logr/logr" ) @@ -42,10 +41,6 @@ func usage() { keyctl approve - approve a key request (admin) keyctl reject - reject a key request (admin) keyctl del - delete a key (admin) - keyctl ssh login - request ssh access - keyctl ssh req - list current ssh requests (admin) - keyctl ssh approve - approve ssh access (admin) - keyctl ssh reject - reject ssh access (admin) keyctl help - show this usage `, version) os.Exit(1) @@ -61,11 +56,6 @@ func main() { cli.Run(arg) // does not return case "server": server() // does not return - case "ssh": - if len(os.Args) < 3 { - usage() // does not return - } - ssh.Run(os.Args[2]) // does not return default: usage() // does not return } diff --git a/pkg/api/client/client.go b/pkg/api/client/client.go index 6003227..36dc483 100644 --- a/pkg/api/client/client.go +++ b/pkg/api/client/client.go @@ -264,9 +264,7 @@ func (c *Client) FollowRedirectLoop(r *http.Request, v any, delay time.Duration) rdr, ok := err.(Redirect) if err != nil && !ok { if errors.Is(err, types.ErrISE) || - errors.Is(err, types.ErrKeyNotFound) || - errors.Is(err, types.ErrAccessReqNotFound) || - errors.Is(err, types.ErrAccessRequestRejected) { + errors.Is(err, types.ErrKeyNotFound) { return } if errors.Is(err, types.ErrReqNotFound) || diff --git a/pkg/api/server.go b/pkg/api/server.go index 9a90f66..ed85fa1 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -48,7 +48,6 @@ func NewServer(log logr.Logger, listen, socket string, db *db.DB) (*Server, erro log: log, data: &types.ContextData{ Approver: types.NewApprover(), - SSH: types.NewSSH(), DB: db, }, srv: &http.Server{ diff --git a/pkg/api/types/context.go b/pkg/api/types/context.go index 93de796..a272ebc 100644 --- a/pkg/api/types/context.go +++ b/pkg/api/types/context.go @@ -14,7 +14,6 @@ import ( type ContextData struct { Approver *Approver - SSH *SSH DB *db.DB } @@ -98,10 +97,6 @@ func (c *Context) Approver() *Approver { return c.data.Approver } -func (c *Context) SSH() *SSH { - return c.data.SSH -} - // OK is a empty HTTP 200 JSON response func (c *Context) OK() { c.log() diff --git a/pkg/api/types/error.go b/pkg/api/types/error.go index 81f9805..df249b1 100644 --- a/pkg/api/types/error.go +++ b/pkg/api/types/error.go @@ -43,17 +43,15 @@ func newError(err string, code int) Error { } var ( - ErrInvalidAPIRoute = newError("invalid api route", http.StatusNotFound) - ErrInvalid = newError("invalid data", http.StatusBadRequest) - ErrBadreq = newError("bad request", http.StatusBadRequest) - ErrForbidden = newError("forbidden", http.StatusForbidden) - ErrNotFound = newError("not found", http.StatusNotFound) - ErrKeyNotFound = newError("key not found", http.StatusNotFound) - ErrReqNotFound = newError("key request not found", http.StatusNotFound) - ErrAccessReqNotFound = newError("ssh request not found", http.StatusNotFound) - ErrTooManyApprovals = newError("too many approvals pending", http.StatusConflict) - ErrKeyRequestRejected = newError("key request rejected", http.StatusForbidden) - ErrAccessRequestRejected = newError("ssh request rejected", http.StatusForbidden) - ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed) - ErrISE = newError("internal server error", http.StatusInternalServerError) + ErrInvalidAPIRoute = newError("invalid api route", http.StatusNotFound) + ErrInvalid = newError("invalid data", http.StatusBadRequest) + ErrBadreq = newError("bad request", http.StatusBadRequest) + ErrForbidden = newError("forbidden", http.StatusForbidden) + ErrNotFound = newError("not found", http.StatusNotFound) + ErrKeyNotFound = newError("key not found", http.StatusNotFound) + ErrReqNotFound = newError("key request not found", http.StatusNotFound) + ErrTooManyApprovals = newError("too many approvals pending", http.StatusConflict) + ErrKeyRequestRejected = newError("key request rejected", http.StatusForbidden) + ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed) + ErrISE = newError("internal server error", http.StatusInternalServerError) ) diff --git a/pkg/api/types/ssh.go b/pkg/api/types/ssh.go deleted file mode 100644 index 74bdfba..0000000 --- a/pkg/api/types/ssh.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (C) 2023 Marius Schellenberger - -package types - -import ( - "maps" - "sync" - "time" - - "git.giftfish.de/ston1th/keyctl/pkg/key" -) - -type SSHMap map[string]SSHAccess - -type SSH struct { - // protects m - mu sync.RWMutex - - m SSHMap -} - -func NewSSH() *SSH { - return &SSH{ - m: make(SSHMap), - } -} - -func (s *SSH) New(host, user, ip string) (aid string, err error) { - s.mu.Lock() - defer s.mu.Unlock() - size := len(s.m) - if size > maxSize { - err = ErrTooManyApprovals - return - } - aid, err = key.GenerateID() - if err != nil { - return - } - s.m[aid] = SSHAccess{ - Host: host, - User: user, - IP: ip, - } - return -} - -func (s *SSH) List() (m SSHMap) { - s.mu.RLock() - m = maps.Clone(s.m) - s.mu.RUnlock() - return -} -func (s *SSH) Status(aid, host, user, ip string) (status Status) { - s.mu.RLock() - if app, ok := s.m[aid]; ok { - if app.Host == host && app.IP == ip && app.User == user { - status = app.Status - } else { - status = Rejected - } - } else { - status = NotFound - } - s.mu.RUnlock() - if status == Approved || status == Rejected { - s.mu.Lock() - delete(s.m, aid) - s.mu.Unlock() - } - return -} - -func (s *SSH) Update(aid string, status Status) (err error) { - s.mu.Lock() - if app, ok := s.m[aid]; ok { - app.Status = status - s.m[aid] = app - go s.cleanup(aid) - } else { - err = ErrAccessReqNotFound - } - s.mu.Unlock() - return -} - -func (s *SSH) cleanup(aid string) { - time.Sleep(time.Second * 10) - s.mu.Lock() - delete(s.m, aid) - s.mu.Unlock() -} - -type SSHAccess struct { - Host string - User string - IP string - Status Status -} diff --git a/pkg/api/v1/client/client.go b/pkg/api/v1/client/client.go index 5135c55..f96994f 100644 --- a/pkg/api/v1/client/client.go +++ b/pkg/api/v1/client/client.go @@ -18,7 +18,6 @@ var ( ErrClientIsNil = errors.New("client is nil") keyPath = schema.KeyPath reqPath = schema.ReqPath - accessPath = schema.AccessPath ) type Client struct { @@ -122,45 +121,3 @@ func (c *Client) DeleteKey(ctx context.Context, id string) (err error) { _, err = c.c.Do(req, nil) return } - -func (c *Client) Access(ctx context.Context, host, user string) (ok bool, err error) { - path := fmt.Sprintf("%s/%s/%s", accessPath, host, user) - req, err := c.c.NewRequest(ctx, "GET", path, nil) - if err != nil { - return - } - _, err = c.c.FollowRedirectLoop(req, &ok, time.Second*2) - return -} - -func (c *Client) GetAccess(ctx context.Context) (accessList schema.AccessList, err error) { - path := accessPath - req, err := c.c.NewRequest(ctx, "GET", path, nil) - if err != nil { - return - } - _, err = c.c.Do(req, &accessList) - return -} - -func (c *Client) RejectAccess(ctx context.Context, id string) error { - return c.sendAccess(ctx, id, false) -} - -func (c *Client) AcceptAccess(ctx context.Context, id string) error { - return c.sendAccess(ctx, id, true) -} - -func (c *Client) sendAccess(ctx context.Context, id string, approve bool) (err error) { - path := fmt.Sprintf("%s/%s", accessPath, id) - method := "DELETE" - if approve { - method = "PUT" - } - req, err := c.c.NewRequest(ctx, method, path, nil) - if err != nil { - return - } - _, err = c.c.Do(req, nil) - return -} diff --git a/pkg/api/v1/schema/schema.go b/pkg/api/v1/schema/schema.go index f5d304d..3dd3466 100644 --- a/pkg/api/v1/schema/schema.go +++ b/pkg/api/v1/schema/schema.go @@ -14,13 +14,11 @@ import ( ) const ( - Version = "v1" - KeyName = "key" - ReqName = "req" - KeyPath = "/" + Version + "/" + KeyName - ReqPath = "/" + Version + "/" + ReqName - SSH = "ssh" - AccessPath = "/" + Version + "/" + SSH + Version = "v1" + KeyName = "key" + ReqName = "req" + KeyPath = "/" + Version + "/" + KeyName + ReqPath = "/" + Version + "/" + ReqName ) type NewKey struct { @@ -70,10 +68,6 @@ func ApproveURL(id, aid string) string { return KeyPath + "/" + id + "/" + aid } -func AccessURL(host, user, aid string) string { - return AccessPath + "/" + host + "/" + user + "/" + aid -} - type Key struct { ID string `json:"id"` Name string `json:"name"` @@ -176,47 +170,3 @@ func NewApprovals(m types.ApprovalMap) (a Approvals) { a.Sort() return } - -type Access struct { - Host string `json:"host"` - User string `json:"user"` - AID string `json:"aid"` - IP string `json:"ip"` - Status types.Status `json:"status"` -} - -type AccessList []Access - -func (AccessList) Less(a, b Access) int { return cmp.Compare(a.Host, b.Host) } -func (a AccessList) Sort() { slices.SortStableFunc(a, a.Less) } -func (a AccessList) Len() int { return len(a) } -func (a AccessList) Fields() string { - return "Host\t User\t Approval ID\t IP\t Status" -} -func (a AccessList) Values() []string { - vals := make([]string, len(a)) - for i, v := range a { - vals[i] = fmt.Sprintf("%s\t %s\t %s\t %s\t %s", - v.Host, - v.User, - v.AID, - v.IP, - v.Status, - ) - } - return vals -} - -func NewAccessList(m types.SSHMap) (a AccessList) { - for k, v := range m { - a = append(a, Access{ - Host: v.Host, - User: v.User, - AID: k, - IP: v.IP, - Status: v.Status, - }) - } - a.Sort() - return -} diff --git a/pkg/api/v1/server/handler.go b/pkg/api/v1/server/handler.go index 6c93482..fe8b0be 100644 --- a/pkg/api/v1/server/handler.go +++ b/pkg/api/v1/server/handler.go @@ -157,57 +157,3 @@ func reqHandler(ctx *types.Context) { } ctx.OK() } - -func accessHandler(ctx *types.Context) { - host := ctx.Var("host") - user := ctx.Var("user") - ip := ctx.ClientIP() - ssh := ctx.SSH() - aid := ctx.Var("aid") - var err error - if aid == "" { - aid, err = ssh.New(host, user, ip) - if err != nil { - ctx.Log.Error(err, "error creating access", "host", host, "user", user) - ctx.Err(types.ErrTooManyApprovals) - return - } - ctx.Redirect(schema.AccessURL(host, user, aid), http.StatusFound) - return - } - s := ssh.Status(aid, host, user, ip) - switch s { - case types.Pending: - ctx.Redirect(schema.AccessURL(host, user, aid), http.StatusFound) - return - case types.Rejected: - ctx.Err(types.ErrAccessRequestRejected) - return - case types.NotFound: - ctx.Err(types.ErrAccessReqNotFound) - return - } - ctx.JSON(true) -} -func accessReqHandler(ctx *types.Context) { - id := ctx.Var("id") - status := types.Pending - switch ctx.Method() { - case "PUT": - status = types.Approved - case "DELETE": - status = types.Rejected - } - err := ctx.SSH().Update(id, status) - if err != nil { - ctx.Log.Error(err, "error updating ssh request") - ctx.Err(types.ErrAccessReqNotFound) - return - } - ctx.OK() -} - -func accessListHandler(ctx *types.Context) { - l := ctx.SSH().List() - ctx.JSON(schema.NewAccessList(l)) -} diff --git a/pkg/api/v1/server/routes.go b/pkg/api/v1/server/routes.go index a3d2368..89b58b8 100644 --- a/pkg/api/v1/server/routes.go +++ b/pkg/api/v1/server/routes.go @@ -9,12 +9,9 @@ import ( ) const ( - keyID = schema.KeyPath + core.IDRoute - keyReqID = schema.KeyPath + core.IDRoute + core.AIDRoute - reqID = schema.ReqPath + core.IDRoute - accessID = schema.AccessPath + core.IDRoute - access = schema.AccessPath + core.HostRoute + core.UserRoute - accessAID = schema.AccessPath + core.HostRoute + core.UserRoute + core.AIDRoute + keyID = schema.KeyPath + core.IDRoute + keyReqID = schema.KeyPath + core.IDRoute + core.AIDRoute + reqID = schema.ReqPath + core.IDRoute ) var Routes = []types.Route{ @@ -53,25 +50,4 @@ var Routes = []types.Route{ Handler: local(reqHandler), Methods: []string{"PUT", "DELETE"}, }, - // SSH - { - Path: accessID, - Handler: local(accessReqHandler), - Methods: []string{"PUT", "DELETE"}, - }, - { - Path: schema.AccessPath, - Handler: local(accessListHandler), - Methods: []string{"GET"}, - }, - { - Path: access, - Handler: accessHandler, - Methods: []string{"GET"}, - }, - { - Path: accessAID, - Handler: accessHandler, - Methods: []string{"GET"}, - }, } diff --git a/pkg/core/key.go b/pkg/core/key.go index a1d74bb..1180358 100644 --- a/pkg/core/key.go +++ b/pkg/core/key.go @@ -9,8 +9,6 @@ const ( nameReReverse = "[^" + re + "]+" IDRoute = "/{id}" AIDRoute = "/{aid}" - UserRoute = "/{user}" - HostRoute = "/{host}" ) var NameRe = regexp.MustCompile(nameReReverse) diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go deleted file mode 100644 index 6ffbd43..0000000 --- a/pkg/ssh/ssh.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (C) 2023 Marius Schellenberger - -package ssh - -import ( - "context" - "errors" - "flag" - "fmt" - "os" - "text/tabwriter" - - apiclient "git.giftfish.de/ston1th/keyctl/pkg/api/client" - clientv1 "git.giftfish.de/ston1th/keyctl/pkg/api/v1/client" - "git.giftfish.de/ston1th/keyctl/pkg/core" - "golang.org/x/sys/unix" -) - -var fs = flag.NewFlagSet("", flag.ExitOnError) - -func exit(err error) { - fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(1) -} - -func Run(arg string) { - ctx := context.Background() - switch arg { - case "approve": - id, err := idFlags("access id") - if err != nil { - exit(err) - } - c := client() - err = c.AcceptAccess(ctx, id) - if err != nil { - exit(err) - } - case "reject": - id, err := idFlags("access id") - if err != nil { - exit(err) - } - c := client() - err = c.RejectAccess(ctx, id) - if err != nil { - exit(err) - } - case "login": - endpoint, err := loginFlags() - if err != nil { - exit(err) - } - opts, err := apiclient.ClientOptionsFromConfig( - apiclient.Config{Endpoint: endpoint}, - ) - if err != nil { - exit(err) - } - c := client(opts...) - host, err := os.Hostname() - if err != nil { - exit(err) - } - // TODO: fix me (user) - ok, err := c.Access(ctx, host, "user") - if err != nil { - exit(err) - } - if ok { - unix.Exec("/bin/bash", []string{"bash"}, os.Environ()) // does not return - } - case "req": - c := client() - access, err := c.GetAccess(ctx) - if err != nil { - exit(err) - } - print(access) - } - os.Exit(0) -} - -func client(opts ...apiclient.ClientOption) (c *clientv1.Client) { - var err error - if opts == nil { - opts, err = apiclient.ClientOptionsFromEnv() - if err != nil { - exit(err) - } - } - apic := apiclient.NewClient(opts...) - //if socket, ok := apic.IsUnix(); ok { - // err = unveil(socket) - // if err != nil { - // exit(err) - // } - //} - c, err = clientv1.NewClient(apic) - if err != nil { - exit(err) - } - return -} - -func print(tw core.TableWriter) { - if tw.Len() == 0 { - return - } - w := tabwriter.NewWriter(os.Stdout, 1, 0, 1, ' ', tabwriter.Debug) - fmt.Fprintln(w, tw.Fields()) - for _, v := range tw.Values() { - fmt.Fprintln(w, v) - } - w.Flush() -} - -func idFlags(desc string) (id string, err error) { - fs.StringVar(&id, "id", "", desc) - fs.Parse(os.Args[3:]) - if id == "" { - err = errors.New("missing flag: id") - } - return -} - -func loginFlags() (endpoint string, err error) { - fs.StringVar(&endpoint, "endpoint", "", "keyctl endpoint") - fs.Parse(os.Args[3:]) - if endpoint == "" { - err = errors.New("missing flag: endpoint") - } - return -}