Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSH secrets engine - Enabled creation of key pairs (CA Mode) #15561

Merged

Conversation

Gabrielopesantos
Copy link
Contributor

@Gabrielopesantos Gabrielopesantos commented May 21, 2022

Description

Issue: #10879
Created a new endpoint (/ssh/issue/:name) that does not take a public_key (as /ssh/sign/:name does), and instead generates a new public/private key pair internally which is returned in the response.

Copy link
Contributor

@cipherboy cipherboy left a comment

Choose a reason for hiding this comment

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

Looking like a great start @Gabrielopesantos!

A few nits here and there around how validation of type/bits parameters occurs, that's tricky but expected :-) In retrospect, I really made that more complicated than I intended to.

You might be able to refactor the existing logic into taking a keyType/keyBits parameter and hoisting the type/bits extraction to a separate method.

And regarding your comment:

Also, for now everything is in a single function but I could also refactor the existing pathSignCertificate to support this new case.

I think I'd maybe take the common logic (124 and down in the new implementation?) and refactor it to a shared call. So three functions (pathSignCertificate, pathIssueCertificate, and perhaps pathSignIssueCertificateHelper or something of the sort).

Just a thought. :-)


Also, as a heads up, this likely won't make RC1 milestone for 1.11 which means it'll likely be slated for 1.12, in case you were wondering :-)

builtin/logical/ssh/path_issue.go Outdated Show resolved Hide resolved
builtin/logical/ssh/path_issue.go Outdated Show resolved Hide resolved
builtin/logical/ssh/path_issue.go Outdated Show resolved Hide resolved
@cipherboy cipherboy requested a review from a team May 23, 2022 12:21
@cipherboy cipherboy added this to the 1.12.0-rc1 milestone May 23, 2022
@Gabrielopesantos
Copy link
Contributor Author

Gabrielopesantos commented May 24, 2022

Hello @cipherboy,
just pushed a version that should cover most requested changes/comments.

There are two files path_issue.go and path_sign.go using stuff from each other, maybe joining everything a path_issue_sign.go as in the pki package is the way to go.

What is the behavior expected if no value are defined? Currently, the key_type and key_bits do not have any default values set. So, if not filled, key_type will be an empty string and key_bits 0. I think its ok to leave key_bits as it is handled in generateSSHKeyPair, its set to different values depending on the key_type. However, in the case of key_type, if not defined it returns an error. Took a look at other endpoints that have a key_type field and they seem to have "rsa" as default. Is it ok to also define "rsa" in this endpoint?

@sgmiller
Copy link
Collaborator

Hello @cipherboy, just pushed a version that should cover most requested changes/comments.

There are two files path_issue.go and path_sign.go using stuff from each other, maybe joining everything a path_issue_sign.go as in the pki package is the way to go.

What is the behavior expected if no value are defined? Currently, the key_type and key_bits do not have any default values set. So, if not filled, key_type will be an empty string and key_bits 0. I think its ok to leave key_bits as it is handled in generateSSHKeyPair, its set to different values depending on the key_type. However, in the case of key_type, if not defined it returns an error. Took a look at other endpoints that have a key_type field and they seem to have "rsa" as default. Is it ok to also define "rsa" in this endpoint?

I'm fine with either an error or an implicit default. If the goal is to make the call as simple as possible to make then the default works.

@Gabrielopesantos
Copy link
Contributor Author

Hello, added a few tests which should cover which should the implementation of this new endpoint, as most of it is reusing the existing ssh/sign. Let me know if I am missing any case. With this, I am opening the pull request for review.

Also, while testing found something that I am not sure if it expected. I defined a allowed_user_key_lengths as:
"ssh-rsa": int{2048, 3072, 4096}, "ecdsa-sha2-nistp521": 0, however it seems that it is created as the following.
AllowedUserKeyTypesLengths: map[ecdsa-sha2-nistp521:[0 0] ssh-rsa:[0 0 0 2048 3072 4096]]
Is this expected?

Copy link
Contributor

@cipherboy cipherboy left a comment

Choose a reason for hiding this comment

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

This looks like rather clean, well-written code! The refactor I think helped a fair bit here. I think most of these are rather minor nits.

Oh and a change log entry :-)

builtin/logical/ssh/path_issue.go Outdated Show resolved Hide resolved
builtin/logical/ssh/path_issue.go Outdated Show resolved Hide resolved
builtin/logical/ssh/backend_test.go Outdated Show resolved Hide resolved
@Gabrielopesantos
Copy link
Contributor Author

Gabrielopesantos commented Jun 2, 2022

Hello @cipherboy, made a commit that addresses both comments and proposes a solution for this #15561 (comment) .

There are a few things that still need to be addressed such as help messages, error messages, organizing functions in correct files and creating the changelog. Let me know if am forgetting anything.

@cipherboy
Copy link
Contributor

Hey @Gabrielopesantos Sorry for the delay in getting back to this. I think you need to run make fmt but otherwise (minus what you identified in your last comment) this looks good :)

@Gabrielopesantos
Copy link
Contributor Author

Gabrielopesantos commented Jun 6, 2022

No worries, I know this improvement is set for a 1.12.*** milestone so isn't a priority.

I did not include the make fmt changes as it did not touch any of the files I worked on, not sure if should have added anyway.

changelog/15561.txt Outdated Show resolved Hide resolved
Copy link
Contributor

@cipherboy cipherboy left a comment

Choose a reason for hiding this comment

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

Love the refactor! I'll have a colleague check this out before merging though.

@cipherboy cipherboy requested a review from a team June 8, 2022 13:39
Copy link
Contributor

@stevendpclark stevendpclark left a comment

Choose a reason for hiding this comment

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

Overall this is really great! I have some small nits but nothing preventing this from merging.

An api documentation update is required for this change though, if you do not feel comfortable writing the updated documentation, we can handle that in a separate PR.

ErrorOk: true,
// Returns like nil, err break this
Check: func(resp *logical.Response) error {
if expectError {
Copy link
Contributor

Choose a reason for hiding this comment

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

Because ErrorOk is set to true above, this check function should perhaps validate that resp is not in an error state when expectError is false? At least it would help to diagnose test failures if we returned the error in resp if it was unexpected?

Copy link
Contributor Author

@Gabrielopesantos Gabrielopesantos Jun 8, 2022

Choose a reason for hiding this comment

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

Thank you for the feedback, I agree with the suggestion. Would something like the following after the if expectError block be enough to handle it?

if resp.Error() != nil {
    return fmt.Errorf("unexpected error response returned: %v", resp.Error())
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup that's perfect, just an FYI the following is a little more common (but don't bother changing it)

if resp.IsError() {
    return fmt.Errorf("unexpected error response returned: %v", resp.Error())
}

}

if resp.Data["private_key_type"] != keyType {
return errors.New("private key type does not match provided key_type")
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Could we include expected vs actual within this error please to again help to diagnose future test failures?

// them as 4xx values
keyID, err := b.calculateKeyID(data, req, role, publicKey)
if err != nil {
return logical.ErrorResponse(err.Error()), err
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: It's a tad odd that we return an error response and an err, should we not be consistent and return nil, err ?

Copy link
Contributor

@cipherboy cipherboy Jun 8, 2022

Choose a reason for hiding this comment

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

Not to nitpick your nitpick, but this was just a copy/paste refactor of the existing code:

keyID, err := b.calculateKeyID(data, req, role, userPublicKey)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
}

And worst, it seems consistently used that way.

Never mind, I agree that returning both the formatted stuff and the err is weird. I'd return one or the other like the existing code did.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually there is a behavior change here, as the callers are wrapping any error into a logical.ErrorResponse so some existing error scenarios we did return a 500 will be switched to a 400.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also makes sense, I remember making this change during development because was having some kind of error. The latest commit reverts these returns to how they were initially (also not consistent). If consistency in the return statements is what we are looking for, I can make the change to return nil, err as suggested.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks! What you have changed it too, reverting to what it was, is the correct approach, much appreciated.

Gabrielopesantos and others added 2 commits June 8, 2022 19:05
Co-authored-by: Alexander Scheel <alexander.m.scheel@gmail.com>
@Gabrielopesantos
Copy link
Contributor Author

Gabrielopesantos commented Jun 8, 2022

Pushed a commit with possible fixes to the suggestions made, let me know if there is anything that still should be altered.

Regarding the documentation, is it setting up the documentation for the endpoint, like this https://www.vaultproject.io/api-docs/secret/pki#generate-certificate but for the ssh? If yes, I can give it a try.

@stevendpclark
Copy link
Contributor

Regarding the documentation, is it setting up the documentation for the endpoint, like this https://www.vaultproject.io/api-docs/secret/pki#generate-certificate but for the ssh? If yes, I can give it a try.

Correct the file to update would be https://github.com/hashicorp/vault/blob/main/website/content/api-docs/secret/ssh.mdx, adding a new section for the new api endpoint added. It should include a short description, parameters, sample payload, request and response sections as well.

@Gabrielopesantos
Copy link
Contributor Author

Gabrielopesantos commented Jun 10, 2022

Pushed a commit with the documentation proposal, it is basically a copy of the existing endpoint (with some minor changes), not sure if it enough or might require extra information.

I think I also found an issue with a previous PR (made by me), the documentation of the ssh/sign endpoint has a field (not_before_duration) that is not supported, as, given the current implementation, it is only in the ssh/roles endpoint.

@cipherboy
Copy link
Contributor

This looks excellent @Gabrielopesantos! Going to go ahead and merge this, and I'll get a fix opened for that docs change you noted (needs to be a separate PR as it goes back to 1.11) :)

@cipherboy cipherboy merged commit f2af7f1 into hashicorp:main Jun 10, 2022
cipherboy pushed a commit that referenced this pull request Oct 27, 2022
…7694)

Extend the documentation the API endpoint '/ssh/issue/:name' (added
in #15561 with v1.12.0) and '/ssh/issue/:name':

- Be more specific that the issued certificate uses the defaults
  given of the role at the given endpoint; and that it is subject
  to the limitations configured in this role.

- Note that the endpoint /ssh/issue/:name is available with v1.12+.

- Make it more clear that the generated credentials are only returned
  but not stored by Vault (not just the generated private key).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants