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 Secrets Provider refresh interval annotations and timer #426

Merged
merged 1 commit into from
Feb 7, 2022

Conversation

rpothier
Copy link
Contributor

@rpothier rpothier commented Jan 18, 2022

Desired Outcome

Add annotations and a Go timer to periodically refresh secrets.

golang-gopher-wall-clock

Implemented Changes

Two new annotations have been added for provider refresh.
conjur.org/secrets-refresh-enabled
conjur.org/secrets-refresh-interval
also, side-car has been added to conjur.org/container-mode

Adds a go timer routine to call periodicProvider.
This is configured by adding a wrapper function to provideSecrets, if Secrets Provider
is configured in side-car mode a new goRouting is invoked that waits for a
timer tick that was configured with secrets-refresh-interval, and will update
the secrets on each tick.

Also had to optimize ValidateSecretsProviderSettings due to CodeClimate complaining
there were too many lines in the function.

Connected Issue/Story

Resolves #[relevant GitHub issue(s), e.g. 76]

CyberArk internal issue link: ONYX-15538
ONYX-16504

Definition of Done

  • Add support for new Annotations conjur.org/secrets-refresh-interval and conjur.org/secrets-refresh-enabled.
  • In Secrets Provider, call the provideSecrets() function periodically based on the new configuration.
  • Add UT for the parsing of the new Annotation.

Changelog

  • 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
  • A follow-up issue to update official docs has been filed here: insert issue ID
  • This PR does not require updating any documentation

Behavior

  • This PR changes product behavior and has been reviewed by a PO, or
  • These changes are part of a larger initiative that will be reviewed later, or
  • No behavior was changed with this PR

Security

  • Security architect has reviewed the changes in this PR,
  • These changes are part of a larger initiative with a separate security review, or
  • There are no security aspects to these changes

@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch 4 times, most recently from 1f62430 to 26f25ca Compare January 25, 2022 21:09
@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch from 26f25ca to 2d5edd8 Compare January 27, 2022 17:42
pkg/secrets/config/config.go Outdated Show resolved Hide resolved
@rpothier rpothier changed the title Add Secrets Provider refresh interval Add Secrets Provider refresh interval annotations and timer Jan 27, 2022
@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch from 2d5edd8 to 26c7838 Compare January 27, 2022 20:16
pkg/secrets/config/config.go Outdated Show resolved Hide resolved
@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch 3 times, most recently from d181ae8 to f53cc0a Compare January 28, 2022 18:46
@rpothier rpothier self-assigned this Jan 28, 2022
@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch 3 times, most recently from f04c267 to 7f43c99 Compare January 28, 2022 21:43
pkg/secrets/config/config.go Outdated Show resolved Hide resolved
@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch 7 times, most recently from 27f9b0f to b57062d Compare January 31, 2022 20:32
@rpothier rpothier marked this pull request as ready for review January 31, 2022 20:39
@rpothier rpothier requested a review from a team as a code owner January 31, 2022 20:39
@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch 2 times, most recently from bb49364 to 319786f Compare February 1, 2022 18:45
Copy link
Contributor

@szh szh left a comment

Choose a reason for hiding this comment

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

Looking great! Left a couple of questions

pkg/secrets/provide_conjur_secrets_test.go Outdated Show resolved Hide resolved
cmd/secrets-provider/main.go Outdated Show resolved Hide resolved
@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch from 319786f to 94a1caa Compare February 1, 2022 20:25
return storeType, err
}

