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]