Prepare azidentity v1.7.0-beta.1 for release (#23029)

This commit is contained in:
Charles Lowell 2024-06-10 16:31:00 -07:00 коммит произвёл GitHub
Родитель 3b16f2e0b1
Коммит 58257dd6c9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 145 добавлений и 31 удалений

Просмотреть файл

@ -1,6 +1,20 @@
# Release History
## 1.6.0-beta.5 (Unreleased)
## 1.7.0-beta.1 (2024-06-10)
### Features Added
* Restored `AzurePipelinesCredential` and persistent token caching API
## Breaking Changes
> These changes affect only code written against a beta version such as v1.6.0-beta.4
* Values which `NewAzurePipelinesCredential` read from environment variables in
prior versions are now parameters
* Renamed `AzurePipelinesServiceConnectionCredentialOptions` to `AzurePipelinesCredentialOptions`
### Bugs Fixed
* Managed identity bug fixes
## 1.6.0 (2024-06-10)
### Features Added
* `NewOnBehalfOfCredentialWithClientAssertions` creates an on-behalf-of credential
@ -8,12 +22,11 @@
### Breaking Changes
> These changes affect only code written against a beta version such as v1.6.0-beta.4
* Renamed `AzurePipelinesServiceConnectionCredentialOptions` to `AzurePipelinesCredentialOptions`
* Removed `AzurePipelinesCredential` and the persistent token caching API.
They will return in v1.7.0-beta.1
### Bugs Fixed
### Other Changes
* Added more details to `AzurePipelinesCredential` error messages
* Managed identity bug fixes
## 1.6.0-beta.4 (2024-05-14)

Просмотреть файл

@ -14,13 +14,15 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"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/azcore/runtime"
azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/internal/log"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
@ -65,6 +67,22 @@ type managedIdentityClient struct {
probeIMDS bool
}
// arcKeyDirectory returns the directory expected to contain Azure Arc keys
var arcKeyDirectory = func() (string, error) {
switch runtime.GOOS {
case "linux":
return "/var/opt/azcmagent/tokens", nil
case "windows":
pd := os.Getenv("ProgramData")
if pd == "" {
return "", errors.New("environment variable ProgramData has no value")
}
return filepath.Join(pd, "AzureConnectedMachineAgent", "Tokens"), nil
default:
return "", fmt.Errorf("unsupported OS %q", runtime.GOOS)
}
}
type wrappedNumber json.Number
func (n *wrappedNumber) UnmarshalJSON(b []byte) error {
@ -152,8 +170,8 @@ func newManagedIdentityClient(options *ManagedIdentityCredentialOptions) (*manag
setIMDSRetryOptionDefaults(&cp.Retry)
}
client, err := azcore.NewClient(module, version, runtime.PipelineOptions{
Tracing: runtime.TracingOptions{
client, err := azcore.NewClient(module, version, azruntime.PipelineOptions{
Tracing: azruntime.TracingOptions{
Namespace: traceNamespace,
},
}, &cp)
@ -188,7 +206,7 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
cx, cancel := context.WithTimeout(ctx, imdsProbeTimeout)
defer cancel()
cx = policy.WithRetryOptions(cx, policy.RetryOptions{MaxRetries: -1})
req, err := runtime.NewRequest(cx, http.MethodGet, c.endpoint)
req, err := azruntime.NewRequest(cx, http.MethodGet, c.endpoint)
if err == nil {
_, err = c.azClient.Pipeline().Do(req)
}
@ -213,7 +231,7 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, err.Error(), nil, err)
}
if runtime.HasStatusCode(resp, http.StatusOK, http.StatusCreated) {
if azruntime.HasStatusCode(resp, http.StatusOK, http.StatusCreated) {
return c.createAccessToken(resp)
}
@ -224,21 +242,21 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "the requested identity isn't assigned to this resource", resp, nil)
}
msg := "failed to authenticate a system assigned identity"
if body, err := runtime.Payload(resp); err == nil && len(body) > 0 {
if body, err := azruntime.Payload(resp); err == nil && len(body) > 0 {
msg += fmt.Sprintf(". The endpoint responded with %s", body)
}
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, msg)
case http.StatusForbidden:
// Docker Desktop runs a proxy that responds 403 to IMDS token requests. If we get that response,
// we return credentialUnavailableError so credential chains continue to their next credential
body, err := runtime.Payload(resp)
body, err := azruntime.Payload(resp)
if err == nil && strings.Contains(string(body), "unreachable") {
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, fmt.Sprintf("unexpected response %q", string(body)))
}
}
}
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "", resp, nil)
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "authentication failed", resp, nil)
}
func (c *managedIdentityClient) createAccessToken(res *http.Response) (azcore.AccessToken, error) {
@ -249,7 +267,7 @@ func (c *managedIdentityClient) createAccessToken(res *http.Response) (azcore.Ac
ExpiresIn wrappedNumber `json:"expires_in,omitempty"` // this field should always return the number of seconds for which a token is valid
ExpiresOn interface{} `json:"expires_on,omitempty"` // the value returned in this field varies between a number and a date string
}{}
if err := runtime.UnmarshalAsJSON(res, &value); err != nil {
if err := azruntime.UnmarshalAsJSON(res, &value); err != nil {
return azcore.AccessToken{}, fmt.Errorf("internal AccessToken: %v", err)
}
if value.ExpiresIn != "" {
@ -299,7 +317,7 @@ func (c *managedIdentityClient) createAuthRequest(ctx context.Context, id Manage
}
func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
}
@ -319,7 +337,7 @@ func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id Ma
}
func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
}
@ -339,7 +357,7 @@ func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context,
}
func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
}
@ -362,7 +380,7 @@ func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id
}
func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
}
@ -385,7 +403,7 @@ func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Conte
func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resources []string) (string, error) {
// create the request to retreive the secret key challenge provided by the HIMDS service
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return "", err
}
@ -407,22 +425,36 @@ func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resour
}
header := response.Header.Get("WWW-Authenticate")
if len(header) == 0 {
return "", errors.New("did not receive a value from WWW-Authenticate header")
return "", newAuthenticationFailedError(credNameManagedIdentity, "HIMDS response has no WWW-Authenticate header", nil, nil)
}
// the WWW-Authenticate header is expected in the following format: Basic realm=/some/file/path.key
pos := strings.LastIndex(header, "=")
if pos == -1 {
return "", fmt.Errorf("did not receive a correct value from WWW-Authenticate header: %s", header)
_, p, found := strings.Cut(header, "=")
if !found {
return "", newAuthenticationFailedError(credNameManagedIdentity, "unexpected WWW-Authenticate header from HIMDS: "+header, nil, nil)
}
key, err := os.ReadFile(header[pos+1:])
expected, err := arcKeyDirectory()
if err != nil {
return "", fmt.Errorf("could not read file (%s) contents: %v", header[pos+1:], err)
return "", err
}
if filepath.Dir(p) != expected || !strings.HasSuffix(p, ".key") {
return "", newAuthenticationFailedError(credNameManagedIdentity, "unexpected file path from HIMDS service: "+p, nil, nil)
}
f, err := os.Stat(p)
if err != nil {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("could not stat %q: %v", p, err), nil, nil)
}
if s := f.Size(); s > 4096 {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("key is too large (%d bytes)", s), nil, nil)
}
key, err := os.ReadFile(p)
if err != nil {
return "", newAuthenticationFailedError(credNameManagedIdentity, fmt.Sprintf("could not read %q: %v", p, err), nil, nil)
}
return string(key), nil
}
func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, id ManagedIDKind, resources []string, key string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodGet, c.endpoint)
request, err := azruntime.NewRequest(ctx, http.MethodGet, c.endpoint)
if err != nil {
return nil, err
}
@ -444,7 +476,7 @@ func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, i
}
func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) {
request, err := runtime.NewRequest(ctx, http.MethodPost, c.endpoint)
request, err := azruntime.NewRequest(ctx, http.MethodPost, c.endpoint)
if err != nil {
return nil, err
}

Просмотреть файл

@ -7,6 +7,7 @@
package azidentity
import (
"bytes"
"context"
"fmt"
"net/http"
@ -14,13 +15,14 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"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/azcore/runtime"
azruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/internal/mock"
"github.com/Azure/azure-sdk-for-go/sdk/internal/recording"
"github.com/stretchr/testify/require"
@ -32,7 +34,11 @@ const (
)
func TestManagedIdentityCredential_AzureArc(t *testing.T) {
file, err := os.Create(filepath.Join(t.TempDir(), "arc.key"))
d := t.TempDir()
before := arcKeyDirectory
arcKeyDirectory = func() (string, error) { return d, nil }
defer func() { arcKeyDirectory = before }()
file, err := os.Create(filepath.Join(d, "arc.key"))
if err != nil {
t.Fatal(err)
}
@ -150,6 +156,69 @@ func TestManagedIdentityCredential_AzureArcErrors(t *testing.T) {
t.Fatal("expected an error")
}
})
t.Run("key too large", func(t *testing.T) {
d := t.TempDir()
f := filepath.Join(d, "test.key")
err := os.WriteFile(f, bytes.Repeat([]byte("."), 4097), 0600)
require.NoError(t, err)
before := arcKeyDirectory
arcKeyDirectory = func() (string, error) { return d, nil }
defer func() { arcKeyDirectory = before }()
srv, close := mock.NewServer(mock.WithTransformAllRequestsToTestServerUrl())
defer close()
srv.AppendResponse(
mock.WithHeader("WWW-Authenticate", "Basic realm="+f),
mock.WithStatusCode(http.StatusUnauthorized),
)
cred, err := NewManagedIdentityCredential(&ManagedIdentityCredentialOptions{ClientOptions: azcore.ClientOptions{Transport: srv}})
require.NoError(t, err)
_, err = cred.GetToken(ctx, testTRO)
require.ErrorContains(t, err, "too large")
})
t.Run("unexpected file paths", func(t *testing.T) {
d, err := arcKeyDirectory()
if err != nil {
// test is running on an unsupported OS e.g. darwin
t.Skip(err)
}
srv, close := mock.NewServer(mock.WithTransformAllRequestsToTestServerUrl())
defer close()
srv.AppendResponse(
// unexpected directory
mock.WithHeader("WWW-Authenticate", "Basic realm="+filepath.Join("foo", "bar.key")),
mock.WithStatusCode(http.StatusUnauthorized),
)
o := ManagedIdentityCredentialOptions{ClientOptions: azcore.ClientOptions{Transport: srv}}
cred, err := NewManagedIdentityCredential(&o)
require.NoError(t, err)
_, err = cred.GetToken(ctx, testTRO)
require.ErrorContains(t, err, "unexpected file path")
srv.AppendResponse(
// unexpected extension
mock.WithHeader("WWW-Authenticate", "Basic realm="+filepath.Join(d, "foo")),
mock.WithStatusCode(http.StatusUnauthorized),
)
cred, err = NewManagedIdentityCredential(&o)
require.NoError(t, err)
_, err = cred.GetToken(ctx, testTRO)
require.ErrorContains(t, err, "unexpected file path")
})
if runtime.GOOS == "windows" {
t.Run("ProgramData not set", func(t *testing.T) {
t.Setenv("ProgramData", "")
srv, close := mock.NewServer(mock.WithTransformAllRequestsToTestServerUrl())
defer close()
srv.AppendResponse(
mock.WithHeader("WWW-Authenticate", "Basic realm=foo"),
mock.WithStatusCode(http.StatusUnauthorized),
)
cred, err := NewManagedIdentityCredential(&ManagedIdentityCredentialOptions{ClientOptions: azcore.ClientOptions{Transport: srv}})
require.NoError(t, err)
_, err = cred.GetToken(ctx, testTRO)
require.ErrorContains(t, err, "ProgramData")
})
}
}
func TestManagedIdentityCredential_AzureContainerInstanceLive(t *testing.T) {
@ -184,7 +253,7 @@ func TestManagedIdentityCredential_AzureFunctionsLive(t *testing.T) {
res, err := http.Get(url)
require.NoError(t, err)
if res.StatusCode != http.StatusOK {
b, err := runtime.Payload(res)
b, err := azruntime.Payload(res)
require.NoError(t, err)
t.Fatal("test application returned an error: " + string(b))
}

Просмотреть файл

@ -14,5 +14,5 @@ const (
module = "github.com/Azure/azure-sdk-for-go/sdk/" + component
// Version is the semantic version (see http://semver.org) of this module.
version = "v1.6.0-beta.5"
version = "v1.7.0-beta.1"
)