commonlogk8saudit_impl

package
v0.55.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 18, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// ContainerTypeContainer is the container type for standard containers.
	ContainerTypeContainer containerType = "container"
	// ContainerTypeInitContainer is the container type for init containers.
	ContainerTypeInitContainer containerType = "initContainer"
	// ContainerTypeEphemeral is the container type for ephemeral containers.
	ContainerTypeEphemeral containerType = "ephemeral"
)
View Source
const (
	// Subresource means the subresourceResourceGroupDecider must treat it as subresource by default. This is the default value.
	Subresource = 0
	// Parent means the subresourceResourceGroupDecider must treat it as its parent by default.
	Parent = 1
)

Variables

View Source
var ChangeTargetGrouperTask = inspectiontaskbase.NewProgressReportableInspectionTask[commonlogk8saudit_contract.ResourceLogGroupMap](
	commonlogk8saudit_contract.ChangeTargetGrouperTaskID,
	[]taskid.UntypedTaskReference{commonlogk8saudit_contract.LogSorterTaskID.Ref()},
	func(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType, progress *inspectionmetadata.TaskProgressMetadata) (commonlogk8saudit_contract.ResourceLogGroupMap, error) {
		if taskMode != inspectioncore_contract.TaskModeRun {
			return commonlogk8saudit_contract.ResourceLogGroupMap{}, nil
		}

		progress.MarkIndeterminate()

		logs := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.LogSorterTaskID.Ref())
		result := commonlogk8saudit_contract.ResourceLogGroupMap{}
		scanner := targetResourceScanner{
			resourcesByNamespaceKindAPIVersions: map[string]map[string]struct{}{},
			subresourceDefaultBehaviorOverrides: defaultSubresourceDefaultBehaviorOverrides,
		}

		for _, l := range logs {
			ops := scanner.scanTargetResource(l)
			for _, op := range ops {
				resource := commonlogk8saudit_contract.ResourceIdentityFromKubernetesOperation(op)
				path := resource.ResourcePathString()
				if result[path] == nil {
					result[path] = &commonlogk8saudit_contract.ResourceLogGroup{
						Logs:     []*log.Log{},
						Resource: resource,
					}
				}
				result[path].Logs = append(result[path].Logs, l)
			}
		}

		return result, nil
	},
)

ChangeTargetGrouperTask groups logs by resource that is modified by the operation in the log. This task determines the group, specifically handling the following cases: 1. When multiple resources are modified by the operation, the log entry is duplicated and assigned to each group. 2. When a subresource is modified by the operation and its result contains its parent manifest, it uses the parent resource as the group key.

View Source
var ConditionLogToTimelineMapperTask = commonlogk8saudit_contract.NewManifestLogToTimelineMapper[*conditionLogToTimelineMapperTaskState](&conditionLogToTimelineMapperTaskSetting{
	minimumDeltaTimeToCreateInferredCreationRevision: 10 * time.Second,
})

ConditionLogToTimelineMapperTask is a ManifestLogToTimelineMapper task that tracks and records the history of Kubernetes resource conditions. It analyzes status.conditions fields in audit logs to generate revisions for each condition type (e.g., Ready, Scheduled).

