Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

import io.cdap.cdap.api.annotation.Beta;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;


/**
* Provides read access to the secure store. For write access use {@link SecureStoreManager}.
Expand Down Expand Up @@ -73,4 +76,13 @@ default SecureStoreMetadata getMetadata(String namespace, String name) throws Ex
default byte[] getData(String namespace, String name) throws Exception {
return get(namespace, name).get();
}

/**
* Returns the capabilities supported by this secure store implementation.
*
* @return A set of supported capabilities
*/
default Set<SecureStoreCapability> getCapabilities() throws IOException {
return Collections.emptySet();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright © 2026 Cask Data, 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.cdap.cdap.api.security.store;

/**
* Represents the capabilities of the Secure Store.
*/
public enum SecureStoreCapability {
LEASE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright © 2026 Cask Data, 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.cdap.cdap.api.security.store;

import java.util.Objects;

/**
* API model representing a distributed lease lock on a secret or credential update in SecureStore.
* <p>

Check warning on line 23 in cdap-api/src/main/java/io/cdap/cdap/api/security/store/SecureStoreLease.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck

<p> tag should be placed immediately before the first word, with no space after.

Check warning on line 23 in cdap-api/src/main/java/io/cdap/cdap/api/security/store/SecureStoreLease.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck

<p> tag should be preceded with an empty line.
* Note: This is the caller-facing API counterpart to {@code io.cdap.cdap.securestore.spi.lease.SecretLease}
* in the SPI layer, mirroring the {@link io.cdap.cdap.api.security.store.SecureStoreMetadata} vs {@code SecretMetadata}
* architectural pattern in CDAP.
*/
public class SecureStoreLease {
private final boolean acquired;
private final String lockTimestamp;
private final String lockHolder;

private SecureStoreLease(boolean acquired, String lockTimestamp, String lockHolder) {
this.acquired = acquired;
this.lockTimestamp = lockTimestamp;
this.lockHolder = lockHolder;
}

public static SecureStoreLease acquired(String lockTimestamp, String lockHolder) {
return new SecureStoreLease(true, lockTimestamp, lockHolder);
}

public static SecureStoreLease failed() {
return new SecureStoreLease(false, null, null);
}

public boolean isAcquired() {
return acquired;
}

public String getLockTimestamp() {
return lockTimestamp;
}

public String getLockHolder() {
return lockHolder;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SecureStoreLease that = (SecureStoreLease) o;
return acquired == that.acquired
&& Objects.equals(lockTimestamp, that.lockTimestamp)
&& Objects.equals(lockHolder, that.lockHolder);
}

@Override
public int hashCode() {
return Objects.hash(acquired, lockTimestamp, lockHolder);
}

@Override
public String toString() {
return "SecureStoreLease{"
+ "acquired=" + acquired
+ ", lockTimestamp='" + lockTimestamp + '\''
+ ", lockHolder='" + lockHolder + '\''
+ '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,31 @@
* @throws Exception If the specified namespace or name does not exist
*/
void delete(String namespace, String name) throws Exception;


/**
* Attempts to acquire a lease lock on a secret resource.
*
* @param namespace The namespace that this key belongs to
* @param name Name of the secure key
* @param timeoutMs Lock timeout in milliseconds before lease is considered expired
* @return {@link SecureStoreLease} indicating acquisition success and lock details
* @throws Exception If lock acquisition fails due to underlying storage errors
*/
default SecureStoreLease acquireLease(String namespace, String name, long timeoutMs,
String lockHolder) throws Exception {

Check warning on line 82 in cdap-api/src/main/java/io/cdap/cdap/api/security/store/SecureStoreManager.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=cdapio_cdap&issues=AZ__2ZTlYHbLgCwjr9Zb&open=AZ__2ZTlYHbLgCwjr9Zb&pullRequest=16201
throw new UnsupportedOperationException("Leases are not supported by this SecureStore implementation.");
}

/**
* Releases an acquired lease lock on a secret resource.
*
* @param namespace The namespace that this key belongs to
* @param name Name of the secure key
* @param lease {@link SecureStoreLease} to release
* @throws Exception If lock release fails due to underlying storage errors
*/
default void releaseLease(String namespace, String name, SecureStoreLease lease) throws Exception {

Check warning on line 94 in cdap-api/src/main/java/io/cdap/cdap/api/security/store/SecureStoreManager.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=cdapio_cdap&issues=AZ__2ZTlYHbLgCwjr9Zc&open=AZ__2ZTlYHbLgCwjr9Zc&pullRequest=16201
throw new UnsupportedOperationException("Leases are not supported by this SecureStore implementation.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import io.cdap.cdap.api.schedule.TriggeringScheduleInfo;
import io.cdap.cdap.api.security.AccessException;
import io.cdap.cdap.api.security.store.SecureStore;
import io.cdap.cdap.api.security.store.SecureStoreCapability;
import io.cdap.cdap.api.security.store.SecureStoreData;
import io.cdap.cdap.api.security.store.SecureStoreManager;
import io.cdap.cdap.api.security.store.SecureStoreMetadata;
Expand Down Expand Up @@ -99,8 +100,8 @@
import io.cdap.cdap.internal.app.runtime.schedule.TriggeringScheduleInfoAdapter;
import io.cdap.cdap.logging.context.LoggingContextHelper;
import io.cdap.cdap.messaging.spi.MessagingService;
import io.cdap.cdap.messaging.context.BasicMessagingAdmin;

Check warning on line 103 in cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/AbstractContext.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'io.cdap.cdap.messaging.context.BasicMessagingAdmin' import. Should be before 'io.cdap.cdap.messaging.spi.MessagingService'.
import io.cdap.cdap.messaging.context.MultiThreadMessagingContext;

Check warning on line 104 in cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/AbstractContext.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'io.cdap.cdap.messaging.context.MultiThreadMessagingContext' import. Should be before 'io.cdap.cdap.messaging.spi.MessagingService'.
import io.cdap.cdap.proto.Notification;
import io.cdap.cdap.proto.id.ApplicationId;
import io.cdap.cdap.proto.id.ArtifactId;
Expand Down Expand Up @@ -132,7 +133,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**

Check warning on line 136 in cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/AbstractContext.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck

First sentence of Javadoc is missing an ending period.
* Base class for program runtime context
*/
public abstract class AbstractContext extends AbstractServiceDiscoverer
Expand Down Expand Up @@ -535,6 +536,12 @@
return Retries.callWithRetries(() -> secureStore.getData(namespace, name), retryStrategy);
}


@Override
public Set<SecureStoreCapability> getCapabilities() throws java.io.IOException {
return secureStore.getCapabilities();
}

@Override
public void execute(final TxRunnable runnable) throws TransactionFailureException {
execute(runnable, false);
Expand Down Expand Up @@ -591,7 +598,7 @@
.getDataTracer(applicationId, dataTracerName);
}

@Nullable

Check warning on line 601 in cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/AbstractContext.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.coding.OverloadMethodsDeclarationOrderCheck

All overloaded methods should be placed next to each other. Placing non-overloaded methods in between overloaded methods with the same type is a violation. Previous overloaded method located at line '273'.
@Override
public TriggeringScheduleInfo getTriggeringScheduleInfo() {
return triggeringScheduleInfo;
Expand All @@ -601,7 +608,7 @@
* Run some code with the context class loader combined from the program class loader and the
* system class loader.
*/
public void execute(ThrowingRunnable runnable) throws Exception {

Check warning on line 611 in cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/AbstractContext.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.coding.OverloadMethodsDeclarationOrderCheck

All overloaded methods should be placed next to each other. Placing non-overloaded methods in between overloaded methods with the same type is a violation. Previous overloaded method located at line '575'.
ClassLoader oldClassLoader = ClassLoaders.setContextClassLoader(
getProgramInvocationClassLoader());
try {
Expand Down Expand Up @@ -765,7 +772,7 @@
return messagingContext;
}

@Override

Check warning on line 775 in cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/AbstractContext.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.coding.OverloadMethodsDeclarationOrderCheck

All overloaded methods should be placed next to each other. Placing non-overloaded methods in between overloaded methods with the same type is a violation. Previous overloaded method located at line '529'.
public Map<MetadataScope, Metadata> getMetadata(MetadataEntity metadataEntity)
throws MetadataException {
return Retries.callWithRetries(() -> metadataReader.getMetadata(metadataEntity), retryStrategy);
Expand Down Expand Up @@ -842,7 +849,7 @@
retryStrategy);
}

/**

Check warning on line 852 in cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/AbstractContext.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck

Summary javadoc is missing.
* @return Map with feature flags defined in CConf.
*/
@Override
Expand Down Expand Up @@ -913,7 +920,7 @@
};
}

/**

Check warning on line 923 in cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/AbstractContext.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck

Summary javadoc is missing.
* @return the {@link Set} of field lineage operations
*/
public Set<Operation> getFieldLineageOperations() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.cdap.cdap.api.messaging.MessagingAdmin;
import io.cdap.cdap.api.messaging.TopicAlreadyExistsException;
import io.cdap.cdap.api.messaging.TopicNotFoundException;
import io.cdap.cdap.api.security.store.SecureStoreLease;
import io.cdap.cdap.api.security.store.SecureStoreManager;
import io.cdap.cdap.common.NamespaceNotFoundException;
import io.cdap.cdap.common.namespace.NamespaceQueryAdmin;
Expand Down Expand Up @@ -91,6 +92,19 @@ public void delete(String namespace, String name) throws Exception {
Retries.runWithRetries(() -> secureStoreManager.delete(namespace, name), retryStrategy);
}


@Override
public SecureStoreLease acquireLease(String namespace, String name, long timeoutMs,
String lockHolder) throws Exception {
return Retries.callWithRetries(
() -> secureStoreManager.acquireLease(namespace, name, timeoutMs, lockHolder), retryStrategy);
}

@Override
public void releaseLease(String namespace, String name, SecureStoreLease lease) throws Exception {
Retries.runWithRetries(() -> secureStoreManager.releaseLease(namespace, name, lease), retryStrategy);
}

@Override
public void createTopic(final String topic) throws TopicAlreadyExistsException, IOException {
if (messagingAdmin == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.rpc.ApiException;
import com.google.api.gax.rpc.StatusCode;

Check warning on line 22 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import 'com.google.api.gax.rpc.StatusCode'.

See more on https://sonarcloud.io/project/issues?id=cdapio_cdap&issues=AaAR3AaSTV2yRQvmL5oe&open=AaAR3AaSTV2yRQvmL5oe&pullRequest=16201

Check warning on line 22 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck

Unused import - com.google.api.gax.rpc.StatusCode.
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.ServiceOptions;
import com.google.cloud.secretmanager.v1.AddSecretVersionRequest;
Expand All @@ -35,15 +36,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;

Check warning on line 39 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Extra separation in import group before 'java.io.ByteArrayInputStream'

Check warning on line 39 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'java.io.ByteArrayInputStream' import. Should be before 'org.slf4j.LoggerFactory'.
import java.io.IOException;

Check warning on line 40 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'java.io.IOException' import. Should be before 'org.slf4j.LoggerFactory'.
import java.nio.charset.StandardCharsets;

Check warning on line 41 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'java.nio.charset.StandardCharsets' import. Should be before 'org.slf4j.LoggerFactory'.
import java.security.MessageDigest;

Check warning on line 42 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'java.security.MessageDigest' import. Should be before 'org.slf4j.LoggerFactory'.
import java.security.NoSuchAlgorithmException;

Check warning on line 43 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'java.security.NoSuchAlgorithmException' import. Should be before 'org.slf4j.LoggerFactory'.
import java.util.Map;

Check warning on line 44 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'java.util.Map' import. Should be before 'org.slf4j.LoggerFactory'.
import java.util.Optional;

Check warning on line 45 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck

Wrong lexicographical order for 'java.util.Optional' import. Should be before 'org.slf4j.LoggerFactory'.

/** Client for <a href="https://cloud.google.com/secret-manager">Google Cloud Secret Manager</a> */

Check warning on line 47 in cdap-securestore-ext-gcp-secretstore/src/main/java/io/cdap/cdap/securestore/gcp/cloudsecretmanager/CloudSecretManagerClient.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck

First sentence of Javadoc is missing an ending period.
public class CloudSecretManagerClient {
private static final Logger LOG = LoggerFactory.getLogger(CloudSecretManagerClient.class);
/**
Expand Down Expand Up @@ -175,6 +176,32 @@
fieldMask.build());
}

/**
* Conditionally updates annotations on the specified secret using an ETag for optimistic concurrency control.
*
* @param namespace CDAP secret namespace
* @param name CDAP secret name
* @param annotationsToUpdate Map of annotations to update
* @param etag The expected ETag of the secret
*/
public void updateSecretAnnotations(String namespace,
String name,
Map<String, String> annotationsToUpdate,
String etag) {
String resourceName = getSecretResourceName(namespace, name);
Secret.Builder secretBuilder = Secret.newBuilder()
.setName(resourceName)
.setEtag(etag);

for (Map.Entry<String, String> entry : annotationsToUpdate.entrySet()) {
secretBuilder.putAnnotations(entry.getKey(), entry.getValue());
}

secretManager.updateSecret(
secretBuilder.build(),
FieldMask.newBuilder().addPaths("annotations").build());
}

/**
* Deletes the specified secret.
*
Expand Down
Loading
Loading