Project

General

Profile

« Previous | Next » 

Revision 0f40d6a8

Added by Vincent MEMBRÉ almost 7 years ago

Fixes #10879: Adapt inventory processor so it can read agent certificate

View differences:

inventory-api/src/main/scala/com/normation/inventory/domain/AgentTypes.scala
*
*/
sealed abstract class AgentType {
def toString() : String
def fullname() : String = "CFEngine "+this
def toString : String
def fullname : String = "CFEngine "+this
// Tag used in fusion inventory ( > 2.3 )
lazy val tagValue = s"cfengine-${this}".toLowerCase
def toRulesPath() : String
def toRulesPath : String
// the name to look for in the inventory to know the agent version
def inventorySoftwareName: String
// and a transformation function from reported software version name to agent version name
def toAgentVersionName(softwareVersionName: String): String
}
final case object NOVA_AGENT extends AgentType with HashcodeCaching {
override def toString() = A_NOVA_AGENT
override def toRulesPath() = "/cfengine-nova"
override val inventorySoftwareName = "cfengine nova"
override def toAgentVersionName(softwareVersionName: String) = s"cfe-${softwareVersionName}"
}
final case object COMMUNITY_AGENT extends AgentType with HashcodeCaching {
override def toString() = A_COMMUNITY_AGENT
override def toRulesPath() = "/cfengine-community"
override val inventorySoftwareName = "rudder-agent"
override def toAgentVersionName(softwareVersionName: String) = softwareVersionName
}
object AgentType {
final case object DSC_AGENT extends AgentType with HashcodeCaching {
override def toString() = A_DSC_AGENT
override def toRulesPath() = "/dsc"
override val inventorySoftwareName = "Rudder agent"
override def toAgentVersionName(softwareVersionName: String) = softwareVersionName+" (dsc)"
}
final case object CfeEnterprise extends AgentType with HashcodeCaching {
override def toString = A_NOVA_AGENT
override def toRulesPath = "/cfengine-nova"
override val inventorySoftwareName = "cfengine nova"
override def toAgentVersionName(softwareVersionName: String) = s"cfe-${softwareVersionName}"
}
object AgentType {
def allValues = NOVA_AGENT :: COMMUNITY_AGENT :: DSC_AGENT :: Nil
final case object CfeCommunity extends AgentType with HashcodeCaching {
override def toString = A_COMMUNITY_AGENT
override def toRulesPath = "/cfengine-community"
override val inventorySoftwareName = "rudder-agent"
override def toAgentVersionName(softwareVersionName: String) = softwareVersionName
}
final case object Dsc extends AgentType with HashcodeCaching {
override def toString = A_DSC_AGENT
override def toRulesPath = "/dsc"
override val inventorySoftwareName = "Rudder agent"
override def toAgentVersionName(softwareVersionName: String) = softwareVersionName+" (dsc)"
}
def allValues = CfeEnterprise :: CfeCommunity :: Dsc :: Nil
def fromValue(value : String) : Box[AgentType] = {
// Check if the value is correct compared to the agent tag name (fusion > 2.3) or its toString value (added by CFEngine)
......
final case class AgentVersion(value: String)
final case class AgentInfo(
name : AgentType
agentType : AgentType
//for now, the version must be an option, because we don't add it in the inventory
//and must try to find it from packages
, version: Option[AgentVersion]
, version : Option[AgentVersion]
, securityToken : SecurityToken
)
object AgentInfoSerialisation {
import net.liftweb.json.JsonDSL._
import AgentType._
import net.liftweb.json._
implicit class ToJson(agent: AgentInfo) {
def toJsonString = compactRender(
("agentType" -> agent.name.toString())
~ ("version" -> agent.version.map( _.value ))
)
def toJsonString =
compactRender(
("agentType" -> agent.agentType.toString())
~ ("version" -> agent.version.map( _.value ))
~ ("securityToken" ->
("value" -> agent.securityToken.key)
~ ("type" -> SecurityToken.kind(agent.securityToken))
)
)
}
def parseSecurityToken(agentType : AgentType, tokenJson: JValue, tokenDefault : Option[String]) : Box[SecurityToken]= {
import net.liftweb.json.compactRender
def extractValue(tokenJson : JValue) = {
tokenJson \ "value" match {
case JString(s) => Some(s)
case _ => None
}
}
agentType match {
case Dsc => tokenJson \ "type" match {
case JString(Certificate.kind) => extractValue(tokenJson) match {
case Some(token) => Full(Certificate(token))
case None => Failure("No value defined for security token")
}
case JString(PublicKey.kind) => Failure("Cannot have a public Key for dsc agent, only a certificate is valid")
case JNothing => Failure("No value define for security token")
case invalidJson => Failure(s"Invalid value for security token, ${compactRender(invalidJson)}")
}
case _ => tokenJson \ "type" match {
case JString(Certificate.kind) => extractValue(tokenJson) match {
case Some(token) => Full(Certificate(token))
case None => Failure("No value defined for security token")
}
case JString(PublicKey.kind) => extractValue(tokenJson) match {
case Some(token) => Full(PublicKey(token))
case None => Failure("No value defined for security token")
}
case invalidJson =>
tokenDefault match {
case Some(default) => Full(PublicKey(default))
case None => Failure(s"Invalid value for security token, ${compactRender(invalidJson)}, and no public key were stored")
}
}
}
}
/*
......
* but version isn't, and even if we don't parse it correctly, we
* successfully return an agent (without version).
*/
def parseJson(s: String): Box[AgentInfo] = {
def parseJson(s: String, optToken : Option[String]): Box[AgentInfo] = {
for {
json <- try { Full(parse(s)) } catch { case ex: Exception => Failure(s"Can not parse agent info: ${ex.getMessage }", Full(ex), Empty) }
info <- (json \ "agentType") match {
case JString(tpe) => AgentType.fromValue(tpe).map { agentType =>
(json \ "version") match {
case JString(version) => AgentInfo(agentType, Some(AgentVersion(version)))
case _ => AgentInfo(agentType, None)
}
}
case _ => Failure(s"Error when trying to parse string as JSON Agent Info (missing required field 'agentType'): ${s}")
agentType <- (json \ "agentType") match {
case JString(tpe) => AgentType.fromValue(tpe)
case JNothing => Failure("No value defined for security token")
case invalidJson => Failure(s"Error when trying to parse string as JSON Agent Info (missing required field 'agentType'): ${compactRender(invalidJson)}")
}
agentVersion = json \ "version" match {
case JString(version) => Some(AgentVersion(version))
case _ => None
}
token <- json \ "securityToken" match {
case JObject(json) => parseSecurityToken(agentType, json, optToken)
case _ => parseSecurityToken(agentType, JNothing, optToken)
}
} yield {
info
AgentInfo(agentType, agentVersion, token)
}
}
......
* - try to parse in json: if ok, we have the new version
* - else, try to parse in old format, put None to version.
*/
def parseCompatNonJson(s: String): Box[AgentInfo] = {
parseJson(s).or(
def parseCompatNonJson(s: String, optToken : Option[String]): Box[AgentInfo] = {
parseJson(s, optToken).or(
for {
tpe <- AgentType.fromValue(s) ?~! (
s"Error when mapping '${s}' to an agent info. We are expecting either a json like "+
agentType <- AgentType.fromValue(s) ?~! (
s"Error when mapping '${s}' to an agent info. We are expecting either a json like "+
s"{'agentType': type, 'version': opt_version}, or an agentType with allowed values in ${AgentType.allValues.mkString(", ")}"
)
token <- parseSecurityToken(agentType, JNothing, optToken)
} yield {
AgentInfo(tpe, None)
AgentInfo(agentType, None, token)
}
)
}
inventory-api/src/main/scala/com/normation/inventory/domain/DataTypes.scala
package com.normation.inventory.domain
import com.normation.utils.Utils._
import com.normation.utils.HashcodeCaching
import org.bouncycastle.openssl.PEMParser
......
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import net.liftweb.common._
import org.bouncycastle.cert.X509CertificateHolder
/**
* A file that contains all the simple data types, like Version,
* MemorySize, Manufacturer, etc.
*/
/**
* A simple class to denote a manufacturer
* TODO : Should be merge with SoftwareEditor
......
*/
final case class SoftwareEditor(val name:String) extends HashcodeCaching { assert(!isEmpty(name)) }
sealed trait SecurityToken {
def key : String
}
case object SecurityToken {
def kind(token : SecurityToken) = {
token match {
case _: PublicKey => PublicKey.kind
case _: Certificate => Certificate.kind
}
}
}
object PublicKey {
val kind = "publicKey"
}
object Certificate {
val kind = "certificate"
}
/**
* A simple class to denote a software cryptographic public key
*/
final case class PublicKey(value : String) extends HashcodeCaching { assert(!isEmpty(value))
final case class PublicKey(value : String) extends SecurityToken with HashcodeCaching { assert(!isEmpty(value))
// Value of the key may be stored (with old fusion inventory version) as one line and without rsa header and footer, we should add them if missing and format the key
val key = {
......
}
final case class Certificate(value : String) extends SecurityToken with HashcodeCaching { assert(!isEmpty(value))
// Value of the key may be stored (with old fusion inventory version) as one line and without rsa header and footer, we should add them if missing and format the key
val key = {
if (value.startsWith("-----BEGIN CERTIFICATE-----")) {
value
} else {
s"""-----BEGIN CERTIFICATE-----
|${value.grouped(80).mkString("\n")}
|-----END CERTIFICATE-----""".stripMargin
}
}
def cert : Box[X509CertificateHolder] = {
try {
val reader = new PEMParser(new StringReader(key))
reader.readObject() match {
case a : X509CertificateHolder =>
Full(a)
case _ => Failure(s"Key '${key}' cannot be parsed as a valid certificate")
}
} catch {
case e:Exception => Failure(s"Key '${key}' cannot be parsed as a valid certificate")
}
}
}
/**
* A simple class to denote version
inventory-api/src/main/scala/com/normation/inventory/domain/NodeInventory.scala
, totalSpace : Option[MemorySize] = None
) extends NodeElement with HashcodeCaching
case class Network (
name : String
, description : Option[String] = None
......
}
}
sealed trait OsType {
def kernelName : String
def name : String //name is normalized and not destined to be printed - use localization for that
......
case object UnknownBsdType extends BsdType with HashcodeCaching { val name = "UnknownBSD" }
case object FreeBSD extends BsdType with HashcodeCaching { val name = "FreeBSD" }
/**
* The different OS type. For now, we know:
* - Linux
......
, override val kernelVersion : Version = new Version("N/A")
) extends OsDetails(UnknownOSType, fullName, version, servicePack, kernelVersion) with HashcodeCaching
case class Linux(
override val os : OsType
, override val fullName : String
......
, productId : Option[String] = None
) extends OsDetails(os, fullName, version, servicePack, kernelVersion) with HashcodeCaching
case class NodeSummary(
id : NodeId
, status:InventoryStatus
......
, archDescription : Option[String] = None
, lastLoggedUser : Option[String] = None
, lastLoggedUserTime : Option[DateTime] = None
, agents : Seq[AgentInfo] = Seq()
, publicKeys : Seq[PublicKey] = Seq()
, serverIps : Seq[String] = Seq()
, agents : Seq[AgentInfo] = Seq()
, serverIps : Seq[String] = Seq()
, machineId : Option[(MachineUuid,InventoryStatus)] = None //if we want several ids, we would have to ass an "alternate machine" field
, softwareIds : Seq[SoftwareUuid] = Seq()
, accounts : Seq[String] = Seq()
inventory-api/src/main/scala/com/normation/inventory/services/provisionning/CheckInventoryDigest.scala
import org.apache.commons.io.IOUtils
import javax.xml.bind.DatatypeConverter
import com.normation.inventory.services.core.ReadOnlyFullInventoryRepository
import com.normation.inventory.domain._
import com.normation.inventory.domain.{ PublicKey => AgentKey, _ }
/**
* We are using a simple date structure that handle the digest file
......
trait CheckInventoryDigest {
/**
* Here, we want to calculate the digest. The good library for that is most likely
* bouncy castle: https://www.bouncycastle.org/
*/
def check(pubKey: PublicKey, digest: InventoryDigest, inventoryStream: InputStream): Box[Boolean] = {
try {
val signature = Signature.getInstance("SHA512withRSA", "BC");
signature.initVerify(pubKey);
val data = IOUtils.toByteArray(inventoryStream)
signature.update(data);
digest match {
case InventoryDigestV1(_,digest) =>
val sig = DatatypeConverter.parseHexBinary(digest)
Full(signature.verify(sig))
}
} catch {
case e : Exception =>
Failure(e.getMessage())
def check(securityToken: SecurityToken, digest: InventoryDigest, inventoryStream: InputStream): Box[Boolean] = {
securityToken match {
case rudderKey : com.normation.inventory.domain.PublicKey =>
rudderKey.publicKey match {
case Full(pubKey) =>
try {
val signature = Signature.getInstance("SHA512withRSA", "BC");
signature.initVerify(pubKey);
val data = IOUtils.toByteArray(inventoryStream)
signature.update(data);
digest match {
case InventoryDigestV1(_,digest) =>
val sig = DatatypeConverter.parseHexBinary(digest)
Full(signature.verify(sig))
}
} catch {
case e : Exception =>
Failure(e.getMessage())
}
case eb : EmptyBox =>
eb
}
// We don't sign with certificate for now
case _ : Certificate => Full(true)
}
}
......
*/
trait GetKey {
def getKey (receivedInventory : InventoryReport) : Box[(PublicKey, KeyStatus)]
def getKey (receivedInventory : InventoryReport) : Box[(SecurityToken, KeyStatus)]
}
......
* either an inventory has already been treated before, it will look into ldap repository
* or if there was no inventory before, it will look for the key in the received inventory
*/
def getKey (receivedInventory : InventoryReport) : Box[(PublicKey, KeyStatus)] = {
def getKey (receivedInventory : InventoryReport) : Box[(SecurityToken, KeyStatus)] = {
def extractKey (node : NodeInventory) : Box[(PublicKey)]= {
def extractKey (node : NodeInventory) : Box[SecurityToken]= {
for {
cfengineKey <- Box(node.publicKeys.headOption) ?~! "There is no public key in inventory"
publicKey <- cfengineKey.publicKey
agent <- Box(node.agents.headOption) ?~! "There is no public key in inventory"
} yield {
publicKey
agent.securityToken
}
}
......
val status = storedInventory.node.main.keyStatus
val inventory : NodeInventory = status match {
case UndefinedKey =>
storedInventory.node.publicKeys.headOption match {
storedInventory.node.agents.map(_.securityToken).headOption match {
case None =>
// There is no key and status is undefined, use received key
receivedInventory.node
case Some(key) =>
key.publicKey match {
// Stored key is valid, use it !
case Full(_) => storedInventory.node
// Key stored is not valid and status is undefined try received key,
// There treat the case of the bootstrapped key for rudder root server
case _ => receivedInventory.node
case Some(securityToken) =>
securityToken match {
case key : AgentKey =>
key.publicKey match {
// Stored key is valid, use it !
case Full(_) => storedInventory.node
// Key stored is not valid and status is undefined try received key,
// There treat the case of the bootstrapped key for rudder root server
case _ => receivedInventory.node
}
case cert : Certificate =>
// We don't sign inventory with cert for now, use sorted one
storedInventory.node
}
}
// Certified node always use stored inventory key
inventory-fusion/src/main/scala/com/normation/inventory/provisioning/fusion/FusionReportUnmarshaller.scala
package fusion
import com.normation.inventory.domain._
import com.normation.inventory.domain.AgentType._
import com.normation.inventory.provisioning.fusion._
import java.io.InputStream
import org.joda.time.DateTime
......
// as a temporary solution, we are getting information from packages
val versions = {
def findAgent(software: Seq[Software], agentType: AgentType) = (
software.find(p => p.name.getOrElse("")
.toLowerCase.contains(agentType.inventorySoftwareName ))
.flatMap(s => s.version.map(v => AgentVersion(agentType.toAgentVersionName(v.value))))
)
Map[AgentType, Option[AgentVersion]](
// for nova, we get the cfengine version, which not exactly what we want, but still better than nothing
(NOVA_AGENT , findAgent(report.applications, NOVA_AGENT))
// for community, we only want rudder-agent version
, (COMMUNITY_AGENT , findAgent(report.applications, COMMUNITY_AGENT))
)
}
def findAgent(software: Seq[Software], agentType: AgentType) = (
software.find(p => p.name.getOrElse("")
.toLowerCase.contains(agentType.inventorySoftwareName ))
.flatMap(s => s.version.map(v => AgentVersion(agentType.toAgentVersionName(v.value))))
)
(xml \\ "RUDDER").headOption match {
case Some(rudder) =>
// Fetch all the agents configuration
val agents = ((rudder \\ "AGENT").map { agentXML =>
val agents = (rudder \\ "AGENT").flatMap { agentXML =>
val agent = for {
agentName <- boxFromOption(optText(agentXML \ "AGENT_NAME"), "could not parse agent name (tag AGENT_NAME) from rudder specific inventory")
agentType <- (AgentType.fromValue(agentName))
rootUser <- boxFromOption(optText(agentXML \\ "OWNER") ,"could not parse rudder user (tag OWNER) from rudder specific inventory")
policyServerId <- boxFromOption(optText(agentXML \\ "POLICY_SERVER_UUID") ,"could not parse policy server id (tag POLICY_SERVER_UUID) from specific inventory")
optCert = optText(agentXML \ "AGENT_CERT")
optKey = optText(agentXML \ "AGENT_KEY").orElse(optText(agentXML \ "CFENGINE_KEY"))
securityToken : SecurityToken <- agentType match {
case Dsc => optCert match {
case Some(cert) => Full(Certificate(cert))
case None => Failure("could not parse agent certificate (tag AGENT_CERT), which is mandatory for dsc agent")
}
case _ =>
(optCert,optKey) match {
case (Some(cert),_) => Full(Certificate(cert))
case (None,Some(key)) => Full(PublicKey(key))
case (None,None) => Failure("could not parse agent security Token (tag AGENT_KEY/CFENGINE_KEY/AGENT_CERT), which is mandatory for cfengine agent")
}
}
} yield {
//cfkey is not mandatory
val agentKey = optText(agentXML \ "AGENT_KEY").orElse(optText(agentXML \ "CFENGINE_KEY"))
(agentType, rootUser, policyServerId, agentKey)
val version = findAgent(report.applications,agentType)
(AgentInfo(agentType,version,securityToken), rootUser, policyServerId)
}
agent match {
case eb: EmptyBox =>
......
e
case Full(x) => Full(x)
}
}).flatten
}
( for {
agentOK <- if(agents.size < 1) {
......
policyServerId <- uniqueValueInSeq(agents.map(_._3), "could not parse policy server id (tag POLICY_SERVER_UUID) from specific inventory")
} yield {
val keys = agents.map{case (_,_,_,key) => key.map(PublicKey)}.flatten
report.copy (
node = report.node.copy (
......
, policyServerId = NodeId(policyServerId)
, id = NodeId(uuid)
)
, agents = agents.map(t => AgentInfo(t._1, versions.get(t._1).flatten))
, publicKeys = keys
, agents = agents.map(_._1)
)
)
} ) match {
inventory-fusion/src/main/scala/com/normation/inventory/provisioning/fusion/RudderParsingRules.scala
import java.security.MessageDigest
import com.normation.utils.UuidRegex
import com.normation.inventory.provisioning.fusion.OptText.optText
import com.normation.inventory.domain.AgentType.Dsc
/**
* Special handling of some tags.
......
}
}
/**
* <PROCESSORS>
*
......
}
}
/**
* <CFKEY>
*/
class RudderPublicKeyParsing(keyNormalizer:PrintedKeyNormalizer) extends FusionReportParsingExtension {
override def isDefinedAt(x:(Node,InventoryReport)) = { x._1.label == "CFKEY" }
override def apply(x:(Node,InventoryReport)) : InventoryReport = {
optText(x._1) match {
case None => x._2
case Some(key) =>
keyNormalizer(key) match {
case "" => x._2 //we have an empty key !
case k => x._2.copy( node = x._2.node.copy( publicKeys = (new PublicKey(key) +: x._2.node.publicKeys ) ) )
}
}
}
}
/**
* <USER>
*/
......
}
}
/**
* <AGENTSNAME>
*/
......
x._2.copy( node = x._2.node.copy( agents = x._2.node.agents ++ processAgentName(x._1) ) )
}
def processAgentName(xml:NodeSeq) : Seq[AgentInfo] = {
(xml \ "AGENTNAME").flatMap(e => optText(e).flatMap( a =>
AgentType.fromValue(a) match {
case Full(x) => Full(AgentInfo(x, None))
case e:EmptyBox =>
logger.error("Ignore agent type '%s': unknown value. Authorized values are %s".format(a, AgentType.allValues.mkString(", ")))
val keys = (xml \ "CFKEY").map { optText }
val agents = (xml \ "AGENTNAME").map( optText)
agents.zipAll(keys, None , None).flatMap{
case (None,None) => Empty
case (None,Some(_)) => Empty
// A key but no agent, skip
case (Some(agent), None) =>
// No key error
logger.error(s"No key for agent ${agent} defined, a key is mandatory")
Empty
case (Some(agent),Some(key)) =>
AgentType.fromValue(agent) match {
case Full(Dsc) =>
Failure("Dsc agent cannot be added using AGENTNAME tag")
case Full(agent) =>
Full(AgentInfo(agent, None, PublicKey(key)))
case e:EmptyBox =>
logger.error("Ignore agent type '%s': unknown value. Authorized values are %s".format(agent, AgentType.allValues.mkString(", ")))
Empty
}
}
) )
}
}
......
(xml \ "SERVER_ROLE").flatMap(e => optText(e).map(ServerRole(_)))
}
}
inventory-fusion/src/test/resources/fusion-report/dsc-agent.ocs
<RUDDER>
<AGENT>
<AGENT_NAME>windows-dsc</AGENT_NAME>
<AGENT_KEY>-----BEGIN RSA PUBLIC KEY-----
MIIBCAKCAQEAp8XambRZMLpI9wCqkeZNGQuG02wjjiQD9NIUrESkcfMYZ5qvLhX1
bjufiDTbs1M2ySEGGCDHJN991C67kvPvstFtKx+w8m6DBC9DkpwLR0wGgWN0VCqC
cG8UwJIRz9SrPTQKRLFWI/fl8b92xcZvDPVXv1r0yS75toX3Ja4dBwyOqaiUGLBO
kOpwXSfrmHJ16IDWwLUZlesfomgCyzfZ8AUuTOX7JbvxLI9neXysoueXqDMIXklS
013pCqdZW/kCp3/aPwBjoK5EFnR8XxVLCGSXGXz6JG2Nng7ox8bbjm0VurFQErce
VyodQDN1ecAsViRI1V18Z+PvZll8isonWQIBIw==
-----END RSA PUBLIC KEY-----</AGENT_KEY>
<AGENT_CERT>-----BEGIN CERTIFICATE-----
MIIE0zCCArugAwIBAgIJAJqeHdZEkElqMA0GCSqGSIb3DQEBCwUAMBExDzANBgNV
BAMMBkFHRU5UMTAeFw0xNzA2MDcxMzA1NTlaFw0yNzA2MDUxMzA1NTlaMBExDzAN
BgNVBAMMBkFHRU5UMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOPd
mhaXZcezSjgTi6Zn38alftrYwfAyuKoznll4oM2T5DkYaZ2Lyclvv7jar/4ozUsA
Jmzw6O8QjCiaufVkHCytGJoQmtRPhywgOp0S0vqUVCXsdfh5dhwZ3Qjy107M6Z6E
6lyjggsgGZM23LBUNjsMyY7FefSHGpLpeWXgX/1CW4XeBaBq6mzo5A/1N3iyKLL4
yOGcNli11H4snlmqGJ+YDfNfCIeTkx2wdFftOxRkPFZ1TETmZSt5idrNvGU2IOnp
tXnT1kN/iknW6ZLu0wLRGe/b0Hde6ZqfPZLdrBn2oeMBvkM4eOl5GpFIFQa0uSEh
tr57vCqC6KSVvejjNr2VwKs11+9Bkdim6h7ndX74tFrR3N7cBczkXYoc+tlP2MHX
t/DqXxkB3OhmDVfmlzC/CV9M5rE7GSpWRx8O46wYrkf+6J1Z4p0rOjZNDPolEJWY
KxKzS//34wgqPfqY7iSav7ElpAFcZRFFfIKldpjpM2M5Gul0dXpBMEqHkmlcRDMT
VBrHYuTyyXhtdApdVZXDocfAma7UOkis5WcVn2+6O/pI88yVXkkgK4lSNOPBMgc0
6XLI2YgoGB3hnpGTfKbTbj3uGV8AI2/N3x91gGXRu4EtrvYtiiuxmpT1NJLmHVAA
vkfQwNm+CFqXzNDn+OPOM+cgsc08Qd++UXUmOI5lAgMBAAGjLjAsMAsGA1UdDwQE
AwIEsDAdBgNVHQ4EFgQU+S14UemkyCDsEaa5Gto8Qt9xIFEwDQYJKoZIhvcNAQEL
BQADggIBADTLZLpX/ffSNzxgla7MsboBFVW8JzaFtG8JOe+oYxeI9nHCB6M6fJao
hBhF7nUA0MyjIoEMfFVPF7LUTf3k0XvJNvXMJGNxlye1jsIKTWJo29ESERUc9eRk
LDIC6fbWV0PVjo1VolxnKZoLznOIRITPxAxfmn48n2/YJ5eGT8MF4cgTTpIP0cbD
I7UDDB1GRpLfu/K42Ll4CrTAbLABtdcGv4Rn4CtDXu+KrSpOJG675S6UhjT7ozjj
J/QDV8XcNWW0BF+2w2zzJsmSo8NjExuNXgsVh29uhCPQGYrjO03xBtz1GmWKX8wS
RGqgVQI0dHHlcfYSLaaA/zkwU9wkvmiwUS7ODMfYSrCTVNBkRvV/oIKmN9mY9I3R
//wCOPI18kk6fONEHjOng9NJD6LGVlGrnR+k3MFVWqORuvIUFEhD4YynHPZqu1g8
60ePwvw8pouI4yoRtCQBZEw2rgnS0bGiY3bpou3sTq+eLxQs/3DmvJkhLGzgfeai
MFMym+kchpMxU69krkiZXDWwkXxo3txXnPevPx12pQKBSQJONfeADDok6GQYEOeH
GvNfE6prJSpDchFWdRKK3/RcknoKwq0hDyFICOhZkb3Zc4oWknDgJz/huorU/nnb
QHjxOfVdBMBBXq0oHSUsOzf9rIxMlWQmCQyNDB6qD+bXZnAZp7j7
-----END CERTIFICATE-----
</AGENT_CERT>
<OWNER>agent1\vagrant</OWNER>
<POLICY_SERVER_HOSTNAME>192.168.42.0</POLICY_SERVER_HOSTNAME>
<POLICY_SERVER_UUID>root</POLICY_SERVER_UUID></AGENT>
inventory-fusion/src/test/scala/com/normation/inventory/provisioning/fusion/TestPostUnmarshaller.scala
import net.liftweb.common.EmptyBox
import net.liftweb.common.Full
import java.io.File
import com.normation.inventory.domain.COMMUNITY_AGENT
import com.normation.inventory.domain.NOVA_AGENT
import com.normation.inventory.domain.AgentType._
import com.normation.inventory.domain.Windows
import com.normation.inventory.domain.Windows2012
import com.normation.inventory.services.provisioning.PreUnmarshall
......
import org.xml.sax.SAXParseException
import scala.xml.NodeSeq
/**
* A simple test class to check that the demo data file is up to date
* with the schema (there may still be a desynchronization if both
......
linux.toOption must not beNone
}
"With a valid inventory in Windows" in {
val windows = post.check("fusion-report/WIN-AI8CLNPLOV5-2014-06-20-18-15-49.ocs")
windows.toOption must not beNone
inventory-fusion/src/test/scala/com/normation/inventory/provisioning/fusion/TestReportParsing.scala
import scala.xml.XML
import java.io.File
import com.normation.inventory.domain._
import com.normation.inventory.domain.AgentType._
/**
* A simple test class to check that the demo data file is up to date
......
"Agent in Inventory" should {
"should be empty when there is no agent" in {
val agents = parser.parse("fusion-report/rudder-tag/minimal-zero-agent.ocs").node.agents.map(_.name).toList
val agents = parser.parse("fusion-report/rudder-tag/minimal-zero-agent.ocs").node.agents.map(_.agentType).toList
agents must be empty
}
"should have one agent when using community" in {
val agents = parser.parse("fusion-report/rudder-tag/minimal-one-agent.ocs").node.agents.map(_.name).toList
agents == (COMMUNITY_AGENT :: Nil)
val agents = parser.parse("fusion-report/rudder-tag/minimal-one-agent.ocs").node.agents.map(_.agentType).toList
agents == (CfeCommunity :: Nil)
}
"should have two agent when using community and nova" in {
val agents = parser.parse("fusion-report/rudder-tag/minimal-two-agents.ocs").node.agents.map(_.name).toList
agents == (COMMUNITY_AGENT :: NOVA_AGENT :: Nil)
val agents = parser.parse("fusion-report/rudder-tag/minimal-two-agents.ocs").node.agents.map(_.agentType).toList
agents == (CfeCommunity :: CfeEnterprise :: Nil)
}
"should be empty when there is two agents, using two different policy servers" in {
val agents = parser.parse("fusion-report/rudder-tag/minimal-two-agents-fails.ocs").node.agents.map(_.name).toList
val agents = parser.parse("fusion-report/rudder-tag/minimal-two-agents-fails.ocs").node.agents.map(_.agentType).toList
agents must be empty
}
"should have dsc agent agent when using rudder-agent based on dsc" in {
val agents = parser.parse("fusion-report/dsc-agent.ocs").node.agents.map(_.name).toList
agents == (DSC_AGENT :: Nil)
val agents = parser.parse("fusion-report/dsc-agent.ocs").node.agents.map(_.agentType).toList
agents == (Dsc :: Nil)
}
}
inventory-fusion/src/test/scala/com/normation/inventory/provisioning/fusion/TestSignatureService.scala
*************************************************************************************
*/
package com.normation.inventory.provisioning.fusion
import org.junit.runner._
......
import com.normation.inventory.domain.KeyStatus
import java.security.Security
import org.bouncycastle.jce.provider.BouncyCastleProvider
import com.normation.inventory.domain.SecurityToken
@RunWith(classOf[JUnitRunner])
class TestSignatureService extends Specification with Loggable {
......
* either an inventory has already been treated before, it will look into ldap repository
* or if there was no inventory before, it will look for the key in the received inventory
*/
def getKey (receivedInventory : InventoryReport) : Box[(PublicKey, KeyStatus)] = {
def getKey (receivedInventory : InventoryReport) : Box[(SecurityToken, KeyStatus)] = {
for {
cfengineKey <- Box(receivedInventory.node.publicKeys.headOption) ?~! "There is no public key in inventory"
cfengineKey <- Box(receivedInventory.node.agents.headOption) ?~! "There is no public key in inventory"
keyStatus = receivedInventory.node.main.keyStatus
publicKey <- cfengineKey.publicKey
publicKey = cfengineKey.securityToken//.publicKey
} yield {
(publicKey,keyStatus)
(publicKey,keyStatus)
}
}
}
val keyNorm = new PrintedKeyNormalizer
val extension = new RudderPublicKeyParsing(keyNorm)
val extension = RudderAgentNameParsing
val parser = new FusionReportUnmarshaller(
new StringUuidGeneratorImpl
......
}
}
"a signed report" should {
"Be ok if checked with correct signature" in {
(for {
inventory-provisioning-core/src/main/scala/com/normation/inventory/ldap/provisioning/NodeIdFinder.scala
}
}
/*
* Retrieve the id from the cfengine public key
*/
......
) extends NodeInventoryDNFinder with Loggable {
override def tryWith(entity:NodeInventory) : Box[(NodeId,InventoryStatus)] = {
val keys = entity.publicKeys
val keys = entity.agents.map(_.securityToken)
if(keys.size > 0) {
val keysFilter = OR((keys.map( k => EQ(A_PKEYS,k.key))):_*)
inventory-provisioning-web/src/main/scala/com/normation/inventory/provisioning/endpoint/config/AppConfig.scala
@Bean
def removedNodesDit = new InventoryDit(REMOVED_INVENTORIES_DN,SOFTWARE_INVENTORIES_DN,"Removed Servers")
@Bean
def inventoryDitService = new InventoryDitServiceImpl(pendingNodesDit,acceptedNodesDit, removedNodesDit)
......
RudderPolicyServerParsing ::
RudderMachineIdParsing ::
RudderCpuParsing ::
new RudderPublicKeyParsing(keyNorm) ::
RudderRootUserParsing ::
RudderAgentNameParsing ::
RudderServerRoleParsing ::
......
NamedNodeInventoryDNFinderAction("use_existing_id", new UseExistingNodeIdFinder(inventoryDitService,roLdapConnectionProvider,acceptedNodesDit.BASE_DN.getParent))
))
@Bean
def machineFinder() : MachineDNFinderAction =
new MachineDNFinderService(Seq(
......
, postCommitPipeline
)
/*
* configure the file handler
*/
......
c
}
/*
* The REST end point where OCSi report are
* uploaded
inventory-repository/src/main/scala/com/normation/inventory/ldap/core/InventoryMapper.scala
import com.normation.utils.Control.sequence
import com.normation.inventory.domain.NodeTimezone
class DateTimeSerializer extends Serializer[DateTime] {
private val IntervalClass = classOf[DateTime]
......
}
}
////////////////////////////////////////////////////////////////////
///////////////////////// Machine Elements /////////////////////////
////////////////////////////////////////////////////////////////////
......
///////////////////////// Video /////////////////////////
def entryFromVideo(elt:Video,dit:InventoryDit,machineId:MachineUuid) : LDAPEntry = {
val e = dit.MACHINES.VIDEO.model(machineId,elt.name)
e.setOpt(elt.description, A_DESCRIPTION, {x:String => x})
......
root.setOpt(machine.manufacturer,A_MANUFACTURER, {x:Manufacturer => x.name})
root.setOpt(machine.systemSerialNumber,A_SERIAL_NUMBER, {x:String => x})
val tree = LDAPTree(root)
//now, add machine elements as children
machine.bios.foreach { x => tree.addChild(entryFromBios(x, dit, machine.id)) }
......
///////////////////////// Networks /////////////////////////
def entryFromNetwork(elt:Network, dit:InventoryDit, serverId:NodeId) : LDAPEntry = {
val e = dit.NODES.NETWORK.model(serverId,elt.name)
e.setOpt(elt.description, A_DESCRIPTION, {x:String => x})
......
///////////////////////// VM INFO /////////////////////////
def entryFromVMInfo(elt:VirtualMachine, dit:InventoryDit, serverId:NodeId) : LDAPEntry = {
val e = dit.NODES.VM.model(serverId,elt.uuid.value)
e.setOpt(elt.description, A_DESCRIPTION, {x:String => x})
......
}
}
//////////////////Node/ NodeInventory /////////////////////////
// User defined properties : the regexp that the data should abide by
// {KEY}VALUE
private[this] val userDefinedPropertyRegex = """\{([^\}]+)\}(.+)""".r
......
root.setOpt(server.inventoryDate, A_INVENTORY_DATE, { x: DateTime => GeneralizedTime(x).toString })
root.setOpt(server.receiveDate, A_RECEIVE_DATE, { x: DateTime => GeneralizedTime(x).toString })
root +=! (A_AGENTS_NAME, server.agents.map(x => x.toJsonString):_*)
root +=! (A_PKEYS, server.publicKeys.map(x => x.key):_*)
root +=! (A_SOFTWARE_DN, server.softwareIds.map(x => dit.SOFTWARE.SOFT.dn(x).toString):_*)
root +=! (A_EV, server.environmentVariables.map(x => Serialization.write(x)):_*)
root +=! (A_PROCESS, server.processes.map(x => Serialization.write(x)):_*)
......
hostname <- requiredAttr(A_HOSTNAME)
rootUser <- requiredAttr(A_ROOT_USER)
policyServerId <- requiredAttr(A_POLICY_SERVER_UUID)
agentNames <- sequence(entry.valuesFor(A_AGENTS_NAME).toSeq) { x =>
AgentInfoSerialisation.parseCompatNonJson(x) //compat to be able to migrate easely from Rudder < 4.0
}
publicKeys = entry.valuesFor(A_PKEYS).map(Some(_))
agentNames <- {
val agents = entry.valuesFor(A_AGENTS_NAME).toSeq.map(Some(_))
val agentWithKeys = agents.zipAll(publicKeys, None,None).filter(_._1.isDefined)
sequence(agentWithKeys) {
case (Some(agent),key) =>
AgentInfoSerialisation.parseCompatNonJson(agent,key)
case _ =>
Failure("Should not happen")
}
}
//now, look for the OS type
osDetails <- mapOsDetailsFromEntry(entry)
//now, optionnal things
......
, lastLoggedUser
, lastLoggedUserTime
, agentNames
, publicKeys.toSeq
, serverIps
, machineId
, softwareIds

Also available in: Unified diff