Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions keepercommander/commands/discoveryrotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import re
import time
from datetime import datetime
from typing import Dict, Optional, Any, Set, List
from urllib.parse import urlparse, urlunparse
from typing import Optional, List

import requests
from keeper_secrets_manager_core.utils import url_safe_str_to_bytes
Expand Down Expand Up @@ -77,6 +77,7 @@
from .pam_debug.link import PAMDebugLinkCommand
from .pam_debug.rotation_setting import PAMDebugRotationSettingsCommand
from .pam_debug.vertex import PAMDebugVertexCommand
from .pam.cnapp_commands import PAMCnappCommand
from .pam_import.commands import PAMProjectCommand
from keepercommander.commands.pam_cloud.pam_privileged_access import PAMPrivilegedAccessCommand
from .pam_launch.launch import PAMLaunchCommand
Expand All @@ -93,7 +94,6 @@
from .pam_saas.config import PAMActionSaasConfigCommand
from .pam_saas.update import PAMActionSaasUpdateCommand
from .tunnel_and_connections import PAMTunnelCommand, PAMConnectionCommand, PAMRbiCommand, PAMSplitCommand
from .pam.cnapp_commands import PAMCnappCommand
from .universalsecretsync import (
PAMUniversalSyncConfigCommand,
PAMUniversalSyncRunCommand
Expand Down Expand Up @@ -288,10 +288,9 @@ def __init__(self):
self.register_command('workflow', PAMWorkflowCommand(), 'Manage PAM Workflows', 'w')
self.register_command('access', PAMPrivilegedAccessCommand(),
'Manage privileged cloud access operations', 'ac')
self.register_command('cnapp', PAMCnappCommand(),
'Manage Cloud-Native Application Protection Platform integration', 'cn')
self.register_command('universal-sync-config', PAMUniversalSyncConfigCommand(), 'Manage Universal Sync Configurations', 'usc')
self.register_command('universal-sync-run', PAMUniversalSyncRunCommand(), 'Run Universal Sync', 'usr')
self.register_command('cnapp', PAMCnappCommand(), 'Manage CNAPP integrations', 'cn')


class PAMGatewayCommand(GroupCommand):
Expand Down Expand Up @@ -2340,7 +2339,8 @@ def print_root_rotation_setting(params, is_verbose=False, format_type='table'):

for c in configurations: # type: vault.TypedRecord
if c.record_type in ('pamAwsConfiguration', 'pamAzureConfiguration', 'pamGcpConfiguration',
'pamDomainConfiguration', 'pamNetworkConfiguration', 'pamOciConfiguration'):
'pamDomainConfiguration', 'pamNetworkConfiguration', 'pamOciConfiguration',
'pamGitHubConfiguration'):
facade.record = c
shared_folder_parents = find_parent_top_folder(params, c.record_uid)
if shared_folder_parents:
Expand Down Expand Up @@ -2402,7 +2402,7 @@ def print_root_rotation_setting(params, is_verbose=False, format_type='table'):

common_parser = argparse.ArgumentParser(add_help=False)
common_parser.add_argument('--environment', '-env', dest='config_type', action='store',
choices=['local', 'aws', 'azure', 'gcp', 'domain', 'oci'], help='PAM Configuration Type')
choices=['local', 'aws', 'azure', 'gcp', 'domain', 'oci', 'github'], help='PAM Configuration Type')
common_parser.add_argument('--title', '-t', dest='title', action='store', help='Title of the PAM Configuration')
common_parser.add_argument('--gateway', '-g', dest='gateway_uid', action='store', help='Gateway UID or Name')
common_parser.add_argument('--shared-folder', '-sf', dest='shared_folder_uid', action='store',
Expand Down Expand Up @@ -2461,6 +2461,12 @@ def print_root_rotation_setting(params, is_verbose=False, format_type='table'):
help='Google Workspace Administrator Email Address')
gcp_group.add_argument('--gcp-region', dest='region_names', action='append', help='GCP Region Names')

github_group = common_parser.add_argument_group('github', 'GitHub configuration')
github_group.add_argument('--github-id', dest='github_id', action='store', help='GitHub Id')
github_group.add_argument('--personal-access-token', dest='personal_access_token', action='store',
help='Personal Access Token')
github_group.add_argument('--github-base-url', dest='github_base_url', action='store',
help='GitHub Base URL')

