Skip to content

Commit 37d9c22

Browse files
authored
Merge pull request kubernetes#86377 from wojtek-t/immutable_secrets_api
API for immutable Secrets and ConfigMaps
2 parents c1b696d + 7cc3971 commit 37d9c22

File tree

28 files changed

+1419
-912
lines changed

28 files changed

+1419
-912
lines changed

api/openapi-spec/swagger.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apis/core/types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4735,6 +4735,12 @@ type Secret struct {
47354735
// +optional
47364736
metav1.ObjectMeta
47374737

4738+
// Immutable field, if set, ensures that data stored in the Secret cannot
4739+
// be updated (only object metadata can be modified).
4740+
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
4741+
// +optional
4742+
Immutable *bool
4743+
47384744
// Data contains the secret data. Each key must consist of alphanumeric
47394745
// characters, '-', '_' or '.'. The serialized form of the secret data is a
47404746
// base64 encoded string, representing the arbitrary (possibly non-string)
@@ -4857,6 +4863,12 @@ type ConfigMap struct {
48574863
// +optional
48584864
metav1.ObjectMeta
48594865

4866+
// Immutable field, if set, ensures that data stored in the ConfigMap cannot
4867+
// be updated (only object metadata can be modified).
4868+
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
4869+
// +optional
4870+
Immutable *bool
4871+
48604872
// Data contains the configuration data.
48614873
// Each key must consist of alphanumeric characters, '-', '_' or '.'.
48624874
// Values with non-UTF-8 byte sequences must use the BinaryData field.

pkg/apis/core/v1/zz_generated.conversion.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apis/core/validation/validation.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5005,6 +5005,16 @@ func ValidateSecretUpdate(newSecret, oldSecret *core.Secret) field.ErrorList {
50055005
}
50065006

50075007
allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...)
5008+
if oldSecret.Immutable != nil && *oldSecret.Immutable {
5009+
if !reflect.DeepEqual(newSecret.Immutable, oldSecret.Immutable) {
5010+
allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set"))
5011+
}
5012+
if !reflect.DeepEqual(newSecret.Data, oldSecret.Data) {
5013+
allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set"))
5014+
}
5015+
// We don't validate StringData, as it was already converted back to Data
5016+
// before validation is happening.
5017+
}
50085018

50095019
allErrs = append(allErrs, ValidateSecret(newSecret)...)
50105020
return allErrs
@@ -5051,8 +5061,20 @@ func ValidateConfigMap(cfg *core.ConfigMap) field.ErrorList {
50515061
func ValidateConfigMapUpdate(newCfg, oldCfg *core.ConfigMap) field.ErrorList {
50525062
allErrs := field.ErrorList{}
50535063
allErrs = append(allErrs, ValidateObjectMetaUpdate(&newCfg.ObjectMeta, &oldCfg.ObjectMeta, field.NewPath("metadata"))...)
5054-
allErrs = append(allErrs, ValidateConfigMap(newCfg)...)
50555064

5065+
if oldCfg.Immutable != nil && *oldCfg.Immutable {
5066+
if !reflect.DeepEqual(newCfg.Immutable, oldCfg.Immutable) {
5067+
allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set"))
5068+
}
5069+
if !reflect.DeepEqual(newCfg.Data, oldCfg.Data) {
5070+
allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set"))
5071+
}
5072+
if !reflect.DeepEqual(newCfg.BinaryData, oldCfg.BinaryData) {
5073+
allErrs = append(allErrs, field.Forbidden(field.NewPath("binaryData"), "field is immutable when `immutable` is set"))
5074+
}
5075+
}
5076+
5077+
allErrs = append(allErrs, ValidateConfigMap(newCfg)...)
50565078
return allErrs
50575079
}
50585080

pkg/apis/core/validation/validation_test.go

Lines changed: 186 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13117,6 +13117,104 @@ func TestValidateSecret(t *testing.T) {
1311713117
}
1311813118
}
1311913119

13120+
func TestValidateSecretUpdate(t *testing.T) {
13121+
validSecret := func() core.Secret {
13122+
return core.Secret{
13123+
ObjectMeta: metav1.ObjectMeta{
13124+
Name: "foo",
13125+
Namespace: "bar",
13126+
ResourceVersion: "20",
13127+
},
13128+
Data: map[string][]byte{
13129+
"data-1": []byte("bar"),
13130+
},
13131+
}
13132+
}
13133+
13134+
falseVal := false
13135+
trueVal := true
13136+
13137+
secret := validSecret()
13138+
immutableSecret := validSecret()
13139+
immutableSecret.Immutable = &trueVal
13140+
mutableSecret := validSecret()
13141+
mutableSecret.Immutable = &falseVal
13142+
13143+
secretWithData := validSecret()
13144+
secretWithData.Data["data-2"] = []byte("baz")
13145+
immutableSecretWithData := validSecret()
13146+
immutableSecretWithData.Immutable = &trueVal
13147+
immutableSecretWithData.Data["data-2"] = []byte("baz")
13148+
13149+
secretWithChangedData := validSecret()
13150+
secretWithChangedData.Data["data-1"] = []byte("foo")
13151+
immutableSecretWithChangedData := validSecret()
13152+
immutableSecretWithChangedData.Immutable = &trueVal
13153+
immutableSecretWithChangedData.Data["data-1"] = []byte("foo")
13154+
13155+
tests := []struct {
13156+
name string
13157+
oldSecret core.Secret
13158+
newSecret core.Secret
13159+
valid bool
13160+
}{
13161+
{
13162+
name: "mark secret immutable",
13163+
oldSecret: secret,
13164+
newSecret: immutableSecret,
13165+
valid: true,
13166+
},
13167+
{
13168+
name: "revert immutable secret",
13169+
oldSecret: immutableSecret,
13170+
newSecret: secret,
13171+
valid: false,
13172+
},
13173+
{
13174+
name: "makr immutable secret mutable",
13175+
oldSecret: immutableSecret,
13176+
newSecret: mutableSecret,
13177+
valid: false,
13178+
},
13179+
{
13180+
name: "add data in secret",
13181+
oldSecret: secret,
13182+
newSecret: secretWithData,
13183+
valid: true,
13184+
},
13185+
{
13186+
name: "add data in immutable secret",
13187+
oldSecret: immutableSecret,
13188+
newSecret: immutableSecretWithData,
13189+
valid: false,
13190+
},
13191+
{
13192+
name: "change data in secret",
13193+
oldSecret: secret,
13194+
newSecret: secretWithChangedData,
13195+
valid: true,
13196+
},
13197+
{
13198+
name: "change data in immutable secret",
13199+
oldSecret: immutableSecret,
13200+
newSecret: immutableSecretWithChangedData,
13201+
valid: false,
13202+
},
13203+
}
13204+
13205+
for _, tc := range tests {
13206+
t.Run(tc.name, func(t *testing.T) {
13207+
errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret)
13208+
if tc.valid && len(errs) > 0 {
13209+
t.Errorf("Unexpected error: %v", errs)
13210+
}
13211+
if !tc.valid && len(errs) == 0 {
13212+
t.Errorf("Unexpected lack of error")
13213+
}
13214+
})
13215+
}
13216+
}
13217+
1312013218
func TestValidateDockerConfigSecret(t *testing.T) {
1312113219
validDockerSecret := func() core.Secret {
1312213220
return core.Secret{
@@ -13731,40 +13829,105 @@ func TestValidateConfigMapUpdate(t *testing.T) {
1373113829
Data: data,
1373213830
}
1373313831
}
13832+
validConfigMap := func() core.ConfigMap {
13833+
return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"})
13834+
}
1373413835

13735-
var (
13736-
validConfigMap = newConfigMap("1", "validname", "validns", map[string]string{"key": "value"})
13737-
noVersion = newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
13738-
)
13836+
falseVal := false
13837+
trueVal := true
13838+
13839+
configMap := validConfigMap()
13840+
immutableConfigMap := validConfigMap()
13841+
immutableConfigMap.Immutable = &trueVal
13842+
mutableConfigMap := validConfigMap()
13843+
mutableConfigMap.Immutable = &falseVal
13844+
13845+
configMapWithData := validConfigMap()
13846+
configMapWithData.Data["key-2"] = "value-2"
13847+
immutableConfigMapWithData := validConfigMap()
13848+
immutableConfigMapWithData.Immutable = &trueVal
13849+
immutableConfigMapWithData.Data["key-2"] = "value-2"
13850+
13851+
configMapWithChangedData := validConfigMap()
13852+
configMapWithChangedData.Data["key"] = "foo"
13853+
immutableConfigMapWithChangedData := validConfigMap()
13854+
immutableConfigMapWithChangedData.Immutable = &trueVal
13855+
immutableConfigMapWithChangedData.Data["key"] = "foo"
13856+
13857+
noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
1373913858

1374013859
cases := []struct {
13741-
name string
13742-
newCfg core.ConfigMap
13743-
oldCfg core.ConfigMap
13744-
isValid bool
13860+
name string
13861+
newCfg core.ConfigMap
13862+
oldCfg core.ConfigMap
13863+
valid bool
1374513864
}{
1374613865
{
13747-
name: "valid",
13748-
newCfg: validConfigMap,
13749-
oldCfg: validConfigMap,
13750-
isValid: true,
13866+
name: "valid",
13867+
newCfg: configMap,
13868+
oldCfg: configMap,
13869+
valid: true,
1375113870
},
1375213871
{
13753-
name: "invalid",
13754-
newCfg: noVersion,
13755-
oldCfg: validConfigMap,
13756-
isValid: false,
13872+
name: "invalid",
13873+
newCfg: noVersion,
13874+
oldCfg: configMap,
13875+
valid: false,
13876+
},
13877+
{
13878+
name: "mark configmap immutable",
13879+
oldCfg: configMap,
13880+
newCfg: immutableConfigMap,
13881+
valid: true,
13882+
},
13883+
{
13884+
name: "revert immutable configmap",
13885+
oldCfg: immutableConfigMap,
13886+
newCfg: configMap,
13887+
valid: false,
13888+
},
13889+
{
13890+
name: "mark immutable configmap mutable",
13891+
oldCfg: immutableConfigMap,
13892+
newCfg: mutableConfigMap,
13893+
valid: false,
13894+
},
13895+
{
13896+
name: "add data in configmap",
13897+
oldCfg: configMap,
13898+
newCfg: configMapWithData,
13899+
valid: true,
13900+
},
13901+
{
13902+
name: "add data in immutable configmap",
13903+
oldCfg: immutableConfigMap,
13904+
newCfg: immutableConfigMapWithData,
13905+
valid: false,
13906+
},
13907+
{
13908+
name: "change data in configmap",
13909+
oldCfg: configMap,
13910+
newCfg: configMapWithChangedData,
13911+
valid: true,
13912+
},
13913+
{
13914+
name: "change data in immutable configmap",
13915+
oldCfg: immutableConfigMap,
13916+
newCfg: immutableConfigMapWithChangedData,
13917+
valid: false,
1375713918
},
1375813919
}
1375913920

1376013921
for _, tc := range cases {
13761-
errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
13762-
if tc.isValid && len(errs) > 0 {
13763-
t.Errorf("%v: unexpected error: %v", tc.name, errs)
13764-
}
13765-
if !tc.isValid && len(errs) == 0 {
13766-
t.Errorf("%v: unexpected non-error", tc.name)
13767-
}
13922+
t.Run(tc.name, func(t *testing.T) {
13923+
errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
13924+
if tc.valid && len(errs) > 0 {
13925+
t.Errorf("Unexpected error: %v", errs)
13926+
}
13927+
if !tc.valid && len(errs) == 0 {
13928+
t.Errorf("Unexpected lack of error")
13929+
}
13930+
})
1376813931
}
1376913932
}
1377013933

pkg/apis/core/zz_generated.deepcopy.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/features/kube_features.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,12 @@ const (
524524
//
525525
// Enables topology aware service routing
526526
ServiceTopology featuregate.Feature = "ServiceTopology"
527+
528+
// owner: @wojtek-t
529+
// alpha: v1.18
530+
//
531+
// Enables a feature to make secrets and configmaps data immutable.
532+
ImmutableEphemeralVolumes featuregate.Feature = "ImmutableEphemeralVolumes"
527533
)
528534

529535
func init() {
@@ -607,6 +613,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
607613
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.Beta},
608614
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},
609615
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
616+
ImmutableEphemeralVolumes: {Default: false, PreRelease: featuregate.Alpha},
610617

611618
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
612619
// unintentionally on either side:

0 commit comments

Comments
 (0)