add edit and all in one menu for hosts and services
This commit is contained in:
1 parent
8c1b93c14e
commit
e39ad275b5
15 files changed
+1136
-675
No files matched your search
+157
-19
@@ -1,6 +1,10 @@
|
||||
// Package model defines the data types persisted as the tool's state:
|
||||
// hosts (each offering one or more TCP services) grouped into access
|
||||
// groups, and users assigned to those groups.
|
||||
// Package model defines the data types persisted as the tool's state.
|
||||
//
|
||||
// Hosts (each offering one or more TCP services) form a shared catalog,
|
||||
// defined once; groups reference a subset of that catalog by name rather
|
||||
// than redefining hosts/services per group — this matters when the same
|
||||
// hosts/services are reused across many groups; and users are assigned to
|
||||
// one or more groups.
|
||||
package model
|
||||
|
||||
import (
|
||||
@@ -30,20 +34,25 @@ type Service struct {
|
||||
Description string `yaml:"description,omitempty"`
|
||||
}
|
||||
|
||||
// Host is one jump-target reachable via port forwarding through the
|
||||
// bastion, identified by address and offering one or more Services.
|
||||
// Host is a catalog entry: one jump-target reachable via port forwarding
|
||||
// through the bastion, defined once and referenced by any number of groups
|
||||
// (see Group.Hosts) — hosts/services shared across many groups only need to
|
||||
// be entered a single time.
|
||||
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
|
||||
Services []Service `yaml:"services"`
|
||||
Name string `yaml:"name"` // short identifier, e.g. "db-prod-1"
|
||||
Address string `yaml:"address"` // hostname or IP as seen from the jump host
|
||||
Description string `yaml:"description,omitempty"`
|
||||
Services []Service `yaml:"services"`
|
||||
}
|
||||
|
||||
// Group bundles hosts that share the same access policy (e.g. "team-billing").
|
||||
// Users are assigned to one or more groups; their effective allowed
|
||||
// forwards are the union of all services on all hosts in their groups.
|
||||
// Group is a named access policy referencing a subset of catalog hosts (by
|
||||
// name — all of that host's services are included). Users are assigned to
|
||||
// one or more groups; their effective allowed forwards are the union of
|
||||
// services on all hosts referenced by all their groups.
|
||||
type Group struct {
|
||||
Name string `yaml:"name"`
|
||||
Hosts []Host `yaml:"hosts"`
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description,omitempty"`
|
||||
Hosts []string `yaml:"hosts"` // names referencing State.Hosts
|
||||
}
|
||||
|
||||
// UserStatus is the lifecycle state of a managed account.
|
||||
@@ -68,10 +77,21 @@ type User struct {
|
||||
|
||||
// State is the full persisted configuration of the tool.
|
||||
type State struct {
|
||||
Hosts []Host `yaml:"hosts"` // shared catalog
|
||||
Groups []Group `yaml:"groups"`
|
||||
Users []User `yaml:"users"`
|
||||
}
|
||||
|
||||
// FindHost returns a pointer to the catalog host with the given name, or nil.
|
||||
func (s *State) FindHost(name string) *Host {
|
||||
for i := range s.Hosts {
|
||||
if s.Hosts[i].Name == name {
|
||||
return &s.Hosts[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -105,11 +125,9 @@ type Forward struct {
|
||||
}
|
||||
|
||||
// AllowedForwards returns the de-duplicated union of (host, service) pairs
|
||||
// across all groups the user belongs to, in stable encounter order (group
|
||||
// order, then host order, then service order, as defined in state.yaml).
|
||||
// Two services that resolve to the same address:port (even from different
|
||||
// groups) collapse to a single Forward — forwarding the same destination
|
||||
// twice would be meaningless.
|
||||
// across all catalog hosts referenced by all of the user's groups, in
|
||||
// stable encounter order. Two services that resolve to the same
|
||||
// address:port (even from different groups) collapse to a single Forward.
|
||||
func (s *State) AllowedForwards(u *User) []Forward {
|
||||
seen := map[string]bool{}
|
||||
var out []Forward
|
||||
@@ -118,7 +136,11 @@ func (s *State) AllowedForwards(u *User) []Forward {
|
||||
if g == nil {
|
||||
continue
|
||||
}
|
||||
for _, h := range g.Hosts {
|
||||
for _, hostName := range g.Hosts {
|
||||
h := s.FindHost(hostName)
|
||||
if h == nil {
|
||||
continue
|
||||
}
|
||||
for _, svc := range h.Services {
|
||||
key := fmt.Sprintf("%s:%d", h.Address, svc.Port)
|
||||
if seen[key] {
|
||||
@@ -137,3 +159,119 @@ func (s *State) AllowedForwards(u *User) []Forward {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// GroupNamesReferencingHost returns every group that includes hostName.
|
||||
func (s *State) GroupNamesReferencingHost(hostName string) []string {
|
||||
var out []string
|
||||
for _, g := range s.Groups {
|
||||
for _, h := range g.Hosts {
|
||||
if h == hostName {
|
||||
out = append(out, g.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// UsernamesInGroup returns every user currently a member of groupName.
|
||||
func (s *State) UsernamesInGroup(groupName string) []string {
|
||||
var out []string
|
||||
for _, u := range s.Users {
|
||||
for _, g := range u.Groups {
|
||||
if g == groupName {
|
||||
out = append(out, u.Username)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// UsernamesUsingHost returns every user reachable transitively through any
|
||||
// group that references hostName (deduplicated).
|
||||
func (s *State) UsernamesUsingHost(hostName string) []string {
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
for _, gname := range s.GroupNamesReferencingHost(hostName) {
|
||||
for _, uname := range s.UsernamesInGroup(gname) {
|
||||
if !seen[uname] {
|
||||
seen[uname] = true
|
||||
out = append(out, uname)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RenameGroup renames a group and updates every user's membership reference
|
||||
// so nobody silently falls out of a group just because it was renamed.
|
||||
func (s *State) RenameGroup(oldName, newName string) {
|
||||
if g := s.FindGroup(oldName); g != nil {
|
||||
g.Name = newName
|
||||
}
|
||||
for i := range s.Users {
|
||||
for j, gn := range s.Users[i].Groups {
|
||||
if gn == oldName {
|
||||
s.Users[i].Groups[j] = newName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteGroup removes a group and drops the membership reference from every
|
||||
// user who had it. Caller is responsible for resyncing authorized_keys for
|
||||
// any affected user (see UsernamesInGroup, called before this).
|
||||
func (s *State) DeleteGroup(name string) {
|
||||
for i := range s.Groups {
|
||||
if s.Groups[i].Name == name {
|
||||
s.Groups = append(s.Groups[:i], s.Groups[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := range s.Users {
|
||||
var kept []string
|
||||
for _, gn := range s.Users[i].Groups {
|
||||
if gn != name {
|
||||
kept = append(kept, gn)
|
||||
}
|
||||
}
|
||||
s.Users[i].Groups = kept
|
||||
}
|
||||
}
|
||||
|
||||
// RenameHost renames a catalog host and updates every group's reference to
|
||||
// it, so a rename doesn't silently drop the host out of groups using it.
|
||||
func (s *State) RenameHost(oldName, newName string) {
|
||||
if h := s.FindHost(oldName); h != nil {
|
||||
h.Name = newName
|
||||
}
|
||||
for i := range s.Groups {
|
||||
for j, hn := range s.Groups[i].Hosts {
|
||||
if hn == oldName {
|
||||
s.Groups[i].Hosts[j] = newName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteHost removes a host from the catalog and from every group that
|
||||
// referenced it. Caller is responsible for resyncing authorized_keys for
|
||||
// any affected user (see UsernamesUsingHost, called before this).
|
||||
func (s *State) DeleteHost(name string) {
|
||||
for i := range s.Hosts {
|
||||
if s.Hosts[i].Name == name {
|
||||
s.Hosts = append(s.Hosts[:i], s.Hosts[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := range s.Groups {
|
||||
var kept []string
|
||||
for _, hn := range s.Groups[i].Hosts {
|
||||
if hn != name {
|
||||
kept = append(kept, hn)
|
||||
}
|
||||
}
|
||||
s.Groups[i].Hosts = kept
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user