diff --git a/.kokoro/setup-vars.sh b/.kokoro/setup-vars.sh index e316b91de..cc2b91992 100644 --- a/.kokoro/setup-vars.sh +++ b/.kokoro/setup-vars.sh @@ -23,6 +23,8 @@ export GCN_STORAGE_2ND_PROJECT_KEY=${KOKORO_GFILE_DIR}/no-whitelist-key.json export GOOGLE_CLOUD_KMS_KEY_ASIA="projects/long-door-651/locations/asia/keyRings/test-key-asia/cryptoKeys/test-key-asia" export GOOGLE_CLOUD_KMS_KEY_US="projects/long-door-651/locations/us/keyRings/test-key-us/cryptoKeys/test-key-us" +# Second service account for testing HMAC feature +export HMAC_KEY_TEST_SECOND_SERVICE_ACCOUNT="gcs-hmac-system-test-alt@long-door-651.iam.gserviceaccount.com" # TODO: Switch with service-account pool export POOL_SAMPLES_PROJECT_ID=long-door-651 export POOL_SAMPLES_PROJECT_CREDENTIALS="${KOKORO_GFILE_DIR}/storage-hmac-samples-key.json" diff --git a/system-test/storage.ts b/system-test/storage.ts index 4638d8432..55bec11b2 100644 --- a/system-test/storage.ts +++ b/system-test/storage.ts @@ -2687,6 +2687,116 @@ describe('storage', () => { }); }); + describe('HMAC keys', () => { + // This is generally a valid service account for a project. + const ALTERNATE_SERVICE_ACCOUNT = `${process.env.PROJECT_ID}@appspot.gserviceaccount.com`; + const SERVICE_ACCOUNT = process.env.HMAC_KEY_TEST_SERVICE_ACCOUNT || ALTERNATE_SERVICE_ACCOUNT; + const HMAC_PROJECT = process.env.HMAC_KEY_TEST_SERVICE_ACCOUNT ? process.env.HMAC_KEY_TEST_PROJECT : process.env.PROJECT_ID; + // Second service account to test listing HMAC keys from different accounts. + const SECOND_SERVICE_ACCOUNT = process.env.HMAC_KEY_TEST_SECOND_SERVICE_ACCOUNT; + + let accessId: string; + + before(async () => { + await deleteHmacKeys(SERVICE_ACCOUNT, HMAC_PROJECT!); + }); + + after(async () => { + await deleteHmacKeys(SERVICE_ACCOUNT, HMAC_PROJECT!); + }); + + it('should create an HMAC key for a service account', async () => { + const [hmacKey, secret] = await storage.createHmacKey(SERVICE_ACCOUNT, {projectId: HMAC_PROJECT}); + // We should always get a 40 character secret, which is valid base64. + assert.strictEqual(secret.length, 40); + accessId = hmacKey.id!; + const metadata = hmacKey.metadata!; + assert.strictEqual(metadata.accessId, accessId); + assert.strictEqual(metadata.state, 'ACTIVE'); + assert.strictEqual(metadata.projectId, HMAC_PROJECT); + assert.strictEqual(metadata.serviceAccountEmail, SERVICE_ACCOUNT); + assert(typeof metadata.etag === 'string'); + assert(typeof metadata.timeCreated === 'string'); + assert(typeof metadata.updated === 'string'); + }); + + it('should get metadata for an HMAC key', async () => { + const hmacKey = storage.hmacKey(accessId, {projectId: HMAC_PROJECT}); + const [metadata] = await hmacKey.getMetadata(); + assert.strictEqual(metadata.accessId, accessId); + }); + + it('should show up from getHmacKeys() without serviceAccountEmail param', async () => { + const [hmacKeys] = await storage.getHmacKeys({projectId: HMAC_PROJECT}); + assert(hmacKeys.length > 0); + assert( + hmacKeys.some((hmacKey) => hmacKey.id === accessId), + 'created HMAC key not found from getHmacKeys result'); + }); + + it('should make the key INACTIVE', async () => { + const hmacKey = storage.hmacKey(accessId, {projectId: HMAC_PROJECT}); + let [metadata] = await hmacKey.setMetadata({state: 'INACTIVE'}); + assert.strictEqual(metadata.state, 'INACTIVE'); + + [metadata] = await hmacKey.getMetadata(); + assert.strictEqual(metadata.state, 'INACTIVE'); + }); + + it('should delete the key', async () => { + const hmacKey = storage.hmacKey(accessId, {projectId: HMAC_PROJECT}); + await hmacKey.delete(); + const [metadata] = await hmacKey.getMetadata(); + assert.strictEqual(metadata.state, 'DELETED'); + assert.strictEqual(hmacKey.metadata!.state, 'DELETED'); + }); + + it('deleted key should not show up from getHmacKeys() by default', async () => { + const [hmacKeys] = await storage.getHmacKeys({serviceAccountEmail: SERVICE_ACCOUNT, projectId: HMAC_PROJECT}); + assert(Array.isArray(hmacKeys)); + assert( + !hmacKeys.some((hmacKey) => hmacKey.id === accessId), + 'deleted HMAC key is found from getHmacKeys result'); + }); + + describe('second service account', () => { + before(async function () { + if (!SECOND_SERVICE_ACCOUNT) { + this.skip(); + return; + } + await deleteHmacKeys(SECOND_SERVICE_ACCOUNT, HMAC_PROJECT!); + }); + + it('should create key for a second service account', async () => { + const _ = await storage.createHmacKey(SECOND_SERVICE_ACCOUNT!, {projectId: HMAC_PROJECT}); + }); + + it('get HMAC keys for both service accounts', async () => { + // Create a key for the first service account + const _ = await storage.createHmacKey(SERVICE_ACCOUNT!, {projectId: HMAC_PROJECT}); + + const [hmacKeys] = await storage.getHmacKeys({projectId: HMAC_PROJECT}); + assert(hmacKeys.some((hmacKey) => + hmacKey.metadata!.serviceAccountEmail === SERVICE_ACCOUNT), + `Expected at least 1 key for service account: ${SERVICE_ACCOUNT}` + ); + assert(hmacKeys.some((hmacKey) => + hmacKey.metadata!.serviceAccountEmail === SECOND_SERVICE_ACCOUNT), + `Expected at least 1 key for service account: ${SECOND_SERVICE_ACCOUNT}` + ); + }); + + it('filter by service account email', async () => { + const [hmacKeys] = await storage.getHmacKeys({serviceAccountEmail: SECOND_SERVICE_ACCOUNT, projectId: HMAC_PROJECT}); + assert(hmacKeys.every((hmacKey) => + hmacKey.metadata!.serviceAccountEmail === SECOND_SERVICE_ACCOUNT), + 'HMAC key belonging to other service accounts unexpected' + ); + }); + }); + }); + describe('list files', () => { const DIRECTORY_NAME = 'directory-name'; @@ -3235,6 +3345,17 @@ describe('storage', () => { } } + async function deleteHmacKeys(serviceAccountEmail: string, projectId: string) { + const [hmacKeys] = await storage.getHmacKeys({ serviceAccountEmail, projectId }); + const limit = pLimit(10); + await Promise.all(hmacKeys.map((hmacKey) => limit(async () => { + if (hmacKey.metadata!.state === 'ACTIVE') { + await hmacKey.setMetadata({state:'INACTIVE' }); + } + await hmacKey.delete(); + }))); + } + // tslint:disable-next-line no-any function createFileAsync(fileObject: any) { return fileObject.file.save(fileObject.contents);