Skip to content

Commit bd87369

Browse files
authored
Ensure CI is run in FIPS 140 approved only mode (#64024)
We were depending on the BouncyCastle FIPS own mechanics to set itself in approved only mode since we run with the Security Manager enabled. The check during startup seems to happen before we set our restrictive SecurityManager though in org.elasticsearch.bootstrap.Elasticsearch , and this means that BCFIPS would not be in approved only mode, unless explicitly configured so. This commit sets the appropriate JVM property to explicitly set BCFIPS in approved only mode in CI and adds tests to ensure that we will be running with BCFIPS in approved only mode when we expect to. It also sets xpack.security.fips_mode.enabled to true for all test clusters used in fips mode and sets the distribution to the default one. It adds a password to the elasticsearch keystore for all test clusters that run in fips mode. Moreover, it changes a few unit tests where we would use bcrypt even in FIPS 140 mode. These would still pass since we are bundling our own bcrypt implementation, but are now changed to use FIPS 140 approved algorithms instead for better coverage. It also addresses a number of tests that would fail in approved only mode Mainly: Tests that use PBKDF2 with a password less than 112 bits (14char). We elected to change the passwords used everywhere to be at least 14 characters long instead of mandating the use of pbkdf2_stretch because both pbkdf2 and pbkdf2_stretch are supported and allowed in fips mode and it makes sense to test with both. We could possibly figure out the password algorithm used for each test and adjust password length accordingly only for pbkdf2 but there is little value in that. It's good practice to use strong passwords so if our docs and tests use longer passwords, then it's for the best. The approach is brittle as there is no guarantee that the next test that will be added won't use a short password, so we add some testing documentation too. This leaves us with a possible coverage gap since we do support passwords as short as 6 characters but we only test with > 14 chars but the validation itself was not tested even before. Tests can be added in a followup, outside of fips related context. Tests that use a PKCS12 keystore and were not already muted. Tests that depend on running test clusters with a basic license or using the OSS distribution as FIPS 140 support is not available in neither of these. Finally, it adds some information around FIPS 140 testing in our testing documentation reference so that developers can hopefully keep in mind fips 140 related intricacies when writing/changing docs.
1 parent 6493e65 commit bd87369

File tree

177 files changed

+1075
-584
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

177 files changed

+1075
-584
lines changed

TESTING.asciidoc

+75
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,81 @@ repository without fetching latest. For these use cases, you can set the system
556556
property `tests.bwc.git_fetch_latest` to `false` and the BWC builds will skip
557557
fetching the latest from the remote.
558558

559+
== Testing in FIPS 140-2 mode
560+
561+
We have a CI matrix job that periodically runs all our tests with the JVM configured
562+
to be FIPS 140-2 compliant with the use of the BouncyCastle FIPS approved Security Provider.
563+
FIPS 140-2 imposes certain requirements that affect how our tests should be set up or what
564+
can be tested. This section summarizes what one needs to take into consideration so that
565+
tests won't fail when run in fips mode.
566+
567+
=== Muting tests in FIPS 140-2 mode
568+
569+
If the following limitations cannot be observed, or there is a need to actually test some use
570+
case that is not available/allowed in fips mode, the test can be muted. For unit tests or Java
571+
rest tests one can use
572+
573+
------------------------------------------------
574+
assumeFalse("Justification why this cannot be run in FIPS mode", inFipsJvm());
575+
------------------------------------------------
576+
577+
For specific YAML rest tests one can use
578+
579+
------------------------------------------------
580+
- skip:
581+
features: fips_140
582+
reason: "Justification why this cannot be run in FIPS mode"
583+
------------------------------------------------
584+
585+
For disabling entire types of tests for subprojects, one can use for example:
586+
587+
------------------------------------------------
588+
if (BuildParams.inFipsJvm){
589+
// This test cluster is using a BASIC license and FIPS 140 mode is not supported in BASIC
590+
tasks.named("javaRestTest").configure{enabled = false }
591+
}
592+
------------------------------------------------
593+
594+
in `build.gradle`.
595+
596+
=== Limitations
597+
598+
The following should be taken into consideration when writing new tests or adjusting existing ones:
599+
600+
==== TLS
601+
602+
`JKS` and `PKCS#12` keystores cannot be used in FIPS mode. If the test depends on being able to use
603+
a keystore, it can be muted when needed ( see `ESTestCase#inFipsJvm` ). Alternatively, one can use
604+
PEM encoded files for keys and certificates for the tests or for setting up TLS in a test cluster.
605+
Also, when in FIPS 140 mode, hostname verification for TLS cannot be turned off so if you are using
606+
`*.verification_mode: none` , you'd need to mute the test in fips mode.
607+
608+
When using TLS, ensure that private keys used are longer than 2048 bits, or mute the test in fips mode.
609+
610+
==== Password hashing algorithm
611+
612+
Test clusters are configured with `xpack.security.fips_mode.enabled` set to true. This means that
613+
FIPS 140-2 related bootstrap checks are enabled and the test cluster will fail to form if the
614+
password hashing algorithm is set to something else than a PBKDF2 based one. You can delegate the choice
615+
of algorithm to i.e. `SecurityIntegTestCase#getFastStoredHashAlgoForTests` if you don't mind the
616+
actual algorithm used, or depend on default values for the test cluster nodes.
617+
618+
==== Password length
619+
620+
While using `pbkdf2` as the password hashing algorithm, FIPS 140-2 imposes a requirement that
621+
passwords are longer than 14 characters. You can either ensure that all test user passwords in
622+
your test are longer than 14 characters and use i.e. `SecurityIntegTestCase#getFastStoredHashAlgoForTests`
623+
to randomly select a hashing algorithm, or use `pbkdf2_stretch` that doesn't have the same
624+
limitation.
625+
626+
==== Keystore Password
627+
628+
In FIPS 140-2 mode, the elasticsearch keystore needs to be password protected with a password
629+
of appropriate length. This is handled automatically in `fips.gradle` and the keystore is unlocked
630+
on startup by the test clusters tooling in order to have secure settings available. However, you
631+
might need to take into consideration that the keystore is password-protected with `keystore-password`
632+
if you need to interact with it in a test.
633+
559634
== How to write good tests?
560635

561636
=== Base classes for test cases

build.gradle

+11-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,17 @@ tasks.register("verifyVersions") {
176176
*/
177177

178178
boolean bwc_tests_enabled = true
179-
final String bwc_tests_disabled_issue = "" /* place a PR link here when committing bwc changes */
179+
String bwc_tests_disabled_issue = "" /* place a PR link here when committing bwc changes */
180+
/*
181+
* FIPS 140-2 behavior was fixed in 7.11.0. Before that there is no way to run elasticsearch in a
182+
* JVM that is properly configured to be in fips mode with BCFIPS. For now we need to disable
183+
* all bwc testing in fips mode.
184+
*/
185+
186+
if ( BuildParams.inFipsJvm ) {
187+
bwc_tests_enabled = false
188+
bwc_tests_disabled_issue = "https://github.com/elastic/elasticsearch/issues/66772"
189+
}
180190
if (bwc_tests_enabled == false) {
181191
if (bwc_tests_disabled_issue.isEmpty()) {
182192
throw new GradleException("bwc_tests_disabled_issue must be set when bwc_tests_enabled == false")

buildSrc/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ public synchronized void start() {
511511
if (keystoreSettings.isEmpty() == false || keystoreFiles.isEmpty() == false) {
512512
logToProcessStdout("Adding " + keystoreSettings.size() + " keystore settings and " + keystoreFiles.size() + " keystore files");
513513

514-
keystoreSettings.forEach((key, value) -> runKeystoreCommandWithPassword(keystorePassword, value.toString(), "add", "-x", key));
514+
keystoreSettings.forEach((key, value) -> runKeystoreCommandWithPassword(keystorePassword, value.toString(), "add", key));
515515

516516
for (Map.Entry<String, File> entry : keystoreFiles.entrySet()) {
517517
File file = entry.getValue();

client/rest-high-level/build.gradle

+3-3
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,14 @@ File pkiTrustCert = file("./src/test/resources/org/elasticsearch/client/security
7575
tasks.named("integTest").configure {
7676
systemProperty 'tests.rest.async', 'false'
7777
systemProperty 'tests.rest.cluster.username', System.getProperty('tests.rest.cluster.username', 'test_user')
78-
systemProperty 'tests.rest.cluster.password', System.getProperty('tests.rest.cluster.password', 'test-password')
78+
systemProperty 'tests.rest.cluster.password', System.getProperty('tests.rest.cluster.password', 'test-user-password')
7979
}
8080

8181
// Requires https://github.com/elastic/elasticsearch/pull/64403 to have this moved to task avoidance api.
8282
TaskProvider<RestIntegTestTask> asyncIntegTest = tasks.register("asyncIntegTest", RestIntegTestTask) {
8383
systemProperty 'tests.rest.async', 'true'
8484
systemProperty 'tests.rest.cluster.username', System.getProperty('tests.rest.cluster.username', 'test_user')
85-
systemProperty 'tests.rest.cluster.password', System.getProperty('tests.rest.cluster.password', 'test-password')
85+
systemProperty 'tests.rest.cluster.password', System.getProperty('tests.rest.cluster.password', 'test-user-password')
8686
}
8787

8888
tasks.named("check").configure {
@@ -113,7 +113,7 @@ testClusters.all {
113113
keystore 'xpack.security.transport.ssl.truststore.secure_password', 'testnode'
114114
extraConfigFile 'roles.yml', file('roles.yml')
115115
user username: System.getProperty('tests.rest.cluster.username', 'test_user'),
116-
password: System.getProperty('tests.rest.cluster.password', 'test-password'),
116+
password: System.getProperty('tests.rest.cluster.password', 'test-user-password'),
117117
role: System.getProperty('tests.rest.cluster.role', 'admin')
118118
user username: 'admin_user', password: 'admin-password'
119119

client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ private PutUserRequest randomPutUserRequest(boolean enabled) {
222222
}
223223

224224
private static PutUserRequest randomPutUserRequest(User user, boolean enabled) {
225-
final char[] password = randomAlphaOfLengthBetween(6, 10).toCharArray();
225+
final char[] password = randomAlphaOfLengthBetween(14, 19).toCharArray();
226226
return new PutUserRequest(user, password, enabled, RefreshPolicy.IMMEDIATE);
227227
}
228228

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CRUDDocumentationIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,7 @@ public void testReindex() throws Exception {
845845
Integer remotePort = host.getPort();
846846
String remoteHost = host.getHostName();
847847
String user = "test_user";
848-
String password = "test-password";
848+
String password = "test-user-password";
849849

850850
// tag::reindex-request-remote
851851
request.setRemoteInfo(

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

+14-13
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@ protected Settings restAdminSettings() {
158158
public void testGetUsers() throws Exception {
159159
final RestHighLevelClient client = highLevelClient();
160160
String[] usernames = new String[] {"user1", "user2", "user3"};
161-
addUser(client, usernames[0], randomAlphaOfLengthBetween(6, 10));
162-
addUser(client, usernames[1], randomAlphaOfLengthBetween(6, 10));
163-
addUser(client, usernames[2], randomAlphaOfLengthBetween(6, 10));
161+
addUser(client, usernames[0], randomAlphaOfLengthBetween(14, 18));
162+
addUser(client, usernames[1], randomAlphaOfLengthBetween(14, 18));
163+
addUser(client, usernames[2], randomAlphaOfLengthBetween(14, 18));
164164
{
165165
//tag::get-users-request
166166
GetUsersRequest request = new GetUsersRequest(usernames[0]);
@@ -253,7 +253,7 @@ public void testPutUser() throws Exception {
253253

254254
{
255255
//tag::put-user-password-request
256-
char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
256+
char[] password = new char[]{'t', 'e', 's', 't', '-', 'u','s','e','r','-','p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
257257
User user = new User("example", Collections.singletonList("superuser"));
258258
PutUserRequest request = PutUserRequest.withPassword(user, password, true, RefreshPolicy.NONE);
259259
//end::put-user-password-request
@@ -272,7 +272,7 @@ public void testPutUser() throws Exception {
272272
byte[] salt = new byte[32];
273273
// no need for secure random in a test; it could block and would not be reproducible anyway
274274
random().nextBytes(salt);
275-
char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
275+
char[] password = new char[]{'t', 'e', 's', 't', '-', 'u','s','e','r','-','p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
276276
User user = new User("example2", Collections.singletonList("superuser"));
277277

278278
//tag::put-user-hash-request
@@ -328,7 +328,7 @@ public void onFailure(Exception e) {
328328

329329
public void testDeleteUser() throws Exception {
330330
RestHighLevelClient client = highLevelClient();
331-
addUser(client, "testUser", "testPassword");
331+
addUser(client, "testUser", "testUserPassword");
332332

333333
{
334334
// tag::delete-user-request
@@ -568,7 +568,7 @@ public void onFailure(Exception e) {
568568

569569
public void testEnableUser() throws Exception {
570570
RestHighLevelClient client = highLevelClient();
571-
char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
571+
char[] password = new char[]{'t', 'e', 's', 't', '-', 'u','s','e','r','-','p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
572572
User enable_user = new User("enable_user", Collections.singletonList("superuser"));
573573
PutUserRequest putUserRequest = PutUserRequest.withPassword(enable_user, password, true, RefreshPolicy.IMMEDIATE);
574574
PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT);
@@ -613,7 +613,7 @@ public void onFailure(Exception e) {
613613

614614
public void testDisableUser() throws Exception {
615615
RestHighLevelClient client = highLevelClient();
616-
char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
616+
char[] password = new char[]{'t', 'e', 's', 't', '-', 'u','s','e','r','-','p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
617617
User disable_user = new User("disable_user", Collections.singletonList("superuser"));
618618
PutUserRequest putUserRequest = PutUserRequest.withPassword(disable_user, password, true, RefreshPolicy.IMMEDIATE);
619619
PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT);
@@ -1185,8 +1185,9 @@ public void onFailure(Exception e) {
11851185

11861186
public void testChangePassword() throws Exception {
11871187
RestHighLevelClient client = highLevelClient();
1188-
char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
1189-
char[] newPassword = new char[]{'n', 'e', 'w', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
1188+
char[] password = new char[]{'t', 'e', 's', 't', '-', 'u','s','e','r','-','p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
1189+
char[] newPassword =
1190+
new char[]{'n', 'e', 'w', '-', 't', 'e', 's', 't', '-', 'u','s','e','r','-','p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
11901191
User user = new User("change_password_user", Collections.singletonList("superuser"), Collections.emptyMap(), null, null);
11911192
PutUserRequest putUserRequest = PutUserRequest.withPassword(user, password, true, RefreshPolicy.NONE);
11921193
PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT);
@@ -1405,14 +1406,14 @@ public void testCreateToken() throws Exception {
14051406
{
14061407
// Setup user
14071408
User token_user = new User("token_user", Collections.singletonList("kibana_user"));
1408-
PutUserRequest putUserRequest = PutUserRequest.withPassword(token_user, "password".toCharArray(), true,
1409+
PutUserRequest putUserRequest = PutUserRequest.withPassword(token_user, "test-user-password".toCharArray(), true,
14091410
RefreshPolicy.IMMEDIATE);
14101411
PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT);
14111412
assertTrue(putUserResponse.isCreated());
14121413
}
14131414
{
14141415
// tag::create-token-password-request
1415-
final char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
1416+
final char[] password = new char[]{'t', 'e', 's', 't', '-', 'u','s','e','r','-','p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
14161417
CreateTokenRequest createTokenRequest = CreateTokenRequest.passwordGrant("token_user", password);
14171418
// end::create-token-password-request
14181419

@@ -1482,7 +1483,7 @@ public void testInvalidateToken() throws Exception {
14821483
String refreshToken;
14831484
{
14841485
// Setup users
1485-
final char[] password = "password".toCharArray();
1486+
final char[] password = "test-user-password".toCharArray();
14861487
User user = new User("user", Collections.singletonList("kibana_user"));
14871488
PutUserRequest putUserRequest = PutUserRequest.withPassword(user, password, true, RefreshPolicy.IMMEDIATE);
14881489
PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT);

client/rest/src/test/java/org/elasticsearch/client/documentation/RestClientDocumentation.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ public HttpAsyncClientBuilder customizeHttpClient(
359359
final CredentialsProvider credentialsProvider =
360360
new BasicCredentialsProvider();
361361
credentialsProvider.setCredentials(AuthScope.ANY,
362-
new UsernamePasswordCredentials("user", "password"));
362+
new UsernamePasswordCredentials("user", "test-user-password"));
363363

364364
RestClientBuilder builder = RestClient.builder(
365365
new HttpHost("localhost", 9200))
@@ -378,7 +378,7 @@ public HttpAsyncClientBuilder customizeHttpClient(
378378
final CredentialsProvider credentialsProvider =
379379
new BasicCredentialsProvider();
380380
credentialsProvider.setCredentials(AuthScope.ANY,
381-
new UsernamePasswordCredentials("user", "password"));
381+
new UsernamePasswordCredentials("user", "test-user-password"));
382382

383383
RestClientBuilder builder = RestClient.builder(
384384
new HttpHost("localhost", 9200))

distribution/docker/build.gradle

+12-5
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,17 @@ def createAndSetWritable(Object... locations) {
204204
}
205205
}
206206

207-
tasks.register("copyKeystore", Sync) {
207+
tasks.register("copyNodeKeyMaterial", Sync) {
208208
from project(':x-pack:plugin:core')
209-
.file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks')
209+
.files(
210+
'src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem',
211+
'src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt'
212+
)
210213
into "${buildDir}/certs"
211214
doLast {
212215
file("${buildDir}/certs").setReadable(true, false)
213-
file("${buildDir}/certs/testnode.jks").setReadable(true, false)
216+
file("${buildDir}/certs/testnode.pem").setReadable(true, false)
217+
file("${buildDir}/certs/testnode.crt").setReadable(true, false)
214218
}
215219
}
216220

@@ -230,7 +234,7 @@ elasticsearch_distributions {
230234

231235
tasks.named("preProcessFixture").configure {
232236
dependsOn elasticsearch_distributions.docker_default, elasticsearch_distributions.docker_oss
233-
dependsOn "copyKeystore"
237+
dependsOn "copyNodeKeyMaterial"
234238
doLast {
235239
// tests expect to have an empty repo
236240
project.delete(
@@ -250,7 +254,10 @@ tasks.named("preProcessFixture").configure {
250254

251255
tasks.named("processTestResources").configure {
252256
from project(':x-pack:plugin:core')
253-
.file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks')
257+
.files(
258+
'src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem',
259+
'src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt'
260+
)
254261
}
255262

256263
tasks.register("integTest", Test) {

distribution/docker/docker-compose.yml

+12-6
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,17 @@ services:
2323
- xpack.security.audit.enabled=true
2424
- xpack.security.authc.realms.file.file1.order=0
2525
- xpack.security.authc.realms.native.native1.order=1
26-
- xpack.security.transport.ssl.keystore.path=/usr/share/elasticsearch/config/testnode.jks
27-
- xpack.security.http.ssl.keystore.path=/usr/share/elasticsearch/config/testnode.jks
26+
- xpack.security.transport.ssl.key=/usr/share/elasticsearch/config/testnode.pem
27+
- xpack.security.transport.ssl.certificate=/usr/share/elasticsearch/config/testnode.crt
28+
- xpack.security.http.ssl.key=/usr/share/elasticsearch/config/testnode.pem
29+
- xpack.security.http.ssl.certificate=/usr/share/elasticsearch/config/testnode.crt
2830
- xpack.http.ssl.verification_mode=certificate
2931
- xpack.security.transport.ssl.verification_mode=certificate
3032
- xpack.license.self_generated.type=trial
3133
volumes:
3234
- ./build/repo:/tmp/es-repo
33-
- ./build/certs/testnode.jks:/usr/share/elasticsearch/config/testnode.jks
35+
- ./build/certs/testnode.pem:/usr/share/elasticsearch/config/testnode.pem
36+
- ./build/certs/testnode.crt:/usr/share/elasticsearch/config/testnode.crt
3437
- ./build/logs/default-1:/usr/share/elasticsearch/logs
3538
- ./docker-test-entrypoint.sh:/docker-test-entrypoint.sh
3639
ports:
@@ -71,14 +74,17 @@ services:
7174
- xpack.security.audit.enabled=true
7275
- xpack.security.authc.realms.file.file1.order=0
7376
- xpack.security.authc.realms.native.native1.order=1
74-
- xpack.security.transport.ssl.keystore.path=/usr/share/elasticsearch/config/testnode.jks
75-
- xpack.security.http.ssl.keystore.path=/usr/share/elasticsearch/config/testnode.jks
77+
- xpack.security.transport.ssl.key=/usr/share/elasticsearch/config/testnode.pem
78+
- xpack.security.transport.ssl.certificate=/usr/share/elasticsearch/config/testnode.crt
79+
- xpack.security.http.ssl.key=/usr/share/elasticsearch/config/testnode.pem
80+
- xpack.security.http.ssl.certificate=/usr/share/elasticsearch/config/testnode.crt
7681
- xpack.http.ssl.verification_mode=certificate
7782
- xpack.security.transport.ssl.verification_mode=certificate
7883
- xpack.license.self_generated.type=trial
7984
volumes:
8085
- ./build/repo:/tmp/es-repo
81-
- ./build/certs/testnode.jks:/usr/share/elasticsearch/config/testnode.jks
86+
- ./build/certs/testnode.pem:/usr/share/elasticsearch/config/testnode.pem
87+
- ./build/certs/testnode.crt:/usr/share/elasticsearch/config/testnode.crt
8288
- ./build/logs/default-2:/usr/share/elasticsearch/logs
8389
- ./docker-test-entrypoint.sh:/docker-test-entrypoint.sh
8490
ports:

0 commit comments

Comments
 (0)