| /* |
| * Copyright 2020 The gRPC Authors |
| * |
| * 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.grpc.xds.internal.certprovider; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.util.concurrent.ThreadFactoryBuilder; |
| import io.grpc.internal.BackoffPolicy; |
| import io.grpc.internal.ExponentialBackoffPolicy; |
| import io.grpc.internal.TimeProvider; |
| import io.grpc.xds.internal.sts.StsCredentials; |
| import java.util.Map; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Provider of {@link CertificateProvider}s. Implemented by the implementer of the plugin. We may |
| * move this out of the internal package and make this an official API in the future. |
| */ |
| final class MeshCaCertificateProviderProvider implements CertificateProviderProvider { |
| |
| private static final String MESHCA_URL_KEY = "meshCaUrl"; |
| private static final String RPC_TIMEOUT_SECONDS_KEY = "rpcTimeoutSeconds"; |
| private static final String GKECLUSTER_URL_KEY = "gkeClusterUrl"; |
| private static final String CERT_VALIDITY_SECONDS_KEY = "certValiditySeconds"; |
| private static final String RENEWAL_GRACE_PERIOD_SECONDS_KEY = "renewalGracePeriodSeconds"; |
| private static final String KEY_ALGO_KEY = "keyAlgo"; // aka keyType |
| private static final String KEY_SIZE_KEY = "keySize"; |
| private static final String SIGNATURE_ALGO_KEY = "signatureAlgo"; |
| private static final String MAX_RETRY_ATTEMPTS_KEY = "maxRetryAttempts"; |
| private static final String STS_URL_KEY = "stsUrl"; |
| private static final String GKE_SA_JWT_LOCATION_KEY = "gkeSaJwtLocation"; |
| |
| @VisibleForTesting static final String MESHCA_URL_DEFAULT = "meshca.googleapis.com"; |
| @VisibleForTesting static final long RPC_TIMEOUT_SECONDS_DEFAULT = 5L; |
| @VisibleForTesting static final long CERT_VALIDITY_SECONDS_DEFAULT = 9L * 3600L; |
| @VisibleForTesting static final long RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT = 1L * 3600L; |
| @VisibleForTesting static final String KEY_ALGO_DEFAULT = "RSA"; // aka keyType |
| @VisibleForTesting static final int KEY_SIZE_DEFAULT = 2048; |
| @VisibleForTesting static final String SIGNATURE_ALGO_DEFAULT = "SHA256withRSA"; |
| @VisibleForTesting static final int MAX_RETRY_ATTEMPTS_DEFAULT = 3; |
| @VisibleForTesting |
| static final String STS_URL_DEFAULT = "https://securetoken.googleapis.com/v1/identitybindingtoken"; |
| |
| @VisibleForTesting |
| static final long RPC_TIMEOUT_SECONDS = 10L; |
| |
| private static final Pattern CLUSTER_URL_PATTERN = Pattern |
| .compile(".*/projects/(.*)/locations/(.*)/clusters/.*"); |
| |
| private static final String TRUST_DOMAIN_SUFFIX = ".svc.id.goog"; |
| private static final String AUDIENCE_PREFIX = "identitynamespace:"; |
| static final String MESH_CA_NAME = "meshCA"; |
| |
| static { |
| CertificateProviderRegistry.getInstance() |
| .register( |
| new MeshCaCertificateProviderProvider( |
| StsCredentials.Factory.getInstance(), |
| MeshCaCertificateProvider.MeshCaChannelFactory.getInstance(), |
| new ExponentialBackoffPolicy.Provider(), |
| MeshCaCertificateProvider.Factory.getInstance(), |
| ScheduledExecutorServiceFactory.DEFAULT_INSTANCE, |
| TimeProvider.SYSTEM_TIME_PROVIDER)); |
| } |
| |
| final StsCredentials.Factory stsCredentialsFactory; |
| final MeshCaCertificateProvider.MeshCaChannelFactory meshCaChannelFactory; |
| final BackoffPolicy.Provider backoffPolicyProvider; |
| final MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory; |
| final ScheduledExecutorServiceFactory scheduledExecutorServiceFactory; |
| final TimeProvider timeProvider; |
| |
| @VisibleForTesting |
| MeshCaCertificateProviderProvider( |
| StsCredentials.Factory stsCredentialsFactory, |
| MeshCaCertificateProvider.MeshCaChannelFactory meshCaChannelFactory, |
| BackoffPolicy.Provider backoffPolicyProvider, |
| MeshCaCertificateProvider.Factory meshCaCertificateProviderFactory, |
| ScheduledExecutorServiceFactory scheduledExecutorServiceFactory, |
| TimeProvider timeProvider) { |
| this.stsCredentialsFactory = stsCredentialsFactory; |
| this.meshCaChannelFactory = meshCaChannelFactory; |
| this.backoffPolicyProvider = backoffPolicyProvider; |
| this.meshCaCertificateProviderFactory = meshCaCertificateProviderFactory; |
| this.scheduledExecutorServiceFactory = scheduledExecutorServiceFactory; |
| this.timeProvider = timeProvider; |
| } |
| |
| @Override |
| public String getName() { |
| return MESH_CA_NAME; |
| } |
| |
| @Override |
| public CertificateProvider createCertificateProvider( |
| Object config, CertificateProvider.DistributorWatcher watcher, boolean notifyCertUpdates) { |
| |
| Config configObj = validateAndTranslateConfig(config); |
| |
| // Construct audience from project and gkeClusterUrl |
| String audience = |
| AUDIENCE_PREFIX + configObj.project + TRUST_DOMAIN_SUFFIX + ":" + configObj.gkeClusterUrl; |
| StsCredentials stsCredentials = stsCredentialsFactory |
| .create(configObj.stsUrl, audience, configObj.gkeSaJwtLocation); |
| |
| return meshCaCertificateProviderFactory.create( |
| watcher, |
| notifyCertUpdates, |
| configObj.meshCaUrl, |
| configObj.zone, |
| configObj.certValiditySeconds, |
| configObj.keySize, |
| configObj.keyAlgo, |
| configObj.signatureAlgo, |
| meshCaChannelFactory, |
| backoffPolicyProvider, |
| configObj.renewalGracePeriodSeconds, |
| configObj.maxRetryAttempts, |
| stsCredentials, |
| scheduledExecutorServiceFactory.create(configObj.meshCaUrl), |
| timeProvider, |
| TimeUnit.SECONDS.toMillis(RPC_TIMEOUT_SECONDS)); |
| } |
| |
| private static Config validateAndTranslateConfig(Object config) { |
| // TODO(sanjaypujare): add support for string, struct proto etc |
| checkArgument(config instanceof Map, "Only Map supported for config"); |
| @SuppressWarnings("unchecked") Map<String, String> map = (Map<String, String>)config; |
| |
| Config configObj = new Config(); |
| configObj.meshCaUrl = mapGetOrDefault(map, MESHCA_URL_KEY, MESHCA_URL_DEFAULT); |
| configObj.rpcTimeoutSeconds = |
| mapGetOrDefault(map, RPC_TIMEOUT_SECONDS_KEY, RPC_TIMEOUT_SECONDS_DEFAULT); |
| configObj.gkeClusterUrl = |
| checkNotNull( |
| map.get(GKECLUSTER_URL_KEY), GKECLUSTER_URL_KEY + " is required in the config"); |
| configObj.certValiditySeconds = |
| mapGetOrDefault(map, CERT_VALIDITY_SECONDS_KEY, CERT_VALIDITY_SECONDS_DEFAULT); |
| configObj.renewalGracePeriodSeconds = |
| mapGetOrDefault( |
| map, RENEWAL_GRACE_PERIOD_SECONDS_KEY, RENEWAL_GRACE_PERIOD_SECONDS_DEFAULT); |
| configObj.keyAlgo = mapGetOrDefault(map, KEY_ALGO_KEY, KEY_ALGO_DEFAULT); |
| configObj.keySize = mapGetOrDefault(map, KEY_SIZE_KEY, KEY_SIZE_DEFAULT); |
| configObj.signatureAlgo = mapGetOrDefault(map, SIGNATURE_ALGO_KEY, SIGNATURE_ALGO_DEFAULT); |
| configObj.maxRetryAttempts = |
| mapGetOrDefault(map, MAX_RETRY_ATTEMPTS_KEY, MAX_RETRY_ATTEMPTS_DEFAULT); |
| configObj.stsUrl = mapGetOrDefault(map, STS_URL_KEY, STS_URL_DEFAULT); |
| configObj.gkeSaJwtLocation = |
| checkNotNull( |
| map.get(GKE_SA_JWT_LOCATION_KEY), |
| GKE_SA_JWT_LOCATION_KEY + " is required in the config"); |
| parseProjectAndZone(configObj.gkeClusterUrl, configObj); |
| return configObj; |
| } |
| |
| private static String mapGetOrDefault(Map<String, String> map, String key, String defaultVal) { |
| String value = map.get(key); |
| if (value == null) { |
| return defaultVal; |
| } |
| return value; |
| } |
| |
| private static Long mapGetOrDefault(Map<String, String> map, String key, long defaultVal) { |
| String value = map.get(key); |
| if (value == null) { |
| return defaultVal; |
| } |
| return Long.parseLong(value); |
| } |
| |
| private static Integer mapGetOrDefault(Map<String, String> map, String key, int defaultVal) { |
| String value = map.get(key); |
| if (value == null) { |
| return defaultVal; |
| } |
| return Integer.parseInt(value); |
| } |
| |
| private static void parseProjectAndZone(String gkeClusterUrl, Config configObj) { |
| Matcher matcher = CLUSTER_URL_PATTERN.matcher(gkeClusterUrl); |
| checkState(matcher.find(), "gkeClusterUrl does not have correct format"); |
| checkState(matcher.groupCount() == 2, "gkeClusterUrl does not have project and location parts"); |
| configObj.project = matcher.group(1); |
| configObj.zone = matcher.group(2); |
| } |
| |
| abstract static class ScheduledExecutorServiceFactory { |
| |
| private static final ScheduledExecutorServiceFactory DEFAULT_INSTANCE = |
| new ScheduledExecutorServiceFactory() { |
| |
| @Override |
| ScheduledExecutorService create(String serverUri) { |
| return Executors.newSingleThreadScheduledExecutor( |
| new ThreadFactoryBuilder() |
| .setNameFormat("meshca-" + serverUri + "-%d") |
| .setDaemon(true) |
| .build()); |
| } |
| }; |
| |
| static ScheduledExecutorServiceFactory getInstance() { |
| return DEFAULT_INSTANCE; |
| } |
| |
| abstract ScheduledExecutorService create(String serverUri); |
| } |
| |
| /** POJO class for storing various config values. */ |
| @VisibleForTesting |
| static class Config { |
| String meshCaUrl; |
| Long rpcTimeoutSeconds; |
| String gkeClusterUrl; |
| Long certValiditySeconds; |
| Long renewalGracePeriodSeconds; |
| String keyAlgo; // aka keyType |
| Integer keySize; |
| String signatureAlgo; |
| Integer maxRetryAttempts; |
| String stsUrl; |
| String gkeSaJwtLocation; |
| String zone; |
| String project; |
| } |
| } |