diff --git a/adminapi/src/main/java/io/minio/admin/AddServiceAccountResp.java b/adminapi/src/main/java/io/minio/admin/AddServiceAccountResp.java
new file mode 100644
index 000000000..c48c6f606
--- /dev/null
+++ b/adminapi/src/main/java/io/minio/admin/AddServiceAccountResp.java
@@ -0,0 +1,38 @@
+/*
+ * MinIO Java SDK for Amazon S3 Compatible Cloud Storage,
+ * (C) 2021 MinIO, Inc.
+ *
+ * Licensed 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 io.minio.admin;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.minio.credentials.Credentials;
+
+/**
+ * add service account response.
+ *
+ *
* @see user-commands.go
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AddServiceAccountResp {
+ @JsonProperty("credentials")
+ private Credentials credentials;
+
+ public Credentials credentials() {
+ return credentials;
+ }
+}
diff --git a/adminapi/src/main/java/io/minio/admin/GetServiceAccountInfoResp.java b/adminapi/src/main/java/io/minio/admin/GetServiceAccountInfoResp.java
new file mode 100644
index 000000000..1694bf113
--- /dev/null
+++ b/adminapi/src/main/java/io/minio/admin/GetServiceAccountInfoResp.java
@@ -0,0 +1,75 @@
+/*
+ * MinIO Java SDK for Amazon S3 Compatible Cloud Storage,
+ * (C) 2021 MinIO, Inc.
+ *
+ * Licensed 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 io.minio.admin;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * service account info.
+ *
+ *
* @see user-commands.go
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class GetServiceAccountInfoResp {
+ @JsonProperty("parentUser")
+ private String parentUser;
+
+ @JsonProperty("accountStatus")
+ private String accountStatus;
+
+ @JsonProperty("impliedPolicy")
+ private boolean impliedPolicy;
+
+ @JsonProperty("policy")
+ private String policy;
+
+ @JsonProperty("name")
+ private String name;
+
+ @JsonProperty("description")
+ private String description;
+
+ @JsonProperty("expiration")
+ private String expiration;
+
+ public String parentUser() {
+ return parentUser;
+ }
+
+ public String accountStatus() {
+ return accountStatus;
+ }
+
+ public boolean impliedPolicy() {
+ return impliedPolicy;
+ }
+
+ public String description() {
+ return description;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public String expiration() {
+ return expiration;
+ }
+}
diff --git a/adminapi/src/main/java/io/minio/admin/ListServiceAccountResp.java b/adminapi/src/main/java/io/minio/admin/ListServiceAccountResp.java
new file mode 100644
index 000000000..addc940f8
--- /dev/null
+++ b/adminapi/src/main/java/io/minio/admin/ListServiceAccountResp.java
@@ -0,0 +1,49 @@
+/*
+ * MinIO Java SDK for Amazon S3 Compatible Cloud Storage,
+ * (C) 2021 MinIO, Inc.
+ *
+ * Licensed 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 io.minio.admin;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+
+/** list service account response. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ListServiceAccountResp {
+ @JsonProperty("accounts")
+ private List accounts;
+
+ public List accounts() {
+ return accounts;
+ }
+
+ public static class ListServiceAccountInfo {
+ @JsonProperty("accessKey")
+ private String accessKey;
+
+ @JsonProperty("expiration")
+ private String expiration;
+
+ public String expiration() {
+ return expiration;
+ }
+
+ public String accessKey() {
+ return accessKey;
+ }
+ }
+}
diff --git a/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java b/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java
index ea4bc8adf..7f8e9f236 100644
--- a/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java
+++ b/adminapi/src/main/java/io/minio/admin/MinioAdminClient.java
@@ -53,6 +53,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import okhttp3.HttpUrl;
@@ -81,7 +82,12 @@ private enum Command {
ADD_UPDATE_REMOVE_GROUP("update-group-members"),
GROUP_INFO("group"),
LIST_GROUPS("groups"),
- INFO("info");
+ INFO("info"),
+ ADD_SERVICE_ACCOUNT("add-service-account"),
+ UPDATE_SERVICE_ACCOUNT("update-service-account"),
+ LIST_SERVICE_ACCOUNTS("list-service-accounts"),
+ DELETE_SERVICE_ACCOUNT("delete-service-account"),
+ INFO_SERVICE_ACCOUNT("info-service-account");
private final String value;
private Command(String value) {
@@ -97,6 +103,9 @@ public String toString() {
private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.parse("application/octet-stream");
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ private static final Pattern SERVICE_ACCOUNT_NAME_REGEX =
+ Pattern.compile("^(?!-)(?!_)[a-z_\\d-]{1,31}(?} - List of all users.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on MinIO REST operation.
@@ -338,7 +347,7 @@ public void addUpdateGroup(
* Obtains group info for a specified MinIO group.
*
* @param group Group name.
- * @return group info for the specified group.
+ * @return {@link GroupInfo} - group info for the specified group.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on MinIO REST operation.
@@ -355,7 +364,7 @@ public GroupInfo getGroupInfo(String group)
/**
* Obtains a list of all MinIO groups.
*
- * @return List of all groups.
+ * @return {@link List} - List of all groups.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on MinIO REST operation.
@@ -542,7 +551,8 @@ public void setPolicy(
/**
* Lists all configured canned policies.
*
- * @return Map of policies, keyed by their name, with their actual policy as their value.
+ * @return {@link Map} - Map of policies, keyed by their name, with their actual
+ * policy as their value.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on MinIO REST operation.
@@ -587,7 +597,7 @@ public void removeCannedPolicy(@Nonnull String name)
/**
* Get server/cluster data usage info
*
- * @return DataUsageInfo object
+ * @return {@link DataUsageInfo} - DataUsageInfo object
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on MinIO REST operation.
@@ -602,7 +612,7 @@ public DataUsageInfo getDataUsageInfo()
/**
* Obtains admin info for the Minio server.
*
- * @return admin info for the Minio server.
+ * @return {@link Message} - admin info for the Minio server.
* @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException thrown to indicate I/O error on MinIO REST operation.
@@ -613,6 +623,219 @@ public Message getServerInfo() throws IOException, NoSuchAlgorithmException, Inv
}
}
+ /**
+ * Creates a new service account belonging to the user sending.
+ *
+ * @param accessKey Access key.
+ * @param secretKey Secret key.
+ * @param targetUser Target user.
+ * @param policy Policy as map .
+ * @param name Service account name.
+ * @param description Description for this access key.
+ * @param expiration Expiry time.
+ * @return {@link Credentials} - Service account info for the specified accessKey.
+ * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
+ * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
+ * @throws IOException thrown to indicate I/O error on MinIO REST operation.
+ * @throws InvalidCipherTextException thrown to indicate data cannot be encrypted/decrypted.
+ */
+ public Credentials addServiceAccount(
+ @Nonnull String accessKey,
+ @Nonnull String secretKey,
+ @Nullable String targetUser,
+ @Nullable Map policy,
+ @Nullable String name,
+ @Nullable String description,
+ @Nullable ZonedDateTime expiration)
+ throws NoSuchAlgorithmException, InvalidKeyException, IOException,
+ InvalidCipherTextException {
+ if (accessKey == null || accessKey.isEmpty()) {
+ throw new IllegalArgumentException("access key must be provided");
+ }
+ if (secretKey == null || secretKey.isEmpty()) {
+ throw new IllegalArgumentException("secret key must be provided");
+ }
+ if (name != null && !SERVICE_ACCOUNT_NAME_REGEX.matcher(name).find()) {
+ throw new IllegalArgumentException(
+ "name must contain non-empty alphanumeric, underscore and hyphen characters not longer than 32 characters");
+ }
+ if (description != null && description.length() > 256) {
+ throw new IllegalArgumentException("description must be at most 256 characters long");
+ }
+
+ Map serviceAccount = new HashMap<>();
+ serviceAccount.put("accessKey", accessKey);
+ serviceAccount.put("secretKey", secretKey);
+ if (targetUser != null && !targetUser.isEmpty()) {
+ serviceAccount.put("targetUser", targetUser);
+ }
+ if (policy != null && !policy.isEmpty()) {
+ serviceAccount.put("policy", policy);
+ }
+ if (name != null && !name.isEmpty()) {
+ serviceAccount.put("name", name);
+ }
+ if (description != null && !description.isEmpty()) {
+ serviceAccount.put("description", description);
+ }
+ if (expiration != null) {
+ serviceAccount.put("expiration", expiration.format(Time.EXPIRATION_DATE_FORMAT));
+ }
+
+ Credentials creds = getCredentials();
+ try (Response response =
+ execute(
+ Method.PUT,
+ Command.ADD_SERVICE_ACCOUNT,
+ null,
+ Crypto.encrypt(creds.secretKey(), OBJECT_MAPPER.writeValueAsBytes(serviceAccount)))) {
+ byte[] jsonData = Crypto.decrypt(creds.secretKey(), response.body().bytes());
+ return OBJECT_MAPPER.readValue(jsonData, AddServiceAccountResp.class).credentials();
+ }
+ }
+
+ /**
+ * Edit an existing service account.
+ *
+ * @param accessKey Access key.
+ * @param newSecretKey New secret key.
+ * @param newPolicy New policy as JSON string .
+ * @param newStatus New service account status.
+ * @param newName New service account name.
+ * @param newDescription New description.
+ * @param newExpiration New expiry time.
+ * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
+ * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
+ * @throws IOException thrown to indicate I/O error on MinIO REST operation.
+ * @throws InvalidCipherTextException thrown to indicate data cannot be encrypted/decrypted.
+ */
+ public void updateServiceAccount(
+ @Nonnull String accessKey,
+ @Nullable String newSecretKey,
+ @Nullable Map newPolicy,
+ @Nullable boolean newStatus,
+ @Nullable String newName,
+ @Nullable String newDescription,
+ @Nullable ZonedDateTime newExpiration)
+ throws NoSuchAlgorithmException, InvalidKeyException, IOException,
+ InvalidCipherTextException {
+ if (accessKey == null || accessKey.isEmpty()) {
+ throw new IllegalArgumentException("access key must be provided");
+ }
+ if (newName != null && !SERVICE_ACCOUNT_NAME_REGEX.matcher(newName).find()) {
+ throw new IllegalArgumentException(
+ "new name must contain non-empty alphanumeric, underscore and hyphen characters not longer than 32 characters");
+ }
+ if (newDescription != null && newDescription.length() > 256) {
+ throw new IllegalArgumentException("new description must be at most 256 characters long");
+ }
+
+ Map serviceAccount = new HashMap<>();
+ if (newSecretKey != null && !newSecretKey.isEmpty()) {
+ serviceAccount.put("newSecretKey", newSecretKey);
+ }
+ if (newPolicy != null && !newPolicy.isEmpty()) {
+ serviceAccount.put("newPolicy", newPolicy);
+ }
+ serviceAccount.put("newStatus", newStatus ? "on" : "off");
+ if (newName != null && !newName.isEmpty()) {
+ serviceAccount.put("newName", newName);
+ }
+ if (newDescription != null && !newDescription.isEmpty()) {
+ serviceAccount.put("newDescription", newDescription);
+ }
+ if (newExpiration != null) {
+ serviceAccount.put("newExpiration", newExpiration.format(Time.EXPIRATION_DATE_FORMAT));
+ }
+
+ Credentials creds = getCredentials();
+ try (Response response =
+ execute(
+ Method.POST,
+ Command.UPDATE_SERVICE_ACCOUNT,
+ ImmutableMultimap.of("accessKey", accessKey),
+ Crypto.encrypt(creds.secretKey(), OBJECT_MAPPER.writeValueAsBytes(serviceAccount)))) {}
+ }
+
+ /**
+ * Deletes a service account by it's access key
+ *
+ * @param accessKey Access Key.
+ * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
+ * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
+ * @throws IOException thrown to indicate I/O error on MinIO REST operation.
+ */
+ public void deleteServiceAccount(@Nonnull String accessKey)
+ throws NoSuchAlgorithmException, InvalidKeyException, IOException {
+ if (accessKey == null || accessKey.isEmpty()) {
+ throw new IllegalArgumentException("access key must be provided");
+ }
+
+ try (Response response =
+ execute(
+ Method.DELETE,
+ Command.DELETE_SERVICE_ACCOUNT,
+ ImmutableMultimap.of("accessKey", accessKey),
+ null)) {}
+ }
+
+ /**
+ * Obtains a list of minio service account by user name.
+ *
+ * @param username user name.
+ * @return {@link ListServiceAccountResp} - List of minio service account.
+ * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
+ * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
+ * @throws IOException thrown to indicate I/O error on MinIO REST operation.
+ * @throws InvalidCipherTextException thrown to indicate data cannot be encrypted/decrypted.
+ */
+ public ListServiceAccountResp listServiceAccount(@Nonnull String username)
+ throws NoSuchAlgorithmException, InvalidKeyException, IOException,
+ InvalidCipherTextException {
+ if (username == null || username.isEmpty()) {
+ throw new IllegalArgumentException("user name must be provided");
+ }
+
+ try (Response response =
+ execute(
+ Method.GET,
+ Command.LIST_SERVICE_ACCOUNTS,
+ ImmutableMultimap.of("user", username),
+ null)) {
+ Credentials creds = getCredentials();
+ byte[] jsonData = Crypto.decrypt(creds.secretKey(), response.body().bytes());
+ return OBJECT_MAPPER.readValue(jsonData, ListServiceAccountResp.class);
+ }
+ }
+
+ /**
+ * Obtains service account info for a specified MinIO user.
+ *
+ * @param accessKey Access Key.
+ * @return {@link GetServiceAccountInfoResp} - Service account info for the specified accessKey.
+ * @throws NoSuchAlgorithmException thrown to indicate missing of MD5 or SHA-256 digest library.
+ * @throws InvalidKeyException thrown to indicate missing of HMAC SHA-256 library.
+ * @throws IOException thrown to indicate I/O error on MinIO REST operation.
+ * @throws InvalidCipherTextException thrown to indicate data cannot be encrypted/decrypted.
+ */
+ public GetServiceAccountInfoResp getServiceAccountInfo(@Nonnull String accessKey)
+ throws NoSuchAlgorithmException, InvalidKeyException, IOException,
+ InvalidCipherTextException {
+ if (accessKey == null || accessKey.isEmpty()) {
+ throw new IllegalArgumentException("access key must be provided");
+ }
+ try (Response response =
+ execute(
+ Method.GET,
+ Command.INFO_SERVICE_ACCOUNT,
+ ImmutableMultimap.of("accessKey", accessKey),
+ null)) {
+ Credentials creds = getCredentials();
+ byte[] jsonData = Crypto.decrypt(creds.secretKey(), response.body().bytes());
+ return OBJECT_MAPPER.readValue(jsonData, GetServiceAccountInfoResp.class);
+ }
+ }
+
/**
* Sets HTTP connect, write and read timeouts. A value of 0 means no timeout, otherwise values
* must be between 1 and Integer.MAX_VALUE when converted to milliseconds.
diff --git a/api/src/main/java/io/minio/credentials/Credentials.java b/api/src/main/java/io/minio/credentials/Credentials.java
index e9b589765..fb28da816 100644
--- a/api/src/main/java/io/minio/credentials/Credentials.java
+++ b/api/src/main/java/io/minio/credentials/Credentials.java
@@ -16,6 +16,8 @@
package io.minio.credentials;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
import io.minio.messages.ResponseDate;
import java.time.Duration;
import java.time.ZonedDateTime;
@@ -27,24 +29,29 @@
/** Object representation of credentials access key, secret key and session token. */
@Root(name = "Credentials", strict = false)
+@JsonIgnoreProperties(ignoreUnknown = true)
public class Credentials {
@Element(name = "AccessKeyId")
+ @JsonProperty("accessKey")
private final String accessKey;
@Element(name = "SecretAccessKey")
+ @JsonProperty("secretKey")
private final String secretKey;
@Element(name = "SessionToken")
+ @JsonProperty("sessionToken")
private final String sessionToken;
@Element(name = "Expiration")
+ @JsonProperty("expiration")
private final ResponseDate expiration;
public Credentials(
- @Nonnull @Element(name = "AccessKeyId") String accessKey,
- @Nonnull @Element(name = "SecretAccessKey") String secretKey,
- @Nullable @Element(name = "SessionToken") String sessionToken,
- @Nullable @Element(name = "Expiration") ResponseDate expiration) {
+ @Nonnull @Element(name = "AccessKeyId") @JsonProperty("accessKey") String accessKey,
+ @Nonnull @Element(name = "SecretAccessKey") @JsonProperty("secretKey") String secretKey,
+ @Nullable @Element(name = "SessionToken") @JsonProperty("sessionToken") String sessionToken,
+ @Nullable @Element(name = "Expiration") @JsonProperty("expiration") ResponseDate expiration) {
this.accessKey = Objects.requireNonNull(accessKey, "AccessKey must not be null");
this.secretKey = Objects.requireNonNull(secretKey, "SecretKey must not be null");
if (accessKey.isEmpty() || secretKey.isEmpty()) {