Skip to content

Commit

Permalink
Merge branch 'master' into hyperone
Browse files Browse the repository at this point in the history
  • Loading branch information
m110 committed Jan 22, 2019
2 parents 7947e52 + 4fef738 commit 9cbeae2
Show file tree
Hide file tree
Showing 28 changed files with 992 additions and 526 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* builder/amazon: don't Cleanup Temp Keys when there is no communicator to
avoid a panic [GH-7100] [GH-7095]
* builder/azure: allow to configure disk caching [GH-7061]
* builder/azure: add certificate authentication [GH-7189]
* builder/azure: use deallocate instead of just power-off [GH-7203]
* builder/openstack: Don't require network v2 [GH-6933]
* builder/openstack: Support for tagging new images [GH-7037]
* core/shell: Add env vars "PACKER_HTTP_IP" and "PACKER_HTTP_PORT" to shell
Expand Down
41 changes: 3 additions & 38 deletions builder/azure/arm/authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,9 @@ package arm

import (
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)

type Authenticate struct {
env azure.Environment
clientID string
clientSecret string
tenantID string
}

func NewAuthenticate(env azure.Environment, clientID, clientSecret, tenantID string) *Authenticate {
return &Authenticate{
env: env,
clientID: clientID,
clientSecret: clientSecret,
tenantID: tenantID,
}
}

func (a *Authenticate) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return a.getServicePrincipalTokenWithResource(a.env.ResourceManagerEndpoint)
}

func (a *Authenticate) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(a.env.ActiveDirectoryEndpoint, a.tenantID)
if err != nil {
return nil, err
}

if a.clientID == "" && a.clientSecret == "" {
return adal.NewServicePrincipalTokenFromMSI("http://169.254.169.254/metadata/identity/oauth2/token", resource)
}

spt, err := adal.NewServicePrincipalToken(
*oauthConfig,
a.clientID,
a.clientSecret,
resource)

return spt, err
type oAuthTokenProvider interface {
getServicePrincipalToken() (*adal.ServicePrincipalToken, error)
getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error)
}
155 changes: 155 additions & 0 deletions builder/azure/arm/authenticate_cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package arm

import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"time"

"github.com/Azure/go-autorest/autorest/azure"
jwt "github.com/dgrijalva/jwt-go"
)

func NewCertOAuthTokenProvider(env azure.Environment, clientID, clientCertPath, tenantID string) (oAuthTokenProvider, error) {
cert, key, err := readCert(clientCertPath)
if err != nil {
return nil, fmt.Errorf("Error reading certificate: %v", err)
}

audience := fmt.Sprintf("%s%s/oauth2/token", env.ActiveDirectoryEndpoint, tenantID)
jwt, err := makeJWT(clientID, audience, cert, key, time.Hour, true)
if err != nil {
return nil, fmt.Errorf("Error generating JWT: %v", err)
}

return NewJWTOAuthTokenProvider(env, clientID, jwt, tenantID), nil
}

// Creates a new JSON Web Token to be used as bearer JWT to authenticate
// to the Azure AD token endpoint to retrieve an access token for `audience`.
// If the full certificate is included in the token, then issuer/subject name
// could be used to authenticate if configured by the identity provider (AAD).
func makeJWT(clientID string, audience string,
cert *x509.Certificate, privatekey interface{},
validFor time.Duration, includeFullCertificate bool) (string, error) {

// The jti (JWT ID) claim provides a unique identifier for the JWT.
// See https://tools.ietf.org/html/rfc7519#section-4.1.7
jti := make([]byte, 20)
_, err := rand.Read(jti)
if err != nil {
return "", err
}

var token *jwt.Token
if cert.PublicKeyAlgorithm == x509.RSA {
token = jwt.New(jwt.SigningMethodRS256)
} else if cert.PublicKeyAlgorithm == x509.ECDSA {
token = jwt.New(jwt.SigningMethodES256)
} else {
return "", fmt.Errorf("Don't know how to handle this type of key algorithm: %v", cert.PublicKeyAlgorithm)
}

hasher := sha1.New()
if _, err := hasher.Write(cert.Raw); err != nil {
return "", err
}
thumbprint := base64.URLEncoding.EncodeToString(hasher.Sum(nil))

// X.509 thumbprint, see https://tools.ietf.org/html/rfc7515#section-4.1.7
token.Header["x5t"] = thumbprint
if includeFullCertificate {
// X.509 certificate (chain), see https://tools.ietf.org/html/rfc7515#section-4.1.6
token.Header["x5c"] = []string{base64.StdEncoding.EncodeToString(cert.Raw)}
}

token.Claims = jwt.MapClaims{
// See https://tools.ietf.org/html/rfc7519#section-4.1
"aud": audience,
"iss": clientID,
"sub": clientID,
"jti": base64.URLEncoding.EncodeToString(jti),
"nbf": time.Now().Unix(),
"exp": time.Now().Add(validFor).Unix(),
}

return token.SignedString(privatekey)
}

func readCert(file string) (cert *x509.Certificate, key interface{}, err error) {
f, err := os.Open(file)
if err != nil {
return nil, nil, err
}
defer f.Close()
d, err := ioutil.ReadAll(f)
if err != nil {
return nil, nil, err
}

blocks := []*pem.Block{}
for len(d) > 0 {
var b *pem.Block
b, d = pem.Decode(d)
if b == nil {
break
}
blocks = append(blocks, b)
}

certs := []*x509.Certificate{}
for _, block := range blocks {
if block.Type == "CERTIFICATE" {
c, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, nil, fmt.Errorf(
"Failed to read certificate block: %v", err)
}
certs = append(certs, c)
} else if block.Type == "PRIVATE KEY" {
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, nil, fmt.Errorf(
"Failed to read private key block: %v", err)
}
}
// Don't care about other types of blocks, ignore
}

