Skip to content

Commit

Permalink
Make PKI root generation idempotent-ish and add delete endpoint.
Browse files Browse the repository at this point in the history
Fixes #3086
  • Loading branch information
jefferai committed Aug 14, 2017
1 parent ff0bbbe commit d493905
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 11 deletions.
3 changes: 2 additions & 1 deletion builtin/logical/pki/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ func Backend() *backend {
pathListRoles(&b),
pathRoles(&b),
pathGenerateRoot(&b),
pathSignIntermediate(&b),
pathDeleteRoot(&b),
pathGenerateIntermediate(&b),
pathSetSignedIntermediate(&b),
pathSignIntermediate(&b),
pathConfigCA(&b),
pathConfigCRL(&b),
pathConfigURLs(&b),
Expand Down
78 changes: 78 additions & 0 deletions builtin/logical/pki/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ import (
"time"

"github.com/fatih/structs"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/helper/strutil"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/logical"
logicaltest "github.com/hashicorp/vault/logical/testing"
"github.com/hashicorp/vault/vault"
"github.com/mitchellh/mapstructure"
)

Expand Down Expand Up @@ -2156,6 +2159,81 @@ func TestBackend_SignVerbatim(t *testing.T) {
}
}

func TestBackend_Root_Idempotentcy(t *testing.T) {
coreConfig := &vault.CoreConfig{
LogicalBackends: map[string]logical.Factory{
"pki": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()

client := cluster.Cores[0].Client
var err error
err = client.Sys().Mount("pki", &api.MountInput{
Type: "pki",
Config: api.MountConfigInput{
DefaultLeaseTTL: "16h",
MaxLeaseTTL: "32h",
},
})
if err != nil {
t.Fatal(err)
}

resp, err := client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"common_name": "myvault.com",
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("expected ca info")
}
resp, err = client.Logical().Read("pki/cert/ca_chain")
r1Data := resp.Data

// Try again, make sure it's a 204 and same CA
resp, err = client.Logical().Write("pki/root/generate/internal", map[string]interface{}{
"common_name": "myvault.com",
})
if err != nil {
t.Fatal(err)
}
if resp != nil {
t.Fatal("expected no ca info")
}
resp, err = client.Logical().Read("pki/cert/ca_chain")
r2Data := resp.Data
if !reflect.DeepEqual(r1Data, r2Data) {
t.Fatal("got different ca certs")
}

resp, err = client.Logical().Delete("pki/root")
if err != nil {
t.Fatal(err)
}
if resp != nil {
t.Fatal("expected nil response")
}
// Make sure it behaves the same
resp, err = client.Logical().Delete("pki/root")
if err != nil {
t.Fatal(err)
}
if resp != nil {
t.Fatal("expected nil response")
}

_, err = client.Logical().Read("pki/cert/ca_chain")
if err == nil {
t.Fatal("expected error")
}
}

const (
rsaCAKey string = `-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAmPQlK7xD5p+E8iLQ8XlVmll5uU2NKMxKY3UF5tbh+0vkc+Fy
Expand Down
4 changes: 1 addition & 3 deletions builtin/logical/pki/path_config_ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ func pathConfigCA(b *backend) *framework.Path {
"pem_bundle": &framework.FieldSchema{
Type: framework.TypeString,
Description: `PEM-format, concatenated unencrypted
secret key and certificate, or, if a
CSR was generated with the "generate"
endpoint, just the signed certificate.`,
secret key and certificate.`,
},
},

Expand Down
4 changes: 3 additions & 1 deletion builtin/logical/pki/path_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (b *backend) pathFetchRead(req *logical.Request, data *framework.FieldData)
caInfo, err := fetchCAInfo(req)
switch err.(type) {
case errutil.UserError:
response = logical.ErrorResponse(funcErr.Error())
response = logical.ErrorResponse(err.Error())
goto reply
case errutil.InternalError:
retErr = err
Expand Down Expand Up @@ -244,6 +244,8 @@ reply:
}
case retErr != nil:
response = nil
case response.IsError():
return response, nil
default:
response.Data["certificate"] = string(certificate)
response.Data["revocation_time"] = revocationTime
Expand Down
38 changes: 37 additions & 1 deletion builtin/logical/pki/path_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ func pathGenerateRoot(b *backend) *framework.Path {
return ret
}

func pathDeleteRoot(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "root",

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.DeleteOperation: b.pathCADeleteRoot,
},

HelpSynopsis: pathDeleteRootHelpSyn,
HelpDescription: pathDeleteRootHelpDesc,
}

return ret
}

func pathSignIntermediate(b *backend) *framework.Path {
ret := &framework.Path{
Pattern: "root/sign-intermediate",
Expand Down Expand Up @@ -66,10 +81,23 @@ the non-repudiation flag.`,
return ret
}

func (b *backend) pathCADeleteRoot(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
return nil, req.Storage.Delete("config/ca_bundle")
}

func (b *backend) pathCAGenerateRoot(
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
var err error

entry, err := req.Storage.Get("config/ca_bundle")
if err != nil {
return nil, err
}
if entry != nil {
return nil, nil
}

exported, format, role, errorResp := b.getGenerationParams(data)
if errorResp != nil {
return errorResp, nil
Expand Down Expand Up @@ -133,7 +161,7 @@ func (b *backend) pathCAGenerateRoot(
}

// Store it as the CA bundle
entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
entry, err = logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -299,6 +327,14 @@ const pathGenerateRootHelpDesc = `
See the API documentation for more information.
`

const pathDeleteRootHelpSyn = `
Deletes the root CA key to allow a new one to be generated.
`

const pathDeleteRootHelpDesc = `
See the API documentation for more information.
`

const pathSignIntermediateHelpSyn = `
Issue an intermediate CA certificate based on the provided CSR.
`
Expand Down
28 changes: 23 additions & 5 deletions website/source/api/secret/pki/index.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ update your API calls accordingly.
## Read CA Certificate

This endpoint retrieves the CA certificate *in raw DER-encoded form*. This is a
bare endpoint that does not return a standard Vault data structure. If `/pem` is
added to the endpoint, the CA certificate is returned in PEM format.
bare endpoint that does not return a standard Vault data structure and cannot
be read by the Vault CLI. If `/pem` is added to the endpoint, the CA
certificate is returned in PEM format.

This is an unauthenticated endpoint.

Expand All @@ -73,7 +74,7 @@ $ curl \

This endpoint retrieves the CA certificate chain, including the CA _in PEM
format_. This is a bare endpoint that does not return a standard Vault data
structure.
structure and cannot be read by the Vault CLI.

This is an unauthenticated endpoint.

Expand Down Expand Up @@ -460,8 +461,6 @@ $ curl \
https://vault.rocks/v1/pki/intermediate/generate/internal
```

### Sample Response

```json
{
"lease_id": "",
Expand Down Expand Up @@ -974,6 +973,25 @@ $ curl \
}
```

## Delete Root

This endpoint deletes the current CA key (the old CA certificate will still be
accessible for reading until a new certificate/key are generated or uploaded).

| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
| `DELETE` | `/pki/root` | `204 (empty body)` |


### Sample Request

```
$ curl \
--header "X-Vault-Token: ..." \
--request DELETE \
https://vault.rocks/v1/pki/root
```

## Sign Intermediate

This endpoint uses the configured CA certificate to issue a certificate with
Expand Down

0 comments on commit d493905

Please sign in to comment.