forked from opentofu/opentofu
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
148 lines (119 loc) · 4.62 KB
/
config.go
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package gcp_kms
import (
"context"
"encoding/json"
"os"
"github.com/mitchellh/go-homedir"
"github.com/adrien-f/opentofu-opened/pkg/encryption/keyprovider"
"github.com/adrien-f/opentofu-opened/pkg/httpclient"
"github.com/adrien-f/opentofu-opened/version"
"golang.org/x/oauth2"
"google.golang.org/api/impersonate"
"google.golang.org/api/option"
kms "cloud.google.com/go/kms/apiv1"
)
type keyManagementClientInit func(ctx context.Context, opts ...option.ClientOption) (keyManagementClient, error)
// Can be overridden for test mocking
var newKeyManagementClient keyManagementClientInit = func(ctx context.Context, opts ...option.ClientOption) (keyManagementClient, error) {
return kms.NewKeyManagementClient(ctx, opts...)
}
type Config struct {
Credentials string `hcl:"credentials,optional"`
AccessToken string `hcl:"access_token,optional"`
ImpersonateServiceAccount string `hcl:"impersonate_service_account,optional"`
ImpersonateServiceAccountDelegates []string `hcl:"impersonate_service_account_delegates,optional"`
KMSKeyName string `hcl:"kms_encryption_key"`
KeyLength int `hcl:"key_length"`
}
func stringAttrEnvFallback(val string, env string) string {
if val != "" {
return val
}
return os.Getenv(env)
}
// TODO This is copied in from the backend packge to prevent a circular dependency loop
// If the argument is a path, ReadPathOrContents loads it and returns the contents,
// otherwise the argument is assumed to be the desired contents and is simply
// returned.
func ReadPathOrContents(poc string) (string, error) {
if len(poc) == 0 {
return poc, nil
}
path := poc
if path[0] == '~' {
var err error
path, err = homedir.Expand(path)
if err != nil {
return path, err
}
}
if _, err := os.Stat(path); err == nil {
contents, err := os.ReadFile(path)
if err != nil {
return string(contents), err
}
return string(contents), nil
}
return poc, nil
}
func (c Config) Build() (keyprovider.KeyProvider, keyprovider.KeyMeta, error) {
// This mirrors the gcp remote state backend
// Apply env defaults if nessesary
c.Credentials = stringAttrEnvFallback(c.Credentials, "GOOGLE_CREDENTIALS")
c.AccessToken = stringAttrEnvFallback(c.AccessToken, "GOOGLE_OAUTH_ACCESS_TOKEN")
c.ImpersonateServiceAccount = stringAttrEnvFallback(c.ImpersonateServiceAccount, "GOOGLE_BACKEND_IMPERSONATE_SERVICE_ACCOUNT")
c.ImpersonateServiceAccount = stringAttrEnvFallback(c.ImpersonateServiceAccount, "GOOGLE_IMPERSONATE_SERVICE_ACCOUNT")
ctx := context.Background()
var opts []option.ClientOption
var credOptions []option.ClientOption
if c.AccessToken != "" {
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{
AccessToken: c.AccessToken,
})
credOptions = append(credOptions, option.WithTokenSource(tokenSource))
} else if c.Credentials != "" {
// to mirror how the provider works, we accept the file path or the contents
contents, err := ReadPathOrContents(c.Credentials)
if err != nil {
return nil, nil, &keyprovider.ErrInvalidConfiguration{Message: "Error loading credentials", Cause: err}
}
if !json.Valid([]byte(contents)) {
return nil, nil, &keyprovider.ErrInvalidConfiguration{Message: "the string provided in credentials is neither valid json nor a valid file path"}
}
credOptions = append(credOptions, option.WithCredentialsJSON([]byte(contents)))
}
// Service Account Impersonation
if c.ImpersonateServiceAccount != "" {
ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
TargetPrincipal: c.ImpersonateServiceAccount,
Scopes: []string{"https://www.googleapis.com/auth/cloudkms"}, // I can't find a smaller scope than this...
Delegates: c.ImpersonateServiceAccountDelegates,
}, credOptions...)
if err != nil {
return nil, nil, &keyprovider.ErrInvalidConfiguration{Cause: err}
}
opts = append(opts, option.WithTokenSource(ts))
} else {
opts = append(opts, credOptions...)
}
opts = append(opts, option.WithUserAgent(httpclient.OpenTofuUserAgent(version.Version)))
svc, err := newKeyManagementClient(ctx, opts...)
if err != nil {
return nil, nil, &keyprovider.ErrInvalidConfiguration{Cause: err}
}
if c.KMSKeyName == "" {
return nil, nil, &keyprovider.ErrInvalidConfiguration{Message: "kms_key_name must be provided"}
}
if c.KeyLength < 1 {
return nil, nil, &keyprovider.ErrInvalidConfiguration{Message: "key_length must be at least 1"}
}
if c.KeyLength > 1024 {
return nil, nil, &keyprovider.ErrInvalidConfiguration{Message: "key_length must be less than the GCP limit of 1024"}
}
return &keyProvider{
svc: svc,
ctx: ctx,
keyName: c.KMSKeyName,
keyLength: c.KeyLength,
}, new(keyMeta), nil
}