1// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
2
3package common
4
5import (
6	"crypto/rsa"
7	"errors"
8	"fmt"
9	"io/ioutil"
10	"os"
11	"path"
12	"regexp"
13	"strings"
14)
15
16// ConfigurationProvider wraps information about the account owner
17type ConfigurationProvider interface {
18	KeyProvider
19	TenancyOCID() (string, error)
20	UserOCID() (string, error)
21	KeyFingerprint() (string, error)
22	Region() (string, error)
23}
24
25// IsConfigurationProviderValid Tests all parts of the configuration provider do not return an error
26func IsConfigurationProviderValid(conf ConfigurationProvider) (ok bool, err error) {
27	baseFn := []func() (string, error){conf.TenancyOCID, conf.UserOCID, conf.KeyFingerprint, conf.Region, conf.KeyID}
28	for _, fn := range baseFn {
29		_, err = fn()
30		ok = err == nil
31		if err != nil {
32			return
33		}
34	}
35
36	_, err = conf.PrivateRSAKey()
37	ok = err == nil
38	if err != nil {
39		return
40	}
41	return true, nil
42}
43
44// rawConfigurationProvider allows a user to simply construct a configuration provider from raw values.
45type rawConfigurationProvider struct {
46	tenancy              string
47	user                 string
48	region               string
49	fingerprint          string
50	privateKey           string
51	privateKeyPassphrase *string
52}
53
54// NewRawConfigurationProvider will create a ConfigurationProvider with the arguments of the function
55func NewRawConfigurationProvider(tenancy, user, region, fingerprint, privateKey string, privateKeyPassphrase *string) ConfigurationProvider {
56	return rawConfigurationProvider{tenancy, user, region, fingerprint, privateKey, privateKeyPassphrase}
57}
58
59func (p rawConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
60	return PrivateKeyFromBytes([]byte(p.privateKey), p.privateKeyPassphrase)
61}
62
63func (p rawConfigurationProvider) KeyID() (keyID string, err error) {
64	tenancy, err := p.TenancyOCID()
65	if err != nil {
66		return
67	}
68
69	user, err := p.UserOCID()
70	if err != nil {
71		return
72	}
73
74	fingerprint, err := p.KeyFingerprint()
75	if err != nil {
76		return
77	}
78
79	return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
80}
81
82func (p rawConfigurationProvider) TenancyOCID() (string, error) {
83	if p.tenancy == "" {
84		return "", fmt.Errorf("tenancy OCID can not be empty")
85	}
86	return p.tenancy, nil
87}
88
89func (p rawConfigurationProvider) UserOCID() (string, error) {
90	if p.user == "" {
91		return "", fmt.Errorf("user OCID can not be empty")
92	}
93	return p.user, nil
94}
95
96func (p rawConfigurationProvider) KeyFingerprint() (string, error) {
97	if p.fingerprint == "" {
98		return "", fmt.Errorf("fingerprint can not be empty")
99	}
100	return p.fingerprint, nil
101}
102
103func (p rawConfigurationProvider) Region() (string, error) {
104	return canStringBeRegion(p.region)
105}
106
107// environmentConfigurationProvider reads configuration from environment variables
108type environmentConfigurationProvider struct {
109	PrivateKeyPassword        string
110	EnvironmentVariablePrefix string
111}
112
113// ConfigurationProviderEnvironmentVariables creates a ConfigurationProvider from a uniform set of environment variables starting with a prefix
114// The env variables should look like: [prefix]_private_key_path, [prefix]_tenancy_ocid, [prefix]_user_ocid, [prefix]_fingerprint
115// [prefix]_region
116func ConfigurationProviderEnvironmentVariables(environmentVariablePrefix, privateKeyPassword string) ConfigurationProvider {
117	return environmentConfigurationProvider{EnvironmentVariablePrefix: environmentVariablePrefix,
118		PrivateKeyPassword: privateKeyPassword}
119}
120
121func (p environmentConfigurationProvider) String() string {
122	return fmt.Sprintf("Configuration provided by environment variables prefixed with: %s", p.EnvironmentVariablePrefix)
123}
124
125func (p environmentConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
126	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "private_key_path")
127	var ok bool
128	var value string
129	if value, ok = os.LookupEnv(environmentVariable); !ok {
130		return nil, fmt.Errorf("can not read PrivateKey from env variable: %s", environmentVariable)
131	}
132
133	expandedPath := expandPath(value)
134	pemFileContent, err := ioutil.ReadFile(expandedPath)
135	if err != nil {
136		Debugln("Can not read PrivateKey location from environment variable: " + environmentVariable)
137		return
138	}
139
140	key, err = PrivateKeyFromBytes(pemFileContent, &p.PrivateKeyPassword)
141	return
142}
143
144func (p environmentConfigurationProvider) KeyID() (keyID string, err error) {
145	ocid, err := p.TenancyOCID()
146	if err != nil {
147		return
148	}
149
150	userocid, err := p.UserOCID()
151	if err != nil {
152		return
153	}
154
155	fingerprint, err := p.KeyFingerprint()
156	if err != nil {
157		return
158	}
159
160	return fmt.Sprintf("%s/%s/%s", ocid, userocid, fingerprint), nil
161}
162
163func (p environmentConfigurationProvider) TenancyOCID() (value string, err error) {
164	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "tenancy_ocid")
165	var ok bool
166	if value, ok = os.LookupEnv(environmentVariable); !ok {
167		err = fmt.Errorf("can not read Tenancy from environment variable %s", environmentVariable)
168	}
169	return
170}
171
172func (p environmentConfigurationProvider) UserOCID() (value string, err error) {
173	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "user_ocid")
174	var ok bool
175	if value, ok = os.LookupEnv(environmentVariable); !ok {
176		err = fmt.Errorf("can not read user id from environment variable %s", environmentVariable)
177	}
178	return
179}
180
181func (p environmentConfigurationProvider) KeyFingerprint() (value string, err error) {
182	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "fingerprint")
183	var ok bool
184	if value, ok = os.LookupEnv(environmentVariable); !ok {
185		err = fmt.Errorf("can not read fingerprint from environment variable %s", environmentVariable)
186	}
187	return
188}
189
190func (p environmentConfigurationProvider) Region() (value string, err error) {
191	environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "region")
192	var ok bool
193	if value, ok = os.LookupEnv(environmentVariable); !ok {
194		err = fmt.Errorf("can not read region from environment variable %s", environmentVariable)
195		return value, err
196	}
197
198	return canStringBeRegion(value)
199}
200
201// fileConfigurationProvider. reads configuration information from a file
202type fileConfigurationProvider struct {
203	//The path to the configuration file
204	ConfigPath string
205
206	//The password for the private key
207	PrivateKeyPassword string
208
209	//The profile for the configuration
210	Profile string
211
212	//ConfigFileInfo
213	FileInfo *configFileInfo
214}
215
216// ConfigurationProviderFromFile creates a configuration provider from a configuration file
217// by reading the "DEFAULT" profile
218func ConfigurationProviderFromFile(configFilePath, privateKeyPassword string) (ConfigurationProvider, error) {
219	if configFilePath == "" {
220		return nil, fmt.Errorf("config file path can not be empty")
221	}
222
223	return fileConfigurationProvider{
224		ConfigPath:         configFilePath,
225		PrivateKeyPassword: privateKeyPassword,
226		Profile:            "DEFAULT"}, nil
227}
228
229// ConfigurationProviderFromFileWithProfile creates a configuration provider from a configuration file
230// and the given profile
231func ConfigurationProviderFromFileWithProfile(configFilePath, profile, privateKeyPassword string) (ConfigurationProvider, error) {
232	if configFilePath == "" {
233		return nil, fmt.Errorf("config file path can not be empty")
234	}
235
236	return fileConfigurationProvider{
237		ConfigPath:         configFilePath,
238		PrivateKeyPassword: privateKeyPassword,
239		Profile:            profile}, nil
240}
241
242type configFileInfo struct {
243	UserOcid, Fingerprint, KeyFilePath, TenancyOcid, Region, Passphrase string
244	PresentConfiguration                                                byte
245}
246
247const (
248	hasTenancy = 1 << iota
249	hasUser
250	hasFingerprint
251	hasRegion
252	hasKeyFile
253	hasPassphrase
254	none
255)
256
257var profileRegex = regexp.MustCompile(`^\[(.*)\]`)
258
259func parseConfigFile(data []byte, profile string) (info *configFileInfo, err error) {
260
261	if len(data) == 0 {
262		return nil, fmt.Errorf("configuration file content is empty")
263	}
264
265	content := string(data)
266	splitContent := strings.Split(content, "\n")
267
268	//Look for profile
269	for i, line := range splitContent {
270		if match := profileRegex.FindStringSubmatch(line); match != nil && len(match) > 1 && match[1] == profile {
271			start := i + 1
272			return parseConfigAtLine(start, splitContent)
273		}
274	}
275
276	return nil, fmt.Errorf("configuration file did not contain profile: %s", profile)
277}
278
279func parseConfigAtLine(start int, content []string) (info *configFileInfo, err error) {
280	var configurationPresent byte
281	info = &configFileInfo{}
282	for i := start; i < len(content); i++ {
283		line := content[i]
284		if profileRegex.MatchString(line) {
285			break
286		}
287
288		if !strings.Contains(line, "=") {
289			continue
290		}
291
292		splits := strings.Split(line, "=")
293		switch key, value := strings.TrimSpace(splits[0]), strings.TrimSpace(splits[1]); strings.ToLower(key) {
294		case "passphrase", "pass_phrase":
295			configurationPresent = configurationPresent | hasPassphrase
296			info.Passphrase = value
297		case "user":
298			configurationPresent = configurationPresent | hasUser
299			info.UserOcid = value
300		case "fingerprint":
301			configurationPresent = configurationPresent | hasFingerprint
302			info.Fingerprint = value
303		case "key_file":
304			configurationPresent = configurationPresent | hasKeyFile
305			info.KeyFilePath = value
306		case "tenancy":
307			configurationPresent = configurationPresent | hasTenancy
308			info.TenancyOcid = value
309		case "region":
310			configurationPresent = configurationPresent | hasRegion
311			info.Region = value
312		}
313	}
314	info.PresentConfiguration = configurationPresent
315	return
316
317}
318
319// cleans and expands the path if it contains a tilde , returns the expanded path or the input path as is if not expansion
320// was performed
321func expandPath(filepath string) (expandedPath string) {
322	cleanedPath := path.Clean(filepath)
323	expandedPath = cleanedPath
324	if strings.HasPrefix(cleanedPath, "~") {
325		rest := cleanedPath[2:]
326		expandedPath = path.Join(getHomeFolder(), rest)
327	}
328	return
329}
330
331func openConfigFile(configFilePath string) (data []byte, err error) {
332	expandedPath := expandPath(configFilePath)
333	data, err = ioutil.ReadFile(expandedPath)
334	if err != nil {
335		err = fmt.Errorf("can not read config file: %s due to: %s", configFilePath, err.Error())
336	}
337
338	return
339}
340
341func (p fileConfigurationProvider) String() string {
342	return fmt.Sprintf("Configuration provided by file: %s", p.ConfigPath)
343}
344
345func (p fileConfigurationProvider) readAndParseConfigFile() (info *configFileInfo, err error) {
346	if p.FileInfo != nil {
347		return p.FileInfo, nil
348	}
349
350	if p.ConfigPath == "" {
351		return nil, fmt.Errorf("configuration path can not be empty")
352	}
353
354	data, err := openConfigFile(p.ConfigPath)
355	if err != nil {
356		err = fmt.Errorf("error while parsing config file: %s. Due to: %s", p.ConfigPath, err.Error())
357		return
358	}
359
360	p.FileInfo, err = parseConfigFile(data, p.Profile)
361	return p.FileInfo, err
362}
363
364func presentOrError(value string, expectedConf, presentConf byte, confMissing string) (string, error) {
365	if presentConf&expectedConf == expectedConf {
366		return value, nil
367	}
368	return "", errors.New(confMissing + " configuration is missing from file")
369}
370
371func (p fileConfigurationProvider) TenancyOCID() (value string, err error) {
372	info, err := p.readAndParseConfigFile()
373	if err != nil {
374		err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
375		return
376	}
377
378	value, err = presentOrError(info.TenancyOcid, hasTenancy, info.PresentConfiguration, "tenancy")
379	return
380}
381
382func (p fileConfigurationProvider) UserOCID() (value string, err error) {
383	info, err := p.readAndParseConfigFile()
384	if err != nil {
385		err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
386		return
387	}
388
389	value, err = presentOrError(info.UserOcid, hasUser, info.PresentConfiguration, "user")
390	return
391}
392
393func (p fileConfigurationProvider) KeyFingerprint() (value string, err error) {
394	info, err := p.readAndParseConfigFile()
395	if err != nil {
396		err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
397		return
398	}
399	value, err = presentOrError(info.Fingerprint, hasFingerprint, info.PresentConfiguration, "fingerprint")
400	return
401}
402
403func (p fileConfigurationProvider) KeyID() (keyID string, err error) {
404	info, err := p.readAndParseConfigFile()
405	if err != nil {
406		err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
407		return
408	}
409
410	return fmt.Sprintf("%s/%s/%s", info.TenancyOcid, info.UserOcid, info.Fingerprint), nil
411}
412
413func (p fileConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
414	info, err := p.readAndParseConfigFile()
415	if err != nil {
416		err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
417		return
418	}
419
420	filePath, err := presentOrError(info.KeyFilePath, hasKeyFile, info.PresentConfiguration, "key file path")
421	if err != nil {
422		return
423	}
424
425	expandedPath := expandPath(filePath)
426	pemFileContent, err := ioutil.ReadFile(expandedPath)
427	if err != nil {
428		err = fmt.Errorf("can not read PrivateKey  from configuration file due to: %s", err.Error())
429		return
430	}
431
432	password := p.PrivateKeyPassword
433
434	if password == "" && ((info.PresentConfiguration & hasPassphrase) == hasPassphrase) {
435		password = info.Passphrase
436	}
437
438	key, err = PrivateKeyFromBytes(pemFileContent, &password)
439	return
440}
441
442func (p fileConfigurationProvider) Region() (value string, err error) {
443	info, err := p.readAndParseConfigFile()
444	if err != nil {
445		err = fmt.Errorf("can not read region configuration due to: %s", err.Error())
446		return
447	}
448
449	value, err = presentOrError(info.Region, hasRegion, info.PresentConfiguration, "region")
450	if err != nil {
451		return
452	}
453
454	return canStringBeRegion(value)
455}
456
457// A configuration provider that look for information in  multiple configuration providers
458type composingConfigurationProvider struct {
459	Providers []ConfigurationProvider
460}
461
462// ComposingConfigurationProvider creates a composing configuration provider with the given slice of configuration providers
463// A composing provider will return the configuration of the first provider that has the required property
464// if no provider has the property it will return an error.
465func ComposingConfigurationProvider(providers []ConfigurationProvider) (ConfigurationProvider, error) {
466	if len(providers) == 0 {
467		return nil, fmt.Errorf("providers can not be an empty slice")
468	}
469
470	for i, p := range providers {
471		if p == nil {
472			return nil, fmt.Errorf("provider in position: %d is nil. ComposingConfiurationProvider does not support nil values", i)
473		}
474	}
475	return composingConfigurationProvider{Providers: providers}, nil
476}
477
478func (c composingConfigurationProvider) TenancyOCID() (string, error) {
479	for _, p := range c.Providers {
480		val, err := p.TenancyOCID()
481		if err == nil {
482			return val, nil
483		}
484	}
485	return "", fmt.Errorf("did not find a proper configuration for tenancy")
486}
487
488func (c composingConfigurationProvider) UserOCID() (string, error) {
489	for _, p := range c.Providers {
490		val, err := p.UserOCID()
491		if err == nil {
492			return val, nil
493		}
494	}
495	return "", fmt.Errorf("did not find a proper configuration for user")
496}
497
498func (c composingConfigurationProvider) KeyFingerprint() (string, error) {
499	for _, p := range c.Providers {
500		val, err := p.KeyFingerprint()
501		if err == nil {
502			return val, nil
503		}
504	}
505	return "", fmt.Errorf("did not find a proper configuration for keyFingerprint")
506}
507func (c composingConfigurationProvider) Region() (string, error) {
508	for _, p := range c.Providers {
509		val, err := p.Region()
510		if err == nil {
511			return val, nil
512		}
513	}
514	return "", fmt.Errorf("did not find a proper configuration for region")
515}
516
517func (c composingConfigurationProvider) KeyID() (string, error) {
518	for _, p := range c.Providers {
519		val, err := p.KeyID()
520		if err == nil {
521			return val, nil
522		}
523	}
524	return "", fmt.Errorf("did not find a proper configuration for key id")
525}
526
527func (c composingConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
528	for _, p := range c.Providers {
529		val, err := p.PrivateRSAKey()
530		if err == nil {
531			return val, nil
532		}
533	}
534	return nil, fmt.Errorf("did not find a proper configuration for private key")
535}
536