Skip to content

Commit

Permalink
Adds creation of sentinel files for checking provider status
Browse files Browse the repository at this point in the history
  • Loading branch information
diverdane committed Mar 18, 2022
1 parent e25afb8 commit 3ec40b7
Show file tree
Hide file tree
Showing 17 changed files with 720 additions and 285 deletions.
18 changes: 12 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ COPY --from=busybox /bin/whoami /bin/whoami
COPY --from=busybox /bin/mkdir /bin/mkdir
COPY --from=busybox /bin/chmod /bin/chmod
COPY --from=busybox /bin/cat /bin/cat
COPY bin/run-time-scripts /usr/local/bin/

RUN apk add -u shadow libc6-compat && \
# Add limited user
Expand All @@ -94,15 +95,17 @@ RUN apk add -u shadow libc6-compat && \
-r \
secrets-provider && \
# Ensure plugin dir is owned by secrets-provider user
mkdir -p /usr/local/lib/secrets-provider /etc/conjur/ssl /run/conjur && \
mkdir -p /usr/local/lib/secrets-provider /etc/conjur/ssl /run/conjur /conjur/status && \
# Use GID of 0 since that is what OpenShift will want to be able to read things
chown secrets-provider:0 /usr/local/lib/secrets-provider \
/etc/conjur/ssl \
/run/conjur && \
/run/conjur \
/conjur/status && \
# We need open group permissions in these directories since OpenShift won't
# match our UID when we try to write files to them
chmod 770 /etc/conjur/ssl \
/run/conjur
/run/conjur && \
chmod 777 /conjur/status

USER secrets-provider

Expand Down Expand Up @@ -154,17 +157,20 @@ RUN groupadd -r secrets-provider \
-r \
secrets-provider && \
# Ensure plugin dir is owned by secrets-provider user
mkdir -p /usr/local/lib/secrets-provider /etc/conjur/ssl /run/conjur /licenses && \
mkdir -p /usr/local/lib/secrets-provider /etc/conjur/ssl /run/conjur /conjur/status /licenses && \
# Use GID of 0 since that is what OpenShift will want to be able to read things
chown secrets-provider:0 /usr/local/lib/secrets-provider \
/etc/conjur/ssl \
/run/conjur && \
/run/conjur \
/conjur/status && \
# We need open group permissions in these directories since OpenShift won't
# match our UID when we try to write files to them
chmod 770 /etc/conjur/ssl \
/run/conjur
/run/conjur && \
chmod 777 /conjur/status

COPY --from=secrets-provider-builder /opt/secrets-provider-for-k8s/secrets-provider /usr/local/bin/
COPY bin/run-time-scripts /usr/local/bin/

COPY LICENSE.md /licenses

Expand Down
11 changes: 11 additions & 0 deletions bin/run-time-scripts/conjur-secrets-provided
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

# This is a convenient script that can be used in a Kubernetes 'postStart'
# lifecycle hook for the Secrets Provider container in an application
# Deployment manifest. When used in this manner, Kubernetes will hold off on
# starting other containers until after Secrets Provider has provided
# Conjur secrets via secret files or Kubernetes Secrets.

until [ -f /conjur/status/CONJUR_SECRETS_PROVIDED ]; do
sleep 1
done
14 changes: 14 additions & 0 deletions bin/run-time-scripts/conjur-secrets-unchanged
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

# This is a convenient script that can be used in a Kubernetes 'livenessProbe'
# definition for an application container to trigger Kubernetes to restart
# the container. It returns an exit status of '1', and therefore forces a
# failure of the 'livenessProbe' whenever the Secrets Provider has indicated
# that managed secret files or Kubernetes Secrets have been updated
# with new secret values retrieved from Conjur.

cd "$(dirname "$0")"
if [ -f ./CONJUR_SECRETS_UPDATED ]; then
rm ./CONJUR_SECRETS_UPDATED
exit 1
fi
42 changes: 27 additions & 15 deletions cmd/secrets-provider/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/cyberark/secrets-provider-for-k8s/pkg/secrets/annotations"
"github.com/cyberark/secrets-provider-for-k8s/pkg/secrets/clients/conjur"
secretsConfigProvider "github.com/cyberark/secrets-provider-for-k8s/pkg/secrets/config"
k8sSecretsStorage "github.com/cyberark/secrets-provider-for-k8s/pkg/secrets/k8s_secrets_storage"
"github.com/cyberark/secrets-provider-for-k8s/pkg/secrets/pushtofile"
"go.opentelemetry.io/otel/attribute"
)

Expand Down Expand Up @@ -80,8 +82,8 @@ func main() {
return
}

