@Test public void backfillExpirationTest() throws Exception { byte[] certs = Resources.toByteArray(Resources.getResource("fixtures/expiring-certificates.crt")); byte[] pubring = Resources.toByteArray(Resources.getResource("fixtures/expiring-pubring.gpg")); byte[] p12 = Resources.toByteArray(Resources.getResource("fixtures/expiring-keystore.p12")); byte[] jceks = Resources.toByteArray(Resources.getResource("fixtures/expiring-keystore.jceks")); create(CreateSecretRequestV2.builder() .name("certificate-chain.crt") .content(encoder.encodeToString(certs)) .build()); create(CreateSecretRequestV2.builder() .name("public-keyring.gpg") .content(encoder.encodeToString(pubring)) .build()); create(CreateSecretRequestV2.builder() .name("keystore.p12") .content(encoder.encodeToString(p12)) .build()); create(CreateSecretRequestV2.builder() .name("keystore.jceks") .content(encoder.encodeToString(jceks)) .build()); Response response = backfillExpiration("certificate-chain.crt", ImmutableList.of()); assertThat(response.isSuccessful()).isTrue(); response = backfillExpiration("public-keyring.gpg", ImmutableList.of()); assertThat(response.isSuccessful()).isTrue(); response = backfillExpiration("keystore.p12", ImmutableList.of("password")); assertThat(response.isSuccessful()).isTrue(); response = backfillExpiration("keystore.jceks", ImmutableList.of("password")); assertThat(response.isSuccessful()).isTrue(); SecretDetailResponseV2 details = lookup("certificate-chain.crt"); assertThat(details.expiry()).isEqualTo(1501533950); details = lookup("public-keyring.gpg"); assertThat(details.expiry()).isEqualTo(1536442365); details = lookup("keystore.p12"); assertThat(details.expiry()).isEqualTo(1681596851); details = lookup("keystore.jceks"); assertThat(details.expiry()).isEqualTo(1681596851); }
@Timed @ExceptionMetered @Path("{name}/backfill-expiration") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) public boolean backfillExpiration(@Auth AutomationClient automationClient, @PathParam("name") String name, List<String> passwords) { Optional<Secret> secretOptional = secretController.getSecretByName(name); if (!secretOptional.isPresent()) { throw new NotFoundException("No such secret: " + name); } Secret secret = secretOptional.get(); Optional<Instant> existingExpiry = Optional.empty(); if (secret.getExpiry() > 0) { existingExpiry = Optional.of(Instant.ofEpochMilli(secret.getExpiry()*1000)); } String secretName = secret.getName(); byte[] secretContent = Base64.getDecoder().decode(secret.getSecret()); passwords.add(""); Instant expiry = null; if (secretName.endsWith(".crt") || secretName.endsWith(".pem") || secretName.endsWith(".key")) { expiry = ExpirationExtractor.expirationFromEncodedCertificateChain(secretContent); } else if (secretName.endsWith(".gpg") || secretName.endsWith(".pgp")) { expiry = ExpirationExtractor.expirationFromOpenPGP(secretContent); } else if (secretName.endsWith(".p12") || secretName.endsWith(".pfx")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("PKCS12", password, secretContent); } } else if (secretName.endsWith(".jceks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JCEKS", password, secretContent); } } else if (secretName.endsWith(".jks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JKS", password, secretContent); } } if (expiry != null) { if (existingExpiry.isPresent()) { long offset = existingExpiry.get().until(expiry, HOURS); if (offset > 24 || offset < -24) { logger.warn( "Extracted expiration of secret {} differs from actual by more than {} hours (extracted = {}, database = {}).", secretName, offset, expiry, existingExpiry.get()); } return true; } logger.info("Found expiry for secret {}: {}", secretName, expiry.getEpochSecond()); boolean success = secretDAO.setExpiration(name, expiry); if (success) { Map<String, String> extraInfo = new HashMap<>(); extraInfo.put("backfilled expiry", Long.toString(expiry.getEpochSecond())); auditLog.recordEvent(new Event(Instant.now(), EventTag.SECRET_BACKFILLEXPIRY, automationClient.getName(), name, extraInfo)); } return success; } logger.info("Unable to determine expiry for secret {}", secretName); return false; }
SecretResource { @Timed @ExceptionMetered @Path("{name}/backfill-expiration") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) public boolean backfillExpiration(@Auth AutomationClient automationClient, @PathParam("name") String name, List<String> passwords) { Optional<Secret> secretOptional = secretController.getSecretByName(name); if (!secretOptional.isPresent()) { throw new NotFoundException("No such secret: " + name); } Secret secret = secretOptional.get(); Optional<Instant> existingExpiry = Optional.empty(); if (secret.getExpiry() > 0) { existingExpiry = Optional.of(Instant.ofEpochMilli(secret.getExpiry()*1000)); } String secretName = secret.getName(); byte[] secretContent = Base64.getDecoder().decode(secret.getSecret()); passwords.add(""); Instant expiry = null; if (secretName.endsWith(".crt") || secretName.endsWith(".pem") || secretName.endsWith(".key")) { expiry = ExpirationExtractor.expirationFromEncodedCertificateChain(secretContent); } else if (secretName.endsWith(".gpg") || secretName.endsWith(".pgp")) { expiry = ExpirationExtractor.expirationFromOpenPGP(secretContent); } else if (secretName.endsWith(".p12") || secretName.endsWith(".pfx")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("PKCS12", password, secretContent); } } else if (secretName.endsWith(".jceks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JCEKS", password, secretContent); } } else if (secretName.endsWith(".jks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JKS", password, secretContent); } } if (expiry != null) { if (existingExpiry.isPresent()) { long offset = existingExpiry.get().until(expiry, HOURS); if (offset > 24 || offset < -24) { logger.warn( "Extracted expiration of secret {} differs from actual by more than {} hours (extracted = {}, database = {}).", secretName, offset, expiry, existingExpiry.get()); } return true; } logger.info("Found expiry for secret {}: {}", secretName, expiry.getEpochSecond()); boolean success = secretDAO.setExpiration(name, expiry); if (success) { Map<String, String> extraInfo = new HashMap<>(); extraInfo.put("backfilled expiry", Long.toString(expiry.getEpochSecond())); auditLog.recordEvent(new Event(Instant.now(), EventTag.SECRET_BACKFILLEXPIRY, automationClient.getName(), name, extraInfo)); } return success; } logger.info("Unable to determine expiry for secret {}", secretName); return false; } }
SecretResource { @Timed @ExceptionMetered @Path("{name}/backfill-expiration") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) public boolean backfillExpiration(@Auth AutomationClient automationClient, @PathParam("name") String name, List<String> passwords) { Optional<Secret> secretOptional = secretController.getSecretByName(name); if (!secretOptional.isPresent()) { throw new NotFoundException("No such secret: " + name); } Secret secret = secretOptional.get(); Optional<Instant> existingExpiry = Optional.empty(); if (secret.getExpiry() > 0) { existingExpiry = Optional.of(Instant.ofEpochMilli(secret.getExpiry()*1000)); } String secretName = secret.getName(); byte[] secretContent = Base64.getDecoder().decode(secret.getSecret()); passwords.add(""); Instant expiry = null; if (secretName.endsWith(".crt") || secretName.endsWith(".pem") || secretName.endsWith(".key")) { expiry = ExpirationExtractor.expirationFromEncodedCertificateChain(secretContent); } else if (secretName.endsWith(".gpg") || secretName.endsWith(".pgp")) { expiry = ExpirationExtractor.expirationFromOpenPGP(secretContent); } else if (secretName.endsWith(".p12") || secretName.endsWith(".pfx")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("PKCS12", password, secretContent); } } else if (secretName.endsWith(".jceks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JCEKS", password, secretContent); } } else if (secretName.endsWith(".jks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JKS", password, secretContent); } } if (expiry != null) { if (existingExpiry.isPresent()) { long offset = existingExpiry.get().until(expiry, HOURS); if (offset > 24 || offset < -24) { logger.warn( "Extracted expiration of secret {} differs from actual by more than {} hours (extracted = {}, database = {}).", secretName, offset, expiry, existingExpiry.get()); } return true; } logger.info("Found expiry for secret {}: {}", secretName, expiry.getEpochSecond()); boolean success = secretDAO.setExpiration(name, expiry); if (success) { Map<String, String> extraInfo = new HashMap<>(); extraInfo.put("backfilled expiry", Long.toString(expiry.getEpochSecond())); auditLog.recordEvent(new Event(Instant.now(), EventTag.SECRET_BACKFILLEXPIRY, automationClient.getName(), name, extraInfo)); } return success; } logger.info("Unable to determine expiry for secret {}", secretName); return false; } @Inject  SecretResource(SecretController secretController, AclDAOFactory aclDAOFactory,
      GroupDAOFactory groupDAOFactory, SecretDAOFactory secretDAOFactory, AuditLog auditLog,
      SecretSeriesDAOFactory secretSeriesDAOFactory, ContentCryptographer cryptographer,
      @Readonly SecretController secretControllerReadOnly); }
