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
This commit is contained in:
Tom Harvey 2018-12-10 21:23:30 +00:00 committed by GitHub
parent a475b766de
commit 383bc98f5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 392 additions and 127 deletions

View File

@ -65,9 +65,10 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) {
MsiEndpoint: config.MsiEndpoint, MsiEndpoint: config.MsiEndpoint,
// Feature Toggles // Feature Toggles
SupportsAzureCliToken: true,
SupportsClientSecretAuth: true, SupportsClientSecretAuth: true,
SupportsManagedServiceIdentity: config.UseMsi, SupportsManagedServiceIdentity: config.UseMsi,
// TODO: support for Azure CLI / Client Certificate auth // TODO: support for Client Certificate auth
} }
armConfig, err := builder.Build() armConfig, err := builder.Build()
if err != nil { if err != nil {

6
go.mod
View File

@ -32,8 +32,6 @@ require (
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d // indirect github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/davecgh/go-spew v1.1.1 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/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31 // indirect
github.com/dylanmei/iso8601 v0.1.0 // indirect github.com/dylanmei/iso8601 v0.1.0 // indirect
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 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/grpc-ecosystem/grpc-gateway v1.5.1 // indirect
github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089 github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089
github.com/hashicorp/errwrap v1.0.0 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-checkpoint v0.0.0-20171009173528-1545e56e46de
github.com/hashicorp/go-cleanhttp v0.5.0 github.com/hashicorp/go-cleanhttp v0.5.0
github.com/hashicorp/go-getter v0.0.0-20180327010114-90bb99a48d86 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/cli v0.0.0-20171129193617-33edc47170b5
github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286 github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286
github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3 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-linereader v0.0.0-20141013185533-07bab5fdd958
github.com/mitchellh/go-wordwrap v1.0.0 github.com/mitchellh/go-wordwrap v1.0.0
github.com/mitchellh/hashstructure v0.0.0-20160209213820-6b17d669fac5 github.com/mitchellh/hashstructure v0.0.0-20160209213820-6b17d669fac5

6
go.sum
View File

@ -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/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 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 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 h1:fGC2kkf4qOoKqZ4q7iIh+Vef4ubC1c38UDsEyZynZPc=
github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= 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= 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/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 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-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 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-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= 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/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 h1:eQox4Rh4ewJF+mqYPxCkmBAirRnPaHEB26UkNuPyjlk=
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 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 h1:wN+5lV34eSnVSZgBLWRGHr6L4giR3/wI2B9DLmVnlfI=
github.com/mitchellh/go-linereader v0.0.0-20141013185533-07bab5fdd958/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM= 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= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=

View File

@ -1,8 +1,13 @@
language: go language: go
script:
- go vet ./...
- go test -v ./...
go: go:
- 1.3 - 1.3
- 1.4 - 1.4
- 1.5 - 1.5
- 1.6 - 1.6
- 1.7
- tip - tip

View File

@ -56,8 +56,9 @@ This simple parsing example:
is directly mapped to: is directly mapped to:
```go ```go
if token, err := request.ParseFromRequest(tokenString, request.OAuth2Extractor, req, keyLookupFunc); err == nil { if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil {
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"]) claims := token.Claims.(jwt.MapClaims)
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
} }
``` ```

View File

@ -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) [![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? ## What the heck is a JWT?
@ -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). 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 ## Usage Tips
@ -68,6 +75,14 @@ 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. 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 ### 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. 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.
@ -82,4 +97,4 @@ Without going too far down the rabbit hole, here's a description of the interact
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). 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.

View File

@ -1,5 +1,18 @@
## `jwt-go` Version History ## `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 #### 3.0.0
* **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code * **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code

View File

@ -14,6 +14,7 @@ var (
) )
// Implements the ECDSA family of signing methods signing methods // Implements the ECDSA family of signing methods signing methods
// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification
type SigningMethodECDSA struct { type SigningMethodECDSA struct {
Name string Name string
Hash crypto.Hash Hash crypto.Hash

View File

@ -51,13 +51,9 @@ func (e ValidationError) Error() string {
} else { } else {
return "token is invalid" return "token is invalid"
} }
return e.Inner.Error()
} }
// No errors // No errors
func (e *ValidationError) valid() bool { func (e *ValidationError) valid() bool {
if e.Errors > 0 { return e.Errors == 0
return false
}
return true
} }

View File

@ -7,6 +7,7 @@ import (
) )
// Implements the HMAC-SHA family of signing methods signing methods // Implements the HMAC-SHA family of signing methods signing methods
// Expects key type of []byte for both signing and validation
type SigningMethodHMAC struct { type SigningMethodHMAC struct {
Name string Name string
Hash crypto.Hash Hash crypto.Hash
@ -90,5 +91,5 @@ func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string,
return EncodeSegment(hasher.Sum(nil)), nil return EncodeSegment(hasher.Sum(nil)), nil
} }
return "", ErrInvalidKey return "", ErrInvalidKeyType
} }

