E542 [Context-Switch] Foundation of fast context switch by simonferquel · Pull Request #38148 · moby/moby · GitHub
[go: up one dir, main page]

Skip to content

Conversation

simonferquel
Copy link
Contributor
@simonferquel simonferquel commented Nov 6, 2018

- What I did
This is the foundation of a feature we at Docker want to bring to the docker client ecosystem: A way to store credentials to connect to different clusters and quickly switch from one to the other. The UX related to this PR will mostly appear in PRs against docker CLI, but everything required to interact with the docker context-store will live in the moby/moby repo (mostly to avoid dependent projects to have to vendor Docker cli)

- How I did it
First, I introduced a pkg/contextstore package that does the actual context storage. A context is a named entity that contains arbitrary metadata, and references named endpoints (on the moby/moby side only one endpoint is actually created/consumed, but on the CLI side, we'll complement those contexts with endpoints targeting Kubernetes for our Compose on Kubernetes support). An endpoint also contains arbitrary metadata plus references to TLS materials. The contextstore actually stores metadata and TLS material in different directories to make it easy to put different access rights on them, or put sensitive data on an encrypted filesystem.

Then the client/context package provides facilities to create and parse docker contexts and endpoints in order to configure a client

Client package has been updated with a new WithContextStoreOrEnv that will interact with the context store to configure the client, or fallback to the "legacy" environment variables if there is no context data.

- How to verify it

The PR comes with unit tests. User testing will be available soon with the docker/cli PRs coming

- Description for the changelog

Introduce a context store to make it easy to store and use different sets of credentials to connect to different engines

@simonferquel
Copy link
Contributor Author

@simonferquel simonferquel force-pushed the use-context-store branch 2 times, most recently from a302e27 to 491deb8 Compare November 6, 2018 18:05
Copy link
Member

Choose a reason for hiding this comment

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

I think this interface should support DOCKER_HOST=ssh:// credentials as well. (Implementation can be follow-up PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but if I understand it correctly, the ssh support is purely cli-side. So I think we'll solve that in the cli.
And that is one thing I kind of dislike with the ssh support: it seems very difficult for 3rd party clients to leverage it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@AkihiroSuda btw, do you think it is possible to modify the ssh implementation to bring it to moby/moby/client ? I did not dig sufficiently into that subject to have a clear vision about it, but that might both simplify things a lot for the context-store integration, and makes it available to 3rd party client tools.

Copy link
Member

Choose a reason for hiding this comment

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

Technically yes, although I'm not sure we want to introduce docker system dial-stdio dependency on this repo

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It feels like the subject needs a bit more discussion, and on my side a bit more digging into the design of the ssh support.
What I plan to do is to open PRs on the CLI side without supporting connection helpers, and after that, we can discuss how to make all this fit together

Copy link
Member

Choose a reason for hiding this comment

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

FYI the design of the SSH support has a long story.. docker/go-connections#39 (comment)

@simonferquel
Copy link
Contributor Author

ping @dhiltgen @mavenugo

@codecov
Copy link
codecov bot commented Nov 7, 2018

Codecov Report

❗ No coverage uploaded for pull request base (master@bd224b5). Click here to learn what that means.
The diff coverage is 63.6%.

@@            Coverage Diff            @@
##             master   #38148   +/-   ##
=========================================
  Coverage          ?    36.3%           
=========================================
  Files             ?      614           
  Lines             ?    45539           
  Branches          ?        0           
=========================================
  Hits              ?    16531           
  Misses            ?    26726           
  Partials          ?     2282

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
AF2F
Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Copy link
Contributor
@ijc ijc left a comment

Choose a reason for hiding this comment

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

Went through the client subpackage only and GH is telling me "This page is out of date" so I'll submit this and carry on with pkg/contextstore separately rather than review a potentially obsolete version.

Nothing major so far though.

// if contextNameOverride is set -> load this context from context store
// if DOCKER_CONTEXT env variable is set -> load this context from context store
// if DOCKER_HOST is set -> fallback to FromEnv
// if a current context is set in the store -> load this context
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have a TODO to cause the documentation to be updated? (Seems to not be in this repo)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice catch. @thaJeztah where should we put this kind of documentation (e.g.: now the preferred way to initialize a client is to use WithContextStoreOrEnv)?

// if DOCKER_HOST is set -> fallback to FromEnv
// if a current context is set in the store -> load this context
// fallback to "default" client
func WithContextStoreOrEnv(contextStoreDir, contextNameOverride string) func(*Client) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

I appreciate there is precedent here in WithTLSClientConfig but it seems a bit odd to me to be requiring all callers to opt in to what really ought to be the default behaviour.

IOW -- perhaps the functionality here ought to be folded in to NewClientWithOpts (in place of the call to defaultHTTPClient).

More a question for the maintainers rather than the author I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah... the thing is it also introduce the 2 new parameters. Do we really want every client code to specify those, even if at the end they do things like WithHost("/var/run/docker.sock") ?

Copy link
Contributor

Choose a reason for hiding this comment

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

They both have sensible defaults, so could be separate independent WithContextStore and WithContextName, but without them the default could be to use the default/current context from the default store.

So (IMHO) NewClientWithOptions() with E839 no WithFoo options given should DTRT.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am waiting for feedback on this. I know that at the time being, for exemple, NewClientWithOptions without any parameter does not look for env variables. I am not sure if we want to change that.

Copy link
Contributor
@ijc ijc left a comment

Choose a reason for hiding this comment

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

Covered the contextstore now, some comments, none major.

However I think the on disk formats needs to be documented since right now it is just defined in code, which means it becomes harder to reason about evolutionary changes etc. An in tree doc or (better) a package level godoc would seem reasonable places to write that.

I think the same is probably true of the tar export format, unless you want to document that the format is opaque and the output of Export can only validly be passed to Import. Even then you need to potentially handle evolution of the internal schema, in which case having it be documented would be good.

Speaking of the tar export -- is using the tar format necessary, wouldn't a JSON dump be less code to maintain?

@ijc
Copy link
Contributor
ijc commented Nov 8, 2018

The contextstore actually stores metadata and TLS material in different directories to make it easy to put different access rights on them, or put sensitive data on an encrypted filesystem.

I understand why one might want to lock down the TLS material, but what is the use case for the flipside -- when/why would someone want to expose the other stuff? Just having everything under the stricter regimen would be simpler.

Did you consider other kinds of sensitive information but non-TLS information (perhaps for use with a token based system)? If we don't have any current need for such a thing that's fine, but we should at least have some idea in our head how we will extend the format to cover such things.

@simonferquel
Copy link
Contributor Author

@ijc from internal discussion, we'd like to leverage the host OS secure storage facilities to store TLS data (e.g. the windows Certificate store or th Mac OS Keychain). We are not sure about the shape it will have, but as a first step, having Metadata storage separate from TLS material seemed a good start.

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Copy link
Member
@vdemeester vdemeester left a comment

Choose a reason for hiding this comment

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

The UX related to this PR will mostly appear in PRs against docker CLI, but everything required to interact with the docker context-store will live in the moby/moby repo (mostly to avoid dependent projects to have to vendor Docker cli)

As first, I thought it would live in moby/moby as there was a server side of it. As there is not, I'm not sure why putting this into moby/moby instead of docker/cli.
The functional arguments on the client make it easy to implement the feature outside of the client package.

I also am not sure to see what would be wrong to vendor the docker/cli part of that feature instead of moby/moby… Why would credential-helpers be outside of moby/moby (on it's own repository), but not contextstore ?

"net/http"
"os"

"github.com/docker/docker/client/context"
Copy link
Member

Choose a reason for hiding this comment

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

We may want to always alias the import (it's already pretty messy when reading some part of the moby code with the different package name context …)

}

// LoadTLSData loads TLS materials for the context
func (c *Context) LoadTLSData(s contextstore.Store) (*TLSData, error) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: I wonder why it needs to be attached to the Context struct… func LoadTLSDataFromStore(name string, s contextstore.Store)

}

// LoadTLSConfig extracts a context docker endpoint TLS config
func (c *Context) LoadTLSConfig(s contextstore.Store) (*tls.Config, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Same question/nit here, we really only use SkipTLSVerify in the func


// Context is a typed wrapper around a context-store generic context describing
// a Docker Engine endpoint
type Context struct {
Copy link
Member

Choose a reason for hiding this comment

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

not-a-nit-but-maybe: Endpoint maybe would be better than Context ? 😝
I know the name is kinda a "long" shot but I'm not a fan of Context.

)

// SetDockerEndpoint sets the docker endpoint of a context
func SetDockerEndpoint(s contextstore.Store, name, host, apiVersion string, ca, cert, key []byte, skipTLSVerify bool) error {
Copy link
E839
Member

Choose a reason for hiding this comment

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

And here I would attach SetDockerEndpoint to contextstore.Store (or sthg more like s.Save(context))

// of the remote system. TLS data and metadata are stored separately, so that in the future, we will be able to store sensitive
// information in a more secure way, depending on the os we are running on (e.g.: on Windows we could use the user Certificate Store, on Mac OS the user Keychain...)
//
// current implementation is purely file based with the following structure:
Copy link
Member

Choose a reason for hiding this comment

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

nit: Current

// - tls/
// - context1/endpoint1/: directory containing TLS data for the endpoint1 in context1
//
// the context store itself has abolutely no knowledge about a docker or a kubernetes endpoint should contain in term of metadata or TLS config
Copy link
Member

Choose a reason for hiding this comment

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

nit: The


func (s *metadataStore) remove(name string) error {
contextDir := s.contextDir(name)
return os.RemoveAll(contextDir)
Copy link
Member

Choose a reason for hiding this comment

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

nit: inline ? (os.RemoveAll(s.contextDir(name)))

return !s.IsDir()
}

func listRecursivelyMetadataDirs(root string) ([]string, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not using filepath.Walk ?

}

// Export exports an existing namespace into a stream
func Export(name string, s Store) io.ReadCloser {
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't this be simplified by using pkg/archive ?

@simonferquel
Copy link
Contributor Author

@vdemeester after some time thinking about it, I think you are right about putting all this in docker/cli. I primarily did this way because of the misleading WithFromEnv function that should IMO live in the cli repo as well.
It will also make the whole connection helper integration stuff easier.
I'll chat a bit with @silvin-lubecki about that this morning and will likely close this one / port it to the cli.

@simonferquel
Copy link
Contributor Author

Closing this, as all the code has now been moved to docker/cli#1500.
Thanks for the reviews guys!

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

Successfully merging this pull request may close these issues.

6 participants
0