View Source
var ContainerIDDiscoveryTask = commonlogk8saudit_contract.ContainerIDInventoryBuilder.DiscoveryTask(
	commonlogk8saudit_contract.ContainerIDDiscoveryTaskID,
	[]taskid.UntypedTaskReference{
		commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref(),
	},
	func(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType, progress *inspectionmetadata.TaskProgressMetadata) (commonlogk8saudit_contract.ContainerIDToContainerIdentity, error) {
		if taskMode == inspectioncore_contract.TaskModeDryRun {
			return nil, nil
		}

		result := commonlogk8saudit_contract.ContainerIDToContainerIdentity{}
		resourceLogs := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref())
		for _, group := range resourceLogs {
			if group.Resource.Type() != commonlogk8saudit_contract.Resource {
				continue
			}
			if group.Resource.APIVersion != "core/v1" || group.Resource.Kind != "pod" {
				continue
			}

			for _, log := range group.Logs {
				if log.ResourceBodyReader == nil {
					continue
				}
				extractContainerIDs(log.ResourceBodyReader, "status.containerStatuses", result)
				extractContainerIDs(log.ResourceBodyReader, "status.initContainerStatuses", result)
				extractContainerIDs(log.ResourceBodyReader, "status.ephemeralContainerStatuses", result)
			}
		}
		return result, nil
	},
)
View Source
var ContainerIDInventoryTask = commonlogk8saudit_contract.ContainerIDInventoryBuilder.InventoryTask(&containerIDMergeStrategy{})
View Source
var ContainerLogToTimelineMapperTask = commonlogk8saudit_contract.NewManifestLogToTimelineMapper[*containerLogToTimelineMapperTaskState](&containerLogToTimelineMapperTaskSetting{})

ContainerLogToTimelineMapperTask is the task to generate container history.

DefaultK8sResourceMergeConfigTask is the task that generates the default patch request merge config.

View Source
var EndpointResourceLogToTimelineMapperTask = commonlogk8saudit_contract.NewManifestLogToTimelineMapper[*endpointResourceLogToTimelineMapperState](&endpointResourceLogToTimelineMapperTaskSetting{})

EndpointResourceLogToTimelineMapperTask is the task to generate endpoint resource history.

View Source
var IPLeaseHistoryDiscoveryTask = commonlogk8saudit_contract.IPLeaseHistoryInventoryBuilder.DiscoveryTask(
	commonlogk8saudit_contract.IPLeaseHistoryDiscoveryTaskID,
	[]taskid.UntypedTaskReference{commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref()},
	func(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType, progress *inspectionmetadata.TaskProgressMetadata) (commonlogk8saudit_contract.IPLeaseHistory, error) {
		if taskMode == inspectioncore_contract.TaskModeDryRun {
			return nil, nil
		}
		resourceLogs := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref())
		leaseHistory := resourcelease.NewResourceLeaseHistory[*commonlogk8saudit_contract.ResourceIdentity]()
		for _, group := range resourceLogs {
			if group.Resource.Type() != commonlogk8saudit_contract.Resource {
				continue
			}
			switch {
			case group.Resource.APIVersion == "core/v1" && group.Resource.Kind == "pod":
				processPodResource(group, leaseHistory)
			case group.Resource.APIVersion == "discovery.k8s.io/v1" && group.Resource.Kind == "endpointslice":
				processEndpointSliceResource(ctx, group, leaseHistory)
			}
		}
		return leaseHistory, nil
	},
)
View Source
var IPLeaseHistoryInventoryTask = commonlogk8saudit_contract.IPLeaseHistoryInventoryBuilder.InventoryTask(&ipLeaseHistoryInventoryMergeStrategy{})

LogIngesterTask is the task to serialize k8s audit logs.

LogSorterTask is the task to sort logs by time.

LogSummaryGrouperTask is the task to group logs for summary generation.

View Source
var LogSummaryLogToTimelineMapperTask = inspectiontaskbase.NewLogToTimelineMapperTask[struct{}](
	commonlogk8saudit_contract.LogSummaryLogToTimelineMapperTaskID,
	&logSummaryLogToTimelineMapperSetting{},
)

LogSummaryLogToTimelineMapperTask is the task to generate log summary from given k8s audit log.

View Source
var ManifestGeneratorTask = inspectiontaskbase.NewProgressReportableInspectionTask(commonlogk8saudit_contract.ManifestGeneratorTaskID, []taskid.UntypedTaskReference{
	commonlogk8saudit_contract.ChangeTargetGrouperTaskID.Ref(),
	commonlogk8saudit_contract.K8sResourceMergeConfigTaskID.Ref(),
}, func(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType, progress *inspectionmetadata.TaskProgressMetadata) (commonlogk8saudit_contract.ResourceManifestLogGroupMap, error) {
	if taskMode == inspectioncore_contract.TaskModeDryRun {
		return map[string]*commonlogk8saudit_contract.ResourceManifestLogGroup{}, nil
	}

	logGroups := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.ChangeTargetGrouperTaskID.Ref())
	mergeConfigRegistry := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.K8sResourceMergeConfigTaskID.Ref())
	result := commonlogk8saudit_contract.ResourceManifestLogGroupMap{}
	resultLock := sync.Mutex{}

	doneGroupCount := atomic.Int32{}
	updator := progressutil.NewProgressUpdator(progress, time.Second, func(tp *inspectionmetadata.TaskProgressMetadata) {
		current := doneGroupCount.Load()
		total := len(logGroups)
		if total > 0 {
			tp.Percentage = float32(current) / float32(total)
		} else {
			tp.Percentage = 1.0
		}
		tp.Message = fmt.Sprintf("%d/%d", current, total)
	})
	updator.Start(ctx)
	defer updator.Done()

	grp, childCtx := errgroup.WithContext(ctx)
	grp.SetLimit(runtime.GOMAXPROCS(0))

	for path, group := range logGroups {
		path := path
		group := group
		grp.Go(func() error {
			defer doneGroupCount.Add(1)
			resourceLogs := []*commonlogk8saudit_contract.ResourceManifestLog{}
			generator := groupManifestGenerator{
				mergeConfigRegistry: mergeConfigRegistry,
				resourceName:        group.Resource.Name,
			}
			for _, l := range group.Logs {
				select {
				case <-childCtx.Done():
					return context.Canceled
				default:
					r, err := generator.Process(childCtx, l)
					if err != nil {
						return err
					}
					resourceLogs = append(resourceLogs, r)
				}
			}
			resultLock.Lock()
			defer resultLock.Unlock()
			result[path] = &commonlogk8saudit_contract.ResourceManifestLogGroup{
				Resource: group.Resource,
				Logs:     resourceLogs,
			}
			return nil
		})
	}

	if err := grp.Wait(); err != nil {
		return nil, err
	}

	return result, nil
})

ManifestGeneratorTask is the task to generate manifest from k8s audit logs.

View Source
var NamespaceRequestLogToTimelineMapperTask = inspectiontaskbase.NewProgressReportableInspectionTask(commonlogk8saudit_contract.NamespaceRequestLogToTimelineMapperTaskID, []taskid.UntypedTaskReference{
	commonlogk8saudit_contract.K8sAuditLogIngesterTaskID.Ref(),
	commonlogk8saudit_contract.ChangeTargetGrouperTaskID.Ref(),
}, func(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType, tp *inspectionmetadata.TaskProgressMetadata) (struct{}, error) {
	if taskMode == inspectioncore_contract.TaskModeDryRun {
		return struct{}{}, nil
	}

	builder := khictx.MustGetValue(ctx, inspectioncore_contract.CurrentHistoryBuilder)
	logs := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.ChangeTargetGrouperTaskID.Ref())

	changedPaths := map[string]struct{}{}
	for _, group := range logs {
		if group.Resource.Type() == commonlogk8saudit_contract.Namespace {
			for _, l := range group.Logs {
				cs := history.NewChangeSet(l)
				cs.AddEvent(resourcepath.ResourcePath{
					Path:               group.Resource.ResourcePathString(),
					ParentRelationship: enum.RelationshipChild,
				})
				cp, err := cs.FlushToHistory(builder)
				if err != nil {
					return struct{}{}, err
				}
				for _, path := range cp {
					changedPaths[path] = struct{}{}
				}
			}
		}
	}
	for path := range changedPaths {
		tb := builder.GetTimelineBuilder(path)
		tb.Sort()
	}
	return struct{}{}, nil
})

NamespaceRequestLogToTimelineMapperTask is a task to generate events of requests against namespace wide by deletecollection. TODO: This must be reimplemented with the LogToTimelineMapperTask once it supports receiving group path.

