infisicalsdk 1.0.6__tar.gz → 1.0.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of infisicalsdk might be problematic. Click here for more details.
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/PKG-INFO +2 -2
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/README.md +17 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/infisical_sdk/__init__.py +1 -1
- infisicalsdk-1.0.8/infisical_sdk/client.py +40 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/infisical_sdk/infisical_requests.py +59 -1
- infisicalsdk-1.0.8/infisical_sdk/resources/__init__.py +3 -0
- infisicalsdk-1.0.8/infisical_sdk/resources/auth.py +10 -0
- infisicalsdk-1.0.8/infisical_sdk/resources/auth_methods/__init__.py +2 -0
- infisicalsdk-1.0.8/infisical_sdk/resources/auth_methods/aws_auth.py +134 -0
- infisicalsdk-1.0.8/infisical_sdk/resources/auth_methods/universal_auth.py +35 -0
- infisicalsdk-1.0.8/infisical_sdk/resources/kms.py +177 -0
- infisicalsdk-1.0.8/infisical_sdk/resources/secrets.py +235 -0
- infisicalsdk-1.0.8/infisical_sdk/util/__init__.py +1 -0
- infisicalsdk-1.0.8/infisical_sdk/util/secrets_cache.py +106 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/infisicalsdk.egg-info/PKG-INFO +2 -2
- infisicalsdk-1.0.8/infisicalsdk.egg-info/SOURCES.txt +22 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/setup.cfg +1 -1
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/setup.py +1 -1
- infisicalsdk-1.0.6/infisical_sdk/client.py +0 -525
- infisicalsdk-1.0.6/infisicalsdk.egg-info/SOURCES.txt +0 -13
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/infisical_sdk/api_types.py +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/infisicalsdk.egg-info/dependency_links.txt +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/infisicalsdk.egg-info/requires.txt +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/infisicalsdk.egg-info/top_level.txt +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.8}/pyproject.toml +0 -0
|
@@ -45,12 +45,29 @@ client.auth.universal_auth.login(
|
|
|
45
45
|
secrets = client.secrets.list_secrets(project_id="<project-id>", environment_slug="dev", secret_path="/")
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
## InfisicalSDKClient Parameters
|
|
49
|
+
|
|
50
|
+
The `InfisicalSDKClient` takes the following parameters, which are used as a global configuration for the lifetime of the SDK instance.
|
|
51
|
+
|
|
52
|
+
- **host** (`str`, _Optional_): The host URL for your Infisical instance. Defaults to `https://app.infisical.com`.
|
|
53
|
+
- **token** (`str`, _Optional_): Specify an authentication token to use for all requests. If provided, you will not need to call any of the `auth` methods. Defaults to `None`
|
|
54
|
+
- **cache_ttl** (`int`, _Optional_): The SDK has built-in client-side caching for secrets, greatly improving response times. By default, secrets are cached for 1 minute (60 seconds). You can disable caching by setting `cache_ttl` to `None`, or adjust the duration in seconds as needed.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
client = InfisicalSDKClient(
|
|
58
|
+
host="https://app.infisical.com", # Defaults to https://app.infisical.com
|
|
59
|
+
token="<optional-auth-token>", # If not set, use the client.auth() methods.
|
|
60
|
+
cache_ttl = 300 # `None` to disable caching
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
48
64
|
## Core Methods
|
|
49
65
|
|
|
50
66
|
The SDK methods are organized into the following high-level categories:
|
|
51
67
|
|
|
52
68
|
1. `auth`: Handles authentication methods.
|
|
53
69
|
2. `secrets`: Manages CRUD operations for secrets.
|
|
70
|
+
3. `kms`: Perform cryptographic operations with Infisical KMS.
|
|
54
71
|
|
|
55
72
|
### `auth`
|
|
56
73
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
from .client import InfisicalSDKClient # noqa
|
|
2
2
|
from .infisical_requests import InfisicalError # noqa
|
|
3
|
-
from .api_types import SingleSecretResponse, ListSecretsResponse, BaseSecret # noqa
|
|
3
|
+
from .api_types import SingleSecretResponse, ListSecretsResponse, BaseSecret, SymmetricEncryption # noqa
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .infisical_requests import InfisicalRequests
|
|
2
|
+
|
|
3
|
+
from infisical_sdk.resources import Auth
|
|
4
|
+
from infisical_sdk.resources import V3RawSecrets
|
|
5
|
+
from infisical_sdk.resources import KMS
|
|
6
|
+
|
|
7
|
+
from infisical_sdk.util import SecretsCache
|
|
8
|
+
|
|
9
|
+
class InfisicalSDKClient:
|
|
10
|
+
def __init__(self, host: str, token: str = None, cache_ttl: int = 60):
|
|
11
|
+
"""
|
|
12
|
+
Initialize the Infisical SDK client.
|
|
13
|
+
|
|
14
|
+
:param str host: The host URL for your Infisical instance. Will default to `https://app.infisical.com` if not specified.
|
|
15
|
+
:param str token: The authentication token for the client. If not specified, you can use the `auth` methods to authenticate.
|
|
16
|
+
:param int cache_ttl: The time to live for the secrets cache. This is the number of seconds that secrets fetched from the API will be cached for. Set to `None` to disable caching. Defaults to `60` seconds.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
self.host = host
|
|
20
|
+
self.access_token = token
|
|
21
|
+
|
|
22
|
+
self.api = InfisicalRequests(host=host, token=token)
|
|
23
|
+
self.cache = SecretsCache(cache_ttl)
|
|
24
|
+
self.auth = Auth(self.api, self.set_token)
|
|
25
|
+
self.secrets = V3RawSecrets(self.api, self.cache)
|
|
26
|
+
self.kms = KMS(self.api)
|
|
27
|
+
|
|
28
|
+
def set_token(self, token: str):
|
|
29
|
+
"""
|
|
30
|
+
Set the access token for future requests.
|
|
31
|
+
"""
|
|
32
|
+
self.api.set_token(token)
|
|
33
|
+
self.access_token = token
|
|
34
|
+
|
|
35
|
+
def get_token(self):
|
|
36
|
+
"""
|
|
37
|
+
Set the access token for future requests.
|
|
38
|
+
"""
|
|
39
|
+
return self.access_token
|
|
40
|
+
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
from typing import Any, Dict, Generic, Optional, TypeVar, Type
|
|
1
|
+
from typing import Any, Dict, Generic, Optional, TypeVar, Type, Callable, List
|
|
2
|
+
import socket
|
|
2
3
|
import requests
|
|
4
|
+
import functools
|
|
3
5
|
from dataclasses import dataclass
|
|
6
|
+
import time
|
|
7
|
+
import random
|
|
4
8
|
|
|
5
9
|
T = TypeVar("T")
|
|
6
10
|
|
|
11
|
+
# List of network-related exceptions that should trigger retries
|
|
12
|
+
NETWORK_ERRORS = [
|
|
13
|
+
requests.exceptions.ConnectionError,
|
|
14
|
+
requests.exceptions.ChunkedEncodingError,
|
|
15
|
+
requests.exceptions.ReadTimeout,
|
|
16
|
+
requests.exceptions.ConnectTimeout,
|
|
17
|
+
socket.gaierror,
|
|
18
|
+
socket.timeout,
|
|
19
|
+
ConnectionResetError,
|
|
20
|
+
ConnectionRefusedError,
|
|
21
|
+
ConnectionError,
|
|
22
|
+
ConnectionAbortedError,
|
|
23
|
+
]
|
|
24
|
+
|
|
7
25
|
def join_url(base: str, path: str) -> str:
|
|
8
26
|
"""
|
|
9
27
|
Join base URL and path properly, handling slashes appropriately.
|
|
@@ -49,6 +67,42 @@ class APIResponse(Generic[T]):
|
|
|
49
67
|
headers=data['headers']
|
|
50
68
|
)
|
|
51
69
|
|
|
70
|
+
def with_retry(
|
|
71
|
+
max_retries: int = 3,
|
|
72
|
+
base_delay: float = 1.0,
|
|
73
|
+
network_errors: Optional[List[Type[Exception]]] = None
|
|
74
|
+
) -> Callable:
|
|
75
|
+
"""
|
|
76
|
+
Decorator to add retry logic with exponential backoff to requests methods.
|
|
77
|
+
"""
|
|
78
|
+
if network_errors is None:
|
|
79
|
+
network_errors = NETWORK_ERRORS
|
|
80
|
+
|
|
81
|
+
def decorator(func: Callable) -> Callable:
|
|
82
|
+
@functools.wraps(func)
|
|
83
|
+
def wrapper(*args, **kwargs):
|
|
84
|
+
retry_count = 0
|
|
85
|
+
|
|
86
|
+
while True:
|
|
87
|
+
try:
|
|
88
|
+
return func(*args, **kwargs)
|
|
89
|
+
except tuple(network_errors) as error:
|
|
90
|
+
retry_count += 1
|
|
91
|
+
if retry_count > max_retries:
|
|
92
|
+
raise
|
|
93
|
+
|
|
94
|
+
base_delay_with_backoff = base_delay * (2 ** (retry_count - 1))
|
|
95
|
+
|
|
96
|
+
# +/-20% jitter
|
|
97
|
+
jitter = random.uniform(-0.2, 0.2) * base_delay_with_backoff
|
|
98
|
+
delay = base_delay_with_backoff + jitter
|
|
99
|
+
|
|
100
|
+
time.sleep(delay)
|
|
101
|
+
|
|
102
|
+
return wrapper
|
|
103
|
+
|
|
104
|
+
return decorator
|
|
105
|
+
|
|
52
106
|
|
|
53
107
|
class InfisicalRequests:
|
|
54
108
|
def __init__(self, host: str, token: Optional[str] = None):
|
|
@@ -93,6 +147,7 @@ class InfisicalRequests:
|
|
|
93
147
|
except ValueError:
|
|
94
148
|
raise InfisicalError("Invalid JSON response")
|
|
95
149
|
|
|
150
|
+
@with_retry(max_retries=4, base_delay=1.0)
|
|
96
151
|
def get(
|
|
97
152
|
self,
|
|
98
153
|
path: str,
|
|
@@ -119,6 +174,7 @@ class InfisicalRequests:
|
|
|
119
174
|
headers=dict(response.headers)
|
|
120
175
|
)
|
|
121
176
|
|
|
177
|
+
@with_retry(max_retries=4, base_delay=1.0)
|
|
122
178
|
def post(
|
|
123
179
|
self,
|
|
124
180
|
path: str,
|
|
@@ -143,6 +199,7 @@ class InfisicalRequests:
|
|
|
143
199
|
headers=dict(response.headers)
|
|
144
200
|
)
|
|
145
201
|
|
|
202
|
+
@with_retry(max_retries=4, base_delay=1.0)
|
|
146
203
|
def patch(
|
|
147
204
|
self,
|
|
148
205
|
path: str,
|
|
@@ -167,6 +224,7 @@ class InfisicalRequests:
|
|
|
167
224
|
headers=dict(response.headers)
|
|
168
225
|
)
|
|
169
226
|
|
|
227
|
+
@with_retry(max_retries=4, base_delay=1.0)
|
|
170
228
|
def delete(
|
|
171
229
|
self,
|
|
172
230
|
path: str,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from infisical_sdk.infisical_requests import InfisicalRequests
|
|
2
|
+
from infisical_sdk.resources.auth_methods import AWSAuth
|
|
3
|
+
from infisical_sdk.resources.auth_methods import UniversalAuth
|
|
4
|
+
|
|
5
|
+
from typing import Callable
|
|
6
|
+
class Auth:
|
|
7
|
+
def __init__(self, requests: InfisicalRequests, setToken: Callable[[str], None]):
|
|
8
|
+
self.requests = requests
|
|
9
|
+
self.aws_auth = AWSAuth(requests, setToken)
|
|
10
|
+
self.universal_auth = UniversalAuth(requests, setToken)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from botocore.auth import SigV4Auth
|
|
2
|
+
from botocore.awsrequest import AWSRequest
|
|
3
|
+
from botocore.exceptions import NoCredentialsError
|
|
4
|
+
|
|
5
|
+
from infisical_sdk.infisical_requests import InfisicalRequests
|
|
6
|
+
from infisical_sdk.api_types import MachineIdentityLoginResponse
|
|
7
|
+
|
|
8
|
+
from typing import Callable
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
import boto3
|
|
12
|
+
import base64
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import datetime
|
|
16
|
+
|
|
17
|
+
from typing import Dict, Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AWSAuth:
|
|
21
|
+
def __init__(self, requests: InfisicalRequests, setToken: Callable[[str], None]) -> None:
|
|
22
|
+
self.requests = requests
|
|
23
|
+
self.setToken = setToken
|
|
24
|
+
|
|
25
|
+
def login(self, identity_id: str) -> MachineIdentityLoginResponse:
|
|
26
|
+
"""
|
|
27
|
+
Login with AWS Authentication.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
identity_id (str): Your Machine Identity ID that has AWS Auth configured.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dict: A dictionary containing the access token and related information.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
identity_id = identity_id or os.getenv("INFISICAL_AWS_IAM_AUTH_IDENTITY_ID")
|
|
37
|
+
if not identity_id:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
"Identity ID must be provided or set in the environment variable" +
|
|
40
|
+
"INFISICAL_AWS_IAM_AUTH_IDENTITY_ID."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
aws_region = self.get_aws_region()
|
|
44
|
+
session = boto3.Session(region_name=aws_region)
|
|
45
|
+
|
|
46
|
+
credentials = self._get_aws_credentials(session)
|
|
47
|
+
|
|
48
|
+
iam_request_url = f"https://sts.{aws_region}.amazonaws.com/"
|
|
49
|
+
iam_request_body = "Action=GetCallerIdentity&Version=2011-06-15"
|
|
50
|
+
|
|
51
|
+
request_headers = self._prepare_aws_request(
|
|
52
|
+
iam_request_url,
|
|
53
|
+
iam_request_body,
|
|
54
|
+
credentials,
|
|
55
|
+
aws_region
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
requestBody = {
|
|
59
|
+
"identityId": identity_id,
|
|
60
|
+
"iamRequestBody": base64.b64encode(iam_request_body.encode()).decode(),
|
|
61
|
+
"iamRequestHeaders": base64.b64encode(json.dumps(request_headers).encode()).decode(),
|
|
62
|
+
"iamHttpRequestMethod": "POST"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
result = self.requests.post(
|
|
66
|
+
path="/api/v1/auth/aws-auth/login",
|
|
67
|
+
json=requestBody,
|
|
68
|
+
model=MachineIdentityLoginResponse
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
self.setToken(result.data.accessToken)
|
|
72
|
+
|
|
73
|
+
return result.data
|
|
74
|
+
|
|
75
|
+
def _get_aws_credentials(self, session: boto3.Session) -> Any:
|
|
76
|
+
try:
|
|
77
|
+
credentials = session.get_credentials()
|
|
78
|
+
if credentials is None:
|
|
79
|
+
raise NoCredentialsError("AWS credentials not found.")
|
|
80
|
+
return credentials.get_frozen_credentials()
|
|
81
|
+
except NoCredentialsError as e:
|
|
82
|
+
raise RuntimeError(f"AWS IAM Auth Login failed: {str(e)}")
|
|
83
|
+
|
|
84
|
+
def _prepare_aws_request(
|
|
85
|
+
self,
|
|
86
|
+
url: str,
|
|
87
|
+
body: str,
|
|
88
|
+
credentials: Any,
|
|
89
|
+
region: str) -> Dict[str, str]:
|
|
90
|
+
|
|
91
|
+
current_time = datetime.datetime.now(datetime.timezone.utc)
|
|
92
|
+
amz_date = current_time.strftime('%Y%m%dT%H%M%SZ')
|
|
93
|
+
|
|
94
|
+
request = AWSRequest(method="POST", url=url, data=body)
|
|
95
|
+
request.headers["X-Amz-Date"] = amz_date
|
|
96
|
+
request.headers["Host"] = f"sts.{region}.amazonaws.com"
|
|
97
|
+
request.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
|
|
98
|
+
request.headers["Content-Length"] = str(len(body))
|
|
99
|
+
|
|
100
|
+
signer = SigV4Auth(credentials, "sts", region)
|
|
101
|
+
signer.add_auth(request)
|
|
102
|
+
|
|
103
|
+
return {k: v for k, v in request.headers.items() if k.lower() != "content-length"}
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def get_aws_region() -> str:
|
|
107
|
+
region = os.getenv("AWS_REGION") # Typically found in lambda runtime environment
|
|
108
|
+
if region:
|
|
109
|
+
return region
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
return AWSAuth._get_aws_ec2_identity_document_region()
|
|
113
|
+
except Exception as e:
|
|
114
|
+
raise Exception("Failed to retrieve AWS region") from e
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def _get_aws_ec2_identity_document_region(timeout: int = 5000) -> str:
|
|
118
|
+
session = requests.Session()
|
|
119
|
+
token_response = session.put(
|
|
120
|
+
"http://169.254.169.254/latest/api/token",
|
|
121
|
+
headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"},
|
|
122
|
+
timeout=timeout / 1000
|
|
123
|
+
)
|
|
124
|
+
token_response.raise_for_status()
|
|
125
|
+
metadata_token = token_response.text
|
|
126
|
+
|
|
127
|
+
identity_response = session.get(
|
|
128
|
+
"http://169.254.169.254/latest/dynamic/instance-identity/document",
|
|
129
|
+
headers={"X-aws-ec2-metadata-token": metadata_token, "Accept": "application/json"},
|
|
130
|
+
timeout=timeout / 1000
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
identity_response.raise_for_status()
|
|
134
|
+
return identity_response.json().get("region")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from infisical_sdk.api_types import MachineIdentityLoginResponse
|
|
2
|
+
|
|
3
|
+
from typing import Callable
|
|
4
|
+
from infisical_sdk.infisical_requests import InfisicalRequests
|
|
5
|
+
class UniversalAuth:
|
|
6
|
+
def __init__(self, requests: InfisicalRequests, setToken: Callable[[str], None]):
|
|
7
|
+
self.requests = requests
|
|
8
|
+
self.setToken = setToken
|
|
9
|
+
|
|
10
|
+
def login(self, client_id: str, client_secret: str) -> MachineIdentityLoginResponse:
|
|
11
|
+
"""
|
|
12
|
+
Login with Universal Auth.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
client_id (str): Your Machine Identity Client ID.
|
|
16
|
+
client_secret (str): Your Machine Identity Client Secret.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Dict: A dictionary containing the access token and related information.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
requestBody = {
|
|
23
|
+
"clientId": client_id,
|
|
24
|
+
"clientSecret": client_secret
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
result = self.requests.post(
|
|
28
|
+
path="/api/v1/auth/universal-auth/login",
|
|
29
|
+
json=requestBody,
|
|
30
|
+
model=MachineIdentityLoginResponse
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self.setToken(result.data.accessToken)
|
|
34
|
+
|
|
35
|
+
return result.data
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from infisical_sdk.api_types import SymmetricEncryption, KmsKeysOrderBy, OrderDirection
|
|
2
|
+
from infisical_sdk.api_types import ListKmsKeysResponse, SingleKmsKeyResponse
|
|
3
|
+
from infisical_sdk.api_types import KmsKey, KmsKeyEncryptDataResponse, KmsKeyDecryptDataResponse
|
|
4
|
+
|
|
5
|
+
from infisical_sdk.infisical_requests import InfisicalRequests
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KMS:
|
|
9
|
+
def __init__(self, requests: InfisicalRequests) -> None:
|
|
10
|
+
self.requests = requests
|
|
11
|
+
|
|
12
|
+
def list_keys(
|
|
13
|
+
self,
|
|
14
|
+
project_id: str,
|
|
15
|
+
offset: int = 0,
|
|
16
|
+
limit: int = 100,
|
|
17
|
+
order_by: KmsKeysOrderBy = KmsKeysOrderBy.NAME,
|
|
18
|
+
order_direction: OrderDirection = OrderDirection.ASC,
|
|
19
|
+
search: str = None) -> ListKmsKeysResponse:
|
|
20
|
+
|
|
21
|
+
params = {
|
|
22
|
+
"projectId": project_id,
|
|
23
|
+
"search": search,
|
|
24
|
+
"offset": offset,
|
|
25
|
+
"limit": limit,
|
|
26
|
+
"orderBy": order_by,
|
|
27
|
+
"orderDirection": order_direction,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
result = self.requests.get(
|
|
31
|
+
path="/api/v1/kms/keys",
|
|
32
|
+
params=params,
|
|
33
|
+
model=ListKmsKeysResponse
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return result.data
|
|
37
|
+
|
|
38
|
+
def get_key_by_id(
|
|
39
|
+
self,
|
|
40
|
+
key_id: str) -> KmsKey:
|
|
41
|
+
|
|
42
|
+
result = self.requests.get(
|
|
43
|
+
path=f"/api/v1/kms/keys/{key_id}",
|
|
44
|
+
model=SingleKmsKeyResponse
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return result.data.key
|
|
48
|
+
|
|
49
|
+
def get_key_by_name(
|
|
50
|
+
self,
|
|
51
|
+
key_name: str,
|
|
52
|
+
project_id: str) -> KmsKey:
|
|
53
|
+
|
|
54
|
+
params = {
|
|
55
|
+
"projectId": project_id,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
result = self.requests.get(
|
|
59
|
+
path=f"/api/v1/kms/keys/key-name/{key_name}",
|
|
60
|
+
params=params,
|
|
61
|
+
model=SingleKmsKeyResponse
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return result.data.key
|
|
65
|
+
|
|
66
|
+
def create_key(
|
|
67
|
+
self,
|
|
68
|
+
name: str,
|
|
69
|
+
project_id: str,
|
|
70
|
+
encryption_algorithm: SymmetricEncryption,
|
|
71
|
+
description: str = None) -> KmsKey:
|
|
72
|
+
|
|
73
|
+
request_body = {
|
|
74
|
+
"name": name,
|
|
75
|
+
"projectId": project_id,
|
|
76
|
+
"encryptionAlgorithm": encryption_algorithm,
|
|
77
|
+
"description": description,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
result = self.requests.post(
|
|
81
|
+
path="/api/v1/kms/keys",
|
|
82
|
+
json=request_body,
|
|
83
|
+
model=SingleKmsKeyResponse
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return result.data.key
|
|
87
|
+
|
|
88
|
+
def update_key(
|
|
89
|
+
self,
|
|
90
|
+
key_id: str,
|
|
91
|
+
name: str = None,
|
|
92
|
+
is_disabled: bool = None,
|
|
93
|
+
description: str = None) -> KmsKey:
|
|
94
|
+
|
|
95
|
+
request_body = {
|
|
96
|
+
"name": name,
|
|
97
|
+
"isDisabled": is_disabled,
|
|
98
|
+
"description": description,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
result = self.requests.patch(
|
|
102
|
+
path=f"/api/v1/kms/keys/{key_id}",
|
|
103
|
+
json=request_body,
|
|
104
|
+
model=SingleKmsKeyResponse
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return result.data.key
|
|
108
|
+
|
|
109
|
+
def delete_key(
|
|
110
|
+
self,
|
|
111
|
+
key_id: str) -> KmsKey:
|
|
112
|
+
|
|
113
|
+
result = self.requests.delete(
|
|
114
|
+
path=f"/api/v1/kms/keys/{key_id}",
|
|
115
|
+
json={},
|
|
116
|
+
model=SingleKmsKeyResponse
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return result.data.key
|
|
120
|
+
|
|
121
|
+
def encrypt_data(
|
|
122
|
+
self,
|
|
123
|
+
key_id: str,
|
|
124
|
+
base64EncodedPlaintext: str) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Encrypt data with the specified KMS key.
|
|
127
|
+
|
|
128
|
+
:param key_id: The ID of the key to decrypt the ciphertext with
|
|
129
|
+
:type key_id: str
|
|
130
|
+
:param base64EncodedPlaintext: The base64 encoded plaintext to encrypt
|
|
131
|
+
:type plaintext: str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
:return: The encrypted base64 encoded plaintext (ciphertext)
|
|
135
|
+
:rtype: str
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
request_body = {
|
|
139
|
+
"plaintext": base64EncodedPlaintext
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
result = self.requests.post(
|
|
143
|
+
path=f"/api/v1/kms/keys/{key_id}/encrypt",
|
|
144
|
+
json=request_body,
|
|
145
|
+
model=KmsKeyEncryptDataResponse
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return result.data.ciphertext
|
|
149
|
+
|
|
150
|
+
def decrypt_data(
|
|
151
|
+
self,
|
|
152
|
+
key_id: str,
|
|
153
|
+
ciphertext: str) -> str:
|
|
154
|
+
"""
|
|
155
|
+
Decrypt data with the specified KMS key.
|
|
156
|
+
|
|
157
|
+
:param key_id: The ID of the key to decrypt the ciphertext with
|
|
158
|
+
:type key_id: str
|
|
159
|
+
:param ciphertext: The encrypted base64 plaintext to decrypt
|
|
160
|
+
:type ciphertext: str
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
:return: The base64 encoded plaintext
|
|
164
|
+
:rtype: str
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
request_body = {
|
|
168
|
+
"ciphertext": ciphertext
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
result = self.requests.post(
|
|
172
|
+
path=f"/api/v1/kms/keys/{key_id}/decrypt",
|
|
173
|
+
json=request_body,
|
|
174
|
+
model=KmsKeyDecryptDataResponse
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return result.data.plaintext
|