Skip to content

Commit

Permalink
Add azure managed identity to azure package in blockstorage (#1615)
Browse files Browse the repository at this point in the history
* Add utilities functions for Azure authentication + unit test

to be added for utilities

Signed-off-by: Le Tran <le.tran@kasten.io>

* Add an interface for Azure blockstorage to Authenticate credentials

to be added fro authenticate

Signed-off-by: Le Tran <le.tran@kasten.io>

* Add an option to use MSI Authorizer for Azure

Signed-off-by: Le Tran <le.tran@kasten.io>

* Add guarding for nil + clean up comment

Signed-off-by: Le Tran <le.tran@kasten.io>

* Update gomod and gosum files

Signed-off-by: Le Tran <le.tran@kasten.io>

* Expose functions checking for creds

Signed-off-by: Le Tran <le.tran@kasten.io>

* Add authenticator for client secret credentials type

Signed-off-by: Le Tran <le.tran@kasten.io>

* Clean up according to PR reviews

Signed-off-by: Le Tran <le.tran@kasten.io>

* Remove CredAvailibility, authentication status will be inferred from error

Signed-off-by: Le Tran <le.tran@kasten.io>

* Address review comments

Signed-off-by: Le Tran <le.tran@kasten.io>

* Make Azure authenticator a public interface that can be used directly

Signed-off-by: Le Tran <le.tran@kasten.io>

Signed-off-by: Le Tran <le.tran@kasten.io>
Co-authored-by: Le Tran <le.tran@kasten.io>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 5, 2022
1 parent 6eb5736 commit 09018c8
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 17 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ replace (
require (
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.28
github.com/Azure/go-autorest/autorest/adal v0.9.21
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/BurntSushi/toml v1.2.0
Expand Down Expand Up @@ -72,7 +73,6 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEg
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A=
github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk=
github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4=
Expand Down
108 changes: 108 additions & 0 deletions pkg/blockstorage/azure/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package azure

import (
"context"

"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/kanisterio/kanister/pkg/blockstorage"
"github.com/pkg/errors"
)

// currently avaialble types: https://docs.microsoft.com/en-us/azure/developer/go/azure-sdk-authorization
// to be available with azidentity: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#readme-credential-types
// determine if the combination of creds are client secret creds
func isClientCredsAvailable(config map[string]string) bool {
return (config[blockstorage.AzureTenantID] != "" &&
config[blockstorage.AzureCientID] != "" &&
config[blockstorage.AzureClentSecret] != "")
}

// determine if the combination of creds are MSI creds
func isMSICredsAvailable(config map[string]string) bool {
return (config[blockstorage.AzureTenantID] == "" &&
config[blockstorage.AzureCientID] != "" &&
config[blockstorage.AzureClentSecret] == "")
}

// Public interface to authenticate with different Azure credentials type
type AzureAuthenticator interface {
Authenticate(creds map[string]string) error
}

func NewAzureAutheticator(config map[string]string) (AzureAuthenticator, error) {
switch {
case isMSICredsAvailable(config):
return &msiAuthenticator{}, nil
case isClientCredsAvailable(config):
return &clientSecretAuthenticator{}, nil
default:
return nil, errors.New("Fail to get an authenticator for provided creds combination")
}
}

// authenticate with MSI creds
type msiAuthenticator struct{}

func (m *msiAuthenticator) Authenticate(creds map[string]string) error {
// check if MSI endpoint is available
if !adal.MSIAvailable(context.Background(), nil) {
return errors.New("MSI endpoint is not supported")
}
// create a service principal token
msiConfig := auth.NewMSIConfig()
msiConfig.ClientID = creds[blockstorage.AzureCientID]
spt, err := msiConfig.ServicePrincipalToken()
if err != nil {
return errors.Wrap(err, "Failed to create a service principal token")
}
// network call to check for token
err = spt.Refresh()
if err != nil {
return errors.Wrap(err, "Failed to refresh token")
}
// creds passed authentication
return nil
}

// authenticate with client secret creds
type clientSecretAuthenticator struct{}

func (c *clientSecretAuthenticator) Authenticate(creds map[string]string) error {
credConfig, err := getCredConfigForAuth(creds)
if err != nil {
return errors.Wrap(err, "Failed to get Client Secret config")
}
// create a service principal token
spt, err := credConfig.ServicePrincipalToken()
if err != nil {
return errors.Wrap(err, "Failed to create a service principal token")
}
// network call to check for token
err = spt.Refresh()
if err != nil {
return errors.Wrap(err, "Failed to refresh token")
}
// creds passed authentication
return nil
}

func getCredConfigForAuth(config map[string]string) (auth.ClientCredentialsConfig, error) {
tenantID, ok := config[blockstorage.AzureTenantID]
if !ok {
return auth.ClientCredentialsConfig{}, errors.New("Cannot get tenantID from config")
}

clientID, ok := config[blockstorage.AzureCientID]
if !ok {
return auth.ClientCredentialsConfig{}, errors.New("Cannot get clientID from config")
}

clientSecret, ok := config[blockstorage.AzureClentSecret]
if !ok {
return auth.ClientCredentialsConfig{}, errors.New("Cannot get clientSecret from config")
}

credConfig := auth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
return credConfig, nil
}
94 changes: 94 additions & 0 deletions pkg/blockstorage/azure/auth_tests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2022 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package azure

import (
"github.com/kanisterio/kanister/pkg/blockstorage"
. "gopkg.in/check.v1"
)

type AuthSuite struct{}

var _ = Suite(&AuthSuite{})

func (s *AuthSuite) SetUpSuite(c *C) {
}

func (s *AuthSuite) TestIsClientCredsvailable(c *C) {
// success
config := map[string]string{
blockstorage.AzureTenantID: "some-tenant-id",
blockstorage.AzureCientID: "some-client-id",
blockstorage.AzureClentSecret: "someclient-secret",
}
c.Assert(isClientCredsAvailable(config), Equals, true)

// remove tenantID
delete(config, blockstorage.AzureTenantID)
c.Assert(isClientCredsAvailable(config), Equals, false)

// remove client secret, only client ID left
delete(config, blockstorage.AzureClentSecret)
c.Assert(isClientCredsAvailable(config), Equals, false)
}

func (s *AuthSuite) TestIsMSICredsAvailable(c *C) {
// success
config := map[string]string{
blockstorage.AzureTenantID: "some-tenant-id",
blockstorage.AzureCientID: "some-client-id",
blockstorage.AzureClentSecret: "someclient-secret",
}
c.Assert(isMSICredsAvailable(config), Equals, false)

// remove tenantID
delete(config, blockstorage.AzureTenantID)
c.Assert(isMSICredsAvailable(config), Equals, false)

// remove client secret, only client ID left
delete(config, blockstorage.AzureClentSecret)
c.Assert(isMSICredsAvailable(config), Equals, true)
}

func (s *AuthSuite) TestNewAzureAutheticator(c *C) {
// successful with client secret creds
config := map[string]string{
blockstorage.AzureTenantID: "some-tenant-id",
blockstorage.AzureCientID: "some-client-id",
blockstorage.AzureClentSecret: "some-client-secret",
}
authenticator, err := NewAzureAutheticator(config)
c.Assert(err, IsNil)
_, ok := authenticator.(*clientSecretAuthenticator)
c.Assert(ok, Equals, true)

// successful with msi creds
config = map[string]string{
blockstorage.AzureCientID: "some-client-id",
}
authenticator, err = NewAzureAutheticator(config)
c.Assert(err, IsNil)
_, ok = authenticator.(*msiAuthenticator)
c.Assert(ok, Equals, true)

// unsuccessful with an undefined combo of creds
config = map[string]string{
blockstorage.AzureCientID: "some-client-id",
blockstorage.AzureClentSecret: "some-client-secret",
}
authenticator, err = NewAzureAutheticator(config)
c.Assert(err, NotNil)
c.Assert(authenticator, IsNil)
}
41 changes: 26 additions & 15 deletions pkg/blockstorage/azure/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,39 +98,50 @@ func NewClient(ctx context.Context, config map[string]string) (*Client, error) {

// nolint:unparam
func getAuthorizer(env azure.Environment, config map[string]string) (*autorest.BearerAuthorizer, error) {
if isClientCredsAvailable(config) {
return getClientCredsAuthorizer(env, config)
} else if isMSICredsAvailable(config) {
return getMSIsAuthorizer(config)
}
return nil, errors.New("Missing credentials, or credential type not supported")
}

func getClientCredsAuthorizer(env azure.Environment, config map[string]string) (*autorest.BearerAuthorizer, error) {
credConfig, err := getCredConfig(env, config)
if err != nil {
return nil, errors.Wrap(err, "Failed to get Azure ClientCredentialsConfig")
return nil, errors.Wrap(err, "Failed to get Azure Client Credentials Config")
}
a, err := credConfig.Authorizer()
if err != nil {
return nil, errors.Wrap(err, "Failed to get Azure authorizer")
return nil, errors.Wrap(err, "Failed to get Azure Client Credentials authorizer")
}

ba, ok := a.(*autorest.BearerAuthorizer)
if !ok {
return nil, errors.New("Failed to get Azure authorizer")
}
return ba, nil
}

func getCredConfig(env azure.Environment, config map[string]string) (auth.ClientCredentialsConfig, error) {
tenantID, ok := config[blockstorage.AzureTenantID]
if !ok {
return auth.ClientCredentialsConfig{}, errors.New("Cannot get tenantID from config")
func getMSIsAuthorizer(config map[string]string) (*autorest.BearerAuthorizer, error) {
msiConfig := auth.NewMSIConfig()
msiConfig.ClientID = config[blockstorage.AzureCientID]
a, err := msiConfig.Authorizer()
if err != nil {
return nil, errors.Wrap(err, "Failed to get Azure MSI authorizer")
}

clientID, ok := config[blockstorage.AzureCientID]
ba, ok := a.(*autorest.BearerAuthorizer)
if !ok {
return auth.ClientCredentialsConfig{}, errors.New("Cannot get clientID from config")
return nil, errors.New("Failed to get Azure authorizer")
}
return ba, nil
}

clientSecret, ok := config[blockstorage.AzureClentSecret]
if !ok {
return auth.ClientCredentialsConfig{}, errors.New("Cannot get clientSecret from config")
func getCredConfig(env azure.Environment, config map[string]string) (auth.ClientCredentialsConfig, error) {
credConfig, err := getCredConfigForAuth(config)
if err != nil {
return auth.ClientCredentialsConfig{}, err
}

credConfig := auth.NewClientCredentialsConfig(clientID, clientSecret, tenantID)
var ok bool
if credConfig.AADEndpoint, ok = config[blockstorage.AzureActiveDirEndpoint]; !ok || credConfig.AADEndpoint == "" {
credConfig.AADEndpoint = env.ActiveDirectoryEndpoint
config[blockstorage.AzureActiveDirEndpoint] = credConfig.AADEndpoint
Expand Down

0 comments on commit 09018c8

Please sign in to comment.