mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
feat(gcp): add service account impersonation (#4291)
This commit is contained in:
@@ -25,6 +25,16 @@ Prowler will follow the same credentials search as [Google authentication librar
|
||||
|
||||
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the `Viewer` role to the member associated with the credentials.
|
||||
|
||||
## Impersonate Service Account
|
||||
|
||||
If you want to impersonate a GCP service account, you can use the `--impersonate-service-account` argument:
|
||||
|
||||
```console
|
||||
prowler gcp --impersonate-service-account <service-account-email>
|
||||
```
|
||||
|
||||
This argument will use the default credentials to impersonate the service account provided.
|
||||
|
||||
# GCP Service APIs
|
||||
|
||||
Prowler will use the Google Cloud APIs to get the information needed to perform the checks. Make sure that the following APIs are enabled in the project:
|
||||
|
||||
Generated
+3
-3
@@ -1606,13 +1606,13 @@ uritemplate = ">=3.0.1,<5"
|
||||
|
||||
[[package]]
|
||||
name = "google-auth"
|
||||
version = "2.29.0"
|
||||
version = "2.30.0"
|
||||
description = "Google Authentication Library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "google-auth-2.29.0.tar.gz", hash = "sha256:672dff332d073227550ffc7457868ac4218d6c500b155fe6cc17d2b13602c360"},
|
||||
{file = "google_auth-2.29.0-py2.py3-none-any.whl", hash = "sha256:d452ad095688cd52bae0ad6fafe027f6a6d6f560e810fec20914e17a09526415"},
|
||||
{file = "google-auth-2.30.0.tar.gz", hash = "sha256:ab630a1320f6720909ad76a7dbdb6841cdf5c66b328d690027e4867bdfb16688"},
|
||||
{file = "google_auth-2.30.0-py2.py3-none-any.whl", hash = "sha256:8df7da660f62757388b8a7f249df13549b3373f24388cb5d2f1dd91cc18180b5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
||||
@@ -3,7 +3,8 @@ import re
|
||||
import sys
|
||||
|
||||
from colorama import Fore, Style
|
||||
from google import auth
|
||||
from google.auth import default, impersonated_credentials
|
||||
from google.auth.transport.requests import Request
|
||||
from google.oauth2.credentials import Credentials
|
||||
from googleapiclient import discovery
|
||||
from googleapiclient.errors import HttpError
|
||||
@@ -38,9 +39,12 @@ class GcpProvider(Provider):
|
||||
input_project_ids = arguments.project_id
|
||||
excluded_project_ids = arguments.excluded_project_id
|
||||
credentials_file = arguments.credentials_file
|
||||
self._impersonated_service_account = arguments.impersonate_service_account
|
||||
list_project_ids = arguments.list_project_id
|
||||
|
||||
self._session = self.setup_session(credentials_file)
|
||||
self._session = self.setup_session(
|
||||
credentials_file, self._impersonated_service_account
|
||||
)
|
||||
|
||||
self._project_ids = []
|
||||
self._projects = {}
|
||||
@@ -120,6 +124,10 @@ class GcpProvider(Provider):
|
||||
def projects(self):
|
||||
return self._projects
|
||||
|
||||
@property
|
||||
def impersonated_service_account(self):
|
||||
return self._impersonated_service_account
|
||||
|
||||
@property
|
||||
def project_ids(self):
|
||||
return self._project_ids
|
||||
@@ -168,14 +176,39 @@ class GcpProvider(Provider):
|
||||
# "partition": "identity.partition",
|
||||
}
|
||||
|
||||
def setup_session(self, credentials_file):
|
||||
def setup_session(self, credentials_file: str, service_account: str) -> Credentials:
|
||||
"""
|
||||
Setup the GCP session with the provided credentials file or service account to impersonate
|
||||
Args:
|
||||
credentials_file: str
|
||||
service_account: str
|
||||
Returns:
|
||||
Credentials object
|
||||
"""
|
||||
try:
|
||||
scopes = ["https://www.googleapis.com/auth/cloud-platform"]
|
||||
|
||||
if credentials_file:
|
||||
logger.info(f"Using credentials file: {credentials_file}")
|
||||
self.__set_gcp_creds_env_var__(credentials_file)
|
||||
|
||||
credentials, _ = auth.default(
|
||||
scopes=["https://www.googleapis.com/auth/cloud-platform"]
|
||||
)
|
||||
# Get default credentials
|
||||
credentials, _ = default(scopes=scopes)
|
||||
|
||||
# Refresh the credentials to ensure they are valid
|
||||
credentials.refresh(Request())
|
||||
|
||||
logger.info(f"Initial credentials: {credentials}")
|
||||
|
||||
if service_account:
|
||||
# Create the impersonated credentials
|
||||
credentials = impersonated_credentials.Credentials(
|
||||
source_credentials=credentials,
|
||||
target_principal=service_account,
|
||||
target_scopes=scopes,
|
||||
)
|
||||
logger.info(f"Impersonated credentials: {credentials}")
|
||||
|
||||
return credentials
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
@@ -197,6 +230,10 @@ class GcpProvider(Provider):
|
||||
f"GCP Account: {Fore.YELLOW}{self.identity.profile}{Style.RESET_ALL}",
|
||||
f"GCP Project IDs: {Fore.YELLOW}{', '.join(self.project_ids)}{Style.RESET_ALL}",
|
||||
]
|
||||
if self.impersonated_service_account:
|
||||
report_lines.append(
|
||||
f"Impersonated Service Account: {Fore.YELLOW}{self.impersonated_service_account}{Style.RESET_ALL}"
|
||||
)
|
||||
if self.excluded_project_ids:
|
||||
report_lines.append(
|
||||
f"Excluded GCP Project IDs: {Fore.YELLOW}{', '.join(self.excluded_project_ids)}{Style.RESET_ALL}"
|
||||
@@ -259,14 +296,9 @@ class GcpProvider(Provider):
|
||||
f"{http_error.__class__.__name__}[{http_error.__traceback__.tb_lineno}]: {http_error}"
|
||||
)
|
||||
except Exception as error:
|
||||
if error.__class__.__name__ == "RefreshError":
|
||||
logger.critical(
|
||||
"Google Cloud SDK has not been authenticated or the credentials have expired. Authenticate by running 'gcloud auth application-default login' then retry."
|
||||
)
|
||||
else:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
finally:
|
||||
return projects
|
||||
|
||||
@@ -12,6 +12,12 @@ def init_parser(self):
|
||||
metavar="FILE_PATH",
|
||||
help="Authenticate using a Google Service Account Application Credentials JSON file",
|
||||
)
|
||||
gcp_auth_modes_group.add_argument(
|
||||
"--impersonate-service-account",
|
||||
nargs="?",
|
||||
metavar="SERVICE_ACCOUNT",
|
||||
help="Impersonate a Google Service Account",
|
||||
)
|
||||
# Projects
|
||||
gcp_projects_subparser = gcp_parser.add_argument_group("Projects")
|
||||
gcp_projects_subparser.add_argument(
|
||||
|
||||
@@ -1229,6 +1229,14 @@ class Test_Parser:
|
||||
assert parsed.provider == "gcp"
|
||||
assert parsed.list_project_id
|
||||
|
||||
def test_parser_gcp_impersonate_service_account(self):
|
||||
argument = "--impersonate-service-account"
|
||||
service_account = "test@test.iam.gserviceaccount.com"
|
||||
command = [prowler_command, "gcp", argument, service_account]
|
||||
parsed = self.parser.parse(command)
|
||||
assert parsed.provider == "gcp"
|
||||
assert parsed.impersonate_service_account == service_account
|
||||
|
||||
def test_parser_kubernetes_auth_kubeconfig_file(self):
|
||||
argument = "--kubeconfig-file"
|
||||
file = "config"
|
||||
|
||||
@@ -20,6 +20,7 @@ class TestGCPProvider:
|
||||
arguments.excluded_project_id = []
|
||||
arguments.list_project_id = False
|
||||
arguments.credentials_file = ""
|
||||
arguments.impersonate_service_account = ""
|
||||
arguments.config_file = default_config_file_path
|
||||
arguments.fixer_config = default_fixer_config_file_path
|
||||
|
||||
@@ -56,6 +57,7 @@ class TestGCPProvider:
|
||||
arguments.excluded_project_id = []
|
||||
arguments.list_project_id = False
|
||||
arguments.credentials_file = ""
|
||||
arguments.impersonate_service_account = ""
|
||||
arguments.config_file = default_config_file_path
|
||||
arguments.fixer_config = default_fixer_config_file_path
|
||||
|
||||
@@ -128,6 +130,7 @@ class TestGCPProvider:
|
||||
arguments.excluded_project_id = []
|
||||
arguments.list_project_id = False
|
||||
arguments.credentials_file = ""
|
||||
arguments.impersonate_service_account = ""
|
||||
arguments.config_file = default_config_file_path
|
||||
arguments.fixer_config = default_fixer_config_file_path
|
||||
|
||||
|
||||
Reference in New Issue
Block a user