Skip to content

Commit

Permalink
immutable fields + allowedNamespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
Eneman Donatien authored and Donatien26 committed Sep 13, 2024
1 parent 34b85b3 commit 9710f6a
Show file tree
Hide file tree
Showing 23 changed files with 237 additions and 53 deletions.
1 change: 1 addition & 0 deletions api/v1alpha1/bucket_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type BucketSpec struct {

// s3InstanceRef where create the bucket
// +kubebuilder:validation:Optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="S3InstanceRef is immutable"
S3InstanceRef string `json:"s3InstanceRef,omitempty"`

// Quota to apply to the bucket
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/path_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type PathSpec struct {

// s3InstanceRef where create the Paths
// +kubebuilder:validation:Optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="S3InstanceRef is immutable"
S3InstanceRef string `json:"s3InstanceRef,omitempty"`
}

Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type PolicySpec struct {

// s3InstanceRef where create the Policy
// +kubebuilder:validation:Optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="S3InstanceRef is immutable"
S3InstanceRef string `json:"s3InstanceRef,omitempty"`
}

Expand Down
8 changes: 6 additions & 2 deletions api/v1alpha1/s3instance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ type S3InstanceSpec struct {
// +kubebuilder:validation:Optional
UseSSL bool `json:"useSSL,omitempty"`

// CaCertificatesBase64 associated to the S3InstanceUrl
// Secret containing key ca.crt with the certificate associated to the S3InstanceUrl
// +kubebuilder:validation:Optional
CaCertificatesBase64 []string `json:"caCertificateBase64,omitempty"`
CaCertSecretRef string `json:"caCertSecretRef,omitempty"`

// AllowedNamespaces to use this S3InstanceUrl if empty only the namespace of this instance url is allowed to use it
// +kubebuilder:validation:Optional
AllowedNamespaces []string `json:"allowedNamespaces,omitempty"`
}

// S3InstanceStatus defines the observed state of S3Instance
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/s3user_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type S3UserSpec struct {

// s3InstanceRef where create the user
// +kubebuilder:validation:Optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="S3InstanceRef is immutable"
S3InstanceRef string `json:"s3InstanceRef,omitempty"`
}

Expand Down
4 changes: 2 additions & 2 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions config/crd/bases/s3.onyxia.sh_buckets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ spec:
s3InstanceRef:
description: s3InstanceRef where create the bucket
type: string
x-kubernetes-validations:
- message: S3InstanceRef is immutable
rule: self == oldSelf
required:
- name
- quota
Expand Down
3 changes: 3 additions & 0 deletions config/crd/bases/s3.onyxia.sh_paths.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ spec:
s3InstanceRef:
description: s3InstanceRef where create the Paths
type: string
x-kubernetes-validations:
- message: S3InstanceRef is immutable
rule: self == oldSelf
required:
- bucketName
type: object
Expand Down
3 changes: 3 additions & 0 deletions config/crd/bases/s3.onyxia.sh_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ spec:
s3InstanceRef:
description: s3InstanceRef where create the Policy
type: string
x-kubernetes-validations:
- message: S3InstanceRef is immutable
rule: self == oldSelf
required:
- name
- policyContent
Expand Down
9 changes: 7 additions & 2 deletions config/crd/bases/s3.onyxia.sh_s3instances.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ spec:
spec:
description: S3InstanceSpec defines the desired state of S3Instance
properties:
caCertificateBase64:
description: CaCertificatesBase64 associated to the S3InstanceUrl
allowedNamespaces:
description: AllowedNamespaces to use this S3InstanceUrl if empty
only the namespace of this instance url is allowed to use it
items:
type: string
type: array
caCertSecretRef:
description: Secret containing key ca.crt with the certificate associated
to the S3InstanceUrl
type: string
region:
description: region associated to the S3Instance
type: string
Expand Down
3 changes: 3 additions & 0 deletions config/crd/bases/s3.onyxia.sh_s3users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ spec:
s3InstanceRef:
description: s3InstanceRef where create the user
type: string
x-kubernetes-validations:
- message: S3InstanceRef is immutable
rule: self == oldSelf
secretName:
description: SecretName associated to the S3User
type: string
Expand Down
8 changes: 7 additions & 1 deletion controllers/bucket_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,12 @@ func (r *BucketReconciler) getS3InstanceForObject(ctx context.Context, bucketRes
logger.Error(err, "No client was found")
return nil, err
}
return s3Client, nil
logger.Info(fmt.Sprintf("Check if this bucketRessource can use this S3Instance"))
if utils.IsAllowedNamespaces(bucketResource.Namespace, s3Client.GetConfig().AllowedNamespaces) {
return s3Client, nil
} else {
err := &s3ClientCache.S3ClientCacheError{Reason: fmt.Sprintf("Client %s is not allowed in this namespace", bucketResource.Spec.S3InstanceRef)}
return nil, err
}
}
}
10 changes: 8 additions & 2 deletions controllers/path_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,19 @@ func (r *PathReconciler) getS3InstanceForObject(ctx context.Context, pathResourc
return s3Client, nil
} else {

logger.Info(fmt.Sprintf("Bucket resource doesn't refer to s3Instance: %s, search instance in cache", pathResource.Spec.S3InstanceRef))
logger.Info(fmt.Sprintf("Path resource doesn't refer to s3Instance: %s, search instance in cache", pathResource.Spec.S3InstanceRef))
s3Client, found := r.S3ClientCache.Get(pathResource.Spec.S3InstanceRef)
if !found {
err := &s3ClientCache.S3ClientCacheError{Reason: fmt.Sprintf("S3InstanceRef: %s,not found in cache", pathResource.Spec.S3InstanceRef)}
logger.Error(err, "No client was found")
return nil, err
}
return s3Client, nil
logger.Info(fmt.Sprintf("Check if this PathRessource can use this S3Instance"))
if utils.IsAllowedNamespaces(pathResource.Namespace, s3Client.GetConfig().AllowedNamespaces) {
return s3Client, nil
} else {
err := &s3ClientCache.S3ClientCacheError{Reason: fmt.Sprintf("Client %s is not allowed in this namespace", pathResource.Spec.S3InstanceRef)}
return nil, err
}
}
}
9 changes: 8 additions & 1 deletion controllers/policy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,13 +281,20 @@ func (r *PolicyReconciler) getS3InstanceForObject(ctx context.Context, policyRes
}
return s3Client, nil
} else {
logger.Info(fmt.Sprintf("Bucket resource doesn't refer to s3Instance: %s, search instance in cache", policyResource.Spec.S3InstanceRef))
logger.Info(fmt.Sprintf("Policy resource doesn't refer to s3Instance: %s, search instance in cache", policyResource.Spec.S3InstanceRef))
s3Client, found := r.S3ClientCache.Get(policyResource.Spec.S3InstanceRef)
if !found {
err := &s3ClientCache.S3ClientCacheError{Reason: fmt.Sprintf("S3InstanceRef: %s,not found in cache", policyResource.Spec.S3InstanceRef)}
logger.Error(err, "No client was found")
return nil, err
}
logger.Info(fmt.Sprintf("Check if this PolicyRessource can use this S3Instance"))
if utils.IsAllowedNamespaces(policyResource.Namespace, s3Client.GetConfig().AllowedNamespaces) {
return s3Client, nil
} else {
err := &s3ClientCache.S3ClientCacheError{Reason: fmt.Sprintf("Client %s is not allowed in this namespace", policyResource.Spec.S3InstanceRef)}
return nil, err
}
return s3Client, nil
}
}
75 changes: 68 additions & 7 deletions controllers/s3instance_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,23 @@ func (r *S3InstanceReconciler) handleS3InstanceUpdate(ctx context.Context, s3Ins

// Get S3_ACCESS_KEY and S3_SECRET_KEY related to this s3Instance

s3InstanceSecretSecretExpected, err := r.getS3InstanceSecret(ctx, s3InstanceResource)
s3InstanceSecretSecretExpected, err := r.getS3InstanceAccessSecret(ctx, s3InstanceResource)
if err != nil {
logger.Error(err, "Could not get s3InstanceSecret in namespace", "s3InstanceSecretRefName", s3InstanceResource.Spec.SecretName)
return r.setS3InstanceStatusConditionAndUpdate(ctx, s3InstanceResource, "OperatorFailed", metav1.ConditionFalse, "S3InstanceUpdateFailed",
fmt.Sprintf("Updating secret of S3Instance %s has failed", s3InstanceResource.Name), err)
}

s3InstanceCaCertSecretExpected, err := r.getS3InstanceCaCertSecret(ctx, s3InstanceResource)
if err != nil {
logger.Error(err, "Could not get s3InstanceSecret in namespace", "s3InstanceSecretRefName", s3InstanceResource.Spec.SecretName)
return r.setS3InstanceStatusConditionAndUpdate(ctx, s3InstanceResource, "OperatorFailed", metav1.ConditionFalse, "S3InstanceCreationFailed",
fmt.Sprintf("Getting secret of S3s3Instance %s has failed", s3InstanceResource.Name), err)

}

// if s3Provider have change recreate totaly One Differ instance will be deleted and recreated
if s3Config.S3Provider != s3InstanceResource.Spec.S3Provider || s3Config.S3UrlEndpoint != s3InstanceResource.Spec.UrlEndpoint || s3Config.UseSsl != s3InstanceResource.Spec.UseSSL || s3Config.Region != s3InstanceResource.Spec.Region || !reflect.DeepEqual(s3Config.CaCertificatesBase64, s3InstanceResource.Spec.CaCertificatesBase64) || s3Config.AccessKey != string(s3InstanceSecretSecretExpected.Data["S3_ACCESS_KEY"]) || s3Config.SecretKey != string(s3InstanceSecretSecretExpected.Data["S3_SECRET_KEY"]) {
if s3Config.S3Provider != s3InstanceResource.Spec.S3Provider || s3Config.S3UrlEndpoint != s3InstanceResource.Spec.UrlEndpoint || s3Config.UseSsl != s3InstanceResource.Spec.UseSSL || s3Config.Region != s3InstanceResource.Spec.Region || !reflect.DeepEqual(s3Config.AllowedNamespaces, s3InstanceResource.Spec.AllowedNamespaces) || !reflect.DeepEqual(s3Config.CaCertificatesBase64, []string{string(s3InstanceCaCertSecretExpected.Data["ca.crt"])}) || s3Config.AccessKey != string(s3InstanceSecretSecretExpected.Data["S3_ACCESS_KEY"]) || s3Config.SecretKey != string(s3InstanceSecretSecretExpected.Data["S3_SECRET_KEY"]) {
logger.Info("Instance in cache not equal to expected , cache will be prune and instance recreate", "s3InstanceSecretRefName", s3InstanceResource.Spec.SecretName)
r.S3ClientCache.Remove(s3InstanceResource.Name)
return r.handleS3InstanceCreation(ctx, s3InstanceResource)
Expand All @@ -157,15 +165,22 @@ func (r *S3InstanceReconciler) handleS3InstanceUpdate(ctx context.Context, s3Ins
func (r *S3InstanceReconciler) handleS3InstanceCreation(ctx context.Context, s3InstanceResource *s3v1alpha1.S3Instance) (reconcile.Result, error) {
logger := log.FromContext(ctx)

s3InstanceSecretSecret, err := r.getS3InstanceSecret(ctx, s3InstanceResource)
s3InstanceSecretSecret, err := r.getS3InstanceAccessSecret(ctx, s3InstanceResource)
if err != nil {
logger.Error(err, "Could not get s3InstanceSecret in namespace", "s3InstanceSecretRefName", s3InstanceResource.Spec.SecretName)
return r.setS3InstanceStatusConditionAndUpdate(ctx, s3InstanceResource, "OperatorFailed", metav1.ConditionFalse, "S3InstanceCreationFailed",
fmt.Sprintf("Getting secret of S3s3Instance %s has failed", s3InstanceResource.Name), err)

}

s3Config := &s3Factory.S3Config{S3Provider: s3InstanceResource.Spec.S3Provider, AccessKey: string(s3InstanceSecretSecret.Data["S3_ACCESS_KEY"]), SecretKey: string(s3InstanceSecretSecret.Data["S3_SECRET_KEY"]), S3UrlEndpoint: s3InstanceResource.Spec.UrlEndpoint, Region: s3InstanceResource.Spec.Region, UseSsl: s3InstanceResource.Spec.UseSSL, CaCertificatesBase64: s3InstanceResource.Spec.CaCertificatesBase64}
s3InstanceCaCertSecret, err := r.getS3InstanceCaCertSecret(ctx, s3InstanceResource)
if err != nil {
logger.Error(err, "Could not get s3InstanceSecret in namespace", "s3InstanceSecretRefName", s3InstanceResource.Spec.SecretName)
return r.setS3InstanceStatusConditionAndUpdate(ctx, s3InstanceResource, "OperatorFailed", metav1.ConditionFalse, "S3InstanceCreationFailed",
fmt.Sprintf("Getting secret of S3s3Instance %s has failed", s3InstanceResource.Name), err)
}

s3Config := &s3Factory.S3Config{S3Provider: s3InstanceResource.Spec.S3Provider, AccessKey: string(s3InstanceSecretSecret.Data["S3_ACCESS_KEY"]), SecretKey: string(s3InstanceSecretSecret.Data["S3_SECRET_KEY"]), S3UrlEndpoint: s3InstanceResource.Spec.UrlEndpoint, Region: s3InstanceResource.Spec.Region, UseSsl: s3InstanceResource.Spec.UseSSL, AllowedNamespaces: s3InstanceResource.Spec.AllowedNamespaces, CaCertificatesBase64: []string{string(s3InstanceCaCertSecret.Data["ca.crt"])}}

s3Client, err := s3Factory.GenerateS3Client(s3Config.S3Provider, s3Config)
if err != nil {
Expand Down Expand Up @@ -285,11 +300,12 @@ func (r *S3InstanceReconciler) finalizeS3Instance(ctx context.Context, s3Instanc
return nil
}

func (r *S3InstanceReconciler) getS3InstanceSecret(ctx context.Context, s3InstanceResource *s3v1alpha1.S3Instance) (corev1.Secret, error) {
func (r *S3InstanceReconciler) getS3InstanceAccessSecret(ctx context.Context, s3InstanceResource *s3v1alpha1.S3Instance) (corev1.Secret, error) {
logger := log.FromContext(ctx)

secretsList := &corev1.SecretList{}
s3InstanceSecret := corev1.Secret{}
secretFound := false

err := r.List(ctx, secretsList, client.InNamespace(s3InstanceResource.Namespace))
if err != nil {
Expand All @@ -299,7 +315,7 @@ func (r *S3InstanceReconciler) getS3InstanceSecret(ctx context.Context, s3Instan

if len(secretsList.Items) == 0 {
logger.Info("The s3instance's namespace doesn't appear to contain any secret")
return s3InstanceSecret, nil
return s3InstanceSecret, fmt.Errorf("No secret found in namespace")
}
// In all the secrets inside the s3instance's namespace, one should have a name equal to
// the S3InstanceSecretRefName field.
Expand All @@ -309,9 +325,54 @@ func (r *S3InstanceReconciler) getS3InstanceSecret(ctx context.Context, s3Instan
for _, secret := range secretsList.Items {
if secret.Name == s3InstanceSecretName {
s3InstanceSecret = secret
secretFound = true
break
}
}
if secretFound {
return s3InstanceSecret, nil
} else {
return s3InstanceSecret, fmt.Errorf("Secret not found in namespace")
}
}

func (r *S3InstanceReconciler) getS3InstanceCaCertSecret(ctx context.Context, s3InstanceResource *s3v1alpha1.S3Instance) (corev1.Secret, error) {
logger := log.FromContext(ctx)

secretsList := &corev1.SecretList{}
s3InstanceCaCertSecret := corev1.Secret{}
secretFound := false

if s3InstanceResource.Spec.CaCertSecretRef == "" {
return s3InstanceCaCertSecret, nil
}

err := r.List(ctx, secretsList, client.InNamespace(s3InstanceResource.Namespace))
if err != nil {
logger.Error(err, "An error occurred while listing the secrets in s3instance's namespace")
return s3InstanceCaCertSecret, fmt.Errorf("SecretListingFailed")
}

if len(secretsList.Items) == 0 {
logger.Info("The s3instance's namespace doesn't appear to contain any secret")
return s3InstanceCaCertSecret, nil
}
// In all the secrets inside the s3instance's namespace, one should have a name equal to
// the S3InstanceSecretRefName field.
s3InstanceCaCertSecretRef := s3InstanceResource.Spec.CaCertSecretRef

// cmp.Or takes the first non "zero" value, see https://pkg.go.dev/cmp#Or
for _, secret := range secretsList.Items {
if secret.Name == s3InstanceCaCertSecretRef {
s3InstanceCaCertSecret = secret
break
}
}

return s3InstanceSecret, nil
if secretFound {
return s3InstanceCaCertSecret, nil
} else {
return s3InstanceCaCertSecret, fmt.Errorf("Secret not found in namespace")
}

}
9 changes: 8 additions & 1 deletion controllers/user_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,13 +619,20 @@ func (r *S3UserReconciler) getS3InstanceForObject(ctx context.Context, userResou
}
return s3Client, nil
} else {
logger.Info(fmt.Sprintf("Bucket resource doesn't refer to s3Instance: %s, search instance in cache", userResource.Spec.S3InstanceRef))
logger.Info(fmt.Sprintf("User resource doesn't refer to s3Instance: %s, search instance in cache", userResource.Spec.S3InstanceRef))
s3Client, found := r.S3ClientCache.Get(userResource.Spec.S3InstanceRef)
if !found {
err := &s3ClientCache.S3ClientCacheError{Reason: fmt.Sprintf("S3InstanceRef: %s,not found in cache", userResource.Spec.S3InstanceRef)}
logger.Error(err, "No client was found")
return nil, err
}
logger.Info(fmt.Sprintf("Check if this PathRessource can use this S3Instance"))
if utils.IsAllowedNamespaces(userResource.Namespace, s3Client.GetConfig().AllowedNamespaces) {
return s3Client, nil
} else {
err := &s3ClientCache.S3ClientCacheError{Reason: fmt.Sprintf("Client %s is not allowed in this namespace", userResource.Spec.S3InstanceRef)}
return nil, err
}
return s3Client, nil
}
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.11.4
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
Expand All @@ -29,6 +30,7 @@ require (
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/glob v0.2.3
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
Expand All @@ -37,6 +39,8 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
Expand Down
Loading

0 comments on commit 9710f6a

Please sign in to comment.