Skip to content

Commit

Permalink
Introduce KeyVaultCredential
Browse files Browse the repository at this point in the history
  • Loading branch information
zarvd committed Apr 3, 2024
1 parent c7384e7 commit a2d2828
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 7 deletions.
4 changes: 4 additions & 0 deletions pkg/azclient/arm_conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,7 @@ func GetAzCoreClientOption(armConfig *ARMClientConfig) (*policy.ClientOptions, e
}
return &azCoreClientConfig, nil
}

func IsMultiTenant(armConfig *ARMClientConfig) bool {
return armConfig != nil && armConfig.NetworkResourceTenantID != "" && !strings.EqualFold(armConfig.NetworkResourceTenantID, armConfig.GetTenantID())
}
59 changes: 59 additions & 0 deletions pkg/azclient/armauth/auxiliary_auth_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
Copyright 2024 The Kubernetes 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 armauth

import (
"context"
"fmt"
"net/http"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
)

const (
HeaderAuthorizationAuxiliary = "x-ms-authorization-auxiliary"
)

type AuxiliaryAuthPolicy struct {
credentials []azcore.TokenCredential
scope string
}

func NewAuxiliaryAuthPolicy(credentials []azcore.TokenCredential, scope string) *AuxiliaryAuthPolicy {
return &AuxiliaryAuthPolicy{
credentials: credentials,
scope: scope,
}
}

func (p *AuxiliaryAuthPolicy) Do(req *policy.Request) (*http.Response, error) {
tokens := make([]string, 0, len(p.credentials))

for _, cred := range p.credentials {
token, err := cred.GetToken(context.TODO(), policy.TokenRequestOptions{
Scopes: []string{p.scope},
})
if err != nil {
return nil, err
}
tokens = append(tokens, fmt.Sprintf("Bearer %s", token.Token))
}
req.Raw().Header.Set(HeaderAuthorizationAuxiliary, strings.Join(tokens, ", "))
return req.Next()
}
102 changes: 102 additions & 0 deletions pkg/azclient/armauth/keyvault_credential.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright 2024 The Kubernetes 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 armauth

import (
"context"
"encoding/json"
"fmt"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)

type KeyVaultCredential struct {
secretClient *azsecrets.Client
secretPath string

token *azcore.AccessToken
}

type KeyVaultCredentialSecret struct {
AccessToken string `json:"access_token"`
ExpiresOn time.Time `json:"expires_on"`
}

func NewKeyVaultCredential(
msiCredential azcore.TokenCredential,
keyVaultURL string,
secretName string,
) (*KeyVaultCredential, error) {
cli, err := azsecrets.NewClient(keyVaultURL, msiCredential, nil)
if err != nil {
return nil, fmt.Errorf("create KeyVault client: %w", err)
}

rv := &KeyVaultCredential{
secretClient: cli,
secretPath: secretName,
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := rv.refreshToken(ctx); err != nil {
return nil, fmt.Errorf("refresh token: %w", err)
}

return rv, nil
}

func (c *KeyVaultCredential) refreshToken(ctx context.Context) error {
const LatestVersion = ""

resp, err := c.secretClient.GetSecret(ctx, c.secretPath, LatestVersion, nil)
if err != nil {
return err
}
if resp.Value == nil {
return fmt.Errorf("secret value is nil")
}

var secret KeyVaultCredentialSecret
if err := json.Unmarshal([]byte(*resp.Value), &secret); err != nil {
return fmt.Errorf("unmarshal secret value `%s`: %w", *resp.Value, err)
}

c.token = &azcore.AccessToken{
Token: secret.AccessToken,
ExpiresOn: secret.ExpiresOn,
}

return nil
}

func (c *KeyVaultCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
const RefreshTokenOffset = 5 * time.Minute

if c.token != nil && c.token.ExpiresOn.Add(RefreshTokenOffset).Before(time.Now()) {
return *c.token, nil
}

if err := c.refreshToken(ctx); err != nil {
return azcore.AccessToken{}, fmt.Errorf("refresh token: %w", err)
}

return *c.token, nil
}
38 changes: 31 additions & 7 deletions pkg/azclient/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,21 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"

"sigs.k8s.io/cloud-provider-azure/pkg/azclient/armauth"
)

type AuthProvider struct {
FederatedIdentityCredential azcore.TokenCredential
ManagedIdentityCredential azcore.TokenCredential
ClientSecretCredential azcore.TokenCredential
FederatedIdentityCredential azcore.TokenCredential

ManagedIdentityCredential azcore.TokenCredential
ClientSecretCredential azcore.TokenCredential
ClientCertificateCredential azcore.TokenCredential

NetworkTokenCredential azcore.TokenCredential
NetworkClientSecretCredential azcore.TokenCredential
MultiTenantCredential azcore.TokenCredential
ClientCertificateCredential azcore.TokenCredential

MultiTenantCredential azcore.TokenCredential
}

func NewAuthProvider(armConfig *ARMClientConfig, config *AzureAuthConfig, clientOptionsMutFn ...func(option *policy.ClientOptions)) (*AuthProvider, error) {
Expand Down Expand Up @@ -76,6 +82,20 @@ func NewAuthProvider(armConfig *ARMClientConfig, config *AzureAuthConfig, client
}
}