View Source
var NodeNameDiscoveryTask = commonlogk8saudit_contract.NodeNameInventoryBuilder.DiscoveryTask(
	commonlogk8saudit_contract.NodeNameDiscoveryTaskID,
	[]taskid.UntypedTaskReference{commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref()},
	func(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType, progress *inspectionmetadata.TaskProgressMetadata) ([]string, error) {
		if taskMode == inspectioncore_contract.TaskModeDryRun {
			return nil, nil
		}

		foundNodeNames := map[string]struct{}{}
		resourceLogs := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref())
		for _, group := range resourceLogs {
			if group.Resource.Type() != commonlogk8saudit_contract.Resource {
				continue
			}
			if group.Resource.APIVersion != "core/v1" || group.Resource.Kind != "node" {
				continue
			}
			foundNodeNames[group.Resource.Name] = struct{}{}
		}
		var ret []string
		for k := range foundNodeNames {
			ret = append(ret, k)
		}
		return ret, nil
	},
)

NodeNameDiscoveryTask extracts node name from audit logs and node names are registered on NodeNameInventoryTask.

View Source
var NodeNameInventoryTask = commonlogk8saudit_contract.NodeNameInventoryBuilder.InventoryTask(&nodeNameMergeStrategy{})

NodeNameInventoryTask provides list of node name found in this inspection for later task usage.

NonSuccessLogFilterTask filters out success logs.

NonSuccessLogGrouperTask groups logs by resource path. K8s audit error logs are simply associated with timelines as events. They don't require any special grouping, so they use the resource associated with the original resource name modified by the request.

View Source
var NonSuccessLogLogToTimelineMapperTask = inspectiontaskbase.NewLogToTimelineMapperTask[struct{}](commonlogk8saudit_contract.NonSuccessLogLogToTimelineMapperTaskID, &nonSuccessLogLogToTimelineMapperTaskSetting{
	subresourceMapToWriteToParent: map[string]struct{}{
		"status":   {},
		"finalize": {},
		"approve":  {},
	},
})

NonSuccessLogLogToTimelineMapperTask is the task to generate history from non-success logs.

View Source
var PodPhaseLogToTimelineMapperTask = commonlogk8saudit_contract.NewManifestLogToTimelineMapper[*podPhaseTaskState](&podPhaseLogToTimelineMapperTaskSetting{
	minimumDeltaTimeToCreateInferredCreationRevision: 5 * time.Second,
})

PodPhaseLogToTimelineMapperTask is the task to generate pod phase history.

View Source
var ResourceLifetimeTrackerTask = inspectiontaskbase.NewProgressReportableInspectionTask[commonlogk8saudit_contract.ResourceManifestLogGroupMap](
	commonlogk8saudit_contract.ResourceLifetimeTrackerTaskID,
	[]taskid.UntypedTaskReference{
		commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref(),
		commonlogk8saudit_contract.K8sAuditLogIngesterTaskID.Ref(),
	},
	func(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType, tp *inspectionmetadata.TaskProgressMetadata) (commonlogk8saudit_contract.ResourceManifestLogGroupMap, error) {
		if taskMode == inspectioncore_contract.TaskModeDryRun {
			slog.DebugContext(ctx, "Skipping task because this is dry run mode")
			return commonlogk8saudit_contract.ResourceManifestLogGroupMap{}, nil
		}

		groupedLogs := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref())

		totalLogCount := 0
		var processedLogCount atomic.Uint32
		for _, group := range groupedLogs {
			totalLogCount += len(group.Logs)
		}

		updator := progressutil.NewProgressUpdator(tp, time.Second, func(tp *inspectionmetadata.TaskProgressMetadata) {
			current := processedLogCount.Load()
			tp.Percentage = float32(current) / float32(totalLogCount)
			tp.Message = fmt.Sprintf("%d/%d", current, totalLogCount)
		})
		updator.Start(ctx)

		processedLogCount.Store(0)
		setting := &lifeTimeTrackerTaskSetting{
			kindsToWaitExactDeletionToDetermineDeletion: map[string]struct{}{
				"core/v1#pod": {},
			},
		}

		pool := worker.NewPool(runtime.GOMAXPROCS(0))
		for _, group := range groupedLogs {
			pool.Run(func() {
				var groupData *lifeTimeTrackerGroupState

				if group.Resource.Type() == commonlogk8saudit_contract.Namespace {
					return
				}
				for _, l := range group.Logs {
					var err error
					groupData, err = setting.DetectLifetimeLogEvent(ctx, l, groupData)
					if err != nil {
						var yaml string
						yamlBytes, err2 := l.Log.Serialize("", &structured.YAMLNodeSerializer{})
						if err2 != nil {
							yaml = "ERROR!! failed to dump in yaml"
						} else {
							yaml = string(yamlBytes)
						}
						slog.WarnContext(ctx, "parser ended with an error", "error", err, "logContent", yaml)
						continue
					}
				}
				processedLogCount.Add(uint32(len(group.Logs)))
			})
		}
		pool.Wait()
		updator.Done()

		return groupedLogs, nil
	},
)

