Skip to content

Commit

Permalink
Fixes #8629: Allows generation-time javascript eval in directive para…
Browse files Browse the repository at this point in the history
…meters
  • Loading branch information
fanf authored and ncharles committed Jul 28, 2016
1 parent 6db6c37 commit 67b1d53
Show file tree
Hide file tree
Showing 8 changed files with 1,293 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ package com.normation.rudder.domain.appconfig

import com.normation.utils.HashcodeCaching
import java.util.regex.Pattern
import net.liftweb.common.Full
import net.liftweb.common.Failure
import net.liftweb.common.Box
import ca.mrvisser.sealerate

case class RudderWebPropertyName(value:String) extends HashcodeCaching

Expand All @@ -53,4 +57,29 @@ case class RudderWebProperty(
name : RudderWebPropertyName
, value : String
, description: String
)
)


/**
* A little domain language for feature switches
* (just enabled/disabled with the parsing)
*/
sealed trait FeatureSwitch { def name: String }
object FeatureSwitch {

final case object Enabled extends FeatureSwitch { override val name = "enabled" }
final case object Disabled extends FeatureSwitch { override val name = "disabled" }

final val all: Set[FeatureSwitch] = sealerate.values[FeatureSwitch]

def parse(value: String): Box[FeatureSwitch] = {
value match {
case null|"" => Failure("An empty or null string can not be parsed as a feature switch status")
case s => s.trim.toLowerCase match {
case Enabled.name => Full(Enabled)
case Disabled.name => Full(Disabled)
case _ => Failure(s"Cannot parse the given value as a valid feature switch status: '${value}'. Authorised values are: '${all.map( _.name).mkString(", ")}'")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ import com.normation.rudder.reports.AgentRunInterval
import com.normation.rudder.domain.logger.ComplianceDebugLogger
import com.normation.rudder.services.reports.CachedFindRuleNodeStatusReports
import com.normation.cfclerk.domain.BundleOrder
import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import com.normation.rudder.domain.appconfig.FeatureSwitch
import com.normation.inventory.domain.AixOS



Expand Down Expand Up @@ -123,6 +127,7 @@ trait DeploymentService extends Loggable {
agentRunSplaytime <- getAgentRunSplaytime() ?~! "Could not get agent run splaytime"
agentRunStartMinute <- getAgentRunStartMinute() ?~! "Could not get agent run start time (minute)"
agentRunStartHour <- getAgentRunStartHour() ?~! "Could not get agent run start time (hour)"
scriptEngineEnabled <- getScriptEngineEnabled() ?~! "Could not get if we should use the script engine to evaluate directive parameters"
fetch6Time = System.currentTimeMillis
_ = logger.trace(s"Fetched run infos in ${fetch4Time-fetch3Time}ms")

Expand Down Expand Up @@ -155,6 +160,7 @@ trait DeploymentService extends Loggable {
, globalSystemVariables
, globalRunInterval
, globalComplianceMode
, scriptEngineEnabled
) ?~! "Cannot build target configuration node"
timeBuildConfig = (System.currentTimeMillis - buildConfigTime)
_ = logger.debug(s"Node's target configuration built in ${timeBuildConfig}, start to update rule values.")
Expand Down Expand Up @@ -230,6 +236,8 @@ trait DeploymentService extends Loggable {
def getAgentRunSplaytime : () => Box[Int]
def getAgentRunStartHour : () => Box[Int]
def getAgentRunStartMinute : () => Box[Int]
def getScriptEngineEnabled : () => Box[FeatureSwitch]

/**
* Find all modified rules.
* For them, find all directives with variables
Expand Down Expand Up @@ -270,6 +278,7 @@ trait DeploymentService extends Loggable {
, globalSystemVariable : Map[String, Variable]
, globalAgentRun : AgentRunInterval
, globalComplianceMode : ComplianceMode
, scriptEngineEnabled : FeatureSwitch
) : Box[(Seq[NodeConfiguration])]

/**
Expand Down Expand Up @@ -383,6 +392,7 @@ class DeploymentServiceImpl (
, override val getAgentRunSplaytime: () => Box[Int]
, override val getAgentRunStartHour: () => Box[Int]
, override val getAgentRunStartMinute: () => Box[Int]
, override val getScriptEngineEnabled: () => Box[FeatureSwitch]
) extends DeploymentService with
DeploymentService_findDependantRules_bruteForce with
DeploymentService_buildRuleVals with
Expand Down Expand Up @@ -424,6 +434,7 @@ trait DeploymentService_findDependantRules_bruteForce extends DeploymentService
override def getAllInventories(): Box[Map[NodeId, NodeInventory]] = roInventoryRepository.getAllNodeInventories(AcceptedInventory)
override def getGlobalComplianceMode(): Box[ComplianceMode] = complianceModeService.getComplianceMode
override def getGlobalAgentRun(): Box[AgentRunInterval] = agentRunService.getGlobalAgentRun()

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -559,13 +570,14 @@ trait DeploymentService_buildNodeConfigurations extends DeploymentService with L
* allNodeInfos *must* contains the nodes info of every nodes
*/
override def buildNodeConfigurations(
ruleVals : Seq[RuleVal]
, allNodeInfos : Map[NodeId, NodeInfo]
, groupLib : FullNodeGroupCategory
, parameters : Seq[GlobalParameter]
, globalSystemVariables : Map[String, Variable]
, globalAgentRun : AgentRunInterval
, globalComplianceMode : ComplianceMode
ruleVals : Seq[RuleVal]
, allNodeInfos : Map[NodeId, NodeInfo]
, groupLib : FullNodeGroupCategory
, parameters : Seq[GlobalParameter]
, globalSystemVariables: Map[String, Variable]
, globalAgentRun : AgentRunInterval
, globalComplianceMode : ComplianceMode
, scriptEngineEnabled : FeatureSwitch
) : Box[Seq[NodeConfiguration]] = {


Expand Down Expand Up @@ -656,57 +668,71 @@ trait DeploymentService_buildNodeConfigurations extends DeploymentService with L
}

//1.3: build node config, binding ${rudder.parameters} parameters

val nodeConfigs = sequence(interpolationContexts.toSeq) { case (nodeId, context) =>

// open a scope for the JsEngine, because its init is long.
JsEngineProvider.withNewEngine(scriptEngineEnabled) { jsEngine =>
for {
drafts <- Box(policyDraftByNode.get(nodeId)) ?~! "Promise generation algorithme error: cannot find back the configuration information for a node"
/*
* Clearly, here, we are evaluating parameters, and we are not using that just after in the
* variable expansion, which mean that we are doing the same work again and again and again.
* Moreover, we also are evaluating again and again parameters whose context ONLY depends
* on other parameter, and not node config at all. Bad bad bad bad.
* TODO: two stages parameter evaluation
* - global
* - by node
* + use them in variable expansion (the variable expansion should have a fully evaluated InterpolationContext)
*/
parameters <- sequence(context.parameters.toSeq) { case (name, param) =>
for {
p <- param(context)
} yield {
(name, p)
}
}
cf3PolicyDrafts <- sequence(drafts) { draft =>
//bind variables
draft.variableMap(context).map{ expandedVariables =>

RuleWithCf3PolicyDraft(
ruleId = draft.ruleId
, directiveId = draft.directiveId
, technique = draft.technique
, variableMap = expandedVariables
, trackerVariable = draft.trackerVariable
, priority = draft.priority
, serial = draft.serial
, ruleOrder = draft.ruleOrder
, directiveOrder = draft.directiveOrder
)
}
}
nodeConfigs <- sequence(interpolationContexts.toSeq) { case (nodeId, context) =>
for {
drafts <- Box(policyDraftByNode.get(nodeId)) ?~! "Promise generation algorithme error: cannot find back the configuration information for a node"
/*
* Clearly, here, we are evaluating parameters, and we are not using that just after in the
* variable expansion, which mean that we are doing the same work again and again and again.
* Moreover, we also are evaluating again and again parameters whose context ONLY depends
* on other parameter, and not node config at all. Bad bad bad bad.
* TODO: two stages parameter evaluation
* - global
* - by node
* + use them in variable expansion (the variable expansion should have a fully evaluated InterpolationContext)
*/
parameters <- sequence(context.parameters.toSeq) { case (name, param) =>
for {
p <- param(context)
} yield {
(name, p)
}
}
cf3PolicyDrafts <- sequence(drafts) { draft =>
for {
//bind variables with interpolated context
expandedVariables <- draft.variableMap(context)
// And now, for each variable, eval - if needed - the result
evaluatedVars <- sequence(expandedVariables.toSeq) { case (k, v) =>
//js lib is specific to the node os, bind here to not leak eval between vars
val jsLib = context.nodeInfo.osDetails.os match {
case AixOS => JsRudderLibBinding.Aix
case _ => JsRudderLibBinding.Crypt
}
jsEngine.eval(v, jsLib).map( x => (k, x) )
}
} yield {

RuleWithCf3PolicyDraft(
ruleId = draft.ruleId
, directiveId = draft.directiveId
, technique = draft.technique
, variableMap = evaluatedVars.toMap
, trackerVariable = draft.trackerVariable
, priority = draft.priority
, serial = draft.serial
, ruleOrder = draft.ruleOrder
, directiveOrder = draft.directiveOrder
)
}
}
} yield {
NodeConfiguration(
nodeInfo = context.nodeInfo
, policyDrafts = cf3PolicyDrafts.toSet
, nodeContext = context.nodeContext
, parameters = parameters.map { case (k,v) => ParameterForConfiguration(k, v) }.toSet
, isRootServer = context.nodeInfo.id == context.policyServerInfo.id
)
}
}
} yield {
NodeConfiguration(
nodeInfo = context.nodeInfo
, policyDrafts = cf3PolicyDrafts.toSet
, nodeContext = context.nodeContext
, parameters = parameters.map { case (k,v) => ParameterForConfiguration(k, v) }.toSet
, isRootServer = context.nodeInfo.id == context.policyServerInfo.id
)
nodeConfigs
}
}

nodeConfigs
}

}
Expand Down
Loading

0 comments on commit 67b1d53

Please sign in to comment.