From 383bc98f5ca15fc43f07dcbf567dd7cd1ffe2e33 Mon Sep 17 00:00:00 2001 From: Tom Harvey Date: Mon, 10 Dec 2018 21:23:30 +0000 Subject: [PATCH] backend/azurerm: Support for authenticating using the Azure CLI (#19465) * Upgrading to 2.0.0 of github.com/hashicorp/go-azure-helpers * Support for authenticating using Azure CLI * backend/azurerm: support for authenticating using the Azure CLI --- backend/remote-state/azure/arm_client.go | 3 +- go.mod | 6 +- go.sum | 6 + .../github.com/dgrijalva/jwt-go/.travis.yml | 5 + .../dgrijalva/jwt-go/MIGRATION_GUIDE.md | 5 +- vendor/github.com/dgrijalva/jwt-go/README.md | 31 +++- .../dgrijalva/jwt-go/VERSION_HISTORY.md | 13 ++ vendor/github.com/dgrijalva/jwt-go/ecdsa.go | 1 + vendor/github.com/dgrijalva/jwt-go/errors.go | 6 +- vendor/github.com/dgrijalva/jwt-go/hmac.go | 3 +- vendor/github.com/dgrijalva/jwt-go/parser.go | 134 +++++++++------- vendor/github.com/dgrijalva/jwt-go/rsa.go | 5 +- .../github.com/dgrijalva/jwt-go/rsa_utils.go | 32 ++++ .../auth_method_azure_cli_parsing.go | 11 +- .../auth_method_azure_cli_token.go | 146 ++++++++++++++++++ .../authentication/azure_cli_access_token.go | 6 +- .../authentication/azure_cli_profile.go | 7 - .../azure_cli_profile_population.go | 26 +++- .../authentication/builder.go | 8 +- vendor/github.com/mitchellh/go-homedir/go.mod | 1 + .../mitchellh/go-homedir/homedir.go | 56 ++++--- vendor/modules.txt | 6 +- website/docs/backends/types/azurerm.html.md | 2 +- 23 files changed, 392 insertions(+), 127 deletions(-) create mode 100644 vendor/github.com/hashicorp/go-azure-helpers/authentication/auth_method_azure_cli_token.go create mode 100644 vendor/github.com/mitchellh/go-homedir/go.mod diff --git a/backend/remote-state/azure/arm_client.go b/backend/remote-state/azure/arm_client.go index 20814a0ee..bcc511a44 100644 --- a/backend/remote-state/azure/arm_client.go +++ b/backend/remote-state/azure/arm_client.go @@ -65,9 +65,10 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) { MsiEndpoint: config.MsiEndpoint, // Feature Toggles + SupportsAzureCliToken: true, SupportsClientSecretAuth: true, SupportsManagedServiceIdentity: config.UseMsi, - // TODO: support for Azure CLI / Client Certificate auth + // TODO: support for Client Certificate auth } armConfig, err := builder.Build() if err != nil { diff --git a/go.mod b/go.mod index 6998fb395..fb9876a5b 100644 --- a/go.mod +++ b/go.mod @@ -32,8 +32,6 @@ require ( github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/davecgh/go-spew v1.1.1 - github.com/dgrijalva/jwt-go v0.0.0-20160617170158-f0777076321a // indirect - github.com/dimchansky/utfbom v1.0.0 // indirect github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31 // indirect github.com/dylanmei/iso8601 v0.1.0 // indirect github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 @@ -55,7 +53,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.5.1 // indirect github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089 github.com/hashicorp/errwrap v1.0.0 - github.com/hashicorp/go-azure-helpers v0.0.0-20181122151743-c51a3103be3b + github.com/hashicorp/go-azure-helpers v0.0.0-20181126135526-ec113df69f49 github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de github.com/hashicorp/go-cleanhttp v0.5.0 github.com/hashicorp/go-getter v0.0.0-20180327010114-90bb99a48d86 @@ -98,7 +96,7 @@ require ( github.com/mitchellh/cli v0.0.0-20171129193617-33edc47170b5 github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286 github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3 - github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 + github.com/mitchellh/go-homedir v1.0.0 github.com/mitchellh/go-linereader v0.0.0-20141013185533-07bab5fdd958 github.com/mitchellh/go-wordwrap v1.0.0 github.com/mitchellh/hashstructure v0.0.0-20160209213820-6b17d669fac5 diff --git a/go.sum b/go.sum index 3c563ad6f..a565b886f 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v0.0.0-20160617170158-f0777076321a h1:pzKxqfSfp4kqrm6jfyVYYkWhf+e1hPRt3rX+Yj/3UBU= github.com/dgrijalva/jwt-go v0.0.0-20160617170158-f0777076321a/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.0.0 h1:fGC2kkf4qOoKqZ4q7iIh+Vef4ubC1c38UDsEyZynZPc= github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31 h1:Dzuw9GtbmllUqEcoHfScT9YpKFUssSiZ5PgZkIGf/YQ= @@ -121,6 +123,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-azure-helpers v0.0.0-20181122151743-c51a3103be3b h1:AJjRaIeZiWlhYVf2skNYOjooaaIOcQzS94a3iSKnXKE= github.com/hashicorp/go-azure-helpers v0.0.0-20181122151743-c51a3103be3b/go.mod h1:e+GPy2nvD+spqsdjUyw5tbo73rBbu955QBaV9GZoBEA= +github.com/hashicorp/go-azure-helpers v0.0.0-20181126135526-ec113df69f49 h1:jkGYE3AQEm95w4n8o/iR59duXnuSjMCf1MCNSwOJxAw= +github.com/hashicorp/go-azure-helpers v0.0.0-20181126135526-ec113df69f49/go.mod h1:Y5ejHZY3jQby82dOASJzyQ2xZw37zs+D5x6AaOC6O5E= github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8= github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= @@ -229,6 +233,8 @@ github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3 h1:dECZqiJ github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3/go.mod h1:eOsF2yLPlBBJPvD+nhl5QMTBSOBbOph6N7j/IDUw7PY= github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ewJF+mqYPxCkmBAirRnPaHEB26UkNuPyjlk= github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-linereader v0.0.0-20141013185533-07bab5fdd958 h1:wN+5lV34eSnVSZgBLWRGHr6L4giR3/wI2B9DLmVnlfI= github.com/mitchellh/go-linereader v0.0.0-20141013185533-07bab5fdd958/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= diff --git a/vendor/github.com/dgrijalva/jwt-go/.travis.yml b/vendor/github.com/dgrijalva/jwt-go/.travis.yml index bde823d8a..1027f56cd 100644 --- a/vendor/github.com/dgrijalva/jwt-go/.travis.yml +++ b/vendor/github.com/dgrijalva/jwt-go/.travis.yml @@ -1,8 +1,13 @@ language: go +script: + - go vet ./... + - go test -v ./... + go: - 1.3 - 1.4 - 1.5 - 1.6 + - 1.7 - tip diff --git a/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md b/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md index fd62e9490..7fc1f793c 100644 --- a/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md +++ b/vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md @@ -56,8 +56,9 @@ This simple parsing example: is directly mapped to: ```go - if token, err := request.ParseFromRequest(tokenString, request.OAuth2Extractor, req, keyLookupFunc); err == nil { - fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) + if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil { + claims := token.Claims.(jwt.MapClaims) + fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"]) } ``` diff --git a/vendor/github.com/dgrijalva/jwt-go/README.md b/vendor/github.com/dgrijalva/jwt-go/README.md index f48365faf..d358d881b 100644 --- a/vendor/github.com/dgrijalva/jwt-go/README.md +++ b/vendor/github.com/dgrijalva/jwt-go/README.md @@ -1,11 +1,15 @@ -A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) +# jwt-go [![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go) +[![GoDoc](https://godoc.org/github.com/dgrijalva/jwt-go?status.svg)](https://godoc.org/github.com/dgrijalva/jwt-go) -**BREAKING CHANGES:*** Version 3.0.0 is here. It includes _a lot_ of changes including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code. +A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html) -**NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect. +**NEW VERSION COMING:** There have been a lot of improvements suggested since the version 3.0.0 released in 2016. I'm working now on cutting two different releases: 3.2.0 will contain any non-breaking changes or enhancements. 4.0.0 will follow shortly which will include breaking changes. See the 4.0.0 milestone to get an idea of what's coming. If you have other ideas, or would like to participate in 4.0.0, now's the time. If you depend on this library and don't want to be interrupted, I recommend you use your dependency mangement tool to pin to version 3. +**SECURITY NOTICE:** Some older versions of Go have a security issue in the cryotp/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue #216 for more detail. + +**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided. ## What the heck is a JWT? @@ -37,7 +41,7 @@ Here's an example of an extension that integrates with the Google App Engine sig ## Compliance -This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences: +This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences: * In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key. @@ -47,7 +51,10 @@ This library is considered production ready. Feedback and feature requests are This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases). -While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v2`. It will do the right thing WRT semantic versioning. +While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v3`. It will do the right thing WRT semantic versioning. + +**BREAKING CHANGES:*** +* Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code. ## Usage Tips @@ -68,18 +75,26 @@ Symmetric signing methods, such as HSA, use only a single secret. This is probab Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification. +### Signing Methods and Key Types + +Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones: + +* The [HMAC signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation +* The [RSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation +* The [ECDSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation + ### JWT and OAuth It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication. Without going too far down the rabbit hole, here's a description of the interaction of these technologies: -* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth. +* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth. * OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token. * Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL. - + ## More Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). -The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in to documentation. +The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation. diff --git a/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md b/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md index b605b4509..637029831 100644 --- a/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md +++ b/vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md @@ -1,5 +1,18 @@ ## `jwt-go` Version History +#### 3.2.0 + +* Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation +* HMAC signing method returns `ErrInvalidKeyType` instead of `ErrInvalidKey` where appropriate +* Added options to `request.ParseFromRequest`, which allows for an arbitrary list of modifiers to parsing behavior. Initial set include `WithClaims` and `WithParser`. Existing usage of this function will continue to work as before. +* Deprecated `ParseFromRequestWithClaims` to simplify API in the future. + +#### 3.1.0 + +* Improvements to `jwt` command line tool +* Added `SkipClaimsValidation` option to `Parser` +* Documentation updates + #### 3.0.0 * **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code diff --git a/vendor/github.com/dgrijalva/jwt-go/ecdsa.go b/vendor/github.com/dgrijalva/jwt-go/ecdsa.go index 2f59a2223..f97738124 100644 --- a/vendor/github.com/dgrijalva/jwt-go/ecdsa.go +++ b/vendor/github.com/dgrijalva/jwt-go/ecdsa.go @@ -14,6 +14,7 @@ var ( ) // Implements the ECDSA family of signing methods signing methods +// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification type SigningMethodECDSA struct { Name string Hash crypto.Hash diff --git a/vendor/github.com/dgrijalva/jwt-go/errors.go b/vendor/github.com/dgrijalva/jwt-go/errors.go index 662df19d4..1c93024aa 100644 --- a/vendor/github.com/dgrijalva/jwt-go/errors.go +++ b/vendor/github.com/dgrijalva/jwt-go/errors.go @@ -51,13 +51,9 @@ func (e ValidationError) Error() string { } else { return "token is invalid" } - return e.Inner.Error() } // No errors func (e *ValidationError) valid() bool { - if e.Errors > 0 { - return false - } - return true + return e.Errors == 0 } diff --git a/vendor/github.com/dgrijalva/jwt-go/hmac.go b/vendor/github.com/dgrijalva/jwt-go/hmac.go index c22991925..addbe5d40 100644 --- a/vendor/github.com/dgrijalva/jwt-go/hmac.go +++ b/vendor/github.com/dgrijalva/jwt-go/hmac.go @@ -7,6 +7,7 @@ import ( ) // Implements the HMAC-SHA family of signing methods signing methods +// Expects key type of []byte for both signing and validation type SigningMethodHMAC struct { Name string Hash crypto.Hash @@ -90,5 +91,5 @@ func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, return EncodeSegment(hasher.Sum(nil)), nil } - return "", ErrInvalidKey + return "", ErrInvalidKeyType } diff --git a/vendor/github.com/dgrijalva/jwt-go/parser.go b/vendor/github.com/dgrijalva/jwt-go/parser.go index 7020c52a1..d6901d9ad 100644 --- a/vendor/github.com/dgrijalva/jwt-go/parser.go +++ b/vendor/github.com/dgrijalva/jwt-go/parser.go @@ -8,8 +8,9 @@ import ( ) type Parser struct { - ValidMethods []string // If populated, only these methods will be considered valid - UseJSONNumber bool // Use JSON Number format in JSON decoder + ValidMethods []string // If populated, only these methods will be considered valid + UseJSONNumber bool // Use JSON Number format in JSON decoder + SkipClaimsValidation bool // Skip claims validation during token parsing } // Parse, validate, and return a token. @@ -20,55 +21,9 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { } func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { - parts := strings.Split(tokenString, ".") - if len(parts) != 3 { - return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) - } - - var err error - token := &Token{Raw: tokenString} - - // parse Header - var headerBytes []byte - if headerBytes, err = DecodeSegment(parts[0]); err != nil { - if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { - return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed) - } - return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - if err = json.Unmarshal(headerBytes, &token.Header); err != nil { - return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - - // parse Claims - var claimBytes []byte - token.Claims = claims - - if claimBytes, err = DecodeSegment(parts[1]); err != nil { - return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) - if p.UseJSONNumber { - dec.UseNumber() - } - // JSON Decode. Special case for map type to avoid weird pointer behavior - if c, ok := token.Claims.(MapClaims); ok { - err = dec.Decode(&c) - } else { - err = dec.Decode(&claims) - } - // Handle decode error + token, parts, err := p.ParseUnverified(tokenString, claims) if err != nil { - return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} - } - - // Lookup signature method - if method, ok := token.Header["alg"].(string); ok { - if token.Method = GetSigningMethod(method); token.Method == nil { - return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable) - } - } else { - return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable) + return token, err } // Verify signing method is in the required set @@ -95,20 +50,25 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf } if key, err = keyFunc(token); err != nil { // keyFunc returned an error + if ve, ok := err.(*ValidationError); ok { + return token, ve + } return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} } vErr := &ValidationError{} // Validate Claims - if err := token.Claims.Valid(); err != nil { + if !p.SkipClaimsValidation { + if err := token.Claims.Valid(); err != nil { - // If the Claims Valid returned an error, check if it is a validation error, - // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set - if e, ok := err.(*ValidationError); !ok { - vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} - } else { - vErr = e + // If the Claims Valid returned an error, check if it is a validation error, + // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set + if e, ok := err.(*ValidationError); !ok { + vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} + } else { + vErr = e + } } } @@ -126,3 +86,63 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf return token, vErr } + +// WARNING: Don't use this method unless you know what you're doing +// +// This method parses the token but doesn't validate the signature. It's only +// ever useful in cases where you know the signature is valid (because it has +// been checked previously in the stack) and you want to extract values from +// it. +func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) { + parts = strings.Split(tokenString, ".") + if len(parts) != 3 { + return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) + } + + token = &Token{Raw: tokenString} + + // parse Header + var headerBytes []byte + if headerBytes, err = DecodeSegment(parts[0]); err != nil { + if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { + return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed) + } + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + if err = json.Unmarshal(headerBytes, &token.Header); err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + + // parse Claims + var claimBytes []byte + token.Claims = claims + + if claimBytes, err = DecodeSegment(parts[1]); err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) + if p.UseJSONNumber { + dec.UseNumber() + } + // JSON Decode. Special case for map type to avoid weird pointer behavior + if c, ok := token.Claims.(MapClaims); ok { + err = dec.Decode(&c) + } else { + err = dec.Decode(&claims) + } + // Handle decode error + if err != nil { + return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} + } + + // Lookup signature method + if method, ok := token.Header["alg"].(string); ok { + if token.Method = GetSigningMethod(method); token.Method == nil { + return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable) + } + } else { + return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable) + } + + return token, parts, nil +} diff --git a/vendor/github.com/dgrijalva/jwt-go/rsa.go b/vendor/github.com/dgrijalva/jwt-go/rsa.go index 0ae0b1984..e4caf1ca4 100644 --- a/vendor/github.com/dgrijalva/jwt-go/rsa.go +++ b/vendor/github.com/dgrijalva/jwt-go/rsa.go @@ -7,6 +7,7 @@ import ( ) // Implements the RSA family of signing methods signing methods +// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation type SigningMethodRSA struct { Name string Hash crypto.Hash @@ -44,7 +45,7 @@ func (m *SigningMethodRSA) Alg() string { } // Implements the Verify method from SigningMethod -// For this signing method, must be an rsa.PublicKey structure. +// For this signing method, must be an *rsa.PublicKey structure. func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error { var err error @@ -73,7 +74,7 @@ func (m *SigningMethodRSA) Verify(signingString, signature string, key interface } // Implements the Sign method from SigningMethod -// For this signing method, must be an rsa.PrivateKey structure. +// For this signing method, must be an *rsa.PrivateKey structure. func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) { var rsaKey *rsa.PrivateKey var ok bool diff --git a/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go b/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go index 213a90dbb..a5ababf95 100644 --- a/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go +++ b/vendor/github.com/dgrijalva/jwt-go/rsa_utils.go @@ -39,6 +39,38 @@ func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { return pkey, nil } +// Parse PEM encoded PKCS1 or PKCS8 private key protected with password +func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + var parsedKey interface{} + + var blockDecrypted []byte + if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil { + return nil, err + } + + if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil { + if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil { + return nil, err + } + } + + var pkey *rsa.PrivateKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { + return nil, ErrNotRSAPrivateKey + } + + return pkey, nil +} + // Parse PEM encoded PKCS1 or PKCS8 public key func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { var err error diff --git a/vendor/github.com/hashicorp/go-azure-helpers/authentication/auth_method_azure_cli_parsing.go b/vendor/github.com/hashicorp/go-azure-helpers/authentication/auth_method_azure_cli_parsing.go index 623e68d9e..70ba05a15 100644 --- a/vendor/github.com/hashicorp/go-azure-helpers/authentication/auth_method_azure_cli_parsing.go +++ b/vendor/github.com/hashicorp/go-azure-helpers/authentication/auth_method_azure_cli_parsing.go @@ -48,25 +48,16 @@ func (a azureCliParsingAuth) build(b Builder) (authMethod, error) { } func (a azureCliParsingAuth) isApplicable(b Builder) bool { - return b.SupportsAzureCliCloudShellParsing + return b.SupportsAzureCliParsing } func (a azureCliParsingAuth) getAuthorizationToken(oauthConfig *adal.OAuthConfig, endpoint string) (*autorest.BearerAuthorizer, error) { - if a.profile.usingCloudShell { - // load the refreshed tokens from the CloudShell Azure CLI credentials - err := a.profile.populateClientIdAndAccessToken() - if err != nil { - return nil, fmt.Errorf("Error loading the refreshed CloudShell tokens: %+v", err) - } - } - spt, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, a.profile.clientId, endpoint, *a.profile.accessToken) if err != nil { return nil, err } err = spt.Refresh() - if err != nil { return nil, fmt.Errorf("Error refreshing Service Principal Token: %+v", err) } diff --git a/vendor/github.com/hashicorp/go-azure-helpers/authentication/auth_method_azure_cli_token.go b/vendor/github.com/hashicorp/go-azure-helpers/authentication/auth_method_azure_cli_token.go new file mode 100644 index 000000000..9518cea9f --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-helpers/authentication/auth_method_azure_cli_token.go @@ -0,0 +1,146 @@ +package authentication + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + "strings" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/adal" + "github.com/Azure/go-autorest/autorest/azure/cli" + "github.com/hashicorp/go-multierror" +) + +type azureCliTokenAuth struct { + profile *azureCLIProfile +} + +func (a azureCliTokenAuth) build(b Builder) (authMethod, error) { + auth := azureCliTokenAuth{ + profile: &azureCLIProfile{ + clientId: b.ClientID, + environment: b.Environment, + subscriptionId: b.SubscriptionID, + tenantId: b.TenantID, + }, + } + profilePath, err := cli.ProfilePath() + if err != nil { + return nil, fmt.Errorf("Error loading the Profile Path from the Azure CLI: %+v", err) + } + + profile, err := cli.LoadProfile(profilePath) + if err != nil { + return nil, fmt.Errorf("Azure CLI Authorization Profile was not found. Please ensure the Azure CLI is installed and then log-in with `az login`.") + } + + auth.profile.profile = profile + + err = auth.profile.populateFields() + if err != nil { + return nil, err + } + + err = auth.profile.populateClientId() + if err != nil { + return nil, fmt.Errorf("Error populating Client ID from the Azure CLI: %+v", err) + } + + return auth, nil +} + +func (a azureCliTokenAuth) isApplicable(b Builder) bool { + return b.SupportsAzureCliToken +} + +func (a azureCliTokenAuth) getAuthorizationToken(oauthConfig *adal.OAuthConfig, endpoint string) (*autorest.BearerAuthorizer, error) { + // the Azure CLI appears to cache these, so to maintain compatibility with the interface this method is intentionally not on the pointer + token, err := obtainAuthorizationToken(endpoint, a.profile.subscriptionId) + if err != nil { + return nil, fmt.Errorf("Error obtaining Authorization Token from the Azure CLI: %s", err) + } + + adalToken, err := token.ToADALToken() + if err != nil { + return nil, fmt.Errorf("Error converting Authorization Token to an ADAL Token: %s", err) + } + + spt, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, a.profile.clientId, endpoint, adalToken) + if err != nil { + return nil, err + } + + auth := autorest.NewBearerAuthorizer(spt) + return auth, nil +} + +func (a azureCliTokenAuth) name() string { + return "Obtaining a token from the Azure CLI" +} + +func (a azureCliTokenAuth) populateConfig(c *Config) error { + c.ClientID = a.profile.clientId + c.Environment = a.profile.environment + c.SubscriptionID = a.profile.subscriptionId + c.TenantID = a.profile.tenantId + return nil +} + +func (a azureCliTokenAuth) validate() error { + var err *multierror.Error + + errorMessageFmt := "A %s was not found in your Azure CLI Credentials.\n\nPlease login to the Azure CLI again via `az login`" + + if a.profile == nil { + return fmt.Errorf("Azure CLI Profile is nil - this is an internal error and should be reported.") + } + + if a.profile.clientId == "" { + err = multierror.Append(err, fmt.Errorf(errorMessageFmt, "Client ID")) + } + + if a.profile.subscriptionId == "" { + err = multierror.Append(err, fmt.Errorf(errorMessageFmt, "Subscription ID")) + } + + if a.profile.tenantId == "" { + err = multierror.Append(err, fmt.Errorf(errorMessageFmt, "Tenant ID")) + } + + return err.ErrorOrNil() +} + +func obtainAuthorizationToken(endpoint string, subscriptionId string) (*cli.Token, error) { + var stderr bytes.Buffer + var stdout bytes.Buffer + + cmd := exec.Command("az", "account", "get-access-token", "--resource", endpoint, "--subscription", subscriptionId, "-o=json") + + cmd.Stderr = &stderr + cmd.Stdout = &stdout + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("Error launching Azure CLI: %+v", err) + } + + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("Error waiting for the Azure CLI: %+v", err) + } + + stdOutStr := stdout.String() + stdErrStr := stderr.String() + + if stdErrStr != "" { + return nil, fmt.Errorf("Error retrieving access token from Azure CLI: %s", strings.TrimSpace(stdErrStr)) + } + + var token *cli.Token + err := json.Unmarshal([]byte(stdOutStr), &token) + if err != nil { + return nil, fmt.Errorf("Error unmarshaling Access Token from the Azure CLI: %s", err) + } + + return token, nil +} diff --git a/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_access_token.go b/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_access_token.go index fc35da359..4e66dfcef 100644 --- a/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_access_token.go +++ b/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_access_token.go @@ -13,10 +13,9 @@ import ( type azureCliAccessToken struct { ClientID string AccessToken *adal.Token - IsCloudShell bool } -func findValidAccessTokenForTenant(tokens []cli.Token, tenantId string) (*azureCliAccessToken, error) { +func findValidAccessTokenForTenant(tokens []cli.Token, tenantId string, allowExpired bool) (*azureCliAccessToken, error) { for _, accessToken := range tokens { token, err := accessToken.ToADALToken() if err != nil { @@ -28,7 +27,7 @@ func findValidAccessTokenForTenant(tokens []cli.Token, tenantId string) (*azureC return nil, fmt.Errorf("Error parsing expiration date: %q", accessToken.ExpiresOn) } - if expirationDate.UTC().Before(time.Now().UTC()) { + if expirationDate.UTC().Before(time.Now().UTC()) && !allowExpired { log.Printf("[DEBUG] Token %q has expired", token.AccessToken) continue } @@ -46,7 +45,6 @@ func findValidAccessTokenForTenant(tokens []cli.Token, tenantId string) (*azureC validAccessToken := azureCliAccessToken{ ClientID: accessToken.ClientID, AccessToken: &token, - IsCloudShell: accessToken.RefreshToken == "", } return &validAccessToken, nil } diff --git a/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_profile.go b/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_profile.go index a70d90c39..b18d02f7e 100644 --- a/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_profile.go +++ b/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_profile.go @@ -13,7 +13,6 @@ type azureCLIProfile struct { subscriptionId string tenantId string accessToken *adal.Token - usingCloudShell bool } func (a *azureCLIProfile) populateFields() error { @@ -33,12 +32,6 @@ func (a *azureCLIProfile) populateFields() error { } } - // now we know the Subscription ID & Tenant ID we can find the associated Client ID/Access Token - err := a.populateClientIdAndAccessToken() - if err != nil { - return err - } - // always pull the environment from the Azure CLI, since the Access Token's associated with it return a.populateEnvironment() } diff --git a/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_profile_population.go b/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_profile_population.go index 65aa7f6d1..000875282 100644 --- a/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_profile_population.go +++ b/vendor/github.com/hashicorp/go-azure-helpers/authentication/azure_cli_profile_population.go @@ -27,6 +27,29 @@ func (a *azureCLIProfile) populateTenantID() error { return nil } +func (a *azureCLIProfile) populateClientId() error { + // we can now pull out the ClientID and the Access Token to use from the Access Token + tokensPath, err := cli.AccessTokensPath() + if err != nil { + return fmt.Errorf("Error loading the Tokens Path from the Azure CLI: %+v", err) + } + + tokens, err := cli.LoadTokens(tokensPath) + if err != nil { + return fmt.Errorf("No Authorization Tokens were found - please ensure the Azure CLI is installed and then log-in with `az login`.") + } + + validToken, err := findValidAccessTokenForTenant(tokens, a.tenantId, true) + if err != nil { + return fmt.Errorf("No Authorization Tokens were found - please re-authenticate using `az login`.") + } + + token := *validToken + a.clientId = token.ClientID + + return nil +} + func (a *azureCLIProfile) populateClientIdAndAccessToken() error { // we can now pull out the ClientID and the Access Token to use from the Access Token tokensPath, err := cli.AccessTokensPath() @@ -39,7 +62,7 @@ func (a *azureCLIProfile) populateClientIdAndAccessToken() error { return fmt.Errorf("No Authorization Tokens were found - please ensure the Azure CLI is installed and then log-in with `az login`.") } - validToken, err := findValidAccessTokenForTenant(tokens, a.tenantId) + validToken, err := findValidAccessTokenForTenant(tokens, a.tenantId, false) if err != nil { return fmt.Errorf("No (unexpired) Authorization Tokens were found - please re-authenticate using `az login`.") } @@ -47,7 +70,6 @@ func (a *azureCLIProfile) populateClientIdAndAccessToken() error { token := *validToken a.accessToken = token.AccessToken a.clientId = token.ClientID - a.usingCloudShell = token.IsCloudShell return nil } diff --git a/vendor/github.com/hashicorp/go-azure-helpers/authentication/builder.go b/vendor/github.com/hashicorp/go-azure-helpers/authentication/builder.go index b72ac805c..5eb023493 100644 --- a/vendor/github.com/hashicorp/go-azure-helpers/authentication/builder.go +++ b/vendor/github.com/hashicorp/go-azure-helpers/authentication/builder.go @@ -18,8 +18,11 @@ type Builder struct { // only applicable for Azure Stack at this time. CustomResourceManagerEndpoint string - // Azure CLI Parsing / CloudShell Auth - SupportsAzureCliCloudShellParsing bool + // Azure CLI Parsing + SupportsAzureCliParsing bool + + // Azure CLI Tokens Auth + SupportsAzureCliToken bool // Managed Service Identity Auth SupportsManagedServiceIdentity bool @@ -52,6 +55,7 @@ func (b Builder) Build() (*Config, error) { servicePrincipalClientCertificateAuth{}, servicePrincipalClientSecretAuth{}, managedServiceIdentityAuth{}, + azureCliTokenAuth{}, azureCliParsingAuth{}, } diff --git a/vendor/github.com/mitchellh/go-homedir/go.mod b/vendor/github.com/mitchellh/go-homedir/go.mod new file mode 100644 index 000000000..7efa09a04 --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/go.mod @@ -0,0 +1 @@ +module github.com/mitchellh/go-homedir diff --git a/vendor/github.com/mitchellh/go-homedir/homedir.go b/vendor/github.com/mitchellh/go-homedir/homedir.go index 47e1f9ef8..fb87bef94 100644 --- a/vendor/github.com/mitchellh/go-homedir/homedir.go +++ b/vendor/github.com/mitchellh/go-homedir/homedir.go @@ -77,33 +77,51 @@ func Expand(path string) (string, error) { } func dirUnix() (string, error) { + homeEnv := "HOME" + if runtime.GOOS == "plan9" { + // On plan9, env vars are lowercase. + homeEnv = "home" + } + // First prefer the HOME environmental variable - if home := os.Getenv("HOME"); home != "" { + if home := os.Getenv(homeEnv); home != "" { return home, nil } - // If that fails, try getent var stdout bytes.Buffer - cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) - cmd.Stdout = &stdout - if err := cmd.Run(); err != nil { - // If the error is ErrNotFound, we ignore it. Otherwise, return it. - if err != exec.ErrNotFound { - return "", err + + // If that fails, try OS specific commands + if runtime.GOOS == "darwin" { + cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`) + cmd.Stdout = &stdout + if err := cmd.Run(); err == nil { + result := strings.TrimSpace(stdout.String()) + if result != "" { + return result, nil + } } } else { - if passwd := strings.TrimSpace(stdout.String()); passwd != "" { - // username:password:uid:gid:gecos:home:shell - passwdParts := strings.SplitN(passwd, ":", 7) - if len(passwdParts) > 5 { - return passwdParts[5], nil + cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + // If the error is ErrNotFound, we ignore it. Otherwise, return it. + if err != exec.ErrNotFound { + return "", err + } + } else { + if passwd := strings.TrimSpace(stdout.String()); passwd != "" { + // username:password:uid:gid:gecos:home:shell + passwdParts := strings.SplitN(passwd, ":", 7) + if len(passwdParts) > 5 { + return passwdParts[5], nil + } } } } // If all else fails, try the shell stdout.Reset() - cmd = exec.Command("sh", "-c", "cd && pwd") + cmd := exec.Command("sh", "-c", "cd && pwd") cmd.Stdout = &stdout if err := cmd.Run(); err != nil { return "", err @@ -123,14 +141,16 @@ func dirWindows() (string, error) { return home, nil } + // Prefer standard environment variable USERPROFILE + if home := os.Getenv("USERPROFILE"); home != "" { + return home, nil + } + drive := os.Getenv("HOMEDRIVE") path := os.Getenv("HOMEPATH") home := drive + path if drive == "" || path == "" { - home = os.Getenv("USERPROFILE") - } - if home == "" { - return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") + return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank") } return home, nil diff --git a/vendor/modules.txt b/vendor/modules.txt index ca0de07e6..81c9677ab 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -208,7 +208,7 @@ github.com/coreos/etcd/pkg/tlsutil github.com/coreos/go-semver/semver # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew -# github.com/dgrijalva/jwt-go v0.0.0-20160617170158-f0777076321a +# github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go # github.com/dimchansky/utfbom v1.0.0 github.com/dimchansky/utfbom @@ -302,7 +302,7 @@ github.com/hashicorp/consul/lib/freeport github.com/hashicorp/consul/testutil/retry # github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/errwrap -# github.com/hashicorp/go-azure-helpers v0.0.0-20181122151743-c51a3103be3b +# github.com/hashicorp/go-azure-helpers v0.0.0-20181126135526-ec113df69f49 github.com/hashicorp/go-azure-helpers/authentication github.com/hashicorp/go-azure-helpers/storage # github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de @@ -417,7 +417,7 @@ github.com/mitchellh/cli github.com/mitchellh/colorstring # github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3 github.com/mitchellh/copystructure -# github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 +# github.com/mitchellh/go-homedir v1.0.0 github.com/mitchellh/go-homedir # github.com/mitchellh/go-linereader v0.0.0-20141013185533-07bab5fdd958 github.com/mitchellh/go-linereader diff --git a/website/docs/backends/types/azurerm.html.md b/website/docs/backends/types/azurerm.html.md index e852d29fa..11a78e1c9 100644 --- a/website/docs/backends/types/azurerm.html.md +++ b/website/docs/backends/types/azurerm.html.md @@ -15,7 +15,7 @@ Stores the state as a Blob with the given Key within the Blob Container within [ ## Example Configuration -When authenticating using a Service Principal: +When authenticating using the Azure CLI or a Service Principal: ```hcl terraform {