From 435fee580df36d0d299e9d8c1869ef58dcb8ec7c Mon Sep 17 00:00:00 2001 From: Kendrick Cline Date: Tue, 22 Aug 2017 14:05:23 -0400 Subject: [PATCH] KMS: Add set-primary-version to KMS snippets This also fixes failing integration tests. I've executed the tests on my personal project to confirm. Important changes are: * Each test execution creates a CryptoKey. * CryptoKeys are free, and creating a new CryptoKey fixes the caching problem of creating a key version, setting it as primary, then relying on the new primary in a subsequent call. * Destruction tests now destroy keys that they created rather than the primary key set for the execution. * Added the setPrimaryVersion snippet w/ tests. --- .../java/com/example/SnippetCommands.java | 41 +++++++---- kms/src/main/java/com/example/Snippets.java | 23 ++++++ kms/src/test/java/com/example/SnippetsIT.java | 73 ++++++++++++------- 3 files changed, 96 insertions(+), 41 deletions(-) diff --git a/kms/src/main/java/com/example/SnippetCommands.java b/kms/src/main/java/com/example/SnippetCommands.java index 53cb04fa182..8d8b85b1022 100644 --- a/kms/src/main/java/com/example/SnippetCommands.java +++ b/kms/src/main/java/com/example/SnippetCommands.java @@ -109,6 +109,13 @@ public void run() throws IOException { } } + public static class SetPrimaryVersionCommand extends KeyVersionArgs implements Command { + + public void run() throws IOException { + Snippets.setPrimaryVersion(projectId, locationId, keyRingId, cryptoKeyId, version); + } + } + public static class GetKeyRingPolicyCommand extends KeyRingArgs implements Command { public void run() throws IOException { Snippets.getKeyRingPolicy(projectId, locationId, keyRingId); @@ -124,12 +131,12 @@ public void run() throws IOException { public static class AddMemberToKeyRingPolicyCommand extends KeyRingArgs implements Command { @Argument(metaVar = "member", required = true, index = 1, usage = "The member to add.\n" - + "See https://g.co/cloud/kms/docs/reference/rest/v1/Policy#binding " - + "for valid values.") + + "See https://g.co/cloud/kms/docs/reference/rest/v1/Policy#binding " + + "for valid values.") String member; @Argument(metaVar = "role", required = true, index = 2, usage = "The role for the member.\n" - + "See https://g.co/cloud/iam/docs/understanding-roles for valid values.") + + "See https://g.co/cloud/iam/docs/understanding-roles for valid values.") String role; public void run() throws IOException { @@ -140,28 +147,29 @@ public void run() throws IOException { public static class AddMemberToCryptoKeyPolicyCommand extends KeyArgs implements Command { @Argument(metaVar = "member", required = true, index = 2, usage = "The member to add.\n" - + "See https://g.co/cloud/kms/docs/reference/rest/v1/Policy#binding " - + "for valid values.") + + "See https://g.co/cloud/kms/docs/reference/rest/v1/Policy#binding " + + "for valid values.") String member; @Argument(metaVar = "role", required = true, index = 3, usage = "The role for the member.\n" - + "See https://g.co/cloud/iam/docs/understanding-roles for valid values.") + + "See https://g.co/cloud/iam/docs/understanding-roles for valid values.") String role; public void run() throws IOException { - Snippets.addMemberToCryptoKeyPolicy(projectId, locationId, keyRingId, cryptoKeyId, member, role); + Snippets + .addMemberToCryptoKeyPolicy(projectId, locationId, keyRingId, cryptoKeyId, member, role); } } public static class RemoveMemberFromKeyRingPolicyCommand extends KeyRingArgs implements Command { @Argument(metaVar = "member", required = true, index = 1, usage = "The member to add.\n" - + "See https://g.co/cloud/kms/docs/reference/rest/v1/Policy#binding " - + "for valid values.") + + "See https://g.co/cloud/kms/docs/reference/rest/v1/Policy#binding " + + "for valid values.") String member; @Argument(metaVar = "role", required = true, index = 2, usage = "The role for the member.\n" - + "See https://g.co/cloud/iam/docs/understanding-roles for valid values.") + + "See https://g.co/cloud/iam/docs/understanding-roles for valid values.") String role; public void run() throws IOException { @@ -172,16 +180,18 @@ public void run() throws IOException { public static class RemoveMemberFromCryptoKeyPolicyCommand extends KeyArgs implements Command { @Argument(metaVar = "member", required = true, index = 2, usage = "The member to add.\n" - + "See https://g.co/cloud/kms/docs/reference/rest/v1/Policy#binding " - + "for valid values.") + + "See https://g.co/cloud/kms/docs/reference/rest/v1/Policy#binding " + + "for valid values.") String member; @Argument(metaVar = "role", required = true, index = 3, usage = "The role for the member.\n" - + "See https://g.co/cloud/iam/docs/understanding-roles for valid values.") + + "See https://g.co/cloud/iam/docs/understanding-roles for valid values.") String role; public void run() throws IOException { - Snippets.removeMemberFromCryptoKeyPolicy(projectId, locationId, keyRingId, cryptoKeyId, member, role); + Snippets + .removeMemberFromCryptoKeyPolicy(projectId, locationId, keyRingId, cryptoKeyId, member, + role); } } @@ -198,6 +208,7 @@ public void run() throws IOException { @SubCommand(name = "destroyCryptoKeyVersion", impl = DestroyCryptoKeyVersionCommand.class), @SubCommand(name = "getKeyRingPolicy", impl = GetKeyRingPolicyCommand.class), @SubCommand(name = "getCryptoKeyPolicy", impl = GetCryptoKeyPolicyCommand.class), + @SubCommand(name = "setPrimaryVersion", impl = SetPrimaryVersionCommand.class), @SubCommand(name = "addMemberToKeyRingPolicy", impl = AddMemberToKeyRingPolicyCommand.class), @SubCommand(name = "addMemberToCryptoKeyPolicy", impl = AddMemberToCryptoKeyPolicyCommand.class), @@ -205,6 +216,6 @@ public void run() throws IOException { impl = RemoveMemberFromKeyRingPolicyCommand.class), @SubCommand(name = "removeMemberFromCryptoKeyPolicy", impl = RemoveMemberFromCryptoKeyPolicyCommand.class) - }) + }) Command command; } diff --git a/kms/src/main/java/com/example/Snippets.java b/kms/src/main/java/com/example/Snippets.java index e862e0befe1..cacda5dc343 100644 --- a/kms/src/main/java/com/example/Snippets.java +++ b/kms/src/main/java/com/example/Snippets.java @@ -20,6 +20,7 @@ import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.services.cloudkms.v1.CloudKMS; +import com.google.api.services.cloudkms.v1.CloudKMS.Projects.Locations.KeyRings.CryptoKeys.UpdatePrimaryVersion; import com.google.api.services.cloudkms.v1.CloudKMSScopes; import com.google.api.services.cloudkms.v1.model.Binding; import com.google.api.services.cloudkms.v1.model.CryptoKey; @@ -31,6 +32,7 @@ import com.google.api.services.cloudkms.v1.model.ListKeyRingsResponse; import com.google.api.services.cloudkms.v1.model.Policy; import com.google.api.services.cloudkms.v1.model.SetIamPolicyRequest; +import com.google.api.services.cloudkms.v1.model.UpdateCryptoKeyPrimaryVersionRequest; import java.io.IOException; import java.util.Collections; import java.util.List; @@ -525,6 +527,27 @@ public static void listCryptoKeyVersions( } } + /** + * Sets a version as the primary version for a crypto key. + */ + public static void setPrimaryVersion(String projectId, String locationId, String keyRingId, + String cryptoKeyId, String versionId) throws IOException { + // Create the Cloud KMS client. + CloudKMS kms = createAuthorizedClient(); + + // Resource name of the key version. + String resourceName = String + .format("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", + projectId, locationId, keyRingId, cryptoKeyId); + + CryptoKey key = kms.projects().locations().keyRings().cryptoKeys() + .updatePrimaryVersion(resourceName, + new UpdateCryptoKeyPrimaryVersionRequest().setCryptoKeyVersionId(versionId)).execute(); + + System.out.println(key); + + } + public static void main(String[] args) throws IOException, CmdLineException { SnippetCommands commands = new SnippetCommands(); diff --git a/kms/src/test/java/com/example/SnippetsIT.java b/kms/src/test/java/com/example/SnippetsIT.java index fe37acbd70c..301a55cde40 100644 --- a/kms/src/test/java/com/example/SnippetsIT.java +++ b/kms/src/test/java/com/example/SnippetsIT.java @@ -19,7 +19,11 @@ import com.google.api.client.googleapis.json.GoogleJsonError; import com.google.api.client.googleapis.json.GoogleJsonResponseException; - +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -28,11 +32,6 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * Integration (system) tests for {@link Snippets}. */ @@ -43,7 +42,7 @@ public class SnippetsIT { static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); static final String LOCATION_ID = "global"; static final String KEY_RING_ID = "test-snippets-key-ring"; - static final String CRYPTO_KEY_ID = "test-snippets-crypto-key"; + static final String CRYPTO_KEY_ID = UUID.randomUUID().toString(); static final String TEST_USER = "serviceAccount:" + "131304031188-compute@developer.gserviceaccount.com"; static final String TEST_ROLE = "roles/viewer"; @@ -64,7 +63,7 @@ public static void setUpClass() throws Exception { // Since you can't delete keyrings & cryptokeys atm, these tests assume they already exist. // Use the snippets functions to create them. try { - Snippets.createKeyRing(PROJECT_ID,LOCATION_ID, KEY_RING_ID); + Snippets.createKeyRing(PROJECT_ID, LOCATION_ID, KEY_RING_ID); // Since there's no way to delete keyrings atm, have two branches - one for the first time the // test is run, one for after the key already exists @@ -90,6 +89,17 @@ public static void setUpClass() throws Exception { assertThat(error.getMessage()).contains(String.format( "keyRings/%s/cryptoKeys/%s", KEY_RING_ID, CRYPTO_KEY_ID)); } + + // Create a CryptoKeyVersion and set it as primary. + Snippets.createCryptoKeyVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID); + Matcher matcher = Pattern.compile( + ".*cryptoKeyVersions/(\\d+)\",\"state\":\"ENABLED\".*", + Pattern.DOTALL | Pattern.MULTILINE).matcher(bout.toString().trim()); + assertTrue(matcher.matches()); + + String primaryVersion = matcher.group(1); + + Snippets.setPrimaryVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID, primaryVersion); } /** @@ -121,7 +131,8 @@ public static void tearDownClass() throws Exception { } String version = matcher.group(1); - Snippets.destroyCryptoKeyVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID, version); + Snippets + .destroyCryptoKeyVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID, version); } } @@ -130,8 +141,6 @@ public void setUp() throws Exception { bout = new ByteArrayOutputStream(); out = new PrintStream(bout); System.setOut(out); - - Snippets.createCryptoKeyVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID); } @After @@ -165,7 +174,7 @@ public void listCryptoKeyVersions_printsVersions() throws Exception { @Test public void disableCryptoKeyVersion_disables() throws Exception { - Snippets.listCryptoKeyVersions(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID); + Snippets.createCryptoKeyVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID); Matcher matcher = Pattern.compile(".*cryptoKeyVersions/(\\d+)\",\"state\":\"ENABLED\".*", Pattern.DOTALL | Pattern.MULTILINE).matcher(bout.toString().trim()); @@ -180,7 +189,7 @@ public void disableCryptoKeyVersion_disables() throws Exception { @Test public void destroyCryptoKeyVersion_destroys() throws Exception { - Snippets.listCryptoKeyVersions(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID); + Snippets.createCryptoKeyVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID); Matcher matcher = Pattern.compile(".*cryptoKeyVersions/(\\d+)\",\"state\":\"ENABLED\".*", Pattern.DOTALL | Pattern.MULTILINE).matcher(bout.toString().trim()); @@ -195,6 +204,24 @@ public void destroyCryptoKeyVersion_destroys() throws Exception { KEY_RING_ID, CRYPTO_KEY_ID, version)); } + @Test + public void setPrimaryVersion_createKeyAndSetPrimaryVersion() throws Exception { + // We can't test that setPrimaryVersion actually took effect via a list call because of + // caching. So we test that the call was successful. + Snippets.createCryptoKeyVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID); + + Matcher matcher = Pattern.compile(".*cryptoKeyVersions/(\\d+)\",\"state\":\"ENABLED\".*", + Pattern.DOTALL | Pattern.MULTILINE).matcher(bout.toString().trim()); + assertTrue(matcher.matches()); + + String version = matcher.group(1); + + Snippets.setPrimaryVersion(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID, version); + assertThat(bout.toString()).containsMatch(String.format( + "primary.*keyRings/%s/cryptoKeys/%s/cryptoKeyVersions/%s", + KEY_RING_ID, CRYPTO_KEY_ID, version)); + } + @Test public void addAndRemoveMemberToCryptoKeyPolicy_addsDisplaysAndRemoves() throws Exception { // Make sure the policy doesn't already have our test user @@ -259,21 +286,15 @@ public void addAndRemoveMemberToKeyRingPolicy_addsDisplaysAndRemoves() throws Ex @Test public void encryptDecrypt_encryptsAndDecrypts() throws Exception { - // Get an enabled crypto key version, since the primary version is likely disabled - Snippets.listCryptoKeyVersions(PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID); - Matcher matcher = Pattern.compile(".*cryptoKeyVersions/(\\d+)\",\"state\":\"ENABLED\".*", - Pattern.DOTALL | Pattern.MULTILINE).matcher(bout.toString().trim()); - assertTrue(matcher.matches()); - String version = matcher.group(1); - - byte[] encrypted = CryptFile.encrypt( - PROJECT_ID, KEY_RING_ID, CRYPTO_KEY_ID, version, ENCRYPT_STRING.getBytes()); + // Encrypt ENCRYPT_STRING with the current primary version. + byte[] ciphertext = CryptFile.encrypt( + PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID, ENCRYPT_STRING.getBytes()); - assertThat(new String(encrypted)).isNotEqualTo(ENCRYPT_STRING); + assertThat(new String(ciphertext)).isNotEqualTo(ENCRYPT_STRING); - byte[] decrypted = CryptFile.decrypt( - PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID, encrypted); + byte[] plaintext = CryptFile.decrypt( + PROJECT_ID, LOCATION_ID, KEY_RING_ID, CRYPTO_KEY_ID, ciphertext); - assertThat(new String(decrypted)).isEqualTo(ENCRYPT_STRING); + assertThat(new String(plaintext)).isEqualTo(ENCRYPT_STRING); } }