131 lines
4.0 KiB
Go
131 lines
4.0 KiB
Go
// go:generate mockgen -source ../../va/gsb.go -package mock_gsb -destination mock_gsb.go SafeBrowsingV4
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
safebrowsingv4 "github.com/google/safebrowsing"
|
|
"github.com/letsencrypt/boulder/cmd"
|
|
blog "github.com/letsencrypt/boulder/log"
|
|
"github.com/letsencrypt/boulder/va"
|
|
)
|
|
|
|
const (
|
|
// Filename used for the v4 safebrowsing client's local database in the
|
|
// configured GSB data directory. The file contents are a gzipped GOB encoding
|
|
// of the client's in-memory cache.
|
|
v4DbFilename = "safebrowsing.v4.cache.bin"
|
|
)
|
|
|
|
var (
|
|
NilConfigErr = errors.New("Google Safe Browsing config was nil")
|
|
EmptyAPIKeyErr = errors.New("a Google Safe Browsing config was given but " +
|
|
"it did not include a Google API key in APIKey")
|
|
EmptyDataDirErr = errors.New("a Google Safe Browsing config was given but " +
|
|
"it did not include a DataDir for persistence")
|
|
MissingDataDirErr = errors.New("a Google Safe Browsing data directory was " +
|
|
"given but it does not exist")
|
|
BadDataDirErr = errors.New("a Google Safe Browsing data directory was " +
|
|
"given but it cannot be opened")
|
|
EmptyURLThreatErr = errors.New("Empty URLThreat from LookupURLs[0]")
|
|
BadDBFileErr = errors.New("unable to create Google Safe Browsing v4 db " +
|
|
"file in data directory")
|
|
)
|
|
|
|
// configCheck returns an error if:
|
|
// * the gsb config struct given is nil
|
|
// * the gsb config struct's APIKey is empty
|
|
// * the gsb config struct's DataDir is empty
|
|
// * the gsb config struct's DataDir doesn't exist or isn't readable
|
|
func configCheck(gsb *cmd.GoogleSafeBrowsingConfig) error {
|
|
if gsb == nil {
|
|
return NilConfigErr
|
|
}
|
|
if gsb.APIKey == "" {
|
|
return EmptyAPIKeyErr
|
|
}
|
|
if gsb.DataDir == "" {
|
|
return EmptyDataDirErr
|
|
}
|
|
if _, err := os.Stat(gsb.DataDir); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return MissingDataDirErr
|
|
} else {
|
|
return BadDataDirErr
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// gsbAdapter adapts the Google safebrowsing's `SafeBrowser` type to the
|
|
// `va.SafeBrowsing` interface Boulder uses.
|
|
type gsbAdapter struct {
|
|
va.SafeBrowsingV4
|
|
}
|
|
|
|
// IsListed provides the va.SafeBrowsing interface by using the
|
|
// `safebrowsing4v.SafeBrowser` to look up one URL and return the first threat
|
|
// list it is found on, or "" if the URL is safe.
|
|
func (sb gsbAdapter) IsListed(ctx context.Context, url string) (string, error) {
|
|
threats, err := sb.LookupURLsContext(ctx, []string{url})
|
|
if err != nil {
|
|
return "error", err
|
|
}
|
|
if len(threats) > 0 && threats[0] != nil {
|
|
// Note: We don't bother to examine the threats for their ThreatType or
|
|
// other information. The va.SafeBrowser interface expects a "hit" return
|
|
// string that is compared against "" to make a "safe or not" decision. For
|
|
// our purposes it is sufficient to return a fixed string instead of further
|
|
// processing of the results.
|
|
return "v4-gsb-hit", nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// gsbLogAdapter adapts a blog.Logger to the io.Writer interface used by the
|
|
// Google safebrowsing client for a logger. All messages written to the Writer
|
|
// by the library will be adapter to the logger's Info method.
|
|
type gsbLogAdapter struct {
|
|
log blog.Logger
|
|
}
|
|
|
|
func (a gsbLogAdapter) Write(b []byte) (int, error) {
|
|
a.log.Info(string(b))
|
|
return len(b), nil
|
|
}
|
|
|
|
// newGoogleSafeBrowsingV4 constructs a va.SafeBrowsing instance using the new
|
|
// Google upstream Safe Browsing version 4 client.
|
|
func newGoogleSafeBrowsingV4(gsb *cmd.GoogleSafeBrowsingConfig, logger blog.Logger) (va.SafeBrowsing, error) {
|
|
// If there is no GSB configuration, don't create a client
|
|
if gsb == nil {
|
|
return nil, nil
|
|
}
|
|
if err := configCheck(gsb); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dbFile := filepath.Join(gsb.DataDir, v4DbFilename)
|
|
sb, err := safebrowsingv4.NewSafeBrowser(safebrowsingv4.Config{
|
|
APIKey: gsb.APIKey,
|
|
DBPath: dbFile,
|
|
ServerURL: gsb.ServerURL,
|
|
Logger: gsbLogAdapter{logger},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
|
|
defer cancel() // avoid leak
|
|
err = sb.WaitUntilReady(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return gsbAdapter{sb}, nil
|
|
}
|