init
This commit is contained in:
commit
23c7133fa9
33 files changed
+2896
No files matched your search
@@ -0,0 +1,114 @@
|
||||
// Package model defines the data types persisted as the tool's state:
|
||||
// hosts grouped into access groups, and users assigned to those groups.
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// Host is one jump-target reachable via port forwarding through the bastion.
|
||||
type Host struct {
|
||||
Name string `yaml:"name"` // short identifier, e.g. "db-prod-1"
|
||||
Address string `yaml:"address"` // hostname or IP as seen from the jump host
|
||||
Port int `yaml:"port"` // TCP port allowed for forwarding
|
||||
Description string `yaml:"description,omitempty"`
|
||||
}
|
||||
|
||||
// Group bundles hosts that share the same access policy (e.g. "team-billing").
|
||||
// Users are assigned to one or more groups; their effective allowed hosts are
|
||||
// the union of all hosts in their groups.
|
||||
type Group struct {
|
||||
Name string `yaml:"name"`
|
||||
Hosts []Host `yaml:"hosts"`
|
||||
}
|
||||
|
||||
// UserStatus is the lifecycle state of a managed account.
|
||||
type UserStatus string
|
||||
|
||||
const (
|
||||
StatusActive UserStatus = "active"
|
||||
StatusLocked UserStatus = "locked"
|
||||
)
|
||||
|
||||
// User is one managed Linux/SSH account on the jump host.
|
||||
type User struct {
|
||||
Username string `yaml:"username"`
|
||||
Groups []string `yaml:"groups"`
|
||||
Status UserStatus `yaml:"status"`
|
||||
Fingerprint string `yaml:"fingerprint"` // SHA256 fingerprint of the current authorized public key
|
||||
PublicKey string `yaml:"public_key,omitempty"` // full "ssh-ed25519 AAAA... comment" line; safe to store/back up (public), lets us rewrite authorized_keys (e.g. after a group's host list changes) without re-issuing the keypair
|
||||
Comment string `yaml:"comment,omitempty"`
|
||||
CreatedAt time.Time `yaml:"created_at"`
|
||||
LockedAt *time.Time `yaml:"locked_at,omitempty"`
|
||||
}
|
||||
|
||||
// State is the full persisted configuration of the tool.
|
||||
type State struct {
|
||||
Groups []Group `yaml:"groups"`
|
||||
Users []User `yaml:"users"`
|
||||
}
|
||||
|
||||
// FindGroup returns a pointer to the group with the given name, or nil.
|
||||
func (s *State) FindGroup(name string) *Group {
|
||||
for i := range s.Groups {
|
||||
if s.Groups[i].Name == name {
|
||||
return &s.Groups[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindUser returns a pointer to the user with the given username, or nil.
|
||||
func (s *State) FindUser(username string) *User {
|
||||
for i := range s.Users {
|
||||
if s.Users[i].Username == username {
|
||||
return &s.Users[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllowedHosts returns the de-duplicated union of hosts across all groups
|
||||
// the user belongs to, sorted by "name" for stable authorized_keys output.
|
||||
func (s *State) AllowedHosts(u *User) []Host {
|
||||
seen := map[string]Host{}
|
||||
order := []string{}
|
||||
for _, gname := range u.Groups {
|
||||
g := s.FindGroup(gname)
|
||||
if g == nil {
|
||||
continue
|
||||
}
|
||||
for _, h := range g.Hosts {
|
||||
key := h.Address + ":" + itoa(h.Port)
|
||||
if _, ok := seen[key]; !ok {
|
||||
order = append(order, key)
|
||||
}
|
||||
seen[key] = h
|
||||
}
|
||||
}
|
||||
out := make([]Host, 0, len(order))
|
||||
for _, k := range order {
|
||||
out = append(out, seen[k])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func itoa(n int) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
neg := n < 0
|
||||
if neg {
|
||||
n = -n
|
||||
}
|
||||
var buf [20]byte
|
||||
i := len(buf)
|
||||
for n > 0 {
|
||||
i--
|
||||
buf[i] = byte('0' + n%10)
|
||||
n /= 10
|
||||
}
|
||||
if neg {
|
||||
i--
|
||||
buf[i] = '-'
|
||||
}
|
||||
return string(buf[i:])
|
||||
}
|
||||
Reference in new issue
Block a user