Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: plugins as OCI artifacts #519

Merged
merged 39 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
66b1f84
Initial spike of support for downloading plugins as OCI artifacts
noelbundick-msft Dec 14, 2022
027e13f
Merge remote-tracking branch 'upstream/main' into plugins-as-oci-arti…
noelbundick-msft Dec 14, 2022
26178a1
add log level toggle to helm chart
akashsinghal Dec 15, 2022
2a356df
Merge remote-tracking branch 'upstream/main' into plugins-as-oci-arti…
noelbundick-msft Dec 16, 2022
b5d3601
Merge commit '26178a171e58cd62ff25dbd0fd477f14fa8e564b' of https://gi…
noelbundick-msft Dec 16, 2022
cf3ae07
Fix permissions on plugin dir
noelbundick-msft Dec 16, 2022
7a1e73c
Add CRD support for OCI artifact plugins
noelbundick-msft Dec 21, 2022
cd6834b
Merge remote-tracking branch 'upstream/main' into plugins-as-oci-arti…
noelbundick-msft Dec 21, 2022
37a852c
Merge branch 'main' of https://github.com/deislabs/ratify into plugin…
noelbundick-msft Jan 13, 2023
1d7a209
Update imports
noelbundick-msft Jan 13, 2023
82bb22d
add ability to specify authProvider for dynamic plugins
noelbundick-msft Jan 13, 2023
a38067c
e2e tests for dynamic plugins
noelbundick-msft Jan 13, 2023
f747d6b
fix bats teardown calls
noelbundick-msft Jan 13, 2023
2cdfffc
try larger runners for e2e
noelbundick-msft Jan 13, 2023
987e088
ci: revert large runners
noelbundick-msft Jan 13, 2023
76df183
wip: re-add chart upgrade
noelbundick-msft Jan 13, 2023
f2f3a86
add cli test for dynamic plugins
noelbundick-msft Jan 14, 2023
9bf4994
Merge branch 'main' into plugins-as-oci-artifacts
noelbundick-msft Jan 14, 2023
68beaeb
Merge branch 'main' of https://github.com/deislabs/ratify into plugin…
noelbundick-msft Jan 18, 2023
32d73aa
Add config parsing tests
noelbundick-msft Jan 18, 2023
a733749
go fmt
noelbundick-msft Jan 18, 2023
f1633e9
s/install/upgrade
noelbundick-msft Jan 18, 2023
2f2bc71
fix path in markdown
noelbundick-msft Jan 18, 2023
bee82cb
Merge branch 'main' into plugins-as-oci-artifacts
noelbundick-msft Jan 18, 2023
da3dcbd
use %w in error handling
noelbundick-msft Jan 18, 2023
396160e
Merge branch 'main' of https://github.com/deislabs/ratify into plugin…
noelbundick-msft Jan 19, 2023
f898518
s/ratify-service/gatekeeper-system
noelbundick-msft Jan 19, 2023
675989c
add cli test for dynamic referrerstore
noelbundick-msft Jan 23, 2023
c711d12
Merge branch 'main' of https://github.com/deislabs/ratify into plugin…
noelbundick-msft Jan 23, 2023
6fcc37a
enable dynamic plugin e2e test on aks
noelbundick-msft Jan 23, 2023
712083a
Merge branch 'main' of https://github.com/deislabs/ratify into plugin…
noelbundick-msft Jan 24, 2023
5c6b340
Merge branch 'main' of https://github.com/deislabs/ratify into plugin…
noelbundick-msft Jan 25, 2023
9978da5
Merge remote-tracking branch 'upstream/main' into plugins-as-oci-arti…
noelbundick-msft Jan 27, 2023
b343f44
re-run make generate
noelbundick-msft Jan 27, 2023
32653ae
fix test merge
noelbundick-msft Jan 27, 2023
65f9ab6
Merge remote-tracking branch 'upstream/main' into plugins-as-oci-arti…
noelbundick-msft Jan 31, 2023
3b3196c
use wabbitnetworks plugins
noelbundick-msft Jan 31, 2023
0f36bd9
Merge branch 'main' into plugins-as-oci-artifacts
noelbundick-msft Jan 31, 2023
f11c38a
Merge branch 'main' into plugins-as-oci-artifacts
noelbundick-msft Jan 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ RUN curl -Lo oras.tar.gz https://github.com/oras-project/oras/releases/download/
&& mv oras-install/oras /usr/local/bin/ \
&& rm -rf ./oras-install oras.tar.gz

