Skip to content

Commit 11bedbd

Browse files
committed
Phase 1 features list, sync, diff, iam-admins
* S3 and Local database modeling * List and synchronize reports in an S3 bucket * Extract iam-admin risks from a specified report * Calculate the ARN-level diff between a historical principals or resources report and the latest such report * Dev tooling updates: * Prevent accidentally commiting test reports * Updated deps Signed-off-by: Jeff Nickoloff <[email protected]>
1 parent 17ed66a commit 11bedbd

21 files changed

+1984
-29
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ resources/
44
vendor/
55
secrets/
66
*.swp
7+
customers/

cmd/debug_env.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,50 @@ import (
2222
"fmt"
2323

2424
"github.com/aws/aws-sdk-go-v2/config"
25+
"github.com/aws/aws-sdk-go-v2/service/s3"
26+
"github.com/k9securityio/k9-cli/core"
2527
"github.com/spf13/cobra"
2628
)
2729

2830
// debugEnvCmd represents the debugEnv command
2931
var debugEnvCmd = &cobra.Command{
3032
Hidden: true,
3133
Run: func(cmd *cobra.Command, args []string) {
34+
// fmt.Println("debug-env called")
3235
cfg, err := config.LoadDefaultConfig(context.TODO())
3336
if err != nil {
3437
fmt.Printf("Error retrieving AWS configuration: %+v\n", err)
3538
} else {
36-
fmt.Printf("AWS Configuration: %+v\n", cfg)
37-
fmt.Printf("AWS Credentials: %+v\n", cfg.Credentials)
39+
// fmt.Printf("AWS Configuration: %+v\n", cfg)
40+
// fmt.Printf("AWS Credentials: %+v\n", cfg.Credentials)
41+
}
42+
43+
bucket, _ := cmd.Flags().GetString(`bucket`)
44+
if len(bucket) > 0 {
45+
client := s3.NewFromConfig(cfg)
46+
s3db, err := core.LoadS3DB(client, bucket)
47+
if err != nil {
48+
fmt.Fprintf(cmd.ErrOrStderr(), "Unable to load s3 database, %v\n", err)
49+
} else {
50+
fmt.Fprintln(cmd.OutOrStdout(), `Bucket database summary:`)
51+
s3db.Dump(cmd.OutOrStdout(), true)
52+
}
53+
} else {
54+
db, err := core.LoadLocalDB(cmd.Flags().Lookup(`report-home`).Value.String())
55+
if err != nil {
56+
fmt.Fprintf(cmd.ErrOrStderr(), "Unable to load local database, %v\n", err)
57+
} else {
58+
fmt.Fprintln(cmd.OutOrStdout(), `Local database summary:`)
59+
db.Dump(cmd.OutOrStdout(), true)
60+
}
3861
}
39-
fmt.Println("debug-env called")
4062
},
4163
Use: "debug-env",
4264
Short: "Display debugEnv information about the environment",
4365
}
4466

4567
func init() {
4668
rootCmd.AddCommand(debugEnvCmd)
69+
debugEnvCmd.Flags().String(`bucket`, ``, `S3 bucket location of your K9 secure inbox`)
70+
4771
}

