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

Extract env var value read into a util #160

Merged
merged 2 commits into from
Sep 13, 2020
Merged

Extract env var value read into a util #160

merged 2 commits into from
Sep 13, 2020

Conversation

orenbm
Copy link
Member

@orenbm orenbm commented Sep 10, 2020

We would like to better UT the logic of reading a value from the env
and defaulting to a default value if it's not present. We also have the
logic that transforms this env var value into an int or a duration (and
maybe more in the future) and it is best that this logic is UTed and is
implemented in its own util. This improves the readability of the
config.go file and of the util functions themselves.

This will also let us use these utils in other projects that consume the
authn-client without implementing them there as well.

@orenbm orenbm requested a review from a team as a code owner September 10, 2020 09:01
@orenbm orenbm requested a review from izgeri September 10, 2020 09:01
}

envVarValue := envVarValueOrDefault(envVarName, defaultValue)
envVarValueDuration, err := time.ParseDuration(envVarValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this needed when you can just get it via 6*time.seconds?

Copy link
Member Author

Choose a reason for hiding this comment

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

but what if the value is from the env and not the default value? in that case it will be a string.

Copy link
Member Author

Choose a reason for hiding this comment

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

that's why i think it's more simple to have them both as strings. this will be the case for every env var regardless to the expected type. The value is always a string and the logic that converts it to the required type is in the util. it will create a uniformity throughout configurable values in this repo and in our other k8s projects.

Copy link
Contributor

Choose a reason for hiding this comment

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

the 6 would be from the env and timeseconds would be from the code

Copy link
Member Author

@orenbm orenbm Sep 10, 2020

Choose a reason for hiding this comment

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

i prefer not to do that

Copy link
Member Author

Choose a reason for hiding this comment

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

yep, this won't work in the current product. i get a ERROR: 2020/09/10 11:56:22 config.go:128: CAKC010E Failed to parse CONJUR_TOKEN_TIMEOUT. Reason: time: missing unit in duration 6 error.

@izgeri when this env var was created how did you intend to set it? with 6m0s? do you know who defined this?
do you see a problem with changing this to an int input so it will be CONJUR_TOKEN_TIMEOUT="6" instead of CONJUR_TOKEN_TIMEOUT="6m0s"?

Copy link
Member Author

Choose a reason for hiding this comment

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

once we get to a decision i will update the README with the valid value pattern.

Copy link
Member Author

@orenbm orenbm Sep 10, 2020

Choose a reason for hiding this comment

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

aha, i can see that this env var was used before as:

- name: CONJUR_TOKEN_TIMEOUT
+            value: "15s"

(saw it in Slack) so i wouldn't change the UX to be of int to not break changes.

Copy link
Member Author

Choose a reason for hiding this comment

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

@izgeri can you please review the commit i added with the readme change?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm pretty sure that CONJUR_TOKEN_TIMEOUT was added for dev troubleshooting, though I see it is documented in the README

I think it is wise to set a standard and enforce it, we should just make sure that the standard (and the default, btw) is clear

defaultValue string,
envVarValueOrDefaultFunc EnvVarValueOrDefaultFunc,
) (int, error) {
if envVarValueOrDefaultFunc == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

when would this not be nil? an example would help me understand what we are trying to accomplish here (:

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO this breaking up of parameters style is really hard for my 👀 to follow

Copy link
Member Author

Choose a reason for hiding this comment

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

look at the UT :)

Copy link
Member Author

Choose a reason for hiding this comment

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

this is the way we can pass mocks to the functions in go

Copy link
Member Author

Choose a reason for hiding this comment

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

IMO this breaking up of parameters style is really hard for my 👀 to follow

i understand. i had a conversation about this with @sgnn7 yesterday. the alternative is a line that is too long which is an issue as well.


// DefaultClientCertRetryCountLimit is the amount of times we wait after successful
// login for the client certificate file to exist, where each time we wait for a second.
DefaultClientCertRetryCountLimit = 10
DefaultClientCertRetryCountLimit = "10"
Copy link
Contributor

Choose a reason for hiding this comment

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

are there minimum or maximum values for this limit? Can they put -5 for example? Would the logic hold up against negative numbers?

Copy link
Contributor

Choose a reason for hiding this comment

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

In SP we added a check for envValueInt < minValue with a minValue being 0 in order to avoid this

Copy link
Member Author

Choose a reason for hiding this comment

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

hmmmm. do you think it is needed or is it too low level?

Copy link
Member Author

@orenbm orenbm Sep 10, 2020

Choose a reason for hiding this comment

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

do we want to not let users set (for example) CONJUR_TOKEN_TIMEOUT=-5? looks like too much of an effort to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

I can totally see this being an issue especially because it is configurable no? It is coming from an environment and I don't trust user input 🙃

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, but it adds another param that the function consumer needs to set where for most of the times it will be 0 and not interesting. i'll let @izgeri give her take on this before going forward.

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 should have error checking to ensure sane and acceptable values are input for this param. is the param currently documented? in the README or just in the CONTRIB? if it's just for dev purposes / dev troubleshooting, we could just leave undocumented for now. if we're adding it AND documenting it, then we need to be more careful (and this would need a changelog entry)

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 want to hold up this PR, but we should really consider doing a followup to allow for minimum/maximum range validation. In most cases, I'd bet that we can define hard-coded values for min and max values that are sensible and are liberal enough to cover all cases. For example, for certificate retries, we could set the minimum to 0 and the maximum to something astronomical (2**32 - 1?) If we don't add range checks, I don't think we know what would happen if someone sets retries or some other configured numbered value to -1 (I'm sure it's not in our integration testing).

Copy link
Member Author

Choose a reason for hiding this comment

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

by adding a minimum value we raise a few questions:

  1. what is the minimum value of CONJUR_TOKEN_TIMEOUT?
  2. what is the minimum value of CONJUR_CLIENT_CERT_RETRY_COUNT_LIMIT?
  3. what do we do in case the given value is less than the minimum? Do we fail the app or use the default value?
  4. does the answer to question kubernetes authenticator client works with conjur v5 #3 changes how we handle cases where the user gave an invalid value for CONJUR_TOKEN_TIMEOUT?

while these are good questions, i don't want to ask them in this PR and in this release that we are preparing. Once this is merged and the release is out there, we can look back at these questions and decide properly.

Copy link
Member Author

Choose a reason for hiding this comment

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

created an issue for this: #162

}
defaultConfig.ClientCertRetryCountLimit = clientCertRetryCountLimit

