diff --git a/README.md b/README.md index 4dcf88b6..5f79f4f0 100644 --- a/README.md +++ b/README.md @@ -25,21 +25,16 @@ The Nasp kernel module comes with a user space [CLI](./cli/) written in Go. The ### Agent (server) The agent is the server side of the CLI. It is responsible for the following: + - communicates with the kernel module directly -- parses and compiles the policy file to Wasm and loads it to the kernel module +- parses policy rule files and loads them to the kernel module - signs CSR requests generated by the kernel module - adds metadata from the host environment to enrich process data. (e.g. Kubernetes, AWS, etc...) Usage: ```bash -sudo nasp agent -``` - -### Client - -```bash -nasp load-policy --policy-file ./policy.json +sudo nasp agent --rules-path $(pwd)/nasp.d/rules --sd-path $(pwd)/nasp.d/services ``` ## Development @@ -59,7 +54,7 @@ GOOS=linux make build ### Run the agent on the Lima guest ```bash -sudo ./bin/nasp --rules-path $(pwd)/rules.d +sudo ./bin/nasp --rules-path $(pwd)/nasp.d/rules --sd-path $(pwd)/nasp.d/services ``` ## Installation diff --git a/internal/cli/cmd/agent/agent.go b/internal/cli/cmd/agent/agent.go index 8b705009..d1d0aca2 100644 --- a/internal/cli/cmd/agent/agent.go +++ b/internal/cli/cmd/agent/agent.go @@ -33,6 +33,7 @@ import ( "github.com/cisco-open/nasp/pkg/agent/server" "github.com/cisco-open/nasp/pkg/config" "github.com/cisco-open/nasp/pkg/rules" + "github.com/cisco-open/nasp/pkg/sd" "github.com/cisco-open/nasp/pkg/tls" ) @@ -59,6 +60,7 @@ func NewCommand(c cli.CLI) *cobra.Command { cmd.PersistentFlags().String("agent-local-address", "/tmp/nasp/agent.sock", "Local address") cmd.Flags().String("kernel-module-device", "/dev/nasp", "Device for the Nasp kernel module") cmd.Flags().StringSlice("rules-path", nil, "Rules path") + cmd.Flags().StringSlice("sd-path", nil, "Service discovery definition path") cmd.Flags().String("trust-domain", config.DefaultTrustDomain, "Trust domain") cmd.Flags().Duration("default-cert-ttl", config.DefaultCertTTLDuration, "Default certificate TTL") cmd.Flags().String("ca-pem-path", "", "Path for CA pem") @@ -116,8 +118,6 @@ func (c *agentCommand) run(cmd *cobra.Command) error { logger.Info("sending config to kernel") if cj, err := json.Marshal(config.KernelModuleConfig{TrustDomain: c.cli.Configuration().Agent.TrustDomain}); err != nil { c.cli.Logger().Error(err, "could not marshal module config") - - return } else { eventBus.Publish(messenger.MessageOutgoingTopic, messenger.NewCommand(messenger.Command{ Command: "load_config", @@ -126,6 +126,25 @@ func (c *agentCommand) run(cmd *cobra.Command) error { } }) + // service discovery loader + eventBus.Subscribe(messenger.MessengerStartedTopic, func(topic string, _ bool) { + go func() { + l := sd.NewFilesLoader(c.cli.Viper().GetStringSlice("agent.sdPath"), logger) + if err := l.Run(cmd.Context(), func(entries sd.Entries) { + if j, err := json.Marshal(entries); err != nil { + c.cli.Logger().Error(err, "could not marshal module config") + } else { + eventBus.Publish(messenger.MessageOutgoingTopic, messenger.NewCommand(messenger.Command{ + Command: "load_sd_info", + Code: j, + })) + } + }); err != nil { + errChan <- err + } + }() + }) + // rules loader eventBus.Subscribe(messenger.MessengerStartedTopic, func(topic string, _ bool) { go func() { diff --git a/rules.d/ci.yaml b/nasp.d/rules/ci.yaml similarity index 88% rename from rules.d/ci.yaml rename to nasp.d/rules/ci.yaml index a485657c..34fe4e1a 100644 --- a/rules.d/ci.yaml +++ b/nasp.d/rules/ci.yaml @@ -20,7 +20,7 @@ # local python or go file-server - linux:uid: [501, 1001] linux:binary:name: [python3.10, python3.11, file-server] - destination:port: 8000 + destination:port: [8000, 8080] properties: mtls: true workloadID: accounting/department-a/important-backend @@ -41,3 +41,8 @@ properties: mtls: true workloadID: curl + egress: + - selectors: + - label: traefik + properties: + workloadID: specific-workload-id diff --git a/nasp.d/services/ci.yaml b/nasp.d/services/ci.yaml new file mode 100644 index 00000000..3ce652a3 --- /dev/null +++ b/nasp.d/services/ci.yaml @@ -0,0 +1,10 @@ +- tags: + - app:label:traefik + - linux:uid:500 + - linux:binary:name:traefik + addresses: + - localhost:8000 +- addresses: + - localhost:8080 + tags: + - app-label:python diff --git a/pkg/sd/entry.go b/pkg/sd/entry.go new file mode 100644 index 00000000..b1b90ae6 --- /dev/null +++ b/pkg/sd/entry.go @@ -0,0 +1,33 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2023 Cisco and/or its affiliates. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package sd + +type Entries map[string]Entry + +type RawEntries []*RawEntry + +type RawEntry struct { + Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"` + Addresses []string `yaml:"addresses,omitempty" json:"addresses,omitempty"` +} + +type Entry struct { + Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"` +} diff --git a/pkg/sd/loader.go b/pkg/sd/loader.go new file mode 100644 index 00000000..261a32f4 --- /dev/null +++ b/pkg/sd/loader.go @@ -0,0 +1,154 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2023 Cisco and/or its affiliates. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package sd + +import ( + "context" + "fmt" + "net" + "net/netip" + "strconv" + "strings" + "sync" + + "github.com/go-logr/logr" + "gopkg.in/yaml.v2" + + "github.com/cisco-open/nasp/pkg/util" +) + +type FilesLoader interface { + Run(context.Context, EntryHandlerFunc) error +} + +type filesLoader struct { + fileContentLoader util.FileContentLoader + logger logr.Logger + + mu sync.Mutex +} + +type EntryHandlerFunc func(Entries) + +func NewFilesLoader(paths []string, logger logr.Logger) FilesLoader { + l := logger.WithName("sdFileLoader") + + return &filesLoader{ + fileContentLoader: util.NewFileContentLoader(paths, l), + logger: l, + + mu: sync.Mutex{}, + } +} + +func (r *filesLoader) Run(ctx context.Context, h EntryHandlerFunc) error { + return r.fileContentLoader.Run(ctx, func(contents map[string][]byte) { + loadedEntries := make(Entries, 0) + + for file, content := range contents { + var rawEntries RawEntries + if err := yaml.Unmarshal(content, &rawEntries); err != nil { + r.logger.Error(err, "error during sd file parsing", "path", file) + continue + } + + for _, entry := range rawEntries { + // skip invalid entries + if len(entry.Tags) == 0 || len(entry.Addresses) == 0 { + continue + } + for _, address := range entry.Addresses { + addrs := resolveAddress(address) + for _, addr := range addrs { + loadedEntries[addressToString(addr)] = Entry{ + Tags: entry.Tags, + } + } + } + } + } + + h(loadedEntries) + }) +} + +func addressToString(addr netip.AddrPort) string { + if addr.Port() == 0 { + return addr.Addr().StringExpanded() + } + + return fmt.Sprintf("%s:%d", addr.Addr().StringExpanded(), addr.Port()) +} + +func resolveAddress(address string) []netip.AddrPort { + hasPort := strings.ContainsRune(address, ':') + addrporrs := make([]netip.AddrPort, 0) + + var host, port string + var err error + + if hasPort { + host, port, err = net.SplitHostPort(address) + if err != nil { + return nil + } + } else { + host = address + } + + var intport int + if port != "" { + intport, err = strconv.Atoi(port) + if err != nil { + return nil + } + } + + // there are some issues at some systems retrieving ipv4 address when there are ipv6 addresses as well + // for that reason there is a separate direct ipv4 lookup + // TODO @wayne - more investigation needed + addrs := make(map[netip.Addr]struct{}) + if addr, err := netip.ParseAddr(host); err != nil { + netips, err := net.DefaultResolver.LookupIP(context.Background(), "ip4", host) + if err == nil { + for _, ip := range netips { + if addr, ok := netip.AddrFromSlice(ip.To4()); ok { + addrs[addr] = struct{}{} + } + } + } + netips, err = net.DefaultResolver.LookupIP(context.Background(), "ip", host) + if err == nil { + for _, ip := range netips { + if addr, ok := netip.AddrFromSlice(ip); ok { + addrs[addr] = struct{}{} + } + } + } + } else { + addrs[addr] = struct{}{} + } + + for addr := range addrs { + addrporrs = append(addrporrs, netip.AddrPortFrom(addr, uint16(intport))) + } + + return addrporrs +}