405a2cfc0f
Enforce mTLS and least-privilege runtime boundaries while adding repeatable MySQL 8.4 and Docker smoke checks that require no production secrets.
74 lines
2.8 KiB
Kotlin
74 lines
2.8 KiB
Kotlin
package com.osglab.account.config
|
|
|
|
import ch.qos.logback.classic.LoggerContext
|
|
import ch.qos.logback.classic.joran.JoranConfigurator
|
|
import ch.qos.logback.core.status.Status
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.matchers.collections.shouldContainExactly
|
|
import io.kotest.matchers.collections.shouldBeEmpty
|
|
import io.kotest.matchers.shouldBe
|
|
import io.kotest.matchers.string.shouldContain
|
|
import java.nio.file.Path
|
|
import javax.xml.XMLConstants
|
|
import javax.xml.parsers.DocumentBuilderFactory
|
|
import org.w3c.dom.Element
|
|
import org.w3c.dom.Node
|
|
|
|
class LogbackConfigurationTest : FunSpec({
|
|
val logbackPath = Path.of(System.getProperty("user.dir"), "src/main/resources/logback.xml")
|
|
|
|
test("logback configuration is parseable and has one root") {
|
|
val document = secureDocumentBuilderFactory()
|
|
.newDocumentBuilder()
|
|
.parse(logbackPath.toFile())
|
|
|
|
document.documentElement.tagName shouldBe "configuration"
|
|
document.getElementsByTagName("configuration").length shouldBe 1
|
|
document.childElements().map(Element::getTagName) shouldContainExactly listOf("configuration")
|
|
}
|
|
|
|
test("console pattern is single-line and contains required structured fields") {
|
|
val document = secureDocumentBuilderFactory()
|
|
.newDocumentBuilder()
|
|
.parse(logbackPath.toFile())
|
|
val patterns = document.getElementsByTagName("pattern")
|
|
|
|
patterns.length shouldBe 1
|
|
val pattern = patterns.item(0).textContent.trim()
|
|
pattern.lines().size shouldBe 1
|
|
listOf("time=", "level=", "logger=", "requestId=", "message=").forEach(pattern::shouldContain)
|
|
pattern shouldContain "[REDACTED]"
|
|
pattern shouldContain "%nopex"
|
|
}
|
|
|
|
test("logback accepts the structured pattern without configuration errors") {
|
|
val context = LoggerContext()
|
|
|
|
try {
|
|
JoranConfigurator().apply { this.context = context }.doConfigure(logbackPath.toFile())
|
|
|
|
context.statusManager.copyOfStatusList
|
|
.filter { it.level == Status.ERROR }
|
|
.shouldBeEmpty()
|
|
} finally {
|
|
context.stop()
|
|
}
|
|
}
|
|
})
|
|
|
|
private fun secureDocumentBuilderFactory(): DocumentBuilderFactory =
|
|
DocumentBuilderFactory.newInstance().apply {
|
|
setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)
|
|
setFeature("http://xml.org/sax/features/external-general-entities", false)
|
|
setFeature("http://xml.org/sax/features/external-parameter-entities", false)
|
|
setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "")
|
|
setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "")
|
|
isXIncludeAware = false
|
|
isExpandEntityReferences = false
|
|
}
|
|
|
|
private fun Node.childElements(): List<Element> =
|
|
(0 until childNodes.length)
|
|
.map(childNodes::item)
|
|
.filterIsInstance<Element>()
|