mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	 df79e2c0eb
			
		
	
	df79e2c0eb
	
	
	
		
			
			Update AWS auth method certificates Add tests that the `rsa2048` document can also be verified using the `pkcs7` field for AWS auth. Due to the use of SHA-1-based signatures for the `identity` and `pkcs7` methods, we want to encourage moving toward using the RSA 2048 workflow, https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-rsa2048.html This doesn't require code changes for Vault necessarily, but adding in the (many) certificates will help end users. Also adds `rsa2048` option to API to fetch the RSA 2048 signature. I will make a PR to update to the AWS auth docs to document the RSA 2048 flow soon after this.
		
			
				
	
	
		
			285 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			285 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package aws
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/base64"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/aws/aws-sdk-go/aws/credentials"
 | |
| 	"github.com/aws/aws-sdk-go/aws/ec2metadata"
 | |
| 	"github.com/aws/aws-sdk-go/aws/session"
 | |
| 	"github.com/hashicorp/go-hclog"
 | |
| 	"github.com/hashicorp/go-secure-stdlib/awsutil"
 | |
| 	"github.com/hashicorp/go-uuid"
 | |
| 	"github.com/hashicorp/vault/api"
 | |
| )
 | |
| 
 | |
| type AWSAuth struct {
 | |
| 	// If not provided with the WithRole login option, the Vault server will look for a role
 | |
| 	// with the friendly name of the IAM principal if using the IAM auth type,
 | |
| 	// or the name of the EC2 instance's AMI ID if using the EC2 auth type.
 | |
| 	// If no matching role is found, login will fail.
 | |
| 	roleName  string
 | |
| 	mountPath string
 | |
| 	// Can be "iam" or "ec2". Defaults to "iam".
 | |
| 	authType string
 | |
| 	// Can be "pkcs7", "identity", or "rsa2048". Defaults to "pkcs7".
 | |
| 	signatureType          string
 | |
| 	region                 string
 | |
| 	iamServerIDHeaderValue string
 | |
| 	creds                  *credentials.Credentials
 | |
| 	nonce                  string
 | |
| }
 | |
| 
 | |
| var _ api.AuthMethod = (*AWSAuth)(nil)
 | |
| 
 | |
| type LoginOption func(a *AWSAuth) error
 | |
| 
 | |
| const (
 | |
| 	iamType              = "iam"
 | |
| 	ec2Type              = "ec2"
 | |
| 	pkcs7Type            = "pkcs7"
 | |
| 	identityType         = "identity"
 | |
| 	rsa2048Type          = "rsa2048"
 | |
| 	defaultMountPath     = "aws"
 | |
| 	defaultAuthType      = iamType
 | |
| 	defaultRegion        = "us-east-1"
 | |
| 	defaultSignatureType = pkcs7Type
 | |
| )
 | |
| 
 | |
| // NewAWSAuth initializes a new AWS auth method interface to be
 | |
| // passed as a parameter to the client.Auth().Login method.
 | |
| //
 | |
| // Supported options: WithRole, WithMountPath, WithIAMAuth, WithEC2Auth,
 | |
| // WithPKCS7Signature, WithIdentitySignature, WithIAMServerIDHeader, WithNonce, WithRegion
 | |
