Reduce IAM API to only user and policies

This commit is contained in:
Ingo Oppermann 2023-05-17 18:19:23 +02:00
parent f03e2ca5c5
commit 6f831fd190
No known key found for this signature in database
GPG Key ID: 2AB32426E9DD229E
9 changed files with 258 additions and 150 deletions

View File

@ -119,7 +119,7 @@ type IAMAuth0Tenant struct {
} }
type IAMPolicy struct { type IAMPolicy struct {
Domain string `json:"group"` Domain string `json:"domain"`
Resource string `json:"resource"` Resource string `json:"resource"`
Actions []string `json:"actions"` Actions []string `json:"actions"`
} }

View File

@ -28,6 +28,7 @@ func NewIAM(iam iam.IAM) *IAMHandler {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param config body api.IAMUser true "User definition" // @Param config body api.IAMUser true "User definition"
// @Param domain query string false "Domain of the acting user"
// @Success 200 {object} api.IAMUser // @Success 200 {object} api.IAMUser
// @Failure 400 {object} api.Error // @Failure 400 {object} api.Error
// @Failure 500 {object} api.Error // @Failure 500 {object} api.Error
@ -35,10 +36,8 @@ func NewIAM(iam iam.IAM) *IAMHandler {
// @Router /api/v3/iam/user [post] // @Router /api/v3/iam/user [post]
func (h *IAMHandler) AddUser(c echo.Context) error { func (h *IAMHandler) AddUser(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "") ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
if !h.iam.Enforce(ctxuser, "$none", "iam:/user", "write") { domain := util.DefaultQuery(c, "domain", "$none")
return api.Err(http.StatusForbidden, "Forbidden")
}
user := api.IAMUser{} user := api.IAMUser{}
@ -48,17 +47,27 @@ func (h *IAMHandler) AddUser(c echo.Context) error {
iamuser, iampolicies := user.Unmarshal() iamuser, iampolicies := user.Unmarshal()
if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") {
return api.Err(http.StatusForbidden, "Forbidden")
}
for _, p := range iampolicies {
if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") {
return api.Err(http.StatusForbidden, "Forbidden", "Not allowed to write policy: %v", p)
}
}
if !superuser && iamuser.Superuser {
return api.Err(http.StatusForbidden, "Forbidden", "Only superusers can add superusers")
}
err := h.iam.CreateIdentity(iamuser) err := h.iam.CreateIdentity(iamuser)
if err != nil { if err != nil {
return api.Err(http.StatusBadRequest, "Bad request", "%s", err) return api.Err(http.StatusBadRequest, "Bad request", "%s", err)
} }
for _, p := range iampolicies { for _, p := range iampolicies {
if len(p.Domain) != 0 { h.iam.AddPolicy(p.Name, p.Domain, p.Resource, p.Actions)
continue
}
h.iam.AddPolicy(p.Name, "$none", p.Resource, p.Actions)
} }
err = h.iam.SaveIdentities() err = h.iam.SaveIdentities()
@ -76,6 +85,7 @@ func (h *IAMHandler) AddUser(c echo.Context) error {
// @ID iam-3-delete-user // @ID iam-3-delete-user
// @Produce json // @Produce json
// @Param name path string true "Username" // @Param name path string true "Username"
// @Param domain query string false "Domain of the acting user"
// @Success 200 {string} string // @Success 200 {string} string
// @Failure 404 {object} api.Error // @Failure 404 {object} api.Error
// @Failure 500 {object} api.Error // @Failure 500 {object} api.Error
@ -83,17 +93,25 @@ func (h *IAMHandler) AddUser(c echo.Context) error {
// @Router /api/v3/iam/user/{name} [delete] // @Router /api/v3/iam/user/{name} [delete]
func (h *IAMHandler) RemoveUser(c echo.Context) error { func (h *IAMHandler) RemoveUser(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "") ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
domain := util.DefaultQuery(c, "domain", "$none")
name := util.PathParam(c, "name") name := util.PathParam(c, "name")
if !h.iam.Enforce(ctxuser, "$none", "iam:/user", "write") { if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") {
return api.Err(http.StatusForbidden, "Forbidden") return api.Err(http.StatusForbidden, "Forbidden")
} }
// Remove all policies of that user iamuser, err := h.iam.GetIdentity(name)
h.iam.RemovePolicy(name, "", "", nil) if err != nil {
return api.Err(http.StatusNotFound, "Not found", "%s", err)
}
if !superuser && iamuser.Superuser {
return api.Err(http.StatusForbidden, "Forbidden", "Only superusers can remove superusers")
}
// Remove the user // Remove the user
err := h.iam.DeleteIdentity(name) err = h.iam.DeleteIdentity(name)
if err != nil { if err != nil {
return api.Err(http.StatusBadRequest, "Bad request", "%s", err) return api.Err(http.StatusBadRequest, "Bad request", "%s", err)
} }
@ -103,6 +121,9 @@ func (h *IAMHandler) RemoveUser(c echo.Context) error {
return api.Err(http.StatusInternalServerError, "Internal server error", "%s", err) return api.Err(http.StatusInternalServerError, "Internal server error", "%s", err)
} }
// Remove all policies of that user
h.iam.RemovePolicy(name, "", "", nil)
return c.JSON(http.StatusOK, "OK") return c.JSON(http.StatusOK, "OK")
} }
@ -114,6 +135,7 @@ func (h *IAMHandler) RemoveUser(c echo.Context) error {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param name path string true "Username" // @Param name path string true "Username"
// @Param domain query string false "Domain of the acting user"
// @Param user body api.IAMUser true "User definition" // @Param user body api.IAMUser true "User definition"
// @Success 200 {object} api.IAMUser // @Success 200 {object} api.IAMUser
// @Failure 400 {object} api.Error // @Failure 400 {object} api.Error
@ -123,15 +145,26 @@ func (h *IAMHandler) RemoveUser(c echo.Context) error {
// @Router /api/v3/iam/user/{name} [put] // @Router /api/v3/iam/user/{name} [put]
func (h *IAMHandler) UpdateUser(c echo.Context) error { func (h *IAMHandler) UpdateUser(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "") ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
domain := util.DefaultQuery(c, "domain", "$none")
name := util.PathParam(c, "name") name := util.PathParam(c, "name")
if !h.iam.Enforce(ctxuser, "$none", "iam:/user", "write") { if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") {
return api.Err(http.StatusForbidden, "Forbidden") return api.Err(http.StatusForbidden, "Forbidden")
} }
iamuser, err := h.iam.GetIdentity(name) var iamuser iam.User
if err != nil { var err error
return api.Err(http.StatusNotFound, "Not found", "%s", err)
if name != "$anon" {
iamuser, err = h.iam.GetIdentity(name)
if err != nil {
return api.Err(http.StatusNotFound, "Not found", "%s", err)
}
} else {
iamuser = iam.User{
Name: "$anon",
}
} }
iampolicies := h.iam.ListPolicies(name, "", "", nil) iampolicies := h.iam.ListPolicies(name, "", "", nil)
@ -145,19 +178,31 @@ func (h *IAMHandler) UpdateUser(c echo.Context) error {
iamuser, iampolicies = user.Unmarshal() iamuser, iampolicies = user.Unmarshal()
err = h.iam.UpdateIdentity(name, iamuser) if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") {
if err != nil { return api.Err(http.StatusForbidden, "Forbidden")
return api.Err(http.StatusBadRequest, "Bad request", "%s", err)
} }
h.iam.RemovePolicy(name, "$none", "", nil) for _, p := range iampolicies {
if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") {
return api.Err(http.StatusForbidden, "Forbidden", "Not allowed to write policy: %v", p)
}
}
if !superuser && iamuser.Superuser {
return api.Err(http.StatusForbidden, "Forbidden", "Only superusers can modify superusers")
}
if name != "$anon" {
err = h.iam.UpdateIdentity(name, iamuser)
if err != nil {
return api.Err(http.StatusBadRequest, "Bad request", "%s", err)
}
}
h.iam.RemovePolicy(name, "", "", nil)
for _, p := range iampolicies { for _, p := range iampolicies {
if len(p.Domain) != 0 { h.iam.AddPolicy(p.Name, p.Domain, p.Resource, p.Actions)
continue
}
h.iam.AddPolicy(p.Name, "$none", p.Resource, p.Actions)
} }
err = h.iam.SaveIdentities() err = h.iam.SaveIdentities()
@ -168,6 +213,71 @@ func (h *IAMHandler) UpdateUser(c echo.Context) error {
return c.JSON(http.StatusOK, user) return c.JSON(http.StatusOK, user)
} }
// UpdateUserPolicies replaces existing user policies
// @Summary Replace policies of an user
// @Description Replace policies of an user
// @Tags v16.?.?
// @ID iam-3-update-user
// @Accept json
// @Produce json
// @Param name path string true "Username"
// @Param domain query string false "Domain of the acting user"
// @Param user body []api.IAMPolicy true "Policy definitions"
// @Success 200 {array} api.IAMPolicy
// @Failure 400 {object} api.Error
// @Failure 404 {object} api.Error
// @Failure 500 {object} api.Error
// @Security ApiKeyAuth
// @Router /api/v3/iam/user/{name}/policy [put]
func (h *IAMHandler) UpdateUserPolicies(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
domain := util.DefaultQuery(c, "domain", "$none")
name := util.PathParam(c, "name")
if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") {
return api.Err(http.StatusForbidden, "Forbidden")
}
var iamuser iam.User
var err error
if name != "$anon" {
iamuser, err = h.iam.GetIdentity(name)
if err != nil {
return api.Err(http.StatusNotFound, "Not found", "%s", err)
}
} else {
iamuser = iam.User{
Name: "$anon",
}
}
policies := []api.IAMPolicy{}
if err := util.ShouldBindJSON(c, &policies); err != nil {
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
}
for _, p := range policies {
if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") {
return api.Err(http.StatusForbidden, "Forbidden", "Not allowed to write policy: %v", p)
}
}
if !superuser && iamuser.Superuser {
return api.Err(http.StatusForbidden, "Forbidden", "Only superusers can modify superusers")
}
h.iam.RemovePolicy(name, "", "", nil)
for _, p := range policies {
h.iam.AddPolicy(iamuser.Name, p.Domain, p.Resource, p.Actions)
}
return c.JSON(http.StatusOK, policies)
}
// GetUser returns the user with the given name // GetUser returns the user with the given name
// @Summary List an user by its name // @Summary List an user by its name
// @Description List aa user by its name // @Description List aa user by its name
@ -175,24 +285,44 @@ func (h *IAMHandler) UpdateUser(c echo.Context) error {
// @ID iam-3-get-user // @ID iam-3-get-user
// @Produce json // @Produce json
// @Param name path string true "Username" // @Param name path string true "Username"
// @Param domain query string false "Domain of the acting user"
// @Success 200 {object} api.IAMUser // @Success 200 {object} api.IAMUser
// @Failure 404 {object} api.Error // @Failure 404 {object} api.Error
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /api/v3/iam/user/{name} [get] // @Router /api/v3/iam/user/{name} [get]
func (h *IAMHandler) GetUser(c echo.Context) error { func (h *IAMHandler) GetUser(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "") ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
domain := util.DefaultQuery(c, "domain", "$none")
name := util.PathParam(c, "name") name := util.PathParam(c, "name")
if !h.iam.Enforce(ctxuser, "$none", "iam:/user", "read") { if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "read") {
return api.Err(http.StatusForbidden, "Forbidden") return api.Err(http.StatusForbidden, "Forbidden")
} }
iamuser, err := h.iam.GetIdentity(name) var iamuser iam.User
if err != nil { var err error
return api.Err(http.StatusNotFound, "Not found", "%s", err)
if name != "$anon" {
iamuser, err = h.iam.GetIdentity(name)
if err != nil {
return api.Err(http.StatusNotFound, "Not found", "%s", err)
}
if !superuser && name != iamuser.Name {
if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") {
iamuser = iam.User{
Name: iamuser.Name,
}
}
}
} else {
iamuser = iam.User{
Name: "$anon",
}
} }
iampolicies := h.iam.ListPolicies(name, "$none", "", nil) iampolicies := h.iam.ListPolicies(name, "", "", nil)
user := api.IAMUser{} user := api.IAMUser{}
user.Marshal(iamuser, iampolicies) user.Marshal(iamuser, iampolicies)

