123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- 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,
- }
- }
|