本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
測試 AWS CloudFormation Guard 規則
您可以使用 AWS CloudFormation Guard 內建單元測試架構來驗證 Guard 規則是否如預期般運作。本節提供如何撰寫單元測試檔案,以及如何使用 test命令來測試規則檔案的逐步解說。
您的單元測試檔案必須具有下列其中一個副檔名:.json、.JSON、.jsn.yaml、.YAML、 或 .yml。
先決條件
撰寫 Guard 規則來評估您的輸入資料。如需詳細資訊,請參閱撰寫 Guard 規則。
Guard 單位測試檔案概觀
Guard 單位測試檔案是 JSON 或 YAML 格式的檔案,其中包含多個輸入,以及在 Guard 規則檔案中寫入規則的預期結果。可以有多個範例來評估不同的期望。我們建議您先測試空白輸入,然後逐步新增評估各種規則和子句的資訊。
此外,我們建議您使用尾碼 _test.json或 命名單元測試檔案_tests.yaml。例如,如果您有一個名為 的規則檔案my_rules.guard,請命名您的單元測試檔案 my_rules_tests.yaml。
語法
以下顯示 YAML 格式的單位測試檔案語法。
--- - name: <TEST NAME> input: <SAMPLE INPUT> expectations: rules: <RULE NAME>: [PASS|FAIL|SKIP]
屬性
以下是 Guard 測試檔案的屬性。
input-
要測試規則的資料。我們建議您的第一個測試使用空白輸入,如下列範例所示。
--- - name: MyTest1 input {}針對後續測試,新增要測試的輸入資料。
必要:是
expectations-
根據您的輸入資料評估特定規則時的預期結果。除了每個規則的預期結果之外,指定您要測試的一或多個規則。預期結果必須是下列其中一項:
-
PASS– 當 對您的輸入資料執行時,規則會評估為true。 -
FAIL– 當 對您的輸入資料執行時,規則會評估為false。 -
SKIP– 對輸入資料執行 時,不會觸發規則。
expectations: rules: check_rest_api_is_private: PASS必要:是
-
撰寫 Guard 規則單位測試檔案的演練
以下是名為 的規則檔案api_gateway_private.guard。此規則的目的是檢查 CloudFormation 範本中定義的所有 Amazon API Gateway 資源類型是否僅部署為私有存取。它也會檢查至少一個政策陳述式是否允許從虛擬私有雲端 (VPC) 存取。
# # Select allAWS::ApiGateway::RestApiresources # present in theResourcessection of the template. # let api_gws = Resources.*[ Type == 'AWS::ApiGateway::RestApi'] # # Rule intent: # 1) AllAWS::ApiGateway::RestApiresources deployed must be private. # 2) AllAWS::ApiGateway::RestApiresources deployed must have at least one AWS Identity and Access Management (IAM) policy condition key to allow access from a VPC. # # Expectations: # 1) SKIP when there are noAWS::ApiGateway::RestApiresources in the template. # 2) PASS when: # ALLAWS::ApiGateway::RestApiresources in the template have theEndpointConfigurationproperty set toType:PRIVATE. # ALLAWS::ApiGateway::RestApiresources in the template have one IAM condition key specified in thePolicyproperty withaws:sourceVpcor:SourceVpc. # 3) FAIL otherwise. # # rule check_rest_api_is_private when %api_gws !empty { %api_gws { Properties.EndpointConfiguration.Types[*] == "PRIVATE" } } rule check_rest_api_has_vpc_access when check_rest_api_is_private { %api_gws { Properties { # # ALLAWS::ApiGateway::RestApiresources in the template have one IAM condition key specified in thePolicyproperty with #aws:sourceVpcor:SourceVpc# some Policy.Statement[*] { Condition.*[ keys == /aws:[sS]ource(Vpc|VPC|Vpce|VPCE)/ ] !empty } } } }
此演練會測試第一個規則意圖:所有部署AWS::ApiGateway::RestApi的資源都必須是私有的。
-
建立名為 的單元測試檔案
api_gateway_private_tests.yaml,其中包含下列初始測試。在初始測試中,新增空白輸入,並預期規則check_rest_api_is_private會略過,因為沒有AWS::ApiGateway::RestApi資源做為輸入。--- - name: MyTest1 input: {} expectations: rules: check_rest_api_is_private: SKIP -
使用
test命令在終端機中執行第一個測試。針對--rules-file參數,指定您的規則檔案。針對--test-data參數,指定您的單位測試檔案。cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml第一個測試的結果是
PASS。Test Case #1 Name: "MyTest1" PASS Rules: check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP -
將另一個測試新增至您的單元測試檔案。現在,擴展測試以包含空白資源。以下是更新的
api_gateway_private_tests.yaml檔案。--- - name: MyTest1 input: {} expectations: rules: check_rest_api_is_private: SKIP - name: MyTest2 input: Resources: {} expectations: rules: check_rest_api_is_private: SKIP -
test使用更新的單元測試檔案執行 。cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml第二個測試的結果是
PASS。Test Case #1 Name: "MyTest1" PASS Rules: check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP Test Case #2 Name: "MyTest2" PASS Rules: check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP -
將另外兩個測試新增至您的單元測試檔案。擴展測試以包含下列項目:
-
未指定屬性
AWS::ApiGateway::RestApi的資源。注意
這不是有效的 CloudFormation 範本,但即使輸入格式不正確,測試規則是否正常運作也很有用。
預期此測試會失敗,因為未指定
EndpointConfiguration屬性,因此未設定為PRIVATE。 -
一種
AWS::ApiGateway::RestApi資源,可滿足EndpointConfiguration屬性設定為 的第一個意圖,PRIVATE但無法滿足第二個意圖,因為它未定義政策陳述式。預期此測試將會通過。
以下是更新的單位測試檔案。
--- - name: MyTest1 input: {} expectations: rules: check_rest_api_is_private: SKIP - name: MyTest2 input: Resources: {} expectations: rules: check_rest_api_is_private: SKIP - name: MyTest3 input: Resources: apiGw: Type: AWS::ApiGateway::RestApi expectations: rules: check_rest_api_is_private: FAIL - name: MyTest4 input: Resources: apiGw: Type: AWS::ApiGateway::RestApi Properties: EndpointConfiguration: Types: "PRIVATE" expectations: rules: check_rest_api_is_private: PASS -
-
test使用更新的單元測試檔案執行 。cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml \第三個結果是
FAIL,第四個結果是PASS。Test Case #1 Name: "MyTest1" PASS Rules: check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP Test Case #2 Name: "MyTest2" PASS Rules: check_rest_api_is_private: Expected = SKIP, Evaluated = SKIP Test Case #3 Name: "MyTest3" PASS Rules: check_rest_api_is_private: Expected = FAIL, Evaluated = FAIL Test Case #4 Name: "MyTest4" PASS Rules: check_rest_api_is_private: Expected = PASS, Evaluated = PASS -
在您的單元測試檔案中註解測試 1–3。僅存取第四個測試的詳細內容。以下是更新的單位測試檔案。
--- #- name: MyTest1 # input: {} # expectations: # rules: # check_rest_api_is_private_and_has_access: SKIP #- name: MyTest2 # input: # Resources: {} # expectations: # rules: # check_rest_api_is_private_and_has_access: SKIP #- name: MyTest3 # input: # Resources: # apiGw: # Type: AWS::ApiGateway::RestApi # expectations: # rules: # check_rest_api_is_private_and_has_access: FAIL - name: MyTest4 input: Resources: apiGw: Type: AWS::ApiGateway::RestApi Properties: EndpointConfiguration: Types: "PRIVATE" expectations: rules: check_rest_api_is_private: PASS -
使用
--verbose旗標,在終端機中執行test命令來檢查評估結果。詳細內容有助於了解評估。在這種情況下,它會提供有關第四個測試成功產生PASS結果的原因的詳細資訊。cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml \ --verbose以下是該執行的輸出。
Test Case #1 Name: "MyTest4" PASS Rules: check_rest_api_is_private: Expected = PASS, Evaluated = PASS Rule(check_rest_api_is_private, PASS) | Message: DEFAULT MESSAGE(PASS) Condition(check_rest_api_is_private, PASS) | Message: DEFAULT MESSAGE(PASS) Clause(Clause(Location[file:api_gateway_private.guard, line:20, column:37], Check: %api_gws NOT EMPTY ), PASS) | From: Map((Path("/Resources/apiGw"), MapValue { keys: [String((Path("/Resources/apiGw/Type"), "Type")), String((Path("/Resources/apiGw/Properties"), "Properties"))], values: {"Type": String((Path("/Resources/apiGw/Type"), "AWS::ApiGateway::RestApi")), "Properties": Map((Path("/Resources/apiGw/Properties"), MapValue { keys: [String((Path("/Resources/apiGw/Properties/EndpointConfiguration"), "EndpointConfiguration"))], values: {"EndpointConfiguration": Map((Path("/Resources/apiGw/Properties/EndpointConfiguration"), MapValue { keys: [String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types"), "Types"))], values: {"Types": String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types"), "PRIVATE"))} }))} }))} })) | Message: (DEFAULT: NO_MESSAGE) Conjunction(cfn_guard::rules::exprs::GuardClause, PASS) | Message: DEFAULT MESSAGE(PASS) Clause(Clause(Location[file:api_gateway_private.guard, line:22, column:5], Check: Properties.EndpointConfiguration.Types[*] EQUALS String("PRIVATE")), PASS) | Message: (DEFAULT: NO_MESSAGE)輸出的關鍵觀察是行
Clause(Location[file:api_gateway_private.guard, line:22, column:5], Check: Properties.EndpointConfiguration.Types[*] EQUALS String("PRIVATE")), PASS),指出檢查通過。此範例也顯示Types預期為陣列,但提供單一值的情況。在這種情況下,Guard 會繼續評估並提供正確的結果。 -
將如第四個測試案例的測試案例新增至具有指定
EndpointConfiguration屬性之AWS::ApiGateway::RestApi資源的單元測試檔案。測試案例將會失敗,而不是通過。以下是更新的單位測試檔案。--- #- name: MyTest1 # input: {} # expectations: # rules: # check_rest_api_is_private_and_has_access: SKIP #- name: MyTest2 # input: # Resources: {} # expectations: # rules: # check_rest_api_is_private_and_has_access: SKIP #- name: MyTest3 # input: # Resources: # apiGw: # Type: AWS::ApiGateway::RestApi # expectations: # rules: # check_rest_api_is_private_and_has_access: FAIL #- name: MyTest4 # input: # Resources: # apiGw: # Type: AWS::ApiGateway::RestApi # Properties: # EndpointConfiguration: # Types: "PRIVATE" # expectations: # rules: # check_rest_api_is_private: PASS - name: MyTest5 input: Resources: apiGw: Type: AWS::ApiGateway::RestApi Properties: EndpointConfiguration: Types: [PRIVATE, REGIONAL] expectations: rules: check_rest_api_is_private: FAIL -
使用
--verbose旗標搭配更新的單元測試檔案執行test命令。cfn-guard test --rules-file api_gateway_private.guard --test-data api_gateway_private_tests.yaml \ --verbose結果會
FAIL如預期,因為REGIONAL是針對 指定,EndpointConfiguration但不預期。Test Case #1 Name: "MyTest5" PASS Rules: check_rest_api_is_private: Expected = FAIL, Evaluated = FAIL Rule(check_rest_api_is_private, FAIL) | Message: DEFAULT MESSAGE(FAIL) Condition(check_rest_api_is_private, PASS) | Message: DEFAULT MESSAGE(PASS) Clause(Clause(Location[file:api_gateway_private.guard, line:20, column:37], Check: %api_gws NOT EMPTY ), PASS) | From: Map((Path("/Resources/apiGw"), MapValue { keys: [String((Path("/Resources/apiGw/Type"), "Type")), String((Path("/Resources/apiGw/Properties"), "Properties"))], values: {"Type": String((Path("/Resources/apiGw/Type"), "AWS::ApiGateway::RestApi")), "Properties": Map((Path("/Resources/apiGw/Properties"), MapValue { keys: [String((Path("/Resources/apiGw/Properties/EndpointConfiguration"), "EndpointConfiguration"))], values: {"EndpointConfiguration": Map((Path("/Resources/apiGw/Properties/EndpointConfiguration"), MapValue { keys: [String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types"), "Types"))], values: {"Types": List((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types"), [String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types/0"), "PRIVATE")), String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types/1"), "REGIONAL"))]))} }))} }))} })) | Message: DEFAULT MESSAGE(PASS) BlockClause(Block[Location[file:api_gateway_private.guard, line:21, column:3]], FAIL) | Message: DEFAULT MESSAGE(FAIL) Conjunction(cfn_guard::rules::exprs::GuardClause, FAIL) | Message: DEFAULT MESSAGE(FAIL) Clause(Clause(Location[file:api_gateway_private.guard, line:22, column:5], Check: Properties.EndpointConfiguration.Types[*] EQUALS String("PRIVATE")), FAIL) | From: String((Path("/Resources/apiGw/Properties/EndpointConfiguration/Types/1"), "REGIONAL")) | To: String((Path("api_gateway_private.guard/22/5/Clause/"), "PRIVATE")) | Message: (DEFAULT: NO_MESSAGE)test命令的詳細輸出遵循規則檔案的結構。規則檔案中的每個區塊都是詳細輸出中的區塊。最上方的區塊是每個規則。如果有針對規則when的條件,這些條件會出現在同盟條件區塊中。在下列範例中,%api_gws !empty測試條件並通過。rule check_rest_api_is_private when %api_gws !empty {一旦條件通過,我們會測試規則子句。
%api_gws { Properties.EndpointConfiguration.Types[*] == "PRIVATE" }%api_gws是區塊規則,對應至輸出中的BlockClause層級 (行:21)。規則子句是一組結合 (AND) 子句,其中每個結合子句都是一組接合 (ORs)。結合具有單一子句Properties.EndpointConfiguration.Types[*] == "PRIVATE"。因此,詳細輸出會顯示單一子句。路徑/Resources/apiGw/Properties/EndpointConfiguration/Types/1會顯示要比較輸入中的哪些值,在此案例中是索引Types為 1 的 元素。
在 中根據 Guard 規則驗證輸入資料,您可以使用本節中的範例,使用 validate命令根據規則評估輸入資料。