This is an automated email from the ASF dual-hosted git repository.

ChenSammi pushed a commit to branch HDDS-13323-sts
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-13323-sts by this push:
     new 5000f2835c5 HDDS-16186. Move sts robot tests out of misc suite (#11042)
5000f2835c5 is described below

commit 5000f2835c5940a82b8135c9db16b82cc3a4f1ce
Author: fmorg-git <[email protected]>
AuthorDate: Sun Aug 23 21:02:10 2026 -0700

    HDDS-16186. Move sts robot tests out of misc suite (#11042)
---
 .../main/compose/ozonesecure-ha/ranger-testlib.sh  | 111 +++++++++++++++++++++
 .../src/main/compose/ozonesecure-ha/ranger.yaml    |   2 +-
 .../src/main/compose/ozonesecure-ha/test-ranger.sh |  90 +----------------
 .../src/main/compose/ozonesecure-ha/test-sts.sh    |  30 ++++++
 .../smoketest/security/ozone-secure-sts.resource   |  57 +++++++++++
 .../main/smoketest/security/ozone-secure-sts.robot |  43 ++++++++
 6 files changed, 244 insertions(+), 89 deletions(-)

diff --git 
a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/ranger-testlib.sh 
b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/ranger-testlib.sh
new file mode 100644
index 00000000000..1046a412b24
--- /dev/null
+++ b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/ranger-testlib.sh
@@ -0,0 +1,111 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+_ranger_testlib_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 
&& pwd )"
+: "${COMPOSE_DIR:=$_ranger_testlib_dir}"
+export COMPOSE_DIR
+
+# shellcheck source=/dev/null
+source "$COMPOSE_DIR/../testlib.sh"
+
+setup_ranger_acceptance_env() {
+  # Load FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION from .env without overriding 
other env.
+  # Ranger reads this value from install.properties (not process env), but we 
allow
+  # controlling the mounted install.properties via .env.
+  if [[ -z "${FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION:-}" ]] && [[ -f 
"${COMPOSE_DIR}/.env" ]]; then
+    local ff_from_dotenv
+    ff_from_dotenv="$(
+      (
+        set -a
+        # shellcheck source=/dev/null
+        source "${COMPOSE_DIR}/.env" || true
+        echo "${FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION:-}"
+      ) 2>/dev/null
+    )"
+    if [[ -n "${ff_from_dotenv}" ]]; then
+      export FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION="${ff_from_dotenv}"
+    fi
+  fi
+
+  if [[ -z "${RANGER_VERSION:-}" ]]; then
+    export RANGER_VERSION="${ranger.version}"
+  fi
+
+  : "${DOWNLOAD_DIR:=${TEMP_DIR:-/tmp}}"
+
+  export COMPOSE_FILE=docker-compose.yaml:ranger.yaml:../common/ranger.yaml
+  export OM_SERVICE_ID="omservice"
+  export SCM=scm1.org
+  export SECURITY_ENABLED=true
+
+  if [[ "${SKIP_APACHE_VERIFY_DOWNLOAD}" != "true" ]]; then
+    curl -LO https://downloads.apache.org/ranger/KEYS
+    gpg --import KEYS
+  fi
+
+  download_and_verify_apache_release 
"ranger/${RANGER_VERSION}/apache-ranger-${RANGER_VERSION}.tar.gz"
+  tar -C "${DOWNLOAD_DIR}" -x -z -f 
"${DOWNLOAD_DIR}/apache-ranger-${RANGER_VERSION}.tar.gz"
+  export RANGER_SOURCE_DIR="${DOWNLOAD_DIR}/apache-ranger-${RANGER_VERSION}"
+  chmod -R a+rX "${RANGER_SOURCE_DIR}"
+  export 
RANGER_INIT_POSTGRES_SH="${RANGER_SOURCE_DIR}/dev-support/ranger-docker/scripts/rdbms/init_postgres.sh"
+
+  # Create a temp install.properties so we can override feature flags from 
.env.
+  local ranger_admin_install_properties_src
+  
ranger_admin_install_properties_src="${RANGER_SOURCE_DIR}/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties"
+  RANGER_ADMIN_INSTALL_PROPERTIES="$(mktemp 
"${DOWNLOAD_DIR%/}/ranger-admin-install-postgres.XXXXXX")"
+  cp -f "${ranger_admin_install_properties_src}" 
"${RANGER_ADMIN_INSTALL_PROPERTIES}"
+  chmod a+r "${RANGER_ADMIN_INSTALL_PROPERTIES}"
+
+  local ff
+  ff="$(echo "${FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION:-false}" | tr 
'[:upper:]' '[:lower:]')"
+  if [[ "${ff}" != "true" ]]; then
+    ff="false"
+  fi
+  if grep -Eq 
'^[[:space:]]*#?[[:space:]]*FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=' 
"${RANGER_ADMIN_INSTALL_PROPERTIES}"; then
+    perl -pi -e 
"s@^[[:space:]]*#?[[:space:]]*FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=.*@FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=${ff}@g"
 \