if key == nil {
return nil, nil, fmt.Errorf("Did not find private key in pem file")
}

// find the certificate that belongs to the private key by comparing the public keys
switch key := key.(type) {
case *rsa.PrivateKey:
for _, c := range certs {
if cp, ok := c.PublicKey.(*rsa.PublicKey); ok &&
(cp.N.Cmp(key.PublicKey.N) == 0) {
cert = c
}
}

case *ecdsa.PrivateKey:
for _, c := range certs {
if cp, ok := c.PublicKey.(*ecdsa.PublicKey); ok &&
(cp.X.Cmp(key.PublicKey.X) == 0) &&
(cp.Y.Cmp(key.PublicKey.Y) == 0) {
cert = c
}
}
}

if cert == nil {
return nil, nil, fmt.Errorf("Did not find certificate belonging to private key in pem file")
}

return cert, key, nil
}
36 changes: 36 additions & 0 deletions builder/azure/arm/authenticate_devicewflow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package arm

import (
"fmt"
"strings"

"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
packerAzureCommon "github.com/hashicorp/packer/builder/azure/common"
)

func NewDeviceFlowOAuthTokenProvider(env azure.Environment, say func(string), tenantID string) oAuthTokenProvider {
return &deviceflowOauthTokenProvider{}
}

type deviceflowOauthTokenProvider struct {
env azure.Environment
say func(string)
tenantID string
}

func (tp *deviceflowOauthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}

func (tp *deviceflowOauthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
if resource == tp.env.ServiceManagementEndpoint {
tp.say("Getting auth token for Service management endpoint")
} else if resource == strings.TrimRight(tp.env.KeyVaultEndpoint, "/") {
tp.say("Getting token for Vault resource")
} else {
tp.say(fmt.Sprintf("Getting token for %s", resource))
}

return packerAzureCommon.Authenticate(tp.env, tp.tenantID, tp.say, resource)
}
43 changes: 43 additions & 0 deletions builder/azure/arm/authenticate_jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package arm

import (
"net/url"

"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)

// for clientID/bearer JWT auth
type jwtOAuthTokenProvider struct {
env azure.Environment
clientID, clientJWT, tenantID string
}

func NewJWTOAuthTokenProvider(env azure.Environment, clientID, clientJWT, tenantID string) oAuthTokenProvider {
return &jwtOAuthTokenProvider{env, clientID, clientJWT, tenantID}
}

func (tp *jwtOAuthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}

func (tp *jwtOAuthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(tp.env.ActiveDirectoryEndpoint, tp.tenantID)
if err != nil {
return nil, err
}

return adal.NewServicePrincipalTokenWithSecret(
*oauthConfig,
tp.clientID,
resource,
tp)
}

// implements github.com/Azure/go-autorest/autorest/adal.ServicePrincipalSecret
func (tp *jwtOAuthTokenProvider) SetAuthenticationValues(
t *adal.ServicePrincipalToken, v *url.Values) error {
v.Set("client_assertion", tp.clientJWT)
v.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
return nil
}
23 changes: 23 additions & 0 deletions builder/azure/arm/authenticate_msi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package arm

import (
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)

// for managed identity auth
type msiOAuthTokenProvider struct {
env azure.Environment
}

func NewMSIOAuthTokenProvider(env azure.Environment) oAuthTokenProvider {
return &msiOAuthTokenProvider{env}
}

func (tp *msiOAuthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}

func (tp *msiOAuthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
return adal.NewServicePrincipalTokenFromMSI("http://169.254.169.254/metadata/identity/oauth2/token", resource)
}
35 changes: 35 additions & 0 deletions builder/azure/arm/authenticate_secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package arm

import (
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
)

// for clientID/secret auth
type secretOAuthTokenProvider struct {
env azure.Environment
clientID, clientSecret, tenantID string
}

func NewSecretOAuthTokenProvider(env azure.Environment, clientID, clientSecret, tenantID string) oAuthTokenProvider {
return &secretOAuthTokenProvider{env, clientID, clientSecret, tenantID}
}

func (tp *secretOAuthTokenProvider) getServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
return tp.getServicePrincipalTokenWithResource(tp.env.ResourceManagerEndpoint)
}

func (tp *secretOAuthTokenProvider) getServicePrincipalTokenWithResource(resource string) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(tp.env.ActiveDirectoryEndpoint, tp.tenantID)
if err != nil {
return nil, err
}

spt, err := adal.NewServicePrincipalToken(
*oauthConfig,
tp.clientID,
tp.clientSecret,
resource)

return spt, err
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
// Behavior is the most important thing to assert for ServicePrincipalToken, but
// that cannot be done in a unit test because it involves network access. Instead,
// I assert the expected inertness of this class.
func TestNewAuthenticate(t *testing.T) {
testSubject := NewAuthenticate(azure.PublicCloud, "clientID", "clientString", "tenantID")
func TestNewSecretOAuthTokenProvider(t *testing.T) {
testSubject := NewSecretOAuthTokenProvider(azure.PublicCloud, "clientID", "clientString", "tenantID")
spn, err := testSubject.getServicePrincipalToken()
if err != nil {
t.Fatalf(err.Error())
Expand Down
Loading

0 comments on commit 9cbeae2

Please sign in to comment.