eryph-compute 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +47 -0
- data/LICENSE +21 -0
- data/README.md +144 -0
- data/eryph-clientruntime.gemspec +42 -0
- data/eryph-compute.gemspec +44 -0
- data/lib/eryph/clientruntime/client_credentials_lookup.rb +295 -0
- data/lib/eryph/clientruntime/config_store.rb +206 -0
- data/lib/eryph/clientruntime/config_stores_reader.rb +211 -0
- data/lib/eryph/clientruntime/endpoint_lookup.rb +226 -0
- data/lib/eryph/clientruntime/environment.rb +440 -0
- data/lib/eryph/clientruntime/local_identity_provider_info.rb +153 -0
- data/lib/eryph/clientruntime/token_provider.rb +275 -0
- data/lib/eryph/clientruntime/version.rb +6 -0
- data/lib/eryph/clientruntime.rb +79 -0
- data/lib/eryph/compute/client.rb +442 -0
- data/lib/eryph/compute/generated/Gemfile +9 -0
- data/lib/eryph/compute/generated/README.md +206 -0
- data/lib/eryph/compute/generated/Rakefile +10 -0
- data/lib/eryph/compute/generated/compute_client.gemspec +41 -0
- data/lib/eryph/compute/generated/docs/ApiVersion.md +20 -0
- data/lib/eryph/compute/generated/docs/ApiVersionResponse.md +18 -0
- data/lib/eryph/compute/generated/docs/Catlet.md +32 -0
- data/lib/eryph/compute/generated/docs/CatletConfigOperationResult.md +18 -0
- data/lib/eryph/compute/generated/docs/CatletConfigValidationResult.md +20 -0
- data/lib/eryph/compute/generated/docs/CatletConfiguration.md +18 -0
- data/lib/eryph/compute/generated/docs/CatletDrive.md +20 -0
- data/lib/eryph/compute/generated/docs/CatletDriveType.md +15 -0
- data/lib/eryph/compute/generated/docs/CatletList.md +18 -0
- data/lib/eryph/compute/generated/docs/CatletNetwork.md +30 -0
- data/lib/eryph/compute/generated/docs/CatletNetworkAdapter.md +20 -0
- data/lib/eryph/compute/generated/docs/CatletStatus.md +15 -0
- data/lib/eryph/compute/generated/docs/CatletStopMode.md +15 -0
- data/lib/eryph/compute/generated/docs/CatletsApi.md +863 -0
- data/lib/eryph/compute/generated/docs/DiskStatus.md +15 -0
- data/lib/eryph/compute/generated/docs/ExpandCatletConfigRequestBody.md +22 -0
- data/lib/eryph/compute/generated/docs/ExpandNewCatletConfigRequest.md +22 -0
- data/lib/eryph/compute/generated/docs/FloatingNetworkPort.md +26 -0
- data/lib/eryph/compute/generated/docs/Gene.md +30 -0
- data/lib/eryph/compute/generated/docs/GeneList.md +18 -0
- data/lib/eryph/compute/generated/docs/GeneType.md +15 -0
- data/lib/eryph/compute/generated/docs/GeneWithUsage.md +34 -0
- data/lib/eryph/compute/generated/docs/GenesApi.md +281 -0
- data/lib/eryph/compute/generated/docs/NewCatletRequest.md +20 -0
- data/lib/eryph/compute/generated/docs/NewProjectMemberBody.md +22 -0
- data/lib/eryph/compute/generated/docs/NewProjectRequest.md +20 -0
- data/lib/eryph/compute/generated/docs/NewVirtualDiskRequest.md +30 -0
- data/lib/eryph/compute/generated/docs/Operation.md +32 -0
- data/lib/eryph/compute/generated/docs/OperationList.md +18 -0
- data/lib/eryph/compute/generated/docs/OperationLogEntry.md +24 -0
- data/lib/eryph/compute/generated/docs/OperationResource.md +22 -0
- data/lib/eryph/compute/generated/docs/OperationResult.md +18 -0
- data/lib/eryph/compute/generated/docs/OperationStatus.md +15 -0
- data/lib/eryph/compute/generated/docs/OperationTask.md +30 -0
- data/lib/eryph/compute/generated/docs/OperationTaskReference.md +22 -0
- data/lib/eryph/compute/generated/docs/OperationTaskStatus.md +15 -0
- data/lib/eryph/compute/generated/docs/OperationsApi.md +157 -0
- data/lib/eryph/compute/generated/docs/PopulateCatletConfigVariablesRequest.md +20 -0
- data/lib/eryph/compute/generated/docs/ProblemDetails.md +26 -0
- data/lib/eryph/compute/generated/docs/Project.md +22 -0
- data/lib/eryph/compute/generated/docs/ProjectList.md +18 -0
- data/lib/eryph/compute/generated/docs/ProjectMemberRole.md +26 -0
- data/lib/eryph/compute/generated/docs/ProjectMemberRoleList.md +18 -0
- data/lib/eryph/compute/generated/docs/ProjectMembersApi.md +293 -0
- data/lib/eryph/compute/generated/docs/ProjectsApi.md +286 -0
- data/lib/eryph/compute/generated/docs/ResourceType.md +15 -0
- data/lib/eryph/compute/generated/docs/StopCatletRequestBody.md +18 -0
- data/lib/eryph/compute/generated/docs/TaskReferenceType.md +15 -0
- data/lib/eryph/compute/generated/docs/UpdateCatletRequestBody.md +20 -0
- data/lib/eryph/compute/generated/docs/UpdateProjectNetworksRequestBody.md +20 -0
- data/lib/eryph/compute/generated/docs/ValidateConfigRequest.md +18 -0
- data/lib/eryph/compute/generated/docs/ValidationIssue.md +20 -0
- data/lib/eryph/compute/generated/docs/VersionApi.md +69 -0
- data/lib/eryph/compute/generated/docs/VirtualDisk.md +42 -0
- data/lib/eryph/compute/generated/docs/VirtualDiskAttachedCatlet.md +20 -0
- data/lib/eryph/compute/generated/docs/VirtualDiskGeneInfo.md +22 -0
- data/lib/eryph/compute/generated/docs/VirtualDiskList.md +18 -0
- data/lib/eryph/compute/generated/docs/VirtualDisksApi.md +291 -0
- data/lib/eryph/compute/generated/docs/VirtualNetwork.md +28 -0
- data/lib/eryph/compute/generated/docs/VirtualNetworkConfiguration.md +18 -0
- data/lib/eryph/compute/generated/docs/VirtualNetworkList.md +18 -0
- data/lib/eryph/compute/generated/docs/VirtualNetworksApi.md +291 -0
- data/lib/eryph/compute/generated/git_push.sh +57 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/catlets_api.rb +812 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/genes_api.rb +262 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/operations_api.rb +154 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/project_members_api.rb +297 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/projects_api.rb +269 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/version_api.rb +79 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/virtual_disks_api.rb +272 -0
- data/lib/eryph/compute/generated/lib/compute_client/api/virtual_networks_api.rb +282 -0
- data/lib/eryph/compute/generated/lib/compute_client/api_client.rb +437 -0
- data/lib/eryph/compute/generated/lib/compute_client/api_error.rb +58 -0
- data/lib/eryph/compute/generated/lib/compute_client/configuration.rb +392 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/api_version.rb +263 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/api_version_response.rb +237 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet.rb +400 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_config_operation_result.rb +234 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_config_validation_result.rb +251 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_configuration.rb +223 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_drive.rb +270 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_drive_type.rb +43 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_network.rb +318 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_network_adapter.rb +247 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_status.rb +43 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/catlet_stop_mode.rb +41 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/disk_status.rb +40 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/expand_catlet_config_request_body.rb +243 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/expand_new_catlet_config_request.rb +243 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/floating_network_port.rb +313 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/gene.rb +415 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/gene_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/gene_type.rb +41 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/gene_with_usage.rb +439 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/new_catlet_request.rb +233 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/new_project_member_body.rb +273 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/new_project_request.rb +247 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/new_virtual_disk_request.rb +345 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation.rb +352 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_log_entry.rb +299 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_resource.rb +311 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_result.rb +242 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_status.rb +42 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_task.rb +366 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_task_reference.rb +311 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/operation_task_status.rb +42 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/populate_catlet_config_variables_request.rb +233 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/problem_details.rb +261 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/project.rb +289 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/project_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/project_member_role.rb +341 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/project_member_role_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/resource_type.rb +42 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/stop_catlet_request_body.rb +259 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/task_reference_type.rb +40 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/update_catlet_request_body.rb +233 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/update_project_networks_request_body.rb +233 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/validate_config_request.rb +223 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/validation_issue.rb +249 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk.rb +479 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_attached_catlet.rb +285 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_gene_info.rb +289 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_disk_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network.rb +351 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network_configuration.rb +223 -0
- data/lib/eryph/compute/generated/lib/compute_client/models/virtual_network_list.rb +239 -0
- data/lib/eryph/compute/generated/lib/compute_client/version.rb +15 -0
- data/lib/eryph/compute/generated/lib/compute_client.rb +101 -0
- data/lib/eryph/compute/generated/spec/api/catlets_api_spec.rb +182 -0
- data/lib/eryph/compute/generated/spec/api/genes_api_spec.rb +81 -0
- data/lib/eryph/compute/generated/spec/api/operations_api_spec.rb +62 -0
- data/lib/eryph/compute/generated/spec/api/project_members_api_spec.rb +86 -0
- data/lib/eryph/compute/generated/spec/api/projects_api_spec.rb +82 -0
- data/lib/eryph/compute/generated/spec/api/version_api_spec.rb +46 -0
- data/lib/eryph/compute/generated/spec/api/virtual_disks_api_spec.rb +83 -0
- data/lib/eryph/compute/generated/spec/api/virtual_networks_api_spec.rb +84 -0
- data/lib/eryph/compute/generated/spec/models/api_version_response_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/api_version_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/catlet_config_operation_result_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/catlet_config_validation_result_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/catlet_configuration_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/catlet_drive_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/catlet_drive_type_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/catlet_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/catlet_network_adapter_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/catlet_network_spec.rb +72 -0
- data/lib/eryph/compute/generated/spec/models/catlet_spec.rb +78 -0
- data/lib/eryph/compute/generated/spec/models/catlet_status_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/catlet_stop_mode_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/disk_status_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/expand_catlet_config_request_body_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/expand_new_catlet_config_request_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/floating_network_port_spec.rb +60 -0
- data/lib/eryph/compute/generated/spec/models/gene_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/gene_spec.rb +72 -0
- data/lib/eryph/compute/generated/spec/models/gene_type_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/gene_with_usage_spec.rb +84 -0
- data/lib/eryph/compute/generated/spec/models/new_catlet_request_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/new_project_member_body_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/new_project_request_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/new_virtual_disk_request_spec.rb +72 -0
- data/lib/eryph/compute/generated/spec/models/operation_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/operation_log_entry_spec.rb +54 -0
- data/lib/eryph/compute/generated/spec/models/operation_resource_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/operation_result_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/operation_spec.rb +78 -0
- data/lib/eryph/compute/generated/spec/models/operation_status_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/operation_task_reference_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/operation_task_spec.rb +72 -0
- data/lib/eryph/compute/generated/spec/models/operation_task_status_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/populate_catlet_config_variables_request_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/problem_details_spec.rb +60 -0
- data/lib/eryph/compute/generated/spec/models/project_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/project_member_role_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/project_member_role_spec.rb +60 -0
- data/lib/eryph/compute/generated/spec/models/project_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/resource_type_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/stop_catlet_request_body_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/task_reference_type_spec.rb +30 -0
- data/lib/eryph/compute/generated/spec/models/update_catlet_request_body_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/update_project_networks_request_body_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/validate_config_request_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/validation_issue_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/virtual_disk_attached_catlet_spec.rb +42 -0
- data/lib/eryph/compute/generated/spec/models/virtual_disk_gene_info_spec.rb +48 -0
- data/lib/eryph/compute/generated/spec/models/virtual_disk_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/virtual_disk_spec.rb +108 -0
- data/lib/eryph/compute/generated/spec/models/virtual_network_configuration_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/virtual_network_list_spec.rb +36 -0
- data/lib/eryph/compute/generated/spec/models/virtual_network_spec.rb +66 -0
- data/lib/eryph/compute/generated/spec/spec_helper.rb +111 -0
- data/lib/eryph/compute/generated.rb +137 -0
- data/lib/eryph/compute/operation_result.rb +255 -0
- data/lib/eryph/compute/operation_tracker.rb +247 -0
- data/lib/eryph/compute/problem_details_error.rb +139 -0
- data/lib/eryph/compute/version.rb +6 -0
- data/lib/eryph/compute.rb +40 -0
- data/lib/eryph/version.rb +4 -0
- data/lib/eryph.rb +68 -0
- metadata +329 -0
@@ -0,0 +1,440 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Eryph
|
6
|
+
module ClientRuntime
|
7
|
+
# Cross-platform environment abstraction
|
8
|
+
# Provides platform-specific file system access and configuration paths
|
9
|
+
class Environment
|
10
|
+
# Configuration store locations
|
11
|
+
module ConfigStoreLocation
|
12
|
+
CURRENT_DIRECTORY = :current_directory
|
13
|
+
USER = :user
|
14
|
+
SYSTEM = :system
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the configuration path for the specified location
|
18
|
+
# @param location [Symbol] the configuration store location
|
19
|
+
# @return [String] the configuration path
|
20
|
+
def get_config_path(location)
|
21
|
+
case location
|
22
|
+
when ConfigStoreLocation::CURRENT_DIRECTORY
|
23
|
+
Dir.pwd
|
24
|
+
when ConfigStoreLocation::USER
|
25
|
+
user_config_path
|
26
|
+
when ConfigStoreLocation::SYSTEM
|
27
|
+
system_config_path
|
28
|
+
else
|
29
|
+
raise ArgumentError, "Unknown configuration store location: #{location}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if running on Windows platform
|
34
|
+
# @return [Boolean] true if running on Windows
|
35
|
+
def windows?
|
36
|
+
!!(RbConfig::CONFIG['target_os'] =~ /mswin|mingw|cygwin/)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Check if running on macOS platform
|
40
|
+
# @return [Boolean] true if running on macOS
|
41
|
+
def macos?
|
42
|
+
RbConfig::CONFIG['target_os'] =~ /darwin/
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check if running on Linux platform
|
46
|
+
# @return [Boolean] true if running on Linux
|
47
|
+
def linux?
|
48
|
+
RbConfig::CONFIG['target_os'] =~ /linux/
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the current directory
|
52
|
+
# @return [String] current working directory
|
53
|
+
def current_directory
|
54
|
+
Dir.pwd
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check if a file exists
|
58
|
+
# @param path [String] file path
|
59
|
+
# @return [Boolean] true if file exists
|
60
|
+
def file_exists?(path)
|
61
|
+
File.exist?(path) && File.file?(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check if a directory exists
|
65
|
+
# @param path [String] directory path
|
66
|
+
# @return [Boolean] true if directory exists
|
67
|
+
def directory_exists?(path)
|
68
|
+
File.exist?(path) && File.directory?(path)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Read file content
|
72
|
+
# @param path [String] file path
|
73
|
+
# @return [String] file content
|
74
|
+
# @raise [IOError] if file cannot be read
|
75
|
+
def read_file(path)
|
76
|
+
File.read(path)
|
77
|
+
rescue Errno::ENOENT, Errno::EACCES => e
|
78
|
+
raise IOError, "Cannot read file #{path}: #{e.message}"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Write file content
|
82
|
+
# @param path [String] file path
|
83
|
+
# @param content [String] content to write
|
84
|
+
# @raise [IOError] if file cannot be written
|
85
|
+
def write_file(path, content)
|
86
|
+
# Ensure directory exists
|
87
|
+
dir = File.dirname(path)
|
88
|
+
FileUtils.mkdir_p(dir) unless directory_exists?(dir)
|
89
|
+
|
90
|
+
File.write(path, content)
|
91
|
+
rescue Errno::EACCES, Errno::ENOENT => e
|
92
|
+
raise IOError, "Cannot write file #{path}: #{e.message}"
|
93
|
+
end
|
94
|
+
|
95
|
+
# Create directory if it doesn't exist
|
96
|
+
# @param path [String] directory path
|
97
|
+
# @raise [IOError] if directory cannot be created
|
98
|
+
def ensure_directory(path)
|
99
|
+
return if directory_exists?(path)
|
100
|
+
|
101
|
+
FileUtils.mkdir_p(path)
|
102
|
+
rescue Errno::EACCES => e
|
103
|
+
raise IOError, "Cannot create directory #{path}: #{e.message}"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Check if current user has admin/root privileges
|
107
|
+
# @return [Boolean] true if user has elevated privileges
|
108
|
+
def admin_user?
|
109
|
+
if windows?
|
110
|
+
# Check if running as administrator on Windows using PowerShell
|
111
|
+
check_windows_admin_privileges
|
112
|
+
else
|
113
|
+
# Check if running as root on Unix-like systems
|
114
|
+
Process.uid.zero?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Get environment variable with optional default
|
119
|
+
# @param name [String] environment variable name
|
120
|
+
# @param default [String, nil] default value if not found
|
121
|
+
# @return [String, nil] environment variable value
|
122
|
+
def get_environment_variable(name, default = nil)
|
123
|
+
ENV[name] || default
|
124
|
+
end
|
125
|
+
|
126
|
+
# Create a temporary file
|
127
|
+
# @param prefix [String] filename prefix
|
128
|
+
# @param suffix [String] filename suffix
|
129
|
+
# @return [Tempfile] temporary file object
|
130
|
+
def create_temp_file(prefix, suffix = '')
|
131
|
+
require 'tempfile'
|
132
|
+
Tempfile.new([prefix, suffix])
|
133
|
+
end
|
134
|
+
|
135
|
+
# Execute PowerShell script from file
|
136
|
+
# @param script_path [String] path to PowerShell script file
|
137
|
+
# @return [Boolean] true if script executed successfully
|
138
|
+
def execute_powershell_script_file(script_path)
|
139
|
+
return false unless windows?
|
140
|
+
|
141
|
+
# Try regular PowerShell first
|
142
|
+
success = execute_command('powershell.exe', '-ExecutionPolicy', 'Bypass', '-NoProfile', '-File', script_path)
|
143
|
+
|
144
|
+
# If that fails, try the full path
|
145
|
+
success ||= execute_command(
|
146
|
+
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
|
147
|
+
'-ExecutionPolicy', 'Bypass', '-NoProfile', '-File', script_path
|
148
|
+
)
|
149
|
+
|
150
|
+
success
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get application data path (system-wide)
|
154
|
+
# @param app_name [String] application name (default: 'eryph')
|
155
|
+
# @return [String] application data path
|
156
|
+
def get_application_data_path(app_name = 'eryph')
|
157
|
+
if windows?
|
158
|
+
# Windows: Use PROGRAMDATA
|
159
|
+
program_data = get_environment_variable('PROGRAMDATA', 'C:/ProgramData')
|
160
|
+
File.join(program_data, app_name)
|
161
|
+
else
|
162
|
+
# Unix-like: Use /var/lib for system data
|
163
|
+
File.join('/var/lib', app_name)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Check if a process is running
|
168
|
+
# @param process_id [Integer] process ID
|
169
|
+
# @param process_name [String, nil] optional process name for verification
|
170
|
+
# @return [Boolean] true if process is running
|
171
|
+
def process_running?(process_id, process_name = nil)
|
172
|
+
return false if process_id.nil? || process_id <= 0
|
173
|
+
|
174
|
+
if windows?
|
175
|
+
# On Windows, use PowerShell to check if process exists
|
176
|
+
execute_command('powershell', '-Command',
|
177
|
+
"Get-Process -Id #{process_id} -ErrorAction SilentlyContinue | Out-Null")
|
178
|
+
elsif file_exists?("/proc/#{process_id}")
|
179
|
+
# On Unix-like systems, check /proc or use ps
|
180
|
+
return true unless process_name
|
181
|
+
|
182
|
+
# Verify process name if provided
|
183
|
+
begin
|
184
|
+
cmdline = read_file("/proc/#{process_id}/cmdline")
|
185
|
+
cmdline.include?(process_name)
|
186
|
+
rescue IOError
|
187
|
+
false
|
188
|
+
end
|
189
|
+
else
|
190
|
+
# Fallback to ps command
|
191
|
+
execute_command('ps', '-p', process_id.to_s)
|
192
|
+
end
|
193
|
+
rescue StandardError
|
194
|
+
false
|
195
|
+
end
|
196
|
+
|
197
|
+
# Get encrypted system client private key for identity provider
|
198
|
+
# @param identity_provider_name [String] identity provider name ('zero' or 'local')
|
199
|
+
# @param identity_endpoint [String] identity endpoint for DPAPI entropy
|
200
|
+
# @return [String, nil] private key PEM content or nil if not found
|
201
|
+
def get_encrypted_system_client(identity_provider_name, identity_endpoint = nil)
|
202
|
+
private_key_path = File.join(
|
203
|
+
get_application_data_path,
|
204
|
+
identity_provider_name,
|
205
|
+
'private',
|
206
|
+
'clients',
|
207
|
+
'system-client.key'
|
208
|
+
)
|
209
|
+
|
210
|
+
return nil unless file_exists?(private_key_path)
|
211
|
+
|
212
|
+
encrypted_data = read_binary_file(private_key_path)
|
213
|
+
|
214
|
+
if windows?
|
215
|
+
# On Windows, decrypt DPAPI-protected data
|
216
|
+
decrypted_data = decrypt_dpapi_data(encrypted_data, identity_endpoint)
|
217
|
+
raise IOError, 'Failed to decrypt system client private key using DPAPI' if decrypted_data.nil?
|
218
|
+
|
219
|
+
decrypted_data
|
220
|
+
else
|
221
|
+
# On non-Windows systems, assume it's already in PEM format
|
222
|
+
encrypted_data
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Decrypt DPAPI-protected data (Windows only)
|
227
|
+
# @param encrypted_data [String] binary encrypted data
|
228
|
+
# @param entropy [String, nil] entropy string (typically the identity endpoint URI)
|
229
|
+
# @return [String, nil] decrypted PEM private key or nil if decryption fails
|
230
|
+
def decrypt_dpapi_data(encrypted_data, entropy = nil)
|
231
|
+
return nil unless windows?
|
232
|
+
|
233
|
+
# Create cache key from encrypted data and entropy
|
234
|
+
require 'digest'
|
235
|
+
cache_key = Digest::SHA256.hexdigest("#{encrypted_data}#{entropy}")
|
236
|
+
|
237
|
+
# Check cache first
|
238
|
+
return @dpapi_cache[cache_key] if @dpapi_cache && @dpapi_cache[cache_key]
|
239
|
+
|
240
|
+
temp_file = nil
|
241
|
+
script_file = nil
|
242
|
+
output_file = nil
|
243
|
+
|
244
|
+
begin
|
245
|
+
# Create temporary files for DPAPI decryption process
|
246
|
+
temp_file = create_temp_file('encrypted_key', '.bin')
|
247
|
+
temp_file.binmode
|
248
|
+
temp_file.write(encrypted_data)
|
249
|
+
temp_file.close
|
250
|
+
|
251
|
+
script_file = create_temp_file('decrypt_dpapi', '.ps1')
|
252
|
+
output_file = create_temp_file('decrypted_output', '.txt')
|
253
|
+
|
254
|
+
# Close output file immediately after creation so PowerShell can write to it
|
255
|
+
output_file.close
|
256
|
+
|
257
|
+
# Create PowerShell script for DPAPI decryption
|
258
|
+
entropy_value = entropy&.gsub("'", "''")
|
259
|
+
script_content = create_dpapi_decrypt_script(
|
260
|
+
temp_file.path.gsub('/', '\\'),
|
261
|
+
output_file.path.gsub('/', '\\'),
|
262
|
+
entropy_value
|
263
|
+
)
|
264
|
+
|
265
|
+
# Write script content and close file properly
|
266
|
+
script_file.write(script_content)
|
267
|
+
script_file.close
|
268
|
+
|
269
|
+
# Execute PowerShell script
|
270
|
+
script_path = script_file.path.gsub('/', '\\')
|
271
|
+
|
272
|
+
success = execute_powershell_script_file(script_path)
|
273
|
+
|
274
|
+
result = nil
|
275
|
+
if success && file_exists?(output_file.path) && File.size(output_file.path).positive?
|
276
|
+
output_content = read_file(output_file.path).strip
|
277
|
+
# Return the result if it doesn't contain an error
|
278
|
+
result = output_content.start_with?('ERROR:') ? nil : output_content
|
279
|
+
end
|
280
|
+
|
281
|
+
# Cache the result (including nil results to avoid repeated failures)
|
282
|
+
@dpapi_cache ||= {}
|
283
|
+
@dpapi_cache[cache_key] = result
|
284
|
+
|
285
|
+
result
|
286
|
+
ensure
|
287
|
+
temp_file&.close
|
288
|
+
script_file&.close
|
289
|
+
output_file&.close
|
290
|
+
File.unlink(temp_file.path) if temp_file && File.exist?(temp_file.path)
|
291
|
+
File.unlink(script_file.path) if script_file && File.exist?(script_file.path)
|
292
|
+
File.unlink(output_file.path) if output_file && File.exist?(output_file.path)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
private
|
297
|
+
|
298
|
+
# Check if running as administrator on Windows using PowerShell
|
299
|
+
# @return [Boolean] true if current process has admin privileges
|
300
|
+
def check_windows_admin_privileges
|
301
|
+
# Use PowerShell to check if current user is in Administrators group and process is elevated
|
302
|
+
cmd = '([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent())' \
|
303
|
+
'.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)'
|
304
|
+
result = `powershell.exe -Command "#{cmd}" 2>nul`.strip
|
305
|
+
result.downcase == 'true'
|
306
|
+
rescue StandardError
|
307
|
+
# If PowerShell fails for any reason, assume not admin
|
308
|
+
false
|
309
|
+
end
|
310
|
+
|
311
|
+
# Read binary file content
|
312
|
+
# @param path [String] file path
|
313
|
+
# @return [String] binary file content
|
314
|
+
# @raise [IOError] if file cannot be read
|
315
|
+
def read_binary_file(path)
|
316
|
+
File.binread(path)
|
317
|
+
rescue Errno::ENOENT, Errno::EACCES => e
|
318
|
+
raise IOError, "Cannot read binary file #{path}: #{e.message}"
|
319
|
+
end
|
320
|
+
|
321
|
+
# Execute a system command
|
322
|
+
# @param command [String] command to execute
|
323
|
+
# @param args [Array<String>] command arguments
|
324
|
+
# @return [Boolean] true if command succeeded
|
325
|
+
def execute_command(command, *args)
|
326
|
+
if args.empty?
|
327
|
+
system(command)
|
328
|
+
else
|
329
|
+
system(command, *args)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Execute PowerShell script and return output
|
334
|
+
# @param script_content [String] PowerShell script content
|
335
|
+
# @return [String, nil] script output or nil if failed
|
336
|
+
def execute_powershell_script(script_content)
|
337
|
+
return nil unless windows?
|
338
|
+
|
339
|
+
require 'tempfile'
|
340
|
+
script_file = nil
|
341
|
+
output_file = nil
|
342
|
+
|
343
|
+
begin
|
344
|
+
script_file = create_temp_file('powershell_script', '.ps1')
|
345
|
+
output_file = create_temp_file('powershell_output', '.txt')
|
346
|
+
|
347
|
+
write_file(script_file.path, script_content)
|
348
|
+
|
349
|
+
# Execute PowerShell script
|
350
|
+
script_path = script_file.path.gsub('/', '\\')
|
351
|
+
output_path = output_file.path.gsub('/', '\\')
|
352
|
+
|
353
|
+
# Modify script to redirect output to file
|
354
|
+
unless script_content.include?('Out-File')
|
355
|
+
modified_script = script_content + "\n} | Out-File -FilePath '#{output_path}' -Encoding UTF8"
|
356
|
+
end
|
357
|
+
write_file(script_file.path, modified_script || script_content)
|
358
|
+
|
359
|
+
success = execute_command('powershell.exe', '-ExecutionPolicy', 'Bypass', '-NoProfile', '-File', script_path)
|
360
|
+
|
361
|
+
if success && file_exists?(output_file.path)
|
362
|
+
result = read_file(output_file.path).strip
|
363
|
+
result.empty? ? nil : result
|
364
|
+
end
|
365
|
+
rescue StandardError
|
366
|
+
nil
|
367
|
+
ensure
|
368
|
+
script_file&.close
|
369
|
+
output_file&.close
|
370
|
+
File.unlink(script_file.path) if script_file && File.exist?(script_file.path)
|
371
|
+
File.unlink(output_file.path) if output_file && File.exist?(output_file.path)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def user_config_path
|
376
|
+
if windows?
|
377
|
+
# Windows: Use APPDATA
|
378
|
+
get_environment_variable('APPDATA') || File.expand_path('~/AppData/Roaming')
|
379
|
+
else
|
380
|
+
# Unix-like: Use XDG_CONFIG_HOME or fallback to ~/.config
|
381
|
+
get_environment_variable('XDG_CONFIG_HOME') || File.expand_path('~/.config')
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def system_config_path
|
386
|
+
if windows?
|
387
|
+
# Windows: Use PROGRAMDATA
|
388
|
+
get_environment_variable('PROGRAMDATA', 'C:/ProgramData')
|
389
|
+
else
|
390
|
+
# Unix-like: Use /etc
|
391
|
+
'/etc'
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def create_dpapi_decrypt_script(encrypted_file_path, output_file_path, entropy_value)
|
396
|
+
<<~PS1
|
397
|
+
try {
|
398
|
+
# Load required .NET assemblies
|
399
|
+
Add-Type -AssemblyName System.Security -ErrorAction Stop
|
400
|
+
#{' '}
|
401
|
+
$encryptedBytes = [System.IO.File]::ReadAllBytes('#{encrypted_file_path}')
|
402
|
+
#{' '}
|
403
|
+
# Prepare entropy values to try
|
404
|
+
$entropyValues = @()
|
405
|
+
#{' '}
|
406
|
+
# First try the provided entropy (identity endpoint URI)
|
407
|
+
#{"$entropyValues += ,[System.Text.Encoding]::UTF8.GetBytes('#{entropy_value}')" if entropy_value}
|
408
|
+
#{' '}
|
409
|
+
# Also try some fallback values
|
410
|
+
$entropyValues += ,$null
|
411
|
+
$entropyValues += ,[System.Text.Encoding]::UTF8.GetBytes("eryph")
|
412
|
+
$entropyValues += ,[System.Text.Encoding]::UTF8.GetBytes("zero")
|
413
|
+
#{' '}
|
414
|
+
$scopes = @([System.Security.Cryptography.DataProtectionScope]::CurrentUser, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
|
415
|
+
#{' '}
|
416
|
+
foreach ($entropy in $entropyValues) {
|
417
|
+
foreach ($scope in $scopes) {
|
418
|
+
try {
|
419
|
+
$decryptedBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedBytes, $entropy, $scope)
|
420
|
+
$decryptedString = [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
|
421
|
+
[System.IO.File]::WriteAllText('#{output_file_path}', $decryptedString)
|
422
|
+
exit 0
|
423
|
+
} catch {
|
424
|
+
# Continue to next combination
|
425
|
+
}
|
426
|
+
}
|
427
|
+
}
|
428
|
+
#{' '}
|
429
|
+
# If we get here, all combinations failed
|
430
|
+
[System.IO.File]::WriteAllText('#{output_file_path}', "ERROR: All DPAPI decryption attempts failed")
|
431
|
+
exit 1
|
432
|
+
} catch {
|
433
|
+
[System.IO.File]::WriteAllText('#{output_file_path}', "ERROR: $($_.Exception.Message)")
|
434
|
+
exit 1
|
435
|
+
}
|
436
|
+
PS1
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'uri'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Eryph
|
6
|
+
module ClientRuntime
|
7
|
+
# Provides information about locally running eryph identity providers
|
8
|
+
# Contains decision logic for interpreting identity provider data
|
9
|
+
class LocalIdentityProviderInfo
|
10
|
+
# @return [Environment] environment abstraction
|
11
|
+
attr_reader :environment
|
12
|
+
|
13
|
+
# @return [String] identity provider name
|
14
|
+
attr_reader :identity_provider_name
|
15
|
+
|
16
|
+
# @return [Logger] logger instance
|
17
|
+
attr_reader :logger
|
18
|
+
|
19
|
+
# Initialize local identity provider info
|
20
|
+
# @param environment [Environment] environment abstraction
|
21
|
+
# @param identity_provider_name [String] identity provider name
|
22
|
+
# @param logger [Logger, nil] logger instance
|
23
|
+
def initialize(environment, identity_provider_name = 'identity', logger: nil)
|
24
|
+
@environment = environment
|
25
|
+
@identity_provider_name = identity_provider_name
|
26
|
+
@logger = logger || Logger.new($stdout).tap { |l| l.level = Logger::WARN }
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check if the identity provider is running
|
30
|
+
# @return [Boolean] true if the identity provider is running
|
31
|
+
def running?
|
32
|
+
@logger.debug("provider.running?: name=#{@identity_provider_name}")
|
33
|
+
|
34
|
+
meta = metadata
|
35
|
+
@logger.debug("provider.running?: metadata=#{meta.empty? ? 'empty' : 'found'}")
|
36
|
+
|
37
|
+
return false if meta.empty?
|
38
|
+
|
39
|
+
process_name = meta['processName']
|
40
|
+
process_id = meta['processId']
|
41
|
+
@logger.debug("provider.running?: process=#{process_name}, pid=#{process_id}")
|
42
|
+
|
43
|
+
if process_name.nil? || process_name.empty? || process_id.nil?
|
44
|
+
@logger.debug('provider.running?: invalid process info')
|
45
|
+
return false
|
46
|
+
end
|
47
|
+
|
48
|
+
# Check if the process is actually running
|
49
|
+
is_running = @environment.process_running?(process_id.to_i, process_name)
|
50
|
+
@logger.debug("provider.running?: process_check=#{is_running}")
|
51
|
+
is_running
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get endpoints from the running identity provider
|
55
|
+
# @return [Hash] endpoint name -> URI mapping
|
56
|
+
def endpoints
|
57
|
+
return {} unless running?
|
58
|
+
|
59
|
+
meta = metadata
|
60
|
+
endpoints_data = meta['endpoints']
|
61
|
+
return {} unless endpoints_data
|
62
|
+
|
63
|
+
result = {}
|
64
|
+
endpoints_data.each do |key, value|
|
65
|
+
uri = URI.parse(value.to_s)
|
66
|
+
# Only include valid HTTP/HTTPS URIs
|
67
|
+
result[key] = uri if uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
68
|
+
rescue URI::InvalidURIError
|
69
|
+
# Skip invalid URIs
|
70
|
+
next
|
71
|
+
end
|
72
|
+
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get the system client private key for eryph-zero
|
77
|
+
# @return [String, nil] private key content or nil if not found
|
78
|
+
def system_client_private_key
|
79
|
+
@logger.debug('system_client_private_key: getting endpoints')
|
80
|
+
endpoints_hash = endpoints
|
81
|
+
@logger.debug("system_client_private_key: endpoints=#{endpoints_hash.keys.join(',')}")
|
82
|
+
|
83
|
+
unless endpoints_hash.key?('identity')
|
84
|
+
@logger.debug('system_client_private_key: no identity endpoint')
|
85
|
+
return nil
|
86
|
+
end
|
87
|
+
|
88
|
+
identity_endpoint = endpoints_hash['identity']&.to_s
|
89
|
+
@logger.debug("system_client_private_key: identity_endpoint=#{identity_endpoint}")
|
90
|
+
|
91
|
+
private_key = @environment.get_encrypted_system_client(@identity_provider_name, identity_endpoint)
|
92
|
+
@logger.debug("system_client_private_key: result=#{private_key ? 'found' : 'nil'}")
|
93
|
+
private_key
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get system client credentials for eryph-zero
|
97
|
+
# @return [Hash, nil] client credentials hash or nil if not available
|
98
|
+
def system_client_credentials
|
99
|
+
@logger.debug('system_client_credentials: checking endpoints')
|
100
|
+
endpoints_hash = endpoints
|
101
|
+
@logger.debug("system_client_credentials: endpoints=#{endpoints_hash.keys.join(',')}")
|
102
|
+
|
103
|
+
unless endpoints_hash.key?('identity')
|
104
|
+
@logger.debug('system_client_credentials: no identity endpoint')
|
105
|
+
return nil
|
106
|
+
end
|
107
|
+
|
108
|
+
private_key = system_client_private_key
|
109
|
+
@logger.debug("system_client_credentials: private_key=#{private_key ? 'found' : 'nil'}")
|
110
|
+
return nil unless private_key
|
111
|
+
|
112
|
+
@logger.debug('system_client_credentials: creating credentials hash')
|
113
|
+
{
|
114
|
+
'id' => 'system-client',
|
115
|
+
'name' => 'Eryph Zero System Client',
|
116
|
+
'private_key' => private_key,
|
117
|
+
'identity_endpoint' => endpoints_hash['identity'].to_s,
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def metadata
|
124
|
+
lock_file_path = File.join(
|
125
|
+
@environment.get_application_data_path,
|
126
|
+
@identity_provider_name,
|
127
|
+
'.lock'
|
128
|
+
)
|
129
|
+
@logger.debug("get_metadata: checking #{lock_file_path}")
|
130
|
+
|
131
|
+
unless @environment.file_exists?(lock_file_path)
|
132
|
+
@logger.debug('get_metadata: file not found')
|
133
|
+
return {}
|
134
|
+
end
|
135
|
+
|
136
|
+
begin
|
137
|
+
content = @environment.read_file(lock_file_path)
|
138
|
+
# Strip BOM if present
|
139
|
+
content = content.sub(/\A\xEF\xBB\xBF/, '')
|
140
|
+
result = JSON.parse(content)
|
141
|
+
@logger.debug('get_metadata: parsed successfully')
|
142
|
+
result
|
143
|
+
rescue JSON::ParserError
|
144
|
+
@logger.debug('get_metadata: JSON parse error')
|
145
|
+
{}
|
146
|
+
rescue IOError
|
147
|
+
@logger.debug('get_metadata: IO error')
|
148
|
+
{}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|