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

ACME client integration into webpkgserver. #53

Merged
merged 1 commit into from
Sep 25, 2020
Merged

ACME client integration into webpkgserver. #53

merged 1 commit into from
Sep 25, 2020

Conversation

banaag
Copy link
Collaborator

@banaag banaag commented Sep 5, 2020

Integrate support for ACME client into webpkgserver start-up.

@banaag banaag requested a review from yuizumi September 5, 2020 00:05
@banaag
Copy link
Collaborator Author

banaag commented Sep 5, 2020

cc:@twifkak

Copy link
Contributor

@yuizumi yuizumi left a comment

Choose a reason for hiding this comment

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

Mostly good. Sorry for slow response.

"io/ioutil"
"net/http"
"net/url"
"os"

"github.com/WICG/webpackage/go/signedexchange"
"github.com/google/webpackager/certchain"
"github.com/pkg/errors"
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't use pkg/errors. Use xerrors instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Apologies, I forgot about this. Fixed.

mc := certmanager.Config{
RawChainSource: certmanager.NewLocalCertFile(
var rcs certmanager.RawChainSource
rcs = certmanager.NewLocalCertFile(
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks to be unused.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, already removed but perhaps you saw an older copy.

@@ -77,6 +80,24 @@ func ReadPrivateKeyFile(filename string) (crypto.PrivateKey, error) {
return signedexchange.ParsePrivateKey(pem)
}

// ReadCertificateRequestFile reads a PEM file and returns an x508 Certificate
Copy link
Contributor

Choose a reason for hiding this comment

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

"x508" looks like typo. It should be "x509" or "X.509", but I guess we can just remove it given (IIRC) we call it just "Certificate Request" elsewhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed.

return nil, err
}

rcs, err = acmeclient.NewClient(
Copy link
Contributor

Choose a reason for hiding this comment

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

Consistency:

rcs, err = acmeclient.NewClient(
    acmeclient.Config{
        // ...
    },                          // Note the trailing comma.
) 

OR

rcs, err = acmeclient.NewClient(acmeclient.Config{
    // ...
})

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed (2nd choice).

return nil, err
}
} else {
rcs = certmanager.NewLocalCertFile(
Copy link
Contributor

Choose a reason for hiding this comment

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

Same.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

func ReadCertificateRequestFile(filename string) (*x509.CertificateRequest, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, errors.Wrapf(err, "reading %s", filename)
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to wrap. ioutil.ReadFile (naturally) calls os.Open internally, os.Open always returns *os.PathError, thus the message already contains the filename.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}
block, _ := pem.Decode(data)
if block == nil {
return nil, errors.Errorf("pem decode: no key found in %s", filename)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does "no key found" describe the problem correctly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

@@ -130,13 +127,18 @@ func (a *Augmentor) daemon(rcNext, orNext futureevent.Event) {
orNext, err = a.maintainOCSPResp()
if err != nil {
log.Printf("cannot update the OCSP response: %v", err)
} else {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note that I added these log statements to indicate that the OCSP retrieval was successful after several failed retries.

@@ -107,23 +107,23 @@ func (c OCSPClient) Fetch(chain *certchain.RawChain, now func() time.Time) (ocsp

raw, expiry, err := c.fetch(chain)
if err != nil {
err = fmt.Errorf("cannot fetch OCSP response: %v", err)
err = fmt.Errorf("cannot fetch OCSP response: %v, retrying in %v", err, retryWait)
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 also added the amount of time before next retry in the error, because it was useful to know how long the next retry would be.

@@ -107,9 +107,6 @@ func (a *Augmentor) Start() error {
return err
}
orNext, err := a.maintainOCSPResp()
if err != nil {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yusuke, I had to remove this check and return statement. In the event that during start-up, we had an expired certificate and ACME retrieved it, it is very common for DigiCert OCSP response to fail for a period of time (due to updates of their data in the CDN). In this case, it will be beneficial to retry until we get the proper OCSP response. In the previous state of the code, no retry was attempted and the server exits with an error.

Copy link
Contributor

Choose a reason for hiding this comment

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

I understand there is a problem but I'm not fully sure if you had to ― I don't think dropping this if statement is the only option. At the very least the code is now violating the contract written in GoDoc: "It produces the first AugmentedChain before starting the goroutine and blocks until it is ready." This change can introduce subtle bugs since the rest of codebase is written with relying on that contract.

I have to say removing this check is over-focused on the DigiCert+ACME case. With this change Augmentor won't be able to handle some bad certificates in a proper way, including:

  • those truly revoked, for which the OCSP responder will never return okay.
  • those with a bogus OCSP URI, which can't be accessed in the first place.

The most straightforward approach is to do a "white-true" loop to wait until the function receives a valid ocsp response:

var orNext futureevent.Event
for {
  orNext,err = a.maintainOCSPResp()
  if err == nil {
    break
  }
  log.Print(err)
  <-orNext.Chan()  // I don't mean a busy loop.
}

If possible, it's nicer to determine whether the failure is transient or permanent, and exit the loop on a permanent error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the comments. Updated to your suggested logic but added a retry counter so that it can break out of the loop after N retries.


var orNext futureevent.Event
i := 0
maxRetries := 3
Copy link
Contributor

Choose a reason for hiding this comment

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

Make it a const.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}
if i >= maxRetries {
log.Println("Exceeded number of retries for OCSP.")
break
Copy link
Contributor

Choose a reason for hiding this comment

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

return err. otherwise my point is not (fully) addressed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

return err

var orNext futureevent.Event
i := 0
Copy link
Contributor

Choose a reason for hiding this comment

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

Give a little more meaningful name, say, retryCount.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

OCSPRespSource: certmanager.NewOCSPClient(
certmanager.OCSPClientConfig{
AllowTestCert: c.SXG.Cert.AllowTestCert,
},
),
}
if c.SXG.Cert.CacheDir == "" {
if c.SXG.Cert.CacheDir != "" {
fmt.Printf("Creating SXG Cert Cache Directory: %s\n", c.SXG.Cert.CacheDir)
Copy link
Contributor

Choose a reason for hiding this comment

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

"Creating SXG cert cache directory" ― sentence case rather than title case. Maybe s/cert/certificate/.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

@@ -77,6 +80,24 @@ func ReadPrivateKeyFile(filename string) (crypto.PrivateKey, error) {
return signedexchange.ParsePrivateKey(pem)
}

// ReadCertificateRequestFile reads a PEM file and returns a Certificate
// Request usable for requesting Signed Exchange certificates.
Copy link
Contributor

Choose a reason for hiding this comment

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

GoDoc should be just "... returns a Certificate Request." This function checks nothing of SXG compatibility.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

}

Copy link
Contributor

Choose a reason for hiding this comment

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

If you find this empty line increase the readability, add another above the line case <-orNext.Chan() for consistency. Otherwise, remove this empty line.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed.


var orNext futureevent.Event
retryCount := 0
const maxRetries = 3
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe maxRetryCount (for consistency with retryCount). Alternative is to rename retryCount to retries, but I think retryCount is more descriptive.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

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.

2 participants