Skip to content

[ca] Extricate ExternalCA from SecurityConfig#2262

Merged
cyli merged 4 commits intomoby:masterfrom
cyli:extricate-externalca-from-securityconfig
Jul 19, 2017
Merged

[ca] Extricate ExternalCA from SecurityConfig#2262
cyli merged 4 commits intomoby:masterfrom
cyli:extricate-externalca-from-securityconfig

Conversation

@cyli
Copy link
Copy Markdown
Contributor

@cyli cyli commented Jun 15, 2017

This is the first step towards splitting the CA server's signing functionality from SecurityConfig, which serves more for providing creds for mTLS. Doing so will help unify the root CA update paths in node/node.go.

The only node that ever needs the ExternalCA object is the leader that is running the CA server anyway, so it makes sense for it to go into the CA server. This also better makes it possible at a future point to provide separate creds for contacting the external CA server if we ever needed to do so.

@codecov
Copy link
Copy Markdown

codecov bot commented Jun 15, 2017

Codecov Report

Merging #2262 into master will decrease coverage by 0.08%.
The diff coverage is 89.09%.

@@            Coverage Diff             @@
##           master    #2262      +/-   ##
==========================================
- Coverage   60.35%   60.27%   -0.09%     
==========================================
  Files         128      128              
  Lines       25979    25994      +15     
==========================================
- Hits        15679    15667      -12     
- Misses       8906     8936      +30     
+ Partials     1394     1391       -3

ca/server.go Outdated

// ExternalCA returns the current external CA
func (s *Server) ExternalCA() *ExternalCA {
// don't need a lock because this never gets updated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It does get updated in UpdateRootCA.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah right sorry, it wasn't in a previous version, and I forgot to update.

ca/server.go Outdated
}

// filterAndSaveExternalCAURLS saves a copy of the external CAs to the server, and also returns a list of external CA
// urls filtered by the desired cert. This function assumes that something else has called s.secConfugMu.Lock() and
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I guess it's more like saveAndFilter?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

typo: secConfugMu

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

:D I should probably change that name now to signingMu or something.

@cyli cyli force-pushed the extricate-externalca-from-securityconfig branch from 2c6e06f to 73c0261 Compare June 15, 2017 22:36
ca/server.go Outdated
if rootCAChanged || externalCAChanged {
// we want to update only if the external CA URLS have changed, since if the root CA has changed we already
// run similar logic
if !rootCAChanged && externalCAChanged {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could be else if externalCAChanged {, but fine with this if it's more readable to be redundant.

ca/server.go Outdated
// will also unlock it
func (s *Server) saveAndFilterExternalCAURLS(ctx context.Context, desiredCert, defaultCert []byte, apiExternalCAs []*api.ExternalCA) (urls []string) {
desiredCert = NormalizePEMs(desiredCert)
s.lastSeenExternalCAs = make([]*api.ExternalCA, len(apiExternalCAs))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this is missing a , 0

Copy link
Copy Markdown
Contributor Author

@cyli cyli Jun 16, 2017

Choose a reason for hiding this comment

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

Oops, yes, sorry, although I actually just left this and instead of doing append, set s.lastSeenExternalCAs[i] = .... Don't feel strongly or anything, just in mind setting the values makes it slightly more clear that we're copying rather than creating something new, but that's just how I tend to read these things.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I have a slight preference for going back to the old code's s.lastSeenExternalCAs = cluster.Spec.CAConfig.Copy().ExternalCAs expression, moved outside this function.

The reason is that saving the URL list and filtering it are different operations, so I think keeping them separate is more understandable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Makes sense

ca/server.go Outdated
Certificates: s.securityConfig.ClientTLSCreds.Config().Certificates,
RootCAs: s.externalCAPool,
MinVersion: tls.VersionTLS12,
}, urls...)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can this use NewExternalCATLSConfig to avoid hardocding the minimum version?

Certificates: securityConfig.ClientTLSCreds.Config().Certificates,
RootCAs: rootPool,
MinVersion: tls.VersionTLS12,
}, urls...).CrossSignRootCA(ctx, newCARootCA)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can this use NewExternalCATLSConfig to avoid hardocding the minimum version?

@cyli cyli force-pushed the extricate-externalca-from-securityconfig branch 4 times, most recently from b94298b to 4a0490f Compare June 20, 2017 01:10
@cyli cyli changed the title WIP: [ca] Extricate ExternalCA from SecurityConfig [ca] Extricate ExternalCA from SecurityConfig Jun 20, 2017
@cyli
Copy link
Copy Markdown
Contributor Author

cyli commented Jun 20, 2017

I've added an interface to ca.Server that will update the external CA's TLS creds when the security config's TLS creds are updated. PTAL - will also be happy to split this up in to 2 or 3 PRs, since a lot of files have been changed.

ca/server.go Outdated

// SecurityConfigTLSUpdater takes a security config and sets a watch so we can tell when TLS credentials have changed
type SecurityConfigTLSUpdater struct {
s *SecurityConfig
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's call it securityConfig to avoid confusion with server.

ca/server.go Outdated
return s.s.ClientTLSCreds.Config().Certificates
}

// NotifyTLSChange starts watching for TLS changes in the security config, and returns a channel through which
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Comment seems outdated

ca/server.go Outdated
}

