Vault 11798 vault cli issue intermediate (#18467)
* The verify-sign command in it's cleanest existing form. * Working state * Updates to proper verification syntax Co-authored-by: 'Alex Scheel' <alex.scheel@hashicorp.com> * make fmt * Git CI caught some stuff. * Base functionality. * make fmt; changelog * pki issue command. * Make fmt. Changelog. * Error Handling Is Almost A Tutorial * What I thought empty issuers response fix would be. * Some tests * PR-review updates. * make fmt. * 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. * make fmt. * Updates based on PR feedback. * Allow multiple arguements (space separated) * Remove bad merge-thing. * White-space hell fix change. * Tests, and return information for issue ca * Fix make fmt error introduced here: https://github.com/hashicorp/vault/pull/18876 * Update command/pki_issue_intermediate.go Puncutation. Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com> * Remove smart quotes for standard quotes. * More information as part of the help text. * Better help text. * Add missing "/" into error message. --------- Co-authored-by: 'Alex Scheel' <alex.scheel@hashicorp.com> Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
This commit is contained in:
parent
da325bef31
commit
5ece71109a
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
cli/pki: Add pki issue command, which creates a CSR, has a vault mount sign it, then reimports it.
|
||||||
|
```
|
|
@ -543,6 +543,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
"pki issue": func() (cli.Command, error) {
|
||||||
|
return &PKIIssueCACommand{
|
||||||
|
BaseCommand: getBaseCommand(),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
"pki list-intermediates": func() (cli.Command, error) {
|
"pki list-intermediates": func() (cli.Command, error) {
|
||||||
return &PKIListIntermediateCommand{
|
return &PKIListIntermediateCommand{
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
|
|
|
@ -0,0 +1,355 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
paths "path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/api"
|
||||||
|
"github.com/posener/complete"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PKIIssueCACommand struct {
|
||||||
|
*BaseCommand
|
||||||
|
|
||||||
|
flagConfig string
|
||||||
|
flagReturnIndicator string
|
||||||
|
flagDefaultDisabled bool
|
||||||
|
flagList bool
|
||||||
|
|
||||||
|
flagKeyStorageSource string
|
||||||
|
flagNewIssuerName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PKIIssueCACommand) Synopsis() string {
|
||||||
|
return "Given a Parent Certificate, and a List of Generation Parameters, Creates an Issue on a Specified Mount"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PKIIssueCACommand) Help() string {
|
||||||
|
helpText := `
|
||||||
|
Usage: vault pki issue PARENT CHILD_MOUNT options
|
||||||
|
|
||||||
|
PARENT is the fully qualified path of the Certificate Authority in vault which will issue the new intermediate certificate.
|
||||||
|
|
||||||
|
CHILD_MOUNT is the path of the mount in vault where the new issuer is saved.
|
||||||
|
|
||||||
|
options are the superset of the options passed to generate/intermediate and sign-intermediate commands. At least one option must be set.
|
||||||
|
|
||||||
|
This command creates a intermediate certificate authority certificate signed by the parent in the CHILD_MOUNT.
|
||||||
|
|
||||||
|
` + c.Flags().Help()
|
||||||
|
return strings.TrimSpace(helpText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PKIIssueCACommand) Flags() *FlagSets {
|
||||||
|
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
|
||||||
|
f := set.NewFlagSet("Command Options")
|
||||||
|
|
||||||
|
f.StringVar(&StringVar{
|
||||||
|
Name: "type",
|
||||||
|
Target: &c.flagKeyStorageSource,
|
||||||
|
Default: "internal",
|
||||||
|
EnvVar: "",
|
||||||
|
Usage: `Options are "existing" - to use an existing key inside vault, "internal" - to generate a new key inside vault, or "kms" - to link to an external key. Exported keys are not available through this API.`,
|
||||||
|
Completion: complete.PredictSet("internal", "existing", "kms"),
|
||||||
|
})
|
||||||
|
|
||||||
|
f.StringVar(&StringVar{
|
||||||
|
Name: "issuer_name",
|
||||||
|
Target: &c.flagNewIssuerName,
|
||||||
|
Default: "",
|
||||||
|
EnvVar: "",
|
||||||
|
Usage: `If present, the newly created issuer will be given this name.`,
|
||||||
|
})
|
||||||
|
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PKIIssueCACommand) Run(args []string) int {
|
||||||
|
// Parse Args
|
||||||
|
f := c.Flags()
|
||||||
|
if err := f.Parse(args); err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
args = f.Args()
|
||||||
|
|
||||||
|
if len(args) < 3 {
|
||||||
|
c.UI.Error("Not enough arguments expected parent issuer and child-mount location and some key_value argument")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
stdin := (io.Reader)(os.Stdin)
|
||||||
|
data, err := parseArgsData(stdin, args[2:])
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to parse K=V data: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check We Have a Client
|
||||||
|
client, err := c.Client()
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to obtain client: %v", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity Check the Parent Issuer
|
||||||
|
parentMountIssuer := sanitizePath(args[0]) // /pki/issuer/default
|
||||||
|
_, parentIssuerName := paths.Split(parentMountIssuer)
|
||||||
|
if !strings.Contains(parentMountIssuer, "/issuer/") {
|
||||||
|
c.UI.Error(fmt.Sprintf("Parent Issuer %v is Not a PKI Issuer Path of the format /mount/issuer/issuer-ref", parentMountIssuer))
|
||||||
|
}
|
||||||
|
_, err = client.Logical().Read(parentMountIssuer + "/json")
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Unable to access parent issuer %v: %v", parentMountIssuer, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set-up Failure State (Immediately Before First Write Call)
|
||||||
|
intermediateMount := sanitizePath(args[1])
|
||||||
|
failureState := inCaseOfFailure{
|
||||||
|
intermediateMount: intermediateMount,
|
||||||
|
parentMount: strings.Split(parentMountIssuer, "/issuer/")[0],
|
||||||
|
parentIssuer: parentMountIssuer,
|
||||||
|
newName: c.flagNewIssuerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Certificate Signing Request
|
||||||
|
csrResp, err := client.Logical().Write(intermediateMount+"/intermediate/generate/"+c.flagKeyStorageSource, data)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "no handler for route") { // Mount Given Does Not Exist
|
||||||
|
c.UI.Error(fmt.Sprintf("Given Intermediate Mount %v Does Not Exist: %v", intermediateMount, err))
|
||||||
|
} else if strings.Contains(err.Error(), "unsupported path") { // Expected if Not a PKI Mount
|
||||||
|
c.UI.Error(fmt.Sprintf("Given Intermeidate Mount %v Is Not a PKI Mount: %v", intermediateMount, err))
|
||||||
|
} else {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failled to Generate Intermediate CSR on %v: %v", intermediateMount, err))
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// Parse CSR Response, Also Verifies that this is a PKI Mount
|
||||||
|
// (eg. calling the above call on cubbyhole/ won't return an error response)
|
||||||
|
csrPemRaw, present := csrResp.Data["csr"]
|
||||||
|
if !present {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to Generate Intermediate CSR on %v, got response: %v", intermediateMount, csrResp))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
keyIdRaw, present := csrResp.Data["key_id"]
|
||||||
|
if !present && c.flagKeyStorageSource == "internal" {
|
||||||
|
c.UI.Error(fmt.Sprintf("Failed to Generate Key on %v, got response: %v", intermediateMount, csrResp))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that all Parses, then we've successfully generated a CSR! Save It (and the Key-ID)
|
||||||
|
failureState.csrGenerated = true
|
||||||
|
if c.flagKeyStorageSource == "internal" {
|
||||||
|
failureState.createdKeyId = keyIdRaw.(string)
|
||||||
|
}
|
||||||
|
csr := csrPemRaw.(string)
|
||||||
|
failureState.csr = csr
|
||||||
|
data["csr"] = csr
|
||||||
|
|
||||||
|
// Next, Sign the CSR
|
||||||
|
rootResp, err := client.Logical().Write(parentMountIssuer+"/sign-intermediate", data)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(failureState.generateFailureMessage())
|
||||||
|
c.UI.Error(fmt.Sprintf("Error Signing Intermiate On %v", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// Success! Save Our Progress (and Parse the Response)
|
||||||
|
failureState.csrSigned = true
|
||||||
|
serialNumber := rootResp.Data["serial_number"].(string)
|
||||||
|
failureState.certSerialNumber = serialNumber
|
||||||
|
|
||||||
|
caChain := rootResp.Data["ca_chain"].([]interface{})
|
||||||
|
caChainPemBundle := ""
|
||||||
|
for _, cert := range caChain {
|
||||||
|
caChainPemBundle += cert.(string) + "\n"
|
||||||
|
}
|
||||||
|
failureState.caChain = caChainPemBundle
|
||||||
|
|
||||||
|
// Next Import Certificate
|
||||||
|
certificate := rootResp.Data["certificate"].(string)
|
||||||
|
issuerId, err := importIssuerWithName(client, intermediateMount, certificate, c.flagNewIssuerName)
|
||||||
|
failureState.certIssuerId = issuerId
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "error naming issuer") {
|
||||||
|
failureState.certImported = true
|
||||||
|
c.UI.Error(failureState.generateFailureMessage())
|
||||||
|
c.UI.Error(fmt.Sprintf("Error Naming Newly Imported Issuer: %v", err))
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
c.UI.Error(failureState.generateFailureMessage())
|
||||||
|
c.UI.Error(fmt.Sprintf("Error Importing Into %v Newly Created Issuer %v: %v", intermediateMount, certificate, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
failureState.certImported = true
|
||||||
|
|
||||||
|
// Then Import Issuing Certificate
|
||||||
|
issuingCa := rootResp.Data["issuing_ca"].(string)
|
||||||
|
_, err = importIssuerWithName(client, intermediateMount, issuingCa, parentIssuerName)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "error naming issuer") {
|
||||||
|
c.UI.Warn(fmt.Sprintf("Unable to Set Name on Parent Cert from %v Imported Into %v with serial %v, err: %v", parentIssuerName, intermediateMount, serialNumber, err))
|
||||||
|
} else {
|
||||||
|
c.UI.Error(failureState.generateFailureMessage())
|
||||||
|
c.UI.Error(fmt.Sprintf("Error Importing Into %v Newly Created Issuer %v: %v", intermediateMount, certificate, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally Import CA_Chain (just in case there's more information)
|
||||||
|
if len(caChain) > 2 { // We've already imported parent cert and newly issued cert above
|
||||||
|
importData := map[string]interface{}{
|
||||||
|
"pem_bundle": caChainPemBundle,
|
||||||
|
}
|
||||||
|
_, err := client.Logical().Write(intermediateMount+"/issuers/import/cert", importData)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(failureState.generateFailureMessage())
|
||||||
|
c.UI.Error(fmt.Sprintf("Error Importing CaChain into %v: %v", intermediateMount, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
failureState.caChainImported = true
|
||||||
|
|
||||||
|
// Finally we read our newly issued certificate in order to tell our caller about it
|
||||||
|
c.readAndOutputNewCertificate(client, intermediateMount, issuerId)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PKIIssueCACommand) readAndOutputNewCertificate(client *api.Client, intermediateMount string, issuerId string) {
|
||||||
|
resp, err := client.Logical().Read(sanitizePath(intermediateMount + "/issuer/" + issuerId))
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error Reading Fully Imported Certificate from %v : %v",
|
||||||
|
intermediateMount+"/issuer/"+issuerId, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputSecret(c.UI, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importIssuerWithName(client *api.Client, mount string, bundle string, name string) (issuerUUID string, err error) {
|
||||||
|
importData := map[string]interface{}{
|
||||||
|
"pem_bundle": bundle,
|
||||||
|
}
|
||||||
|
writeResp, err := client.Logical().Write(mount+"/issuers/import/cert", importData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
mapping := writeResp.Data["mapping"].(map[string]interface{})
|
||||||
|
if len(mapping) > 1 {
|
||||||
|
return "", fmt.Errorf("multiple issuers returned, while expected one, got %v", writeResp)
|
||||||
|
}
|
||||||
|
for issuerId := range mapping {
|
||||||
|
issuerUUID = issuerId
|
||||||
|
}
|
||||||
|
if name != "" && name != "default" {
|
||||||
|
nameReq := map[string]interface{}{
|
||||||
|
"issuer_name": name,
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err = client.Logical().JSONMergePatch(ctx, mount+"/issuer/"+issuerUUID, nameReq)
|
||||||
|
if err != nil {
|
||||||
|
return issuerUUID, fmt.Errorf("error naming issuer %v to %v: %v", issuerUUID, name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issuerUUID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type inCaseOfFailure struct {
|
||||||
|
csrGenerated bool
|
||||||
|
csrSigned bool
|
||||||
|
certImported bool
|
||||||
|
certNamed bool
|
||||||
|
caChainImported bool
|
||||||
|
|
||||||
|
intermediateMount string
|
||||||
|
createdKeyId string
|
||||||
|
csr string
|
||||||
|
caChain string
|
||||||
|
parentMount string
|
||||||
|
parentIssuer string
|
||||||
|
certSerialNumber string
|
||||||
|
certIssuerId string
|
||||||
|
newName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state inCaseOfFailure) generateFailureMessage() string {
|
||||||
|
message := "A failure has occurred"
|
||||||
|
|
||||||
|
if state.csrGenerated {
|
||||||
|
message += fmt.Sprintf(" after \n a Certificate Signing Request was successfully generated on mount %v", state.intermediateMount)
|
||||||
|
}
|
||||||
|
if state.csrSigned {
|
||||||
|
message += fmt.Sprintf(" and after \n that Certificate Signing Request was successfully signed by mount %v", state.parentMount)
|
||||||
|
}
|
||||||
|
if state.certImported {
|
||||||
|
message += fmt.Sprintf(" and after \n the signed certificate was reimported into mount %v , with issuerID %v", state.intermediateMount, state.certIssuerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.csrGenerated {
|
||||||
|
message += "\n\nTO CONTINUE: \n" + state.toContinue()
|
||||||
|
}
|
||||||
|
if state.csrGenerated && !state.certImported {
|
||||||
|
message += "\n\nTO ABORT: \n" + state.toAbort()
|
||||||
|
}
|
||||||
|
|
||||||
|
message += "\n"
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state inCaseOfFailure) toContinue() string {
|
||||||
|
message := ""
|
||||||
|
if !state.csrSigned {
|
||||||
|
message += fmt.Sprintf("You can continue to work with this Certificate Signing Request CSR PEM, by saving"+
|
||||||
|
" it as `pki_int.csr`: %v \n Then call `vault write %v/sign-intermediate csr=@pki_int.csr ...` adding the "+
|
||||||
|
"same key-value arguements as to `pki issue` (except key_type and issuer_name) to generate the certificate "+
|
||||||
|
"and ca_chain", state.csr, state.parentIssuer)
|
||||||
|
}
|
||||||
|
if !state.certImported {
|
||||||
|
if state.caChain != "" {
|
||||||
|
message += fmt.Sprintf("The certificate chain, signed by %v, for this new certificate is: %v", state.parentIssuer, state.caChain)
|
||||||
|
}
|
||||||
|
message += fmt.Sprintf("You can continue to work with this Certificate (and chain) by saving it as "+
|
||||||
|
"chain.pem and importing it as `vault write %v/issuers/import/cert pem_bundle=@chain.pem`",
|
||||||
|
state.intermediateMount)
|
||||||
|
}
|
||||||
|
if !state.certNamed {
|
||||||
|
issuerId := state.certIssuerId
|
||||||
|
if issuerId == "" {
|
||||||
|
message += fmt.Sprintf("The issuer_id is returned as the key in a key_value map from importing the " +
|
||||||
|
"certificate chain.")
|
||||||
|
issuerId = "<issuer-uuid>"
|
||||||
|
}
|
||||||
|
message += fmt.Sprintf("You can name the newly imported issuer by calling `vault patch %v/issuer/%v "+
|
||||||
|
"issuer_name=%v`", state.intermediateMount, issuerId, state.newName)
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (state inCaseOfFailure) toAbort() string {
|
||||||
|
if !state.csrGenerated || (!state.csrSigned && state.createdKeyId == "") {
|
||||||
|
return "No state was created by running this command. Try rerunning this command after resolving the error."
|
||||||
|
}
|
||||||
|
message := ""
|
||||||
|
if state.csrGenerated && state.createdKeyId != "" {
|
||||||
|
message += fmt.Sprintf(" A key, with key ID %v was created on mount %v as part of this command."+
|
||||||
|
" If you do not with to use this key and corresponding CSR/cert, you can delete that information by calling"+
|
||||||
|
" `vault delete %v/key/%v`", state.createdKeyId, state.intermediateMount, state.intermediateMount, state.createdKeyId)
|
||||||
|
}
|
||||||
|
if state.csrSigned {
|
||||||
|
message += fmt.Sprintf("A certificate with serial number %v was signed by mount %v as part of this command."+
|
||||||
|
" If you do not want to use this certificate, consider revoking it by calling `vault write %v/revoke/%v`",
|
||||||
|
state.certSerialNumber, state.parentMount, state.parentMount, state.certSerialNumber)
|
||||||
|
}
|
||||||
|
//if state.certImported {
|
||||||
|
// message += fmt.Sprintf("An issuer with UUID %v was created on mount %v as part of this command. " +
|
||||||
|
// "If you do not wish to use this issuer, consider deleting it by calling `vault delete %v/issuer/%v`",
|
||||||
|
// state.certIssuerId, state.intermediateMount, state.intermediateMount, state.certIssuerId)
|
||||||
|
//}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPKIIssueIntermediate(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
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
createComplicatedIssuerSetUpWithIssueIntermediate(t, client)
|
||||||
|
|
||||||
|
runPkiVerifySignTests(t, client)
|
||||||
|
|
||||||
|
runPkiListIntermediateTests(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createComplicatedIssuerSetUpWithIssueIntermediate(t *testing.T, client *api.Client) {
|
||||||
|
// Relationship Map to Create
|
||||||
|
// pki-root | pki-newroot | pki-empty
|
||||||
|
// RootX1 RootX2 RootX4 RootX3
|
||||||
|
// | |
|
||||||
|
// ----------------------------------------------
|
||||||
|
// v v
|
||||||
|
// IntX1 IntX2 pki-int
|
||||||
|
// | |
|
||||||
|
// v v
|
||||||
|
// IntX3 (-----------------------) IntX3
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
if err := client.Sys().Mount("pki-root", &api.MountInput{
|
||||||
|
Type: "pki",
|
||||||
|
Config: api.MountConfigInput{
|
||||||
|
MaxLeaseTTL: "36500d",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("pki mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Sys().Mount("pki-newroot", &api.MountInput{
|
||||||
|
Type: "pki",
|
||||||
|
Config: api.MountConfigInput{
|
||||||
|
MaxLeaseTTL: "36500d",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("pki mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Sys().Mount("pki-int", &api.MountInput{
|
||||||
|
Type: "pki",
|
||||||
|
Config: api.MountConfigInput{
|
||||||
|
MaxLeaseTTL: "36500d",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
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{}{
|
||||||
|
"key_type": "ec",
|
||||||
|
"common_name": "Root X",
|
||||||
|
"ttl": "3650d",
|
||||||
|
"issuer_name": "rootX1",
|
||||||
|
"key_name": "rootX1",
|
||||||
|
})
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = client.Logical().Write("pki-root/root/generate/internal", map[string]interface{}{
|
||||||
|
"key_type": "ec",
|
||||||
|
"common_name": "Root X",
|
||||||
|
"ttl": "3650d",
|
||||||
|
"issuer_name": "rootX2",
|
||||||
|
})
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp, err := client.Logical().Write("pki-newroot/root/generate/internal", map[string]interface{}{
|
||||||
|
"key_type": "ec",
|
||||||
|
"common_name": "Root X",
|
||||||
|
"ttl": "3650d",
|
||||||
|
"issuer_name": "rootX3",
|
||||||
|
}); err != nil || resp == nil {
|
||||||
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp, err := client.Logical().Write("pki-root/root/generate/existing", map[string]interface{}{
|
||||||
|
"common_name": "Root X4",
|
||||||
|
"ttl": "3650d",
|
||||||
|
"issuer_name": "rootX4",
|
||||||
|
"key_ref": "rootX1",
|
||||||
|
}); err != nil || resp == nil {
|
||||||
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next we create the Intermediates Using the Issue Intermediate Command
|
||||||
|
stdout := bytes.NewBuffer(nil)
|
||||||
|
stderr := bytes.NewBuffer(nil)
|
||||||
|
runOpts := &RunOptions{
|
||||||
|
Stdout: stdout,
|
||||||
|
Stderr: stderr,
|
||||||
|
Client: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intermediate X1
|
||||||
|
intX1CallArgs := []string{
|
||||||
|
"pki", "issue", "-format=json", "-issuer_name=intX1",
|
||||||
|
"pki-root/issuer/rootX1",
|
||||||
|
"pki-int/",
|
||||||
|
"key_type=rsa",
|
||||||
|
"common_name=Int X1",
|
||||||
|
"ttl=3650d",
|
||||||
|
}
|
||||||
|
codeOut := RunCustom(intX1CallArgs, runOpts)
|
||||||
|
if codeOut != 0 {
|
||||||
|
t.Fatalf("error issuing intermediate X1, code: %d \n stdout: %v \n stderr: %v", codeOut, stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intermediate X2
|
||||||
|
intX2CallArgs := []string{
|
||||||
|
"pki", "issue", "-format=json", "-issuer_name=intX2",
|
||||||
|
"pki-newroot/issuer/rootX3",
|
||||||
|
"pki-int/",
|
||||||
|
"key_type=ed25519",
|
||||||
|
"common_name=Int X2",
|
||||||
|
"ttl=3650d",
|
||||||
|
}
|
||||||
|
codeOut = RunCustom(intX2CallArgs, runOpts)
|
||||||
|
if codeOut != 0 {
|
||||||
|
t.Fatalf("error issuing intermediate X2, code: %d \n stdout: %v \n stderr: %v", codeOut, stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intermediate X3
|
||||||
|
// Clear Buffers so that we can unmarshall json of just this call
|
||||||
|
stdout = bytes.NewBuffer(nil)
|
||||||
|
stderr = bytes.NewBuffer(nil)
|
||||||
|
runOpts = &RunOptions{
|
||||||
|
Stdout: stdout,
|
||||||
|
Stderr: stderr,
|
||||||
|
Client: client,
|
||||||
|
}
|
||||||
|
intX3OriginalCallArgs := []string{
|
||||||
|
"pki", "issue", "-format=json", "-issuer_name=intX3",
|
||||||
|
"pki-int/issuer/intX1",
|
||||||
|
"pki-int/",
|
||||||
|
"key_type=rsa",
|
||||||
|
"common_name=Int X3",
|
||||||
|
"ttl=3650d",
|
||||||
|
}
|
||||||
|
codeOut = RunCustom(intX3OriginalCallArgs, runOpts)
|
||||||
|
if codeOut != 0 {
|
||||||
|
t.Fatalf("error issuing intermediate X3, code: %d \n stdout: %v \n stderr: %v", codeOut, stdout, stderr)
|
||||||
|
}
|
||||||
|
var intX3Resp map[string]interface{}
|
||||||
|
json.Unmarshal(stdout.Bytes(), &intX3Resp)
|
||||||
|
intX3Data := intX3Resp["data"].(map[string]interface{})
|
||||||
|
keyId := intX3Data["key_id"].(string)
|
||||||
|
|
||||||
|
intX3AdaptedCallArgs := []string{
|
||||||
|
"pki", "issue", "-format=json", "-issuer_name=intX3also", "-type=existing",
|
||||||
|
"pki-int/issuer/intX2",
|
||||||
|
"pki-int/",
|
||||||
|
"key_ref=" + keyId,
|
||||||
|
"common_name=Int X3",
|
||||||
|
"ttl=3650d",
|
||||||
|
}
|
||||||
|
codeOut = RunCustom(intX3AdaptedCallArgs, runOpts)
|
||||||
|
if codeOut != 0 {
|
||||||
|
t.Fatalf("error issuing intermediate X3also, code: %d \n stdout: %v \n stderr: %v", codeOut, stdout, stderr)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ package command
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPKIListIntermediate(t *testing.T) {
|
func TestPKIListIntermediate(t *testing.T) {
|
||||||
|
@ -28,6 +30,10 @@ func TestPKIListIntermediate(t *testing.T) {
|
||||||
// RootX1 has issued IntX1; RootX3 has issued IntX2
|
// RootX1 has issued IntX1; RootX3 has issued IntX2
|
||||||
createComplicatedIssuerSetUp(t, client)
|
createComplicatedIssuerSetUp(t, client)
|
||||||
|
|
||||||
|
runPkiListIntermediateTests(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPkiListIntermediateTests(t *testing.T, client *api.Client) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestPKIVerifySign(t *testing.T) {
|
||||||
defer closer()
|
defer closer()
|
||||||
|
|
||||||
// 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
|
||||||
// | |
|
// | |
|
||||||
// ----------------------------------------------
|
// ----------------------------------------------
|
||||||
|
@ -34,6 +34,10 @@ func TestPKIVerifySign(t *testing.T) {
|
||||||
// RootX1 has issued IntX1; RootX3 has issued IntX2
|
// RootX1 has issued IntX1; RootX3 has issued IntX2
|
||||||
createComplicatedIssuerSetUp(t, client)
|
createComplicatedIssuerSetUp(t, client)
|
||||||
|
|
||||||
|
runPkiVerifySignTests(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPkiVerifySignTests(t *testing.T, client *api.Client) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
args []string
|
args []string
|
||||||
|
@ -217,7 +221,6 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
"issuer_name": "rootX1",
|
"issuer_name": "rootX1",
|
||||||
"key_name": "rootX1",
|
"key_name": "rootX1",
|
||||||
})
|
})
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -228,7 +231,6 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
"ttl": "3650d",
|
"ttl": "3650d",
|
||||||
"issuer_name": "rootX2",
|
"issuer_name": "rootX2",
|
||||||
})
|
})
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -414,6 +416,7 @@ func createComplicatedIssuerSetUp(t *testing.T, client *api.Client) {
|
||||||
break
|
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,
|
||||||
|
|
|
@ -122,14 +122,14 @@ func (l *InmemLayer) Dial(addr string, timeout time.Duration, tlsConfig *tls.Con
|
||||||
panic(fmt.Sprintf("%q attempted to dial itself", l.addr))
|
panic(fmt.Sprintf("%q attempted to dial itself", l.addr))
|
||||||
}
|
}
|
||||||
|
|
||||||
// This simulates an i/o timeout by sleeping for 20 seconds and returning
|
// This simulates an i/o timeout by sleeping for 20 seconds and returning
|
||||||
// an error when the forceTimeout name is the same as the host we are
|
// an error when the forceTimeout name is the same as the host we are
|
||||||
// currently connecting to. Useful for checking how gRPC connections react
|
// currently connecting to. Useful for checking how gRPC connections react
|
||||||
// with timeouts.
|
// with timeouts.
|
||||||
if l.forceTimeout == addr {
|
if l.forceTimeout == addr {
|
||||||
l.logger.Debug("forcing timeout", "addr", addr, "me", l.addr)
|
l.logger.Debug("forcing timeout", "addr", addr, "me", l.addr)
|
||||||
|
|
||||||
// gRPC sets a deadline of 20 seconds on the dail attempt, so
|
// gRPC sets a deadline of 20 seconds on the dail attempt, so
|
||||||
// matching that here.
|
// matching that here.
|
||||||
time.Sleep(time.Second * 20)
|
time.Sleep(time.Second * 20)
|
||||||
return nil, deadlineError("i/o timeout")
|
return nil, deadlineError("i/o timeout")
|
||||||
|
|
Loading…
Reference in New Issue