-
Notifications
You must be signed in to change notification settings - Fork 38.8k
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
Support persisting config from kubecfg AuthProvider plugins #24304
Conversation
contextName string | ||
overrides *ConfigOverrides | ||
fallbackReader io.Reader | ||
persistAuthProviderConfig PersistAuthProviderConfigForUser |
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.
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.
@deads2k you mean strongly-typed persisters? How would one do that in Go, given the lack of generics, templating, etc.
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.
@deads2k you mean strongly-typed persisters? How would one do that in Go, given the lack of generics, templating, etc.
Yeah, that's what I meant, but without generics or templating I can see that the effort would be too great to be worth it.
My main fear is trying to put the burden of building the persistAuthProviderConfigForUser
on the caller to a new config. I don't think most callers will pass anything other than nil and that will produce problems later.
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.
Any caller that has the ability to persist should be able to handle providing a persister without too much trouble. A caller that doesn't have a notion of persisting the config may not be able to provide a persister, in which case the plugin should handle a nil persister gracefully.
Is that a reasonable assertion for now?
@@ -30,9 +30,16 @@ type AuthProvider interface { | |||
// WrapTransport allows the plugin to create a modified RoundTripper that | |||
// attaches authorization headers (or other info) to requests. | |||
WrapTransport(http.RoundTripper) http.RoundTripper | |||
// Login is expected to maybe require user interaction; it won't be called | |||
// in environments where a user is not present | |||
Login() 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.
I think we'll want to provide people the option for two different methods. Something like InteractiveAuthentication(io.Reader, io.Writer, io.Writer) error
and NonInteractiveAuthentication() error
. I'm pretty sure that @bobbyrullo's flow doesn't require user interaction in refresh cases (no matter where the refresh token actually lives).
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.
With that split, how would the caller know which one to call?
I'm guessing that the standard case for Interactive would be Stdin/Stdout. The provider plugin can create its own Reader/Writer if it needs them.
I know that we pass through os.Stdin
as the "fallbackReader" in the interactive basic-auth case. Are their expected cases where we think Stdin/Stdout won't be the right place for an interactive login?
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.
@deads2k: For the refresh case I don't think we need to add anything to the interface: The RoundTripper
that is returned by WrapTransport
should be able to handle that as necessary (eg, when short term creds are about to expire/have expired), as long as it can get a Persister.
So I think we only need a method for getting the initial credentials, which I would think it's safe to assume, will always require some sort of user interaction.
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.
@deads2k: Ping on this question. Do we need to disambiguate the flows at the plugin level? Can't we just defer to letting the plugin modify its config however it likes (either interactively or non-interactively)?
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.
@deads2k: Ping on this question. Do we need to disambiguate the flows at the plugin level? Can't we just defer to letting the plugin modify its config however it likes (either interactively or non-interactively)?
I feel like I'm missing something. If this requires user interaction, they'll need an io.Reader
, right? I could live with a nil
reader meaning, "non-interactive", but they'll still need one, right?
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 see. You're thinking we may want the option to interactively login through the command line (w/ Readers/Writers provided by kubectl)?
Neither @bobbyrullo's use case or mine would need this right now. Can we defer on adding this to the plugin until we have a user?
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.
Neither @bobbyrullo's use case or mine would need this right now. Can we defer on adding this to the plugin until we have a user?
If you update the comment to say that it "must not require user interaction", then ok.
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'm expecting that it might require user interaction, just not through the command line (passing an io.Reader). I'll change the comment to try to reflect this. I'm mostly going to defer its usage (and any necessary changes) to @bobbyrullo, because it isn't currently used anywhere.
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.
yes that's true - for my case, we'll spawn a browser.
@cjcullen : This all looks good for my case. |
} | ||
|
||
func PersistAuthConfigForUser(loadingRules *ClientConfigLoadingRules) func(string) restclient.AuthProviderConfigPersister { | ||
configAccess := NewDefaultPathOptions() |
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 doesn't look right. Seems like you should be building the ConfigAccess
from the LoadingRules
since they're giving (I think) the same information about where you're loading the config from.
It might even be appropriate to have the ClientConfig
interface have a method for getting its ConfigAccess
.
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.
Ok. I've tried to only pass around a ConfigAccess (which contains LoadingRules).
@@ -246,6 +248,11 @@ func (config *DirectClientConfig) Namespace() (string, bool, error) { | |||
return configContext.Namespace, overridden, nil | |||
} | |||
|
|||
// Namespace implements KubeConfig |
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.
bad comment?
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.
Yup. Fixed.
) | ||
|
||
// ConfigAccess is used by subcommands and methods in this package to load and modify the appropriate config files | ||
type ConfigAccess interface { |
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.
Can't ClientConfigLoadingRules
implement this interface?
GetLoadingPrecedence -> return Precedence
GetStartingConfig() -> call through to NewNonInteractiveDeferredLoadingClientConfig with no overrides?
GetDefaultFilename -> explicit if you have one, otherwise first existing from precedence, otherwise first from precedence
IsExplicitFilename -> len(explicitpath)
GetExplicitFile -> explicitPath
Seems like that about covers it and would eliminate the duplicate information.
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.
Well that makes everything a heck of a lot cleaner. Good catch.
That fell out pretty well. I think you can actually get the existing struct to conform to the interface to simplify a little more. |
Cleaned up the redundancy. If it looks good, I'd like to squash that last commit before merge. |
@@ -229,6 +229,54 @@ func (rules *ClientConfigLoadingRules) Migrate() error { | |||
return nil | |||
} | |||
|
|||
// GetLoadingPrecedence implements ClientConfig |
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.
ConfigAccess
here and elsewhere in this file?
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.
lgtm |
4969774
to
beb8515
Compare
Squashed & pushed. |
// config is the inital configuration for this plugin. | ||
// persister allows the plugin to save updated configuration. | ||
type Factory func(clusterAddress string, config map[string]string, persister AuthProviderConfigPersister) (AuthProvider, 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.
Would it make sense for the config to be a map[string]interface so that it could support lists of strings as well? I just ran into this case in the work I am doing; I could just as well have a comma-separated list of strings but it's not ideal.
Sorry for saying this so late in the game but it didn't occur to me until I started working with it.
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'd eventually (post 1.3) be interested in making the config use runtime.RawExtension/runtime.Object so that plugins could have strongly typed config. Would you be okay w/ map[string]string
until we move to that? If not, I'd be fine with you making the changes in a following PR.
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.
Would it make sense for the config to be a map[string]interface so that it could support lists of strings as well?
map[string]interface{}
won't work well because we're trying to serialize the data out into json.
map[string]string
works well, map[string][]string
also works fine. I don't have any objection to tweaking the type to map[string][]string
in this pull or in a very close followup. The datatype change would be a breaking serialization change for kubeconfig files, so we'd have to change it before anyone gets a chance to use it.
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.
Hmmm. I'd rather keep it as map[string]string
then.
@bobbyrullo, how bad would it be for your case to just use strings.Split
for a comma-separated list of strings?
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.
strings.Split
is just fine.
PR didn't actually change, I just rebased and repushed to try to unstick the pr-builder. |
GCE e2e build/test passed for commit 13a7d92. |
@k8s-bot test this [submit-queue is verifying that this PR is safe to merge] |
GCE e2e build/test passed for commit 13a7d92. |
Automatic merge from submit-queue |
Plumbs through an interface to the plugin that can persist a
map[string]string
config for just that plugin. Also addsconfig
to the AuthProvider serialization type, andLogin()
to the AuthProvider plugin interface.Modified the gcp AuthProvider to cache short-term access tokens in the kubecfg file.
Builds on #23066
@bobbyrullo @deads2k @jlowdermilk @erictune