cmd/diff.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ var diffCmd = &cobra.Command{
3636
// init defines and wires flags
3737
func init() {
3838
rootCmd.AddCommand(diffCmd)
39-
diffCmd.Flags().String(`format`, `csv`, `Output format: [csv]`)
40-
viper.BindPFlag(`diff_format`, diffCmd.Flags().Lookup(`format`))
39+
diffCmd.PersistentFlags().String(`format`, `csv`, `Output format: [csv]`)
40+
viper.BindPFlag(`diff_format`, diffCmd.PersistentFlags().Lookup(`format`))
41+
42+
diffCmd.PersistentFlags().String(`analysis-date`, ``, `Use snapshot from the specified date in YYYY-MM-DD (required)`)
43+
diffCmd.MarkFlagRequired(`analysis-date`)
44+
diffCmd.PersistentFlags().String(`customer_id`, ``, `K9 customer ID for analysis (required)`)
45+
diffCmd.MarkFlagRequired(`customer_id`)
46+
diffCmd.PersistentFlags().String(`account`, ``, `AWS account ID for analysis (required)`)
47+
diffCmd.MarkFlagRequired(`account`)
4148
}

cmd/diff_principals.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
Copyright © 2022 The K9CLI Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package cmd contains all cobra commands
18+
package cmd
19+
20+
import (
21+
"fmt"
22+
"io"
23+
"os"
24+
"time"
25+
26+
"github.com/k9securityio/k9-cli/core"
27+
"github.com/k9securityio/k9-cli/views"
28+
"github.com/spf13/cobra"
29+
)
30+
31+
// diffPrincipalsCmd represents the principals subcommand of diff
32+
var diffPrincipalsCmd = &cobra.Command{
33+
Use: "principals",
34+
Short: `Calculate the difference between a principals snapshot and last scan`,
35+
Run: func(cmd *cobra.Command, args []string) {
36+
verbose, _ := cmd.Flags().GetBool(`verbose`)
37+
customerID, _ := cmd.Flags().GetString(`customer_id`)
38+
accountID, _ := cmd.Flags().GetString(`account`)
39+
analysisDate, _ := cmd.Flags().GetString(`analysis-date`)
40+
reportHome, _ := cmd.Flags().GetString(`report-home`)
41+
stdout := cmd.OutOrStdout()
42+
stderr := cmd.ErrOrStderr()
43+
DoDiffPrincipals(stdout, stderr, reportHome, customerID, accountID, analysisDate, verbose)
44+
},
45+
}
46+
47+
// init defines and wires flags
48+
func init() {
49+
diffCmd.AddCommand(diffPrincipalsCmd)
50+
}
51+
52+
func DoDiffPrincipals(stdout, stderr io.Writer, reportHome, customerID, accountID, analysisDate string, verbose bool) {
53+
// load the local report database
54+
db, err := core.LoadLocalDB(reportHome)
55+
if err != nil {
56+
fmt.Fprintf(stderr, "Unable to load local database, %v\n", err)
57+
}
58+
59+
// get the latest analysis
60+
var latestReportPath, targetReportPath string
61+
62+
if qr := db.GetPathForCustomerAccountLatestKind(
63+
customerID, accountID, core.REPORT_TYPE_PREFIX_PRINCIPALS); qr != nil {
64+
latestReportPath = *qr
65+
} else {
66+
fmt.Fprintf(stderr,
67+
"No such latest report: %v, %v, total records: %v\n",
68+
customerID, accountID, db.Size())
69+
os.Exit(1)
70+
return
71+
}
72+
73+
// get the target analysis
74+
// determine the file name for the desired report
75+
reportDateTime, err := time.Parse(core.FILENAME_TIMESTAMP_ANALYSIS_DATE_LAYOUT, analysisDate)
76+
if err != nil {
77+
fmt.Fprintf(stderr, "Invalid analysis-date: %v\n", analysisDate)
78+
os.Exit(1)
79+
return
80+
}
81+
if qr := db.GetPathForCustomerAccountTimeKind(
82+
customerID, accountID, reportDateTime,
83+
core.REPORT_TYPE_PREFIX_PRINCIPALS); qr != nil {
84+
targetReportPath = *qr
85+
} else {
86+
fmt.Fprintf(stderr,
87+
"No such target report: %v, %v, %v, total records: %v\n",
88+
customerID, accountID,
89+
reportDateTime.Format(core.FILENAME_TIMESTAMP_ANALYSIS_DATE_LAYOUT),
90+
db.Size())
91+
os.Exit(1)
92+
return
93+
}
94+
95+
// open and load the reports
96+
lf, err := os.Open(latestReportPath)
97+
if err != nil {
98+
fmt.Fprintf(stderr, "Unable to open the latest report: %v\n", err)
99+
os.Exit(1)
100+
return
101+
}
102+
tf, err := os.Open(targetReportPath)
103+
if err != nil {
104+
fmt.Fprintf(stderr, "Unable to open the target report: %v\n", err)
105+
os.Exit(1)
106+
return
107+
}
108+
109+
latest, err := core.LoadPrincipalsReport(lf)
110+
if err != nil {
111+
fmt.Fprintf(stderr, "Unable to open the latest report: %v\n", err)
112+
os.Exit(1)
113+
return
114+
}
115+
target, err := core.LoadPrincipalsReport(tf)
116+
if err != nil {
117+
fmt.Fprintf(stderr, "Unable to open the target report: %v\n", err)
118+
os.Exit(1)
119+
return
120+
}
121+
122+
if verbose {
123+
fmt.Fprintf(stderr, "Target Analysis: %v, records: %v\nLatest Analysis: %v, records: %v\n", reportDateTime, len(latest), latest[0].AnalysisTime, len(target))
124+
}
125+
126+
// index on principal ARN for each ReportItem
127+
targetByARN := map[string]core.PrincipalsReportItem{}
128+
for _, ri := range target {
129+
targetByARN[ri.PrincipalARN] = ri
130+
}
131+
132+
// This loop marks the ARNs that it sees in the latest report
133+
// and subsequently verifies that the target report does not
134+
// contain records that have yet to be seen.
135+
seen := map[string]struct{}{}
136+
mark := struct{}{}
137+
diffs := []core.PrincipalsReportItemDifference{}
138+
for _, ri := range latest {
139+
seen[ri.PrincipalARN] = mark
140+
if ti, ok := targetByARN[ri.PrincipalARN]; !ok {
141+
diffs = append(diffs, ri.AddedDiff())
142+
} else if !ri.Equivallent(ti) {
143+
diffs = append(diffs, ri.Diff(ti))
144+
}
145+
}
146+
for _, ri := range target {
147+
if _, ok := seen[ri.PrincipalARN]; !ok {
148+
diffs = append(diffs, ri.DeletedDiff())
149+
}
150+
}
151+
views.WriteCSVTo(stdout, stderr, diffs)
152+
}

cmd/diff_resources.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
Copyright © 2022 The K9CLI Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package cmd contains all cobra commands
18+
package cmd
19+
20+
import (
21+
"fmt"
22+
"io"
23+
"os"
24+
"time"
25+
26+
"github.com/k9securityio/k9-cli/core"
27+
"github.com/k9securityio/k9-cli/views"
28+
"github.com/spf13/cobra"
29+
)
30+
31+
// diffResourcesCmd represents the resources subcommand of diff
32+
var diffResourcesCmd = &cobra.Command{
33+
Use: "resources",
34+
Short: `Calculate the difference between a resources snapshot and last scan`,
35+
Run: func(cmd *cobra.Command, args []string) {
36+
verbose, _ := cmd.Flags().GetBool(`verbose`)
37+
customerID, _ := cmd.Flags().GetString(`customer_id`)
38+
accountID, _ := cmd.Flags().GetString(`account`)
39+
analysisDate, _ := cmd.Flags().GetString(`analysis-date`)
40+
reportHome, _ := cmd.Flags().GetString(`report-home`)
41+
stdout := cmd.OutOrStdout()
42+
stderr := cmd.ErrOrStderr()
43+
DoDiffResources(stdout, stderr, reportHome, customerID, accountID, analysisDate, verbose)
44+
},
45+
}
46+
47+
// init defines and wires flags
48+
func init() {
49+
diffCmd.AddCommand(diffResourcesCmd)
50+
}
51+
52+
// DoDiffResources
53+
func DoDiffResources(stdout, stderr io.Writer, reportHome, customerID, accountID, analysisDate string, verbose bool) {
54+
// load the local report database
55+
db, err := core.LoadLocalDB(reportHome)
56+
if err != nil {
57+
fmt.Fprintf(stderr, "Unable to load local database, %v\n", err)
58+
}
59+
60+
// get the latest analysis
61+
var latestReportPath, targetReportPath string
62+
63+
if qr := db.GetPathForCustomerAccountLatestKind(
64+
customerID, accountID, core.REPORT_TYPE_PREFIX_RESOURCES); qr != nil {
65+
latestReportPath = *qr
66+
} else {
67+
fmt.Fprintf(stderr,
68+
"No such latest report: %v, %v, total records: %v\n",
69+
customerID, accountID, db.Size())
70+
os.Exit(1)
71+
return
72+
}
73+
74+
// get the target analysis
75+
// determine the file name for the desired report
76+
reportDateTime, err := time.Parse(core.FILENAME_TIMESTAMP_ANALYSIS_DATE_LAYOUT, analysisDate)
77+
if err != nil {
78+
fmt.Fprintf(stderr, "Invalid analysis-date: %v\n", analysisDate)
79+
os.Exit(1)
80+
return
81+
}
82+
if qr := db.GetPathForCustomerAccountTimeKind(
83+
customerID, accountID, reportDateTime,
84+
core.REPORT_TYPE_PREFIX_RESOURCES); qr != nil {
85+
targetReportPath = *qr
86+
} else {
87+
fmt.Fprintf(stderr,
88+
"No such target report: %v, %v, %v, total records: %v\n",
89+
customerID, accountID,
90+
reportDateTime.Format(core.FILENAME_TIMESTAMP_ANALYSIS_DATE_LAYOUT),
91+
db.Size())
92+
os.Exit(1)
93+
return
94+
}
95+
96+
// open and load the reports
97+
lf, err := os.Open(latestReportPath)
98+
if err != nil {
99+
fmt.Fprintf(stderr, "Unable to open the latest report: %v\n", err)
100+
os.Exit(1)
101+
return
102+
}
103+
tf, err := os.Open(targetReportPath)
104+
if err != nil {
105+
fmt.Fprintf(stderr, "Unable to open the target report: %v\n", err)
106+
os.Exit(1)
107+
return
108+
}
109+
110+
latest, err := core.LoadResourcesReport(lf)
111+
if err != nil {
112+
fmt.Fprintf(stderr, "Unable to open the latest report: %v\n", err)
113+
os.Exit(1)
114+
return
115+
}
116+
target, err := core.LoadResourcesReport(tf)
117+
if err != nil {
118+
fmt.Fprintf(stderr, "Unable to open the target report: %v\n", err)
119+
os.Exit(1)
120+
return
121+
}
122+
123+
if verbose {
124+
fmt.Fprintf(stderr, "Target Analysis: %v, records: %v\nLatest Analysis: %v, records: %v\n", reportDateTime, len(latest), latest[0].AnalysisTime, len(target))
125+
}
126+
127+
// index on principal ARN for each ReportItem
128+
targetByARN := map[string]core.ResourcesReportItem{}
129+
for _, ri := range target {
130+
targetByARN[ri.ResourceARN] = ri
131+
}
132+
133+
// This loop marks the ARNs that it sees in the latest report
134+
// and subsequently verifies that the target report does not
135+
// contain records that have yet to be seen.
136+
seen := map[string]struct{}{}
137+
mark := struct{}{}
138+
diffs := []core.ResourcesReportItemDifference{}
139+
for _, ri := range latest {
140+
seen[ri.ResourceARN] = mark
141+
if ti, ok := targetByARN[ri.ResourceARN]; !ok {
142+
diffs = append(diffs, ri.AddedDiff())
143+
} else if !ri.Equivallent(ti) {
144+
diffs = append(diffs, ri.Diff(ti))
145+
}
146+
}
147+
for _, ri := range target {
148+
if _, ok := seen[ri.ResourceARN]; !ok {
149+
diffs = append(diffs, ri.DeletedDiff())
150+
}
151+
}
152+
views.WriteCSVTo(stdout, stderr, diffs)
153+
}

0 commit comments

Comments
 (0)