func validRefreshInterval(intervalStr string, enableStr string, containerMode string) error {
Copy link

Choose a reason for hiding this comment

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

Function validRefreshInterval has 5 return statements (exceeds 4 allowed).

@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch 2 times, most recently from 9aa12a7 to da0db49 Compare February 2, 2022 01:38
Copy link
Contributor

@szh szh left a comment

Choose a reason for hiding this comment

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

Looks great! Just one comment

PUSH_TO_FILE.md Outdated
| `conjur.org/secrets-destination` | `SECRETS_DESTINATION` | Allowed values: <ul><li>`file`</li><li>`k8s_secrets`</li></ul> |
| `conjur.org/k8s-secrets` | `K8S_SECRETS` | This list is ignored when `conjur.org/secrets-destination` annotation is set to **`file`** |
| `conjur.org/retry-count-limit` | `RETRY_COUNT_LIMIT` | Defaults to 5
| `conjur.org/retry-interval-sec` | `RETRY_INTERVAL_SEC` | Defaults to 1 (sec) |
| `conjur.org/debug-logging` | `DEBUG` | Defaults to `false` |
| `conjur.org/secrets-refresh-enabled`| Note\* | Defaults to `false`. Secrets Provider will exit with error if this is explicitly set to `false` and `conjur.org/secrets-rotation-interval` is explicitly set. |
| `conjur.org/secrets-refresh-interval` | Note\* | Set to a valid duration string as defined [here](https://pkg.go.dev/time#ParseDuration). Valid time units are `s`, `m`, and `h` (for seconds, minutes, and hours, respectively). Some examples of valid duration strings:<ul><li>`5m`</li><li>`2h30m`</li><li>`48h`</li></ul>The minimum refresh interval is 1 second. A refresh interval of 0 seconds is treated as a fatal configuration error. The maximum refresh interval is approximately 290 years. |
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
| `conjur.org/secrets-refresh-interval` | Note\* | Set to a valid duration string as defined [here](https://pkg.go.dev/time#ParseDuration). Valid time units are `s`, `m`, and `h` (for seconds, minutes, and hours, respectively). Some examples of valid duration strings:<ul><li>`5m`</li><li>`2h30m`</li><li>`48h`</li></ul>The minimum refresh interval is 1 second. A refresh interval of 0 seconds is treated as a fatal configuration error. The maximum refresh interval is approximately 290 years. |
| `conjur.org/secrets-refresh-interval` | Note\* | Set to a valid duration string as defined [here](https://pkg.go.dev/time#ParseDuration). Valid time units are `s`, `m`, and `h` (for seconds, minutes, and hours, respectively). Some examples of valid duration strings:<ul><li>`5m`</li><li>`2h30m`</li><li>`48h`</li></ul>The minimum refresh interval is 1 second. A refresh interval of 0 seconds is treated as a fatal configuration error. The maximum refresh interval is 100 years. |

to match https://github.com/cyberark/secrets-provider-for-k8s/pull/426/files#diff-1a2f43d8162d071687f1a5864ce5b6b825e9f25d5a74a1df1c218bbbfe597a33R25

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, would it be better to leave this and change the default timeout?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it makes much of a difference, 100 or 290.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on other offline conversation, I'm removing the timeout so this is no longer an issue.

@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch 4 times, most recently from d35c8e6 to a3d4ed4 Compare February 3, 2022 14:34
PUSH_TO_FILE.md Outdated
@@ -323,12 +323,14 @@ for a description of each environment variable setting:
| K8s Annotation | Equivalent<br>Environment Variable | Description, Notes |
|-----------------------------------------|---------------------|----------------------------------|
| `conjur.org/authn-identity` | `CONJUR_AUTHN_LOGIN` | Required value. Example: `host/conjur/authn-k8s/cluster/apps/inventory-api` |
| `conjur.org/container-mode` | `CONTAINER_MODE` | Allowed values: <ul><li>`init`</li><li>`application`</li></ul>Defaults to `init`.<br>Must be set (or default) to `init` for Push to File mode.|
| `conjur.org/container-mode` | `CONTAINER_MODE` | Allowed values: <ul><li>`init`</li><li>`application`</li><li>`side-car`</li></ul>Defaults to `init`.<br>Must be set (or default) to `init` or `side-car`for Push to File mode.|
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo, side-car should be single word sidecar. Same throughout the rest of the changes.
Also, we should consider making the sidecar option the default, but probably should discuss with Alex.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks for catching that, updated.

@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch from a3d4ed4 to 3d2ba81 Compare February 3, 2022 15:40
getContainerMode(),
provideSecrets,
// Set a large timeout, basically forever
secretsConfigProvider.MaxRefreshTimeout,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this MaxRefreshTimeout used for implementing a close proximity for "sleep forever" functionality? If so, I think we can do this differently, and have the SP block forever.

pkg/secrets/provide_conjur_secrets.go Outdated Show resolved Hide resolved
PUSH_TO_FILE.md Outdated
| `conjur.org/secrets-destination` | `SECRETS_DESTINATION` | Allowed values: <ul><li>`file`</li><li>`k8s_secrets`</li></ul> |
| `conjur.org/k8s-secrets` | `K8S_SECRETS` | This list is ignored when `conjur.org/secrets-destination` annotation is set to **`file`** |
| `conjur.org/retry-count-limit` | `RETRY_COUNT_LIMIT` | Defaults to 5
| `conjur.org/retry-interval-sec` | `RETRY_INTERVAL_SEC` | Defaults to 1 (sec) |
| `conjur.org/debug-logging` | `DEBUG` | Defaults to `false` |
| `conjur.org/secrets-refresh-enabled`| Note\* | Defaults to `false`. Secrets Provider will exit with error if this is explicitly set to `false` and `conjur.org/secrets-rotation-interval` is explicitly set. |
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer what you have here: having the secrets-refresh-enabled setting simply default to false, rather then defaulting to true or false depending upon whether secrets-refresh-enabled is explicitly set. The latter way is more convenient in some ways, but it's MUCH more difficult to document. I think we need to get an okay from Alex on this, since I think his expectation is the latter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The implementation does not require a true if the secrets-refresh-interval set. So this needs to be updated still.

PUSH_TO_FILE.md Outdated
| `conjur.org/secrets-destination` | `SECRETS_DESTINATION` | Allowed values: <ul><li>`file`</li><li>`k8s_secrets`</li></ul> |
| `conjur.org/k8s-secrets` | `K8S_SECRETS` | This list is ignored when `conjur.org/secrets-destination` annotation is set to **`file`** |
| `conjur.org/retry-count-limit` | `RETRY_COUNT_LIMIT` | Defaults to 5
| `conjur.org/retry-interval-sec` | `RETRY_INTERVAL_SEC` | Defaults to 1 (sec) |
| `conjur.org/debug-logging` | `DEBUG` | Defaults to `false` |
| `conjur.org/secrets-refresh-enabled`| Note\* | Defaults to `false`. Secrets Provider will exit with error if this is explicitly set to `false` and `conjur.org/secrets-rotation-interval` is explicitly set. |
| `conjur.org/secrets-refresh-interval` | Note\* | Set to a valid duration string as defined [here](https://pkg.go.dev/time#ParseDuration). Valid time units are `s`, `m`, and `h` (for seconds, minutes, and hours, respectively). Some examples of valid duration strings:<ul><li>`5m`</li><li>`2h30m`</li><li>`48h`</li></ul>The minimum refresh interval is 1 second. A refresh interval of 0 seconds is treated as a fatal configuration error. The maximum refresh interval is approximately 290 years. |
Copy link
Contributor

Choose a reason for hiding this comment

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

Before "The minimum refresh interval is 1 second", it should also say "The default refresh interval is 5 minutes."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.

@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch 4 times, most recently from 58cbe8e to ed8b6cb Compare February 4, 2022 20:26
@@ -186,6 +184,12 @@ func retryableSecretsProvider(
secretsConfig.RetryCountLimit,
provideSecrets,
)

provideSecrets = secrets.PeriodicSecretProvider(
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this function is now returning a PeriodicSecretProvider, should the function be renamed periodicSecretsProvider or similar? Or alternatively, move the call to secrets.PeriodicSecretProvider outside of this retryableSecretsProvider() function.... unless you're adding this to get by some CodeClimate limitation e.g. on number of return statements in another function?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe just rename it to something very generic e.g. "SecretProvider()"? Not sure what makes sense here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea, as it's not always periodic, we could go with SecretProvider()

logError(errStr)
go provideSecrets()
select {
case err = <-secrets.SecretProviderError:
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like secrets.SecretProviderError is currently a global variable. Can we change this so that the error channel is created dynamically in secrets.PeriodicSecretProvider(), and passed up the call stack and returned to this function when we call retryableSecretsProvider() on line 83 above?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at this again, I think the logic of calling a goroutine and providing an error channel should be moved to the pkg/secrets package. Generally, we want the main.go to be as simple as possible, and I think that this implementation detail for periodic secrets provider function should be abstracted away from main.go.

We would still need to do exactly what you're doing here inside the test code, since that will need to have a goroutine that can either run once and return, run once and sleep, or run periodically... and be able to stop that goroutine from the test thread.

To see what I'm suggesting, take a look at these diffs that I created on a branch based on your PR branch:
d61c96c

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Patched in the diffs.

@@ -46,17 +52,38 @@ type annotationRestraints struct {
allowedValues []string
}

// Annotations used to support Conjur secrets rotation.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment needs to be updated. The Annotations listed aren't just for secrets rotation, but for all SP non-push-to-file Annotations.

storeType := ""
var err error

if annotStoreType == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Your call on this, but since you're rewriting some of this already.... Golang coding style recommends using switch block rather than if/else if/else block. So this section would have the form:

    switch {
        case annotStoreType == "":
            // Do things
        case validStoreType(annotStoreType):
            // Do other things
        default:
            // Log an error
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

logError(errStr)
go provideSecrets()
select {
case err = <-secrets.SecretProviderError:
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at this again, I think the logic of calling a goroutine and providing an error channel should be moved to the pkg/secrets package. Generally, we want the main.go to be as simple as possible, and I think that this implementation detail for periodic secrets provider function should be abstracted away from main.go.

We would still need to do exactly what you're doing here inside the test code, since that will need to have a goroutine that can either run once and return, run once and sleep, or run periodically... and be able to stop that goroutine from the test thread.

To see what I'm suggesting, take a look at these diffs that I created on a branch based on your PR branch:
d61c96c

@@ -186,6 +184,12 @@ func retryableSecretsProvider(
secretsConfig.RetryCountLimit,
provideSecrets,
)

provideSecrets = secrets.PeriodicSecretProvider(
Copy link
Contributor

Choose a reason for hiding this comment

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

Or maybe just rename it to something very generic e.g. "SecretProvider()"? Not sure what makes sense here.

}
if mode == "sidecar" && secretRefreshInterval > 0{
TickerQuit <- true
time.Sleep(1 * time.Second) // give a second for the go routine to shut down
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be better to define this as a const at the top of the file so it's easy to find/change.

@@ -97,3 +96,55 @@ func RetryableSecretProvider(
return err
}
}

var TickerQuit = make(chan bool)
Copy link
Contributor

Choose a reason for hiding this comment

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

These three channels don't need to be defined/declared as global variables.

To make this testable, the "ProviderDone" channel should passed in from the caller to PeriodicSecretProvider (in main.go, we'll pass it a channel that's not used for now, but in the future we can add a signal handler that can use this channel to stop the SP).

See diffs here:
d61c96c#diff-c083eb56e1c331264db79fbb96d5fbb5ecfd8f082e9dde16e1a0e28f5f0f4230L100-R119

Copy link
Contributor Author

Choose a reason for hiding this comment

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

made suggested changes.

}
}

func periodicSecretProvider(provideSecrets ProviderFunc, ticker *time.Ticker) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can pass in the error channel (for output) and the quit channel (for input) to this function, so that you won't have to use global variables.

See: d61c96c#diff-c083eb56e1c331264db79fbb96d5fbb5ecfd8f082e9dde16e1a0e28f5f0f4230L136-R164

const (
providerDelayTime = 500
)
var providerCount = 0
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the reason that we're seeing some flaky test cases below (the ones that are commented out) is that we're using one common providerCount gobal variable for all test cases. It makes testing more complex when you're testing slow provider functionality because you have to wait for some grace period at the end of each test case for the previous test case's slow provider to finish.

Generally, we want each test case to have its own state. So this should be implemented as a structure that has a provide() method, and instantiate one of those for each test case.

To see what I mean, check out this mocking:
d61c96c#diff-c0314624500148cb0ccac2409ef0d4dc9a8cddf7603e8879bae326cc2d96d7d5R17-R52

This mocking works, except that I haven't reconciled it with the eventualProvider() function, which depends upon those base objects.

@@ -14,11 +14,31 @@ import (
"github.com/stretchr/testify/assert"
)

const (
providerDelayTime = 500
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can get by with 10s of milliseconds instead of 100s.
For example, this seems to work:
d61c96c#diff-c0314624500148cb0ccac2409ef0d4dc9a8cddf7603e8879bae326cc2d96d7d5L132-R176

Copy link
Contributor Author

Choose a reason for hiding this comment

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

made the change.

@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch from ed8b6cb to 11991d4 Compare February 7, 2022 15:22
@rpothier rpothier force-pushed the ONYX-15538-secrets-rotation branch from 11991d4 to 50bb93e Compare February 7, 2022 18:42
@rpothier rpothier marked this pull request as draft February 7, 2022 18:43
@codeclimate
Copy link

codeclimate bot commented Feb 7, 2022

Code Climate has analyzed commit 50bb93e and detected 0 issues on this pull request.

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

This pull request will bring the total coverage in the repository to 93.1% (0.5% change).

View more on Code Climate.

@rpothier rpothier marked this pull request as ready for review February 7, 2022 19:39
Copy link
Contributor

@szh szh left a comment

Choose a reason for hiding this comment

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

🎉

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.

3 participants