open-vault/builtin/logical/nomad/path_config_access.go
Alexander Scheel 6f66e5cd48
Allow reading Nomad CA/Client cert configuration (#15809)
* Allow reading Nomad CA/Client cert configuration

In the Nomad secret engine, writing to /nomad/config/access allows users
to specify a CA certificate and client credential pair. However, these
values are not in the read of the endpoint, making it hard for operators
to see if these values were specified and if they need to be rotated.

Add `ca_cert` and `client_cert` parameters to the response, eliding the
`client_key` parameter as it is more sensitive (and should most likely
be replaced at the same time as `client_cert`).

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add changelog

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Fix tests to expect additional fields

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>

* Add test with existing CA/client cert+key

Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
2022-06-10 10:09:54 -04:00

174 lines
4.5 KiB
Go

package nomad
import (
"context"
"fmt"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const configAccessKey = "config/access"
func pathConfigAccess(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/access",
Fields: map[string]*framework.FieldSchema{
"address": {
Type: framework.TypeString,
Description: "Nomad server address",
},
"token": {
Type: framework.TypeString,
Description: "Token for API calls",
},
"max_token_name_length": {
Type: framework.TypeInt,
Description: "Max length for name of generated Nomad tokens",
},
"ca_cert": {
Type: framework.TypeString,
Description: `CA certificate to use when verifying Nomad server certificate,
must be x509 PEM encoded.`,
},
"client_cert": {
Type: framework.TypeString,
Description: `Client certificate used for Nomad's TLS communication,
must be x509 PEM encoded and if this is set you need to also set client_key.`,
},
"client_key": {
Type: framework.TypeString,
Description: `Client key used for Nomad's TLS communication,
must be x509 PEM encoded and if this is set you need to also set client_cert.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathConfigAccessRead,
logical.CreateOperation: b.pathConfigAccessWrite,
logical.UpdateOperation: b.pathConfigAccessWrite,
logical.DeleteOperation: b.pathConfigAccessDelete,
},
ExistenceCheck: b.configExistenceCheck,
}
}
func (b *backend) configExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
entry, err := b.readConfigAccess(ctx, req.Storage)
if err != nil {
return false, err
}
return entry != nil, nil
}
func (b *backend) readConfigAccess(ctx context.Context, storage logical.Storage) (*accessConfig, error) {
entry, err := storage.Get(ctx, configAccessKey)
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
conf := &accessConfig{}
if err := entry.DecodeJSON(conf); err != nil {
return nil, fmt.Errorf("error reading nomad access configuration: %w", err)
}
return conf, nil
}
func (b *backend) pathConfigAccessRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
conf, err := b.readConfigAccess(ctx, req.Storage)
if err != nil {
return nil, err
}
if conf == nil {
return nil, nil
}
return &logical.Response{
Data: map[string]interface{}{
"address": conf.Address,
"max_token_name_length": conf.MaxTokenNameLength,
"ca_cert": conf.CACert,
"client_cert": conf.ClientCert,
},
}, nil
}
func (b *backend) pathConfigAccessWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
conf, err := b.readConfigAccess(ctx, req.Storage)
if err != nil {
return nil, err
}
if conf == nil {
conf = &accessConfig{}
}
address, ok := data.GetOk("address")
if ok {
conf.Address = address.(string)
}
token, ok := data.GetOk("token")
if ok {
conf.Token = token.(string)
}
caCert, ok := data.GetOk("ca_cert")
if ok {
conf.CACert = caCert.(string)
}
clientCert, ok := data.GetOk("client_cert")
if ok {
conf.ClientCert = clientCert.(string)
}
clientKey, ok := data.GetOk("client_key")
if ok {
conf.ClientKey = clientKey.(string)
}
if conf.Token == "" {
client, err := clientFromConfig(conf)
if err != nil {
return logical.ErrorResponse("Token not provided and failed to constuct client"), err
}
token, _, err := client.ACLTokens().Bootstrap(nil)
if err != nil {
return logical.ErrorResponse("Token not provided and failed to bootstrap ACLs"), err
}
conf.Token = token.SecretID
}
conf.MaxTokenNameLength = data.Get("max_token_name_length").(int)
entry, err := logical.StorageEntryJSON("config/access", conf)
if err != nil {
return nil, err
}
if err := req.Storage.Put(ctx, entry); err != nil {
return nil, err
}
return nil, nil
}
func (b *backend) pathConfigAccessDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
if err := req.Storage.Delete(ctx, configAccessKey); err != nil {
return nil, err
}
return nil, nil
}
type accessConfig struct {
Address string `json:"address"`
Token string `json:"token"`
MaxTokenNameLength int `json:"max_token_name_length"`
CACert string `json:"ca_cert"`
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
}