v0.2.0
This commit is contained in:
1 parent
23c7133fa9
commit
8c1b93c14e
16 files changed
+1187
-295
No files matched your search
+69
-44
@@ -1,20 +1,46 @@
|
||||
// Package model defines the data types persisted as the tool's state:
|
||||
// hosts grouped into access groups, and users assigned to those groups.
|
||||
// hosts (each offering one or more TCP services) grouped into access
|
||||
// groups, and users assigned to those groups.
|
||||
package model
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"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
|
||||
// nameRe restricts identifiers that end up as ssh_config tokens or
|
||||
// authorized_keys comments: no spaces, no shell/config metacharacters.
|
||||
var nameRe = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]*$`)
|
||||
|
||||
// ValidName reports whether s is safe to use as a group/host/service name:
|
||||
// letters, digits, dot, underscore, hyphen, no spaces. Names end up as
|
||||
// ssh_config comments/tokens and authorized_keys metadata, where a stray
|
||||
// space or "#" would corrupt the generated file.
|
||||
func ValidName(s string) bool {
|
||||
return nameRe.MatchString(s)
|
||||
}
|
||||
|
||||
// Service is one TCP port reachable on a Host — e.g. the host's sshd on
|
||||
// port 22, or an unrelated application on port 7888. A single Host commonly
|
||||
// exposes several of these.
|
||||
type Service struct {
|
||||
Name string `yaml:"name"` // short identifier, e.g. "ssh" or "web-api"
|
||||
Port int `yaml:"port"` // remote TCP port on the host
|
||||
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.
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Users are assigned to one or more groups; their effective allowed
|
||||
// forwards are the union of all services on all hosts in their groups.
|
||||
type Group struct {
|
||||
Name string `yaml:"name"`
|
||||
Hosts []Host `yaml:"hosts"`
|
||||
@@ -66,49 +92,48 @@ func (s *State) FindUser(username string) *User {
|
||||
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{}
|
||||
// Forward is one flattened (host, service) pair a user is allowed to reach.
|
||||
// This is the actual unit both server-side enforcement (authorized_keys
|
||||
// "permitopen=") and the client config (one "LocalForward" line per
|
||||
// Forward) are built from.
|
||||
type Forward struct {
|
||||
HostName string
|
||||
Address string
|
||||
Port int
|
||||
ServiceName string
|
||||
Description string
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *State) AllowedForwards(u *User) []Forward {
|
||||
seen := map[string]bool{}
|
||||
var out []Forward
|
||||
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)
|
||||
for _, svc := range h.Services {
|
||||
key := fmt.Sprintf("%s:%d", h.Address, svc.Port)
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
out = append(out, Forward{
|
||||
HostName: h.Name,
|
||||
Address: h.Address,
|
||||
Port: svc.Port,
|
||||
ServiceName: svc.Name,
|
||||
Description: svc.Description,
|
||||
})
|
||||
}
|
||||
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