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

advancedtls: Rename custom verification function APIs #7140

Merged
merged 13 commits into from
Apr 23, 2024

Conversation

gtcooke94
Copy link
Contributor

@gtcooke94 gtcooke94 commented Apr 17, 2024

This change renames the APIs for injecting a custom verification function such that the names are clearer that it is occurs post-handshake with already built chains and does not replace the base chain building and chain verification that the underlying security library does.

I've left type aliases to the old names and marked them as deprecated.

(To add to release notes? - In advancedtls, change CustomVerificationFunc to PostHandshakeVerificationFunc, VerificationFuncParams to HandshakeVerificationInfo, and VerificationResults to PostHandshakeVerificationResults.)

RELEASE NOTES: none

@gtcooke94 gtcooke94 requested a review from erm-g April 17, 2024 20:41
@gtcooke94 gtcooke94 added Type: Internal Cleanup Refactors, etc Type: Security A bug or other problem affecting security labels Apr 17, 2024
@gtcooke94 gtcooke94 added this to the 1.64 Release milestone Apr 17, 2024
security/advancedtls/advancedtls.go Show resolved Hide resolved
@@ -76,8 +76,8 @@ func main() {
IdentityOptions: advancedtls.IdentityCertificateOptions{
IdentityProvider: identityProvider,
},
VerifyPeer: func(params *advancedtls.VerificationFuncParams) (*advancedtls.VerificationResults, error) {
return &advancedtls.VerificationResults{}, nil
VerifyPeer: func(params *advancedtls.HandshakeVerificationInfo) (*advancedtls.PostHandshakeVerificationResults, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at both 'main.go' examples it looks like 'xOptions' is used more here, so I'm leaning towards 'HandshakeVerificationOptions'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See other comment

@erm-g erm-g self-requested a review April 18, 2024 18:09
@gtcooke94 gtcooke94 requested a review from dfawley April 18, 2024 18:10
Copy link
Contributor

@erm-g erm-g left a comment

Choose a reason for hiding this comment

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

LGTM, let's ask Doug to have the final look.

security/advancedtls/advancedtls.go Outdated Show resolved Hide resolved
security/advancedtls/advancedtls.go Outdated Show resolved Hide resolved
Comment on lines 75 to 76
// PostHandshakeVerificationFunc returns nil
// if the authorization fails; otherwise returns an empty struct.
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this.

The convention is that functions reutrn <zero value AKA nil here>, error or error and _, nil on success. If nil, nil is illegal, then that makes the API confusing and hard to use -- in which case you should have it return a value instead of a pointer.

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 returns nil, error in the case the authorization fails, <currently empty struct>, nil on success

We discussed and don't think there is harm in keeping the struct even though it's currently unused - there's a good chance it could be in the future and it's already implemented as such

Copy link
Member

Choose a reason for hiding this comment

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

OK - it's a little unusual but should be OK. If that's what you want you should make sure you're panicking if you call it and the function ever returns nil, 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.

I don't think there's a compelling reason to return a pointer vs. a struct if that is what is making this unclear, but that would represent a breaking behavior change. Looking at it, I don't think nil, nil is illegal, it just means there's no information to pass back out.

I added some text to the comment to make it a little more clear.

That being said, looking at it again today with fresh eyes, it's called here in our code. And the struct is always thrown away. So I'm rethinking the value of keeping this in the API. Will discuss with my team and add another comment soon.

Copy link
Member

Choose a reason for hiding this comment

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

It says "otherwise returns an empty struct" which implies that nil, nil is invalid. If nil, nil is valid, then you can just delete this part of the documentation. Returning <zero>, error or _, nil is just how regular Go conventions always work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I see, I'll remove that part and then this is resolved? I think that it's more pain that its worth to remove the struct return

Copy link
Member

Choose a reason for hiding this comment

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

SGTM. Maybe PostHandshakeVerificationFunc should return an error if the authorization should fail or If PostHandshakeVerificationFunc returns an error, authorization fails or something? (Something a user implements shouldn't be documented as "this is what it does" but "this is what you should make it do" or "this is how the library responds based on what it does".)

Copy link
Contributor Author

@gtcooke94 gtcooke94 Apr 23, 2024

Choose a reason for hiding this comment

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

Re-worded, PTAL

// verification has been completed.
// PostHandshakeVerificationFunc returns nil
// if the authorization fails; otherwise returns an empty struct.
type PostHandshakeVerificationFunc func(params *HandshakeVerificationInfo) (*PostHandshakeVerificationResults, error)
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: PeerVerificationFunc (and all associated types)? It's a shorter name and (maybe?) is named after the intent of the thing vs. when it happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want to make sure that we are differentiating between verification happening after the normal chain building and verification that happens by default (which this is), and overriding the base chain building and verification itself.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I follow all this.

What is "post handshake verification" exactly? And you're saying this can be used to replace the default behavior? What is the default behavior? Do you have any examples of what users would want to do with this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's two main different ways users could desire to customize verification behavior.
During verification we takes the peer's untrusted chain and build a chain from it to a trusted root.

  1. A user could want to fully replace this chain building behavior (rarer, more difficult use-case that is not supported but might be in the future).
  2. A user could also just want to do additional checks after the regular default chain building (this is a much more common use-case). We want to preemptively make sure the naming for (2) does not get it confused with (1), as we see them as fully different features with different use cases.

A concrete example of (2) is doing additional check on the hostname of the peer's cert.
A concrete example of (1) would be fully changing the process by which you build a chain to a trusted root using other information, for example SPIFFE IDs.

Copy link
Member

Choose a reason for hiding this comment

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

What's the API for (2)? VerifyPeer?

So is it the case that if you specify this callback then VerifyPeer isn't called?

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 the API for (2) is VerifyPeer. When set it eventually cascades to here -

if c.verifyFunc != nil {
_, err := c.verifyFunc(&VerificationFuncParams{

And VerifyPeer is of type PostHandshakeVerificationFunc, I think renaming this option from VerifyPeer to something more clear is probably belongs in this PR as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Renamed VerifyPeer in the settings struct, PTAL

@dfawley dfawley assigned gtcooke94 and unassigned dfawley and erm-g Apr 19, 2024
@gtcooke94 gtcooke94 assigned dfawley and unassigned gtcooke94 Apr 19, 2024
@gtcooke94 gtcooke94 requested a review from dfawley April 19, 2024 17:39
@dfawley dfawley assigned gtcooke94 and unassigned dfawley Apr 19, 2024
@gtcooke94 gtcooke94 assigned dfawley and unassigned gtcooke94 Apr 19, 2024
@dfawley dfawley assigned gtcooke94 and unassigned dfawley Apr 22, 2024
@dfawley dfawley assigned dfawley and unassigned gtcooke94 Apr 23, 2024
@dfawley
Copy link
Member

dfawley commented Apr 23, 2024

Can you please resolve the conflicts as well? Thanks!

@gtcooke94
Copy link
Contributor Author

Fixed merge conflicts, should be all good

// AdditionalPeerVerification is a custom verification check after certificate signature
// check.
// If this is set, we will perform this customized check after doing the
// normal check(s) indicated by setting VType.
Copy link
Member

Choose a reason for hiding this comment

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

VType->VerificationType?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I think I lost some in the merge, good catch

// AdditionalPeerVerification is a custom verification check after certificate signature
// check.
// If this is set, we will perform this customized check after doing the
// normal check(s) indicated by setting VType.
Copy link
Member

Choose a reason for hiding this comment

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

VerificationType

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch

// TODO(gtcooke94) Remove this block when remove o.VerifyPeer
// Set AdditionalPeerVerification if the user is still using VerifyPeer.
if o.VerifyPeer != nil {
o.AdditionalPeerVerification = o.VerifyPeer
Copy link
Member

Choose a reason for hiding this comment

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

if o.AdditionalPeerVerification != nil { return <err> }?

Or ignore the old deprecated field VerifyPeer instead by reversing it:

if o.AdditionalPeerVerification == nil {
	o.AdditionalPeerVerification = o.VerifyPeer
}

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's not required, so the error return I think doesn't make the most sense.

The reverse works too, I guess it just matters for the precedence - I was going with "if the old field is set they probably haven't migrated", but the bottom is good too as a "if the new field isn't set take whatever is in the old field"

Copy link
Member

Choose a reason for hiding this comment

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

The error would be "you set two fields that mean the same thing, one of which is deprecated: what were you thinking?" 😆

@dfawley dfawley assigned gtcooke94 and unassigned dfawley Apr 23, 2024
@dfawley dfawley removed their assignment Apr 23, 2024
@gtcooke94 gtcooke94 merged commit d75b5e2 into grpc:master Apr 23, 2024
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Internal Cleanup Refactors, etc Type: Security A bug or other problem affecting security
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants