@@ -33,6 +33,16 @@ typedef enum
33
33
JRESP_EXPECT_KEY
34
34
} JsonVaultRespSemState ;
35
35
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
+
36
46
typedef enum
37
47
{
38
48
JRESP_F_UNUSED ,
@@ -49,12 +59,27 @@ typedef struct JsonVaultRespState
49
59
char * key ;
50
60
} JsonVaultRespState ;
51
61
62
+ typedef struct JsonVaultMountInfoState
63
+ {
64
+ JsonVaultRespMountInfoSemState state ;
65
+ int level ;
66
+
67
+ char * type ;
68
+ char * version ;
69
+ } JsonVaultMountInfoState ;
70
+
52
71
static JsonParseErrorType json_resp_object_start (void * state );
53
72
static JsonParseErrorType json_resp_object_end (void * state );
54
73
static JsonParseErrorType json_resp_scalar (void * state , char * token , JsonTokenType tokentype );
55
74
static JsonParseErrorType json_resp_object_field_start (void * state , char * fname , bool isnull );
56
75
static JsonParseErrorType parse_json_response (JsonVaultRespState * parse , JsonLexContext * lex );
57
76
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
+
58
83
static char * get_keyring_vault_url (VaultV2Keyring * keyring , const char * key_name , char * out , size_t out_size );
59
84
static bool curl_perform (VaultV2Keyring * keyring , const char * url , CurlString * outStr , long * httpCode , const char * postData );
60
85
@@ -299,38 +324,99 @@ validate(GenericKeyring *keyring)
299
324
{
300
325
VaultV2Keyring * vault_keyring = (VaultV2Keyring * ) keyring ;
301
326
char url [VAULT_URL_MAX_LEN ];
327
+ int len = 0 ;
302
328
CurlString str ;
303
329
long httpCode = 0 ;
330
+ JsonParseErrorType json_error ;
331
+ JsonLexContext * jlex = NULL ;
332
+ JsonVaultMountInfoState parse ;
304
333
305
334
/*
306
- * Validate connection by listing available keys at the root level of the
307
- * mount point
335
+ * Validate that the mount has the correct engine type and version.
308
336
*/
309
- snprintf (url , VAULT_URL_MAX_LEN , "%s/v1/%s/metadata/?list=true" ,
310
- vault_keyring -> vault_url , vault_keyring -> vault_mount_path );
337
+ len = snprintf (url , VAULT_URL_MAX_LEN , "%s/v1/sys/mounts/%s" , vault_keyring -> vault_url , vault_keyring -> vault_mount_path );
338
+ if (len >= VAULT_URL_MAX_LEN )
339
+ ereport (ERROR ,
340
+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
341
+ errmsg ("vault mounts URL is too long" ));
342
+
343
+ if (!curl_perform (vault_keyring , url , & str , & httpCode , NULL ))
344
+ ereport (ERROR ,
345
+ errmsg ("HTTP(S) request to keyring provider \"%s\" failed" ,
346
+ vault_keyring -> keyring .provider_name ));
347
+
348
+ if (httpCode != 200 )
349
+ ereport (ERROR ,
350
+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
351
+ errmsg ("failed to get mount info for \"%s\" at mountpoint \"%s\" (HTTP %ld)" ,
352
+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path , httpCode ));
353
+
354
+ jlex = makeJsonLexContextCstringLen (NULL , str .ptr , str .len , PG_UTF8 , true);
355
+ json_error = parse_vault_mount_info (& parse , jlex );
356
+
357
+ if (json_error != JSON_SUCCESS )
358
+ ereport (ERROR ,
359
+ errcode (ERRCODE_INVALID_JSON_TEXT ),
360
+ errmsg ("failed to parse mount info for \"%s\" at mountpoint \"%s\": %s" ,
361
+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path , json_errdetail (json_error , jlex )));
362
+
363
+ if (parse .type == NULL )
364
+ ereport (ERROR ,
365
+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
366
+ errmsg ("failed to parse mount info for \"%s\" at mountpoint \"%s\": missing type field" ,
367
+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path ));
368
+
369
+ if (strcmp (parse .type , "kv" ) != 0 )
370
+ ereport (ERROR ,
371
+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
372
+ errmsg ("vault mount at \"%s\" has unsupported engine type \"%s\"" ,
373
+ vault_keyring -> vault_mount_path , parse .type ),
374
+ errhint ("The only supported vault engine type is Key/Value version \"2\"" ));
375
+
376
+ if (parse .version == NULL )
377
+ ereport (ERROR ,
378
+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
379
+ errmsg ("failed to parse mount info for \"%s\" at mountpoint \"%s\": missing version field" ,
380
+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path ));
381
+
382
+ if (strcmp (parse .version , "2" ) != 0 )
383
+ ereport (ERROR ,
384
+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
385
+ errmsg ("vault mount at \"%s\" has unsupported Key/Value engine version \"%s\"" ,
386
+ vault_keyring -> vault_mount_path , parse .version ),
387
+ errhint ("The only supported vault engine type is Key/Value version \"2\"" ));
388
+
389
+ /*
390
+ * Validate that we can read the secrets at the mount point.
391
+ */
392
+ len = snprintf (url , VAULT_URL_MAX_LEN , "%s/v1/%s/metadata/?list=true" ,
393
+ vault_keyring -> vault_url , vault_keyring -> vault_mount_path );
394
+ if (len >= VAULT_URL_MAX_LEN )
395
+ ereport (ERROR ,
396
+ errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
397
+ errmsg ("vault metadata URL is too long" ));
311
398
312
399
if (!curl_perform (vault_keyring , url , & str , & httpCode , NULL ))
313
- {
314
400
ereport (ERROR ,
315
401
errmsg ("HTTP(S) request to keyring provider \"%s\" failed" ,
316
402
vault_keyring -> keyring .provider_name ));
317
- }
318
403
319
404
/* If the mount point doesn't have any secrets yet, we'll get a 404. */
320
405
if (httpCode != 200 && httpCode != 404 )
321
- {
322
406
ereport (ERROR ,
323
407
errcode (ERRCODE_INVALID_PARAMETER_VALUE ),
324
408
errmsg ("Listing secrets of \"%s\" at mountpoint \"%s\" failed" ,
325
409
vault_keyring -> vault_url , vault_keyring -> vault_mount_path ));
326
- }
327
410
328
411
if (str .ptr != NULL )
329
412
pfree (str .ptr );
413
+
414
+ if (jlex != NULL )
415
+ freeJsonLexContext (jlex );
330
416
}
331
417
332
418
/*
333
- * JSON parser routines
419
+ * JSON parser routines for key response
334
420
*
335
421
* We expect the response in the form of:
336
422
* {
@@ -445,6 +531,152 @@ json_resp_object_field_start(void *state, char *fname, bool isnull)
445
531
if (strcmp (fname , "key" ) == 0 && parse -> level == 2 )
446
532
parse -> field = JRESP_F_KEY ;
447
533
break ;
534
+ default :
535
+ /* NOP */
536
+ break ;
537
+ }
538
+
539
+ return JSON_SUCCESS ;
540
+ }
541
+
542
+ /*
543
+ * JSON parser routines for mount info
544
+ *
545
+ * We expect the response in the form of:
546
+ * {
547
+ * ...
548
+ * "type": "kv",
549
+ * "options": {
550
+ * "version": "2"
551
+ * }
552
+ * ...
553
+ * }
554
+ *
555
+ * the rest fields are ignored
556
+ */
557
+
558
+ static JsonParseErrorType
559
+ parse_vault_mount_info (JsonVaultMountInfoState * state , JsonLexContext * lex )
560
+ {
561
+ JsonSemAction sem ;
562
+
563
+ state -> state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ;
564
+ state -> type = NULL ;
565
+ state -> version = NULL ;
566
+ state -> level = -1 ;
567
+
568
+ memset (& sem , 0 , sizeof (sem ));
569
+ sem .semstate = state ;
570
+ sem .object_start = json_mountinfo_object_start ;
571
+ sem .object_end = json_mountinfo_object_end ;
572
+ sem .scalar = json_mountinfo_scalar ;
573
+ sem .object_field_start = json_mountinfo_object_field_start ;
574
+
575
+ return pg_parse_json (lex , & sem );
576
+ }
577
+
578
+ static JsonParseErrorType
579
+ json_mountinfo_object_start (void * state )
580
+ {
581
+ JsonVaultMountInfoState * parse = (JsonVaultMountInfoState * ) state ;
582
+
583
+ switch (parse -> state )
584
+ {
585
+ case JRESP_MOUNT_INFO_EXPECT_OPTIONS_START :
586
+ parse -> state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD ;
587
+ break ;
588
+ default :
589
+ /* NOP */
590
+ break ;
591
+ }
592
+
593
+ parse -> level ++ ;
594
+
595
+ return JSON_SUCCESS ;
596
+ }
597
+
598
+ static JsonParseErrorType
599
+ json_mountinfo_object_end (void * state )
600
+ {
601
+ JsonVaultMountInfoState * parse = (JsonVaultMountInfoState * ) state ;
602
+
603
+ if (parse -> state == JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD )
604
+ parse -> state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ;
605
+
606
+ parse -> level -- ;
607
+
608
+ return JSON_SUCCESS ;
609
+ }
610
+
611
+ static JsonParseErrorType
612
+ json_mountinfo_scalar (void * state , char * token , JsonTokenType tokentype )
613
+ {
614
+ JsonVaultMountInfoState * parse = (JsonVaultMountInfoState * ) state ;
615
+
616
+ switch (parse -> state )
617
+ {
618
+ case JRESP_MOUNT_INFO_EXPECT_TYPE_VALUE :
619
+ parse -> type = token ;
620
+ parse -> state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ;
621
+ break ;
622
+ case JRESP_MOUNT_INFO_EXPECT_VERSION_VALUE :
623
+ parse -> version = token ;
624
+ parse -> state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD ;
625
+ break ;
626
+ case JRESP_MOUNT_INFO_EXPECT_OPTIONS_START :
627
+
628
+ /*
629
+ * Reset "options" object expectations if we got scalar. Most
630
+ * likely just a null.
631
+ */
632
+ parse -> state = JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD ;
633
+ break ;
634
+ default :
635
+ /* NOP */
636
+ break ;
637
+ }
638
+
639
+ return JSON_SUCCESS ;
640
+ }
641
+
642
+ static JsonParseErrorType
643
+ json_mountinfo_object_field_start (void * state , char * fname , bool isnull )
644
+ {
645
+ JsonVaultMountInfoState * parse = (JsonVaultMountInfoState * ) state ;
646
+
647
+ switch (parse -> state )
648
+ {
649
+ case JRESP_MOUNT_INFO_EXPECT_TOPLEVEL_FIELD :
650
+ if (parse -> level == 0 )
651
+ {
652
+ if (strcmp (fname , "type" ) == 0 )
653
+ {
654
+ parse -> state = JRESP_MOUNT_INFO_EXPECT_TYPE_VALUE ;
655
+ break ;
656
+ }
657
+
658
+ if (strcmp (fname , "options" ) == 0 )
659
+ {
660
+ parse -> state = JRESP_MOUNT_INFO_EXPECT_OPTIONS_START ;
661
+ break ;
662
+ }
663
+ }
664
+ break ;
665
+
666
+ case JRESP_MOUNT_INFO_EXPECT_OPTIONS_FIELD :
667
+ if (parse -> level == 1 )
668
+ {
669
+ if (strcmp (fname , "version" ) == 0 )
670
+ {
671
+ parse -> state = JRESP_MOUNT_INFO_EXPECT_VERSION_VALUE ;
672
+ break ;
673
+ }
674
+ }
675
+ break ;
676
+
677
+ default :
678
+ /* NOP */
679
+ break ;
448
680
}
449
681
450
682
pfree (fname );
0 commit comments