| func NewAWSAuth(opts ...LoginOption) (*AWSAuth, error) {
 | |
| 	a := &AWSAuth{
 | |
| 		mountPath:     defaultMountPath,
 | |
| 		authType:      defaultAuthType,
 | |
| 		region:        defaultRegion,
 | |
| 		signatureType: defaultSignatureType,
 | |
| 	}
 | |
| 
 | |
| 	// Loop through each option
 | |
| 	for _, opt := range opts {
 | |
| 		// Call the option giving the instantiated
 | |
| 		// *AWSAuth as the argument
 | |
| 		err := opt(a)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("error with login option: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// return the modified auth struct instance
 | |
| 	return a, nil
 | |
| }
 | |
| 
 | |
| // Login sets up the required request body for the AWS auth method's /login
 | |
| // endpoint, and performs a write to it. This method defaults to the "iam"
 | |
| // auth type unless NewAWSAuth is called with WithEC2Auth().
 | |
| //
 | |
| // The Vault client will set its credentials to the values of the
 | |
| // AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION environment
 | |
| // variables. To specify a path to a credentials file on disk instead, set
 | |
| // the environment variable AWS_SHARED_CREDENTIALS_FILE.
 | |
| func (a *AWSAuth) Login(ctx context.Context, client *api.Client) (*api.Secret, error) {
 | |
| 	if ctx == nil {
 | |
| 		ctx = context.Background()
 | |
| 	}
 | |
| 
 | |
| 	loginData := make(map[string]interface{})
 | |
| 	switch a.authType {
 | |
| 	case ec2Type:
 | |
| 		sess, err := session.NewSession()
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("error creating session to probe EC2 metadata: %w", err)
 | |
| 		}
 | |
| 		metadataSvc := ec2metadata.New(sess)
 | |
| 		if !metadataSvc.Available() {
 | |
| 			return nil, fmt.Errorf("metadata service not available")
 | |
| 		}
 | |
| 
 | |
| 		if a.signatureType == pkcs7Type {
 | |
| 			// fetch PKCS #7 signature
 | |
| 			resp, err := metadataSvc.GetDynamicData("/instance-identity/pkcs7")
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("unable to get PKCS 7 data from metadata service: %w", err)
 | |
| 			}
 | |
| 			pkcs7 := strings.TrimSpace(resp)
 | |
| 			loginData["pkcs7"] = pkcs7
 | |
| 		} else if a.signatureType == identityType {
 | |
| 			// fetch signature from identity document
 | |
| 			doc, err := metadataSvc.GetDynamicData("/instance-identity/document")
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("error requesting instance identity doc: %w", err)
 | |
| 			}
 | |
| 			loginData["identity"] = base64.StdEncoding.EncodeToString([]byte(doc))
 | |
| 
 | |
| 			signature, err := metadataSvc.GetDynamicData("/instance-identity/signature")
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("error requesting signature: %w", err)
 | |
| 			}
 | |
| 			loginData["signature"] = signature
 | |
| 		} else if a.signatureType == rsa2048Type {
 | |
| 			// fetch RSA 2048 signature, which is also a PKCS#7 signature
 | |
| 			resp, err := metadataSvc.GetDynamicData("/instance-identity/rsa2048")
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("unable to get PKCS 7 data from metadata service: %w", err)
 | |
| 			}
 | |
| 			pkcs7 := strings.TrimSpace(resp)
 | |
| 			loginData["pkcs7"] = pkcs7
 | |
| 		} else {
 | |
| 			return nil, fmt.Errorf("unknown signature type: %s", a.signatureType)
 | |
| 		}
 | |
| 
 | |
| 		// Add the reauthentication value, if we have one
 | |
| 		if a.nonce == "" {
 | |
| 			uid, err := uuid.GenerateUUID()
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("error generating uuid for reauthentication value: %w", err)
 | |
| 			}
 | |
| 			a.nonce = uid
 | |
| 		}
 | |
| 		loginData["nonce"] = a.nonce
 | |
| 	case iamType:
 | |
| 		logger := hclog.Default()
 | |
