Secret Manager supports using entity tags (ETags) for optimistic concurrency control.
In some cases, two processes updating the same resource in parallel may interfere with one another, where the latter process overwrites the effort of the former one.
ETags provide a means for optimistic concurrency control by allowing processes to see if a resource has been modified before taking action on that resource.
Use ETags with Secret Manager
The following resource modification requests support ETags:
In a secrets.patch
request, the request ETag is embedded in the Secret
data. All other requests accept an optional etag
parameter.
If an ETag is provided and matches the current resource ETag, the request
succeeds; otherwise, it fails with a FAILED_PRECONDITION
error and an HTTP status code
400. If an ETag isn't provided, the request proceeds without checking the currently stored ETag value.
Resource ETags are generated at resource creation (projects.secrets.create, projects.secrets.addVersion) and updated for each of the above-listed modification requests. A modification request only updates the ETag of the resource to which it applies. That is, updating a secret version doesn't affect the secret ETag, and similarly, updating the ETag doesn't affect the secret version.
Even when an update doesn't change a resource's state, it still updates the resource ETag.
Consider the following example:
-
User 1 tries to enable a secret version unaware that it's already enabled. The system processes this, changing nothing but the version's ETag.
-
User 2, using the old ETag, tries to disable the version.
-
This fails because the system recognizes the newer ETag, which indicates a more recent intent to keep the version enabled.
Even seemingly minor updates matter due to ETag changes. This ensures data consistency, especially with multiple users or systems interacting with the same resource.
The resource etag
is returned in the response whenever a resource
(Secret or
SecretVersion)
is included.
Delete a secret with ETags
This section describes using ETags when deleting a secret. If the secret has been modified by another process, the delete operation fails.
gcloud
Before using any of the command data below, make the following replacements:
- SECRET_ID: the ID of the secret or fully qualified identifier for the secret.
- LOCATION: the Google Cloud location of the secret.
- ETAG: the entity tag of the secret. The ETag must include the surrounding quotes.
For example, if the ETag value was
"abc"
, the shell-escaped value would be"\"abc\""
.
Execute the following command:
Linux, macOS, or Cloud Shell
gcloud secrets delete SECRET_ID --location=LOCATION \ --etag "ETAG"
Windows (PowerShell)
gcloud secrets delete SECRET_ID --location=LOCATION ` --etag "ETAG"
Windows (cmd.exe)
gcloud secrets delete SECRET_ID --location=LOCATION ^ --etag "ETAG"
REST
Before using any of the request data, make the following replacements:
- LOCATION: the Google Cloud location of the secret.
- PROJECT_ID: the Google Cloud project ID.
- SECRET_ID: the ID of the secret or fully qualified identifier for the secret.
- ETAG: the entity tag of the secret. The ETag is specified as part of the URL's querystring
and must be URL-encoded. For example, if the ETag value is
"abc"
, the URL-encoded value would be%22abc%22
because the quote character is encoded as%22
.
HTTP method and URL:
DELETE https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID?etag=ETAG
Request JSON body:
{}
To send your request, choose one of these options:
curl
Save the request body in a file named request.json
,
and execute the following command:
curl -X DELETE \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json; charset=utf-8" \
-d @request.json \
"https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID?etag=ETAG"
PowerShell
Save the request body in a file named request.json
,
and execute the following command:
$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }
Invoke-WebRequest `
-Method DELETE `
-Headers $headers `
-ContentType: "application/json; charset=utf-8" `
-InFile request.json `
-Uri "https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID?etag=ETAG" | Select-Object -Expand Content
You should receive a JSON response similar to the following:
{}
Go
To run this code, first set up a Go development environment and install the Secret Manager Go SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
import (
"context"
"fmt"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
"google.golang.org/api/option"
)
// deleteSecretWithEtag deletes the secret with the given name and all of its versions.
func DeleteRegionalSecretWithEtag(projectId, locationId, secretId, etag string) error {
// name := "projects/my-project/locations/my-location/secrets/my-secret"
// etag := `"123"`
// Create the client.
ctx := context.Background()
//Endpoint to send the request to regional server
endpoint := fmt.Sprintf("secretmanager.%s.rep.googleapis.com:443", locationId)
client, err := secretmanager.NewClient(ctx, option.WithEndpoint(endpoint))
if err != nil {
return fmt.Errorf("failed to create secretmanager client: %w", err)
}
defer client.Close()
//Endpoint to send the request to regional server
name := fmt.Sprintf("projects/%s/locations/%s/secrets/%s", projectId, locationId, secretId)
// Build the request.
req := &secretmanagerpb.DeleteSecretRequest{
Name: name,
Etag: etag,
}
// Call the API.
if err := client.DeleteSecret(ctx, req); err != nil {
return fmt.Errorf("failed to delete regional secret: %w", err)
}
return nil
}
Java
To run this code, first set up a Java development environment and install the Secret Manager Java SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
import com.google.cloud.secretmanager.v1.DeleteSecretRequest;
import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
import com.google.cloud.secretmanager.v1.SecretName;
import java.io.IOException;
public class DeleteRegionalSecretWithEtag {
public static void main(String[] args) throws IOException {
// TODO(developer): Replace these variables before running the sample.
// Your GCP project ID.
String projectId = "your-project-id";
// Location of the secret.
String locationId = "your-location-id";
// Resource ID of the secret to delete.
String secretId = "your-secret-id";
// Etag associated with the secret. Quotes should be included as part of the string.
String etag = "\"1234\"";
deleteRegionalSecretWithEtag(projectId, locationId, secretId, etag);
}
// Delete an existing secret with the given name and etag.
public static void deleteRegionalSecretWithEtag(
String projectId, String locationId, String secretId, String etag)
throws IOException {
// Endpoint to call the regional secret manager sever
String apiEndpoint = String.format("secretmanager.%s.rep.googleapis.com:443", locationId);
SecretManagerServiceSettings secretManagerServiceSettings =
SecretManagerServiceSettings.newBuilder().setEndpoint(apiEndpoint).build();
// Initialize the client that will be used to send requests. This client only needs to be
// created once, and can be reused for multiple requests.
try (SecretManagerServiceClient client =
SecretManagerServiceClient.create(secretManagerServiceSettings)) {
// Build the secret name.
SecretName secretName = SecretName.ofProjectLocationSecretName(
projectId, locationId, secretId);
// Construct the request.
DeleteSecretRequest request =
DeleteSecretRequest.newBuilder()
.setName(secretName.toString())
.setEtag(etag)
.build();
// Delete the secret.
client.deleteSecret(request);
System.out.printf("Deleted regional secret %s\n", secretId);
}
}
}
Python
To run this code, first set up a Python development environment and install the Secret Manager Python SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
# Import the Secret Manager client library and types.
from google.cloud import secretmanager_v1
def delete_regional_secret_with_etag(
project_id: str,
location_id: str,
secret_id: str,
etag: str,
) -> None:
"""
Deletes the regional secret with the given name, etag, and all of its versions.
"""
# Endpoint to call the regional secret manager sever.
api_endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
# Create the Secret Manager client.
client = secretmanager_v1.SecretManagerServiceClient(
client_options={"api_endpoint": api_endpoint},
)
# Build the resource name of the secret.
name = f"projects/{project_id}/locations/{location_id}/secrets/{secret_id}"
# Build the request
request = secretmanager_v1.types.service.DeleteSecretRequest(
name=name,
etag=etag,
)
# Delete the secret.
client.delete_secret(request=request)
Update a secret with ETags
This section describes using ETags when updating a secret. If the secret has been modified by another process, the update operation will fail.
gcloud
Before using any of the command data below, make the following replacements:
- SECRET_ID: the ID of the secret or fully qualified identifier for the secret.
- LOCATION: the Google Cloud location of the secret.
- KEY: the label name.
- VALUE: the corresponding label value.
- ETAG: the entity tag of the secret. The ETag must include the surrounding quotes.
For example, if the ETag value was
"abc"
, the shell-escaped value would be"\"abc\""
.
Execute the following command:
Linux, macOS, or Cloud Shell
gcloud secrets update SECRET_ID --location=LOCATION \ --update-labels "KEY=VALUE" \ --etag "ETAG"
Windows (PowerShell)
gcloud secrets update SECRET_ID --location=LOCATION ` --update-labels "KEY=VALUE" ` --etag "ETAG"
Windows (cmd.exe)
gcloud secrets update SECRET_ID --location=LOCATION ^ --update-labels "KEY=VALUE" ^ --etag "ETAG"
The response returns the secret.
REST
Before using any of the request data, make the following replacements:
- LOCATION: the Google Cloud location of the secret.
- PROJECT_ID: the Google Cloud project ID.
- SECRET_ID: the ID of the secret or fully qualified identifier for the secret.
- ETAG: the entity tag of the secret. The ETag is specified as a field on the
Secret
and must include the surrounding quotes. For example, if the ETag value was
"abc"
, the JSON-escaped value would be{"etag":"\"abc\""}
. - KEY: the label name.
- VALUE: the corresponding label value.
HTTP method and URL:
PATCH https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID?updateMask=labels
Request JSON body:
{"etag":"ETAG", "labels":{"KEY": "VALUE"}}
To send your request, choose one of these options:
curl
Save the request body in a file named request.json
,
and execute the following command:
curl -X PATCH \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json; charset=utf-8" \
-d @request.json \
"https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID?updateMask=labels"
PowerShell
Save the request body in a file named request.json
,
and execute the following command:
$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }
Invoke-WebRequest `
-Method PATCH `
-Headers $headers `
-ContentType: "application/json; charset=utf-8" `
-InFile request.json `
-Uri "https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID?updateMask=labels" | Select-Object -Expand Content
You should receive a JSON response similar to the following:
{ "name": "projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID", "createTime": "2024-09-04T04:06:00.660420Z", "labels": { "KEY": "VALUE" }, "etag": "\"162145a4f894d5\"" }
Go
To run this code, first set up a Go development environment and install the Secret Manager Go SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
import (
"context"
"fmt"
"io"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
"google.golang.org/api/option"
"google.golang.org/genproto/protobuf/field_mask"
)
// updateSecretWithEtag updates the metadata about an existing secret.
func UpdateRegionalSecretWithEtag(w io.Writer, projectId, locationId, secretId, etag string) error {
// name := "projects/my-project/locations/my-location/secrets/my-secret"
// etag := `"123"`
// Create the client.
ctx := context.Background()
//Endpoint to send the request to regional server
endpoint := fmt.Sprintf("secretmanager.%s.rep.googleapis.com:443", locationId)
client, err := secretmanager.NewClient(ctx, option.WithEndpoint(endpoint))
if err != nil {
return fmt.Errorf("failed to create regional secretmanager client: %w", err)
}
defer client.Close()
name := fmt.Sprintf("projects/%s/locations/%s/secrets/%s", projectId, locationId, secretId)
// Build the request.
req := &secretmanagerpb.UpdateSecretRequest{
Secret: &secretmanagerpb.Secret{
Name: name,
Etag: etag,
Labels: map[string]string{
"secretmanager": "rocks",
},
},
UpdateMask: &field_mask.FieldMask{
Paths: []string{"labels"},
},
}
// Call the API.
result, err := client.UpdateSecret(ctx, req)
if err != nil {
return fmt.Errorf("failed to update regional secret: %w", err)
}
fmt.Fprintf(w, "Updated regional secret: %s\n", result.Name)
return nil
}
Java
To run this code, first set up a Java development environment and install the Secret Manager Java SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
import com.google.cloud.secretmanager.v1.Secret;
import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
import com.google.cloud.secretmanager.v1.SecretName;
import com.google.protobuf.FieldMask;
import com.google.protobuf.util.FieldMaskUtil;
import java.io.IOException;
public class UpdateRegionalSecretWithEtag {
public static void main(String[] args) throws IOException {
// TODO(developer): Replace these variables before running the sample.
// Your GCP project ID.
String projectId = "your-project-id";
// Location of the secret.
String locationId = "your-location-id";
// Resource ID of the secret to update.
String secretId = "your-secret-id";
// Etag associated with the secret. Quotes should be included as part of the string.
String etag = "\"1234\"";
updateRegionalSecretWithEtag(projectId, locationId, secretId, etag);
}
// Update an existing secret with etag.
public static Secret updateRegionalSecretWithEtag(
String projectId, String locationId, String secretId, String etag)
throws IOException {
// Endpoint to call the regional secret manager sever
String apiEndpoint = String.format("secretmanager.%s.rep.googleapis.com:443", locationId);
SecretManagerServiceSettings secretManagerServiceSettings =
SecretManagerServiceSettings.newBuilder().setEndpoint(apiEndpoint).build();
// Initialize the client that will be used to send requests. This client only needs to be
// created once, and can be reused for multiple requests.
try (SecretManagerServiceClient client =
SecretManagerServiceClient.create(secretManagerServiceSettings)) {
// Build the name.
SecretName secretName =
SecretName.ofProjectLocationSecretName(projectId, locationId, secretId);
// Build the updated secret.
Secret secret =
Secret.newBuilder()
.setName(secretName.toString())
.setEtag(etag)
.putLabels("secretmanager", "rocks")
.build();
// Build the field mask.
FieldMask fieldMask = FieldMaskUtil.fromString("labels");
// Update the secret.
Secret updatedSecret = client.updateSecret(secret, fieldMask);
System.out.printf("Updated regional secret %s\n", updatedSecret.getName());
return updatedSecret;
}
}
}
Python
To run this code, first set up a Python development environment and install the Secret Manager Python SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
# Import the Secret Manager client library.
from google.cloud import secretmanager_v1
def update_regional_secret_with_etag(
project_id: str,
location_id: str,
secret_id: str,
etag: str,
) -> secretmanager_v1.UpdateSecretRequest:
"""
Update the metadata about an existing secret, using etag.
"""
# Endpoint to call the regional secret manager sever
api_endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
# Create the Secret Manager client.
client = secretmanager_v1.SecretManagerServiceClient(
client_options={"api_endpoint": api_endpoint},
)
# Build the resource name of the secret.
name = f"projects/{project_id}/locations/{location_id}/secrets/{secret_id}"
# Update the secret.
secret = {"name": name, "labels": {"secretmanager": "rocks"}, "etag": etag}
update_mask = {"paths": ["labels"]}
response = client.update_secret(
request={"secret": secret, "update_mask": update_mask}
)
# Print the new secret name.
print(f"Updated secret: {response.name}")
return response
Update a secret version with ETags
This section describes using ETags when updating a secret version. If the secret version has been modified by another process, the update operation will fail.
gcloud
Before using any of the command data below, make the following replacements:
- VERSION_ID: the ID of the secret version.
- SECRET_ID: the ID of the secret or fully qualified identifier for the secret.
- LOCATION: the Google Cloud location of the secret.
- ETAG: the entity tag. The ETag must include the surrounding quotes.
For example, if the ETag value was
"abc"
, the shell-escaped value would be"\"abc\""
.
Execute the following command:
Linux, macOS, or Cloud Shell
gcloud secrets versions disable VERSION_ID \ --secret SECRET_ID \ --location=LOCATION \ --etag "ETAG"
Windows (PowerShell)
gcloud secrets versions disable VERSION_ID ` --secret SECRET_ID ` --location=LOCATION ` --etag "ETAG"
Windows (cmd.exe)
gcloud secrets versions disable VERSION_ID ^ --secret SECRET_ID ^ --location=LOCATION ^ --etag "ETAG"
The response returns the secret.
REST
Before using any of the request data, make the following replacements:
- LOCATION: the Google Cloud location of the secret
- PROJECT_ID: the Google Cloud project ID
- SECRET_ID: the ID of the secret or fully qualified identifier for the secret
- VERSION_ID: the ID of the secret version
- ETAG: the entity tag of the secret version. The ETag is specified as a field on the
SecretVersion
and must include the surrounding quotes. For example, if the ETag value was
"abc"
, the JSON-escaped value would be{"etag":"\"abc\""}
.
HTTP method and URL:
POST https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID/versions/VERSION_ID:disable
Request JSON body:
{"etag":"ETAG"}
To send your request, choose one of these options:
curl
Save the request body in a file named request.json
,
and execute the following command:
curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json; charset=utf-8" \
-d @request.json \
"https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID/versions/VERSION_ID:disable"
PowerShell
Save the request body in a file named request.json
,
and execute the following command:
$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }
Invoke-WebRequest `
-Method POST `
-Headers $headers `
-ContentType: "application/json; charset=utf-8" `
-InFile request.json `
-Uri "https://secretmanager.LOCATION.rep.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID/versions/VERSION_ID:disable" | Select-Object -Expand Content
You should receive a JSON response similar to the following:
{ "name": "projects/PROJECT_ID/locations/LOCATION/secrets/SECRET_ID/versions/VERSION_ID", "createTime": "2024-09-04T06:41:57.859674Z", "state": "DISABLED", "etag": "\"1621457b3c1459\"" }
Go
To run this code, first set up a Go development environment and install the Secret Manager Go SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
import (
"context"
"fmt"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
"google.golang.org/api/option"
)
// disableSecretVersionWithEtag disables the given secret version. Future requests will
// throw an error until the secret version is enabled. Other secrets versions
// are unaffected.
func DisableRegionalSecretVersionWithEtag(projectId, locationId, secretId, versionId, etag string) error {
// name := "projects/my-project/locations/my-location/secrets/my-secret/versions/5"
// etag := `"123"`
// Create the client.
ctx := context.Background()
//Endpoint to send the request to regional server
endpoint := fmt.Sprintf("secretmanager.%s.rep.googleapis.com:443", locationId)
client, err := secretmanager.NewClient(ctx, option.WithEndpoint(endpoint))
if err != nil {
return fmt.Errorf("failed to create regional secretmanager client: %w", err)
}
defer client.Close()
name := fmt.Sprintf("projects/%s/locations/%s/secrets/%s/versions/%s", projectId, locationId, secretId, versionId)
// Build the request.
req := &secretmanagerpb.DisableSecretVersionRequest{
Name: name,
Etag: etag,
}
// Call the API.
if _, err := client.DisableSecretVersion(ctx, req); err != nil {
return fmt.Errorf("failed to disable regional secret version: %w", err)
}
return nil
}
Java
To run this code, first set up a Java development environment and install the Secret Manager Java SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
import com.google.cloud.secretmanager.v1.DisableSecretVersionRequest;
import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
import com.google.cloud.secretmanager.v1.SecretVersion;
import com.google.cloud.secretmanager.v1.SecretVersionName;
import java.io.IOException;
public class DisableRegionalSecretVersionWithEtag {
public static void main(String[] args) throws IOException {
// TODO(developer): Replace these variables before running the sample.
// Your GCP project ID.
String projectId = "your-project-id";
// Location of the secret.
String locationId = "your-location-id";
// Resource ID of the secret.
String secretId = "your-secret-id";
// Version of the Secret ID you want to disable.
String versionId = "your-version-id";
// Etag associated with the secret. Quotes should be included as part of the string.
String etag = "\"1234\"";
disableRegionalSecretVersionWithEtag(projectId, locationId, secretId, versionId, etag);
}
// Disable an existing secret version.
public static SecretVersion disableRegionalSecretVersionWithEtag(
String projectId, String locationId, String secretId, String versionId, String etag)
throws IOException {
// Endpoint to call the regional secret manager sever
String apiEndpoint = String.format("secretmanager.%s.rep.googleapis.com:443", locationId);
SecretManagerServiceSettings secretManagerServiceSettings =
SecretManagerServiceSettings.newBuilder().setEndpoint(apiEndpoint).build();
// Initialize the client that will be used to send requests. This client only needs to be
// created once, and can be reused for multiple requests.
try (SecretManagerServiceClient client =
SecretManagerServiceClient.create(secretManagerServiceSettings)) {
// Build the name from the version.
SecretVersionName secretVersionName
= SecretVersionName.ofProjectLocationSecretSecretVersionName(
projectId, locationId, secretId, versionId);
// Build the request.
DisableSecretVersionRequest request =
DisableSecretVersionRequest.newBuilder()
.setName(secretVersionName.toString())
.setEtag(etag)
.build();
// Disable the secret version.
SecretVersion version = client.disableSecretVersion(request);
System.out.printf("Disabled regional secret version %s\n", version.getName());
return version;
}
}
}
Python
To run this code, first set up a Python development environment and install the Secret Manager Python SDK. On Compute Engine or GKE, you must authenticate with the cloud-platform scope.
# Import the Secret Manager client library.
from google.cloud import secretmanager_v1
def disable_regional_secret_version_with_etag(
project_id: str,
location_id: str,
secret_id: str,
version_id: str,
etag: str,
) -> secretmanager_v1.DisableSecretVersionRequest:
"""
Disables the given secret version. Future requests will throw an error until
the secret version is enabled. Other secrets versions are unaffected.
"""
# Endpoint to call the regional secret manager sever.
api_endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
# Create the Secret Manager client.
client = secretmanager_v1.SecretManagerServiceClient(
client_options={"api_endpoint": api_endpoint},
)
# Build the resource name of the secret version.
name = f"projects/{project_id}/locations/{location_id}/secrets/{secret_id}/versions/{version_id}"
# Build the request.
request = secretmanager_v1.types.service.DisableSecretVersionRequest(
name=name,
etag=etag,
)
# Disable the secret version.
response = client.disable_secret_version(request=request)
print(f"Disabled secret version: {response.name}")
return response
The code sample here describes enabling a secret version with ETags. You can also specify ETags during other secret mutation operations, such as when disabling or destroying secret versions. Refer to the code samples for Secret Manager.