ARG KUBEBUILDER_VERSION="3.8.0"
RUN curl -L https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${KUBEBUILDER_VERSION}/kubebuilder_linux_amd64 -o /usr/local/bin/kubebuilder \
&& chmod +x /usr/local/bin/kubebuilder

# [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends protobuf-compiler libprotobuf-dev
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha1/store_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type StoreSpec struct {
Name string `json:"name,omitempty"`
// Plugin path, optional
Address string `json:"address,omitempty"`
// OCI Artifact source to download the plugin from, optional
Source string `json:"source,omitempty"`

// +kubebuilder:pruning:PreserveUnknownFields
// Parameters of the store
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/verifier_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type VerifierSpec struct {
// # Optional. URL/file path
Address string `json:"address,omitempty"`

// OCI Artifact source to download the plugin from, optional
Source string `json:"source,omitempty"`
susanshi marked this conversation as resolved.
Show resolved Hide resolved

// +kubebuilder:pruning:PreserveUnknownFields
// Parameters for this verifier
Parameters runtime.RawExtension `json:"parameters,omitempty"`
Expand Down
5 changes: 4 additions & 1 deletion charts/ratify/crds/store-customresourcedefinition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ spec:
description: Parameters of this store
type: object
x-kubernetes-preserve-unknown-fields: true
source:
description: OCI Artifact source to download the plugin from, optional
type: string
type: object
status:
description: StoreStatus defines the observed state of Store
type: object
type: object
served: true
storage: true
storage: true
5 changes: 4 additions & 1 deletion charts/ratify/crds/verifier-customresourcedefinition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ spec:
description: Parameters of this verifier
type: object
x-kubernetes-preserve-unknown-fields: true
source:
description: OCI Artifact source to download the plugin from, optional
type: string
type: object
status:
description: VerifierStatus defines the observed state of Verifier
type: object
type: object
served: true
storage: true
storage: true
3 changes: 3 additions & 0 deletions config/crd/bases/config.ratify.deislabs.io_stores.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ spec:
description: Parameters of the store
type: object
x-kubernetes-preserve-unknown-fields: true
source:
description: OCI Artifact source to download the plugin from, optional
type: string
type: object
status:
description: StoreStatus defines the observed state of Store
Expand Down
3 changes: 3 additions & 0 deletions config/crd/bases/config.ratify.deislabs.io_verifiers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ spec:
description: Parameters for this verifier
type: object
x-kubernetes-preserve-unknown-fields: true
source:
susanshi marked this conversation as resolved.
Show resolved Hide resolved
description: OCI Artifact source to download the plugin from, optional
type: string
type: object
status:
description: VerifierStatus defines the observed state of Verifier
Expand Down
9 changes: 9 additions & 0 deletions config/samples/config_v1alpha1_verifier_sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: config.ratify.deislabs.io/v1alpha1
kind: Verifier
metadata:
name: verifier-sample
spec:
name: sample
artifactTypes: application/spdx+json
source: noelfdpo.azurecr.io/sample-plugin:v1
noelbundick-msft marked this conversation as resolved.
Show resolved Hide resolved
parameters: {}
2 changes: 1 addition & 1 deletion httpserver/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ ARG RATIFY_FOLDER=$HOME/.ratify/
WORKDIR /

COPY --from=builder /app/out/ratify /app/
COPY --from=builder /app/out/plugins ${RATIFY_FOLDER}/plugins
COPY --from=builder --chown=65532:65532 /app/out/plugins ${RATIFY_FOLDER}/plugins
noelbundick-msft marked this conversation as resolved.
Show resolved Hide resolved
COPY --from=builder /app/config/config.json ${RATIFY_FOLDER}

ENV RATIFY_CONFIG=${RATIFY_FOLDER}
Expand Down
111 changes: 111 additions & 0 deletions pkg/common/plugin/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package plugin

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"time"

"github.com/deislabs/ratify/pkg/ocispecs"
"github.com/deislabs/ratify/pkg/referrerstore/oras/authprovider"
noelbundick-msft marked this conversation as resolved.
Show resolved Hide resolved
"github.com/sirupsen/logrus"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
)

func DownloadPlugin(name string, source string, pluginBinDir string) error {
ctx := context.TODO()

// Initialize a repository
repository, err := remote.NewRepository(source)
if err != nil {
return err
}

// TODO: move the ORAS auth code into a separate package that's not referrerstore-specific
repository.Client = &auth.Client{
Client: &http.Client{Timeout: 10 * time.Second, Transport: http.DefaultTransport.(*http.Transport).Clone()},
Header: http.Header{
"User-Agent": {"ratify"},
},
Cache: auth.NewCache(),
Credential: func(ctx context.Context, registry string) (auth.Credential, error) {
authProvider, err := authprovider.CreateAuthProviderFromConfig(nil)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: what should the config look like for specifying auth for pulling plugins. Should look & feel similar to oras store config, - but plugins might (will probably?) come from a different registry than application images

Perhaps the config should read something like

source:
  reference: noelfdpo.azurecr.io/sample-plugin:v1
  authProvider:
    name: azureWorkloadIdentity

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, we need to restructure our auth config. Curious do you think customer will use same identity for plugins /artifacts / certs from keyvault or will they use different clientIds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Late follow-up: I would expect to use the same app identity here, at least in Azure

So if you pulled plugins from a different registry than runtime images: it would still be the one "Ratify" Managed Identity that has AcrPull on both the "plugin registry" and the "normal one"

if err != nil {
return auth.EmptyCredential, err
}

authConfig, err := authProvider.Provide(ctx, source)
if err != nil {
return auth.EmptyCredential, err
}

if authConfig.Username != "" || authConfig.Password != "" || authConfig.IdentityToken != "" {
return auth.Credential{
Username: authConfig.Username,
Password: authConfig.Password,
RefreshToken: authConfig.IdentityToken,
}, nil
}
return auth.EmptyCredential, nil
},
}

// read the reference manifest
logrus.Infof("Downloading plugin %s from %s", name, source)
referenceManifestDescriptor, err := repository.Resolve(ctx, source)
if err != nil {
return err
}
logrus.Debugf("Resolved plugin manifest: %v", referenceManifestDescriptor)

manifestReader, err := repository.Fetch(ctx, referenceManifestDescriptor)
if err != nil {
return err
}

manifestBytes, err := io.ReadAll(manifestReader)
if err != nil {
return err
}

referenceManifest := ocispecs.ReferenceManifest{}
if err := json.Unmarshal(manifestBytes, &referenceManifest); err != nil {
return err
}

// Download the contents of the first blob as the named plugin. This matches `oras push registry.example.com/sample-plugin:v1 ./sample`
// TODO: should this be "first/only blob of media type `application/vnd.ratify.plugin`"?
blobReference := fmt.Sprintf("%s@%s", source, referenceManifest.Blobs[0].Digest)
logrus.Debugf("Downloading blob %s", blobReference)
_, blobReader, err := repository.Blobs().FetchReference(ctx, blobReference)
if err != nil {
return err
}

pluginPath := path.Join(pluginBinDir, name)
logrus.Infof("Downloading plugin %s to %s", name, pluginPath)
pluginFile, err := os.Create(pluginPath)
if err != nil {
return err
}

defer pluginFile.Close()
_, err = io.Copy(pluginFile, blobReader)
if err != nil {
return err
}

// Mark the plugin as executable
logrus.Debugf("Marking plugin %s as executable", pluginPath)
err = os.Chmod(pluginPath, 0700)
if err != nil {
return err
}

return nil
}
1 change: 1 addition & 0 deletions pkg/controllers/store_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func specToStoreConfig(storeSpec configv1alpha1.StoreSpec) (rc.StorePluginConfig
}
}
storeConfig[types.Name] = storeSpec.Name
storeConfig[types.Source] = storeSpec.Source

