1111class SecretsManager :
1212 def __init__ (self ):
1313 self ._aws_client = None
14+ self ._gcp_client = None
1415
1516 @property
1617 def aws_client (self ):
1718 if not self ._aws_client :
1819 self ._aws_client = boto3 .client ("secretsmanager" )
1920 return self ._aws_client
2021
22+ @property
23+ def gcp_client (self ):
24+ if not self ._gcp_client :
25+ try :
26+ from google .cloud import secretmanager
27+ except ImportError as e :
28+ raise ConfigurationError (
29+ "google-cloud-secret-manager is required for vault+gcp:// "
30+ "secrets. Install with: pip install google-cloud-secret-manager"
31+ ) from e
32+ self ._gcp_client = secretmanager .SecretManagerServiceClient ()
33+ return self ._gcp_client
34+
2135 def load_as_dict (self , url : str ) -> dict :
22- """Load a secret from the supported vault. In this case only AWS Secrets Manager"""
36+ """Load a JSON secret from a supported vault.
37+
38+ Supported schemes:
39+ vault+aws://<secret_id> (AWS Secrets Manager)
40+ vault+gcp://<project>/<secret> (GCP Secret Manager)
41+ """
2342 parts = urlparse (url )
2443 if parts .scheme == "vault+aws" :
2544 secret_id = parts .netloc + parts .path
@@ -35,12 +54,32 @@ def load_as_dict(self, url: str) -> dict:
3554 except JSONDecodeError :
3655 raise Exception (f"Secret url '{ url } ' could not be parsed." )
3756
57+ elif parts .scheme == "vault+gcp" :
58+ project = parts .netloc
59+ secret_name = parts .path .lstrip ("/" )
60+ if not project or not secret_name :
61+ raise ConfigurationError (
62+ f"Invalid GCP secret URL '{ url } '. "
63+ f"Expected format: vault+gcp://<project>/<secret>"
64+ )
65+ name = f"projects/{ project } /secrets/{ secret_name } /versions/latest"
66+ try :
67+ response = self .gcp_client .access_secret_version (request = {"name" : name })
68+ except Exception as err :
69+ raise ConfigurationError (
70+ f"Couldn't load GCP secret '{ url } ': { err } "
71+ ) from err
72+ try :
73+ secrets = json .loads (response .payload .data .decode ("utf-8" ))
74+ except JSONDecodeError :
75+ raise Exception (f"Secret url '{ url } ' could not be parsed." )
76+
3877 else :
3978 raise Exception (f"Secret url '{ url } ' is not supported." )
4079 return secrets
4180
4281 def supports (self , url : str ):
43- return url .startswith ("vault+aws://" )
82+ return url .startswith ("vault+aws://" ) or url . startswith ( "vault+gcp://" )
4483
4584 def load_as_db_url (self , secret_uri : str ):
4685 """Load the secret and return it as a database url."""
0 commit comments