Ejemplo n.º 1
0
// IsRunningImageAllowed returns true iff the policy allows running the image.
// If it returns false, err must be non-nil, and should be an PolicyRequirementError if evaluation
// succeeded but the result was rejection.
// WARNING: This validates signatures and the manifest, but does not download or validate the
// layers. Users must validate that the layers match their expected digests.
func (pc *PolicyContext) IsRunningImageAllowed(image types.Image) (res bool, finalErr error) {
	if err := pc.changeState(pcReady, pcInUse); err != nil {
		return false, err
	}
	defer func() {
		if err := pc.changeState(pcInUse, pcReady); err != nil {
			res = false
			finalErr = err
		}
	}()

	logrus.Debugf("IsRunningImageAllowed for image %s", image.IntendedDockerReference())

	reqs, err := pc.requirementsForImage(image)
	if err != nil {
		return false, err
	}

	if len(reqs) == 0 {
		return false, PolicyRequirementError("List of verification policy requirements must not be empty")
	}

	for reqNumber, req := range reqs {
		// FIXME: supply state
		allowed, err := req.isRunningImageAllowed(image)
		if !allowed {
			logrus.Debugf("Requirement %d: denied, done", reqNumber)
			return false, err
		}
		logrus.Debugf(" Requirement %d: allowed", reqNumber)
	}
	// We have tested that len(reqs) != 0, so at least one req must have explicitly allowed this image.
	logrus.Debugf("Overall: allowed")
	return true, nil
}
Ejemplo n.º 2
0
func (prm *prmMatchRepository) matchesDockerReference(image types.Image, signatureDockerReference string) bool {
	intended, signature, err := parseDockerReferences(image.IntendedDockerReference(), signatureDockerReference)
	if err != nil {
		return false
	}
	return signature.Name() == intended.Name()
}
Ejemplo n.º 3
0
func (prm *prmMatchExact) matchesDockerReference(image types.Image, signatureDockerReference string) bool {
	intended, signature, err := parseDockerReferences(image.IntendedDockerReference(), signatureDockerReference)
	if err != nil {
		return false
	}
	// Do not add default tags: image.IntendedDockerReference() has it added already per its construction, and signatureDockerReference should be exact; so, verify that now.
	if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) {
		return false
	}
	return signature.String() == intended.String()
}
Ejemplo n.º 4
0
// requirementsForImage selects the appropriate requirements for image.
func (pc *PolicyContext) requirementsForImage(image types.Image) (PolicyRequirements, error) {
	imageIdentity := image.IntendedDockerReference()
	// We don't technically need to parse it first in order to match the full name:tag,
	// but do so anyway to ensure that the intended identity really does follow that
	// format, or at least that it is not demonstrably wrong.
	ref, err := reference.ParseNamed(imageIdentity)
	if err != nil {
		return nil, err
	}
	ref = reference.WithDefaultTag(ref)

	// Look for a full match.
	fullyExpanded, err := fullyExpandedDockerReference(ref)
	if err != nil { // Coverage: This cannot currently happen.
		return nil, err
	}
	if req, ok := pc.Policy.Specific[fullyExpanded]; ok {
		logrus.Debugf(" Using specific policy section %s", fullyExpanded)
		return req, nil
	}

	// Look for a match of the repository, and then of the possible parent
	// namespaces. Note that this only happens on the expanded host names
	// and repository names, i.e. "busybox" is looked up as "docker.io/library/busybox",
	// then in its parent "docker.io/library"; in none of "busybox",
	// un-namespaced "library" nor in "" implicitly representing "library/".
	//
	// ref.FullName() == ref.Hostname() + "/" + ref.RemoteName(), so the last
	// iteration matches the host name (for any namespace).
	name := ref.FullName()
	for {
		if req, ok := pc.Policy.Specific[name]; ok {
			logrus.Debugf(" Using specific policy section %s", name)
			return req, nil
		}

		lastSlash := strings.LastIndex(name, "/")
		if lastSlash == -1 {
			break
		}
		name = name[:lastSlash]
	}

	logrus.Debugf(" Using default policy section")
	return pc.Policy.Default, nil
}
Ejemplo n.º 5
0
func (pr *prSignedBy) isRunningImageAllowed(image types.Image) (bool, error) {
	sigs, err := image.Signatures()
	if err != nil {
		return false, err
	}
	var rejections []error
	for _, s := range sigs {
		var reason error
		switch res, _, err := pr.isSignatureAuthorAccepted(image, s); res {
		case sarAccepted:
			// One accepted signature is enough.
			return true, nil
		case sarRejected:
			reason = err
		case sarUnknown:
			// Huh?! This should not happen at all; treat it as any other invalid value.
			fallthrough
		default:
			reason = fmt.Errorf(`Internal error: Unexpected signature verification result "%s"`, string(res))
		}
		rejections = append(rejections, reason)
	}
	var summary error
	switch len(rejections) {
	case 0:
		summary = PolicyRequirementError("A signature was required, but no signature exists")
	case 1:
		summary = rejections[0]
	default:
		var msgs []string
		for _, e := range rejections {
			msgs = append(msgs, e.Error())
		}
		summary = PolicyRequirementError(fmt.Sprintf("None of the signatures were accepted, reasons: %s",
			strings.Join(msgs, "; ")))
	}
	return false, summary
}
Ejemplo n.º 6
0
func (pr *prSignedBy) isSignatureAuthorAccepted(image types.Image, sig []byte) (signatureAcceptanceResult, *Signature, error) {
	switch pr.KeyType {
	case SBKeyTypeGPGKeys:
	case SBKeyTypeSignedByGPGKeys, SBKeyTypeX509Certificates, SBKeyTypeSignedByX509CAs:
		// FIXME? Reject this at policy parsing time already?
		return sarRejected, nil, fmt.Errorf(`"Unimplemented "keyType" value "%s"`, string(pr.KeyType))
	default:
		// This should never happen, newPRSignedBy ensures KeyType.IsValid()
		return sarRejected, nil, fmt.Errorf(`"Unknown "keyType" value "%s"`, string(pr.KeyType))
	}

	if pr.KeyPath != "" && pr.KeyData != nil {
		return sarRejected, nil, errors.New(`Internal inconsistency: both "keyPath" and "keyData" specified`)
	}
	// FIXME: move this to per-context initialization
	var data []byte
	if pr.KeyData != nil {
		data = pr.KeyData
	} else {
		d, err := ioutil.ReadFile(pr.KeyPath)
		if err != nil {
			return sarRejected, nil, err
		}
		data = d
	}

	// FIXME: move this to per-context initialization
	dir, err := ioutil.TempDir("", "skopeo-signedBy-")
	if err != nil {
		return sarRejected, nil, err
	}
	defer os.RemoveAll(dir)
	mech, err := newGPGSigningMechanismInDirectory(dir)
	if err != nil {
		return sarRejected, nil, err
	}

	trustedIdentities, err := mech.ImportKeysFromBytes(data)
	if err != nil {
		return sarRejected, nil, err
	}
	if len(trustedIdentities) == 0 {
		return sarRejected, nil, PolicyRequirementError("No public keys imported")
	}

	signature, err := verifyAndExtractSignature(mech, sig, signatureAcceptanceRules{
		validateKeyIdentity: func(keyIdentity string) error {
			for _, trustedIdentity := range trustedIdentities {
				if keyIdentity == trustedIdentity {
					return nil
				}
			}
			// Coverage: We use a private GPG home directory and only import trusted keys, so this should
			// not be reachable.
			return PolicyRequirementError(fmt.Sprintf("Signature by key %s is not accepted", keyIdentity))
		},
		validateSignedDockerReference: func(ref string) error {
			if !pr.SignedIdentity.matchesDockerReference(image, ref) {
				return PolicyRequirementError(fmt.Sprintf("Signature for identity %s is not accepted", ref))
			}
			return nil
		},
		validateSignedDockerManifestDigest: func(digest string) error {
			m, _, err := image.Manifest()
			if err != nil {
				return err
			}
			digestMatches, err := manifest.MatchesDigest(m, digest)
			if err != nil {
				return err
			}
			if !digestMatches {
				return PolicyRequirementError(fmt.Sprintf("Signature for digest %s does not match", digest))
			}
			return nil
		},
	})
	if err != nil {
		return sarRejected, nil, err
	}

	return sarAccepted, signature, nil
}
Ejemplo n.º 7
0
// GetSignaturesWithAcceptedAuthor returns those signatures from an image
// for which the policy accepts the author (and which have been successfully
// verified).
// NOTE: This may legitimately return an empty list and no error, if the image
// has no signatures or only invalid signatures.
// WARNING: This makes the signature contents acceptable for futher processing,
// but it does not necessarily mean that the contents of the signature are
// consistent with local policy.
// For example:
// - Do not use a an existence of an accepted signature to determine whether to run
//   a container based on this image; use IsRunningImageAllowed instead.
// - Just because a signature is accepted does not automatically mean the contents of the
//   signature are authorized to run code as root, or to affect system or cluster configuration.
func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(image types.Image) (sigs []*Signature, finalErr error) {
	if err := pc.changeState(pcReady, pcInUse); err != nil {
		return nil, err
	}
	defer func() {
		if err := pc.changeState(pcInUse, pcReady); err != nil {
			sigs = nil
			finalErr = err
		}
	}()

	logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s", image.IntendedDockerReference())

	reqs, err := pc.requirementsForImage(image)
	if err != nil {
		return nil, err
	}

	// FIXME: rename Signatures to UnverifiedSignatures
	unverifiedSignatures, err := image.Signatures()
	if err != nil {
		return nil, err
	}

	res := make([]*Signature, 0, len(unverifiedSignatures))
	for sigNumber, sig := range unverifiedSignatures {
		var acceptedSig *Signature // non-nil if accepted
		rejected := false
		// FIXME? Say more about the contents of the signature, i.e. parse it even before verification?!
		logrus.Debugf("Evaluating signature %d:", sigNumber)
	interpretingReqs:
		for reqNumber, req := range reqs {
			// FIXME: Log the requirement itself? For now, we use just the number.
			// FIXME: supply state
			switch res, as, err := req.isSignatureAuthorAccepted(image, sig); res {
			case sarAccepted:
				if as == nil { // Coverage: this should never happen
					logrus.Debugf(" Requirement %d: internal inconsistency: sarAccepted but no parsed contents", reqNumber)
					rejected = true
					break interpretingReqs
				}
				logrus.Debugf(" Requirement %d: signature accepted", reqNumber)
				if acceptedSig == nil {
					acceptedSig = as
				} else if *as != *acceptedSig { // Coverage: this should never happen
					// Huh?! Two ways of verifying the same signature blob resulted in two different parses of its already accepted contents?
					logrus.Debugf(" Requirement %d: internal inconsistency: sarAccepted but different parsed contents", reqNumber)
					rejected = true
					acceptedSig = nil
					break interpretingReqs
				}
			case sarRejected:
				logrus.Debugf(" Requirement %d: signature rejected: %s", reqNumber, err.Error())
				rejected = true
				break interpretingReqs
			case sarUnknown:
				if err != nil { // Coverage: this should never happen
					logrus.Debugf(" Requirement %d: internal inconsistency: sarUnknown but an error message %s", reqNumber, err.Error())
					rejected = true
					break interpretingReqs
				}
				logrus.Debugf(" Requirement %d: signature state unknown, continuing", reqNumber)
			default: // Coverage: this should never happen
				logrus.Debugf(" Requirement %d: internal inconsistency: unknown result %#v", reqNumber, string(res))
				rejected = true
				break interpretingReqs
			}
		}
		// This also handles the (invalid) case of empty reqs, by rejecting the signature.
		if acceptedSig != nil && !rejected {
			logrus.Debugf(" Overall: OK, signature accepted")
			res = append(res, acceptedSig)
		} else {
			logrus.Debugf(" Overall: Signature not accepted")
		}
	}
	return res, nil
}