Skip to content

Commit

Permalink
feat(insecure): ensure builds are secure by default
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `insecure:` directive is now `true` by default.
Previously, it was `false`.

Fixes issue #311

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
  • Loading branch information
rchincha committed Feb 28, 2023
1 parent 7174ee3 commit 53612c8
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 1 deletion.
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

0 comments on commit 53612c8

Please sign in to comment.