From ad2a50df6f76a0e599e993d56dbb8b8c068895da Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 18 Sep 2023 17:11:09 +0200 Subject: [PATCH] Separate resource type and resource for IAM policies --- app/api/api.go | 23 ++++++--- cluster/docs/ClusterAPI_docs.go | 6 +++ cluster/docs/ClusterAPI_swagger.json | 6 +++ cluster/docs/ClusterAPI_swagger.yaml | 4 ++ cluster/iam/iam.go | 16 +++--- docs/docs.go | 6 +++ docs/swagger.json | 6 +++ docs/swagger.yaml | 4 ++ http/api/iam.go | 3 ++ http/graph/resolver/playout.resolvers.go | 2 +- http/graph/resolver/process.resolvers.go | 6 +-- http/handler/api/cluster_iam.go | 34 ++++++------- http/handler/api/cluster_node.go | 2 +- http/handler/api/cluster_process.go | 28 +++++----- http/handler/api/cluster_store.go | 12 ++--- http/handler/api/iam.go | 44 ++++++++-------- http/handler/api/playout.go | 12 ++--- http/handler/api/restream.go | 32 ++++++------ http/handler/api/restream_test.go | 6 +-- http/middleware/iam/iam.go | 11 ++-- http/middleware/iam/iam_test.go | 18 +++---- iam/access/access.go | 65 ++++++++++++++++++------ iam/access/access_test.go | 32 +++++++----- iam/access/adapter.go | 22 ++++---- iam/access/adapter_test.go | 2 +- iam/access/functions.go | 22 ++++++-- iam/iam.go | 31 +++++------ restream/restream_test.go | 2 +- rtmp/rtmp.go | 8 +-- srt/srt.go | 4 +- 30 files changed, 283 insertions(+), 186 deletions(-) diff --git a/app/api/api.go b/app/api/api.go index f6a6b12e..8745dccf 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -696,19 +696,22 @@ func (a *api) start(ctx context.Context) error { { Name: "$anon", Domain: "$none", - Resource: "fs:/**", + Types: []string{"fs"}, + Resource: "/**", Actions: []string{"GET", "HEAD", "OPTIONS"}, }, { Name: "$anon", Domain: "$none", - Resource: "api:/api", + Types: []string{"api"}, + Resource: "/api", Actions: []string{"GET", "HEAD", "OPTIONS"}, }, { Name: "$anon", Domain: "$none", - Resource: "api:/api/v3/widget/process/**", + Types: []string{"api"}, + Resource: "/api/v3/widget/process/**", Actions: []string{"GET", "HEAD", "OPTIONS"}, }, } @@ -728,7 +731,8 @@ func (a *api) start(ctx context.Context) error { policies = append(policies, iamaccess.Policy{ Name: cfg.Storage.Memory.Auth.Username, Domain: "$none", - Resource: "fs:/memfs/**", + Types: []string{"fs"}, + Resource: "/memfs/**", Actions: []string{"ANY"}, }) } @@ -753,7 +757,8 @@ func (a *api) start(ctx context.Context) error { policies = append(policies, iamaccess.Policy{ Name: s.Auth.Username, Domain: "$none", - Resource: "fs:" + s.Mountpoint + "/**", + Types: []string{"fs"}, + Resource: s.Mountpoint + "/**", Actions: []string{"ANY"}, }) } @@ -763,7 +768,8 @@ func (a *api) start(ctx context.Context) error { policies = append(policies, iamaccess.Policy{ Name: "$anon", Domain: "$none", - Resource: "rtmp:/**", + Types: []string{"rtmp"}, + Resource: "/**", Actions: []string{"ANY"}, }) } @@ -772,7 +778,8 @@ func (a *api) start(ctx context.Context) error { policies = append(policies, iamaccess.Policy{ Name: "$anon", Domain: "$none", - Resource: "srt:**", + Types: []string{"srt"}, + Resource: "**", Actions: []string{"ANY"}, }) } @@ -789,7 +796,7 @@ func (a *api) start(ctx context.Context) error { } for _, policy := range policies { - manager.AddPolicy(policy.Name, policy.Domain, policy.Resource, policy.Actions) + manager.AddPolicy(policy.Name, policy.Domain, policy.Types, policy.Resource, policy.Actions) } } } diff --git a/cluster/docs/ClusterAPI_docs.go b/cluster/docs/ClusterAPI_docs.go index 04eb41dc..ea5c68b2 100644 --- a/cluster/docs/ClusterAPI_docs.go +++ b/cluster/docs/ClusterAPI_docs.go @@ -1142,6 +1142,12 @@ const docTemplateClusterAPI = `{ }, "resource": { "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } } } }, diff --git a/cluster/docs/ClusterAPI_swagger.json b/cluster/docs/ClusterAPI_swagger.json index a1701feb..99382802 100644 --- a/cluster/docs/ClusterAPI_swagger.json +++ b/cluster/docs/ClusterAPI_swagger.json @@ -1134,6 +1134,12 @@ }, "resource": { "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } } } }, diff --git a/cluster/docs/ClusterAPI_swagger.yaml b/cluster/docs/ClusterAPI_swagger.yaml index 2b1342fe..f8031bde 100644 --- a/cluster/docs/ClusterAPI_swagger.yaml +++ b/cluster/docs/ClusterAPI_swagger.yaml @@ -12,6 +12,10 @@ definitions: type: string resource: type: string + types: + items: + type: string + type: array type: object app.Config: properties: diff --git a/cluster/iam/iam.go b/cluster/iam/iam.go index 948217b4..fc57fdc0 100644 --- a/cluster/iam/iam.go +++ b/cluster/iam/iam.go @@ -60,8 +60,8 @@ func (m *manager) apply(op store.Operation) { } } -func (m *manager) Enforce(name, domain, resource, action string) bool { - return m.iam.Enforce(name, domain, resource, action) +func (m *manager) Enforce(name, domain, rtype, resource, action string) bool { + return m.iam.Enforce(name, domain, rtype, resource, action) } func (m *manager) HasDomain(domain string) bool { @@ -72,20 +72,20 @@ func (m *manager) ListDomains() []string { return m.iam.ListDomains() } -func (m *manager) HasPolicy(name, domain, resource string, actions []string) bool { - return m.iam.HasPolicy(name, domain, resource, actions) +func (m *manager) HasPolicy(name, domain string, types []string, resource string, actions []string) bool { + return m.iam.HasPolicy(name, domain, types, resource, actions) } -func (m *manager) AddPolicy(name, domain, resource string, actions []string) error { +func (m *manager) AddPolicy(name, domain string, types []string, resource string, actions []string) error { return ErrClusterMode } -func (m *manager) RemovePolicy(name, domain, resource string, actions []string) error { +func (m *manager) RemovePolicy(name, domain string, types []string, resource string, actions []string) error { return ErrClusterMode } -func (m *manager) ListPolicies(name, domain, resource string, actions []string) []access.Policy { - return m.iam.ListPolicies(name, domain, resource, actions) +func (m *manager) ListPolicies(name, domain string, types []string, resource string, actions []string) []access.Policy { + return m.iam.ListPolicies(name, domain, types, resource, actions) } func (m *manager) ReloadPolicies() error { diff --git a/docs/docs.go b/docs/docs.go index a58259fc..fcfc06c3 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5689,6 +5689,12 @@ const docTemplate = `{ }, "resource": { "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } } } }, diff --git a/docs/swagger.json b/docs/swagger.json index 037f1cf0..837f763f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -5681,6 +5681,12 @@ }, "resource": { "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index dcc719a2..d2233b3e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -770,6 +770,10 @@ definitions: type: string resource: type: string + types: + items: + type: string + type: array type: object api.IAMUser: properties: diff --git a/http/api/iam.go b/http/api/iam.go index 1632310d..3109e51f 100644 --- a/http/api/iam.go +++ b/http/api/iam.go @@ -45,6 +45,7 @@ func (u *IAMUser) Marshal(user identity.User, policies []access.Policy) { for _, p := range policies { u.Policies = append(u.Policies, IAMPolicy{ Domain: p.Domain, + Types: p.Types, Resource: p.Resource, Actions: p.Actions, }) @@ -84,6 +85,7 @@ func (u *IAMUser) Unmarshal() (identity.User, []access.Policy) { iampolicies = append(iampolicies, access.Policy{ Name: u.Name, Domain: p.Domain, + Types: p.Types, Resource: p.Resource, Actions: p.Actions, }) @@ -122,6 +124,7 @@ type IAMAuth0Tenant struct { type IAMPolicy struct { Name string `json:"name,omitempty"` Domain string `json:"domain"` + Types []string `json:"types"` Resource string `json:"resource"` Actions []string `json:"actions"` } diff --git a/http/graph/resolver/playout.resolvers.go b/http/graph/resolver/playout.resolvers.go index d0b77740..f4395f35 100644 --- a/http/graph/resolver/playout.resolvers.go +++ b/http/graph/resolver/playout.resolvers.go @@ -19,7 +19,7 @@ import ( func (r *queryResolver) PlayoutStatus(ctx context.Context, id string, domain string, input string) (*models.RawAVstream, error) { user, _ := ctx.Value("user").(string) - if !r.IAM.Enforce(user, domain, "process:"+id, "read") { + if !r.IAM.Enforce(user, domain, "process", id, "read") { return nil, fmt.Errorf("forbidden") } diff --git a/http/graph/resolver/process.resolvers.go b/http/graph/resolver/process.resolvers.go index 07f4b72b..47fd4bca 100644 --- a/http/graph/resolver/process.resolvers.go +++ b/http/graph/resolver/process.resolvers.go @@ -21,7 +21,7 @@ func (r *queryResolver) Processes(ctx context.Context, idpattern *string, refpat procs := []*models.Process{} for _, id := range ids { - if !r.IAM.Enforce(user, id.Domain, "process:"+id.ID, "read") { + if !r.IAM.Enforce(user, id.Domain, "process", id.ID, "read") { continue } @@ -40,7 +40,7 @@ func (r *queryResolver) Processes(ctx context.Context, idpattern *string, refpat func (r *queryResolver) Process(ctx context.Context, id string, domain string) (*models.Process, error) { user, _ := ctx.Value(GraphKey("user")).(string) - if !r.IAM.Enforce(user, domain, "process:"+id, "read") { + if !r.IAM.Enforce(user, domain, "process", id, "read") { return nil, fmt.Errorf("forbidden") } @@ -56,7 +56,7 @@ func (r *queryResolver) Process(ctx context.Context, id string, domain string) ( func (r *queryResolver) Probe(ctx context.Context, id string, domain string) (*models.Probe, error) { user, _ := ctx.Value(GraphKey("user")).(string) - if !r.IAM.Enforce(user, domain, "process:"+id, "write") { + if !r.IAM.Enforce(user, domain, "process", id, "write") { return nil, fmt.Errorf("forbidden") } diff --git a/http/handler/api/cluster_iam.go b/http/handler/api/cluster_iam.go index ec1941db..95271281 100644 --- a/http/handler/api/cluster_iam.go +++ b/http/handler/api/cluster_iam.go @@ -36,12 +36,12 @@ func (h *ClusterHandler) AddIdentity(c echo.Context) error { iamuser, iampolicies := user.Unmarshal() - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "Not allowed to create user '%s'", iamuser.Name) } for _, p := range iampolicies { - if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "Not allowed to write policy: %v", p) } } @@ -84,7 +84,7 @@ func (h *ClusterHandler) UpdateIdentity(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { return api.Err(http.StatusForbidden, "", "not allowed to modify this user") } @@ -102,7 +102,7 @@ func (h *ClusterHandler) UpdateIdentity(c echo.Context) error { } } - iampolicies := h.iam.ListPolicies(name, "", "", nil) + iampolicies := h.iam.ListPolicies(name, "", nil, "", nil) user := api.IAMUser{} user.Marshal(iamuser, iampolicies) @@ -113,12 +113,12 @@ func (h *ClusterHandler) UpdateIdentity(c echo.Context) error { iamuser, iampolicies = user.Unmarshal() - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "not allowed to create user '%s'", iamuser.Name) } for _, p := range iampolicies { - if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "not allowed to write policy: %v", p) } } @@ -165,7 +165,7 @@ func (h *ClusterHandler) UpdateIdentityPolicies(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { return api.Err(http.StatusForbidden, "", "not allowed to modify this user") } @@ -199,7 +199,7 @@ func (h *ClusterHandler) UpdateIdentityPolicies(c echo.Context) error { accessPolicies := []access.Policy{} for _, p := range policies { - if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "not allowed to write policy: %v", p) } @@ -265,17 +265,17 @@ func (h *ClusterHandler) ListIdentities(c echo.Context) error { users := make([]api.IAMUser, len(identities)+1) for i, iamuser := range identities { - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "read") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "read") { continue } - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") { iamuser = identity.User{ Name: iamuser.Name, } } - policies := h.iam.ListPolicies(iamuser.Name, "", "", nil) + policies := h.iam.ListPolicies(iamuser.Name, "", nil, "", nil) users[i].Marshal(iamuser, policies) } @@ -284,7 +284,7 @@ func (h *ClusterHandler) ListIdentities(c echo.Context) error { Name: "$anon", } - policies := h.iam.ListPolicies("$anon", "", "", nil) + policies := h.iam.ListPolicies("$anon", "", nil, "", nil) users[len(users)-1].Marshal(anon, policies) @@ -307,7 +307,7 @@ func (h *ClusterHandler) ListIdentity(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "read") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "read") { return api.Err(http.StatusForbidden, "", "Not allowed to access this user") } @@ -321,7 +321,7 @@ func (h *ClusterHandler) ListIdentity(c echo.Context) error { } if ctxuser != iamuser.Name { - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { iamuser = identity.User{ Name: iamuser.Name, } @@ -333,7 +333,7 @@ func (h *ClusterHandler) ListIdentity(c echo.Context) error { } } - iampolicies := h.iam.ListPolicies(name, "", "", nil) + iampolicies := h.iam.ListPolicies(name, "", nil, "", nil) user := api.IAMUser{} user.Marshal(iamuser, iampolicies) @@ -351,7 +351,7 @@ func (h *ClusterHandler) ListIdentity(c echo.Context) error { // @Security ApiKeyAuth // @Router /api/v3/cluster/iam/policies [get] func (h *ClusterHandler) ListPolicies(c echo.Context) error { - iampolicies := h.iam.ListPolicies("", "", "", nil) + iampolicies := h.iam.ListPolicies("", "", nil, "", nil) policies := []api.IAMPolicy{} @@ -385,7 +385,7 @@ func (h *ClusterHandler) RemoveIdentity(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "$none") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { return api.Err(http.StatusForbidden, "", "Not allowed to delete this user") } diff --git a/http/handler/api/cluster_node.go b/http/handler/api/cluster_node.go index c6c78d57..c69c9d02 100644 --- a/http/handler/api/cluster_node.go +++ b/http/handler/api/cluster_node.go @@ -194,7 +194,7 @@ func (h *ClusterHandler) ListNodeProcesses(c echo.Context) error { processes := []clientapi.Process{} for _, p := range procs { - if !h.iam.Enforce(ctxuser, domain, "process:"+p.Config.ID, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", p.Config.ID, "read") { continue } diff --git a/http/handler/api/cluster_process.go b/http/handler/api/cluster_process.go index 4f5a34e5..fc8c1dbf 100644 --- a/http/handler/api/cluster_process.go +++ b/http/handler/api/cluster_process.go @@ -65,7 +65,7 @@ func (h *ClusterHandler) GetAllProcesses(c echo.Context) error { pmap := map[app.ProcessID]struct{}{} for _, p := range procs { - if !h.iam.Enforce(ctxuser, domain, "process:"+p.ID, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", p.ID, "read") { continue } @@ -81,7 +81,7 @@ func (h *ClusterHandler) GetAllProcesses(c echo.Context) error { filtered := h.getFilteredStoreProcesses(processes, wantids, domain, reference, idpattern, refpattern, ownerpattern, domainpattern) for _, p := range filtered { - if !h.iam.Enforce(ctxuser, domain, "process:"+p.Config.ID, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", p.Config.ID, "read") { continue } @@ -313,7 +313,7 @@ func (h *ClusterHandler) GetProcess(c echo.Context) error { filter := newFilter(util.DefaultQuery(c, "filter", "")) domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "read") { return api.Err(http.StatusForbidden, "") } @@ -370,12 +370,12 @@ func (h *ClusterHandler) AddProcess(c echo.Context) error { return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error()) } - if !h.iam.Enforce(ctxuser, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(ctxuser, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "API user %s is not allowed to write this process in domain %s", ctxuser, process.Domain) } if !superuser { - if !h.iam.Enforce(process.Owner, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(process.Owner, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "user %s is not allowed to write this process in domain %s", process.Owner, process.Domain) } } @@ -431,7 +431,7 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error { Autostart: true, } - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "", "API user %s is not allowed to write the process in domain: %s", ctxuser, domain) } @@ -449,12 +449,12 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error { return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error()) } - if !h.iam.Enforce(ctxuser, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(ctxuser, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "API user %s is not allowed to write this process", ctxuser) } if !superuser { - if !h.iam.Enforce(process.Owner, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(process.Owner, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "user %s is not allowed to write this process", process.Owner) } } @@ -499,7 +499,7 @@ func (h *ClusterHandler) SetProcessCommand(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "", "API user %s is not allowed to write the process in domain: %s", ctxuser, domain) } @@ -552,7 +552,7 @@ func (h *ClusterHandler) SetProcessMetadata(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "", "API user %s is not allowed to write the process in domain: %s", ctxuser, domain) } @@ -599,7 +599,7 @@ func (h *ClusterHandler) GetProcessMetadata(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "read") { return api.Err(http.StatusForbidden, "") } @@ -633,7 +633,7 @@ func (h *ClusterHandler) ProbeProcess(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "") } @@ -684,7 +684,7 @@ func (h *ClusterHandler) ProbeProcessConfig(c echo.Context) error { return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error()) } - if !h.iam.Enforce(ctxuser, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(ctxuser, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "API user %s is not allowed to write this process in domain %s", ctxuser, process.Domain) } @@ -729,7 +729,7 @@ func (h *ClusterHandler) DeleteProcess(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "") id := util.PathParam(c, "id") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "", "API user %s is not allowed to write the process in domain: %s", ctxuser, domain) } diff --git a/http/handler/api/cluster_store.go b/http/handler/api/cluster_store.go index 6682dc5f..d2a56798 100644 --- a/http/handler/api/cluster_store.go +++ b/http/handler/api/cluster_store.go @@ -29,7 +29,7 @@ func (h *ClusterHandler) ListStoreProcesses(c echo.Context) error { processes := []api.Process{} for _, p := range procs { - if !h.iam.Enforce(ctxuser, p.Config.Domain, "process:"+p.Config.ID, "read") { + if !h.iam.Enforce(ctxuser, p.Config.Domain, "process", p.Config.ID, "read") { continue } @@ -62,7 +62,7 @@ func (h *ClusterHandler) GetStoreProcess(c echo.Context) error { Domain: domain, } - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "read") { return api.Err(http.StatusForbidden, "", "API user %s is not allowed to read this process", ctxuser) } @@ -109,11 +109,11 @@ func (h *ClusterHandler) ListStoreIdentities(c echo.Context) error { users := make([]api.IAMUser, len(identities)) for i, iamuser := range identities { - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "read") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "read") { continue } - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") { iamuser = identity.User{ Name: iamuser.Name, } @@ -144,7 +144,7 @@ func (h *ClusterHandler) ListStoreIdentity(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "read") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "read") { return api.Err(http.StatusForbidden, "", "Not allowed to access this user") } @@ -159,7 +159,7 @@ func (h *ClusterHandler) ListStoreIdentity(c echo.Context) error { } if ctxuser != iamuser.Name { - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { iamuser = identity.User{ Name: iamuser.Name, } diff --git a/http/handler/api/iam.go b/http/handler/api/iam.go index 1280a03a..731d809e 100644 --- a/http/handler/api/iam.go +++ b/http/handler/api/iam.go @@ -49,12 +49,12 @@ func (h *IAMHandler) AddIdentity(c echo.Context) error { iamuser, iampolicies := user.Unmarshal() - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "not allowed to create user '%s'", iamuser.Name) } for _, p := range iampolicies { - if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "not allowed to write policy: %v", p) } } @@ -69,7 +69,7 @@ func (h *IAMHandler) AddIdentity(c echo.Context) error { } for _, p := range iampolicies { - h.iam.AddPolicy(p.Name, p.Domain, p.Resource, p.Actions) + h.iam.AddPolicy(p.Name, p.Domain, p.Types, p.Resource, p.Actions) } return c.JSON(http.StatusOK, user) @@ -95,7 +95,7 @@ func (h *IAMHandler) RemoveIdentity(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "$none") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { return api.Err(http.StatusForbidden, "", "Not allowed to delete this user") } @@ -114,7 +114,7 @@ func (h *IAMHandler) RemoveIdentity(c echo.Context) error { } // Remove all policies of that user - if err := h.iam.RemovePolicy(name, "", "", nil); err != nil { + if err := h.iam.RemovePolicy(name, "", nil, "", nil); err != nil { return api.Err(http.StatusBadRequest, "", "%s", err.Error()) } @@ -144,7 +144,7 @@ func (h *IAMHandler) UpdateIdentity(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "$none") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { return api.Err(http.StatusForbidden, "", "Not allowed to modify this user") } @@ -162,7 +162,7 @@ func (h *IAMHandler) UpdateIdentity(c echo.Context) error { } } - iampolicies := h.iam.ListPolicies(name, "", "", nil) + iampolicies := h.iam.ListPolicies(name, "", nil, "", nil) user := api.IAMUser{} user.Marshal(iamuser, iampolicies) @@ -173,12 +173,12 @@ func (h *IAMHandler) UpdateIdentity(c echo.Context) error { iamuser, iampolicies = user.Unmarshal() - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "Not allowed to create user '%s'", iamuser.Name) } for _, p := range iampolicies { - if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "Not allowed to write policy: %v", p) } } @@ -194,12 +194,12 @@ func (h *IAMHandler) UpdateIdentity(c echo.Context) error { } } - if err := h.iam.RemovePolicy(name, "", "", nil); err != nil { + if err := h.iam.RemovePolicy(name, "", nil, "", nil); err != nil { return api.Err(http.StatusBadRequest, "", "%s", err.Error()) } for _, p := range iampolicies { - h.iam.AddPolicy(p.Name, p.Domain, p.Resource, p.Actions) + h.iam.AddPolicy(p.Name, p.Domain, p.Types, p.Resource, p.Actions) } return c.JSON(http.StatusOK, user) @@ -228,7 +228,7 @@ func (h *IAMHandler) UpdateIdentityPolicies(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "$none") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { return api.Err(http.StatusForbidden, "", "Not allowed to modify this user") } @@ -260,7 +260,7 @@ func (h *IAMHandler) UpdateIdentityPolicies(c echo.Context) error { } for _, p := range policies { - if !h.iam.Enforce(ctxuser, p.Domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") { return api.Err(http.StatusForbidden, "", "not allowed to write policy: %v", p) } } @@ -269,12 +269,12 @@ func (h *IAMHandler) UpdateIdentityPolicies(c echo.Context) error { return api.Err(http.StatusForbidden, "", "only superusers can modify superusers") } - if err := h.iam.RemovePolicy(name, "", "", nil); err != nil { + if err := h.iam.RemovePolicy(name, "", nil, "", nil); err != nil { return api.Err(http.StatusBadRequest, "", "%s", err.Error()) } for _, p := range policies { - h.iam.AddPolicy(iamuser.Name, p.Domain, p.Resource, p.Actions) + h.iam.AddPolicy(iamuser.Name, p.Domain, p.Types, p.Resource, p.Actions) } return c.JSON(http.StatusOK, policies) @@ -298,17 +298,17 @@ func (h *IAMHandler) ListIdentities(c echo.Context) error { users := make([]api.IAMUser, len(identities)+1) for i, iamuser := range identities { - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "read") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "read") { continue } - if !h.iam.Enforce(ctxuser, domain, "iam:"+iamuser.Name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") { iamuser = identity.User{ Name: iamuser.Name, } } - policies := h.iam.ListPolicies(iamuser.Name, "", "", nil) + policies := h.iam.ListPolicies(iamuser.Name, "", nil, "", nil) users[i].Marshal(iamuser, policies) } @@ -317,7 +317,7 @@ func (h *IAMHandler) ListIdentities(c echo.Context) error { Name: "$anon", } - policies := h.iam.ListPolicies("$anon", "", "", nil) + policies := h.iam.ListPolicies("$anon", "", nil, "", nil) users[len(users)-1].Marshal(anon, policies) @@ -342,7 +342,7 @@ func (h *IAMHandler) GetIdentity(c echo.Context) error { domain := util.DefaultQuery(c, "domain", "$none") name := util.PathParam(c, "name") - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "read") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "read") { return api.Err(http.StatusForbidden, "", "not allowed to access this user") } @@ -356,7 +356,7 @@ func (h *IAMHandler) GetIdentity(c echo.Context) error { } if ctxuser != iamuser.Name { - if !h.iam.Enforce(ctxuser, domain, "iam:"+name, "write") { + if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") { iamuser = identity.User{ Name: iamuser.Name, } @@ -368,7 +368,7 @@ func (h *IAMHandler) GetIdentity(c echo.Context) error { } } - iampolicies := h.iam.ListPolicies(name, "", "", nil) + iampolicies := h.iam.ListPolicies(name, "", nil, "", nil) user := api.IAMUser{} user.Marshal(iamuser, iampolicies) diff --git a/http/handler/api/playout.go b/http/handler/api/playout.go index 99d513bb..b4b9e382 100644 --- a/http/handler/api/playout.go +++ b/http/handler/api/playout.go @@ -35,7 +35,7 @@ func (h *RestreamHandler) PlayoutStatus(c echo.Context) error { user := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(user, domain, "process:"+id, "read") { + if !h.iam.Enforce(user, domain, "process:", id, "read") { return api.Err(http.StatusForbidden, "") } @@ -104,7 +104,7 @@ func (h *RestreamHandler) PlayoutKeyframe(c echo.Context) error { user := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(user, domain, "process:"+id, "read") { + if !h.iam.Enforce(user, domain, "process:", id, "read") { return api.Err(http.StatusForbidden, "") } @@ -162,7 +162,7 @@ func (h *RestreamHandler) PlayoutEncodeErrorframe(c echo.Context) error { user := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(user, domain, "process:"+id, "write") { + if !h.iam.Enforce(user, domain, "process:", id, "write") { return api.Err(http.StatusForbidden, "") } @@ -217,7 +217,7 @@ func (h *RestreamHandler) PlayoutSetErrorframe(c echo.Context) error { user := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(user, domain, "process:"+id, "write") { + if !h.iam.Enforce(user, domain, "process:", id, "write") { return api.Err(http.StatusForbidden, "") } @@ -273,7 +273,7 @@ func (h *RestreamHandler) PlayoutReopenInput(c echo.Context) error { user := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(user, domain, "process:"+id, "write") { + if !h.iam.Enforce(user, domain, "process:", id, "write") { return api.Err(http.StatusForbidden, "Forbidden") } @@ -327,7 +327,7 @@ func (h *RestreamHandler) PlayoutSetStream(c echo.Context) error { user := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(user, domain, "process:"+id, "write") { + if !h.iam.Enforce(user, domain, "process:", id, "write") { return api.Err(http.StatusForbidden, "") } diff --git a/http/handler/api/restream.go b/http/handler/api/restream.go index 49f4920b..d7089902 100644 --- a/http/handler/api/restream.go +++ b/http/handler/api/restream.go @@ -59,12 +59,12 @@ func (h *RestreamHandler) Add(c echo.Context) error { return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error()) } - if !h.iam.Enforce(ctxuser, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(ctxuser, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "You are not allowed to write this process, check the domain and process ID") } if !superuser { - if !h.iam.Enforce(process.Owner, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(process.Owner, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "The owner '%s' is not allowed to write this process", process.Owner) } } @@ -131,7 +131,7 @@ func (h *RestreamHandler) GetAll(c echo.Context) error { ids := []app.ProcessID{} for _, id := range preids { - if !h.iam.Enforce(ctxuser, domain, "process:"+id.ID, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id.ID, "read") { continue } @@ -184,7 +184,7 @@ func (h *RestreamHandler) Get(c echo.Context) error { filter := util.DefaultQuery(c, "filter", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "read") { return api.Err(http.StatusForbidden, "Forbidden") } @@ -226,7 +226,7 @@ func (h *RestreamHandler) Delete(c echo.Context) error { } if !superuser { - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "") } } @@ -271,7 +271,7 @@ func (h *RestreamHandler) Update(c echo.Context) error { Autostart: true, } - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "", "You are not allowed to write this process: %s", id) } @@ -292,12 +292,12 @@ func (h *RestreamHandler) Update(c echo.Context) error { return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error()) } - if !h.iam.Enforce(ctxuser, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(ctxuser, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "You are not allowed to write this process: %s", process.ID) } if !superuser { - if !h.iam.Enforce(process.Owner, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(process.Owner, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "The owner '%s' is not allowed to write this process: %s", process.Owner, process.ID) } } @@ -352,7 +352,7 @@ func (h *RestreamHandler) Command(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "") } @@ -406,7 +406,7 @@ func (h *RestreamHandler) GetConfig(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "read") { return api.Err(http.StatusForbidden, "") } @@ -445,7 +445,7 @@ func (h *RestreamHandler) GetState(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "read") { return api.Err(http.StatusForbidden, "") } @@ -507,7 +507,7 @@ func (h *RestreamHandler) GetReport(c echo.Context) error { } } - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "read") { return api.Err(http.StatusForbidden, "") } @@ -651,7 +651,7 @@ func (h *RestreamHandler) Probe(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "") } @@ -698,7 +698,7 @@ func (h *RestreamHandler) ProbeConfig(c echo.Context) error { return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error()) } - if !h.iam.Enforce(ctxuser, process.Domain, "process:"+process.ID, "write") { + if !h.iam.Enforce(ctxuser, process.Domain, "process", process.ID, "write") { return api.Err(http.StatusForbidden, "", "You are not allowed to probe this process, check the domain and process ID") } @@ -778,7 +778,7 @@ func (h *RestreamHandler) GetProcessMetadata(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "read") { return api.Err(http.StatusForbidden, "") } @@ -821,7 +821,7 @@ func (h *RestreamHandler) SetProcessMetadata(c echo.Context) error { ctxuser := util.DefaultContext(c, "user", "") domain := util.DefaultQuery(c, "domain", "") - if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") { + if !h.iam.Enforce(ctxuser, domain, "process", id, "write") { return api.Err(http.StatusForbidden, "") } diff --git a/http/handler/api/restream_test.go b/http/handler/api/restream_test.go index e3202bf2..59f1ee0f 100644 --- a/http/handler/api/restream_test.go +++ b/http/handler/api/restream_test.go @@ -61,9 +61,9 @@ func getDummyRestreamHandler() (*RestreamHandler, error) { return nil, err } - iam.AddPolicy("$anon", "$none", "api:/**", []string{"ANY"}) - iam.AddPolicy("$anon", "$none", "fs:/**", []string{"ANY"}) - iam.AddPolicy("$anon", "$none", "process:**", []string{"ANY"}) + iam.AddPolicy("$anon", "$none", []string{"api"}, "/**", []string{"ANY"}) + iam.AddPolicy("$anon", "$none", []string{"fs"}, "/**", []string{"ANY"}) + iam.AddPolicy("$anon", "$none", []string{"process"}, "**", []string{"ANY"}) handler := NewRestream(rs, iam) diff --git a/http/middleware/iam/iam.go b/http/middleware/iam/iam.go index b92ba286..33855428 100644 --- a/http/middleware/iam/iam.go +++ b/http/middleware/iam/iam.go @@ -124,6 +124,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { username := "$anon" resource := c.Request().URL.Path + rtype := "fs" var domain string c.Set("user", username) @@ -184,7 +185,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { } domain = c.QueryParam("domain") - resource = "api:" + resource + rtype = "api" } else { identity, err = mw.findIdentityFromSession(c) if err != nil { @@ -215,7 +216,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { } domain = mw.findDomainFromFilesystem(resource) - resource = "fs:" + resource + rtype = "fs" } superuser := false @@ -234,11 +235,11 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { action := c.Request().Method - if username == "$anon" && resource == "api:/api" { + if username == "$anon" && rtype == "api" && resource == "/api" { return next(c) } - if !config.IAM.Enforce(username, domain, resource, action) { + if !config.IAM.Enforce(username, domain, rtype, resource, action) { return api.Err(http.StatusForbidden, "Forbidden", "access denied") } @@ -263,7 +264,7 @@ func (m *iammiddleware) findIdentityFromBasicAuth(c echo.Context) (iamidentity.V domain = "$none" } - if !m.iam.Enforce("$anon", domain, "fs:"+path, c.Request().Method) { + if !m.iam.Enforce("$anon", domain, "fs", path, c.Request().Method) { return nil, ErrAuthRequired } diff --git a/http/middleware/iam/iam_test.go b/http/middleware/iam/iam_test.go index 6447ac96..63937472 100644 --- a/http/middleware/iam/iam_test.go +++ b/http/middleware/iam/iam_test.go @@ -101,7 +101,7 @@ func TestBasicAuth(t *testing.T) { iam, err := getIAM() require.NoError(t, err) - iam.AddPolicy("foobar", "$none", "fs:/**", []string{"ANY"}) + iam.AddPolicy("foobar", "$none", []string{"fs"}, "/**", []string{"ANY"}) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) @@ -160,7 +160,7 @@ func TestBasicAuthEncoding(t *testing.T) { iam, err := getIAM() require.NoError(t, err) - iam.AddPolicy("foobar:2", "$none", "fs:/**", []string{"ANY"}) + iam.AddPolicy("foobar:2", "$none", []string{"fs"}, "/**", []string{"ANY"}) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) @@ -196,9 +196,9 @@ func TestFindDomainFromFilesystem(t *testing.T) { iam, err := getIAM() require.NoError(t, err) - iam.AddPolicy("$anon", "$none", "fs:/**", []string{"ANY"}) - iam.AddPolicy("foobar", "group", "fs:/group/**", []string{"ANY"}) - iam.AddPolicy("foobar", "anothergroup", "fs:/memfs/anothergroup/**", []string{"ANY"}) + iam.AddPolicy("$anon", "$none", []string{"fs"}, "/**", []string{"ANY"}) + iam.AddPolicy("foobar", "group", []string{"fs"}, "/group/**", []string{"ANY"}) + iam.AddPolicy("foobar", "anothergroup", []string{"fs"}, "/memfs/anothergroup/**", []string{"ANY"}) mw := &iammiddleware{ iam: iam, @@ -222,8 +222,8 @@ func TestBasicAuthDomain(t *testing.T) { iam, err := getIAM() require.NoError(t, err) - iam.AddPolicy("$anon", "$none", "fs:/**", []string{"ANY"}) - iam.AddPolicy("foobar", "group", "fs:/group/**", []string{"ANY"}) + iam.AddPolicy("$anon", "$none", []string{"fs"}, "/**", []string{"ANY"}) + iam.AddPolicy("foobar", "group", []string{"fs"}, "/group/**", []string{"ANY"}) e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) @@ -255,7 +255,7 @@ func TestBasicAuthDomain(t *testing.T) { require.NoError(t, h(c)) // Allow anonymous group read access - iam.AddPolicy("$anon", "group", "fs:/group/**", []string{"GET"}) + iam.AddPolicy("$anon", "group", []string{"fs"}, "/group/**", []string{"GET"}) req.Header.Del(echo.HeaderAuthorization) require.NoError(t, h(c)) @@ -265,7 +265,7 @@ func TestAPILoginAndRefresh(t *testing.T) { iam, err := getIAM() require.NoError(t, err) - iam.AddPolicy("foobar", "$none", "api:/**", []string{"ANY"}) + iam.AddPolicy("foobar", "$none", []string{"api"}, "/**", []string{"ANY"}) jwthandler := apihandler.NewJWT(iam) diff --git a/iam/access/access.go b/iam/access/access.go index 331bdb40..4be22631 100644 --- a/iam/access/access.go +++ b/iam/access/access.go @@ -1,6 +1,7 @@ package access import ( + "sort" "strings" "github.com/datarhei/core/v16/log" @@ -12,12 +13,13 @@ import ( type Policy struct { Name string Domain string + Types []string Resource string Actions []string } type Enforcer interface { - Enforce(name, domain, resource, action string) (bool, string) + Enforce(name, domain, rtype, resource, action string) (bool, string) HasDomain(name string) bool ListDomains() []string @@ -26,10 +28,10 @@ type Enforcer interface { type Manager interface { Enforcer - HasPolicy(name, domain, resource string, actions []string) bool - AddPolicy(name, domain, resource string, actions []string) error - RemovePolicy(name, domain, resource string, actions []string) error - ListPolicies(name, domain, resource string, actions []string) []Policy + HasPolicy(name, domain string, types []string, resource string, actions []string) bool + AddPolicy(name, domain string, types []string, resource string, actions []string) error + RemovePolicy(name, domain string, types []string, resource string, actions []string) error + ListPolicies(name, domain string, types []string, resource string, actions []string) []Policy ReloadPolicies() error } @@ -77,14 +79,14 @@ func New(config Config) (Manager, error) { return am, nil } -func (am *access) HasPolicy(name, domain, resource string, actions []string) bool { - policy := []string{name, domain, resource, strings.Join(actions, "|")} +func (am *access) HasPolicy(name, domain string, types []string, resource string, actions []string) bool { + policy := []string{name, domain, encodeResource(types, resource), encodeActions(actions)} return am.enforcer.HasPolicy(policy) } -func (am *access) AddPolicy(name, domain, resource string, actions []string) error { - policy := []string{name, domain, resource, strings.Join(actions, "|")} +func (am *access) AddPolicy(name, domain string, types []string, resource string, actions []string) error { + policy := []string{name, domain, encodeResource(types, resource), encodeActions(actions)} if am.enforcer.HasPolicy(policy) { return nil @@ -95,24 +97,26 @@ func (am *access) AddPolicy(name, domain, resource string, actions []string) err return err } -func (am *access) RemovePolicy(name, domain, resource string, actions []string) error { - policies := am.enforcer.GetFilteredPolicy(0, name, domain, resource, strings.Join(actions, "|")) +func (am *access) RemovePolicy(name, domain string, types []string, resource string, actions []string) error { + policies := am.enforcer.GetFilteredPolicy(0, name, domain, encodeResource(types, resource), encodeActions(actions)) _, err := am.enforcer.RemovePolicies(policies) return err } -func (am *access) ListPolicies(name, domain, resource string, actions []string) []Policy { +func (am *access) ListPolicies(name, domain string, types []string, resource string, actions []string) []Policy { policies := []Policy{} - ps := am.enforcer.GetFilteredPolicy(0, name, domain, resource, strings.Join(actions, "|")) + ps := am.enforcer.GetFilteredPolicy(0, name, domain, encodeResource(types, resource), encodeActions(actions)) for _, p := range ps { + types, resource := decodeResource(p[2]) policies = append(policies, Policy{ Name: p[0], Domain: p[1], - Resource: p[2], - Actions: strings.Split(p[3], "|"), + Types: types, + Resource: resource, + Actions: decodeActions(p[3]), }) } @@ -133,8 +137,37 @@ func (am *access) ListDomains() []string { return am.adapter.AllDomains() } -func (am *access) Enforce(name, domain, resource, action string) (bool, string) { +func (am *access) Enforce(name, domain, rtype, resource, action string) (bool, string) { + resource = rtype + ":" + resource + ok, rule, _ := am.enforcer.EnforceEx(name, domain, resource, action) return ok, strings.Join(rule, ", ") } + +func encodeActions(actions []string) string { + return strings.Join(actions, "|") +} + +func decodeActions(actions string) []string { + return strings.Split(actions, "|") +} + +func encodeResource(types []string, resource string) string { + if len(types) == 0 { + return resource + } + + sort.Strings(types) + + return strings.Join(types, "|") + ":" + resource +} + +func decodeResource(resource string) ([]string, string) { + before, after, found := strings.Cut(resource, ":") + if !found { + return []string{}, resource + } + + return strings.Split(before, "|"), after +} diff --git a/iam/access/access_test.go b/iam/access/access_test.go index 47bb9d43..cf2c9dd2 100644 --- a/iam/access/access_test.go +++ b/iam/access/access_test.go @@ -26,42 +26,47 @@ func TestAccessManager(t *testing.T) { }) require.NoError(t, err) - policies := am.ListPolicies("", "", "", nil) + policies := am.ListPolicies("", "", nil, "", nil) require.ElementsMatch(t, []Policy{ { Name: "ingo", Domain: "$none", - Resource: "rtmp:/bla-*", + Types: []string{"rtmp"}, + Resource: "/bla-*", Actions: []string{"play", "publish"}, }, { Name: "ingo", Domain: "igelcamp", - Resource: "rtmp:/igelcamp/**", + Types: []string{"rtmp"}, + Resource: "/igelcamp/**", Actions: []string{"publish"}, }, }, policies) - am.AddPolicy("foobar", "group", "bla:/", []string{"write"}) + am.AddPolicy("foobar", "group", []string{"bla"}, "/", []string{"write"}) - policies = am.ListPolicies("", "", "", nil) + policies = am.ListPolicies("", "", nil, "", nil) require.ElementsMatch(t, []Policy{ { Name: "ingo", Domain: "$none", - Resource: "rtmp:/bla-*", + Types: []string{"rtmp"}, + Resource: "/bla-*", Actions: []string{"play", "publish"}, }, { Name: "ingo", Domain: "igelcamp", - Resource: "rtmp:/igelcamp/**", + Types: []string{"rtmp"}, + Resource: "/igelcamp/**", Actions: []string{"publish"}, }, { Name: "foobar", Domain: "group", - Resource: "bla:/", + Types: []string{"bla"}, + Resource: "/", Actions: []string{"write"}, }, }, policies) @@ -70,14 +75,15 @@ func TestAccessManager(t *testing.T) { require.True(t, am.HasDomain("group")) require.False(t, am.HasDomain("$none")) - am.RemovePolicy("ingo", "", "", nil) + am.RemovePolicy("ingo", "", nil, "", nil) - policies = am.ListPolicies("", "", "", nil) + policies = am.ListPolicies("", "", nil, "", nil) require.ElementsMatch(t, []Policy{ { Name: "foobar", Domain: "group", - Resource: "bla:/", + Types: []string{"bla"}, + Resource: "/", Actions: []string{"write"}, }, }, policies) @@ -86,9 +92,9 @@ func TestAccessManager(t *testing.T) { require.True(t, am.HasDomain("group")) require.False(t, am.HasDomain("$none")) - ok, _ := am.Enforce("foobar", "group", "bla:/", "read") + ok, _ := am.Enforce("foobar", "group", "bla", "/", "read") require.False(t, ok) - ok, _ = am.Enforce("foobar", "group", "bla:/", "write") + ok, _ = am.Enforce("foobar", "group", "bla", "/", "write") require.True(t, ok) } diff --git a/iam/access/adapter.go b/iam/access/adapter.go index e444e793..be24adf7 100644 --- a/iam/access/adapter.go +++ b/iam/access/adapter.go @@ -92,7 +92,7 @@ func (a *adapter) loadPolicyFile(model model.Model) error { rule[1] = "role:" + name for _, role := range roles { rule[3] = role.Resource - rule[4] = formatActions(role.Actions) + rule[4] = formatList(role.Actions) if err := a.importPolicy(model, rule[0:5]); err != nil { return err @@ -103,7 +103,7 @@ func (a *adapter) loadPolicyFile(model model.Model) error { for _, policy := range domain.Policies { rule[1] = policy.Username rule[3] = policy.Resource - rule[4] = formatActions(policy.Actions) + rule[4] = formatList(policy.Actions) if err := a.importPolicy(model, rule[0:5]); err != nil { return err @@ -220,7 +220,7 @@ func (a *adapter) addPolicy(ptype string, rule []string) error { username = rule[0] domain = rule[1] resource = rule[2] - actions = formatActions(rule[3]) + actions = formatList(rule[3]) a.logger.Debug().WithFields(log.Fields{ "subject": username, @@ -307,7 +307,7 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) { username = rule[0] domain = rule[1] resource = rule[2] - actions = formatActions(rule[3]) + actions = formatList(rule[3]) } else if ptype == "g" { if len(rule) < 3 { return false, fmt.Errorf("invalid rule length. must be 'user, role, domain'") @@ -348,13 +348,13 @@ func (a *adapter) hasPolicy(ptype string, rule []string) (bool, error) { } for _, role := range roles { - if role.Resource == resource && formatActions(role.Actions) == actions { + if role.Resource == resource && formatList(role.Actions) == actions { return true, nil } } } else { for _, p := range dom.Policies { - if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions { + if p.Username == username && p.Resource == resource && formatList(p.Actions) == actions { return true, nil } } @@ -420,7 +420,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error { username = rule[0] domain = rule[1] resource = rule[2] - actions = formatActions(rule[3]) + actions = formatList(rule[3]) a.logger.Debug().WithFields(log.Fields{ "subject": username, @@ -463,7 +463,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error { newRoles := []Role{} for _, role := range roles { - if role.Resource == resource && formatActions(role.Actions) == actions { + if role.Resource == resource && formatList(role.Actions) == actions { continue } @@ -475,7 +475,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error { policies := []DomainPolicy{} for _, p := range dom.Policies { - if p.Username == username && p.Resource == resource && formatActions(p.Actions) == actions { + if p.Username == username && p.Resource == resource && formatList(p.Actions) == actions { continue } @@ -579,8 +579,8 @@ type DomainPolicy struct { Role } -func formatActions(actions string) string { - a := strings.Split(actions, "|") +func formatList(list string) string { + a := strings.Split(list, "|") sort.Strings(a) diff --git a/iam/access/adapter_test.go b/iam/access/adapter_test.go index 1a6dc287..10e46d9f 100644 --- a/iam/access/adapter_test.go +++ b/iam/access/adapter_test.go @@ -48,7 +48,7 @@ func TestFormatActions(t *testing.T) { } for _, d := range data { - require.Equal(t, d[1], formatActions(d[0]), d[0]) + require.Equal(t, d[1], formatList(d[0]), d[0]) } } diff --git a/iam/access/functions.go b/iam/access/functions.go index c455dbd9..5cc61b1e 100644 --- a/iam/access/functions.go +++ b/iam/access/functions.go @@ -10,14 +10,28 @@ func resourceMatch(request, policy string) bool { reqPrefix, reqResource := getPrefix(request) polPrefix, polResource := getPrefix(policy) - if reqPrefix != polPrefix { + var match bool = false + var err error = nil + + reqType := strings.ToLower(reqPrefix) + polTypes := strings.Split(strings.ToLower(polPrefix), "|") + + for _, polType := range polTypes { + if reqType != polType { + continue + } + + match = true + break + } + + if !match { return false } - var match bool - var err error + match = false - if reqPrefix == "api" || reqPrefix == "fs" || reqPrefix == "rtmp" || reqPrefix == "srt" { + if reqType == "api" || reqType == "fs" || reqType == "rtmp" || reqType == "srt" { match, err = glob.Match(polResource, reqResource, rune('/')) if err != nil { return false diff --git a/iam/iam.go b/iam/iam.go index 0e8c3b13..6b22404b 100644 --- a/iam/iam.go +++ b/iam/iam.go @@ -7,7 +7,7 @@ import ( ) type Enforcer interface { - Enforce(name, domain, resource, action string) bool + Enforce(name, domain, rtype, resource, action string) bool } type IAM interface { @@ -16,10 +16,10 @@ type IAM interface { HasDomain(domain string) bool ListDomains() []string - HasPolicy(name, domain, resource string, actions []string) bool - AddPolicy(name, domain, resource string, actions []string) error - RemovePolicy(name, domain, resource string, actions []string) error - ListPolicies(name, domain, resource string, actions []string) []access.Policy + HasPolicy(name, domain string, types []string, resource string, actions []string) bool + AddPolicy(name, domain string, types []string, resource string, actions []string) error + RemovePolicy(name, domain string, types []string, resource string, actions []string) error + ListPolicies(name, domain string, types []string, resource string, actions []string) []access.Policy ReloadPolicies() error Validators() []string @@ -96,7 +96,7 @@ func (i *iam) Close() { i.am = nil } -func (i *iam) Enforce(name, domain, resource, action string) bool { +func (i *iam) Enforce(name, domain, rtype, resource, action string) bool { if len(name) == 0 { name = "$anon" } @@ -116,6 +116,7 @@ func (i *iam) Enforce(name, domain, resource, action string) bool { l := i.logger.Debug().WithFields(log.Fields{ "subject": name, "domain": domain, + "type": rtype, "resource": resource, "action": action, "superuser": superuser, @@ -125,7 +126,7 @@ func (i *iam) Enforce(name, domain, resource, action string) bool { name = "$superuser" } - ok, rule := i.am.Enforce(name, domain, resource, action) + ok, rule := i.am.Enforce(name, domain, rtype, resource, action) if !ok { l.Log("no match") @@ -194,7 +195,7 @@ func (i *iam) Validators() []string { return i.im.Validators() } -func (i *iam) HasPolicy(name, domain, resource string, actions []string) bool { +func (i *iam) HasPolicy(name, domain string, types []string, resource string, actions []string) bool { if len(name) == 0 { name = "$anon" } @@ -203,10 +204,10 @@ func (i *iam) HasPolicy(name, domain, resource string, actions []string) bool { domain = "$none" } - return i.am.HasPolicy(name, domain, resource, actions) + return i.am.HasPolicy(name, domain, types, resource, actions) } -func (i *iam) AddPolicy(name, domain, resource string, actions []string) error { +func (i *iam) AddPolicy(name, domain string, types []string, resource string, actions []string) error { if len(name) == 0 { name = "$anon" } @@ -222,10 +223,10 @@ func (i *iam) AddPolicy(name, domain, resource string, actions []string) error { } } - return i.am.AddPolicy(name, domain, resource, actions) + return i.am.AddPolicy(name, domain, types, resource, actions) } -func (i *iam) RemovePolicy(name, domain, resource string, actions []string) error { +func (i *iam) RemovePolicy(name, domain string, types []string, resource string, actions []string) error { if len(name) != 0 && name != "$anon" { if user, err := i.im.Get(name); err == nil { // Update the "updatedAt" field @@ -233,11 +234,11 @@ func (i *iam) RemovePolicy(name, domain, resource string, actions []string) erro } } - return i.am.RemovePolicy(name, domain, resource, actions) + return i.am.RemovePolicy(name, domain, types, resource, actions) } -func (i *iam) ListPolicies(name, domain, resource string, actions []string) []access.Policy { - return i.am.ListPolicies(name, domain, resource, actions) +func (i *iam) ListPolicies(name, domain string, types []string, resource string, actions []string) []access.Policy { + return i.am.ListPolicies(name, domain, types, resource, actions) } func (i *iam) ReloadPolicies() error { diff --git a/restream/restream_test.go b/restream/restream_test.go index f60712c9..dd7d7a5c 100644 --- a/restream/restream_test.go +++ b/restream/restream_test.go @@ -69,7 +69,7 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp return nil, err } - iam.AddPolicy("$anon", "$none", "process:*", []string{"CREATE", "GET", "DELETE", "UPDATE", "COMMAND", "PROBE", "METADATA", "PLAYOUT"}) + iam.AddPolicy("$anon", "$none", []string{"process"}, "*", []string{"CREATE", "GET", "DELETE", "UPDATE", "COMMAND", "PROBE", "METADATA", "PLAYOUT"}) rewriter, err := rewrite.New(rewrite.Config{ IAM: iam, diff --git a/rtmp/rtmp.go b/rtmp/rtmp.go index 5d80d22e..1369af2e 100644 --- a/rtmp/rtmp.go +++ b/rtmp/rtmp.go @@ -273,9 +273,9 @@ func (s *server) handlePlay(conn *rtmp.Conn) { } domain := s.findDomainFromPlaypath(playpath) - resource := "rtmp:" + playpath + resource := playpath - if !s.iam.Enforce(identity, domain, resource, "PLAY") { + if !s.iam.Enforce(identity, domain, "rtmp", resource, "PLAY") { s.log(identity, "PLAY", "FORBIDDEN", playpath, "access denied", remote) return } @@ -402,9 +402,9 @@ func (s *server) handlePublish(conn *rtmp.Conn) { } domain := s.findDomainFromPlaypath(playpath) - resource := "rtmp:" + playpath + resource := playpath - if !s.iam.Enforce(identity, domain, resource, "PUBLISH") { + if !s.iam.Enforce(identity, domain, "rtmp", resource, "PUBLISH") { s.log(identity, "PUBLISH", "FORBIDDEN", playpath, "access denied", remote) return } diff --git a/srt/srt.go b/srt/srt.go index b1516c88..89b483cb 100644 --- a/srt/srt.go +++ b/srt/srt.go @@ -330,13 +330,13 @@ func (s *server) handleConnect(req srt.ConnRequest) srt.ConnType { } domain := s.findDomainFromPlaypath(si.Resource) - resource := "srt:" + si.Resource + resource := si.Resource action := "PLAY" if mode == srt.PUBLISH { action = "PUBLISH" } - if !s.iam.Enforce(identity, domain, resource, action) { + if !s.iam.Enforce(identity, domain, "srt", resource, action) { s.log(identity, "CONNECT", "FORBIDDEN", si.Resource, "access denied", client) return srt.REJECT }