Skip to content

Conversation

gtcooke94
Copy link
Contributor

This adds support for providers, (and specifically root file watcher providers) to return SPIFFE Bundle Maps in the KeyMaterial.

See the gRFC for more detail grpc/proposal#462

RELEASE NOTES: N/A

@gtcooke94 gtcooke94 assigned easwars and unassigned easwars Mar 13, 2025
// SPIFFEBundleMap is an in-memory representation of a spiffe trust bundle
// map. If this value exists, it will be used to find the roots for a given
// trust domain rather than the Roots in this struct.
SPIFFEBundleMap spiffe.BundleMap
Copy link
Contributor

Choose a reason for hiding this comment

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

This type spiffe.BundleMap, is defined in an internal package. But the KeyMaterial struct is exported to our users. This will be unfortunate if we add a new field in a publicly exported type, but our users can't have access to that type.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible instead of the pemfile certificate provider to convert the SPIFFE bundle into an x509.CertPool and return it using the Roots field instead? I see that there is a way to extract the certificates from a SPIFFE bundle. So, you might be able to extract them and create an x509.CertPool and add those certificates to that pool and return it. But I don't know if functionally, that will still do what you want it to do.

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 think the right thing to do here would be to make spiffe.BundleMap publicly exported

Tne SPIFFE bundle map can't be directly set as x509.CertPool roots. It's essentially a map of string->root certs, where the string is a SPIFFE ID, and at handshake time you lookup the roots for the peer's SPIFFE ID, and after this map lookup we set the roots. But the KeyMaterial call doesn't necessarily happen during the handshake.

We could make it a map of strings to x509.CertPool (which is essentially what it is), but I think it would be okay to publicly export the type - would you rather see a public module that has the spiffe.BundleMap, or I think we can typealias and say pemfile.spiffeBundleMap = spiffe.BundleMap?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be easaier/nicer if we make SPIFFEBundleMap to be of type map[string]*spiffebundle.Bundle here and get rid of the BundleMap type in the internal spiffe package.

We also need to document the Roots field that it has lower priority over the SPIFFEBundleMap field.

Also, maybe SPIFFEBundles is a better name as opposed to SPIFFEBundleMap?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the type in preference of map[string]*spiffebundle.Bundle and notated the preferences in the Roots field

The SPIFFEBundleMap name here comes from the spec, rather than because it is a Map in Go - I think we should keep the name.

