vault/api/ssh_agent.go

279 lines
8.2 KiB
Go
Raw Normal View History

// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: MPL-2.0
2015-08-12 13:48:58 -04:00
package api
import (
"context"
"crypto/tls"
"crypto/x509"
2015-08-12 13:48:58 -04:00
"fmt"
"io/ioutil"
"net/http"
"os"
2015-08-12 13:48:58 -04:00
"github.com/hashicorp/errwrap"
cleanhttp "github.com/hashicorp/go-cleanhttp"
multierror "github.com/hashicorp/go-multierror"
rootcerts "github.com/hashicorp/go-rootcerts"
"github.com/hashicorp/hcl"
2016-03-10 16:47:46 -05:00
"github.com/hashicorp/hcl/hcl/ast"
2015-08-12 13:48:58 -04:00
"github.com/mitchellh/mapstructure"
)
const (
2016-03-10 16:47:46 -05:00
// SSHHelperDefaultMountPoint is the default path at which SSH backend will be
// mounted in the Vault server.
2016-02-23 00:08:21 -05:00
SSHHelperDefaultMountPoint = "ssh"
2016-03-10 16:47:46 -05:00
// VerifyEchoRequest is the echo request message sent as OTP by the helper.
VerifyEchoRequest = "verify-echo-request"
2016-03-10 16:47:46 -05:00
// VerifyEchoResponse is the echo response message sent as a response to OTP
// matching echo request.
VerifyEchoResponse = "verify-echo-response"
)
2015-08-12 13:48:58 -04:00
// SSHHelper is a structure representing a vault-ssh-helper which can talk to vault server
2015-08-12 13:48:58 -04:00
// in order to verify the OTP entered by the user. It contains the path at which
// SSH backend is mounted at the server.
2016-02-23 00:08:21 -05:00
type SSHHelper struct {
2015-08-12 13:48:58 -04:00
c *Client
MountPoint string
}
2015-09-29 03:35:16 -04:00
// SSHVerifyResponse is a structure representing the fields in Vault server's
2015-08-12 13:48:58 -04:00
// response.
type SSHVerifyResponse struct {
// Usually empty. If the request OTP is echo request message, this will
// be set to the corresponding echo response message.
Message string `json:"message" mapstructure:"message"`
// Username associated with the OTP
Username string `json:"username" mapstructure:"username"`
// IP associated with the OTP
IP string `json:"ip" mapstructure:"ip"`
// Name of the role against which the OTP was issued
RoleName string `json:"role_name" mapstructure:"role_name"`
2015-08-12 13:48:58 -04:00
}
// SSHHelperConfig is a structure which represents the entries from the vault-ssh-helper's configuration file.
2016-02-23 00:08:21 -05:00
type SSHHelperConfig struct {
VaultAddr string `hcl:"vault_addr"`
SSHMountPoint string `hcl:"ssh_mount_point"`
Namespace string `hcl:"namespace"`
CACert string `hcl:"ca_cert"`
CAPath string `hcl:"ca_path"`
AllowedCidrList string `hcl:"allowed_cidr_list"`
AllowedRoles string `hcl:"allowed_roles"`
TLSSkipVerify bool `hcl:"tls_skip_verify"`
TLSServerName string `hcl:"tls_server_name"`
}
2016-03-10 16:47:46 -05:00
// SetTLSParameters sets the TLS parameters for this SSH agent.
2016-02-23 00:08:21 -05:00
func (c *SSHHelperConfig) SetTLSParameters(clientConfig *Config, certPool *x509.CertPool) {
tlsConfig := &tls.Config{
InsecureSkipVerify: c.TLSSkipVerify,
MinVersion: tls.VersionTLS12,
RootCAs: certPool,
ServerName: c.TLSServerName,
}
transport := cleanhttp.DefaultTransport()
transport.TLSClientConfig = tlsConfig
clientConfig.HttpClient.Transport = transport
}
// Returns true if any of the following conditions are true:
// - CA cert is configured
// - CA path is configured
// - configured to skip certificate verification
// - TLS server name is configured
func (c *SSHHelperConfig) shouldSetTLSParameters() bool {
return c.CACert != "" || c.CAPath != "" || c.TLSServerName != "" || c.TLSSkipVerify
}
2015-09-29 03:35:16 -04:00
// NewClient returns a new client for the configuration. This client will be used by the
// vault-ssh-helper to communicate with Vault server and verify the OTP entered by user.
// If the configuration supplies Vault SSL certificates, then the client will
// have TLS configured in its transport.
2016-02-23 00:08:21 -05:00
func (c *SSHHelperConfig) NewClient() (*Client, error) {
// Creating a default client configuration for communicating with vault server.
clientConfig := DefaultConfig()
// Pointing the client to the actual address of vault server.
clientConfig.Address = c.VaultAddr
// Check if certificates are provided via config file.
if c.shouldSetTLSParameters() {
rootConfig := &rootcerts.Config{
CAFile: c.CACert,
CAPath: c.CAPath,
}
certPool, err := rootcerts.LoadCACerts(rootConfig)
if err != nil {
return nil, err
}
// Enable TLS on the HTTP client information
c.SetTLSParameters(clientConfig, certPool)
}
// Creating the client object for the given configuration
client, err := NewClient(clientConfig)
if err != nil {
return nil, err
}
// Configure namespace
if c.Namespace != "" {
client.SetNamespace(c.Namespace)
}
return client, nil
}
2016-02-23 00:08:21 -05:00
// LoadSSHHelperConfig loads ssh-helper's configuration from the file and populates the corresponding
// in-memory structure.
//
// Vault address is a required parameter.
// Mount point defaults to "ssh".
2016-02-23 00:08:21 -05:00
func LoadSSHHelperConfig(path string) (*SSHHelperConfig, error) {
contents, err := ioutil.ReadFile(path)
2016-03-10 16:47:46 -05:00
if err != nil && !os.IsNotExist(err) {
return nil, multierror.Prefix(err, "ssh_helper:")
}
return ParseSSHHelperConfig(string(contents))
}
2016-03-10 16:47:46 -05:00
// ParseSSHHelperConfig parses the given contents as a string for the SSHHelper
// configuration.
func ParseSSHHelperConfig(contents string) (*SSHHelperConfig, error) {
VAULT-32657 deprecate duplicate attributes in HCL configs and policies (#30386) * upgrade hcl dependency on api pkg This upgrades the hcl dependency for the API pkg, and adapts its usage so users of our API pkg are not affected. There's no good way of communicating a warning via a library call so we don't. The tokenHelper which is used by all Vault CLI commands in order to create the Vault client, as well as directly used by the login and server commands, is implemented on the api pkg, so this upgrade also affects all of those commands. Seems like this was only moved to the api pkg because the Terraform provider uses it, and I thought creating a full copy of all those files back under command would be too much spaghetti. Also leaving some TODOs to make next deprecation steps easier. * upgrade hcl dependency in vault and sdk pkgs * upgrade hcl dependency in vault and sdk pkgs * add CLI warnings to commands that take a config - vault agent (unit test on CMD warning) - vault proxy (unit test on CMD warning) - vault server (no test for the warning) - vault operator diagnose (no tests at all, uses the same function as vault server * ignore duplicates on ParseKMSes function * Extend policy parsing functions and warn on policy store * Add warning on policy fmt with duplicate attributes * Add warnings when creating/updating policy with duplicate HCL attrs * Add log warning when switchedGetPolicy finds duplicate attrs Following operations can trigger this warning when they run into a policy with duplicate attributes: * replication filtered path namespaces invalidation * policy read API * building an ACL (for many different purposes like most authZ operations) * looking up DR token policies * creating a token with named policies * when caching the policies for all namespaces during unseal * Print log warnings when token inline policy has duplicate attrs No unit tests on these as new test infra would have to be built on all. Operations affected, which will now print a log warning when the retrieved token has an inline policy with duplicate attributes: * capabilities endpoints in sys mount * handing events under a subscription with a token with duplicate attrs in inline policies * token used to create another token has duplicate attrs in inline policies (sudo check) * all uses of fetchACLTokenEntryAndEntity when the request uses a token with inline policies with duplicate attrs. Almost all reqs are subject to this * when tokens are created with inline policies (unclear exactly how that can happen) * add changelog and deprecation notice * add missing copywrite notice * fix copy-paste mistake good thing it was covered by unit tests * Fix manual parsing of telemetry field in SharedConfig This commit in the hcl library was not in the v1.0.1-vault-5 version we're using but is included in v1.0.1-vault-7: https://github.com/hashicorp/hcl/commit/e80118accb521e47bc5b93104bf46c67d89d2242 This thing of reusing when parsing means that our approach of manually re-parsing fields on top of fields that have already been parsed by the hcl annotation causes strings (maybe more?) to concatenate. Fix that by removing annotation. There's actually more occurrences of this thing of automatically parsing something that is also manually parsing. In some places we could just remove the boilerplate manual parsing, in others we better remove the auto parsing, but I don't wanna pull at that thread right now. I just checked that all places at least fully overwrite the automatically parsed field instead of reusing it as the target of the decode call. The only exception is the AOP field on ent but that doesn't have maps or slices, so I think it's fine. An alternative approach would be to ensure that the auto-parsed value is discarded, like the current parseCache function does note how it's template not templates * Fix linter complaints * Update command/base_predict.go Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com> * address review * remove copywrite headers * re-add copywrite headers * make fmt * Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/partials/deprecation/duplicate-hcl-attributes.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * undo changes to deprecation.mdx * remove deprecation doc * fix conflict with changes from main --------- Co-authored-by: Mike Palmiotto <mike.palmiotto@hashicorp.com> Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com>
2025-05-23 15:02:07 -04:00
// TODO (HCL_DUP_KEYS_DEPRECATION): replace with simple call to hcl.Parse once deprecation of duplicate attributes
// is over, for now just ignore duplicates
root, _, err := parseAndCheckForDuplicateHclAttributes(contents)
2016-03-10 16:47:46 -05:00
if err != nil {
return nil, errwrap.Wrapf("error parsing config: {{err}}", err)
}
2016-03-10 16:47:46 -05:00
list, ok := root.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("error parsing config: file doesn't contain a root object")
}
2016-03-10 16:47:46 -05:00
valid := []string{
"vault_addr",
"ssh_mount_point",
"namespace",
2016-03-10 16:47:46 -05:00
"ca_cert",
"ca_path",
"allowed_cidr_list",
"allowed_roles",
2016-03-10 16:47:46 -05:00
"tls_skip_verify",
"tls_server_name",
2016-03-10 16:47:46 -05:00
}
2023-02-06 09:41:56 -05:00
if err := CheckHCLKeys(list, valid); err != nil {
2016-03-10 16:47:46 -05:00
return nil, multierror.Prefix(err, "ssh_helper:")
}
2016-03-10 16:47:46 -05:00
var c SSHHelperConfig
c.SSHMountPoint = SSHHelperDefaultMountPoint
if err := hcl.DecodeObject(&c, list); err != nil {
return nil, multierror.Prefix(err, "ssh_helper:")
}
if c.VaultAddr == "" {
return nil, fmt.Errorf(`missing config "vault_addr"`)
2016-03-10 16:47:46 -05:00
}
return &c, nil
}
2023-02-06 09:41:56 -05:00
func CheckHCLKeys(node ast.Node, valid []string) error {
var list *ast.ObjectList
switch n := node.(type) {
case *ast.ObjectList:
list = n
case *ast.ObjectType:
list = n.List
default:
return fmt.Errorf("cannot check HCL keys of type %T", n)
}
validMap := make(map[string]struct{}, len(valid))
for _, v := range valid {
validMap[v] = struct{}{}
}
var result error
for _, item := range list.Items {
key := item.Keys[0].Token.Value().(string)
if _, ok := validMap[key]; !ok {
result = multierror.Append(result, fmt.Errorf("invalid key %q on line %d", key, item.Assign.Line))
}
}
return result
}
2016-02-23 00:08:21 -05:00
// SSHHelper creates an SSHHelper object which can talk to Vault server with SSH backend
2015-08-12 13:48:58 -04:00
// mounted at default path ("ssh").
2016-02-23 00:08:21 -05:00
func (c *Client) SSHHelper() *SSHHelper {
return c.SSHHelperWithMountPoint(SSHHelperDefaultMountPoint)
2015-08-12 13:48:58 -04:00
}
2016-02-23 00:08:21 -05:00
// SSHHelperWithMountPoint creates an SSHHelper object which can talk to Vault server with SSH backend
2015-08-12 13:48:58 -04:00
// mounted at a specific mount point.
2016-02-23 00:08:21 -05:00
func (c *Client) SSHHelperWithMountPoint(mountPoint string) *SSHHelper {
return &SSHHelper{
2015-08-12 13:48:58 -04:00
c: c,
MountPoint: mountPoint,
}
}
2015-09-29 03:35:16 -04:00
// Verify verifies if the key provided by user is present in Vault server. The response
// will contain the IP address and username associated with the OTP. In case the
// OTP matches the echo request message, instead of searching an entry for the OTP,
2016-02-23 00:08:21 -05:00
// an echo response message is returned. This feature is used by ssh-helper to verify if
// its configured correctly.
2016-02-23 00:08:21 -05:00
func (c *SSHHelper) Verify(otp string) (*SSHVerifyResponse, error) {
return c.VerifyWithContext(context.Background(), otp)
}
// VerifyWithContext the same as Verify but with a custom context.
func (c *SSHHelper) VerifyWithContext(ctx context.Context, otp string) (*SSHVerifyResponse, error) {
ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
defer cancelFunc()
2015-08-12 13:48:58 -04:00
data := map[string]interface{}{
"otp": otp,
}
verifyPath := fmt.Sprintf("/v1/%s/verify", c.MountPoint)
r := c.c.NewRequest(http.MethodPut, verifyPath)
2015-08-12 13:48:58 -04:00
if err := r.SetJSONBody(data); err != nil {
return nil, err
}
resp, err := c.c.rawRequestWithContext(ctx, r)
2015-08-12 13:48:58 -04:00
if err != nil {
return nil, err
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret.Data == nil {
return nil, nil
}
var verifyResp SSHVerifyResponse
err = mapstructure.Decode(secret.Data, &verifyResp)
if err != nil {
return nil, err
}
return &verifyResp, nil
}