4242import static org .elasticsearch .test .MapMatcher .matchesMap ;
4343import static org .hamcrest .Matchers .containsString ;
4444import static org .hamcrest .Matchers .equalTo ;
45+ import static org .hamcrest .Matchers .is ;
4546import static org .hamcrest .Matchers .not ;
47+ import static org .hamcrest .Matchers .nullValue ;
4648
4749public class EsqlSecurityIT extends ESRestTestCase {
4850 @ ClassRule
@@ -59,10 +61,14 @@ public class EsqlSecurityIT extends ESRestTestCase {
5961 .user ("user5" , "x-pack-test-password" , "user5" , false )
6062 .user ("fls_user" , "x-pack-test-password" , "fls_user" , false )
6163 .user ("fls_user2" , "x-pack-test-password" , "fls_user2" , false )
64+ .user ("fls_user2_alias" , "x-pack-test-password" , "fls_user2_alias" , false )
6265 .user ("fls_user3" , "x-pack-test-password" , "fls_user3" , false )
66+ .user ("fls_user3_alias" , "x-pack-test-password" , "fls_user3_alias" , false )
6367 .user ("fls_user4_1" , "x-pack-test-password" , "fls_user4_1" , false )
68+ .user ("fls_user4_1_alias" , "x-pack-test-password" , "fls_user4_1_alias" , false )
6469 .user ("dls_user" , "x-pack-test-password" , "dls_user" , false )
6570 .user ("metadata1_read2" , "x-pack-test-password" , "metadata1_read2" , false )
71+ .user ("metadata1_alias_read2" , "x-pack-test-password" , "metadata1_alias_read2" , false )
6672 .user ("alias_user1" , "x-pack-test-password" , "alias_user1" , false )
6773 .user ("alias_user2" , "x-pack-test-password" , "alias_user2" , false )
6874 .user ("logs_foo_all" , "x-pack-test-password" , "logs_foo_all" , false )
@@ -159,6 +165,12 @@ public void indexDocuments() throws IOException {
159165 }
160166 }
161167 },
168+ {
169+ "add": {
170+ "alias": "lookup-second-alias",
171+ "index": "lookup-user2"
172+ }
173+ },
162174 {
163175 "add": {
164176 "alias": "second-alias",
@@ -196,6 +208,17 @@ private void createMultiRoleUsers() throws IOException {
196208 }
197209 """ );
198210 assertOK (client ().performRequest (request ));
211+
212+ request = new Request ("POST" , "_security/user/fls_user4_alias" );
213+ request .setJsonEntity ("""
214+ {
215+ "password" : "x-pack-test-password",
216+ "roles" : [ "fls_user4_1_alias", "fls_user4_2_alias" ],
217+ "full_name" : "Test Role",
218+ 219+ }
220+ """ );
221+ assertOK (client ().performRequest (request ));
199222 }
200223
201224 protected MapMatcher responseMatcher (Map <String , Object > result ) {
@@ -586,22 +609,51 @@ public void testLookupJoinIndexAllowed() throws Exception {
586609 );
587610 assertThat (respMap .get ("values" ), equalTo (List .of (List .of (40.0 , "sales" ))));
588611
589- // Aliases are not allowed in LOOKUP JOIN
590- var resp2 = expectThrows (
612+ // user is not allowed to use the alias (but is allowed to use the index)
613+ expectThrows (
591614 ResponseException .class ,
592- () -> runESQLCommand ("alias_user1" , "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org" )
615+ () -> runESQLCommand (
616+ "metadata1_read2" ,
617+ "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value | KEEP x, org"
618+ )
593619 );
594620
595- assertThat (resp2 .getMessage (), containsString ("Aliases and index patterns are not allowed for LOOKUP JOIN [lookup-first-alias]" ));
596- assertThat (resp2 .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
597-
598- // Aliases are not allowed in LOOKUP JOIN, regardless of alias filters
599- resp2 = expectThrows (
621+ // user is not allowed to use the index (but is allowed to use the alias)
622+ expectThrows (
600623 ResponseException .class ,
601- () -> runESQLCommand ("alias_user1" , "ROW x = 123.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org" )
624+ () -> runESQLCommand ("metadata1_alias_read2" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org" )
625+ );
626+
627+ // user has permission on the alias, and can read the key
628+ resp = runESQLCommand (
629+ "metadata1_alias_read2" ,
630+ "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value | KEEP x, org"
631+ );
632+ assertOK (resp );
633+ respMap = entityAsMap (resp );
634+ assertThat (
635+ respMap .get ("columns" ),
636+ equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
637+ );
638+ assertThat (respMap .get ("values" ), equalTo (List .of (List .of (40.0 , "sales" ))));
639+
640+ // user has permission on the alias, but can't read the key (doc level security)
641+ resp = runESQLCommand (
642+ "metadata1_alias_read2" ,
643+ "ROW x = 32.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value | KEEP x, org"
644+ );
645+ assertOK (resp );
646+ respMap = entityAsMap (resp );
647+ assertThat (
648+ respMap .get ("columns" ),
649+ equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
602650 );
603- assertThat (resp2 .getMessage (), containsString ("Aliases and index patterns are not allowed for LOOKUP JOIN [lookup-first-alias]" ));
604- assertThat (resp2 .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
651+ List <?> values = (List <?>) respMap .get ("values" );
652+ assertThat (values .size (), is (1 ));
653+ List <?> row = (List <?>) values .get (0 );
654+ assertThat (row .size (), is (2 ));
655+ assertThat (row .get (0 ), is (32.0 ));
656+ assertThat (row .get (1 ), is (nullValue ()));
605657 }
606658
607659 @ SuppressWarnings ("unchecked" )
@@ -712,6 +764,64 @@ public void testLookupJoinFieldLevelSecurity() throws Exception {
712764 assertThat (error .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
713765 }
714766
767+ public void testLookupJoinFieldLevelSecurityOnAlias () throws Exception {
768+ assumeTrue (
769+ "Requires LOOKUP JOIN capability" ,
770+ EsqlSpecTestCase .hasCapabilities (adminClient (), List .of (EsqlCapabilities .Cap .JOIN_LOOKUP_V12 .capabilityName ()))
771+ );
772+
773+ Response resp = runESQLCommand ("fls_user2_alias" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value" );
774+ assertOK (resp );
775+ Map <String , Object > respMap = entityAsMap (resp );
776+ assertThat (
777+ respMap .get ("columns" ),
778+ equalTo (
779+ List .of (
780+ Map .of ("name" , "x" , "type" , "double" ),
781+ Map .of ("name" , "value" , "type" , "double" ),
782+ Map .of ("name" , "org" , "type" , "keyword" )
783+ )
784+ )
785+ );
786+
787+ resp = runESQLCommand ("fls_user3_alias" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value" );
788+ assertOK (resp );
789+ respMap = entityAsMap (resp );
790+ assertThat (
791+ respMap .get ("columns" ),
792+ equalTo (
793+ List .of (
794+ Map .of ("name" , "x" , "type" , "double" ),
795+ Map .of ("name" , "value" , "type" , "double" ),
796+ Map .of ("name" , "org" , "type" , "keyword" ),
797+ Map .of ("name" , "other" , "type" , "keyword" )
798+ )
799+ )
800+
801+ );
802+
803+ resp = runESQLCommand ("fls_user4_alias" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value" );
804+ assertOK (resp );
805+ respMap = entityAsMap (resp );
806+ assertThat (
807+ respMap .get ("columns" ),
808+ equalTo (
809+ List .of (
810+ Map .of ("name" , "x" , "type" , "double" ),
811+ Map .of ("name" , "value" , "type" , "double" ),
812+ Map .of ("name" , "org" , "type" , "keyword" )
813+ )
814+ )
815+ );
816+
817+ ResponseException error = expectThrows (
818+ ResponseException .class ,
819+ () -> runESQLCommand ("fls_user4_1_alias" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-second-alias ON value" )
820+ );
821+ assertThat (error .getMessage (), containsString ("Unknown column [value] in right side of join" ));
822+ assertThat (error .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
823+ }
824+
715825 public void testLookupJoinIndexForbidden () throws Exception {
716826 assumeTrue (
717827 "Requires LOOKUP JOIN capability" ,
0 commit comments