@@ -33,6 +33,16 @@ typedef enum
3333 JRESP_EXPECT_KEY
3434} JsonVaultRespSemState ;
3535
36+ typedef enum
37+ {
38+ JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ,
39+ JRESP_MOUNT_INFO_EXPECT_TYPE_VALUE ,
40+ JRESP_MOUNT_INFO_EXPECT_VERSION_VALUE ,
41+ JRESP_MOUNT_INFO_EXPECT_OPTIONS_START ,
42+ JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD ,
43+ } JsonVaultRespMountInfoSemState ;
44+
45+
3646typedef enum
3747{
3848 JRESP_F_UNUSED ,
@@ -49,12 +59,27 @@ typedef struct JsonVaultRespState
4959 char * key ;
5060} JsonVaultRespState ;
5161
62+ typedef struct JsonVaultMountInfoState
63+ {
64+ JsonVaultRespMountInfoSemState state ;
65+ int level ;
66+
67+ char * type ;
68+ char * version ;
69+ } JsonVaultMountInfoState ;
70+
5271static JsonParseErrorType json_resp_object_start (void * state );
5372static JsonParseErrorType json_resp_object_end (void * state );
5473static JsonParseErrorType json_resp_scalar (void * state , char * token , JsonTokenType tokentype );
5574static JsonParseErrorType json_resp_object_field_start (void * state , char * fname , bool isnull );
5675static JsonParseErrorType parse_json_response (JsonVaultRespState * parse , JsonLexContext * lex );
5776
77+ static JsonParseErrorType json_mountinfo_object_start (void * state );
78+ static JsonParseErrorType json_mountinfo_object_end (void * state );
79+ static JsonParseErrorType json_mountinfo_scalar (void * state , char * token , JsonTokenType tokentype );
80+ static JsonParseErrorType json_mountinfo_object_field_start (void * state , char * fname , bool isnull );
81+ static JsonParseErrorType parse_vault_mount_info (JsonVaultMountInfoState * state , JsonLexContext * lex );
82+
5883static char * get_keyring_vault_url (VaultV2Keyring * keyring , const char * key_name , char * out , size_t out_size );
5984static bool curl_perform (VaultV2Keyring * keyring , const char * url , CurlString * outStr , long * httpCode , const char * postData );
6085
@@ -290,38 +315,99 @@ validate(GenericKeyring *keyring)
290315{
291316 VaultV2Keyring * vault_keyring = (VaultV2Keyring * ) keyring ;
292317 char url [VAULT_URL_MAX_LEN ];
318+ int len = 0 ;
293319 CurlString str ;
294320 long httpCode = 0 ;
321+ JsonParseErrorType json_error ;
322+ JsonLexContext * jlex = NULL ;
323+ JsonVaultMountInfoState parse ;
295324
296325 /*
297- * Validate connection by listing available keys at the root level of the
298- * mount point
326+ * Validate that the mount has the correct engine type and version.
299327 */
300- snprintf (url , VAULT_URL_MAX_LEN , "%s/v1/%s/metadata/?list=true" ,
301- vault_keyring -> vault_url , vault_keyring -> vault_mount_path );
328+ len = snprintf (url , VAULT_URL_MAX_LEN , "%s/v1/sys/mounts/%s" , vault_keyring -> vault_url , vault_keyring -> vault_mount_path );
329+ if (len >= VAULT_URL_MAX_LEN )
330+ ereport (ERROR ,
331+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
332+ errmsg ("vault mounts URL is too long" ));
333+
334+ if (!curl_perform (vault_keyring , url , & str , & httpCode , NULL ))
335+ ereport (ERROR ,
336+ errmsg ("HTTP(S) request to keyring provider \"%s\" failed" ,
337+ vault_keyring -> keyring .provider_name ));
338+
339+ if (httpCode != 200 )
340+ ereport (ERROR ,
341+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
342+ errmsg ("failed to get mount info for \"%s\" at mountpoint \"%s\" (HTTP %ld)" ,
343+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path , httpCode ));
344+
345+ jlex = makeJsonLexContextCstringLen (NULL , str .ptr , str .len , PG_UTF8 , true);
346+ json_error = parse_vault_mount_info (& parse , jlex );
347+
348+ if (json_error != JSON_SUCCESS )
349+ ereport (ERROR ,
350+ errcode (ERRCODE_INVALID_JSON_TEXT ),
351+ errmsg ("failed to parse mount info for \"%s\" at mountpoint \"%s\": %s" ,
352+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path , json_errdetail (json_error , jlex )));
353+
354+ if (parse .type == NULL )
355+ ereport (ERROR ,
356+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
357+ errmsg ("failed to parse mount info for \"%s\" at mountpoint \"%s\": missing type field" ,
358+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path ));
359+
360+ if (strcmp (parse .type , "kv" ) != 0 )
361+ ereport (ERROR ,
362+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
363+ errmsg ("vault mount at \"%s\" has unsupported engine type \"%s\"" ,
364+ vault_keyring -> vault_mount_path , parse .type ),
365+ errhint ("The only supported vault engine type is Key/Value version \"2\"" ));
366+
367+ if (parse .version == NULL )
368+ ereport (ERROR ,
369+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
370+ errmsg ("failed to parse mount info for \"%s\" at mountpoint \"%s\": missing version field" ,
371+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path ));
372+
373+ if (strcmp (parse .version , "2" ) != 0 )
374+ ereport (ERROR ,
375+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
376+ errmsg ("vault mount at \"%s\" has unsupported Key/Value engine version \"%s\"" ,
377+ vault_keyring -> vault_mount_path , parse .version ),
378+ errhint ("The only supported vault engine type is Key/Value version \"2\"" ));
379+
380+ /*
381+ * Validate that we can read the secrets at the mount point.
382+ */
383+ len = snprintf (url , VAULT_URL_MAX_LEN , "%s/v1/%s/metadata/?list=true" ,
384+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path );
385+ if (len >= VAULT_URL_MAX_LEN )
386+ ereport (ERROR ,
387+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
388+ errmsg ("vault metadata URL is too long" ));
302389
303390 if (!curl_perform (vault_keyring , url , & str , & httpCode , NULL ))
304- {
305391 ereport (ERROR ,
306392 errmsg ("HTTP(S) request to keyring provider \"%s\" failed" ,
307393 vault_keyring -> keyring .provider_name ));
308- }
309394
310395 /* If the mount point doesn't have any secrets yet, we'll get a 404. */
311396 if (httpCode != 200 && httpCode != 404 )
312- {
313397 ereport (ERROR ,
314398 errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
315399 errmsg ("Listing secrets of \"%s\" at mountpoint \"%s\" failed" ,
316400 vault_keyring -> vault_url , vault_keyring -> vault_mount_path ));
317- }
318401
319402 if (str .ptr != NULL )
320403 pfree (str .ptr );
404+
405+ if (jlex != NULL )
406+ freeJsonLexContext (jlex );
321407}
322408
323409/*
324- * JSON parser routines
410+ * JSON parser routines for key response
325411 *
326412 * We expect the response in the form of:
327413 * {
@@ -436,6 +522,152 @@ json_resp_object_field_start(void *state, char *fname, bool isnull)
436522 if (strcmp (fname , "key" ) == 0 && parse -> level == 2 )
437523 parse -> field = JRESP_F_KEY ;
438524 break ;
525+ default :
526+ /* NOP */
527+ break ;
528+ }
529+
530+ return JSON_SUCCESS ;
531+ }
532+
533+ /*
534+ * JSON parser routines for mount info
535+ *
536+ * We expect the response in the form of:
537+ * {
538+ * ...
539+ * "type": "kv",
540+ * "options": {
541+ * "version": "2"
542+ * }
543+ * ...
544+ * }
545+ *
546+ * the rest fields are ignored
547+ */
548+
549+ static JsonParseErrorType
550+ parse_vault_mount_info (JsonVaultMountInfoState * state , JsonLexContext * lex )
551+ {
552+ JsonSemAction sem ;
553+
554+ state -> state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ;
555+ state -> type = NULL ;
556+ state -> version = NULL ;
557+ state -> level = -1 ;
558+
559+ memset (& sem , 0 , sizeof (sem ));
560+ sem .semstate = state ;
561+ sem .object_start = json_mountinfo_object_start ;
562+ sem .object_end = json_mountinfo_object_end ;
563+ sem .scalar = json_mountinfo_scalar ;
564+ sem .object_field_start = json_mountinfo_object_field_start ;
565+
566+ return pg_parse_json (lex , & sem );
567+ }
568+
569+ static JsonParseErrorType
570+ json_mountinfo_object_start (void * state )
571+ {
572+ JsonVaultMountInfoState * parse = (JsonVaultMountInfoState * ) state ;
573+
574+ switch (parse -> state )
575+ {
576+ case JRESP_MOUNT_INFO_EXPECT_OPTIONS_START :
577+ parse -> state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD ;
578+ break ;
579+ default :
580+ /* NOP */
581+ break ;
582+ }
583+
584+ parse -> level ++ ;
585+
586+ return JSON_SUCCESS ;
587+ }
588+
589+ static JsonParseErrorType
590+ json_mountinfo_object_end (void * state )
591+ {
592+ JsonVaultMountInfoState * parse = (JsonVaultMountInfoState * ) state ;
593+
594+ if (parse -> state == JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD )
595+ parse -> state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ;
596+
597+ parse -> level -- ;
598+
599+ return JSON_SUCCESS ;
600+ }
601+
602+ static JsonParseErrorType
603+ json_mountinfo_scalar (void * state , char * token , JsonTokenType tokentype )
604+ {
605+ JsonVaultMountInfoState * parse = (JsonVaultMountInfoState * ) state ;
606+
607+ switch (parse -> state )
608+ {
609+ case JRESP_MOUNT_INFO_EXPECT_TYPE_VALUE :
610+ parse -> type = token ;
611+ parse -> state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ;
612+ break ;
613+ case JRESP_MOUNT_INFO_EXPECT_VERSION_VALUE :
614+ parse -> version = token ;
615+ parse -> state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD ;
616+ break ;
617+ case JRESP_MOUNT_INFO_EXPECT_OPTIONS_START :
618+
619+ /*
620+ * Reset "options" object expectations if we got scalar. Most
621+ * likely just a null.
622+ */
623+ parse -> state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ;
624+ break ;
625+ default :
626+ /* NOP */
627+ break ;
628+ }
629+
630+ return JSON_SUCCESS ;
631+ }
632+
633+ static JsonParseErrorType
634+ json_mountinfo_object_field_start (void * state , char * fname , bool isnull )
635+ {
636+ JsonVaultMountInfoState * parse = (JsonVaultMountInfoState * ) state ;
637+
638+ switch (parse -> state )
639+ {
640+ case JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD :
641+ if (parse -> level == 0 )
642+ {
643+ if (strcmp (fname , "type" ) == 0 )
644+ {
645+ parse -> state = JRESP_MOUNT_INFO_EXPECT_TYPE_VALUE ;
646+ break ;
647+ }
648+
649+ if (strcmp (fname , "options" ) == 0 )
650+ {
651+ parse -> state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_START ;
652+ break ;
653+ }
654+ }
655+ break ;
656+
657+ case JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD :
658+ if (parse -> level == 1 )
659+ {
660+ if (strcmp (fname , "version" ) == 0 )
661+ {
662+ parse -> state = JRESP_MOUNT_INFO_EXPECT_VERSION_VALUE ;
663+ break ;
664+ }
665+ }
666+ break ;
667+
668+ default :
669+ /* NOP */
670+ break ;
439671 }
440672
441673 return JSON_SUCCESS ;
0 commit comments