diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 91ffad491..fddda979d 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -27,32 +27,39 @@ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } -# Function to run keeper service with intelligent lifecycle management +# Function to run keeper service with intelligent lifecycle management. +# Usage: run_keeper_service [args...] +# Arguments are passed through as an array so values containing spaces survive. run_keeper_service() { - local config_arg="$1" - local command_args="$2" - + local config_file="$1" + shift + + local config_args=() + if [[ -n "${config_file}" ]]; then + config_args=("--config" "${config_file}") + fi + # Check if command contains service-create - if [[ "$command_args" =~ service-create ]]; then + if [[ "$*" =~ service-create ]]; then log "Service command detected, checking service status..." - + # Get service status local service_status - service_status=$(python3 keeper.py ${config_arg} service-status 2>/dev/null || true) - + service_status=$(python3 keeper.py "${config_args[@]}" service-status 2>/dev/null || true) + if echo "${service_status}" | grep -q "Stopped"; then log "Service exists but is stopped, starting it..." - python3 keeper.py ${config_arg} service-start + python3 keeper.py "${config_args[@]}" service-start elif echo "${service_status}" | grep -q "Running"; then log "Service is already running, no action needed." else log "Service not found, creating new service..." - log "Running: python3 keeper.py ${config_arg} ${command_args}" - python3 keeper.py ${config_arg} ${command_args} + log "Running: python3 keeper.py ${config_args[*]} $*" + python3 keeper.py "${config_args[@]}" "$@" fi else # Not a service command, run as normal - python3 keeper.py ${config_arg} ${command_args} + python3 keeper.py "${config_args[@]}" "$@" fi } @@ -103,23 +110,28 @@ parse_credentials() { done } -# Filter out authentication arguments and return remaining command arguments +# Filter out authentication arguments, leaving the actual Commander command in +# the global COMMAND_ARGS array. An array rather than a string so that arguments +# containing spaces -- record titles, notes, search queries -- reach Commander +# as single arguments instead of being re-split by word splitting. filter_args() { - local filtered_args=() - + COMMAND_ARGS=() + while [[ $# -gt 0 ]]; do case $1 in --user|--password|--server|--ksm-config|--ksm-token|--record) - shift 2 # Skip argument and its value + # Skip the flag and its value, tolerating a missing value. + shift + if [[ $# -gt 0 ]]; then + shift + fi ;; *) - filtered_args+=("$1") + COMMAND_ARGS+=("$1") shift ;; esac done - - echo "${filtered_args[@]}" } @@ -221,6 +233,36 @@ download_config_from_ksm() { log "Config.json downloaded successfully from KSM record" } +# Upload config.json back to the KSM record once, without starting a monitor. +# Used after a one-shot command so refreshed device/session state is persisted +# before the container exits. Best effort: a failed upload must not mask the +# status of the command the user actually asked for. +upload_config_to_ksm() { + local ksm_config_path="$1" + local ksm_token="$2" + local record_uid="$3" + + if [[ ! -f "${CONFIG_FILE}" ]]; then + return 0 + fi + + local helper_args=("upload" "--record-uid" "${record_uid}" \ + "--config-file" "${CONFIG_FILE}") + + if [[ -n "${ksm_config_path}" ]]; then + helper_args+=("--ksm-config" "${ksm_config_path}") + elif [[ -n "${ksm_token}" ]]; then + helper_args+=("--ksm-token" "${ksm_token}") + else + return 0 + fi + + log "Syncing config.json back to KSM record: ${record_uid}" + if ! python3 docker_ksm_utility.py "${helper_args[@]}"; then + log "WARNING: Failed to sync config back to KSM record" + fi +} + # Start config.json monitoring and upload changes start_config_monitor() { local ksm_config_path="$1" @@ -275,6 +317,9 @@ stop_config_monitor() { # CLEANUP AND SIGNAL HANDLING # ============================================================================= +# PID of the idle process used by keep-alive mode, so signal handling can end it. +KEEP_ALIVE_PID="" + # Handle cleanup on exit cleanup_on_exit() { log "Performing cleanup on exit..." @@ -282,8 +327,93 @@ cleanup_on_exit() { log "Cleanup completed" } -# Set up exit trap for cleanup -trap cleanup_on_exit EXIT INT TERM +# Handle SIGTERM/SIGINT (e.g. `docker stop`) by ending the keep-alive wait and +# exiting, which runs the EXIT trap and therefore the cleanup above. +on_terminate() { + local signum="$1" + + log "Received signal, shutting down..." + if [[ -n "${KEEP_ALIVE_PID}" ]]; then + kill "${KEEP_ALIVE_PID}" 2>/dev/null || true + KEEP_ALIVE_PID="" + fi + + # Conventional shell status for death by signal. + exit $((128 + signum)) +} + +# Set up traps: cleanup always runs on exit, signals shut down deliberately. +trap cleanup_on_exit EXIT +trap 'on_terminate 15' TERM +trap 'on_terminate 2' INT + + +# ============================================================================= +# CONTAINER LIFECYCLE +# ============================================================================= + +# Decide whether the container should stay resident once setup is done. +# +# Persistent modes have to stay up: service mode leaves Commander running as a +# background daemon, and a bare `docker run` with no command is a request for a +# live container to exec into. A one-shot command is the opposite -- it must +# exit with its own status so the container behaves like the CLI it wraps and +# can be used in scripts and pipelines. Set KEEPER_KEEP_ALIVE=true to force the +# container to stay up regardless. +should_keep_alive() { + local flag + flag=$(printf '%s' "${KEEPER_KEEP_ALIVE:-}" | tr '[:upper:]' '[:lower:]') + case "${flag}" in + 1|true|yes) + return 0 + ;; + esac + + # No command given -- nothing to finish, so keep the container available. + if [[ ${#COMMAND_ARGS[@]} -eq 0 ]]; then + return 0 + fi + + # Service mode runs Commander as a daemon inside the container. + if [[ "${COMMAND_ARGS[*]}" =~ (service-create|service-start) ]]; then + return 0 + fi + + return 1 +} + +# Run the requested command (if any) and return its exit status. Uses +# `|| status=$?` so that `set -e` does not abort before we can report it. +run_command_args() { + local config_file="$1" + local status=0 + + if [[ ${#COMMAND_ARGS[@]} -gt 0 ]]; then + run_keeper_service "${config_file}" "${COMMAND_ARGS[@]}" || status=$? + fi + + return "${status}" +} + +# Final disposition: hold the container open for persistent modes, otherwise +# exit with the wrapped command's status. +finish() { + local status="${1:-0}" + + if should_keep_alive; then + log "Keeping container alive..." + # Idle in the background and `wait` rather than running `sleep infinity` + # in the foreground: bash defers trap handlers until a foreground child + # finishes, so a foreground sleep would make the container ignore + # SIGTERM entirely and force `docker stop` to fall back to SIGKILL + # without ever running cleanup. + sleep infinity & + KEEP_ALIVE_PID=$! + wait "${KEEP_ALIVE_PID}" || true + fi + + exit "${status}" +} # ============================================================================= @@ -331,40 +461,35 @@ if [[ (-n "${KSM_CONFIG}" || -n "${KSM_TOKEN}") && -n "${RECORD}" ]]; then # Download config.json from KSM record download_config_from_ksm "${KSM_CONFIG}" "${KSM_TOKEN}" "${RECORD}" - - # Start monitoring for config.json changes to upload back to KSM - start_config_monitor "${KSM_CONFIG}" "${KSM_TOKEN}" "${RECORD}" - + # Filter out KSM arguments from command args - COMMAND_ARGS=$(filter_args "$@") - - # Execute commands or keep container alive - if [[ -z "${COMMAND_ARGS}" ]]; then - log "No command arguments provided, keeping container alive..." - sleep infinity - else - # Run the service command with downloaded config file - run_keeper_service "--config ${CONFIG_FILE}" "${COMMAND_ARGS}" - log "Keeping container alive..." - sleep infinity + filter_args "$@" + + if should_keep_alive; then + # Persistent run: watch config.json and push changes back to the record + # for as long as the container lives. + start_config_monitor "${KSM_CONFIG}" "${KSM_TOKEN}" "${RECORD}" + status=0 + run_command_args "${CONFIG_FILE}" || status=$? + finish "${status}" fi + + # One-shot run: no monitor needed. Sync the config back once so refreshed + # device/session state is preserved, then exit with the command's status. + status=0 + run_command_args "${CONFIG_FILE}" || status=$? + upload_config_to_ksm "${KSM_CONFIG}" "${KSM_TOKEN}" "${RECORD}" + exit "${status}" # Check if config.json is mounted or available elif [[ -f "${CONFIG_FILE}" ]]; then log "Config file found at ${CONFIG_FILE}, using config-based authentication" - + # Filter out authentication arguments, keep the rest - COMMAND_ARGS=$(filter_args "$@") - - # Execute commands or keep container alive - if [[ -z "${COMMAND_ARGS}" ]]; then - log "No command arguments provided, keeping container alive..." - sleep infinity - else - # Run the service command with config file - run_keeper_service "--config ${CONFIG_FILE}" "${COMMAND_ARGS}" - log "Keeping container alive..." - sleep infinity - fi + filter_args "$@" + + status=0 + run_command_args "${CONFIG_FILE}" || status=$? + finish "${status}" # Check if user/password authentication is provided elif [[ -n "${USER}" && -n "${PASSWORD}" ]]; then log "No config file found, using user/password authentication" @@ -381,33 +506,21 @@ elif [[ -n "${USER}" && -n "${PASSWORD}" ]]; then setup_device "${USER}" "${PASSWORD}" "${SERVER}" # Filter out authentication arguments, keep the rest - COMMAND_ARGS=$(filter_args "$@") + filter_args "$@" - # Execute commands or keep container alive - if [[ -z "${COMMAND_ARGS}" ]]; then - log "Keeping container alive..." - sleep infinity - else - # Run the service command without credentials (device is now registered) - run_keeper_service "" "${COMMAND_ARGS}" - log "Keeping container alive..." - sleep infinity - fi + # Run without credentials -- the device is registered at this point. + status=0 + run_command_args "" || status=$? + finish "${status}" # Fallback: no authentication provided else log "No config file found and no user/password provided" - + # Filter out authentication arguments, keep the rest - COMMAND_ARGS=$(filter_args "$@") + filter_args "$@" - # Execute commands or keep container alive - if [[ -z "${COMMAND_ARGS}" ]]; then - log "Keeping container alive..." - sleep infinity - else - # Run the command directly without any authentication setup - run_keeper_service "" "${COMMAND_ARGS}" - log "Keeping container alive..." - sleep infinity - fi + # Run the command directly without any authentication setup + status=0 + run_command_args "" || status=$? + finish "${status}" fi \ No newline at end of file diff --git a/keeper-win-file.spec b/keeper-win-file.spec deleted file mode 100644 index 4f3cb975b..000000000 --- a/keeper-win-file.spec +++ /dev/null @@ -1,51 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - -block_cipher = None - - -a = Analysis(['keeper.py'], - pathex=['.'], - binaries=[], - datas=[(os.path.join('keepercommander', 'importer', 'keepass', 'template.kdbx'), - os.path.join('keepercommander', 'importer', 'keepass')), - ('public_suffix_list.dat', 'fido2'), - ], - hiddenimports=[ - 'fido2', - 'ldap3', - 'awscli', - 'msal', - 'json', - 'csv', - 'lxml', - 'pykeepass', - 'msvcrt', - 'keepercommander.importer.json', - 'keepercommander.importer.csv', - 'keepercommander.importer.keepass', - 'keepercommander.importer.lastpass', - 'keepercommander.plugins', - 'keepercommander.yubikey'], - hookspath=[], - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name='keeper', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True ) diff --git a/keeper_file.spec b/keeper_file.spec deleted file mode 100644 index dafee497d..000000000 --- a/keeper_file.spec +++ /dev/null @@ -1,43 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - -block_cipher = None - -import os - -a = Analysis(['keeper.py'], - pathex=['.'], - binaries=[], - datas=[(os.path.join('keepercommander', 'importer', 'keepass', 'template.kdbx'), - os.path.join('keepercommander', 'importer', 'keepass')), - ('public_suffix_list.dat', 'fido2'), - ], - hiddenimports=['keepercommander.importer.json', - 'keepercommander.importer.csv', - 'keepercommander.importer.keepass', - 'keepercommander.importer.lastpass', - 'keepercommander.plugins', - 'keepercommander.yubikey', - ], - hookspath=[], - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name='keeper', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=False, - upx_exclude=[], - runtime_tmpdir=None, - console=True ) diff --git a/keeper_folder.spec b/keeper_folder.spec deleted file mode 100644 index fe770231e..000000000 --- a/keeper_folder.spec +++ /dev/null @@ -1,47 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - -block_cipher = None - -import os - -a = Analysis(['keeper.py'], - pathex=['.'], - binaries=[], - datas=[(os.path.join('keepercommander', 'importer', 'keepass', 'template.kdbx'), - os.path.join('keepercommander', 'importer', 'keepass')), - ('public_suffix_list.dat', 'fido2'), - ], - hiddenimports=['keepercommander.importer.json', - 'keepercommander.importer.csv', - 'keepercommander.importer.keepass', - 'keepercommander.importer.lastpass', - 'keepercommander.plugins', - 'keepercommander.yubikey', - ], - hookspath=[], - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - [], - exclude_binaries=True, - name='keeper', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=False, - console=True) -coll = COLLECT(exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=True, - upx_exclude=[], - name='keeper') diff --git a/keepercommander/commands/discover/__init__.py b/keepercommander/commands/discover/__init__.py index 6ef3c4b10..5086dfce5 100644 --- a/keepercommander/commands/discover/__init__.py +++ b/keepercommander/commands/discover/__init__.py @@ -270,8 +270,11 @@ def from_gateway(params: KeeperParams, gateway: str, configuration_uid: str | No if application is None: logging.debug(f"cannot find application for gateway {gateway}, skipping.") - if (utils.base64_url_encode(found_gateway.controllerUid) == gateway or - found_gateway.controllerName.lower() == gateway.lower()): + # When --configuration-uid selected the config, trust that selection. + # Otherwise require the gateway name/UID to match. + if (configuration_uid is not None + or utils.base64_url_encode(found_gateway.controllerUid) == gateway + or found_gateway.controllerName.lower() == gateway.lower()): return GatewayContext( configuration=configuration_record, facade=configuration_facade, diff --git a/keepercommander/commands/discover/job_status.py b/keepercommander/commands/discover/job_status.py index 25450489a..fe04492c1 100644 --- a/keepercommander/commands/discover/job_status.py +++ b/keepercommander/commands/discover/job_status.py @@ -1,5 +1,6 @@ from __future__ import annotations import argparse +import json import logging from . import PAMGatewayActionDiscoverCommandBase, GatewayContext from ..pam.router_helper import router_get_connected_gateways @@ -48,6 +49,8 @@ class PAMGatewayActionDiscoverJobStatusCommand(PAMGatewayActionDiscoverCommandBa help='Show history') parser.add_argument('--configuration-uid', '-c', required=False, dest='configuration_uid', action='store', help='PAM configuration UID is using --history') + parser.add_argument('--format', dest='format', action='store', choices=['table', 'json'], + default='table', help='Output format (table, json)') def get_parser(self): return PAMGatewayActionDiscoverJobStatusCommand.parser @@ -155,9 +158,9 @@ def print_job_table(jobs: List[Dict], print("") @staticmethod - def print_job_detail(params: KeeperParams, - all_gateways: List, - job_id: str): + def _build_job_detail(params: KeeperParams, + all_gateways: List, + job_id: str) -> Optional[Dict]: def _find_job(configuration_record) -> Optional[Dict]: jobs_obj = Jobs(record=configuration_record, params=params) @@ -172,96 +175,162 @@ def _find_job(configuration_record) -> Optional[Dict]: find_func=_find_job, gateways=all_gateways) - if gateway_context is not None: - jobs = payload["jobs"] - job = jobs.get_job(job_id) # type: JobItem - infra = Infrastructure(record=gateway_context.configuration, params=params) - - color = bcolors.OKBLUE - status = "RUNNING" - if job.end_ts is not None and not job.error: - if job.success is None: - color = bcolors.WHITE - status = "CANCELLED" + if gateway_context is None: + return None + + jobs = payload["jobs"] + job = jobs.get_job(job_id) + infra = Infrastructure(record=gateway_context.configuration, params=params) + + status = "RUNNING" + if job.end_ts is not None and not job.error: + if job.success is None: + status = "CANCELLED" + else: + status = "COMPLETE" + elif job.error: + status = "FAILED" + + detail = { + "job_id": job.job_id, + "sync_point": job.sync_point, + "gateway": gateway_context.gateway_name, + "gateway_uid": gateway_context.gateway_uid, + "configuration_uid": gateway_context.configuration_uid, + "status": status, + "resource_uid": job.resource_uid or "", + "started": job.start_ts_str or "", + "completed": job.end_ts_str or "", + "duration": job.duration_sec_str or "", + } + + if status == "FAILED": + detail["error"] = job.error + detail["stacktrace"] = job.stacktrace + elif job.end_ts is not None: + try: + infra.load(sync_point=0) + delta_json = job.delta + if delta_json is not None: + delta = DiscoveryDelta.model_validate(delta_json) + added = [] + for item in delta.added: + vertex = infra.dag.get_vertex(item.uid) + if vertex is None or vertex.active is False or vertex.has_data is False: + logging.debug("added: vertex is none, inactive or has no data") + continue + discovery_object = DiscoveryObject.get_discovery_object(vertex) + added.append({ + "uid": item.uid, + "description": discovery_object.description, + }) + + changed = [] + for item in delta.changed: + vertex = infra.dag.get_vertex(item.uid) + if vertex is None or vertex.active is False or vertex.has_data is False: + logging.debug("changed: vertex is none, inactive or has no data") + continue + discovery_object = DiscoveryObject.get_discovery_object(vertex) + changed.append({ + "uid": item.uid, + "description": discovery_object.description, + "changes": item.changes, + }) + + deleted = [{"uid": item.uid} for item in delta.deleted] + detail["delta"] = { + "added": added, + "changed": changed, + "deleted": deleted, + } else: - color = bcolors.OKGREEN - status = "COMPLETE" - elif job.error: - color = bcolors.FAIL - status = "FAILED" + detail["delta"] = None + detail["message"] = "There are no available delta changes for this job." + except Exception as err: + detail["delta_error"] = str(err) + try: + dag = DAG(conn=infra.conn, record=infra.record, + graph_id=PamGraphId.INFRASTRUCTURE) + detail["raw_graph_dot"] = dag.to_dot_raw(sync_point=job.sync_point, rank_dir="RL") + except Exception: + pass - color_status = f"{color}{status}{bcolors.ENDC}" + return detail + @staticmethod + def print_job_detail(params: KeeperParams, + all_gateways: List, + job_id: str): + + detail = PAMGatewayActionDiscoverJobStatusCommand._build_job_detail( + params=params, all_gateways=all_gateways, job_id=job_id) + + if detail is None: + print(f"{bcolors.FAIL}Could not find the gateway with job {job_id}.") + return + + status = detail["status"] + color = bcolors.OKBLUE + if status == "CANCELLED": + color = bcolors.WHITE + elif status == "COMPLETE": + color = bcolors.OKGREEN + elif status == "FAILED": + color = bcolors.FAIL + + color_status = f"{color}{status}{bcolors.ENDC}" + + print("") + print(f"{_h('Job ID')}: {detail['job_id']}") + print(f"{_h('Sync Point')}: {detail['sync_point']}") + print(f"{_h('Gateway Name')}: {detail['gateway']}") + print(f"{_h('Gateway UID')}: {detail['gateway_uid']}") + print(f"{_h('Configuration UID')}: {detail['configuration_uid']}") + print(f"{_h('Status')}: {color_status}") + print(f"{_h('Resource UID')}: {detail['resource_uid'] or 'NA'}") + print(f"{_h('Started')}: {detail['started']}") + print(f"{_h('Completed')}: {detail['completed']}") + print(f"{_h('Duration')}: {detail['duration']}") + + if status == "FAILED": print("") - print(f"{_h('Job ID')}: {job.job_id}") - print(f"{_h('Sync Point')}: {job.sync_point}") - print(f"{_h('Gateway Name')}: {gateway_context.gateway_name}") - print(f"{_h('Gateway UID')}: {gateway_context.gateway_uid}") - print(f"{_h('Configuration UID')}: {gateway_context.configuration_uid}") - print(f"{_h('Status')}: {color_status}") - print(f"{_h('Resource UID')}: {job.resource_uid or 'NA'}") - print(f"{_h('Started')}: {job.start_ts_str}") - print(f"{_h('Completed')}: {job.end_ts_str}") - print(f"{_h('Duration')}: {job.duration_sec_str}") - - # If it failed, show the error and stacktrace. - if status == "FAILED": + print(f"{_h('Gateway Error')}:") + print(f"{color}{detail.get('error')}{bcolors.ENDC}") + print("") + print(f"{_h('Gateway Stacktrace')}:") + print(f"{color}{detail.get('stacktrace')}{bcolors.ENDC}") + elif detail.get("completed"): + if detail.get("delta_error"): + print(f"{_f('Could not load delta from infrastructure: ' + detail['delta_error'])}") + if detail.get("raw_graph_dot"): + print("Fall back to raw graph.") + print("") + print(detail["raw_graph_dot"]) + elif detail.get("delta") is None: print("") - print(f"{_h('Gateway Error')}:") - print(f"{color}{job.error}{bcolors.ENDC}") + print(f"{_f(detail.get('message') or 'There are no available delta changes for this job.')}") + else: + delta = detail["delta"] print("") - print(f"{_h('Gateway Stacktrace')}:") - print(f"{color}{job.stacktrace}{bcolors.ENDC}") - # If it finished, show information about what was discovered. - elif job.end_ts is not None: + print(f"{_h('Added')} - {len(delta['added'])} count") + for item in delta["added"]: + print(f" * {item['description']}") - try: - infra.load(sync_point=0) - print("") - delta_json = job.delta - if delta_json is not None: - delta = DiscoveryDelta.model_validate(delta_json) - print(f"{_h('Added')} - {len(delta.added)} count") - for item in delta.added: - vertex = infra.dag.get_vertex(item.uid) - if vertex is None or vertex.active is False or vertex.has_data is False: - logging.debug("added: vertex is none, inactive or has no data") - continue - discovery_object = DiscoveryObject.get_discovery_object(vertex) - print(f" * {discovery_object.description}") - - print("") - print(f"{_h('Changed')} - {len(delta.changed)} count") - for item in delta.changed: - vertex = infra.dag.get_vertex(item.uid) - if vertex is None or vertex.active is False or vertex.has_data is False: - logging.debug("changed: vertex is none, inactive or has no data") - continue - discovery_object = DiscoveryObject.get_discovery_object(vertex) - print(f" * {discovery_object.description}") - if item.changes is None: - print(" no changed, may be a object not added in prior discoveries.") - else: - for key, value in item.changes.items(): - print(f" - {key} = {value}") - - print("") - print(f"{_h('Deleted')} - {len(delta.deleted)} count") - for item in delta.deleted: - print(f" * discovery vertex {item.uid}") + print("") + print(f"{_h('Changed')} - {len(delta['changed'])} count") + for item in delta["changed"]: + print(f" * {item['description']}") + if item.get("changes") is None: + print(" no changed, may be a object not added in prior discoveries.") else: - print(f"{_f('There are no available delta changes for this job.')}") + for key, value in item["changes"].items(): + print(f" - {key} = {value}") - except Exception as err: - print(f"{_f('Could not load delta from infrastructure: ' + str(err))}") - print("Fall back to raw graph.") - print("") - dag = DAG(conn=infra.conn, record=infra.record, - graph_id=PamGraphId.INFRASTRUCTURE) - print(dag.to_dot_raw(sync_point=job.sync_point, rank_dir="RL")) - - else: - print(f"{bcolors.FAIL}Could not find the gateway with job {job_id}.") + print("") + print(f"{_h('Deleted')} - {len(delta['deleted'])} count") + for item in delta["deleted"]: + print(f" * discovery vertex {item['uid']}") def execute(self, params, **kwargs): @@ -277,6 +346,7 @@ def execute(self, params, **kwargs): # Show the history for the gateway. # gateway_filter needs to be set for show_history = kwargs.get("show_history") + format_type = kwargs.get("format") or "table" # Get all the gateways here so we don't have to keep calling this method. # It gets passed into find_gateway, and find_gateway will pass it around. @@ -292,6 +362,19 @@ def execute(self, params, **kwargs): # If we have a job id, only display information about the one job if job_id: + if format_type == "json": + detail = self._build_job_detail(params=params, + all_gateways=all_gateways, + job_id=job_id) + if detail is None: + print(json.dumps({ + "job": None, + "message": f"Could not find the gateway with job {job_id}." + }, indent=2)) + else: + print(json.dumps({"job": detail}, indent=2)) + return + self.print_job_detail(params=params, all_gateways=all_gateways, job_id=job_id) @@ -301,6 +384,7 @@ def execute(self, params, **kwargs): # Based on parameters set by user, select specific jobs to be displayed. selected_jobs = [] # type: List[Dict] + json_jobs = [] # type: List[Dict] # For each configuration/ gateway, we are going to get all jobs. # We are going to query the gateway for any updated status. @@ -357,10 +441,29 @@ def execute(self, params, **kwargs): job["status"] = "FAILED" selected_jobs.append(job) + json_jobs.append({ + "job_id": job_item.job_id, + "gateway": gateway_context.gateway_name, + "gateway_uid": gateway_context.gateway_uid, + "configuration_uid": gateway_context.configuration_uid, + "status": job["status"], + "resource_uid": getattr(job_item, "resource_uid", None) or "", + "started": job_item.start_ts_str if job_item.start_ts is not None else "", + "completed": job_item.end_ts_str if job_item.end_ts is not None else "", + "duration": job_item.duration_sec_str, + }) if len(selected_jobs) == 0: - print(f"{bcolors.FAIL}There are no discovery jobs. Use 'pam action discover start' to start a " - f"discovery job.{bcolors.ENDC}") + message = ("There are no discovery jobs. Use 'pam action discover start' to start a " + "discovery job.") + if format_type == "json": + print(json.dumps({"jobs": [], "message": message}, indent=2)) + else: + print(f"{bcolors.FAIL}{message}{bcolors.ENDC}") + return + + if format_type == "json": + print(json.dumps({"jobs": json_jobs}, indent=2)) return self.print_job_table(jobs=selected_jobs, diff --git a/keepercommander/commands/discover/result_process.py b/keepercommander/commands/discover/result_process.py index d221aab20..e7e5d474d 100644 --- a/keepercommander/commands/discover/result_process.py +++ b/keepercommander/commands/discover/result_process.py @@ -34,7 +34,6 @@ from ...params import KeeperParams from ...vault import TypedRecord, KeeperRecord from ...keeper_dag.vertex import DAGVertex - from ...discovery_common.record_link import RecordLink def _h(value: str) -> str: @@ -1662,6 +1661,7 @@ def execute(self, params: KeeperParams, **kwargs): process = Process( record=configuration_record, job_id=job_item.job_id, + record_lookup_func=self._record_lookup, params=params, logger=logging, debug_level=debug_level, @@ -1674,9 +1674,6 @@ def execute(self, params: KeeperParams, **kwargs): try: results = process.run( - # This method can get a record using the record UID - record_lookup_func=self._record_lookup, - # Prompt user the about adding records prompt_func=self._prompt, diff --git a/keepercommander/commands/discover/rule_list.py b/keepercommander/commands/discover/rule_list.py index 9819fc361..139d8e26d 100644 --- a/keepercommander/commands/discover/rule_list.py +++ b/keepercommander/commands/discover/rule_list.py @@ -1,5 +1,6 @@ from __future__ import annotations import argparse +import json from . import PAMGatewayActionDiscoverCommandBase, GatewayContext, MultiConfigurationException, multi_conf_msg from ...display import bcolors from ..pam.router_helper import router_get_connected_gateways @@ -20,10 +21,29 @@ class PAMGatewayActionDiscoverRuleListCommand(PAMGatewayActionDiscoverCommandBas parser.add_argument('--search', '-s', required=False, dest='search', action='store', help='Search for rules.') + parser.add_argument('--format', dest='format', action='store', choices=['table', 'json'], + default='table', help='Output format (table, json)') def get_parser(self): return PAMGatewayActionDiscoverRuleListCommand.parser + @staticmethod + def _rule_to_dict(rule: RuleItem): + action_value = None + if getattr(rule, 'action', None) is not None: + action_value = rule.action.value + return { + 'rule_id': rule.rule_id, + 'name': rule.name or '', + 'action': action_value, + 'priority': rule.priority, + 'case_sensitive': bool(rule.case_sensitive), + 'added': rule.added_ts_str if rule.added_ts else '', + 'shared_folder_uid': getattr(rule, 'shared_folder_uid', None) or '', + 'admin_uid': getattr(rule, 'admin_uid', None) or '', + 'rule': Rules.make_action_rule_statement_str(rule.statement), + } + @staticmethod def print_rule_table(rule_list: List[RuleItem]): @@ -90,21 +110,43 @@ def execute(self, params, **kwargs): gateway = kwargs.get("gateway") configuration_uid = kwargs.get('configuration_uid') + format_type = kwargs.get('format') or 'table' try: gateway_context = GatewayContext.from_gateway(params=params, gateway=gateway, configuration_uid=configuration_uid) if gateway_context is None: - print(f"{bcolors.FAIL}Could not find the gateway configuration for {gateway}.{bcolors.ENDC}") + message = f'Could not find the gateway configuration for {gateway}.' + if format_type == 'json': + print(json.dumps({'message': message}, indent=2)) + else: + print(f"{bcolors.FAIL}{message}{bcolors.ENDC}") return except MultiConfigurationException as err: - multi_conf_msg(gateway, err) + if format_type == 'json': + configs = [] + for item in (err.items or []): + record = item.get('configuration_record') + if record is not None: + configs.append({ + 'uid': getattr(record, 'record_uid', ''), + 'title': getattr(record, 'title', ''), + }) + print(json.dumps({ + 'message': f'Found multiple configuration records for gateway {gateway}.', + 'configurations': configs, + }, indent=2)) + else: + multi_conf_msg(gateway, err) return rules = Rules(record=gateway_context.configuration, params=params) rule_list = rules.rule_list(rule_type=RuleTypeEnum.ACTION, search=kwargs.get("search")) # type: List[RuleItem] if len(rule_list) == 0: + if format_type == 'json': + print(json.dumps({'rules': []}, indent=2)) + return print("") text = f"{bcolors.FAIL}There are no rules. " \ f"Use 'pam action discover rule add -g {gateway_context.gateway_uid} " @@ -114,4 +156,13 @@ def execute(self, params, **kwargs): print(text) return + if format_type == 'json': + print(json.dumps({ + 'gateway': gateway_context.gateway_name, + 'gateway_uid': gateway_context.gateway_uid, + 'configuration_uid': gateway_context.configuration_uid, + 'rules': [self._rule_to_dict(rule) for rule in rule_list], + }, indent=2)) + return + self.print_rule_table(rule_list=rule_list) diff --git a/keepercommander/commands/discoveryrotation.py b/keepercommander/commands/discoveryrotation.py index 8e8aaa5af..29a2797d7 100644 --- a/keepercommander/commands/discoveryrotation.py +++ b/keepercommander/commands/discoveryrotation.py @@ -1683,6 +1683,8 @@ class PAMListRecordRotationCommand(Command): parser = argparse.ArgumentParser(prog='pam rotation list') parser.add_argument('--verbose', '-v', required=False, default=False, dest='is_verbose', action='store_true', help='Verbose output') + parser.add_argument('--format', dest='format', action='store', choices=['table', 'json'], default='table', + help='Output format (table, json)') def get_parser(self): return PAMListRecordRotationCommand.parser @@ -1690,6 +1692,7 @@ def get_parser(self): def execute(self, params, **kwargs): is_verbose = kwargs.get('is_verbose') + format_type = kwargs.get('format') or 'table' rq = pam_pb2.PAMGenericUidsRequest() schedules_proto = router_get_rotation_schedules(params, rq) @@ -1708,19 +1711,27 @@ def execute(self, params, **kwargs): all_pam_config_records = pam_configurations_get_all(params) table = [] - headers = [] - headers.append('Record UID') - headers.append('Record Title') - headers.append('Record Type') - headers.append('Schedule') + if format_type == 'json': + headers = ['record_uid', 'record_title', 'record_type', 'schedule', 'gateway'] + if is_verbose: + headers.append('gateway_uid') + headers.append('pam_configuration') + if is_verbose: + headers.append('pam_configuration_uid') + else: + headers = [] + headers.append('Record UID') + headers.append('Record Title') + headers.append('Record Type') + headers.append('Schedule') - headers.append('Gateway') - if is_verbose: - headers.append('Gateway UID') + headers.append('Gateway') + if is_verbose: + headers.append('Gateway UID') - headers.append('PAM Configuration (Type)') - if is_verbose: - headers.append('PAM Configuration UID') + headers.append('PAM Configuration (Type)') + if is_verbose: + headers.append('PAM Configuration UID') for s in schedules: row = [] @@ -1737,12 +1748,11 @@ def execute(self, params, **kwargs): is_controller_online = any( (poc for poc in enterprise_controllers_connected_uids_bytes if poc == controller_uid)) - row_color = '' if record_exists_in_vault(params, record_uid): - row_color = bcolors.HIGHINTENSITYWHITE + record_accessible = True record_title, record_type = get_vault_record_title_type(params, record_uid) else: - row_color = bcolors.WHITE + record_accessible = False record_title = '[record inaccessible]' record_type = '[record inaccessible]' @@ -1750,9 +1760,15 @@ def execute(self, params, **kwargs): # only pamUser records are supported for rotation continue - row.append(f'{row_color}{record_uid}') - row.append(record_title or '[untitled]') - row.append(record_type or '[unknown]') + if format_type == 'json': + row.append(record_uid) + row.append(record_title or '[untitled]') + row.append(record_type or '[unknown]') + else: + row_color = bcolors.HIGHINTENSITYWHITE if record_accessible else bcolors.WHITE + row.append(f'{row_color}{record_uid}') + row.append(record_title or '[untitled]') + row.append(record_type or '[unknown]') if s.noSchedule is True: # Per Sergey A: @@ -1769,9 +1785,15 @@ def execute(self, params, **kwargs): else: schedule_str = s.scheduleData else: - schedule_str = f'{bcolors.FAIL}[empty]' + schedule_str = '[empty]' - row.append(f'{schedule_str}') + if format_type == 'json': + row.append(schedule_str) + else: + if schedule_str == '[empty]': + row.append(f'{bcolors.FAIL}[empty]') + else: + row.append(f'{schedule_str}') # Controller Info connected_controller = None @@ -1780,29 +1802,45 @@ def execute(self, params, **kwargs): list(enterprise_controllers_connected_resp.controllers)} connected_controller = router_controllers.get(controller_details.controllerUid) - if connected_controller: - controller_stat_color = bcolors.OKGREEN + if format_type == 'json': + if controller_details: + row.append(controller_details.controllerName) + else: + row.append('[Does not exist]') + if is_verbose: + row.append(utils.base64_url_encode(controller_uid)) else: - controller_stat_color = bcolors.WHITE + if connected_controller: + controller_stat_color = bcolors.OKGREEN + else: + controller_stat_color = bcolors.WHITE - controller_color = bcolors.WHITE - if is_controller_online: - controller_color = bcolors.OKGREEN + controller_color = bcolors.WHITE + if is_controller_online: + controller_color = bcolors.OKGREEN - if controller_details: - row.append(f'{controller_stat_color}{controller_details.controllerName}{bcolors.ENDC}') - else: - row.append(f'{controller_stat_color}[Does not exist]{bcolors.ENDC}') + if controller_details: + row.append(f'{controller_stat_color}{controller_details.controllerName}{bcolors.ENDC}') + else: + row.append(f'{controller_stat_color}[Does not exist]{bcolors.ENDC}') - if is_verbose: - row.append(f'{controller_color}{utils.base64_url_encode(controller_uid)}{bcolors.ENDC}') + if is_verbose: + row.append(f'{controller_color}{utils.base64_url_encode(controller_uid)}{bcolors.ENDC}') if not pam_configuration: - if not is_verbose: - row.append(f"{bcolors.FAIL}[No config found]{bcolors.ENDC}") + if format_type == 'json': + if not is_verbose: + row.append('[No config found]') + else: + row.append( + f'[No config found. Looks like configuration {configuration_uid_str} was removed ' + f'but rotation schedule was not modified]') else: - row.append( - f"{bcolors.FAIL}[No config found. Looks like configuration {configuration_uid_str} was removed but rotation schedule was not modified{bcolors.ENDC}") + if not is_verbose: + row.append(f"{bcolors.FAIL}[No config found]{bcolors.ENDC}") + else: + row.append( + f"{bcolors.FAIL}[No config found. Looks like configuration {configuration_uid_str} was removed but rotation schedule was not modified{bcolors.ENDC}") else: pam_config_name, pam_config_type = get_vault_record_title_type(params, configuration_uid_str) @@ -1826,13 +1864,19 @@ def execute(self, params, **kwargs): row.append(f"{pam_config_name or '[untitled]'} ({pam_config_type or '[unknown]'})") if is_verbose: - row.append(f'{utils.base64_url_encode(configuration_uid)}{bcolors.ENDC}') + if format_type == 'json': + row.append(utils.base64_url_encode(configuration_uid)) + else: + row.append(f'{utils.base64_url_encode(configuration_uid)}{bcolors.ENDC}') table.append(row) table.sort(key=lambda x: (x[1] or '')) - dump_report_data(table, headers, fmt='table', filename="", row_number=False, column_width=None) + report = dump_report_data(table, headers, fmt=format_type, filename="", + row_number=False, column_width=None) + if format_type == 'json': + return report print(f"\n{bcolors.OKBLUE}----------------------------------------------------------{bcolors.ENDC}") print(f"{bcolors.OKBLUE}Example to rotate record to which this user has access to:{bcolors.ENDC}") @@ -2522,6 +2566,7 @@ class PamConfigurationEditMixin(RecordEditMixin): PAM_CONFIG_RECORD_TYPES = frozenset({ 'pamAwsConfiguration', 'pamAzureConfiguration', 'pamGcpConfiguration', 'pamDomainConfiguration', 'pamNetworkConfiguration', 'pamOciConfiguration', + 'pamGitHubConfiguration', }) PAM_RESOURCE_RECORD_TYPES = frozenset({ 'pamDatabase', 'pamDirectory', 'pamMachine', 'pamRemoteBrowser', @@ -2714,7 +2759,7 @@ def parse_properties(self, params, record, **kwargs): # type: (KeeperParams, va 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}') + extra_properties.append(f'secret.pamGitHubPersonalAccessToken={personal_access_token}') github_base_url = kwargs.get('github_base_url') if github_base_url: extra_properties.append(f'text.pamGitHubBaseUrl={github_base_url}') @@ -2826,7 +2871,8 @@ def parse_properties(self, params, record, **kwargs): # type: (KeeperParams, va # Fields that the backend previously required but now treats as optional for pamAzureConfiguration. AZURE_OPTIONAL_FIELDS = frozenset({'clientId', 'clientSecret'}) - def verify_required(self, record): # type: (vault.TypedRecord) -> None + def verify_required(self, record, command=''): # type: (vault.TypedRecord, str) -> None + missing_fields = [] for field in record.fields: if field.required: if len(field.value) == 0: @@ -2838,10 +2884,15 @@ def verify_required(self, record): # type: (vault.TypedRecord) -> None and field.label in self.AZURE_OPTIONAL_FIELDS): pass else: - self.warnings.append(f'Empty required field: "{field.get_field_name()}"') + missing_fields.append(field.get_field_name()) for custom in record.custom: if custom.required: custom.required = False + if missing_fields: + if len(missing_fields) == 1: + raise CommandError(command, f'Empty required field: "{missing_fields[0]}"') + fields_text = ', '.join(f'"{x}"' for x in missing_fields) + raise CommandError(command, f'Empty required fields: {fields_text}') class PAMConfigurationNewCommand(Command, PamConfigurationEditMixin): @@ -2942,7 +2993,7 @@ def execute(self, params, **kwargs): if not gateway_uid: logging.warning(f'Gateway "{gw_name}" not found.') - self.verify_required(record) + self.verify_required(record, command='pam-config-new') create_pam_configuration_in_folder(params, record, shared_folder_uid, command='pam-config-new') @@ -3075,7 +3126,7 @@ def execute(self, params, **kwargs): orig_admin_cred_ref = value.get('adminCredentialRef') or '' self.parse_properties(params, configuration, config_edit=True, **kwargs) - self.verify_required(configuration) + self.verify_required(configuration, command='pam-config-edit') update_pam_record(params, configuration, command='pam-config-edit') @@ -4217,13 +4268,18 @@ class PAMGatewayActionServerInfoCommand(Command): parser = argparse.ArgumentParser(prog='dr-info-command') parser.add_argument('--gateway', '-g', required=False, dest='gateway_uid', action='store', help='Gateway UID') parser.add_argument('--verbose', '-v', required=False, dest='verbose', action='store_true', help='Verbose Output') + parser.add_argument('--format', dest='format', action='store', choices=['text', 'json'], + default='text', help='Output format (text, json)') def get_parser(self): return PAMGatewayActionServerInfoCommand.parser def execute(self, params, **kwargs): + from .pam.router_helper import get_response_payload + destination_gateway_uid_str = kwargs.get('gateway_uid') is_verbose = kwargs.get('verbose') + format_type = kwargs.get('format') or 'text' router_response = router_send_action_to_gateway( params=params, gateway_action=GatewayActionGatewayInfo(is_scheduled=False), @@ -4232,6 +4288,23 @@ def execute(self, params, **kwargs): destination_gateway_uid_str=destination_gateway_uid_str ) + if format_type == 'json': + if not router_response: + print(json.dumps({"gateway_info": None, "message": "No response from gateway."}, indent=2)) + return + payload = get_response_payload(router_response) + if not (payload.get('is_ok') or payload.get('isOk')): + print(json.dumps({"ok": False, "response": payload}, indent=2)) + return + result = { + "ok": True, + "gateway_info": payload.get('data'), + } + if payload.get('warnings'): + result['warnings'] = payload.get('warnings') + print(json.dumps(result, indent=2)) + return + print_router_response(router_response, 'gateway_info', is_verbose=is_verbose, gateway_uid=destination_gateway_uid_str) diff --git a/keepercommander/commands/discoveryrotation_v1.py b/keepercommander/commands/discoveryrotation_v1.py index cadd5f0c9..8d2d21502 100644 --- a/keepercommander/commands/discoveryrotation_v1.py +++ b/keepercommander/commands/discoveryrotation_v1.py @@ -970,7 +970,8 @@ def parse_properties(self, params, record, **kwargs): # type: (KeeperParams, va if extra_properties: self.assign_typed_fields(record, [RecordEditMixin.parse_field(x) for x in extra_properties]) - def verify_required(self, record): # type: (vault.TypedRecord) -> None + def verify_required(self, record, command=''): # type: (vault.TypedRecord, str) -> None + missing_fields = [] for field in record.fields: if field.required: if len(field.value) == 0: @@ -981,10 +982,15 @@ def verify_required(self, record): # type: (vault.TypedRecord) -> None 'tz': 'Etc/UTC', }] else: - self.warnings.append(f'Empty required field: "{field.get_field_name()}"') + missing_fields.append(field.get_field_name()) for custom in record.custom: if custom.required: custom.required = False + if missing_fields: + if len(missing_fields) == 1: + raise CommandError(command, f'Empty required field: "{missing_fields[0]}"') + fields_text = ', '.join(f'"{x}"' for x in missing_fields) + raise CommandError(command, f'Empty required fields: {fields_text}') class PAMConfigurationNewCommand(Command, PamConfigurationEditMixin): @@ -1037,7 +1043,7 @@ def execute(self, params, **kwargs): if not shared_folder_uid: raise CommandError('pam-config-new', '--shared_folder parameter is required to create a PAM configuration') - self.verify_required(record) + self.verify_required(record, command='pam-config-new') pam_configuration_create_record_v6(params, record, shared_folder_uid) @@ -1126,7 +1132,7 @@ def execute(self, params, **kwargs): orig_shared_folder_uid = value.get('folderUid') or '' self.parse_properties(params, configuration, **kwargs) - self.verify_required(configuration) + self.verify_required(configuration, command='pam-config-edit') record_management.update_record(params, configuration) diff --git a/keepercommander/commands/ksm.py b/keepercommander/commands/ksm.py index 45d0aa61c..a80c5ad05 100644 --- a/keepercommander/commands/ksm.py +++ b/keepercommander/commands/ksm.py @@ -1757,7 +1757,7 @@ def add_new_v5_app(params, app_name, force_to_add=False, format_type='table'): logging.debug("Creating new KSM Application named '%s'" % app_name) found_app = KSMCommand.get_app_record(params, app_name) - if (found_app is not None) and (found_app is not force_to_add): + if found_app is not None and not force_to_add: if format_type == 'json': return json.dumps({"error": f'Application with the same name "{app_name}" already exists.'}) else: diff --git a/keepercommander/commands/nested_share_folder/helpers.py b/keepercommander/commands/nested_share_folder/helpers.py index 00ad50152..5b2718375 100644 --- a/keepercommander/commands/nested_share_folder/helpers.py +++ b/keepercommander/commands/nested_share_folder/helpers.py @@ -265,13 +265,16 @@ def classify_share_recipient(params, recipient): (cap of 500 entries), ``params.available_team_cache``, and ``resolve_team_identifier`` (``team_cache``). A match by team name *or* team UID returns ``('team', team_uid_b64)``. - 3. No match → logs the same warning as legacy and returns ``None``. - 4. Multiple matches → logs the same warning and returns ``None``. + 3. Unresolved Keeper UID-shaped strings are treated as account UIDs + (``('user', uid)``) — never as teams. + 4. Unresolved display names → logs a warning and returns ``None`` + (do not send them to ``team_get_keys``). + 5. Multiple team name matches → logs a warning and returns ``None``. Returns ``(kind, identifier)`` or ``None``. """ from ... import constants, api - from ...nested_share_folder.common import resolve_team_identifier + from ...nested_share_folder.common import is_keeper_uid, resolve_team_identifier if re.match(constants.EMAIL_PATTERN, recipient): return 'user', recipient.lower() @@ -296,16 +299,24 @@ def classify_share_recipient(params, recipient): if len(matches) == 1: return 'team', matches[0] - if not matches: - resolved_team = resolve_team_identifier(params, recipient) - if resolved_team: - return 'team', resolved_team[0] - logger.warning('User "%s" could not be resolved as email or team', - recipient) - else: + if matches: logger.warning( 'Multiple matches were found for team "%s". Try using its UID -- ' 'which can be found via `list-team` -- instead', recipient) + return None + + # Classic share-folder parity: resolve team by UID/name; do not treat + # unresolved display names as users (and never send them to team_get_keys). + resolved_team = resolve_team_identifier(params, recipient) + if resolved_team: + return 'team', resolved_team[0] + + if is_keeper_uid(recipient): + # Account UID (non-email user identifier) + return 'user', recipient + + logger.warning('User "%s" could not be resolved as email or team', + recipient) return None diff --git a/keepercommander/commands/pam/vault_target.py b/keepercommander/commands/pam/vault_target.py index dae5e96cb..d30377e07 100644 --- a/keepercommander/commands/pam/vault_target.py +++ b/keepercommander/commands/pam/vault_target.py @@ -1,3 +1,5 @@ +import logging + from ..folder import FolderMoveCommand from ... import api, record_management, vault, vault_extensions from ...error import CommandError @@ -553,8 +555,14 @@ def is_pam_nsf_record(params, record_uid): for folder_uid in find_folders(params, record_uid): if is_nested_share_folder(params, folder_uid): return True - except Exception: - pass + except Exception as e: + # Fail open to the classic update path when folder lookup fails. + # NSF records missing from nested_share_* caches can then silently use + # classic update_record and appear not to persist. + logging.debug( + 'is_pam_nsf_record: folder lookup failed for record %s (%s); ' + 'treating as classic/non-NSF', + record_uid, e) return False diff --git a/keepercommander/commands/pam_cloud/pam_privileged_access.py b/keepercommander/commands/pam_cloud/pam_privileged_access.py index 768302c17..d783057d0 100644 --- a/keepercommander/commands/pam_cloud/pam_privileged_access.py +++ b/keepercommander/commands/pam_cloud/pam_privileged_access.py @@ -417,16 +417,111 @@ class PAMAccessUserListCommand(Command): description='List users in the Identity Provider') parser.add_argument('--config', '-c', required=True, dest='config_uid', help='PAM configuration UID') + parser.add_argument('--format', '-f', dest='output_format', choices=['table', 'json'], + default='table', help='Output format (default: table)') parser.add_argument('--gateway', '-g', dest='gateway', help='Gateway UID or name') def get_parser(self): return PAMAccessUserListCommand.parser + @staticmethod + def _normalize_user(entry): + """Normalize a group member entry into a dict with id/name keys.""" + if isinstance(entry, dict): + user_id = entry.get('id') or entry.get('userId') or entry.get('uid') or '' + name = (entry.get('name') or entry.get('primaryEmail') or entry.get('username') + or entry.get('mail') or entry.get('userPrincipalName') or '') + return {'id': user_id, 'name': name, 'raw': entry} + if isinstance(entry, str): + return {'id': '', 'name': entry, 'raw': {'name': entry}} + return None + def execute(self, params, **kwargs): - raise CommandError('pam-privileged-access', - 'User listing is not yet implemented. ' - 'Use "pam idp group list" to list groups, or check the IdP portal directly.') + config_uid = kwargs['config_uid'] + idp_config_uid = resolve_pam_idp_config(params, config_uid) + + # There is no dedicated IdP user-list gateway action; reuse group list with members. + inputs = GatewayActionIdpInputs( + configuration_uid=config_uid, + idp_config_uid=idp_config_uid, + includeUsers=True, + ) + action = GatewayActionIdpGroupList(inputs=inputs) + + payload = _dispatch_idp_action(params, action, kwargs.get('gateway')) + + response_data = payload.get('data', {}) + if isinstance(response_data, str): + try: + response_data = json.loads(response_data) + except (json.JSONDecodeError, TypeError): + response_data = {} + + if not isinstance(response_data, dict) or not response_data.get('success'): + error = response_data.get('error', 'Unknown error') if isinstance(response_data, dict) else str(response_data) + raise CommandError('pam-privileged-access', f'Gateway reported failure: {error}') + + encrypted_content = response_data.get('data') + if not encrypted_content: + if kwargs.get('output_format') == 'json': + print(json.dumps([], indent=2)) + else: + print('No users found.') + return + + groups = _decrypt_gateway_data(params, config_uid, encrypted_content) + + users_by_key = {} + if isinstance(groups, list): + for group in groups: + if not isinstance(group, dict): + continue + group_info = { + 'id': group.get('id', ''), + 'name': group.get('name', ''), + } + members = group.get('users', []) + if not isinstance(members, list): + continue + for member in members: + normalized = self._normalize_user(member) + if not normalized: + continue + key = (normalized['id'] or normalized['name'] or '').lower() + if not key: + continue + if key not in users_by_key: + users_by_key[key] = { + 'id': normalized['id'], + 'name': normalized['name'], + 'groups': [], + } + users_by_key[key]['groups'].append(group_info) + + users = sorted(users_by_key.values(), key=lambda u: (u.get('name') or '').lower()) + + if kwargs.get('output_format') == 'json': + print(json.dumps(users, indent=2)) + return + + if not users: + print('No users found.') + return + + from keepercommander.commands.base import dump_report_data + headers = ['User ID', 'Name', 'Groups'] + table = [] + for user in users: + group_names = ', '.join( + g.get('name') or g.get('id') or '' for g in user.get('groups', []) + ) + table.append([ + user.get('id', ''), + user.get('name', ''), + group_names, + ]) + dump_report_data(table, headers=headers) # --- Group Commands --- diff --git a/keepercommander/commands/pam_import/workflow_apply.py b/keepercommander/commands/pam_import/workflow_apply.py index 65ffc8c5f..c02236f33 100644 --- a/keepercommander/commands/pam_import/workflow_apply.py +++ b/keepercommander/commands/pam_import/workflow_apply.py @@ -19,6 +19,7 @@ RecordResolver, WorkflowFormatter, sanitize_router_error, + ensure_can_configure_workflow_settings, ) from ...error import CommandError, KeeperApiError from ...params import KeeperParams @@ -192,6 +193,8 @@ def apply_workflow( opts: PamWorkflowOptions, ) -> None: """Create or update workflow config via Krouter. Raises CommandError on failure.""" + ensure_can_configure_workflow_settings(params, refresh=True, action='apply') + record_uid_bytes = utils.base64_url_decode(record_uid) ref = ProtobufRefBuilder.record_ref(record_uid_bytes, record_title) diff --git a/keepercommander/commands/pam_saas/user.py b/keepercommander/commands/pam_saas/user.py index d006c13f0..ecae1f016 100644 --- a/keepercommander/commands/pam_saas/user.py +++ b/keepercommander/commands/pam_saas/user.py @@ -1,5 +1,6 @@ from __future__ import annotations import argparse +import json from ..discover import PAMGatewayActionDiscoverCommandBase, GatewayContext from ...display import bcolors from ... import vault @@ -19,6 +20,8 @@ class PAMActionSaasUserCommand(PAMGatewayActionDiscoverCommandBase): parser.add_argument('--user-record-uid', '-u', required=True, dest='user_uid', action='store', help='The UID of the User record') + parser.add_argument('--format', dest='format', action='store', choices=['text', 'json'], + default='text', help='Output format (text, json)') def get_parser(self): return PAMActionSaasUserCommand.parser @@ -26,35 +29,45 @@ def get_parser(self): def execute(self, params: KeeperParams, **kwargs): user_uid = kwargs.get("user_uid") # type: str + format_type = kwargs.get("format") or "text" + as_json = format_type == "json" - print("") + def _fail(message: str): + if as_json: + print(json.dumps({"user": None, "message": message}, indent=2)) + else: + print("") + print(self._f(message)) + + if not as_json: + print("") # Check to see if the record exists. user_record = vault.KeeperRecord.load(params, user_uid) # type: Optional[TypedRecord] if user_record is None: - print(self._f("The user record does not exists.")) + _fail("The user record does not exists.") return # Make sure this user is a PAM User. if user_record.record_type != PAM_USER: - print(self._f("The user record is not a PAM User.")) + _fail("The user record is not a PAM User.") return record_rotation = params.record_rotation_cache.get(user_record.record_uid) if record_rotation is not None: configuration_uid = record_rotation.get("configuration_uid") else: - print(self._f("The user record does not have any rotation settings.")) + _fail("The user record does not have any rotation settings.") return if configuration_uid is None: - print(self._f("The user record does not have the configuration record set in the rotation settings.")) + _fail("The user record does not have the configuration record set in the rotation settings.") return gateway_context = GatewayContext.from_configuration_uid(params, configuration_uid) if gateway_context is None: - print(self._f("The user record does not have the set gateway")) + _fail("The user record does not have the set gateway") return plugins = get_plugins_map(params, gateway_context) @@ -62,12 +75,19 @@ def execute(self, params: KeeperParams, **kwargs): record_link = RecordLink(record=gateway_context.configuration, params=params, fail_on_corrupt=False) user_vertex = record_link.get_record_link(user_uid) if user_vertex is None: - print(self._f("Cannot find the user in the record link graph.")) + _fail("Cannot find the user in the record link graph.") return - print(self._h(user_record.title)) + result = { + "user": { + "uid": user_record.record_uid, + "title": user_record.title, + }, + "parents": [], + } - missing_configs = [] + if not as_json: + print(self._h(user_record.title)) # User's can have multiple ACL edges to different parents. # One of those ACL edges, in the rotation settings, may a populated saas_record_uid_list @@ -76,27 +96,61 @@ def execute(self, params: KeeperParams, **kwargs): # Check to see if the record exists. parent_record = vault.KeeperRecord.load(params, parent_vertex.uid) # type: Optional[TypedRecord] if parent_record is None: - print(self._f(f"* Parent record UID {parent_vertex.uid} does not exists.")) - print(" The record may have been deleted, however the relationship still exists.") - print("") + parent_entry = { + "uid": parent_vertex.uid, + "title": None, + "record_type": None, + "message": "Parent record does not exist. The record may have been deleted, " + "however the relationship still exists.", + "saas_configs": [], + } + result["parents"].append(parent_entry) + if not as_json: + print(self._f(f"* Parent record UID {parent_vertex.uid} does not exists.")) + print(" The record may have been deleted, however the relationship still exists.") + print("") continue - print(self._b(f" * {parent_record.title}, {parent_record.record_type}")) - print("") + parent_entry = { + "uid": parent_record.record_uid, + "title": parent_record.title, + "record_type": parent_record.record_type, + "saas_configs": [], + } + + if not as_json: + print(self._b(f" * {parent_record.title}, {parent_record.record_type}")) + print("") acl = record_link.get_acl(user_uid, parent_vertex.uid) if acl is not None and acl.rotation_settings is not None: saas_record_uid_list = acl.rotation_settings.saas_record_uid_list if saas_record_uid_list is None or len(saas_record_uid_list) == 0: - print(f"{bcolors.WARNING} The user does not have any SaaS service rotations.{bcolors.ENDC}") + message = "The user does not have any SaaS service rotations." + parent_entry["message"] = message + result["parents"].append(parent_entry) + if as_json: + print(json.dumps(result, indent=2)) + else: + print(f"{bcolors.WARNING} {message}{bcolors.ENDC}") return for config_record_uid in saas_record_uid_list: config_record = vault.KeeperRecord.load(params, config_record_uid) # type: Optional[TypedRecord] if config_record is None: - print(f"{bcolors.WARNING} * Record UID {config_record_uid} not longer exists.{bcolors.ENDC}") + missing = { + "uid": config_record_uid, + "title": None, + "message": "Record no longer exists.", + } + parent_entry["saas_configs"].append(missing) + if not as_json: + print(f"{bcolors.WARNING} * Record UID {config_record_uid} not longer exists." + f"{bcolors.ENDC}") continue - print(self._gr(f" {config_record.title}")) + + if not as_json: + print(self._gr(f" {config_record.title}")) plugin_name = "" saas_type_field = next((x for x in config_record.custom if x.label == "SaaS Type"), None) @@ -105,27 +159,20 @@ def execute(self, params: KeeperParams, **kwargs): plugin_name = saas_type_field.value[0] plugin = plugins.get(plugin_name) + supported = plugin is not None - # This might have been a valid plugin, or the name is mistyped, so it's not supported. - if plugin is None: - plugin_name += " (" + self._f("Not Supported") + ")" - - rotation_active = self._gr("Active") + is_active = True rotation_active_field = next((x for x in config_record.custom if x.label == "Active"), None) if (rotation_active_field is not None and rotation_active_field.value is not None and len(rotation_active_field.value) > 0): is_active = value_to_boolean(rotation_active_field.value[0]) - if is_active is False: - rotation_active = self._f("Inactive") - - print(f" {bcolors.BOLD}SaaS Type{bcolors.ENDC}: {plugin_name}") - print(f" {bcolors.BOLD}Config Record UID{bcolors.ENDC}: {config_record.record_uid}") - print(f" {bcolors.BOLD}Active{bcolors.ENDC}: {rotation_active}") + if is_active is None: + is_active = True + fields = {} if plugin is not None: - for field in plugin.fields: value = next((x.value for x in config_record.custom if x.label == field.label), None) if value is not None: @@ -133,10 +180,48 @@ def execute(self, params: KeeperParams, **kwargs): value = value[0] else: value = None - if value is None: - if field.default_value is not None: - value = f"{field.default_value} ({bcolors.OKBLUE}Default{bcolors.ENDC})" - else: + field_info = { + "value": value, + "default": False, + "set": value is not None, + } + if value is None and field.default_value is not None: + field_info["value"] = field.default_value + field_info["default"] = True + field_info["set"] = False + fields[field.label] = field_info + + saas_entry = { + "uid": config_record.record_uid, + "title": config_record.title, + "saas_type": plugin_name, + "supported": supported, + "active": bool(is_active), + "fields": fields, + } + parent_entry["saas_configs"].append(saas_entry) + + if not as_json: + display_plugin = plugin_name + if not supported: + display_plugin += " (" + self._f("Not Supported") + ")" + rotation_active = self._gr("Active") if is_active else self._f("Inactive") + print(f" {bcolors.BOLD}SaaS Type{bcolors.ENDC}: {display_plugin}") + print(f" {bcolors.BOLD}Config Record UID{bcolors.ENDC}: {config_record.record_uid}") + print(f" {bcolors.BOLD}Active{bcolors.ENDC}: {rotation_active}") + + if plugin is not None: + for field in plugin.fields: + field_info = fields[field.label] + value = field_info["value"] + if not field_info["set"] and field_info["default"]: + value = f"{value} ({bcolors.OKBLUE}Default{bcolors.ENDC})" + elif not field_info["set"]: value = f"{bcolors.FAIL}Not Set{bcolors.ENDC}" - print(f" {bcolors.BOLD}{field.label}{bcolors.ENDC}: {value}") - print("") + print(f" {bcolors.BOLD}{field.label}{bcolors.ENDC}: {value}") + print("") + + result["parents"].append(parent_entry) + + if as_json: + print(json.dumps(result, indent=2)) diff --git a/keepercommander/commands/pam_service/__init__.py b/keepercommander/commands/pam_service/__init__.py index b41fd4018..fe732764f 100644 --- a/keepercommander/commands/pam_service/__init__.py +++ b/keepercommander/commands/pam_service/__init__.py @@ -1,10 +1,13 @@ from __future__ import annotations +from ...discovery_common.types import NormalizedRecord, RecordField from ...utils import value_to_boolean +from ... import vault import os -from typing import TYPE_CHECKING +from typing import Any, TYPE_CHECKING if TYPE_CHECKING: from ...params import KeeperParams + from ...vault import TypedRecord from ...keeper_dag.connection import ConnectionBase @@ -14,4 +17,45 @@ def get_connection(params: KeeperParams) -> ConnectionBase: return CommanderConnection(params=params) else: from ...keeper_dag.connection.local import Connection as LocalConnection - return LocalConnection() \ No newline at end of file + return LocalConnection() + + +def record_lookup(record_uid: str, + context: Any | None = None, + **kwargs) -> NormalizedRecord | None: + + """ + Get the record from the Vault, normalize it, and return it. + + Since common code is using this method we want to flatten/abstract the KeeperRecord/TypedRecord. + """ + + params = context.get("params") + record = vault.TypedRecord.load(params, record_uid) # type: TypedRecord | None + if record is None: + return None + + normalized_record = NormalizedRecord( + record_uid=record.record_uid, + record_type=record.record_type, + record_key_bytes=record.record_key, + title=record.title, + ) + for field in record.fields: + normalized_record.fields.append( + RecordField( + type=field.type, + label=field.label, + value=field.value, + ) + ) + if record.custom is not None: + for field in record.custom: + normalized_record.fields.append( + RecordField( + type=field.type, + label=field.label, + value=field.value, + ) + ) + return normalized_record \ No newline at end of file diff --git a/keepercommander/commands/pam_service/add.py b/keepercommander/commands/pam_service/add.py index 92aa13bee..05c9ec055 100644 --- a/keepercommander/commands/pam_service/add.py +++ b/keepercommander/commands/pam_service/add.py @@ -1,13 +1,14 @@ from __future__ import annotations import argparse import logging +from . import record_lookup from ..discover import PAMGatewayActionDiscoverCommandBase, GatewayContext, MultiConfigurationException, multi_conf_msg from ...display import bcolors from ... import vault from ...discovery_common.user_service import UserService from ...discovery_common.record_link import RecordLink from ...discovery_common.constants import PAM_USER, PAM_MACHINE -from ...discovery_common.types import UserAcl +from ...discovery_common.types import UserAcl, ServiceEnum from ...keeper_dag.types import RefType, EdgeType from ... import __version__ from typing import Optional, TYPE_CHECKING @@ -30,6 +31,11 @@ class PAMActionServiceAddCommand(PAMGatewayActionDiscoverCommandBase): help='The UID of the Windows Machine record') parser.add_argument('--user-uid', '-u', required=True, dest='user_uid', action='store', help='The UID of the User record') + parser.add_argument('--type', '-t', required=True, dest='service_type', action='store', + choices=["service", "task", "iis_pool"], + help='Type of service.') + parser.add_argument('--name', '-n', required=True, dest='name', action='store', + help='Name label for reporting.') def get_parser(self): return PAMActionServiceAddCommand.parser @@ -39,6 +45,8 @@ def execute(self, params: KeeperParams, **kwargs): gateway = kwargs.get("gateway", "not_set") machine_uid = kwargs.get("machine_uid", "not_set") user_uid = kwargs.get("user_uid", "not_set") + service_type = kwargs.get("service_type", "unknown") + name = kwargs.get("name", "Unknown") print("") @@ -63,6 +71,7 @@ def execute(self, params: KeeperParams, **kwargs): agent=f"Cmdr/{__version__}", use_per_graph_endpoints=False) user_service = UserService(record=gateway_context.configuration, + record_lookup_func=record_lookup, record_linking=record_link, params=params, fail_on_corrupt=False, @@ -165,18 +174,24 @@ def execute(self, params: KeeperParams, **kwargs): if acl is None: acl = UserAcl.default() - if not hasattr(acl, "controls_services") or not acl.controls_services: - acl.controls_services = True + orig_md5 = acl.md5(user_record.record_key) - # Make sure the machine has a LINK connection to the configuration. - if not root_vertex.has(machine_vertex): - machine_vertex.belongs_to_root(edge_type=EdgeType.LINK) + # Make sure the machine has a LINK connection to the configuration. + if not root_vertex.has(machine_vertex): + machine_vertex.belongs_to_root(edge_type=EdgeType.LINK) - # Add our new ACL edge between the machine and the user. - user_service.set_acl(resource_uid=machine_vertex.uid, - user_uid=user_vertex.uid, - acl=acl) + user_service.add_service_name(acl=acl, + service_type=ServiceEnum.find_enum(service_type), + service_name=name, + record_key_bytes=user_record.record_key) + # Add our new ACL edge between the machine and the user. + user_service.set_acl(resource_uid=machine_vertex.uid, + user_uid=user_vertex.uid, + acl=acl) + + # Only save if the ACL changed. + if orig_md5 != acl.md5(user_record.record_key): record_link.save() else: logging.debug("user already set to control services on this machine.") diff --git a/keepercommander/commands/pam_service/list.py b/keepercommander/commands/pam_service/list.py index df571a3d9..c74cd4c6e 100644 --- a/keepercommander/commands/pam_service/list.py +++ b/keepercommander/commands/pam_service/list.py @@ -1,12 +1,13 @@ from __future__ import annotations import argparse +from . import record_lookup from ..discover import PAMGatewayActionDiscoverCommandBase, GatewayContext, MultiConfigurationException, multi_conf_msg from ...display import bcolors from ... import vault from ...discovery_common.user_service import UserService from ...discovery_common.record_link import RecordLink from ...discovery_common.constants import PAM_MACHINE -from ...discovery_common.types import UserData, MachineData +from ...discovery_common.types import ServiceEnum, MachineData, UserData from ...keeper_dag import EdgeType from ... import __version__ from typing import Optional, TYPE_CHECKING @@ -39,10 +40,11 @@ def _by_user(self, params: KeeperParams, record_link: RecordLink, user_service: continue resource_active = True - user_data_edge = resource_vertex.get_data() - if user_data_edge is not None: - user_data = user_data_edge.content_as_object(MachineData) - resource_active = not user_data.no_update_services + resource_data_edge = resource_vertex.get_data() + if resource_data_edge is not None: + resource_data_edge = resource_data_edge.content_as_object(MachineData) + if resource_data_edge is not None: + resource_active = not resource_data_edge.no_update_services user_vertices = user_service.get_user_vertices(resource_vertex.uid) if len(user_vertices) > 0: @@ -58,7 +60,8 @@ def _by_user(self, params: KeeperParams, record_link: RecordLink, user_service: user_data_edge = user_vertex.get_data() if user_data_edge is not None: user_data = user_data_edge.content_as_object(UserData) - user_active = not user_data.no_update_services + if user_data is not None: + user_active = not user_data.no_update_services if user_record.record_uid not in service_map: service_map[user_record.record_uid] = { @@ -66,10 +69,32 @@ def _by_user(self, params: KeeperParams, record_link: RecordLink, user_service: "active": user_active, "machines": [] } - text = f"{resource_record.title} ({resource_record.record_uid})" + machine_name = f"{resource_record.title} ({resource_record.record_uid})" if not resource_active: - text += f" : {bcolors.FAIL}Disabled{bcolors.ENDC}" - service_map[user_record.record_uid]["machines"].append(text) + machine_name += f" : {bcolors.FAIL}Disabled{bcolors.ENDC}" + + items = [] + if acl.service_names is not None or acl.service_names != "": + for service_name in acl.get_service_names(user_record.record_key): + text = "" + if service_name.type == ServiceEnum.service: + text = "Service" + elif service_name.type == ServiceEnum.task: + text = "Scheduled Task" + elif service_name.type == ServiceEnum.iis_pool: + text = "IIS Pool" + for item in service_name.items: + text += f": {item.name}" + if "Unknown" in item.name: + text += " (from migration)" + elif not item.via_discovery: + text += " (manually set)" + items.append(text) + + service_map[user_record.record_uid]["machines"].append({ + "name": machine_name, + "items": items + }) print("") printed_something = False @@ -82,7 +107,9 @@ def _by_user(self, params: KeeperParams, record_link: RecordLink, user_service: active_text = f" {bcolors.FAIL}Disabled{bcolors.ENDC}" print(f" {self._b(user['title'])} ({user_uid}){active_text}") for machine in user["machines"]: - print(f" * {machine}") + print(f" * {machine['name']}") + for item in machine["items"]: + print(f" - {self._gr(item)}") print("") if not printed_something: print(f" {bcolors.FAIL}There are no service mappings.{bcolors.ENDC}") @@ -90,15 +117,17 @@ def _by_user(self, params: KeeperParams, record_link: RecordLink, user_service: def _by_machine(self, params: KeeperParams, record_link: RecordLink, user_service: UserService): service_map = {} for resource_vertex in record_link.dag.get_root.has_vertices(edge_type=EdgeType.LINK): + resource_record = vault.KeeperRecord.load(params, resource_vertex.uid) # type: Optional[TypedRecord] if resource_record is None or resource_record.record_type != PAM_MACHINE: continue resource_active = True - user_data_edge = resource_vertex.get_data() - if user_data_edge is not None: - user_data = user_data_edge.content_as_object(MachineData) - resource_active = not user_data.no_update_services + resource_data_edge = resource_vertex.get_data() + if resource_data_edge is not None: + resource_data_edge = resource_data_edge.content_as_object(MachineData) + if resource_data_edge is not None: + resource_active = not resource_data_edge.no_update_services user_vertices = user_service.get_user_vertices(resource_vertex.uid) if len(user_vertices) > 0: @@ -114,7 +143,8 @@ def _by_machine(self, params: KeeperParams, record_link: RecordLink, user_servic user_data_edge = user_vertex.get_data() if user_data_edge is not None: user_data = user_data_edge.content_as_object(UserData) - user_active = not user_data.no_update_services + if user_data is not None: + user_active = not user_data.no_update_services if resource_record.record_uid not in service_map: service_map[resource_record.record_uid] = { @@ -122,10 +152,32 @@ def _by_machine(self, params: KeeperParams, record_link: RecordLink, user_servic "active": resource_active, "users": [] } - text = f"{user_record.title} ({user_record.record_uid})" + user_name = f"{user_record.title} ({user_record.record_uid})" if not user_active: - text += f" : {bcolors.FAIL}Disabled{bcolors.ENDC}" - service_map[resource_record.record_uid]["users"].append(text) + user_name += f" : {bcolors.FAIL}Disabled{bcolors.ENDC}" + + items = [] + if acl.service_names is not None or acl.service_names != "": + for service_name in acl.get_service_names(user_record.record_key): + text = "" + if service_name.type == ServiceEnum.service: + text = "Service" + elif service_name.type == ServiceEnum.task: + text = "Scheduled Task" + elif service_name.type == ServiceEnum.iis_pool: + text = "IIS Pool" + for item in service_name.items: + text += f": {item.name}" + if "Unknown" in item.name: + text += " (from migration)" + elif not item.via_discovery: + text += " (manually set)" + items.append(text) + + service_map[resource_record.record_uid]["users"].append({ + "name": user_name, + "items": items + }) print("") printed_something = False @@ -138,7 +190,9 @@ def _by_machine(self, params: KeeperParams, record_link: RecordLink, user_servic active_text = f" {bcolors.FAIL}Disabled{bcolors.ENDC}" print(f" {self._b(user['title'])} ({resource_uid}){active_text}") for user in user["users"]: - print(f" * {user}") + print(f" * {user['name']}") + for item in user["items"]: + print(f" - {self._gr(item)}") print("") if not printed_something: print(f" {bcolors.FAIL}There are no service mappings.{bcolors.ENDC}") @@ -166,6 +220,7 @@ def execute(self, params: KeeperParams, **kwargs): # This will trigger the migration. user_service = UserService(record=gateway_context.configuration, + record_lookup_func=record_lookup, record_linking=record_link, params=params, fail_on_corrupt=False, diff --git a/keepercommander/commands/pam_service/map.py b/keepercommander/commands/pam_service/map.py index 599a50c5a..9bc07ad94 100644 --- a/keepercommander/commands/pam_service/map.py +++ b/keepercommander/commands/pam_service/map.py @@ -40,6 +40,7 @@ def __init__(self, inputs: GatewayActionUserServiceMapCommandInputs, conversatio def toJSON(self): return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) + class PAMActionUserServiceMapCommand(PAMGatewayActionDiscoverCommandBase): parser = argparse.ArgumentParser(prog='pam action saas config') diff --git a/keepercommander/commands/pam_service/remove.py b/keepercommander/commands/pam_service/remove.py index 3f6f7087c..f66da90e0 100644 --- a/keepercommander/commands/pam_service/remove.py +++ b/keepercommander/commands/pam_service/remove.py @@ -1,10 +1,12 @@ from __future__ import annotations import argparse +from . import record_lookup from ..discover import PAMGatewayActionDiscoverCommandBase, GatewayContext, MultiConfigurationException, multi_conf_msg from ... import vault from ...discovery_common.constants import PAM_USER, PAM_MACHINE from ...discovery_common.user_service import UserService from ...discovery_common.record_link import RecordLink +from ...discovery_common.types import ServiceEnum from ...display import bcolors from ... import __version__ from typing import Optional, TYPE_CHECKING @@ -28,6 +30,15 @@ class PAMActionServiceRemoveCommand(PAMGatewayActionDiscoverCommandBase): parser.add_argument('--user-uid', '-u', required=True, dest='user_uid', action='store', help='The UID of the User record') + parser.add_argument('--type', '-t', required=True, dest='service_type', action='store', + choices=["service", "task", "iis_pool", "all"], + help='Type of service. "all" will clear all') + parser.add_argument('--name', '-n', required=False, dest='name', action='store', + help='Name label for reporting. Exclude will remove all service types.') + + parser.add_argument('--force', required=False, dest='do_force', action='store_true', + help='Force') + def get_parser(self): return PAMActionServiceRemoveCommand.parser @@ -36,6 +47,9 @@ def execute(self, params: KeeperParams, **kwargs): gateway = kwargs.get("gateway", "not_set") machine_uid = kwargs.get("machine_uid", "not_set") user_uid = kwargs.get("user_uid", "not_set") + service_type = kwargs.get("service_type", "unknown") + name = kwargs.get("name", None) + do_force = kwargs.get("do_force", False) print("") @@ -60,6 +74,7 @@ def execute(self, params: KeeperParams, **kwargs): agent=f"Cmdr/{__version__}", use_per_graph_endpoints=False) user_service = UserService(record=gateway_context.configuration, + record_lookup_func=record_lookup, record_linking=record_link, params=params, fail_on_corrupt=False, @@ -100,12 +115,22 @@ def execute(self, params: KeeperParams, **kwargs): return if not hasattr(acl, "controls_services") or acl.controls_services: - acl.controls_services = False - user_service.set_acl(resource_uid=machine_vertex.uid, - user_uid=user_vertex.uid, - acl=acl) - record_link.save() + if (service_type == "all" or name is None) and not do_force: + print(f"{bcolors.WARNING}Require --force parameter to make changes.{bcolors.ENDC}") + return + + orig_md5 = acl.md5(user_record.record_key) + user_service.remove_service_name(acl=acl, + record_key_bytes=user_record.record_key, + service_type=ServiceEnum.find_enum(service_type), + service_name=name) + + if orig_md5 != acl.md5(user_record.record_key): + user_service.set_acl(resource_uid=machine_vertex.uid, + user_uid=user_vertex.uid, + acl=acl) + record_link.save() print( self._gr( diff --git a/keepercommander/commands/pedm/pedm_admin.py b/keepercommander/commands/pedm/pedm_admin.py index 4e7f75c67..278d55fe0 100644 --- a/keepercommander/commands/pedm/pedm_admin.py +++ b/keepercommander/commands/pedm/pedm_admin.py @@ -446,6 +446,7 @@ def build_user(user: ScimUser) -> Optional[Tuple[admin_types.CollectionData, boo return None key = (account_type_key, user_domain.lower(), user_login.lower()) data = { + 'Id': user.id, 'Domainname': user_domain, 'Username': user_login, 'AccountType': account_type, @@ -476,8 +477,9 @@ def build_group(group: ScimGroup) -> Optional[Tuple[admin_types.CollectionData, return None group_domain = group.domain or domain_name data = { + 'Id': group.id, + 'Domainname': group_domain, 'GroupName': group.name, - 'Domainname': group_domain } key = (group_domain.lower(), group.name.lower()) diff --git a/keepercommander/commands/record.py b/keepercommander/commands/record.py index c8c87fc93..1774a65d5 100644 --- a/keepercommander/commands/record.py +++ b/keepercommander/commands/record.py @@ -1990,6 +1990,8 @@ def fetch_members(team_uid): # type: (str) -> List[str] trash_get_parser = argparse.ArgumentParser(prog='trash get', description='Get the details of a deleted record') +trash_get_parser.add_argument('--format', dest='format', action='store', choices=['detail', 'json'], + default='detail', help='output format') trash_get_parser.add_argument('record', action='store', help='Deleted record UID') trash_restore_parser = argparse.ArgumentParser(prog='trash restore', description='Restores deleted records') @@ -2308,13 +2310,20 @@ def get_parser(self): def execute(self, params, **kwargs): deleted_records = self.get_deleted_records(params) orphaned_records = self.get_orphaned_records(params) + fmt = kwargs.get('format') or 'detail' if len(deleted_records) == 0 and len(orphaned_records) == 0: - logging.info('Trash is empty') + if fmt == 'json': + print(json.dumps({'message': 'Trash is empty'}, indent=2)) + else: + logging.info('Trash is empty') return record_uid = kwargs.get('record') if not record_uid: - logging.info('Record UID parameter is required') + if fmt == 'json': + print(json.dumps({'message': 'Record UID parameter is required'}, indent=2)) + else: + logging.info('Record UID parameter is required') return is_shared = False @@ -2323,12 +2332,68 @@ def execute(self, params, **kwargs): rec = orphaned_records.get(record_uid) is_shared = True if not rec: - logging.info('%s is not a valid deleted record UID', record_uid) + message = f'{record_uid} is not a valid deleted record UID' + if fmt == 'json': + print(json.dumps({'message': message}, indent=2)) + else: + logging.info('%s is not a valid deleted record UID', record_uid) return record = vault.KeeperRecord.load(params, rec) if not record: - logging.info('Cannot restore record %s', record_uid) + message = f'Cannot restore record {record_uid}' + if fmt == 'json': + print(json.dumps({'message': message}, indent=2)) + else: + logging.info('Cannot restore record %s', record_uid) + return + + if fmt == 'json': + payload = { + 'record_uid': record.record_uid, + 'title': record.title, + 'record_type': record.record_type, + 'status': 'Share' if is_shared else 'Record', + 'fields': {}, + } + for name, value in record.enumerate_fields(): + if value: + if isinstance(value, list): + payload['fields'][name] = value + elif len(value) > 100: + payload['fields'][name] = value[:99] + '...' + else: + payload['fields'][name] = value + + if is_shared: + if 'shares' not in rec: + rec['shares'] = {} + shares = api.get_record_shares(params, (record_uid,), True) + if isinstance(shares, list): + record_shares = next( + (x.get('shares') for x in shares if x.get('record_uid') == record_uid), None) + if isinstance(record_shares, dict): + rec['shares'] = record_shares + + user_shares = [] + if 'shares' in rec and 'user_permissions' in rec['shares']: + for uo in rec['shares']['user_permissions']: + if uo.get('owner'): + continue + flags = [] + if uo.get('editable'): + flags.append('Can Edit') + if uo.get('shareable'): + flags.append('Can Share') + user_shares.append({ + 'username': uo.get('username'), + 'permissions': ' & '.join(flags) if flags else 'Read Only', + 'self': uo.get('username') == params.user, + }) + if user_shares: + payload['direct_user_shares'] = user_shares + + print(json.dumps(payload, indent=2, default=base.json_serialized)) return for name, value in record.enumerate_fields(): @@ -2671,6 +2736,62 @@ def execute(self, params, **kwargs): if action == 'view': rev = history[index] record = vault.KeeperRecord.load(params, rev) + fmt = kwargs.get('format') or '' + + if fmt == 'json': + version = rev.get('version') or 0 + ro = { + 'record_uid': record.record_uid + } + if version < 3: + ro['title'] = record.title + if isinstance(record, vault.PasswordRecord): + if record.login: + ro['login'] = record.login + if record.password: + ro['password'] = record.password + if record.link: + ro['login_url'] = record.link + if record.custom: + ro['custom_fields'] = [{ + 'name': x.name, + 'value': x.value, + 'type': x.type + } for x in record.custom] + if record.totp: + ro['totp'] = record.totp + if record.attachments: + ro['attachments'] = [{ + 'id': a.id, + 'name': a.name, + 'size': a.size + } for a in record.attachments] + if record.notes: + ro['notes'] = record.notes + else: + data = rev['data_unencrypted'] if 'data_unencrypted' in rev else b'{}' + data = json.loads(data.decode()) + ro.update(data) + ro['version'] = version + ro['revision'] = record.revision + if 'user_name' in rev: + ro['modified_by'] = rev['user_name'] + if 'client_modified_time' in rev: + cmt = rev['client_modified_time'] + if isinstance(cmt, (int, float)): + dt = datetime.datetime.fromtimestamp(int(cmt / 1000)) + ro['client_modified_time'] = dt.isoformat() + + output = kwargs.get('output') + if output: + _, ext = os.path.splitext(output) + if not ext: + output += '.json' + with open(output, 'w') as f: + json.dump(ro, f, indent=2) + else: + print(json.dumps(ro, indent=2)) + return rows = [] for name, value in record.enumerate_fields(): @@ -2682,7 +2803,6 @@ def execute(self, params, **kwargs): rows.append([name, value]) modified = datetime.datetime.fromtimestamp(int(rev['client_modified_time'] / 1000.0)) rows.append(['Modified', modified]) - fmt = kwargs.get('format') or '' return base.dump_report_data(rows, headers=['Name', 'Value'], title=f'Record Revision V.{revision}', no_header=True, right_align=(0,), fmt=fmt, filename=kwargs.get('output')) diff --git a/keepercommander/commands/tunnel_and_connections.py b/keepercommander/commands/tunnel_and_connections.py index 2b478e83e..60b90c59d 100644 --- a/keepercommander/commands/tunnel_and_connections.py +++ b/keepercommander/commands/tunnel_and_connections.py @@ -124,13 +124,19 @@ def __init__(self): # Individual Commands class PAMTunnelListCommand(Command): pam_cmd_parser = argparse.ArgumentParser(prog='pam tunnel list') + pam_cmd_parser.add_argument('--format', dest='format', action='store', choices=['table', 'json'], + default='table', help='Output format (table, json)') def get_parser(self): return PAMTunnelListCommand.pam_cmd_parser def execute(self, params, **kwargs): + format_type = kwargs.get('format') or 'table' table = [] - headers = ['Record', 'Remote Target', 'Local Address', 'Tunnel ID', 'Conversation ID', 'Status'] + if format_type == 'json': + headers = ['record', 'remote_target', 'local_address', 'tunnel_id', 'conversation_id', 'status'] + else: + headers = ['Record', 'Remote Target', 'Local Address', 'Tunnel ID', 'Conversation ID', 'Status'] # In-process tunnels from the Rust PyTubeRegistry tube_registry = get_or_create_tube_registry(params) @@ -142,28 +148,39 @@ def execute(self, params, **kwargs): conversation_ids = tube_registry.get_conversation_ids_by_tube_id(tube_id) tunnel_session = get_tunnel_session(tube_id) - record_title = tunnel_session.record_title if tunnel_session and tunnel_session.record_title else f"{bcolors.WARNING}unknown{bcolors.ENDC}" + if tunnel_session and tunnel_session.record_title: + record_title = tunnel_session.record_title + else: + record_title = 'unknown' if tunnel_session and tunnel_session.target_host and tunnel_session.target_port: remote_target = f"{tunnel_session.target_host}:{tunnel_session.target_port}" else: - remote_target = f"{bcolors.WARNING}unknown{bcolors.ENDC}" + remote_target = 'unknown' if tunnel_session and tunnel_session.host and tunnel_session.port: - local_addr = f"{bcolors.OKGREEN}{tunnel_session.host}:{tunnel_session.port}{bcolors.ENDC}" + local_addr = f"{tunnel_session.host}:{tunnel_session.port}" else: - local_addr = f"{bcolors.WARNING}unknown{bcolors.ENDC}" + local_addr = 'unknown' conv_id = conversation_ids[0] if conversation_ids else (tunnel_session.conversation_id if tunnel_session else 'none') try: state = tube_registry.get_connection_state(tube_id) - status_color = f"{bcolors.OKGREEN}" if state.lower() == "connected" else f"{bcolors.WARNING}" - status = f"{status_color}{state}{bcolors.ENDC}" + status = state except Exception: - status = f"{bcolors.WARNING}unknown{bcolors.ENDC}" + status = 'unknown' - table.append([record_title, remote_target, local_addr, tube_id, conv_id, status]) + if format_type == 'json': + table.append([record_title, remote_target, local_addr, tube_id, conv_id, status]) + else: + rec_disp = record_title if record_title != 'unknown' else f"{bcolors.WARNING}unknown{bcolors.ENDC}" + rem_disp = remote_target if remote_target != 'unknown' else f"{bcolors.WARNING}unknown{bcolors.ENDC}" + loc_disp = (f"{bcolors.OKGREEN}{local_addr}{bcolors.ENDC}" + if local_addr != 'unknown' else f"{bcolors.WARNING}unknown{bcolors.ENDC}") + status_color = f"{bcolors.OKGREEN}" if status.lower() == "connected" else f"{bcolors.WARNING}" + status_disp = f"{status_color}{status}{bcolors.ENDC}" + table.append([rec_disp, rem_disp, loc_disp, tube_id, conv_id, status_disp]) # Cross-process tunnels from the file-based registry for entry in list_registered_tunnels(): @@ -173,20 +190,31 @@ def execute(self, params, **kwargs): rec = entry.get('record_title') or entry.get('record_uid', '?') th = entry.get('target_host') tp = entry.get('target_port') - remote = f"{th}:{tp}" if th and tp else f"{bcolors.WARNING}n/a{bcolors.ENDC}" + remote = f"{th}:{tp}" if th and tp else 'n/a' h = entry.get('host', '127.0.0.1') p = entry.get('port', '?') - local = f"{bcolors.OKGREEN}{h}:{p}{bcolors.ENDC}" + local = f"{h}:{p}" tid = entry.get('tube_id', '') mode = entry.get('mode', '?') - status = f"{bcolors.OKGREEN}{mode} (PID {pid}){bcolors.ENDC}" - table.append([rec, remote, local, tid, '', status]) + status = f"{mode} (PID {pid})" + if format_type == 'json': + table.append([rec, remote, local, tid, '', status]) + else: + rem_disp = remote if remote != 'n/a' else f"{bcolors.WARNING}n/a{bcolors.ENDC}" + table.append([rec, rem_disp, f"{bcolors.OKGREEN}{local}{bcolors.ENDC}", tid, '', + f"{bcolors.OKGREEN}{status}{bcolors.ENDC}"]) if not table: - logging.warning(f"{bcolors.OKBLUE}No Tunnels running{bcolors.ENDC}") + message = "No Tunnels running" + if format_type == 'json': + print(json.dumps({"tunnels": [], "message": message}, indent=2)) + else: + logging.warning(f"{bcolors.OKBLUE}{message}{bcolors.ENDC}") return - dump_report_data(table, headers, fmt='table', filename="", row_number=False, column_width=None) + report = dump_report_data(table, headers, fmt=format_type, filename="", row_number=False, column_width=None) + if format_type == 'json': + return report class PAMTunnelStopCommand(Command): @@ -4027,8 +4055,7 @@ def update_connection_choice(field_name, value): update_connection_choice('sessionPersistence', session_persistence) if dirty: - record_management.update_record(params, record) - api.sync_down(params) + update_pam_record(params, record, command='pam rbi edit') traffic_encryption_key = record.get_typed_field('trafficEncryptionSeed') if not traffic_encryption_key: diff --git a/keepercommander/commands/universalsecretsync.py b/keepercommander/commands/universalsecretsync.py index 3dddfacfb..5bbda109e 100644 --- a/keepercommander/commands/universalsecretsync.py +++ b/keepercommander/commands/universalsecretsync.py @@ -78,7 +78,8 @@ def print_uss_configurations_list(params, format_type='table'): configurations = list(vault_extensions.find_records(params, record_version=6)) # Only process these specific configuration types - uss_supported_types = ('pamGcpConfiguration', 'pamAzureConfiguration', 'pamAwsConfiguration') + uss_supported_types = ('pamGcpConfiguration', 'pamAzureConfiguration', 'pamAwsConfiguration', + 'pamGitHubConfiguration') configs_data = [] for record in configurations: @@ -111,6 +112,9 @@ def print_uss_configurations_list(params, format_type='table'): # Get the configuration data config_data = universal_sync_edge.content_as_dict or {} + # GitHub-specific fields are nested under the 'github' key. + github_data = config_data.get('github') or {} + # Decrypt vault_name if present. The router stores it under the # 'vaultName' key as a base64-url string of the encrypted bytes. vault_name = 'N/A' @@ -143,6 +147,44 @@ def print_uss_configurations_list(params, format_type='table'): logging.debug(f"Failed to decrypt sync_identity for record {record.record_uid}: {e}") sync_identity = 'N/A' + # Decrypt owner if present. The router stores it under the + # 'github.owner' key as a base64-url string of the encrypted bytes. + owner = 'N/A' + owner_encrypted = github_data.get('owner') + if owner_encrypted: + try: + owner_bytes = crypto.decrypt_aes_v2( + utils.base64_url_decode(owner_encrypted), record.record_key) + owner = owner_bytes.decode('utf-8') + except Exception as e: + logging.debug(f"Failed to decrypt owner for record {record.record_uid}: {e}") + owner = 'N/A' + + # scope/organizationVisibility are stored unencrypted (plain enum values) + scope_value = github_data.get('scope') + try: + scope_str = pam_pb2.GitHubScope.Name(scope_value) if scope_value is not None else 'N/A' + except ValueError: + scope_str = 'N/A' + + org_visibility_value = github_data.get('organizationVisibility') + try: + org_visibility_str = pam_pb2.GitHubOrganizationVisibility.Name(org_visibility_value) \ + if org_visibility_value is not None else 'N/A' + except ValueError: + org_visibility_str = 'N/A' + + # Decrypt GitHub repository names if present. The router stores them under + # the 'github.repos' key as a list of base64-url strings of the encrypted bytes. + repo_names = [] + for repo_encrypted in github_data.get('repos', []): + try: + repo_bytes = crypto.decrypt_aes_v2( + utils.base64_url_decode(repo_encrypted), record.record_key) + repo_names.append(repo_bytes.decode('utf-8')) + except Exception as e: + logging.debug(f"Failed to decrypt repo name for record {record.record_uid}: {e}") + # Get folder UIDs - folders connected via universal_sync_folder (simple count only) folder_count = 0 for vertex in root.has_vertices(): @@ -160,6 +202,10 @@ def print_uss_configurations_list(params, format_type='table'): 'folder_count': folder_count, 'vault_name': vault_name, 'sync_identity': sync_identity, + 'scope': scope_str, + 'owner': owner, + 'organization_visibility': org_visibility_str, + 'repos': repo_names, }) except Exception as e: # Skip records that fail to load or don't have USS config @@ -180,13 +226,16 @@ def print_uss_configurations_list(params, format_type='table'): # Display as simple summary table table = [] - headers = ['Network UID', 'Title', 'Type', 'Enabled', 'Dry Run', 'Folders', 'Vault Name', 'Sync Identity'] + headers = ['Network UID', 'Title', 'Type', 'Enabled', 'Dry Run', 'Folders', 'Vault Name', 'Sync Identity', + 'Scope', 'Owner', 'Org Visibility', 'Repos'] for config in configs_data: enabled_str = f"{bcolors.OKGREEN}Yes{bcolors.ENDC}" if config['enabled'] else f"{bcolors.FAIL}No{bcolors.ENDC}" dry_run_str = f"{bcolors.WARNING}Yes{bcolors.ENDC}" if config['dry_run_enabled'] else "No" folder_count = config.get('folder_count', 0) folders_str = f"{folder_count} folder(s)" if folder_count > 0 else "None" + repos = config.get('repos', []) + repos_str = f"{len(repos)} repo(s)" if repos else "None" row = [ config['record_uid'], @@ -196,7 +245,11 @@ def print_uss_configurations_list(params, format_type='table'): dry_run_str, folders_str, config['vault_name'], - config['sync_identity'] + config['sync_identity'], + config.get('scope', 'N/A'), + config.get('owner', 'N/A'), + config.get('organization_visibility', 'N/A'), + repos_str ] table.append(row) @@ -219,7 +272,8 @@ def print_uss_configuration_details(params, network_uid, format_type='table'): raise CommandError('', f'{bcolors.FAIL}Network "{network_uid}" not found{bcolors.ENDC}') # Check if it's a supported USS configuration type - uss_supported_types = ('pamGcpConfiguration', 'pamAzureConfiguration', 'pamAwsConfiguration') + uss_supported_types = ('pamGcpConfiguration', 'pamAzureConfiguration', 'pamAwsConfiguration', + 'pamGitHubConfiguration') if not isinstance(network, vault.TypedRecord) or network.record_type not in uss_supported_types: if format_type == 'json': return json.dumps({"error": f'Record "{network_uid}" is not a USS configuration'}) @@ -253,6 +307,9 @@ def print_uss_configuration_details(params, network_uid, format_type='table'): # Get the configuration data config_data = universal_sync_edge.content_as_dict or {} + # GitHub-specific fields are nested under the 'github' key. + github_data = config_data.get('github') or {} + # Decrypt vault_name if present. The router stores it under the # 'vaultName' key as a base64-url string of the encrypted bytes. vault_name = 'N/A' @@ -285,6 +342,44 @@ def print_uss_configuration_details(params, network_uid, format_type='table'): logging.debug(f"Failed to decrypt sync_identity for network {network.record_uid}: {e}") sync_identity = 'N/A' + # Decrypt owner if present. The router stores it under the + # 'github.owner' key as a base64-url string of the encrypted bytes. + owner = 'N/A' + owner_encrypted = github_data.get('owner') + if owner_encrypted: + try: + owner_bytes = crypto.decrypt_aes_v2( + utils.base64_url_decode(owner_encrypted), network.record_key) + owner = owner_bytes.decode('utf-8') + except Exception as e: + logging.debug(f"Failed to decrypt owner for network {network.record_uid}: {e}") + owner = 'N/A' + + # scope/organizationVisibility are stored unencrypted (plain enum values) + scope_value = github_data.get('scope') + try: + scope_str = pam_pb2.GitHubScope.Name(scope_value) if scope_value is not None else 'N/A' + except ValueError: + scope_str = 'N/A' + + org_visibility_value = github_data.get('organizationVisibility') + try: + org_visibility_str = pam_pb2.GitHubOrganizationVisibility.Name(org_visibility_value) \ + if org_visibility_value is not None else 'N/A' + except ValueError: + org_visibility_str = 'N/A' + + # Decrypt GitHub repository names if present. The router stores them under + # the 'github.repos' key as a list of base64-url strings of the encrypted bytes. + repo_names = [] + for repo_encrypted in github_data.get('repos', []): + try: + repo_bytes = crypto.decrypt_aes_v2( + utils.base64_url_decode(repo_encrypted), network.record_key) + repo_names.append(repo_bytes.decode('utf-8')) + except Exception as e: + logging.debug(f"Failed to decrypt repo name for network {network.record_uid}: {e}") + # Get folder vertices with sync status folder_vertices = [] for vertex in root.has_vertices(): @@ -339,6 +434,10 @@ def print_uss_configuration_details(params, network_uid, format_type='table'): 'dry_run_enabled': config_data.get('dryRunEnabled', False), 'vault_name': vault_name, 'sync_identity': sync_identity, + 'scope': scope_str, + 'owner': owner, + 'organization_visibility': org_visibility_str, + 'repos': repo_names, 'folders': [] } @@ -370,6 +469,10 @@ def print_uss_configuration_details(params, network_uid, format_type='table'): table.append(['Dry Run', 'Yes' if config_data.get('dryRunEnabled', False) else 'No']) table.append(['Vault Name', vault_name]) table.append(['Sync Identity', sync_identity]) + table.append(['Scope', scope_str]) + table.append(['Owner', owner]) + table.append(['Org Visibility', org_visibility_str]) + table.append(['Repos', ', '.join(repo_names) if repo_names else 'None']) table.append(['', '']) # Blank row separator # Display folder sync details @@ -430,6 +533,16 @@ class PAMUniversalSyncConfigAddCommand(Command): help='Identity record UID to use for syncing') parser.add_argument('--vault-name', '-vn', dest='vault_name', action='store', help='Vault name for universal sync') + parser.add_argument('--scope', '-sc', dest='scope', action='store', + choices=['repository', 'organization'], + help='GitHub sync scope: a single repository or an entire organization') + parser.add_argument('--owner', '-o', dest='owner', action='store', + help='GitHub organization or user name that owns the repositories') + parser.add_argument('--org-visibility', '-ov', dest='org_visibility', action='store', + choices=['all', 'private', 'selected'], + help='Repository visibility to sync when scope is organization') + parser.add_argument('--repo', '-r', dest='repo', action='append', + help='GitHub repository name to sync (can be specified multiple times; scope must be repository)') def get_parser(self): return PAMUniversalSyncConfigAddCommand.parser @@ -480,6 +593,29 @@ def execute(self, params, **kwargs): encrypted_vault_name = crypto.encrypt_aes_v2(vault_name_bytes, network.record_key) rq.vaultName = encrypted_vault_name + scope = kwargs.get('scope') + if scope is not None: + rq.github.scope = pam_pb2.ORGANIZATION if scope == 'organization' else pam_pb2.REPOSITORY + + owner = kwargs.get('owner') + if owner: + owner_bytes = string_to_bytes(owner) + encrypted_owner = crypto.encrypt_aes_v2(owner_bytes, network.record_key) + rq.github.owner = encrypted_owner + + org_visibility = kwargs.get('org_visibility') + if org_visibility is not None: + org_visibility_map = {'all': pam_pb2.ALL, 'private': pam_pb2.PRIVATE, 'selected': pam_pb2.SELECTED} + rq.github.organizationVisibility = org_visibility_map[org_visibility] + + repos = kwargs.get('repo') + if repos: + for repo_name in repos: + repo_bytes = string_to_bytes(repo_name) + repo_obj = pam_pb2.GitHubRepository() + repo_obj.name = crypto.encrypt_aes_v2(repo_bytes, network.record_key) + rq.github.repos.append(repo_obj) + encrypted_session_token, encrypted_transmission_key, transmission_key = get_keeper_tokens(params) try: @@ -505,6 +641,16 @@ class PAMUniversalSyncConfigEditCommand(Command): help='Identity record UID to use for syncing') parser.add_argument('--vault-name', '-vn', dest='vault_name', action='store', help='Vault name for universal sync') + parser.add_argument('--scope', '-sc', dest='scope', action='store', + choices=['repository', 'organization'], + help='GitHub sync scope: a single repository or an entire organization') + parser.add_argument('--owner', '-o', dest='owner', action='store', + help='GitHub organization or user name that owns the repositories') + parser.add_argument('--org-visibility', '-ov', dest='org_visibility', action='store', + choices=['all', 'private', 'selected'], + help='Repository visibility to sync when scope is organization') + parser.add_argument('--repo', '-r', dest='repo', action='append', + help='GitHub repository name to sync (can be specified multiple times; scope must be repository)') def get_parser(self): return PAMUniversalSyncConfigEditCommand.parser @@ -601,6 +747,45 @@ def execute(self, params, **kwargs): elif existing_config.get('vaultName'): rq.vaultName = utils.base64_url_decode(existing_config['vaultName']) + # GitHub-specific fields live under the nested 'github' object, both in the + # request message (rq.github) and in the existing DAG edge content. + existing_github = existing_config.get('github') or {} + + scope = kwargs.get('scope') + if scope is not None: + rq.github.scope = pam_pb2.ORGANIZATION if scope == 'organization' else pam_pb2.REPOSITORY + elif 'scope' in existing_github: + rq.github.scope = existing_github['scope'] + + owner = kwargs.get('owner') + if owner: + owner_bytes = string_to_bytes(owner) + encrypted_owner = crypto.encrypt_aes_v2(owner_bytes, network.record_key) + rq.github.owner = encrypted_owner + elif existing_github.get('owner'): + rq.github.owner = utils.base64_url_decode(existing_github['owner']) + + org_visibility = kwargs.get('org_visibility') + if org_visibility is not None: + org_visibility_map = {'all': pam_pb2.ALL, 'private': pam_pb2.PRIVATE, 'selected': pam_pb2.SELECTED} + rq.github.organizationVisibility = org_visibility_map[org_visibility] + elif 'organizationVisibility' in existing_github: + rq.github.organizationVisibility = existing_github['organizationVisibility'] + + # Handle repos - if provided, replace all; if not, keep existing (re-encrypted values carried forward as-is) + repos = kwargs.get('repo') + if repos: + for repo_name in repos: + repo_bytes = string_to_bytes(repo_name) + repo_obj = pam_pb2.GitHubRepository() + repo_obj.name = crypto.encrypt_aes_v2(repo_bytes, network.record_key) + rq.github.repos.append(repo_obj) + else: + for existing_repo in existing_github.get('repos', []): + repo_obj = pam_pb2.GitHubRepository() + repo_obj.name = utils.base64_url_decode(existing_repo) + rq.github.repos.append(repo_obj) + encrypted_session_token, encrypted_transmission_key, transmission_key = get_keeper_tokens(params) try: diff --git a/keepercommander/commands/workflow/config_commands.py b/keepercommander/commands/workflow/config_commands.py index 1fe17d59a..670a8da4e 100644 --- a/keepercommander/commands/workflow/config_commands.py +++ b/keepercommander/commands/workflow/config_commands.py @@ -22,7 +22,13 @@ from ...proto import workflow_pb2 from ... import utils -from .helpers import RecordResolver, ProtobufRefBuilder, WorkflowFormatter, sanitize_router_error +from .helpers import ( + RecordResolver, + ProtobufRefBuilder, + WorkflowFormatter, + sanitize_router_error, + ensure_can_configure_workflow_settings, +) def _add_approvers_to_workflow(params, record_uid, record_name, @@ -89,6 +95,10 @@ def get_parser(self): return WorkflowCreateCommand.parser def execute(self, params: KeeperParams, **kwargs): + # Defense in depth: registry also gates admin verbs after refreshing + # enforcements. Re-check here so direct execute() cannot skip the policy. + ensure_can_configure_workflow_settings(params, refresh=True, action='create') + record_uid, record = RecordResolver.resolve(params, kwargs.get('record')) RecordResolver.validate_workflow_record_type(record) record_uid_bytes = utils.base64_url_decode(record_uid) @@ -376,6 +386,8 @@ def get_parser(self): return WorkflowUpdateCommand.parser def execute(self, params: KeeperParams, **kwargs): + ensure_can_configure_workflow_settings(params, refresh=True, action='update') + record_uid, record = RecordResolver.resolve(params, kwargs.get('record')) record_uid_bytes = utils.base64_url_decode(record_uid) @@ -456,6 +468,8 @@ def get_parser(self): return WorkflowDeleteCommand.parser def execute(self, params: KeeperParams, **kwargs): + ensure_can_configure_workflow_settings(params, refresh=True, action='delete') + record_uid, record = RecordResolver.resolve(params, kwargs.get('record')) record_uid_bytes = utils.base64_url_decode(record_uid) ref = ProtobufRefBuilder.record_ref(record_uid_bytes, record.title) @@ -511,6 +525,8 @@ def get_parser(self): return WorkflowAddApproversCommand.parser def execute(self, params: KeeperParams, **kwargs): + ensure_can_configure_workflow_settings(params, refresh=True, action='add-approver') + users = list(dict.fromkeys( u.strip() for u in (kwargs.get('user') or []) if u and u.strip() )) @@ -582,6 +598,8 @@ def get_parser(self): return WorkflowDeleteApproversCommand.parser def execute(self, params: KeeperParams, **kwargs): + ensure_can_configure_workflow_settings(params, refresh=True, action='remove-approver') + users = kwargs.get('user') or [] teams = kwargs.get('team') or [] diff --git a/keepercommander/commands/workflow/helpers.py b/keepercommander/commands/workflow/helpers.py index 96e49c4fd..d0dc1999d 100644 --- a/keepercommander/commands/workflow/helpers.py +++ b/keepercommander/commands/workflow/helpers.py @@ -9,8 +9,10 @@ # Contact: ops@keepersecurity.com # +import logging import re import shlex +import time from typing import List, Optional, Tuple from ...error import CommandError @@ -24,6 +26,68 @@ ) _RESPONSE_CODE_RE = re.compile(r'\s*[Rr]esponse\s+code:\s*\S+\s*$') +# Enterprise role enforcement: "Can manage workflow settings" +WORKFLOW_SETTINGS_ENFORCEMENT_KEY = 'allow_configure_workflow_settings' + +# Avoid duplicate account-summary calls when registry + command both refresh. +_ENFORCEMENT_REFRESH_TTL_SEC = 2.0 +_ENFORCEMENT_REFRESH_ATTR = '_workflow_enforcement_refreshed_at' + + +def refresh_enforcements(params: KeeperParams) -> None: + """Reload account summary so revoked role enforcements take effect without re-login.""" + now = time.monotonic() + last = getattr(params, _ENFORCEMENT_REFRESH_ATTR, 0.0) + if not isinstance(last, (int, float)): + last = 0.0 + if now - last < _ENFORCEMENT_REFRESH_TTL_SEC: + return + try: + from ...loginv3 import LoginV3Flow + LoginV3Flow.populateAccountSummary(params) + setattr(params, _ENFORCEMENT_REFRESH_ATTR, now) + except Exception as e: + logging.error('Failed to refresh enforcements: %s', e, exc_info=True) + + +def can_configure_workflow_settings(params: KeeperParams, *, refresh: bool = False) -> bool: + """True when the user currently has allow_configure_workflow_settings. + + When refresh=True, reloads account summary first so a mid-session policy revoke + is visible (plain sync-down does not refresh enforcements). + """ + if refresh: + refresh_enforcements(params) + enforcements = getattr(params, 'enforcements', None) + if not enforcements or 'booleans' not in enforcements: + return False + booleans = enforcements.get('booleans') or [] + if not isinstance(booleans, list): + return False + return any( + bool(b.get('value')) + for b in booleans + if isinstance(b, dict) and b.get('key') == WORKFLOW_SETTINGS_ENFORCEMENT_KEY + ) + + +def ensure_can_configure_workflow_settings( + params: KeeperParams, + *, + refresh: bool = True, + action: str = 'manage', +) -> None: + """Raise CommandError unless the user may configure workflow settings.""" + if can_configure_workflow_settings(params, refresh=refresh): + return + raise CommandError( + '', + f'You do not have permission to manage workflow settings. ' + f'The "{action}" command requires the "Can manage workflow settings" ' + f'enforcement policy. Contact your Keeper administrator to enable this ' + f'for your role.', + ) + class DashUidArgsMixin: """Mixin for commands whose positional flow-UID arg may start with '-' (base64url).""" diff --git a/keepercommander/commands/workflow/registry.py b/keepercommander/commands/workflow/registry.py index 5353dfa52..8e8a4b66d 100644 --- a/keepercommander/commands/workflow/registry.py +++ b/keepercommander/commands/workflow/registry.py @@ -12,8 +12,7 @@ from ..base import GroupCommand, dump_report_data from ...display import bcolors -_ENFORCEMENT_KEY = 'allow_configure_workflow_settings' - +from .helpers import can_configure_workflow_settings from .config_commands import ( WorkflowCreateCommand, WorkflowReadCommand, @@ -43,14 +42,8 @@ class PAMWorkflowCommand(GroupCommand): _ADMIN_VERBS = frozenset({'create', 'update', 'delete', 'add-approver', 'remove-approver'}) @staticmethod - def _can_manage_workflows(params): - enforcements = getattr(params, 'enforcements', None) - if not enforcements or 'booleans' not in enforcements: - return False - return any( - b.get('value') for b in enforcements['booleans'] - if b.get('key') == _ENFORCEMENT_KEY - ) + def _can_manage_workflows(params, *, refresh=False): + return can_configure_workflow_settings(params, refresh=refresh) def execute_args(self, params, args, **kwargs): self._current_params = params @@ -59,7 +52,10 @@ def execute_args(self, params, args, **kwargs): verb = (args[:pos].strip() if pos > 0 else args.strip()).lower() if args else '' resolved_verb = self._aliases.get(verb, verb) - if resolved_verb in self._ADMIN_VERBS and not self._can_manage_workflows(params): + # Refresh enforcements before admin verbs so a mid-session revoke of + # "Can manage workflow settings" blocks create/update/delete equally. + # (Plain sync-down does not reload account enforcements.) + if resolved_verb in self._ADMIN_VERBS and not self._can_manage_workflows(params, refresh=True): print( f"\n{bcolors.WARNING}You do not have permission to manage workflow settings.{bcolors.ENDC}\n" f"The '{bcolors.BOLD}{resolved_verb}{bcolors.ENDC}' command requires the " diff --git a/keepercommander/discovery_common/__version__.py b/keepercommander/discovery_common/__version__.py index ec5c76229..b46c29a4b 100644 --- a/keepercommander/discovery_common/__version__.py +++ b/keepercommander/discovery_common/__version__.py @@ -1 +1 @@ -__version__ = '1.1.16' +__version__ = '1.1.22' diff --git a/keepercommander/discovery_common/process.py b/keepercommander/discovery_common/process.py index 33f5a5fd4..206dad667 100644 --- a/keepercommander/discovery_common/process.py +++ b/keepercommander/discovery_common/process.py @@ -68,9 +68,17 @@ class Process: # Hard limit for bulk record lists (safety mechanism) BULK_LIST_MAX_SIZE = 50000 - def __init__(self, record: Any, job_id: str, logger: Optional[Any] = None, debug_level: int = 0, **kwargs): + def __init__(self, + record: Any, + job_id: str, + record_lookup_func: Callable, + logger: Optional[Any] = None, + debug_level: int = 0, + **kwargs): + self.job_id = job_id self.record = record + self.record_lookup_func = record_lookup_func env_debug_level = os.environ.get("PROCESS_GS_DEBUG_LEVEL") if env_debug_level is not None: @@ -92,6 +100,7 @@ def __init__(self, record: Any, job_id: str, logger: Optional[Any] = None, debug logger=logger, debug_level=debug_level, record_linking=self.record_link, + record_lookup_func=record_lookup_func, **kwargs) # This is the root UID for all graphs; get it from one of them. @@ -421,7 +430,6 @@ def _directory_exists(self, domain: str, directory_info_func: Callable, context: def _find_directory_user(self, results: DirectoryResult, - record_lookup_func: Callable, context: Any, find_user: Optional[str] = None, find_dn: Optional[str] = None) -> Optional[DirectoryUserResult]: @@ -431,7 +439,7 @@ def _find_directory_user(self, self.logger.debug("search for directory user from vault records") self.logger.debug(f"have {len(results.directory_user_record_uids)} users") for user_record_id in results.directory_user_record_uids: - record = record_lookup_func(record_uid=user_record_id, context=context) # type: NormalizedRecord + record = self.record_lookup_func(user_record_id, context=context) # type: NormalizedRecord if record is not None: found = None self.logger.debug(f"find user {find_user}, dn {find_dn}") @@ -551,7 +559,6 @@ def _find_admin_directory_user(self, domain: str, admin_acl: UserAcl, directory_info_func: Callable, - record_lookup_func: Callable, context: Any, user: Optional[str] = None, dn: Optional[str] = None) -> Optional[str]: @@ -564,7 +571,6 @@ def _find_admin_directory_user(self, if results is not None: # Find the user (clean of domain) or DN in the found directories. directory_user = self._find_directory_user(results=results, - record_lookup_func=record_lookup_func, context=context, find_user=user, find_dn=dn) @@ -603,7 +609,6 @@ def _process_auto_add_level(self, current_vertex: DAGVertex, bulk_add_records: List[BulkRecordAdd], bulk_convert_records: List[BulkRecordConvert], - record_lookup_func: Callable, record_prepare_func: Callable, directory_info_func: Callable, record_cache: dict, @@ -617,7 +622,6 @@ def _process_auto_add_level(self, :param current_vertex: The current/parent discovery vertex. :param bulk_add_records: List of records to be added. :param bulk_convert_records: List of existing records to be covert to this gateway. - :params record_lookup_func: A function to lookup records to see if they exist. :param record_prepare_func: Function to convert content into an unsaved record. :param directory_info_func: Function to lookup directories. :param record_cache: @@ -672,7 +676,7 @@ def _process_auto_add_level(self, # not created a record for yet, however it might have been assigned a record UID from a prior prompt. existing_record = child_content.record_exists - if record_lookup_func is not None: + if self.record_lookup_func is not None: check_the_vault = True for item in bulk_add_records: if item.record_uid == child_content.record_uid: @@ -680,8 +684,8 @@ def _process_auto_add_level(self, check_the_vault = False break if check_the_vault: - existing_record = record_lookup_func(record_uid=child_content.record_uid, - context=context) is not None + existing_record = self.record_lookup_func(child_content.record_uid, + context=context) is not None self.logger.debug(f" record exists in the vault: {existing_record}") else: self.logger.debug(f" record lookup function not defined, record existing: {existing_record}") @@ -706,12 +710,12 @@ def _process_auto_add_level(self, # If the rules have set the admin_uid then connect the user to the resource. if (child_content.admin_uid is not None and child_content.record_type != PAM_USER - and record_lookup_func is not None): + and self.record_lookup_func is not None): self.logger.debug("the admin UID has been set for this resource") - admin_record = record_lookup_func(record_uid=child_content.admin_uid, - context=context) # type: NormalizedRecord + admin_record = self.record_lookup_func(child_content.admin_uid, + context=context) # type: NormalizedRecord if admin_record is not None and admin_record.record_type == PAM_USER: self.logger.debug("was able to find the admin record, connect to resource") admin_uid = child_content.admin_uid @@ -745,7 +749,6 @@ def _process_auto_add_level(self, current_vertex=vertex, bulk_add_records=bulk_add_records, bulk_convert_records=bulk_convert_records, - record_lookup_func=record_lookup_func, record_prepare_func=record_prepare_func, directory_info_func=directory_info_func, record_cache=record_cache, @@ -769,7 +772,6 @@ def _process_level(self, current_vertex: DAGVertex, bulk_add_records: List[BulkRecordAdd], bulk_convert_records: List[BulkRecordConvert], - record_lookup_func: Callable, prompt_func: Callable, prompt_admin_func: Callable, record_prepare_func: Callable, @@ -837,7 +839,7 @@ def _process_level(self, # not created a record for yet, however it might have been assigned a record UID from a prior prompt. existing_record = child_content.record_exists - if record_lookup_func is not None: + if self.record_lookup_func is not None: check_the_vault = True for item in bulk_add_records: if item.record_uid == child_content.record_uid: @@ -845,8 +847,8 @@ def _process_level(self, check_the_vault = False break if check_the_vault: - existing_record = record_lookup_func(record_uid=child_content.record_uid, - context=context) is not None + existing_record = self.record_lookup_func(child_content.record_uid, + context=context) is not None self.logger.debug(f" record exists in the vault: {existing_record}") else: self.logger.debug(f" record lookup function not defined, record existing: {existing_record}") @@ -965,12 +967,12 @@ def _process_level(self, self.logger.debug("checking if can add admin") # If the rule engine sets the admin UID - if child_content.admin_uid is not None and record_lookup_func is not None: + if child_content.admin_uid is not None and self.record_lookup_func is not None: self.logger.debug(f"the resource rule set the admin uid to {add_content.admin_uid}") - admin_record = record_lookup_func(record_uid=add_content.admin_uid, - context=context) # type: NormalizedRecord + admin_record = self.record_lookup_func(add_content.admin_uid, + context=context) # type: NormalizedRecord if admin_record is not None and admin_record.record_type == PAM_USER: self.logger.debug("was able to find the admin record, connect to resource") @@ -1015,7 +1017,6 @@ def _process_level(self, domain=source, admin_acl=acl, directory_info_func=directory_info_func, - record_lookup_func=record_lookup_func, context=context, user=add_content.access_user.user, dn=add_content.access_user.dn @@ -1040,7 +1041,6 @@ def _process_level(self, resource_content=add_content, bulk_add_records=bulk_add_records, bulk_convert_records=bulk_convert_records, - record_lookup_func=record_lookup_func, directory_info_func=directory_info_func, prompt_admin_func=prompt_admin_func, record_prepare_func=record_prepare_func, @@ -1069,7 +1069,6 @@ def _process_level(self, current_vertex=vertex, bulk_add_records=bulk_add_records, bulk_convert_records=bulk_convert_records, - record_lookup_func=record_lookup_func, prompt_func=prompt_func, prompt_admin_func=prompt_admin_func, record_prepare_func=record_prepare_func, @@ -1088,7 +1087,6 @@ def _process_admin_user(self, resource_content: DiscoveryObject, bulk_add_records: List[BulkRecordAdd], bulk_convert_records: List[BulkRecordConvert], - record_lookup_func: Callable, directory_info_func: Callable, prompt_admin_func: Callable, record_prepare_func: Callable, @@ -1199,7 +1197,6 @@ def _process_admin_user(self, domain=ad_domain, admin_acl=admin_acl, directory_info_func=directory_info_func, - record_lookup_func=record_lookup_func, context=context, user=admin_content.item.user, dn=admin_content.item.dn @@ -1366,7 +1363,6 @@ def run(self, prompt_func: Callable, record_prepare_func: Callable, smart_add: bool = False, - record_lookup_func: Optional[Callable] = None, record_create_func: Optional[Callable] = None, record_convert_func: Optional[Callable] = None, prompt_confirm_add_func: Optional[Callable] = None, @@ -1383,7 +1379,6 @@ def run(self, :param record_cache: A dictionary of record types to keys to record UID. :param prompt_func: Function to call when the user needs to make a decision about an object. :param smart_add: If we have resource cred, add the resource and the users. DEPRECATED - :param record_lookup_func: Function to look up a record by UID. :param record_prepare_func: Function to call to prepare a record to be created. :param record_create_func: Function to call to save the prepared records. :param record_convert_func: Function to convert record to use this gateway. @@ -1459,7 +1454,6 @@ def run(self, current_vertex=configuration, bulk_add_records=bulk_add_records, bulk_convert_records=bulk_convert_records, - record_lookup_func=record_lookup_func, record_prepare_func=record_prepare_func, directory_info_func=directory_info_func, record_cache=record_cache, @@ -1482,7 +1476,6 @@ def run(self, current_vertex=configuration, bulk_add_records=bulk_add_records, bulk_convert_records=bulk_convert_records, - record_lookup_func=record_lookup_func, prompt_func=prompt_func, prompt_admin_func=prompt_admin_func, record_prepare_func=record_prepare_func, diff --git a/keepercommander/discovery_common/rm_types.py b/keepercommander/discovery_common/rm_types.py index 1aa513d88..6829da93a 100644 --- a/keepercommander/discovery_common/rm_types.py +++ b/keepercommander/discovery_common/rm_types.py @@ -84,6 +84,105 @@ class RmGroup(BaseModel): users: List[RmUser] = [] +class RmPermission(BaseModel): + """ + A permission granted by a role, reduced to its action strings. + + :param id: Provider identifier for the permission source (policy ARN, role-definition id, etc.). + :param name: Human-readable name of the permission source. + :param source: Where the permission comes from, e.g. "managed-policy", "inline-policy", + "role-definition". + :param actions: The granted action strings (e.g. "s3:GetObject", "Microsoft.Compute/*"). + """ + id: Optional[str] = None + name: Optional[str] = None + source: Optional[str] = None + actions: List[str] = [] + + +class RmMappedUser(BaseModel): + """ + A user in the standing-permissions map, with the roles it holds. + + :param id: Provider identifier for the user. + :param name: Human-readable user name. + :param role_ids: Ids of the roles this user holds (references RmMappedRole.id). + """ + id: str + name: Optional[str] = None + role_ids: List[str] = [] + + +class RmMappedGroup(BaseModel): + """ + A group in the standing-permissions map, with its members and the roles it holds. + + :param id: Provider identifier for the group. + :param name: Human-readable group name. + :param user_ids: Ids of the users that are members of this group (references RmMappedUser.id). + :param role_ids: Ids of the roles this group holds (references RmMappedRole.id). + Empty for providers with no group-role concept (e.g. AWS IAM). + """ + id: str + name: Optional[str] = None + user_ids: List[str] = [] + role_ids: List[str] = [] + + +class RmMappedServiceAccount(BaseModel): + """ + A service account in the standing-permissions map, with the roles it holds. + + A service account is a non-human principal: an Azure service principal or managed identity, + a GCP service account, etc. + + :param id: Provider identifier for the service account. + :param name: Human-readable service account name. + :param role_ids: Ids of the roles this service account holds (references RmMappedRole.id). + """ + id: str + name: Optional[str] = None + role_ids: List[str] = [] + + +class RmMappedRole(BaseModel): + """ + A role in the standing-permissions map, with the permissions it grants. + + :param id: Provider identifier for the role. + :param name: Human-readable role name. + :param permissions: The permissions granted by this role. + """ + id: str + name: Optional[str] = None + permissions: List[RmPermission] = [] + + +class RmStandingPermissions(BaseModel): + """ + The standing-permissions map for one identity provider. + + Relationships are normalized: roles are listed once with their permissions, while users and + groups reference roles by id. Group membership is carried as user ids on each group. + + :param provider: The identity provider the map was built for, e.g. "aws", "azure". + :param configuration_uid: The PAM configuration record UID the map was built from. + :param supported: False when the provider does not support standing-permission mapping + (e.g. GCP, Okta); the principal/role lists are then empty. + :param users: Users and the roles they hold. + :param groups: Groups, their members, and the roles they hold. + :param service_accounts: Service accounts (non-human principals) and the roles they hold. + :param roles: Roles and the permissions they grant. + """ + provider: str + configuration_uid: Optional[str] = None + supported: bool = True + users: List[RmMappedUser] = [] + groups: List[RmMappedGroup] = [] + service_accounts: List[RmMappedServiceAccount] = [] + roles: List[RmMappedRole] = [] + + class RmMetaBase(BaseModel): pass @@ -159,6 +258,10 @@ class RmOktaUserAddMeta(RmMetaBase): password_reset_required: Optional[bool] = False password_reset_required_with_mfa: Optional[bool] = False groups: List[str] = [] + # Okta app-instance ids ("0oa...") to assign the user to directly at creation, in addition to + # group-based access. Used for launch-URL flows (the pamCloudResource "Account Identifier") so + # an ephemeral user can reach the specific app the session launches into. + app_ids: List[str] = [] class RmOktaGroupAddMeta(RmMetaBase): @@ -503,6 +606,10 @@ class RmMachineUserAddMeta(RmMetaBase): use_private_key_type: Optional[str] = "ecdsa_sha2_nistp521" private_key: Optional[str] = None private_key_passphrase: Optional[str] = None + private_key_format: Optional[str] = None + public_key_format: Optional[str] = None + private_key_encoding: Optional[str] = None + public_key_encoding: Optional[str] = None authorized_keys: List[str] = [] # SSH certificate diff --git a/keepercommander/discovery_common/types.py b/keepercommander/discovery_common/types.py index 83512ab9b..04ecbae81 100644 --- a/keepercommander/discovery_common/types.py +++ b/keepercommander/discovery_common/types.py @@ -346,6 +346,25 @@ def get_schedule(self) -> Optional[dict]: return json.loads(self.schedule) +class UserAclServiceNamesItem(BaseModel): + name: str + + # If this was added via Discovery, this will be True + via_discovery: bool = False + + +class ServiceEnum(BaseEnum): + service = "service" + task = "task" + iis_pool = "iis_pool" + dcom = "dcom" + + +class UserAclServiceNames(BaseModel): + type: ServiceEnum + items: List[UserAclServiceNamesItem] = [] + + class UserAcl(BaseModel): # Vault team may add attributes without telling others :( model_config = ConfigDict(extra='allow') @@ -368,6 +387,10 @@ class UserAcl(BaseModel): # Do we need to rotate service passwords on this machine when this password is rotated? controls_services: bool = False + # For the relationship between the user and machine, these are services the user controls. + # The value is encrypted. + service_names: Optional[str] = "" + rotation_settings: Optional[UserAclRotationSettings] = None @staticmethod @@ -379,6 +402,58 @@ def default(): rotation_settings=UserAclRotationSettings() ) + def md5(self, record_key_bytes: bytes) -> str: + """ + Make an MD5 signature of this ACL. + + Dictionary keys and list items are sorted so the signature is repeatable + regardless of the order items appear in an array. The encrypted service_names + is replaced with its decrypted content; encryption is not deterministic, so + hashing the ciphertext would change the signature even when the content did not. + """ + + def _normalize(value: Any) -> Any: + if isinstance(value, dict): + return {k: _normalize(v) for k, v in value.items()} + if isinstance(value, list): + return sorted( + (_normalize(item) for item in value), + key=lambda item: json.dumps(item, sort_keys=True) + ) + return value + + data = self.model_dump(mode="json") + data["service_names"] = [ + service_names.model_dump(mode="json") + for service_names in self.get_service_names(record_key_bytes) + ] + data = _normalize(data) + return hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest() + + def set_service_names(self, + service_names: Union[List[UserAclServiceNames], list, str, bytes], + record_key_bytes: bytes): + if isinstance(service_names, list): + service_names = json.dumps([ + item.model_dump(mode="json") if isinstance(item, UserAclServiceNames) else item + for item in service_names + ]) + if isinstance(service_names, str): + service_names = service_names.encode() + + if not isinstance(service_names, bytes): + raise ValueError("The service_names is not a list, string or is bytes.") + + self.service_names = base64.b64encode(CryptoUtils.encrypt_aes(service_names, record_key_bytes)).decode() + + def get_service_names(self, record_key_bytes: bytes) -> List[UserAclServiceNames]: + if self.service_names is None or self.service_names == "": + return [] + enc_bytes = base64.b64decode(self.service_names.encode()) + service_names_bytes = CryptoUtils.decrypt_aes(enc_bytes, record_key_bytes) + list_of_service_names = json.loads(service_names_bytes) + return [UserAclServiceNames.model_validate(item) for item in list_of_service_names] + # ------------- @@ -475,6 +550,7 @@ class Facts(BaseModel): services: List[FactsNameUser] = [] tasks: List[FactsNameUser] = [] iis_pools: List[FactsNameUser] = [] + dcoms: List[FactsNameUser] = [] @property def has_services(self): @@ -488,9 +564,13 @@ def has_tasks(self): def has_iis_pools(self): return self.iis_pools is not None and len(self.iis_pools) > 0 + @property + def has_dcoms(self): + return self.dcoms is not None and len(self.dcoms) > 0 + @property def has_service_items(self): - return self.has_services or self.has_tasks or self.has_iis_pools + return self.has_services or self.has_tasks or self.has_iis_pools or self.has_dcoms class DiscoveryMachine(DiscoveryItem): @@ -657,6 +737,7 @@ class NormalizedRecord(BaseModel): """ record_uid: str record_type: str + record_key_bytes: Optional[bytes] = None title: str fields: List[RecordField] = [] note: Optional[str] = None diff --git a/keepercommander/discovery_common/user_service.py b/keepercommander/discovery_common/user_service.py index 04aab341c..328903561 100644 --- a/keepercommander/discovery_common/user_service.py +++ b/keepercommander/discovery_common/user_service.py @@ -4,13 +4,14 @@ from .constants import PAM_MACHINE, PAM_USER, PAM_DIRECTORY, DOMAIN_USER_CONFIGS from .utils import get_connection, make_agent, split_user_and_domain, value_to_boolean -from .types import DiscoveryObject, ServiceAcl, NormalizedRecord, UserAcl +from .types import (DiscoveryObject, ServiceAcl, NormalizedRecord, UserAcl, UserAclServiceNames, + UserAclServiceNamesItem, ServiceEnum) from .infrastructure import Infrastructure from .record_link import RecordLink from ..keeper_dag import DAG, EdgeType from ..keeper_dag.types import PamGraphId import importlib -from typing import Any, Optional, List, Callable, Dict, TYPE_CHECKING +from typing import Any, Optional, List, Callable, Dict, Tuple, TYPE_CHECKING if TYPE_CHECKING: from ..keeper_dag.vertex import DAGVertex @@ -39,6 +40,7 @@ class UserService: def __init__(self, record_linking: RecordLink, record: Any, + record_lookup_func: Callable, logger: Optional[Any] = None, history_level: int = 0, debug_level: int = 0, @@ -47,15 +49,23 @@ def __init__(self, save_batch_count: int = 200, agent: Optional[str] = None, use_per_graph_endpoints: bool = False, + context: Optional[Dict] = None, **kwargs): # Keep these for other graphs self._params = kwargs.get("params") self._ksm = kwargs.get("ksm") self.record_linking = record_linking + self.record_lookup_func = record_lookup_func self.service_graph = None self.did_migration = False + if context is None: + context = {} + if self._params is not None: + context["params"] = self._params + self.context = context + self.conn = get_connection(**kwargs) # This will either be a KSM Record, or Commander KeeperRecord @@ -91,6 +101,8 @@ def __init__(self, except (Exception,): pass + self._infra = None + self._migrate() def debug(self, msg, level: int = 0, secret: bool = False): @@ -101,6 +113,21 @@ def debug(self, msg, level: int = 0, secret: bool = False): else: self.logger.debug(msg) + @property + def record_key_bytes(self) -> bytes: + # The KSM Record exposes record_key_bytes; the Commander KeeperRecord exposes record_key. + if hasattr(self.record, "record_key_bytes"): + return self.record.record_key_bytes + return self.record.record_key + + @property + def infra(self): + + if self._infra is None: + self._infra = Infrastructure(record=self.record, logger=self.logger, ksm=self._ksm, params=self._params) + self._infra.load(sync_point=0) + return self._infra + def _migrate(self): """ @@ -122,25 +149,93 @@ def _migrate(self): save_batch_count=self.save_batch_count, agent=self.agent) + # Cannot add service names via migration. Not enough data in graph to perform it. + + # First migration - ServiceACL + # If still using the service_graph, migrate to the PAM graph. + # Add a generic label per service type being used. self.service_graph.load(sync_point=0) - if self.service_graph.has_graph: - for resource_vertex in self.service_graph.get_root.has_vertices(): - for user_vertex in resource_vertex.has_vertices(): - acl_edge = user_vertex.get_edge(resource_vertex, edge_type=EdgeType.ACL) + updated_service_graph = False + if self.service_graph.has_graph and len(self.service_graph.get_root.has_vertices()) > 0: + self.logger.debug("migrate from user service graph") + for s_resource_vertex in self.service_graph.get_root.has_vertices(): + for s_user_vertex in s_resource_vertex.has_vertices(): + acl_edge = s_user_vertex.get_edge(s_resource_vertex, edge_type=EdgeType.ACL) if acl_edge is not None: + service_acl = acl_edge.content_as_object(ServiceAcl) # type: ServiceAcl if service_acl.is_used: - user_acl = self.record_linking.get_acl(user_vertex.uid, resource_vertex.uid) + self.logger.debug(f" * {s_resource_vertex.uid} <- {s_user_vertex.uid}") + + record = self.record_lookup_func(s_user_vertex.uid, context=self.context) + user_acl = self.record_linking.get_acl(s_user_vertex.uid, s_resource_vertex.uid) if user_acl is None: user_acl = UserAcl.default() - user_acl.controls_services = True - self.record_linking.belongs_to(user_vertex.uid, resource_vertex.uid, acl=user_acl) - user_vertex.delete() - resource_vertex.delete() + + if record is not None: + + # Set via_discovery=True so that discovery will remove these and replace with + # real services, tasks, and IIS pools. + + if service_acl.is_service: + self.add_service_name(acl=user_acl, + service_type=ServiceEnum.service, + service_name="Unknown Service(s)", + record_key_bytes=record.record_key_bytes, + via_discovery=True) + self.logger.debug(f" - user controls service") + + if service_acl.is_task: + self.add_service_name(acl=user_acl, + service_type=ServiceEnum.task, + service_name="Unknown Task(s)", + record_key_bytes=record.record_key_bytes, + via_discovery=True) + self.logger.debug(f" - user controls task") + + if service_acl.is_iis_pool: + self.add_service_name(acl=user_acl, + service_type=ServiceEnum.iis_pool, + service_name="Unknown IIS Pool(s)", + record_key_bytes=record.record_key_bytes, + via_discovery=True) + self.logger.debug(f" - user controls iis pool") + else: + self.logger.debug(f" !! could not find user record UID {s_user_vertex.uid}") + user_acl.controls_services = True + + # set_acl also links the resource to the root; without that link the + # migrated vertices would not survive a save/load of the graph. + self.set_acl(resource_uid=s_resource_vertex.uid, user_uid=s_user_vertex.uid, acl=user_acl) + s_user_vertex.delete() + s_resource_vertex.delete() + updated_service_graph = True self.did_migration = True + # Second Migration - UserACL + # If migrated to the PAM graph, and there are no service_names, add a generic label for all services. + for resource_vertex in self.record_linking.dag.get_root.has_vertices(): + for user_vertex in resource_vertex.has_vertices(): + user_acl = self.record_linking.get_acl(record_uid=user_vertex.uid, + parent_record_uid=resource_vertex.uid) + if user_acl: + # If the user controls services and the names are blank + if user_acl.controls_services and (user_acl.service_names is None + or user_acl.service_names == ""): + record = self.record_lookup_func(user_vertex.uid, context=self.context) + if record: + self.logger.debug(f" * {resource_vertex.uid} <- {user_vertex.uid}; add placeholder") + self.did_migration = True + self.add_service_name(acl=user_acl, + service_type=ServiceEnum.service, + service_name="Unknown Service, Task, or IIS Pools", + record_key_bytes=record.record_key_bytes, + via_discovery=True) + self.record_linking.belongs_to(user_vertex.uid, resource_vertex.uid, acl=user_acl) + if self.did_migration: - self.service_graph.save() + if updated_service_graph: + self.service_graph.save() self.record_linking.save() self.debug(" items were migrated") else: @@ -156,6 +251,7 @@ def close(self): self.conn = None self._params = None self._ksm = None + self._infra = None def __enter__(self): """Context manager entry.""" @@ -331,63 +427,15 @@ def to_dot(self, graph_format: str = "svg", graph_type: str = "dot"): return dot - def _init_cleanup_user_mapping(self): - - """ - Create of mapping of existing user services to see what was updated. - - This is the basically graph in dictionary format with the update flag set to False. - """ - - self.cleanup_mapping = {} - for resource_vertex in self.record_linking.dag.get_root.has_vertices(): - if resource_vertex.uid not in self.cleanup_mapping: - self.cleanup_mapping[resource_vertex.uid] = {} - for user_vertex in resource_vertex.has_vertices(): - acl = self.record_linking.get_acl(user_vertex.uid, resource_vertex.uid) - if acl is not None and acl.controls_services: - self.cleanup_mapping[resource_vertex.uid][user_vertex.uid] = False - - def _user_is_used(self, machine_record_uid: str, user_record_uid: str): - - """ - Flag the user exists for a machine. - """ - - if machine_record_uid in self.cleanup_mapping and user_record_uid in self.cleanup_mapping[machine_record_uid]: - self.cleanup_mapping[machine_record_uid][user_record_uid] = True - - def _cleanup_users(self): - - """ - Disconnect all users from machines that are not used. - """ - - self.debug("cleaning up unused user service relationships") - - did_something = False - for machine_record_uid in self.cleanup_mapping: - for user_record_uid in self.cleanup_mapping[machine_record_uid]: - if not self.cleanup_mapping[machine_record_uid][user_record_uid]: - acl = self.record_linking.get_acl(user_record_uid, machine_record_uid) - if acl is not None and acl.controls_services: - self.debug(f" * set controls_services for user {user_record_uid} " - f"from machine {machine_record_uid} to false") - did_something = True - acl.controls_services = False - self.record_linking.belongs_to(user_record_uid, machine_record_uid, acl=acl) - if not did_something: - self.debug(f" nothing to cleanup") - - @staticmethod - def _get_local_users_from_record(record_lookup_func: Callable, - rl_machine_vertex: DAGVertex) -> Dict[str, str]: + def _get_local_users_from_record(self, rl_machine_vertex: DAGVertex) -> Dict[str, str]: # Get the local users user_records: Dict[str, str] = {} for rl_user_vertex in rl_machine_vertex.has_vertices(): - record = record_lookup_func(rl_user_vertex.uid, allow_sm=False) # type: NormalizedRecord + record = self.record_lookup_func(rl_user_vertex.uid, + context=self.context, + allow_sm=False) # type: NormalizedRecord if record and record.record_type == PAM_USER: user = record.get_user() if user is not None: @@ -404,16 +452,14 @@ def _get_local_users_from_record(record_lookup_func: Callable, return user_records - @staticmethod - def _get_local_users_from_infra(record_lookup_func: Callable, - infra_machine_vertex: DAGVertex) -> Dict[str, str]: + def _get_local_users_from_infra(self, infra_machine_vertex: DAGVertex) -> Dict[str, str]: user_records: Dict[str, str] = {} for infra_user_vertex in infra_machine_vertex.has_vertices(): user_content = DiscoveryObject.get_discovery_object(infra_user_vertex) if user_content.record_type != PAM_USER or user_content.record_uid is None: continue - if record_lookup_func(user_content.record_uid, allow_sm=False): + if self.record_lookup_func(user_content.record_uid, context=self.context, allow_sm=False): user, domain = split_user_and_domain(user_content.item.user.lower()) if domain is not None: user += "@" + domain @@ -423,15 +469,15 @@ def _get_local_users_from_infra(record_lookup_func: Callable, def _get_directory_users_from_conf_record(self, domain_name: str, - record_lookup_func: Callable, netbios: Optional[str] = None) -> Dict[str, str]: user_records: Dict[str, str] = {} # check if a PAM configuration that support having users (Azure, Domain Controller) # We need to get the normalized record of the configuration record. - configuration_record = record_lookup_func( - self.conn.get_record_uid(self.record), allow_sm=False) # type: NormalizedRecord + configuration_record = self.record_lookup_func( self.conn.get_record_uid(self.record), + context=self.context, + allow_sm=False) # type: NormalizedRecord if configuration_record.record_type in DOMAIN_USER_CONFIGS: # The Domain Controller record will have the domain; Azure record will not. config_domain_name = configuration_record.get_value(label="pamdomainid") @@ -441,7 +487,9 @@ def _get_directory_users_from_conf_record(self, or (config_domain_name.lower() == domain_name or config_domain_name.lower() == netbios)): config_vertex = self.record_linking.dag.get_vertex(configuration_record.record_uid) for child_vertex in config_vertex.has_vertices(): - user_record = record_lookup_func(child_vertex.uid, allow_sm=False) # type: NormalizedRecord + user_record = self.record_lookup_func(child_vertex.uid, + context=self.context, + allow_sm=False) # type: NormalizedRecord if not user_record: # self.debug(f" * record uid {child_vertex.uid} not found") continue @@ -471,7 +519,6 @@ def _get_directory_users_from_conf_record(self, def _get_directory_users_from_conf_infra(self, infra: Infrastructure, domain_name: str, - record_lookup_func: Callable, netbios: Optional[str] = None) -> Dict[str, str]: user_records: Dict[str, str] = {} @@ -485,8 +532,9 @@ def _get_directory_users_from_conf_infra(self, self.debug(f" domain name {config_domain_name} MATCHED {domain_name}/{netbios}") for child_vertex in config_vertex.has_vertices(): child_context = DiscoveryObject.get_discovery_object(child_vertex) - if child_context.record_type == PAM_USER and record_lookup_func(child_context.record_uid, - allow_sm=False): + if child_context.record_type == PAM_USER and self.record_lookup_func(child_context.record_uid, + context=self.context, + allow_sm=False): user, domain = split_user_and_domain(child_context.item.user.lower()) if domain is None: domain = domain_name @@ -499,15 +547,16 @@ def _get_directory_users_from_conf_infra(self, return user_records def _get_directory_users_from_records(self, - domain_name: str, - record_lookup_func: Callable) -> Dict[str, str]: + domain_name: str) -> Dict[str, str]: user_records: Dict[str, str] = {} # From the record linking graph, check each record connected to the configuration to see if it is a # PAM directory record. for rl_resource_vertex in self.record_linking.dag.get_root.has_vertices(): - directory_record = record_lookup_func(rl_resource_vertex.uid, allow_sm=False) # type: NormalizedRecord + directory_record = self.record_lookup_func(rl_resource_vertex.uid, + context=self.context, + allow_sm=False) # type: NormalizedRecord if directory_record and directory_record.record_type == PAM_DIRECTORY: record_domain_name = directory_record.get_value(label="domainName") if record_domain_name is None: @@ -517,7 +566,9 @@ def _get_directory_users_from_records(self, if record_domain_name.lower() == domain_name: self.debug(f" record uid {rl_resource_vertex.uid} matches the domain name") for rl_user_vertex in rl_resource_vertex.has_vertices(): - user_record = record_lookup_func(rl_user_vertex.uid, allow_sm=False) # type: NormalizedRecord + user_record = self.record_lookup_func(rl_user_vertex.uid, + context=self.context, + allow_sm=False) # type: NormalizedRecord if user_record is None or user_record.record_type != PAM_USER: continue @@ -541,10 +592,9 @@ def _get_directory_users_from_records(self, return user_records - @staticmethod - def _get_directory_users_from_infra(infra_machine_vertex: DAGVertex, - domain_name: str, - record_lookup_func: Callable) -> Dict[str, str]: + def _get_directory_users_from_infra(self, + infra_machine_vertex: DAGVertex, + domain_name: str) -> Dict[str, str]: user_records: Dict[str, str] = {} @@ -561,7 +611,7 @@ def _get_directory_users_from_infra(infra_machine_vertex: DAGVertex, user_content = DiscoveryObject.get_discovery_object(user_vertex) if user_content.record_type != PAM_USER and user_content.record_uid is None: continue - if record_lookup_func(user_content.record_uid, allow_sm=False): + if self.record_lookup_func(user_content.record_uid, context=self.context, allow_sm=False): # Format the username to be user@domain user, domain = split_user_and_domain(user_content.item.user.lower()) @@ -575,7 +625,6 @@ def _get_users(self, infra: Infrastructure, infra_machine_content: DiscoveryObject, infra_machine_vertex: DAGVertex, - record_lookup_func: Callable, netbios: Optional[str] = None, domain_name: Optional[str] = None) -> Dict[str, str]: @@ -607,10 +656,15 @@ def _get_users(self, # Once we get directory users for a domain name, they will not change. # Cache them so we don't have to get them again. if self.directory_user_cache is not None: - directory_user_records = self.directory_user_cache.get(domain_name) - self.debug(f" using directory user cache for {domain_name}, " - f"{len(directory_user_records)} users") - using_directory_user_cache = True + directory_user_records = self.directory_user_cache.get(domain_name, {}) + if directory_user_records is not None and len(directory_user_records) > 0: + self.debug(f" using directory user cache for {domain_name}, " + f"{len(directory_user_records)} users") + using_directory_user_cache = True + # If directory_user_records is None, make sure it's an empty dictionary. + # We might try to merge dictionaries; we don't want it None. + else: + directory_user_records = {} ########################### @@ -623,8 +677,7 @@ def _get_users(self, # Get the local users from records self.debug(" getting local users from records", level=1) - user_records = self._get_local_users_from_record(rl_machine_vertex=record_link_vertex, - record_lookup_func=record_lookup_func) + user_records = self._get_local_users_from_record(rl_machine_vertex=record_link_vertex) self.debug(f" * found {len(user_records)} local users from records", level=1) local_user_records = {**local_user_records, **user_records} @@ -632,16 +685,14 @@ def _get_users(self, self.debug(" getting directory users from the configuration record", level=1) user_records = self._get_directory_users_from_conf_record(domain_name=domain_name, - netbios=netbios, - record_lookup_func=record_lookup_func) + netbios=netbios) self.debug(f" * found {len(user_records)} directory users records from " "the configuration record", level=1) directory_user_records = {**directory_user_records, **user_records} self.debug(" getting directory users from directory records", level=1) - user_records = self._get_directory_users_from_records(domain_name=domain_name, - record_lookup_func=record_lookup_func) + user_records = self._get_directory_users_from_records(domain_name=domain_name) self.debug(f" * found {len(user_records)} directory users from records for {domain_name}", level=1) @@ -653,8 +704,7 @@ def _get_users(self, self.debug(f" getting users from infrastructure", level=1) self.debug(" getting local users from infrastructure", level=1) - user_records = self._get_local_users_from_infra(infra_machine_vertex=infra_machine_vertex, - record_lookup_func=record_lookup_func) + user_records = self._get_local_users_from_infra(infra_machine_vertex=infra_machine_vertex) self.debug(f" * found {len(user_records)} local users from graph", level=1) local_user_records = {**user_records, **local_user_records} @@ -662,8 +712,7 @@ def _get_users(self, self.debug(" getting directory users from configuration infrastructure", level=1) user_records = self._get_directory_users_from_conf_infra(infra=infra, - domain_name=domain_name, - record_lookup_func=record_lookup_func) + domain_name=domain_name) self.debug(f" * found {len(user_records)} directory users from configuration for {domain_name}", level=1) directory_user_records = {**user_records, **directory_user_records} @@ -672,9 +721,9 @@ def _get_users(self, self.debug(" getting directory users from directory infrastructure", level=1) user_records = self._get_directory_users_from_infra(infra_machine_vertex=infra_machine_vertex, - domain_name=domain_name, - record_lookup_func=record_lookup_func) - self.debug(f" * found {len(user_records)} directory users from graph for {domain_name}", level=1) + domain_name=domain_name) + self.debug(f" * found {len(user_records)} directory users from graph for {domain_name}", + level=1) directory_user_records = {**user_records, **directory_user_records} # If we were not using the directory cache, cache them. @@ -689,14 +738,91 @@ def _get_users(self, return all_record - def _connect_users_to_services(self, - infra: Infrastructure, - infra_machine_content: DiscoveryObject, - infra_machine_vertex: DAGVertex, - record_lookup_func: Callable, - strict: bool = False, - domain_name: Optional[str] = None, - netbios: Optional[str] = None): + @staticmethod + def clear_discovery_service_names(acl: UserAcl, record_key_bytes: bytes): + + """ + Remove all the service names added by discovery from the list. + """ + + new_service_names = [] + for service_names in acl.get_service_names(record_key_bytes): + service_names.items = [item for item in service_names.items if not item.via_discovery] + if service_names.items: + new_service_names.append(service_names) + acl.set_service_names(new_service_names, record_key_bytes) + + @staticmethod + def add_service_name(acl: UserAcl, + service_type: ServiceEnum, + service_name: str, + record_key_bytes: bytes, + via_discovery: bool = False): + + all_service_names = acl.get_service_names(record_key_bytes) + + service_names = None + for name in all_service_names: + if name.type == service_type: + service_names = name + break + + if service_names is None: + service_names = UserAclServiceNames(type=service_type) + all_service_names.append(service_names) + + for item in service_names.items: + if item.name.lower() == service_name.lower(): + return + + service_names.items.append( + UserAclServiceNamesItem( + name=service_name, + via_discovery=via_discovery) + ) + + acl.set_service_names(all_service_names, record_key_bytes) + + if len(all_service_names) > 0: + acl.controls_services = True + + @staticmethod + def remove_service_name(acl: UserAcl, + record_key_bytes: bytes, + service_type: Optional[ServiceEnum] = None, + service_name: Optional[str] = None): + + # Without a service type, clear all the service names for all types. + if service_type is None: + acl.service_names = "" + return + + all_service_names = acl.get_service_names(record_key_bytes) + + new_service_names = [] + for service_names in all_service_names: + if service_names.type == service_type: + # Without a service name, remove all the items for this service type. + if service_name is None: + continue + service_names.items = [item for item in service_names.items + if item.name.lower() != service_name.lower()] + if not service_names.items: + continue + new_service_names.append(service_names) + + if len(new_service_names) == 0: + acl.controls_services = False + + acl.set_service_names(new_service_names, record_key_bytes) + + def _connect_users_to_machine_services(self, + infra: Infrastructure, + infra_machine_content: DiscoveryObject, + infra_machine_vertex: DAGVertex, + strict: bool = False, + domain_name: Optional[str] = None, + netbios: Optional[str] = None): if domain_name is None: for directory in infra_machine_content.item.facts.directories: @@ -715,19 +841,41 @@ def _connect_users_to_services(self, if hasattr(config_object.item, "info"): netbios = config_object.item.info.get("netbios") + # We don't want to keep update the ACL in the graph, it will make a lot of edges. + # Update the cache, then update the graph at the end. + acl_cache: Dict[str, Tuple] = {} + acl_md5_cache: Dict[str, str] = {} + + # These are the user available on a machine. + users = self._get_users(infra=infra, + infra_machine_content=infra_machine_content, + infra_machine_vertex=infra_machine_vertex, + netbios=netbios, + domain_name=domain_name) + + if self.log_finer_level >= 2 and self.insecure_debug: + for k, v in users.items(): + self.debug(f" > {k} = {v}") + # Add mapping from user to machine, that control services. - for service_type in ["service", "task", "iis_pool"]: + for service_type in [ServiceEnum.service, ServiceEnum.task, ServiceEnum.iis_pool]: self.debug("-" * 40) - self.debug(f"processing {service_type}s for {infra_machine_content.name} " + self.debug(f"processing {service_type.value}s for {infra_machine_content.name} " f"({infra_machine_vertex.uid})") - # We don't care about the name of the service, we just need a list users. + # Get the pair of name of the service and the user that controls it. + # This is from discovery. + service_pairs = getattr(infra_machine_content.item.facts, f"{service_type.value}s") + if len(service_pairs) == 0: + self.debug(" no users control this type of service, skipping") + continue + + for service_pair in service_pairs: + self.debug(f" * {service_type.value}: {service_pair.name} ({service_pair.user})", secret=True) - service_users = [] - for service_user in getattr(infra_machine_content.item.facts, f"{service_type}s"): - self.debug(f" * {service_type}: {service_user.name} ({service_user.user})", secret=True) - user = service_user.user.lower() + user = service_pair.user.lower() + service_users = [] if not strict: user, domain = split_user_and_domain(user) if user is not None: @@ -742,48 +890,56 @@ def _connect_users_to_services(self, else: service_users.append(user) + # de-dup the list service_users = list(set(service_users)) if len(service_users) == 0: - self.debug(f" no users control {service_type}s, skipping.") + self.debug(f" no users control {service_type.value}s, skipping.") continue - users = self._get_users(infra=infra, - infra_machine_content=infra_machine_content, - infra_machine_vertex=infra_machine_vertex, - record_lookup_func=record_lookup_func, - netbios=netbios, - domain_name=domain_name) - - if self.log_finer_level >= 2 and self.insecure_debug: - for k, v in users.items(): - self.debug(f" > {k} = {v}") - - self.debug(f" users to check: {service_users}", secret=True) - for service_user in service_users: - self.debug(f" . {service_user}", secret=True) - if service_user in users: - record_uid = users[service_user] - self.debug(f" found user {service_user} for {service_type}", secret=True) - acl = self.get_acl(infra_machine_content.record_uid, record_uid) - if acl is None: - acl = UserAcl().default() - - # Flag the user was found; don't disconnect - self._user_is_used(machine_record_uid=infra_machine_content.record_uid, - user_record_uid=record_uid) - - # Only update if the attribute is currently False; reduce edges. - if not acl.controls_services: + self.debug(f" users to check: {service_users}", secret=True) + for service_user in service_users: + self.debug(f" . {service_user}", secret=True) + if service_user in users: + record_uid = users[service_user] + self.debug(f" found user {service_user} for {service_type.value}", secret=True) + + # Get the ACL edge from the cache. + # If not there load it from the graph. + # If not in the graph, create it, and cache it for the next time. + acl, record_key_bytes = acl_cache.get(record_uid, (None, None)) + if acl is None: + acl = self.get_acl(infra_machine_content.record_uid, record_uid) + if acl is None: + acl = UserAcl.default() + record = self.record_lookup_func(record_uid, context=self.context) # type: NormalizedRecord + record_key_bytes = record.record_key_bytes + acl_cache[record_uid] = (acl, record_key_bytes) + acl_md5_cache[record_uid] = acl.md5(record_key_bytes) + + # Remove all the items marked as discovery. + self.clear_discovery_service_names(acl, record_key_bytes) + acl.controls_services = True - self.set_acl(resource_uid=infra_machine_content.record_uid, - user_uid=record_uid, - acl=acl) + self.add_service_name(acl, + service_name=service_pair.name, + service_type=service_type, + record_key_bytes=record_key_bytes, + via_discovery=True) + + # Update the ACL for the machine. + for record_uid, acl_tuple in acl_cache.items(): + new_acl, record_key_bytes = acl_tuple + if new_acl.md5(record_key_bytes) != acl_md5_cache[record_uid]: + self.set_acl(resource_uid=infra_machine_content.record_uid, + user_uid=record_uid, + acl=new_acl) + acl_cache.clear() + acl_md5_cache.clear() def _get_resource_info(self, record_uid: str, infra: Infrastructure, - record_lookup_func: Callable, record_types: Optional[List[str]] = None) -> Optional[NormalizedRecord]: """ @@ -795,7 +951,7 @@ def _get_resource_info(self, """ # Check the record first; return a NormalizedRecord - record = record_lookup_func(record_uid, allow_sm=False) # type: NormalizedRecord + record = self.record_lookup_func(record_uid, context=self.context, allow_sm=False) # type: NormalizedRecord if record is not None: self.debug(f" resource is {record.title}") if record_types is not None and record.record_type not in record_types: @@ -830,7 +986,6 @@ def run_user(self): pass def run_full(self, - record_lookup_func: Callable, infra: Optional[Infrastructure] = None, domain_name: Optional[str] = None, netbios: Optional[str] = None, @@ -841,7 +996,6 @@ def run_full(self, This is driven by the record linking graph. :param infra: Instance of Infrastructure graph. - :param record_lookup_func: A function that will return a record by record id. Returns a normalize record. :param domain_name: Domain name if there is a directory (i.e. example.com) :param netbios: NetBIOS of the domain controller (i.e. EXMAPLE) """ @@ -856,10 +1010,6 @@ def run_full(self, created_infra = False try: - - # Make of map of the current user to machine relationship. - self._init_cleanup_user_mapping() - if not infra: infra = Infrastructure(record=self.record, logger=self.logger, ksm=self._ksm, params=self._params) infra.load(sync_point=0) @@ -877,7 +1027,6 @@ def run_full(self, # The results is a NormalizedRecord. machine_record = self._get_resource_info(record_uid=rl_resource_vertex.uid, infra=infra, - record_lookup_func=record_lookup_func, record_types=[PAM_MACHINE]) if machine_record is None: @@ -925,22 +1074,14 @@ def run_full(self, user_service_machine_vertex.belongs_to_root(EdgeType.LINK) self.debug("-" * 40) - self._connect_users_to_services( + self._connect_users_to_machine_services( infra=infra, infra_machine_content=infra_machine_content, infra_machine_vertex=infra_machine_vertex, - record_lookup_func=record_lookup_func, domain_name=domain_name, netbios=netbios) self.debug("-" * 40) - # Disconnect any users not used. - # TODO - Handle this better. - # If a machine is off, or we cannot connect, we might disconnect users. - # We can uses the infrastructure graph to see this. - # This needs more testing. - # self._cleanup_users() - self.save() except Exception as err: diff --git a/keepercommander/importer/commands.py b/keepercommander/importer/commands.py index 5fae4198a..fef8a3b83 100644 --- a/keepercommander/importer/commands.py +++ b/keepercommander/importer/commands.py @@ -61,6 +61,8 @@ def register_command_info(aliases, command_info): help='display records to be imported without importing them') import_parser.add_argument('-s', '--shared', dest='shared', action='store_true', help='import folders as Keeper shared folders') +import_parser.add_argument('--nsf', dest='use_nsf', action='store_true', + help='import folders and records into Nested Share Folders (works with any --format)') import_parser.add_argument('-p', '--permissions', dest='permissions', action='store', help='default shared folder permissions: manage (U)sers, manage (R)ecords, can (E)dit, can (S)hare, or (A)ll, (N)one') import_parser.add_argument('--update', dest='update_flag', action='store_true', @@ -68,7 +70,7 @@ def register_command_info(aliases, command_info): import_parser.add_argument('--no-shortcuts', dest='no_shortcuts', action='store_true', help='disable shortcut handling (only new records)') import_parser.add_argument('--users', dest='users_only', action='store_true', - help='update shared folder user permissions only') + help='update shared folder / NSF user permissions only') import_parser.add_argument('--record-type', dest='record_type', action='store', help='Import legacy records as record type') import_parser.add_argument('--login-type', '-l', dest='login_type', action='store_true', @@ -84,7 +86,8 @@ def register_command_info(aliases, command_info): import_parser.add_argument('--secret-ids', dest='secret_ids', action='store', help='Comma separated list of secret IDs to fetch (Thycotic)') import_parser.add_argument( - 'name', type=str, help='file name (json, csv, keepass, 1password), account name (lastpass), or URL (ManageEngine, Thycotic)' + 'name', type=str, + help='file name (json, csv , keepass, 1password), account name (lastpass), or URL (ManageEngine, Thycotic). ' ) import_parser.error = raise_parse_exception import_parser.exit = suppress_exit @@ -163,6 +166,9 @@ def register_command_info(aliases, command_info): To load the sample data: import --format=csv sample_data/import.csv + +Nested Share Folders (NSF): +import --format=csv --nsf sample_data/import_nsf.csv ''' json_instructions = '''JSON Import Instructions @@ -175,6 +181,23 @@ def register_command_info(aliases, command_info): To load the sample file into your vault, run this command: import --format=json sample_data/import.json.txt + +Nested Share Folders (NSF) — works with any --format (json, csv, keepass, …): +import --format=json --nsf sample_data/import_nsf.txt + +With --nsf, shared_folders[].permissions from JSON are granted on Nested +Share Folders after folders and records are created. Prefer NSF roles: + + "permissions": [ + { "name": "user@company.com", "role": "viewer" }, + { "name": "lead@company.com", "role": "content-manager" }, + { "name": "admin@company.com", "role": "full-manager" } + ] + +Accepted roles: requestor, viewer, share-manager, content-manager, +content-share-manager, full-manager. +Sample: sample_data/import_nsf_permissions.txt +Use --users --nsf to update NSF permissions only (no record import). ''' @@ -216,7 +239,9 @@ def execute(self, params, **kwargs): permissions = kwargs.get('permissions') or '' del kwargs['permissions'] - if kwargs.get('shared') is True and not permissions: + # Classic --shared prompts for default U/R/E/S permissions. + # NSF uses roles from the import file (and optional -p); do not prompt. + if kwargs.get('shared') is True and not kwargs.get('use_nsf') and not permissions: permissions = user_choice('Default shared folder permissions: manage (U)sers, manage (R)ecords, can (E)dit, can (S)hare, or (A)ll, (N)one', 'uresan', show_choice=False, multi_choice=True) if permissions: chars = set() diff --git a/keepercommander/importer/imp_exp.py b/keepercommander/importer/imp_exp.py index 912ab9ea4..1acb9ed0b 100644 --- a/keepercommander/importer/imp_exp.py +++ b/keepercommander/importer/imp_exp.py @@ -706,9 +706,41 @@ def import_user_permissions(params, logging.info("%d user(s) removed from shared folders", users_removed) +def _strip_path_prefix(records, folders, prefix): + # type: (List[ImportRecord], List[ImportSharedFolder], str) -> None + """Remove an import --folder prefix from record/shared-folder paths.""" + if not prefix: + return + prefix_l = prefix.lower() + delim = PathDelimiter + + def strip_one(path): + if not path: + return path + path_l = path.lower() + if path_l == prefix_l: + return '' + if path_l.startswith(prefix_l + delim): + return path[len(prefix) + len(delim):] + return path + + for rec in records or []: + if not rec.folders: + continue + for fol in rec.folders: + if fol.domain: + fol.domain = strip_one(fol.domain) + if fol.path: + fol.path = strip_one(fol.path) + for fol in folders or []: + if fol.path: + fol.path = strip_one(fol.path) + + def _import(params, file_format, filename, **kwargs): """Import records from one of a variety of sources.""" shared = kwargs.get('shared') or False + use_nsf = kwargs.get('use_nsf') or False import_users = kwargs.get('users_only') or False old_domain = kwargs.get('old_domain') new_domain = kwargs.get('new_domain') @@ -735,6 +767,9 @@ def _import(params, file_format, filename, **kwargs): filter_folder_lower = filter_folder.lower() if isinstance(filter_folder, str) else '' + # When --nsf is set, skip classic shared-folder domain splitting. + classic_shared = shared and not use_nsf + for x in importer.execute(filename, params=params, users_only=import_users, filter_folder=filter_folder, old_domain=old_domain, new_domain=new_domain, tmpdir=tmpdir, secret_ids=secret_ids, dry_run=dry_run): if isinstance(x, ImportRecord): @@ -759,11 +794,11 @@ def _import(params, file_format, filename, **kwargs): else: continue - if shared or import_into: + if classic_shared or import_into or use_nsf: if not x.folders: x.folders = [ImportFolder()] for f in x.folders: - if shared: + if classic_shared: d_comps = list(path_components(f.domain)) if f.domain else [] p_comps = list(path_components(f.path)) if f.path else [] if len(d_comps) > 0: @@ -789,7 +824,7 @@ def _import(params, file_format, filename, **kwargs): logging.info(ce.message) records.append(x) elif isinstance(x, ImportSharedFolder): - if shared: + if classic_shared: continue if filter_folder and not importer.support_folder_filter(): name = x.path.lower().lstrip('\\') @@ -803,18 +838,40 @@ def _import(params, file_format, filename, **kwargs): folders.append(x) - if import_users: - import_user_permissions(params, folders) - return - - sync_down.sync_down(params) - manage_users = kwargs.get('manage_users') or False manage_records = kwargs.get('manage_records') or False can_edit = kwargs.get('can_edit') or False can_share = kwargs.get('can_share') or False - if shared: + if import_users: + if use_nsf: + from .nsf_import import apply_nsf_folder_permissions, prepare_nsf_folders + sync_down.sync_down(params) + prepare_nsf_folders(params, folders, [], '') + apply_nsf_folder_permissions( + params, folders, manage_users, manage_records, can_edit, can_share) + else: + import_user_permissions(params, folders) + return + + sync_down.sync_down(params) + + nsf_base_parent = '' + if import_into: + from .nsf_import import resolve_nsf_folder + resolved_nsf = resolve_nsf_folder(params, import_into) + if resolved_nsf: + use_nsf = True + # If --folder was an NSF UID, treat it as parent and strip the UID prefix. + if import_into == resolved_nsf: + nsf_base_parent = resolved_nsf + _strip_path_prefix(records, folders, import_into) + + if use_nsf: + from .nsf_import import flatten_record_folder_paths + flatten_record_folder_paths(records) + + if classic_shared: sfol = set() for r in records: if r.folders: @@ -830,52 +887,58 @@ def _import(params, file_format, filename, **kwargs): sf.can_share = can_share folders.append(sf) - # shared folder mapping + # shared folder mapping (classic only) sf_map = {} # type: Dict[str, str] - for shared_folder_uid in params.shared_folder_cache: - folder = params.folder_cache.get(shared_folder_uid) - if not folder: - continue - if folder.parent_uid: - sf_path = get_folder_path(params, folder.parent_uid) - sf_path.strip(PathDelimiter) - else: - sf_path = '' - sf_name = folder.name.strip() - if ' - ' in sf_name: - sf_from_name = sf_name.replace(' - ', PathDelimiter) - sf_to_name = folder.name - if sf_path: - sf_from_name = sf_path + PathDelimiter + sf_from_name - sf_to_name = sf_path + PathDelimiter + sf_to_name - sf_map[sf_from_name.lower()] = sf_to_name - if len(sf_map) > 0: - sf_keys = list(sf_map.keys()) - sf_keys.sort() - for record in records: - if isinstance(record.folders, list) and len(record.folders) > 0: - for fol in record.folders: - path = fol.domain or '' - if fol.path: - if path: - path += PathDelimiter - path += fol.path - path_l = path.lower() - idx = bisect.bisect_right(sf_keys, path_l) - if 0 <= idx < len(sf_map) and path_l == sf_keys[idx]: - fol.domain = sf_map[path_l] - fol.path = '' - elif 0 < idx <= len(sf_map) and path_l.startswith(sf_keys[idx - 1]): - sf_name = sf_keys[idx - 1] - fol.domain = sf_map[sf_name] - fol.path = (path[len(sf_name):]).strip(PathDelimiter) - - folder_add = prepare_folder_add(params, folders, records, manage_users, manage_records, can_edit, can_share) - if folder_add: + if not use_nsf: + for shared_folder_uid in params.shared_folder_cache: + folder = params.folder_cache.get(shared_folder_uid) + if not folder: + continue + if folder.parent_uid: + sf_path = get_folder_path(params, folder.parent_uid) + sf_path.strip(PathDelimiter) + else: + sf_path = '' + sf_name = folder.name.strip() + if ' - ' in sf_name: + sf_from_name = sf_name.replace(' - ', PathDelimiter) + sf_to_name = folder.name + if sf_path: + sf_from_name = sf_path + PathDelimiter + sf_from_name + sf_to_name = sf_path + PathDelimiter + sf_to_name + sf_map[sf_from_name.lower()] = sf_to_name + if len(sf_map) > 0: + sf_keys = list(sf_map.keys()) + sf_keys.sort() + for record in records: + if isinstance(record.folders, list) and len(record.folders) > 0: + for fol in record.folders: + path = fol.domain or '' + if fol.path: + if path: + path += PathDelimiter + path += fol.path + path_l = path.lower() + idx = bisect.bisect_right(sf_keys, path_l) + if 0 <= idx < len(sf_map) and path_l == sf_keys[idx]: + fol.domain = sf_map[path_l] + fol.path = '' + elif 0 < idx <= len(sf_map) and path_l.startswith(sf_keys[idx - 1]): + sf_name = sf_keys[idx - 1] + fol.domain = sf_map[sf_name] + fol.path = (path[len(sf_name):]).strip(PathDelimiter) + + if use_nsf: + from .nsf_import import prepare_nsf_folders if not dry_run: - fol_rs, _ = execute_import_folder_record(params, folder_add, None) - _ = fol_rs - sync_down.sync_down(params) + prepare_nsf_folders(params, folders, records, nsf_base_parent) + else: + folder_add = prepare_folder_add(params, folders, records, manage_users, manage_records, can_edit, can_share) + if folder_add: + if not dry_run: + fol_rs, _ = execute_import_folder_record(params, folder_add, None) + _ = fol_rs + sync_down.sync_down(params) record_keys = {} audit_uids = [] @@ -884,6 +947,7 @@ def _import(params, file_format, filename, **kwargs): records_v2_to_update = [] # type: List[dict] records_v3_to_add = [] # type: List[record_pb2.RecordAdd] records_v3_to_update = [] # type: List[record_pb2.RecordUpdate] + nsf_records_to_add = [] # NSF vault/records/v3/add payloads import_uids = {} records_to_import, record_exists, external_lookup = prepare_record_add_or_update(update_flag, no_shortcuts, params, records) @@ -1028,6 +1092,23 @@ def _import(params, file_format, filename, **kwargs): if folder.type == BaseFolderNode.RootFolderType: folder_uid = '' + from .nsf_import import is_nsf_folder + if folder_uid and is_nsf_folder(params, folder_uid): + data = _construct_record_v3_data(import_record) + data_bytes = api.get_record_data_json_bytes(data) + if len(data_bytes) > RECORD_MAX_DATA_LEN: + logging.warning(RECORD_MAX_DATA_WARN.format(data['title'], len(data_bytes), RECORD_MAX_DATA_LEN)) + continue + from .nsf_import import build_nsf_record_add + try: + nsf_add = build_nsf_record_add(params, import_record, record_key, data) + except Exception as exc: + logging.warning('Skipping NSF record "%s": %s', import_record.title, exc) + continue + import_uids[import_record.uid] = {'ver': 'nsf', 'op': 'add'} + nsf_records_to_add.append(nsf_add) + continue + v3_add_rq = record_pb2.RecordAdd() v3_add_rq.record_uid = utils.base64_url_decode(import_record.uid) import_uids[import_record.uid] = {'ver': 'v3', 'op': 'add'} @@ -1088,6 +1169,9 @@ def _import(params, file_format, filename, **kwargs): if record_key and link_key: link.record_key = crypto.encrypt_aes_v2(link_key, record_key) + if nsf_records_to_add: + from .nsf_import import execute_nsf_records_add + execute_nsf_records_add(params, nsf_records_to_add) if records_v3_to_add: rec_rs = execute_records_add(params, records_v3_to_add) if records_v2_to_update: @@ -1095,7 +1179,11 @@ def _import(params, file_format, filename, **kwargs): if records_v3_to_update: rec_rs = execute_records_update(params, records_v3_to_update) - sync_down.sync_down(params) + if use_nsf or nsf_records_to_add: + from ..commands.pam_import.nsf_helpers import sync_down_preserving_nsf_keys + sync_down_preserving_nsf_keys(params) + else: + sync_down.sync_down(params) # update audit data if audit_uids and params.enterprise_ec_key: @@ -1161,7 +1249,7 @@ def _import(params, file_format, filename, **kwargs): if r.attachments: if r.uid in import_uids: ver = import_uids[r.uid]['ver'] - if ver == 'v3': + if ver == 'v3' or ver == 'nsf': v3_atts.append(r) else: for a in r.attachments: @@ -1207,6 +1295,11 @@ def _import(params, file_format, filename, **kwargs): if len(v3_atts) > 0: upload_v3_attachments(params, v3_atts) + if use_nsf and folders and not dry_run: + from .nsf_import import apply_nsf_folder_permissions + apply_nsf_folder_permissions( + params, folders, manage_users, manage_records, can_edit, can_share) + if hasattr(importer, 'cleanup') and callable(importer.cleanup): importer.cleanup() @@ -2224,6 +2317,10 @@ def prepare_record_link(params, records): folder_uid = fol.uid if fol else '' if folder_uid and folder_uid not in params.folder_cache: continue + from .nsf_import import is_nsf_folder + if folder_uid and is_nsf_folder(params, folder_uid): + # NSF multi-folder links use a different API; skip classic move/link. + continue if folder_uid in folder_ids: continue if len(folder_ids) > 0: diff --git a/keepercommander/importer/importer.py b/keepercommander/importer/importer.py index 3241c0371..9742b5119 100644 --- a/keepercommander/importer/importer.py +++ b/keepercommander/importer/importer.py @@ -103,6 +103,7 @@ def __init__(self): self.name = None self.manage_users = None self.manage_records = None + self.role = None # NSF role name: viewer, content-manager, full-manager, … class SharedFolder: @@ -398,21 +399,57 @@ class BaseFileImporter(BaseImporter, abc.ABC): def __init__(self): super(BaseFileImporter, self).__init__() - def execute(self, name, **kwargs): - # type: (str, ...) -> Iterable[Union[Record, SharedFolder, File]] + def _path_candidates(self, name): + # type: (str) -> List[str] + """Yield existing-path candidates for *name* (exact, +ext, sample .ext.txt).""" + path = os.path.expanduser(name) + candidates = [path] + ext = (self.extension() or '').lstrip('.') + if ext: + lower = path.lower() + ext_suffix = '.' + ext.lower() + ext_txt_suffix = ext_suffix + '.txt' + if lower.endswith(ext_txt_suffix): + pass + elif lower.endswith(ext_suffix): + # foo.json → also try foo.json.txt (sample_data convention) + candidates.append(path + '.txt') + else: + # foo → foo.json, foo.json.txt, foo.txt + candidates.append(path + '.' + ext) + candidates.append(path + '.' + ext + '.txt') + candidates.append(path + '.txt') + # Deduplicate while preserving order + seen = set() + for candidate in candidates: + if candidate in seen: + continue + seen.add(candidate) + yield candidate + + def resolve_file_path(self, name): + # type: (str) -> str + """Resolve an import file path, trying common extensions and sample suffixes.""" try: json.loads(name) - path = name - except ValueError as e: - path = os.path.expanduser(name) - if not os.path.isfile(path): - ext = self.extension() - if ext: - path = path + '.' + ext - - if not os.path.isfile(path): - raise CommandError('import', f'File \'{name}\' does not exist') + return name + except ValueError: + pass + + tried = [] + for candidate in self._path_candidates(name): + tried.append(candidate) + if os.path.isfile(candidate): + return candidate + hint = '' + ext = (self.extension() or '').lstrip('.') + if ext: + hint = f' (tried: {", ".join(tried[:4])}{"…" if len(tried) > 4 else ""})' + raise CommandError('import', f'File \'{name}\' does not exist{hint}') + def execute(self, name, **kwargs): + # type: (str, ...) -> Iterable[Union[Record, SharedFolder, File]] + path = self.resolve_file_path(name) yield from self.do_import(path, **kwargs) diff --git a/keepercommander/importer/json/json.py b/keepercommander/importer/json/json.py index b4c7a2484..567cf86a2 100644 --- a/keepercommander/importer/json/json.py +++ b/keepercommander/importer/json/json.py @@ -203,7 +203,9 @@ def do_import(self, filename, **kwargs): fol.manage_users = shf.get('manage_users') or False fol.can_edit = shf.get('can_edit') or False fol.can_share = shf.get('can_share') or False - if users_only and 'permissions' in shf: + # Always parse user/team permissions. Classic shared folders apply + # them with --users; NSF applies them during --nsf import. + if 'permissions' in shf: fol.permissions = [] permissions = shf['permissions'] if not isinstance(permissions, list): @@ -216,6 +218,9 @@ def do_import(self, filename, **kwargs): if p.uid or p.name: p.manage_records = perm.get('manage_records') or False p.manage_users = perm.get('manage_users') or False + role = perm.get('role') + if isinstance(role, str) and role.strip(): + p.role = role.strip() fol.permissions.append(p) yield fol diff --git a/keepercommander/importer/nsf_import.py b/keepercommander/importer/nsf_import.py new file mode 100644 index 000000000..a3d79fd90 --- /dev/null +++ b/keepercommander/importer/nsf_import.py @@ -0,0 +1,452 @@ +# _ __ +# | |/ /___ ___ _ __ ___ _ _ ® +# | ' bool + """Return True when *folder_uid* is a Nested Share Folder.""" + if not folder_uid: + return False + if folder_uid in (getattr(params, 'nested_share_folders', None) or {}): + return True + subfolder = (getattr(params, 'subfolder_cache', None) or {}).get(folder_uid) or {} + if subfolder.get('source') == 'nested_share_folder': + return True + folder = (getattr(params, 'folder_cache', None) or {}).get(folder_uid) + return bool(folder and getattr(folder, 'type', None) == BaseFolderNode.NestedShareFolderType) + + +def resolve_nsf_folder(params, identifier): # type: (KeeperParams, Optional[str]) -> Optional[str] + """Resolve a folder name, path, or UID to an NSF folder UID.""" + if not identifier: + return None + from ..nested_share_folder.folder_api import resolve_folder_identifier + + for candidate in (identifier, identifier.replace('\\', '/')): + uid = resolve_folder_identifier(params, candidate) + if uid and is_nsf_folder(params, uid): + return uid + return None + + +def classic_perms_to_nsf_role(manage_users=False, manage_records=False, + can_edit=False, can_share=False): + """Map classic shared-folder booleans to an NSF role name.""" + if manage_users and manage_records: + return 'full-manager' + if manage_records and can_share: + return 'content-share-manager' + if manage_records or can_edit: + return 'content-manager' + if can_share: + return 'share-manager' + return 'viewer' + + +def flatten_record_folder_paths(records): # type: (List[ImportRecord]) -> None + """Collapse domain + path into a single NSF path on each record folder.""" + for record in records: + if not record.folders: + continue + for folder in record.folders: + comps = [] + if folder.domain: + comps.extend(path_components(folder.domain)) + if folder.path: + comps.extend(path_components(folder.path)) + folder.domain = '' + folder.path = PathDelimiter.join( + [c.replace(PathDelimiter, 2 * PathDelimiter) for c in comps] + ) if comps else '' + + +def find_nsf_child(params, folder_name, parent_uid): + # type: (KeeperParams, str, Optional[str]) -> Optional[str] + """Find an NSF child named *folder_name* under *parent_uid* (or vault root).""" + from ..commands.nested_share_folder.helpers import normalize_parent_uid + + nsf_folders = getattr(params, 'nested_share_folders', None) or {} + name_lower = (folder_name or '').lower() + looking_for_root = not parent_uid + for fuid, fobj in nsf_folders.items(): + if (fobj.get('name') or '').lower() != name_lower: + continue + raw_parent = fobj.get('parent_uid') or '' + normalized = normalize_parent_uid(raw_parent) + is_root_child = ( + normalized in ('', 'root') + or (raw_parent and raw_parent not in nsf_folders) + ) + if looking_for_root: + if is_root_child: + return fuid + elif raw_parent == parent_uid: + return fuid + return None + + +def _is_throttle_error(exc): # type: (BaseException) -> bool + if isinstance(exc, KeeperApiError) and str(getattr(exc, 'result_code', '')).lower() in ( + 'throttled', '429', 'too_many_requests'): + return True + text = str(getattr(exc, 'message', None) or exc).lower() + return 'throttl' in text or 'too many' in text or 'rate limit' in text + + +def _call_with_throttle_retry(description, fn, *args, **kwargs): + """Retry *fn* with progressive backoff when the API returns throttled.""" + wait = _THROTTLE_BASE_WAIT + for attempt in range(1, _THROTTLE_MAX_RETRIES + 1): + try: + return fn(*args, **kwargs) + except Exception as exc: + if not _is_throttle_error(exc) or attempt >= _THROTTLE_MAX_RETRIES: + raise + logging.warning( + '%s throttled (attempt %d/%d); waiting %.0fs before retry', + description, attempt, _THROTTLE_MAX_RETRIES, wait, + ) + time.sleep(wait) + wait *= _THROTTLE_MULTIPLIER + + +def create_nsf_folder(params, name, parent_uid=''): + # type: (KeeperParams, str, Optional[str]) -> str + """Create one NSF folder and seed local caches. Returns folder UID.""" + results = create_nsf_folders_batch(params, [(name, parent_uid or '')]) + if not results: + raise CommandError('import', f'Failed to create NSF folder "{name}"') + return results[0] + + +def create_nsf_folders_batch(params, folders): + # type: (KeeperParams, List[Tuple[str, str]]) -> List[str] + """Create many NSF folders that share the same depth (parents already exist). + + *folders* is a list of ``(name, parent_uid)`` pairs. Returns UIDs in the + same order. Uses ``vault/folders/v3/add`` batches of up to 100 with + throttle retries. + """ + from ..nested_share_folder.folder_api import create_folders_batch_v3 + from ..commands.pam_import.nsf_helpers import seed_nsf_folder_cache + + if not folders: + return [] + + created_uids = [] # type: List[str] + for start in range(0, len(folders), NSF_FOLDER_BATCH): + chunk = folders[start:start + NSF_FOLDER_BATCH] + specs = [ + { + 'name': name, + 'parent_uid': parent_uid or None, + 'inherit_permissions': True, + } + for name, parent_uid in chunk + ] + results = _call_with_throttle_retry( + 'NSF folder create', + create_folders_batch_v3, + params, + specs, + ) + if len(results) != len(chunk): + raise CommandError( + 'import', + f'NSF folder batch returned {len(results)} results for {len(chunk)} folders', + ) + for (name, parent_uid), result in zip(chunk, results): + if not isinstance(result, dict) or result.get('success') is False: + message = (result or {}).get('message') if isinstance(result, dict) else None + raise CommandError( + 'import', + message or f'Failed to create NSF folder "{name}"', + ) + folder_uid = result['folder_uid'] + seed_nsf_folder_cache( + params, folder_uid, name, parent_uid or None, + result.get('folder_key_unencrypted'), + ) + created_uids.append(folder_uid) + return created_uids + + +def ensure_nsf_path(params, comps, parent_uid=''): + # type: (KeeperParams, Iterable[str], Optional[str]) -> str + """Ensure path components exist under *parent_uid*; return leaf folder UID.""" + current = parent_uid or '' + for comp in comps: + if not comp: + continue + existing = find_nsf_child(params, comp, current) + if existing: + current = existing + continue + current = create_nsf_folder(params, comp, current) + return current + + +def _folder_comp_lists(folders, records): + # type: (List[ImportSharedFolder], List[ImportRecord]) -> List[Tuple[object, List[str]]] + """Return ``(target, component_list)`` pairs for every folder to resolve.""" + targets = [] # type: List[Tuple[object, List[str]]] + for fol in folders or []: + comps = [c for c in path_components(fol.path or '') if c] + if comps: + targets.append((fol, comps)) + for rec in records or []: + if not rec.folders: + continue + for fol in rec.folders: + comps = [] + if fol.domain: + comps.extend(path_components(fol.domain)) + if fol.path: + comps.extend(path_components(fol.path)) + comps = [c for c in comps if c] + targets.append((fol, comps)) + return targets + + +def prepare_nsf_folders(params, folders, records, base_parent_uid=''): + # type: (KeeperParams, List[ImportSharedFolder], List[ImportRecord], str) -> int + """Create NSF folder trees for import shared folders and record folders. + + Builds the full tree first, then creates missing folders **by depth** in + batches of up to 100 (``vault/folders/v3/add``), with throttle retries. + Sets ``uid`` on each resolved ``ImportSharedFolder`` / ``ImportFolder``. + Returns the number of folders created. + """ + before = set((getattr(params, 'nested_share_folders', None) or {}).keys()) + targets = _folder_comp_lists(folders, records) + + # path_tuple -> folder_uid + uid_by_path = {} # type: Dict[Tuple[str, ...], str] + if base_parent_uid: + uid_by_path[()] = base_parent_uid + + # Resolve / create level by level so parent keys exist for children. + max_depth = max((len(comps) for _, comps in targets), default=0) + for depth in range(1, max_depth + 1): + pending = [] # type: List[Tuple[Tuple[str, ...], str, str]] + seen = set() # type: set + for _, comps in targets: + if len(comps) < depth: + continue + path_tuple = tuple(comps[:depth]) + if path_tuple in uid_by_path or path_tuple in seen: + continue + parent_tuple = path_tuple[:-1] + if parent_tuple not in uid_by_path and parent_tuple != (): + # Parent should have been resolved at previous depth. + continue + parent_uid = uid_by_path.get(parent_tuple, base_parent_uid or '') + name = path_tuple[-1] + existing = find_nsf_child(params, name, parent_uid) + if existing: + uid_by_path[path_tuple] = existing + continue + seen.add(path_tuple) + pending.append((path_tuple, name, parent_uid)) + + if not pending: + continue + + batch_input = [(name, parent_uid) for _, name, parent_uid in pending] + created_uids = create_nsf_folders_batch(params, batch_input) + for (path_tuple, _name, _parent_uid), folder_uid in zip(pending, created_uids): + uid_by_path[path_tuple] = folder_uid + + # Assign leaf UIDs back onto import objects. + for target, comps in targets: + if not comps: + if base_parent_uid: + target.uid = base_parent_uid + else: + target.uid = '' + continue + path_tuple = tuple(comps) + target.uid = uid_by_path.get(path_tuple, '') + + after = set((getattr(params, 'nested_share_folders', None) or {}).keys()) + created = len(after - before) + if created: + logging.info('Created %d Nested Share Folder(s)', created) + return created + + +def build_nsf_record_add(params, import_record, record_key, data): + """Build a ``vault/records/v3/add`` RecordAdd for an NSF folder.""" + from ..nested_share_folder.common import get_folder_key + from ..nested_share_folder.record_api import create_record_data_v3 + + folder_uid = '' + if import_record.folders: + folder_uid = import_record.folders[0].uid or '' + if not folder_uid or not is_nsf_folder(params, folder_uid): + raise CommandError('import', f'NSF folder not found for record "{import_record.title}"') + + folder_key = get_folder_key(params, folder_uid, raise_on_missing=True) + return create_record_data_v3( + record_uid=import_record.uid, + record_key=record_key, + data=data, + folder_uid=folder_uid, + folder_key=folder_key, + data_key=params.data_key, + client_modified_time=utils.current_milli_time(), + ) + + +def execute_nsf_records_add(params, record_adds): + # type: (KeeperParams, list) -> list + """Submit NSF record adds in batches of up to 1000 with throttle retries.""" + from ..nested_share_folder.record_api import record_add_v3 + + results = [] + for start in range(0, len(record_adds), NSF_RECORD_BATCH): + batch = record_adds[start:start + NSF_RECORD_BATCH] + try: + rs = _call_with_throttle_retry( + 'NSF record add', + record_add_v3, + params, + batch, + ) + results.extend(list(rs.records)) + except Exception as exc: + logging.warning('NSF record add batch failed: %s', exc) + for ra in batch: + uid = utils.base64_url_encode(ra.recordUid) + logging.warning('Failed to create NSF record %s', uid) + return results + + +def apply_nsf_folder_permissions(params, folders, manage_users=False, manage_records=False, + can_edit=False, can_share=False): + # type: (KeeperParams, List[ImportSharedFolder], bool, bool, bool, bool) -> None + """Grant NSF folder access from ImportSharedFolder.permissions. + + Each permission may specify an NSF ``role`` (viewer, content-manager, + full-manager, …). When ``role`` is absent, classic manage_users / + manage_records flags are mapped via ``classic_perms_to_nsf_role``. + """ + from ..nested_share_folder.folder_api import grant_folder_access_v3 + from ..nested_share_folder.permissions import resolve_role_name + + default_role = classic_perms_to_nsf_role( + manage_users, manage_records, can_edit, can_share) + + granted = 0 + for fol in folders or []: + if not fol.uid or not is_nsf_folder(params, fol.uid): + if not fol.uid and fol.path: + comps = list(path_components(fol.path)) + parent = '' + for comp in comps: + parent = find_nsf_child(params, comp, parent) or '' + if not parent: + break + fol.uid = parent + if not fol.uid or not is_nsf_folder(params, fol.uid): + continue + + folder_role = classic_perms_to_nsf_role( + fol.manage_users if fol.manage_users is not None else manage_users, + fol.manage_records if fol.manage_records is not None else manage_records, + fol.can_edit if fol.can_edit is not None else can_edit, + fol.can_share if fol.can_share is not None else can_share, + ) + + for perm in fol.permissions or []: # type: ImportPermission + explicit_role = (getattr(perm, 'role', None) or '').strip() + if explicit_role: + try: + resolve_role_name(explicit_role) + except ValueError as exc: + logging.warning( + 'NSF permission for "%s" on "%s": %s', + perm.name or perm.uid, fol.path or fol.uid, exc) + continue + role = explicit_role.strip().lower().replace('_', '-') + if role == 'shared-manager': + role = 'share-manager' + elif role == 'contributor': + role = 'requestor' + else: + role = classic_perms_to_nsf_role( + perm.manage_users if perm.manage_users is not None else False, + perm.manage_records if perm.manage_records is not None else False, + ) + if role == 'viewer' and (manage_users or manage_records or can_edit or can_share): + role = folder_role if folder_role != 'viewer' else default_role + + name = perm.name or '' + uid = perm.uid or '' + recipient = name or uid + if not recipient: + continue + if uid and '@' not in uid and (not name or '@' not in name): + as_team = True + recipient = uid + elif name and '@' in name: + as_team = False + recipient = name + elif name: + as_team = False + recipient = name + else: + as_team = False + + try: + result = _call_with_throttle_retry( + 'NSF folder permission', + grant_folder_access_v3, + params, fol.uid, recipient, role=role, as_team=as_team, + ) + if result.get('success'): + granted += 1 + else: + logging.warning( + 'NSF permission for "%s" on "%s": %s', + recipient, fol.path or fol.uid, result.get('message')) + except Exception as exc: + logging.warning( + 'NSF permission for "%s" on "%s" failed: %s', + recipient, fol.path or fol.uid, exc) + + if granted: + logging.info('Granted %d Nested Share Folder permission(s)', granted) diff --git a/keepercommander/nested_share_folder/__init__.py b/keepercommander/nested_share_folder/__init__.py index 14c005919..e9d8ffe00 100644 --- a/keepercommander/nested_share_folder/__init__.py +++ b/keepercommander/nested_share_folder/__init__.py @@ -24,7 +24,7 @@ 'handle_share_invite', 'resolve_user_uid_bytes', 'load_user_public_key', 'parse_folder_access_result', 'resolve_team_uid_bytes', 'resolve_team_identifier', - 'get_team_keys', 'encrypt_for_team', + 'get_team_keys', 'encrypt_for_team', 'is_keeper_uid', ], 'folder_api': [ 'create_folder_data', 'encrypt_folder_key', diff --git a/keepercommander/nested_share_folder/common.py b/keepercommander/nested_share_folder/common.py index 261f47651..4bbdb561e 100644 --- a/keepercommander/nested_share_folder/common.py +++ b/keepercommander/nested_share_folder/common.py @@ -6,6 +6,7 @@ """ import logging +import re from typing import Optional, Dict, Any, Tuple from .. import utils, crypto, api @@ -14,6 +15,14 @@ logger = logging.getLogger(__name__) +# Keeper UIDs are 16 random bytes encoded as unpadded base64url (22 chars). +_KEEPER_UID_RE = re.compile(r'^[A-Za-z0-9_-]{22}$') + + +def is_keeper_uid(value: str) -> bool: + """True when *value* looks like a Keeper UID (not a display name).""" + return bool(value and _KEEPER_UID_RE.match(value)) + # ═══════════════════════════════════════════════════════════════════════════ # Key lookup helpers (previously copy-pasted 9+ / 3+ times) @@ -93,10 +102,9 @@ def encrypt_for_recipient(plaintext_key: bytes, public_key, use_ecc: bool) -> by # ═══════════════════════════════════════════════════════════════════════════ def resolve_user_uid_bytes(params, user_identifier: str) -> Optional[bytes]: - """Resolve an email or base64-url UID to raw UID bytes. + """Resolve a user email or account UID to raw account UID bytes. - Lookup order: user_cache → enterprise users → base64 decode. - Returns None when the identifier cannot be resolved. + Only UID-shaped strings are decoded — never display names. """ is_email = '@' in user_identifier @@ -114,56 +122,70 @@ def resolve_user_uid_bytes(params, user_identifier: str) -> Optional[bytes]: break return None - try: - return utils.base64_url_decode(user_identifier) - except Exception: - return None - + # Only treat UID-shaped strings as account UIDs — never decode display names. + if is_keeper_uid(user_identifier): + try: + return utils.base64_url_decode(user_identifier) + except Exception: + return None + return None def resolve_team_uid_bytes(params, team_identifier: str) -> Optional[bytes]: - """Resolve a team name or base64-url team UID to raw UID bytes. + """Resolve team UID or display name to team UID bytes. + + Matches classic share-folder: look up shareable + teams by UID or exact name. Does **not** base64-decode arbitrary strings — + that produced garbage UIDs (e.g. truncated names) and + ``bad_inputs_invalid_uid_string`` on ``team_get_keys``. """ - if not team_identifier: + identifier = (team_identifier or '').strip() + if not identifier: return None team_cache = getattr(params, 'team_cache', None) or {} - if team_identifier in team_cache: - return utils.base64_url_decode(team_identifier) - lower = team_identifier.lower() + if identifier in team_cache: + return utils.base64_url_decode(identifier) + lower = identifier.casefold() for uid, t in team_cache.items(): name = t.get('name') if isinstance(t, dict) else getattr(t, 'name', None) - if name and name.lower() == lower: + if name and name.casefold() == lower: return utils.base64_url_decode(uid) try: share_objects = api.get_share_objects(params).get('teams', {}) or {} except Exception: share_objects = {} - if team_identifier in share_objects: - return utils.base64_url_decode(team_identifier) + if identifier in share_objects: + return utils.base64_url_decode(identifier) for uid, t in share_objects.items(): name = (t.get('name') if isinstance(t, dict) else None) or '' - if name.lower() == lower: + if name and name.casefold() == lower: return utils.base64_url_decode(uid) - if len(share_objects) >= 500 or getattr(params, 'available_team_cache', None) is None: + # Enterprise teams not always present in get_share_objects — same as classic + # share-folder when the share-objects list is large / incomplete. + available = getattr(params, 'available_team_cache', None) + if len(share_objects) >= 500 or available is None: try: api.load_available_teams(params) + available = getattr(params, 'available_team_cache', None) except Exception: pass - for t in (getattr(params, 'available_team_cache', None) or []): + if not isinstance(available, (list, tuple)): + available = [] + for t in available: uid = t.get('team_uid') name = t.get('team_name', '') - if uid and (uid == team_identifier or name.lower() == lower): + if uid and (uid == identifier or (name and name.casefold() == lower)): return utils.base64_url_decode(uid) - try: - decoded = utils.base64_url_decode(team_identifier) - return decoded if decoded else None - except Exception: - return None - + if is_keeper_uid(identifier): + try: + return utils.base64_url_decode(identifier) + except Exception: + return None + return None def resolve_team_identifier(params, team_identifier: str) -> Optional[Tuple[str, bytes]]: """Resolve a team name/UID to ``(team_uid_b64, team_uid_bytes)`` or ``None``.""" @@ -178,6 +200,9 @@ def get_team_keys(params, team_uid_b64: str): """ from ..params import PublicKeys + if not is_keeper_uid(team_uid_b64): + raise ValueError(f'Invalid team UID: {team_uid_b64}') + cached = params.key_cache.get(team_uid_b64) has_asym = bool(cached and (getattr(cached, 'rsa', None) or getattr(cached, 'ec', None))) if cached and has_asym: diff --git a/keepercommander/proto/pam_pb2.py b/keepercommander/proto/pam_pb2.py index 05f9d0a0c..b773f6c8e 100644 --- a/keepercommander/proto/pam_pb2.py +++ b/keepercommander/proto/pam_pb2.py @@ -26,7 +26,7 @@ from . import record_pb2 as record__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tpam.proto\x12\x03PAM\x1a\x10\x65nterprise.proto\x1a\x0crecord.proto\"\x83\x01\n\x13PAMRotationSchedule\x12\x11\n\trecordUid\x18\x01 \x01(\x0c\x12\x18\n\x10\x63onfigurationUid\x18\x02 \x01(\x0c\x12\x15\n\rcontrollerUid\x18\x03 \x01(\x0c\x12\x14\n\x0cscheduleData\x18\x04 \x01(\t\x12\x12\n\nnoSchedule\x18\x05 \x01(\x08\"K\n\x1cPAMRotationSchedulesResponse\x12+\n\tschedules\x18\x01 \x03(\x0b\x32\x18.PAM.PAMRotationSchedule\"\x94\x01\n\x13PAMOnlineController\x12\x15\n\rcontrollerUid\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63onnectedOn\x18\x02 \x01(\x03\x12\x11\n\tipAddress\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\x12-\n\x0b\x63onnections\x18\x05 \x03(\x0b\x32\x18.PAM.PAMWebRtcConnection\"\xa7\x01\n\x13PAMWebRtcConnection\x12\x15\n\rconnectionUid\x18\x01 \x01(\x0c\x12\'\n\x04type\x18\x02 \x01(\x0e\x32\x19.PAM.WebRtcConnectionType\x12\x11\n\trecordUid\x18\x03 \x01(\x0c\x12\x10\n\x08userName\x18\x04 \x01(\t\x12\x11\n\tstartedOn\x18\x05 \x01(\x03\x12\x18\n\x10\x63onfigurationUid\x18\x06 \x01(\x0c\"Y\n\x14PAMOnlineControllers\x12\x12\n\ndeprecated\x18\x01 \x03(\x0c\x12-\n\x0b\x63ontrollers\x18\x02 \x03(\x0b\x32\x18.PAM.PAMOnlineController\"9\n\x10PAMRotateRequest\x12\x12\n\nrequestUid\x18\x01 \x01(\x0c\x12\x11\n\trecordUid\x18\x02 \x01(\x0c\"A\n\x16PAMControllersResponse\x12\'\n\x0b\x63ontrollers\x18\x01 \x03(\x0b\x32\x12.PAM.PAMController\"=\n\x13PAMRemoveController\x12\x15\n\rcontrollerUid\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x02 \x01(\t\"L\n\x1bPAMRemoveControllerResponse\x12-\n\x0b\x63ontrollers\x18\x01 \x03(\x0b\x32\x18.PAM.PAMRemoveController\"=\n\x10PAMModifyRequest\x12)\n\noperations\x18\x01 \x03(\x0b\x32\x15.PAM.PAMDataOperation\"\x98\x01\n\x10PAMDataOperation\x12,\n\roperationType\x18\x01 \x01(\x0e\x32\x15.PAM.PAMOperationType\x12\x30\n\rconfiguration\x18\x02 \x01(\x0b\x32\x19.PAM.PAMConfigurationData\x12$\n\x07\x65lement\x18\x03 \x01(\x0b\x32\x13.PAM.PAMElementData\"e\n\x14PAMConfigurationData\x12\x18\n\x10\x63onfigurationUid\x18\x01 \x01(\x0c\x12\x0e\n\x06nodeId\x18\x02 \x01(\x03\x12\x15\n\rcontrollerUid\x18\x03 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\"E\n\x0ePAMElementData\x12\x12\n\nelementUid\x18\x01 \x01(\x0c\x12\x11\n\tparentUid\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"m\n\x19PAMElementOperationResult\x12\x12\n\nelementUid\x18\x01 \x01(\x0c\x12+\n\x06result\x18\x02 \x01(\x0e\x32\x1b.PAM.PAMOperationResultType\x12\x0f\n\x07message\x18\x03 \x01(\t\"B\n\x0fPAMModifyResult\x12/\n\x07results\x18\x01 \x03(\x0b\x32\x1e.PAM.PAMElementOperationResult\"x\n\nPAMElement\x12\x12\n\nelementUid\x18\x01 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63reated\x18\x03 \x01(\x03\x12\x14\n\x0clastModified\x18\x04 \x01(\x03\x12!\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x0f.PAM.PAMElement\"#\n\x14PAMGenericUidRequest\x12\x0b\n\x03uid\x18\x01 \x01(\x0c\"%\n\x15PAMGenericUidsRequest\x12\x0c\n\x04uids\x18\x01 \x03(\x0c\"\xab\x01\n\x10PAMConfiguration\x12\x18\n\x10\x63onfigurationUid\x18\x01 \x01(\x0c\x12\x0e\n\x06nodeId\x18\x02 \x01(\x03\x12\x15\n\rcontrollerUid\x18\x03 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0f\n\x07\x63reated\x18\x05 \x01(\x03\x12\x14\n\x0clastModified\x18\x06 \x01(\x03\x12!\n\x08\x63hildren\x18\x07 \x03(\x0b\x32\x0f.PAM.PAMElement\"B\n\x11PAMConfigurations\x12-\n\x0e\x63onfigurations\x18\x01 \x03(\x0b\x32\x15.PAM.PAMConfiguration\"\xff\x01\n\rPAMController\x12\x15\n\rcontrollerUid\x18\x01 \x01(\x0c\x12\x16\n\x0e\x63ontrollerName\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65viceToken\x18\x03 \x01(\t\x12\x12\n\ndeviceName\x18\x04 \x01(\t\x12\x0e\n\x06nodeId\x18\x05 \x01(\x03\x12\x0f\n\x07\x63reated\x18\x06 \x01(\x03\x12\x14\n\x0clastModified\x18\x07 \x01(\x03\x12\x16\n\x0e\x61pplicationUid\x18\x08 \x01(\x0c\x12\x30\n\rappClientType\x18\t \x01(\x0e\x32\x19.Enterprise.AppClientType\x12\x15\n\risInitialized\x18\n \x01(\x08\"P\n\x1dPAMSetMaxInstanceCountRequest\x12\x15\n\rcontrollerUid\x18\x01 \x01(\x0c\x12\x18\n\x10maxInstanceCount\x18\x02 \x01(\x05\"%\n\x12\x43ontrollerResponse\x12\x0f\n\x07payload\x18\x01 \x01(\t\"M\n\x1aPAMConfigurationController\x12\x18\n\x10\x63onfigurationUid\x18\x01 \x01(\x0c\x12\x15\n\rcontrollerUid\x18\x02 \x01(\x0c\"\xa3\x01\n\x17\x43onfigurationAddRequest\x12\x18\n\x10\x63onfigurationUid\x18\x01 \x01(\x0c\x12\x11\n\trecordKey\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12(\n\x0brecordLinks\x18\x04 \x03(\x0b\x32\x13.Records.RecordLink\x12#\n\x05\x61udit\x18\x05 \x01(\x0b\x32\x14.Records.RecordAudit\"J\n\x10RelayAccessCreds\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x12\n\nserverTime\x18\x03 \x01(\x03\"\x81\x02\n\x14PAMRecordingsRequest\x12\x11\n\trecordUid\x18\x01 \x01(\x0c\x12\x10\n\x08maxCount\x18\x02 \x01(\x05\x12\x17\n\nrangeStart\x18\x03 \x01(\x03H\x00\x88\x01\x01\x12\x15\n\x08rangeEnd\x18\x04 \x01(\x03H\x01\x88\x01\x01\x12$\n\x05types\x18\x05 \x03(\x0e\x32\x15.PAM.PAMRecordingType\x12)\n\x05risks\x18\x06 \x03(\x0e\x32\x1a.PAM.PAMRecordingRiskLevel\x12\x11\n\tprotocols\x18\x07 \x03(\t\x12\x14\n\x0c\x63loseReasons\x18\x08 \x03(\x05\x42\r\n\x0b_rangeStartB\x0b\n\t_rangeEnd\"\x89\x02\n\x1cPAMRecordingsForUsersRequest\x12\x11\n\tusernames\x18\x01 \x03(\t\x12\x10\n\x08maxCount\x18\x02 \x01(\x05\x12\x17\n\nrangeStart\x18\x03 \x01(\x03H\x00\x88\x01\x01\x12\x15\n\x08rangeEnd\x18\x04 \x01(\x03H\x01\x88\x01\x01\x12$\n\x05types\x18\x05 \x03(\x0e\x32\x15.PAM.PAMRecordingType\x12)\n\x05risks\x18\x06 \x03(\x0e\x32\x1a.PAM.PAMRecordingRiskLevel\x12\x11\n\tprotocols\x18\x07 \x03(\t\x12\x14\n\x0c\x63loseReasons\x18\x08 \x03(\x05\x42\r\n\x0b_rangeStartB\x0b\n\t_rangeEnd\"\xd4\x02\n\x0cPAMRecording\x12\x15\n\rconnectionUid\x18\x01 \x01(\x0c\x12,\n\rrecordingType\x18\x02 \x01(\x0e\x32\x15.PAM.PAMRecordingType\x12\x11\n\trecordUid\x18\x03 \x01(\x0c\x12\x10\n\x08userName\x18\x04 \x01(\t\x12\x11\n\tstartedOn\x18\x05 \x01(\x03\x12\x0e\n\x06length\x18\x06 \x01(\x05\x12\x10\n\x08\x66ileSize\x18\x07 \x01(\x03\x12\x11\n\tcreatedOn\x18\x08 \x01(\x03\x12\x10\n\x08protocol\x18\t \x01(\t\x12\x13\n\x0b\x63loseReason\x18\n \x01(\x05\x12\x19\n\x11recordingDuration\x18\x0b \x01(\x05\x12\x36\n\x12\x61iOverallRiskLevel\x18\x0c \x01(\x0e\x32\x1a.PAM.PAMRecordingRiskLevel\x12\x18\n\x10\x61iOverallSummary\x18\r \x01(\x0c\"O\n\x15PAMRecordingsResponse\x12%\n\nrecordings\x18\x01 \x03(\x0b\x32\x11.PAM.PAMRecording\x12\x0f\n\x07hasMore\x18\x02 \x01(\x08\"*\n\x07PAMData\x12\x0e\n\x06vertex\x18\x01 \x01(\x0c\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\x0c\"\x17\n\x07UidList\x12\x0c\n\x04uids\x18\x01 \x03(\x0c\"\x84\x03\n\x11PAMResourceConfig\x12\x11\n\trecordUid\x18\x01 \x01(\x0c\x12\x17\n\nnetworkUid\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x15\n\x08\x61\x64minUid\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x11\n\x04meta\x18\x04 \x01(\x0cH\x02\x88\x01\x01\x12\x1f\n\x12\x63onnectionSettings\x18\x05 \x01(\x0cH\x03\x88\x01\x01\x12\'\n\x0c\x63onnectUsers\x18\x06 \x01(\x0b\x32\x0c.PAM.UidListH\x04\x88\x01\x01\x12\x16\n\tdomainUid\x18\x07 \x01(\x0cH\x05\x88\x01\x01\x12\x18\n\x0bjitSettings\x18\x08 \x01(\x0cH\x06\x88\x01\x01\x12\x1d\n\x10keeperAiSettings\x18\t \x01(\x0cH\x07\x88\x01\x01\x42\r\n\x0b_networkUidB\x0b\n\t_adminUidB\x07\n\x05_metaB\x15\n\x13_connectionSettingsB\x0f\n\r_connectUsersB\x0c\n\n_domainUidB\x0e\n\x0c_jitSettingsB\x13\n\x11_keeperAiSettings\"%\n\x16PAMUniversalSyncFolder\x12\x0b\n\x03uid\x18\x01 \x01(\x0c\"\xfc\x01\n\x16PAMUniversalSyncConfig\x12\x12\n\nnetworkUid\x18\x01 \x01(\x0c\x12\x14\n\x07\x65nabled\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12\x1a\n\rdryRunEnabled\x18\x03 \x01(\x08H\x01\x88\x01\x01\x12,\n\x07\x66olders\x18\x04 \x03(\x0b\x32\x1b.PAM.PAMUniversalSyncFolder\x12\x19\n\x0csyncIdentity\x18\x05 \x01(\x0cH\x02\x88\x01\x01\x12\x16\n\tvaultName\x18\x06 \x01(\x0cH\x03\x88\x01\x01\x42\n\n\x08_enabledB\x10\n\x0e_dryRunEnabledB\x0f\n\r_syncIdentityB\x0c\n\n_vaultName\"7\n\x11NhiMetricsRequest\x12\x11\n\tstartTime\x18\x01 \x01(\x03\x12\x0f\n\x07\x65ndTime\x18\x02 \x01(\x03\"\x9c\x02\n\x0ePamUsageByUser\x12\x0e\n\x06userId\x18\x01 \x01(\x05\x12!\n\x19recordRotationScheduledOk\x18\x02 \x01(\x05\x12\x1c\n\x14pamConnectionStarted\x18\x03 \x01(\x05\x12\x18\n\x10pamTunnelStarted\x18\x04 \x01(\x05\x12\x1b\n\x13\x64iscoveryJobStarted\x18\x05 \x01(\x05\x12 \n\x18recordRotationOnDemandOk\x18\x06 \x01(\x05\x12\"\n\x1apamSessionRecordingStarted\x18\x07 \x01(\x05\x12\x15\n\rpamRbiStarted\x18\x08 \x01(\x05\x12%\n\x1dpamSessionRbiRecordingStarted\x18\t \x01(\x05\"p\n\x0eNhiUsageByUser\x12\x0e\n\x06userId\x18\x01 \x01(\x05\x12\x11\n\trotations\x18\x02 \x01(\x05\x12\x0f\n\x07tunnels\x18\x03 \x01(\x05\x12\x13\n\x0b\x63onnections\x18\x04 \x01(\x05\x12\x15\n\rdiscoveryJobs\x18\x05 \x01(\x05\"\x84\x02\n\x12NhiMetricsResponse\x12\x14\n\x0c\x65nterpriseId\x18\x01 \x01(\x03\x12\x11\n\tstartTime\x18\x02 \x01(\x03\x12\x0f\n\x07\x65ndTime\x18\x03 \x01(\x03\x12\x18\n\x10uniqueKsmDevices\x18\x04 \x01(\x05\x12\x18\n\x10pamGatewayOnline\x18\x05 \x01(\x05\x12/\n\x0epamUsageByUser\x18\x06 \x03(\x0b\x32\x13.PAM.PamUsageByUserB\x02\x18\x01\x12\x10\n\x08nhiCount\x18\x07 \x01(\x05\x12\x13\n\x0bksmNhiCount\x18\x08 \x01(\x05\x12(\n\x0busageByUser\x18\t \x03(\x0b\x32\x13.PAM.NhiUsageByUser\"D\n\x16NhiBulkMetricsResponse\x12*\n\tresponses\x18\x01 \x03(\x0b\x32\x17.PAM.NhiMetricsResponse\"N\n\x0bNhiUidEntry\x12\x0b\n\x03uid\x18\x01 \x01(\t\x12\"\n\x08\x63\x61tegory\x18\x02 \x01(\x0e\x32\x10.PAM.NhiCategory\x12\x0e\n\x06ksmNhi\x18\x03 \x01(\x08\"7\n\x11GetNhiUidsRequest\x12\x11\n\tstartTime\x18\x01 \x01(\x03\x12\x0f\n\x07\x65ndTime\x18\x02 \x01(\x03\"4\n\x12GetNhiUidsResponse\x12\x1e\n\x04uids\x18\x01 \x03(\x0b\x32\x10.PAM.NhiUidEntry*\x9e\x01\n\x14WebRtcConnectionType\x12\x0e\n\nCONNECTION\x10\x00\x12\n\n\x06TUNNEL\x10\x01\x12\x07\n\x03SSH\x10\x02\x12\x07\n\x03RDP\x10\x03\x12\x08\n\x04HTTP\x10\x04\x12\x07\n\x03VNC\x10\x05\x12\n\n\x06TELNET\x10\x06\x12\t\n\x05MYSQL\x10\x07\x12\x0e\n\nSQL_SERVER\x10\x08\x12\x0e\n\nPOSTGRESQL\x10\t\x12\x0e\n\nKUBERNETES\x10\n*@\n\x10PAMOperationType\x12\x07\n\x03\x41\x44\x44\x10\x00\x12\n\n\x06UPDATE\x10\x01\x12\x0b\n\x07REPLACE\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x03*p\n\x16PAMOperationResultType\x12\x0f\n\x0bPOT_SUCCESS\x10\x00\x12\x15\n\x11POT_UNKNOWN_ERROR\x10\x01\x12\x16\n\x12POT_ALREADY_EXISTS\x10\x02\x12\x16\n\x12POT_DOES_NOT_EXIST\x10\x03*\xc9\x01\n\x15\x43ontrollerMessageType\x12\x0f\n\x0b\x43MT_GENERAL\x10\x00\x12\x0e\n\nCMT_ROTATE\x10\x01\x12\x11\n\rCMT_DISCOVERY\x10\x02\x12\x0f\n\x0b\x43MT_CONNECT\x10\x03\x12\x19\n\x15\x43MT_ANALYZE_RECORDING\x10\x04\x12!\n\x1d\x43MT_WORKFLOW_ACCESS_ELEVATION\x10\x05\x12\x0b\n\x07\x43MT_USS\x10\x06\x12\x0c\n\x08\x43MT_INFO\x10\x07\x12\x12\n\x0e\x43MT_AUTOMATION\x10\x08*V\n\x10PAMRecordingType\x12\x0f\n\x0bPRT_SESSION\x10\x00\x12\x12\n\x0ePRT_TYPESCRIPT\x10\x01\x12\x0c\n\x08PRT_TIME\x10\x02\x12\x0f\n\x0bPRT_SUMMARY\x10\x03*i\n\x15PAMRecordingRiskLevel\x12\x13\n\x0fPRR_UNSPECIFIED\x10\x00\x12\x0b\n\x07PRR_LOW\x10\x01\x12\x0e\n\nPRR_MEDIUM\x10\x02\x12\x0c\n\x08PRR_HIGH\x10\x03\x12\x10\n\x0cPRR_CRITICAL\x10\x04*`\n\x0bNhiCategory\x12\x18\n\x14NHI_CATEGORY_UNKNOWN\x10\x00\x12\x0c\n\x08PAM_USER\x10\x01\x12\x10\n\x0cPAM_RESOURCE\x10\x02\x12\x0b\n\x07GATEWAY\x10\x03\x12\n\n\x06\x44\x45VICE\x10\x04\x42\x1f\n\x18\x63om.keepersecurity.protoB\x03PAMb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tpam.proto\x12\x03PAM\x1a\x10\x65nterprise.proto\x1a\x0crecord.proto\"\x83\x01\n\x13PAMRotationSchedule\x12\x11\n\trecordUid\x18\x01 \x01(\x0c\x12\x18\n\x10\x63onfigurationUid\x18\x02 \x01(\x0c\x12\x15\n\rcontrollerUid\x18\x03 \x01(\x0c\x12\x14\n\x0cscheduleData\x18\x04 \x01(\t\x12\x12\n\nnoSchedule\x18\x05 \x01(\x08\"K\n\x1cPAMRotationSchedulesResponse\x12+\n\tschedules\x18\x01 \x03(\x0b\x32\x18.PAM.PAMRotationSchedule\"\x94\x01\n\x13PAMOnlineController\x12\x15\n\rcontrollerUid\x18\x01 \x01(\x0c\x12\x13\n\x0b\x63onnectedOn\x18\x02 \x01(\x03\x12\x11\n\tipAddress\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\x12-\n\x0b\x63onnections\x18\x05 \x03(\x0b\x32\x18.PAM.PAMWebRtcConnection\"\xa7\x01\n\x13PAMWebRtcConnection\x12\x15\n\rconnectionUid\x18\x01 \x01(\x0c\x12\'\n\x04type\x18\x02 \x01(\x0e\x32\x19.PAM.WebRtcConnectionType\x12\x11\n\trecordUid\x18\x03 \x01(\x0c\x12\x10\n\x08userName\x18\x04 \x01(\t\x12\x11\n\tstartedOn\x18\x05 \x01(\x03\x12\x18\n\x10\x63onfigurationUid\x18\x06 \x01(\x0c\"Y\n\x14PAMOnlineControllers\x12\x12\n\ndeprecated\x18\x01 \x03(\x0c\x12-\n\x0b\x63ontrollers\x18\x02 \x03(\x0b\x32\x18.PAM.PAMOnlineController\"9\n\x10PAMRotateRequest\x12\x12\n\nrequestUid\x18\x01 \x01(\x0c\x12\x11\n\trecordUid\x18\x02 \x01(\x0c\"A\n\x16PAMControllersResponse\x12\'\n\x0b\x63ontrollers\x18\x01 \x03(\x0b\x32\x12.PAM.PAMController\"=\n\x13PAMRemoveController\x12\x15\n\rcontrollerUid\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x02 \x01(\t\"L\n\x1bPAMRemoveControllerResponse\x12-\n\x0b\x63ontrollers\x18\x01 \x03(\x0b\x32\x18.PAM.PAMRemoveController\"=\n\x10PAMModifyRequest\x12)\n\noperations\x18\x01 \x03(\x0b\x32\x15.PAM.PAMDataOperation\"\x98\x01\n\x10PAMDataOperation\x12,\n\roperationType\x18\x01 \x01(\x0e\x32\x15.PAM.PAMOperationType\x12\x30\n\rconfiguration\x18\x02 \x01(\x0b\x32\x19.PAM.PAMConfigurationData\x12$\n\x07\x65lement\x18\x03 \x01(\x0b\x32\x13.PAM.PAMElementData\"e\n\x14PAMConfigurationData\x12\x18\n\x10\x63onfigurationUid\x18\x01 \x01(\x0c\x12\x0e\n\x06nodeId\x18\x02 \x01(\x03\x12\x15\n\rcontrollerUid\x18\x03 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\"E\n\x0ePAMElementData\x12\x12\n\nelementUid\x18\x01 \x01(\x0c\x12\x11\n\tparentUid\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"m\n\x19PAMElementOperationResult\x12\x12\n\nelementUid\x18\x01 \x01(\x0c\x12+\n\x06result\x18\x02 \x01(\x0e\x32\x1b.PAM.PAMOperationResultType\x12\x0f\n\x07message\x18\x03 \x01(\t\"B\n\x0fPAMModifyResult\x12/\n\x07results\x18\x01 \x03(\x0b\x32\x1e.PAM.PAMElementOperationResult\"x\n\nPAMElement\x12\x12\n\nelementUid\x18\x01 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0f\n\x07\x63reated\x18\x03 \x01(\x03\x12\x14\n\x0clastModified\x18\x04 \x01(\x03\x12!\n\x08\x63hildren\x18\x05 \x03(\x0b\x32\x0f.PAM.PAMElement\"#\n\x14PAMGenericUidRequest\x12\x0b\n\x03uid\x18\x01 \x01(\x0c\"%\n\x15PAMGenericUidsRequest\x12\x0c\n\x04uids\x18\x01 \x03(\x0c\"\xab\x01\n\x10PAMConfiguration\x12\x18\n\x10\x63onfigurationUid\x18\x01 \x01(\x0c\x12\x0e\n\x06nodeId\x18\x02 \x01(\x03\x12\x15\n\rcontrollerUid\x18\x03 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0f\n\x07\x63reated\x18\x05 \x01(\x03\x12\x14\n\x0clastModified\x18\x06 \x01(\x03\x12!\n\x08\x63hildren\x18\x07 \x03(\x0b\x32\x0f.PAM.PAMElement\"B\n\x11PAMConfigurations\x12-\n\x0e\x63onfigurations\x18\x01 \x03(\x0b\x32\x15.PAM.PAMConfiguration\"\xab\x02\n\rPAMController\x12\x15\n\rcontrollerUid\x18\x01 \x01(\x0c\x12\x16\n\x0e\x63ontrollerName\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65viceToken\x18\x03 \x01(\t\x12\x12\n\ndeviceName\x18\x04 \x01(\t\x12\x0e\n\x06nodeId\x18\x05 \x01(\x03\x12\x0f\n\x07\x63reated\x18\x06 \x01(\x03\x12\x14\n\x0clastModified\x18\x07 \x01(\x03\x12\x16\n\x0e\x61pplicationUid\x18\x08 \x01(\x0c\x12\x30\n\rappClientType\x18\t \x01(\x0e\x32\x19.Enterprise.AppClientType\x12\x15\n\risInitialized\x18\n \x01(\x08\x12\x18\n\x10maxInstanceCount\x18\x0b \x01(\x05\x12\x10\n\x08lastSeen\x18\x0c \x01(\x03\"P\n\x1dPAMSetMaxInstanceCountRequest\x12\x15\n\rcontrollerUid\x18\x01 \x01(\x0c\x12\x18\n\x10maxInstanceCount\x18\x02 \x01(\x05\"%\n\x12\x43ontrollerResponse\x12\x0f\n\x07payload\x18\x01 \x01(\t\"M\n\x1aPAMConfigurationController\x12\x18\n\x10\x63onfigurationUid\x18\x01 \x01(\x0c\x12\x15\n\rcontrollerUid\x18\x02 \x01(\x0c\"\xa3\x01\n\x17\x43onfigurationAddRequest\x12\x18\n\x10\x63onfigurationUid\x18\x01 \x01(\x0c\x12\x11\n\trecordKey\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12(\n\x0brecordLinks\x18\x04 \x03(\x0b\x32\x13.Records.RecordLink\x12#\n\x05\x61udit\x18\x05 \x01(\x0b\x32\x14.Records.RecordAudit\"J\n\x10RelayAccessCreds\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x12\n\nserverTime\x18\x03 \x01(\x03\"\\\n\x10KturnAccessCreds\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\x0c\x12\x18\n\x10\x63\x65rt_fingerprint\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x04\"\x81\x02\n\x14PAMRecordingsRequest\x12\x11\n\trecordUid\x18\x01 \x01(\x0c\x12\x10\n\x08maxCount\x18\x02 \x01(\x05\x12\x17\n\nrangeStart\x18\x03 \x01(\x03H\x00\x88\x01\x01\x12\x15\n\x08rangeEnd\x18\x04 \x01(\x03H\x01\x88\x01\x01\x12$\n\x05types\x18\x05 \x03(\x0e\x32\x15.PAM.PAMRecordingType\x12)\n\x05risks\x18\x06 \x03(\x0e\x32\x1a.PAM.PAMRecordingRiskLevel\x12\x11\n\tprotocols\x18\x07 \x03(\t\x12\x14\n\x0c\x63loseReasons\x18\x08 \x03(\x05\x42\r\n\x0b_rangeStartB\x0b\n\t_rangeEnd\"\x89\x02\n\x1cPAMRecordingsForUsersRequest\x12\x11\n\tusernames\x18\x01 \x03(\t\x12\x10\n\x08maxCount\x18\x02 \x01(\x05\x12\x17\n\nrangeStart\x18\x03 \x01(\x03H\x00\x88\x01\x01\x12\x15\n\x08rangeEnd\x18\x04 \x01(\x03H\x01\x88\x01\x01\x12$\n\x05types\x18\x05 \x03(\x0e\x32\x15.PAM.PAMRecordingType\x12)\n\x05risks\x18\x06 \x03(\x0e\x32\x1a.PAM.PAMRecordingRiskLevel\x12\x11\n\tprotocols\x18\x07 \x03(\t\x12\x14\n\x0c\x63loseReasons\x18\x08 \x03(\x05\x42\r\n\x0b_rangeStartB\x0b\n\t_rangeEnd\"\xd4\x02\n\x0cPAMRecording\x12\x15\n\rconnectionUid\x18\x01 \x01(\x0c\x12,\n\rrecordingType\x18\x02 \x01(\x0e\x32\x15.PAM.PAMRecordingType\x12\x11\n\trecordUid\x18\x03 \x01(\x0c\x12\x10\n\x08userName\x18\x04 \x01(\t\x12\x11\n\tstartedOn\x18\x05 \x01(\x03\x12\x0e\n\x06length\x18\x06 \x01(\x05\x12\x10\n\x08\x66ileSize\x18\x07 \x01(\x03\x12\x11\n\tcreatedOn\x18\x08 \x01(\x03\x12\x10\n\x08protocol\x18\t \x01(\t\x12\x13\n\x0b\x63loseReason\x18\n \x01(\x05\x12\x19\n\x11recordingDuration\x18\x0b \x01(\x05\x12\x36\n\x12\x61iOverallRiskLevel\x18\x0c \x01(\x0e\x32\x1a.PAM.PAMRecordingRiskLevel\x12\x18\n\x10\x61iOverallSummary\x18\r \x01(\x0c\"O\n\x15PAMRecordingsResponse\x12%\n\nrecordings\x18\x01 \x03(\x0b\x32\x11.PAM.PAMRecording\x12\x0f\n\x07hasMore\x18\x02 \x01(\x08\"*\n\x07PAMData\x12\x0e\n\x06vertex\x18\x01 \x01(\x0c\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\x0c\"\x17\n\x07UidList\x12\x0c\n\x04uids\x18\x01 \x03(\x0c\"5\n\x0fPAMServiceNames\x12\x13\n\x0bresourceUid\x18\x01 \x01(\x0c\x12\r\n\x05names\x18\x02 \x01(\x0c\"\xb4\x03\n\x11PAMResourceConfig\x12\x11\n\trecordUid\x18\x01 \x01(\x0c\x12\x17\n\nnetworkUid\x18\x02 \x01(\x0cH\x00\x88\x01\x01\x12\x15\n\x08\x61\x64minUid\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x11\n\x04meta\x18\x04 \x01(\x0cH\x02\x88\x01\x01\x12\x1f\n\x12\x63onnectionSettings\x18\x05 \x01(\x0cH\x03\x88\x01\x01\x12\'\n\x0c\x63onnectUsers\x18\x06 \x01(\x0b\x32\x0c.PAM.UidListH\x04\x88\x01\x01\x12\x16\n\tdomainUid\x18\x07 \x01(\x0cH\x05\x88\x01\x01\x12\x18\n\x0bjitSettings\x18\x08 \x01(\x0cH\x06\x88\x01\x01\x12\x1d\n\x10keeperAiSettings\x18\t \x01(\x0cH\x07\x88\x01\x01\x12\x1b\n\x0eupdateServices\x18\n \x01(\x08H\x08\x88\x01\x01\x42\r\n\x0b_networkUidB\x0b\n\t_adminUidB\x07\n\x05_metaB\x15\n\x13_connectionSettingsB\x0f\n\r_connectUsersB\x0c\n\n_domainUidB\x0e\n\x0c_jitSettingsB\x13\n\x11_keeperAiSettingsB\x11\n\x0f_updateServices\"%\n\x16PAMUniversalSyncFolder\x12\x0b\n\x03uid\x18\x01 \x01(\x0c\" \n\x10GitHubRepository\x12\x0c\n\x04name\x18\x01 \x01(\x0c\"\xd6\x01\n\x0cGitHubConfig\x12$\n\x05scope\x18\x01 \x01(\x0e\x32\x10.PAM.GitHubScopeH\x00\x88\x01\x01\x12\r\n\x05owner\x18\x02 \x01(\x0c\x12\x46\n\x16organizationVisibility\x18\x03 \x01(\x0e\x32!.PAM.GitHubOrganizationVisibilityH\x01\x88\x01\x01\x12$\n\x05repos\x18\x04 \x03(\x0b\x32\x15.PAM.GitHubRepositoryB\x08\n\x06_scopeB\x19\n\x17_organizationVisibility\"\xaf\x02\n\x16PAMUniversalSyncConfig\x12\x12\n\nnetworkUid\x18\x01 \x01(\x0c\x12\x14\n\x07\x65nabled\x18\x02 \x01(\x08H\x00\x88\x01\x01\x12\x1a\n\rdryRunEnabled\x18\x03 \x01(\x08H\x01\x88\x01\x01\x12,\n\x07\x66olders\x18\x04 \x03(\x0b\x32\x1b.PAM.PAMUniversalSyncFolder\x12\x19\n\x0csyncIdentity\x18\x05 \x01(\x0cH\x02\x88\x01\x01\x12\x16\n\tvaultName\x18\x06 \x01(\x0cH\x03\x88\x01\x01\x12&\n\x06github\x18\x07 \x01(\x0b\x32\x11.PAM.GitHubConfigH\x04\x88\x01\x01\x42\n\n\x08_enabledB\x10\n\x0e_dryRunEnabledB\x0f\n\r_syncIdentityB\x0c\n\n_vaultNameB\t\n\x07_github\"7\n\x11NhiMetricsRequest\x12\x11\n\tstartTime\x18\x01 \x01(\x03\x12\x0f\n\x07\x65ndTime\x18\x02 \x01(\x03\"\x9c\x02\n\x0ePamUsageByUser\x12\x0e\n\x06userId\x18\x01 \x01(\x05\x12!\n\x19recordRotationScheduledOk\x18\x02 \x01(\x05\x12\x1c\n\x14pamConnectionStarted\x18\x03 \x01(\x05\x12\x18\n\x10pamTunnelStarted\x18\x04 \x01(\x05\x12\x1b\n\x13\x64iscoveryJobStarted\x18\x05 \x01(\x05\x12 \n\x18recordRotationOnDemandOk\x18\x06 \x01(\x05\x12\"\n\x1apamSessionRecordingStarted\x18\x07 \x01(\x05\x12\x15\n\rpamRbiStarted\x18\x08 \x01(\x05\x12%\n\x1dpamSessionRbiRecordingStarted\x18\t \x01(\x05\"p\n\x0eNhiUsageByUser\x12\x0e\n\x06userId\x18\x01 \x01(\x05\x12\x11\n\trotations\x18\x02 \x01(\x05\x12\x0f\n\x07tunnels\x18\x03 \x01(\x05\x12\x13\n\x0b\x63onnections\x18\x04 \x01(\x05\x12\x15\n\rdiscoveryJobs\x18\x05 \x01(\x05\"\x84\x02\n\x12NhiMetricsResponse\x12\x14\n\x0c\x65nterpriseId\x18\x01 \x01(\x03\x12\x11\n\tstartTime\x18\x02 \x01(\x03\x12\x0f\n\x07\x65ndTime\x18\x03 \x01(\x03\x12\x18\n\x10uniqueKsmDevices\x18\x04 \x01(\x05\x12\x18\n\x10pamGatewayOnline\x18\x05 \x01(\x05\x12/\n\x0epamUsageByUser\x18\x06 \x03(\x0b\x32\x13.PAM.PamUsageByUserB\x02\x18\x01\x12\x10\n\x08nhiCount\x18\x07 \x01(\x05\x12\x13\n\x0bksmNhiCount\x18\x08 \x01(\x05\x12(\n\x0busageByUser\x18\t \x03(\x0b\x32\x13.PAM.NhiUsageByUser\"D\n\x16NhiBulkMetricsResponse\x12*\n\tresponses\x18\x01 \x03(\x0b\x32\x17.PAM.NhiMetricsResponse\"^\n\x0bNhiUidEntry\x12\x0b\n\x03uid\x18\x01 \x01(\t\x12\"\n\x08\x63\x61tegory\x18\x02 \x01(\x0e\x32\x10.PAM.NhiCategory\x12\x0e\n\x06ksmNhi\x18\x03 \x01(\x08\x12\x0e\n\x06\x61ppUid\x18\x04 \x01(\t\"7\n\x11GetNhiUidsRequest\x12\x11\n\tstartTime\x18\x01 \x01(\x03\x12\x0f\n\x07\x65ndTime\x18\x02 \x01(\x03\"4\n\x12GetNhiUidsResponse\x12\x1e\n\x04uids\x18\x01 \x03(\x0b\x32\x10.PAM.NhiUidEntry\"6\n\x1dSetNhiKsmEffectiveDateRequest\x12\x15\n\reffectiveDate\x18\x01 \x01(\x03\"L\n\x1eGetNhiKsmEffectiveDateResponse\x12\x15\n\reffectiveDate\x18\x01 \x01(\x03\x12\x13\n\x0b\x64\x65\x66\x61ultDate\x18\x02 \x01(\x03\"I\n\x1fPAMUniversalSyncPreCheckRequest\x12\x12\n\nnetworkUid\x18\x01 \x01(\x0c\x12\x12\n\nfolderUids\x18\x02 \x03(\x0c\"C\n\x1ePAMUniversalSyncPreCheckResult\x12\x11\n\tfolderUid\x18\x01 \x01(\x0c\x12\x0e\n\x06isUsed\x18\x02 \x01(\x08\"X\n PAMUniversalSyncPreCheckResponse\x12\x34\n\x07results\x18\x01 \x03(\x0b\x32#.PAM.PAMUniversalSyncPreCheckResult*\x9e\x01\n\x14WebRtcConnectionType\x12\x0e\n\nCONNECTION\x10\x00\x12\n\n\x06TUNNEL\x10\x01\x12\x07\n\x03SSH\x10\x02\x12\x07\n\x03RDP\x10\x03\x12\x08\n\x04HTTP\x10\x04\x12\x07\n\x03VNC\x10\x05\x12\n\n\x06TELNET\x10\x06\x12\t\n\x05MYSQL\x10\x07\x12\x0e\n\nSQL_SERVER\x10\x08\x12\x0e\n\nPOSTGRESQL\x10\t\x12\x0e\n\nKUBERNETES\x10\n*@\n\x10PAMOperationType\x12\x07\n\x03\x41\x44\x44\x10\x00\x12\n\n\x06UPDATE\x10\x01\x12\x0b\n\x07REPLACE\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x03*p\n\x16PAMOperationResultType\x12\x0f\n\x0bPOT_SUCCESS\x10\x00\x12\x15\n\x11POT_UNKNOWN_ERROR\x10\x01\x12\x16\n\x12POT_ALREADY_EXISTS\x10\x02\x12\x16\n\x12POT_DOES_NOT_EXIST\x10\x03*\xc9\x01\n\x15\x43ontrollerMessageType\x12\x0f\n\x0b\x43MT_GENERAL\x10\x00\x12\x0e\n\nCMT_ROTATE\x10\x01\x12\x11\n\rCMT_DISCOVERY\x10\x02\x12\x0f\n\x0b\x43MT_CONNECT\x10\x03\x12\x19\n\x15\x43MT_ANALYZE_RECORDING\x10\x04\x12!\n\x1d\x43MT_WORKFLOW_ACCESS_ELEVATION\x10\x05\x12\x0b\n\x07\x43MT_USS\x10\x06\x12\x0c\n\x08\x43MT_INFO\x10\x07\x12\x12\n\x0e\x43MT_AUTOMATION\x10\x08*V\n\x10PAMRecordingType\x12\x0f\n\x0bPRT_SESSION\x10\x00\x12\x12\n\x0ePRT_TYPESCRIPT\x10\x01\x12\x0c\n\x08PRT_TIME\x10\x02\x12\x0f\n\x0bPRT_SUMMARY\x10\x03*i\n\x15PAMRecordingRiskLevel\x12\x13\n\x0fPRR_UNSPECIFIED\x10\x00\x12\x0b\n\x07PRR_LOW\x10\x01\x12\x0e\n\nPRR_MEDIUM\x10\x02\x12\x0c\n\x08PRR_HIGH\x10\x03\x12\x10\n\x0cPRR_CRITICAL\x10\x04*/\n\x0bGitHubScope\x12\x0e\n\nREPOSITORY\x10\x00\x12\x10\n\x0cORGANIZATION\x10\x01*B\n\x1cGitHubOrganizationVisibility\x12\x07\n\x03\x41LL\x10\x00\x12\x0b\n\x07PRIVATE\x10\x01\x12\x0c\n\x08SELECTED\x10\x02*`\n\x0bNhiCategory\x12\x18\n\x14NHI_CATEGORY_UNKNOWN\x10\x00\x12\x0c\n\x08PAM_USER\x10\x01\x12\x10\n\x0cPAM_RESOURCE\x10\x02\x12\x0b\n\x07GATEWAY\x10\x03\x12\n\n\x06\x44\x45VICE\x10\x04\x42\x1f\n\x18\x63om.keepersecurity.protoB\x03PAMb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -36,20 +36,24 @@ _globals['DESCRIPTOR']._serialized_options = b'\n\030com.keepersecurity.protoB\003PAM' _globals['_NHIMETRICSRESPONSE'].fields_by_name['pamUsageByUser']._loaded_options = None _globals['_NHIMETRICSRESPONSE'].fields_by_name['pamUsageByUser']._serialized_options = b'\030\001' - _globals['_WEBRTCCONNECTIONTYPE']._serialized_start=5340 - _globals['_WEBRTCCONNECTIONTYPE']._serialized_end=5498 - _globals['_PAMOPERATIONTYPE']._serialized_start=5500 - _globals['_PAMOPERATIONTYPE']._serialized_end=5564 - _globals['_PAMOPERATIONRESULTTYPE']._serialized_start=5566 - _globals['_PAMOPERATIONRESULTTYPE']._serialized_end=5678 - _globals['_CONTROLLERMESSAGETYPE']._serialized_start=5681 - _globals['_CONTROLLERMESSAGETYPE']._serialized_end=5882 - _globals['_PAMRECORDINGTYPE']._serialized_start=5884 - _globals['_PAMRECORDINGTYPE']._serialized_end=5970 - _globals['_PAMRECORDINGRISKLEVEL']._serialized_start=5972 - _globals['_PAMRECORDINGRISKLEVEL']._serialized_end=6077 - _globals['_NHICATEGORY']._serialized_start=6079 - _globals['_NHICATEGORY']._serialized_end=6175 + _globals['_WEBRTCCONNECTIONTYPE']._serialized_start=6267 + _globals['_WEBRTCCONNECTIONTYPE']._serialized_end=6425 + _globals['_PAMOPERATIONTYPE']._serialized_start=6427 + _globals['_PAMOPERATIONTYPE']._serialized_end=6491 + _globals['_PAMOPERATIONRESULTTYPE']._serialized_start=6493 + _globals['_PAMOPERATIONRESULTTYPE']._serialized_end=6605 + _globals['_CONTROLLERMESSAGETYPE']._serialized_start=6608 + _globals['_CONTROLLERMESSAGETYPE']._serialized_end=6809 + _globals['_PAMRECORDINGTYPE']._serialized_start=6811 + _globals['_PAMRECORDINGTYPE']._serialized_end=6897 + _globals['_PAMRECORDINGRISKLEVEL']._serialized_start=6899 + _globals['_PAMRECORDINGRISKLEVEL']._serialized_end=7004 + _globals['_GITHUBSCOPE']._serialized_start=7006 + _globals['_GITHUBSCOPE']._serialized_end=7053 + _globals['_GITHUBORGANIZATIONVISIBILITY']._serialized_start=7055 + _globals['_GITHUBORGANIZATIONVISIBILITY']._serialized_end=7121 + _globals['_NHICATEGORY']._serialized_start=7123 + _globals['_NHICATEGORY']._serialized_end=7219 _globals['_PAMROTATIONSCHEDULE']._serialized_start=51 _globals['_PAMROTATIONSCHEDULE']._serialized_end=182 _globals['_PAMROTATIONSCHEDULESRESPONSE']._serialized_start=184 @@ -91,49 +95,67 @@ _globals['_PAMCONFIGURATIONS']._serialized_start=1883 _globals['_PAMCONFIGURATIONS']._serialized_end=1949 _globals['_PAMCONTROLLER']._serialized_start=1952 - _globals['_PAMCONTROLLER']._serialized_end=2207 - _globals['_PAMSETMAXINSTANCECOUNTREQUEST']._serialized_start=2209 - _globals['_PAMSETMAXINSTANCECOUNTREQUEST']._serialized_end=2289 - _globals['_CONTROLLERRESPONSE']._serialized_start=2291 - _globals['_CONTROLLERRESPONSE']._serialized_end=2328 - _globals['_PAMCONFIGURATIONCONTROLLER']._serialized_start=2330 - _globals['_PAMCONFIGURATIONCONTROLLER']._serialized_end=2407 - _globals['_CONFIGURATIONADDREQUEST']._serialized_start=2410 - _globals['_CONFIGURATIONADDREQUEST']._serialized_end=2573 - _globals['_RELAYACCESSCREDS']._serialized_start=2575 - _globals['_RELAYACCESSCREDS']._serialized_end=2649 - _globals['_PAMRECORDINGSREQUEST']._serialized_start=2652 - _globals['_PAMRECORDINGSREQUEST']._serialized_end=2909 - _globals['_PAMRECORDINGSFORUSERSREQUEST']._serialized_start=2912 - _globals['_PAMRECORDINGSFORUSERSREQUEST']._serialized_end=3177 - _globals['_PAMRECORDING']._serialized_start=3180 - _globals['_PAMRECORDING']._serialized_end=3520 - _globals['_PAMRECORDINGSRESPONSE']._serialized_start=3522 - _globals['_PAMRECORDINGSRESPONSE']._serialized_end=3601 - _globals['_PAMDATA']._serialized_start=3603 - _globals['_PAMDATA']._serialized_end=3645 - _globals['_UIDLIST']._serialized_start=3647 - _globals['_UIDLIST']._serialized_end=3670 - _globals['_PAMRESOURCECONFIG']._serialized_start=3673 - _globals['_PAMRESOURCECONFIG']._serialized_end=4061 - _globals['_PAMUNIVERSALSYNCFOLDER']._serialized_start=4063 - _globals['_PAMUNIVERSALSYNCFOLDER']._serialized_end=4100 - _globals['_PAMUNIVERSALSYNCCONFIG']._serialized_start=4103 - _globals['_PAMUNIVERSALSYNCCONFIG']._serialized_end=4355 - _globals['_NHIMETRICSREQUEST']._serialized_start=4357 - _globals['_NHIMETRICSREQUEST']._serialized_end=4412 - _globals['_PAMUSAGEBYUSER']._serialized_start=4415 - _globals['_PAMUSAGEBYUSER']._serialized_end=4699 - _globals['_NHIUSAGEBYUSER']._serialized_start=4701 - _globals['_NHIUSAGEBYUSER']._serialized_end=4813 - _globals['_NHIMETRICSRESPONSE']._serialized_start=4816 - _globals['_NHIMETRICSRESPONSE']._serialized_end=5076 - _globals['_NHIBULKMETRICSRESPONSE']._serialized_start=5078 - _globals['_NHIBULKMETRICSRESPONSE']._serialized_end=5146 - _globals['_NHIUIDENTRY']._serialized_start=5148 - _globals['_NHIUIDENTRY']._serialized_end=5226 - _globals['_GETNHIUIDSREQUEST']._serialized_start=5228 - _globals['_GETNHIUIDSREQUEST']._serialized_end=5283 - _globals['_GETNHIUIDSRESPONSE']._serialized_start=5285 - _globals['_GETNHIUIDSRESPONSE']._serialized_end=5337 + _globals['_PAMCONTROLLER']._serialized_end=2251 + _globals['_PAMSETMAXINSTANCECOUNTREQUEST']._serialized_start=2253 + _globals['_PAMSETMAXINSTANCECOUNTREQUEST']._serialized_end=2333 + _globals['_CONTROLLERRESPONSE']._serialized_start=2335 + _globals['_CONTROLLERRESPONSE']._serialized_end=2372 + _globals['_PAMCONFIGURATIONCONTROLLER']._serialized_start=2374 + _globals['_PAMCONFIGURATIONCONTROLLER']._serialized_end=2451 + _globals['_CONFIGURATIONADDREQUEST']._serialized_start=2454 + _globals['_CONFIGURATIONADDREQUEST']._serialized_end=2617 + _globals['_RELAYACCESSCREDS']._serialized_start=2619 + _globals['_RELAYACCESSCREDS']._serialized_end=2693 + _globals['_KTURNACCESSCREDS']._serialized_start=2695 + _globals['_KTURNACCESSCREDS']._serialized_end=2787 + _globals['_PAMRECORDINGSREQUEST']._serialized_start=2790 + _globals['_PAMRECORDINGSREQUEST']._serialized_end=3047 + _globals['_PAMRECORDINGSFORUSERSREQUEST']._serialized_start=3050 + _globals['_PAMRECORDINGSFORUSERSREQUEST']._serialized_end=3315 + _globals['_PAMRECORDING']._serialized_start=3318 + _globals['_PAMRECORDING']._serialized_end=3658 + _globals['_PAMRECORDINGSRESPONSE']._serialized_start=3660 + _globals['_PAMRECORDINGSRESPONSE']._serialized_end=3739 + _globals['_PAMDATA']._serialized_start=3741 + _globals['_PAMDATA']._serialized_end=3783 + _globals['_UIDLIST']._serialized_start=3785 + _globals['_UIDLIST']._serialized_end=3808 + _globals['_PAMSERVICENAMES']._serialized_start=3810 + _globals['_PAMSERVICENAMES']._serialized_end=3863 + _globals['_PAMRESOURCECONFIG']._serialized_start=3866 + _globals['_PAMRESOURCECONFIG']._serialized_end=4302 + _globals['_PAMUNIVERSALSYNCFOLDER']._serialized_start=4304 + _globals['_PAMUNIVERSALSYNCFOLDER']._serialized_end=4341 + _globals['_GITHUBREPOSITORY']._serialized_start=4343 + _globals['_GITHUBREPOSITORY']._serialized_end=4375 + _globals['_GITHUBCONFIG']._serialized_start=4378 + _globals['_GITHUBCONFIG']._serialized_end=4592 + _globals['_PAMUNIVERSALSYNCCONFIG']._serialized_start=4595 + _globals['_PAMUNIVERSALSYNCCONFIG']._serialized_end=4898 + _globals['_NHIMETRICSREQUEST']._serialized_start=4900 + _globals['_NHIMETRICSREQUEST']._serialized_end=4955 + _globals['_PAMUSAGEBYUSER']._serialized_start=4958 + _globals['_PAMUSAGEBYUSER']._serialized_end=5242 + _globals['_NHIUSAGEBYUSER']._serialized_start=5244 + _globals['_NHIUSAGEBYUSER']._serialized_end=5356 + _globals['_NHIMETRICSRESPONSE']._serialized_start=5359 + _globals['_NHIMETRICSRESPONSE']._serialized_end=5619 + _globals['_NHIBULKMETRICSRESPONSE']._serialized_start=5621 + _globals['_NHIBULKMETRICSRESPONSE']._serialized_end=5689 + _globals['_NHIUIDENTRY']._serialized_start=5691 + _globals['_NHIUIDENTRY']._serialized_end=5785 + _globals['_GETNHIUIDSREQUEST']._serialized_start=5787 + _globals['_GETNHIUIDSREQUEST']._serialized_end=5842 + _globals['_GETNHIUIDSRESPONSE']._serialized_start=5844 + _globals['_GETNHIUIDSRESPONSE']._serialized_end=5896 + _globals['_SETNHIKSMEFFECTIVEDATEREQUEST']._serialized_start=5898 + _globals['_SETNHIKSMEFFECTIVEDATEREQUEST']._serialized_end=5952 + _globals['_GETNHIKSMEFFECTIVEDATERESPONSE']._serialized_start=5954 + _globals['_GETNHIKSMEFFECTIVEDATERESPONSE']._serialized_end=6030 + _globals['_PAMUNIVERSALSYNCPRECHECKREQUEST']._serialized_start=6032 + _globals['_PAMUNIVERSALSYNCPRECHECKREQUEST']._serialized_end=6105 + _globals['_PAMUNIVERSALSYNCPRECHECKRESULT']._serialized_start=6107 + _globals['_PAMUNIVERSALSYNCPRECHECKRESULT']._serialized_end=6174 + _globals['_PAMUNIVERSALSYNCPRECHECKRESPONSE']._serialized_start=6176 + _globals['_PAMUNIVERSALSYNCPRECHECKRESPONSE']._serialized_end=6264 # @@protoc_insertion_point(module_scope) diff --git a/keepercommander/proto/pam_pb2.pyi b/keepercommander/proto/pam_pb2.pyi index b5a56975a..b83fdc735 100644 --- a/keepercommander/proto/pam_pb2.pyi +++ b/keepercommander/proto/pam_pb2.pyi @@ -4,7 +4,8 @@ from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union DESCRIPTOR: _descriptor.FileDescriptor @@ -63,6 +64,17 @@ class PAMRecordingRiskLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): PRR_HIGH: _ClassVar[PAMRecordingRiskLevel] PRR_CRITICAL: _ClassVar[PAMRecordingRiskLevel] +class GitHubScope(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + REPOSITORY: _ClassVar[GitHubScope] + ORGANIZATION: _ClassVar[GitHubScope] + +class GitHubOrganizationVisibility(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ALL: _ClassVar[GitHubOrganizationVisibility] + PRIVATE: _ClassVar[GitHubOrganizationVisibility] + SELECTED: _ClassVar[GitHubOrganizationVisibility] + class NhiCategory(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): __slots__ = () NHI_CATEGORY_UNKNOWN: _ClassVar[NhiCategory] @@ -107,6 +119,11 @@ PRR_LOW: PAMRecordingRiskLevel PRR_MEDIUM: PAMRecordingRiskLevel PRR_HIGH: PAMRecordingRiskLevel PRR_CRITICAL: PAMRecordingRiskLevel +REPOSITORY: GitHubScope +ORGANIZATION: GitHubScope +ALL: GitHubOrganizationVisibility +PRIVATE: GitHubOrganizationVisibility +SELECTED: GitHubOrganizationVisibility NHI_CATEGORY_UNKNOWN: NhiCategory PAM_USER: NhiCategory PAM_RESOURCE: NhiCategory @@ -125,7 +142,7 @@ class PAMRotationSchedule(_message.Message): controllerUid: bytes scheduleData: str noSchedule: bool - def __init__(self, recordUid: _Optional[bytes] = ..., configurationUid: _Optional[bytes] = ..., controllerUid: _Optional[bytes] = ..., scheduleData: _Optional[str] = ..., noSchedule: bool = ...) -> None: ... + def __init__(self, recordUid: _Optional[bytes] = ..., configurationUid: _Optional[bytes] = ..., controllerUid: _Optional[bytes] = ..., scheduleData: _Optional[str] = ..., noSchedule: _Optional[bool] = ...) -> None: ... class PAMRotationSchedulesResponse(_message.Message): __slots__ = ("schedules",) @@ -304,7 +321,7 @@ class PAMConfigurations(_message.Message): def __init__(self, configurations: _Optional[_Iterable[_Union[PAMConfiguration, _Mapping]]] = ...) -> None: ... class PAMController(_message.Message): - __slots__ = ("controllerUid", "controllerName", "deviceToken", "deviceName", "nodeId", "created", "lastModified", "applicationUid", "appClientType", "isInitialized") + __slots__ = ("controllerUid", "controllerName", "deviceToken", "deviceName", "nodeId", "created", "lastModified", "applicationUid", "appClientType", "isInitialized", "maxInstanceCount", "lastSeen") CONTROLLERUID_FIELD_NUMBER: _ClassVar[int] CONTROLLERNAME_FIELD_NUMBER: _ClassVar[int] DEVICETOKEN_FIELD_NUMBER: _ClassVar[int] @@ -315,6 +332,8 @@ class PAMController(_message.Message): APPLICATIONUID_FIELD_NUMBER: _ClassVar[int] APPCLIENTTYPE_FIELD_NUMBER: _ClassVar[int] ISINITIALIZED_FIELD_NUMBER: _ClassVar[int] + MAXINSTANCECOUNT_FIELD_NUMBER: _ClassVar[int] + LASTSEEN_FIELD_NUMBER: _ClassVar[int] controllerUid: bytes controllerName: str deviceToken: str @@ -325,7 +344,9 @@ class PAMController(_message.Message): applicationUid: bytes appClientType: _enterprise_pb2.AppClientType isInitialized: bool - def __init__(self, controllerUid: _Optional[bytes] = ..., controllerName: _Optional[str] = ..., deviceToken: _Optional[str] = ..., deviceName: _Optional[str] = ..., nodeId: _Optional[int] = ..., created: _Optional[int] = ..., lastModified: _Optional[int] = ..., applicationUid: _Optional[bytes] = ..., appClientType: _Optional[_Union[_enterprise_pb2.AppClientType, str]] = ..., isInitialized: bool = ...) -> None: ... + maxInstanceCount: int + lastSeen: int + def __init__(self, controllerUid: _Optional[bytes] = ..., controllerName: _Optional[str] = ..., deviceToken: _Optional[str] = ..., deviceName: _Optional[str] = ..., nodeId: _Optional[int] = ..., created: _Optional[int] = ..., lastModified: _Optional[int] = ..., applicationUid: _Optional[bytes] = ..., appClientType: _Optional[_Union[_enterprise_pb2.AppClientType, str]] = ..., isInitialized: _Optional[bool] = ..., maxInstanceCount: _Optional[int] = ..., lastSeen: _Optional[int] = ...) -> None: ... class PAMSetMaxInstanceCountRequest(_message.Message): __slots__ = ("controllerUid", "maxInstanceCount") @@ -373,6 +394,18 @@ class RelayAccessCreds(_message.Message): serverTime: int def __init__(self, username: _Optional[str] = ..., password: _Optional[str] = ..., serverTime: _Optional[int] = ...) -> None: ... +class KturnAccessCreds(_message.Message): + __slots__ = ("url", "token", "cert_fingerprint", "expires_at") + URL_FIELD_NUMBER: _ClassVar[int] + TOKEN_FIELD_NUMBER: _ClassVar[int] + CERT_FINGERPRINT_FIELD_NUMBER: _ClassVar[int] + EXPIRES_AT_FIELD_NUMBER: _ClassVar[int] + url: str + token: bytes + cert_fingerprint: str + expires_at: int + def __init__(self, url: _Optional[str] = ..., token: _Optional[bytes] = ..., cert_fingerprint: _Optional[str] = ..., expires_at: _Optional[int] = ...) -> None: ... + class PAMRecordingsRequest(_message.Message): __slots__ = ("recordUid", "maxCount", "rangeStart", "rangeEnd", "types", "risks", "protocols", "closeReasons") RECORDUID_FIELD_NUMBER: _ClassVar[int] @@ -393,6 +426,26 @@ class PAMRecordingsRequest(_message.Message): closeReasons: _containers.RepeatedScalarFieldContainer[int] def __init__(self, recordUid: _Optional[bytes] = ..., maxCount: _Optional[int] = ..., rangeStart: _Optional[int] = ..., rangeEnd: _Optional[int] = ..., types: _Optional[_Iterable[_Union[PAMRecordingType, str]]] = ..., risks: _Optional[_Iterable[_Union[PAMRecordingRiskLevel, str]]] = ..., protocols: _Optional[_Iterable[str]] = ..., closeReasons: _Optional[_Iterable[int]] = ...) -> None: ... +class PAMRecordingsForUsersRequest(_message.Message): + __slots__ = ("usernames", "maxCount", "rangeStart", "rangeEnd", "types", "risks", "protocols", "closeReasons") + USERNAMES_FIELD_NUMBER: _ClassVar[int] + MAXCOUNT_FIELD_NUMBER: _ClassVar[int] + RANGESTART_FIELD_NUMBER: _ClassVar[int] + RANGEEND_FIELD_NUMBER: _ClassVar[int] + TYPES_FIELD_NUMBER: _ClassVar[int] + RISKS_FIELD_NUMBER: _ClassVar[int] + PROTOCOLS_FIELD_NUMBER: _ClassVar[int] + CLOSEREASONS_FIELD_NUMBER: _ClassVar[int] + usernames: _containers.RepeatedScalarFieldContainer[str] + maxCount: int + rangeStart: int + rangeEnd: int + types: _containers.RepeatedScalarFieldContainer[PAMRecordingType] + risks: _containers.RepeatedScalarFieldContainer[PAMRecordingRiskLevel] + protocols: _containers.RepeatedScalarFieldContainer[str] + closeReasons: _containers.RepeatedScalarFieldContainer[int] + def __init__(self, usernames: _Optional[_Iterable[str]] = ..., maxCount: _Optional[int] = ..., rangeStart: _Optional[int] = ..., rangeEnd: _Optional[int] = ..., types: _Optional[_Iterable[_Union[PAMRecordingType, str]]] = ..., risks: _Optional[_Iterable[_Union[PAMRecordingRiskLevel, str]]] = ..., protocols: _Optional[_Iterable[str]] = ..., closeReasons: _Optional[_Iterable[int]] = ...) -> None: ... + class PAMRecording(_message.Message): __slots__ = ("connectionUid", "recordingType", "recordUid", "userName", "startedOn", "length", "fileSize", "createdOn", "protocol", "closeReason", "recordingDuration", "aiOverallRiskLevel", "aiOverallSummary") CONNECTIONUID_FIELD_NUMBER: _ClassVar[int] @@ -429,7 +482,7 @@ class PAMRecordingsResponse(_message.Message): HASMORE_FIELD_NUMBER: _ClassVar[int] recordings: _containers.RepeatedCompositeFieldContainer[PAMRecording] hasMore: bool - def __init__(self, recordings: _Optional[_Iterable[_Union[PAMRecording, _Mapping]]] = ..., hasMore: bool = ...) -> None: ... + def __init__(self, recordings: _Optional[_Iterable[_Union[PAMRecording, _Mapping]]] = ..., hasMore: _Optional[bool] = ...) -> None: ... class PAMData(_message.Message): __slots__ = ("vertex", "content") @@ -445,6 +498,14 @@ class UidList(_message.Message): uids: _containers.RepeatedScalarFieldContainer[bytes] def __init__(self, uids: _Optional[_Iterable[bytes]] = ...) -> None: ... +class PAMServiceNames(_message.Message): + __slots__ = ("resourceUid", "names") + RESOURCEUID_FIELD_NUMBER: _ClassVar[int] + NAMES_FIELD_NUMBER: _ClassVar[int] + resourceUid: bytes + names: bytes + def __init__(self, resourceUid: _Optional[bytes] = ..., names: _Optional[bytes] = ...) -> None: ... + class PAMResourceConfig(_message.Message): __slots__ = ("recordUid", "networkUid", "adminUid", "meta", "connectionSettings", "connectUsers", "domainUid", "jitSettings", "keeperAiSettings", "updateServices") RECORDUID_FIELD_NUMBER: _ClassVar[int] @@ -467,7 +528,7 @@ class PAMResourceConfig(_message.Message): jitSettings: bytes keeperAiSettings: bytes updateServices: bool - def __init__(self, recordUid: _Optional[bytes] = ..., networkUid: _Optional[bytes] = ..., adminUid: _Optional[bytes] = ..., meta: _Optional[bytes] = ..., connectionSettings: _Optional[bytes] = ..., connectUsers: _Optional[_Union[UidList, _Mapping]] = ..., domainUid: _Optional[bytes] = ..., jitSettings: _Optional[bytes] = ..., keeperAiSettings: _Optional[bytes] = ..., updateServices: bool = ...) -> None: ... + def __init__(self, recordUid: _Optional[bytes] = ..., networkUid: _Optional[bytes] = ..., adminUid: _Optional[bytes] = ..., meta: _Optional[bytes] = ..., connectionSettings: _Optional[bytes] = ..., connectUsers: _Optional[_Union[UidList, _Mapping]] = ..., domainUid: _Optional[bytes] = ..., jitSettings: _Optional[bytes] = ..., keeperAiSettings: _Optional[bytes] = ..., updateServices: _Optional[bool] = ...) -> None: ... class PAMUniversalSyncFolder(_message.Message): __slots__ = ("uid",) @@ -475,21 +536,41 @@ class PAMUniversalSyncFolder(_message.Message): uid: bytes def __init__(self, uid: _Optional[bytes] = ...) -> None: ... +class GitHubRepository(_message.Message): + __slots__ = ("name",) + NAME_FIELD_NUMBER: _ClassVar[int] + name: bytes + def __init__(self, name: _Optional[bytes] = ...) -> None: ... + +class GitHubConfig(_message.Message): + __slots__ = ("scope", "owner", "organizationVisibility", "repos") + SCOPE_FIELD_NUMBER: _ClassVar[int] + OWNER_FIELD_NUMBER: _ClassVar[int] + ORGANIZATIONVISIBILITY_FIELD_NUMBER: _ClassVar[int] + REPOS_FIELD_NUMBER: _ClassVar[int] + scope: GitHubScope + owner: bytes + organizationVisibility: GitHubOrganizationVisibility + repos: _containers.RepeatedCompositeFieldContainer[GitHubRepository] + def __init__(self, scope: _Optional[_Union[GitHubScope, str]] = ..., owner: _Optional[bytes] = ..., organizationVisibility: _Optional[_Union[GitHubOrganizationVisibility, str]] = ..., repos: _Optional[_Iterable[_Union[GitHubRepository, _Mapping]]] = ...) -> None: ... + class PAMUniversalSyncConfig(_message.Message): - __slots__ = ("networkUid", "enabled", "dryRunEnabled", "folders", "syncIdentity", "vaultName") + __slots__ = ("networkUid", "enabled", "dryRunEnabled", "folders", "syncIdentity", "vaultName", "github") NETWORKUID_FIELD_NUMBER: _ClassVar[int] ENABLED_FIELD_NUMBER: _ClassVar[int] DRYRUNENABLED_FIELD_NUMBER: _ClassVar[int] FOLDERS_FIELD_NUMBER: _ClassVar[int] SYNCIDENTITY_FIELD_NUMBER: _ClassVar[int] VAULTNAME_FIELD_NUMBER: _ClassVar[int] + GITHUB_FIELD_NUMBER: _ClassVar[int] networkUid: bytes enabled: bool dryRunEnabled: bool folders: _containers.RepeatedCompositeFieldContainer[PAMUniversalSyncFolder] syncIdentity: bytes vaultName: bytes - def __init__(self, networkUid: _Optional[bytes] = ..., enabled: bool = ..., dryRunEnabled: bool = ..., folders: _Optional[_Iterable[_Union[PAMUniversalSyncFolder, _Mapping]]] = ..., syncIdentity: _Optional[bytes] = ..., vaultName: _Optional[bytes] = ...) -> None: ... + github: GitHubConfig + def __init__(self, networkUid: _Optional[bytes] = ..., enabled: _Optional[bool] = ..., dryRunEnabled: _Optional[bool] = ..., folders: _Optional[_Iterable[_Union[PAMUniversalSyncFolder, _Mapping]]] = ..., syncIdentity: _Optional[bytes] = ..., vaultName: _Optional[bytes] = ..., github: _Optional[_Union[GitHubConfig, _Mapping]] = ...) -> None: ... class NhiMetricsRequest(_message.Message): __slots__ = ("startTime", "endTime") @@ -564,14 +645,16 @@ class NhiBulkMetricsResponse(_message.Message): def __init__(self, responses: _Optional[_Iterable[_Union[NhiMetricsResponse, _Mapping]]] = ...) -> None: ... class NhiUidEntry(_message.Message): - __slots__ = ("uid", "category", "ksmNhi") + __slots__ = ("uid", "category", "ksmNhi", "appUid") UID_FIELD_NUMBER: _ClassVar[int] CATEGORY_FIELD_NUMBER: _ClassVar[int] KSMNHI_FIELD_NUMBER: _ClassVar[int] + APPUID_FIELD_NUMBER: _ClassVar[int] uid: str category: NhiCategory ksmNhi: bool - def __init__(self, uid: _Optional[str] = ..., category: _Optional[_Union[NhiCategory, str]] = ..., ksmNhi: bool = ...) -> None: ... + appUid: str + def __init__(self, uid: _Optional[str] = ..., category: _Optional[_Union[NhiCategory, str]] = ..., ksmNhi: _Optional[bool] = ..., appUid: _Optional[str] = ...) -> None: ... class GetNhiUidsRequest(_message.Message): __slots__ = ("startTime", "endTime") @@ -587,6 +670,20 @@ class GetNhiUidsResponse(_message.Message): uids: _containers.RepeatedCompositeFieldContainer[NhiUidEntry] def __init__(self, uids: _Optional[_Iterable[_Union[NhiUidEntry, _Mapping]]] = ...) -> None: ... +class SetNhiKsmEffectiveDateRequest(_message.Message): + __slots__ = ("effectiveDate",) + EFFECTIVEDATE_FIELD_NUMBER: _ClassVar[int] + effectiveDate: int + def __init__(self, effectiveDate: _Optional[int] = ...) -> None: ... + +class GetNhiKsmEffectiveDateResponse(_message.Message): + __slots__ = ("effectiveDate", "defaultDate") + EFFECTIVEDATE_FIELD_NUMBER: _ClassVar[int] + DEFAULTDATE_FIELD_NUMBER: _ClassVar[int] + effectiveDate: int + defaultDate: int + def __init__(self, effectiveDate: _Optional[int] = ..., defaultDate: _Optional[int] = ...) -> None: ... + class PAMUniversalSyncPreCheckRequest(_message.Message): __slots__ = ("networkUid", "folderUids") NETWORKUID_FIELD_NUMBER: _ClassVar[int] @@ -601,7 +698,7 @@ class PAMUniversalSyncPreCheckResult(_message.Message): ISUSED_FIELD_NUMBER: _ClassVar[int] folderUid: bytes isUsed: bool - def __init__(self, folderUid: _Optional[bytes] = ..., isUsed: bool = ...) -> None: ... + def __init__(self, folderUid: _Optional[bytes] = ..., isUsed: _Optional[bool] = ...) -> None: ... class PAMUniversalSyncPreCheckResponse(_message.Message): __slots__ = ("results",) diff --git a/keepercommander/rest_api.py b/keepercommander/rest_api.py index bae28ec21..8105b0221 100644 --- a/keepercommander/rest_api.py +++ b/keepercommander/rest_api.py @@ -131,7 +131,7 @@ def encrypt_with_keeper_key(context, data: bytes) -> bytes: raise KeeperApiError('invalid_key_id', f'Key ID \"{key_id}\" is not valid.') -DEFAULT_TIMEOUT = (15, 120) +DEFAULT_TIMEOUT = (15, 300) def execute_rest(context, endpoint, payload, timeout=None): diff --git a/keepercommander/service/commands/integrations/integration_setup_base.py b/keepercommander/service/commands/integrations/integration_setup_base.py index ee7e783ad..aea4cb932 100644 --- a/keepercommander/service/commands/integrations/integration_setup_base.py +++ b/keepercommander/service/commands/integrations/integration_setup_base.py @@ -344,11 +344,10 @@ def _create_integration_record(self, params, record_name: str, return record_uid def _find_record_in_folder(self, params, folder_uid: str, record_name: str): - if folder_uid in params.subfolder_record_cache: - for rec_uid in params.subfolder_record_cache[folder_uid]: - rec = api.get_record(params, rec_uid) - if rec.title == record_name: - return rec_uid + for rec_uid in (params.subfolder_record_cache or {}).get(folder_uid, []): + rec = api.get_record(params, rec_uid) + if rec.title == record_name and self._is_owned_record(params, rec_uid): + return rec_uid return None def _create_login_record(self, params, folder_uid: str, record_name: str) -> str: @@ -394,9 +393,9 @@ def _patch_record_approvals(self, params, record_uid: str, approvals: ApprovalsC raise CommandError(self.get_command_name(), f'Failed to update record fields: {str(e)}') def _find_folder_uid_by_name(self, params, folder_name: str) -> Optional[str]: - # Prefer shared folders; integration setup always creates a shared folder. - for folder_uid, folder_data in params.shared_folder_cache.items(): - if folder_data.get('name') == folder_name: + """Return an owned shared folder UID with this name, or None.""" + for folder_uid, folder_data in (params.shared_folder_cache or {}).items(): + if folder_data.get('name') == folder_name and self._is_owned_shared_folder(params, folder_uid): return folder_uid return None diff --git a/keepercommander/service/docker/setup_base.py b/keepercommander/service/docker/setup_base.py index 49285225a..48fbb07bb 100644 --- a/keepercommander/service/docker/setup_base.py +++ b/keepercommander/service/docker/setup_base.py @@ -24,9 +24,8 @@ import tempfile from typing import Dict, Any -from ...commands.folder import FolderMakeCommand from ...commands.ksm import KSMCommand -from ... import api, vault, utils, attachment, record_management, loginv3 +from ... import api, vault, utils, crypto, attachment, record_management, loginv3 from ...display import bcolors from ...error import CommandError @@ -39,6 +38,51 @@ class DockerSetupBase: """Base class for Docker setup with reusable core logic""" + @staticmethod + def _is_owned_record(params, record_uid: str) -> bool: + """True when record_owner_cache marks the record as owned by this account.""" + if not record_uid: + return False + owner_cache = getattr(params, 'record_owner_cache', None) or {} + owner = owner_cache.get(record_uid) + return bool(owner and owner.owner) + + @staticmethod + def _is_owned_shared_folder(params, folder_uid: str) -> bool: + """True when the shared folder is owned by the current account.""" + if not folder_uid: + return False + shared_folder = (getattr(params, 'shared_folder_cache', None) or {}).get(folder_uid) + if not shared_folder: + return False + + owner_uid = shared_folder.get('owner_account_uid') + account_uid_bytes = getattr(params, 'account_uid_bytes', None) + if owner_uid and account_uid_bytes: + return owner_uid == utils.base64_url_encode(account_uid_bytes) + + owner_username = shared_folder.get('owner_username') + if owner_username and getattr(params, 'user', None): + return owner_username.lower() == params.user.lower() + return False + + @staticmethod + def _find_owned_ksm_app_by_title(params, app_name: str): + """Return an owned KSM app with this title, or None (ignores non-owned squats).""" + if not app_name: + return None + for rec_cache_val in (getattr(params, 'record_cache', None) or {}).values(): + rec = KSMCommand._app_record_from_cache_entry(rec_cache_val) + if not rec: + continue + try: + title = json.loads(rec['data_unencrypted'].decode('utf-8')).get('title') + except Exception: + continue + if title == app_name and DockerSetupBase._is_owned_record(params, rec.get('record_uid')): + return rec + return None + @staticmethod def resolve_commander_config_path(config_path: str = None, params=None) -> str: """Resolve config.json using the same rules as login/shell startup.""" @@ -163,57 +207,94 @@ def _setup_device(self, params, timeout: str) -> None: except Exception as e: raise CommandError('docker-setup', f'Device setup failed: {str(e)}') + @staticmethod + def _add_owned_shared_folder(params, folder_name: str) -> str: + """Create a root-level shared folder via folder_add (allows duplicate display names). + + Bypasses FolderMakeCommand/mkdir, which refuses to create when any visible + folder already has the same name — including a non-owned squat. + """ + name = (folder_name or '').strip() + if not name: + raise CommandError('docker-setup', 'Shared folder name is required') + if not getattr(params, 'data_key', None): + raise CommandError('docker-setup', 'Not logged in (missing data key)') + + folder_uid = api.generate_record_uid() + folder_key = utils.generate_aes_key() + request = { + 'command': 'folder_add', + 'folder_type': 'shared_folder', + 'folder_uid': folder_uid, + 'key': utils.base64_url_encode(crypto.encrypt_aes_v1(folder_key, params.data_key)), + 'name': utils.base64_url_encode(crypto.encrypt_aes_v1(name.encode('utf-8'), folder_key)), + 'data': utils.base64_url_encode( + crypto.encrypt_aes_v1(json.dumps({'name': name}).encode('utf-8'), folder_key) + ), + 'manage_users': True, + 'manage_records': True, + 'can_share': True, + 'can_edit': True, + } + api.communicate(params, request) + params.sync_data = True + return folder_uid + def _create_shared_folder(self, params, folder_name: str) -> str: - """Create shared folder or return existing one""" - # Check if folder exists - for folder_uid, folder in params.folder_cache.items(): - if folder.name == folder_name and folder_uid in params.shared_folder_cache: - DockerSetupPrinter.print_success("Using existing shared folder") + """Create a shared folder, or reuse one owned by this account with the same name.""" + saw_non_owned = False + for folder_uid, folder in (params.folder_cache or {}).items(): + if folder.name != folder_name or folder_uid not in (params.shared_folder_cache or {}): + continue + if self._is_owned_shared_folder(params, folder_uid): + DockerSetupPrinter.print_success(f"Using existing shared folder (UID: {folder_uid})") return folder_uid + saw_non_owned = True + + if saw_non_owned: + DockerSetupPrinter.print_warning( + f"Ignoring shared folder(s) named '{folder_name}' owned by another account; " + f"creating a new owned folder" + ) - # Create new folder try: - old_stdout = sys.stdout - sys.stdout = io.StringIO() - try: - folder_uid = FolderMakeCommand().execute( - params, - folder=folder_name, - shared_folder=True, - manage_users=True, - manage_records=True, - can_edit=True, - can_share=True - ) - finally: - sys.stdout = old_stdout + folder_uid = self._add_owned_shared_folder(params, folder_name) api.sync_down(params) DockerSetupPrinter.print_success(f"Shared folder created successfully (UID: {folder_uid})") return folder_uid + except CommandError: + raise except Exception as e: raise CommandError('docker-setup', f'Failed to create shared folder: {str(e)}') def _create_config_record(self, params, record_name: str, folder_uid: str) -> str: - """Create a config record or return existing one""" - # Check if record exists - if folder_uid in params.subfolder_record_cache: - for rec_uid in params.subfolder_record_cache[folder_uid]: - rec = api.get_record(params, rec_uid) - if rec.title == record_name: - DockerSetupPrinter.print_success("Using existing record") - return rec_uid - - # Create new record + """Create a config record, or reuse an owned one with the same title in the folder.""" + saw_non_owned = False + for rec_uid in (params.subfolder_record_cache or {}).get(folder_uid, []): + rec = api.get_record(params, rec_uid) + if rec.title != record_name: + continue + if self._is_owned_record(params, rec_uid): + DockerSetupPrinter.print_success("Using existing record") + return rec_uid + saw_non_owned = True + + if saw_non_owned: + DockerSetupPrinter.print_warning( + f"Ignoring record(s) titled '{record_name}' owned by another account; " + f"creating a new owned record" + ) + try: record = vault.KeeperRecord.create(params, 'login') record.record_uid = utils.generate_uid() record.record_key = utils.generate_aes_key() record.title = record_name record.type_name = 'login' - + record_management.add_record_to_folder(params, record, folder_uid) api.sync_down(params) - + DockerSetupPrinter.print_success(f"Record created successfully (UID: {record.record_uid})") return record.record_uid except Exception as e: @@ -336,32 +417,46 @@ def _clean_config_json(self, config_path: str) -> str: return config_path def _create_ksm_app(self, params, app_name: str) -> str: - """Create KSM app or return existing one""" - # Check if app exists - existing_app = KSMCommand.get_app_record(params, app_name) + """Create a KSM app, or reuse an owned app with the same title.""" + existing_app = self._find_owned_ksm_app_by_title(params, app_name) if existing_app: DockerSetupPrinter.print_success("Using existing app") return existing_app.get('record_uid') - # Create new app + existing_any = KSMCommand.get_app_record(params, app_name) + force_to_add = False + if existing_any: + existing_uid = existing_any.get('record_uid') + if self._is_owned_record(params, existing_uid): + DockerSetupPrinter.print_success("Using existing app") + return existing_uid + force_to_add = True + DockerSetupPrinter.print_warning( + f"Ignoring Secrets Manager app '{app_name}' owned by another account; " + f"creating a new owned app" + ) + try: - # Suppress KSM command output old_stdout = sys.stdout sys.stdout = io.StringIO() try: - KSMCommand.add_new_v5_app(params, app_name, force_to_add=False, format_type='table') + KSMCommand.add_new_v5_app( + params, app_name, force_to_add=force_to_add, format_type='table' + ) finally: sys.stdout = old_stdout - + api.sync_down(params) - - app_rec = KSMCommand.get_app_record(params, app_name) + + app_rec = self._find_owned_ksm_app_by_title(params, app_name) if not app_rec: raise CommandError('docker-setup', 'Failed to retrieve created app') - + app_uid = app_rec.get('record_uid') DockerSetupPrinter.print_success(f"App created successfully (UID: {app_uid})") return app_uid + except CommandError: + raise except Exception as e: raise CommandError('docker-setup', f'Failed to create KSM app: {str(e)}') diff --git a/public_suffix_list.dat b/public_suffix_list.dat deleted file mode 100644 index eab989967..000000000 --- a/public_suffix_list.dat +++ /dev/null @@ -1,13756 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -// Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat, -// rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported. - -// Instructions on pulling and using this list can be found at https://publicsuffix.org/list/. - -// ===BEGIN ICANN DOMAINS=== - -// ac : http://nic.ac/rules.htm -ac -com.ac -edu.ac -gov.ac -net.ac -mil.ac -org.ac - -// ad : https://en.wikipedia.org/wiki/.ad -ad -nom.ad - -// ae : https://tdra.gov.ae/en/aeda/ae-policies -ae -co.ae -net.ae -org.ae -sch.ae -ac.ae -gov.ae -mil.ae - -// aero : see https://www.information.aero/index.php?id=66 -aero -accident-investigation.aero -accident-prevention.aero -aerobatic.aero -aeroclub.aero -aerodrome.aero -agents.aero -aircraft.aero -airline.aero -airport.aero -air-surveillance.aero -airtraffic.aero -air-traffic-control.aero -ambulance.aero -amusement.aero -association.aero -author.aero -ballooning.aero -broker.aero -caa.aero -cargo.aero -catering.aero -certification.aero -championship.aero -charter.aero -civilaviation.aero -club.aero -conference.aero -consultant.aero -consulting.aero -control.aero -council.aero -crew.aero -design.aero -dgca.aero -educator.aero -emergency.aero -engine.aero -engineer.aero -entertainment.aero -equipment.aero -exchange.aero -express.aero -federation.aero -flight.aero -fuel.aero -gliding.aero -government.aero -groundhandling.aero -group.aero -hanggliding.aero -homebuilt.aero -insurance.aero -journal.aero -journalist.aero -leasing.aero -logistics.aero -magazine.aero -maintenance.aero -media.aero -microlight.aero -modelling.aero -navigation.aero -parachuting.aero -paragliding.aero -passenger-association.aero -pilot.aero -press.aero -production.aero -recreation.aero -repbody.aero -res.aero -research.aero -rotorcraft.aero -safety.aero -scientist.aero -services.aero -show.aero -skydiving.aero -software.aero -student.aero -trader.aero -trading.aero -trainer.aero -union.aero -workinggroup.aero -works.aero - -// af : http://www.nic.af/help.jsp -af -gov.af -com.af -org.af -net.af -edu.af - -// ag : http://www.nic.ag/prices.htm -ag -com.ag -org.ag -net.ag -co.ag -nom.ag - -// ai : http://nic.com.ai/ -ai -off.ai -com.ai -net.ai -org.ai - -// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31 -al -com.al -edu.al -gov.al -mil.al -net.al -org.al - -// am : https://www.amnic.net/policy/en/Policy_EN.pdf -am -co.am -com.am -commune.am -net.am -org.am - -// ao : https://en.wikipedia.org/wiki/.ao -// http://www.dns.ao/REGISTR.DOC -ao -ed.ao -gv.ao -og.ao -co.ao -pb.ao -it.ao - -// aq : https://en.wikipedia.org/wiki/.aq -aq - -// ar : https://nic.ar/es/nic-argentina/normativa -ar -bet.ar -com.ar -coop.ar -edu.ar -gob.ar -gov.ar -int.ar -mil.ar -musica.ar -mutual.ar -net.ar -org.ar -senasa.ar -tur.ar - -// arpa : https://en.wikipedia.org/wiki/.arpa -// Confirmed by registry 2008-06-18 -arpa -e164.arpa -in-addr.arpa -ip6.arpa -iris.arpa -uri.arpa -urn.arpa - -// as : https://en.wikipedia.org/wiki/.as -as -gov.as - -// asia : https://en.wikipedia.org/wiki/.asia -asia - -// at : https://en.wikipedia.org/wiki/.at -// Confirmed by registry 2008-06-17 -at -ac.at -co.at -gv.at -or.at -sth.ac.at - -// au : https://en.wikipedia.org/wiki/.au -// http://www.auda.org.au/ -au -// 2LDs -com.au -net.au -org.au -edu.au -gov.au -asn.au -id.au -// Historic 2LDs (closed to new registration, but sites still exist) -info.au -conf.au -oz.au -// CGDNs - http://www.cgdn.org.au/ -act.au -nsw.au -nt.au -qld.au -sa.au -tas.au -vic.au -wa.au -// 3LDs -act.edu.au -catholic.edu.au -// eq.edu.au - Removed at the request of the Queensland Department of Education -nsw.edu.au -nt.edu.au -qld.edu.au -sa.edu.au -tas.edu.au -vic.edu.au -wa.edu.au -// act.gov.au Bug 984824 - Removed at request of Greg Tankard -// nsw.gov.au Bug 547985 - Removed at request of -// nt.gov.au Bug 940478 - Removed at request of Greg Connors -qld.gov.au -sa.gov.au -tas.gov.au -vic.gov.au -wa.gov.au -// 4LDs -// education.tas.edu.au - Removed at the request of the Department of Education Tasmania -schools.nsw.edu.au - -// aw : https://en.wikipedia.org/wiki/.aw -aw -com.aw - -// ax : https://en.wikipedia.org/wiki/.ax -ax - -// az : https://en.wikipedia.org/wiki/.az -az -com.az -net.az -int.az -gov.az -org.az -edu.az -info.az -pp.az -mil.az -name.az -pro.az -biz.az - -// ba : http://nic.ba/users_data/files/pravilnik_o_registraciji.pdf -ba -com.ba -edu.ba -gov.ba -mil.ba -net.ba -org.ba - -// bb : https://en.wikipedia.org/wiki/.bb -bb -biz.bb -co.bb -com.bb -edu.bb -gov.bb -info.bb -net.bb -org.bb -store.bb -tv.bb - -// bd : https://en.wikipedia.org/wiki/.bd -*.bd - -// be : https://en.wikipedia.org/wiki/.be -// Confirmed by registry 2008-06-08 -be -ac.be - -// bf : https://en.wikipedia.org/wiki/.bf -bf -gov.bf - -// bg : https://en.wikipedia.org/wiki/.bg -// https://www.register.bg/user/static/rules/en/index.html -bg -a.bg -b.bg -c.bg -d.bg -e.bg -f.bg -g.bg -h.bg -i.bg -j.bg -k.bg -l.bg -m.bg -n.bg -o.bg -p.bg -q.bg -r.bg -s.bg -t.bg -u.bg -v.bg -w.bg -x.bg -y.bg -z.bg -0.bg -1.bg -2.bg -3.bg -4.bg -5.bg -6.bg -7.bg -8.bg -9.bg - -// bh : https://en.wikipedia.org/wiki/.bh -bh -com.bh -edu.bh -net.bh -org.bh -gov.bh - -// bi : https://en.wikipedia.org/wiki/.bi -// http://whois.nic.bi/ -bi -co.bi -com.bi -edu.bi -or.bi -org.bi - -// biz : https://en.wikipedia.org/wiki/.biz -biz - -// bj : https://nic.bj/bj-suffixes.txt -// submitted by registry -bj -africa.bj -agro.bj -architectes.bj -assur.bj -avocats.bj -co.bj -com.bj -eco.bj -econo.bj -edu.bj -info.bj -loisirs.bj -money.bj -net.bj -org.bj -ote.bj -resto.bj -restaurant.bj -tourism.bj -univ.bj - -// bm : http://www.bermudanic.bm/dnr-text.txt -bm -com.bm -edu.bm -gov.bm -net.bm -org.bm - -// bn : http://www.bnnic.bn/faqs -bn -com.bn -edu.bn -gov.bn -net.bn -org.bn - -// bo : https://nic.bo/delegacion2015.php#h-1.10 -bo -com.bo -edu.bo -gob.bo -int.bo -org.bo -net.bo -mil.bo -tv.bo -web.bo -// Social Domains -academia.bo -agro.bo -arte.bo -blog.bo -bolivia.bo -ciencia.bo -cooperativa.bo -democracia.bo -deporte.bo -ecologia.bo -economia.bo -empresa.bo -indigena.bo -industria.bo -info.bo -medicina.bo -movimiento.bo -musica.bo -natural.bo -nombre.bo -noticias.bo -patria.bo -politica.bo -profesional.bo -plurinacional.bo -pueblo.bo -revista.bo -salud.bo -tecnologia.bo -tksat.bo -transporte.bo -wiki.bo - -// br : http://registro.br/dominio/categoria.html -// Submitted by registry -br -9guacu.br -abc.br -adm.br -adv.br -agr.br -aju.br -am.br -anani.br -aparecida.br -app.br -arq.br -art.br -ato.br -b.br -barueri.br -belem.br -bhz.br -bib.br -bio.br -blog.br -bmd.br -boavista.br -bsb.br -campinagrande.br -campinas.br -caxias.br -cim.br -cng.br -cnt.br -com.br -contagem.br -coop.br -coz.br -cri.br -cuiaba.br -curitiba.br -def.br -des.br -det.br -dev.br -ecn.br -eco.br -edu.br -emp.br -enf.br -eng.br -esp.br -etc.br -eti.br -far.br -feira.br -flog.br -floripa.br -fm.br -fnd.br -fortal.br -fot.br -foz.br -fst.br -g12.br -geo.br -ggf.br -goiania.br -gov.br -// gov.br 26 states + df https://en.wikipedia.org/wiki/States_of_Brazil -ac.gov.br -al.gov.br -am.gov.br -ap.gov.br -ba.gov.br -ce.gov.br -df.gov.br -es.gov.br -go.gov.br -ma.gov.br -mg.gov.br -ms.gov.br -mt.gov.br -pa.gov.br -pb.gov.br -pe.gov.br -pi.gov.br -pr.gov.br -rj.gov.br -rn.gov.br -ro.gov.br -rr.gov.br -rs.gov.br -sc.gov.br -se.gov.br -sp.gov.br -to.gov.br -gru.br -imb.br -ind.br -inf.br -jab.br -jampa.br -jdf.br -joinville.br -jor.br -jus.br -leg.br -lel.br -log.br -londrina.br -macapa.br -maceio.br -manaus.br -maringa.br -mat.br -med.br -mil.br -morena.br -mp.br -mus.br -natal.br -net.br -niteroi.br -*.nom.br -not.br -ntr.br -odo.br -ong.br -org.br -osasco.br -palmas.br -poa.br -ppg.br -pro.br -psc.br -psi.br -pvh.br -qsl.br -radio.br -rec.br -recife.br -rep.br -ribeirao.br -rio.br -riobranco.br -riopreto.br -salvador.br -sampa.br -santamaria.br -santoandre.br -saobernardo.br -saogonca.br -seg.br -sjc.br -slg.br -slz.br -sorocaba.br -srv.br -taxi.br -tc.br -tec.br -teo.br -the.br -tmp.br -trd.br -tur.br -tv.br -udi.br -vet.br -vix.br -vlog.br -wiki.br -zlg.br - -// bs : http://www.nic.bs/rules.html -bs -com.bs -net.bs -org.bs -edu.bs -gov.bs - -// bt : https://en.wikipedia.org/wiki/.bt -bt -com.bt -edu.bt -gov.bt -net.bt -org.bt - -// bv : No registrations at this time. -// Submitted by registry -bv - -// bw : https://en.wikipedia.org/wiki/.bw -// http://www.gobin.info/domainname/bw.doc -// list of other 2nd level tlds ? -bw -co.bw -org.bw - -// by : https://en.wikipedia.org/wiki/.by -// http://tld.by/rules_2006_en.html -// list of other 2nd level tlds ? -by -gov.by -mil.by -// Official information does not indicate that com.by is a reserved -// second-level domain, but it's being used as one (see www.google.com.by and -// www.yahoo.com.by, for example), so we list it here for safety's sake. -com.by - -// http://hoster.by/ -of.by - -// bz : https://en.wikipedia.org/wiki/.bz -// http://www.belizenic.bz/ -bz -com.bz -net.bz -org.bz -edu.bz -gov.bz - -// ca : https://en.wikipedia.org/wiki/.ca -ca -// ca geographical names -ab.ca -bc.ca -mb.ca -nb.ca -nf.ca -nl.ca -ns.ca -nt.ca -nu.ca -on.ca -pe.ca -qc.ca -sk.ca -yk.ca -// gc.ca: https://en.wikipedia.org/wiki/.gc.ca -// see also: http://registry.gc.ca/en/SubdomainFAQ -gc.ca - -// cat : https://en.wikipedia.org/wiki/.cat -cat - -// cc : https://en.wikipedia.org/wiki/.cc -cc - -// cd : https://en.wikipedia.org/wiki/.cd -// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1 -cd -gov.cd - -// cf : https://en.wikipedia.org/wiki/.cf -cf - -// cg : https://en.wikipedia.org/wiki/.cg -cg - -// ch : https://en.wikipedia.org/wiki/.ch -ch - -// ci : https://en.wikipedia.org/wiki/.ci -// http://www.nic.ci/index.php?page=charte -ci -org.ci -or.ci -com.ci -co.ci -edu.ci -ed.ci -ac.ci -net.ci -go.ci -asso.ci -aéroport.ci -int.ci -presse.ci -md.ci -gouv.ci - -// ck : https://en.wikipedia.org/wiki/.ck -*.ck -!www.ck - -// cl : https://www.nic.cl -// Confirmed by .CL registry -cl -co.cl -gob.cl -gov.cl -mil.cl - -// cm : https://en.wikipedia.org/wiki/.cm plus bug 981927 -cm -co.cm -com.cm -gov.cm -net.cm - -// cn : https://en.wikipedia.org/wiki/.cn -// Submitted by registry -cn -ac.cn -com.cn -edu.cn -gov.cn -net.cn -org.cn -mil.cn -公司.cn -网络.cn -網絡.cn -// cn geographic names -ah.cn -bj.cn -cq.cn -fj.cn -gd.cn -gs.cn -gz.cn -gx.cn -ha.cn -hb.cn -he.cn -hi.cn -hl.cn -hn.cn -jl.cn -js.cn -jx.cn -ln.cn -nm.cn -nx.cn -qh.cn -sc.cn -sd.cn -sh.cn -sn.cn -sx.cn -tj.cn -xj.cn -xz.cn -yn.cn -zj.cn -hk.cn -mo.cn -tw.cn - -// co : https://en.wikipedia.org/wiki/.co -// Submitted by registry -co -arts.co -com.co -edu.co -firm.co -gov.co -info.co -int.co -mil.co -net.co -nom.co -org.co -rec.co -web.co - -// com : https://en.wikipedia.org/wiki/.com -com - -// coop : https://en.wikipedia.org/wiki/.coop -coop - -// cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do -cr -ac.cr -co.cr -ed.cr -fi.cr -go.cr -or.cr -sa.cr - -// cu : https://en.wikipedia.org/wiki/.cu -cu -com.cu -edu.cu -org.cu -net.cu -gov.cu -inf.cu - -// cv : https://en.wikipedia.org/wiki/.cv -// cv : http://www.dns.cv/tldcv_portal/do?com=DS;5446457100;111;+PAGE(4000018)+K-CAT-CODIGO(RDOM)+RCNT(100); <- registration rules -cv -com.cv -edu.cv -int.cv -nome.cv -org.cv - -// cw : http://www.una.cw/cw_registry/ -// Confirmed by registry 2013-03-26 -cw -com.cw -edu.cw -net.cw -org.cw - -// cx : https://en.wikipedia.org/wiki/.cx -// list of other 2nd level tlds ? -cx -gov.cx - -// cy : http://www.nic.cy/ -// Submitted by registry Panayiotou Fotia -// namespace policies URL https://www.nic.cy/portal//sites/default/files/symfonia_gia_eggrafi.pdf -cy -ac.cy -biz.cy -com.cy -ekloges.cy -gov.cy -ltd.cy -mil.cy -net.cy -org.cy -press.cy -pro.cy -tm.cy - -// cz : https://en.wikipedia.org/wiki/.cz -cz - -// de : https://en.wikipedia.org/wiki/.de -// Confirmed by registry (with technical -// reservations) 2008-07-01 -de - -// dj : https://en.wikipedia.org/wiki/.dj -dj - -// dk : https://en.wikipedia.org/wiki/.dk -// Confirmed by registry 2008-06-17 -dk - -// dm : https://en.wikipedia.org/wiki/.dm -dm -com.dm -net.dm -org.dm -edu.dm -gov.dm - -// do : https://en.wikipedia.org/wiki/.do -do -art.do -com.do -edu.do -gob.do -gov.do -mil.do -net.do -org.do -sld.do -web.do - -// dz : http://www.nic.dz/images/pdf_nic/charte.pdf -dz -art.dz -asso.dz -com.dz -edu.dz -gov.dz -org.dz -net.dz -pol.dz -soc.dz -tm.dz - -// ec : http://www.nic.ec/reg/paso1.asp -// Submitted by registry -ec -com.ec -info.ec -net.ec -fin.ec -k12.ec -med.ec -pro.ec -org.ec -edu.ec -gov.ec -gob.ec -mil.ec - -// edu : https://en.wikipedia.org/wiki/.edu -edu - -// ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B -ee -edu.ee -gov.ee -riik.ee -lib.ee -med.ee -com.ee -pri.ee -aip.ee -org.ee -fie.ee - -// eg : https://en.wikipedia.org/wiki/.eg -eg -com.eg -edu.eg -eun.eg -gov.eg -mil.eg -name.eg -net.eg -org.eg -sci.eg - -// er : https://en.wikipedia.org/wiki/.er -*.er - -// es : https://www.nic.es/site_ingles/ingles/dominios/index.html -es -com.es -nom.es -org.es -gob.es -edu.es - -// et : https://en.wikipedia.org/wiki/.et -et -com.et -gov.et -org.et -edu.et -biz.et -name.et -info.et -net.et - -// eu : https://en.wikipedia.org/wiki/.eu -eu - -// fi : https://en.wikipedia.org/wiki/.fi -fi -// aland.fi : https://en.wikipedia.org/wiki/.ax -// This domain is being phased out in favor of .ax. As there are still many -// domains under aland.fi, we still keep it on the list until aland.fi is -// completely removed. -// TODO: Check for updates (expected to be phased out around Q1/2009) -aland.fi - -// fj : http://domains.fj/ -// Submitted by registry 2020-02-11 -fj -ac.fj -biz.fj -com.fj -gov.fj -info.fj -mil.fj -name.fj -net.fj -org.fj -pro.fj - -// fk : https://en.wikipedia.org/wiki/.fk -*.fk - -// fm : https://en.wikipedia.org/wiki/.fm -com.fm -edu.fm -net.fm -org.fm -fm - -// fo : https://en.wikipedia.org/wiki/.fo -fo - -// fr : http://www.afnic.fr/ -// domaines descriptifs : https://www.afnic.fr/medias/documents/Cadre_legal/Afnic_Naming_Policy_12122016_VEN.pdf -fr -asso.fr -com.fr -gouv.fr -nom.fr -prd.fr -tm.fr -// domaines sectoriels : https://www.afnic.fr/en/products-and-services/the-fr-tld/sector-based-fr-domains-4.html -aeroport.fr -avocat.fr -avoues.fr -cci.fr -chambagri.fr -chirurgiens-dentistes.fr -experts-comptables.fr -geometre-expert.fr -greta.fr -huissier-justice.fr -medecin.fr -notaires.fr -pharmacien.fr -port.fr -veterinaire.fr - -// ga : https://en.wikipedia.org/wiki/.ga -ga - -// gb : This registry is effectively dormant -// Submitted by registry -gb - -// gd : https://en.wikipedia.org/wiki/.gd -edu.gd -gov.gd -gd - -// ge : http://www.nic.net.ge/policy_en.pdf -ge -com.ge -edu.ge -gov.ge -org.ge -mil.ge -net.ge -pvt.ge - -// gf : https://en.wikipedia.org/wiki/.gf -gf - -// gg : http://www.channelisles.net/register-domains/ -// Confirmed by registry 2013-11-28 -gg -co.gg -net.gg -org.gg - -// gh : https://en.wikipedia.org/wiki/.gh -// see also: http://www.nic.gh/reg_now.php -// Although domains directly at second level are not possible at the moment, -// they have been possible for some time and may come back. -gh -com.gh -edu.gh -gov.gh -org.gh -mil.gh - -// gi : http://www.nic.gi/rules.html -gi -com.gi -ltd.gi -gov.gi -mod.gi -edu.gi -org.gi - -// gl : https://en.wikipedia.org/wiki/.gl -// http://nic.gl -gl -co.gl -com.gl -edu.gl -net.gl -org.gl - -// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm -gm - -// gn : http://psg.com/dns/gn/gn.txt -// Submitted by registry -gn -ac.gn -com.gn -edu.gn -gov.gn -org.gn -net.gn - -// gov : https://en.wikipedia.org/wiki/.gov -gov - -// gp : http://www.nic.gp/index.php?lang=en -gp -com.gp -net.gp -mobi.gp -edu.gp -org.gp -asso.gp - -// gq : https://en.wikipedia.org/wiki/.gq -gq - -// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html -// Submitted by registry -gr -com.gr -edu.gr -net.gr -org.gr -gov.gr - -// gs : https://en.wikipedia.org/wiki/.gs -gs - -// gt : https://www.gt/sitio/registration_policy.php?lang=en -gt -com.gt -edu.gt -gob.gt -ind.gt -mil.gt -net.gt -org.gt - -// gu : http://gadao.gov.gu/register.html -// University of Guam : https://www.uog.edu -// Submitted by uognoc@triton.uog.edu -gu -com.gu -edu.gu -gov.gu -guam.gu -info.gu -net.gu -org.gu -web.gu - -// gw : https://en.wikipedia.org/wiki/.gw -// gw : https://nic.gw/regras/ -gw - -// gy : https://en.wikipedia.org/wiki/.gy -// http://registry.gy/ -gy -co.gy -com.gy -edu.gy -gov.gy -net.gy -org.gy - -// hk : https://www.hkirc.hk -// Submitted by registry -hk -com.hk -edu.hk -gov.hk -idv.hk -net.hk -org.hk -公司.hk -教育.hk -敎育.hk -政府.hk -個人.hk -个人.hk -箇人.hk -網络.hk -网络.hk -组織.hk -網絡.hk -网絡.hk -组织.hk -組織.hk -組织.hk - -// hm : https://en.wikipedia.org/wiki/.hm -hm - -// hn : http://www.nic.hn/politicas/ps02,,05.html -hn -com.hn -edu.hn -org.hn -net.hn -mil.hn -gob.hn - -// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf -hr -iz.hr -from.hr -name.hr -com.hr - -// ht : http://www.nic.ht/info/charte.cfm -ht -com.ht -shop.ht -firm.ht -info.ht -adult.ht -net.ht -pro.ht -org.ht -med.ht -art.ht -coop.ht -pol.ht -asso.ht -edu.ht -rel.ht -gouv.ht -perso.ht - -// hu : http://www.domain.hu/domain/English/sld.html -// Confirmed by registry 2008-06-12 -hu -co.hu -info.hu -org.hu -priv.hu -sport.hu -tm.hu -2000.hu -agrar.hu -bolt.hu -casino.hu -city.hu -erotica.hu -erotika.hu -film.hu -forum.hu -games.hu -hotel.hu -ingatlan.hu -jogasz.hu -konyvelo.hu -lakas.hu -media.hu -news.hu -reklam.hu -sex.hu -shop.hu -suli.hu -szex.hu -tozsde.hu -utazas.hu -video.hu - -// id : https://pandi.id/en/domain/registration-requirements/ -id -ac.id -biz.id -co.id -desa.id -go.id -mil.id -my.id -net.id -or.id -ponpes.id -sch.id -web.id - -// ie : https://en.wikipedia.org/wiki/.ie -ie -gov.ie - -// il : http://www.isoc.org.il/domains/ -// see also: https://en.isoc.org.il/il-cctld/registration-rules -// ISOC-IL (operated by .il Registry) -il -ac.il -co.il -gov.il -idf.il -k12.il -muni.il -net.il -org.il -// xn--4dbrk0ce ("Israel", Hebrew) : IL -ישראל -// xn--4dbgdty6c.xn--4dbrk0ce. -אקדמיה.ישראל -// xn--5dbhl8d.xn--4dbrk0ce. -ישוב.ישראל -// xn--8dbq2a.xn--4dbrk0ce. -צהל.ישראל -// xn--hebda8b.xn--4dbrk0ce. -ממשל.ישראל - -// im : https://www.nic.im/ -// Submitted by registry -im -ac.im -co.im -com.im -ltd.co.im -net.im -org.im -plc.co.im -tt.im -tv.im - -// in : https://en.wikipedia.org/wiki/.in -// see also: https://registry.in/policies -// Please note, that nic.in is not an official eTLD, but used by most -// government institutions. -in -5g.in -6g.in -ac.in -ai.in -am.in -bihar.in -biz.in -business.in -ca.in -cn.in -co.in -com.in -coop.in -cs.in -delhi.in -dr.in -edu.in -er.in -firm.in -gen.in -gov.in -gujarat.in -ind.in -info.in -int.in -internet.in -io.in -me.in -mil.in -net.in -nic.in -org.in -pg.in -post.in -pro.in -res.in -travel.in -tv.in -uk.in -up.in -us.in - -// info : https://en.wikipedia.org/wiki/.info -info - -// int : https://en.wikipedia.org/wiki/.int -// Confirmed by registry 2008-06-18 -int -eu.int - -// io : http://www.nic.io/rules.htm -// list of other 2nd level tlds ? -io -com.io - -// iq : http://www.cmc.iq/english/iq/iqregister1.htm -iq -gov.iq -edu.iq -mil.iq -com.iq -org.iq -net.iq - -// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules -// Also see http://www.nic.ir/Internationalized_Domain_Names -// Two .ir entries added at request of , 2010-04-16 -ir -ac.ir -co.ir -gov.ir -id.ir -net.ir -org.ir -sch.ir -// xn--mgba3a4f16a.ir (.ir, Persian YEH) -ایران.ir -// xn--mgba3a4fra.ir (.ir, Arabic YEH) -ايران.ir - -// is : http://www.isnic.is/domain/rules.php -// Confirmed by registry 2008-12-06 -is -net.is -com.is -edu.is -gov.is -org.is -int.is - -// it : https://en.wikipedia.org/wiki/.it -it -gov.it -edu.it -// Reserved geo-names (regions and provinces): -// https://www.nic.it/sites/default/files/archivio/docs/Regulation_assignation_v7.1.pdf -// Regions -abr.it -abruzzo.it -aosta-valley.it -aostavalley.it -bas.it -basilicata.it -cal.it -calabria.it -cam.it -campania.it -emilia-romagna.it -emiliaromagna.it -emr.it -friuli-v-giulia.it -friuli-ve-giulia.it -friuli-vegiulia.it -friuli-venezia-giulia.it -friuli-veneziagiulia.it -friuli-vgiulia.it -friuliv-giulia.it -friulive-giulia.it -friulivegiulia.it -friulivenezia-giulia.it -friuliveneziagiulia.it -friulivgiulia.it -fvg.it -laz.it -lazio.it -lig.it -liguria.it -lom.it -lombardia.it -lombardy.it -lucania.it -mar.it -marche.it -mol.it -molise.it -piedmont.it -piemonte.it -pmn.it -pug.it -puglia.it -sar.it -sardegna.it -sardinia.it -sic.it -sicilia.it -sicily.it -taa.it -tos.it -toscana.it -trentin-sud-tirol.it -trentin-süd-tirol.it -trentin-sudtirol.it -trentin-südtirol.it -trentin-sued-tirol.it -trentin-suedtirol.it -trentino-a-adige.it -trentino-aadige.it -trentino-alto-adige.it -trentino-altoadige.it -trentino-s-tirol.it -trentino-stirol.it -trentino-sud-tirol.it -trentino-süd-tirol.it -trentino-sudtirol.it -trentino-südtirol.it -trentino-sued-tirol.it -trentino-suedtirol.it -trentino.it -trentinoa-adige.it -trentinoaadige.it -trentinoalto-adige.it -trentinoaltoadige.it -trentinos-tirol.it -trentinostirol.it -trentinosud-tirol.it -trentinosüd-tirol.it -trentinosudtirol.it -trentinosüdtirol.it -trentinosued-tirol.it -trentinosuedtirol.it -trentinsud-tirol.it -trentinsüd-tirol.it -trentinsudtirol.it -trentinsüdtirol.it -trentinsued-tirol.it -trentinsuedtirol.it -tuscany.it -umb.it -umbria.it -val-d-aosta.it -val-daosta.it -vald-aosta.it -valdaosta.it -valle-aosta.it -valle-d-aosta.it -valle-daosta.it -valleaosta.it -valled-aosta.it -valledaosta.it -vallee-aoste.it -vallée-aoste.it -vallee-d-aoste.it -vallée-d-aoste.it -valleeaoste.it -valléeaoste.it -valleedaoste.it -valléedaoste.it -vao.it -vda.it -ven.it -veneto.it -// Provinces -ag.it -agrigento.it -al.it -alessandria.it -alto-adige.it -altoadige.it -an.it -ancona.it -andria-barletta-trani.it -andria-trani-barletta.it -andriabarlettatrani.it -andriatranibarletta.it -ao.it -aosta.it -aoste.it -ap.it -aq.it -aquila.it -ar.it -arezzo.it -ascoli-piceno.it -ascolipiceno.it -asti.it -at.it -av.it -avellino.it -ba.it -balsan-sudtirol.it -balsan-südtirol.it -balsan-suedtirol.it -balsan.it -bari.it -barletta-trani-andria.it -barlettatraniandria.it -belluno.it -benevento.it -bergamo.it -bg.it -bi.it -biella.it -bl.it -bn.it -bo.it -bologna.it -bolzano-altoadige.it -bolzano.it -bozen-sudtirol.it -bozen-südtirol.it -bozen-suedtirol.it -bozen.it -br.it -brescia.it -brindisi.it -bs.it -bt.it -bulsan-sudtirol.it -bulsan-südtirol.it -bulsan-suedtirol.it -bulsan.it -bz.it -ca.it -cagliari.it -caltanissetta.it -campidano-medio.it -campidanomedio.it -campobasso.it -carbonia-iglesias.it -carboniaiglesias.it -carrara-massa.it -carraramassa.it -caserta.it -catania.it -catanzaro.it -cb.it -ce.it -cesena-forli.it -cesena-forlì.it -cesenaforli.it -cesenaforlì.it -ch.it -chieti.it -ci.it -cl.it -cn.it -co.it -como.it -cosenza.it -cr.it -cremona.it -crotone.it -cs.it -ct.it -cuneo.it -cz.it -dell-ogliastra.it -dellogliastra.it -en.it -enna.it -fc.it -fe.it -fermo.it -ferrara.it -fg.it -fi.it -firenze.it -florence.it -fm.it -foggia.it -forli-cesena.it -forlì-cesena.it -forlicesena.it -forlìcesena.it -fr.it -frosinone.it -ge.it -genoa.it -genova.it -go.it -gorizia.it -gr.it -grosseto.it -iglesias-carbonia.it -iglesiascarbonia.it -im.it -imperia.it -is.it -isernia.it -kr.it -la-spezia.it -laquila.it -laspezia.it -latina.it -lc.it -le.it -lecce.it -lecco.it -li.it -livorno.it -lo.it -lodi.it -lt.it -lu.it -lucca.it -macerata.it -mantova.it -massa-carrara.it -massacarrara.it -matera.it -mb.it -mc.it -me.it -medio-campidano.it -mediocampidano.it -messina.it -mi.it -milan.it -milano.it -mn.it -mo.it -modena.it -monza-brianza.it -monza-e-della-brianza.it -monza.it -monzabrianza.it -monzaebrianza.it -monzaedellabrianza.it -ms.it -mt.it -na.it -naples.it -napoli.it -no.it -novara.it -nu.it -nuoro.it -og.it -ogliastra.it -olbia-tempio.it -olbiatempio.it -or.it -oristano.it -ot.it -pa.it -padova.it -padua.it -palermo.it -parma.it -pavia.it -pc.it -pd.it -pe.it -perugia.it -pesaro-urbino.it -pesarourbino.it -pescara.it -pg.it -pi.it -piacenza.it -pisa.it -pistoia.it -pn.it -po.it -pordenone.it -potenza.it -pr.it -prato.it -pt.it -pu.it -pv.it -pz.it -ra.it -ragusa.it -ravenna.it -rc.it -re.it -reggio-calabria.it -reggio-emilia.it -reggiocalabria.it -reggioemilia.it -rg.it -ri.it -rieti.it -rimini.it -rm.it -rn.it -ro.it -roma.it -rome.it -rovigo.it -sa.it -salerno.it -sassari.it -savona.it -si.it -siena.it -siracusa.it -so.it -sondrio.it -sp.it -sr.it -ss.it -suedtirol.it -südtirol.it -sv.it -ta.it -taranto.it -te.it -tempio-olbia.it -tempioolbia.it -teramo.it -terni.it -tn.it -to.it -torino.it -tp.it -tr.it -trani-andria-barletta.it -trani-barletta-andria.it -traniandriabarletta.it -tranibarlettaandria.it -trapani.it -trento.it -treviso.it -trieste.it -ts.it -turin.it -tv.it -ud.it -udine.it -urbino-pesaro.it -urbinopesaro.it -va.it -varese.it -vb.it -vc.it -ve.it -venezia.it -venice.it -verbania.it -vercelli.it -verona.it -vi.it -vibo-valentia.it -vibovalentia.it -vicenza.it -viterbo.it -vr.it -vs.it -vt.it -vv.it - -// je : http://www.channelisles.net/register-domains/ -// Confirmed by registry 2013-11-28 -je -co.je -net.je -org.je - -// jm : http://www.com.jm/register.html -*.jm - -// jo : http://www.dns.jo/Registration_policy.aspx -jo -com.jo -org.jo -net.jo -edu.jo -sch.jo -gov.jo -mil.jo -name.jo - -// jobs : https://en.wikipedia.org/wiki/.jobs -jobs - -// jp : https://en.wikipedia.org/wiki/.jp -// http://jprs.co.jp/en/jpdomain.html -// Submitted by registry -jp -// jp organizational type names -ac.jp -ad.jp -co.jp -ed.jp -go.jp -gr.jp -lg.jp -ne.jp -or.jp -// jp prefecture type names -aichi.jp -akita.jp -aomori.jp -chiba.jp -ehime.jp -fukui.jp -fukuoka.jp -fukushima.jp -gifu.jp -gunma.jp -hiroshima.jp -hokkaido.jp -hyogo.jp -ibaraki.jp -ishikawa.jp -iwate.jp -kagawa.jp -kagoshima.jp -kanagawa.jp -kochi.jp -kumamoto.jp -kyoto.jp -mie.jp -miyagi.jp -miyazaki.jp -nagano.jp -nagasaki.jp -nara.jp -niigata.jp -oita.jp -okayama.jp -okinawa.jp -osaka.jp -saga.jp -saitama.jp -shiga.jp -shimane.jp -shizuoka.jp -tochigi.jp -tokushima.jp -tokyo.jp -tottori.jp -toyama.jp -wakayama.jp -yamagata.jp -yamaguchi.jp -yamanashi.jp -栃木.jp -愛知.jp -愛媛.jp -兵庫.jp -熊本.jp -茨城.jp -北海道.jp -千葉.jp -和歌山.jp -長崎.jp -長野.jp -新潟.jp -青森.jp -静岡.jp -東京.jp -石川.jp -埼玉.jp -三重.jp -京都.jp -佐賀.jp -大分.jp -大阪.jp -奈良.jp -宮城.jp -宮崎.jp -富山.jp -山口.jp -山形.jp -山梨.jp -岩手.jp -岐阜.jp -岡山.jp -島根.jp -広島.jp -徳島.jp -沖縄.jp -滋賀.jp -神奈川.jp -福井.jp -福岡.jp -福島.jp -秋田.jp -群馬.jp -香川.jp -高知.jp -鳥取.jp -鹿児島.jp -// jp geographic type names -// http://jprs.jp/doc/rule/saisoku-1.html -*.kawasaki.jp -*.kitakyushu.jp -*.kobe.jp -*.nagoya.jp -*.sapporo.jp -*.sendai.jp -*.yokohama.jp -!city.kawasaki.jp -!city.kitakyushu.jp -!city.kobe.jp -!city.nagoya.jp -!city.sapporo.jp -!city.sendai.jp -!city.yokohama.jp -// 4th level registration -aisai.aichi.jp -ama.aichi.jp -anjo.aichi.jp -asuke.aichi.jp -chiryu.aichi.jp -chita.aichi.jp -fuso.aichi.jp -gamagori.aichi.jp -handa.aichi.jp -hazu.aichi.jp -hekinan.aichi.jp -higashiura.aichi.jp -ichinomiya.aichi.jp -inazawa.aichi.jp -inuyama.aichi.jp -isshiki.aichi.jp -iwakura.aichi.jp -kanie.aichi.jp -kariya.aichi.jp -kasugai.aichi.jp -kira.aichi.jp -kiyosu.aichi.jp -komaki.aichi.jp -konan.aichi.jp -kota.aichi.jp -mihama.aichi.jp -miyoshi.aichi.jp -nishio.aichi.jp -nisshin.aichi.jp -obu.aichi.jp -oguchi.aichi.jp -oharu.aichi.jp -okazaki.aichi.jp -owariasahi.aichi.jp -seto.aichi.jp -shikatsu.aichi.jp -shinshiro.aichi.jp -shitara.aichi.jp -tahara.aichi.jp -takahama.aichi.jp -tobishima.aichi.jp -toei.aichi.jp -togo.aichi.jp -tokai.aichi.jp -tokoname.aichi.jp -toyoake.aichi.jp -toyohashi.aichi.jp -toyokawa.aichi.jp -toyone.aichi.jp -toyota.aichi.jp -tsushima.aichi.jp -yatomi.aichi.jp -akita.akita.jp -daisen.akita.jp -fujisato.akita.jp -gojome.akita.jp -hachirogata.akita.jp -happou.akita.jp -higashinaruse.akita.jp -honjo.akita.jp -honjyo.akita.jp -ikawa.akita.jp -kamikoani.akita.jp -kamioka.akita.jp -katagami.akita.jp -kazuno.akita.jp -kitaakita.akita.jp -kosaka.akita.jp -kyowa.akita.jp -misato.akita.jp -mitane.akita.jp -moriyoshi.akita.jp -nikaho.akita.jp -noshiro.akita.jp -odate.akita.jp -oga.akita.jp -ogata.akita.jp -semboku.akita.jp -yokote.akita.jp -yurihonjo.akita.jp -aomori.aomori.jp -gonohe.aomori.jp -hachinohe.aomori.jp -hashikami.aomori.jp -hiranai.aomori.jp -hirosaki.aomori.jp -itayanagi.aomori.jp -kuroishi.aomori.jp -misawa.aomori.jp -mutsu.aomori.jp -nakadomari.aomori.jp -noheji.aomori.jp -oirase.aomori.jp -owani.aomori.jp -rokunohe.aomori.jp -sannohe.aomori.jp -shichinohe.aomori.jp -shingo.aomori.jp -takko.aomori.jp -towada.aomori.jp -tsugaru.aomori.jp -tsuruta.aomori.jp -abiko.chiba.jp -asahi.chiba.jp -chonan.chiba.jp -chosei.chiba.jp -choshi.chiba.jp -chuo.chiba.jp -funabashi.chiba.jp -futtsu.chiba.jp -hanamigawa.chiba.jp -ichihara.chiba.jp -ichikawa.chiba.jp -ichinomiya.chiba.jp -inzai.chiba.jp -isumi.chiba.jp -kamagaya.chiba.jp -kamogawa.chiba.jp -kashiwa.chiba.jp -katori.chiba.jp -katsuura.chiba.jp -kimitsu.chiba.jp -kisarazu.chiba.jp -kozaki.chiba.jp -kujukuri.chiba.jp -kyonan.chiba.jp -matsudo.chiba.jp -midori.chiba.jp -mihama.chiba.jp -minamiboso.chiba.jp -mobara.chiba.jp -mutsuzawa.chiba.jp -nagara.chiba.jp -nagareyama.chiba.jp -narashino.chiba.jp -narita.chiba.jp -noda.chiba.jp -oamishirasato.chiba.jp -omigawa.chiba.jp -onjuku.chiba.jp -otaki.chiba.jp -sakae.chiba.jp -sakura.chiba.jp -shimofusa.chiba.jp -shirako.chiba.jp -shiroi.chiba.jp -shisui.chiba.jp -sodegaura.chiba.jp -sosa.chiba.jp -tako.chiba.jp -tateyama.chiba.jp -togane.chiba.jp -tohnosho.chiba.jp -tomisato.chiba.jp -urayasu.chiba.jp -yachimata.chiba.jp -yachiyo.chiba.jp -yokaichiba.chiba.jp -yokoshibahikari.chiba.jp -yotsukaido.chiba.jp -ainan.ehime.jp -honai.ehime.jp -ikata.ehime.jp -imabari.ehime.jp -iyo.ehime.jp -kamijima.ehime.jp -kihoku.ehime.jp -kumakogen.ehime.jp -masaki.ehime.jp -matsuno.ehime.jp -matsuyama.ehime.jp -namikata.ehime.jp -niihama.ehime.jp -ozu.ehime.jp -saijo.ehime.jp -seiyo.ehime.jp -shikokuchuo.ehime.jp -tobe.ehime.jp -toon.ehime.jp -uchiko.ehime.jp -uwajima.ehime.jp -yawatahama.ehime.jp -echizen.fukui.jp -eiheiji.fukui.jp -fukui.fukui.jp -ikeda.fukui.jp -katsuyama.fukui.jp -mihama.fukui.jp -minamiechizen.fukui.jp -obama.fukui.jp -ohi.fukui.jp -ono.fukui.jp -sabae.fukui.jp -sakai.fukui.jp -takahama.fukui.jp -tsuruga.fukui.jp -wakasa.fukui.jp -ashiya.fukuoka.jp -buzen.fukuoka.jp -chikugo.fukuoka.jp -chikuho.fukuoka.jp -chikujo.fukuoka.jp -chikushino.fukuoka.jp -chikuzen.fukuoka.jp -chuo.fukuoka.jp -dazaifu.fukuoka.jp -fukuchi.fukuoka.jp -hakata.fukuoka.jp -higashi.fukuoka.jp -hirokawa.fukuoka.jp -hisayama.fukuoka.jp -iizuka.fukuoka.jp -inatsuki.fukuoka.jp -kaho.fukuoka.jp -kasuga.fukuoka.jp -kasuya.fukuoka.jp -kawara.fukuoka.jp -keisen.fukuoka.jp -koga.fukuoka.jp -kurate.fukuoka.jp -kurogi.fukuoka.jp -kurume.fukuoka.jp -minami.fukuoka.jp -miyako.fukuoka.jp -miyama.fukuoka.jp -miyawaka.fukuoka.jp -mizumaki.fukuoka.jp -munakata.fukuoka.jp -nakagawa.fukuoka.jp -nakama.fukuoka.jp -nishi.fukuoka.jp -nogata.fukuoka.jp -ogori.fukuoka.jp -okagaki.fukuoka.jp -okawa.fukuoka.jp -oki.fukuoka.jp -omuta.fukuoka.jp -onga.fukuoka.jp -onojo.fukuoka.jp -oto.fukuoka.jp -saigawa.fukuoka.jp -sasaguri.fukuoka.jp -shingu.fukuoka.jp -shinyoshitomi.fukuoka.jp -shonai.fukuoka.jp -soeda.fukuoka.jp -sue.fukuoka.jp -tachiarai.fukuoka.jp -tagawa.fukuoka.jp -takata.fukuoka.jp -toho.fukuoka.jp -toyotsu.fukuoka.jp -tsuiki.fukuoka.jp -ukiha.fukuoka.jp -umi.fukuoka.jp -usui.fukuoka.jp -yamada.fukuoka.jp -yame.fukuoka.jp -yanagawa.fukuoka.jp -yukuhashi.fukuoka.jp -aizubange.fukushima.jp -aizumisato.fukushima.jp -aizuwakamatsu.fukushima.jp -asakawa.fukushima.jp -bandai.fukushima.jp -date.fukushima.jp -fukushima.fukushima.jp -furudono.fukushima.jp -futaba.fukushima.jp -hanawa.fukushima.jp -higashi.fukushima.jp -hirata.fukushima.jp -hirono.fukushima.jp -iitate.fukushima.jp -inawashiro.fukushima.jp -ishikawa.fukushima.jp -iwaki.fukushima.jp -izumizaki.fukushima.jp -kagamiishi.fukushima.jp -kaneyama.fukushima.jp -kawamata.fukushima.jp -kitakata.fukushima.jp -kitashiobara.fukushima.jp -koori.fukushima.jp -koriyama.fukushima.jp -kunimi.fukushima.jp -miharu.fukushima.jp -mishima.fukushima.jp -namie.fukushima.jp -nango.fukushima.jp -nishiaizu.fukushima.jp -nishigo.fukushima.jp -okuma.fukushima.jp -omotego.fukushima.jp -ono.fukushima.jp -otama.fukushima.jp -samegawa.fukushima.jp -shimogo.fukushima.jp -shirakawa.fukushima.jp -showa.fukushima.jp -soma.fukushima.jp -sukagawa.fukushima.jp -taishin.fukushima.jp -tamakawa.fukushima.jp -tanagura.fukushima.jp -tenei.fukushima.jp -yabuki.fukushima.jp -yamato.fukushima.jp -yamatsuri.fukushima.jp -yanaizu.fukushima.jp -yugawa.fukushima.jp -anpachi.gifu.jp -ena.gifu.jp -gifu.gifu.jp -ginan.gifu.jp -godo.gifu.jp -gujo.gifu.jp -hashima.gifu.jp -hichiso.gifu.jp -hida.gifu.jp -higashishirakawa.gifu.jp -ibigawa.gifu.jp -ikeda.gifu.jp -kakamigahara.gifu.jp -kani.gifu.jp -kasahara.gifu.jp -kasamatsu.gifu.jp -kawaue.gifu.jp -kitagata.gifu.jp -mino.gifu.jp -minokamo.gifu.jp -mitake.gifu.jp -mizunami.gifu.jp -motosu.gifu.jp -nakatsugawa.gifu.jp -ogaki.gifu.jp -sakahogi.gifu.jp -seki.gifu.jp -sekigahara.gifu.jp -shirakawa.gifu.jp -tajimi.gifu.jp -takayama.gifu.jp -tarui.gifu.jp -toki.gifu.jp -tomika.gifu.jp -wanouchi.gifu.jp -yamagata.gifu.jp -yaotsu.gifu.jp -yoro.gifu.jp -annaka.gunma.jp -chiyoda.gunma.jp -fujioka.gunma.jp -higashiagatsuma.gunma.jp -isesaki.gunma.jp -itakura.gunma.jp -kanna.gunma.jp -kanra.gunma.jp -katashina.gunma.jp -kawaba.gunma.jp -kiryu.gunma.jp -kusatsu.gunma.jp -maebashi.gunma.jp -meiwa.gunma.jp -midori.gunma.jp -minakami.gunma.jp -naganohara.gunma.jp -nakanojo.gunma.jp -nanmoku.gunma.jp -numata.gunma.jp -oizumi.gunma.jp -ora.gunma.jp -ota.gunma.jp -shibukawa.gunma.jp -shimonita.gunma.jp -shinto.gunma.jp -showa.gunma.jp -takasaki.gunma.jp -takayama.gunma.jp -tamamura.gunma.jp -tatebayashi.gunma.jp -tomioka.gunma.jp -tsukiyono.gunma.jp -tsumagoi.gunma.jp -ueno.gunma.jp -yoshioka.gunma.jp -asaminami.hiroshima.jp -daiwa.hiroshima.jp -etajima.hiroshima.jp -fuchu.hiroshima.jp -fukuyama.hiroshima.jp -hatsukaichi.hiroshima.jp -higashihiroshima.hiroshima.jp -hongo.hiroshima.jp -jinsekikogen.hiroshima.jp -kaita.hiroshima.jp -kui.hiroshima.jp -kumano.hiroshima.jp -kure.hiroshima.jp -mihara.hiroshima.jp -miyoshi.hiroshima.jp -naka.hiroshima.jp -onomichi.hiroshima.jp -osakikamijima.hiroshima.jp -otake.hiroshima.jp -saka.hiroshima.jp -sera.hiroshima.jp -seranishi.hiroshima.jp -shinichi.hiroshima.jp -shobara.hiroshima.jp -takehara.hiroshima.jp -abashiri.hokkaido.jp -abira.hokkaido.jp -aibetsu.hokkaido.jp -akabira.hokkaido.jp -akkeshi.hokkaido.jp -asahikawa.hokkaido.jp -ashibetsu.hokkaido.jp -ashoro.hokkaido.jp -assabu.hokkaido.jp -atsuma.hokkaido.jp -bibai.hokkaido.jp -biei.hokkaido.jp -bifuka.hokkaido.jp -bihoro.hokkaido.jp -biratori.hokkaido.jp -chippubetsu.hokkaido.jp -chitose.hokkaido.jp -date.hokkaido.jp -ebetsu.hokkaido.jp -embetsu.hokkaido.jp -eniwa.hokkaido.jp -erimo.hokkaido.jp -esan.hokkaido.jp -esashi.hokkaido.jp -fukagawa.hokkaido.jp -fukushima.hokkaido.jp -furano.hokkaido.jp -furubira.hokkaido.jp -haboro.hokkaido.jp -hakodate.hokkaido.jp -hamatonbetsu.hokkaido.jp -hidaka.hokkaido.jp -higashikagura.hokkaido.jp -higashikawa.hokkaido.jp -hiroo.hokkaido.jp -hokuryu.hokkaido.jp -hokuto.hokkaido.jp -honbetsu.hokkaido.jp -horokanai.hokkaido.jp -horonobe.hokkaido.jp -ikeda.hokkaido.jp -imakane.hokkaido.jp -ishikari.hokkaido.jp -iwamizawa.hokkaido.jp -iwanai.hokkaido.jp -kamifurano.hokkaido.jp -kamikawa.hokkaido.jp -kamishihoro.hokkaido.jp -kamisunagawa.hokkaido.jp -kamoenai.hokkaido.jp -kayabe.hokkaido.jp -kembuchi.hokkaido.jp -kikonai.hokkaido.jp -kimobetsu.hokkaido.jp -kitahiroshima.hokkaido.jp -kitami.hokkaido.jp -kiyosato.hokkaido.jp -koshimizu.hokkaido.jp -kunneppu.hokkaido.jp -kuriyama.hokkaido.jp -kuromatsunai.hokkaido.jp -kushiro.hokkaido.jp -kutchan.hokkaido.jp -kyowa.hokkaido.jp -mashike.hokkaido.jp -matsumae.hokkaido.jp -mikasa.hokkaido.jp -minamifurano.hokkaido.jp -mombetsu.hokkaido.jp -moseushi.hokkaido.jp -mukawa.hokkaido.jp -muroran.hokkaido.jp -naie.hokkaido.jp -nakagawa.hokkaido.jp -nakasatsunai.hokkaido.jp -nakatombetsu.hokkaido.jp -nanae.hokkaido.jp -nanporo.hokkaido.jp -nayoro.hokkaido.jp -nemuro.hokkaido.jp -niikappu.hokkaido.jp -niki.hokkaido.jp -nishiokoppe.hokkaido.jp -noboribetsu.hokkaido.jp -numata.hokkaido.jp -obihiro.hokkaido.jp -obira.hokkaido.jp -oketo.hokkaido.jp -okoppe.hokkaido.jp -otaru.hokkaido.jp -otobe.hokkaido.jp -otofuke.hokkaido.jp -otoineppu.hokkaido.jp -oumu.hokkaido.jp -ozora.hokkaido.jp -pippu.hokkaido.jp -rankoshi.hokkaido.jp -rebun.hokkaido.jp -rikubetsu.hokkaido.jp -rishiri.hokkaido.jp -rishirifuji.hokkaido.jp -saroma.hokkaido.jp -sarufutsu.hokkaido.jp -shakotan.hokkaido.jp -shari.hokkaido.jp -shibecha.hokkaido.jp -shibetsu.hokkaido.jp -shikabe.hokkaido.jp -shikaoi.hokkaido.jp -shimamaki.hokkaido.jp -shimizu.hokkaido.jp -shimokawa.hokkaido.jp -shinshinotsu.hokkaido.jp -shintoku.hokkaido.jp -shiranuka.hokkaido.jp -shiraoi.hokkaido.jp -shiriuchi.hokkaido.jp -sobetsu.hokkaido.jp -sunagawa.hokkaido.jp -taiki.hokkaido.jp -takasu.hokkaido.jp -takikawa.hokkaido.jp -takinoue.hokkaido.jp -teshikaga.hokkaido.jp -tobetsu.hokkaido.jp -tohma.hokkaido.jp -tomakomai.hokkaido.jp -tomari.hokkaido.jp -toya.hokkaido.jp -toyako.hokkaido.jp -toyotomi.hokkaido.jp -toyoura.hokkaido.jp -tsubetsu.hokkaido.jp -tsukigata.hokkaido.jp -urakawa.hokkaido.jp -urausu.hokkaido.jp -uryu.hokkaido.jp -utashinai.hokkaido.jp -wakkanai.hokkaido.jp -wassamu.hokkaido.jp -yakumo.hokkaido.jp -yoichi.hokkaido.jp -aioi.hyogo.jp -akashi.hyogo.jp -ako.hyogo.jp -amagasaki.hyogo.jp -aogaki.hyogo.jp -asago.hyogo.jp -ashiya.hyogo.jp -awaji.hyogo.jp -fukusaki.hyogo.jp -goshiki.hyogo.jp -harima.hyogo.jp -himeji.hyogo.jp -ichikawa.hyogo.jp -inagawa.hyogo.jp -itami.hyogo.jp -kakogawa.hyogo.jp -kamigori.hyogo.jp -kamikawa.hyogo.jp -kasai.hyogo.jp -kasuga.hyogo.jp -kawanishi.hyogo.jp -miki.hyogo.jp -minamiawaji.hyogo.jp -nishinomiya.hyogo.jp -nishiwaki.hyogo.jp -ono.hyogo.jp -sanda.hyogo.jp -sannan.hyogo.jp -sasayama.hyogo.jp -sayo.hyogo.jp -shingu.hyogo.jp -shinonsen.hyogo.jp -shiso.hyogo.jp -sumoto.hyogo.jp -taishi.hyogo.jp -taka.hyogo.jp -takarazuka.hyogo.jp -takasago.hyogo.jp -takino.hyogo.jp -tamba.hyogo.jp -tatsuno.hyogo.jp -toyooka.hyogo.jp -yabu.hyogo.jp -yashiro.hyogo.jp -yoka.hyogo.jp -yokawa.hyogo.jp -ami.ibaraki.jp -asahi.ibaraki.jp -bando.ibaraki.jp -chikusei.ibaraki.jp -daigo.ibaraki.jp -fujishiro.ibaraki.jp -hitachi.ibaraki.jp -hitachinaka.ibaraki.jp -hitachiomiya.ibaraki.jp -hitachiota.ibaraki.jp -ibaraki.ibaraki.jp -ina.ibaraki.jp -inashiki.ibaraki.jp -itako.ibaraki.jp -iwama.ibaraki.jp -joso.ibaraki.jp -kamisu.ibaraki.jp -kasama.ibaraki.jp -kashima.ibaraki.jp -kasumigaura.ibaraki.jp -koga.ibaraki.jp -miho.ibaraki.jp -mito.ibaraki.jp -moriya.ibaraki.jp -naka.ibaraki.jp -namegata.ibaraki.jp -oarai.ibaraki.jp -ogawa.ibaraki.jp -omitama.ibaraki.jp -ryugasaki.ibaraki.jp -sakai.ibaraki.jp -sakuragawa.ibaraki.jp -shimodate.ibaraki.jp -shimotsuma.ibaraki.jp -shirosato.ibaraki.jp -sowa.ibaraki.jp -suifu.ibaraki.jp -takahagi.ibaraki.jp -tamatsukuri.ibaraki.jp -tokai.ibaraki.jp -tomobe.ibaraki.jp -tone.ibaraki.jp -toride.ibaraki.jp -tsuchiura.ibaraki.jp -tsukuba.ibaraki.jp -uchihara.ibaraki.jp -ushiku.ibaraki.jp -yachiyo.ibaraki.jp -yamagata.ibaraki.jp -yawara.ibaraki.jp -yuki.ibaraki.jp -anamizu.ishikawa.jp -hakui.ishikawa.jp -hakusan.ishikawa.jp -kaga.ishikawa.jp -kahoku.ishikawa.jp -kanazawa.ishikawa.jp -kawakita.ishikawa.jp -komatsu.ishikawa.jp -nakanoto.ishikawa.jp -nanao.ishikawa.jp -nomi.ishikawa.jp -nonoichi.ishikawa.jp -noto.ishikawa.jp -shika.ishikawa.jp -suzu.ishikawa.jp -tsubata.ishikawa.jp -tsurugi.ishikawa.jp -uchinada.ishikawa.jp -wajima.ishikawa.jp -fudai.iwate.jp -fujisawa.iwate.jp -hanamaki.iwate.jp -hiraizumi.iwate.jp -hirono.iwate.jp -ichinohe.iwate.jp -ichinoseki.iwate.jp -iwaizumi.iwate.jp -iwate.iwate.jp -joboji.iwate.jp -kamaishi.iwate.jp -kanegasaki.iwate.jp -karumai.iwate.jp -kawai.iwate.jp -kitakami.iwate.jp -kuji.iwate.jp -kunohe.iwate.jp -kuzumaki.iwate.jp -miyako.iwate.jp -mizusawa.iwate.jp -morioka.iwate.jp -ninohe.iwate.jp -noda.iwate.jp -ofunato.iwate.jp -oshu.iwate.jp -otsuchi.iwate.jp -rikuzentakata.iwate.jp -shiwa.iwate.jp -shizukuishi.iwate.jp -sumita.iwate.jp -tanohata.iwate.jp -tono.iwate.jp -yahaba.iwate.jp -yamada.iwate.jp -ayagawa.kagawa.jp -higashikagawa.kagawa.jp -kanonji.kagawa.jp -kotohira.kagawa.jp -manno.kagawa.jp -marugame.kagawa.jp -mitoyo.kagawa.jp -naoshima.kagawa.jp -sanuki.kagawa.jp -tadotsu.kagawa.jp -takamatsu.kagawa.jp -tonosho.kagawa.jp -uchinomi.kagawa.jp -utazu.kagawa.jp -zentsuji.kagawa.jp -akune.kagoshima.jp -amami.kagoshima.jp -hioki.kagoshima.jp -isa.kagoshima.jp -isen.kagoshima.jp -izumi.kagoshima.jp -kagoshima.kagoshima.jp -kanoya.kagoshima.jp -kawanabe.kagoshima.jp -kinko.kagoshima.jp -kouyama.kagoshima.jp -makurazaki.kagoshima.jp -matsumoto.kagoshima.jp -minamitane.kagoshima.jp -nakatane.kagoshima.jp -nishinoomote.kagoshima.jp -satsumasendai.kagoshima.jp -soo.kagoshima.jp -tarumizu.kagoshima.jp -yusui.kagoshima.jp -aikawa.kanagawa.jp -atsugi.kanagawa.jp -ayase.kanagawa.jp -chigasaki.kanagawa.jp -ebina.kanagawa.jp -fujisawa.kanagawa.jp -hadano.kanagawa.jp -hakone.kanagawa.jp -hiratsuka.kanagawa.jp -isehara.kanagawa.jp -kaisei.kanagawa.jp -kamakura.kanagawa.jp -kiyokawa.kanagawa.jp -matsuda.kanagawa.jp -minamiashigara.kanagawa.jp -miura.kanagawa.jp -nakai.kanagawa.jp -ninomiya.kanagawa.jp -odawara.kanagawa.jp -oi.kanagawa.jp -oiso.kanagawa.jp -sagamihara.kanagawa.jp -samukawa.kanagawa.jp -tsukui.kanagawa.jp -yamakita.kanagawa.jp -yamato.kanagawa.jp -yokosuka.kanagawa.jp -yugawara.kanagawa.jp -zama.kanagawa.jp -zushi.kanagawa.jp -aki.kochi.jp -geisei.kochi.jp -hidaka.kochi.jp -higashitsuno.kochi.jp -ino.kochi.jp -kagami.kochi.jp -kami.kochi.jp -kitagawa.kochi.jp -kochi.kochi.jp -mihara.kochi.jp -motoyama.kochi.jp -muroto.kochi.jp -nahari.kochi.jp -nakamura.kochi.jp -nankoku.kochi.jp -nishitosa.kochi.jp -niyodogawa.kochi.jp -ochi.kochi.jp -okawa.kochi.jp -otoyo.kochi.jp -otsuki.kochi.jp -sakawa.kochi.jp -sukumo.kochi.jp -susaki.kochi.jp -tosa.kochi.jp -tosashimizu.kochi.jp -toyo.kochi.jp -tsuno.kochi.jp -umaji.kochi.jp -yasuda.kochi.jp -yusuhara.kochi.jp -amakusa.kumamoto.jp -arao.kumamoto.jp -aso.kumamoto.jp -choyo.kumamoto.jp -gyokuto.kumamoto.jp -kamiamakusa.kumamoto.jp -kikuchi.kumamoto.jp -kumamoto.kumamoto.jp -mashiki.kumamoto.jp -mifune.kumamoto.jp -minamata.kumamoto.jp -minamioguni.kumamoto.jp -nagasu.kumamoto.jp -nishihara.kumamoto.jp -oguni.kumamoto.jp -ozu.kumamoto.jp -sumoto.kumamoto.jp -takamori.kumamoto.jp -uki.kumamoto.jp -uto.kumamoto.jp -yamaga.kumamoto.jp -yamato.kumamoto.jp -yatsushiro.kumamoto.jp -ayabe.kyoto.jp -fukuchiyama.kyoto.jp -higashiyama.kyoto.jp -ide.kyoto.jp -ine.kyoto.jp -joyo.kyoto.jp -kameoka.kyoto.jp -kamo.kyoto.jp -kita.kyoto.jp -kizu.kyoto.jp -kumiyama.kyoto.jp -kyotamba.kyoto.jp -kyotanabe.kyoto.jp -kyotango.kyoto.jp -maizuru.kyoto.jp -minami.kyoto.jp -minamiyamashiro.kyoto.jp -miyazu.kyoto.jp -muko.kyoto.jp -nagaokakyo.kyoto.jp -nakagyo.kyoto.jp -nantan.kyoto.jp -oyamazaki.kyoto.jp -sakyo.kyoto.jp -seika.kyoto.jp -tanabe.kyoto.jp -uji.kyoto.jp -ujitawara.kyoto.jp -wazuka.kyoto.jp -yamashina.kyoto.jp -yawata.kyoto.jp -asahi.mie.jp -inabe.mie.jp -ise.mie.jp -kameyama.mie.jp -kawagoe.mie.jp -kiho.mie.jp -kisosaki.mie.jp -kiwa.mie.jp -komono.mie.jp -kumano.mie.jp -kuwana.mie.jp -matsusaka.mie.jp -meiwa.mie.jp -mihama.mie.jp -minamiise.mie.jp -misugi.mie.jp -miyama.mie.jp -nabari.mie.jp -shima.mie.jp -suzuka.mie.jp -tado.mie.jp -taiki.mie.jp -taki.mie.jp -tamaki.mie.jp -toba.mie.jp -tsu.mie.jp -udono.mie.jp -ureshino.mie.jp -watarai.mie.jp -yokkaichi.mie.jp -furukawa.miyagi.jp -higashimatsushima.miyagi.jp -ishinomaki.miyagi.jp -iwanuma.miyagi.jp -kakuda.miyagi.jp -kami.miyagi.jp -kawasaki.miyagi.jp -marumori.miyagi.jp -matsushima.miyagi.jp -minamisanriku.miyagi.jp -misato.miyagi.jp -murata.miyagi.jp -natori.miyagi.jp -ogawara.miyagi.jp -ohira.miyagi.jp -onagawa.miyagi.jp -osaki.miyagi.jp -rifu.miyagi.jp -semine.miyagi.jp -shibata.miyagi.jp -shichikashuku.miyagi.jp -shikama.miyagi.jp -shiogama.miyagi.jp -shiroishi.miyagi.jp -tagajo.miyagi.jp -taiwa.miyagi.jp -tome.miyagi.jp -tomiya.miyagi.jp -wakuya.miyagi.jp -watari.miyagi.jp -yamamoto.miyagi.jp -zao.miyagi.jp -aya.miyazaki.jp -ebino.miyazaki.jp -gokase.miyazaki.jp -hyuga.miyazaki.jp -kadogawa.miyazaki.jp -kawaminami.miyazaki.jp -kijo.miyazaki.jp -kitagawa.miyazaki.jp -kitakata.miyazaki.jp -kitaura.miyazaki.jp -kobayashi.miyazaki.jp -kunitomi.miyazaki.jp -kushima.miyazaki.jp -mimata.miyazaki.jp -miyakonojo.miyazaki.jp -miyazaki.miyazaki.jp -morotsuka.miyazaki.jp -nichinan.miyazaki.jp -nishimera.miyazaki.jp -nobeoka.miyazaki.jp -saito.miyazaki.jp -shiiba.miyazaki.jp -shintomi.miyazaki.jp -takaharu.miyazaki.jp -takanabe.miyazaki.jp -takazaki.miyazaki.jp -tsuno.miyazaki.jp -achi.nagano.jp -agematsu.nagano.jp -anan.nagano.jp -aoki.nagano.jp -asahi.nagano.jp -azumino.nagano.jp -chikuhoku.nagano.jp -chikuma.nagano.jp -chino.nagano.jp -fujimi.nagano.jp -hakuba.nagano.jp -hara.nagano.jp -hiraya.nagano.jp -iida.nagano.jp -iijima.nagano.jp -iiyama.nagano.jp -iizuna.nagano.jp -ikeda.nagano.jp -ikusaka.nagano.jp -ina.nagano.jp -karuizawa.nagano.jp -kawakami.nagano.jp -kiso.nagano.jp -kisofukushima.nagano.jp -kitaaiki.nagano.jp -komagane.nagano.jp -komoro.nagano.jp -matsukawa.nagano.jp -matsumoto.nagano.jp -miasa.nagano.jp -minamiaiki.nagano.jp -minamimaki.nagano.jp -minamiminowa.nagano.jp -minowa.nagano.jp -miyada.nagano.jp -miyota.nagano.jp -mochizuki.nagano.jp -nagano.nagano.jp -nagawa.nagano.jp -nagiso.nagano.jp -nakagawa.nagano.jp -nakano.nagano.jp -nozawaonsen.nagano.jp -obuse.nagano.jp -ogawa.nagano.jp -okaya.nagano.jp -omachi.nagano.jp -omi.nagano.jp -ookuwa.nagano.jp -ooshika.nagano.jp -otaki.nagano.jp -otari.nagano.jp -sakae.nagano.jp -sakaki.nagano.jp -saku.nagano.jp -sakuho.nagano.jp -shimosuwa.nagano.jp -shinanomachi.nagano.jp -shiojiri.nagano.jp -suwa.nagano.jp -suzaka.nagano.jp -takagi.nagano.jp -takamori.nagano.jp -takayama.nagano.jp -tateshina.nagano.jp -tatsuno.nagano.jp -togakushi.nagano.jp -togura.nagano.jp -tomi.nagano.jp -ueda.nagano.jp -wada.nagano.jp -yamagata.nagano.jp -yamanouchi.nagano.jp -yasaka.nagano.jp -yasuoka.nagano.jp -chijiwa.nagasaki.jp -futsu.nagasaki.jp -goto.nagasaki.jp -hasami.nagasaki.jp -hirado.nagasaki.jp -iki.nagasaki.jp -isahaya.nagasaki.jp -kawatana.nagasaki.jp -kuchinotsu.nagasaki.jp -matsuura.nagasaki.jp -nagasaki.nagasaki.jp -obama.nagasaki.jp -omura.nagasaki.jp -oseto.nagasaki.jp -saikai.nagasaki.jp -sasebo.nagasaki.jp -seihi.nagasaki.jp -shimabara.nagasaki.jp -shinkamigoto.nagasaki.jp -togitsu.nagasaki.jp -tsushima.nagasaki.jp -unzen.nagasaki.jp -ando.nara.jp -gose.nara.jp -heguri.nara.jp -higashiyoshino.nara.jp -ikaruga.nara.jp -ikoma.nara.jp -kamikitayama.nara.jp -kanmaki.nara.jp -kashiba.nara.jp -kashihara.nara.jp -katsuragi.nara.jp -kawai.nara.jp -kawakami.nara.jp -kawanishi.nara.jp -koryo.nara.jp -kurotaki.nara.jp -mitsue.nara.jp -miyake.nara.jp -nara.nara.jp -nosegawa.nara.jp -oji.nara.jp -ouda.nara.jp -oyodo.nara.jp -sakurai.nara.jp -sango.nara.jp -shimoichi.nara.jp -shimokitayama.nara.jp -shinjo.nara.jp -soni.nara.jp -takatori.nara.jp -tawaramoto.nara.jp -tenkawa.nara.jp -tenri.nara.jp -uda.nara.jp -yamatokoriyama.nara.jp -yamatotakada.nara.jp -yamazoe.nara.jp -yoshino.nara.jp -aga.niigata.jp -agano.niigata.jp -gosen.niigata.jp -itoigawa.niigata.jp -izumozaki.niigata.jp -joetsu.niigata.jp -kamo.niigata.jp -kariwa.niigata.jp -kashiwazaki.niigata.jp -minamiuonuma.niigata.jp -mitsuke.niigata.jp -muika.niigata.jp -murakami.niigata.jp -myoko.niigata.jp -nagaoka.niigata.jp -niigata.niigata.jp -ojiya.niigata.jp -omi.niigata.jp -sado.niigata.jp -sanjo.niigata.jp -seiro.niigata.jp -seirou.niigata.jp -sekikawa.niigata.jp -shibata.niigata.jp -tagami.niigata.jp -tainai.niigata.jp -tochio.niigata.jp -tokamachi.niigata.jp -tsubame.niigata.jp -tsunan.niigata.jp -uonuma.niigata.jp -yahiko.niigata.jp -yoita.niigata.jp -yuzawa.niigata.jp -beppu.oita.jp -bungoono.oita.jp -bungotakada.oita.jp -hasama.oita.jp -hiji.oita.jp -himeshima.oita.jp -hita.oita.jp -kamitsue.oita.jp -kokonoe.oita.jp -kuju.oita.jp -kunisaki.oita.jp -kusu.oita.jp -oita.oita.jp -saiki.oita.jp -taketa.oita.jp -tsukumi.oita.jp -usa.oita.jp -usuki.oita.jp -yufu.oita.jp -akaiwa.okayama.jp -asakuchi.okayama.jp -bizen.okayama.jp -hayashima.okayama.jp -ibara.okayama.jp -kagamino.okayama.jp -kasaoka.okayama.jp -kibichuo.okayama.jp -kumenan.okayama.jp -kurashiki.okayama.jp -maniwa.okayama.jp -misaki.okayama.jp -nagi.okayama.jp -niimi.okayama.jp -nishiawakura.okayama.jp -okayama.okayama.jp -satosho.okayama.jp -setouchi.okayama.jp -shinjo.okayama.jp -shoo.okayama.jp -soja.okayama.jp -takahashi.okayama.jp -tamano.okayama.jp -tsuyama.okayama.jp -wake.okayama.jp -yakage.okayama.jp -aguni.okinawa.jp -ginowan.okinawa.jp -ginoza.okinawa.jp -gushikami.okinawa.jp -haebaru.okinawa.jp -higashi.okinawa.jp -hirara.okinawa.jp -iheya.okinawa.jp -ishigaki.okinawa.jp -ishikawa.okinawa.jp -itoman.okinawa.jp -izena.okinawa.jp -kadena.okinawa.jp -kin.okinawa.jp -kitadaito.okinawa.jp -kitanakagusuku.okinawa.jp -kumejima.okinawa.jp -kunigami.okinawa.jp -minamidaito.okinawa.jp -motobu.okinawa.jp -nago.okinawa.jp -naha.okinawa.jp -nakagusuku.okinawa.jp -nakijin.okinawa.jp -nanjo.okinawa.jp -nishihara.okinawa.jp -ogimi.okinawa.jp -okinawa.okinawa.jp -onna.okinawa.jp -shimoji.okinawa.jp -taketomi.okinawa.jp -tarama.okinawa.jp -tokashiki.okinawa.jp -tomigusuku.okinawa.jp -tonaki.okinawa.jp -urasoe.okinawa.jp -uruma.okinawa.jp -yaese.okinawa.jp -yomitan.okinawa.jp -yonabaru.okinawa.jp -yonaguni.okinawa.jp -zamami.okinawa.jp -abeno.osaka.jp -chihayaakasaka.osaka.jp -chuo.osaka.jp -daito.osaka.jp -fujiidera.osaka.jp -habikino.osaka.jp -hannan.osaka.jp -higashiosaka.osaka.jp -higashisumiyoshi.osaka.jp -higashiyodogawa.osaka.jp -hirakata.osaka.jp -ibaraki.osaka.jp -ikeda.osaka.jp -izumi.osaka.jp -izumiotsu.osaka.jp -izumisano.osaka.jp -kadoma.osaka.jp -kaizuka.osaka.jp -kanan.osaka.jp -kashiwara.osaka.jp -katano.osaka.jp -kawachinagano.osaka.jp -kishiwada.osaka.jp -kita.osaka.jp -kumatori.osaka.jp -matsubara.osaka.jp -minato.osaka.jp -minoh.osaka.jp -misaki.osaka.jp -moriguchi.osaka.jp -neyagawa.osaka.jp -nishi.osaka.jp -nose.osaka.jp -osakasayama.osaka.jp -sakai.osaka.jp -sayama.osaka.jp -sennan.osaka.jp -settsu.osaka.jp -shijonawate.osaka.jp -shimamoto.osaka.jp -suita.osaka.jp -tadaoka.osaka.jp -taishi.osaka.jp -tajiri.osaka.jp -takaishi.osaka.jp -takatsuki.osaka.jp -tondabayashi.osaka.jp -toyonaka.osaka.jp -toyono.osaka.jp -yao.osaka.jp -ariake.saga.jp -arita.saga.jp -fukudomi.saga.jp -genkai.saga.jp -hamatama.saga.jp -hizen.saga.jp -imari.saga.jp -kamimine.saga.jp -kanzaki.saga.jp -karatsu.saga.jp -kashima.saga.jp -kitagata.saga.jp -kitahata.saga.jp -kiyama.saga.jp -kouhoku.saga.jp -kyuragi.saga.jp -nishiarita.saga.jp -ogi.saga.jp -omachi.saga.jp -ouchi.saga.jp -saga.saga.jp -shiroishi.saga.jp -taku.saga.jp -tara.saga.jp -tosu.saga.jp -yoshinogari.saga.jp -arakawa.saitama.jp -asaka.saitama.jp -chichibu.saitama.jp -fujimi.saitama.jp -fujimino.saitama.jp -fukaya.saitama.jp -hanno.saitama.jp -hanyu.saitama.jp -hasuda.saitama.jp -hatogaya.saitama.jp -hatoyama.saitama.jp -hidaka.saitama.jp -higashichichibu.saitama.jp -higashimatsuyama.saitama.jp -honjo.saitama.jp -ina.saitama.jp -iruma.saitama.jp -iwatsuki.saitama.jp -kamiizumi.saitama.jp -kamikawa.saitama.jp -kamisato.saitama.jp -kasukabe.saitama.jp -kawagoe.saitama.jp -kawaguchi.saitama.jp -kawajima.saitama.jp -kazo.saitama.jp -kitamoto.saitama.jp -koshigaya.saitama.jp -kounosu.saitama.jp -kuki.saitama.jp -kumagaya.saitama.jp -matsubushi.saitama.jp -minano.saitama.jp -misato.saitama.jp -miyashiro.saitama.jp -miyoshi.saitama.jp -moroyama.saitama.jp -nagatoro.saitama.jp -namegawa.saitama.jp -niiza.saitama.jp -ogano.saitama.jp -ogawa.saitama.jp -ogose.saitama.jp -okegawa.saitama.jp -omiya.saitama.jp -otaki.saitama.jp -ranzan.saitama.jp -ryokami.saitama.jp -saitama.saitama.jp -sakado.saitama.jp -satte.saitama.jp -sayama.saitama.jp -shiki.saitama.jp -shiraoka.saitama.jp -soka.saitama.jp -sugito.saitama.jp -toda.saitama.jp -tokigawa.saitama.jp -tokorozawa.saitama.jp -tsurugashima.saitama.jp -urawa.saitama.jp -warabi.saitama.jp -yashio.saitama.jp -yokoze.saitama.jp -yono.saitama.jp -yorii.saitama.jp -yoshida.saitama.jp -yoshikawa.saitama.jp -yoshimi.saitama.jp -aisho.shiga.jp -gamo.shiga.jp -higashiomi.shiga.jp -hikone.shiga.jp -koka.shiga.jp -konan.shiga.jp -kosei.shiga.jp -koto.shiga.jp -kusatsu.shiga.jp -maibara.shiga.jp -moriyama.shiga.jp -nagahama.shiga.jp -nishiazai.shiga.jp -notogawa.shiga.jp -omihachiman.shiga.jp -otsu.shiga.jp -ritto.shiga.jp -ryuoh.shiga.jp -takashima.shiga.jp -takatsuki.shiga.jp -torahime.shiga.jp -toyosato.shiga.jp -yasu.shiga.jp -akagi.shimane.jp -ama.shimane.jp -gotsu.shimane.jp -hamada.shimane.jp -higashiizumo.shimane.jp -hikawa.shimane.jp -hikimi.shimane.jp -izumo.shimane.jp -kakinoki.shimane.jp -masuda.shimane.jp -matsue.shimane.jp -misato.shimane.jp -nishinoshima.shimane.jp -ohda.shimane.jp -okinoshima.shimane.jp -okuizumo.shimane.jp -shimane.shimane.jp -tamayu.shimane.jp -tsuwano.shimane.jp -unnan.shimane.jp -yakumo.shimane.jp -yasugi.shimane.jp -yatsuka.shimane.jp -arai.shizuoka.jp -atami.shizuoka.jp -fuji.shizuoka.jp -fujieda.shizuoka.jp -fujikawa.shizuoka.jp -fujinomiya.shizuoka.jp -fukuroi.shizuoka.jp -gotemba.shizuoka.jp -haibara.shizuoka.jp -hamamatsu.shizuoka.jp -higashiizu.shizuoka.jp -ito.shizuoka.jp -iwata.shizuoka.jp -izu.shizuoka.jp -izunokuni.shizuoka.jp -kakegawa.shizuoka.jp -kannami.shizuoka.jp -kawanehon.shizuoka.jp -kawazu.shizuoka.jp -kikugawa.shizuoka.jp -kosai.shizuoka.jp -makinohara.shizuoka.jp -matsuzaki.shizuoka.jp -minamiizu.shizuoka.jp -mishima.shizuoka.jp -morimachi.shizuoka.jp -nishiizu.shizuoka.jp -numazu.shizuoka.jp -omaezaki.shizuoka.jp -shimada.shizuoka.jp -shimizu.shizuoka.jp -shimoda.shizuoka.jp -shizuoka.shizuoka.jp -susono.shizuoka.jp -yaizu.shizuoka.jp -yoshida.shizuoka.jp -ashikaga.tochigi.jp -bato.tochigi.jp -haga.tochigi.jp -ichikai.tochigi.jp -iwafune.tochigi.jp -kaminokawa.tochigi.jp -kanuma.tochigi.jp -karasuyama.tochigi.jp -kuroiso.tochigi.jp -mashiko.tochigi.jp -mibu.tochigi.jp -moka.tochigi.jp -motegi.tochigi.jp -nasu.tochigi.jp -nasushiobara.tochigi.jp -nikko.tochigi.jp -nishikata.tochigi.jp -nogi.tochigi.jp -ohira.tochigi.jp -ohtawara.tochigi.jp -oyama.tochigi.jp -sakura.tochigi.jp -sano.tochigi.jp -shimotsuke.tochigi.jp -shioya.tochigi.jp -takanezawa.tochigi.jp -tochigi.tochigi.jp -tsuga.tochigi.jp -ujiie.tochigi.jp -utsunomiya.tochigi.jp -yaita.tochigi.jp -aizumi.tokushima.jp -anan.tokushima.jp -ichiba.tokushima.jp -itano.tokushima.jp -kainan.tokushima.jp -komatsushima.tokushima.jp -matsushige.tokushima.jp -mima.tokushima.jp -minami.tokushima.jp -miyoshi.tokushima.jp -mugi.tokushima.jp -nakagawa.tokushima.jp -naruto.tokushima.jp -sanagochi.tokushima.jp -shishikui.tokushima.jp -tokushima.tokushima.jp -wajiki.tokushima.jp -adachi.tokyo.jp -akiruno.tokyo.jp -akishima.tokyo.jp -aogashima.tokyo.jp -arakawa.tokyo.jp -bunkyo.tokyo.jp -chiyoda.tokyo.jp -chofu.tokyo.jp -chuo.tokyo.jp -edogawa.tokyo.jp -fuchu.tokyo.jp -fussa.tokyo.jp -hachijo.tokyo.jp -hachioji.tokyo.jp -hamura.tokyo.jp -higashikurume.tokyo.jp -higashimurayama.tokyo.jp -higashiyamato.tokyo.jp -hino.tokyo.jp -hinode.tokyo.jp -hinohara.tokyo.jp -inagi.tokyo.jp -itabashi.tokyo.jp -katsushika.tokyo.jp -kita.tokyo.jp -kiyose.tokyo.jp -kodaira.tokyo.jp -koganei.tokyo.jp -kokubunji.tokyo.jp -komae.tokyo.jp -koto.tokyo.jp -kouzushima.tokyo.jp -kunitachi.tokyo.jp -machida.tokyo.jp -meguro.tokyo.jp -minato.tokyo.jp -mitaka.tokyo.jp -mizuho.tokyo.jp -musashimurayama.tokyo.jp -musashino.tokyo.jp -nakano.tokyo.jp -nerima.tokyo.jp -ogasawara.tokyo.jp -okutama.tokyo.jp -ome.tokyo.jp -oshima.tokyo.jp -ota.tokyo.jp -setagaya.tokyo.jp -shibuya.tokyo.jp -shinagawa.tokyo.jp -shinjuku.tokyo.jp -suginami.tokyo.jp -sumida.tokyo.jp -tachikawa.tokyo.jp -taito.tokyo.jp -tama.tokyo.jp -toshima.tokyo.jp -chizu.tottori.jp -hino.tottori.jp -kawahara.tottori.jp -koge.tottori.jp -kotoura.tottori.jp -misasa.tottori.jp -nanbu.tottori.jp -nichinan.tottori.jp -sakaiminato.tottori.jp -tottori.tottori.jp -wakasa.tottori.jp -yazu.tottori.jp -yonago.tottori.jp -asahi.toyama.jp -fuchu.toyama.jp -fukumitsu.toyama.jp -funahashi.toyama.jp -himi.toyama.jp -imizu.toyama.jp -inami.toyama.jp -johana.toyama.jp -kamiichi.toyama.jp -kurobe.toyama.jp -nakaniikawa.toyama.jp -namerikawa.toyama.jp -nanto.toyama.jp -nyuzen.toyama.jp -oyabe.toyama.jp -taira.toyama.jp -takaoka.toyama.jp -tateyama.toyama.jp -toga.toyama.jp -tonami.toyama.jp -toyama.toyama.jp -unazuki.toyama.jp -uozu.toyama.jp -yamada.toyama.jp -arida.wakayama.jp -aridagawa.wakayama.jp -gobo.wakayama.jp -hashimoto.wakayama.jp -hidaka.wakayama.jp -hirogawa.wakayama.jp -inami.wakayama.jp -iwade.wakayama.jp -kainan.wakayama.jp -kamitonda.wakayama.jp -katsuragi.wakayama.jp -kimino.wakayama.jp -kinokawa.wakayama.jp -kitayama.wakayama.jp -koya.wakayama.jp -koza.wakayama.jp -kozagawa.wakayama.jp -kudoyama.wakayama.jp -kushimoto.wakayama.jp -mihama.wakayama.jp -misato.wakayama.jp -nachikatsuura.wakayama.jp -shingu.wakayama.jp -shirahama.wakayama.jp -taiji.wakayama.jp -tanabe.wakayama.jp -wakayama.wakayama.jp -yuasa.wakayama.jp -yura.wakayama.jp -asahi.yamagata.jp -funagata.yamagata.jp -higashine.yamagata.jp -iide.yamagata.jp -kahoku.yamagata.jp -kaminoyama.yamagata.jp -kaneyama.yamagata.jp -kawanishi.yamagata.jp -mamurogawa.yamagata.jp -mikawa.yamagata.jp -murayama.yamagata.jp -nagai.yamagata.jp -nakayama.yamagata.jp -nanyo.yamagata.jp -nishikawa.yamagata.jp -obanazawa.yamagata.jp -oe.yamagata.jp -oguni.yamagata.jp -ohkura.yamagata.jp -oishida.yamagata.jp -sagae.yamagata.jp -sakata.yamagata.jp -sakegawa.yamagata.jp -shinjo.yamagata.jp -shirataka.yamagata.jp -shonai.yamagata.jp -takahata.yamagata.jp -tendo.yamagata.jp -tozawa.yamagata.jp -tsuruoka.yamagata.jp -yamagata.yamagata.jp -yamanobe.yamagata.jp -yonezawa.yamagata.jp -yuza.yamagata.jp -abu.yamaguchi.jp -hagi.yamaguchi.jp -hikari.yamaguchi.jp -hofu.yamaguchi.jp -iwakuni.yamaguchi.jp -kudamatsu.yamaguchi.jp -mitou.yamaguchi.jp -nagato.yamaguchi.jp -oshima.yamaguchi.jp -shimonoseki.yamaguchi.jp -shunan.yamaguchi.jp -tabuse.yamaguchi.jp -tokuyama.yamaguchi.jp -toyota.yamaguchi.jp -ube.yamaguchi.jp -yuu.yamaguchi.jp -chuo.yamanashi.jp -doshi.yamanashi.jp -fuefuki.yamanashi.jp -fujikawa.yamanashi.jp -fujikawaguchiko.yamanashi.jp -fujiyoshida.yamanashi.jp -hayakawa.yamanashi.jp -hokuto.yamanashi.jp -ichikawamisato.yamanashi.jp -kai.yamanashi.jp -kofu.yamanashi.jp -koshu.yamanashi.jp -kosuge.yamanashi.jp -minami-alps.yamanashi.jp -minobu.yamanashi.jp -nakamichi.yamanashi.jp -nanbu.yamanashi.jp -narusawa.yamanashi.jp -nirasaki.yamanashi.jp -nishikatsura.yamanashi.jp -oshino.yamanashi.jp -otsuki.yamanashi.jp -showa.yamanashi.jp -tabayama.yamanashi.jp -tsuru.yamanashi.jp -uenohara.yamanashi.jp -yamanakako.yamanashi.jp -yamanashi.yamanashi.jp - -// ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains -ke -ac.ke -co.ke -go.ke -info.ke -me.ke -mobi.ke -ne.ke -or.ke -sc.ke - -// kg : http://www.domain.kg/dmn_n.html -kg -org.kg -net.kg -com.kg -edu.kg -gov.kg -mil.kg - -// kh : http://www.mptc.gov.kh/dns_registration.htm -*.kh - -// ki : http://www.ki/dns/index.html -ki -edu.ki -biz.ki -net.ki -org.ki -gov.ki -info.ki -com.ki - -// km : https://en.wikipedia.org/wiki/.km -// http://www.domaine.km/documents/charte.doc -km -org.km -nom.km -gov.km -prd.km -tm.km -edu.km -mil.km -ass.km -com.km -// These are only mentioned as proposed suggestions at domaine.km, but -// https://en.wikipedia.org/wiki/.km says they're available for registration: -coop.km -asso.km -presse.km -medecin.km -notaires.km -pharmaciens.km -veterinaire.km -gouv.km - -// kn : https://en.wikipedia.org/wiki/.kn -// http://www.dot.kn/domainRules.html -kn -net.kn -org.kn -edu.kn -gov.kn - -// kp : http://www.kcce.kp/en_index.php -kp -com.kp -edu.kp -gov.kp -org.kp -rep.kp -tra.kp - -// kr : https://en.wikipedia.org/wiki/.kr -// see also: http://domain.nida.or.kr/eng/registration.jsp -kr -ac.kr -co.kr -es.kr -go.kr -hs.kr -kg.kr -mil.kr -ms.kr -ne.kr -or.kr -pe.kr -re.kr -sc.kr -// kr geographical names -busan.kr -chungbuk.kr -chungnam.kr -daegu.kr -daejeon.kr -gangwon.kr -gwangju.kr -gyeongbuk.kr -gyeonggi.kr -gyeongnam.kr -incheon.kr -jeju.kr -jeonbuk.kr -jeonnam.kr -seoul.kr -ulsan.kr - -// kw : https://www.nic.kw/policies/ -// Confirmed by registry -kw -com.kw -edu.kw -emb.kw -gov.kw -ind.kw -net.kw -org.kw - -// ky : http://www.icta.ky/da_ky_reg_dom.php -// Confirmed by registry 2008-06-17 -ky -com.ky -edu.ky -net.ky -org.ky - -// kz : https://en.wikipedia.org/wiki/.kz -// see also: http://www.nic.kz/rules/index.jsp -kz -org.kz -edu.kz -net.kz -gov.kz -mil.kz -com.kz - -// la : https://en.wikipedia.org/wiki/.la -// Submitted by registry -la -int.la -net.la -info.la -edu.la -gov.la -per.la -com.la -org.la - -// lb : https://en.wikipedia.org/wiki/.lb -// Submitted by registry -lb -com.lb -edu.lb -gov.lb -net.lb -org.lb - -// lc : https://en.wikipedia.org/wiki/.lc -// see also: http://www.nic.lc/rules.htm -lc -com.lc -net.lc -co.lc -org.lc -edu.lc -gov.lc - -// li : https://en.wikipedia.org/wiki/.li -li - -// lk : https://www.nic.lk/index.php/domain-registration/lk-domain-naming-structure -lk -gov.lk -sch.lk -net.lk -int.lk -com.lk -org.lk -edu.lk -ngo.lk -soc.lk -web.lk -ltd.lk -assn.lk -grp.lk -hotel.lk -ac.lk - -// lr : http://psg.com/dns/lr/lr.txt -// Submitted by registry -lr -com.lr -edu.lr -gov.lr -org.lr -net.lr - -// ls : http://www.nic.ls/ -// Confirmed by registry -ls -ac.ls -biz.ls -co.ls -edu.ls -gov.ls -info.ls -net.ls -org.ls -sc.ls - -// lt : https://en.wikipedia.org/wiki/.lt -lt -// gov.lt : http://www.gov.lt/index_en.php -gov.lt - -// lu : http://www.dns.lu/en/ -lu - -// lv : http://www.nic.lv/DNS/En/generic.php -lv -com.lv -edu.lv -gov.lv -org.lv -mil.lv -id.lv -net.lv -asn.lv -conf.lv - -// ly : http://www.nic.ly/regulations.php -ly -com.ly -net.ly -gov.ly -plc.ly -edu.ly -sch.ly -med.ly -org.ly -id.ly - -// ma : https://en.wikipedia.org/wiki/.ma -// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf -ma -co.ma -net.ma -gov.ma -org.ma -ac.ma -press.ma - -// mc : http://www.nic.mc/ -mc -tm.mc -asso.mc - -// md : https://en.wikipedia.org/wiki/.md -md - -// me : https://en.wikipedia.org/wiki/.me -me -co.me -net.me -org.me -edu.me -ac.me -gov.me -its.me -priv.me - -// mg : http://nic.mg/nicmg/?page_id=39 -mg -org.mg -nom.mg -gov.mg -prd.mg -tm.mg -edu.mg -mil.mg -com.mg -co.mg - -// mh : https://en.wikipedia.org/wiki/.mh -mh - -// mil : https://en.wikipedia.org/wiki/.mil -mil - -// mk : https://en.wikipedia.org/wiki/.mk -// see also: http://dns.marnet.net.mk/postapka.php -mk -com.mk -org.mk -net.mk -edu.mk -gov.mk -inf.mk -name.mk - -// ml : http://www.gobin.info/domainname/ml-template.doc -// see also: https://en.wikipedia.org/wiki/.ml -ml -com.ml -edu.ml -gouv.ml -gov.ml -net.ml -org.ml -presse.ml - -// mm : https://en.wikipedia.org/wiki/.mm -*.mm - -// mn : https://en.wikipedia.org/wiki/.mn -mn -gov.mn -edu.mn -org.mn - -// mo : http://www.monic.net.mo/ -mo -com.mo -net.mo -org.mo -edu.mo -gov.mo - -// mobi : https://en.wikipedia.org/wiki/.mobi -mobi - -// mp : http://www.dot.mp/ -// Confirmed by registry 2008-06-17 -mp - -// mq : https://en.wikipedia.org/wiki/.mq -mq - -// mr : https://en.wikipedia.org/wiki/.mr -mr -gov.mr - -// ms : http://www.nic.ms/pdf/MS_Domain_Name_Rules.pdf -ms -com.ms -edu.ms -gov.ms -net.ms -org.ms - -// mt : https://www.nic.org.mt/go/policy -// Submitted by registry -mt -com.mt -edu.mt -net.mt -org.mt - -// mu : https://en.wikipedia.org/wiki/.mu -mu -com.mu -net.mu -org.mu -gov.mu -ac.mu -co.mu -or.mu - -// museum : https://welcome.museum/wp-content/uploads/2018/05/20180525-Registration-Policy-MUSEUM-EN_VF-2.pdf https://welcome.museum/buy-your-dot-museum-2/ -museum - -// mv : https://en.wikipedia.org/wiki/.mv -// "mv" included because, contra Wikipedia, google.mv exists. -mv -aero.mv -biz.mv -com.mv -coop.mv -edu.mv -gov.mv -info.mv -int.mv -mil.mv -museum.mv -name.mv -net.mv -org.mv -pro.mv - -// mw : http://www.registrar.mw/ -mw -ac.mw -biz.mw -co.mw -com.mw -coop.mw -edu.mw -gov.mw -int.mw -museum.mw -net.mw -org.mw - -// mx : http://www.nic.mx/ -// Submitted by registry -mx -com.mx -org.mx -gob.mx -edu.mx -net.mx - -// my : http://www.mynic.my/ -// Available strings: https://mynic.my/resources/domains/buying-a-domain/ -my -biz.my -com.my -edu.my -gov.my -mil.my -name.my -net.my -org.my - -// mz : http://www.uem.mz/ -// Submitted by registry -mz -ac.mz -adv.mz -co.mz -edu.mz -gov.mz -mil.mz -net.mz -org.mz - -// na : http://www.na-nic.com.na/ -// http://www.info.na/domain/ -na -info.na -pro.na -name.na -school.na -or.na -dr.na -us.na -mx.na -ca.na -in.na -cc.na -tv.na -ws.na -mobi.na -co.na -com.na -org.na - -// name : has 2nd-level tlds, but there's no list of them -name - -// nc : http://www.cctld.nc/ -nc -asso.nc -nom.nc - -// ne : https://en.wikipedia.org/wiki/.ne -ne - -// net : https://en.wikipedia.org/wiki/.net -net - -// nf : https://en.wikipedia.org/wiki/.nf -nf -com.nf -net.nf -per.nf -rec.nf -web.nf -arts.nf -firm.nf -info.nf -other.nf -store.nf - -// ng : http://www.nira.org.ng/index.php/join-us/register-ng-domain/189-nira-slds -ng -com.ng -edu.ng -gov.ng -i.ng -mil.ng -mobi.ng -name.ng -net.ng -org.ng -sch.ng - -// ni : http://www.nic.ni/ -ni -ac.ni -biz.ni -co.ni -com.ni -edu.ni -gob.ni -in.ni -info.ni -int.ni -mil.ni -net.ni -nom.ni -org.ni -web.ni - -// nl : https://en.wikipedia.org/wiki/.nl -// https://www.sidn.nl/ -// ccTLD for the Netherlands -nl - -// no : https://www.norid.no/en/om-domenenavn/regelverk-for-no/ -// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ -// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ -// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ -// RSS feed: https://teknisk.norid.no/en/feed/ -no -// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ -fhs.no -vgs.no -fylkesbibl.no -folkebibl.no -museum.no -idrett.no -priv.no -// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ -mil.no -stat.no -dep.no -kommune.no -herad.no -// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ -// counties -aa.no -ah.no -bu.no -fm.no -hl.no -hm.no -jan-mayen.no -mr.no -nl.no -nt.no -of.no -ol.no -oslo.no -rl.no -sf.no -st.no -svalbard.no -tm.no -tr.no -va.no -vf.no -// primary and lower secondary schools per county -gs.aa.no -gs.ah.no -gs.bu.no -gs.fm.no -gs.hl.no -gs.hm.no -gs.jan-mayen.no -gs.mr.no -gs.nl.no -gs.nt.no -gs.of.no -gs.ol.no -gs.oslo.no -gs.rl.no -gs.sf.no -gs.st.no -gs.svalbard.no -gs.tm.no -gs.tr.no -gs.va.no -gs.vf.no -// cities -akrehamn.no -åkrehamn.no -algard.no -ålgård.no -arna.no -brumunddal.no -bryne.no -bronnoysund.no -brønnøysund.no -drobak.no -drøbak.no -egersund.no -fetsund.no -floro.no -florø.no -fredrikstad.no -hokksund.no -honefoss.no -hønefoss.no -jessheim.no -jorpeland.no -jørpeland.no -kirkenes.no -kopervik.no -krokstadelva.no -langevag.no -langevåg.no -leirvik.no -mjondalen.no -mjøndalen.no -mo-i-rana.no -mosjoen.no -mosjøen.no -nesoddtangen.no -orkanger.no -osoyro.no -osøyro.no -raholt.no -råholt.no -sandnessjoen.no -sandnessjøen.no -skedsmokorset.no -slattum.no -spjelkavik.no -stathelle.no -stavern.no -stjordalshalsen.no -stjørdalshalsen.no -tananger.no -tranby.no -vossevangen.no -// communities -afjord.no -åfjord.no -agdenes.no -al.no -ål.no -alesund.no -ålesund.no -alstahaug.no -alta.no -áltá.no -alaheadju.no -álaheadju.no -alvdal.no -amli.no -åmli.no -amot.no -åmot.no -andebu.no -andoy.no -andøy.no -andasuolo.no -ardal.no -årdal.no -aremark.no -arendal.no -ås.no -aseral.no -åseral.no -asker.no -askim.no -askvoll.no -askoy.no -askøy.no -asnes.no -åsnes.no -audnedaln.no -aukra.no -aure.no -aurland.no -aurskog-holand.no -aurskog-høland.no -austevoll.no -austrheim.no -averoy.no -averøy.no -balestrand.no -ballangen.no -balat.no -bálát.no -balsfjord.no -bahccavuotna.no -báhccavuotna.no -bamble.no -bardu.no -beardu.no -beiarn.no -bajddar.no -bájddar.no -baidar.no -báidár.no -berg.no -bergen.no -berlevag.no -berlevåg.no -bearalvahki.no -bearalváhki.no -bindal.no -birkenes.no -bjarkoy.no -bjarkøy.no -bjerkreim.no -bjugn.no -bodo.no -bodø.no -badaddja.no -bådåddjå.no -budejju.no -bokn.no -bremanger.no -bronnoy.no -brønnøy.no -bygland.no -bykle.no -barum.no -bærum.no -bo.telemark.no -bø.telemark.no -bo.nordland.no -bø.nordland.no -bievat.no -bievát.no -bomlo.no -bømlo.no -batsfjord.no -båtsfjord.no -bahcavuotna.no -báhcavuotna.no -dovre.no -drammen.no -drangedal.no -dyroy.no -dyrøy.no -donna.no -dønna.no -eid.no -eidfjord.no -eidsberg.no -eidskog.no -eidsvoll.no -eigersund.no -elverum.no -enebakk.no -engerdal.no -etne.no -etnedal.no -evenes.no -evenassi.no -evenášši.no -evje-og-hornnes.no -farsund.no -fauske.no -fuossko.no -fuoisku.no -fedje.no -fet.no -finnoy.no -finnøy.no -fitjar.no -fjaler.no -fjell.no -flakstad.no -flatanger.no -flekkefjord.no -flesberg.no -flora.no -fla.no -flå.no -folldal.no -forsand.no -fosnes.no -frei.no -frogn.no -froland.no -frosta.no -frana.no -fræna.no -froya.no -frøya.no -fusa.no -fyresdal.no -forde.no -førde.no -gamvik.no -gangaviika.no -gáŋgaviika.no -gaular.no -gausdal.no -gildeskal.no -gildeskål.no -giske.no -gjemnes.no -gjerdrum.no -gjerstad.no -gjesdal.no -gjovik.no -gjøvik.no -gloppen.no -gol.no -gran.no -grane.no -granvin.no -gratangen.no -grimstad.no -grong.no -kraanghke.no -kråanghke.no -grue.no -gulen.no -hadsel.no -halden.no -halsa.no -hamar.no -hamaroy.no -habmer.no -hábmer.no -hapmir.no -hápmir.no -hammerfest.no -hammarfeasta.no -hámmárfeasta.no -haram.no -hareid.no -harstad.no -hasvik.no -aknoluokta.no -ákŋoluokta.no -hattfjelldal.no -aarborte.no -haugesund.no -hemne.no -hemnes.no -hemsedal.no -heroy.more-og-romsdal.no -herøy.møre-og-romsdal.no -heroy.nordland.no -herøy.nordland.no -hitra.no -hjartdal.no -hjelmeland.no -hobol.no -hobøl.no -hof.no -hol.no -hole.no -holmestrand.no -holtalen.no -holtålen.no -hornindal.no -horten.no -hurdal.no -hurum.no -hvaler.no -hyllestad.no -hagebostad.no -hægebostad.no -hoyanger.no -høyanger.no -hoylandet.no -høylandet.no -ha.no -hå.no -ibestad.no -inderoy.no -inderøy.no -iveland.no -jevnaker.no -jondal.no -jolster.no -jølster.no -karasjok.no -karasjohka.no -kárášjohka.no -karlsoy.no -galsa.no -gálsá.no -karmoy.no -karmøy.no -kautokeino.no -guovdageaidnu.no -klepp.no -klabu.no -klæbu.no -kongsberg.no -kongsvinger.no -kragero.no -kragerø.no -kristiansand.no -kristiansund.no -krodsherad.no -krødsherad.no -kvalsund.no -rahkkeravju.no -ráhkkerávju.no -kvam.no -kvinesdal.no -kvinnherad.no -kviteseid.no -kvitsoy.no -kvitsøy.no -kvafjord.no -kvæfjord.no -giehtavuoatna.no -kvanangen.no -kvænangen.no -navuotna.no -návuotna.no -kafjord.no -kåfjord.no -gaivuotna.no -gáivuotna.no -larvik.no -lavangen.no -lavagis.no -loabat.no -loabát.no -lebesby.no -davvesiida.no -leikanger.no -leirfjord.no -leka.no -leksvik.no -lenvik.no -leangaviika.no -leaŋgaviika.no -lesja.no -levanger.no -lier.no -lierne.no -lillehammer.no -lillesand.no -lindesnes.no -lindas.no -lindås.no -lom.no -loppa.no -lahppi.no -láhppi.no -lund.no -lunner.no -luroy.no -lurøy.no -luster.no -lyngdal.no -lyngen.no -ivgu.no -lardal.no -lerdal.no -lærdal.no -lodingen.no -lødingen.no -lorenskog.no -lørenskog.no -loten.no -løten.no -malvik.no -masoy.no -måsøy.no -muosat.no -muosát.no -mandal.no -marker.no -marnardal.no -masfjorden.no -meland.no -meldal.no -melhus.no -meloy.no -meløy.no -meraker.no -meråker.no -moareke.no -moåreke.no -midsund.no -midtre-gauldal.no -modalen.no -modum.no -molde.no -moskenes.no -moss.no -mosvik.no -malselv.no -målselv.no -malatvuopmi.no -málatvuopmi.no -namdalseid.no -aejrie.no -namsos.no -namsskogan.no -naamesjevuemie.no -nååmesjevuemie.no -laakesvuemie.no -nannestad.no -narvik.no -narviika.no -naustdal.no -nedre-eiker.no -nes.akershus.no -nes.buskerud.no -nesna.no -nesodden.no -nesseby.no -unjarga.no -unjárga.no -nesset.no -nissedal.no -nittedal.no -nord-aurdal.no -nord-fron.no -nord-odal.no -norddal.no -nordkapp.no -davvenjarga.no -davvenjárga.no -nordre-land.no -nordreisa.no -raisa.no -ráisa.no -nore-og-uvdal.no -notodden.no -naroy.no -nærøy.no -notteroy.no -nøtterøy.no -odda.no -oksnes.no -øksnes.no -oppdal.no -oppegard.no -oppegård.no -orkdal.no -orland.no -ørland.no -orskog.no -ørskog.no -orsta.no -ørsta.no -os.hedmark.no -os.hordaland.no -osen.no -osteroy.no -osterøy.no -ostre-toten.no -østre-toten.no -overhalla.no -ovre-eiker.no -øvre-eiker.no -oyer.no -øyer.no -oygarden.no -øygarden.no -oystre-slidre.no -øystre-slidre.no -porsanger.no -porsangu.no -porsáŋgu.no -porsgrunn.no -radoy.no -radøy.no -rakkestad.no -rana.no -ruovat.no -randaberg.no -rauma.no -rendalen.no -rennebu.no -rennesoy.no -rennesøy.no -rindal.no -ringebu.no -ringerike.no -ringsaker.no -rissa.no -risor.no -risør.no -roan.no -rollag.no -rygge.no -ralingen.no -rælingen.no -rodoy.no -rødøy.no -romskog.no -rømskog.no -roros.no -røros.no -rost.no -røst.no -royken.no -røyken.no -royrvik.no -røyrvik.no -rade.no -råde.no -salangen.no -siellak.no -saltdal.no -salat.no -sálát.no -sálat.no -samnanger.no -sande.more-og-romsdal.no -sande.møre-og-romsdal.no -sande.vestfold.no -sandefjord.no -sandnes.no -sandoy.no -sandøy.no -sarpsborg.no -sauda.no -sauherad.no -sel.no -selbu.no -selje.no -seljord.no -sigdal.no -siljan.no -sirdal.no -skaun.no -skedsmo.no -ski.no -skien.no -skiptvet.no -skjervoy.no -skjervøy.no -skierva.no -skiervá.no -skjak.no -skjåk.no -skodje.no -skanland.no -skånland.no -skanit.no -skánit.no -smola.no -smøla.no -snillfjord.no -snasa.no -snåsa.no -snoasa.no -snaase.no -snåase.no -sogndal.no -sokndal.no -sola.no -solund.no -songdalen.no -sortland.no -spydeberg.no -stange.no -stavanger.no -steigen.no -steinkjer.no -stjordal.no -stjørdal.no -stokke.no -stor-elvdal.no -stord.no -stordal.no -storfjord.no -omasvuotna.no -strand.no -stranda.no -stryn.no -sula.no -suldal.no -sund.no -sunndal.no -surnadal.no -sveio.no -svelvik.no -sykkylven.no -sogne.no -søgne.no -somna.no -sømna.no -sondre-land.no -søndre-land.no -sor-aurdal.no -sør-aurdal.no -sor-fron.no -sør-fron.no -sor-odal.no -sør-odal.no -sor-varanger.no -sør-varanger.no -matta-varjjat.no -mátta-várjjat.no -sorfold.no -sørfold.no -sorreisa.no -sørreisa.no -sorum.no -sørum.no -tana.no -deatnu.no -time.no -tingvoll.no -tinn.no -tjeldsund.no -dielddanuorri.no -tjome.no -tjøme.no -tokke.no -tolga.no -torsken.no -tranoy.no -tranøy.no -tromso.no -tromsø.no -tromsa.no -romsa.no -trondheim.no -troandin.no -trysil.no -trana.no -træna.no -trogstad.no -trøgstad.no -tvedestrand.no -tydal.no -tynset.no -tysfjord.no -divtasvuodna.no -divttasvuotna.no -tysnes.no -tysvar.no -tysvær.no -tonsberg.no -tønsberg.no -ullensaker.no -ullensvang.no -ulvik.no -utsira.no -vadso.no -vadsø.no -cahcesuolo.no -čáhcesuolo.no -vaksdal.no -valle.no -vang.no -vanylven.no -vardo.no -vardø.no -varggat.no -várggát.no -vefsn.no -vaapste.no -vega.no -vegarshei.no -vegårshei.no -vennesla.no -verdal.no -verran.no -vestby.no -vestnes.no -vestre-slidre.no -vestre-toten.no -vestvagoy.no -vestvågøy.no -vevelstad.no -vik.no -vikna.no -vindafjord.no -volda.no -voss.no -varoy.no -værøy.no -vagan.no -vågan.no -voagat.no -vagsoy.no -vågsøy.no -vaga.no -vågå.no -valer.ostfold.no -våler.østfold.no -valer.hedmark.no -våler.hedmark.no - -// np : http://www.mos.com.np/register.html -*.np - -// nr : http://cenpac.net.nr/dns/index.html -// Submitted by registry -nr -biz.nr -info.nr -gov.nr -edu.nr -org.nr -net.nr -com.nr - -// nu : https://en.wikipedia.org/wiki/.nu -nu - -// nz : https://en.wikipedia.org/wiki/.nz -// Submitted by registry -nz -ac.nz -co.nz -cri.nz -geek.nz -gen.nz -govt.nz -health.nz -iwi.nz -kiwi.nz -maori.nz -mil.nz -māori.nz -net.nz -org.nz -parliament.nz -school.nz - -// om : https://en.wikipedia.org/wiki/.om -om -co.om -com.om -edu.om -gov.om -med.om -museum.om -net.om -org.om -pro.om - -// onion : https://tools.ietf.org/html/rfc7686 -onion - -// org : https://en.wikipedia.org/wiki/.org -org - -// pa : http://www.nic.pa/ -// Some additional second level "domains" resolve directly as hostnames, such as -// pannet.pa, so we add a rule for "pa". -pa -ac.pa -gob.pa -com.pa -org.pa -sld.pa -edu.pa -net.pa -ing.pa -abo.pa -med.pa -nom.pa - -// pe : https://www.nic.pe/InformeFinalComision.pdf -pe -edu.pe -gob.pe -nom.pe -mil.pe -org.pe -com.pe -net.pe - -// pf : http://www.gobin.info/domainname/formulaire-pf.pdf -pf -com.pf -org.pf -edu.pf - -// pg : https://en.wikipedia.org/wiki/.pg -*.pg - -// ph : http://www.domains.ph/FAQ2.asp -// Submitted by registry -ph -com.ph -net.ph -org.ph -gov.ph -edu.ph -ngo.ph -mil.ph -i.ph - -// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK -pk -com.pk -net.pk -edu.pk -org.pk -fam.pk -biz.pk -web.pk -gov.pk -gob.pk -gok.pk -gon.pk -gop.pk -gos.pk -info.pk - -// pl http://www.dns.pl/english/index.html -// Submitted by registry -pl -com.pl -net.pl -org.pl -// pl functional domains (http://www.dns.pl/english/index.html) -aid.pl -agro.pl -atm.pl -auto.pl -biz.pl -edu.pl -gmina.pl -gsm.pl -info.pl -mail.pl -miasta.pl -media.pl -mil.pl -nieruchomosci.pl -nom.pl -pc.pl -powiat.pl -priv.pl -realestate.pl -rel.pl -sex.pl -shop.pl -sklep.pl -sos.pl -szkola.pl -targi.pl -tm.pl -tourism.pl -travel.pl -turystyka.pl -// Government domains -gov.pl -ap.gov.pl -ic.gov.pl -is.gov.pl -us.gov.pl -kmpsp.gov.pl -kppsp.gov.pl -kwpsp.gov.pl -psp.gov.pl -wskr.gov.pl -kwp.gov.pl -mw.gov.pl -ug.gov.pl -um.gov.pl -umig.gov.pl -ugim.gov.pl -upow.gov.pl -uw.gov.pl -starostwo.gov.pl -pa.gov.pl -po.gov.pl -psse.gov.pl -pup.gov.pl -rzgw.gov.pl -sa.gov.pl -so.gov.pl -sr.gov.pl -wsa.gov.pl -sko.gov.pl -uzs.gov.pl -wiih.gov.pl -winb.gov.pl -pinb.gov.pl -wios.gov.pl -witd.gov.pl -wzmiuw.gov.pl -piw.gov.pl -wiw.gov.pl -griw.gov.pl -wif.gov.pl -oum.gov.pl -sdn.gov.pl -zp.gov.pl -uppo.gov.pl -mup.gov.pl -wuoz.gov.pl -konsulat.gov.pl -oirm.gov.pl -// pl regional domains (http://www.dns.pl/english/index.html) -augustow.pl -babia-gora.pl -bedzin.pl -beskidy.pl -bialowieza.pl -bialystok.pl -bielawa.pl -bieszczady.pl -boleslawiec.pl -bydgoszcz.pl -bytom.pl -cieszyn.pl -czeladz.pl -czest.pl -dlugoleka.pl -elblag.pl -elk.pl -glogow.pl -gniezno.pl -gorlice.pl -grajewo.pl -ilawa.pl -jaworzno.pl -jelenia-gora.pl -jgora.pl -kalisz.pl -kazimierz-dolny.pl -karpacz.pl -kartuzy.pl -kaszuby.pl -katowice.pl -kepno.pl -ketrzyn.pl -klodzko.pl -kobierzyce.pl -kolobrzeg.pl -konin.pl -konskowola.pl -kutno.pl -lapy.pl -lebork.pl -legnica.pl -lezajsk.pl -limanowa.pl -lomza.pl -lowicz.pl -lubin.pl -lukow.pl -malbork.pl -malopolska.pl -mazowsze.pl -mazury.pl -mielec.pl -mielno.pl -mragowo.pl -naklo.pl -nowaruda.pl -nysa.pl -olawa.pl -olecko.pl -olkusz.pl -olsztyn.pl -opoczno.pl -opole.pl -ostroda.pl -ostroleka.pl -ostrowiec.pl -ostrowwlkp.pl -pila.pl -pisz.pl -podhale.pl -podlasie.pl -polkowice.pl -pomorze.pl -pomorskie.pl -prochowice.pl -pruszkow.pl -przeworsk.pl -pulawy.pl -radom.pl -rawa-maz.pl -rybnik.pl -rzeszow.pl -sanok.pl -sejny.pl -slask.pl -slupsk.pl -sosnowiec.pl -stalowa-wola.pl -skoczow.pl -starachowice.pl -stargard.pl -suwalki.pl -swidnica.pl -swiebodzin.pl -swinoujscie.pl -szczecin.pl -szczytno.pl -tarnobrzeg.pl -tgory.pl -turek.pl -tychy.pl -ustka.pl -walbrzych.pl -warmia.pl -warszawa.pl -waw.pl -wegrow.pl -wielun.pl -wlocl.pl -wloclawek.pl -wodzislaw.pl -wolomin.pl -wroclaw.pl -zachpomor.pl -zagan.pl -zarow.pl -zgora.pl -zgorzelec.pl - -// pm : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf -pm - -// pn : http://www.government.pn/PnRegistry/policies.htm -pn -gov.pn -co.pn -org.pn -edu.pn -net.pn - -// post : https://en.wikipedia.org/wiki/.post -post - -// pr : http://www.nic.pr/index.asp?f=1 -pr -com.pr -net.pr -org.pr -gov.pr -edu.pr -isla.pr -pro.pr -biz.pr -info.pr -name.pr -// these aren't mentioned on nic.pr, but on https://en.wikipedia.org/wiki/.pr -est.pr -prof.pr -ac.pr - -// pro : http://registry.pro/get-pro -pro -aaa.pro -aca.pro -acct.pro -avocat.pro -bar.pro -cpa.pro -eng.pro -jur.pro -law.pro -med.pro -recht.pro - -// ps : https://en.wikipedia.org/wiki/.ps -// http://www.nic.ps/registration/policy.html#reg -ps -edu.ps -gov.ps -sec.ps -plo.ps -com.ps -org.ps -net.ps - -// pt : https://www.dns.pt/en/domain/pt-terms-and-conditions-registration-rules/ -pt -net.pt -gov.pt -org.pt -edu.pt -int.pt -publ.pt -com.pt -nome.pt - -// pw : https://en.wikipedia.org/wiki/.pw -pw -co.pw -ne.pw -or.pw -ed.pw -go.pw -belau.pw - -// py : http://www.nic.py/pautas.html#seccion_9 -// Submitted by registry -py -com.py -coop.py -edu.py -gov.py -mil.py -net.py -org.py - -// qa : http://domains.qa/en/ -qa -com.qa -edu.qa -gov.qa -mil.qa -name.qa -net.qa -org.qa -sch.qa - -// re : http://www.afnic.re/obtenir/chartes/nommage-re/annexe-descriptifs -re -asso.re -com.re -nom.re - -// ro : http://www.rotld.ro/ -ro -arts.ro -com.ro -firm.ro -info.ro -nom.ro -nt.ro -org.ro -rec.ro -store.ro -tm.ro -www.ro - -// rs : https://www.rnids.rs/en/domains/national-domains -rs -ac.rs -co.rs -edu.rs -gov.rs -in.rs -org.rs - -// ru : https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf -// Submitted by George Georgievsky -ru - -// rw : https://www.ricta.org.rw/sites/default/files/resources/registry_registrar_contract_0.pdf -rw -ac.rw -co.rw -coop.rw -gov.rw -mil.rw -net.rw -org.rw - -// sa : http://www.nic.net.sa/ -sa -com.sa -net.sa -org.sa -gov.sa -med.sa -pub.sa -edu.sa -sch.sa - -// sb : http://www.sbnic.net.sb/ -// Submitted by registry -sb -com.sb -edu.sb -gov.sb -net.sb -org.sb - -// sc : http://www.nic.sc/ -sc -com.sc -gov.sc -net.sc -org.sc -edu.sc - -// sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm -// Submitted by registry -sd -com.sd -net.sd -org.sd -edu.sd -med.sd -tv.sd -gov.sd -info.sd - -// se : https://en.wikipedia.org/wiki/.se -// Submitted by registry -se -a.se -ac.se -b.se -bd.se -brand.se -c.se -d.se -e.se -f.se -fh.se -fhsk.se -fhv.se -g.se -h.se -i.se -k.se -komforb.se -kommunalforbund.se -komvux.se -l.se -lanbib.se -m.se -n.se -naturbruksgymn.se -o.se -org.se -p.se -parti.se -pp.se -press.se -r.se -s.se -t.se -tm.se -u.se -w.se -x.se -y.se -z.se - -// sg : http://www.nic.net.sg/page/registration-policies-procedures-and-guidelines -sg -com.sg -net.sg -org.sg -gov.sg -edu.sg -per.sg - -// sh : http://nic.sh/rules.htm -sh -com.sh -net.sh -gov.sh -org.sh -mil.sh - -// si : https://en.wikipedia.org/wiki/.si -si - -// sj : No registrations at this time. -// Submitted by registry -sj - -// sk : https://en.wikipedia.org/wiki/.sk -// list of 2nd level domains ? -sk - -// sl : http://www.nic.sl -// Submitted by registry -sl -com.sl -net.sl -edu.sl -gov.sl -org.sl - -// sm : https://en.wikipedia.org/wiki/.sm -sm - -// sn : https://en.wikipedia.org/wiki/.sn -sn -art.sn -com.sn -edu.sn -gouv.sn -org.sn -perso.sn -univ.sn - -// so : http://sonic.so/policies/ -so -com.so -edu.so -gov.so -me.so -net.so -org.so - -// sr : https://en.wikipedia.org/wiki/.sr -sr - -// ss : https://registry.nic.ss/ -// Submitted by registry -ss -biz.ss -com.ss -edu.ss -gov.ss -me.ss -net.ss -org.ss -sch.ss - -// st : http://www.nic.st/html/policyrules/ -st -co.st -com.st -consulado.st -edu.st -embaixada.st -mil.st -net.st -org.st -principe.st -saotome.st -store.st - -// su : https://en.wikipedia.org/wiki/.su -su - -// sv : http://www.svnet.org.sv/niveldos.pdf -sv -com.sv -edu.sv -gob.sv -org.sv -red.sv - -// sx : https://en.wikipedia.org/wiki/.sx -// Submitted by registry -sx -gov.sx - -// sy : https://en.wikipedia.org/wiki/.sy -// see also: http://www.gobin.info/domainname/sy.doc -sy -edu.sy -gov.sy -net.sy -mil.sy -com.sy -org.sy - -// sz : https://en.wikipedia.org/wiki/.sz -// http://www.sispa.org.sz/ -sz -co.sz -ac.sz -org.sz - -// tc : https://en.wikipedia.org/wiki/.tc -tc - -// td : https://en.wikipedia.org/wiki/.td -td - -// tel: https://en.wikipedia.org/wiki/.tel -// http://www.telnic.org/ -tel - -// tf : https://en.wikipedia.org/wiki/.tf -tf - -// tg : https://en.wikipedia.org/wiki/.tg -// http://www.nic.tg/ -tg - -// th : https://en.wikipedia.org/wiki/.th -// Submitted by registry -th -ac.th -co.th -go.th -in.th -mi.th -net.th -or.th - -// tj : http://www.nic.tj/policy.html -tj -ac.tj -biz.tj -co.tj -com.tj -edu.tj -go.tj -gov.tj -int.tj -mil.tj -name.tj -net.tj -nic.tj -org.tj -test.tj -web.tj - -// tk : https://en.wikipedia.org/wiki/.tk -tk - -// tl : https://en.wikipedia.org/wiki/.tl -tl -gov.tl - -// tm : http://www.nic.tm/local.html -tm -com.tm -co.tm -org.tm -net.tm -nom.tm -gov.tm -mil.tm -edu.tm - -// tn : http://www.registre.tn/fr/ -// https://whois.ati.tn/ -tn -com.tn -ens.tn -fin.tn -gov.tn -ind.tn -info.tn -intl.tn -mincom.tn -nat.tn -net.tn -org.tn -perso.tn -tourism.tn - -// to : https://en.wikipedia.org/wiki/.to -// Submitted by registry -to -com.to -gov.to -net.to -org.to -edu.to -mil.to - -// tr : https://nic.tr/ -// https://nic.tr/forms/eng/policies.pdf -// https://nic.tr/index.php?USRACTN=PRICELST -tr -av.tr -bbs.tr -bel.tr -biz.tr -com.tr -dr.tr -edu.tr -gen.tr -gov.tr -info.tr -mil.tr -k12.tr -kep.tr -name.tr -net.tr -org.tr -pol.tr -tel.tr -tsk.tr -tv.tr -web.tr -// Used by Northern Cyprus -nc.tr -// Used by government agencies of Northern Cyprus -gov.nc.tr - -// tt : http://www.nic.tt/ -tt -co.tt -com.tt -org.tt -net.tt -biz.tt -info.tt -pro.tt -int.tt -coop.tt -jobs.tt -mobi.tt -travel.tt -museum.tt -aero.tt -name.tt -gov.tt -edu.tt - -// tv : https://en.wikipedia.org/wiki/.tv -// Not listing any 2LDs as reserved since none seem to exist in practice, -// Wikipedia notwithstanding. -tv - -// tw : https://en.wikipedia.org/wiki/.tw -tw -edu.tw -gov.tw -mil.tw -com.tw -net.tw -org.tw -idv.tw -game.tw -ebiz.tw -club.tw -網路.tw -組織.tw -商業.tw - -// tz : http://www.tznic.or.tz/index.php/domains -// Submitted by registry -tz -ac.tz -co.tz -go.tz -hotel.tz -info.tz -me.tz -mil.tz -mobi.tz -ne.tz -or.tz -sc.tz -tv.tz - -// ua : https://hostmaster.ua/policy/?ua -// Submitted by registry -ua -// ua 2LD -com.ua -edu.ua -gov.ua -in.ua -net.ua -org.ua -// ua geographic names -// https://hostmaster.ua/2ld/ -cherkassy.ua -cherkasy.ua -chernigov.ua -chernihiv.ua -chernivtsi.ua -chernovtsy.ua -ck.ua -cn.ua -cr.ua -crimea.ua -cv.ua -dn.ua -dnepropetrovsk.ua -dnipropetrovsk.ua -donetsk.ua -dp.ua -if.ua -ivano-frankivsk.ua -kh.ua -kharkiv.ua -kharkov.ua -kherson.ua -khmelnitskiy.ua -khmelnytskyi.ua -kiev.ua -kirovograd.ua -km.ua -kr.ua -krym.ua -ks.ua -kv.ua -kyiv.ua -lg.ua -lt.ua -lugansk.ua -lutsk.ua -lv.ua -lviv.ua -mk.ua -mykolaiv.ua -nikolaev.ua -od.ua -odesa.ua -odessa.ua -pl.ua -poltava.ua -rivne.ua -rovno.ua -rv.ua -sb.ua -sebastopol.ua -sevastopol.ua -sm.ua -sumy.ua -te.ua -ternopil.ua -uz.ua -uzhgorod.ua -vinnica.ua -vinnytsia.ua -vn.ua -volyn.ua -yalta.ua -zaporizhzhe.ua -zaporizhzhia.ua -zhitomir.ua -zhytomyr.ua -zp.ua -zt.ua - -// ug : https://www.registry.co.ug/ -ug -co.ug -or.ug -ac.ug -sc.ug -go.ug -ne.ug -com.ug -org.ug - -// uk : https://en.wikipedia.org/wiki/.uk -// Submitted by registry -uk -ac.uk -co.uk -gov.uk -ltd.uk -me.uk -net.uk -nhs.uk -org.uk -plc.uk -police.uk -*.sch.uk - -// us : https://en.wikipedia.org/wiki/.us -us -dni.us -fed.us -isa.us -kids.us -nsn.us -// us geographic names -ak.us -al.us -ar.us -as.us -az.us -ca.us -co.us -ct.us -dc.us -de.us -fl.us -ga.us -gu.us -hi.us -ia.us -id.us -il.us -in.us -ks.us -ky.us -la.us -ma.us -md.us -me.us -mi.us -mn.us -mo.us -ms.us -mt.us -nc.us -nd.us -ne.us -nh.us -nj.us -nm.us -nv.us -ny.us -oh.us -ok.us -or.us -pa.us -pr.us -ri.us -sc.us -sd.us -tn.us -tx.us -ut.us -vi.us -vt.us -va.us -wa.us -wi.us -wv.us -wy.us -// The registrar notes several more specific domains available in each state, -// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat -// haphazard; in some states these domains resolve as addresses, while in others -// only subdomains are available, or even nothing at all. We include the -// most common ones where it's clear that different sites are different -// entities. -k12.ak.us -k12.al.us -k12.ar.us -k12.as.us -k12.az.us -k12.ca.us -k12.co.us -k12.ct.us -k12.dc.us -k12.de.us -k12.fl.us -k12.ga.us -k12.gu.us -// k12.hi.us Bug 614565 - Hawaii has a state-wide DOE login -k12.ia.us -k12.id.us -k12.il.us -k12.in.us -k12.ks.us -k12.ky.us -k12.la.us -k12.ma.us -k12.md.us -k12.me.us -k12.mi.us -k12.mn.us -k12.mo.us -k12.ms.us -k12.mt.us -k12.nc.us -// k12.nd.us Bug 1028347 - Removed at request of Travis Rosso -k12.ne.us -k12.nh.us -k12.nj.us -k12.nm.us -k12.nv.us -k12.ny.us -k12.oh.us -k12.ok.us -k12.or.us -k12.pa.us -k12.pr.us -// k12.ri.us Removed at request of Kim Cournoyer -k12.sc.us -// k12.sd.us Bug 934131 - Removed at request of James Booze -k12.tn.us -k12.tx.us -k12.ut.us -k12.vi.us -k12.vt.us -k12.va.us -k12.wa.us -k12.wi.us -// k12.wv.us Bug 947705 - Removed at request of Verne Britton -k12.wy.us -cc.ak.us -cc.al.us -cc.ar.us -cc.as.us -cc.az.us -cc.ca.us -cc.co.us -cc.ct.us -cc.dc.us -cc.de.us -cc.fl.us -cc.ga.us -cc.gu.us -cc.hi.us -cc.ia.us -cc.id.us -cc.il.us -cc.in.us -cc.ks.us -cc.ky.us -cc.la.us -cc.ma.us -cc.md.us -cc.me.us -cc.mi.us -cc.mn.us -cc.mo.us -cc.ms.us -cc.mt.us -cc.nc.us -cc.nd.us -cc.ne.us -cc.nh.us -cc.nj.us -cc.nm.us -cc.nv.us -cc.ny.us -cc.oh.us -cc.ok.us -cc.or.us -cc.pa.us -cc.pr.us -cc.ri.us -cc.sc.us -cc.sd.us -cc.tn.us -cc.tx.us -cc.ut.us -cc.vi.us -cc.vt.us -cc.va.us -cc.wa.us -cc.wi.us -cc.wv.us -cc.wy.us -lib.ak.us -lib.al.us -lib.ar.us -lib.as.us -lib.az.us -lib.ca.us -lib.co.us -lib.ct.us -lib.dc.us -// lib.de.us Issue #243 - Moved to Private section at request of Ed Moore -lib.fl.us -lib.ga.us -lib.gu.us -lib.hi.us -lib.ia.us -lib.id.us -lib.il.us -lib.in.us -lib.ks.us -lib.ky.us -lib.la.us -lib.ma.us -lib.md.us -lib.me.us -lib.mi.us -lib.mn.us -lib.mo.us -lib.ms.us -lib.mt.us -lib.nc.us -lib.nd.us -lib.ne.us -lib.nh.us -lib.nj.us -lib.nm.us -lib.nv.us -lib.ny.us -lib.oh.us -lib.ok.us -lib.or.us -lib.pa.us -lib.pr.us -lib.ri.us -lib.sc.us -lib.sd.us -lib.tn.us -lib.tx.us -lib.ut.us -lib.vi.us -lib.vt.us -lib.va.us -lib.wa.us -lib.wi.us -// lib.wv.us Bug 941670 - Removed at request of Larry W Arnold -lib.wy.us -// k12.ma.us contains school districts in Massachusetts. The 4LDs are -// managed independently except for private (PVT), charter (CHTR) and -// parochial (PAROCH) schools. Those are delegated directly to the -// 5LD operators. -pvt.k12.ma.us -chtr.k12.ma.us -paroch.k12.ma.us -// Merit Network, Inc. maintains the registry for =~ /(k12|cc|lib).mi.us/ and the following -// see also: http://domreg.merit.edu -// see also: whois -h whois.domreg.merit.edu help -ann-arbor.mi.us -cog.mi.us -dst.mi.us -eaton.mi.us -gen.mi.us -mus.mi.us -tec.mi.us -washtenaw.mi.us - -// uy : http://www.nic.org.uy/ -uy -com.uy -edu.uy -gub.uy -mil.uy -net.uy -org.uy - -// uz : http://www.reg.uz/ -uz -co.uz -com.uz -net.uz -org.uz - -// va : https://en.wikipedia.org/wiki/.va -va - -// vc : https://en.wikipedia.org/wiki/.vc -// Submitted by registry -vc -com.vc -net.vc -org.vc -gov.vc -mil.vc -edu.vc - -// ve : https://registro.nic.ve/ -// Submitted by registry nic@nic.ve and nicve@conatel.gob.ve -ve -arts.ve -bib.ve -co.ve -com.ve -e12.ve -edu.ve -firm.ve -gob.ve -gov.ve -info.ve -int.ve -mil.ve -net.ve -nom.ve -org.ve -rar.ve -rec.ve -store.ve -tec.ve -web.ve - -// vg : https://en.wikipedia.org/wiki/.vg -vg - -// vi : http://www.nic.vi/newdomainform.htm -// http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other -// TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they -// are available for registration (which they do not seem to be). -vi -co.vi -com.vi -k12.vi -net.vi -org.vi - -// vn : https://www.dot.vn/vnnic/vnnic/domainregistration.jsp -vn -com.vn -net.vn -org.vn -edu.vn -gov.vn -int.vn -ac.vn -biz.vn -info.vn -name.vn -pro.vn -health.vn - -// vu : https://en.wikipedia.org/wiki/.vu -// http://www.vunic.vu/ -vu -com.vu -edu.vu -net.vu -org.vu - -// wf : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf -wf - -// ws : https://en.wikipedia.org/wiki/.ws -// http://samoanic.ws/index.dhtml -ws -com.ws -net.ws -org.ws -gov.ws -edu.ws - -// yt : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf -yt - -// IDN ccTLDs -// When submitting patches, please maintain a sort by ISO 3166 ccTLD, then -// U-label, and follow this format: -// // A-Label ("", [, variant info]) : -// // [sponsoring org] -// U-Label - -// xn--mgbaam7a8h ("Emerat", Arabic) : AE -// http://nic.ae/english/arabicdomain/rules.jsp -امارات - -// xn--y9a3aq ("hye", Armenian) : AM -// ISOC AM (operated by .am Registry) -հայ - -// xn--54b7fta0cc ("Bangla", Bangla) : BD -বাংলা - -// xn--90ae ("bg", Bulgarian) : BG -бг - -// xn--mgbcpq6gpa1a ("albahrain", Arabic) : BH -البحرين - -// xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY -// Operated by .by registry -бел - -// xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN -// CNNIC -// http://cnnic.cn/html/Dir/2005/10/11/3218.htm -中国 - -// xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN -// CNNIC -// http://cnnic.cn/html/Dir/2005/10/11/3218.htm -中國 - -// xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ -الجزائر - -// xn--wgbh1c ("Egypt/Masr", Arabic) : EG -// http://www.dotmasr.eg/ -مصر - -// xn--e1a4c ("eu", Cyrillic) : EU -// https://eurid.eu -ею - -// xn--qxa6a ("eu", Greek) : EU -// https://eurid.eu -ευ - -// xn--mgbah1a3hjkrd ("Mauritania", Arabic) : MR -موريتانيا - -// xn--node ("ge", Georgian Mkhedruli) : GE -გე - -// xn--qxam ("el", Greek) : GR -// Hellenic Ministry of Infrastructure, Transport, and Networks -ελ - -// xn--j6w193g ("Hong Kong", Chinese) : HK -// https://www.hkirc.hk -// Submitted by registry -// https://www.hkirc.hk/content.jsp?id=30#!/34 -香港 -公司.香港 -教育.香港 -政府.香港 -個人.香港 -網絡.香港 -組織.香港 - -// xn--2scrj9c ("Bharat", Kannada) : IN -// India -ಭಾರತ - -// xn--3hcrj9c ("Bharat", Oriya) : IN -// India -ଭାରତ - -// xn--45br5cyl ("Bharatam", Assamese) : IN -// India -ভাৰত - -// xn--h2breg3eve ("Bharatam", Sanskrit) : IN -// India -भारतम् - -// xn--h2brj9c8c ("Bharot", Santali) : IN -// India -भारोत - -// xn--mgbgu82a ("Bharat", Sindhi) : IN -// India -ڀارت - -// xn--rvc1e0am3e ("Bharatam", Malayalam) : IN -// India -ഭാരതം - -// xn--h2brj9c ("Bharat", Devanagari) : IN -// India -भारत - -// xn--mgbbh1a ("Bharat", Kashmiri) : IN -// India -بارت - -// xn--mgbbh1a71e ("Bharat", Arabic) : IN -// India -بھارت - -// xn--fpcrj9c3d ("Bharat", Telugu) : IN -// India -భారత్ - -// xn--gecrj9c ("Bharat", Gujarati) : IN -// India -ભારત - -// xn--s9brj9c ("Bharat", Gurmukhi) : IN -// India -ਭਾਰਤ - -// xn--45brj9c ("Bharat", Bengali) : IN -// India -ভারত - -// xn--xkc2dl3a5ee0h ("India", Tamil) : IN -// India -இந்தியா - -// xn--mgba3a4f16a ("Iran", Persian) : IR -ایران - -// xn--mgba3a4fra ("Iran", Arabic) : IR -ايران - -// xn--mgbtx2b ("Iraq", Arabic) : IQ -// Communications and Media Commission -عراق - -// xn--mgbayh7gpa ("al-Ordon", Arabic) : JO -// National Information Technology Center (NITC) -// Royal Scientific Society, Al-Jubeiha -الاردن - -// xn--3e0b707e ("Republic of Korea", Hangul) : KR -한국 - -// xn--80ao21a ("Kaz", Kazakh) : KZ -қаз - -// xn--q7ce6a ("Lao", Lao) : LA -ລາວ - -// xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK -// https://nic.lk -ලංකා - -// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK -// https://nic.lk -இலங்கை - -// xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA -المغرب - -// xn--d1alf ("mkd", Macedonian) : MK -// MARnet -мкд - -// xn--l1acc ("mon", Mongolian) : MN -мон - -// xn--mix891f ("Macao", Chinese, Traditional) : MO -// MONIC / HNET Asia (Registry Operator for .mo) -澳門 - -// xn--mix082f ("Macao", Chinese, Simplified) : MO -澳门 - -// xn--mgbx4cd0ab ("Malaysia", Malay) : MY -مليسيا - -// xn--mgb9awbf ("Oman", Arabic) : OM -عمان - -// xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK -پاکستان - -// xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK -پاكستان - -// xn--ygbi2ammx ("Falasteen", Arabic) : PS -// The Palestinian National Internet Naming Authority (PNINA) -// http://www.pnina.ps -فلسطين - -// xn--90a3ac ("srb", Cyrillic) : RS -// https://www.rnids.rs/en/domains/national-domains -срб -пр.срб -орг.срб -обр.срб -од.срб -упр.срб -ак.срб - -// xn--p1ai ("rf", Russian-Cyrillic) : RU -// https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf -// Submitted by George Georgievsky -рф - -// xn--wgbl6a ("Qatar", Arabic) : QA -// http://www.ict.gov.qa/ -قطر - -// xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA -// http://www.nic.net.sa/ -السعودية - -// xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant) : SA -السعودیة - -// xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA -السعودیۃ - -// xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA -السعوديه - -// xn--mgbpl2fh ("sudan", Arabic) : SD -// Operated by .sd registry -سودان - -// xn--yfro4i67o Singapore ("Singapore", Chinese) : SG -新加坡 - -// xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG -சிங்கப்பூர் - -// xn--ogbpf8fl ("Syria", Arabic) : SY -سورية - -// xn--mgbtf8fl ("Syria", Arabic, variant) : SY -سوريا - -// xn--o3cw4h ("Thai", Thai) : TH -// http://www.thnic.co.th -ไทย -ศึกษา.ไทย -ธุรกิจ.ไทย -รัฐบาล.ไทย -ทหาร.ไทย -เน็ต.ไทย -องค์กร.ไทย - -// xn--pgbs0dh ("Tunisia", Arabic) : TN -// http://nic.tn -تونس - -// xn--kpry57d ("Taiwan", Chinese, Traditional) : TW -// http://www.twnic.net/english/dn/dn_07a.htm -台灣 - -// xn--kprw13d ("Taiwan", Chinese, Simplified) : TW -// http://www.twnic.net/english/dn/dn_07a.htm -台湾 - -// xn--nnx388a ("Taiwan", Chinese, variant) : TW -臺灣 - -// xn--j1amh ("ukr", Cyrillic) : UA -укр - -// xn--mgb2ddes ("AlYemen", Arabic) : YE -اليمن - -// xxx : http://icmregistry.com -xxx - -// ye : http://www.y.net.ye/services/domain_name.htm -ye -com.ye -edu.ye -gov.ye -net.ye -mil.ye -org.ye - -// za : https://www.zadna.org.za/content/page/domain-information/ -ac.za -agric.za -alt.za -co.za -edu.za -gov.za -grondar.za -law.za -mil.za -net.za -ngo.za -nic.za -nis.za -nom.za -org.za -school.za -tm.za -web.za - -// zm : https://zicta.zm/ -// Submitted by registry -zm -ac.zm -biz.zm -co.zm -com.zm -edu.zm -gov.zm -info.zm -mil.zm -net.zm -org.zm -sch.zm - -// zw : https://www.potraz.gov.zw/ -// Confirmed by registry 2017-01-25 -zw -ac.zw -co.zw -gov.zw -mil.zw -org.zw - - -// newGTLDs - -// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2023-03-18T15:13:13Z -// This list is auto-generated, don't edit it manually. -// aaa : 2015-02-26 American Automobile Association, Inc. -aaa - -// aarp : 2015-05-21 AARP -aarp - -// abarth : 2015-07-30 Fiat Chrysler Automobiles N.V. -abarth - -// abb : 2014-10-24 ABB Ltd -abb - -// abbott : 2014-07-24 Abbott Laboratories, Inc. -abbott - -// abbvie : 2015-07-30 AbbVie Inc. -abbvie - -// abc : 2015-07-30 Disney Enterprises, Inc. -abc - -// able : 2015-06-25 Able Inc. -able - -// abogado : 2014-04-24 Registry Services, LLC -abogado - -// abudhabi : 2015-07-30 Abu Dhabi Systems and Information Centre -abudhabi - -// academy : 2013-11-07 Binky Moon, LLC -academy - -// accenture : 2014-08-15 Accenture plc -accenture - -// accountant : 2014-11-20 dot Accountant Limited -accountant - -// accountants : 2014-03-20 Binky Moon, LLC -accountants - -// aco : 2015-01-08 ACO Severin Ahlmann GmbH & Co. KG -aco - -// actor : 2013-12-12 Dog Beach, LLC -actor - -// ads : 2014-12-04 Charleston Road Registry Inc. -ads - -// adult : 2014-10-16 ICM Registry AD LLC -adult - -// aeg : 2015-03-19 Aktiebolaget Electrolux -aeg - -// aetna : 2015-05-21 Aetna Life Insurance Company -aetna - -// afl : 2014-10-02 Australian Football League -afl - -// africa : 2014-03-24 ZA Central Registry NPC trading as Registry.Africa -africa - -// agakhan : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation) -agakhan - -// agency : 2013-11-14 Binky Moon, LLC -agency - -// aig : 2014-12-18 American International Group, Inc. -aig - -// airbus : 2015-07-30 Airbus S.A.S. -airbus - -// airforce : 2014-03-06 Dog Beach, LLC -airforce - -// airtel : 2014-10-24 Bharti Airtel Limited -airtel - -// akdn : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation) -akdn - -// alfaromeo : 2015-07-31 Fiat Chrysler Automobiles N.V. -alfaromeo - -// alibaba : 2015-01-15 Alibaba Group Holding Limited -alibaba - -// alipay : 2015-01-15 Alibaba Group Holding Limited -alipay - -// allfinanz : 2014-07-03 Allfinanz Deutsche Vermögensberatung Aktiengesellschaft -allfinanz - -// allstate : 2015-07-31 Allstate Fire and Casualty Insurance Company -allstate - -// ally : 2015-06-18 Ally Financial Inc. -ally - -// alsace : 2014-07-02 Region Grand Est -alsace - -// alstom : 2015-07-30 ALSTOM -alstom - -// amazon : 2019-12-19 Amazon Registry Services, Inc. -amazon - -// americanexpress : 2015-07-31 American Express Travel Related Services Company, Inc. -americanexpress - -// americanfamily : 2015-07-23 AmFam, Inc. -americanfamily - -// amex : 2015-07-31 American Express Travel Related Services Company, Inc. -amex - -// amfam : 2015-07-23 AmFam, Inc. -amfam - -// amica : 2015-05-28 Amica Mutual Insurance Company -amica - -// amsterdam : 2014-07-24 Gemeente Amsterdam -amsterdam - -// analytics : 2014-12-18 Campus IP LLC -analytics - -// android : 2014-08-07 Charleston Road Registry Inc. -android - -// anquan : 2015-01-08 Beijing Qihu Keji Co., Ltd. -anquan - -// anz : 2015-07-31 Australia and New Zealand Banking Group Limited -anz - -// aol : 2015-09-17 Oath Inc. -aol - -// apartments : 2014-12-11 Binky Moon, LLC -apartments - -// app : 2015-05-14 Charleston Road Registry Inc. -app - -// apple : 2015-05-14 Apple Inc. -apple - -// aquarelle : 2014-07-24 Aquarelle.com -aquarelle - -// arab : 2015-11-12 League of Arab States -arab - -// aramco : 2014-11-20 Aramco Services Company -aramco - -// archi : 2014-02-06 Identity Digital Limited -archi - -// army : 2014-03-06 Dog Beach, LLC -army - -// art : 2016-03-24 UK Creative Ideas Limited -art - -// arte : 2014-12-11 Association Relative à la Télévision Européenne G.E.I.E. -arte - -// asda : 2015-07-31 Wal-Mart Stores, Inc. -asda - -// associates : 2014-03-06 Binky Moon, LLC -associates - -// athleta : 2015-07-30 The Gap, Inc. -athleta - -// attorney : 2014-03-20 Dog Beach, LLC -attorney - -// auction : 2014-03-20 Dog Beach, LLC -auction - -// audi : 2015-05-21 AUDI Aktiengesellschaft -audi - -// audible : 2015-06-25 Amazon Registry Services, Inc. -audible - -// audio : 2014-03-20 XYZ.COM LLC -audio - -// auspost : 2015-08-13 Australian Postal Corporation -auspost - -// author : 2014-12-18 Amazon Registry Services, Inc. -author - -// auto : 2014-11-13 XYZ.COM LLC -auto - -// autos : 2014-01-09 XYZ.COM LLC -autos - -// avianca : 2015-01-08 Avianca Inc. -avianca - -// aws : 2015-06-25 AWS Registry LLC -aws - -// axa : 2013-12-19 AXA Group Operations SAS -axa - -// azure : 2014-12-18 Microsoft Corporation -azure - -// baby : 2015-04-09 XYZ.COM LLC -baby - -// baidu : 2015-01-08 Baidu, Inc. -baidu - -// banamex : 2015-07-30 Citigroup Inc. -banamex - -// bananarepublic : 2015-07-31 The Gap, Inc. -bananarepublic - -// band : 2014-06-12 Dog Beach, LLC -band - -// bank : 2014-09-25 fTLD Registry Services LLC -bank - -// bar : 2013-12-12 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable -bar - -// barcelona : 2014-07-24 Municipi de Barcelona -barcelona - -// barclaycard : 2014-11-20 Barclays Bank PLC -barclaycard - -// barclays : 2014-11-20 Barclays Bank PLC -barclays - -// barefoot : 2015-06-11 Gallo Vineyards, Inc. -barefoot - -// bargains : 2013-11-14 Binky Moon, LLC -bargains - -// baseball : 2015-10-29 MLB Advanced Media DH, LLC -baseball - -// basketball : 2015-08-20 Fédération Internationale de Basketball (FIBA) -basketball - -// bauhaus : 2014-04-17 Werkhaus GmbH -bauhaus - -// bayern : 2014-01-23 Bayern Connect GmbH -bayern - -// bbc : 2014-12-18 British Broadcasting Corporation -bbc - -// bbt : 2015-07-23 BB&T Corporation -bbt - -// bbva : 2014-10-02 BANCO BILBAO VIZCAYA ARGENTARIA, S.A. -bbva - -// bcg : 2015-04-02 The Boston Consulting Group, Inc. -bcg - -// bcn : 2014-07-24 Municipi de Barcelona -bcn - -// beats : 2015-05-14 Beats Electronics, LLC -beats - -// beauty : 2015-12-03 XYZ.COM LLC -beauty - -// beer : 2014-01-09 Registry Services, LLC -beer - -// bentley : 2014-12-18 Bentley Motors Limited -bentley - -// berlin : 2013-10-31 dotBERLIN GmbH & Co. KG -berlin - -// best : 2013-12-19 BestTLD Pty Ltd -best - -// bestbuy : 2015-07-31 BBY Solutions, Inc. -bestbuy - -// bet : 2015-05-07 Identity Digital Limited -bet - -// bharti : 2014-01-09 Bharti Enterprises (Holding) Private Limited -bharti - -// bible : 2014-06-19 American Bible Society -bible - -// bid : 2013-12-19 dot Bid Limited -bid - -// bike : 2013-08-27 Binky Moon, LLC -bike - -// bing : 2014-12-18 Microsoft Corporation -bing - -// bingo : 2014-12-04 Binky Moon, LLC -bingo - -// bio : 2014-03-06 Identity Digital Limited -bio - -// black : 2014-01-16 Identity Digital Limited -black - -// blackfriday : 2014-01-16 Registry Services, LLC -blackfriday - -// blockbuster : 2015-07-30 Dish DBS Corporation -blockbuster - -// blog : 2015-05-14 Knock Knock WHOIS There, LLC -blog - -// bloomberg : 2014-07-17 Bloomberg IP Holdings LLC -bloomberg - -// blue : 2013-11-07 Identity Digital Limited -blue - -// bms : 2014-10-30 Bristol-Myers Squibb Company -bms - -// bmw : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft -bmw - -// bnpparibas : 2014-05-29 BNP Paribas -bnpparibas - -// boats : 2014-12-04 XYZ.COM LLC -boats - -// boehringer : 2015-07-09 Boehringer Ingelheim International GmbH -boehringer - -// bofa : 2015-07-31 Bank of America Corporation -bofa - -// bom : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br -bom - -// bond : 2014-06-05 ShortDot SA -bond - -// boo : 2014-01-30 Charleston Road Registry Inc. -boo - -// book : 2015-08-27 Amazon Registry Services, Inc. -book - -// booking : 2015-07-16 Booking.com B.V. -booking - -// bosch : 2015-06-18 Robert Bosch GMBH -bosch - -// bostik : 2015-05-28 Bostik SA -bostik - -// boston : 2015-12-10 Registry Services, LLC -boston - -// bot : 2014-12-18 Amazon Registry Services, Inc. -bot - -// boutique : 2013-11-14 Binky Moon, LLC -boutique - -// box : 2015-11-12 Intercap Registry Inc. -box - -// bradesco : 2014-12-18 Banco Bradesco S.A. -bradesco - -// bridgestone : 2014-12-18 Bridgestone Corporation -bridgestone - -// broadway : 2014-12-22 Celebrate Broadway, Inc. -broadway - -// broker : 2014-12-11 Dog Beach, LLC -broker - -// brother : 2015-01-29 Brother Industries, Ltd. -brother - -// brussels : 2014-02-06 DNS.be vzw -brussels - -// build : 2013-11-07 Plan Bee LLC -build - -// builders : 2013-11-07 Binky Moon, LLC -builders - -// business : 2013-11-07 Binky Moon, LLC -business - -// buy : 2014-12-18 Amazon Registry Services, Inc. -buy - -// buzz : 2013-10-02 DOTSTRATEGY CO. -buzz - -// bzh : 2014-02-27 Association www.bzh -bzh - -// cab : 2013-10-24 Binky Moon, LLC -cab - -// cafe : 2015-02-11 Binky Moon, LLC -cafe - -// cal : 2014-07-24 Charleston Road Registry Inc. -cal - -// call : 2014-12-18 Amazon Registry Services, Inc. -call - -// calvinklein : 2015-07-30 PVH gTLD Holdings LLC -calvinklein - -// cam : 2016-04-21 Cam Connecting SARL -cam - -// camera : 2013-08-27 Binky Moon, LLC -camera - -// camp : 2013-11-07 Binky Moon, LLC -camp - -// canon : 2014-09-12 Canon Inc. -canon - -// capetown : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry -capetown - -// capital : 2014-03-06 Binky Moon, LLC -capital - -// capitalone : 2015-08-06 Capital One Financial Corporation -capitalone - -// car : 2015-01-22 XYZ.COM LLC -car - -// caravan : 2013-12-12 Caravan International, Inc. -caravan - -// cards : 2013-12-05 Binky Moon, LLC -cards - -// care : 2014-03-06 Binky Moon, LLC -care - -// career : 2013-10-09 dotCareer LLC -career - -// careers : 2013-10-02 Binky Moon, LLC -careers - -// cars : 2014-11-13 XYZ.COM LLC -cars - -// casa : 2013-11-21 Registry Services, LLC -casa - -// case : 2015-09-03 Digity, LLC -case - -// cash : 2014-03-06 Binky Moon, LLC -cash - -// casino : 2014-12-18 Binky Moon, LLC -casino - -// catering : 2013-12-05 Binky Moon, LLC -catering - -// catholic : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) -catholic - -// cba : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA -cba - -// cbn : 2014-08-22 The Christian Broadcasting Network, Inc. -cbn - -// cbre : 2015-07-02 CBRE, Inc. -cbre - -// cbs : 2015-08-06 CBS Domains Inc. -cbs - -// center : 2013-11-07 Binky Moon, LLC -center - -// ceo : 2013-11-07 CEOTLD Pty Ltd -ceo - -// cern : 2014-06-05 European Organization for Nuclear Research ("CERN") -cern - -// cfa : 2014-08-28 CFA Institute -cfa - -// cfd : 2014-12-11 ShortDot SA -cfd - -// chanel : 2015-04-09 Chanel International B.V. -chanel - -// channel : 2014-05-08 Charleston Road Registry Inc. -channel - -// charity : 2018-04-11 Public Interest Registry -charity - -// chase : 2015-04-30 JPMorgan Chase Bank, National Association -chase - -// chat : 2014-12-04 Binky Moon, LLC -chat - -// cheap : 2013-11-14 Binky Moon, LLC -cheap - -// chintai : 2015-06-11 CHINTAI Corporation -chintai - -// christmas : 2013-11-21 XYZ.COM LLC -christmas - -// chrome : 2014-07-24 Charleston Road Registry Inc. -chrome - -// church : 2014-02-06 Binky Moon, LLC -church - -// cipriani : 2015-02-19 Hotel Cipriani Srl -cipriani - -// circle : 2014-12-18 Amazon Registry Services, Inc. -circle - -// cisco : 2014-12-22 Cisco Technology, Inc. -cisco - -// citadel : 2015-07-23 Citadel Domain LLC -citadel - -// citi : 2015-07-30 Citigroup Inc. -citi - -// citic : 2014-01-09 CITIC Group Corporation -citic - -// city : 2014-05-29 Binky Moon, LLC -city - -// cityeats : 2014-12-11 Lifestyle Domain Holdings, Inc. -cityeats - -// claims : 2014-03-20 Binky Moon, LLC -claims - -// cleaning : 2013-12-05 Binky Moon, LLC -cleaning - -// click : 2014-06-05 Internet Naming Company LLC -click - -// clinic : 2014-03-20 Binky Moon, LLC -clinic - -// clinique : 2015-10-01 The Estée Lauder Companies Inc. -clinique - -// clothing : 2013-08-27 Binky Moon, LLC -clothing - -// cloud : 2015-04-16 Aruba PEC S.p.A. -cloud - -// club : 2013-11-08 Registry Services, LLC -club - -// clubmed : 2015-06-25 Club Méditerranée S.A. -clubmed - -// coach : 2014-10-09 Binky Moon, LLC -coach - -// codes : 2013-10-31 Binky Moon, LLC -codes - -// coffee : 2013-10-17 Binky Moon, LLC -coffee - -// college : 2014-01-16 XYZ.COM LLC -college - -// cologne : 2014-02-05 dotKoeln GmbH -cologne - -// comcast : 2015-07-23 Comcast IP Holdings I, LLC -comcast - -// commbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA -commbank - -// community : 2013-12-05 Binky Moon, LLC -community - -// company : 2013-11-07 Binky Moon, LLC -company - -// compare : 2015-10-08 Registry Services, LLC -compare - -// computer : 2013-10-24 Binky Moon, LLC -computer - -// comsec : 2015-01-08 VeriSign, Inc. -comsec - -// condos : 2013-12-05 Binky Moon, LLC -condos - -// construction : 2013-09-16 Binky Moon, LLC -construction - -// consulting : 2013-12-05 Dog Beach, LLC -consulting - -// contact : 2015-01-08 Dog Beach, LLC -contact - -// contractors : 2013-09-10 Binky Moon, LLC -contractors - -// cooking : 2013-11-21 Registry Services, LLC -cooking - -// cookingchannel : 2015-07-02 Lifestyle Domain Holdings, Inc. -cookingchannel - -// cool : 2013-11-14 Binky Moon, LLC -cool - -// corsica : 2014-09-25 Collectivité de Corse -corsica - -// country : 2013-12-19 Internet Naming Company LLC -country - -// coupon : 2015-02-26 Amazon Registry Services, Inc. -coupon - -// coupons : 2015-03-26 Binky Moon, LLC -coupons - -// courses : 2014-12-04 Registry Services, LLC -courses - -// cpa : 2019-06-10 American Institute of Certified Public Accountants -cpa - -// credit : 2014-03-20 Binky Moon, LLC -credit - -// creditcard : 2014-03-20 Binky Moon, LLC -creditcard - -// creditunion : 2015-01-22 DotCooperation LLC -creditunion - -// cricket : 2014-10-09 dot Cricket Limited -cricket - -// crown : 2014-10-24 Crown Equipment Corporation -crown - -// crs : 2014-04-03 Federated Co-operatives Limited -crs - -// cruise : 2015-12-10 Viking River Cruises (Bermuda) Ltd. -cruise - -// cruises : 2013-12-05 Binky Moon, LLC -cruises - -// cuisinella : 2014-04-03 SCHMIDT GROUPE S.A.S. -cuisinella - -// cymru : 2014-05-08 Nominet UK -cymru - -// cyou : 2015-01-22 ShortDot SA -cyou - -// dabur : 2014-02-06 Dabur India Limited -dabur - -// dad : 2014-01-23 Charleston Road Registry Inc. -dad - -// dance : 2013-10-24 Dog Beach, LLC -dance - -// data : 2016-06-02 Dish DBS Corporation -data - -// date : 2014-11-20 dot Date Limited -date - -// dating : 2013-12-05 Binky Moon, LLC -dating - -// datsun : 2014-03-27 NISSAN MOTOR CO., LTD. -datsun - -// day : 2014-01-30 Charleston Road Registry Inc. -day - -// dclk : 2014-11-20 Charleston Road Registry Inc. -dclk - -// dds : 2015-05-07 Registry Services, LLC -dds - -// deal : 2015-06-25 Amazon Registry Services, Inc. -deal - -// dealer : 2014-12-22 Intercap Registry Inc. -dealer - -// deals : 2014-05-22 Binky Moon, LLC -deals - -// degree : 2014-03-06 Dog Beach, LLC -degree - -// delivery : 2014-09-11 Binky Moon, LLC -delivery - -// dell : 2014-10-24 Dell Inc. -dell - -// deloitte : 2015-07-31 Deloitte Touche Tohmatsu -deloitte - -// delta : 2015-02-19 Delta Air Lines, Inc. -delta - -// democrat : 2013-10-24 Dog Beach, LLC -democrat - -// dental : 2014-03-20 Binky Moon, LLC -dental - -// dentist : 2014-03-20 Dog Beach, LLC -dentist - -// desi : 2013-11-14 Desi Networks LLC -desi - -// design : 2014-11-07 Registry Services, LLC -design - -// dev : 2014-10-16 Charleston Road Registry Inc. -dev - -// dhl : 2015-07-23 Deutsche Post AG -dhl - -// diamonds : 2013-09-22 Binky Moon, LLC -diamonds - -// diet : 2014-06-26 XYZ.COM LLC -diet - -// digital : 2014-03-06 Binky Moon, LLC -digital - -// direct : 2014-04-10 Binky Moon, LLC -direct - -// directory : 2013-09-20 Binky Moon, LLC -directory - -// discount : 2014-03-06 Binky Moon, LLC -discount - -// discover : 2015-07-23 Discover Financial Services -discover - -// dish : 2015-07-30 Dish DBS Corporation -dish - -// diy : 2015-11-05 Lifestyle Domain Holdings, Inc. -diy - -// dnp : 2013-12-13 Dai Nippon Printing Co., Ltd. -dnp - -// docs : 2014-10-16 Charleston Road Registry Inc. -docs - -// doctor : 2016-06-02 Binky Moon, LLC -doctor - -// dog : 2014-12-04 Binky Moon, LLC -dog - -// domains : 2013-10-17 Binky Moon, LLC -domains - -// dot : 2015-05-21 Dish DBS Corporation -dot - -// download : 2014-11-20 dot Support Limited -download - -// drive : 2015-03-05 Charleston Road Registry Inc. -drive - -// dtv : 2015-06-04 Dish DBS Corporation -dtv - -// dubai : 2015-01-01 Dubai Smart Government Department -dubai - -// dunlop : 2015-07-02 The Goodyear Tire & Rubber Company -dunlop - -// dupont : 2015-06-25 DuPont Specialty Products USA, LLC -dupont - -// durban : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry -durban - -// dvag : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG -dvag - -// dvr : 2016-05-26 DISH Technologies L.L.C. -dvr - -// earth : 2014-12-04 Interlink Systems Innovation Institute K.K. -earth - -// eat : 2014-01-23 Charleston Road Registry Inc. -eat - -// eco : 2016-07-08 Big Room Inc. -eco - -// edeka : 2014-12-18 EDEKA Verband kaufmännischer Genossenschaften e.V. -edeka - -// education : 2013-11-07 Binky Moon, LLC -education - -// email : 2013-10-31 Binky Moon, LLC -email - -// emerck : 2014-04-03 Merck KGaA -emerck - -// energy : 2014-09-11 Binky Moon, LLC -energy - -// engineer : 2014-03-06 Dog Beach, LLC -engineer - -// engineering : 2014-03-06 Binky Moon, LLC -engineering - -// enterprises : 2013-09-20 Binky Moon, LLC -enterprises - -// epson : 2014-12-04 Seiko Epson Corporation -epson - -// equipment : 2013-08-27 Binky Moon, LLC -equipment - -// ericsson : 2015-07-09 Telefonaktiebolaget L M Ericsson -ericsson - -// erni : 2014-04-03 ERNI Group Holding AG -erni - -// esq : 2014-05-08 Charleston Road Registry Inc. -esq - -// estate : 2013-08-27 Binky Moon, LLC -estate - -// etisalat : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat) -etisalat - -// eurovision : 2014-04-24 European Broadcasting Union (EBU) -eurovision - -// eus : 2013-12-12 Puntueus Fundazioa -eus - -// events : 2013-12-05 Binky Moon, LLC -events - -// exchange : 2014-03-06 Binky Moon, LLC -exchange - -// expert : 2013-11-21 Binky Moon, LLC -expert - -// exposed : 2013-12-05 Binky Moon, LLC -exposed - -// express : 2015-02-11 Binky Moon, LLC -express - -// extraspace : 2015-05-14 Extra Space Storage LLC -extraspace - -// fage : 2014-12-18 Fage International S.A. -fage - -// fail : 2014-03-06 Binky Moon, LLC -fail - -// fairwinds : 2014-11-13 FairWinds Partners, LLC -fairwinds - -// faith : 2014-11-20 dot Faith Limited -faith - -// family : 2015-04-02 Dog Beach, LLC -family - -// fan : 2014-03-06 Dog Beach, LLC -fan - -// fans : 2014-11-07 ZDNS International Limited -fans - -// farm : 2013-11-07 Binky Moon, LLC -farm - -// farmers : 2015-07-09 Farmers Insurance Exchange -farmers - -// fashion : 2014-07-03 Registry Services, LLC -fashion - -// fast : 2014-12-18 Amazon Registry Services, Inc. -fast - -// fedex : 2015-08-06 Federal Express Corporation -fedex - -// feedback : 2013-12-19 Top Level Spectrum, Inc. -feedback - -// ferrari : 2015-07-31 Fiat Chrysler Automobiles N.V. -ferrari - -// ferrero : 2014-12-18 Ferrero Trading Lux S.A. -ferrero - -// fiat : 2015-07-31 Fiat Chrysler Automobiles N.V. -fiat - -// fidelity : 2015-07-30 Fidelity Brokerage Services LLC -fidelity - -// fido : 2015-08-06 Rogers Communications Canada Inc. -fido - -// film : 2015-01-08 Motion Picture Domain Registry Pty Ltd -film - -// final : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br -final - -// finance : 2014-03-20 Binky Moon, LLC -finance - -// financial : 2014-03-06 Binky Moon, LLC -financial - -// fire : 2015-06-25 Amazon Registry Services, Inc. -fire - -// firestone : 2014-12-18 Bridgestone Licensing Services, Inc -firestone - -// firmdale : 2014-03-27 Firmdale Holdings Limited -firmdale - -// fish : 2013-12-12 Binky Moon, LLC -fish - -// fishing : 2013-11-21 Registry Services, LLC -fishing - -// fit : 2014-11-07 Registry Services, LLC -fit - -// fitness : 2014-03-06 Binky Moon, LLC -fitness - -// flickr : 2015-04-02 Flickr, Inc. -flickr - -// flights : 2013-12-05 Binky Moon, LLC -flights - -// flir : 2015-07-23 FLIR Systems, Inc. -flir - -// florist : 2013-11-07 Binky Moon, LLC -florist - -// flowers : 2014-10-09 XYZ.COM LLC -flowers - -// fly : 2014-05-08 Charleston Road Registry Inc. -fly - -// foo : 2014-01-23 Charleston Road Registry Inc. -foo - -// food : 2016-04-21 Lifestyle Domain Holdings, Inc. -food - -// foodnetwork : 2015-07-02 Lifestyle Domain Holdings, Inc. -foodnetwork - -// football : 2014-12-18 Binky Moon, LLC -football - -// ford : 2014-11-13 Ford Motor Company -ford - -// forex : 2014-12-11 Dog Beach, LLC -forex - -// forsale : 2014-05-22 Dog Beach, LLC -forsale - -// forum : 2015-04-02 Fegistry, LLC -forum - -// foundation : 2013-12-05 Public Interest Registry -foundation - -// fox : 2015-09-11 FOX Registry, LLC -fox - -// free : 2015-12-10 Amazon Registry Services, Inc. -free - -// fresenius : 2015-07-30 Fresenius Immobilien-Verwaltungs-GmbH -fresenius - -// frl : 2014-05-15 FRLregistry B.V. -frl - -// frogans : 2013-12-19 OP3FT -frogans - -// frontdoor : 2015-07-02 Lifestyle Domain Holdings, Inc. -frontdoor - -// frontier : 2015-02-05 Frontier Communications Corporation -frontier - -// ftr : 2015-07-16 Frontier Communications Corporation -ftr - -// fujitsu : 2015-07-30 Fujitsu Limited -fujitsu - -// fun : 2016-01-14 Radix FZC -fun - -// fund : 2014-03-20 Binky Moon, LLC -fund - -// furniture : 2014-03-20 Binky Moon, LLC -furniture - -// futbol : 2013-09-20 Dog Beach, LLC -futbol - -// fyi : 2015-04-02 Binky Moon, LLC -fyi - -// gal : 2013-11-07 Asociación puntoGAL -gal - -// gallery : 2013-09-13 Binky Moon, LLC -gallery - -// gallo : 2015-06-11 Gallo Vineyards, Inc. -gallo - -// gallup : 2015-02-19 Gallup, Inc. -gallup - -// game : 2015-05-28 XYZ.COM LLC -game - -// games : 2015-05-28 Dog Beach, LLC -games - -// gap : 2015-07-31 The Gap, Inc. -gap - -// garden : 2014-06-26 Registry Services, LLC -garden - -// gay : 2019-05-23 Top Level Design, LLC -gay - -// gbiz : 2014-07-17 Charleston Road Registry Inc. -gbiz - -// gdn : 2014-07-31 Joint Stock Company "Navigation-information systems" -gdn - -// gea : 2014-12-04 GEA Group Aktiengesellschaft -gea - -// gent : 2014-01-23 Easyhost BV -gent - -// genting : 2015-03-12 Resorts World Inc Pte. Ltd. -genting - -// george : 2015-07-31 Wal-Mart Stores, Inc. -george - -// ggee : 2014-01-09 GMO Internet, Inc. -ggee - -// gift : 2013-10-17 DotGift, LLC -gift - -// gifts : 2014-07-03 Binky Moon, LLC -gifts - -// gives : 2014-03-06 Public Interest Registry -gives - -// giving : 2014-11-13 Public Interest Registry -giving - -// glass : 2013-11-07 Binky Moon, LLC -glass - -// gle : 2014-07-24 Charleston Road Registry Inc. -gle - -// global : 2014-04-17 Identity Digital Limited -global - -// globo : 2013-12-19 Globo Comunicação e Participações S.A -globo - -// gmail : 2014-05-01 Charleston Road Registry Inc. -gmail - -// gmbh : 2016-01-29 Binky Moon, LLC -gmbh - -// gmo : 2014-01-09 GMO Internet, Inc. -gmo - -// gmx : 2014-04-24 1&1 Mail & Media GmbH -gmx - -// godaddy : 2015-07-23 Go Daddy East, LLC -godaddy - -// gold : 2015-01-22 Binky Moon, LLC -gold - -// goldpoint : 2014-11-20 YODOBASHI CAMERA CO.,LTD. -goldpoint - -// golf : 2014-12-18 Binky Moon, LLC -golf - -// goo : 2014-12-18 NTT Resonant Inc. -goo - -// goodyear : 2015-07-02 The Goodyear Tire & Rubber Company -goodyear - -// goog : 2014-11-20 Charleston Road Registry Inc. -goog - -// google : 2014-07-24 Charleston Road Registry Inc. -google - -// gop : 2014-01-16 Republican State Leadership Committee, Inc. -gop - -// got : 2014-12-18 Amazon Registry Services, Inc. -got - -// grainger : 2015-05-07 Grainger Registry Services, LLC -grainger - -// graphics : 2013-09-13 Binky Moon, LLC -graphics - -// gratis : 2014-03-20 Binky Moon, LLC -gratis - -// green : 2014-05-08 Identity Digital Limited -green - -// gripe : 2014-03-06 Binky Moon, LLC -gripe - -// grocery : 2016-06-16 Wal-Mart Stores, Inc. -grocery - -// group : 2014-08-15 Binky Moon, LLC -group - -// guardian : 2015-07-30 The Guardian Life Insurance Company of America -guardian - -// gucci : 2014-11-13 Guccio Gucci S.p.a. -gucci - -// guge : 2014-08-28 Charleston Road Registry Inc. -guge - -// guide : 2013-09-13 Binky Moon, LLC -guide - -// guitars : 2013-11-14 XYZ.COM LLC -guitars - -// guru : 2013-08-27 Binky Moon, LLC -guru - -// hair : 2015-12-03 XYZ.COM LLC -hair - -// hamburg : 2014-02-20 Hamburg Top-Level-Domain GmbH -hamburg - -// hangout : 2014-11-13 Charleston Road Registry Inc. -hangout - -// haus : 2013-12-05 Dog Beach, LLC -haus - -// hbo : 2015-07-30 HBO Registry Services, Inc. -hbo - -// hdfc : 2015-07-30 HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED -hdfc - -// hdfcbank : 2015-02-12 HDFC Bank Limited -hdfcbank - -// health : 2015-02-11 DotHealth, LLC -health - -// healthcare : 2014-06-12 Binky Moon, LLC -healthcare - -// help : 2014-06-26 Innovation service Limited -help - -// helsinki : 2015-02-05 City of Helsinki -helsinki - -// here : 2014-02-06 Charleston Road Registry Inc. -here - -// hermes : 2014-07-10 HERMES INTERNATIONAL -hermes - -// hgtv : 2015-07-02 Lifestyle Domain Holdings, Inc. -hgtv - -// hiphop : 2014-03-06 Dot Hip Hop, LLC -hiphop - -// hisamitsu : 2015-07-16 Hisamitsu Pharmaceutical Co.,Inc. -hisamitsu - -// hitachi : 2014-10-31 Hitachi, Ltd. -hitachi - -// hiv : 2014-03-13 Internet Naming Company LLC -hiv - -// hkt : 2015-05-14 PCCW-HKT DataCom Services Limited -hkt - -// hockey : 2015-03-19 Binky Moon, LLC -hockey - -// holdings : 2013-08-27 Binky Moon, LLC -holdings - -// holiday : 2013-11-07 Binky Moon, LLC -holiday - -// homedepot : 2015-04-02 Home Depot Product Authority, LLC -homedepot - -// homegoods : 2015-07-16 The TJX Companies, Inc. -homegoods - -// homes : 2014-01-09 XYZ.COM LLC -homes - -// homesense : 2015-07-16 The TJX Companies, Inc. -homesense - -// honda : 2014-12-18 Honda Motor Co., Ltd. -honda - -// horse : 2013-11-21 Registry Services, LLC -horse - -// hospital : 2016-10-20 Binky Moon, LLC -hospital - -// host : 2014-04-17 Radix FZC -host - -// hosting : 2014-05-29 XYZ.COM LLC -hosting - -// hot : 2015-08-27 Amazon Registry Services, Inc. -hot - -// hoteles : 2015-03-05 Travel Reservations SRL -hoteles - -// hotels : 2016-04-07 Booking.com B.V. -hotels - -// hotmail : 2014-12-18 Microsoft Corporation -hotmail - -// house : 2013-11-07 Binky Moon, LLC -house - -// how : 2014-01-23 Charleston Road Registry Inc. -how - -// hsbc : 2014-10-24 HSBC Global Services (UK) Limited -hsbc - -// hughes : 2015-07-30 Hughes Satellite Systems Corporation -hughes - -// hyatt : 2015-07-30 Hyatt GTLD, L.L.C. -hyatt - -// hyundai : 2015-07-09 Hyundai Motor Company -hyundai - -// ibm : 2014-07-31 International Business Machines Corporation -ibm - -// icbc : 2015-02-19 Industrial and Commercial Bank of China Limited -icbc - -// ice : 2014-10-30 IntercontinentalExchange, Inc. -ice - -// icu : 2015-01-08 ShortDot SA -icu - -// ieee : 2015-07-23 IEEE Global LLC -ieee - -// ifm : 2014-01-30 ifm electronic gmbh -ifm - -// ikano : 2015-07-09 Ikano S.A. -ikano - -// imamat : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation) -imamat - -// imdb : 2015-06-25 Amazon Registry Services, Inc. -imdb - -// immo : 2014-07-10 Binky Moon, LLC -immo - -// immobilien : 2013-11-07 Dog Beach, LLC -immobilien - -// inc : 2018-03-10 Intercap Registry Inc. -inc - -// industries : 2013-12-05 Binky Moon, LLC -industries - -// infiniti : 2014-03-27 NISSAN MOTOR CO., LTD. -infiniti - -// ing : 2014-01-23 Charleston Road Registry Inc. -ing - -// ink : 2013-12-05 Top Level Design, LLC -ink - -// institute : 2013-11-07 Binky Moon, LLC -institute - -// insurance : 2015-02-19 fTLD Registry Services LLC -insurance - -// insure : 2014-03-20 Binky Moon, LLC -insure - -// international : 2013-11-07 Binky Moon, LLC -international - -// intuit : 2015-07-30 Intuit Administrative Services, Inc. -intuit - -// investments : 2014-03-20 Binky Moon, LLC -investments - -// ipiranga : 2014-08-28 Ipiranga Produtos de Petroleo S.A. -ipiranga - -// irish : 2014-08-07 Binky Moon, LLC -irish - -// ismaili : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation) -ismaili - -// ist : 2014-08-28 Istanbul Metropolitan Municipality -ist - -// istanbul : 2014-08-28 Istanbul Metropolitan Municipality -istanbul - -// itau : 2014-10-02 Itau Unibanco Holding S.A. -itau - -// itv : 2015-07-09 ITV Services Limited -itv - -// jaguar : 2014-11-13 Jaguar Land Rover Ltd -jaguar - -// java : 2014-06-19 Oracle Corporation -java - -// jcb : 2014-11-20 JCB Co., Ltd. -jcb - -// jeep : 2015-07-30 FCA US LLC. -jeep - -// jetzt : 2014-01-09 Binky Moon, LLC -jetzt - -// jewelry : 2015-03-05 Binky Moon, LLC -jewelry - -// jio : 2015-04-02 Reliance Industries Limited -jio - -// jll : 2015-04-02 Jones Lang LaSalle Incorporated -jll - -// jmp : 2015-03-26 Matrix IP LLC -jmp - -// jnj : 2015-06-18 Johnson & Johnson Services, Inc. -jnj - -// joburg : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry -joburg - -// jot : 2014-12-18 Amazon Registry Services, Inc. -jot - -// joy : 2014-12-18 Amazon Registry Services, Inc. -joy - -// jpmorgan : 2015-04-30 JPMorgan Chase Bank, National Association -jpmorgan - -// jprs : 2014-09-18 Japan Registry Services Co., Ltd. -jprs - -// juegos : 2014-03-20 Internet Naming Company LLC -juegos - -// juniper : 2015-07-30 JUNIPER NETWORKS, INC. -juniper - -// kaufen : 2013-11-07 Dog Beach, LLC -kaufen - -// kddi : 2014-09-12 KDDI CORPORATION -kddi - -// kerryhotels : 2015-04-30 Kerry Trading Co. Limited -kerryhotels - -// kerrylogistics : 2015-04-09 Kerry Trading Co. Limited -kerrylogistics - -// kerryproperties : 2015-04-09 Kerry Trading Co. Limited -kerryproperties - -// kfh : 2014-12-04 Kuwait Finance House -kfh - -// kia : 2015-07-09 KIA MOTORS CORPORATION -kia - -// kids : 2021-08-13 DotKids Foundation Limited -kids - -// kim : 2013-09-23 Identity Digital Limited -kim - -// kinder : 2014-11-07 Ferrero Trading Lux S.A. -kinder - -// kindle : 2015-06-25 Amazon Registry Services, Inc. -kindle - -// kitchen : 2013-09-20 Binky Moon, LLC -kitchen - -// kiwi : 2013-09-20 DOT KIWI LIMITED -kiwi - -// koeln : 2014-01-09 dotKoeln GmbH -koeln - -// komatsu : 2015-01-08 Komatsu Ltd. -komatsu - -// kosher : 2015-08-20 Kosher Marketing Assets LLC -kosher - -// kpmg : 2015-04-23 KPMG International Cooperative (KPMG International Genossenschaft) -kpmg - -// kpn : 2015-01-08 Koninklijke KPN N.V. -kpn - -// krd : 2013-12-05 KRG Department of Information Technology -krd - -// kred : 2013-12-19 KredTLD Pty Ltd -kred - -// kuokgroup : 2015-04-09 Kerry Trading Co. Limited -kuokgroup - -// kyoto : 2014-11-07 Academic Institution: Kyoto Jyoho Gakuen -kyoto - -// lacaixa : 2014-01-09 Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa” -lacaixa - -// lamborghini : 2015-06-04 Automobili Lamborghini S.p.A. -lamborghini - -// lamer : 2015-10-01 The Estée Lauder Companies Inc. -lamer - -// lancaster : 2015-02-12 LANCASTER -lancaster - -// lancia : 2015-07-31 Fiat Chrysler Automobiles N.V. -lancia - -// land : 2013-09-10 Binky Moon, LLC -land - -// landrover : 2014-11-13 Jaguar Land Rover Ltd -landrover - -// lanxess : 2015-07-30 LANXESS Corporation -lanxess - -// lasalle : 2015-04-02 Jones Lang LaSalle Incorporated -lasalle - -// lat : 2014-10-16 XYZ.COM LLC -lat - -// latino : 2015-07-30 Dish DBS Corporation -latino - -// latrobe : 2014-06-16 La Trobe University -latrobe - -// law : 2015-01-22 Registry Services, LLC -law - -// lawyer : 2014-03-20 Dog Beach, LLC -lawyer - -// lds : 2014-03-20 IRI Domain Management, LLC -lds - -// lease : 2014-03-06 Binky Moon, LLC -lease - -// leclerc : 2014-08-07 A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc -leclerc - -// lefrak : 2015-07-16 LeFrak Organization, Inc. -lefrak - -// legal : 2014-10-16 Binky Moon, LLC -legal - -// lego : 2015-07-16 LEGO Juris A/S -lego - -// lexus : 2015-04-23 TOYOTA MOTOR CORPORATION -lexus - -// lgbt : 2014-05-08 Identity Digital Limited -lgbt - -// lidl : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG -lidl - -// life : 2014-02-06 Binky Moon, LLC -life - -// lifeinsurance : 2015-01-15 American Council of Life Insurers -lifeinsurance - -// lifestyle : 2014-12-11 Lifestyle Domain Holdings, Inc. -lifestyle - -// lighting : 2013-08-27 Binky Moon, LLC -lighting - -// like : 2014-12-18 Amazon Registry Services, Inc. -like - -// lilly : 2015-07-31 Eli Lilly and Company -lilly - -// limited : 2014-03-06 Binky Moon, LLC -limited - -// limo : 2013-10-17 Binky Moon, LLC -limo - -// lincoln : 2014-11-13 Ford Motor Company -lincoln - -// link : 2013-11-14 Nova Registry Ltd -link - -// lipsy : 2015-06-25 Lipsy Ltd -lipsy - -// live : 2014-12-04 Dog Beach, LLC -live - -// living : 2015-07-30 Lifestyle Domain Holdings, Inc. -living - -// llc : 2017-12-14 Identity Digital Limited -llc - -// llp : 2019-08-26 Intercap Registry Inc. -llp - -// loan : 2014-11-20 dot Loan Limited -loan - -// loans : 2014-03-20 Binky Moon, LLC -loans - -// locker : 2015-06-04 Dish DBS Corporation -locker - -// locus : 2015-06-25 Locus Analytics LLC -locus - -// lol : 2015-01-30 XYZ.COM LLC -lol - -// london : 2013-11-14 Dot London Domains Limited -london - -// lotte : 2014-11-07 Lotte Holdings Co., Ltd. -lotte - -// lotto : 2014-04-10 Identity Digital Limited -lotto - -// love : 2014-12-22 Merchant Law Group LLP -love - -// lpl : 2015-07-30 LPL Holdings, Inc. -lpl - -// lplfinancial : 2015-07-30 LPL Holdings, Inc. -lplfinancial - -// ltd : 2014-09-25 Binky Moon, LLC -ltd - -// ltda : 2014-04-17 InterNetX, Corp -ltda - -// lundbeck : 2015-08-06 H. Lundbeck A/S -lundbeck - -// luxe : 2014-01-09 Registry Services, LLC -luxe - -// luxury : 2013-10-17 Luxury Partners, LLC -luxury - -// madrid : 2014-05-01 Comunidad de Madrid -madrid - -// maif : 2014-10-02 Mutuelle Assurance Instituteur France (MAIF) -maif - -// maison : 2013-12-05 Binky Moon, LLC -maison - -// makeup : 2015-01-15 XYZ.COM LLC -makeup - -// man : 2014-12-04 MAN SE -man - -// management : 2013-11-07 Binky Moon, LLC -management - -// mango : 2013-10-24 PUNTO FA S.L. -mango - -// map : 2016-06-09 Charleston Road Registry Inc. -map - -// market : 2014-03-06 Dog Beach, LLC -market - -// marketing : 2013-11-07 Binky Moon, LLC -marketing - -// markets : 2014-12-11 Dog Beach, LLC -markets - -// marriott : 2014-10-09 Marriott Worldwide Corporation -marriott - -// marshalls : 2015-07-16 The TJX Companies, Inc. -marshalls - -// maserati : 2015-07-31 Fiat Chrysler Automobiles N.V. -maserati - -// mattel : 2015-08-06 Mattel Sites, Inc. -mattel - -// mba : 2015-04-02 Binky Moon, LLC -mba - -// mckinsey : 2015-07-31 McKinsey Holdings, Inc. -mckinsey - -// med : 2015-08-06 Medistry LLC -med - -// media : 2014-03-06 Binky Moon, LLC -media - -// meet : 2014-01-16 Charleston Road Registry Inc. -meet - -// melbourne : 2014-05-29 The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation -melbourne - -// meme : 2014-01-30 Charleston Road Registry Inc. -meme - -// memorial : 2014-10-16 Dog Beach, LLC -memorial - -// men : 2015-02-26 Exclusive Registry Limited -men - -// menu : 2013-09-11 Dot Menu Registry, LLC -menu - -// merckmsd : 2016-07-14 MSD Registry Holdings, Inc. -merckmsd - -// miami : 2013-12-19 Registry Services, LLC -miami - -// microsoft : 2014-12-18 Microsoft Corporation -microsoft - -// mini : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft -mini - -// mint : 2015-07-30 Intuit Administrative Services, Inc. -mint - -// mit : 2015-07-02 Massachusetts Institute of Technology -mit - -// mitsubishi : 2015-07-23 Mitsubishi Corporation -mitsubishi - -// mlb : 2015-05-21 MLB Advanced Media DH, LLC -mlb - -// mls : 2015-04-23 The Canadian Real Estate Association -mls - -// mma : 2014-11-07 MMA IARD -mma - -// mobile : 2016-06-02 Dish DBS Corporation -mobile - -// moda : 2013-11-07 Dog Beach, LLC -moda - -// moe : 2013-11-13 Interlink Systems Innovation Institute K.K. -moe - -// moi : 2014-12-18 Amazon Registry Services, Inc. -moi - -// mom : 2015-04-16 XYZ.COM LLC -mom - -// monash : 2013-09-30 Monash University -monash - -// money : 2014-10-16 Binky Moon, LLC -money - -// monster : 2015-09-11 XYZ.COM LLC -monster - -// mormon : 2013-12-05 IRI Domain Management, LLC -mormon - -// mortgage : 2014-03-20 Dog Beach, LLC -mortgage - -// moscow : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) -moscow - -// moto : 2015-06-04 Motorola Trademark Holdings, LLC -moto - -// motorcycles : 2014-01-09 XYZ.COM LLC -motorcycles - -// mov : 2014-01-30 Charleston Road Registry Inc. -mov - -// movie : 2015-02-05 Binky Moon, LLC -movie - -// msd : 2015-07-23 MSD Registry Holdings, Inc. -msd - -// mtn : 2014-12-04 MTN Dubai Limited -mtn - -// mtr : 2015-03-12 MTR Corporation Limited -mtr - -// music : 2021-05-04 DotMusic Limited -music - -// mutual : 2015-04-02 Northwestern Mutual MU TLD Registry, LLC -mutual - -// nab : 2015-08-20 National Australia Bank Limited -nab - -// nagoya : 2013-10-24 GMO Registry, Inc. -nagoya - -// natura : 2015-03-12 NATURA COSMÉTICOS S.A. -natura - -// navy : 2014-03-06 Dog Beach, LLC -navy - -// nba : 2015-07-31 NBA REGISTRY, LLC -nba - -// nec : 2015-01-08 NEC Corporation -nec - -// netbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA -netbank - -// netflix : 2015-06-18 Netflix, Inc. -netflix - -// network : 2013-11-14 Binky Moon, LLC -network - -// neustar : 2013-12-05 NeuStar, Inc. -neustar - -// new : 2014-01-30 Charleston Road Registry Inc. -new - -// news : 2014-12-18 Dog Beach, LLC -news - -// next : 2015-06-18 Next plc -next - -// nextdirect : 2015-06-18 Next plc -nextdirect - -// nexus : 2014-07-24 Charleston Road Registry Inc. -nexus - -// nfl : 2015-07-23 NFL Reg Ops LLC -nfl - -// ngo : 2014-03-06 Public Interest Registry -ngo - -// nhk : 2014-02-13 Japan Broadcasting Corporation (NHK) -nhk - -// nico : 2014-12-04 DWANGO Co., Ltd. -nico - -// nike : 2015-07-23 NIKE, Inc. -nike - -// nikon : 2015-05-21 NIKON CORPORATION -nikon - -// ninja : 2013-11-07 Dog Beach, LLC -ninja - -// nissan : 2014-03-27 NISSAN MOTOR CO., LTD. -nissan - -// nissay : 2015-10-29 Nippon Life Insurance Company -nissay - -// nokia : 2015-01-08 Nokia Corporation -nokia - -// northwesternmutual : 2015-06-18 Northwestern Mutual Registry, LLC -northwesternmutual - -// norton : 2014-12-04 NortonLifeLock Inc. -norton - -// now : 2015-06-25 Amazon Registry Services, Inc. -now - -// nowruz : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. -nowruz - -// nowtv : 2015-05-14 Starbucks (HK) Limited -nowtv - -// nra : 2014-05-22 NRA Holdings Company, INC. -nra - -// nrw : 2013-11-21 Minds + Machines GmbH -nrw - -// ntt : 2014-10-31 NIPPON TELEGRAPH AND TELEPHONE CORPORATION -ntt - -// nyc : 2014-01-23 The City of New York by and through the New York City Department of Information Technology & Telecommunications -nyc - -// obi : 2014-09-25 OBI Group Holding SE & Co. KGaA -obi - -// observer : 2015-04-30 Dog Beach, LLC -observer - -// office : 2015-03-12 Microsoft Corporation -office - -// okinawa : 2013-12-05 BRregistry, Inc. -okinawa - -// olayan : 2015-05-14 Crescent Holding GmbH -olayan - -// olayangroup : 2015-05-14 Crescent Holding GmbH -olayangroup - -// oldnavy : 2015-07-31 The Gap, Inc. -oldnavy - -// ollo : 2015-06-04 Dish DBS Corporation -ollo - -// omega : 2015-01-08 The Swatch Group Ltd -omega - -// one : 2014-11-07 One.com A/S -one - -// ong : 2014-03-06 Public Interest Registry -ong - -// onl : 2013-09-16 iRegistry GmbH -onl - -// online : 2015-01-15 Radix FZC -online - -// ooo : 2014-01-09 INFIBEAM AVENUES LIMITED -ooo - -// open : 2015-07-31 American Express Travel Related Services Company, Inc. -open - -// oracle : 2014-06-19 Oracle Corporation -oracle - -// orange : 2015-03-12 Orange Brand Services Limited -orange - -// organic : 2014-03-27 Identity Digital Limited -organic - -// origins : 2015-10-01 The Estée Lauder Companies Inc. -origins - -// osaka : 2014-09-04 Osaka Registry Co., Ltd. -osaka - -// otsuka : 2013-10-11 Otsuka Holdings Co., Ltd. -otsuka - -// ott : 2015-06-04 Dish DBS Corporation -ott - -// ovh : 2014-01-16 MédiaBC -ovh - -// page : 2014-12-04 Charleston Road Registry Inc. -page - -// panasonic : 2015-07-30 Panasonic Corporation -panasonic - -// paris : 2014-01-30 City of Paris -paris - -// pars : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. -pars - -// partners : 2013-12-05 Binky Moon, LLC -partners - -// parts : 2013-12-05 Binky Moon, LLC -parts - -// party : 2014-09-11 Blue Sky Registry Limited -party - -// passagens : 2015-03-05 Travel Reservations SRL -passagens - -// pay : 2015-08-27 Amazon Registry Services, Inc. -pay - -// pccw : 2015-05-14 PCCW Enterprises Limited -pccw - -// pet : 2015-05-07 Identity Digital Limited -pet - -// pfizer : 2015-09-11 Pfizer Inc. -pfizer - -// pharmacy : 2014-06-19 National Association of Boards of Pharmacy -pharmacy - -// phd : 2016-07-28 Charleston Road Registry Inc. -phd - -// philips : 2014-11-07 Koninklijke Philips N.V. -philips - -// phone : 2016-06-02 Dish DBS Corporation -phone - -// photo : 2013-11-14 Registry Services, LLC -photo - -// photography : 2013-09-20 Binky Moon, LLC -photography - -// photos : 2013-10-17 Binky Moon, LLC -photos - -// physio : 2014-05-01 PhysBiz Pty Ltd -physio - -// pics : 2013-11-14 XYZ.COM LLC -pics - -// pictet : 2014-06-26 Pictet Europe S.A. -pictet - -// pictures : 2014-03-06 Binky Moon, LLC -pictures - -// pid : 2015-01-08 Top Level Spectrum, Inc. -pid - -// pin : 2014-12-18 Amazon Registry Services, Inc. -pin - -// ping : 2015-06-11 Ping Registry Provider, Inc. -ping - -// pink : 2013-10-01 Identity Digital Limited -pink - -// pioneer : 2015-07-16 Pioneer Corporation -pioneer - -// pizza : 2014-06-26 Binky Moon, LLC -pizza - -// place : 2014-04-24 Binky Moon, LLC -place - -// play : 2015-03-05 Charleston Road Registry Inc. -play - -// playstation : 2015-07-02 Sony Interactive Entertainment Inc. -playstation - -// plumbing : 2013-09-10 Binky Moon, LLC -plumbing - -// plus : 2015-02-05 Binky Moon, LLC -plus - -// pnc : 2015-07-02 PNC Domain Co., LLC -pnc - -// pohl : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG -pohl - -// poker : 2014-07-03 Identity Digital Limited -poker - -// politie : 2015-08-20 Politie Nederland -politie - -// porn : 2014-10-16 ICM Registry PN LLC -porn - -// pramerica : 2015-07-30 Prudential Financial, Inc. -pramerica - -// praxi : 2013-12-05 Praxi S.p.A. -praxi - -// press : 2014-04-03 Radix FZC -press - -// prime : 2015-06-25 Amazon Registry Services, Inc. -prime - -// prod : 2014-01-23 Charleston Road Registry Inc. -prod - -// productions : 2013-12-05 Binky Moon, LLC -productions - -// prof : 2014-07-24 Charleston Road Registry Inc. -prof - -// progressive : 2015-07-23 Progressive Casualty Insurance Company -progressive - -// promo : 2014-12-18 Identity Digital Limited -promo - -// properties : 2013-12-05 Binky Moon, LLC -properties - -// property : 2014-05-22 Internet Naming Company LLC -property - -// protection : 2015-04-23 XYZ.COM LLC -protection - -// pru : 2015-07-30 Prudential Financial, Inc. -pru - -// prudential : 2015-07-30 Prudential Financial, Inc. -prudential - -// pub : 2013-12-12 Dog Beach, LLC -pub - -// pwc : 2015-10-29 PricewaterhouseCoopers LLP -pwc - -// qpon : 2013-11-14 dotQPON LLC -qpon - -// quebec : 2013-12-19 PointQuébec Inc -quebec - -// quest : 2015-03-26 XYZ.COM LLC -quest - -// racing : 2014-12-04 Premier Registry Limited -racing - -// radio : 2016-07-21 European Broadcasting Union (EBU) -radio - -// read : 2014-12-18 Amazon Registry Services, Inc. -read - -// realestate : 2015-09-11 dotRealEstate LLC -realestate - -// realtor : 2014-05-29 Real Estate Domains LLC -realtor - -// realty : 2015-03-19 Dog Beach, LLC -realty - -// recipes : 2013-10-17 Binky Moon, LLC -recipes - -// red : 2013-11-07 Identity Digital Limited -red - -// redstone : 2014-10-31 Redstone Haute Couture Co., Ltd. -redstone - -// redumbrella : 2015-03-26 Travelers TLD, LLC -redumbrella - -// rehab : 2014-03-06 Dog Beach, LLC -rehab - -// reise : 2014-03-13 Binky Moon, LLC -reise - -// reisen : 2014-03-06 Binky Moon, LLC -reisen - -// reit : 2014-09-04 National Association of Real Estate Investment Trusts, Inc. -reit - -// reliance : 2015-04-02 Reliance Industries Limited -reliance - -// ren : 2013-12-12 ZDNS International Limited -ren - -// rent : 2014-12-04 XYZ.COM LLC -rent - -// rentals : 2013-12-05 Binky Moon, LLC -rentals - -// repair : 2013-11-07 Binky Moon, LLC -repair - -// report : 2013-12-05 Binky Moon, LLC -report - -// republican : 2014-03-20 Dog Beach, LLC -republican - -// rest : 2013-12-19 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable -rest - -// restaurant : 2014-07-03 Binky Moon, LLC -restaurant - -// review : 2014-11-20 dot Review Limited -review - -// reviews : 2013-09-13 Dog Beach, LLC -reviews - -// rexroth : 2015-06-18 Robert Bosch GMBH -rexroth - -// rich : 2013-11-21 iRegistry GmbH -rich - -// richardli : 2015-05-14 Pacific Century Asset Management (HK) Limited -richardli - -// ricoh : 2014-11-20 Ricoh Company, Ltd. -ricoh - -// ril : 2015-04-02 Reliance Industries Limited -ril - -// rio : 2014-02-27 Empresa Municipal de Informática SA - IPLANRIO -rio - -// rip : 2014-07-10 Dog Beach, LLC -rip - -// rocher : 2014-12-18 Ferrero Trading Lux S.A. -rocher - -// rocks : 2013-11-14 Dog Beach, LLC -rocks - -// rodeo : 2013-12-19 Registry Services, LLC -rodeo - -// rogers : 2015-08-06 Rogers Communications Canada Inc. -rogers - -// room : 2014-12-18 Amazon Registry Services, Inc. -room - -// rsvp : 2014-05-08 Charleston Road Registry Inc. -rsvp - -// rugby : 2016-12-15 World Rugby Strategic Developments Limited -rugby - -// ruhr : 2013-10-02 dotSaarland GmbH -ruhr - -// run : 2015-03-19 Binky Moon, LLC -run - -// rwe : 2015-04-02 RWE AG -rwe - -// ryukyu : 2014-01-09 BRregistry, Inc. -ryukyu - -// saarland : 2013-12-12 dotSaarland GmbH -saarland - -// safe : 2014-12-18 Amazon Registry Services, Inc. -safe - -// safety : 2015-01-08 Safety Registry Services, LLC. -safety - -// sakura : 2014-12-18 SAKURA Internet Inc. -sakura - -// sale : 2014-10-16 Dog Beach, LLC -sale - -// salon : 2014-12-11 Binky Moon, LLC -salon - -// samsclub : 2015-07-31 Wal-Mart Stores, Inc. -samsclub - -// samsung : 2014-04-03 SAMSUNG SDS CO., LTD -samsung - -// sandvik : 2014-11-13 Sandvik AB -sandvik - -// sandvikcoromant : 2014-11-07 Sandvik AB -sandvikcoromant - -// sanofi : 2014-10-09 Sanofi -sanofi - -// sap : 2014-03-27 SAP AG -sap - -// sarl : 2014-07-03 Binky Moon, LLC -sarl - -// sas : 2015-04-02 Research IP LLC -sas - -// save : 2015-06-25 Amazon Registry Services, Inc. -save - -// saxo : 2014-10-31 Saxo Bank A/S -saxo - -// sbi : 2015-03-12 STATE BANK OF INDIA -sbi - -// sbs : 2014-11-07 ShortDot SA -sbs - -// sca : 2014-03-13 SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) -sca - -// scb : 2014-02-20 The Siam Commercial Bank Public Company Limited ("SCB") -scb - -// schaeffler : 2015-08-06 Schaeffler Technologies AG & Co. KG -schaeffler - -// schmidt : 2014-04-03 SCHMIDT GROUPE S.A.S. -schmidt - -// scholarships : 2014-04-24 Scholarships.com, LLC -scholarships - -// school : 2014-12-18 Binky Moon, LLC -school - -// schule : 2014-03-06 Binky Moon, LLC -schule - -// schwarz : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG -schwarz - -// science : 2014-09-11 dot Science Limited -science - -// scot : 2014-01-23 Dot Scot Registry Limited -scot - -// search : 2016-06-09 Charleston Road Registry Inc. -search - -// seat : 2014-05-22 SEAT, S.A. (Sociedad Unipersonal) -seat - -// secure : 2015-08-27 Amazon Registry Services, Inc. -secure - -// security : 2015-05-14 XYZ.COM LLC -security - -// seek : 2014-12-04 Seek Limited -seek - -// select : 2015-10-08 Registry Services, LLC -select - -// sener : 2014-10-24 Sener Ingeniería y Sistemas, S.A. -sener - -// services : 2014-02-27 Binky Moon, LLC -services - -// seven : 2015-08-06 Seven West Media Ltd -seven - -// sew : 2014-07-17 SEW-EURODRIVE GmbH & Co KG -sew - -// sex : 2014-11-13 ICM Registry SX LLC -sex - -// sexy : 2013-09-11 Internet Naming Company LLC -sexy - -// sfr : 2015-08-13 Societe Francaise du Radiotelephone - SFR -sfr - -// shangrila : 2015-09-03 Shangri‐La International Hotel Management Limited -shangrila - -// sharp : 2014-05-01 Sharp Corporation -sharp - -// shaw : 2015-04-23 Shaw Cablesystems G.P. -shaw - -// shell : 2015-07-30 Shell Information Technology International Inc -shell - -// shia : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. -shia - -// shiksha : 2013-11-14 Identity Digital Limited -shiksha - -// shoes : 2013-10-02 Binky Moon, LLC -shoes - -// shop : 2016-04-08 GMO Registry, Inc. -shop - -// shopping : 2016-03-31 Binky Moon, LLC -shopping - -// shouji : 2015-01-08 Beijing Qihu Keji Co., Ltd. -shouji - -// show : 2015-03-05 Binky Moon, LLC -show - -// showtime : 2015-08-06 CBS Domains Inc. -showtime - -// silk : 2015-06-25 Amazon Registry Services, Inc. -silk - -// sina : 2015-03-12 Sina Corporation -sina - -// singles : 2013-08-27 Binky Moon, LLC -singles - -// site : 2015-01-15 Radix FZC -site - -// ski : 2015-04-09 Identity Digital Limited -ski - -// skin : 2015-01-15 XYZ.COM LLC -skin - -// sky : 2014-06-19 Sky International AG -sky - -// skype : 2014-12-18 Microsoft Corporation -skype - -// sling : 2015-07-30 DISH Technologies L.L.C. -sling - -// smart : 2015-07-09 Smart Communications, Inc. (SMART) -smart - -// smile : 2014-12-18 Amazon Registry Services, Inc. -smile - -// sncf : 2015-02-19 Société Nationale SNCF -sncf - -// soccer : 2015-03-26 Binky Moon, LLC -soccer - -// social : 2013-11-07 Dog Beach, LLC -social - -// softbank : 2015-07-02 SoftBank Group Corp. -softbank - -// software : 2014-03-20 Dog Beach, LLC -software - -// sohu : 2013-12-19 Sohu.com Limited -sohu - -// solar : 2013-11-07 Binky Moon, LLC -solar - -// solutions : 2013-11-07 Binky Moon, LLC -solutions - -// song : 2015-02-26 Amazon Registry Services, Inc. -song - -// sony : 2015-01-08 Sony Corporation -sony - -// soy : 2014-01-23 Charleston Road Registry Inc. -soy - -// spa : 2019-09-19 Asia Spa and Wellness Promotion Council Limited -spa - -// space : 2014-04-03 Radix FZC -space - -// sport : 2017-11-16 Global Association of International Sports Federations (GAISF) -sport - -// spot : 2015-02-26 Amazon Registry Services, Inc. -spot - -// srl : 2015-05-07 InterNetX, Corp -srl - -// stada : 2014-11-13 STADA Arzneimittel AG -stada - -// staples : 2015-07-30 Staples, Inc. -staples - -// star : 2015-01-08 Star India Private Limited -star - -// statebank : 2015-03-12 STATE BANK OF INDIA -statebank - -// statefarm : 2015-07-30 State Farm Mutual Automobile Insurance Company -statefarm - -// stc : 2014-10-09 Saudi Telecom Company -stc - -// stcgroup : 2014-10-09 Saudi Telecom Company -stcgroup - -// stockholm : 2014-12-18 Stockholms kommun -stockholm - -// storage : 2014-12-22 XYZ.COM LLC -storage - -// store : 2015-04-09 Radix FZC -store - -// stream : 2016-01-08 dot Stream Limited -stream - -// studio : 2015-02-11 Dog Beach, LLC -studio - -// study : 2014-12-11 Registry Services, LLC -study - -// style : 2014-12-04 Binky Moon, LLC -style - -// sucks : 2014-12-22 Vox Populi Registry Ltd. -sucks - -// supplies : 2013-12-19 Binky Moon, LLC -supplies - -// supply : 2013-12-19 Binky Moon, LLC -supply - -// support : 2013-10-24 Binky Moon, LLC -support - -// surf : 2014-01-09 Registry Services, LLC -surf - -// surgery : 2014-03-20 Binky Moon, LLC -surgery - -// suzuki : 2014-02-20 SUZUKI MOTOR CORPORATION -suzuki - -// swatch : 2015-01-08 The Swatch Group Ltd -swatch - -// swiss : 2014-10-16 Swiss Confederation -swiss - -// sydney : 2014-09-18 State of New South Wales, Department of Premier and Cabinet -sydney - -// systems : 2013-11-07 Binky Moon, LLC -systems - -// tab : 2014-12-04 Tabcorp Holdings Limited -tab - -// taipei : 2014-07-10 Taipei City Government -taipei - -// talk : 2015-04-09 Amazon Registry Services, Inc. -talk - -// taobao : 2015-01-15 Alibaba Group Holding Limited -taobao - -// target : 2015-07-31 Target Domain Holdings, LLC -target - -// tatamotors : 2015-03-12 Tata Motors Ltd -tatamotors - -// tatar : 2014-04-24 Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" -tatar - -// tattoo : 2013-08-30 Top Level Design, LLC -tattoo - -// tax : 2014-03-20 Binky Moon, LLC -tax - -// taxi : 2015-03-19 Binky Moon, LLC -taxi - -// tci : 2014-09-12 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. -tci - -// tdk : 2015-06-11 TDK Corporation -tdk - -// team : 2015-03-05 Binky Moon, LLC -team - -// tech : 2015-01-30 Radix FZC -tech - -// technology : 2013-09-13 Binky Moon, LLC -technology - -// temasek : 2014-08-07 Temasek Holdings (Private) Limited -temasek - -// tennis : 2014-12-04 Binky Moon, LLC -tennis - -// teva : 2015-07-02 Teva Pharmaceutical Industries Limited -teva - -// thd : 2015-04-02 Home Depot Product Authority, LLC -thd - -// theater : 2015-03-19 Binky Moon, LLC -theater - -// theatre : 2015-05-07 XYZ.COM LLC -theatre - -// tiaa : 2015-07-23 Teachers Insurance and Annuity Association of America -tiaa - -// tickets : 2015-02-05 XYZ.COM LLC -tickets - -// tienda : 2013-11-14 Binky Moon, LLC -tienda - -// tiffany : 2015-01-30 Tiffany and Company -tiffany - -// tips : 2013-09-20 Binky Moon, LLC -tips - -// tires : 2014-11-07 Binky Moon, LLC -tires - -// tirol : 2014-04-24 punkt Tirol GmbH -tirol - -// tjmaxx : 2015-07-16 The TJX Companies, Inc. -tjmaxx - -// tjx : 2015-07-16 The TJX Companies, Inc. -tjx - -// tkmaxx : 2015-07-16 The TJX Companies, Inc. -tkmaxx - -// tmall : 2015-01-15 Alibaba Group Holding Limited -tmall - -// today : 2013-09-20 Binky Moon, LLC -today - -// tokyo : 2013-11-13 GMO Registry, Inc. -tokyo - -// tools : 2013-11-21 Binky Moon, LLC -tools - -// top : 2014-03-20 .TOP Registry -top - -// toray : 2014-12-18 Toray Industries, Inc. -toray - -// toshiba : 2014-04-10 TOSHIBA Corporation -toshiba - -// total : 2015-08-06 TotalEnergies SE -total - -// tours : 2015-01-22 Binky Moon, LLC -tours - -// town : 2014-03-06 Binky Moon, LLC -town - -// toyota : 2015-04-23 TOYOTA MOTOR CORPORATION -toyota - -// toys : 2014-03-06 Binky Moon, LLC -toys - -// trade : 2014-01-23 Elite Registry Limited -trade - -// trading : 2014-12-11 Dog Beach, LLC -trading - -// training : 2013-11-07 Binky Moon, LLC -training - -// travel : 2015-10-09 Dog Beach, LLC -travel - -// travelchannel : 2015-07-02 Lifestyle Domain Holdings, Inc. -travelchannel - -// travelers : 2015-03-26 Travelers TLD, LLC -travelers - -// travelersinsurance : 2015-03-26 Travelers TLD, LLC -travelersinsurance - -// trust : 2014-10-16 Internet Naming Company LLC -trust - -// trv : 2015-03-26 Travelers TLD, LLC -trv - -// tube : 2015-06-11 Latin American Telecom LLC -tube - -// tui : 2014-07-03 TUI AG -tui - -// tunes : 2015-02-26 Amazon Registry Services, Inc. -tunes - -// tushu : 2014-12-18 Amazon Registry Services, Inc. -tushu - -// tvs : 2015-02-19 T V SUNDRAM IYENGAR & SONS LIMITED -tvs - -// ubank : 2015-08-20 National Australia Bank Limited -ubank - -// ubs : 2014-12-11 UBS AG -ubs - -// unicom : 2015-10-15 China United Network Communications Corporation Limited -unicom - -// university : 2014-03-06 Binky Moon, LLC -university - -// uno : 2013-09-11 Radix FZC -uno - -// uol : 2014-05-01 UBN INTERNET LTDA. -uol - -// ups : 2015-06-25 UPS Market Driver, Inc. -ups - -// vacations : 2013-12-05 Binky Moon, LLC -vacations - -// vana : 2014-12-11 Lifestyle Domain Holdings, Inc. -vana - -// vanguard : 2015-09-03 The Vanguard Group, Inc. -vanguard - -// vegas : 2014-01-16 Dot Vegas, Inc. -vegas - -// ventures : 2013-08-27 Binky Moon, LLC -ventures - -// verisign : 2015-08-13 VeriSign, Inc. -verisign - -// versicherung : 2014-03-20 tldbox GmbH -versicherung - -// vet : 2014-03-06 Dog Beach, LLC -vet - -// viajes : 2013-10-17 Binky Moon, LLC -viajes - -// video : 2014-10-16 Dog Beach, LLC -video - -// vig : 2015-05-14 VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe -vig - -// viking : 2015-04-02 Viking River Cruises (Bermuda) Ltd. -viking - -// villas : 2013-12-05 Binky Moon, LLC -villas - -// vin : 2015-06-18 Binky Moon, LLC -vin - -// vip : 2015-01-22 Registry Services, LLC -vip - -// virgin : 2014-09-25 Virgin Enterprises Limited -virgin - -// visa : 2015-07-30 Visa Worldwide Pte. Limited -visa - -// vision : 2013-12-05 Binky Moon, LLC -vision - -// viva : 2014-11-07 Saudi Telecom Company -viva - -// vivo : 2015-07-31 Telefonica Brasil S.A. -vivo - -// vlaanderen : 2014-02-06 DNS.be vzw -vlaanderen - -// vodka : 2013-12-19 Registry Services, LLC -vodka - -// volkswagen : 2015-05-14 Volkswagen Group of America Inc. -volkswagen - -// volvo : 2015-11-12 Volvo Holding Sverige Aktiebolag -volvo - -// vote : 2013-11-21 Monolith Registry LLC -vote - -// voting : 2013-11-13 Valuetainment Corp. -voting - -// voto : 2013-11-21 Monolith Registry LLC -voto - -// voyage : 2013-08-27 Binky Moon, LLC -voyage - -// vuelos : 2015-03-05 Travel Reservations SRL -vuelos - -// wales : 2014-05-08 Nominet UK -wales - -// walmart : 2015-07-31 Wal-Mart Stores, Inc. -walmart - -// walter : 2014-11-13 Sandvik AB -walter - -// wang : 2013-10-24 Zodiac Wang Limited -wang - -// wanggou : 2014-12-18 Amazon Registry Services, Inc. -wanggou - -// watch : 2013-11-14 Binky Moon, LLC -watch - -// watches : 2014-12-22 Identity Digital Limited -watches - -// weather : 2015-01-08 International Business Machines Corporation -weather - -// weatherchannel : 2015-03-12 International Business Machines Corporation -weatherchannel - -// webcam : 2014-01-23 dot Webcam Limited -webcam - -// weber : 2015-06-04 Saint-Gobain Weber SA -weber - -// website : 2014-04-03 Radix FZC -website - -// wedding : 2014-04-24 Registry Services, LLC -wedding - -// weibo : 2015-03-05 Sina Corporation -weibo - -// weir : 2015-01-29 Weir Group IP Limited -weir - -// whoswho : 2014-02-20 Who's Who Registry -whoswho - -// wien : 2013-10-28 punkt.wien GmbH -wien - -// wiki : 2013-11-07 Top Level Design, LLC -wiki - -// williamhill : 2014-03-13 William Hill Organization Limited -williamhill - -// win : 2014-11-20 First Registry Limited -win - -// windows : 2014-12-18 Microsoft Corporation -windows - -// wine : 2015-06-18 Binky Moon, LLC -wine - -// winners : 2015-07-16 The TJX Companies, Inc. -winners - -// wme : 2014-02-13 William Morris Endeavor Entertainment, LLC -wme - -// wolterskluwer : 2015-08-06 Wolters Kluwer N.V. -wolterskluwer - -// woodside : 2015-07-09 Woodside Petroleum Limited -woodside - -// work : 2013-12-19 Registry Services, LLC -work - -// works : 2013-11-14 Binky Moon, LLC -works - -// world : 2014-06-12 Binky Moon, LLC -world - -// wow : 2015-10-08 Amazon Registry Services, Inc. -wow - -// wtc : 2013-12-19 World Trade Centers Association, Inc. -wtc - -// wtf : 2014-03-06 Binky Moon, LLC -wtf - -// xbox : 2014-12-18 Microsoft Corporation -xbox - -// xerox : 2014-10-24 Xerox DNHC LLC -xerox - -// xfinity : 2015-07-09 Comcast IP Holdings I, LLC -xfinity - -// xihuan : 2015-01-08 Beijing Qihu Keji Co., Ltd. -xihuan - -// xin : 2014-12-11 Elegant Leader Limited -xin - -// xn--11b4c3d : 2015-01-15 VeriSign Sarl -कॉम - -// xn--1ck2e1b : 2015-02-26 Amazon Registry Services, Inc. -セール - -// xn--1qqw23a : 2014-01-09 Guangzhou YU Wei Information Technology Co., Ltd. -佛山 - -// xn--30rr7y : 2014-06-12 Excellent First Limited -慈善 - -// xn--3bst00m : 2013-09-13 Eagle Horizon Limited -集团 - -// xn--3ds443g : 2013-09-08 TLD REGISTRY LIMITED OY -在线 - -// xn--3pxu8k : 2015-01-15 VeriSign Sarl -点看 - -// xn--42c2d9a : 2015-01-15 VeriSign Sarl -คอม - -// xn--45q11c : 2013-11-21 Zodiac Gemini Ltd -八卦 - -// xn--4gbrim : 2013-10-04 Helium TLDs Ltd -موقع - -// xn--55qw42g : 2013-11-08 China Organizational Name Administration Center -公益 - -// xn--55qx5d : 2013-11-14 China Internet Network Information Center (CNNIC) -公司 - -// xn--5su34j936bgsg : 2015-09-03 Shangri‐La International Hotel Management Limited -香格里拉 - -// xn--5tzm5g : 2014-12-22 Global Website TLD Asia Limited -网站 - -// xn--6frz82g : 2013-09-23 Identity Digital Limited -移动 - -// xn--6qq986b3xl : 2013-09-13 Tycoon Treasure Limited -我爱你 - -// xn--80adxhks : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) -москва - -// xn--80aqecdr1a : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) -католик - -// xn--80asehdb : 2013-07-14 CORE Association -онлайн - -// xn--80aswg : 2013-07-14 CORE Association -сайт - -// xn--8y0a063a : 2015-03-26 China United Network Communications Corporation Limited -联通 - -// xn--9dbq2a : 2015-01-15 VeriSign Sarl -קום - -// xn--9et52u : 2014-06-12 RISE VICTORY LIMITED -时尚 - -// xn--9krt00a : 2015-03-12 Sina Corporation -微博 - -// xn--b4w605ferd : 2014-08-07 Temasek Holdings (Private) Limited -淡马锡 - -// xn--bck1b9a5dre4c : 2015-02-26 Amazon Registry Services, Inc. -ファッション - -// xn--c1avg : 2013-11-14 Public Interest Registry -орг - -// xn--c2br7g : 2015-01-15 VeriSign Sarl -नेट - -// xn--cck2b3b : 2015-02-26 Amazon Registry Services, Inc. -ストア - -// xn--cckwcxetd : 2019-12-19 Amazon Registry Services, Inc. -アマゾン - -// xn--cg4bki : 2013-09-27 SAMSUNG SDS CO., LTD -삼성 - -// xn--czr694b : 2014-01-16 Internet DotTrademark Organisation Limited -商标 - -// xn--czrs0t : 2013-12-19 Binky Moon, LLC -商店 - -// xn--czru2d : 2013-11-21 Zodiac Aquarius Limited -商城 - -// xn--d1acj3b : 2013-11-20 The Foundation for Network Initiatives “The Smart Internet” -дети - -// xn--eckvdtc9d : 2014-12-18 Amazon Registry Services, Inc. -ポイント - -// xn--efvy88h : 2014-08-22 Guangzhou YU Wei Information Technology Co., Ltd. -新闻 - -// xn--fct429k : 2015-04-09 Amazon Registry Services, Inc. -家電 - -// xn--fhbei : 2015-01-15 VeriSign Sarl -كوم - -// xn--fiq228c5hs : 2013-09-08 TLD REGISTRY LIMITED OY -中文网 - -// xn--fiq64b : 2013-10-14 CITIC Group Corporation -中信 - -// xn--fjq720a : 2014-05-22 Binky Moon, LLC -娱乐 - -// xn--flw351e : 2014-07-31 Charleston Road Registry Inc. -谷歌 - -// xn--fzys8d69uvgm : 2015-05-14 PCCW Enterprises Limited -電訊盈科 - -// xn--g2xx48c : 2015-01-30 Nawang Heli(Xiamen) Network Service Co., LTD. -购物 - -// xn--gckr3f0f : 2015-02-26 Amazon Registry Services, Inc. -クラウド - -// xn--gk3at1e : 2015-10-08 Amazon Registry Services, Inc. -通販 - -// xn--hxt814e : 2014-05-15 Zodiac Taurus Limited -网店 - -// xn--i1b6b1a6a2e : 2013-11-14 Public Interest Registry -संगठन - -// xn--imr513n : 2014-12-11 Internet DotTrademark Organisation Limited -餐厅 - -// xn--io0a7i : 2013-11-14 China Internet Network Information Center (CNNIC) -网络 - -// xn--j1aef : 2015-01-15 VeriSign Sarl -ком - -// xn--jlq480n2rg : 2019-12-19 Amazon Registry Services, Inc. -亚马逊 - -// xn--jvr189m : 2015-02-26 Amazon Registry Services, Inc. -食品 - -// xn--kcrx77d1x4a : 2014-11-07 Koninklijke Philips N.V. -飞利浦 - -// xn--kput3i : 2014-02-13 Beijing RITT-Net Technology Development Co., Ltd -手机 - -// xn--mgba3a3ejt : 2014-11-20 Aramco Services Company -ارامكو - -// xn--mgba7c0bbn0a : 2015-05-14 Crescent Holding GmbH -العليان - -// xn--mgbaakc7dvf : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat) -اتصالات - -// xn--mgbab2bd : 2013-10-31 CORE Association -بازار - -// xn--mgbca7dzdo : 2015-07-30 Abu Dhabi Systems and Information Centre -ابوظبي - -// xn--mgbi4ecexp : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) -كاثوليك - -// xn--mgbt3dhd : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. -همراه - -// xn--mk1bu44c : 2015-01-15 VeriSign Sarl -닷컴 - -// xn--mxtq1m : 2014-03-06 Net-Chinese Co., Ltd. -政府 - -// xn--ngbc5azd : 2013-07-13 International Domain Registry Pty. Ltd. -شبكة - -// xn--ngbe9e0a : 2014-12-04 Kuwait Finance House -بيتك - -// xn--ngbrx : 2015-11-12 League of Arab States -عرب - -// xn--nqv7f : 2013-11-14 Public Interest Registry -机构 - -// xn--nqv7fs00ema : 2013-11-14 Public Interest Registry -组织机构 - -// xn--nyqy26a : 2014-11-07 Stable Tone Limited -健康 - -// xn--otu796d : 2017-08-06 Jiang Yu Liang Cai Technology Company Limited -招聘 - -// xn--p1acf : 2013-12-12 Rusnames Limited -рус - -// xn--pssy2u : 2015-01-15 VeriSign Sarl -大拿 - -// xn--q9jyb4c : 2013-09-17 Charleston Road Registry Inc. -みんな - -// xn--qcka1pmc : 2014-07-31 Charleston Road Registry Inc. -グーグル - -// xn--rhqv96g : 2013-09-11 Stable Tone Limited -世界 - -// xn--rovu88b : 2015-02-26 Amazon Registry Services, Inc. -書籍 - -// xn--ses554g : 2014-01-16 KNET Co., Ltd. -网址 - -// xn--t60b56a : 2015-01-15 VeriSign Sarl -닷넷 - -// xn--tckwe : 2015-01-15 VeriSign Sarl -コム - -// xn--tiq49xqyj : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) -天主教 - -// xn--unup4y : 2013-07-14 Binky Moon, LLC -游戏 - -// xn--vermgensberater-ctb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG -vermögensberater - -// xn--vermgensberatung-pwb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG -vermögensberatung - -// xn--vhquv : 2013-08-27 Binky Moon, LLC -企业 - -// xn--vuq861b : 2014-10-16 Beijing Tele-info Network Technology Co., Ltd. -信息 - -// xn--w4r85el8fhu5dnra : 2015-04-30 Kerry Trading Co. Limited -嘉里大酒店 - -// xn--w4rs40l : 2015-07-30 Kerry Trading Co. Limited -嘉里 - -// xn--xhq521b : 2013-11-14 Guangzhou YU Wei Information Technology Co., Ltd. -广东 - -// xn--zfr164b : 2013-11-08 China Organizational Name Administration Center -政务 - -// xyz : 2013-12-05 XYZ.COM LLC -xyz - -// yachts : 2014-01-09 XYZ.COM LLC -yachts - -// yahoo : 2015-04-02 Oath Inc. -yahoo - -// yamaxun : 2014-12-18 Amazon Registry Services, Inc. -yamaxun - -// yandex : 2014-04-10 Yandex Europe B.V. -yandex - -// yodobashi : 2014-11-20 YODOBASHI CAMERA CO.,LTD. -yodobashi - -// yoga : 2014-05-29 Registry Services, LLC -yoga - -// yokohama : 2013-12-12 GMO Registry, Inc. -yokohama - -// you : 2015-04-09 Amazon Registry Services, Inc. -you - -// youtube : 2014-05-01 Charleston Road Registry Inc. -youtube - -// yun : 2015-01-08 Beijing Qihu Keji Co., Ltd. -yun - -// zappos : 2015-06-25 Amazon Registry Services, Inc. -zappos - -// zara : 2014-11-07 Industria de Diseño Textil, S.A. (INDITEX, S.A.) -zara - -// zero : 2014-12-18 Amazon Registry Services, Inc. -zero - -// zip : 2014-05-08 Charleston Road Registry Inc. -zip - -// zone : 2013-11-14 Binky Moon, LLC -zone - -// zuerich : 2014-11-07 Kanton Zürich (Canton of Zurich) -zuerich - - -// ===END ICANN DOMAINS=== -// ===BEGIN PRIVATE DOMAINS=== -// (Note: these are in alphabetical order by company name) - -// 1GB LLC : https://www.1gb.ua/ -// Submitted by 1GB LLC -cc.ua -inf.ua -ltd.ua - -// 611coin : https://611project.org/ -611.to - -// Aaron Marais' Gitlab pages: https://lab.aaronleem.co.za -// Submitted by Aaron Marais -graphox.us - -// accesso Technology Group, plc. : https://accesso.com/ -// Submitted by accesso Team -*.devcdnaccesso.com - -// Acorn Labs : https://acorn.io -// Submitted by Craig Jellick -*.on-acorn.io - -// ActiveTrail: https://www.activetrail.biz/ -// Submitted by Ofer Kalaora -activetrail.biz - -// Adobe : https://www.adobe.com/ -// Submitted by Ian Boston and Lars Trieloff -adobeaemcloud.com -*.dev.adobeaemcloud.com -hlx.live -adobeaemcloud.net -hlx.page -hlx3.page - -// Adobe Developer Platform : https://developer.adobe.com -// Submitted by Jesse MacFadyen -adobeio-static.net -adobeioruntime.net - -// Agnat sp. z o.o. : https://domena.pl -// Submitted by Przemyslaw Plewa -beep.pl - -// Airkit : https://www.airkit.com/ -// Submitted by Grant Cooksey -airkitapps.com -airkitapps-au.com -airkitapps.eu - -// Aiven: https://aiven.io/ -// Submitted by Etienne Stalmans -aivencloud.com - -// Akamai : https://www.akamai.com/ -// Submitted by Akamai Team -akadns.net -akamai.net -akamai-staging.net -akamaiedge.net -akamaiedge-staging.net -akamaihd.net -akamaihd-staging.net -akamaiorigin.net -akamaiorigin-staging.net -akamaized.net -akamaized-staging.net -edgekey.net -edgekey-staging.net -edgesuite.net -edgesuite-staging.net - -// alboto.ca : http://alboto.ca -// Submitted by Anton Avramov -barsy.ca - -// Alces Software Ltd : http://alces-software.com -// Submitted by Mark J. Titorenko -*.compute.estate -*.alces.network - -// all-inkl.com : https://all-inkl.com -// Submitted by Werner Kaltofen -kasserver.com - -// Altervista: https://www.altervista.org -// Submitted by Carlo Cannas -altervista.org - -// alwaysdata : https://www.alwaysdata.com -// Submitted by Cyril -alwaysdata.net - -// Amaze Software : https://amaze.co -// Submitted by Domain Admin -myamaze.net - -// Amazon : https://www.amazon.com/ -// Submitted by AWS Security -// Subsections of Amazon/subsidiaries will appear until "concludes" tag - -// Amazon CloudFront -// Submitted by Donavan Miller -// Reference: 54144616-fd49-4435-8535-19c6a601bdb3 -cloudfront.net - -// Amazon EC2 -// Submitted by Luke Wells -// Reference: 4c38fa71-58ac-4768-99e5-689c1767e537 -*.compute.amazonaws.com -*.compute-1.amazonaws.com -*.compute.amazonaws.com.cn -us-east-1.amazonaws.com - -// Amazon S3 -// Submitted by Luke Wells -// Reference: d068bd97-f0a9-4838-a6d8-954b622ef4ae -s3.cn-north-1.amazonaws.com.cn -s3.dualstack.ap-northeast-1.amazonaws.com -s3.dualstack.ap-northeast-2.amazonaws.com -s3.ap-northeast-2.amazonaws.com -s3-website.ap-northeast-2.amazonaws.com -s3.dualstack.ap-south-1.amazonaws.com -s3.ap-south-1.amazonaws.com -s3-website.ap-south-1.amazonaws.com -s3.dualstack.ap-southeast-1.amazonaws.com -s3.dualstack.ap-southeast-2.amazonaws.com -s3.dualstack.ca-central-1.amazonaws.com -s3.ca-central-1.amazonaws.com -s3-website.ca-central-1.amazonaws.com -s3.dualstack.eu-central-1.amazonaws.com -s3.eu-central-1.amazonaws.com -s3-website.eu-central-1.amazonaws.com -s3.dualstack.eu-west-1.amazonaws.com -s3.dualstack.eu-west-2.amazonaws.com -s3.eu-west-2.amazonaws.com -s3-website.eu-west-2.amazonaws.com -s3.dualstack.eu-west-3.amazonaws.com -s3.eu-west-3.amazonaws.com -s3-website.eu-west-3.amazonaws.com -s3.amazonaws.com -s3-ap-northeast-1.amazonaws.com -s3-ap-northeast-2.amazonaws.com -s3-ap-south-1.amazonaws.com -s3-ap-southeast-1.amazonaws.com -s3-ap-southeast-2.amazonaws.com -s3-ca-central-1.amazonaws.com -s3-eu-central-1.amazonaws.com -s3-eu-west-1.amazonaws.com -s3-eu-west-2.amazonaws.com -s3-eu-west-3.amazonaws.com -s3-external-1.amazonaws.com -s3-fips-us-gov-west-1.amazonaws.com -s3-sa-east-1.amazonaws.com -s3-us-east-2.amazonaws.com -s3-us-gov-west-1.amazonaws.com -s3-us-west-1.amazonaws.com -s3-us-west-2.amazonaws.com -s3-website-ap-northeast-1.amazonaws.com -s3-website-ap-southeast-1.amazonaws.com -s3-website-ap-southeast-2.amazonaws.com -s3-website-eu-west-1.amazonaws.com -s3-website-sa-east-1.amazonaws.com -s3-website-us-east-1.amazonaws.com -s3-website-us-west-1.amazonaws.com -s3-website-us-west-2.amazonaws.com -s3.dualstack.sa-east-1.amazonaws.com -s3.dualstack.us-east-1.amazonaws.com -s3.dualstack.us-east-2.amazonaws.com -s3.us-east-2.amazonaws.com -s3-website.us-east-2.amazonaws.com - -// AWS Cloud9 -// Submitted by: AWS Security -// Reference: 2b6dfa9a-3a7f-4367-b2e7-0321e77c0d59 -vfs.cloud9.af-south-1.amazonaws.com -webview-assets.cloud9.af-south-1.amazonaws.com -vfs.cloud9.ap-east-1.amazonaws.com -webview-assets.cloud9.ap-east-1.amazonaws.com -vfs.cloud9.ap-northeast-1.amazonaws.com -webview-assets.cloud9.ap-northeast-1.amazonaws.com -vfs.cloud9.ap-northeast-2.amazonaws.com -webview-assets.cloud9.ap-northeast-2.amazonaws.com -vfs.cloud9.ap-northeast-3.amazonaws.com -webview-assets.cloud9.ap-northeast-3.amazonaws.com -vfs.cloud9.ap-south-1.amazonaws.com -webview-assets.cloud9.ap-south-1.amazonaws.com -vfs.cloud9.ap-southeast-1.amazonaws.com -webview-assets.cloud9.ap-southeast-1.amazonaws.com -vfs.cloud9.ap-southeast-2.amazonaws.com -webview-assets.cloud9.ap-southeast-2.amazonaws.com -vfs.cloud9.ca-central-1.amazonaws.com -webview-assets.cloud9.ca-central-1.amazonaws.com -vfs.cloud9.eu-central-1.amazonaws.com -webview-assets.cloud9.eu-central-1.amazonaws.com -vfs.cloud9.eu-north-1.amazonaws.com -webview-assets.cloud9.eu-north-1.amazonaws.com -vfs.cloud9.eu-south-1.amazonaws.com -webview-assets.cloud9.eu-south-1.amazonaws.com -vfs.cloud9.eu-west-1.amazonaws.com -webview-assets.cloud9.eu-west-1.amazonaws.com -vfs.cloud9.eu-west-2.amazonaws.com -webview-assets.cloud9.eu-west-2.amazonaws.com -vfs.cloud9.eu-west-3.amazonaws.com -webview-assets.cloud9.eu-west-3.amazonaws.com -vfs.cloud9.me-south-1.amazonaws.com -webview-assets.cloud9.me-south-1.amazonaws.com -vfs.cloud9.sa-east-1.amazonaws.com -webview-assets.cloud9.sa-east-1.amazonaws.com -vfs.cloud9.us-east-1.amazonaws.com -webview-assets.cloud9.us-east-1.amazonaws.com -vfs.cloud9.us-east-2.amazonaws.com -webview-assets.cloud9.us-east-2.amazonaws.com -vfs.cloud9.us-west-1.amazonaws.com -webview-assets.cloud9.us-west-1.amazonaws.com -vfs.cloud9.us-west-2.amazonaws.com -webview-assets.cloud9.us-west-2.amazonaws.com - -// AWS Elastic Beanstalk -// Submitted by Luke Wells -// Reference: aa202394-43a0-4857-b245-8db04549137e -cn-north-1.eb.amazonaws.com.cn -cn-northwest-1.eb.amazonaws.com.cn -elasticbeanstalk.com -ap-northeast-1.elasticbeanstalk.com -ap-northeast-2.elasticbeanstalk.com -ap-northeast-3.elasticbeanstalk.com -ap-south-1.elasticbeanstalk.com -ap-southeast-1.elasticbeanstalk.com -ap-southeast-2.elasticbeanstalk.com -ca-central-1.elasticbeanstalk.com -eu-central-1.elasticbeanstalk.com -eu-west-1.elasticbeanstalk.com -eu-west-2.elasticbeanstalk.com -eu-west-3.elasticbeanstalk.com -sa-east-1.elasticbeanstalk.com -us-east-1.elasticbeanstalk.com -us-east-2.elasticbeanstalk.com -us-gov-west-1.elasticbeanstalk.com -us-west-1.elasticbeanstalk.com -us-west-2.elasticbeanstalk.com - -// (AWS) Elastic Load Balancing -// Submitted by Luke Wells -// Reference: 12a3d528-1bac-4433-a359-a395867ffed2 -*.elb.amazonaws.com.cn -*.elb.amazonaws.com - -// AWS Global Accelerator -// Submitted by Daniel Massaguer -// Reference: d916759d-a08b-4241-b536-4db887383a6a -awsglobalaccelerator.com - -// eero -// Submitted by Yue Kang -// Reference: 264afe70-f62c-4c02-8ab9-b5281ed24461 -eero.online -eero-stage.online - -// concludes Amazon - -// Amune : https://amune.org/ -// Submitted by Team Amune -t3l3p0rt.net -tele.amune.org - -// Apigee : https://apigee.com/ -// Submitted by Apigee Security Team -apigee.io - -// Apphud : https://apphud.com -// Submitted by Alexander Selivanov -siiites.com - -// Appspace : https://www.appspace.com -// Submitted by Appspace Security Team -appspacehosted.com -appspaceusercontent.com - -// Appudo UG (haftungsbeschränkt) : https://www.appudo.com -// Submitted by Alexander Hochbaum -appudo.net - -// Aptible : https://www.aptible.com/ -// Submitted by Thomas Orozco -on-aptible.com - -// ASEINet : https://www.aseinet.com/ -// Submitted by Asei SEKIGUCHI -user.aseinet.ne.jp -gv.vc -d.gv.vc - -// Asociación Amigos de la Informática "Euskalamiga" : http://encounter.eus/ -// Submitted by Hector Martin -user.party.eus - -// Association potager.org : https://potager.org/ -// Submitted by Lunar -pimienta.org -poivron.org -potager.org -sweetpepper.org - -// ASUSTOR Inc. : http://www.asustor.com -// Submitted by Vincent Tseng -myasustor.com - -// Atlassian : https://atlassian.com -// Submitted by Sam Smyth -cdn.prod.atlassian-dev.net - -// Authentick UG (haftungsbeschränkt) : https://authentick.net -// Submitted by Lukas Reschke -translated.page - -// Autocode : https://autocode.com -// Submitted by Jacob Lee -autocode.dev - -// AVM : https://avm.de -// Submitted by Andreas Weise -myfritz.net - -// AVStack Pte. Ltd. : https://avstack.io -// Submitted by Jasper Hugo -onavstack.net - -// AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com -// Submitted by James Kennedy -*.awdev.ca -*.advisor.ws - -// AZ.pl sp. z.o.o: https://az.pl -// Submitted by Krzysztof Wolski -ecommerce-shop.pl - -// b-data GmbH : https://www.b-data.io -// Submitted by Olivier Benz -b-data.io - -// backplane : https://www.backplane.io -// Submitted by Anthony Voutas -backplaneapp.io - -// Balena : https://www.balena.io -// Submitted by Petros Angelatos -balena-devices.com - -// University of Banja Luka : https://unibl.org -// Domains for Republic of Srpska administrative entity. -// Submitted by Marko Ivanovic -rs.ba - -// Banzai Cloud -// Submitted by Janos Matyas -*.banzai.cloud -app.banzaicloud.io -*.backyards.banzaicloud.io - -// BASE, Inc. : https://binc.jp -// Submitted by Yuya NAGASAWA -base.ec -official.ec -buyshop.jp -fashionstore.jp -handcrafted.jp -kawaiishop.jp -supersale.jp -theshop.jp -shopselect.net -base.shop - -// BeagleBoard.org Foundation : https://beagleboard.org -// Submitted by Jason Kridner -beagleboard.io - -// Beget Ltd -// Submitted by Lev Nekrasov -*.beget.app - -// BetaInABox -// Submitted by Adrian -betainabox.com - -// BinaryLane : http://www.binarylane.com -// Submitted by Nathan O'Sullivan -bnr.la - -// Bitbucket : http://bitbucket.org -// Submitted by Andy Ortlieb -bitbucket.io - -// Blackbaud, Inc. : https://www.blackbaud.com -// Submitted by Paul Crowder -blackbaudcdn.net - -// Blatech : http://www.blatech.net -// Submitted by Luke Bratch -of.je - -// Blue Bite, LLC : https://bluebite.com -// Submitted by Joshua Weiss -bluebite.io - -// Boomla : https://boomla.com -// Submitted by Tibor Halter -boomla.net - -// Boutir : https://www.boutir.com -// Submitted by Eric Ng Ka Ka -boutir.com - -// Boxfuse : https://boxfuse.com -// Submitted by Axel Fontaine -boxfuse.io - -// bplaced : https://www.bplaced.net/ -// Submitted by Miroslav Bozic -square7.ch -bplaced.com -bplaced.de -square7.de -bplaced.net -square7.net - -// Brendly : https://brendly.rs -// Submitted by Dusan Radovanovic -shop.brendly.rs - -// BrowserSafetyMark -// Submitted by Dave Tharp -browsersafetymark.io - -// Bytemark Hosting : https://www.bytemark.co.uk -// Submitted by Paul Cammish -uk0.bigv.io -dh.bytemark.co.uk -vm.bytemark.co.uk - -// Caf.js Labs LLC : https://www.cafjs.com -// Submitted by Antonio Lain -cafjs.com - -// callidomus : https://www.callidomus.com/ -// Submitted by Marcus Popp -mycd.eu - -// Canva Pty Ltd : https://canva.com/ -// Submitted by Joel Aquilina -canva-apps.cn -canva-apps.com - -// Carrd : https://carrd.co -// Submitted by AJ -drr.ac -uwu.ai -carrd.co -crd.co -ju.mp - -// CentralNic : http://www.centralnic.com/names/domains -// Submitted by registry -ae.org -br.com -cn.com -com.de -com.se -de.com -eu.com -gb.net -hu.net -jp.net -jpn.com -mex.com -ru.com -sa.com -se.net -uk.com -uk.net -us.com -za.bz -za.com - -// No longer operated by CentralNic, these entries should be adopted and/or removed by current operators -// Submitted by Gavin Brown -ar.com -hu.com -kr.com -no.com -qc.com -uy.com - -// Africa.com Web Solutions Ltd : https://registry.africa.com -// Submitted by Gavin Brown -africa.com - -// iDOT Services Limited : http://www.domain.gr.com -// Submitted by Gavin Brown -gr.com - -// Radix FZC : http://domains.in.net -// Submitted by Gavin Brown -in.net -web.in - -// US REGISTRY LLC : http://us.org -// Submitted by Gavin Brown -us.org - -// co.com Registry, LLC : https://registry.co.com -// Submitted by Gavin Brown -co.com - -// Roar Domains LLC : https://roar.basketball/ -// Submitted by Gavin Brown -aus.basketball -nz.basketball - -// BRS Media : https://brsmedia.com/ -// Submitted by Gavin Brown -radio.am -radio.fm - -// c.la : http://www.c.la/ -c.la - -// certmgr.org : https://certmgr.org -// Submitted by B. Blechschmidt -certmgr.org - -// Cityhost LLC : https://cityhost.ua -// Submitted by Maksym Rivtin -cx.ua - -// Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/ -// Submitted by Rishabh Nambiar & Michael Brown -discourse.group -discourse.team - -// Clever Cloud : https://www.clever-cloud.com/ -// Submitted by Quentin Adam -cleverapps.io - -// Clerk : https://www.clerk.dev -// Submitted by Colin Sidoti -clerk.app -clerkstage.app -*.lcl.dev -*.lclstage.dev -*.stg.dev -*.stgstage.dev - -// ClickRising : https://clickrising.com/ -// Submitted by Umut Gumeli -clickrising.net - -// Cloud66 : https://www.cloud66.com/ -// Submitted by Khash Sajadi -c66.me -cloud66.ws -cloud66.zone - -// CloudAccess.net : https://www.cloudaccess.net/ -// Submitted by Pawel Panek -jdevcloud.com -wpdevcloud.com -cloudaccess.host -freesite.host -cloudaccess.net - -// cloudControl : https://www.cloudcontrol.com/ -// Submitted by Tobias Wilken -cloudcontrolled.com -cloudcontrolapp.com - -// Cloudera, Inc. : https://www.cloudera.com/ -// Submitted by Kedarnath Waikar -*.cloudera.site - -// Cloudflare, Inc. : https://www.cloudflare.com/ -// Submitted by Cloudflare Team -cf-ipfs.com -cloudflare-ipfs.com -trycloudflare.com -pages.dev -r2.dev -workers.dev - -// Clovyr : https://clovyr.io -// Submitted by Patrick Nielsen -wnext.app - -// co.ca : http://registry.co.ca/ -co.ca - -// Co & Co : https://co-co.nl/ -// Submitted by Govert Versluis -*.otap.co - -// i-registry s.r.o. : http://www.i-registry.cz/ -// Submitted by Martin Semrad -co.cz - -// CDN77.com : http://www.cdn77.com -// Submitted by Jan Krpes -c.cdn77.org -cdn77-ssl.net -r.cdn77.net -rsc.cdn77.org -ssl.origin.cdn77-secure.org - -// Cloud DNS Ltd : http://www.cloudns.net -// Submitted by Aleksander Hristov -cloudns.asia -cloudns.biz -cloudns.club -cloudns.cc -cloudns.eu -cloudns.in -cloudns.info -cloudns.org -cloudns.pro -cloudns.pw -cloudns.us - -// CNPY : https://cnpy.gdn -// Submitted by Angelo Gladding -cnpy.gdn - -// Codeberg e. V. : https://codeberg.org -// Submitted by Moritz Marquardt -codeberg.page - -// CoDNS B.V. -co.nl -co.no - -// Combell.com : https://www.combell.com -// Submitted by Thomas Wouters -webhosting.be -hosting-cluster.nl - -// Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/ -// Submitted by George Georgievsky -ac.ru -edu.ru -gov.ru -int.ru -mil.ru -test.ru - -// COSIMO GmbH : http://www.cosimo.de -// Submitted by Rene Marticke -dyn.cosidns.de -dynamisches-dns.de -dnsupdater.de -internet-dns.de -l-o-g-i-n.de -dynamic-dns.info -feste-ip.net -knx-server.net -static-access.net - -// Craynic, s.r.o. : http://www.craynic.com/ -// Submitted by Ales Krajnik -realm.cz - -// Cryptonomic : https://cryptonomic.net/ -// Submitted by Andrew Cady -*.cryptonomic.net - -// Cupcake : https://cupcake.io/ -// Submitted by Jonathan Rudenberg -cupcake.is - -// Curv UG : https://curv-labs.de/ -// Submitted by Marvin Wiesner -curv.dev - -// Customer OCI - Oracle Dyn https://cloud.oracle.com/home https://dyn.com/dns/ -// Submitted by Gregory Drake -// Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label -*.customer-oci.com -*.oci.customer-oci.com -*.ocp.customer-oci.com -*.ocs.customer-oci.com - -// cyon GmbH : https://www.cyon.ch/ -// Submitted by Dominic Luechinger -cyon.link -cyon.site - -// Danger Science Group: https://dangerscience.com/ -// Submitted by Skylar MacDonald -fnwk.site -folionetwork.site -platform0.app - -// Daplie, Inc : https://daplie.com -// Submitted by AJ ONeal -daplie.me -localhost.daplie.me - -// Datto, Inc. : https://www.datto.com/ -// Submitted by Philipp Heckel -dattolocal.com -dattorelay.com -dattoweb.com -mydatto.com -dattolocal.net -mydatto.net - -// Dansk.net : http://www.dansk.net/ -// Submitted by Anani Voule -biz.dk -co.dk -firm.dk -reg.dk -store.dk - -// dappnode.io : https://dappnode.io/ -// Submitted by Abel Boldu / DAppNode Team -dyndns.dappnode.io - -// dapps.earth : https://dapps.earth/ -// Submitted by Daniil Burdakov -*.dapps.earth -*.bzz.dapps.earth - -// Dark, Inc. : https://darklang.com -// Submitted by Paul Biggar -builtwithdark.com - -// DataDetect, LLC. : https://datadetect.com -// Submitted by Andrew Banchich -demo.datadetect.com -instance.datadetect.com - -// Datawire, Inc : https://www.datawire.io -// Submitted by Richard Li -edgestack.me - -// DDNS5 : https://ddns5.com -// Submitted by Cameron Elliott -ddns5.com - -// Debian : https://www.debian.org/ -// Submitted by Peter Palfrader / Debian Sysadmin Team -debian.net - -// Deno Land Inc : https://deno.com/ -// Submitted by Luca Casonato -deno.dev -deno-staging.dev - -// deSEC : https://desec.io/ -// Submitted by Peter Thomassen -dedyn.io - -// Deta: https://www.deta.sh/ -// Submitted by Aavash Shrestha -deta.app -deta.dev - -// Diher Solutions : https://diher.solutions -// Submitted by Didi Hermawan -*.rss.my.id -*.diher.solutions - -// Discord Inc : https://discord.com -// Submitted by Sahn Lam -discordsays.com -discordsez.com - -// DNS Africa Ltd https://dns.business -// Submitted by Calvin Browne -jozi.biz - -// DNShome : https://www.dnshome.de/ -// Submitted by Norbert Auler -dnshome.de - -// DotArai : https://www.dotarai.com/ -// Submitted by Atsadawat Netcharadsang -online.th -shop.th - -// DrayTek Corp. : https://www.draytek.com/ -// Submitted by Paul Fang -drayddns.com - -// DreamCommerce : https://shoper.pl/ -// Submitted by Konrad Kotarba -shoparena.pl - -// DreamHost : http://www.dreamhost.com/ -// Submitted by Andrew Farmer -dreamhosters.com - -// Drobo : http://www.drobo.com/ -// Submitted by Ricardo Padilha -mydrobo.com - -// Drud Holdings, LLC. : https://www.drud.com/ -// Submitted by Kevin Bridges -drud.io -drud.us - -// DuckDNS : http://www.duckdns.org/ -// Submitted by Richard Harper -duckdns.org - -// Bip : https://bip.sh -// Submitted by Joel Kennedy -bip.sh - -// bitbridge.net : Submitted by Craig Welch, abeliidev@gmail.com -bitbridge.net - -// dy.fi : http://dy.fi/ -// Submitted by Heikki Hannikainen -dy.fi -tunk.org - -// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ -dyndns-at-home.com -dyndns-at-work.com -dyndns-blog.com -dyndns-free.com -dyndns-home.com -dyndns-ip.com -dyndns-mail.com -dyndns-office.com -dyndns-pics.com -dyndns-remote.com -dyndns-server.com -dyndns-web.com -dyndns-wiki.com -dyndns-work.com -dyndns.biz -dyndns.info -dyndns.org -dyndns.tv -at-band-camp.net -ath.cx -barrel-of-knowledge.info -barrell-of-knowledge.info -better-than.tv -blogdns.com -blogdns.net -blogdns.org -blogsite.org -boldlygoingnowhere.org -broke-it.net -buyshouses.net -cechire.com -dnsalias.com -dnsalias.net -dnsalias.org -dnsdojo.com -dnsdojo.net -dnsdojo.org -does-it.net -doesntexist.com -doesntexist.org -dontexist.com -dontexist.net -dontexist.org -doomdns.com -doomdns.org -dvrdns.org -dyn-o-saur.com -dynalias.com -dynalias.net -dynalias.org -dynathome.net -dyndns.ws -endofinternet.net -endofinternet.org -endoftheinternet.org -est-a-la-maison.com -est-a-la-masion.com -est-le-patron.com -est-mon-blogueur.com -for-better.biz -for-more.biz -for-our.info -for-some.biz -for-the.biz -forgot.her.name -forgot.his.name -from-ak.com -from-al.com -from-ar.com -from-az.net -from-ca.com -from-co.net -from-ct.com -from-dc.com -from-de.com -from-fl.com -from-ga.com -from-hi.com -from-ia.com -from-id.com -from-il.com -from-in.com -from-ks.com -from-ky.com -from-la.net -from-ma.com -from-md.com -from-me.org -from-mi.com -from-mn.com -from-mo.com -from-ms.com -from-mt.com -from-nc.com -from-nd.com -from-ne.com -from-nh.com -from-nj.com -from-nm.com -from-nv.com -from-ny.net -from-oh.com -from-ok.com -from-or.com -from-pa.com -from-pr.com -from-ri.com -from-sc.com -from-sd.com -from-tn.com -from-tx.com -from-ut.com -from-va.com -from-vt.com -from-wa.com -from-wi.com -from-wv.com -from-wy.com -ftpaccess.cc -fuettertdasnetz.de -game-host.org -game-server.cc -getmyip.com -gets-it.net -go.dyndns.org -gotdns.com -gotdns.org -groks-the.info -groks-this.info -ham-radio-op.net -here-for-more.info -hobby-site.com -hobby-site.org -home.dyndns.org -homedns.org -homeftp.net -homeftp.org -homeip.net -homelinux.com -homelinux.net -homelinux.org -homeunix.com -homeunix.net -homeunix.org -iamallama.com -in-the-band.net -is-a-anarchist.com -is-a-blogger.com -is-a-bookkeeper.com -is-a-bruinsfan.org -is-a-bulls-fan.com -is-a-candidate.org -is-a-caterer.com -is-a-celticsfan.org -is-a-chef.com -is-a-chef.net -is-a-chef.org -is-a-conservative.com -is-a-cpa.com -is-a-cubicle-slave.com -is-a-democrat.com -is-a-designer.com -is-a-doctor.com -is-a-financialadvisor.com -is-a-geek.com -is-a-geek.net -is-a-geek.org -is-a-green.com -is-a-guru.com -is-a-hard-worker.com -is-a-hunter.com -is-a-knight.org -is-a-landscaper.com -is-a-lawyer.com -is-a-liberal.com -is-a-libertarian.com -is-a-linux-user.org -is-a-llama.com -is-a-musician.com -is-a-nascarfan.com -is-a-nurse.com -is-a-painter.com -is-a-patsfan.org -is-a-personaltrainer.com -is-a-photographer.com -is-a-player.com -is-a-republican.com -is-a-rockstar.com -is-a-socialist.com -is-a-soxfan.org -is-a-student.com -is-a-teacher.com -is-a-techie.com -is-a-therapist.com -is-an-accountant.com -is-an-actor.com -is-an-actress.com -is-an-anarchist.com -is-an-artist.com -is-an-engineer.com -is-an-entertainer.com -is-by.us -is-certified.com -is-found.org -is-gone.com -is-into-anime.com -is-into-cars.com -is-into-cartoons.com -is-into-games.com -is-leet.com -is-lost.org -is-not-certified.com -is-saved.org -is-slick.com -is-uberleet.com -is-very-bad.org -is-very-evil.org -is-very-good.org -is-very-nice.org -is-very-sweet.org -is-with-theband.com -isa-geek.com -isa-geek.net -isa-geek.org -isa-hockeynut.com -issmarterthanyou.com -isteingeek.de -istmein.de -kicks-ass.net -kicks-ass.org -knowsitall.info -land-4-sale.us -lebtimnetz.de -leitungsen.de -likes-pie.com -likescandy.com -merseine.nu -mine.nu -misconfused.org -mypets.ws -myphotos.cc -neat-url.com -office-on-the.net -on-the-web.tv -podzone.net -podzone.org -readmyblog.org -saves-the-whales.com -scrapper-site.net -scrapping.cc -selfip.biz -selfip.com -selfip.info -selfip.net -selfip.org -sells-for-less.com -sells-for-u.com -sells-it.net -sellsyourhome.org -servebbs.com -servebbs.net -servebbs.org -serveftp.net -serveftp.org -servegame.org -shacknet.nu -simple-url.com -space-to-rent.com -stuff-4-sale.org -stuff-4-sale.us -teaches-yoga.com -thruhere.net -traeumtgerade.de -webhop.biz -webhop.info -webhop.net -webhop.org -worse-than.tv -writesthisblog.com - -// ddnss.de : https://www.ddnss.de/ -// Submitted by Robert Niedziela -ddnss.de -dyn.ddnss.de -dyndns.ddnss.de -dyndns1.de -dyn-ip24.de -home-webserver.de -dyn.home-webserver.de -myhome-server.de -ddnss.org - -// Definima : http://www.definima.com/ -// Submitted by Maxence Bitterli -definima.net -definima.io - -// DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/ -// Submitted by Braxton Huggins -ondigitalocean.app - -// DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/ -// Submitted by Robin H. Johnson -*.digitaloceanspaces.com - -// dnstrace.pro : https://dnstrace.pro/ -// Submitted by Chris Partridge -bci.dnstrace.pro - -// Dynu.com : https://www.dynu.com/ -// Submitted by Sue Ye -ddnsfree.com -ddnsgeek.com -giize.com -gleeze.com -kozow.com -loseyourip.com -ooguy.com -theworkpc.com -casacam.net -dynu.net -accesscam.org -camdvr.org -freeddns.org -mywire.org -webredirect.org -myddns.rocks -blogsite.xyz - -// dynv6 : https://dynv6.com -// Submitted by Dominik Menke -dynv6.net - -// E4YOU spol. s.r.o. : https://e4you.cz/ -// Submitted by Vladimir Dudr -e4.cz - -// Easypanel : https://easypanel.io -// Submitted by Andrei Canta -easypanel.app -easypanel.host - -// Elementor : Elementor Ltd. -// Submitted by Anton Barkan -elementor.cloud -elementor.cool - -// En root‽ : https://en-root.org -// Submitted by Emmanuel Raviart -en-root.fr - -// Enalean SAS: https://www.enalean.com -// Submitted by Thomas Cottier -mytuleap.com -tuleap-partners.com - -// Encoretivity AB: https://encore.dev -// Submitted by André Eriksson -encr.app -encoreapi.com - -// ECG Robotics, Inc: https://ecgrobotics.org -// Submitted by -onred.one -staging.onred.one - -// encoway GmbH : https://www.encoway.de -// Submitted by Marcel Daus -eu.encoway.cloud - -// EU.org https://eu.org/ -// Submitted by Pierre Beyssac -eu.org -al.eu.org -asso.eu.org -at.eu.org -au.eu.org -be.eu.org -bg.eu.org -ca.eu.org -cd.eu.org -ch.eu.org -cn.eu.org -cy.eu.org -cz.eu.org -de.eu.org -dk.eu.org -edu.eu.org -ee.eu.org -es.eu.org -fi.eu.org -fr.eu.org -gr.eu.org -hr.eu.org -hu.eu.org -ie.eu.org -il.eu.org -in.eu.org -int.eu.org -is.eu.org -it.eu.org -jp.eu.org -kr.eu.org -lt.eu.org -lu.eu.org -lv.eu.org -mc.eu.org -me.eu.org -mk.eu.org -mt.eu.org -my.eu.org -net.eu.org -ng.eu.org -nl.eu.org -no.eu.org -nz.eu.org -paris.eu.org -pl.eu.org -pt.eu.org -q-a.eu.org -ro.eu.org -ru.eu.org -se.eu.org -si.eu.org -sk.eu.org -tr.eu.org -uk.eu.org -us.eu.org - -// Eurobyte : https://eurobyte.ru -// Submitted by Evgeniy Subbotin -eurodir.ru - -// Evennode : http://www.evennode.com/ -// Submitted by Michal Kralik -eu-1.evennode.com -eu-2.evennode.com -eu-3.evennode.com -eu-4.evennode.com -us-1.evennode.com -us-2.evennode.com -us-3.evennode.com -us-4.evennode.com - -// eDirect Corp. : https://hosting.url.com.tw/ -// Submitted by C.S. chang -twmail.cc -twmail.net -twmail.org -mymailer.com.tw -url.tw - -// Fabrica Technologies, Inc. : https://www.fabrica.dev/ -// Submitted by Eric Jiang -onfabrica.com - -// Facebook, Inc. -// Submitted by Peter Ruibal -apps.fbsbx.com - -// FAITID : https://faitid.org/ -// Submitted by Maxim Alzoba -// https://www.flexireg.net/stat_info -ru.net -adygeya.ru -bashkiria.ru -bir.ru -cbg.ru -com.ru -dagestan.ru -grozny.ru -kalmykia.ru -kustanai.ru -marine.ru -mordovia.ru -msk.ru -mytis.ru -nalchik.ru -nov.ru -pyatigorsk.ru -spb.ru -vladikavkaz.ru -vladimir.ru -abkhazia.su -adygeya.su -aktyubinsk.su -arkhangelsk.su -armenia.su -ashgabad.su -azerbaijan.su -balashov.su -bashkiria.su -bryansk.su -bukhara.su -chimkent.su -dagestan.su -east-kazakhstan.su -exnet.su -georgia.su -grozny.su -ivanovo.su -jambyl.su -kalmykia.su -kaluga.su -karacol.su -karaganda.su -karelia.su -khakassia.su -krasnodar.su -kurgan.su -kustanai.su -lenug.su -mangyshlak.su -mordovia.su -msk.su -murmansk.su -nalchik.su -navoi.su -north-kazakhstan.su -nov.su -obninsk.su -penza.su -pokrovsk.su -sochi.su -spb.su -tashkent.su -termez.su -togliatti.su -troitsk.su -tselinograd.su -tula.su -tuva.su -vladikavkaz.su -vladimir.su -vologda.su - -// Fancy Bits, LLC : http://getchannels.com -// Submitted by Aman Gupta -channelsdvr.net -u.channelsdvr.net - -// Fastly Inc. : http://www.fastly.com/ -// Submitted by Fastly Security -edgecompute.app -fastly-edge.com -fastly-terrarium.com -fastlylb.net -map.fastlylb.net -freetls.fastly.net -map.fastly.net -a.prod.fastly.net -global.prod.fastly.net -a.ssl.fastly.net -b.ssl.fastly.net -global.ssl.fastly.net - -// Fastmail : https://www.fastmail.com/ -// Submitted by Marc Bradshaw -*.user.fm - -// FASTVPS EESTI OU : https://fastvps.ru/ -// Submitted by Likhachev Vasiliy -fastvps-server.com -fastvps.host -myfast.host -fastvps.site -myfast.space - -// Fedora : https://fedoraproject.org/ -// submitted by Patrick Uiterwijk -fedorainfracloud.org -fedorapeople.org -cloud.fedoraproject.org -app.os.fedoraproject.org -app.os.stg.fedoraproject.org - -// FearWorks Media Ltd. : https://fearworksmedia.co.uk -// submitted by Keith Fairley -conn.uk -copro.uk -hosp.uk - -// Fermax : https://fermax.com/ -// submitted by Koen Van Isterdael -mydobiss.com - -// FH Muenster : https://www.fh-muenster.de -// Submitted by Robin Naundorf -fh-muenster.io - -// Filegear Inc. : https://www.filegear.com -// Submitted by Jason Zhu -filegear.me -filegear-au.me -filegear-de.me -filegear-gb.me -filegear-ie.me -filegear-jp.me -filegear-sg.me - -// Firebase, Inc. -// Submitted by Chris Raynor -firebaseapp.com - -// Firewebkit : https://www.firewebkit.com -// Submitted by Majid Qureshi -fireweb.app - -// FLAP : https://www.flap.cloud -// Submitted by Louis Chemineau -flap.id - -// FlashDrive : https://flashdrive.io -// Submitted by Eric Chan -onflashdrive.app -fldrv.com - -// fly.io: https://fly.io -// Submitted by Kurt Mackey -fly.dev -edgeapp.net -shw.io - -// Flynn : https://flynn.io -// Submitted by Jonathan Rudenberg -flynnhosting.net - -// Forgerock : https://www.forgerock.com -// Submitted by Roderick Parr -forgeblocks.com -id.forgerock.io - -// Framer : https://www.framer.com -// Submitted by Koen Rouwhorst -framer.app -framercanvas.com -framer.media -framer.photos -framer.website -framer.wiki - -// Frusky MEDIA&PR : https://www.frusky.de -// Submitted by Victor Pupynin -*.frusky.de - -// RavPage : https://www.ravpage.co.il -// Submitted by Roni Horowitz -ravpage.co.il - -// Frederik Braun https://frederik-braun.com -// Submitted by Frederik Braun -0e.vc - -// Freebox : http://www.freebox.fr -// Submitted by Romain Fliedel -freebox-os.com -freeboxos.com -fbx-os.fr -fbxos.fr -freebox-os.fr -freeboxos.fr - -// freedesktop.org : https://www.freedesktop.org -// Submitted by Daniel Stone -freedesktop.org - -// freemyip.com : https://freemyip.com -// Submitted by Cadence -freemyip.com - -// FunkFeuer - Verein zur Förderung freier Netze : https://www.funkfeuer.at -// Submitted by Daniel A. Maierhofer -wien.funkfeuer.at - -// Futureweb OG : http://www.futureweb.at -// Submitted by Andreas Schnederle-Wagner -*.futurecms.at -*.ex.futurecms.at -*.in.futurecms.at -futurehosting.at -futuremailing.at -*.ex.ortsinfo.at -*.kunden.ortsinfo.at -*.statics.cloud - -// GDS : https://www.gov.uk/service-manual/technology/managing-domain-names -// Submitted by Stephen Ford -independent-commission.uk -independent-inquest.uk -independent-inquiry.uk -independent-panel.uk -independent-review.uk -public-inquiry.uk -royal-commission.uk -campaign.gov.uk -service.gov.uk - -// CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk -// Submitted by Jamie Tanna -api.gov.uk - -// Gehirn Inc. : https://www.gehirn.co.jp/ -// Submitted by Kohei YOSHIDA -gehirn.ne.jp -usercontent.jp - -// Gentlent, Inc. : https://www.gentlent.com -// Submitted by Tom Klein -gentapps.com -gentlentapis.com -lab.ms -cdn-edges.net - -// Ghost Foundation : https://ghost.org -// Submitted by Matt Hanley -ghost.io - -// GignoSystemJapan: http://gsj.bz -// Submitted by GignoSystemJapan -gsj.bz - -// GitHub, Inc. -// Submitted by Patrick Toomey -githubusercontent.com -githubpreview.dev -github.io - -// GitLab, Inc. -// Submitted by Alex Hanselka -gitlab.io - -// Gitplac.si - https://gitplac.si -// Submitted by Aljaž Starc -gitapp.si -gitpage.si - -// Glitch, Inc : https://glitch.com -// Submitted by Mads Hartmann -glitch.me - -// Global NOG Alliance : https://nogalliance.org/ -// Submitted by Sander Steffann -nog.community - -// Globe Hosting SRL : https://www.globehosting.com/ -// Submitted by Gavin Brown -co.ro -shop.ro - -// GMO Pepabo, Inc. : https://pepabo.com/ -// Submitted by Hosting Div -lolipop.io -angry.jp -babyblue.jp -babymilk.jp -backdrop.jp -bambina.jp -bitter.jp -blush.jp -boo.jp -boy.jp -boyfriend.jp -but.jp -candypop.jp -capoo.jp -catfood.jp -cheap.jp -chicappa.jp -chillout.jp -chips.jp -chowder.jp -chu.jp -ciao.jp -cocotte.jp -coolblog.jp -cranky.jp -cutegirl.jp -daa.jp -deca.jp -deci.jp -digick.jp -egoism.jp -fakefur.jp -fem.jp -flier.jp -floppy.jp -fool.jp -frenchkiss.jp -girlfriend.jp -girly.jp -gloomy.jp -gonna.jp -greater.jp -hacca.jp -heavy.jp -her.jp -hiho.jp -hippy.jp -holy.jp -hungry.jp -icurus.jp -itigo.jp -jellybean.jp -kikirara.jp -kill.jp -kilo.jp -kuron.jp -littlestar.jp -lolipopmc.jp -lolitapunk.jp -lomo.jp -lovepop.jp -lovesick.jp -main.jp -mods.jp -mond.jp -mongolian.jp -moo.jp -namaste.jp -nikita.jp -nobushi.jp -noor.jp -oops.jp -parallel.jp -parasite.jp -pecori.jp -peewee.jp -penne.jp -pepper.jp -perma.jp -pigboat.jp -pinoko.jp -punyu.jp -pupu.jp -pussycat.jp -pya.jp -raindrop.jp -readymade.jp -sadist.jp -schoolbus.jp -secret.jp -staba.jp -stripper.jp -sub.jp -sunnyday.jp -thick.jp -tonkotsu.jp -under.jp -upper.jp -velvet.jp -verse.jp -versus.jp -vivian.jp -watson.jp -weblike.jp -whitesnow.jp -zombie.jp -heteml.net - -// GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ -// Submitted by Tom Whitwell -cloudapps.digital -london.cloudapps.digital - -// GOV.UK Pay : https://www.payments.service.gov.uk/ -// Submitted by Richard Baker -pymnt.uk - -// UKHomeOffice : https://www.gov.uk/government/organisations/home-office -// Submitted by Jon Shanks -homeoffice.gov.uk - -// GlobeHosting, Inc. -// Submitted by Zoltan Egresi -ro.im - -// GoIP DNS Services : http://www.goip.de -// Submitted by Christian Poulter -goip.de - -// Google, Inc. -// Submitted by Eduardo Vela -run.app -a.run.app -web.app -*.0emm.com -appspot.com -*.r.appspot.com -codespot.com -googleapis.com -googlecode.com -pagespeedmobilizer.com -publishproxy.com -withgoogle.com -withyoutube.com -*.gateway.dev -cloud.goog -translate.goog -*.usercontent.goog -cloudfunctions.net -blogspot.ae -blogspot.al -blogspot.am -blogspot.ba -blogspot.be -blogspot.bg -blogspot.bj -blogspot.ca -blogspot.cf -blogspot.ch -blogspot.cl -blogspot.co.at -blogspot.co.id -blogspot.co.il -blogspot.co.ke -blogspot.co.nz -blogspot.co.uk -blogspot.co.za -blogspot.com -blogspot.com.ar -blogspot.com.au -blogspot.com.br -blogspot.com.by -blogspot.com.co -blogspot.com.cy -blogspot.com.ee -blogspot.com.eg -blogspot.com.es -blogspot.com.mt -blogspot.com.ng -blogspot.com.tr -blogspot.com.uy -blogspot.cv -blogspot.cz -blogspot.de -blogspot.dk -blogspot.fi -blogspot.fr -blogspot.gr -blogspot.hk -blogspot.hr -blogspot.hu -blogspot.ie -blogspot.in -blogspot.is -blogspot.it -blogspot.jp -blogspot.kr -blogspot.li -blogspot.lt -blogspot.lu -blogspot.md -blogspot.mk -blogspot.mr -blogspot.mx -blogspot.my -blogspot.nl -blogspot.no -blogspot.pe -blogspot.pt -blogspot.qa -blogspot.re -blogspot.ro -blogspot.rs -blogspot.ru -blogspot.se -blogspot.sg -blogspot.si -blogspot.sk -blogspot.sn -blogspot.td -blogspot.tw -blogspot.ug -blogspot.vn - -// Goupile : https://goupile.fr -// Submitted by Niels Martignene -goupile.fr - -// Government of the Netherlands: https://www.government.nl -// Submitted by -gov.nl - -// Group 53, LLC : https://www.group53.com -// Submitted by Tyler Todd -awsmppl.com - -// GünstigBestellen : https://günstigbestellen.de -// Submitted by Furkan Akkoc -günstigbestellen.de -günstigliefern.de - -// Hakaran group: http://hakaran.cz -// Submitted by Arseniy Sokolov -fin.ci -free.hr -caa.li -ua.rs -conf.se - -// Handshake : https://handshake.org -// Submitted by Mike Damm -hs.zone -hs.run - -// Hashbang : https://hashbang.sh -hashbang.sh - -// Hasura : https://hasura.io -// Submitted by Shahidh K Muhammed -hasura.app -hasura-app.io - -// Heilbronn University of Applied Sciences - Faculty Informatics (GitLab Pages): https://www.hs-heilbronn.de -// Submitted by Richard Zowalla -pages.it.hs-heilbronn.de - -// Hepforge : https://www.hepforge.org -// Submitted by David Grellscheid -hepforge.org - -// Heroku : https://www.heroku.com/ -// Submitted by Tom Maher -herokuapp.com -herokussl.com - -// Hibernating Rhinos -// Submitted by Oren Eini -ravendb.cloud -ravendb.community -ravendb.me -development.run -ravendb.run - -// home.pl S.A.: https://home.pl -// Submitted by Krzysztof Wolski -homesklep.pl - -// Hong Kong Productivity Council: https://www.hkpc.org/ -// Submitted by SECaaS Team -secaas.hk - -// Hoplix : https://www.hoplix.com -// Submitted by Danilo De Franco -hoplix.shop - - -// HOSTBIP REGISTRY : https://www.hostbip.com/ -// Submitted by Atanunu Igbunuroghene -orx.biz -biz.gl -col.ng -firm.ng -gen.ng -ltd.ng -ngo.ng -edu.scot -sch.so - -// HostFly : https://www.ie.ua -// Submitted by Bohdan Dub -ie.ua - -// HostyHosting (hostyhosting.com) -hostyhosting.io - -// Häkkinen.fi -// Submitted by Eero Häkkinen -häkkinen.fi - -// Ici la Lune : http://www.icilalune.com/ -// Submitted by Simon Morvan -*.moonscale.io -moonscale.net - -// iki.fi -// Submitted by Hannu Aronsson -iki.fi - -// iliad italia: https://www.iliad.it -// Submitted by Marios Makassikis -ibxos.it -iliadboxos.it - -// Impertrix Solutions : -// Submitted by Zhixiang Zhao -impertrixcdn.com -impertrix.com - -// Incsub, LLC: https://incsub.com/ -// Submitted by Aaron Edwards -smushcdn.com -wphostedmail.com -wpmucdn.com -tempurl.host -wpmudev.host - -// Individual Network Berlin e.V. : https://www.in-berlin.de/ -// Submitted by Christian Seitz -dyn-berlin.de -in-berlin.de -in-brb.de -in-butter.de -in-dsl.de -in-dsl.net -in-dsl.org -in-vpn.de -in-vpn.net -in-vpn.org - -// info.at : http://www.info.at/ -biz.at -info.at - -// info.cx : http://info.cx -// Submitted by Jacob Slater -info.cx - -// Interlegis : http://www.interlegis.leg.br -// Submitted by Gabriel Ferreira -ac.leg.br -al.leg.br -am.leg.br -ap.leg.br -ba.leg.br -ce.leg.br -df.leg.br -es.leg.br -go.leg.br -ma.leg.br -mg.leg.br -ms.leg.br -mt.leg.br -pa.leg.br -pb.leg.br -pe.leg.br -pi.leg.br -pr.leg.br -rj.leg.br -rn.leg.br -ro.leg.br -rr.leg.br -rs.leg.br -sc.leg.br -se.leg.br -sp.leg.br -to.leg.br - -// intermetrics GmbH : https://pixolino.com/ -// Submitted by Wolfgang Schwarz -pixolino.com - -// Internet-Pro, LLP: https://netangels.ru/ -// Submitted by Vasiliy Sheredeko -na4u.ru - -// iopsys software solutions AB : https://iopsys.eu/ -// Submitted by Roman Azarenko -iopsys.se - -// IPiFony Systems, Inc. : https://www.ipifony.com/ -// Submitted by Matthew Hardeman -ipifony.net - -// IServ GmbH : https://iserv.de -// Submitted by Mario Hoberg -iservschule.de -mein-iserv.de -schulplattform.de -schulserver.de -test-iserv.de -iserv.dev - -// I-O DATA DEVICE, INC. : http://www.iodata.com/ -// Submitted by Yuji Minagawa -iobb.net - -// Jelastic, Inc. : https://jelastic.com/ -// Submitted by Ihor Kolodyuk -mel.cloudlets.com.au -cloud.interhostsolutions.be -users.scale.virtualcloud.com.br -mycloud.by -alp1.ae.flow.ch -appengine.flow.ch -es-1.axarnet.cloud -diadem.cloud -vip.jelastic.cloud -jele.cloud -it1.eur.aruba.jenv-aruba.cloud -it1.jenv-aruba.cloud -keliweb.cloud -cs.keliweb.cloud -oxa.cloud -tn.oxa.cloud -uk.oxa.cloud -primetel.cloud -uk.primetel.cloud -ca.reclaim.cloud -uk.reclaim.cloud -us.reclaim.cloud -ch.trendhosting.cloud -de.trendhosting.cloud -jele.club -amscompute.com -clicketcloud.com -dopaas.com -hidora.com -paas.hosted-by-previder.com -rag-cloud.hosteur.com -rag-cloud-ch.hosteur.com -jcloud.ik-server.com -jcloud-ver-jpc.ik-server.com -demo.jelastic.com -kilatiron.com -paas.massivegrid.com -jed.wafaicloud.com -lon.wafaicloud.com -ryd.wafaicloud.com -j.scaleforce.com.cy -jelastic.dogado.eu -fi.cloudplatform.fi -demo.datacenter.fi -paas.datacenter.fi -jele.host -mircloud.host -paas.beebyte.io -sekd1.beebyteapp.io -jele.io -cloud-fr1.unispace.io -jc.neen.it -cloud.jelastic.open.tim.it -jcloud.kz -upaas.kazteleport.kz -cloudjiffy.net -fra1-de.cloudjiffy.net -west1-us.cloudjiffy.net -jls-sto1.elastx.net -jls-sto2.elastx.net -jls-sto3.elastx.net -faststacks.net -fr-1.paas.massivegrid.net -lon-1.paas.massivegrid.net -lon-2.paas.massivegrid.net -ny-1.paas.massivegrid.net -ny-2.paas.massivegrid.net -sg-1.paas.massivegrid.net -jelastic.saveincloud.net -nordeste-idc.saveincloud.net -j.scaleforce.net -jelastic.tsukaeru.net -sdscloud.pl -unicloud.pl -mircloud.ru -jelastic.regruhosting.ru -enscaled.sg -jele.site -jelastic.team -orangecloud.tn -j.layershift.co.uk -phx.enscaled.us -mircloud.us - -// Jino : https://www.jino.ru -// Submitted by Sergey Ulyashin -myjino.ru -*.hosting.myjino.ru -*.landing.myjino.ru -*.spectrum.myjino.ru -*.vps.myjino.ru - -// Jotelulu S.L. : https://jotelulu.com -// Submitted by Daniel Fariña -jotelulu.cloud - -// Joyent : https://www.joyent.com/ -// Submitted by Brian Bennett -*.triton.zone -*.cns.joyent.com - -// JS.ORG : http://dns.js.org -// Submitted by Stefan Keim -js.org - -// KaasHosting : http://www.kaashosting.nl/ -// Submitted by Wouter Bakker -kaas.gg -khplay.nl - -// Kakao : https://www.kakaocorp.com/ -// Submitted by JaeYoong Lee -ktistory.com - -// Kapsi : https://kapsi.fi -// Submitted by Tomi Juntunen -kapsi.fi - -// Keyweb AG : https://www.keyweb.de -// Submitted by Martin Dannehl -keymachine.de - -// KingHost : https://king.host -// Submitted by Felipe Keller Braz -kinghost.net -uni5.net - -// KnightPoint Systems, LLC : http://www.knightpoint.com/ -// Submitted by Roy Keene -knightpoint.systems - -// KoobinEvent, SL: https://www.koobin.com -// Submitted by Iván Oliva -koobin.events - -// KUROKU LTD : https://kuroku.ltd/ -// Submitted by DisposaBoy -oya.to - -// Katholieke Universiteit Leuven: https://www.kuleuven.be -// Submitted by Abuse KU Leuven -kuleuven.cloud -ezproxy.kuleuven.be - -// .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf -co.krd -edu.krd - -// Krellian Ltd. : https://krellian.com -// Submitted by Ben Francis -krellian.net -webthings.io - -// LCube - Professional hosting e.K. : https://www.lcube-webhosting.de -// Submitted by Lars Laehn -git-repos.de -lcube-server.de -svn-repos.de - -// Leadpages : https://www.leadpages.net -// Submitted by Greg Dallavalle -leadpages.co -lpages.co -lpusercontent.com - -// Lelux.fi : https://lelux.fi/ -// Submitted by Lelux Admin -lelux.site - -// Lifetime Hosting : https://Lifetime.Hosting/ -// Submitted by Mike Fillator -co.business -co.education -co.events -co.financial -co.network -co.place -co.technology - -// Lightmaker Property Manager, Inc. : https://app.lmpm.com/ -// Submitted by Greg Holland -app.lmpm.com - -// linkyard ldt: https://www.linkyard.ch/ -// Submitted by Mario Siegenthaler -linkyard.cloud -linkyard-cloud.ch - -// Linode : https://linode.com -// Submitted by -members.linode.com -*.nodebalancer.linode.com -*.linodeobjects.com -ip.linodeusercontent.com - -// LiquidNet Ltd : http://www.liquidnetlimited.com/ -// Submitted by Victor Velchev -we.bs - -// Localcert : https://localcert.dev -// Submitted by Lann Martin -*.user.localcert.dev - -// localzone.xyz -// Submitted by Kenny Niehage -localzone.xyz - -// Log'in Line : https://www.loginline.com/ -// Submitted by Rémi Mach -loginline.app -loginline.dev -loginline.io -loginline.services -loginline.site - -// Lokalized : https://lokalized.nl -// Submitted by Noah Taheij -servers.run - -// Lõhmus Family, The -// Submitted by Heiki Lõhmus -lohmus.me - -// LubMAN UMCS Sp. z o.o : https://lubman.pl/ -// Submitted by Ireneusz Maliszewski -krasnik.pl -leczna.pl -lubartow.pl -lublin.pl -poniatowa.pl -swidnik.pl - -// Lug.org.uk : https://lug.org.uk -// Submitted by Jon Spriggs -glug.org.uk -lug.org.uk -lugs.org.uk - -// Lukanet Ltd : https://lukanet.com -// Submitted by Anton Avramov -barsy.bg -barsy.co.uk -barsyonline.co.uk -barsycenter.com -barsyonline.com -barsy.club -barsy.de -barsy.eu -barsy.in -barsy.info -barsy.io -barsy.me -barsy.menu -barsy.mobi -barsy.net -barsy.online -barsy.org -barsy.pro -barsy.pub -barsy.ro -barsy.shop -barsy.site -barsy.support -barsy.uk - -// Magento Commerce -// Submitted by Damien Tournoud -*.magentosite.cloud - -// May First - People Link : https://mayfirst.org/ -// Submitted by Jamie McClelland -mayfirst.info -mayfirst.org - -// Mail.Ru Group : https://hb.cldmail.ru -// Submitted by Ilya Zaretskiy -hb.cldmail.ru - -// Mail Transfer Platform : https://www.neupeer.com -// Submitted by Li Hui -cn.vu - -// Maze Play: https://www.mazeplay.com -// Submitted by Adam Humpherys -mazeplay.com - -// mcpe.me : https://mcpe.me -// Submitted by Noa Heyl -mcpe.me - -// McHost : https://mchost.ru -// Submitted by Evgeniy Subbotin -mcdir.me -mcdir.ru -mcpre.ru -vps.mcdir.ru - -// Mediatech : https://mediatech.by -// Submitted by Evgeniy Kozhuhovskiy -mediatech.by -mediatech.dev - -// Medicom Health : https://medicomhealth.com -// Submitted by Michael Olson -hra.health - -// Memset hosting : https://www.memset.com -// Submitted by Tom Whitwell -miniserver.com -memset.net - -// Messerli Informatik AG : https://www.messerli.ch/ -// Submitted by Ruben Schmidmeister -messerli.app - -// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ -// Submitted by Zdeněk Šustr -*.cloud.metacentrum.cz -custom.metacentrum.cz - -// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ -// Submitted by Radim Janča -flt.cloud.muni.cz -usr.cloud.muni.cz - -// Meteor Development Group : https://www.meteor.com/hosting -// Submitted by Pierre Carrier -meteorapp.com -eu.meteorapp.com - -// Michau Enterprises Limited : http://www.co.pl/ -co.pl - -// Microsoft Corporation : http://microsoft.com -// Submitted by Public Suffix List Admin -*.azurecontainer.io -azurewebsites.net -azure-mobile.net -cloudapp.net -azurestaticapps.net -1.azurestaticapps.net -2.azurestaticapps.net -3.azurestaticapps.net -centralus.azurestaticapps.net -eastasia.azurestaticapps.net -eastus2.azurestaticapps.net -westeurope.azurestaticapps.net -westus2.azurestaticapps.net - -// minion.systems : http://minion.systems -// Submitted by Robert Böttinger -csx.cc - -// Mintere : https://mintere.com/ -// Submitted by Ben Aubin -mintere.site - -// MobileEducation, LLC : https://joinforte.com -// Submitted by Grayson Martin -forte.id - -// Mozilla Corporation : https://mozilla.com -// Submitted by Ben Francis -mozilla-iot.org - -// Mozilla Foundation : https://mozilla.org/ -// Submitted by glob -bmoattachments.org - -// MSK-IX : https://www.msk-ix.ru/ -// Submitted by Khannanov Roman -net.ru -org.ru -pp.ru - -// Mythic Beasts : https://www.mythic-beasts.com -// Submitted by Paul Cammish -hostedpi.com -customer.mythic-beasts.com -caracal.mythic-beasts.com -fentiger.mythic-beasts.com -lynx.mythic-beasts.com -ocelot.mythic-beasts.com -oncilla.mythic-beasts.com -onza.mythic-beasts.com -sphinx.mythic-beasts.com -vs.mythic-beasts.com -x.mythic-beasts.com -yali.mythic-beasts.com -cust.retrosnub.co.uk - -// Nabu Casa : https://www.nabucasa.com -// Submitted by Paulus Schoutsen -ui.nabu.casa - -// Net at Work Gmbh : https://www.netatwork.de -// Submitted by Jan Jaeschke -cloud.nospamproxy.com - -// Netlify : https://www.netlify.com -// Submitted by Jessica Parsons -netlify.app - -// Neustar Inc. -// Submitted by Trung Tran -4u.com - -// ngrok : https://ngrok.com/ -// Submitted by Alan Shreve -ngrok.app -ngrok-free.app -ngrok.dev -ngrok-free.dev -ngrok.io -ap.ngrok.io -au.ngrok.io -eu.ngrok.io -in.ngrok.io -jp.ngrok.io -sa.ngrok.io -us.ngrok.io -ngrok.pizza - -// Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/ -// Submitted by Nicholas Ford -nh-serv.co.uk - -// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ -// Submitted by Jeff Wheelhouse -nfshost.com - -// Noop : https://noop.app -// Submitted by Nathaniel Schweinberg -*.developer.app -noop.app - -// Northflank Ltd. : https://northflank.com/ -// Submitted by Marco Suter -*.northflank.app -*.build.run -*.code.run -*.database.run -*.migration.run - -// Noticeable : https://noticeable.io -// Submitted by Laurent Pellegrino -noticeable.news - -// Now-DNS : https://now-dns.com -// Submitted by Steve Russell -dnsking.ch -mypi.co -n4t.co -001www.com -ddnslive.com -myiphost.com -forumz.info -16-b.it -32-b.it -64-b.it -soundcast.me -tcp4.me -dnsup.net -hicam.net -now-dns.net -ownip.net -vpndns.net -dynserv.org -now-dns.org -x443.pw -now-dns.top -ntdll.top -freeddns.us -crafting.xyz -zapto.xyz - -// nsupdate.info : https://www.nsupdate.info/ -// Submitted by Thomas Waldmann -nsupdate.info -nerdpol.ovh - -// No-IP.com : https://noip.com/ -// Submitted by Deven Reza -blogsyte.com -brasilia.me -cable-modem.org -ciscofreak.com -collegefan.org -couchpotatofries.org -damnserver.com -ddns.me -ditchyourip.com -dnsfor.me -dnsiskinky.com -dvrcam.info -dynns.com -eating-organic.net -fantasyleague.cc -geekgalaxy.com -golffan.us -health-carereform.com -homesecuritymac.com -homesecuritypc.com -hopto.me -ilovecollege.info -loginto.me -mlbfan.org -mmafan.biz -myactivedirectory.com -mydissent.net -myeffect.net -mymediapc.net -mypsx.net -mysecuritycamera.com -mysecuritycamera.net -mysecuritycamera.org -net-freaks.com -nflfan.org -nhlfan.net -no-ip.ca -no-ip.co.uk -no-ip.net -noip.us -onthewifi.com -pgafan.net -point2this.com -pointto.us -privatizehealthinsurance.net -quicksytes.com -read-books.org -securitytactics.com -serveexchange.com -servehumour.com -servep2p.com -servesarcasm.com -stufftoread.com -ufcfan.org -unusualperson.com -workisboring.com -3utilities.com -bounceme.net -ddns.net -ddnsking.com -gotdns.ch -hopto.org -myftp.biz -myftp.org -myvnc.com -no-ip.biz -no-ip.info -no-ip.org -noip.me -redirectme.net -servebeer.com -serveblog.net -servecounterstrike.com -serveftp.com -servegame.com -servehalflife.com -servehttp.com -serveirc.com -serveminecraft.net -servemp3.com -servepics.com -servequake.com -sytes.net -webhop.me -zapto.org - -// NodeArt : https://nodeart.io -// Submitted by Konstantin Nosov -stage.nodeart.io - -// Nucleos Inc. : https://nucleos.com -// Submitted by Piotr Zduniak -pcloud.host - -// NYC.mn : http://www.information.nyc.mn -// Submitted by Matthew Brown -nyc.mn - -// Observable, Inc. : https://observablehq.com -// Submitted by Mike Bostock -static.observableusercontent.com - -// Octopodal Solutions, LLC. : https://ulterius.io/ -// Submitted by Andrew Sampson -cya.gg - -// OMG.LOL : -// Submitted by Adam Newbold -omg.lol - -// Omnibond Systems, LLC. : https://www.omnibond.com -// Submitted by Cole Estep -cloudycluster.net - -// OmniWe Limited: https://omniwe.com -// Submitted by Vicary Archangel -omniwe.site - -// One.com: https://www.one.com/ -// Submitted by Jacob Bunk Nielsen -123hjemmeside.dk -123hjemmeside.no -123homepage.it -123kotisivu.fi -123minsida.se -123miweb.es -123paginaweb.pt -123sait.ru -123siteweb.fr -123webseite.at -123webseite.de -123website.be -123website.ch -123website.lu -123website.nl -service.one -simplesite.com -simplesite.com.br -simplesite.gr -simplesite.pl - -// One Fold Media : http://www.onefoldmedia.com/ -// Submitted by Eddie Jones -nid.io - -// Open Social : https://www.getopensocial.com/ -// Submitted by Alexander Varwijk -opensocial.site - -// OpenCraft GmbH : http://opencraft.com/ -// Submitted by Sven Marnach -opencraft.hosting - -// OpenResearch GmbH: https://openresearch.com/ -// Submitted by Philipp Schmid -orsites.com - -// Opera Software, A.S.A. -// Submitted by Yngve Pettersen -operaunite.com - -// Orange : https://www.orange.com -// Submitted by Alexandre Linte -tech.orange - -// Oursky Limited : https://authgear.com/, https://skygear.io/ -// Submitted by Authgear Team , Skygear Developer -authgear-staging.com -authgearapps.com -skygearapp.com - -// OutSystems -// Submitted by Duarte Santos -outsystemscloud.com - -// OVHcloud: https://ovhcloud.com -// Submitted by Vincent Cassé -*.webpaas.ovh.net -*.hosting.ovh.net - -// OwnProvider GmbH: http://www.ownprovider.com -// Submitted by Jan Moennich -ownprovider.com -own.pm - -// OwO : https://whats-th.is/ -// Submitted by Dean Sheather -*.owo.codes - -// OX : http://www.ox.rs -// Submitted by Adam Grand -ox.rs - -// oy.lc -// Submitted by Charly Coste -oy.lc - -// Pagefog : https://pagefog.com/ -// Submitted by Derek Myers -pgfog.com - -// Pagefront : https://www.pagefronthq.com/ -// Submitted by Jason Kriss -pagefrontapp.com - -// PageXL : https://pagexl.com -// Submitted by Yann Guichard -pagexl.com - -// Paywhirl, Inc : https://paywhirl.com/ -// Submitted by Daniel Netzer -*.paywhirl.com - -// pcarrier.ca Software Inc: https://pcarrier.ca/ -// Submitted by Pierre Carrier -bar0.net -bar1.net -bar2.net -rdv.to - -// .pl domains (grandfathered) -art.pl -gliwice.pl -krakow.pl -poznan.pl -wroc.pl -zakopane.pl - -// Pantheon Systems, Inc. : https://pantheon.io/ -// Submitted by Gary Dylina -pantheonsite.io -gotpantheon.com - -// Peplink | Pepwave : http://peplink.com/ -// Submitted by Steve Leung -mypep.link - -// Perspecta : https://perspecta.com/ -// Submitted by Kenneth Van Alstyne -perspecta.cloud - -// PE Ulyanov Kirill Sergeevich : https://airy.host -// Submitted by Kirill Ulyanov -lk3.ru - -// Planet-Work : https://www.planet-work.com/ -// Submitted by Frédéric VANNIÈRE -on-web.fr - -// Platform.sh : https://platform.sh -// Submitted by Nikola Kotur -bc.platform.sh -ent.platform.sh -eu.platform.sh -us.platform.sh -*.platformsh.site -*.tst.site - -// Platter: https://platter.dev -// Submitted by Patrick Flor -platter-app.com -platter-app.dev -platterp.us - -// Plesk : https://www.plesk.com/ -// Submitted by Anton Akhtyamov -pdns.page -plesk.page -pleskns.com - -// Port53 : https://port53.io/ -// Submitted by Maximilian Schieder -dyn53.io - -// Porter : https://porter.run/ -// Submitted by Rudraksh MK -onporter.run - -// Positive Codes Technology Company : http://co.bn/faq.html -// Submitted by Zulfais -co.bn - -// Postman, Inc : https://postman.com -// Submitted by Rahul Dhawan -postman-echo.com -pstmn.io -mock.pstmn.io -httpbin.org - -//prequalifyme.today : https://prequalifyme.today -//Submitted by DeepakTiwari deepak@ivylead.io -prequalifyme.today - -// prgmr.com : https://prgmr.com/ -// Submitted by Sarah Newman -xen.prgmr.com - -// priv.at : http://www.nic.priv.at/ -// Submitted by registry -priv.at - -// privacytools.io : https://www.privacytools.io/ -// Submitted by Jonah Aragon -prvcy.page - -// Protocol Labs : https://protocol.ai/ -// Submitted by Michael Burns -*.dweb.link - -// Protonet GmbH : http://protonet.io -// Submitted by Martin Meier -protonet.io - -// Publication Presse Communication SARL : https://ppcom.fr -// Submitted by Yaacov Akiba Slama -chirurgiens-dentistes-en-france.fr -byen.site - -// pubtls.org: https://www.pubtls.org -// Submitted by Kor Nielsen -pubtls.org - -// PythonAnywhere LLP: https://www.pythonanywhere.com -// Submitted by Giles Thomas -pythonanywhere.com -eu.pythonanywhere.com - -// QOTO, Org. -// Submitted by Jeffrey Phillips Freeman -qoto.io - -// Qualifio : https://qualifio.com/ -// Submitted by Xavier De Cock -qualifioapp.com - -// Quality Unit: https://qualityunit.com -// Submitted by Vasyl Tsalko -ladesk.com - -// QuickBackend: https://www.quickbackend.com -// Submitted by Dani Biro -qbuser.com - -// Rad Web Hosting: https://radwebhosting.com -// Submitted by Scott Claeys -cloudsite.builders - -// Redgate Software: https://red-gate.com -// Submitted by Andrew Farries -instances.spawn.cc - -// Redstar Consultants : https://www.redstarconsultants.com/ -// Submitted by Jons Slemmer -instantcloud.cn - -// Russian Academy of Sciences -// Submitted by Tech Support -ras.ru - -// QA2 -// Submitted by Daniel Dent (https://www.danieldent.com/) -qa2.com - -// QCX -// Submitted by Cassandra Beelen -qcx.io -*.sys.qcx.io - -// QNAP System Inc : https://www.qnap.com -// Submitted by Nick Chang -dev-myqnapcloud.com -alpha-myqnapcloud.com -myqnapcloud.com - -// Quip : https://quip.com -// Submitted by Patrick Linehan -*.quipelements.com - -// Qutheory LLC : http://qutheory.io -// Submitted by Jonas Schwartz -vapor.cloud -vaporcloud.io - -// Rackmaze LLC : https://www.rackmaze.com -// Submitted by Kirill Pertsev -rackmaze.com -rackmaze.net - -// Rakuten Games, Inc : https://dev.viberplay.io -// Submitted by Joshua Zhang -g.vbrplsbx.io - -// Rancher Labs, Inc : https://rancher.com -// Submitted by Vincent Fiduccia -*.on-k3s.io -*.on-rancher.cloud -*.on-rio.io - -// Read The Docs, Inc : https://www.readthedocs.org -// Submitted by David Fischer -readthedocs.io - -// Red Hat, Inc. OpenShift : https://openshift.redhat.com/ -// Submitted by Tim Kramer -rhcloud.com - -// Render : https://render.com -// Submitted by Anurag Goel -app.render.com -onrender.com - -// Repl.it : https://repl.it -// Submitted by Lincoln Bergeson -firewalledreplit.co -id.firewalledreplit.co -repl.co -id.repl.co -repl.run - -// Resin.io : https://resin.io -// Submitted by Tim Perry -resindevice.io -devices.resinstaging.io - -// RethinkDB : https://www.rethinkdb.com/ -// Submitted by Chris Kastorff -hzc.io - -// Revitalised Limited : http://www.revitalised.co.uk -// Submitted by Jack Price -wellbeingzone.eu -wellbeingzone.co.uk - -// Rico Developments Limited : https://adimo.co -// Submitted by Colin Brown -adimo.co.uk - -// Riseup Networks : https://riseup.net -// Submitted by Micah Anderson -itcouldbewor.se - -// Rochester Institute of Technology : http://www.rit.edu/ -// Submitted by Jennifer Herting -git-pages.rit.edu - -// Rocky Enterprise Software Foundation : https://resf.org -// Submitted by Neil Hanlon -rocky.page - -// Rusnames Limited: http://rusnames.ru/ -// Submitted by Sergey Zotov -биз.рус -ком.рус -крым.рус -мир.рус -мск.рус -орг.рус -самара.рус -сочи.рус -спб.рус -я.рус - -// SAKURA Internet Inc. : https://www.sakura.ad.jp/ -// Submitted by Internet Service Department -180r.com -dojin.com -sakuratan.com -sakuraweb.com -x0.com -2-d.jp -bona.jp -crap.jp -daynight.jp -eek.jp -flop.jp -halfmoon.jp -jeez.jp -matrix.jp -mimoza.jp -ivory.ne.jp -mail-box.ne.jp -mints.ne.jp -mokuren.ne.jp -opal.ne.jp -sakura.ne.jp -sumomo.ne.jp -topaz.ne.jp -netgamers.jp -nyanta.jp -o0o0.jp -rdy.jp -rgr.jp -rulez.jp -s3.isk01.sakurastorage.jp -s3.isk02.sakurastorage.jp -saloon.jp -sblo.jp -skr.jp -tank.jp -uh-oh.jp -undo.jp -rs.webaccel.jp -user.webaccel.jp -websozai.jp -xii.jp -squares.net -jpn.org -kirara.st -x0.to -from.tv -sakura.tv - -// Salesforce.com, Inc. https://salesforce.com/ -// Submitted by Michael Biven -*.builder.code.com -*.dev-builder.code.com -*.stg-builder.code.com - -// Sandstorm Development Group, Inc. : https://sandcats.io/ -// Submitted by Asheesh Laroia -sandcats.io - -// SBE network solutions GmbH : https://www.sbe.de/ -// Submitted by Norman Meilick -logoip.de -logoip.com - -// Scaleway : https://www.scaleway.com/ -// Submitted by Rémy Léone -fr-par-1.baremetal.scw.cloud -fr-par-2.baremetal.scw.cloud -nl-ams-1.baremetal.scw.cloud -fnc.fr-par.scw.cloud -functions.fnc.fr-par.scw.cloud -k8s.fr-par.scw.cloud -nodes.k8s.fr-par.scw.cloud -s3.fr-par.scw.cloud -s3-website.fr-par.scw.cloud -whm.fr-par.scw.cloud -priv.instances.scw.cloud -pub.instances.scw.cloud -k8s.scw.cloud -k8s.nl-ams.scw.cloud -nodes.k8s.nl-ams.scw.cloud -s3.nl-ams.scw.cloud -s3-website.nl-ams.scw.cloud -whm.nl-ams.scw.cloud -k8s.pl-waw.scw.cloud -nodes.k8s.pl-waw.scw.cloud -s3.pl-waw.scw.cloud -s3-website.pl-waw.scw.cloud -scalebook.scw.cloud -smartlabeling.scw.cloud -dedibox.fr - -// schokokeks.org GbR : https://schokokeks.org/ -// Submitted by Hanno Böck -schokokeks.net - -// Scottish Government: https://www.gov.scot -// Submitted by Martin Ellis -gov.scot -service.gov.scot - -// Scry Security : http://www.scrysec.com -// Submitted by Shante Adam -scrysec.com - -// Securepoint GmbH : https://www.securepoint.de -// Submitted by Erik Anders -firewall-gateway.com -firewall-gateway.de -my-gateway.de -my-router.de -spdns.de -spdns.eu -firewall-gateway.net -my-firewall.org -myfirewall.org -spdns.org - -// Seidat : https://www.seidat.com -// Submitted by Artem Kondratev -seidat.net - -// Sellfy : https://sellfy.com -// Submitted by Yuriy Romadin -sellfy.store - -// Senseering GmbH : https://www.senseering.de -// Submitted by Felix Mönckemeyer -senseering.net - -// Sendmsg: https://www.sendmsg.co.il -// Submitted by Assaf Stern -minisite.ms - -// Service Magnet : https://myservicemagnet.com -// Submitted by Dave Sanders -magnet.page - -// Service Online LLC : http://drs.ua/ -// Submitted by Serhii Bulakh -biz.ua -co.ua -pp.ua - -// Shift Crypto AG : https://shiftcrypto.ch -// Submitted by alex -shiftcrypto.dev -shiftcrypto.io - -// ShiftEdit : https://shiftedit.net/ -// Submitted by Adam Jimenez -shiftedit.io - -// Shopblocks : http://www.shopblocks.com/ -// Submitted by Alex Bowers -myshopblocks.com - -// Shopify : https://www.shopify.com -// Submitted by Alex Richter -myshopify.com - -// Shopit : https://www.shopitcommerce.com/ -// Submitted by Craig McMahon -shopitsite.com - -// shopware AG : https://shopware.com -// Submitted by Jens Küper -shopware.store - -// Siemens Mobility GmbH -// Submitted by Oliver Graebner -mo-siemens.io - -// SinaAppEngine : http://sae.sina.com.cn/ -// Submitted by SinaAppEngine -1kapp.com -appchizi.com -applinzi.com -sinaapp.com -vipsinaapp.com - -// Siteleaf : https://www.siteleaf.com/ -// Submitted by Skylar Challand -siteleaf.net - -// Skyhat : http://www.skyhat.io -// Submitted by Shante Adam -bounty-full.com -alpha.bounty-full.com -beta.bounty-full.com - -// Small Technology Foundation : https://small-tech.org -// Submitted by Aral Balkan -small-web.org - -// Smoove.io : https://www.smoove.io/ -// Submitted by Dan Kozak -vp4.me - -// Snowflake Inc : https://www.snowflake.com/ -// Submitted by Faith Olapade -snowflake.app -privatelink.snowflake.app -streamlit.app -streamlitapp.com - -// Snowplow Analytics : https://snowplowanalytics.com/ -// Submitted by Ian Streeter -try-snowplow.com - -// SourceHut : https://sourcehut.org -// Submitted by Drew DeVault -srht.site - -// Stackhero : https://www.stackhero.io -// Submitted by Adrien Gillon -stackhero-network.com - -// Staclar : https://staclar.com -// Submitted by Q Misell -musician.io -// Submitted by Matthias Merkel -novecore.site - -// staticland : https://static.land -// Submitted by Seth Vincent -static.land -dev.static.land -sites.static.land - -// Storebase : https://www.storebase.io -// Submitted by Tony Schirmer -storebase.store - -// Strategic System Consulting (eApps Hosting): https://www.eapps.com/ -// Submitted by Alex Oancea -vps-host.net -atl.jelastic.vps-host.net -njs.jelastic.vps-host.net -ric.jelastic.vps-host.net - -// Sony Interactive Entertainment LLC : https://sie.com/ -// Submitted by David Coles -playstation-cloud.com - -// SourceLair PC : https://www.sourcelair.com -// Submitted by Antonis Kalipetis -apps.lair.io -*.stolos.io - -// SpaceKit : https://www.spacekit.io/ -// Submitted by Reza Akhavan -spacekit.io - -// SpeedPartner GmbH: https://www.speedpartner.de/ -// Submitted by Stefan Neufeind -customer.speedpartner.de - -// Spreadshop (sprd.net AG) : https://www.spreadshop.com/ -// Submitted by Martin Breest -myspreadshop.at -myspreadshop.com.au -myspreadshop.be -myspreadshop.ca -myspreadshop.ch -myspreadshop.com -myspreadshop.de -myspreadshop.dk -myspreadshop.es -myspreadshop.fi -myspreadshop.fr -myspreadshop.ie -myspreadshop.it -myspreadshop.net -myspreadshop.nl -myspreadshop.no -myspreadshop.pl -myspreadshop.se -myspreadshop.co.uk - -// Standard Library : https://stdlib.com -// Submitted by Jacob Lee -api.stdlib.com - -// Storj Labs Inc. : https://storj.io/ -// Submitted by Philip Hutchins -storj.farm - -// Studenten Net Twente : http://www.snt.utwente.nl/ -// Submitted by Silke Hofstra -utwente.io - -// Student-Run Computing Facility : https://www.srcf.net/ -// Submitted by Edwin Balani -soc.srcf.net -user.srcf.net - -// Sub 6 Limited: http://www.sub6.com -// Submitted by Dan Miller -temp-dns.com - -// Supabase : https://supabase.io -// Submitted by Inian Parameshwaran -supabase.co -supabase.in -supabase.net -su.paba.se - -// Symfony, SAS : https://symfony.com/ -// Submitted by Fabien Potencier -*.s5y.io -*.sensiosite.cloud - -// Syncloud : https://syncloud.org -// Submitted by Boris Rybalkin -syncloud.it - -// Synology, Inc. : https://www.synology.com/ -// Submitted by Rony Weng -dscloud.biz -direct.quickconnect.cn -dsmynas.com -familyds.com -diskstation.me -dscloud.me -i234.me -myds.me -synology.me -dscloud.mobi -dsmynas.net -familyds.net -dsmynas.org -familyds.org -vpnplus.to -direct.quickconnect.to - -// Tabit Technologies Ltd. : https://tabit.cloud/ -// Submitted by Oren Agiv -tabitorder.co.il -mytabit.co.il -mytabit.com - -// TAIFUN Software AG : http://taifun-software.de -// Submitted by Bjoern Henke -taifun-dns.de - -// Tailscale Inc. : https://www.tailscale.com -// Submitted by David Anderson -beta.tailscale.net -ts.net - -// TASK geographical domains (www.task.gda.pl/uslugi/dns) -gda.pl -gdansk.pl -gdynia.pl -med.pl -sopot.pl - -// team.blue https://team.blue -// Submitted by Cedric Dubois -site.tb-hosting.com - -// Teckids e.V. : https://www.teckids.org -// Submitted by Dominik George -edugit.io -s3.teckids.org - -// Telebit : https://telebit.cloud -// Submitted by AJ ONeal -telebit.app -telebit.io -*.telebit.xyz - -// Thingdust AG : https://thingdust.com/ -// Submitted by Adrian Imboden -*.firenet.ch -*.svc.firenet.ch -reservd.com -thingdustdata.com -cust.dev.thingdust.io -cust.disrec.thingdust.io -cust.prod.thingdust.io -cust.testing.thingdust.io -reservd.dev.thingdust.io -reservd.disrec.thingdust.io -reservd.testing.thingdust.io - -// ticket i/O GmbH : https://ticket.io -// Submitted by Christian Franke -tickets.io - -// Tlon.io : https://tlon.io -// Submitted by Mark Staarink -arvo.network -azimuth.network -tlon.network - -// Tor Project, Inc. : https://torproject.org -// Submitted by Antoine Beaupré -bloxcms.com -townnews-staging.com - -// TrafficPlex GmbH : https://www.trafficplex.de/ -// Submitted by Phillipp Röll -12hp.at -2ix.at -4lima.at -lima-city.at -12hp.ch -2ix.ch -4lima.ch -lima-city.ch -trafficplex.cloud -de.cool -12hp.de -2ix.de -4lima.de -lima-city.de -1337.pictures -clan.rip -lima-city.rocks -webspace.rocks -lima.zone - -// TransIP : https://www.transip.nl -// Submitted by Rory Breuk -*.transurl.be -*.transurl.eu -*.transurl.nl - -// TransIP: https://www.transip.nl -// Submitted by Cedric Dubois -site.transip.me - -// TuxFamily : http://tuxfamily.org -// Submitted by TuxFamily administrators -tuxfamily.org - -// TwoDNS : https://www.twodns.de/ -// Submitted by TwoDNS-Support -dd-dns.de -diskstation.eu -diskstation.org -dray-dns.de -draydns.de -dyn-vpn.de -dynvpn.de -mein-vigor.de -my-vigor.de -my-wan.de -syno-ds.de -synology-diskstation.de -synology-ds.de - -// Typedream : https://typedream.com -// Submitted by Putri Karunia -typedream.app - -// Typeform : https://www.typeform.com -// Submitted by Sergi Ferriz -pro.typeform.com - -// Uberspace : https://uberspace.de -// Submitted by Moritz Werner -uber.space -*.uberspace.de - -// UDR Limited : http://www.udr.hk.com -// Submitted by registry -hk.com -hk.org -ltd.hk -inc.hk - -// UK Intis Telecom LTD : https://it.com -// Submitted by ITComdomains -it.com - -// UNIVERSAL DOMAIN REGISTRY : https://www.udr.org.yt/ -// see also: whois -h whois.udr.org.yt help -// Submitted by Atanunu Igbunuroghene -name.pm -sch.tf -biz.wf -sch.wf -org.yt - -// United Gameserver GmbH : https://united-gameserver.de -// Submitted by Stefan Schwarz -virtualuser.de -virtual-user.de - -// Upli : https://upli.io -// Submitted by Lenny Bakkalian -upli.io - -// urown.net : https://urown.net -// Submitted by Hostmaster -urown.cloud -dnsupdate.info - -// .US -// Submitted by Ed Moore -lib.de.us - -// VeryPositive SIA : http://very.lv -// Submitted by Danko Aleksejevs -2038.io - -// Vercel, Inc : https://vercel.com/ -// Submitted by Connor Davis -vercel.app -vercel.dev -now.sh - -// Viprinet Europe GmbH : http://www.viprinet.com -// Submitted by Simon Kissel -router.management - -// Virtual-Info : https://www.virtual-info.info/ -// Submitted by Adnan RIHAN -v-info.info - -// Voorloper.com: https://voorloper.com -// Submitted by Nathan van Bakel -voorloper.cloud - -// Voxel.sh DNS : https://voxel.sh/dns/ -// Submitted by Mia Rehlinger -neko.am -nyaa.am -be.ax -cat.ax -es.ax -eu.ax -gg.ax -mc.ax -us.ax -xy.ax -nl.ci -xx.gl -app.gp -blog.gt -de.gt -to.gt -be.gy -cc.hn -blog.kg -io.kg -jp.kg -tv.kg -uk.kg -us.kg -de.ls -at.md -de.md -jp.md -to.md -indie.porn -vxl.sh -ch.tc -me.tc -we.tc -nyan.to -at.vg -blog.vu -dev.vu -me.vu - -// V.UA Domain Administrator : https://domain.v.ua/ -// Submitted by Serhii Rostilo -v.ua - -// Vultr Objects : https://www.vultr.com/products/object-storage/ -// Submitted by Niels Maumenee -*.vultrobjects.com - -// Waffle Computer Inc., Ltd. : https://docs.waffleinfo.com -// Submitted by Masayuki Note -wafflecell.com - -// WebHare bv: https://www.webhare.com/ -// Submitted by Arnold Hendriks -*.webhare.dev - -// WebHotelier Technologies Ltd: https://www.webhotelier.net/ -// Submitted by Apostolos Tsakpinis -reserve-online.net -reserve-online.com -bookonline.app -hotelwithflight.com - -// WeDeploy by Liferay, Inc. : https://www.wedeploy.com -// Submitted by Henrique Vicente -wedeploy.io -wedeploy.me -wedeploy.sh - -// Western Digital Technologies, Inc : https://www.wdc.com -// Submitted by Jung Jin -remotewd.com - -// WIARD Enterprises : https://wiardweb.com -// Submitted by Kidd Hustle -pages.wiardweb.com - -// Wikimedia Labs : https://wikitech.wikimedia.org -// Submitted by Arturo Borrero Gonzalez -wmflabs.org -toolforge.org -wmcloud.org - -// WISP : https://wisp.gg -// Submitted by Stepan Fedotov -panel.gg -daemon.panel.gg - -// Wizard Zines : https://wizardzines.com -// Submitted by Julia Evans -messwithdns.com - -// WoltLab GmbH : https://www.woltlab.com -// Submitted by Tim Düsterhus -woltlab-demo.com -myforum.community -community-pro.de -diskussionsbereich.de -community-pro.net -meinforum.net - -// Woods Valldata : https://www.woodsvalldata.co.uk/ -// Submitted by Chris Whittle -affinitylottery.org.uk -raffleentry.org.uk -weeklylottery.org.uk - -// WP Engine : https://wpengine.com/ -// Submitted by Michael Smith -// Submitted by Brandon DuRette -wpenginepowered.com -js.wpenginepowered.com - -// Wix.com, Inc. : https://www.wix.com -// Submitted by Shahar Talmi -wixsite.com -editorx.io - -// XenonCloud GbR: https://xenoncloud.net -// Submitted by Julian Uphoff -half.host - -// XnBay Technology : http://www.xnbay.com/ -// Submitted by XnBay Developer -xnbay.com -u2.xnbay.com -u2-local.xnbay.com - -// XS4ALL Internet bv : https://www.xs4all.nl/ -// Submitted by Daniel Mostertman -cistron.nl -demon.nl -xs4all.space - -// Yandex.Cloud LLC: https://cloud.yandex.com -// Submitted by Alexander Lodin -yandexcloud.net -storage.yandexcloud.net -website.yandexcloud.net - -// YesCourse Pty Ltd : https://yescourse.com -// Submitted by Atul Bhouraskar -official.academy - -// Yola : https://www.yola.com/ -// Submitted by Stefano Rivera -yolasite.com - -// Yombo : https://yombo.net -// Submitted by Mitch Schwenk -ybo.faith -yombo.me -homelink.one -ybo.party -ybo.review -ybo.science -ybo.trade - -// Yunohost : https://yunohost.org -// Submitted by Valentin Grimaud -ynh.fr -nohost.me -noho.st - -// ZaNiC : http://www.za.net/ -// Submitted by registry -za.net -za.org - -// Zine EOOD : https://zine.bg/ -// Submitted by Martin Angelov -bss.design - -// Zitcom A/S : https://www.zitcom.dk -// Submitted by Emil Stahl -basicserver.io -virtualserver.io -enterprisecloud.nu - -// ===END PRIVATE DOMAINS=== diff --git a/sample_data/import_nsf.txt b/sample_data/import_nsf.txt new file mode 100644 index 000000000..01b75dedb --- /dev/null +++ b/sample_data/import_nsf.txt @@ -0,0 +1,499 @@ +{ + "shared_folders": [ + { + "path": "NSF All Types\\Credentials", + "manage_users": true, + "manage_records": true, + "can_edit": true, + "can_share": true, + "permissions": [ + { + "name": "user@mycompany.com", + "manage_users": false, + "manage_records": true + }, + { + "name": "admin@mycompany.com", + "manage_users": true, + "manage_records": true + } + ] + }, + { + "path": "NSF All Types\\Personal", + "manage_users": false, + "manage_records": true, + "can_edit": true, + "can_share": false, + "permissions": [ + { + "name": "user@mycompany.com", + "manage_users": false, + "manage_records": false + } + ] + }, + { + "path": "NSF All Types\\Infrastructure\\Servers", + "manage_users": true, + "manage_records": true, + "can_edit": true, + "can_share": true + }, + { + "path": "NSF All Types\\Infrastructure\\Databases", + "manage_users": true, + "manage_records": true, + "can_edit": true, + "can_share": false + } + ], + "records": [ + { + "uid": "AddrRec00000000000001", + "title": "Sample - Address", + "$type": "address", + "notes": "Home address sample", + "custom_fields": { + "$address": { + "street1": "100 Main Street", + "street2": "Apt 4B", + "city": "San Francisco", + "state": "CA", + "zip": "94105", + "country": "US" + } + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Identity" + } + ] + }, + { + "uid": "CardRec00000000000001", + "title": "Sample - Payment Card", + "$type": "bankCard", + "notes": "Visa demo card", + "custom_fields": { + "$paymentCard": { + "cardNumber": "4111111111111111", + "cardExpirationDate": "12/2030", + "cardSecurityCode": "123" + }, + "$text:cardholderName": "Jane Doe", + "$pinCode": "4321", + "$addressRef": "AddrRec00000000000001" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Finance" + } + ] + }, + { + "title": "Sample - Bank Account", + "$type": "bankAccount", + "login": "jdoe", + "password": "BankPass!234", + "login_url": "https://bank.example.com", + "notes": "Checking account sample", + "custom_fields": { + "$bankAccount": { + "accountType": "Checking", + "routingNumber": "021000021", + "accountNumber": "1234567890", + "otherType": "" + }, + "$name": { + "first": "Jane", + "middle": "Q", + "last": "Doe" + }, + "$cardRef": "CardRec00000000000001" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Finance" + } + ] + }, + { + "title": "Sample - Login", + "$type": "login", + "login": "jane.doe@example.com", + "password": "LoginPass!234", + "login_url": "https://accounts.example.com", + "notes": "Standard login with TOTP and security question", + "custom_fields": { + "$oneTimeCode": "otpauth://totp/Example:jane.doe@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30", + "$securityQuestion": [ + { + "question": "What is your favorite color?", + "answer": "Blue" + }, + { + "question": "Pet name?", + "answer": "Fluffy" + } + ], + "Department": "Engineering", + "Employee ID": "E-10042" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Credentials", + "folder": "Websites", + "can_edit": true, + "can_share": true + } + ] + }, + { + "title": "Sample - General", + "$type": "general", + "login": "general-user", + "password": "GeneralPass!234", + "login_url": "https://general.example.com", + "notes": "Catch-all / general purpose record", + "custom_fields": { + "$text:label": "Misc info", + "Freeform Field": "Any custom value" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Credentials", + "folder": "Misc" + } + ] + }, + { + "title": "Sample - Encrypted Notes", + "$type": "encryptedNotes", + "notes": "Visible notes field", + "custom_fields": { + "$note": "This is a secured / masked note body.\nLine 2 of the secure note.", + "$date": 1719792000000 + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Notes" + } + ] + }, + { + "title": "Sample - Contact", + "$type": "contact", + "notes": "Business contact", + "custom_fields": { + "$name": { + "first": "John", + "middle": "A", + "last": "Smith" + }, + "$text:company": "Acme Corp", + "$email": "john.smith@acme.example.com", + "$phone": [ + { + "region": "+1", + "number": "415-555-0100", + "ext": "200", + "type": "Work" + }, + { + "region": "+1", + "number": "415-555-0199", + "type": "Mobile" + } + ], + "$addressRef": "AddrRec00000000000001" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Contacts" + } + ] + }, + { + "title": "Sample - Birth Certificate", + "$type": "birthCertificate", + "notes": "Birth certificate sample", + "custom_fields": { + "$name": { + "first": "Jane", + "middle": "Q", + "last": "Doe" + }, + "$birthDate": 631152000000 + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Identity" + } + ] + }, + { + "title": "Sample - Driver License", + "$type": "driverLicense", + "notes": "Driver license sample", + "custom_fields": { + "$accountNumber": "D1234567", + "$name": { + "first": "Jane", + "middle": "Q", + "last": "Doe" + }, + "$birthDate": 631152000000, + "$expirationDate": 1893456000000, + "$addressRef": "AddrRec00000000000001" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Identity" + } + ] + }, + { + "title": "Sample - Passport", + "$type": "passport", + "password": "PassportPin!99", + "notes": "Passport sample", + "custom_fields": { + "$accountNumber": "P123456789", + "$name": { + "first": "Jane", + "middle": "Q", + "last": "Doe" + }, + "$birthDate": 631152000000, + "$expirationDate": 1924905600000, + "$date:dateIssued": 1609459200000, + "$addressRef": "AddrRec00000000000001" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Identity" + } + ] + }, + { + "title": "Sample - SSN / Identity Card", + "$type": "ssnCard", + "notes": "Identity card sample", + "custom_fields": { + "$accountNumber": "123-45-6789", + "$name": { + "first": "Jane", + "middle": "Q", + "last": "Doe" + } + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Identity" + } + ] + }, + { + "title": "Sample - Health Insurance", + "$type": "healthInsurance", + "login": "member123", + "password": "HealthPass!234", + "login_url": "https://health.example.com", + "notes": "Health insurance sample", + "custom_fields": { + "$accountNumber": "HIN-998877", + "$name": { + "first": "Jane", + "middle": "Q", + "last": "Doe" + } + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Insurance" + } + ] + }, + { + "title": "Sample - Membership", + "$type": "membership", + "password": "MemberPass!234", + "notes": "Gym / club membership", + "custom_fields": { + "$accountNumber": "MEM-554433", + "$name": { + "first": "Jane", + "middle": "Q", + "last": "Doe" + } + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Memberships" + } + ] + }, + { + "title": "Sample - Software License", + "$type": "softwareLicense", + "notes": "Software license key sample", + "custom_fields": { + "$licenseNumber": "AAAA-BBBB-CCCC-DDDD-EEEE", + "$expirationDate": 1893456000000, + "$date:dateActive": 1704067200000, + "$text:product": "Example Pro Suite", + "$text:seats": "10" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Credentials", + "folder": "Licenses" + } + ] + }, + { + "title": "Sample - Server Credentials", + "$type": "serverCredentials", + "login": "root", + "password": "ServerPass!234", + "notes": "Linux / Windows server", + "custom_fields": { + "$host": { + "hostName": "10.0.1.30", + "port": "22" + }, + "$text:platformId": "UnixSSH", + "$oneTimeCode": "otpauth://totp/Server:root?secret=JBSWY3DPEHPK3PXP&issuer=Server&algorithm=SHA1&digits=6&period=30" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Infrastructure\\Servers", + "folder": "Unix", + "can_edit": true, + "can_share": false + } + ] + }, + { + "title": "Sample - Database Credentials", + "$type": "databaseCredentials", + "login": "dbadmin", + "password": "DbPass!234", + "notes": "PostgreSQL sample", + "custom_fields": { + "$text:type": "postgresql", + "$host": { + "hostName": "db.example.com", + "port": "5432" + } + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Infrastructure\\Databases", + "can_edit": true, + "can_share": false + } + ] + }, + { + "title": "Sample - SSH Keys", + "$type": "sshKeys", + "login": "deploy", + "password": "KeyPassphrase!234", + "notes": "SSH key pair sample", + "custom_fields": { + "$host": { + "hostName": "git.example.com", + "port": "22" + }, + "$keyPair": { + "publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExamplePublicKeyOnlyNotReal deploy@example.com", + "privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\nEXAMPLE_PRIVATE_KEY_PLACEHOLDER_NOT_REAL\n-----END OPENSSH PRIVATE KEY-----" + } + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Infrastructure\\Servers", + "folder": "SSH Keys", + "can_edit": true, + "can_share": false + } + ] + }, + { + "title": "Sample - WiFi Credentials", + "$type": "wifiCredentials", + "password": "WifiPass!234", + "notes": "Office WiFi", + "custom_fields": { + "$text:ssid": "Office-Guest", + "$text:networkSecurityType": "WPA2", + "$text:hiddenNetwork": "false" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Credentials", + "folder": "Networks" + } + ] + }, + { + "title": "Sample - File Attachment Placeholder", + "$type": "file", + "notes": "File records are normally created when uploading attachments. This is a placeholder title/notes-only sample.", + "custom_fields": {}, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Files" + } + ] + }, + { + "title": "Sample - Photo Placeholder", + "$type": "photo", + "notes": "Photo records are normally created when uploading images. Placeholder sample only.", + "custom_fields": {}, + "folders": [ + { + "shared_folder": "NSF All Types\\Personal", + "folder": "Photos" + } + ] + }, + { + "title": "Sample - Multi Folder Placement", + "$type": "login", + "login": "shared.user", + "password": "MultiFolder!234", + "login_url": "https://shared.example.com", + "notes": "Same record path appears under Credentials with NSF nesting from shared_folder + folder", + "custom_fields": { + "Owner Team": "Platform" + }, + "folders": [ + { + "shared_folder": "NSF All Types\\Credentials", + "folder": "Shared Apps", + "can_edit": true, + "can_share": true + }, + { + "folder": "NSF All Types\\Personal\\Bookmarks" + } + ] + } + ] +} diff --git a/sample_data/import_nsf_permissions.txt b/sample_data/import_nsf_permissions.txt new file mode 100644 index 000000000..322ccaabe --- /dev/null +++ b/sample_data/import_nsf_permissions.txt @@ -0,0 +1,102 @@ +{ + "shared_folders": [ + { + "path": "NSF Roles Demo\\Engineering", + "permissions": [ + { + "name": "user@mycompany.com", + "role": "viewer" + }, + { + "name": "user1@mycompany.com", + "role": "share-manager" + }, + { + "name": "user2@mycompany.com", + "role": "share-manager" + }, + { + "name": "user3@mycompany.com", + "role": "content-manager" + }, + { + "name": "user4@mycompany.com", + "role": "content-share-manager" + }, + { + "name": "user5@mycompany.com", + "role": "full-manager" + }, + { + "uid": "REPLACE_WITH_TEAM_UID", + "name": "Engineering Team", + "role": "content-manager" + } + ] + }, + { + "path": "NSF Roles Demo\\Finance", + "permissions": [ + { + "name": "user6@mycompany.com", + "role": "viewer" + }, + { + "name": "user7@mycompany.com", + "role": "full-manager" + } + ] + }, + { + "path": "NSF Roles Demo\\Contractors", + "permissions": [ + { + "name": "user8@mycompany.com", + "role": "requestor" + }, + { + "name": "user9@mycompany.com", + "role": "share-manager" + } + ] + } + ], + "records": [ + { + "title": "GitHub — Engineering", + "login": "eng-bot@mycompany.com", + "password": "EngSamp1e!Pass", + "login_url": "https://github.com/mycompany", + "notes": "Shared engineering org credentials", + "folders": [ + { + "folder": "NSF Roles Demo\\Engineering" + } + ] + }, + { + "title": "AWS Console — Finance", + "login": "finance-ops@mycompany.com", + "password": "FinSamp1e!Pass", + "login_url": "https://console.aws.amazon.com", + "notes": "Finance AWS account", + "folders": [ + { + "folder": "NSF Roles Demo\\Finance" + } + ] + }, + { + "title": "Partner Portal", + "login": "contractor@partner.com", + "password": "PartnerSamp1e!Pass", + "login_url": "https://partners.example.com", + "notes": "Limited contractor access folder", + "folders": [ + { + "folder": "NSF Roles Demo\\Contractors" + } + ] + } + ] +} diff --git a/unit-tests/pam/test_pam_nsf_config.py b/unit-tests/pam/test_pam_nsf_config.py index aa75e930c..30086837a 100644 --- a/unit-tests/pam/test_pam_nsf_config.py +++ b/unit-tests/pam/test_pam_nsf_config.py @@ -345,6 +345,98 @@ def parse_properties(_, record, **kwargs): params, 'config_uid', 'legacy_folder', command='pam-config-edit') +class TestPamConfigRequiredFieldValidation(unittest.TestCase): + """Regression tests: an empty required field (e.g. a GitHub config with no PAT) must abort + the whole operation instead of just logging a warning after the record was already saved.""" + + @staticmethod + def _make_missing_pat_field(): + field = vault.TypedField.new_field('secret', None, 'pamGitHubPersonalAccessToken') + field.required = True + return field + + @mock.patch('keepercommander.commands.discoveryrotation.create_pam_configuration_in_folder') + @mock.patch('keepercommander.commands.discoveryrotation.RecordEditMixin.get_record_type_fields', + return_value=[]) + def test_new_raises_and_does_not_create_record_when_required_field_empty( + self, mock_record_fields, mock_create_config): + params = _make_params() + folder = SharedFolderNode() + folder.uid = 'legacy_folder' + params.folder_cache[folder.uid] = folder + command = PAMConfigurationNewCommand() + + def parse_properties(_, record, **kwargs): + record.fields.append(vault.TypedField.new_field( + 'pamResources', {'folderUid': 'legacy_folder'})) + record.fields.append(self._make_missing_pat_field()) + + with mock.patch.object(command, 'parse_properties', side_effect=parse_properties): + with self.assertRaises(CommandError) as ctx: + command.execute(params, config_type='github', title='GitHub Config') + + self.assertIn('pamGitHubPersonalAccessToken', str(ctx.exception)) + mock_create_config.assert_not_called() + + @mock.patch('keepercommander.commands.discoveryrotation.update_pam_record') + @mock.patch('keepercommander.commands.discoveryrotation.RecordEditMixin.get_record_type_fields', + return_value=[]) + @mock.patch('keepercommander.vault.KeeperRecord.load') + def test_edit_raises_and_does_not_update_record_when_required_field_empty( + self, mock_load, mock_record_fields, mock_update_record): + params = _make_params() + folder = SharedFolderNode() + folder.uid = 'legacy_folder' + params.folder_cache[folder.uid] = folder + params.record_cache = {'config_uid': {}} + command = PAMConfigurationEditCommand() + + configuration = vault.TypedRecord(version=6) + configuration.record_uid = 'config_uid' + configuration.type_name = 'pamGitHubConfiguration' + configuration.fields.append(vault.TypedField.new_field( + 'pamResources', {'folderUid': 'legacy_folder'})) + mock_load.return_value = configuration + + def parse_properties(_, record, **kwargs): + record.fields.append(self._make_missing_pat_field()) + + with mock.patch.object(command, 'parse_properties', side_effect=parse_properties): + with self.assertRaises(CommandError) as ctx: + command.execute(params, uid='config_uid') + + self.assertIn('pamGitHubPersonalAccessToken', str(ctx.exception)) + mock_update_record.assert_not_called() + + +class TestPamConfigGitHubFieldMapping(unittest.TestCase): + """Regression test: --personal-access-token must land on the schema field the backend + actually marks required ("(secret).pamGitHubPersonalAccessToken"), not on a differently + labeled field, or the value is silently dropped and "pam config new" always aborts.""" + + def test_personal_access_token_populates_the_required_schema_field(self): + params = _make_params() + record = vault.TypedRecord(version=6) + record.type_name = 'pamGitHubConfiguration' + record.fields.append(vault.TypedField.new_field( + 'pamResources', {'folderUid': 'legacy_folder'})) + required_field = vault.TypedField.new_field('secret', None, 'pamGitHubPersonalAccessToken') + required_field.required = True + record.fields.append(required_field) + + command = PAMConfigurationNewCommand() + command.parse_properties( + params, record, github_id='github-test-acct', personal_access_token='github_pat_example') + + field = record.get_typed_field('secret', 'pamGitHubPersonalAccessToken') + self.assertIsNotNone(field) + self.assertEqual(field.get_default_value(str), 'github_pat_example') + # Regression guard: previously the mismatched label ("personalAccessToken" instead of + # "pamGitHubPersonalAccessToken") caused the value to be misfiled into a new custom field, + # leaving the actual required field empty. + self.assertIsNone(record.get_typed_field('secret', 'personalAccessToken')) + + class TestPamConfigRemoveNsf(unittest.TestCase): @mock.patch('keepercommander.vault.KeeperRecord.load') @@ -389,6 +481,21 @@ def test_resolve_pam_config_uid_finds_nsf_record_by_title(self, mock_load): params, 'PAM NSF Test Configuration') self.assertEqual(uid, 'config_uid') + @mock.patch('keepercommander.vault.KeeperRecord.load') + def test_resolve_pam_config_uid_finds_github_config_by_uid(self, mock_load): + # Regression test: pamGitHubConfiguration must be resolvable like every other + # PAM configuration type, or "pam config edit"/"pam config remove" can never find it. + params = _make_params() + params.record_cache = {'config_uid': {}} + + configuration = vault.TypedRecord(version=6) + configuration.record_uid = 'config_uid' + configuration.type_name = 'pamGitHubConfiguration' + mock_load.return_value = configuration + + uid = PAMConfigurationRemoveCommand._resolve_pam_config_uid(params, 'config_uid') + self.assertEqual(uid, 'config_uid') + @mock.patch('keepercommander.commands.pam.config_helper.RecordRemoveCommand') @mock.patch('keepercommander.commands.nested_share_folder.helpers.is_nested_share_record', return_value=False) diff --git a/unit-tests/pam/test_pam_rbi_edit.py b/unit-tests/pam/test_pam_rbi_edit.py index d3a451b20..67e5ad155 100644 --- a/unit-tests/pam/test_pam_rbi_edit.py +++ b/unit-tests/pam/test_pam_rbi_edit.py @@ -250,142 +250,125 @@ def test_no_param_raises_error_with_new_settings_check(self): self.assertIn('At least one parameter is required', str(context.exception)) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_url_navigation_on_sets_true(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_url_navigation_on_sets_true(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_url_navigation='on') self.assertEqual(self.pam_settings['connection'].get('allowUrlManipulation'), True) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_url_navigation_off_sets_false(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_url_navigation_off_sets_false(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_url_navigation='off') self.assertEqual(self.pam_settings['connection'].get('allowUrlManipulation'), False) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_url_navigation_default_removes_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_url_navigation_default_removes_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.pam_settings['connection']['allowUrlManipulation'] = True self.command.execute(self.mock_params, record='test-record', allow_url_navigation='default') self.assertNotIn('allowUrlManipulation', self.pam_settings['connection']) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_ignore_server_cert_on_sets_true(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_ignore_server_cert_on_sets_true(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', ignore_server_cert='on') self.assertEqual(self.pam_settings['connection'].get('ignoreInitialSslCert'), True) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_file_uploads_on_sets_true(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_file_uploads_on_sets_true(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_file_uploads='on') self.assertEqual(self.pam_settings['connection'].get('allowFileUploads'), True) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_file_uploads_off_sets_false(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_file_uploads_off_sets_false(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_file_uploads='off') self.assertEqual(self.pam_settings['connection'].get('allowFileUploads'), False) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_file_uploads_default_removes_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_file_uploads_default_removes_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.pam_settings['connection']['allowFileUploads'] = True self.command.execute(self.mock_params, record='test-record', allow_file_uploads='default') self.assertNotIn('allowFileUploads', self.pam_settings['connection']) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_file_downloads_on_sets_true(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_file_downloads_on_sets_true(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_file_downloads='on') self.assertEqual(self.pam_settings['connection'].get('allowFileDownloads'), True) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_file_downloads_off_sets_false(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_file_downloads_off_sets_false(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_file_downloads='off') self.assertEqual(self.pam_settings['connection'].get('allowFileDownloads'), False) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_file_downloads_default_removes_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_file_downloads_default_removes_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.pam_settings['connection']['allowFileDownloads'] = True self.command.execute(self.mock_params, record='test-record', allow_file_downloads='default') self.assertNotIn('allowFileDownloads', self.pam_settings['connection']) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allowed_urls_joins_with_newlines(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allowed_urls_joins_with_newlines(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allowed_urls=['*.example.com', '*.test.com']) self.assertEqual(self.pam_settings['connection'].get('allowedUrlPatterns'), '*.example.com\n*.test.com') @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allowed_resource_urls_joins_with_newlines(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allowed_resource_urls_joins_with_newlines(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allowed_resource_urls=['*.cdn.example.com']) self.assertEqual(self.pam_settings['connection'].get('allowedResourceUrlPatterns'), '*.cdn.example.com') @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_autofill_targets_joins_with_newlines(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_autofill_targets_joins_with_newlines(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', autofill_targets=['#username', '#password']) self.assertEqual(self.pam_settings['connection'].get('autofillConfiguration'), '#username\n#password') @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_session_persistence_sets_value(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_session_persistence_sets_value(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', session_persistence='user') self.assertEqual(self.pam_settings['connection'].get('sessionPersistence'), 'user') @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_session_persistence_none_sets_literal(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_session_persistence_none_sets_literal(self, mock_update, mock_resolve): # 'none' is a real enum value (no persistence), not a removal sentinel mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', session_persistence='none') self.assertEqual(self.pam_settings['connection'].get('sessionPersistence'), 'none') @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_session_persistence_default_removes_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_session_persistence_default_removes_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.pam_settings['connection']['sessionPersistence'] = 'user' self.command.execute(self.mock_params, record='test-record', session_persistence='default') self.assertNotIn('sessionPersistence', self.pam_settings['connection']) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_session_persistence_default_removes_present_but_null(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_session_persistence_default_removes_present_but_null(self, mock_update, mock_resolve): # A present-but-null value must still be removed (membership check, not None check) mock_resolve.return_value = self.mock_record self.pam_settings['connection']['sessionPersistence'] = None @@ -409,59 +392,52 @@ def setUp(self): self.mock_params = mock.MagicMock() @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_copy_on_sets_disable_copy_false(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_copy_on_sets_disable_copy_false(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_copy='on') self.assertEqual(self.pam_settings['connection'].get('disableCopy'), False) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_copy_off_sets_disable_copy_true(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_copy_off_sets_disable_copy_true(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_copy='off') self.assertEqual(self.pam_settings['connection'].get('disableCopy'), True) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_copy_default_removes_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_copy_default_removes_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.pam_settings['connection']['disableCopy'] = True self.command.execute(self.mock_params, record='test-record', allow_copy='default') self.assertNotIn('disableCopy', self.pam_settings['connection']) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_paste_on_sets_disable_paste_false(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_paste_on_sets_disable_paste_false(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_paste='on') self.assertEqual(self.pam_settings['connection'].get('disablePaste'), False) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_allow_paste_off_sets_disable_paste_true(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_allow_paste_off_sets_disable_paste_true(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_paste='off') self.assertEqual(self.pam_settings['connection'].get('disablePaste'), True) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_clipboard_both_on(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_clipboard_both_on(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_copy='on', allow_paste='on') self.assertEqual(self.pam_settings['connection'].get('disableCopy'), False) self.assertEqual(self.pam_settings['connection'].get('disablePaste'), False) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_clipboard_both_off(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_clipboard_both_off(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_copy='off', allow_paste='off') self.assertEqual(self.pam_settings['connection'].get('disableCopy'), True) @@ -484,23 +460,58 @@ def setUp(self): self.mock_params = mock.MagicMock() @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_record_update_called_when_field_changes(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_record_update_called_when_field_changes(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_url_navigation='on') mock_update.assert_called_once() - mock_sync.assert_called_once() + args, kwargs = mock_update.call_args + self.assertEqual(kwargs.get('command'), 'pam rbi edit') @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_multiple_fields_single_update(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_multiple_fields_single_update(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', allow_url_navigation='on', ignore_server_cert='off', allow_copy='on', allowed_urls=['*.example.com']) mock_update.assert_called_once() + @mock.patch('keepercommander.commands.pam.vault_target.record_management.update_record') + @mock.patch('keepercommander.commands.pam_import.nsf_helpers.sync_down_preserving_nsf_keys') + @mock.patch('keepercommander.nested_share_folder.record_api.update_record_v3', + return_value={'success': True}) + @mock.patch('keepercommander.vault_extensions.extract_typed_record_data', + return_value={'type': 'pamRemoteBrowser', 'title': 'NSF RBI', 'fields': []}) + @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') + def test_nsf_rbi_edit_routes_to_update_record_v3(self, mock_resolve, _extract, mock_update_v3, + mock_nsf_sync, mock_classic_update): + """Regression: NSF pamRemoteBrowser edits must use NSF v3 update, not classic update_record.""" + record_uid = 'nsf-rbi-uid' + record = vault.TypedRecord(version=3) + record.record_uid = record_uid + record.type_name = 'pamRemoteBrowser' + record.title = 'NSF RBI' + record.fields.append(vault.TypedField.new_field('trafficEncryptionSeed', 'seed', '')) + record.fields.append(vault.TypedField.new_field( + 'pamRemoteBrowserSettings', + {'connection': {'protocol': 'http', 'sessionPersistence': 'resource'}}, + '')) + + params = mock.MagicMock() + params.nested_share_records = {record_uid: {'revision': 1}} + params.nested_share_record_data = {} + mock_resolve.return_value = record + + self.command.execute(params, record=record_uid, session_persistence='user') + + mock_update_v3.assert_called_once() + self.assertEqual(mock_update_v3.call_args.args[1], record_uid) + mock_nsf_sync.assert_called_once_with(params) + mock_classic_update.assert_not_called() + self.assertEqual( + record.get_typed_field('pamRemoteBrowserSettings').value[0]['connection']['sessionPersistence'], + 'user') + @unittest.skipIf(skip_tests, skip_reason) class TestPamRbiEditHelp(unittest.TestCase): @@ -616,25 +627,22 @@ def test_disable_audio_default(self): self.assertEqual(args.disable_audio, 'default') @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_disable_audio_on_sets_true(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_disable_audio_on_sets_true(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', disable_audio='on') self.assertEqual(self.pam_settings['connection'].get('disableAudio'), True) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_disable_audio_off_sets_false(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_disable_audio_off_sets_false(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', disable_audio='off') self.assertEqual(self.pam_settings['connection'].get('disableAudio'), False) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_disable_audio_default_removes_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_disable_audio_default_removes_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.pam_settings['connection']['disableAudio'] = True self.command.execute(self.mock_params, record='test-record', disable_audio='default') @@ -649,9 +657,8 @@ def test_audio_channels_mono(self): self.assertEqual(args.audio_channels, 1) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_audio_channels_sets_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_audio_channels_sets_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', audio_channels=2) self.assertEqual(self.pam_settings['connection'].get('audioChannels'), 2) @@ -669,9 +676,8 @@ def test_audio_bit_depth_invalid(self): self.parser.parse_args(['--record', 'test-record', '--audio-bit-depth', '24']) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_audio_bit_depth_sets_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_audio_bit_depth_sets_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', audio_bit_depth=16) self.assertEqual(self.pam_settings['connection'].get('audioBps'), 16) @@ -685,17 +691,15 @@ def test_audio_sample_rate_48000(self): self.assertEqual(args.audio_sample_rate, 48000) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_audio_sample_rate_sets_field(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_audio_sample_rate_sets_field(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', audio_sample_rate=48000) self.assertEqual(self.pam_settings['connection'].get('audioSampleRate'), 48000) @mock.patch('keepercommander.commands.tunnel_and_connections.RecordMixin.resolve_single_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.record_management.update_record') - @mock.patch('keepercommander.commands.tunnel_and_connections.api.sync_down') - def test_all_audio_settings_combined(self, mock_sync, mock_update, mock_resolve): + @mock.patch('keepercommander.commands.tunnel_and_connections.update_pam_record') + def test_all_audio_settings_combined(self, mock_update, mock_resolve): mock_resolve.return_value = self.mock_record self.command.execute(self.mock_params, record='test-record', disable_audio='off', audio_channels=2, audio_bit_depth=16, audio_sample_rate=44100) diff --git a/unit-tests/pam/test_uss_github_config.py b/unit-tests/pam/test_uss_github_config.py new file mode 100644 index 000000000..cb05596f5 --- /dev/null +++ b/unit-tests/pam/test_uss_github_config.py @@ -0,0 +1,239 @@ +""" +Unit tests for the Universal Secrets Sync (USS) GitHub configuration fields on +`pam universal-sync-config add / edit / list`. + +Covers the nested `GitHubConfig` shape documented in krouter's +`docs/User API/ConfigureUniversalSync.md`: GitHub's scope/owner/organizationVisibility/repos +live under a single `github` message field on `PAMUniversalSyncConfig`, not as flat fields. +""" +import json +import unittest +from unittest import mock + +from keepercommander import crypto, utils, vault +from keepercommander.commands.universalsecretsync import ( + PAMUniversalSyncConfigAddCommand, + PAMUniversalSyncConfigEditCommand, + PAMUniversalSyncConfigListCommand, +) +from keepercommander.proto import pam_pb2 + +NETWORK_UID = 'AAAAAAAAAAAAAAAAAAAAAA' # roundtrip-safe base64url, 22 chars +RECORD_KEY = bytes(range(16)) +FAKE_TOKENS = (b'enc-session-token', b'enc-transmission-key', b'transmission-key') + + +def _make_network_record(record_type='pamGitHubConfiguration'): + network = vault.TypedRecord() + network.record_uid = NETWORK_UID + network.record_key = RECORD_KEY + network.title = 'GitHub Network' + network.type_name = record_type + return network + + +class FakeEdge: + def __init__(self, path, head_uid, content=None): + self.path = path + self.head_uid = head_uid + self._content = content + + @property + def content_as_dict(self): + return self._content + + +class FakeVertex: + def __init__(self, uid, edges=None, child_vertices=None): + self.uid = uid + self.edges = edges or [] + self._child_vertices = child_vertices or [] + + def has_vertices(self, *_args, **_kwargs): + return self._child_vertices + + +def _patch_dag(existing_config=None): + """Build mock DAG/Connection classes matching the lazy `from ..keeper_dag import DAG` + imports inside universalsecretsync.py. Returns (dag_class, connection_class, root).""" + root = FakeVertex(uid=NETWORK_UID) + if existing_config is not None: + root.edges = [FakeEdge('universal_sync', head_uid=NETWORK_UID, content=existing_config)] + + dag_instance = mock.MagicMock() + dag_instance.get_root = root + dag_instance.load.return_value = None + + dag_class = mock.MagicMock(return_value=dag_instance) + connection_class = mock.MagicMock() + return dag_class, connection_class, root + + +class TestPAMUniversalSyncConfigAddCommand(unittest.TestCase): + def setUp(self): + self.params = mock.MagicMock() + self.network = _make_network_record() + mock.patch('keepercommander.commands.universalsecretsync.get_keeper_tokens', + return_value=FAKE_TOKENS).start() + mock.patch('keepercommander.vault.KeeperRecord.load', return_value=self.network).start() + self.mock_router = mock.patch( + 'keepercommander.commands.universalsecretsync.router_helper.router_configure_universal_sync').start() + self.addCleanup(mock.patch.stopall) + + def _execute(self, **kwargs): + kwargs.setdefault('network', NETWORK_UID) + kwargs.setdefault('enabled', 'true') + kwargs.setdefault('dry_run', 'false') + PAMUniversalSyncConfigAddCommand().execute(self.params, **kwargs) + self.assertEqual(self.mock_router.call_count, 1) + return self.mock_router.call_args[0][1] + + def test_writes_github_config_under_nested_field(self): + rq = self._execute(scope='organization', owner='my-org', org_visibility='selected', + repo=['repo-a', 'repo-b']) + + self.assertTrue(rq.HasField('github')) + self.assertEqual(rq.github.scope, pam_pb2.ORGANIZATION) + self.assertEqual(rq.github.organizationVisibility, pam_pb2.SELECTED) + self.assertEqual(crypto.decrypt_aes_v2(rq.github.owner, RECORD_KEY), b'my-org') + + self.assertEqual(len(rq.github.repos), 2) + decrypted_repos = [crypto.decrypt_aes_v2(r.name, RECORD_KEY).decode('utf-8') for r in rq.github.repos] + self.assertEqual(decrypted_repos, ['repo-a', 'repo-b']) + + def test_repository_scope_maps_correctly(self): + rq = self._execute(scope='repository', owner='my-user') + self.assertEqual(rq.github.scope, pam_pb2.REPOSITORY) + + def test_no_github_flags_leaves_github_field_unset(self): + rq = self._execute() + self.assertFalse(rq.HasField('github')) + + def test_flat_fields_no_longer_exist_on_the_message(self): + rq = pam_pb2.PAMUniversalSyncConfig() + for flat_field in ('scope', 'owner', 'organizationVisibility', 'repos'): + with self.assertRaises(AttributeError): + getattr(rq, flat_field) + + +class TestPAMUniversalSyncConfigEditCommand(unittest.TestCase): + def setUp(self): + self.params = mock.MagicMock() + self.network = _make_network_record() + mock.patch('keepercommander.commands.universalsecretsync.get_keeper_tokens', + return_value=FAKE_TOKENS).start() + mock.patch('keepercommander.vault.KeeperRecord.load', return_value=self.network).start() + self.mock_router = mock.patch( + 'keepercommander.commands.universalsecretsync.router_helper.router_configure_universal_sync').start() + self.addCleanup(mock.patch.stopall) + + @staticmethod + def _existing_github_config(repo_names, scope=pam_pb2.REPOSITORY, org_visibility=pam_pb2.PRIVATE, + owner='existing-owner'): + encrypted_owner = crypto.encrypt_aes_v2(owner.encode('utf-8'), RECORD_KEY) + repos_b64 = [utils.base64_url_encode(crypto.encrypt_aes_v2(name.encode('utf-8'), RECORD_KEY)) + for name in repo_names] + return { + 'scope': scope, + 'owner': utils.base64_url_encode(encrypted_owner), + 'organizationVisibility': org_visibility, + 'repos': repos_b64, + } + + def _execute_with_dag(self, existing_config, **kwargs): + dag_class, connection_class, _root = _patch_dag(existing_config) + kwargs.setdefault('network', NETWORK_UID) + with mock.patch('keepercommander.keeper_dag.DAG', dag_class), \ + mock.patch('keepercommander.keeper_dag.connection.commander.Connection', connection_class): + PAMUniversalSyncConfigEditCommand().execute(self.params, **kwargs) + # Guard against the broad try/except in the command silently swallowing a bad mock wire-up. + self.assertEqual(dag_class.call_count, 1) + self.assertEqual(self.mock_router.call_count, 1) + return self.mock_router.call_args[0][1] + + def test_preserves_existing_github_config_when_not_overridden(self): + existing = {'github': self._existing_github_config(['repo-1', 'repo-2'])} + rq = self._execute_with_dag(existing) + + self.assertTrue(rq.HasField('github')) + self.assertEqual(rq.github.scope, pam_pb2.REPOSITORY) + self.assertEqual(rq.github.organizationVisibility, pam_pb2.PRIVATE) + self.assertEqual(crypto.decrypt_aes_v2(rq.github.owner, RECORD_KEY), b'existing-owner') + decrypted = [crypto.decrypt_aes_v2(r.name, RECORD_KEY).decode('utf-8') for r in rq.github.repos] + self.assertEqual(decrypted, ['repo-1', 'repo-2']) + + def test_overrides_take_precedence_over_existing(self): + existing = {'github': self._existing_github_config(['old-repo'])} + rq = self._execute_with_dag(existing, scope='organization', owner='new-owner', + org_visibility='all', repo=['new-repo']) + + self.assertEqual(rq.github.scope, pam_pb2.ORGANIZATION) + self.assertEqual(rq.github.organizationVisibility, pam_pb2.ALL) + self.assertEqual(crypto.decrypt_aes_v2(rq.github.owner, RECORD_KEY), b'new-owner') + decrypted = [crypto.decrypt_aes_v2(r.name, RECORD_KEY).decode('utf-8') for r in rq.github.repos] + self.assertEqual(decrypted, ['new-repo']) + + def test_partial_override_preserves_the_rest(self): + # Only --owner is overridden; scope/org-visibility/repos should carry forward. + existing = {'github': self._existing_github_config(['repo-1'], scope=pam_pb2.ORGANIZATION, + org_visibility=pam_pb2.SELECTED)} + rq = self._execute_with_dag(existing, owner='new-owner') + + self.assertEqual(crypto.decrypt_aes_v2(rq.github.owner, RECORD_KEY), b'new-owner') + self.assertEqual(rq.github.scope, pam_pb2.ORGANIZATION) + self.assertEqual(rq.github.organizationVisibility, pam_pb2.SELECTED) + decrypted = [crypto.decrypt_aes_v2(r.name, RECORD_KEY).decode('utf-8') for r in rq.github.repos] + self.assertEqual(decrypted, ['repo-1']) + + def test_no_existing_and_no_override_leaves_github_unset(self): + rq = self._execute_with_dag(None) + self.assertFalse(rq.HasField('github')) + + +class TestPAMUniversalSyncConfigListDetails(unittest.TestCase): + def setUp(self): + self.params = mock.MagicMock() + self.params.folder_cache = {} + self.params.subfolder_cache = {} + self.network = _make_network_record() + mock.patch('keepercommander.vault.KeeperRecord.load', return_value=self.network).start() + self.addCleanup(mock.patch.stopall) + + def _print_details(self, existing_config, format_type='json'): + dag_class, connection_class, _root = _patch_dag(existing_config) + with mock.patch('keepercommander.keeper_dag.DAG', dag_class), \ + mock.patch('keepercommander.keeper_dag.connection.commander.Connection', connection_class): + return PAMUniversalSyncConfigListCommand.print_uss_configuration_details( + self.params, NETWORK_UID, format_type=format_type) + + def test_json_details_reads_nested_github_fields(self): + encrypted_owner = crypto.encrypt_aes_v2(b'my-org', RECORD_KEY) + encrypted_repo = crypto.encrypt_aes_v2(b'my-repo', RECORD_KEY) + existing_config = { + 'enabled': True, + 'github': { + 'scope': pam_pb2.ORGANIZATION, + 'owner': utils.base64_url_encode(encrypted_owner), + 'organizationVisibility': pam_pb2.ALL, + 'repos': [utils.base64_url_encode(encrypted_repo)], + }, + } + + result = json.loads(self._print_details(existing_config)) + + self.assertEqual(result['scope'], 'ORGANIZATION') + self.assertEqual(result['organization_visibility'], 'ALL') + self.assertEqual(result['owner'], 'my-org') + self.assertEqual(result['repos'], ['my-repo']) + + def test_json_details_without_github_config(self): + result = json.loads(self._print_details({'enabled': True})) + + self.assertEqual(result['scope'], 'N/A') + self.assertEqual(result['organization_visibility'], 'N/A') + self.assertEqual(result['owner'], 'N/A') + self.assertEqual(result['repos'], []) + + +if __name__ == '__main__': + unittest.main() diff --git a/unit-tests/pam/test_workflow_enforcement_gate.py b/unit-tests/pam/test_workflow_enforcement_gate.py new file mode 100644 index 000000000..b8f16e273 --- /dev/null +++ b/unit-tests/pam/test_workflow_enforcement_gate.py @@ -0,0 +1,108 @@ +# _ __ +# | |/ /___ ___ _ __ ___ _ _ ® +# | ' = {'viewer', 'full-manager', 'content-manager'}), + ): + path = os.path.join(SAMPLE, name) + if not os.path.isfile(path): + self.skipTest(f'{name} missing') + folders = [x for x in KeeperJsonImporter().do_import(path) if isinstance(x, SharedFolder)] + self.assertTrue(check(folders), name) + + def test_cli_nsf_skips_classic_prompt(self): + cmd = RecordImportCommand() + with mock.patch('keepercommander.importer.commands.user_choice') as choice, \ + mock.patch('keepercommander.importer.commands.imp_exp._import') as imp: + cmd.execute(_params(), format='json', name='sample_data/import_nsf.txt', + use_nsf=True, users_only=True) + choice.assert_not_called() + self.assertTrue(all(not imp.call_args.kwargs.get(k) + for k in ('manage_users', 'manage_records', 'can_edit', 'can_share'))) + + # classic --shared still prompts (name is unused; import is mocked) + choice.reset_mock() + choice.return_value = 'a' + cmd.execute(_params(), format='json', name='sample_data/import_nsf.txt', shared=True) + choice.assert_called_once() + self.assertTrue(all(imp.call_args.kwargs.get(k) + for k in ('manage_users', 'manage_records', 'can_edit', 'can_share'))) + + def test_parser_and_path_resolve(self): + self.assertTrue(any('--nsf' in (a.option_strings or []) for a in import_parser._actions)) + + nsf_txt = os.path.join(SAMPLE, 'import_nsf.txt') + if not os.path.isfile(nsf_txt): + self.skipTest('sample_data/import_nsf.txt missing') + + json_imp = KeeperJsonImporter() + # stem → import_nsf.txt + self.assertTrue( + json_imp.resolve_file_path(os.path.join(SAMPLE, 'import_nsf')).endswith('import_nsf.txt')) + # exact .txt path + self.assertEqual( + os.path.abspath(json_imp.resolve_file_path(nsf_txt)), + os.path.abspath(nsf_txt)) + + csv_path = os.path.join(SAMPLE, 'import_nsf.csv') + if os.path.isfile(csv_path): + self.assertEqual( + os.path.abspath(KeeperCsvImporter().resolve_file_path(csv_path)), + os.path.abspath(csv_path))