More test cases

This commit is contained in:
Fang-Pen Lin
2025-11-13 09:21:40 -08:00
parent e31c8c364c
commit 771b026175
2 changed files with 135 additions and 22 deletions
@@ -14,8 +14,82 @@ Feature: Challenge
And I create a RSA private key pair as cert_key And I create a RSA private key pair as cert_key
And I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format And I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format
And I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order And I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order
And I select challenge with type http-01 for domain localhost from order at order as challenge And I select challenge with type http-01 for domain localhost from order in order as challenge
And I serve challenge response for challenge at localhost And I serve challenge response for challenge at localhost
And I tell ACME server that challenge is ready to be verified And I tell ACME server that challenge is ready to be verified
And I poll and finalize the ACME order order as finalized_order And I poll and finalize the ACME order order as finalized_order
And the value finalized_order.body with jq ".status" should be equal to "valid" And the value finalized_order.body with jq ".status" should be equal to "valid"
Scenario: Did not finish all challenges
Given I have an ACME cert profile as "acme_profile"
When I have an ACME client connecting to "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory"
Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account
When I create certificate signing request as csr
Then I add names to certificate signing request csr
"""
{
"COMMON_NAME": "localhost"
}
"""
And I add subject alternative name to certificate signing request csr
"""
[
"infisical.com"
]
"""
And I create a RSA private key pair as cert_key
And I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format
And I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order
And I select challenge with type http-01 for domain localhost from order in order as challenge
And I serve challenge response for challenge at localhost
And I tell ACME server that challenge is ready to be verified
# the localhost auth should be valid
And I memorize order with jq ".authorizations | map(select(.body.identifier.value == "localhost")) | first | .uri" as localhost_auth
And I peak and memorize the next nonce as nonce
When I send a raw ACME request to "{localhost_auth}"
"""
{
"protected": {
"alg": "RS256",
"nonce": "{nonce}",
"url": "{localhost_auth}",
"kid": "{acme_account.uri}"
}
}
"""
Then the value response.status_code should be equal to 200
And the value response with jq ".status" should be equal to "valid"
# the infisical.com auth should still be pending
And I memorize order with jq ".authorizations | map(select(.body.identifier.value == "infisical.com")) | first | .uri" as infisical_auth
And I memorize response.headers with jq ".["replay-nonce"]" as nonce
When I send a raw ACME request to "{infisical_auth}"
"""
{
"protected": {
"alg": "RS256",
"nonce": "{nonce}",
"url": "{infisical_auth}",
"kid": "{acme_account.uri}"
}
}
"""
Then the value response.status_code should be equal to 200
And the value response with jq ".status" should be equal to "pending"
# the order should be pending as well
And I memorize response.headers with jq ".["replay-nonce"]" as nonce
When I send a raw ACME request to "{order.uri}"
"""
{
"protected": {
"alg": "RS256",
"nonce": "{nonce}",
"url": "{order.uri}",
"kid": "{acme_account.uri}"
}
}
"""
Then the value response.status_code should be equal to 200
And the value response with jq ".status" should be equal to "pending"
+60 -21
View File
@@ -8,6 +8,7 @@ import acme.client
import httpx import httpx
import jq import jq
import requests import requests
import requests.structures
import glom import glom
from faker import Faker from faker import Faker
from acme import client from acme import client
@@ -115,6 +116,8 @@ def eval_var(context: Context, var_path: str, as_json: bool = True):
value = value.to_json() value = value.to_json()
elif isinstance(value, requests.Response): elif isinstance(value, requests.Response):
value = value.json() value = value.json()
elif isinstance(value, requests.structures.CaseInsensitiveDict):
value = dict(value.lower_items())
elif isinstance(value, httpx.Response): elif isinstance(value, httpx.Response):
value = value.json() value = value.json()
return value return value
@@ -569,51 +572,58 @@ def step_impl(context: Context, var_path: str):
print(json.dumps(value.json(), indent=2)) print(json.dumps(value.json(), indent=2))
@then( def select_challenge(
"I select challenge with type {challenge_type} for domain {domain} from order at {var_path} as {challenge_var}"
)
def step_impl(
context: Context, context: Context,
challenge_type: str, challenge_type: str,
order_var_path: str,
domain: str, domain: str,
var_path: str,
challenge_var: str,
): ):
order = eval_var(context, var_path, as_json=False) acme_client = context.acme_client
order = eval_var(context, order_var_path, as_json=False)
if isinstance(order, dict):
order_body = messages.Order.from_json(order)
order = messages.OrderResource(
body=order_body,
authorizations=[
acme_client._authzr_from_response(
acme_client._post_as_get(url), uri=url
)
for url in order_body.authorizations
],
)
if not isinstance(order, messages.OrderResource): if not isinstance(order, messages.OrderResource):
raise ValueError( raise ValueError(
f"Expected OrderResource but got {type(order)!r} at {var_path!r}" f"Expected OrderResource but got {type(order)!r} at {order_var_path!r}"
) )
auths = list( auths = list(
filter(lambda o: o.body.identifier.value == domain, order.authorizations) filter(lambda o: o.body.identifier.value == domain, order.authorizations)
) )
if not auths: if not auths:
raise ValueError( raise ValueError(
f"Authorization for domain {domain!r} not found in {var_path!r}" f"Authorization for domain {domain!r} not found in {order_var_path!r}"
) )
if len(auths) > 1: if len(auths) > 1:
raise ValueError( raise ValueError(
f"More than one order for domain {domain!r} found in {var_path!r}" f"More than one order for domain {domain!r} found in {order_var_path!r}"
) )
auth = auths[0] auth = auths[0]
challenges = list(filter(lambda a: a.typ == challenge_type, auth.body.challenges)) challenges = list(filter(lambda a: a.typ == challenge_type, auth.body.challenges))
if not challenges: if not challenges:
raise ValueError( raise ValueError(
f"Authorization type {challenge_type!r} not found in {var_path!r}" f"Authorization type {challenge_type!r} not found in {order_var_path!r}"
) )
if len(challenges) > 1: if len(challenges) > 1:
raise ValueError( raise ValueError(
f"More than one authorization for type {challenge_type!r} found in {var_path!r}" f"More than one authorization for type {challenge_type!r} found in {order_var_path!r}"
) )
context.vars[challenge_var] = challenges[0] return challenges[0]
@then("I serve challenge response for {var_path} at {hostname}") def serve_challenge(
def step_impl(context: Context, var_path: str, hostname: str): context: Context,
if hostname != "localhost": challenge: messages.ChallengeBody,
raise ValueError("Currently only localhost is supported") ):
challenge = eval_var(context, var_path, as_json=False)
response, validation = challenge.response_and_validation( response, validation = challenge.response_and_validation(
context.acme_client.net.key context.acme_client.net.key
) )
@@ -629,14 +639,43 @@ def step_impl(context: Context, var_path: str, hostname: str):
context.web_server = web_server context.web_server = web_server
@then("I tell ACME server that {var_path} is ready to be verified") def notify_challenge_ready(context: Context, challenge: messages.ChallengeBody):
def step_impl(context: Context, var_path: str):
challenge = eval_var(context, var_path, as_json=False)
acme_client = context.acme_client acme_client = context.acme_client
response, validation = challenge.response_and_validation(acme_client.net.key) response, validation = challenge.response_and_validation(acme_client.net.key)
acme_client.answer_challenge(challenge, response) acme_client.answer_challenge(challenge, response)
@then(
"I select challenge with type {challenge_type} for domain {domain} from order in {var_path} as {challenge_var}"
)
def step_impl(
context: Context,
challenge_type: str,
domain: str,
var_path: str,
challenge_var: str,
):
challenge = select_challenge(
context=context,
challenge_type=challenge_type,
domain=domain,
order_var_path=var_path,
)
context.vars[challenge_var] = challenge
@then("I serve challenge response for {var_path} at {hostname}")
def step_impl(context: Context, var_path: str, hostname: str):
challenge = eval_var(context, var_path, as_json=False)
serve_challenge(context=context, challenge=challenge)
@then("I tell ACME server that {var_path} is ready to be verified")
def step_impl(context: Context, var_path: str):
challenge = eval_var(context, var_path, as_json=False)
notify_challenge_ready(context=context, challenge=challenge)
@then("I poll and finalize the ACME order {var_path} as {finalized_var}") @then("I poll and finalize the ACME order {var_path} as {finalized_var}")
def step_impl(context: Context, var_path: str, finalized_var: str): def step_impl(context: Context, var_path: str, finalized_var: str):
order = eval_var(context, var_path, as_json=False) order = eval_var(context, var_path, as_json=False)