Sounds daunting right? But there is always a case where your organisation has either a custom, or third party system that you could leverage generating secrets for, or maybe an IAM system that doesn't quite fit on the ones included in Vault. Well, a couple of months ago I went from "no go" to writing my first plugin from Vault, and I'd like to tell you how I did it. This talk doesn't require you to know go, but it does require a minimal level of understanding of object oriented programming.
2. Nicolas Corrarello
Solutions Engineering Lead - International
International Man of Mystery, licensed to:
• Methodology
• Field Work
• Implementations
• Develop
• Document
• School runs
@nomadic_geek
3. @nomadic_geek
Vault rocks!!!
• Takes a sensible and programmatic
approach to security
• It’s Open Source
• It’s fast!
• It’s a “Security product” (As defined by
INFOSEC)
8. @nomadic_geek
Problems
A few biggies….
• Vault is written in Go, I’ve never written a single line of Go
• I knew how Vault works, but never payed attention to the
internals (See “I don’t know Go”)
• I’m in the US, jet lagged, and in a conference
9. @nomadic_geek
Advantages
1. I understand how both Vault and Nomad work
2. I know that the Consul backend does something pretty
similar
3. I’m motivated
4. I’m in the US, in a conference, next to a bunch of Vault
engineers I can ask questions to!
10. @nomadic_geek
What do I need to accomplish
Nomad Secret Backend
Access / TTL
Vault Logical Storage
config/access
config/lease
lease/*
role/*
Token Renew/Revoke
Role Create/Update/
Delete
Nomad Client
11. func Backend() *backend {
var b backend
b.Backend = &framework.Backend{
Paths: []*framework.Path{
pathConfigAccess(&b),
pathConfigLease(&b),
pathListRoles(&b),
pathRoles(&b),
pathCredsCreate(&b),
},
Secrets: []*framework.Secret{
secretToken(&b),
},
BackendType: logical.TypeLogical,
}
return &b
}
Backend Type:
TypeLogical,
TypeCredential
Standard Framework
Backend
FrontEnd Paths
Type of Secret
Standard Secret Framework
16. func (b *backend) pathRolesWrite(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
tokenType := d.Get("type").(string)
name := d.Get("name").(string)
global := d.Get("global").(bool)
policy := d.Get("policy").([]string)
switch tokenType {
case "client":
if len(policy) == 0 {
return logical.ErrorResponse(
"policy cannot be empty when using client tokens"), nil
}
case "management":
if len(policy) != 0 {
return logical.ErrorResponse(
"policy should be empty when using management tokens"), nil
}
default:
return logical.ErrorResponse(
"type must be "client" or "management""), nil
}
entry, err := logical.StorageEntryJSON("role/"+name, roleConfig{
Policy: policy,
TokenType: tokenType,
Global: global,
})
if err != nil {
return nil, err
}
if err := req.Storage.Put(entry); err != nil {
return nil, err
}
return nil, nil
}
Define the role creation function
Token Attributes
Store the role
Validate parameters
17. func pathCredsCreate(b *backend) *framework.Path {
return &framework.Path{
Pattern: "creds/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Name of the role",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathTokenRead,
},
}
}
Define the Credentials Creation Function
Map Functions
18. func (b *backend) pathTokenRead(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
role, err := b.Role(req.Storage, name)
if err != nil {
return nil, fmt.Errorf("error retrieving role: %s", err)
}
if role == nil {
return logical.ErrorResponse(fmt.Sprintf("Role '%s' not found", name)), nil
}
leaseConfig, err := b.LeaseConfig(req.Storage)
if err != nil {
return nil, err
}
if leaseConfig == nil {
leaseConfig = &configLease{}
}
c, err := b.client(req.Storage)
if err != nil {
return nil, err
}
tokenName := fmt.Sprintf("Vault %s %s %d", name, req.DisplayName, time.Now().UnixNano())
token, _, err := c.ACLTokens().Create(&api.ACLToken{
Name: tokenName,
Type: role.TokenType,
Policies: role.Policy,
Global: role.Global,
}, nil)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
}
resp := b.Secret(SecretTokenType).Response(map[string]interface{}{
"secret_id": token.SecretID,
"accessor_id": token.AccessorID,
}, map[string]interface{}{
"accessor_id": token.AccessorID,
})
resp.Secret.TTL = leaseConfig.TTL
return resp, nil
}
Random string for token Name
Get the Role object (for the policy names)
Return accessor and token
But only store accessor
21. func main() {
apiClientMeta := &pluginutil.APIClientMeta{}
flags := apiClientMeta.FlagSet()
flags.Parse(os.Args[1:])
tlsConfig := apiClientMeta.GetTLSConfig()
tlsProviderFunc := pluginutil.VaultPluginTLSProvider(tlsConfig)
if err := plugin.Serve(&plugin.ServeOpts{
BackendFactoryFunc: Factory,
TLSProviderFunc: tlsProviderFunc,
}); err != nil {
log.Fatal(err)
}
}
func Factory(c *logical.BackendConfig) (logical.Backend, error) {
b := Backend(c)
if err := b.Setup(c); err != nil {
return nil, err
}
return b, nil
}
type backend struct {
*framework.Backend
}
An independent process that communicates
With Vault using RPC
Using Mutual TLS automatically generated
Create Factory
Define Backend
22. @nomadic_geek
Lessons learned
• Go, err := ‘awesome’, nil
• So many things in Vault were so seamless that really looked like magic.
• The best kept secret is the one you don’t know about.
• Attach all your functions to the Backend of your plugin (Client, Token, Role).
• If I have a pence for each if err == nil … (Seriously, maybe 30% of my code is error handling).
• The other 60% of my code, is just mapping interfaces existing in Vault.
• The remaining 10%, logic that I wrote.
• GoDoc is your friend.
• You can actually write plugins in different languages (see gRPC)
• Document your stuff (https://github.com/hashicorp/vault/tree/f-nomad/website/source/docs/secrets/
nomad).
23. @nomadic_geek
Reference
• Full Code in the f-nomad branch (soon to be master): https://github.com/hashicorp/vault/tree/f-
nomad/builtin/logical/nomad
• Seth Vargo did an awesome post on this (with examples) that cover Auth Plugins:
• https://www.hashicorp.com/blog/building-a-vault-secure-plugin
• https://github.com/sethvargo/vault-auth-slack
• A couple of recommended HashiConf talks:
• https://www.youtube.com/watch?v=bCNSvUrK_BA - Deep dive on Vault AWS Auth backend
• https://www.youtube.com/watch?v=rd0xyT8xMqg - Authenticating to Vault with Google Platform