@@ -28,6 +28,10 @@ class DestinationCreate(BaseModel):
2828 config_write_token : Optional [str ] = Field (None , description = "Config API token for reading and writing parsers" )
2929 powerquery_read_token : Optional [str ] = Field (None , description = "PowerQuery Log Read Access token for querying SIEM data" )
3030
31+ # S1 Management API
32+ s1_management_url : Optional [str ] = Field (None , description = "S1 Management API URL for asset lookups (e.g., https://demo.sentinelone.net)" )
33+ s1_api_token : Optional [str ] = Field (None , description = "S1 API Token for management API calls (Settings → Users → API Token)" )
34+
3135 # UAM Alert Ingest (Service Account)
3236 uam_ingest_url : Optional [str ] = Field (None , description = "UAM ingest URL (e.g., https://ingest.us1.sentinelone.net)" )
3337 uam_account_id : Optional [str ] = Field (None , description = "SentinelOne account ID for S1-Scope header" )
@@ -48,6 +52,8 @@ class DestinationUpdate(BaseModel):
4852 config_api_url : Optional [str ] = None
4953 config_write_token : Optional [str ] = None
5054 powerquery_read_token : Optional [str ] = None
55+ s1_management_url : Optional [str ] = None
56+ s1_api_token : Optional [str ] = None
5157 uam_ingest_url : Optional [str ] = None
5258 uam_account_id : Optional [str ] = None
5359 uam_site_id : Optional [str ] = None
@@ -72,6 +78,8 @@ class DestinationResponse(BaseModel):
7278 config_api_url : Optional [str ] = None # Config API URL for parser management
7379 has_config_write_token : Optional [bool ] = None # True if config API token is set
7480 has_powerquery_read_token : Optional [bool ] = None # True if PowerQuery read token is set
81+ s1_management_url : Optional [str ] = None # S1 Management API URL
82+ has_s1_api_token : Optional [bool ] = None # True if S1 API token is set
7583 uam_ingest_url : Optional [str ] = None # UAM ingest URL
7684 uam_account_id : Optional [str ] = None # SentinelOne account ID
7785 uam_site_id : Optional [str ] = None # Optional site ID
@@ -153,6 +161,8 @@ async def create_destination(
153161 config_api_url = destination .config_api_url ,
154162 config_write_token = destination .config_write_token ,
155163 powerquery_read_token = destination .powerquery_read_token ,
164+ s1_management_url = destination .s1_management_url ,
165+ s1_api_token = destination .s1_api_token ,
156166 uam_ingest_url = destination .uam_ingest_url ,
157167 uam_account_id = destination .uam_account_id ,
158168 uam_site_id = destination .uam_site_id ,
@@ -290,6 +300,8 @@ async def update_destination(
290300 config_api_url = update .config_api_url ,
291301 config_write_token = update .config_write_token ,
292302 powerquery_read_token = update .powerquery_read_token ,
303+ s1_management_url = update .s1_management_url ,
304+ s1_api_token = update .s1_api_token ,
293305 uam_ingest_url = update .uam_ingest_url ,
294306 uam_account_id = update .uam_account_id ,
295307 uam_site_id = update .uam_site_id ,
@@ -472,3 +484,55 @@ async def get_destination_uam_token(
472484 status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
473485 detail = "Failed to decrypt UAM token"
474486 )
487+
488+
489+ @router .get ("/{dest_id}/s1-api-token" )
490+ async def get_destination_s1_api_token (
491+ dest_id : str ,
492+ session : AsyncSession = Depends (get_session ),
493+ auth_info : tuple = Depends (get_api_key )
494+ ):
495+ """
496+ Get decrypted S1 API Token for a destination (internal use only)
497+
498+ Returns the decrypted S1 API token along with the management URL
499+ for making agent/asset lookups via the S1 management API
500+ """
501+ service = DestinationService (session )
502+ destination = await service .get_destination (dest_id )
503+ if not destination :
504+ raise HTTPException (
505+ status_code = status .HTTP_404_NOT_FOUND ,
506+ detail = f"Destination '{ dest_id } ' not found"
507+ )
508+
509+ if destination .type != 'hec' :
510+ raise HTTPException (
511+ status_code = status .HTTP_400_BAD_REQUEST ,
512+ detail = "Only HEC destinations have S1 API tokens"
513+ )
514+
515+ if not destination .s1_api_token_encrypted :
516+ raise HTTPException (
517+ status_code = status .HTTP_404_NOT_FOUND ,
518+ detail = "No S1 API Token found for this destination"
519+ )
520+
521+ if not destination .s1_management_url :
522+ raise HTTPException (
523+ status_code = status .HTTP_400_BAD_REQUEST ,
524+ detail = "No S1 Management URL configured for this destination"
525+ )
526+
527+ try :
528+ token = service .decrypt_token (destination .s1_api_token_encrypted )
529+ return {
530+ "token" : token ,
531+ "s1_management_url" : destination .s1_management_url ,
532+ }
533+ except Exception as e :
534+ logger .error (f"Failed to decrypt S1 API token: { e } " )
535+ raise HTTPException (
536+ status_code = status .HTTP_500_INTERNAL_SERVER_ERROR ,
537+ detail = "Failed to decrypt S1 API token"
538+ )
0 commit comments