All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aliyuncs.kms.secretsmanager.client.SecretCacheClient Maven / Gradle / Ivy

package com.aliyuncs.kms.secretsmanager.client;

import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.kms.model.v20160120.GetSecretValueRequest;
import com.aliyuncs.kms.model.v20160120.GetSecretValueResponse;
import com.aliyuncs.kms.secretsmanager.client.cache.CacheSecretStoreStrategy;
import com.aliyuncs.kms.secretsmanager.client.cache.SecretCacheHook;
import com.aliyuncs.kms.secretsmanager.client.exception.CacheSecretException;
import com.aliyuncs.kms.secretsmanager.client.model.CacheSecretInfo;
import com.aliyuncs.kms.secretsmanager.client.model.SecretInfo;
import com.aliyuncs.kms.secretsmanager.client.service.RefreshSecretStrategy;
import com.aliyuncs.kms.secretsmanager.client.service.SecretManagerClient;
import com.aliyuncs.kms.secretsmanager.client.utils.BackoffUtils;
import com.aliyuncs.kms.secretsmanager.client.utils.ByteBufferUtils;
import com.aliyuncs.kms.secretsmanager.client.utils.CacheClientConstant;
import com.aliyuncs.kms.secretsmanager.client.utils.CommonLogger;
import com.aliyuncs.utils.StringUtils;

import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;

public class SecretCacheClient implements Closeable {

    /**
     * 默认TTL时间
     */
    private static final long DEFAULT_TTL = 60 * 60 * 1000;

    private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(5);
    private final Map scheduledFutureMap = new ConcurrentHashMap<>();
    private final Map nextExecuteTimeMap = new ConcurrentHashMap<>();

    /**
     * 凭据Version Stage
     */
    protected String stage = CacheClientConstant.STAGE_ACS_CURRENT;

    /**
     * secret value解析TTL字段名称
     */
    protected String jsonTTLPropertyName = "ttl";

    protected SecretManagerClient secretClient;
    protected CacheSecretStoreStrategy cacheSecretStoreStrategy;
    protected RefreshSecretStrategy refreshSecretStrategy;
    protected SecretCacheHook cacheHook;
    protected Map secretTTLMap = new HashMap<>();

    public SecretCacheClient() {

    }

    /**
     * 根据凭据名称获取secretInfo信息
     *
     * @param secretName 指定的凭据名称
     * @return secretInfo信息
     */
    public SecretInfo getSecretInfo(final String secretName) throws CacheSecretException {
        if (StringUtils.isEmpty(secretName)) {
            throw new IllegalArgumentException("the argument[secretName] must not be null");
        }
        CacheSecretInfo cacheSecretInfo = this.cacheSecretStoreStrategy.getCacheSecretInfo(secretName);
        if (checkCacheSecretInfoIsValid(cacheSecretInfo)) {
            return cacheHook.get(cacheSecretInfo);
        } else {
            synchronized (secretName.intern()) {
                cacheSecretInfo = this.cacheSecretStoreStrategy.getCacheSecretInfo(secretName);
                if (checkCacheSecretInfoIsValid(cacheSecretInfo)) {
                    return cacheHook.get(cacheSecretInfo);
                } else {
                    SecretInfo secretInfo = getSecretValue(secretName);
                    storeAndRefresh(secretName, secretInfo);
                    return cacheHook.put(secretInfo) == null ? null : cacheHook.put(secretInfo).getSecretInfo();
                }
            }
        }
    }

    /**
     * 根据凭据名称获取凭据存储值文本信息
     *
     * @param secretName 指定的凭据名称
     * @return 凭据存储值文本信息
     */
    public String getStringValue(final String secretName) throws CacheSecretException {
        SecretInfo secretInfo = getSecretInfo(secretName);
        if (secretInfo == null) {
            return null;
        }
        if (!CacheClientConstant.TEXT_DATA_TYPE.equals(secretInfo.getSecretDataType())) {
            throw new IllegalArgumentException(String.format("the secret named[%s] do not support text value", secretName));
        }
        return secretInfo.getSecretValue();
    }

    /**
     * 根据凭据名称获取凭据存储的二进制信息
     *
     * @param secretName 指定的凭据名称
     * @return 凭据存储值二进制信息
     */
    public ByteBuffer getBinaryValue(final String secretName) throws CacheSecretException {
        SecretInfo secretInfo = getSecretInfo(secretName);
        if (secretInfo == null) {
            return null;
        }
        if (!CacheClientConstant.BINARY_DATA_TYPE.equals(secretInfo.getSecretDataType())) {
            throw new IllegalArgumentException(String.format("the secret named[%s] do not support binary value", secretName));
        }
        return ByteBufferUtils.convertStringToByte(secretInfo.getSecretValue());
    }

