snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
239
security/advancedtls/crl_provider.go
Normal file
239
security/advancedtls/crl_provider.go
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package advancedtls
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultCRLRefreshDuration = 1 * time.Hour
|
||||
const minCRLRefreshDuration = 1 * time.Minute
|
||||
|
||||
// CRLProvider is the interface to be implemented to enable custom CRL provider
|
||||
// behavior, as defined in [gRFC A69].
|
||||
//
|
||||
// The interface defines how gRPC gets CRLs from the provider during handshakes,
|
||||
// but doesn't prescribe a specific way to load and store CRLs. Such
|
||||
// implementations can be used in RevocationOptions of advancedtls.ClientOptions
|
||||
// and/or advancedtls.ServerOptions.
|
||||
// Please note that checking CRLs is directly on the path of connection
|
||||
// establishment, so implementations of the CRL function need to be fast, and
|
||||
// slow things such as file IO should be done asynchronously.
|
||||
//
|
||||
// [gRFC A69]: https://github.com/grpc/proposal/pull/382
|
||||
type CRLProvider interface {
|
||||
// CRL accepts x509 Cert and returns a related CRL struct, which can contain
|
||||
// either an empty or non-empty list of revoked certificates. If an error is
|
||||
// thrown or (nil, nil) is returned, it indicates that we can't load any
|
||||
// authoritative CRL files (which may not necessarily be a problem). It's not
|
||||
// considered invalid to have no CRLs if there are no revocations for an
|
||||
// issuer. In such cases, the status of the check CRL operation is marked as
|
||||
// RevocationUndetermined, as defined in [RFC5280 - Undetermined].
|
||||
//
|
||||
// [RFC5280 - Undetermined]: https://datatracker.ietf.org/doc/html/rfc5280#section-6.3.3
|
||||
CRL(cert *x509.Certificate) (*CRL, error)
|
||||
}
|
||||
|
||||
// StaticCRLProvider implements CRLProvider interface by accepting raw content
|
||||
// of CRL files at creation time and storing parsed CRL structs in-memory.
|
||||
type StaticCRLProvider struct {
|
||||
crls map[string]*CRL
|
||||
}
|
||||
|
||||
// NewStaticCRLProvider processes raw content of CRL files, adds parsed CRL
|
||||
// structs into in-memory, and returns a new instance of the StaticCRLProvider.
|
||||
func NewStaticCRLProvider(rawCRLs [][]byte) *StaticCRLProvider {
|
||||
p := StaticCRLProvider{}
|
||||
p.crls = make(map[string]*CRL)
|
||||
for idx, rawCRL := range rawCRLs {
|
||||
cRL, err := NewCRL(rawCRL)
|
||||
if err != nil {
|
||||
grpclogLogger.Warningf("Can't parse raw CRL number %v from the slice: %v", idx, err)
|
||||
continue
|
||||
}
|
||||
p.addCRL(cRL)
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
// AddCRL adds/updates provided CRL to in-memory storage.
|
||||
func (p *StaticCRLProvider) addCRL(crl *CRL) {
|
||||
key := crl.certList.Issuer.ToRDNSequence().String()
|
||||
p.crls[key] = crl
|
||||
}
|
||||
|
||||
// CRL returns CRL struct if it was passed to NewStaticCRLProvider.
|
||||
func (p *StaticCRLProvider) CRL(cert *x509.Certificate) (*CRL, error) {
|
||||
return p.crls[cert.Issuer.ToRDNSequence().String()], nil
|
||||
}
|
||||
|
||||
// FileWatcherOptions represents a data structure holding a configuration for
|
||||
// FileWatcherCRLProvider.
|
||||
type FileWatcherOptions struct {
|
||||
CRLDirectory string // Required: Path of the directory containing CRL files
|
||||
RefreshDuration time.Duration // Optional: Time interval (default 1 hour) between CRLDirectory scans, can't be smaller than 1 minute
|
||||
CRLReloadingFailedCallback func(err error) // Optional: Custom callback executed when a CRL file can’t be processed
|
||||
}
|
||||
|
||||
// FileWatcherCRLProvider implements the CRLProvider interface by periodically
|
||||
// scanning CRLDirectory (see FileWatcherOptions) and storing CRL structs
|
||||
// in-memory. Users should call Close to stop the background refresh of
|
||||
// CRLDirectory.
|
||||
type FileWatcherCRLProvider struct {
|
||||
crls map[string]*CRL
|
||||
opts FileWatcherOptions
|
||||
mu sync.Mutex
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewFileWatcherCRLProvider returns a new instance of the
|
||||
// FileWatcherCRLProvider. It uses FileWatcherOptions to validate and apply
|
||||
// configuration required for creating a new instance. The initial scan of
|
||||
// CRLDirectory is performed inside this function. Users should call Close to
|
||||
// stop the background refresh of CRLDirectory.
|
||||
func NewFileWatcherCRLProvider(o FileWatcherOptions) (*FileWatcherCRLProvider, error) {
|
||||
if err := o.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider := &FileWatcherCRLProvider{
|
||||
crls: make(map[string]*CRL),
|
||||
opts: o,
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
provider.scanCRLDirectory()
|
||||
go provider.run()
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (o *FileWatcherOptions) validate() error {
|
||||
// Checks relates to CRLDirectory.
|
||||
if o.CRLDirectory == "" {
|
||||
return fmt.Errorf("advancedtls: CRLDirectory needs to be specified")
|
||||
}
|
||||
if _, err := os.ReadDir(o.CRLDirectory); err != nil {
|
||||
return fmt.Errorf("advancedtls: CRLDirectory %v is not readable: %v", o.CRLDirectory, err)
|
||||
}
|
||||
// Checks related to RefreshDuration.
|
||||
if o.RefreshDuration == 0 {
|
||||
o.RefreshDuration = defaultCRLRefreshDuration
|
||||
}
|
||||
if o.RefreshDuration < minCRLRefreshDuration {
|
||||
grpclogLogger.Warningf("RefreshDuration must be at least 1 minute: provided value %v, minimum value %v will be used.", o.RefreshDuration, minCRLRefreshDuration)
|
||||
o.RefreshDuration = minCRLRefreshDuration
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts watching the directory for CRL files and updates the provider accordingly.
|
||||
func (p *FileWatcherCRLProvider) run() {
|
||||
defer close(p.done)
|
||||
ticker := time.NewTicker(p.opts.RefreshDuration)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-p.stop:
|
||||
grpclogLogger.Infof("Scanning of CRLDirectory %v stopped", p.opts.CRLDirectory)
|
||||
return
|
||||
case <-ticker.C:
|
||||
p.scanCRLDirectory()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close waits till the background refresh of CRLDirectory of
|
||||
// FileWatcherCRLProvider is done and then stops it.
|
||||
func (p *FileWatcherCRLProvider) Close() {
|
||||
close(p.stop)
|
||||
<-p.done
|
||||
}
|
||||
|
||||
// scanCRLDirectory starts the process of scanning
|
||||
// FileWatcherOptions.CRLDirectory and updating in-memory storage of CRL
|
||||
// structs, as defined in [gRFC A69]. It's called periodically
|
||||
// (see FileWatcherOptions.RefreshDuration) by run goroutine.
|
||||
//
|
||||
// [gRFC A69]: https://github.com/grpc/proposal/pull/382
|
||||
func (p *FileWatcherCRLProvider) scanCRLDirectory() {
|
||||
dir, err := os.Open(p.opts.CRLDirectory)
|
||||
if err != nil {
|
||||
grpclogLogger.Errorf("Can't open CRLDirectory %v", p.opts.CRLDirectory, err)
|
||||
if p.opts.CRLReloadingFailedCallback != nil {
|
||||
p.opts.CRLReloadingFailedCallback(err)
|
||||
}
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
files, err := dir.ReadDir(0)
|
||||
if err != nil {
|
||||
grpclogLogger.Errorf("Can't access files under CRLDirectory %v", p.opts.CRLDirectory, err)
|
||||
if p.opts.CRLReloadingFailedCallback != nil {
|
||||
p.opts.CRLReloadingFailedCallback(err)
|
||||
}
|
||||
}
|
||||
|
||||
tempCRLs := make(map[string]*CRL)
|
||||
successCounter := 0
|
||||
failCounter := 0
|
||||
for _, file := range files {
|
||||
filePath := fmt.Sprintf("%s/%s", p.opts.CRLDirectory, file.Name())
|
||||
crl, err := ReadCRLFile(filePath)
|
||||
if err != nil {
|
||||
failCounter++
|
||||
grpclogLogger.Warningf("Can't add CRL from file %v under CRLDirectory %v", filePath, p.opts.CRLDirectory, err)
|
||||
if p.opts.CRLReloadingFailedCallback != nil {
|
||||
p.opts.CRLReloadingFailedCallback(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
tempCRLs[crl.certList.Issuer.ToRDNSequence().String()] = crl
|
||||
successCounter++
|
||||
}
|
||||
// Only if all the files are processed successfully we can swap maps (there
|
||||
// might be deletions of entries in this case).
|
||||
if len(files) == successCounter {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.crls = tempCRLs
|
||||
grpclogLogger.Infof("Scan of CRLDirectory %v completed, %v files found and processed successfully, in-memory CRL storage flushed and repopulated", p.opts.CRLDirectory, len(files))
|
||||
} else {
|
||||
// Since some of the files failed we can only add/update entries in the map.
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
for key, value := range tempCRLs {
|
||||
p.crls[key] = value
|
||||
}
|
||||
grpclogLogger.Infof("Scan of CRLDirectory %v completed, %v files found, %v files processing failed, %v entries of in-memory CRL storage added/updated", p.opts.CRLDirectory, len(files), failCounter, successCounter)
|
||||
}
|
||||
}
|
||||
|
||||
// CRL retrieves the CRL associated with the given certificate's issuer DN from
|
||||
// in-memory if it was loaded during FileWatcherOptions.CRLDirectory scan before
|
||||
// the execution of this function.
|
||||
func (p *FileWatcherCRLProvider) CRL(cert *x509.Certificate) (*CRL, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.crls[cert.Issuer.ToRDNSequence().String()], nil
|
||||
}
|
||||
Reference in New Issue
Block a user