-
Notifications
You must be signed in to change notification settings - Fork 504
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds support for scanning tfplan json file (#562)
* add support for scanning tfplan file * vendoring update for tfplan scanning * moving JQFilterWithQuery method to utils * add unit tests for jqhelper * add unit tests for tfplan iac provider * add method to validate tfplan json file, add more unit tests * fixing e2e tests for help
- Loading branch information
1 parent
bda153e
commit 2296d3a
Showing
16 changed files
with
649 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
Copyright (C) 2020 Accurics, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package iacprovider | ||
|
||
import ( | ||
"reflect" | ||
|
||
tfplanv1 "github.com/accurics/terrascan/pkg/iac-providers/tfplan/v1" | ||
) | ||
|
||
// tfplan specific constants | ||
const ( | ||
tfplan supportedIacType = "tfplan" | ||
tfplanV1 supportedIacVersion = "v1" | ||
tfplanDefaultIacVersion = tfplanV1 | ||
) | ||
|
||
// register tfplan as an IaC provider with terrascan | ||
func init() { | ||
// register iac provider | ||
RegisterIacProvider(tfplan, tfplanV1, tfplanDefaultIacVersion, reflect.TypeOf(tfplanv1.TFPlan{})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
Copyright (C) 2020 Accurics, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package tfplan | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/accurics/terrascan/pkg/iac-providers/output" | ||
) | ||
|
||
var ( | ||
errIacDirNotSupport = fmt.Errorf("tfplan should always be a file, not a directory. Please specify path to tfplan file with '-f' option") | ||
) | ||
|
||
// LoadIacDir is not supported for tfplan IacType. Terraform plan should always | ||
// be a file and not a directory | ||
func (k *TFPlan) LoadIacDir(absRootDir string) (output.AllResourceConfigs, error) { | ||
return output.AllResourceConfigs{}, errIacDirNotSupport | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
Copyright (C) 2020 Accurics, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package tfplan | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestLoadIacDir(t *testing.T) { | ||
|
||
t.Run("directory not supported", func(t *testing.T) { | ||
var ( | ||
dirPath = "some-path" | ||
tfplan = TFPlan{} | ||
wantErr = errIacDirNotSupport | ||
) | ||
_, err := tfplan.LoadIacDir(dirPath) | ||
if !reflect.DeepEqual(wantErr, err) { | ||
t.Errorf("error want: '%v', got: '%v'", wantErr, err) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
Copyright (C) 2020 Accurics, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package tfplan | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"strings" | ||
|
||
"github.com/accurics/terrascan/pkg/iac-providers/output" | ||
"github.com/accurics/terrascan/pkg/utils" | ||
"go.uber.org/zap" | ||
) | ||
|
||
const ( | ||
jqQuery = `[.planned_values.root_module | .. | select(.type? != null and .address? != null and .mode? == "managed") | {id: .address?, type: .type?, name: .name?, config: .values?, source: ""}]` | ||
tfPlanFormatVersion = "0.1" | ||
) | ||
|
||
var ( | ||
errIncorrectFormatVersion = fmt.Errorf("terraform format version shoule be '%s'", tfPlanFormatVersion) | ||
errEmptyTerraformVersion = fmt.Errorf("terraform version cannot be empty in tfplan json") | ||
) | ||
|
||
// LoadIacFile parses the given tfplan file from the given file path | ||
func (t *TFPlan) LoadIacFile(absFilePath string) (allResourcesConfig output.AllResourceConfigs, err error) { | ||
|
||
zap.S().Debug("processing tfplan file") | ||
|
||
// read tfplan json file | ||
tfjson, err := ioutil.ReadFile(absFilePath) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("failed to read tfplan JSON file. error: '%v'", err) | ||
zap.S().Debug(errMsg) | ||
return allResourcesConfig, fmt.Errorf(errMsg) | ||
} | ||
|
||
// validate if provide file is a valid tfplan file | ||
if err := t.isValidTFPlanJSON(tfjson); err != nil { | ||
return allResourcesConfig, fmt.Errorf("invalid terraform json file; error: '%v'", err) | ||
} | ||
|
||
// run jq query on tfplan json | ||
processed, err := utils.JQFilterWithQuery(jqQuery, tfjson) | ||
if err != nil { | ||
errMsg := fmt.Sprintf("failed to process tfplan JSON. error: '%v'", err) | ||
zap.S().Debug(errMsg) | ||
return allResourcesConfig, fmt.Errorf(errMsg) | ||
} | ||
|
||
// decode processed out into output.ResourceConfig | ||
var resourceConfigs []output.ResourceConfig | ||
if err := json.Unmarshal(processed, &resourceConfigs); err != nil { | ||
errMsg := fmt.Sprintf("failed to decode proceesed jq output. error: '%v'", err) | ||
zap.S().Debug(errMsg) | ||
return allResourcesConfig, fmt.Errorf(errMsg) | ||
} | ||
|
||
// create AllResourceConfigs from resourceConfigs | ||
allResourcesConfig = make(map[string][]output.ResourceConfig) | ||
for _, r := range resourceConfigs { | ||
r.ID = getTFID(r.ID) | ||
if _, present := allResourcesConfig[r.Type]; !present { | ||
allResourcesConfig[r.Type] = []output.ResourceConfig{r} | ||
} else { | ||
allResourcesConfig[r.Type] = append(allResourcesConfig[r.Type], r) | ||
} | ||
} | ||
|
||
// return output | ||
return allResourcesConfig, nil | ||
} | ||
|
||
// isValidTFPlanJSON validates whether the provided file is a valid tf json file | ||
func (t *TFPlan) isValidTFPlanJSON(tfjson []byte) error { | ||
|
||
// decode tfjson into map[string]interface{} | ||
if err := json.Unmarshal(tfjson, &t); err != nil { | ||
return fmt.Errorf("failed to decode tfplan json. error: '%v'", err) | ||
} | ||
|
||
// check format version | ||
if t.FormatVersion != tfPlanFormatVersion { | ||
return errIncorrectFormatVersion | ||
} | ||
|
||
// check terraform version | ||
if t.TerraformVersion == "" { | ||
return errEmptyTerraformVersion | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// getTFID returns a valid resource ID for terraform | ||
func getTFID(id string) string { | ||
split := strings.Split(id, ".") | ||
if len(split) <= 2 { | ||
return strings.Join(split, ".") | ||
} | ||
return strings.Join(split[len(split)-2:], ".") | ||
} |
Oops, something went wrong.