Skip to content
This repository has been archived by the owner on Oct 22, 2021. It is now read-only.

add support for generating basic-auth secret types #6

Merged
merged 6 commits into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 1 addition & 1 deletion docs/crds/quarks_v1alpha1_quarkssecret_crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ spec:
type: string
type:
description: 'What kind of secret to generate: password, certificate,
ssh, rsa'
ssh, rsa, basic-auth'
minLength: 1
type: string
required:
Expand Down
18 changes: 18 additions & 0 deletions docs/examples/basic-auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: quarks.cloudfoundry.org/v1alpha1
kind: QuarksSecret
metadata:
name: generate-basic-auth-1
spec:
type: basic-auth
secretName: gen-secret-basic-with-user
request:
basic-auth:
username: my-user
---
apiVersion: quarks.cloudfoundry.org/v1alpha1
kind: QuarksSecret
metadata:
name: generate-basic-auth-2
spec:
type: basic-auth
secretName: gen-secret-basic
24 changes: 24 additions & 0 deletions integration/quarks_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,28 @@ var _ = Describe("QuarksSecret", func() {
})
})
})

It("generates a basic auth secret with a username and password and deletes it when being deleted", func() {
// Create an QuarksSecret
manno marked this conversation as resolved.
Show resolved Hide resolved
var qs *qsv1a1.QuarksSecret
qSecret.Spec.SecretName = "generated-basic-auth-secret"
qSecret.Spec.Type = qsv1a1.BasicAuth
qSecret.Spec.Request.BasicAuthRequest.Username = "some-passed-in-username"
qs, tearDown, err := env.CreateQuarksSecret(env.Namespace, qSecret)
Expect(err).NotTo(HaveOccurred())
Expect(qs).NotTo(Equal(nil))
defer func(tdf machine.TearDownFunc) { Expect(tdf()).To(Succeed()) }(tearDown)

// check for generated secret
secret, err := env.CollectSecret(env.Namespace, "generated-basic-auth-secret")
Expect(err).NotTo(HaveOccurred())
Expect(secret.Data["password"]).To(MatchRegexp("^\\w{64}$"))
Expect(string(secret.Data["username"])).To(Equal("some-passed-in-username"))

// delete quarksSecret
err = env.DeleteQuarksSecret(env.Namespace, qSecret.Name)
Expect(err).NotTo(HaveOccurred())
err = env.WaitForSecretDeletion(env.Namespace, "generated-basic-auth-secret")
Expect(err).NotTo(HaveOccurred(), "dependent secret not deleted")
})
})
7 changes: 7 additions & 0 deletions pkg/kube/apis/quarkssecret/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
Certificate SecretType = "certificate"
SSHKey SecretType = "ssh"
RSAKey SecretType = "rsa"
BasicAuth SecretType = "basic-auth"
)

// SignerType defines the type of the certificate signer
Expand Down Expand Up @@ -92,8 +93,14 @@ type CertificateRequest struct {
ActivateEKSWorkaroundForSAN bool `json:"activateEKSWorkaroundForSAN,omitempty"`
}

// BasicAuthRequest specifies the details for generating a basic-auth secret
type BasicAuthRequest struct {
Username string `json:"username"`
}

// Request specifies details for the secret generation
type Request struct {
BasicAuthRequest BasicAuthRequest `json:"basic-auth"`
CertificateRequest CertificateRequest `json:"certificate"`
}

Expand Down
24 changes: 24 additions & 0 deletions pkg/kube/apis/quarkssecret/v1alpha1/zz_generated.deepcopy.go

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

27 changes: 27 additions & 0 deletions pkg/kube/controllers/quarkssecret/quarkssecret_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ func (r *ReconcileQuarksSecret) Reconcile(request reconcile.Request) (reconcile.
ctxlog.Info(ctx, "Error generating certificate secret: "+err.Error())
return reconcile.Result{}, errors.Wrap(err, "generating certificate secret.")
}
case qsv1a1.BasicAuth:
err = r.createBasicAuthSecret(ctx, qsec)
if err != nil {
return reconcile.Result{}, errors.Wrap(err, "generating basic-auth secret")
}
default:
err = ctxlog.WithEvent(qsec, "InvalidTypeError").Errorf(ctx, "Invalid type: %s", qsec.Spec.Type)
return reconcile.Result{}, err
Expand Down Expand Up @@ -318,6 +323,28 @@ func (r *ReconcileQuarksSecret) createCertificateSecret(ctx context.Context, qse
}
}

