Skip to content

Commit

Permalink
Add artificial delay
Browse files Browse the repository at this point in the history
This adds an artificial delay to the VSO. Some services (such as AWS IAM)
are eventually consistent and require some time between generating
the secret, and using the secret.

Without this delay our services can't access AWS for a short while
immediately after the secret rotation happens.
  • Loading branch information
ebdekock committed Aug 30, 2024
1 parent 54dde37 commit d106dd2
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 0 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/salesloft_build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Docker Build and Push

on:
push:
branches:
- 'salesloft/*'

jobs:
build-pre-checks:
runs-on: ubuntu-22.04
outputs:
go-version: ${{ steps.setup-go.outputs.go-version }}
steps:
- uses: actions/[email protected]
- id: setup-go
uses: actions/[email protected]
with:
go-version-file: .go-version

vso-docker-build-push:
runs-on: ubuntu-22.04
needs: [build-pre-checks]

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Extract branch name
run: echo "BRANCH_NAME=${GITHUB_REF#refs/heads/salesloft/}" >> $GITHUB_ENV

- name: Setup Docker BuildX
uses: docker/setup-buildx-action@v3

- name: Login to Docker Registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build & Push
uses: docker/build-push-action@v5
with:
platforms: linux/amd64
push: true
file: Dockerfile
build-args: |
GO_VERSION: ${{ needs.build-pre-checks.outputs.go-version }}
tags: salesloft/vault-secrets-operator:${{ env.BRANCH_NAME }}-salesloft

- name: Image digest
run: echo "Image - vault-secrets-operator:${{ env.BRANCH_NAME }}-salesloft"
9 changes: 9 additions & 0 deletions controllers/vaultdynamicsecret_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ func (r *VaultDynamicSecretReconciler) syncSecret(ctx context.Context, c vault.C
}
}

if ad := os.Getenv("ARTIFICIAL_DELAY"); ad != "" {
d, err := time.ParseDuration(ad)
if err != nil {
logger.Error(err, "Invalid ARTIFICIAL_DELAY value, sleeping for 60 seconds")
time.Sleep(60 * time.Second)
}
time.Sleep(d)
}

if err := helpers.SyncSecret(ctx, r.Client, o, data); err != nil {
logger.Error(err, "Destination sync failed")
return nil, false, err
Expand Down
138 changes: 138 additions & 0 deletions controllers/vaultdynamicsecret_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"math"
"net/http"
"os"
"testing"
"time"

Expand Down Expand Up @@ -734,6 +735,143 @@ func Test_computeRotationTime(t *testing.T) {
}
}

func Test_artificialDelay(t *testing.T) {
type fields struct {
Client client.Client
runtimePodUID types.UID
}
type args struct {
ctx context.Context
vClient *vault.MockRecordingVaultClient
o *secretsv1beta1.VaultDynamicSecret
}
tests := []struct {
name string
envs map[string]string
fields fields
args args
want int
wantErr assert.ErrorAssertionFunc
}{
{
name: "no-delay",
envs: map[string]string{},
fields: fields{
Client: fake.NewClientBuilder().Build(),
runtimePodUID: "",
},
args: args{
ctx: nil,
vClient: &vault.MockRecordingVaultClient{},
o: &secretsv1beta1.VaultDynamicSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "default",
},
Spec: secretsv1beta1.VaultDynamicSecretSpec{
Mount: "baz",
Path: "foo",
Destination: secretsv1beta1.Destination{
Name: "baz",
Create: true,
},
},
Status: secretsv1beta1.VaultDynamicSecretStatus{},
},
},
wantErr: assert.NoError,
want: 0,
},
{
name: "with-two-second-delay",
envs: map[string]string{
"ARTIFICIAL_DELAY": "2s",
},
fields: fields{
Client: fake.NewClientBuilder().Build(),
runtimePodUID: "",
},
args: args{
ctx: nil,
vClient: &vault.MockRecordingVaultClient{},
o: &secretsv1beta1.VaultDynamicSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "default",
},
Spec: secretsv1beta1.VaultDynamicSecretSpec{
Mount: "baz",
Path: "foo",
Destination: secretsv1beta1.Destination{
Name: "baz",
Create: true,
},
},

Status: secretsv1beta1.VaultDynamicSecretStatus{},
},
},
want: 2,
wantErr: assert.NoError,
},
{
name: "with-broken-env",
envs: map[string]string{
"ARTIFICIAL_DELAY": "rubbish",
},
fields: fields{
Client: fake.NewClientBuilder().Build(),
runtimePodUID: "",
},
args: args{
ctx: nil,
vClient: &vault.MockRecordingVaultClient{},
o: &secretsv1beta1.VaultDynamicSecret{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Namespace: "default",
},
Spec: secretsv1beta1.VaultDynamicSecretSpec{
Mount: "baz",
Path: "foo",
Destination: secretsv1beta1.Destination{
Name: "baz",
Create: true,
},
},

Status: secretsv1beta1.VaultDynamicSecretStatus{},
},
},
want: 60,
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for env, val := range tt.envs {
require.NoError(t, os.Setenv(env, val))
}
r := &VaultDynamicSecretReconciler{
Client: tt.fields.Client,
}
start := time.Now()
_, _, err := r.syncSecret(tt.args.ctx, tt.args.vClient, tt.args.o, nil)
if !tt.wantErr(t, err, fmt.Sprintf("syncSecret(%v, %v, %v, %v)", tt.args.ctx, tt.args.vClient, tt.args.o, nil)) {
return
}

duration := time.Since(start).Round(time.Second).Seconds()

fmt.Println(duration)
if duration != float64(tt.want) {
fmt.Println(duration != float64(tt.want))
t.Error("Sleep length not correct")
}
})
}
}

func Test_computeRelativeHorizonWithJitter(t *testing.T) {
staticNow := time.Unix(nowFunc().Unix(), 0)
defaultNowFunc := func() time.Time { return staticNow }
Expand Down

0 comments on commit d106dd2

Please sign in to comment.