Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add startup logic to enable Push-to-File #377

Merged
merged 9 commits into from
Oct 29, 2021
Merged

Add startup logic to enable Push-to-File #377

merged 9 commits into from
Oct 29, 2021

Conversation

john-odonnell
Copy link
Contributor

@john-odonnell john-odonnell commented Oct 22, 2021

What does this PR do?

  • Adds new type ProviderFunc func() error
    • Represents an ambiguous style of secret provider, pushing secrets either to K8s Secrets or to File
  • func NewProviderForType(...) (ProviderFunc, []error)
    • Returns a ProviderFunc given SP configuration
  • func RetryableSecretProvider(...) ProviderFunc
    • Wraps a given ProviderFunc in a limitedBackOff regulated Retry loop
    • Maintains the general ProviderFunc type
  • New provider type structs in k8s_secrets_storage and pushtofile packages
    • NewProvider(...) defines dependencies required by each process
    • Each provider has an attached Provide() method which Implements the ProviderFunc type
  • Implementation details of retrieving secrets from Conjur, specifically generating an access token, is relegated to the conjur package
  • The above changes are reflected in main.go

What ticket does this PR close?

ONYX-12537

Checklists

Change log

  • The CHANGELOG has been updated, or
  • This PR does not include user-facing changes and doesn't require a CHANGELOG update

Test coverage

  • This PR includes new unit and integration tests to go with the code changes, or
  • The changes in this PR do not require tests

Documentation

  • Docs (e.g. READMEs) were updated in this PR, and/or there is a follow-on issue to update docs, or
  • This PR does not require updating any documentation

