Allow certificate bearer JWT client authentication
This allows certificate based authentication, both by just referencing the certificate file as well as by providing a bearer JWT. This last option allows authentication without exposing the private key to packer using an expiring JWT containting the thumbprint (and sometimes the whole certificate for subject/issuer based auth), signed using the certificate private key.
This commit is contained in:
parent
7e34579b7e
commit
aa29facdae
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -2,10 +2,13 @@ package arm
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
|
@ -21,7 +24,11 @@ type ClientConfig struct {
|
|||
// Client ID
|
||||
ClientID string `mapstructure:"client_id"`
|
||||
// Client secret/password
|
||||
ClientSecret string `mapstructure:"client_secret"`
|
||||
ClientSecret string `mapstructure:"client_secret"`
|
||||
// Certificate path for client auth
|
||||
ClientCertPath string `mapstructure:"client_cert_path"`
|
||||
// JWT bearer token for client auth (RFC 7523, Sec. 2.2)
|
||||
ClientJWT string `mapstructure:"client_jwt"`
|
||||
ObjectID string `mapstructure:"object_id"`
|
||||
TenantID string `mapstructure:"tenant_id"`
|
||||
SubscriptionID string `mapstructure:"subscription_id"`
|
||||
|
@ -86,29 +93,69 @@ func (c ClientConfig) assertRequiredParametersSet(errs *packer.MultiError) {
|
|||
return
|
||||
}
|
||||
|
||||
if c.SubscriptionID == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("A subscription_id must be specified"))
|
||||
}
|
||||
|
||||
if c.useDeviceLogin() {
|
||||
return
|
||||
}
|
||||
|
||||
if c.SubscriptionID != "" && c.ClientID != "" && c.ClientSecret != "" {
|
||||
if c.SubscriptionID != "" && c.ClientID != "" &&
|
||||
c.ClientSecret != "" &&
|
||||
c.ClientCertPath == "" &&
|
||||
c.ClientJWT == "" {
|
||||
// Service principal using secret
|
||||
return
|
||||
}
|
||||
|
||||
if c.SubscriptionID != "" && c.ClientID != "" &&
|
||||
c.ClientSecret == "" &&
|
||||
c.ClientCertPath != "" &&
|
||||
c.ClientJWT == "" {
|
||||
// Service principal using certificate
|
||||
|
||||
if _, err := os.Stat(c.ClientCertPath); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("client_cert_path is not an accessible file: %v", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if c.SubscriptionID != "" && c.ClientID != "" &&
|
||||
c.ClientSecret == "" &&
|
||||
c.ClientCertPath == "" &&
|
||||
c.ClientJWT != "" {
|
||||
// Service principal using JWT
|
||||
// Check that JWT is valid for at least 5 more minutes
|
||||
|
||||
p := jwt.Parser{}
|
||||
claims := jwt.StandardClaims{}
|
||||
token, _, err := p.ParseUnverified(c.ClientJWT, &claims)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("client_jwt is not a JWT: %v", err))
|
||||
} else {
|
||||
if claims.ExpiresAt < time.Now().Add(5*time.Minute).Unix() {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("client_jwt will expire within 5 minutes, please use a JWT that is valid for at least 5 minutes"))
|
||||
}
|
||||
if t, ok := token.Header["x5t"]; !ok || t == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("client_jwt is missing the x5t header value, which is required for bearer JWT client authentication to Azure"))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("No valid set of authentication values specified:\n"+
|
||||
"* to use the Managed Identity of teh current machine, do not specify any of the fields below\n"+
|
||||
"* to use interactive user authentication, specify only subscription_id\n"+
|
||||
"* to use an Azure Active Directory service principal, specify subscription_id, client_id and client_secret."))
|
||||
" to use the Managed Identity of the current machine, do not specify any of the fields below\n"+
|
||||
" to use interactive user authentication, specify only subscription_id\n"+
|
||||
" to use an Azure Active Directory service principal, specify either:\n"+
|
||||
" - subscription_id, client_id and client_secret\n"+
|
||||
" - subscription_id, client_id and client_cert_path\n"+
|
||||
" - subscription_id, client_id and client_jwt."))
|
||||
}
|
||||
|
||||
func (c ClientConfig) useDeviceLogin() bool {
|
||||
return c.SubscriptionID != "" &&
|
||||
c.ClientID == "" &&
|
||||
c.ClientSecret == "" &&
|
||||
c.ClientJWT == "" &&
|
||||
c.ClientCertPath == "" &&
|
||||
c.TenantID == ""
|
||||
}
|
||||
|
||||
|
@ -116,6 +163,8 @@ func (c ClientConfig) useMSI() bool {
|
|||
return c.SubscriptionID == "" &&
|
||||
c.ClientID == "" &&
|
||||
c.ClientSecret == "" &&
|
||||
c.ClientJWT == "" &&
|
||||
c.ClientCertPath == "" &&
|
||||
c.TenantID == ""
|
||||
}
|
||||
|
||||
|
@ -135,9 +184,18 @@ func (c ClientConfig) getServicePrincipalTokens(
|
|||
} else if c.useMSI() {
|
||||
say("Getting tokens using Managed Identity for Azure")
|
||||
auth = NewMSIOAuthTokenProvider(*c.cloudEnvironment)
|
||||
} else {
|
||||
} else if c.ClientSecret != "" {
|
||||
say("Getting tokens using client secret")
|
||||
auth = NewSecretOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientSecret, tenantID)
|
||||
} else if c.ClientCertPath != "" {
|
||||
say("Getting tokens using client certificate")
|
||||
auth, err = NewCertOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientCertPath, tenantID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
say("Getting tokens using client bearer JWT")
|
||||
auth = NewJWTOAuthTokenProvider(*c.cloudEnvironment, c.ClientID, c.ClientJWT, tenantID)
|
||||
}
|
||||
|
||||
servicePrincipalToken, err = auth.getServicePrincipalToken()
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
package arm
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
|
@ -29,7 +37,7 @@ func Test_ClientConfig_RequiredParametersSet(t *testing.T) {
|
|||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "client_id without client_secret should error",
|
||||
name: "client_id without client_secret, client_cert_path or client_jwt should error",
|
||||
config: ClientConfig{
|
||||
ClientID: "error",
|
||||
},
|
||||
|
@ -42,6 +50,20 @@ func Test_ClientConfig_RequiredParametersSet(t *testing.T) {
|
|||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "client_cert_path without client_id should error",
|
||||
config: ClientConfig{
|
||||
ClientCertPath: "/dev/null",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "client_jwt without client_id should error",
|
||||
config: ClientConfig{
|
||||
ClientJWT: "error",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing subscription_id when using secret",
|
||||
config: ClientConfig{
|
||||
|
@ -50,6 +72,42 @@ func Test_ClientConfig_RequiredParametersSet(t *testing.T) {
|
|||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing subscription_id when using certificate",
|
||||
config: ClientConfig{
|
||||
ClientID: "ok",
|
||||
ClientCertPath: "ok",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing subscription_id when using JWT",
|
||||
config: ClientConfig{
|
||||
ClientID: "ok",
|
||||
ClientJWT: "ok",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "too many client_* values",
|
||||
config: ClientConfig{
|
||||
SubscriptionID: "ok",
|
||||
ClientID: "ok",
|
||||
ClientSecret: "ok",
|
||||
ClientCertPath: "error",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "too many client_* values (2)",
|
||||
config: ClientConfig{
|
||||
SubscriptionID: "ok",
|
||||
ClientID: "ok",
|
||||
ClientSecret: "ok",
|
||||
ClientJWT: "error",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "tenant_id alone should fail",
|
||||
config: ClientConfig{
|
||||
|
@ -130,6 +188,66 @@ func Test_ClientConfig_ClientPassword(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_ClientConfig_ClientCert(t *testing.T) {
|
||||
cfg := ClientConfig{
|
||||
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
|
||||
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
|
||||
ClientCertPath: getEnvOrSkip(t, "AZURE_CLIENTCERT"),
|
||||
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
|
||||
cloudEnvironment: getCloud(),
|
||||
}
|
||||
assertValid(t, cfg)
|
||||
|
||||
spt, sptkv, err := cfg.getServicePrincipalTokens(func(s string) { fmt.Printf("SAY: %s\n", s) })
|
||||
if err != nil {
|
||||
t.Fatalf("Expected nil err, but got: %v", err)
|
||||
}
|
||||
token := spt.Token()
|
||||
if token.AccessToken == "" {
|
||||
t.Fatal("Expected management token to have non-nil access token")
|
||||
}
|
||||
if token.RefreshToken != "" {
|
||||
t.Fatal("Expected management token to have no refresh token")
|
||||
}
|
||||
kvtoken := sptkv.Token()
|
||||
if kvtoken.AccessToken == "" {
|
||||
t.Fatal("Expected keyvault token to have non-nil access token")
|
||||
}
|
||||
if kvtoken.RefreshToken != "" {
|
||||
t.Fatal("Expected keyvault token to have no refresh token")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ClientConfig_ClientJWT(t *testing.T) {
|
||||
cfg := ClientConfig{
|
||||
SubscriptionID: getEnvOrSkip(t, "AZURE_SUBSCRIPTION"),
|
||||
ClientID: getEnvOrSkip(t, "AZURE_CLIENTID"),
|
||||
ClientJWT: getEnvOrSkip(t, "AZURE_CLIENTJWT"),
|
||||
TenantID: getEnvOrSkip(t, "AZURE_TENANTID"),
|
||||
cloudEnvironment: getCloud(),
|
||||
}
|
||||
assertValid(t, cfg)
|
||||
|
||||
spt, sptkv, err := cfg.getServicePrincipalTokens(func(s string) { fmt.Printf("SAY: %s\n", s) })
|
||||
if err != nil {
|
||||
t.Fatalf("Expected nil err, but got: %v", err)
|
||||
}
|
||||
token := spt.Token()
|
||||
if token.AccessToken == "" {
|
||||
t.Fatal("Expected management token to have non-nil access token")
|
||||
}
|
||||
if token.RefreshToken != "" {
|
||||
t.Fatal("Expected management token to have no refresh token")
|
||||
}
|
||||
kvtoken := sptkv.Token()
|
||||
if kvtoken.AccessToken == "" {
|
||||
t.Fatal("Expected keyvault token to have non-nil access token")
|
||||
}
|
||||
if kvtoken.RefreshToken != "" {
|
||||
t.Fatal("Expected keyvault token to have no refresh token")
|
||||
}
|
||||
}
|
||||
|
||||
func getEnvOrSkip(t *testing.T, envVar string) string {
|
||||
v := os.Getenv(envVar)
|
||||
if v == "" {
|
||||
|
@ -192,8 +310,88 @@ func Test_ClientConfig_CanUseClientSecretWithTenantID(t *testing.T) {
|
|||
assertValid(t, cfg)
|
||||
}
|
||||
|
||||
func Test_ClientConfig_CanUseClientJWT(t *testing.T) {
|
||||
cfg := emptyClientConfig()
|
||||
cfg.SubscriptionID = "12345"
|
||||
cfg.ClientID = "12345"
|
||||
cfg.ClientJWT = getJWT(10*time.Minute, true)
|
||||
|
||||
assertValid(t, cfg)
|
||||
}
|
||||
|
||||
func Test_ClientConfig_CanUseClientJWTWithTenantID(t *testing.T) {
|
||||
cfg := emptyClientConfig()
|
||||
cfg.SubscriptionID = "12345"
|
||||
cfg.ClientID = "12345"
|
||||
cfg.ClientJWT = getJWT(10*time.Minute, true)
|
||||
cfg.TenantID = "12345"
|
||||
|
||||
assertValid(t, cfg)
|
||||
}
|
||||
|
||||
func Test_ClientConfig_CannotUseBothClientJWTAndSecret(t *testing.T) {
|
||||
cfg := emptyClientConfig()
|
||||
cfg.SubscriptionID = "12345"
|
||||
cfg.ClientID = "12345"
|
||||
cfg.ClientSecret = "12345"
|
||||
cfg.ClientJWT = getJWT(10*time.Minute, true)
|
||||
|
||||
assertInvalid(t, cfg)
|
||||
}
|
||||
|
||||
func Test_ClientConfig_ClientJWTShouldBeValidForAtLeast5Minutes(t *testing.T) {
|
||||
cfg := emptyClientConfig()
|
||||
cfg.SubscriptionID = "12345"
|
||||
cfg.ClientID = "12345"
|
||||
cfg.ClientJWT = getJWT(time.Minute, true)
|
||||
|
||||
assertInvalid(t, cfg)
|
||||
}
|
||||
|
||||
func Test_ClientConfig_ClientJWTShouldHaveThumbprint(t *testing.T) {
|
||||
cfg := emptyClientConfig()
|
||||
cfg.SubscriptionID = "12345"
|
||||
cfg.ClientID = "12345"
|
||||
cfg.ClientJWT = getJWT(10*time.Minute, false)
|
||||
|
||||
assertInvalid(t, cfg)
|
||||
}
|
||||
|
||||
func emptyClientConfig() ClientConfig {
|
||||
cfg := ClientConfig{}
|
||||
_ = cfg.setCloudEnvironment()
|
||||
return cfg
|
||||
}
|
||||
|
||||
func Test_getJWT(t *testing.T) {
|
||||
if getJWT(time.Minute, true) == "" {
|
||||
t.Fatalf("getJWT is broken")
|
||||
}
|
||||
}
|
||||
|
||||
func newRandReader() io.Reader {
|
||||
var seed int64
|
||||
binary.Read(crand.Reader, binary.LittleEndian, &seed)
|
||||
|
||||
return mrand.New(mrand.NewSource(seed))
|
||||
}
|
||||
|
||||
func getJWT(validFor time.Duration, withX5tHeader bool) string {
|
||||
token := jwt.New(jwt.SigningMethodRS256)
|
||||
key, _ := rsa.GenerateKey(newRandReader(), 2048)
|
||||
|
||||
token.Claims = jwt.MapClaims{
|
||||
"aud": "https://login.microsoftonline.com/tenant.onmicrosoft.com/oauth2/token?api-version=1.0",
|
||||
"iss": "355dff10-cd78-11e8-89fe-000d3afd16e3",
|
||||
"sub": "355dff10-cd78-11e8-89fe-000d3afd16e3",
|
||||
"jti": base64.URLEncoding.EncodeToString([]byte{0}),
|
||||
"nbf": time.Now().Unix(),
|
||||
"exp": time.Now().Add(validFor).Unix(),
|
||||
}
|
||||
if withX5tHeader {
|
||||
token.Header["x5t"] = base64.URLEncoding.EncodeToString([]byte("thumbprint"))
|
||||
}
|
||||
|
||||
jwt, _ := token.SignedString(key)
|
||||
return jwt
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue