mirror of
https://github.com/portainer/portainer.git
synced 2026-06-23 06:40:13 +00:00
185 lines
5.1 KiB
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)
|
|
}
|