@@ -282,11 +282,171 @@ describe('Value Encryption', () => {
282282 } ) ;
283283} ) ;
284284
285+ describe ( 'per environment encryption E2E' , ( ) => {
286+ it ( 'sets up, trusts and untrusts users correctly' , ( ) => {
287+ const cwd = process . cwd ( ) ;
288+
289+ return withTempFiles ( { } , async ( inDir ) => {
290+ // run environmentless
291+ delete process . env . NODE_ENV ;
292+
293+ process . chdir ( inDir ( '.' ) ) ;
294+ process . env . APP_CONFIG_SECRETS_KEYCHAIN_FOLDER = inDir ( 'keychain' ) ;
295+
296+ const keys = await initializeKeysManually ( {
297+ name : 'Tester' ,
298+ email : 'test@example.com' ,
299+ } ) ;
300+
301+ const dirs = {
302+ keychain : inDir ( 'keychain' ) ,
303+ privateKey : inDir ( 'keychain/private-key.asc' ) ,
304+ publicKey : inDir ( 'keychain/public-key.asc' ) ,
305+ revocationCert : inDir ( 'keychain/revocation.asc' ) ,
306+ } ;
307+
308+ expect ( await initializeLocalKeys ( keys , dirs ) ) . toEqual ( {
309+ publicKeyArmored : keys . publicKeyArmored ,
310+ } ) ;
311+
312+ const publicKey = await loadPublicKey ( ) ;
313+ const privateKey = await loadPrivateKey ( ) ;
314+
315+ // this is what init-repo does
316+ await trustTeamMember ( publicKey , privateKey ) ;
317+
318+ // at this point, we should have ourselves trusted, and 1 symmetric key
319+ const { value : meta } = await loadMetaConfig ( ) ;
320+
321+ expect ( meta . teamMembers ) . toHaveProperty ( 'default' ) ;
322+ expect ( meta . encryptionKeys ) . toHaveProperty ( 'default' ) ;
323+ expect ( ( meta . teamMembers ! as any ) . default ) . toHaveLength ( 1 ) ;
324+ expect ( ( meta . encryptionKeys ! as any ) . default ) . toHaveLength ( 1 ) ;
325+
326+ const encryptionKey = await loadLatestSymmetricKey ( privateKey ) ;
327+ const encrypted = await encryptValue ( 'a secret value' , encryptionKey ) ;
328+ await expect ( decryptValue ( encrypted , encryptionKey ) ) . resolves . toBe ( 'a secret value' ) ;
329+
330+ // now lets create a new environment
331+ await trustTeamMember ( publicKey , privateKey , { ...defaultEnvOptions , override : 'prod' } ) ;
332+
333+ // at this point, we should have a default and a prod env with 1 trusted member and 1 key each
334+ const { value : prodEnvMeta } = await loadMetaConfig ( ) ;
335+
336+ expect ( prodEnvMeta . teamMembers ) . toHaveProperty ( 'default' ) ;
337+ expect ( prodEnvMeta . encryptionKeys ) . toHaveProperty ( 'default' ) ;
338+ expect ( ( prodEnvMeta . teamMembers ! as any ) . default ) . toHaveLength ( 1 ) ;
339+ expect ( ( prodEnvMeta . encryptionKeys ! as any ) . default ) . toHaveLength ( 1 ) ;
340+ expect ( prodEnvMeta . teamMembers ) . toHaveProperty ( 'production' ) ;
341+ expect ( prodEnvMeta . encryptionKeys ) . toHaveProperty ( 'production' ) ;
342+ expect ( ( prodEnvMeta . teamMembers ! as any ) . production ) . toHaveLength ( 1 ) ;
343+ expect ( ( prodEnvMeta . encryptionKeys ! as any ) . production ) . toHaveLength ( 1 ) ;
344+
345+ const prodEncryptionKey = await loadLatestSymmetricKey ( privateKey ) ;
346+ const prodEncrypted = await encryptValue ( 'a secret value' , prodEncryptionKey ) ;
347+ await expect ( decryptValue ( prodEncrypted , prodEncryptionKey ) ) . resolves . toBe ( 'a secret value' ) ;
348+
349+ const teammateKeys = await initializeKeysManually ( {
350+ name : 'A Teammate' ,
351+ email : 'teammate@example.com' ,
352+ } ) ;
353+
354+ const teammatePublicKey = await loadPublicKey ( teammateKeys . publicKeyArmored ) ;
355+ const teammatePrivateKey = await loadPrivateKey ( teammateKeys . privateKeyArmored ) ;
356+
357+ await trustTeamMember ( teammatePublicKey , privateKey ) ;
358+
359+ // at this point, we should have 2 team members, but still 1 symmetric key
360+ const { value : metaAfterTrustingTeammate } = await loadMetaConfig ( ) ;
361+
362+ expect ( metaAfterTrustingTeammate . teamMembers ) . toHaveProperty ( 'default' ) ;
363+ expect ( metaAfterTrustingTeammate . encryptionKeys ) . toHaveProperty ( 'default' ) ;
364+ expect ( ( metaAfterTrustingTeammate . teamMembers ! as any ) . default ) . toHaveLength ( 2 ) ;
365+ expect ( ( metaAfterTrustingTeammate . encryptionKeys ! as any ) . default ) . toHaveLength ( 1 ) ;
366+ expect ( metaAfterTrustingTeammate . teamMembers ) . toHaveProperty ( 'production' ) ;
367+ expect ( metaAfterTrustingTeammate . encryptionKeys ) . toHaveProperty ( 'production' ) ;
368+ expect ( ( metaAfterTrustingTeammate . teamMembers ! as any ) . production ) . toHaveLength ( 1 ) ;
369+ expect ( ( metaAfterTrustingTeammate . encryptionKeys ! as any ) . production ) . toHaveLength ( 1 ) ;
370+
371+ // ensures that the teammate can now encrypt/decrypt values
372+ const encryptedByTeammate = await encryptValue (
373+ 'a secret value' ,
374+ await loadLatestSymmetricKey ( teammatePrivateKey ) ,
375+ ) ;
376+ await expect (
377+ decryptValue ( encryptedByTeammate , await loadLatestSymmetricKey ( teammatePrivateKey ) ) ,
378+ ) . resolves . toBe ( 'a secret value' ) ;
379+
380+ // ensures that we can still encrypt/decrypt values
381+ const encryptedByUs = await encryptValue (
382+ 'a secret value' ,
383+ await loadLatestSymmetricKey ( privateKey ) ,
384+ ) ;
385+ await expect (
386+ decryptValue ( encryptedByUs , await loadLatestSymmetricKey ( privateKey ) ) ,
387+ ) . resolves . toBe ( 'a secret value' ) ;
388+
389+ await untrustTeamMember ( 'teammate@example.com' , privateKey ) ;
390+
391+ // at this point, we should have 1 team members, and a newly generated symmetric key
392+ const { value : metaAfterUntrustingTeammate } = await loadMetaConfig ( ) ;
393+
394+ expect ( metaAfterUntrustingTeammate . teamMembers ) . toHaveProperty ( 'default' ) ;
395+ expect ( metaAfterUntrustingTeammate . encryptionKeys ) . toHaveProperty ( 'default' ) ;
396+ expect ( ( metaAfterUntrustingTeammate . teamMembers ! as any ) . default ) . toHaveLength ( 1 ) ;
397+ expect ( ( metaAfterUntrustingTeammate . encryptionKeys ! as any ) . default ) . toHaveLength ( 2 ) ;
398+ expect ( metaAfterUntrustingTeammate . teamMembers ) . toHaveProperty ( 'production' ) ;
399+ expect ( metaAfterUntrustingTeammate . encryptionKeys ) . toHaveProperty ( 'production' ) ;
400+ expect ( ( metaAfterUntrustingTeammate . teamMembers ! as any ) . production ) . toHaveLength ( 1 ) ;
401+ expect ( ( metaAfterUntrustingTeammate . encryptionKeys ! as any ) . production ) . toHaveLength ( 1 ) ;
402+
403+ // ensures that we can still encrypt/decrypt values
404+ const newlyEncryptedByUs = await encryptValue (
405+ 'a secret value' ,
406+ await loadLatestSymmetricKey ( privateKey ) ,
407+ ) ;
408+ await expect (
409+ decryptValue ( newlyEncryptedByUs , await loadLatestSymmetricKey ( privateKey ) ) ,
410+ ) . resolves . toBe ( 'a secret value' ) ;
411+
412+ // now, the teammate should have no access
413+ await expect ( loadLatestSymmetricKey ( teammatePrivateKey ) ) . rejects . toThrow ( ) ;
414+
415+ // just for test coverage, create a new symmetric key
416+ const latestSymmetricKey = await loadLatestSymmetricKey ( privateKey ) ;
417+
418+ const newRevisionNumber = getRevisionNumber ( latestSymmetricKey . revision ) + 1 ;
419+
420+ await saveNewSymmetricKey (
421+ await generateSymmetricKey ( newRevisionNumber . toString ( ) ) ,
422+ await loadTeamMembers ( ) ,
423+ ) ;
424+
425+ const { value : metaAfterNewSymmetricKey } = await loadMetaConfig ( ) ;
426+
427+ expect ( metaAfterNewSymmetricKey . teamMembers ) . toHaveProperty ( 'default' ) ;
428+ expect ( metaAfterNewSymmetricKey . encryptionKeys ) . toHaveProperty ( 'default' ) ;
429+ expect ( ( metaAfterNewSymmetricKey . teamMembers ! as any ) . default ) . toHaveLength ( 1 ) ;
430+ expect ( ( metaAfterNewSymmetricKey . encryptionKeys ! as any ) . default ) . toHaveLength ( 3 ) ;
431+ expect ( metaAfterNewSymmetricKey . teamMembers ) . toHaveProperty ( 'production' ) ;
432+ expect ( metaAfterNewSymmetricKey . encryptionKeys ) . toHaveProperty ( 'production' ) ;
433+ expect ( ( metaAfterNewSymmetricKey . teamMembers ! as any ) . production ) . toHaveLength ( 1 ) ;
434+ expect ( ( metaAfterNewSymmetricKey . encryptionKeys ! as any ) . production ) . toHaveLength ( 1 ) ;
435+
436+ // get out of the directory, Windows doesn't like unlink while cwd
437+ process . chdir ( cwd ) ;
438+ } ) ;
439+ } ) ;
440+ } ) ;
441+
285442describe ( 'E2E Encrypted Repo' , ( ) => {
286443 it ( 'sets up, trusts and untrusts users correctly' , ( ) => {
287444 const cwd = process . cwd ( ) ;
288445
289446 return withTempFiles ( { } , async ( inDir ) => {
447+ // run environmentless
448+ delete process . env . NODE_ENV ;
449+
290450 process . chdir ( inDir ( '.' ) ) ;
291451 process . env . APP_CONFIG_SECRETS_KEYCHAIN_FOLDER = inDir ( 'keychain' ) ;
292452
@@ -315,8 +475,10 @@ describe('E2E Encrypted Repo', () => {
315475 // at this point, we should have ourselves trusted, and 1 symmetric key
316476 const { value : meta } = await loadMetaConfig ( ) ;
317477
318- expect ( meta . teamMembers ) . toHaveLength ( 1 ) ;
319- expect ( meta . encryptionKeys ) . toHaveLength ( 1 ) ;
478+ expect ( meta . teamMembers ) . toHaveProperty ( 'default' ) ;
479+ expect ( meta . encryptionKeys ) . toHaveProperty ( 'default' ) ;
480+ expect ( ( meta . teamMembers ! as any ) . default ) . toHaveLength ( 1 ) ;
481+ expect ( ( meta . encryptionKeys ! as any ) . default ) . toHaveLength ( 1 ) ;
320482
321483 const encryptionKey = await loadLatestSymmetricKey ( privateKey ) ;
322484 const encrypted = await encryptValue ( 'a secret value' , encryptionKey ) ;
@@ -335,8 +497,10 @@ describe('E2E Encrypted Repo', () => {
335497 // at this point, we should have 2 team members, but still 1 symmetric key
336498 const { value : metaAfterTrustingTeammate } = await loadMetaConfig ( ) ;
337499
338- expect ( metaAfterTrustingTeammate . teamMembers ) . toHaveLength ( 2 ) ;
339- expect ( metaAfterTrustingTeammate . encryptionKeys ) . toHaveLength ( 1 ) ;
500+ expect ( metaAfterTrustingTeammate . teamMembers ) . toHaveProperty ( 'default' ) ;
501+ expect ( metaAfterTrustingTeammate . encryptionKeys ) . toHaveProperty ( 'default' ) ;
502+ expect ( ( metaAfterTrustingTeammate . teamMembers ! as any ) . default ) . toHaveLength ( 2 ) ;
503+ expect ( ( metaAfterTrustingTeammate . encryptionKeys ! as any ) . default ) . toHaveLength ( 1 ) ;
340504
341505 // ensures that the teammate can now encrypt/decrypt values
342506 const encryptedByTeammate = await encryptValue (
@@ -361,8 +525,10 @@ describe('E2E Encrypted Repo', () => {
361525 // at this point, we should have 1 team members, and a newly generated symmetric key
362526 const { value : metaAfterUntrustingTeammate } = await loadMetaConfig ( ) ;
363527
364- expect ( metaAfterUntrustingTeammate . teamMembers ) . toHaveLength ( 1 ) ;
365- expect ( metaAfterUntrustingTeammate . encryptionKeys ) . toHaveLength ( 2 ) ;
528+ expect ( metaAfterUntrustingTeammate . teamMembers ) . toHaveProperty ( 'default' ) ;
529+ expect ( metaAfterUntrustingTeammate . encryptionKeys ) . toHaveProperty ( 'default' ) ;
530+ expect ( ( metaAfterUntrustingTeammate . teamMembers ! as any ) . default ) . toHaveLength ( 1 ) ;
531+ expect ( ( metaAfterUntrustingTeammate . encryptionKeys ! as any ) . default ) . toHaveLength ( 2 ) ;
366532
367533 // ensures that we can still encrypt/decrypt values
368534 const newlyEncryptedByUs = await encryptValue (
@@ -388,8 +554,10 @@ describe('E2E Encrypted Repo', () => {
388554
389555 const { value : metaAfterNewSymmetricKey } = await loadMetaConfig ( ) ;
390556
391- expect ( metaAfterNewSymmetricKey . teamMembers ) . toHaveLength ( 1 ) ;
392- expect ( metaAfterNewSymmetricKey . encryptionKeys ) . toHaveLength ( 3 ) ;
557+ expect ( metaAfterNewSymmetricKey . teamMembers ) . toHaveProperty ( 'default' ) ;
558+ expect ( metaAfterNewSymmetricKey . encryptionKeys ) . toHaveProperty ( 'default' ) ;
559+ expect ( ( metaAfterNewSymmetricKey . teamMembers ! as any ) . default ) . toHaveLength ( 1 ) ;
560+ expect ( ( metaAfterNewSymmetricKey . encryptionKeys ! as any ) . default ) . toHaveLength ( 3 ) ;
393561
394562 // get out of the directory, Windows doesn't like unlink while cwd
395563 process . chdir ( cwd ) ;
0 commit comments