6f66e5cd48
* 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>
174 lines
4.5 KiB
Go
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"`
|
|
}
|