View File

@ -77,8 +77,8 @@ func DefaultQuery(c echo.Context, name, defValue string) string {
return param return param
} }
func DefaultContext(c echo.Context, name, defValue string) string { func DefaultContext[T any](c echo.Context, name string, defValue T) T {
value, ok := c.Get(name).(string) value, ok := c.Get(name).(T)
if !ok { if !ok {
return defValue return defValue
} }

View File

@ -12,7 +12,7 @@
// - Auth0 access token // - Auth0 access token
// - Basic auth // - Basic auth
// //
// The path prefix /api/login is treated specially in order to accomodate // The path prefix /api/login is treated specially in order to accommodate
// different ways of identification (UserPass, Auth0). All other /api paths // different ways of identification (UserPass, Auth0). All other /api paths
// only allow JWT as authentication method. // only allow JWT as authentication method.
// //
@ -202,11 +202,15 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
resource = "fs:" + resource resource = "fs:" + resource
} }
superuser := false
if identity != nil { if identity != nil {
username = identity.Name() username = identity.Name()
superuser = identity.IsSuperuser()
} }
c.Set("user", username) c.Set("user", username)
c.Set("superuser", superuser)
if len(domain) == 0 { if len(domain) == 0 {
domain = "$none" domain = "$none"

View File

@ -537,18 +537,8 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
v3.POST("/iam/user", s.v3handler.iam.AddUser) v3.POST("/iam/user", s.v3handler.iam.AddUser)
v3.GET("/iam/user/:name", s.v3handler.iam.GetUser) v3.GET("/iam/user/:name", s.v3handler.iam.GetUser)
v3.PUT("/iam/user/:name", s.v3handler.iam.UpdateUser) v3.PUT("/iam/user/:name", s.v3handler.iam.UpdateUser)
v3.PUT("/iam/user/:name/policy", s.v3handler.iam.UpdateUserPolicies)
v3.DELETE("/iam/user/:name", s.v3handler.iam.RemoveUser) v3.DELETE("/iam/user/:name", s.v3handler.iam.RemoveUser)
v3.POST("/iam/group", s.v3handler.iam.AddGroup)
v3.GET("/iam/group", s.v3handler.iam.ListGroups)
v3.GET("/iam/group/:group", s.v3handler.iam.GetGroup)
v3.DELETE("/iam/group/:group", s.v3handler.iam.RemoveGroup)
v3.POST("/iam/group/:group/user", s.v3handler.iam.AddGroupUser)
v3.GET("/iam/group/:group/user", s.v3handler.iam.ListGroupUsers)
v3.GET("/iam/group/:group/user/:name", s.v3handler.iam.GetGroupUser)
v3.PUT("/iam/group/:group/user/:name", s.v3handler.iam.UpdateGroupUser)
v3.DELETE("/iam/group/:group/user/:name", s.v3handler.iam.RemoveGroupUser)
} }
// v3 Restreamer // v3 Restreamer

