infisicalsdk 1.0.5__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.

Files changed (25) hide show
  1. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/PKG-INFO +2 -3
  2. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/README.md +17 -0
  3. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/infisical_sdk/__init__.py +1 -1
  4. infisicalsdk-1.0.7/infisical_sdk/client.py +40 -0
  5. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/infisical_sdk/infisical_requests.py +8 -2
  6. infisicalsdk-1.0.7/infisical_sdk/resources/__init__.py +3 -0
  7. infisicalsdk-1.0.7/infisical_sdk/resources/auth.py +10 -0
  8. infisicalsdk-1.0.7/infisical_sdk/resources/auth_methods/__init__.py +2 -0
  9. infisicalsdk-1.0.7/infisical_sdk/resources/auth_methods/aws_auth.py +134 -0
  10. infisicalsdk-1.0.7/infisical_sdk/resources/auth_methods/universal_auth.py +35 -0
  11. infisicalsdk-1.0.7/infisical_sdk/resources/kms.py +177 -0
  12. infisicalsdk-1.0.7/infisical_sdk/resources/secrets.py +235 -0
  13. infisicalsdk-1.0.7/infisical_sdk/util/__init__.py +1 -0
  14. infisicalsdk-1.0.7/infisical_sdk/util/secrets_cache.py +106 -0
  15. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/infisicalsdk.egg-info/PKG-INFO +2 -3
  16. infisicalsdk-1.0.7/infisicalsdk.egg-info/SOURCES.txt +22 -0
  17. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/infisicalsdk.egg-info/requires.txt +0 -1
  18. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/pyproject.toml +0 -1
  19. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/setup.cfg +1 -1
  20. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/setup.py +1 -2
  21. infisicalsdk-1.0.5/infisical_sdk/client.py +0 -525
  22. infisicalsdk-1.0.5/infisicalsdk.egg-info/SOURCES.txt +0 -13
  23. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/infisical_sdk/api_types.py +0 -0
  24. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/infisicalsdk.egg-info/dependency_links.txt +0 -0
  25. {infisicalsdk-1.0.5 → infisicalsdk-1.0.7}/infisicalsdk.egg-info/top_level.txt +0 -0
@@ -1,13 +1,12 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: infisicalsdk
3
- Version: 1.0.5
3
+ Version: 1.0.7
4
4
  Summary: Infisical API Client
5
5
  Home-page: https://github.com/Infisical/python-sdk-official
6
6
  Author: Infisical
7
7
  Author-email: support@infisical.com
8
8
  Keywords: Infisical,Infisical API,Infisical SDK
9
9
  Description-Content-Type: text/markdown
10
- Requires-Dist: urllib3<2.1.0,>=1.25.3
11
10
  Requires-Dist: python-dateutil
12
11
  Requires-Dist: aenum
13
12
  Requires-Dist: requests~=2.32
@@ -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,10 +1,16 @@
1
1
  from typing import Any, Dict, Generic, Optional, TypeVar, Type
2
- from urllib.parse import urljoin
3
2
  import requests
4
3
  from dataclasses import dataclass
5
4
 
6
5
  T = TypeVar("T")
7
6
 
7
+ def join_url(base: str, path: str) -> str:
8
+ """
9
+ Join base URL and path properly, handling slashes appropriately.
10
+ """
11
+ if not base.endswith('/'):
12
+ base += '/'
13
+ return base + path.lstrip('/')
8
14
 
9
15
  class InfisicalError(Exception):
10
16
  """Base exception for Infisical client errors"""
@@ -60,7 +66,7 @@ class InfisicalRequests:
60
66
 
61
67
  def _build_url(self, path: str) -> str:
62
68
  """Construct full URL from path"""
63
- return urljoin(self.host, path.lstrip("/"))
69
+ return join_url(self.host, path.lstrip("/"))
64
70
 
65
71
  def set_token(self, token: str):
66
72
  """Set authorization token"""
@@ -0,0 +1,3 @@
1
+ from .secrets import V3RawSecrets
2
+ from .kms import KMS
3
+ from .auth import Auth
@@ -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,2 @@
1
+ from .aws_auth import AWSAuth
2
+ from .universal_auth import UniversalAuth
@@ -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