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

Add cached OCSP client support to Cert Auth #17093

Merged
merged 44 commits into from
Nov 21, 2022
Merged

Add cached OCSP client support to Cert Auth #17093

merged 44 commits into from
Nov 21, 2022

Conversation

sgmiller
Copy link
Collaborator

@sgmiller sgmiller commented Sep 9, 2022

Using a modified version of an Apache licensed OCSP client for Go, modified
to use Vault's storage and have better more consistent error handling,
support for custom configured CAs and overridden OCSP servers.

Mostly for early consideration.

@sgmiller sgmiller requested a review from a team September 9, 2022 20:08
Copy link
Contributor

@stevendpclark stevendpclark left a comment

Choose a reason for hiding this comment

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

A bunch of nits but overall 👍

sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Show resolved Hide resolved
builtin/credential/cert/path_certs.go Show resolved Hide resolved
@sgmiller
Copy link
Collaborator Author

sgmiller commented Sep 9, 2022

A bunch of nits but overall +1

Yeah, I expected a lot of nits given the sprint of coding.

1 similar comment
@sgmiller
Copy link
Collaborator Author

sgmiller commented Sep 9, 2022

A bunch of nits but overall +1

Yeah, I expected a lot of nits given the sprint of coding.

Copy link
Contributor

@cipherboy cipherboy left a comment

Choose a reason for hiding this comment

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

I've gotta take another look at the trusted cert stuff and make sure I understand that, but this is a first pass review on stuff.

sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
builtin/credential/cert/backend.go Outdated Show resolved Hide resolved
builtin/credential/cert/backend.go Outdated Show resolved Hide resolved
sdk/helper/ocsp/client.go Outdated Show resolved Hide resolved
Copy link
Contributor

@cipherboy cipherboy left a comment

Choose a reason for hiding this comment

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

A few nits on the non-OCSP responder stuff. I think the last one might be a bug.

builtin/credential/cert/backend.go Outdated Show resolved Hide resolved
builtin/credential/cert/path_certs.go Outdated Show resolved Hide resolved
builtin/credential/cert/path_certs.go Outdated Show resolved Hide resolved
builtin/credential/cert/path_certs.go Outdated Show resolved Hide resolved
Copy link
Contributor

@cipherboy cipherboy left a comment

Choose a reason for hiding this comment

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

Some initial thoughts.

I think two things are worth addressing:

  • The behavior within doRequest in the OCSP client,
  • that nextUpdate is an optional field.

