Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #24872: Rework api authorization models #5669

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -226,8 +226,10 @@ trait EndpointSchema {
// data container name: the expected object key in answer
def dataContainer: Option[String]

// any authorization that allows to access that API - by default, admin.write
def authz: List[AuthorizationType] = List(AuthorizationType.Administration.Write)
// any authorization that allows to access that API (with a "OR" semantic)
// Nil means special `any_righs`, ie special admin role, is needed, so
// that removing the last right effectively remove all permissions
def authz: List[AuthorizationType]
}

trait EndpointSchema0 extends EndpointSchema {

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -40,7 +40,6 @@ package com.normation.rudder.rest
import com.normation.rudder.AuthorizationType
import com.normation.rudder.Rights
import com.normation.rudder.Role
import com.normation.rudder.api.AclPathSegment
import com.normation.rudder.api.ApiAclElement
import com.normation.rudder.api.ApiAuthorization as ApiAuthz

@@ -61,6 +60,7 @@ class AuthorizationMappingListEndpoint(endpoints: List[EndpointSchema]) extends

override def mapAuthorization(authz: AuthorizationType): List[ApiAclElement] = {
acls.get(authz).getOrElse(Nil)

}
}

@@ -73,14 +73,17 @@ class ExtensibleAuthorizationApiMapping(base: List[AuthorizationApiMapping]) ext
private var mappers: List[AuthorizationApiMapping] = base

def addMapper(mapper: AuthorizationApiMapping): Unit = {
// no need to add again and again the default mapper - it's ok, we have it.
if (mapper != AuthorizationApiMapping.OnlyAdmin) {
mappers = mappers :+ mapper
}
mappers = mappers :+ mapper
}

override def mapAuthorization(authz: AuthorizationType): List[ApiAclElement] = {
mappers.flatMap(_.mapAuthorization(authz))
import AuthorizationType.*
authz match {
case NoRights => Nil
case AnyRights => ApiAuthz.allAuthz.acl
case _ =>
mappers.flatMap(_.mapAuthorization(authz))
}
}
}

@@ -89,161 +92,6 @@ object AuthorizationApiMapping {
def x: ApiAclElement = AuthzForApi(api)
}

/*
* A default mapping for "only 'all rights' (ie admin) can access it"
*/
case object OnlyAdmin extends AuthorizationApiMapping {
override def mapAuthorization(authz: AuthorizationType): List[ApiAclElement] = Nil
}

