-
Notifications
You must be signed in to change notification settings - Fork 157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add azure managed identity to azure package in blockstorage #1615
Changes from 12 commits
7e4f2f6
1250b28
9401cb7
8daf339
f867abc
f38bbab
3055210
5e0819a
1d425f4
b0d1bbc
183b99e
867878e
60a3d63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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] != "" && | ||
leuyentran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add comments for exported methods/structs in this PR. |
||
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{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: consider renaming to msiAzureAuthenticator |
||
|
||
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 | ||
Comment on lines
+59
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TIOLI: Consider just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The caller, i.e. |
||
} | ||
|
||
// authenticate with client secret creds | ||
type clientSecretAuthenticator struct{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: consider renaming to clientSecretAzureAuthenitcator. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the package name is |
||
|
||
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 | ||
} |
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) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this package will be deprecated next year. Can we look into using
azidentity
instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of interface
Authenticate()
is to validate Azure creds as a one-of action. These creds are then passed on to anAuthorizer()
that later authenticates and return a token for every Azure API calls that interact with Azure resources.And for
Authorizer()
, we are still usingadal
indirectly as part ofgithub.com/Azure/go-autorest/autorest/azure/auth
Furthermore,
azidentity
andadal
differs slightly in their credential definitions and workflows. So I'd prefer not to useazidentity
forAuthenticate()
andadal
forAuthorizer()
in case we don't completely understand the nuances in expectations.Instead, I had abstracted out the logics as much as I can, which should make APIs swapping easier during migration :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you create a GH issue to track the migration task for both the
adal
andauth
packages? Thanks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1621 fyi @ihcsim