@@ -953,4 +953,290 @@ describe('commandEnv', () => {
953953 expect ( result . stdout ) . toEqual ( formatResult [ format ] ) ;
954954 } ,
955955 ) ;
956+
957+ describe ( 'should apply json schema to control secrets egress' , ( ) => {
958+ test ( 'should filter secrets based on a schema' , async ( ) => {
959+ // Write secrets to vault
960+ const vaultName = 'vault' ;
961+ const vaultId = await polykeyAgent . vaultManager . createVault ( vaultName ) ;
962+ const secretName1 = 'SECRET1' ;
963+ const secretName2 = 'SECRET2' ;
964+ const secretName3 = 'SECRET3' ;
965+ await polykeyAgent . vaultManager . withVaults ( [ vaultId ] , async ( vault ) => {
966+ await vaultOps . addSecret ( vault , secretName1 , secretName1 ) ;
967+ await vaultOps . addSecret ( vault , secretName2 , secretName2 ) ;
968+ await vaultOps . addSecret ( vault , secretName3 , secretName3 ) ;
969+ } ) ;
970+
971+ // Write schema to file
972+ const schema = {
973+ type : 'object' ,
974+ properties : {
975+ SECRET1 : { type : 'string' } ,
976+ SECRET2 : { type : 'string' } ,
977+ } ,
978+ required : [ 'SECRET1' , 'SECRET2' ] ,
979+ } ;
980+ const schemaPath = path . join ( dataDir , 'egress.schema.json' ) ;
981+ await fs . promises . writeFile ( schemaPath , JSON . stringify ( schema , null , 2 ) ) ;
982+
983+ // Run command with the schema
984+ const command = [
985+ 'secrets' ,
986+ 'env' ,
987+ '-np' ,
988+ dataDir ,
989+ '--env-format' ,
990+ 'unix' ,
991+ '--egress-schema' ,
992+ schemaPath ,
993+ vaultName ,
994+ ] ;
995+ const result = await testUtils . pkExec ( command , {
996+ env : { PK_PASSWORD : password } ,
997+ } ) ;
998+ expect ( result . exitCode ) . toBe ( 0 ) ;
999+
1000+ // Confirm only the specified secrets were exported
1001+ expect ( result . stdout ) . toContain ( "SECRET1='SECRET1'" ) ;
1002+ expect ( result . stdout ) . toContain ( "SECRET2='SECRET2'" ) ;
1003+ expect ( result . stdout ) . not . toContain ( "SECRET3='SECRET3'" ) ;
1004+ } ) ;
1005+
1006+ test ( 'should apply schema to secrets from multiple vaults' , async ( ) => {
1007+ // Write secrets to vault
1008+ const vaultName1 = 'vault1' ;
1009+ const vaultName2 = 'vault2' ;
1010+ const vaultId1 = await polykeyAgent . vaultManager . createVault ( vaultName1 ) ;
1011+ const vaultId2 = await polykeyAgent . vaultManager . createVault ( vaultName2 ) ;
1012+ const secretName1 = 'SECRET1' ;
1013+ const secretName2 = 'SECRET2' ;
1014+ const secretName3 = 'SECRET3' ;
1015+ await polykeyAgent . vaultManager . withVaults (
1016+ [ vaultId1 , vaultId2 ] ,
1017+ async ( vault1 , vault2 ) => {
1018+ await vaultOps . addSecret ( vault1 , secretName1 , secretName1 ) ;
1019+ await vaultOps . addSecret ( vault2 , secretName2 , secretName2 ) ;
1020+ await vaultOps . addSecret ( vault1 , secretName3 , secretName3 ) ;
1021+ } ,
1022+ ) ;
1023+
1024+ // Write schema to file
1025+ const schema = {
1026+ type : 'object' ,
1027+ properties : {
1028+ SECRET1 : { type : 'string' } ,
1029+ SECRET2 : { type : 'string' } ,
1030+ } ,
1031+ required : [ 'SECRET1' , 'SECRET2' ] ,
1032+ } ;
1033+ const schemaPath = path . join ( dataDir , 'egress.schema.json' ) ;
1034+ await fs . promises . writeFile ( schemaPath , JSON . stringify ( schema , null , 2 ) ) ;
1035+
1036+ // Run command with the schema
1037+ const command = [
1038+ 'secrets' ,
1039+ 'env' ,
1040+ '-np' ,
1041+ dataDir ,
1042+ '--env-format' ,
1043+ 'unix' ,
1044+ '--egress-schema' ,
1045+ schemaPath ,
1046+ vaultName1 ,
1047+ vaultName2 ,
1048+ ] ;
1049+ const result = await testUtils . pkExec ( command , {
1050+ env : { PK_PASSWORD : password } ,
1051+ } ) ;
1052+ expect ( result . exitCode ) . toBe ( 0 ) ;
1053+
1054+ // Confirm only the specified secrets were exported
1055+ expect ( result . stdout ) . toContain ( "SECRET1='SECRET1'" ) ;
1056+ expect ( result . stdout ) . toContain ( "SECRET2='SECRET2'" ) ;
1057+ expect ( result . stdout ) . not . toContain ( "SECRET3='SECRET3'" ) ;
1058+ } ) ;
1059+
1060+ test ( 'should handle secret renames' , async ( ) => {
1061+ // Write secrets to vault
1062+ const vaultName = 'vault' ;
1063+ const vaultId = await polykeyAgent . vaultManager . createVault ( vaultName ) ;
1064+ const secretName1 = 'SECRET1' ;
1065+ const secretName2 = 'SECRET2' ;
1066+ const secretName3 = 'SECRET3' ;
1067+ const secretRename = 'RENAMED' ;
1068+ await polykeyAgent . vaultManager . withVaults ( [ vaultId ] , async ( vault ) => {
1069+ await vaultOps . addSecret ( vault , secretName1 , secretName1 ) ;
1070+ await vaultOps . addSecret ( vault , secretName2 , secretName2 ) ;
1071+ await vaultOps . addSecret ( vault , secretName3 , secretName3 ) ;
1072+ } ) ;
1073+
1074+ // Write schema to file
1075+ const schema = {
1076+ type : 'object' ,
1077+ properties : {
1078+ SECRET1 : { type : 'string' } ,
1079+ RENAMED : { type : 'string' } ,
1080+ } ,
1081+ required : [ 'SECRET1' , 'RENAMED' ] ,
1082+ } ;
1083+ const schemaPath = path . join ( dataDir , 'egress.schema.json' ) ;
1084+ await fs . promises . writeFile ( schemaPath , JSON . stringify ( schema , null , 2 ) ) ;
1085+
1086+ // Run command with the schema. Should first export all relevant secrets
1087+ // from the vault, then process the renamed secret.
1088+ const command = [
1089+ 'secrets' ,
1090+ 'env' ,
1091+ '-np' ,
1092+ dataDir ,
1093+ '--env-format' ,
1094+ 'unix' ,
1095+ '--egress-schema' ,
1096+ schemaPath ,
1097+ vaultName ,
1098+ `${ vaultName } :${ secretName2 } =${ secretRename } ` ,
1099+ ] ;
1100+ const result = await testUtils . pkExec ( command , {
1101+ env : { PK_PASSWORD : password } ,
1102+ } ) ;
1103+ expect ( result . exitCode ) . toBe ( 0 ) ;
1104+
1105+ // Confirm only the specified secrets were exported
1106+ expect ( result . stdout ) . toContain ( "SECRET1='SECRET1'" ) ;
1107+ expect ( result . stdout ) . toContain ( "RENAMED='SECRET2'" ) ;
1108+ expect ( result . stdout ) . not . toContain ( "SECRET2='SECRET2'" ) ;
1109+ expect ( result . stdout ) . not . toContain ( "SECRET3='SECRET3'" ) ;
1110+ } ) ;
1111+
1112+ test ( 'should not fail when missing non-required secret' , async ( ) => {
1113+ // Write secrets to vault
1114+ const vaultName = 'vault' ;
1115+ const vaultId = await polykeyAgent . vaultManager . createVault ( vaultName ) ;
1116+ const secretName1 = 'SECRET1' ;
1117+ await polykeyAgent . vaultManager . withVaults ( [ vaultId ] , async ( vault ) => {
1118+ await vaultOps . addSecret ( vault , secretName1 , secretName1 ) ;
1119+ } ) ;
1120+
1121+ // Write schema to file
1122+ const schema = {
1123+ type : 'object' ,
1124+ properties : {
1125+ SECRET1 : { type : 'string' } ,
1126+ SECRET2 : { type : 'string' } ,
1127+ } ,
1128+ required : [ 'SECRET1' ] ,
1129+ } ;
1130+ const schemaPath = path . join ( dataDir , 'egress.schema.json' ) ;
1131+ await fs . promises . writeFile ( schemaPath , JSON . stringify ( schema , null , 2 ) ) ;
1132+
1133+ // Run command with the schema. Should first export all relevant secrets
1134+ // from the vault, then process the renamed secret.
1135+ const command = [
1136+ 'secrets' ,
1137+ 'env' ,
1138+ '-np' ,
1139+ dataDir ,
1140+ '--env-format' ,
1141+ 'unix' ,
1142+ '--egress-schema' ,
1143+ schemaPath ,
1144+ vaultName ,
1145+ ] ;
1146+ const result = await testUtils . pkExec ( command , {
1147+ env : { PK_PASSWORD : password } ,
1148+ } ) ;
1149+ expect ( result . exitCode ) . toBe ( 0 ) ;
1150+
1151+ // Confirm only the specified secrets were exported
1152+ expect ( result . stdout ) . toContain ( "SECRET1='SECRET1'" ) ;
1153+ expect ( result . stdout ) . not . toContain ( 'SECRET2' ) ;
1154+ } ) ;
1155+
1156+ test ( 'should fail when missing required secret' , async ( ) => {
1157+ // Write secrets to vault
1158+ const vaultName = 'vault' ;
1159+ const vaultId = await polykeyAgent . vaultManager . createVault ( vaultName ) ;
1160+ const secretName1 = 'SECRET1' ;
1161+ await polykeyAgent . vaultManager . withVaults ( [ vaultId ] , async ( vault ) => {
1162+ await vaultOps . addSecret ( vault , secretName1 , secretName1 ) ;
1163+ } ) ;
1164+
1165+ // Write schema to file
1166+ const schema = {
1167+ type : 'object' ,
1168+ properties : {
1169+ SECRET1 : { type : 'string' } ,
1170+ SECRET2 : { type : 'string' } ,
1171+ } ,
1172+ required : [ 'SECRET1' , 'SECRET2' ] ,
1173+ } ;
1174+ const schemaPath = path . join ( dataDir , 'egress.schema.json' ) ;
1175+ await fs . promises . writeFile ( schemaPath , JSON . stringify ( schema , null , 2 ) ) ;
1176+
1177+ // Run command with the schema
1178+ const command = [
1179+ 'secrets' ,
1180+ 'env' ,
1181+ '-np' ,
1182+ dataDir ,
1183+ '--env-format' ,
1184+ 'unix' ,
1185+ '--egress-schema' ,
1186+ schemaPath ,
1187+ vaultName ,
1188+ ] ;
1189+ const result = await testUtils . pkExec ( command , {
1190+ env : { PK_PASSWORD : password } ,
1191+ } ) ;
1192+ expect ( result . exitCode ) . toBe ( 64 ) ;
1193+
1194+ // Confirm the validity of the error
1195+ expect ( result . stderr ) . toInclude ( 'ErrorPolykeyCLIMissingRequiredEnvName' ) ;
1196+ expect ( result . stderr ) . toInclude ( 'SECRET2' ) ;
1197+ } ) ;
1198+
1199+ test ( 'should replace variable with default if present' , async ( ) => {
1200+ // Write secrets to vault
1201+ const vaultName = 'vault' ;
1202+ const vaultId = await polykeyAgent . vaultManager . createVault ( vaultName ) ;
1203+ const secretName1 = 'SECRET1' ;
1204+ await polykeyAgent . vaultManager . withVaults ( [ vaultId ] , async ( vault ) => {
1205+ await vaultOps . addSecret ( vault , secretName1 , secretName1 ) ;
1206+ } ) ;
1207+
1208+ // Write schema to file
1209+ const schema = {
1210+ type : 'object' ,
1211+ properties : {
1212+ SECRET1 : { type : 'string' } ,
1213+ SECRET2 : { type : 'string' , default : 'abc' } ,
1214+ } ,
1215+ required : [ 'SECRET1' , 'SECRET2' ] ,
1216+ } ;
1217+ const schemaPath = path . join ( dataDir , 'egress.schema.json' ) ;
1218+ await fs . promises . writeFile ( schemaPath , JSON . stringify ( schema , null , 2 ) ) ;
1219+
1220+ // Run command with the schema
1221+ const command = [
1222+ 'secrets' ,
1223+ 'env' ,
1224+ '-np' ,
1225+ dataDir ,
1226+ '--env-format' ,
1227+ 'unix' ,
1228+ '--egress-schema' ,
1229+ schemaPath ,
1230+ vaultName ,
1231+ ] ;
1232+ const result = await testUtils . pkExec ( command , {
1233+ env : { PK_PASSWORD : password } ,
1234+ } ) ;
1235+ expect ( result . exitCode ) . toBe ( 0 ) ;
1236+
1237+ // Confirm only the specified secrets were exported
1238+ expect ( result . stdout ) . toInclude ( "SECRET1='SECRET1'" ) ;
1239+ expect ( result . stdout ) . toInclude ( "SECRET2='abc" ) ;
1240+ } ) ;
1241+ } ) ;
9561242} ) ;
0 commit comments