/*
* The core authorization/api mapping, ie the authorization for Rudder
* default API.
*/
object Core extends AuthorizationApiMapping {

override def mapAuthorization(authz: AuthorizationType): List[ApiAclElement] = {
import AuthorizationType.*
// shorthand to get authz for a given api
authz match {
case NoRights => Nil
case AnyRights => ApiAuthz.allAuthz.acl
// Administration is Rudder setting

case Administration.Read =>
EventLogApi.GetEventLogs.x :: EventLogApi.GetEventLogDetails.x :: SettingsApi.GetAllSettings.x :: SettingsApi.GetSetting.x :: SystemApi.ArchivesDirectivesList.x ::
SystemApi.ArchivesFullList.x :: SystemApi.ArchivesGroupsList.x :: SystemApi.ArchivesRulesList.x ::
SystemApi.GetAllZipArchive.x :: SystemApi.GetDirectivesZipArchive.x :: SystemApi.GetGroupsZipArchive.x ::
SystemApi.GetRulesZipArchive.x :: SystemApi.Info.x :: SystemApi.Status.x :: SystemApi.ArchivesParametersList.x ::
SystemApi.GetParametersZipArchive.x :: SystemApi.GetHealthcheckResult.x :: PluginApi.GetPluginsSettings.x ::
SettingsApi.GetAllowedNetworks.x :: SettingsApi.GetAllAllowedNetworks.x :: HookApi.GetHooks.x :: InfoApi.endpoints.map(
_.x
)
case Administration.Write =>
EventLogApi.RollbackEventLog.x :: PluginApi.UpdatePluginsSettings.x :: SettingsApi.ModifySettings.x :: SettingsApi.ModifySetting.x ::
InventoryApi.FileWatcherRestart.x :: InventoryApi.FileWatcherStart.x :: InventoryApi.FileWatcherStop.x ::
NodeApi.CreateNodes.x :: SystemApi.endpoints.map(_.x)
case Administration.Edit =>
PluginApi.UpdatePluginsSettings.x :: SettingsApi.ModifySettings.x :: SettingsApi.ModifySetting.x ::
SettingsApi.ModifyAllowedNetworks.x :: SettingsApi.ModifyDiffAllowedNetworks.x ::
Nil

case Compliance.Read =>
ComplianceApi.GetGlobalCompliance.x :: ComplianceApi.GetRulesCompliance.x :: ComplianceApi.GetRulesComplianceId.x ::
ComplianceApi.GetNodesCompliance.x :: ComplianceApi.GetNodeComplianceId.x :: ChangesApi.GetRuleRepairedReports.x ::
ChangesApi.GetRecentChanges.x :: ComplianceApi.GetDirectiveComplianceId.x :: ComplianceApi.GetNodeSystemCompliance.x ::
ComplianceApi.GetDirectivesCompliance.x :: ComplianceApi.GetNodeGroupComplianceId.x :: ComplianceApi.GetNodeGroupComplianceTargetId.x ::
ComplianceApi.GetNodeGroupComplianceSummary.x :: Nil
case Compliance.Write => Nil
case Compliance.Edit => Nil

case Configuration.Read =>
(Parameter.Read :: Technique.Read :: Directive.Read :: Rule.Read :: Nil).flatMap(c => mapAuthorization(c))
case Configuration.Write =>
(Parameter.Write :: Technique.Write :: Directive.Write :: Rule.Write :: Nil).flatMap(c => mapAuthorization(c))
case Configuration.Edit =>
(Parameter.Edit :: Technique.Edit :: Directive.Edit :: Rule.Edit :: Nil).flatMap(c => mapAuthorization(c))

case Deployment.Read => Nil
case Deployment.Write => Nil
case Deployment.Edit => Nil

case Deployer.Read => Nil // ChangeRequestApi.ListChangeRequests.x :: ChangeRequestApi.ChangeRequestsDetails.x :: Nil
case Deployer.Write => Nil // ChangeRequestApi.DeclineRequestsDetails.x :: ChangeRequestApi.AcceptRequestsDetails.x :: Nil
case Deployer.Edit => Nil // ChangeRequestApi.UpdateRequestsDetails.x :: Nil

case Parameter.Read => ParameterApi.ListParameters.x :: ParameterApi.ParameterDetails.x :: Nil
case Parameter.Write => ParameterApi.CreateParameter.x :: ParameterApi.DeleteParameter.x :: Nil
case Parameter.Edit => ParameterApi.UpdateParameter.x :: Nil

case Directive.Read =>
DirectiveApi.ListDirectives.x :: DirectiveApi.DirectiveDetails.x ::
DirectiveApi.DirectiveTree.x :: DirectiveApi.DirectiveRevisions.x ::
Nil
case Directive.Write =>
DirectiveApi.CreateDirective.x :: DirectiveApi.DeleteDirective.x ::
DirectiveApi.CheckDirective.x :: Nil
case Directive.Edit => DirectiveApi.UpdateDirective.x :: Nil

case Group.Read =>
GroupApi.ListGroups.x :: GroupApi.GroupDetails.x :: GroupApi.GetGroupTree.x ::
GroupApi.GetGroupCategoryDetails.x :: GroupApi.GroupInheritedProperties.x ::
NodeApi.NodeDetailsTable.x :: GroupApi.GroupDisplayInheritedProperties.x ::
GroupInternalApi.GetGroupCategoryTree.x :: Nil
case Group.Write =>
GroupApi.CreateGroup.x :: GroupApi.DeleteGroup.x :: GroupApi.ReloadGroup.x ::
GroupApi.DeleteGroupCategory.x :: GroupApi.CreateGroupCategory.x :: Nil
case Group.Edit => GroupApi.UpdateGroup.x :: GroupApi.UpdateGroupCategory.x :: Nil

case Node.Read =>
NodeApi.ListAcceptedNodes.x :: NodeApi.ListPendingNodes.x :: NodeApi.NodeDetails.x ::
NodeApi.NodeInheritedProperties.x :: NodeApi.NodeDisplayInheritedProperties.x :: NodeApi.NodeDetailsTable.x ::
NodeApi.PendingNodeDetails.x :: NodeApi.NodeDetailsSoftware.x :: NodeApi.NodeDetailsProperty.x ::
NodeApi.GetNodesStatus.x :: InventoryApi.QueueInformation.x ::
NodeApi.NodeGlobalScore.x :: NodeApi.NodeScoreDetail.x :: NodeApi.NodeScoreDetails.x ::
NodeApi.GetNodesStatus.x ::
// score about node
NodeApi.NodeGlobalScore.x :: NodeApi.NodeScoreDetails.x :: NodeApi.NodeScoreDetail.x ::
InventoryApi.QueueInformation.x ::
// this compliance is more about how rudder works on that node, it's not really "compliance"
ComplianceApi.GetNodeSystemCompliance.x ::
// node read also allows to read some settings
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("global_policy_mode") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("global_policy_mode_overridable") :: Nil) ::
ScoreApi.GetScoreList.x ::
Nil
case Node.Write =>
NodeApi.DeleteNode.x :: NodeApi.ChangePendingNodeStatus.x :: NodeApi.ChangePendingNodeStatus2.x ::
NodeApi.ApplyPolicyAllNodes.x :: NodeApi.ApplyPolicy.x :: Nil
case Node.Edit => NodeApi.UpdateNode.x :: InventoryApi.UploadInventory.x :: Nil

case Rule.Read =>
RuleApi.ListRules.x :: RuleApi.RuleDetails.x :: RuleApi.GetRuleTree.x ::
RuleApi.GetRuleCategoryDetails.x :: RuleInternalApi.GetRuleNodesAndDirectives.x :: RuleInternalApi.GetGroupRelatedRules.x ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_change_message") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_change_request") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_self_deployment") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_self_validation") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_validate_all") :: Nil) :: Nil
case Rule.Write =>
RuleApi.CreateRule.x :: RuleApi.DeleteRule.x :: RuleApi.CreateRuleCategory.x ::
RuleApi.DeleteRuleCategory.x :: RuleApi.LoadRuleRevisionForGeneration.x :: RuleApi.UnloadRuleRevisionForGeneration.x ::
Nil
case Rule.Edit => RuleApi.UpdateRule.x :: RuleApi.UpdateRuleCategory.x :: Nil

