HoustonPutman commented on a change in pull request #221:
URL: 
https://github.com/apache/lucene-solr-operator/pull/221#discussion_r582129440



##########
File path: api/v1beta1/solrcloud_types.go
##########
@@ -1022,3 +1040,37 @@ type SolrTLSOptions struct {
        // +optional
        RestartOnTLSSecretUpdate bool 
`json:"restartOnTLSSecretUpdate,omitempty"`
 }
+
+// +kubebuilder:validation:Enum=Basic
+type AuthenticationType string
+
+const (
+       Basic AuthenticationType = "Basic"
+)
+
+type SolrSecurityOptions struct {
+       // Indicates the authentication plugin type that is being used by Solr; 
for now only "Basic" is supported by the
+       // Solr operator but support for other authentication plugins may be 
added in the future.
+       AuthenticationType AuthenticationType 
`json:"authenticationType,omitempty"`

Review comment:
       Might as well put a default value of "Basic" here. We can always remove 
it when we add more types.

##########
File path: docs/solr-cloud/solr-cloud-crd.md
##########
@@ -543,3 +543,234 @@ spec:
 ```
 The example settings above will result in your Solr pods getting names like: 
`<ns>-search-solrcloud-0.k8s.solr.cloud` 
 which you can request TLS certificates from LetsEncrypt assuming you own the 
`k8s.solr.cloud` domain.
+
+## Solr Security: Authentication and Authorization
+
+All well-configured Solr clusters should enforce users to authenticate, even 
for read-only operations. Even if you want
+to allow anonymous query requests from unknown users, you should make this 
explicit using Solr's rule-based authorization
+plugin. In other words, always enforce security and then relax constraints as 
needed for specific endpoints based on your
+use case. The Solr operator can bootstrap a default security configuration for 
your SolrCloud during initialization. As such,
+there is no reason to deploy an unsecured SolrCloud cluster when using the 
Solr operator. In most cases, you'll want to combine
+basic authentication with TLS to ensure credentials are never passed in clear 
text.
+
+For background on Solr security, please refer to the [Reference 
Guide](https://lucene.apache.org/solr/guide) for your version of Solr.
+
+Basic authentication is the only authentication scheme supported by the Solr 
operator at this time. In general, you have 
+two basic options for configuring basic authentication with the Solr operator:
+1. Let the Solr operator bootstrap the `security.json` to configure *basic 
authentication* for Solr.
+2. Supply your own `security.json` to Solr, which must define a user account 
that the operator can use to make API requests to secured Solr pods.
+
+If you choose option 2, then you need to provide the credentials to the Solr 
operator using a Kubernetes [Basic Authentication 
Secret](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret).
+With option 1, the operator creates the Basic Authentication Secret for you.
+
+### Option 1: Bootstrap Security
+
+The easiest way to get started with Solr security is to have the operator 
bootstrap a `security.json` (stored in ZK) as part of the initial deployment 
process.
+To activate this feature, add the following configuration to your SolrCloud 
CRD definition YAML:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+```
+
+Once the cluster is up, you'll need the `admin` user password to login to the 
Solr Admin UI.
+The `admin` user will have a random password generated by the operator during 
`security.json` bootstrapping.
+Use the following command to retrieve the password from the bootstrap secret 
created by the operator:
+```
+kubectl get secret <CLOUD>-solrcloud-security-bootstrap -o 
jsonpath='{.data.admin}' | base64 --decode
+```
+_where `<CLOUD>` is the name of your SolrCloud_
+
+Once `security.json` is bootstrapped, the operator will not update it! You're 
expected to use the `admin` user to access the Security API to make further 
changes.
+In addition to the `admin` user, the operator defines a `solr` user, which has 
basic read access to Solr resources. You can retrieve the `solr` user password 
using:
+```
+kubectl get secret <CLOUD>-solrcloud-security-bootstrap -o 
jsonpath='{.data.solr}' | base64 --decode
+```
+
+The operator makes requests to secured Solr endpoints as the `k8s-oper` user; 
credentials for the `k8s-oper` user are stored in a separate secret of type 
`kubernetes.io/basic-auth`
+with name `<CLOUD>-solrcloud-basic-auth`. The `k8s-oper` user is configured 
with read-only access to a minimal set of endpoints, see details in the 
**Authorization** sub-section below.
+Remember, if you change the `k8s-oper` password using the Solr security API, 
then you **must** update the secret with the new password or the operator will 
be locked out.
+Also, changing the password for the `k8s-oper` user in the K8s secret after 
bootstrapping will not update Solr! You're responsible for changing the 
password in both places.
+
+#### Liveness and Readiness Probes

Review comment:
       I think this would fit better as a `###` section after `Option 2: ...`

##########
File path: controllers/solrbackup_controller.go
##########
@@ -19,6 +19,7 @@ package controllers
 
 import (
        "context"
+       corev1 "k8s.io/api/core/v1"
        "reflect"
        "time"
 

Review comment:
       Need to add:
   
   `// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list`
   
   Mostly for best practice. It's already in the rbac permissions, because of 
the cloud operator so it is working currently. But it's nice to have everything 
each controller needs listed independently.

##########
File path: controllers/util/solr_util.go
##########
@@ -1157,3 +1193,240 @@ func createZkConnectionEnvVars(solrCloud 
*solr.SolrCloud, solrCloudStatus *solr.
 
        return envVars, solrOpt, len(zkChroot) > 1
 }
+
+func setupVolumeMountForUserProvidedConfigMapEntry(reconcileConfigInfo 
map[string]string, fileKey string, solrVolumes []corev1.Volume, envVar string) 
(*corev1.VolumeMount, *corev1.EnvVar, *corev1.Volume) {
+       volName := strings.ReplaceAll(fileKey, ".", "-")
+       mountPath := fmt.Sprintf("/var/solr/%s", reconcileConfigInfo[fileKey])
+       appendedToExisting := false
+       if reconcileConfigInfo[fileKey] == reconcileConfigInfo[SolrXmlFile] {
+               // the user provided a custom log4j2.xml and solr.xml, append 
to the volume for solr.xml created above
+               for _, vol := range solrVolumes {
+                       if vol.Name == "solr-xml" {
+                               vol.ConfigMap.Items = 
append(vol.ConfigMap.Items, corev1.KeyToPath{Key: fileKey, Path: fileKey})
+                               appendedToExisting = true
+                               volName = vol.Name
+                               break
+                       }
+               }
+       }
+
+       var vol *corev1.Volume = nil
+       if !appendedToExisting {
+               defaultMode := int32(420)
+               vol = &corev1.Volume{
+                       Name: volName,
+                       VolumeSource: corev1.VolumeSource{
+                               ConfigMap: &corev1.ConfigMapVolumeSource{
+                                       LocalObjectReference: 
corev1.LocalObjectReference{Name: reconcileConfigInfo[fileKey]},
+                                       Items:                
[]corev1.KeyToPath{{Key: fileKey, Path: fileKey}},
+                                       DefaultMode:          &defaultMode,
+                               },
+                       },
+               }
+       }
+       pathToFile := fmt.Sprintf("%s/%s", mountPath, fileKey)
+
+       return &corev1.VolumeMount{Name: volName, MountPath: mountPath}, 
&corev1.EnvVar{Name: envVar, Value: pathToFile}, vol
+}
+
+func BasicAuthHeader(basicAuthSecret *corev1.Secret) string {
+       creds := fmt.Sprintf("%s:%s", 
basicAuthSecret.Data[corev1.BasicAuthUsernameKey], 
basicAuthSecret.Data[corev1.BasicAuthPasswordKey])
+       return "Basic " + b64.StdEncoding.EncodeToString([]byte(creds))
+}
+
+func ValidateBasicAuthSecret(basicAuthSecret *corev1.Secret) error {
+       if basicAuthSecret.Type != corev1.SecretTypeBasicAuth {
+               return fmt.Errorf("invalid secret type %v; user-provided secret 
%s must be of type: %v",
+                       basicAuthSecret.Type, basicAuthSecret.Name, 
corev1.SecretTypeBasicAuth)
+       }
+
+       if _, ok := basicAuthSecret.Data[corev1.BasicAuthUsernameKey]; !ok {

Review comment:
       I think these two are validated by the API server, but not a bad thing 
to have it here as well.

##########
File path: docs/solr-cloud/solr-cloud-crd.md
##########
@@ -543,3 +543,234 @@ spec:
 ```
 The example settings above will result in your Solr pods getting names like: 
`<ns>-search-solrcloud-0.k8s.solr.cloud` 
 which you can request TLS certificates from LetsEncrypt assuming you own the 
`k8s.solr.cloud` domain.
+
+## Solr Security: Authentication and Authorization
+
+All well-configured Solr clusters should enforce users to authenticate, even 
for read-only operations. Even if you want
+to allow anonymous query requests from unknown users, you should make this 
explicit using Solr's rule-based authorization
+plugin. In other words, always enforce security and then relax constraints as 
needed for specific endpoints based on your
+use case. The Solr operator can bootstrap a default security configuration for 
your SolrCloud during initialization. As such,
+there is no reason to deploy an unsecured SolrCloud cluster when using the 
Solr operator. In most cases, you'll want to combine
+basic authentication with TLS to ensure credentials are never passed in clear 
text.
+
+For background on Solr security, please refer to the [Reference 
Guide](https://lucene.apache.org/solr/guide) for your version of Solr.
+
+Basic authentication is the only authentication scheme supported by the Solr 
operator at this time. In general, you have 
+two basic options for configuring basic authentication with the Solr operator:
+1. Let the Solr operator bootstrap the `security.json` to configure *basic 
authentication* for Solr.
+2. Supply your own `security.json` to Solr, which must define a user account 
that the operator can use to make API requests to secured Solr pods.
+
+If you choose option 2, then you need to provide the credentials to the Solr 
operator using a Kubernetes [Basic Authentication 
Secret](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret).
+With option 1, the operator creates the Basic Authentication Secret for you.
+
+### Option 1: Bootstrap Security
+
+The easiest way to get started with Solr security is to have the operator 
bootstrap a `security.json` (stored in ZK) as part of the initial deployment 
process.
+To activate this feature, add the following configuration to your SolrCloud 
CRD definition YAML:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+```
+
+Once the cluster is up, you'll need the `admin` user password to login to the 
Solr Admin UI.
+The `admin` user will have a random password generated by the operator during 
`security.json` bootstrapping.
+Use the following command to retrieve the password from the bootstrap secret 
created by the operator:
+```
+kubectl get secret <CLOUD>-solrcloud-security-bootstrap -o 
jsonpath='{.data.admin}' | base64 --decode
+```
+_where `<CLOUD>` is the name of your SolrCloud_
+
+Once `security.json` is bootstrapped, the operator will not update it! You're 
expected to use the `admin` user to access the Security API to make further 
changes.
+In addition to the `admin` user, the operator defines a `solr` user, which has 
basic read access to Solr resources. You can retrieve the `solr` user password 
using:
+```
+kubectl get secret <CLOUD>-solrcloud-security-bootstrap -o 
jsonpath='{.data.solr}' | base64 --decode
+```
+
+The operator makes requests to secured Solr endpoints as the `k8s-oper` user; 
credentials for the `k8s-oper` user are stored in a separate secret of type 
`kubernetes.io/basic-auth`
+with name `<CLOUD>-solrcloud-basic-auth`. The `k8s-oper` user is configured 
with read-only access to a minimal set of endpoints, see details in the 
**Authorization** sub-section below.
+Remember, if you change the `k8s-oper` password using the Solr security API, 
then you **must** update the secret with the new password or the operator will 
be locked out.
+Also, changing the password for the `k8s-oper` user in the K8s secret after 
bootstrapping will not update Solr! You're responsible for changing the 
password in both places.
+
+#### Liveness and Readiness Probes
+
+We recommend configuring Solr to allow un-authenticated access over HTTP to 
the probe endpoint(s) and the bootstrapped `security.json` does this for you 
automatically (see next sub-section). 
+However, if you want to secure the probe endpoints, then you need to set 
`probesRequireAuth: true` as shown below:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+    probesRequireAuth: true
+```
+When `probesRequireAuth` is set to `true`, the liveness and readiness probes 
execute a command instead of using HTTP. 
+The operator configures a command instead of setting the `Authorization` 
header for the HTTP probes, as that would require a restart of all pods if the 
password changes. 
+With a command, we can load the username and password from a secret; 
Kubernetes will 
+[update the mounted secret 
files](https://kubernetes.io/docs/concepts/configuration/secret/#mounted-secrets-are-updated-automatically)
 when the secret changes automatically.
+
+#### Authorization
+
+The default `security.json` created by the operator during initialization is 
shown below; the passwords for each user are randomized for every SolrCloud you 
create.
+In addition to configuring the `solr.BasicAuthPlugin`, the operator 
initializes a set of authorization rules for the default user accounts: 
`admin`, `solr`, and `k8s-oper`.
+Take a moment to review these authorization rules so that you're aware of the 
roles and access granted to each user in your cluster.
+```
+{
+  "authentication": {
+    "blockUnknown": false,
+    "class": "solr.BasicAuthPlugin",
+    "credentials": {
+      "admin": "...",
+      "k8s-oper": "...",
+      "solr": "..."
+    },
+    "realm": "Solr Basic Auth",
+    "forwardCredentials": false
+  },
+  "authorization": {
+    "class": "solr.RuleBasedAuthorizationPlugin",
+    "user-role": {
+      "admin": [ "admin", "k8s" ],
+      "k8s-oper": [ "k8s" ],
+      "solr": [ "users", "k8s" ]
+    },
+    "permissions": [
+      {
+        "name": "k8s-probe-0",
+        "role": null,
+        "collection": null,
+        "path": "/admin/info/system"
+      },
+      {
+        "name": "k8s-status",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/collections"
+      },
+      {
+        "name": "k8s-metrics",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/metrics"
+      },
+      {
+        "name": "k8s-ping",
+        "role": "k8s",
+        "collection": "*",
+        "path": "/admin/ping"
+      },
+      {
+        "name": "all",
+        "role": [ "admin", "users" ]
+      },
+      {
+        "name": "read",
+        "role": [ "admin", "users" ]
+      },
+      {
+        "name": "update",
+        "role": [ "admin" ]
+      },
+      {
+        "name": "security-read",
+        "role": "admin"
+      },
+      {
+        "name": "security-edit",
+        "role": "admin"
+      }
+    ]
+  }
+}
+```
+A few aspects of the default `security.json` configuration warrant a closer 
look. First, the `probesRequireAuth` setting 
+(defaults to `false`) governs the value for `blockUnknown` (under 
`authentication`) and whether the probe endpoint(s) require authentication:
+```
+      {
+        "name": "k8s-probe-0",
+        "role": null,
+        "collection": null,
+        "path": "/admin/info/system"
+      },
+``` 
+In this case, the `"role":null` indicates this endpoint allows anonymous 
access by unknown users. 
+The `"collection":null` value indicates the path is not associated with any 
collection, i.e. it is a top-level system path.
+
+The operator sends GET requests to the `/admin/collections` endpoint to get 
cluster status to determine the rolling restart order:
+```
+      {
+        "name": "k8s-status",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/collections"
+      },
+``` 
+In this case, the `"role":"k8s"` indicates the requesting user must be in the 
`k8s` role; notice that all default users have the `k8s` role.
+
+The Prometheus exporter sends GET requests to the `/admin/metrics` endpoint to 
collect metrics from each pod. 
+The exporter also hits the `/admin/ping` endpoint for every collection, which 
requires the following authorization rules:
+```
+      {
+        "name": "k8s-metrics",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/metrics"
+      },
+      {
+        "name": "k8s-ping",
+        "role": "k8s",
+        "collection": "*",
+        "path": "/admin/ping"
+      },
+```
+The `"collection":"*"` setting indicates this path applies to all collections, 
which maps to endpoint `/collections/<COLL>/admin/ping` at runtime.
+
+### Option 2: User-provided Basic Auth Secret
+
+Alternatively, if users want full control over their cluster's security 
config, then they can provide a `kubernetes.io/basic-auth` secret containing 
the credentials for the user they want the operator to make API requests as:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+    basicAuthSecret: user-provided-secret
+```
+The supplied secret must be of type [Basic Authentication 
Secret](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret)
 and define both a `username` and `password`.
+Here is an example of how to define a basic auth secret using YAML:
+```
+apiVersion: v1
+kind: Secret
+metadata:
+  name: my-basic-auth-secret
+type: kubernetes.io/basic-auth
+stringData:
+  username: k8s-oper
+  password: Test1234
+```
+With this config, the operator will make API requests to secured Solr pods as 
the `k8s-oper` user. 
+_Note: be sure to use a stronger password for real deployments_
+
+If users supply their own basic auth secret, then the operator *does not* 
bootstrap the `security.json`; 
+the reasoning is that if you're supplying your own basic auth credentials then 
you're also assuming the responsibility for configuring the desired access for 
this user.
+
+Users need to ensure their `security.json` contains the user supplied in the 
`basicAuthSecret` with read access to:
+```
+/admin/info/system
+/admin/collections
+/admin/metrics
+/admin/ping (for collection="*")
+```
+_Tip: see the authorization rules defined by the default `security.json` as a 
guide for configuring access for the operator user_
+
+### Prometheus Exporter with Basic Auth

Review comment:
       Can you add this to the Prometheus Exporter documentation? (And then 
link to that page & section here)

##########
File path: docs/solr-cloud/solr-cloud-crd.md
##########
@@ -543,3 +543,234 @@ spec:
 ```
 The example settings above will result in your Solr pods getting names like: 
`<ns>-search-solrcloud-0.k8s.solr.cloud` 
 which you can request TLS certificates from LetsEncrypt assuming you own the 
`k8s.solr.cloud` domain.
+
+## Solr Security: Authentication and Authorization
+
+All well-configured Solr clusters should enforce users to authenticate, even 
for read-only operations. Even if you want
+to allow anonymous query requests from unknown users, you should make this 
explicit using Solr's rule-based authorization
+plugin. In other words, always enforce security and then relax constraints as 
needed for specific endpoints based on your
+use case. The Solr operator can bootstrap a default security configuration for 
your SolrCloud during initialization. As such,
+there is no reason to deploy an unsecured SolrCloud cluster when using the 
Solr operator. In most cases, you'll want to combine
+basic authentication with TLS to ensure credentials are never passed in clear 
text.
+
+For background on Solr security, please refer to the [Reference 
Guide](https://lucene.apache.org/solr/guide) for your version of Solr.
+
+Basic authentication is the only authentication scheme supported by the Solr 
operator at this time. In general, you have 
+two basic options for configuring basic authentication with the Solr operator:
+1. Let the Solr operator bootstrap the `security.json` to configure *basic 
authentication* for Solr.
+2. Supply your own `security.json` to Solr, which must define a user account 
that the operator can use to make API requests to secured Solr pods.
+
+If you choose option 2, then you need to provide the credentials to the Solr 
operator using a Kubernetes [Basic Authentication 
Secret](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret).
+With option 1, the operator creates the Basic Authentication Secret for you.
+
+### Option 1: Bootstrap Security
+
+The easiest way to get started with Solr security is to have the operator 
bootstrap a `security.json` (stored in ZK) as part of the initial deployment 
process.
+To activate this feature, add the following configuration to your SolrCloud 
CRD definition YAML:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+```
+
+Once the cluster is up, you'll need the `admin` user password to login to the 
Solr Admin UI.
+The `admin` user will have a random password generated by the operator during 
`security.json` bootstrapping.
+Use the following command to retrieve the password from the bootstrap secret 
created by the operator:
+```
+kubectl get secret <CLOUD>-solrcloud-security-bootstrap -o 
jsonpath='{.data.admin}' | base64 --decode
+```
+_where `<CLOUD>` is the name of your SolrCloud_
+
+Once `security.json` is bootstrapped, the operator will not update it! You're 
expected to use the `admin` user to access the Security API to make further 
changes.
+In addition to the `admin` user, the operator defines a `solr` user, which has 
basic read access to Solr resources. You can retrieve the `solr` user password 
using:
+```
+kubectl get secret <CLOUD>-solrcloud-security-bootstrap -o 
jsonpath='{.data.solr}' | base64 --decode
+```
+
+The operator makes requests to secured Solr endpoints as the `k8s-oper` user; 
credentials for the `k8s-oper` user are stored in a separate secret of type 
`kubernetes.io/basic-auth`
+with name `<CLOUD>-solrcloud-basic-auth`. The `k8s-oper` user is configured 
with read-only access to a minimal set of endpoints, see details in the 
**Authorization** sub-section below.
+Remember, if you change the `k8s-oper` password using the Solr security API, 
then you **must** update the secret with the new password or the operator will 
be locked out.
+Also, changing the password for the `k8s-oper` user in the K8s secret after 
bootstrapping will not update Solr! You're responsible for changing the 
password in both places.
+
+#### Liveness and Readiness Probes
+
+We recommend configuring Solr to allow un-authenticated access over HTTP to 
the probe endpoint(s) and the bootstrapped `security.json` does this for you 
automatically (see next sub-section). 
+However, if you want to secure the probe endpoints, then you need to set 
`probesRequireAuth: true` as shown below:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+    probesRequireAuth: true
+```
+When `probesRequireAuth` is set to `true`, the liveness and readiness probes 
execute a command instead of using HTTP. 
+The operator configures a command instead of setting the `Authorization` 
header for the HTTP probes, as that would require a restart of all pods if the 
password changes. 
+With a command, we can load the username and password from a secret; 
Kubernetes will 
+[update the mounted secret 
files](https://kubernetes.io/docs/concepts/configuration/secret/#mounted-secrets-are-updated-automatically)
 when the secret changes automatically.
+
+#### Authorization
+
+The default `security.json` created by the operator during initialization is 
shown below; the passwords for each user are randomized for every SolrCloud you 
create.
+In addition to configuring the `solr.BasicAuthPlugin`, the operator 
initializes a set of authorization rules for the default user accounts: 
`admin`, `solr`, and `k8s-oper`.
+Take a moment to review these authorization rules so that you're aware of the 
roles and access granted to each user in your cluster.
+```
+{
+  "authentication": {
+    "blockUnknown": false,
+    "class": "solr.BasicAuthPlugin",
+    "credentials": {
+      "admin": "...",
+      "k8s-oper": "...",
+      "solr": "..."
+    },
+    "realm": "Solr Basic Auth",
+    "forwardCredentials": false
+  },
+  "authorization": {
+    "class": "solr.RuleBasedAuthorizationPlugin",
+    "user-role": {
+      "admin": [ "admin", "k8s" ],
+      "k8s-oper": [ "k8s" ],
+      "solr": [ "users", "k8s" ]
+    },
+    "permissions": [
+      {
+        "name": "k8s-probe-0",
+        "role": null,
+        "collection": null,
+        "path": "/admin/info/system"
+      },
+      {
+        "name": "k8s-status",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/collections"
+      },
+      {
+        "name": "k8s-metrics",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/metrics"
+      },
+      {
+        "name": "k8s-ping",
+        "role": "k8s",
+        "collection": "*",
+        "path": "/admin/ping"
+      },
+      {
+        "name": "all",
+        "role": [ "admin", "users" ]
+      },
+      {
+        "name": "read",
+        "role": [ "admin", "users" ]
+      },
+      {
+        "name": "update",
+        "role": [ "admin" ]
+      },
+      {
+        "name": "security-read",
+        "role": "admin"
+      },
+      {
+        "name": "security-edit",
+        "role": "admin"
+      }
+    ]
+  }
+}
+```
+A few aspects of the default `security.json` configuration warrant a closer 
look. First, the `probesRequireAuth` setting 
+(defaults to `false`) governs the value for `blockUnknown` (under 
`authentication`) and whether the probe endpoint(s) require authentication:
+```
+      {
+        "name": "k8s-probe-0",
+        "role": null,
+        "collection": null,
+        "path": "/admin/info/system"

Review comment:
       We probably want to add `/admin/info/health` for the future as well. 
(this would affect multiple places in the PR)

##########
File path: docs/solr-cloud/solr-cloud-crd.md
##########
@@ -543,3 +543,234 @@ spec:
 ```
 The example settings above will result in your Solr pods getting names like: 
`<ns>-search-solrcloud-0.k8s.solr.cloud` 
 which you can request TLS certificates from LetsEncrypt assuming you own the 
`k8s.solr.cloud` domain.
+
+## Solr Security: Authentication and Authorization
+
+All well-configured Solr clusters should enforce users to authenticate, even 
for read-only operations. Even if you want
+to allow anonymous query requests from unknown users, you should make this 
explicit using Solr's rule-based authorization
+plugin. In other words, always enforce security and then relax constraints as 
needed for specific endpoints based on your
+use case. The Solr operator can bootstrap a default security configuration for 
your SolrCloud during initialization. As such,
+there is no reason to deploy an unsecured SolrCloud cluster when using the 
Solr operator. In most cases, you'll want to combine
+basic authentication with TLS to ensure credentials are never passed in clear 
text.
+
+For background on Solr security, please refer to the [Reference 
Guide](https://lucene.apache.org/solr/guide) for your version of Solr.
+
+Basic authentication is the only authentication scheme supported by the Solr 
operator at this time. In general, you have 
+two basic options for configuring basic authentication with the Solr operator:
+1. Let the Solr operator bootstrap the `security.json` to configure *basic 
authentication* for Solr.
+2. Supply your own `security.json` to Solr, which must define a user account 
that the operator can use to make API requests to secured Solr pods.
+
+If you choose option 2, then you need to provide the credentials to the Solr 
operator using a Kubernetes [Basic Authentication 
Secret](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret).
+With option 1, the operator creates the Basic Authentication Secret for you.
+
+### Option 1: Bootstrap Security
+
+The easiest way to get started with Solr security is to have the operator 
bootstrap a `security.json` (stored in ZK) as part of the initial deployment 
process.
+To activate this feature, add the following configuration to your SolrCloud 
CRD definition YAML:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+```
+
+Once the cluster is up, you'll need the `admin` user password to login to the 
Solr Admin UI.
+The `admin` user will have a random password generated by the operator during 
`security.json` bootstrapping.
+Use the following command to retrieve the password from the bootstrap secret 
created by the operator:
+```
+kubectl get secret <CLOUD>-solrcloud-security-bootstrap -o 
jsonpath='{.data.admin}' | base64 --decode
+```
+_where `<CLOUD>` is the name of your SolrCloud_
+
+Once `security.json` is bootstrapped, the operator will not update it! You're 
expected to use the `admin` user to access the Security API to make further 
changes.
+In addition to the `admin` user, the operator defines a `solr` user, which has 
basic read access to Solr resources. You can retrieve the `solr` user password 
using:
+```
+kubectl get secret <CLOUD>-solrcloud-security-bootstrap -o 
jsonpath='{.data.solr}' | base64 --decode
+```
+
+The operator makes requests to secured Solr endpoints as the `k8s-oper` user; 
credentials for the `k8s-oper` user are stored in a separate secret of type 
`kubernetes.io/basic-auth`
+with name `<CLOUD>-solrcloud-basic-auth`. The `k8s-oper` user is configured 
with read-only access to a minimal set of endpoints, see details in the 
**Authorization** sub-section below.
+Remember, if you change the `k8s-oper` password using the Solr security API, 
then you **must** update the secret with the new password or the operator will 
be locked out.
+Also, changing the password for the `k8s-oper` user in the K8s secret after 
bootstrapping will not update Solr! You're responsible for changing the 
password in both places.
+
+#### Liveness and Readiness Probes
+
+We recommend configuring Solr to allow un-authenticated access over HTTP to 
the probe endpoint(s) and the bootstrapped `security.json` does this for you 
automatically (see next sub-section). 
+However, if you want to secure the probe endpoints, then you need to set 
`probesRequireAuth: true` as shown below:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+    probesRequireAuth: true
+```
+When `probesRequireAuth` is set to `true`, the liveness and readiness probes 
execute a command instead of using HTTP. 
+The operator configures a command instead of setting the `Authorization` 
header for the HTTP probes, as that would require a restart of all pods if the 
password changes. 
+With a command, we can load the username and password from a secret; 
Kubernetes will 
+[update the mounted secret 
files](https://kubernetes.io/docs/concepts/configuration/secret/#mounted-secrets-are-updated-automatically)
 when the secret changes automatically.
+
+#### Authorization
+
+The default `security.json` created by the operator during initialization is 
shown below; the passwords for each user are randomized for every SolrCloud you 
create.
+In addition to configuring the `solr.BasicAuthPlugin`, the operator 
initializes a set of authorization rules for the default user accounts: 
`admin`, `solr`, and `k8s-oper`.
+Take a moment to review these authorization rules so that you're aware of the 
roles and access granted to each user in your cluster.
+```
+{
+  "authentication": {
+    "blockUnknown": false,
+    "class": "solr.BasicAuthPlugin",
+    "credentials": {
+      "admin": "...",
+      "k8s-oper": "...",
+      "solr": "..."
+    },
+    "realm": "Solr Basic Auth",
+    "forwardCredentials": false
+  },
+  "authorization": {
+    "class": "solr.RuleBasedAuthorizationPlugin",
+    "user-role": {
+      "admin": [ "admin", "k8s" ],
+      "k8s-oper": [ "k8s" ],
+      "solr": [ "users", "k8s" ]
+    },
+    "permissions": [
+      {
+        "name": "k8s-probe-0",
+        "role": null,
+        "collection": null,
+        "path": "/admin/info/system"
+      },
+      {
+        "name": "k8s-status",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/collections"
+      },
+      {
+        "name": "k8s-metrics",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/metrics"
+      },
+      {
+        "name": "k8s-ping",
+        "role": "k8s",
+        "collection": "*",
+        "path": "/admin/ping"
+      },
+      {
+        "name": "all",
+        "role": [ "admin", "users" ]
+      },
+      {
+        "name": "read",
+        "role": [ "admin", "users" ]
+      },
+      {
+        "name": "update",
+        "role": [ "admin" ]
+      },
+      {
+        "name": "security-read",
+        "role": "admin"
+      },
+      {
+        "name": "security-edit",
+        "role": "admin"
+      }
+    ]
+  }
+}
+```
+A few aspects of the default `security.json` configuration warrant a closer 
look. First, the `probesRequireAuth` setting 
+(defaults to `false`) governs the value for `blockUnknown` (under 
`authentication`) and whether the probe endpoint(s) require authentication:
+```
+      {
+        "name": "k8s-probe-0",
+        "role": null,
+        "collection": null,
+        "path": "/admin/info/system"
+      },
+``` 
+In this case, the `"role":null` indicates this endpoint allows anonymous 
access by unknown users. 
+The `"collection":null` value indicates the path is not associated with any 
collection, i.e. it is a top-level system path.
+
+The operator sends GET requests to the `/admin/collections` endpoint to get 
cluster status to determine the rolling restart order:
+```
+      {
+        "name": "k8s-status",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/collections"
+      },
+``` 
+In this case, the `"role":"k8s"` indicates the requesting user must be in the 
`k8s` role; notice that all default users have the `k8s` role.
+
+The Prometheus exporter sends GET requests to the `/admin/metrics` endpoint to 
collect metrics from each pod. 
+The exporter also hits the `/admin/ping` endpoint for every collection, which 
requires the following authorization rules:
+```
+      {
+        "name": "k8s-metrics",
+        "role": "k8s",
+        "collection": null,
+        "path": "/admin/metrics"
+      },
+      {
+        "name": "k8s-ping",
+        "role": "k8s",
+        "collection": "*",
+        "path": "/admin/ping"
+      },
+```
+The `"collection":"*"` setting indicates this path applies to all collections, 
which maps to endpoint `/collections/<COLL>/admin/ping` at runtime.
+
+### Option 2: User-provided Basic Auth Secret
+
+Alternatively, if users want full control over their cluster's security 
config, then they can provide a `kubernetes.io/basic-auth` secret containing 
the credentials for the user they want the operator to make API requests as:
+```
+spec:
+  ...
+  solrSecurity:
+    authenticationType: Basic
+    basicAuthSecret: user-provided-secret
+```
+The supplied secret must be of type [Basic Authentication 
Secret](https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret)
 and define both a `username` and `password`.
+Here is an example of how to define a basic auth secret using YAML:
+```
+apiVersion: v1
+kind: Secret
+metadata:
+  name: my-basic-auth-secret
+type: kubernetes.io/basic-auth
+stringData:
+  username: k8s-oper
+  password: Test1234
+```
+With this config, the operator will make API requests to secured Solr pods as 
the `k8s-oper` user. 
+_Note: be sure to use a stronger password for real deployments_
+
+If users supply their own basic auth secret, then the operator *does not* 
bootstrap the `security.json`; 
+the reasoning is that if you're supplying your own basic auth credentials then 
you're also assuming the responsibility for configuring the desired access for 
this user.
+
+Users need to ensure their `security.json` contains the user supplied in the 
`basicAuthSecret` with read access to:
+```
+/admin/info/system
+/admin/collections
+/admin/metrics
+/admin/ping (for collection="*")
+```
+_Tip: see the authorization rules defined by the default `security.json` as a 
guide for configuring access for the operator user_
+
+### Prometheus Exporter with Basic Auth
+
+If you enable basic auth for your SolrCloud cluster, then you need to point 
the Prometheus exporter at the basic auth secret containing the credentials for 
making API requests to `/admin/metrics` and `/admin/ping` for all collections.
+
+```
+spec:
+  solrReference:
+    basicAuthSecret: user-provided-secret
+```

Review comment:
       I would re-iterate here the endpoints needed for the prometheus exporter 
auth user, when moving it to the prometheus exporter docs.

##########
File path: controllers/solrcloud_controller.go
##########
@@ -256,6 +259,74 @@ func (r *SolrCloudReconciler) Reconcile(req ctrl.Request) 
(ctrl.Result, error) {
                }
        }
 
+       basicAuthHeader := ""
+       if instance.Spec.SolrSecurity != nil {
+               sec := instance.Spec.SolrSecurity
+
+               if sec.AuthenticationType != solr.Basic {
+                       return requeueOrNot, fmt.Errorf("%s not supported! Only 
'Basic' authentication is supported by the Solr operator.",
+                               instance.Spec.SolrSecurity.AuthenticationType)
+               }
+
+               ctx := context.TODO()
+               basicAuthSecret := &corev1.Secret{}
+
+               // user has the option of providing a secret with credentials 
the operator should use to make requests to Solr
+               if sec.BasicAuthSecret != "" {
+                       if err := r.Get(ctx, types.NamespacedName{Name: 
sec.BasicAuthSecret, Namespace: instance.Namespace}, basicAuthSecret); err != 
nil {
+                               return requeueOrNot, err
+                       }
+
+                       err = util.ValidateBasicAuthSecret(basicAuthSecret)
+                       if err != nil {
+                               return requeueOrNot, err
+                       }
+
+               } else {
+                       // We're supplying a secret with random passwords and a 
default security.json
+                       // since we randomly generate the passwords, we need to 
lookup the secret first and only create if not exist
+                       err = r.Get(ctx, types.NamespacedName{Name: 
instance.BasicAuthSecretName(), Namespace: instance.Namespace}, basicAuthSecret)
+                       if err != nil && errors.IsNotFound(err) {
+                               authSecret, bootstrapSecret := 
util.GenerateBasicAuthSecretWithBootstrap(instance)
+                               if err := 
controllerutil.SetControllerReference(instance, authSecret, r.scheme); err != 
nil {
+                                       return requeueOrNot, err
+                               }
+                               if err := 
controllerutil.SetControllerReference(instance, bootstrapSecret, r.scheme); err 
!= nil {
+                                       return requeueOrNot, err
+                               }
+                               err = r.Create(ctx, authSecret)
+                               if err != nil {
+                                       return requeueOrNot, err
+                               }
+                               err = r.Create(ctx, bootstrapSecret)
+                               if err == nil {
+                                       // supply the bootstrap security.json 
to the initContainer via a simple BASE64 encoding env var
+                                       
reconcileConfigInfo[util.SecurityJsonFile] = 
string(bootstrapSecret.Data[util.SecurityJsonFile])
+                               }
+
+                               basicAuthSecret = authSecret
+                       }
+                       if err != nil {
+                               return requeueOrNot, err
+                       }
+
+                       if reconcileConfigInfo[util.SecurityJsonFile] == "" {

Review comment:
       So if the user wants to delete the bootstrap secret in the future, this 
will start failing right?
   
   I think this code is good, except don't throw the error if the error is a 
NotFound error. That way the user can delete the bootstrap secret, and the pods 
will just stop using the bootstrap afterwards.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org
For additional commands, e-mail: issues-h...@lucene.apache.org

Reply via email to