| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package consul |
| |
| import ( |
| "context" |
| "fmt" |
| |
| "github.com/hashicorp/consul/api" |
| "github.com/hashicorp/vault/sdk/framework" |
| "github.com/hashicorp/vault/sdk/logical" |
| ) |
| |
| func pathConfigAccess(b *backend) *framework.Path { |
| return &framework.Path{ |
| Pattern: "config/access", |
| |
| DisplayAttrs: &framework.DisplayAttributes{ |
| OperationPrefix: operationPrefixConsul, |
| }, |
| |
| Fields: map[string]*framework.FieldSchema{ |
| "address": { |
| Type: framework.TypeString, |
| Description: "Consul server address", |
| }, |
| |
| "scheme": { |
| Type: framework.TypeString, |
| Description: "URI scheme for the Consul address", |
| |
| // 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", |
| }, |
| |
| "token": { |
| Type: framework.TypeString, |
| Description: "Token for API calls", |
| }, |
| |
| "ca_cert": { |
| Type: framework.TypeString, |
| Description: `CA certificate to use when verifying Consul server certificate, |
| must be x509 PEM encoded.`, |
| }, |
| |
| "client_cert": { |
| Type: framework.TypeString, |
| Description: `Client certificate used for Consul'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 Consul's TLS communication, |
| must be x509 PEM encoded and if this is set you need to also set client_cert.`, |
| }, |
| }, |
| |
| Operations: map[logical.Operation]framework.OperationHandler{ |
| logical.ReadOperation: &framework.PathOperation{ |
| Callback: b.pathConfigAccessRead, |
| DisplayAttrs: &framework.DisplayAttributes{ |
| OperationSuffix: "access-configuration", |
| }, |
| }, |
| logical.UpdateOperation: &framework.PathOperation{ |
| Callback: b.pathConfigAccessWrite, |
| DisplayAttrs: &framework.DisplayAttributes{ |
| OperationVerb: "configure", |
| OperationSuffix: "access", |
| }, |
| }, |
| }, |
| } |
| } |
| |
| func (b *backend) readConfigAccess(ctx context.Context, storage logical.Storage) (*accessConfig, error, error) { |
| entry, err := storage.Get(ctx, "config/access") |
| if err != nil { |
| return nil, nil, err |
| } |
| if entry == nil { |
| return nil, fmt.Errorf("access credentials for the backend itself haven't been configured; please configure them at the '/config/access' endpoint"), nil |
| } |
| |
| conf := &accessConfig{} |
| if err := entry.DecodeJSON(conf); err != nil { |
| return nil, nil, fmt.Errorf("error reading consul access configuration: %w", err) |
| } |
| |
| return conf, nil, nil |
| } |
| |
| func (b *backend) pathConfigAccessRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { |
| conf, userErr, intErr := b.readConfigAccess(ctx, req.Storage) |
| if intErr != nil { |
| return nil, intErr |
| } |
| if userErr != nil { |
| return logical.ErrorResponse(userErr.Error()), nil |
| } |
| if conf == nil { |
| return nil, fmt.Errorf("no user error reported but consul access configuration not found") |
| } |
| |
| return &logical.Response{ |
| Data: map[string]interface{}{ |
| "address": conf.Address, |
| "scheme": conf.Scheme, |
| }, |
| }, nil |
| } |
| |
| func (b *backend) pathConfigAccessWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { |
| config := accessConfig{ |
| Address: data.Get("address").(string), |
| Scheme: data.Get("scheme").(string), |
| Token: data.Get("token").(string), |
| CACert: data.Get("ca_cert").(string), |
| ClientCert: data.Get("client_cert").(string), |
| ClientKey: data.Get("client_key").(string), |
| } |
| |
| // If a token has not been given by the user, we try to boostrap the ACL |
| // support |
| if config.Token == "" { |
| consulConf := config.NewConfig() |
| client, err := api.NewClient(consulConf) |
| if err != nil { |
| return nil, err |
| } |
| token, _, err := client.ACL().Bootstrap() |
| if err != nil { |
| return logical.ErrorResponse("Token not provided and failed to bootstrap ACLs: %s", err), nil |
| } |
| config.Token = token.SecretID |
| } |
| |
| entry, err := logical.StorageEntryJSON("config/access", config) |
| if err != nil { |
| return nil, err |
| } |
| |
| if err := req.Storage.Put(ctx, entry); err != nil { |
| return nil, err |
| } |
| |
| return nil, nil |
| } |
| |
| type accessConfig struct { |
| Address string `json:"address"` |
| Scheme string `json:"scheme"` |
| Token string `json:"token"` |
| CACert string `json:"ca_cert"` |
| ClientCert string `json:"client_cert"` |
| ClientKey string `json:"client_key"` |
| } |
| |
| func (conf *accessConfig) NewConfig() *api.Config { |
| consulConf := api.DefaultNonPooledConfig() |
| consulConf.Address = conf.Address |
| consulConf.Scheme = conf.Scheme |
| consulConf.Token = conf.Token |
| consulConf.TLSConfig.CAPem = []byte(conf.CACert) |
| consulConf.TLSConfig.CertPEM = []byte(conf.ClientCert) |
| consulConf.TLSConfig.KeyPEM = []byte(conf.ClientKey) |
| |
| return consulConf |
| } |