mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			277 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			277 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) HashiCorp, Inc.
 | |
| // SPDX-License-Identifier: MPL-2.0
 | |
| 
 | |
| package api
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/tls"
 | |
| 	"crypto/x509"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 
 | |
| 	"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"
 | |
| 	"github.com/hashicorp/hcl/hcl/ast"
 | |
| 	"github.com/mitchellh/mapstructure"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// SSHHelperDefaultMountPoint is the default path at which SSH backend will be
 | |
| 	// mounted in the Vault server.
 | |
| 	SSHHelperDefaultMountPoint = "ssh"
 | |
| 
 | |
| 	// VerifyEchoRequest is the echo request message sent as OTP by the helper.
 | |
| 	VerifyEchoRequest = "verify-echo-request"
 | |
| 
 | |
| 	// VerifyEchoResponse is the echo response message sent as a response to OTP
 | |
| 	// matching echo request.
 | |
| 	VerifyEchoResponse = "verify-echo-response"
 | |
| )
 | |
| 
 | |
| // SSHHelper is a structure representing a vault-ssh-helper which can talk to vault server
 | |
| // in order to verify the OTP entered by the user. It contains the path at which
 | |
| // SSH backend is mounted at the server.
 | |
| type SSHHelper struct {
 | |
| 	c          *Client
 | |
| 	MountPoint string
 | |
| }
 | |
| 
 | |
| // SSHVerifyResponse is a structure representing the fields in Vault server's
 | |
| // 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"`
 | |
| }
 | |
| 
 | |
| // SSHHelperConfig is a structure which represents the entries from the vault-ssh-helper's configuration file.
 | |
| 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"`
 | |
| }
 | |
| 
 | |
| // SetTLSParameters sets the TLS parameters for this SSH agent.
 | |
| 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
 | |
| }
 | |
| 
 | |
| // 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.
 | |
| 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
 | |
| }
 | |
| 
 | |
| // 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".
 | |
| func LoadSSHHelperConfig(path string) (*SSHHelperConfig, error) {
 | |
| 	contents, err := ioutil.ReadFile(path)
 | |
| 	if err != nil && !os.IsNotExist(err) {
 | |
| 		return nil, multierror.Prefix(err, "ssh_helper:")
 | |
| 	}
 | |
| 	return ParseSSHHelperConfig(string(contents))
 | |
| }
 | |
| 
 | |
| // ParseSSHHelperConfig parses the given contents as a string for the SSHHelper
 | |
| // configuration.
 | |
| func ParseSSHHelperConfig(contents string) (*SSHHelperConfig, error) {
 | |
| 	root, err := hcl.Parse(string(contents))
 | |
| 	if err != nil {
 | |
| 		return nil, errwrap.Wrapf("error parsing config: {{err}}", err)
 | |
| 	}
 | |
| 
 | |
| 	list, ok := root.Node.(*ast.ObjectList)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("error parsing config: file doesn't contain a root object")
 | |
| 	}
 | |
| 
 | |
| 	valid := []string{
 | |
| 		"vault_addr",
 | |
| 		"ssh_mount_point",
 | |
| 		"namespace",
 | |
| 		"ca_cert",
 | |
| 		"ca_path",
 | |
| 		"allowed_cidr_list",
 | |
| 		"allowed_roles",
 | |
| 		"tls_skip_verify",
 | |
| 		"tls_server_name",
 | |
| 	}
 | |
| 	if err := CheckHCLKeys(list, valid); err != nil {
 | |
| 		return nil, multierror.Prefix(err, "ssh_helper:")
 | |
| 	}
 | |
| 
 | |
| 	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"`)
 | |
| 	}
 | |
| 	return &c, nil
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| // SSHHelper creates an SSHHelper object which can talk to Vault server with SSH backend
 | |
| // mounted at default path ("ssh").
 | |
| func (c *Client) SSHHelper() *SSHHelper {
 | |
| 	return c.SSHHelperWithMountPoint(SSHHelperDefaultMountPoint)
 | |
| }
 | |
| 
 | |
| // SSHHelperWithMountPoint creates an SSHHelper object which can talk to Vault server with SSH backend
 | |
| // mounted at a specific mount point.
 | |
| func (c *Client) SSHHelperWithMountPoint(mountPoint string) *SSHHelper {
 | |
| 	return &SSHHelper{
 | |
| 		c:          c,
 | |
| 		MountPoint: mountPoint,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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,
 | |
| // an echo response message is returned. This feature is used by ssh-helper to verify if
 | |
| // its configured correctly.
 | |
| 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()
 | |
| 
 | |
| 	data := map[string]interface{}{
 | |
| 		"otp": otp,
 | |
| 	}
 | |
| 	verifyPath := fmt.Sprintf("/v1/%s/verify", c.MountPoint)
 | |
| 	r := c.c.NewRequest(http.MethodPut, verifyPath)
 | |
| 	if err := r.SetJSONBody(data); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	resp, err := c.c.rawRequestWithContext(ctx, r)
 | |
| 	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
 | |
| }
 | 
