Skip to content

Commit c48c47f

Browse files
authored
[backport] [2.11] add validation for etcd s3 cloud credential (#1021)
1 parent 88998da commit c48c47f

File tree

4 files changed

+361
-21
lines changed

4 files changed

+361
-21
lines changed

docs.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,10 @@ following:
545545
- Equal to another data directory
546546
- Attempts to nest another data directory
547547

548+
##### Etcd S3 CloudCredential Secret
549+
550+
Prevent the creation of objects if the secret specified in `.spec.rkeConfig.etcd.s3.cloudCredentialName` does not exist.
551+
548552
#### On Update
549553

550554
##### Creator ID Annotation
@@ -561,7 +565,7 @@ section. A secondary validator will ensure that the effective data directory for
561565
from the one chosen during cluster creation. Additionally, the changing of a data directory for the `system-agent`,
562566
kubernetes distro (RKE2/K3s), and CAPR components is also prohibited.
563567

564-
#### cluster.spec.clusterAgentDeploymentCustomization and cluster.spec.fleetAgentDeploymentCustomization
568+
##### cluster.spec.clusterAgentDeploymentCustomization and cluster.spec.fleetAgentDeploymentCustomization
565569

566570
The `DeploymentCustomization` fields are of 3 types:
567571
- `appendTolerations`: adds tolerations to the appropriate deployment (cluster-agent/fleet-agent)
@@ -576,7 +580,7 @@ A `Toleration` is matched to a regex which is provided by upstream [apimachinery
576580

577581
For the `Affinity` based rules, the `podAffinity`/`podAntiAffinity` are validated via label selectors via [this apimachinery function](https://github.com/kubernetes/apimachinery/blob/02a41040d88da08de6765573ae2b1a51f424e1ca/pkg/apis/meta/v1/validation/validation.go#L56) whereas the `nodeAffinity` `nodeSelectorTerms` are validated via the same `Toleration` function.
578582

579-
#### cluster.spec.clusterAgentDeploymentCustomization.schedulingCustomization
583+
##### cluster.spec.clusterAgentDeploymentCustomization.schedulingCustomization
580584

581585
The `SchedulingCustomization` subfield of the `DeploymentCustomization` field defines the properties of a Pod Disruption Budget and Priority Class which will be automatically deployed by Rancher for the cattle-cluster-agent.
582586

@@ -595,10 +599,16 @@ Both `minAvailable` and `maxUnavailable` must be a string which represents a non
595599
^([0-9]|[1-9][0-9]|100)%$
596600
```
597601

602+
##### Etcd S3 CloudCredential Secret
603+
604+
Prevent the update of objects if the secret specified in `.spec.rkeConfig.etcd.s3.cloudCredentialName` does not exist.
605+
598606
### Mutation Checks
599607

600608
#### On Create
601609

610+
##### Creator ID Annotation
611+
602612
When a cluster is created `field.cattle.io/creatorId` is set to the Username from the request.
603613

604614
If `field.cattle.io/no-creator-rbac` annotation is set, `field.cattle.io/creatorId` does not get set.

pkg/resources/provisioning.cattle.io/v1/cluster/Cluster.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ following:
1919
- Equal to another data directory
2020
- Attempts to nest another data directory
2121

22+
#### Etcd S3 CloudCredential Secret
23+
24+
Prevent the creation of objects if the secret specified in `.spec.rkeConfig.etcd.s3.cloudCredentialName` does not exist.
25+
2226
### On Update
2327

2428
#### Creator ID Annotation
@@ -35,7 +39,7 @@ section. A secondary validator will ensure that the effective data directory for
3539
from the one chosen during cluster creation. Additionally, the changing of a data directory for the `system-agent`,
3640
kubernetes distro (RKE2/K3s), and CAPR components is also prohibited.
3741

38-
### cluster.spec.clusterAgentDeploymentCustomization and cluster.spec.fleetAgentDeploymentCustomization
42+
#### cluster.spec.clusterAgentDeploymentCustomization and cluster.spec.fleetAgentDeploymentCustomization
3943

4044
The `DeploymentCustomization` fields are of 3 types:
4145
- `appendTolerations`: adds tolerations to the appropriate deployment (cluster-agent/fleet-agent)
@@ -50,7 +54,7 @@ A `Toleration` is matched to a regex which is provided by upstream [apimachinery
5054

5155
For the `Affinity` based rules, the `podAffinity`/`podAntiAffinity` are validated via label selectors via [this apimachinery function](https://github.com/kubernetes/apimachinery/blob/02a41040d88da08de6765573ae2b1a51f424e1ca/pkg/apis/meta/v1/validation/validation.go#L56) whereas the `nodeAffinity` `nodeSelectorTerms` are validated via the same `Toleration` function.
5256

53-
### cluster.spec.clusterAgentDeploymentCustomization.schedulingCustomization
57+
#### cluster.spec.clusterAgentDeploymentCustomization.schedulingCustomization
5458

5559
The `SchedulingCustomization` subfield of the `DeploymentCustomization` field defines the properties of a Pod Disruption Budget and Priority Class which will be automatically deployed by Rancher for the cattle-cluster-agent.
5660

@@ -69,10 +73,16 @@ Both `minAvailable` and `maxUnavailable` must be a string which represents a non
6973
^([0-9]|[1-9][0-9]|100)%$
7074
```
7175

76+
#### Etcd S3 CloudCredential Secret
77+
78+
Prevent the update of objects if the secret specified in `.spec.rkeConfig.etcd.s3.cloudCredentialName` does not exist.
79+
7280
## Mutation Checks
7381

7482
### On Create
7583

84+
#### Creator ID Annotation
85+
7686
When a cluster is created `field.cattle.io/creatorId` is set to the Username from the request.
7787

7888
If `field.cattle.io/no-creator-rbac` annotation is set, `field.cattle.io/creatorId` does not get set.

pkg/resources/provisioning.cattle.io/v1/cluster/validator.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func NewProvisioningClusterValidator(client *clients.Clients) *ProvisioningClust
5353
admitter: provisioningAdmitter{
5454
sar: client.K8s.AuthorizationV1().SubjectAccessReviews(),
5555
mgmtClusterClient: client.Management.Cluster(),
56+
secretClient: client.Core.Secret(),
5657
secretCache: client.Core.Secret().Cache(),
5758
psactCache: client.Management.PodSecurityAdmissionConfigurationTemplate().Cache(),
5859
featureCache: client.Management.Feature().Cache(),
@@ -87,6 +88,7 @@ func (p *ProvisioningClusterValidator) Admitters() []admission.Admitter {
8788
type provisioningAdmitter struct {
8889
sar authorizationv1.SubjectAccessReviewInterface
8990
mgmtClusterClient v3.ClusterClient
91+
secretClient corev1controller.SecretController
9092
secretCache corev1controller.SecretCache
9193
psactCache v3.PodSecurityAdmissionConfigurationTemplateCache
9294
featureCache v3.FeatureCache
@@ -150,6 +152,10 @@ func (p *provisioningAdmitter) Admit(request *admission.Request) (*admissionv1.A
150152
if response, err = p.validatePriorityClass(oldCluster, cluster); err != nil || !response.Allowed {
151153
return response, err
152154
}
155+
156+
if response, err = p.validateS3Secret(oldCluster, cluster); err != nil || !response.Allowed {
157+
return response, err
158+
}
153159
}
154160

155161
if err := p.validatePSACT(request, response, cluster); err != nil || response.Result != nil {
@@ -670,6 +676,51 @@ func (p *provisioningAdmitter) validatePodDisruptionBudget(oldCluster, cluster *
670676
return admission.ResponseAllowed(), nil
671677
}
672678

679+
// validateS3Secret checks if the S3 cloud credential secret referenced in the cluster exists.
680+
func (p *provisioningAdmitter) validateS3Secret(oldCluster, cluster *v1.Cluster) (*admissionv1.AdmissionResponse, error) {
681+
if cluster.Name == localCluster {
682+
return admission.ResponseAllowed(), nil
683+
}
684+
685+
rkeConfig := cluster.Spec.RKEConfig
686+
if rkeConfig == nil || rkeConfig.ETCD == nil || rkeConfig.ETCD.S3 == nil || rkeConfig.ETCD.S3.CloudCredentialName == "" {
687+
return admission.ResponseAllowed(), nil
688+
}
689+
690+
var (
691+
oldSecret string
692+
newSecret string
693+
)
694+
if oldCluster.Spec.RKEConfig != nil &&
695+
oldCluster.Spec.RKEConfig.ETCD != nil &&
696+
oldCluster.Spec.RKEConfig.ETCD.S3 != nil {
697+
oldSecret = oldCluster.Spec.RKEConfig.ETCD.S3.CloudCredentialName
698+
}
699+
newSecret = cluster.Spec.RKEConfig.ETCD.S3.CloudCredentialName
700+
701+
// Skip if the credential names remain the same
702+
if oldSecret == newSecret {
703+
return admission.ResponseAllowed(), nil
704+
}
705+
706+
// check if the secret exists
707+
namespace, name := getCloudCredentialSecretInfo(cluster.Namespace, newSecret)
708+
_, err := p.secretCache.Get(namespace, name)
709+
if apierrors.IsNotFound(err) {
710+
// Since the secret can be created alongside the cluster via the UI, there's a chance it's not yet present in the cache.
711+
// Therefore, we perform an additional check directly against the API server.
712+
_, err = p.secretClient.Get(namespace, name, metav1.GetOptions{})
713+
}
714+
if apierrors.IsNotFound(err) {
715+
msg := fmt.Sprintf("etcd s3 cloud credential secret %s/%s is not found", namespace, name)
716+
return admission.ResponseBadRequest(msg), nil
717+
} else if err != nil {
718+
return nil, fmt.Errorf("failed to get etcd s3 cloud credential secret %s/%s: %w", namespace, name, err)
719+
}
720+
721+
return admission.ResponseAllowed(), nil
722+
}
723+
673724
func validateAgentDeploymentCustomization(customization *v1.AgentDeploymentCustomization, path *field.Path) field.ErrorList {
674725
if customization == nil {
675726
return nil

0 commit comments

Comments
 (0)