// SecurityConfigTLSUpdater takes a security config and sets a watch so we can tell when TLS credentials have changed
type SecurityConfigTLSUpdater struct {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm wondering if this abstraction is really useful. It's only used in one place, and it seems like calling Watch would be clearer and lower this risk of leaking the watch (because the cancel return value would be ignored in that case).

Is there an upcoming change that will make this more useful?

If we keep the abstraction, can we return a cancel function instead of having a Stop method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can pass a channel instead to the CA server notifying it when the TLS creds have been updated instead. But the SecurityConfig object already had a watch on it to notify the agent when the credentials or the root CA have changed so the agent session could restart. It seemed easier to bottleneck it through the SecurityConfig, but I could also instead try setting a private watch in the node.go code.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I was just asking if we need a SecurityConfigTLSUpdater rather than having the single place where it's used call the Watch method directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was planning on removing the security config entirely from the CA server at a later point, as part of separating out the security config from the CA server, in which case I just needed some kind of interface from which I can get the updated external CA creds.

Perhaps it'd be better just to pass a channel?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'd suggest removing the abstraction until we get to that point. Then we can see what makes most sense. Passing a channel may be best, especially if the lifecycle of the watch can be managed by something external to the CA server.

ca/config.go Outdated
}

// Close closes the security config, freeing up the watch queue resource
func (s *SecurityConfig) Close() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not sure if it makes sense, but having NewSecurityConfig return a cancel function might make it harder to leak these. On the other hand, security configs might always be stored somewhere long-term, in which case it wouldn't help.

In general this change seems to be going in the right direction (I'm happy to see SetWatch go), but making the SecurityConfig something whose resources need to be consciously deallocated is unfortunate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We mainly create the security config in the node/node.go code, and the watch gets cleaned up at the end of run anyway, so holding onto the cancel seems fine? Returning the cancel may make it harder to leak in tests, though, so: #2274

@aaronlehmann
Copy link
Copy Markdown
Collaborator

Coming back to this after awhile. Is it ready for review?

ca/server.go Outdated
lastSeenClusterRootCA *api.RootCA
lastSeenExternalCAs []*api.ExternalCA
secConfigMu sync.Mutex
signingMu sync.Mutex
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm not sure signingMu is the right name. It seems to protect s.externalCA, and some aspects of the securityConfig.

Let's try to articulate what this mutex is used for, and from that we can figure out if the name makes sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I guess it's supposed to protect the components of the CA server used to issue new certificates, so the rootCA object, and everything needed to connect to an external CA (hence the parts of securityConfig, because it has the TLS credentials needed to connect to the external CA)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add this in a comment :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah ok, cool, I was hoping you might have a better name. :) I'm terrible at naming things, which is why I tend to end up with Java names (like RootCAAndExternalCAMutex or something)

}

// ExternalCA returns the current external CA
func (s *Server) ExternalCA() *ExternalCA {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I see this used in a unit test. Is it called anywhere else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nope, just the test. I can make the server test part of the ca package instead of the ca_test package to access it directly, if you prefer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Update: oh, right import cycle issues. :| nm, I can't actually do that.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add a comment saying this is exposed to support unit testing and externalCA is nominally a private field.

@cyli cyli force-pushed the extricate-externalca-from-securityconfig branch from 34f7bc5 to afac780 Compare July 14, 2017 16:37
@cyli
Copy link
Copy Markdown
Contributor Author

cyli commented Jul 14, 2017

@aaronlehmann Also, apologies, I spent a while trying to remember what thought I had regarding this PR and I no longer remember it. :) So yes, this is ready for review.

@cyli cyli force-pushed the extricate-externalca-from-securityconfig branch from afac780 to 2658824 Compare July 14, 2017 16:52
rootPool.AppendCertsFromPEM(apiRootCA.CACert)
crossSignedCert, err = ca.NewExternalCA(nil,
ca.NewExternalCATLSConfig(securityConfig.ClientTLSCreds.Config().Certificates, rootPool),
urls...).CrossSignRootCA(ctx, newCARootCA)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please break up this statement into two.

if len(clusters) != 1 {
return errors.New("could not find cluster object")
}
s.UpdateRootCA(ctx, clusters[0]) // call once to ensure that the join tokens are always set
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Making a note to close #2241 when this is merged.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is one more place where we have to change this (in manager) before closing that.

case <-externalTLSCredsChange:
s.signingMu.Lock()
s.externalCA.UpdateTLSConfig(NewExternalCATLSConfig(
s.securityConfig.ClientTLSCreds.Config().Certificates, s.externalCAPool))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems like this update to externalCA's TLS config is "eventually consistent", since we might pick up events from the updates channel between the time the credentials change and this is called. Will that cause any problems?

I suppose the use case here is node certificate renewals, so it seems reasonable to expect the old node certificate to keep working for that short interval. Root rotations might be a bit more of a corner case, but I'm not sure that's worth worrying about either.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry for the delay in response! Yes, it is eventually consistent - during that period, signing nodes may fail, but in a recoverable way, so eventually the correct certs will be issued. I've added a comment to that effect.

@diogomonica
Copy link
Copy Markdown
Contributor

Overall I'm good with the refactor 👍

cyli added 3 commits July 19, 2017 14:56
…ficate,

rather than a RootCA object.

Signed-off-by: Ying Li <ying.li@docker.com>
…irst

step toward separating the root CA object used to sign certificates on the
CA server from the SecurityConfig, which is mainly used to maintain client
and server TLS credentials.

Signed-off-by: Ying Li <ying.li@docker.com>
SecurityConfig's TLS credentials change.

Signed-off-by: Ying Li <ying.li@docker.com>
Signed-off-by: Ying Li <ying.li@docker.com>
@cyli cyli force-pushed the extricate-externalca-from-securityconfig branch from 2658824 to 22b5c13 Compare July 19, 2017 22:12
@cyli cyli merged commit 64aab74 into moby:master Jul 19, 2017
@cyli cyli deleted the extricate-externalca-from-securityconfig branch July 19, 2017 22:40
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.

3 participants