Skip to content

Commit c5efd9a

Browse files
feat: deployment secret single-key (#816)
Signed-off-by: nishantmunjal7 <[email protected]> Co-authored-by: Louis Christopher <[email protected]>
1 parent abf5f6d commit c5efd9a

File tree

14 files changed

+143
-103
lines changed

14 files changed

+143
-103
lines changed

.env.example

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,18 @@ ATLAN_START_TO_CLOSE_TIMEOUT_SECONDS=7200 # 2 hours
2424
# if you plan to deploy the app in customer environment, below is a sample environment variables you need to set
2525
ATLAN_WORKFLOW_HOST="tenant-temporal.atlan.com"
2626
ATLAN_WORKFLOW_PORT="443"
27-
ATLAN_WORKFLOW_AUTH_ENABLED="true"
27+
ATLAN_AUTH_ENABLED="true"
28+
ATLAN_AUTH_URL="https://tenant.atlan.com/auth/realms/default/protocol/openid-connect/token"
29+
ATLAN_WORKFLOW_TLS_ENABLED="true"
2830
ENABLE_ATLAN_UPLOAD="true"
29-
ATLAN_DEPLOYMENT_SECRETS='{"appName_app_client_id":"enter_client_id","appName_app_client_secret":"enter_secret","atlan_auth_url":"https://tenant.atlan.com/auth/realms/default/protocol/openid-connect/token","workflow_tls_enabled":"true","deployment_name":"agent-v2"}'
3031

3132
# Storage:
3233
DEPLOYMENT_OBJECT_STORE_NAME="objectstore"
33-
UPSTREAM_OBJECT_STORE_NAME="atlan-storage"
34+
UPSTREAM_OBJECT_STORE_NAME="atlan-objectstore"
3435

3536
# Secret Store
36-
SECRET_STORE_NAME="aws-secrets"
37+
SECRET_STORE_NAME="aws-secrets"
38+
39+
# Client ID/Secret key fields, the value is the name of the key in the secret store
40+
ATLAN_AUTH_CLIENT_ID_KEY="ATLAN_AUTH_CLIENT_ID"
41+
ATLAN_AUTH_CLIENT_SECRET_KEY="ATLAN_AUTH_CLIENT_SECRET"

application_sdk/clients/atlan_auth.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
"""OAuth2 token manager with automatic secret store discovery."""
22

33
import time
4-
from typing import Any, Dict, Optional
4+
from typing import Dict, Optional
55

66
import aiohttp
77

88
from application_sdk.common.error_codes import ClientError
99
from application_sdk.constants import (
1010
APPLICATION_NAME,
11+
AUTH_ENABLED,
12+
AUTH_URL,
1113
WORKFLOW_AUTH_CLIENT_ID_KEY,
1214
WORKFLOW_AUTH_CLIENT_SECRET_KEY,
13-
WORKFLOW_AUTH_ENABLED,
14-
WORKFLOW_AUTH_URL_KEY,
1515
)
1616
from application_sdk.observability.logger_adaptor import get_logger
1717
from application_sdk.services.secretstore import SecretStore
@@ -39,9 +39,8 @@ def __init__(self):
3939
(environment variables, AWS Secrets Manager, Azure Key Vault, etc.)
4040
"""
4141
self.application_name = APPLICATION_NAME
42-
self.auth_config: Dict[str, Any] = SecretStore.get_deployment_secret()
43-
self.auth_enabled: bool = WORKFLOW_AUTH_ENABLED
44-
self.auth_url: Optional[str] = None
42+
self.auth_enabled: bool = AUTH_ENABLED
43+
self.auth_url: Optional[str] = AUTH_URL
4544

4645
# Secret store credentials (cached after first fetch)
4746
self.credentials: Optional[Dict[str, str]] = None
@@ -175,18 +174,17 @@ def get_time_until_expiry(self) -> Optional[float]:
175174

176175
async def _extract_auth_credentials(self) -> Optional[Dict[str, str]]:
177176
"""Fetch app credentials from secret store - auth-specific logic"""
178-
if (
179-
WORKFLOW_AUTH_CLIENT_ID_KEY in self.auth_config
180-
and WORKFLOW_AUTH_CLIENT_SECRET_KEY in self.auth_config
181-
):
177+
client_id = SecretStore.get_deployment_secret(WORKFLOW_AUTH_CLIENT_ID_KEY)
178+
client_secret = SecretStore.get_deployment_secret(
179+
WORKFLOW_AUTH_CLIENT_SECRET_KEY
180+
)
181+
182+
if client_id and client_secret:
182183
credentials = {
183-
"client_id": self.auth_config[WORKFLOW_AUTH_CLIENT_ID_KEY],
184-
"client_secret": self.auth_config[WORKFLOW_AUTH_CLIENT_SECRET_KEY],
184+
"client_id": client_id,
185+
"client_secret": client_secret,
185186
}
186187

187-
if WORKFLOW_AUTH_URL_KEY in self.auth_config:
188-
self.auth_url = self.auth_config[WORKFLOW_AUTH_URL_KEY]
189-
190188
return credentials
191189
return None
192190

@@ -199,10 +197,8 @@ def clear_cache(self) -> None:
199197
"""
200198
# we are doing this to force a fetch of the credentials from secret store
201199
self.credentials = None
202-
self.auth_url = None
203200
self._access_token = None
204201
self._token_expiry = 0
205-
self.auth_config = {}
206202

207203
def calculate_refresh_interval(self) -> int:
208204
"""Calculate the optimal token refresh interval based on token expiry.

application_sdk/clients/temporal.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@
1818
from application_sdk.constants import (
1919
APPLICATION_NAME,
2020
DEPLOYMENT_NAME,
21-
DEPLOYMENT_NAME_KEY,
2221
IS_LOCKING_DISABLED,
2322
MAX_CONCURRENT_ACTIVITIES,
2423
WORKFLOW_HOST,
2524
WORKFLOW_MAX_TIMEOUT_HOURS,
2625
WORKFLOW_NAMESPACE,
2726
WORKFLOW_PORT,
28-
WORKFLOW_TLS_ENABLED_KEY,
27+
WORKFLOW_TLS_ENABLED,
2928
)
3029
from application_sdk.events.models import (
3130
ApplicationEventNames,
@@ -97,7 +96,6 @@ def __init__(
9796
self.port = port if port else WORKFLOW_PORT
9897
self.namespace = namespace if namespace else WORKFLOW_NAMESPACE
9998

100-
self.deployment_config: Dict[str, Any] = SecretStore.get_deployment_secret()
10199
self.worker_task_queue = self.get_worker_task_queue()
102100
self.auth_manager = AtlanAuthClient()
103101

@@ -118,12 +116,8 @@ def get_worker_task_queue(self) -> str:
118116
Returns:
119117
str: The task queue name in format "app_name-deployment_name".
120118
"""
121-
deployment_name = self.deployment_config.get(
122-
DEPLOYMENT_NAME_KEY, DEPLOYMENT_NAME
123-
)
124-
125-
if deployment_name:
126-
return f"atlan-{self.application_name}-{deployment_name}"
119+
if DEPLOYMENT_NAME:
120+
return f"atlan-{self.application_name}-{DEPLOYMENT_NAME}"
127121
else:
128122
return self.application_name
129123

@@ -228,12 +222,9 @@ async def load(self) -> None:
228222
connection_options: Dict[str, Any] = {
229223
"target_host": self.get_connection_string(),
230224
"namespace": self.namespace,
231-
"tls": False,
225+
"tls": WORKFLOW_TLS_ENABLED,
232226
}
233227

234-
connection_options["tls"] = self.deployment_config.get(
235-
WORKFLOW_TLS_ENABLED_KEY, False
236-
)
237228
self.worker_task_queue = self.get_worker_task_queue()
238229

239230
if self.auth_manager.auth_enabled:

application_sdk/constants.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,23 @@
105105
DEPLOYMENT_SECRET_PATH = os.getenv(
106106
"ATLAN_DEPLOYMENT_SECRET_PATH", "ATLAN_DEPLOYMENT_SECRETS"
107107
)
108-
WORKFLOW_AUTH_ENABLED = (
109-
os.getenv("ATLAN_WORKFLOW_AUTH_ENABLED", "false").lower() == "true"
108+
AUTH_ENABLED = os.getenv("ATLAN_AUTH_ENABLED", "false").lower() == "true"
109+
#: OAuth2 authentication URL for workflow services
110+
AUTH_URL = os.getenv("ATLAN_AUTH_URL")
111+
#: Whether to enable TLS for Temporal workflow connections
112+
WORKFLOW_TLS_ENABLED = (
113+
os.getenv("ATLAN_WORKFLOW_TLS_ENABLED", "false").lower() == "true"
110114
)
111115

112116
# Deployment Secret Store Key Names
113-
WORKFLOW_AUTH_CLIENT_ID_KEY = f"{APPLICATION_NAME}_app_client_id"
114-
WORKFLOW_AUTH_CLIENT_SECRET_KEY = f"{APPLICATION_NAME}_app_client_secret"
115-
WORKFLOW_AUTH_URL_KEY = "atlan_auth_url"
116-
WORKFLOW_TLS_ENABLED_KEY = "workflow_tls_enabled"
117-
DEPLOYMENT_NAME_KEY = "deployment_name"
117+
#: Key name for OAuth2 client ID in deployment secrets (can be overridden via ATLAN_AUTH_CLIENT_ID_KEY)
118+
WORKFLOW_AUTH_CLIENT_ID_KEY = os.getenv(
119+
"ATLAN_AUTH_CLIENT_ID_KEY", "ATLAN_AUTH_CLIENT_ID"
120+
)
121+
#: Key name for OAuth2 client secret in deployment secrets (can be overridden via ATLAN_AUTH_CLIENT_SECRET_KEY)
122+
WORKFLOW_AUTH_CLIENT_SECRET_KEY = os.getenv(
123+
"ATLAN_AUTH_CLIENT_SECRET_KEY", "ATLAN_AUTH_CLIENT_SECRET"
124+
)
118125

119126
# Workflow Constants
120127
#: Timeout duration for activity heartbeats

application_sdk/events/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ class WorkerStartEventData(BaseModel):
155155
156156
Attributes:
157157
application_name: Name of the application the worker belongs to.
158+
deployment_name: Name of the deployment the worker belongs to.
158159
task_queue: Task queue name for the worker.
159160
namespace: Temporal namespace for the worker.
160161
host: Host address of the Temporal server.
@@ -167,6 +168,7 @@ class WorkerStartEventData(BaseModel):
167168

168169
version: str = WORKER_START_EVENT_VERSION
169170
application_name: str
171+
deployment_name: str
170172
task_queue: str
171173
namespace: str
172174
host: str

application_sdk/services/secretstore.py

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -235,41 +235,59 @@ def resolve_credentials(
235235
return credentials
236236

237237
@classmethod
238-
def get_deployment_secret(cls) -> Dict[str, Any]:
239-
"""Get deployment configuration from the deployment secret store.
238+
def get_deployment_secret(cls, key: str) -> Any:
239+
"""Get a specific key from deployment configuration in the deployment secret store.
240240
241241
Validates that the deployment secret store component is registered
242242
before attempting to fetch secrets to prevent errors. This method
243-
is commonly used to retrieve environment-specific configuration.
243+
fetches only the specified key from the deployment secret, rather than
244+
the entire secret dictionary.
245+
246+
Args:
247+
key (str): The key to fetch from the deployment secret.
244248
245249
Returns:
246-
Dict[str, Any]: Deployment configuration data, or empty dict if
247-
component is unavailable or fetch fails.
250+
Any: The value for the specified key, or None if the key is not found
251+
or the component is unavailable.
248252
249253
Examples:
250-
>>> # Get deployment configuration
251-
>>> config = SecretStore.get_deployment_secret()
252-
>>> if config:
253-
... print(f"Environment: {config.get('environment')}")
254-
... print(f"Region: {config.get('region')}")
255-
>>> else:
256-
... print("No deployment configuration available")
257-
>>> # Use in application initialization
258-
>>> deployment_config = SecretStore.get_deployment_secret()
259-
>>> if deployment_config.get('debug_mode'):
260-
... logging.getLogger().setLevel(logging.DEBUG)
254+
>>> # Get a specific deployment configuration value
255+
>>> auth_url = SecretStore.get_deployment_secret("ATLAN_AUTH_CLIENT_ID")
256+
>>> if auth_url:
257+
... print(f"Auth URL: {auth_url}")
258+
>>> # Get deployment name
259+
>>> deployment_name = SecretStore.get_deployment_secret("deployment_name")
260+
>>> if deployment_name:
261+
... print(f"Deployment: {deployment_name}")
261262
"""
262263
if not is_component_registered(DEPLOYMENT_SECRET_STORE_NAME):
263264
logger.warning(
264265
f"Deployment secret store component '{DEPLOYMENT_SECRET_STORE_NAME}' not registered."
265266
)
266-
return {}
267+
return None
267268

268269
try:
269-
return cls.get_secret(DEPLOYMENT_SECRET_PATH, DEPLOYMENT_SECRET_STORE_NAME)
270+
secret_data = cls.get_secret(
271+
DEPLOYMENT_SECRET_PATH, DEPLOYMENT_SECRET_STORE_NAME
272+
)
273+
if isinstance(secret_data, dict) and key in secret_data:
274+
return secret_data[key]
275+
276+
logger.debug(f"Multi-key not found, checking single-key secret for '{key}'")
277+
single_secret_data = cls.get_secret(key, DEPLOYMENT_SECRET_STORE_NAME)
278+
if isinstance(single_secret_data, dict):
279+
# Handle both {key:value} and {"value": "..."} cases
280+
if key in single_secret_data:
281+
return single_secret_data[key]
282+
elif len(single_secret_data) == 1:
283+
# extract single value
284+
return list(single_secret_data.values())[0]
285+
286+
return None
287+
270288
except Exception as e:
271-
logger.error(f"Failed to fetch deployment config: {e}")
272-
return {}
289+
logger.error(f"Failed to fetch deployment config key '{key}': {e}")
290+
return None
273291

274292
@classmethod
275293
def get_secret(

application_sdk/worker.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from temporalio.worker import Worker as TemporalWorker
1515

1616
from application_sdk.clients.workflow import WorkflowClient
17-
from application_sdk.constants import MAX_CONCURRENT_ACTIVITIES
17+
from application_sdk.constants import DEPLOYMENT_NAME, MAX_CONCURRENT_ACTIVITIES
1818
from application_sdk.events.models import (
1919
ApplicationEventNames,
2020
Event,
@@ -122,6 +122,7 @@ def __init__(
122122
if self.workflow_client:
123123
self._worker_creation_event_data = WorkerStartEventData(
124124
application_name=self.workflow_client.application_name,
125+
deployment_name=DEPLOYMENT_NAME,
125126
task_queue=self.workflow_client.worker_task_queue,
126127
namespace=self.workflow_client.namespace,
127128
host=self.workflow_client.host,

components/atlan-storage.yaml

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
apiVersion: dapr.io/v1alpha1
22
kind: Component
33
metadata:
4-
name: atlan-storage
4+
name: atlan-objectstore
55
spec:
6-
version: v1
76
type: bindings.aws.s3
8-
ignoreErrors: true
7+
version: v1
98
metadata:
9+
- name: bucket
10+
value: atlan-bucket
11+
- name: region
12+
value: us-east-1
13+
- name: endpoint
14+
value: https://{{tenant}}.atlan.com/api/blobstorage
1015
- name: accessKey
11-
value: "{{clientId}}"
16+
secretKeyRef:
17+
name: ATLAN_AUTH_CLIENT_ID
18+
key: ATLAN_AUTH_CLIENT_ID
1219
- name: secretKey
13-
value: "{{clientSecret}}"
14-
- name: endpoint
15-
value: "https://{{tenant}}.atlan.com/api/blobstorage"
16-
- name: bucket
17-
value: "atlan-default-bucket"
20+
secretKeyRef:
21+
name: ATLAN_AUTH_CLIENT_SECRET
22+
key: ATLAN_AUTH_CLIENT_SECRET
1823
- name: forcePathStyle
1924
value: "true"
20-
- name: region
21-
value: "us-east-1"
25+
auth:
26+
secretStore: deployment-secret-store

docs/docs/concepts/temporal_auth.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ The authentication system uses a Dapr secret store component that can be configu
5858
**Environment Variables:**
5959
```bash
6060
# Authentication settings
61-
ATLAN_WORKFLOW_AUTH_ENABLED=true
62-
ATLAN_WORKFLOW_AUTH_URL=https://your-oauth-provider.com/oauth/token
61+
ATLAN_AUTH_ENABLED=true
62+
ATLAN_AUTH_URL=https://tenant.atlan.com/auth/realms/default/protocol/openid-connect/token
6363

6464
# Secret store component configuration
6565
ATLAN_DEPLOYMENT_SECRET_COMPONENT=deployment-secret-store
@@ -329,7 +329,7 @@ dapr invoke --app-id your-app --method get-secret --data '{"key": "atlan-deploym
329329
**Symptom**: Token refresh failures
330330
```bash
331331
# Verify auth URL accessibility
332-
curl -X POST $ATLAN_WORKFLOW_AUTH_URL \
332+
curl -X POST $ATLAN_AUTH_URL \
333333
-d "grant_type=client_credentials&client_id=...&client_secret=..."
334334

335335
# Check credential validity in secret store
@@ -507,8 +507,8 @@ spec:
507507
**Environment Variables:**
508508
```bash
509509
# Authentication settings
510-
ATLAN_WORKFLOW_AUTH_ENABLED=true
511-
ATLAN_WORKFLOW_AUTH_URL=https://your-oauth-server.com/oauth/token
510+
ATLAN_AUTH_ENABLED=true
511+
ATLAN_AUTH_URL=https://tenant.atlan.com/auth/realms/default/protocol/openid-connect/token
512512

513513
# Secret store configuration
514514
ATLAN_DEPLOYMENT_SECRET_COMPONENT=deployment-secret-store

docs/docs/configuration.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ The Application SDK uses environment variables for configuration. These can be s
3030
| `ATLAN_MAX_CONCURRENT_ACTIVITIES` | Maximum number of activities that can run concurrently | `5` | Controls resource usage and prevents overwhelming target systems |
3131
| `ATLAN_HEARTBEAT_TIMEOUT_SECONDS` | Timeout duration for activity heartbeats (in seconds) | `300` | Detects stuck activities and enables recovery |
3232
| `ATLAN_START_TO_CLOSE_TIMEOUT_SECONDS` | Maximum duration an activity can run before timing out (in seconds) | `7200` | Prevents activities from running indefinitely |
33-
| `ATLAN_WORKFLOW_AUTH_ENABLED` | Whether to enable authentication for Temporal workflows | `false` | Used in production deployments with secure Temporal clusters |
33+
| `ATLAN_AUTH_ENABLED` | Whether to enable authentication for Temporal workflows | `false` | Used in production deployments with secure Temporal clusters |
3434
| `ATLAN_DEPLOYMENT_SECRET_PATH` | Path to deployment secrets in secret store | `ATLAN_DEPLOYMENT_SECRETS` | Contains authentication credentials for production |
3535

3636
## SQL Client Configuration
@@ -129,7 +129,7 @@ For local development, most defaults work out of the box. Key configurations to
129129

130130
### Production Deployment
131131
For production deployments, consider these essential configurations:
132-
- `ATLAN_WORKFLOW_AUTH_ENABLED=true`: Enable authentication for Temporal
132+
- `ATLAN_AUTH_ENABLED=true`: Enable authentication for Temporal
133133
- `ENABLE_ATLAN_UPLOAD=true`: Enable data upload to Atlan platform
134134
- `ATLAN_DEPLOYMENT_SECRETS`: Configure authentication secrets
135135
- `ATLAN_WORKFLOW_HOST` and `ATLAN_WORKFLOW_PORT`: Point to production Temporal cluster

0 commit comments

Comments
 (0)