Fix process and iam enforcing credentials

This commit is contained in:
Ingo Oppermann
2025-07-07 13:14:00 +02:00
parent 15a0f4dbc5
commit 290c612d01
4 changed files with 82 additions and 103 deletions

View File

@@ -122,6 +122,7 @@ func (r *ProcessReport) Marshal() app.Report {
type ProcessReportSearchResult struct {
ProcessID string `json:"id"`
Domain string `json:"domain"`
Reference string `json:"reference"`
ExitState string `json:"exit_state"`
CreatedAt int64 `json:"created_at" format:"int64"`

View File

@@ -22,7 +22,6 @@ import (
// @Tags v16.?.?
// @ID cluster-3-get-all-processes
// @Produce json
// @Param domain query string false "Domain to act on"
// @Param filter query string false "Comma separated list of fields (config, state, report, metadata) that will be part of the output. If empty, all fields will be part of the output."
// @Param reference query string false "Return only these process that have this reference value. If empty, the reference will be ignored."
// @Param id query string false "Comma separated list of process ids to list. Overrides the reference. If empty all IDs will be returned."
@@ -40,16 +39,15 @@ func (h *ClusterHandler) ProcessList(c echo.Context) error {
wantids := strings.FieldsFunc(util.DefaultQuery(c, "id", ""), func(r rune) bool {
return r == rune(',')
})
domain := util.DefaultQuery(c, "domain", "")
idpattern := util.DefaultQuery(c, "idpattern", "")
refpattern := util.DefaultQuery(c, "refpattern", "")
ownerpattern := util.DefaultQuery(c, "ownerpattern", "")
domainpattern := util.DefaultQuery(c, "domainpattern", "")
procs := h.proxy.ProcessList(node.ProcessListOptions{
ID: wantids,
Filter: filter.Slice(),
Domain: domain,
ID: wantids,
Filter: filter.Slice(),
//Domain: domain,
Reference: reference,
IDPattern: idpattern,
RefPattern: refpattern,
@@ -60,7 +58,7 @@ func (h *ClusterHandler) ProcessList(c echo.Context) error {
pmap := map[app.ProcessID]api.Process{}
for _, p := range procs {
if !h.iam.Enforce(ctxuser, domain, "process", p.ID, "read") {
if !h.iam.Enforce(ctxuser, p.Domain, "process", p.ID, "read") {
continue
}
@@ -72,10 +70,10 @@ func (h *ClusterHandler) ProcessList(c echo.Context) error {
// Here we have to add those processes that are in the cluster DB and couldn't be deployed
{
processes := h.cluster.Store().ProcessList()
filtered := h.getFilteredStoreProcesses(processes, wantids, domain, reference, idpattern, refpattern, ownerpattern, domainpattern)
filtered := h.getFilteredStoreProcesses(processes, wantids, 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, p.Config.Domain, "process", p.Config.ID, "read") {
continue
}
@@ -105,7 +103,7 @@ func (h *ClusterHandler) ProcessList(c echo.Context) error {
return c.JSON(http.StatusOK, processes)
}
func (h *ClusterHandler) getFilteredStoreProcesses(processes []store.Process, wantids []string, _, reference, idpattern, refpattern, ownerpattern, domainpattern string) []store.Process {
func (h *ClusterHandler) getFilteredStoreProcesses(processes []store.Process, wantids []string, reference, idpattern, refpattern, ownerpattern, domainpattern string) []store.Process {
filtered := []store.Process{}
count := 0
@@ -252,7 +250,6 @@ func (h *ClusterHandler) ProcessGet(c echo.Context) error {
// @Router /api/v3/cluster/process [post]
func (h *ClusterHandler) ProcessAdd(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
process := api.ProcessConfig{
ID: shortuuid.New(),
@@ -269,12 +266,6 @@ func (h *ClusterHandler) ProcessAdd(c echo.Context) error {
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") {
return api.Err(http.StatusForbidden, "", "user %s is not allowed to write this process in domain %s", process.Owner, process.Domain)
}
}
if process.Type != "ffmpeg" {
return api.Err(http.StatusBadRequest, "", "unsupported process type: supported process types are: ffmpeg")
}
@@ -314,7 +305,6 @@ func (h *ClusterHandler) ProcessAdd(c echo.Context) error {
// @Router /api/v3/cluster/process/{id} [put]
func (h *ClusterHandler) ProcessUpdate(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
domain := util.DefaultQuery(c, "domain", "")
id := util.PathParam(c, "id")
@@ -348,12 +338,6 @@ func (h *ClusterHandler) ProcessUpdate(c echo.Context) error {
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") {
return api.Err(http.StatusForbidden, "", "user %s is not allowed to write this process", process.Owner)
}
}
config, metadata := process.Marshal()
if err := h.cluster.ProcessUpdate("", pid, config); err != nil {

View File

@@ -292,13 +292,13 @@ func (h *IAMHandler) UpdateIdentityPolicies(c echo.Context) error {
// @Router /api/v3/iam/user [get]
func (h *IAMHandler) ListIdentities(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
domain := util.DefaultQuery(c, "domain", "$none")
domain := util.DefaultQuery(c, "domain", "")
identities := h.iam.ListIdentities()
users := make([]api.IAMUser, len(identities)+1)
users := []api.IAMUser{}
for i, iamuser := range identities {
for _, iamuser := range identities {
if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "read") {
continue
}
@@ -311,7 +311,9 @@ func (h *IAMHandler) ListIdentities(c echo.Context) error {
policies := h.iam.ListPolicies(iamuser.Name, "", nil, "", nil)
users[i].Marshal(iamuser, policies)
user := api.IAMUser{}
user.Marshal(iamuser, policies)
users = append(users, user)
}
anon := identity.User{
@@ -320,7 +322,9 @@ func (h *IAMHandler) ListIdentities(c echo.Context) error {
policies := h.iam.ListPolicies("$anon", "", nil, "", nil)
users[len(users)-1].Marshal(anon, policies)
user := api.IAMUser{}
user.Marshal(anon, policies)
users = append(users, user)
return c.JSON(http.StatusOK, users)
}

View File

@@ -50,7 +50,6 @@ func NewProcess(restream restream.Restreamer, iam iam.IAM) *ProcessHandler {
// @Router /api/v3/process [post]
func (h *ProcessHandler) Add(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
process := api.ProcessConfig{
ID: shortuuid.New(),
@@ -67,12 +66,6 @@ func (h *ProcessHandler) Add(c echo.Context) error {
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") {
return api.Err(http.StatusForbidden, "", "The owner '%s' is not allowed to write this process", process.Owner)
}
}
if process.Type != "ffmpeg" {
return api.Err(http.StatusBadRequest, "", "unsupported process type, supported process types are: ffmpeg")
}
@@ -107,7 +100,6 @@ func (h *ProcessHandler) Add(c echo.Context) error {
// @Tags v16.7.2
// @ID process-3-get-all
// @Produce json
// @Param domain query string false "Domain to act on"
// @Param filter query string false "Comma separated list of fields (config, state, report, metadata) that will be part of the output. If empty, all fields will be part of the output."
// @Param reference query string false "Return only these process that have this reference value. If empty, the reference will be ignored."
// @Param id query string false "Comma separated list of process ids to list. Overrides the reference. If empty all IDs will be returned."
@@ -125,7 +117,6 @@ func (h *ProcessHandler) GetAll(c echo.Context) error {
wantids := strings.FieldsFunc(util.DefaultQuery(c, "id", ""), func(r rune) bool {
return r == rune(',')
})
domain := util.DefaultQuery(c, "domain", "")
idpattern := util.DefaultQuery(c, "idpattern", "")
refpattern := util.DefaultQuery(c, "refpattern", "")
ownerpattern := util.DefaultQuery(c, "ownerpattern", "")
@@ -157,19 +148,19 @@ func (h *ProcessHandler) GetAll(c echo.Context) error {
wg := sync.WaitGroup{}
for i := 0; i < 8; /*runtime.NumCPU()*/ i++ {
for range 8 {
wg.Add(1)
go func(idChan <-chan app.ProcessID) {
defer wg.Done()
for id := range idChan {
if !h.iam.Enforce(ctxuser, domain, "process", id.ID, "read") {
process, err := h.getProcess(id, filter)
if err != nil {
continue
}
process, err := h.getProcess(id, filter)
if err != nil {
if !h.iam.Enforce(ctxuser, process.Domain, "process", id.ID, "read") {
continue
}
@@ -218,7 +209,7 @@ func (h *ProcessHandler) Get(c echo.Context) error {
domain := util.DefaultQuery(c, "domain", "")
if !h.iam.Enforce(ctxuser, domain, "process", id, "read") {
return api.Err(http.StatusForbidden, "Forbidden")
return api.Err(http.StatusForbidden, "")
}
tid := app.ProcessID{
@@ -251,7 +242,6 @@ func (h *ProcessHandler) Get(c echo.Context) error {
// @Router /api/v3/process/{id} [delete]
func (h *ProcessHandler) Delete(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
id := util.PathParam(c, "id")
domain := util.DefaultQuery(c, "domain", "")
@@ -260,10 +250,8 @@ func (h *ProcessHandler) Delete(c echo.Context) error {
Domain: domain,
}
if !superuser {
if !h.iam.Enforce(ctxuser, domain, "process", id, "write") {
return api.Err(http.StatusForbidden, "")
}
if !h.iam.Enforce(ctxuser, domain, "process", id, "write") {
return api.Err(http.StatusForbidden, "")
}
if err := h.restream.StopProcess(tid); err != nil {
@@ -296,7 +284,6 @@ func (h *ProcessHandler) Delete(c echo.Context) error {
// @Router /api/v3/process/{id} [put]
func (h *ProcessHandler) Update(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
domain := util.DefaultQuery(c, "domain", "")
id := util.PathParam(c, "id")
@@ -333,12 +320,6 @@ func (h *ProcessHandler) Update(c echo.Context) error {
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") {
return api.Err(http.StatusForbidden, "", "The owner '%s' is not allowed to write this process: %s", process.Owner, process.ID)
}
}
config, metadata := process.Marshal()
if err := h.restream.UpdateProcess(tid, config); err != nil {
@@ -397,15 +378,16 @@ func (h *ProcessHandler) Command(c echo.Context) error {
}
var err error
if command.Command == "start" {
switch command.Command {
case "start":
err = h.restream.StartProcess(tid)
} else if command.Command == "stop" {
case "stop":
err = h.restream.StopProcess(tid)
} else if command.Command == "restart" {
case "restart":
err = h.restream.RestartProcess(tid)
} else if command.Command == "reload" {
case "reload":
err = h.restream.ReloadProcess(tid)
} else {
default:
return api.Err(http.StatusBadRequest, "", "unknown command provided: known commands are: start, stop, reload, restart")
}
@@ -673,6 +655,7 @@ func (h *ProcessHandler) SetReport(c echo.Context) error {
// @Security ApiKeyAuth
// @Router /api/v3/report/process [get]
func (h *ProcessHandler) SearchReportHistory(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
idpattern := util.DefaultQuery(c, "idpattern", "")
refpattern := util.DefaultQuery(c, "refpattern", "")
state := util.DefaultQuery(c, "state", "")
@@ -701,13 +684,20 @@ func (h *ProcessHandler) SearchReportHistory(c echo.Context) error {
result := h.restream.SearchProcessLogHistory(idpattern, refpattern, state, from, to)
response := make([]api.ProcessReportSearchResult, len(result))
for i, b := range result {
response[i].ProcessID = b.ProcessID
response[i].Reference = b.Reference
response[i].ExitState = b.ExitState
response[i].CreatedAt = b.CreatedAt.Unix()
response[i].ExitedAt = b.ExitedAt.Unix()
response := []api.ProcessReportSearchResult{}
for _, b := range result {
if !h.iam.Enforce(ctxuser, b.Domain, "process", b.ProcessID, "read") {
continue
}
response = append(response, api.ProcessReportSearchResult{
ProcessID: b.ProcessID,
Domain: b.Domain,
Reference: b.Reference,
ExitState: b.ExitState,
CreatedAt: b.CreatedAt.Unix(),
ExitedAt: b.ExitedAt.Unix(),
})
}
return c.JSON(http.StatusOK, response)
@@ -843,43 +833,6 @@ func (h *ProcessHandler) ValidateConfig(c echo.Context) error {
return c.JSON(http.StatusOK, process)
}
// Skills returns the detected FFmpeg capabilities
// @Summary FFmpeg capabilities
// @Description List all detected FFmpeg capabilities.
// @Tags v16.7.2
// @ID skills-3
// @Produce json
// @Success 200 {object} api.Skills
// @Security ApiKeyAuth
// @Router /api/v3/skills [get]
func (h *ProcessHandler) Skills(c echo.Context) error {
skills := h.restream.Skills()
apiskills := api.Skills{}
apiskills.Unmarshal(skills)
return c.JSON(http.StatusOK, apiskills)
}
// ReloadSkills will refresh the FFmpeg capabilities
// @Summary Refresh FFmpeg capabilities
// @Description Refresh the available FFmpeg capabilities.
// @Tags v16.7.2
// @ID skills-3-reload
// @Produce json
// @Success 200 {object} api.Skills
// @Security ApiKeyAuth
// @Router /api/v3/skills/reload [get]
func (h *ProcessHandler) ReloadSkills(c echo.Context) error {
h.restream.ReloadSkills()
skills := h.restream.Skills()
apiskills := api.Skills{}
apiskills.Unmarshal(skills)
return c.JSON(http.StatusOK, apiskills)
}
// GetProcessMetadata returns the metadata stored with a process
// @Summary Retrieve JSON metadata stored with a process under a key
// @Description Retrieve the previously stored JSON metadata under the given key. If the key is empty, all metadata will be returned.
@@ -1028,6 +981,43 @@ func (h *ProcessHandler) SetMetadata(c echo.Context) error {
return c.JSON(http.StatusOK, data)
}
// Skills returns the detected FFmpeg capabilities
// @Summary FFmpeg capabilities
// @Description List all detected FFmpeg capabilities.
// @Tags v16.7.2
// @ID skills-3
// @Produce json
// @Success 200 {object} api.Skills
// @Security ApiKeyAuth
// @Router /api/v3/skills [get]
func (h *ProcessHandler) Skills(c echo.Context) error {
skills := h.restream.Skills()
apiskills := api.Skills{}
apiskills.Unmarshal(skills)
return c.JSON(http.StatusOK, apiskills)
}
// ReloadSkills will refresh the FFmpeg capabilities
// @Summary Refresh FFmpeg capabilities
// @Description Refresh the available FFmpeg capabilities.
// @Tags v16.7.2
// @ID skills-3-reload
// @Produce json
// @Success 200 {object} api.Skills
// @Security ApiKeyAuth
// @Router /api/v3/skills/reload [get]
func (h *ProcessHandler) ReloadSkills(c echo.Context) error {
h.restream.ReloadSkills()
skills := h.restream.Skills()
apiskills := api.Skills{}
apiskills.Unmarshal(skills)
return c.JSON(http.StatusOK, apiskills)
}
type filter struct {
config bool
state bool