azure-amqp-common-go/aad/jwt.go

249 строки
8.0 KiB
Go

// Package aad provides an implementation of an Azure Active Directory JWT provider which implements TokenProvider
// from package auth for use with Azure Event Hubs and Service Bus.
package aad
// MIT License
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE
import (
"crypto/rsa"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"strconv"
"time"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"golang.org/x/crypto/pkcs12"
"github.com/Azure/azure-amqp-common-go/v2/auth"
)
const (
eventhubResourceURI = "https://eventhubs.azure.net/"
)
type (
// TokenProviderConfiguration provides configuration parameters for building JWT AAD providers
TokenProviderConfiguration struct {
TenantID string
ClientID string
ClientSecret string
CertificatePath string
CertificatePassword string
ResourceURI string
aadToken *adal.ServicePrincipalToken
Env *azure.Environment
}
// TokenProvider provides cbs.TokenProvider functionality for Azure Active Directory JWTs
TokenProvider struct {
tokenProvider *adal.ServicePrincipalToken
}
// JWTProviderOption provides configuration options for constructing AAD Token Providers
JWTProviderOption func(provider *TokenProviderConfiguration) error
)
// JWTProviderWithAzureEnvironment configures the token provider to use a specific Azure Environment
func JWTProviderWithAzureEnvironment(env *azure.Environment) JWTProviderOption {
return func(config *TokenProviderConfiguration) error {
config.Env = env
return nil
}
}
// JWTProviderWithEnvironmentVars configures the TokenProvider using the environment variables available
//
// 1. Client Credentials: attempt to authenticate with a Service Principal via "AZURE_TENANT_ID", "AZURE_CLIENT_ID" and
// "AZURE_CLIENT_SECRET"
//
// 2. Client Certificate: attempt to authenticate with a Service Principal via "AZURE_TENANT_ID", "AZURE_CLIENT_ID",
// "AZURE_CERTIFICATE_PATH" and "AZURE_CERTIFICATE_PASSWORD"
//
// 3. Managed Service Identity (MSI): attempt to authenticate via MSI
//
//
// The Azure Environment used can be specified using the name of the Azure Environment set in "AZURE_ENVIRONMENT" var.
func JWTProviderWithEnvironmentVars() JWTProviderOption {
return func(config *TokenProviderConfiguration) error {
config.TenantID = os.Getenv("AZURE_TENANT_ID")
config.ClientID = os.Getenv("AZURE_CLIENT_ID")
config.ClientSecret = os.Getenv("AZURE_CLIENT_SECRET")
config.CertificatePath = os.Getenv("AZURE_CERTIFICATE_PATH")
config.CertificatePassword = os.Getenv("AZURE_CERTIFICATE_PASSWORD")
if config.Env == nil {
env, err := azureEnvFromEnvironment()
if err != nil {
return err
}
config.Env = env
}
return nil
}
}
// JWTProviderWithResourceURI configures the token provider to use a specific eventhubResourceURI URI
func JWTProviderWithResourceURI(resourceURI string) JWTProviderOption {
return func(config *TokenProviderConfiguration) error {
config.ResourceURI = resourceURI
return nil
}
}
// JWTProviderWithAADToken configures the token provider to use a specific Azure Active Directory Service Principal token
func JWTProviderWithAADToken(aadToken *adal.ServicePrincipalToken) JWTProviderOption {
return func(config *TokenProviderConfiguration) error {
config.aadToken = aadToken
return nil
}
}
// NewJWTProvider builds an Azure Active Directory claims-based security token provider
func NewJWTProvider(opts ...JWTProviderOption) (*TokenProvider, error) {
config := &TokenProviderConfiguration{
ResourceURI: eventhubResourceURI,
}
for _, opt := range opts {
err := opt(config)
if err != nil {
return nil, err
}
}
if config.aadToken == nil {
spToken, err := config.NewServicePrincipalToken()
if err != nil {
return nil, err
}
config.aadToken = spToken
}
return &TokenProvider{tokenProvider: config.aadToken}, nil
}
// NewServicePrincipalToken creates a new Azure Active Directory Service Principal token provider
func (c *TokenProviderConfiguration) NewServicePrincipalToken() (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(c.Env.ActiveDirectoryEndpoint, c.TenantID)
if err != nil {
return nil, err
}
// 1.Client Credentials
if c.ClientSecret != "" {
spToken, err := adal.NewServicePrincipalToken(*oauthConfig, c.ClientID, c.ClientSecret, c.ResourceURI)
if err != nil {
return nil, fmt.Errorf("failed to get oauth token from client credentials: %v", err)
}
if err := spToken.Refresh(); err != nil {
return nil, fmt.Errorf("failed to refersh token: %v", spToken)
}
return spToken, nil
}
// 2. Client Certificate
if c.CertificatePath != "" {
certData, err := ioutil.ReadFile(c.CertificatePath)
if err != nil {
return nil, fmt.Errorf("failed to read the certificate file (%s): %v", c.CertificatePath, err)
}
certificate, rsaPrivateKey, err := decodePkcs12(certData, c.CertificatePassword)
if err != nil {
return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
}
spToken, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, c.ClientID, certificate, rsaPrivateKey, c.ResourceURI)
if err != nil {
return nil, fmt.Errorf("failed to get oauth token from certificate auth: %v", err)
}
if err := spToken.Refresh(); err != nil {
return nil, fmt.Errorf("failed to refersh token: %v", spToken)
}
return spToken, nil
}
// 3. By default return MSI
msiEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, err
}
spToken, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, c.ResourceURI)
if err != nil {
return nil, fmt.Errorf("failed to get oauth token from MSI: %v", err)
}
if err := spToken.Refresh(); err != nil {
return nil, fmt.Errorf("failed to refersh token: %v", spToken)
}
return spToken, nil
}
// GetToken gets a CBS JWT
func (t *TokenProvider) GetToken(audience string) (*auth.Token, error) {
token := t.tokenProvider.Token()
expireTicks, err := strconv.ParseInt(string(token.ExpiresOn), 10, 64)
if err != nil {
return nil, err
}
expires := time.Unix(expireTicks, 0)
if expires.Before(time.Now()) {
if err := t.tokenProvider.Refresh(); err != nil {
return nil, err
}
token = t.tokenProvider.Token()
}
return auth.NewToken(auth.CBSTokenTypeJWT, token.AccessToken, string(token.ExpiresOn)), nil
}
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
privateKey, certificate, err := pkcs12.Decode(pkcs, password)
if err != nil {
return nil, nil, err
}
rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)
if !isRsaKey {
return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key")
}
return certificate, rsaPrivateKey, nil
}
func azureEnvFromEnvironment() (*azure.Environment, error) {
envName := os.Getenv("AZURE_ENVIRONMENT")
var env azure.Environment
if envName == "" {
env = azure.PublicCloud
} else {
var err error
env, err = azure.EnvironmentFromName(envName)
if err != nil {
return nil, err
}
}
return &env, nil
}