You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by sh...@apache.org on 2019/08/29 05:27:55 UTC

[lucene-solr] branch master updated: SOLR-13649 change the default behavior of the basic authentication plugin. (#805)

This is an automated email from the ASF dual-hosted git repository.

shalin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/master by this push:
     new b37d92b  SOLR-13649 change the default behavior of the basic authentication plugin. (#805)
b37d92b is described below

commit b37d92bfee63a9ede2a754347cbe8627dedade33
Author: Marcus <ma...@gmail.com>
AuthorDate: Wed Aug 28 22:27:50 2019 -0700

    SOLR-13649 change the default behavior of the basic authentication plugin. (#805)
    
    SOLR-13649: Property 'blockUnknown' of BasicAuthPlugin and JWTAuthPlugin now defaults to 'true'. This change is backward incompatible. To achieve the previous default behavior, explicitly set 'blockUnknown':'false' in security.json
---
 solr/CHANGES.txt                                   |  4 ++
 .../org/apache/solr/security/BasicAuthPlugin.java  |  2 +-
 .../security/Sha256AuthenticationProvider.java     | 17 +++++--
 .../src/java/org/apache/solr/util/SolrCLI.java     |  2 +-
 .../org/apache/solr/cloud/TestConfigSetsAPI.java   |  1 +
 .../handler/admin/SecurityConfHandlerTest.java     |  9 ++--
 .../solr/security/BasicAuthIntegrationTest.java    |  3 +-
 .../security/TestSha256AuthenticationProvider.java | 58 +++++++++++++++++++---
 .../src/basic-authentication-plugin.adoc           |  2 +-
 .../src/jwt-authentication-plugin.adoc             | 11 ++--
 .../src/major-changes-in-solr-9.adoc               | 46 +++++++++++++++++
 .../src/solr-control-script-reference.adoc         |  2 +-
 solr/solr-ref-guide/src/solr-upgrade-notes.adoc    |  2 +-
 13 files changed, 131 insertions(+), 28 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 8c0fe1a..7bef392 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -57,6 +57,10 @@ Upgrade Notes
 
 * SOLR-13596: Deprecated GroupingSpecification methods are removed. (Munendra S N)
 
+* SOLR-13649: Property 'blockUnknown' of BasicAuthPlugin and JWTAuthPlugin now defaults to 'true'. This change is backward
+  incompatible. To achieve the previous default behavior, explicitly set 'blockUnknown':'false' in security.json
+  (marcussorealheis, janhoy, shalin)
+
 * SOLR-11266: default Content-Type override for JSONResponseWriter from _default configSet is removed. Example has been
   provided in sample_techproducts_configs to override content-type. (Ishan Chattopadhyaya, Munendra S N, Gus Heck)
 
diff --git a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
index 770cedf..06b3b26 100644
--- a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
@@ -62,7 +62,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
   private AuthenticationProvider authenticationProvider;
   private final static ThreadLocal<Header> authHeader = new ThreadLocal<>();
   private static final String X_REQUESTED_WITH_HEADER = "X-Requested-With";
-  private boolean blockUnknown = false;
+  private boolean blockUnknown = true;
   private boolean forwardCredentials = false;
 
   public boolean authenticate(String username, String pwd) {
diff --git a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
index e8cc87a..1c6264e 100644
--- a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
+++ b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
@@ -40,6 +40,8 @@ import org.slf4j.LoggerFactory;
 import static org.apache.solr.handler.admin.SecurityConfHandler.getMapValue;
 
 public class Sha256AuthenticationProvider implements ConfigEditablePlugin,  BasicAuthPlugin.AuthenticationProvider {
+
+  static String CANNOT_DELETE_LAST_USER_ERROR = "You cannot delete the last user. At least one user must be configured at all times.";
   private Map<String, String> credentials;
   private String realm;
   private Map<String, String> promptHeader;
@@ -73,9 +75,8 @@ public class Sha256AuthenticationProvider implements ConfigEditablePlugin,  Basi
     promptHeader = Collections.unmodifiableMap(Collections.singletonMap("WWW-Authenticate", "Basic realm=\"" + realm + "\""));
     credentials = new LinkedHashMap<>();
     Map<String,String> users = (Map<String,String>) pluginConfig.get("credentials");
-    if (users == null) {
-      log.debug("No users configured yet");
-      return;
+    if (users == null || users.isEmpty()) {
+      throw new IllegalStateException("No users configured yet. At least one user must be configured in security.json");
     }
     for (Map.Entry<String, String> e : users.entrySet()) {
       String v = e.getValue();
@@ -142,7 +143,15 @@ public class Sha256AuthenticationProvider implements ConfigEditablePlugin,  Basi
           cmd.addError("No such user(s) " +names );
           return null;
         }
-        for (String name : names) map.remove(name);
+        for (String name : names) {
+          if (map.containsKey(name)) {
+            if (map.size() == 1){
+              cmd.addError(CANNOT_DELETE_LAST_USER_ERROR);
+              return null;
+            }
+          }
+          map.remove(name);
+        }
         return latestConf;
       }
       if ("set-user".equals(cmd.name) ) {
diff --git a/solr/core/src/java/org/apache/solr/util/SolrCLI.java b/solr/core/src/java/org/apache/solr/util/SolrCLI.java
index c6b205b..ca4e461 100755
--- a/solr/core/src/java/org/apache/solr/util/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/util/SolrCLI.java
@@ -4414,7 +4414,7 @@ public class SolrCLI implements CLIO {
             password = new String(console.readPassword("Enter password: "));
           }
 
-          boolean blockUnknown = Boolean.valueOf(cli.getOptionValue("blockUnknown", "false"));
+          boolean blockUnknown = Boolean.valueOf(cli.getOptionValue("blockUnknown", "true"));
 
           String securityJson = "{" +
               "\n  \"authentication\":{" +
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
index e4ebf29..f4c1ec2 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestConfigSetsAPI.java
@@ -383,6 +383,7 @@ public class TestConfigSetsAPI extends SolrTestCaseJ4 {
     String securityJson = "{\n" +
         "  'authentication':{\n" +
         "    'class':'solr.BasicAuthPlugin',\n" +
+        "    'blockUnknown': false,\n" +
         "    'credentials':{'solr':'orwp2Ghgj39lmnrZOTm7Qtre1VqHFDfwAEzr0ApbN3Y= Ju5osoAqOX8iafhWpPP01E5P+sg8tK8tHON7rCYZRRw='}},\n" +
         "  'authorization':{\n" +
         "    'class':'solr.RuleBasedAuthorizationPlugin',\n" +
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/SecurityConfHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/SecurityConfHandlerTest.java
index f11f9e4..76eb658 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/SecurityConfHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/SecurityConfHandlerTest.java
@@ -24,13 +24,13 @@ import java.util.Map;
 
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.CommandOperation;
 import org.apache.solr.common.util.ContentStreamBase;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.security.BasicAuthPlugin;
 import org.apache.solr.security.RuleBasedAuthorizationPlugin;
-import org.apache.solr.common.util.CommandOperation;
 
 import static org.apache.solr.common.util.Utils.makeMap;
 import static org.apache.solr.handler.admin.SecurityConfHandler.SecurityConfig;
@@ -55,10 +55,9 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
       basicAuth.init((Map<String, Object>) securityCfg.getData().get("authentication"));
       assertTrue(basicAuth.authenticate("tom", "TomIsUberCool"));
 
-
       command = "{\n" +
           "'set-user': {'harry':'HarryIsCool'},\n" +
-          "'delete-user': ['tom','harry']\n" +
+          "'delete-user': ['tom']\n" +
           "}";
       o = new ContentStreamBase.ByteArrayStream(command.getBytes(StandardCharsets.UTF_8), "");
       req.setContentStreams(Collections.singletonList(o));
@@ -67,7 +66,7 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
       assertEquals(3, securityCfg.getVersion());
       Map result = (Map) securityCfg.getData().get("authentication");
       result = (Map) result.get("credentials");
-      assertTrue(result.isEmpty());
+      assertEquals(1,result.size());
     }
 
 
@@ -194,7 +193,7 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
       sp.getData().put("authorization", makeMap("class", "solr."+RuleBasedAuthorizationPlugin.class.getSimpleName()));
       m.put("/security.json", sp);
 
-      basicAuthPlugin.init(new HashMap<>());
+      basicAuthPlugin.init(Collections.singletonMap("credentials", Collections.singletonMap("ignore", "me")));
 
       rulesBasedAuthorizationPlugin.init(new HashMap<>());
     }
diff --git a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
index 975c252..96c82ab 100644
--- a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
@@ -356,7 +356,7 @@ public class BasicAuthIntegrationTest extends SolrCloudAuthTestCase {
     // before returning...
     final Set<Map.Entry<String,Object>> initialPlugins
       = getAuthPluginsInUseForCluster(url).entrySet();
-    
+
     HttpPost httpPost;
     HttpResponse r;
     httpPost = new HttpPost(url);
@@ -394,6 +394,7 @@ public class BasicAuthIntegrationTest extends SolrCloudAuthTestCase {
   //this could be generated everytime. But , then we will not know if there is any regression
   protected static final String STD_CONF = "{\n" +
       "  'authentication':{\n" +
+      "    'blockUnknown':'false',\n" +
       "    'class':'solr.BasicAuthPlugin',\n" +
       "    'credentials':{'solr':'orwp2Ghgj39lmnrZOTm7Qtre1VqHFDfwAEzr0ApbN3Y= Ju5osoAqOX8iafhWpPP01E5P+sg8tK8tHON7rCYZRRw='}},\n" +
       "  'authorization':{\n" +
diff --git a/solr/core/src/test/org/apache/solr/security/TestSha256AuthenticationProvider.java b/solr/core/src/test/org/apache/solr/security/TestSha256AuthenticationProvider.java
index 26c3597..c62354b 100644
--- a/solr/core/src/test/org/apache/solr/security/TestSha256AuthenticationProvider.java
+++ b/solr/core/src/test/org/apache/solr/security/TestSha256AuthenticationProvider.java
@@ -17,23 +17,25 @@
 package org.apache.solr.security;
 
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashMap;
+import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.util.CommandOperation;
+import org.junit.Test;
 
 import static java.util.Collections.singletonMap;
 
 public class TestSha256AuthenticationProvider extends SolrTestCaseJ4 {
   public void testAuthenticate(){
     Sha256AuthenticationProvider zkAuthenticationProvider = new Sha256AuthenticationProvider();
-    zkAuthenticationProvider.init(Collections.emptyMap());
+    zkAuthenticationProvider.init(createConfigMap("ignore", "me"));
 
-    String pwd = "My#$Password";
-    String user = "noble";
-    Map latestConf = new LinkedHashMap<>();
+    String pwd = "Friendly";
+    String user = "marcus";
+    Map latestConf = createConfigMap(user, pwd);
     Map<String, Object> params = singletonMap(user, pwd);
     Map<String, Object> result = zkAuthenticationProvider.edit(latestConf,
         Collections.singletonList(new CommandOperation("set-user",params )));
@@ -48,9 +50,9 @@ public class TestSha256AuthenticationProvider extends SolrTestCaseJ4 {
 
   public void testBasicAuthCommands() throws IOException {
     try (BasicAuthPlugin basicAuthPlugin = new BasicAuthPlugin()) {
-      basicAuthPlugin.init(Collections.emptyMap());
+      basicAuthPlugin.init(createConfigMap("ignore", "me"));
 
-      Map latestConf = new LinkedHashMap<>();
+      Map latestConf = createConfigMap("solr", "SolrRocks");
 
       CommandOperation blockUnknown = new CommandOperation("set-property", singletonMap("blockUnknown", true));
       basicAuthPlugin.edit(latestConf, Collections.singletonList(blockUnknown));
@@ -64,4 +66,46 @@ public class TestSha256AuthenticationProvider extends SolrTestCaseJ4 {
       assertFalse(basicAuthPlugin.getBlockUnknown());
     }
   }
+
+  public void testBasicAuthWithCredentials() throws IOException {
+    try (BasicAuthPlugin basicAuthPlugin = new BasicAuthPlugin()) {
+      Map<String, Object> config = createConfigMap("solr", "IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c=");
+      basicAuthPlugin.init(config);
+      assertTrue(basicAuthPlugin.authenticate("solr", "SolrRocks"));
+    }
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testBasicAuthUserNotFound() throws IOException {
+    try (BasicAuthPlugin basicAuthPlugin = new BasicAuthPlugin()) {
+      Map<String, Object> config = createConfigMap(null, null);
+      basicAuthPlugin.init(config);
+    }
+  }
+
+  public void testBasicAuthDeleteFinalUser() throws IOException {
+    try (BasicAuthPlugin basicAuthPlugin = new BasicAuthPlugin()) {
+      Map<String, Object> config = createConfigMap("solr", "IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c=");
+      basicAuthPlugin.init(config);
+      assertTrue(basicAuthPlugin.authenticate("solr", "SolrRocks"));
+
+      CommandOperation deleteUser = new CommandOperation("delete-user", "solr");
+      assertFalse(deleteUser.hasError());
+      basicAuthPlugin.edit(config, Arrays.asList(deleteUser));
+      assertTrue(deleteUser.hasError());
+      assertTrue(deleteUser.getErrors().contains(Sha256AuthenticationProvider.CANNOT_DELETE_LAST_USER_ERROR));
+    }
+  }
+
+  private Map<String, Object> createConfigMap(String user, String pw) {
+    Map<String, Object> config = new HashMap<>();
+    Map<String, String> credentials = new HashMap<>();
+    if (user != null) {
+      credentials.put(user, pw);
+    }
+    config.put("credentials", credentials);
+    return config;
+  }
+
 }
+
diff --git a/solr/solr-ref-guide/src/basic-authentication-plugin.adoc b/solr/solr-ref-guide/src/basic-authentication-plugin.adoc
index 10449ee..18ad2b8 100644
--- a/solr/solr-ref-guide/src/basic-authentication-plugin.adoc
+++ b/solr/solr-ref-guide/src/basic-authentication-plugin.adoc
@@ -60,7 +60,7 @@ There are several things defined in this file:
 
 Save your settings to a file called `security.json` locally. If you are using Solr in standalone mode, you should put this file in `$SOLR_HOME`.
 
-If `blockUnknown` does not appear in the `security.json` file, it will default to `false`. This has the effect of not requiring authentication at all. In some cases, you may want this; for example, if you want to have `security.json` in place but aren't ready to enable authentication. However, you will want to ensure that this parameter is set to `true` in order for authentication to be truly enabled in your system.
+If `blockUnknown` does not appear in the `security.json` file, it will default to `true`. This has the effect of requiring authentication for HTTP access to Solr. In some cases, you may not want authentication after enabling the plugin; for example, if you want to have `security.json` in place but aren't ready to enable authentication. However, you will want to ensure that `blockUnknown` is set to `true` or omitted entirely in order for authentication to be enforced for all requests to y [...]
 
 If `realm` is not defined, it will default to `solr`.
 
diff --git a/solr/solr-ref-guide/src/jwt-authentication-plugin.adoc b/solr/solr-ref-guide/src/jwt-authentication-plugin.adoc
index 4993149..841fe52 100644
--- a/solr/solr-ref-guide/src/jwt-authentication-plugin.adoc
+++ b/solr/solr-ref-guide/src/jwt-authentication-plugin.adoc
@@ -28,12 +28,13 @@ The simplest possible `security.json` for registering the plugin without configu
 ----
 {
   "authentication": {
-    "class":"solr.JWTAuthPlugin"
+    "class":"solr.JWTAuthPlugin",
+    "blockUnknown":"false"
   }
 }
 ----
 
-The plugin will NOT block anonymous traffic in this mode, since the default for `blockUnknown` is false. It is then possible to start configuring the plugin using REST API calls, which is described below.
+The plugin will by default require a valid JWT token for all traffic. If the `blockUnknown` property is set to false as in the above example, it is possible to start configuring the plugin using REST API calls, which is further described below.
 
 == Configuration Parameters
 
@@ -41,7 +42,7 @@ The plugin will NOT block anonymous traffic in this mode, since the default for
 [%header,format=csv,separator=;]
 |===
 Key                  ; Description                                             ; Default
-blockUnknown         ; Set to `true` in order to block requests from users without a token  ; `false`
+blockUnknown         ; Set to `false` to if you need to perform configuration through REST API or if you use an Authorization Plugin and only want certain paths protected. By default all requests will require a token  ; `true`
 wellKnownUrl         ; URL to an https://openid.net/specs/openid-connect-discovery-1_0.html[OpenID Connect Discovery] endpoint ; (no default)
 clientId             ; Client identifier for use with OpenID Connect           ; (no default value) Required to authenticate with Admin UI
 realm                ; Name of the authentication realm to echo back in HTTP 401 responses. Will also be displayed in Admin UI login page ; 'solr-jwt'
@@ -70,7 +71,6 @@ To start enforcing authentication for all users, requiring a valid JWT in the `A
 {
   "authentication": {
     "class": "solr.JWTAuthPlugin",
-    "blockUnknown": true,
     "jwkUrl": "https://my.key.server/jwk.json"
   }
 }
@@ -84,7 +84,6 @@ The next example shows configuring using https://openid.net/specs/openid-connect
 {
   "authentication": {
     "class": "solr.JWTAuthPlugin",
-    "blockUnknown": true,
     "wellKnownUrl": "https://idp.example.com/.well-known/openid-configuration",
     "clientId": "xyz",
     "redirectUri": "https://my.solr.server:8983/solr/"
@@ -123,7 +122,7 @@ Let's look at a more complex configuration, this time with a static embedded JWK
 Let's comment on this config:
 
 <1> Plugin class
-<2> Make sure to block anyone without a valid token
+<2> Make sure to block anyone without a valid token (this is also the default)
 <3> Here we pass the JWK inline instead of referring to a URL with `jwkUrl`
 <4> Set the client id registered with Identity Provider
 <5> The issuer claim must match "https://example.com/idp"
diff --git a/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc b/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
new file mode 100644
index 0000000..d5aeb81
--- /dev/null
+++ b/solr/solr-ref-guide/src/major-changes-in-solr-9.adoc
@@ -0,0 +1,46 @@
+= Major Changes in Solr 9
+:page-tocclass: right
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+Solr 9.0 is a major new release of Solr.
+
+This page highlights the biggest changes, including new features you may want to be aware of, and changes in default behavior and deprecated features that have been removed.
+
+== Solr 9 Upgrade Planning
+
+Before staring an upgrade to Solr 9, please take the time to review all information about changes from the version you are currently on up to Solr 9.
+
+You should also consider all changes that have been made to Solr in any version you have not upgraded to already. For example, if you are currently using Solr 8.1, you should review changes made in all subsequent 8.x releases in addition to changes for 9.0.
+
+A thorough review of the list in Major Changes in Earlier 8.x Versions as well as the {solr-javadocs}/changes/Changes.html[CHANGES.txt] in your Solr instance will help you plan your migration to Solr 9.
+
+=== Upgrade Prerequisites in Solr 9
+
+=== Rolling Upgrades with Solr 9
+
+=== Reindexing After Upgrades in Solr 9
+
+== New Features & Enhancements in Solr 9
+
+== Configuration and Default Parameter Changes in Solr 9
+
+=== Schema Changes in 9
+
+=== Authentication & Security Changes in Solr 9
+
+* BasicAuthPlugin property 'blockUnknown' now defaults to 'true'. This change is backward incompatible. If you need the pre-9.0 default behavior, you need to explicitly set 'blockUnknown':'false' in security.json.
diff --git a/solr/solr-ref-guide/src/solr-control-script-reference.adoc b/solr/solr-ref-guide/src/solr-control-script-reference.adoc
index e71b668..c3c8e26 100644
--- a/solr/solr-ref-guide/src/solr-control-script-reference.adoc
+++ b/solr/solr-ref-guide/src/solr-control-script-reference.adoc
@@ -559,7 +559,7 @@ If prompt is preferred, pass *true* as a parameter to request the script to prom
 Either `-credentials` or `-prompt` *must* be specified.
 
 `-blockUnknown`::
-When *true*, blocks all unauthenticated users from accessing Solr. This defaults to *false*, which means unauthenticated users will still be able to access Solr.
+When *true*, blocks all unauthenticated users from accessing Solr. When *false*,unauthenticated users will still be able to access Solr, but only for operations not explicitly requiring a user role in the Authorization plugin configuration.
 
 `-updateIncludeFileOnly`::
 When *true*, only the settings in `bin/solr.in.sh` or `bin\solr.in.cmd` will be updated, and `security.json` will not be created.
diff --git a/solr/solr-ref-guide/src/solr-upgrade-notes.adoc b/solr/solr-ref-guide/src/solr-upgrade-notes.adoc
index 760fba7..0d1b70a 100644
--- a/solr/solr-ref-guide/src/solr-upgrade-notes.adoc
+++ b/solr/solr-ref-guide/src/solr-upgrade-notes.adoc
@@ -1,5 +1,5 @@
 = Solr Upgrade Notes
-:page-children: major-changes-in-solr-8, major-changes-in-solr-7, major-changes-from-solr-5-to-solr-6
+:page-children: major-changes-in-solr-9, major-changes-in-solr-8, major-changes-in-solr-7, major-changes-from-solr-5-to-solr-6
 :page-toclevels: 3
 :page-tocclass: right
 // Licensed to the Apache Software Foundation (ASF) under one