runbooks 0.2.5__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.
Files changed (249) hide show
  1. conftest.py +26 -0
  2. jupyter-agent/.env +2 -0
  3. jupyter-agent/.env.template +2 -0
  4. jupyter-agent/.gitattributes +35 -0
  5. jupyter-agent/.gradio/certificate.pem +31 -0
  6. jupyter-agent/README.md +16 -0
  7. jupyter-agent/__main__.log +8 -0
  8. jupyter-agent/app.py +256 -0
  9. jupyter-agent/cloudops-agent.png +0 -0
  10. jupyter-agent/ds-system-prompt.txt +154 -0
  11. jupyter-agent/jupyter-agent.png +0 -0
  12. jupyter-agent/llama3_template.jinja +123 -0
  13. jupyter-agent/requirements.txt +9 -0
  14. jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +68 -0
  15. jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +91 -0
  16. jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +91 -0
  17. jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +57 -0
  18. jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +53 -0
  19. jupyter-agent/tmp/jupyter-agent.ipynb +27 -0
  20. jupyter-agent/utils.py +409 -0
  21. runbooks/__init__.py +71 -3
  22. runbooks/__main__.py +13 -0
  23. runbooks/aws/ec2_describe_instances.py +1 -1
  24. runbooks/aws/ec2_run_instances.py +8 -2
  25. runbooks/aws/ec2_start_stop_instances.py +17 -4
  26. runbooks/aws/ec2_unused_volumes.py +5 -1
  27. runbooks/aws/s3_create_bucket.py +4 -2
  28. runbooks/aws/s3_list_objects.py +6 -1
  29. runbooks/aws/tagging_lambda_handler.py +13 -2
  30. runbooks/aws/tags.json +12 -0
  31. runbooks/base.py +353 -0
  32. runbooks/cfat/README.md +49 -0
  33. runbooks/cfat/__init__.py +74 -0
  34. runbooks/cfat/app.ts +644 -0
  35. runbooks/cfat/assessment/__init__.py +40 -0
  36. runbooks/cfat/assessment/asana-import.csv +39 -0
  37. runbooks/cfat/assessment/cfat-checks.csv +31 -0
  38. runbooks/cfat/assessment/cfat.txt +520 -0
  39. runbooks/cfat/assessment/collectors.py +200 -0
  40. runbooks/cfat/assessment/jira-import.csv +39 -0
  41. runbooks/cfat/assessment/runner.py +387 -0
  42. runbooks/cfat/assessment/validators.py +290 -0
  43. runbooks/cfat/cli.py +103 -0
  44. runbooks/cfat/docs/asana-import.csv +24 -0
  45. runbooks/cfat/docs/cfat-checks.csv +31 -0
  46. runbooks/cfat/docs/cfat.txt +335 -0
  47. runbooks/cfat/docs/checks-output.png +0 -0
  48. runbooks/cfat/docs/cloudshell-console-run.png +0 -0
  49. runbooks/cfat/docs/cloudshell-download.png +0 -0
  50. runbooks/cfat/docs/cloudshell-output.png +0 -0
  51. runbooks/cfat/docs/downloadfile.png +0 -0
  52. runbooks/cfat/docs/jira-import.csv +24 -0
  53. runbooks/cfat/docs/open-cloudshell.png +0 -0
  54. runbooks/cfat/docs/report-header.png +0 -0
  55. runbooks/cfat/models.py +1026 -0
  56. runbooks/cfat/package-lock.json +5116 -0
  57. runbooks/cfat/package.json +38 -0
  58. runbooks/cfat/report.py +496 -0
  59. runbooks/cfat/reporting/__init__.py +46 -0
  60. runbooks/cfat/reporting/exporters.py +337 -0
  61. runbooks/cfat/reporting/formatters.py +496 -0
  62. runbooks/cfat/reporting/templates.py +135 -0
  63. runbooks/cfat/run-assessment.sh +23 -0
  64. runbooks/cfat/runner.py +69 -0
  65. runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
  66. runbooks/cfat/src/actions/check-config-existence.ts +37 -0
  67. runbooks/cfat/src/actions/check-control-tower.ts +37 -0
  68. runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
  69. runbooks/cfat/src/actions/check-iam-users.ts +50 -0
  70. runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
  71. runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
  72. runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
  73. runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
  74. runbooks/cfat/src/actions/create-backlog.ts +372 -0
  75. runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
  76. runbooks/cfat/src/actions/create-report.ts +616 -0
  77. runbooks/cfat/src/actions/define-account-type.ts +51 -0
  78. runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
  79. runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
  80. runbooks/cfat/src/actions/get-idc-info.ts +34 -0
  81. runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
  82. runbooks/cfat/src/actions/get-org-details.ts +35 -0
  83. runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
  84. runbooks/cfat/src/actions/get-org-ous.ts +35 -0
  85. runbooks/cfat/src/actions/get-regions.ts +22 -0
  86. runbooks/cfat/src/actions/zip-assessment.ts +27 -0
  87. runbooks/cfat/src/types/index.d.ts +147 -0
  88. runbooks/cfat/tests/__init__.py +141 -0
  89. runbooks/cfat/tests/test_cli.py +340 -0
  90. runbooks/cfat/tests/test_integration.py +290 -0
  91. runbooks/cfat/tests/test_models.py +505 -0
  92. runbooks/cfat/tests/test_reporting.py +354 -0
  93. runbooks/cfat/tsconfig.json +16 -0
  94. runbooks/cfat/webpack.config.cjs +27 -0
  95. runbooks/config.py +260 -0
  96. runbooks/finops/README.md +337 -0
  97. runbooks/finops/__init__.py +86 -0
  98. runbooks/finops/aws_client.py +245 -0
  99. runbooks/finops/cli.py +151 -0
  100. runbooks/finops/cost_processor.py +410 -0
  101. runbooks/finops/dashboard_runner.py +448 -0
  102. runbooks/finops/helpers.py +355 -0
  103. runbooks/finops/main.py +14 -0
  104. runbooks/finops/profile_processor.py +174 -0
  105. runbooks/finops/types.py +66 -0
  106. runbooks/finops/visualisations.py +80 -0
  107. runbooks/inventory/.gitignore +354 -0
  108. runbooks/inventory/ArgumentsClass.py +261 -0
  109. runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +619 -0
  110. runbooks/inventory/Inventory_Modules.py +6130 -0
  111. runbooks/inventory/LandingZone/delete_lz.py +1075 -0
  112. runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +738 -0
  113. runbooks/inventory/README.md +1320 -0
  114. runbooks/inventory/__init__.py +62 -0
  115. runbooks/inventory/account_class.py +532 -0
  116. runbooks/inventory/all_my_instances_wrapper.py +123 -0
  117. runbooks/inventory/aws_decorators.py +201 -0
  118. runbooks/inventory/aws_organization.png +0 -0
  119. runbooks/inventory/cfn_move_stack_instances.py +1526 -0
  120. runbooks/inventory/check_cloudtrail_compliance.py +614 -0
  121. runbooks/inventory/check_controltower_readiness.py +1107 -0
  122. runbooks/inventory/check_landingzone_readiness.py +711 -0
  123. runbooks/inventory/cloudtrail.md +727 -0
  124. runbooks/inventory/collectors/__init__.py +20 -0
  125. runbooks/inventory/collectors/aws_compute.py +518 -0
  126. runbooks/inventory/collectors/aws_networking.py +275 -0
  127. runbooks/inventory/collectors/base.py +222 -0
  128. runbooks/inventory/core/__init__.py +19 -0
  129. runbooks/inventory/core/collector.py +303 -0
  130. runbooks/inventory/core/formatter.py +296 -0
  131. runbooks/inventory/delete_s3_buckets_objects.py +169 -0
  132. runbooks/inventory/discovery.md +81 -0
  133. runbooks/inventory/draw_org_structure.py +748 -0
  134. runbooks/inventory/ec2_vpc_utils.py +341 -0
  135. runbooks/inventory/find_cfn_drift_detection.py +272 -0
  136. runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
  137. runbooks/inventory/find_cfn_stackset_drift.py +733 -0
  138. runbooks/inventory/find_ec2_security_groups.py +669 -0
  139. runbooks/inventory/find_landingzone_versions.py +201 -0
  140. runbooks/inventory/find_vpc_flow_logs.py +1221 -0
  141. runbooks/inventory/inventory.sh +659 -0
  142. runbooks/inventory/list_cfn_stacks.py +558 -0
  143. runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
  144. runbooks/inventory/list_cfn_stackset_operations.py +734 -0
  145. runbooks/inventory/list_cfn_stacksets.py +453 -0
  146. runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
  147. runbooks/inventory/list_ds_directories.py +354 -0
  148. runbooks/inventory/list_ec2_availability_zones.py +286 -0
  149. runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
  150. runbooks/inventory/list_ec2_instances.py +425 -0
  151. runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
  152. runbooks/inventory/list_elbs_load_balancers.py +411 -0
  153. runbooks/inventory/list_enis_network_interfaces.py +526 -0
  154. runbooks/inventory/list_guardduty_detectors.py +568 -0
  155. runbooks/inventory/list_iam_policies.py +404 -0
  156. runbooks/inventory/list_iam_roles.py +518 -0
  157. runbooks/inventory/list_iam_saml_providers.py +359 -0
  158. runbooks/inventory/list_lambda_functions.py +882 -0
  159. runbooks/inventory/list_org_accounts.py +446 -0
  160. runbooks/inventory/list_org_accounts_users.py +354 -0
  161. runbooks/inventory/list_rds_db_instances.py +406 -0
  162. runbooks/inventory/list_route53_hosted_zones.py +318 -0
  163. runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
  164. runbooks/inventory/list_sns_topics.py +360 -0
  165. runbooks/inventory/list_ssm_parameters.py +402 -0
  166. runbooks/inventory/list_vpc_subnets.py +433 -0
  167. runbooks/inventory/list_vpcs.py +422 -0
  168. runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
  169. runbooks/inventory/models/__init__.py +24 -0
  170. runbooks/inventory/models/account.py +192 -0
  171. runbooks/inventory/models/inventory.py +309 -0
  172. runbooks/inventory/models/resource.py +247 -0
  173. runbooks/inventory/recover_cfn_stack_ids.py +205 -0
  174. runbooks/inventory/requirements.txt +12 -0
  175. runbooks/inventory/run_on_multi_accounts.py +211 -0
  176. runbooks/inventory/tests/common_test_data.py +3661 -0
  177. runbooks/inventory/tests/common_test_functions.py +204 -0
  178. runbooks/inventory/tests/setup.py +24 -0
  179. runbooks/inventory/tests/src.py +18 -0
  180. runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
  181. runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
  182. runbooks/inventory/tests/test_inventory_modules.py +55 -0
  183. runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
  184. runbooks/inventory/tests/test_moto_integration_example.py +273 -0
  185. runbooks/inventory/tests/test_org_list_accounts.py +49 -0
  186. runbooks/inventory/update_aws_actions.py +173 -0
  187. runbooks/inventory/update_cfn_stacksets.py +1215 -0
  188. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
  189. runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
  190. runbooks/inventory/update_s3_public_access_block.py +539 -0
  191. runbooks/inventory/utils/__init__.py +23 -0
  192. runbooks/inventory/utils/aws_helpers.py +510 -0
  193. runbooks/inventory/utils/threading_utils.py +493 -0
  194. runbooks/inventory/utils/validation.py +682 -0
  195. runbooks/inventory/verify_ec2_security_groups.py +1430 -0
  196. runbooks/main.py +1004 -0
  197. runbooks/organizations/__init__.py +12 -0
  198. runbooks/organizations/manager.py +374 -0
  199. runbooks/security/README.md +447 -0
  200. runbooks/security/__init__.py +71 -0
  201. runbooks/{security_baseline → security}/checklist/alternate_contacts.py +8 -1
  202. runbooks/{security_baseline → security}/checklist/bucket_public_access.py +4 -1
  203. runbooks/{security_baseline → security}/checklist/cloudwatch_alarm_configuration.py +9 -2
  204. runbooks/{security_baseline → security}/checklist/guardduty_enabled.py +9 -2
  205. runbooks/{security_baseline → security}/checklist/multi_region_instance_usage.py +5 -1
  206. runbooks/{security_baseline → security}/checklist/root_access_key.py +6 -1
  207. runbooks/{security_baseline → security}/config-origin.json +1 -1
  208. runbooks/{security_baseline → security}/config.json +1 -1
  209. runbooks/{security_baseline → security}/permission.json +1 -1
  210. runbooks/{security_baseline → security}/report_generator.py +10 -2
  211. runbooks/{security_baseline → security}/report_template_en.html +7 -7
  212. runbooks/{security_baseline → security}/report_template_jp.html +7 -7
  213. runbooks/{security_baseline → security}/report_template_kr.html +12 -12
  214. runbooks/{security_baseline → security}/report_template_vn.html +7 -7
  215. runbooks/{security_baseline → security}/run_script.py +8 -2
  216. runbooks/{security_baseline → security}/security_baseline_tester.py +12 -4
  217. runbooks/{security_baseline → security}/utils/common.py +5 -1
  218. runbooks/utils/__init__.py +204 -0
  219. runbooks-0.7.0.dist-info/METADATA +375 -0
  220. runbooks-0.7.0.dist-info/RECORD +249 -0
  221. {runbooks-0.2.5.dist-info → runbooks-0.7.0.dist-info}/WHEEL +1 -1
  222. runbooks-0.7.0.dist-info/entry_points.txt +7 -0
  223. runbooks-0.7.0.dist-info/licenses/LICENSE +201 -0
  224. runbooks-0.7.0.dist-info/top_level.txt +3 -0
  225. runbooks/python101/calculator.py +0 -34
  226. runbooks/python101/config.py +0 -1
  227. runbooks/python101/exceptions.py +0 -16
  228. runbooks/python101/file_manager.py +0 -218
  229. runbooks/python101/toolkit.py +0 -153
  230. runbooks-0.2.5.dist-info/METADATA +0 -439
  231. runbooks-0.2.5.dist-info/RECORD +0 -61
  232. runbooks-0.2.5.dist-info/entry_points.txt +0 -3
  233. runbooks-0.2.5.dist-info/top_level.txt +0 -1
  234. /runbooks/{security_baseline/__init__.py → inventory/tests/script_test_data.py} +0 -0
  235. /runbooks/{security_baseline → security}/checklist/__init__.py +0 -0
  236. /runbooks/{security_baseline → security}/checklist/account_level_bucket_public_access.py +0 -0
  237. /runbooks/{security_baseline → security}/checklist/direct_attached_policy.py +0 -0
  238. /runbooks/{security_baseline → security}/checklist/iam_password_policy.py +0 -0
  239. /runbooks/{security_baseline → security}/checklist/iam_user_mfa.py +0 -0
  240. /runbooks/{security_baseline → security}/checklist/multi_region_trail.py +0 -0
  241. /runbooks/{security_baseline → security}/checklist/root_mfa.py +0 -0
  242. /runbooks/{security_baseline → security}/checklist/root_usage.py +0 -0
  243. /runbooks/{security_baseline → security}/checklist/trail_enabled.py +0 -0
  244. /runbooks/{security_baseline → security}/checklist/trusted_advisor.py +0 -0
  245. /runbooks/{security_baseline → security}/utils/__init__.py +0 -0
  246. /runbooks/{security_baseline → security}/utils/enums.py +0 -0
  247. /runbooks/{security_baseline → security}/utils/language.py +0 -0
  248. /runbooks/{security_baseline → security}/utils/level_const.py +0 -0
  249. /runbooks/{security_baseline → security}/utils/permission_list.py +0 -0
@@ -0,0 +1,247 @@
1
+ """
2
+ AWS resource models for inventory system.
3
+
4
+ This module provides Pydantic models for representing individual AWS
5
+ resources with proper validation, metadata, and cost information.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from typing import Any, Dict, List, Optional, Union
12
+
13
+ from pydantic import BaseModel, Field, validator
14
+
15
+
16
+ class ResourceState(str, Enum):
17
+ """AWS resource state enumeration."""
18
+
19
+ RUNNING = "running"
20
+ STOPPED = "stopped"
21
+ PENDING = "pending"
22
+ TERMINATED = "terminated"
23
+ AVAILABLE = "available"
24
+ UNAVAILABLE = "unavailable"
25
+ IN_USE = "in-use"
26
+ CREATING = "creating"
27
+ DELETING = "deleting"
28
+ UNKNOWN = "unknown"
29
+
30
+
31
+ class ResourceType(str, Enum):
32
+ """Supported AWS resource types."""
33
+
34
+ # Compute
35
+ EC2_INSTANCE = "ec2:instance"
36
+ LAMBDA_FUNCTION = "lambda:function"
37
+ ECS_CLUSTER = "ecs:cluster"
38
+ ECS_SERVICE = "ecs:service"
39
+ ECS_TASK = "ecs:task"
40
+
41
+ # Storage
42
+ S3_BUCKET = "s3:bucket"
43
+ EBS_VOLUME = "ebs:volume"
44
+ EBS_SNAPSHOT = "ebs:snapshot"
45
+ EFS_FILESYSTEM = "efs:filesystem"
46
+
47
+ # Database
48
+ RDS_INSTANCE = "rds:instance"
49
+ RDS_CLUSTER = "rds:cluster"
50
+ DYNAMODB_TABLE = "dynamodb:table"
51
+ ELASTICACHE_CLUSTER = "elasticache:cluster"
52
+
53
+ # Network
54
+ VPC = "vpc:vpc"
55
+ SUBNET = "vpc:subnet"
56
+ SECURITY_GROUP = "vpc:security-group"
57
+ LOAD_BALANCER = "elb:load-balancer"
58
+ ENI = "ec2:network-interface"
59
+
60
+ # Security & Identity
61
+ IAM_USER = "iam:user"
62
+ IAM_ROLE = "iam:role"
63
+ IAM_POLICY = "iam:policy"
64
+ GUARDDUTY_DETECTOR = "guardduty:detector"
65
+
66
+ # Management & Governance
67
+ CLOUDFORMATION_STACK = "cloudformation:stack"
68
+ CLOUDFORMATION_STACKSET = "cloudformation:stackset"
69
+ CONFIG_RECORDER = "config:recorder"
70
+ CLOUDTRAIL_TRAIL = "cloudtrail:trail"
71
+
72
+
73
+ @dataclass
74
+ class ResourceCost:
75
+ """Cost information for an AWS resource."""
76
+
77
+ monthly_cost: Optional[float] = None
78
+ currency: str = "USD"
79
+ cost_center: Optional[str] = None
80
+ billing_period: Optional[str] = None
81
+ cost_breakdown: Dict[str, float] = None
82
+
83
+ def __post_init__(self):
84
+ """Initialize cost breakdown if not provided."""
85
+ if self.cost_breakdown is None:
86
+ self.cost_breakdown = {}
87
+
88
+
89
+ class ResourceMetadata(BaseModel):
90
+ """
91
+ Metadata for AWS resource collection and management.
92
+
93
+ Provides context about when and how the resource was discovered,
94
+ along with collection-specific information.
95
+ """
96
+
97
+ account_id: str = Field(..., pattern=r"^\d{12}$", description="AWS account ID where resource exists")
98
+
99
+ region: str = Field(..., description="AWS region where resource is located")
100
+
101
+ service_category: str = Field(..., description="AWS service category (compute, storage, etc.)")
102
+
103
+ collection_timestamp: datetime = Field(..., description="When this resource was discovered")
104
+
105
+ collector_version: Optional[str] = Field(None, description="Version of collector that discovered this resource")
106
+
107
+ raw_data: Dict[str, Any] = Field(default_factory=dict, description="Raw AWS API response data")
108
+
109
+ discovery_method: str = Field("api", description="How this resource was discovered (api, cloudtrail, etc.)")
110
+
111
+ confidence_score: float = Field(1.0, ge=0.0, le=1.0, description="Confidence in resource data accuracy (0.0-1.0)")
112
+
113
+ class Config:
114
+ """Pydantic configuration."""
115
+
116
+ json_encoders = {datetime: lambda v: v.isoformat()}
117
+
118
+
119
+ class AWSResource(BaseModel):
120
+ """
121
+ Comprehensive model for AWS resources.
122
+
123
+ Represents any AWS resource with standardized fields for identification,
124
+ state management, cost tracking, and metadata.
125
+ """
126
+
127
+ # Core identification
128
+ resource_id: str = Field(..., description="AWS resource identifier (instance ID, bucket name, etc.)")
129
+
130
+ resource_type: str = Field(..., description="Type of AWS resource")
131
+
132
+ resource_arn: Optional[str] = Field(None, description="Amazon Resource Name (ARN) if available")
133
+
134
+ resource_name: Optional[str] = Field(None, description="Human-readable resource name")
135
+
136
+ # State and lifecycle
137
+ state: ResourceState = Field(ResourceState.UNKNOWN, description="Current resource state")
138
+
139
+ creation_date: Optional[datetime] = Field(None, description="Resource creation timestamp")
140
+
141
+ last_modified_date: Optional[datetime] = Field(None, description="Last modification timestamp")
142
+
143
+ # Location and organization
144
+ account_id: str = Field(..., pattern=r"^\d{12}$", description="AWS account ID")
145
+
146
+ region: str = Field(..., description="AWS region")
147
+
148
+ availability_zone: Optional[str] = Field(None, description="Availability zone if applicable")
149
+
150
+ # Configuration and properties
151
+ configuration: Dict[str, Any] = Field(default_factory=dict, description="Resource-specific configuration details")
152
+
153
+ tags: Dict[str, str] = Field(default_factory=dict, description="Resource tags")
154
+
155
+ # Security and compliance
156
+ security_groups: List[str] = Field(default_factory=list, description="Associated security group IDs")
157
+
158
+ encryption_status: Optional[str] = Field(None, description="Encryption status (encrypted, not-encrypted, unknown)")
159
+
160
+ public_access: bool = Field(False, description="Whether resource has public internet access")
161
+
162
+ # Cost information
163
+ cost_info: Optional[ResourceCost] = Field(None, description="Cost information if available")
164
+
165
+ # Metadata
166
+ metadata: ResourceMetadata = Field(..., description="Collection and discovery metadata")
167
+
168
+ # Relationships
169
+ dependencies: List[str] = Field(default_factory=list, description="List of resource ARNs this resource depends on")
170
+
171
+ dependents: List[str] = Field(
172
+ default_factory=list, description="List of resource ARNs that depend on this resource"
173
+ )
174
+
175
+ class Config:
176
+ """Pydantic configuration."""
177
+
178
+ use_enum_values = True
179
+ json_encoders = {datetime: lambda v: v.isoformat() if v else None}
180
+
181
+ @validator("resource_arn")
182
+ def validate_arn_format(cls, v):
183
+ """Validate ARN format if provided."""
184
+ if v and not v.startswith("arn:aws:"):
185
+ raise ValueError('ARN must start with "arn:aws:"')
186
+ return v
187
+
188
+ @validator("account_id")
189
+ def validate_account_id(cls, v):
190
+ """Validate account ID format."""
191
+ if not v.isdigit() or len(v) != 12:
192
+ raise ValueError("Account ID must be exactly 12 digits")
193
+ return v
194
+
195
+ def is_active(self) -> bool:
196
+ """Check if resource is in an active state."""
197
+ active_states = {ResourceState.RUNNING, ResourceState.AVAILABLE, ResourceState.IN_USE}
198
+ return self.state in active_states
199
+
200
+ def is_billable(self) -> bool:
201
+ """Check if resource is likely to incur costs."""
202
+ # Most resources are billable unless explicitly stopped/terminated
203
+ non_billable_states = {ResourceState.STOPPED, ResourceState.TERMINATED, ResourceState.UNAVAILABLE}
204
+ return self.state not in non_billable_states
205
+
206
+ def get_cost_estimate(self) -> float:
207
+ """Get monthly cost estimate for this resource."""
208
+ if self.cost_info and self.cost_info.monthly_cost:
209
+ return self.cost_info.monthly_cost
210
+ return 0.0
211
+
212
+ def has_tag(self, key: str, value: Optional[str] = None) -> bool:
213
+ """Check if resource has a specific tag."""
214
+ if key not in self.tags:
215
+ return False
216
+ if value is None:
217
+ return True
218
+ return self.tags[key] == value
219
+
220
+ def get_tag(self, key: str, default: str = "") -> str:
221
+ """Get tag value with optional default."""
222
+ return self.tags.get(key, default)
223
+
224
+ def add_dependency(self, resource_arn: str) -> None:
225
+ """Add a resource dependency."""
226
+ if resource_arn not in self.dependencies:
227
+ self.dependencies.append(resource_arn)
228
+
229
+ def add_dependent(self, resource_arn: str) -> None:
230
+ """Add a dependent resource."""
231
+ if resource_arn not in self.dependents:
232
+ self.dependents.append(resource_arn)
233
+
234
+ def get_service_name(self) -> str:
235
+ """Extract AWS service name from resource type."""
236
+ if ":" in self.resource_type:
237
+ return self.resource_type.split(":")[0]
238
+ return self.resource_type
239
+
240
+ def to_dict(self) -> Dict[str, Any]:
241
+ """Convert to dictionary representation."""
242
+ return self.dict()
243
+
244
+ def __str__(self) -> str:
245
+ """String representation."""
246
+ name = self.resource_name or self.resource_id
247
+ return f"AWSResource({self.resource_type}: {name})"
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+ import sys
5
+ from os.path import split
6
+ from pprint import pprint
7
+ from time import time
8
+
9
+ import Inventory_Modules
10
+ import simplejson as json
11
+ from account_class import aws_acct_access
12
+ from ArgumentsClass import CommonArguments
13
+ from colorama import Fore, init
14
+ from Inventory_Modules import get_credentials_for_accounts_in_org
15
+
16
+ """
17
+ This script was created to help solve a testing problem for the "move_stack_instances.py" script.e
18
+ Originally, that script didn't have built-in recovery, so we needed this script to "recover" those stack-instance ids that might have been lost during the move_stack_instances.py run. However, that script now has built-in recovery, so this script isn't really needed. However, it can still be used to find any stack-instances that have been orphaned from their original stack-set, if that happens.
19
+ """
20
+
21
+ init()
22
+ __version__ = "2024.05.18"
23
+
24
+
25
+ #########################
26
+ # Functions
27
+ #########################
28
+
29
+
30
+ def parse_args(args):
31
+ """
32
+ Description: Parse the arguments sent to the script
33
+ @param args: namespace of the arguments passed in at the command line
34
+ @return: namespace with all parameters parsed out
35
+ """
36
+ script_path, script_name = split(sys.argv[0])
37
+ parser = CommonArguments()
38
+ parser.singleprofile() # Allows for a single profile to be specified
39
+ parser.singleregion() # Allows for a single region to be specified at the command line
40
+ parser.extendedargs() # Allows for extended arguments like which accounts to skip, and whether Force is enabled.
41
+ parser.fragment()
42
+ parser.timing()
43
+ parser.rolestouse()
44
+ parser.verbosity() # Allows for the verbosity to be handled.
45
+ parser.version(__version__)
46
+ local = parser.my_parser.add_argument_group(script_name, "Parameters specific to this script")
47
+ # local.add_argument(
48
+ # "-s", "--status",
49
+ # dest="status",
50
+ # metavar="CloudFormation status",
51
+ # default="active",
52
+ # help="String that determines whether we only see 'CREATE_COMPLETE' or 'DELETE_COMPLETE' too")
53
+ local.add_argument(
54
+ "-R", "--SearchRegions", dest="pRegionList", metavar="Region name", help="Regions where StackSets are applied"
55
+ )
56
+ local.add_argument("--new", dest="newStackSetName", metavar="New Stackset name", help="The NEW Stack Set name")
57
+ local.add_argument("--old", dest="oldStackSetName", metavar="Old Stackset name", help="The OLD Stack Set name")
58
+ return parser.my_parser.parse_args(args)
59
+
60
+
61
+ def setup_auth_and_regions(fProfile: str = None) -> (object, list, list):
62
+ """
63
+ Description: This function takes in a profile, and returns the account object and the regions valid for this account / org.
64
+ @param fProfile: A string representing the profile provided by the user. If nothing, then use the default profile or credentials.
65
+ @return: (aws_acct_access, list)
66
+ """
67
+ aws_acct = aws_acct_access(fProfile)
68
+ ChildAccounts = aws_acct.ChildAccounts
69
+ RegionList = Inventory_Modules.get_regions3(aws_acct, pRegionList)
70
+ ChildAccounts = Inventory_Modules.RemoveCoreAccounts(ChildAccounts, pAccountsToSkip)
71
+ AccountList = [account["AccountId"] for account in ChildAccounts]
72
+
73
+ print(f"You asked to find stacks with this fragment list: {Fore.RED}{pFragments}{Fore.RESET}")
74
+ print(f"\t\tin these accounts: {Fore.RED}{AccountList}{Fore.RESET}")
75
+ print(f"\t\tin these regions: {Fore.RED}{RegionList}{Fore.RESET}")
76
+ return aws_acct, AccountList, RegionList
77
+
78
+
79
+ #########################
80
+ # Main
81
+ #########################
82
+
83
+ if __name__ == "__main__":
84
+ args = parse_args(sys.argv[1:])
85
+ pProfile = args.Profile
86
+ pRegion = args.Region
87
+ pRegionList = args.pRegionList
88
+ pAccountsToSkip = args.SkipAccounts
89
+ pAccounts = args.Accounts
90
+ pOldStackSetName = args.oldStackSetName
91
+ pNewStackSetName = args.newStackSetName
92
+ pTiming = args.Time
93
+ pFragments = args.Fragments
94
+ # pstatus = args.status
95
+ verbose = args.loglevel
96
+ # Setup logging levels
97
+ logging.basicConfig(level=verbose, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
98
+ logging.getLogger("boto3").setLevel(logging.CRITICAL)
99
+ logging.getLogger("botocore").setLevel(logging.CRITICAL)
100
+ logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
101
+ logging.getLogger("urllib3").setLevel(logging.CRITICAL)
102
+
103
+ ##########################
104
+ ERASE_LINE = "\x1b[2K"
105
+ begin_time = time()
106
+
107
+ # Setup credentials and regions (filtered by what they wanted to check)
108
+ aws_acct, AccountList, RegionList = setup_auth_and_regions(pProfile)
109
+
110
+ pStackIdFlag = True
111
+ precoveryFlag = True
112
+
113
+ print()
114
+ fmt = "%-20s %-15s %-15s %-50s %-50s"
115
+ print(fmt % ("Account", "Region", "Stack Status", "Stack Name", "Stack ID"))
116
+ print(fmt % ("-------", "------", "------------", "----------", "--------"))
117
+
118
+ StacksFound = []
119
+ sts_client = aws_acct.session.client("sts")
120
+ item_counter = 0
121
+ AllCredentials = get_credentials_for_accounts_in_org(aws_acct)
122
+ for cred in AllCredentials:
123
+ if not cred["Success"]:
124
+ continue
125
+ for region in RegionList:
126
+ item_counter += 1
127
+ Stacks = False
128
+ try:
129
+ Stacks = Inventory_Modules.find_stacks2(cred, region, pFragments)
130
+ logging.warning(f"Account: {cred['AccountId']} | Region: {region} | Found {len(Stacks)} Stacks")
131
+ print(
132
+ f"{ERASE_LINE}{Fore.RED}Account: {cred['AccountId']} Region: {region} Found {len(Stacks)} Stacks{Fore.RESET} ({item_counter} of {len(AccountList) * len(RegionList)})",
133
+ end="\r",
134
+ )
135
+ except Exception as my_Error:
136
+ print(f"Error: {my_Error}")
137
+ # TODO: Currently we're using this "Stacks" list as a boolean if it's populated. We should change this.
138
+ if Stacks and len(Stacks) > 0:
139
+ for y in range(len(Stacks)):
140
+ StackName = Stacks[y]["StackName"]
141
+ StackStatus = Stacks[y]["StackStatus"]
142
+ StackID = Stacks[y]["StackId"]
143
+ if pStackIdFlag:
144
+ print(fmt % (cred["AccountId"], region, StackStatus, StackName, StackID))
145
+ else:
146
+ print(fmt % (cred["AccountId"], region, StackStatus, StackName))
147
+ StacksFound.append(
148
+ {
149
+ "Account": cred["AccountId"],
150
+ "Region": region,
151
+ "StackName": StackName,
152
+ "StackStatus": StackStatus,
153
+ "StackArn": StackID,
154
+ }
155
+ )
156
+ lAccounts = []
157
+ lRegions = []
158
+ lAccountsAndRegions = []
159
+ for i in range(len(StacksFound)):
160
+ lAccounts.append(StacksFound[i]["Account"])
161
+ lRegions.append(StacksFound[i]["Region"])
162
+ lAccountsAndRegions.append((StacksFound[i]["Account"], StacksFound[i]["Region"]))
163
+ print(ERASE_LINE)
164
+ print(
165
+ f"{Fore.RED}Looked through {len(StacksFound)} Stacks across {len(AccountList)} accounts across {len(RegionList)} regions{Fore.RESET}"
166
+ )
167
+ print()
168
+ if args.loglevel < 21: # INFO level
169
+ print("The list of accounts and regions:")
170
+ pprint(list(sorted(set(lAccountsAndRegions))))
171
+
172
+ if precoveryFlag:
173
+ Stack_Instances = []
174
+ for item in StacksFound:
175
+ Stack_Instances.append(
176
+ {
177
+ "Account": item["Account"],
178
+ "Region": item["Region"],
179
+ "Status": item["StackStatus"],
180
+ "StackId": item["StackArn"],
181
+ }
182
+ )
183
+ stack_ids = {"Stack_instances": Stack_Instances, "Success": True}
184
+ BigString = {
185
+ "AccountNumber": aws_acct.acct_number,
186
+ "AccountToMove": None,
187
+ "ManagementAccount": aws_acct.MgmtAccount,
188
+ "NewStackSetName": pNewStackSetName,
189
+ "OldStackSetName": pOldStackSetName,
190
+ "ProfileUsed": pProfile,
191
+ "Region": pRegion,
192
+ "stack_ids": stack_ids,
193
+ }
194
+ file_data = json.dumps(BigString, sort_keys=True, indent=4 * " ")
195
+ OutputFilename = f"{pOldStackSetName}-{pNewStackSetName}-{aws_acct.acct_number}-{pRegion}"
196
+ with open(OutputFilename, "w") as out:
197
+ print(file_data, file=out)
198
+
199
+ if pTiming:
200
+ print(ERASE_LINE)
201
+ print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
202
+
203
+ print()
204
+ print("Thanks for using this script...")
205
+ print()
@@ -0,0 +1,12 @@
1
+ boto3>=1.26.84
2
+ botocore>=1.29.84
3
+ colorama>=0.4.6
4
+ prettytable>=3.6.0
5
+ simplejson>=3.18.3
6
+ urllib3>=1.26.14
7
+ randominfo>=2.0.2
8
+ graphviz>=0.20.1
9
+ tqdm>=4.65.0
10
+ python-dateutil>=2.9.0.post0
11
+ pytest>=8.2.2
12
+ ipywidgets<=8.1.3
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+ import sys
5
+
6
+ import Inventory_Modules
7
+ from account_class import aws_acct_access
8
+ from ArgumentsClass import CommonArguments
9
+ from botocore.exceptions import ClientError
10
+ from colorama import Fore, init
11
+
12
+ init()
13
+ __version__ = "2023.05.04"
14
+
15
+ parser = CommonArguments()
16
+ parser.singleprofile()
17
+ parser.verbosity() # Allows for the verbosity to be handled.
18
+ parser.version(__version__)
19
+ parser.my_parser.add_argument(
20
+ "-f",
21
+ "--file",
22
+ dest="pAccountFile",
23
+ metavar="Account File",
24
+ required=True,
25
+ default=None,
26
+ help="List of account numbers, one per line.",
27
+ )
28
+ parser.my_parser.add_argument(
29
+ "--Role",
30
+ dest="pAccessRole",
31
+ metavar="Access Role",
32
+ default="Admin",
33
+ help="Role used to gain access to the list of accounts.",
34
+ )
35
+ args = parser.my_parser.parse_args()
36
+
37
+ pProfile = args.Profile
38
+ pAccountFile = args.pAccountFile
39
+ pAccessRole = args.pAccessRole
40
+ verbose = args.loglevel
41
+ logging.basicConfig(level=args.loglevel, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
42
+
43
+ aws_acct = aws_acct_access(pProfile)
44
+
45
+ #####################
46
+
47
+
48
+ def check_account_access(faws_acct, faccount_num, fAccessRole=None):
49
+ if fAccessRole is None:
50
+ logging.error(f"Role must be provided")
51
+ return_response = {"Success": False, "ErrorMessage": "Role wasn't provided"}
52
+ return return_response
53
+ sts_client = faws_acct.session.client("sts")
54
+ try:
55
+ role_arn = f"arn:aws:iam::{faccount_num}:role/{fAccessRole}"
56
+ credentials = sts_client.assume_role(RoleArn=role_arn, RoleSessionName="TheOtherGuy")["Credentials"]
57
+ return_response = {"Credentials": credentials, "Success": True, "ErrorMessage": ""}
58
+ return return_response
59
+ except ClientError as my_Error:
60
+ print(f"Client Error: {my_Error}")
61
+ return_response = {"Success": False, "ErrorMessage": "Client Error"}
62
+ return return_response
63
+ except sts_client.exceptions.MalformedPolicyDocumentException as my_Error:
64
+ print(f"MalformedPolicy: {my_Error}")
65
+ return_response = {"Success": False, "ErrorMessage": "Malformed Policy"}
66
+ return return_response
67
+ except sts_client.exceptions.PackedPolicyTooLargeException as my_Error:
68
+ print(f"Policy is too large: {my_Error}")
69
+ return_response = {"Success": False, "ErrorMessage": "Policy is too large"}
70
+ return return_response
71
+ except sts_client.exceptions.RegionDisabledException as my_Error:
72
+ print(f"Region is disabled: {my_Error}")
73
+ return_response = {"Success": False, "ErrorMessage": "Region Disabled"}
74
+ return return_response
75
+ except sts_client.exceptions.ExpiredTokenException as my_Error:
76
+ print(f"Expired Token: {my_Error}")
77
+ return_response = {"Success": False, "ErrorMessage": "Expired Token"}
78
+ return return_response
79
+
80
+
81
+ def participant_user(faws_acct, create=None, username=None):
82
+ from randominfo import random_password
83
+
84
+ if create is None:
85
+ create = True
86
+ if username is None:
87
+ username = "reinvent_admin"
88
+ password = random_password()
89
+ client_iam = faws_acct.session.client("iam")
90
+ return_response = {
91
+ "Success": False,
92
+ "ErrorMessage": None,
93
+ "AccountId": None,
94
+ "User": None,
95
+ "Password": None,
96
+ "AccessKeyId": None,
97
+ "SecretAccessKey": None,
98
+ }
99
+ if create:
100
+ try:
101
+ user_response = client_iam.create_user(UserName=username)["User"]
102
+ return_response = {"Success": True, "AccountId": faws_acct.acct_number, "User": username, "Password": None}
103
+
104
+ except client_iam.exceptions.EntityAlreadyExistsException as my_Error:
105
+ logging.error(f"User {username} already exists... moving on")
106
+ try:
107
+ # user_response = client_iam.create_user(UserName=username)['User']
108
+ password_response = client_iam.create_login_profile(
109
+ UserName=username, Password=password, PasswordResetRequired=False
110
+ )
111
+ return_response = {
112
+ "Success": True,
113
+ "AccountId": faws_acct.acct_number,
114
+ "User": username,
115
+ "Password": password,
116
+ }
117
+ except client_iam.exceptions.EntityAlreadyExistsException as my_Error:
118
+ logging.error("User login profile already exists, so we'll update it instead")
119
+ try:
120
+ password_response = client_iam.update_login_profile(
121
+ UserName=username, Password=password, PasswordResetRequired=False
122
+ )
123
+ return_response = {
124
+ "Success": True,
125
+ "AccountId": faws_acct.acct_number,
126
+ "User": username,
127
+ "Password": password,
128
+ }
129
+ except ClientError as my_Error:
130
+ ErrorMessage = f"Client Error: {my_Error}"
131
+ logging.error(f"ErrorMessage: {ErrorMessage}")
132
+ return_response = {"Success": False, "ErrorMessage": ErrorMessage}
133
+ except ClientError as my_Error:
134
+ ErrorMessage = f"Client Error: {my_Error}"
135
+ logging.error(f"ErrorMessage: {ErrorMessage}")
136
+ return_response = {"Success": False, "ErrorMessage": ErrorMessage}
137
+ try:
138
+ access_keys = client_iam.create_access_key(UserName=username)["AccessKey"]
139
+ return_response["AccessKeyId"] = access_keys["AccessKeyId"]
140
+ return_response["SecretAccessKey"] = access_keys["SecretAccessKey"]
141
+ except (
142
+ ClientError,
143
+ client_iam.exceptions.NoSuchEntityException,
144
+ client_iam.exceptions.LimitExceededException,
145
+ client_iam.exceptions.ServiceFailureException,
146
+ ) as my_Error:
147
+ ErrorMessage = f"Client Error: {my_Error}"
148
+ logging.error(f"ErrorMessage: {ErrorMessage}")
149
+ return_response["Success"] = False
150
+ return_response["ErrorMessage"] = ErrorMessage
151
+ try:
152
+ attached_policy = client_iam.attach_user_policy(
153
+ UserName=username, PolicyArn="arn:aws:iam::aws:policy/AdministratorAccess"
154
+ )
155
+ return_response["Success"] = True
156
+ except (
157
+ client_iam.exceptions.EntityTemporarilyUnmodifiableException,
158
+ client_iam.exceptions.NoSuchEntityException,
159
+ client_iam.exceptions.PasswordPolicyViolationException,
160
+ client_iam.exceptions.PasswordPolicyViolationException,
161
+ client_iam.exceptions.LimitExceededException,
162
+ client_iam.exceptions.ServiceFailureException,
163
+ ) as my_Error:
164
+ ErrorMessage = f"Specific Error: {my_Error}"
165
+ logging.error(f"ErrorMessage: {ErrorMessage}")
166
+ return_response["Success"] = False
167
+ return_response["ErrorMessage"] = ErrorMessage
168
+ return return_response
169
+
170
+
171
+ #####################
172
+
173
+
174
+ if pAccountFile is None:
175
+ logging.critical(f"file of accounts was not provided. This is required. Exiting.")
176
+ sys.exit(1)
177
+
178
+ Accounts = []
179
+ with open(pAccountFile, "r") as infile:
180
+ for line in infile:
181
+ Accounts.append(line.rstrip("\r\n,"))
182
+ infile.close()
183
+
184
+ for account_num in Accounts:
185
+ logging.info(
186
+ f"Accessing account #{account_num} as {pAccessRole} using account's {aws_acct.acct_number}'s credentials"
187
+ )
188
+ response = check_account_access(aws_acct, account_num, pAccessRole)
189
+ if response["Success"]:
190
+ print(f"Account {account_num} was successfully connected via role {pAccessRole} from {aws_acct.acct_number}")
191
+ """
192
+ Put more commands here... Or you can write functions that represent your commands and call them from here.
193
+ """
194
+ credentials = Inventory_Modules.get_child_access3(aws_acct, account_num, "us-east-1", ["reinvent-Admin"])
195
+ tgt_aws_access = aws_acct_access(ocredentials=credentials)
196
+ username = "Paul"
197
+ user_response = participant_user(tgt_aws_access, username=username)
198
+ else:
199
+ print(
200
+ f"Access Role {pAccessRole} failed to connect to {account_num} from {aws_acct.acct_number} with error: {response['ErrorMessage']}"
201
+ )
202
+
203
+ # Display access keys
204
+ print(f"Credentials for account {tgt_aws_access.acct_number}")
205
+ print(f"User {username} has been created (or confirmed) in account {user_response['AccountId']}")
206
+ print(f"Password for {user_response['User']} is {user_response['Password']}")
207
+ print(f"Access Keys are:\n{user_response['AccessKeyId']}\n{user_response['SecretAccessKey']}")
208
+
209
+ print()
210
+ print("Thanks for using this script...")
211
+ print()