return storeConfig, nil
}
1 change: 1 addition & 0 deletions pkg/controllers/verifier_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ func specToVerifierConfig(verifierSpec configv1alpha1.VerifierSpec) (vc.Verifier

verifierConfig[types.Name] = verifierSpec.Name
verifierConfig[types.ArtifactTypes] = verifierSpec.ArtifactTypes
verifierConfig[types.Source] = verifierSpec.Source

return verifierConfig, nil
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/referrerstore/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import (
"os"
"strings"

pluginCommon "github.com/deislabs/ratify/pkg/common/plugin"
"github.com/deislabs/ratify/pkg/referrerstore"
"github.com/deislabs/ratify/pkg/referrerstore/config"
"github.com/deislabs/ratify/pkg/referrerstore/plugin"
"github.com/deislabs/ratify/pkg/referrerstore/types"
"github.com/sirupsen/logrus"
)

var builtInStores = make(map[string]StoreFactory)
Expand Down Expand Up @@ -57,6 +59,16 @@ func CreateStoreFromConfig(storeConfig config.StorePluginConfig, configVersion s
return nil, fmt.Errorf("invalid plugin name for a store: %s", storeName)
}

// if source is specified, download the plugin
if source, ok := storeConfig[types.Source]; ok && source != "" {
sourceStr := fmt.Sprintf("%s", source)
err := pluginCommon.DownloadPlugin(storeNameStr, sourceStr, pluginBinDir[0])
if err != nil {
return nil, fmt.Errorf("failed to download plugin: %v", err)
}
logrus.Infof("downloaded store plugin %s from %s to %s", storeNameStr, sourceStr, pluginBinDir[0])
}

storeFactory, ok := builtInStores[storeNameStr]
if ok {
return storeFactory.Create(configVersion, storeConfig)
Expand Down
1 change: 1 addition & 0 deletions pkg/referrerstore/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
SpecVersion string = "0.1.0"
Version string = "version"
Name string = "name"
Source string = "source"
)

const (
Expand Down
11 changes: 11 additions & 0 deletions pkg/verifier/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"
"strings"

pluginCommon "github.com/deislabs/ratify/pkg/common/plugin"
"github.com/deislabs/ratify/pkg/verifier"
"github.com/deislabs/ratify/pkg/verifier/config"
"github.com/deislabs/ratify/pkg/verifier/plugin"
Expand Down Expand Up @@ -59,6 +60,16 @@ func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersio
return nil, fmt.Errorf("invalid plugin name for a verifier: %s", verifierNameStr)
}

// if source is specified, download the plugin
if source, ok := verifierConfig[types.Source]; ok && source != "" {
sourceStr := fmt.Sprintf("%s", source)
err := pluginCommon.DownloadPlugin(verifierNameStr, sourceStr, pluginBinDir[0])
if err != nil {
return nil, fmt.Errorf("failed to download plugin: %v", err)
}
logrus.Infof("downloaded verifier plugin %s from %s to %s", verifierNameStr, sourceStr, pluginBinDir[0])
}

verifierFactory, ok := builtInVerifiers[verifierNameStr]
if ok {
return verifierFactory.Create(configVersion, verifierConfig)
Expand Down
1 change: 1 addition & 0 deletions pkg/verifier/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
Name string = "name"
ArtifactTypes string = "artifactTypes"
NestedReferences string = "nestedReferences"
Source string = "source"
)

const (
Expand Down