Skip to content

Commit

Permalink
Adds support for legacy kubernetes schemas.
Browse files Browse the repository at this point in the history
In juju 2.9 we changed the schemas supported by Kubernetes as pre 2.9
they where incorrect. This change allows existing credentials to
continue working with Juju 2.9.

It's expected that in juju 3.0+ this support will be dropped.

- Fixes LP-1918486
  • Loading branch information
tlm committed Mar 18, 2021
1 parent 44f30e9 commit 000dba6
Show file tree
Hide file tree
Showing 16 changed files with 630 additions and 29 deletions.
2 changes: 1 addition & 1 deletion caas/kubernetes/clientconfig/k8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func (s *k8sConfigSuite) TestGetMultiConfig(c *gc.C) {
"the-user", cloud.OAuth2AuthType,
map[string]string{"Token": "tokenwithcerttoken"}, false)
secondCred := cloud.NewNamedCredential(
"second-user", cloud.CertificateAuthType,
"second-user", cloud.ClientCertificateAuthType,
map[string]string{"ClientCertificateData": "A", "ClientKeyData": "A"}, false)

for i, v := range []newK8sClientConfigTestCase{
Expand Down
149 changes: 138 additions & 11 deletions caas/kubernetes/cloud/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ var SupportedCredentialSchemas = map[cloud.AuthType]cloud.CredentialSchema{
},
},
},
cloud.CertificateAuthType: {
cloud.ClientCertificateAuthType: {
{
Name: CredAttrClientCertificateData,
CredentialAttr: cloud.CredentialAttr{
Expand All @@ -82,15 +82,55 @@ var SupportedCredentialSchemas = map[cloud.AuthType]cloud.CredentialSchema{
},
}

// SupportedAuthTypes returns a slice of support auth types that the Kubernetes
// caas provider supports.
func SupportedAuthTypes() cloud.AuthTypes {
var ats cloud.AuthTypes
for k := range SupportedCredentialSchemas {
ats = append(ats, k)
}
sort.Sort(ats)
return ats
// LegacyCredentialsSchemas represents legacy credentials schemas that Juju used
// to output but still need to be supported to maintain working Kubernetes
// support. These types should be liberally allowed as input but not used as
// new output from Juju. This change was introduced by tlm in juju 2.9
var LegacyCredentialSchemas = map[cloud.AuthType]cloud.CredentialSchema{
cloud.OAuth2WithCertAuthType: {
{
Name: CredAttrClientCertificateData,
CredentialAttr: cloud.CredentialAttr{
Description: "the kubernetes certificate data",
},
},
{
Name: CredAttrClientKeyData,
CredentialAttr: cloud.CredentialAttr{
Description: "the kubernetes private key data",
Hidden: true,
},
},
{
Name: CredAttrToken,
CredentialAttr: cloud.CredentialAttr{
Description: "the kubernetes token",
Hidden: true,
},
},
},
cloud.CertificateAuthType: {
{
Name: CredAttrClientCertificateData,
CredentialAttr: cloud.CredentialAttr{
Description: "the kubernetes certificate data",
},
},
{
Name: CredAttrToken,
CredentialAttr: cloud.CredentialAttr{
Description: "the kubernetes service account bearer token",
Hidden: true,
},
},
{
Name: RBACLabelKeyName,
CredentialAttr: cloud.CredentialAttr{
Optional: true,
Description: "the unique ID key name of the rbac resources",
},
},
},
}

// CredentialFromAuthInfo will generate a Juju credential based on the supplied
Expand Down Expand Up @@ -139,7 +179,7 @@ func CredentialFromAuthInfo(
var authType cloud.AuthType
switch {
case hasClientCert && hasClientCertKey:
authType = cloud.CertificateAuthType
authType = cloud.ClientCertificateAuthType
attrs["ClientCertificateData"] = string(clientCertData)
attrs["ClientKeyData"] = string(clientCertKeyData)
case hasToken:
Expand Down Expand Up @@ -182,3 +222,90 @@ func CredentialFromKubeConfigContext(
}
return CredentialFromKubeConfig(ctx.AuthInfo, config)
}

func MigrateLegacyCredential(cred *cloud.Credential) (cloud.Credential, error) {
switch cred.AuthType() {
case cloud.OAuth2WithCertAuthType:
return migrateOAuth2WithCertAuthType(cred)
case cloud.CertificateAuthType:
return migrateCertificateAuthType(cred)
default:
return cloud.Credential{}, errors.NotSupportedf(
"migration for auth type %s", cred.AuthType())
}
}

func migrateCertificateAuthType(cred *cloud.Credential) (cloud.Credential, error) {
attrs := cred.Attributes()
newAttrs := map[string]string{}

token, exists := attrs[CredAttrToken]
if !exists {
return cloud.Credential{}, errors.NotFoundf(
"certificate oauth token during migration, expect key %s",
CredAttrToken)
}

newAttrs[CredAttrToken] = token
if _, exists := attrs[RBACLabelKeyName]; exists {
newAttrs[RBACLabelKeyName] = attrs[RBACLabelKeyName]
}

return cloud.NewNamedCredential(
cred.Label,
cloud.OAuth2AuthType,
newAttrs,
false), nil
}

func migrateOAuth2WithCertAuthType(cred *cloud.Credential) (cloud.Credential, error) {
attrs := cred.Attributes()
newAttrs := map[string]string{}
var authType cloud.AuthType

_, clientCertExists := attrs[CredAttrClientCertificateData]
_, clientCertKeyExists := attrs[CredAttrClientKeyData]
if clientCertExists && clientCertKeyExists {
authType = cloud.ClientCertificateAuthType
newAttrs[CredAttrClientCertificateData] = attrs[CredAttrClientCertificateData]
newAttrs[CredAttrClientKeyData] = attrs[CredAttrClientKeyData]
} else if _, tokenExists := attrs[CredAttrToken]; tokenExists {
authType = cloud.OAuth2AuthType
newAttrs[CredAttrToken] = attrs[CredAttrToken]
} else {
return cloud.Credential{}, errors.NotValidf(
"migrating oauth2cert must have either %s & %s attributes or %s attribute",
CredAttrClientCertificateData,
CredAttrClientKeyData,
CredAttrToken)
}

return cloud.NewNamedCredential(
cred.Label,
authType,
newAttrs,
false), nil
}

// SupportedAuthTypes returns a slice of supported auth types that the Kubernetes
// caas provider supports.
func SupportedAuthTypes() cloud.AuthTypes {
var ats cloud.AuthTypes
for k := range LegacyCredentialSchemas {
ats = append(ats, k)
}
ats = append(ats, SupportedNonLegacyAuthTypes()...)
sort.Sort(ats)
return ats
}

// SupportedNonLegacyAuthTypes returns a slice of supported auth types that
// Kubernetes caas provider supports with legacy auth types removed.
func SupportedNonLegacyAuthTypes() cloud.AuthTypes {
var ats cloud.AuthTypes
for k := range SupportedCredentialSchemas {
ats = append(ats, k)
}
sort.Sort(ats)
return ats
}
116 changes: 114 additions & 2 deletions caas/kubernetes/cloud/credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package cloud_test
import (
"io/ioutil"

"github.com/juju/errors"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
Expand All @@ -32,7 +33,7 @@ func (s *credentialSuite) TestValidCredentials(c *gc.C) {
ClientCertificateData: []byte("cert-data"),
ClientKeyData: []byte("cert-key-data"),
},
AuthType: cloud.CertificateAuthType,
AuthType: cloud.ClientCertificateAuthType,
Attributes: map[string]string{
"ClientCertificateData": "cert-data",
"ClientKeyData": "cert-key-data",
Expand All @@ -41,7 +42,7 @@ func (s *credentialSuite) TestValidCredentials(c *gc.C) {
},
{
AuthInfo: &clientcmdapi.AuthInfo{},
AuthType: cloud.CertificateAuthType,
AuthType: cloud.ClientCertificateAuthType,
Attributes: map[string]string{
"ClientCertificateData": "cert-data",
"ClientKeyData": "cert-key-data",
Expand Down Expand Up @@ -138,3 +139,114 @@ func (s *credentialSuite) TestUnsupportedCredentials(c *gc.C) {
_, err := k8scloud.CredentialFromAuthInfo("unsupported", authInfo)
c.Assert(err.Error(), gc.Equals, "configuration for \"unsupported\" not supported")
}

func (s *credentialSuite) TestUnsuportedCredentialMigration(c *gc.C) {
cred := cloud.NewNamedCredential(
"doesnotexist",
cloud.ClientCertificateAuthType,
map[string]string{},
false)

_, err := k8scloud.MigrateLegacyCredential(&cred)
c.Assert(errors.IsNotSupported(err), jc.IsTrue)
}

func (s *credentialSuite) TestCertificateAuthMigrationMissingToken(c *gc.C) {
cred := cloud.NewNamedCredential(
"missingtoken",
cloud.CertificateAuthType,
map[string]string{},
false)

_, err := k8scloud.MigrateLegacyCredential(&cred)
c.Assert(err.Error(), gc.Equals, "certificate oauth token during migration, expect key Token not found")
}

func (s *credentialSuite) TestCertificateAuthMigration(c *gc.C) {
cred := cloud.NewNamedCredential(
"missingtoken",
cloud.CertificateAuthType,
map[string]string{
"Token": "mytoken",
},
false)

cred, err := k8scloud.MigrateLegacyCredential(&cred)
c.Assert(err, jc.ErrorIsNil)
c.Assert(cred.AuthType(), gc.Equals, cloud.OAuth2AuthType)
c.Assert(cred.Label, gc.Equals, "missingtoken")
c.Assert(cred.Attributes(), jc.DeepEquals, map[string]string{
"Token": "mytoken",
})
}

func (s *credentialSuite) TestCertificateAuthMigrationRBACId(c *gc.C) {
cred := cloud.NewNamedCredential(
"missingtoken",
cloud.CertificateAuthType,
map[string]string{
"Token": "mytoken",
"rbac-id": "id",
},
false)

cred, err := k8scloud.MigrateLegacyCredential(&cred)
c.Assert(err, jc.ErrorIsNil)
c.Assert(cred.AuthType(), gc.Equals, cloud.OAuth2AuthType)
c.Assert(cred.Label, gc.Equals, "missingtoken")
c.Assert(cred.Attributes(), jc.DeepEquals, map[string]string{
"Token": "mytoken",
"rbac-id": "id",
})
}

func (s *credentialSuite) TestOAuth2CertMigrationWithoutToken(c *gc.C) {
cred := cloud.NewNamedCredential(
"missingtoken",
cloud.OAuth2WithCertAuthType,
map[string]string{
"ClientCertificateData": "data",
"ClientKeyData": "key",
},
false)

cred, err := k8scloud.MigrateLegacyCredential(&cred)
c.Assert(err, jc.ErrorIsNil)
c.Assert(cred.AuthType(), gc.Equals, cloud.ClientCertificateAuthType)
c.Assert(cred.Label, gc.Equals, "missingtoken")
c.Assert(cred.Attributes(), jc.DeepEquals, map[string]string{
"ClientCertificateData": "data",
"ClientKeyData": "key",
})
}

func (s *credentialSuite) TestOAuth2CertMigrationWithoutTokenCert(c *gc.C) {
cred := cloud.NewNamedCredential(
"missingtoken",
cloud.OAuth2WithCertAuthType,
map[string]string{
"ClientCertificateData": "data",
},
false)

_, err := k8scloud.MigrateLegacyCredential(&cred)
c.Assert(err.Error(), gc.Equals, "migrating oauth2cert must have either ClientCertificateData & ClientKeyData attributes or Token attribute not valid")
}

func (s *credentialSuite) TestOAuth2CertMigrationWithToken(c *gc.C) {
cred := cloud.NewNamedCredential(
"missingtoken",
cloud.OAuth2WithCertAuthType,
map[string]string{
"Token": "mytoken",
},
false)

cred, err := k8scloud.MigrateLegacyCredential(&cred)
c.Assert(err, jc.ErrorIsNil)
c.Assert(cred.AuthType(), gc.Equals, cloud.OAuth2AuthType)
c.Assert(cred.Label, gc.Equals, "missingtoken")
c.Assert(cred.Attributes(), jc.DeepEquals, map[string]string{
"Token": "mytoken",
})
}
2 changes: 2 additions & 0 deletions caas/kubernetes/provider/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ func (s *builtinSuite) TestAttemptMicroK8sCloud(c *gc.C) {
Type: cloud.CloudTypeCAAS,
AuthTypes: []cloud.AuthType{
cloud.CertificateAuthType,
cloud.ClientCertificateAuthType,
cloud.OAuth2AuthType,
cloud.OAuth2WithCertAuthType,
cloud.UserPassAuthType,
},
CACertificates: []string{""},
Expand Down
7 changes: 5 additions & 2 deletions caas/kubernetes/provider/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/juju/juju/caas"
"github.com/juju/juju/caas/kubernetes/clientconfig"
k8scloud "github.com/juju/juju/caas/kubernetes/cloud"
k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants"
"github.com/juju/juju/cloud"
"github.com/juju/juju/environs"
Expand Down Expand Up @@ -270,8 +271,10 @@ func BaseKubeCloudOpenParams(cloud cloud.Cloud, credential cloud.Credential) (en

// FinalizeCloud is part of the environs.CloudFinalizer interface.
func (p kubernetesEnvironProvider) FinalizeCloud(ctx environs.FinalizeCloudContext, cld cloud.Cloud) (cloud.Cloud, error) {
// We special case Microk8s here as we need to query the cluster for the
// storage details with no input from the user
// We set the clouds auth types to all kubernetes supported auth types here
// so that finalize credentials is free to change the credentials of the
// bootstrap. See lp-1918486
cld.AuthTypes = k8scloud.SupportedAuthTypes()

// if storage is already defined there is no need to query the cluster
if opStorage, ok := cld.Config[k8sconstants.OperatorStorageKey]; ok && opStorage != "" {
Expand Down
3 changes: 2 additions & 1 deletion caas/kubernetes/provider/cloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
gc "gopkg.in/check.v1"

"github.com/juju/juju/caas"
k8scloud "github.com/juju/juju/caas/kubernetes/cloud"
"github.com/juju/juju/caas/kubernetes/provider"
"github.com/juju/juju/cloud"
jujucloud "github.com/juju/juju/cloud"
Expand Down Expand Up @@ -166,7 +167,7 @@ func (s *cloudSuite) TestFinalizeCloudMicrok8sAlreadyStorage(c *gc.C) {
c.Assert(cloud, jc.DeepEquals, jujucloud.Cloud{
Name: caas.K8sCloudMicrok8s,
Type: jujucloud.CloudTypeCAAS,
AuthTypes: []jujucloud.AuthType{jujucloud.UserPassAuthType},
AuthTypes: k8scloud.SupportedAuthTypes(),
CACertificates: []string{""},
Endpoint: "http://1.1.1.1:8080",
HostCloudRegion: fmt.Sprintf("%s/%s", caas.K8sCloudMicrok8s, caas.Microk8sRegion),
Expand Down
Loading

0 comments on commit 000dba6

Please sign in to comment.