SecretResource { @Timed @ExceptionMetered @Path("{name}/backfill-expiration") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) public boolean backfillExpiration(@Auth AutomationClient automationClient, @PathParam("name") String name, List<String> passwords) { Optional<Secret> secretOptional = secretController.getSecretByName(name); if (!secretOptional.isPresent()) { throw new NotFoundException("No such secret: " + name); } Secret secret = secretOptional.get(); Optional<Instant> existingExpiry = Optional.empty(); if (secret.getExpiry() > 0) { existingExpiry = Optional.of(Instant.ofEpochMilli(secret.getExpiry()*1000)); } String secretName = secret.getName(); byte[] secretContent = Base64.getDecoder().decode(secret.getSecret()); passwords.add(""); Instant expiry = null; if (secretName.endsWith(".crt") || secretName.endsWith(".pem") || secretName.endsWith(".key")) { expiry = ExpirationExtractor.expirationFromEncodedCertificateChain(secretContent); } else if (secretName.endsWith(".gpg") || secretName.endsWith(".pgp")) { expiry = ExpirationExtractor.expirationFromOpenPGP(secretContent); } else if (secretName.endsWith(".p12") || secretName.endsWith(".pfx")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("PKCS12", password, secretContent); } } else if (secretName.endsWith(".jceks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JCEKS", password, secretContent); } } else if (secretName.endsWith(".jks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JKS", password, secretContent); } } if (expiry != null) { if (existingExpiry.isPresent()) { long offset = existingExpiry.get().until(expiry, HOURS); if (offset > 24 || offset < -24) { logger.warn( "Extracted expiration of secret {} differs from actual by more than {} hours (extracted = {}, database = {}).", secretName, offset, expiry, existingExpiry.get()); } return true; } logger.info("Found expiry for secret {}: {}", secretName, expiry.getEpochSecond()); boolean success = secretDAO.setExpiration(name, expiry); if (success) { Map<String, String> extraInfo = new HashMap<>(); extraInfo.put("backfilled expiry", Long.toString(expiry.getEpochSecond())); auditLog.recordEvent(new Event(Instant.now(), EventTag.SECRET_BACKFILLEXPIRY, automationClient.getName(), name, extraInfo)); } return success; } logger.info("Unable to determine expiry for secret {}", secretName); return false; } @Inject  SecretResource(SecretController secretController, AclDAOFactory aclDAOFactory,
      GroupDAOFactory groupDAOFactory, SecretDAOFactory secretDAOFactory, AuditLog auditLog,
      SecretSeriesDAOFactory secretSeriesDAOFactory, ContentCryptographer cryptographer,
      @Readonly SecretController secretControllerReadOnly); @Timed @ExceptionMetered @POST @Consumes(APPLICATION_JSON) Response createSecret(@Auth AutomationClient automationClient,
      @Valid CreateSecretRequestV2 request); @Timed @ExceptionMetered @Path("{name}") @POST @Consumes(APPLICATION_JSON) Response createOrUpdateSecret(@Auth AutomationClient automationClient,
      @PathParam("name") String name,
      @Valid CreateOrUpdateSecretRequestV2 request); @Timed @ExceptionMetered @Path("{name}/partialupdate") @POST @Consumes(APPLICATION_JSON) Response partialUpdateSecret(@Auth AutomationClient automationClient,
      @PathParam("name") String name,
      @Valid PartialUpdateSecretRequestV2 request); @Timed @ExceptionMetered @GET @Produces(APPLICATION_JSON) Iterable<String> secretListing(@Auth AutomationClient automationClient,
      @QueryParam("idx") Integer idx, @QueryParam("num") Integer num,
      @DefaultValue("true") @QueryParam("newestFirst") boolean newestFirst); @Timed @ExceptionMetered @Path("/v2") @GET @Produces(APPLICATION_JSON) Iterable<SanitizedSecret> secretListingV2(@Auth AutomationClient automationClient,
      @QueryParam("idx") Integer idx, @QueryParam("num") Integer num,
      @DefaultValue("true") @QueryParam("newestFirst") boolean newestFirst); @Timed @ExceptionMetered @Path("expiring/{time}") @GET @Produces(APPLICATION_JSON) Iterable<String> secretListingExpiring(@Auth AutomationClient automationClient, @PathParam("time") Long time); @Timed @ExceptionMetered @Path("expiring/v2/{time}") @GET @Produces(APPLICATION_JSON) Iterable<SanitizedSecret> secretListingExpiringV2(@Auth AutomationClient automationClient, @PathParam("time") Long time); @Timed @ExceptionMetered @Path("expiring/v3/{time}") @GET @Produces(APPLICATION_JSON) Iterable<SanitizedSecretWithGroups> secretListingExpiringV3(@Auth AutomationClient automationClient,
      @PathParam("time") Long maxTime); @Timed @ExceptionMetered @Path("expiring/v4") @GET @Produces(APPLICATION_JSON) SanitizedSecretWithGroupsListAndCursor secretListingExpiringV4(
      @Auth AutomationClient automationClient,
      @QueryParam("maxTime") Long maxTime,
      @QueryParam("limit") Integer limit,
      @QueryParam("cursor") String cursor); @Timed @ExceptionMetered @Path("{name}/backfill-expiration") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) boolean backfillExpiration(@Auth AutomationClient automationClient, @PathParam("name") String name, List<String> passwords); @Timed @ExceptionMetered @Path("{name}/backfill-hmac") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) boolean backfillHmac(@Auth AutomationClient automationClient, @PathParam("name") String name); @Timed @ExceptionMetered @Path("expiring/{time}/{name}") @GET @Produces(APPLICATION_JSON) Iterable<String> secretListingExpiringForGroup(@Auth AutomationClient automationClient,
      @PathParam("time") Long time, @PathParam("name") String name); @Timed @ExceptionMetered @GET @Path("{name}") @Produces(APPLICATION_JSON) SecretDetailResponseV2 secretInfo(@Auth AutomationClient automationClient,
      @PathParam("name") String name); @Timed @ExceptionMetered @GET @Path("{name}/sanitized") @Produces(APPLICATION_JSON) SanitizedSecret getSanitizedSecret(@Auth AutomationClient automationClient,
      @PathParam("name") String name); @Timed @ExceptionMetered @POST @Path("request/contents") @Produces(APPLICATION_JSON) SecretContentsResponseV2 secretContents(@Auth AutomationClient automationClient,
      @Valid SecretContentsRequestV2 request); @Timed @ExceptionMetered @GET @Path("{name}/versions") @Produces(APPLICATION_JSON) Iterable<SecretDetailResponseV2> secretVersions(@Auth AutomationClient automationClient,
      @PathParam("name") String name, @QueryParam("versionIdx") int versionIdx,
      @QueryParam("numVersions") int numVersions); @Timed @ExceptionMetered @Path("{name}/setversion") @POST Response resetSecretVersion(@Auth AutomationClient automationClient,
      @Valid SetSecretVersionRequestV2 request); @Timed @ExceptionMetered @GET @Path("{name}/groups") @Produces(APPLICATION_JSON) Iterable<String> secretGroupsListing(@Auth AutomationClient automationClient,
      @PathParam("name") String name); @Timed @ExceptionMetered @PUT @Path("{name}/groups") @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) Iterable<String> modifySecretGroups(@Auth AutomationClient automationClient,
      @PathParam("name") String name, @Valid ModifyGroupsRequestV2 request); @Timed @ExceptionMetered @DELETE @Path("{name}") Response deleteSecretSeries(@Auth AutomationClient automationClient,
      @PathParam("name") String name); }
