feat(ACI): EE-261 Add RBAC to ACI (#226)
Co-authored-by: Simon Meng <simon.meng@portainer.io>
This commit is contained in:
@@ -24,7 +24,7 @@ func (handler *Handler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.R
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,22 @@ func (transport *Transport) createAzureRequestContext(request *http.Request) (*a
|
||||
}
|
||||
|
||||
context := &azureRequestContext{
|
||||
isAdmin: true,
|
||||
userID: tokenData.ID,
|
||||
resourceControls: resourceControls,
|
||||
isAdmin: true,
|
||||
userID: tokenData.ID,
|
||||
resourceControls: resourceControls,
|
||||
endpointResourceAccess: false,
|
||||
}
|
||||
|
||||
if tokenData.Role != portainer.AdministratorRole {
|
||||
context.isAdmin = false
|
||||
|
||||
user, err := transport.dataStore.User().User(context.userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, context.endpointResourceAccess = user.EndpointAuthorizations[transport.endpoint.ID][portainer.EndpointResourcesAccess]
|
||||
|
||||
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -72,7 +80,7 @@ func (transport *Transport) createPrivateResourceControl(
|
||||
}
|
||||
|
||||
func (transport *Transport) userCanDeleteContainerGroup(request *http.Request, context *azureRequestContext) bool {
|
||||
if context.isAdmin {
|
||||
if context.isAdmin || context.endpointResourceAccess {
|
||||
return true
|
||||
}
|
||||
resourceIdentifier := request.URL.Path
|
||||
@@ -119,7 +127,7 @@ func (transport *Transport) filterContainerGroups(containerGroups []interface{},
|
||||
}
|
||||
}
|
||||
|
||||
if context.isAdmin || userCanAccessResource {
|
||||
if context.isAdmin || context.endpointResourceAccess || userCanAccessResource {
|
||||
filteredContainerGroups = append(filteredContainerGroups, containerGroup)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,11 @@ type (
|
||||
}
|
||||
|
||||
azureRequestContext struct {
|
||||
isAdmin bool
|
||||
userID portainer.UserID
|
||||
userTeamIDs []portainer.TeamID
|
||||
resourceControls []portainer.ResourceControl
|
||||
isAdmin bool
|
||||
endpointResourceAccess bool
|
||||
userID portainer.UserID
|
||||
userTeamIDs []portainer.TeamID
|
||||
resourceControls []portainer.ResourceControl
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getAzureOperationAuthorization(url, method string) portainer.Authorization {
|
||||
url = strings.Split(url, "?")[0]
|
||||
if matched, _ := path.Match("/subscriptions", url); matched {
|
||||
return azureSubscriptionsOperationAuthorization(url, method)
|
||||
} else if matched, _ := path.Match("/subscriptions/*", url); matched {
|
||||
return azureSubscriptionOperationAuthorization(url, method)
|
||||
} else if matched, _ := path.Match("/subscriptions/*/providers/*", url); matched {
|
||||
return azureProviderOperationAuthorization(url, method)
|
||||
} else if matched, _ := path.Match("/subscriptions/*/resourcegroups", url); matched {
|
||||
return azureResourceGroupsOperationAuthorization(url, method)
|
||||
} else if matched, _ := path.Match("/subscriptions/*/resourcegroups/*", url); matched {
|
||||
return azureResourceGroupOperationAuthorization(url, method)
|
||||
} else if matched, _ := path.Match("/subscriptions/*/providers/*/containerGroups", url); matched {
|
||||
return azureContainerGroupsOperationAuthorization(url, method)
|
||||
} else if matched, _ := path.Match("/subscriptions/*/resourceGroups/*/providers/*/containerGroups/*", url); matched {
|
||||
return azureContainerGroupOperationAuthorization(url, method)
|
||||
}
|
||||
|
||||
return portainer.OperationAzureUndefined
|
||||
}
|
||||
|
||||
// /subscriptions
|
||||
func azureSubscriptionsOperationAuthorization(url, method string) portainer.Authorization {
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
return portainer.OperationAzureSubscriptionsList
|
||||
default:
|
||||
return portainer.OperationAzureUndefined
|
||||
}
|
||||
}
|
||||
|
||||
// /subscriptions/*
|
||||
func azureSubscriptionOperationAuthorization(url, method string) portainer.Authorization {
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
return portainer.OperationAzureSubscriptionGet
|
||||
default:
|
||||
return portainer.OperationAzureUndefined
|
||||
}
|
||||
}
|
||||
|
||||
// /subscriptions/*/resourcegroups
|
||||
func azureResourceGroupsOperationAuthorization(url, method string) portainer.Authorization {
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
return portainer.OperationAzureResourceGroupsList
|
||||
default:
|
||||
return portainer.OperationAzureUndefined
|
||||
}
|
||||
}
|
||||
|
||||
// /subscriptions/*/resourcegroups/*
|
||||
func azureResourceGroupOperationAuthorization(url, method string) portainer.Authorization {
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
return portainer.OperationAzureResourceGroupGet
|
||||
default:
|
||||
return portainer.OperationAzureUndefined
|
||||
}
|
||||
}
|
||||
|
||||
// /subscriptions/*/providers/*
|
||||
func azureProviderOperationAuthorization(url, method string) portainer.Authorization {
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
return portainer.OperationAzureProviderGet
|
||||
default:
|
||||
return portainer.OperationAzureUndefined
|
||||
}
|
||||
}
|
||||
|
||||
// /subscriptions/*/providers/Microsoft.ContainerInstance/containerGroups
|
||||
func azureContainerGroupsOperationAuthorization(url, method string) portainer.Authorization {
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
return portainer.OperationAzureContainerGroupsList
|
||||
default:
|
||||
return portainer.OperationAzureUndefined
|
||||
}
|
||||
}
|
||||
|
||||
// /subscriptions/*/resourceGroups/*/providers/Microsoft.ContainerInstance/containerGroups/*
|
||||
func azureContainerGroupOperationAuthorization(url, method string) portainer.Authorization {
|
||||
switch method {
|
||||
case http.MethodPut:
|
||||
return portainer.OperationAzureContainerGroupCreate
|
||||
case http.MethodGet:
|
||||
return portainer.OperationAzureContainerGroupGet
|
||||
case http.MethodDelete:
|
||||
return portainer.OperationAzureContainerGroupDelete
|
||||
default:
|
||||
return portainer.OperationAzureUndefined
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ func authorizedOperation(operation *portainer.APIOperationAuthorizationRequest)
|
||||
var dockerRule = regexp.MustCompile(`/(?P<identifier>\d+)/docker(?P<operation>/.*)`)
|
||||
var storidgeRule = regexp.MustCompile(`/(?P<identifier>\d+)/storidge(?P<operation>/.*)`)
|
||||
var k8sRule = regexp.MustCompile(`/(?P<identifier>\d+)/kubernetes(?P<operation>/.*)`)
|
||||
var azureRule = regexp.MustCompile(`/(?P<identifier>\d+)/azure(?P<operation>/.*)`)
|
||||
|
||||
func extractMatches(regex *regexp.Regexp, str string) map[string]string {
|
||||
match := regex.FindStringSubmatch(str)
|
||||
@@ -49,6 +50,9 @@ func getOperationAuthorization(url, method string) portainer.Authorization {
|
||||
// the current endpoint. The namespace + resource authorization
|
||||
// is done in the k8s level.
|
||||
return portainer.OperationK8sResourcePoolsR
|
||||
} else if azureRule.MatchString(url) {
|
||||
match := azureRule.FindStringSubmatch(url)
|
||||
return getAzureOperationAuthorization(strings.TrimPrefix(url, "/"+match[1]+"/azure"), method)
|
||||
}
|
||||
|
||||
return getPortainerOperationAuthorization(url, method)
|
||||
|
||||
Reference in New Issue
Block a user