Skip to content
Merged
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
47 changes: 47 additions & 0 deletions lib/tbot/config/destination_kubernetes_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ type DestinationKubernetesSecret struct {
// Name is the name the Kubernetes Secret that should be created and written
// to.
Name string `yaml:"name"`
// Labels is a set of labels to apply to the output Kubernetes secret.
// When configured, these labels will overwrite any existing labels on the
// secret.
Labels map[string]string `yaml:"labels,omitempty"`

mu sync.Mutex
namespace string
Expand Down Expand Up @@ -68,6 +72,7 @@ func (dks *DestinationKubernetesSecret) secretTemplate() *corev1.Secret {
ObjectMeta: v1.ObjectMeta{
Name: dks.Name,
Namespace: dks.namespace,
Labels: dks.Labels,
},
Data: map[string][]byte{},
}
Expand All @@ -79,6 +84,12 @@ func (dks *DestinationKubernetesSecret) upsertSecret(ctx context.Context, secret
WithResourceVersion(secret.ResourceVersion).
WithType(secret.Type)

// If user has configured labels, we overwrite the labels on the secret.
if len(dks.Labels) > 0 {
apply = apply.
WithLabels(dks.Labels)
}

applyOpts := v1.ApplyOptions{
FieldManager: "tbot",
}
Expand Down Expand Up @@ -197,6 +208,42 @@ func (dks *DestinationKubernetesSecret) Write(ctx context.Context, name string,
return trace.Wrap(err)
}

// WriteMany allows you to write multiple artifacts to a destination at once.
// This should be atomic, meaning all artifacts are written or none are. Any
// artifacts that are not specified will be removed from the destination.
func (dks *DestinationKubernetesSecret) WriteMany(ctx context.Context, toWrite map[string][]byte) error {
ctx, span := tracer.Start(
ctx,
"DestinationKubernetesSecret/WriteMany",
)
defer span.End()

dks.mu.Lock()
defer dks.mu.Unlock()
if dks.initialized == false {
return trace.BadParameter("destination has not been initialized")
}

secret, err := dks.getSecret(ctx)
if err != nil {
if !kubeerrors.IsNotFound(err) {
return trace.Wrap(err)
}
log.WithFields(logrus.Fields{
"secret_name": dks.Name,
"secret_namespace": dks.namespace,
}).Warn("Kubernetes secret missing on attempt to write data. One will be created.")
// If the secret doesn't exist, we create it on write - this is ensures
// that we can recover if the secret is deleted between renewal loops.
secret = dks.secretTemplate()
}

secret.Data = toWrite

err = dks.upsertSecret(ctx, secret, false)
return trace.Wrap(err)
}

func (dks *DestinationKubernetesSecret) Read(ctx context.Context, name string) ([]byte, error) {
ctx, span := tracer.Start(
ctx,
Expand Down
30 changes: 30 additions & 0 deletions lib/tbot/config/destination_kubernetes_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ func TestDestinationKubernetesSecret(t *testing.T) {
k8s: fakeClientSet(),
},
},
{
name: "labels",
dest: &DestinationKubernetesSecret{
Name: "my-secret",
Labels: map[string]string{
"key": "value",
"bar": "baz",
},
k8s: fakeClientSet(),
},
},
{
name: "existing secret",
dest: &DestinationKubernetesSecret{
Expand All @@ -97,6 +108,8 @@ func TestDestinationKubernetesSecret(t *testing.T) {
defer cancel()

require.NoError(t, tt.dest.Init(ctx, []string{}))

// Test individual write
require.NoError(t, tt.dest.Write(ctx, "artifact-a", []byte("data-a")))
require.NoError(t, tt.dest.Write(ctx, "artifact-b", []byte("data-b")))
aData, err := tt.dest.Read(ctx, "artifact-a")
Expand All @@ -105,6 +118,23 @@ func TestDestinationKubernetesSecret(t *testing.T) {
bData, err := tt.dest.Read(ctx, "artifact-b")
require.NoError(t, err)
require.Equal(t, []byte("data-b"), bData)

// Test write many
require.NoError(t, tt.dest.WriteMany(ctx, map[string][]byte{
"artifact-a": []byte("data-c"),
"artifact-b": []byte("data-d"),
}))
aData, err = tt.dest.Read(ctx, "artifact-a")
require.NoError(t, err)
require.Equal(t, []byte("data-c"), aData)
bData, err = tt.dest.Read(ctx, "artifact-b")
require.NoError(t, err)
require.Equal(t, []byte("data-d"), bData)

// Check labels have been set
secret, err := tt.dest.k8s.CoreV1().Secrets("test-namespace").Get(ctx, tt.dest.Name, metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, tt.dest.Labels, secret.Labels)
})
}
}
Expand Down