Skip to content

Commit

Permalink
Add Support for NAP Pre-compiled Publication
Browse files Browse the repository at this point in the history
Add Agent support for pre-compiled NAP content published via
an external source.
  • Loading branch information
edarzins committed Jan 13, 2023
1 parent c0b8695 commit 75b6643
Show file tree
Hide file tree
Showing 26 changed files with 1,162 additions and 76 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ require (
require (
github.com/go-resty/resty/v2 v2.7.0
github.com/nginx/agent/sdk/v2 v2.0.0-00010101000000-000000000000
github.com/nginxinc/nginx-go-crossplane v0.4.1
github.com/prometheus/client_golang v1.13.0
gopkg.in/yaml.v2 v2.4.0
)

require (
Expand All @@ -54,7 +56,6 @@ require (
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/nginxinc/nginx-go-crossplane v0.4.1 // indirect
github.com/pascaldekloe/name v1.0.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
Expand All @@ -80,7 +81,6 @@ require (
google.golang.org/genproto v0.0.0-20220805133916-01dd62135a58 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

replace github.com/nginx/agent/sdk/v2 => ./sdk
4 changes: 3 additions & 1 deletion src/core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func SetDefaults() {

func SetNginxAppProtectDefaults() {
Viper.SetDefault(NginxAppProtectReportInterval, Defaults.NginxAppProtect.ReportInterval)
Viper.SetDefault(NginxAppProtectPrecompiledPublication, Defaults.NginxAppProtect.PrecompiledPublication)
}

func SetNAPMonitoringDefaults() {
Expand Down Expand Up @@ -307,7 +308,8 @@ func getDataplane() Dataplane {

func getNginxAppProtect() NginxAppProtect {
return NginxAppProtect{
ReportInterval: Viper.GetDuration(NginxAppProtectReportInterval),
ReportInterval: Viper.GetDuration(NginxAppProtectReportInterval),
PrecompiledPublication: Viper.GetBool(NginxAppProtectPrecompiledPublication),
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/core/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ var (
ReportInterval: time.Minute,
ReportCount: 400,
},
NginxAppProtect: NginxAppProtect{
PrecompiledPublication: false,
},
}
AllowedDirectoriesMap map[string]struct{}
)
Expand Down Expand Up @@ -172,7 +175,8 @@ const (
// viper keys used in config
NginxAppProtectKey = "nginx_app_protect"

NginxAppProtectReportInterval = NginxAppProtectKey + agent_config.KeyDelimiter + "report_interval"
NginxAppProtectReportInterval = NginxAppProtectKey + agent_config.KeyDelimiter + "report_interval"
NginxAppProtectPrecompiledPublication = NginxAppProtectKey + agent_config.KeyDelimiter + "precompiled_publication"

// viper keys used in config
NAPMonitoringKey = "nap_monitoring"
Expand Down Expand Up @@ -362,6 +366,11 @@ var (
Name: NginxAppProtectReportInterval,
Usage: "The period of time the agent will check for App Protect software changes on the dataplane",
},
&BoolFlag{
Name: NginxAppProtectPrecompiledPublication,
Usage: "Enables publication of NGINX App Protect pre-compiled content from an external source.",
DefaultValue: Defaults.NginxAppProtect.PrecompiledPublication,
},
// NAP Monitoring
&IntFlag{
Name: NAPMonitoringCollectorBufferSize,
Expand Down
7 changes: 6 additions & 1 deletion src/core/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func (c *Config) IsNginxAppProtectConfigured() bool {
return c.NginxAppProtect != (NginxAppProtect{})
}

func (c *Config) IsNginxAppProtectPrecompiledPublicationConfigured() bool {
return c.NginxAppProtect.PrecompiledPublication
}

func (c *Config) IsFeatureEnabled(feature string) bool {
for _, configFeature := range c.Features {
if configFeature == feature {
Expand Down Expand Up @@ -120,7 +124,8 @@ type AdvancedMetrics struct {
}

type NginxAppProtect struct {
ReportInterval time.Duration `mapstructure:"report_interval" yaml:"-"`
ReportInterval time.Duration `mapstructure:"report_interval" yaml:"-"`
PrecompiledPublication bool `mapstructure:"precompiled_publication" yaml:"-"`
}

type NAPMonitoring struct {
Expand Down
54 changes: 54 additions & 0 deletions src/extensions/nginx-app-protect/nap/attack_signatures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright (c) F5, Inc.
*
* This source code is licensed under the Apache License, Version 2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

package nap

import (
"fmt"
"io/ioutil"
"time"

"github.com/nginx/agent/v2/src/core"

"gopkg.in/yaml.v2"
)

// getAttackSignaturesVersion gets the version of the attack signatures package that is
// installed on the system, the version format is YYYY.MM.DD.
func getAttackSignaturesVersion(versionFile string) (string, error) {
// Check if attack signatures version file exists
logger.Debugf("Checking for the required NAP attack signatures version file - %v\n", versionFile)
installed, err := core.FileExists(versionFile)
if !installed && err == nil {
return "", nil
} else if err != nil {
return "", err
}

// Get the version bytes
versionBytes, err := ioutil.ReadFile(versionFile)
if err != nil {
return "", err
}

// Read bytes into object
attackSigVersionDateTime := napRevisionDateTime{}
err = yaml.UnmarshalStrict([]byte(versionBytes), &attackSigVersionDateTime)
if err != nil {
return "", err
}

// Convert revision date into the proper version format
attackSigTime, err := time.Parse(time.RFC3339, attackSigVersionDateTime.RevisionDatetime)
if err != nil {
return "", err
}
attackSignatureReleaseVersion := fmt.Sprintf("%d.%02d.%02d", attackSigTime.Year(), attackSigTime.Month(), attackSigTime.Day())
logger.Debugf("Converted attack signature version (%s) found in %s to - %s\n", attackSigVersionDateTime.RevisionDatetime, ATTACK_SIGNATURES_UPDATE_FILE, attackSignatureReleaseVersion)

return attackSignatureReleaseVersion, nil
}
70 changes: 70 additions & 0 deletions src/extensions/nginx-app-protect/nap/attack_signatures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright (c) F5, Inc.
*
* This source code is licensed under the Apache License, Version 2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

package nap

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
testAttackSigVersionFile = "/tmp/test-attack-sigs-version.yaml"
testAttackSigVersionFileContents = `---
checksum: t+N7AHGIKPhdDwb8zMZh2w
filename: signatures.bin.tgz
revisionDatetime: 2022-02-24T20:32:01Z`
)

func TestGetAttackSignaturesVersion(t *testing.T) {
testCases := []struct {
testName string
versionFile string
attackSigDateTime *napRevisionDateTime
expVersion string
expError error
}{
{
testName: "AttackSignaturesInstalled",
versionFile: testAttackSigVersionFile,
attackSigDateTime: &napRevisionDateTime{
RevisionDatetime: "2022-02-24T20:32:01Z",
},
expVersion: "2022.02.24",
expError: nil,
},
{
testName: "AttackSignaturesNotInstalled",
versionFile: ATTACK_SIGNATURES_UPDATE_FILE,
attackSigDateTime: nil,
expVersion: "",
expError: nil,
},
}

for _, tc := range testCases {
t.Run(tc.testName, func(t *testing.T) {
// Create a fake version file if required by test
if tc.attackSigDateTime != nil {
err := os.WriteFile(tc.versionFile, []byte(testAttackSigVersionFileContents), 0644)
require.NoError(t, err)

defer func() {
err := os.Remove(tc.versionFile)
require.NoError(t, err)
}()
}

version, err := getAttackSignaturesVersion(tc.versionFile)
assert.Equal(t, err, tc.expError)
assert.Equal(t, tc.expVersion, version)
})
}
}
15 changes: 15 additions & 0 deletions src/extensions/nginx-app-protect/nap/nap.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package nap

import (
"fmt"
"io/fs"
"os"
"path/filepath"
Expand Down Expand Up @@ -62,8 +63,22 @@ func NewNginxAppProtect(optDirPath, symLinkDir string) (*NginxAppProtect, error)
}
}

// Get attack signatures version
attackSigsVersion, err := getAttackSignaturesVersion(ATTACK_SIGNATURES_UPDATE_FILE)
if err != nil && err.Error() != fmt.Sprintf(FILE_NOT_FOUND, ATTACK_SIGNATURES_UPDATE_FILE) {
return nil, err
}

// Get threat campaigns version
threatCampaignsVersion, err := getThreatCampaignsVersion(THREAT_CAMPAIGNS_UPDATE_FILE)
if err != nil && err.Error() != fmt.Sprintf(FILE_NOT_FOUND, THREAT_CAMPAIGNS_UPDATE_FILE) {
return nil, err
}

// Update the NAP object with the values from NAP on the system
nap.Status = status.String()
nap.AttackSignaturesVersion = attackSigsVersion
nap.ThreatCampaignsVersion = threatCampaignsVersion
if napRelease != nil {
nap.Release = *napRelease
}
Expand Down
72 changes: 72 additions & 0 deletions src/extensions/nginx-app-protect/nap/nap_content.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright (c) F5, Inc.
*
* This source code is licensed under the Apache License, Version 2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

package nap

import (
"path"

"github.com/nginx/agent/sdk/v2"
"github.com/nginx/agent/sdk/v2/proto"

"github.com/nginxinc/nginx-go-crossplane"
)

// getContent parses the config for NAP policies and profiles
func getContent(cfg *proto.NginxConfig) ([]string, []string) {
policyMap := make(map[string]bool)
profileMap := make(map[string]bool)

for _, directory := range cfg.GetDirectoryMap().GetDirectories() {
for _, file := range directory.GetFiles() {
confFile := path.Join(directory.GetName(), file.GetName())
payload, err := crossplane.Parse(confFile,
&crossplane.ParseOptions{
SingleFile: false,
StopParsingOnError: true,
},
)
if err != nil {
continue
}
for _, conf := range payload.Config {
err = sdk.CrossplaneConfigTraverse(&conf,
func(parent *crossplane.Directive, directive *crossplane.Directive) (bool, error) {
switch directive.Directive {
case "app_protect_policy_file":
if len(directive.Args) == 1 {
_, policy := path.Split(directive.Args[0])
policyMap[policy] = true
}
case "app_protect_security_log":
if len(directive.Args) == 2 {
_, profile := path.Split(directive.Args[0])
profileMap[profile] = true
}
}
return true, nil
})
if err != nil {
continue
}
}
if err != nil {
continue
}
}
}
policies := []string{}
for policy, _ := range policyMap {
policies = append(policies, policy)
}
profiles := []string{}
for profile, _ := range profileMap {
profiles = append(profiles, profile)
}

return policies, profiles
}
Loading

0 comments on commit 75b6643

Please sign in to comment.