Files
portainer/api/uac/generic_rc_getter.go

185 lines
5.1 KiB
Go

package uac
import (
"strings"
"github.com/docker/docker/api/types/swarm"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/docker/consts"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/slicesx"
"github.com/rs/zerolog/log"
)
type rcServiceLike interface {
ResourceControlByResourceIDAndType(resourceID string, resourceType portainer.ResourceControlType) (*portainer.ResourceControl, error)
}
type teamServiceLike interface {
TeamByName(name string) (*portainer.Team, error)
}
type userServiceLike interface {
UserIDByUsername(name string) (portainer.UserID, error)
}
type txLike[
RCS rcServiceLike,
TS teamServiceLike,
US userServiceLike,
] interface {
ResourceControl() RCS
Team() TS
User() US
}
type ResourceContext[T any] struct {
RCType portainer.ResourceControlType
IDGetter func(T) string
LabelsGetter func(T) map[string]string
}
func genericResourcControlGetter[
T any,
TX txLike[RCS, TS, US],
RCS rcServiceLike,
TS teamServiceLike,
US userServiceLike,
](
tx TX,
endpointID portainer.EndpointID,
context ResourceContext[T],
) func(T) (*portainer.ResourceControl, error) {
return func(item T) (*portainer.ResourceControl, error) {
resourceID := context.IDGetter(item)
resourceType := context.RCType
if rc, err := tx.ResourceControl().ResourceControlByResourceIDAndType(
resourceID, resourceType,
); err != nil && !dataservices.IsErrObjectNotFound(err) {
return nil, err
} else if rc != nil {
return rc, nil
}
if context.LabelsGetter == nil {
return authorization.NewEmptyRestrictedResourceControl(resourceID, resourceType), nil
}
labels := context.LabelsGetter(item)
if labels == nil {
return authorization.NewEmptyRestrictedResourceControl(resourceID, resourceType), nil
}
if serviceId, ok := labels[consts.SwarmServiceIDLabel]; ok {
if rc, err := tx.ResourceControl().ResourceControlByResourceIDAndType(
ServiceResourceControlID(swarm.Service{ID: serviceId}),
portainer.ServiceResourceControl,
); err != nil && !dataservices.IsErrObjectNotFound(err) {
return nil, err
} else if rc != nil {
return rc, nil
}
}
if stackName, ok := labels[consts.SwarmStackNameLabel]; ok {
if rc, err := tx.ResourceControl().ResourceControlByResourceIDAndType(
StackResourceControlID(endpointID, stackName),
portainer.StackResourceControl,
); err != nil && !dataservices.IsErrObjectNotFound(err) {
return nil, err
} else if rc != nil {
return rc, nil
}
}
if stackName, ok := labels[consts.ComposeStackNameLabel]; ok {
if rc, err := tx.ResourceControl().ResourceControlByResourceIDAndType(
StackResourceControlID(endpointID, stackName),
portainer.StackResourceControl,
); err != nil && !dataservices.IsErrObjectNotFound(err) {
return nil, err
} else if rc != nil {
return rc, nil
}
}
return rcFromPortainerLabels(tx, labels, resourceID, resourceType)
}
}
const (
publicRCLabel = "io.portainer.accesscontrol.public"
userRCLabel = "io.portainer.accesscontrol.users"
teamRCLabel = "io.portainer.accesscontrol.teams"
)
// translation of rules from transport.newResourceControlFromPortainerLabels(resourceLabelsObject, resourceIdentifier, resourceType)
func rcFromPortainerLabels[
TX txLike[RCS, TS, US],
RCS rcServiceLike,
TS teamServiceLike,
US userServiceLike,
](
tx TX, labels map[string]string, resourceID string, resourceType portainer.ResourceControlType,
) (*portainer.ResourceControl, error) {
if _, ok := labels[publicRCLabel]; ok {
return authorization.NewPublicResourceControl(resourceID, resourceType), nil
}
teamNames := make([]string, 0)
userNames := make([]string, 0)
if teams, ok := labels[teamRCLabel]; ok {
teamNames = getUniqueElements(teams)
}
if users, ok := labels[userRCLabel]; ok {
userNames = getUniqueElements(users)
}
if len(teamNames) == 0 && len(userNames) == 0 {
return authorization.NewEmptyRestrictedResourceControl(resourceID, resourceType), nil
}
teamIDs := make([]portainer.TeamID, 0)
userIDs := make([]portainer.UserID, 0)
for _, name := range teamNames {
team, err := tx.Team().TeamByName(name)
if err != nil {
log.Warn().
Str("name", name).
Str("resource_id", resourceID).
Msg("unknown team name in access control label, ignoring access control rule for this team")
continue
}
teamIDs = append(teamIDs, team.ID)
}
for _, name := range userNames {
userID, err := tx.User().UserIDByUsername(name)
if err != nil {
log.Warn().
Str("name", name).
Str("resource_id", resourceID).
Msg("unknown user name in access control label, ignoring access control rule for this user")
continue
}
userIDs = append(userIDs, userID)
}
return authorization.NewRestrictedResourceControl(resourceID, resourceType, userIDs, teamIDs), nil
}
func getUniqueElements(items string) []string {
xs := strings.Split(items, ",")
xs = slicesx.Map(xs, strings.TrimSpace)
xs = slicesx.FilterInPlace(xs, func(x string) bool { return len(x) > 0 })
return slicesx.Unique(xs)
}