Comment on lines 382 to 398
var opts Options
if useSPIFFEBundle {
opts = Options{
CertFile: path.Join(symLinkName, certFile),
KeyFile: path.Join(symLinkName, keyFile),
RootFile: path.Join(symLinkName, rootFile),
SPIFFEBundleMapFile: path.Join(symLinkName, spiffeBundleFile),
RefreshDuration: defaultTestRefreshDuration,
}
} else {
opts = Options{
CertFile: path.Join(symLinkName, certFile),
KeyFile: path.Join(symLinkName, keyFile),
RootFile: path.Join(symLinkName, rootFile),
RefreshDuration: defaultTestRefreshDuration,
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar comment as in another place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

// Since we have root and identity certs, we need to make sure the
// update is pushed on both of them.
if _, err := distCh.Receive(ctx); err != nil {
t.Fatalf("timeout waiting for provider to read files and push key material to distributor: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here and in other places in this PR, test errors need not follow the error string capitalization guideline. Please see: go/go-style/decisions#error-strings

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 apologize, a lot of this is just code that is in the diff because it was tabbed over, I didn't closely read all of the existing errors. I'll do my best to clean up

Comment on lines +261 to +265
for _, useSPIFFEBundle := range []bool{true, false} {
testName := baseName
if useSPIFFEBundle {
testName = testName + "_" + "withSPIFFEBundle"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Making tests easier to read (even if that means they are more verbose) should be an ideal to strive for. Would it be possible to refactor this such that you have separate tests for the SPIFFE case and the non-spiffe case, but they both call into a common function to do the common parts.

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 think they are all fundamentally testing the same thing, except setting one option value or not (i.e. are the configured files reloaded). And the test setup for creating temp files and a distributor who's channel we control and block on is relatively complicated - in my opinion it's easiest to do this setup with these if statements for configuration rather than duplicating the complex code.

Or, do you mean just separate this for loop into two separate tests that call into a func that is the content of t.Run?

@easwars easwars assigned gtcooke94 and unassigned easwars Mar 14, 2025
Copy link
Contributor Author

@gtcooke94 gtcooke94 left a comment

Choose a reason for hiding this comment

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

Sorry, missed a few comments

Comment on lines +261 to +265
for _, useSPIFFEBundle := range []bool{true, false} {
testName := baseName
if useSPIFFEBundle {
testName = testName + "_" + "withSPIFFEBundle"
}
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 think they are all fundamentally testing the same thing, except setting one option value or not (i.e. are the configured files reloaded). And the test setup for creating temp files and a distributor who's channel we control and block on is relatively complicated - in my opinion it's easiest to do this setup with these if statements for configuration rather than duplicating the complex code.

Or, do you mean just separate this for loop into two separate tests that call into a func that is the content of t.Run?

Comment on lines 382 to 398
var opts Options
if useSPIFFEBundle {
opts = Options{
CertFile: path.Join(symLinkName, certFile),
KeyFile: path.Join(symLinkName, keyFile),
RootFile: path.Join(symLinkName, rootFile),
SPIFFEBundleMapFile: path.Join(symLinkName, spiffeBundleFile),
RefreshDuration: defaultTestRefreshDuration,
}
} else {
opts = Options{
CertFile: path.Join(symLinkName, certFile),
KeyFile: path.Join(symLinkName, keyFile),
RootFile: path.Join(symLinkName, rootFile),
RefreshDuration: defaultTestRefreshDuration,
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

// Since we have root and identity certs, we need to make sure the
// update is pushed on both of them.
if _, err := distCh.Receive(ctx); err != nil {
t.Fatalf("timeout waiting for provider to read files and push key material to distributor: %v", err)
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 apologize, a lot of this is just code that is in the diff because it was tabbed over, I didn't closely read all of the existing errors. I'll do my best to clean up

@gtcooke94 gtcooke94 requested a review from easwars March 17, 2025 15:48
@gtcooke94 gtcooke94 assigned easwars and unassigned gtcooke94 Mar 17, 2025
@easwars
Copy link
Contributor

easwars commented Mar 17, 2025

Please don't mark conversations as resolved. It should be the reviewer who marks them as resolved, when they have been satisfactorily handled. Now I have to click unresolve on every comment and verify that it has been handled satisfactorily.

@easwars easwars assigned gtcooke94 and unassigned easwars Mar 17, 2025
@gtcooke94 gtcooke94 assigned easwars and unassigned gtcooke94 Mar 17, 2025
Comment on lines 32 to 33
// // BundleMap represents a SPIFFE Bundle Map per the spec
// // https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#4-spiffe-bundle-format.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Please remove the double //

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


// // BundleMap represents a SPIFFE Bundle Map per the spec
// // https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#4-spiffe-bundle-format.
// type BundleMap map[string]*spiffebundle.Bundle
Copy link
Contributor

Choose a reason for hiding this comment

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

And remove this commented out line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@easwars easwars assigned gtcooke94 and unassigned easwars Mar 17, 2025
@gtcooke94 gtcooke94 merged commit 1f6b0cf into grpc:master Mar 17, 2025
14 of 15 checks passed
gtcooke94 added a commit to gtcooke94/grpc-go that referenced this pull request Mar 17, 2025
gtcooke94 added a commit to gtcooke94/grpc-go that referenced this pull request Mar 17, 2025
gtcooke94 added a commit to gtcooke94/grpc-go that referenced this pull request Mar 17, 2025
janardhanvissa pushed a commit to janardhanvissa/grpc-go that referenced this pull request Mar 24, 2025
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 14, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Area: Auth Includes regular credentials API and implementation. Also includes advancedtls, authz, rbac etc. Area: xDS Includes everything xDS related, including LB policies used with xDS. Type: Security A bug or other problem affecting security
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants