Skip to content

Commit 6c7ff6b

Browse files
HuangYanyan-AmberfranklwyLiu Wenyuashorezfc11zyw0599
authored
Feature/ccm (#222)
* Add files via upload add huaweicloud Kafka custodian * Changes set-config Delete json-diff * delete import * rename set_config * Feature/dns (#9) add dns actions, filters, UTs * Feature/dns (#10) * add readme_dns * add readme_dns * add readme_dns * Update readme_dns.md * Update readme_dns.md * add dns * update readme_dns * update kafka * update client * update dns * update dns * update dns add dns_test * update dns_test update mock * add mock * update dns * Delete tools/c7n_huaweicloud/c7n_huaweicloud/resources/dns_files directory * update dns lint * lint modify * lint modify * modify * modify * update test --------- Co-authored-by: Liu Wenyu <[email protected]> * Create readme_dns.md * feat:ccm服务开发 * feat:scm服务开发 * feat:mock文件提交 * fix:修复查询DC资源测试用例报错的问题 * fix:修改lint门禁 * fix:lint门禁修改 * fix:lint门禁修改 * fix:test门禁修改 * fix:test门禁修改 * fix:修改lint门禁 * fix:mock文件修改 * Update ci.yml * feat:修改scm代码 * fix:test门禁处理 * fix:test门禁处理 * fix:test门禁处理 * fix:test门禁修改 * fix:test门禁修改 * fix:test门禁处理 * fix:门禁处理 * fix:test门禁修改 * fix:删除poetry.lock文件中阿里云镜像依赖 * fxi:删除阿里云镜像依赖 * fix: client change * Update ci.yml * fix:tag_resource_type修改 * fix:修改test门禁 * feat:添加tag_resource_type相关代码 * feat:ccm新需求开发 * feat:ccm服务新需求开发 * feat:ccm资源名称修改及ccm标签增强 * fix:lint门禁修改 * feat:修改查询标签的client * fix:修改lint门禁 * fix:test门禁修改 * feat:删除多余重复代码 * feat:ccm需求更改 * fix:lint门禁修改 * fix:test门禁处理 * feat:ccm需求更改 * fix:lint门禁修改 * fix:lint门禁修改 * feat:ccm服务添加issue_name filter * fix:修改lint门禁问题 * fix:修改issue——name测试用例 * fix:修改issue——name测试用例mock文件 * fix:mock文件提交 * fix:gitignore文件中删除多余代码 * feat:私有证书列表添加按create_time过滤的filter * feat:lint门禁修改和创建时间提交 * fix:修改tag_resource_type的判断条件 * fix:test门禁处理 * fix:test门禁处理 * fix:ccm mock文件还原 * feat:scm服务名称修改 * feat:scm添加查询tags的功能 * fix:tags添加修改 * fix:lts_transfer mock文件去除分页 * fix:分页参数修改 * fix:修改分页参数 * fix:修改mock文件 * fix:修改lint门禁 * fix:添加异常抛出 * fix:修改异常抛出 * fix:修改lint门禁 * fix:抛出异常逻辑修改 * feat:ccm添加obs桶策略过滤 * fix:修改lint门禁问题 * feat:修改obs桶策略判断逻辑 * fix:修改lint门禁 --------- Co-authored-by: franklwy <[email protected]> Co-authored-by: Liu Wenyu <[email protected]> Co-authored-by: zfc1996 <[email protected]> Co-authored-by: zyw0599 <[email protected]>
1 parent 8c3205b commit 6c7ff6b

File tree

7 files changed

+561
-146
lines changed

7 files changed

+561
-146
lines changed

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,3 @@ venv*/
6262

6363
# files generated during "make data-update"
6464
iam_docs/
65-
huaweicloudsdkccm
66-
output

tools/c7n_huaweicloud/c7n_huaweicloud/resources/ccm.py

Lines changed: 223 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
log = logging.getLogger('custodian.huaweicloud.resources.ccm')
1818

1919

20+
class ObsSdkError():
21+
def __init__(self, code, message, request_id):
22+
self.error_code = code
23+
self.error_msg = message
24+
self.request_id = request_id
25+
self.encoded_auth_msg = ""
26+
27+
2028
@resources.register('ccm-private-ca')
2129
class CertificateAuthority(QueryResourceManager):
2230
"""Huawei Cloud Certificate Authority Resource Manager
@@ -71,9 +79,8 @@ def augment(self, resources):
7179
# Convert response tags to standard dict format
7280
tags = []
7381
for tag in response.tags:
74-
if hasattr(tag, 'key') and hasattr(tag, 'value'):
75-
tags.append(
76-
{'key': tag.key, 'value': tag.value})
82+
tags.append(
83+
{'key': tag.key, 'value': tag.value})
7784
resource['tags'] = tags
7885
else:
7986
resource['tags'] = []
@@ -289,13 +296,31 @@ def process(self, resources, event=None):
289296

290297
if should_include:
291298
results.append(resource)
299+
elif resp.status == 403:
300+
error_obj = ObsSdkError(
301+
"PermissionDenied,Please confirm that",
302+
"you have 'obs:bucket:GetBucketPublicAccessBlock' permission",
303+
""
304+
)
305+
raise exceptions.ClientRequestException(
306+
resp.status, error_obj)
307+
elif resp.status >= 300:
308+
error_obj = ObsSdkError(
309+
"RequestFailed",
310+
f"Request failed, status code: {resp.status}",
311+
""
312+
)
313+
raise exceptions.ClientRequestException(
314+
resp.status, error_obj)
292315

293316
except exceptions.ClientRequestException as e:
294317
# Log the error but don't include the resource in results
295318
log.error(
296319
f"Failed to get bucket PublicAccessBlock for {obs_bucket_name}: {e.error_msg}")
297-
continue
298-
320+
if e.status_code == 403:
321+
raise e
322+
else:
323+
continue
299324
return results
300325

301326

@@ -377,6 +402,197 @@ def process(self, resources, event=None):
377402
return results
378403

379404

405+
@CertificateAuthority.filter_registry.register('obs-bucket-policy')
406+
class CertificateAuthorityObsBucketPolicyFilter(Filter):
407+
"""Filter certificate authorities by their OBS bucket policy configuration
408+
409+
This filter checks if the OBS bucket associated with a CA has proper policy
410+
configuration for secure access. It filters out CAs whose OBS bucket policy
411+
doesn't meet both of the following criteria:
412+
413+
1. Has a statement with sid='deny_except_agency' and effect='Deny', and NotPrincipal
414+
contains at least one ID where the part after the last '/' equals 'PCAAccessPrivateOBS',
415+
and Action equals 'PutObject'
416+
2. Has a statement with sid='allow_agency' and effect='Allow', and Principal
417+
contains at least one ID where the part after the last '/' equals 'PCAAccessPrivateOBS',
418+
and Action equals 'PutObject'
419+
420+
You can also specify a domain_id to check if the Principal and NotPrincipal IDs
421+
contain this domain_id.
422+
423+
:example:
424+
425+
.. code-block:: yaml
426+
427+
policies:
428+
- name: find-cas-with-improper-obs-bucket-policy
429+
resource: huaweicloud.ccm-private-ca
430+
filters:
431+
- type: obs-bucket-policy
432+
domain_id: xxxxxx
433+
"""
434+
schema = type_schema(
435+
'obs-bucket-policy',
436+
domain_id={'type': 'string'}
437+
)
438+
439+
def process(self, resources, event=None):
440+
session = local_session(self.manager.session_factory)
441+
obs_client = session.client('obs')
442+
443+
# Get domain_id from filter parameters
444+
domain_id = self.data.get('domain_id')
445+
446+
results = []
447+
448+
for resource in resources:
449+
# Check if CRL configuration exists and has OBS bucket name
450+
crl_config = resource.get('crl_configuration', {})
451+
if not crl_config:
452+
self.log.debug(
453+
f"CA {resource.get('name')} has no CRL configuration")
454+
results.append(resource)
455+
continue
456+
457+
# Get OBS bucket name
458+
obs_bucket_name = crl_config.get('obs_bucket_name')
459+
if not obs_bucket_name:
460+
self.log.debug(
461+
f"CA {resource.get('name')} has no OBS bucket specified")
462+
results.append(resource)
463+
continue
464+
465+
try:
466+
# Call getBucketPolicy
467+
response = obs_client.getBucketPolicy(obs_bucket_name)
468+
469+
# Check if bucket policy exists
470+
if response.status < 300 and hasattr(response, 'body'):
471+
policy_json = response.body.policyJSON
472+
if not policy_json:
473+
self.log.debug(
474+
f"OBS bucket {obs_bucket_name} has no policy")
475+
resource['obs_bucket_policy_check'] = "No policy found"
476+
results.append(resource)
477+
continue
478+
479+
# Parse policy JSON
480+
import json
481+
try:
482+
policy = json.loads(policy_json)
483+
resource['obs_bucket_policy'] = policy
484+
485+
# Validate policy statements
486+
if not self.validate_policy_statements(
487+
policy, resource, obs_bucket_name, domain_id):
488+
results.append(resource)
489+
except json.JSONDecodeError:
490+
self.log.error(
491+
f"Failed to parse policy JSON for bucket {obs_bucket_name}")
492+
resource['obs_bucket_policy_check'] = "Invalid policy JSON format"
493+
results.append(resource)
494+
elif response.status == 403:
495+
error_obj = ObsSdkError(
496+
"PermissionDenied,Please confirm that",
497+
"you have 'obs:bucket:GetBucketPolicy' permission",
498+
""
499+
)
500+
raise exceptions.ClientRequestException(
501+
response.status, error_obj)
502+
else:
503+
error_obj = ObsSdkError(
504+
"RequestFailed",
505+
f"Request failed, status code: {response.status}",
506+
""
507+
)
508+
resource['obs_bucket_policy_check'] = (
509+
f"Failed to get policy: Status {response.status}")
510+
raise exceptions.ClientRequestException(
511+
response.status, error_obj)
512+
except exceptions.ClientRequestException as e:
513+
self.log.error(
514+
f"Failed to get bucket policy for {obs_bucket_name}: {e.error_msg}")
515+
resource['obs_bucket_policy_check'] = f"Error: {e.error_msg}"
516+
results.append(resource)
517+
if e.status_code == 403:
518+
raise e
519+
else:
520+
continue
521+
522+
return results
523+
524+
def validate_policy_statements(self, policy, resource, obs_bucket_name, domain_id=None):
525+
"""Validate if policy statements meet required criteria"""
526+
statements = policy.get('Statement', [])
527+
528+
# Initialize flags for conditions
529+
has_deny_except_agency = False
530+
has_allow_agency = False
531+
532+
for statement in statements:
533+
sid = statement.get('Sid', '')
534+
effect = statement.get('Effect', '')
535+
action = statement.get('Action', '')
536+
537+
# Convert action to list if it's a string
538+
if isinstance(action, str):
539+
action = [action]
540+
541+
# Check for deny_except_agency condition
542+
if sid == 'deny_except_agency' and effect == 'Deny' and 'PutObject' in action:
543+
obs_resources = statement.get('Resource', [])
544+
obs_resources_valid = False
545+
for obs_resource in obs_resources:
546+
if obs_bucket_name in obs_resource:
547+
obs_resources_valid = True
548+
break
549+
if obs_resources_valid:
550+
not_principal = statement.get('NotPrincipal', {})
551+
not_principal_ids = not_principal.get('ID', [])
552+
553+
# Convert to list if it's a string
554+
if isinstance(not_principal_ids, str):
555+
not_principal_ids = [not_principal_ids]
556+
domain_id_match_str = 'domain/' + domain_id + ':agency/PCAAccessPrivateOBS'
557+
558+
for principal_id in not_principal_ids:
559+
if domain_id_match_str == principal_id:
560+
has_deny_except_agency = True
561+
break
562+
563+
# Check for allow_agency condition
564+
if sid == 'allow_agency' and effect == 'Allow' and 'PutObject' in action:
565+
obs_resources = statement.get('Resource', [])
566+
obs_resources_valid = False
567+
for obs_resource in obs_resources:
568+
if obs_bucket_name in obs_resource:
569+
obs_resources_valid = True
570+
break
571+
if obs_resources_valid:
572+
principal = statement.get('Principal', {})
573+
principal_ids = principal.get('ID', [])
574+
575+
# Convert to list if it's a string
576+
if isinstance(principal_ids, str):
577+
principal_ids = [principal_ids]
578+
domain_id_match_str = 'domain/' + domain_id + ':agency/PCAAccessPrivateOBS'
579+
for principal_id in principal_ids:
580+
if domain_id_match_str == principal_id:
581+
has_allow_agency = True
582+
break
583+
584+
# Record the check results in the resource
585+
resource['obs_bucket_policy_check'] = {
586+
'has_deny_except_agency': has_deny_except_agency,
587+
'has_allow_agency': has_allow_agency,
588+
'is_valid': has_deny_except_agency and has_allow_agency,
589+
'domain_id_checked': domain_id is not None
590+
}
591+
592+
# Return True if both conditions are met, False otherwise
593+
return has_deny_except_agency and has_allow_agency
594+
595+
380596
@CertificateAuthority.action_registry.register('disable')
381597
class DisableCertificateAuthority(HuaweiCloudBaseAction):
382598
"""Disable Certificate Authority
@@ -484,9 +700,8 @@ def augment(self, resources):
484700
# Convert response tags to standard dict format
485701
tags = []
486702
for tag in response.tags:
487-
if hasattr(tag, 'key') and hasattr(tag, 'value'):
488-
tags.append(
489-
{'key': tag.key, 'value': tag.value})
703+
tags.append(
704+
{'key': tag.key, 'value': tag.value})
490705
resource['tags'] = tags
491706
else:
492707
resource['tags'] = []

tools/c7n_huaweicloud/c7n_huaweicloud/resources/scm.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from huaweicloudsdkscm.v3 import (
77
# Certificate management related
88
DeleteCertificateRequest,
9+
ListTagsByCertificateRequest,
910
)
1011

11-
from c7n.utils import type_schema
12+
from c7n.utils import type_schema, local_session
1213
from c7n_huaweicloud.provider import resources
1314
from c7n_huaweicloud.query import QueryResourceManager, TypeInfo
1415
from c7n_huaweicloud.actions.base import HuaweiCloudBaseAction
@@ -21,7 +22,7 @@ class Scm(QueryResourceManager):
2122

2223
class resource_type(TypeInfo):
2324
service = 'ccm-ssl-certificate'
24-
enum_spec = ('list_certificates', 'certificates', None)
25+
enum_spec = ('list_certificates', 'certificates', 'offset', 50)
2526
id = 'id'
2627
name = 'name'
2728
filter_name = 'name'
@@ -30,6 +31,47 @@ class resource_type(TypeInfo):
3031
# Set tag resource type for TMS operations
3132
tag_resource_type = 'scm'
3233

34+
def augment(self, resources):
35+
# Query tags for each certificate and add them to the resource properties
36+
session = local_session(self.session_factory)
37+
client = session.client('ccm-ssl-certificate')
38+
39+
for resource in resources:
40+
try:
41+
# Get certificate ID
42+
resource_id = resource.get('id')
43+
if not resource_id:
44+
continue
45+
46+
# Use ListTagsByCertificateRequest to query tags for this certificate
47+
request = ListTagsByCertificateRequest()
48+
request.resource_id = resource_id
49+
50+
try:
51+
# Call the API to get tags
52+
response = client.list_tags_by_certificate(request)
53+
54+
# Check if tags are available in the response
55+
if hasattr(response, 'tags') and response.tags is not None:
56+
# Convert response tags to standard dict format
57+
tags = []
58+
for tag in response.tags:
59+
tags.append(
60+
{'key': tag.key, 'value': tag.value})
61+
resource['tags'] = tags
62+
else:
63+
resource['tags'] = []
64+
except exceptions.ClientRequestException as e:
65+
log.warning(
66+
f"Failed to retrieve tags for certificate {resource_id}: {e.error_msg}")
67+
resource['tags'] = []
68+
except Exception as e:
69+
log.error(
70+
f"Error retrieving tags for certificate {resource.get('id')}: {str(e)}")
71+
resource['tags'] = []
72+
73+
return resources
74+
3375

3476
@Scm.action_registry.register('delete')
3577
class DeleteCertificateAction(HuaweiCloudBaseAction):

0 commit comments

Comments
 (0)