Skip to content

Commit

Permalink
Pattern for testing using standarized JVM config
Browse files Browse the repository at this point in the history
In a previous iteration jvm options were attempted to be configured at the
gradle level. This presented a challeng in rspec loading because webmock was not
being loaded correctly with the fips providers. That initial approach was also
problematic because it introduced a fork in configuration changes between
configuring the JVM via the enviornment variables and files for running logstash
in the container vs in the tests.

This new apprach attempts to separate aout all of the test setup and building in
gradle from a pure "just run the tests" task. The fundamental idea is that we
dont want to use FIPS mode for downloading dependencies and building/preparing
an environment we only want that configured at the very end. This apprach
accomplishes that by teasing out the dependencies that trigger downloads etc in
gradel from tasks that will only run the tests. The dockerfile order will call
all the gradle tasks for setup, then configure FIPS mode and call the tests that
should be run under fips mode.
  • Loading branch information
donoghuc committed Jan 24, 2025
1 parent e30e728 commit a7224b0
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 128 deletions.
42 changes: 22 additions & 20 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ RUN mkdir -p /root/.gradle
# Copy configuration files
COPY java.security /etc/java/security/
COPY java.policy /etc/java/security/
COPY test-init.gradle /tmp/test-init.gradle

# Set environment variables
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk
Expand All @@ -27,14 +26,15 @@ COPY . .

# Initial build using JKS truststore
# ENV JAVA_OPTS="-Djavax.net.ssl.trustStore=$JAVA_HOME/lib/security/cacerts -Djavax.net.ssl.trustStoreType=JKS -Djavax.net.ssl.trustStorePassword=changeit"
RUN ./gradlew clean bootstrap assemble installDefaultGems --no-daemon
RUN ./gradlew clean bootstrap assemble installDefaultGems testSetup --no-daemon

# CMD ["sleep", "infinity"]
RUN keytool -importkeystore \
-srckeystore $JAVA_HOME/lib/security/cacerts \
-destkeystore /etc/java/security/cacerts.bcfks \
-srcstoretype jks \
-deststoretype bcfks \
-providerpath /root/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bc-fips/2.0.0/ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab/bc-fips-2.0.0.jar \
-providerpath /logstash/logstash-core/lib/jars/bc-fips-2.0.0.jar \
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \
-deststorepass changeit \
-srcstorepass changeit \
Expand All @@ -45,31 +45,33 @@ RUN keytool -importkeystore \
-destkeystore /etc/java/security/keystore.bcfks \
-srcstoretype jks \
-deststoretype bcfks \
-providerpath /root/.gradle/caches/modules-2/files-2.1/org.bouncycastle/bc-fips/2.0.0/ee9ac432cf08f9a9ebee35d7cf8a45f94959a7ab/bc-fips-2.0.0.jar \
-providerpath /logstash/logstash-core/lib/jars/bc-fips-2.0.0.jar \
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \
-deststorepass changeit \
-srcstorepass changeit \
-noprompt


# Uncomment this out and build for interactive terminal sessions in a container
# Currently this dockerfile is just for running tests, but I have been also using it to
# do manual validation. Need to think more about how to separate concerns.
# ENV JAVA_SECURITY_PROPERTIES=/etc/java/security/java.security
# ENV JAVA_OPTS="\
# -Djava.security.debug=ssl,provider \
# -Djava.security.properties=${JAVA_SECURITY_PROPERTIES} \
# -Djavax.net.ssl.keyStore=/etc/java/security/keystore.bcfks \
# -Djavax.net.ssl.keyStoreType=BCFKS \
# -Djavax.net.ssl.keyStoreProvider=BCFIPS \
# -Djavax.net.ssl.keyStorePassword=changeit \
# -Djavax.net.ssl.trustStore=/etc/java/security/cacerts.bcfks \
# -Djavax.net.ssl.trustStoreType=BCFKS \
# -Djavax.net.ssl.trustStoreProvider=BCFIPS \
# -Djavax.net.ssl.trustStorePassword=changeit \
# -Dssl.KeyManagerFactory.algorithm=PKIX \
# -Dssl.TrustManagerFactory.algorithm=PKIX \
# -Dorg.bouncycastle.fips.approved_only=true"
ENV JAVA_SECURITY_PROPERTIES=/etc/java/security/java.security
ENV JAVA_OPTS="\
-Djava.security.debug=ssl,provider \
-Djava.security.properties=${JAVA_SECURITY_PROPERTIES} \
-Djavax.net.ssl.keyStore=/etc/java/security/keystore.bcfks \
-Djavax.net.ssl.keyStoreType=BCFKS \
-Djavax.net.ssl.keyStoreProvider=BCFIPS \
-Djavax.net.ssl.keyStorePassword=changeit \
-Djavax.net.ssl.trustStore=/etc/java/security/cacerts.bcfks \
-Djavax.net.ssl.trustStoreType=BCFKS \
-Djavax.net.ssl.trustStoreProvider=BCFIPS \
-Djavax.net.ssl.trustStorePassword=changeit \
-Dssl.KeyManagerFactory.algorithm=PKIX \
-Dssl.TrustManagerFactory.algorithm=PKIX \
-Dorg.bouncycastle.fips.approved_only=true"

