infisicalsdk 1.0.6__tar.gz → 1.0.7__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.7}/PKG-INFO +2 -2
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/README.md +17 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/infisical_sdk/__init__.py +1 -1
- infisicalsdk-1.0.7/infisical_sdk/client.py +40 -0
- infisicalsdk-1.0.7/infisical_sdk/resources/__init__.py +3 -0
- infisicalsdk-1.0.7/infisical_sdk/resources/auth.py +10 -0
- infisicalsdk-1.0.7/infisical_sdk/resources/auth_methods/__init__.py +2 -0
- infisicalsdk-1.0.7/infisical_sdk/resources/auth_methods/aws_auth.py +134 -0
- infisicalsdk-1.0.7/infisical_sdk/resources/auth_methods/universal_auth.py +35 -0
- infisicalsdk-1.0.7/infisical_sdk/resources/kms.py +177 -0
- infisicalsdk-1.0.7/infisical_sdk/resources/secrets.py +235 -0
- infisicalsdk-1.0.7/infisical_sdk/util/__init__.py +1 -0
- infisicalsdk-1.0.7/infisical_sdk/util/secrets_cache.py +106 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/infisicalsdk.egg-info/PKG-INFO +2 -2
- infisicalsdk-1.0.7/infisicalsdk.egg-info/SOURCES.txt +22 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/setup.cfg +1 -1
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/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.7}/infisical_sdk/api_types.py +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/infisical_sdk/infisical_requests.py +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/infisicalsdk.egg-info/dependency_links.txt +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/infisicalsdk.egg-info/requires.txt +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/infisicalsdk.egg-info/top_level.txt +0 -0
- {infisicalsdk-1.0.6 → infisicalsdk-1.0.7}/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
|
+
|
|
@@ -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
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
|
|
3
|
+
from infisical_sdk.infisical_requests import InfisicalRequests
|
|
4
|
+
from infisical_sdk.api_types import ListSecretsResponse, SingleSecretResponse, BaseSecret
|
|
5
|
+
from infisical_sdk.util import SecretsCache
|
|
6
|
+
|
|
7
|
+
CACHE_KEY_LIST_SECRETS = "cache-list-secrets"
|
|
8
|
+
CACHE_KEY_SINGLE_SECRET = "cache-single-secret"
|
|
9
|
+
|
|
10
|
+
class V3RawSecrets:
|
|
11
|
+
def __init__(self, requests: InfisicalRequests, cache: SecretsCache) -> None:
|
|
12
|
+
self.requests = requests
|
|
13
|
+
self.cache = cache
|
|
14
|
+
|
|
15
|
+
def list_secrets(
|
|
16
|
+
self,
|
|
17
|
+
project_id: str,
|
|
18
|
+
environment_slug: str,
|
|
19
|
+
secret_path: str,
|
|
20
|
+
expand_secret_references: bool = True,
|
|
21
|
+
view_secret_value: bool = True,
|
|
22
|
+
recursive: bool = False,
|
|
23
|
+
include_imports: bool = True,
|
|
24
|
+
tag_filters: List[str] = []) -> ListSecretsResponse:
|
|
25
|
+
|
|
26
|
+
params = {
|
|
27
|
+
"workspaceId": project_id,
|
|
28
|
+
"environment": environment_slug,
|
|
29
|
+
"secretPath": secret_path,
|
|
30
|
+
"viewSecretValue": str(view_secret_value).lower(),
|
|
31
|
+
"expandSecretReferences": str(expand_secret_references).lower(),
|
|
32
|
+
"recursive": str(recursive).lower(),
|
|
33
|
+
"include_imports": str(include_imports).lower(),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if tag_filters:
|
|
37
|
+
params["tagSlugs"] = ",".join(tag_filters)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
cache_key = self.cache.compute_cache_key(CACHE_KEY_LIST_SECRETS, **params)
|
|
41
|
+
if self.cache.enabled:
|
|
42
|
+
cached_response = self.cache.get(cache_key)
|
|
43
|
+
|
|
44
|
+
if cached_response is not None and isinstance(cached_response, ListSecretsResponse):
|
|
45
|
+
return cached_response
|
|
46
|
+
|
|
47
|
+
result = self.requests.get(
|
|
48
|
+
path="/api/v3/secrets/raw",
|
|
49
|
+
params=params,
|
|
50
|
+
model=ListSecretsResponse
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if self.cache.enabled:
|
|
54
|
+
self.cache.set(cache_key, result.data)
|
|
55
|
+
|
|
56
|
+
return result.data
|
|
57
|
+
|
|
58
|
+
def get_secret_by_name(
|
|
59
|
+
self,
|
|
60
|
+
secret_name: str,
|
|
61
|
+
project_id: str,
|
|
62
|
+
environment_slug: str,
|
|
63
|
+
secret_path: str,
|
|
64
|
+
expand_secret_references: bool = True,
|
|
65
|
+
include_imports: bool = True,
|
|
66
|
+
view_secret_value: bool = True,
|
|
67
|
+
version: str = None) -> BaseSecret:
|
|
68
|
+
|
|
69
|
+
params = {
|
|
70
|
+
"workspaceId": project_id,
|
|
71
|
+
"viewSecretValue": str(view_secret_value).lower(),
|
|
72
|
+
"environment": environment_slug,
|
|
73
|
+
"secretPath": secret_path,
|
|
74
|
+
"expandSecretReferences": str(expand_secret_references).lower(),
|
|
75
|
+
"include_imports": str(include_imports).lower(),
|
|
76
|
+
"version": version
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
cache_params = {
|
|
80
|
+
"project_id": project_id,
|
|
81
|
+
"environment_slug": environment_slug,
|
|
82
|
+
"secret_path": secret_path,
|
|
83
|
+
"secret_name": secret_name,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
cache_key = self.cache.compute_cache_key(CACHE_KEY_SINGLE_SECRET, **cache_params)
|
|
87
|
+
|
|
88
|
+
if self.cache.enabled:
|
|
89
|
+
cached_response = self.cache.get(cache_key)
|
|
90
|
+
|
|
91
|
+
if cached_response is not None and isinstance(cached_response, BaseSecret):
|
|
92
|
+
return cached_response
|
|
93
|
+
|
|
94
|
+
result = self.requests.get(
|
|
95
|
+
path=f"/api/v3/secrets/raw/{secret_name}",
|
|
96
|
+
params=params,
|
|
97
|
+
model=SingleSecretResponse
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if self.cache.enabled:
|
|
101
|
+
self.cache.set(cache_key, result.data.secret)
|
|
102
|
+
|
|
103
|
+
return result.data.secret
|
|
104
|
+
|
|
105
|
+
def create_secret_by_name(
|
|
106
|
+
self,
|
|
107
|
+
secret_name: str,
|
|
108
|
+
project_id: str,
|
|
109
|
+
secret_path: str,
|
|
110
|
+
environment_slug: str,
|
|
111
|
+
secret_value: str = None,
|
|
112
|
+
secret_comment: str = None,
|
|
113
|
+
skip_multiline_encoding: bool = False,
|
|
114
|
+
secret_reminder_repeat_days: Union[float, int] = None,
|
|
115
|
+
secret_reminder_note: str = None) -> BaseSecret:
|
|
116
|
+
|
|
117
|
+
requestBody = {
|
|
118
|
+
"workspaceId": project_id,
|
|
119
|
+
"environment": environment_slug,
|
|
120
|
+
"secretPath": secret_path,
|
|
121
|
+
"secretValue": secret_value,
|
|
122
|
+
"secretComment": secret_comment,
|
|
123
|
+
"tagIds": None,
|
|
124
|
+
"skipMultilineEncoding": skip_multiline_encoding,
|
|
125
|
+
"type": "shared",
|
|
126
|
+
"secretReminderRepeatDays": secret_reminder_repeat_days,
|
|
127
|
+
"secretReminderNote": secret_reminder_note
|
|
128
|
+
}
|
|
129
|
+
result = self.requests.post(
|
|
130
|
+
path=f"/api/v3/secrets/raw/{secret_name}",
|
|
131
|
+
json=requestBody,
|
|
132
|
+
model=SingleSecretResponse
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if self.cache.enabled:
|
|
137
|
+
cache_params = {
|
|
138
|
+
"project_id": project_id,
|
|
139
|
+
"environment_slug": environment_slug,
|
|
140
|
+
"secret_path": secret_path,
|
|
141
|
+
"secret_name": secret_name,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
cache_key = self.cache.compute_cache_key(CACHE_KEY_SINGLE_SECRET, **cache_params)
|
|
145
|
+
self.cache.set(cache_key, result.data.secret)
|
|
146
|
+
|
|
147
|
+
# Invalidates all list secret cache
|
|
148
|
+
self.cache.invalidate_operation(CACHE_KEY_LIST_SECRETS)
|
|
149
|
+
|
|
150
|
+
return result.data.secret
|
|
151
|
+
|
|
152
|
+
def update_secret_by_name(
|
|
153
|
+
self,
|
|
154
|
+
current_secret_name: str,
|
|
155
|
+
project_id: str,
|
|
156
|
+
secret_path: str,
|
|
157
|
+
environment_slug: str,
|
|
158
|
+
secret_value: str = None,
|
|
159
|
+
secret_comment: str = None,
|
|
160
|
+
skip_multiline_encoding: bool = False,
|
|
161
|
+
secret_reminder_repeat_days: Union[float, int] = None,
|
|
162
|
+
secret_reminder_note: str = None,
|
|
163
|
+
new_secret_name: str = None) -> BaseSecret:
|
|
164
|
+
|
|
165
|
+
requestBody = {
|
|
166
|
+
"workspaceId": project_id,
|
|
167
|
+
"environment": environment_slug,
|
|
168
|
+
"secretPath": secret_path,
|
|
169
|
+
"secretValue": secret_value,
|
|
170
|
+
"secretComment": secret_comment,
|
|
171
|
+
"newSecretName": new_secret_name,
|
|
172
|
+
"tagIds": None,
|
|
173
|
+
"skipMultilineEncoding": skip_multiline_encoding,
|
|
174
|
+
"type": "shared",
|
|
175
|
+
"secretReminderRepeatDays": secret_reminder_repeat_days,
|
|
176
|
+
"secretReminderNote": secret_reminder_note
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
result = self.requests.patch(
|
|
180
|
+
path=f"/api/v3/secrets/raw/{current_secret_name}",
|
|
181
|
+
json=requestBody,
|
|
182
|
+
model=SingleSecretResponse
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if self.cache.enabled:
|
|
186
|
+
cache_params = {
|
|
187
|
+
"project_id": project_id,
|
|
188
|
+
"environment_slug": environment_slug,
|
|
189
|
+
"secret_path": secret_path,
|
|
190
|
+
"secret_name": current_secret_name,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
cache_key = self.cache.compute_cache_key(CACHE_KEY_SINGLE_SECRET, **cache_params)
|
|
194
|
+
self.cache.unset(cache_key)
|
|
195
|
+
|
|
196
|
+
# Invalidates all list secret cache
|
|
197
|
+
self.cache.invalidate_operation(CACHE_KEY_LIST_SECRETS)
|
|
198
|
+
|
|
199
|
+
return result.data.secret
|
|
200
|
+
|
|
201
|
+
def delete_secret_by_name(
|
|
202
|
+
self,
|
|
203
|
+
secret_name: str,
|
|
204
|
+
project_id: str,
|
|
205
|
+
secret_path: str,
|
|
206
|
+
environment_slug: str) -> BaseSecret:
|
|
207
|
+
|
|
208
|
+
requestBody = {
|
|
209
|
+
"workspaceId": project_id,
|
|
210
|
+
"environment": environment_slug,
|
|
211
|
+
"secretPath": secret_path,
|
|
212
|
+
"type": "shared",
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
result = self.requests.delete(
|
|
216
|
+
path=f"/api/v3/secrets/raw/{secret_name}",
|
|
217
|
+
json=requestBody,
|
|
218
|
+
model=SingleSecretResponse
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if self.cache.enabled:
|
|
222
|
+
cache_params = {
|
|
223
|
+
"project_id": project_id,
|
|
224
|
+
"environment_slug": environment_slug,
|
|
225
|
+
"secret_path": secret_path,
|
|
226
|
+
"secret_name": secret_name,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
cache_key = self.cache.compute_cache_key(CACHE_KEY_SINGLE_SECRET, **cache_params)
|
|
230
|
+
self.cache.unset(cache_key)
|
|
231
|
+
|
|
232
|
+
# Invalidates all list secret cache
|
|
233
|
+
self.cache.invalidate_operation(CACHE_KEY_LIST_SECRETS)
|
|
234
|
+
|
|
235
|
+
return result.data.secret
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .secrets_cache import SecretsCache
|