@@ -472,19 +472,18 @@ def test_enable_lake_formation_fails_with_nonexistent_role(
472472
473473@pytest .mark .serial
474474@pytest .mark .slow_test
475- def test_enable_lake_formation_full_flow_with_policy_output (s3_uri , role , region , caplog ):
475+ def test_enable_lake_formation_full_flow_with_policy_output (s3_uri , role , region ):
476476 """
477- Test the full Lake Formation flow with S3 deny policy output .
477+ Test the full Lake Formation flow with S3 deny policy application .
478478
479479 This test verifies:
480480 1. Creates a FeatureGroupManager with offline store
481- 2. Enables Lake Formation with show_s3_policy=True
482- 3. Verifies all Lake Formation phases complete successfully
483- 4. Verifies the S3 deny policy is logged
484- 5. Verifies the policy structure contains expected elements
485-
486- This validates Requirements 6.1-6.9 from the design document.
481+ 2. Enables Lake Formation (bucket policy is applied automatically)
482+ 3. Verifies all Lake Formation phases complete successfully (including bucket policy)
483+ 4. Fetches the actual bucket policy and verifies its structure
487484 """
485+ import json
486+
488487 fg_name = generate_feature_group_name ()
489488 fg = None
490489
@@ -497,48 +496,25 @@ def test_enable_lake_formation_full_flow_with_policy_output(s3_uri, role, region
497496 fg .wait_for_status (target_status = "Created" , poll = 30 , timeout = 300 )
498497 assert fg .feature_group_status == "Created"
499498
500- # Enable Lake Formation governance with policy output
501- with caplog .at_level (logging .INFO , logger = "sagemaker.mlops.feature_store.feature_group_manager" ):
502- result = fg .enable_lake_formation (show_s3_policy = True )
499+ # Enable Lake Formation governance
500+ result = fg .enable_lake_formation ()
503501
504502 # Verify all phases completed successfully
505503 assert result ["s3_registration" ] is True
506504 assert result ["permissions_granted" ] is True
507505 assert result ["iam_principal_revoked" ] is True
506+ assert result ["bucket_policy_applied" ] is True
508507
509- output = caplog .text
510-
511- # Verify the policy header is logged
512- assert "S3 Bucket Policy Update recommended" in output
513- # Verify bucket information is logged
514- # Extract bucket name from s3_uri (s3://bucket/path -> bucket)
515- expected_bucket = s3_uri .replace ("s3://" , "" ).split ("/" )[0 ]
516- assert f"Bucket: { expected_bucket } " in output
517-
518- # Verify policy structure elements are present
519- assert '"Version": "2012-10-17"' in output
520- assert '"Statement"' in output
521- assert '"Effect": "Deny"' in output
522- assert '"Principal": "*"' in output
523-
524- # Verify the deny actions are present
525- assert "s3:GetObject" in output
526- assert "s3:PutObject" in output
527- assert "s3:DeleteObject" in output
528- assert "s3:ListBucket" in output
529-
530- # Verify the condition structure is present
531- assert "StringNotEquals" in output
532- assert "aws:PrincipalArn" in output
533-
534- # Verify the role ARN is in the allowed principals
535- assert role in output
536-
537- # Verify the service-linked role pattern is present (default use_service_linked_role=True)
538- assert "aws-service-role/lakeformation.amazonaws.com/AWSServiceRoleForLakeFormationDataAccess" in output
508+ # Fetch the actual bucket policy from S3 and verify the role is allowed
509+ bucket_name = s3_uri .replace ("s3://" , "" ).split ("/" )[0 ]
510+ s3_client = boto3 .client ("s3" )
511+ policy = json .loads (s3_client .get_bucket_policy (Bucket = bucket_name )["Policy" ])
539512
540- # Verify instructions are logged
541- assert "Merge this with your existing bucket policy" in output
513+ # Find a DenyFS statement and verify the execution role is in allowed principals
514+ deny_stmts = [s for s in policy ["Statement" ] if s .get ("Sid" , "" ).startswith ("DenyFS" )]
515+ assert len (deny_stmts ) >= 1
516+ allowed = deny_stmts [0 ]["Condition" ]["StringNotEquals" ]["aws:PrincipalArn" ]
517+ assert role in allowed
542518
543519 finally :
544520 # Cleanup
@@ -548,17 +524,14 @@ def test_enable_lake_formation_full_flow_with_policy_output(s3_uri, role, region
548524
549525@pytest .mark .serial
550526@pytest .mark .slow_test
551- def test_enable_lake_formation_no_policy_output_by_default (s3_uri , role , region , caplog ):
527+ def test_enable_lake_formation_default_applies_bucket_policy (s3_uri , role , region , caplog ):
552528 """
553- Test that S3 deny policy is NOT logged when show_s3_policy=False ( default) .
529+ Test that bucket policy is applied automatically with default arguments .
554530
555531 This test verifies:
556532 1. Creates a FeatureGroupManager with offline store
557- 2. Enables Lake Formation without show_s3_policy (defaults to False)
558- 3. Verifies all Lake Formation phases complete successfully
559- 4. Verifies the S3 deny policy is NOT logged
560-
561- This validates Requirement 6.2 from the design document.
533+ 2. Enables Lake Formation with default arguments
534+ 3. Verifies all four phases complete successfully (including bucket policy)
562535 """
563536 fg_name = generate_feature_group_name ()
564537 fg = None
@@ -572,21 +545,15 @@ def test_enable_lake_formation_no_policy_output_by_default(s3_uri, role, region,
572545 fg .wait_for_status (target_status = "Created" , poll = 30 , timeout = 300 )
573546 assert fg .feature_group_status == "Created"
574547
575- # Enable Lake Formation governance WITHOUT policy output (default)
548+ # Enable Lake Formation governance with defaults
576549 with caplog .at_level (logging .INFO , logger = "sagemaker.mlops.feature_store.feature_group_manager" ):
577550 result = fg .enable_lake_formation ()
578551
579552 # Verify all phases completed successfully
580553 assert result ["s3_registration" ] is True
581554 assert result ["permissions_granted" ] is True
582555 assert result ["iam_principal_revoked" ] is True
583-
584- output = caplog .text
585-
586- # Verify the policy is NOT logged
587- assert "S3 Bucket Policy Update recommended" not in output
588- assert '"Version": "2012-10-17"' not in output
589- assert "s3:GetObject" not in output
556+ assert result ["bucket_policy_applied" ] is True
590557
591558 finally :
592559 # Cleanup
@@ -596,20 +563,17 @@ def test_enable_lake_formation_no_policy_output_by_default(s3_uri, role, region,
596563
597564@pytest .mark .serial
598565@pytest .mark .slow_test
599- def test_enable_lake_formation_with_custom_role_policy_output (s3_uri , role , region , caplog ):
566+ def test_enable_lake_formation_with_custom_role_policy_output (s3_uri , role , region ):
600567 """
601- Test the full Lake Formation flow with custom registration role and policy output .
568+ Test the full Lake Formation flow with custom registration role.
602569
603570 This test verifies:
604571 1. Creates a FeatureGroupManager with offline store
605572 2. Enables Lake Formation with use_service_linked_role=False and a custom registration_role_arn
606- 3. Verifies the S3 deny policy uses the custom role ARN instead of service-linked role
607-
608- This validates Requirements 6.4, 6.5 from the design document.
609-
610- Note: This test uses the same execution role as the registration role for simplicity.
611- In production, these would typically be different roles.
573+ 3. Fetches the actual bucket policy and verifies the custom role ARN is in the allowed principals
612574 """
575+ import json
576+
613577 fg_name = generate_feature_group_name ()
614578 fg = None
615579
@@ -622,31 +586,36 @@ def test_enable_lake_formation_with_custom_role_policy_output(s3_uri, role, regi
622586 fg .wait_for_status (target_status = "Created" , poll = 30 , timeout = 300 )
623587 assert fg .feature_group_status == "Created"
624588
625- # Enable Lake Formation with custom registration role and policy output
626- # Using the same role for both execution and registration for test simplicity
627- with caplog .at_level (logging .INFO , logger = "sagemaker.mlops.feature_store.feature_group_manager" ):
628- result = fg .enable_lake_formation (
629- use_service_linked_role = False ,
630- registration_role_arn = role ,
631- show_s3_policy = True ,
632- )
589+ # Enable Lake Formation with custom registration role
590+ result = fg .enable_lake_formation (
591+ use_service_linked_role = False ,
592+ registration_role_arn = role ,
593+ )
633594
634595 # Verify all phases completed successfully
635596 assert result ["s3_registration" ] is True
636597 assert result ["permissions_granted" ] is True
637598 assert result ["iam_principal_revoked" ] is True
599+ assert result ["bucket_policy_applied" ] is True
638600
639- output = caplog .text
601+ # Fetch the actual bucket policy from S3 and verify the role is in allowed principals
602+ bucket_name = s3_uri .replace ("s3://" , "" ).split ("/" )[0 ]
603+ s3_client = boto3 .client ("s3" )
604+ policy = json .loads (s3_client .get_bucket_policy (Bucket = bucket_name )["Policy" ])
640605
641- # Verify the policy header is logged
642- assert "S3 Bucket Policy Update recommended" in output
606+ # Find the deny statements for this feature group's prefix
607+ fg .refresh ()
608+ s3_resolved = fg .offline_store_config .s3_storage_config .resolved_output_s3_uri
609+ fg_prefix = s3_resolved .replace (f"s3://{ bucket_name } /" , "" ).rstrip ("/" )
610+ sid_suffix = fg_prefix .rsplit ("/" , 1 )[- 1 ]
643611
644- # Verify the custom role ARN is used in the policy (appears twice - once for each principal)
645- # The role should appear as both the Lake Formation role and the Feature Store role
646- assert output . count ( role ) >= 2
612+ object_sid = f"DenyFSObjectAccess_ { sid_suffix } "
613+ deny_stmts = [ s for s in policy [ "Statement" ] if s . get ( "Sid" ) == object_sid ]
614+ assert len ( deny_stmts ) == 1
647615
648- # Verify the service-linked role is NOT used
649- assert "aws-service-role/lakeformation.amazonaws.com/AWSServiceRoleForLakeFormationDataAccess" not in output
616+ # Verify the custom role ARN is in the allowed principals
617+ allowed = deny_stmts [0 ]["Condition" ]["StringNotEquals" ]["aws:PrincipalArn" ]
618+ assert role in allowed
650619
651620 finally :
652621 # Cleanup
0 commit comments