From 1794387355a3c51c7799c105fd23416611b78fc5 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Fri, 14 Apr 2017 09:48:14 -0400 Subject: [PATCH 1/2] Update sign-verbatim to correctly set generate_lease, as well as (if using a role) the role's ttl, max_ttl, generate_lease, and no_store. Fixes #2592 --- builtin/logical/pki/backend_test.go | 166 ++++++++++++++++++++ builtin/logical/pki/cert_util.go | 5 +- builtin/logical/pki/path_issue_sign.go | 34 +++- website/source/api/secret/pki/index.html.md | 10 +- 4 files changed, 208 insertions(+), 7 deletions(-) diff --git a/builtin/logical/pki/backend_test.go b/builtin/logical/pki/backend_test.go index 7418fb68ebc6..d9ec0b1e2484 100644 --- a/builtin/logical/pki/backend_test.go +++ b/builtin/logical/pki/backend_test.go @@ -1984,6 +1984,172 @@ func TestBackend_PathFetchCertList(t *testing.T) { } } +func TestBackend_SignVerbatim(t *testing.T) { + // create the backend + config := logical.TestBackendConfig() + storage := &logical.InmemStorage{} + config.StorageView = storage + + b := Backend() + _, err := b.Setup(config) + if err != nil { + t.Fatal(err) + } + + // generate root + rootData := map[string]interface{}{ + "common_name": "test.com", + "ttl": "172800", + } + + resp, err := b.HandleRequest(&logical.Request{ + Operation: logical.UpdateOperation, + Path: "root/generate/internal", + Storage: storage, + Data: rootData, + }) + if resp != nil && resp.IsError() { + t.Fatalf("failed to generate root, %#v", *resp) + } + if err != nil { + t.Fatal(err) + } + + // create a CSR and key + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + csrReq := &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "foo.bar.com", + }, + } + csr, err := x509.CreateCertificateRequest(rand.Reader, csrReq, key) + if err != nil { + t.Fatal(err) + } + if len(csr) == 0 { + t.Fatal("generated csr is empty") + } + pemCSR := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: csr, + }) + if len(pemCSR) == 0 { + t.Fatal("pem csr is empty") + } + + resp, err = b.HandleRequest(&logical.Request{ + Operation: logical.UpdateOperation, + Path: "sign-verbatim", + Storage: storage, + Data: map[string]interface{}{ + "csr": string(pemCSR), + }, + }) + if resp != nil && resp.IsError() { + t.Fatalf("failed to sign-verbatim basic CSR: %#v", *resp) + } + if err != nil { + t.Fatal(err) + } + if resp.Secret != nil { + t.Fatal("secret is not nil") + } + + // create a role entry; we use this to check that sign-verbatim when used with a role is still honoring TTLs + roleData := map[string]interface{}{ + "ttl": "4h", + "max_ttl": "8h", + } + resp, err = b.HandleRequest(&logical.Request{ + Operation: logical.UpdateOperation, + Path: "roles/test", + Storage: storage, + Data: roleData, + }) + if resp != nil && resp.IsError() { + t.Fatalf("failed to create a role, %#v", *resp) + } + if err != nil { + t.Fatal(err) + } + resp, err = b.HandleRequest(&logical.Request{ + Operation: logical.UpdateOperation, + Path: "sign-verbatim/test", + Storage: storage, + Data: map[string]interface{}{ + "csr": string(pemCSR), + "ttl": "5h", + }, + }) + if resp != nil && resp.IsError() { + t.Fatalf("failed to sign-verbatim ttl'd CSR: %#v", *resp) + } + if err != nil { + t.Fatal(err) + } + if resp.Secret != nil { + t.Fatal("got a lease when we should not have") + } + resp, err = b.HandleRequest(&logical.Request{ + Operation: logical.UpdateOperation, + Path: "sign-verbatim/test", + Storage: storage, + Data: map[string]interface{}{ + "csr": string(pemCSR), + "ttl": "12h", + }, + }) + if resp != nil && !resp.IsError() { + t.Fatalf("sign-verbatim signed too-large-ttl'd CSR: %#v", *resp) + } + if err != nil { + t.Fatal(err) + } + + // now check that if we set generate-lease it takes it from the role and the TTLs match + roleData = map[string]interface{}{ + "ttl": "4h", + "max_ttl": "8h", + "generate_lease": true, + } + resp, err = b.HandleRequest(&logical.Request{ + Operation: logical.UpdateOperation, + Path: "roles/test", + Storage: storage, + Data: roleData, + }) + if resp != nil && resp.IsError() { + t.Fatalf("failed to create a role, %#v", *resp) + } + if err != nil { + t.Fatal(err) + } + resp, err = b.HandleRequest(&logical.Request{ + Operation: logical.UpdateOperation, + Path: "sign-verbatim/test", + Storage: storage, + Data: map[string]interface{}{ + "csr": string(pemCSR), + "ttl": "5h", + }, + }) + if resp != nil && resp.IsError() { + t.Fatalf("failed to sign-verbatim role-leased CSR: %#v", *resp) + } + if err != nil { + t.Fatal(err) + } + if resp.Secret == nil { + t.Fatalf("secret is nil, response is %#v", *resp) + } + if math.Abs(float64(resp.Secret.TTL-(5*time.Hour))) > float64(5*time.Hour) { + t.Fatalf("ttl not default; wanted %v, got %v", b.System().DefaultLeaseTTL(), resp.Secret.TTL) + } +} + const ( rsaCAKey string = `-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAmPQlK7xD5p+E8iLQ8XlVmll5uU2NKMxKY3UF5tbh+0vkc+Fy diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index d9425fc5a0aa..07ecfb24a070 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/vault/helper/certutil" "github.com/hashicorp/vault/helper/errutil" + "github.com/hashicorp/vault/helper/parseutil" "github.com/hashicorp/vault/helper/strutil" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" @@ -695,7 +696,7 @@ func generateCreationBundle(b *backend, if len(ttlField) == 0 { ttl = b.System().DefaultLeaseTTL() } else { - ttl, err = time.ParseDuration(ttlField) + ttl, err = parseutil.ParseDurationSecond(ttlField) if err != nil { return nil, errutil.UserError{Err: fmt.Sprintf( "invalid requested ttl: %s", err)} @@ -705,7 +706,7 @@ func generateCreationBundle(b *backend, if len(role.MaxTTL) == 0 { maxTTL = b.System().MaxLeaseTTL() } else { - maxTTL, err = time.ParseDuration(role.MaxTTL) + maxTTL, err = parseutil.ParseDurationSecond(role.MaxTTL) if err != nil { return nil, errutil.UserError{Err: fmt.Sprintf( "invalid ttl: %s", err)} diff --git a/builtin/logical/pki/path_issue_sign.go b/builtin/logical/pki/path_issue_sign.go index 429d96315b7b..6905e898ae8a 100644 --- a/builtin/logical/pki/path_issue_sign.go +++ b/builtin/logical/pki/path_issue_sign.go @@ -116,9 +116,20 @@ func (b *backend) pathSign( func (b *backend) pathSignVerbatim( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + roleName := data.Get("role").(string) + + // Get the role if one was specified + role, err := b.getRole(req.Storage, roleName) + if err != nil { + return nil, err + } + ttl := b.System().DefaultLeaseTTL() - role := &roleEntry{ + maxTTL := b.System().MaxLeaseTTL() + + entry := &roleEntry{ TTL: ttl.String(), + MaxTTL: maxTTL.String(), AllowLocalhost: true, AllowAnyName: true, AllowIPSANs: true, @@ -126,9 +137,28 @@ func (b *backend) pathSignVerbatim( KeyType: "any", UseCSRCommonName: true, UseCSRSANs: true, + GenerateLease: new(bool), + } + + if role != nil { + if role.TTL != "" { + entry.TTL = role.TTL + } + if role.MaxTTL != "" { + entry.MaxTTL = role.MaxTTL + } + } + + *entry.GenerateLease = false + if role != nil && role.GenerateLease != nil { + *entry.GenerateLease = *role.GenerateLease + } + + if role != nil { + entry.NoStore = role.NoStore } - return b.pathIssueSignCert(req, data, role, true, true) + return b.pathIssueSignCert(req, data, entry, true, true) } func (b *backend) pathIssueSignCert( diff --git a/website/source/api/secret/pki/index.html.md b/website/source/api/secret/pki/index.html.md index ecfb73dfdbd6..4d36ca1a7a01 100644 --- a/website/source/api/secret/pki/index.html.md +++ b/website/source/api/secret/pki/index.html.md @@ -1116,12 +1116,16 @@ refuse to issue an intermediate CA certificate (see the **This is a potentially dangerous endpoint and only highly trusted users should have access.** -| Method | Path | Produces | -| :------- | :--------------------------- | :--------------------- | -| `POST` | `/pki/sign-verbatim` | `200 application/json` | +| Method | Path | Produces | +| :------- | :----------------------------------- | :--------------------- | +| `POST` | `/pki/sign-verbatim(/:name)` | `200 application/json` | ### Parameters +- `name` `(string: "")` - Specifies a role. If set, the following parameters + from the role will have effect: `ttl`, `max_ttl`, `generate_lease`, and + `no_store`. + - `csr` `(string: )` – Specifies the PEM-encoded CSR. - `ttl` `(string: "")` – Specifies the requested Time To Live. Cannot be greater From c34bff2ec6c01457b88b97c92599d88931b79a4b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 18 Apr 2017 12:23:36 -0400 Subject: [PATCH 2/2] Address feedback --- builtin/logical/pki/path_issue_sign.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/builtin/logical/pki/path_issue_sign.go b/builtin/logical/pki/path_issue_sign.go index 6905e898ae8a..f615f98d1dab 100644 --- a/builtin/logical/pki/path_issue_sign.go +++ b/builtin/logical/pki/path_issue_sign.go @@ -147,6 +147,7 @@ func (b *backend) pathSignVerbatim( if role.MaxTTL != "" { entry.MaxTTL = role.MaxTTL } + entry.NoStore = role.NoStore } *entry.GenerateLease = false @@ -154,10 +155,6 @@ func (b *backend) pathSignVerbatim( *entry.GenerateLease = *role.GenerateLease } - if role != nil { - entry.NoStore = role.NoStore - } - return b.pathIssueSignCert(req, data, entry, true, true) }