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

@ -18,6 +18,7 @@ var (
ErrClientIsNil = errors.New("client is nil")
keyPath = schema.KeyPath
reqPath = schema.ReqPath
accessPath = schema.AccessPath
)
type Client struct {
@ -121,3 +122,45 @@ 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
}

View file

@ -14,11 +14,13 @@ import (
)
const (
Version = "v1"
KeyName = "key"
ReqName = "req"
KeyPath = "/" + Version + "/" + KeyName
ReqPath = "/" + Version + "/" + ReqName
Version = "v1"
KeyName = "key"
ReqName = "req"
KeyPath = "/" + Version + "/" + KeyName
ReqPath = "/" + Version + "/" + ReqName
SSH = "ssh"
AccessPath = "/" + Version + "/" + SSH
)
type NewKey struct {
@ -68,6 +70,10 @@ 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"`
@ -170,3 +176,47 @@ 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
}

View file

@ -157,3 +157,57 @@ 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))
}

View file

@ -9,9 +9,12 @@ import (
)
const (
keyID = schema.KeyPath + core.IDRoute
keyReqID = schema.KeyPath + core.IDRoute + core.AIDRoute
reqID = schema.ReqPath + core.IDRoute
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
)
var Routes = []types.Route{
@ -50,4 +53,25 @@ 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"},
},
}