ResourceLifetimeTrackerTask is the task to track the lifetime of resources.

View Source
var ResourceOwnerReferenceTimelineMapperTask = commonlogk8saudit_contract.NewManifestLogToTimelineMapper[struct{}](&resourceOwnerReferenceTimelineMapperTaskSetting{
	nonNamespacedOwnerTypes: map[string]struct{}{
		"core/v1#node": {},
	},
})

ResourceOwnerReferenceTimelineMapperTask is the task to map logs into resource owner reference.

View Source
var ResourceRevisionLogToTimelineMapperTask = commonlogk8saudit_contract.NewManifestLogToTimelineMapper(&ResourceRevisionLogToTimelineMapperTaskSetting{
	minimumDeltaTimeToCreateInferredCreationRevision: 5 * time.Second,
	kindsToWaitExactDeletionToDeterminDeletion: map[string]struct{}{
		"core/v1#pod": {},
	},
})

ResourceRevisionLogToTimelineMapperTask is the task to generate resource revision history.

View Source
var ResourceUIDDiscoveryTask = commonlogk8saudit_contract.ResourceUIDInventoryBuilder.DiscoveryTask(
	commonlogk8saudit_contract.ResourceUIDDiscoveryTaskID,
	[]taskid.UntypedTaskReference{commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref()},
	func(ctx context.Context, taskMode inspectioncore_contract.InspectionTaskModeType, progress *inspectionmetadata.TaskProgressMetadata) (commonlogk8saudit_contract.UIDToResourceIdentity, error) {
		if taskMode == inspectioncore_contract.TaskModeDryRun {
			return commonlogk8saudit_contract.UIDToResourceIdentity{}, nil
		}
		result := commonlogk8saudit_contract.UIDToResourceIdentity{}
		resourceLogs := coretask.GetTaskResult(ctx, commonlogk8saudit_contract.ManifestGeneratorTaskID.Ref())
		for _, group := range resourceLogs {
			if group.Resource.Type() != commonlogk8saudit_contract.Resource {
				continue
			}
			for _, log := range group.Logs {
				if log.ResourceBodyReader == nil {
					continue
				}
				uid, err := log.ResourceBodyReader.ReadString("metadata.uid")
				if err != nil {
					continue
				}
				result[uid] = group.Resource
			}
		}
		return result, nil
	},
)
View Source
var ResourceUIDInventoryTask = commonlogk8saudit_contract.ResourceUIDInventoryBuilder.InventoryTask(&resourceUIDMergeStrategy{})

SuccessLogFilterTask filters out non-success logs.

Functions

func GetCreationTimestamp added in v0.54.0

func GetCreationTimestamp(reader *structured.NodeReader) (time.Time, bool)

GetCreationTimestamp returns the value of metadata.creationTimestamp. It returns the value and true if the field exists and is a valid timestamp. Otherwise, it returns time.Time{} and false.

func GetDeletionGracePeriodSeconds added in v0.54.0