var (
networkTokenCredential azcore.TokenCredential
)
if config.UseManagedIdentityExtension && config.AuxiliaryTokenProvider != nil && IsMultiTenant(armConfig) {
networkTokenCredential, err = armauth.NewKeyVaultCredential(
managedIdentityCredential,
config.AuxiliaryTokenProvider.KeyVaultURL,
config.AuxiliaryTokenProvider.SecretName,
)
if err != nil {
return nil, fmt.Errorf("create KeyVaultCredential for auxiliary token provider: %w", err)
}
}

// ClientSecretCredential is used for client secret
var clientSecretCredential azcore.TokenCredential
var networkClientSecretCredential azcore.TokenCredential
Expand All @@ -88,7 +108,7 @@ func NewAuthProvider(armConfig *ARMClientConfig, config *AzureAuthConfig, client
if err != nil {
return nil, err
}
if len(armConfig.NetworkResourceTenantID) > 0 && !strings.EqualFold(armConfig.NetworkResourceTenantID, armConfig.GetTenantID()) {
if IsMultiTenant(armConfig) {
credOptions := &azidentity.ClientSecretCredentialOptions{
ClientOptions: *clientOption,
}
Expand Down Expand Up @@ -128,7 +148,7 @@ func NewAuthProvider(armConfig *ARMClientConfig, config *AzureAuthConfig, client
if err != nil {
return nil, err
}
if len(armConfig.NetworkResourceTenantID) > 0 && !strings.EqualFold(armConfig.NetworkResourceTenantID, armConfig.GetTenantID()) {
if IsMultiTenant(armConfig) {
networkClientSecretCredential, err = azidentity.NewClientCertificateCredential(armConfig.NetworkResourceTenantID, config.GetAADClientID(), certificate, privateKey, credOptions)
if err != nil {
return nil, err
Expand All @@ -150,6 +170,7 @@ func NewAuthProvider(armConfig *ARMClientConfig, config *AzureAuthConfig, client
ClientSecretCredential: clientSecretCredential,
ClientCertificateCredential: clientCertificateCredential,
NetworkClientSecretCredential: networkClientSecretCredential,
NetworkTokenCredential: networkTokenCredential,
MultiTenantCredential: multiTenantCredential,
}, nil
}
Expand All @@ -173,6 +194,9 @@ func (factory *AuthProvider) GetNetworkAzIdentity() azcore.TokenCredential {
if factory.NetworkClientSecretCredential != nil {
return factory.NetworkClientSecretCredential
}
if factory.NetworkTokenCredential != nil {
return factory.NetworkTokenCredential
}
return nil
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/azclient/auth_conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ type AzureAuthConfig struct {
AADFederatedTokenFile string `json:"aadFederatedTokenFile,omitempty" yaml:"aadFederatedTokenFile,omitempty"`
// Use workload identity federation for the virtual machine to access Azure ARM APIs
UseFederatedWorkloadIdentityExtension bool `json:"useFederatedWorkloadIdentityExtension,omitempty" yaml:"useFederatedWorkloadIdentityExtension,omitempty"`
// Auxiliary token provider for accessing resources from network tenant
// Require MSI to be enabled and have permission to access the KeyVault
AuxiliaryTokenProvider *AzureAuthAuxiliaryTokenProvider `json:"auxiliaryTokenProvider,omitempty" yaml:"auxiliaryTokenProvider,omitempty"`
}

type AzureAuthAuxiliaryTokenProvider struct {
KeyVaultURL string `json:"keyVaultURL,omitempty" yaml:"keyVaultURL,omitempty"`
SecretName string `json:"secretName" yaml:"secretName"`
}

func (config *AzureAuthConfig) GetAADClientID() string {
Expand Down
5 changes: 5 additions & 0 deletions pkg/azclient/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.20
require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.6.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v4 v4.8.0
Expand All @@ -16,6 +17,7 @@ require (
github.com/google/uuid v1.6.0
github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.32.0
github.com/stretchr/testify v1.8.4
go.uber.org/mock v0.4.0
golang.org/x/crypto v0.21.0
golang.org/x/sync v0.6.0
Expand All @@ -26,7 +28,9 @@ require (

require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
Expand All @@ -35,6 +39,7 @@ require (
github.com/kr/pretty v0.3.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions pkg/azclient/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 h1:xnO4sFyG8UH2fElBkcqLTOZsAajvKfnSlgBBW8dXYjw=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0/go.mod h1:XD3DIOOVgBCO03OleB1fHjgktVRFxlT++KwKgIOewdM=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.6.0 h1:ui3YNbxfW7J3tTFIZMH6LIGRjCngp+J+nIFlnizfNTE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.6.0/go.mod h1:gZmgV+qBqygoznvqo2J9oKZAFziqhLZ2xE/WVUmzkHA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v1.2.0 h1:DWlwvVV5r/Wy1561nZ3wrpI1/vDIBRY/Wd1HWaRBZWA=
Expand Down Expand Up @@ -65,6 +69,7 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
Expand Down

0 comments on commit a2d2828

Please sign in to comment.