Skip to content

Commit

Permalink
Adding graph permissions to the MSI
Browse files Browse the repository at this point in the history
  • Loading branch information
mvbrock committed Jan 3, 2025
1 parent 68ff0af commit f25b55b
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 25 deletions.
50 changes: 32 additions & 18 deletions lib/integrations/azureoidc/accessgraph_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,26 @@ import (
"github.com/gravitational/teleport/lib/cloud/provisioning"
"github.com/gravitational/teleport/lib/config"
"github.com/gravitational/teleport/lib/msgraph"
"github.com/gravitational/teleport/lib/utils/slices"
tslices "github.com/gravitational/teleport/lib/utils/slices"
"github.com/gravitational/trace"
"log/slog"
"os"
"slices"
)

// graphAppId is the pre-defined application ID of the Graph API
// Ref: [https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications].
const graphAppId = "00000003-0000-0000-c000-000000000000"

var requiredGraphRoleNames = []string{
"User.ReadBasic.All",
"Group.Read.All",
"Directory.Read.All",
"User.Read.All",
"Policy.Read.All",
}

func newManagedIdAction(cred *azidentity.DefaultAzureCredential, subId string, managedId string, roleName string) (*provisioning.Action, error) {
runnerFn := func(ctx context.Context) error {
// Create the managed identity
userIdCli, err := armmsi.NewUserAssignedIdentitiesClient(subId, cred, nil)
if err != nil {
return trace.Wrap(fmt.Errorf("could not create managed identity client: %v", err))
}
id := armmsi.Identity{}
userIdCli.Get(ctx)
mgdIdRes, err := userIdCli.CreateOrUpdate(ctx, "", name, id, nil)
if err != nil {
return trace.Wrap(fmt.Errorf("could not create managed identity: %v", err))
}
slog.InfoContext(ctx, fmt.Sprintf(
"Managed identity created, Name: %s, ID: %s", *mgdIdRes.Name, *mgdIdRes.ID))

// Create the role
roleDefCli, err := armauthorization.NewRoleDefinitionsClient(cred, nil)
if err != nil {
Expand All @@ -47,7 +45,7 @@ func newManagedIdAction(cred *azidentity.DefaultAzureCredential, subId string, m
RoleType: &customRole,
Permissions: []*armauthorization.Permission{
{
Actions: slices.ToPointers([]string{
Actions: tslices.ToPointers([]string{
"Microsoft.Compute/virtualMachines/read",
"Microsoft.Compute/virtualMachines/list",
"Microsoft.Compute/virtualMachineScaleSets/virtualMachines/read",
Expand Down Expand Up @@ -89,7 +87,23 @@ func newManagedIdAction(cred *azidentity.DefaultAzureCredential, subId string, m
graphCli, err := msgraph.NewClient(msgraph.Config{
TokenProvider: cred,
})
graphCli.GetServicePrincipalByAppId()
graphPrincipal, err := graphCli.GetServicePrincipalByAppId(ctx, graphAppId)
var graphRoleIds []string
for _, appRole := range graphPrincipal.AppRoles {
if slices.Contains(requiredGraphRoleNames, *appRole.Value) {
graphRoleIds = append(graphRoleIds, *appRole.ID)
}
}
for _, graphRoleId := range graphRoleIds {
_, err := graphCli.GrantAppRoleToServicePrincipal(ctx, managedId, &msgraph.AppRoleAssignment{
AppRoleID: &graphRoleId,
PrincipalID: &managedId,
ResourceID: graphPrincipal.ID,
})
if err != nil {
return trace.Wrap(fmt.Errorf("failed to create role assignment: %v", err))
}
}

return nil
}
Expand Down
13 changes: 9 additions & 4 deletions lib/msgraph/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ const baseURL = "https://graph.microsoft.com/v1.0"
// defaultPageSize is the page size used when [Config.PageSize] is not specified.
const defaultPageSize = 500

// graphAppId is the pre-defined application ID of the Graph API
// Ref: [https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in#application-ids-of-commonly-used-microsoft-applications].
const graphAppId = "00000003-0000-0000-c000-000000000000"

// scopes defines OAuth scopes the client authenticates for.
var scopes = []string{"https://graph.microsoft.com/.default"}

Expand Down Expand Up @@ -340,6 +336,15 @@ func (c *Client) GetServicePrincipalsByDisplayName(ctx context.Context, displayN
return out.Value, nil
}

func (c *Client) GetServicePrincipal(ctx context.Context, principalId string) (*ServicePrincipal, error) {
uri := c.endpointURI(fmt.Sprintf("servicePrincipals/%s", principalId))
out, err := roundtrip[*ServicePrincipal](ctx, c, http.MethodGet, uri.String(), nil)
if err != nil {
return nil, trace.Wrap(err)
}
return out, nil
}

// GrantAppRoleToServicePrincipal grants the given app role to the specified Service Principal.
// Ref: [https://learn.microsoft.com/en-us/graph/api/serviceprincipal-post-approleassignedto]
func (c *Client) GrantAppRoleToServicePrincipal(ctx context.Context, spID string, assignment *AppRoleAssignment) (*AppRoleAssignment, error) {
Expand Down
12 changes: 9 additions & 3 deletions lib/msgraph/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,10 @@ type WebApplication struct {

type ServicePrincipal struct {
DirectoryObject
AppRoleAssignmentRequired *bool `json:"appRoleAssignmentRequired,omitempty"`
PreferredSingleSignOnMode *string `json:"preferredSingleSignOnMode,omitempty"`
PreferredTokenSigningKeyThumbprint *string `json:"preferredTokenSigningKeyThumbprint,omitempty"`
AppRoleAssignmentRequired *bool `json:"appRoleAssignmentRequired,omitempty"`
PreferredSingleSignOnMode *string `json:"preferredSingleSignOnMode,omitempty"`
PreferredTokenSigningKeyThumbprint *string `json:"preferredTokenSigningKeyThumbprint,omitempty"`
AppRoles []*AppRole `json:"appRoles,omitempty"`
}

type ApplicationServicePrincipal struct {
Expand All @@ -144,6 +145,11 @@ type SelfSignedCertificate struct {
Thumbprint *string `json:"thumbprint,omitempty"`
}

type AppRole struct {
ID *string `json:"id,omitempty"`
Value *string `json:"value,omitempty"`
}

type AppRoleAssignment struct {
ID *string `json:"id,omitempty"`
AppRoleID *string `json:"appRoleId,omitempty"`
Expand Down

0 comments on commit f25b55b

Please sign in to comment.