|
1 | 1 | #!/bin/bash |
2 | | -# Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved. |
| 2 | +# Copyright (c) 2021-2026, NVIDIA CORPORATION. All rights reserved. |
3 | 3 | # |
4 | 4 | # Redistribution and use in source and binary forms, with or without |
5 | 5 | # modification, are permitted provided that the following conditions |
@@ -612,6 +612,196 @@ set -e |
612 | 612 | kill $SERVER_PID |
613 | 613 | wait $SERVE_PID |
614 | 614 |
|
| 615 | +### Restricted API regression for SageMaker endpoint ### |
| 616 | +# Verify that --http-restricted-api applies to SageMaker model management |
| 617 | +# endpoints (load, unload, list, get) while leaving health and inference |
| 618 | +# unrestricted. |
| 619 | + |
| 620 | +SERVER_LOG="./sagemaker_restricted_api_server.log" |
| 621 | +SERVER_ARGS="--allow-sagemaker=true --allow-http=true \ |
| 622 | + --allow-grpc=false --allow-metrics=false \ |
| 623 | + --model-repository=`pwd`/models \ |
| 624 | + --model-control-mode=explicit \ |
| 625 | + --load-model=sm_model \ |
| 626 | + --http-restricted-api=model-repository:X-SM-Auth=secret" |
| 627 | +run_server_nowait |
| 628 | +sagemaker_wait_for_server_ready $SERVER_PID 10 |
| 629 | +if [ "$WAIT_RET" != "0" ]; then |
| 630 | + echo -e "\n***\n*** Failed to start $SERVER\n***" |
| 631 | + kill $SERVER_PID || true |
| 632 | + cat $SERVER_LOG |
| 633 | + exit 1 |
| 634 | +fi |
| 635 | + |
| 636 | +set +e |
| 637 | + |
| 638 | +# Health should succeed without restricted header |
| 639 | +rm -f ./curl.out |
| 640 | +code=`curl -s -w %{http_code} -o ./curl.out localhost:8080/ping` |
| 641 | +if [ "$code" != "200" ]; then |
| 642 | + cat ./curl.out |
| 643 | + echo -e "\n***\n*** Failed. Expected /ping to succeed without restricted header\n***" |
| 644 | + RET=1 |
| 645 | +fi |
| 646 | + |
| 647 | +# Inference should succeed without restricted header |
| 648 | +rm -f ./curl.out |
| 649 | +code=`curl -s -w %{http_code} -o ./curl.out -X POST localhost:8080/invocations \ |
| 650 | + -H "Content-Type: application/json" \ |
| 651 | + -d '{"inputs":[{"name":"INPUT0","datatype":"INT32","shape":[1,16],"data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},{"name":"INPUT1","datatype":"INT32","shape":[1,16],"data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}]}'` |
| 652 | +if [ "$code" != "200" ]; then |
| 653 | + cat ./curl.out |
| 654 | + echo -e "\n***\n*** Failed. Expected /invocations inference to succeed without restricted header\n***" |
| 655 | + RET=1 |
| 656 | +fi |
| 657 | + |
| 658 | +# List models without auth header should be blocked |
| 659 | +rm -f ./curl.out |
| 660 | +code=`curl -s -w %{http_code} -o ./curl.out localhost:8080/models` |
| 661 | +if [ "$code" != "403" ]; then |
| 662 | + cat ./curl.out |
| 663 | + echo -e "\n***\n*** Failed. Expected GET /models to return 403 without restricted header (got $code)\n***" |
| 664 | + RET=1 |
| 665 | +else |
| 666 | + grep "This API is restricted" ./curl.out |
| 667 | + if [ $? -ne 0 ]; then |
| 668 | + cat ./curl.out |
| 669 | + echo -e "\n***\n*** Failed. Expected restriction error message in response body\n***" |
| 670 | + RET=1 |
| 671 | + fi |
| 672 | +fi |
| 673 | + |
| 674 | +# Get model without auth header should be blocked |
| 675 | +rm -f ./curl.out |
| 676 | +code=`curl -s -w %{http_code} -o ./curl.out localhost:8080/models/sm_model` |
| 677 | +if [ "$code" != "403" ]; then |
| 678 | + cat ./curl.out |
| 679 | + echo -e "\n***\n*** Failed. Expected GET /models/<name> to return 403 without restricted header (got $code)\n***" |
| 680 | + RET=1 |
| 681 | +fi |
| 682 | + |
| 683 | +# Load model without auth header should be blocked |
| 684 | +rm -f ./curl.out |
| 685 | +code=`curl -s -w %{http_code} -o ./curl.out -X POST localhost:8080/models \ |
| 686 | + -H "Content-Type: application/json" \ |
| 687 | + -d '{"model_name":"test","url":"/opt/ml/models/123/model"}'` |
| 688 | +if [ "$code" != "403" ]; then |
| 689 | + cat ./curl.out |
| 690 | + echo -e "\n***\n*** Failed. Expected POST /models (load) to return 403 without restricted header (got $code)\n***" |
| 691 | + RET=1 |
| 692 | +fi |
| 693 | + |
| 694 | +# Unload model without auth header should be blocked |
| 695 | +rm -f ./curl.out |
| 696 | +code=`curl -s -w %{http_code} -o ./curl.out -X DELETE localhost:8080/models/sm_model` |
| 697 | +if [ "$code" != "403" ]; then |
| 698 | + cat ./curl.out |
| 699 | + echo -e "\n***\n*** Failed. Expected DELETE /models (unload) to return 403 without restricted header (got $code)\n***" |
| 700 | + RET=1 |
| 701 | +fi |
| 702 | + |
| 703 | +# List models WITH correct auth header should succeed |
| 704 | +rm -f ./curl.out |
| 705 | +code=`curl -s -w %{http_code} -o ./curl.out -H "X-SM-Auth: secret" localhost:8080/models` |
| 706 | +if [ "$code" == "403" ]; then |
| 707 | + cat ./curl.out |
| 708 | + echo -e "\n***\n*** Failed. Expected GET /models with auth header to pass restriction check\n***" |
| 709 | + RET=1 |
| 710 | +fi |
| 711 | + |
| 712 | +# Get model WITH correct auth header should pass restriction check |
| 713 | +rm -f ./curl.out |
| 714 | +code=`curl -s -w %{http_code} -o ./curl.out -H "X-SM-Auth: secret" localhost:8080/models/sm_model` |
| 715 | +if [ "$code" == "403" ]; then |
| 716 | + cat ./curl.out |
| 717 | + echo -e "\n***\n*** Failed. Expected GET /models/<name> with auth header to pass restriction check\n***" |
| 718 | + RET=1 |
| 719 | +fi |
| 720 | + |
| 721 | +# Wrong auth header value should be rejected |
| 722 | +rm -f ./curl.out |
| 723 | +code=`curl -s -w %{http_code} -o ./curl.out -H "X-SM-Auth: wrong" localhost:8080/models` |
| 724 | +if [ "$code" != "403" ]; then |
| 725 | + cat ./curl.out |
| 726 | + echo -e "\n***\n*** Failed. Expected wrong auth header value to return 403 (got $code)\n***" |
| 727 | + RET=1 |
| 728 | +fi |
| 729 | + |
| 730 | +# Verify core HTTP endpoint is also restricted by the same flag |
| 731 | +rm -f ./curl.out |
| 732 | +code=`curl -s -w %{http_code} -o ./curl.out -X POST localhost:8000/v2/repository/index` |
| 733 | +if [ "$code" != "403" ]; then |
| 734 | + cat ./curl.out |
| 735 | + echo -e "\n***\n*** Failed. Expected core HTTP repository index to be restricted (got $code)\n***" |
| 736 | + RET=1 |
| 737 | +fi |
| 738 | + |
| 739 | +set -e |
| 740 | + |
| 741 | +kill $SERVER_PID |
| 742 | +wait $SERVER_PID |
| 743 | + |
| 744 | +### HTTP max input size enforcement on SageMaker endpoint ### |
| 745 | +# Verify that --http-max-input-size is enforced on the SageMaker /invocations |
| 746 | +# path, not just the core HTTP endpoint. |
| 747 | + |
| 748 | +SERVER_LOG="./sagemaker_max_input_size_server.log" |
| 749 | +SERVER_ARGS="--allow-sagemaker=true --allow-http=true \ |
| 750 | + --allow-grpc=false --allow-metrics=false \ |
| 751 | + --model-repository=`pwd`/models \ |
| 752 | + --model-control-mode=explicit \ |
| 753 | + --load-model=sm_model \ |
| 754 | + --http-max-input-size=256" |
| 755 | +run_server_nowait |
| 756 | +sagemaker_wait_for_server_ready $SERVER_PID 10 |
| 757 | +if [ "$WAIT_RET" != "0" ]; then |
| 758 | + echo -e "\n***\n*** Failed to start $SERVER\n***" |
| 759 | + kill $SERVER_PID || true |
| 760 | + cat $SERVER_LOG |
| 761 | + exit 1 |
| 762 | +fi |
| 763 | + |
| 764 | +set +e |
| 765 | + |
| 766 | +# Small payload under 256 bytes should succeed |
| 767 | +rm -f ./curl.out |
| 768 | +code=`curl -s -w %{http_code} -o ./curl.out -X POST localhost:8080/invocations \ |
| 769 | + -H "Content-Type: application/json" \ |
| 770 | + -d '{"inputs":[{"name":"INPUT0","datatype":"INT32","shape":[1,1],"data":[1]},{"name":"INPUT1","datatype":"INT32","shape":[1,1],"data":[1]}]}'` |
| 771 | +if [ "$code" != "200" ]; then |
| 772 | + cat ./curl.out |
| 773 | + echo -e "\n***\n*** Failed. Expected small payload to succeed on SageMaker endpoint (got $code)\n***" |
| 774 | + RET=1 |
| 775 | +fi |
| 776 | + |
| 777 | +# Large payload over 256 bytes should be rejected |
| 778 | +rm -f ./curl.out |
| 779 | +LARGE_PAYLOAD='{"inputs":[{"name":"INPUT0","datatype":"INT32","shape":[1,16],"data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]},{"name":"INPUT1","datatype":"INT32","shape":[1,16],"data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}]}' |
| 780 | +code=`curl -s -w %{http_code} -o ./curl.out -X POST localhost:8080/invocations \ |
| 781 | + -H "Content-Type: application/json" \ |
| 782 | + -d "$LARGE_PAYLOAD"` |
| 783 | +if [ "$code" == "200" ]; then |
| 784 | + cat ./curl.out |
| 785 | + echo -e "\n***\n*** Failed. Expected oversized payload to be rejected on SageMaker endpoint\n***" |
| 786 | + RET=1 |
| 787 | +fi |
| 788 | + |
| 789 | +# Same limit should apply to core HTTP endpoint |
| 790 | +rm -f ./curl.out |
| 791 | +code=`curl -s -w %{http_code} -o ./curl.out -X POST localhost:8000/v2/models/sm_model/infer \ |
| 792 | + -H "Content-Type: application/json" \ |
| 793 | + -d "$LARGE_PAYLOAD"` |
| 794 | +if [ "$code" == "200" ]; then |
| 795 | + cat ./curl.out |
| 796 | + echo -e "\n***\n*** Failed. Expected oversized payload to be rejected on core HTTP endpoint\n***" |
| 797 | + RET=1 |
| 798 | +fi |
| 799 | + |
| 800 | +set -e |
| 801 | + |
| 802 | +kill $SERVER_PID |
| 803 | +wait $SERVER_PID |
| 804 | + |
615 | 805 | unlink /opt/ml/model |
616 | 806 | rm -rf /opt/ml/model |
617 | 807 |
|
|
0 commit comments