tpodowd commented on code in PR #9748:
URL: https://github.com/apache/cloudstack/pull/9748#discussion_r1805930584


##########
plugins/storage/object/cloudian/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudianHyperStoreObjectStoreDriverImpl.java:
##########
@@ -0,0 +1,877 @@
+/*
+ * 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.
+ */
+// SPDX-License-Identifier: Apache-2.0
+package org.apache.cloudstack.storage.datastore.driver;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.cloudian.client.CloudianClient;
+import org.apache.cloudstack.cloudian.client.CloudianCredential;
+import org.apache.cloudstack.cloudian.client.CloudianGroup;
+import org.apache.cloudstack.cloudian.client.CloudianUser;
+import org.apache.cloudstack.cloudian.client.CloudianUserBucketUsage;
+import 
org.apache.cloudstack.cloudian.client.CloudianUserBucketUsage.CloudianBucketUsage;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreDetailsDao;
+import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO;
+import org.apache.cloudstack.storage.datastore.util.CloudianHyperStoreUtil;
+import org.apache.cloudstack.storage.object.BaseObjectStoreDriverImpl;
+import org.apache.cloudstack.storage.object.Bucket;
+import org.apache.cloudstack.storage.object.BucketObject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.amazonaws.AmazonClientException;
+import com.amazonaws.services.identitymanagement.AmazonIdentityManagement;
+import com.amazonaws.services.identitymanagement.model.AccessKey;
+import com.amazonaws.services.identitymanagement.model.AccessKeyMetadata;
+import com.amazonaws.services.identitymanagement.model.CreateAccessKeyRequest;
+import com.amazonaws.services.identitymanagement.model.CreateUserRequest;
+import com.amazonaws.services.identitymanagement.model.DeleteAccessKeyRequest;
+import 
com.amazonaws.services.identitymanagement.model.EntityAlreadyExistsException;
+import com.amazonaws.services.identitymanagement.model.ListAccessKeysRequest;
+import com.amazonaws.services.identitymanagement.model.ListAccessKeysResult;
+import com.amazonaws.services.identitymanagement.model.NoSuchEntityException;
+import com.amazonaws.services.identitymanagement.model.PutUserPolicyRequest;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.model.AccessControlList;
+import com.amazonaws.services.s3.model.BucketCrossOriginConfiguration;
+import com.amazonaws.services.s3.model.BucketPolicy;
+import com.amazonaws.services.s3.model.BucketVersioningConfiguration;
+import com.amazonaws.services.s3.model.CORSRule;
+import com.amazonaws.services.s3.model.CreateBucketRequest;
+import com.amazonaws.services.s3.model.SSEAlgorithm;
+import com.amazonaws.services.s3.model.ServerSideEncryptionByDefault;
+import com.amazonaws.services.s3.model.ServerSideEncryptionConfiguration;
+import com.amazonaws.services.s3.model.ServerSideEncryptionRule;
+import 
com.amazonaws.services.s3.model.SetBucketCrossOriginConfigurationRequest;
+import com.amazonaws.services.s3.model.SetBucketEncryptionRequest;
+import com.amazonaws.services.s3.model.SetBucketVersioningConfigurationRequest;
+import com.cloud.agent.api.to.BucketTO;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.storage.BucketVO;
+import com.cloud.storage.dao.BucketDao;
+import com.cloud.domain.Domain;
+import com.cloud.domain.dao.DomainDao;
+import com.cloud.user.Account;
+import com.cloud.user.AccountDetailsDao;
+import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class CloudianHyperStoreObjectStoreDriverImpl extends 
BaseObjectStoreDriverImpl {
+    protected Logger logger = 
LogManager.getLogger(CloudianHyperStoreObjectStoreDriverImpl.class);
+
+    @Inject
+    AccountDao _accountDao;
+
+    @Inject
+    AccountDetailsDao _accountDetailsDao;
+
+    @Inject
+    DomainDao _domainDao;
+
+    @Inject
+    ObjectStoreDao _storeDao;
+
+    @Inject
+    BucketDao _bucketDao;
+
+    @Inject
+    ObjectStoreDetailsDao _storeDetailsDao;
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store) {
+        return null;
+    }
+
+    /**
+     * Get the HyperStore user id for the current account.
+     * @param account the current account
+     * @return the userId based on the CloudStack account uuid.
+     */
+    protected String getHyperStoreUserId(Account account) {
+        return account.getUuid();
+    }
+
+    /**
+     * Get the HyperStore tenant/group id for the current domain.
+     * @param domain the current domain
+     * @return the groupId based on the CloudStack domain uuid
+     */
+    protected String getHyperStoreGroupId(Domain domain) {
+        return domain.getUuid();
+    }
+
+    /**
+     * Create the HyperStore user resources matching this account if it 
doesn't exist.
+     *
+     * The following resources are created for the account:
+     * - HyperStore Group to match the CloudStack Domain UUID
+     * - HyperStore User to match the CloudStack Account UUID
+     * - HyperStore Root User Credentials to manage Account Buckets etc (kept 
private to this plugin)
+     * - HyperStore IAM User with IAM policy granting all S3 actions except 
create/delete buckets.
+     * - HyperStore IAM User Credentials (visible to end user as part of 
Bucket Details)
+     *
+     * @param accountId the CloudStack account
+     * @param storeId the object store.
+     *
+     * @return true if user exists or was created, false if there was some 
issue creating it.
+     * @throws CloudRuntimeException on errors checking if the user exists or 
if the HyperStore user or group is disabled.
+     */
+    @Override
+    public boolean createUser(long accountId, long storeId) {
+        Account account = _accountDao.findById(accountId);
+        Domain domain = _domainDao.findById(account.getDomainId());
+        String hsUserId = getHyperStoreUserId(account);
+        String hsGroupId = getHyperStoreGroupId(domain);
+
+        CloudianClient client = getCloudianClientByStoreId(storeId);
+        logger.debug("Checking if user id={} group id={} exists.", hsGroupId, 
hsUserId);
+        CloudianUser user = client.listUser(hsUserId, hsGroupId);
+        if (user == null) {
+            // Create the group if it doesn't already exist
+            createHSGroup(client, hsGroupId, domain);
+            // Create the user under the group.
+            user = createHSUser(client, hsUserId, hsGroupId, account);
+            if (user == null) {
+                return false; // already logged.
+            }
+        } else if (! user.getActive()) {
+            // Normally this would be true unless an administrator has 
explicitly disabled the user account.
+            String msg = String.format("The User id=%s group id=%s is 
Disabled. Consult your HyperStore Administrator.", hsUserId, hsGroupId);
+            logger.error(msg);
+            throw new CloudRuntimeException(msg);
+        } else {
+            // User exists and is active. We know that the group therefore 
exists but
+            // we should ensure that it is active or it will lead to unknown 
access key errors
+            // which might confuse the administrator. Checking is clearer.
+            CloudianGroup group = client.listGroup(hsGroupId);
+            if (group != null && ! group.getActive()) {
+                String msg = String.format("The group id=%s is Disabled. 
Consult your HyperStore Administrator.", hsGroupId);
+                logger.error(msg);
+                throw new CloudRuntimeException(msg);
+            }
+        }
+
+        // We either created a new account or found an existing one.
+        CloudianCredential credential = createHSCredential(client, hsUserId, 
hsGroupId);
+
+        // Next, ensure we the IAM User Credentials exist. These are available
+        // to the user as part of the bucket details instead of the Root 
credentials.
+        Map<String, String> details = 
_accountDetailsDao.findDetails(accountId);
+        AccessKey iamCredential = createIAMCredentials(storeId, details, 
credential);
+
+        // persist the root and iam credentials in the database and update all 
bucket details.
+        persistCredentials(storeId, accountId, details, credential, 
iamCredential);
+        return true;
+    }
+
+    /**
+     * Create IAM credentials if required.
+     *
+     * When the HyperStore user is first created, this method will create an 
IAM User with an appropriate
+     * permission policy and a set of credentials which will be returned.
+     * After the first run, the IAM resources should already be in place in 
which case we just ensure
+     * the credentials we are using are still available and if not, it tries 
to recreate the IAM resources.
+     *
+     * @param storeId the store
+     * @param details a map of existing account details that we know about 
including any saved IAM credentials.
+     * @param credential the account Root User credentials (to manage the IAM 
resources).
+     * @return an AccessKey object for newly created IAM credentials or null 
if existing credentials were ok
+     *    and nothing was created.
+     */
+    protected AccessKey createIAMCredentials(long storeId, Map<String, String> 
details, CloudianCredential credential) {
+        AmazonIdentityManagement iamClient = getIAMClientByStoreId(storeId, 
credential);
+        final String iamUser = CloudianHyperStoreUtil.IAM_USER_USERNAME;
+
+        // If an accessKeyId is known to us, check IAM still has it.
+        String iamAccessKeyId = 
details.get(CloudianHyperStoreUtil.KEY_IAM_ACCESS_KEY);
+        if (iamAccessKeyId != null) {
+            try {
+                logger.debug("Looking for IAM credential {} for IAM User {}", 
iamAccessKeyId, iamUser);
+                ListAccessKeysResult listAccessKeyResult = 
iamClient.listAccessKeys(new ListAccessKeysRequest().withUserName(iamUser));
+                for (AccessKeyMetadata accessKeyMetadata : 
listAccessKeyResult.getAccessKeyMetadata()) {
+                    if 
(iamAccessKeyId.equals(accessKeyMetadata.getAccessKeyId())) {
+                        return null;  // The IAM AccessKeyId still exists (as 
expected). return null.
+                    }
+                    // Usually, there will only be 1 credential that we 
manage, but an error persisting
+                    // credentials might leave an un-managed credential which 
we can just delete. It is better
+                    // to delete as otherwise, we may hit a max credential 
limit for this IAM user.
+                    DeleteAccessKeyRequest deleteAccessKeyRequest = new 
DeleteAccessKeyRequest();
+                    deleteAccessKeyRequest.setUserName(iamUser);
+                    
deleteAccessKeyRequest.setAccessKeyId(accessKeyMetadata.getAccessKeyId());
+                    logger.info("Deleting un-managed IAM AccessKeyId {} for 
IAM User {}", accessKeyMetadata.getAccessKeyId(), iamUser);
+                    iamClient.deleteAccessKey(deleteAccessKeyRequest);
+                }
+            } catch (NoSuchEntityException e) {
+                // No IAM User. Ignore and fix this below.
+            }
+        }
+
+        // If we get here, a usable credential does not yet exist so create it.
+        // Before creating it, we also need to ensure the IAM User that will 
own it exists.
+        boolean createdUser = false;
+        try {
+            iamClient.createUser(new CreateUserRequest(iamUser));
+            logger.info("Created IAM user {} for account", iamUser);
+            createdUser = true;
+        } catch (EntityAlreadyExistsException e) {
+            // User already exists. Ignore and continue.
+        }
+
+        // Always Add or Update the IAM policy
+        iamClient.putUserPolicy(new PutUserPolicyRequest(iamUser, 
CloudianHyperStoreUtil.IAM_USER_POLICY_NAME, 
CloudianHyperStoreUtil.IAM_USER_POLICY));
+
+        if (! createdUser && iamAccessKeyId == null) {
+            // User already exists but we never saved any access key before. 
We should try clean up
+            logger.debug("Looking for any un-managed IAM credentials for IAM 
User {}", iamUser);
+            ListAccessKeysResult listRes = iamClient.listAccessKeys(new 
ListAccessKeysRequest().withUserName(iamUser));
+            for (AccessKeyMetadata accessKeyMetadata : 
listRes.getAccessKeyMetadata()) {
+                DeleteAccessKeyRequest deleteAccessKeyRequest = new 
DeleteAccessKeyRequest();
+                deleteAccessKeyRequest.setUserName(iamUser);
+                
deleteAccessKeyRequest.setAccessKeyId(accessKeyMetadata.getAccessKeyId());
+                logger.info("Deleting un-managed IAM AccessKeyId {} for IAM 
User {}", accessKeyMetadata.getAccessKeyId(), iamUser);
+                iamClient.deleteAccessKey(deleteAccessKeyRequest);
+            }

Review Comment:
   Thanks yes. Same answer really. The plugin should manage this for the 
account that is created specifically for CloudStack.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to