case Technique.Read =>
TechniqueApi.ListTechniques.x :: TechniqueApi.ListTechniquesDirectives.x ::
TechniqueApi.ListTechniqueDirectives.x :: TechniqueApi.TechniqueRevisions.x ::
TechniqueApi.GetMethods.x :: TechniqueApi.GetTechniques.x ::
TechniqueApi.GetAllTechniqueCategories.x :: TechniqueApi.GetResources.x :: TechniqueApi.GetNewResources.x ::
TechniqueApi.GetTechniqueAllVersion.x :: TechniqueApi.GetTechnique.x :: Nil
case Technique.Write =>
TechniqueApi.CreateTechnique.x :: SystemApi.PoliciesUpdate.x :: SystemApi.PoliciesRegenerate.x ::
TechniqueApi.DeleteTechnique.x :: Nil
case Technique.Edit =>
TechniqueApi.UpdateTechnique.x :: SystemApi.PoliciesUpdate.x :: SystemApi.PoliciesRegenerate.x ::
TechniqueApi.UpdateTechniques.x :: TechniqueApi.UpdateMethods.x :: Nil

case UserAccount.Read => UserApi.GetApiToken.x :: UserApi.GetTokenFeatureStatus.x :: Nil
case UserAccount.Write => UserApi.CreateApiToken.x :: UserApi.DeleteApiToken.x :: Nil
case UserAccount.Edit => UserApi.UpdateApiToken.x :: Nil

