Adding the Azure sync module functions along with new cloud client functionality (#50366)

* Protobuf and configuration for Access Graph Azure Discovery

* Adding the Azure sync module functions along with new cloud client functionality

* Forgot to decouple role definitions fetching function from the fetcher

* Moving reconciliation to the upstream azure sync PR

* Moving reconciliation test to the upstream azure sync PR

* Updating go.sum

* Fixing rebase after protobuf gen

* Nolinting until upstream PRs

* Updating to use existing msgraph client

* Adding protection around nil values

* PR feedback

* Updating principal fetching to incorporate metadata from principal subtypes

* Updating opts to not leak URL parameters

* Conformant package name

* Using variadic options

* PR feedback

* Removing memberOf expansion

* Expanding memberships by calling memberOf on each user

* Also returning expanded principals for improved readability

* Removing ptrToList

* PR feedback

* Rebase go.sum stuff

* Go mod tidy

* Linting

* Linting

* Collecting errors from fetching memberships and using a WithContext error group

* Fixing go.mod

* Update lib/msgraph/paginated.go

Co-authored-by: Tiago Silva <tiago.silva@goteleport.com>

* PR feedback

* e ref update

* e ref update

* Fixing method

* Fetching group members from groups rather than memberships of each principal

* Linting

---------

Co-authored-by: Tiago Silva <tiago.silva@goteleport.com>
This commit is contained in:
Matt Brock
2025-01-13 19:27:58 +00:00
committed by GitHub
co-authored by Tiago Silva
parent b6e2badbe9
commit 47f4498b76
15 changed files with 517 additions and 1 deletions
+1
View File
@@ -16,6 +16,7 @@ require (
connectrpc.com/connect v1.18.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v6 v6.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.2.0
+2
View File
@@ -668,6 +668,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLC
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.1/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 h1:Hp+EScFOu9HeCbeW8WU2yQPJd4gGwhMgKxWe+G6jNzw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0/go.mod h1:/pz8dyNQe+Ey3yBp/XuYz7oqX8YDNWVpPB0hH3XWfbc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0 h1:JAebRMoc3vL+Nd97GBprHYHucO4+wlW+tNbBIumqJlk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0/go.mod h1:zflC9v4VfViJrSvcvplqws/yGXVbUEMZi/iHpZdSPWA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v5 v5.0.0 h1:5n7dPVqsWfVKw+ZiEKSd3Kzu7gwBkbEBkeXb8rgaE9Q=
+1
View File
@@ -37,6 +37,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v6 v6.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.2.0 // indirect
+2
View File
@@ -631,6 +631,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvUL
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 h1:Hp+EScFOu9HeCbeW8WU2yQPJd4gGwhMgKxWe+G6jNzw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0/go.mod h1:/pz8dyNQe+Ey3yBp/XuYz7oqX8YDNWVpPB0hH3XWfbc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0 h1:JAebRMoc3vL+Nd97GBprHYHucO4+wlW+tNbBIumqJlk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0/go.mod h1:zflC9v4VfViJrSvcvplqws/yGXVbUEMZi/iHpZdSPWA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v5 v5.0.0 h1:5n7dPVqsWfVKw+ZiEKSd3Kzu7gwBkbEBkeXb8rgaE9Q=
+1
View File
@@ -43,6 +43,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v6 v6.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.2.0 // indirect
+2
View File
@@ -644,6 +644,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvUL
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 h1:Hp+EScFOu9HeCbeW8WU2yQPJd4gGwhMgKxWe+G6jNzw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0/go.mod h1:/pz8dyNQe+Ey3yBp/XuYz7oqX8YDNWVpPB0hH3XWfbc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0 h1:JAebRMoc3vL+Nd97GBprHYHucO4+wlW+tNbBIumqJlk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6 v6.2.0/go.mod h1:zflC9v4VfViJrSvcvplqws/yGXVbUEMZi/iHpZdSPWA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v5 v5.0.0 h1:5n7dPVqsWfVKw+ZiEKSd3Kzu7gwBkbEBkeXb8rgaE9Q=
+57
View File
@@ -0,0 +1,57 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package azure
import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2"
"github.com/gravitational/trace"
)
// RoleAssignmentsClient wraps the Azure API to provide a high level subset of functionality
type RoleAssignmentsClient struct {
cli *armauthorization.RoleAssignmentsClient
}
// NewRoleAssignmentsClient creates a new client for a given subscription and credentials
func NewRoleAssignmentsClient(subscription string, cred azcore.TokenCredential, options *arm.ClientOptions) (*RoleAssignmentsClient, error) {
clientFactory, err := armauthorization.NewClientFactory(subscription, cred, options)
if err != nil {
return nil, trace.Wrap(err)
}
roleDefCli := clientFactory.NewRoleAssignmentsClient()
return &RoleAssignmentsClient{cli: roleDefCli}, nil
}
// ListRoleAssignments returns role assignments for a given scope
func (c *RoleAssignmentsClient) ListRoleAssignments(ctx context.Context, scope string) ([]*armauthorization.RoleAssignment, error) {
pager := c.cli.NewListForScopePager(scope, nil)
var roleDefs []*armauthorization.RoleAssignment
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
roleDefs = append(roleDefs, page.Value...)
}
return roleDefs, nil
}
+57
View File
@@ -0,0 +1,57 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package azure
import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2"
"github.com/gravitational/trace"
)
// RoleDefinitionsClient wraps the Azure API to provide a high level subset of functionality
type RoleDefinitionsClient struct {
cli *armauthorization.RoleDefinitionsClient
}
// NewRoleDefinitionsClient creates a new client for a given subscription and credentials
func NewRoleDefinitionsClient(subscription string, cred azcore.TokenCredential, options *arm.ClientOptions) (*RoleDefinitionsClient, error) {
clientFactory, err := armauthorization.NewClientFactory(subscription, cred, options)
if err != nil {
return nil, trace.Wrap(err)
}
roleDefCli := clientFactory.NewRoleDefinitionsClient()
return &RoleDefinitionsClient{cli: roleDefCli}, nil
}
// ListRoleDefinitions returns role definitions for a given scope
func (c *RoleDefinitionsClient) ListRoleDefinitions(ctx context.Context, scope string) ([]*armauthorization.RoleDefinition, error) {
pager := c.cli.NewListPager(scope, nil)
var roleDefs []*armauthorization.RoleDefinition
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
roleDefs = append(roleDefs, page.Value...)
}
return roleDefs, nil
}
+27 -1
View File
@@ -344,6 +344,10 @@ type azureClients struct {
azurePostgresFlexServersClients azure.ClientMap[azure.PostgresFlexServersClient]
// azureRunCommandClients contains the cached Azure Run Command clients.
azureRunCommandClients azure.ClientMap[azure.RunCommandClient]
// azureRoleDefinitionsClients contains the cached Azure Role Definitions clients.
azureRoleDefinitionsClients azure.ClientMap[azure.RoleDefinitionsClient]
// azureRoleAssignmentsClients contains the cached Azure Role Assignments clients.
azureRoleAssignmentsClients azure.ClientMap[azure.RoleAssignmentsClient]
}
// credentialsSource defines where the credentials must come from.
@@ -717,6 +721,16 @@ func (c *cloudClients) GetAzureRunCommandClient(subscription string) (azure.RunC
return c.azureRunCommandClients.Get(subscription, c.GetAzureCredential)
}
// GetAzureRoleDefinitionsClient returns an Azure Role Definitions client
func (c *cloudClients) GetAzureRoleDefinitionsClient(subscription string) (azure.RoleDefinitionsClient, error) {
return c.azureRoleDefinitionsClients.Get(subscription, c.GetAzureCredential)
}
// GetAzureRoleAssignmentsClient returns an Azure Role Assignments client
func (c *cloudClients) GetAzureRoleAssignmentsClient(subscription string) (azure.RoleAssignmentsClient, error) {
return c.azureRoleAssignmentsClients.Get(subscription, c.GetAzureCredential)
}
// Close closes all initialized clients.
func (c *cloudClients) Close() (err error) {
c.mtx.Lock()
@@ -1021,6 +1035,8 @@ type TestCloudClients struct {
AzureMySQLFlex azure.MySQLFlexServersClient
AzurePostgresFlex azure.PostgresFlexServersClient
AzureRunCommand azure.RunCommandClient
AzureRoleDefinitions azure.RoleDefinitionsClient
AzureRoleAssignments azure.RoleAssignmentsClient
}
// GetAWSSession returns AWS session for the specified region, optionally
@@ -1244,11 +1260,21 @@ func (c *TestCloudClients) GetAzurePostgresFlexServersClient(subscription string
return c.AzurePostgresFlex, nil
}
// GetAzureRunCommand returns an Azure Run Command client for the given subscription.
// GetAzureRunCommandClient returns an Azure Run Command client for the given subscription.
func (c *TestCloudClients) GetAzureRunCommandClient(subscription string) (azure.RunCommandClient, error) {
return c.AzureRunCommand, nil
}
// GetAzureRoleDefinitionsClient returns an Azure Role Definitions client for the given subscription.
func (c *TestCloudClients) GetAzureRoleDefinitionsClient(subscription string) (azure.RoleDefinitionsClient, error) {
return c.AzureRoleDefinitions, nil
}
// GetAzureRoleAssignmentsClient returns an Azure Role Assignments client for the given subscription.
func (c *TestCloudClients) GetAzureRoleAssignmentsClient(subscription string) (azure.RoleAssignmentsClient, error) {
return c.AzureRoleAssignments, nil
}
// Close closes all initialized clients.
func (c *TestCloudClients) Close() error {
return nil
+8
View File
@@ -101,6 +101,14 @@ func (c *Client) IterateUsers(ctx context.Context, f func(*User) bool) error {
return iterateSimple(c, ctx, "users", f)
}
// IterateServicePrincipals lists all service principals in the Entra ID directory using pagination.
// `f` will be called for each object in the result set.
// if `f` returns `false`, the iteration is stopped (equivalent to `break` in a normal loop).
// Ref: [https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list].
func (c *Client) IterateServicePrincipals(ctx context.Context, f func(principal *ServicePrincipal) bool) error {
return iterateSimple(c, ctx, "servicePrincipals", f)
}
// IterateGroupMembers lists all members for the given Entra ID group using pagination.
// `f` will be called for each object in the result set.
// if `f` returns `false`, the iteration is stopped (equivalent to `break` in a normal loop).
@@ -0,0 +1,65 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package azuresync
import (
"context"
"github.com/gravitational/trace"
"golang.org/x/sync/errgroup"
accessgraphv1alpha "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha"
"github.com/gravitational/teleport/lib/msgraph"
)
const parallelism = 10 //nolint:unused // invoked in a dependent PR
// expandMemberships adds membership data to AzurePrincipal objects by querying the Graph API for group memberships
func expandMemberships(ctx context.Context, cli *msgraph.Client, principals []*accessgraphv1alpha.AzurePrincipal) ([]*accessgraphv1alpha.AzurePrincipal, error) { //nolint:unused // invoked in a dependent PR
// Map principals by ID
var principalsMap = make(map[string]*accessgraphv1alpha.AzurePrincipal)
for _, principal := range principals {
principalsMap[principal.Id] = principal
}
// Iterate through the Azure groups and add the group ID as a membership for its corresponding principal
eg, _ := errgroup.WithContext(ctx)
eg.SetLimit(parallelism)
errCh := make(chan error, len(principals))
for _, principal := range principals {
if principal.ObjectType != "group" {
continue
}
group := principal
eg.Go(func() error {
err := cli.IterateGroupMembers(ctx, group.Id, func(member msgraph.GroupMember) bool {
if memberPrincipal, ok := principalsMap[*member.GetID()]; ok {
memberPrincipal.MemberOf = append(memberPrincipal.MemberOf, group.Id)
}
return true
})
if err != nil {
errCh <- err
}
return nil
})
}
_ = eg.Wait()
close(errCh)
return principals, trace.NewAggregateFromChannel(errCh, ctx)
}
@@ -0,0 +1,87 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package azuresync
import (
"context"
"github.com/gravitational/trace"
"google.golang.org/protobuf/types/known/timestamppb"
accessgraphv1alpha "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha"
"github.com/gravitational/teleport/lib/msgraph"
)
type dirObjMetadata struct { //nolint:unused // invoked in a dependent PR
objectType string
}
type queryResult struct { //nolint:unused // invoked in a dependent PR
metadata dirObjMetadata
dirObj msgraph.DirectoryObject
}
// fetchPrincipals fetches the Azure principals (users, groups, and service principals) using the Graph API
func fetchPrincipals(ctx context.Context, subscriptionID string, cli *msgraph.Client) ([]*accessgraphv1alpha.AzurePrincipal, error) { //nolint: unused // invoked in a dependent PR
// Fetch the users, groups, and service principals as directory objects
var queryResults []queryResult
err := cli.IterateUsers(ctx, func(user *msgraph.User) bool {
res := queryResult{metadata: dirObjMetadata{objectType: "user"}, dirObj: user.DirectoryObject}
queryResults = append(queryResults, res)
return true
})
if err != nil {
return nil, trace.Wrap(err)
}
err = cli.IterateGroups(ctx, func(group *msgraph.Group) bool {
res := queryResult{metadata: dirObjMetadata{objectType: "group"}, dirObj: group.DirectoryObject}
queryResults = append(queryResults, res)
return true
})
if err != nil {
return nil, trace.Wrap(err)
}
err = cli.IterateServicePrincipals(ctx, func(servicePrincipal *msgraph.ServicePrincipal) bool {
res := queryResult{metadata: dirObjMetadata{objectType: "servicePrincipal"}, dirObj: servicePrincipal.DirectoryObject}
queryResults = append(queryResults, res)
return true
})
if err != nil {
return nil, trace.Wrap(err)
}
// Return the users, groups, and service principals as protobuf messages
var fetchErrs []error
var pbPrincipals []*accessgraphv1alpha.AzurePrincipal
for _, res := range queryResults {
if res.dirObj.ID == nil || res.dirObj.DisplayName == nil {
fetchErrs = append(fetchErrs,
trace.BadParameter("nil values on msgraph directory object: %v", res.dirObj))
continue
}
pbPrincipals = append(pbPrincipals, &accessgraphv1alpha.AzurePrincipal{
Id: *res.dirObj.ID,
SubscriptionId: subscriptionID,
LastSyncTime: timestamppb.Now(),
DisplayName: *res.dirObj.DisplayName,
ObjectType: res.metadata.objectType,
})
}
return pbPrincipals, trace.NewAggregate(fetchErrs...)
}
@@ -0,0 +1,68 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package azuresync
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2"
"github.com/gravitational/trace"
"google.golang.org/protobuf/types/known/timestamppb"
accessgraphv1alpha "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha"
)
// RoleAssignmentsClient specifies the methods used to fetch role assignments from Azure
type RoleAssignmentsClient interface {
ListRoleAssignments(ctx context.Context, scope string) ([]*armauthorization.RoleAssignment, error)
}
// fetchRoleAssignments fetches Azure role assignments using the Azure role assignments API
func fetchRoleAssignments(ctx context.Context, subscriptionID string, cli RoleAssignmentsClient) ([]*accessgraphv1alpha.AzureRoleAssignment, error) { //nolint:unused // invoked in a dependent PR
// List the role definitions
roleAssigns, err := cli.ListRoleAssignments(ctx, fmt.Sprintf("/subscriptions/%s", subscriptionID))
if err != nil {
return nil, trace.Wrap(err)
}
// Convert to protobuf format
pbRoleAssigns := make([]*accessgraphv1alpha.AzureRoleAssignment, 0, len(roleAssigns))
var fetchErrs []error
for _, roleAssign := range roleAssigns {
if roleAssign.ID == nil ||
roleAssign.Properties == nil ||
roleAssign.Properties.PrincipalID == nil ||
roleAssign.Properties.Scope == nil {
fetchErrs = append(fetchErrs,
trace.BadParameter("nil values on AzureRoleAssignment object: %v", roleAssign))
continue
}
pbRoleAssign := &accessgraphv1alpha.AzureRoleAssignment{
Id: *roleAssign.ID,
SubscriptionId: subscriptionID,
LastSyncTime: timestamppb.Now(),
PrincipalId: *roleAssign.Properties.PrincipalID,
RoleDefinitionId: *roleAssign.Properties.RoleDefinitionID,
Scope: *roleAssign.Properties.Scope,
}
pbRoleAssigns = append(pbRoleAssigns, pbRoleAssign)
}
return pbRoleAssigns, trace.NewAggregate(fetchErrs...)
}
@@ -0,0 +1,78 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package azuresync
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2"
"github.com/gravitational/trace"
"google.golang.org/protobuf/types/known/timestamppb"
accessgraphv1alpha "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha"
"github.com/gravitational/teleport/lib/utils/slices"
)
// RoleDefinitionsClient specifies the methods used to fetch roles from Azure
type RoleDefinitionsClient interface {
ListRoleDefinitions(ctx context.Context, scope string) ([]*armauthorization.RoleDefinition, error)
}
func fetchRoleDefinitions(ctx context.Context, subscriptionID string, cli RoleDefinitionsClient) ([]*accessgraphv1alpha.AzureRoleDefinition, error) { //nolint:unused // used in a dependent PR
// List the role definitions
roleDefs, err := cli.ListRoleDefinitions(ctx, fmt.Sprintf("/subscriptions/%s", subscriptionID))
if err != nil {
return nil, trace.Wrap(err)
}
// Convert to protobuf format
pbRoleDefs := make([]*accessgraphv1alpha.AzureRoleDefinition, 0, len(roleDefs))
var fetchErrs []error
for _, roleDef := range roleDefs {
if roleDef.ID == nil ||
roleDef.Properties == nil ||
roleDef.Properties.Permissions == nil ||
roleDef.Properties.RoleName == nil {
fetchErrs = append(fetchErrs, trace.BadParameter("nil values on AzureRoleDefinition object: %v", roleDef))
continue
}
pbPerms := make([]*accessgraphv1alpha.AzureRBACPermission, 0, len(roleDef.Properties.Permissions))
for _, perm := range roleDef.Properties.Permissions {
if perm.Actions == nil && perm.NotActions == nil {
fetchErrs = append(fetchErrs, trace.BadParameter("nil values on Permission object: %v", perm))
continue
}
pbPerm := accessgraphv1alpha.AzureRBACPermission{
Actions: slices.FromPointers(perm.Actions),
NotActions: slices.FromPointers(perm.NotActions),
}
pbPerms = append(pbPerms, &pbPerm)
}
pbRoleDef := &accessgraphv1alpha.AzureRoleDefinition{
Id: *roleDef.ID,
Name: *roleDef.Properties.RoleName,
SubscriptionId: subscriptionID,
LastSyncTime: timestamppb.Now(),
Permissions: pbPerms,
}
pbRoleDefs = append(pbRoleDefs, pbRoleDef)
}
return pbRoleDefs, trace.NewAggregate(fetchErrs...)
}
@@ -0,0 +1,61 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package azuresync
import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6"
"github.com/gravitational/trace"
"google.golang.org/protobuf/types/known/timestamppb"
accessgraphv1alpha "github.com/gravitational/teleport/gen/proto/go/accessgraph/v1alpha"
)
const allResourceGroups = "*" //nolint:unused // invoked in a dependent PR
// VirtualMachinesClient specifies the methods used to fetch virtual machines from Azure
type VirtualMachinesClient interface {
ListVirtualMachines(ctx context.Context, resourceGroup string) ([]*armcompute.VirtualMachine, error)
}
func fetchVirtualMachines(ctx context.Context, subscriptionID string, cli VirtualMachinesClient) ([]*accessgraphv1alpha.AzureVirtualMachine, error) { //nolint:unused // invoked in a dependent PR
vms, err := cli.ListVirtualMachines(ctx, allResourceGroups)
if err != nil {
return nil, trace.Wrap(err)
}
// Return the VMs as protobuf messages
pbVms := make([]*accessgraphv1alpha.AzureVirtualMachine, 0, len(vms))
var fetchErrs []error
for _, vm := range vms {
if vm.ID == nil || vm.Name == nil {
fetchErrs = append(fetchErrs, trace.BadParameter("nil values on AzureVirtualMachine object: %v", vm))
continue
}
pbVm := accessgraphv1alpha.AzureVirtualMachine{
Id: *vm.ID,
SubscriptionId: subscriptionID,
LastSyncTime: timestamppb.Now(),
Name: *vm.Name,
}
pbVms = append(pbVms, &pbVm)
}
return pbVms, trace.NewAggregate(fetchErrs...)
}