    /**
     * 强制刷新指定的凭据名称
     *
     * @param secretName 指定的凭据名称
     * @return 刷新是否成功
     * @throws InterruptedException
     */
    public boolean refreshNow(final String secretName) throws InterruptedException {
        if (StringUtils.isEmpty(secretName)) {
            throw new IllegalArgumentException("the argument[secretName] must not be null");
        }
        return refreshNow(secretName, null);
    }

    private boolean refreshNow(final String secretName, SecretInfo secretInfo) throws InterruptedException {
        boolean executeResult = true;
        synchronized (secretName.intern()) {
            try {
                refresh(secretName, secretInfo);
            } catch (Throwable e) {
                CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("action:refresh", e);
                executeResult = false;
            }
            try {
                removeRefreshTask(secretName);
            } catch (Throwable e) {
                CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("action:removeRefreshTask", e);
                executeResult = false;
            }
            try {
                addRefreshTask(secretName, new RefreshSecretTask(secretName));
            } catch (Throwable e) {
                CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("action:addRefreshTask", e);
                executeResult = false;
            }
        }
        return executeResult;
    }

    protected void init() throws CacheSecretException {
        secretClient.init();
        cacheSecretStoreStrategy.init();
        refreshSecretStrategy.init();
        cacheHook.init();
        for (String secretName : secretTTLMap.keySet()) {
            SecretInfo secretInfo = null;
            try {
                secretInfo = getSecretValue(secretName);
            } catch (CacheSecretException e) {
                CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("action:initSecretCacheClient", e);
                if (judgeSkipRefreshException(e)) {
                    throw e;
                }
            }
            storeAndRefresh(secretName, secretInfo);
        }
        new Thread(new MonitorRefreshSecretTask()).start();
        CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).infof("secretCacheClient init success");
    }

    private boolean judgeCacheExpire(final CacheSecretInfo cacheSecretInfo) {
        long ttl = refreshSecretStrategy.parseTTL(cacheSecretInfo.getSecretInfo());
        if (ttl <= 0) {
            ttl = secretTTLMap.getOrDefault(cacheSecretInfo.getSecretInfo().getSecretName(), DEFAULT_TTL);
        }
        return System.currentTimeMillis() - cacheSecretInfo.getRefreshTimestamp() > ttl;
    }

    private SecretInfo getSecretValue(final String secretName) throws CacheSecretException {
        GetSecretValueRequest request = new GetSecretValueRequest();
        request.setSecretName(secretName);
        request.setVersionStage(stage);
        request.setFetchExtendedConfig(true);
        GetSecretValueResponse resp;
        try {
            resp = secretClient.getSecretValue(request);
        } catch (ClientException e) {
            CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("action:getSecretValue", e);
            if (!BackoffUtils.judgeNeedRecoveryException(e)) {
                throw new CacheSecretException(e);
            }
            try {
                SecretInfo secretInfo = cacheHook.recoveryGetSecret(secretName);
                if (secretInfo != null) {
                    return secretInfo;
                } else {
                    throw e;
                }
            } catch (ClientException ce) {
                CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("action:getSecretValue", e);
                throw new CacheSecretException(e);
            }
        }
        return new SecretInfo(resp.getSecretName(), resp.getVersionId(), resp.getSecretData(), resp.getSecretDataType(), resp.getCreateTime(), resp.getSecretType(), resp.getAutomaticRotation(), resp.getExtendedConfig(), resp.getRotationInterval(), resp.getNextRotationDate());

    }

    private void storeAndRefresh(final String secretName, final SecretInfo secretInfo) throws CacheSecretException {
        try {
            refreshNow(secretName, secretInfo);
        } catch (InterruptedException ignore) {
            // 此异常忽略不阻碍用户流程
        }
    }

    private void refresh(String secretName, SecretInfo secretInfo) throws CacheSecretException {
        if (secretInfo == null) {
            secretInfo = getSecretValue(secretName);
        }
        CacheSecretInfo cacheSecretInfo = cacheHook.put(secretInfo);
        if (cacheSecretInfo != null) {
            cacheSecretStoreStrategy.storeSecret(cacheSecretInfo);
        }
        CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).infof("secretName:{} refresh success", secretName);
    }

    private void addRefreshTask(String secretName, Runnable runnable) throws CacheSecretException {
        CacheSecretInfo cacheSecretInfo = cacheSecretStoreStrategy.getCacheSecretInfo(secretName);
        long executeTime = refreshSecretStrategy.parseNextExecuteTime(cacheSecretInfo);
        if (executeTime <= 0) {
            long refreshTimestamp = cacheSecretInfo.getRefreshTimestamp();
            executeTime = refreshSecretStrategy.getNextExecuteTime(secretName, secretTTLMap.getOrDefault(secretName, DEFAULT_TTL), refreshTimestamp);
            executeTime = Math.max(executeTime, System.currentTimeMillis());
        }
        nextExecuteTimeMap.put(secretName, executeTime);
        ScheduledFuture schedule = scheduledThreadPoolExecutor.schedule(runnable, executeTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        scheduledFutureMap.put(secretName, schedule);
        CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).infof("secretName:{} addRefreshTask success", secretName);
    }

    private void removeRefreshTask(String secretName) throws InterruptedException {
        if (scheduledFutureMap.get(secretName) != null) {
            scheduledThreadPoolExecutor.remove((RunnableScheduledFuture) scheduledFutureMap.get(secretName));
        }
    }

    private boolean checkCacheSecretInfoIsValid(CacheSecretInfo cacheSecretInfo) {
        if (cacheSecretInfo != null && !judgeCacheExpire(cacheSecretInfo)) {
            return checkSecretValueIsEmpty(cacheSecretInfo);
        }
        return false;
    }

    private boolean checkSecretValueIsEmpty(CacheSecretInfo cacheSecretInfo) {
        if (cacheSecretInfo != null) {
            SecretInfo secretInfo = cacheSecretInfo.getSecretInfo();
            if (secretInfo != null && !StringUtils.isEmpty(secretInfo.getSecretValue())) {
                return true;
            }
        }
        return false;
    }

    private boolean judgeServerException(ClientException e) {
        return BackoffUtils.judgeNeedBackoff(e);
    }

    private boolean judgeSkipRefreshException(CacheSecretException e) {
        return e.getCause() instanceof ClientException && !judgeServerException((ClientException) e.getCause())
                && !(CacheClientConstant.CLIENT_EXCEPTION_ERROR_CODE_FORBIDDEN_IN_DEBT_OVER_DUE.equals(((ClientException) e.getCause()).getErrCode()) || CacheClientConstant.CLIENT_EXCEPTION_ERROR_CODE_FORBIDDEN_IN_DEBT.equals(((ClientException) e.getCause()).getErrCode()));
    }

    @Override
    public void close() throws IOException {
        if (cacheSecretStoreStrategy != null) {
            cacheSecretStoreStrategy.close();
        }
        if (refreshSecretStrategy != null) {
            refreshSecretStrategy.close();
        }
        if (secretClient != null) {
            secretClient.close();
        }
        if (cacheHook != null) {
            cacheHook.close();
        }
        if (!scheduledThreadPoolExecutor.isShutdown()) {
            scheduledThreadPoolExecutor.shutdownNow();
        }
    }

    class RefreshSecretTask implements Runnable {
        private final String secretName;

        public RefreshSecretTask(String secretName) {
            this.secretName = secretName;
        }

        @Override
        public void run() {
            try {
                refresh(secretName, null);
            } catch (Throwable e) {
                CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("action:refreshSecretTask", e);
            }
            try {
                addRefreshTask(secretName, this);
            } catch (Throwable e) {
                CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("action:addRefreshTask", e);
            }
        }
    }

    class MonitorRefreshSecretTask implements Runnable {
        @Override
        public void run() {
            while (true) {
                Set secretNames = nextExecuteTimeMap.keySet();
                if (secretNames != null && !secretNames.isEmpty()) {
                    for (String secretName : secretNames) {
                        try {
                            CacheSecretInfo cacheSecretInfo = cacheSecretStoreStrategy.getCacheSecretInfo(secretName);
                            if (cacheSecretInfo != null) {
                                Long nextExecuteTime = nextExecuteTimeMap.get(secretName);
                                if (System.currentTimeMillis() > nextExecuteTime + CacheClientConstant.REQUEST_WAITING_TIME) {
                                    CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).warnf("MonitorRefreshSecretTask secret is expired, secretName={}", secretName);
                                    try {
                                        refreshNow(secretName);
                                    } catch (Throwable e) {
                                        CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("MonitorRefreshSecretTask refreshNow error, secretName={}", secretName, e);
                                    }
                                }
                            } else {
                                CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).warnf("MonitorRefreshSecretTask can not get cache secret, secretName={}", secretName);
                                try {
                                    refreshNow(secretName);
                                } catch (Throwable e) {
                                    CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("MonitorRefreshSecretTask refreshNow error, secretName={}", secretName, e);
                                }
                            }
                        } catch (Throwable e) {
                            CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("MonitorRefreshSecretTask error, secretName={}", secretName, e);
                        }
                    }
                }
                try {
                    Thread.sleep(CacheClientConstant.MONITOR_INTERVAL);
                } catch (Throwable e) {
                    CommonLogger.getCommonLogger(CacheClientConstant.MODE_NAME).errorf("MonitorRefreshSecretTask sleep error", e);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy