rebase: bump github.com/Azure/azure-sdk-for-go/sdk/azidentity

Bumps [github.com/Azure/azure-sdk-for-go/sdk/azidentity](https://github.com/Azure/azure-sdk-for-go) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-go/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md)
- [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/internal/v1.5.1...sdk/azcore/v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azidentity
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] 2024-06-11 20:22:58 +00:00 committed by mergify[bot]
parent ec8017512f
commit b658290b37
67 changed files with 1276 additions and 355 deletions

10
go.mod
View File

@ -46,15 +46,15 @@ require (
) )
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0
) )
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/ansel1/merry v1.6.2 // indirect github.com/ansel1/merry v1.6.2 // indirect
github.com/ansel1/merry/v2 v2.0.1 // indirect github.com/ansel1/merry/v2 v2.0.1 // indirect
@ -90,7 +90,7 @@ require (
github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/cel-go v0.17.8 // indirect github.com/google/cel-go v0.17.8 // indirect
github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gnostic-models v0.6.8 // indirect

25
go.sum
View File

@ -759,12 +759,12 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
github.com/Azure/azure-sdk-for-go v62.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v62.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= 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.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= 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.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= 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.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= 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.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= 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/security/keyvault/azsecrets v1.1.0 h1:h4Zxgmi9oyZL2l8jeg1iRTqPloHktywWcu0nlJmo1tA= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0 h1:h4Zxgmi9oyZL2l8jeg1iRTqPloHktywWcu0nlJmo1tA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0/go.mod h1:LgLGXawqSreJz135Elog0ywTJDsm0Hz2k+N+6ZK35u8= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.1.0/go.mod h1:LgLGXawqSreJz135Elog0ywTJDsm0Hz2k+N+6ZK35u8=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
@ -781,8 +781,8 @@ github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcP
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/IBM/keyprotect-go-client v0.5.1/go.mod h1:5TwDM/4FRJq1ZOlwQL1xFahLWQ3TveR88VmL1u3njyI= github.com/IBM/keyprotect-go-client v0.5.1/go.mod h1:5TwDM/4FRJq1ZOlwQL1xFahLWQ3TveR88VmL1u3njyI=
@ -928,8 +928,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@ -1065,8 +1063,8 @@ github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@ -1569,8 +1567,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=

View File

@ -1,5 +1,56 @@
# Release History # Release History
## 1.11.1 (2024-04-02)
### Bugs Fixed
* Pollers that use the `Location` header won't consider `http.StatusRequestTimeout` a terminal failure.
* `runtime.Poller[T].Result` won't consider non-terminal error responses as terminal.
## 1.11.0 (2024-04-01)
### Features Added
* Added `StatusCodes` to `arm/policy.RegistrationOptions` to allow supporting non-standard HTTP status codes during registration.
* Added field `InsecureAllowCredentialWithHTTP` to `azcore.ClientOptions` and dependent authentication pipeline policies.
* Added type `MultipartContent` to the `streaming` package to support multipart/form payloads with custom Content-Type and file name.
### Bugs Fixed
* `runtime.SetMultipartFormData` won't try to stringify `[]byte` values.
* Pollers that use the `Location` header won't consider `http.StatusTooManyRequests` a terminal failure.
### Other Changes
* Update dependencies.
## 1.10.0 (2024-02-29)
### Features Added
* Added logging event `log.EventResponseError` that will contain the contents of `ResponseError.Error()` whenever an `azcore.ResponseError` is created.
* Added `runtime.NewResponseErrorWithErrorCode` for creating an `azcore.ResponseError` with a caller-supplied error code.
* Added type `MatchConditions` for use in conditional requests.
### Bugs Fixed
* Fixed a potential race condition between `NullValue` and `IsNullValue`.
* `runtime.EncodeQueryParams` will escape semicolons before calling `url.ParseQuery`.
### Other Changes
* Update dependencies.
## 1.9.2 (2024-02-06)
### Bugs Fixed
* `runtime.MarshalAsByteArray` and `runtime.MarshalAsJSON` will preserve the preexisting value of the `Content-Type` header.
### Other Changes
* Update to latest version of `internal`.
## 1.9.1 (2023-12-11) ## 1.9.1 (2023-12-11)
### Bugs Fixed ### Bugs Fixed

View File

@ -20,6 +20,11 @@ type BearerTokenOptions struct {
// policy's credential must support multitenant authentication. // policy's credential must support multitenant authentication.
AuxiliaryTenants []string AuxiliaryTenants []string
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the authentication key in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
// Scopes contains the list of permission scopes required for the token. // Scopes contains the list of permission scopes required for the token.
Scopes []string Scopes []string
} }
@ -44,6 +49,11 @@ type RegistrationOptions struct {
// The default valule is 5 minutes. // The default valule is 5 minutes.
// NOTE: Setting this to a small value might cause the policy to prematurely fail. // NOTE: Setting this to a small value might cause the policy to prematurely fail.
PollingDuration time.Duration PollingDuration time.Duration
// StatusCodes contains the slice of custom HTTP status codes to use instead
// of the default http.StatusConflict. This should only be set if a service
// returns a non-standard HTTP status code when unregistered.
StatusCodes []int
} }
// ClientOptions contains configuration settings for a client's pipeline. // ClientOptions contains configuration settings for a client's pipeline.

View File

@ -30,8 +30,9 @@ func NewPipeline(module, version string, cred azcore.TokenCredential, plOpts azr
return azruntime.Pipeline{}, err return azruntime.Pipeline{}, err
} }
authPolicy := NewBearerTokenPolicy(cred, &armpolicy.BearerTokenOptions{ authPolicy := NewBearerTokenPolicy(cred, &armpolicy.BearerTokenOptions{
AuxiliaryTenants: options.AuxiliaryTenants, AuxiliaryTenants: options.AuxiliaryTenants,
Scopes: []string{conf.Audience + "/.default"}, InsecureAllowCredentialWithHTTP: options.InsecureAllowCredentialWithHTTP,
Scopes: []string{conf.Audience + "/.default"},
}) })
perRetry := make([]azpolicy.Policy, len(plOpts.PerRetry), len(plOpts.PerRetry)+1) perRetry := make([]azpolicy.Policy, len(plOpts.PerRetry), len(plOpts.PerRetry)+1)
copy(perRetry, plOpts.PerRetry) copy(perRetry, plOpts.PerRetry)

View File

@ -64,6 +64,7 @@ func NewBearerTokenPolicy(cred azcore.TokenCredential, opts *armpolicy.BearerTok
p.scopes = make([]string, len(opts.Scopes)) p.scopes = make([]string, len(opts.Scopes))
copy(p.scopes, opts.Scopes) copy(p.scopes, opts.Scopes)
p.btp = azruntime.NewBearerTokenPolicy(cred, opts.Scopes, &azpolicy.BearerTokenOptions{ p.btp = azruntime.NewBearerTokenPolicy(cred, opts.Scopes, &azpolicy.BearerTokenOptions{
InsecureAllowCredentialWithHTTP: opts.InsecureAllowCredentialWithHTTP,
AuthorizationHandler: azpolicy.AuthorizationHandler{ AuthorizationHandler: azpolicy.AuthorizationHandler{
OnChallenge: p.onChallenge, OnChallenge: p.onChallenge,
OnRequest: p.onRequest, OnRequest: p.onRequest,

View File

@ -8,7 +8,6 @@ package runtime
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@ -16,6 +15,7 @@ import (
"time" "time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource"
armpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy" armpolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
@ -45,6 +45,9 @@ func setDefaults(r *armpolicy.RegistrationOptions) {
if r.PollingDuration == 0 { if r.PollingDuration == 0 {
r.PollingDuration = 5 * time.Minute r.PollingDuration = 5 * time.Minute
} }
if len(r.StatusCodes) == 0 {
r.StatusCodes = []int{http.StatusConflict}
}
} }
// NewRPRegistrationPolicy creates a policy object configured using the specified options. // NewRPRegistrationPolicy creates a policy object configured using the specified options.
@ -88,7 +91,7 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
// make the original request // make the original request
resp, err = req.Next() resp, err = req.Next()
// getting a 409 is the first indication that the RP might need to be registered, check error response // getting a 409 is the first indication that the RP might need to be registered, check error response
if err != nil || resp.StatusCode != http.StatusConflict { if err != nil || !runtime.HasStatusCode(resp, r.options.StatusCodes...) {
return resp, err return resp, err
} }
var reqErr requestError var reqErr requestError
@ -105,17 +108,12 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
// to the caller so its error unmarshalling will kick in // to the caller so its error unmarshalling will kick in
return resp, err return resp, err
} }
// RP needs to be registered. start by getting the subscription ID from the original request res, err := resource.ParseResourceID(req.Raw().URL.Path)
subID, err := getSubscription(req.Raw().URL.Path)
if err != nil { if err != nil {
return resp, err return resp, err
} }
// now get the RP from the error rp = res.ResourceType.Namespace
rp, err = getProvider(reqErr) logRegistrationExit := func(v any) {
if err != nil {
return resp, err
}
logRegistrationExit := func(v interface{}) {
log.Writef(LogRPRegistration, "END registration for %s: %v", rp, v) log.Writef(LogRPRegistration, "END registration for %s: %v", rp, v)
} }
log.Writef(LogRPRegistration, "BEGIN registration for %s", rp) log.Writef(LogRPRegistration, "BEGIN registration for %s", rp)
@ -124,7 +122,7 @@ func (r *rpRegistrationPolicy) Do(req *azpolicy.Request) (*http.Response, error)
rpOps := &providersOperations{ rpOps := &providersOperations{
p: r.pipeline, p: r.pipeline,
u: r.endpoint, u: r.endpoint,
subID: subID, subID: res.SubscriptionID,
} }
if _, err = rpOps.Register(&shared.ContextWithDeniedValues{Context: req.Raw().Context()}, rp); err != nil { if _, err = rpOps.Register(&shared.ContextWithDeniedValues{Context: req.Raw().Context()}, rp); err != nil {
logRegistrationExit(err) logRegistrationExit(err)
@ -189,36 +187,13 @@ func isUnregisteredRPCode(errorCode string) bool {
return false return false
} }
func getSubscription(path string) (string, error) {
parts := strings.Split(path, "/")
for i, v := range parts {
if v == "subscriptions" && (i+1) < len(parts) {
return parts[i+1], nil
}
}
return "", fmt.Errorf("failed to obtain subscription ID from %s", path)
}
func getProvider(re requestError) (string, error) {
if len(re.ServiceError.Details) > 0 {
return re.ServiceError.Details[0].Target, nil
}
return "", errors.New("unexpected empty Details")
}
// minimal error definitions to simplify detection // minimal error definitions to simplify detection
type requestError struct { type requestError struct {
ServiceError *serviceError `json:"error"` ServiceError *serviceError `json:"error"`
} }
type serviceError struct { type serviceError struct {
Code string `json:"code"` Code string `json:"code"`
Details []serviceErrorDetails `json:"details"`
}
type serviceErrorDetails struct {
Code string `json:"code"`
Target string `json:"target"`
} }
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -23,7 +23,7 @@ pr:
- sdk/azcore/ - sdk/azcore/
- eng/ - eng/
stages: extends:
- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml
parameters: parameters:
ServiceDirectory: azcore ServiceDirectory: azcore

View File

@ -8,6 +8,7 @@ package azcore
import ( import (
"reflect" "reflect"
"sync"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
@ -41,13 +42,28 @@ func NewSASCredential(sas string) *SASCredential {
} }
// holds sentinel values used to send nulls // holds sentinel values used to send nulls
var nullables map[reflect.Type]interface{} = map[reflect.Type]interface{}{} var nullables map[reflect.Type]any = map[reflect.Type]any{}
var nullablesMu sync.RWMutex
// NullValue is used to send an explicit 'null' within a request. // NullValue is used to send an explicit 'null' within a request.
// This is typically used in JSON-MERGE-PATCH operations to delete a value. // This is typically used in JSON-MERGE-PATCH operations to delete a value.
func NullValue[T any]() T { func NullValue[T any]() T {
t := shared.TypeOfT[T]() t := shared.TypeOfT[T]()
nullablesMu.RLock()
v, found := nullables[t] v, found := nullables[t]
nullablesMu.RUnlock()
if found {
// return the sentinel object
return v.(T)
}
// promote to exclusive lock and check again (double-checked locking pattern)
nullablesMu.Lock()
defer nullablesMu.Unlock()
v, found = nullables[t]
if !found { if !found {
var o reflect.Value var o reflect.Value
if k := t.Kind(); k == reflect.Map { if k := t.Kind(); k == reflect.Map {
@ -72,6 +88,9 @@ func NullValue[T any]() T {
func IsNullValue[T any](v T) bool { func IsNullValue[T any](v T) bool {
// see if our map has a sentinel object for this *T // see if our map has a sentinel object for this *T
t := reflect.TypeOf(v) t := reflect.TypeOf(v)
nullablesMu.RLock()
defer nullablesMu.RUnlock()
if o, found := nullables[t]; found { if o, found := nullables[t]; found {
o1 := reflect.ValueOf(o) o1 := reflect.ValueOf(o)
v1 := reflect.ValueOf(v) v1 := reflect.ValueOf(v)

View File

@ -46,3 +46,12 @@ func (e ETag) WeakEquals(other ETag) bool {
func (e ETag) IsWeak() bool { func (e ETag) IsWeak() bool {
return len(e) >= 4 && strings.HasPrefix(string(e), "W/\"") && strings.HasSuffix(string(e), "\"") return len(e) >= 4 && strings.HasPrefix(string(e), "W/\"") && strings.HasSuffix(string(e), "\"")
} }
// MatchConditions specifies HTTP options for conditional requests.
type MatchConditions struct {
// Optionally limit requests to resources that have a matching ETag.
IfMatch *ETag
// Optionally limit requests to resources that do not match the ETag.
IfNoneMatch *ETag
}

View File

@ -51,15 +51,15 @@ type Request struct {
values opValues values opValues
} }
type opValues map[reflect.Type]interface{} type opValues map[reflect.Type]any
// Set adds/changes a value // Set adds/changes a value
func (ov opValues) set(value interface{}) { func (ov opValues) set(value any) {
ov[reflect.TypeOf(value)] = value ov[reflect.TypeOf(value)] = value
} }
// Get looks for a value set by SetValue first // Get looks for a value set by SetValue first
func (ov opValues) get(value interface{}) bool { func (ov opValues) get(value any) bool {
v, ok := ov[reflect.ValueOf(value).Elem().Type()] v, ok := ov[reflect.ValueOf(value).Elem().Type()]
if ok { if ok {
reflect.ValueOf(value).Elem().Set(reflect.ValueOf(v)) reflect.ValueOf(value).Elem().Set(reflect.ValueOf(v))
@ -108,7 +108,7 @@ func (req *Request) Next() (*http.Response, error) {
} }
// SetOperationValue adds/changes a mutable key/value associated with a single operation. // SetOperationValue adds/changes a mutable key/value associated with a single operation.
func (req *Request) SetOperationValue(value interface{}) { func (req *Request) SetOperationValue(value any) {
if req.values == nil { if req.values == nil {
req.values = opValues{} req.values = opValues{}
} }
@ -116,7 +116,7 @@ func (req *Request) SetOperationValue(value interface{}) {
} }
// OperationValue looks for a value set by SetOperationValue(). // OperationValue looks for a value set by SetOperationValue().
func (req *Request) OperationValue(value interface{}) bool { func (req *Request) OperationValue(value any) bool {
if req.values == nil { if req.values == nil {
return false return false
} }
@ -125,46 +125,11 @@ func (req *Request) OperationValue(value interface{}) bool {
// SetBody sets the specified ReadSeekCloser as the HTTP request body, and sets Content-Type and Content-Length // SetBody sets the specified ReadSeekCloser as the HTTP request body, and sets Content-Type and Content-Length
// accordingly. If the ReadSeekCloser is nil or empty, Content-Length won't be set. If contentType is "", // accordingly. If the ReadSeekCloser is nil or empty, Content-Length won't be set. If contentType is "",
// Content-Type won't be set. // Content-Type won't be set, and if it was set, will be deleted.
// Use streaming.NopCloser to turn an io.ReadSeeker into an io.ReadSeekCloser. // Use streaming.NopCloser to turn an io.ReadSeeker into an io.ReadSeekCloser.
func (req *Request) SetBody(body io.ReadSeekCloser, contentType string) error { func (req *Request) SetBody(body io.ReadSeekCloser, contentType string) error {
var err error // clobber the existing Content-Type to preserve behavior
var size int64 return SetBody(req, body, contentType, true)
if body != nil {
size, err = body.Seek(0, io.SeekEnd) // Seek to the end to get the stream's size
if err != nil {
return err
}
}
if size == 0 {
// treat an empty stream the same as a nil one: assign req a nil body
body = nil
// RFC 9110 specifies a client shouldn't set Content-Length on a request containing no content
// (Del is a no-op when the header has no value)
req.req.Header.Del(shared.HeaderContentLength)
} else {
_, err = body.Seek(0, io.SeekStart)
if err != nil {
return err
}
req.req.Header.Set(shared.HeaderContentLength, strconv.FormatInt(size, 10))
req.Raw().GetBody = func() (io.ReadCloser, error) {
_, err := body.Seek(0, io.SeekStart) // Seek back to the beginning of the stream
return body, err
}
}
// keep a copy of the body argument. this is to handle cases
// where req.Body is replaced, e.g. httputil.DumpRequest and friends.
req.body = body
req.req.Body = body
req.req.ContentLength = size
if contentType == "" {
// Del is a no-op when the header has no value
req.req.Header.Del(shared.HeaderContentType)
} else {
req.req.Header.Set(shared.HeaderContentType, contentType)
}
return nil
} }
// RewindBody seeks the request's Body stream back to the beginning so it can be resent when retrying an operation. // RewindBody seeks the request's Body stream back to the beginning so it can be resent when retrying an operation.
@ -211,3 +176,48 @@ type PolicyFunc func(*Request) (*http.Response, error)
func (pf PolicyFunc) Do(req *Request) (*http.Response, error) { func (pf PolicyFunc) Do(req *Request) (*http.Response, error) {
return pf(req) return pf(req)
} }
// SetBody sets the specified ReadSeekCloser as the HTTP request body, and sets Content-Type and Content-Length accordingly.
// - req is the request to modify
// - body is the request body; if nil or empty, Content-Length won't be set
// - contentType is the value for the Content-Type header; if empty, Content-Type will be deleted
// - clobberContentType when true, will overwrite the existing value of Content-Type with contentType
func SetBody(req *Request, body io.ReadSeekCloser, contentType string, clobberContentType bool) error {
var err error
var size int64
if body != nil {
size, err = body.Seek(0, io.SeekEnd) // Seek to the end to get the stream's size
if err != nil {
return err
}
}
if size == 0 {
// treat an empty stream the same as a nil one: assign req a nil body
body = nil
// RFC 9110 specifies a client shouldn't set Content-Length on a request containing no content
// (Del is a no-op when the header has no value)
req.req.Header.Del(shared.HeaderContentLength)
} else {
_, err = body.Seek(0, io.SeekStart)
if err != nil {
return err
}
req.req.Header.Set(shared.HeaderContentLength, strconv.FormatInt(size, 10))
req.Raw().GetBody = func() (io.ReadCloser, error) {
_, err := body.Seek(0, io.SeekStart) // Seek back to the beginning of the stream
return body, err
}
}
// keep a copy of the body argument. this is to handle cases
// where req.Body is replaced, e.g. httputil.DumpRequest and friends.
req.body = body
req.req.Body = body
req.req.ContentLength = size
if contentType == "" {
// Del is a no-op when the header has no value
req.req.Header.Del(shared.HeaderContentType)
} else if req.req.Header.Get(shared.HeaderContentType) == "" || clobberContentType {
req.req.Header.Set(shared.HeaderContentType, contentType)
}
return nil
}

View File

@ -13,6 +13,7 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/log"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/internal/exported"
) )
@ -20,36 +21,45 @@ import (
// NewResponseError creates a new *ResponseError from the provided HTTP response. // NewResponseError creates a new *ResponseError from the provided HTTP response.
// Exported as runtime.NewResponseError(). // Exported as runtime.NewResponseError().
func NewResponseError(resp *http.Response) error { func NewResponseError(resp *http.Response) error {
respErr := &ResponseError{
StatusCode: resp.StatusCode,
RawResponse: resp,
}
// prefer the error code in the response header // prefer the error code in the response header
if ec := resp.Header.Get(shared.HeaderXMSErrorCode); ec != "" { if ec := resp.Header.Get(shared.HeaderXMSErrorCode); ec != "" {
respErr.ErrorCode = ec return NewResponseErrorWithErrorCode(resp, ec)
return respErr
} }
// if we didn't get x-ms-error-code, check in the response body // if we didn't get x-ms-error-code, check in the response body
body, err := exported.Payload(resp, nil) body, err := exported.Payload(resp, nil)
if err != nil { if err != nil {
// since we're not returning the ResponseError in this
// case we also don't want to write it to the log.
return err return err
} }
var errorCode string
if len(body) > 0 { if len(body) > 0 {
if code := extractErrorCodeJSON(body); code != "" { if fromJSON := extractErrorCodeJSON(body); fromJSON != "" {
respErr.ErrorCode = code errorCode = fromJSON
} else if code := extractErrorCodeXML(body); code != "" { } else if fromXML := extractErrorCodeXML(body); fromXML != "" {
respErr.ErrorCode = code errorCode = fromXML
} }
} }
return NewResponseErrorWithErrorCode(resp, errorCode)
}
// NewResponseErrorWithErrorCode creates an *azcore.ResponseError from the provided HTTP response and errorCode.
// Exported as runtime.NewResponseErrorWithErrorCode().
func NewResponseErrorWithErrorCode(resp *http.Response, errorCode string) error {
respErr := &ResponseError{
ErrorCode: errorCode,
StatusCode: resp.StatusCode,
RawResponse: resp,
}
log.Write(log.EventResponseError, respErr.Error())
return respErr return respErr
} }
func extractErrorCodeJSON(body []byte) string { func extractErrorCodeJSON(body []byte) string {
var rawObj map[string]interface{} var rawObj map[string]any
if err := json.Unmarshal(body, &rawObj); err != nil { if err := json.Unmarshal(body, &rawObj); err != nil {
// not a JSON object // not a JSON object
return "" return ""
@ -58,7 +68,7 @@ func extractErrorCodeJSON(body []byte) string {
// check if this is a wrapped error, i.e. { "error": { ... } } // check if this is a wrapped error, i.e. { "error": { ... } }
// if so then unwrap it // if so then unwrap it
if wrapped, ok := rawObj["error"]; ok { if wrapped, ok := rawObj["error"]; ok {
unwrapped, ok := wrapped.(map[string]interface{}) unwrapped, ok := wrapped.(map[string]any)
if !ok { if !ok {
return "" return ""
} }

View File

@ -15,24 +15,36 @@ import (
type Event = log.Event type Event = log.Event
const ( const (
EventRequest = azlog.EventRequest EventRequest = azlog.EventRequest
EventResponse = azlog.EventResponse EventResponse = azlog.EventResponse
EventRetryPolicy = azlog.EventRetryPolicy EventResponseError = azlog.EventResponseError
EventLRO = azlog.EventLRO EventRetryPolicy = azlog.EventRetryPolicy
EventLRO = azlog.EventLRO
) )
// Write invokes the underlying listener with the specified event and message.
// If the event shouldn't be logged or there is no listener then Write does nothing.
func Write(cls log.Event, msg string) { func Write(cls log.Event, msg string) {
log.Write(cls, msg) log.Write(cls, msg)
} }
func Writef(cls log.Event, format string, a ...interface{}) { // Writef invokes the underlying listener with the specified event and formatted message.
// If the event shouldn't be logged or there is no listener then Writef does nothing.
func Writef(cls log.Event, format string, a ...any) {
log.Writef(cls, format, a...) log.Writef(cls, format, a...)
} }
// SetListener will set the Logger to write to the specified listener.
func SetListener(lst func(Event, string)) { func SetListener(lst func(Event, string)) {
log.SetListener(lst) log.SetListener(lst)
} }
// Should returns true if the specified log event should be written to the log.
// By default all log events will be logged. Call SetEvents() to limit
// the log events for logging.
// If no listener has been set this will return false.
// Calling this method is useful when the message to log is computationally expensive
// and you want to avoid the overhead if its log event is not enabled.
func Should(cls log.Event) bool { func Should(cls log.Event) bool {
return log.Should(cls) return log.Should(cls)
} }

View File

@ -27,7 +27,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
_, ok := token["asyncURL"] _, ok := token["asyncURL"]
return ok return ok
} }

View File

@ -29,7 +29,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
t, ok := token["type"] t, ok := token["type"]
if !ok { if !ok {
return false return false

View File

@ -26,7 +26,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
_, ok := token["fakeURL"] _, ok := token["fakeURL"]
return ok return ok
} }

View File

@ -28,7 +28,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
t, ok := token["type"] t, ok := token["type"]
if !ok { if !ok {
return false return false
@ -103,6 +103,10 @@ func (p *Poller[T]) Poll(ctx context.Context) (*http.Response, error) {
} else if resp.StatusCode > 199 && resp.StatusCode < 300 { } else if resp.StatusCode > 199 && resp.StatusCode < 300 {
// any 2xx other than a 202 indicates success // any 2xx other than a 202 indicates success
p.CurState = poller.StatusSucceeded p.CurState = poller.StatusSucceeded
} else if pollers.IsNonTerminalHTTPStatusCode(resp) {
// the request timed out or is being throttled.
// DO NOT include this as a terminal failure. preserve
// the existing state and return the response.
} else { } else {
p.CurState = poller.StatusFailed p.CurState = poller.StatusFailed
} }

View File

@ -25,7 +25,7 @@ func Applicable(resp *http.Response) bool {
} }
// CanResume returns true if the token can rehydrate this poller type. // CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool { func CanResume(token map[string]any) bool {
_, ok := token["oplocURL"] _, ok := token["oplocURL"]
return ok return ok
} }

View File

@ -74,7 +74,7 @@ func ExtractToken(token string) ([]byte, error) {
// IsTokenValid returns an error if the specified token isn't applicable for generic type T. // IsTokenValid returns an error if the specified token isn't applicable for generic type T.
func IsTokenValid[T any](token string) error { func IsTokenValid[T any](token string) error {
raw := map[string]interface{}{} raw := map[string]any{}
if err := json.Unmarshal([]byte(token), &raw); err != nil { if err := json.Unmarshal([]byte(token), &raw); err != nil {
return err return err
} }
@ -185,3 +185,16 @@ func ResultHelper[T any](resp *http.Response, failed bool, out *T) error {
} }
return nil return nil
} }
// IsNonTerminalHTTPStatusCode returns true if the HTTP status code should be
// considered non-terminal thus eligible for retry.
func IsNonTerminalHTTPStatusCode(resp *http.Response) bool {
return exported.HasStatusCode(resp,
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
http.StatusBadGateway, // 502
http.StatusServiceUnavailable, // 503
http.StatusGatewayTimeout, // 504
)
}

View File

@ -40,5 +40,5 @@ const (
Module = "azcore" Module = "azcore"
// Version is the semantic version (see http://semver.org) of this module. // Version is the semantic version (see http://semver.org) of this module.
Version = "v1.9.1" Version = "v1.11.1"
) )

View File

@ -23,6 +23,11 @@ const (
// This includes information like the HTTP status code, headers, and request URL. // This includes information like the HTTP status code, headers, and request URL.
EventResponse Event = "Response" EventResponse Event = "Response"
// EventResponseError entries contain information about HTTP responses that returned
// an *azcore.ResponseError (i.e. responses with a non 2xx HTTP status code).
// This includes the contents of ResponseError.Error().
EventResponseError Event = "ResponseError"
// EventRetryPolicy entries contain information specific to the retry policy in use. // EventRetryPolicy entries contain information specific to the retry policy in use.
EventRetryPolicy Event = "Retry" EventRetryPolicy Event = "Retry"

View File

@ -39,6 +39,11 @@ type ClientOptions struct {
// Cloud specifies a cloud for the client. The default is Azure Public Cloud. // Cloud specifies a cloud for the client. The default is Azure Public Cloud.
Cloud cloud.Configuration Cloud cloud.Configuration
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the credential in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
// Logging configures the built-in logging policy. // Logging configures the built-in logging policy.
Logging LogOptions Logging LogOptions
@ -147,6 +152,11 @@ type BearerTokenOptions struct {
// When this field isn't set, the policy follows its default behavior of authorizing every request with a bearer token from // When this field isn't set, the policy follows its default behavior of authorizing every request with a bearer token from
// its given credential. // its given credential.
AuthorizationHandler AuthorizationHandler AuthorizationHandler AuthorizationHandler
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the bearer token in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
} }
// AuthorizationHandler allows SDK developers to insert custom logic that runs when BearerTokenPolicy must authorize a request. // AuthorizationHandler allows SDK developers to insert custom logic that runs when BearerTokenPolicy must authorize a request.

View File

@ -14,6 +14,14 @@ import (
// NewResponseError creates an *azcore.ResponseError from the provided HTTP response. // NewResponseError creates an *azcore.ResponseError from the provided HTTP response.
// Call this when a service request returns a non-successful status code. // Call this when a service request returns a non-successful status code.
// The error code will be extracted from the *http.Response, either from the x-ms-error-code
// header (preferred) or attempted to be parsed from the response body.
func NewResponseError(resp *http.Response) error { func NewResponseError(resp *http.Response) error {
return exported.NewResponseError(resp) return exported.NewResponseError(resp)
} }
// NewResponseErrorWithErrorCode creates an *azcore.ResponseError from the provided HTTP response and errorCode.
// Use this variant when the error code is in a non-standard location.
func NewResponseErrorWithErrorCode(resp *http.Response, errorCode string) error {
return exported.NewResponseErrorWithErrorCode(resp, errorCode)
}

View File

@ -24,6 +24,7 @@ type BearerTokenPolicy struct {
authzHandler policy.AuthorizationHandler authzHandler policy.AuthorizationHandler
cred exported.TokenCredential cred exported.TokenCredential
scopes []string scopes []string
allowHTTP bool
} }
type acquiringResourceState struct { type acquiringResourceState struct {
@ -55,6 +56,7 @@ func NewBearerTokenPolicy(cred exported.TokenCredential, scopes []string, opts *
cred: cred, cred: cred,
scopes: scopes, scopes: scopes,
mainResource: temporal.NewResource(acquire), mainResource: temporal.NewResource(acquire),
allowHTTP: opts.InsecureAllowCredentialWithHTTP,
} }
} }
@ -80,7 +82,7 @@ func (b *BearerTokenPolicy) Do(req *policy.Request) (*http.Response, error) {
return req.Next() return req.Next()
} }
if err := checkHTTPSForAuth(req); err != nil { if err := checkHTTPSForAuth(req, b.allowHTTP); err != nil {
return nil, err return nil, err
} }
@ -113,8 +115,8 @@ func (b *BearerTokenPolicy) Do(req *policy.Request) (*http.Response, error) {
return res, err return res, err
} }
func checkHTTPSForAuth(req *policy.Request) error { func checkHTTPSForAuth(req *policy.Request, allowHTTP bool) error {
if strings.ToLower(req.Raw().URL.Scheme) != "https" { if strings.ToLower(req.Raw().URL.Scheme) != "https" && !allowHTTP {
return errorinfo.NonRetriableError(errors.New("authenticated requests are not permitted for non TLS protected (https) endpoints")) return errorinfo.NonRetriableError(errors.New("authenticated requests are not permitted for non TLS protected (https) endpoints"))
} }
return nil return nil

View File

@ -12,13 +12,19 @@ import (
// KeyCredentialPolicy authorizes requests with a [azcore.KeyCredential]. // KeyCredentialPolicy authorizes requests with a [azcore.KeyCredential].
type KeyCredentialPolicy struct { type KeyCredentialPolicy struct {
cred *exported.KeyCredential cred *exported.KeyCredential
header string header string
prefix string prefix string
allowHTTP bool
} }
// KeyCredentialPolicyOptions contains the optional values configuring [KeyCredentialPolicy]. // KeyCredentialPolicyOptions contains the optional values configuring [KeyCredentialPolicy].
type KeyCredentialPolicyOptions struct { type KeyCredentialPolicyOptions struct {
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the authentication key in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
// Prefix is used if the key requires a prefix before it's inserted into the HTTP request. // Prefix is used if the key requires a prefix before it's inserted into the HTTP request.
Prefix string Prefix string
} }
@ -32,9 +38,10 @@ func NewKeyCredentialPolicy(cred *exported.KeyCredential, header string, options
options = &KeyCredentialPolicyOptions{} options = &KeyCredentialPolicyOptions{}
} }
return &KeyCredentialPolicy{ return &KeyCredentialPolicy{
cred: cred, cred: cred,
header: header, header: header,
prefix: options.Prefix, prefix: options.Prefix,
allowHTTP: options.InsecureAllowCredentialWithHTTP,
} }
} }
@ -44,7 +51,7 @@ func (k *KeyCredentialPolicy) Do(req *policy.Request) (*http.Response, error) {
// this prevents a panic that might be hard to diagnose and allows testing // this prevents a panic that might be hard to diagnose and allows testing
// against http endpoints that don't require authentication. // against http endpoints that don't require authentication.
if k.cred != nil { if k.cred != nil {
if err := checkHTTPSForAuth(req); err != nil { if err := checkHTTPSForAuth(req, k.allowHTTP); err != nil {
return nil, err return nil, err
} }
val := exported.KeyCredentialGet(k.cred) val := exported.KeyCredentialGet(k.cred)

View File

@ -12,13 +12,17 @@ import (
// SASCredentialPolicy authorizes requests with a [azcore.SASCredential]. // SASCredentialPolicy authorizes requests with a [azcore.SASCredential].
type SASCredentialPolicy struct { type SASCredentialPolicy struct {
cred *exported.SASCredential cred *exported.SASCredential
header string header string
allowHTTP bool
} }
// SASCredentialPolicyOptions contains the optional values configuring [SASCredentialPolicy]. // SASCredentialPolicyOptions contains the optional values configuring [SASCredentialPolicy].
type SASCredentialPolicyOptions struct { type SASCredentialPolicyOptions struct {
// placeholder for future optional values // InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the authentication key in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
} }
// NewSASCredentialPolicy creates a new instance of [SASCredentialPolicy]. // NewSASCredentialPolicy creates a new instance of [SASCredentialPolicy].
@ -26,9 +30,13 @@ type SASCredentialPolicyOptions struct {
// - header is the name of the HTTP request header in which the shared access signature is placed // - header is the name of the HTTP request header in which the shared access signature is placed
// - options contains optional configuration, pass nil to accept the default values // - options contains optional configuration, pass nil to accept the default values
func NewSASCredentialPolicy(cred *exported.SASCredential, header string, options *SASCredentialPolicyOptions) *SASCredentialPolicy { func NewSASCredentialPolicy(cred *exported.SASCredential, header string, options *SASCredentialPolicyOptions) *SASCredentialPolicy {
if options == nil {
options = &SASCredentialPolicyOptions{}
}
return &SASCredentialPolicy{ return &SASCredentialPolicy{
cred: cred, cred: cred,
header: header, header: header,
allowHTTP: options.InsecureAllowCredentialWithHTTP,
} }
} }
@ -38,7 +46,7 @@ func (k *SASCredentialPolicy) Do(req *policy.Request) (*http.Response, error) {
// this prevents a panic that might be hard to diagnose and allows testing // this prevents a panic that might be hard to diagnose and allows testing
// against http endpoints that don't require authentication. // against http endpoints that don't require authentication.
if k.cred != nil { if k.cred != nil {
if err := checkHTTPSForAuth(req); err != nil { if err := checkHTTPSForAuth(req, k.allowHTTP); err != nil {
return nil, err return nil, err
} }
req.Raw().Header.Add(k.header, exported.SASCredentialGet(k.cred)) req.Raw().Header.Add(k.header, exported.SASCredentialGet(k.cred))

View File

@ -154,7 +154,7 @@ func NewPollerFromResumeToken[T any](token string, pl exported.Pipeline, options
if err != nil { if err != nil {
return nil, err return nil, err
} }
var asJSON map[string]interface{} var asJSON map[string]any
if err := json.Unmarshal(raw, &asJSON); err != nil { if err := json.Unmarshal(raw, &asJSON); err != nil {
return nil, err return nil, err
} }
@ -240,7 +240,7 @@ func (p *Poller[T]) PollUntilDone(ctx context.Context, options *PollUntilDoneOpt
} }
start := time.Now() start := time.Now()
logPollUntilDoneExit := func(v interface{}) { logPollUntilDoneExit := func(v any) {
log.Writef(log.EventLRO, "END PollUntilDone() for %T: %v, total time: %s", p.op, v, time.Since(start)) log.Writef(log.EventLRO, "END PollUntilDone() for %T: %v, total time: %s", p.op, v, time.Since(start))
} }
log.Writef(log.EventLRO, "BEGIN PollUntilDone() for %T", p.op) log.Writef(log.EventLRO, "BEGIN PollUntilDone() for %T", p.op)
@ -334,6 +334,11 @@ func (p *Poller[T]) Result(ctx context.Context) (res T, err error) {
err = p.op.Result(ctx, p.result) err = p.op.Result(ctx, p.result)
var respErr *exported.ResponseError var respErr *exported.ResponseError
if errors.As(err, &respErr) { if errors.As(err, &respErr) {
if pollers.IsNonTerminalHTTPStatusCode(respErr.RawResponse) {
// the request failed in a non-terminal way.
// don't cache the error or mark the Poller as done
return
}
// the LRO failed. record the error // the LRO failed. record the error
p.err = err p.err = err
} else if err != nil { } else if err != nil {

View File

@ -11,9 +11,11 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
"net/textproto"
"net/url" "net/url"
"path" "path"
"strings" "strings"
@ -21,6 +23,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared" "github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
) )
// Base64Encoding is usesd to specify which base-64 encoder/decoder to use when // Base64Encoding is usesd to specify which base-64 encoder/decoder to use when
@ -42,12 +45,19 @@ func NewRequest(ctx context.Context, httpMethod string, endpoint string) (*polic
} }
// EncodeQueryParams will parse and encode any query parameters in the specified URL. // EncodeQueryParams will parse and encode any query parameters in the specified URL.
// Any semicolons will automatically be escaped.
func EncodeQueryParams(u string) (string, error) { func EncodeQueryParams(u string) (string, error) {
before, after, found := strings.Cut(u, "?") before, after, found := strings.Cut(u, "?")
if !found { if !found {
return u, nil return u, nil
} }
qp, err := url.ParseQuery(after) // starting in Go 1.17, url.ParseQuery will reject semicolons in query params.
// so, we must escape them first. note that this assumes that semicolons aren't
// being used as query param separators which is per the current RFC.
// for more info:
// https://github.com/golang/go/issues/25192
// https://github.com/golang/go/issues/50034
qp, err := url.ParseQuery(strings.ReplaceAll(after, ";", "%3B"))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -97,20 +107,22 @@ func EncodeByteArray(v []byte, format Base64Encoding) string {
func MarshalAsByteArray(req *policy.Request, v []byte, format Base64Encoding) error { func MarshalAsByteArray(req *policy.Request, v []byte, format Base64Encoding) error {
// send as a JSON string // send as a JSON string
encode := fmt.Sprintf("\"%s\"", EncodeByteArray(v, format)) encode := fmt.Sprintf("\"%s\"", EncodeByteArray(v, format))
return req.SetBody(exported.NopCloser(strings.NewReader(encode)), shared.ContentTypeAppJSON) // tsp generated code can set Content-Type so we must prefer that
return exported.SetBody(req, exported.NopCloser(strings.NewReader(encode)), shared.ContentTypeAppJSON, false)
} }
// MarshalAsJSON calls json.Marshal() to get the JSON encoding of v then calls SetBody. // MarshalAsJSON calls json.Marshal() to get the JSON encoding of v then calls SetBody.
func MarshalAsJSON(req *policy.Request, v interface{}) error { func MarshalAsJSON(req *policy.Request, v any) error {
b, err := json.Marshal(v) b, err := json.Marshal(v)
if err != nil { if err != nil {
return fmt.Errorf("error marshalling type %T: %s", v, err) return fmt.Errorf("error marshalling type %T: %s", v, err)
} }
return req.SetBody(exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppJSON) // tsp generated code can set Content-Type so we must prefer that
return exported.SetBody(req, exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppJSON, false)
} }
// MarshalAsXML calls xml.Marshal() to get the XML encoding of v then calls SetBody. // MarshalAsXML calls xml.Marshal() to get the XML encoding of v then calls SetBody.
func MarshalAsXML(req *policy.Request, v interface{}) error { func MarshalAsXML(req *policy.Request, v any) error {
b, err := xml.Marshal(v) b, err := xml.Marshal(v)
if err != nil { if err != nil {
return fmt.Errorf("error marshalling type %T: %s", v, err) return fmt.Errorf("error marshalling type %T: %s", v, err)
@ -120,10 +132,10 @@ func MarshalAsXML(req *policy.Request, v interface{}) error {
return req.SetBody(exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppXML) return req.SetBody(exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppXML)
} }
// SetMultipartFormData writes the specified keys/values as multi-part form // SetMultipartFormData writes the specified keys/values as multi-part form fields with the specified value.
// fields with the specified value. File content must be specified as a ReadSeekCloser. // File content must be specified as an [io.ReadSeekCloser] or [streaming.MultipartContent].
// All other values are treated as string values. // Byte slices will be treated as JSON. All other values are treated as string values.
func SetMultipartFormData(req *policy.Request, formData map[string]interface{}) error { func SetMultipartFormData(req *policy.Request, formData map[string]any) error {
body := bytes.Buffer{} body := bytes.Buffer{}
writer := multipart.NewWriter(&body) writer := multipart.NewWriter(&body)
@ -139,6 +151,60 @@ func SetMultipartFormData(req *policy.Request, formData map[string]interface{})
return nil return nil
} }
quoteEscaper := strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
writeMultipartContent := func(fieldname string, mpc streaming.MultipartContent) error {
if mpc.Body == nil {
return errors.New("streaming.MultipartContent.Body cannot be nil")
}
// use fieldname for the file name when unspecified
filename := fieldname
if mpc.ContentType == "" && mpc.Filename == "" {
return writeContent(fieldname, filename, mpc.Body)
}
if mpc.Filename != "" {
filename = mpc.Filename
}
// this is pretty much copied from multipart.Writer.CreateFormFile
// but lets us set the caller provided Content-Type and filename
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
quoteEscaper.Replace(fieldname), quoteEscaper.Replace(filename)))
contentType := "application/octet-stream"
if mpc.ContentType != "" {
contentType = mpc.ContentType
}
h.Set("Content-Type", contentType)
fd, err := writer.CreatePart(h)
if err != nil {
return err
}
// copy the data to the form file
if _, err = io.Copy(fd, mpc.Body); err != nil {
return err
}
return nil
}
// the same as multipart.Writer.WriteField but lets us specify the Content-Type
writeField := func(fieldname, contentType string, value string) error {
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"`, quoteEscaper.Replace(fieldname)))
h.Set("Content-Type", contentType)
fd, err := writer.CreatePart(h)
if err != nil {
return err
}
if _, err = fd.Write([]byte(value)); err != nil {
return err
}
return nil
}
for k, v := range formData { for k, v := range formData {
if rsc, ok := v.(io.ReadSeekCloser); ok { if rsc, ok := v.(io.ReadSeekCloser); ok {
if err := writeContent(k, k, rsc); err != nil { if err := writeContent(k, k, rsc); err != nil {
@ -152,13 +218,35 @@ func SetMultipartFormData(req *policy.Request, formData map[string]interface{})
} }
} }
continue continue
} else if mpc, ok := v.(streaming.MultipartContent); ok {
if err := writeMultipartContent(k, mpc); err != nil {
return err
}
continue
} else if mpcs, ok := v.([]streaming.MultipartContent); ok {
for _, mpc := range mpcs {
if err := writeMultipartContent(k, mpc); err != nil {
return err
}
}
continue
} }
// ensure the value is in string format
s, ok := v.(string) var content string
if !ok { contentType := shared.ContentTypeTextPlain
s = fmt.Sprintf("%v", v) switch tt := v.(type) {
case []byte:
// JSON, don't quote it
content = string(tt)
contentType = shared.ContentTypeAppJSON
case string:
content = tt
default:
// ensure the value is in string format
content = fmt.Sprintf("%v", v)
} }
if err := writer.WriteField(k, s); err != nil {
if err := writeField(k, contentType, content); err != nil {
return err return err
} }
} }

View File

@ -40,7 +40,7 @@ func UnmarshalAsByteArray(resp *http.Response, v *[]byte, format Base64Encoding)
} }
// UnmarshalAsJSON calls json.Unmarshal() to unmarshal the received payload into the value pointed to by v. // UnmarshalAsJSON calls json.Unmarshal() to unmarshal the received payload into the value pointed to by v.
func UnmarshalAsJSON(resp *http.Response, v interface{}) error { func UnmarshalAsJSON(resp *http.Response, v any) error {
payload, err := Payload(resp) payload, err := Payload(resp)
if err != nil { if err != nil {
return err return err
@ -61,7 +61,7 @@ func UnmarshalAsJSON(resp *http.Response, v interface{}) error {
} }
// UnmarshalAsXML calls xml.Unmarshal() to unmarshal the received payload into the value pointed to by v. // UnmarshalAsXML calls xml.Unmarshal() to unmarshal the received payload into the value pointed to by v.
func UnmarshalAsXML(resp *http.Response, v interface{}) error { func UnmarshalAsXML(resp *http.Response, v any) error {
payload, err := Payload(resp) payload, err := Payload(resp)
if err != nil { if err != nil {
return err return err

View File

@ -73,3 +73,17 @@ func (p *progress) Seek(offset int64, whence int) (int64, error) {
func (p *progress) Close() error { func (p *progress) Close() error {
return p.rc.Close() return p.rc.Close()
} }
// MultipartContent contains streaming content used in multipart/form payloads.
type MultipartContent struct {
// Body contains the required content body.
Body io.ReadSeekCloser
// ContentType optionally specifies the HTTP Content-Type for this Body.
// The default value is application/octet-stream.
ContentType string
// Filename optionally specifies the filename for this Body.
// The default value is the field name for the multipart/form section.
Filename string
}

View File

@ -0,0 +1,4 @@
# live test artifacts
Dockerfile
k8s.yaml
sshkey*

View File

@ -1,5 +1,66 @@
# Release History # Release History
## 1.6.0 (2024-06-10)
### Features Added
* `NewOnBehalfOfCredentialWithClientAssertions` creates an on-behalf-of credential
that authenticates with client assertions such as federated credentials
### Breaking Changes
> These changes affect only code written against a beta version such as v1.6.0-beta.4
* Removed `AzurePipelinesCredential` and the persistent token caching API.
They will return in v1.7.0-beta.1
### Bugs Fixed
* Managed identity bug fixes
## 1.6.0-beta.4 (2024-05-14)
### Features Added
* `AzurePipelinesCredential` authenticates an Azure Pipeline service connection with
workload identity federation
## 1.6.0-beta.3 (2024-04-09)
### Breaking Changes
* `DefaultAzureCredential` now sends a probe request with no retries for IMDS managed identity
environments to avoid excessive retry delays when the IMDS endpoint is not available. This
should improve credential chain resolution for local development scenarios.
### Bugs Fixed
* `ManagedIdentityCredential` now specifies resource IDs correctly for Azure Container Instances
## 1.5.2 (2024-04-09)
### Bugs Fixed
* `ManagedIdentityCredential` now specifies resource IDs correctly for Azure Container Instances
### Other Changes
* Restored v1.4.0 error behavior for empty tenant IDs
* Upgraded dependencies
## 1.6.0-beta.2 (2024-02-06)
### Breaking Changes
> These changes affect only code written against a beta version such as v1.6.0-beta.1
* Replaced `ErrAuthenticationRequired` with `AuthenticationRequiredError`, a struct
type that carries the `TokenRequestOptions` passed to the `GetToken` call which
returned the error.
### Bugs Fixed
* Fixed more cases in which credential chains like `DefaultAzureCredential`
should try their next credential after attempting managed identity
authentication in a Docker Desktop container
### Other Changes
* `AzureCLICredential` uses the CLI's `expires_on` value for token expiration
## 1.6.0-beta.1 (2024-01-17)
### Features Added
* Restored persistent token caching API first added in v1.5.0-beta.1
* Added `AzureCLICredentialOptions.Subscription`
## 1.5.1 (2024-01-17) ## 1.5.1 (2024-01-17)
### Bugs Fixed ### Bugs Fixed
@ -126,7 +187,7 @@
### Features Added ### Features Added
* By default, credentials set client capability "CP1" to enable support for * By default, credentials set client capability "CP1" to enable support for
[Continuous Access Evaluation (CAE)](https://docs.microsoft.com/azure/active-directory/develop/app-resilience-continuous-access-evaluation). [Continuous Access Evaluation (CAE)](https://learn.microsoft.com/entra/identity-platform/app-resilience-continuous-access-evaluation).
This indicates to Microsoft Entra ID that your application can handle CAE claims challenges. This indicates to Microsoft Entra ID that your application can handle CAE claims challenges.
You can disable this behavior by setting the environment variable "AZURE_IDENTITY_DISABLE_CP1" to "true". You can disable this behavior by setting the environment variable "AZURE_IDENTITY_DISABLE_CP1" to "true".
* `InteractiveBrowserCredentialOptions.LoginHint` enables pre-populating the login * `InteractiveBrowserCredentialOptions.LoginHint` enables pre-populating the login

View File

@ -1,6 +1,6 @@
# Migrating from autorest/adal to azidentity # Migrating from autorest/adal to azidentity
`azidentity` provides Microsoft Entra ID ([formerly Azure Active Directory](https://learn.microsoft.com/azure/active-directory/fundamentals/new-name)) authentication for the newest Azure SDK modules (`github.com/azure-sdk-for-go/sdk/...`). Older Azure SDK packages (`github.com/azure-sdk-for-go/services/...`) use types from `github.com/go-autorest/autorest/adal` instead. `azidentity` provides Microsoft Entra ID ([formerly Azure Active Directory](https://learn.microsoft.com/entra/fundamentals/new-name)) authentication for the newest Azure SDK modules (`github.com/azure-sdk-for-go/sdk/...`). Older Azure SDK packages (`github.com/azure-sdk-for-go/services/...`) use types from `github.com/go-autorest/autorest/adal` instead.
This guide shows common authentication code using `autorest/adal` and its equivalent using `azidentity`. This guide shows common authentication code using `autorest/adal` and its equivalent using `azidentity`.
@ -284,7 +284,7 @@ if err == nil {
} }
``` ```
Note that `azidentity` credentials use the Microsoft Entra endpoint, which requires OAuth 2 scopes instead of the resource identifiers `autorest/adal` expects. For more information, see [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/permissions-consent-overview). Note that `azidentity` credentials use the Microsoft Entra endpoint, which requires OAuth 2 scopes instead of the resource identifiers `autorest/adal` expects. For more information, see [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/permissions-consent-overview).
## Use azidentity credentials with older packages ## Use azidentity credentials with older packages

View File

@ -1,9 +1,9 @@
# Azure Identity Client Module for Go # Azure Identity Client Module for Go
The Azure Identity module provides Microsoft Entra ID ([formerly Azure Active Directory](https://learn.microsoft.com/azure/active-directory/fundamentals/new-name)) token authentication support across the Azure SDK. It includes a set of `TokenCredential` implementations, which can be used with Azure SDK clients supporting token authentication. The Azure Identity module provides Microsoft Entra ID ([formerly Azure Active Directory](https://learn.microsoft.com/entra/fundamentals/new-name)) token authentication support across the Azure SDK. It includes a set of `TokenCredential` implementations, which can be used with Azure SDK clients supporting token authentication.
[![PkgGoDev](https://pkg.go.dev/badge/github.com/Azure/azure-sdk-for-go/sdk/azidentity)](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) [![PkgGoDev](https://pkg.go.dev/badge/github.com/Azure/azure-sdk-for-go/sdk/azidentity)](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity)
| [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/) | [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity/)
| [Source code](https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity) | [Source code](https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity)
# Getting started # Getting started
@ -30,7 +30,7 @@ When debugging and executing code locally, developers typically use their own ac
#### Authenticating via the Azure CLI #### Authenticating via the Azure CLI
`DefaultAzureCredential` and `AzureCLICredential` can authenticate as the user `DefaultAzureCredential` and `AzureCLICredential` can authenticate as the user
signed in to the [Azure CLI](https://docs.microsoft.com/cli/azure). To sign in to the Azure CLI, run `az login`. On a system with a default web browser, the Azure CLI will launch the browser to authenticate a user. signed in to the [Azure CLI](https://learn.microsoft.com/cli/azure). To sign in to the Azure CLI, run `az login`. On a system with a default web browser, the Azure CLI will launch the browser to authenticate a user.
When no default browser is available, `az login` will use the device code When no default browser is available, `az login` will use the device code
authentication flow. This can also be selected manually by running `az login --use-device-code`. authentication flow. This can also be selected manually by running `az login --use-device-code`.
@ -69,14 +69,14 @@ The `azidentity` module focuses on OAuth authentication with Microsoft Entra ID.
## Managed Identity ## Managed Identity
`DefaultAzureCredential` and `ManagedIdentityCredential` support `DefaultAzureCredential` and `ManagedIdentityCredential` support
[managed identity authentication](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) [managed identity authentication](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview)
in any hosting environment which supports managed identities, such as (this list is not exhaustive): in any hosting environment which supports managed identities, such as (this list is not exhaustive):
* [Azure App Service](https://docs.microsoft.com/azure/app-service/overview-managed-identity) * [Azure App Service](https://learn.microsoft.com/azure/app-service/overview-managed-identity)
* [Azure Arc](https://docs.microsoft.com/azure/azure-arc/servers/managed-identity-authentication) * [Azure Arc](https://learn.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)
* [Azure Cloud Shell](https://docs.microsoft.com/azure/cloud-shell/msi-authorization) * [Azure Cloud Shell](https://learn.microsoft.com/azure/cloud-shell/msi-authorization)
* [Azure Kubernetes Service](https://docs.microsoft.com/azure/aks/use-managed-identity) * [Azure Kubernetes Service](https://learn.microsoft.com/azure/aks/use-managed-identity)
* [Azure Service Fabric](https://docs.microsoft.com/azure/service-fabric/concepts-managed-identity) * [Azure Service Fabric](https://learn.microsoft.com/azure/service-fabric/concepts-managed-identity)
* [Azure Virtual Machines](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token) * [Azure Virtual Machines](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token)
## Examples ## Examples
@ -207,7 +207,7 @@ For more details, see the [token caching documentation](https://aka.ms/azsdk/go/
Credentials return an `error` when they fail to authenticate or lack data they require to authenticate. For guidance on resolving errors from specific credential types, see the [troubleshooting guide](https://aka.ms/azsdk/go/identity/troubleshoot). Credentials return an `error` when they fail to authenticate or lack data they require to authenticate. For guidance on resolving errors from specific credential types, see the [troubleshooting guide](https://aka.ms/azsdk/go/identity/troubleshoot).
For more details on handling specific Microsoft Entra errors, see the Microsoft Entra [error code documentation](https://learn.microsoft.com/azure/active-directory/develop/reference-error-codes). For more details on handling specific Microsoft Entra errors, see the Microsoft Entra [error code documentation](https://learn.microsoft.com/entra/identity-platform/reference-error-codes).
### Logging ### Logging

View File

@ -45,7 +45,7 @@ With persistent disk token caching enabled, the library first determines if a va
#### Example code #### Example code
See the [package documentation](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity@v1.5.0-beta.1#pkg-overview) for code examples demonstrating how to configure persistent caching and access cached data. See the [package documentation](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity@v1.6.0-beta.2#pkg-overview) for example code demonstrating how to configure persistent caching and access cached data.
### Credentials supporting token caching ### Credentials supporting token caching

View File

@ -58,7 +58,7 @@ This error contains several pieces of information:
- __Failing Credential Type__: The type of credential that failed to authenticate. This can be helpful when diagnosing issues with chained credential types such as `DefaultAzureCredential` or `ChainedTokenCredential`. - __Failing Credential Type__: The type of credential that failed to authenticate. This can be helpful when diagnosing issues with chained credential types such as `DefaultAzureCredential` or `ChainedTokenCredential`.
- __Microsoft Entra ID Error Code and Message__: The error code and message returned by Microsoft Entra ID. This can give insight into the specific reason the request failed. For instance, in this case authentication failed because the provided client secret is incorrect. [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/reference-error-codes#aadsts-error-codes) has more information on AADSTS error codes. - __Microsoft Entra ID Error Code and Message__: The error code and message returned by Microsoft Entra ID. This can give insight into the specific reason the request failed. For instance, in this case authentication failed because the provided client secret is incorrect. [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/reference-error-codes#aadsts-error-codes) has more information on AADSTS error codes.
- __Correlation ID and Timestamp__: The correlation ID and timestamp identify the request in server-side logs. This information can be useful to support engineers diagnosing unexpected Microsoft Entra failures. - __Correlation ID and Timestamp__: The correlation ID and timestamp identify the request in server-side logs. This information can be useful to support engineers diagnosing unexpected Microsoft Entra failures.
@ -97,17 +97,17 @@ azlog.SetEvents(azidentity.EventAuthentication)
| Error Code | Issue | Mitigation | | Error Code | Issue | Mitigation |
|---|---|---| |---|---|---|
|AADSTS7000215|An invalid client secret was provided.|Ensure the secret provided to the credential constructor is valid. If unsure, create a new client secret using the Azure portal. Details on creating a new client secret are in [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret).| |AADSTS7000215|An invalid client secret was provided.|Ensure the secret provided to the credential constructor is valid. If unsure, create a new client secret using the Azure portal. Details on creating a new client secret are in [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal#option-2-create-a-new-application-secret).|
|AADSTS7000222|An expired client secret was provided.|Create a new client secret using the Azure portal. Details on creating a new client secret are in [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret).| |AADSTS7000222|An expired client secret was provided.|Create a new client secret using the Azure portal. Details on creating a new client secret are in [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal#option-2-create-a-new-application-secret).|
|AADSTS700016|The specified application wasn't found in the specified tenant.|Ensure the client and tenant IDs provided to the credential constructor are correct for your application registration. For multi-tenant apps, ensure the application has been added to the desired tenant by a tenant admin. To add a new application in the desired tenant, follow the [Microsoft Entra ID instructions](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal).| |AADSTS700016|The specified application wasn't found in the specified tenant.|Ensure the client and tenant IDs provided to the credential constructor are correct for your application registration. For multi-tenant apps, ensure the application has been added to the desired tenant by a tenant admin. To add a new application in the desired tenant, follow the [Microsoft Entra ID instructions](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal).|
<a id="client-cert"></a> <a id="client-cert"></a>
## Troubleshoot ClientCertificateCredential authentication issues ## Troubleshoot ClientCertificateCredential authentication issues
| Error Code | Description | Mitigation | | Error Code | Description | Mitigation |
|---|---|---| |---|---|---|
|AADSTS700027|Client assertion contains an invalid signature.|Ensure the specified certificate has been uploaded to the application registration as described in [Microsoft Entra ID documentation](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal#option-1-upload-a-certificate).| |AADSTS700027|Client assertion contains an invalid signature.|Ensure the specified certificate has been uploaded to the application registration as described in [Microsoft Entra ID documentation](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal#option-1-upload-a-certificate).|
|AADSTS700016|The specified application wasn't found in the specified tenant.|Ensure the client and tenant IDs provided to the credential constructor are correct for your application registration. For multi-tenant apps, ensure the application has been added to the desired tenant by a tenant admin. To add a new application in the desired tenant, follow the [Microsoft Entra ID instructions](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal).| |AADSTS700016|The specified application wasn't found in the specified tenant.|Ensure the client and tenant IDs provided to the credential constructor are correct for your application registration. For multi-tenant apps, ensure the application has been added to the desired tenant by a tenant admin. To add a new application in the desired tenant, follow the [Microsoft Entra ID instructions](https://learn.microsoft.com/entra/identity-platform/howto-create-service-principal-portal).|
<a id="username-password"></a> <a id="username-password"></a>
## Troubleshoot UsernamePasswordCredential authentication issues ## Troubleshoot UsernamePasswordCredential authentication issues
@ -123,20 +123,20 @@ azlog.SetEvents(azidentity.EventAuthentication)
|Host Environment| | | |Host Environment| | |
|---|---|---| |---|---|---|
|Azure Virtual Machines and Scale Sets|[Configuration](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm)|[Troubleshooting](#azure-virtual-machine-managed-identity)| |Azure Virtual Machines and Scale Sets|[Configuration](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm)|[Troubleshooting](#azure-virtual-machine-managed-identity)|
|Azure App Service and Azure Functions|[Configuration](https://docs.microsoft.com/azure/app-service/overview-managed-identity)|[Troubleshooting](#azure-app-service-and-azure-functions-managed-identity)| |Azure App Service and Azure Functions|[Configuration](https://learn.microsoft.com/azure/app-service/overview-managed-identity)|[Troubleshooting](#azure-app-service-and-azure-functions-managed-identity)|
|Azure Kubernetes Service|[Configuration](https://azure.github.io/aad-pod-identity/docs/)|[Troubleshooting](#azure-kubernetes-service-managed-identity)| |Azure Kubernetes Service|[Configuration](https://azure.github.io/aad-pod-identity/docs/)|[Troubleshooting](#azure-kubernetes-service-managed-identity)|
|Azure Arc|[Configuration](https://docs.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)|| |Azure Arc|[Configuration](https://learn.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)||
|Azure Service Fabric|[Configuration](https://docs.microsoft.com/azure/service-fabric/concepts-managed-identity)|| |Azure Service Fabric|[Configuration](https://learn.microsoft.com/azure/service-fabric/concepts-managed-identity)||
### Azure Virtual Machine managed identity ### Azure Virtual Machine managed identity
| Error Message |Description| Mitigation | | Error Message |Description| Mitigation |
|---|---|---| |---|---|---|
|The requested identity hasnt been assigned to this resource.|The IMDS endpoint responded with a status code of 400, indicating the requested identity isnt assigned to the VM.|If using a user assigned identity, ensure the specified ID is correct.<p/><p/>If using a system assigned identity, make sure it has been enabled as described in [managed identity documentation](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm#enable-system-assigned-managed-identity-on-an-existing-vm).| |The requested identity hasnt been assigned to this resource.|The IMDS endpoint responded with a status code of 400, indicating the requested identity isnt assigned to the VM.|If using a user assigned identity, ensure the specified ID is correct.<p/><p/>If using a system assigned identity, make sure it has been enabled as described in [managed identity documentation](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm#enable-system-assigned-managed-identity-on-an-existing-vm).|
|The request failed due to a gateway error.|The request to the IMDS endpoint failed due to a gateway error, 502 or 504 status code.|IMDS doesn't support requests via proxy or gateway. Disable proxies or gateways running on the VM for requests to the IMDS endpoint `http://169.254.169.254`| |The request failed due to a gateway error.|The request to the IMDS endpoint failed due to a gateway error, 502 or 504 status code.|IMDS doesn't support requests via proxy or gateway. Disable proxies or gateways running on the VM for requests to the IMDS endpoint `http://169.254.169.254`|
|No response received from the managed identity endpoint.|No response was received for the request to IMDS or the request timed out.|<ul><li>Ensure the VM is configured for managed identity as described in [managed identity documentation](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm).</li><li>Verify the IMDS endpoint is reachable on the VM. See [below](#verify-imds-is-available-on-the-vm) for instructions.</li></ul>| |No response received from the managed identity endpoint.|No response was received for the request to IMDS or the request timed out.|<ul><li>Ensure the VM is configured for managed identity as described in [managed identity documentation](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm).</li><li>Verify the IMDS endpoint is reachable on the VM. See [below](#verify-imds-is-available-on-the-vm) for instructions.</li></ul>|
|Multiple attempts failed to obtain a token from the managed identity endpoint.|The credential has exhausted its retries for a token request.|<ul><li>Refer to the error message for more details on specific failures.<li>Ensure the VM is configured for managed identity as described in [managed identity documentation](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/qs-configure-portal-windows-vm).</li><li>Verify the IMDS endpoint is reachable on the VM. See [below](#verify-imds-is-available-on-the-vm) for instructions.</li></ul>| |Multiple attempts failed to obtain a token from the managed identity endpoint.|The credential has exhausted its retries for a token request.|<ul><li>Refer to the error message for more details on specific failures.<li>Ensure the VM is configured for managed identity as described in [managed identity documentation](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/qs-configure-portal-windows-vm).</li><li>Verify the IMDS endpoint is reachable on the VM. See [below](#verify-imds-is-available-on-the-vm) for instructions.</li></ul>|
#### Verify IMDS is available on the VM #### Verify IMDS is available on the VM
@ -152,7 +152,7 @@ curl 'http://169.254.169.254/metadata/identity/oauth2/token?resource=https://man
| Error Message |Description| Mitigation | | Error Message |Description| Mitigation |
|---|---|---| |---|---|---|
|Get "`http://169.254.169.254/...`" i/o timeout|The App Service host hasn't set environment variables for managed identity configuration.|<ul><li>Ensure the App Service is configured for managed identity as described in [App Service documentation](https://docs.microsoft.com/azure/app-service/overview-managed-identity).</li><li>Verify the App Service environment is properly configured and the managed identity endpoint is available. See [below](#verify-the-app-service-managed-identity-endpoint-is-available) for instructions.</li></ul>| |Get "`http://169.254.169.254/...`" i/o timeout|The App Service host hasn't set environment variables for managed identity configuration.|<ul><li>Ensure the App Service is configured for managed identity as described in [App Service documentation](https://learn.microsoft.com/azure/app-service/overview-managed-identity).</li><li>Verify the App Service environment is properly configured and the managed identity endpoint is available. See [below](#verify-the-app-service-managed-identity-endpoint-is-available) for instructions.</li></ul>|
#### Verify the App Service managed identity endpoint is available #### Verify the App Service managed identity endpoint is available
@ -177,8 +177,8 @@ curl "$IDENTITY_ENDPOINT?resource=https://management.core.windows.net&api-versio
| Error Message |Description| Mitigation | | Error Message |Description| Mitigation |
|---|---|---| |---|---|---|
|Azure CLI not found on path|The Azure CLI isnt installed or isn't on the application's path.|<ul><li>Ensure the Azure CLI is installed as described in [Azure CLI documentation](https://docs.microsoft.com/cli/azure/install-azure-cli).</li><li>Validate the installation location is in the application's `PATH` environment variable.</li></ul>| |Azure CLI not found on path|The Azure CLI isnt installed or isn't on the application's path.|<ul><li>Ensure the Azure CLI is installed as described in [Azure CLI documentation](https://learn.microsoft.com/cli/azure/install-azure-cli).</li><li>Validate the installation location is in the application's `PATH` environment variable.</li></ul>|
|Please run 'az login' to set up account|No account is currently logged into the Azure CLI, or the login has expired.|<ul><li>Run `az login` to log into the Azure CLI. More information about Azure CLI authentication is available in the [Azure CLI documentation](https://docs.microsoft.com/cli/azure/authenticate-azure-cli).</li><li>Verify that the Azure CLI can obtain tokens. See [below](#verify-the-azure-cli-can-obtain-tokens) for instructions.</li></ul>| |Please run 'az login' to set up account|No account is currently logged into the Azure CLI, or the login has expired.|<ul><li>Run `az login` to log into the Azure CLI. More information about Azure CLI authentication is available in the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli).</li><li>Verify that the Azure CLI can obtain tokens. See [below](#verify-the-azure-cli-can-obtain-tokens) for instructions.</li></ul>|
#### Verify the Azure CLI can obtain tokens #### Verify the Azure CLI can obtain tokens

View File

@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "go", "AssetsRepoPrefixPath": "go",
"TagPrefix": "go/azidentity", "TagPrefix": "go/azidentity",
"Tag": "go/azidentity_db4a26f583" "Tag": "go/azidentity_087379b475"
} }

View File

@ -121,6 +121,9 @@ func alphanumeric(r rune) bool {
} }
func validTenantID(tenantID string) bool { func validTenantID(tenantID string) bool {
if len(tenantID) < 1 {
return false
}
for _, r := range tenantID { for _, r := range tenantID {
if !(alphanumeric(r) || r == '.' || r == '-') { if !(alphanumeric(r) || r == '.' || r == '-') {
return false return false

View File

@ -35,9 +35,9 @@ type AzureCLICredentialOptions struct {
// logged in account can access. // logged in account can access.
AdditionallyAllowedTenants []string AdditionallyAllowedTenants []string
// subscription is the name or ID of a subscription. Set this to acquire tokens for an account other // Subscription is the name or ID of a subscription. Set this to acquire tokens for an account other
// than the Azure CLI's current account. // than the Azure CLI's current account.
subscription string Subscription string
// TenantID identifies the tenant the credential should authenticate in. // TenantID identifies the tenant the credential should authenticate in.
// Defaults to the CLI's default tenant, which is typically the home tenant of the logged in user. // Defaults to the CLI's default tenant, which is typically the home tenant of the logged in user.
@ -68,9 +68,9 @@ func NewAzureCLICredential(options *AzureCLICredentialOptions) (*AzureCLICredent
if options != nil { if options != nil {
cp = *options cp = *options
} }
for _, r := range cp.subscription { for _, r := range cp.Subscription {
if !(alphanumeric(r) || r == '-' || r == '_' || r == ' ' || r == '.') { if !(alphanumeric(r) || r == '-' || r == '_' || r == ' ' || r == '.') {
return nil, fmt.Errorf("%s: invalid Subscription %q", credNameAzureCLI, cp.subscription) return nil, fmt.Errorf("%s: invalid Subscription %q", credNameAzureCLI, cp.Subscription)
} }
} }
if cp.TenantID != "" && !validTenantID(cp.TenantID) { if cp.TenantID != "" && !validTenantID(cp.TenantID) {
@ -97,7 +97,7 @@ func (c *AzureCLICredential) GetToken(ctx context.Context, opts policy.TokenRequ
} }
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
b, err := c.opts.tokenProvider(ctx, opts.Scopes, tenant, c.opts.subscription) b, err := c.opts.tokenProvider(ctx, opts.Scopes, tenant, c.opts.Subscription)
if err == nil { if err == nil {
at, err = c.createAccessToken(b) at, err = c.createAccessToken(b)
} }
@ -163,26 +163,21 @@ var defaultAzTokenProvider azTokenProvider = func(ctx context.Context, scopes []
func (c *AzureCLICredential) createAccessToken(tk []byte) (azcore.AccessToken, error) { func (c *AzureCLICredential) createAccessToken(tk []byte) (azcore.AccessToken, error) {
t := struct { t := struct {
AccessToken string `json:"accessToken"` AccessToken string `json:"accessToken"`
Authority string `json:"_authority"` Expires_On int64 `json:"expires_on"`
ClientID string `json:"_clientId"` ExpiresOn string `json:"expiresOn"`
ExpiresOn string `json:"expiresOn"`
IdentityProvider string `json:"identityProvider"`
IsMRRT bool `json:"isMRRT"`
RefreshToken string `json:"refreshToken"`
Resource string `json:"resource"`
TokenType string `json:"tokenType"`
UserID string `json:"userId"`
}{} }{}
err := json.Unmarshal(tk, &t) err := json.Unmarshal(tk, &t)
if err != nil { if err != nil {
return azcore.AccessToken{}, err return azcore.AccessToken{}, err
} }
// the Azure CLI's "expiresOn" is local time exp := time.Unix(t.Expires_On, 0)
exp, err := time.ParseInLocation("2006-01-02 15:04:05.999999", t.ExpiresOn, time.Local) if t.Expires_On == 0 {
if err != nil { exp, err = time.ParseInLocation("2006-01-02 15:04:05.999999", t.ExpiresOn, time.Local)
return azcore.AccessToken{}, fmt.Errorf("Error parsing token expiration time %q: %v", t.ExpiresOn, err) if err != nil {
return azcore.AccessToken{}, fmt.Errorf("%s: error parsing token expiration time %q: %v", credNameAzureCLI, t.ExpiresOn, err)
}
} }
converted := azcore.AccessToken{ converted := azcore.AccessToken{

View File

@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package azidentity
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"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"
)
const (
credNameAzurePipelines = "AzurePipelinesCredential"
oidcAPIVersion = "7.1"
systemAccessToken = "SYSTEM_ACCESSTOKEN"
systemOIDCRequestURI = "SYSTEM_OIDCREQUESTURI"
)
// azurePipelinesCredential authenticates with workload identity federation in an Azure Pipeline. See
// [Azure Pipelines documentation] for more information.
//
// [Azure Pipelines documentation]: https://learn.microsoft.com/azure/devops/pipelines/library/connect-to-azure?view=azure-devops#create-an-azure-resource-manager-service-connection-that-uses-workload-identity-federation
type azurePipelinesCredential struct {
connectionID, oidcURI, systemAccessToken string
cred *ClientAssertionCredential
}
// azurePipelinesCredentialOptions contains optional parameters for AzurePipelinesCredential.
type azurePipelinesCredentialOptions struct {
azcore.ClientOptions
// AdditionallyAllowedTenants specifies additional tenants for which the credential may acquire tokens.
// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the
// application is registered.
AdditionallyAllowedTenants []string
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
// the application responsible for ensuring the configured authority is valid and trustworthy.
DisableInstanceDiscovery bool
}
// newAzurePipelinesCredential is the constructor for AzurePipelinesCredential. In addition to its required arguments,
// it reads a security token for the running build, which is required to authenticate the service connection, from the
// environment variable SYSTEM_ACCESSTOKEN. See the [Azure Pipelines documentation] for an example showing how to set
// this variable in build job YAML.
//
// [Azure Pipelines documentation]: https://learn.microsoft.com/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken
func newAzurePipelinesCredential(tenantID, clientID, serviceConnectionID string, options *azurePipelinesCredentialOptions) (*azurePipelinesCredential, error) {
if options == nil {
options = &azurePipelinesCredentialOptions{}
}
u := os.Getenv(systemOIDCRequestURI)
if u == "" {
return nil, fmt.Errorf("no value for environment variable %s. This should be set by Azure Pipelines", systemOIDCRequestURI)
}
sat := os.Getenv(systemAccessToken)
if sat == "" {
return nil, errors.New("no value for environment variable " + systemAccessToken)
}
a := azurePipelinesCredential{
connectionID: serviceConnectionID,
oidcURI: u,
systemAccessToken: sat,
}
caco := ClientAssertionCredentialOptions{
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
ClientOptions: options.ClientOptions,
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
}
cred, err := NewClientAssertionCredential(tenantID, clientID, a.getAssertion, &caco)
if err != nil {
return nil, err
}
cred.client.name = credNameAzurePipelines
a.cred = cred
return &a, nil
}
// GetToken requests an access token from Microsoft Entra ID. Azure SDK clients call this method automatically.
func (a *azurePipelinesCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
var err error
ctx, endSpan := runtime.StartSpan(ctx, credNameAzurePipelines+"."+traceOpGetToken, a.cred.client.azClient.Tracer(), nil)
defer func() { endSpan(err) }()
tk, err := a.cred.GetToken(ctx, opts)
return tk, err
}
func (a *azurePipelinesCredential) getAssertion(ctx context.Context) (string, error) {
url := a.oidcURI + "?api-version=" + oidcAPIVersion + "&serviceConnectionId=" + a.connectionID
url, err := runtime.EncodeQueryParams(url)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't encode OIDC URL: "+err.Error(), nil, nil)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't create OIDC token request: "+err.Error(), nil, nil)
}
req.Header.Set("Authorization", "Bearer "+a.systemAccessToken)
res, err := doForClient(a.cred.client.azClient, req)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't send OIDC token request: "+err.Error(), nil, nil)
}
if res.StatusCode != http.StatusOK {
msg := res.Status + " response from the OIDC endpoint. Check service connection ID and Pipeline configuration"
// include the response because its body, if any, probably contains an error message.
// OK responses aren't included with errors because they probably contain secrets
return "", newAuthenticationFailedError(credNameAzurePipelines, msg, res, nil)
}
b, err := runtime.Payload(res)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "couldn't read OIDC response content: "+err.Error(), nil, nil)
}
var r struct {
OIDCToken string `json:"oidcToken"`
}
err = json.Unmarshal(b, &r)
if err != nil {
return "", newAuthenticationFailedError(credNameAzurePipelines, "unexpected response from OIDC endpoint", nil, nil)
}
return r.OIDCToken, nil
}

View File

@ -86,7 +86,7 @@ func (c *ChainedTokenCredential) GetToken(ctx context.Context, opts policy.Token
errs []error errs []error
successfulCredential azcore.TokenCredential successfulCredential azcore.TokenCredential
token azcore.AccessToken token azcore.AccessToken
unavailableErr *credentialUnavailableError unavailableErr credentialUnavailable
) )
for _, cred := range c.sources { for _, cred := range c.sources {
token, err = cred.GetToken(ctx, opts) token, err = cred.GetToken(ctx, opts)

View File

@ -8,7 +8,7 @@ trigger:
- release/* - release/*
paths: paths:
include: include:
- sdk/azidentity/ - sdk/azidentity/
pr: pr:
branches: branches:
@ -19,17 +19,28 @@ pr:
- release/* - release/*
paths: paths:
include: include:
- sdk/azidentity/ - sdk/azidentity/
stages: extends:
- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml
parameters: parameters:
RunLiveTests: true CloudConfig:
UsePipelineProxy: false Public:
ServiceDirectory: 'azidentity' SubscriptionConfigurations:
CloudConfig: - $(sub-config-azure-cloud-test-resources)
Public: - $(sub-config-identity-test-resources)
SubscriptionConfigurations: EnvVars:
- $(sub-config-azure-cloud-test-resources) SYSTEM_ACCESSTOKEN: $(System.AccessToken)
# Contains alternate tenant, AAD app and cert info for testing RunLiveTests: true
- $(sub-config-identity-test-resources) ServiceDirectory: azidentity
UsePipelineProxy: false
${{ if endsWith(variables['Build.DefinitionName'], 'weekly') }}:
MatrixConfigs:
- Name: managed_identity_matrix
GenerateVMJobs: true
Path: sdk/azidentity/managed-identity-matrix.json
Selection: sparse
MatrixReplace:
- Pool=.*LINUXPOOL.*/azsdk-pool-mms-ubuntu-2204-identitymsi
- OSVmImage=.*LINUXNEXTVMIMAGE.*/azsdk-pool-mms-ubuntu-2204-1espt

View File

@ -23,7 +23,7 @@ const credNameAssertion = "ClientAssertionCredential"
// the most common assertion scenario, authenticating a service principal with a certificate. See // the most common assertion scenario, authenticating a service principal with a certificate. See
// [Microsoft Entra ID documentation] for details of the assertion format. // [Microsoft Entra ID documentation] for details of the assertion format.
// //
// [Microsoft Entra ID documentation]: https://learn.microsoft.com/azure/active-directory/develop/active-directory-certificate-credentials#assertion-format // [Microsoft Entra ID documentation]: https://learn.microsoft.com/entra/identity-platform/certificate-credentials#assertion-format
type ClientAssertionCredential struct { type ClientAssertionCredential struct {
client *confidentialClient client *confidentialClient
} }

View File

@ -51,7 +51,8 @@ type ClientCertificateCredential struct {
client *confidentialClient client *confidentialClient
} }
// NewClientCertificateCredential constructs a ClientCertificateCredential. Pass nil for options to accept defaults. // NewClientCertificateCredential constructs a ClientCertificateCredential. Pass nil for options to accept defaults. See
// [ParseCertificates] for help loading a certificate.
func NewClientCertificateCredential(tenantID string, clientID string, certs []*x509.Certificate, key crypto.PrivateKey, options *ClientCertificateCredentialOptions) (*ClientCertificateCredential, error) { func NewClientCertificateCredential(tenantID string, clientID string, certs []*x509.Certificate, key crypto.PrivateKey, options *ClientCertificateCredentialOptions) (*ClientCertificateCredential, error) {
if len(certs) == 0 { if len(certs) == 0 {
return nil, errors.New("at least one certificate is required") return nil, errors.New("at least one certificate is required")
@ -86,8 +87,10 @@ func (c *ClientCertificateCredential) GetToken(ctx context.Context, opts policy.
return tk, err return tk, err
} }
// ParseCertificates loads certificates and a private key, in PEM or PKCS12 format, for use with NewClientCertificateCredential. // ParseCertificates loads certificates and a private key, in PEM or PKCS#12 format, for use with [NewClientCertificateCredential].
// Pass nil for password if the private key isn't encrypted. This function can't decrypt keys in PEM format. // Pass nil for password if the private key isn't encrypted. This function has limitations, for example it can't decrypt keys in
// PEM format or PKCS#12 certificates that use SHA256 for message authentication. If you encounter such limitations, consider
// using another module to load the certificate and private key.
func ParseCertificates(certData []byte, password []byte) ([]*x509.Certificate, crypto.PrivateKey, error) { func ParseCertificates(certData []byte, password []byte) ([]*x509.Certificate, crypto.PrivateKey, error) {
var blocks []*pem.Block var blocks []*pem.Block
var err error var err error

View File

@ -91,7 +91,7 @@ func (c *confidentialClient) GetToken(ctx context.Context, tro policy.TokenReque
} }
tro.TenantID = tenant tro.TenantID = tenant
} }
client, mu, err := c.client(ctx, tro) client, mu, err := c.client(tro)
if err != nil { if err != nil {
return azcore.AccessToken{}, err return azcore.AccessToken{}, err
} }
@ -109,7 +109,7 @@ func (c *confidentialClient) GetToken(ctx context.Context, tro policy.TokenReque
if err != nil { if err != nil {
// We could get a credentialUnavailableError from managed identity authentication because in that case the error comes from our code. // We could get a credentialUnavailableError from managed identity authentication because in that case the error comes from our code.
// We return it directly because it affects the behavior of credential chains. Otherwise, we return AuthenticationFailedError. // We return it directly because it affects the behavior of credential chains. Otherwise, we return AuthenticationFailedError.
var unavailableErr *credentialUnavailableError var unavailableErr credentialUnavailable
if !errors.As(err, &unavailableErr) { if !errors.As(err, &unavailableErr) {
res := getResponseFromError(err) res := getResponseFromError(err)
err = newAuthenticationFailedError(c.name, err.Error(), res, err) err = newAuthenticationFailedError(c.name, err.Error(), res, err)
@ -121,7 +121,7 @@ func (c *confidentialClient) GetToken(ctx context.Context, tro policy.TokenReque
return azcore.AccessToken{Token: ar.AccessToken, ExpiresOn: ar.ExpiresOn.UTC()}, err return azcore.AccessToken{Token: ar.AccessToken, ExpiresOn: ar.ExpiresOn.UTC()}, err
} }
func (c *confidentialClient) client(ctx context.Context, tro policy.TokenRequestOptions) (msalConfidentialClient, *sync.Mutex, error) { func (c *confidentialClient) client(tro policy.TokenRequestOptions) (msalConfidentialClient, *sync.Mutex, error) {
c.clientMu.Lock() c.clientMu.Lock()
defer c.clientMu.Unlock() defer c.clientMu.Unlock()
if tro.EnableCAE { if tro.EnableCAE {

View File

@ -8,10 +8,8 @@ package azidentity
import ( import (
"context" "context"
"errors"
"os" "os"
"strings" "strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "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/policy"
@ -98,13 +96,13 @@ func NewDefaultAzureCredential(options *DefaultAzureCredentialOptions) (*Default
creds = append(creds, &defaultCredentialErrorReporter{credType: credNameWorkloadIdentity, err: err}) creds = append(creds, &defaultCredentialErrorReporter{credType: credNameWorkloadIdentity, err: err})
} }
o := &ManagedIdentityCredentialOptions{ClientOptions: options.ClientOptions} o := &ManagedIdentityCredentialOptions{ClientOptions: options.ClientOptions, dac: true}
if ID, ok := os.LookupEnv(azureClientID); ok { if ID, ok := os.LookupEnv(azureClientID); ok {
o.ID = ClientID(ID) o.ID = ClientID(ID)
} }
miCred, err := NewManagedIdentityCredential(o) miCred, err := NewManagedIdentityCredential(o)
if err == nil { if err == nil {
creds = append(creds, &timeoutWrapper{mic: miCred, timeout: time.Second}) creds = append(creds, miCred)
} else { } else {
errorMessages = append(errorMessages, credNameManagedIdentity+": "+err.Error()) errorMessages = append(errorMessages, credNameManagedIdentity+": "+err.Error())
creds = append(creds, &defaultCredentialErrorReporter{credType: credNameManagedIdentity, err: err}) creds = append(creds, &defaultCredentialErrorReporter{credType: credNameManagedIdentity, err: err})
@ -158,51 +156,10 @@ type defaultCredentialErrorReporter struct {
} }
func (d *defaultCredentialErrorReporter) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { func (d *defaultCredentialErrorReporter) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
if _, ok := d.err.(*credentialUnavailableError); ok { if _, ok := d.err.(credentialUnavailable); ok {
return azcore.AccessToken{}, d.err return azcore.AccessToken{}, d.err
} }
return azcore.AccessToken{}, newCredentialUnavailableError(d.credType, d.err.Error()) return azcore.AccessToken{}, newCredentialUnavailableError(d.credType, d.err.Error())
} }
var _ azcore.TokenCredential = (*defaultCredentialErrorReporter)(nil) var _ azcore.TokenCredential = (*defaultCredentialErrorReporter)(nil)
// timeoutWrapper prevents a potentially very long timeout when managed identity isn't available
type timeoutWrapper struct {
mic *ManagedIdentityCredential
// timeout applies to all auth attempts until one doesn't time out
timeout time.Duration
}
// GetToken wraps DefaultAzureCredential's initial managed identity auth attempt with a short timeout
// because managed identity may not be available and connecting to IMDS can take several minutes to time out.
func (w *timeoutWrapper) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
var tk azcore.AccessToken
var err error
// no need to synchronize around this value because it's written only within ChainedTokenCredential's critical section
if w.timeout > 0 {
c, cancel := context.WithTimeout(ctx, w.timeout)
defer cancel()
tk, err = w.mic.GetToken(c, opts)
if isAuthFailedDueToContext(err) {
err = newCredentialUnavailableError(credNameManagedIdentity, "managed identity timed out. See https://aka.ms/azsdk/go/identity/troubleshoot#dac for more information")
} else {
// some managed identity implementation is available, so don't apply the timeout to future calls
w.timeout = 0
}
} else {
tk, err = w.mic.GetToken(ctx, opts)
}
return tk, err
}
// unwraps nested AuthenticationFailedErrors to get the root error
func isAuthFailedDueToContext(err error) bool {
for {
var authFailedErr *AuthenticationFailedError
if !errors.As(err, &authFailedErr) {
break
}
err = authFailedErr.err
}
return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)
}

View File

@ -19,7 +19,7 @@ const cliTimeout = 10 * time.Second
// the next credential in its chain (another developer credential). // the next credential in its chain (another developer credential).
func unavailableIfInChain(err error, inDefaultChain bool) error { func unavailableIfInChain(err error, inDefaultChain bool) error {
if err != nil && inDefaultChain { if err != nil && inDefaultChain {
var unavailableErr *credentialUnavailableError var unavailableErr credentialUnavailable
if !errors.As(err, &unavailableErr) { if !errors.As(err, &unavailableErr) {
err = newCredentialUnavailableError(credNameAzureDeveloperCLI, err.Error()) err = newCredentialUnavailableError(credNameAzureDeveloperCLI, err.Error())
} }

View File

@ -34,8 +34,8 @@ type DeviceCodeCredentialOptions struct {
ClientID string ClientID string
// disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate. // disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
// When this option is true, [DeviceCodeCredential.GetToken] will return [ErrAuthenticationRequired] when user // When this option is true, GetToken will return authenticationRequiredError when user interaction is necessary
// interaction is necessary to acquire a token. // to acquire a token.
disableAutomaticAuthentication bool disableAutomaticAuthentication bool
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or // DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or

View File

@ -57,6 +57,9 @@ type EnvironmentCredentialOptions struct {
// //
// AZURE_CLIENT_CERTIFICATE_PASSWORD: (optional) password for the certificate file. // AZURE_CLIENT_CERTIFICATE_PASSWORD: (optional) password for the certificate file.
// //
// Note that this credential uses [ParseCertificates] to load the certificate and key from the file. If this
// function isn't able to parse your certificate, use [ClientCertificateCredential] instead.
//
// # User with username and password // # User with username and password
// //
// AZURE_TENANT_ID: (optional) tenant to authenticate in. Defaults to "organizations". // AZURE_TENANT_ID: (optional) tenant to authenticate in. Defaults to "organizations".
@ -121,7 +124,7 @@ func NewEnvironmentCredential(options *EnvironmentCredentialOptions) (*Environme
} }
certs, key, err := ParseCertificates(certData, password) certs, key, err := ParseCertificates(certData, password)
if err != nil { if err != nil {
return nil, fmt.Errorf(`failed to load certificate from "%s": %v`, certPath, err) return nil, fmt.Errorf("failed to parse %q due to error %q. This may be due to a limitation of this module's certificate loader. Consider calling NewClientCertificateCredential instead", certPath, err.Error())
} }
o := &ClientCertificateCredentialOptions{ o := &ClientCertificateCredentialOptions{
AdditionallyAllowedTenants: additionalTenants, AdditionallyAllowedTenants: additionalTenants,

View File

@ -13,15 +13,12 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo" "github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors" msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
) )
// errAuthenticationRequired indicates a credential's Authenticate method must be called to acquire a token
// because user interaction is required and the credential is configured not to automatically prompt the user.
var errAuthenticationRequired error = &credentialUnavailableError{"can't acquire a token without user interaction. Call Authenticate to interactively authenticate a user"}
// getResponseFromError retrieves the response carried by // getResponseFromError retrieves the response carried by
// an AuthenticationFailedError or MSAL CallErr, if any // an AuthenticationFailedError or MSAL CallErr, if any
func getResponseFromError(err error) *http.Response { func getResponseFromError(err error) *http.Response {
@ -56,7 +53,7 @@ func (e *AuthenticationFailedError) Error() string {
return e.credType + ": " + e.message return e.credType + ": " + e.message
} }
msg := &bytes.Buffer{} msg := &bytes.Buffer{}
fmt.Fprintf(msg, e.credType+" authentication failed\n") fmt.Fprintf(msg, "%s authentication failed. %s\n", e.credType, e.message)
if e.RawResponse.Request != nil { if e.RawResponse.Request != nil {
fmt.Fprintf(msg, "%s %s://%s%s\n", e.RawResponse.Request.Method, e.RawResponse.Request.URL.Scheme, e.RawResponse.Request.URL.Host, e.RawResponse.Request.URL.Path) fmt.Fprintf(msg, "%s %s://%s%s\n", e.RawResponse.Request.Method, e.RawResponse.Request.URL.Scheme, e.RawResponse.Request.URL.Host, e.RawResponse.Request.URL.Path)
} else { } else {
@ -110,8 +107,34 @@ func (*AuthenticationFailedError) NonRetriable() {
var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil) var _ errorinfo.NonRetriable = (*AuthenticationFailedError)(nil)
// credentialUnavailableError indicates a credential can't attempt authentication because it lacks required // authenticationRequiredError indicates a credential's Authenticate method must be called to acquire a token
// data or state // because the credential requires user interaction and is configured not to request it automatically.
type authenticationRequiredError struct {
credentialUnavailableError
// TokenRequestOptions for the required token. Pass this to the credential's Authenticate method.
TokenRequestOptions policy.TokenRequestOptions
}
func newauthenticationRequiredError(credType string, tro policy.TokenRequestOptions) error {
return &authenticationRequiredError{
credentialUnavailableError: credentialUnavailableError{
credType + " can't acquire a token without user interaction. Call Authenticate to authenticate a user interactively",
},
TokenRequestOptions: tro,
}
}
var (
_ credentialUnavailable = (*authenticationRequiredError)(nil)
_ errorinfo.NonRetriable = (*authenticationRequiredError)(nil)
)
type credentialUnavailable interface {
error
credentialUnavailable()
}
type credentialUnavailableError struct { type credentialUnavailableError struct {
message string message string
} }
@ -135,6 +158,11 @@ func (e *credentialUnavailableError) Error() string {
} }
// NonRetriable is a marker method indicating this error should not be retried. It has no implementation. // NonRetriable is a marker method indicating this error should not be retried. It has no implementation.
func (e *credentialUnavailableError) NonRetriable() {} func (*credentialUnavailableError) NonRetriable() {}
var _ errorinfo.NonRetriable = (*credentialUnavailableError)(nil) func (*credentialUnavailableError) credentialUnavailable() {}
var (
_ credentialUnavailable = (*credentialUnavailableError)(nil)
_ errorinfo.NonRetriable = (*credentialUnavailableError)(nil)
)

View File

@ -3,26 +3,40 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0-beta.1/go.mod h1:3Ug6Qzto9an
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a/go.mod h1:YPNKjjE7Ubp9dTbnWvsP3HT+hYnY6TfXzubYTBeUxc8=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -33,7 +47,14 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -33,8 +33,8 @@ type InteractiveBrowserCredentialOptions struct {
ClientID string ClientID string
// disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate. // disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
// When this option is true, [InteractiveBrowserCredential.GetToken] will return [ErrAuthenticationRequired] when // When this option is true, GetToken will return authenticationRequiredError when user interaction is necessary
// user interaction is necessary to acquire a token. // to acquire a token.
disableAutomaticAuthentication bool disableAutomaticAuthentication bool
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or // DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or

View File

@ -0,0 +1,17 @@
{
"include": [
{
"Agent": {
"msi_image": {
"ArmTemplateParameters": "@{deployResources = $true}",
"OSVmImage": "env:LINUXNEXTVMIMAGE",
"Pool": "env:LINUXPOOL"
}
},
"GoVersion": [
"1.22.1"
],
"IDENTITY_IMDS_AVAILABLE": "1"
}
]
}

View File

@ -14,13 +14,15 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "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/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/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/internal/log" "github.com/Azure/azure-sdk-for-go/sdk/internal/log"
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
@ -34,16 +36,18 @@ const (
identityServerThumbprint = "IDENTITY_SERVER_THUMBPRINT" identityServerThumbprint = "IDENTITY_SERVER_THUMBPRINT"
headerMetadata = "Metadata" headerMetadata = "Metadata"
imdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token" imdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"
miResID = "mi_res_id"
msiEndpoint = "MSI_ENDPOINT" msiEndpoint = "MSI_ENDPOINT"
msiResID = "msi_res_id"
msiSecret = "MSI_SECRET" msiSecret = "MSI_SECRET"
imdsAPIVersion = "2018-02-01" imdsAPIVersion = "2018-02-01"
azureArcAPIVersion = "2019-08-15" azureArcAPIVersion = "2019-08-15"
qpClientID = "client_id"
serviceFabricAPIVersion = "2019-07-01-preview" serviceFabricAPIVersion = "2019-07-01-preview"
qpClientID = "client_id"
qpResID = "mi_res_id"
) )
var imdsProbeTimeout = time.Second
type msiType int type msiType int
const ( const (
@ -55,13 +59,28 @@ const (
msiTypeServiceFabric msiTypeServiceFabric
) )
// managedIdentityClient provides the base for authenticating in managed identity environments
// This type includes an runtime.Pipeline and TokenCredentialOptions.
type managedIdentityClient struct { type managedIdentityClient struct {
azClient *azcore.Client azClient *azcore.Client
msiType msiType endpoint string
endpoint string id ManagedIDKind
id ManagedIDKind msiType msiType
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 type wrappedNumber json.Number
@ -88,7 +107,7 @@ func setIMDSRetryOptionDefaults(o *policy.RetryOptions) {
if o.StatusCodes == nil { if o.StatusCodes == nil {
o.StatusCodes = []int{ o.StatusCodes = []int{
// IMDS docs recommend retrying 404, 410, 429 and 5xx // IMDS docs recommend retrying 404, 410, 429 and 5xx
// https://learn.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#error-handling // https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#error-handling
http.StatusNotFound, // 404 http.StatusNotFound, // 404
http.StatusGone, // 410 http.StatusGone, // 410
http.StatusTooManyRequests, // 429 http.StatusTooManyRequests, // 429
@ -147,11 +166,12 @@ func newManagedIdentityClient(options *ManagedIdentityCredentialOptions) (*manag
c.msiType = msiTypeCloudShell c.msiType = msiTypeCloudShell
} }
} else { } else {
c.probeIMDS = options.dac
setIMDSRetryOptionDefaults(&cp.Retry) setIMDSRetryOptionDefaults(&cp.Retry)
} }
client, err := azcore.NewClient(module, version, runtime.PipelineOptions{ client, err := azcore.NewClient(module, version, azruntime.PipelineOptions{
Tracing: runtime.TracingOptions{ Tracing: azruntime.TracingOptions{
Namespace: traceNamespace, Namespace: traceNamespace,
}, },
}, &cp) }, &cp)
@ -180,6 +200,27 @@ func (c *managedIdentityClient) provideToken(ctx context.Context, params confide
// authenticate acquires an access token // authenticate acquires an access token
func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKind, scopes []string) (azcore.AccessToken, error) { func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKind, scopes []string) (azcore.AccessToken, error) {
// no need to synchronize around this value because it's true only when DefaultAzureCredential constructed the client,
// and in that case ChainedTokenCredential.GetToken synchronizes goroutines that would execute this block
if c.probeIMDS {
cx, cancel := context.WithTimeout(ctx, imdsProbeTimeout)
defer cancel()
cx = policy.WithRetryOptions(cx, policy.RetryOptions{MaxRetries: -1})
req, err := azruntime.NewRequest(cx, http.MethodGet, c.endpoint)
if err == nil {
_, err = c.azClient.Pipeline().Do(req)
}
if err != nil {
msg := err.Error()
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
msg = "managed identity timed out. See https://aka.ms/azsdk/go/identity/troubleshoot#dac for more information"
}
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, msg)
}
// send normal token requests from now on because something responded
c.probeIMDS = false
}
msg, err := c.createAuthRequest(ctx, id, scopes) msg, err := c.createAuthRequest(ctx, id, scopes)
if err != nil { if err != nil {
return azcore.AccessToken{}, err return azcore.AccessToken{}, err
@ -190,7 +231,7 @@ func (c *managedIdentityClient) authenticate(ctx context.Context, id ManagedIDKi
return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, err.Error(), nil, err) 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) return c.createAccessToken(resp)
} }
@ -201,15 +242,15 @@ 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) return azcore.AccessToken{}, newAuthenticationFailedError(credNameManagedIdentity, "the requested identity isn't assigned to this resource", resp, nil)
} }
msg := "failed to authenticate a system assigned identity" 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) msg += fmt.Sprintf(". The endpoint responded with %s", body)
} }
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, msg) return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, msg)
case http.StatusForbidden: case http.StatusForbidden:
// Docker Desktop runs a proxy that responds 403 to IMDS token requests. If we get that response, // 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 // 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), "A socket operation was attempted to an unreachable network") { if err == nil && strings.Contains(string(body), "unreachable") {
return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, fmt.Sprintf("unexpected response %q", string(body))) return azcore.AccessToken{}, newCredentialUnavailableError(credNameManagedIdentity, fmt.Sprintf("unexpected response %q", string(body)))
} }
} }
@ -226,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 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 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) return azcore.AccessToken{}, fmt.Errorf("internal AccessToken: %v", err)
} }
if value.ExpiresIn != "" { if value.ExpiresIn != "" {
@ -276,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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -286,7 +327,7 @@ func (c *managedIdentityClient) createIMDSAuthRequest(ctx context.Context, id Ma
q.Add("resource", strings.Join(scopes, " ")) q.Add("resource", strings.Join(scopes, " "))
if id != nil { if id != nil {
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(msiResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }
@ -296,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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -306,7 +347,7 @@ func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context,
q.Add("resource", scopes[0]) q.Add("resource", scopes[0])
if id != nil { if id != nil {
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(miResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }
@ -316,7 +357,7 @@ func (c *managedIdentityClient) createAppServiceAuthRequest(ctx context.Context,
} }
func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id ManagedIDKind, scopes []string) (*policy.Request, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -329,7 +370,7 @@ func (c *managedIdentityClient) createAzureMLAuthRequest(ctx context.Context, id
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
log.Write(EventAuthentication, "WARNING: Azure ML doesn't support specifying a managed identity by resource ID") log.Write(EventAuthentication, "WARNING: Azure ML doesn't support specifying a managed identity by resource ID")
q.Set("clientid", "") q.Set("clientid", "")
q.Set(qpResID, id.String()) q.Set(miResID, id.String())
} else { } else {
q.Set("clientid", id.String()) q.Set("clientid", id.String())
} }
@ -339,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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -351,7 +392,7 @@ func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Conte
if id != nil { if id != nil {
log.Write(EventAuthentication, "WARNING: Service Fabric doesn't support selecting a user-assigned identity at runtime") log.Write(EventAuthentication, "WARNING: Service Fabric doesn't support selecting a user-assigned identity at runtime")
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(miResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }
@ -362,7 +403,7 @@ func (c *managedIdentityClient) createServiceFabricAuthRequest(ctx context.Conte
func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resources []string) (string, error) { 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 // 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 { if err != nil {
return "", err return "", err
} }
@ -384,22 +425,36 @@ func (c *managedIdentityClient) getAzureArcSecretKey(ctx context.Context, resour
} }
header := response.Header.Get("WWW-Authenticate") header := response.Header.Get("WWW-Authenticate")
if len(header) == 0 { 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 // the WWW-Authenticate header is expected in the following format: Basic realm=/some/file/path.key
pos := strings.LastIndex(header, "=") _, p, found := strings.Cut(header, "=")
if pos == -1 { if !found {
return "", fmt.Errorf("did not receive a correct value from WWW-Authenticate header: %s", header) 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 { 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 return string(key), nil
} }
func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, id ManagedIDKind, resources []string, key string) (*policy.Request, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -411,7 +466,7 @@ func (c *managedIdentityClient) createAzureArcAuthRequest(ctx context.Context, i
if id != nil { if id != nil {
log.Write(EventAuthentication, "WARNING: Azure Arc doesn't support user-assigned managed identities") log.Write(EventAuthentication, "WARNING: Azure Arc doesn't support user-assigned managed identities")
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(miResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }
@ -421,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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -437,7 +492,7 @@ func (c *managedIdentityClient) createCloudShellAuthRequest(ctx context.Context,
log.Write(EventAuthentication, "WARNING: Cloud Shell doesn't support user-assigned managed identities") log.Write(EventAuthentication, "WARNING: Cloud Shell doesn't support user-assigned managed identities")
q := request.Raw().URL.Query() q := request.Raw().URL.Query()
if id.idKind() == miResourceID { if id.idKind() == miResourceID {
q.Add(qpResID, id.String()) q.Add(miResID, id.String())
} else { } else {
q.Add(qpClientID, id.String()) q.Add(qpClientID, id.String())
} }

View File

@ -64,12 +64,19 @@ type ManagedIdentityCredentialOptions struct {
// instead of the hosting environment's default. The value may be the identity's client ID or resource ID, but note that // instead of the hosting environment's default. The value may be the identity's client ID or resource ID, but note that
// some platforms don't accept resource IDs. // some platforms don't accept resource IDs.
ID ManagedIDKind ID ManagedIDKind
// dac indicates whether the credential is part of DefaultAzureCredential. When true, and the environment doesn't have
// configuration for a specific managed identity API, the credential tries to determine whether IMDS is available before
// sending its first token request. It does this by sending a malformed request with a short timeout. Any response to that
// request is taken to mean IMDS is available, in which case the credential will send ordinary token requests thereafter
// with no special timeout. The purpose of this behavior is to prevent a very long timeout when IMDS isn't available.
dac bool
} }
// ManagedIdentityCredential authenticates an Azure managed identity in any hosting environment supporting managed identities. // ManagedIdentityCredential authenticates an Azure managed identity in any hosting environment supporting managed identities.
// This credential authenticates a system-assigned identity by default. Use ManagedIdentityCredentialOptions.ID to specify a // This credential authenticates a system-assigned identity by default. Use ManagedIdentityCredentialOptions.ID to specify a
// user-assigned identity. See Microsoft Entra ID documentation for more information about managed identities: // user-assigned identity. See Microsoft Entra ID documentation for more information about managed identities:
// https://learn.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview // https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview
type ManagedIdentityCredential struct { type ManagedIdentityCredential struct {
client *confidentialClient client *confidentialClient
mic *managedIdentityClient mic *managedIdentityClient

View File

@ -10,6 +10,7 @@ import (
"context" "context"
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"errors"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "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/policy"
@ -24,7 +25,7 @@ const credNameOBO = "OnBehalfOfCredential"
// is not an interactive authentication flow, an application using it must have admin consent for any delegated // is not an interactive authentication flow, an application using it must have admin consent for any delegated
// permissions before requesting tokens for them. See [Microsoft Entra ID documentation] for more details. // permissions before requesting tokens for them. See [Microsoft Entra ID documentation] for more details.
// //
// [Microsoft Entra ID documentation]: https://learn.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow // [Microsoft Entra ID documentation]: https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow
type OnBehalfOfCredential struct { type OnBehalfOfCredential struct {
client *confidentialClient client *confidentialClient
} }
@ -60,6 +61,19 @@ func NewOnBehalfOfCredentialWithCertificate(tenantID, clientID, userAssertion st
return newOnBehalfOfCredential(tenantID, clientID, userAssertion, cred, options) return newOnBehalfOfCredential(tenantID, clientID, userAssertion, cred, options)
} }
// NewOnBehalfOfCredentialWithClientAssertions constructs an OnBehalfOfCredential that authenticates with client assertions.
// userAssertion is the user's access token for the application. The getAssertion function should return client assertions
// that authenticate the application to Microsoft Entra ID, such as federated credentials.
func NewOnBehalfOfCredentialWithClientAssertions(tenantID, clientID, userAssertion string, getAssertion func(context.Context) (string, error), options *OnBehalfOfCredentialOptions) (*OnBehalfOfCredential, error) {
if getAssertion == nil {
return nil, errors.New("getAssertion can't be nil. It must be a function that returns client assertions")
}
cred := confidential.NewCredFromAssertionCallback(func(ctx context.Context, _ confidential.AssertionRequestOptions) (string, error) {
return getAssertion(ctx)
})
return newOnBehalfOfCredential(tenantID, clientID, userAssertion, cred, options)
}
// NewOnBehalfOfCredentialWithSecret constructs an OnBehalfOfCredential that authenticates with a client secret. // NewOnBehalfOfCredentialWithSecret constructs an OnBehalfOfCredential that authenticates with a client secret.
func NewOnBehalfOfCredentialWithSecret(tenantID, clientID, userAssertion, clientSecret string, options *OnBehalfOfCredentialOptions) (*OnBehalfOfCredential, error) { func NewOnBehalfOfCredentialWithSecret(tenantID, clientID, userAssertion, clientSecret string, options *OnBehalfOfCredentialOptions) (*OnBehalfOfCredential, error) {
cred, err := confidential.NewCredFromSecret(clientSecret) cred, err := confidential.NewCredFromSecret(clientSecret)

View File

@ -152,7 +152,7 @@ func (p *publicClient) GetToken(ctx context.Context, tro policy.TokenRequestOpti
return p.token(ar, err) return p.token(ar, err)
} }
if p.opts.DisableAutomaticAuthentication { if p.opts.DisableAutomaticAuthentication {
return azcore.AccessToken{}, errAuthenticationRequired return azcore.AccessToken{}, newauthenticationRequiredError(p.name, tro)
} }
at, err := p.reqToken(ctx, client, tro) at, err := p.reqToken(ctx, client, tro)
if err == nil { if err == nil {

View File

@ -0,0 +1,112 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# IMPORTANT: Do not invoke this file directly. Please instead run eng/common/TestResources/New-TestResources.ps1 from the repository root.
param (
[hashtable] $AdditionalParameters = @{},
[hashtable] $DeploymentOutputs
)
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
if ($CI) {
if (!$AdditionalParameters['deployResources']) {
Write-Host "Skipping post-provisioning script because resources weren't deployed"
return
}
az login --service-principal -u $DeploymentOutputs['AZIDENTITY_CLIENT_ID'] -p $DeploymentOutputs['AZIDENTITY_CLIENT_SECRET'] --tenant $DeploymentOutputs['AZIDENTITY_TENANT_ID']
az account set --subscription $DeploymentOutputs['AZIDENTITY_SUBSCRIPTION_ID']
}
Write-Host "Building container"
$image = "$($DeploymentOutputs['AZIDENTITY_ACR_LOGIN_SERVER'])/azidentity-managed-id-test"
Set-Content -Path "$PSScriptRoot/Dockerfile" -Value @"
FROM mcr.microsoft.com/oss/go/microsoft/golang:latest as builder
ENV GOARCH=amd64 GOWORK=off
COPY . /azidentity
WORKDIR /azidentity/testdata/managed-id-test
RUN go mod tidy
RUN go build -o /build/managed-id-test .
RUN GOOS=windows go build -o /build/managed-id-test.exe .
FROM mcr.microsoft.com/mirror/docker/library/alpine:3.16
RUN apk add gcompat
COPY --from=builder /build/* .
RUN chmod +x managed-id-test
CMD ["./managed-id-test"]
"@
# build from sdk/azidentity because we need that dir in the context (because the test app uses local azidentity)
docker build -t $image "$PSScriptRoot"
az acr login -n $DeploymentOutputs['AZIDENTITY_ACR_NAME']
docker push $image
$rg = $DeploymentOutputs['AZIDENTITY_RESOURCE_GROUP']
# ACI is easier to provision here than in the bicep file because the image isn't available before now
Write-Host "Deploying Azure Container Instance"
$aciName = "azidentity-test"
az container create -g $rg -n $aciName --image $image `
--acr-identity $($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
--assign-identity [system] $($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
--role "Storage Blob Data Reader" `
--scope $($DeploymentOutputs['AZIDENTITY_STORAGE_ID']) `
-e AZIDENTITY_STORAGE_NAME=$($DeploymentOutputs['AZIDENTITY_STORAGE_NAME']) `
AZIDENTITY_STORAGE_NAME_USER_ASSIGNED=$($DeploymentOutputs['AZIDENTITY_STORAGE_NAME_USER_ASSIGNED']) `
AZIDENTITY_USER_ASSIGNED_IDENTITY=$($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY']) `
FUNCTIONS_CUSTOMHANDLER_PORT=80
Write-Host "##vso[task.setvariable variable=AZIDENTITY_ACI_NAME;]$aciName"
# Azure Functions deployment: copy the Windows binary from the Docker image, deploy it in a zip
Write-Host "Deploying to Azure Functions"
$container = docker create $image
docker cp ${container}:managed-id-test.exe "$PSScriptRoot/testdata/managed-id-test/"
docker rm -v $container
Compress-Archive -Path "$PSScriptRoot/testdata/managed-id-test/*" -DestinationPath func.zip -Force
az functionapp deploy -g $rg -n $DeploymentOutputs['AZIDENTITY_FUNCTION_NAME'] --src-path func.zip --type zip
Write-Host "Creating federated identity"
$aksName = $DeploymentOutputs['AZIDENTITY_AKS_NAME']
$idName = $DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY_NAME']
$issuer = az aks show -g $rg -n $aksName --query "oidcIssuerProfile.issuerUrl" -otsv
$podName = "azidentity-test"
$serviceAccountName = "workload-identity-sa"
az identity federated-credential create -g $rg --identity-name $idName --issuer $issuer --name $idName --subject system:serviceaccount:default:$serviceAccountName
Write-Host "Deploying to AKS"
az aks get-credentials -g $rg -n $aksName
az aks update --attach-acr $DeploymentOutputs['AZIDENTITY_ACR_NAME'] -g $rg -n $aksName
Set-Content -Path "$PSScriptRoot/k8s.yaml" -Value @"
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: $($DeploymentOutputs['AZIDENTITY_USER_ASSIGNED_IDENTITY_CLIENT_ID'])
name: $serviceAccountName
namespace: default
---
apiVersion: v1
kind: Pod
metadata:
name: $podName
namespace: default
labels:
app: $podName
azure.workload.identity/use: "true"
spec:
serviceAccountName: $serviceAccountName
containers:
- name: $podName
image: $image
env:
- name: AZIDENTITY_STORAGE_NAME
value: $($DeploymentOutputs['AZIDENTITY_STORAGE_NAME_USER_ASSIGNED'])
- name: AZIDENTITY_USE_WORKLOAD_IDENTITY
value: "true"
- name: FUNCTIONS_CUSTOMHANDLER_PORT
value: "80"
nodeSelector:
kubernetes.io/os: linux
"@
kubectl apply -f "$PSScriptRoot/k8s.yaml"
Write-Host "##vso[task.setvariable variable=AZIDENTITY_POD_NAME;]$podName"

View File

@ -1,36 +1,44 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# IMPORTANT: Do not invoke this file directly. Please instead run eng/common/TestResources/New-TestResources.ps1 from the repository root.
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param ( param (
[hashtable] $AdditionalParameters = @{},
# Captures any arguments from eng/New-TestResources.ps1 not declared here (no parameter errors). # Captures any arguments from eng/New-TestResources.ps1 not declared here (no parameter errors).
[Parameter(ValueFromRemainingArguments = $true)] [Parameter(ValueFromRemainingArguments = $true)]
$RemainingArguments $RemainingArguments
) )
if (-not (Test-Path "$PSScriptRoot/sshkey.pub")) {
ssh-keygen -t rsa -b 4096 -f "$PSScriptRoot/sshkey" -N '' -C ''
}
$templateFileParameters['sshPubKey'] = Get-Content "$PSScriptRoot/sshkey.pub"
if (!$CI) { if (!$CI) {
# TODO: Remove this once auto-cloud config downloads are supported locally # TODO: Remove this once auto-cloud config downloads are supported locally
Write-Host "Skipping cert setup in local testing mode" Write-Host "Skipping cert setup in local testing mode"
return return
} }
if ($EnvironmentVariables -eq $null -or $EnvironmentVariables.Count -eq 0) { if ($null -eq $EnvironmentVariables -or $EnvironmentVariables.Count -eq 0) {
throw "EnvironmentVariables must be set in the calling script New-TestResources.ps1" throw "EnvironmentVariables must be set in the calling script New-TestResources.ps1"
} }
$tmp = $env:TEMP ? $env:TEMP : [System.IO.Path]::GetTempPath() $tmp = $env:TEMP ? $env:TEMP : [System.IO.Path]::GetTempPath()
$pfxPath = Join-Path $tmp "test.pfx" $pfxPath = Join-Path $tmp "test.pfx"
$pemPath = Join-Path $tmp "test.pem" $pemPath = Join-Path $tmp "test.pem"
$sniPath = Join-Path $tmp "testsni.pfx"
Write-Host "Creating identity test files: $pfxPath $pemPath $sniPath" Write-Host "Creating identity test files: $pfxPath $pemPath"
[System.Convert]::FromBase64String($EnvironmentVariables['PFX_CONTENTS']) | Set-Content -Path $pfxPath -AsByteStream [System.Convert]::FromBase64String($EnvironmentVariables['PFX_CONTENTS']) | Set-Content -Path $pfxPath -AsByteStream
Set-Content -Path $pemPath -Value $EnvironmentVariables['PEM_CONTENTS'] Set-Content -Path $pemPath -Value $EnvironmentVariables['PEM_CONTENTS']
[System.Convert]::FromBase64String($EnvironmentVariables['SNI_CONTENTS']) | Set-Content -Path $sniPath -AsByteStream
# Set for pipeline # Set for pipeline
Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_PFX;]$pfxPath" Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_PFX;]$pfxPath"
Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_PEM;]$pemPath" Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_PEM;]$pemPath"
Write-Host "##vso[task.setvariable variable=IDENTITY_SP_CERT_SNI;]$sniPath"
# Set for local # Set for local
$env:IDENTITY_SP_CERT_PFX = $pfxPath $env:IDENTITY_SP_CERT_PFX = $pfxPath
$env:IDENTITY_SP_CERT_PEM = $pemPath $env:IDENTITY_SP_CERT_PEM = $pemPath
$env:IDENTITY_SP_CERT_SNI = $sniPath

View File

@ -1 +1,219 @@
param baseName string // Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
@description('Kubernetes cluster admin user name.')
param adminUser string = 'azureuser'
@minLength(6)
@maxLength(23)
@description('The base resource name.')
param baseName string = resourceGroup().name
@description('Whether to deploy resources. When set to false, this file deploys nothing.')
param deployResources bool = false
param sshPubKey string = ''
@description('The location of the resource. By default, this is the same as the resource group.')
param location string = resourceGroup().location
// https://learn.microsoft.com/azure/role-based-access-control/built-in-roles
var acrPull = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
var blobReader = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')
resource sa 'Microsoft.Storage/storageAccounts@2021-08-01' = if (deployResources) {
kind: 'StorageV2'
location: location
name: 'sa${uniqueString(baseName)}'
properties: {
accessTier: 'Hot'
}
sku: {
name: 'Standard_LRS'
}
}
resource saUserAssigned 'Microsoft.Storage/storageAccounts@2021-08-01' = if (deployResources) {
kind: 'StorageV2'
location: location
name: 'sa2${uniqueString(baseName)}'
properties: {
accessTier: 'Hot'
}
sku: {
name: 'Standard_LRS'
}
}
resource usermgdid 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = if (deployResources) {
location: location
name: baseName
}
resource acrPullContainerInstance 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (deployResources) {
name: guid(resourceGroup().id, acrPull, 'containerInstance')
properties: {
principalId: deployResources ? usermgdid.properties.principalId : ''
principalType: 'ServicePrincipal'
roleDefinitionId: acrPull
}
scope: containerRegistry
}
resource blobRoleUserAssigned 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (deployResources) {
scope: saUserAssigned
name: guid(resourceGroup().id, blobReader, usermgdid.id)
properties: {
principalId: deployResources ? usermgdid.properties.principalId : ''
principalType: 'ServicePrincipal'
roleDefinitionId: blobReader
}
}
resource blobRoleFunc 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (deployResources) {
name: guid(resourceGroup().id, blobReader, 'azfunc')
properties: {
principalId: deployResources ? azfunc.identity.principalId : ''
roleDefinitionId: blobReader
principalType: 'ServicePrincipal'
}
scope: sa
}
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' = if (deployResources) {
location: location
name: uniqueString(resourceGroup().id)
properties: {
adminUserEnabled: true
}
sku: {
name: 'Basic'
}
}
resource farm 'Microsoft.Web/serverfarms@2021-03-01' = if (deployResources) {
kind: 'app'
location: location
name: '${baseName}_asp'
properties: {}
sku: {
capacity: 1
family: 'B'
name: 'B1'
size: 'B1'
tier: 'Basic'
}
}
resource azfunc 'Microsoft.Web/sites@2021-03-01' = if (deployResources) {
identity: {
type: 'SystemAssigned, UserAssigned'
userAssignedIdentities: {
'${deployResources ? usermgdid.id : ''}': {}
}
}
kind: 'functionapp'
location: location
name: '${baseName}func'
properties: {
enabled: true
httpsOnly: true
keyVaultReferenceIdentity: 'SystemAssigned'
serverFarmId: farm.id
siteConfig: {
alwaysOn: true
appSettings: [
{
name: 'AZIDENTITY_STORAGE_NAME'
value: deployResources ? sa.name : null
}
{
name: 'AZIDENTITY_STORAGE_NAME_USER_ASSIGNED'
value: deployResources ? saUserAssigned.name : null
}
{
name: 'AZIDENTITY_USER_ASSIGNED_IDENTITY'
value: deployResources ? usermgdid.id : null
}
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${deployResources ? sa.name : ''};EndpointSuffix=${deployResources ? environment().suffixes.storage : ''};AccountKey=${deployResources ? sa.listKeys().keys[0].value : ''}'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'custom'
}
{
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
value: 'DefaultEndpointsProtocol=https;AccountName=${deployResources ? sa.name : ''};EndpointSuffix=${deployResources ? environment().suffixes.storage : ''};AccountKey=${deployResources ? sa.listKeys().keys[0].value : ''}'
}
{
name: 'WEBSITE_CONTENTSHARE'
value: toLower('${baseName}-func')
}
]
http20Enabled: true
minTlsVersion: '1.2'
}
}
}
resource aks 'Microsoft.ContainerService/managedClusters@2023-06-01' = if (deployResources) {
name: baseName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
agentPoolProfiles: [
{
count: 1
enableAutoScaling: false
kubeletDiskType: 'OS'
mode: 'System'
name: 'agentpool'
osDiskSizeGB: 128
osDiskType: 'Managed'
osSKU: 'Ubuntu'
osType: 'Linux'
type: 'VirtualMachineScaleSets'
vmSize: 'Standard_D2s_v3'
}
]
dnsPrefix: 'identitytest'
enableRBAC: true
linuxProfile: {
adminUsername: adminUser
ssh: {
publicKeys: [
{
keyData: sshPubKey
}
]
}
}
oidcIssuerProfile: {
enabled: true
}
securityProfile: {
workloadIdentity: {
enabled: true
}
}
}
}
output AZIDENTITY_ACR_LOGIN_SERVER string = deployResources ? containerRegistry.properties.loginServer : ''
output AZIDENTITY_ACR_NAME string = deployResources ? containerRegistry.name : ''
output AZIDENTITY_AKS_NAME string = deployResources ? aks.name : ''
output AZIDENTITY_FUNCTION_NAME string = deployResources ? azfunc.name : ''
output AZIDENTITY_STORAGE_ID string = deployResources ? sa.id : ''
output AZIDENTITY_STORAGE_NAME string = deployResources ? sa.name : ''
output AZIDENTITY_STORAGE_NAME_USER_ASSIGNED string = deployResources ? saUserAssigned.name : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY string = deployResources ? usermgdid.id : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY_CLIENT_ID string = deployResources ? usermgdid.properties.clientId : ''
output AZIDENTITY_USER_ASSIGNED_IDENTITY_NAME string = deployResources ? usermgdid.name : ''

View File

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

View File

@ -39,6 +39,11 @@ type PayloadOptions struct {
// Subsequent reads will access the cached value. // Subsequent reads will access the cached value.
// Exported as runtime.Payload() WITHOUT the opts parameter. // Exported as runtime.Payload() WITHOUT the opts parameter.
func Payload(resp *http.Response, opts *PayloadOptions) ([]byte, error) { func Payload(resp *http.Response, opts *PayloadOptions) ([]byte, error) {
if resp.Body == nil {
// this shouldn't happen in real-world scenarios as a
// response with no body should set it to http.NoBody
return nil, nil
}
modifyBytes := func(b []byte) []byte { return b } modifyBytes := func(b []byte) []byte { return b }
if opts != nil && opts.BytesModifier != nil { if opts != nil && opts.BytesModifier != nil {
modifyBytes = opts.BytesModifier modifyBytes = opts.BytesModifier

View File

@ -48,8 +48,8 @@ duplication.
.Net People, Take note on X509: .Net People, Take note on X509:
This uses x509.Certificates and private keys. x509 does not store private keys. .Net This uses x509.Certificates and private keys. x509 does not store private keys. .Net
has some x509.Certificate2 thing that has private keys, but that is just some bullcrap that .Net has a x509.Certificate2 abstraction that has private keys, but that just a strange invention.
added, it doesn't exist in real life. As such I've put a PEM decoder into here. As such I've put a PEM decoder into here.
*/ */
// TODO(msal): This should have example code for each method on client using Go's example doc framework. // TODO(msal): This should have example code for each method on client using Go's example doc framework.

View File

@ -217,11 +217,13 @@ func WithClaims(claims string) interface {
func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface { func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface {
AcquireSilentOption AcquireSilentOption
AcquireInteractiveOption AcquireInteractiveOption
AcquireByUsernamePasswordOption
options.CallOption options.CallOption
} { } {
return struct { return struct {
AcquireSilentOption AcquireSilentOption
AcquireInteractiveOption AcquireInteractiveOption
AcquireByUsernamePasswordOption
options.CallOption options.CallOption
}{ }{
CallOption: options.NewCallOption( CallOption: options.NewCallOption(
@ -231,6 +233,8 @@ func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface {
t.authnScheme = authnScheme t.authnScheme = authnScheme
case *interactiveAuthOptions: case *interactiveAuthOptions:
t.authnScheme = authnScheme t.authnScheme = authnScheme
case *acquireTokenByUsernamePasswordOptions:
t.authnScheme = authnScheme
default: default:
return fmt.Errorf("unexpected options type %T", a) return fmt.Errorf("unexpected options type %T", a)
} }
@ -349,6 +353,7 @@ func (pca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts
// acquireTokenByUsernamePasswordOptions contains optional configuration for AcquireTokenByUsernamePassword // acquireTokenByUsernamePasswordOptions contains optional configuration for AcquireTokenByUsernamePassword
type acquireTokenByUsernamePasswordOptions struct { type acquireTokenByUsernamePasswordOptions struct {
claims, tenantID string claims, tenantID string
authnScheme AuthenticationScheme
} }
// AcquireByUsernamePasswordOption is implemented by options for AcquireTokenByUsernamePassword // AcquireByUsernamePasswordOption is implemented by options for AcquireTokenByUsernamePassword
@ -374,6 +379,9 @@ func (pca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []s
authParams.Claims = o.claims authParams.Claims = o.claims
authParams.Username = username authParams.Username = username
authParams.Password = password authParams.Password = password
if o.authnScheme != nil {
authParams.AuthnScheme = o.authnScheme
}
token, err := pca.base.Token.UsernamePassword(ctx, authParams) token, err := pca.base.Token.UsernamePassword(ctx, authParams)
if err != nil { if err != nil {

View File

@ -62,7 +62,7 @@ func (m *SigningMethodECDSA) Verify(signingString string, sig []byte, key interf
case *ecdsa.PublicKey: case *ecdsa.PublicKey:
ecdsaKey = k ecdsaKey = k
default: default:
return newError("ECDSA verify expects *ecsda.PublicKey", ErrInvalidKeyType) return newError("ECDSA verify expects *ecdsa.PublicKey", ErrInvalidKeyType)
} }
if len(sig) != 2*m.KeySize { if len(sig) != 2*m.KeySize {
@ -96,7 +96,7 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) ([]byte
case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
ecdsaKey = k ecdsaKey = k
default: default:
return nil, newError("ECDSA sign expects *ecsda.PrivateKey", ErrInvalidKeyType) return nil, newError("ECDSA sign expects *ecdsa.PrivateKey", ErrInvalidKeyType)
} }
// Create the hasher // Create the hasher

View File

@ -91,7 +91,7 @@ func (m *SigningMethodHMAC) Verify(signingString string, sig []byte, key interfa
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) ([]byte, error) { func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) ([]byte, error) {
if keyBytes, ok := key.([]byte); ok { if keyBytes, ok := key.([]byte); ok {
if !m.Hash.Available() { if !m.Hash.Available() {
return nil, newError("HMAC sign expects []byte", ErrInvalidKeyType) return nil, ErrHashUnavailable
} }
hasher := hmac.New(m.Hash.New, keyBytes) hasher := hmac.New(m.Hash.New, keyBytes)
@ -100,5 +100,5 @@ func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) ([]byte,
return hasher.Sum(nil), nil return hasher.Sum(nil), nil
} }
return nil, ErrInvalidKeyType return nil, newError("HMAC sign expects []byte", ErrInvalidKeyType)
} }

10
vendor/modules.txt vendored
View File

@ -1,4 +1,4 @@
# github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 # github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
## explicit; go 1.18 ## explicit; go 1.18
github.com/Azure/azure-sdk-for-go/sdk/azcore github.com/Azure/azure-sdk-for-go/sdk/azcore
github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource
@ -20,11 +20,11 @@ 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/azcore/streaming
github.com/Azure/azure-sdk-for-go/sdk/azcore/to github.com/Azure/azure-sdk-for-go/sdk/azcore/to
github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing
# github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 # github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0
## explicit; go 1.18 ## explicit; go 1.18
github.com/Azure/azure-sdk-for-go/sdk/azidentity github.com/Azure/azure-sdk-for-go/sdk/azidentity
github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal
# github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 # github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0
## explicit; go 1.18 ## explicit; go 1.18
github.com/Azure/azure-sdk-for-go/sdk/internal/diag github.com/Azure/azure-sdk-for-go/sdk/internal/diag
github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo
@ -39,7 +39,7 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets
# github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 # github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0
## explicit; go 1.18 ## explicit; go 1.18
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal
# github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 # github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2
## explicit; go 1.18 ## explicit; go 1.18
github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache
github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential
@ -307,7 +307,7 @@ github.com/gogo/protobuf/gogoproto
github.com/gogo/protobuf/proto github.com/gogo/protobuf/proto
github.com/gogo/protobuf/protoc-gen-gogo/descriptor github.com/gogo/protobuf/protoc-gen-gogo/descriptor
github.com/gogo/protobuf/sortkeys github.com/gogo/protobuf/sortkeys
# github.com/golang-jwt/jwt/v5 v5.2.0 # github.com/golang-jwt/jwt/v5 v5.2.1
## explicit; go 1.18 ## explicit; go 1.18
github.com/golang-jwt/jwt/v5 github.com/golang-jwt/jwt/v5
# github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da # github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da