diff --git a/pkg/stacker/base.go b/pkg/stacker/base.go index 34ec2e63..90e135ba 100644 --- a/pkg/stacker/base.go +++ b/pkg/stacker/base.go @@ -94,6 +94,10 @@ func SetupRootfs(o BaseLayerOpts) error { } func importContainersImage(is types.ImageSource, config types.StackerConfig, progress bool) error { + // FIXME: first verify + if !is.Insecure { + } + toImport, err := is.ContainersImageURL() if err != nil { return err diff --git a/pkg/trust/cosign.go b/pkg/trust/cosign.go new file mode 100644 index 00000000..b93d838f --- /dev/null +++ b/pkg/trust/cosign.go @@ -0,0 +1,4 @@ +package trust + +func VerifyCosign() { +} diff --git a/pkg/trust/docker.go b/pkg/trust/docker.go new file mode 100644 index 00000000..6b2f1c5b --- /dev/null +++ b/pkg/trust/docker.go @@ -0,0 +1,148 @@ +package trust + +import ( + "context" + "encoding/json" + "fmt" + + "sort" + + "github.com/docker/cli/cli/command/image" + "github.com/docker/cli/cli/trust" + "github.com/docker/distribution/reference" + "github.com/docker/docker/registry" + "github.com/sirupsen/logrus" + "github.com/storageos/go-api/types" + "github.com/theupdateframework/notary/client" + "github.com/theupdateframework/notary/tuf/data" +) + +// GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name +// as an ImageRefAndAuth struct +func GetImageReferencesAndAuth(ctx context.Context, rs registry.Service, + authResolver func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig, + imgName string, +) (ImageRefAndAuth, error) { + ref, err := reference.ParseNormalizedNamed(imgName) + if err != nil { + return ImageRefAndAuth{}, err + } + + // Resolve the Repository name from fqn to RepositoryInfo + var repoInfo *registry.RepositoryInfo + if rs != nil { + repoInfo, err = rs.ResolveRepository(ref) + } else { + repoInfo, err = registry.ParseRepositoryInfo(ref) + } + + if err != nil { + return ImageRefAndAuth{}, err + } + + authConfig := authResolver(ctx, repoInfo.Index) + return ImageRefAndAuth{ + original: imgName, + authConfig: &authConfig, + reference: ref, + repoInfo: repoInfo, + tag: getTag(ref), + digest: getDigest(ref), + }, nil +} + +// lookupTrustInfo returns processed signature and role information about a notary repository. +// This information is to be pretty printed or serialized into a machine-readable format. +func lookupTrustInfo(remote string) ([]trustTagRow, []client.RoleWithSignatures, []data.Role, error) { + ctx := context.Background() + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), remote) + if err != nil { + return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err + } + tag := imgRefAndAuth.Tag() + notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly) + if err != nil { + return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, trust.NotaryError(imgRefAndAuth.Reference().Name(), err) + } + + if err = clearChangeList(notaryRepo); err != nil { + return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err + } + defer clearChangeList(notaryRepo) + + // Retrieve all released signatures, match them, and pretty print them + allSignedTargets, err := notaryRepo.GetAllTargetMetadataByName(tag) + if err != nil { + logrus.Debug(trust.NotaryError(remote, err)) + // print an empty table if we don't have signed targets, but have an initialized notary repo + if _, ok := err.(client.ErrNoSuchTarget); !ok { + return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("no signatures or cannot access %s", remote) + } + } + signatureRows := matchReleasedSignatures(allSignedTargets) + + // get the administrative roles + adminRolesWithSigs, err := notaryRepo.ListRoles() + if err != nil { + return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("no signers for %s", remote) + } + + // get delegation roles with the canonical key IDs + delegationRoles, err := notaryRepo.GetDelegationRoles() + if err != nil { + logrus.Debugf("no delegation roles found, or error fetching them for %s: %v", remote, err) + } + + return signatureRows, adminRolesWithSigs, delegationRoles, nil +} + +func getRepoTrustInfo(remote string) ([]byte, error) { + signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(cli, remote) + if err != nil { + return []byte{}, err + } + // process the signatures to include repo admin if signed by the base targets role + for idx, sig := range signatureRows { + if len(sig.Signers) == 0 { + signatureRows[idx].Signers = append(sig.Signers, releasedRoleName) + } + } + + signerList, adminList := []trustSigner{}, []trustSigner{} + + signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles) + + for signerName, signerKeys := range signerRoleToKeyIDs { + signerKeyList := []trustKey{} + for _, keyID := range signerKeys { + signerKeyList = append(signerKeyList, trustKey{ID: keyID}) + } + signerList = append(signerList, trustSigner{signerName, signerKeyList}) + } + sort.Slice(signerList, func(i, j int) bool { return signerList[i].Name > signerList[j].Name }) + + for _, adminRole := range adminRolesWithSigs { + switch adminRole.Name { + case data.CanonicalRootRole: + rootKeys := []trustKey{} + for _, keyID := range adminRole.KeyIDs { + rootKeys = append(rootKeys, trustKey{ID: keyID}) + } + adminList = append(adminList, trustSigner{"Root", rootKeys}) + case data.CanonicalTargetsRole: + targetKeys := []trustKey{} + for _, keyID := range adminRole.KeyIDs { + targetKeys = append(targetKeys, trustKey{ID: keyID}) + } + adminList = append(adminList, trustSigner{"Repository", targetKeys}) + } + } + sort.Slice(adminList, func(i, j int) bool { return adminList[i].Name > adminList[j].Name }) + + return json.Marshal(trustRepo{ + Name: remote, + SignedTags: signatureRows, + Signers: signerList, + AdministrativeKeys: adminList, + }) +} diff --git a/pkg/trust/notation.go b/pkg/trust/notation.go new file mode 100644 index 00000000..5cbd6a1a --- /dev/null +++ b/pkg/trust/notation.go @@ -0,0 +1,120 @@ +package trust + +import ( + "context" + "errors" + "math" + "os" + "path" + + "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/verifier" + "oras.land/oras-go/v2/registry" + "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry/remote/auth" +) + +func VerifyWithNotation(reference string, tdir string) error { + // check if trustpolicy.json exists + trustpolicyPath := path.Join(tdir, "notation/trustpolicy.json") + + if _, err := os.Stat(trustpolicyPath); errors.Is(err, os.ErrNotExist) { + trustPolicy := ` + { + "version": "1.0", + "trustPolicies": [ + { + "name": "good", + "registryScopes": [ "*" ], + "signatureVerification": { + "level" : "audit" + }, + "trustStores": ["ca:good"], + "trustedIdentities": [ + "*" + ] + } + ] + }` + + file, err := os.Create(trustpolicyPath) + if err != nil { + return err + } + + defer file.Close() + + _, err = file.WriteString(trustPolicy) + if err != nil { + return err + } + } + + // start verifying signatures + ctx := context.TODO() + + // getRepositoryClient + authClient := &auth.Client{ + Credential: func(ctx context.Context, reg string) (auth.Credential, error) { + return auth.EmptyCredential, nil + }, + Cache: auth.NewCache(), + ClientID: "notation", + } + + authClient.SetUserAgent("notation/zot_tests") + + plainHTTP := true + + // Resolve referance + ref, err := registry.ParseReference(reference) + if err != nil { + return err + } + + remoteRepo := &remote.Repository{ + Client: authClient, + Reference: ref, + PlainHTTP: plainHTTP, + } + + repo := notreg.NewRepository(remoteRepo) + + manifestDesc, err := repo.Resolve(ctx, ref.Reference) + if err != nil { + return err + } + + if err := ref.ValidateReferenceAsDigest(); err != nil { + ref.Reference = manifestDesc.Digest.String() + } + + // getVerifier + newVerifier, err := verifier.NewFromConfig() + if err != nil { + return err + } + + remoteRepo = &remote.Repository{ + Client: authClient, + Reference: ref, + PlainHTTP: plainHTTP, + } + + repo = notreg.NewRepository(remoteRepo) + + configs := map[string]string{} + + verifyOpts := notation.RemoteVerifyOptions{ + ArtifactReference: ref.String(), + PluginConfig: configs, + MaxSignatureAttempts: math.MaxInt64, + } + + _, outcomes, err := notation.Verify(ctx, newVerifier, repo, verifyOpts) + if err != nil || len(outcomes) == 0 { + return ErrSignatureVerification + } + + return nil +} diff --git a/pkg/types/imagesource.go b/pkg/types/imagesource.go index b3565c60..06ed746d 100644 --- a/pkg/types/imagesource.go +++ b/pkg/types/imagesource.go @@ -50,7 +50,7 @@ type ImageSource struct { } func NewImageSource(containersImageString string) (*ImageSource, error) { - ret := &ImageSource{} + ret := &ImageSource{Insecure: false} if strings.HasPrefix(containersImageString, "oci:") { ret.Type = OCILayer ret.Url = containersImageString[len("oci:"):]