the rest I don't think is critical AFAICT.

}
if len(rest) > 0 {
// extra bytes to the end
return nil, err
Copy link
Contributor

Choose a reason for hiding this comment

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

This returns nil, nil, wouldn't we want to return a non-nil error here?


// isInValidityRange checks the validity
func isInValidityRange(currTime, nextUpdate time.Time) bool {
return !nextUpdate.IsZero() && !currTime.After(nextUpdate)
Copy link
Contributor

Choose a reason for hiding this comment

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

My reading of https://datatracker.ietf.org/doc/html/rfc6960#section-3.2 seems to state that nextUpdate is an optional field, i.e., if the OCSP responder doesn't know, they can leave this field blank.

This matches the ASN.1:

      nextUpdate         [0]       EXPLICIT GeneralizedTime OPTIONAL,

If that's the case, I'd probably just sub in the default cache expiry period to make things simple, tbh.

case ocsp.Good:
return &ocspStatus{
code: ocspStatusGood,
err: nil,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can probably elide err: nil for consistency.

return returnOCSPStatus(ocspRes), nil
}

func returnOCSPStatus(ocspRes *ocsp.Response) *ocspStatus {
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems weird that we're translating from one internal form to another internal form?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is this possibly for their file caching?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, wondering if perhaps it is that. Then they're stable internal identifiers rather than external identifiers that might potentially change and break their cached entries.

But still, I don't think Go upstream would make that type of constants change, except maybe if they import x/ocsp into the main series.

if cacheValue == nil {
return &ocspStatus{
code: ocspMissedCache,
err: fmt.Errorf("miss cache data. subject: %v", subjectName),
Copy link
Contributor

Choose a reason for hiding this comment

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

Subject... is just used to have something to log?

producedAt: float64(ocspRes.ProducedAt.UTC().Unix()),
thisUpdate: float64(ocspRes.ThisUpdate.UTC().Unix()),
nextUpdate: float64(ocspRes.NextUpdate.UTC().Unix()),
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we need to set status here, out of curiosity?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I would think so

if !verifiedChains[i][numberOfNoneRootCerts].IsCA || string(verifiedChains[i][numberOfNoneRootCerts].RawIssuer) != string(verifiedChains[i][numberOfNoneRootCerts].RawSubject) {
// Check if the last Non Root Cert is also a CA or is self signed.
// if the last certificate is not, add it to the list
rca := c.caRoot[string(verifiedChains[i][numberOfNoneRootCerts].RawIssuer)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Root subjects are not necessarily unique, but we can fix this later I suppose :-)

return nil
}

func (c *Client) ValidateWithCacheForAllCertificates(verifiedChains []*x509.Certificate) (bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious why this is exported?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Earlier version of the code called this. Fixed.

return true, nil
}

func (c *Client) ValidateWithCache(subject, issuer *x509.Certificate) (*ocspStatus, []byte, *certIDKey, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

As above. :-)

}

// insecureOcspTransport is the transport object that doesn't do certificate revocation check.
func newInsecureOcspTransport(extraCas []*x509.Certificate) *http.Transport {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this just go in the test helpers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now that we have them, yes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actually, no, it is used by the OCSP request logic itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, d'oh, sorry.

I don't like the name then, its not any more insecure than the default Go helper, perhaps just newTransportWithoutOcsp?

Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>

// cache key
type certIDKey struct {
HashAlgorithm crypto.Hash
Copy link
Contributor

@cipherboy cipherboy Nov 7, 2022

Choose a reason for hiding this comment

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

Per discussion on Slack, we can probably remove this field and clarify that NameHash is the Issuer's Name (for uniqueness) and the SerialNumber is the leaf's serial number; combination of (IssuerName, IssuerKey) should uniquely identify the issuer, and LeafSerialNumber should uniquely identify the leaf within that parent issuer.

Consensus seems to be that the hash algorithm doesn't matter (as it is whatever the OCSP responder uses and doesn't really change our behavior if we want to prefer other hashes) even if it has changed, doesn't really impact our treating previously cached entries as valid or not, given the other three fields are still unique.

@sgmiller sgmiller requested review from cipherboy and a team November 8, 2022 16:44
@sgmiller sgmiller requested a review from a team November 8, 2022 18:05
Copy link
Contributor

@cipherboy cipherboy left a comment

Choose a reason for hiding this comment

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

I don't think anything left blocks merging!

builtin/credential/cert/responder.go Outdated Show resolved Hide resolved
builtin/credential/cert/responder.go Outdated Show resolved Hide resolved
return returnOCSPStatus(ocspRes), nil
}

func returnOCSPStatus(ocspRes *ocsp.Response) *ocspStatus {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, wondering if perhaps it is that. Then they're stable internal identifiers rather than external identifiers that might potentially change and break their cached entries.

But still, I don't think Go upstream would make that type of constants change, except maybe if they import x/ocsp into the main series.

@cipherboy cipherboy removed the request for review from a team November 9, 2022 14:43
@sgmiller sgmiller merged commit a611748 into main Nov 21, 2022
@sgmiller sgmiller deleted the cert-auth-ocsp branch November 21, 2022 16:39
@cipherboy cipherboy added this to the 1.13.0 milestone Apr 13, 2023
@celesteking
Copy link

Can we please get configurable ocsp request timeouts? Imagine ocsp responder being down (firewalled, incorrectly configured, etc), your ocsp client would be banging it 5 times before giving up. And the cert auth would hang for ~1 min. This isn't the way to do things. Thanks.

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.

4 participants