+      "${RANGER_ADMIN_INSTALL_PROPERTIES}"
+  else
+    printf '\nFF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=%s\n' "${ff}" >> 
"${RANGER_ADMIN_INSTALL_PROPERTIES}"
+  fi
+
+  export RANGER_ADMIN_INSTALL_PROPERTIES
+
+  # Ranger docker support scripts moved between releases (eg: from config/*.sh 
to scripts/**).
+  # Ensure we don't fail if a glob doesn't match, but still make init scripts 
executable when present.
+  if [[ -d "${RANGER_SOURCE_DIR}/dev-support/ranger-docker" ]]; then
+    find "${RANGER_SOURCE_DIR}/dev-support/ranger-docker" -type f -name '*.sh' 
-exec chmod a+x {} +
+  fi
+  download_and_verify_apache_release 
"ranger/${RANGER_VERSION}/plugins/ozone/ranger-${RANGER_VERSION}-ozone-plugin.tar.gz"
+  tar -C "${DOWNLOAD_DIR}" -x -z -f 
"${DOWNLOAD_DIR}/ranger-${RANGER_VERSION}-ozone-plugin.tar.gz"
+  export 
RANGER_OZONE_PLUGIN_DIR="${DOWNLOAD_DIR}/ranger-${RANGER_VERSION}-ozone-plugin"
+  chmod -R a+rX "${RANGER_OZONE_PLUGIN_DIR}"
+  chmod a+x "${RANGER_OZONE_PLUGIN_DIR}"/*.sh
+
+  # customizations before install
+  perl -wpl -i \
+    -e 's@^POLICY_MGR_URL=.*@POLICY_MGR_URL=http://ranger:6080@;' \
+    -e 's@^REPOSITORY_NAME=.*@REPOSITORY_NAME=dev_ozone@;' \
+    -e 's@^CUSTOM_USER=ozone@CUSTOM_USER=hadoop@;' \
+    -e 's@^[email protected]=false@;' \
+    -e 
's@^XAAUDIT.LOG4J.DESTINATION.LOG4J=true@XAAUDIT.LOG4J.DESTINATION.LOG4J=false@;'
 \
+    "${RANGER_OZONE_PLUGIN_DIR}/install.properties"
+
+  echo 'machine ranger login admin password rangerR0cks!' > 
"${COMPOSE_DIR}/../../.netrc"
+
+  start_docker_env
+  wait_for_port ranger 6080 120
+}
diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/ranger.yaml 
b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/ranger.yaml
index 579dbfc0cc1..2dad84dbb5f 100644
--- a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/ranger.yaml
+++ b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/ranger.yaml
@@ -42,7 +42,7 @@ x-om-ranger-config:
     # JDK17+ no longer ships javax.annotation, but the Ranger Ozone plugin's 
Jersey2
     # client needs javax.annotation.Priority. Copy the jar into the plugin 
impl dir
     # so the isolated RangerPluginClassLoader can find it.
-    command: bash -c 'sudo mkdir -p 
/opt/ranger/ozone-plugin/lib/libext/ranger-ozone-plugin-impl && for j in 
/opt/hadoop/share/ozone/lib/javax.annotation-api-*.jar; do [ -e "$$j" ] || 
continue; sudo cp -n "$$j" 
/opt/ranger/ozone-plugin/lib/libext/ranger-ozone-plugin-impl/; done && sudo 
--preserve-env /opt/ranger/ozone-plugin/enable-ozone-plugin.sh && exec 
/opt/hadoop/bin/ozone om'
+    command: bash -c 'sudo mkdir -p 
/opt/ranger/ozone-plugin/lib/libext/ranger-ozone-plugin-impl && for j in 
/opt/hadoop/share/ozone/lib/javax.annotation-api-*.jar; do [ -e "$$j" ] || 
continue; 
dest="/opt/ranger/ozone-plugin/lib/libext/ranger-ozone-plugin-impl/$$(basename 
"$$j")"; sudo test -e "$$dest" || sudo cp -n "$$j" "$$dest" || sudo test -e 
"$$dest"; done && sudo --preserve-env 
/opt/ranger/ozone-plugin/enable-ozone-plugin.sh && exec /opt/hadoop/bin/ozone 
om'
 
 services:
   om1:
diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-ranger.sh 
b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-ranger.sh
index 4940e782da5..7d6950ccaa7 100755
--- a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-ranger.sh
+++ b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-ranger.sh
@@ -20,99 +20,13 @@
 COMPOSE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
 export COMPOSE_DIR
 
-# Load FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION from .env without overriding 
other env.
-# Ranger reads this value from install.properties (not process env), but we 
allow
-# controlling the mounted install.properties via .env.
-if [[ -z "${FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION:-}" ]] && [[ -f 
"${COMPOSE_DIR}/.env" ]]; then
-  _ff_from_dotenv="$(
-    (
-      set -a
-      # shellcheck source=/dev/null
-      source "${COMPOSE_DIR}/.env"
-      echo "${FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION:-}"
-    ) 2>/dev/null
-  )"
-  if [[ -n "${_ff_from_dotenv}" ]]; then
-    export FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION="${_ff_from_dotenv}"
-  fi
-  unset _ff_from_dotenv
-fi
-
-if [[ -z "${RANGER_VERSION:-}" ]]; then
-  export RANGER_VERSION="${ranger.version}"
-fi
-
-: "${DOWNLOAD_DIR:=${TEMP_DIR:-/tmp}}"
-
 # shellcheck source=/dev/null
-source "$COMPOSE_DIR/../testlib.sh"
-
-export COMPOSE_FILE=docker-compose.yaml:ranger.yaml:../common/ranger.yaml
-export OM_SERVICE_ID="omservice"
-export SCM=scm1.org
-export SECURITY_ENABLED=true
-
-if [[ "${SKIP_APACHE_VERIFY_DOWNLOAD}" != "true" ]]; then
-  curl -LO https://downloads.apache.org/ranger/KEYS
-  gpg --import KEYS
-fi
-
-download_and_verify_apache_release 
"ranger/${RANGER_VERSION}/apache-ranger-${RANGER_VERSION}.tar.gz"
-tar -C "${DOWNLOAD_DIR}" -x -z -f 
"${DOWNLOAD_DIR}/apache-ranger-${RANGER_VERSION}.tar.gz"
-export RANGER_SOURCE_DIR="${DOWNLOAD_DIR}/apache-ranger-${RANGER_VERSION}"
-chmod -R a+rX "${RANGER_SOURCE_DIR}"
-export 
RANGER_INIT_POSTGRES_SH="${RANGER_SOURCE_DIR}/dev-support/ranger-docker/scripts/rdbms/init_postgres.sh"
-
-# Create a temp install.properties so we can override feature flags from .env.
-RANGER_ADMIN_INSTALL_PROPERTIES_SRC="${RANGER_SOURCE_DIR}/dev-support/ranger-docker/scripts/admin/ranger-admin-install-postgres.properties"
-RANGER_ADMIN_INSTALL_PROPERTIES="$(mktemp 
"${DOWNLOAD_DIR%/}/ranger-admin-install-postgres.XXXXXX")"
-cp -f "${RANGER_ADMIN_INSTALL_PROPERTIES_SRC}" 
"${RANGER_ADMIN_INSTALL_PROPERTIES}"
-chmod a+r "${RANGER_ADMIN_INSTALL_PROPERTIES}"
-
-_ff="$(echo "${FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION:-false}" | tr 
'[:upper:]' '[:lower:]')"
-if [[ "${_ff}" != "true" ]]; then
-  _ff="false"
-fi
-if grep -Eq 
'^[[:space:]]*#?[[:space:]]*FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=' 
"${RANGER_ADMIN_INSTALL_PROPERTIES}"; then
-  perl -pi -e 
"s@^[[:space:]]*#?[[:space:]]*FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=.*@FF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=${_ff}@g"
 \
-    "${RANGER_ADMIN_INSTALL_PROPERTIES}"
-else
-  printf '\nFF_ENABLE_OZONE_ACTION_MATCHES_CONDITION=%s\n' "${_ff}" >> 
"${RANGER_ADMIN_INSTALL_PROPERTIES}"
-fi
-unset _ff
-
-export RANGER_ADMIN_INSTALL_PROPERTIES
-
-# Ranger docker support scripts moved between releases (eg: from config/*.sh 
to scripts/**).
-# Ensure we don't fail if a glob doesn't match, but still make init scripts 
executable when present.
-if [[ -d "${RANGER_SOURCE_DIR}/dev-support/ranger-docker" ]]; then
-  find "${RANGER_SOURCE_DIR}/dev-support/ranger-docker" -type f -name '*.sh' 
-exec chmod a+x {} +
-fi
-download_and_verify_apache_release 
"ranger/${RANGER_VERSION}/plugins/ozone/ranger-${RANGER_VERSION}-ozone-plugin.tar.gz"
-tar -C "${DOWNLOAD_DIR}" -x -z -f 
"${DOWNLOAD_DIR}/ranger-${RANGER_VERSION}-ozone-plugin.tar.gz"
-export 
RANGER_OZONE_PLUGIN_DIR="${DOWNLOAD_DIR}/ranger-${RANGER_VERSION}-ozone-plugin"
-chmod -R a+rX "${RANGER_OZONE_PLUGIN_DIR}"
-chmod a+x "${RANGER_OZONE_PLUGIN_DIR}"/*.sh
-
-# customizations before install
-perl -wpl -i \
-  -e 's@^POLICY_MGR_URL=.*@POLICY_MGR_URL=http://ranger:6080@;' \
-  -e 's@^REPOSITORY_NAME=.*@REPOSITORY_NAME=dev_ozone@;' \
-  -e 's@^CUSTOM_USER=ozone@CUSTOM_USER=hadoop@;' \
-  -e 's@^[email protected]=false@;' \
-  -e 
's@^XAAUDIT.LOG4J.DESTINATION.LOG4J=true@XAAUDIT.LOG4J.DESTINATION.LOG4J=false@;'
 \
-  "${RANGER_OZONE_PLUGIN_DIR}/install.properties"
-
-echo 'machine ranger login admin password rangerR0cks!' > ../../.netrc
+source "$COMPOSE_DIR/ranger-testlib.sh"
 
-start_docker_env
-wait_for_port ranger 6080 120
+setup_ranger_acceptance_env
 
 execute_robot_test s3g -v USER:hdfs kinit.robot
 execute_robot_test s3g freon/generate.robot
 execute_robot_test s3g freon/validate.robot
 
 execute_robot_test s3g -v RANGER_ENDPOINT_URL:"http://ranger:6080"; -v 
USER:hdfs security/ozone-secure-tenant.robot
-execute_robot_test s3g -v RANGER_ENDPOINT_URL:"http://ranger:6080"; -v 
USER:hdfs security/ozone-secure-sts.robot
-execute_robot_test s3g -v RANGER_ENDPOINT_URL:"http://ranger:6080"; -v 
USER:hdfs security/ozone-secure-sts-multitenant.robot
-"${COMPOSE_DIR}/polaris-smoketest.sh"
diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-sts.sh 
b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-sts.sh
new file mode 100755
index 00000000000..879dd1b5851
--- /dev/null
+++ b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-sts.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#suite:sts
+
+COMPOSE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+export COMPOSE_DIR
+
+# shellcheck source=/dev/null
+source "$COMPOSE_DIR/ranger-testlib.sh"
+
+setup_ranger_acceptance_env
+
+execute_robot_test s3g -v RANGER_ENDPOINT_URL:"http://ranger:6080"; -v 
USER:hdfs security/ozone-secure-sts.robot
+execute_robot_test s3g -v RANGER_ENDPOINT_URL:"http://ranger:6080"; -v 
USER:hdfs security/ozone-secure-sts-multitenant.robot
+"${COMPOSE_DIR}/polaris-smoketest.sh"
diff --git 
a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.resource 
b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.resource
index dd697b1598e..19cb6f4e202 100644
--- a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.resource
+++ b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.resource
@@ -27,6 +27,11 @@ ${RANGER_ENDPOINT_URL}                      ${EMPTY}
 ${STS_ENDPOINT_URL}                         http://s3g:9880
 ${S3G_ENDPOINT_URL}                         http://s3g:9878
 ${ROLE_SESSION_NAME}                        sts-session-name
+${EXPIRED_STS_TOKEN_PROFILE}                expired_sts_token
+${EXPIRED_STS_TOKEN_ACCESS_KEY_ID}          ${EMPTY}
+${EXPIRED_STS_TOKEN_SECRET_ACCESS_KEY}      ${EMPTY}
+${EXPIRED_STS_TOKEN_SESSION_TOKEN}          ${EMPTY}
+${EXPIRED_STS_TOKEN_EXPIRATION}             ${EMPTY}
 
 *** Keywords ***
 Configure AWS Profile
@@ -115,6 +120,58 @@ Assume Role And Get Temporary Credentials
     Should Be True                ${time_diff} >= ${minimum_expected}          
 Expected expiration to be at least ${minimum_expected}s in the future, but was 
${time_diff}s
     Should Be True                ${time_diff} <= ${maximum_expected}          
 Expected expiration to be at most ${maximum_expected}s in the future, but was 
${time_diff}s
 
+Assume Role And Store Expired STS Token Credentials
+    # Issue a 900s STS credential and store it in EXPIRED_STS_TOKEN_* globals 
only (it should never be used/modified until the "Expired STS temporary 
credentials return ExpiredToken on S3 APIs" test).
+    [Arguments]                   ${perm_access_key_id}  ${perm_secret_key}  
${role_arn}  ${duration_seconds}=900
+    Configure AWS Profile         permanent  ${perm_access_key_id}  
${perm_secret_key}
+    ${suffix} =                   Generate Random String        8    [LOWER]
+    ${role_session_name} =        Set Variable                  
expired-sts-token-${suffix}
+
+    ${cmd} =                      Set Variable                  aws sts 
assume-role --endpoint-url ${STS_ENDPOINT_URL} --role-arn ${role_arn} 
--role-session-name ${role_session_name} --duration-seconds ${duration_seconds} 
--output json --profile permanent
+
+    ${json} =                     Execute                       ${cmd}
+    Should Contain                ${json}                       Credentials
+
+    ${expiredStsAccessKeyId} =    Execute                       printf '%s' 
'${json}' | jq -r '.Credentials.AccessKeyId'
+    ${expiredStsSecretKey} =      Execute                       printf '%s' 
'${json}' | jq -r '.Credentials.SecretAccessKey'
+    ${expiredStsSessionToken} =   Execute                       printf '%s' 
'${json}' | jq -r '.Credentials.SessionToken'
+    ${expiration} =               Execute                       printf '%s' 
'${json}' | jq -r '.Credentials.Expiration'
+    Should Start With             ${expiredStsAccessKeyId}      ASIA
+    Set Global Variable           ${EXPIRED_STS_TOKEN_ACCESS_KEY_ID}         
${expiredStsAccessKeyId}
+    Set Global Variable           ${EXPIRED_STS_TOKEN_SECRET_ACCESS_KEY}     
${expiredStsSecretKey}
+    Set Global Variable           ${EXPIRED_STS_TOKEN_SESSION_TOKEN}         
${expiredStsSessionToken}
+    Set Global Variable           ${EXPIRED_STS_TOKEN_EXPIRATION}            
${expiration}
+
+Expired STS Token Credentials Should Be Available
+    Skip If                       '${EXPIRED_STS_TOKEN_EXPIRATION}' == 
'${EMPTY}'  Expired STS token credentials are unavailable. Run the prerequisite 
AssumeRole test before expired-token checks.
+    Should Not Be Empty           ${EXPIRED_STS_TOKEN_ACCESS_KEY_ID}         
Expired STS token access key ID is unavailable.
+    Should Not Be Empty           ${EXPIRED_STS_TOKEN_SECRET_ACCESS_KEY}     
Expired STS token secret key is unavailable.
+    Should Not Be Empty           ${EXPIRED_STS_TOKEN_SESSION_TOKEN}         
Expired STS token session token is unavailable.
+
+Wait Until Expired STS Token Expiration Elapsed
+    # Matches the +/-2s skew tolerance used for AssumeRole duration checks.
+    Expired STS Token Credentials Should Be Available
+    ${now} =                      Get Current Date              time_zone=UTC
+    ${secondsUntilExpiration} =   Subtract Date From Date       
${EXPIRED_STS_TOKEN_EXPIRATION}    ${now}
+    ${remaining} =                Evaluate                      
${secondsUntilExpiration} + 2
+    Run Keyword If                ${remaining} > 0              Sleep  
${remaining}
+
+Configure Expired STS Token S3 Profile
+    Expired STS Token Credentials Should Be Available
+    Configure AWS Profile         ${EXPIRED_STS_TOKEN_PROFILE}  
${EXPIRED_STS_TOKEN_ACCESS_KEY_ID}  ${EXPIRED_STS_TOKEN_SECRET_ACCESS_KEY}  
${EXPIRED_STS_TOKEN_SESSION_TOKEN}
+
+Execute S3api Expect Expired Token
+    [Arguments]                   ${command_tail}
+    ${output} =                   Execute And Ignore Error      aws s3api 
--endpoint-url ${S3G_ENDPOINT_URL} ${command_tail} --profile 
${EXPIRED_STS_TOKEN_PROFILE}
+    Should Contain                ${output}                     ExpiredToken
+
+Execute S3api Expect Expired Token Head Operation
+    [Arguments]                   ${command_tail}               
${expected_aws_operation_name}
+    [Documentation]               head-bucket and head-object omit S3 XML 
error bodies so we can't check for ExpiredToken in the body.  awscli surfaces 
HTTP 400 and the operation label (HeadBucket / HeadObject) instead of 
ExpiredToken. Assert status and operation together to avoid matching unrelated 
400s.
+    ${output} =                   Execute And Ignore Error      aws s3api 
--endpoint-url ${S3G_ENDPOINT_URL} ${command_tail} --profile 
${EXPIRED_STS_TOKEN_PROFILE}
+    Should Contain                ${output}                     (400)
+    Should Contain                ${output}                     
${expected_aws_operation_name}
+
 Assume Role And Configure STS Profile
     [Arguments]                   ${perm_access_key_id}  ${perm_secret_key}  
${policy_json}=${EMPTY}  ${role_arn}=${ROLE_ARN_OBS}  
${role_session_name}=${ROLE_SESSION_NAME}  ${duration_seconds}=900
     Assume Role And Get Temporary Credentials                   
perm_access_key_id=${perm_access_key_id}  perm_secret_key=${perm_secret_key}  
policy_json=${policy_json}  role_arn=${role_arn}  
role_session_name=${role_session_name}  duration_seconds=${duration_seconds}
diff --git 
a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot 
b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot
index a1f34e818b7..c0362d74704 100644
--- a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot
@@ -408,6 +408,9 @@ Get S3 Credentials for Service Catalog Principal, Create 
Iceberg Buckets, and Up
     # Switch back to the service catalog principal for running S3/STS requests.
     Kinit test user               ${ICEBERG_SVC_CATALOG_USER}   
${ICEBERG_SVC_CATALOG_USER}.keytab
 
+    # Long-lived 15-minute STS credential to test STS token expiration and 
proper error message
+    Assume Role And Store Expired STS Token Credentials         
perm_access_key_id=${PERMANENT_ACCESS_KEY_ID}  
perm_secret_key=${PERMANENT_SECRET_KEY}  
role_arn=${ICEBERG_ALL_ACCESS_ROLE_OBS_ARN}
+
 Assume Role for Limited-Scope Token
     # All access role is limited to read-only via session policy
     FOR    ${bucket}    ${role_arn}    IN
@@ -1237,6 +1240,46 @@ STS session policy containing only GetObject must deny 
DeleteObjects
     ${output} =                  Execute                        aws s3api 
--endpoint-url ${S3G_ENDPOINT_URL} delete-bucket --bucket ${bucket} --profile 
sts
     Should Not Contain           ${output}                      AccessDenied
 
+Expired STS temporary credentials must return ExpiredToken on S3 APIs
+    # Increase timeout to account for 15 minute STS token expiration plus the 
time to execute the api calls
+    [Timeout]                     25 minutes
+    ${dummy_mpu_upload_id} =      Set Variable                  
dummyExpiredStsMpuUploadId01
+
+    Wait Until Expired STS Token Expiration Elapsed
+    Configure Expired STS Token S3 Profile
+
+    Execute S3api Expect Expired Token                          list-buckets 
--output json
+    Execute S3api Expect Expired Token Head Operation           head-bucket 
--bucket ${ICEBERG_BUCKET_OBS}    HeadBucket
+    Execute S3api Expect Expired Token                          
list-objects-v2 --bucket ${ICEBERG_BUCKET_OBS} --output json
+    Execute S3api Expect Expired Token                          list-objects 
--bucket ${ICEBERG_BUCKET_OBS} --output json
+    Execute S3api Expect Expired Token                          get-object 
--bucket ${ICEBERG_BUCKET_OBS} --key ${ICEBERG_BUCKET_TESTFILE} 
${TEMP_DIR}/expired-sts-token-get-object.out
+
+    Create File                   
${TEMP_DIR}/expired-sts-token-put-object-body.txt    expired sts token put body
+
+    Execute S3api Expect Expired Token                          put-object 
--bucket ${ICEBERG_BUCKET_OBS} --key sts-expired-sts-token-put.txt --body 
${TEMP_DIR}/expired-sts-token-put-object-body.txt
+    Execute S3api Expect Expired Token Head Operation           head-object 
--bucket ${ICEBERG_BUCKET_OBS} --key ${ICEBERG_BUCKET_TESTFILE}    HeadObject
+    Execute S3api Expect Expired Token                          delete-object 
--bucket ${ICEBERG_BUCKET_OBS} --key sts-expired-sts-token-delete-marker.txt
+    Execute S3api Expect Expired Token                          delete-objects 
--bucket ${ICEBERG_BUCKET_OBS} --delete 
'Objects=[{Key=sts-expired-sts-token-delete-objects-marker.txt}],Quiet=false'
+
+    ${bucket_suffix} =            Generate Random String        8   [LOWER]
+    ${exp_bucket} =               Set Variable                  
sts-bucket-expired-sts-token-${bucket_suffix}
+
+    Execute S3api Expect Expired Token                          create-bucket 
--bucket ${exp_bucket}
+    Execute S3api Expect Expired Token                          delete-bucket 
--bucket ${exp_bucket}
+    Execute S3api Expect Expired Token                          get-bucket-acl 
--bucket ${ICEBERG_BUCKET_OBS}
+    Execute S3api Expect Expired Token                          put-bucket-acl 
--bucket ${ICEBERG_BUCKET_OBS} --grant-read ''
+    Execute S3api Expect Expired Token                          
list-multipart-uploads --bucket ${ICEBERG_BUCKET_OBS}
+    Execute S3api Expect Expired Token                          
create-multipart-upload --bucket ${ICEBERG_BUCKET_OBS} --key 
sts-expired-sts-token-mpu.txt
+    Execute S3api Expect Expired Token                          upload-part 
--bucket ${ICEBERG_BUCKET_OBS} --key sts-expired-sts-token-mpu.txt 
--part-number 1 --body ${TEMP_DIR}/expired-sts-token-put-object-body.txt 
--upload-id ${dummy_mpu_upload_id}
+    Execute S3api Expect Expired Token                          
upload-part-copy --bucket ${ICEBERG_BUCKET_OBS} --key 
sts-expired-sts-token-mpu-copy.txt --part-number 1 --upload-id 
${dummy_mpu_upload_id} --copy-source 
${ICEBERG_BUCKET_OBS}/${ICEBERG_BUCKET_TESTFILE}
+    Execute S3api Expect Expired Token                          list-parts 
--bucket ${ICEBERG_BUCKET_OBS} --key sts-expired-sts-token-mpu.txt --upload-id 
${dummy_mpu_upload_id}
+    Execute S3api Expect Expired Token                          
abort-multipart-upload --bucket ${ICEBERG_BUCKET_OBS} --key 
sts-expired-sts-token-mpu.txt --upload-id ${dummy_mpu_upload_id}
+    Execute S3api Expect Expired Token                          
complete-multipart-upload --bucket ${ICEBERG_BUCKET_OBS} --key 
sts-expired-sts-token-mpu.txt --upload-id ${dummy_mpu_upload_id} 
--multipart-upload 
'{"Parts":[{"ETag":"d41d8cd98f00b204e9800998ecf8427e","PartNumber":1}]}'
+    Execute S3api Expect Expired Token                          copy-object 
--bucket ${ICEBERG_BUCKET_OBS} --copy-source 
${ICEBERG_BUCKET_OBS}/${ICEBERG_BUCKET_TESTFILE} --key 
sts-expired-sts-token-copy-dest.txt
+    Execute S3api Expect Expired Token                          
get-object-tagging --bucket ${ICEBERG_BUCKET_OBS} --key 
${ICEBERG_BUCKET_TESTFILE}
+    Execute S3api Expect Expired Token                          
put-object-tagging --bucket ${ICEBERG_BUCKET_OBS} --key 
${ICEBERG_BUCKET_TESTFILE} --tagging 
'{"TagSet":[{"Key":"tag-key-expired-sts-token","Value":"tag-value-expired-sts-token"}]}'
+    Execute S3api Expect Expired Token                          
delete-object-tagging --bucket ${ICEBERG_BUCKET_OBS} --key 
${ICEBERG_BUCKET_TESTFILE}
+
 Revoking Permanent User Must Revoke Existing Session Token
     # Create session tokens for both buckets, verify they work, then revoke 
permanent user secret and verify both fail.
     Assume Role And Get Temporary Credentials                   
perm_access_key_id=${PERMANENT_ACCESS_KEY_ID}  
perm_secret_key=${PERMANENT_SECRET_KEY}  
role_arn=${ICEBERG_ALL_ACCESS_ROLE_OBS_ARN}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to