|
19 | 19 | package cmd |
20 | 20 |
|
21 | 21 | import ( |
| 22 | + "github.com/fatih/color" |
| 23 | + |
| 24 | + "github.com/aws/aws-sdk-go-v2/aws/arn" |
| 25 | + "github.com/lacework/go-sdk/api" |
22 | 26 | "github.com/spf13/cobra" |
| 27 | + "golang.org/x/exp/slices" |
23 | 28 | ) |
24 | 29 |
|
25 | 30 | var ( |
@@ -63,4 +68,192 @@ func init() { |
63 | 68 | // gcp |
64 | 69 | suppressionsCommand.AddCommand(suppressionsGcpCmd) |
65 | 70 | 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 |
66 | 259 | } |
0 commit comments