Skip to content
This repository was archived by the owner on Feb 29, 2024. It is now read-only.

Commit cfecf1b

Browse files
authored
Merge pull request #35 from cloudflare/feature/filter
Filter and assertion implementation (#34)
2 parents efd6962 + 60f8a6f commit cfecf1b

File tree

6 files changed

+507
-8
lines changed

6 files changed

+507
-8
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ vet:
2424
.PHONY: test
2525
test:
2626
go test -v github.com/cloudflare/gortr/lib
27+
go test -v github.com/cloudflare/gortr/prefixfile
2728

2829
.PHONY: prepare
2930
prepare:

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,72 @@ And to configure a bypass for every SSH key:
188188
$ ./gortr -ssh.bind :8282 -ssh.key private.pem -ssh.method.key=true -ssh.auth.key.bypass=true -bind ""
189189
```
190190

191+
## Configure filters and overrides (SLURM)
192+
193+
GoRTR supports SLURM configuration files ([RFC8416](https://tools.ietf.org/html/rfc8416)).
194+
195+
Create a json file (`slurm.json`):
196+
197+
```
198+
{
199+
"slurmVersion": 1,
200+
"validationOutputFilters": {
201+
"prefixFilters": [
202+
{
203+
"prefix": "10.0.0.0/8",
204+
"comment": "Everything inside will be removed"
205+
},
206+
{
207+
"asn": 65001,
208+
},
209+
{
210+
"asn": 65002,
211+
"prefix": "192.168.0.0/24",
212+
},
213+
],
214+
"bgpsecFilters": []
215+
},
216+
"locallyAddedAssertions": {
217+
"prefixAssertions": [
218+
{
219+
"asn": 65001,
220+
"prefix": "2001:db8::/32",
221+
"maxPrefixLength": 48,
222+
"comment": "Manual add"
223+
}
224+
],
225+
"bgpsecAssertions": [
226+
]
227+
}
228+
}
229+
```
230+
231+
When starting GoRTR, add the `-slurm ./slurm.json` argument.
232+
233+
The log should display something similar to the following:
234+
235+
```
236+
INFO[0001] Slurm filtering: 112214 kept, 159 removed, 1 asserted
237+
INFO[0002] New update (112215 uniques, 112215 total prefixes).
238+
```
239+
240+
For instance, if the original JSON fetched contains the ROA: `10.0.0.0/24-24 AS65001`,
241+
it will be removed.
242+
243+
The JSON exported by GoRTR will contain the overrides and the file can be signed again.
244+
Others GoRTR can be configured to fetch the ROAs from the filtering GoRTR:
245+
the operator manages one SLURM file on a leader GoRTR.
246+
191247
## Debug the content
192248

249+
You can check the content provided over RTR with rtrdump tool
250+
193251
```bash
194252
$ ./rtrdump -connect 127.0.0.1:8282 -file debug.json
195253
```
196254

255+
You can also fetch the re-generated JSON from the `-export.path` endpoint (default: `http://localhost:8080/rpki.json`)
256+
197257
### Data sources
198258

199259
Use your own validator, as long as the JSON source follows the following schema:

cmd/gortr/gortr.go

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"os/signal"
2727
"runtime"
2828
"strings"
29+
"sync"
2930
"syscall"
3031
"time"
3132
)
@@ -46,7 +47,11 @@ var (
4647

4748
MetricsAddr = flag.String("metrics.addr", ":8080", "Metrics address")
4849
MetricsPath = flag.String("metrics.path", "/metrics", "Metrics path")
49-
RTRVersion = flag.Int("protocol", 1, "RTR protocol version")
50+
51+
ExportPath = flag.String("export.path", "/rpki.json", "Export path")
52+
ExportSign = flag.String("export.sign", "", "Sign export with key")
53+
54+
RTRVersion = flag.Int("protocol", 1, "RTR protocol version")
5055

5156
Bind = flag.String("bind", ":8282", "Bind address")
5257

@@ -75,6 +80,9 @@ var (
7580
MaxConn = flag.Int("maxconn", 0, "Max simultaneous connections (0 to disable limit)")
7681
SendNotifs = flag.Bool("notifications", true, "Send notifications to clients")
7782

83+
Slurm = flag.String("slurm", "", "Slurm configuration file (filters and assertions)")
84+
SlurmRefresh = flag.Bool("slurm.refresh", true, "Refresh along the cache")
85+
7886
LogLevel = flag.String("loglevel", "info", "Log level")
7987
Version = flag.Bool("version", false, "Print version")
8088

@@ -201,15 +209,15 @@ func decodeJSON(data []byte) (*prefixfile.ROAList, error) {
201209
return &roalistjson, err
202210
}
203211

204-
func processData(roalistjson *prefixfile.ROAList) ([]rtr.ROA, int, int, int) {
212+
func processData(roalistjson []prefixfile.ROAJson) ([]rtr.ROA, int, int, int) {
205213
filterDuplicates := make(map[string]bool)
206214

207215
roalist := make([]rtr.ROA, 0)
208216

209217
var count int
210218
var countv4 int
211219
var countv6 int
212-
for _, v := range roalistjson.Data {
220+
for _, v := range roalistjson {
213221
prefix, err := v.GetPrefix2()
214222
if err != nil {
215223
log.Error(err)
@@ -300,7 +308,37 @@ func (s *state) updateFile(file string) error {
300308
log.Debugf("Signature verified")
301309
}
302310

303-
roas, count, countv4, countv6 := processData(roalistjson)
311+
roasjson := roalistjson.Data
312+
if s.slurm != nil {
313+
kept, removed := s.slurm.FilterOnROAs(roasjson)
314+
asserted := s.slurm.AssertROAs()
315+
log.Infof("Slurm filtering: %v kept, %v removed, %v asserted", len(kept), len(removed), len(asserted))
316+
roasjson = append(kept, asserted...)
317+
}
318+
s.lockJson.Lock()
319+
s.exported = prefixfile.ROAList{
320+
Metadata: prefixfile.MetaData{
321+
Counts: len(roasjson),
322+
Generated: roalistjson.Metadata.Generated,
323+
Valid: roalistjson.Metadata.Valid,
324+
/*Signature: roalistjson.Metadata.Signature,
325+
SignatureDate: roalistjson.Metadata.SignatureDate,*/
326+
},
327+
Data: roasjson,
328+
}
329+
330+
if s.key != nil {
331+
signdate, sign, err := s.exported.Sign(s.key)
332+
if err != nil {
333+
log.Error(err)
334+
}
335+
s.exported.Metadata.Signature = sign
336+
s.exported.Metadata.SignatureDate = signdate
337+
}
338+
339+
s.lockJson.Unlock()
340+
341+
roas, count, countv4, countv6 := processData(roasjson)
304342
if err != nil {
305343
return err
306344
}
@@ -333,8 +371,26 @@ func (s *state) updateFile(file string) error {
333371
return nil
334372
}
335373

336-
func (s *state) routineUpdate(file string, interval int) {
337-
log.Debugf("Starting refresh routine (file: %v, interval: %vs)", file, interval)
374+
func (s *state) updateSlurm(file string) error {
375+
log.Debugf("Refreshing slurm from %v", file)
376+
data, err := fetchFile(file, s.userAgent)
377+
if err != nil {
378+
log.Error(err)
379+
return err
380+
}
381+
382+
buf := bytes.NewBuffer(data)
383+
384+
slurm, err := prefixfile.DecodeJSONSlurm(buf)
385+
if err != nil {
386+
return err
387+
}
388+
s.slurm = slurm
389+
return nil
390+
}
391+
392+
func (s *state) routineUpdate(file string, interval int, slurmFile string) {
393+
log.Debugf("Starting refresh routine (file: %v, interval: %vs, slurm: %v)", file, interval, slurmFile)
338394
signals := make(chan os.Signal, 1)
339395
signal.Notify(signals, syscall.SIGHUP)
340396
for {
@@ -345,6 +401,12 @@ func (s *state) routineUpdate(file string, interval int) {
345401
log.Debug("Received HUP signal")
346402
}
347403
delay.Stop()
404+
if slurmFile != "" {
405+
err := s.updateSlurm(slurmFile)
406+
if err != nil {
407+
log.Errorf("Slurm: %v", err)
408+
}
409+
}
348410
err := s.updateFile(file)
349411
if err != nil {
350412
switch err.(type) {
@@ -357,6 +419,14 @@ func (s *state) routineUpdate(file string, interval int) {
357419
}
358420
}
359421

422+
func (s *state) exporter(wr http.ResponseWriter, r *http.Request) {
423+
s.lockJson.RLock()
424+
toExport := s.exported
425+
s.lockJson.RUnlock()
426+
enc := json.NewEncoder(wr)
427+
enc.Encode(toExport)
428+
}
429+
360430
type state struct {
361431
lastdata []byte
362432
lastconverted []byte
@@ -369,6 +439,12 @@ type state struct {
369439

370440
metricsEvent *metricsEvent
371441

442+
exported prefixfile.ROAList
443+
lockJson *sync.RWMutex
444+
key *ecdsa.PrivateKey
445+
446+
slurm *prefixfile.SlurmConfig
447+
372448
pubkey *ecdsa.PublicKey
373449
verify bool
374450
checktime bool
@@ -420,6 +496,19 @@ func ReadPublicKey(key []byte, isPem bool) (*ecdsa.PublicKey, error) {
420496
return kconv, nil
421497
}
422498

499+
func ReadKey(key []byte, isPem bool) (*ecdsa.PrivateKey, error) {
500+
if isPem {
501+
block, _ := pem.Decode(key)
502+
key = block.Bytes
503+
}
504+
505+
k, err := x509.ParseECPrivateKey(key)
506+
if err != nil {
507+
return nil, err
508+
}
509+
return k, nil
510+
}
511+
423512
func main() {
424513
runtime.GOMAXPROCS(runtime.NumCPU())
425514

@@ -443,10 +532,11 @@ func main() {
443532
}
444533

445534
var me *metricsEvent
535+
var enableHTTP bool
446536
if *MetricsAddr != "" {
447537
initMetrics()
448-
go metricHTTP()
449538
me = &metricsEvent{}
539+
enableHTTP = true
450540
}
451541

452542
server := rtr.NewServer(sc, me, deh)
@@ -473,6 +563,31 @@ func main() {
473563
verify: *Verify,
474564
checktime: *TimeCheck,
475565
userAgent: *UserAgent,
566+
lockJson: &sync.RWMutex{},
567+
}
568+
569+
if *ExportSign != "" {
570+
keyFile, err := os.Open(*ExportSign)
571+
if err != nil {
572+
log.Fatal(err)
573+
}
574+
keyBytes, err := ioutil.ReadAll(keyFile)
575+
if err != nil {
576+
log.Fatal(err)
577+
}
578+
keyFile.Close()
579+
keyDec, err := ReadKey(keyBytes, true)
580+
if err != nil {
581+
log.Fatal(err)
582+
}
583+
s.key = keyDec
584+
}
585+
586+
if enableHTTP {
587+
if *ExportPath != "" {
588+
http.HandleFunc(*ExportPath, s.exporter)
589+
}
590+
go metricHTTP()
476591
}
477592

478593
if *Bind == "" && *BindTLS == "" && *BindSSH == "" {
@@ -589,6 +704,17 @@ func main() {
589704
}()
590705
}
591706

707+
slurmFile := *Slurm
708+
if slurmFile != "" {
709+
err := s.updateSlurm(slurmFile)
710+
if err != nil {
711+
log.Errorf("Slurm: %v", err)
712+
}
713+
if !*SlurmRefresh {
714+
slurmFile = ""
715+
}
716+
}
717+
592718
err := s.updateFile(*CacheBin)
593719
if err != nil {
594720
switch err.(type) {
@@ -598,6 +724,6 @@ func main() {
598724
log.Errorf("Error updating: %v", err)
599725
}
600726
}
601-
s.routineUpdate(*CacheBin, *RefreshInterval)
727+
s.routineUpdate(*CacheBin, *RefreshInterval, slurmFile)
602728

603729
}

prefixfile/prefixfile.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,11 @@ func (roa *ROAJson) GetMaxLen() int {
145145
func (roa *ROAJson) String() string {
146146
return fmt.Sprintf("%v/%v/%v", roa.Prefix, roa.Length, roa.ASN)
147147
}
148+
149+
func GetIPBroadcast(ipnet net.IPNet) net.IP {
150+
br := make([]byte, len(ipnet.IP))
151+
for i := 0; i < len(ipnet.IP); i++ {
152+
br[i] = ipnet.IP[i] | (^ipnet.Mask[i])
153+
}
154+
return net.IP(br)
155+
}

0 commit comments

Comments
 (0)