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

Support foreign trust domains admin ids config #3642

Conversation

guilhermocc
Copy link
Contributor

@guilhermocc guilhermocc commented Nov 28, 2022

Pull Request check list ✔️

  • Commit conforms to CONTRIBUTING.md?
  • Proper tests/regressions included?
  • Documentation updated?

Affected functionality 🤔

admin_ids configuration values can now reside in different trust domains than the server, but this foreign trust domain must be federated.

Description of change ✍️

  • Updated admin_ids configuration validation
  • Updated endpoints getCerts method, to include federated bundle CAs for establishing TLS connections with spire server.

Which issue this PR fixes
fixes #3400

Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
…nection

Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
rootCACerts, err := x509.ParseCertificates(rootCA.DerBytes)
if err != nil {
return nil, nil, fmt.Errorf("parse bundle: %w", err)
for _, bundle := range listBundlesResponse.Bundles {
Copy link
Member

Choose a reason for hiding this comment

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

We should probably scope which bundles we use here to the trust domain for the server and other trust domains expected in admin_ids.

caPool := x509.NewCertPool()
for _, c := range caCerts {
caPool.AddCert(c)
for _, c := range caCerts {
Copy link
Member

Choose a reason for hiding this comment

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

Adding all the certs to the same CA pool is a security risk. It allows trust domain impersonation since any trust domain could mint an SVID for any other trusted trust domain and have it authenticated.

We might need a small refactor to this function to go proper spiffe authentication. The tlsconfig package might prove helpful.

Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
@guilhermocc guilhermocc force-pushed the support-foreign-trust-domain-admin-ids-config branch 2 times, most recently from 934cb12 to bcb47f4 Compare December 2, 2022 13:27
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
@guilhermocc guilhermocc force-pushed the support-foreign-trust-domain-admin-ids-config branch from bcb47f4 to e1c306e Compare December 2, 2022 13:28
@guilhermocc guilhermocc requested a review from azdagron December 2, 2022 13:54
// certificate is not provided, the function will not make any verification and return nil.
func (e *Endpoints) buildServerSpiffeAuthenticationFunction(bundleSet *x509bundle.Set) func(_ [][]byte, _ [][]*x509.Certificate) error {
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if rawCerts == nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the same verification logic that occurs on go-spiffe mtls configuration, except that it skips verification for connections with empty cert (so we can still accept connections with no auth in spire server), and applies a custom authorization logic that is a combination of tlsconfig.AuthorizeMemberOf and tlsconfig.AuthorizeOneOf authorizers

e.Log.
WithField(telemetry.AdminID, adminID.String()).
WithField(telemetry.TrustDomain, adminID.TrustDomain().String()).
Warn("No bundle found for foreign admin trust domain, admins from this trust doiman will not be able to connect")
Copy link
Contributor Author

@guilhermocc guilhermocc Dec 2, 2022

Choose a reason for hiding this comment

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

If an adminID from a foreign trust domain not yet federated with the spire server is configured, this warning will be triggered for all connection attempts. This could have been prevented if we had validation for admin_ids configuration during server start-up, what do you guys think about this? should I put work on another PR implementing this validation, or this warning log (and the documentation) should be enough for an operator to understand and resolve this kind of misconfiguration?

guilhermocc and others added 3 commits December 13, 2022 22:28
@guilhermocc guilhermocc force-pushed the support-foreign-trust-domain-admin-ids-config branch from 94fdee3 to 7dea1ab Compare December 14, 2022 14:54
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
@guilhermocc guilhermocc force-pushed the support-foreign-trust-domain-admin-ids-config branch from 7dea1ab to 55c936f Compare December 14, 2022 17:08
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
@guilhermocc guilhermocc force-pushed the support-foreign-trust-domain-admin-ids-config branch from 1afff30 to fa07255 Compare December 14, 2022 18:26
Copy link
Member

@azdagron azdagron left a comment

Choose a reason for hiding this comment

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

Thanks, @guilhermocc! In general I think this is looking good.

Comment on lines 7 to 8
# that caller admin privileges. The admin IDs must reside in a federated trust domain or on the same as the server,
# and need not have a corresponding admin registration.
Copy link
Member

Choose a reason for hiding this comment

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

There is something up with the indentation here. Also, i think this file has imposed a column limit on comments, we should probably stick with that.

| `trust_domain` | The trust domain that this server belongs to (should be no more than 255 characters) | |
| Configuration | Description | Default |
|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------|
| `admin_ids` | SPIFFE IDs that, when present in a caller's X509-SVID, grant that caller admin privileges. The admin IDs must reside in a federated trust domain or on the same as the server, and need not have a corresponding admin registration entry with the server. | |
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
| `admin_ids` | SPIFFE IDs that, when present in a caller's X509-SVID, grant that caller admin privileges. The admin IDs must reside in a federated trust domain or on the same as the server, and need not have a corresponding admin registration entry with the server. | |
| `admin_ids` | SPIFFE IDs that, when present in a caller's X509-SVID, grant that caller admin privileges. The admin IDs can be from any trust domain and need not have a corresponding admin registration entry with the server. | |

"Make sure this trust domain is correctly federated.",
)
}
return nil, fmt.Errorf("no bundle found for trust domain %s", e.TrustDomain.String())
Copy link
Member

Choose a reason for hiding this comment

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

String() method is implicitly called when using %s or %q formatting:

Suggested change
return nil, fmt.Errorf("no bundle found for trust domain %s", e.TrustDomain.String())
return nil, fmt.Errorf("no bundle found for trust domain %q", e.TrustDomain)

e.Log.
WithField(telemetry.TrustDomain, td.String()).
Warn(
"No bundle found for foreign admin trust domain, admins from this trust domain will not be able to connect. " +
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"No bundle found for foreign admin trust domain, admins from this trust domain will not be able to connect. " +
"No bundle found for foreign admin trust domain; admins from this trust domain will not be able to connect. " +

Comment on lines 89 to 93
return func(peerSpiffeID spiffeid.ID) error {
permissiveIDsSet := make(map[spiffeid.ID]struct{})
for _, adminID := range adminIds {
permissiveIDsSet[adminID] = struct{}{}
}
Copy link
Member

Choose a reason for hiding this comment

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

Let's lift this out of the closure so that we don't recompute this map on every invocation of the matcher.

Suggested change
return func(peerSpiffeID spiffeid.ID) error {
permissiveIDsSet := make(map[spiffeid.ID]struct{})
for _, adminID := range adminIds {
permissiveIDsSet[adminID] = struct{}{}
}
permissiveIDsSet := make(map[spiffeid.ID]struct{})
for _, adminID := range adminIds {
permissiveIDsSet[adminID] = struct{}{}
}
return func(peerID spiffeid.ID) error {

})

spiffeTLSConfig := tlsconfig.MTLSServerConfig(serverSvid, bundle, nil)
spiffeTLSConfig.ClientAuth = tls.RequestClientCert
Copy link
Member

Choose a reason for hiding this comment

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

Can we leave a note here just emphasizing that client certificates will still be verified using the custom VerifyPeerCertificates callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure!

Comment on lines +342 to +343
_, err = entryv1.NewEntryClient(conn).ListEntries(ctx, nil)
require.Error(t, err)
Copy link
Member

Choose a reason for hiding this comment

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

Instead of actually issuing an RPC we can use grpc.FailOnNonTempDialError(true) and grpc.WithReturnConnectionError() to cause the dial to fail when the handshake is unsuccessful.

}

// getServerCertificate returns the server certificate to be used in the TLS config.
func (e *Endpoints) getServerCertificate() *tls.Certificate {
svidState := e.SVIDObserver.State()
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we can change the SVIDObserver to store an x509svid.SVID instead of the tls.Certificate and push the parsing the conversion to the server SVID rotator so we don't have to repeat it on each handshake.

}

// parseBundle parses a *x509bundle.Bundle from a *common.bundle.
func parseBundle(td spiffeid.TrustDomain, commonBundle *common.Bundle) (*x509bundle.Bundle, error) {
Copy link
Member

Choose a reason for hiding this comment

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

It's a little unfortunate that we parse this on each handshake. I think we are already doing that today, but we should probably push this conversion behind the cache so we don't have to do it so much. That can happen in a later PR though.


// shouldLogFederationMisconfiguration returns true if the last time a misconfiguration
// was logged was more than misconfigLogEvery ago.
func shouldLogFederationMisconfiguration() bool {
Copy link
Member

Choose a reason for hiding this comment

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

hmm... should we be doing this logging rate limiting per trust domain?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, that's a good idea!

Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
@guilhermocc guilhermocc requested a review from azdagron December 16, 2022 21:34

// machMemberOrOneOf is a custom spiffeid.Matcher which will validate that the peerSpiffeID belongs to the server
// trust domain or if it is included in the admin_ids configuration permissive list.
func machMemberOrOneOf(trustDomain spiffeid.TrustDomain, adminIds ...spiffeid.ID) spiffeid.Matcher {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
func machMemberOrOneOf(trustDomain spiffeid.TrustDomain, adminIds ...spiffeid.ID) spiffeid.Matcher {
func matchMemberOrOneOf(trustDomain spiffeid.TrustDomain, adminIds ...spiffeid.ID) spiffeid.Matcher {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @MarcosDY 🙈
3f743fc


serverBundle, err := parseBundle(e.TrustDomain, commonServerBundle)
if err != nil {
return nil, fmt.Errorf("parse bundle: %w", err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this error will result in: "parse bundle: parse bundle: SOME ERROR" I think we may remove fmt error from here or from parseBundle

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
@evan2645 evan2645 added this to the 1.5.4 milestone Dec 20, 2022
Copy link
Member

@azdagron azdagron 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 looking pretty close! Just a few small comments.

"Make sure this trust domain is correctly federated.",
)
}
return nil, fmt.Errorf("no bundle found for trust domain %q", e.TrustDomain)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't these be logging td?

Suggested change
return nil, fmt.Errorf("no bundle found for trust domain %q", e.TrustDomain)
return nil, fmt.Errorf("no bundle found for trust domain %q", td)

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!

Copy link
Contributor Author

Choose a reason for hiding this comment

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


var (
misconfigLogMtx sync.Mutex
misconfigLogTimes = make(map[string]time.Time)
Copy link
Member

Choose a reason for hiding this comment

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

nit: we should use the concrete trust domain and ID types when possible

Suggested change
misconfigLogTimes = make(map[string]time.Time)
misconfigLogTimes = make(map[spiffeid.ID]time.Time)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Comment on lines 75 to 83
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if rawCerts == nil {
return nil
}

return tlsconfig.VerifyPeerCertificate(
bundleSource,
tlsconfig.AdaptMatcher(matchMemberOrOneOf(e.TrustDomain, e.AdminIDs...)),
)(rawCerts, nil)
Copy link
Member

Choose a reason for hiding this comment

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

nit: we should probably create this function before the closure to avoid unnecessary allocations on the handshake:

Suggested change
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if rawCerts == nil {
return nil
}
return tlsconfig.VerifyPeerCertificate(
bundleSource,
tlsconfig.AdaptMatcher(matchMemberOrOneOf(e.TrustDomain, e.AdminIDs...)),
)(rawCerts, nil)
verifyPeerCertificate := tlsconfig.VerifyPeerCertificate(
bundleSource,
tlsconfig.AdaptMatcher(matchMemberOrOneOf(e.TrustDomain, e.AdminIDs...)),
)
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if rawCerts == nil {
return nil
}
return verifyPeerCertificate(rawCerts, nil)

Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Copy link
Member

@azdagron azdagron left a comment

Choose a reason for hiding this comment

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

\o/, thanks for this @guilhermocc !

@amartinezfayo amartinezfayo merged commit 7bff3a0 into spiffe:main Jan 5, 2023
amartinezfayo pushed a commit that referenced this pull request Jan 5, 2023
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
stevend-uber pushed a commit to stevend-uber/spire that referenced this pull request Oct 16, 2023
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
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.

Enhance the admin_ids configuration option to support foreign trust
5 participants