mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-08 07:37:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
305 lines
8.3 KiB
Go
305 lines
8.3 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/vault/api"
|
|
|
|
"github.com/ghodss/yaml"
|
|
"github.com/ryanuber/columnize"
|
|
)
|
|
|
|
type PKIListIntermediateCommand struct {
|
|
*BaseCommand
|
|
|
|
flagConfig string
|
|
flagReturnIndicator string
|
|
flagDefaultDisabled bool
|
|
flagList bool
|
|
|
|
flagUseNames bool
|
|
|
|
flagSignatureMatch bool
|
|
flagIndirectSignMatch bool
|
|
flagKeyIdMatch bool
|
|
flagSubjectMatch bool
|
|
flagPathMatch bool
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) Synopsis() string {
|
|
return "Determine which of a list of certificates, were issued by a given parent certificate"
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) Help() string {
|
|
helpText := `
|
|
Usage: vault pki list-intermediates PARENT [CHILD] [CHILD] [CHILD] ...
|
|
|
|
Lists the set of intermediate CAs issued by this parent issuer.
|
|
|
|
PARENT is the certificate that might be the issuer that everything should
|
|
be verified against.
|
|
|
|
CHILD is an optional list of paths to certificates to be compared to the
|
|
PARENT, or pki mounts to look for certificates on. If CHILD is omitted
|
|
entirely, the list will be constructed from all accessible pki mounts.
|
|
|
|
This returns a list of issuing certificates, and whether they are a match.
|
|
By default, the type of match required is whether the PARENT has the
|
|
expected subject, key_id, and could have (directly) signed this issuer.
|
|
The match criteria can be updated by changed the corresponding flag.
|
|
|
|
` + c.Flags().Help()
|
|
return strings.TrimSpace(helpText)
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) Flags() *FlagSets {
|
|
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
|
f := set.NewFlagSet("Command Options")
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "subject_match",
|
|
Target: &c.flagSubjectMatch,
|
|
Default: true,
|
|
EnvVar: "",
|
|
Usage: `Whether the subject name of the potential parent cert matches the issuer name of the child cert.`,
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "key_id_match",
|
|
Target: &c.flagKeyIdMatch,
|
|
Default: true,
|
|
EnvVar: "",
|
|
Usage: `Whether the subject key id (SKID) of the potential parent cert matches the authority key id (AKID) of the child cert.`,
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "path_match",
|
|
Target: &c.flagPathMatch,
|
|
Default: false,
|
|
EnvVar: "",
|
|
Usage: `Whether the potential parent appears in the certificate chain field (ca_chain) of the issued cert.`,
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "direct_sign",
|
|
Target: &c.flagSignatureMatch,
|
|
Default: true,
|
|
EnvVar: "",
|
|
Usage: `Whether the key of the potential parent directly signed this issued certificate.`,
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "indirect_sign",
|
|
Target: &c.flagIndirectSignMatch,
|
|
Default: true,
|
|
EnvVar: "",
|
|
Usage: `Whether trusting the parent certificate is sufficient to trust the child certificate.`,
|
|
})
|
|
|
|
f.BoolVar(&BoolVar{
|
|
Name: "use_names",
|
|
Target: &c.flagUseNames,
|
|
Default: false,
|
|
EnvVar: "",
|
|
Usage: `Whether the list of issuers returned is referred to by name (when it exists) rather than by uuid.`,
|
|
})
|
|
|
|
return set
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) Run(args []string) int {
|
|
f := c.Flags()
|
|
if err := f.Parse(args); err != nil {
|
|
c.UI.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
args = f.Args()
|
|
|
|
if len(args) < 1 {
|
|
c.UI.Error("Not enough arguments (expected potential parent, got nothing)")
|
|
return 1
|
|
} else if len(args) > 2 {
|
|
for _, arg := range args {
|
|
if strings.HasPrefix(arg, "-") {
|
|
c.UI.Warn(fmt.Sprintf("Options (%v) must be specified before positional arguments (%v)", arg, args[0]))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
client, err := c.Client()
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Failed to obtain client: %s", err))
|
|
return 1
|
|
}
|
|
|
|
issuer := sanitizePath(args[0])
|
|
var issued []string
|
|
if len(args) > 1 {
|
|
for _, arg := range args[1:] {
|
|
cleanPath := sanitizePath(arg)
|
|
// Arg Might be a Fully Qualified Path
|
|
if strings.Contains(cleanPath, "/issuer/") ||
|
|
strings.Contains(cleanPath, "/certs/") ||
|
|
strings.Contains(cleanPath, "/revoked/") {
|
|
issued = append(issued, cleanPath)
|
|
} else { // Or Arg Might be a Mount
|
|
mountCaList, err := c.getIssuerListFromMount(client, arg)
|
|
if err != nil {
|
|
c.UI.Error(err.Error())
|
|
return 1
|
|
}
|
|
issued = append(issued, mountCaList...)
|
|
}
|
|
}
|
|
} else {
|
|
mountListRaw, err := client.Logical().Read("/sys/mounts/")
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Failed to Read List of Mounts With Potential Issuers: %v", err))
|
|
return 1
|
|
}
|
|
for path, rawValueMap := range mountListRaw.Data {
|
|
valueMap := rawValueMap.(map[string]interface{})
|
|
if valueMap["type"].(string) == "pki" {
|
|
mountCaList, err := c.getIssuerListFromMount(client, sanitizePath(path))
|
|
if err != nil {
|
|
c.UI.Error(err.Error())
|
|
return 1
|
|
}
|
|
issued = append(issued, mountCaList...)
|
|
}
|
|
}
|
|
}
|
|
|
|
childrenMatches := make(map[string]bool)
|
|
|
|
constraintMap := map[string]bool{
|
|
// This comparison isn't strictly correct, despite a standard ordering these are sets
|
|
"subject_match": c.flagSubjectMatch,
|
|
"path_match": c.flagPathMatch,
|
|
"trust_match": c.flagIndirectSignMatch,
|
|
"key_id_match": c.flagKeyIdMatch,
|
|
"signature_match": c.flagSignatureMatch,
|
|
}
|
|
|
|
issuerResp, err := readIssuer(client, issuer)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Failed to read parent issuer on path %s: %s", issuer, err.Error()))
|
|
return 1
|
|
}
|
|
|
|
for _, child := range issued {
|
|
path := sanitizePath(child)
|
|
if path != "" {
|
|
verifyResults, err := verifySignBetween(client, issuerResp, path)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Failed to run verification on path %v: %v", path, err))
|
|
return 1
|
|
}
|
|
childrenMatches[path] = checkIfResultsMatchFilters(verifyResults, constraintMap)
|
|
}
|
|
}
|
|
|
|
err = c.outputResults(childrenMatches)
|
|
if err != nil {
|
|
c.UI.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) getIssuerListFromMount(client *api.Client, mountString string) ([]string, error) {
|
|
var issuerList []string
|
|
issuerListEndpoint := sanitizePath(mountString) + "/issuers"
|
|
rawIssuersResp, err := client.Logical().List(issuerListEndpoint)
|
|
if err != nil {
|
|
return issuerList, fmt.Errorf("failed to read list of issuers within mount %v: %v", mountString, err)
|
|
}
|
|
if rawIssuersResp == nil { // No Issuers (Empty Mount)
|
|
return issuerList, nil
|
|
}
|
|
issuersMap := rawIssuersResp.Data["keys"]
|
|
certList := issuersMap.([]interface{})
|
|
for _, certId := range certList {
|
|
identifier := certId.(string)
|
|
if c.flagUseNames {
|
|
issuerReadResp, err := client.Logical().Read(sanitizePath(mountString) + "/issuer/" + identifier)
|
|
if err != nil {
|
|
c.UI.Warn(fmt.Sprintf("Unable to Fetch Issuer to Recover Name at: %v", sanitizePath(mountString)+"/issuer/"+identifier))
|
|
}
|
|
if issuerReadResp != nil {
|
|
issuerName := issuerReadResp.Data["issuer_name"].(string)
|
|
if issuerName != "" {
|
|
identifier = issuerName
|
|
}
|
|
}
|
|
}
|
|
issuerList = append(issuerList, sanitizePath(mountString)+"/issuer/"+identifier)
|
|
}
|
|
return issuerList, nil
|
|
}
|
|
|
|
func checkIfResultsMatchFilters(verifyResults, constraintMap map[string]bool) bool {
|
|
for key, required := range constraintMap {
|
|
if required && !verifyResults[key] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) outputResults(results map[string]bool) error {
|
|
switch Format(c.UI) {
|
|
case "", "table":
|
|
return c.outputResultsTable(results)
|
|
case "json":
|
|
return c.outputResultsJSON(results)
|
|
case "yaml":
|
|
return c.outputResultsYAML(results)
|
|
default:
|
|
return fmt.Errorf("unknown output format: %v", Format(c.UI))
|
|
}
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) outputResultsTable(results map[string]bool) error {
|
|
data := []string{"intermediate" + hopeDelim + "match?"}
|
|
for field, finding := range results {
|
|
row := field + hopeDelim + strconv.FormatBool(finding)
|
|
data = append(data, row)
|
|
}
|
|
c.UI.Output(tableOutput(data, &columnize.Config{
|
|
Delim: hopeDelim,
|
|
}))
|
|
c.UI.Output("\n")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) outputResultsJSON(results map[string]bool) error {
|
|
bytes, err := json.MarshalIndent(results, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.UI.Output(string(bytes))
|
|
return nil
|
|
}
|
|
|
|
func (c *PKIListIntermediateCommand) outputResultsYAML(results map[string]bool) error {
|
|
bytes, err := yaml.Marshal(results)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.UI.Output(string(bytes))
|
|
return nil
|
|
}
|