// Gather secrets config and create a retryable Secrets Provider
provideSecrets, _, err := retryableSecretsProvider(ctx, tracer, secretRetriever)
// Gather secrets config and create a repeatable Secrets Provider
provideSecrets, _, err := repeatableSecretsProvider(ctx, tracer, secretRetriever)
if err != nil {
logError(err.Error())
return
Expand Down Expand Up @@ -139,10 +141,10 @@ func secretRetriever(ctx context.Context,
return secretRetriever, nil
}

func retryableSecretsProvider(
func repeatableSecretsProvider(
ctx context.Context,
tracer trace.Tracer,
secretRetriever *conjur.SecretRetriever) (secrets.ProviderFunc, *secretsConfigProvider.Config, error) {
secretRetriever *conjur.SecretRetriever) (secrets.RepeatableProviderFunc, *secretsConfigProvider.Config, error) {

_, span := tracer.Start(ctx, "Create retryable secrets provider")
defer span.End()
Expand All @@ -155,12 +157,18 @@ func retryableSecretsProvider(
return nil, nil, err
}
providerConfig := &secrets.ProviderConfig{
StoreType: secretsConfig.StoreType,
PodNamespace: secretsConfig.PodNamespace,
RequiredK8sSecrets: secretsConfig.RequiredK8sSecrets,
SecretFileBasePath: secretsBasePath,
TemplateFileBasePath: templatesBasePath,
AnnotationsMap: annotationsMap,
CommonProviderConfig: secrets.CommonProviderConfig{
StoreType: secretsConfig.StoreType,
},
K8sProviderConfig: k8sSecretsStorage.K8sProviderConfig{
PodNamespace: secretsConfig.PodNamespace,
RequiredK8sSecrets: secretsConfig.RequiredK8sSecrets,
},
P2FProviderConfig: pushtofile.P2FProviderConfig{
SecretFileBasePath: secretsBasePath,
TemplateFileBasePath: templatesBasePath,
AnnotationsMap: annotationsMap,
},
}

// Tag the span with the secrets provider mode
Expand Down Expand Up @@ -188,13 +196,17 @@ func retryableSecretsProvider(
// on this channel to trigger a graceful shut down of the Secrets Provider.
providerQuit := make(chan struct{})

provideSecrets = secrets.SecretProvider(
secretsConfig.SecretsRefreshInterval,
getContainerMode(),
refreshConfig := secrets.ProviderRefreshConfig{
Mode: getContainerMode(),
SecretRefreshInterval: secretsConfig.SecretsRefreshInterval,
ProviderQuit: providerQuit,
}

repeatableProvideSecrets := secrets.RepeatableSecretProvider(
refreshConfig,
provideSecrets,
providerQuit,
)
return provideSecrets, secretsConfig, nil
return repeatableProvideSecrets, secretsConfig, nil
}

func customEnv(key string) string {
Expand Down
3 changes: 3 additions & 0 deletions pkg/secrets/clients/conjur/conjur_secrets_retriever.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
"github.com/cyberark/secrets-provider-for-k8s/pkg/log/messages"
)

// SecretRetriever implements a Retrieve function that is capable of
// authenticating with Conjur and retrieving multiple Conjur variables
// in bulk.
type SecretRetriever struct {
authn authenticator.Authenticator
}
Expand Down
35 changes: 22 additions & 13 deletions pkg/secrets/k8s_secrets_storage/provide_conjur_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,17 @@ type K8sProvider struct {
traceContext context.Context
}

// K8sProviderConfig provides config specific to Kubernetes Secrets provider
type K8sProviderConfig struct {
PodNamespace string
RequiredK8sSecrets []string
}

// NewProvider creates a new secret provider for K8s Secrets mode.
func NewProvider(
traceContext context.Context,
retrieveConjurSecrets conjur.RetrieveSecretsFunc,
requiredK8sSecrets []string,
podNamespace string,
providerConfig K8sProviderConfig,
) K8sProvider {
return newProvider(
k8sProviderDeps{
Expand All @@ -102,8 +107,8 @@ func NewProvider(
log.Debug,
},
},
requiredK8sSecrets,
podNamespace,
providerConfig.RequiredK8sSecrets,
providerConfig.PodNamespace,
traceContext)
}

Expand Down Expand Up @@ -131,27 +136,28 @@ func newProvider(
}

// Provide implements a ProviderFunc to retrieve and push secrets to K8s secrets.
func (p K8sProvider) Provide() error {
func (p K8sProvider) Provide() (bool, error) {
// Use the global TracerProvider
tr := trace.NewOtelTracer(otel.Tracer("secrets-provider"))
// Retrieve required K8s Secrets and parse their Data fields.
if err := p.retrieveRequiredK8sSecrets(tr); err != nil {
return p.log.recordedError(messages.CSPFK021E)
return false, p.log.recordedError(messages.CSPFK021E)
}

// Retrieve Conjur secrets for all K8s Secrets.
retrievedConjurSecrets, err := p.retrieveConjurSecrets(tr)
if err != nil {
return p.log.recordedError(messages.CSPFK034E, err.Error())
return false, p.log.recordedError(messages.CSPFK034E, err.Error())
}

// Update all K8s Secrets with the retrieved Conjur secrets.
if err = p.updateRequiredK8sSecrets(retrievedConjurSecrets, tr); err != nil {
return p.log.recordedError(messages.CSPFK023E)
updated, err := p.updateRequiredK8sSecrets(retrievedConjurSecrets, tr)
if err != nil {
return updated, p.log.recordedError(messages.CSPFK023E)
}

p.log.info(messages.CSPFK009I)
return nil
return updated, nil
}

// retrieveRequiredK8sSecrets retrieves all K8s Secrets that need to be
Expand Down Expand Up @@ -257,7 +263,9 @@ func (p K8sProvider) retrieveConjurSecrets(tracer trace.Tracer) (map[string][]by
}

func (p K8sProvider) updateRequiredK8sSecrets(
conjurSecrets map[string][]byte, tracer trace.Tracer) error {
conjurSecrets map[string][]byte, tracer trace.Tracer) (bool, error) {

var updated bool

spanCtx, span := tracer.Start(p.traceContext, "Update K8s Secrets")
defer span.End()
Expand Down Expand Up @@ -296,9 +304,10 @@ func (p K8sProvider) updateRequiredK8sSecrets(
// Error messages returned from K8s should be printed only in debug mode
p.log.debug(messages.CSPFK005D, err.Error())
childSpan.RecordErrorAndSetStatus(err)
return p.log.recordedError(messages.CSPFK022E)
return updated, p.log.recordedError(messages.CSPFK022E)
}
updated = true
}

return nil
return updated, nil
}
49 changes: 28 additions & 21 deletions pkg/secrets/k8s_secrets_storage/provide_conjur_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,23 +73,27 @@ func (m testMocks) newProvider(requiredSecrets []string) K8sProvider {
context.Background())
}

type assertFunc func(*testing.T, testMocks, error, string)
type assertFunc func(*testing.T, testMocks, bool, error, string)
type expectedK8sSecrets map[string]map[string]string
type expectedMissingValues map[string][]string

func assertErrorContains(expErrStr string) assertFunc {
return func(t *testing.T, _ testMocks,
err error, desc string) {
updated bool, err error, desc string) {

assert.Error(t, err, desc)
assert.Contains(t, err.Error(), expErrStr, desc)
assert.Error(t, err)
assert.False(t, updated)
assert.Contains(t, err.Error(), expErrStr)
}
}

func assertSecretsUpdated(expK8sSecrets expectedK8sSecrets,
expMissingValues expectedMissingValues) assertFunc {
return func(t *testing.T, mocks testMocks, err error, desc string) {
assert.NoError(t, err, desc)
return func(t *testing.T, mocks testMocks, updated bool,
err error, desc string) {

assert.NoError(t, err)
assert.True(t, updated)
// Check that K8s Secrets contain expected Conjur secret values
for k8sSecretName, expSecretData := range expK8sSecrets {
actualSecretData := mocks.kubeClient.InspectSecret(k8sSecretName)
Expand All @@ -112,10 +116,11 @@ func assertSecretsUpdated(expK8sSecrets expectedK8sSecrets,
}

func assertErrorLogged(msg string, args ...interface{}) assertFunc {
return func(t *testing.T, mocks testMocks, err error, desc string) {
return func(t *testing.T, mocks testMocks, updated bool, err error, desc string) {
errStr := fmt.Sprintf(msg, args...)
newDesc := desc + ", error logged: " + errStr
assert.True(t, mocks.logger.ErrorWasLogged(errStr), newDesc)
assert.False(t, updated)
}
}

Expand Down Expand Up @@ -365,21 +370,23 @@ func TestProvide(t *testing.T) {
}

for _, tc := range testCases {
// Set up test case
mocks := newTestMocks()
mocks.setPermissions(tc.denyConjurRetrieve, tc.denyK8sRetrieve,
tc.denyK8sUpdate)
for secretName, secretData := range tc.k8sSecrets {
mocks.kubeClient.AddSecret(secretName, secretData)
}
provider := mocks.newProvider(tc.requiredSecrets)
t.Run(tc.desc, func(t *testing.T) {
// Set up test case
mocks := newTestMocks()
mocks.setPermissions(tc.denyConjurRetrieve, tc.denyK8sRetrieve,
tc.denyK8sUpdate)
for secretName, secretData := range tc.k8sSecrets {
mocks.kubeClient.AddSecret(secretName, secretData)
}
provider := mocks.newProvider(tc.requiredSecrets)

// Run test case
err := provider.Provide()
// Run test case
updated, err := provider.Provide()

// Confirm results
for _, assert := range tc.asserts {
assert(t, mocks, err, tc.desc)
}
// Confirm results
for _, assert := range tc.asserts {
assert(t, mocks, updated, err, tc.desc)
}
})
}
}
Loading

0 comments on commit 3ec40b7

Please sign in to comment.