Skip to content

Commit 7b5334b

Browse files
feat(cli): add hidden suppressions migrate gcp command (#1120)
* feat: Initial add of GCP suppression migration command Signed-off-by: Ross <[email protected]> * chore: fix issues with gcp migrate command Signed-off-by: Ross <[email protected]> * chore: Add aws-sdk-go-v2 ARN class to address AWS migration issue Signed-off-by: Ross <[email protected]> * chore: Update suppression migration commands and add tests Signed-off-by: Ross <[email protected]> * chore: Update unit test Signed-off-by: Ross <[email protected]> * chore: Comment convertResourceNamesSupConditions functionality Signed-off-by: Ross <[email protected]> * Apply suggestions from code review Co-authored-by: Darren <[email protected]> * chore: Add comments for discared AWS suppressions too Signed-off-by: Ross <[email protected]> * Update cli/cmd/suppressions_gcp.go Co-authored-by: Darren <[email protected]> Signed-off-by: Ross <[email protected]> Co-authored-by: Darren <[email protected]>
1 parent b742eb7 commit 7b5334b

File tree

7 files changed

+2010
-138
lines changed

7 files changed

+2010
-138
lines changed

cli/cmd/suppressions.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@
1919
package cmd
2020

2121
import (
22+
"github.com/fatih/color"
23+
24+
"github.com/aws/aws-sdk-go-v2/aws/arn"
25+
"github.com/lacework/go-sdk/api"
2226
"github.com/spf13/cobra"
27+
"golang.org/x/exp/slices"
2328
)
2429

2530
var (
@@ -63,4 +68,192 @@ func init() {
6368
// gcp
6469
suppressionsCommand.AddCommand(suppressionsGcpCmd)
6570
suppressionsGcpCmd.AddCommand(suppressionsListGcpCmd)
71+
suppressionsGcpCmd.AddCommand(suppressionsMigrateGcpCmd)
72+
}
73+
74+
func autoConvertSuppressions(convertedPolicyExceptions []map[string]api.PolicyException) {
75+
cli.StartProgress("Creating policy exceptions...")
76+
for _, exceptionMap := range convertedPolicyExceptions {
77+
for policyId, exception := range exceptionMap {
78+
response, err := cli.LwApi.V2.Policy.Exceptions.Create(policyId, exception)
79+
if err != nil {
80+
cli.Log.Debug(err, "unable to create exception")
81+
cli.OutputHuman(color.RedString(
82+
"Error creating policy exception to create exception. %s"),
83+
err)
84+
continue
85+
}
86+
cli.OutputHuman("Exception created for PolicyId: %s - ExceptionId: %s\n\n",
87+
color.GreenString(policyId), color.BlueString(response.Data.ExceptionID))
88+
}
89+
}
90+
91+
cli.StopProgress()
92+
}
93+
94+
func printPayloadsText(payloadsText []string) {
95+
if len(payloadsText) >= 1 {
96+
cli.OutputHuman(color.YellowString("#### Legacy Suppressions --> Exceptions payloads\n\n"))
97+
for _, payload := range payloadsText {
98+
cli.OutputHuman(color.GreenString("%s \n\n", payload))
99+
}
100+
} else {
101+
cli.OutputHuman("No legacy suppressions found that could be migrated\n")
102+
}
103+
}
104+
105+
func printConvertedSuppressions(convertedSuppressions []map[string]api.PolicyException) {
106+
if len(convertedSuppressions) >= 1 {
107+
cli.OutputHuman(color.YellowString("#### Converted legacy suppressions in Policy Exception" +
108+
" format" +
109+
"\n"))
110+
for _, exception := range convertedSuppressions {
111+
err := cli.OutputJSON(exception)
112+
if err != nil {
113+
return
114+
}
115+
}
116+
colorizeR := color.New(color.FgRed, color.Bold)
117+
cli.OutputHuman(colorizeR.Sprintf("WARNING: Before continuing, " +
118+
"please thoroughly inspect the above exceptions to ensure they are valid and" +
119+
" required. By continuing, you accept liability for any compliance violations" +
120+
" missed as a result of the above exceptions!\n\n"))
121+
122+
}
123+
}
124+
125+
func printDiscardedSuppressions(discardedSuppressions []map[string]api.SuppressionV2) {
126+
if len(discardedSuppressions) >= 1 {
127+
cli.OutputHuman(color.YellowString("#### Discarded legacy suppressions\n"))
128+
for _, suppression := range discardedSuppressions {
129+
err := cli.OutputJSON(suppression)
130+
if err != nil {
131+
return
132+
}
133+
}
134+
}
135+
}
136+
137+
func convertSupCondition(supConditions []string, fieldKey string,
138+
policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint {
139+
if len(supConditions) >= 1 && slices.Contains(
140+
policyIdExceptionsTemplate, fieldKey) {
141+
142+
var condition []any
143+
// verify for aws:
144+
// if "ALL_ACCOUNTS" OR "ALL_REGIONS" is in the suppression condition slice
145+
// verify for gcp:
146+
// if "ALL_ORGANIZATIONS" OR "ALL_PROJECTS" is in the suppression condition slice
147+
// if so we should ignore the supplied conditions and replace with a wildcard *
148+
if (slices.Contains(supConditions, "ALL_ACCOUNTS") && fieldKey == "accountIds") ||
149+
(slices.Contains(supConditions, "ALL_REGIONS") && fieldKey == "regionNames") {
150+
condition = append(condition, "*")
151+
} else if (slices.Contains(supConditions, "ALL_ORGANIZATIONS") && fieldKey == "organizations") ||
152+
(slices.Contains(supConditions, "ALL_PROJECTS") && fieldKey == "projects") {
153+
condition = append(condition, "*")
154+
} else if fieldKey == "resourceNames" {
155+
condition = convertResourceNamesSupConditions(supConditions)
156+
} else if fieldKey == "resourceName" {
157+
// resourceName singular is specific to GCP
158+
condition = convertGcpResourceNameSupConditions(supConditions)
159+
} else {
160+
condition = convertToAnySlice(supConditions)
161+
}
162+
163+
return api.PolicyExceptionConstraint{
164+
FieldKey: fieldKey,
165+
FieldValues: condition,
166+
}
167+
}
168+
return api.PolicyExceptionConstraint{}
169+
}
170+
171+
func convertResourceNamesSupConditions(supConditions []string) []any {
172+
var conditions []any
173+
for _, condition := range supConditions {
174+
ok := arn.IsARN(condition)
175+
// If the legacy suppression resourceNames field contains an ARN, we should parse and pull
176+
// out the resource name. ARNs are not supported in policy exceptions
177+
if ok {
178+
parsedEntry, _ := arn.Parse(condition)
179+
condition = parsedEntry.Resource
180+
}
181+
conditions = append(conditions, condition)
182+
}
183+
return conditions
184+
}
185+
186+
func convertGcpResourceNameSupConditions(supConditions []string) []any {
187+
var conditions []any
188+
for _, condition := range supConditions {
189+
// skip this logic if we already have a wildcard
190+
if condition != "*" {
191+
// It appears that for GCP, the resourceName field for policy exceptions is in fact expecting
192+
// users to provider the full GCP resource_id.
193+
// Example resourceId: //compute.googleapis.com/projects/gke-project-01-c8403ba1/zones/us-central1-a/instances/squid-proxy
194+
// This was not the case for legacy suppressions and in most cases it's unlikely that the
195+
// users will have provided this. Instead, we are more likely to have
196+
// the resource name provided. To cover this scenario we prepend the resource name
197+
// from the legacy suppression with "*/" to make it match the resource name while
198+
// wildcarding the rest of the resourceId
199+
condition = "*/" + condition
200+
}
201+
conditions = append(conditions, condition)
202+
}
203+
return conditions
204+
}
205+
206+
func convertSupConditionTags(supCondition []map[string]string, fieldKey string,
207+
policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint {
208+
if len(supCondition) >= 1 && slices.Contains(
209+
policyIdExceptionsTemplate, fieldKey) {
210+
211+
// api.PolicyExceptionConstraint expects []any for the FieldValues
212+
// Therefore we need to take the supCondition []map[string]string and append each map to
213+
// the new convertedTags []any var
214+
var convertedTags []any
215+
for _, tagMap := range supCondition {
216+
convertedTags = append(convertedTags, tagMap)
217+
}
218+
219+
return api.PolicyExceptionConstraint{
220+
FieldKey: fieldKey,
221+
FieldValues: convertedTags,
222+
}
223+
}
224+
return api.PolicyExceptionConstraint{}
225+
}
226+
227+
func getPoliciesExceptionConstraintsMap() map[string][]string {
228+
// get a list of all policies and parse the valid exception constraints and return a map of
229+
// {"<policyId>": [<validPolicyConstraints>]}
230+
policies, err := cli.LwApi.V2.Policy.List()
231+
if err != nil {
232+
return nil
233+
}
234+
235+
policiesSupportedConstraints := make(map[string][]string)
236+
for _, policy := range policies.Data {
237+
exceptionConstraints := getPolicyExceptionConstraintsSlice(policy.ExceptionConfiguration)
238+
policiesSupportedConstraints[policy.PolicyID] = exceptionConstraints
239+
}
240+
241+
return policiesSupportedConstraints
242+
}
243+
244+
func convertToAnySlice(slice []string) []any {
245+
s := make([]interface{}, len(slice))
246+
for i, v := range slice {
247+
s[i] = v
248+
}
249+
return s
250+
}
251+
252+
func updateDiscardedSupConditionsComments(suppressionInfo api.SuppressionV2, comment string) api.SuppressionV2 {
253+
var updatedSupInfo api.SuppressionV2
254+
for _, suppression := range suppressionInfo.SuppressionConditions {
255+
suppression.Comment = comment
256+
updatedSupInfo.SuppressionConditions = append(updatedSupInfo.SuppressionConditions, suppression)
257+
}
258+
return updatedSupInfo
66259
}

cli/cmd/suppressions_aws.go

Lines changed: 17 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
"github.com/lacework/go-sdk/api"
3030
"github.com/pkg/errors"
3131
"github.com/spf13/cobra"
32-
"golang.org/x/exp/slices"
3332
)
3433

3534
var (
@@ -297,7 +296,7 @@ func suppressionsAwsMigrate(_ *cobra.Command, _ []string) error {
297296
return err
298297
}
299298
if confirm {
300-
autoConvertAwsSuppressions(convertedPolicyExceptions)
299+
autoConvertSuppressions(convertedPolicyExceptions)
301300
printDiscardedSuppressions(discardedSuppressions)
302301
cli.OutputHuman(color.GreenString("To view the newly created Exceptions, " +
303302
"try running `lacework policy-exceptions list <policyId>"))
@@ -309,26 +308,6 @@ func suppressionsAwsMigrate(_ *cobra.Command, _ []string) error {
309308
return nil
310309
}
311310

312-
func autoConvertAwsSuppressions(convertedPolicyExceptions []map[string]api.PolicyException) {
313-
cli.StartProgress("Creating policy exceptions ...")
314-
for _, exceptionMap := range convertedPolicyExceptions {
315-
for policyId, exception := range exceptionMap {
316-
response, err := cli.LwApi.V2.Policy.Exceptions.Create(policyId, exception)
317-
if err != nil {
318-
cli.Log.Debug(err, "unable to create exception")
319-
cli.OutputHuman(color.RedString(
320-
"Error creating policy exception to create exception. %e"),
321-
err)
322-
continue
323-
}
324-
cli.OutputHuman("Exception created for PolicyId: %s - ExceptionId: %s\n\n",
325-
color.GreenString(policyId), color.BlueString(response.Data.ExceptionID))
326-
}
327-
}
328-
329-
cli.StopProgress()
330-
}
331-
332311
func convertAwsSuppressions(
333312
suppressionsMap map[string]api.SuppressionV2,
334313
policyExceptionsConstraintsMap map[string][]string,
@@ -348,6 +327,8 @@ func convertAwsSuppressions(
348327
if !ok {
349328
// when we don't have a mapped policy, add the legacy suppression info
350329
if suppressionInfo.SuppressionConditions != nil {
330+
suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo,
331+
"Legacy suppression discarded as there is no equivalent policy")
351332
discardedSuppressions = append(
352333
discardedSuppressions,
353334
map[string]api.SuppressionV2{id: suppressionInfo},
@@ -362,6 +343,20 @@ func convertAwsSuppressions(
362343
// We then parse this into a list of constraints
363344
policyIdExceptionsTemplate := policyExceptionsConstraintsMap[mappedPolicyId]
364345
if policyIdExceptionsTemplate == nil {
346+
// Updating the suppression conditions comments to make it clear why these were
347+
// discarded
348+
if len(suppressionInfo.SuppressionConditions) >= 1 {
349+
suppressionInfo = updateDiscardedSupConditionsComments(suppressionInfo,
350+
fmt.Sprintf("Legacy suppression discarded as the new policy: %s does not"+
351+
" support exception conditions", mappedPolicyId))
352+
353+
// if the list of supported constraints is empty for a policy,
354+
// we should let the customers know that we have discarded this suppression
355+
discardedSuppressions = append(
356+
discardedSuppressions,
357+
map[string]api.SuppressionV2{id: suppressionInfo},
358+
)
359+
}
365360
continue
366361
}
367362
if len(suppressionInfo.SuppressionConditions) >= 1 {
@@ -440,115 +435,3 @@ func convertAwsSuppressions(
440435

441436
return convertedPolicyExceptions, payloadsText, discardedSuppressions
442437
}
443-
444-
func printPayloadsText(payloadsText []string) {
445-
if len(payloadsText) >= 1 {
446-
cli.OutputHuman(color.YellowString("#### Legacy Suppressions --> Exceptions payloads\n\n"))
447-
for _, payload := range payloadsText {
448-
cli.OutputHuman(color.GreenString("%s \n\n", payload))
449-
}
450-
} else {
451-
cli.OutputHuman("No legacy suppressions found that could be migrated\n")
452-
}
453-
}
454-
455-
func printConvertedSuppressions(convertedSuppressions []map[string]api.PolicyException) {
456-
if len(convertedSuppressions) >= 1 {
457-
cli.OutputHuman(color.YellowString("#### Converted legacy suppressions in Policy Exception" +
458-
" format" +
459-
"\n"))
460-
for _, exception := range convertedSuppressions {
461-
err := cli.OutputJSON(exception)
462-
if err != nil {
463-
return
464-
}
465-
}
466-
colorizeR := color.New(color.FgRed, color.Bold)
467-
cli.OutputHuman(colorizeR.Sprintf("WARNING: Before continuing, " +
468-
"please thoroughly inspect the above exceptions to ensure they are valid and" +
469-
" required. By continuing, you accept liability for any compliance violations" +
470-
" missed as a result of the above exceptions!\n\n"))
471-
472-
}
473-
}
474-
475-
func printDiscardedSuppressions(discardedSuppressions []map[string]api.SuppressionV2) {
476-
if len(discardedSuppressions) >= 1 {
477-
cli.OutputHuman(color.YellowString("#### Discarded legacy suppressions\n"))
478-
for _, suppression := range discardedSuppressions {
479-
err := cli.OutputJSON(suppression)
480-
if err != nil {
481-
return
482-
}
483-
}
484-
}
485-
}
486-
487-
func convertSupCondition(supCondition []string, fieldKey string,
488-
policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint {
489-
if len(supCondition) >= 1 && slices.Contains(
490-
policyIdExceptionsTemplate, fieldKey) {
491-
492-
var condition []any
493-
// verify if "ALL_ACCOUNTS" OR "ALL_REGIONS" is in the suppression condition slice
494-
// if so we should ignore the supplied conditions and replace with a wildcard *
495-
if (slices.Contains(supCondition, "ALL_ACCOUNTS") && fieldKey == "accountIds") ||
496-
(slices.Contains(supCondition, "ALL_REGIONS") && fieldKey == "regionNames") {
497-
condition = append(condition, "*")
498-
} else {
499-
condition = convertToAnySlice(supCondition)
500-
}
501-
502-
return api.PolicyExceptionConstraint{
503-
FieldKey: fieldKey,
504-
FieldValues: condition,
505-
}
506-
}
507-
return api.PolicyExceptionConstraint{}
508-
}
509-
510-
func convertSupConditionTags(supCondition []map[string]string, fieldKey string,
511-
policyIdExceptionsTemplate []string) api.PolicyExceptionConstraint {
512-
if len(supCondition) >= 1 && slices.Contains(
513-
policyIdExceptionsTemplate, fieldKey) {
514-
515-
// api.PolicyExceptionConstraint expects []any for the FieldValues
516-
// Therefore we need to take the supCondition []map[string]string and append each map to
517-
// the new convertedTags []any var
518-
var convertedTags []any
519-
for _, tagMap := range supCondition {
520-
convertedTags = append(convertedTags, tagMap)
521-
}
522-
523-
return api.PolicyExceptionConstraint{
524-
FieldKey: fieldKey,
525-
FieldValues: convertedTags,
526-
}
527-
}
528-
return api.PolicyExceptionConstraint{}
529-
}
530-
531-
func getPoliciesExceptionConstraintsMap() map[string][]string {
532-
// get a list of all policies and parse the valid exception constraints and return a map of
533-
// {"<policyId>": [<validPolicyConstraints>]}
534-
policies, err := cli.LwApi.V2.Policy.List()
535-
if err != nil {
536-
return nil
537-
}
538-
539-
policiesSupportedConstraints := make(map[string][]string)
540-
for _, policy := range policies.Data {
541-
exceptionConstraints := getPolicyExceptionConstraintsSlice(policy.ExceptionConfiguration)
542-
policiesSupportedConstraints[policy.PolicyID] = exceptionConstraints
543-
}
544-
545-
return policiesSupportedConstraints
546-
}
547-
548-
func convertToAnySlice(slice []string) []any {
549-
s := make([]interface{}, len(slice))
550-
for i, v := range slice {
551-
s[i] = v
552-
}
553-
return s
554-
}

0 commit comments

Comments
 (0)