From 8379f898f4c3bf6bd2c13be61bcd288da7faeece Mon Sep 17 00:00:00 2001 From: Matthias Theuermann Date: Thu, 5 Dec 2024 17:50:11 +0100 Subject: [PATCH] fix: list exception group Signed-off-by: Matthias Theuermann --- .../resources/mondoo_exception/resource.tf | 4 +- internal/provider/exception_resource.go | 139 +++++++++++------- internal/provider/gql.go | 110 ++++++++------ 3 files changed, 152 insertions(+), 101 deletions(-) diff --git a/examples/resources/mondoo_exception/resource.tf b/examples/resources/mondoo_exception/resource.tf index 440b0ac..e2c6599 100644 --- a/examples/resources/mondoo_exception/resource.tf +++ b/examples/resources/mondoo_exception/resource.tf @@ -1,5 +1,5 @@ variable "spaceId" { - type = string + type = string } provider "mondoo" { @@ -19,7 +19,7 @@ locals { resource "mondoo_exception" "exception" { scope_mrn = "//assets.api.mondoo.app/spaces/${var.spaceId}/assets/${local.assetId}" - valid_until = "2024-12-12T09:33:46.206Z" + valid_until = "2024-12-11" # will be formatted to 2024-12-12T09:33:46.206Z justification = "testing" action ="SNOOZE" // check_mrns = ["//policy.api.mondoo.app/queries/mondoo-tls-security-no-weak-block-cipher-modes"] diff --git a/internal/provider/exception_resource.go b/internal/provider/exception_resource.go index d6a95e9..b45ea3b 100644 --- a/internal/provider/exception_resource.go +++ b/internal/provider/exception_resource.go @@ -126,6 +126,11 @@ func (r *exceptionResource) Create(ctx context.Context, req resource.CreateReque return } + scopeMrn := data.ScopeMrn.ValueString() + if scopeMrn == "" { + scopeMrn = r.client.space.MRN() + } + checks := []string{} data.CheckMrns.ElementsAs(ctx, &checks, false) @@ -141,22 +146,65 @@ func (r *exceptionResource) Create(ctx context.Context, req resource.CreateReque resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } - validUntilStr = time.Date(year, month, day, time.Now().Hour(), time.Now().Minute(), time.Now().Second(), time.Now().Nanosecond(), time.Now().Location()).Format(time.RFC3339) + now := time.Now().UTC() // Use UTC directly + validUntilStr = time.Date( + year, + time.Month(month), + day, + now.Hour(), + now.Minute(), + now.Second(), + now.Nanosecond(), + time.UTC, + ).Format(time.RFC3339Nano) // Use RFC3339Nano to include nanoseconds + } + + // Disable existing exceptions + checksToDisable := []string{} + for _, mrn := range checks { + exceptionGroups, err := r.client.ListExceptionGroups(ctx, scopeMrn, mrn, []string{}, []mondoov1.ExceptionMutationAction{}) + if err != nil { + resp.Diagnostics.AddError("Failed to list existing exceptions", err.Error()) + return + } + if len(exceptionGroups) > 0 { + for _, group := range exceptionGroups { + checksToDisable = append(checksToDisable, group.Exceptions[0].AssetCheckException.Mrn) + } + } + } + vulnerabilitiesToDisable := []string{} + for _, mrn := range vulnerabilities { + exceptionGroups, err := r.client.ListExceptionGroups(ctx, scopeMrn, mrn, []string{}, []mondoov1.ExceptionMutationAction{}) + if err != nil { + resp.Diagnostics.AddError("Failed to list existing exceptions", err.Error()) + return + } + if len(exceptionGroups) > 0 { + for _, group := range exceptionGroups { + vulnerabilitiesToDisable = append(vulnerabilitiesToDisable, group.Exceptions[0].AssetAdvisoryException.Mrn) + } + } + } + if len(checksToDisable) > 0 || len(vulnerabilitiesToDisable) > 0 { + + tflog.Debug((ctx), fmt.Sprintf("Disabling existing exceptions for scope %s", data.ScopeMrn.ValueString())) + err := r.client.ApplyException(ctx, scopeMrn, mondoov1.ExceptionMutationAction("ENABLE"), checksToDisable, []string{}, []string{}, vulnerabilitiesToDisable, (*string)(mondoov1.NewStringPtr("")), (*string)(mondoov1.NewStringPtr("")), (*bool)(mondoov1.NewBooleanPtr(false))) + if err != nil { + resp.Diagnostics.AddError("Failed to disable existing exceptions", err.Error()) + return + } } // Create API call logic - // mondoov1.ExceptionMutationAction(data.Action.ValueString()) tflog.Debug(ctx, fmt.Sprintf("Creating exception for scope %s", data.ScopeMrn.ValueString())) - err := r.client.ApplyException(ctx, data.ScopeMrn.ValueString(), mondoov1.ExceptionMutationAction(data.Action.ValueString()), checks, []string{}, []string{}, vulnerabilities, data.Justification.ValueStringPointer(), &validUntilStr, (*bool)(mondoov1.NewBooleanPtr(false))) - fmt.Println("====================================") - fmt.Println("Error:", err) - fmt.Println("====================================") + err := r.client.ApplyException(ctx, scopeMrn, mondoov1.ExceptionMutationAction(data.Action.ValueString()), checks, []string{}, []string{}, vulnerabilities, data.Justification.ValueStringPointer(), &validUntilStr, (*bool)(mondoov1.NewBooleanPtr(false))) if err != nil { resp.Diagnostics.AddError("Failed to create exception", err.Error()) return } - data.ScopeMrn = types.StringValue(data.ScopeMrn.ValueString()) + data.ScopeMrn = types.StringValue(scopeMrn) data.ValidUntil = types.StringValue(validUntilStr) // Save data into Terraform state @@ -189,25 +237,11 @@ func (r *exceptionResource) Update(ctx context.Context, req resource.UpdateReque return } - // Compute and validate the space - scope, err := r.client.ComputeSpace(data.ScopeMrn) - if err != nil { - resp.Diagnostics.AddError("Invalid Configuration", err.Error()) - return - } - ctx = tflog.SetField(ctx, "scope_mrn", scope.MRN()) - - checks := make([]string, 0) - checkMrns := data.CheckMrns.Elements() - for _, check := range checkMrns { - checks = append(checks, check.(types.String).ValueString()) - } + checks := []string{} + data.CheckMrns.ElementsAs(ctx, &checks, false) - vulnerabilities := make([]string, 0) - vulnerabilityMrns := data.VulnerabilityMrns.Elements() - for _, vulnerability := range vulnerabilityMrns { - vulnerabilities = append(vulnerabilities, vulnerability.(types.String).ValueString()) - } + vulnerabilities := []string{} + data.VulnerabilityMrns.ElementsAs(ctx, &vulnerabilities, false) // Format ValidUntil to RFC3339 if provided var validUntilStr string @@ -218,18 +252,35 @@ func (r *exceptionResource) Update(ctx context.Context, req resource.UpdateReque resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } - validUntilStr = time.Date(year, month, day, time.Now().Hour(), time.Now().Minute(), time.Now().Second(), time.Now().Nanosecond(), time.Now().Location()).Format(time.RFC3339) + now := time.Now().UTC() // Use UTC directly + validUntilStr = time.Date( + year, + time.Month(month), + day, + now.Hour(), + now.Minute(), + now.Second(), + now.Nanosecond(), + time.UTC, + ).Format(time.RFC3339Nano) // Use RFC3339Nano to include nanoseconds + } + + tflog.Debug((ctx), fmt.Sprintf("Disabling existing exceptions for scope %s", data.ScopeMrn.ValueString())) + err := r.client.ApplyException(ctx, data.ScopeMrn.ValueString(), mondoov1.ExceptionMutationAction("ENABLE"), checks, []string{}, []string{}, vulnerabilities, (*string)(mondoov1.NewStringPtr("")), (*string)(mondoov1.NewStringPtr("")), (*bool)(mondoov1.NewBooleanPtr(false))) + if err != nil { + resp.Diagnostics.AddError("Failed to disable existing exceptions", err.Error()) + return } - // Update API call logic - tflog.Debug(ctx, fmt.Sprintf("Updating exception for scope %s", scope.MRN())) - err = r.client.ApplyException(ctx, data.ScopeMrn.ValueString(), mondoov1.ExceptionMutationAction(data.Action.ValueString()), checks, []string{}, []string{}, vulnerabilities, data.Justification.ValueStringPointer(), &validUntilStr, (*bool)(mondoov1.NewBooleanPtr(false))) + // Create API call logic + tflog.Debug(ctx, fmt.Sprintf("Creating exception for scope %s", data.ScopeMrn.ValueString())) + err = r.client.ApplyException(ctx, data.ScopeMrn.String(), mondoov1.ExceptionMutationAction(data.Action.ValueString()), checks, []string{}, []string{}, vulnerabilities, data.Justification.ValueStringPointer(), &validUntilStr, (*bool)(mondoov1.NewBooleanPtr(false))) if err != nil { - resp.Diagnostics.AddError("Failed to update exception", err.Error()) + resp.Diagnostics.AddError("Failed to create exception", err.Error()) return } - data.ScopeMrn = types.StringValue(scope.MRN()) + data.ScopeMrn = types.StringValue(data.ScopeMrn.ValueString()) data.ValidUntil = types.StringValue(validUntilStr) // Save updated data into Terraform state @@ -246,29 +297,15 @@ func (r *exceptionResource) Delete(ctx context.Context, req resource.DeleteReque return } - // Compute and validate the space - scope, err := r.client.ComputeSpace(data.ScopeMrn) - if err != nil { - resp.Diagnostics.AddError("Invalid Configuration", err.Error()) - return - } - ctx = tflog.SetField(ctx, "scope_mrn", scope.MRN()) - - checks := make([]string, 0) - checkMrns := data.CheckMrns.Elements() - for _, check := range checkMrns { - checks = append(checks, check.(types.String).ValueString()) - } + checks := []string{} + data.CheckMrns.ElementsAs(ctx, &checks, false) - vulnerabilities := make([]string, 0) - vulnerabilityMrns := data.VulnerabilityMrns.Elements() - for _, vulnerability := range vulnerabilityMrns { - vulnerabilities = append(vulnerabilities, vulnerability.(types.String).ValueString()) - } + vulnerabilities := []string{} + data.VulnerabilityMrns.ElementsAs(ctx, &vulnerabilities, false) // Delete API call logic - tflog.Debug(ctx, fmt.Sprintf("Deleting exception for scope %s", scope.MRN())) - err = r.client.ApplyException(ctx, data.ScopeMrn.ValueString(), mondoov1.ExceptionMutationAction("ENABLE"), checks, []string{}, []string{}, vulnerabilities, data.Justification.ValueStringPointer(), (*string)(mondoov1.NewStringPtr("")), (*bool)(mondoov1.NewBooleanPtr(false))) + tflog.Debug(ctx, fmt.Sprintf("Deleting exception for scope %s", data.ScopeMrn.ValueString())) + err := r.client.ApplyException(ctx, data.ScopeMrn.ValueString(), mondoov1.ExceptionMutationAction("ENABLE"), checks, []string{}, []string{}, vulnerabilities, (*string)(mondoov1.NewStringPtr("")), (*string)(mondoov1.NewStringPtr("")), (*bool)(mondoov1.NewBooleanPtr(false))) if err != nil { resp.Diagnostics.AddError("Failed to delete exception", err.Error()) return diff --git a/internal/provider/gql.go b/internal/provider/gql.go index 7ea69cb..3d71e65 100644 --- a/internal/provider/gql.go +++ b/internal/provider/gql.go @@ -962,16 +962,17 @@ func (c *ExtendedGqlClient) ApplyException( // Prepare input fields input := mondoov1.ExceptionMutationInput{ - ScopeMrn: mondoov1.String(scopeMrn), - Action: action, - QueryMrns: convertToGraphQLList(checkMrns), - // ControlMrns: convertToGraphQLList(controlMrns), - // CveMrns: convertToGraphQLList(cveMrns), + ScopeMrn: mondoov1.String(scopeMrn), + Action: action, + QueryMrns: convertToGraphQLList(checkMrns), + ControlMrns: convertToGraphQLList(controlMrns), + CveMrns: convertToGraphQLList(cveMrns), AdvisoryMrns: convertToGraphQLList(vulnerabilityMrns), Justification: (*mondoov1.String)(justification), - // ValidUntil: (*mondoov1.String)(validUntil), - ApplyToCves: mondoov1.NewBooleanPtr(mondoov1.Boolean(*applyToCves)), + ValidUntil: (*mondoov1.String)(validUntil), + ApplyToCves: mondoov1.NewBooleanPtr(mondoov1.Boolean(*applyToCves)), } + fmt.Println("====================================") fmt.Println("Scope:", input.ScopeMrn) fmt.Println("Action:", input.Action) @@ -979,57 +980,77 @@ func (c *ExtendedGqlClient) ApplyException( // fmt.Println("ControlMrns:", *input.ControlMrns) // fmt.Println("CveMrns:", *input.CveMrns) // fmt.Println("VulnerabilityMrns:", *input.AdvisoryMrns) - // fmt.Println("Justification:", *input.Justification) - // fmt.Println("ValidUntil:", *input.ValidUntil) + fmt.Println("Justification:", *input.Justification) + fmt.Println("ValidUntil:", *input.ValidUntil) fmt.Println("ApplyToCves:", *input.ApplyToCves) fmt.Println("====================================") return c.Mutate(ctx, &applyException, input, nil) } -// ListExceptionGroupsInput defines the input for the ListExceptionGroups query -type ListExceptionGroupsInput struct { - ScopeMrn string `json:"scopeMrn"` - Types []string `json:"types"` -} - // Reviewer represents the reviewer of the exception group type Reviewer struct { Email string `json:"email"` Name string `json:"name"` } -type ExceptionGroup struct { - Action string `json:"action"` - CreatedAt string `json:"createdAt"` - ID string `json:"id"` - Justification string `json:"justification"` - ModifiedAt string `json:"modifiedAt"` - ReviewStatus string `json:"reviewStatus"` - ScopeMrn string `json:"scopeMrn"` - Title string `json:"title"` - Author Author `json:"author"` - Reviewer Reviewer `json:"reviewer"` +// AssetAdvisoryException represents exceptions related to asset advisories +type AssetAdvisoryException struct { + Mrn string `graphql:"mrn"` } -// ListExceptionGroupsResponse represents the GraphQL response structure -type ListExceptionGroupsResponse struct { - ExceptionGroups []ExceptionGroup `json:"exceptionGroups"` +// AssetCheckException represents exceptions related to asset checks +type AssetCheckException struct { + Mrn string `graphql:"mrn"` } -// ExtendedGqlClient represents a client to communicate with the GraphQL API +// AssetCveException represents exceptions related to CVEs +type AssetCveException struct { + Mrn string `graphql:"mrn"` +} + +// SpaceCheckException represents exceptions related to space checks +type SpaceCheckException struct { + Mrn string `graphql:"mrn"` +} + +// Exception is a union of all exception types +type Exception struct { + AssetAdvisoryException *AssetAdvisoryException `graphql:"... on AssetAdvisoryException"` + AssetCheckException *AssetCheckException `graphql:"... on AssetCheckException"` + AssetCveException *AssetCveException `graphql:"... on AssetCveException"` + SpaceCheckException *SpaceCheckException `graphql:"... on SpaceCheckException"` +} +// ExceptionGroup represents a group of exceptions +type ExceptionGroup struct { + ID string `graphql:"id"` + ScopeMrn string `graphql:"scopeMrn"` + Title string `graphql:"title"` + Justification string `graphql:"justification"` + ReviewStatus string `graphql:"reviewStatus"` + Action string `graphql:"action"` + CreatedAt string `graphql:"createdAt"` + ModifiedAt string `graphql:"modifiedAt"` + Author Author `graphql:"author"` + Reviewer Reviewer `graphql:"reviewer"` + Exceptions []Exception `graphql:"exceptions"` +} + +// ListExceptionGroups retrieves a list of exception groups func (c *ExtendedGqlClient) ListExceptionGroups( ctx context.Context, scopeMrn string, + mrn string, types []string, + actions []mondoov1.ExceptionMutationAction, ) ([]ExceptionGroup, error) { // Struct to hold the query response var listExceptionGroups struct { ExceptionGroups []ExceptionGroup `graphql:"exceptionGroups(input: $input)"` } - // Helper function to convert string slices to *[]mondoov1.String + // Helper function to convert string slices to *[]mondoov1.ExceptionType convertToGraphQLList := func(values []string) *[]mondoov1.ExceptionType { if len(values) == 0 { return nil @@ -1041,10 +1062,20 @@ func (c *ExtendedGqlClient) ListExceptionGroups( return &entries } + // Safely construct input + var mrnValue *mondoov1.String + if mrn != "" { + mrnValue = mondoov1.NewStringPtr(mondoov1.String(mrn)) + } + + fmt.Println("mrnValue", mrn) + // Prepare input for the query input := mondoov1.ExceptionGroupsInput{ ScopeMrn: mondoov1.String(scopeMrn), + Mrn: mrnValue, Types: convertToGraphQLList(types), + Actions: &actions, } variables := map[string]interface{}{ "input": input, @@ -1057,20 +1088,3 @@ func (c *ExtendedGqlClient) ListExceptionGroups( return listExceptionGroups.ExceptionGroups, nil } - -// ListExceptionGroup -// func (c *ExtendedGqlClient) ListExceptionGroup(ctx context.Context, scopeMrn string) ([]mondoov1.ExceptionGroupsInput, error) { -// var q struct { -// ExceptionGroups []mondoov1.ExceptionGroupsInput `graphql:"exceptionGroups(scopeMrn: $scopeMrn)"` -// } -// variables := map[string]interface{}{ -// "scopeMrn": mondoov1.String(scopeMrn), -// } - -// err := c.Query(ctx, &q, variables) -// if err != nil { -// return nil, err -// } - -// return q.ExceptionGroups, nil -// }