@@ -517,6 +517,58 @@ def test_create_input_data_channel(mock_default_bucket, mock_upload_data, model_
517517 assert channel .data_source .s3_data_source .s3_uri == expected_s3_uri
518518
519519
520+ @patch ("sagemaker.train.model_trainer.Session.upload_data" )
521+ @patch ("sagemaker.train.model_trainer.Session.default_bucket" )
522+ def test_create_input_data_channel_encrypts_uploads_with_output_kms_key (
523+ mock_default_bucket , mock_upload_data
524+ ):
525+ """GH #5956: local source uploads are encrypted with output_data_config.kms_key_id."""
526+ mock_default_bucket .return_value = DEFAULT_BUCKET
527+ mock_upload_data .return_value = f"s3://{ DEFAULT_BUCKET } /code"
528+ trainer = ModelTrainer (
529+ training_image = DEFAULT_IMAGE ,
530+ role = DEFAULT_ROLE ,
531+ compute = DEFAULT_COMPUTE_CONFIG ,
532+ stopping_condition = DEFAULT_STOPPING_CONDITION ,
533+ output_data_config = OutputDataConfig (
534+ s3_output_path = f"s3://{ DEFAULT_BUCKET } /out" ,
535+ kms_key_id = "my-kms-key" ,
536+ ),
537+ )
538+ trainer .create_input_data_channel ("code" , DEFAULT_SOURCE_DIR )
539+ assert mock_upload_data .call_args .kwargs ["extra_args" ] == {
540+ "ServerSideEncryption" : "aws:kms" ,
541+ "SSEKMSKeyId" : "my-kms-key" ,
542+ }
543+
544+
545+ @patch ("sagemaker.train.model_trainer.Session.upload_data" )
546+ @patch ("sagemaker.train.model_trainer.Session.default_bucket" )
547+ def test_create_input_data_channel_explicit_kms_key_overrides (
548+ mock_default_bucket , mock_upload_data , model_trainer
549+ ):
550+ """An explicit kms_key argument takes precedence over output_data_config."""
551+ mock_default_bucket .return_value = DEFAULT_BUCKET
552+ mock_upload_data .return_value = f"s3://{ DEFAULT_BUCKET } /code"
553+ model_trainer .create_input_data_channel ("code" , DEFAULT_SOURCE_DIR , kms_key = "explicit-key" )
554+ assert mock_upload_data .call_args .kwargs ["extra_args" ] == {
555+ "ServerSideEncryption" : "aws:kms" ,
556+ "SSEKMSKeyId" : "explicit-key" ,
557+ }
558+
559+
560+ @patch ("sagemaker.train.model_trainer.Session.upload_data" )
561+ @patch ("sagemaker.train.model_trainer.Session.default_bucket" )
562+ def test_create_input_data_channel_no_kms_by_default (
563+ mock_default_bucket , mock_upload_data , model_trainer
564+ ):
565+ """Default (no kms_key_id) leaves uploads unencrypted -> extra_args None (unchanged)."""
566+ mock_default_bucket .return_value = DEFAULT_BUCKET
567+ mock_upload_data .return_value = f"s3://{ DEFAULT_BUCKET } /code"
568+ model_trainer .create_input_data_channel ("code" , DEFAULT_SOURCE_DIR )
569+ assert mock_upload_data .call_args .kwargs ["extra_args" ] is None
570+
571+
520572def test_create_input_data_channel_with_instance_group_names (model_trainer ):
521573 """instance_group_names is propagated onto the channel's S3DataSource."""
522574 channel = model_trainer .create_input_data_channel (
@@ -917,7 +969,7 @@ def test_remote_debug_config(mock_training_job, modules_session):
917969@patch ("sagemaker.train.model_trainer._get_unique_name" )
918970@patch ("sagemaker.train.model_trainer.TrainingJob" )
919971def test_model_trainer_full_init (mock_training_job , mock_unique_name , modules_session ):
920- def mock_upload_data (path , bucket , key_prefix ):
972+ def mock_upload_data (path , bucket , key_prefix , extra_args = None ):
921973 return f"s3://{ bucket } /{ key_prefix } "
922974
923975 modules_session .upload_data .side_effect = mock_upload_data
@@ -1195,7 +1247,7 @@ def mock_upload_data(path, bucket, key_prefix):
11951247# def test_model_trainer_local_full_init(
11961248# mock_download_folder, mock_unique_name, mock_local_container, modules_session
11971249# ):
1198- # def mock_upload_data(path, bucket, key_prefix):
1250+ # def mock_upload_data(path, bucket, key_prefix, extra_args=None ):
11991251# return f"s3://{bucket}/{key_prefix}"
12001252
12011253# modules_session.upload_data.side_effect = mock_upload_data
@@ -1449,7 +1501,7 @@ def test_hyperparameters_invalid(mock_exists, modules_session):
14491501@patch ("sagemaker.train.model_trainer._get_unique_name" )
14501502@patch ("sagemaker.train.model_trainer.TrainingJob" )
14511503def test_model_trainer_default_paths (mock_training_job , mock_unique_name , modules_session ):
1452- def mock_upload_data (path , bucket , key_prefix ):
1504+ def mock_upload_data (path , bucket , key_prefix , extra_args = None ):
14531505 return f"s3://{ bucket } /{ key_prefix } "
14541506
14551507 unique_name = "base-job-0123456789"
@@ -1553,7 +1605,7 @@ def test_metric_definitions(mock_training_job, modules_session):
15531605@patch ("sagemaker.train.model_trainer._get_unique_name" )
15541606@patch ("sagemaker.core.resources.TrainingJob" )
15551607def test_nova_recipe (mock_training_job , mock_unique_name , modules_session ):
1556- def mock_upload_data (path , bucket , key_prefix ):
1608+ def mock_upload_data (path , bucket , key_prefix , extra_args = None ):
15571609 if os .path .isfile (path ):
15581610 file_name = os .path .basename (path )
15591611 return f"s3://{ bucket } /{ key_prefix } /{ file_name } "
@@ -1825,7 +1877,7 @@ def test_nova_recipe_model_package_config_only_mpg_from_recipe(modules_session):
18251877@patch ("sagemaker.train.model_trainer._get_unique_name" )
18261878@patch ("sagemaker.train.model_trainer.TrainingJob" )
18271879def test_llmft_recipe (mock_training_job , mock_unique_name , modules_session ):
1828- def mock_upload_data (path , bucket , key_prefix ):
1880+ def mock_upload_data (path , bucket , key_prefix , extra_args = None ):
18291881 if os .path .isfile (path ):
18301882 file_name = os .path .basename (path )
18311883 return f"s3://{ bucket } /{ key_prefix } /{ file_name } "
0 commit comments