return defaultConfig, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

if we are updating this, can we change the name of this var? it's not the default config, it's the config that the client will use based on the default config and the user-provided config

Copy link
Member Author

Choose a reason for hiding this comment

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

sure, nice catch.

"github.com/cyberark/conjur-authn-k8s-client/pkg/log"
)

var defaultEnvVarValueOrDefault = envVarValueOrDefault
Copy link
Contributor

Choose a reason for hiding this comment

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

what is happening on this line?

Copy link
Member Author

Choose a reason for hiding this comment

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

we set the function as a variable so it's clearer to the reader that the function being used in the one defined at the env of the file

CHANGELOG.md Outdated
Comment on lines 10 to 14
- Username formatting now correctly only prints FullUsername field ([#126](https://github.com/cyberark/conjur-authn-k8s-client/issues/126))
- Timeout for token retrievals is now correctly reset in consequent failures
([cyberark/conjur-authn-k8s-client#158](https://github.com/cyberark/conjur-authn-k8s-client/issues/158))
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
- Username formatting now correctly only prints FullUsername field ([#126](https://github.com/cyberark/conjur-authn-k8s-client/issues/126))
- Timeout for token retrievals is now correctly reset in consequent failures
([cyberark/conjur-authn-k8s-client#158](https://github.com/cyberark/conjur-authn-k8s-client/issues/158))
- Username formatting now correctly only prints `FullUsername` field.
[cyberark/conjur-authn-k8s-client#126](https://github.com/cyberark/conjur-authn-k8s-client/issues/126))
- Timeout for token retrievals is now correctly reset in consecutive authentication failures.
[cyberark/conjur-authn-k8s-client#158](https://github.com/cyberark/conjur-authn-k8s-client/issues/158)

I am not sure the user impact from these changes is clear

for the first, is it that the log messages now print a more succinct name or a more detailed name? I'd call that out specifically

Logs now correctly print only the Conjur identity without the policy branch prefix.

for the second option, it's more like

When authentication fails, the exponential backoff retry is correctly reset so that it will continue to attempt to authenticate until backoff is exhausted.

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks, that is much better

CHANGELOG.md Outdated
Comment on lines 15 to 18
- Wait slightly for the client certificate file to exist after login before
raising an error.
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the user impact of this change? How does it affect the UX?

Copy link
Member Author

Choose a reason for hiding this comment

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

the client will fail less frequently as we now wait for up to 10 seconds to read the cert instead of immediately reading it. We are also logging our efforts so that's another customer-facing change. would you change the entry according to this?

izgeri
izgeri previously requested changes Sep 10, 2020
Copy link
Contributor

@izgeri izgeri left a comment

Choose a reason for hiding this comment

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

left a handful of comments, still would be good for @sgnn7 to weigh in since he chatted with you about some of the implementation details

README.md Outdated
@@ -38,7 +38,7 @@ questions, please contact us on [Discourse](https://discuss.cyberarkcommons.org/
- `CONJUR_AUTHN_URL`: URL pointing to authenticator service endpoint
- `CONJUR_AUTHN_LOGIN`: Host login for pod e.g. `namespace/service_account/some_service_account`
- `CONJUR_SSL_CERTIFICATE`: Public SSL cert for Conjur connection
- `CONJUR_TOKEN_TIMEOUT`: Timeout for fetching a new token (defaults to 6 minutes). In most cases, this variable should not be modified.
- `CONJUR_TOKEN_TIMEOUT`: Timeout for fetching a new token (defaults to 6 minutes). In most cases, this variable should not be modified. The value should be in a signed sequence of decimal numbers with optional fraction and unit suffix (e.g "6m0s")
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be broken into multiple lines

a signed sequence of decimal numbers with optional fraction and unit suffix
I don't know what this means without the example. is there a standard we could link to here?

Copy link
Member Author

Choose a reason for hiding this comment

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

i took it from the golang docs for time: https://golang.org/pkg/time/#ParseDuration. i'll add a link

Copy link
Member Author

Choose a reason for hiding this comment

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

how about The value should be in a format that can be parsed with [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) (e.g "6m0s")?

@@ -0,0 +1,71 @@
# Solution Design - Template
Copy link
Contributor

Choose a reason for hiding this comment

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

does this indicate your branch may need a rebase?

Copy link
Member Author

Choose a reason for hiding this comment

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

correct


// DefaultClientCertRetryCountLimit is the amount of times we wait after successful
// login for the client certificate file to exist, where each time we wait for a second.
DefaultClientCertRetryCountLimit = 10
DefaultClientCertRetryCountLimit = "10"
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 should have error checking to ensure sane and acceptable values are input for this param. is the param currently documented? in the README or just in the CONTRIB? if it's just for dev purposes / dev troubleshooting, we could just leave undocumented for now. if we're adding it AND documenting it, then we need to be more careful (and this would need a changelog entry)

pkg/utils/environment_variable.go Outdated Show resolved Hide resolved
pkg/utils/environment_variable.go Outdated Show resolved Hide resolved
}

envVarValue := envVarValueOrDefault(envVarName, defaultValue)
envVarValueDuration, err := time.ParseDuration(envVarValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm pretty sure that CONJUR_TOKEN_TIMEOUT was added for dev troubleshooting, though I see it is documented in the README

I think it is wise to set a standard and enforce it, we should just make sure that the standard (and the default, btw) is clear

@orenbm
Copy link
Member Author

orenbm commented Sep 10, 2020

I think we should have error checking to ensure sane and acceptable values are input for this param. is the param currently documented? in the README or just in the CONTRIB? if it's just for dev purposes / dev troubleshooting, we could just leave undocumented for now. if we're adding it AND documenting it, then we need to be more careful (and this would need a changelog entry)

CONJUR_CLIENT_CERT_RETRY_COUNT_LIMIT is for dev purposes so i'm leaving it undocumented. It is verified to be an int value when parsing it.

@orenbm
Copy link
Member Author

orenbm commented Sep 10, 2020

might be worth a comment - it's not a pattern I've seen before. maybe other devs would disagree but @sigalsax already had a question :)

@izgeri added a comment

@orenbm
Copy link
Member Author

orenbm commented Sep 10, 2020

I'm pretty sure that CONJUR_TOKEN_TIMEOUT was added for dev troubleshooting, though I see it is documented in the README. I think it is wise to set a standard and enforce it, we should just make sure that the standard (and the default, btw) is clear

we enforce it in DurationFromEnvOrDefault. an error will be raised if the value is not in the valid format.

the error will be CAKC010E Failed to parse CONJUR_TOKEN_TIMEOUT. Reason: time: invalid duration <given-value>

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.

This is good stuff, thanks for moving this logic to a re-usable util package!
I've included some suggestions on how the code might be refactored to simplify and maybe help readability and code coverage.

if err != nil {
// We return -1 as we can't return nil for int. This value will not be used
// as err is not nil.
return -1, log.RecordedError(log.CAKC010E, envVarName, err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

The convention that I've seen is that when returning an error along with other return values, the other return values should be set to their zero values (i.e. 0 for int returns).

I can't find anything official in Golang docs, but I did find this:
https://www.digitalocean.com/community/tutorials/handling-errors-in-go#:~:text=When%20a%20function%20returns%20multiple,value%20to%20a%20zero%20value.

When a function returns multiple values, including an error, convention requests that we return the error as the last item. When returning an error from a function with multiple return values, idiomatic Go code also will set each non-error value to a zero value. Zero values are, for example, an empty string for strings, 0 for integers, an empty struct for struct types, and nil for interface and pointer types, to name a few.

Copy link
Member Author

Choose a reason for hiding this comment

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

nice, great to learn this!

@sigalsax, @izgeri ^

Copy link
Contributor

@sigalsax sigalsax Sep 13, 2020

Choose a reason for hiding this comment

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

So I am understanding correctly if we return an error (for example return -1, log.RecordedError(log.CAKC010E, envVarName, err.Error())), no matter what, Golang will zero the return values? So for example, -1 will be 0 because it is an integer?
@diverdane @orenbm

Copy link
Member Author

Choose a reason for hiding this comment

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

i haven't verified it but in any case we will return 0 to maintain the standard

pkg/utils/environment_variable.go Outdated Show resolved Hide resolved
pkg/utils/environment_variable.go Outdated Show resolved Hide resolved
pkg/utils/environment_variable.go Outdated Show resolved Hide resolved
pkg/utils/environment_variable.go Outdated Show resolved Hide resolved
pkg/utils/environment_variable.go Outdated Show resolved Hide resolved
pkg/utils/environment_variable_test.go Outdated Show resolved Hide resolved
pkg/utils/environment_variable_test.go Outdated Show resolved Hide resolved
}

func TestEnvironmentVariable(t *testing.T) {
Convey("IntFromEnvOrDefault", t, func() {
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're deprecating the use of Convey? But personally I'm fine with it... it tells the story of what you're testing.

Copy link
Contributor

Choose a reason for hiding this comment

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

@diverdane what are we moving to if not Convey


// DefaultClientCertRetryCountLimit is the amount of times we wait after successful
// login for the client certificate file to exist, where each time we wait for a second.
DefaultClientCertRetryCountLimit = 10
DefaultClientCertRetryCountLimit = "10"
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 want to hold up this PR, but we should really consider doing a followup to allow for minimum/maximum range validation. In most cases, I'd bet that we can define hard-coded values for min and max values that are sensible and are liberal enough to cover all cases. For example, for certificate retries, we could set the minimum to 0 and the maximum to something astronomical (2**32 - 1?) If we don't add range checks, I don't think we know what would happen if someone sets retries or some other configured numbered value to -1 (I'm sure it's not in our integration testing).

@orenbm orenbm force-pushed the env-var-util branch 5 times, most recently from b206292 to bbea837 Compare September 13, 2020 10:14
We would like to better UT the logic of reading a value from the env
and defaulting to a default value if it's not present. We also have the
logic that transforms this env var value into an int or a duration (and
maybe more in the future) and it is best that this logic is UTed and is
implemented in its own util. This improves the readability of the
config.go file and of the util functions themselves.

This will also let us use these utils in other projects that consume the
authn-client without implementing them there as well.
Copy link
Contributor

@sigalsax sigalsax left a comment

Choose a reason for hiding this comment

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

LGTM great work!

@orenbm orenbm dismissed izgeri’s stale review September 13, 2020 10:38

Implemented requested changes

@orenbm orenbm merged commit f3f6d02 into master Sep 13, 2020
@orenbm orenbm deleted the env-var-util branch September 13, 2020 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants