59
59
import static org .elasticsearch .packaging .util .Docker .waitForPathToExist ;
60
60
import static org .elasticsearch .packaging .util .FileMatcher .p600 ;
61
61
import static org .elasticsearch .packaging .util .FileMatcher .p660 ;
62
+ import static org .elasticsearch .packaging .util .FileMatcher .p775 ;
62
63
import static org .elasticsearch .packaging .util .FileUtils .append ;
63
64
import static org .elasticsearch .packaging .util .FileUtils .getTempDir ;
64
65
import static org .elasticsearch .packaging .util .FileUtils .rm ;
73
74
import static org .hamcrest .Matchers .is ;
74
75
import static org .hamcrest .Matchers .not ;
75
76
import static org .hamcrest .Matchers .nullValue ;
77
+ import static org .junit .Assume .assumeFalse ;
76
78
import static org .junit .Assume .assumeTrue ;
77
79
78
80
public class DockerTests extends PackagingTestCase {
@@ -334,10 +336,51 @@ public void test081ConfigurePasswordThroughEnvironmentVariableFile() throws Exce
334
336
assertThat ("Expected server to require authentication" , statusCode , equalTo (401 ));
335
337
}
336
338
339
+ /**
340
+ * Check that when verifying the file permissions of _FILE environment variables, symlinks
341
+ * are followed.
342
+ */
343
+ public void test082SymlinksAreFollowedWithEnvironmentVariableFiles () throws Exception {
344
+ // Test relies on configuring security
345
+ assumeTrue (distribution .isDefault ());
346
+ // Test relies on symlinks
347
+ assumeFalse (Platforms .WINDOWS );
348
+
349
+ final String xpackPassword = "hunter2" ;
350
+ final String passwordFilename = "password.txt" ;
351
+ final String symlinkFilename = "password_symlink" ;
352
+
353
+ // ELASTIC_PASSWORD_FILE
354
+ Files .writeString (tempDir .resolve (passwordFilename ), xpackPassword + "\n " );
355
+
356
+ // Link to the password file. We can't use an absolute path for the target, because
357
+ // it won't resolve inside the container.
358
+ Files .createSymbolicLink (tempDir .resolve (symlinkFilename ), Path .of (passwordFilename ));
359
+
360
+ Map <String , String > envVars = Map .of (
361
+ "ELASTIC_PASSWORD_FILE" ,
362
+ "/run/secrets/" + symlinkFilename ,
363
+ // Enable security so that we can test that the password has been used
364
+ "xpack.security.enabled" ,
365
+ "true"
366
+ );
367
+
368
+ // File permissions need to be secured in order for the ES wrapper to accept
369
+ // them for populating env var values. The wrapper will resolve the symlink
370
+ // and check the target's permissions.
371
+ Files .setPosixFilePermissions (tempDir .resolve (passwordFilename ), p600 );
372
+
373
+ final Map <Path , Path > volumes = Map .of (tempDir , Path .of ("/run/secrets" ));
374
+
375
+ // Restart the container - this will check that Elasticsearch started correctly,
376
+ // and didn't fail to follow the symlink and check the file permissions
377
+ runContainer (distribution (), volumes , envVars );
378
+ }
379
+
337
380
/**
338
381
* Check that environment variables cannot be used with _FILE environment variables.
339
382
*/
340
- public void test081CannotUseEnvVarsAndFiles () throws Exception {
383
+ public void test083CannotUseEnvVarsAndFiles () throws Exception {
341
384
final String optionsFilename = "esJavaOpts.txt" ;
342
385
343
386
// ES_JAVA_OPTS_FILE
@@ -368,7 +411,7 @@ public void test081CannotUseEnvVarsAndFiles() throws Exception {
368
411
* Check that when populating environment variables by setting variables with the suffix "_FILE",
369
412
* the files' permissions are checked.
370
413
*/
371
- public void test082EnvironmentVariablesUsingFilesHaveCorrectPermissions () throws Exception {
414
+ public void test084EnvironmentVariablesUsingFilesHaveCorrectPermissions () throws Exception {
372
415
final String optionsFilename = "esJavaOpts.txt" ;
373
416
374
417
// ES_JAVA_OPTS_FILE
@@ -390,11 +433,60 @@ public void test082EnvironmentVariablesUsingFilesHaveCorrectPermissions() throws
390
433
);
391
434
}
392
435
436
+ /**
437
+ * Check that when verifying the file permissions of _FILE environment variables, symlinks
438
+ * are followed, and that invalid target permissions are detected.
439
+ */
440
+ public void test085SymlinkToFileWithInvalidPermissionsIsRejected () throws Exception {
441
+ // Test relies on configuring security
442
+ assumeTrue (distribution .isDefault ());
443
+ // Test relies on symlinks
444
+ assumeFalse (Platforms .WINDOWS );
445
+
446
+ final String xpackPassword = "hunter2" ;
447
+ final String passwordFilename = "password.txt" ;
448
+ final String symlinkFilename = "password_symlink" ;
449
+
450
+ // ELASTIC_PASSWORD_FILE
451
+ Files .writeString (tempDir .resolve (passwordFilename ), xpackPassword + "\n " );
452
+
453
+ // Link to the password file. We can't use an absolute path for the target, because
454
+ // it won't resolve inside the container.
455
+ Files .createSymbolicLink (tempDir .resolve (symlinkFilename ), Path .of (passwordFilename ));
456
+
457
+ Map <String , String > envVars = Map .of (
458
+ "ELASTIC_PASSWORD_FILE" ,
459
+ "/run/secrets/" + symlinkFilename ,
460
+ // Enable security so that we can test that the password has been used
461
+ "xpack.security.enabled" ,
462
+ "true"
463
+ );
464
+
465
+ // Set invalid permissions on the file that the symlink targets
466
+ Files .setPosixFilePermissions (tempDir .resolve (passwordFilename ), p775 );
467
+
468
+ final Map <Path , Path > volumes = Map .of (tempDir , Path .of ("/run/secrets" ));
469
+
470
+ // Restart the container
471
+ final Result dockerLogs = runContainerExpectingFailure (distribution (), volumes , envVars );
472
+
473
+ assertThat (
474
+ dockerLogs .stderr ,
475
+ containsString (
476
+ "ERROR: File "
477
+ + passwordFilename
478
+ + " (target of symlink /run/secrets/"
479
+ + symlinkFilename
480
+ + " from ELASTIC_PASSWORD_FILE) must have file permissions 400 or 600, but actually has: 775"
481
+ )
482
+ );
483
+ }
484
+
393
485
/**
394
486
* Check that environment variables are translated to -E options even for commands invoked under
395
487
* `docker exec`, where the Docker image's entrypoint is not executed.
396
488
*/
397
- public void test83EnvironmentVariablesAreRespectedUnderDockerExec () {
489
+ public void test086EnvironmentVariablesAreRespectedUnderDockerExec () {
398
490
// This test relies on a CLI tool attempting to connect to Elasticsearch, and the
399
491
// tool in question is only in the default distribution.
400
492
assumeTrue (distribution .isDefault ());
@@ -405,10 +497,7 @@ public void test83EnvironmentVariablesAreRespectedUnderDockerExec() {
405
497
final Result result = sh .runIgnoreExitCode ("elasticsearch-setup-passwords auto" );
406
498
407
499
assertFalse ("elasticsearch-setup-passwords command should have failed" , result .isSuccess ());
408
- assertThat (
409
- result .stdout ,
410
- containsString ("java.net.UnknownHostException: this.is.not.valid: Name or service not known" )
411
- );
500
+ assertThat (result .stdout , containsString ("java.net.UnknownHostException: this.is.not.valid: Name or service not known" ));
412
501
}
413
502
414
503
/**
0 commit comments