Encoding secret data in airshipctl config files

* encoding while saving credentials in authInfo
* decoding while fethcing credentials from authInfo
* Credentials are found only in ~/.airship/kubeconfig, so did not find
  anything in ~/.airship/config to be encoded.

Change-Id: I13f3d49b2ad7ccd1388cabd015fe5a93be2c7b96
Closes: #155
This commit is contained in:
Yasin, Siraj (SY495P) 2020-04-15 16:13:41 -05:00 committed by Sirajudeen
parent 31b8b11f0f
commit 318895d89d
12 changed files with 169 additions and 64 deletions

View File

@ -60,7 +60,10 @@ func NewGetAuthInfoCommand(rootSettings *environment.AirshipCTLSettings) *cobra.
}
fmt.Fprintln(cmd.OutOrStdout(), authinfo)
} else {
authinfos := airconfig.GetAuthInfos()
authinfos, err := airconfig.GetAuthInfos()
if err != nil {
return err
}
if len(authinfos) == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "No User credentials found in the configuration.")
}

View File

@ -20,6 +20,8 @@ import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
cmd "opendev.org/airship/airshipctl/cmd/config"
@ -39,9 +41,15 @@ func TestGetAuthInfoCmd(t *testing.T) {
settings := &environment.AirshipCTLSettings{
Config: &config.Config{
AuthInfos: map[string]*config.AuthInfo{
fooAuthInfo: getTestAuthInfo(),
barAuthInfo: getTestAuthInfo(),
bazAuthInfo: getTestAuthInfo(),
fooAuthInfo: getTestAuthInfo(fooAuthInfo),
},
},
}
settingsWithMultipleAuth := &environment.AirshipCTLSettings{
Config: &config.Config{
AuthInfos: map[string]*config.AuthInfo{
barAuthInfo: getTestAuthInfo(barAuthInfo),
bazAuthInfo: getTestAuthInfo(bazAuthInfo),
},
},
}
@ -53,8 +61,9 @@ func TestGetAuthInfoCmd(t *testing.T) {
Cmd: cmd.NewGetAuthInfoCommand(settings),
},
{
Name: "get-all-credentials",
Cmd: cmd.NewGetAuthInfoCommand(settings),
Name: "get-all-credentials",
CmdLine: "",
Cmd: cmd.NewGetAuthInfoCommand(settingsWithMultipleAuth),
},
{
Name: "missing",
@ -80,17 +89,31 @@ func TestNoAuthInfosGetAuthInfoCmd(t *testing.T) {
testutil.RunTest(t, cmdTest)
}
func getTestAuthInfo() *config.AuthInfo {
func TestDecodeAuthInfo(t *testing.T) {
_, err := config.DecodeAuthInfo(&kubeconfig.AuthInfo{Password: "dummy_password"})
assert.Error(t, err, config.ErrDecodingCredentials{Given: "dummy_password"})
_, err = config.DecodeAuthInfo(&kubeconfig.AuthInfo{ClientCertificate: "dummy_certificate"})
assert.Error(t, err, config.ErrDecodingCredentials{Given: "dummy_certificate"})
_, err = config.DecodeAuthInfo(&kubeconfig.AuthInfo{ClientKey: "dummy_key"})
assert.Error(t, err, config.ErrDecodingCredentials{Given: "dummy_key"})
_, err = config.DecodeAuthInfo(&kubeconfig.AuthInfo{Token: "dummy_token"})
assert.Error(t, err, config.ErrDecodingCredentials{Given: "dummy_token"})
}
func getTestAuthInfo(authName string) *config.AuthInfo {
kAuthInfo := &kubeconfig.AuthInfo{
Username: "dummy_user",
Password: "dummy_password",
ClientCertificate: "dummy_certificate",
ClientKey: "dummy_key",
Token: "dummy_token",
Username: authName + "_user",
Password: authName + "_password",
ClientCertificate: authName + "_certificate",
ClientKey: authName + "_key",
Token: authName + "_token",
}
newAuthInfo := &config.AuthInfo{}
newAuthInfo.SetKubeAuthInfo(kAuthInfo)
encodedKAuthInfo := config.EncodeAuthInfo(kAuthInfo)
newAuthInfo.SetKubeAuthInfo(encodedKAuthInfo)
return newAuthInfo
}

View File

@ -81,8 +81,9 @@ func initInputConfig(t *testing.T) (given *config.Config, cleanup func(*testing.
kubeAuthInfo := kubeconfig.NewAuthInfo()
kubeAuthInfo.Username = testUsername
kubeAuthInfo.Password = testPassword
given.KubeConfig().AuthInfos[existingUserName] = kubeAuthInfo
given.AuthInfos[existingUserName].SetKubeAuthInfo(kubeAuthInfo)
encodedKAuthInfo := config.EncodeAuthInfo(kubeAuthInfo)
given.KubeConfig().AuthInfos[existingUserName] = encodedKAuthInfo
given.AuthInfos[existingUserName].SetKubeAuthInfo(encodedKAuthInfo)
return given, givenCleanup
}

View File

@ -1,21 +1,14 @@
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user
client-certificate: AuthInfoBar_certificate
client-key: AuthInfoBar_key
password: AuthInfoBar_password
token: AuthInfoBar_token
username: AuthInfoBar_user
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user
client-certificate: AuthInfoBaz_certificate
client-key: AuthInfoBaz_key
password: AuthInfoBaz_password
token: AuthInfoBaz_token
username: AuthInfoBaz_user

View File

@ -1,7 +1,7 @@
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
username: dummy_user
client-certificate: AuthInfoFoo_certificate
client-key: AuthInfoFoo_key
password: AuthInfoFoo_password
token: AuthInfoFoo_token
username: AuthInfoFoo_user

View File

@ -29,7 +29,8 @@ func TestGetAuthInfos(t *testing.T) {
conf, cleanup := testutil.InitConfig(t)
defer cleanup(t)
authinfos := conf.GetAuthInfos()
authinfos, err := conf.GetAuthInfos()
require.NoError(t, err)
assert.Len(t, authinfos, 3)
}
@ -65,15 +66,17 @@ func TestModifyAuthInfo(t *testing.T) {
authinfo := conf.AddAuthInfo(co)
co.Username += stringDelta
co.Password += stringDelta
co.ClientCertificate += stringDelta
co.ClientKey += stringDelta
co.Token += stringDelta
co.Password = newPassword
co.ClientCertificate = newCertificate
co.ClientKey = newKey
co.Token = newToken
conf.ModifyAuthInfo(authinfo, co)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().Username, co.Username)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().Password, co.Password)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().ClientCertificate, co.ClientCertificate)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().ClientKey, co.ClientKey)
assert.EqualValues(t, conf.AuthInfos[co.Name].KubeAuthInfo().Token, co.Token)
assert.EqualValues(t, conf.AuthInfos[co.Name], authinfo)
modifiedAuthinfo, err := conf.GetAuthInfo(co.Name)
assert.NoError(t, err)
assert.EqualValues(t, modifiedAuthinfo.KubeAuthInfo().Username, co.Username)
assert.EqualValues(t, modifiedAuthinfo.KubeAuthInfo().Password, co.Password)
assert.EqualValues(t, modifiedAuthinfo.KubeAuthInfo().ClientCertificate, co.ClientCertificate)
assert.EqualValues(t, modifiedAuthinfo.KubeAuthInfo().ClientKey, co.ClientKey)
assert.EqualValues(t, modifiedAuthinfo.KubeAuthInfo().Token, co.Token)
assert.EqualValues(t, modifiedAuthinfo, authinfo)
}

View File

@ -265,7 +265,7 @@ func (c *Config) reconcileAuthInfos() {
// Add the reference
c.AuthInfos[key] = NewAuthInfo()
}
c.AuthInfos[key].SetKubeAuthInfo(authinfo)
c.AuthInfos[key].authInfo = authinfo
}
// Checking if there is any AuthInfo reference in airship config that does not match
// an actual Auth Info struct in kubeconfig
@ -711,12 +711,17 @@ func (c *Config) GetAuthInfo(aiName string) (*AuthInfo, error) {
if !exists {
return nil, ErrMissingConfig{What: fmt.Sprintf("User credentials with name '%s'", aiName)}
}
decodedAuthInfo, err := DecodeAuthInfo(authinfo.authInfo)
if err != nil {
return nil, err
}
authinfo.authInfo = decodedAuthInfo
return authinfo, nil
}
// GetAuthInfos returns a slice containing all the AuthInfos associated with
// the Config sorted by name
func (c *Config) GetAuthInfos() []*AuthInfo {
func (c *Config) GetAuthInfos() ([]*AuthInfo, error) {
keys := make([]string, 0, len(c.AuthInfos))
for name := range c.AuthInfos {
keys = append(keys, name)
@ -725,9 +730,14 @@ func (c *Config) GetAuthInfos() []*AuthInfo {
authInfos := make([]*AuthInfo, 0, len(c.AuthInfos))
for _, name := range keys {
decodedAuthInfo, err := DecodeAuthInfo(c.AuthInfos[name].authInfo)
if err != nil {
return []*AuthInfo{}, err
}
c.AuthInfos[name].authInfo = decodedAuthInfo
authInfos = append(authInfos, c.AuthInfos[name])
}
return authInfos
return authInfos, nil
}
// AddAuthInfo creates new AuthInfo with context details updated
@ -738,7 +748,7 @@ func (c *Config) AddAuthInfo(theAuthInfo *AuthInfoOptions) *AuthInfo {
c.AuthInfos[theAuthInfo.Name] = nAuthInfo
// Create a new KubeConfig AuthInfo object as well
authInfo := clientcmdapi.NewAuthInfo()
nAuthInfo.SetKubeAuthInfo(authInfo)
nAuthInfo.authInfo = authInfo
c.KubeConfig().AuthInfos[theAuthInfo.Name] = authInfo
c.ModifyAuthInfo(nAuthInfo, theAuthInfo)
@ -747,24 +757,24 @@ func (c *Config) AddAuthInfo(theAuthInfo *AuthInfoOptions) *AuthInfo {
// ModifyAuthInfo updates the AuthInfo in the Config object
func (c *Config) ModifyAuthInfo(authinfo *AuthInfo, theAuthInfo *AuthInfoOptions) {
kubeAuthInfo := authinfo.KubeAuthInfo()
kubeAuthInfo := EncodeAuthInfo(authinfo.KubeAuthInfo())
if kubeAuthInfo == nil {
return
}
if theAuthInfo.ClientCertificate != "" {
kubeAuthInfo.ClientCertificate = theAuthInfo.ClientCertificate
kubeAuthInfo.ClientCertificate = EncodeString(theAuthInfo.ClientCertificate)
}
if theAuthInfo.Token != "" {
kubeAuthInfo.Token = theAuthInfo.Token
kubeAuthInfo.Token = EncodeString(theAuthInfo.Token)
}
if theAuthInfo.Username != "" {
kubeAuthInfo.Username = theAuthInfo.Username
}
if theAuthInfo.Password != "" {
kubeAuthInfo.Password = theAuthInfo.Password
kubeAuthInfo.Password = EncodeString(theAuthInfo.Password)
}
if theAuthInfo.ClientKey != "" {
kubeAuthInfo.ClientKey = theAuthInfo.ClientKey
kubeAuthInfo.ClientKey = EncodeString(theAuthInfo.ClientKey)
}
}
@ -909,3 +919,44 @@ func (m *ManagementConfiguration) String() string {
}
return string(yamlData)
}
// DecodeAuthInfo returns authInfo with credentials decoded
func DecodeAuthInfo(authinfo *clientcmdapi.AuthInfo) (*clientcmdapi.AuthInfo, error) {
password := authinfo.Password
decodedPassword, err := DecodeString(password)
if err != nil {
return nil, ErrDecodingCredentials{Given: password}
}
authinfo.Password = decodedPassword
token := authinfo.Token
decodedToken, err := DecodeString(token)
if err != nil {
return nil, ErrDecodingCredentials{Given: token}
}
authinfo.Token = decodedToken
clientCert := authinfo.ClientCertificate
decodedClientCertificate, err := DecodeString(clientCert)
if err != nil {
return nil, ErrDecodingCredentials{Given: clientCert}
}
authinfo.ClientCertificate = decodedClientCertificate
clientKey := authinfo.ClientKey
decodedClientKey, err := DecodeString(clientKey)
if err != nil {
return nil, ErrDecodingCredentials{Given: clientKey}
}
authinfo.ClientKey = decodedClientKey
return authinfo, nil
}
// EncodeAuthInfo returns authInfo with credentials base64 encoded
func EncodeAuthInfo(authinfo *clientcmdapi.AuthInfo) *clientcmdapi.AuthInfo {
authinfo.Password = EncodeString(authinfo.Password)
authinfo.Token = EncodeString(authinfo.Token)
authinfo.ClientCertificate = EncodeString(authinfo.ClientCertificate)
authinfo.ClientKey = EncodeString(authinfo.ClientKey)
return authinfo
}

View File

@ -34,6 +34,10 @@ const (
stringDelta = "_changed"
currentContextName = "def_ephemeral"
defaultString = "default"
newToken = "dummy_token_changed"
newPassword = "dummy_password_changed"
newCertificate = "dummy_certificate_changed"
newKey = "dummy_key_changed"
)
func TestString(t *testing.T) {

View File

@ -169,3 +169,12 @@ type ErrEmptyContextName struct {
func (e ErrEmptyContextName) Error() string {
return "you must specify a non-empty context name"
}
// ErrDecodingCredentials returned when the given string cannot be decoded
type ErrDecodingCredentials struct {
Given string
}
func (e ErrDecodingCredentials) Error() string {
return fmt.Sprintf("Error decoding credentials. String '%s' cannot not be decoded", e.Given)
}

View File

@ -1,6 +1,6 @@
LocationOfOrigin: ""
client-certificate: dummy_certificate
client-key: dummy_key
password: dummy_password
token: dummy_token
client-certificate: ZHVtbXlfY2VydGlmaWNhdGU=
client-key: ZHVtbXlfa2V5
password: ZHVtbXlfcGFzc3dvcmQ=
token: ZHVtbXlfdG9rZW4=
username: dummy_username

View File

@ -17,6 +17,8 @@ limitations under the License.
package config
import (
"encoding/base64"
"opendev.org/airship/airshipctl/pkg/remote/redfish"
)
@ -109,3 +111,18 @@ func NewRepository() *Repository {
func NewAuthInfo() *AuthInfo {
return &AuthInfo{}
}
// EncodeString returns the base64 encoding of given string
func EncodeString(given string) string {
return base64.StdEncoding.EncodeToString([]byte(given))
}
// DecodeString returns the base64 decoded string
// If err decoding, return the given string
func DecodeString(given string) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(given)
if err != nil {
return "", err
}
return string(decoded), nil
}

View File

@ -145,7 +145,8 @@ func DummyAuthInfo() *config.AuthInfo {
authinfo.ClientCertificate = "dummy_certificate"
authinfo.ClientKey = "dummy_key"
authinfo.Token = "dummy_token"
a.SetKubeAuthInfo(authinfo)
encodedAuthInfo := config.EncodeAuthInfo(authinfo)
a.SetKubeAuthInfo(encodedAuthInfo)
return a
}
@ -356,7 +357,7 @@ users:
- name: def-user
user:
username: dummy_username
password: dummy_password
password: ZHVtbXlfcGFzc3dvcmQK
- name: k-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQXhEdzk2RUY4SXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RBNU1qa3hOekF6TURsYUZ3MHlNREE1TWpneE56QXpNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXV6R0pZdlBaNkRvaTQyMUQKSzhXSmFaQ25OQWQycXo1cC8wNDJvRnpRUGJyQWd6RTJxWVZrek9MOHhBVmVSN1NONXdXb1RXRXlGOEVWN3JyLwo0K0hoSEdpcTVQbXF1SUZ5enpuNi9JWmM4alU5eEVmenZpa2NpckxmVTR2UlhKUXdWd2dBU05sMkFXQUloMmRECmRUcmpCQ2ZpS1dNSHlqMFJiSGFsc0J6T3BnVC9IVHYzR1F6blVRekZLdjJkajVWMU5rUy9ESGp5UlJKK0VMNlEKQlltR3NlZzVQNE5iQzllYnVpcG1NVEFxL0p1bU9vb2QrRmpMMm5acUw2Zkk2ZkJ0RjVPR2xwQ0IxWUo4ZnpDdApHUVFaN0hUSWJkYjJ0cDQzRlZPaHlRYlZjSHFUQTA0UEoxNSswV0F5bVVKVXo4WEE1NDRyL2J2NzRKY0pVUkZoCmFyWmlRd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMMmhIUmVibEl2VHJTMFNmUVg1RG9ueVVhNy84aTg1endVWApSd3dqdzFuS0U0NDJKbWZWRGZ5b0hRYUM4Ti9MQkxyUXM0U0lqU1JYdmFHU1dSQnRnT1RRV21Db1laMXdSbjdwCndDTXZQTERJdHNWWm90SEZpUFl2b1lHWFFUSXA3YlROMmg1OEJaaEZ3d25nWUovT04zeG1rd29IN1IxYmVxWEYKWHF1TTluekhESk41VlZub1lQR09yRHMwWlg1RnNxNGtWVU0wVExNQm9qN1ZIRDhmU0E5RjRYNU4yMldsZnNPMAo4aksrRFJDWTAyaHBrYTZQQ0pQS0lNOEJaMUFSMG9ZakZxT0plcXpPTjBqcnpYWHh4S2pHVFVUb1BldVA5dCtCCjJOMVA1TnI4a2oxM0lrend5Q1NZclFVN09ZM3ltZmJobHkrcXZxaFVFa014MlQ1SkpmQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=