1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 05:45:22 +02:00

feat(extensions): introduce RBAC extension (#2900)

This commit is contained in:
Anthony Lapenna 2019-05-24 18:04:58 +12:00 committed by GitHub
parent 27a0188949
commit 8057aa45c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
196 changed files with 3321 additions and 1316 deletions

View file

@ -14,7 +14,10 @@ type (
jwtService portainer.JWTService
userService portainer.UserService
teamMembershipService portainer.TeamMembershipService
endpointService portainer.EndpointService
endpointGroupService portainer.EndpointGroupService
extensionService portainer.ExtensionService
rbacExtensionClient *rbacExtensionClient
authDisabled bool
}
@ -23,7 +26,10 @@ type (
JWTService portainer.JWTService
UserService portainer.UserService
TeamMembershipService portainer.TeamMembershipService
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
ExtensionService portainer.ExtensionService
RBACExtensionURL string
AuthDisabled bool
}
@ -43,48 +49,49 @@ func NewRequestBouncer(parameters *RequestBouncerParams) *RequestBouncer {
jwtService: parameters.JWTService,
userService: parameters.UserService,
teamMembershipService: parameters.TeamMembershipService,
endpointService: parameters.EndpointService,
endpointGroupService: parameters.EndpointGroupService,
extensionService: parameters.ExtensionService,
rbacExtensionClient: newRBACExtensionClient(parameters.RBACExtensionURL),
authDisabled: parameters.AuthDisabled,
}
}
// PublicAccess defines a security check for public endpoints.
// PublicAccess defines a security check for public API endpoints.
// No authentication is required to access these endpoints.
func (bouncer *RequestBouncer) PublicAccess(h http.Handler) http.Handler {
h = mwSecureHeaders(h)
return h
}
// AuthenticatedAccess defines a security check for private endpoints.
// AuthorizedAccess defines a security check for API endpoints that require an authorization check.
// Authentication is required to access these endpoints.
func (bouncer *RequestBouncer) AuthenticatedAccess(h http.Handler) http.Handler {
h = bouncer.mwCheckAuthentication(h)
h = mwSecureHeaders(h)
// If the RBAC extension is enabled, authorizations are required to use these endpoints.
// If the RBAC extension is not enabled, the administrator role is required to use these endpoints.
func (bouncer *RequestBouncer) AuthorizedAccess(h http.Handler) http.Handler {
h = bouncer.mwUpgradeToRestrictedRequest(h)
h = bouncer.mwCheckPortainerAuthorizations(h)
h = bouncer.mwAuthenticatedUser(h)
return h
}
// RestrictedAccess defines a security check for restricted endpoints.
// RestrictedAccess defines a security check for restricted API endpoints.
// Authentication is required to access these endpoints.
// The request context will be enhanced with a RestrictedRequestContext object
// that might be used later to authorize/filter access to resources.
// that might be used later to authorize/filter access to resources inside an endpoint.
func (bouncer *RequestBouncer) RestrictedAccess(h http.Handler) http.Handler {
h = bouncer.mwUpgradeToRestrictedRequest(h)
h = bouncer.AuthenticatedAccess(h)
h = bouncer.mwAuthenticatedUser(h)
return h
}
// AdministratorAccess defines a chain of middleware for restricted endpoints.
// Authentication as well as administrator role are required to access these endpoints.
func (bouncer *RequestBouncer) AdministratorAccess(h http.Handler) http.Handler {
h = mwCheckAdministratorRole(h)
h = bouncer.AuthenticatedAccess(h)
return h
}
// EndpointAccess retrieves the JWT token from the request context and verifies
// AuthorizedEndpointOperation retrieves the JWT token from the request context and verifies
// that the user can access the specified endpoint.
// An error is returned when access is denied.
func (bouncer *RequestBouncer) EndpointAccess(r *http.Request, endpoint *portainer.Endpoint) error {
// If the RBAC extension is enabled and the authorizationCheck flag is set,
// it will also validate that the user can execute the specified operation.
// An error is returned when access to the endpoint is denied or if the user do not have the required
// authorization to execute the operation.
func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint, authorizationCheck bool) error {
tokenData, err := RetrieveTokenData(r)
if err != nil {
return err
@ -108,9 +115,43 @@ func (bouncer *RequestBouncer) EndpointAccess(r *http.Request, endpoint *portain
return portainer.ErrEndpointAccessDenied
}
if authorizationCheck {
err = bouncer.checkEndpointOperationAuthorization(r, endpoint)
if err != nil {
return portainer.ErrAuthorizationRequired
}
}
return nil
}
func (bouncer *RequestBouncer) checkEndpointOperationAuthorization(r *http.Request, endpoint *portainer.Endpoint) error {
tokenData, err := RetrieveTokenData(r)
if err != nil {
return err
}
if tokenData.Role == portainer.AdministratorRole {
return nil
}
extension, err := bouncer.extensionService.Extension(portainer.RBACExtension)
if err == portainer.ErrObjectNotFound {
return nil
} else if err != nil {
return err
}
apiOperation := &portainer.APIOperationAuthorizationRequest{
Path: r.URL.String(),
Method: r.Method,
Authorizations: tokenData.EndpointAuthorizations[endpoint.ID],
}
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey)
return bouncer.rbacExtensionClient.checkAuthorization(apiOperation)
}
// RegistryAccess retrieves the JWT token from the request context and verifies
// that the user can access the specified registry.
// An error is returned when access is denied.
@ -136,11 +177,50 @@ func (bouncer *RequestBouncer) RegistryAccess(r *http.Request, registry *portain
return nil
}
// mwSecureHeaders provides secure headers middleware for handlers.
func mwSecureHeaders(next http.Handler) http.Handler {
func (bouncer *RequestBouncer) mwAuthenticatedUser(h http.Handler) http.Handler {
h = bouncer.mwCheckAuthentication(h)
h = mwSecureHeaders(h)
return h
}
// mwCheckPortainerAuthorizations will verify that the user has the required authorization to access
// a specific API endpoint. It will leverage the RBAC extension authorization validation if the extension
// is enabled.
func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-XSS-Protection", "1; mode=block")
w.Header().Add("X-Content-Type-Options", "nosniff")
tokenData, err := RetrieveTokenData(r)
if err != nil {
httperror.WriteError(w, http.StatusForbidden, "Access denied", portainer.ErrResourceAccessDenied)
return
}
if tokenData.Role == portainer.AdministratorRole {
next.ServeHTTP(w, r)
return
}
extension, err := bouncer.extensionService.Extension(portainer.RBACExtension)
if err == portainer.ErrObjectNotFound {
next.ServeHTTP(w, r)
return
} else if err != nil {
httperror.WriteError(w, http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err)
return
}
apiOperation := &portainer.APIOperationAuthorizationRequest{
Path: r.URL.String(),
Method: r.Method,
Authorizations: tokenData.PortainerAuthorizations,
}
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey)
err = bouncer.rbacExtensionClient.checkAuthorization(apiOperation)
if err != nil {
httperror.WriteError(w, http.StatusForbidden, "Access denied", portainer.ErrAuthorizationRequired)
return
}
next.ServeHTTP(w, r)
})
}
@ -166,19 +246,6 @@ func (bouncer *RequestBouncer) mwUpgradeToRestrictedRequest(next http.Handler) h
})
}
// mwCheckAdministratorRole check the role of the user associated to the request
func mwCheckAdministratorRole(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenData, err := RetrieveTokenData(r)
if err != nil || tokenData.Role != portainer.AdministratorRole {
httperror.WriteError(w, http.StatusForbidden, "Access denied", portainer.ErrResourceAccessDenied)
return
}
next.ServeHTTP(w, r)
})
}
// mwCheckAuthentication provides Authentication middleware for handlers
func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -229,6 +296,15 @@ func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Han
})
}
// mwSecureHeaders provides secure headers middleware for handlers.
func mwSecureHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-XSS-Protection", "1; mode=block")
w.Header().Add("X-Content-Type-Options", "nosniff")
next.ServeHTTP(w, r)
})
}
func (bouncer *RequestBouncer) newRestrictedContextRequest(userID portainer.UserID, userRole portainer.UserRole) (*RestrictedRequestContext, error) {
requestContext := &RestrictedRequestContext{
IsAdmin: true,