chore: add mercury
This commit is contained in:
157
mercury/app/app_test.go
Normal file
157
mercury/app/app_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.sour.is/pkg/mercury"
|
||||
"go.sour.is/pkg/ident"
|
||||
)
|
||||
|
||||
type mockUser struct {
|
||||
roles map[string]struct{}
|
||||
ident.SessionInfo
|
||||
}
|
||||
|
||||
func (m *mockUser) Identity() string { return "user" }
|
||||
func (m *mockUser) HasRole(roles ...string) bool {
|
||||
var found bool
|
||||
for _, role := range roles {
|
||||
if _, ok := m.roles[role]; ok {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func Test_appConfig_GetRules(t *testing.T) {
|
||||
type args struct {
|
||||
u ident.Ident
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantLis mercury.Rules
|
||||
}{
|
||||
{"normal", args{&mockUser{}}, nil},
|
||||
{
|
||||
"admin",
|
||||
args{
|
||||
&mockUser{
|
||||
SessionInfo: ident.SessionInfo{Active: true},
|
||||
roles: map[string]struct{}{"admin": {}},
|
||||
},
|
||||
},
|
||||
mercury.Rules{
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: "mercury.source.*",
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: "mercury.priority",
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: "mercury.host",
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: "mercury.environ",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := mercuryEnviron{}
|
||||
if gotLis, _ := a.GetRules(context.TODO(), tt.args.u); !reflect.DeepEqual(gotLis, tt.wantLis) {
|
||||
t.Errorf("appConfig.GetRules() = %v, want %v", gotLis, tt.wantLis)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// func Test_appConfig_GetIndex(t *testing.T) {
|
||||
// type args struct {
|
||||
// search mercury.NamespaceSearch
|
||||
// in1 *rsql.Program
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// args args
|
||||
// wantLis mercury.Config
|
||||
// }{
|
||||
// {"nil", args{
|
||||
// nil,
|
||||
// nil,
|
||||
// }, nil},
|
||||
|
||||
// {"app.settings", args{
|
||||
// mercury.ParseNamespace("app.settings"),
|
||||
// nil,
|
||||
// }, mercury.Config{&mercury.Space{Space: "app.settings"}}},
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// a := mercuryEnviron{}
|
||||
// if gotLis, _ := a.GetIndex(tt.args.search, tt.args.in1); !reflect.DeepEqual(gotLis, tt.wantLis) {
|
||||
// t.Errorf("appConfig.GetIndex() = %#v, want %#v", gotLis, tt.wantLis)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func Test_appConfig_GetObjects(t *testing.T) {
|
||||
// cfg, err := mercury.ParseText(strings.NewReader(`
|
||||
// @mercury.source.mercury-settings.default
|
||||
// match :0 *
|
||||
// `))
|
||||
|
||||
// type args struct {
|
||||
// search mercury.NamespaceSearch
|
||||
// in1 *rsql.Program
|
||||
// in2 []string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// args args
|
||||
// wantLis mercury.Config
|
||||
// }{
|
||||
// {"nil", args{
|
||||
// nil,
|
||||
// nil,
|
||||
// nil,
|
||||
// }, nil},
|
||||
|
||||
// {"app.settings", args{
|
||||
// mercury.ParseNamespace("app.settings"),
|
||||
// nil,
|
||||
// nil,
|
||||
// }, mercury.Config{
|
||||
// &mercury.Space{
|
||||
// Space: "app.settings",
|
||||
// List: []mercury.Value{{
|
||||
// Space: "app.settings",
|
||||
// Name: "app.setting",
|
||||
// Values: []string{"TRUE"}},
|
||||
// },
|
||||
// },
|
||||
// }},
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// cfg, err :=
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// a := appConfig{cfg: }
|
||||
// if gotLis, _ := a.GetConfig(tt.args.search, tt.args.in1, tt.args.in2); !reflect.DeepEqual(gotLis, tt.wantLis) {
|
||||
// t.Errorf("appConfig.GetIndex() = %#v, want %#v", gotLis, tt.wantLis)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
98
mercury/app/default-rules.go
Normal file
98
mercury/app/default-rules.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"go.sour.is/pkg/mercury"
|
||||
"go.sour.is/pkg/ident"
|
||||
)
|
||||
|
||||
type mercuryDefault struct {
|
||||
name string
|
||||
cfg mercury.SpaceMap
|
||||
}
|
||||
|
||||
var (
|
||||
_ mercury.GetRules = (*mercuryDefault)(nil)
|
||||
|
||||
_ mercury.GetIndex = (*mercuryEnviron)(nil)
|
||||
_ mercury.GetConfig = (*mercuryEnviron)(nil)
|
||||
_ mercury.GetRules = (*mercuryEnviron)(nil)
|
||||
)
|
||||
|
||||
// GetRules returns default rules for user role.
|
||||
func (app *mercuryDefault) GetRules(ctx context.Context, id ident.Ident) (lis mercury.Rules, err error) {
|
||||
identity := id.Identity()
|
||||
|
||||
lis = append(lis,
|
||||
mercury.Rule{
|
||||
Role: "write",
|
||||
Type: "NS",
|
||||
Match: "mercury.@" + identity,
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "write",
|
||||
Type: "NS",
|
||||
Match: "mercury.@" + identity + ".*",
|
||||
},
|
||||
)
|
||||
|
||||
groups := groups(identity, &app.cfg)
|
||||
|
||||
if s, ok := app.cfg.Space("mercury.policy."+app.name); ok {
|
||||
for _, p := range s.List {
|
||||
if groups.Has(p.Name) {
|
||||
for _, r := range p.Values {
|
||||
fds := strings.Fields(r)
|
||||
if len(fds) < 3 {
|
||||
continue
|
||||
}
|
||||
lis = append(lis, mercury.Rule{
|
||||
Role: fds[0],
|
||||
Type: fds[1],
|
||||
Match: fds[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if u, ok := id.(hasRole); groups.Has("admin") || ok && u.HasRole("admin") {
|
||||
lis = append(lis,
|
||||
mercury.Rule{
|
||||
Role: "admin",
|
||||
Type: "NS",
|
||||
Match: "*",
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "write",
|
||||
Type: "NS",
|
||||
Match: "*",
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "admin",
|
||||
Type: "GR",
|
||||
Match: "*",
|
||||
},
|
||||
)
|
||||
} else if u.HasRole("write") {
|
||||
lis = append(lis,
|
||||
mercury.Rule{
|
||||
Role: "write",
|
||||
Type: "NS",
|
||||
Match: "*",
|
||||
},
|
||||
)
|
||||
} else if u.HasRole("read") {
|
||||
lis = append(lis,
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: "*",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return lis, nil
|
||||
}
|
||||
288
mercury/app/environ.go
Normal file
288
mercury/app/environ.go
Normal file
@@ -0,0 +1,288 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"go.sour.is/pkg/mercury"
|
||||
"go.sour.is/pkg/ident"
|
||||
"go.sour.is/pkg/rsql"
|
||||
"go.sour.is/pkg/set"
|
||||
)
|
||||
|
||||
const (
|
||||
mercurySource = "mercury.source.*"
|
||||
mercuryPriority = "mercury.priority"
|
||||
mercuryHost = "mercury.host"
|
||||
appDotEnviron = "mercury.environ"
|
||||
)
|
||||
var (
|
||||
mercuryPolicy = func(id string) string { return "mercury.@" + id + ".policy" }
|
||||
)
|
||||
|
||||
func Register(name string, cfg mercury.SpaceMap) {
|
||||
for _, c := range cfg {
|
||||
c.Tags = append(c.Tags, "RO")
|
||||
}
|
||||
mercury.Registry.Register("mercury-default", func(s *mercury.Space) any { return &mercuryDefault{name: name, cfg: cfg} })
|
||||
mercury.Registry.Register("mercury-environ", func(s *mercury.Space) any { return &mercuryEnviron{cfg: cfg, lookup: mercury.Registry.GetRules} })
|
||||
}
|
||||
|
||||
type hasRole interface {
|
||||
HasRole(r ...string) bool
|
||||
}
|
||||
|
||||
type mercuryEnviron struct {
|
||||
cfg mercury.SpaceMap
|
||||
lookup func(context.Context, ident.Ident) (mercury.Rules, error)
|
||||
}
|
||||
|
||||
// Index returns nil
|
||||
func (app *mercuryEnviron) GetIndex(ctx context.Context, search mercury.NamespaceSearch, _ *rsql.Program) (lis mercury.Config, err error) {
|
||||
|
||||
if search.Match(mercurySource) {
|
||||
for _, s := range app.cfg.ToArray() {
|
||||
if search.Match(s.Space) {
|
||||
lis = append(lis, &mercury.Space{Space: s.Space, Tags: []string{"RO"}})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if search.Match(mercuryPriority) {
|
||||
lis = append(lis, &mercury.Space{Space: mercuryPriority, Tags: []string{"RO"}})
|
||||
}
|
||||
|
||||
if search.Match(mercuryHost) {
|
||||
lis = append(lis, &mercury.Space{Space: mercuryHost, Tags: []string{"RO"}})
|
||||
}
|
||||
|
||||
if search.Match(appDotEnviron) {
|
||||
lis = append(lis, &mercury.Space{Space: appDotEnviron, Tags: []string{"RO"}})
|
||||
}
|
||||
if id := ident.FromContext(ctx); id != nil {
|
||||
identity := id.Identity()
|
||||
match := mercuryPolicy(identity)
|
||||
if search.Match(match) {
|
||||
lis = append(lis, &mercury.Space{Space: match, Tags: []string{"RO"}})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Objects returns nil
|
||||
func (app *mercuryEnviron) GetConfig(ctx context.Context, search mercury.NamespaceSearch, _ *rsql.Program, _ []string) (lis mercury.Config, err error) {
|
||||
if search.Match(mercurySource) {
|
||||
for _, s := range app.cfg.ToArray() {
|
||||
if search.Match(s.Space) {
|
||||
lis = append(lis, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if search.Match(mercuryPriority) {
|
||||
space := mercury.Space{
|
||||
Space: mercuryPriority,
|
||||
Tags: []string{"RO"},
|
||||
}
|
||||
|
||||
// for i, key := range mercury.Registry {
|
||||
// space.List = append(space.List, mercury.Value{
|
||||
// Space: appDotPriority,
|
||||
// Seq: uint64(i),
|
||||
// Name: key.Match,
|
||||
// Values: []string{fmt.Sprint(key.Priority)},
|
||||
// })
|
||||
// }
|
||||
|
||||
lis = append(lis, &space)
|
||||
}
|
||||
|
||||
if search.Match(mercuryHost) {
|
||||
if usr, err := user.Current(); err == nil {
|
||||
space := mercury.Space{
|
||||
Space: mercuryHost,
|
||||
Tags: []string{"RO"},
|
||||
}
|
||||
|
||||
hostname, _ := os.Hostname()
|
||||
wd, _ := os.Getwd()
|
||||
grp, _ := usr.GroupIds()
|
||||
space.List = []mercury.Value{
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 1,
|
||||
Name: "hostname",
|
||||
Values: []string{hostname},
|
||||
},
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 2,
|
||||
Name: "username",
|
||||
Values: []string{usr.Username},
|
||||
},
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 3,
|
||||
Name: "uid",
|
||||
Values: []string{usr.Uid},
|
||||
},
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 4,
|
||||
Name: "gid",
|
||||
Values: []string{usr.Gid},
|
||||
},
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 5,
|
||||
Name: "display",
|
||||
Values: []string{usr.Name},
|
||||
},
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 6,
|
||||
Name: "home",
|
||||
Values: []string{usr.HomeDir},
|
||||
},
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 7,
|
||||
Name: "groups",
|
||||
Values: grp,
|
||||
},
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 8,
|
||||
Name: "pid",
|
||||
Values: []string{fmt.Sprintf("%v", os.Getpid())},
|
||||
},
|
||||
{
|
||||
Space: mercuryHost,
|
||||
Seq: 9,
|
||||
Name: "wd",
|
||||
Values: []string{wd},
|
||||
},
|
||||
}
|
||||
|
||||
lis = append(lis, &space)
|
||||
}
|
||||
}
|
||||
|
||||
if search.Match(appDotEnviron) {
|
||||
env := os.Environ()
|
||||
space := mercury.Space{
|
||||
Space: appDotEnviron,
|
||||
Tags: []string{"RO"},
|
||||
}
|
||||
|
||||
sort.Strings(env)
|
||||
for i, s := range env {
|
||||
key, val, _ := strings.Cut(s, "=")
|
||||
|
||||
vals := []string{val}
|
||||
if strings.Contains(key, "PATH") || strings.Contains(key, "XDG") {
|
||||
vals = strings.Split(val, ":")
|
||||
}
|
||||
|
||||
space.List = append(space.List, mercury.Value{
|
||||
Space: appDotEnviron,
|
||||
Seq: uint64(i),
|
||||
Name: key,
|
||||
Values: vals,
|
||||
})
|
||||
}
|
||||
lis = append(lis, &space)
|
||||
}
|
||||
|
||||
if id := ident.FromContext(ctx); id != nil {
|
||||
identity := id.Identity()
|
||||
groups := groups(identity, &app.cfg)
|
||||
match := mercuryPolicy(identity)
|
||||
if search.Match(match) {
|
||||
space := &mercury.Space{
|
||||
Space: match,
|
||||
Tags: []string{"RO"},
|
||||
}
|
||||
|
||||
lis = append(lis, space)
|
||||
rules, err := app.lookup(ctx, id)
|
||||
if err != nil {
|
||||
space.AddNotes(err.Error())
|
||||
} else {
|
||||
k := mercury.NewValue("groups")
|
||||
k.AddValues(strings.Join(groups.Values(), " "))
|
||||
space.AddKeys(k)
|
||||
|
||||
k = mercury.NewValue("rules")
|
||||
for _, r := range rules {
|
||||
k.AddValues(strings.Join([]string{r.Role, r.Type, r.Match}, " "))
|
||||
}
|
||||
space.AddKeys(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Rules returns nil
|
||||
func (app *mercuryEnviron) GetRules(ctx context.Context, id ident.Ident) (lis mercury.Rules, err error) {
|
||||
identity := id.Identity()
|
||||
|
||||
lis = append(lis,
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: mercuryPolicy(identity),
|
||||
},
|
||||
)
|
||||
|
||||
groups := groups(identity, &app.cfg)
|
||||
|
||||
if u, ok := id.(hasRole); groups.Has("admin") || ok && u.HasRole("admin") {
|
||||
lis = append(lis,
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: mercurySource,
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: mercuryPriority,
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: mercuryHost,
|
||||
},
|
||||
mercury.Rule{
|
||||
Role: "read",
|
||||
Type: "NS",
|
||||
Match: appDotEnviron,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return lis, nil
|
||||
}
|
||||
|
||||
func groups(identity string, cfg *mercury.SpaceMap) set.Set[string] {
|
||||
groups := set.New[string]()
|
||||
if s, ok := cfg.Space("mercury.groups"); ok {
|
||||
for _, g := range s.List {
|
||||
for _, v := range g.Values {
|
||||
for _, u := range strings.Fields(v) {
|
||||
if u == identity {
|
||||
groups.Add(g.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return groups
|
||||
}
|
||||
Reference in New Issue
Block a user