@john-odonnell john-odonnell force-pushed the startup-logic branch 2 times, most recently from c314d85 to ed71869 Compare October 22, 2021 21:35
Base automatically changed from ONYX-11974 to main October 26, 2021 14:17
if _, err := os.Stat(annotationsFile); err == nil {
annotationsMap, err = annotations.NewAnnotationsFromFile(annotationsFile)
// Only attempt to populate from annotations if the annotations file exists
// TODO: Figure out strategy for dealing with explicit annotation file path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would the annotation file path be anything other than /conjur/podinfo ? I don't think it makes sense that the file path would be user configurable...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is actually from #367 by @doodlesbykumbi. GH did a weird auto-rebase on my branch, but I've corrected it since.

I think I would agree with you here, though. The location of the annotations file seems like an internal detail rather than something the user should be concerned with. I feel differently about a configurable secrets file base path, though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of the fixed paths /conjur/podinfo and the /conjur/secrets are from the perspective of the Secrets Provider container only. For the application container, the user can mount the corresponding volumes at any arbitrary mountPaths (though they might not care about the podinfo volume).

We intentionally left out "config knobs" for these SP mountPaths to simplify the user experience and so that we have two less things that can get misconfigured.

With that in mind, we can probably change defaultAnnotationsFile back to annotationsFile, and defaultSecretBasePath to secretBasePath.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My motivation for making these values configurable is for

  1. For testing purposes we should be able to specify arbitrary mouth paths.
  2. Potentially supporting dry run capabilities. I acknowledge this as a nice-to-have, and something that might require a separate dedicated discussion.

We don't have to expose these as config knobs to the user, but I think the hardcoded value should only be present in ./main.go.
I opted for defaultAnnotationsFile vs annotationsFile to specifically call out the fact that this is the default, but perhaps that causes some confusion due to the word "default" implying that there are other options. Perhaps a better name would be fixedAnnotationsFilePath.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to annotationsFilePath and secretBasePath. When they're made "configurable" in the context of testing/dry run, then we can change it to something more suitable.

provideConjurSecretFunc = k8s_secrets_storage.ProvideConjurSecretsToK8sSecrets
// NewProviderForType returns a ProviderFunc responsible for retrieving and providing secrets in a given StoreType mode.
func NewProviderForType(
retrievek8sSecret k8s.RetrieveK8sSecretFunc,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function NewProviderForType has 7 arguments (exceeds 4 allowed). Consider refactoring.

pkg/secrets/k8s_secrets_storage/provide_conjur_secrets.go Outdated Show resolved Hide resolved

// NewProvider creates a new secret provider for K8s Secrets mode.
func NewProvider(
retrievek8sSecret k8s.RetrieveK8sSecretFunc,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function NewProvider has 5 arguments (exceeds 4 allowed). Consider refactoring.

pkg/secrets/k8s_secrets_storage/provide_conjur_secrets.go Outdated Show resolved Hide resolved
@john-odonnell john-odonnell marked this pull request as ready for review October 26, 2021 18:51
@john-odonnell john-odonnell requested review from a team as code owners October 26, 2021 18:51
}

// Provide implements a ProviderFunc to retrieve and push secrets to K8s secrets.
func (p k8sProvider) Provide() error {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method k8sProvider.Provide has 6 return statements (exceeds 4 allowed).

Copy link
Contributor

@diverdane diverdane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!!! Nice work on the provider retry wrapper, and on adding testability for the retry mechanism!

if _, err := os.Stat(annotationsFile); err == nil {
annotationsMap, err = annotations.NewAnnotationsFromFile(annotationsFile)
// Only attempt to populate from annotations if the annotations file exists
// TODO: Figure out strategy for dealing with explicit annotation file path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of the fixed paths /conjur/podinfo and the /conjur/secrets are from the perspective of the Secrets Provider container only. For the application container, the user can mount the corresponding volumes at any arbitrary mountPaths (though they might not care about the podinfo volume).

We intentionally left out "config knobs" for these SP mountPaths to simplify the user experience and so that we have two less things that can get misconfigured.

With that in mind, we can probably change defaultAnnotationsFile back to annotationsFile, and defaultSecretBasePath to secretBasePath.

secretsConfig := setupSecretsConfig()

provideConjurSecrets, err := secrets.GetProvideConjurSecretFunc(secretsConfig.StoreType)
// Initialize a Conjur Secret Fetcher
secretRetriver, err := conjur.NewConjurSecretRetriever(*authnConfig)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: secretRetriver should be secretRetriever.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

)
logErrorsAndConditionalExit(errs, nil, messages.CSPFK053E)

provideSecrets = secrets.RetryableSecretProvider(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I like the RetryableSecretProvider(...) wrapper!

return nil, log.RecordedError(messages.CSPFK010E)
}

// NOTE: the token is cleanup up by whoever created it!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be "the token is cleaned up"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this comment entirely - seems repetitive given the comment a few lines later:

// Always delete the access token. The deletion is idempotent and never fails
defer authn.AccessToken.Delete()

if err != nil {
return nil, log.RecordedError(messages.CSPFK002E)
}
// Always delete the access token. The deletion idempotent and never fails
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be "The deletion is idempotent"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -74,3 +74,6 @@ const CSPFK049E string = "CSPFK049E Failed to validate Pod annotations"
const CSPFK050E string = "CSPFK050E Failed to unmarshal Push-to-File secrets. Reason: %s"
const CSPFK051E string = "CSPFK051E Unknown file format '%s'"
const CSPFK052E string = "CSPFK052E Failed to retrieve secrets. Reason: %s"

const CSPFK053E string = "CSPFK053E Unable to initialize Secrets Provider: unable to create secret group collection"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh-oh, I used the same CSPFK053E error code. Looks like we're going to need to arm wrestle for it.

Copy link
Contributor Author

@john-odonnell john-odonnell Oct 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Branching thought off of this...

This file has all error messages numbered sequentially, but it also has them loosely grouped by area of concern, separated by comments, ie. // General, // Push to File, etc.

I've added CSPFK054E, which arises when SP fails for a bad StoreType - which is more of a // General failure. Moving this error to the // General section would require changing the identifiers for every error message declared afterwords. Updating the numbering style might help the error messages to be more extensible and static through future changes.

I think if the messages here are to be grouped by area of concern, then maybe they should be numbered within their area of concern:

const CSPFK-A001E string = "first message concerned with aspect A"
const CSPFK-A002E string = "second message concerned with aspect A"

const CSPFK-B001E string = "first message concerned with aspect B"

This also might help to avoid collisions like this one! (EDIT: not this one exactly, because we're both adding to the same concern group w/ 053E, but similar cases)


for _, tc := range TestCases {
var logBuffer bytes.Buffer
logger.InfoLogger = log.New(&logBuffer, "", 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logger.InfoLogger is a global, exported variable for the authn-k8s-client log package. So if we ever run tests in parallel, I think we could have multiple tests contending for this global setting?

At some point, we'll probably want to revisit how this is done in the conjur-authn-client log package... maybe have it return the info logger to the entity that's using the log package, rather than storing the info logger is as a global, exported variable?

@john-odonnell john-odonnell force-pushed the startup-logic branch 2 times, most recently from 015eb46 to f2d66a1 Compare October 27, 2021 13:34
@codeclimate
Copy link

codeclimate bot commented Oct 27, 2021

Code Climate has analyzed commit bc9fffa and detected 3 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 3

The test coverage on the diff in this pull request is 63.1% (50% is the threshold).

This pull request will bring the total coverage in the repository to 87.6% (-1.6% change).

View more on Code Climate.

Copy link
Contributor

@diverdane diverdane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
Thanks for the final push in getting us across the E2E goal line!
I think we should merge this as is, and handle these with separate, smaller PRs:

  • Issue with having to specify full secret file path
  • Code Climate issues
    I can work on the CC issues if you'd like... your choice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants