Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1a7bb57

Browse files
committedJan 15, 2025
feat(update-security-json): Update security.json when secret is updated
1 parent 1fbc6aa commit 1a7bb57

13 files changed

+410
-90
lines changed
 

‎api/v1beta1/solrcloud_types.go

+17-7
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ package v1beta1
1919

2020
import (
2121
"fmt"
22-
"github.com/go-logr/logr"
23-
zkApi "github.com/pravega/zookeeper-operator/api/v1beta1"
2422
"math/rand"
2523
"strconv"
2624
"strings"
2725

26+
"github.com/go-logr/logr"
27+
zkApi "github.com/pravega/zookeeper-operator/api/v1beta1"
28+
2829
"k8s.io/apimachinery/pkg/util/intstr"
2930

3031
corev1 "k8s.io/api/core/v1"
@@ -681,7 +682,8 @@ type ManagedUpdateOptions struct {
681682
// The maximum number of pods that can be unavailable during the update.
682683
// Value can be an absolute number (ex: 5) or a percentage of the desired number of pods (ex: 10%).
683684
// Absolute number is calculated from percentage by rounding down.
684-
// If the provided number is 0 or negative, then all pods will be allowed to be updated in unison.
685+
// If the provided number is 0, then all pods will be allowed to be updated in unison.
686+
// Negatives are not allowed.
685687
//
686688
// Defaults to 25%.
687689
//
@@ -691,7 +693,8 @@ type ManagedUpdateOptions struct {
691693
// The maximum number of replicas for each shard that can be unavailable during the update.
692694
// Value can be an absolute number (ex: 5) or a percentage of replicas in a shard (ex: 25%).
693695
// Absolute number is calculated from percentage by rounding down.
694-
// If the provided number is 0 or negative, then all replicas will be allowed to be updated in unison.
696+
// If the provided number is 0 , then all replicas will be allowed to be updated in unison.
697+
// Negatives are not allowed.
695698
//
696699
// Defaults to 1.
697700
//
@@ -1621,6 +1624,15 @@ const (
16211624
Basic AuthenticationType = "Basic"
16221625
)
16231626

1627+
type BootstrapSecurityJson struct {
1628+
SecurityJsonSecret *corev1.SecretKeySelector `json:"bootstrapSecurityJson,omitempty"`
1629+
1630+
// Flag to indicate if the operator should overwrite an existing security.json if there are changes
1631+
// as compared to the underlying secret
1632+
// +optional
1633+
Overwrite bool `json:"probesRequireAuth,omitempty"`
1634+
}
1635+
16241636
type SolrSecurityOptions struct {
16251637
// Indicates the authentication plugin type that is being used by Solr; for now only "Basic" is supported by the
16261638
// Solr operator but support for other authentication plugins may be added in the future.
@@ -1649,7 +1661,5 @@ type SolrSecurityOptions struct {
16491661

16501662
// Configure a user-provided security.json from a secret to allow for advanced security config.
16511663
// If not specified, the operator bootstraps a security.json with basic auth enabled.
1652-
// This is a bootstrapping config only; once Solr is initialized, the security config should be managed by the security API.
1653-
// +optional
1654-
BootstrapSecurityJson *corev1.SecretKeySelector `json:"bootstrapSecurityJson,omitempty"`
1664+
BootstrapSecurityJson *BootstrapSecurityJson `json:"bootstrapSecurityJson,omitempty"`
16551665
}

‎api/v1beta1/zz_generated.deepcopy.go

+21-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎config/crd/bases/solr.apache.org_solrclouds.yaml

+28-21
Original file line numberDiff line numberDiff line change
@@ -10205,29 +10205,34 @@ spec:
1020510205
description: |-
1020610206
Configure a user-provided security.json from a secret to allow for advanced security config.
1020710207
If not specified, the operator bootstraps a security.json with basic auth enabled.
10208-
This is a bootstrapping config only; once Solr is initialized, the security config should be managed by the security API.
1020910208
properties:
10210-
key:
10211-
description: The key of the secret to select from. Must be
10212-
a valid secret key.
10213-
type: string
10214-
name:
10215-
default: ""
10209+
bootstrapSecurityJson:
10210+
description: SecretKeySelector selects a key of a Secret.
10211+
properties:
10212+
key:
10213+
description: The key of the secret to select from. Must
10214+
be a valid secret key.
10215+
type: string
10216+
name:
10217+
description: |-
10218+
Name of the referent.
10219+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
10220+
TODO: Add other useful fields. apiVersion, kind, uid?
10221+
type: string
10222+
optional:
10223+
description: Specify whether the Secret or its key must
10224+
be defined
10225+
type: boolean
10226+
required:
10227+
- key
10228+
type: object
10229+
x-kubernetes-map-type: atomic
10230+
overwrite:
1021610231
description: |-
10217-
Name of the referent.
10218-
This field is effectively required, but due to backwards compatibility is
10219-
allowed to be empty. Instances of this type with an empty value here are
10220-
almost certainly wrong.
10221-
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
10222-
type: string
10223-
optional:
10224-
description: Specify whether the Secret or its key must be
10225-
defined
10232+
Flag to indicate if the operator should overwrite an existing security.json if there are changes
10233+
as compared to the underlying secret
1022610234
type: boolean
10227-
required:
10228-
- key
1022910235
type: object
10230-
x-kubernetes-map-type: atomic
1023110236
probesRequireAuth:
1023210237
description: |-
1023310238
Flag to indicate if the configured HTTP endpoint(s) used for the probes require authentication; defaults
@@ -10428,7 +10433,8 @@ spec:
1042810433
The maximum number of pods that can be unavailable during the update.
1042910434
Value can be an absolute number (ex: 5) or a percentage of the desired number of pods (ex: 10%).
1043010435
Absolute number is calculated from percentage by rounding down.
10431-
If the provided number is 0 or negative, then all pods will be allowed to be updated in unison.
10436+
If the provided number is 0, then all pods will be allowed to be updated in unison.
10437+
Negatives are not allowed.
1043210438

1043310439
Defaults to 25%.
1043410440
x-kubernetes-int-or-string: true
@@ -10440,7 +10446,8 @@ spec:
1044010446
The maximum number of replicas for each shard that can be unavailable during the update.
1044110447
Value can be an absolute number (ex: 5) or a percentage of replicas in a shard (ex: 25%).
1044210448
Absolute number is calculated from percentage by rounding down.
10443-
If the provided number is 0 or negative, then all replicas will be allowed to be updated in unison.
10449+
If the provided number is 0 , then all replicas will be allowed to be updated in unison.
10450+
Negatives are not allowed.
1044410451

1044510452
Defaults to 1.
1044610453
x-kubernetes-int-or-string: true

‎controllers/solrcloud_controller_basic_auth_test.go

+19-9
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,12 @@ var _ = FDescribe("SolrCloud controller - Basic Auth", func() {
177177
solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{
178178
AuthenticationType: solrv1beta1.Basic,
179179
BasicAuthSecret: basicAuthSecretName,
180-
BootstrapSecurityJson: &corev1.SecretKeySelector{
181-
LocalObjectReference: corev1.LocalObjectReference{Name: "my-security-json"},
182-
Key: util.SecurityJsonFile,
180+
BootstrapSecurityJson: &solrv1beta1.BootstrapSecurityJson{
181+
SecurityJsonSecret: &corev1.SecretKeySelector{
182+
LocalObjectReference: corev1.LocalObjectReference{Name: "my-security-json"},
183+
Key: util.SecurityJsonFile,
184+
},
185+
Overwrite: false,
183186
},
184187
}
185188
})
@@ -324,10 +327,11 @@ func expectBasicAuthConfigOnPodTemplateWithGomega(g Gomega, solrCloud *solrv1bet
324327
// if the zookeeperRef has ACLs set, verify the env vars were set correctly for this initContainer
325328
allACL, _ := solrCloud.Spec.ZookeeperRef.GetACLs()
326329
if allACL != nil {
327-
g.Expect(expInitContainer.Env).To(HaveLen(10), "Wrong number of env vars using ACLs and Basic Auth")
328-
g.Expect(expInitContainer.Env[len(expInitContainer.Env)-2].Name).To(Equal("SOLR_OPTS"), "Env var SOLR_OPTS is misplaced the Solr Pod env vars")
329-
g.Expect(expInitContainer.Env[len(expInitContainer.Env)-1].Name).To(Equal("SECURITY_JSON"), "Env var SECURITY_JSON is misplaced the Solr Pod env vars")
330-
testACLEnvVarsWithGomega(g, expInitContainer.Env[3:len(expInitContainer.Env)-2], true)
330+
g.Expect(expInitContainer.Env).To(HaveLen(11), "Wrong number of env vars using ACLs and Basic Auth")
331+
g.Expect(expInitContainer.Env[len(expInitContainer.Env)-3].Name).To(Equal("SOLR_OPTS"), "Env var SOLR_OPTS is misplaced the Solr Pod env vars")
332+
g.Expect(expInitContainer.Env[len(expInitContainer.Env)-2].Name).To(Equal("SECURITY_JSON"), "Env var SECURITY_JSON is misplaced the Solr Pod env vars")
333+
g.Expect(expInitContainer.Env[len(expInitContainer.Env)-1].Name).To(Equal("SECURITY_JSON_OVERWRITE"), "Env var SECURITY_JSON_OVERWRITE is misplaced the Solr Pod env vars")
334+
testACLEnvVarsWithGomega(g, expInitContainer.Env[3:len(expInitContainer.Env)-3], true)
331335
} // else this ref not using ACLs
332336

333337
expectPutSecurityJsonInZkCmd(g, expInitContainer)
@@ -353,9 +357,15 @@ func expectPutSecurityJsonInZkCmd(g Gomega, expInitContainer *corev1.Container)
353357
g.Expect(expInitContainer).To(Not(BeNil()), "Didn't find the setup-zk InitContainer in the sts!")
354358
expCmd := "solr zk cp zk:/security.json /tmp/current_security.json >/dev/null 2>&1; " +
355359
"GET_CURRENT_SECURITY_JSON_EXIT_CODE=$?; if [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 0 ]; then " +
356-
"if [ ! -s /tmp/current_security.json ] || grep -q '^{}$' /tmp/current_security.json ]; then " +
360+
"if [ ! -s /tmp/current_security.json ] || grep -q '^{}$' /tmp/current_security.json; then " +
357361
"echo $SECURITY_JSON > /tmp/security.json; solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " +
358-
" echo 'Blank security.json found. Put new security.json in ZK'; fi; elif [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 1 ]; then " +
362+
" echo 'Blank security.json found. Put new security.json in ZK'; " +
363+
"elif [ \"${SECURITY_JSON_OVERWRITE}\" = true ] && [ \"$(cat /tmp/current_security.json)\" != \"$(echo $SECURITY_JSON)\" ]; then " +
364+
" echo $SECURITY_JSON > /tmp/security.json; solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " +
365+
" echo 'Diff found. Overwriting security.json in ZK'; " +
366+
" else " +
367+
" echo 'Not overwriting security.json'; fi; " +
368+
"elif [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 1 ]; then " +
359369
" echo $SECURITY_JSON > /tmp/security.json; solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " +
360370
" echo 'No security.json found. Put new security.json in ZK'; fi"
361371
g.Expect(expInitContainer.Command[2]).To(ContainSubstring(expCmd), "setup-zk initContainer not configured to bootstrap security.json!")

‎controllers/solrcloud_controller_tls_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ func expectZkSetupInitContainerForTLSWithGomega(g Gomega, solrCloud *solrv1beta1
937937
g.Expect(zkSetupInitContainer.Command[2]).To(ContainSubstring(expChrootCmd), "ZK Setup command does init the chroot")
938938
expNumVars := 3
939939
if solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.BasicAuthSecret == "" {
940-
expNumVars = 4 // one more for SECURITY_JSON
940+
expNumVars = 5 // two more for SECURITY_JSON and SECURITY_JSON_OVERWRITE
941941
}
942942
g.Expect(zkSetupInitContainer.Env).To(HaveLen(expNumVars), "Wrong number of envVars for zk-setup init container")
943943
}

‎controllers/util/solr_security_util.go

+23-13
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,20 @@ import (
2323
b64 "encoding/base64"
2424
"encoding/json"
2525
"fmt"
26+
"math/rand"
27+
"regexp"
28+
"strings"
29+
"time"
30+
2631
solr "github.com/apache/solr-operator/api/v1beta1"
2732
"github.com/apache/solr-operator/controllers/util/solr_api"
2833
appsv1 "k8s.io/api/apps/v1"
2934
corev1 "k8s.io/api/core/v1"
3035
"k8s.io/apimachinery/pkg/api/errors"
3136
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3237
"k8s.io/apimachinery/pkg/types"
33-
"math/rand"
34-
"regexp"
3538
"sigs.k8s.io/controller-runtime/pkg/client"
3639
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
37-
"strings"
38-
"time"
3940
)
4041

4142
const (
@@ -49,10 +50,11 @@ const (
4950
// Utility struct holding security related config and objects resolved at runtime needed during reconciliation,
5051
// such as the secret holding credentials the operator should use to make calls to secure Solr
5152
type SecurityConfig struct {
52-
SolrSecurity *solr.SolrSecurityOptions
53-
CredentialsSecret *corev1.Secret
54-
SecurityJson string
55-
SecurityJsonSrc *corev1.EnvVarSource
53+
SolrSecurity *solr.SolrSecurityOptions
54+
CredentialsSecret *corev1.Secret
55+
SecurityJson string
56+
SecurityJsonSrc *corev1.EnvVarSource
57+
SecurityJsonOverwrite bool
5658
}
5759

5860
// Given a SolrCloud instance and an API service client, produce a SecurityConfig needed to enable Solr security
@@ -117,6 +119,7 @@ func reconcileForBasicAuthWithBootstrappedSecurityJson(ctx context.Context, clie
117119

118120
// supply the bootstrap security.json to the initContainer via a simple BASE64 encoding env var
119121
security.SecurityJson = string(bootstrapSecret.Data[SecurityJsonFile])
122+
security.SecurityJsonOverwrite = false
120123
basicAuthSecret = authSecret
121124
}
122125

@@ -136,6 +139,7 @@ func reconcileForBasicAuthWithBootstrappedSecurityJson(ctx context.Context, clie
136139
} else {
137140
// stash this so we can configure the setup-zk initContainer to bootstrap the security.json in ZK
138141
security.SecurityJson = string(bootstrapSecret.Data[SecurityJsonFile])
142+
security.SecurityJsonOverwrite = false
139143
security.SecurityJsonSrc = &corev1.EnvVarSource{
140144
SecretKeyRef: &corev1.SecretKeySelector{
141145
LocalObjectReference: corev1.LocalObjectReference{Name: bootstrapSecret.Name}, Key: SecurityJsonFile}}
@@ -166,13 +170,14 @@ func reconcileForBasicAuthWithUserProvidedSecret(ctx context.Context, client *cl
166170

167171
// is there a user-provided security.json in a secret?
168172
// in this config, we don't need to enforce the user providing a security.json as they can bootstrap the security.json however they want
169-
if sec.BootstrapSecurityJson != nil {
170-
securityJson, err := loadSecurityJsonFromSecret(ctx, client, sec.BootstrapSecurityJson, instance.Namespace)
173+
if sec.BootstrapSecurityJson != nil && sec.BootstrapSecurityJson.SecurityJsonSecret != nil {
174+
securityJson, err := loadSecurityJsonFromSecret(ctx, client, sec.BootstrapSecurityJson.SecurityJsonSecret, instance.Namespace)
171175
if err != nil {
172176
return nil, err
173177
}
174178
security.SecurityJson = securityJson
175-
security.SecurityJsonSrc = &corev1.EnvVarSource{SecretKeyRef: sec.BootstrapSecurityJson}
179+
security.SecurityJsonSrc = &corev1.EnvVarSource{SecretKeyRef: sec.BootstrapSecurityJson.SecurityJsonSecret}
180+
security.SecurityJsonOverwrite = sec.BootstrapSecurityJson.Overwrite
176181
} // else no user-provided secret, no sweat for us
177182

178183
return security, nil
@@ -240,11 +245,16 @@ func cmdToPutSecurityJsonInZk() string {
240245
cmd := " solr zk cp zk:/security.json /tmp/current_security.json >/dev/null 2>&1; " +
241246
" GET_CURRENT_SECURITY_JSON_EXIT_CODE=$?; " +
242247
"if [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 0 ]; then " + // JSON already exists
243-
"if [ ! -s /tmp/current_security.json ] || grep -q '^{}$' /tmp/current_security.json ]; then " + // File doesn't exist, is empty, or is just '{}'
248+
"if [ ! -s /tmp/current_security.json ] || grep -q '^{}$' /tmp/current_security.json; then " + // File doesn't exist, is empty, or is just '{}'
244249
" echo $SECURITY_JSON > /tmp/security.json;" +
245250
" solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " +
246251
" echo 'Blank security.json found. Put new security.json in ZK'; " +
247-
"fi; " + // TODO: Consider checking a diff and still applying over the top
252+
"elif [ \"${SECURITY_JSON_OVERWRITE}\" = true ] && [ \"$(cat /tmp/current_security.json)\" != \"$(echo $SECURITY_JSON)\" ]; then " + // We want to overwrite the security config if there's a diff
253+
" echo $SECURITY_JSON > /tmp/security.json;" +
254+
" solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " +
255+
" echo 'Diff found. Overwriting security.json in ZK'; " +
256+
" else " +
257+
" echo 'Not overwriting security.json'; fi; " +
248258
"elif [ ${GET_CURRENT_SECURITY_JSON_EXIT_CODE} -eq 1 ]; then " + // JSON doesn't exist, but not other error types
249259
" echo $SECURITY_JSON > /tmp/security.json;" +
250260
" solr zk cp /tmp/security.json zk:/security.json >/dev/null 2>&1; " +

‎controllers/util/solr_util.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ package util
1919

2020
import (
2121
"fmt"
22+
"sort"
23+
"strconv"
24+
"strings"
25+
2226
solr "github.com/apache/solr-operator/api/v1beta1"
2327
appsv1 "k8s.io/api/apps/v1"
2428
corev1 "k8s.io/api/core/v1"
@@ -28,9 +32,6 @@ import (
2832
"k8s.io/apimachinery/pkg/util/intstr"
2933
"k8s.io/utils/pointer"
3034
"k8s.io/utils/ptr"
31-
"sort"
32-
"strconv"
33-
"strings"
3435
)
3536

3637
const (
@@ -1263,6 +1264,7 @@ func generateZKInteractionInitContainer(solrCloud *solr.SolrCloud, solrCloudStat
12631264

12641265
if security != nil && security.SecurityJson != "" {
12651266
envVars = append(envVars, corev1.EnvVar{Name: "SECURITY_JSON", ValueFrom: security.SecurityJsonSrc})
1267+
envVars = append(envVars, corev1.EnvVar{Name: "SECURITY_JSON_OVERWRITE", Value: strconv.FormatBool(security.SecurityJsonOverwrite)})
12661268
if solrCloud.Spec.SolrZkOpts != "" {
12671269
envVars = append(envVars, corev1.EnvVar{Name: "ZKCLI_JVM_FLAGS", Value: solrCloud.Spec.SolrZkOpts})
12681270
}

‎docs/solr-cloud/solr-cloud-crd.md

+3
Original file line numberDiff line numberDiff line change
@@ -1126,9 +1126,12 @@ spec:
11261126
bootstrapSecurityJson:
11271127
name: my-custom-security-json
11281128
key: security.json
1129+
overwrite: false
11291130
```
11301131
For `Basic` authentication, if you don't supply a `security.json` Secret, then the operator assumes you are bootstrapping the security configuration via some other means.
11311132

1133+
If `overwrite` is set to `true`, the security.json for the cluster will be updated if there is a difference between the underlying secret and the security.json in ZK.
1134+
11321135
Refer to the example `security.json` shown in the Authorization section above to help you get started crafting your own custom configuration.
11331136

11341137
#### Basic Authentication

0 commit comments

Comments
 (0)
Please sign in to comment.