ENV LS_JAVA_OPTS="${JAVA_OPTS}"
# Run tests with BCFKS truststore
CMD ["./gradlew", "--info", "--stacktrace", "test"]
CMD ["./gradlew","-PskipJRubySetup=true", "--info", "--stacktrace", "javaTestsOnly", "rubyTestsOnly"]

13 changes: 8 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -295,15 +295,18 @@ clean {
delete "${projectDir}/logstash-core/src/main/resources/org/logstash/plugins/plugin_aliases.yml"
}

def assemblyDeps = [downloadAndInstallJRuby, assemble] + subprojects.collect {
it.tasks.findByName("assemble")
def getAssemblyDeps() {
return project.hasProperty('skipJRubySetup') ? [] : [downloadAndInstallJRuby, assemble] + subprojects.collect {
it.tasks.findByName("assemble")
}
}

tasks.register("bootstrap") {
dependsOn assemblyDeps
onlyIf { !project.hasProperty('skipJRubySetup') }
dependsOn getAssemblyDeps()
doLast {
setupJruby(projectDir, buildDir)
}
setupJruby(projectDir, buildDir)
}
}


Expand Down
101 changes: 45 additions & 56 deletions logstash-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,12 @@ buildscript {
}
}

plugins {
id "jacoco"
id "org.sonarqube" version "4.3.0.3225"
}

apply plugin: 'jacoco'
apply plugin: "org.sonarqube"

repositories {
mavenCentral()
}

sonarqube {
properties {
property 'sonar.coverage.jacoco.xmlReportPaths', "${buildDir}/reports/jacoco/test/jacocoTestReport.xml"
}
}

jacoco {
toolVersion = "0.8.9"
}


import org.yaml.snakeyaml.Yaml
Expand Down Expand Up @@ -110,26 +95,7 @@ configurations.archives {
extendsFrom configurations.javadoc
}

def setupBCProvider() {
println "Setting up BC provider"
System.setProperty("javax.net.ssl.trustStore", "/etc/java/security/cacerts.bcfks")
System.setProperty("javax.net.ssl.trustStoreType", "BCFKS")
System.setProperty("javax.net.ssl.trustStoreProvider", "BCFIPS")
System.setProperty("javax.net.ssl.trustStorePassword", "changeit")

def provider = Class.forName('org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider').getDeclaredConstructor().newInstance()
java.security.Security.insertProviderAt(provider, 1)
}

