-
Notifications
You must be signed in to change notification settings - Fork 11
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
Conversation
1f62430
to
26f25ca
Compare
26f25ca
to
2d5edd8
Compare
2d5edd8
to
26c7838
Compare
d181ae8
to
f53cc0a
Compare
f04c267
to
7f43c99
Compare
27f9b0f
to
b57062d
Compare
bb49364
to
319786f
Compare
There was a problem hiding this 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
319786f
to
94a1caa
Compare
return storeType, err | ||
} | ||
|
||
func validRefreshInterval(intervalStr string, enableStr string, containerMode string) error { |
There was a problem hiding this comment.
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).
9aa12a7
to
da0db49
Compare
There was a problem hiding this 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. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| `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. | |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
d35c8e6
to
a3d4ed4
Compare
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.| |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
a3d4ed4
to
3d2ba81
Compare
cmd/secrets-provider/main.go
Outdated
getContainerMode(), | ||
provideSecrets, | ||
// Set a large timeout, basically forever | ||
secretsConfigProvider.MaxRefreshTimeout, |
There was a problem hiding this comment.
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.
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. | |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. | |
There was a problem hiding this comment.
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."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
58cbe8e
to
ed8b6cb
Compare
@@ -186,6 +184,12 @@ func retryableSecretsProvider( | |||
secretsConfig.RetryCountLimit, | |||
provideSecrets, | |||
) | |||
|
|||
provideSecrets = secrets.PeriodicSecretProvider( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()
cmd/secrets-provider/main.go
Outdated
logError(errStr) | ||
go provideSecrets() | ||
select { | ||
case err = <-secrets.SecretProviderError: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patched in the diffs.
pkg/secrets/config/config.go
Outdated
@@ -46,17 +52,38 @@ type annotationRestraints struct { | |||
allowedValues []string | |||
} | |||
|
|||
// Annotations used to support Conjur secrets rotation. |
There was a problem hiding this comment.
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.
pkg/secrets/config/config.go
Outdated
storeType := "" | ||
var err error | ||
|
||
if annotStoreType == "" { |
There was a problem hiding this comment.
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
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
cmd/secrets-provider/main.go
Outdated
logError(errStr) | ||
go provideSecrets() | ||
select { | ||
case err = <-secrets.SecretProviderError: |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
made the change.
ed8b6cb
to
11991d4
Compare
11991d4
to
50bb93e
Compare
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
Desired Outcome
Add annotations and a Go timer to periodically refresh secrets.
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 toconjur.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
conjur.org/secrets-refresh-interval
andconjur.org/secrets-refresh-enabled
.provideSecrets()
function periodically based on the new configuration.Changelog
CHANGELOG update
Test coverage
changes, or
Documentation
README
s) were updated in this PRBehavior
Security