mirror of https://gerrit.hackerspace.pl/hscloud
248 lines
5.1 KiB
Go
248 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"code.hackerspace.pl/hscloud/go/pki"
|
|
"github.com/golang/glog"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
pb "code.hackerspace.pl/hscloud/bgpwtf/cccampix/proto"
|
|
"code.hackerspace.pl/hscloud/bgpwtf/cccampix/verifier/model"
|
|
)
|
|
|
|
const (
|
|
RS_ASN = "AS208521"
|
|
RS_ASSET = "AS-CCCAMP19-IX"
|
|
)
|
|
|
|
type irr struct {
|
|
irrc pb.IRRClient
|
|
}
|
|
|
|
func newIRR(addr string) (processor, error) {
|
|
conn, err := grpc.Dial(addr, pki.WithClientHSPKI())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not connect to irr service: %v", err)
|
|
}
|
|
|
|
return &irr{
|
|
irrc: pb.NewIRRClient(conn),
|
|
}, nil
|
|
}
|
|
|
|
func (i *irr) Name() string {
|
|
return "IRR"
|
|
}
|
|
|
|
func (i *irr) NextRun(now time.Time) time.Time {
|
|
return now.Add(5 * time.Minute)
|
|
}
|
|
|
|
func (i *irr) RunAll(ctx context.Context, m model.Model) error {
|
|
peers, err := m.GetCheckablePeers(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("could not retrieve peers: %v", err)
|
|
}
|
|
|
|
results := make(chan *model.PeerCheckResult)
|
|
pcr := []*model.PeerCheckResult{}
|
|
pcrDone := make(chan struct{})
|
|
|
|
pgpKeys := make(chan *model.PeerPGPKey)
|
|
pk := []*model.PeerPGPKey{}
|
|
pkDone := make(chan struct{})
|
|
|
|
go func() {
|
|
for res := range results {
|
|
pcr = append(pcr, res)
|
|
}
|
|
pcrDone <- struct{}{}
|
|
}()
|
|
go func() {
|
|
for res := range pgpKeys {
|
|
pk = append(pk, res)
|
|
}
|
|
pkDone <- struct{}{}
|
|
}()
|
|
|
|
fail := func(p *model.Peer, hard bool, f string, args ...interface{}) {
|
|
status := model.PeerCheckStatus_SoftFailed
|
|
if hard {
|
|
status = model.PeerCheckStatus_Failed
|
|
}
|
|
results <- &model.PeerCheckResult{
|
|
PeerASN: p.ASN,
|
|
CheckName: "irr",
|
|
Time: time.Now(),
|
|
Status: status,
|
|
Message: fmt.Sprintf(f, args...),
|
|
}
|
|
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(peers))
|
|
|
|
sem := make(chan struct{}, 10)
|
|
|
|
for _, peer := range peers {
|
|
go func(p *model.Peer) {
|
|
sem <- struct{}{}
|
|
defer func() {
|
|
<-sem
|
|
wg.Done()
|
|
}()
|
|
|
|
req := &pb.IRRQueryRequest{
|
|
As: fmt.Sprintf("%d", p.ASN),
|
|
}
|
|
res, err := i.irrc.Query(ctx, req)
|
|
if err != nil {
|
|
s, ok := status.FromError(err)
|
|
switch {
|
|
case ok && s.Code() == codes.NotFound:
|
|
fail(p, true, "ASN %d not found in IRR", p.ASN)
|
|
case ok && s.Code() == codes.Unimplemented:
|
|
fail(p, true, "ASN %d belongs to an unknown IRR/RIR", p.ASN)
|
|
case ok && s.Code() == codes.Unavailable:
|
|
fail(p, false, "could not contact IRR")
|
|
default:
|
|
glog.Errorf("IRR.Query(%d): %v", p.ASN, err)
|
|
fail(p, false, "unhandled IRR error")
|
|
}
|
|
return
|
|
}
|
|
|
|
importOkay := false
|
|
exportOkay := false
|
|
pgpKey := ""
|
|
|
|
for _, attr := range res.Attributes {
|
|
switch value := attr.Value.(type) {
|
|
case *pb.IRRAttribute_Remarks:
|
|
if ok, key := i.checkRemarks(value.Remarks); ok {
|
|
pgpKey = key
|
|
}
|
|
case *pb.IRRAttribute_Import:
|
|
if i.checkImport(value.Import) {
|
|
importOkay = true
|
|
}
|
|
case *pb.IRRAttribute_Export:
|
|
if i.checkExport(value.Export, p.ASN) {
|
|
exportOkay = true
|
|
}
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case !importOkay:
|
|
fail(p, true, "no `import: from %s accept %s` entry", RS_ASN, RS_ASSET)
|
|
return
|
|
case !exportOkay:
|
|
fail(p, true, "no `export: to %s announce AS%d` entry", RS_ASN, p.ASN)
|
|
return
|
|
case pgpKey == "":
|
|
fail(p, true, "no `remarks: CCCAMP19-IX PGP: <...>` entry")
|
|
return
|
|
}
|
|
|
|
pgpKeys <- &model.PeerPGPKey{
|
|
PeerASN: p.ASN,
|
|
Fingerprint: pgpKey,
|
|
}
|
|
|
|
results <- &model.PeerCheckResult{
|
|
PeerASN: p.ASN,
|
|
CheckName: "irr",
|
|
Time: time.Now(),
|
|
Status: model.PeerCheckStatus_Okay,
|
|
Message: "",
|
|
}
|
|
}(peer)
|
|
}
|
|
|
|
wg.Wait()
|
|
close(results)
|
|
close(pgpKeys)
|
|
<-pcrDone
|
|
<-pkDone
|
|
|
|
err = m.SubmitPeerCheckResults(ctx, pcr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, k := range pk {
|
|
err = m.UpdatePGPKey(ctx, k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i *irr) checkRemarks(remarks string) (bool, string) {
|
|
label := "cccamp19-ix pgp:"
|
|
remarks = strings.TrimSpace(strings.ToLower(remarks))
|
|
if !strings.HasPrefix(remarks, label) {
|
|
return false, ""
|
|
}
|
|
|
|
data := strings.TrimSpace(strings.TrimPrefix(remarks, label))
|
|
data = strings.ReplaceAll(data, " ", "")
|
|
data = strings.ReplaceAll(data, "\t", "")
|
|
|
|
if len(data) != 40 {
|
|
return false, ""
|
|
}
|
|
|
|
if _, err := hex.DecodeString(data); err != nil {
|
|
return false, ""
|
|
}
|
|
|
|
return true, data
|
|
}
|
|
|
|
func (i *irr) checkImport(imp *pb.IRRAttribute_ImportExport) bool {
|
|
if imp.ProtocolFrom != "" && strings.ToLower(imp.ProtocolFrom) != "bgp" {
|
|
return false
|
|
}
|
|
if strings.ToUpper(imp.Filter) != RS_ASSET {
|
|
return false
|
|
}
|
|
|
|
for _, expression := range imp.Expressions {
|
|
if strings.ToUpper(expression.Peering) == RS_ASN {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (i *irr) checkExport(exp *pb.IRRAttribute_ImportExport, asn int64) bool {
|
|
if exp.ProtocolInto != "" && strings.ToLower(exp.ProtocolInto) != "bgp" {
|
|
return false
|
|
}
|
|
if strings.ToUpper(exp.Filter) != fmt.Sprintf("AS%d", asn) {
|
|
return false
|
|
}
|
|
|
|
for _, expression := range exp.Expressions {
|
|
if strings.ToUpper(expression.Peering) == RS_ASN {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|