fleet/server/service/conditional_access_microsoft.go
Victor Lyuboslavsky 4dfdc870bd
slog migration: service layer + subsystem libraries (#40661)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #40540 

# Checklist for submitter

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
  - Changes present in previous PR

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Updated internal logging infrastructure to improve consistency and
maintainability across the application.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-26 17:40:46 -06:00

248 lines
9.2 KiB
Go

package service
import (
"context"
"errors"
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
)
type conditionalAccessMicrosoftCreateRequest struct {
// MicrosoftTenantID holds the Entra tenant ID.
MicrosoftTenantID string `json:"microsoft_tenant_id"`
}
type conditionalAccessMicrosoftCreateResponse struct {
// MicrosoftAuthenticationURL holds the URL to redirect the admin to consent access
// to the tenant to Fleet's multi-tenant application.
MicrosoftAuthenticationURL string `json:"microsoft_authentication_url"`
Err error `json:"error,omitempty"`
}
func (r conditionalAccessMicrosoftCreateResponse) Error() error { return r.Err }
func conditionalAccessMicrosoftCreateEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
req := request.(*conditionalAccessMicrosoftCreateRequest)
adminConsentURL, err := svc.ConditionalAccessMicrosoftCreateIntegration(ctx, req.MicrosoftTenantID)
if err != nil {
return conditionalAccessMicrosoftCreateResponse{Err: err}, nil
}
return conditionalAccessMicrosoftCreateResponse{
MicrosoftAuthenticationURL: adminConsentURL,
}, nil
}
func (svc *Service) ConditionalAccessMicrosoftCreateIntegration(ctx context.Context, tenantID string) (adminConsentURL string, err error) {
// 0. Check user is authorized to create an integration.
if err := svc.authz.Authorize(ctx, &fleet.ConditionalAccessMicrosoftIntegration{}, fleet.ActionWrite); err != nil {
return "", ctxerr.Wrap(ctx, err, "failed to authorize")
}
if !svc.config.MicrosoftCompliancePartner.IsSet() {
return "", &fleet.BadRequestError{Message: "microsoft conditional access configuration not set"}
}
// Load current integration, if any.
existingIntegration, err := svc.ConditionalAccessMicrosoftGet(ctx)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "failed to load the integration")
}
switch {
case existingIntegration != nil && existingIntegration.TenantID == tenantID:
// Nothing to do, integration with same tenant ID has already been created.
// Retrieve settings of the integration to get the admin consent URL.
getResponse, err := svc.conditionalAccessMicrosoftProxy.Get(ctx, existingIntegration.TenantID, existingIntegration.ProxyServerSecret)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "failed to get the integration settings")
}
return getResponse.AdminConsentURL, nil
case existingIntegration != nil && existingIntegration.SetupDone:
return "", &fleet.BadRequestError{Message: "integration already setup"}
}
//
// At this point we have two scenarios:
// - There's no integration yet, so we need to create a new one.
// - There's an integration already with a different TenantID and has not been setup.
//
// Create integration on the proxy.
proxyCreateResponse, err := svc.conditionalAccessMicrosoftProxy.Create(ctx, tenantID)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "failed to create integration in proxy")
}
// Create integration in datastore.
if err := svc.ds.ConditionalAccessMicrosoftCreateIntegration(ctx, proxyCreateResponse.TenantID, proxyCreateResponse.Secret); err != nil {
return "", ctxerr.Wrap(ctx, err, "failed to create integration in datastore")
}
// Retrieve settings of the integration to get the admin consent URL.
getResponse, err := svc.conditionalAccessMicrosoftProxy.Get(ctx, proxyCreateResponse.TenantID, proxyCreateResponse.Secret)
if err != nil {
return "", ctxerr.Wrap(ctx, err, "failed to get the integration settings")
}
return getResponse.AdminConsentURL, nil
}
type conditionalAccessMicrosoftConfirmRequest struct{}
type conditionalAccessMicrosoftConfirmResponse struct {
ConfigurationCompleted bool `json:"configuration_completed"`
SetupError string `json:"setup_error"`
Err error `json:"error,omitempty"`
}
func (r conditionalAccessMicrosoftConfirmResponse) Error() error { return r.Err }
func conditionalAccessMicrosoftConfirmEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
_ = request.(*conditionalAccessMicrosoftConfirmRequest)
configurationCompleted, setupError, err := svc.ConditionalAccessMicrosoftConfirm(ctx)
if err != nil {
return conditionalAccessMicrosoftConfirmResponse{Err: err}, nil
}
return conditionalAccessMicrosoftConfirmResponse{
ConfigurationCompleted: configurationCompleted,
SetupError: setupError,
}, nil
}
func (svc *Service) ConditionalAccessMicrosoftConfirm(ctx context.Context) (configurationCompleted bool, setupError string, err error) {
// Check user is authorized to write integrations.
if err := svc.authz.Authorize(ctx, &fleet.ConditionalAccessMicrosoftIntegration{}, fleet.ActionWrite); err != nil {
return false, "", ctxerr.Wrap(ctx, err, "failed to authorize")
}
if !svc.config.MicrosoftCompliancePartner.IsSet() {
return false, "", &fleet.BadRequestError{Message: "microsoft conditional access configuration not set"}
}
// Load current integration.
integration, err := svc.ds.ConditionalAccessMicrosoftGet(ctx)
if err != nil {
return false, "", ctxerr.Wrap(ctx, err, "failed to load the integration")
}
if integration.SetupDone {
return true, "", nil
}
getResponse, err := svc.conditionalAccessMicrosoftProxy.Get(ctx, integration.TenantID, integration.ProxyServerSecret)
if err != nil {
svc.logger.ErrorContext(ctx, "failed to get integration settings from proxy", "err", err)
return false, "", nil
}
if !getResponse.SetupDone {
var setupError string
if getResponse.SetupError != nil {
svc.logger.ErrorContext(ctx, "setup is not done", "setup_error", getResponse.SetupError)
setupError = *getResponse.SetupError
}
return false, setupError, nil
}
if err := svc.ds.ConditionalAccessMicrosoftMarkSetupDone(ctx); err != nil {
return false, "", ctxerr.Wrap(ctx, err, "failed to mark setup_done=true")
}
if err := svc.NewActivity(
ctx,
authz.UserFromContext(ctx),
fleet.ActivityTypeAddedConditionalAccessIntegrationMicrosoft{},
); err != nil {
return false, "", ctxerr.Wrap(ctx, err, "create activity for conditional access integration microsoft")
}
return true, "", nil
}
type conditionalAccessMicrosoftDeleteRequest struct{}
type conditionalAccessMicrosoftDeleteResponse struct {
Err error `json:"error,omitempty"`
}
func (r conditionalAccessMicrosoftDeleteResponse) Error() error { return r.Err }
func conditionalAccessMicrosoftDeleteEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
_ = request.(*conditionalAccessMicrosoftDeleteRequest)
if err := svc.ConditionalAccessMicrosoftDelete(ctx); err != nil {
return conditionalAccessMicrosoftDeleteResponse{Err: err}, nil
}
return conditionalAccessMicrosoftDeleteResponse{}, nil
}
func (svc *Service) ConditionalAccessMicrosoftDelete(ctx context.Context) error {
// Check user is authorized to delete an integration.
if err := svc.authz.Authorize(ctx, &fleet.ConditionalAccessMicrosoftIntegration{}, fleet.ActionWrite); err != nil {
return ctxerr.Wrap(ctx, err, "failed to authorize")
}
if !svc.config.MicrosoftCompliancePartner.IsSet() {
return &fleet.BadRequestError{Message: "microsoft conditional access configuration not set"}
}
// Load current integration.
integration, err := svc.ds.ConditionalAccessMicrosoftGet(ctx)
if err != nil {
if fleet.IsNotFound(err) {
return &fleet.BadRequestError{Message: "integration not found"}
}
return ctxerr.Wrap(ctx, err, "failed to load the integration")
}
// Delete integration on the proxy.
deleteResponse, err := svc.conditionalAccessMicrosoftProxy.Delete(ctx, integration.TenantID, integration.ProxyServerSecret)
if err != nil {
if fleet.IsNotFound(err) {
// In case there's an issue on the Proxy database we want to make sure to
// allow deleting the integration in Fleet, so we continue.
svc.logger.WarnContext(ctx, "delete returned not found, continuing...")
} else {
return ctxerr.Wrap(ctx, err, "failed to delete the integration on the proxy")
}
} else if deleteResponse.Error != "" {
return ctxerr.Wrap(ctx, errors.New(deleteResponse.Error), "delete on the proxy failed")
}
// Delete integration in datastore.
if err := svc.ds.ConditionalAccessMicrosoftDelete(ctx); err != nil {
return ctxerr.Wrap(ctx, err, "failed to delete integration in datastore")
}
if err := svc.NewActivity(
ctx,
authz.UserFromContext(ctx),
fleet.ActivityTypeDeletedConditionalAccessIntegrationMicrosoft{},
); err != nil {
return ctxerr.Wrap(ctx, err, "create activity for deletion of conditional access integration microsoft")
}
return nil
}
func (svc *Service) ConditionalAccessMicrosoftGet(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) {
// Check user is authorized to read app config (which is where expose integration information)
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
return nil, ctxerr.Wrap(ctx, err, "failed to authorize")
}
if !svc.config.MicrosoftCompliancePartner.IsSet() {
return nil, nil
}
// Load current integration.
integration, err := svc.ds.ConditionalAccessMicrosoftGet(ctx)
if err != nil {
if fleet.IsNotFound(err) {
return nil, nil
}
return nil, ctxerr.Wrap(ctx, err, "failed to load the integration")
}
return integration, nil
}