View File

@ -130,7 +130,7 @@ func (am *access) ListPolicies(name, domain, resource string, actions []string)
} }
func (am *access) HasDomain(name string) bool { func (am *access) HasDomain(name string) bool {
groups := am.adapter.getAllGroups() groups := am.adapter.getAllDomains()
for _, g := range groups { for _, g := range groups {
if g == name { if g == name {
@ -142,7 +142,7 @@ func (am *access) HasDomain(name string) bool {
} }
func (am *access) ListDomains() []string { func (am *access) ListDomains() []string {
return am.adapter.getAllGroups() return am.adapter.getAllDomains()
} }
func (am *access) Enforce(name, domain, resource, action string) (bool, string) { func (am *access) Enforce(name, domain, resource, action string) (bool, string) {

View File

@ -20,7 +20,7 @@ type adapter struct {
fs fs.Filesystem fs fs.Filesystem
filePath string filePath string
logger log.Logger logger log.Logger
groups []Group domains []Domain
lock sync.Mutex lock sync.Mutex
} }
@ -56,7 +56,7 @@ func (a *adapter) LoadPolicy(model model.Model) error {
func (a *adapter) loadPolicyFile(model model.Model) error { func (a *adapter) loadPolicyFile(model model.Model) error {
if _, err := a.fs.Stat(a.filePath); os.IsNotExist(err) { if _, err := a.fs.Stat(a.filePath); os.IsNotExist(err) {
a.groups = []Group{} a.domains = []Domain{}
return nil return nil
} }
@ -65,18 +65,18 @@ func (a *adapter) loadPolicyFile(model model.Model) error {
return err return err
} }
groups := []Group{} domains := []Domain{}
err = json.Unmarshal(data, &groups) err = json.Unmarshal(data, &domains)
if err != nil { if err != nil {
return err return err
} }
rule := [5]string{} rule := [5]string{}
for _, group := range groups { for _, domain := range domains {
rule[0] = "p" rule[0] = "p"
rule[2] = group.Name rule[2] = domain.Name
for name, roles := range group.Roles { for name, roles := range domain.Roles {
rule[1] = "role:" + name rule[1] = "role:" + name
for _, role := range roles { for _, role := range roles {
rule[3] = role.Resource rule[3] = role.Resource
@ -88,7 +88,7 @@ func (a *adapter) loadPolicyFile(model model.Model) error {
} }
} }
for _, policy := range group.Policies { for _, policy := range domain.Policies {
rule[1] = policy.Username rule[1] = policy.Username
rule[3] = policy.Resource rule[3] = policy.Resource
rule[4] = formatActions(policy.Actions) rule[4] = formatActions(policy.Actions)
@ -99,9 +99,9 @@ func (a *adapter) loadPolicyFile(model model.Model) error {
} }
rule[0] = "g" rule[0] = "g"
rule[3] = group.Name rule[3] = domain.Name
for _, ug := range group.UserRoles { for _, ug := range domain.UserRoles {
rule[1] = ug.Username rule[1] = ug.Username
rule[2] = "role:" + ug.Role rule[2] = "role:" + ug.Role
@ -111,7 +111,7 @@ func (a *adapter) loadPolicyFile(model model.Model) error {
} }
} }
a.groups = groups a.domains = domains
return nil return nil
} }
@ -121,7 +121,7 @@ func (a *adapter) importPolicy(model model.Model, rule []string) error {
copy(copiedRule, rule) copy(copiedRule, rule)
a.logger.Debug().WithFields(log.Fields{ a.logger.Debug().WithFields(log.Fields{
"username": copiedRule[1], "subject": copiedRule[1],
"domain": copiedRule[2], "domain": copiedRule[2],
"resource": copiedRule[3], "resource": copiedRule[3],
"actions": copiedRule[4], "actions": copiedRule[4],
@ -149,7 +149,7 @@ func (a *adapter) SavePolicy(model model.Model) error {
} }
func (a *adapter) savePolicyFile() error { func (a *adapter) savePolicyFile() error {
jsondata, err := json.MarshalIndent(a.groups, "", " ") jsondata, err := json.MarshalIndent(a.domains, "", " ")
if err != nil { if err != nil {
return err return err
} }
@ -211,7 +211,7 @@ func (a *adapter) addPolicy(ptype string, rule []string) error {
actions = formatActions(rule[3]) actions = formatActions(rule[3])
a.logger.Debug().WithFields(log.Fields{ a.logger.Debug().WithFields(log.Fields{
"username": username, "subject": username,
"domain": domain, "domain": domain,
"resource": resource, "resource": resource,
"actions": actions, "actions": actions,
@ -222,47 +222,47 @@ func (a *adapter) addPolicy(ptype string, rule []string) error {
domain = rule[2] domain = rule[2]
a.logger.Debug().WithFields(log.Fields{ a.logger.Debug().WithFields(log.Fields{
"username": username, "subject": username,
"role": role, "role": role,
"domain": domain, "domain": domain,
}).Log("adding role mapping") }).Log("adding role mapping")
} else { } else {
return fmt.Errorf("unknown ptype: %s", ptype) return fmt.Errorf("unknown ptype: %s", ptype)
} }
var group *Group = nil var dom *Domain = nil
for i := range a.groups { for i := range a.domains {
if a.groups[i].Name == domain { if a.domains[i].Name == domain {
group = &a.groups[i] dom = &a.domains[i]
break break
} }
} }
if group == nil { if dom == nil {
g := Group{ g := Domain{
Name: domain, Name: domain,
Roles: map[string][]Role{}, Roles: map[string][]Role{},
UserRoles: []MapUserRole{}, UserRoles: []MapUserRole{},
Policies: []GroupPolicy{}, Policies: []DomainPolicy{},
} }
a.groups = append(a.groups, g) a.domains = append(a.domains, g)
group = &a.groups[len(a.groups)-1] dom = &a.domains[len(a.domains)-1]
} }
if ptype == "p" { if ptype == "p" {
if strings.HasPrefix(username, "role:") { if strings.HasPrefix(username, "role:") {
if group.Roles == nil { if dom.Roles == nil {
group.Roles = make(map[string][]Role) dom.Roles = make(map[string][]Role)
} }
role := strings.TrimPrefix(username, "role:") role := strings.TrimPrefix(username, "role:")
group.Roles[role] = append(group.Roles[role], Role{ dom.Roles[role] = append(dom.Roles[role], Role{
Resource: resource, Resource: resource,
Actions: actions, Actions: actions,
}) })
} else { } else {
group.Policies = append(group.Policies, GroupPolicy{ dom.Policies = append(dom.Policies, DomainPolicy{
Username: username, Username: username,
Role: Role{ Role: Role{
Resource: resource, Resource: resource,
@ -271,7 +271,7 @@ func (a *adapter) addPolicy(ptype string, rule []string) error {
}) })
} }
} else { } else {
group.UserRoles = append(group.UserRoles, MapUserRole{ dom.UserRoles = append(dom.UserRoles, MapUserRole{
Username: username, Username: username,
Role: strings.TrimPrefix(role, "role:"), Role: strings.TrimPrefix(role, "role:"),
}) })
@ -288,7 +288,7 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
var actions string var actions string
if ptype == "p" { if ptype == "p" {
if len(rule) != 4 { if len(rule) < 4 {
return false, fmt.Errorf("invalid rule length. must be 'user/role, domain, resource, actions'") return false, fmt.Errorf("invalid rule length. must be 'user/role, domain, resource, actions'")
} }
@ -297,6 +297,10 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
resource = rule[2] resource = rule[2]
actions = formatActions(rule[3]) actions = formatActions(rule[3])
} else if ptype == "g" { } else if ptype == "g" {
if len(rule) < 3 {
return false, fmt.Errorf("invalid rule length. must be 'user, role, domain'")
}
username = rule[0] username = rule[0]
role = rule[1] role = rule[1]
domain = rule[2] domain = rule[2]
@ -304,16 +308,16 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
return false, fmt.Errorf("unknown ptype: %s", ptype) return false, fmt.Errorf("unknown ptype: %s", ptype)
} }
var group *Group = nil var dom *Domain = nil
for i := range a.groups { for i := range a.domains {
if a.groups[i].Name == domain { if a.domains[i].Name == domain {
group = &a.groups[i] dom = &a.domains[i]
break break
} }
} }
if group == nil { if dom == nil {
// if we can't find any group with that name, then the policy doesn't exist // if we can't find any domain with that name, then the policy doesn't exist
return false, nil return false, nil
} }
@ -325,7 +329,7 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
} }
if isRole { if isRole {
roles, ok := group.Roles[username] roles, ok := dom.Roles[username]
if !ok { if !ok {
// unknown role, policy doesn't exist // unknown role, policy doesn't exist
return false, nil return false, nil
@ -337,7 +341,7 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
} }
} }
} else { } else {
for _, p := range group.Policies { for _, p := range dom.Policies {
if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions { if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions {
return true, nil return true, nil
} }
@ -345,7 +349,7 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) {
} }
} else { } else {
role = strings.TrimPrefix(role, "role:") role = strings.TrimPrefix(role, "role:")
for _, user := range group.UserRoles { for _, user := range dom.UserRoles {
if user.Username == username && user.Role == role { if user.Username == username && user.Role == role {
return true, nil return true, nil
} }
@ -407,7 +411,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
actions = formatActions(rule[3]) actions = formatActions(rule[3])
a.logger.Debug().WithFields(log.Fields{ a.logger.Debug().WithFields(log.Fields{
"username": username, "subject": username,
"domain": domain, "domain": domain,
"resource": resource, "resource": resource,
"actions": actions, "actions": actions,
@ -418,18 +422,18 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
domain = rule[2] domain = rule[2]
a.logger.Debug().WithFields(log.Fields{ a.logger.Debug().WithFields(log.Fields{
"username": username, "subject": username,
"role": role, "role": role,
"domain": domain, "domain": domain,
}).Log("adding role mapping") }).Log("adding role mapping")
} else { } else {
return fmt.Errorf("unknown ptype: %s", ptype) return fmt.Errorf("unknown ptype: %s", ptype)
} }
var group *Group = nil var dom *Domain = nil
for i := range a.groups { for i := range a.domains {
if a.groups[i].Name == domain { if a.domains[i].Name == domain {
group = &a.groups[i] dom = &a.domains[i]
break break
} }
} }
@ -442,7 +446,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
} }
if isRole { if isRole {
roles := group.Roles[username] roles := dom.Roles[username]
newRoles := []Role{} newRoles := []Role{}
@ -454,11 +458,11 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
newRoles = append(newRoles, role) newRoles = append(newRoles, role)
} }
group.Roles[username] = newRoles dom.Roles[username] = newRoles
} else { } else {
policies := []GroupPolicy{} policies := []DomainPolicy{}
for _, p := range group.Policies { for _, p := range dom.Policies {
if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions { if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions {
continue continue
} }
@ -466,14 +470,14 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
policies = append(policies, p) policies = append(policies, p)
} }
group.Policies = policies dom.Policies = policies
} }
} else { } else {
role = strings.TrimPrefix(role, "role:") role = strings.TrimPrefix(role, "role:")
users := []MapUserRole{} users := []MapUserRole{}
for _, user := range group.UserRoles { for _, user := range dom.UserRoles {
if user.Username == username && user.Role == role { if user.Username == username && user.Role == role {
continue continue
} }
@ -481,22 +485,22 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
users = append(users, user) users = append(users, user)
} }
group.UserRoles = users dom.UserRoles = users
} }
// Remove the group if there are no rules and policies // Remove the group if there are no rules and policies
if len(group.Roles) == 0 && len(group.UserRoles) == 0 && len(group.Policies) == 0 { if len(dom.Roles) == 0 && len(dom.UserRoles) == 0 && len(dom.Policies) == 0 {
groups := []Group{} groups := []Domain{}
for _, g := range a.groups { for _, g := range a.domains {
if g.Name == group.Name { if g.Name == dom.Name {
continue continue
} }
groups = append(groups, g) groups = append(groups, g)
} }
a.groups = groups a.domains = groups
} }
return nil return nil
@ -507,25 +511,25 @@ func (a *adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int,
return fmt.Errorf("not implemented") return fmt.Errorf("not implemented")
} }
func (a *adapter) getAllGroups() []string { func (a *adapter) getAllDomains() []string {
names := []string{} names := []string{}
for _, group := range a.groups { for _, domain := range a.domains {
if group.Name[0] == '$' { if domain.Name[0] == '$' {
continue continue
} }
names = append(names, group.Name) names = append(names, domain.Name)
} }
return names return names
} }
type Group struct { type Domain struct {
Name string `json:"name"` Name string `json:"name"`
Roles map[string][]Role `json:"roles"` Roles map[string][]Role `json:"roles"`
UserRoles []MapUserRole `json:"userroles"` UserRoles []MapUserRole `json:"userroles"`
Policies []GroupPolicy `json:"policies"` Policies []DomainPolicy `json:"policies"`
} }
type Role struct { type Role struct {
@ -538,7 +542,7 @@ type MapUserRole struct {
Role string `json:"role"` Role string `json:"role"`
} }
type GroupPolicy struct { type DomainPolicy struct {
Username string `json:"username"` Username string `json:"username"`
Role Role
} }

View File

@ -18,18 +18,18 @@ func TestAddPolicy(t *testing.T) {
err = a.AddPolicy("p", "p", []string{"foobar", "group", "resource", "action"}) err = a.AddPolicy("p", "p", []string{"foobar", "group", "resource", "action"})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(a.groups)) require.Equal(t, 1, len(a.domains))
data, err := memfs.ReadFile("/policy.json") data, err := memfs.ReadFile("/policy.json")
require.NoError(t, err) require.NoError(t, err)
g := []Group{} g := []Domain{}
err = json.Unmarshal(data, &g) err = json.Unmarshal(data, &g)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "group", g[0].Name) require.Equal(t, "group", g[0].Name)
require.Equal(t, 1, len(g[0].Policies)) require.Equal(t, 1, len(g[0].Policies))
require.Equal(t, GroupPolicy{ require.Equal(t, DomainPolicy{
Username: "foobar", Username: "foobar",
Role: Role{ Role: Role{
Resource: "resource", Resource: "resource",
@ -62,24 +62,24 @@ func TestRemovePolicy(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(a.groups)) require.Equal(t, 1, len(a.domains))
require.Equal(t, 2, len(a.groups[0].Policies)) require.Equal(t, 2, len(a.domains[0].Policies))
err = a.RemovePolicy("p", "p", []string{"foobar1", "group", "resource1", "action1"}) err = a.RemovePolicy("p", "p", []string{"foobar1", "group", "resource1", "action1"})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(a.groups)) require.Equal(t, 1, len(a.domains))
require.Equal(t, 1, len(a.groups[0].Policies)) require.Equal(t, 1, len(a.domains[0].Policies))
err = a.RemovePolicy("p", "p", []string{"foobar2", "group", "resource2", "action2"}) err = a.RemovePolicy("p", "p", []string{"foobar2", "group", "resource2", "action2"})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 0, len(a.groups)) require.Equal(t, 0, len(a.domains))
data, err := memfs.ReadFile("/policy.json") data, err := memfs.ReadFile("/policy.json")
require.NoError(t, err) require.NoError(t, err)
g := []Group{} g := []Domain{}
err = json.Unmarshal(data, &g) err = json.Unmarshal(data, &g)
require.NoError(t, err) require.NoError(t, err)

View File

@ -17,22 +17,7 @@ func resourceMatch(request, domain, policy string) bool {
var match bool var match bool
var err error var err error
if reqPrefix == "api" { if reqPrefix == "api" || reqPrefix == "fs" || reqPrefix == "rtmp" || reqPrefix == "srt" {
match, err = globMatch(polResource, reqResource, rune('/'))
if err != nil {
return false
}
} else if reqPrefix == "fs" {
match, err = globMatch(polResource, reqResource, rune('/'))
if err != nil {
return false
}
} else if reqPrefix == "rtmp" {
match, err = globMatch(polResource, reqResource, rune('/'))
if err != nil {
return false
}
} else if reqPrefix == "srt" {
match, err = globMatch(polResource, reqResource, rune('/')) match, err = globMatch(polResource, reqResource, rune('/'))
if err != nil { if err != nil {
return false return false
@ -83,17 +68,12 @@ func actionMatchFunc(args ...interface{}) (interface{}, error) {
} }
func getPrefix(s string) (string, string) { func getPrefix(s string) (string, string) {
splits := strings.SplitN(s, ":", 2) prefix, resource, found := strings.Cut(s, ":")
if !found {
if len(splits) == 0 { return "", s
return "", ""
} }
if len(splits) == 1 { return prefix, resource
return "", splits[0]
}
return splits[0], splits[1]
} }
func globMatch(pattern, name string, separators ...rune) (bool, error) { func globMatch(pattern, name string, separators ...rune) (bool, error) {