Skip to content

Vault issuer support#292

Merged
jetstack-bot merged 3 commits intocert-manager:masterfrom
vdesjardins:master
May 2, 2018
Merged

Vault issuer support#292
jetstack-bot merged 3 commits intocert-manager:masterfrom
vdesjardins:master

Conversation

@vdesjardins
Copy link
Copy Markdown
Contributor

@vdesjardins vdesjardins commented Feb 5, 2018

What this PR does / why we need it:
Initial Vault support.

Which issue this PR fixes
fixes #17

Special notes for your reviewer:
Where I work we need support for Vault so I took a shot at it. Please tell me if you guys are interested in what I did. If yes I can write some documentation and rework the code if need be.

Thanks

Add experimental support for Hashicorp Vault issuers

@jetstack-bot jetstack-bot added do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Feb 5, 2018
Copy link
Copy Markdown
Member

@munnerz munnerz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thanks very much for this contribution. It looks like a great start at a Vault issuer, and is definitely something we'll want to merge provided we can resolve some of the finer details.

I've added a few comments, and I'm keen to run e2e tests on this. There's one particular comment (left above) that is blocking end-to-end testing this (a change to the test fixtures, I guess that you made so you can test locally).

It also looks like you've used a different version of dep to update dependencies. I hope our cert-manager-quick-verify job will pick that up 😅 . In the meantime I'll add a update-deps.sh script that ensures versions are correct.

If you've got any questions (either about the review, or more generally), feel free to reach out either here or on the Kubernetes slack (my username is @munnerz).

// Server is the vault connection address
Server string `json:"server"`
// Vault URL path to the certificate role
Path string `json:"path"`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies a 1:1 binding between Issuer and a role within a PKI backend. I think this makes sense to be honest, and I like keeping it simple here, but wanted to highlight it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps before 1.0 we could add some sort of 'role binding' support. This would probably be a breaking change... we'll see what users want.


const (
// certificateDuration of 90 days
certificateDuration = time.Hour * 24 * 90
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be configurable - perhaps as a field on the issuer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Initially, I was hoping for a shorter certificate duration but I hit the default renewBefore in pkg/controller/certificates/sync.go:

const renewBefore = time.Hour * 24 * 30

Maybe renewBefore and certificateDuration should be configurable? For Vault and CA issuers It could make sense I think. For ACME it is fixed to 90 days so It could be enforced at 30 days in that specific issuer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ACME it is fixed to 90 days so It could be enforced at 30 days in that specific issuer.

Careful, that's not true for ACME in general and might be over-fitting Let's Encrypt. The protocol allows flexibility in certificate lifetime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ok sorry about that. Thanks for pointing it out!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to be sorry :-)

token, err := vaultTokenRef(v.secretsLister, v.issuerResourcesNamespace, tokenRef.Name, "token")
if err != nil {
return nil, fmt.Errorf("error reading Vault token from secret %s/%s: %s", v.issuerResourcesNamespace, tokenRef.Name, err.Error())
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably check here to see why it failed (via the k8s.io/apimachinery/pkg/api/errors package).

This isn't something that's handled well in the CA or ACME implementation currently, and I think would take some fairly significant refactoring to add retrospectively (as the top level sync function needs a way to return nil despite an error occurring).

When initially writing cert-manager, I created a TransientError type that could be used to indicate a sync should be retried. This would save all the refactoring, at the expense of needing to check for transient errors at various points (and/or not 'masking' errors with fmt.Errorf)

"exclude_cn_from_sans": "true",
}

url := fmt.Sprintf("/v1%s", v.issuer.GetSpec().Vault.Path)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think v.issuer.GetSpec().Vault.Path should not be required to start with a /, as vault itself doesn't use this afaik.

If it does start with a /, we could also automatically strip it, thus allowing both. But this fmt.Sprintf should be using /v1/%s imo.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change it for a path.Join so it will work in both cases.


defer resp.Body.Close()

vaultResult := VaultResult{}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the vault package doesn't provide any pre-made structs for us to use?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some digging there is in fact some structs that we could use. Even some helper methods too.

