@@ -105,7 +105,7 @@ public void setup() throws IOException {
105105 upsertRole (Strings .format ("""
106106 {
107107 "cluster": ["all"],
108- "indices": [{"names": ["test*"], "privileges": ["write", "auto_configure"]}]
108+ "indices": [{"names": ["test*", "other*" ], "privileges": ["write", "auto_configure"]}]
109109 }""" ), WRITE_ACCESS );
110110 }
111111
@@ -1921,6 +1921,245 @@ public void testFailureStoreAccess() throws Exception {
19211921 }
19221922 }
19231923
1924+ public void testAliasBasedAccess () throws Exception {
1925+ List <String > docIds = setupDataStream ();
1926+ assertThat (docIds .size (), equalTo (2 ));
1927+ assertThat (docIds , hasItem ("1" ));
1928+ String dataDocId = "1" ;
1929+ String failuresDocId = docIds .stream ().filter (id -> false == id .equals (dataDocId )).findFirst ().get ();
1930+
1931+ List <String > otherDocIds = setupOtherDataStream ();
1932+ assertThat (otherDocIds .size (), equalTo (2 ));
1933+ assertThat (otherDocIds , hasItem ("3" ));
1934+ String otherDataDocId = "3" ;
1935+ String otherFailuresDocId = otherDocIds .stream ().filter (id -> false == id .equals (otherDataDocId )).findFirst ().get ();
1936+
1937+ final Tuple <String , String > backingIndices = getSingleDataAndFailureIndices ("test1" );
1938+ final String dataIndexName = backingIndices .v1 ();
1939+ final String failureIndexName = backingIndices .v2 ();
1940+
1941+ final String aliasName = "my-alias" ;
1942+ final String username = "user" ;
1943+ final String roleName = "role" ;
1944+
1945+ createUser (username , PASSWORD , roleName );
1946+ // manage is required to add the alias to the data stream
1947+ createOrUpdateRoleAndApiKey (username , roleName , Strings .format ("""
1948+ {
1949+ "cluster": ["all"],
1950+ "indices": [
1951+ {
1952+ "names": ["test1", "%s", "other1"],
1953+ "privileges": ["manage"]
1954+ }
1955+ ]
1956+ }
1957+ """ , aliasName ));
1958+
1959+ addAlias (username , "test1" , aliasName , "" );
1960+ addAlias (username , "other1" , aliasName , "" );
1961+ assertThat (fetchAliases (username , "test1" ), containsInAnyOrder (aliasName ));
1962+ expectSearchThrows (username , new Search (randomFrom (aliasName + "::data" , aliasName )), 403 );
1963+ expectSearchThrows (username , new Search (randomFrom (aliasName + "::failures" )), 403 );
1964+
1965+ createOrUpdateRoleAndApiKey (username , roleName , Strings .format ("""
1966+ {
1967+ "cluster": ["all"],
1968+ "indices": [
1969+ {
1970+ "names": ["%s"],
1971+ "privileges": ["read_failure_store"]
1972+ }
1973+ ]
1974+ }
1975+ """ , aliasName ));
1976+ expectSearch (username , new Search (aliasName + "::failures" ), failuresDocId , otherFailuresDocId );
1977+ expectSearchThrows (
1978+ username ,
1979+ new Search (randomFrom (aliasName + "::data" , "my-alias::failures" , dataIndexName , failureIndexName )),
1980+ 403
1981+ );
1982+
1983+ createOrUpdateRoleAndApiKey (username , roleName , Strings .format ("""
1984+ {
1985+ "cluster": ["all"],
1986+ "indices": [
1987+ {
1988+ "names": ["%s"],
1989+ "privileges": ["read"]
1990+ }
1991+ ]
1992+ }
1993+ """ , aliasName ));
1994+ expectSearch (username , new Search (randomFrom (aliasName + "::data" )), dataDocId , otherDataDocId );
1995+ expectSearchThrows (username , new Search (aliasName + "::failures" ), 403 );
1996+
1997+ expectThrows (() -> removeAlias (username , "test1" , aliasName ), 403 );
1998+ createOrUpdateRoleAndApiKey (username , roleName , Strings .format ("""
1999+ {
2000+ "cluster": ["all"],
2001+ "indices": [
2002+ {
2003+ "names": ["test1", "%s", "other1"],
2004+ "privileges": ["manage"]
2005+ }
2006+ ]
2007+ }
2008+ """ , aliasName ));
2009+ removeAlias (username , "test1" , aliasName );
2010+ removeAlias (username , "other1" , aliasName );
2011+
2012+ final String filteredAliasName = "my-filtered-alias" ;
2013+ createOrUpdateRoleAndApiKey (username , roleName , Strings .format ("""
2014+ {
2015+ "cluster": ["all"],
2016+ "indices": [
2017+ {
2018+ "names": ["test1", "%s", "other1"],
2019+ "privileges": ["manage"]
2020+ }
2021+ ]
2022+ }
2023+ """ , filteredAliasName ));
2024+ addAlias (username , "test1" , filteredAliasName , """
2025+ {
2026+ "term": {
2027+ "document.source.name": "jack"
2028+ }
2029+ }
2030+ """ );
2031+ addAlias (username , "other1" , filteredAliasName , """
2032+ {
2033+ "term": {
2034+ "document.source.name": "jack"
2035+ }
2036+ }
2037+ """ );
2038+ assertThat (fetchAliases (username , "test1" ), containsInAnyOrder (filteredAliasName ));
2039+ assertThat (fetchAliases (username , "other1" ), containsInAnyOrder (filteredAliasName ));
2040+
2041+ createOrUpdateRoleAndApiKey (username , roleName , Strings .format ("""
2042+ {
2043+ "cluster": ["all"],
2044+ "indices": [
2045+ {
2046+ "names": ["%s"],
2047+ "privileges": ["read", "read_failure_store"]
2048+ }
2049+ ]
2050+ }
2051+ """ , filteredAliasName ));
2052+
2053+ expectSearch (username , new Search (randomFrom (filteredAliasName + "::data" , filteredAliasName )));
2054+ // the alias filter is not applied to the failure store
2055+ expectSearch (username , new Search (filteredAliasName + "::failures" ), failuresDocId , otherFailuresDocId );
2056+ }
2057+
2058+ private void createOrUpdateRoleAndApiKey (String username , String roleName , String roleDescriptor ) throws IOException {
2059+ upsertRole (roleDescriptor , roleName );
2060+ createOrUpdateApiKey (username , randomBoolean () ? null : Strings .format ("""
2061+ {
2062+ "%s": %s
2063+ }
2064+ """ , roleName , roleDescriptor ));
2065+ }
2066+
2067+ private void addAlias (String user , String dataStream , String alias , String filter ) throws IOException {
2068+ aliasAction (user , "add" , dataStream , alias , filter );
2069+ }
2070+
2071+ private void removeAlias (String user , String dataStream , String alias ) throws IOException {
2072+ aliasAction (user , "remove" , dataStream , alias , "" );
2073+ }
2074+
2075+ private void aliasAction (String user , String action , String dataStream , String alias , String filter ) throws IOException {
2076+ Request request = new Request ("POST" , "/_aliases" );
2077+ if (filter == null || filter .isEmpty ()) {
2078+ request .setJsonEntity (Strings .format ("""
2079+ {
2080+ "actions": [
2081+ {
2082+ "%s": {
2083+ "index": "%s",
2084+ "alias": "%s"
2085+ }
2086+ }
2087+ ]
2088+ }
2089+ """ , action , dataStream , alias ));
2090+ } else {
2091+ request .setJsonEntity (Strings .format ("""
2092+ {
2093+ "actions": [
2094+ {
2095+ "%s": {
2096+ "index": "%s",
2097+ "alias": "%s",
2098+ "filter": %s
2099+ }
2100+ }
2101+ ]
2102+ }
2103+ """ , action , dataStream , alias , filter ));
2104+ }
2105+ Response response = performRequestMaybeUsingApiKey (user , request );
2106+ var path = assertOKAndCreateObjectPath (response );
2107+ assertThat (path .evaluate ("acknowledged" ), is (true ));
2108+ assertThat (path .evaluate ("errors" ), is (false ));
2109+
2110+ }
2111+
2112+ private Set <String > fetchAliases (String user , String dataStream ) throws IOException {
2113+ Response response = performRequestMaybeUsingApiKey (user , new Request ("GET" , dataStream + "/_alias" ));
2114+ ObjectPath path = assertOKAndCreateObjectPath (response );
2115+ Map <String , Object > aliases = path .evaluate (dataStream + ".aliases" );
2116+ return aliases .keySet ();
2117+ }
2118+
2119+ public void testPatternExclusions () throws Exception {
2120+ List <String > docIds = setupDataStream ();
2121+ assertThat (docIds .size (), equalTo (2 ));
2122+ assertThat (docIds , hasItem ("1" ));
2123+ String dataDocId = "1" ;
2124+ String failuresDocId = docIds .stream ().filter (id -> false == id .equals (dataDocId )).findFirst ().get ();
2125+
2126+ List <String > otherDocIds = setupOtherDataStream ();
2127+ assertThat (otherDocIds .size (), equalTo (2 ));
2128+ assertThat (otherDocIds , hasItem ("3" ));
2129+ String otherDataDocId = "3" ;
2130+ String otherFailuresDocId = otherDocIds .stream ().filter (id -> false == id .equals (otherDataDocId )).findFirst ().get ();
2131+
2132+ createUser ("user" , PASSWORD , "role" );
2133+ upsertRole ("""
2134+ {
2135+ "cluster": ["all"],
2136+ "indices": [
2137+ {
2138+ "names": ["test*", "other*"],
2139+ "privileges": ["read", "read_failure_store"]
2140+ }
2141+ ]
2142+ }
2143+ """ , "role" );
2144+ createAndStoreApiKey ("user" , randomBoolean () ? null : """
2145+ {
2146+ "role": {
2147+ "cluster": ["all"],
2148+ "indices": [
2149+ {
2150+ "names": ["*"],
2151+ "privileges": ["read", "read_failure_store"]
2152+ }
2153+ ]
2154+ }
2155+ }
2156+ """ );
2157+
2158+ // no exclusion -> should return two failure docs
2159+ expectSearch ("user" , new Search ("*::failures" ), failuresDocId , otherFailuresDocId );
2160+ expectSearch ("user" , new Search ("*::failures,-other*::failures" ), failuresDocId );
2161+ }
2162+
19242163 @ SuppressWarnings ("unchecked" )
19252164 private void expectEsql (String user , Search search , String ... docIds ) throws Exception {
19262165 var response = performRequestMaybeUsingApiKey (user , search .toEsqlRequest ());
0 commit comments