From d6bfabb71e7b0a510bded86aaa12940a4d3c5395 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Tue, 24 Sep 2024 14:42:42 +1000 Subject: [PATCH 1/4] chore: migrate azure-sdk-for-go/containerregistry to the latest release Signed-off-by: Shahram Kalantari --- go.mod | 17 +++++----- go.sum | 34 ++++++++++--------- .../oras/authprovider/azure/azureidentity.go | 19 +++++++++-- .../azure/azureworkloadidentity.go | 20 +++++++++-- 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 7256e34b5..60727f04f 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,9 @@ retract ( require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 + github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.2 github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 github.com/aws/aws-sdk-go-v2 v1.31.0 github.com/aws/aws-sdk-go-v2/config v1.27.36 @@ -130,7 +131,7 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect @@ -234,14 +235,14 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.27.0 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 28ceffc59..fb725836e 100644 --- a/go.sum +++ b/go.sum @@ -18,12 +18,14 @@ github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.2 h1:wBx10efdJcl8FSewgc41kAW4AvHPgmJZmN7fpNxn8rc= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.2/go.mod h1:zzmu18cpAinSbhC86oWd47nmgbb91Fl+Yac2PE8NdYk= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= @@ -780,8 +782,8 @@ golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= @@ -823,8 +825,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -871,8 +873,8 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -883,8 +885,8 @@ golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -897,8 +899,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 0a5a00e5c..19fd78ca1 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -29,7 +29,7 @@ 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" - "github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/runtime/2019-08-15-preview/containerregistry" + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" ) type azureManagedIdentityProviderFactory struct{} @@ -135,8 +135,21 @@ func (d *azureManagedIdentityAuthProvider) Provide(ctx context.Context, artifact serverURL := "https://" + artifactHostName // create registry client and exchange AAD token for registry refresh token - refreshTokenClient := containerregistry.NewRefreshTokensClient(serverURL) - rt, err := refreshTokenClient.GetFromExchange(ctx, "access_token", artifactHostName, d.tenantID, "", d.identityToken.Token) + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, nil) // &AuthenticationClientOptions{ClientOptions: options}) + if err != nil { + return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry by azure managed identity token", re.HideStackTrace) + } + // refreshTokenClient := containerregistry.NewRefreshTokensClient(serverURL) + rt, err := client.ExchangeAADAccessTokenForACRRefreshToken( + context.Background(), + "access_token", + artifactHostName, + &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ + AccessToken: &d.identityToken.Token, + Tenant: &d.tenantID, + }, + ) + // rt, err := refreshTokenClient.GetFromExchange(ctx, "access_token", artifactHostName, d.tenantID, "", d.identityToken.Token) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureManagedIdentityLink, err, "failed to get refresh token for container registry by azure managed identity token", re.HideStackTrace) } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index a40ce4436..c5acbcee0 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -21,13 +21,13 @@ import ( "os" "time" + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" re "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/internal/logger" provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" "github.com/ratify-project/ratify/pkg/metrics" "github.com/ratify-project/ratify/pkg/utils/azureauth" - "github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/runtime/2019-08-15-preview/containerregistry" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ) @@ -130,9 +130,23 @@ func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (pro serverURL := "https://" + artifactHostName // create registry client and exchange AAD token for registry refresh token - refreshTokenClient := containerregistry.NewRefreshTokensClient(serverURL) + // TODO: Consider adding authentication client options for multicloud scenarios + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, nil) // &AuthenticationClientOptions{ClientOptions: options}) + if err != nil { + return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) + } + // refreshTokenClient := azcontainerregistry.NewRefreshTokensClient(serverURL) startTime := time.Now() - rt, err := refreshTokenClient.GetFromExchange(context.Background(), "access_token", artifactHostName, d.tenantID, "", d.aadToken.AccessToken) + rt, err := client.ExchangeAADAccessTokenForACRRefreshToken( + context.Background(), + "access_token", + artifactHostName, + &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ + AccessToken: &d.aadToken.AccessToken, + Tenant: &d.tenantID, + }, + ) + // rt, err := refreshTokenClient.GetFromExchange(context.Background(), "access_token", artifactHostName, d.tenantID, "", d.aadToken.AccessToken) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to get refresh token for container registry", re.HideStackTrace) } From 6476bb558b56146a86b041f79cfc036adcadf74b Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Sun, 29 Sep 2024 19:11:51 +1000 Subject: [PATCH 2/4] chore: refactor to enable mocking and add unit tests to azureworkloadidentity_test.go Signed-off-by: Shahram Kalantari --- go.mod | 1 + go.sum | 1 + .../azure/azureworkloadidentity.go | 65 +++++++++++++------ .../azure/azureworkloadidentity_test.go | 60 +++++++++++++++++ 4 files changed, 107 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 60727f04f..dd149976a 100644 --- a/go.mod +++ b/go.mod @@ -119,6 +119,7 @@ require ( github.com/sigstore/timestamp-authority v1.2.2 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/sourcegraph/conc v0.3.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect diff --git a/go.sum b/go.sum index fb725836e..6545947fd 100644 --- a/go.sum +++ b/go.sum @@ -659,6 +659,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index c5acbcee0..183b7f87a 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -21,7 +21,7 @@ import ( "os" "time" - "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" + azcontainerregistry "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" re "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/internal/logger" provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" @@ -33,9 +33,40 @@ import ( type AzureWIProviderFactory struct{} //nolint:revive // ignore linter to have unique type name type azureWIAuthProvider struct { - aadToken confidential.AuthResult - tenantID string - clientID string + aadToken confidential.AuthResult + tenantID string + clientID string + authClientFactory func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) + getRegistryHost func(artifact string) (string, error) + getAADAccessToken func(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) + reportMetrics func(ctx context.Context, duration int64, artifactHostName string) +} + +type authenticationClientWrapper struct { + client *azcontainerregistry.AuthenticationClient +} + +func (w *authenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { + return w.client.ExchangeAADAccessTokenForACRRefreshToken(ctx, azcontainerregistry.PostContentSchemaGrantType(grantType), service, options) +} + +type authClient interface { + ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) +} + +func NewAzureWIAuthProvider() *azureWIAuthProvider { + return &azureWIAuthProvider{ + authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) + if err != nil { + return nil, err + } + return &authenticationClientWrapper{client: client}, nil + }, + getRegistryHost: provider.GetRegistryHostName, + getAADAccessToken: azureauth.GetAADAccessToken, + reportMetrics: metrics.ReportACRExchangeDuration, + } } type azureWIAuthProviderConf struct { @@ -103,22 +134,18 @@ func (d *azureWIAuthProvider) Enabled(_ context.Context) bool { return true } -// Provide returns the credentials for a specified artifact. -// Uses Azure Workload Identity to retrieve an AAD access token which can be -// exchanged for a valid ACR refresh token for login. func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { if !d.Enabled(ctx) { return provider.AuthConfig{}, re.ErrorCodeConfigInvalid.WithComponentType(re.AuthProvider).WithDetail("azure workload identity auth provider is not properly enabled") } - // parse the artifact reference string to extract the registry host name - artifactHostName, err := provider.GetRegistryHostName(artifact) + + artifactHostName, err := d.getRegistryHost(artifact) if err != nil { return provider.AuthConfig{}, re.ErrorCodeHostNameInvalid.WithComponentType(re.AuthProvider) } - // need to refresh AAD token if it's expired if time.Now().Add(time.Minute * 5).After(d.aadToken.ExpiresOn) { - newToken, err := azureauth.GetAADAccessToken(ctx, d.tenantID, d.clientID, AADResource) + newToken, err := d.getAADAccessToken(ctx, d.tenantID, d.clientID, AADResource) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, nil, "could not refresh AAD token", re.HideStackTrace) } @@ -126,19 +153,16 @@ func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (pro logger.GetLogger(ctx, logOpt).Info("successfully refreshed AAD token") } - // add protocol to generate complete URI serverURL := "https://" + artifactHostName - - // create registry client and exchange AAD token for registry refresh token // TODO: Consider adding authentication client options for multicloud scenarios - client, err := azcontainerregistry.NewAuthenticationClient(serverURL, nil) // &AuthenticationClientOptions{ClientOptions: options}) + client, err := d.authClientFactory(serverURL, nil) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) } - // refreshTokenClient := azcontainerregistry.NewRefreshTokensClient(serverURL) + startTime := time.Now() - rt, err := client.ExchangeAADAccessTokenForACRRefreshToken( - context.Background(), + response, err := client.ExchangeAADAccessTokenForACRRefreshToken( + ctx, "access_token", artifactHostName, &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ @@ -146,11 +170,12 @@ func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (pro Tenant: &d.tenantID, }, ) - // rt, err := refreshTokenClient.GetFromExchange(context.Background(), "access_token", artifactHostName, d.tenantID, "", d.aadToken.AccessToken) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to get refresh token for container registry", re.HideStackTrace) } - metrics.ReportACRExchangeDuration(ctx, time.Since(startTime).Milliseconds(), artifactHostName) + rt := response.ACRRefreshToken + + d.reportMetrics(ctx, time.Since(startTime).Milliseconds(), artifactHostName) refreshTokenExpiry := getACRExpiryIfEarlier(d.aadToken.ExpiresOn) authConfig := provider.AuthConfig{ diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 3695ef65a..a10f374d3 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -22,9 +22,12 @@ import ( "testing" "time" + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ratifyerrors "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/pkg/common/oras/authprovider" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) // Verifies that Enabled checks if tenantID is empty or AAD token is empty @@ -131,3 +134,60 @@ func TestAzureWIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) { t.Fatalf("create auth provider should have failed: expected err %s, but got err %s", expectedErr, err) } } + +type mockAuthClient struct { + mock.Mock +} + +func (m *mockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { + args := m.Called(ctx, grantType, service, options) + return args.Get(0).(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse), args.Error(1) +} + +func TestProvide_Success(t *testing.T) { + mockClient := new(mockAuthClient) + expectedRefreshToken := "mocked_refresh_token" + mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "myregistry.azurecr.io", mock.Anything). + Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &expectedRefreshToken}, + }, nil) + + provider := &azureWIAuthProvider{ + aadToken: confidential.AuthResult{ + AccessToken: "mockToken", + ExpiresOn: time.Now().Add(time.Hour), + }, + tenantID: "mockTenantID", + clientID: "mockClientID", + authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { + return mockClient, nil + }, + getRegistryHost: func(artifact string) (string, error) { + return "myregistry.azurecr.io", nil + }, + getAADAccessToken: func(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + return confidential.AuthResult{ + AccessToken: "mockToken", + ExpiresOn: time.Now().Add(time.Hour), + }, nil + }, + reportMetrics: func(ctx context.Context, duration int64, artifactHostName string) {}, + } + + authConfig, err := provider.Provide(context.Background(), "artifact") + + assert.NoError(t, err) + // Assert that the returned refresh token matches the expected one + assert.Equal(t, expectedRefreshToken, authConfig.Password) +} + +func TestProvide_Failure_InvalidHostName(t *testing.T) { + provider := &azureWIAuthProvider{ + getRegistryHost: func(artifact string) (string, error) { + return "", errors.New("invalid hostname") + }, + } + + _, err := provider.Provide(context.Background(), "artifact") + assert.Error(t, err) +} From 5d4396cdf024bd0342430f1c63bbbed9899e8d69 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Sun, 29 Sep 2024 19:22:49 +1000 Subject: [PATCH 3/4] chore: lint Signed-off-by: Shahram Kalantari --- .../authprovider/azure/azureworkloadidentity.go | 12 ++++++------ .../azure/azureworkloadidentity_test.go | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 183b7f87a..241696bcf 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -32,7 +32,7 @@ import ( ) type AzureWIProviderFactory struct{} //nolint:revive // ignore linter to have unique type name -type azureWIAuthProvider struct { +type WIAuthProvider struct { aadToken confidential.AuthResult tenantID string clientID string @@ -54,8 +54,8 @@ type authClient interface { ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) } -func NewAzureWIAuthProvider() *azureWIAuthProvider { - return &azureWIAuthProvider{ +func NewAzureWIAuthProvider() *WIAuthProvider { + return &WIAuthProvider{ authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) if err != nil { @@ -114,7 +114,7 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider return nil, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "", re.HideStackTrace) } - return &azureWIAuthProvider{ + return &WIAuthProvider{ aadToken: token, tenantID: tenant, clientID: clientID, @@ -122,7 +122,7 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider } // Enabled checks for non empty tenant ID and AAD access token -func (d *azureWIAuthProvider) Enabled(_ context.Context) bool { +func (d *WIAuthProvider) Enabled(_ context.Context) bool { if d.tenantID == "" || d.clientID == "" { return false } @@ -134,7 +134,7 @@ func (d *azureWIAuthProvider) Enabled(_ context.Context) bool { return true } -func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { +func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { if !d.Enabled(ctx) { return provider.AuthConfig{}, re.ErrorCodeConfigInvalid.WithComponentType(re.AuthProvider).WithDetail("azure workload identity auth provider is not properly enabled") } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index a10f374d3..b4a9a1f7c 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -32,7 +32,7 @@ import ( // Verifies that Enabled checks if tenantID is empty or AAD token is empty func TestAzureWIEnabled_ExpectedResults(t *testing.T) { - azAuthProvider := azureWIAuthProvider{ + azAuthProvider := WIAuthProvider{ tenantID: "test_tenant", clientID: "test_client", aadToken: confidential.AuthResult{ @@ -152,26 +152,26 @@ func TestProvide_Success(t *testing.T) { ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &expectedRefreshToken}, }, nil) - provider := &azureWIAuthProvider{ + provider := &WIAuthProvider{ aadToken: confidential.AuthResult{ AccessToken: "mockToken", ExpiresOn: time.Now().Add(time.Hour), }, tenantID: "mockTenantID", clientID: "mockClientID", - authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { + authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { return mockClient, nil }, - getRegistryHost: func(artifact string) (string, error) { + getRegistryHost: func(_ string) (string, error) { return "myregistry.azurecr.io", nil }, - getAADAccessToken: func(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + getAADAccessToken: func(_ context.Context, _, _, _ string) (confidential.AuthResult, error) { return confidential.AuthResult{ AccessToken: "mockToken", ExpiresOn: time.Now().Add(time.Hour), }, nil }, - reportMetrics: func(ctx context.Context, duration int64, artifactHostName string) {}, + reportMetrics: func(_ context.Context, _ int64, _ string) {}, } authConfig, err := provider.Provide(context.Background(), "artifact") @@ -182,8 +182,8 @@ func TestProvide_Success(t *testing.T) { } func TestProvide_Failure_InvalidHostName(t *testing.T) { - provider := &azureWIAuthProvider{ - getRegistryHost: func(artifact string) (string, error) { + provider := &WIAuthProvider{ + getRegistryHost: func(_ string) (string, error) { return "", errors.New("invalid hostname") }, } From 4df7003020bc6d964f9a27a41d74c75f434c6ce3 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Sun, 29 Sep 2024 19:35:43 +1000 Subject: [PATCH 4/4] chore: address comments Signed-off-by: Shahram Kalantari --- pkg/common/oras/authprovider/azure/azureworkloadidentity.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 241696bcf..b35a4e6ec 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -155,7 +155,8 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider serverURL := "https://" + artifactHostName // TODO: Consider adding authentication client options for multicloud scenarios - client, err := d.authClientFactory(serverURL, nil) + var options *azcontainerregistry.AuthenticationClientOptions + client, err := d.authClientFactory(serverURL, options) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) }