From b84ae51dee423e1760f8b8f86664cd5e4c68c0e7 Mon Sep 17 00:00:00 2001 From: Shireesh Anjal <355479+anjalshireesh@users.noreply.github.com> Date: Thu, 20 Jul 2023 21:46:11 +0530 Subject: [PATCH] Add SFTP support (#1685) Add a new boolean field enableSFTP to the tenant spec. When it is set to true, pass the required arguments to minio to enable SFTP support. Also ensure that the required port (8022) is opened when SFTP is enabled. --- examples/kustomization/base/tenant.yaml | 3 ++ .../templates/minio.min.io_tenants.yaml | 2 + helm/tenant/templates/tenant.yaml | 1 + helm/tenant/values.yaml | 1 + kubectl-minio/cmd/resources/tenant.go | 4 ++ kubectl-minio/cmd/tenant-create.go | 2 + pkg/apis/minio.min.io/v2/constants.go | 6 +++ pkg/apis/minio.min.io/v2/types.go | 5 +++ .../minio.min.io/v2/zz_generated.deepcopy.go | 12 ++++++ .../minio.min.io/v2/features.go | 13 +++++- pkg/controller/minio-services.go | 4 ++ pkg/resources/services/service.go | 9 +++- .../statefulsets/minio-statefulset.go | 42 ++++++++++++++----- resources/base/crds/minio.min.io_tenants.yaml | 2 + 14 files changed, 92 insertions(+), 14 deletions(-) diff --git a/examples/kustomization/base/tenant.yaml b/examples/kustomization/base/tenant.yaml index cfc8b416cfc..9173717d058 100644 --- a/examples/kustomization/base/tenant.yaml +++ b/examples/kustomization/base/tenant.yaml @@ -25,6 +25,9 @@ spec: bucketDNS: false ## Specify a list of domains used to access MinIO and Console domains: { } + ## Enable access via SFTP + ## This feature is turned off by default + # enableSFTP: false ## Create users in the Tenant using this field. Make sure to create secrets per user added here. ## Secret should follow the format used in `minio-creds-secret`. users: diff --git a/helm/operator/templates/minio.min.io_tenants.yaml b/helm/operator/templates/minio.min.io_tenants.yaml index b938e447873..f6be3c2116a 100644 --- a/helm/operator/templates/minio.min.io_tenants.yaml +++ b/helm/operator/templates/minio.min.io_tenants.yaml @@ -204,6 +204,8 @@ spec: type: string type: array type: object + enableSFTP: + type: boolean type: object image: type: string diff --git a/helm/tenant/templates/tenant.yaml b/helm/tenant/templates/tenant.yaml index be79b345c0c..de67813832b 100644 --- a/helm/tenant/templates/tenant.yaml +++ b/helm/tenant/templates/tenant.yaml @@ -91,6 +91,7 @@ spec: {{- with (dig "features" "domains" (dict) .) }} domains: {{- toYaml . | nindent 6 }} {{- end }} + enableSFTP: {{ dig "features" "enableSFTP" false . }} {{- with (dig "buckets" (list) .) }} buckets: {{- toYaml . | nindent 4 }} {{- end }} diff --git a/helm/tenant/values.yaml b/helm/tenant/values.yaml index d5d6c542dc6..b277ef80f8f 100644 --- a/helm/tenant/values.yaml +++ b/helm/tenant/values.yaml @@ -107,6 +107,7 @@ tenant: features: bucketDNS: false domains: { } + enableSFTP: false ## List of bucket definitions to create during tenant provisioning. ## Example: # - name: my-minio-bucket diff --git a/kubectl-minio/cmd/resources/tenant.go b/kubectl-minio/cmd/resources/tenant.go index fb70dea6762..d5e15e7d3c5 100644 --- a/kubectl-minio/cmd/resources/tenant.go +++ b/kubectl-minio/cmd/resources/tenant.go @@ -44,6 +44,7 @@ type TenantOptions struct { DisableAntiAffinity bool ExposeMinioService bool ExposeConsoleService bool + EnableSFTP bool Interactive bool } @@ -136,6 +137,9 @@ func NewTenant(opts *TenantOptions, userSecret *v1.Secret) (*miniov2.Tenant, err Name: userSecret.Name, }, }, + Features: &miniov2.Features{ + EnableSFTP: &opts.EnableSFTP, + }, }, } diff --git a/kubectl-minio/cmd/tenant-create.go b/kubectl-minio/cmd/tenant-create.go index 10bf032d34b..fa93ee6c3bb 100644 --- a/kubectl-minio/cmd/tenant-create.go +++ b/kubectl-minio/cmd/tenant-create.go @@ -92,6 +92,7 @@ func newTenantCreateCmd(out io.Writer, errOut io.Writer) *cobra.Command { f.BoolVar(&c.tenantOpts.Interactive, "interactive", false, "Create tenant in interactive mode") f.BoolVar(&c.tenantOpts.ExposeMinioService, "expose-minio-service", false, "Enable/Disable expose the Minio Service") f.BoolVar(&c.tenantOpts.ExposeConsoleService, "expose-console-service", false, "Enable/Disable expose the Console service") + f.BoolVar(&c.tenantOpts.EnableSFTP, "enable-sftp", false, "Enable/Disable SFTP access to the tenant") return cmd } @@ -189,6 +190,7 @@ func (c *createCmd) populateInteractiveTenant() error { c.tenantOpts.DisableTLS = helpers.Ask("Disable TLS") c.tenantOpts.ExposeMinioService = helpers.Ask("Expose Minio Service") c.tenantOpts.ExposeConsoleService = helpers.Ask("Expose Console Service") + c.tenantOpts.EnableSFTP = helpers.Ask("Enable SFTP") return nil } diff --git a/pkg/apis/minio.min.io/v2/constants.go b/pkg/apis/minio.min.io/v2/constants.go index 5bbea43083d..fc977722491 100644 --- a/pkg/apis/minio.min.io/v2/constants.go +++ b/pkg/apis/minio.min.io/v2/constants.go @@ -69,6 +69,9 @@ const Revision = "min.io/revision" // MinIOPort specifies the default Tenant port number. const MinIOPort = 9000 +// MinIOSFTPPort specifies the default Tenant SFTP port number. +const MinIOSFTPPort = 8022 + // MinIOPortLoadBalancerSVC specifies the default Service port number for the load balancer service. const MinIOPortLoadBalancerSVC = 80 @@ -81,6 +84,9 @@ const MinIOServiceHTTPPortName = "http-minio" // MinIOServiceHTTPSPortName specifies the default Service's https port name, e.g. for automatic protocol selection in Istio const MinIOServiceHTTPSPortName = "https-minio" +// MinIOServiceSFTPPortName specifies the default Service's FTP port name +const MinIOServiceSFTPPortName = "sftp-minio" + // MinIOVolumeName specifies the default volume name for MinIO volumes const MinIOVolumeName = "export" diff --git a/pkg/apis/minio.min.io/v2/types.go b/pkg/apis/minio.min.io/v2/types.go index 911f8455c22..8c6dd992632 100644 --- a/pkg/apis/minio.min.io/v2/types.go +++ b/pkg/apis/minio.min.io/v2/types.go @@ -84,6 +84,10 @@ type Features struct { // Specify a list of domains used to access MinIO and Console. // Domains *TenantDomains `json:"domains,omitempty"` + // *Optional* + + // + // Starts minio server with SFTP support + EnableSFTP *bool `json:"enableSFTP,omitempty"` } // TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + @@ -132,6 +136,7 @@ type TenantSpec struct { // If provided, the MinIO Operator adds the specified environment variables when deploying the Tenant resource. // +optional Env []corev1.EnvVar `json:"env,omitempty"` + // *Optional* + // // Enables TLS with SNI support on each MinIO pod in the tenant. If `externalCertSecret` is omitted *and* `requestAutoCert` is set to `false`, the MinIO Tenant deploys *without* TLS enabled. + diff --git a/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go b/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go index 4f8f8eaa489..a5d0b57007a 100644 --- a/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go +++ b/pkg/apis/minio.min.io/v2/zz_generated.deepcopy.go @@ -209,6 +209,11 @@ func (in *Features) DeepCopyInto(out *Features) { *out = new(TenantDomains) (*in).DeepCopyInto(*out) } + if in.EnableSFTP != nil { + in, out := &in.EnableSFTP, &out.EnableSFTP + *out = new(bool) + **out = **in + } return } @@ -748,6 +753,13 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = new(v1.LocalObjectReference) **out = **in } + if in.InitContainers != nil { + in, out := &in.InitContainers, &out.InitContainers + *out = make([]v1.Container, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/pkg/client/applyconfiguration/minio.min.io/v2/features.go b/pkg/client/applyconfiguration/minio.min.io/v2/features.go index 23caee16c14..4da0ab0ee9e 100644 --- a/pkg/client/applyconfiguration/minio.min.io/v2/features.go +++ b/pkg/client/applyconfiguration/minio.min.io/v2/features.go @@ -21,8 +21,9 @@ package v2 // FeaturesApplyConfiguration represents an declarative configuration of the Features type for use // with apply. type FeaturesApplyConfiguration struct { - BucketDNS *bool `json:"bucketDNS,omitempty"` - Domains *TenantDomainsApplyConfiguration `json:"domains,omitempty"` + BucketDNS *bool `json:"bucketDNS,omitempty"` + Domains *TenantDomainsApplyConfiguration `json:"domains,omitempty"` + EnableSFTP *bool `json:"enableSFTP,omitempty"` } // FeaturesApplyConfiguration constructs an declarative configuration of the Features type for use with @@ -46,3 +47,11 @@ func (b *FeaturesApplyConfiguration) WithDomains(value *TenantDomainsApplyConfig b.Domains = value return b } + +// WithEnableSFTP sets the EnableSFTP field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the EnableSFTP field is set to the value of the last call. +func (b *FeaturesApplyConfiguration) WithEnableSFTP(value bool) *FeaturesApplyConfiguration { + b.EnableSFTP = &value + return b +} diff --git a/pkg/controller/minio-services.go b/pkg/controller/minio-services.go index 4a99c59a78b..fe425c94176 100644 --- a/pkg/controller/minio-services.go +++ b/pkg/controller/minio-services.go @@ -104,6 +104,10 @@ func minioSvcMatchesSpecification(svc *v1.Service, expectedSvc *v1.Service) (boo } } // expected ports match + if len(svc.Spec.Ports) != len(expectedSvc.Spec.Ports) { + return false, errors.New("service ports don't match") + } + for i, expPort := range expectedSvc.Spec.Ports { if expPort.Name != svc.Spec.Ports[i].Name || expPort.Port != svc.Spec.Ports[i].Port || diff --git a/pkg/resources/services/service.go b/pkg/resources/services/service.go index 01b16b0bdb4..3212e6b7953 100644 --- a/pkg/resources/services/service.go +++ b/pkg/resources/services/service.go @@ -150,6 +150,13 @@ func ServiceForBucket(t *miniov2.Tenant, bucket string) *corev1.Service { // NewHeadlessForMinIO will return a new headless Kubernetes service for a Tenant func NewHeadlessForMinIO(t *miniov2.Tenant) *corev1.Service { minioPort := corev1.ServicePort{Port: miniov2.MinIOPort, Name: miniov2.MinIOServiceHTTPPortName} + ports := []corev1.ServicePort{minioPort} + + if t.Spec.Features != nil && t.Spec.Features.EnableSFTP != nil && *t.Spec.Features.EnableSFTP { + minioSFTPPort := corev1.ServicePort{Port: miniov2.MinIOSFTPPort, Name: miniov2.MinIOServiceSFTPPortName} + ports = append(ports, minioSFTPPort) + } + svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Labels: t.MinIOPodLabels(), @@ -158,7 +165,7 @@ func NewHeadlessForMinIO(t *miniov2.Tenant) *corev1.Service { OwnerReferences: t.OwnerRef(), }, Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{minioPort}, + Ports: ports, Selector: t.MinIOPodLabels(), Type: corev1.ServiceTypeClusterIP, ClusterIP: corev1.ClusterIPNone, diff --git a/pkg/resources/statefulsets/minio-statefulset.go b/pkg/resources/statefulsets/minio-statefulset.go index 6b21c6148b9..840e295fc52 100644 --- a/pkg/resources/statefulsets/minio-statefulset.go +++ b/pkg/resources/statefulsets/minio-statefulset.go @@ -16,10 +16,12 @@ package statefulsets import ( "fmt" + "path/filepath" "sort" "strconv" "strings" + "github.com/minio/operator/pkg/certs" "github.com/minio/operator/pkg/common" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" @@ -292,7 +294,32 @@ func poolMinioServerContainer(t *miniov2.Tenant, skipEnvVars map[string][]byte, if t.TLS() { consolePort = miniov2.ConsoleTLSPort } - args := []string{"server", "--certs-dir", miniov2.MinIOCertPath, "--console-address", ":" + strconv.Itoa(consolePort)} + args := []string{ + "server", + "--certs-dir", miniov2.MinIOCertPath, + "--console-address", ":" + strconv.Itoa(consolePort), + } + + containerPorts := []corev1.ContainerPort{ + { + ContainerPort: miniov2.MinIOPort, + }, + { + ContainerPort: int32(consolePort), + }, + } + + if t.Spec.Features != nil && t.Spec.Features.EnableSFTP != nil && *t.Spec.Features.EnableSFTP { + pkFile := filepath.Join(miniov2.MinIOCertPath, certs.PrivateKeyFile) + args = append(args, []string{ + "--sftp", fmt.Sprintf("address=:%d", miniov2.MinIOSFTPPort), + "--sftp", "ssh-private-key=" + pkFile, + }...) + containerPorts = append(containerPorts, v1.ContainerPort{ + ContainerPort: miniov2.MinIOSFTPPort, + }) + } + if t.Spec.Logging != nil { // If logging is specified, expect users to // provide the right set of settings to toggle @@ -309,16 +336,9 @@ func poolMinioServerContainer(t *miniov2.Tenant, skipEnvVars map[string][]byte, } return corev1.Container{ - Name: miniov2.MinIOServerName, - Image: t.Spec.Image, - Ports: []corev1.ContainerPort{ - { - ContainerPort: miniov2.MinIOPort, - }, - { - ContainerPort: int32(consolePort), - }, - }, + Name: miniov2.MinIOServerName, + Image: t.Spec.Image, + Ports: containerPorts, ImagePullPolicy: t.Spec.ImagePullPolicy, VolumeMounts: volumeMounts(t, pool, certVolumeSources), Args: args, diff --git a/resources/base/crds/minio.min.io_tenants.yaml b/resources/base/crds/minio.min.io_tenants.yaml index 985d53fcd96..d6cd62bcc58 100644 --- a/resources/base/crds/minio.min.io_tenants.yaml +++ b/resources/base/crds/minio.min.io_tenants.yaml @@ -205,6 +205,8 @@ spec: type: string type: array type: object + enableSFTP: + type: boolean type: object image: type: string