Vault 11796 vault cli list intermediates (#18463)
* Base functionality. * make fmt; changelog * What I thought empty issuers response fix would be. * Fix null response data for listing empty issuers causing a crash. * Update command/pki_list_children_command.go Fix double specifier Co-authored-by: Steven Clark <steven.clark@hashicorp.com> * Add test for pki_list_children. * Fix tests. * Update descriptions for correctness based on PR reviews. Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
This commit is contained in:
parent
4a9610f382
commit
1cef81f025
3
changelog/18463.txt
Normal file
3
changelog/18463.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
cli/pki: Add List-Intermediates functionality to pki client.
|
||||||
|
```
|
|
@ -543,6 +543,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
"pki list-intermediates": func() (cli.Command, error) {
|
||||||
|
return &PKIListChildrenCommand{
|
||||||
|
BaseCommand: getBaseCommand(),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
"pki verify-sign": func() (cli.Command, error) {
|
"pki verify-sign": func() (cli.Command, error) {
|
||||||
return &PKIVerifySignCommand{
|
return &PKIVerifySignCommand{
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
|
|
288
command/pki_list_children_command.go
Normal file
288
command/pki_list_children_command.go
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/api"
|
||||||
|
|
||||||
|
"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] [CHILD] [CHILD] ...
|
||||||
|
PARENT is the certificate that might be the issuer that everything should be verified against.
|
||||||
|
CHILD is a 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.
|
||||||
|
`
|
||||||
|
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 {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.outputResults(childrenMatches)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PKIListChildrenCommand) 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 == 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
|
||||||
|
}
|
237
command/pki_list_children_test.go
Normal file
237
command/pki_list_children_test.go
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPKIListChildren(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client, closer := testVaultServer(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
// Relationship Map to Create
|
||||||
|
// pki-root | pki-newroot | pki-empty
|
||||||
|
// RootX1 RootX2 RootX4 RootX3
|
||||||
|
// | |
|
||||||
|
// ----------------------------------------------
|
||||||
|
// v v
|
||||||
|
// IntX1 IntX2 pki-int
|
||||||
|
// | |
|
||||||
|
// v v
|
||||||
|
// IntX3 (-----------------------) IntX3(also)
|
||||||
|
//
|
||||||
|
// Here X1,X2 have the same name (same mount)
|
||||||
|
// RootX4 uses the same key as RootX1 (but a different common_name/subject)
|
||||||
|
// RootX3 has the same name, and is on a different mount
|
||||||
|
// RootX1 has issued IntX1; RootX3 has issued IntX2
|
||||||
|
createComplicatedIssuerSetUp(t, client)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
expectedMatches map[string]bool
|
||||||
|
jsonOut bool
|
||||||
|
shouldError bool
|
||||||
|
expectErrorCont string
|
||||||
|
expectErrorNotCont string
|
||||||
|
nonJsonOutputCont string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"rootX1-match-everything-no-constraints",
|
||||||
|
[]string{
|
||||||
|
"pki", "list-intermediates", "-format=json", "-use_names=true",
|
||||||
|
"-subject_match=false", "-key_id_match=false", "-direct_sign=false", "-indirect_sign=false", "-path_match=false",
|
||||||
|
"pki-root/issuer/rootX1",
|
||||||
|
},
|
||||||
|
map[string]bool{
|
||||||
|
"pki-root/issuer/rootX1": true,
|
||||||
|
"pki-root/issuer/rootX2": true,
|
||||||
|
"pki-newroot/issuer/rootX3": true,
|
||||||
|
"pki-root/issuer/rootX4": true,
|
||||||
|
"pki-int/issuer/intX1": true,
|
||||||
|
"pki-int/issuer/intX2": true,
|
||||||
|
"pki-int/issuer/intX3": true,
|
||||||
|
"pki-int/issuer/intX3also": true,
|
||||||
|
"pki-int/issuer/rootX1": true,
|
||||||
|
"pki-int/issuer/rootX3": true,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rootX1-default-children",
|
||||||
|
[]string{"pki", "list-intermediates", "-format=json", "-use_names=true", "pki-root/issuer/rootX1"},
|
||||||
|
map[string]bool{
|
||||||
|
"pki-root/issuer/rootX1": true,
|
||||||
|
"pki-root/issuer/rootX2": false,
|
||||||
|
"pki-newroot/issuer/rootX3": false,
|
||||||
|
"pki-root/issuer/rootX4": false,
|
||||||
|
"pki-int/issuer/intX1": true,
|
||||||
|
"pki-int/issuer/intX2": false,
|
||||||
|
"pki-int/issuer/intX3": false,
|
||||||
|
"pki-int/issuer/intX3also": false,
|
||||||
|
"pki-int/issuer/rootX1": true,
|
||||||
|
"pki-int/issuer/rootX3": false,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rootX1-subject-match-only",
|
||||||
|
[]string{
|
||||||
|
"pki", "list-intermediates", "-format=json", "-use_names=true",
|
||||||
|
"-key_id_match=false", "-direct_sign=false", "-indirect_sign=false",
|
||||||
|
"pki-root/issuer/rootX1",
|
||||||
|
},
|
||||||
|
map[string]bool{
|
||||||
|
"pki-root/issuer/rootX1": true,
|
||||||
|
"pki-root/issuer/rootX2": true,
|
||||||
|
"pki-newroot/issuer/rootX3": true,
|
||||||
|
"pki-root/issuer/rootX4": false,
|
||||||
|
"pki-int/issuer/intX1": true,
|
||||||
|
"pki-int/issuer/intX2": true,
|
||||||
|
"pki-int/issuer/intX3": false,
|
||||||
|
"pki-int/issuer/intX3also": false,
|
||||||
|
"pki-int/issuer/rootX1": true,
|
||||||
|
"pki-int/issuer/rootX3": true,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rootX1-in-path",
|
||||||
|
[]string{
|
||||||
|
"pki", "list-intermediates", "-format=json", "-use_names=true",
|
||||||
|
"-subject_match=false", "-key_id_match=false", "-direct_sign=false", "-indirect_sign=false", "-path_match=true",
|
||||||
|
"pki-root/issuer/rootX1",
|
||||||
|
},
|
||||||
|
map[string]bool{
|
||||||
|
"pki-root/issuer/rootX1": true,
|
||||||
|
"pki-root/issuer/rootX2": false,
|
||||||
|
"pki-newroot/issuer/rootX3": false,
|
||||||
|
"pki-root/issuer/rootX4": false,
|
||||||
|
"pki-int/issuer/intX1": true,
|
||||||
|
"pki-int/issuer/intX2": false,
|
||||||
|
"pki-int/issuer/intX3": true,
|
||||||
|
"pki-int/issuer/intX3also": false,
|
||||||
|
"pki-int/issuer/rootX1": true,
|
||||||
|
"pki-int/issuer/rootX3": false,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rootX1-only-int-mount",
|
||||||
|
[]string{
|
||||||
|
"pki", "list-intermediates", "-format=json", "-use_names=true",
|
||||||
|
"-subject_match=false", "-key_id_match=false", "-direct_sign=false", "-indirect_sign=false", "-path_match=true",
|
||||||
|
"pki-root/issuer/rootX1", "pki-int/",
|
||||||
|
},
|
||||||
|
map[string]bool{
|
||||||
|
"pki-int/issuer/intX1": true,
|
||||||
|
"pki-int/issuer/intX2": false,
|
||||||
|
"pki-int/issuer/intX3": true,
|
||||||
|
"pki-int/issuer/intX3also": false,
|
||||||
|
"pki-int/issuer/rootX1": true,
|
||||||
|
"pki-int/issuer/rootX3": false,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rootX1-subject-match-root-mounts-only",
|
||||||
|
[]string{
|
||||||
|
"pki", "list-intermediates", "-format=json", "-use_names=true",
|
||||||
|
"-key_id_match=false", "-direct_sign=false", "-indirect_sign=false",
|
||||||
|
"pki-root/issuer/rootX1", "pki-root/", "pki-newroot", "pki-empty",
|
||||||
|
},
|
||||||
|
map[string]bool{
|
||||||
|
"pki-root/issuer/rootX1": true,
|
||||||
|
"pki-root/issuer/rootX2": true,
|
||||||
|
"pki-newroot/issuer/rootX3": true,
|
||||||
|
"pki-root/issuer/rootX4": false,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rootX1-subject-match-these-certs-only",
|
||||||
|
[]string{
|
||||||
|
"pki", "list-intermediates", "-format=json", "-use_names=true",
|
||||||
|
"-key_id_match=false", "-direct_sign=false", "-indirect_sign=false",
|
||||||
|
"pki-root/issuer/rootX1", "pki-root/issuer/rootX2", "pki-newroot/issuer/rootX3", "pki-root/issuer/rootX4",
|
||||||
|
},
|
||||||
|
map[string]bool{
|
||||||
|
"pki-root/issuer/rootX2": true,
|
||||||
|
"pki-newroot/issuer/rootX3": true,
|
||||||
|
"pki-root/issuer/rootX4": false,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range cases {
|
||||||
|
var errString string
|
||||||
|
var results map[string]interface{}
|
||||||
|
var stdOut string
|
||||||
|
|
||||||
|
if testCase.jsonOut {
|
||||||
|
results, errString = execPKIVerifyJson(t, client, false, testCase.shouldError, testCase.args)
|
||||||
|
} else {
|
||||||
|
stdOut, errString = execPKIVerifyNonJson(t, client, testCase.shouldError, testCase.args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Error Behavior
|
||||||
|
if testCase.shouldError {
|
||||||
|
if errString == "" {
|
||||||
|
t.Fatalf("Expected error in Testcase %s : no error produced, got results %s", testCase.name, results)
|
||||||
|
}
|
||||||
|
if testCase.expectErrorCont != "" && !strings.Contains(errString, testCase.expectErrorCont) {
|
||||||
|
t.Fatalf("Expected error in Testcase %s to contain %s, but got error %s", testCase.name, testCase.expectErrorCont, errString)
|
||||||
|
}
|
||||||
|
if testCase.expectErrorNotCont != "" && strings.Contains(errString, testCase.expectErrorNotCont) {
|
||||||
|
t.Fatalf("Expected error in Testcase %s to not contain %s, but got error %s", testCase.name, testCase.expectErrorNotCont, errString)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if errString != "" {
|
||||||
|
t.Fatalf("Error in Testcase %s : no error expected, but got error: %s", testCase.name, errString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Output
|
||||||
|
if testCase.jsonOut {
|
||||||
|
isMatch, errString := verifyExpectedJson(testCase.expectedMatches, results)
|
||||||
|
if !isMatch {
|
||||||
|
t.Fatalf("Expected Results for Testcase %s, do not match returned results %s", testCase.name, errString)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !strings.Contains(stdOut, testCase.nonJsonOutputCont) {
|
||||||
|
t.Fatalf("Expected standard output for Testcase %s to contain %s, but got %s", testCase.name, testCase.nonJsonOutputCont, stdOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -115,7 +115,7 @@ func verifySignBetween(client *api.Client, issuerPath string, issuedPath string)
|
||||||
return fmt.Errorf("error: unable to fetch issuer %v: %w", issuerPath, err), nil
|
return fmt.Errorf("error: unable to fetch issuer %v: %w", issuerPath, err), nil
|
||||||
}
|
}
|
||||||
if len(issuedPath) <= 2 {
|
if len(issuedPath) <= 2 {
|
||||||
return fmt.Errorf(fmt.Sprintf("%v", issuedPath)), nil
|
return fmt.Errorf("%v", issuedPath), nil
|
||||||
}
|
}
|
||||||
caChainRaw := issuedCertResp.Data["ca_chain"]
|
caChainRaw := issuedCertResp.Data["ca_chain"]
|
||||||
if caChainRaw == nil {
|
if caChainRaw == nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -159,7 +160,7 @@ func convertListOfInterfaceToString(list []interface{}, sep string) string {
|
||||||
|
|
||||||
func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
// Relationship Map to Create
|
// Relationship Map to Create
|
||||||
// pki-root | pki-newroot
|
// pki-root | pki-newroot | pki-empty
|
||||||
// RootX1 RootX2 RootX4 RootX3
|
// RootX1 RootX2 RootX4 RootX3
|
||||||
// | |
|
// | |
|
||||||
// ----------------------------------------------
|
// ----------------------------------------------
|
||||||
|
@ -201,6 +202,14 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
t.Fatalf("pki mount error: %#v", err)
|
t.Fatalf("pki mount error: %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to check handling empty list responses: Not Used for Any Issuers / Certificates
|
||||||
|
if err := client.Sys().Mount("pki-empty", &api.MountInput{
|
||||||
|
Type: "pki",
|
||||||
|
Config: api.MountConfigInput{},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("pki mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := client.Logical().Write("pki-root/root/generate/internal", map[string]interface{}{
|
resp, err := client.Logical().Write("pki-root/root/generate/internal", map[string]interface{}{
|
||||||
"key_type": "ec",
|
"key_type": "ec",
|
||||||
"common_name": "Root X",
|
"common_name": "Root X",
|
||||||
|
@ -208,7 +217,7 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
"issuer_name": "rootX1",
|
"issuer_name": "rootX1",
|
||||||
"key_name": "rootX1",
|
"key_name": "rootX1",
|
||||||
})
|
})
|
||||||
t.Logf("%s", resp.Data)
|
|
||||||
if err != nil || resp == nil {
|
if err != nil || resp == nil {
|
||||||
t.Fatalf("failed to prime CA: %v", err)
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -219,7 +228,7 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
"ttl": "3650d",
|
"ttl": "3650d",
|
||||||
"issuer_name": "rootX2",
|
"issuer_name": "rootX2",
|
||||||
})
|
})
|
||||||
t.Logf("%s", resp.Data)
|
|
||||||
if err != nil || resp == nil {
|
if err != nil || resp == nil {
|
||||||
t.Fatalf("failed to prime CA: %v", err)
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -251,6 +260,10 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
if err != nil || int1CsrResp == nil {
|
if err != nil || int1CsrResp == nil {
|
||||||
t.Fatalf("failed to generate CSR: %v", err)
|
t.Fatalf("failed to generate CSR: %v", err)
|
||||||
}
|
}
|
||||||
|
int1KeyId, ok := int1CsrResp.Data["key_id"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("no key_id produced when generating csr, response %v", int1CsrResp.Data)
|
||||||
|
}
|
||||||
int1CsrRaw, ok := int1CsrResp.Data["csr"]
|
int1CsrRaw, ok := int1CsrResp.Data["csr"]
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("no csr produced when generating intermediate, resp: %v", int1CsrResp)
|
t.Fatalf("no csr produced when generating intermediate, resp: %v", int1CsrResp)
|
||||||
|
@ -277,18 +290,24 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("no mapping data returned on issuer import: %v", importInt1Resp)
|
t.Fatalf("no mapping data returned on issuer import: %v", importInt1Resp)
|
||||||
}
|
}
|
||||||
importIssuerId := ""
|
|
||||||
for key, value := range importIssuerIdMap.(map[string]interface{}) {
|
for key, value := range importIssuerIdMap.(map[string]interface{}) {
|
||||||
if value != nil && len(value.(string)) > 0 {
|
if value != nil && len(value.(string)) > 0 {
|
||||||
importIssuerId = key
|
if value != int1KeyId {
|
||||||
break
|
t.Fatalf("Expected exactly one key_match to %v, got multiple: %v", int1KeyId, importIssuerIdMap)
|
||||||
}
|
}
|
||||||
}
|
if resp, err := client.Logical().JSONMergePatch(context.Background(), "pki-int/issuer/"+key, map[string]interface{}{
|
||||||
if resp, err := client.Logical().Write("pki-int/issuer/"+importIssuerId, map[string]interface{}{
|
|
||||||
"issuer_name": "intX1",
|
"issuer_name": "intX1",
|
||||||
}); err != nil || resp == nil {
|
}); err != nil || resp == nil {
|
||||||
t.Fatalf("error naming issuer %v", err)
|
t.Fatalf("error naming issuer %v", err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if resp, err := client.Logical().JSONMergePatch(context.Background(), "pki-int/issuer/"+key, map[string]interface{}{
|
||||||
|
"issuer_name": "rootX1",
|
||||||
|
}); err != nil || resp == nil {
|
||||||
|
t.Fatalf("error naming issuer parent %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Intermediate X2
|
// Intermediate X2
|
||||||
int2CsrResp, err := client.Logical().Write("pki-int/intermediate/generate/internal", map[string]interface{}{
|
int2CsrResp, err := client.Logical().Write("pki-int/intermediate/generate/internal", map[string]interface{}{
|
||||||
|
@ -299,6 +318,10 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
if err != nil || int2CsrResp == nil {
|
if err != nil || int2CsrResp == nil {
|
||||||
t.Fatalf("failed to generate CSR: %v", err)
|
t.Fatalf("failed to generate CSR: %v", err)
|
||||||
}
|
}
|
||||||
|
int2KeyId, ok := int2CsrResp.Data["key_id"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("no key material returned from producing csr, resp: %v", int2CsrResp)
|
||||||
|
}
|
||||||
int2CsrRaw, ok := int2CsrResp.Data["csr"]
|
int2CsrRaw, ok := int2CsrResp.Data["csr"]
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("no csr produced when generating intermediate, resp: %v", int2CsrResp)
|
t.Fatalf("no csr produced when generating intermediate, resp: %v", int2CsrResp)
|
||||||
|
@ -317,7 +340,6 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
int2CertChain := convertListOfInterfaceToString(int2CertChainRaw.([]interface{}), "\n")
|
int2CertChain := convertListOfInterfaceToString(int2CertChainRaw.([]interface{}), "\n")
|
||||||
importInt2Resp, err := client.Logical().Write("pki-int/issuers/import/cert", map[string]interface{}{
|
importInt2Resp, err := client.Logical().Write("pki-int/issuers/import/cert", map[string]interface{}{
|
||||||
"pem_bundle": int2CertChain,
|
"pem_bundle": int2CertChain,
|
||||||
"issuer_name": "intX2",
|
|
||||||
})
|
})
|
||||||
if err != nil || importInt2Resp == nil {
|
if err != nil || importInt2Resp == nil {
|
||||||
t.Fatalf("failed to import certificate: %v", err)
|
t.Fatalf("failed to import certificate: %v", err)
|
||||||
|
@ -326,18 +348,24 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("no mapping data returned on issuer import: %v", importInt2Resp)
|
t.Fatalf("no mapping data returned on issuer import: %v", importInt2Resp)
|
||||||
}
|
}
|
||||||
importIssuer2Id := ""
|
|
||||||
for key, value := range importIssuer2IdMap.(map[string]interface{}) {
|
for key, value := range importIssuer2IdMap.(map[string]interface{}) {
|
||||||
if value != nil && len(value.(string)) > 0 {
|
if value != nil && len(value.(string)) > 0 {
|
||||||
importIssuer2Id = key
|
if value != int2KeyId {
|
||||||
break
|
t.Fatalf("unexpected key_match with ca_chain, expected only %v, got %v", int2KeyId, importIssuer2IdMap)
|
||||||
}
|
}
|
||||||
}
|
if resp, err := client.Logical().JSONMergePatch(context.Background(), "pki-int/issuer/"+key, map[string]interface{}{
|
||||||
if resp, err := client.Logical().Write("pki-int/issuer/"+importIssuer2Id, map[string]interface{}{
|
|
||||||
"issuer_name": "intX2",
|
"issuer_name": "intX2",
|
||||||
}); err != nil || resp == nil {
|
}); err != nil || resp == nil {
|
||||||
t.Fatalf("error naming issuer %v", err)
|
t.Fatalf("error naming issuer %v", err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if resp, err := client.Logical().Write("pki-int/issuer/"+key, map[string]interface{}{
|
||||||
|
"issuer_name": "rootX3",
|
||||||
|
}); err != nil || resp == nil {
|
||||||
|
t.Fatalf("error naming parent issuer %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Intermediate X3
|
// Intermediate X3
|
||||||
int3CsrResp, err := client.Logical().Write("pki-int/intermediate/generate/internal", map[string]interface{}{
|
int3CsrResp, err := client.Logical().Write("pki-int/intermediate/generate/internal", map[string]interface{}{
|
||||||
|
@ -348,6 +376,7 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
if err != nil || int3CsrResp == nil {
|
if err != nil || int3CsrResp == nil {
|
||||||
t.Fatalf("failed to generate CSR: %v", err)
|
t.Fatalf("failed to generate CSR: %v", err)
|
||||||
}
|
}
|
||||||
|
int3KeyId, ok := int3CsrResp.Data["key_id"]
|
||||||
int3CsrRaw, ok := int3CsrResp.Data["csr"]
|
int3CsrRaw, ok := int3CsrResp.Data["csr"]
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("no csr produced when generating intermediate, resp: %v", int3CsrResp)
|
t.Fatalf("no csr produced when generating intermediate, resp: %v", int3CsrResp)
|
||||||
|
@ -375,18 +404,16 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("no mapping data returned on issuer import: %v", importInt2Resp)
|
t.Fatalf("no mapping data returned on issuer import: %v", importInt2Resp)
|
||||||
}
|
}
|
||||||
importIssuer3Id1 := ""
|
|
||||||
for key, value := range importIssuer3IdMap1.(map[string]interface{}) {
|
for key, value := range importIssuer3IdMap1.(map[string]interface{}) {
|
||||||
if value != nil && len(value.(string)) > 0 {
|
if value != nil && len(value.(string)) > 0 && value == int3KeyId {
|
||||||
importIssuer3Id1 = key
|
if resp, err := client.Logical().JSONMergePatch(context.Background(), "pki-int/issuer/"+key, map[string]interface{}{
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if resp, err := client.Logical().Write("pki-int/issuer/"+importIssuer3Id1, map[string]interface{}{
|
|
||||||
"issuer_name": "intX3",
|
"issuer_name": "intX3",
|
||||||
}); err != nil || resp == nil {
|
}); err != nil || resp == nil {
|
||||||
t.Fatalf("error naming issuer %v", err)
|
t.Fatalf("error naming issuer %v", err)
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
// sign by intX2 and import
|
// sign by intX2 and import
|
||||||
int3CertResp2, err := client.Logical().Write("pki-int/issuer/intX2/sign-intermediate", map[string]interface{}{
|
int3CertResp2, err := client.Logical().Write("pki-int/issuer/intX2/sign-intermediate", map[string]interface{}{
|
||||||
"csr": int3Csr,
|
"csr": int3Csr,
|
||||||
|
@ -409,18 +436,16 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("no mapping data returned on issuer import: %v", importInt2Resp)
|
t.Fatalf("no mapping data returned on issuer import: %v", importInt2Resp)
|
||||||
}
|
}
|
||||||
importIssuer3Id2 := ""
|
|
||||||
for key, value := range importIssuer3IdMap2.(map[string]interface{}) {
|
for key, value := range importIssuer3IdMap2.(map[string]interface{}) {
|
||||||
if value != nil && len(value.(string)) > 0 {
|
if value != nil && len(value.(string)) > 0 && value == int3KeyId {
|
||||||
importIssuer3Id2 = key
|
if resp, err := client.Logical().JSONMergePatch(context.Background(), "pki-int/issuer/"+key, map[string]interface{}{
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if resp, err := client.Logical().Write("pki-int/issuer/"+importIssuer3Id2, map[string]interface{}{
|
|
||||||
"issuer_name": "intX3also",
|
"issuer_name": "intX3also",
|
||||||
}); err != nil || resp == nil {
|
}); err != nil || resp == nil {
|
||||||
t.Fatalf("error naming issuer %v", err)
|
t.Fatalf("error naming issuer %v", err)
|
||||||
}
|
}
|
||||||
|
break // Parent Certs Already Named
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyExpectedJson(expectedResults map[string]bool, results map[string]interface{}) (isMatch bool, error string) {
|
func verifyExpectedJson(expectedResults map[string]bool, results map[string]interface{}) (isMatch bool, error string) {
|
||||||
|
|
Loading…
Reference in a new issue