Skip to content

Commit

Permalink
Feature/s3 simplify onboarding (#2299)
Browse files Browse the repository at this point in the history
* Updated S3 Log Source onboard fields (#2183)

* export goToStep from wizard context

* Added condition for skipping step in S3SourceConfigurationPanel

* Transform prefix & logTypes to S3PrefixLogTypes

* Changed order of KMS field

* Fixes based on feedback

* Quick wording fix

* Fix creating s3 log source tests

* Fixes tests for EditLogSource

* Added additonal logType for combobox

* Added type to validation

* Renamed var for skipping cfn step

* Optimizing changes

* Fix breaking tes

* Added a test case for removing an existing prefix

* Added React.memo for flatten logTypes

* Fixed issue where button was active before query execution

* Updated snapshots

* Fixed dependicies for memo

* Feedback fixes

* Updated LinkButton

* Updated broken tests

* Removed s3PrefixLogTypes from GetCfnTemplate

* Backend: Allow users to specify multiple s3 prefix->logtypes mappings per s3 log source (#2232)

* Allow multiple s3 prefixes per s3 source

* Cleanup prefix parameter from log-onboarding CFN template

Now that users can define multiple s3 prefixes per onboarded
bucket (log source), this commit removes the prefix filter from the
`s3:GetObject` permission for the log processing role so that users
can eventually update prefixes in an s3 source without having to
update the CFN template.

"Eventually" is the key word here though. Due to backwards compatibility,
users that already have deployed a stack that includes a prefix restriction,
need to update their stack to remove it. For now, we have to prompt all users
to update their stack.

In order to allow some users to not update their CFN stack, we have to change
the current FE<>BE flow so that the backend can check whether the installed
stack needs update (by querying AWS) and return a flag to the frontend accordingly.

* Add unit test for backwards compatibility

* Fix json fields to match graphql schema

* Get unique logtypes for s3 source

* Add validation for unique prefixes per source and prefix/bucket pairs

* Narrow down prefixes in the self registered sources for Cloudtrail, GuardDuty, ALB, s3 access logs

* For s3 sources in the log processor, use the log types of the longest prefix that matches the input object key

Co-authored-by: Alex Mylonas <[email protected]>
  • Loading branch information
giorgosp and alexmylonas authored Dec 18, 2020
1 parent 5b74652 commit c8e5667
Show file tree
Hide file tree
Showing 46 changed files with 1,044 additions and 362 deletions.
19 changes: 12 additions & 7 deletions api/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@ input GetS3LogIntegrationTemplateInput {
awsAccountId: String!
integrationLabel: String!
s3Bucket: String!
s3Prefix: String
kmsKey: String
logTypes: [String!]!
}

input ListAlertsInput {
Expand Down Expand Up @@ -562,6 +560,10 @@ type ComplianceIntegration {

union LogIntegration = S3LogIntegration | SqsLogSourceIntegration

type S3PrefixLogTypes {
prefix: String!
logTypes: [String!]!
}
type S3LogIntegration {
awsAccountId: String!
createdAtTime: AWSDateTime!
Expand All @@ -573,7 +575,7 @@ type S3LogIntegration {
s3Bucket: String!
s3Prefix: String
kmsKey: String
logTypes: [String!]!
s3PrefixLogTypes: [S3PrefixLogTypes!]!
health: S3LogIntegrationHealth!
stackName: String!
}
Expand All @@ -596,13 +598,17 @@ input AddComplianceIntegrationInput {
cweEnabled: Boolean
}

input S3PrefixLogTypesInput {
prefix: String!
logTypes: [String!]!
}

input AddS3LogIntegrationInput {
awsAccountId: String!
integrationLabel: String!
s3Bucket: String!
kmsKey: String
s3Prefix: String
logTypes: [String!]!
s3PrefixLogTypes: [S3PrefixLogTypesInput!]!
}

input SqsLogConfigInput {
Expand All @@ -628,8 +634,7 @@ input UpdateS3LogIntegrationInput {
integrationLabel: String
s3Bucket: String
kmsKey: String
s3Prefix: String
logTypes: [String!]
s3PrefixLogTypes: [S3PrefixLogTypesInput!]
}

input UpdateSqsLogIntegrationInput {
Expand Down
44 changes: 21 additions & 23 deletions api/lambda/source/models/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ type CheckIntegrationInput struct {
EnableRemediation *bool `json:"enableRemediation"`

// Checks for log analysis integrations
S3Bucket string `json:"s3Bucket"`
S3Prefix string `json:"s3Prefix"`
KmsKey string `json:"kmsKey"`
S3Bucket string `json:"s3Bucket"`
S3PrefixLogTypes S3PrefixLogtypes `json:"s3PrefixLogTypes,omitempty"`
KmsKey string `json:"kmsKey"`

// Checks for Sqs configuration
SqsConfig *SqsConfig `json:"sqsConfig,omitempty"`
Expand All @@ -74,17 +74,16 @@ type PutIntegrationInput struct {

// PutIntegrationSettings are all the settings for the new integration.
type PutIntegrationSettings struct {
IntegrationLabel string `json:"integrationLabel" validate:"required,integrationLabel,excludesall='<>&\""`
IntegrationType string `json:"integrationType" validate:"oneof=aws-scan aws-s3 aws-sqs"`
UserID string `json:"userId" validate:"required,uuid4"`
AWSAccountID string `genericapi:"redact" json:"awsAccountId" validate:"omitempty,len=12,numeric"`
CWEEnabled *bool `json:"cweEnabled"`
RemediationEnabled *bool `json:"remediationEnabled"`
ScanIntervalMins int `json:"scanIntervalMins" validate:"omitempty,oneof=60 180 360 720 1440"`
S3Bucket string `json:"s3Bucket"`
S3Prefix string `json:"s3Prefix" validate:"omitempty,min=1"`
KmsKey string `json:"kmsKey" validate:"omitempty,kmsKeyArn"`
LogTypes []string `json:"logTypes" validate:"omitempty,min=1"`
IntegrationLabel string `json:"integrationLabel" validate:"required,integrationLabel,excludesall='<>&\""`
IntegrationType string `json:"integrationType" validate:"oneof=aws-scan aws-s3 aws-sqs"`
UserID string `json:"userId" validate:"required,uuid4"`
AWSAccountID string `genericapi:"redact" json:"awsAccountId" validate:"omitempty,len=12,numeric"`
CWEEnabled *bool `json:"cweEnabled"`
RemediationEnabled *bool `json:"remediationEnabled"`
ScanIntervalMins int `json:"scanIntervalMins" validate:"omitempty,oneof=60 180 360 720 1440"`
S3Bucket string `json:"s3Bucket"`
S3PrefixLogTypes S3PrefixLogtypes `json:"s3PrefixLogTypes,omitempty" validate:"omitempty,min=1"`
KmsKey string `json:"kmsKey" validate:"omitempty,kmsKeyArn"`

SqsConfig *SqsConfig `json:"sqsConfig,omitempty"`
}
Expand All @@ -100,15 +99,14 @@ type ListIntegrationsInput struct {

// UpdateIntegrationSettingsInput is used to update integration settings.
type UpdateIntegrationSettingsInput struct {
IntegrationID string `json:"integrationId" validate:"required,uuid4"`
IntegrationLabel string `json:"integrationLabel" validate:"required,integrationLabel,excludesall='<>&\""`
CWEEnabled *bool `json:"cweEnabled"`
RemediationEnabled *bool `json:"remediationEnabled"`
ScanIntervalMins int `json:"scanIntervalMins" validate:"omitempty,oneof=60 180 360 720 1440"`
S3Bucket string `json:"s3Bucket" validate:"omitempty,min=1"`
S3Prefix string `json:"s3Prefix" validate:"omitempty,min=1"`
KmsKey string `json:"kmsKey" validate:"omitempty,kmsKeyArn"`
LogTypes []string `json:"logTypes" validate:"omitempty,min=1"`
IntegrationID string `json:"integrationId" validate:"required,uuid4"`
IntegrationLabel string `json:"integrationLabel" validate:"required,integrationLabel,excludesall='<>&\""`
CWEEnabled *bool `json:"cweEnabled"`
RemediationEnabled *bool `json:"remediationEnabled"`
ScanIntervalMins int `json:"scanIntervalMins" validate:"omitempty,oneof=60 180 360 720 1440"`
S3Bucket string `json:"s3Bucket" validate:"omitempty,min=1"`
S3PrefixLogTypes S3PrefixLogtypes `json:"s3PrefixLogTypes,omitempty" validate:"omitempty,min=1"`
KmsKey string `json:"kmsKey" validate:"omitempty,kmsKeyArn"`

SqsConfig *SqsConfig `json:"sqsConfig,omitempty"`
}
Expand Down
114 changes: 76 additions & 38 deletions api/lambda/source/models/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ package models
*/

import (
"fmt"
"strings"
"time"

"github.com/panther-labs/panther/internal/compliance/snapshotlogs"
"github.com/panther-labs/panther/internal/log_analysis/log_processor/logtypes"
"github.com/panther-labs/panther/pkg/stringset"
)

// SourceIntegration represents a Panther integration with a source.
Expand All @@ -48,34 +51,76 @@ type SourceIntegrationScanInformation struct {

// SourceIntegrationMetadata is general settings and metadata for an integration.
type SourceIntegrationMetadata struct {
AWSAccountID string `json:"awsAccountId,omitempty"`
CreatedAtTime time.Time `json:"createdAtTime,omitempty"`
CreatedBy string `json:"createdBy,omitempty"`
IntegrationID string `json:"integrationId,omitempty"`
IntegrationLabel string `json:"integrationLabel,omitempty"`
IntegrationType string `json:"integrationType,omitempty"`
RemediationEnabled *bool `json:"remediationEnabled,omitempty"`
CWEEnabled *bool `json:"cweEnabled,omitempty"`
ScanIntervalMins int `json:"scanIntervalMins,omitempty"`
S3Bucket string `json:"s3Bucket,omitempty"`
S3Prefix string `json:"s3Prefix,omitempty"`
KmsKey string `json:"kmsKey,omitempty"`
LogTypes []string `json:"logTypes,omitempty"`
LogProcessingRole string `json:"logProcessingRole,omitempty"`
StackName string `json:"stackName,omitempty"`
SqsConfig *SqsConfig `json:"sqsConfig,omitempty"`
AWSAccountID string `json:"awsAccountId,omitempty"`
CreatedAtTime time.Time `json:"createdAtTime,omitempty"`
CreatedBy string `json:"createdBy,omitempty"`
IntegrationID string `json:"integrationId,omitempty"`
IntegrationLabel string `json:"integrationLabel,omitempty"`
IntegrationType string `json:"integrationType,omitempty"`
RemediationEnabled *bool `json:"remediationEnabled,omitempty"`
CWEEnabled *bool `json:"cweEnabled,omitempty"`
ScanIntervalMins int `json:"scanIntervalMins,omitempty"`

// fields specific for an s3 integration (plus AWSAccountID, StackName)
S3Bucket string `json:"s3Bucket,omitempty"`
S3PrefixLogTypes S3PrefixLogtypes `json:"s3PrefixLogTypes,omitempty"`
KmsKey string `json:"kmsKey,omitempty"`
LogProcessingRole string `json:"logProcessingRole,omitempty"`

StackName string `json:"stackName,omitempty"`

SqsConfig *SqsConfig `json:"sqsConfig,omitempty"`
}

// S3PrefixLogtypesMapping contains the logtypes Panther should parse for this s3 prefix.
type S3PrefixLogtypesMapping struct {
S3Prefix string `json:"prefix"`
LogTypes []string `json:"logTypes" validate:"required,min=1"`
}

type S3PrefixLogtypes []S3PrefixLogtypesMapping

func (pl S3PrefixLogtypes) LogTypes() []string {
var logTypes []string
for _, m := range pl {
logTypes = stringset.Append(logTypes, m.LogTypes...)
}
return logTypes
}

func (pl S3PrefixLogtypes) S3Prefixes() []string {
prefixes := make([]string, len(pl))
for i, m := range pl {
prefixes[i] = m.S3Prefix
}
return prefixes
}

// Return the S3PrefixLogtypesMapping whose prefix is the longest one that matches the objectKey.
func (pl S3PrefixLogtypes) LongestPrefixMatch(objectKey string) (bestMatch S3PrefixLogtypesMapping, matched bool) {
for _, m := range pl {
if strings.HasPrefix(objectKey, m.S3Prefix) && len(m.S3Prefix) >= len(bestMatch.S3Prefix) {
bestMatch = m
matched = true
}
}
return bestMatch, matched
}

// Note: Don't use this for classification as the S3 source has different
// log types per prefix defined.
func (s *SourceIntegration) RequiredLogTypes() (logTypes []string) {
switch typ := s.IntegrationType; typ {
case IntegrationTypeAWS3:
return s.LogTypes
switch s.IntegrationType {
case IntegrationTypeAWSScan:
return logtypes.CollectNames(snapshotlogs.LogTypes())
case IntegrationTypeAWS3:
return s.S3PrefixLogTypes.LogTypes()
case IntegrationTypeSqs:
return s.SqsConfig.LogTypes
default:
panic("Unknown type " + typ)
// should not be reached
panic(fmt.Sprintf("Could not determine logtypes for source {id:%s label:%s type:%s}",
s.IntegrationID, s.IntegrationLabel, s.IntegrationType))
}
}

Expand All @@ -90,27 +135,20 @@ func (s *SourceIntegration) RequiredLogProcessingRole() string {
}
}

func (s *SourceIntegration) RequiredS3Prefix() string {
switch typ := s.IntegrationType; typ {
case IntegrationTypeAWS3:
return s.S3Prefix
// Return the s3 bucket and prefixes configured to hold input data for this source.
// For an s3 source, bucket and prefixes are user inputs.
func (s *SourceIntegration) S3Info() (bucket string, prefixes []string) {
switch s.IntegrationType {
case IntegrationTypeAWSScan:
return "cloudsecurity"
case IntegrationTypeSqs:
return "forwarder"
default:
panic("Unknown type " + typ)
}
}

func (s *SourceIntegration) RequiredS3Bucket() string {
switch typ := s.IntegrationType; typ {
case IntegrationTypeAWS3, IntegrationTypeAWSScan:
return s.S3Bucket
return s.S3Bucket, []string{"cloudsecurity"}
case IntegrationTypeAWS3:
return s.S3Bucket, s.S3PrefixLogTypes.S3Prefixes()
case IntegrationTypeSqs:
return s.SqsConfig.S3Bucket
return s.SqsConfig.S3Bucket, []string{"forwarder"}
default:
panic("Unknown type " + typ)
// should not be reached
panic(fmt.Sprintf("Could not determine s3 info for source {id:%s label:%s type:%s}",
s.IntegrationID, s.IntegrationLabel, s.IntegrationType))
}
}

Expand Down
61 changes: 61 additions & 0 deletions api/lambda/source/models/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package models

/**
* Panther is a Cloud-Native SIEM for the Modern Security Team.
* Copyright (C) 2020 Panther Labs Inc
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import (
"testing"

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

func TestS3PrefixLogtypes_LongestPrefixMatch(t *testing.T) {
pl := S3PrefixLogtypes{
{"prefixA/", []string{"Log.A"}},
{"prefixA/prefixB", []string{"Log.B"}},
{"", []string{"Log.C"}},
}

testcases := []struct {
objectKey string
expected S3PrefixLogtypesMapping
}{
{"prefixA/log.json", pl[0]},
{"prefixA/pref/log.json", pl[0]},
{"prefixA/prefixB/log.json", pl[1]},
{"log.json", pl[2]},
{"logs/log.json", pl[2]},
{"prefixB/log.json", pl[2]},
}
for _, tc := range testcases {
actual, _ := pl.LongestPrefixMatch(tc.objectKey)
require.Equal(t, tc.expected, actual, "Fail for input object key '%s'", tc.objectKey)
}
}

func TestS3PrefixLogtypes_LongestPrefixMatch_ReturnNil(t *testing.T) {
pl := S3PrefixLogtypes{
{"prefixA/", []string{"Log.A"}},
{"prefixA/prefixB", []string{"Log.B"}},
}

_, matched := pl.LongestPrefixMatch("logs/log.json")

// No prefix matched
require.False(t, matched)
}
2 changes: 1 addition & 1 deletion cmd/devtools/logprocessor/logprocessor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func main() {
IntegrationID: *flagSourceID,
IntegrationType: models.IntegrationTypeAWS3,
IntegrationLabel: *flagSourceLabel,
LogTypes: logTypes,
S3PrefixLogTypes: models.S3PrefixLogtypes{{S3Prefix: "", LogTypes: logTypes}},
},
},
}
Expand Down
20 changes: 3 additions & 17 deletions deployments/auxiliary/cloudformation/panther-log-analysis-iam.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ Mappings:
Value: '' # RoleSuffix
S3Bucket:
Value: '' # S3Bucket
S3Prefix:
Value: '' # S3Prefix
KmsKey:
Value: '' # KmsKey

Expand All @@ -48,10 +46,6 @@ Parameters:
Default: ''

# Optional configuration parameters
S3Prefix:
Type: String
Description: DO NOT EDIT MANUALLY! Parameter is already populated with the appropriate value.
Default: ''
KmsKey:
Type: String
Description: DO NOT EDIT MANUALLY! Parameter is already populated with the appropriate value.
Expand Down Expand Up @@ -113,17 +107,9 @@ Resources:
- Effect: Allow
Action: s3:GetBucketLocation
Resource: !Sub 'arn:aws:s3:::${S3Bucket}'
- !If
- IsGenerated
- Effect: Allow
Action: s3:GetObject
Resource: !Sub
- 'arn:aws:s3:::${Bucket}/${Prefix}*'
- Bucket: !FindInMap [PantherParameters, S3Bucket, Value]
Prefix: !FindInMap [PantherParameters, S3Prefix, Value]
- Effect: Allow
Action: s3:GetObject
Resource: !Sub 'arn:aws:s3:::${S3Bucket}/${S3Prefix}*'
- Effect: Allow
Action: s3:GetObject
Resource: !Sub 'arn:aws:s3:::${S3Bucket}/*'
- !If
- IncludeKmsKey
- !If
Expand Down
Loading

0 comments on commit c8e5667

Please sign in to comment.