-
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
Nomad Secret Backend integration #3401
Conversation
Hi Nic, Thanks for working on this! This should be refactored on top of the combined database backend. Shouldn't be too hard though. |
After talking with @schmichael apparently the failure in the tests was caused by hashicorp/nomad#3302 which is merged now. |
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.
Overall, this is looking pretty good. There are a lot of comments but mostly relating to new conventions. The backend that this was modeled after was one of the oldest backends and much has not been changed for backwards compatibility reasonse.
builtin/logical/nomad/path_config.go
Outdated
} | ||
} | ||
|
||
func readConfigAccess(storage logical.Storage) (*accessConfig, error, error) { |
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 think you only need to have a single error. I'm not sure the differentiation between the two error returns but I'm not sure it is necessary.
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 honestly don't see why in this case an error isn't just an error, and we need to differentiate between userErr and intErr. Maybe in an old Vault version something allowed to like "enable" the backend without configuration? In any case, I refactored it so an error is just an error, looks cleaner. Fixed in ca92922
} | ||
|
||
tokenRaw, ok := req.Secret.InternalData["accessor_id"] | ||
if !ok { |
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 never be true since we are always setting the accessor_id in the response data.
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.
Fixed in 15de885
builtin/logical/nomad/path_token.go
Outdated
"secret_id": token.SecretID, | ||
"accessor_id": token.AccessorID, | ||
}, map[string]interface{}{ | ||
"secret_id": token.SecretID, |
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.
Although this is never exposed to the user, there is no reason to keep secret_id around since Vault will never be making calls in behalf the the user.
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.
Fixed in 7015139
builtin/logical/nomad/path_config.go
Outdated
Type: framework.TypeString, | ||
Description: "URI scheme for the Nomad address", | ||
|
||
// https would be a better default but Consul on its own |
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/Consul/Nomad/
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.
✔️
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.
Fixed in 482d73a
builtin/logical/nomad/path_config.go
Outdated
// https would be a better default but Consul on its own | ||
// defaults to HTTP access, and when HTTPS is enabled it | ||
// disables HTTP, so there isn't really any harm done here. | ||
Default: "http", |
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 think we should default to https and have to specify explicitly if they want http.
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.
✔️
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.
Fixed in 482d73a
builtin/logical/nomad/path_roles.go
Outdated
global := d.Get("global").(bool) | ||
policy := d.Get("policy").([]string) | ||
var err error | ||
if tokenType != "management" { |
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.
Can a management token have associated policies? Should you verify the management token has zero policies?
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.
Should be fixed in ffb9343
builtin/logical/nomad/path_roles.go
Outdated
|
||
entry, err := logical.StorageEntryJSON("policy/"+name, roleConfig{ | ||
Policy: policy, | ||
Lease: lease, |
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/Lease/TTL/
builtin/logical/nomad/path_roles.go
Outdated
|
||
type roleConfig struct { | ||
Policy []string `json:"policy"` | ||
Lease time.Duration `json:"lease"` |
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/Lease/TTL/
func (b *backend) secretTokenRenew( | ||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||
|
||
return framework.LeaseExtend(0, 0, b.System())(req, d) |
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 you add a config/lease
endpoint you are going to want to pass in the configured values instead of 0
.
builtin/logical/nomad/backend.go
Outdated
var b backend | ||
b.Backend = &framework.Backend{ | ||
Paths: []*framework.Path{ | ||
pathConfigAccess(), |
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 config/lease
path for configuring the ttl
and max_ttl
for the backend.
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.
Fixed in d1e3eff
|
||
conf := &accessConfig{} | ||
if err := entry.DecodeJSON(conf); err != nil { | ||
return nil, fmt.Errorf("error reading nomad access configuration: %s", err) |
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 use errwrap here.
func (b *backend) pathConfigAccessWrite( | ||
req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||
address := data.Get("address").(string) | ||
if address == "" { |
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.
See earlier comments; I don't think this (or token
) should be required.
Callbacks: map[logical.Operation]framework.OperationFunc{ | ||
logical.ReadOperation: b.pathConfigAccessRead, | ||
logical.UpdateOperation: b.pathConfigAccessWrite, | ||
}, |
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 client can be successful through env vars alone, there should be a DeleteOperation to clear out the access information if it's not needed or if it's conflicting.
|
||
return &logical.Response{ | ||
Data: map[string]interface{}{ | ||
"ttl": lease.TTL.Seconds(), |
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 int64()
these values.
|
||
role, err := b.Role(req.Storage, name) | ||
if err != nil { | ||
return nil, fmt.Errorf("error retrieving role: %v", err) |
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 use errwrap here
return nil, fmt.Errorf("error retrieving role: %v", err) | ||
} | ||
if role == nil { | ||
return logical.ErrorResponse(fmt.Sprintf("Role %q not found", name)), 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.
Please start the error with a lowercase letter (Go convention)
} | ||
|
||
// Generate a name for the token | ||
tokenName := fmt.Sprintf("Vault-%s-%s-%d", name, req.DisplayName, time.Now().UnixNano()) |
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/Vault/vault
builtin/logical/nomad/path_roles.go
Outdated
|
||
"policies": &framework.FieldSchema{ | ||
Type: framework.TypeCommaStringSlice, | ||
Description: "Comma separated list of policies as previously created in Nomad. Required", |
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 update this description to make it clear that it can be comma-separated or an array of strings.
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.
Also, if this is going to be two separate sentences, please put a period after "Required".
builtin/logical/nomad/path_roles.go
Outdated
"global": &framework.FieldSchema{ | ||
Type: framework.TypeBool, | ||
Default: false, | ||
Description: "Boolean value describing if the token should be global or not. Defaults to false", |
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 with "Defaults to false"
builtin/logical/nomad/path_roles.go
Outdated
|
||
Callbacks: map[logical.Operation]framework.OperationFunc{ | ||
logical.ReadOperation: b.pathRolesRead, | ||
logical.UpdateOperation: b.pathRolesWrite, |
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 CreateOperation and an existence check and update pathRolesWrite accordingly.
builtin/logical/nomad/path_roles.go
Outdated
func (b *backend) Role(storage logical.Storage, name string) (*roleConfig, error) { | ||
entry, err := storage.Get("role/" + name) | ||
if err != nil { | ||
return nil, fmt.Errorf("error retrieving role: %s", err) |
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.
Use errwrap please.
builtin/logical/nomad/path_roles.go
Outdated
} | ||
default: | ||
return logical.ErrorResponse( | ||
"type must be \"client\" or \"management\""), 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.
You can use `` for the string delimeter to avoid needing to escape quotes here
} | ||
|
||
if c == nil { | ||
return nil, fmt.Errorf("Error connecting with Nomad") |
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.
lowercase the start of the error please
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.
Also I think the error should be more descriptive...it's not an error connecting, it's an error getting a client.
return nil, fmt.Errorf("accessor_id is missing on the lease") | ||
} | ||
|
||
_, err = c.ACLTokens().Delete(accessorIDRaw.(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.
If for some reason it's not a string, this will panic. Please separate out the string conversion into its own guarded check.
* oss/master: Defer reader.Close that is used to determine sha256 changelog++ Avoid unseal failure if plugin backends fail to setup during postUnseal (#3686) Add logic for using Auth.Period when handling auth login/renew requests (#3677) plugins/database: use context with plugins that use database/sql package (#3691) changelog++ Fix plaintext backup in transit (#3692) Database gRPC plugins (#3666)
* oss/master: Add support for encrypted TLS key files (#3685)
Considering that Nomad has shipped an ACL system it makes sense that Vault would be able to bridge the gap between the identity providers and the Nomad ACL system through the use of dynamic short-lived tokens.
Taking the Consul backend as an example, and refactoring to match the Nomad API data model (and with help from @chrishoffman whose input was far from negligible, and immensely appreciated), I've added a Nomad Secret Backend