class PamConfigurationEditMixin(RecordEditMixin):
pam_record_types = None
Expand Down Expand Up @@ -2642,6 +2648,16 @@ def parse_properties(self, params, record, **kwargs): # type: (KeeperParams, va
if gcp_region:
regions = '\n'.join(gcp_region)
extra_properties.append(f'multiline.pamGcpRegionName={regions}')
elif record.record_type == 'pamGitHubConfiguration':
github_id = kwargs.get('github_id')
if github_id:
extra_properties.append(f'text.pamGitHubId={github_id}')
personal_access_token = kwargs.get('personal_access_token')
if personal_access_token:
extra_properties.append(f'secret.personalAccessToken={personal_access_token}')
github_base_url = kwargs.get('github_base_url')
if github_base_url:
extra_properties.append(f'text.pamGitHubBaseUrl={github_base_url}')
elif record.record_type == 'pamAzureConfiguration':
azure_id = kwargs.get('azure_id')
if azure_id:
Expand Down Expand Up @@ -2803,13 +2819,15 @@ def execute(self, params, **kwargs):
record_type = 'pamNetworkConfiguration'
elif config_type == 'gcp':
record_type = 'pamGcpConfiguration'
elif config_type == 'github':
record_type = 'pamGitHubConfiguration'
elif config_type == 'domain':
record_type = 'pamDomainConfiguration'
elif config_type == 'oci':
record_type = 'pamOciConfiguration'
else:
raise CommandError('pam-config-new', f'--environment {config_type} is not supported'
' - supported options: local, aws, azure, gcp, domain, oci')
' - supported options: local, aws, azure, gcp, domain, oci, github')

title = kwargs.get('title')
if not title:
Expand Down Expand Up @@ -2958,6 +2976,8 @@ def execute(self, params, **kwargs):
record_type = 'pamNetworkConfiguration'
elif config_type == 'gcp':
record_type = 'pamGcpConfiguration'
elif config_type == 'github':
record_type = 'pamGitHubConfiguration'
elif config_type == 'domain':
record_type = 'pamDomainConfiguration'
elif config_type == 'oci':
Expand Down
10 changes: 7 additions & 3 deletions unit-tests/test_command_enterprise_api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ def test_api_key_list_json_format(self):

self.assertEqual(len(TestEnterpriseApiKeys.expected_commands), 0)
self.assertIsNotNone(result)
# Compute the expected expiration date for the active token (matches mock: now + 365 days)
token4_expiry = datetime.datetime.now() + datetime.timedelta(days=365)
token4_expiry_str = token4_expiry.strftime('%Y-%m-%d %H:%M:%S')
# Assert that the JSON result matches the expected values for all entries
expected_json = [
{
Expand Down Expand Up @@ -100,7 +103,7 @@ def test_api_key_list_json_format(self):
"name": "SIEM Tool",
"status": "Active",
"issued_date": "2025-07-08 14:16:07",
"expiration_date": "2030-07-08 14:16:07",
"expiration_date": token4_expiry_str,
"integration": "SIEM:2"
},
{
Expand Down Expand Up @@ -661,13 +664,14 @@ def communicate_rest_success(params, request, path, rs_type=None):
integration5.apiIntegrationTypeName = "SIEM"
integration5.actionType = 2

# Token 53 - Active
# Token 53 - Active (expiration is always 1 year from now to keep tests passing over time)
token4 = rs.tokens.add()
token4.token = "active_token_53"
token4.name = "SIEM Tool"
token4.enterprise_id = 8560
token4.issuedDate = int(datetime.datetime(2025, 7, 8, 14, 16, 7).timestamp() * 1000)
token4.expirationDate = int(datetime.datetime(2030, 7, 8, 14, 16, 7).timestamp() * 1000)
token4_expiry = datetime.datetime.now() + datetime.timedelta(days=365)
token4.expirationDate = int(token4_expiry.timestamp() * 1000)
integration7 = token4.integrations.add()
integration7.roleName = "SIEM"
integration7.apiIntegrationTypeName = "SIEM"
Expand Down
Loading