summaryrefslogtreecommitdiff
path: root/internal/datafs/vaultauth.go
blob: 83b9daa57e6afe588b38706bf49396c6173084b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package datafs

import (
	"context"
	"fmt"
	"io/fs"
	"os"

	"github.com/hairyhenderson/go-fsimpl/vaultfs/vaultauth"
	"github.com/hairyhenderson/gomplate/v4/internal/deprecated"
	"github.com/hairyhenderson/gomplate/v4/internal/iohelpers"
	"github.com/hashicorp/vault/api"
	"github.com/hashicorp/vault/api/auth/aws"
)

// compositeVaultAuthMethod configures the auth method based on environment
// variables. It extends [vaultfs.EnvAuthMethod] by falling back to AWS EC2
// authentication if the other methods fail.
func compositeVaultAuthMethod(envFsys fs.FS) api.AuthMethod {
	return vaultauth.CompositeAuthMethod(
		vaultauth.EnvAuthMethod(),
		envEC2AuthAdapter(envFsys),
		envIAMAuthAdapter(envFsys),
	)
}

// envEC2AuthAdapter builds an AWS EC2 authentication method from environment
// variables, for use only with [compositeVaultAuthMethod]
func envEC2AuthAdapter(envFS fs.FS) api.AuthMethod {
	mountPath := GetenvFsys(envFS, "VAULT_AUTH_AWS_MOUNT", "aws")

	nonce := GetenvFsys(envFS, "VAULT_AUTH_AWS_NONCE")
	role := GetenvFsys(envFS, "VAULT_AUTH_AWS_ROLE")

	// temporary workaround while we wait to deprecate AWS_META_ENDPOINT
	if endpoint := os.Getenv("AWS_META_ENDPOINT"); endpoint != "" {
		deprecated.WarnDeprecated(context.Background(), "Use AWS_EC2_METADATA_SERVICE_ENDPOINT instead of AWS_META_ENDPOINT")
		if os.Getenv("AWS_EC2_METADATA_SERVICE_ENDPOINT") == "" {
			os.Setenv("AWS_EC2_METADATA_SERVICE_ENDPOINT", endpoint)
		}
	}

	awsauth, err := aws.NewAWSAuth(
		aws.WithEC2Auth(),
		aws.WithMountPath(mountPath),
		aws.WithNonce(nonce),
		aws.WithRole(role),
	)
	if err != nil {
		return nil
	}

	output := GetenvFsys(envFS, "VAULT_AUTH_AWS_NONCE_OUTPUT")
	if output == "" {
		return awsauth
	}

	return &ec2AuthNonceWriter{AWSAuth: awsauth, nonce: nonce, output: output}
}

// envIAMAuthAdapter builds an AWS IAM authentication method from environment
// variables, for use only with [compositeVaultAuthMethod]
func envIAMAuthAdapter(envFS fs.FS) api.AuthMethod {
	mountPath := GetenvFsys(envFS, "VAULT_AUTH_AWS_MOUNT", "aws")
	role := GetenvFsys(envFS, "VAULT_AUTH_AWS_ROLE")

	// temporary workaround while we wait to deprecate AWS_META_ENDPOINT
	if endpoint := os.Getenv("AWS_META_ENDPOINT"); endpoint != "" {
		deprecated.WarnDeprecated(context.Background(), "Use AWS_EC2_METADATA_SERVICE_ENDPOINT instead of AWS_META_ENDPOINT")
		if os.Getenv("AWS_EC2_METADATA_SERVICE_ENDPOINT") == "" {
			os.Setenv("AWS_EC2_METADATA_SERVICE_ENDPOINT", endpoint)
		}
	}

	awsauth, err := aws.NewAWSAuth(
		aws.WithIAMAuth(),
		aws.WithMountPath(mountPath),
		aws.WithRole(role),
	)
	if err != nil {
		return nil
	}

	return awsauth
}

// ec2AuthNonceWriter - wraps an AWSAuth, and writes the nonce to the nonce
// output file - only for ec2 auth
type ec2AuthNonceWriter struct {
	*aws.AWSAuth
	nonce  string
	output string
}

func (a *ec2AuthNonceWriter) Login(ctx context.Context, client *api.Client) (*api.Secret, error) {
	secret, err := a.AWSAuth.Login(ctx, client)
	if err != nil {
		return nil, err
	}

	nonce := a.nonce
	if val, ok := secret.Auth.Metadata["nonce"]; ok {
		nonce = val
	}

	err = os.WriteFile(a.output, []byte(nonce+"\n"), iohelpers.NormalizeFileMode(0o600))
	if err != nil {
		return nil, fmt.Errorf("error writing nonce output file: %w", err)
	}

	return secret, nil
}