Skip to content

Commit

Permalink
connect to ms365 via oidc token
Browse files Browse the repository at this point in the history
  • Loading branch information
vjeffrey committed Jan 30, 2025
1 parent ec10d80 commit 3764915
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 2 deletions.
53 changes: 52 additions & 1 deletion providers-sdk/v1/util/azauth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
package azauth

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

Expand Down Expand Up @@ -107,9 +112,13 @@ func (t *retryableManagedIdentityCredential) tryGetToken(ctx context.Context, op
return
}

func GetTokenFromCredential(credential *vault.Credential, tenantId, clientId string) (azcore.TokenCredential, error) {
func GetTokenFromCredential(credential *vault.Credential, tenantId, clientId, oToken string) (azcore.TokenCredential, error) {
var azCred azcore.TokenCredential
var err error
if oToken != "" {
return OidcToken{tenantId: tenantId, clientId: clientId, jwtToken: oToken}, nil
}

// fallback to default authorizer if no credentials are specified
if credential == nil {
log.Debug().Msg("using default azure token chain resolver")
Expand Down Expand Up @@ -139,3 +148,45 @@ func GetTokenFromCredential(credential *vault.Credential, tenantId, clientId str
}
return azCred, nil
}

type OidcToken struct {
tenantId string
clientId string
jwtToken string
}

func (o OidcToken) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
var tokenBeg, tokenEnd string
if len(o.jwtToken) > 10 {
tokenBeg = o.jwtToken[0:10]
tokenEnd = o.jwtToken[:10]
}
log.Debug().Str("tenant id", o.tenantId).Str("client id", o.clientId).Str("oidc token", fmt.Sprintf("%s ... %s", tokenBeg, tokenEnd)).Msg("fetching credentials from microsoft using oidc token")
requestUrl := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", o.tenantId)
tokenRequestBody := url.QueryEscape(fmt.Sprintf("client_id=%s&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion=%s&grant_type=client_credentials&scope=https://graph.microsoft.com/.default", o.clientId, o.jwtToken))

log.Debug().Str("url", requestUrl).Str("body", tokenRequestBody).Msg("making http request")
req, err := http.NewRequest(http.MethodPost, requestUrl, bytes.NewReader([]byte(tokenRequestBody)))
if err != nil {
return azcore.AccessToken{}, err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Host", "login.microsoftonline.com")
res, err := http.DefaultClient.Do(req)
if err != nil {
return azcore.AccessToken{}, err
}
b, err := io.ReadAll(res.Body)
if err != nil {
return azcore.AccessToken{}, err
}
type accessToken struct {
AccessToken string `json:"access_token"`
}
var tok accessToken
err = json.Unmarshal(b, &tok)
if err != nil {
return azcore.AccessToken{}, err
}
return azcore.AccessToken{Token: tok.AccessToken}, nil
}
7 changes: 7 additions & 0 deletions providers/azure/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ Examples run in the Azure CLI:
Default: "",
Desc: "Comma-separated list of Azure subscriptions to exclude",
},
{
Long: "oidc-token",
Type: plugin.FlagType_String,
Default: "",
Desc: "JWT token to use for OIDC exchange with MS365",
Option: plugin.FlagOption_Hidden,
},
},
},
},
Expand Down
1 change: 1 addition & 0 deletions providers/azure/connection/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
const (
OptionTenantID = "tenant-id"
OptionClientID = "client-id"
OIDCToken = "oidc-token"
OptionDataReport = "mondoo-ms365-datareport"
OptionSubscriptionID = "subscription-id"
OptionPlatformOverride = "platform-override"
Expand Down
2 changes: 2 additions & 0 deletions providers/azure/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error)

tenantId := flags["tenant-id"]
clientId := flags["client-id"]
oidcToken := flags["oidc-token"]
clientSecret := flags["client-secret"]
subscriptionId := flags["subscription"]
subscriptions := flags["subscriptions"]
Expand All @@ -49,6 +50,7 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error)

opts["tenant-id"] = string(tenantId.Value)
opts["client-id"] = string(clientId.Value)
opts["oidc-token"] = string(oidcToken.Value)
if len(subscriptionId.Value) > 0 {
opts["subscriptions"] = string(subscriptionId.Value)
}
Expand Down
7 changes: 7 additions & 0 deletions providers/ms365/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ Notes:
Default: "",
Desc: "Passphrase for the authentication certificate file",
},
{
Long: "oidc-token",
Type: plugin.FlagType_String,
Default: "",
Desc: "JWT token to use for OIDC exchange with MS365",
Option: plugin.FlagOption_Hidden,
},
},
},
},
Expand Down
5 changes: 4 additions & 1 deletion providers/ms365/connection/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
const (
OptionTenantID = "tenant-id"
OptionClientID = "client-id"
OptionOIDCToken = "oidc-token"
OptionOrganization = "organization"
OptionSharepointUrl = "sharepoint-url"
)
Expand All @@ -41,6 +42,8 @@ type Ms365Connection struct {
func NewMs365Connection(id uint32, asset *inventory.Asset, conf *inventory.Config) (*Ms365Connection, error) {
tenantId := conf.Options[OptionTenantID]
clientId := conf.Options[OptionClientID]
oidcToken := conf.Options[OptionOIDCToken]

organization := conf.Options[OptionOrganization]
sharepointUrl := conf.Options[OptionSharepointUrl]
var cred *vault.Credential
Expand All @@ -51,7 +54,7 @@ func NewMs365Connection(id uint32, asset *inventory.Asset, conf *inventory.Confi
if len(tenantId) == 0 {
return nil, errors.New("ms365 backend requires a tenant-id")
}
token, err := azauth.GetTokenFromCredential(cred, tenantId, clientId)
token, err := azauth.GetTokenFromCredential(cred, tenantId, clientId, oidcToken)
if err != nil {
return nil, errors.Wrap(err, "cannot fetch credentials for microsoft provider")
}
Expand Down
2 changes: 2 additions & 0 deletions providers/ms365/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error)

tenantId := flags["tenant-id"]
clientId := flags["client-id"]
oidcToken := flags["oidc-token"]
clientSecret := flags["client-secret"]
certificatePath := flags["certificate-path"]
certificateSecret := flags["certificate-secret"]
Expand All @@ -43,6 +44,7 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error)
opts := map[string]string{}
creds := []*vault.Credential{}

opts[connection.OptionOIDCToken] = string(oidcToken.Value)
opts[connection.OptionTenantID] = string(tenantId.Value)
opts[connection.OptionClientID] = string(clientId.Value)
opts[connection.OptionOrganization] = string(organization.Value)
Expand Down

0 comments on commit 3764915

Please sign in to comment.