From fd91f298e43162c83c172020131d3ca6d53a8b52 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Wed, 24 Jun 2026 17:37:31 -0400 Subject: [PATCH 1/3] feat(ci): Adds enforce DPoP option to start-additional-kas --- test/start-additional-kas/action.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/start-additional-kas/action.yaml b/test/start-additional-kas/action.yaml index 41692969c3..d057eee5b5 100644 --- a/test/start-additional-kas/action.yaml +++ b/test/start-additional-kas/action.yaml @@ -34,6 +34,10 @@ inputs: default: "text" description: 'Log format type (text, json)' required: false + dpop-challenge-enabled: + default: "false" + description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true)' + required: false outputs: log-file: @@ -54,6 +58,7 @@ runs: ROOT_KEY: ${{ inputs.root-key }} LOG_LEVEL: ${{ inputs.log-level }} LOG_TYPE: ${{ inputs.log-type }} + DPOP_CHALLENGE_ENABLED: ${{ inputs.dpop-challenge-enabled }} run: | # Validate kas-port (must be a valid port number 1-65535) if [[ ! "${KAS_PORT}" =~ ^[0-9]+$ ]] || (( KAS_PORT < 1 || KAS_PORT > 65535 )); then @@ -122,6 +127,16 @@ runs: exit 1 ;; esac + + # Validate dpop-challenge-enabled (must be true or false) + case "${DPOP_CHALLENGE_ENABLED}" in + true|false) + ;; + *) + echo "Error: dpop-challenge-enabled must be 'true' or 'false'." + exit 1 + ;; + esac - name: Set log file path id: log-path shell: bash @@ -143,6 +158,7 @@ runs: ROOT_KEY: ${{ inputs.root-key }} LOG_LEVEL: ${{ inputs.log-level }} LOG_TYPE: ${{ inputs.log-type }} + DPOP_CHALLENGE_ENABLED: ${{ inputs.dpop-challenge-enabled }} with: run: | # Disable PQC if key files weren't generated by the platform @@ -164,6 +180,7 @@ runs: | del(.services.kas.root_key) | (.logger.level = env(LOG_LEVEL)) | (.logger.type = env(LOG_TYPE)) + | (.server.auth.dpop.require_nonce = (env(DPOP_CHALLENGE_ENABLED) == "true")) | (.sdk_config = {"client_id":"opentdf","client_secret":"secret","core":{"endpoint":"http://localhost:8080","plaintext":true}}) ' opentdf-dev.yaml > opentdf-${KAS_NAME}.yaml if [ "${KEY_MANAGEMENT}" == "true" ]; then From 527ed49a6ad373e52c874b51ea41ffbc1fab3777 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Thu, 25 Jun 2026 06:10:17 -0400 Subject: [PATCH 2/3] feat(ci): enforce DPoP when enabling the nonce challenge flow The DPoP nonce challenge only applies to DPoP-bound requests; without enforcement a plain Bearer token bypasses DPoP validation and never sees a challenge. When dpop-challenge-enabled is set, also set server.auth.dpop.enforce alongside require_nonce in both start actions. The flag only ever turns enforcement on: start-additional-kas uses with(select(...)) so it never writes enforce: false (preserving any base value), and start-up-with-containers sets it inside the step already gated on the flag. Signed-off-by: Dave Mihalcik --- test/start-additional-kas/action.yaml | 3 ++- test/start-up-with-containers/action.yaml | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/start-additional-kas/action.yaml b/test/start-additional-kas/action.yaml index d057eee5b5..c6135c7269 100644 --- a/test/start-additional-kas/action.yaml +++ b/test/start-additional-kas/action.yaml @@ -36,7 +36,7 @@ inputs: required: false dpop-challenge-enabled: default: "false" - description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true)' + description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true and server.auth.dpop.enforce: true)' required: false outputs: @@ -181,6 +181,7 @@ runs: | (.logger.level = env(LOG_LEVEL)) | (.logger.type = env(LOG_TYPE)) | (.server.auth.dpop.require_nonce = (env(DPOP_CHALLENGE_ENABLED) == "true")) + | with(select(env(DPOP_CHALLENGE_ENABLED) == "true"); .server.auth.dpop.enforce = true) | (.sdk_config = {"client_id":"opentdf","client_secret":"secret","core":{"endpoint":"http://localhost:8080","plaintext":true}}) ' opentdf-dev.yaml > opentdf-${KAS_NAME}.yaml if [ "${KEY_MANAGEMENT}" == "true" ]; then diff --git a/test/start-up-with-containers/action.yaml b/test/start-up-with-containers/action.yaml index 33ccd5cec7..e802f04792 100644 --- a/test/start-up-with-containers/action.yaml +++ b/test/start-up-with-containers/action.yaml @@ -33,7 +33,7 @@ inputs: required: false dpop-challenge-enabled: default: "false" - description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true)' + description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true and server.auth.dpop.enforce: true)' required: false outputs: @@ -279,7 +279,10 @@ runs: shell: bash if: ${{ inputs.dpop-challenge-enabled == 'true' }} run: | - yq e '.server.auth.dpop.require_nonce = true' -i opentdf.yaml + yq e ' + (.server.auth.dpop.require_nonce = true) + | (.server.auth.dpop.enforce = true) + ' -i opentdf.yaml working-directory: otdf-test-platform - name: Overlay DPoP-capable Keycloak (26.2) # The default docker-compose pins Keycloak 25 so downstream consumers stay on From d9fdaddfc1633ae7127bd4d1a8fef4a089e24a99 Mon Sep 17 00:00:00 2001 From: Dave Mihalcik Date: Thu, 25 Jun 2026 07:32:52 -0400 Subject: [PATCH 3/3] feat(ci): add dpop-enforce-required input, decouple from nonce challenge DPoP enforcement and the nonce-challenge flow are separate concerns. Replace the coupling (where dpop-challenge-enabled also set server.auth.dpop.enforce) with a dedicated dpop-enforce-required input (default false) that drives enforcement on its own. dpop-challenge-enabled again sets only require_nonce. The enforce knob only ever turns enforcement on: start-additional-kas uses with(select(...)) keyed on DPOP_ENFORCE_REQUIRED, and start-up-with-containers sets it in a new step gated on the flag, so enforce: false is never written. Signed-off-by: Dave Mihalcik --- test/start-additional-kas/action.yaml | 20 ++++++++++++++-- test/start-up-with-containers/action.yaml | 28 +++++++++++++++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/test/start-additional-kas/action.yaml b/test/start-additional-kas/action.yaml index c6135c7269..2bd07cf59a 100644 --- a/test/start-additional-kas/action.yaml +++ b/test/start-additional-kas/action.yaml @@ -36,7 +36,11 @@ inputs: required: false dpop-challenge-enabled: default: "false" - description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true and server.auth.dpop.enforce: true)' + description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true)' + required: false + dpop-enforce-required: + default: "false" + description: 'Whether to enforce DPoP-bound access tokens (sets server.auth.dpop.enforce: true)' required: false outputs: @@ -59,6 +63,7 @@ runs: LOG_LEVEL: ${{ inputs.log-level }} LOG_TYPE: ${{ inputs.log-type }} DPOP_CHALLENGE_ENABLED: ${{ inputs.dpop-challenge-enabled }} + DPOP_ENFORCE_REQUIRED: ${{ inputs.dpop-enforce-required }} run: | # Validate kas-port (must be a valid port number 1-65535) if [[ ! "${KAS_PORT}" =~ ^[0-9]+$ ]] || (( KAS_PORT < 1 || KAS_PORT > 65535 )); then @@ -137,6 +142,16 @@ runs: exit 1 ;; esac + + # Validate dpop-enforce-required (must be true or false) + case "${DPOP_ENFORCE_REQUIRED}" in + true|false) + ;; + *) + echo "Error: dpop-enforce-required must be 'true' or 'false'." + exit 1 + ;; + esac - name: Set log file path id: log-path shell: bash @@ -159,6 +174,7 @@ runs: LOG_LEVEL: ${{ inputs.log-level }} LOG_TYPE: ${{ inputs.log-type }} DPOP_CHALLENGE_ENABLED: ${{ inputs.dpop-challenge-enabled }} + DPOP_ENFORCE_REQUIRED: ${{ inputs.dpop-enforce-required }} with: run: | # Disable PQC if key files weren't generated by the platform @@ -181,7 +197,7 @@ runs: | (.logger.level = env(LOG_LEVEL)) | (.logger.type = env(LOG_TYPE)) | (.server.auth.dpop.require_nonce = (env(DPOP_CHALLENGE_ENABLED) == "true")) - | with(select(env(DPOP_CHALLENGE_ENABLED) == "true"); .server.auth.dpop.enforce = true) + | with(select(env(DPOP_ENFORCE_REQUIRED) == "true"); .server.auth.dpop.enforce = true) | (.sdk_config = {"client_id":"opentdf","client_secret":"secret","core":{"endpoint":"http://localhost:8080","plaintext":true}}) ' opentdf-dev.yaml > opentdf-${KAS_NAME}.yaml if [ "${KEY_MANAGEMENT}" == "true" ]; then diff --git a/test/start-up-with-containers/action.yaml b/test/start-up-with-containers/action.yaml index e802f04792..0a773fbe03 100644 --- a/test/start-up-with-containers/action.yaml +++ b/test/start-up-with-containers/action.yaml @@ -33,7 +33,11 @@ inputs: required: false dpop-challenge-enabled: default: "false" - description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true and server.auth.dpop.enforce: true)' + description: 'Whether to enable the DPoP nonce challenge flow (sets server.auth.dpop.require_nonce: true)' + required: false + dpop-enforce-required: + default: "false" + description: 'Whether to enforce DPoP-bound access tokens (sets server.auth.dpop.enforce: true)' required: false outputs: @@ -58,6 +62,7 @@ runs: LOG_TYPE: ${{ inputs.log-type }} PROVISION_POLICY_FIXTURES: ${{ inputs.provision-policy-fixtures }} DPOP_CHALLENGE_ENABLED: ${{ inputs.dpop-challenge-enabled }} + DPOP_ENFORCE_REQUIRED: ${{ inputs.dpop-enforce-required }} run: | # Validate platform-ref (must contain only safe characters for a git ref) if [[ ! "${PLATFORM_REF}" =~ ^[a-zA-Z0-9._/-]+$ ]]; then @@ -130,6 +135,16 @@ runs: exit 1 ;; esac + + # Validate dpop-enforce-required (must be true or false) + case "${DPOP_ENFORCE_REQUIRED}" in + true|false) + ;; + *) + echo "Error: dpop-enforce-required must be 'true' or 'false'." + exit 1 + ;; + esac - name: Check out platform uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -279,10 +294,13 @@ runs: shell: bash if: ${{ inputs.dpop-challenge-enabled == 'true' }} run: | - yq e ' - (.server.auth.dpop.require_nonce = true) - | (.server.auth.dpop.enforce = true) - ' -i opentdf.yaml + yq e '.server.auth.dpop.require_nonce = true' -i opentdf.yaml + working-directory: otdf-test-platform + - name: Enable DPoP enforcement + shell: bash + if: ${{ inputs.dpop-enforce-required == 'true' }} + run: | + yq e '.server.auth.dpop.enforce = true' -i opentdf.yaml working-directory: otdf-test-platform - name: Overlay DPoP-capable Keycloak (26.2) # The default docker-compose pins Keycloak 25 so downstream consumers stay on