View File

@ -10,6 +10,7 @@ import (
type Parser struct { type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder UseJSONNumber bool // Use JSON Number format in JSON decoder
SkipClaimsValidation bool // Skip claims validation during token parsing
} }
// Parse, validate, and return a token. // 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) { func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
parts := strings.Split(tokenString, ".") token, parts, err := p.ParseUnverified(tokenString, claims)
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
if err != nil { if err != nil {
return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} return token, err
}
// 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)
} }
// Verify signing method is in the required set // Verify signing method is in the required set
@ -95,12 +50,16 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
} }
if key, err = keyFunc(token); err != nil { if key, err = keyFunc(token); err != nil {
// keyFunc returned an error // keyFunc returned an error
if ve, ok := err.(*ValidationError); ok {
return token, ve
}
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
} }
vErr := &ValidationError{} vErr := &ValidationError{}
// Validate Claims // Validate Claims
if !p.SkipClaimsValidation {
if err := token.Claims.Valid(); err != nil { if err := token.Claims.Valid(); err != nil {
// If the Claims Valid returned an error, check if it is a validation error, // If the Claims Valid returned an error, check if it is a validation error,
@ -111,6 +70,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
vErr = e vErr = e
} }
} }
}
// Perform validation // Perform validation
token.Signature = parts[2] token.Signature = parts[2]
@ -126,3 +86,63 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
return token, vErr 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
}

View File

@ -7,6 +7,7 @@ import (
) )
// Implements the RSA family of signing methods signing methods // Implements the RSA family of signing methods signing methods
// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation
type SigningMethodRSA struct { type SigningMethodRSA struct {
Name string Name string
Hash crypto.Hash Hash crypto.Hash
@ -44,7 +45,7 @@ func (m *SigningMethodRSA) Alg() string {
} }
// Implements the Verify method from SigningMethod // 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 { func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
var err error var err error
@ -73,7 +74,7 @@ func (m *SigningMethodRSA) Verify(signingString, signature string, key interface
} }
// Implements the Sign method from SigningMethod // 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) { func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
var rsaKey *rsa.PrivateKey var rsaKey *rsa.PrivateKey
var ok bool var ok bool

View File

@ -39,6 +39,38 @@ func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
return pkey, nil 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 // Parse PEM encoded PKCS1 or PKCS8 public key
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
var err error var err error

View File

@ -48,25 +48,16 @@ func (a azureCliParsingAuth) build(b Builder) (authMethod, error) {
} }
func (a azureCliParsingAuth) isApplicable(b Builder) bool { 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) { 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) spt, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, a.profile.clientId, endpoint, *a.profile.accessToken)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = spt.Refresh() err = spt.Refresh()
if err != nil { if err != nil {
return nil, fmt.Errorf("Error refreshing Service Principal Token: %+v", err) return nil, fmt.Errorf("Error refreshing Service Principal Token: %+v", err)
} }

View File

@ -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
}

View File

@ -13,10 +13,9 @@ import (
type azureCliAccessToken struct { type azureCliAccessToken struct {
ClientID string ClientID string
AccessToken *adal.Token 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 { for _, accessToken := range tokens {
token, err := accessToken.ToADALToken() token, err := accessToken.ToADALToken()
if err != nil { 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) 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) log.Printf("[DEBUG] Token %q has expired", token.AccessToken)
continue continue
} }
@ -46,7 +45,6 @@ func findValidAccessTokenForTenant(tokens []cli.Token, tenantId string) (*azureC
validAccessToken := azureCliAccessToken{ validAccessToken := azureCliAccessToken{
ClientID: accessToken.ClientID, ClientID: accessToken.ClientID,
AccessToken: &token, AccessToken: &token,
IsCloudShell: accessToken.RefreshToken == "",
} }
return &validAccessToken, nil return &validAccessToken, nil
} }

View File

@ -13,7 +13,6 @@ type azureCLIProfile struct {
subscriptionId string subscriptionId string
tenantId string tenantId string
accessToken *adal.Token accessToken *adal.Token
usingCloudShell bool
} }
func (a *azureCLIProfile) populateFields() error { 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 // always pull the environment from the Azure CLI, since the Access Token's associated with it
return a.populateEnvironment() return a.populateEnvironment()
} }

View File

