Skip to content

feat(insecure): ensure builds are secure by default #419

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 pkg/stacker/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions pkg/trust/cosign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package trust

func VerifyCosign() {
}
148 changes: 148 additions & 0 deletions pkg/trust/docker.go
Original file line number Diff line number Diff line change
@@ -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,
})
}
120 changes: 120 additions & 0 deletions pkg/trust/notation.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion pkg/types/imagesource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"):]
Expand Down