tasks.register("javaTests", Test) {
doFirst {
configurations.testRuntimeClasspath.resolvedConfiguration.files.each { file ->
if (file.name.startsWith("bc-")) {
println "Found BC jar: ${file}"
}
}
setupBCProvider()
}
dependsOn ':bootstrap'
exclude '/org/logstash/RSpecTests.class'
exclude '/org/logstash/config/ir/ConfigCompilerTest.class'
Expand All @@ -143,31 +109,10 @@ tasks.register("javaTests", Test) {
exclude '/org/logstash/plugins/factory/PluginFactoryExtTest.class'
exclude '/org/logstash/execution/ObservedExecutionTest.class'

jacoco {
enabled = true
destinationFile = layout.buildDirectory.file('jacoco/test.exec').get().asFile
classDumpDir = layout.buildDirectory.dir('jacoco/classpathdumps').get().asFile
}
}

jacocoTestReport {
reports {
xml.required = true
html.required = true
}
}

javaTests.finalizedBy(jacocoTestReport)

tasks.register("rubyTests", Test) {
doFirst {
configurations.testRuntimeClasspath.resolvedConfiguration.files.each { file ->
if (file.name.startsWith("bc-")) {
println "Found BC jar: ${file}"
}
}
setupBCProvider()
}
dependsOn compileTestJava
inputs.files fileTree("${projectDir}/lib")
inputs.files fileTree("${projectDir}/spec")
Expand Down Expand Up @@ -223,6 +168,50 @@ task generateVersionInfoResources(type: DefaultTask) {
resourceFile.text = "logstash-core: ${logstashCoreVersion}"
}
}
// Test execution tasks without dependencies
tasks.register("javaTestsOnly", Test) {
setDependsOn([])
exclude '/org/logstash/RSpecTests.class'
exclude '/org/logstash/config/ir/ConfigCompilerTest.class'
exclude '/org/logstash/config/ir/CompiledPipelineTest.class'
exclude '/org/logstash/config/ir/EventConditionTest.class'
exclude '/org/logstash/config/ir/PipelineConfigTest.class'
exclude '/org/logstash/config/ir/compiler/OutputDelegatorTest.class'
exclude '/org/logstash/config/ir/compiler/JavaCodecDelegatorTest.class'
exclude '/org/logstash/plugins/NamespacedMetricImplTest.class'
exclude '/org/logstash/plugins/CounterMetricImplTest.class'
exclude '/org/logstash/plugins/factory/PluginFactoryExtTest.class'
exclude '/org/logstash/execution/ObservedExecutionTest.class'
}

tasks.register("rubyTestsOnly", Test) {
setDependsOn([])
inputs.files fileTree("${projectDir}/lib")
inputs.files fileTree("${projectDir}/spec")
systemProperty 'logstash.root.dir', projectDir.parent

include '/org/logstash/RSpecTests.class'
include '/org/logstash/config/ir/ConfigCompilerTest.class'
include '/org/logstash/config/ir/CompiledPipelineTest.class'
include '/org/logstash/config/ir/EventConditionTest.class'
include '/org/logstash/config/ir/PipelineConfigTest.class'
include '/org/logstash/config/ir/compiler/OutputDelegatorTest.class'
include '/org/logstash/config/ir/compiler/JavaCodecDelegatorTest.class'
include '/org/logstash/plugins/NamespacedMetricImplTest.class'
include '/org/logstash/plugins/CounterMetricImplTest.class'
include '/org/logstash/plugins/factory/PluginFactoryExtTest.class'
include '/org/logstash/execution/ObservedExecutionTest.class'
}

// Setup task combining all prerequisites
tasks.register("testSetup") {
dependsOn ':bootstrap'
dependsOn 'classes'
dependsOn 'testClasses'
dependsOn 'compileTestJava'
dependsOn 'copyRuntimeLibs'
dependsOn 'generateVersionInfoResources'
}
sourceSets {
main { output.dir(generateVersionInfoResources.outputs.files) }
}
Expand Down Expand Up @@ -294,4 +283,4 @@ dependencies {
api group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.14'
api group: 'commons-codec', name: 'commons-codec', version: '1.17.0'
api group: 'org.apache.httpcomponents', name: 'httpcore', version: '4.4.16'
}
}
1 change: 1 addition & 0 deletions rubyUtils.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ def customJRubyVersion = customJRubyDir == "" ? "" : Files.readAllLines(Paths.ge
def customJRubyTar = customJRubyDir == "" ? "" : (customJRubyDir + "/maven/jruby-dist/target/jruby-dist-${customJRubyVersion}-bin.tar.gz")

tasks.register("downloadJRuby", Download) {
onlyIf { !project.hasProperty('skipJRubySetup') }
description "Download JRuby artifact from this specific URL: ${jRubyURL}"
src jRubyURL
onlyIfNewer true
Expand Down
47 changes: 0 additions & 47 deletions test-init.gradle

This file was deleted.

0 comments on commit a7224b0

Please sign in to comment.