Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ RUN go mod download
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
COPY internal/ internal/


# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
Expand Down
77 changes: 56 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This Operator SDK based tool aims at managing S3 related resources (buckets, pol

## At a glance

- Current S3 providers : [Minio](https://github.com/InseeFrLab/s3-operator/blob/main/controllers/s3/factory/minioS3Client.go)
- Current S3 providers : [Minio](https://github.com/InseeFrLab/s3-operator/blob/main/internal/s3/factory/minioS3Client.go)
- Currently managed S3 resources : [buckets](https://github.com/InseeFrLab/s3-operator/blob/main/api/v1alpha1/bucket_types.go), [policies](https://github.com/InseeFrLab/s3-operator/blob/main/api/v1alpha1/policy_types.go)

## Compatibility
Expand All @@ -21,14 +21,16 @@ At its heart, the operator revolves around CRDs that match S3 resources :
- `buckets.s3.onyxia.sh`
- `policies.s3.onyxia.sh`
- `paths.s3.onyxia.sh`
- `users.s3.onyxia.sh`
- `s3Users.s3.onyxia.sh`
- `s3Instances.s3.onyxia.sh`

The custom resources based on these CRDs are a somewhat simplified projection of the real S3 resources. From the operator's point of view :

- A `Bucket` CR matches a S3 bucket, and only has a name, a quota (actually two, [see Bucket example in *Usage* section below](#bucket)), and optionally, a set of paths
- A `Policy` CR matches a "canned" policy (not a bucket policy, but a global one, that can be attached to a user), and has a name, and its actual content (IAM JSON)
- A `Path` CR matches a set of paths inside of a policy. This is akin to the `paths` property of the `Bucket` CRD, except `Path` is not responsible for Bucket creation.
- A `S3User` CR matches a user in the s3 server, and has a name, a set of policy and a set of group.
- A `S3Instance` CR matches a s3Instance.

Each custom resource based on these CRDs on Kubernetes is to be matched with a resource on the S3 instance. If the CR and the corresponding S3 resource diverge, the operator will create or update the S3 resource to bring it back to.

Expand Down Expand Up @@ -72,25 +74,12 @@ The operator exposes a few parameters, meant to be set as arguments, though it's

The parameters are summarized in the table below :

| Flag name | Default | Environment variable | Multiple values allowed | Description |
| ------------------------------- | ---------------- | -------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `health-probe-bind-address` | `:8081` | - | no | The address the probe endpoint binds to. Comes from Operator SDK. |
| `leader-elect` | `false` | - | no | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. Comes from Operator SDK. |
| `metrics-bind-address` | `:8080` | - | no | The address the metric endpoint binds to. Comes from Operator SDK. |
| `region` | `us-east-1` | - | no | The region to configure for the S3 client. |
| `s3-access-key` | - | `S3_ACCESS_KEY` | no | The access key used to interact with the S3 server. |
| `s3-ca-certificate-base64` | - | - | yes | (Optional) Base64 encoded, PEM format CA certificate, for https requests to the S3 server. |
| `s3-ca-certificate-bundle-path` | - | - | no | (Optional) Path to a CA certificates bundle file, for https requests to the S3 server. |
| `s3-endpoint-url` | `localhost:9000` | - | no | Hostname (or hostname:port) of the S3 server. |
| `s3-provider` | `minio` | - | no | S3 provider (possible values : `minio`, `mockedS3Provider`) |
| `s3-secret-key` | - | `S3_SECRET_KEY` | no | The secret key used to interact with the S3 server. |
| `useSsl` | true | - | no | Use of SSL/TLS to connect to the S3 server |
| `bucket-deletion` | false | - | no | Trigger bucket deletion on the S3 backend upon CR deletion. Will fail if bucket is not empty. |
| `policy-deletion` | false | - | no | Trigger policy deletion on the S3 backend upon CR deletion |
| `path-deletion` | false | - | no | Trigger path deletion on the S3 backend upon CR deletion. Limited to deleting the `.keep` files used by the operator. |
| `s3User-deletion` | false | - | no | Trigger S3User deletion on the S3 backend upon CR deletion. |
| `override-existing-secret` | false | - | no | Update secret linked to s3User if already exist, else noop |

| Flag name | Default | Environment variable | Multiple values allowed | Description |
| --------------------------- | ------- | -------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `health-probe-bind-address` | `:8081` | - | no | The address the probe endpoint binds to. Comes from Operator SDK. |
| `leader-elect` | `false` | - | no | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. Comes from Operator SDK. |
| `metrics-bind-address` | `:8080` | - | no | The address the metric endpoint binds to. Comes from Operator SDK. | |
| `override-existing-secret` | false | - | no | Update secret linked to s3User if already exist, else noop |
## Minimal rights needed to work

The Operator need at least this rights:
Expand Down Expand Up @@ -147,6 +136,34 @@ The Operator need at least this rights:
- The same will happen if you modify a CR - the operator will adjust the S3 bucket or policy accordingly - with the notable exception that it will not delete paths for buckets.
- Upon deleting a CR, the corresponding bucket or policy will be left as is, as mentioned in the [*Description* section above](#description)

An instance of S3Operator can manage multiple S3. On each resource created you can set where to create it. To add multiple instance of S3 see S3Instance example. On each object deployed you can attach it to an existing s3Instance. If no instance is set on the resource, S3Operator will failback to default instance configured by env var.

### S3Instance example

```yaml
apiVersion: s3.onyxia.sh/v1alpha1
kind: S3Instance
metadata:
labels:
app.kubernetes.io/name: bucket
app.kubernetes.io/instance: bucket-sample
app.kubernetes.io/part-of: s3-operator
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: s3-operator
name: s3-default-instance # Name of the S3Instance
spec:
s3Provider: minio # Type of the Provider. Can be "mockedS3Provider" or "minio"
url: https://minio.example.com # URL of the Provider
secretRef: minio-credentials # Name of the secret containing 2 Keys S3_ACCESS_KEY and S3_SECRET_KEY
caCertSecretRef: minio-certs # Name of the secret containing key ca.crt with cert of s3provider
region: us-east-1 # Region of the Provider
allowedNamespaces: [] # namespaces allowed to have buckets, policies, ... Wildcard prefix/suffix allowed. If empty only the same namespace as s3instance is allowed
bucketDeletionEnabled: true # Allowed bucket entity suppression on s3instance
policyDeletionEnabled: true # Allowed policy entity suppression on s3instance
pathDeletionEnabled: true # Allowed path entity suppression on s3instance
s3UserDeletionEnabled: true # Allowed s3User entity suppression on s3instance
```

### Bucket example

```yaml
Expand Down Expand Up @@ -182,6 +199,10 @@ spec:
quota:
default: 10000000
# override: 20000000

# Optionnal, let empty if you have configured the default s3 else use an existing s3Instance
s3InstanceRef: "s3-default-instance"


```

Expand All @@ -202,6 +223,9 @@ spec:
# Policy name (on S3 server, as opposed to the name of the CR)
name: dummy-policy

# Optionnal, let empty if you have configured the default s3 else use an existing s3Instance
s3InstanceRef: "s3-default-instance"

# Content of the policy, as a multiline string
# This should be IAM compliant JSON - follow the guidelines of the actual
# S3 provider you're using, as sometimes only a subset is available.
Expand Down Expand Up @@ -245,6 +269,8 @@ spec:
- /home/alice
- /home/bob

# Optionnal, let empty if you have configured the default s3 else use an existing s3Instance
s3InstanceRef: "s3-default-instance"

```

Expand All @@ -266,11 +292,20 @@ spec:
policies:
- policy-example1
- policy-example2
# Optionnal, let empty if you have configured the default s3 else use an existing s3Instance
s3InstanceRef: "s3-default-instance"

```

Each S3user is linked to a kubernetes secret which have the same name that the S3User. The secret contains 2 keys: `accessKey` and `secretKey`.

### :info: How works s3InstanceRef

S3InstanceRef can get the following values:
- empty: In this case the s3instance use will be the default one configured at startup if the namespace is in the namespace allowed for this s3Instance
- `s3InstanceName`: In this case the s3Instance use will be the s3Instance with the name `s3InstanceName` in the current namespace (if the current namespace is allowed)
- `namespace/s3InstanceName`: In this case the s3Instance use will be the s3Instance with the name `s3InstanceName` in the namespace `namespace` (if the current namespace is allowed to use this s3Instance)

## Operator SDK generated guidelines

<details>
Expand Down
10 changes: 9 additions & 1 deletion api/v1alpha1/bucket_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,22 @@ type BucketSpec struct {
// +kubebuilder:validation:Optional
Paths []string `json:"paths,omitempty"`

// s3InstanceRef where create the bucket
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?(/[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?)?$`
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=127
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="s3InstanceRef is immutable"
// +kubebuilder:default=s3-operator/default
S3InstanceRef string `json:"s3InstanceRef"`

// Quota to apply to the bucket
// +kubebuilder:validation:Required
Quota Quota `json:"quota"`
}

// BucketStatus defines the observed state of Bucket
type BucketStatus struct {
// Status management using Conditions.
// Status management using Conditions.
// See also : https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
Expand Down
8 changes: 8 additions & 0 deletions api/v1alpha1/path_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ type PathSpec struct {
// Paths (folders) to create inside the bucket
// +kubebuilder:validation:Optional
Paths []string `json:"paths,omitempty"`

// s3InstanceRef where create the Paths
// +kubebuilder:default=s3-operator/default
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="s3InstanceRef is immutable"
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?(/[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?)?$`
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=127
S3InstanceRef string `json:"s3InstanceRef,omitempty"`
}

// PathStatus defines the observed state of Path
Expand Down
10 changes: 9 additions & 1 deletion api/v1alpha1/policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,19 @@ type PolicySpec struct {
// +kubebuilder:validation:Required
// Content of the policy (IAM JSON format)
PolicyContent string `json:"policyContent"`

// s3InstanceRef where create the Policy
// +kubebuilder:default=s3-operator/default
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="s3InstanceRef is immutable"
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?(/[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?)?$`
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=127
S3InstanceRef string `json:"s3InstanceRef,omitempty"`
}

// PolicyStatus defines the observed state of Policy
type PolicyStatus struct {
// Status management using Conditions.
// Status management using Conditions.
// See also : https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
Expand Down
103 changes: 103 additions & 0 deletions api/v1alpha1/s3instance_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Copyright 2023.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// S3InstanceSpec defines the desired state of S3Instance
type S3InstanceSpec struct {

// type of the S3Instance
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="S3Provider is immutable"
// +kubebuilder:default=minio
// +kubebuilder:validation:Enum=minio;mockedS3Provider
S3Provider string `json:"s3Provider,omitempty"`

// url of the S3Instance
// +kubebuilder:validation:Required
Url string `json:"url"`

// Ref to Secret associated to the S3Instance containing accessKey and secretKey
// +kubebuilder:validation:Required
SecretRef string `json:"secretRef"`

// region associated to the S3Instance
// +kubebuilder:validation:Optional
Region string `json:"region,omitempty"`

// Secret containing key ca.crt with the certificate associated to the S3InstanceUrl
// +kubebuilder:validation:Optional
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"`

// BucketDeletionEnabled Trigger bucket deletion on the S3 backend upon CR deletion. Will fail if bucket is not empty.
// +kubebuilder:default=false
BucketDeletionEnabled bool `json:"bucketDeletionEnabled,omitempty"`

// PolicyDeletionEnabled Trigger policy deletion on the S3 backend upon CR deletion.
// +kubebuilder:default=false
PolicyDeletionEnabled bool `json:"policyDeletionEnabled,omitempty"`

// PathDeletionEnabled Trigger path deletion on the S3 backend upon CR deletion. Limited to deleting the `.keep` files used by the operator.
// +kubebuilder:default=false
PathDeletionEnabled bool `json:"pathDeletionEnabled,omitempty"`

// S3UserDeletionEnabled Trigger S3 deletion on the S3 backend upon CR deletion.
// +kubebuilder:default=false
S3UserDeletionEnabled bool `json:"s3UserDeletionEnabled,omitempty"`
}

// S3InstanceStatus defines the observed state of S3Instance
type S3InstanceStatus struct {
// Status management using Conditions.
// See also : https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// S3Instance is the Schema for the S3Instances API
type S3Instance struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec S3InstanceSpec `json:"spec,omitempty"`
Status S3InstanceStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// S3InstanceList contains a list of S3Instance
type S3InstanceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []S3Instance `json:"items"`
}

func init() {
SchemeBuilder.Register(&S3Instance{}, &S3InstanceList{})
}
8 changes: 8 additions & 0 deletions api/v1alpha1/s3user_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ type S3UserSpec struct {
// SecretName associated to the S3User
// +kubebuilder:validation:Optional
SecretName string `json:"secretName"`

// s3InstanceRef where create the user
// +kubebuilder:default=s3-operator/default
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="s3InstanceRef is immutable"
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?(/[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?)?$`
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=127
S3InstanceRef string `json:"s3InstanceRef,omitempty"`
}

// S3UserStatus defines the observed state of S3User
Expand Down
16 changes: 16 additions & 0 deletions api/v1alpha1/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package v1alpha1

// Definitions to manage status condition types
const (
// ConditionReconciled represents the status of the resource reconciliation
ConditionReconciled = "Reconciled"
)

// Definitions to manage status condition reasons
const (
Reconciling = "Reconciling"
Unreachable = "Unreachable"
CreationFailure = "CreationFailure"
Reconciled = "Reconciled"
DeletionFailure = "DeletionFailure"
)
Loading