func (r *ReconcileQuarksSecret) createBasicAuthSecret(ctx context.Context, qsec *qsv1a1.QuarksSecret) error {
username := qsec.Spec.Request.BasicAuthRequest.Username
if username == "" {
username = r.generator.GeneratePassword(fmt.Sprintf("%s/username", qsec.Name), credsgen.PasswordGenerationRequest{})
}
password := r.generator.GeneratePassword(fmt.Sprintf("%s/password", qsec.Name), credsgen.PasswordGenerationRequest{})

secret := &corev1.Secret{
Type: corev1.SecretTypeBasicAuth,
ObjectMeta: metav1.ObjectMeta{
Name: qsec.Spec.SecretName,
Namespace: qsec.GetNamespace(),
},
StringData: map[string]string{
"username": username,
"password": password,
},
}

return r.createSecrets(ctx, qsec, secret)
}

// Skip creation when
// * secret is already generated according to qsecs status field
// * secret exists, but was not generated (user created secret)
Expand Down
105 changes: 105 additions & 0 deletions pkg/kube/controllers/quarkssecret/quarkssecret_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package quarkssecret_test

import (
"context"
"fmt"
"time"

. "github.com/onsi/ginkgo"
Expand Down Expand Up @@ -439,6 +440,110 @@ var _ = Describe("ReconcileQuarksSecret", func() {
})
})

Context("when creating basic-auth", func() {
BeforeEach(func() {
qSecret.Spec.Type = "basic-auth"
})

It("creates a secret with k8s type basic-auth", func() {
client.CreateCalls(func(context context.Context, object runtime.Object, _ ...crc.CreateOption) error {
secret := object.(*corev1.Secret)
Expect(secret.Type).To(Equal(corev1.SecretTypeBasicAuth))
return nil
})

result, err := reconciler.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
Expect(client.CreateCallCount()).To(Equal(1))
Expect(reconcile.Result{}).To(Equal(result))
})

It("creates a secret in the correct namespace", func() {
client.CreateCalls(func(context context.Context, object runtime.Object, _ ...crc.CreateOption) error {
secret := object.(*corev1.Secret)
Expect(secret.Namespace).To(Equal(qSecret.Namespace))
return nil
})

result, err := reconciler.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
Expect(client.CreateCallCount()).To(Equal(1))
Expect(reconcile.Result{}).To(Equal(result))
})

It("creates a secret with the correct name", func() {
client.CreateCalls(func(context context.Context, object runtime.Object, _ ...crc.CreateOption) error {
secret := object.(*corev1.Secret)
Expect(secret.Name).To(Equal(qSecret.Spec.SecretName))
return nil
})

result, err := reconciler.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
Expect(client.CreateCallCount()).To(Equal(1))
Expect(reconcile.Result{}).To(Equal(result))
})

When("create basic auth secret fails", func() {
It("returns an error message", func() {
client.CreateReturns(fmt.Errorf("something went terribly wrong"))

_, err := reconciler.Reconcile(request)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("generating basic-auth secret: could not create or update secret 'default/generated-secret': something went terribly wrong"))
})

It("does not requeue", func() {
client.CreateReturns(fmt.Errorf("something went terribly wrong"))

result, _ := reconciler.Reconcile(request)
Expect(result.Requeue).To(BeFalse())
})
})

When("username is not provided", func() {
It("generates a username and password", func() {
generator.GeneratePasswordReturnsOnCall(0, "some-secret-user")
generator.GeneratePasswordReturnsOnCall(1,"some-secret-password")

client.CreateCalls(func(context context.Context, object runtime.Object, _ ...crc.CreateOption) error {
secret := object.(*corev1.Secret)
Expect(secret.Type).To(Equal(corev1.SecretTypeBasicAuth))
Expect(secret.StringData["username"]).To(Equal("some-secret-user"))
Expect(secret.StringData["password"]).To(Equal("some-secret-password"))
return nil
})

result, err := reconciler.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
Expect(generator.GeneratePasswordCallCount()).To(Equal(2))
Expect(client.CreateCallCount()).To(Equal(1))
Expect(reconcile.Result{}).To(Equal(result))
})
})

When("username is provided", func() {
manno marked this conversation as resolved.
Show resolved Hide resolved
It("generates a password, but not a username", func() {
qSecret.Spec.Request.BasicAuthRequest.Username = "some-passed-in-username"
generator.GeneratePasswordReturns("some-secret-password")

client.CreateCalls(func(context context.Context, object runtime.Object, _ ...crc.CreateOption) error {
secret := object.(*corev1.Secret)
Expect(secret.Type).To(Equal(corev1.SecretTypeBasicAuth))
Expect(secret.StringData["username"]).To(Equal("some-passed-in-username"))
Expect(secret.StringData["password"]).To(Equal("some-secret-password"))
return nil
})

result, err := reconciler.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
Expect(generator.GeneratePasswordCallCount()).To(Equal(1))
Expect(client.CreateCallCount()).To(Equal(1))
Expect(reconcile.Result{}).To(Equal(result))
})
})
})

Context("when secret is set manually", func() {
var (
password string
Expand Down