kubernetes/vendor/github.com/cloudflare/cfssl/scan/scan_common.go
2018-08-08 21:22:01 -07:00

282 lines
6.9 KiB
Go

package scan
import (
"crypto/x509"
"net"
"net/http"
"regexp"
"sync"
"time"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/scan/crypto/tls"
)
var (
// Network is the default network to use.
Network = "tcp"
// Dialer is the default dialer to use, with a 1s timeout.
Dialer = &net.Dialer{Timeout: time.Second}
// Client is the default HTTP Client.
Client = &http.Client{Transport: &http.Transport{Dial: Dialer.Dial}}
// RootCAs defines the default root certificate authorities to be used for scan.
RootCAs *x509.CertPool
)
// Grade gives a subjective rating of the host's success in a scan.
type Grade int
const (
// Bad describes a host with serious misconfiguration or vulnerability.
Bad Grade = iota
// Warning describes a host with non-ideal configuration that maintains support for Warning clients.
Warning
// Good describes host performing the expected state-of-the-art.
Good
// Skipped descibes the "grade" of a scan that has been skipped.
Skipped
)
// String gives the name of the Grade as a string.
func (g Grade) String() string {
switch g {
case Bad:
return "Bad"
case Warning:
return "Warning"
case Good:
return "Good"
case Skipped:
return "Skipped"
default:
return "Invalid"
}
}
// Output is the result of a scan, to be stored for potential use by later Scanners.
type Output interface{}
// multiscan scans all DNS addresses returned for the host, returning the lowest grade
// and the concatenation of all the output.
func multiscan(host string, scan func(string) (Grade, Output, error)) (grade Grade, output Output, err error) {
domain, port, _ := net.SplitHostPort(host)
var addrs []string
addrs, err = net.LookupHost(domain)
if err != nil {
return
}
grade = Good
out := make(map[string]Output)
for _, addr := range addrs {
var g Grade
var o Output
g, o, err = scan(net.JoinHostPort(addr, port))
if err != nil {
grade = Bad
return
}
if g < grade {
grade = g
}
out[addr] = o
}
output = out
return
}
// Scanner describes a type of scan to perform on a host.
type Scanner struct {
// Description describes the nature of the scan to be performed.
Description string `json:"description"`
// scan is the function that scans the given host and provides a Grade and Output.
scan func(string, string) (Grade, Output, error)
}
// Scan performs the scan to be performed on the given host and stores its result.
func (s *Scanner) Scan(addr, hostname string) (Grade, Output, error) {
grade, output, err := s.scan(addr, hostname)
if err != nil {
log.Debugf("scan: %v", err)
return grade, output, err
}
return grade, output, err
}
// Family defines a set of related scans meant to be run together in sequence.
type Family struct {
// Description gives a short description of the scans performed scan/scan_common.goon the host.
Description string `json:"description"`
// Scanners is a list of scanners that are to be run in sequence.
Scanners map[string]*Scanner `json:"scanners"`
}
// FamilySet contains a set of Families to run Scans from.
type FamilySet map[string]*Family
// Default contains each scan Family that is defined
var Default = FamilySet{
"Connectivity": Connectivity,
"TLSHandshake": TLSHandshake,
"TLSSession": TLSSession,
"PKI": PKI,
"Broad": Broad,
}
// ScannerResult contains the result for a single scan.
type ScannerResult struct {
Grade string `json:"grade"`
Output Output `json:"output,omitempty"`
Error string `json:"error,omitempty"`
}
// FamilyResult contains a scan response for a single Family
type FamilyResult map[string]ScannerResult
// A Result contains a ScannerResult along with it's scanner and family names.
type Result struct {
Family, Scanner string
ScannerResult
}
type context struct {
sync.WaitGroup
addr, hostname string
familyRegexp, scannerRegexp *regexp.Regexp
resultChan chan *Result
}
func newContext(addr, hostname string, familyRegexp, scannerRegexp *regexp.Regexp, numFamilies int) *context {
ctx := &context{
addr: addr,
hostname: hostname,
familyRegexp: familyRegexp,
scannerRegexp: scannerRegexp,
resultChan: make(chan *Result),
}
ctx.Add(numFamilies)
go func() {
ctx.Wait()
close(ctx.resultChan)
}()
return ctx
}
type familyContext struct {
sync.WaitGroup
ctx *context
}
func (ctx *context) newfamilyContext(numScanners int) *familyContext {
familyCtx := &familyContext{ctx: ctx}
familyCtx.Add(numScanners)
go func() {
familyCtx.Wait()
familyCtx.ctx.Done()
}()
return familyCtx
}
func (ctx *context) copyResults(timeout time.Duration) map[string]FamilyResult {
results := make(map[string]FamilyResult)
for {
var result *Result
select {
case <-time.After(timeout):
log.Warningf("Scan timed out after %v", timeout)
return results
case result = <-ctx.resultChan:
if result == nil {
return results
}
}
if results[result.Family] == nil {
results[result.Family] = make(FamilyResult)
}
results[result.Family][result.Scanner] = result.ScannerResult
}
}
func (familyCtx *familyContext) runScanner(familyName, scannerName string, scanner *Scanner) {
if familyCtx.ctx.familyRegexp.MatchString(familyName) && familyCtx.ctx.scannerRegexp.MatchString(scannerName) {
grade, output, err := scanner.Scan(familyCtx.ctx.addr, familyCtx.ctx.hostname)
result := &Result{
familyName,
scannerName,
ScannerResult{
Grade: grade.String(),
Output: output,
},
}
if err != nil {
result.Error = err.Error()
}
familyCtx.ctx.resultChan <- result
}
familyCtx.Done()
}
// RunScans iterates over AllScans, running each scan that matches the family
// and scanner regular expressions concurrently.
func (fs FamilySet) RunScans(host, ip, family, scanner string, timeout time.Duration) (map[string]FamilyResult, error) {
hostname, port, err := net.SplitHostPort(host)
if err != nil {
hostname = host
port = "443"
}
var addr string
if net.ParseIP(ip) != nil {
addr = net.JoinHostPort(ip, port)
} else {
addr = net.JoinHostPort(hostname, port)
}
familyRegexp, err := regexp.Compile(family)
if err != nil {
return nil, err
}
scannerRegexp, err := regexp.Compile(scanner)
if err != nil {
return nil, err
}
ctx := newContext(addr, hostname, familyRegexp, scannerRegexp, len(fs))
for familyName, family := range fs {
familyCtx := ctx.newfamilyContext(len(family.Scanners))
for scannerName, scanner := range family.Scanners {
go familyCtx.runScanner(familyName, scannerName, scanner)
}
}
return ctx.copyResults(timeout), nil
}
// LoadRootCAs loads the default root certificate authorities from file.
func LoadRootCAs(caBundleFile string) (err error) {
if caBundleFile != "" {
log.Debugf("Loading scan RootCAs: %s", caBundleFile)
RootCAs, err = helpers.LoadPEMCertPool(caBundleFile)
}
return
}
func defaultTLSConfig(hostname string) *tls.Config {
return &tls.Config{
ServerName: hostname,
RootCAs: RootCAs,
InsecureSkipVerify: true,
}
}