for _, v := range vaultResult.Data.Chain {
certBytes.WriteString(v)
certBytes.WriteString("\n")
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is okay I think, but it's worth noting right now we don't expose any way to obtain the CA that signed a Certificate via cert-manager (this is something that would be great to change, but I've not been able to find a way to get a CA certificate from an ACME server (cc @cpu)).

iirc, the certificate chain here doesn't actually include that CA certificate anyway so this should be okay to include here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not been able to find a way to get a CA certificate from an ACME server (cc @cpu)).

@munnerz For the V1 API the new-cert HTTP response includes a Link header with an up relation that points to where you can find the issuer certificate for the issued leaf certificate.

For the V2 API things are much easier. After finalizing an order the URL of the certificate in the order object returns the full PEM encoded certificate chain. When you GET the cert URL you'll get both the leaf and the intermediate(s).

Hope that helps!

)

const (
successVaultVerified = "VaultVerified"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for you to fix as part of this PR: we should probably move these Reason messages into a generic package

glog.Info(messageVaultVerified)
v.recorder.Event(v.issuer, v1.EventTypeNormal, successVaultVerified, messageVaultVerified)
v.issuer.UpdateStatusCondition(v1alpha1.IssuerConditionReady, v1alpha1.ConditionTrue, successVaultVerified, messageVaultVerified)
return nil
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should perform additional checks here if possible - i.e. ensure we can connect to Vault and ensure the path is valid for issuing certs.

}
})

})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 ❤️ thanks for adding this

tag: build
pullPolicy: Never
repository: us.gcr.io/projet-labo-1/cert-manager-ingress-shim
pullPolicy: Always
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs switching back - if you do this first, I can /test all this 😄 (can't verify what is in that image, so I'm reluctant to test this on our infrastructure!)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, this file slipped in the commit.

@vdesjardins
Copy link
Copy Markdown
Contributor Author

Thanks for the review! I'm going to start working on your requested improvements in the coming days.

@munnerz
Copy link
Copy Markdown
Member

munnerz commented Feb 6, 2018

/ok-to-test

@jetstack-bot jetstack-bot removed the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Feb 6, 2018
@vdesjardins vdesjardins force-pushed the master branch 2 times, most recently from 4a11dce to b4c65b6 Compare February 7, 2018 19:05
@vdesjardins vdesjardins force-pushed the master branch 2 times, most recently from c0a4ae8 to a72fdbd Compare February 16, 2018 03:33
@munnerz
Copy link
Copy Markdown
Member

munnerz commented Feb 21, 2018

/retest

@vdesjardins vdesjardins force-pushed the master branch 3 times, most recently from eada339 to 82a305c Compare February 21, 2018 12:52
@munnerz
Copy link
Copy Markdown
Member

munnerz commented Feb 21, 2018

@vdesjardins would you mind splitting this into an "Update vendor" PR and "Vault issuer support" to make it easier to review?

& if it isn't too much of a pain, keeping your changes split into multiple commits until we're ready to merge would be great 😄 (obviously I'm not asking you to retroactively do this!)

@jetstack-ci-bot jetstack-ci-bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 21, 2018
@jetstack-ci-bot
Copy link
Copy Markdown
Contributor

@vdesjardins PR needs rebase

@vdesjardins
Copy link
Copy Markdown
Contributor Author

vdesjardins commented Feb 21, 2018 via email

@munnerz munnerz mentioned this pull request Feb 21, 2018
@jetstack-ci-bot jetstack-ci-bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 6, 2018
@vdesjardins vdesjardins force-pushed the master branch 4 times, most recently from ace1e76 to 48a3bcc Compare March 17, 2018 19:58
@jetstack-bot jetstack-bot added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Apr 15, 2018
@jetstack-bot jetstack-bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 16, 2018
@mwthink
Copy link
Copy Markdown

mwthink commented Apr 16, 2018

Is there anyone actively working on the pending changes? This feature is the only reason I'm not using certmanager at the moment and would be glad to help out if it'll get it out the gate faster.

@vdesjardins
Copy link
Copy Markdown
Contributor Author

@mwthink yes I'm working on it! I need to finish writing the documentation.

@munnerz
Copy link
Copy Markdown
Member

munnerz commented Apr 25, 2018

It looks like this PR may touch more dependencies than required... this commit adds nearly 2M lines of code, and removes 1M: a48908c

I'd expect a large addition, but not such a large reduction in LoC.

Could you take another look? If you drop that commit out of your tree (with something like git rebase), and then run dep ensure -v you should hopefully be able to clean up a lot of those changes 😃

@vdesjardins vdesjardins force-pushed the master branch 3 times, most recently from f094e55 to 2b91fe2 Compare April 26, 2018 17:43
@vdesjardins
Copy link
Copy Markdown
Contributor Author

I fixed the dependencies. Look much better now 😀

I did some commit cleanup too.

Copy link
Copy Markdown
Member

@munnerz munnerz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing too major now, thanks very much for squashing - far easier to review!

Looking forward to getting this merged in 😃

// Certificate default Duration
Duration time.Duration `json:"duration,omitempty"`
// Certificate renew before expiration duration
RenewBefore time.Duration `json:"renewBefore,omitempty"`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 this looks good to me.

We may in future want to consider switching this to some form of 'CertificatePolicy' resource (ie certificate-class), which can be associated with a set of certificates somehow. This would help admins provide cert-specific default config easily.

For now though, this seems acceptable. We can always retain this in future as the issuer default (where a CertificatePolicy would override the issuers defaults)

const (
errorIssueError = "IssueError"

// default certificateDuration of zero, we let ACME decide
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This const doesn't seem to be used in issue.go, and only in acme.go. Can you move it into acme.go?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also rename to defaultCertificateDuration


if err := issuer.ValidateDuration(issuerObj); err != nil {
return nil, fmt.Errorf("CA %s", err.Error())
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking ValidateDuration in the New* functions, we should perform it in Setup() and if they are invalid, mark the Issuer NotReady. This will cause Certificate requests to fail as well as a side effect, and providers a cleaner error reporting interface (we can add a status condition in future to explain why it failed, e.g. a ConfigValid status condition)

// WaitForCertificateEvent waits for an event on the named Certificate to contain
// an event reason matches the supplied one.
func WaitForCertificateEvent(client kubernetes.Interface, cert *v1alpha1.Certificate, reason string, timeout time.Duration) error {
return wait.PollImmediate(500*time.Millisecond, timeout,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduce this to check every 1s or 2s? Not sure what sort of values are used for timeout here.

return err
}

err = i.Setup(ctx)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the previously mentioned change to validate config in Setup(), we can revert the changes here and in issuers/sync.go

}

glog.Info(messageVaultVerified)
v.recorder.Event(v.issuer, v1.EventTypeNormal, successVaultVerified, messageVaultVerified)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't fire an event here either


if err := issuer.ValidateDuration(issuerObj); err != nil {
return nil, updateEventAndCondition(fmt.Errorf(messageVaultDurationInvalid, err.Error()), errorVaultDurationInvalid, issuerObj, recorder)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All validation of the Issuer resource should happen in Setup() - other fields (eg resourceNamespace) can be validated here, but not the issuer itself.

recorder.Event(issuerObj, v1.EventTypeWarning, event, err.Error())
issuerObj.UpdateStatusCondition(v1alpha1.IssuerConditionReady, v1alpha1.ConditionFalse, event, err.Error())
return err
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably remove this function after changes above

}, nil
}

func (v *VaultInitializer) Setup() error {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks stressful to maintain 😅

It may be best to move all the vault specific test utils into their own package so you can add an OWNERS file in there to be pinged for code reviews in future.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests for our tests anyone? 😁

This can be deferred for now though.

the Vault server base URL. The secret created previously is referenced in the issuer
with its *name* and *key* corresponding to the name of the Kubernetes secret and the
property name containing the token value respectively.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small addition to explain that these Issuers can be referenced like any other and/or an example of a certificate now referencing the issuer we just created would be great.

I'm envisioning the tutorials section to be quite hand-holdy and explain how to get from point A to point B (i.e. no certs to signed cert).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May also be worth creating an OWNERS file in this directory too

@munnerz
Copy link
Copy Markdown
Member

munnerz commented May 2, 2018

Let's get this in and see how we get on - we can iterate from there 😄

Thanks so much for all your hard work @vdesjardins! 🙏 👏

/lgtm
/approve

@jetstack-bot jetstack-bot added the lgtm Indicates that a PR is ready to be merged. label May 2, 2018
@jetstack-bot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: munnerz

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@jetstack-bot jetstack-bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. release-note Denotes a PR that will be considered when it comes time to generate release notes. and removed do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels May 2, 2018
@jetstack-bot jetstack-bot merged commit ba36751 into cert-manager:master May 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add 'vault' CA issuer

7 participants