case Validator.Read =>
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_change_message") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("mandatory_change_message") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("change_message_prompt") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_change_request") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_self_deployment") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_self_validation") :: Nil) ::
AuthzForApi.withValues(SettingsApi.GetSetting, AclPathSegment.Segment("enable_validate_all") :: Nil) :: Nil

case Validator.Write =>
Nil // ChangeRequestApi.DeclineRequestsDetails.x :: ChangeRequestApi.AcceptRequestsDetails.x :: Nil
case Validator.Edit => Nil // ChangeRequestApi.UpdateRequestsDetails.x :: Nil
case _ => Nil // Done within plugin
}
}
}
}

/*
Original file line number Diff line number Diff line change
@@ -128,8 +128,8 @@ object UserManagementApi extends Enum[UserManagementApi] with ApiModuleProvider[
val z = implicitly[Line].value
val description = "Reload (read again rudder-users.xml and process result) information about registered users in Rudder"
val (action, path) = POST / "usermanagement" / "users" / "reload"

override def dataContainer: Option[String] = None
override def dataContainer: Option[String] = None
val authz: List[AuthorizationType] = AuthorizationType.UserAccount.Write :: Nil
}

final case object DeleteUser extends UserManagementApi with OneParam with StartsAtVersion10 {
@@ -138,6 +138,8 @@ object UserManagementApi extends Enum[UserManagementApi] with ApiModuleProvider[
val (action, path) = DELETE / "usermanagement" / "{username}"

override def dataContainer: Option[String] = None

val authz: List[AuthorizationType] = AuthorizationType.UserAccount.Write :: Nil
}

final case object AddUser extends UserManagementApi with ZeroParam with StartsAtVersion10 {
@@ -146,6 +148,8 @@ object UserManagementApi extends Enum[UserManagementApi] with ApiModuleProvider[
val (action, path) = POST / "usermanagement"

override def dataContainer: Option[String] = None

val authz: List[AuthorizationType] = AuthorizationType.UserAccount.Write :: Nil
}

final case object UpdateUser extends UserManagementApi with OneParam with StartsAtVersion10 {
@@ -154,6 +158,8 @@ object UserManagementApi extends Enum[UserManagementApi] with ApiModuleProvider[
val (action, path) = POST / "usermanagement" / "update" / "{username}"

override def dataContainer: Option[String] = None

val authz: List[AuthorizationType] = AuthorizationType.UserAccount.Write :: Nil
}

final case object UpdateUserInfo extends UserManagementApi with OneParam with StartsAtVersion10 {
@@ -162,6 +168,8 @@ object UserManagementApi extends Enum[UserManagementApi] with ApiModuleProvider[
val (action, path) = POST / "usermanagement" / "update" / "info" / "{username}"

override def dataContainer: Option[String] = None

val authz: List[AuthorizationType] = AuthorizationType.UserAccount.Write :: Nil
}

final case object ActivateUser extends UserManagementApi with OneParam with StartsAtVersion10 {
@@ -170,6 +178,8 @@ object UserManagementApi extends Enum[UserManagementApi] with ApiModuleProvider[
val (action, path) = PUT / "usermanagement" / "status" / "activate" / "{username}"

override def dataContainer: Option[String] = None

val authz: List[AuthorizationType] = AuthorizationType.UserAccount.Write :: Nil
}

final case object DisableUser extends UserManagementApi with OneParam with StartsAtVersion10 {
@@ -178,6 +188,8 @@ object UserManagementApi extends Enum[UserManagementApi] with ApiModuleProvider[
val (action, path) = PUT / "usermanagement" / "status" / "disable" / "{username}"

override def dataContainer: Option[String] = None

val authz: List[AuthorizationType] = AuthorizationType.UserAccount.Write :: Nil
}

final case object RoleCoverage extends UserManagementApi with OneParam with StartsAtVersion10 {
@@ -186,6 +198,8 @@ object UserManagementApi extends Enum[UserManagementApi] with ApiModuleProvider[
val (action, path) = POST / "usermanagement" / "coverage" / "{username}"

override def dataContainer: Option[String] = None

val authz: List[AuthorizationType] = AuthorizationType.UserAccount.Read :: Nil
}

def endpoints = values.toList.sortBy(_.z)
Original file line number Diff line number Diff line change
@@ -75,7 +75,7 @@ import com.normation.rudder.repository.FullNodeGroupCategory
import com.normation.rudder.repository.RoNodeGroupRepository
import com.normation.rudder.repository.RoRuleRepository
import com.normation.rudder.repository.WoRuleRepository
import com.normation.rudder.rest.AuthorizationApiMapping
import com.normation.rudder.rest.ExtensibleAuthorizationApiMapping
import com.normation.rudder.rest.ProviderRoleExtension
import com.normation.rudder.rest.RoleApiMapping
import com.normation.rudder.rest.data.ApiAccountDetails
@@ -876,7 +876,7 @@ class MockUserManagement(userInfos: List[UserInfo], userSessions: List[UserSessi
val userService: FileUserDetailListProvider = {
val usersFile = UserFile(usersConfigFile.pathAsString, usersInputStream)

val roleApiMapping = new RoleApiMapping(AuthorizationApiMapping.Core)
val roleApiMapping = new RoleApiMapping(new ExtensibleAuthorizationApiMapping(Nil))

val res = new FileUserDetailListProvider(roleApiMapping, usersFile, passwordEncoderDispatcher)
res.reload()
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@ import com.normation.rudder.domain.logger.ApplicationLogger
import com.normation.rudder.domain.logger.ApplicationLoggerPure
import com.normation.rudder.domain.logger.PluginLogger
import com.normation.rudder.rest.ApiModuleProvider
import com.normation.rudder.rest.AuthorizationMappingListEndpoint
import com.normation.rudder.rest.EndpointSchema
import com.normation.rudder.rest.InfoApi as InfoApiDef
import com.normation.rudder.rest.data.JsonGlobalPluginLimits
@@ -534,13 +535,15 @@ class Boot extends Loggable {
LiftRules.statelessDispatch.append(RudderConfig.sharedFileApi)
// REST API (all public/internal API)
// we need to add "info" API here to have all used API (even plugins)
val infoApi = {
val infoApi = {
// all used api - add info as it is not yet declared
val schemas = RudderConfig.rudderApi.apis().map(_.schema) ++ InfoApiDef.endpoints
val endpoints = schemas.flatMap(RudderConfig.apiDispatcher.withVersion(_, RudderConfig.ApiVersions))
new InfoApi(RudderConfig.ApiVersions, endpoints)
}
RudderConfig.rudderApi.addModules(infoApi.getLiftEndpoints())
val apiRoleMapper = new AuthorizationMappingListEndpoint(RudderConfig.rudderApi.apis().map(_.schema))
RudderConfig.authorizationApiMapping.addMapper(apiRoleMapper)
LiftRules.statelessDispatch.append(RudderConfig.rudderApi.getLiftRestApi())

// URL rewrites
Original file line number Diff line number Diff line change
@@ -1593,7 +1593,7 @@ object RudderConfigInit {
lazy val authenticationProviders = new AuthBackendProvidersManager()

// Plugin input interface for Authorization for API
lazy val authorizationApiMapping = new ExtensibleAuthorizationApiMapping(AuthorizationApiMapping.Core :: Nil)
lazy val authorizationApiMapping = new ExtensibleAuthorizationApiMapping(Nil)

////////// end pluggable service providers //////////