func GetDeletionGracePeriodSeconds(reader *structured.NodeReader) (int, bool)

GetDeletionGracePeriodSeconds returns the value of metadata.deletionGracePeriodSeconds. It returns the value and true if the field exists and is an integer. Otherwise, it returns 0 and false.

func GetDeletionTimestamp added in v0.54.0

func GetDeletionTimestamp(reader *structured.NodeReader) (string, bool)

GetDeletionTimestamp returns the value of metadata.deletionTimestamp. It returns the value and true if the field exists and is a string. Otherwise, it returns empty string and false.

func GetFinalizers added in v0.54.0

func GetFinalizers(reader *structured.NodeReader) ([]string, bool)

GetFinalizers returns the list of finalizers from metadata.finalizers or spec.finalizers. It checks metadata.finalizers first, then spec.finalizers (for Namespace). It returns the list and true if at least one finalizer list exists. Note that it returns true even if the list is empty, as long as the field exists.

func GetNodeNameOfPod added in v0.54.0

func GetNodeNameOfPod(reader *structured.NodeReader) (string, bool)

GetNodeNameOfPod returns the value of spec.nodeName. It returns the value and true if the field exists and is a string. Otherwise, it returns empty string and false.

func GetPodPhase added in v0.54.0

func GetPodPhase(reader *structured.NodeReader) (string, bool)

GetPodPhase returns the value of status.phase. It returns the value and true if the field exists and is a string. Otherwise, it returns empty string and false.

func GetUID added in v0.54.0

func GetUID(reader *structured.NodeReader) (string, bool)

GetUID returns the value of metadata.uid. It returns the value and true if the field exists and is a string. Otherwise, it returns empty string and false.

func Register

func Register(registry coreinspection.InspectionTaskRegistry) error

Registers all commonlogk8saudit inspection tasks to the registry.

Types

type ResourceRevisionLogToTimelineMapperTaskSetting added in v0.54.0

type ResourceRevisionLogToTimelineMapperTaskSetting struct {
	// contains filtered or unexported fields
}

ResourceRevisionLogToTimelineMapperTaskSetting is the setting for the resource revision timeline mapper task.

func (*ResourceRevisionLogToTimelineMapperTaskSetting) Dependencies added in v0.54.0

Dependencies implements commonlogk8saudit_contract.ManifestLogToTimelineMapperTaskSetting.

func (*ResourceRevisionLogToTimelineMapperTaskSetting) GroupedLogTask added in v0.54.0

GroupedLogTask implements commonlogk8saudit_contract.ManifestLogToTimelineMapperTaskSetting.

func (*ResourceRevisionLogToTimelineMapperTaskSetting) LogIngesterTask added in v0.54.0

LogIngesterTask implements commonlogk8saudit_contract.ManifestLogToTimelineMapperTaskSetting.

func (*ResourceRevisionLogToTimelineMapperTaskSetting) PassCount added in v0.54.0

PassCount implements commonlogk8saudit_contract.ManifestLogToTimelineMapperTaskSetting.

func (*ResourceRevisionLogToTimelineMapperTaskSetting) Process added in v0.54.0

func (r *ResourceRevisionLogToTimelineMapperTaskSetting) Process(ctx context.Context, passIndex int, event commonlogk8saudit_contract.ResourceChangeEvent, cs *history.ChangeSet, builder *history.Builder, prevGroupData *resourceRevisionLogToTimelineMapperState) (*resourceRevisionLogToTimelineMapperState, error)

Process implements commonlogk8saudit_contract.ManifestLogToTimelineMapperTaskSetting.

func (*ResourceRevisionLogToTimelineMapperTaskSetting) ResourcePairs added in v0.54.0

ResourcePairs implements commonlogk8saudit_contract.ManifestLogToTimelineMapperTaskSetting.

func (*ResourceRevisionLogToTimelineMapperTaskSetting) TaskID added in v0.54.0

TaskID implements commonlogk8saudit_contract.ManifestLogToTimelineMapperTaskSetting.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL