Skip to content

Commit

Permalink
feat: expose capability to get OCI image configuration.
Browse files Browse the repository at this point in the history
Adds a new function to allow policy author to get the OCI image manifest
and configuration from a given OCI image name.

Signed-off-by: José Guilherme Vanz <jguilhermevanz@suse.com>
  • Loading branch information
jvanz committed Jun 18, 2024
1 parent f5fa7bb commit 219d79a
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 0 deletions.
32 changes: 32 additions & 0 deletions pkg/capabilities/oci/manifest_config/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package manifest_config

import (
"encoding/json"
"errors"
"fmt"

cap "github.com/kubewarden/policy-sdk-go/pkg/capabilities"
)

// GetOCIManifestAndConfig fetches the OCI manifest and configuration for the given image URI.
// Arguments:
// * image: image to be verified (e.g.: `registry.testing.lan/busybox:1.0.0`)
func GetOCIManifestAndConfig(h *cap.Host, image string) (*OciImageManifestAndConfigResponse, error) {
// build request payload, e.g: `"ghcr.io/kubewarden/policies/pod-privileged:v0.1.10"`
payload, err := json.Marshal(image)
if err != nil {
return nil, fmt.Errorf("cannot serialize image URI to JSON: %w", err)
}

// perform host callback
responsePayload, err := h.Client.HostCall("kubewarden", "oci", "v1/oci_manifest_config", payload)
if err != nil {
return nil, err
}

response := OciImageManifestAndConfigResponse{}
if err := json.Unmarshal(responsePayload, &response); err != nil {
return nil, errors.Join(fmt.Errorf("failed to parse response from the host"), err)
}
return &response, nil
}
115 changes: 115 additions & 0 deletions pkg/capabilities/oci/manifest_config/manifest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package manifest_config

import (
_ "crypto/sha256"
"encoding/json"
"testing"
"time"

cap "github.com/kubewarden/policy-sdk-go/pkg/capabilities"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/google/go-cmp/cmp"
"github.com/kubewarden/policy-sdk-go/pkg/capabilities/mocks"
)

func buildHostMock(imageURI string, returnPayload []byte) (*cap.Host, error) {
mockWapcClient := &mocks.MockWapcClient{}
expectedPayload, err := json.Marshal(imageURI)
if err != nil {
return nil, err
}
mockWapcClient.EXPECT().HostCall("kubewarden", "oci", "v1/oci_manifest_config", expectedPayload).Return(returnPayload, nil).Times(1)
return &cap.Host{
Client: mockWapcClient,
}, nil
}

func buildManifestAndConfigResponse() interface{} {
manifest := specs.Manifest{
MediaType: specs.MediaTypeImageManifest,
Config: specs.Descriptor{
MediaType: specs.MediaTypeDescriptor,
Digest: digest.FromString("mydummydigest"),
Size: 1024,
URLs: []string{"ghcr.io/kubewarden/policy-server:latest"},
Annotations: map[string]string{"annotation": "value"},
Platform: &specs.Platform{
Architecture: "amd64",
OS: "linux",
},
},
Layers: []specs.Descriptor{},
Annotations: map[string]string{"annotation": "value"},
}
now := time.Now()
image := specs.Image{
Created: &now,
Author: "kubewarden",
Platform: specs.Platform{
Architecture: "amd64",
OS: "linux",
OSVersion: "1.0.0",
OSFeatures: []string{"feature1", "feature2"},
Variant: "variant",
},
Config: specs.ImageConfig{
User: "1000",
Cmd: []string{"echo", "hello"},
Entrypoint: []string{"echo"},
Env: []string{"key=value"},
WorkingDir: "/",
Labels: map[string]string{"label": "value"},
StopSignal: "SIGTERM",
ExposedPorts: map[string]struct{}{"80/tcp": {}},
Volumes: map[string]struct{}{"/tmp": {}},
ArgsEscaped: true,
},
RootFS: specs.RootFS{
Type: "layers",
DiffIDs: []digest.Digest{digest.FromString("mydummydigest")},
},
History: []specs.History{
{
Created: &now,
CreatedBy: "kubewarden",
Author: "kubewarden",
Comment: "initial commit",
EmptyLayer: false,
},
},
}
return OciImageManifestAndConfigResponse{
Manifest: &manifest,
Digest: "mydummydigest",
ImageConfig: &image,
}
}

func TestV1OciManifest(t *testing.T) {
manifest := buildManifestAndConfigResponse()
manifestPayload, err := json.Marshal(manifest)
if err != nil {
t.Fatalf("cannot serialize response object: %v", err)
}
response := OciImageManifestAndConfigResponse{}
if err := json.Unmarshal(manifestPayload, &response); err != nil {
t.Fatalf("failed to parse response from the host")
}

imageURI := "myimage:latest"
host, err := buildHostMock(imageURI, manifestPayload)
if err != nil {
t.Fatalf("cannot build host mock: %q", err)
}

res, err := GetOCIManifestAndConfig(host, imageURI)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if diff := cmp.Diff(*res, manifest); diff != "" {
t.Fatalf("invalid manifest and config response:\n%s", diff)
}
}
11 changes: 11 additions & 0 deletions pkg/capabilities/oci/manifest_config/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package manifest_config

import (
specs "github.com/opencontainers/image-spec/specs-go/v1"
)

type OciImageManifestAndConfigResponse struct {
Manifest *specs.Manifest `json:"manifest"`
Digest string `json:"digest"`
ImageConfig *specs.Image `json:"config"`
}

0 comments on commit 219d79a

Please sign in to comment.