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

sammichen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 4ee6c07acf HDDS-9203. Allow generating/revoking S3 secret for other 
users via REST (#5233)
4ee6c07acf is described below

commit 4ee6c07acfdf893df5417858a53bcb2fc4919609
Author: Ivan Zlenko <[email protected]>
AuthorDate: Wed Sep 6 13:43:18 2023 +0500

    HDDS-9203. Allow generating/revoking S3 secret for other users via REST 
(#5233)
---
 hadoop-hdds/docs/content/security/SecuringS3.md    |  4 +-
 hadoop-hdds/docs/content/security/SecuringS3.zh.md |  4 +-
 .../src/main/smoketest/s3/secretgenerate.robot     | 12 +++-
 .../dist/src/main/smoketest/s3/secretrevoke.robot  | 11 +++-
 .../ozone/s3secret/S3SecretGenerateEndpoint.java   | 49 ----------------
 ...dpoint.java => S3SecretManagementEndpoint.java} | 65 ++++++++++++++++++----
 .../hadoop/ozone/s3secret/TestSecretGenerate.java  | 32 ++++++++---
 .../hadoop/ozone/s3secret/TestSecretRevoke.java    | 25 +++++++--
 8 files changed, 121 insertions(+), 81 deletions(-)

diff --git a/hadoop-hdds/docs/content/security/SecuringS3.md 
b/hadoop-hdds/docs/content/security/SecuringS3.md
index 21af373435..e6218b95e9 100644
--- a/hadoop-hdds/docs/content/security/SecuringS3.md
+++ b/hadoop-hdds/docs/content/security/SecuringS3.md
@@ -42,10 +42,10 @@ The user needs to `kinit` first and once they have 
authenticated via kerberos
 ozone s3 getsecret
 ```
 
-* Or by sending request to /secret/generate S3 REST endpoint.
+* Or by sending request to /secret S3 REST endpoint.
 
 ```bash
-curl -X POST --negotiate -u : https://localhost:9879/secret/generate
+curl -X PUT --negotiate -u : https://localhost:9879/secret
 ```
 
 This command will talk to ozone, validate the user via Kerberos and generate
diff --git a/hadoop-hdds/docs/content/security/SecuringS3.zh.md 
b/hadoop-hdds/docs/content/security/SecuringS3.zh.md
index ff27f2de56..218786fd36 100644
--- a/hadoop-hdds/docs/content/security/SecuringS3.zh.md
+++ b/hadoop-hdds/docs/content/security/SecuringS3.zh.md
@@ -36,10 +36,10 @@ icon: cloud
 ozone s3 getsecret
 ```
 
-* 或者通过向 /secret/generate S3 REST 端点发送请求。
+* 或者通过向 /secret S3 REST 端点发送请求。
 
 ```bash
-curl -X POST --negotiate -u : https://localhost:9879/secret/generate
+curl -X PUT --negotiate -u : https://localhost:9879/secret
 ```
 
 这条命令会与 Ozone 进行通信,对用户进行 Kerberos 认证并生成 AWS 凭据,结果会直接打印在屏幕上,你可以将其配置在 _.aws._ 
文件中,这样可以在操作 Ozone S3 桶时自动进行认证。
diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/secretgenerate.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/secretgenerate.robot
index 8224d9ac02..b9f6993f45 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/secretgenerate.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/secretgenerate.robot
@@ -31,7 +31,17 @@ ${ENDPOINT_URL}       http://s3g:9878
 
 S3 Gateway Generate Secret
     Run Keyword if      '${SECURITY_ENABLED}' == 'true'     Kinit HTTP user
-    ${result} =         Execute                             curl -X POST 
--negotiate -u : -v ${ENDPOINT_URL}/secret/generate
+    ${result} =         Execute                             curl -X PUT 
--negotiate -u : -v ${ENDPOINT_URL}/secret
+                        IF   '${SECURITY_ENABLED}' == 'true'
+                            Should contain          ${result}       HTTP/1.1 
200 OK    ignore_case=True
+                            Should Match Regexp     ${result}       
<awsAccessKey>.*</awsAccessKey><awsSecret>.*</awsSecret>
+                        ELSE
+                            Should contain          ${result}       S3 Secret 
endpoint is disabled.
+                        END
+
+S3 Gateway Generate Secret By Username
+    Run Keyword if      '${SECURITY_ENABLED}' == 'true'     Kinit test user    
 testuser     testuser.keytab
+    ${result} =         Execute                             curl -X PUT 
--negotiate -u : -v ${ENDPOINT_URL}/secret/testuser2
                         IF   '${SECURITY_ENABLED}' == 'true'
                             Should contain          ${result}       HTTP/1.1 
200 OK    ignore_case=True
                             Should Match Regexp     ${result}       
<awsAccessKey>.*</awsAccessKey><awsSecret>.*</awsSecret>
diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/secretrevoke.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/secretrevoke.robot
index 4cc21b3b8b..27b4580f41 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/secretrevoke.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/secretrevoke.robot
@@ -32,7 +32,16 @@ ${SECURITY_ENABLED}   true
 
 S3 Gateway Revoke Secret
     Run Keyword if      '${SECURITY_ENABLED}' == 'true'     Kinit HTTP user
-    ${result} =         Execute                             curl -X POST 
--negotiate -u : -v ${ENDPOINT_URL}/secret/revoke
+    ${result} =         Execute                             curl -X DELETE 
--negotiate -u : -v ${ENDPOINT_URL}/secret
+                        IF   '${SECURITY_ENABLED}' == 'true'
+                            Should contain      ${result}       HTTP/1.1 200 
OK    ignore_case=True
+                        ELSE
+                            Should contain      ${result}       S3 Secret 
endpoint is disabled.
+                        END
+
+S3 Gateway Revoke Secret By Username
+    Run Keyword if      '${SECURITY_ENABLED}' == 'true'     Kinit test user    
 testuser     testuser.keytab
+    ${result} =         Execute                             curl -X DELETE 
--negotiate -u : -v ${ENDPOINT_URL}/secret/testuser2
                         IF   '${SECURITY_ENABLED}' == 'true'
                             Should contain      ${result}       HTTP/1.1 200 
OK    ignore_case=True
                         ELSE
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretGenerateEndpoint.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretGenerateEndpoint.java
deleted file mode 100644
index 4fe9fd47fb..0000000000
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretGenerateEndpoint.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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.s3secret;
-
-import org.apache.hadoop.ozone.audit.S3GAction;
-import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
-
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.core.Response;
-import java.io.IOException;
-
-/**
- * Endpoint to generate and return S3 secret.
- */
-@Path("/secret/generate")
-@S3SecretEnabled
-public class S3SecretGenerateEndpoint extends S3SecretEndpointBase {
-  @POST
-  public Response generate() throws IOException {
-    S3SecretResponse s3SecretResponse = new S3SecretResponse();
-    S3SecretValue s3SecretValue = generateS3Secret();
-    s3SecretResponse.setAwsSecret(s3SecretValue.getAwsSecret());
-    s3SecretResponse.setAwsAccessKey(s3SecretValue.getAwsAccessKey());
-    AUDIT.logReadSuccess(buildAuditMessageForSuccess(
-        S3GAction.GENERATE_SECRET, getAuditParameters()));
-    return Response.ok(s3SecretResponse).build();
-  }
-
-  private S3SecretValue generateS3Secret() throws IOException {
-    return getClient().getObjectStore().getS3Secret(userNameFromRequest());
-  }
-}
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretRevokeEndpoint.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretManagementEndpoint.java
similarity index 50%
rename from 
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretRevokeEndpoint.java
rename to 
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretManagementEndpoint.java
index 423790ba92..3c932da57d 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretRevokeEndpoint.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3secret/S3SecretManagementEndpoint.java
@@ -20,31 +20,74 @@ package org.apache.hadoop.ozone.s3secret;
 
 import org.apache.hadoop.ozone.audit.S3GAction;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.ws.rs.POST;
+import javax.annotation.Nullable;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.core.Response;
 import java.io.IOException;
 
 import static javax.ws.rs.core.Response.Status.NOT_FOUND;
 
 /**
- * Revoke secret endpoint.
+ * Endpoint to manage S3 secret.
  */
-@Path("/secret/revoke")
+@Path("/secret")
 @S3SecretEnabled
-public class S3SecretRevokeEndpoint extends S3SecretEndpointBase {
-
+public class S3SecretManagementEndpoint extends S3SecretEndpointBase {
   private static final Logger LOG =
-          LoggerFactory.getLogger(S3SecretRevokeEndpoint.class);
+      LoggerFactory.getLogger(S3SecretManagementEndpoint.class);
+
+  @PUT
+  public Response generate() throws IOException {
+    return generateInternal(null);
+  }
 
+  @PUT
+  @Path("/{username}")
+  public Response generate(@PathParam("username") String username)
+      throws IOException {
+    return generateInternal(username);
+  }
 
-  @POST
+  private Response generateInternal(@Nullable String username)
+      throws IOException {
+    S3SecretResponse s3SecretResponse = new S3SecretResponse();
+    S3SecretValue s3SecretValue = generateS3Secret(username);
+    s3SecretResponse.setAwsSecret(s3SecretValue.getAwsSecret());
+    s3SecretResponse.setAwsAccessKey(s3SecretValue.getAwsAccessKey());
+    AUDIT.logReadSuccess(buildAuditMessageForSuccess(
+        S3GAction.GENERATE_SECRET, getAuditParameters()));
+    return Response.ok(s3SecretResponse).build();
+  }
+
+  private S3SecretValue generateS3Secret(@Nullable String username)
+      throws IOException {
+    String actualUsername = username == null ? userNameFromRequest() : 
username;
+    return getClient().getObjectStore().getS3Secret(actualUsername);
+  }
+
+  @DELETE
   public Response revoke() throws IOException {
+    return revokeInternal(null);
+  }
+
+  @DELETE
+  @Path("/{username}")
+  public Response revoke(@PathParam("username") String username)
+      throws IOException {
+    return revokeInternal(username);
+  }
+
+  private Response revokeInternal(@Nullable String username)
+      throws IOException {
     try {
-      revokeSecret();
+      revokeSecret(username);
       AUDIT.logWriteSuccess(buildAuditMessageForSuccess(
           S3GAction.REVOKE_SECRET, getAuditParameters()));
       return Response.ok().build();
@@ -62,8 +105,8 @@ public class S3SecretRevokeEndpoint extends 
S3SecretEndpointBase {
     }
   }
 
-  private void revokeSecret() throws IOException {
-    getClient().getObjectStore().revokeS3Secret(userNameFromRequest());
+  private void revokeSecret(@Nullable String username) throws IOException {
+    String actualUsername = username == null ? userNameFromRequest() : 
username;
+    getClient().getObjectStore().revokeS3Secret(actualUsername);
   }
-
 }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretGenerate.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretGenerate.java
index 6a8b63e83a..f3c17d5807 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretGenerate.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretGenerate.java
@@ -35,10 +35,11 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
 /**
@@ -47,9 +48,10 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 public class TestSecretGenerate {
   private static final String USER_NAME = "test";
+  private static final String OTHER_USER_NAME = "test2";
   private static final String USER_SECRET = "test_secret";
 
-  private S3SecretGenerateEndpoint endpoint;
+  private S3SecretManagementEndpoint endpoint;
 
   @Mock
   private ClientProtocol proxy;
@@ -62,31 +64,43 @@ public class TestSecretGenerate {
   @Mock
   private Principal principal;
 
+  private static S3SecretValue getS3SecretValue(InvocationOnMock invocation) {
+    Object[] args = invocation.getArguments();
+    return new S3SecretValue((String) args[0], USER_SECRET);
+  }
+
   @BeforeEach
   void setUp() throws IOException {
-    S3SecretValue value = new S3SecretValue(USER_NAME, USER_SECRET);
-    when(proxy.getS3Secret(eq(USER_NAME))).thenReturn(value);
+    when(proxy.getS3Secret(any())).then(TestSecretGenerate::getS3SecretValue);
     OzoneConfiguration conf = new OzoneConfiguration();
     OzoneClient client = new OzoneClientStub(new ObjectStoreStub(conf, proxy));
 
-    when(principal.getName()).thenReturn(USER_NAME);
-    when(securityContext.getUserPrincipal()).thenReturn(principal);
-    when(context.getSecurityContext()).thenReturn(securityContext);
-
     when(uriInfo.getPathParameters()).thenReturn(new MultivaluedHashMap<>());
     when(uriInfo.getQueryParameters()).thenReturn(new MultivaluedHashMap<>());
     when(context.getUriInfo()).thenReturn(uriInfo);
 
-    endpoint = new S3SecretGenerateEndpoint();
+    endpoint = new S3SecretManagementEndpoint();
     endpoint.setClient(client);
     endpoint.setContext(context);
   }
 
   @Test
   void testSecretGenerate() throws IOException {
+    when(principal.getName()).thenReturn(USER_NAME);
+    when(securityContext.getUserPrincipal()).thenReturn(principal);
+    when(context.getSecurityContext()).thenReturn(securityContext);
+
     S3SecretResponse response =
             (S3SecretResponse) endpoint.generate().getEntity();
     assertEquals(USER_SECRET, response.getAwsSecret());
     assertEquals(USER_NAME, response.getAwsAccessKey());
   }
+
+  @Test
+  void testSecretGenerateWithUsername() throws IOException {
+    S3SecretResponse response =
+            (S3SecretResponse) endpoint.generate(OTHER_USER_NAME).getEntity();
+    assertEquals(USER_SECRET, response.getAwsSecret());
+    assertEquals(OTHER_USER_NAME, response.getAwsAccessKey());
+  }
 }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretRevoke.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretRevoke.java
index 8a1f81d132..a319496419 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretRevoke.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3secret/TestSecretRevoke.java
@@ -55,8 +55,9 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 public class TestSecretRevoke {
   private static final String USER_NAME = "test";
+  private static final String OTHER_USER_NAME = "test2";
 
-  private S3SecretRevokeEndpoint endpoint;
+  private S3SecretManagementEndpoint endpoint;
 
   @Mock
   private ObjectStoreStub objectStore;
@@ -73,27 +74,38 @@ public class TestSecretRevoke {
   void setUp() {
     OzoneClient client = new OzoneClientStub(objectStore);
 
-    when(principal.getName()).thenReturn(USER_NAME);
-    when(securityContext.getUserPrincipal()).thenReturn(principal);
-    when(context.getSecurityContext()).thenReturn(securityContext);
-
     when(uriInfo.getPathParameters()).thenReturn(new MultivaluedHashMap<>());
     when(uriInfo.getQueryParameters()).thenReturn(new MultivaluedHashMap<>());
     when(context.getUriInfo()).thenReturn(uriInfo);
 
-    endpoint = new S3SecretRevokeEndpoint();
+    endpoint = new S3SecretManagementEndpoint();
     endpoint.setClient(client);
     endpoint.setContext(context);
   }
 
+  private void mockSecurityContext() {
+    when(principal.getName()).thenReturn(USER_NAME);
+    when(securityContext.getUserPrincipal()).thenReturn(principal);
+    when(context.getSecurityContext()).thenReturn(securityContext);
+  }
+
   @Test
   void testSecretRevoke() throws IOException {
+    mockSecurityContext();
     endpoint.revoke();
     verify(objectStore, times(1)).revokeS3Secret(eq(USER_NAME));
   }
 
+  @Test
+  void testSecretRevokeWithUsername() throws IOException {
+    endpoint.revoke(OTHER_USER_NAME);
+    verify(objectStore, times(1))
+        .revokeS3Secret(eq(OTHER_USER_NAME));
+  }
+
   @Test
   void testSecretSequentialRevokes() throws IOException {
+    mockSecurityContext();
     Response firstResponse = endpoint.revoke();
     assertEquals(OK.getStatusCode(), firstResponse.getStatus());
     doThrow(new OMException(S3_SECRET_NOT_FOUND))
@@ -104,6 +116,7 @@ public class TestSecretRevoke {
 
   @Test
   void testSecretRevokesHandlesException() throws IOException {
+    mockSecurityContext();
     doThrow(new OMException(ACCESS_DENIED))
         .when(objectStore).revokeS3Secret(any());
     Response response = endpoint.revoke();


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

Reply via email to