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{}