venafi-connector-machine 1.0.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -9
- package/bundle.mjs +472 -22
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# venafi-connector-machine
|
|
2
2
|
|
|
3
|
-
An MCP (Model Context Protocol) server that provides machine connector-specific knowledge, templates, and tools for building **Venafi
|
|
3
|
+
An MCP (Model Context Protocol) server that provides machine connector-specific knowledge, templates, and tools for building **Venafi machine connectors**.
|
|
4
4
|
|
|
5
5
|
Use this with Claude Code or any MCP-compatible AI assistant to get expert guidance on SSH and REST API machine connectors — discovery, provisioning, manifest design, and all the gotchas.
|
|
6
6
|
|
|
@@ -43,11 +43,8 @@ Covering SSH and REST API patterns from all machine connector projects:
|
|
|
43
43
|
### Quick Install (Claude Code CLI)
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
# Add to your project
|
|
47
|
-
claude mcp add venafi-
|
|
48
|
-
|
|
49
|
-
# Best used alongside the core MCP
|
|
50
|
-
claude mcp add venafi-connector-core -- npx -y venafi-connector-core
|
|
46
|
+
# Add to your project (best used alongside the core MCP)
|
|
47
|
+
claude mcp add venafi-integration-core -- npx -y venafi-integration-core
|
|
51
48
|
claude mcp add venafi-connector-machine -- npx -y venafi-connector-machine
|
|
52
49
|
```
|
|
53
50
|
|
|
@@ -58,9 +55,9 @@ Alternatively, add to your project's `.claude/settings.json`:
|
|
|
58
55
|
```json
|
|
59
56
|
{
|
|
60
57
|
"mcpServers": {
|
|
61
|
-
"venafi-
|
|
58
|
+
"venafi-integration-core": {
|
|
62
59
|
"command": "npx",
|
|
63
|
-
"args": ["-y", "venafi-
|
|
60
|
+
"args": ["-y", "venafi-integration-core"]
|
|
64
61
|
},
|
|
65
62
|
"venafi-connector-machine": {
|
|
66
63
|
"command": "npx",
|
|
@@ -83,8 +80,9 @@ Knowledge extracted from building these machine connectors:
|
|
|
83
80
|
|
|
84
81
|
## Related Packages
|
|
85
82
|
|
|
86
|
-
- [`venafi-
|
|
83
|
+
- [`venafi-integration-core`](https://www.npmjs.com/package/venafi-integration-core) — Shared architecture, templates, deployment, troubleshooting
|
|
87
84
|
- [`venafi-connector-ca`](https://www.npmjs.com/package/venafi-connector-ca) — CA connector endpoints, certificate issuance/import/revocation
|
|
85
|
+
- [`venafi-adaptable-app`](https://www.npmjs.com/package/venafi-adaptable-app) — Adaptable app driver templates, field definitions, PowerShell patterns
|
|
88
86
|
|
|
89
87
|
## License
|
|
90
88
|
|
package/bundle.mjs
CHANGED
|
@@ -30802,22 +30802,83 @@ Here, the first three types all come from the same data source (keystores), but
|
|
|
30802
30802
|
|
|
30803
30803
|
**Key: choosing the right categories.** Ask: "How would an admin of this platform describe their certificates?" For FortiGate, it's by service type (VPN, firewall, management). For APIC, it's by infrastructure role (gateway, backend, unassigned, consumer). For F5, it might be by partition or virtual server type. The checkboxes should match the admin's mental model.
|
|
30804
30804
|
|
|
30805
|
-
**Implementation \u2014 binding metadata:**
|
|
30805
|
+
**Implementation \u2014 binding metadata with per-type dropdown fields:**
|
|
30806
30806
|
|
|
30807
|
-
|
|
30808
|
-
|
|
30809
|
-
|
|
30807
|
+
Instead of a generic \`targetName\` field, use **per-type dropdown fields** that are conditionally shown based on \`bindingType\`. Each field links to the \`getTargetConfiguration\` response via \`x-targetConfigurationRef\`, creating actual dropdowns populated from the target system:
|
|
30808
|
+
|
|
30809
|
+
\`\`\`json
|
|
30810
|
+
"binding": {
|
|
30811
|
+
"properties": {
|
|
30812
|
+
"bindingType": {
|
|
30813
|
+
"oneOf": [
|
|
30814
|
+
{ "const": "management", "x-labelLocalizationKey": "bindingType.management" },
|
|
30815
|
+
{ "const": "https-listener", "x-labelLocalizationKey": "bindingType.httpsListener" },
|
|
30816
|
+
{ "const": "outbound-tls", "x-labelLocalizationKey": "bindingType.outboundTls" }
|
|
30817
|
+
]
|
|
30818
|
+
},
|
|
30819
|
+
"vipName": {
|
|
30820
|
+
"type": "string",
|
|
30821
|
+
"default": "",
|
|
30822
|
+
"x-targetConfigurationRef": "/vips",
|
|
30823
|
+
"x-rule": {
|
|
30824
|
+
"effect": "SHOW",
|
|
30825
|
+
"condition": { "scope": "#/properties/bindingType", "schema": { "const": "https-listener" } }
|
|
30826
|
+
}
|
|
30827
|
+
},
|
|
30828
|
+
"tunnelName": {
|
|
30829
|
+
"type": "string",
|
|
30830
|
+
"default": "",
|
|
30831
|
+
"x-targetConfigurationRef": "/ipsecTunnels",
|
|
30832
|
+
"x-rule": {
|
|
30833
|
+
"effect": "SHOW",
|
|
30834
|
+
"condition": { "scope": "#/properties/bindingType", "schema": { "const": "outbound-tls" } }
|
|
30835
|
+
}
|
|
30836
|
+
},
|
|
30837
|
+
"guiPath": {
|
|
30838
|
+
"type": "string",
|
|
30839
|
+
"default": "",
|
|
30840
|
+
"readOnly": true,
|
|
30841
|
+
"x-hidden": true
|
|
30842
|
+
}
|
|
30843
|
+
},
|
|
30844
|
+
"x-primaryKey": ["#/bindingType", "#/vipName", "#/tunnelName", "#/guiPath"]
|
|
30845
|
+
}
|
|
30846
|
+
\`\`\`
|
|
30847
|
+
|
|
30848
|
+
Key features of this pattern:
|
|
30849
|
+
- **\`x-targetConfigurationRef\`** links a field to a JSON path in the \`getTargetConfiguration\` response, populating a dropdown
|
|
30850
|
+
- **\`x-rule\` with \`"effect": "SHOW"\`** conditionally displays each field only when the relevant \`bindingType\` is selected
|
|
30851
|
+
- **\`default: ""\`** on every \`x-primaryKey\` field \u2014 CRITICAL for net new certificate provisioning (see section 30)
|
|
30852
|
+
- **\`x-hidden: true\`** hides metadata-only fields (like guiPath) from the provisioning UI while keeping them in the schema
|
|
30853
|
+
- Singleton types (management, SSL VPN) don't need a named target \u2014 the dropdown field stays hidden and empty
|
|
30854
|
+
- Named-target types (VIP, tunnel, profile) show a dropdown populated from the target system
|
|
30810
30855
|
|
|
30811
30856
|
**Implementation \u2014 buildBindingMap:**
|
|
30812
30857
|
|
|
30813
|
-
Query each service endpoint selectively based on the user's \`discoveryTypes\` selection. Build a \`map[string][]domain.Binding\` keyed by certificate name. Then when constructing discovery results, look up each cert's bindings and attach them as machine identities. Certs without bindings still get reported (with
|
|
30858
|
+
Query each service endpoint selectively based on the user's \`discoveryTypes\` selection. Build a \`map[string][]domain.Binding\` keyed by certificate name. Then when constructing discovery results, look up each cert's bindings and attach them as machine identities. Certs without bindings still get reported (with a fallback "unbound" binding) so they appear in Venafi.
|
|
30814
30859
|
|
|
30815
|
-
**
|
|
30816
|
-
- \`x-rule\` with \`effect: "SHOW"\` on \`targetName\` \u2014 hide it for singleton types where there's no named object to select
|
|
30817
|
-
- \`x-primaryKey: ["#/bindingType", "#/targetName"]\` \u2014 uniquely identifies each binding
|
|
30818
|
-
- \`discoveryTypes\` uses \`uniqueItems: true\` to prevent duplicate selections
|
|
30860
|
+
**Implementation \u2014 provisioning dispatch:**
|
|
30819
30861
|
|
|
30820
|
-
|
|
30862
|
+
In \`configureInstallationEndpoint\`, dispatch based on \`bindingType\` and the per-type field values:
|
|
30863
|
+
|
|
30864
|
+
\`\`\`go
|
|
30865
|
+
switch binding.BindingType {
|
|
30866
|
+
case "https-listener":
|
|
30867
|
+
if binding.VipName == "" {
|
|
30868
|
+
return handler.SetSSLVPNServerCert(certName) // global SSL VPN
|
|
30869
|
+
}
|
|
30870
|
+
return handler.SetVIPCertificate(binding.VipName, certName) // specific VIP
|
|
30871
|
+
case "outbound-tls":
|
|
30872
|
+
if binding.TunnelName == "" {
|
|
30873
|
+
return fmt.Errorf("tunnelName is required for outbound-tls")
|
|
30874
|
+
}
|
|
30875
|
+
return handler.SetIPsecCertificate(binding.TunnelName, certName)
|
|
30876
|
+
}
|
|
30877
|
+
\`\`\`
|
|
30878
|
+
|
|
30879
|
+
This eliminates guiPath-based dispatch and gives users explicit dropdown selection for each target type.
|
|
30880
|
+
|
|
30881
|
+
**Why this matters**: Without per-type dropdowns, users must type exact target names from memory. With \`x-targetConfigurationRef\`, they get a dropdown populated from the actual target system \u2014 reducing errors and improving the provisioning UX significantly.
|
|
30821
30882
|
|
|
30822
30883
|
### 15. Onboard Discovery vs Network Discovery \u2014 Don't Report What You Can't Observe
|
|
30823
30884
|
|
|
@@ -31004,30 +31065,35 @@ With catalog-level enrichment, BOTH entries show: Product = "Product Discovery,
|
|
|
31004
31065
|
\`\`\`go
|
|
31005
31066
|
// matchProfileToProduct matches a TLS profile to a specific product
|
|
31006
31067
|
// by checking if the profile title starts with a product title.
|
|
31007
|
-
//
|
|
31068
|
+
// Returns empty strings if no match (caller defaults to "N/A").
|
|
31069
|
+
// NEVER fall back to catalog-level aggregates \u2014 a catalog with 1786 APIs
|
|
31070
|
+
// produces an ~80KB string that gets duplicated into every binding,
|
|
31071
|
+
// causing 1MB Kafka message limit failures on satellite\u2192platform transport.
|
|
31008
31072
|
func matchProfileToProduct(profileTitle string, cp catalogProductInfo) (string, string) {
|
|
31009
31073
|
if profileTitle != "" {
|
|
31010
31074
|
for _, pd := range cp.Products {
|
|
31011
31075
|
if strings.HasPrefix(profileTitle, pd.Title) {
|
|
31012
|
-
|
|
31076
|
+
apiStr := strings.Join(pd.ApiTitles, ", ")
|
|
31077
|
+
return pd.Title, truncateMetadata(apiStr, len(pd.ApiTitles))
|
|
31013
31078
|
}
|
|
31014
31079
|
}
|
|
31015
31080
|
}
|
|
31016
|
-
return
|
|
31081
|
+
return "", "" // caller sets "N/A" defaults
|
|
31017
31082
|
}
|
|
31018
31083
|
\`\`\`
|
|
31019
31084
|
|
|
31020
31085
|
**Implementation requirements**:
|
|
31021
|
-
1. \`buildCatalogProductMap\` must return per-product detail (title + API list)
|
|
31086
|
+
1. \`buildCatalogProductMap\` must return per-product detail (title + API list) \u2014 do NOT store catalog-level aggregate strings (\`AllProductNames\`, \`AllApiNames\`)
|
|
31022
31087
|
2. Products have \`api_urls\` referencing their APIs \u2014 resolve these against the API list to get per-product API titles
|
|
31023
31088
|
3. The matching uses \`strings.HasPrefix\` on the profile title vs product title \u2014 reliable for standard naming conventions
|
|
31024
|
-
4.
|
|
31089
|
+
4. **NEVER fall back to catalog-level aggregates** \u2014 return empty strings (caller defaults to "N/A"). Catalog-level aggregates were the root cause of 1MB Kafka limit failures in production (a single catalog with 1786 APIs produced 80KB strings duplicated into every binding, creating 2MB+ pages that were silently dropped)
|
|
31025
31090
|
|
|
31026
31091
|
**Result after fix**:
|
|
31027
31092
|
- \`tls-gw-product-discovery\` \u2192 Product = "Product Discovery", APIs = "Product Catalog API"
|
|
31028
31093
|
- \`tls-gw-customer-platform\` \u2192 Product = "Customer Platform", APIs = "Customer API, Order API"
|
|
31094
|
+
- Unmatched profiles \u2192 Product = "N/A", APIs = "N/A" (safe, small, and honest)
|
|
31029
31095
|
|
|
31030
|
-
**Why this matters**: When users see duplicate-looking entries in the Venafi UI, they can't determine which platform service each certificate installation belongs to. Discovery should provide the MOST SPECIFIC context available for each binding,
|
|
31096
|
+
**Why this matters**: When users see duplicate-looking entries in the Venafi UI, they can't determine which platform service each certificate installation belongs to. Discovery should provide the MOST SPECIFIC context available for each binding. If no specific match exists, "N/A" is better than dumping every product/API in the catalog \u2014 which is both misleading and a response size bomb.
|
|
31031
31097
|
|
|
31032
31098
|
### 21. CRITICAL: Discovery Field Labels \u2014 title vs x-labelLocalizationKey
|
|
31033
31099
|
|
|
@@ -31500,6 +31566,390 @@ dc := &DiscoveredCertificate{
|
|
|
31500
31566
|
\`\`\`
|
|
31501
31567
|
|
|
31502
31568
|
**Alternative**: Use a pointer with \`omitempty\` if the field is truly optional. But for \`certificateChain\` and \`machineIdentities\`, always send \`[]\` rather than omitting or sending \`null\`.
|
|
31569
|
+
|
|
31570
|
+
### 29. CRITICAL: Discovery Response Size \u2014 1MB Kafka Limit and Metadata String Inflation
|
|
31571
|
+
|
|
31572
|
+
**The symptom**: Discovery completes page 1 successfully (connector logs show valid response with \`hasMorePages:true\` and correct paginator), but the platform never requests page 2. The discovery job stays in RUNNING state forever. No errors appear in the connector logs \u2014 the response write succeeds from the connector's perspective.
|
|
31573
|
+
|
|
31574
|
+
**The root cause**: The vSatellite relays discovery responses to the Venafi platform via **Kafka**, which has a **1MB message size limit**. When the response exceeds this limit, the message is silently dropped. The platform never receives the response (including the pagination token) and never requests the next page. Note: the connector communicates with the satellite over HTTP, and the satellite communicates with the platform over Kafka \u2014 so the connector never sees the Kafka rejection.
|
|
31575
|
+
|
|
31576
|
+
**How responses get inflated**: The #1 cause is **metadata string duplication in bindings**. Never enrich bindings with aggregated catalog-level or scope-level data (e.g., all product names or all API titles). In production, a single catalog had 1786 APIs \u2014 joining all titles into one string produced ~80KB that got copied into every binding. With 25 certs per page, that's 2MB+ just from API names, far exceeding the 1MB Kafka limit.
|
|
31577
|
+
|
|
31578
|
+
**How we discovered this**: We reproduced the issue by injecting 1786 fake API names into the enrichment path. The response grew to 2.1MB per page of 25 certs, and the Venafi platform silently stopped paginating \u2014 exactly matching the customer's symptom. Without the injection (our 9-API test environment), pages were 54KB and pagination worked perfectly.
|
|
31579
|
+
|
|
31580
|
+
**The fix \u2014 only enrich on exact match, never aggregate**:
|
|
31581
|
+
|
|
31582
|
+
The root cause was a fallback path in \`matchProfileToProduct\` that dumped ALL product/API names from the entire catalog into every binding that didn't match a specific product. The fix is simple: **if you can't match to a specific item, return empty/default values ("N/A"), never aggregate**.
|
|
31583
|
+
|
|
31584
|
+
\`\`\`go
|
|
31585
|
+
// CORRECT: Only return metadata on exact match. Default to empty (caller sets "N/A").
|
|
31586
|
+
func matchProfileToProduct(profileTitle string, cp catalogProductInfo) (string, string) {
|
|
31587
|
+
if profileTitle != "" {
|
|
31588
|
+
for _, pd := range cp.Products {
|
|
31589
|
+
if strings.HasPrefix(profileTitle, pd.Title) {
|
|
31590
|
+
apiStr := strings.Join(pd.ApiTitles, ", ")
|
|
31591
|
+
return pd.Title, truncateMetadata(apiStr, len(pd.ApiTitles))
|
|
31592
|
+
}
|
|
31593
|
+
}
|
|
31594
|
+
}
|
|
31595
|
+
return "", "" // caller defaults to "N/A"
|
|
31596
|
+
}
|
|
31597
|
+
|
|
31598
|
+
// WRONG: Never fall back to catalog-level aggregates
|
|
31599
|
+
// return cp.AllProductNames, cp.AllApiNames // <-- THIS CAUSED 2MB+ PAGES
|
|
31600
|
+
\`\`\`
|
|
31601
|
+
|
|
31602
|
+
**Defense in depth \u2014 truncateMetadata for exact matches**: Even a single product can have many APIs, so apply \`truncateMetadata()\` to cap any metadata string at 200 chars:
|
|
31603
|
+
|
|
31604
|
+
\`\`\`go
|
|
31605
|
+
const maxMetadataFieldLen = 200
|
|
31606
|
+
|
|
31607
|
+
func truncateMetadata(s string, totalCount int) string {
|
|
31608
|
+
if len(s) <= maxMetadataFieldLen {
|
|
31609
|
+
return s
|
|
31610
|
+
}
|
|
31611
|
+
cut := strings.LastIndex(s[:maxMetadataFieldLen], ",")
|
|
31612
|
+
if cut <= 0 {
|
|
31613
|
+
cut = maxMetadataFieldLen
|
|
31614
|
+
}
|
|
31615
|
+
shown := strings.Count(s[:cut], ",") + 1
|
|
31616
|
+
return fmt.Sprintf("%s (+%d more)", s[:cut], totalCount-shown)
|
|
31617
|
+
}
|
|
31618
|
+
\`\`\`
|
|
31619
|
+
|
|
31620
|
+
**Additional defensive measures**:
|
|
31621
|
+
1. **Keep page size small**: Use \`maxPageSize = 10\` to provide headroom against the 1MB Kafka limit
|
|
31622
|
+
2. **Do not store catalog-level aggregate strings**: The \`catalogProductInfo\` struct should only hold per-product detail (\`[]productDetail\`), never \`AllProductNames\` or \`AllApiNames\` aggregate fields \u2014 if those fields exist, someone will use them in a fallback
|
|
31623
|
+
3. **Single marshal + JSONBlob**: Marshal the response once, use \`c.JSONBlob()\` to send. Avoids double-allocating multi-MB responses
|
|
31624
|
+
4. **Profile every page**: Log \`responseSizeBytes\`, \`maxBindingApiNamesLen\`, \`avgMIBytes\`, and \`totalMIJSONBytes\` per batch to detect inflation early
|
|
31625
|
+
|
|
31626
|
+
**Key insight**: This failure is invisible to the connector. The HTTP response write succeeds (Echo sends the bytes to the satellite), but Kafka silently drops the oversized message before it reaches the platform. The platform never sees the response and never requests the next page. The only way to detect it is by checking \`responseSizeBytes\` in the connector logs and comparing against the 1MB Kafka limit.
|
|
31627
|
+
|
|
31628
|
+
**Rule of thumb**: Keep \`responseSizeBytes\` well under 1MB per page. If it exceeds 500KB, investigate immediately. Never put aggregated/unbounded strings into binding metadata \u2014 if you can't match to a specific item, use "N/A".
|
|
31629
|
+
|
|
31630
|
+
### 30. CRITICAL: x-primaryKey Fields MUST Have default Values for Net New Provisioning
|
|
31631
|
+
|
|
31632
|
+
**The symptom**: Provisioning an existing (discovered) certificate works fine \u2014 the certificate is pushed to the target and the binding is configured. But provisioning a **net new certificate** (one that was not previously discovered) stalls indefinitely. The Venafi UI shows the provisioning job as RUNNING but it never completes.
|
|
31633
|
+
|
|
31634
|
+
**The root cause**: When provisioning a net new certificate, the Venafi platform constructs the binding from the manifest schema. If any \`x-primaryKey\` field lacks a \`"default"\` value, the platform returns error **20106**: *"property X is a primary key but is not specified in request and has no default value"*. This error is NOT visible in the connector logs \u2014 it happens platform-side before the request ever reaches the connector.
|
|
31635
|
+
|
|
31636
|
+
**Why discovered certs work**: When re-provisioning a discovered certificate, the binding already has values for all fields (populated during discovery). The \`default\` is only needed when the platform must construct a binding from scratch.
|
|
31637
|
+
|
|
31638
|
+
**The fix**: Every field in \`x-primaryKey\` MUST have a \`"default"\` value in the manifest schema:
|
|
31639
|
+
|
|
31640
|
+
\`\`\`json
|
|
31641
|
+
"binding": {
|
|
31642
|
+
"properties": {
|
|
31643
|
+
"bindingType": { "oneOf": [...] },
|
|
31644
|
+
"vipName": { "type": "string", "default": "" },
|
|
31645
|
+
"tunnelName": { "type": "string", "default": "" },
|
|
31646
|
+
"profileName": { "type": "string", "default": "" },
|
|
31647
|
+
"guiPath": { "type": "string", "default": "", "readOnly": true, "x-hidden": true }
|
|
31648
|
+
},
|
|
31649
|
+
"x-primaryKey": ["#/bindingType", "#/vipName", "#/tunnelName", "#/profileName", "#/guiPath"]
|
|
31650
|
+
}
|
|
31651
|
+
\`\`\`
|
|
31652
|
+
|
|
31653
|
+
**Debugging checklist** when net new provisioning stalls:
|
|
31654
|
+
1. Check if provisioning of previously-discovered certs works (if yes, this is likely the issue)
|
|
31655
|
+
2. Look for error 20106 in the Venafi platform activity log (\`POST /v1/activitylogsearch\`)
|
|
31656
|
+
3. Verify every \`x-primaryKey\` field has a \`"default"\` in the manifest
|
|
31657
|
+
4. Deploy a new plugin revision with the defaults added
|
|
31658
|
+
|
|
31659
|
+
### 31. CRITICAL: PKCS12 Must Use Legacy Encoding for Network Appliances
|
|
31660
|
+
|
|
31661
|
+
**The mistake**: Using modern PKCS12 encoding (AES-256-CBC + SHA-256) when building PKCS12 bundles for certificate import to network appliances.
|
|
31662
|
+
|
|
31663
|
+
**The problem**: Many network appliances (FortiGate confirmed, likely F5 and others) cannot parse modern PKCS12 encoding. The import API returns success or a vague error, but the certificate is not usable. FortiGate specifically requires the legacy PKCS12 format (3DES + SHA1).
|
|
31664
|
+
|
|
31665
|
+
**The fix**: When building PKCS12 bundles in Go, use the Legacy encoder:
|
|
31666
|
+
|
|
31667
|
+
\`\`\`go
|
|
31668
|
+
import gopkcs12 "software.sslmate.com/src/go-pkcs12"
|
|
31669
|
+
|
|
31670
|
+
// CORRECT: Legacy encoding (3DES/SHA1) \u2014 compatible with network appliances
|
|
31671
|
+
p12Data, err := gopkcs12.Legacy.Encode(key, cert, caCerts, password)
|
|
31672
|
+
|
|
31673
|
+
// WRONG: Modern encoding (AES-256/SHA-256) \u2014 rejected by FortiGate and similar appliances
|
|
31674
|
+
// p12Data, err := gopkcs12.Modern.Encode(key, cert, caCerts, password)
|
|
31675
|
+
\`\`\`
|
|
31676
|
+
|
|
31677
|
+
**Why this happens**: The \`go-pkcs12\` library defaults to \`Modern\` encoding which uses AES-256-CBC and SHA-256. While more secure, this format is not supported by older TLS stacks. Network appliances typically use OpenSSL 1.x-era PKCS12 parsing that only understands the legacy 3DES/SHA1 format.
|
|
31678
|
+
|
|
31679
|
+
**Recommendation**: Always use \`gopkcs12.Legacy.Encode()\` for any connector targeting a network appliance. Only consider \`Modern\` for targets you've explicitly verified support it.
|
|
31680
|
+
|
|
31681
|
+
### 32. x-hidden Hides Manifest Fields from Provisioning UI
|
|
31682
|
+
|
|
31683
|
+
**The pattern**: When a binding field is needed for schema purposes (\`x-primaryKey\`) but should not be shown to users during provisioning, use \`"x-hidden": true\`:
|
|
31684
|
+
|
|
31685
|
+
\`\`\`json
|
|
31686
|
+
"guiPath": {
|
|
31687
|
+
"type": "string",
|
|
31688
|
+
"default": "",
|
|
31689
|
+
"readOnly": true,
|
|
31690
|
+
"x-hidden": true,
|
|
31691
|
+
"x-labelLocalizationKey": "guiPath.label",
|
|
31692
|
+
"x-rank": 4
|
|
31693
|
+
}
|
|
31694
|
+
\`\`\`
|
|
31695
|
+
|
|
31696
|
+
This is useful for metadata fields populated by discovery (like a navigation path or internal ID) that provide context in the Venafi UI's installation detail view but shouldn't appear as an input field when a user is provisioning a new certificate.
|
|
31697
|
+
|
|
31698
|
+
**Difference from readOnly**: \`readOnly: true\` alone still renders the field in the UI (as a non-editable input). \`x-hidden: true\` prevents it from rendering entirely.
|
|
31699
|
+
|
|
31700
|
+
### 33. Image Digest, Not Tag, for vSatellite Deployments
|
|
31701
|
+
|
|
31702
|
+
**The problem**: Using \`:latest\` tag in the plugin manifest's \`deployment.image\` field causes intermittent image pull failures on vSatellite, even after pushing a new image.
|
|
31703
|
+
|
|
31704
|
+
**The root cause**: The vSatellite's k3s runtime caches images by digest. When the manifest specifies \`:latest\`, the satellite resolves it to a digest at pull time. If the registry mirror (via \`registries.yaml\`) rewrites or caches the digest resolution, subsequent pulls may fail with \`ErrImagePull\` due to digest mismatch.
|
|
31705
|
+
|
|
31706
|
+
**The fix**: Always use \`image@sha256:...\` (digest reference) in the plugin manifest, never a tag:
|
|
31707
|
+
|
|
31708
|
+
\`\`\`json
|
|
31709
|
+
"deployment": {
|
|
31710
|
+
"image": "public.ecr.aws/example/connector@sha256:abc123...",
|
|
31711
|
+
"executionTarget": "vsat"
|
|
31712
|
+
}
|
|
31713
|
+
\`\`\`
|
|
31714
|
+
|
|
31715
|
+
Extract the digest from the build output:
|
|
31716
|
+
|
|
31717
|
+
\`\`\`bash
|
|
31718
|
+
# After docker buildx push
|
|
31719
|
+
DIGEST=$(jq -r '.["containerimage.digest"]' buildx-digest.json)
|
|
31720
|
+
\`\`\`
|
|
31721
|
+
|
|
31722
|
+
**Additional k3s gotcha**: The vSatellite's \`registries.yaml\` may have a catch-all mirror entry for \`registry.venafi.cloud\` that rewrites image pulls. If your image is in a public registry (e.g., public ECR), ensure that registry's mirror entry appears BEFORE the wildcard catch-all. A misplaced wildcard causes digest mismatch errors on pull.
|
|
31723
|
+
|
|
31724
|
+
### 34. Plugin API \u2014 PATCH Not POST, pluginType at Top Level
|
|
31725
|
+
|
|
31726
|
+
**When registering a connector on a new tenant**: Use \`POST /v1/plugins\` with \`pluginType\` at the top level alongside \`manifest\`:
|
|
31727
|
+
|
|
31728
|
+
\`\`\`bash
|
|
31729
|
+
curl -X POST -H "tppl-api-key: $KEY" -H "Content-Type: application/json" \\
|
|
31730
|
+
-d '{"pluginType": "MACHINE", "manifest": {...}}' \\
|
|
31731
|
+
"https://api.venafi.cloud/v1/plugins"
|
|
31732
|
+
\`\`\`
|
|
31733
|
+
|
|
31734
|
+
**When updating an existing plugin**: Use \`PATCH /v1/plugins/{id}\`. POST returns error **16004** if the plugin already exists.
|
|
31735
|
+
|
|
31736
|
+
**Common mistake**: Putting \`pluginType\` inside the \`manifest\` object. The API requires it at the top level of the request body alongside \`manifest\`.
|
|
31737
|
+
|
|
31738
|
+
### 35. x-targetConfigurationRef Powers Dropdown Fields from getTargetConfiguration
|
|
31739
|
+
|
|
31740
|
+
**The pattern**: When your target system has enumerable resources (scopes, profiles, tunnels, virtual IPs, partitions), return them from the \`getTargetConfiguration\` endpoint and reference them in the manifest to create dropdown fields:
|
|
31741
|
+
|
|
31742
|
+
**Step 1 \u2014 Define the targetConfiguration schema in manifest:**
|
|
31743
|
+
\`\`\`json
|
|
31744
|
+
"targetConfiguration": {
|
|
31745
|
+
"properties": {
|
|
31746
|
+
"vdoms": { "type": "array", "items": { "type": "string" } },
|
|
31747
|
+
"vips": { "type": "array", "items": { "type": "string" } },
|
|
31748
|
+
"tunnels": { "type": "array", "items": { "type": "string" } }
|
|
31749
|
+
},
|
|
31750
|
+
"type": "object"
|
|
31751
|
+
}
|
|
31752
|
+
\`\`\`
|
|
31753
|
+
|
|
31754
|
+
**Step 2 \u2014 Return values from the Go handler:**
|
|
31755
|
+
\`\`\`go
|
|
31756
|
+
type GetTargetConfigurationResponse struct {
|
|
31757
|
+
VDOMs []string \`json:"vdoms"\`
|
|
31758
|
+
VIPs []string \`json:"vips"\`
|
|
31759
|
+
Tunnels []string \`json:"tunnels"\`
|
|
31760
|
+
}
|
|
31761
|
+
\`\`\`
|
|
31762
|
+
|
|
31763
|
+
**Step 3 \u2014 Reference in binding/keystore fields:**
|
|
31764
|
+
\`\`\`json
|
|
31765
|
+
"vipName": {
|
|
31766
|
+
"type": "string",
|
|
31767
|
+
"default": "",
|
|
31768
|
+
"x-targetConfigurationRef": "/vips",
|
|
31769
|
+
"x-rule": {
|
|
31770
|
+
"effect": "SHOW",
|
|
31771
|
+
"condition": { "scope": "#/properties/bindingType", "schema": { "const": "https-listener" } }
|
|
31772
|
+
}
|
|
31773
|
+
}
|
|
31774
|
+
\`\`\`
|
|
31775
|
+
|
|
31776
|
+
The JSON path in \`x-targetConfigurationRef\` (e.g., \`"/vips"\`) must match the key in your \`getTargetConfiguration\` response. The UI will populate a dropdown with the returned values.
|
|
31777
|
+
|
|
31778
|
+
**Combine with x-rule**: Use \`x-rule\` to only show the dropdown when the relevant binding type is selected. This keeps the UI clean \u2014 users only see fields relevant to their selected binding type.
|
|
31779
|
+
|
|
31780
|
+
**IMPORTANT limitation**: \`x-targetConfigurationRef\` dropdowns do NOT populate during net new provisioning in the Venafi UI. The platform only calls \`getTargetConfiguration\` for discovery workflows and when editing existing installations. During net new provisioning, these fields render as plain text boxes. Users must type the exact target name. Use \`oneOf\` in the manifest schema for fields that MUST always be dropdowns (like binding type, certificate type).
|
|
31781
|
+
|
|
31782
|
+
### 36. CRITICAL: Duplicate Certificate Handling During Provisioning
|
|
31783
|
+
|
|
31784
|
+
**The problem**: When provisioning is retried (due to timeouts, partial failures, or the user re-provisioning the same cert to a different binding), the target system may already have the certificate from a previous attempt. Attempting to import a certificate that already exists often returns an error (e.g., HTTP 500 "entry already exists").
|
|
31785
|
+
|
|
31786
|
+
**The fix**: Before importing, always check if a certificate with the target name already exists on the system. If it does, skip the import and proceed to the binding step:
|
|
31787
|
+
|
|
31788
|
+
\`\`\`go
|
|
31789
|
+
existingCerts, err := handler.GetCertificateDetails()
|
|
31790
|
+
if err == nil {
|
|
31791
|
+
if certExistsByName(existingCerts, certName) {
|
|
31792
|
+
log.Info("certificate already exists, skipping upload", "name", certName)
|
|
31793
|
+
keystore.CertificateName = certName
|
|
31794
|
+
return keystore, nil
|
|
31795
|
+
}
|
|
31796
|
+
}
|
|
31797
|
+
\`\`\`
|
|
31798
|
+
|
|
31799
|
+
**Name collision with different content**: If a cert with the same name exists but has different content (e.g., renewed cert), generate a unique name. Then check if THAT name also exists before importing \u2014 previous failed attempts may have already uploaded it:
|
|
31800
|
+
|
|
31801
|
+
\`\`\`go
|
|
31802
|
+
// Name collision \u2014 generate unique name
|
|
31803
|
+
uniqueName := generateUniqueName(baseName, cert)
|
|
31804
|
+
// Check if the unique name also exists from a previous attempt
|
|
31805
|
+
if certExistsByName(existingCerts, uniqueName) {
|
|
31806
|
+
log.Info("unique name also exists, skipping upload", "name", uniqueName)
|
|
31807
|
+
keystore.CertificateName = uniqueName
|
|
31808
|
+
return keystore, nil
|
|
31809
|
+
}
|
|
31810
|
+
// Safe to import with unique name
|
|
31811
|
+
\`\`\`
|
|
31812
|
+
|
|
31813
|
+
**Key insight**: The Venafi platform retries provisioning workflows. Your connector MUST be idempotent \u2014 importing the same certificate twice should not fail.
|
|
31814
|
+
|
|
31815
|
+
### 37. CRITICAL: Fingerprint Format Varies by Target System
|
|
31816
|
+
|
|
31817
|
+
**The problem**: When comparing certificate fingerprints to detect duplicates, the target system may use a different hash algorithm than what you compute. For example, SHA-1 (40 hex chars) vs SHA-256 (64 hex chars), or different formatting (colons vs no colons, uppercase vs lowercase).
|
|
31818
|
+
|
|
31819
|
+
**The fix**: Normalize fingerprints before comparing \u2014 strip colons, uppercase, and check that lengths match (same hash algorithm) before comparing values:
|
|
31820
|
+
|
|
31821
|
+
\`\`\`go
|
|
31822
|
+
func fingerprintsMatch(a, b string) bool {
|
|
31823
|
+
normA := strings.ToUpper(strings.ReplaceAll(a, ":", ""))
|
|
31824
|
+
normB := strings.ToUpper(strings.ReplaceAll(b, ":", ""))
|
|
31825
|
+
if normA == "" || normB == "" {
|
|
31826
|
+
return false
|
|
31827
|
+
}
|
|
31828
|
+
if len(normA) != len(normB) {
|
|
31829
|
+
return false // Different hash algorithms, can't compare
|
|
31830
|
+
}
|
|
31831
|
+
return normA == normB
|
|
31832
|
+
}
|
|
31833
|
+
\`\`\`
|
|
31834
|
+
|
|
31835
|
+
If fingerprints can't be reliably compared (different hash algorithms), fall back to name-based duplicate detection rather than assuming the content is different.
|
|
31836
|
+
|
|
31837
|
+
### 38. CRITICAL: Service-Disruptive Binding Changes Need Short Timeouts
|
|
31838
|
+
|
|
31839
|
+
**The problem**: Some binding operations cause the target system to restart a service, which drops the HTTP connection used to make the API call. The connector blocks waiting for a response that will never arrive (TCP timeout can be 30-60+ seconds). Meanwhile, the Venafi platform's workflow engine times out waiting for the connector's HTTP response, causing a \`RETRY_STATE_TIMEOUT\` failure.
|
|
31840
|
+
|
|
31841
|
+
**Examples**: Changing an admin/management HTTPS certificate, updating an SSL VPN certificate, rotating a load balancer listener cert.
|
|
31842
|
+
|
|
31843
|
+
**The fix**: Use a short request timeout (5-10 seconds) for binding operations that are known to cause service restarts. Treat any network error as success, since the change was already applied before the connection dropped:
|
|
31844
|
+
|
|
31845
|
+
\`\`\`go
|
|
31846
|
+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
31847
|
+
defer cancel()
|
|
31848
|
+
|
|
31849
|
+
resp, err := client.R().
|
|
31850
|
+
SetContext(ctx).
|
|
31851
|
+
SetBody(payload).
|
|
31852
|
+
Put("/api/endpoint/that/restarts/service")
|
|
31853
|
+
if err != nil {
|
|
31854
|
+
// Service restarted \u2014 the change was applied before the connection dropped.
|
|
31855
|
+
log.Info("expected connection drop after binding change, treating as success")
|
|
31856
|
+
return nil
|
|
31857
|
+
}
|
|
31858
|
+
\`\`\`
|
|
31859
|
+
|
|
31860
|
+
**Why this matters**: The Venafi platform has its own timeout for workflow activities. If your connector blocks for 60 seconds waiting for a dead TCP connection, the platform will timeout and mark the provisioning as failed \u2014 even though the change succeeded on the target system.
|
|
31861
|
+
|
|
31862
|
+
### 39. x-hidden Does NOT Hide Fields from the Provisioning UI
|
|
31863
|
+
|
|
31864
|
+
**The problem**: Setting \`"x-hidden": true\` on a manifest field does NOT prevent it from appearing in the net new provisioning form. The field still renders as a text box.
|
|
31865
|
+
|
|
31866
|
+
**What x-hidden actually does**: It may only affect discovery result display or other non-provisioning contexts. The Venafi provisioning UI renders ALL fields defined in \`properties\`, regardless of \`x-hidden\`, \`readOnly\`, or other attributes.
|
|
31867
|
+
|
|
31868
|
+
**What you CANNOT do**: Remove the field from \`properties\` if it's referenced in \`x-primaryKey\`. Doing so crashes the Venafi UI (blank page, user gets logged out). All \`x-primaryKey\` fields MUST exist in \`properties\`.
|
|
31869
|
+
|
|
31870
|
+
**Workaround**: Accept that internal/metadata fields (like GUI navigation paths) will appear as read-only text boxes in the provisioning form. Use \`"readOnly": true\` to prevent user modification, and set \`"default": ""\` so net new provisioning works. This is cosmetic \u2014 it doesn't affect functionality.
|
|
31871
|
+
|
|
31872
|
+
### 40. x-primaryKey Field Requirements \u2014 Summary
|
|
31873
|
+
|
|
31874
|
+
All fields listed in \`x-primaryKey\` MUST:
|
|
31875
|
+
1. Exist in the \`properties\` object (removing them crashes the UI)
|
|
31876
|
+
2. Have \`"default": ""\` (or another default) \u2014 without a default, net new provisioning fails with error 20106
|
|
31877
|
+
3. Be of type \`"string"\` \u2014 complex types are not supported in primary keys
|
|
31878
|
+
|
|
31879
|
+
The \`x-primaryKey\` array defines uniqueness for machine identity bindings during discovery. Every unique combination of primary key values creates a separate binding entry. For provisioning, unused primary key fields default to empty string.
|
|
31880
|
+
|
|
31881
|
+
### 41. CRITICAL: Do NOT Re-Marshal Private Keys When Target Accepts PEM
|
|
31882
|
+
|
|
31883
|
+
**The mistake**: Parsing a DER private key into a Go type and then re-marshaling it back to DER before PEM-encoding. This round-trip can subtly change the DER encoding, causing the target to reject the key.
|
|
31884
|
+
|
|
31885
|
+
**The symptom**: GCP Certificate Manager returns \`400: unsupported encryption key size\` when updating a self-managed certificate with a re-marshaled EC key. The key parses fine in Go, but the re-marshaled DER bytes differ from the original.
|
|
31886
|
+
|
|
31887
|
+
**The wrong approach** (from lesson #21's example used for PKCS12):
|
|
31888
|
+
|
|
31889
|
+
\`\`\`go
|
|
31890
|
+
// WRONG for PEM output \u2014 re-marshaling changes the DER encoding
|
|
31891
|
+
if key, err := x509.ParseECPrivateKey(keyDER); err == nil {
|
|
31892
|
+
ecDER, _ := x509.MarshalECPrivateKey(key) // re-marshaled bytes may differ!
|
|
31893
|
+
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: ecDER}), nil
|
|
31894
|
+
}
|
|
31895
|
+
\`\`\`
|
|
31896
|
+
|
|
31897
|
+
**The correct approach** \u2014 detect the type but wrap the original bytes:
|
|
31898
|
+
|
|
31899
|
+
\`\`\`go
|
|
31900
|
+
// CORRECT for PEM output \u2014 use original DER bytes, no re-marshaling
|
|
31901
|
+
if _, err := x509.ParsePKCS1PrivateKey(keyDER); err == nil {
|
|
31902
|
+
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyDER}), nil
|
|
31903
|
+
}
|
|
31904
|
+
if _, err := x509.ParseECPrivateKey(keyDER); err == nil {
|
|
31905
|
+
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}), nil
|
|
31906
|
+
}
|
|
31907
|
+
if _, err := x509.ParsePKCS8PrivateKey(keyDER); err == nil {
|
|
31908
|
+
return pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), nil
|
|
31909
|
+
}
|
|
31910
|
+
\`\`\`
|
|
31911
|
+
|
|
31912
|
+
**When to use which approach**:
|
|
31913
|
+
- **Target accepts PEM** (GCP, AWS, cloud APIs): Use the no-re-marshal approach above. Just detect the type for the PEM header and wrap the original DER bytes.
|
|
31914
|
+
- **Target requires PKCS12** (FortiGate, network appliances): You MUST parse into Go types to pass to \`pkcs12.Encode()\`. Re-marshaling is unavoidable and acceptable here because the PKCS12 encoder produces its own encoding.
|
|
31915
|
+
|
|
31916
|
+
### 42. CRITICAL: Handle Both DER and PEM Input in Certificate Bundle
|
|
31917
|
+
|
|
31918
|
+
**The problem**: The Venafi platform sends certificate bundles with \`contentEncoding: "base64"\` in the manifest. Go's JSON unmarshaler automatically base64-decodes \`[]byte\` fields. However, the decoded bytes may be either **raw DER** or **PEM-encoded text**, depending on how the certificate was originally stored.
|
|
31919
|
+
|
|
31920
|
+
**The symptom**: \`x509.ParseCertificate()\` fails with "asn1: structure error" when the input is PEM text (which starts with \`-----BEGIN\` ASCII bytes, not a valid ASN.1 sequence). Or the private key parser fails silently.
|
|
31921
|
+
|
|
31922
|
+
**The fix**: Always check for PEM encoding first before assuming DER:
|
|
31923
|
+
|
|
31924
|
+
\`\`\`go
|
|
31925
|
+
func convertBundleToPEM(bundle *domain.CertificateBundle) (certPEM, keyPEM string, chainPEMs []string, err error) {
|
|
31926
|
+
// Handle both DER and PEM input
|
|
31927
|
+
certBytes := bundle.Certificate
|
|
31928
|
+
if block, _ := pem.Decode(bundle.Certificate); block != nil {
|
|
31929
|
+
certBytes = block.Bytes // extract DER from PEM
|
|
31930
|
+
}
|
|
31931
|
+
cert, err := x509.ParseCertificate(certBytes)
|
|
31932
|
+
// ...
|
|
31933
|
+
|
|
31934
|
+
// Same for private key
|
|
31935
|
+
if block, _ := pem.Decode(bundle.PrivateKey); block != nil {
|
|
31936
|
+
keyPEM = string(pem.EncodeToMemory(block)) // already PEM, re-encode cleanly
|
|
31937
|
+
} else {
|
|
31938
|
+
keyPEM = derKeyToPEM(bundle.PrivateKey) // DER, wrap in PEM
|
|
31939
|
+
}
|
|
31940
|
+
|
|
31941
|
+
// Same for chain certificates
|
|
31942
|
+
for _, chainBytes := range bundle.CertificateChain {
|
|
31943
|
+
if block, _ := pem.Decode(chainBytes); block != nil {
|
|
31944
|
+
chainPEMs = append(chainPEMs, string(pem.EncodeToMemory(block)))
|
|
31945
|
+
} else {
|
|
31946
|
+
// parse DER and encode to PEM
|
|
31947
|
+
}
|
|
31948
|
+
}
|
|
31949
|
+
}
|
|
31950
|
+
\`\`\`
|
|
31951
|
+
|
|
31952
|
+
**Key insight**: This pattern makes the connector resilient to both input formats. The \`pem.Decode()\` check is cheap (just looks for \`-----BEGIN\`) and handles the ambiguity gracefully.
|
|
31503
31953
|
`;
|
|
31504
31954
|
var MACHINE_MANIFEST_TEMPLATE = `
|
|
31505
31955
|
{
|
|
@@ -33426,37 +33876,37 @@ var server = new McpServer({
|
|
|
33426
33876
|
name: "venafi-connector-machine",
|
|
33427
33877
|
version: "1.0.0"
|
|
33428
33878
|
});
|
|
33429
|
-
server.resource("machine-blueprint", "machine
|
|
33879
|
+
server.resource("machine-blueprint", "venafi://connector-machine/blueprint", {
|
|
33430
33880
|
description: "Machine connector architecture \u2014 5 endpoints, Temporal workflow model, SSH client abstraction, discovery/provisioning patterns, manifest domainSchema",
|
|
33431
33881
|
mimeType: "text/markdown"
|
|
33432
33882
|
}, async () => ({
|
|
33433
33883
|
contents: [
|
|
33434
33884
|
{
|
|
33435
|
-
uri: "machine
|
|
33885
|
+
uri: "venafi://connector-machine/blueprint",
|
|
33436
33886
|
text: MACHINE_BLUEPRINT,
|
|
33437
33887
|
mimeType: "text/markdown"
|
|
33438
33888
|
}
|
|
33439
33889
|
]
|
|
33440
33890
|
}));
|
|
33441
|
-
server.resource("lessons-learned", "machine
|
|
33891
|
+
server.resource("lessons-learned", "venafi://connector-machine/lessons-learned", {
|
|
33442
33892
|
description: "What worked, what failed, and mistakes to avoid \u2014 from building the Splunk SSH connector. Covers SSH patterns, discovery challenges, certificate format issues, and more.",
|
|
33443
33893
|
mimeType: "text/markdown"
|
|
33444
33894
|
}, async () => ({
|
|
33445
33895
|
contents: [
|
|
33446
33896
|
{
|
|
33447
|
-
uri: "machine
|
|
33897
|
+
uri: "venafi://connector-machine/lessons-learned",
|
|
33448
33898
|
text: LESSONS_LEARNED,
|
|
33449
33899
|
mimeType: "text/markdown"
|
|
33450
33900
|
}
|
|
33451
33901
|
]
|
|
33452
33902
|
}));
|
|
33453
|
-
server.resource("machine-manifest-template", "machine
|
|
33903
|
+
server.resource("machine-manifest-template", "venafi://connector-machine/manifest-template", {
|
|
33454
33904
|
description: "Complete machine connector manifest.json template with connection (SSH), keystore, binding, certificateBundle, discovery, discoveryControl, discoveryPage sections",
|
|
33455
33905
|
mimeType: "application/json"
|
|
33456
33906
|
}, async () => ({
|
|
33457
33907
|
contents: [
|
|
33458
33908
|
{
|
|
33459
|
-
uri: "machine
|
|
33909
|
+
uri: "venafi://connector-machine/manifest-template",
|
|
33460
33910
|
text: MACHINE_MANIFEST_TEMPLATE,
|
|
33461
33911
|
mimeType: "application/json"
|
|
33462
33912
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "venafi-connector-machine",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "MCP server providing machine connector-specific knowledge, templates, and tools for building Venafi
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "MCP server providing machine connector-specific knowledge, templates, and tools for building Venafi machine connectors",
|
|
5
5
|
"main": "bundle.mjs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"mcp",
|
|
21
21
|
"model-context-protocol",
|
|
22
22
|
"venafi",
|
|
23
|
-
"tls-protect-cloud",
|
|
24
23
|
"machine-connector",
|
|
25
24
|
"ssh",
|
|
26
25
|
"rest-api",
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
"license": "Apache-2.0",
|
|
33
32
|
"repository": {
|
|
34
33
|
"type": "git",
|
|
35
|
-
"url": "https://github.com/abhadfield/venafi-
|
|
34
|
+
"url": "https://github.com/abhadfield/venafi-integration-mcps"
|
|
36
35
|
},
|
|
37
36
|
"dependencies": {
|
|
38
37
|
"@modelcontextprotocol/sdk": "^1.12.1"
|