implemented simple ssh dual control

This commit is contained in:
ston1th 2025-01-23 00:15:25 +01:00
commit 60012be942
12 changed files with 446 additions and 20 deletions

View file

@ -14,6 +14,7 @@ import (
type ContextData struct {
Approver *Approver
SSH *SSH
DB *db.DB
}
@ -97,6 +98,10 @@ 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()

View file

@ -43,15 +43,17 @@ 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)
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)
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)
)

99
pkg/api/types/ssh.go Normal file
View file

@ -0,0 +1,99 @@
// 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
}