2019-11-12 23:41:42 +00:00
package docker
import (
"log"
"net/http"
"strings"
2021-02-23 20:18:05 +00:00
"github.com/portainer/portainer/api/internal/stackutils"
2021-07-14 09:15:21 +00:00
"github.com/portainer/portainer/api/http/proxy/factory/utils"
2020-06-16 07:58:16 +00:00
"github.com/portainer/portainer/api/internal/authorization"
2019-11-12 23:41:42 +00:00
2021-02-23 20:18:05 +00:00
portainer "github.com/portainer/portainer/api"
2019-11-12 23:41:42 +00:00
)
const (
resourceLabelForPortainerTeamResourceControl = "io.portainer.accesscontrol.teams"
resourceLabelForPortainerUserResourceControl = "io.portainer.accesscontrol.users"
resourceLabelForPortainerPublicResourceControl = "io.portainer.accesscontrol.public"
resourceLabelForDockerSwarmStackName = "com.docker.stack.namespace"
resourceLabelForDockerServiceID = "com.docker.swarm.service.id"
resourceLabelForDockerComposeStackName = "com.docker.compose.project"
)
type (
resourceLabelsObjectSelector func ( map [ string ] interface { } ) map [ string ] interface { }
resourceOperationParameters struct {
resourceIdentifierAttribute string
resourceType portainer . ResourceControlType
labelsObjectSelector resourceLabelsObjectSelector
}
)
2021-03-03 09:38:59 +00:00
func getUniqueElements ( items string ) [ ] string {
result := [ ] string { }
seen := make ( map [ string ] struct { } )
for _ , item := range strings . Split ( items , "," ) {
v := strings . TrimSpace ( item )
if v == "" {
continue
}
if _ , ok := seen [ v ] ; ! ok {
result = append ( result , v )
seen [ v ] = struct { } { }
}
}
return result
}
2019-11-12 23:41:42 +00:00
func ( transport * Transport ) newResourceControlFromPortainerLabels ( labelsObject map [ string ] interface { } , resourceID string , resourceType portainer . ResourceControlType ) ( * portainer . ResourceControl , error ) {
if labelsObject [ resourceLabelForPortainerPublicResourceControl ] != nil {
2020-06-16 07:58:16 +00:00
resourceControl := authorization . NewPublicResourceControl ( resourceID , resourceType )
2019-11-12 23:41:42 +00:00
2020-05-20 05:23:15 +00:00
err := transport . dataStore . ResourceControl ( ) . CreateResourceControl ( resourceControl )
2019-11-12 23:41:42 +00:00
if err != nil {
return nil , err
}
return resourceControl , nil
}
teamNames := make ( [ ] string , 0 )
userNames := make ( [ ] string , 0 )
if labelsObject [ resourceLabelForPortainerTeamResourceControl ] != nil {
concatenatedTeamNames := labelsObject [ resourceLabelForPortainerTeamResourceControl ] . ( string )
2021-03-03 09:38:59 +00:00
teamNames = getUniqueElements ( concatenatedTeamNames )
2019-11-12 23:41:42 +00:00
}
if labelsObject [ resourceLabelForPortainerUserResourceControl ] != nil {
concatenatedUserNames := labelsObject [ resourceLabelForPortainerUserResourceControl ] . ( string )
2021-03-03 09:38:59 +00:00
userNames = getUniqueElements ( concatenatedUserNames )
2019-11-12 23:41:42 +00:00
}
if len ( teamNames ) > 0 || len ( userNames ) > 0 {
teamIDs := make ( [ ] portainer . TeamID , 0 )
userIDs := make ( [ ] portainer . UserID , 0 )
for _ , name := range teamNames {
2020-05-20 05:23:15 +00:00
team , err := transport . dataStore . Team ( ) . TeamByName ( name )
2019-11-12 23:41:42 +00:00
if err != nil {
log . Printf ( "[WARN] [http,proxy,docker] [message: unknown team name in access control label, ignoring access control rule for this team] [name: %s] [resource_id: %s]" , name , resourceID )
continue
}
teamIDs = append ( teamIDs , team . ID )
}
for _ , name := range userNames {
2020-05-20 05:23:15 +00:00
user , err := transport . dataStore . User ( ) . UserByUsername ( name )
2019-11-12 23:41:42 +00:00
if err != nil {
log . Printf ( "[WARN] [http,proxy,docker] [message: unknown user name in access control label, ignoring access control rule for this user] [name: %s] [resource_id: %s]" , name , resourceID )
continue
}
userIDs = append ( userIDs , user . ID )
}
2020-06-16 07:58:16 +00:00
resourceControl := authorization . NewRestrictedResourceControl ( resourceID , resourceType , userIDs , teamIDs )
2019-11-12 23:41:42 +00:00
2020-05-20 05:23:15 +00:00
err := transport . dataStore . ResourceControl ( ) . CreateResourceControl ( resourceControl )
2019-11-12 23:41:42 +00:00
if err != nil {
return nil , err
}
return resourceControl , nil
}
return nil , nil
}
func ( transport * Transport ) createPrivateResourceControl ( resourceIdentifier string , resourceType portainer . ResourceControlType , userID portainer . UserID ) ( * portainer . ResourceControl , error ) {
2020-06-16 07:58:16 +00:00
resourceControl := authorization . NewPrivateResourceControl ( resourceIdentifier , resourceType , userID )
2019-11-12 23:41:42 +00:00
2020-05-20 05:23:15 +00:00
err := transport . dataStore . ResourceControl ( ) . CreateResourceControl ( resourceControl )
2019-11-12 23:41:42 +00:00
if err != nil {
log . Printf ( "[ERROR] [http,proxy,docker,transport] [message: unable to persist resource control] [resource: %s] [err: %s]" , resourceIdentifier , err )
return nil , err
}
return resourceControl , nil
}
func ( transport * Transport ) getInheritedResourceControlFromServiceOrStack ( resourceIdentifier , nodeName string , resourceType portainer . ResourceControlType , resourceControls [ ] portainer . ResourceControl ) ( * portainer . ResourceControl , error ) {
2021-10-15 05:13:20 +00:00
client , err := transport . dockerClientFactory . CreateClient ( transport . endpoint , nodeName )
if err != nil {
return nil , err
2019-11-12 23:41:42 +00:00
}
2021-10-15 05:13:20 +00:00
defer client . Close ( )
2019-11-12 23:41:42 +00:00
switch resourceType {
case portainer . ContainerResourceControl :
2021-02-23 20:18:05 +00:00
return getInheritedResourceControlFromContainerLabels ( client , transport . endpoint . ID , resourceIdentifier , resourceControls )
2019-11-12 23:41:42 +00:00
case portainer . NetworkResourceControl :
2021-02-23 20:18:05 +00:00
return getInheritedResourceControlFromNetworkLabels ( client , transport . endpoint . ID , resourceIdentifier , resourceControls )
2019-11-12 23:41:42 +00:00
case portainer . VolumeResourceControl :
2021-02-23 20:18:05 +00:00
return getInheritedResourceControlFromVolumeLabels ( client , transport . endpoint . ID , resourceIdentifier , resourceControls )
2019-11-12 23:41:42 +00:00
case portainer . ServiceResourceControl :
2021-02-23 20:18:05 +00:00
return getInheritedResourceControlFromServiceLabels ( client , transport . endpoint . ID , resourceIdentifier , resourceControls )
2019-11-12 23:41:42 +00:00
case portainer . ConfigResourceControl :
2021-02-23 20:18:05 +00:00
return getInheritedResourceControlFromConfigLabels ( client , transport . endpoint . ID , resourceIdentifier , resourceControls )
2019-11-12 23:41:42 +00:00
case portainer . SecretResourceControl :
2021-02-23 20:18:05 +00:00
return getInheritedResourceControlFromSecretLabels ( client , transport . endpoint . ID , resourceIdentifier , resourceControls )
2019-11-12 23:41:42 +00:00
}
return nil , nil
}
func ( transport * Transport ) applyAccessControlOnResource ( parameters * resourceOperationParameters , responseObject map [ string ] interface { } , response * http . Response , executor * operationExecutor ) error {
if responseObject [ parameters . resourceIdentifierAttribute ] == nil {
log . Printf ( "[WARN] [message: unable to find resource identifier property in resource object] [identifier_attribute: %s]" , parameters . resourceIdentifierAttribute )
return nil
}
if parameters . resourceType == portainer . NetworkResourceControl {
systemResourceControl := findSystemNetworkResourceControl ( responseObject )
if systemResourceControl != nil {
responseObject = decorateObject ( responseObject , systemResourceControl )
2021-07-14 09:15:21 +00:00
return utils . RewriteResponse ( response , responseObject , http . StatusOK )
2019-11-12 23:41:42 +00:00
}
}
resourceIdentifier := responseObject [ parameters . resourceIdentifierAttribute ] . ( string )
resourceLabelsObject := parameters . labelsObjectSelector ( responseObject )
resourceControl , err := transport . findResourceControl ( resourceIdentifier , parameters . resourceType , resourceLabelsObject , executor . operationContext . resourceControls )
if err != nil {
return err
}
2020-08-24 23:04:51 +00:00
if resourceControl == nil && ( executor . operationContext . isAdmin ) {
2021-07-14 09:15:21 +00:00
return utils . RewriteResponse ( response , responseObject , http . StatusOK )
2019-11-12 23:41:42 +00:00
}
2020-08-24 23:04:51 +00:00
if executor . operationContext . isAdmin || ( resourceControl != nil && authorization . UserCanAccessResource ( executor . operationContext . userID , executor . operationContext . userTeamIDs , resourceControl ) ) {
2019-11-12 23:41:42 +00:00
responseObject = decorateObject ( responseObject , resourceControl )
2021-07-14 09:15:21 +00:00
return utils . RewriteResponse ( response , responseObject , http . StatusOK )
2019-11-12 23:41:42 +00:00
}
2021-07-14 09:15:21 +00:00
return utils . RewriteAccessDeniedResponse ( response )
2019-11-12 23:41:42 +00:00
}
func ( transport * Transport ) applyAccessControlOnResourceList ( parameters * resourceOperationParameters , resourceData [ ] interface { } , executor * operationExecutor ) ( [ ] interface { } , error ) {
2020-08-24 23:04:51 +00:00
if executor . operationContext . isAdmin {
2019-11-12 23:41:42 +00:00
return transport . decorateResourceList ( parameters , resourceData , executor . operationContext . resourceControls )
}
return transport . filterResourceList ( parameters , resourceData , executor . operationContext )
}
func ( transport * Transport ) decorateResourceList ( parameters * resourceOperationParameters , resourceData [ ] interface { } , resourceControls [ ] portainer . ResourceControl ) ( [ ] interface { } , error ) {
decoratedResourceData := make ( [ ] interface { } , 0 )
for _ , resource := range resourceData {
resourceObject := resource . ( map [ string ] interface { } )
if resourceObject [ parameters . resourceIdentifierAttribute ] == nil {
log . Printf ( "[WARN] [http,proxy,docker,decorate] [message: unable to find resource identifier property in resource list element] [identifier_attribute: %s]" , parameters . resourceIdentifierAttribute )
continue
}
if parameters . resourceType == portainer . NetworkResourceControl {
systemResourceControl := findSystemNetworkResourceControl ( resourceObject )
if systemResourceControl != nil {
resourceObject = decorateObject ( resourceObject , systemResourceControl )
decoratedResourceData = append ( decoratedResourceData , resourceObject )
continue
}
}
resourceIdentifier := resourceObject [ parameters . resourceIdentifierAttribute ] . ( string )
resourceLabelsObject := parameters . labelsObjectSelector ( resourceObject )
resourceControl , err := transport . findResourceControl ( resourceIdentifier , parameters . resourceType , resourceLabelsObject , resourceControls )
if err != nil {
return nil , err
}
if resourceControl != nil {
resourceObject = decorateObject ( resourceObject , resourceControl )
}
decoratedResourceData = append ( decoratedResourceData , resourceObject )
}
return decoratedResourceData , nil
}
func ( transport * Transport ) filterResourceList ( parameters * resourceOperationParameters , resourceData [ ] interface { } , context * restrictedDockerOperationContext ) ( [ ] interface { } , error ) {
filteredResourceData := make ( [ ] interface { } , 0 )
for _ , resource := range resourceData {
resourceObject := resource . ( map [ string ] interface { } )
if resourceObject [ parameters . resourceIdentifierAttribute ] == nil {
log . Printf ( "[WARN] [http,proxy,docker,filter] [message: unable to find resource identifier property in resource list element] [identifier_attribute: %s]" , parameters . resourceIdentifierAttribute )
continue
}
resourceIdentifier := resourceObject [ parameters . resourceIdentifierAttribute ] . ( string )
resourceLabelsObject := parameters . labelsObjectSelector ( resourceObject )
if parameters . resourceType == portainer . NetworkResourceControl {
systemResourceControl := findSystemNetworkResourceControl ( resourceObject )
if systemResourceControl != nil {
resourceObject = decorateObject ( resourceObject , systemResourceControl )
filteredResourceData = append ( filteredResourceData , resourceObject )
continue
}
}
resourceControl , err := transport . findResourceControl ( resourceIdentifier , parameters . resourceType , resourceLabelsObject , context . resourceControls )
if err != nil {
return nil , err
}
if resourceControl == nil {
2020-08-24 23:04:51 +00:00
if context . isAdmin {
2019-11-12 23:41:42 +00:00
filteredResourceData = append ( filteredResourceData , resourceObject )
}
continue
}
2020-08-24 23:04:51 +00:00
if context . isAdmin || authorization . UserCanAccessResource ( context . userID , context . userTeamIDs , resourceControl ) {
2019-11-12 23:41:42 +00:00
resourceObject = decorateObject ( resourceObject , resourceControl )
filteredResourceData = append ( filteredResourceData , resourceObject )
}
}
return filteredResourceData , nil
}
func ( transport * Transport ) findResourceControl ( resourceIdentifier string , resourceType portainer . ResourceControlType , resourceLabelsObject map [ string ] interface { } , resourceControls [ ] portainer . ResourceControl ) ( * portainer . ResourceControl , error ) {
2020-06-16 07:58:16 +00:00
resourceControl := authorization . GetResourceControlByResourceIDAndType ( resourceIdentifier , resourceType , resourceControls )
2019-11-12 23:41:42 +00:00
if resourceControl != nil {
return resourceControl , nil
}
if resourceLabelsObject != nil {
if resourceLabelsObject [ resourceLabelForDockerServiceID ] != nil {
inheritedServiceIdentifier := resourceLabelsObject [ resourceLabelForDockerServiceID ] . ( string )
2020-06-16 07:58:16 +00:00
resourceControl = authorization . GetResourceControlByResourceIDAndType ( inheritedServiceIdentifier , portainer . ServiceResourceControl , resourceControls )
2019-11-12 23:41:42 +00:00
if resourceControl != nil {
return resourceControl , nil
}
}
if resourceLabelsObject [ resourceLabelForDockerSwarmStackName ] != nil {
2021-02-23 20:18:05 +00:00
stackName := resourceLabelsObject [ resourceLabelForDockerSwarmStackName ] . ( string )
stackResourceID := stackutils . ResourceControlID ( transport . endpoint . ID , stackName )
resourceControl = authorization . GetResourceControlByResourceIDAndType ( stackResourceID , portainer . StackResourceControl , resourceControls )
2019-11-12 23:41:42 +00:00
if resourceControl != nil {
return resourceControl , nil
}
}
if resourceLabelsObject [ resourceLabelForDockerComposeStackName ] != nil {
2021-02-23 20:18:05 +00:00
stackName := resourceLabelsObject [ resourceLabelForDockerComposeStackName ] . ( string )
stackResourceID := stackutils . ResourceControlID ( transport . endpoint . ID , stackName )
resourceControl = authorization . GetResourceControlByResourceIDAndType ( stackResourceID , portainer . StackResourceControl , resourceControls )
2019-11-12 23:41:42 +00:00
if resourceControl != nil {
return resourceControl , nil
}
}
return transport . newResourceControlFromPortainerLabels ( resourceLabelsObject , resourceIdentifier , resourceType )
}
return nil , nil
}
2021-02-23 20:18:05 +00:00
func getStackResourceIDFromLabels ( resourceLabelsObject map [ string ] string , endpointID portainer . EndpointID ) string {
if resourceLabelsObject [ resourceLabelForDockerSwarmStackName ] != "" {
stackName := resourceLabelsObject [ resourceLabelForDockerSwarmStackName ]
return stackutils . ResourceControlID ( endpointID , stackName )
}
if resourceLabelsObject [ resourceLabelForDockerComposeStackName ] != "" {
stackName := resourceLabelsObject [ resourceLabelForDockerComposeStackName ]
return stackutils . ResourceControlID ( endpointID , stackName )
}
return ""
}
2019-11-12 23:41:42 +00:00
func decorateObject ( object map [ string ] interface { } , resourceControl * portainer . ResourceControl ) map [ string ] interface { } {
if object [ "Portainer" ] == nil {
object [ "Portainer" ] = make ( map [ string ] interface { } )
}
portainerMetadata := object [ "Portainer" ] . ( map [ string ] interface { } )
portainerMetadata [ "ResourceControl" ] = resourceControl
return object
}