Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vault 11796 vault cli list intermediates #18463

Merged
merged 24 commits into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6ccd5b1
The verify-sign command in it's cleanest existing form.
kitography Dec 16, 2022
7c793c3
Working state
kitography Dec 16, 2022
4f83d60
Updates to proper verification syntax
kitography Dec 16, 2022
0733ea8
make fmt
kitography Dec 16, 2022
6e2c578
Git CI caught some stuff.
kitography Dec 16, 2022
456a2ec
Base functionality.
kitography Dec 19, 2022
7c1fc93
make fmt; changelog
kitography Dec 19, 2022
dab9f23
What I thought empty issuers response fix would be.
kitography Jan 25, 2023
c258cf0
Some tests
kitography Jan 25, 2023
77d1351
PR-review updates.
kitography Jan 25, 2023
500483b
Merge branch 'main' into VAULT-11795-vault-cli-verify-s-ign
kitography Jan 25, 2023
be41954
make fmt.
kitography Jan 25, 2023
86e4ea6
Merge branch 'VAULT-11795-vault-cli-verify-s-ign' into VAULT-11796-va…
kitography Jan 25, 2023
9f4f9cf
Fix null response data for listing empty issuers causing a crash.
kitography Jan 25, 2023
20d96a4
Update command/pki_list_children_command.go
kitography Jan 26, 2023
87e9dab
Add test for pki_list_children.
kitography Jan 26, 2023
4613848
Fix tests.
kitography Jan 26, 2023
77f79a5
Update descriptions for correctness based on PR reviews.
kitography Jan 26, 2023
42ba023
make fmt.
kitography Jan 26, 2023
f9ac195
Updates based on PR feedback.
kitography Jan 26, 2023
90eb1bc
Allow multiple arguements (space separated)
kitography Jan 27, 2023
97a068d
Typo-fix from PR review.
kitography Jan 27, 2023
5028ef1
sanitizePath once.
kitography Jan 27, 2023
da1e381
Merge branch 'main' into VAULT-11796-vault-cli-list-intermediates
kitography Jan 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/18437.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
client/pki: Add a new command verify-sign which checks the relationship between two certificates.
```
3 changes: 3 additions & 0 deletions changelog/18463.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
cli/pki: Add List-Children functionality to pki client.
kitography marked this conversation as resolved.
Show resolved Hide resolved
```
10 changes: 10 additions & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,16 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
BaseCommand: getBaseCommand(),
}, nil
},
"pki list-intermediates": func() (cli.Command, error) {
return &PKIListChildrenCommand{
BaseCommand: getBaseCommand(),
}, nil
},
"pki verify-sign": func() (cli.Command, error) {
return &PKIVerifySignCommand{
BaseCommand: getBaseCommand(),
}, nil
},
"plugin": func() (cli.Command, error) {
return &PluginCommand{
BaseCommand: getBaseCommand(),
Expand Down
263 changes: 263 additions & 0 deletions command/pki_list_children_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package command

import (
"encoding/json"
"fmt"
"strconv"
"strings"

"github.com/ghodss/yaml"
"github.com/ryanuber/columnize"
)

type PKIListChildrenCommand struct {
*BaseCommand

flagConfig string
flagReturnIndicator string
flagDefaultDisabled bool
flagList bool

flagUseNames bool

flagSignatureMatch bool
flagIndirectSignMatch bool
flagKeyIdMatch bool
flagSubjectMatch bool
flagPathMatch bool
}

func (c *PKIListChildrenCommand) Synopsis() string {
return "Determine Which (of a List) of Certificates Were Issued by A Given Parent Certificate"
}

func (c *PKIListChildrenCommand) Help() string {
helpText := `
Usage: vault pki list-intermediates PARENT CHILD-LIST
PARENT is the certificate that might be the issuer that everything should be verified against.
CHILD-LIST is a comma-separated list of intermediates to be compared to the PARENT. It can be omitted, in which case a
kitography marked this conversation as resolved.
Show resolved Hide resolved
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. This can be updated by changed the corresponding flag.
`
return strings.TrimSpace(helpText)
}

func (c *PKIListChildrenCommand) 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 of the potential parent cert matches the issuing key id 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 of the issued cert`,
})

f.BoolVar(&BoolVar{
Name: "direct_sign",
Target: &c.flagSignatureMatch,
Default: true,
EnvVar: "",
Usage: `Whether the key of the potential parent 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 uuid`,
})

return set
}

func (c *PKIListChildrenCommand) 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 {
c.UI.Error(fmt.Sprintf("Too many arguments (expected only potential issuer and issued, got %d arguments)", len(args)))
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
}
}
return 1
}

client, err := c.Client()
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to obtain client: %s", err))
return 1
}

issuer := sanitizePath(args[0])
issued := ""
if len(args) > 1 {
issued = sanitizePath(args[1])
kitography marked this conversation as resolved.
Show resolved Hide resolved
} 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" {
issuerListEndpoint := sanitizePath(path) + "/issuers"
rawIssuersResp, err := client.Logical().List(issuerListEndpoint)
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to Read List of Issuers within Mount %v: %v", path, err))
return 1
}
if rawIssuersResp == nil {
continue // TODO: Empty Issuers Response this throws an error
kitography marked this conversation as resolved.
Show resolved Hide resolved
}
issuersMap := rawIssuersResp.Data["keys"]
certList := issuersMap.([]interface{})
for _, certId := range certList {
identifier := certId.(string)
if c.flagUseNames {
issuerReadResp, err := client.Logical().Read(sanitizePath(path) + "/issuer/" + identifier)
if err != nil {
c.UI.Warn(fmt.Sprintf("Unable to Fetch Issuer to Recover Name at: %v", sanitizePath(path)+"/issuer/"+identifier))
}
if issuerReadResp != nil {
issuerName := issuerReadResp.Data["issuer_name"].(string)
if issuerName != "" {
identifier = issuerName
}
}
}
if len(issued) == 0 {
issued = sanitizePath(path) + "/issuer/" + identifier
} else {
issued = issued + "," + sanitizePath(path) + "/issuer/" + identifier
}
}
}
}
}

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,
}

for _, child := range strings.Split(issued, ",") {
path := sanitizePath(child)
if path != "" {
err, verifyResults := verifySignBetween(client, issuer, 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)
}
}

c.outputResults(childrenMatches)

return 0
}

func checkIfResultsMatchFilters(verifyResults, constraintMap map[string]bool) bool {
for key, required := range constraintMap {
if required == true {
if verifyResults[key] == false {
return false
}
}
}
return true
}

func (c *PKIListChildrenCommand) 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 *PKIListChildrenCommand) 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 *PKIListChildrenCommand) 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 *PKIListChildrenCommand) outputResultsYAML(results map[string]bool) error {
bytes, err := yaml.Marshal(results)
if err != nil {
return err
}

c.UI.Output(string(bytes))
return nil
}
Loading