Skip to content

Commit

Permalink
Fixes #15212: Generate a file on root server containing all nodes cer…
Browse files Browse the repository at this point in the history
…tificate
  • Loading branch information
fanf committed Jul 17, 2019
1 parent 50d94ee commit b3ace77
Show file tree
Hide file tree
Showing 15 changed files with 436 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class TestPreUnmarshaller extends Specification {
url.openStream()
}
} { is =>
IOResult.effectRunUnit(is.close)
IOResult.effectUioUnit(is.close)
} {
is => fromXml("check", is).flatMap[Any, RudderError, NodeSeq](pre.apply)
}).either.runNow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class TestSignatureService extends Specification with Loggable {
val parser = new FusionReportUnmarshaller(new StringUuidGeneratorImpl)

def parseSignature(path: String): IOResult[InventoryDigest] = {
IO.bracket(getInputStream(path))(is => IOResult.effectRunUnit(is.close))(TestInventoryDigestServiceV1.parse)
IO.bracket(getInputStream(path))(is => IOResult.effectUioUnit(is.close))(TestInventoryDigestServiceV1.parse)
}

val boxedSignature = parseSignature("fusion-report/signed_inventory.ocs.sign")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ trait ScalaLock {
.mapError(ex => SystemError(s"Error when trying to get LDAP lock", ex))
)(_ =>
LdapLockLogger.logPure.error("Release lock") *>
IOResult.effectRunUnit(this.unlock())
IOResult.effectUioUnit(this.unlock())
)(_ =>
LdapLockLogger.logPure.error("Do things in lock") *>
block
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ trait GitArchiverFullCommitUtils extends NamedZioLogger {
import scala.collection.mutable.ArrayBuffer

gitRepo.db.flatMap(db =>
ZIO.bracket(IOResult.effect(new RevWalk(db)))(db => IOResult.effectRunUnit(db.close)){ revWalk =>
ZIO.bracket(IOResult.effect(new RevWalk(db)))(db => IOResult.effectUioUnit(db.close)){ revWalk =>
IOResult.effect {
val tags = ArrayBuffer[RevTag]()
val refList = db.getRefDatabase().getRefsByPrefix(Constants.R_TAGS).asScala
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ object GitFindUtils extends NamedZioLogger {
case Nil =>
Inconsistancy(s"No file were found at path '${filePath}}'").fail
case h :: Nil =>
ZIO.bracket(IOResult.effect(db.open(h).openStream()))(s => IOResult.effectRunUnit(s.close()))(useIt)
ZIO.bracket(IOResult.effect(db.open(h).openStream()))(s => IOResult.effectUioUnit(s.close()))(useIt)
case _ =>
Inconsistancy(s"More than exactly one matching file were found in the git tree for path '${filePath}', I can not know which one to choose. IDs: ${ids}}").fail
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class ItemArchiveManagerImpl(
_ <- woRuleRepository.deleteSavedRuleArchiveId(imported).catchAll(err =>
logPure.warn(s"Error when trying to delete saved archive of old rule: ${err.fullMsg}")
)
_ <- IOResult.effectRunUnit(if(deploy) {asyncDeploymentAgent ! AutomaticStartDeployment(modId, actor)})
_ <- IOResult.effectUioUnit(if(deploy) {asyncDeploymentAgent ! AutomaticStartDeployment(modId, actor)})
} yield {
if(deploy) { asyncDeploymentAgent ! AutomaticStartDeployment(modId, actor) }
archiveId
Expand Down Expand Up @@ -385,7 +385,7 @@ class ItemArchiveManagerImpl(
_ <- woParameterRepository.deleteSavedParametersArchiveId(imported).catchAll(err =>
logPure.warn(s"Error when trying to delete saved archive of old parameters: ${err.fullMsg}")
)
_ <- IOResult.effectRunUnit(if(deploy) {asyncDeploymentAgent ! AutomaticStartDeployment(modId, actor)})
_ <- IOResult.effectUioUnit(if(deploy) {asyncDeploymentAgent ! AutomaticStartDeployment(modId, actor)})
} yield {
archiveId
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ object ZipUtils {
*/

def zip(zipout:OutputStream, toAdds:Seq[Zippable]) : IOResult[Unit] = {
ZIO.bracket(IOResult.effect(new ZipOutputStream(zipout)))(zout => IOResult.effectRunUnit(zout.close())) { zout =>
ZIO.bracket(IOResult.effect(new ZipOutputStream(zipout)))(zout => IOResult.effectUioUnit(zout.close())) { zout =>
val addToZout = (is:InputStream) => IOResult.effect("Error when copying file")(IOUtils.copy(is, zout))

ZIO.foreach(toAdds) { x =>
Expand Down Expand Up @@ -128,7 +128,7 @@ object ZipUtils {
}
} else {
def buildContent(use: InputStream => Any) : IOResult[Any] = {
ZIO.bracket(IOResult.effect(new FileInputStream(f)))(is => IOResult.effectRunUnit(is.close)){ is =>
ZIO.bracket(IOResult.effect(new FileInputStream(f)))(is => IOResult.effectUioUnit(is.close)){ is =>
IOResult.effect("Error when using file")(use(is))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ trait PromiseGenerationService {
_ = PolicyLogger.debug(s"Historization of names done in ${timeHistorize} ms, start to build rule values.")
///// end ignoring

/////
///// Generate the root file with all certificate. This could be done in the node lifecycle management.
///// For now, it's just a trigger: the generation is async and doesn't fail policy generation.
///// File is: /var/rudder/lib/ssl/allnodecerts.pem
_ = writeCertificatesPem(allNodeInfos)

///// parse rule for directive parameters and build node context that will be used for them
///// also restrict generation to only active rules & nodes:
///// - number of nodes: only node somehow targetted by a rule have to be considered.
Expand Down Expand Up @@ -396,6 +402,7 @@ trait PromiseGenerationService {
def getMaxParallelism : () => Box[String]
def getJsTimeout : () => Box[Int]
def getGenerationContinueOnError :() => Box[Boolean]
def writeCertificatesPem(allNodeInfos: Map[NodeId, NodeInfo]): Unit

// Trigger dynamic group update
def triggerNodeGroupUpdate(): Box[Unit]
Expand Down Expand Up @@ -591,6 +598,7 @@ class PromiseGenerationServiceImpl (
, override val agentRunService : AgentRunIntervalService
, override val complianceCache : CachedFindRuleNodeStatusReports
, override val promisesFileWriterService: PolicyWriterService
, override val writeNodeCertificatesPem: WriteNodeCertificatesPem
, override val getAgentRunInterval : () => Box[Int]
, override val getAgentRunSplaytime : () => Box[Int]
, override val getAgentRunStartHour : () => Box[Int]
Expand All @@ -605,8 +613,10 @@ class PromiseGenerationServiceImpl (
, override val HOOKS_IGNORE_SUFFIXES: List[String]
, override val UPDATED_NODE_IDS_PATH: String
, override val postGenerationHookCompabilityMode: Option[Boolean]
, override val allNodeCertificatesPemFile: File
) extends PromiseGenerationService with
PromiseGeneration_performeIO with
PromiseGeneration_NodeCertificates with
PromiseGeneration_buildRuleVals with
PromiseGeneration_buildNodeConfigurations with
PromiseGeneration_updateAndWriteRule with
Expand Down Expand Up @@ -1506,3 +1516,14 @@ trait PromiseGeneration_Hooks extends PromiseGenerationService with PromiseGener
}

}


trait PromiseGeneration_NodeCertificates extends PromiseGenerationService {

def allNodeCertificatesPemFile: File
def writeNodeCertificatesPem: WriteNodeCertificatesPem

override def writeCertificatesPem(allNodeInfos: Map[NodeId, NodeInfo]): Unit = {
writeNodeCertificatesPem.writeCerticatesAsync(allNodeCertificatesPemFile, allNodeInfos)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
*************************************************************************************
* Copyright 2019 Normation SAS
*************************************************************************************
*
* This file is part of Rudder.
*
* Rudder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In accordance with the terms of section 7 (7. Additional Terms.) of
* the GNU General Public License version 3, the copyright holders add
* the following Additional permissions:
* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General
* Public License version 3, when you create a Related Module, this
* Related Module is not considered as a part of the work and may be
* distributed under the license agreement of your choice.
* A "Related Module" means a set of sources files including their
* documentation that, without modification of the Source Code, enables
* supplementary functions or services in addition to those offered by
* the Software.
*
* Rudder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Rudder. If not, see <http://www.gnu.org/licenses/>.
*
*************************************************************************************
*/

package com.normation.rudder.services.policies

import java.nio.charset.StandardCharsets

import better.files.File
import com.normation.NamedZioLogger
import com.normation.errors._
import com.normation.inventory.domain.Certificate
import com.normation.inventory.domain.NodeId
import com.normation.rudder.domain.nodes.NodeInfo
import com.normation.rudder.hooks.Cmd
import com.normation.rudder.hooks.RunNuCommand
import com.normation.zio.ZioRuntime
import zio._
import zio.duration.Duration
import zio.syntax._

/**
* This class will generate a file containing all nodes certificates concatenated.
* The file can be used for Apache config for example.
*/
trait WriteNodeCertificatesPem {

/*
* Write certificates for nodes in a given, implementation dependant, location.
* File should not be overwritten until the replacement is ready.
*/
def writeCertificates(file: File, allNodeInfos: Map[NodeId, NodeInfo]): IOResult[Unit]

/*
* Same but async.
*/
def writeCerticatesAsync(file: File, allNodeInfos: Map[NodeId, NodeInfo]): Unit
}

/*
* In a default Rudder app, the file path is: /var/rudder/lib/ssl/allnodecerts.pem
* After file is written, a reload hook can be executed if `reloadScriptPath` is not empty
*/
class WriteNodeCertificatesPemImpl(reloadScriptPath: Option[String]) extends WriteNodeCertificatesPem {

val logger = NamedZioLogger(this.getClass.getName)


override def writeCerticatesAsync(file: File, allNodeInfos: Map[NodeId, NodeInfo]): Unit = {
ZioRuntime.runNow(writeCertificates(file, allNodeInfos).catchAll(e => logger.error(e.fullMsg)).fork)
}

override def writeCertificates(file: File, allNodeInfos: Map[NodeId, NodeInfo]): IOResult[Unit] = {
val allCertsNew = File(file.pathAsString + ".new")

for {
_ <- checkParentDirOK(file)
certs = allNodeInfos.flatMap { case (id, node) => node.agentsName.flatMap( _.securityToken match {
case x: Certificate => Some(x.key)
case _ => None
})}
writen <- writeCertificatesToNew(allCertsNew, certs)
moved <- IOResult.effect(allCertsNew.moveTo(file, overwrite = true))
hook <- execHook(reloadScriptPath)
} yield ()
}

/*
* write certificates in allCertsFile.new (if exists, delete)
* once written, move (overwrite allCertsFiles.new to
*
*/
def writeCertificatesToNew(file: File, certs: Iterable[String]): IOResult[Unit] = {
implicit val charset = StandardCharsets.UTF_8
implicit val writeAppend = File.OpenOptions.append

for {
_ <- ZIO.when(file.exists)(IOResult.effect(file.delete()))
_ <- ZIO.foreach(certs) { cert =>
IOResult.effect(file.writeText(cert + "\n"))
}
} yield ()
}

// check that parent directory exists and is writable or try to create it.
def checkParentDirOK(file: File): IOResult[Unit] = {
val parent = file.parent

for {
_ <- IOResult.effectM(s"Error when trying to create parent directory for node certificate file: ${parent.pathAsString}") {
val p = file.parent
if(p.exists) {
UIO.unit
} else {
p.createDirectories()
UIO.unit
}
}
_ <- IOResult.effectM{
if(parent.isDirectory) UIO.unit else Unexpected(s"Error: path '${parent.pathAsString}' must be a directory").fail
}
_ <- IOResult.effectM{
if(parent.isWriteable) UIO.unit else Unexpected(s"Error: path '${parent.pathAsString}' must be a writable directory").fail
}
} yield ()
}

def execHook(path: Option[String]): IOResult[Unit] = {
path match {
case None => UIO.unit
case Some(cmd) =>
// for the cmd, first arg is "cmd path", other are "parameters", and they must be splits
val (cmdPath :: args) = cmd.split("""\s""").toList

for {
promise <- RunNuCommand.run(Cmd(cmdPath, args, Map()), Duration.fromNanos(5L * 60 * 1000 * 1000)) // error after 5 mins
result <- promise.await
_ <- ZIO.when(result.code != 0) {
Unexpected(s"Error when executing reload command '${cmd}' after writing node certificates file. Command " +
s"output: code: ${result.code}\nstdout: ${result.stdout}\nstderr: ${result.stderr}").fail
}
} yield ()
}
}
}
Loading

0 comments on commit b3ace77

Please sign in to comment.