SecretResource { @Timed @ExceptionMetered @Path("{name}/backfill-expiration") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) public boolean backfillExpiration(@Auth AutomationClient automationClient, @PathParam("name") String name, List<String> passwords) { Optional<Secret> secretOptional = secretController.getSecretByName(name); if (!secretOptional.isPresent()) { throw new NotFoundException("No such secret: " + name); } Secret secret = secretOptional.get(); Optional<Instant> existingExpiry = Optional.empty(); if (secret.getExpiry() > 0) { existingExpiry = Optional.of(Instant.ofEpochMilli(secret.getExpiry()*1000)); } String secretName = secret.getName(); byte[] secretContent = Base64.getDecoder().decode(secret.getSecret()); passwords.add(""); Instant expiry = null; if (secretName.endsWith(".crt") || secretName.endsWith(".pem") || secretName.endsWith(".key")) { expiry = ExpirationExtractor.expirationFromEncodedCertificateChain(secretContent); } else if (secretName.endsWith(".gpg") || secretName.endsWith(".pgp")) { expiry = ExpirationExtractor.expirationFromOpenPGP(secretContent); } else if (secretName.endsWith(".p12") || secretName.endsWith(".pfx")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("PKCS12", password, secretContent); } } else if (secretName.endsWith(".jceks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JCEKS", password, secretContent); } } else if (secretName.endsWith(".jks")) { while (expiry == null && !passwords.isEmpty()) { String password = passwords.remove(0); expiry = ExpirationExtractor.expirationFromKeystore("JKS", password, secretContent); } } if (expiry != null) { if (existingExpiry.isPresent()) { long offset = existingExpiry.get().until(expiry, HOURS); if (offset > 24 || offset < -24) { logger.warn( "Extracted expiration of secret {} differs from actual by more than {} hours (extracted = {}, database = {}).", secretName, offset, expiry, existingExpiry.get()); } return true; } logger.info("Found expiry for secret {}: {}", secretName, expiry.getEpochSecond()); boolean success = secretDAO.setExpiration(name, expiry); if (success) { Map<String, String> extraInfo = new HashMap<>(); extraInfo.put("backfilled expiry", Long.toString(expiry.getEpochSecond())); auditLog.recordEvent(new Event(Instant.now(), EventTag.SECRET_BACKFILLEXPIRY, automationClient.getName(), name, extraInfo)); } return success; } logger.info("Unable to determine expiry for secret {}", secretName); return false; } @Inject  SecretResource(SecretController secretController, AclDAOFactory aclDAOFactory,
      GroupDAOFactory groupDAOFactory, SecretDAOFactory secretDAOFactory, AuditLog auditLog,
      SecretSeriesDAOFactory secretSeriesDAOFactory, ContentCryptographer cryptographer,
      @Readonly SecretController secretControllerReadOnly); @Timed @ExceptionMetered @POST @Consumes(APPLICATION_JSON) Response createSecret(@Auth AutomationClient automationClient,
      @Valid CreateSecretRequestV2 request); @Timed @ExceptionMetered @Path("{name}") @POST @Consumes(APPLICATION_JSON) Response createOrUpdateSecret(@Auth AutomationClient automationClient,
      @PathParam("name") String name,
      @Valid CreateOrUpdateSecretRequestV2 request); @Timed @ExceptionMetered @Path("{name}/partialupdate") @POST @Consumes(APPLICATION_JSON) Response partialUpdateSecret(@Auth AutomationClient automationClient,
      @PathParam("name") String name,
      @Valid PartialUpdateSecretRequestV2 request); @Timed @ExceptionMetered @GET @Produces(APPLICATION_JSON) Iterable<String> secretListing(@Auth AutomationClient automationClient,
      @QueryParam("idx") Integer idx, @QueryParam("num") Integer num,
      @DefaultValue("true") @QueryParam("newestFirst") boolean newestFirst); @Timed @ExceptionMetered @Path("/v2") @GET @Produces(APPLICATION_JSON) Iterable<SanitizedSecret> secretListingV2(@Auth AutomationClient automationClient,
      @QueryParam("idx") Integer idx, @QueryParam("num") Integer num,
      @DefaultValue("true") @QueryParam("newestFirst") boolean newestFirst); @Timed @ExceptionMetered @Path("expiring/{time}") @GET @Produces(APPLICATION_JSON) Iterable<String> secretListingExpiring(@Auth AutomationClient automationClient, @PathParam("time") Long time); @Timed @ExceptionMetered @Path("expiring/v2/{time}") @GET @Produces(APPLICATION_JSON) Iterable<SanitizedSecret> secretListingExpiringV2(@Auth AutomationClient automationClient, @PathParam("time") Long time); @Timed @ExceptionMetered @Path("expiring/v3/{time}") @GET @Produces(APPLICATION_JSON) Iterable<SanitizedSecretWithGroups> secretListingExpiringV3(@Auth AutomationClient automationClient,
      @PathParam("time") Long maxTime); @Timed @ExceptionMetered @Path("expiring/v4") @GET @Produces(APPLICATION_JSON) SanitizedSecretWithGroupsListAndCursor secretListingExpiringV4(
      @Auth AutomationClient automationClient,
      @QueryParam("maxTime") Long maxTime,
      @QueryParam("limit") Integer limit,
      @QueryParam("cursor") String cursor); @Timed @ExceptionMetered @Path("{name}/backfill-expiration") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) boolean backfillExpiration(@Auth AutomationClient automationClient, @PathParam("name") String name, List<String> passwords); @Timed @ExceptionMetered @Path("{name}/backfill-hmac") @POST @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) boolean backfillHmac(@Auth AutomationClient automationClient, @PathParam("name") String name); @Timed @ExceptionMetered @Path("expiring/{time}/{name}") @GET @Produces(APPLICATION_JSON) Iterable<String> secretListingExpiringForGroup(@Auth AutomationClient automationClient,
      @PathParam("time") Long time, @PathParam("name") String name); @Timed @ExceptionMetered @GET @Path("{name}") @Produces(APPLICATION_JSON) SecretDetailResponseV2 secretInfo(@Auth AutomationClient automationClient,
      @PathParam("name") String name); @Timed @ExceptionMetered @GET @Path("{name}/sanitized") @Produces(APPLICATION_JSON) SanitizedSecret getSanitizedSecret(@Auth AutomationClient automationClient,
      @PathParam("name") String name); @Timed @ExceptionMetered @POST @Path("request/contents") @Produces(APPLICATION_JSON) SecretContentsResponseV2 secretContents(@Auth AutomationClient automationClient,
      @Valid SecretContentsRequestV2 request); @Timed @ExceptionMetered @GET @Path("{name}/versions") @Produces(APPLICATION_JSON) Iterable<SecretDetailResponseV2> secretVersions(@Auth AutomationClient automationClient,
      @PathParam("name") String name, @QueryParam("versionIdx") int versionIdx,
      @QueryParam("numVersions") int numVersions); @Timed @ExceptionMetered @Path("{name}/setversion") @POST Response resetSecretVersion(@Auth AutomationClient automationClient,
      @Valid SetSecretVersionRequestV2 request); @Timed @ExceptionMetered @GET @Path("{name}/groups") @Produces(APPLICATION_JSON) Iterable<String> secretGroupsListing(@Auth AutomationClient automationClient,
      @PathParam("name") String name); @Timed @ExceptionMetered @PUT @Path("{name}/groups") @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) Iterable<String> modifySecretGroups(@Auth AutomationClient automationClient,
      @PathParam("name") String name, @Valid ModifyGroupsRequestV2 request); @Timed @ExceptionMetered @DELETE @Path("{name}") Response deleteSecretSeries(@Auth AutomationClient automationClient,
      @PathParam("name") String name);  }