github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/cloud/credentials_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloud_test
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  
    12  	"github.com/juju/errors"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils/v3"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/cloud"
    18  	"github.com/juju/juju/testing"
    19  )
    20  
    21  type credentialsSuite struct {
    22  	testing.FakeJujuXDGDataHomeSuite
    23  }
    24  
    25  var _ = gc.Suite(&credentialsSuite{})
    26  
    27  func (s *credentialsSuite) TestMarshalAccessKey(c *gc.C) {
    28  	creds := map[string]cloud.CloudCredential{
    29  		"aws": {
    30  			DefaultCredential: "default-cred",
    31  			DefaultRegion:     "us-west-2",
    32  			AuthCredentials: map[string]cloud.Credential{
    33  				"peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
    34  					"access-key": "key",
    35  					"secret-key": "secret",
    36  				}),
    37  				// TODO(wallyworld) - add anther credential once goyaml.v2 supports inline MapSlice.
    38  				//"paul": &cloud.AccessKeyCredentials{
    39  				//	Key: "paulkey",
    40  				//	Secret: "paulsecret",
    41  				//},
    42  			},
    43  		},
    44  	}
    45  	out, err := cloud.MarshalCredentials(creds)
    46  	c.Assert(err, jc.ErrorIsNil)
    47  	c.Assert(string(out), gc.Equals, `
    48  credentials:
    49    aws:
    50      default-credential: default-cred
    51      default-region: us-west-2
    52      peter:
    53        auth-type: access-key
    54        access-key: key
    55        secret-key: secret
    56  `[1:])
    57  }
    58  
    59  func (s *credentialsSuite) TestMarshalOpenstackAccessKey(c *gc.C) {
    60  	creds := map[string]cloud.CloudCredential{
    61  		"openstack": {
    62  			DefaultCredential: "default-cred",
    63  			DefaultRegion:     "region-a",
    64  			AuthCredentials: map[string]cloud.Credential{
    65  				"peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
    66  					"access-key":  "key",
    67  					"secret-key":  "secret",
    68  					"tenant-name": "tenant",
    69  				}),
    70  			},
    71  		},
    72  	}
    73  	out, err := cloud.MarshalCredentials(creds)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	c.Assert(string(out), gc.Equals, `
    76  credentials:
    77    openstack:
    78      default-credential: default-cred
    79      default-region: region-a
    80      peter:
    81        auth-type: access-key
    82        access-key: key
    83        secret-key: secret
    84        tenant-name: tenant
    85  `[1:])
    86  }
    87  
    88  func (s *credentialsSuite) TestMarshalOpenstackUserPass(c *gc.C) {
    89  	creds := map[string]cloud.CloudCredential{
    90  		"openstack": {
    91  			DefaultCredential: "default-cred",
    92  			DefaultRegion:     "region-a",
    93  			AuthCredentials: map[string]cloud.Credential{
    94  				"peter": cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
    95  					"username":    "user",
    96  					"password":    "secret",
    97  					"tenant-name": "tenant",
    98  				}),
    99  			},
   100  		},
   101  	}
   102  	out, err := cloud.MarshalCredentials(creds)
   103  	c.Assert(err, jc.ErrorIsNil)
   104  	c.Assert(string(out), gc.Equals, `
   105  credentials:
   106    openstack:
   107      default-credential: default-cred
   108      default-region: region-a
   109      peter:
   110        auth-type: userpass
   111        password: secret
   112        tenant-name: tenant
   113        username: user
   114  `[1:])
   115  }
   116  
   117  func (s *credentialsSuite) TestMarshalAzureCredntials(c *gc.C) {
   118  	creds := map[string]cloud.CloudCredential{
   119  		"azure": {
   120  			DefaultCredential: "default-cred",
   121  			DefaultRegion:     "Central US",
   122  			AuthCredentials: map[string]cloud.Credential{
   123  				"peter": cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   124  					"application-id":       "app-id",
   125  					"application-password": "app-secret",
   126  					"subscription-id":      "subscription-id",
   127  					"tenant-id":            "tenant-id",
   128  				}),
   129  			},
   130  		},
   131  	}
   132  	out, err := cloud.MarshalCredentials(creds)
   133  	c.Assert(err, jc.ErrorIsNil)
   134  	c.Assert(string(out), gc.Equals, `
   135  credentials:
   136    azure:
   137      default-credential: default-cred
   138      default-region: Central US
   139      peter:
   140        auth-type: userpass
   141        application-id: app-id
   142        application-password: app-secret
   143        subscription-id: subscription-id
   144        tenant-id: tenant-id
   145  `[1:])
   146  }
   147  
   148  func (s *credentialsSuite) TestMarshalOAuth1(c *gc.C) {
   149  	creds := map[string]cloud.CloudCredential{
   150  		"maas": {
   151  			DefaultCredential: "default-cred",
   152  			DefaultRegion:     "region-default",
   153  			AuthCredentials: map[string]cloud.Credential{
   154  				"peter": cloud.NewCredential(cloud.OAuth1AuthType, map[string]string{
   155  					"consumer-key":    "consumer-key",
   156  					"consumer-secret": "consumer-secret",
   157  					"access-token":    "access-token",
   158  					"token-secret":    "token-secret",
   159  				}),
   160  			},
   161  		},
   162  	}
   163  	out, err := cloud.MarshalCredentials(creds)
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	c.Assert(string(out), gc.Equals, `
   166  credentials:
   167    maas:
   168      default-credential: default-cred
   169      default-region: region-default
   170      peter:
   171        auth-type: oauth1
   172        access-token: access-token
   173        consumer-key: consumer-key
   174        consumer-secret: consumer-secret
   175        token-secret: token-secret
   176  `[1:])
   177  }
   178  
   179  func (s *credentialsSuite) TestMarshalOAuth2(c *gc.C) {
   180  	creds := map[string]cloud.CloudCredential{
   181  		"google": {
   182  			DefaultCredential: "default-cred",
   183  			DefaultRegion:     "West US",
   184  			AuthCredentials: map[string]cloud.Credential{
   185  				"peter": cloud.NewCredential(cloud.OAuth2AuthType, map[string]string{
   186  					"client-id":    "client-id",
   187  					"client-email": "client-email",
   188  					"private-key":  "secret",
   189  				}),
   190  			},
   191  		},
   192  	}
   193  	out, err := cloud.MarshalCredentials(creds)
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	c.Assert(string(out), gc.Equals, `
   196  credentials:
   197    google:
   198      default-credential: default-cred
   199      default-region: West US
   200      peter:
   201        auth-type: oauth2
   202        client-email: client-email
   203        client-id: client-id
   204        private-key: secret
   205  `[1:])
   206  }
   207  
   208  func (s *credentialsSuite) TestParseCredentials(c *gc.C) {
   209  	s.testParseCredentials(c, []byte(`
   210  credentials:
   211    aws:
   212      default-credential: peter
   213      default-region: us-east-2
   214      peter:
   215        auth-type: access-key
   216        access-key: key
   217        secret-key: secret
   218    aws-china:
   219      default-credential: zhu8jie
   220      zhu8jie:
   221        auth-type: access-key
   222        access-key: key
   223        secret-key: secret
   224      sun5kong:
   225        auth-type: access-key
   226        access-key: quay
   227        secret-key: sekrit
   228    aws-gov:
   229      default-region: us-gov-west-1
   230      supersekrit:
   231        auth-type: access-key
   232        access-key: super
   233        secret-key: sekrit
   234  `[1:]), map[string]cloud.CloudCredential{
   235  		"aws": {
   236  			DefaultCredential: "peter",
   237  			DefaultRegion:     "us-east-2",
   238  			AuthCredentials: map[string]cloud.Credential{
   239  				"peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   240  					"access-key": "key",
   241  					"secret-key": "secret",
   242  				}),
   243  			},
   244  		},
   245  		"aws-china": {
   246  			DefaultCredential: "zhu8jie",
   247  			AuthCredentials: map[string]cloud.Credential{
   248  				"zhu8jie": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   249  					"access-key": "key",
   250  					"secret-key": "secret",
   251  				}),
   252  				"sun5kong": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   253  					"access-key": "quay",
   254  					"secret-key": "sekrit",
   255  				}),
   256  			},
   257  		},
   258  		"aws-gov": {
   259  			DefaultRegion: "us-gov-west-1",
   260  			AuthCredentials: map[string]cloud.Credential{
   261  				"supersekrit": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   262  					"access-key": "super",
   263  					"secret-key": "sekrit",
   264  				}),
   265  			},
   266  		},
   267  	})
   268  }
   269  
   270  func (s *credentialsSuite) TestParseCredentialsUnknownAuthType(c *gc.C) {
   271  	// Unknown auth-type is not validated by ParseCredentials.
   272  	// Validation is deferred to FinalizeCredential.
   273  	s.testParseCredentials(c, []byte(`
   274  credentials:
   275    cloud-name:
   276      credential-name:
   277        auth-type: woop
   278  `[1:]), map[string]cloud.CloudCredential{
   279  		"cloud-name": {
   280  			AuthCredentials: map[string]cloud.Credential{
   281  				"credential-name": cloud.NewCredential("woop", nil),
   282  			},
   283  		},
   284  	})
   285  }
   286  
   287  func (s *credentialsSuite) testParseCredentials(c *gc.C, input []byte, expect map[string]cloud.CloudCredential) {
   288  	output, err := cloud.ParseCredentials(input)
   289  	c.Assert(err, jc.ErrorIsNil)
   290  	c.Assert(output, jc.DeepEquals, expect)
   291  }
   292  
   293  func (s *credentialsSuite) TestParseCredentialsMissingAuthType(c *gc.C) {
   294  	s.testParseCredentialsError(c, []byte(`
   295  credentials:
   296    cloud-name:
   297      credential-name:
   298        doesnt: really-matter
   299  `[1:]), "credentials.cloud-name.credential-name: missing auth-type")
   300  }
   301  
   302  func (s *credentialsSuite) TestParseCredentialsNonStringValue(c *gc.C) {
   303  	s.testParseCredentialsError(c, []byte(`
   304  credentials:
   305    cloud-name:
   306      credential-name:
   307        non-string-value: 123
   308  `[1:]), `credentials\.cloud-name\.credential-name\.non-string-value: expected string, got int\(123\)`)
   309  }
   310  
   311  func (s *credentialsSuite) testParseCredentialsError(c *gc.C, input []byte, expect string) {
   312  	_, err := cloud.ParseCredentials(input)
   313  	c.Assert(err, gc.ErrorMatches, expect)
   314  }
   315  
   316  func (s *credentialsSuite) TestFinalizeCredential(c *gc.C) {
   317  	cred := cloud.NewCredential(
   318  		cloud.UserPassAuthType,
   319  		map[string]string{
   320  			"key": "value",
   321  		},
   322  	)
   323  	schema := cloud.CredentialSchema{{
   324  		"key",
   325  		cloud.CredentialAttr{
   326  			Description: "key credential",
   327  			Hidden:      true,
   328  		},
   329  	}}
   330  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   331  		cloud.UserPassAuthType: schema,
   332  	}, readFileNotSupported)
   333  	c.Assert(err, jc.ErrorIsNil)
   334  }
   335  
   336  func (s *credentialsSuite) TestFinalizeCredentialFileAttr(c *gc.C) {
   337  	cred := cloud.NewCredential(
   338  		cloud.UserPassAuthType,
   339  		map[string]string{
   340  			"key-file": "path",
   341  			"quay":     "value",
   342  		},
   343  	)
   344  	schema := cloud.CredentialSchema{{
   345  		"key",
   346  		cloud.CredentialAttr{
   347  			Description: "key credential",
   348  			Hidden:      true,
   349  			FileAttr:    "key-file",
   350  		},
   351  	}, {
   352  		"quay", cloud.CredentialAttr{FileAttr: "quay-file"},
   353  	}}
   354  	readFile := func(s string) ([]byte, error) {
   355  		c.Assert(s, gc.Equals, "path")
   356  		return []byte("file-value"), nil
   357  	}
   358  	newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   359  		cloud.UserPassAuthType: schema,
   360  	}, readFile)
   361  	c.Assert(err, jc.ErrorIsNil)
   362  	c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{
   363  		"key":  "file-value",
   364  		"quay": "value",
   365  	})
   366  }
   367  
   368  func (s *credentialsSuite) TestFinalizeCredentialFileEmpty(c *gc.C) {
   369  	cred := cloud.NewCredential(
   370  		cloud.UserPassAuthType,
   371  		map[string]string{
   372  			"key-file": "path",
   373  		},
   374  	)
   375  	schema := cloud.CredentialSchema{{
   376  		"key",
   377  		cloud.CredentialAttr{
   378  			Description: "key credential",
   379  			Hidden:      true,
   380  			FileAttr:    "key-file",
   381  		},
   382  	}}
   383  	readFile := func(string) ([]byte, error) {
   384  		return nil, nil
   385  	}
   386  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   387  		cloud.UserPassAuthType: schema,
   388  	}, readFile)
   389  	c.Assert(err, gc.ErrorMatches, `empty file for "key" not valid`)
   390  }
   391  
   392  func (s *credentialsSuite) TestFinalizeCredentialFileAttrNeither(c *gc.C) {
   393  	cred := cloud.NewCredential(
   394  		cloud.UserPassAuthType,
   395  		map[string]string{},
   396  	)
   397  	schema := cloud.CredentialSchema{{
   398  		"key",
   399  		cloud.CredentialAttr{
   400  			Description: "key credential",
   401  			Hidden:      true,
   402  			FileAttr:    "key-file",
   403  		},
   404  	}}
   405  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   406  		cloud.UserPassAuthType: schema,
   407  	}, readFileNotSupported)
   408  	c.Assert(err, gc.ErrorMatches, `either "key" or "key-file" must be specified`)
   409  }
   410  
   411  func (s *credentialsSuite) TestFinalizeCredentialFileAttrBoth(c *gc.C) {
   412  	cred := cloud.NewCredential(
   413  		cloud.UserPassAuthType,
   414  		map[string]string{
   415  			"key":      "value",
   416  			"key-file": "path",
   417  		},
   418  	)
   419  	schema := cloud.CredentialSchema{{
   420  		"key",
   421  		cloud.CredentialAttr{
   422  			Description: "key credential",
   423  			Hidden:      true,
   424  			FileAttr:    "key-file",
   425  		},
   426  	}}
   427  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   428  		cloud.UserPassAuthType: schema,
   429  	}, readFileNotSupported)
   430  	c.Assert(err, gc.ErrorMatches, `specifying both "key" and "key-file" not valid`)
   431  }
   432  
   433  func (s *credentialsSuite) TestFinalizeCredentialInvalid(c *gc.C) {
   434  	cred := cloud.NewCredential(
   435  		cloud.UserPassAuthType,
   436  		map[string]string{},
   437  	)
   438  	schema := cloud.CredentialSchema{{
   439  		"key",
   440  		cloud.CredentialAttr{
   441  			Description: "key credential",
   442  			Hidden:      true,
   443  		},
   444  	}}
   445  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   446  		cloud.UserPassAuthType: schema,
   447  	}, readFileNotSupported)
   448  	c.Assert(err, gc.ErrorMatches, "key: expected string, got nothing")
   449  }
   450  
   451  func (s *credentialsSuite) TestFinalizeCredentialNotSupported(c *gc.C) {
   452  	cred := cloud.NewCredential(
   453  		cloud.OAuth2AuthType,
   454  		map[string]string{},
   455  	)
   456  	_, err := cloud.FinalizeCredential(
   457  		cred, map[cloud.AuthType]cloud.CredentialSchema{}, readFileNotSupported,
   458  	)
   459  	c.Assert(err, jc.Satisfies, errors.IsNotSupported)
   460  	c.Assert(err, gc.ErrorMatches, `auth-type "oauth2" not supported`)
   461  }
   462  
   463  func readFileNotSupported(f string) ([]byte, error) {
   464  	return nil, errors.NotSupportedf("reading file %q", f)
   465  }
   466  
   467  func (s *credentialsSuite) TestFinalizeCredentialMandatoryFieldMissing(c *gc.C) {
   468  	cred := cloud.NewCredential(
   469  		cloud.UserPassAuthType,
   470  		map[string]string{
   471  			"password": "secret",
   472  			"domain":   "domain",
   473  		},
   474  	)
   475  	schema := cloud.CredentialSchema{
   476  		{"username", cloud.CredentialAttr{Optional: false}},
   477  		{"password", cloud.CredentialAttr{Hidden: true}},
   478  		{"domain", cloud.CredentialAttr{}},
   479  	}
   480  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   481  		cloud.UserPassAuthType: schema,
   482  	}, nil)
   483  	c.Assert(err, gc.ErrorMatches, "username: expected string, got nothing")
   484  }
   485  
   486  func (s *credentialsSuite) TestFinalizeCredentialMandatoryFieldFromFile(c *gc.C) {
   487  	cred := cloud.NewCredential(
   488  		cloud.UserPassAuthType,
   489  		map[string]string{
   490  			"key-file": "path",
   491  		},
   492  	)
   493  	schema := cloud.CredentialSchema{{
   494  		"key",
   495  		cloud.CredentialAttr{
   496  			Description: "key credential",
   497  			Optional:    false,
   498  			FileAttr:    "key-file",
   499  		},
   500  	}}
   501  	readFile := func(s string) ([]byte, error) {
   502  		c.Assert(s, gc.Equals, "path")
   503  		return []byte("file-value"), nil
   504  	}
   505  	newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   506  		cloud.UserPassAuthType: schema,
   507  	}, readFile)
   508  	c.Assert(err, jc.ErrorIsNil)
   509  	c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{
   510  		"key": "file-value",
   511  	})
   512  }
   513  
   514  func (s *credentialsSuite) TestFinalizeCredentialExtraField(c *gc.C) {
   515  	cred := cloud.NewCredential(
   516  		cloud.UserPassAuthType,
   517  		map[string]string{
   518  			"username":   "user",
   519  			"password":   "secret",
   520  			"domain":     "domain",
   521  			"access-key": "access-key",
   522  		},
   523  	)
   524  	schema := cloud.CredentialSchema{
   525  		{"username", cloud.CredentialAttr{Optional: false}},
   526  		{"password", cloud.CredentialAttr{Hidden: true}},
   527  		{"domain", cloud.CredentialAttr{}},
   528  	}
   529  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   530  		cloud.UserPassAuthType: schema,
   531  	}, nil)
   532  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`unknown key "access-key" (value "access-key")`))
   533  }
   534  
   535  func (s *credentialsSuite) TestFinalizeCredentialInvalidChoice(c *gc.C) {
   536  	cred := cloud.NewCredential(
   537  		cloud.UserPassAuthType,
   538  		map[string]string{
   539  			"username":  "user",
   540  			"password":  "secret",
   541  			"algorithm": "foo",
   542  		},
   543  	)
   544  	schema := cloud.CredentialSchema{
   545  		{"username", cloud.CredentialAttr{Optional: false}},
   546  		{"password", cloud.CredentialAttr{Hidden: true}},
   547  		{"algorithm", cloud.CredentialAttr{Options: []interface{}{"bar", "foobar"}}},
   548  	}
   549  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   550  		cloud.UserPassAuthType: schema,
   551  	}, nil)
   552  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`algorithm: expected one of [bar foobar], got "foo"`))
   553  }
   554  
   555  func (s *credentialsSuite) TestFinalizeCredentialFilePath(c *gc.C) {
   556  	dir := c.MkDir()
   557  	filename := filepath.Join(dir, "filename")
   558  	err := os.WriteFile(filename, []byte{}, 0600)
   559  	c.Assert(err, jc.ErrorIsNil)
   560  
   561  	cred := cloud.NewCredential(
   562  		cloud.JSONFileAuthType,
   563  		map[string]string{
   564  			"file": filename,
   565  		},
   566  	)
   567  	schema := cloud.CredentialSchema{{
   568  		"file", cloud.CredentialAttr{FilePath: true},
   569  	}}
   570  
   571  	readFile := func(path string) ([]byte, error) {
   572  		c.Assert(path, gc.Equals, filename)
   573  		return []byte("file-contents"), nil
   574  	}
   575  
   576  	newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   577  		cloud.JSONFileAuthType: schema,
   578  	}, readFile)
   579  	c.Assert(err, jc.ErrorIsNil)
   580  	c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{
   581  		"file": "file-contents",
   582  	})
   583  }
   584  
   585  func (s *credentialsSuite) TestFinalizeCredentialRelativeFilePath(c *gc.C) {
   586  	absFilename := filepath.Join(utils.Home(), "filename")
   587  	err := os.WriteFile(absFilename, []byte{}, 0600)
   588  	c.Assert(err, jc.ErrorIsNil)
   589  
   590  	cred := cloud.NewCredential(
   591  		cloud.JSONFileAuthType,
   592  		map[string]string{
   593  			"file": "~/filename",
   594  		},
   595  	)
   596  	schema := cloud.CredentialSchema{{
   597  		"file", cloud.CredentialAttr{FilePath: true},
   598  	}}
   599  	readFile := func(path string) ([]byte, error) {
   600  		c.Assert(path, gc.Equals, absFilename)
   601  		return []byte("file-contents"), nil
   602  	}
   603  	newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   604  		cloud.JSONFileAuthType: schema,
   605  	}, readFile)
   606  	c.Assert(err, jc.ErrorIsNil)
   607  	c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{
   608  		"file": "file-contents",
   609  	})
   610  }
   611  
   612  func (s *credentialsSuite) TestFinalizeCredentialInvalidFilePath(c *gc.C) {
   613  	fp := filepath.Join(c.MkDir(), "somefile")
   614  	cred := cloud.NewCredential(
   615  		cloud.JSONFileAuthType,
   616  		map[string]string{
   617  			"file": fp,
   618  		},
   619  	)
   620  	schema := cloud.CredentialSchema{{
   621  		"file", cloud.CredentialAttr{FilePath: true},
   622  	}}
   623  	_, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{
   624  		cloud.JSONFileAuthType: schema,
   625  	}, nil)
   626  	c.Assert(err, gc.ErrorMatches, "invalid file path: .*")
   627  }
   628  
   629  func (s *credentialsSuite) TestRemoveSecrets(c *gc.C) {
   630  	cred := cloud.NewCredential(
   631  		cloud.UserPassAuthType,
   632  		map[string]string{
   633  			"username": "user",
   634  			"password": "secret",
   635  		},
   636  	)
   637  	c.Assert(cred.Revoked, jc.IsFalse)
   638  	schema := cloud.CredentialSchema{{
   639  		"username", cloud.CredentialAttr{},
   640  	}, {
   641  		"password", cloud.CredentialAttr{Hidden: true},
   642  	}}
   643  	sanitisedCred, err := cloud.RemoveSecrets(cred, map[cloud.AuthType]cloud.CredentialSchema{
   644  		cloud.UserPassAuthType: schema,
   645  	})
   646  	c.Assert(err, jc.ErrorIsNil)
   647  	c.Assert(sanitisedCred.Attributes(), jc.DeepEquals, map[string]string{
   648  		"username": "user",
   649  	})
   650  }
   651  
   652  func (s *credentialsSuite) TestValidateFileAttrValue(c *gc.C) {
   653  	_, err := cloud.ValidateFileAttrValue("/xyz/nothing.blah")
   654  	c.Assert(err, gc.ErrorMatches, "invalid file path: stat /xyz/nothing.blah: no such file or directory")
   655  
   656  	absPathNewFile := filepath.Join(utils.Home(), "new-creds.json")
   657  	err = os.WriteFile(absPathNewFile, []byte("abc"), 0600)
   658  	c.Assert(err, jc.ErrorIsNil)
   659  
   660  	absPath, err := cloud.ValidateFileAttrValue("~/new-creds.json")
   661  	c.Assert(err, jc.ErrorIsNil)
   662  	c.Assert(absPath, gc.Equals, absPathNewFile)
   663  
   664  	_, err = cloud.ValidateFileAttrValue(utils.Home())
   665  	c.Assert(err, gc.ErrorMatches, fmt.Sprintf("file path %q must be a file", utils.Home()))
   666  }
   667  
   668  func (s *credentialsSuite) TestExpandFilePathsOfCredential(c *gc.C) {
   669  	tempFile, err := os.CreateTemp("", "")
   670  	c.Assert(err, jc.ErrorIsNil)
   671  
   672  	_, err = tempFile.WriteString("test")
   673  	c.Assert(err, jc.ErrorIsNil)
   674  
   675  	c.Assert(tempFile.Close(), jc.ErrorIsNil)
   676  
   677  	cred := cloud.NewNamedCredential("test",
   678  		cloud.AuthType("test"),
   679  		map[string]string{
   680  			"test-key":  tempFile.Name(),
   681  			"test-key1": "test-value",
   682  		},
   683  		false,
   684  	)
   685  
   686  	credSchema := cloud.CredentialSchema{
   687  		{
   688  			Name: "test-key",
   689  			CredentialAttr: cloud.CredentialAttr{
   690  				Description:    "test credential attribute",
   691  				ExpandFilePath: true,
   692  				Hidden:         false,
   693  			},
   694  		},
   695  		{
   696  			Name: "test-key1",
   697  			CredentialAttr: cloud.CredentialAttr{
   698  				Description:    "test credential attribute",
   699  				ExpandFilePath: false,
   700  				Hidden:         false,
   701  			},
   702  		},
   703  	}
   704  
   705  	cred, err = cloud.ExpandFilePathsOfCredential(
   706  		cred, map[cloud.AuthType]cloud.CredentialSchema{
   707  			cloud.AuthType("test"): credSchema,
   708  		},
   709  	)
   710  	c.Assert(err, jc.ErrorIsNil)
   711  
   712  	c.Assert(cred.Attributes()["test-key"], gc.Equals, "test")
   713  	c.Assert(cred.Attributes()["test-key1"], gc.Equals, "test-value")
   714  }
   715  
   716  // Regression test for lp1976620
   717  func (s *credentialsSuite) TestExpandFilePathsOfPem(c *gc.C) {
   718  	testPemCert := `
   719  -----BEGIN CERTIFICATE-----
   720  MIIB5DCCAWugAwIBAgIRAI0U9NoAVVolPG4O85Zr3dgwCgYIKoZIzj0EAwMwOjEc
   721  MBoGA1UEChMTbGludXhjb250YWluZXJzLm9yZzEaMBgGA1UEAwwRdGxtQHRsbS1t
   722  YnAubG9jYWwwHhcNMjIwMzAzMTMxNzMxWhcNMzIwMjI5MTMxNzMxWjA6MRwwGgYD
   723  VQQKExNsaW51eGNvbnRhaW5lcnMub3JnMRowGAYDVQQDDBF0bG1AdGxtLW1icC5s
   724  b2NhbDB2MBAGByqGSM49AgEGBSuBBAAiA2IABHvoqBLC2amlFuAQq/IrMUd4Cver
   725  teYK/BkJfTOx5M6Gt+RE7Vi0uVO0MfzOPrtTKQQPtffSelyGtpxZtQjRLKhdzCa9
   726  E2lDhIf/j6axT64cp3vdA3XU96pIfFH32Ff1yqM1MDMwDgYDVR0PAQH/BAQDAgWg
   727  MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0EAwMD
   728  ZwAwZAIwcac9nw5lXFtQyO9d5ZDUBfjafw/fg0YvaypV5KeRhC/ljB4ooN+DuJjy
   729  L8jEeLyWAjA53jxIoL5A5CyXKhqQPcYyfTMHstcP7ip8wLMnee1y3b8wwq9celAa
   730  QD0jIrgXpik=
   731  -----END CERTIFICATE-----
   732  `
   733  
   734  	cred := cloud.NewNamedCredential("test",
   735  		cloud.AuthType("test"),
   736  		map[string]string{
   737  			"test-key": testPemCert,
   738  		},
   739  		false,
   740  	)
   741  
   742  	credSchema := cloud.CredentialSchema{
   743  		{
   744  			Name: "test-key",
   745  			CredentialAttr: cloud.CredentialAttr{
   746  				Description:    "test credential attribute",
   747  				ExpandFilePath: true,
   748  				Hidden:         false,
   749  			},
   750  		},
   751  	}
   752  
   753  	cred, err := cloud.ExpandFilePathsOfCredential(
   754  		cred, map[cloud.AuthType]cloud.CredentialSchema{
   755  			cloud.AuthType("test"): credSchema,
   756  		},
   757  	)
   758  	c.Assert(err, jc.ErrorIsNil)
   759  	c.Assert(cred.Attributes()["test-key"], gc.Equals, testPemCert)
   760  }