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

Nomad Secret Backend integration #3401

Merged
merged 57 commits into from
Dec 18, 2017
Merged

Nomad Secret Backend integration #3401

merged 57 commits into from
Dec 18, 2017

Conversation

ncorrare
Copy link
Contributor

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

@jefferai
Copy link
Member

Hi Nic,

Thanks for working on this!

This should be refactored on top of the combined database backend. Shouldn't be too hard though.

@ncorrare
Copy link
Contributor Author

ncorrare commented Oct 2, 2017

After talking with @schmichael apparently the failure in the tests was caused by hashicorp/nomad#3302 which is merged now.

Copy link
Contributor

@chrishoffman chrishoffman 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 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.

}
}

func readConfigAccess(storage logical.Storage) (*accessConfig, error, error) {
Copy link
Contributor

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.

Copy link
Contributor Author

@ncorrare ncorrare Nov 1, 2017

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 15de885

"secret_id": token.SecretID,
"accessor_id": token.AccessorID,
}, map[string]interface{}{
"secret_id": token.SecretID,
Copy link
Contributor

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.

Copy link
Contributor Author

@ncorrare ncorrare Nov 3, 2017

Choose a reason for hiding this comment

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

Fixed in 7015139

Type: framework.TypeString,
Description: "URI scheme for the Nomad address",

// https would be a better default but Consul on its own
Copy link
Contributor

Choose a reason for hiding this comment

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

s/Consul/Nomad/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✔️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 482d73a

// 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",
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✔️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 482d73a

global := d.Get("global").(bool)
policy := d.Get("policy").([]string)
var err error
if tokenType != "management" {
Copy link
Contributor

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?

Copy link
Contributor Author

@ncorrare ncorrare Nov 1, 2017

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


entry, err := logical.StorageEntryJSON("policy/"+name, roleConfig{
Policy: policy,
Lease: lease,
Copy link
Contributor

Choose a reason for hiding this comment

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

s/Lease/TTL/


type roleConfig struct {
Policy []string `json:"policy"`
Lease time.Duration `json:"lease"`
Copy link
Contributor

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)
Copy link
Contributor

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.

var b backend
b.Backend = &framework.Backend{
Paths: []*framework.Path{
pathConfigAccess(),
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Member

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 == "" {
Copy link
Member

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,
},
Copy link
Member

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(),
Copy link
Member

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)
Copy link
Member

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
Copy link
Member

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())
Copy link
Member

Choose a reason for hiding this comment

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

s/Vault/vault


"policies": &framework.FieldSchema{
Type: framework.TypeCommaStringSlice,
Description: "Comma separated list of policies as previously created in Nomad. Required",
Copy link
Member

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.

Copy link
Member

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".

"global": &framework.FieldSchema{
Type: framework.TypeBool,
Default: false,
Description: "Boolean value describing if the token should be global or not. Defaults to false",
Copy link
Member

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"


Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathRolesRead,
logical.UpdateOperation: b.pathRolesWrite,
Copy link
Member

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.

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)
Copy link
Member

Choose a reason for hiding this comment

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

Use errwrap please.

}
default:
return logical.ErrorResponse(
"type must be \"client\" or \"management\""), nil
Copy link
Member

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

@hashicorp hashicorp deleted a comment from vishalnayak Dec 14, 2017
@hashicorp hashicorp deleted a comment from ncorrare Dec 14, 2017
}

if c == nil {
return nil, fmt.Errorf("Error connecting with Nomad")
Copy link
Member

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

Copy link
Member

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)
Copy link
Member

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.

Chris Hoffman added 2 commits December 15, 2017 17:05
* 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)
@hashicorp hashicorp deleted a comment from jefferai Dec 15, 2017
@hashicorp hashicorp deleted a comment from jefferai Dec 15, 2017
@jefferai jefferai merged commit 7fd6103 into master Dec 18, 2017
@jefferai jefferai deleted the f-nomad branch December 18, 2017 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants