mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(extensions): remove rbac extension (#4157)
* feat(extensions): remove rbac extension client code * feat(extensions): remove server rbac code * remove extensions code * fix(notifications): remove error * feat(extensions): remove authorizations service * feat(rbac): deprecate fields * fix(portainer): revert change * fix(bouncer): remove rbac authorization check * feat(sidebar): remove roles link * fix(portainer): remove portainer module
This commit is contained in:
parent
8629738e34
commit
9d18d47194
117 changed files with 98 additions and 3487 deletions
|
@ -14,9 +14,8 @@ import (
|
|||
type (
|
||||
// RequestBouncer represents an entity that manages API request accesses
|
||||
RequestBouncer struct {
|
||||
dataStore portainer.DataStore
|
||||
jwtService portainer.JWTService
|
||||
rbacExtensionClient *rbacExtensionClient
|
||||
dataStore portainer.DataStore
|
||||
jwtService portainer.JWTService
|
||||
}
|
||||
|
||||
// RestrictedRequestContext is a data structure containing information
|
||||
|
@ -30,11 +29,10 @@ type (
|
|||
)
|
||||
|
||||
// NewRequestBouncer initializes a new RequestBouncer
|
||||
func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService, rbacExtensionURL string) *RequestBouncer {
|
||||
func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService) *RequestBouncer {
|
||||
return &RequestBouncer{
|
||||
dataStore: dataStore,
|
||||
jwtService: jwtService,
|
||||
rbacExtensionClient: newRBACExtensionClient(rbacExtensionURL),
|
||||
dataStore: dataStore,
|
||||
jwtService: jwtService,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,8 +45,7 @@ func (bouncer *RequestBouncer) PublicAccess(h http.Handler) http.Handler {
|
|||
|
||||
// AdminAccess defines a security check for API endpoints that require an authorization check.
|
||||
// Authentication is required to access these endpoints.
|
||||
// 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.
|
||||
// The administrator role is required to use these endpoints.
|
||||
// The request context will be enhanced with a RestrictedRequestContext object
|
||||
// that might be used later to inside the API operation for extra authorization validation
|
||||
// and resource filtering.
|
||||
|
@ -61,8 +58,6 @@ func (bouncer *RequestBouncer) AdminAccess(h http.Handler) http.Handler {
|
|||
|
||||
// RestrictedAccess defines a security check for restricted API endpoints.
|
||||
// Authentication is required to access these endpoints.
|
||||
// If the RBAC extension is enabled, authorizations are required to use these endpoints.
|
||||
// If the RBAC extension is not enabled, access is granted to any authenticated user.
|
||||
// The request context will be enhanced with a RestrictedRequestContext object
|
||||
// that might be used later to inside the API operation for extra authorization validation
|
||||
// and resource filtering.
|
||||
|
@ -86,11 +81,9 @@ func (bouncer *RequestBouncer) AuthenticatedAccess(h http.Handler) http.Handler
|
|||
|
||||
// AuthorizedEndpointOperation retrieves the JWT token from the request context and verifies
|
||||
// that the user can access the specified endpoint.
|
||||
// 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 {
|
||||
func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint) error {
|
||||
tokenData, err := RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -114,13 +107,6 @@ func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endp
|
|||
return httperrors.ErrEndpointAccessDenied
|
||||
}
|
||||
|
||||
if authorizationCheck {
|
||||
err = bouncer.checkEndpointOperationAuthorization(r, endpoint)
|
||||
if err != nil {
|
||||
return ErrAuthorizationRequired
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -142,38 +128,6 @@ func (bouncer *RequestBouncer) AuthorizedEdgeEndpointOperation(r *http.Request,
|
|||
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.dataStore.Extension().Extension(portainer.RBACExtension)
|
||||
if err == bolterrors.ErrObjectNotFound {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := bouncer.dataStore.User().User(tokenData.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiOperation := &portainer.APIOperationAuthorizationRequest{
|
||||
Path: r.URL.String(),
|
||||
Method: r.Method,
|
||||
Authorizations: user.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.
|
||||
|
@ -206,9 +160,8 @@ func (bouncer *RequestBouncer) mwAuthenticatedUser(h http.Handler) http.Handler
|
|||
}
|
||||
|
||||
// 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.
|
||||
// If the administratorOnly flag is specified and the RBAC extension is not enabled, this will prevent non-admin
|
||||
// a specific API endpoint.
|
||||
// If the administratorOnly flag is specified, this will prevent non-admin
|
||||
// users from accessing the endpoint.
|
||||
func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler, administratorOnly bool) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -223,21 +176,12 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler,
|
|||
return
|
||||
}
|
||||
|
||||
extension, err := bouncer.dataStore.Extension().Extension(portainer.RBACExtension)
|
||||
if err == bolterrors.ErrObjectNotFound {
|
||||
if administratorOnly {
|
||||
httperror.WriteError(w, http.StatusForbidden, "Access denied", httperrors.ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
if administratorOnly {
|
||||
httperror.WriteError(w, http.StatusForbidden, "Access denied", httperrors.ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := bouncer.dataStore.User().User(tokenData.ID)
|
||||
_, err = bouncer.dataStore.User().User(tokenData.ID)
|
||||
if err != nil && err == bolterrors.ErrObjectNotFound {
|
||||
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", httperrors.ErrUnauthorized)
|
||||
return
|
||||
|
@ -246,19 +190,6 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler,
|
|||
return
|
||||
}
|
||||
|
||||
apiOperation := &portainer.APIOperationAuthorizationRequest{
|
||||
Path: r.URL.String(),
|
||||
Method: r.Method,
|
||||
Authorizations: user.PortainerAuthorizations,
|
||||
}
|
||||
|
||||
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey)
|
||||
err = bouncer.rbacExtensionClient.checkAuthorization(apiOperation)
|
||||
if err != nil {
|
||||
httperror.WriteError(w, http.StatusForbidden, "Access denied", ErrAuthorizationRequired)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
package security
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHTTPTimeout = 5
|
||||
)
|
||||
|
||||
type rbacExtensionClient struct {
|
||||
httpClient *http.Client
|
||||
extensionURL string
|
||||
licenseKey string
|
||||
}
|
||||
|
||||
func newRBACExtensionClient(extensionURL string) *rbacExtensionClient {
|
||||
return &rbacExtensionClient{
|
||||
extensionURL: extensionURL,
|
||||
httpClient: &http.Client{
|
||||
Timeout: time.Second * time.Duration(defaultHTTPTimeout),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (client *rbacExtensionClient) setLicenseKey(licenseKey string) {
|
||||
client.licenseKey = licenseKey
|
||||
}
|
||||
|
||||
func (client *rbacExtensionClient) checkAuthorization(authRequest *portainer.APIOperationAuthorizationRequest) error {
|
||||
encodedAuthRequest, err := json.Marshal(authRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", client.extensionURL+"/authorized_operation", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("X-RBAC-AuthorizationRequest", string(encodedAuthRequest))
|
||||
req.Header.Set("X-PortainerExtension-License", client.licenseKey)
|
||||
|
||||
resp, err := client.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return ErrAuthorizationRequired
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue