Skip to content

Commit d17a8e4

Browse files
authored
[9.0] [Build] Add FIPS docker image for GovCloud (elastic#117152) (elastic#125674)
* [Build] Add FIPS docker image for GovCloud (elastic#117152) - Adds docker image based on chainguard base fips image - x86 only for now as the base image is x86 only - the image does not provide any elasticsearch.yml configuration. for testing purposes you can follow the elasticsearch fips guide available at https://github.com/elastic/FIPSGuide/tree/main/elasticsearch The image is shipped with: - org.bouncycastle:bc-fips:1.0.2.5 and org.bouncycastle:bctls-fips:1.0.19 in Elasticsearch libs folder - config/jvm.options.d/fips.options for fips specific JVM options - fips_java.security file - fips_java.policy Out of scope: - Add packaging test coverage (part of later PR as we want to provide that image for testing early and packaging tests require more general restructuring for support fips scenarios) * Fix fips tests reported as broken due to issue in gradle setup
1 parent 2b7c802 commit d17a8e4

File tree

8 files changed

+149
-19
lines changed

8 files changed

+149
-19
lines changed

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DockerBase.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public enum DockerBase {
3434
"apk",
3535
"Dockerfile"
3636
),
37+
38+
FIPS("docker.elastic.co/wolfi/chainguard-base-fips:sha256-feb7aeb1bbcb331afa089388f2fa1e81997fc24642ca4fa06b7e502ff599a4cf", "-fips", "apk"),
3739
// spotless:on
3840
// Based on WOLFI above, with more extras. We don't set a base image because
3941
// we programmatically extend from the wolfi image.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.gradle.internal.distribution;
11+
12+
import org.elasticsearch.gradle.ElasticsearchDistributionType;
13+
14+
public class DockerFipsElasticsearchDistributionType implements ElasticsearchDistributionType {
15+
16+
DockerFipsElasticsearchDistributionType() {}
17+
18+
@Override
19+
public String getName() {
20+
return "dockerFips";
21+
}
22+
23+
@Override
24+
public boolean isDocker() {
25+
return true;
26+
}
27+
}

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/distribution/InternalElasticsearchDistributionTypes.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,22 @@
1414
import java.util.List;
1515

1616
public class InternalElasticsearchDistributionTypes {
17+
1718
public static final ElasticsearchDistributionType DEB = new DebElasticsearchDistributionType();
1819
public static final ElasticsearchDistributionType RPM = new RpmElasticsearchDistributionType();
1920
public static final ElasticsearchDistributionType DOCKER = new DockerElasticsearchDistributionType();
2021
public static final ElasticsearchDistributionType DOCKER_IRONBANK = new DockerIronBankElasticsearchDistributionType();
2122
public static final ElasticsearchDistributionType DOCKER_CLOUD_ESS = new DockerCloudEssElasticsearchDistributionType();
2223
public static final ElasticsearchDistributionType DOCKER_WOLFI = new DockerWolfiElasticsearchDistributionType();
24+
public static final ElasticsearchDistributionType DOCKER_FIPS = new DockerFipsElasticsearchDistributionType();
2325

2426
public static final List<ElasticsearchDistributionType> ALL_INTERNAL = List.of(
2527
DEB,
2628
RPM,
2729
DOCKER,
2830
DOCKER_IRONBANK,
31+
DOCKER_WOLFI,
2932
DOCKER_CLOUD_ESS,
30-
DOCKER_WOLFI
33+
DOCKER_FIPS
3134
);
3235
}

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/DistroTestPlugin.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.DEB;
5252
import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.DOCKER;
5353
import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.DOCKER_CLOUD_ESS;
54+
import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.DOCKER_FIPS;
5455
import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.DOCKER_IRONBANK;
5556
import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.DOCKER_WOLFI;
5657
import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.RPM;
@@ -151,6 +152,7 @@ private static Map<ElasticsearchDistributionType, TaskProvider<?>> lifecycleTask
151152
lifecyleTasks.put(DOCKER_IRONBANK, project.getTasks().register(taskPrefix + ".docker-ironbank"));
152153
lifecyleTasks.put(DOCKER_CLOUD_ESS, project.getTasks().register(taskPrefix + ".docker-cloud-ess"));
153154
lifecyleTasks.put(DOCKER_WOLFI, project.getTasks().register(taskPrefix + ".docker-wolfi"));
155+
lifecyleTasks.put(DOCKER_FIPS, project.getTasks().register(taskPrefix + ".docker-fips"));
154156
lifecyleTasks.put(ARCHIVE, project.getTasks().register(taskPrefix + ".archives"));
155157
lifecyleTasks.put(DEB, project.getTasks().register(taskPrefix + ".packages"));
156158
lifecyleTasks.put(RPM, lifecyleTasks.get(DEB));

distribution/docker/build.gradle

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import org.elasticsearch.gradle.LoggedExec
22
import org.elasticsearch.gradle.VersionProperties
33
import org.elasticsearch.gradle.internal.DockerBase
44
import org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes
5+
import org.elasticsearch.gradle.internal.ExportElasticsearchBuildResourcesTask
56
import org.elasticsearch.gradle.internal.docker.DockerBuildTask
67
import org.elasticsearch.gradle.internal.docker.DockerSupportPlugin
78
import org.elasticsearch.gradle.internal.docker.DockerSupportService
@@ -17,6 +18,8 @@ apply plugin: 'elasticsearch.legacy-yaml-rest-test'
1718
apply plugin: 'elasticsearch.test.fixtures'
1819
apply plugin: 'elasticsearch.internal-distribution-download'
1920
apply plugin: 'elasticsearch.dra-artifacts'
21+
apply plugin: 'elasticsearch.jdk-download'
22+
apply plugin: 'elasticsearch.repositories'
2023

2124
String buildId = providers.systemProperty('build.id').getOrNull()
2225
boolean useLocalArtifacts = buildId != null && buildId.isBlank() == false && useDra == false
@@ -93,6 +96,7 @@ configurations {
9396
filebeat_x86_64
9497
metricbeat_aarch64
9598
metricbeat_x86_64
99+
fips
96100
}
97101

98102
String tiniArch = Architecture.current() == Architecture.AARCH64 ? 'arm64' : 'amd64'
@@ -109,6 +113,8 @@ dependencies {
109113
filebeat_x86_64 "beats:filebeat:${VersionProperties.elasticsearch}:[email protected]"
110114
metricbeat_aarch64 "beats:metricbeat:${VersionProperties.elasticsearch}:[email protected]"
111115
metricbeat_x86_64 "beats:metricbeat:${VersionProperties.elasticsearch}:[email protected]"
116+
fips "org.bouncycastle:bc-fips:1.0.2.5"
117+
fips "org.bouncycastle:bctls-fips:1.0.19"
112118
}
113119

114120
ext.expansions = { Architecture architecture, DockerBase base ->
@@ -287,6 +293,34 @@ void addBuildDockerContextTask(Architecture architecture, DockerBase base) {
287293
filter TransformLog4jConfigFilter
288294
}
289295
}
296+
if(base == DockerBase.FIPS) {
297+
298+
// If we're performing a release build, but `build.id` hasn't been set, we can
299+
// infer that we're not at the Docker building stage of the build, and therefore
300+
// we should skip the beats part of the build.
301+
String buildId = providers.systemProperty('build.id').getOrNull()
302+
boolean includeBeats = VersionProperties.isElasticsearchSnapshot() == true || buildId != null || useDra
303+
304+
if (includeBeats) {
305+
from configurations.getByName("filebeat_${architecture.classifier}")
306+
from configurations.getByName("metricbeat_${architecture.classifier}")
307+
// For some reason, the artifact name can differ depending on what repository we used.
308+
rename ~/((?:file|metric)beat)-.*\.tar\.gz$/, "\$1-${VersionProperties.elasticsearch}.tar.gz"
309+
}
310+
311+
into("plugins") {
312+
from configurations.allPlugins
313+
}
314+
315+
into("fips") {
316+
into("libs") {
317+
from configurations.fips
318+
}
319+
into("resources") {
320+
from tasks.named('fipsDockerResources')
321+
}
322+
}
323+
}
290324

291325
Provider<DockerSupportService> serviceProvider = GradleUtils.getBuildService(
292326
project.gradle.sharedServices,
@@ -442,7 +476,7 @@ void addBuildDockerImageTask(Architecture architecture, DockerBase base) {
442476
}
443477
}
444478

445-
void addBuildEssDockerImageTask(Architecture architecture) {
479+
void addBuildCloudDockerImageTasks(Architecture architecture) {
446480
DockerBase dockerBase = DockerBase.CLOUD_ESS
447481
String arch = architecture == Architecture.AARCH64 ? '-aarch64' : ''
448482
String contextDir = "${project.buildDir}/docker-context/elasticsearch${dockerBase.suffix}-${VersionProperties.elasticsearch}-docker-build-context${arch}"
@@ -474,10 +508,10 @@ void addBuildEssDockerImageTask(Architecture architecture) {
474508
from(projectDir.resolve("src/docker/Dockerfile.ess")) {
475509
expand(
476510
[
477-
base_image: "elasticsearch${baseSuffix}:${architecture.classifier}",
511+
base_image : "elasticsearch${baseSuffix}:${architecture.classifier}",
478512
docker_base: "${dockerBase.name().toLowerCase()}",
479-
version: "${VersionProperties.elasticsearch}",
480-
retry: ShellRetry
513+
version : "${VersionProperties.elasticsearch}",
514+
retry : ShellRetry
481515
]
482516
)
483517
filter SquashNewlinesFilter
@@ -512,17 +546,24 @@ void addBuildEssDockerImageTask(Architecture architecture) {
512546
}
513547
}
514548

549+
// fips
550+
TaskProvider<ExportElasticsearchBuildResourcesTask> fipsResourcesTask = tasks.register('fipsDockerResources', ExportElasticsearchBuildResourcesTask)
551+
fipsResourcesTask.configure {
552+
outputDir = project.layout.buildDirectory.dir('fips-docker-resources').get().asFile
553+
copy 'fips_java.security'
554+
copy 'fips_java.policy'
555+
}
556+
515557
for (final Architecture architecture : Architecture.values()) {
516558
for (final DockerBase base : DockerBase.values()) {
517559
if (base == DockerBase.CLOUD_ESS) {
518-
continue
560+
addBuildCloudDockerImageTasks(architecture)
561+
} else {
562+
addBuildDockerContextTask(architecture, base)
563+
addTransformDockerContextTask(architecture, base)
564+
addBuildDockerImageTask(architecture, base)
519565
}
520-
addBuildDockerContextTask(architecture, base)
521-
addTransformDockerContextTask(architecture, base)
522-
addBuildDockerImageTask(architecture, base)
523566
}
524-
525-
addBuildEssDockerImageTask(architecture)
526567
}
527568

528569
def exportDockerImages = tasks.register("exportDockerImages")
@@ -544,14 +585,17 @@ subprojects { Project subProject ->
544585
base = DockerBase.CLOUD_ESS
545586
} else if (subProject.name.contains('wolfi-')) {
546587
base = DockerBase.WOLFI
588+
} else if (subProject.name.contains('fips-')) {
589+
base = DockerBase.FIPS
547590
}
548591

549592
final String arch = architecture == Architecture.AARCH64 ? '-aarch64' : ''
550593
final String extension =
551594
(base == DockerBase.IRON_BANK ? 'ironbank.tar' :
552595
(base == DockerBase.CLOUD_ESS ? 'cloud-ess.tar' :
553-
(base == DockerBase.WOLFI ? 'wolfi.tar' :
554-
'docker.tar')))
596+
(base == DockerBase.FIPS ? 'fips.tar' :
597+
(base == DockerBase.WOLFI ? 'wolfi.tar' :
598+
'docker.tar'))))
555599
final String artifactName = "elasticsearch${arch}${base.suffix}_test"
556600

557601
final String exportTaskName = taskName("export", architecture, base, 'DockerImage')

distribution/docker/fips-docker-export/build.gradle

Whitespace-only changes.

distribution/docker/src/docker/Dockerfile

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ RUN chmod 0555 /bin/tini
4141
<% } else { %>
4242
4343
# Install required packages to extract the Elasticsearch distribution
44-
<% if (docker_base == "wolfi") { %>
44+
<% if (docker_base == "wolfi" || docker_base == "fips") { %>
4545
RUN <%= retry.loop(package_manager, "export DEBIAN_FRONTEND=noninteractive && ${package_manager} update && ${package_manager} update && ${package_manager} add --no-cache curl") %>
4646
<% } else { %>
4747
RUN <%= retry.loop(package_manager, "${package_manager} install -y findutils tar gzip") %>
@@ -115,6 +115,51 @@ RUN sed -i -e 's/ES_DISTRIBUTION_TYPE=tar/ES_DISTRIBUTION_TYPE=docker/' bin/elas
115115
chmod 0775 bin config config/jvm.options.d data logs plugins && \\
116116
find config -type f -exec chmod 0664 {} +
117117

118+
<% if (docker_base == "fips") { %>
119+
120+
# Add plugins infrastructure
121+
RUN mkdir -p /opt/plugins/archive
122+
RUN chmod -R 0555 /opt/plugins
123+
124+
RUN mkdir -p /fips/libs
125+
COPY fips/libs/*.jar /fips/libs/
126+
127+
COPY filebeat-${version}.tar.gz metricbeat-${version}.tar.gz /tmp/
128+
RUN set -eux ; \\
129+
for beat in filebeat metricbeat ; do \\
130+
if [ ! -s /tmp/\$beat-${version}.tar.gz ]; then \\
131+
echo "/tmp/\$beat-${version}.tar.gz is empty - cannot uncompress" 2>&1 ; \\
132+
exit 1 ; \\
133+
fi ; \\
134+
if ! tar tf /tmp/\$beat-${version}.tar.gz >/dev/null; then \\
135+
echo "/tmp/\$beat-${version}.tar.gz is corrupt - cannot uncompress" 2>&1 ; \\
136+
exit 1 ; \\
137+
fi ; \\
138+
mkdir -p /opt/\$beat ; \\
139+
tar xf /tmp/\$beat-${version}.tar.gz -C /opt/\$beat --strip-components=1 ; \\
140+
done
141+
142+
COPY plugins/*.zip /opt/plugins/archive/
143+
144+
RUN chown 1000:1000 /opt/plugins/archive/*
145+
RUN chmod 0444 /opt/plugins/archive/*
146+
147+
COPY fips/resources/fips_java.security /usr/share/elasticsearch/config/fips_java.security
148+
COPY fips/resources/fips_java.policy /usr/share/elasticsearch/config/fips_java.policy
149+
150+
WORKDIR /usr/share/elasticsearch/config
151+
152+
## Add fips specific JVM options
153+
RUN cat <<EOF > /usr/share/elasticsearch/config/jvm.options.d/fips.options
154+
-Djavax.net.ssl.keyStoreType=BCFKS
155+
-Dorg.bouncycastle.fips.approved_only=true
156+
-Djava.security.properties=config/fips_java.security
157+
-Djava.security.policy=config/fips_java.policy
158+
EOF
159+
160+
<% } %>
161+
162+
118163
################################################################################
119164
# Build stage 2 (the actual Elasticsearch image):
120165
#
@@ -134,7 +179,7 @@ RUN ${package_manager} update --setopt=tsflags=nodocs -y && \\
134179
nc shadow-utils zip findutils unzip procps-ng && \\
135180
${package_manager} clean all
136181
137-
<% } else if (docker_base == "wolfi") { %>
182+
<% } else if (docker_base == "wolfi" || docker_base == "fips") { %>
138183
RUN <%= retry.loop(package_manager,
139184
"export DEBIAN_FRONTEND=noninteractive && \n" +
140185
" ${package_manager} update && \n" +
@@ -163,7 +208,7 @@ RUN <%= retry.loop(
163208
<% } %>
164209
165210
166-
<% if (docker_base == "wolfi") { %>
211+
<% if (docker_base == "wolfi" || docker_base == "fips") { %>
167212
RUN groupadd -g 1000 elasticsearch && \
168213
adduser -G elasticsearch -u 1000 elasticsearch -D --home /usr/share/elasticsearch elasticsearch && \
169214
adduser elasticsearch root && \
@@ -179,7 +224,7 @@ ENV ELASTIC_CONTAINER true
179224
WORKDIR /usr/share/elasticsearch
180225
181226
COPY --from=builder --chown=0:0 /usr/share/elasticsearch /usr/share/elasticsearch
182-
<% if (docker_base != "wolfi") { %>
227+
<% if (docker_base != "wolfi" && docker_base != "fips") { %>
183228
COPY --from=builder --chown=0:0 /bin/tini /bin/tini
184229
<% } %>
185230
@@ -204,7 +249,7 @@ RUN chmod g=u /etc/passwd && \\
204249
chmod 0775 /usr/share/elasticsearch && \\
205250
chown elasticsearch bin config config/jvm.options.d data logs plugins
206251

207-
<% if (docker_base == 'wolfi') { %>
252+
<% if (docker_base == 'wolfi' || docker_base == "fips") { %>
208253
RUN ln -sf /etc/ssl/certs/java/cacerts /usr/share/elasticsearch/jdk/lib/security/cacerts
209254
<% } else { %>
210255
RUN ln -sf /etc/pki/ca-trust/extracted/java/cacerts /usr/share/elasticsearch/jdk/lib/security/cacerts
@@ -247,7 +292,7 @@ RUN mkdir /licenses && ln LICENSE.txt /licenses/LICENSE
247292
COPY LICENSE /licenses/LICENSE.addendum
248293
<% } %>
249294

250-
<% if (docker_base == "wolfi") { %>
295+
<% if (docker_base == "wolfi" || docker_base == "fips") { %>
251296
# Our actual entrypoint is `tini`, a minimal but functional init program. It
252297
# calls the entrypoint we provide, while correctly forwarding signals.
253298
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
@@ -267,6 +312,12 @@ USER 1000:0
267312
HEALTHCHECK --interval=10s --timeout=5s --start-period=1m --retries=5 CMD curl -I -f --max-time 5 http://localhost:9200 || exit 1
268313
<% } %>
269314

315+
<% if (docker_base == 'fips') { %>
316+
COPY --from=builder --chown=0:0 /opt /opt
317+
ENV ES_PLUGIN_ARCHIVE_DIR /opt/plugins/archive
318+
WORKDIR /usr/share/elasticsearch
319+
COPY --from=builder --chown=0:0 /fips/libs/*.jar /usr/share/elasticsearch/lib/
320+
<% } %>
270321
################################################################################
271322
# End of multi-stage Dockerfile
272323
################################################################################

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ List projects = [
7070
'distribution:docker:ironbank-docker-export',
7171
'distribution:docker:wolfi-docker-aarch64-export',
7272
'distribution:docker:wolfi-docker-export',
73+
'distribution:docker:fips-docker-export',
7374
'distribution:packages:aarch64-deb',
7475
'distribution:packages:deb',
7576
'distribution:packages:aarch64-rpm',

0 commit comments

Comments
 (0)