From 3f83af8cbbe28aef23f2e796be965da5add5968c Mon Sep 17 00:00:00 2001 From: omris94 <46892443+omris94@users.noreply.github.com> Date: Tue, 10 Dec 2024 19:33:30 +0200 Subject: [PATCH] Enhance discovered intents by including details on identity resolution methods (#255) --- src/mapper/pkg/cloudclient/generated.go | 50 +- src/mapper/pkg/cloudclient/schema.graphql | 81 ++- src/mapper/pkg/clouduploader/cloud_upload.go | 21 + src/mapper/pkg/graph/generated/generated.go | 607 +++++++++++++++++- src/mapper/pkg/graph/model/models_gen.go | 31 +- src/mapper/pkg/kubefinder/kubefinder.go | 17 +- src/mapper/pkg/resolvers/helpers.go | 28 +- .../pkg/resolvers/schema.helpers.resolvers.go | 78 ++- src/mappergraphql/schema.graphql | 13 + 9 files changed, 863 insertions(+), 63 deletions(-) diff --git a/src/mapper/pkg/cloudclient/generated.go b/src/mapper/pkg/cloudclient/generated.go index 39e8da5f..c8ea5576 100644 --- a/src/mapper/pkg/cloudclient/generated.go +++ b/src/mapper/pkg/cloudclient/generated.go @@ -205,25 +205,28 @@ func (v *IntOrStringInput) GetIntVal() nilable.Nilable[int] { return v.IntVal } func (v *IntOrStringInput) GetStrVal() nilable.Nilable[string] { return v.StrVal } type IntentInput struct { - Namespace *string `json:"namespace"` - ClientName *string `json:"clientName"` - ClientWorkloadKind *string `json:"clientWorkloadKind"` - ServerName *string `json:"serverName"` - ServerWorkloadKind *string `json:"serverWorkloadKind"` - ServerAlias *ServerAliasInput `json:"serverAlias"` - ServerNamespace *string `json:"serverNamespace"` - Type *IntentType `json:"type"` - Topics []*KafkaConfigInput `json:"topics"` - Resources []*HTTPConfigInput `json:"resources"` - DatabaseResources []*DatabaseConfigInput `json:"databaseResources"` - AwsActions []*string `json:"awsActions"` - AzureRoles []*string `json:"azureRoles"` - AzureActions []*string `json:"azureActions"` - AzureDataActions []*string `json:"azureDataActions"` - AzureKeyVaultPolicy *AzureKeyVaultPolicyInput `json:"azureKeyVaultPolicy"` - GcpPermissions []*string `json:"gcpPermissions"` - Internet *InternetConfigInput `json:"internet"` - Status *IntentStatusInput `json:"status"` + Namespace *string `json:"namespace"` + ClientName *string `json:"clientName"` + ClientResolutionData *string `json:"clientResolutionData"` + ClientWorkloadKind *string `json:"clientWorkloadKind"` + ServerName *string `json:"serverName"` + ServerResolutionData *string `json:"serverResolutionData"` + ServerWorkloadKind *string `json:"serverWorkloadKind"` + ServerAlias *ServerAliasInput `json:"serverAlias"` + ServerNamespace *string `json:"serverNamespace"` + Type *IntentType `json:"type"` + Topics []*KafkaConfigInput `json:"topics"` + Resources []*HTTPConfigInput `json:"resources"` + DatabaseResources []*DatabaseConfigInput `json:"databaseResources"` + AwsActions []*string `json:"awsActions"` + AzureRoles []*string `json:"azureRoles"` + AzureActions []*string `json:"azureActions"` + AzureDataActions []*string `json:"azureDataActions"` + AzureKeyVaultPolicy *AzureKeyVaultPolicyInput `json:"azureKeyVaultPolicy"` + GcpPermissions []*string `json:"gcpPermissions"` + Internet *InternetConfigInput `json:"internet"` + Status *IntentStatusInput `json:"status"` + ResolutionData *string `json:"resolutionData"` } // GetNamespace returns IntentInput.Namespace, and is useful for accessing the field via an interface. @@ -232,12 +235,18 @@ func (v *IntentInput) GetNamespace() *string { return v.Namespace } // GetClientName returns IntentInput.ClientName, and is useful for accessing the field via an interface. func (v *IntentInput) GetClientName() *string { return v.ClientName } +// GetClientResolutionData returns IntentInput.ClientResolutionData, and is useful for accessing the field via an interface. +func (v *IntentInput) GetClientResolutionData() *string { return v.ClientResolutionData } + // GetClientWorkloadKind returns IntentInput.ClientWorkloadKind, and is useful for accessing the field via an interface. func (v *IntentInput) GetClientWorkloadKind() *string { return v.ClientWorkloadKind } // GetServerName returns IntentInput.ServerName, and is useful for accessing the field via an interface. func (v *IntentInput) GetServerName() *string { return v.ServerName } +// GetServerResolutionData returns IntentInput.ServerResolutionData, and is useful for accessing the field via an interface. +func (v *IntentInput) GetServerResolutionData() *string { return v.ServerResolutionData } + // GetServerWorkloadKind returns IntentInput.ServerWorkloadKind, and is useful for accessing the field via an interface. func (v *IntentInput) GetServerWorkloadKind() *string { return v.ServerWorkloadKind } @@ -285,6 +294,9 @@ func (v *IntentInput) GetInternet() *InternetConfigInput { return v.Internet } // GetStatus returns IntentInput.Status, and is useful for accessing the field via an interface. func (v *IntentInput) GetStatus() *IntentStatusInput { return v.Status } +// GetResolutionData returns IntentInput.ResolutionData, and is useful for accessing the field via an interface. +func (v *IntentInput) GetResolutionData() *string { return v.ResolutionData } + type IntentStatusInput struct { IstioStatus *IstioStatusInput `json:"istioStatus"` } diff --git a/src/mapper/pkg/cloudclient/schema.graphql b/src/mapper/pkg/cloudclient/schema.graphql index 928428a5..c015acc7 100644 --- a/src/mapper/pkg/cloudclient/schema.graphql +++ b/src/mapper/pkg/cloudclient/schema.graphql @@ -252,6 +252,11 @@ type AzureResource { resource: String! } +type BasicEntity { + id: ID! + name: String! +} + """The `Boolean` scalar type represents `true` or `false`.""" scalar Boolean @@ -598,6 +603,7 @@ enum EdgeAccessStatusReason { INTENTS_OPERATOR_NOT_ENFORCING_MISSING_APPLIED_INTENT INTENTS_OPERATOR_NOT_ENFORCING_KAFKA_INTENTS_NOT_REQUIRED_FOR_TOPIC MISSING_APPLIED_INTENT + MISSING_APPLIED_CLOUD_RESOURCE_INTENT NOT_IN_PROTECTED_SERVICES INTENTS_OPERATOR_NEVER_CONNECTED NETWORK_MAPPER_NEVER_CONNECTED @@ -705,15 +711,35 @@ input ExternallyManagedPolicyWorkloadInput { type FeatureFlags { isCloudServicesDetectionEnabled: Boolean isCloudSecurityEnabled: Boolean + useClientIntentsV2: Boolean +} + +type Finding { + hash: String! + service: BasicEntity! + serviceNamespace: BasicEntity + server: BasicEntity! + cluster: BasicEntity! + intentsOperatorState: IntentsOperatorState + clusterRelatedServices: [Service!] + reason: String! + status: FindingStatus! + ignoredReason: String + type: FindingType! } -"""NEW findings""" enum FindingStatus { OPEN RESOLVED IGNORED } +type FindingStatusHistory { + timestamp: Time! + status: FindingStatus! + reason: String +} + type FindingSummary { standard: RegulationStandard! codeLabel: String! @@ -728,6 +754,11 @@ type FindingSummary { requirements: [FindingSummary!] } +type FindingSummaryResponse { + findingSummaries: [FindingSummaryV2!]! + timestamp: Time! +} + type FindingSummaryV2 { standard: RegulationStandard! code: RegulationCode! @@ -736,6 +767,7 @@ type FindingSummaryV2 { description: String! validationDescription: String status: FindingStatus! + ignoredReason: String serviceTotalCount: Int! serviceOpenCount: Int! clusterTotalCount: Int! @@ -743,6 +775,12 @@ type FindingSummaryV2 { requirements: [FindingSummaryV2!] } +"""NEW findings""" +enum FindingType { + SERVICE + CLUSTER +} + """The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).""" scalar Float @@ -942,6 +980,7 @@ input InputAccessLogFilter { input InputFeatureFlags { isCloudServicesDetectionEnabled: Boolean isCloudSecurityEnabled: Boolean + useClientIntentsV2: Boolean } """ Findings filter """ @@ -960,6 +999,10 @@ input InputFindingFilter { regulationIds: InputIDFilterValue """ Findings filter """ environmentIds: InputIDFilterValue +""" Findings filter """ + findingTypes: InputIDFilterValue +""" Findings filter """ + hashes: InputIDFilterValue } input InputIDFilterValue { @@ -1124,8 +1167,10 @@ type Intent { input IntentInput { namespace: String! clientName: String! + clientResolutionData: String clientWorkloadKind: String serverName: String! + serverResolutionData: String serverWorkloadKind: String serverAlias: ServerAliasInput serverNamespace: String @@ -1141,6 +1186,7 @@ input IntentInput { gcpPermissions: [String!] internet: InternetConfigInput status: IntentStatusInput + resolutionData: String } type IntentStatus { @@ -1571,6 +1617,16 @@ type Mutation { component: Component errors: [Error!]! ): Boolean! + setFindingsIgnoredByHashes( + hashes: [String!]! + ignored: Boolean! + reason: String + ): Boolean! + setFindingsIgnoredByControlIds( + controlIds: [RegulationCode!]! + ignored: Boolean! + reason: String + ): Boolean! """Create a new generic integration""" createGenericIntegration( name: String! @@ -1765,11 +1821,6 @@ type Mutation { id: ID! userId: ID! ): ID! -"""Ignore domain for organization""" - ignoreOrganizationDomain( - id: ID! - domain: String! - ): Organization! reportProtectedServicesSnapshot( namespace: String! services: [ProtectedServiceInput!]! @@ -1944,6 +1995,13 @@ type Query { enableInternetIntents: Boolean featureFlags: InputFeatureFlags ): ServiceClientIntents! +""" Get service ClientIntents by filter """ + clientIntents( + filter: InputServiceFilter! + lastSeenAfter: Time + clusterIds: [ID!] + featureFlags: InputFeatureFlags + ): [ClientIntentsFileRepresentation!]! """Get access log""" accessLog( filter: InputAccessLogFilter @@ -1984,7 +2042,16 @@ type Query { """NEW findings""" findingSummary( filter: InputFindingFilter - ): [FindingSummaryV2!]! + ): FindingSummaryResponse! + findingsV2( + filter: InputFindingFilter + ): [Finding!]! + findingStatusHistory( + hash: String! + ): [FindingStatusHistory!]! + findingSummaryStatusHistory( + leafControlIDs: [RegulationCode!]! + ): [FindingStatusHistory!]! """List integrations""" integrations( name: String diff --git a/src/mapper/pkg/clouduploader/cloud_upload.go b/src/mapper/pkg/clouduploader/cloud_upload.go index 4d5ecd22..998ca2ab 100644 --- a/src/mapper/pkg/clouduploader/cloud_upload.go +++ b/src/mapper/pkg/clouduploader/cloud_upload.go @@ -2,6 +2,7 @@ package clouduploader import ( "context" + "encoding/json" "github.com/otterize/intents-operator/src/shared/errors" "github.com/otterize/intents-operator/src/shared/serviceidresolver/serviceidentity" "github.com/otterize/network-mapper/src/mapper/pkg/awsintentsholder" @@ -62,6 +63,26 @@ func (c *CloudUploader) NotifyIntents(ctx context.Context, intents []intentsstor if intent.Intent.Server.KubernetesService != nil { toCloud.Intent.ServerAlias = &cloudclient.ServerAliasInput{Name: intent.Intent.Server.KubernetesService, Kind: lo.ToPtr(serviceidentity.KindService)} } + if intent.Intent.Client.ResolutionData != nil && !lo.IsEmpty(*intent.Intent.Client.ResolutionData) { + jsonStr, err := json.Marshal(intent.Intent.Client.ResolutionData) + if err != nil { + logrus.WithError(err).Error("Failed to marshal client resolution data") + } else { + toCloud.Intent.ClientResolutionData = lo.ToPtr(string(jsonStr)) + } + + } + if intent.Intent.Server.ResolutionData != nil && !lo.IsEmpty(*intent.Intent.Server.ResolutionData) { + jsonStr, err := json.Marshal(intent.Intent.Server.ResolutionData) + if err != nil { + logrus.WithError(err).Error("Failed to marshal server resolution data") + } else { + toCloud.Intent.ServerResolutionData = lo.ToPtr(string(jsonStr)) + } + } + if intent.Intent.ResolutionData != nil { + toCloud.Intent.ResolutionData = lo.ToPtr(*intent.Intent.ResolutionData) + } // debug log all the fields of intent input one by one with their values logrus.Debugf("intent ClientName: %s\t Namespace: %s\t ServerName: %s\t ServerNamespace: %s\t ClientWorkloadKind: %s\t ServerWorkloadKind: %s\t ServerAlias: %v", lo.FromPtr(toCloud.Intent.ClientName), lo.FromPtr(toCloud.Intent.Namespace), lo.FromPtr(toCloud.Intent.ServerName), lo.FromPtr(toCloud.Intent.ServerNamespace), lo.FromPtr(toCloud.Intent.ClientWorkloadKind), lo.FromPtr(toCloud.Intent.ServerWorkloadKind), lo.FromPtr(toCloud.Intent.ServerAlias)) diff --git a/src/mapper/pkg/graph/generated/generated.go b/src/mapper/pkg/graph/generated/generated.go index e0b3ecba..b2486eaf 100644 --- a/src/mapper/pkg/graph/generated/generated.go +++ b/src/mapper/pkg/graph/generated/generated.go @@ -58,13 +58,25 @@ type ComplexityRoot struct { Path func(childComplexity int) int } + IdentityResolutionData struct { + ExtraInfo func(childComplexity int) int + Host func(childComplexity int) int + IsService func(childComplexity int) int + LastSeen func(childComplexity int) int + PodHostname func(childComplexity int) int + Port func(childComplexity int) int + ProcfsHostname func(childComplexity int) int + Uptime func(childComplexity int) int + } + Intent struct { - AwsActions func(childComplexity int) int - Client func(childComplexity int) int - HTTPResources func(childComplexity int) int - KafkaTopics func(childComplexity int) int - Server func(childComplexity int) int - Type func(childComplexity int) int + AwsActions func(childComplexity int) int + Client func(childComplexity int) int + HTTPResources func(childComplexity int) int + KafkaTopics func(childComplexity int) int + ResolutionData func(childComplexity int) int + Server func(childComplexity int) int + Type func(childComplexity int) int } KafkaConfig struct { @@ -89,6 +101,7 @@ type ComplexityRoot struct { Name func(childComplexity int) int Namespace func(childComplexity int) int PodOwnerKind func(childComplexity int) int + ResolutionData func(childComplexity int) int } PodLabel struct { @@ -178,6 +191,62 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.HttpResource.Path(childComplexity), true + case "IdentityResolutionData.extraInfo": + if e.complexity.IdentityResolutionData.ExtraInfo == nil { + break + } + + return e.complexity.IdentityResolutionData.ExtraInfo(childComplexity), true + + case "IdentityResolutionData.host": + if e.complexity.IdentityResolutionData.Host == nil { + break + } + + return e.complexity.IdentityResolutionData.Host(childComplexity), true + + case "IdentityResolutionData.isService": + if e.complexity.IdentityResolutionData.IsService == nil { + break + } + + return e.complexity.IdentityResolutionData.IsService(childComplexity), true + + case "IdentityResolutionData.lastSeen": + if e.complexity.IdentityResolutionData.LastSeen == nil { + break + } + + return e.complexity.IdentityResolutionData.LastSeen(childComplexity), true + + case "IdentityResolutionData.podHostname": + if e.complexity.IdentityResolutionData.PodHostname == nil { + break + } + + return e.complexity.IdentityResolutionData.PodHostname(childComplexity), true + + case "IdentityResolutionData.port": + if e.complexity.IdentityResolutionData.Port == nil { + break + } + + return e.complexity.IdentityResolutionData.Port(childComplexity), true + + case "IdentityResolutionData.procfsHostname": + if e.complexity.IdentityResolutionData.ProcfsHostname == nil { + break + } + + return e.complexity.IdentityResolutionData.ProcfsHostname(childComplexity), true + + case "IdentityResolutionData.uptime": + if e.complexity.IdentityResolutionData.Uptime == nil { + break + } + + return e.complexity.IdentityResolutionData.Uptime(childComplexity), true + case "Intent.awsActions": if e.complexity.Intent.AwsActions == nil { break @@ -206,6 +275,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Intent.KafkaTopics(childComplexity), true + case "Intent.resolutionData": + if e.complexity.Intent.ResolutionData == nil { + break + } + + return e.complexity.Intent.ResolutionData(childComplexity), true + case "Intent.server": if e.complexity.Intent.Server == nil { break @@ -360,6 +436,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.OtterizeServiceIdentity.PodOwnerKind(childComplexity), true + case "OtterizeServiceIdentity.resolutionData": + if e.complexity.OtterizeServiceIdentity.ResolutionData == nil { + break + } + + return e.complexity.OtterizeServiceIdentity.ResolutionData(childComplexity), true + case "PodLabel.key": if e.complexity.PodLabel.Key == nil { break @@ -577,10 +660,22 @@ type GroupVersionKind { kind: String! } +type IdentityResolutionData { + host: String + podHostname: String + procfsHostname: String + port: Int + isService: Boolean + uptime: String + lastSeen: String + extraInfo: String +} + type OtterizeServiceIdentity { name: String! namespace: String! labels: [PodLabel!] + resolutionData: IdentityResolutionData """ If the service identity was resolved from a pod owner, the GroupVersionKind of the pod owner. """ @@ -639,6 +734,7 @@ type Intent { client: OtterizeServiceIdentity! server: OtterizeServiceIdentity! type: IntentType + resolutionData: String kafkaTopics: [KafkaConfig!] httpResources: [HttpResource!] awsActions: [String!] @@ -1197,6 +1293,334 @@ func (ec *executionContext) fieldContext_HttpResource_methods(ctx context.Contex return fc, nil } +func (ec *executionContext) _IdentityResolutionData_host(ctx context.Context, field graphql.CollectedField, obj *model.IdentityResolutionData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityResolutionData_host(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Host, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityResolutionData_host(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityResolutionData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _IdentityResolutionData_podHostname(ctx context.Context, field graphql.CollectedField, obj *model.IdentityResolutionData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityResolutionData_podHostname(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.PodHostname, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityResolutionData_podHostname(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityResolutionData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _IdentityResolutionData_procfsHostname(ctx context.Context, field graphql.CollectedField, obj *model.IdentityResolutionData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityResolutionData_procfsHostname(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ProcfsHostname, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityResolutionData_procfsHostname(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityResolutionData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _IdentityResolutionData_port(ctx context.Context, field graphql.CollectedField, obj *model.IdentityResolutionData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityResolutionData_port(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Port, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*int64) + fc.Result = res + return ec.marshalOInt2ᚖint64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityResolutionData_port(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityResolutionData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _IdentityResolutionData_isService(ctx context.Context, field graphql.CollectedField, obj *model.IdentityResolutionData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityResolutionData_isService(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.IsService, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*bool) + fc.Result = res + return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityResolutionData_isService(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityResolutionData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _IdentityResolutionData_uptime(ctx context.Context, field graphql.CollectedField, obj *model.IdentityResolutionData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityResolutionData_uptime(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Uptime, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityResolutionData_uptime(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityResolutionData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _IdentityResolutionData_lastSeen(ctx context.Context, field graphql.CollectedField, obj *model.IdentityResolutionData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityResolutionData_lastSeen(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.LastSeen, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityResolutionData_lastSeen(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityResolutionData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _IdentityResolutionData_extraInfo(ctx context.Context, field graphql.CollectedField, obj *model.IdentityResolutionData) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityResolutionData_extraInfo(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ExtraInfo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityResolutionData_extraInfo(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityResolutionData", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Intent_client(ctx context.Context, field graphql.CollectedField, obj *model.Intent) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Intent_client(ctx, field) if err != nil { @@ -1242,6 +1666,8 @@ func (ec *executionContext) fieldContext_Intent_client(ctx context.Context, fiel return ec.fieldContext_OtterizeServiceIdentity_namespace(ctx, field) case "labels": return ec.fieldContext_OtterizeServiceIdentity_labels(ctx, field) + case "resolutionData": + return ec.fieldContext_OtterizeServiceIdentity_resolutionData(ctx, field) case "podOwnerKind": return ec.fieldContext_OtterizeServiceIdentity_podOwnerKind(ctx, field) case "kubernetesService": @@ -1298,6 +1724,8 @@ func (ec *executionContext) fieldContext_Intent_server(ctx context.Context, fiel return ec.fieldContext_OtterizeServiceIdentity_namespace(ctx, field) case "labels": return ec.fieldContext_OtterizeServiceIdentity_labels(ctx, field) + case "resolutionData": + return ec.fieldContext_OtterizeServiceIdentity_resolutionData(ctx, field) case "podOwnerKind": return ec.fieldContext_OtterizeServiceIdentity_podOwnerKind(ctx, field) case "kubernetesService": @@ -1350,6 +1778,47 @@ func (ec *executionContext) fieldContext_Intent_type(ctx context.Context, field return fc, nil } +func (ec *executionContext) _Intent_resolutionData(ctx context.Context, field graphql.CollectedField, obj *model.Intent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Intent_resolutionData(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ResolutionData, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Intent_resolutionData(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Intent", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Intent_kafkaTopics(ctx context.Context, field graphql.CollectedField, obj *model.Intent) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Intent_kafkaTopics(ctx, field) if err != nil { @@ -2134,6 +2603,65 @@ func (ec *executionContext) fieldContext_OtterizeServiceIdentity_labels(ctx cont return fc, nil } +func (ec *executionContext) _OtterizeServiceIdentity_resolutionData(ctx context.Context, field graphql.CollectedField, obj *model.OtterizeServiceIdentity) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_OtterizeServiceIdentity_resolutionData(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ResolutionData, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*model.IdentityResolutionData) + fc.Result = res + return ec.marshalOIdentityResolutionData2ᚖgithubᚗcomᚋotterizeᚋnetworkᚑmapperᚋsrcᚋmapperᚋpkgᚋgraphᚋmodelᚐIdentityResolutionData(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_OtterizeServiceIdentity_resolutionData(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "OtterizeServiceIdentity", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "host": + return ec.fieldContext_IdentityResolutionData_host(ctx, field) + case "podHostname": + return ec.fieldContext_IdentityResolutionData_podHostname(ctx, field) + case "procfsHostname": + return ec.fieldContext_IdentityResolutionData_procfsHostname(ctx, field) + case "port": + return ec.fieldContext_IdentityResolutionData_port(ctx, field) + case "isService": + return ec.fieldContext_IdentityResolutionData_isService(ctx, field) + case "uptime": + return ec.fieldContext_IdentityResolutionData_uptime(ctx, field) + case "lastSeen": + return ec.fieldContext_IdentityResolutionData_lastSeen(ctx, field) + case "extraInfo": + return ec.fieldContext_IdentityResolutionData_extraInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type IdentityResolutionData", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _OtterizeServiceIdentity_podOwnerKind(ctx context.Context, field graphql.CollectedField, obj *model.OtterizeServiceIdentity) (ret graphql.Marshaler) { fc, err := ec.fieldContext_OtterizeServiceIdentity_podOwnerKind(ctx, field) if err != nil { @@ -2418,6 +2946,8 @@ func (ec *executionContext) fieldContext_Query_intents(ctx context.Context, fiel return ec.fieldContext_Intent_server(ctx, field) case "type": return ec.fieldContext_Intent_type(ctx, field) + case "resolutionData": + return ec.fieldContext_Intent_resolutionData(ctx, field) case "kafkaTopics": return ec.fieldContext_Intent_kafkaTopics(ctx, field) case "httpResources": @@ -2660,6 +3190,8 @@ func (ec *executionContext) fieldContext_ServiceIntents_client(ctx context.Conte return ec.fieldContext_OtterizeServiceIdentity_namespace(ctx, field) case "labels": return ec.fieldContext_OtterizeServiceIdentity_labels(ctx, field) + case "resolutionData": + return ec.fieldContext_OtterizeServiceIdentity_resolutionData(ctx, field) case "podOwnerKind": return ec.fieldContext_OtterizeServiceIdentity_podOwnerKind(ctx, field) case "kubernetesService": @@ -2716,6 +3248,8 @@ func (ec *executionContext) fieldContext_ServiceIntents_intents(ctx context.Cont return ec.fieldContext_OtterizeServiceIdentity_namespace(ctx, field) case "labels": return ec.fieldContext_OtterizeServiceIdentity_labels(ctx, field) + case "resolutionData": + return ec.fieldContext_OtterizeServiceIdentity_resolutionData(ctx, field) case "podOwnerKind": return ec.fieldContext_OtterizeServiceIdentity_podOwnerKind(ctx, field) case "kubernetesService": @@ -5094,6 +5628,56 @@ func (ec *executionContext) _HttpResource(ctx context.Context, sel ast.Selection return out } +var identityResolutionDataImplementors = []string{"IdentityResolutionData"} + +func (ec *executionContext) _IdentityResolutionData(ctx context.Context, sel ast.SelectionSet, obj *model.IdentityResolutionData) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, identityResolutionDataImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("IdentityResolutionData") + case "host": + out.Values[i] = ec._IdentityResolutionData_host(ctx, field, obj) + case "podHostname": + out.Values[i] = ec._IdentityResolutionData_podHostname(ctx, field, obj) + case "procfsHostname": + out.Values[i] = ec._IdentityResolutionData_procfsHostname(ctx, field, obj) + case "port": + out.Values[i] = ec._IdentityResolutionData_port(ctx, field, obj) + case "isService": + out.Values[i] = ec._IdentityResolutionData_isService(ctx, field, obj) + case "uptime": + out.Values[i] = ec._IdentityResolutionData_uptime(ctx, field, obj) + case "lastSeen": + out.Values[i] = ec._IdentityResolutionData_lastSeen(ctx, field, obj) + case "extraInfo": + out.Values[i] = ec._IdentityResolutionData_extraInfo(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var intentImplementors = []string{"Intent"} func (ec *executionContext) _Intent(ctx context.Context, sel ast.SelectionSet, obj *model.Intent) graphql.Marshaler { @@ -5117,6 +5701,8 @@ func (ec *executionContext) _Intent(ctx context.Context, sel ast.SelectionSet, o } case "type": out.Values[i] = ec._Intent_type(ctx, field, obj) + case "resolutionData": + out.Values[i] = ec._Intent_resolutionData(ctx, field, obj) case "kafkaTopics": out.Values[i] = ec._Intent_kafkaTopics(ctx, field, obj) case "httpResources": @@ -5308,6 +5894,8 @@ func (ec *executionContext) _OtterizeServiceIdentity(ctx context.Context, sel as } case "labels": out.Values[i] = ec._OtterizeServiceIdentity_labels(ctx, field, obj) + case "resolutionData": + out.Values[i] = ec._OtterizeServiceIdentity_resolutionData(ctx, field, obj) case "podOwnerKind": out.Values[i] = ec._OtterizeServiceIdentity_podOwnerKind(ctx, field, obj) case "kubernetesService": @@ -6746,6 +7334,13 @@ func (ec *executionContext) marshalOHttpResource2ᚕgithubᚗcomᚋotterizeᚋne return ret } +func (ec *executionContext) marshalOIdentityResolutionData2ᚖgithubᚗcomᚋotterizeᚋnetworkᚑmapperᚋsrcᚋmapperᚋpkgᚋgraphᚋmodelᚐIdentityResolutionData(ctx context.Context, sel ast.SelectionSet, v *model.IdentityResolutionData) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._IdentityResolutionData(ctx, sel, v) +} + func (ec *executionContext) unmarshalOInt2ᚖint64(ctx context.Context, v interface{}) (*int64, error) { if v == nil { return nil, nil diff --git a/src/mapper/pkg/graph/model/models_gen.go b/src/mapper/pkg/graph/model/models_gen.go index f23c0425..458c1ab6 100644 --- a/src/mapper/pkg/graph/model/models_gen.go +++ b/src/mapper/pkg/graph/model/models_gen.go @@ -50,13 +50,25 @@ type HTTPResource struct { Methods []HTTPMethod `json:"methods,omitempty"` } +type IdentityResolutionData struct { + Host *string `json:"host,omitempty"` + PodHostname *string `json:"podHostname,omitempty"` + ProcfsHostname *string `json:"procfsHostname,omitempty"` + Port *int64 `json:"port,omitempty"` + IsService *bool `json:"isService,omitempty"` + Uptime *string `json:"uptime,omitempty"` + LastSeen *string `json:"lastSeen,omitempty"` + ExtraInfo *string `json:"extraInfo,omitempty"` +} + type Intent struct { - Client *OtterizeServiceIdentity `json:"client"` - Server *OtterizeServiceIdentity `json:"server"` - Type *IntentType `json:"type,omitempty"` - KafkaTopics []KafkaConfig `json:"kafkaTopics,omitempty"` - HTTPResources []HTTPResource `json:"httpResources,omitempty"` - AwsActions []string `json:"awsActions,omitempty"` + Client *OtterizeServiceIdentity `json:"client"` + Server *OtterizeServiceIdentity `json:"server"` + Type *IntentType `json:"type,omitempty"` + ResolutionData *string `json:"resolutionData,omitempty"` + KafkaTopics []KafkaConfig `json:"kafkaTopics,omitempty"` + HTTPResources []HTTPResource `json:"httpResources,omitempty"` + AwsActions []string `json:"awsActions,omitempty"` } type IstioConnection struct { @@ -96,9 +108,10 @@ type Mutation struct { } type OtterizeServiceIdentity struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Labels []PodLabel `json:"labels,omitempty"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Labels []PodLabel `json:"labels,omitempty"` + ResolutionData *IdentityResolutionData `json:"resolutionData,omitempty"` // If the service identity was resolved from a pod owner, the GroupVersionKind of the pod owner. PodOwnerKind *GroupVersionKind `json:"podOwnerKind,omitempty"` // If the service identity was resolved from a Kubernetes service, its name. diff --git a/src/mapper/pkg/kubefinder/kubefinder.go b/src/mapper/pkg/kubefinder/kubefinder.go index 34e91556..8023e6d6 100644 --- a/src/mapper/pkg/kubefinder/kubefinder.go +++ b/src/mapper/pkg/kubefinder/kubefinder.go @@ -436,12 +436,19 @@ func (k *KubeFinder) ResolveOtterizeIdentityForService(ctx context.Context, svc return model.OtterizeServiceIdentity{}, false, errors.Wrap(err) } + resolutionData := model.IdentityResolutionData{ + IsService: lo.ToPtr(true), + ExtraInfo: lo.ToPtr("ResolveOtterizeIdentityForService"), + LastSeen: lo.ToPtr(lastSeen.String()), + } + if len(pods) == 0 { if ServiceIsAPIServer(svc.Name, svc.Namespace) { return model.OtterizeServiceIdentity{ Name: svc.Name, Namespace: svc.Namespace, KubernetesService: &svc.Name, + ResolutionData: &resolutionData, }, true, nil } @@ -462,10 +469,14 @@ func (k *KubeFinder) ResolveOtterizeIdentityForService(ctx context.Context, svc return model.OtterizeServiceIdentity{}, false, errors.Wrap(err) } + resolutionData.PodHostname = lo.ToPtr(pod.Name) + resolutionData.Uptime = lo.ToPtr(time.Since(pod.CreationTimestamp.Time).String()) + dstSvcIdentity := model.OtterizeServiceIdentity{ - Name: dstService.Name, - Namespace: pod.Namespace, - Labels: PodLabelsToOtterizeLabels(&pod), + Name: dstService.Name, + Namespace: pod.Namespace, + Labels: PodLabelsToOtterizeLabels(&pod), + ResolutionData: &resolutionData, } if dstService.OwnerObject != nil { diff --git a/src/mapper/pkg/resolvers/helpers.go b/src/mapper/pkg/resolvers/helpers.go index 4a6a883c..0934ceb1 100644 --- a/src/mapper/pkg/resolvers/helpers.go +++ b/src/mapper/pkg/resolvers/helpers.go @@ -5,8 +5,10 @@ import ( "github.com/otterize/intents-operator/src/shared/errors" "github.com/otterize/network-mapper/src/mapper/pkg/graph/model" "github.com/otterize/network-mapper/src/mapper/pkg/kubefinder" + "github.com/samber/lo" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" + "time" ) func (r *Resolver) discoverInternalSrcIdentity(ctx context.Context, src *model.RecordedDestinationsForSrc) (model.OtterizeServiceIdentity, error) { @@ -16,7 +18,11 @@ func (r *Resolver) discoverInternalSrcIdentity(ctx context.Context, src *model.R } if ok { - return model.OtterizeServiceIdentity{Name: svc.Name, Namespace: svc.Namespace, KubernetesService: &svc.Name}, nil + resolutionData := model.IdentityResolutionData{ + Host: lo.ToPtr(src.SrcIP), + PodHostname: lo.ToPtr(src.SrcHostname), + } + return model.OtterizeServiceIdentity{Name: svc.Name, Namespace: svc.Namespace, KubernetesService: &svc.Name, ResolutionData: &resolutionData}, nil } srcPod, err := r.kubeFinder.ResolveIPToPod(ctx, src.SrcIP) @@ -39,7 +45,12 @@ func (r *Resolver) discoverInternalSrcIdentity(ctx context.Context, src *model.R // It may cause a bug because the function will not be able to modify the "src" object of the caller. r.filterTargetsAccordingToPodCreationTime(src, srcPod) - return r.resolveInClusterIdentity(ctx, srcPod) + svcIdentity, err := r.resolveInClusterIdentity(ctx, srcPod) + if err != nil { + return model.OtterizeServiceIdentity{}, errors.Wrap(err) + } + svcIdentity.ResolutionData.ProcfsHostname = lo.Ternary(src.SrcHostname != "", lo.ToPtr(src.SrcHostname), nil) + return svcIdentity, nil } func (r *Resolver) filterTargetsAccordingToPodCreationTime(src *model.RecordedDestinationsForSrc, srcPod *corev1.Pod) { @@ -64,7 +75,18 @@ func (r *Resolver) resolveInClusterIdentity(ctx context.Context, pod *corev1.Pod return model.OtterizeServiceIdentity{}, errors.Errorf("could not resolve pod %s to identity: %w", pod.Name, err) } - modelSvcIdentity := model.OtterizeServiceIdentity{Name: svcIdentity.Name, Namespace: pod.Namespace, Labels: kubefinder.PodLabelsToOtterizeLabels(pod)} + modelSvcIdentity := model.OtterizeServiceIdentity{ + Name: svcIdentity.Name, + Namespace: pod.Namespace, + Labels: kubefinder.PodLabelsToOtterizeLabels(pod), + ResolutionData: &model.IdentityResolutionData{ + Host: lo.ToPtr(pod.Status.PodIP), + PodHostname: lo.ToPtr(pod.Name), + IsService: lo.ToPtr(false), + ExtraInfo: lo.ToPtr("resolveInClusterIdentity"), + Uptime: lo.ToPtr(time.Since(pod.CreationTimestamp.Time).String()), + }, + } if svcIdentity.OwnerObject != nil { modelSvcIdentity.PodOwnerKind = model.GroupVersionKindFromKubeGVK(svcIdentity.OwnerObject.GetObjectKind().GroupVersionKind()) } diff --git a/src/mapper/pkg/resolvers/schema.helpers.resolvers.go b/src/mapper/pkg/resolvers/schema.helpers.resolvers.go index 3ffa3145..4f6c1beb 100644 --- a/src/mapper/pkg/resolvers/schema.helpers.resolvers.go +++ b/src/mapper/pkg/resolvers/schema.helpers.resolvers.go @@ -68,6 +68,9 @@ func (r *Resolver) resolveDestIdentity(ctx context.Context, dest model.Destinati return model.OtterizeServiceIdentity{}, false, errors.Wrap(err) } if ok { + dstSvcIdentity.ResolutionData.Host = lo.ToPtr(dest.Destination) + dstSvcIdentity.ResolutionData.Port = dest.DestinationPort + dstSvcIdentity.ResolutionData.ExtraInfo = lo.ToPtr("resolveDestIdentity") return dstSvcIdentity, true, nil } } @@ -98,7 +101,19 @@ func (r *Resolver) resolveDestIdentity(ctx context.Context, dest model.Destinati return model.OtterizeServiceIdentity{}, false, nil } - dstSvcIdentity := model.OtterizeServiceIdentity{Name: dstService.Name, Namespace: destPod.Namespace, Labels: kubefinder.PodLabelsToOtterizeLabels(destPod)} + dstSvcIdentity := model.OtterizeServiceIdentity{ + Name: dstService.Name, + Namespace: destPod.Namespace, + Labels: kubefinder.PodLabelsToOtterizeLabels(destPod), + ResolutionData: &model.IdentityResolutionData{ + Host: lo.ToPtr(dest.Destination), + Port: dest.DestinationPort, + IsService: lo.ToPtr(false), + ExtraInfo: lo.ToPtr("resolveDestIdentity"), + LastSeen: lo.ToPtr(dest.LastSeen.String()), + Uptime: lo.ToPtr(time.Since(destPod.CreationTimestamp.Time).String()), + }, + } if dstService.OwnerObject != nil { dstSvcIdentity.PodOwnerKind = model.GroupVersionKindFromKubeGVK(dstService.OwnerObject.GetObjectKind().GroupVersionKind()) } @@ -131,10 +146,13 @@ func (r *Resolver) addSocketScanServiceIntent(ctx context.Context, srcSvcIdentit if !ok { return nil } + dstSvcIdentity.ResolutionData.Host = lo.ToPtr(dest.Destination) + dstSvcIdentity.ResolutionData.Port = dest.DestinationPort intent := model.Intent{ - Client: &srcSvcIdentity, - Server: &dstSvcIdentity, + Client: &srcSvcIdentity, + Server: &dstSvcIdentity, + ResolutionData: lo.ToPtr("addSocketScanServiceIntent"), } r.intentsHolder.AddIntent( @@ -163,15 +181,28 @@ func (r *Resolver) addSocketScanPodIntent(ctx context.Context, srcSvcIdentity mo if err != nil { return errors.Wrap(err) } - - dstSvcIdentity := &model.OtterizeServiceIdentity{Name: dstService.Name, Namespace: destPod.Namespace, Labels: kubefinder.PodLabelsToOtterizeLabels(destPod)} + dstSvcIdentity := &model.OtterizeServiceIdentity{ + Name: dstService.Name, + Namespace: destPod.Namespace, + Labels: kubefinder.PodLabelsToOtterizeLabels(destPod), + ResolutionData: &model.IdentityResolutionData{ + Host: lo.ToPtr(dest.Destination), + PodHostname: lo.ToPtr(destPod.Name), + Port: dest.DestinationPort, + IsService: lo.ToPtr(false), + ExtraInfo: lo.ToPtr("addSocketScanPodIntent"), + LastSeen: lo.ToPtr(dest.LastSeen.String()), + Uptime: lo.ToPtr(time.Since(destPod.CreationTimestamp.Time).String()), + }, + } if dstService.OwnerObject != nil { dstSvcIdentity.PodOwnerKind = model.GroupVersionKindFromKubeGVK(dstService.OwnerObject.GetObjectKind().GroupVersionKind()) } intent := model.Intent{ - Client: &srcSvcIdentity, - Server: dstSvcIdentity, + Client: &srcSvcIdentity, + Server: dstSvcIdentity, + ResolutionData: lo.ToPtr("addSocketScanPodIntent"), } r.intentsHolder.AddIntent( @@ -273,8 +304,9 @@ func (r *Resolver) handleDNSCaptureResultsAsKubernetesPods(ctx context.Context, } intent := model.Intent{ - Client: &srcSvcIdentity, - Server: dstSvcIdentity, + Client: &srcSvcIdentity, + Server: dstSvcIdentity, + ResolutionData: lo.ToPtr("handleDNSCaptureResultsAsKubernetesPods"), } r.intentsHolder.AddIntent( @@ -288,6 +320,12 @@ func (r *Resolver) handleDNSCaptureResultsAsKubernetesPods(ctx context.Context, func (r *Resolver) resolveOtterizeIdentityForDestinationAddress(ctx context.Context, dest model.Destination) (*model.OtterizeServiceIdentity, bool, error) { destAddress := dest.Destination + resolutionData := model.IdentityResolutionData{ + Host: lo.ToPtr(destAddress), + LastSeen: lo.ToPtr(dest.LastSeen.String()), + IsService: lo.ToPtr(true), + ExtraInfo: lo.ToPtr("resolveOtterizeIdentityForDestinationAddress"), + } pods, serviceName, err := r.kubeFinder.ResolveServiceAddressToPods(ctx, destAddress) if err != nil { logrus.WithError(err).Warningf("Could not resolve service address %s", destAddress) @@ -299,6 +337,7 @@ func (r *Resolver) resolveOtterizeIdentityForDestinationAddress(ctx context.Cont Name: serviceName.Name, Namespace: serviceName.Namespace, KubernetesService: &serviceName.Name, + ResolutionData: &resolutionData, }, true, nil } @@ -322,13 +361,16 @@ func (r *Resolver) resolveOtterizeIdentityForDestinationAddress(ctx context.Cont destPod := &filteredPods[0] + resolutionData.PodHostname = lo.ToPtr(destPod.Name) + resolutionData.Uptime = lo.ToPtr(time.Since(destPod.CreationTimestamp.Time).String()) + dstService, err := r.serviceIdResolver.ResolvePodToServiceIdentity(ctx, destPod) if err != nil { logrus.WithError(err).Debugf("Could not resolve pod %s to identity", destPod.Name) return nil, false, nil } - dstSvcIdentity := &model.OtterizeServiceIdentity{Name: dstService.Name, Namespace: destPod.Namespace, Labels: kubefinder.PodLabelsToOtterizeLabels(destPod)} + dstSvcIdentity := &model.OtterizeServiceIdentity{Name: dstService.Name, Namespace: destPod.Namespace, Labels: kubefinder.PodLabelsToOtterizeLabels(destPod), ResolutionData: &resolutionData} if dstService.OwnerObject != nil { dstSvcIdentity.PodOwnerKind = model.GroupVersionKindFromKubeGVK(dstService.OwnerObject.GetObjectKind().GroupVersionKind()) } @@ -393,6 +435,7 @@ func (r *Resolver) resolveOtterizeIdentityForExternalAccessDestination(ctx conte } dstSvcIdentity, ok, err := r.kubeFinder.ResolveOtterizeIdentityForService(ctx, destService, dest.LastSeen) + dstSvcIdentity.ResolutionData.Host = lo.ToPtr(destIP) if err != nil { return model.OtterizeServiceIdentity{}, false, errors.Wrap(err) } @@ -477,8 +520,9 @@ func (r *Resolver) handleInternalTrafficTCPResult(ctx context.Context, srcIdenti } intent := model.Intent{ - Client: &srcIdentity, - Server: &destIdentity, + Client: &srcIdentity, + Server: &destIdentity, + ResolutionData: lo.ToPtr("handleInternalTrafficTCPResult"), } r.intentsHolder.AddIntent( @@ -628,6 +672,7 @@ func (r *Resolver) handleReportKafkaMapperResults(ctx context.Context, results m Operations: []model.KafkaOperation{operation}, }, }, + ResolutionData: lo.ToPtr("handleReportKafkaMapperResults"), } updateTelemetriesCounters(SourceTypeKafkaMapper, intent) @@ -681,10 +726,11 @@ func (r *Resolver) handleReportIstioConnectionResults(ctx context.Context, resul } intent := model.Intent{ - Client: &srcSvcIdentity, - Server: &dstSvcIdentity, - Type: lo.ToPtr(model.IntentTypeHTTP), - HTTPResources: []model.HTTPResource{{Path: result.Path, Methods: result.Methods}}, + Client: &srcSvcIdentity, + Server: &dstSvcIdentity, + Type: lo.ToPtr(model.IntentTypeHTTP), + HTTPResources: []model.HTTPResource{{Path: result.Path, Methods: result.Methods}}, + ResolutionData: lo.ToPtr("handleReportIstioConnectionResults"), } updateTelemetriesCounters(SourceTypeIstio, intent) diff --git a/src/mappergraphql/schema.graphql b/src/mappergraphql/schema.graphql index 148aec94..5b2a1685 100644 --- a/src/mappergraphql/schema.graphql +++ b/src/mappergraphql/schema.graphql @@ -39,10 +39,22 @@ type GroupVersionKind { kind: String! } +type IdentityResolutionData { + host: String + podHostname: String + procfsHostname: String + port: Int + isService: Boolean + uptime: String + lastSeen: String + extraInfo: String +} + type OtterizeServiceIdentity { name: String! namespace: String! labels: [PodLabel!] + resolutionData: IdentityResolutionData """ If the service identity was resolved from a pod owner, the GroupVersionKind of the pod owner. """ @@ -101,6 +113,7 @@ type Intent { client: OtterizeServiceIdentity! server: OtterizeServiceIdentity! type: IntentType + resolutionData: String kafkaTopics: [KafkaConfig!] httpResources: [HttpResource!] awsActions: [String!]