Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for NAP Precompiled Publication #167

Merged
merged 1 commit into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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