Merge pull request #1585 from fluxcd/bucket-sts-endpoint-ldap
Add LDAP provider for Bucket STS API
This commit is contained in:
commit
74e82d2467
|
@ -49,8 +49,11 @@ const (
|
|||
|
||||
// BucketSpec specifies the required configuration to produce an Artifact for
|
||||
// an object storage bucket.
|
||||
// +kubebuilder:validation:XValidation:rule="self.provider == 'aws' || !has(self.sts)", message="STS configuration is only supported for the 'aws' Bucket provider"
|
||||
// +kubebuilder:validation:XValidation:rule="self.provider == 'aws' || self.provider == 'generic' || !has(self.sts)", message="STS configuration is only supported for the 'aws' and 'generic' Bucket providers"
|
||||
// +kubebuilder:validation:XValidation:rule="self.provider != 'aws' || !has(self.sts) || self.sts.provider == 'aws'", message="'aws' is the only supported STS provider for the 'aws' Bucket provider"
|
||||
// +kubebuilder:validation:XValidation:rule="self.provider != 'generic' || !has(self.sts) || self.sts.provider == 'ldap'", message="'ldap' is the only supported STS provider for the 'generic' Bucket provider"
|
||||
// +kubebuilder:validation:XValidation:rule="!has(self.sts) || self.sts.provider != 'aws' || !has(self.sts.secretRef)", message="spec.sts.secretRef is not required for the 'aws' STS provider"
|
||||
// +kubebuilder:validation:XValidation:rule="!has(self.sts) || self.sts.provider != 'aws' || !has(self.sts.certSecretRef)", message="spec.sts.certSecretRef is not required for the 'aws' STS provider"
|
||||
type BucketSpec struct {
|
||||
// Provider of the object storage bucket.
|
||||
// Defaults to 'generic', which expects an S3 (API) compatible object
|
||||
|
@ -72,7 +75,7 @@ type BucketSpec struct {
|
|||
// Service for fetching temporary credentials to authenticate in a
|
||||
// Bucket provider.
|
||||
//
|
||||
// This field is only supported for the `aws` provider.
|
||||
// This field is only supported for the `aws` and `generic` providers.
|
||||
// +optional
|
||||
STS *BucketSTSSpec `json:"sts,omitempty"`
|
||||
|
||||
|
@ -153,7 +156,7 @@ type BucketSpec struct {
|
|||
// provider.
|
||||
type BucketSTSSpec struct {
|
||||
// Provider of the Security Token Service.
|
||||
// +kubebuilder:validation:Enum=aws
|
||||
// +kubebuilder:validation:Enum=aws;ldap
|
||||
// +required
|
||||
Provider string `json:"provider"`
|
||||
|
||||
|
@ -162,6 +165,29 @@ type BucketSTSSpec struct {
|
|||
// +required
|
||||
// +kubebuilder:validation:Pattern="^(http|https)://.*$"
|
||||
Endpoint string `json:"endpoint"`
|
||||
|
||||
// SecretRef specifies the Secret containing authentication credentials
|
||||
// for the STS endpoint. This Secret must contain the fields `username`
|
||||
// and `password` and is supported only for the `ldap` provider.
|
||||
// +optional
|
||||
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
|
||||
|
||||
// CertSecretRef can be given the name of a Secret containing
|
||||
// either or both of
|
||||
//
|
||||
// - a PEM-encoded client certificate (`tls.crt`) and private
|
||||
// key (`tls.key`);
|
||||
// - a PEM-encoded CA certificate (`ca.crt`)
|
||||
//
|
||||
// and whichever are supplied, will be used for connecting to the
|
||||
// STS endpoint. The client cert and key are useful if you are
|
||||
// authenticating with a certificate; the CA cert is useful if
|
||||
// you are using a self-signed server certificate. The Secret must
|
||||
// be of type `Opaque` or `kubernetes.io/tls`.
|
||||
//
|
||||
// This field is only supported for the `ldap` provider.
|
||||
// +optional
|
||||
CertSecretRef *meta.LocalObjectReference `json:"certSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// BucketStatus records the observed state of a Bucket.
|
||||
|
|
|
@ -20,4 +20,7 @@ const (
|
|||
// STSProviderAmazon represents the AWS provider for Security Token Service.
|
||||
// Provides support for fetching temporary credentials from an AWS STS endpoint.
|
||||
STSProviderAmazon string = "aws"
|
||||
// STSProviderLDAP represents the LDAP provider for Security Token Service.
|
||||
// Provides support for fetching temporary credentials from an LDAP endpoint.
|
||||
STSProviderLDAP string = "ldap"
|
||||
)
|
||||
|
|
|
@ -118,6 +118,16 @@ func (in *BucketList) DeepCopyObject() runtime.Object {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BucketSTSSpec) DeepCopyInto(out *BucketSTSSpec) {
|
||||
*out = *in
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
*out = new(meta.LocalObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.CertSecretRef != nil {
|
||||
in, out := &in.CertSecretRef, &out.CertSecretRef
|
||||
*out = new(meta.LocalObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BucketSTSSpec.
|
||||
|
@ -136,7 +146,7 @@ func (in *BucketSpec) DeepCopyInto(out *BucketSpec) {
|
|||
if in.STS != nil {
|
||||
in, out := &in.STS, &out.STS
|
||||
*out = new(BucketSTSSpec)
|
||||
**out = **in
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
|
|
|
@ -424,8 +424,34 @@ spec:
|
|||
Bucket provider.
|
||||
|
||||
|
||||
This field is only supported for the `aws` provider.
|
||||
This field is only supported for the `aws` and `generic` providers.
|
||||
properties:
|
||||
certSecretRef:
|
||||
description: |-
|
||||
CertSecretRef can be given the name of a Secret containing
|
||||
either or both of
|
||||
|
||||
|
||||
- a PEM-encoded client certificate (`tls.crt`) and private
|
||||
key (`tls.key`);
|
||||
- a PEM-encoded CA certificate (`ca.crt`)
|
||||
|
||||
|
||||
and whichever are supplied, will be used for connecting to the
|
||||
STS endpoint. The client cert and key are useful if you are
|
||||
authenticating with a certificate; the CA cert is useful if
|
||||
you are using a self-signed server certificate. The Secret must
|
||||
be of type `Opaque` or `kubernetes.io/tls`.
|
||||
|
||||
|
||||
This field is only supported for the `ldap` provider.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
endpoint:
|
||||
description: |-
|
||||
Endpoint is the HTTP/S endpoint of the Security Token Service from
|
||||
|
@ -436,7 +462,20 @@ spec:
|
|||
description: Provider of the Security Token Service.
|
||||
enum:
|
||||
- aws
|
||||
- ldap
|
||||
type: string
|
||||
secretRef:
|
||||
description: |-
|
||||
SecretRef specifies the Secret containing authentication credentials
|
||||
for the STS endpoint. This Secret must contain the fields `username`
|
||||
and `password` and is supported only for the `ldap` provider.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the referent.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
required:
|
||||
- endpoint
|
||||
- provider
|
||||
|
@ -457,12 +496,21 @@ spec:
|
|||
- interval
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: STS configuration is only supported for the 'aws' Bucket provider
|
||||
rule: self.provider == 'aws' || !has(self.sts)
|
||||
- message: STS configuration is only supported for the 'aws' and 'generic'
|
||||
Bucket providers
|
||||
rule: self.provider == 'aws' || self.provider == 'generic' || !has(self.sts)
|
||||
- message: '''aws'' is the only supported STS provider for the ''aws''
|
||||
Bucket provider'
|
||||
rule: self.provider != 'aws' || !has(self.sts) || self.sts.provider
|
||||
== 'aws'
|
||||
- message: '''ldap'' is the only supported STS provider for the ''generic''
|
||||
Bucket provider'
|
||||
rule: self.provider != 'generic' || !has(self.sts) || self.sts.provider
|
||||
== 'ldap'
|
||||
- message: spec.sts.secretRef is not required for the 'aws' STS provider
|
||||
rule: '!has(self.sts) || self.sts.provider != ''aws'' || !has(self.sts.secretRef)'
|
||||
- message: spec.sts.certSecretRef is not required for the 'aws' STS provider
|
||||
rule: '!has(self.sts) || self.sts.provider != ''aws'' || !has(self.sts.certSecretRef)'
|
||||
status:
|
||||
default:
|
||||
observedGeneration: -1
|
||||
|
|
|
@ -126,7 +126,7 @@ BucketSTSSpec
|
|||
<p>STS specifies the required configuration to use a Security Token
|
||||
Service for fetching temporary credentials to authenticate in a
|
||||
Bucket provider.</p>
|
||||
<p>This field is only supported for the <code>aws</code> provider.</p>
|
||||
<p>This field is only supported for the <code>aws</code> and <code>generic</code> providers.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -1497,6 +1497,48 @@ string
|
|||
where temporary credentials will be fetched.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>secretRef</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>SecretRef specifies the Secret containing authentication credentials
|
||||
for the STS endpoint. This Secret must contain the fields <code>username</code>
|
||||
and <code>password</code> and is supported only for the <code>ldap</code> provider.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>certSecretRef</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
|
||||
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>CertSecretRef can be given the name of a Secret containing
|
||||
either or both of</p>
|
||||
<ul>
|
||||
<li>a PEM-encoded client certificate (<code>tls.crt</code>) and private
|
||||
key (<code>tls.key</code>);</li>
|
||||
<li>a PEM-encoded CA certificate (<code>ca.crt</code>)</li>
|
||||
</ul>
|
||||
<p>and whichever are supplied, will be used for connecting to the
|
||||
STS endpoint. The client cert and key are useful if you are
|
||||
authenticating with a certificate; the CA cert is useful if
|
||||
you are using a self-signed server certificate. The Secret must
|
||||
be of type <code>Opaque</code> or <code>kubernetes.io/tls</code>.</p>
|
||||
<p>This field is only supported for the <code>ldap</code> provider.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -1569,7 +1611,7 @@ BucketSTSSpec
|
|||
<p>STS specifies the required configuration to use a Security Token
|
||||
Service for fetching temporary credentials to authenticate in a
|
||||
Bucket provider.</p>
|
||||
<p>This field is only supported for the <code>aws</code> provider.</p>
|
||||
<p>This field is only supported for the <code>aws</code> and <code>generic</code> providers.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -756,15 +756,75 @@ configuration. A Security Token Service (STS) is a web service that issues
|
|||
temporary security credentials. By adding this field, one may specify the
|
||||
STS endpoint from where temporary credentials will be fetched.
|
||||
|
||||
This field is only supported for the `aws` and `generic` bucket [providers](#provider).
|
||||
|
||||
If using `.spec.sts`, the following fields are required:
|
||||
|
||||
- `.spec.sts.provider`, the Security Token Service provider. The only supported
|
||||
option is `aws`.
|
||||
option for the `generic` bucket provider is `ldap`. The only supported option
|
||||
for the `aws` bucket provider is `aws`.
|
||||
- `.spec.sts.endpoint`, the HTTP/S endpoint of the Security Token Service. In
|
||||
the case of AWS, this can be `https://sts.amazonaws.com`, or a Regional STS
|
||||
Endpoint, or an Interface Endpoint created inside a VPC.
|
||||
the case of `aws` this can be `https://sts.amazonaws.com`, or a Regional STS
|
||||
Endpoint, or an Interface Endpoint created inside a VPC. In the case of
|
||||
`ldap` this must be the LDAP server endpoint.
|
||||
|
||||
This field is only supported for the `aws` bucket provider.
|
||||
When using the `ldap` provider, the following fields may also be specified:
|
||||
|
||||
- `.spec.sts.secretRef.name`, the name of the Secret containing the LDAP
|
||||
credentials. The Secret must contain the following keys:
|
||||
- `username`, the username to authenticate with.
|
||||
- `password`, the password to authenticate with.
|
||||
- `.spec.sts.certSecretRef.name`, the name of the Secret containing the
|
||||
TLS configuration for communicating with the STS endpoint. The contents
|
||||
of this Secret must follow the same structure of
|
||||
[`.spec.certSecretRef.name`](#cert-secret-reference).
|
||||
|
||||
If [`.spec.proxySecretRef.name`](#proxy-secret-reference) is specified,
|
||||
the proxy configuration will be used for commucating with the STS endpoint.
|
||||
|
||||
Example for the `ldap` provider:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||
kind: Bucket
|
||||
metadata:
|
||||
name: example
|
||||
namespace: example
|
||||
spec:
|
||||
interval: 5m
|
||||
bucketName: example
|
||||
provider: generic
|
||||
endpoint: minio.example.com
|
||||
sts:
|
||||
provider: ldap
|
||||
endpoint: https://ldap.example.com
|
||||
secretRef:
|
||||
name: ldap-credentials
|
||||
certSecretRef:
|
||||
name: ldap-tls
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ldap-credentials
|
||||
namespace: example
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: <username>
|
||||
password: <password>
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ldap-tls
|
||||
namespace: example
|
||||
type: kubernetes.io/tls # or Opaque
|
||||
stringData:
|
||||
tls.crt: <PEM-encoded cert>
|
||||
tls.key: <PEM-encoded key>
|
||||
ca.crt: <PEM-encoded cert>
|
||||
```
|
||||
|
||||
### Bucket name
|
||||
|
||||
|
|
|
@ -483,8 +483,27 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
|
|||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
tlsConfig, err := r.getTLSConfig(ctx, obj.Spec.CertSecretRef, obj.GetNamespace(), obj.Spec.Endpoint)
|
||||
if err != nil {
|
||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
stsSecret, err := r.getSTSSecret(ctx, obj)
|
||||
if err != nil {
|
||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
stsTLSConfig, err := r.getSTSTLSConfig(ctx, obj)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("failed to get STS TLS config: %w", err)
|
||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
if sts := obj.Spec.STS; sts != nil {
|
||||
if err := minio.ValidateSTSProvider(obj.Spec.Provider, sts.Provider); err != nil {
|
||||
if err := minio.ValidateSTSProvider(obj.Spec.Provider, sts); err != nil {
|
||||
e := serror.NewStalling(err, sourcev1.InvalidSTSConfigurationReason)
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
return sreconcile.ResultEmpty, e
|
||||
|
@ -495,12 +514,11 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
|
|||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
}
|
||||
tlsConfig, err := r.getTLSConfig(ctx, obj)
|
||||
if err != nil {
|
||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
return sreconcile.ResultEmpty, e
|
||||
if err := minio.ValidateSTSSecret(sts.Provider, stsSecret); err != nil {
|
||||
e := serror.NewGeneric(err, sourcev1.AuthenticationFailedReason)
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
}
|
||||
var opts []minio.Option
|
||||
if secret != nil {
|
||||
|
@ -512,6 +530,12 @@ func (r *BucketReconciler) reconcileSource(ctx context.Context, sp *patch.Serial
|
|||
if proxyURL != nil {
|
||||
opts = append(opts, minio.WithProxyURL(proxyURL))
|
||||
}
|
||||
if stsSecret != nil {
|
||||
opts = append(opts, minio.WithSTSSecret(stsSecret))
|
||||
}
|
||||
if stsTLSConfig != nil {
|
||||
opts = append(opts, minio.WithSTSTLSConfig(stsTLSConfig))
|
||||
}
|
||||
if provider, err = minio.NewClient(obj, opts...); err != nil {
|
||||
e := serror.NewGeneric(err, "ClientError")
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e)
|
||||
|
@ -732,12 +756,15 @@ func (r *BucketReconciler) getSecret(ctx context.Context, secretRef *meta.LocalO
|
|||
return secret, nil
|
||||
}
|
||||
|
||||
func (r *BucketReconciler) getTLSConfig(ctx context.Context, obj *bucketv1.Bucket) (*stdtls.Config, error) {
|
||||
certSecret, err := r.getSecret(ctx, obj.Spec.CertSecretRef, obj.GetNamespace())
|
||||
// getTLSConfig attempts to fetch a TLS configuration from the given
|
||||
// Secret reference, namespace and endpoint.
|
||||
func (r *BucketReconciler) getTLSConfig(ctx context.Context,
|
||||
secretRef *meta.LocalObjectReference, namespace, endpoint string) (*stdtls.Config, error) {
|
||||
certSecret, err := r.getSecret(ctx, secretRef, namespace)
|
||||
if err != nil || certSecret == nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(*certSecret, obj.Spec.Endpoint)
|
||||
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(*certSecret, endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create TLS config: %w", err)
|
||||
}
|
||||
|
@ -747,6 +774,8 @@ func (r *BucketReconciler) getTLSConfig(ctx context.Context, obj *bucketv1.Bucke
|
|||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
// getProxyURL attempts to fetch a proxy URL from the object's proxy secret
|
||||
// reference.
|
||||
func (r *BucketReconciler) getProxyURL(ctx context.Context, obj *bucketv1.Bucket) (*url.URL, error) {
|
||||
namespace := obj.GetNamespace()
|
||||
proxySecret, err := r.getSecret(ctx, obj.Spec.ProxySecretRef, namespace)
|
||||
|
@ -771,6 +800,24 @@ func (r *BucketReconciler) getProxyURL(ctx context.Context, obj *bucketv1.Bucket
|
|||
return proxyURL, nil
|
||||
}
|
||||
|
||||
// getSTSSecret attempts to fetch the secret from the object's STS secret
|
||||
// reference.
|
||||
func (r *BucketReconciler) getSTSSecret(ctx context.Context, obj *bucketv1.Bucket) (*corev1.Secret, error) {
|
||||
if obj.Spec.STS == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return r.getSecret(ctx, obj.Spec.STS.SecretRef, obj.GetNamespace())
|
||||
}
|
||||
|
||||
// getSTSTLSConfig attempts to fetch the certificate secret from the object's
|
||||
// STS configuration.
|
||||
func (r *BucketReconciler) getSTSTLSConfig(ctx context.Context, obj *bucketv1.Bucket) (*stdtls.Config, error) {
|
||||
if obj.Spec.STS == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return r.getTLSConfig(ctx, obj.Spec.STS.CertSecretRef, obj.GetNamespace(), obj.Spec.STS.Endpoint)
|
||||
}
|
||||
|
||||
// eventLogf records events, and logs at the same time.
|
||||
//
|
||||
// This log is different from the debug log in the EventRecorder, in the sense
|
||||
|
|
|
@ -592,6 +592,94 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
|||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "invalid proxy secret '/dummy': key 'address' is missing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Observes non-existing sts.secretRef",
|
||||
bucketName: "dummy",
|
||||
beforeFunc: func(obj *bucketv1.Bucket) {
|
||||
obj.Spec.STS = &bucketv1.BucketSTSSpec{
|
||||
SecretRef: &meta.LocalObjectReference{Name: "dummy"},
|
||||
}
|
||||
conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar")
|
||||
},
|
||||
wantErr: true,
|
||||
assertIndex: index.NewDigester(),
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
|
||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Observes invalid sts.secretRef",
|
||||
bucketName: "dummy",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "dummy",
|
||||
},
|
||||
},
|
||||
beforeFunc: func(obj *bucketv1.Bucket) {
|
||||
obj.Spec.Provider = "generic"
|
||||
obj.Spec.STS = &bucketv1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: "https://something",
|
||||
SecretRef: &meta.LocalObjectReference{Name: "dummy"},
|
||||
}
|
||||
conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar")
|
||||
},
|
||||
wantErr: true,
|
||||
assertIndex: index.NewDigester(),
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "invalid 'dummy' secret data for 'ldap' STS provider: required fields username, password"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Observes non-existing sts.certSecretRef",
|
||||
bucketName: "dummy",
|
||||
beforeFunc: func(obj *bucketv1.Bucket) {
|
||||
obj.Spec.STS = &bucketv1.BucketSTSSpec{
|
||||
CertSecretRef: &meta.LocalObjectReference{Name: "dummy"},
|
||||
}
|
||||
conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar")
|
||||
},
|
||||
wantErr: true,
|
||||
assertIndex: index.NewDigester(),
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get secret '/dummy': secrets \"dummy\" not found"),
|
||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Observes invalid sts.certSecretRef",
|
||||
bucketName: "dummy",
|
||||
secret: &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "dummy",
|
||||
},
|
||||
},
|
||||
beforeFunc: func(obj *bucketv1.Bucket) {
|
||||
obj.Spec.Provider = "generic"
|
||||
obj.Spec.STS = &bucketv1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: "https://something",
|
||||
CertSecretRef: &meta.LocalObjectReference{Name: "dummy"},
|
||||
}
|
||||
conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
|
||||
conditions.MarkUnknown(obj, meta.ReadyCondition, "foo", "bar")
|
||||
},
|
||||
wantErr: true,
|
||||
assertIndex: index.NewDigester(),
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.AuthenticationFailedReason, "failed to get STS TLS config: certificate secret does not contain any TLS configuration"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Observes non-existing bucket name",
|
||||
bucketName: "dummy",
|
||||
|
@ -609,7 +697,7 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "Observes incompatible STS provider",
|
||||
name: "Observes incompatible sts.provider",
|
||||
bucketName: "dummy",
|
||||
beforeFunc: func(obj *bucketv1.Bucket) {
|
||||
obj.Spec.Provider = "generic"
|
||||
|
@ -622,18 +710,18 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) {
|
|||
wantErr: true,
|
||||
assertIndex: index.NewDigester(),
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.InvalidSTSConfigurationReason, "STS configuration is not supported for 'generic' bucket provider"),
|
||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.InvalidSTSConfigurationReason, "STS provider 'aws' is not supported for 'generic' bucket provider"),
|
||||
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"),
|
||||
*conditions.UnknownCondition(meta.ReadyCondition, "foo", "bar"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Observes invalid STS endpoint",
|
||||
name: "Observes invalid sts.endpoint",
|
||||
bucketName: "dummy",
|
||||
beforeFunc: func(obj *bucketv1.Bucket) {
|
||||
obj.Spec.Provider = "aws" // TODO: change to generic when ldap STS provider is implemented
|
||||
obj.Spec.Provider = "generic"
|
||||
obj.Spec.STS = &bucketv1.BucketSTSSpec{
|
||||
Provider: "aws", // TODO: change to ldap when ldap STS provider is implemented
|
||||
Provider: "ldap",
|
||||
Endpoint: "something\t",
|
||||
}
|
||||
conditions.MarkReconciling(obj, meta.ProgressingReason, "foo")
|
||||
|
@ -1863,7 +1951,7 @@ func TestBucketReconciler_APIServerValidation_STS(t *testing.T) {
|
|||
Provider: "aws",
|
||||
Endpoint: "http://test",
|
||||
},
|
||||
err: "STS configuration is only supported for the 'aws' Bucket provider",
|
||||
err: "STS configuration is only supported for the 'aws' and 'generic' Bucket providers",
|
||||
},
|
||||
{
|
||||
name: "azure unsupported",
|
||||
|
@ -1872,16 +1960,7 @@ func TestBucketReconciler_APIServerValidation_STS(t *testing.T) {
|
|||
Provider: "aws",
|
||||
Endpoint: "http://test",
|
||||
},
|
||||
err: "STS configuration is only supported for the 'aws' Bucket provider",
|
||||
},
|
||||
{
|
||||
name: "generic unsupported",
|
||||
bucketProvider: "generic",
|
||||
stsConfig: &bucketv1.BucketSTSSpec{
|
||||
Provider: "aws",
|
||||
Endpoint: "http://test",
|
||||
},
|
||||
err: "STS configuration is only supported for the 'aws' Bucket provider",
|
||||
err: "STS configuration is only supported for the 'aws' and 'generic' Bucket providers",
|
||||
},
|
||||
{
|
||||
name: "aws supported",
|
||||
|
@ -1916,16 +1995,70 @@ func TestBucketReconciler_APIServerValidation_STS(t *testing.T) {
|
|||
name: "aws can be created without STS config",
|
||||
bucketProvider: "aws",
|
||||
},
|
||||
// Can't be tested at present with only one allowed sts provider.
|
||||
// {
|
||||
// name: "ldap unsupported for aws",
|
||||
// bucketProvider: "aws",
|
||||
// stsConfig: &bucketv1.BucketSTSSpec{
|
||||
// Provider: "ldap",
|
||||
// Endpoint: "http://test",
|
||||
// },
|
||||
// err: "'aws' is the only supported STS provider for the 'aws' Bucket provider",
|
||||
// },
|
||||
{
|
||||
name: "ldap unsupported for aws",
|
||||
bucketProvider: "aws",
|
||||
stsConfig: &bucketv1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: "http://test",
|
||||
},
|
||||
err: "'aws' is the only supported STS provider for the 'aws' Bucket provider",
|
||||
},
|
||||
{
|
||||
name: "aws unsupported for generic",
|
||||
bucketProvider: "generic",
|
||||
stsConfig: &bucketv1.BucketSTSSpec{
|
||||
Provider: "aws",
|
||||
Endpoint: "http://test",
|
||||
},
|
||||
err: "'ldap' is the only supported STS provider for the 'generic' Bucket provider",
|
||||
},
|
||||
{
|
||||
name: "aws does not require a secret",
|
||||
bucketProvider: "aws",
|
||||
stsConfig: &bucketv1.BucketSTSSpec{
|
||||
Provider: "aws",
|
||||
Endpoint: "http://test",
|
||||
SecretRef: &meta.LocalObjectReference{},
|
||||
},
|
||||
err: "spec.sts.secretRef is not required for the 'aws' STS provider",
|
||||
},
|
||||
{
|
||||
name: "aws does not require a cert secret",
|
||||
bucketProvider: "aws",
|
||||
stsConfig: &bucketv1.BucketSTSSpec{
|
||||
Provider: "aws",
|
||||
Endpoint: "http://test",
|
||||
CertSecretRef: &meta.LocalObjectReference{},
|
||||
},
|
||||
err: "spec.sts.certSecretRef is not required for the 'aws' STS provider",
|
||||
},
|
||||
{
|
||||
name: "ldap may use a secret",
|
||||
bucketProvider: "generic",
|
||||
stsConfig: &bucketv1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: "http://test",
|
||||
SecretRef: &meta.LocalObjectReference{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ldap may use a cert secret",
|
||||
bucketProvider: "generic",
|
||||
stsConfig: &bucketv1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: "http://test",
|
||||
CertSecretRef: &meta.LocalObjectReference{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ldap may not use a secret or cert secret",
|
||||
bucketProvider: "generic",
|
||||
stsConfig: &bucketv1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: "http://test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
|
@ -40,9 +41,11 @@ type MinioClient struct {
|
|||
|
||||
// options holds the configuration for the Minio client.
|
||||
type options struct {
|
||||
secret *corev1.Secret
|
||||
tlsConfig *tls.Config
|
||||
proxyURL *url.URL
|
||||
secret *corev1.Secret
|
||||
stsSecret *corev1.Secret
|
||||
tlsConfig *tls.Config
|
||||
stsTLSConfig *tls.Config
|
||||
proxyURL *url.URL
|
||||
}
|
||||
|
||||
// Option is a function that configures the Minio client.
|
||||
|
@ -69,6 +72,20 @@ func WithProxyURL(proxyURL *url.URL) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithSTSSecret sets the STS secret for the Minio client.
|
||||
func WithSTSSecret(secret *corev1.Secret) Option {
|
||||
return func(o *options) {
|
||||
o.stsSecret = secret
|
||||
}
|
||||
}
|
||||
|
||||
// WithSTSTLSConfig sets the STS TLS configuration for the Minio client.
|
||||
func WithSTSTLSConfig(tlsConfig *tls.Config) Option {
|
||||
return func(o *options) {
|
||||
o.stsTLSConfig = tlsConfig
|
||||
}
|
||||
}
|
||||
|
||||
// NewClient creates a new Minio storage client.
|
||||
func NewClient(bucket *sourcev1.Bucket, opts ...Option) (*MinioClient, error) {
|
||||
var o options
|
||||
|
@ -89,6 +106,8 @@ func NewClient(bucket *sourcev1.Bucket, opts ...Option) (*MinioClient, error) {
|
|||
minioOpts.Creds = newCredsFromSecret(o.secret)
|
||||
case bucketProvider == sourcev1.AmazonBucketProvider:
|
||||
minioOpts.Creds = newAWSCreds(bucket, o.proxyURL)
|
||||
case bucketProvider == sourcev1.GenericBucketProvider:
|
||||
minioOpts.Creds = newGenericCreds(bucket, &o)
|
||||
}
|
||||
|
||||
var transportOpts []func(*http.Transport)
|
||||
|
@ -159,6 +178,43 @@ func newAWSCreds(bucket *sourcev1.Bucket, proxyURL *url.URL) *credentials.Creden
|
|||
return creds
|
||||
}
|
||||
|
||||
// newGenericCreds creates a new Minio credentials object for the `generic` bucket provider.
|
||||
func newGenericCreds(bucket *sourcev1.Bucket, o *options) *credentials.Credentials {
|
||||
|
||||
sts := bucket.Spec.STS
|
||||
if sts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch sts.Provider {
|
||||
case sourcev1.STSProviderLDAP:
|
||||
client := &http.Client{Transport: http.DefaultTransport}
|
||||
if o.proxyURL != nil || o.stsTLSConfig != nil {
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
if o.proxyURL != nil {
|
||||
transport.Proxy = http.ProxyURL(o.proxyURL)
|
||||
}
|
||||
if o.stsTLSConfig != nil {
|
||||
transport.TLSClientConfig = o.stsTLSConfig.Clone()
|
||||
}
|
||||
client = &http.Client{Transport: transport}
|
||||
}
|
||||
var username, password string
|
||||
if o.stsSecret != nil {
|
||||
username = string(o.stsSecret.Data["username"])
|
||||
password = string(o.stsSecret.Data["password"])
|
||||
}
|
||||
return credentials.New(&credentials.LDAPIdentity{
|
||||
Client: client,
|
||||
STSEndpoint: sts.Endpoint,
|
||||
LDAPUsername: username,
|
||||
LDAPPassword: password,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateSecret validates the credential secret. The provided Secret may
|
||||
// be nil.
|
||||
func ValidateSecret(secret *corev1.Secret) error {
|
||||
|
@ -176,14 +232,31 @@ func ValidateSecret(secret *corev1.Secret) error {
|
|||
}
|
||||
|
||||
// ValidateSTSProvider validates the STS provider.
|
||||
func ValidateSTSProvider(bucketProvider, stsProvider string) error {
|
||||
func ValidateSTSProvider(bucketProvider string, sts *sourcev1.BucketSTSSpec) error {
|
||||
errProviderIncompatbility := fmt.Errorf("STS provider '%s' is not supported for '%s' bucket provider",
|
||||
stsProvider, bucketProvider)
|
||||
sts.Provider, bucketProvider)
|
||||
errSecretNotRequired := fmt.Errorf("spec.sts.secretRef is not required for the '%s' STS provider",
|
||||
sts.Provider)
|
||||
errCertSecretNotRequired := fmt.Errorf("spec.sts.certSecretRef is not required for the '%s' STS provider",
|
||||
sts.Provider)
|
||||
|
||||
switch bucketProvider {
|
||||
case sourcev1.AmazonBucketProvider:
|
||||
switch stsProvider {
|
||||
switch sts.Provider {
|
||||
case sourcev1.STSProviderAmazon:
|
||||
if sts.SecretRef != nil {
|
||||
return errSecretNotRequired
|
||||
}
|
||||
if sts.CertSecretRef != nil {
|
||||
return errCertSecretNotRequired
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errProviderIncompatbility
|
||||
}
|
||||
case sourcev1.GenericBucketProvider:
|
||||
switch sts.Provider {
|
||||
case sourcev1.STSProviderLDAP:
|
||||
return nil
|
||||
default:
|
||||
return errProviderIncompatbility
|
||||
|
@ -193,6 +266,36 @@ func ValidateSTSProvider(bucketProvider, stsProvider string) error {
|
|||
return fmt.Errorf("STS configuration is not supported for '%s' bucket provider", bucketProvider)
|
||||
}
|
||||
|
||||
// ValidateSTSSecret validates the STS secret. The provided Secret may be nil.
|
||||
func ValidateSTSSecret(stsProvider string, secret *corev1.Secret) error {
|
||||
switch stsProvider {
|
||||
case sourcev1.STSProviderLDAP:
|
||||
return validateSTSSecretForProvider(stsProvider, secret, "username", "password")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// validateSTSSecretForProvider validates the STS secret for each provider.
|
||||
// The provided Secret may be nil.
|
||||
func validateSTSSecretForProvider(stsProvider string, secret *corev1.Secret, keys ...string) error {
|
||||
if secret == nil {
|
||||
return nil
|
||||
}
|
||||
err := fmt.Errorf("invalid '%s' secret data for '%s' STS provider: required fields %s",
|
||||
secret.Name, stsProvider, strings.Join(keys, ", "))
|
||||
if len(secret.Data) == 0 {
|
||||
return err
|
||||
}
|
||||
for _, key := range keys {
|
||||
value, ok := secret.Data[key]
|
||||
if !ok || len(value) == 0 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FGetObject gets the object from the provided object storage bucket, and
|
||||
// writes it to targetPath.
|
||||
// It returns the etag of the successfully fetched file, or any error.
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
@ -70,6 +71,11 @@ var (
|
|||
testMinioClient *MinioClient
|
||||
// testTLSConfig is the TLS configuration used to connect to the Minio server.
|
||||
testTLSConfig *tls.Config
|
||||
// testServerCert is the path to the server certificate used to start the Minio
|
||||
// and STS servers.
|
||||
testServerCert string
|
||||
// testServerKey is the path to the server key used to start the Minio and STS servers.
|
||||
testServerKey string
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -128,8 +134,7 @@ func TestMain(m *testing.M) {
|
|||
|
||||
// Load a private key and certificate from a self-signed CA for the Minio server and
|
||||
// a client TLS configuration to connect to the Minio server.
|
||||
var serverCert, serverKey string
|
||||
serverCert, serverKey, testTLSConfig, err = loadServerCertAndClientTLSConfig()
|
||||
testServerCert, testServerKey, testTLSConfig, err = loadServerCertAndClientTLSConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("could not load server cert and client TLS config: %s", err)
|
||||
}
|
||||
|
@ -148,8 +153,8 @@ func TestMain(m *testing.M) {
|
|||
},
|
||||
Cmd: []string{"server", "/data", "--console-address", ":9001"},
|
||||
Mounts: []string{
|
||||
fmt.Sprintf("%s:/root/.minio/certs/public.crt", serverCert),
|
||||
fmt.Sprintf("%s:/root/.minio/certs/private.key", serverKey),
|
||||
fmt.Sprintf("%s:/root/.minio/certs/public.crt", testServerCert),
|
||||
fmt.Sprintf("%s:/root/.minio/certs/private.key", testServerKey),
|
||||
},
|
||||
}, func(config *docker.HostConfig) {
|
||||
config.AutoRemove = true
|
||||
|
@ -247,24 +252,24 @@ func TestFGetObject(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewClientAndFGetObjectWithSTSEndpoint(t *testing.T) {
|
||||
// start a mock STS server
|
||||
stsListener, stsAddr, stsPort := testlistener.New(t)
|
||||
stsEndpoint := fmt.Sprintf("http://%s", stsAddr)
|
||||
stsHandler := http.NewServeMux()
|
||||
stsHandler.HandleFunc("PUT "+credentials.TokenPath,
|
||||
// start a mock AWS STS server
|
||||
awsSTSListener, awsSTSAddr, awsSTSPort := testlistener.New(t)
|
||||
awsSTSEndpoint := fmt.Sprintf("http://%s", awsSTSAddr)
|
||||
awsSTSHandler := http.NewServeMux()
|
||||
awsSTSHandler.HandleFunc("PUT "+credentials.TokenPath,
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("mock-token"))
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
stsHandler.HandleFunc("GET "+credentials.DefaultIAMSecurityCredsPath,
|
||||
awsSTSHandler.HandleFunc("GET "+credentials.DefaultIAMSecurityCredsPath,
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get(credentials.TokenRequestHeader)
|
||||
assert.Equal(t, token, "mock-token")
|
||||
_, err := w.Write([]byte("mock-role"))
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
var roleCredsRetrieved bool
|
||||
stsHandler.HandleFunc("GET "+credentials.DefaultIAMSecurityCredsPath+"mock-role",
|
||||
var credsRetrieved bool
|
||||
awsSTSHandler.HandleFunc("GET "+credentials.DefaultIAMSecurityCredsPath+"mock-role",
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get(credentials.TokenRequestHeader)
|
||||
assert.Equal(t, token, "mock-token")
|
||||
|
@ -274,81 +279,187 @@ func TestNewClientAndFGetObjectWithSTSEndpoint(t *testing.T) {
|
|||
"SecretAccessKey": testMinioRootPassword,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
roleCredsRetrieved = true
|
||||
credsRetrieved = true
|
||||
})
|
||||
stsServer := &http.Server{
|
||||
Addr: stsAddr,
|
||||
Handler: stsHandler,
|
||||
awsSTSServer := &http.Server{
|
||||
Addr: awsSTSAddr,
|
||||
Handler: awsSTSHandler,
|
||||
}
|
||||
go stsServer.Serve(stsListener)
|
||||
defer stsServer.Shutdown(context.Background())
|
||||
go awsSTSServer.Serve(awsSTSListener)
|
||||
defer awsSTSServer.Shutdown(context.Background())
|
||||
|
||||
// start a mock LDAP STS server
|
||||
ldapSTSListener, ldapSTSAddr, ldapSTSPort := testlistener.New(t)
|
||||
ldapSTSEndpoint := fmt.Sprintf("https://%s", ldapSTSAddr)
|
||||
ldapSTSHandler := http.NewServeMux()
|
||||
var ldapUsername, ldapPassword string
|
||||
ldapSTSHandler.HandleFunc("POST /",
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
assert.NilError(t, err)
|
||||
username := r.Form.Get("LDAPUsername")
|
||||
password := r.Form.Get("LDAPPassword")
|
||||
assert.Equal(t, username, ldapUsername)
|
||||
assert.Equal(t, password, ldapPassword)
|
||||
var result credentials.LDAPIdentityResult
|
||||
result.Credentials.AccessKey = testMinioRootUser
|
||||
result.Credentials.SecretKey = testMinioRootPassword
|
||||
err = xml.NewEncoder(w).Encode(credentials.AssumeRoleWithLDAPResponse{Result: result})
|
||||
assert.NilError(t, err)
|
||||
credsRetrieved = true
|
||||
})
|
||||
ldapSTSServer := &http.Server{
|
||||
Addr: ldapSTSAddr,
|
||||
Handler: ldapSTSHandler,
|
||||
}
|
||||
go ldapSTSServer.ServeTLS(ldapSTSListener, testServerCert, testServerKey)
|
||||
defer ldapSTSServer.Shutdown(context.Background())
|
||||
|
||||
// start proxy
|
||||
proxyAddr, proxyPort := testproxy.New(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
provider string
|
||||
stsSpec *sourcev1.BucketSTSSpec
|
||||
opts []Option
|
||||
err string
|
||||
name string
|
||||
provider string
|
||||
stsSpec *sourcev1.BucketSTSSpec
|
||||
opts []Option
|
||||
ldapUsername string
|
||||
ldapPassword string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "with correct endpoint",
|
||||
name: "with correct aws endpoint",
|
||||
provider: "aws",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "aws",
|
||||
Endpoint: stsEndpoint,
|
||||
Endpoint: awsSTSEndpoint,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with incorrect endpoint",
|
||||
name: "with incorrect aws endpoint",
|
||||
provider: "aws",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "aws",
|
||||
Endpoint: fmt.Sprintf("http://localhost:%d", stsPort+1),
|
||||
Endpoint: fmt.Sprintf("http://localhost:%d", awsSTSPort+1),
|
||||
},
|
||||
err: "connection refused",
|
||||
},
|
||||
{
|
||||
name: "with correct endpoint and proxy",
|
||||
name: "with correct aws endpoint and proxy",
|
||||
provider: "aws",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "aws",
|
||||
Endpoint: stsEndpoint,
|
||||
Endpoint: awsSTSEndpoint,
|
||||
},
|
||||
opts: []Option{WithProxyURL(&url.URL{Scheme: "http", Host: proxyAddr})},
|
||||
},
|
||||
{
|
||||
name: "with correct endpoint and incorrect proxy",
|
||||
name: "with correct aws endpoint and incorrect proxy",
|
||||
provider: "aws",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "aws",
|
||||
Endpoint: stsEndpoint,
|
||||
Endpoint: awsSTSEndpoint,
|
||||
},
|
||||
opts: []Option{WithProxyURL(&url.URL{Scheme: "http", Host: fmt.Sprintf("localhost:%d", proxyPort+1)})},
|
||||
err: "connection refused",
|
||||
},
|
||||
{
|
||||
name: "with correct ldap endpoint",
|
||||
provider: "generic",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: ldapSTSEndpoint,
|
||||
},
|
||||
opts: []Option{WithSTSTLSConfig(testTLSConfig)},
|
||||
},
|
||||
{
|
||||
name: "with incorrect ldap endpoint",
|
||||
provider: "generic",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: fmt.Sprintf("http://localhost:%d", ldapSTSPort+1),
|
||||
},
|
||||
err: "connection refused",
|
||||
},
|
||||
{
|
||||
name: "with correct ldap endpoint and secret",
|
||||
provider: "generic",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: ldapSTSEndpoint,
|
||||
},
|
||||
opts: []Option{
|
||||
WithSTSTLSConfig(testTLSConfig),
|
||||
WithSTSSecret(&corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("user"),
|
||||
"password": []byte("password"),
|
||||
},
|
||||
}),
|
||||
},
|
||||
ldapUsername: "user",
|
||||
ldapPassword: "password",
|
||||
},
|
||||
{
|
||||
name: "with correct ldap endpoint and proxy",
|
||||
provider: "generic",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: ldapSTSEndpoint,
|
||||
},
|
||||
opts: []Option{
|
||||
WithProxyURL(&url.URL{Scheme: "http", Host: proxyAddr}),
|
||||
WithSTSTLSConfig(testTLSConfig),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with correct ldap endpoint and incorrect proxy",
|
||||
provider: "generic",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: ldapSTSEndpoint,
|
||||
},
|
||||
opts: []Option{
|
||||
WithProxyURL(&url.URL{Scheme: "http", Host: fmt.Sprintf("localhost:%d", proxyPort+1)}),
|
||||
},
|
||||
err: "connection refused",
|
||||
},
|
||||
{
|
||||
name: "with correct ldap endpoint and without client tls config",
|
||||
provider: "generic",
|
||||
stsSpec: &sourcev1.BucketSTSSpec{
|
||||
Provider: "ldap",
|
||||
Endpoint: ldapSTSEndpoint,
|
||||
},
|
||||
err: "tls: failed to verify certificate: x509: certificate signed by unknown authority",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
roleCredsRetrieved = false
|
||||
credsRetrieved = false
|
||||
ldapUsername = tt.ldapUsername
|
||||
ldapPassword = tt.ldapPassword
|
||||
|
||||
bucket := bucketStub(bucket, testMinioAddress)
|
||||
bucket.Spec.Provider = tt.provider
|
||||
bucket.Spec.STS = tt.stsSpec
|
||||
minioClient, err := NewClient(bucket, append(tt.opts, WithTLSConfig(testTLSConfig))...)
|
||||
|
||||
opts := tt.opts
|
||||
opts = append(opts, WithTLSConfig(testTLSConfig))
|
||||
|
||||
minioClient, err := NewClient(bucket, opts...)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, minioClient != nil)
|
||||
|
||||
ctx := context.Background()
|
||||
tempDir := t.TempDir()
|
||||
path := filepath.Join(tempDir, sourceignore.IgnoreFile)
|
||||
path := filepath.Join(t.TempDir(), sourceignore.IgnoreFile)
|
||||
_, err = minioClient.FGetObject(ctx, bucketName, objectName, path)
|
||||
if tt.err != "" {
|
||||
assert.ErrorContains(t, err, tt.err)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, roleCredsRetrieved)
|
||||
assert.Assert(t, credsRetrieved)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -477,6 +588,8 @@ func TestValidateSTSProvider(t *testing.T) {
|
|||
name string
|
||||
bucketProvider string
|
||||
stsProvider string
|
||||
withSecret bool
|
||||
withCertSecret bool
|
||||
err string
|
||||
}{
|
||||
{
|
||||
|
@ -485,15 +598,52 @@ func TestValidateSTSProvider(t *testing.T) {
|
|||
stsProvider: "aws",
|
||||
},
|
||||
{
|
||||
name: "unsupported for aws",
|
||||
name: "aws does not require a secret",
|
||||
bucketProvider: "aws",
|
||||
stsProvider: "aws",
|
||||
withSecret: true,
|
||||
err: "spec.sts.secretRef is not required for the 'aws' STS provider",
|
||||
},
|
||||
{
|
||||
name: "aws does not require a cert secret",
|
||||
bucketProvider: "aws",
|
||||
stsProvider: "aws",
|
||||
withCertSecret: true,
|
||||
err: "spec.sts.certSecretRef is not required for the 'aws' STS provider",
|
||||
},
|
||||
{
|
||||
name: "ldap",
|
||||
bucketProvider: "generic",
|
||||
stsProvider: "ldap",
|
||||
},
|
||||
{
|
||||
name: "ldap may use a secret",
|
||||
bucketProvider: "generic",
|
||||
stsProvider: "ldap",
|
||||
withSecret: true,
|
||||
},
|
||||
{
|
||||
name: "ldap may use a cert secret",
|
||||
bucketProvider: "generic",
|
||||
stsProvider: "ldap",
|
||||
withCertSecret: true,
|
||||
},
|
||||
{
|
||||
name: "ldap sts provider unsupported for aws bucket provider",
|
||||
bucketProvider: "aws",
|
||||
stsProvider: "ldap",
|
||||
err: "STS provider 'ldap' is not supported for 'aws' bucket provider",
|
||||
},
|
||||
{
|
||||
name: "aws sts provider unsupported for generic bucket provider",
|
||||
bucketProvider: "generic",
|
||||
stsProvider: "aws",
|
||||
err: "STS provider 'aws' is not supported for 'generic' bucket provider",
|
||||
},
|
||||
{
|
||||
name: "unsupported bucket provider",
|
||||
bucketProvider: "gcp",
|
||||
stsProvider: "gcp",
|
||||
stsProvider: "ldap",
|
||||
err: "STS configuration is not supported for 'gcp' bucket provider",
|
||||
},
|
||||
}
|
||||
|
@ -501,7 +651,102 @@ func TestValidateSTSProvider(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := ValidateSTSProvider(tt.bucketProvider, tt.stsProvider)
|
||||
sts := &sourcev1.BucketSTSSpec{
|
||||
Provider: tt.stsProvider,
|
||||
}
|
||||
if tt.withSecret {
|
||||
sts.SecretRef = &meta.LocalObjectReference{}
|
||||
}
|
||||
if tt.withCertSecret {
|
||||
sts.CertSecretRef = &meta.LocalObjectReference{}
|
||||
}
|
||||
err := ValidateSTSProvider(tt.bucketProvider, sts)
|
||||
if tt.err != "" {
|
||||
assert.Error(t, err, tt.err)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSTSSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
provider string
|
||||
secret *corev1.Secret
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "ldap provider does not require a secret",
|
||||
provider: "ldap",
|
||||
},
|
||||
{
|
||||
name: "valid ldap secret",
|
||||
provider: "ldap",
|
||||
secret: &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("user"),
|
||||
"password": []byte("pass"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty ldap secret",
|
||||
provider: "ldap",
|
||||
secret: &corev1.Secret{ObjectMeta: v1.ObjectMeta{Name: "ldap-secret"}},
|
||||
err: "invalid 'ldap-secret' secret data for 'ldap' STS provider: required fields username, password",
|
||||
},
|
||||
{
|
||||
name: "ldap secret missing password",
|
||||
provider: "ldap",
|
||||
secret: &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("user"),
|
||||
},
|
||||
},
|
||||
err: "invalid '' secret data for 'ldap' STS provider: required fields username, password",
|
||||
},
|
||||
{
|
||||
name: "ldap secret missing username",
|
||||
provider: "ldap",
|
||||
secret: &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
"password": []byte("pass"),
|
||||
},
|
||||
},
|
||||
err: "invalid '' secret data for 'ldap' STS provider: required fields username, password",
|
||||
},
|
||||
{
|
||||
name: "ldap secret with empty username",
|
||||
provider: "ldap",
|
||||
secret: &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
"username": []byte(""),
|
||||
"password": []byte("pass"),
|
||||
},
|
||||
},
|
||||
err: "invalid '' secret data for 'ldap' STS provider: required fields username, password",
|
||||
},
|
||||
{
|
||||
name: "ldap secret with empty password",
|
||||
provider: "ldap",
|
||||
secret: &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("user"),
|
||||
"password": []byte(""),
|
||||
},
|
||||
},
|
||||
err: "invalid '' secret data for 'ldap' STS provider: required fields username, password",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := ValidateSTSSecret(tt.provider, tt.secret)
|
||||
if tt.err != "" {
|
||||
assert.Error(t, err, tt.err)
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue