-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Implemented TOTP Secret Backend #2492
Conversation
…t conversion of period when generating passwords
This reverts commit fcd0309.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor things overall. Looking really good!
case "SHA512": | ||
algorithm = otplib.AlgorithmSHA512 | ||
default: | ||
algorithm = otplib.AlgorithmSHA1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the default case is ever hit, it seems to me like something went wrong somewhere. Maybe it should return an error here instead.
case 6: | ||
digits = otplib.DigitsSix | ||
case 8: | ||
digits = otplib.DigitsEight |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a default case that returns an error
builtin/logical/totp/path_roles.go
Outdated
"Invalid key value: %s", err)), nil | ||
} | ||
|
||
if period < 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
period <= 0
builtin/logical/totp/path_roles.go
Outdated
// Set optional parameters if neccessary | ||
if period == 0 { | ||
period = 30 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed now that there is a default and if the above branch is changed to <=
builtin/logical/totp/path_roles.go
Outdated
entry, err := logical.StorageEntryJSON("role/"+name, &roleEntry{ | ||
Key: key, | ||
Issuer: issuer, | ||
Account_Name: account_name, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on Go's best practices Account_Name
should be: AccountName
builtin/logical/totp/path_roles.go
Outdated
type roleEntry struct { | ||
Key string `json:"key" mapstructure:"key" structs:"key"` | ||
Issuer string `json:"issuer" mapstructure:"issuer" structs:"issuer"` | ||
Account_Name string `json:"account_name" mapstructure:"account_name" structs:"account_name"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Account_Name
-> AccountName
|
||
resp, err := &logical.Response{ | ||
Data: map[string]interface{}{ | ||
"token": totpToken, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think the response should be key'ed as "code" rather than "token"?
builtin/logical/totp/path_keys.go
Outdated
|
||
//Read digits | ||
digitsQuery, err := strconv.Atoi(urlQuery.Get("digits")) | ||
if err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be if err != nil
and error out if digits can't be parsed. Same with the period above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Digits, Algorithm and Period are all optional values of a url. The error would come from strconv attempting to parse an empty string. Should I first do a check if the query is empty and if it is, skip that field and if it isn't parse it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep!
builtin/logical/totp/path_keys.go
Outdated
case 8: | ||
keyDigits = otplib.DigitsEight | ||
default: | ||
return logical.ErrorResponse("the digit value can only be 6 or 8"), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/digit/digits
builtin/logical/totp/path_keys.go
Outdated
return logical.ErrorResponse("the skew value must be 0 or 1"), nil | ||
} | ||
|
||
if qrSize <= 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should be required; if they want to only get a URL back (and the actual key separately) and skip the QR code we shouldn't force it. I'd have qrSize being 0 just disable QR code generation.
builtin/logical/totp/path_keys.go
Outdated
//Parse url | ||
urlObject, err := url.Parse(inputURL) | ||
if err != nil { | ||
return logical.ErrorResponse("an error occured while parsing url string"), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...and what is that error? :-) You may not want to put it in the URL, but then at least log it (probably at debug or info level).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean log the error or log the URL? The URL might contain secret data that we don't want in the logs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did mean log the error, although I guess depending on what that error is, if it spits out parts of the url it could log something secret. It may be worth returning the error to the caller though, presumably since they provided the URL they can be privy to parts of it in the error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless they're automated systems that then log the error automatically. So pick: turtles all the way down, or a potentially but not necessarily useful error message.
builtin/logical/totp/path_keys.go
Outdated
SecretSize: uintKeySize, | ||
}) | ||
if err != nil { | ||
return logical.ErrorResponse("an error occured while generating a key"), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, log the error or return it (in this case since the error wouldn't have the key value in it it's probably safe to just return) -- or both!
builtin/logical/totp/path_keys.go
Outdated
urlString := keyObject.String() | ||
barcode, err := keyObject.Image(qrSize, qrSize) | ||
if err != nil { | ||
return logical.ErrorResponse("an error occured while generating a QR code image"), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment...
builtin/logical/totp/path_keys.go
Outdated
uintSkew := uint(skew) | ||
uintKeySize := uint(keySize) | ||
|
||
var response logical.Response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this a pointer -- the default value will be nil. That way at the bottom you don't need an if
block to switch on generate
-- you just return response
and if generate
was false it'll automatically be nil
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking much better! Some relatively minor comments.
|
||
- `name` `(string: <required>)` – Specifies the name of the key to create. This is specified as part of the URL. | ||
|
||
- `generate` `(bool: false)` – Specifies if a key is generated by Vault or if a key is being passed from another service. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say "...if a key should be generated by Vault..."
|
||
- `key` `(string: <required - if generate is false and url is empty>)` – Specifies the master key used to generate a TOTP code. Only used if generate is false. | ||
|
||
- `issuer` `(string: "" <required - if generate is true and url is empty>)` – Specifies the name of the key’s issuing organization. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment here about generate being true and url is empty not matching the actual code and the description of the parameters in the backend.
|
||
- `digits` `(int: 6)` – Specifies the number of digits in the generated TOTP code. This value can be set to 6 or 8. | ||
|
||
- `skew` `(int: 0)` – Specifies the number of delay periods that are allowed when validating a TOTP code. This value can be either 0 or 1. Only used if generate is true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A reminder if you default to 1 to change this.
https://vault.rocks/v1/totp/keys/my-key | ||
``` | ||
|
||
### Sample Response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add some documentation here indicating that the returned QR code, if any, is a base64-encoded PNG.
} | ||
``` | ||
|
||
### Embedding Barcodes in HTML |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just saw this; I think you can add the note above, and then just move this text up there along with it, e.g. "If a QR code is returned, it is base64-formatted PNG bytes. You can embed it in a web page by including..."
func pathKeys(b *backend) *framework.Path { | ||
return &framework.Path{ | ||
Pattern: "keys/" + framework.GenericNameRegex("name"), | ||
Fields: map[string]*framework.FieldSchema{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a boolean field (exported
probably) that defaults to true and that controls whether or not a key that is generated is actually returned as a URL/QR code. The use-case is if you want to give one Vault user access to fetch a code, another access to validate the codes, but you don't ever actually want the key to be exposed.
A neat enhancement in this case might be to have the algorithm default to SHA256 if this is false, because if you know the key is never used anywhere else, you don't have to worry about compatibility issues.
… up error reporting, changed default skew value, updated documentation and added additional tests
I made another round of changes based on Jeff's comments. |
return logical.ErrorResponse(fmt.Sprintf("unknown key: %s", name)), nil | ||
} | ||
|
||
valid, err := totplib.ValidateCustom(code, key.Key, time.Now(), totplib.ValidateOpts{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're not checking this error -- if there is an error here it should bail out.
builtin/logical/totp/path_code.go
Outdated
} | ||
|
||
// Return the secret | ||
resp, err := &logical.Response{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/resp, err :=/return/ ? There's really no reason to define both of these only to return them, just return them directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢 🕺 👯♂️ 👯 💃
Oh, sorry -- after those two comments are fixed that is!
… validating codes and clarified documentation for generate parameters in path_keys
I fixed the two issues you mentioned. One note, one of the errors that the validation code generates is if the code is of invalid length (doesn't match digits). I set it to ignore that particular error but I can change it if you want. My thought was that if a code is invalid the backend should just return the false value in the return data. |
That sounds fine! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me! Fantastic work!
@mymercurialsky So good...seriously stoked about this. Thanks for all of the hard work and perseverance! |
@vsivsi No -- that's something else entirely. It is on the roadmap, though. |
Right, but this update contains a lot of functionality useful for implementing the MFA auth code, no?. Anyway, glad to hear it is on the roadmap because it is necessary. |
@vsivsi No, it's entirely separate. Code that can be used for MFA has been in a branch for a long time, but it's been waiting on some other core changes to happen. |
Implementation and test file for TOTP secret backend. This resolves #1197.