@ -27,6 +27,29 @@ func (a *azureCLIProfile) populateTenantID() error {
return nil 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 { func (a *azureCLIProfile) populateClientIdAndAccessToken() error {
// we can now pull out the ClientID and the Access Token to use from the Access Token // we can now pull out the ClientID and the Access Token to use from the Access Token
tokensPath, err := cli.AccessTokensPath() 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`.") 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 { if err != nil {
return fmt.Errorf("No (unexpired) Authorization Tokens were found - please re-authenticate using `az login`.") 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 token := *validToken
a.accessToken = token.AccessToken a.accessToken = token.AccessToken
a.clientId = token.ClientID a.clientId = token.ClientID
a.usingCloudShell = token.IsCloudShell
return nil return nil
} }

View File

@ -18,8 +18,11 @@ type Builder struct {
// only applicable for Azure Stack at this time. // only applicable for Azure Stack at this time.
CustomResourceManagerEndpoint string CustomResourceManagerEndpoint string
// Azure CLI Parsing / CloudShell Auth // Azure CLI Parsing
SupportsAzureCliCloudShellParsing bool SupportsAzureCliParsing bool
// Azure CLI Tokens Auth
SupportsAzureCliToken bool
// Managed Service Identity Auth // Managed Service Identity Auth
SupportsManagedServiceIdentity bool SupportsManagedServiceIdentity bool
@ -52,6 +55,7 @@ func (b Builder) Build() (*Config, error) {
servicePrincipalClientCertificateAuth{}, servicePrincipalClientCertificateAuth{},
servicePrincipalClientSecretAuth{}, servicePrincipalClientSecretAuth{},
managedServiceIdentityAuth{}, managedServiceIdentityAuth{},
azureCliTokenAuth{},
azureCliParsingAuth{}, azureCliParsingAuth{},
} }

1
vendor/github.com/mitchellh/go-homedir/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module github.com/mitchellh/go-homedir

View File

@ -77,13 +77,30 @@ func Expand(path string) (string, error) {
} }
func dirUnix() (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 // First prefer the HOME environmental variable
if home := os.Getenv("HOME"); home != "" { if home := os.Getenv(homeEnv); home != "" {
return home, nil return home, nil
} }
// If that fails, try getent
var stdout bytes.Buffer var stdout bytes.Buffer
// 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 {
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
cmd.Stdout = &stdout cmd.Stdout = &stdout
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
@ -100,10 +117,11 @@ func dirUnix() (string, error) {
} }
} }
} }
}
// If all else fails, try the shell // If all else fails, try the shell
stdout.Reset() stdout.Reset()
cmd = exec.Command("sh", "-c", "cd && pwd") cmd := exec.Command("sh", "-c", "cd && pwd")
cmd.Stdout = &stdout cmd.Stdout = &stdout
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return "", err return "", err
@ -123,14 +141,16 @@ func dirWindows() (string, error) {
return home, nil return home, nil
} }
// Prefer standard environment variable USERPROFILE
if home := os.Getenv("USERPROFILE"); home != "" {
return home, nil
}
drive := os.Getenv("HOMEDRIVE") drive := os.Getenv("HOMEDRIVE")
path := os.Getenv("HOMEPATH") path := os.Getenv("HOMEPATH")
home := drive + path home := drive + path
if drive == "" || path == "" { if drive == "" || path == "" {
home = os.Getenv("USERPROFILE") return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank")
}
if home == "" {
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
} }
return home, nil return home, nil

6
vendor/modules.txt vendored
View File

@ -208,7 +208,7 @@ github.com/coreos/etcd/pkg/tlsutil
github.com/coreos/go-semver/semver github.com/coreos/go-semver/semver
# github.com/davecgh/go-spew v1.1.1 # github.com/davecgh/go-spew v1.1.1
github.com/davecgh/go-spew/spew 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/dgrijalva/jwt-go
# github.com/dimchansky/utfbom v1.0.0 # github.com/dimchansky/utfbom v1.0.0
github.com/dimchansky/utfbom github.com/dimchansky/utfbom
@ -302,7 +302,7 @@ github.com/hashicorp/consul/lib/freeport
github.com/hashicorp/consul/testutil/retry github.com/hashicorp/consul/testutil/retry
# github.com/hashicorp/errwrap v1.0.0 # github.com/hashicorp/errwrap v1.0.0
github.com/hashicorp/errwrap 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/authentication
github.com/hashicorp/go-azure-helpers/storage github.com/hashicorp/go-azure-helpers/storage
# github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de # 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/colorstring
# github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3 # github.com/mitchellh/copystructure v0.0.0-20170525013902-d23ffcb85de3
github.com/mitchellh/copystructure 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-homedir
# github.com/mitchellh/go-linereader v0.0.0-20141013185533-07bab5fdd958 # github.com/mitchellh/go-linereader v0.0.0-20141013185533-07bab5fdd958
github.com/mitchellh/go-linereader github.com/mitchellh/go-linereader

View File

@ -15,7 +15,7 @@ Stores the state as a Blob with the given Key within the Blob Container within [
## Example Configuration ## Example Configuration
When authenticating using a Service Principal: When authenticating using the Azure CLI or a Service Principal:
```hcl ```hcl
terraform { terraform {