github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/operators/oci-handler/oci.go (about)

     1  // Copyright 2024 The Inspektor Gadget authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ocihandler
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  
    23  	"github.com/spf13/viper"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api"
    28  	apihelpers "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api-helpers"
    29  	"github.com/inspektor-gadget/inspektor-gadget/pkg/k8sutil"
    30  	"github.com/inspektor-gadget/inspektor-gadget/pkg/oci"
    31  	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
    32  	"github.com/inspektor-gadget/inspektor-gadget/pkg/params"
    33  	"github.com/inspektor-gadget/inspektor-gadget/pkg/resources"
    34  )
    35  
    36  const (
    37  	validateMetadataParam = "validate-metadata"
    38  	authfileParam         = "authfile"
    39  	insecureParam         = "insecure"
    40  	pullParam             = "pull"
    41  	pullSecret            = "pull-secret"
    42  	verifyImage           = "verify-image"
    43  	publicKey             = "public-key"
    44  )
    45  
    46  type ociHandler struct{}
    47  
    48  func (o *ociHandler) Name() string {
    49  	return "oci"
    50  }
    51  
    52  func (o *ociHandler) Init(*params.Params) error {
    53  	return nil
    54  }
    55  
    56  func (o *ociHandler) GlobalParams() api.Params {
    57  	return nil
    58  }
    59  
    60  func (o *ociHandler) InstanceParams() api.Params {
    61  	return api.Params{
    62  		// Hardcoded for now
    63  		{
    64  			Key:          authfileParam,
    65  			Title:        "Auth file",
    66  			Description:  "Path of the authentication file. This overrides the REGISTRY_AUTH_FILE environment variable",
    67  			DefaultValue: oci.DefaultAuthFile,
    68  			TypeHint:     api.TypeString,
    69  		},
    70  		{
    71  			Key:          validateMetadataParam,
    72  			Title:        "Validate metadata",
    73  			Description:  "Validate the gadget metadata before running the gadget",
    74  			DefaultValue: "true",
    75  			TypeHint:     api.TypeBool,
    76  		},
    77  		{
    78  			Key:          insecureParam,
    79  			Title:        "Insecure connection",
    80  			Description:  "Allow connections to HTTP only registries",
    81  			DefaultValue: "false",
    82  			TypeHint:     api.TypeBool,
    83  		},
    84  		{
    85  			Key:          pullParam,
    86  			Title:        "Pull policy",
    87  			Description:  "Specify when the gadget image should be pulled",
    88  			DefaultValue: oci.PullImageMissing,
    89  			PossibleValues: []string{
    90  				oci.PullImageAlways,
    91  				oci.PullImageMissing,
    92  				oci.PullImageNever,
    93  			},
    94  			TypeHint: api.TypeString,
    95  		},
    96  		{
    97  			Key:         pullSecret,
    98  			Title:       "Pull secret",
    99  			Description: "Secret to use when pulling the gadget image",
   100  			TypeHint:    api.TypeString,
   101  		},
   102  		{
   103  			Key:          verifyImage,
   104  			Title:        "Verify image",
   105  			Description:  "Verify image using the provided public key",
   106  			DefaultValue: "true",
   107  			TypeHint:     api.TypeBool,
   108  		},
   109  		{
   110  			Key:          publicKey,
   111  			Title:        "Public key",
   112  			Description:  "Public key used to verify the image based gadget",
   113  			DefaultValue: resources.InspektorGadgetPublicKey,
   114  			TypeHint:     api.TypeString,
   115  		},
   116  	}
   117  }
   118  
   119  func getPullSecret(pullSecretString string, gadgetNamespace string) ([]byte, error) {
   120  	k8sClient, err := k8sutil.NewClientset("")
   121  	if err != nil {
   122  		return nil, fmt.Errorf("creating new k8s clientset: %w", err)
   123  	}
   124  	gps, err := k8sClient.CoreV1().Secrets(gadgetNamespace).Get(context.TODO(), pullSecretString, metav1.GetOptions{})
   125  	if err != nil {
   126  		return nil, fmt.Errorf("getting secret %q: %w", pullSecretString, err)
   127  	}
   128  	if gps.Type != corev1.SecretTypeDockerConfigJson {
   129  		return nil, fmt.Errorf("secret %q is not of type %q", pullSecretString, corev1.SecretTypeDockerConfigJson)
   130  	}
   131  	return gps.Data[corev1.DockerConfigJsonKey], nil
   132  }
   133  
   134  func (o *ociHandler) InstantiateDataOperator(gadgetCtx operators.GadgetContext, instanceParamValues api.ParamValues) (
   135  	operators.DataOperatorInstance, error,
   136  ) {
   137  	ociParams := apihelpers.ToParamDescs(o.InstanceParams()).ToParams()
   138  	err := ociParams.CopyFromMap(instanceParamValues, "")
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	instance := &OciHandlerInstance{
   144  		ociHandler:  o,
   145  		gadgetCtx:   gadgetCtx,
   146  		ociParams:   ociParams,
   147  		paramValues: instanceParamValues,
   148  	}
   149  
   150  	err = instance.init(gadgetCtx)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	return instance, nil
   156  }
   157  
   158  func (o *OciHandlerInstance) ExtraParams(gadgetCtx operators.GadgetContext) api.Params {
   159  	return o.extraParams
   160  }
   161  
   162  func (o *OciHandlerInstance) init(gadgetCtx operators.GadgetContext) error {
   163  	if len(gadgetCtx.ImageName()) == 0 {
   164  		return fmt.Errorf("imageName empty")
   165  	}
   166  
   167  	// TODO: move to a place without dependency on k8s
   168  	pullSecretString := o.ociParams.Get(pullSecret).AsString()
   169  	var secretBytes []byte = nil
   170  	if pullSecretString != "" {
   171  		var err error
   172  		// TODO: Namespace is still hardcoded
   173  		secretBytes, err = getPullSecret(pullSecretString, "gadget")
   174  		if err != nil {
   175  			return err
   176  		}
   177  	}
   178  
   179  	imgOpts := &oci.ImageOptions{
   180  		AuthOptions: oci.AuthOptions{
   181  			AuthFile:    o.ociParams.Get(authfileParam).AsString(),
   182  			SecretBytes: secretBytes,
   183  			Insecure:    o.ociParams.Get(insecureParam).AsBool(),
   184  		},
   185  		VerifyOptions: oci.VerifyOptions{
   186  			VerifyPublicKey: o.ociParams.Get(verifyImage).AsBool(),
   187  			PublicKey:       o.ociParams.Get(publicKey).AsString(),
   188  		},
   189  	}
   190  
   191  	// Make sure the image is available, either through pulling or by just accessing a local copy
   192  	// TODO: add security constraints (e.g. don't allow pulling - add GlobalParams for that)
   193  	err := oci.EnsureImage(gadgetCtx.Context(), gadgetCtx.ImageName(), imgOpts, o.ociParams.Get(pullParam).AsString())
   194  	if err != nil {
   195  		return fmt.Errorf("ensuring image: %w", err)
   196  	}
   197  
   198  	manifest, err := oci.GetManifestForHost(gadgetCtx.Context(), gadgetCtx.ImageName())
   199  	if err != nil {
   200  		return fmt.Errorf("getting manifest: %w", err)
   201  	}
   202  
   203  	log := gadgetCtx.Logger()
   204  
   205  	r, err := oci.GetContentFromDescriptor(gadgetCtx.Context(), manifest.Config)
   206  	if err != nil {
   207  		return fmt.Errorf("getting metadata: %w", err)
   208  	}
   209  	metadata, err := io.ReadAll(r)
   210  	if err != nil {
   211  		r.Close()
   212  		return fmt.Errorf("reading metadata: %w", err)
   213  	}
   214  	r.Close()
   215  
   216  	// Store metadata for serialization
   217  	gadgetCtx.SetMetadata(metadata)
   218  
   219  	viper := viper.New()
   220  	viper.SetConfigType("yaml")
   221  	err = viper.ReadConfig(bytes.NewReader(metadata))
   222  
   223  	if err != nil {
   224  		return fmt.Errorf("unmarshalling metadata: %w", err)
   225  	}
   226  
   227  	gadgetCtx.SetVar("config", viper)
   228  
   229  	for _, layer := range manifest.Layers {
   230  		log.Debugf("layer > %+v", layer)
   231  		op, ok := operators.GetImageOperatorForMediaType(layer.MediaType)
   232  		if !ok {
   233  			continue
   234  		}
   235  
   236  		log.Debugf("found image op %q", op.Name())
   237  		opInst, err := op.InstantiateImageOperator(gadgetCtx, layer, o.paramValues.ExtractPrefixedValues(op.Name()))
   238  		if err != nil {
   239  			log.Errorf("instantiating operator %q: %v", op.Name(), err)
   240  		}
   241  		if opInst == nil {
   242  			log.Debugf("> skipped %s", op.Name())
   243  			continue
   244  		}
   245  		o.imageOperatorInstances = append(o.imageOperatorInstances, opInst)
   246  	}
   247  
   248  	if len(o.imageOperatorInstances) == 0 {
   249  		return fmt.Errorf("image doesn't contain valid gadget layers")
   250  	}
   251  
   252  	extraParams := make([]*api.Param, 0)
   253  	for _, opInst := range o.imageOperatorInstances {
   254  		err := opInst.Prepare(o.gadgetCtx)
   255  		if err != nil {
   256  			o.gadgetCtx.Logger().Errorf("preparing operator %q: %v", opInst.Name(), err)
   257  			continue
   258  		}
   259  
   260  		// Add gadget params prefixed with operators' name
   261  		extraParams = append(extraParams, opInst.ExtraParams(gadgetCtx).AddPrefix(opInst.Name())...)
   262  	}
   263  
   264  	o.extraParams = extraParams
   265  	return nil
   266  }
   267  
   268  func (o *OciHandlerInstance) Start(gadgetCtx operators.GadgetContext) error {
   269  	for _, opInst := range o.imageOperatorInstances {
   270  		err := opInst.Start(o.gadgetCtx)
   271  		if err != nil {
   272  			o.gadgetCtx.Logger().Errorf("starting operator %q: %v", opInst.Name(), err)
   273  		}
   274  	}
   275  	return nil
   276  }
   277  
   278  func (o *OciHandlerInstance) Stop(gadgetCtx operators.GadgetContext) error {
   279  	for _, opInst := range o.imageOperatorInstances {
   280  		err := opInst.Stop(o.gadgetCtx)
   281  		if err != nil {
   282  			o.gadgetCtx.Logger().Errorf("starting operator %q: %v", opInst.Name(), err)
   283  		}
   284  	}
   285  	return nil
   286  }
   287  
   288  type OciHandlerInstance struct {
   289  	ociHandler             *ociHandler
   290  	gadgetCtx              operators.GadgetContext
   291  	imageOperatorInstances []operators.ImageOperatorInstance
   292  	dataOperatorInstances  []operators.DataOperatorInstance
   293  	extraParams            api.Params
   294  	paramValues            api.ParamValues
   295  	ociParams              *params.Params
   296  	paramValueMap          map[string]string
   297  }
   298  
   299  func (o *OciHandlerInstance) Name() string {
   300  	return "oci"
   301  }
   302  
   303  func (o *ociHandler) Priority() int {
   304  	return -1000
   305  }
   306  
   307  // OciHandler is a singleton of ociHandler
   308  var OciHandler = &ociHandler{}