| 		if a.creds == nil {
 | |
| 			credsConfig := awsutil.CredentialsConfig{
 | |
| 				AccessKey:    os.Getenv("AWS_ACCESS_KEY_ID"),
 | |
| 				SecretKey:    os.Getenv("AWS_SECRET_ACCESS_KEY"),
 | |
| 				SessionToken: os.Getenv("AWS_SESSION_TOKEN"),
 | |
| 				Logger:       logger,
 | |
| 			}
 | |
| 
 | |
| 			// the env vars above will take precedence if they are set, as
 | |
| 			// they will be added to the ChainProvider stack first
 | |
| 			var hasCredsFile bool
 | |
| 			credsFilePath := os.Getenv("AWS_SHARED_CREDENTIALS_FILE")
 | |
| 			if credsFilePath != "" {
 | |
| 				hasCredsFile = true
 | |
| 				credsConfig.Filename = credsFilePath
 | |
| 			}
 | |
| 
 | |
| 			creds, err := credsConfig.GenerateCredentialChain(awsutil.WithSharedCredentials(hasCredsFile))
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			if creds == nil {
 | |
| 				return nil, fmt.Errorf("could not compile valid credential providers from static config, environment, shared, or instance metadata")
 | |
| 			}
 | |
| 
 | |
| 			_, err = creds.Get()
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("failed to retrieve credentials from credential chain: %w", err)
 | |
| 			}
 | |
| 
 | |
| 			a.creds = creds
 | |
| 		}
 | |
| 
 | |
| 		data, err := awsutil.GenerateLoginData(a.creds, a.iamServerIDHeaderValue, a.region, logger)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("unable to generate login data for AWS auth endpoint: %w", err)
 | |
| 		}
 | |
| 		loginData = data
 | |
| 	}
 | |
| 
 | |
| 	// Add role if we have one. If not, Vault will infer the role name based
 | |
| 	// on the IAM friendly name (iam auth type) or EC2 instance's
 | |
| 	// AMI ID (ec2 auth type).
 | |
| 	if a.roleName != "" {
 | |
| 		loginData["role"] = a.roleName
 | |
| 	}
 | |
| 
 | |
| 	if a.iamServerIDHeaderValue != "" {
 | |
| 		client.AddHeader("iam_server_id_header_value", a.iamServerIDHeaderValue)
 | |
| 	}
 | |
| 
 | |
| 	path := fmt.Sprintf("auth/%s/login", a.mountPath)
 | |
| 	resp, err := client.Logical().WriteWithContext(ctx, path, loginData)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to log in with AWS auth: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func WithRole(roleName string) LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.roleName = roleName
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func WithMountPath(mountPath string) LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.mountPath = mountPath
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func WithEC2Auth() LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.authType = ec2Type
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func WithIAMAuth() LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.authType = iamType
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // WithIdentitySignature will have the client send the cryptographic identity
 | |
| // document signature to verify EC2 auth logins. Only used by EC2 auth type.
 | |
| // If this option is not provided, will default to using the PKCS #7 signature.
 | |
| // The signature type used should match the type of the public AWS cert Vault
 | |
| // has been configured with to verify EC2 instance identity.
 | |
| // https://www.vaultproject.io/api/auth/aws#create-certificate-configuration
 | |
| func WithIdentitySignature() LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.signatureType = identityType
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // WithPKCS7Signature will explicitly tell the client to send the PKCS #7
 | |
| // signature to verify EC2 auth logins. Only used by EC2 auth type.
 | |
| // PKCS #7 is the default, but this method is provided for additional clarity.
 | |
| // The signature type used should match the type of the public AWS cert Vault
 | |
| // has been configured with to verify EC2 instance identity.
 | |
| // https://www.vaultproject.io/api/auth/aws#create-certificate-configuration
 | |
| func WithPKCS7Signature() LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.signatureType = pkcs7Type
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func WithIAMServerIDHeader(headerValue string) LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.iamServerIDHeaderValue = headerValue
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // WithNonce can be used to specify a named nonce for the ec2 auth login
 | |
| // method. If not provided, an automatically-generated uuid will be used
 | |
| // instead.
 | |
| func WithNonce(nonce string) LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.nonce = nonce
 | |
| 		return nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func WithRegion(region string) LoginOption {
 | |
| 	return func(a *AWSAuth) error {
 | |
| 		a.region = region
 | |
| 		return nil
 | |
| 	}
 | |
| }
 |