diff --git a/README.md b/README.md index 07f2c2aaf9..b2fb61e66c 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,10 @@ on Google Cloud Platform. * [Stackdriver Logging](#stackdriver-logging) * [Stackdriver Monitoring](#stackdriver-monitoring) * [Stackdriver Trace](#stackdriver-trace) - * [**Networking**](#management-tools) + * [**Networking**](#networking) * [Google Cloud DNS](#google-cloud-dns) + * [**Identity and Security**](#identity-and-security) + * [Google Cloud Key Management Service](#google-cloud-key-management-service) * [Other sample applications](#other-sample-applications) * [Bookshelf tutorial app](#bookshelf-tutorial-app) * [LabelCat](#labelcat) @@ -382,6 +384,20 @@ View the [Google Cloud DNS Node.js sample][dns_sample]. [dns_docs]: https://cloud.google.com/dns/docs/ [dns_sample]: dns +### Identity and Security + +#### Google Cloud Key Management Service + +The [Cloud KMS API][kms_docs] is a service that allows you to keep encryption +keys centrally in the cloud, for direct use by cloud services. + +[kms_docs]: https://cloud.google.com/kms/docs/ + +View the [Google Cloud Key Management Service Node.js sample][kms_sample]. + +[kms_docs]: https://cloud.google.com/kms/docs/ +[kms_sample]: dns + ## Other sample applications ### Bookshelf tutorial app diff --git a/circle.yml b/circle.yml index 6a37f58f3d..98a916566b 100644 --- a/circle.yml +++ b/circle.yml @@ -71,6 +71,6 @@ dependencies: # Run your tests test: override: - - yarn run all-cover + - node scripts/test post: - - nyc report --reporter=lcov > coverage.lcov && codecov + - nyc report --reporter=lcov > coverage.lcov && codecov || true diff --git a/kms/README.md b/kms/README.md index 42bb05dcf7..15b0961f7b 100644 --- a/kms/README.md +++ b/kms/README.md @@ -35,3 +35,55 @@ Run the sample: [quickstart_docs]: https://cloud.google.com/kms/docs [quickstart_code]: hostedmodels.js + +### Keys + +View the [documentation][keys_docs] or the [source code][keys_code]. + +__Usage:__ `node keys.js --help` + +``` +Commands: + create Creates a crypto key. + decrypt Decrypts a file. + encrypt Encrypts a file. + get Gets a crypto key. + get-policy Gets a crypto key's IAM policy. + grant-access Adds a members to a crypto key's IAM policy. + keyrings Access key rings subcommands. + list Lists crypto keys. + revoke-access Removes a member from a crypto key's IAM policy. + set-primary Sets a crypto key's primary version. + versions Access crypto key versions subcommands. + +Options: + --help Show help [boolean] + --location, -l [string] [default: "global"] + --projectId, -p [string] + +Examples: + node keys.js keyrings create "my-key-ring" + node keys.js keyrings list + node keys.js keyrings get-policy "my-key-ring" + node keys.js keyrings grant-access "my-key-ring" "user:developer@company.com" "roles/viewer" + node keys.js keyrings revoke-access "my-key-ring" "user:developer@company.com" "roles/viewer" + node keys.js create "my-key-ring" "my-key" + node keys.js list + node keys.js encrypt "my-key-ring" "my-key" ./resources/plaintext.txt ./resources/plaintext.txt.encrypted + node keys.js decrypt "my-key-ring" "my-key" ./resources/plaintext.txt.encrypted ./resources/plaintext.txt.decrypted + node keys.js set-primary "my-key-ring" "my-key" 123 + node keys.js get-policy "my-key-ring" "my-key" + node keys.js grant-access "my-key-ring" "my-key" "user:developer@company.com" "roles/viewer" + node keys.js revoke-access "my-key-ring" "my-key" "user:developer@company.com" "roles/viewer" + node keys.js versions create "my-key-ring" "my-key" + node keys.js versions list "my-key-ring" "my-key" + node keys.js versions destroy "my-key-ring" "my-key" 123 + node keys.js versions restore "my-key-ring" "my-key" 123 + node keys.js versions disable "my-key-ring" "my-key" 123 + node keys.js versions enable "my-key-ring" "my-key" 123 + +For more information, see https://cloud.google.com/kms/docs +``` + +[keys_docs]: https://cloud.google.com/kms/docs +[keys_code]: keys.js diff --git a/kms/keys.js b/kms/keys.js new file mode 100644 index 0000000000..41f1477606 --- /dev/null +++ b/kms/keys.js @@ -0,0 +1,1467 @@ + /** + * Copyright 2017, Google, 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. + */ + +'use strict'; + +function createKeyRing (projectId, location, keyRingName) { + // [START kms_create_keyring] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the new key ring, e.g. "global" + // const location = 'global'; + + // The name of the new key ring, e.g. "my-new-key-ring" + // const keyRingName = 'my-new-key-ring'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + parent: `projects/${projectId}/locations/${location}`, + // This will be a path parameter in the request URL + keyRingId: keyRingName + }; + + // Creates a new key ring + cloudkms.projects.locations.keyRings.create(request, (err, keyRing) => { + if (err) { + console.log(err); + return; + } + + console.log(`Key ring ${keyRing.name} created.`); + }); + }); + // [END kms_create_keyring] +} + +function listKeyRings (projectId, location) { + // [START kms_list_keyrings] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location from which to list key rings, e.g. "global" + // const location = 'global'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + parent: `projects/${projectId}/locations/${location}` + }; + + // Lists key rings + cloudkms.projects.locations.keyRings.list(request, (err, result) => { + if (err) { + console.log(err); + return; + } + + const keyRings = result.keyRings || []; + + if (keyRings.length) { + keyRings.forEach((keyRing) => { + console.log(`${keyRing.name}:`); + console.log(` Created: ${new Date(keyRing.createTime)}`); + }); + } else { + console.log('No key rings found.'); + } + }); + }); + // [END kms_list_keyrings] +} + +function getKeyRing (projectId, location, keyRingName) { + // [START kms_get_keyring] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the key ring, e.g. "global" + // const location = 'global'; + + // The name of the key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}` + }; + + // Gets a key ring + cloudkms.projects.locations.keyRings.get(request, (err, keyRing) => { + if (err) { + console.log(err); + return; + } + + console.log(`Name: ${keyRing.name}`); + console.log(`Created: ${new Date(keyRing.createTime)}`); + }); + }); + // [END kms_get_keyring] +} + +function getKeyRingIamPolicy (projectId, location, keyRingName) { + // [START kms_get_keyring_policy] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the key ring, e.g. "global" + // const location = 'global'; + + // The name of the key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}` + }; + + // Gets the IAM policy of a key ring + cloudkms.projects.locations.keyRings.getIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + if (policy.bindings) { + policy.bindings.forEach((binding) => { + if (binding.members && binding.members.length) { + console.log(`${binding.role}:`); + binding.members.forEach((member) => { + console.log(` ${member}`); + }); + } + }); + } else { + console.log(`Policy for key ring ${keyRingName} is empty.`); + } + }); + }); + // [END kms_get_keyring_policy] +} + +function addMemberToKeyRingPolicy (projectId, location, keyRingName, member, role) { + // [START kms_add_member_to_keyring_policy] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the key ring, e.g. "global" + // const location = 'global'; + + // The name of the key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The member to add to the key ring, e.g. "user:developer@company.com" + // const member = 'user:developer@company.com'; + + // The role to give the member, e.g. "roles/viewer" + // const role = 'roles/viewer'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + let request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}` + }; + + // Gets the IAM policy of a key ring + cloudkms.projects.locations.keyRings.getIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + policy = Object.assign({ bindings: [] }, policy); + + const index = policy.bindings.findIndex((binding) => binding.role === role); + + // Add the role/member combo to the policy + const binding = Object.assign({ + role: role, + members: [] + }, policy.bindings[index]); + if (index === -1) { + policy.bindings.push(binding); + } + if (!binding.members.includes(member)) { + binding.members.push(member); + } + + request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}`, + // This will be the request body + resource: { + policy: policy + } + }; + + // Adds the member/role combo to the policy of the key ring + cloudkms.projects.locations.keyRings.setIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + console.log(`${member}/${role} combo added to policy for key ring ${keyRingName}.`); + if (policy.bindings) { + policy.bindings.forEach((binding) => { + if (binding.members && binding.members.length) { + console.log(`${binding.role}:`); + binding.members.forEach((member) => { + console.log(` ${member}`); + }); + } + }); + } else { + console.log(`Policy for key ring ${keyRingName} is empty.`); + } + }); + }); + }); + // [END kms_add_member_to_keyring_policy] +} + +function removeMemberFromKeyRingPolicy (projectId, location, keyRingName, member, role) { + // [START kms_remove_member_from_keyring_policy] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the key ring, e.g. "global" + // const location = 'global'; + + // The name of the key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The member to add to the key ring, e.g. "user:developer@company.com" + // const member = 'user:developer@company.com'; + + // The role to give the member, e.g. "roles/viewer" + // const role = 'roles/viewer'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + let request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}` + }; + + // Gets the IAM policy of a key ring + cloudkms.projects.locations.keyRings.getIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + policy = Object.assign({ bindings: [] }, policy); + + let index = policy.bindings.findIndex((binding) => binding.role === role); + + const binding = Object.assign({ + role: role, + members: [] + }, policy.bindings[index]); + if (index === -1) { + return; + } + if (!binding.members.includes(member)) { + return; + } + + // Remove the role/member combo from the policy + binding.members.splice(binding.members.indexOf(member), 1); + + request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}`, + // This will be the request body + resource: { + policy: policy + } + }; + + // Removes the role/member combo from the policy of the key ring + cloudkms.projects.locations.keyRings.setIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + console.log(`${member}/${role} combo removed from policy for key ring ${keyRingName}.`); + if (policy.bindings) { + policy.bindings.forEach((binding) => { + if (binding.members && binding.members.length) { + console.log(`${binding.role}:`); + binding.members.forEach((member) => { + console.log(` ${member}`); + }); + } + }); + } else { + console.log(`Policy for key ring ${keyRingName} is empty.`); + } + }); + }); + }); + // [END kms_remove_member_from_keyring_policy] +} + +function createCryptoKey (projectId, location, keyRingName, keyName) { + // [START kms_create_cryptokey] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the new crypto key's key ring, e.g. "global" + // const location = 'global'; + + // The name of the new crypto key's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name for the new crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + parent: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}`, + // This will be a path parameter in the request URL + cryptoKeyId: keyName, + + resource: { + // This will allow the API access to the key for encryption and decryption + purpose: 'ENCRYPT_DECRYPT' + } + }; + + // Creates a new key ring + cloudkms.projects.locations.keyRings.cryptoKeys.create(request, (err, cryptoKey) => { + if (err) { + console.log(err); + return; + } + + console.log(`Key ${cryptoKey.name} created.`); + }); + }); + // [END kms_create_cryptokey] +} + +function listCryptoKeys (projectId, location, keyRingName) { + // [START kms_list_cryptokeys] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the key ring from which to list crypto keys, e.g. "global" + // const location = 'global'; + + // The name of the key ring from which to list crypto keys, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + parent: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}` + }; + + // Creates a new key ring + cloudkms.projects.locations.keyRings.cryptoKeys.list(request, (err, result) => { + if (err) { + console.log(err); + return; + } + + const cryptoKeys = result.cryptoKeys || []; + + if (cryptoKeys.length) { + cryptoKeys.forEach((cryptoKey) => { + console.log(`${cryptoKey.name}:`); + console.log(` Created: ${new Date(cryptoKey.createTime)}`); + console.log(` Purpose: ${cryptoKey.purpose}`); + console.log(` Primary: ${cryptoKey.primary.name}`); + console.log(` State: ${cryptoKey.primary.state}`); + console.log(` Created: ${new Date(cryptoKey.primary.createTime)}`); + }); + } else { + console.log('No crypto keys found.'); + } + }); + }); + // [END kms_list_cryptokeys] +} + +function encrypt (projectId, location, keyRingName, keyName, infile, outfile) { + // [START kms_encrypt] + const fs = require('fs'); + + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The path to the file to encrypt, e.g. "./path/to/plaintext.txt" + // const infile = './path/to/plaintext.txt'; + + // The path where the encrypted file should be written, e.g. "./path/to/plaintext.txt.encrypted" + // const outfile = './path/to/plaintext.txt.encrypted'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + // Reads the file to be encrypted + fs.readFile(infile, (err, contentsBuffer) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}`, + // This will be the request body + resource: { + plaintext: contentsBuffer.toString('base64') + } + }; + + // Encrypts the file using the specified crypto key + cloudkms.projects.locations.keyRings.cryptoKeys.encrypt(request, (err, result) => { + if (err) { + console.log(err); + return; + } + + // Writes the encrypted file to disk + fs.writeFile(outfile, Buffer.from(result.ciphertext), (err) => { + if (err) { + console.log(err); + return; + } + + console.log(`Encrypted ${infile} using ${result.name}.`); + console.log(`Result saved to ${outfile}.`); + }); + }); + }); + }); + // [END kms_encrypt] +} + +function decrypt (projectId, location, keyRingName, keyName, infile, outfile) { + // [START kms_decrypt] + const fs = require('fs'); + + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The path to the file to decrypt, e.g. "./path/to/plaintext.txt.encrypted" + // const infile = './path/to/plaintext.txt.encrypted'; + + // The path where the decrypted file should be written, e.g. "./path/to/plaintext.txt.decrypted" + // const outfile = './path/to/plaintext.txt.decrypted'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + // Reads the file to be decrypted + fs.readFile(infile, 'utf8', (err, contentsBuffer) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}`, + // This will be the request body + resource: { + ciphertext: contentsBuffer + } + }; + + // Dencrypts the file using the specified crypto key + cloudkms.projects.locations.keyRings.cryptoKeys.decrypt(request, (err, result) => { + if (err) { + console.log(err); + return; + } + + // Writes the dencrypted file to disk + fs.writeFile(outfile, Buffer.from(result.plaintext, 'base64'), (err) => { + if (err) { + console.log(err); + return; + } + + console.log(`Decrypted ${infile}, result saved to ${outfile}.`); + }); + }); + }); + }); + // [END kms_decrypt] +} + +function getCryptoKey (projectId, location, keyRingName, keyName) { + // [START kms_get_cryptokey] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}` + }; + + // Gets a crypto key + cloudkms.projects.locations.keyRings.cryptoKeys.get(request, (err, cryptoKey) => { + if (err) { + console.log(err); + return; + } + + console.log(`Name: ${cryptoKey.name}:`); + console.log(`Created: ${new Date(cryptoKey.createTime)}`); + console.log(`Purpose: ${cryptoKey.purpose}`); + console.log(`Primary: ${cryptoKey.primary.name}`); + console.log(` State: ${cryptoKey.primary.state}`); + console.log(` Created: ${new Date(cryptoKey.primary.createTime)}`); + }); + }); + // [END kms_get_cryptokey] +} + +function setPrimaryCryptoKeyVersion (projectId, location, keyRingName, keyName, version) { + // [START kms_set_cryptokey_primary_version] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key versions's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key version's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the version's crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The version's id, e.g. 123 + // const version = 123; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}`, + // This will be the request body + resource: { + cryptoKeyVersionId: `${version}` + } + }; + + // Sets a crypto key's primary version + cloudkms.projects.locations.keyRings.cryptoKeys.updatePrimaryVersion(request, (err, cryptoKey) => { + if (err) { + console.log(err); + return; + } + + console.log(`Set ${version} as primary version for crypto key ${keyName}.\n`); + console.log(`Name: ${cryptoKey.name}:`); + console.log(`Created: ${new Date(cryptoKey.createTime)}`); + console.log(`Purpose: ${cryptoKey.purpose}`); + console.log(`Primary: ${cryptoKey.primary.name}`); + console.log(` State: ${cryptoKey.primary.state}`); + console.log(` Created: ${new Date(cryptoKey.primary.createTime)}`); + }); + }); + // [END kms_set_cryptokey_primary_version] +} + +function getCryptoKeyIamPolicy (projectId, location, keyRingName, keyName) { + // [START kms_get_cryptokey_policy] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}` + }; + + // Gets the IAM policy of a crypto key + cloudkms.projects.locations.keyRings.cryptoKeys.getIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + if (policy.bindings) { + policy.bindings.forEach((binding) => { + if (binding.members && binding.members.length) { + console.log(`${binding.role}:`); + binding.members.forEach((member) => { + console.log(` ${member}`); + }); + } + }); + } else { + console.log(`Policy for crypto key ${keyName} is empty.`); + } + }); + }); + // [END kms_get_cryptokey_policy] +} + +function addMemberToCryptoKeyPolicy (projectId, location, keyRingName, keyName, member, role) { + // [START kms_add_member_to_cryptokey_policy] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The member to add to the crypto key, e.g. "user:developer@company.com" + // const member = 'user:developer@company.com'; + + // The role to give the member, e.g. "roles/viewer" + // const role = 'roles/viewer'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + let request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}` + }; + + // Gets the IAM policy of a crypto key + cloudkms.projects.locations.keyRings.cryptoKeys.getIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + policy = Object.assign({ bindings: [] }, policy); + + const index = policy.bindings.findIndex((binding) => binding.role === role); + + // Add the role/member combo to the policy + const binding = Object.assign({ + role: role, + members: [] + }, policy.bindings[index]); + if (index === -1) { + policy.bindings.push(binding); + } + if (!binding.members.includes(member)) { + binding.members.push(member); + } + + request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}`, + // This will be the request body + resource: { + policy: policy + } + }; + + // Adds the member/role combo to the policy of the crypto key + cloudkms.projects.locations.keyRings.cryptoKeys.setIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + console.log(`${member}/${role} combo added to policy for crypto key ${keyName}.`); + if (policy.bindings) { + policy.bindings.forEach((binding) => { + if (binding.members && binding.members.length) { + console.log(`${binding.role}:`); + binding.members.forEach((member) => { + console.log(` ${member}`); + }); + } + }); + } else { + console.log(`Policy for crypto key ${keyName} is empty.`); + } + }); + }); + }); + // [END kms_add_member_to_cryptokey_policy] +} + +function removeMemberFromCryptoKeyPolicy (projectId, location, keyRingName, keyName, member, role) { + // [START kms_remove_member_from_cryptokey_policy] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The member to add to the crypto key, e.g. "user:developer@company.com" + // const member = 'user:developer@company.com'; + + // The role to give the member, e.g. "roles/viewer" + // const role = 'roles/viewer'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + let request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}` + }; + + // Gets the IAM policy of a crypto key + cloudkms.projects.locations.keyRings.cryptoKeys.getIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + policy = Object.assign({ bindings: [] }, policy); + + let index = policy.bindings.findIndex((binding) => binding.role === role); + + const binding = Object.assign({ + role: role, + members: [] + }, policy.bindings[index]); + if (index === -1) { + return; + } + if (!binding.members.includes(member)) { + return; + } + + // Remove the role/member combo from the policy + binding.members.splice(binding.members.indexOf(member), 1); + + request = { + // This will be a path parameter in the request URL + resource_: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}`, + // This will be the request body + resource: { + policy: policy + } + }; + + console.log(JSON.stringify(request, null, 2)); + + // Removes the member/role combo from the policy of the crypto key + cloudkms.projects.locations.keyRings.cryptoKeys.setIamPolicy(request, (err, policy) => { + if (err) { + console.log(err); + return; + } + + console.log(`${member}/${role} combo removed from policy for crypto key ${keyName}.`); + if (policy.bindings) { + policy.bindings.forEach((binding) => { + if (binding.members && binding.members.length) { + console.log(`${binding.role}:`); + binding.members.forEach((member) => { + console.log(` ${member}`); + }); + } + }); + } else { + console.log(`Policy for crypto key ${keyName} is empty.`); + } + }); + }); + }); + // [END kms_remove_member_from_cryptokey_policy] +} + +function createCryptoKeyVersion (projectId, location, keyRingName, keyName) { + // [START kms_create_cryptokey_version] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key versions's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key version's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the version's crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + parent: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}` + }; + + // Creates a new crypto key version + cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions.create(request, (err, cryptoKeyVersion) => { + if (err) { + console.log(err); + return; + } + + console.log(`Crypto key version ${cryptoKeyVersion.name} created.`); + }); + }); + // [END kms_create_cryptokey_version] +} + +function listCryptoKeyVersions (projectId, location, keyRingName, keyName) { + // [START kms_list_cryptokey_versions] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the crypto key from which to list versions, e.g. "my-key" + // const keyName = 'my-key-ring'; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + parent: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}` + }; + + // Creates a new key ring + cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions.list(request, (err, result) => { + if (err) { + console.log(err); + return; + } + + const cryptoKeyVersions = result.cryptoKeyVersions || []; + + if (cryptoKeyVersions.length) { + cryptoKeyVersions.forEach((version) => { + console.log(`${version.name}:`); + console.log(` Created: ${new Date(version.createTime)}`); + console.log(` State: ${version.state}`); + }); + } else { + console.log('No crypto key versions found.'); + } + }); + }); + // [END kms_list_cryptokey_versions] +} + +function destroyCryptoKeyVersion (projectId, location, keyRingName, keyName, version) { + // [START kms_destroy_cryptokey_version] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key versions's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key version's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the version's crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The version's id, e.g. 123 + // const version = 123; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}/cryptoKeyVersions/${version}` + }; + + // Destroys a crypto key version + cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions.destroy(request, (err, cryptoKeyVersion) => { + if (err) { + console.log(err); + return; + } + + console.log(`Crypto key version ${cryptoKeyVersion.name} destroyed.`); + }); + }); + // [END kms_destroy_cryptokey_version] +} + +function restoreCryptoKeyVersion (projectId, location, keyRingName, keyName, version) { + // [START kms_restore_cryptokey_version] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key versions's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key version's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the version's crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The version's id, e.g. 123 + // const version = 123; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + const request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}/cryptoKeyVersions/${version}` + }; + + // Restores a crypto key version + cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions.restore(request, (err, cryptoKeyVersion) => { + if (err) { + console.log(err); + return; + } + + console.log(`Crypto key version ${cryptoKeyVersion.name} restored.`); + }); + }); + // [END kms_restore_cryptokey_version] +} + +function enableCryptoKeyVersion (projectId, location, keyRingName, keyName, version) { + // [START kms_enable_cryptokey_version] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key versions's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key version's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the version's crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The version's id, e.g. 123 + // const version = 123; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + let request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}/cryptoKeyVersions/${version}` + }; + + // Gets a crypto key version + cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions.get(request, (err, cryptoKeyVersion) => { + if (err) { + console.log(err); + return; + } + + cryptoKeyVersion.state = 'ENABLED'; + + request = { + // This will be a path parameter in the request URL + name: request.name, + // This will be a query parameter in the request URL + updateMask: 'state', + // This will be the request body + resource: cryptoKeyVersion + }; + + // Enables a crypto key version + cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions.patch(request, (err, cryptoKeyVersion) => { + if (err) { + console.log(err); + return; + } + + console.log(`Crypto key version ${cryptoKeyVersion.name} enabled.`); + }); + }); + }); + // [END kms_enable_cryptokey_version] +} + +function disableCryptoKeyVersion (projectId, location, keyRingName, keyName, version) { + // [START kms_disable_cryptokey_version] + // Your Google Cloud Platform project ID + // const projectId = 'YOUR_PROJECT_ID'; + + // The location of the crypto key versions's key ring, e.g. "global" + // const location = 'global'; + + // The name of the crypto key version's key ring, e.g. "my-key-ring" + // const keyRingName = 'my-key-ring'; + + // The name of the version's crypto key, e.g. "my-key" + // const keyName = 'my-key'; + + // The version's id, e.g. 123 + // const version = 123; + + // Builds and authorizes a Cloud KMS client + buildAndAuthorizeService((err, cloudkms) => { + if (err) { + console.log(err); + return; + } + + let request = { + // This will be a path parameter in the request URL + name: `projects/${projectId}/locations/${location}/keyRings/${keyRingName}/cryptoKeys/${keyName}/cryptoKeyVersions/${version}` + }; + + // Gets a crypto key version + cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions.get(request, (err, cryptoKeyVersion) => { + if (err) { + console.log(err); + return; + } + + cryptoKeyVersion.state = 'DISABLED'; + + request = { + // This will be a path parameter in the request URL + name: request.name, + // This will be a query parameter in the request URL + updateMask: 'state', + // This will be the request body + resource: cryptoKeyVersion + }; + + // Disables a crypto key version + cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions.patch(request, (err, cryptoKeyVersion) => { + if (err) { + console.log(err); + return; + } + + console.log(`Crypto key version ${cryptoKeyVersion.name} disabled.`); + }); + }); + }); + // [END kms_disable_cryptokey_version] +} + +// [START kms_create_keyring] +// [START kms_list_keyrings] +// [START kms_get_keyring] +// [START kms_get_keyring_policy] +// [START kms_add_member_to_keyring_policy] +// [START kms_remove_member_from_keyring_policy] +// [START kms_create_cryptokey] +// [START kms_list_cryptokeys] +// [START kms_encrypt] +// [START kms_decrypt] +// [START kms_get_cryptokey] +// [START kms_set_cryptokey_primary_version] +// [START kms_get_cryptokey_policy] +// [START kms_add_member_to_cryptokey_policy] +// [START kms_remove_member_from_cryptokey_policy] +// [START kms_list_cryptokey_versions] +// [START kms_create_cryptokey_version] +// [START kms_destroy_cryptokey_version] +// [START kms_restore_cryptokey_version] +// [START kms_enable_cryptokey_version] +// [START kms_disable_cryptokey_version] +function buildAndAuthorizeService (callback) { + // Imports the Google APIs client library + const google = require('googleapis'); + + // Acquires credentials + google.auth.getApplicationDefault((err, authClient) => { + if (err) { + callback(err); + return; + } + + if (authClient.createScopedRequired && authClient.createScopedRequired()) { + authClient = authClient.createScoped([ + 'https://www.googleapis.com/auth/cloud-platform' + ]); + } + + // Instantiates an authorized client + const cloudkms = google.cloudkms({ + version: 'v1beta1', + auth: authClient + }); + + callback(null, cloudkms); + }); +} + +// [END kms_create_keyring] +// [END kms_list_keyrings] +// [END kms_get_keyring] +// [END kms_get_keyring_policy] +// [END kms_add_member_to_keyring_policy] +// [END kms_remove_member_from_keyring_policy] +// [END kms_create_cryptokey] +// [END kms_list_cryptokeys] +// [END kms_encrypt] +// [END kms_decrypt] +// [END kms_get_cryptokey] +// [END kms_set_cryptokey_primary_version] +// [END kms_get_cryptokey_policy] +// [END kms_add_member_to_cryptokey_policy] +// [END kms_remove_member_from_cryptokey_policy] +// [END kms_list_cryptokey_versions] +// [END kms_create_cryptokey_version] +// [END kms_destroy_cryptokey_version] +// [END kms_restore_cryptokey_version] +// [END kms_enable_cryptokey_version] +// [END kms_disable_cryptokey_version] + +const cli = require(`yargs`) + .demand(1) + .command( + `create `, + `Creates a crypto key.`, + {}, + (opts) => createCryptoKey(opts.projectId, opts.location, opts.keyRingName, opts.keyName) + ) + .command( + `decrypt `, + `Decrypts a file.`, + {}, + (opts) => decrypt(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.infile, opts.outfile) + ) + .command( + `encrypt `, + `Encrypts a file.`, + {}, + (opts) => encrypt(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.infile, opts.outfile) + ) + .command( + `get `, + `Gets a crypto key.`, + {}, + (opts) => getCryptoKey(opts.projectId, opts.location, opts.keyRingName, opts.keyName) + ) + .command( + `get-policy `, + `Gets a crypto key's IAM policy.`, + {}, + (opts) => getCryptoKeyIamPolicy(opts.projectId, opts.location, opts.keyRingName, opts.keyName) + ) + .command( + `grant-access `, + `Adds a members to a crypto key's IAM policy.`, + {}, + (opts) => addMemberToCryptoKeyPolicy(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.member, opts.role) + ) + .command( + `keyrings `, + `Access key rings subcommands.`, + (yargs) => { + yargs + .command( + `create `, + `Creates a key ring.`, + {}, + (opts) => createKeyRing(opts.projectId, opts.location, opts.keyRingName) + ) + .command( + `list`, + `Lists key rings.`, + {}, + (opts) => listKeyRings(opts.projectId, opts.location) + ) + .command( + `get `, + `Gets a key ring.`, + {}, + (opts) => getKeyRing(opts.projectId, opts.location, opts.keyRingName) + ) + .command( + `get-policy `, + `Gets a key ring's IAM policy.`, + {}, + (opts) => getKeyRingIamPolicy(opts.projectId, opts.location, opts.keyRingName) + ) + .command( + `grant-access `, + `Adds a members to a key ring's IAM policy.`, + {}, + (opts) => addMemberToKeyRingPolicy(opts.projectId, opts.location, opts.keyRingName, opts.member, opts.role) + ) + .command( + `revoke-access `, + `Removes a member from a key ring's IAM policy.`, + {}, + (opts) => removeMemberFromKeyRingPolicy(opts.projectId, opts.location, opts.keyRingName, opts.member, opts.role) + ); + }, + () => {} + ) + .command( + `list `, + `Lists crypto keys.`, + {}, + (opts) => listCryptoKeys(opts.projectId, opts.location, opts.keyRingName) + ) + .command( + `revoke-access `, + `Removes a member from a crypto key's IAM policy.`, + {}, + (opts) => removeMemberFromCryptoKeyPolicy(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.member, opts.role) + ) + .command( + `set-primary `, + `Sets a crypto key's primary version.`, + {}, + (opts) => setPrimaryCryptoKeyVersion(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.version) + ) + .command( + `versions `, + `Access crypto key versions subcommands.`, + (yargs) => { + yargs + .command( + `create `, + `Creates a crypto key version.`, + {}, + (opts) => createCryptoKeyVersion(opts.projectId, opts.location, opts.keyRingName, opts.keyName) + ) + .command( + `destroy `, + `Destroys a crypto key version.`, + {}, + (opts) => destroyCryptoKeyVersion(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.version) + ) + .command( + `disable `, + `Disables a crypto key version.`, + {}, + (opts) => disableCryptoKeyVersion(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.version) + ) + .command( + `enable `, + `Enables a crypto key version.`, + {}, + (opts) => enableCryptoKeyVersion(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.version) + ) + .command( + `list `, + `Lists crypto key versions.`, + {}, + (opts) => listCryptoKeyVersions(opts.projectId, opts.location, opts.keyRingName, opts.keyName) + ) + .command( + `restore `, + `Restores a crypto key version.`, + {}, + (opts) => restoreCryptoKeyVersion(opts.projectId, opts.location, opts.keyRingName, opts.keyName, opts.version) + ); + }, + () => {} + ) + .options({ + location: { + alias: 'l', + default: 'global', + global: true, + requiresArg: true, + type: 'string' + }, + projectId: { + alias: 'p', + default: process.env.GCLOUD_PROJECT, + global: true, + requiresArg: true, + type: 'string' + } + }) + .example(`node $0 keyrings create "my-key-ring"`) + .example(`node $0 keyrings list`) + .example(`node $0 keyrings get-policy "my-key-ring"`) + .example(`node $0 keyrings grant-access "my-key-ring" "user:developer@company.com" "roles/viewer"`) + .example(`node $0 keyrings revoke-access "my-key-ring" "user:developer@company.com" "roles/viewer"`) + .example(`node $0 create "my-key-ring" "my-key"`) + .example(`node $0 list`) + .example(`node $0 encrypt "my-key-ring" "my-key" ./resources/plaintext.txt ./resources/plaintext.txt.encrypted`) + .example(`node $0 decrypt "my-key-ring" "my-key" ./resources/plaintext.txt.encrypted ./resources/plaintext.txt.decrypted`) + .example(`node $0 set-primary "my-key-ring" "my-key" 123`) + .example(`node $0 get-policy "my-key-ring" "my-key"`) + .example(`node $0 grant-access "my-key-ring" "my-key" "user:developer@company.com" "roles/viewer"`) + .example(`node $0 revoke-access "my-key-ring" "my-key" "user:developer@company.com" "roles/viewer"`) + .example(`node $0 versions create "my-key-ring" "my-key"`) + .example(`node $0 versions list "my-key-ring" "my-key"`) + .example(`node $0 versions destroy "my-key-ring" "my-key" 123`) + .example(`node $0 versions restore "my-key-ring" "my-key" 123`) + .example(`node $0 versions disable "my-key-ring" "my-key" 123`) + .example(`node $0 versions enable "my-key-ring" "my-key" 123`) + .wrap(120) + .recommendCommands() + .epilogue(`For more information, see https://cloud.google.com/kms/docs`); + +if (module === require.main) { + cli.help().strict().argv; +} diff --git a/kms/package.json b/kms/package.json index fd321601e1..08cc91bb19 100644 --- a/kms/package.json +++ b/kms/package.json @@ -8,6 +8,7 @@ "test": "cd ..; npm run st -- --verbose kms/system-test/*.test.js" }, "dependencies": { - "googleapis": "16.1.0" + "googleapis": "16.1.0", + "yargs": "6.6.0" } } diff --git a/kms/quickstart.js b/kms/quickstart.js index 7fe7a80bc5..a3ce7eb710 100644 --- a/kms/quickstart.js +++ b/kms/quickstart.js @@ -43,12 +43,12 @@ google.auth.getApplicationDefault((err, authClient) => { version: 'v1beta1', auth: authClient }); - const params = { + const request = { parent: `projects/${projectId}/locations/${location}` }; // Lists key rings - cloudkms.projects.locations.keyRings.list(params, (err, result) => { + cloudkms.projects.locations.keyRings.list(request, (err, result) => { if (err) { console.error(err); return; @@ -57,7 +57,7 @@ google.auth.getApplicationDefault((err, authClient) => { const keyRings = result.keyRings || []; if (keyRings.length) { - console.log('Key kings:'); + console.log('Key rings:'); result.keyRings.forEach((keyRing) => console.log(keyRing.name)); } else { console.log(`No key rings found.`); diff --git a/kms/resources/plaintext.txt b/kms/resources/plaintext.txt new file mode 100644 index 0000000000..2af8b3cee8 --- /dev/null +++ b/kms/resources/plaintext.txt @@ -0,0 +1,4 @@ +So if you're lost and on your own +You can never surrender +And if your path won't lead you home +You can never surrender \ No newline at end of file diff --git a/kms/system-test/keys.test.js b/kms/system-test/keys.test.js new file mode 100644 index 0000000000..9406bafb87 --- /dev/null +++ b/kms/system-test/keys.test.js @@ -0,0 +1,199 @@ +/** + * Copyright 2017, Google, 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. + */ + +'use strict'; + +require(`../../system-test/_setup`); + +const fs = require(`fs`); +const path = require(`path`); + +const cmd = `node keys.js`; +const cwd = path.join(__dirname, `..`); +const keyRingName = `nodejs-docs-samples-test-ring`; +const keyNameOne = `nodejs-docs-samples-test-key-one`; +const member = `allAuthenticatedUsers`; +const role = `roles/viewer`; +const projectId = process.env.GCLOUD_PROJECT; + +const plaintext = path.join(__dirname, `../resources/plaintext.txt`); +const ciphertext = path.join(__dirname, `../resources/plaintext.txt.encrypted`); +const decrypted = path.join(__dirname, `../resources/plaintext.txt.decrypted`); + +const formattedKeyRingName = `projects/${projectId}/locations/global/keyRings/${keyRingName}`; +const formattedKeyName = `${formattedKeyRingName}/cryptoKeys/${keyNameOne}`; + +test.before.cb((t) => { + // Delete the ciphertext file, if it exists + fs.unlink(ciphertext, () => { + // Delete the decrypted file, if it exists + fs.unlink(decrypted, () => t.end()); + }); +}); + +test.after.always.cb((t) => { + // Delete the ciphertext file, if it exists + fs.unlink(ciphertext, () => { + // Delete the decrypted file, if it exists + fs.unlink(decrypted, () => t.end()); + }); +}); + +test.beforeEach(stubConsole); +test.afterEach.always(restoreConsole); + +// Key ring tests + +test.serial(`should create a key ring`, async (t) => { + const output = await runAsync(`${cmd} keyrings create "${keyRingName}"`, cwd); + if (!output.includes(`KeyRing ${formattedKeyRingName} already exists`)) { + t.true(output.includes(`Key ring ${formattedKeyRingName} created.`)); + } +}); + +test.serial(`should list key rings`, async (t) => { + await tryTest(async () => { + const output = await runAsync(`${cmd} keyrings list`, cwd); + t.true(output.includes(formattedKeyRingName)); + }).start(); +}); + +test.serial(`should get a key ring`, async (t) => { + const output = await runAsync(`${cmd} keyrings get "${keyRingName}"`, cwd); + t.true(output.includes(`Name: ${formattedKeyRingName}`)); + t.true(output.includes(`Created: `)); +}); + +// Key ring IAM tests + +test.serial(`should get a key ring's empty IAM policy`, async (t) => { + const output = await runAsync(`${cmd} keyrings get-policy "${keyRingName}"`, cwd); + t.true(output.includes(`Policy for key ring ${keyRingName} is empty.`)); +}); + +test.serial(`should grant access to a key ring`, async (t) => { + const output = await runAsync(`${cmd} keyrings grant-access "${keyRingName}" ${member} ${role}`, cwd); + t.true(output.includes(`${member}/${role} combo added to policy for key ring ${keyRingName}.`)); +}); + +test.serial(`should get a key ring's updated IAM policy`, async (t) => { + await tryTest(async () => { + const output = await runAsync(`${cmd} keyrings get-policy "${keyRingName}"`, cwd); + t.true(output.includes(`${role}:`)); + t.true(output.includes(` ${member}`)); + }).start(); +}); + +test.serial(`should revoke access to a key ring`, async (t) => { + const output = await runAsync(`${cmd} keyrings revoke-access "${keyRingName}" ${member} ${role}`, cwd); + t.true(output.includes(`${member}/${role} combo removed from policy for key ring ${keyRingName}.`)); +}); + +// Crypto key tests +test.serial(`should create a key`, async (t) => { + const output = await runAsync(`${cmd} create "${keyRingName}" "${keyNameOne}"`, cwd); + if (!output.includes(`CryptoKey ${formattedKeyName} already exists`)) { + t.true(output.includes(`Key ${formattedKeyName} created.`)); + } +}); + +test.serial(`should list keys`, async (t) => { + await tryTest(async () => { + const output = await runAsync(`${cmd} list "${keyRingName}"`, cwd); + t.true(output.includes(formattedKeyName)); + }).start(); +}); + +test.serial(`should get a key`, async (t) => { + const output = await runAsync(`${cmd} get "${keyRingName}" "${keyNameOne}"`, cwd); + t.true(output.includes(`Name: ${formattedKeyName}`)); + t.true(output.includes(`Created: `)); +}); + +test.serial(`should set a crypto key's primary version`, async (t) => { + const output = await runAsync(`${cmd} set-primary "${keyRingName}" "${keyNameOne}" 1`, cwd); + t.true(output.includes(`Set 1 as primary version for crypto key ${keyNameOne}.\n`)); +}); + +test.serial(`should encrypt a file`, async (t) => { + const output = await runAsync(`${cmd} encrypt "${keyRingName}" "${keyNameOne}" "${plaintext}" "${ciphertext}"`, cwd); + t.true(output.includes(`Encrypted ${plaintext} using ${formattedKeyName}/cryptoKeyVersions/1.`)); + t.true(output.includes(`Result saved to ${ciphertext}.`)); +}); + +test.serial(`should decrypt a file`, async (t) => { + const output = await runAsync(`${cmd} decrypt "${keyRingName}" "${keyNameOne}" "${ciphertext}" "${decrypted}"`, cwd); + t.true(output.includes(`Decrypted ${ciphertext}, result saved to ${decrypted}.`)); + t.is(fs.readFileSync(plaintext, 'utf8'), fs.readFileSync(decrypted, 'utf8')); +}); + +test.serial(`should create a crypto key version`, async (t) => { + const output = await runAsync(`${cmd} versions create "${keyRingName}" "${keyNameOne}"`, cwd); + t.true(output.includes(`Crypto key version ${formattedKeyName}/cryptoKeyVersions/`)); + t.true(output.includes(` created.`)); +}); + +test.serial(`should list crypto key versions`, async (t) => { + await tryTest(async () => { + const output = await runAsync(`${cmd} list "${keyRingName}"`, cwd); + t.true(output.includes(`${formattedKeyName}/cryptoKeyVersions/1`)); + t.true(output.includes(`${formattedKeyName}/cryptoKeyVersions/2`)); + }).start(); +}); + +test.serial(`should destroy a crypto key version`, async (t) => { + const output = await runAsync(`${cmd} versions destroy "${keyRingName}" "${keyNameOne}" 2`, cwd); + t.true(output.includes(`Crypto key version ${formattedKeyName}/cryptoKeyVersions/2 destroyed.`)); +}); + +test.serial(`should restore a crypto key version`, async (t) => { + const output = await runAsync(`${cmd} versions restore "${keyRingName}" "${keyNameOne}" 2`, cwd); + t.true(output.includes(`Crypto key version ${formattedKeyName}/cryptoKeyVersions/2 restored.`)); +}); + +test.serial(`should enable a crypto key version`, async (t) => { + const output = await runAsync(`${cmd} versions enable "${keyRingName}" "${keyNameOne}" 2`, cwd); + t.true(output.includes(`Crypto key version ${formattedKeyName}/cryptoKeyVersions/2 enabled.`)); +}); + +test.serial(`should disable a crypto key version`, async (t) => { + const output = await runAsync(`${cmd} versions disable "${keyRingName}" "${keyNameOne}" 2`, cwd); + t.true(output.includes(`Crypto key version ${formattedKeyName}/cryptoKeyVersions/2 disabled.`)); +}); + +// Crypto key IAM tests + +test.serial(`should get a crypto key's empty IAM policy`, async (t) => { + const output = await runAsync(`${cmd} get-policy "${keyRingName}" "${keyNameOne}"`, cwd); + t.true(output.includes(`Policy for crypto key ${keyNameOne} is empty.`)); +}); + +test.serial(`should grant access to a crypto key`, async (t) => { + const output = await runAsync(`${cmd} grant-access "${keyRingName}" "${keyNameOne}" ${member} ${role}`, cwd); + t.true(output.includes(`${member}/${role} combo added to policy for crypto key ${keyNameOne}.`)); +}); + +test.serial(`should get a crypto key's updated IAM policy`, async (t) => { + await tryTest(async () => { + const output = await runAsync(`${cmd} get-policy "${keyRingName}" "${keyNameOne}"`, cwd); + t.true(output.includes(`${role}:`)); + t.true(output.includes(` ${member}`)); + }).start(); +}); + +test.serial(`should revoke access to a crypto key`, async (t) => { + const output = await runAsync(`${cmd} revoke-access "${keyRingName}" "${keyNameOne}" ${member} ${role}`, cwd); + t.true(output.includes(`${member}/${role} combo removed from policy for crypto key ${keyNameOne}.`)); +}); diff --git a/kms/yarn.lock b/kms/yarn.lock index a0c5d4f458..f967475876 100644 --- a/kms/yarn.lock +++ b/kms/yarn.lock @@ -60,6 +60,14 @@ buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" @@ -74,6 +82,18 @@ chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" @@ -98,6 +118,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -115,6 +139,12 @@ ecdsa-sig-formatter@1.0.9: base64url "^2.0.0" safe-buffer "^5.0.1" +error-ex@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.0.tgz#e67b43f3e82c96ea3a584ffee0b9fc3325d802d9" + dependencies: + is-arrayish "^0.2.1" + escape-string-regexp@^1.0.2: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -127,6 +157,13 @@ extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -149,6 +186,10 @@ generate-object-property@^1.1.0: dependencies: is-property "^1.0.0" +get-caller-file@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + getpass@^0.1.1: version "0.1.6" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" @@ -178,6 +219,10 @@ googleapis@16.1.0: google-auth-library "~0.10.0" string-template "~1.0.0" +graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -219,6 +264,10 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" +hosted-git-info@^2.1.4: + version "2.1.5" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.1.5.tgz#0ba81d90da2e25ab34a332e6ec77936e1598118b" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -227,6 +276,26 @@ http-signature@~1.1.0: jsprim "^1.2.2" sshpk "^1.7.0" +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + is-my-json-valid@^2.12.4: version "2.15.0" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b" @@ -244,6 +313,10 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -295,6 +368,22 @@ jws@^3.0.0, jws@^3.1.4: jwa "^1.1.4" safe-buffer "^5.0.1" +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + lodash.noop@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" @@ -321,10 +410,53 @@ node-forge@^0.6.46: version "0.6.46" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.46.tgz#04a8a1c336eb72ef6f434ba7c854d608916c328d" +normalize-package-data@^2.3.2: + version "2.3.5" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.5.tgz#8d924f142960e1777e7ffe170543631cc7cb02df" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + oauth-sign@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -343,6 +475,21 @@ qs@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + request@^2.72.0, request@^2.74.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" @@ -368,16 +515,46 @@ request@^2.72.0, request@^2.74.0: tunnel-agent "~0.4.1" uuid "^3.0.0" +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + safe-buffer@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" +"semver@2 || 3 || 4 || 5": + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" dependencies: hoek "2.x.x" +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + sshpk@^1.7.0: version "1.10.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.10.1.tgz#30e1a5d329244974a1af61511339d595af6638b0" @@ -397,16 +574,30 @@ string-template@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96" +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + stringstream@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" -strip-ansi@^3.0.0: +strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" dependencies: ansi-regex "^2.0.0" +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -429,12 +620,58 @@ uuid@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + verror@1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" dependencies: extsprintf "1.0.2" +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yargs-parser@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + dependencies: + camelcase "^3.0.0" + +yargs@6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^4.2.0" diff --git a/package.json b/package.json index 1ddc22a7a0..40a8102fe6 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,8 @@ "cover": "nyc --cache npm test && nyc report --reporter=html", "system-test": "npm run st -- --verbose system-test/**/*.test.js **/system-test/**/*.test.js", "system-cover": "npm run pretest && nyc --cache npm run system-test && nyc report --reporter=html", - "all-test": "npm run st -- --no-power-assert bigquery/system-test/*.test.js test/**/*.test.js **/test/**/*.test.js system-test/**/*.test.js **/system-test/**/*.test.js", + "all-test": "npm run st -- --no-power-assert bigquery/system-test/*.test.js kms/system-test/*.test.js test/**/*.test.js **/test/**/*.test.js system-test/**/*.test.js **/system-test/**/*.test.js", + "ci-test": "npm run st -- --no-power-assert", "all-cover": "npm run pretest && nyc --cache npm run all-test && nyc report --reporter=html", "unify": "node scripts/unify" }, diff --git a/scripts/test b/scripts/test new file mode 100644 index 0000000000..ff76d40e7a --- /dev/null +++ b/scripts/test @@ -0,0 +1,76 @@ +#!/usr/bin/env node + +/** + * Copyright 2017, Google, 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. + */ + +const fs = require('fs'); +const path = require('path'); + +require('shelljs/global'); + +const rootDir = path.join(__dirname, '..'); + +if (process.env.CIRCLE_BRANCH === 'master') { + exec('yarn run all-cover', { cwd: rootDir }); +} else { + // Get list of changed files + const changedFiles = exec('git diff-tree --no-commit-id --name-only -r HEAD', { silent: true }).stdout.split('\n') + .filter((line) => line); + + // Get list of directories that have tests + const testDirs = getDirs(rootDir) + .map((name) => name.replace(`${rootDir}/`, '')) + .filter((name) => name.endsWith('/test') || name.endsWith('/system-test')); + + // Find intersection between changed files and test directories + const affectedTestsNames = {}; + changedFiles.forEach((name) => { + testDirs.forEach((dir) => { + const codeDir = `${dir.replace('/test', '').replace('/system-test', '')}/`; + + if (name.includes(codeDir)) { + affectedTestsNames[dir] = true; + } + }); + }); + + // Get final names of tests that need to be run + const affectedTests = Object.keys(affectedTestsNames) + .map((name) => `${name}/*.js`); + + if (affectedTests.length) { + // Run the affected tests + exec(`yarn run ci-test -- '${affectedTests.join(' ')}'`, { cwd: rootDir }); + } else { + // Just run the linting + exec('yarn run lint', { cwd: rootDir }); + } +} + +function getDirs (_path) { + const dirs = fs.readdirSync(_path) + .filter((name) => !name.includes('.')) + .filter((name) => !name.includes('node_modules')) + .filter((name) => !name.includes('bower_components')) + .map((name) => path.join(_path, name)) + .filter((name) => fs.statSync(name).isDirectory()); + + if (!dirs.length) { + return [_path]; + } + + return dirs + .reduce((cur, name) => cur.concat(getDirs(name)), []); +}