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,
// 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 {

6
go.mod
View File

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

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/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=

View File

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

View File

@ -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"])
}
```

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)
[![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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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,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

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/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

View File

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