-
Notifications
You must be signed in to change notification settings - Fork 486
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
Support foreign trust domains admin ids config #3642
Conversation
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>
pkg/server/endpoints/endpoints.go
Outdated
rootCACerts, err := x509.ParseCertificates(rootCA.DerBytes) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("parse bundle: %w", err) | ||
for _, bundle := range listBundlesResponse.Bundles { |
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.
We should probably scope which bundles we use here to the trust domain for the server and other trust domains expected in admin_ids.
pkg/server/endpoints/endpoints.go
Outdated
caPool := x509.NewCertPool() | ||
for _, c := range caCerts { | ||
caPool.AddCert(c) | ||
for _, c := range caCerts { |
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.
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>
934cb12
to
bcb47f4
Compare
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
bcb47f4
to
e1c306e
Compare
pkg/server/endpoints/endpoints.go
Outdated
// 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 { |
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 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
pkg/server/endpoints/endpoints.go
Outdated
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") |
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.
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?
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
94fdee3
to
7dea1ab
Compare
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
7dea1ab
to
55c936f
Compare
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
1afff30
to
fa07255
Compare
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.
Thanks, @guilhermocc! In general I think this is looking good.
conf/server/server_full.conf
Outdated
# 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. |
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 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.
doc/spire_server.md
Outdated
| `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. | | |
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.
| `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. | | |
pkg/server/endpoints/auth.go
Outdated
"Make sure this trust domain is correctly federated.", | ||
) | ||
} | ||
return nil, fmt.Errorf("no bundle found for trust domain %s", e.TrustDomain.String()) |
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.
String() method is implicitly called when using %s or %q formatting:
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) |
pkg/server/endpoints/auth.go
Outdated
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. " + |
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.
"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. " + |
pkg/server/endpoints/auth.go
Outdated
return func(peerSpiffeID spiffeid.ID) error { | ||
permissiveIDsSet := make(map[spiffeid.ID]struct{}) | ||
for _, adminID := range adminIds { | ||
permissiveIDsSet[adminID] = struct{}{} | ||
} |
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.
Let's lift this out of the closure so that we don't recompute this map on every invocation of the matcher.
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 |
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 we leave a note here just emphasizing that client certificates will still be verified using the custom VerifyPeerCertificates callback?
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.
sure!
_, err = entryv1.NewEntryClient(conn).ListEntries(ctx, nil) | ||
require.Error(t, err) |
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.
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.
pkg/server/endpoints/endpoints.go
Outdated
} | ||
|
||
// getServerCertificate returns the server certificate to be used in the TLS config. | ||
func (e *Endpoints) getServerCertificate() *tls.Certificate { | ||
svidState := e.SVIDObserver.State() |
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 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) { |
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.
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.
pkg/server/endpoints/auth.go
Outdated
|
||
// shouldLogFederationMisconfiguration returns true if the last time a misconfiguration | ||
// was logged was more than misconfigLogEvery ago. | ||
func shouldLogFederationMisconfiguration() bool { |
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.
hmm... should we be doing this logging rate limiting per trust domain?
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.
Yep, that's a good idea!
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
pkg/server/endpoints/auth.go
Outdated
|
||
// 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 { |
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.
func machMemberOrOneOf(trustDomain spiffeid.TrustDomain, adminIds ...spiffeid.ID) spiffeid.Matcher { | |
func matchMemberOrOneOf(trustDomain spiffeid.TrustDomain, adminIds ...spiffeid.ID) spiffeid.Matcher { |
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.
pkg/server/endpoints/auth.go
Outdated
|
||
serverBundle, err := parseBundle(e.TrustDomain, commonServerBundle) | ||
if err != nil { | ||
return nil, fmt.Errorf("parse bundle: %w", err) |
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 error will result in: "parse bundle: parse bundle: SOME ERROR" I think we may remove fmt error from here or from parseBundle
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.
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
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 is looking pretty close! Just a few small comments.
pkg/server/endpoints/auth.go
Outdated
"Make sure this trust domain is correctly federated.", | ||
) | ||
} | ||
return nil, fmt.Errorf("no bundle found for trust domain %q", e.TrustDomain) |
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.
Shouldn't these be logging td
?
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) |
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!
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.
pkg/server/endpoints/auth.go
Outdated
|
||
var ( | ||
misconfigLogMtx sync.Mutex | ||
misconfigLogTimes = make(map[string]time.Time) |
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.
nit: we should use the concrete trust domain and ID types when possible
misconfigLogTimes = make(map[string]time.Time) | |
misconfigLogTimes = make(map[spiffeid.ID]time.Time) |
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.
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
pkg/server/endpoints/auth.go
Outdated
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) |
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.
nit: we should probably create this function before the closure to avoid unnecessary allocations on the handshake:
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>
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.
\o/, thanks for this @guilhermocc !
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Signed-off-by: Guilherme Carvalho <guilhermbrsp@gmail.com>
Pull Request check list ✔️
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 ✍️
admin_ids
configuration validationgetCerts
method, to include federated bundle CAs for establishing TLS connections with spire server.Which issue this PR fixes ❓
fixes #3400