This is an automated email from the ASF dual-hosted git repository. jojochuang pushed a commit to branch ozone-2.1 in repository https://gitbox.apache.org/repos/asf/ozone.git
commit 2dff8e6ad809b29d003a2953b48fd333d2bb477b Author: Wei-Chiu Chuang <[email protected]> AuthorDate: Fri Aug 14 09:05:55 2026 -0700 Adding OM tests. Change-Id: Ie5e6ceef61bd6ffa3e2eed30c7ccb3e9d718592a (cherry picked from commit b58885e7bafd71ed837ce757cff447f93c7147cc) --- .../ozone/om/TestGetDBUpdatesAuthorization.java | 137 +++++++++++++++++++++ .../org/apache/hadoop/ozone/om/OzoneManager.java | 27 ++++ 2 files changed, 164 insertions(+) diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestGetDBUpdatesAuthorization.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestGetDBUpdatesAuthorization.java new file mode 100644 index 00000000000..906597647f2 --- /dev/null +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestGetDBUpdatesAuthorization.java @@ -0,0 +1,137 @@ +/* + * 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. + */ + +package org.apache.hadoop.ozone.om; + +import static org.apache.hadoop.hdds.security.SecurityConfig.OZONE_TEST_AUTHORIZATION_ENABLED; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS_NATIVE; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ADMINISTRATORS; +import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_READONLY_ADMINISTRATORS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.security.PrivilegedExceptionAction; +import org.apache.hadoop.hdds.conf.OzoneConfiguration; +import org.apache.hadoop.ozone.MiniOzoneCluster; +import org.apache.hadoop.ozone.client.OzoneBucket; +import org.apache.hadoop.ozone.client.OzoneClient; +import org.apache.hadoop.ozone.client.OzoneClientFactory; +import org.apache.hadoop.ozone.client.OzoneVolume; +import org.apache.hadoop.ozone.client.io.OzoneOutputStream; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.om.helpers.DBUpdates; +import org.apache.hadoop.ozone.om.protocolPB.OmTransportFactory; +import org.apache.hadoop.ozone.om.protocolPB.OzoneManagerProtocolClientSideTranslatorPB; +import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.DBUpdatesRequest; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.ratis.protocol.ClientId; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * Tests authorization of the OzoneManager getDBUpdates RPC (OmClientProtocol DBUpdates). + * getDBUpdates streams the raw RocksDB delta of the whole OM metadata DB and backs + * OM->Recon replication, so it is restricted to admins and read-only admins, like the + * other whole-system reads (listOpenFiles, getQuotaRepairStatus). + */ +public class TestGetDBUpdatesAuthorization { + + private static MiniOzoneCluster cluster; + private static OzoneConfiguration conf; + + private static final String VOL = "vol1"; + private static final String BUCKET = "bucket1"; + private static final String KEY = "key1"; + private static final String RECON_PRINCIPAL = "reconsvc"; + + @BeforeAll + static void init() throws Exception { + conf = new OzoneConfiguration(); + conf.setBoolean(OZONE_ACL_ENABLED, true); + conf.set(OZONE_ACL_AUTHORIZER_CLASS, OZONE_ACL_AUTHORIZER_CLASS_NATIVE); + conf.set(OZONE_ADMINISTRATORS, "admin"); + conf.set(OZONE_READONLY_ADMINISTRATORS, RECON_PRINCIPAL); + // Make admin authorization effective without a KDC so the gate actually runs. + conf.setBoolean(OZONE_TEST_AUTHORIZATION_ENABLED, true); + cluster = MiniOzoneCluster.newBuilder(conf).setNumDatanodes(3).build(); + cluster.waitForClusterToBeReady(); + + UserGroupInformation.createUserForTesting("admin", new String[] {"admins"}) + .doAs((PrivilegedExceptionAction<Void>) () -> { + try (OzoneClient c = OzoneClientFactory.getRpcClient(conf)) { + c.getObjectStore().createVolume(VOL); + OzoneVolume vol = c.getObjectStore().getVolume(VOL); + vol.createBucket(BUCKET); + OzoneBucket b = vol.getBucket(BUCKET); + byte[] data = "hello".getBytes(java.nio.charset.StandardCharsets.UTF_8); + try (OzoneOutputStream os = b.createKey(KEY, data.length)) { + os.write(data); + } + } + return null; + }); + } + + @AfterAll + static void shutdown() { + if (cluster != null) { + cluster.shutdown(); + } + } + + private static DBUpdates getDBUpdates(UserGroupInformation user) throws Exception { + return user.doAs((PrivilegedExceptionAction<DBUpdates>) () -> { + OzoneManagerProtocolClientSideTranslatorPB omClient = + new OzoneManagerProtocolClientSideTranslatorPB( + OmTransportFactory.create(conf, user, null), + ClientId.randomId().toString()); + DBUpdatesRequest req = DBUpdatesRequest.newBuilder() + .setSequenceNumber(0) + .build(); + return omClient.getDBUpdates(req); + }); + } + + @Test + void nonAdminIsDenied() { + UserGroupInformation nonAdmin = + UserGroupInformation.createUserForTesting("nonadmin", new String[] {"users"}); + OMException ex = assertThrows(OMException.class, () -> getDBUpdates(nonAdmin)); + assertEquals(OMException.ResultCodes.PERMISSION_DENIED, ex.getResult()); + } + + @Test + void adminIsAllowed() throws Exception { + UserGroupInformation admin = + UserGroupInformation.createUserForTesting("admin", new String[] {"admins"}); + DBUpdates updates = getDBUpdates(admin); + assertFalse(updates.getData().isEmpty()); + } + + @Test + void readOnlyAdminIsAllowed() throws Exception { + UserGroupInformation recon = + UserGroupInformation.createUserForTesting(RECON_PRINCIPAL, new String[] {"recon"}); + DBUpdates updates = getDBUpdates(recon); + assertTrue(updates.getData() != null && !updates.getData().isEmpty()); + } +} diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java index f0bcafc8cd1..e294b7453cd 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java @@ -4446,6 +4446,13 @@ public boolean isFilesystemSnapshotEnabled() { public DBUpdates getDBUpdates( DBUpdatesRequest dbUpdatesRequest) throws IOException { + // getDBUpdates returns the raw RocksDB delta of the entire OM metadata DB (all + // volume/bucket/key names, ACLs, block locations, tenant/S3-secret state). It backs + // OM->Recon replication and is not a per-object client read, so restrict it to admins + // and read-only admins (the Recon service principal is expected to be one), consistent + // with the gating already applied to the other whole-system reads such as listOpenFiles + // and getQuotaRepairStatus. + checkGetDBUpdatesPrivilege(); long limitCount = Long.MAX_VALUE; if (dbUpdatesRequest.hasLimitCount()) { limitCount = dbUpdatesRequest.getLimitCount(); @@ -4505,6 +4512,26 @@ private void checkAdminUserPrivilege(String operation) throws IOException { } } + /** + * Authorize a getDBUpdates call, which returns the raw whole-DB metadata delta. + * Allowed for full OM admins and read-only admins (the read-only-admin allow-list is the + * mechanism intended to grant the Recon service principal read access to the metadata feed + * without full admin rights). Only enforced when admin authorization is enabled, matching + * {@link #checkAdminUserPrivilege(String)}. + */ + private void checkGetDBUpdatesPrivilege() throws IOException { + // Skip check if authorization is disabled + if (!isAdminAuthorizationEnabled()) { + return; + } + + final UserGroupInformation ugi = getRemoteUser(); + if (!isAdmin(ugi) && !isReadOnlyAdmin(ugi)) { + throw new OMException("Only Ozone admins and read-only admins are allowed to " + + "access the OM metadata database via getDBUpdates.", PERMISSION_DENIED); + } + } + public boolean isS3Admin(UserGroupInformation callerUgi) { return OzoneAdmins.isS3Admin(callerUgi, s3OzoneAdmins); } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
