130 lines
5.0 KiB
Python
130 lines
5.0 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2024 Google LLC. All Rights Reserved.
|
|
#
|
|
# 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.
|
|
"""Command for remediating a Cloud Security Command Center Finding."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
from __future__ import unicode_literals
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from googlecloudsdk.api_lib.scc.iac_remediation import findings
|
|
from googlecloudsdk.api_lib.scc.iac_remediation import git
|
|
from googlecloudsdk.api_lib.scc.iac_remediation import llm
|
|
from googlecloudsdk.api_lib.scc.iac_remediation import prompt
|
|
from googlecloudsdk.api_lib.scc.iac_remediation import pull_requests
|
|
from googlecloudsdk.api_lib.scc.iac_remediation import terraform
|
|
from googlecloudsdk.calliope import base
|
|
from googlecloudsdk.command_lib.scc.iac_remediation import errors
|
|
from googlecloudsdk.command_lib.scc.iac_remediation import flags
|
|
from googlecloudsdk.core import log
|
|
|
|
|
|
@base.Hidden
|
|
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.GA)
|
|
@base.UniverseCompatible
|
|
class Create(base.CreateCommand):
|
|
"""Remediates a Security Command Center finding."""
|
|
|
|
detailed_help = {
|
|
"DESCRIPTION": "Remediates a Security Command Center finding.",
|
|
"EXAMPLES": """
|
|
Sample usage:
|
|
|
|
$ {{command}} scc iac-remediation create --finding-org-id=123456789
|
|
--finding-name=projects/123456789/sources/123456789/locations/global/findings/123456789
|
|
--tfstate-file-paths=/path/to/file1.tfstate,/path/to/file2.tfstate --project-id=my-proj
|
|
--git-config-path=/path/to/config.yaml""",
|
|
}
|
|
|
|
@staticmethod
|
|
def Args(parser):
|
|
flags.FINDING_ORG_ID_FLAG.AddToParser(parser)
|
|
flags.FINDING_NAME_FLAG.AddToParser(parser)
|
|
flags.LLM_PROJ_ID_FLAG.AddToParser(parser)
|
|
flags.TFSTATE_FILE_PATHS_LIST_FLAG.AddToParser(parser)
|
|
flags.GIT_CONFIG_FILE_PATH_FLAG.AddToParser(parser)
|
|
|
|
def Run(self, args: Any) -> None:
|
|
"""Remediates a Security Command Center finding.
|
|
|
|
Args:
|
|
args: Arguments for the command.
|
|
"""
|
|
git_flag, repo_root_dir = git.is_git_repo()
|
|
if not git_flag: # Throw error if command is invoked from a non-git repo
|
|
raise errors.GitRepoNotFoundError()
|
|
git.validate_git_config(args.git_config_path)
|
|
git_config_data = args.git_config_path
|
|
log.Print(git_config_data)
|
|
log.Print("repo_root_dir: ", repo_root_dir)
|
|
resp = findings.MakeApiCall(args.finding_org_id, args.finding_name)
|
|
json_resp = json.loads(resp)
|
|
iam_bindings = findings.FetchIAMBinding(json_resp)
|
|
resource_name = findings.FetchResourceName(json_resp)
|
|
tfstate_json_list = terraform.fetch_tfstate_list(
|
|
args.tfstate_file_paths, repo_root_dir
|
|
)
|
|
if not tfstate_json_list:
|
|
log.Print("No TFState files found.")
|
|
return
|
|
tfstate_information = terraform.get_tfstate_information_per_member(
|
|
iam_bindings, tfstate_json_list, resource_name
|
|
)
|
|
if not tfstate_information:
|
|
for tfstate_json in tfstate_json_list:
|
|
if "google_project_iam_policy" in tfstate_json:
|
|
tfstate_information = "google_project_iam_policy"
|
|
tf_files = terraform.find_tf_files(repo_root_dir)
|
|
original_files_content = terraform.read_original_files_content(tf_files)
|
|
for member, role_data in iam_bindings.items():
|
|
tfstate_data = ""
|
|
if tfstate_information and member in tfstate_information:
|
|
tfstate_data = tfstate_information[member]
|
|
input_prompt = prompt.fetch_input_prompt(
|
|
tfstate_data,
|
|
role_data,
|
|
resource_name,
|
|
tf_files,
|
|
member,
|
|
)
|
|
response = llm.MakeLLMCall(input_prompt, args.project_id)
|
|
response_dict = prompt.llm_response_parser(response)
|
|
check, response = terraform.validate_tf_files(response_dict)
|
|
if not check:
|
|
terraform.update_tf_files(original_files_content)
|
|
raise errors.InvalidLLMResponseError(response)
|
|
else:
|
|
log.Print(response)
|
|
git.push_commit(
|
|
response_dict,
|
|
pull_requests.CreateCommitMessage(resp, member),
|
|
git_config_data["remote"],
|
|
git_config_data["branch-prefix"]
|
|
+ findings.ParseName(args.finding_name),
|
|
)
|
|
terraform.update_tf_files(response_dict)
|
|
git.raise_pr(
|
|
pull_requests.CreatePRMessage(resp),
|
|
pull_requests.CreatePRMessage(resp),
|
|
git_config_data["remote"],
|
|
git_config_data["branch-prefix"]
|
|
+ findings.ParseName(args.finding_name),
|
|
git_config_data["main-branch-name"],
|
|
)
|
|
terraform.update_tf_files(original_files_content)
|