Merge branch 'cache-cred-mgr-lookups' of https://github.com/ralekseenkov/concourse into ralekseenkov-cache-cred-mgr-lookups

Signed-off-by: Alex Suraci <suraci.alex@gmail.com>
This commit is contained in:
Alex Suraci 2019-05-01 14:25:50 -04:00
commit f2a3c3a9f5
59 changed files with 1126 additions and 1123 deletions

View File

@ -49,7 +49,7 @@ var (
dbBuildFactory *dbfakes.FakeBuildFactory
dbTeam *dbfakes.FakeTeam
fakeScannerFactory *resourceserverfakes.FakeScannerFactory
fakeVariablesFactory *credsfakes.FakeVariablesFactory
fakeSecretManager *credsfakes.FakeSecrets
credsManagers creds.Managers
interceptTimeoutFactory *containerserverfakes.FakeInterceptTimeoutFactory
interceptTimeout *containerserverfakes.FakeInterceptTimeout
@ -116,7 +116,7 @@ var _ = BeforeEach(func() {
fakeContainerRepository = new(dbfakes.FakeContainerRepository)
fakeDestroyer = new(gcfakes.FakeDestroyer)
fakeVariablesFactory = new(credsfakes.FakeVariablesFactory)
fakeSecretManager = new(credsfakes.FakeSecrets)
credsManagers = make(creds.Managers)
var err error
@ -179,7 +179,7 @@ var _ = BeforeEach(func() {
cliDownloadsDir,
"1.2.3",
"4.5.6",
fakeVariablesFactory,
fakeSecretManager,
credsManagers,
interceptTimeoutFactory,
)

View File

@ -8,10 +8,10 @@ import (
"mime/multipart"
"net/http"
"net/textproto"
"time"
"github.com/concourse/concourse/atc"
"github.com/concourse/concourse/atc/api/accessor/accessorfakes"
"github.com/concourse/concourse/atc/creds/credsfakes"
"github.com/concourse/concourse/atc/creds/noop"
"github.com/concourse/concourse/atc/db"
"github.com/concourse/concourse/atc/db/dbfakes"
@ -737,9 +737,7 @@ jobs:
Context("when the credential exists in the credential manager", func() {
BeforeEach(func() {
fakeVariables := new(credsfakes.FakeVariables)
fakeVariablesFactory.NewVariablesReturns(fakeVariables)
fakeVariables.GetReturns("this-string-value-doesn't-matter", true, nil)
fakeSecretManager.GetReturns("this-string-value-doesn't-matter", nil, true, nil)
})
It("passes validation and saves it un-interpolated", func() {
@ -760,9 +758,7 @@ jobs:
Context("when the credential does not exist in the credential manager", func() {
BeforeEach(func() {
fakeVariables := new(credsfakes.FakeVariables)
fakeVariablesFactory.NewVariablesReturns(fakeVariables)
fakeVariables.GetReturns(nil, false, nil) // nil value, not found, no error
fakeSecretManager.GetReturns(nil, nil, false, nil) // nil value, nil expiration, not found, no error
})
It("returns 400", func() {
@ -776,8 +772,9 @@ jobs:
Context("when a credentials manager is not used", func() {
BeforeEach(func() {
fakeVariables := noop.Noop{}
fakeVariablesFactory.NewVariablesReturns(&fakeVariables)
fakeSecretManager.GetStub = func(secretPath string) (interface{}, *time.Time, bool, error) {
return noop.Noop{}.Get(secretPath)
}
})
It("returns 400", func() {

View File

@ -118,7 +118,7 @@ func (s *Server) SaveConfig(w http.ResponseWriter, r *http.Request) {
teamName := rata.Param(r, "team_name")
if checkCredentials {
variables := s.variablesFactory.NewVariables(teamName, pipelineName)
variables := creds.NewVariables(s.secretManager, teamName, pipelineName)
errs := validateCredParams(variables, config, session)
if errs != nil {

View File

@ -7,19 +7,19 @@ import (
)
type Server struct {
logger lager.Logger
teamFactory db.TeamFactory
variablesFactory creds.VariablesFactory
logger lager.Logger
teamFactory db.TeamFactory
secretManager creds.Secrets
}
func NewServer(
logger lager.Logger,
teamFactory db.TeamFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
) *Server {
return &Server{
logger: logger,
teamFactory: teamFactory,
variablesFactory: variablesFactory,
logger: logger,
teamFactory: teamFactory,
secretManager: secretManager,
}
}

View File

@ -362,10 +362,10 @@ var _ = Describe("Containers API", func() {
_, err := client.Do(req)
Expect(err).NotTo(HaveOccurred())
_, pipelineName, resourceName, variablesFactory := dbTeam.FindCheckContainersArgsForCall(0)
_, pipelineName, resourceName, secretManager := dbTeam.FindCheckContainersArgsForCall(0)
Expect(pipelineName).To(Equal("some-pipeline"))
Expect(resourceName).To(Equal("some-resource"))
Expect(variablesFactory).To(Equal(fakeVariablesFactory))
Expect(secretManager).To(Equal(fakeSecretManager))
})
})
})

View File

@ -21,7 +21,7 @@ func (s *Server) ListContainers(team db.Team) http.Handler {
"params": params,
})
containerLocator, err := createContainerLocatorFromRequest(team, r, s.variablesFactory)
containerLocator, err := createContainerLocatorFromRequest(team, r, s.secretManager)
if err != nil {
hLog.Error("failed-to-parse-request", err)
http.Error(w, err.Error(), http.StatusBadRequest)
@ -59,7 +59,7 @@ type containerLocator interface {
Locate(logger lager.Logger) ([]db.Container, map[int]time.Time, error)
}
func createContainerLocatorFromRequest(team db.Team, r *http.Request, variablesFactory creds.VariablesFactory) (containerLocator, error) {
func createContainerLocatorFromRequest(team db.Team, r *http.Request, secretManager creds.Secrets) (containerLocator, error) {
query := r.URL.Query()
delete(query, ":team_name")
@ -71,10 +71,10 @@ func createContainerLocatorFromRequest(team db.Team, r *http.Request, variablesF
if query.Get("type") == "check" {
return &checkContainerLocator{
team: team,
pipelineName: query.Get("pipeline_name"),
resourceName: query.Get("resource_name"),
variablesFactory: variablesFactory,
team: team,
pipelineName: query.Get("pipeline_name"),
resourceName: query.Get("resource_name"),
secretManager: secretManager,
}, nil
}
@ -132,14 +132,14 @@ func (l *allContainersLocator) Locate(logger lager.Logger) ([]db.Container, map[
}
type checkContainerLocator struct {
team db.Team
pipelineName string
resourceName string
variablesFactory creds.VariablesFactory
team db.Team
pipelineName string
resourceName string
secretManager creds.Secrets
}
func (l *checkContainerLocator) Locate(logger lager.Logger) ([]db.Container, map[int]time.Time, error) {
return l.team.FindCheckContainers(logger, l.pipelineName, l.resourceName, l.variablesFactory)
return l.team.FindCheckContainers(logger, l.pipelineName, l.resourceName, l.secretManager)
}
type stepContainerLocator struct {

View File

@ -12,7 +12,7 @@ type Server struct {
logger lager.Logger
workerClient worker.Client
variablesFactory creds.VariablesFactory
secretManager creds.Secrets
interceptTimeoutFactory InterceptTimeoutFactory
containerRepository db.ContainerRepository
destroyer gc.Destroyer
@ -21,7 +21,7 @@ type Server struct {
func NewServer(
logger lager.Logger,
workerClient worker.Client,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
interceptTimeoutFactory InterceptTimeoutFactory,
containerRepository db.ContainerRepository,
destroyer gc.Destroyer,
@ -29,7 +29,7 @@ func NewServer(
return &Server{
logger: logger,
workerClient: workerClient,
variablesFactory: variablesFactory,
secretManager: secretManager,
interceptTimeoutFactory: interceptTimeoutFactory,
containerRepository: containerRepository,
destroyer: destroyer,

View File

@ -61,7 +61,7 @@ func NewHandler(
cliDownloadsDir string,
version string,
workerVersion string,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
credsManagers creds.Managers,
interceptTimeoutFactory containerserver.InterceptTimeoutFactory,
) (http.Handler, error) {
@ -76,16 +76,17 @@ func NewHandler(
teamHandlerFactory := NewTeamScopedHandlerFactory(logger, dbTeamFactory)
buildServer := buildserver.NewServer(logger, externalURL, dbTeamFactory, dbBuildFactory, eventHandlerFactory)
jobServer := jobserver.NewServer(logger, externalURL, variablesFactory, dbJobFactory)
resourceServer := resourceserver.NewServer(logger, scannerFactory, variablesFactory, dbResourceFactory, dbResourceConfigFactory)
jobServer := jobserver.NewServer(logger, externalURL, secretManager, dbJobFactory)
resourceServer := resourceserver.NewServer(logger, scannerFactory, secretManager, dbResourceFactory, dbResourceConfigFactory)
versionServer := versionserver.NewServer(logger, externalURL)
pipelineServer := pipelineserver.NewServer(logger, dbTeamFactory, dbPipelineFactory, externalURL)
configServer := configserver.NewServer(logger, dbTeamFactory, variablesFactory)
configServer := configserver.NewServer(logger, dbTeamFactory, secretManager)
ccServer := ccserver.NewServer(logger, dbTeamFactory, externalURL)
workerServer := workerserver.NewServer(logger, dbTeamFactory, dbWorkerFactory)
logLevelServer := loglevelserver.NewServer(logger, sink)
cliServer := cliserver.NewServer(logger, absCLIDownloadsDir)
containerServer := containerserver.NewServer(logger, workerClient, variablesFactory, interceptTimeoutFactory, containerRepository, destroyer)
containerServer := containerserver.NewServer(logger, workerClient, secretManager, interceptTimeoutFactory, containerRepository, destroyer)
volumesServer := volumeserver.NewServer(logger, volumeRepository, destroyer)
teamServer := teamserver.NewServer(logger, dbTeamFactory, externalURL)
infoServer := infoserver.NewServer(logger, version, workerVersion, credsManagers)

View File

@ -107,7 +107,7 @@ var _ = Describe("Pipelines API", func() {
fakeaccess.IsAuthenticatedReturns(true)
fakeaccess.IsAdminReturns(true)
ssmAccess := ssm.NewSsm(lager.NewLogger("ssm_test"), &mockService, "alpha", "bogus", nil)
ssmAccess := ssm.NewSsm(lager.NewLogger("ssm_test"), &mockService, nil)
ssmManager := &ssm.SsmManager{
AwsAccessKeyID: "",
AwsSecretAccessKey: "",
@ -193,8 +193,6 @@ var _ = Describe("Pipelines API", func() {
vaultManager := &vault.VaultManager{
URL: credServer.URL(),
PathPrefix: "testpath",
Cache: false,
MaxLease: 60,
TLS: tls,
Auth: authConfig,
}
@ -264,8 +262,6 @@ var _ = Describe("Pipelines API", func() {
"vault": {
"url": "` + credServer.URL() + `",
"path_prefix": "testpath",
"cache": false,
"max_lease": 60,
"ca_cert": "",
"server_name": "server-name",
"auth_backend": "backend-server",
@ -401,7 +397,7 @@ var _ = Describe("Pipelines API", func() {
fakeaccess.IsAuthenticatedReturns(true)
fakeaccess.IsAdminReturns(true)
secretsManagerAccess := secretsmanager.NewSecretsManager(lager.NewLogger("ssm_test"), &mockService, "alpha", "bogus", nil)
secretsManagerAccess := secretsmanager.NewSecretsManager(lager.NewLogger("ssm_test"), &mockService, nil)
secretsManager := &secretsmanager.Manager{
AwsAccessKeyID: "",

View File

@ -10,23 +10,23 @@ import (
type Server struct {
logger lager.Logger
externalURL string
rejector auth.Rejector
variablesFactory creds.VariablesFactory
jobFactory db.JobFactory
externalURL string
rejector auth.Rejector
secretManager creds.Secrets
jobFactory db.JobFactory
}
func NewServer(
logger lager.Logger,
externalURL string,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
jobFactory db.JobFactory,
) *Server {
return &Server{
logger: logger,
externalURL: externalURL,
rejector: auth.UnauthorizedRejector{},
variablesFactory: variablesFactory,
jobFactory: jobFactory,
logger: logger,
externalURL: externalURL,
rejector: auth.UnauthorizedRejector{},
secretManager: secretManager,
jobFactory: jobFactory,
}
}

View File

@ -38,7 +38,7 @@ func (s *Server) CheckResourceWebHook(dbPipeline db.Pipeline) http.Handler {
return
}
variables := s.variablesFactory.NewVariables(dbPipeline.TeamName(), dbPipeline.Name())
variables := creds.NewVariables(s.secretManager, dbPipeline.TeamName(), dbPipeline.Name())
token, err := creds.NewString(variables, pipelineResource.WebhookToken()).Evaluate()
if token != webhookToken {
logger.Info("invalid-token", lager.Data{"error": fmt.Sprintf("invalid token for webhook %s", webhookToken)})

View File

@ -17,7 +17,7 @@ type ScannerFactory interface {
type Server struct {
logger lager.Logger
scannerFactory ScannerFactory
variablesFactory creds.VariablesFactory
secretManager creds.Secrets
resourceFactory db.ResourceFactory
resourceConfigFactory db.ResourceConfigFactory
}
@ -25,14 +25,14 @@ type Server struct {
func NewServer(
logger lager.Logger,
scannerFactory ScannerFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
resourceFactory db.ResourceFactory,
resourceConfigFactory db.ResourceConfigFactory,
) *Server {
return &Server{
logger: logger,
scannerFactory: scannerFactory,
variablesFactory: variablesFactory,
secretManager: secretManager,
resourceFactory: resourceFactory,
resourceConfigFactory: resourceConfigFactory,
}

View File

@ -51,8 +51,8 @@ import (
"github.com/concourse/flag"
"github.com/concourse/retryhttp"
"github.com/cppforlife/go-semi-semantic/version"
multierror "github.com/hashicorp/go-multierror"
flags "github.com/jessevdk/go-flags"
"github.com/hashicorp/go-multierror"
"github.com/jessevdk/go-flags"
"github.com/tedsuo/ifrit"
"github.com/tedsuo/ifrit/grouper"
"github.com/tedsuo/ifrit/http_server"
@ -393,12 +393,12 @@ func (cmd *RunCommand) Runner(positionalArguments []string) (ifrit.Runner, error
return nil, err
}
variablesFactory, err := cmd.variablesFactory(logger)
secretManager, err := cmd.secretManager(logger)
if err != nil {
return nil, err
}
members, err := cmd.constructMembers(logger, reconfigurableSink, apiConn, backendConn, storage, lockFactory, variablesFactory)
members, err := cmd.constructMembers(logger, reconfigurableSink, apiConn, backendConn, storage, lockFactory, secretManager)
if err != nil {
return nil, err
}
@ -440,7 +440,7 @@ func (cmd *RunCommand) constructMembers(
backendConn db.Conn,
storage storage.Storage,
lockFactory lock.LockFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
) ([]grouper.Member, error) {
if cmd.TelemetryOptIn {
url := fmt.Sprintf("http://telemetry.concourse-ci.org/?version=%s", concourse.Version)
@ -452,12 +452,12 @@ func (cmd *RunCommand) constructMembers(
}()
}
apiMembers, err := cmd.constructAPIMembers(logger, reconfigurableSink, apiConn, storage, lockFactory, variablesFactory)
apiMembers, err := cmd.constructAPIMembers(logger, reconfigurableSink, apiConn, storage, lockFactory, secretManager)
if err != nil {
return nil, err
}
backendMembers, err := cmd.constructBackendMembers(logger, backendConn, lockFactory, variablesFactory)
backendMembers, err := cmd.constructBackendMembers(logger, backendConn, lockFactory, secretManager)
if err != nil {
return nil, err
}
@ -471,7 +471,7 @@ func (cmd *RunCommand) constructAPIMembers(
dbConn db.Conn,
storage storage.Storage,
lockFactory lock.LockFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
) ([]grouper.Member, error) {
teamFactory := db.NewTeamFactory(dbConn, lockFactory)
@ -549,7 +549,7 @@ func (cmd *RunCommand) constructAPIMembers(
cmd.ResourceTypeCheckingInterval,
cmd.ResourceCheckingInterval,
cmd.ExternalURL.String(),
variablesFactory,
secretManager,
checkContainerStrategy,
)
@ -577,7 +577,7 @@ func (cmd *RunCommand) constructAPIMembers(
dbResourceConfigFactory,
workerClient,
radarScannerFactory,
variablesFactory,
secretManager,
credsManagers,
accessFactory,
)
@ -664,7 +664,7 @@ func (cmd *RunCommand) constructBackendMembers(
logger lager.Logger,
dbConn db.Conn,
lockFactory lock.LockFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
) ([]grouper.Member, error) {
if cmd.Syslog.Address != "" && cmd.Syslog.Transport == "" {
@ -731,7 +731,7 @@ func (cmd *RunCommand) constructBackendMembers(
resourceFetcher,
dbResourceCacheFactory,
dbResourceConfigFactory,
variablesFactory,
secretManager,
defaultLimits,
buildContainerStrategy,
resourceFactory,
@ -760,7 +760,7 @@ func (cmd *RunCommand) constructBackendMembers(
logger.Session("pipelines"),
dbPipelineFactory,
radarSchedulerFactory,
variablesFactory,
secretManager,
bus,
),
Interval: 10 * time.Second,
@ -857,8 +857,8 @@ func workerVersion() (version.Version, error) {
return version.NewVersionFromString(concourse.WorkerVersion)
}
func (cmd *RunCommand) variablesFactory(logger lager.Logger) (creds.VariablesFactory, error) {
var variablesFactory creds.VariablesFactory = noop.NewNoopFactory()
func (cmd *RunCommand) secretManager(logger lager.Logger) (creds.Secrets, error) {
var secretsFactory creds.SecretsFactory = noop.NewNoopFactory()
for name, manager := range cmd.CredentialManagers {
if !manager.IsConfigured() {
continue
@ -880,14 +880,20 @@ func (cmd *RunCommand) variablesFactory(logger lager.Logger) (creds.VariablesFac
return nil, fmt.Errorf("credential manager '%s' misconfigured: %s", name, err)
}
variablesFactory, err = manager.NewVariablesFactory(credsLogger)
secretsFactory, err = manager.NewSecretsFactory(credsLogger)
if err != nil {
return nil, err
}
break
}
return creds.NewRetryableVariablesFactory(variablesFactory, cmd.CredentialManagement.RetryConfig), nil
result := secretsFactory.NewSecrets()
result = creds.NewRetryableSecrets(result, cmd.CredentialManagement.RetryConfig)
if cmd.CredentialManagement.CacheConfig.Enabled {
result = creds.NewCachedSecrets(result, cmd.CredentialManagement.CacheConfig)
}
return result, nil
}
func (cmd *RunCommand) newKey() *encryption.Key {
@ -1172,7 +1178,7 @@ func (cmd *RunCommand) constructEngine(
resourceFetcher resource.Fetcher,
resourceCacheFactory db.ResourceCacheFactory,
resourceConfigFactory db.ResourceConfigFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
defaultLimits atc.ContainerLimits,
strategy worker.ContainerPlacementStrategy,
resourceFactory resource.ResourceFactory,
@ -1184,7 +1190,7 @@ func (cmd *RunCommand) constructEngine(
resourceFetcher,
resourceCacheFactory,
resourceConfigFactory,
variablesFactory,
secretManager,
defaultLimits,
strategy,
resourceFactory,
@ -1245,7 +1251,7 @@ func (cmd *RunCommand) constructAPIHandler(
resourceConfigFactory db.ResourceConfigFactory,
workerClient worker.Client,
radarScannerFactory radar.ScannerFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
credsManagers creds.Managers,
accessFactory accessor.AccessFactory,
) (http.Handler, error) {
@ -1295,7 +1301,7 @@ func (cmd *RunCommand) constructAPIHandler(
cmd.CLIArtifactsDir.Path(),
concourse.Version,
concourse.WorkerVersion,
variablesFactory,
secretManager,
credsManagers,
containerserver.NewInterceptTimeoutFactory(cmd.InterceptIdleTimeout),
)
@ -1326,14 +1332,14 @@ func (cmd *RunCommand) constructPipelineSyncer(
logger lager.Logger,
pipelineFactory db.PipelineFactory,
radarSchedulerFactory pipelines.RadarSchedulerFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
bus db.NotificationsBus,
) *pipelines.Syncer {
return pipelines.NewSyncer(
logger,
pipelineFactory,
func(pipeline db.Pipeline) ifrit.Runner {
variables := variablesFactory.NewVariables(pipeline.TeamName(), pipeline.Name())
variables := creds.NewVariables(secretManager, pipeline.TeamName(), pipeline.Name())
return grouper.NewParallel(os.Interrupt, grouper.Members{
{
Name: fmt.Sprintf("radar:%d", pipeline.ID()),

View File

@ -0,0 +1,73 @@
package creds
import (
"github.com/patrickmn/go-cache"
"time"
)
type SecretCacheConfig struct {
Enabled bool `long:"secret-cache-enabled" description:"Enable in-memory cache for secrets"`
Duration time.Duration `long:"secret-cache-duration" default:"1m" description:"If the cache is enabled, secret values will be cached for not longer than this duration (it can be less, if underlying secret lease time is smaller)"`
PurgeInterval time.Duration `long:"secret-cache-purge-interval" default:"10m" description:"If the cache is enabled, expired items will be removed on this internal"`
}
type CachedSecrets struct {
secrets Secrets
cacheConfig SecretCacheConfig
cache *cache.Cache
}
type CacheEntry struct {
value interface{}
expiration *time.Time
found bool
}
func NewCachedSecrets(secrets Secrets, cacheConfig SecretCacheConfig) *CachedSecrets {
// Create a cache with:
// - default expiration time for entries set to 'cacheConfig.Duration'
// - purges expired items regularly, on every `cacheConfig.PurgeInterval` after creation
return &CachedSecrets{
secrets: secrets,
cacheConfig: cacheConfig,
cache: cache.New(cacheConfig.Duration, cacheConfig.PurgeInterval),
}
}
func (cs *CachedSecrets) Get(secretPath string) (interface{}, *time.Time, bool, error) {
// if there is a corresponding entry in the cache, return it
entry, found := cs.cache.Get(secretPath)
if found {
result := entry.(CacheEntry)
return result.value, result.expiration, result.found, nil
}
// otherwise, let's make a request to the underlying secret manager
value, expiration, found, err := cs.secrets.Get(secretPath)
// we don't want to cache errors, let the errors be retried the next time around
if err != nil {
return nil, nil, false, err
}
// here we want to cache secret value, expiration, and found flag too
// meaning that "secret not found" responses will be cached too!
entry = CacheEntry{value: value, expiration: expiration, found: found}
// take default cache ttl
duration := cs.cacheConfig.Duration
if expiration != nil {
// if secret lease time expires sooner, make duration smaller than default duration
itemDuration := expiration.Sub(time.Now())
if itemDuration < duration {
duration = itemDuration
}
}
cs.cache.Set(secretPath, entry, duration)
return value, expiration, found, nil
}
func (cs *CachedSecrets) NewSecretLookupPaths(teamName string, pipelineName string) []SecretLookupPath {
return cs.secrets.NewSecretLookupPaths(teamName, pipelineName)
}

View File

@ -0,0 +1,136 @@
package creds_test
import (
"fmt"
"github.com/concourse/concourse/atc/creds"
"github.com/concourse/concourse/atc/creds/credsfakes"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func makeGetStub(name string, value interface{}, expiration *time.Time, found bool, err error, cntReads *int, cntMisses *int) func(string) (interface{}, *time.Time, bool, error) {
return func(secretPath string) (interface{}, *time.Time, bool, error) {
if secretPath == name {
*cntReads++
return value, expiration, found, err
}
*cntMisses++
return nil, nil, false, nil
}
}
var _ = Describe("Caching of secrets", func() {
var secretManager *credsfakes.FakeSecrets
var cachedSecretManager *creds.CachedSecrets
var underlyingReads int
var underlyingMisses int
BeforeEach(func() {
secretManager = new(credsfakes.FakeSecrets)
cachedSecretManager = creds.NewCachedSecrets(secretManager, creds.SecretCacheConfig{
Duration: 2 * time.Second,
PurgeInterval: 100 * time.Millisecond,
})
underlyingReads = 0
underlyingMisses = 0
})
It("should handle missing secrets correctly and cache misses", func() {
secretManager.GetStub = makeGetStub("foo", "value", nil, true, nil, &underlyingReads, &underlyingMisses)
// miss
value, expiration, found, err := cachedSecretManager.Get("bar")
Expect(value).To(BeNil())
Expect(expiration).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).To(BeNil())
Expect(underlyingReads).To(BeIdenticalTo(0))
Expect(underlyingMisses).To(BeIdenticalTo(1))
// cached miss
value, expiration, found, err = cachedSecretManager.Get("bar")
Expect(value).To(BeNil())
Expect(expiration).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).To(BeNil())
Expect(underlyingReads).To(BeIdenticalTo(0))
Expect(underlyingMisses).To(BeIdenticalTo(1))
})
It("should handle existing secrets correctly and cache them, returning previous value if the underlying value has changed", func() {
secretManager.GetStub = makeGetStub("foo", "value", nil, true, nil, &underlyingReads, &underlyingMisses)
// hit
value, expiration, found, err := cachedSecretManager.Get("foo")
Expect(value).To(BeIdenticalTo("value"))
Expect(expiration).To(BeNil())
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
Expect(underlyingReads).To(BeIdenticalTo(1))
Expect(underlyingMisses).To(BeIdenticalTo(0))
// cached hit
secretManager.GetStub = makeGetStub("foo", "different-value", nil, true, nil, &underlyingReads, &underlyingMisses)
value, expiration, found, err = cachedSecretManager.Get("foo")
Expect(value).To(BeIdenticalTo("value"))
Expect(expiration).To(BeNil())
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
Expect(underlyingReads).To(BeIdenticalTo(1))
Expect(underlyingMisses).To(BeIdenticalTo(0))
})
It("should handle errors correctly and avoid caching errors", func() {
secretManager.GetStub = makeGetStub("baz", nil, nil, false, fmt.Errorf("unexpected error"), &underlyingReads, &underlyingMisses)
// error
value, expiration, found, err := cachedSecretManager.Get("baz")
Expect(value).To(BeNil())
Expect(expiration).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).NotTo(BeNil())
Expect(underlyingReads).To(BeIdenticalTo(1))
Expect(underlyingMisses).To(BeIdenticalTo(0))
// no caching of error
value, expiration, found, err = cachedSecretManager.Get("baz")
Expect(value).To(BeNil())
Expect(expiration).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).NotTo(BeNil())
Expect(underlyingReads).To(BeIdenticalTo(2))
Expect(underlyingMisses).To(BeIdenticalTo(0))
})
It("should re-retrieve expired entries", func() {
secretManager.GetStub = makeGetStub("foo", "value", nil, true, nil, &underlyingReads, &underlyingMisses)
// get few entries first
_, _, _, _ = cachedSecretManager.Get("foo")
_, _, _, _ = cachedSecretManager.Get("bar")
_, _, _, _ = cachedSecretManager.Get("baz")
Expect(underlyingReads).To(BeIdenticalTo(1))
Expect(underlyingMisses).To(BeIdenticalTo(2))
// get these entries again and make sure they are cached
_, _, _, _ = cachedSecretManager.Get("foo")
_, _, _, _ = cachedSecretManager.Get("bar")
_, _, _, _ = cachedSecretManager.Get("baz")
Expect(underlyingReads).To(BeIdenticalTo(1))
Expect(underlyingMisses).To(BeIdenticalTo(2))
// sleep
time.Sleep(3 * time.Second)
// check counters again and make sure the entries are re-retrieved
_, _, _, _ = cachedSecretManager.Get("foo")
_, _, _, _ = cachedSecretManager.Get("bar")
_, _, _, _ = cachedSecretManager.Get("baz")
Expect(underlyingReads).To(BeIdenticalTo(2))
Expect(underlyingMisses).To(BeIdenticalTo(4))
})
})

View File

@ -1,50 +1,48 @@
package credhub
import (
"github.com/concourse/concourse/atc/creds"
"path"
"time"
"code.cloudfoundry.org/credhub-cli/credhub"
"code.cloudfoundry.org/credhub-cli/credhub/credentials"
"code.cloudfoundry.org/lager"
"github.com/cloudfoundry/bosh-cli/director/template"
)
type CredHubAtc struct {
CredHub *LazyCredhub
logger lager.Logger
PathPrefix string
TeamName string
PipelineName string
prefix string
}
func (c CredHubAtc) Get(varDef template.VariableDefinition) (interface{}, bool, error) {
// NewSecretLookupPaths defines how variables will be searched in the underlying secret manager
func (c CredHubAtc) NewSecretLookupPaths(teamName string, pipelineName string) []creds.SecretLookupPath {
lookupPaths := []creds.SecretLookupPath{}
if len(pipelineName) > 0 {
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(path.Join(c.prefix, teamName, pipelineName)+"/"))
}
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(path.Join(c.prefix, teamName)+"/"))
return lookupPaths
}
// Get retrieves the value and expiration of an individual secret
func (c CredHubAtc) Get(secretPath string) (interface{}, *time.Time, bool, error) {
var cred credentials.Credential
var found bool
var err error
if c.PipelineName != "" {
path := c.path(c.TeamName, c.PipelineName, varDef.Name)
cred, found, err = c.findCred(path)
if err != nil {
c.logger.Error("could not find cred", err)
return nil, false, err
}
cred, found, err = c.findCred(secretPath)
if err != nil {
c.logger.Error("unable to retrieve credhub secret", err)
return nil, nil, false, err
}
if !found {
cred, found, err = c.findCred(c.path(c.TeamName, varDef.Name))
if err != nil {
c.logger.Error("could not find cred", err)
return nil, false, err
}
return nil, nil, false, nil
}
if !found {
return nil, false, nil
}
var result interface{} = cred.Value
var result = cred.Value
if standardMap, ok := cred.Value.(map[string]interface{}); ok {
// TODO - we should do this recursively since the cpp4life go-path library
@ -59,7 +57,7 @@ func (c CredHubAtc) Get(varDef template.VariableDefinition) (interface{}, bool,
result = evenLessTyped
}
return result, true, nil
return result, nil, true, nil
}
func (c CredHubAtc) findCred(path string) (credentials.Credential, bool, error) {
@ -87,14 +85,3 @@ func (c CredHubAtc) findCred(path string) (credentials.Credential, bool, error)
return cred, true, nil
}
func (c CredHubAtc) path(segments ...string) string {
return path.Join(append([]string{c.PathPrefix}, segments...)...)
}
func (c CredHubAtc) List() ([]template.VariableDefinition, error) {
// not implemented, see vault implementation
return []template.VariableDefinition{}, nil
}
var _ template.Variables = new(CredHubAtc)

View File

@ -2,7 +2,6 @@ package credhub
import (
"code.cloudfoundry.org/lager"
"github.com/concourse/concourse/atc/creds"
)
@ -20,12 +19,10 @@ func NewCredHubFactory(logger lager.Logger, credhub *LazyCredhub, prefix string)
}
}
func (factory *credhubFactory) NewVariables(teamName string, pipelineName string) creds.Variables {
func (factory *credhubFactory) NewSecrets() creds.Secrets {
return &CredHubAtc{
CredHub: factory.credhub,
PathPrefix: factory.prefix,
TeamName: teamName,
logger: factory.logger,
PipelineName: pipelineName,
CredHub: factory.credhub,
logger: factory.logger,
prefix: factory.prefix,
}
}

View File

@ -148,7 +148,7 @@ func (manager CredHubManager) Health() (*creds.HealthResponse, error) {
return healthResponse, nil
}
func (manager CredHubManager) NewVariablesFactory(logger lager.Logger) (creds.VariablesFactory, error) {
func (manager CredHubManager) NewSecretsFactory(logger lager.Logger) (creds.SecretsFactory, error) {
return NewCredHubFactory(logger, manager.Client, manager.PathPrefix), nil
}

View File

@ -0,0 +1,201 @@
// Code generated by counterfeiter. DO NOT EDIT.
package credsfakes
import (
"sync"
"time"
"github.com/concourse/concourse/atc/creds"
)
type FakeSecrets struct {
GetStub func(string) (interface{}, *time.Time, bool, error)
getMutex sync.RWMutex
getArgsForCall []struct {
arg1 string
}
getReturns struct {
result1 interface{}
result2 *time.Time
result3 bool
result4 error
}
getReturnsOnCall map[int]struct {
result1 interface{}
result2 *time.Time
result3 bool
result4 error
}
NewSecretLookupPathsStub func(string, string) []creds.SecretLookupPath
newSecretLookupPathsMutex sync.RWMutex
newSecretLookupPathsArgsForCall []struct {
arg1 string
arg2 string
}
newSecretLookupPathsReturns struct {
result1 []creds.SecretLookupPath
}
newSecretLookupPathsReturnsOnCall map[int]struct {
result1 []creds.SecretLookupPath
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeSecrets) Get(arg1 string) (interface{}, *time.Time, bool, error) {
fake.getMutex.Lock()
ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)]
fake.getArgsForCall = append(fake.getArgsForCall, struct {
arg1 string
}{arg1})
fake.recordInvocation("Get", []interface{}{arg1})
fake.getMutex.Unlock()
if fake.GetStub != nil {
return fake.GetStub(arg1)
}
if specificReturn {
return ret.result1, ret.result2, ret.result3, ret.result4
}
fakeReturns := fake.getReturns
return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3, fakeReturns.result4
}
func (fake *FakeSecrets) GetCallCount() int {
fake.getMutex.RLock()
defer fake.getMutex.RUnlock()
return len(fake.getArgsForCall)
}
func (fake *FakeSecrets) GetCalls(stub func(string) (interface{}, *time.Time, bool, error)) {
fake.getMutex.Lock()
defer fake.getMutex.Unlock()
fake.GetStub = stub
}
func (fake *FakeSecrets) GetArgsForCall(i int) string {
fake.getMutex.RLock()
defer fake.getMutex.RUnlock()
argsForCall := fake.getArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeSecrets) GetReturns(result1 interface{}, result2 *time.Time, result3 bool, result4 error) {
fake.getMutex.Lock()
defer fake.getMutex.Unlock()
fake.GetStub = nil
fake.getReturns = struct {
result1 interface{}
result2 *time.Time
result3 bool
result4 error
}{result1, result2, result3, result4}
}
func (fake *FakeSecrets) GetReturnsOnCall(i int, result1 interface{}, result2 *time.Time, result3 bool, result4 error) {
fake.getMutex.Lock()
defer fake.getMutex.Unlock()
fake.GetStub = nil
if fake.getReturnsOnCall == nil {
fake.getReturnsOnCall = make(map[int]struct {
result1 interface{}
result2 *time.Time
result3 bool
result4 error
})
}
fake.getReturnsOnCall[i] = struct {
result1 interface{}
result2 *time.Time
result3 bool
result4 error
}{result1, result2, result3, result4}
}
func (fake *FakeSecrets) NewSecretLookupPaths(arg1 string, arg2 string) []creds.SecretLookupPath {
fake.newSecretLookupPathsMutex.Lock()
ret, specificReturn := fake.newSecretLookupPathsReturnsOnCall[len(fake.newSecretLookupPathsArgsForCall)]
fake.newSecretLookupPathsArgsForCall = append(fake.newSecretLookupPathsArgsForCall, struct {
arg1 string
arg2 string
}{arg1, arg2})
fake.recordInvocation("NewSecretLookupPaths", []interface{}{arg1, arg2})
fake.newSecretLookupPathsMutex.Unlock()
if fake.NewSecretLookupPathsStub != nil {
return fake.NewSecretLookupPathsStub(arg1, arg2)
}
if specificReturn {
return ret.result1
}
fakeReturns := fake.newSecretLookupPathsReturns
return fakeReturns.result1
}
func (fake *FakeSecrets) NewSecretLookupPathsCallCount() int {
fake.newSecretLookupPathsMutex.RLock()
defer fake.newSecretLookupPathsMutex.RUnlock()
return len(fake.newSecretLookupPathsArgsForCall)
}
func (fake *FakeSecrets) NewSecretLookupPathsCalls(stub func(string, string) []creds.SecretLookupPath) {
fake.newSecretLookupPathsMutex.Lock()
defer fake.newSecretLookupPathsMutex.Unlock()
fake.NewSecretLookupPathsStub = stub
}
func (fake *FakeSecrets) NewSecretLookupPathsArgsForCall(i int) (string, string) {
fake.newSecretLookupPathsMutex.RLock()
defer fake.newSecretLookupPathsMutex.RUnlock()
argsForCall := fake.newSecretLookupPathsArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeSecrets) NewSecretLookupPathsReturns(result1 []creds.SecretLookupPath) {
fake.newSecretLookupPathsMutex.Lock()
defer fake.newSecretLookupPathsMutex.Unlock()
fake.NewSecretLookupPathsStub = nil
fake.newSecretLookupPathsReturns = struct {
result1 []creds.SecretLookupPath
}{result1}
}
func (fake *FakeSecrets) NewSecretLookupPathsReturnsOnCall(i int, result1 []creds.SecretLookupPath) {
fake.newSecretLookupPathsMutex.Lock()
defer fake.newSecretLookupPathsMutex.Unlock()
fake.NewSecretLookupPathsStub = nil
if fake.newSecretLookupPathsReturnsOnCall == nil {
fake.newSecretLookupPathsReturnsOnCall = make(map[int]struct {
result1 []creds.SecretLookupPath
})
}
fake.newSecretLookupPathsReturnsOnCall[i] = struct {
result1 []creds.SecretLookupPath
}{result1}
}
func (fake *FakeSecrets) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.getMutex.RLock()
defer fake.getMutex.RUnlock()
fake.newSecretLookupPathsMutex.RLock()
defer fake.newSecretLookupPathsMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeSecrets) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ creds.Secrets = new(FakeSecrets)

View File

@ -0,0 +1,101 @@
// Code generated by counterfeiter. DO NOT EDIT.
package credsfakes
import (
"sync"
"github.com/concourse/concourse/atc/creds"
)
type FakeSecretsFactory struct {
NewSecretsStub func() creds.Secrets
newSecretsMutex sync.RWMutex
newSecretsArgsForCall []struct {
}
newSecretsReturns struct {
result1 creds.Secrets
}
newSecretsReturnsOnCall map[int]struct {
result1 creds.Secrets
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeSecretsFactory) NewSecrets() creds.Secrets {
fake.newSecretsMutex.Lock()
ret, specificReturn := fake.newSecretsReturnsOnCall[len(fake.newSecretsArgsForCall)]
fake.newSecretsArgsForCall = append(fake.newSecretsArgsForCall, struct {
}{})
fake.recordInvocation("NewSecrets", []interface{}{})
fake.newSecretsMutex.Unlock()
if fake.NewSecretsStub != nil {
return fake.NewSecretsStub()
}
if specificReturn {
return ret.result1
}
fakeReturns := fake.newSecretsReturns
return fakeReturns.result1
}
func (fake *FakeSecretsFactory) NewSecretsCallCount() int {
fake.newSecretsMutex.RLock()
defer fake.newSecretsMutex.RUnlock()
return len(fake.newSecretsArgsForCall)
}
func (fake *FakeSecretsFactory) NewSecretsCalls(stub func() creds.Secrets) {
fake.newSecretsMutex.Lock()
defer fake.newSecretsMutex.Unlock()
fake.NewSecretsStub = stub
}
func (fake *FakeSecretsFactory) NewSecretsReturns(result1 creds.Secrets) {
fake.newSecretsMutex.Lock()
defer fake.newSecretsMutex.Unlock()
fake.NewSecretsStub = nil
fake.newSecretsReturns = struct {
result1 creds.Secrets
}{result1}
}
func (fake *FakeSecretsFactory) NewSecretsReturnsOnCall(i int, result1 creds.Secrets) {
fake.newSecretsMutex.Lock()
defer fake.newSecretsMutex.Unlock()
fake.NewSecretsStub = nil
if fake.newSecretsReturnsOnCall == nil {
fake.newSecretsReturnsOnCall = make(map[int]struct {
result1 creds.Secrets
})
}
fake.newSecretsReturnsOnCall[i] = struct {
result1 creds.Secrets
}{result1}
}
func (fake *FakeSecretsFactory) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.newSecretsMutex.RLock()
defer fake.newSecretsMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeSecretsFactory) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ creds.SecretsFactory = new(FakeSecretsFactory)

View File

@ -1,190 +0,0 @@
// Code generated by counterfeiter. DO NOT EDIT.
package credsfakes
import (
"sync"
"github.com/cloudfoundry/bosh-cli/director/template"
"github.com/concourse/concourse/atc/creds"
)
type FakeVariables struct {
GetStub func(template.VariableDefinition) (interface{}, bool, error)
getMutex sync.RWMutex
getArgsForCall []struct {
arg1 template.VariableDefinition
}
getReturns struct {
result1 interface{}
result2 bool
result3 error
}
getReturnsOnCall map[int]struct {
result1 interface{}
result2 bool
result3 error
}
ListStub func() ([]template.VariableDefinition, error)
listMutex sync.RWMutex
listArgsForCall []struct {
}
listReturns struct {
result1 []template.VariableDefinition
result2 error
}
listReturnsOnCall map[int]struct {
result1 []template.VariableDefinition
result2 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeVariables) Get(arg1 template.VariableDefinition) (interface{}, bool, error) {
fake.getMutex.Lock()
ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)]
fake.getArgsForCall = append(fake.getArgsForCall, struct {
arg1 template.VariableDefinition
}{arg1})
fake.recordInvocation("Get", []interface{}{arg1})
fake.getMutex.Unlock()
if fake.GetStub != nil {
return fake.GetStub(arg1)
}
if specificReturn {
return ret.result1, ret.result2, ret.result3
}
fakeReturns := fake.getReturns
return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3
}
func (fake *FakeVariables) GetCallCount() int {
fake.getMutex.RLock()
defer fake.getMutex.RUnlock()
return len(fake.getArgsForCall)
}
func (fake *FakeVariables) GetCalls(stub func(template.VariableDefinition) (interface{}, bool, error)) {
fake.getMutex.Lock()
defer fake.getMutex.Unlock()
fake.GetStub = stub
}
func (fake *FakeVariables) GetArgsForCall(i int) template.VariableDefinition {
fake.getMutex.RLock()
defer fake.getMutex.RUnlock()
argsForCall := fake.getArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeVariables) GetReturns(result1 interface{}, result2 bool, result3 error) {
fake.getMutex.Lock()
defer fake.getMutex.Unlock()
fake.GetStub = nil
fake.getReturns = struct {
result1 interface{}
result2 bool
result3 error
}{result1, result2, result3}
}
func (fake *FakeVariables) GetReturnsOnCall(i int, result1 interface{}, result2 bool, result3 error) {
fake.getMutex.Lock()
defer fake.getMutex.Unlock()
fake.GetStub = nil
if fake.getReturnsOnCall == nil {
fake.getReturnsOnCall = make(map[int]struct {
result1 interface{}
result2 bool
result3 error
})
}
fake.getReturnsOnCall[i] = struct {
result1 interface{}
result2 bool
result3 error
}{result1, result2, result3}
}
func (fake *FakeVariables) List() ([]template.VariableDefinition, error) {
fake.listMutex.Lock()
ret, specificReturn := fake.listReturnsOnCall[len(fake.listArgsForCall)]
fake.listArgsForCall = append(fake.listArgsForCall, struct {
}{})
fake.recordInvocation("List", []interface{}{})
fake.listMutex.Unlock()
if fake.ListStub != nil {
return fake.ListStub()
}
if specificReturn {
return ret.result1, ret.result2
}
fakeReturns := fake.listReturns
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakeVariables) ListCallCount() int {
fake.listMutex.RLock()
defer fake.listMutex.RUnlock()
return len(fake.listArgsForCall)
}
func (fake *FakeVariables) ListCalls(stub func() ([]template.VariableDefinition, error)) {
fake.listMutex.Lock()
defer fake.listMutex.Unlock()
fake.ListStub = stub
}
func (fake *FakeVariables) ListReturns(result1 []template.VariableDefinition, result2 error) {
fake.listMutex.Lock()
defer fake.listMutex.Unlock()
fake.ListStub = nil
fake.listReturns = struct {
result1 []template.VariableDefinition
result2 error
}{result1, result2}
}
func (fake *FakeVariables) ListReturnsOnCall(i int, result1 []template.VariableDefinition, result2 error) {
fake.listMutex.Lock()
defer fake.listMutex.Unlock()
fake.ListStub = nil
if fake.listReturnsOnCall == nil {
fake.listReturnsOnCall = make(map[int]struct {
result1 []template.VariableDefinition
result2 error
})
}
fake.listReturnsOnCall[i] = struct {
result1 []template.VariableDefinition
result2 error
}{result1, result2}
}
func (fake *FakeVariables) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.getMutex.RLock()
defer fake.getMutex.RUnlock()
fake.listMutex.RLock()
defer fake.listMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeVariables) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ creds.Variables = new(FakeVariables)

View File

@ -1,112 +0,0 @@
// Code generated by counterfeiter. DO NOT EDIT.
package credsfakes
import (
"sync"
"github.com/concourse/concourse/atc/creds"
)
type FakeVariablesFactory struct {
NewVariablesStub func(string, string) creds.Variables
newVariablesMutex sync.RWMutex
newVariablesArgsForCall []struct {
arg1 string
arg2 string
}
newVariablesReturns struct {
result1 creds.Variables
}
newVariablesReturnsOnCall map[int]struct {
result1 creds.Variables
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeVariablesFactory) NewVariables(arg1 string, arg2 string) creds.Variables {
fake.newVariablesMutex.Lock()
ret, specificReturn := fake.newVariablesReturnsOnCall[len(fake.newVariablesArgsForCall)]
fake.newVariablesArgsForCall = append(fake.newVariablesArgsForCall, struct {
arg1 string
arg2 string
}{arg1, arg2})
fake.recordInvocation("NewVariables", []interface{}{arg1, arg2})
fake.newVariablesMutex.Unlock()
if fake.NewVariablesStub != nil {
return fake.NewVariablesStub(arg1, arg2)
}
if specificReturn {
return ret.result1
}
fakeReturns := fake.newVariablesReturns
return fakeReturns.result1
}
func (fake *FakeVariablesFactory) NewVariablesCallCount() int {
fake.newVariablesMutex.RLock()
defer fake.newVariablesMutex.RUnlock()
return len(fake.newVariablesArgsForCall)
}
func (fake *FakeVariablesFactory) NewVariablesCalls(stub func(string, string) creds.Variables) {
fake.newVariablesMutex.Lock()
defer fake.newVariablesMutex.Unlock()
fake.NewVariablesStub = stub
}
func (fake *FakeVariablesFactory) NewVariablesArgsForCall(i int) (string, string) {
fake.newVariablesMutex.RLock()
defer fake.newVariablesMutex.RUnlock()
argsForCall := fake.newVariablesArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeVariablesFactory) NewVariablesReturns(result1 creds.Variables) {
fake.newVariablesMutex.Lock()
defer fake.newVariablesMutex.Unlock()
fake.NewVariablesStub = nil
fake.newVariablesReturns = struct {
result1 creds.Variables
}{result1}
}
func (fake *FakeVariablesFactory) NewVariablesReturnsOnCall(i int, result1 creds.Variables) {
fake.newVariablesMutex.Lock()
defer fake.newVariablesMutex.Unlock()
fake.NewVariablesStub = nil
if fake.newVariablesReturnsOnCall == nil {
fake.newVariablesReturnsOnCall = make(map[int]struct {
result1 creds.Variables
})
}
fake.newVariablesReturnsOnCall[i] = struct {
result1 creds.Variables
}{result1}
}
func (fake *FakeVariablesFactory) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.newVariablesMutex.RLock()
defer fake.newVariablesMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}
func (fake *FakeVariablesFactory) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ creds.VariablesFactory = new(FakeVariablesFactory)

View File

@ -2,8 +2,12 @@ package kubernetes
import (
"code.cloudfoundry.org/lager"
"fmt"
"github.com/concourse/concourse/atc/creds"
"path"
"strings"
"time"
"github.com/cloudfoundry/bosh-cli/director/template"
v1 "k8s.io/api/core/v1"
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -12,48 +16,56 @@ import (
type Kubernetes struct {
Clientset *kubernetes.Clientset
TeamName string
PipelineName string
NamespacePrefix string
logger lager.Logger
namespacePrefix string
}
func (k Kubernetes) Get(varDef template.VariableDefinition) (interface{}, bool, error) {
var namespace = k.NamespacePrefix + k.TeamName
var pipelineSecretName = k.PipelineName + "." + varDef.Name
var secretName = varDef.Name
// NewSecretLookupPaths defines how variables will be searched in the underlying secret manager
func (k Kubernetes) NewSecretLookupPaths(teamName string, pipelineName string) []creds.SecretLookupPath {
lookupPaths := []creds.SecretLookupPath{}
if len(pipelineName) > 0 {
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(path.Join(k.namespacePrefix, teamName, pipelineName)+"/"))
}
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(path.Join(k.namespacePrefix, teamName)+"/"))
return lookupPaths
}
secret, found, err := k.findSecret(namespace, pipelineSecretName)
if !found && err == nil {
secret, found, err = k.findSecret(namespace, secretName)
// Get retrieves the value and expiration of an individual secret
func (k Kubernetes) Get(secretPath string) (interface{}, *time.Time, bool, error) {
parts := strings.Split(secretPath, ":")
if len(parts) != 2 {
return nil, nil, false, fmt.Errorf("unable to split kubernetes secret path into [namespace]:[secret]: %s", secretPath)
}
var namespace = parts[0]
var secretName = parts[1]
secret, found, err := k.findSecret(namespace, secretName)
if err != nil {
k.logger.Error("k8s-secret-error", err, lager.Data{
"namespace": namespace,
"pipelineSecretName": pipelineSecretName,
"secretName": secretName,
k.logger.Error("unable to retrieve kubernetes secret", err, lager.Data{
"namespace": namespace,
"secretName": secretName,
})
return nil, false, err
return nil, nil, false, err
}
if found {
return k.getValueFromSecret(secret)
}
k.logger.Info("k8s-secret-not-found", lager.Data{
"namespace": namespace,
"pipelineSecretName": pipelineSecretName,
"secretName": secretName,
k.logger.Info("kubernetes secret not found", lager.Data{
"namespace": namespace,
"secretName": secretName,
})
return nil, false, nil
return nil, nil, false, nil
}
func (k Kubernetes) getValueFromSecret(secret *v1.Secret) (interface{}, bool, error) {
func (k Kubernetes) getValueFromSecret(secret *v1.Secret) (interface{}, *time.Time, bool, error) {
val, found := secret.Data["value"]
if found {
return string(val), true, nil
return string(val), nil, true, nil
}
evenLessTyped := map[interface{}]interface{}{}
@ -61,7 +73,7 @@ func (k Kubernetes) getValueFromSecret(secret *v1.Secret) (interface{}, bool, er
evenLessTyped[k] = string(v)
}
return evenLessTyped, true, nil
return evenLessTyped, nil, true, nil
}
func (k Kubernetes) findSecret(namespace, name string) (*v1.Secret, bool, error) {
@ -78,9 +90,3 @@ func (k Kubernetes) findSecret(namespace, name string) (*v1.Secret, bool, error)
return secret, true, err
}
}
func (k Kubernetes) List() ([]template.VariableDefinition, error) {
// Unimplemented for Kubernetes secrets
return []template.VariableDefinition{}, nil
}

View File

@ -23,12 +23,20 @@ func NewKubernetesFactory(logger lager.Logger, clientset *kubernetes.Clientset,
return factory
}
func (factory *kubernetesFactory) NewVariables(teamName string, pipelineName string) creds.Variables {
func (factory *kubernetesFactory) NewSecrets() creds.Secrets {
return &Kubernetes{
Clientset: factory.clientset,
TeamName: teamName,
PipelineName: pipelineName,
NamespacePrefix: factory.namespacePrefix,
logger: factory.logger,
namespacePrefix: factory.namespacePrefix,
}
}
// NewSecretLookupPaths defines how variables will be searched in the underlying secret manager
func (factory *kubernetesFactory) NewSecretLookupPaths(teamName string, pipelineName string) []creds.SecretLookupPath {
lookupPaths := []creds.SecretLookupPath{}
if len(pipelineName) > 0 {
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(factory.namespacePrefix+teamName+":"+pipelineName+"."))
}
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(factory.namespacePrefix+teamName+":"))
return lookupPaths
}

View File

@ -55,7 +55,7 @@ func (manager KubernetesManager) Validate() error {
return err
}
func (manager KubernetesManager) NewVariablesFactory(logger lager.Logger) (creds.VariablesFactory, error) {
func (manager KubernetesManager) NewSecretsFactory(logger lager.Logger) (creds.SecretsFactory, error) {
config, err := manager.buildConfig()
if err != nil {
return nil, err

View File

@ -2,7 +2,7 @@ package creds
import (
"code.cloudfoundry.org/lager"
flags "github.com/jessevdk/go-flags"
"github.com/jessevdk/go-flags"
)
type Manager interface {
@ -11,7 +11,7 @@ type Manager interface {
Health() (*HealthResponse, error)
Init(lager.Logger) error
NewVariablesFactory(lager.Logger) (VariablesFactory, error)
NewSecretsFactory(lager.Logger) (SecretsFactory, error)
}
type ManagerFactory interface {
@ -22,6 +22,7 @@ type Managers map[string]Manager
type CredentialManagementConfig struct {
RetryConfig SecretRetryConfig
CacheConfig SecretCacheConfig
}
type HealthResponse struct {

View File

@ -1,13 +1,16 @@
package noop
import "github.com/cloudfoundry/bosh-cli/director/template"
import (
"github.com/concourse/concourse/atc/creds"
"time"
)
type Noop struct{}
func (n Noop) Get(varDef template.VariableDefinition) (interface{}, bool, error) {
return nil, false, nil
func (n Noop) NewSecretLookupPaths(string, string) []creds.SecretLookupPath {
return []creds.SecretLookupPath{}
}
func (n Noop) List() ([]template.VariableDefinition, error) {
return []template.VariableDefinition{}, nil
func (n Noop) Get(secretPath string) (interface{}, *time.Time, bool, error) {
return nil, nil, false, nil
}

View File

@ -8,6 +8,6 @@ func NewNoopFactory() *noopFactory {
return &noopFactory{}
}
func (*noopFactory) NewVariables(string, string) creds.Variables {
func (*noopFactory) NewSecrets() creds.Secrets {
return &Noop{}
}

View File

@ -1,8 +1,8 @@
package noop_test
import (
"github.com/cloudfoundry/bosh-cli/director/template"
. "github.com/concourse/concourse/atc/creds/noop"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -17,15 +17,17 @@ var _ = Describe("Noop", func() {
Describe("Get", func() {
var val interface{}
var expiration *time.Time
var found bool
var getErr error
JustBeforeEach(func() {
val, found, getErr = noop.Get(template.VariableDefinition{})
val, expiration, found, getErr = noop.Get("foo")
})
It("never locates the variable", func() {
Expect(val).To(BeNil())
Expect(expiration).To(BeNil())
Expect(found).To(BeFalse())
Expect(getErr).ToNot(HaveOccurred())
})

View File

@ -0,0 +1,44 @@
package creds
import (
"fmt"
"github.com/concourse/retryhttp"
"time"
)
type SecretRetryConfig struct {
Attempts int `long:"secret-retry-attempts" default:"5" description:"The number of attempts secret will be retried to be fetched, in case a retryable error happens."`
Interval time.Duration `long:"secret-retry-interval" default:"1s" description:"The interval between secret retry retrieval attempts."`
}
type RetryableSecrets struct {
secrets Secrets
retryConfig SecretRetryConfig
}
func NewRetryableSecrets(secrets Secrets, retryConfig SecretRetryConfig) Secrets {
return &RetryableSecrets{secrets: secrets, retryConfig: retryConfig}
}
// Get retrieves the value and expiration of an individual secret
func (rs RetryableSecrets) Get(secretPath string) (interface{}, *time.Time, bool, error) {
r := &retryhttp.DefaultRetryer{}
for i := 0; i < rs.retryConfig.Attempts-1; i++ {
result, expiration, exists, err := rs.secrets.Get(secretPath)
if err != nil && r.IsRetryable(err) {
time.Sleep(rs.retryConfig.Interval)
continue
}
return result, expiration, exists, err
}
result, expiration, exists, err := rs.secrets.Get(secretPath)
if err != nil {
err = fmt.Errorf("%s (after %d retries)", err, rs.retryConfig.Attempts)
}
return result, expiration, exists, err
}
// NewSecretLookupPaths defines how variables will be searched in the underlying secret manager
func (rs RetryableSecrets) NewSecretLookupPaths(teamName string, pipelineName string) []SecretLookupPath {
return rs.secrets.NewSecretLookupPaths(teamName, pipelineName)
}

View File

@ -0,0 +1,48 @@
package creds_test
import (
"fmt"
"github.com/cloudfoundry/bosh-cli/director/template"
"github.com/concourse/concourse/atc/creds"
"github.com/concourse/concourse/atc/creds/credsfakes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"time"
)
func makeFlakySecretManager(numberOfFails int) creds.Secrets {
fakeSecretManager := new(credsfakes.FakeSecrets)
attempt := 0
fakeSecretManager.GetStub = func(secretPath string) (interface{}, *time.Time, bool, error) {
attempt++
if attempt <= numberOfFails {
return nil, nil, false, fmt.Errorf("remote error: handshake failure")
}
return "received value", nil, true, nil
}
return fakeSecretManager
}
var _ = Describe("Re-retrieval of secrets on retryable errors", func() {
It("should retry receiving a parameter in case of retryable error", func() {
flakySecretManager := makeFlakySecretManager(3)
retryableSecretManager := creds.NewRetryableSecrets(flakySecretManager, creds.SecretRetryConfig{Attempts: 5, Interval: time.Millisecond})
varDef := template.VariableDefinition{Name: "somevar"}
value, found, err := creds.NewVariables(retryableSecretManager, "team", "pipeline").Get(varDef)
Expect(value).To(BeEquivalentTo("received value"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
})
It("should not receive a parameter if the number of retryable errors exceeded the number of allowed attempts", func() {
flakySecretManager := makeFlakySecretManager(10)
retryableSecretManager := creds.NewRetryableSecrets(flakySecretManager, creds.SecretRetryConfig{Attempts: 5, Interval: time.Millisecond})
varDef := template.VariableDefinition{Name: "somevar"}
value, found, err := creds.NewVariables(retryableSecretManager, "team", "pipeline").Get(varDef)
Expect(value).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).NotTo(BeNil())
})
})

View File

@ -1,65 +0,0 @@
package creds
import (
"fmt"
"github.com/cloudfoundry/bosh-cli/director/template"
"github.com/concourse/retryhttp"
"time"
)
type SecretRetryConfig struct {
Attempts int `long:"secret-retry-attempts" default:"5" description:"The number of attempts secret will be retried to be fetched, in case a retryable error happens."`
Interval time.Duration `long:"secret-retry-interval" default:"1s" description:"The interval between secret retry retrieval attempts."`
}
type RetryableVariablesFactory struct {
factory VariablesFactory
retryConfig SecretRetryConfig
}
type RetryableVariables struct {
variables Variables
retryConfig SecretRetryConfig
}
func NewRetryableVariablesFactory(factory VariablesFactory, retryConfig SecretRetryConfig) VariablesFactory {
return &RetryableVariablesFactory{factory: factory, retryConfig: retryConfig}
}
func (rvf RetryableVariablesFactory) NewVariables(teamName string, pipelineName string) Variables {
return RetryableVariables{variables: rvf.factory.NewVariables(teamName, pipelineName), retryConfig: rvf.retryConfig}
}
func (rv RetryableVariables) Get(varDef template.VariableDefinition) (interface{}, bool, error) {
r := &retryhttp.DefaultRetryer{}
for i := 0; i < rv.retryConfig.Attempts-1; i++ {
result, exists, err := rv.variables.Get(varDef)
if err != nil && r.IsRetryable(err) {
time.Sleep(rv.retryConfig.Interval)
continue
}
return result, exists, err
}
result, exists, err := rv.variables.Get(varDef)
if err != nil {
err = fmt.Errorf("%s (after %d retries)", err, rv.retryConfig.Attempts)
}
return result, exists, err
}
func (rv RetryableVariables) List() ([]template.VariableDefinition, error) {
r := &retryhttp.DefaultRetryer{}
for i := 0; i < rv.retryConfig.Attempts-1; i++ {
result, err := rv.variables.List()
if err != nil && r.IsRetryable(err) {
time.Sleep(rv.retryConfig.Interval)
continue
}
return result, err
}
result, err := rv.variables.List()
if err != nil {
err = fmt.Errorf("%s (after %d retries)", err, rv.retryConfig.Attempts)
}
return result, err
}

View File

@ -1,59 +0,0 @@
package creds_test
import (
"fmt"
"github.com/cloudfoundry/bosh-cli/director/template"
"github.com/concourse/concourse/atc/creds"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"time"
)
type flakyVariables struct {
attempt int
numberOfFails int
}
func (f *flakyVariables) Get(template.VariableDefinition) (interface{}, bool, error) {
f.attempt++
if f.attempt <= f.numberOfFails {
return nil, false, fmt.Errorf("remote error: handshake failure")
}
return "received value", true, nil
}
func (f *flakyVariables) List() ([]template.VariableDefinition, error) {
return nil, nil
}
type flakyFactory struct {
flakyVariables creds.Variables
}
func (f *flakyFactory) NewVariables(string, string) creds.Variables {
return f.flakyVariables
}
var _ = Describe("Retryable Variables Factory", func() {
It("should retry receiving a parameter in case of retryable error", func() {
flakyFactory := &flakyFactory{flakyVariables: &flakyVariables{numberOfFails: 3}}
factory := creds.NewRetryableVariablesFactory(flakyFactory, creds.SecretRetryConfig{Attempts: 5, Interval: time.Millisecond})
varDef := template.VariableDefinition{Name: "somevar"}
value, found, err := factory.NewVariables("team", "pipeline").Get(varDef)
Expect(value).To(BeEquivalentTo("received value"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
})
It("should not receive a parameter if the number of retryable errors exceeded the number of allowed attempts", func() {
flakyFactory := &flakyFactory{flakyVariables: &flakyVariables{numberOfFails: 10}}
factory := creds.NewRetryableVariablesFactory(flakyFactory, creds.SecretRetryConfig{Attempts: 5, Interval: time.Millisecond})
varDef := template.VariableDefinition{Name: "somevar"}
value, found, err := factory.NewVariables("team", "pipeline").Get(varDef)
Expect(value).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).NotTo(BeNil())
})
})

View File

@ -0,0 +1,46 @@
package creds
import (
"github.com/cloudfoundry/bosh-cli/director/template"
)
type VariableLookupFromSecrets struct {
Secrets Secrets
LookupPaths []SecretLookupPath
}
func NewVariables(secrets Secrets, teamName string, pipelineName string) template.Variables {
return VariableLookupFromSecrets{
Secrets: secrets,
LookupPaths: secrets.NewSecretLookupPaths(teamName, pipelineName),
}
}
func (sl VariableLookupFromSecrets) Get(varDef template.VariableDefinition) (interface{}, bool, error) {
// try to find a secret according to our var->secret lookup paths
if len(sl.LookupPaths) > 0 {
for _, rule := range sl.LookupPaths {
secretId, err := rule.VariableToSecretPath(varDef.Name)
if err != nil {
return nil, false, err
}
result, _, found, err := sl.Secrets.Get(secretId)
if err != nil {
return nil, false, err
}
if !found {
continue
}
return result, true, nil
}
return nil, false, nil
} else {
// if no paths are specified (i.e. for fake & noop secret managers), then try 1-to-1 var->secret mapping
result, _, found, err := sl.Secrets.Get(varDef.Name)
return result, found, err
}
}
func (sl VariableLookupFromSecrets) List() ([]template.VariableDefinition, error) {
return nil, nil
}

View File

@ -0,0 +1,21 @@
package creds
// SecretLookupPath transforms variable name into full secret path
type SecretLookupPath interface {
VariableToSecretPath(string) (string, error)
}
// SecretLookupWithPrefix is an implementation which returns [prefix][separator][varName]
type SecretLookupWithPrefix struct {
Prefix string
}
func NewSecretLookupWithPrefix(prefix string) SecretLookupPath {
return &SecretLookupWithPrefix{
Prefix: prefix,
}
}
func (sl SecretLookupWithPrefix) VariableToSecretPath(varName string) (string, error) {
return sl.Prefix + varName, nil
}

View File

@ -0,0 +1,25 @@
package creds
import (
"github.com/cloudfoundry/bosh-cli/director/template"
"time"
)
//go:generate counterfeiter . SecretsFactory
type SecretsFactory interface {
// NewSecrets returns an instance of a secret manager, capable of retrieving individual secrets
NewSecrets() Secrets
}
//go:generate counterfeiter . Secrets
type Secrets interface {
// Every credential manager needs to be able to return (secret, secret_expiration_time, exists, error) based on the secret path
Get(string) (interface{}, *time.Time, bool, error)
// NewSecretLookupPaths returns an instance of lookup policy, which can transform pipeline ((var)) into one or more secret paths, based on team name and pipeline name
NewSecretLookupPaths(string, string) []SecretLookupPath
}
type Variables = template.Variables

View File

@ -69,7 +69,7 @@ func (manager *Manager) Health() (*creds.HealthResponse, error) {
Method: "GetSecretValue",
}
_, _, err := manager.SecretManager.getSecretById("__concourse-health-check")
_, _, _, err := manager.SecretManager.getSecretById("__concourse-health-check")
if err != nil {
health.Error = err.Error()
return health, nil
@ -137,7 +137,7 @@ func (manager *Manager) Validate() error {
return nil
}
func (manager *Manager) NewVariablesFactory(log lager.Logger) (creds.VariablesFactory, error) {
func (manager *Manager) NewSecretsFactory(log lager.Logger) (creds.SecretsFactory, error) {
config := &aws.Config{Region: &manager.AwsRegion}
if manager.AwsAccessKeyID != "" {
config.Credentials = credentials.NewStaticCredentials(manager.AwsAccessKeyID, manager.AwsSecretAccessKey, manager.AwsSessionToken)

View File

@ -3,69 +3,60 @@ package secretsmanager
import (
"bytes"
"encoding/json"
"github.com/concourse/concourse/atc/creds"
"strings"
"text/template"
"time"
"code.cloudfoundry.org/lager"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/secretsmanager"
"github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
varTemplate "github.com/cloudfoundry/bosh-cli/director/template"
)
type SecretsManager struct {
log lager.Logger
api secretsmanageriface.SecretsManagerAPI
TeamName string
PipelineName string
SecretTemplates []*template.Template
secretTemplates []*template.Template
}
func NewSecretsManager(log lager.Logger, api secretsmanageriface.SecretsManagerAPI, teamName string, pipelineName string, secretTemplates []*template.Template) *SecretsManager {
func NewSecretsManager(log lager.Logger, api secretsmanageriface.SecretsManagerAPI, secretTemplates []*template.Template) *SecretsManager {
return &SecretsManager{
log: log,
api: api,
TeamName: teamName,
PipelineName: pipelineName,
SecretTemplates: secretTemplates,
secretTemplates: secretTemplates,
}
}
func (s *SecretsManager) buildSecretId(nameTemplate *template.Template, secret string) (string, error) {
var buf bytes.Buffer
err := nameTemplate.Execute(&buf, &Secret{
Team: s.TeamName,
Pipeline: s.PipelineName,
Secret: secret,
})
return buf.String(), err
}
// NewSecretLookupPaths defines how variables will be searched in the underlying secret manager
func (s *SecretsManager) NewSecretLookupPaths(teamName string, pipelineName string) []creds.SecretLookupPath {
lookupPaths := []creds.SecretLookupPath{}
for _, tmpl := range s.secretTemplates {
lPath := NewSecretLookupPathAws(tmpl, teamName, pipelineName)
func (s *SecretsManager) Get(varDef varTemplate.VariableDefinition) (interface{}, bool, error) {
for _, st := range s.SecretTemplates {
secretId, err := s.buildSecretId(st, varDef.Name)
if err != nil {
s.log.Error("build-secret-id", err, lager.Data{"template": st.Name(), "secret": varDef.Name})
return nil, false, err
}
if strings.Contains(secretId, "//") {
continue
}
value, found, err := s.getSecretById(secretId)
if err != nil {
s.log.Error("get-secret", err, lager.Data{
"template": st.Name(), "secret": varDef.Name, "secretId": secretId,
})
return nil, false, err
}
if found {
return value, true, nil
// if pipeline name is empty, double slashes may be present in the rendered template
// let's avoid adding these templates
samplePath, err := lPath.VariableToSecretPath("variable")
if err == nil && !strings.Contains(samplePath, "//") {
lookupPaths = append(lookupPaths, lPath)
}
}
return nil, false, nil
return lookupPaths
}
// Get retrieves the value and expiration of an individual secret
func (s *SecretsManager) Get(secretPath string) (interface{}, *time.Time, bool, error) {
value, expiration, found, err := s.getSecretById(secretPath)
if err != nil {
s.log.Error("unable to retrieve aws secret", err, lager.Data{
"secretPath": secretPath,
})
return nil, nil, false, err
}
if found {
return value, expiration, true, nil
}
return nil, nil, false, nil
}
/*
@ -74,31 +65,26 @@ func (s *SecretsManager) Get(varDef varTemplate.VariableDefinition) (interface{}
In case SecretBinary is set, it is expected to be a valid JSON object or it will error.
*/
func (s *SecretsManager) getSecretById(name string) (interface{}, bool, error) {
func (s *SecretsManager) getSecretById(name string) (interface{}, *time.Time, bool, error) {
value, err := s.api.GetSecretValue(&secretsmanager.GetSecretValueInput{
SecretId: &name,
})
if err == nil {
switch {
case value.SecretString != nil:
return *value.SecretString, true, nil
return *value.SecretString, nil, true, nil
case value.SecretBinary != nil:
values, err := decodeJsonValue(value.SecretBinary)
if err != nil {
return nil, true, err
return nil, nil, true, err
}
return values, true, nil
return values, nil, true, nil
}
} else if errObj, ok := err.(awserr.Error); ok && errObj.Code() == secretsmanager.ErrCodeResourceNotFoundException {
return nil, false, nil
return nil, nil, false, nil
}
return nil, false, err
}
func (s *SecretsManager) List() ([]varTemplate.VariableDefinition, error) {
// not implemented, see vault implementation
return []varTemplate.VariableDefinition{}, nil
return nil, nil, false, err
}
func decodeJsonValue(data []byte) (map[interface{}]interface{}, error) {
@ -112,3 +98,28 @@ func decodeJsonValue(data []byte) (map[interface{}]interface{}, error) {
}
return evenLessTyped, nil
}
// SecretLookupPathAws is an implementation which returns an evaluated go text template
type SecretLookupPathAws struct {
NameTemplate *template.Template
TeamName string
PipelineName string
}
func NewSecretLookupPathAws(nameTemplate *template.Template, teamName string, pipelineName string) creds.SecretLookupPath {
return &SecretLookupPathAws{
NameTemplate: nameTemplate,
TeamName: teamName,
PipelineName: pipelineName,
}
}
func (sl SecretLookupPathAws) VariableToSecretPath(varName string) (string, error) {
var buf bytes.Buffer
err := sl.NameTemplate.Execute(&buf, &Secret{
Team: sl.TeamName,
Pipeline: sl.PipelineName,
Secret: varName,
})
return buf.String(), err
}

View File

@ -23,6 +23,6 @@ func NewSecretsManagerFactory(log lager.Logger, session *session.Session, secret
}
}
func (factory *secretsManagerFactory) NewVariables(teamName string, pipelineName string) creds.Variables {
return NewSecretsManager(factory.log, factory.api, teamName, pipelineName, factory.secretTemplates)
func (factory *secretsManagerFactory) NewSecrets() creds.Secrets {
return NewSecretsManager(factory.log, factory.api, factory.secretTemplates)
}

View File

@ -2,6 +2,7 @@ package secretsmanager_test
import (
"errors"
"github.com/concourse/concourse/atc/creds"
"text/template"
"code.cloudfoundry.org/lager"
@ -37,6 +38,7 @@ func (mock *MockSecretsManagerService) GetSecretValue(input *secretsmanager.GetS
var _ = Describe("SecretsManager", func() {
var secretAccess *SecretsManager
var variables creds.Variables
var varDef varTemplate.VariableDefinition
var mockService MockSecretsManagerService
@ -48,7 +50,8 @@ var _ = Describe("SecretsManager", func() {
t2, err := template.New("test").Parse(DefaultTeamSecretTemplate)
Expect(t2).NotTo(BeNil())
Expect(err).To(BeNil())
secretAccess = NewSecretsManager(lager.NewLogger("secretsmanager_test"), &mockService, "alpha", "bogus", []*template.Template{t1, t2})
secretAccess = NewSecretsManager(lager.NewLogger("secretsmanager_test"), &mockService, []*template.Template{t1, t2})
variables = creds.NewVariables(secretAccess, "alpha", "bogus")
Expect(secretAccess).NotTo(BeNil())
mockService.stubGetParameter = func(input string) (*secretsmanager.GetSecretValueOutput, error) {
if input == "/concourse/alpha/bogus/cheery" {
@ -60,7 +63,7 @@ var _ = Describe("SecretsManager", func() {
Describe("Get()", func() {
It("should get parameter if exists", func() {
value, found, err := secretAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeEquivalentTo("secret value"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
@ -72,7 +75,7 @@ var _ = Describe("SecretsManager", func() {
SecretBinary: []byte(`{"name": "yours", "pass": "truely"}`),
}, nil
}
value, found, err := secretAccess.Get(varTemplate.VariableDefinition{Name: "user"})
value, found, err := variables.Get(varTemplate.VariableDefinition{Name: "user"})
Expect(err).To(BeNil())
Expect(found).To(BeTrue())
Expect(value).To(BeEquivalentTo(map[interface{}]interface{}{
@ -88,7 +91,7 @@ var _ = Describe("SecretsManager", func() {
}
return &secretsmanager.GetSecretValueOutput{SecretString: aws.String("team decrypted value")}, nil
}
value, found, err := secretAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeEquivalentTo("team decrypted value"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
@ -96,19 +99,19 @@ var _ = Describe("SecretsManager", func() {
It("should return not found on error", func() {
mockService.stubGetParameter = nil
value, found, err := secretAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).NotTo(BeNil())
})
It("should allow empty pipeline name", func() {
secretAccess.PipelineName = ""
variables := creds.NewVariables(secretAccess, "alpha", "")
mockService.stubGetParameter = func(input string) (*secretsmanager.GetSecretValueOutput, error) {
Expect(input).To(Equal("/concourse/alpha/cheery"))
return &secretsmanager.GetSecretValueOutput{SecretString: aws.String("team power")}, nil
}
value, found, err := secretAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeEquivalentTo("team power"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())

View File

@ -93,7 +93,7 @@ func (manager *SsmManager) Health() (*creds.HealthResponse, error) {
Method: "GetParameter",
}
_, _, err := manager.Ssm.getParameterByName("__concourse-health-check")
_, _, _, err := manager.Ssm.getParameterByName("__concourse-health-check")
if err != nil {
if errObj, ok := err.(awserr.Error); ok && strings.Contains(errObj.Code(), "AccessDenied") {
health.Response = map[string]string{
@ -153,7 +153,7 @@ func (manager *SsmManager) Validate() error {
return nil
}
func (manager *SsmManager) NewVariablesFactory(log lager.Logger) (creds.VariablesFactory, error) {
func (manager *SsmManager) NewSecretsFactory(log lager.Logger) (creds.SecretsFactory, error) {
session, err := manager.getSession()
if err != nil {

View File

@ -2,104 +2,90 @@ package ssm
import (
"bytes"
"github.com/concourse/concourse/atc/creds"
"strings"
"text/template"
"time"
"code.cloudfoundry.org/lager"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
varTemplate "github.com/cloudfoundry/bosh-cli/director/template"
)
type Ssm struct {
log lager.Logger
api ssmiface.SSMAPI
TeamName string
PipelineName string
SecretTemplates []*template.Template
secretTemplates []*template.Template
}
func NewSsm(log lager.Logger, api ssmiface.SSMAPI, teamName string, pipelineName string, secretTemplates []*template.Template) *Ssm {
func NewSsm(log lager.Logger, api ssmiface.SSMAPI, secretTemplates []*template.Template) *Ssm {
return &Ssm{
log: log,
api: api,
TeamName: teamName,
PipelineName: pipelineName,
SecretTemplates: secretTemplates,
secretTemplates: secretTemplates,
}
}
func (s *Ssm) transformSecret(nameTemplate *template.Template, secret string) (string, error) {
var buf bytes.Buffer
err := nameTemplate.Execute(&buf, &SsmSecret{
Team: s.TeamName,
Pipeline: s.PipelineName,
Secret: secret,
})
return buf.String(), err
}
// NewSecretLookupPaths defines how variables will be searched in the underlying secret manager
func (s *Ssm) NewSecretLookupPaths(teamName string, pipelineName string) []creds.SecretLookupPath {
lookupPaths := []creds.SecretLookupPath{}
for _, tmpl := range s.secretTemplates {
lPath := NewSecretLookupPathSsm(tmpl, teamName, pipelineName)
func (s *Ssm) Get(varDef varTemplate.VariableDefinition) (interface{}, bool, error) {
for _, st := range s.SecretTemplates {
// Try to get the parameter as string value
parameter, err := s.transformSecret(st, varDef.Name)
if err != nil {
s.log.Error("failed-to-build-ssm-parameter-path-from-secret", err, lager.Data{
"template": st.Name(),
"secret": varDef.Name,
})
return nil, false, err
}
// If pipeline name is empty, double slashes may be present in the parameter name
if strings.Contains(parameter, "//") {
continue
}
value, found, err := s.getParameterByName(parameter)
if err != nil {
s.log.Error("failed-to-get-ssm-parameter-by-name", err, lager.Data{
"template": st.Name(),
"secret": varDef.Name,
"parameter": parameter,
})
return nil, false, err
}
if found {
return value, true, nil
}
// // Paramter may exist as a complex value so try again using paramter name as root path
value, found, err = s.getParameterByPath(parameter)
if err != nil {
s.log.Error("failed-to-get-ssm-parameter-by-path", err, lager.Data{
"template": st.Name(),
"secret": varDef.Name,
"parameter": parameter,
})
return nil, false, err
}
if found {
return value, true, nil
// if pipeline name is empty, double slashes may be present in the rendered template
// let's avoid adding these templates
samplePath, err := lPath.VariableToSecretPath("variable")
if err == nil && !strings.Contains(samplePath, "//") {
lookupPaths = append(lookupPaths, lPath)
}
}
return nil, false, nil
return lookupPaths
}
func (s *Ssm) getParameterByName(name string) (interface{}, bool, error) {
// Get retrieves the value and expiration of an individual secret
func (s *Ssm) Get(secretPath string) (interface{}, *time.Time, bool, error) {
// Try to get the parameter as string value, by name
value, expiration, found, err := s.getParameterByName(secretPath)
if err != nil {
s.log.Error("unable to retrieve aws ssm secret by name", err, lager.Data{
"secretPath": secretPath,
})
return nil, nil, false, err
}
if found {
return value, expiration, true, nil
}
// Parameter may exist as a complex value so try again using parameter name as root path
value, expiration, found, err = s.getParameterByPath(secretPath)
if err != nil {
s.log.Error("unable to retrieve aws ssm secret by path", err, lager.Data{
"secretPath": secretPath,
})
return nil, nil, false, err
}
if found {
return value, expiration, true, nil
}
return nil, nil, false, nil
}
func (s *Ssm) getParameterByName(name string) (interface{}, *time.Time, bool, error) {
param, err := s.api.GetParameter(&ssm.GetParameterInput{
Name: &name,
WithDecryption: aws.Bool(true),
})
if err == nil {
return *param.Parameter.Value, true, nil
return *param.Parameter.Value, nil, true, nil
} else if errObj, ok := err.(awserr.Error); ok && errObj.Code() == ssm.ErrCodeParameterNotFound {
return nil, false, nil
return nil, nil, false, nil
}
return nil, false, err
return nil, nil, false, err
}
func (s *Ssm) getParameterByPath(path string) (interface{}, bool, error) {
func (s *Ssm) getParameterByPath(path string) (interface{}, *time.Time, bool, error) {
path = strings.TrimRight(path, "/")
if path == "" {
path = "/"
@ -114,15 +100,35 @@ func (s *Ssm) getParameterByPath(path string) (interface{}, bool, error) {
return true
})
if err != nil {
return nil, false, err
return nil, nil, false, err
}
if len(value) == 0 {
return nil, false, nil
return nil, nil, false, nil
}
return value, true, nil
return value, nil, true, nil
}
func (s *Ssm) List() ([]varTemplate.VariableDefinition, error) {
// not implemented, see vault implementation
return []varTemplate.VariableDefinition{}, nil
// SecretLookupPathSsm is an implementation which returns an evaluated go text template
type SecretLookupPathSsm struct {
NameTemplate *template.Template
TeamName string
PipelineName string
}
func NewSecretLookupPathSsm(nameTemplate *template.Template, teamName string, pipelineName string) creds.SecretLookupPath {
return &SecretLookupPathSsm{
NameTemplate: nameTemplate,
TeamName: teamName,
PipelineName: pipelineName,
}
}
func (sl SecretLookupPathSsm) VariableToSecretPath(varName string) (string, error) {
var buf bytes.Buffer
err := sl.NameTemplate.Execute(&buf, &SsmSecret{
Team: sl.TeamName,
Pipeline: sl.PipelineName,
Secret: varName,
})
return buf.String(), err
}

View File

@ -23,6 +23,6 @@ func NewSsmFactory(log lager.Logger, session *session.Session, secretTemplates [
}
}
func (factory *ssmFactory) NewVariables(teamName string, pipelineName string) creds.Variables {
return NewSsm(factory.log, factory.api, teamName, pipelineName, factory.secretTemplates)
func (factory *ssmFactory) NewSecrets() creds.Secrets {
return NewSsm(factory.log, factory.api, factory.secretTemplates)
}

View File

@ -2,6 +2,7 @@ package ssm_test
import (
"errors"
"github.com/concourse/concourse/atc/creds"
"strconv"
"text/template"
@ -84,6 +85,7 @@ func (mock *MockSsmService) GetParametersByPathPages(input *ssm.GetParametersByP
var _ = Describe("Ssm", func() {
var ssmAccess *Ssm
var variables creds.Variables
var varDef varTemplate.VariableDefinition
var mockService MockSsmService
@ -95,7 +97,8 @@ var _ = Describe("Ssm", func() {
t2, err := template.New("test").Parse(DefaultTeamSecretTemplate)
Expect(t2).NotTo(BeNil())
Expect(err).To(BeNil())
ssmAccess = NewSsm(lager.NewLogger("ssm_test"), &mockService, "alpha", "bogus", []*template.Template{t1, t2})
ssmAccess = NewSsm(lager.NewLogger("ssm_test"), &mockService, []*template.Template{t1, t2})
variables = creds.NewVariables(ssmAccess, "alpha", "bogus")
Expect(ssmAccess).NotTo(BeNil())
mockService.stubGetParameter = func(input string) (string, error) {
if input == "/concourse/alpha/bogus/cheery" {
@ -110,7 +113,7 @@ var _ = Describe("Ssm", func() {
Describe("Get()", func() {
It("should get parameter if exists", func() {
value, found, err := ssmAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeEquivalentTo("ssm decrypted value"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
@ -128,7 +131,7 @@ var _ = Describe("Ssm", func() {
},
}
}
value, found, err := ssmAccess.Get(varTemplate.VariableDefinition{Name: "user"})
value, found, err := variables.Get(varTemplate.VariableDefinition{Name: "user"})
Expect(value).To(BeEquivalentTo(map[interface{}]interface{}{
"name": "yours",
"pass": "truely",
@ -141,7 +144,7 @@ var _ = Describe("Ssm", func() {
mockService.stubGetParameter = func(input string) (string, error) {
return "101", nil
}
value, found, err := ssmAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeEquivalentTo("101"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
@ -154,7 +157,7 @@ var _ = Describe("Ssm", func() {
}
return "team decrypted value", nil
}
value, found, err := ssmAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeEquivalentTo("team decrypted value"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
@ -162,19 +165,19 @@ var _ = Describe("Ssm", func() {
It("should return not found on error", func() {
mockService.stubGetParameter = nil
value, found, err := ssmAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeNil())
Expect(found).To(BeFalse())
Expect(err).NotTo(BeNil())
})
It("should allow empty pipeline name", func() {
ssmAccess.PipelineName = ""
variables := creds.NewVariables(ssmAccess, "alpha", "")
mockService.stubGetParameter = func(input string) (string, error) {
Expect(input).To(Equal("/concourse/alpha/cheery"))
return "team power", nil
}
value, found, err := ssmAccess.Get(varDef)
value, found, err := variables.Get(varDef)
Expect(value).To(BeEquivalentTo("team power"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())

View File

@ -1,16 +0,0 @@
package creds
import "github.com/cloudfoundry/bosh-cli/director/template"
//go:generate counterfeiter . VariablesFactory
type VariablesFactory interface {
NewVariables(string, string) Variables
}
//go:generate counterfeiter . Variables
type Variables interface {
Get(template.VariableDefinition) (interface{}, bool, error)
List() ([]template.VariableDefinition, error)
}

View File

@ -1,121 +0,0 @@
package vault
import (
"sync"
"time"
vaultapi "github.com/hashicorp/vault/api"
)
type cachedSecret struct {
deadline time.Time
secret *vaultapi.Secret
}
// A Cache caches secrets read from a SecretReader until the lease on
// the secret expires. Once expired the credential is proactively
// deleted from cache to maintain a smaller cache footprint.
type Cache struct {
sync.RWMutex
cache map[string]*cachedSecret
newItems chan time.Time
sr SecretReader
maxLease time.Duration
}
// TODO: Should a cache have a max size to
// prevent unbounded growth?
// NewCache using the underlying vault client.
func NewCache(sr SecretReader, maxLease time.Duration) *Cache {
c := &Cache{
cache: make(map[string]*cachedSecret),
newItems: make(chan time.Time, 100),
sr: sr,
maxLease: maxLease,
}
go c.reaperThread()
return c
}
func (c *Cache) reapCache() time.Time {
c.Lock()
defer c.Unlock()
var smallestNext time.Time
for k, secret := range c.cache {
if time.Now().After(secret.deadline) {
delete(c.cache, k)
continue
}
if secret.deadline.Before(smallestNext) {
smallestNext = secret.deadline
}
}
return smallestNext
}
func (c *Cache) reaperThread() {
sleep := time.NewTimer(1 * time.Second)
defer sleep.Stop()
var nextWakeup time.Time
for {
select {
case <-sleep.C:
nextWakeup = c.reapCache()
if !nextWakeup.IsZero() {
sleep.Reset(time.Since(nextWakeup))
}
case t := <-c.newItems:
if t.Before(nextWakeup) {
continue
}
nextWakeup = t
sleep.Reset(time.Until(nextWakeup))
}
}
}
// Read a secret from the cache or the underlying client if not
// present.
func (c *Cache) Read(path string) (*vaultapi.Secret, error) {
// If we have the secret in our cache just return it
c.RLock() // don't use defer because we want to agressively release this lock
cs, cached := c.cache[path]
c.RUnlock()
if cached && time.Now().Before(cs.deadline) {
return cs.secret, nil
}
// Otherwise fetch the secret using the client. Clients are
// thread safe for read use.
secret, err := c.sr.Read(path)
if err != nil || secret == nil {
return secret, err
}
// We will renew the item in half the lease duration to resolve an inherent race
// in this setup: What if the secret becomes invalid _during_ the build. We don't
// want to be issuing secrets that expire in (fex) 100ms.
// This is a problem in any implementation, as lease duration could be 1s and the
// build will _probably_ take longer than that.
dur := time.Duration(secret.LeaseDuration) * time.Second / 2
if c.maxLease != 0 && dur > c.maxLease {
dur = c.maxLease
}
// Store the secret in cache
cs = &cachedSecret{
deadline: time.Now().Add(dur),
secret: secret,
}
c.Lock()
c.cache[path] = cs
c.Unlock()
// Tell the reaper thread it has new items to cleanup
c.newItems <- cs.deadline
return secret, nil
}

View File

@ -1,109 +0,0 @@
package vault
import (
"testing"
"time"
vaultapi "github.com/hashicorp/vault/api"
)
type MockSecretReader struct {
secrets []*vaultapi.Secret
reads []string
}
func (msr *MockSecretReader) Read(path string) (*vaultapi.Secret, error) {
msr.reads = append(msr.reads, path)
s := msr.secrets[0]
msr.secrets = msr.secrets[1:]
return s, nil
}
func TestCache(t *testing.T) {
secrets := []*vaultapi.Secret{
&vaultapi.Secret{
RequestID: "1",
LeaseDuration: 1,
},
&vaultapi.Secret{
RequestID: "1",
LeaseDuration: 2,
},
&vaultapi.Secret{
RequestID: "1",
LeaseDuration: 10,
},
}
msr := &MockSecretReader{
secrets: secrets,
}
cache := NewCache(msr, 5*time.Second)
// miss
secret, err := cache.Read("path1")
if err != nil {
t.Error("got error reading valid secret", err)
}
if secret.RequestID != secrets[0].RequestID {
t.Errorf("read secret %s expected %s", secret.RequestID, secrets[0].RequestID)
}
if len(msr.reads) != 1 && msr.reads[0] != "path1" {
t.Errorf("Got reads [%v], expected [\"%s\"]", msr.reads, "path1")
}
// hit
secret, err = cache.Read("path1")
if err != nil {
t.Error("got error reading valid secret from cache", err)
}
if secret.RequestID != secrets[0].RequestID {
t.Errorf("read secret %s expected %s", secret.RequestID, secrets[0].RequestID)
}
if len(msr.reads) != 1 && msr.reads[0] != "path1" {
t.Errorf("Got reads [%v], expected [\"%s\"]", msr.reads, "path1")
}
// reap
time.Sleep(time.Duration(secret.LeaseDuration)*time.Second + 100*time.Millisecond)
// miss
secret, err = cache.Read("path1")
if err != nil {
t.Error("got error reading valid secret from cache", err)
}
if secret.RequestID != secrets[0].RequestID {
t.Errorf("read secret %s expected %s", secret.RequestID, secrets[0].RequestID)
}
if len(msr.reads) != 2 && msr.reads[0] != "path1" {
t.Errorf("Got reads [%v], expected [\"%s\"]", msr.reads, "path1 path1")
}
// reap
time.Sleep(time.Duration(secret.LeaseDuration)*time.Second + 100*time.Millisecond)
cache.RLock()
if len(cache.cache) != 0 {
t.Errorf("Expectde cache to be clean after expiration, was %v", cache.cache)
}
cache.RUnlock()
// Test max duration
secret, err = cache.Read("path1")
if err != nil {
t.Error("got error reading valid secret from cache", err)
}
if secret.RequestID != secrets[0].RequestID {
t.Errorf("read secret %s expected %s", secret.RequestID, secrets[0].RequestID)
}
if len(msr.reads) != 1 && msr.reads[0] != "path1" {
t.Errorf("Got reads [%v], expected [\"%s\"]", msr.reads, "path1")
}
// reap
time.Sleep(5*time.Second + 100*time.Millisecond)
cache.RLock()
if len(cache.cache) != 0 {
t.Errorf("Expectde cache to be clean after maxu duration, was %v", cache.cache)
}
cache.RUnlock()
}

View File

@ -19,9 +19,6 @@ type VaultManager struct {
PathPrefix string `long:"path-prefix" default:"/concourse" description:"Path under which to namespace credential lookup."`
SharedPath string `long:"shared-path" description:"Path under which to lookup shared credentials."`
Cache bool `long:"cache" description:"Cache returned secrets for their lease duration in memory"`
MaxLease time.Duration `long:"max-lease" description:"If the cache is enabled, and this is set, override secrets lease duration with a maximum value"`
TLS TLS
Auth AuthConfig
Client *APIClient
@ -77,8 +74,6 @@ func (manager *VaultManager) MarshalJSON() ([]byte, error) {
return json.Marshal(&map[string]interface{}{
"url": manager.URL,
"path_prefix": manager.PathPrefix,
"cache": manager.Cache,
"max_lease": manager.MaxLease,
"ca_cert": manager.TLS.CACert,
"server_name": manager.TLS.ServerName,
"auth_backend": manager.Auth.Backend,
@ -125,12 +120,7 @@ func (manager VaultManager) Health() (*creds.HealthResponse, error) {
return health, nil
}
func (manager VaultManager) NewVariablesFactory(logger lager.Logger) (creds.VariablesFactory, error) {
func (manager VaultManager) NewSecretsFactory(logger lager.Logger) (creds.SecretsFactory, error) {
ra := NewReAuther(manager.Client, manager.Auth.BackendMaxTTL, manager.Auth.RetryInitial, manager.Auth.RetryMax)
var sr SecretReader = manager.Client
if manager.Cache {
sr = NewCache(manager.Client, manager.MaxLease)
}
return NewVaultFactory(sr, ra.LoggedIn(), manager.PathPrefix, manager.SharedPath), nil
return NewVaultFactory(manager.Client, ra.LoggedIn(), manager.PathPrefix, manager.SharedPath), nil
}

View File

@ -1,9 +1,10 @@
package vault
import (
"github.com/concourse/concourse/atc/creds"
"path"
"time"
"github.com/cloudfoundry/bosh-cli/director/template"
vaultapi "github.com/hashicorp/vault/api"
)
@ -17,46 +18,34 @@ type SecretReader interface {
// data.
type Vault struct {
SecretReader SecretReader
PathPrefix string
Prefix string
SharedPath string
TeamName string
PipelineName string
}
func (v Vault) Get(varDef template.VariableDefinition) (interface{}, bool, error) {
var secret *vaultapi.Secret
var found bool
var err error
if v.PipelineName != "" {
secret, found, err = v.findSecret(v.path(v.TeamName, v.PipelineName, varDef.Name))
if err != nil {
return nil, false, err
}
// NewSecretLookupPaths defines how variables will be searched in the underlying secret manager
func (v Vault) NewSecretLookupPaths(teamName string, pipelineName string) []creds.SecretLookupPath {
lookupPaths := []creds.SecretLookupPath{}
if len(pipelineName) > 0 {
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(path.Join(v.Prefix, teamName, pipelineName)+"/"))
}
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(path.Join(v.Prefix, teamName)+"/"))
lookupPaths = append(lookupPaths, creds.NewSecretLookupWithPrefix(path.Join(v.Prefix, v.SharedPath)+"/"))
return lookupPaths
}
// Get retrieves the value and expiration of an individual secret
func (v Vault) Get(secretPath string) (interface{}, *time.Time, bool, error) {
secret, expiration, found, err := v.findSecret(secretPath)
if err != nil {
return nil, nil, false, err
}
if !found {
secret, found, err = v.findSecret(v.path(v.TeamName, varDef.Name))
if err != nil {
return nil, false, err
}
}
if !found && v.SharedPath != "" {
secret, found, err = v.findSecret(v.path(v.SharedPath, varDef.Name))
if err != nil {
return nil, false, err
}
}
if !found {
return nil, false, nil
return nil, nil, false, nil
}
val, found := secret.Data["value"]
if found {
return val, true, nil
return val, expiration, true, nil
}
evenLessTyped := map[interface{}]interface{}{}
@ -64,41 +53,22 @@ func (v Vault) Get(varDef template.VariableDefinition) (interface{}, bool, error
evenLessTyped[k] = v
}
return evenLessTyped, true, nil
return evenLessTyped, expiration, true, nil
}
func (v Vault) findSecret(path string) (*vaultapi.Secret, bool, error) {
func (v Vault) findSecret(path string) (*vaultapi.Secret, *time.Time, bool, error) {
secret, err := v.SecretReader.Read(path)
if err != nil {
return nil, false, err
return nil, nil, false, err
}
if secret != nil {
return secret, true, nil
// The lease duration is TTL: the time in seconds for which the lease is valid
// A consumer of this secret must renew the lease within that time.
duration := time.Duration(secret.LeaseDuration) * time.Second / 2
expiration := time.Now().Add(duration)
return secret, &expiration, true, nil
}
return nil, false, nil
}
func (v Vault) path(segments ...string) string {
return path.Join(append([]string{v.PathPrefix}, segments...)...)
}
func (v Vault) List() ([]template.VariableDefinition, error) {
// Don't think this works with vault.. if we need it to we'll figure it out
// var defs []template.VariableDefinition
// secret, err := v.vaultClient.List(v.PathPrefix)
// if err != nil {
// return defs, err
// }
// var def template.VariableDefinition
// for name, _ := range secret.Data {
// defs := append(defs, template.VariableDefinition{
// Name: name,
// })
// }
return []template.VariableDefinition{}, nil
return nil, nil, false, nil
}

View File

@ -8,26 +8,25 @@ import (
// The vaultFactory will return a vault implementation of creds.Variables.
type vaultFactory struct {
sr SecretReader
prefix string
sr SecretReader
prefix string
sharedPath string
loggedIn <-chan struct{}
loggedIn <-chan struct{}
}
func NewVaultFactory(sr SecretReader, loggedIn <-chan struct{}, prefix string, sharedPath string) *vaultFactory {
factory := &vaultFactory{
sr: sr,
prefix: prefix,
sr: sr,
prefix: prefix,
sharedPath: sharedPath,
loggedIn: loggedIn,
loggedIn: loggedIn,
}
return factory
}
// NewVariables will block until the loggedIn channel passed to the
// constructor signals a successful login.
func (factory *vaultFactory) NewVariables(teamName string, pipelineName string) creds.Variables {
// NewSecrets will block until the loggedIn channel passed to the constructor signals a successful login.
func (factory *vaultFactory) NewSecrets() creds.Secrets {
select {
case <-factory.loggedIn:
case <-time.After(5 * time.Second):
@ -35,9 +34,7 @@ func (factory *vaultFactory) NewVariables(teamName string, pipelineName string)
return &Vault{
SecretReader: factory.sr,
PathPrefix: factory.prefix,
Prefix: factory.prefix,
SharedPath: factory.sharedPath,
TeamName: teamName,
PipelineName: pipelineName,
}
}

View File

@ -2,6 +2,7 @@ package vault_test
import (
"github.com/cloudfoundry/bosh-cli/director/template"
"github.com/concourse/concourse/atc/creds"
"github.com/concourse/concourse/atc/creds/vault"
vaultapi "github.com/hashicorp/vault/api"
. "github.com/onsi/ginkgo"
@ -21,7 +22,7 @@ func (msr *MockSecretReader) Read(lookupPath string) (*vaultapi.Secret, error) {
Expect(lookupPath).ToNot(BeNil())
for _, secret := range *msr.secrets {
if (lookupPath == secret.path) {
if lookupPath == secret.path {
return secret.secret, nil
}
}
@ -32,6 +33,7 @@ func (msr *MockSecretReader) Read(lookupPath string) (*vaultapi.Secret, error) {
var _ = Describe("Vault", func() {
var v *vault.Vault
var variables creds.Variables
var msr *MockSecretReader
JustBeforeEach(func() {
@ -47,12 +49,11 @@ var _ = Describe("Vault", func() {
v = &vault.Vault{
SecretReader: msr,
PathPrefix: "/concourse",
Prefix: "/concourse",
SharedPath: "shared",
TeamName: "team",
PipelineName: "pipeline",
}
variables = creds.NewVariables(v, "team", "pipeline")
})
Describe("Get()", func() {
@ -65,7 +66,7 @@ var _ = Describe("Vault", func() {
},
}},
}
value, found, err := v.Get(template.VariableDefinition{Name: "foo"})
value, found, err := variables.Get(template.VariableDefinition{Name: "foo"})
Expect(value).To(BeEquivalentTo("bar"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
@ -80,7 +81,7 @@ var _ = Describe("Vault", func() {
},
}},
}
value, found, err := v.Get(template.VariableDefinition{Name: "foo"})
value, found, err := variables.Get(template.VariableDefinition{Name: "foo"})
Expect(value).To(BeEquivalentTo("bar"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
@ -95,7 +96,7 @@ var _ = Describe("Vault", func() {
},
}},
}
value, found, err := v.Get(template.VariableDefinition{Name: "foo"})
value, found, err := variables.Get(template.VariableDefinition{Name: "foo"})
Expect(value).To(BeEquivalentTo("bar"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())
@ -116,7 +117,7 @@ var _ = Describe("Vault", func() {
},
}},
}
value, found, err := v.Get(template.VariableDefinition{Name: "foo"})
value, found, err := variables.Get(template.VariableDefinition{Name: "foo"})
Expect(value).To(BeEquivalentTo("bar"))
Expect(found).To(BeTrue())
Expect(err).To(BeNil())

View File

@ -110,13 +110,13 @@ type FakeTeam struct {
deleteReturnsOnCall map[int]struct {
result1 error
}
FindCheckContainersStub func(lager.Logger, string, string, creds.VariablesFactory) ([]db.Container, map[int]time.Time, error)
FindCheckContainersStub func(lager.Logger, string, string, creds.Secrets) ([]db.Container, map[int]time.Time, error)
findCheckContainersMutex sync.RWMutex
findCheckContainersArgsForCall []struct {
arg1 lager.Logger
arg2 string
arg3 string
arg4 creds.VariablesFactory
arg4 creds.Secrets
}
findCheckContainersReturns struct {
result1 []db.Container
@ -879,14 +879,14 @@ func (fake *FakeTeam) DeleteReturnsOnCall(i int, result1 error) {
}{result1}
}
func (fake *FakeTeam) FindCheckContainers(arg1 lager.Logger, arg2 string, arg3 string, arg4 creds.VariablesFactory) ([]db.Container, map[int]time.Time, error) {
func (fake *FakeTeam) FindCheckContainers(arg1 lager.Logger, arg2 string, arg3 string, arg4 creds.Secrets) ([]db.Container, map[int]time.Time, error) {
fake.findCheckContainersMutex.Lock()
ret, specificReturn := fake.findCheckContainersReturnsOnCall[len(fake.findCheckContainersArgsForCall)]
fake.findCheckContainersArgsForCall = append(fake.findCheckContainersArgsForCall, struct {
arg1 lager.Logger
arg2 string
arg3 string
arg4 creds.VariablesFactory
arg4 creds.Secrets
}{arg1, arg2, arg3, arg4})
fake.recordInvocation("FindCheckContainers", []interface{}{arg1, arg2, arg3, arg4})
fake.findCheckContainersMutex.Unlock()
@ -906,13 +906,13 @@ func (fake *FakeTeam) FindCheckContainersCallCount() int {
return len(fake.findCheckContainersArgsForCall)
}
func (fake *FakeTeam) FindCheckContainersCalls(stub func(lager.Logger, string, string, creds.VariablesFactory) ([]db.Container, map[int]time.Time, error)) {
func (fake *FakeTeam) FindCheckContainersCalls(stub func(lager.Logger, string, string, creds.Secrets) ([]db.Container, map[int]time.Time, error)) {
fake.findCheckContainersMutex.Lock()
defer fake.findCheckContainersMutex.Unlock()
fake.FindCheckContainersStub = stub
}
func (fake *FakeTeam) FindCheckContainersArgsForCall(i int) (lager.Logger, string, string, creds.VariablesFactory) {
func (fake *FakeTeam) FindCheckContainersArgsForCall(i int) (lager.Logger, string, string, creds.Secrets) {
fake.findCheckContainersMutex.RLock()
defer fake.findCheckContainersMutex.RUnlock()
argsForCall := fake.findCheckContainersArgsForCall[i]

View File

@ -59,7 +59,7 @@ type Team interface {
IsContainerWithinTeam(string, bool) (bool, error)
FindContainerByHandle(string) (Container, bool, error)
FindCheckContainers(lager.Logger, string, string, creds.VariablesFactory) ([]Container, map[int]time.Time, error)
FindCheckContainers(lager.Logger, string, string, creds.Secrets) ([]Container, map[int]time.Time, error)
FindContainersByMetadata(ContainerMetadata) ([]Container, error)
FindCreatedContainerByHandle(string) (CreatedContainer, bool, error)
FindWorkerForContainer(handle string) (Worker, bool, error)
@ -803,7 +803,7 @@ func (t *team) UpdateProviderAuth(auth atc.TeamAuth) error {
return tx.Commit()
}
func (t *team) FindCheckContainers(logger lager.Logger, pipelineName string, resourceName string, variablesFactory creds.VariablesFactory) ([]Container, map[int]time.Time, error) {
func (t *team) FindCheckContainers(logger lager.Logger, pipelineName string, resourceName string, secretManager creds.Secrets) ([]Container, map[int]time.Time, error) {
pipeline, found, err := t.Pipeline(pipelineName)
if err != nil {
return nil, nil, err
@ -825,7 +825,7 @@ func (t *team) FindCheckContainers(logger lager.Logger, pipelineName string, res
return nil, nil, err
}
variables := variablesFactory.NewVariables(t.name, pipeline.Name())
variables := creds.NewVariables(secretManager, t.name, pipeline.Name())
versionedResourceTypes := pipelineResourceTypes.Deserialize()

View File

@ -408,8 +408,7 @@ var _ = Describe("Team", func() {
Describe("Containers", func() {
var (
fakeVariablesFactory *credsfakes.FakeVariablesFactory
variables creds.Variables
fakeSecretManager *credsfakes.FakeSecrets
resourceContainer db.CreatingContainer
resourceConfigScope db.ResourceConfigScope
firstContainerCreating db.CreatingContainer
@ -417,9 +416,8 @@ var _ = Describe("Team", func() {
Context("when there is a task container and a check container", func() {
BeforeEach(func() {
fakeVariablesFactory = new(credsfakes.FakeVariablesFactory)
variables = template.StaticVariables{}
fakeVariablesFactory.NewVariablesReturns(variables)
fakeSecretManager = new(credsfakes.FakeSecrets)
fakeSecretManager.GetReturns("", nil, false, nil)
job, found, err := defaultPipeline.Job("some-job")
Expect(err).ToNot(HaveOccurred())
@ -440,7 +438,7 @@ var _ = Describe("Team", func() {
pipelineResourceTypes, err := defaultPipeline.ResourceTypes()
Expect(err).ToNot(HaveOccurred())
resourceConfigScope, err = defaultResource.SetResourceConfig(logger, defaultResource.Source(), creds.NewVersionedResourceTypes(variables, pipelineResourceTypes.Deserialize()))
resourceConfigScope, err = defaultResource.SetResourceConfig(logger, defaultResource.Source(), creds.NewVersionedResourceTypes(template.StaticVariables{}, pipelineResourceTypes.Deserialize()))
Expect(err).ToNot(HaveOccurred())
resourceContainer, err = defaultWorker.CreateContainer(
@ -2436,8 +2434,8 @@ var _ = Describe("Team", func() {
Describe("FindCheckContainers", func() {
var (
fakeVariablesFactory *credsfakes.FakeVariablesFactory
variables creds.Variables
fakeSecretManager *credsfakes.FakeSecrets
variables creds.Variables
)
expiries := db.ContainerOwnerExpiries{
@ -2447,9 +2445,8 @@ var _ = Describe("Team", func() {
}
BeforeEach(func() {
fakeVariablesFactory = new(credsfakes.FakeVariablesFactory)
variables = template.StaticVariables{}
fakeVariablesFactory.NewVariablesReturns(variables)
fakeSecretManager = new(credsfakes.FakeSecrets)
fakeSecretManager.GetReturns("", nil, false, nil)
})
Context("when pipeline exists", func() {
@ -2478,7 +2475,7 @@ var _ = Describe("Team", func() {
})
It("returns check container for resource", func() {
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "default-pipeline", "some-resource", fakeVariablesFactory)
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "default-pipeline", "some-resource", fakeSecretManager)
Expect(err).ToNot(HaveOccurred())
Expect(containers).To(HaveLen(1))
Expect(containers[0].ID()).To(Equal(resourceContainer.ID()))
@ -2528,7 +2525,7 @@ var _ = Describe("Team", func() {
})
It("returns the same check container", func() {
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "other-pipeline", "some-resource", fakeVariablesFactory)
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "other-pipeline", "some-resource", fakeSecretManager)
Expect(err).ToNot(HaveOccurred())
Expect(containers).To(HaveLen(1))
Expect(containers[0].ID()).To(Equal(otherResourceContainer.ID()))
@ -2541,7 +2538,7 @@ var _ = Describe("Team", func() {
Context("when check container does not exist", func() {
It("returns empty list", func() {
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "default-pipeline", "some-resource", fakeVariablesFactory)
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "default-pipeline", "some-resource", fakeSecretManager)
Expect(err).ToNot(HaveOccurred())
Expect(containers).To(BeEmpty())
Expect(checkContainersExpiresAt).To(BeEmpty())
@ -2551,7 +2548,7 @@ var _ = Describe("Team", func() {
Context("when resource does not exist", func() {
It("returns empty list", func() {
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "default-pipeline", "non-existent-resource", fakeVariablesFactory)
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "default-pipeline", "non-existent-resource", fakeSecretManager)
Expect(err).ToNot(HaveOccurred())
Expect(containers).To(BeEmpty())
Expect(checkContainersExpiresAt).To(BeEmpty())
@ -2561,7 +2558,7 @@ var _ = Describe("Team", func() {
Context("when pipeline does not exist", func() {
It("returns empty list", func() {
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "non-existent-pipeline", "some-resource", fakeVariablesFactory)
containers, checkContainersExpiresAt, err := defaultTeam.FindCheckContainers(logger, "non-existent-pipeline", "some-resource", fakeSecretManager)
Expect(err).ToNot(HaveOccurred())
Expect(containers).To(BeEmpty())
Expect(checkContainersExpiresAt).To(BeEmpty())

View File

@ -20,7 +20,7 @@ type stepFactory struct {
resourceFetcher resource.Fetcher
resourceCacheFactory db.ResourceCacheFactory
resourceConfigFactory db.ResourceConfigFactory
variablesFactory creds.VariablesFactory
secretManager creds.Secrets
defaultLimits atc.ContainerLimits
strategy worker.ContainerPlacementStrategy
resourceFactory resource.ResourceFactory
@ -32,7 +32,7 @@ func NewStepFactory(
resourceFetcher resource.Fetcher,
resourceCacheFactory db.ResourceCacheFactory,
resourceConfigFactory db.ResourceConfigFactory,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
defaultLimits atc.ContainerLimits,
strategy worker.ContainerPlacementStrategy,
resourceFactory resource.ResourceFactory,
@ -43,7 +43,7 @@ func NewStepFactory(
resourceFetcher: resourceFetcher,
resourceCacheFactory: resourceCacheFactory,
resourceConfigFactory: resourceConfigFactory,
variablesFactory: variablesFactory,
secretManager: secretManager,
defaultLimits: defaultLimits,
strategy: strategy,
resourceFactory: resourceFactory,
@ -59,7 +59,7 @@ func (factory *stepFactory) GetStep(
) exec.Step {
workerMetadata.WorkingDirectory = resource.ResourcesDir("get")
variables := factory.variablesFactory.NewVariables(build.TeamName(), build.PipelineName())
variables := creds.NewVariables(factory.secretManager, build.TeamName(), build.PipelineName())
getStep := exec.NewGetStep(
build,
@ -99,7 +99,7 @@ func (factory *stepFactory) PutStep(
) exec.Step {
workerMetadata.WorkingDirectory = resource.ResourcesDir("put")
variables := factory.variablesFactory.NewVariables(build.TeamName(), build.PipelineName())
variables := creds.NewVariables(factory.secretManager, build.TeamName(), build.PipelineName())
var putInputs exec.PutInputs
if plan.Put.Inputs == nil {
@ -152,7 +152,7 @@ func (factory *stepFactory) TaskStep(
containerMetadata.WorkingDirectory = workingDirectory
credMgrVariables := factory.variablesFactory.NewVariables(build.TeamName(), build.PipelineName())
credMgrVariables := creds.NewVariables(factory.secretManager, build.TeamName(), build.PipelineName())
var taskConfigSource exec.TaskConfigSource
var taskVars []template.Variables

View File

@ -11,7 +11,6 @@ import (
"io/ioutil"
"code.cloudfoundry.org/lager/lagertest"
"github.com/cloudfoundry/bosh-cli/director/template"
"github.com/concourse/concourse/atc"
"github.com/concourse/concourse/atc/creds"
"github.com/concourse/concourse/atc/creds/credsfakes"
@ -40,7 +39,7 @@ var _ = Describe("GetStep", func() {
fakeStrategy *workerfakes.FakeContainerPlacementStrategy
fakeResourceFetcher *resourcefakes.FakeFetcher
fakeResourceCacheFactory *dbfakes.FakeResourceCacheFactory
fakeVariablesFactory *credsfakes.FakeVariablesFactory
fakeSecretManager *credsfakes.FakeSecrets
variables creds.Variables
fakeBuild *dbfakes.FakeBuild
fakeDelegate *execfakes.FakeGetDelegate
@ -78,11 +77,9 @@ var _ = Describe("GetStep", func() {
fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
fakeResourceCacheFactory = new(dbfakes.FakeResourceCacheFactory)
fakeVariablesFactory = new(credsfakes.FakeVariablesFactory)
variables = template.StaticVariables{
"source-param": "super-secret-source",
}
fakeVariablesFactory.NewVariablesReturns(variables)
fakeSecretManager = new(credsfakes.FakeSecrets)
fakeSecretManager.GetReturns("super-secret-source", nil, true, nil)
variables = creds.NewVariables(fakeSecretManager, "team", "pipeline")
artifactRepository = artifact.NewRepository()
state = new(execfakes.FakeRunState)
@ -132,7 +129,7 @@ var _ = Describe("GetStep", func() {
Get: getPlan,
}
variables := fakeVariablesFactory.NewVariables(fakeBuild.TeamName(), fakeBuild.PipelineName())
variables := creds.NewVariables(fakeSecretManager, fakeBuild.TeamName(), fakeBuild.PipelineName())
getStep = exec.NewGetStep(
fakeBuild,

View File

@ -26,7 +26,7 @@ type scannerFactory struct {
resourceTypeCheckingInterval time.Duration
resourceCheckingInterval time.Duration
externalURL string
variablesFactory creds.VariablesFactory
secretManager creds.Secrets
strategy worker.ContainerPlacementStrategy
}
@ -43,7 +43,7 @@ func NewScannerFactory(
resourceTypeCheckingInterval time.Duration,
resourceCheckingInterval time.Duration,
externalURL string,
variablesFactory creds.VariablesFactory,
secretManager creds.Secrets,
strategy worker.ContainerPlacementStrategy,
) ScannerFactory {
return &scannerFactory{
@ -53,13 +53,13 @@ func NewScannerFactory(
resourceCheckingInterval: resourceCheckingInterval,
resourceTypeCheckingInterval: resourceTypeCheckingInterval,
externalURL: externalURL,
variablesFactory: variablesFactory,
secretManager: secretManager,
strategy: strategy,
}
}
func (f *scannerFactory) NewResourceScanner(dbPipeline db.Pipeline) Scanner {
variables := f.variablesFactory.NewVariables(dbPipeline.TeamName(), dbPipeline.Name())
variables := creds.NewVariables(f.secretManager, dbPipeline.TeamName(), dbPipeline.Name())
return NewResourceScanner(
clock.NewClock(),
@ -75,7 +75,7 @@ func (f *scannerFactory) NewResourceScanner(dbPipeline db.Pipeline) Scanner {
}
func (f *scannerFactory) NewResourceTypeScanner(dbPipeline db.Pipeline) Scanner {
variables := f.variablesFactory.NewVariables(dbPipeline.TeamName(), dbPipeline.Name())
variables := creds.NewVariables(f.secretManager, dbPipeline.TeamName(), dbPipeline.Name())
return NewResourceTypeScanner(
clock.NewClock(),

2
go.mod
View File

@ -168,7 +168,7 @@ require (
github.com/ory-am/common v0.4.0 // indirect
github.com/ory/dockertest v3.3.2+incompatible // indirect
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pborman/uuid v1.2.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/peterhellberg/link v1.0.0

1
go.sum
View File

@ -135,6 +135,7 @@ github.com/coreos/etcd v3.2.9+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc3
github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v0.0.0-20170307191026-be73733bb8cc h1:9yuvA19Q5WFkLwJcMDoYm8m89ilzqZ5zEHqdvU+Zbds=
github.com/coreos/go-oidc v0.0.0-20170307191026-be73733bb8cc/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5LbakNExNmZIg=
github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=