diff --git a/pkg/connector/database.go b/pkg/connector/database.go index 24c3dc4..2492be8 100644 --- a/pkg/connector/database.go +++ b/pkg/connector/database.go @@ -72,7 +72,7 @@ func (d *databaseSyncer) Entitlements(ctx context.Context, resource *v2.Resource Slug: name, Purpose: v2.Entitlement_PURPOSE_VALUE_PERMISSION, Resource: resource, - GrantableTo: []*v2.ResourceType{resourceTypeUser}, + GrantableTo: []*v2.ResourceType{resourceTypeUser, resourceTypeGroup, resourceTypeDatabaseRole}, }, &v2.Entitlement{ Id: enTypes.NewEntitlementID(resource, key+"-grant"), @@ -80,7 +80,7 @@ func (d *databaseSyncer) Entitlements(ctx context.Context, resource *v2.Resource Slug: grantSlug, Purpose: v2.Entitlement_PURPOSE_VALUE_PERMISSION, Resource: resource, - GrantableTo: []*v2.ResourceType{resourceTypeUser}, + GrantableTo: []*v2.ResourceType{resourceTypeUser, resourceTypeGroup, resourceTypeDatabaseRole}, }) } @@ -143,15 +143,20 @@ func (d *databaseSyncer) Grants(ctx context.Context, resource *v2.Resource, pTok return nil, "", nil, fmt.Errorf("unexpected resource type: %s", rt.Id) } + grantOpts, err := BuildBatonIDGrantOptions(resourceID, p.PrincipalType, p.PrincipalName) + if err != nil { + return nil, "", nil, err + } + switch p.State { case "G": ret = append(ret, grTypes.NewGrant(resource, perm, &v2.Resource{ Id: resourceID, - })) + }, grantOpts...)) case "W": ret = append(ret, grTypes.NewGrant(resource, perm+"-grant", &v2.Resource{ Id: resourceID, - })) + }, grantOpts...)) } } } diff --git a/pkg/connector/database_role.go b/pkg/connector/database_role.go index 8130d54..b10089e 100644 --- a/pkg/connector/database_role.go +++ b/pkg/connector/database_role.go @@ -74,7 +74,8 @@ func (d *databaseRolePrincipalSyncer) List(ctx context.Context, parentResourceID func (d *databaseRolePrincipalSyncer) Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) { var ret []*v2.Entitlement - ret = append(ret, enTypes.NewAssignmentEntitlement(resource, "member")) + grantableTo := enTypes.WithGrantableTo(resourceTypeUser, resourceTypeGroup, resourceTypeDatabaseRole) + ret = append(ret, enTypes.NewAssignmentEntitlement(resource, "member", grantableTo)) return ret, "", nil, nil } @@ -216,7 +217,12 @@ func (d *databaseRolePrincipalSyncer) Grants( return nil, "", nil, fmt.Errorf("invalid state: principalID is nil") } - ret = append(ret, grTypes.NewGrant(resource, "member", principalID)) + grantOpts, err := BuildBatonIDGrantOptions(principalID, dbPrincipal.Type, dbPrincipal.Name) + if err != nil { + return nil, "", nil, err + } + + ret = append(ret, grTypes.NewGrant(resource, "member", principalID, grantOpts...)) } visited[b.ResourceID()] = true diff --git a/pkg/connector/group.go b/pkg/connector/group.go index 139e785..9fb3ce6 100644 --- a/pkg/connector/group.go +++ b/pkg/connector/group.go @@ -32,13 +32,14 @@ func (d *groupPrincipalSyncer) List(ctx context.Context, parentResourceID *v2.Re var ret []*v2.Resource for _, principalModel := range principals { - r, err := resource.NewUserResource( + r, err := resource.NewGroupResource( principalModel.Name, d.ResourceType(ctx), principalModel.ID, nil, resource.WithParentResourceID(parentResourceID), ) + if err != nil { return nil, "", nil, err } diff --git a/pkg/connector/helpers.go b/pkg/connector/helpers.go index ac7bf0a..5e928b4 100644 --- a/pkg/connector/helpers.go +++ b/pkg/connector/helpers.go @@ -23,9 +23,9 @@ func resourceTypeFromDatabasePrincipal(pType string) (*v2.ResourceType, error) { switch pType { case "R": return resourceTypeDatabaseRole, nil - case "G", "X": + case "G", "X": // Windows group, External group from Microsoft Entra ID return resourceTypeGroup, nil - case "S", "U", "C", "E", "K": + case "S", "U", "C", "E", "K", "A": // SQL login, Windows login, Certificate, External login from Microsoft Entra ID, Asymmetric key, Application role? return resourceTypeUser, nil default: return nil, fmt.Errorf("unknown principal type: %s", pType) diff --git a/pkg/connector/resource_types.go b/pkg/connector/resource_types.go index c58a57d..bfba567 100644 --- a/pkg/connector/resource_types.go +++ b/pkg/connector/resource_types.go @@ -23,7 +23,7 @@ var ( resourceTypeGroup = &v2.ResourceType{ Id: mssqldb.GroupType, DisplayName: "Group", - Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_USER}, + Traits: []v2.ResourceType_Trait{v2.ResourceType_TRAIT_GROUP}, } resourceTypeServerRole = &v2.ResourceType{ Id: mssqldb.ServerRoleType, diff --git a/pkg/connector/server.go b/pkg/connector/server.go index 64ee8ca..2270888 100644 --- a/pkg/connector/server.go +++ b/pkg/connector/server.go @@ -99,6 +99,7 @@ func (d *serverSyncer) Entitlements(ctx context.Context, resource *v2.Resource, Slug: name, Purpose: v2.Entitlement_PURPOSE_VALUE_PERMISSION, Resource: resource, + GrantableTo: []*v2.ResourceType{resourceTypeUser, resourceTypeGroup, resourceTypeServerRole}, }) ret = append(ret, &v2.Entitlement{ Id: enTypes.NewEntitlementID(resource, key+"-grant"), @@ -106,6 +107,7 @@ func (d *serverSyncer) Entitlements(ctx context.Context, resource *v2.Resource, Slug: fmt.Sprintf("%s (With Grant)", name), Purpose: v2.Entitlement_PURPOSE_VALUE_PERMISSION, Resource: resource, + GrantableTo: []*v2.ResourceType{resourceTypeUser, resourceTypeGroup, resourceTypeServerRole}, }) } @@ -129,17 +131,22 @@ func (d *serverSyncer) Grants(ctx context.Context, resource *v2.Resource, pToken if err != nil { return nil, "", nil, err } + + principal := &v2.ResourceId{ + ResourceType: rt.Id, + Resource: strconv.FormatInt(p.PrincipalID, 10), + } + + grantOpts, err := BuildBatonIDGrantOptions(principal, p.PrincipalType, p.PrincipalName) + if err != nil { + return nil, "", nil, err + } + switch p.State { case "G": - ret = append(ret, grTypes.NewGrant(resource, perm, &v2.ResourceId{ - ResourceType: rt.Id, - Resource: strconv.FormatInt(p.PrincipalID, 10), - })) + ret = append(ret, grTypes.NewGrant(resource, perm, principal, grantOpts...)) case "W": - ret = append(ret, grTypes.NewGrant(resource, perm+"-grant", &v2.ResourceId{ - ResourceType: rt.Id, - Resource: strconv.FormatInt(p.PrincipalID, 10), - })) + ret = append(ret, grTypes.NewGrant(resource, perm+"-grant", principal, grantOpts...)) } } } diff --git a/pkg/connector/server_role.go b/pkg/connector/server_role.go index e5829e3..bda2ecd 100644 --- a/pkg/connector/server_role.go +++ b/pkg/connector/server_role.go @@ -10,6 +10,7 @@ import ( v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" "github.com/conductorone/baton-sdk/pkg/annotations" _ "github.com/conductorone/baton-sdk/pkg/annotations" + bid "github.com/conductorone/baton-sdk/pkg/bid" "github.com/conductorone/baton-sdk/pkg/pagination" enTypes "github.com/conductorone/baton-sdk/pkg/types/entitlement" grTypes "github.com/conductorone/baton-sdk/pkg/types/grant" @@ -62,7 +63,7 @@ func (d *serverRolePrincipalSyncer) Entitlements(ctx context.Context, resource * ret = append(ret, enTypes.NewAssignmentEntitlement( resource, "member", - enTypes.WithGrantableTo(resourceTypeUser), + enTypes.WithGrantableTo(resourceTypeUser, resourceTypeGroup, resourceTypeServerRole), )) return ret, "", nil, nil @@ -178,7 +179,12 @@ func (d *serverRolePrincipalSyncer) Grants(ctx context.Context, resource *v2.Res return nil, "", nil, err } - ret = append(ret, grTypes.NewGrant(resource, "member", principalID)) + grantOpts, err := BuildBatonIDGrantOptions(principalID, principal.Type, principal.Name) + if err != nil { + return nil, "", nil, err + } + + ret = append(ret, grTypes.NewGrant(resource, "member", principalID, grantOpts...)) } visited[b.ResourceID()] = true @@ -195,6 +201,45 @@ func (d *serverRolePrincipalSyncer) Grants(ctx context.Context, resource *v2.Res return ret, npt, nil, nil } +func BuildBatonIDGrantOptions(principalID *v2.ResourceId, principalType string, principalName string) ([]grTypes.GrantOption, error) { + grantOpts := []grTypes.GrantOption{} + + switch principalType { + case "G": // Configure BatonID matching for Active Directory groups + gr := &v2.Resource{ + Id: principalID, + } + + ent := enTypes.NewAssignmentEntitlement(gr, "member") + bidEnt, err := bid.MakeBid(ent) + if err != nil { + return nil, err + } + + grantOpts = append(grantOpts, + grTypes.WithAnnotation(&v2.ExternalResourceMatch{ + ResourceType: v2.ResourceType_TRAIT_GROUP, + Key: "downlevel_logon_name", + Value: principalName, + }), + grTypes.WithAnnotation(&v2.GrantExpandable{ + EntitlementIds: []string{bidEnt}, + Shallow: true, + }), + ) + case "U": // Configure BatonID matching for Active Directory users + grantOpts = append(grantOpts, + grTypes.WithAnnotation(&v2.ExternalResourceMatch{ + ResourceType: v2.ResourceType_TRAIT_USER, + Key: "downlevel_logon_name", + Value: principalName, + }), + ) + } + + return grantOpts, nil +} + func (d *serverRolePrincipalSyncer) Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) { var err error