pkg/secrets/provide_conjur_secrets.go
package secrets
import (
"context"
"fmt"
"time"
"github.com/cenkalti/backoff"
"github.com/cyberark/conjur-authn-k8s-client/pkg/log"
"github.com/cyberark/secrets-provider-for-k8s/pkg/log/messages"
"github.com/cyberark/secrets-provider-for-k8s/pkg/secrets/clients/conjur"
"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"
"github.com/cyberark/secrets-provider-for-k8s/pkg/utils"
)
const (
secretProviderGracePeriod = time.Duration(10 * time.Millisecond)
)
// CommonProviderConfig provides config that is common to all providers
type CommonProviderConfig struct {
StoreType string
SanitizeEnabled bool
}
// ProviderConfig provides the configuration necessary to create a secrets
// Provider.
type ProviderConfig struct {
CommonProviderConfig
k8sSecretsStorage.K8sProviderConfig
pushtofile.P2FProviderConfig
}
// ProviderFunc describes a function type responsible for providing secrets to
// an unspecified target. It returns either an error, or a flag that indicates
// whether any target secret files or Kubernetes Secrets have been updated.
type ProviderFunc func() (updated bool, err error)
// RepeatableProviderFunc describes a function type that is capable of looping
// indefinitely while providing secrets to unspecified targets.
type RepeatableProviderFunc func() error
// ProviderFactory defines a function type for creating a ProviderFunc given a
// RetrieveSecretsFunc and ProviderConfig.
type ProviderFactory func(traceContent context.Context, secretsRetrieverFunc conjur.RetrieveSecretsFunc, providerConfig ProviderConfig) (ProviderFunc, []error)
// NewProviderForType returns a ProviderFunc responsible for providing secrets in a given mode.
func NewProviderForType(
traceContext context.Context,
secretsRetrieverFunc conjur.RetrieveSecretsFunc,
providerConfig ProviderConfig,
) (ProviderFunc, []error) {
switch providerConfig.StoreType {
case config.K8s:
provider := k8sSecretsStorage.NewProvider(
traceContext,
secretsRetrieverFunc,
providerConfig.CommonProviderConfig.SanitizeEnabled,
providerConfig.K8sProviderConfig,
)
return provider.Provide, nil
case config.File:
provider, err := pushtofile.NewProvider(
secretsRetrieverFunc,
providerConfig.CommonProviderConfig.SanitizeEnabled,
providerConfig.P2FProviderConfig,
)
if err != nil {
return nil, err
}
provider.SetTraceContext(traceContext)
return provider.Provide, nil
default:
return nil, []error{fmt.Errorf(
messages.CSPFK054E,
providerConfig.StoreType,
)}
}
}
// RetryableSecretProvider returns a new ProviderFunc, which wraps the provided ProviderFunc
// in a limitedBackOff-restricted Retry call.
func RetryableSecretProvider(
retryInterval time.Duration,
retryCountLimit int,
provideSecrets ProviderFunc,
) ProviderFunc {
limitedBackOff := utils.NewLimitedBackOff(
retryInterval,
retryCountLimit,
)
return func() (bool, error) {
var updated bool
var retErr error
err := backoff.Retry(func() error {
if limitedBackOff.RetryCount() > 0 {
log.Info(fmt.Sprintf(messages.CSPFK010I, limitedBackOff.RetryCount(), limitedBackOff.RetryLimit))
}
updated, retErr = provideSecrets()
return retErr
}, limitedBackOff)
if err != nil {
log.Error(messages.CSPFK038E, err)
}
return updated, err
}
}
// ProviderRefreshConfig specifies the secret refresh configuration
// for a repeatable secret provider.
type ProviderRefreshConfig struct {
Mode string
SecretRefreshInterval time.Duration
ProviderQuit chan struct{}
}
// RunSecretsProvider takes a retryable ProviderFunc, and runs it in one of three modes:
// - Run once and return (for init or application container modes)
// - Run once and sleep forever (for sidecar mode without periodic refresh)
// - Run periodically (for sidecar mode with periodic refresh)
func RunSecretsProvider(
config ProviderRefreshConfig,
provideSecrets ProviderFunc,
status StatusUpdater,
) error {
var periodicQuit = make(chan struct{})
var periodicError = make(chan error)
var ticker *time.Ticker
var err error
if err = status.CopyScripts(); err != nil {
return err
}
if _, err = provideSecrets(); err != nil {
// Return immediately upon error, regardless of operating mode
return err
}
err = status.SetSecretsProvided()
if err != nil {
return err
}
switch {
case config.Mode != "sidecar":
// Run once and return if not in sidecar mode
return nil
case config.SecretRefreshInterval > 0:
// Run periodically if in sidecar mode with periodic refresh
ticker = time.NewTicker(config.SecretRefreshInterval)
config := periodicConfig{
ticker: ticker,
periodicQuit: periodicQuit,
periodicError: periodicError,
}
go periodicSecretProvider(provideSecrets, config, status)
default:
// Run once and sleep forever if in sidecar mode without
// periodic refresh (fall through)
}
// Wait here for a signal to quit providing secrets or an error
// from the periodicSecretProvider() function
select {
case <-config.ProviderQuit:
break
case err = <-periodicError:
break
}
// Allow the periodicSecretProvider goroutine to gracefully shut down
if config.SecretRefreshInterval > 0 {
// Kill the ticker
ticker.Stop()
periodicQuit <- struct{}{}
// Let the go routine exit
time.Sleep(secretProviderGracePeriod)
}
return err
}
type periodicConfig struct {
ticker *time.Ticker
periodicQuit <-chan struct{}
periodicError chan<- error
}
func periodicSecretProvider(
provideSecrets ProviderFunc,
config periodicConfig,
status StatusUpdater,
) {
for {
select {
case <-config.periodicQuit:
return
case <-config.ticker.C:
updated, err := provideSecrets()
if err == nil && updated {
err = status.SetSecretsUpdated()
}
if err != nil {
config.periodicError <- err
}
}
}
}