voucher

package module
v1.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2019 License: MIT Imports: 13 Imported by: 0

README

Build Status codecov Go Report Card

voucher

Table of Contents

Introduction

Voucher is the missing piece in the binary authorization toolchain which enables you to secure your software supply pipeline. Binary authorization uses an admission controller such as Kritis, which pulls information about a container image from a metadata server such as Grafeas to ensure that the image is not deployed to production unless it has passed an appropriate suite of checks. As running checks on containers during deployment is time consuming and prevents rapid rollout of changes, the checks the admission controller utilizes to verify an image is ready for production should be run at build time. Voucher does exactly that.

Voucher was designed to be called from your CI/DC pipeline, after an image is built, but before that image is deployed to production. Voucher pulls the newly built image from your image registry; runs it through all of the checks that were requested, and generates attestations for every check that the image passes. Those attestations (OpenPGP signatures of container digests) are then pushed to the metadata server, where Kritis can verify them.

Voucher presently includes the following checks:

Test Name Description
diy Can the image be downloaded from our container registry?
nobody Was the image built to run as a user who is not root?
snakeoil Is the image free of known security issues?
provenance Was the image built by us or a trusted system?

Installing voucher

Voucher Server

Install voucher_server by running:

$ go get -u github.com/Shopify/voucher/cmd/voucher_server

This will download and install the voucher server binary into $GOPATH/bin directory.

Voucher Standalone

Install the standalone version of Voucher, voucher_cli, by running:

$ go get -u github.com/Shopify/voucher/cmd/voucher_cli

This will download and install the voucher binary into $GOPATH/bin directory.

Voucher Client

Voucher Client is a tool for connecting to a running Voucher server.

Install the client, voucher_client, by running:

$ go get -u github.com/Shopify/voucher/cmd/voucher_client

This will download and install the voucher client into $GOPATH/bin directory.

Voucher Server and Standalone

See the Tutorial for more thorough setup instructions.

Configuration

An example configuration file can be found in the config directory.

The configuration can be written as a toml, json, or yaml file, and you can specify the path to the configuration file using "-c".

Below are the configuration options for Voucher Standalone and Server:

Group Key Description
dryrun When set, don't create attestations.
scanner The vulnerability scanner to use ("clair" or "gca").
failon The minimum vulnerability to fail on. Discussed below.
valid_repos A list of repos that are owned by your team/organization.
image_project The project in the metadata server that image information is stored.
binauth_project The project in the metadata server that the binauth information is stored.
timeout The number of seconds to spend checking an image, before failing (voucher standalone only).
checks (test name here) A test that is active when running "all" tests.
server port The port that the server can be reached on.
server timeout The number of seconds to spend checking an image, before failing.
server require_auth Require the use of Basic Auth, with the username and password from the configuration.
server username The username that Voucher server users must use.
server password A password hashed with the bcrypt algorithm, for use with the username.
ejson dir The path to the ejson keys directory.
ejson secrets The path to the ejson secrets.
clair address The hostname that Clair exists at. If "http://" or "https://" is omitted, this will default to HTTPS.

Configuration options can be overridden at runtime by setting the appropriate flag. For example, if you set the "port" flag when running voucher_server, that value will override whatever is in the configuration.

Scanner

The scanner option in the configuration is used to select the Vulnerability scanner.

This option supports two values:

  • c or clair to use an instance of CoreOS's Clair.
  • g or gca to use Google Container Analysis.

If you decide to use Clair, you will need to update the clair configuration block to specify the correct address for the server.

Fail-On: Failing on vulnerabilities

The failon option allows you to set the minimum vulnerability to consider an image insecure.

This option supports the following:

  • "negligible"
  • "low"
  • "medium"
  • "unknown"
  • "high"
  • "critical"

For example, if you set failon to "high", only "high" and "critical" vulnerabilities will prevent the image from being attested. A value of "low" will cause "low", "medium", "unknown", "high", and "critical" vulnerabilities to prevent the image from being attested failure.

Valid Repos

The valid_repos option in the configuration is used to limit which repositories images must be from to pass the DIY check.

This option takes a list of repos, which are compared against the repos that images live in. An image will pass if it starts with any of the items in the list.

For example:

{
    "valid_repos": [
        "gcr.io/team-images/",
        "gcr.io/external-images/specific-project",
    ]
}

Will allow images that start with gcr.io/team-images/ and gcr.io/external-images/specific-project/ to pass the DIY check, while blocking other gcr.io/external-images/.

Enabling Checks

You can enable certain checks for the "all" option by updating the checks block in the configuration.

For example:

[checks]
diy      = true
nobody   = true
provenance = false
snakeoil = true

With this configuration, the "diy", "nobody", and "snakeoil" checks would run when running "all" checks. The "provenance" check will be ignored unless called directly.

Running Voucher
Using voucher standalone to check an image

You can run Voucher's standalone version by voucher_cli, using the following syntax:

$ voucher_cli <test to run> --image <image to check> [other options]

voucher_cli supports the following flags:

Flag Short Flag Description
--config -c The path to a configuration file that should be used.
--dryrun When set, don't create attestations.
--scanner The vulnerability scanner to use ("clair" or "gca").
--failon The minimum vulnerability to fail on. Discussed above.
--image -i The image to check and attest.
--timeout The number of seconds to spend checking an image, before failing.

For example:

$ voucher_cli all --image gcr.io/path/to/image@sha256:ab7524b7375fbf09b3784f0bbd9cb2505700dd05e03ce5f5e6d262bf2f5ac51c

This would run all possible tests, or all tests that are enabled by the configuration, against the image located at the passed URL.

You can also run an individual test, by specifying that test:

$ voucher_cli diy --image gcr.io/path/to/image@sha256:ab7524b7375fbf09b3784f0bbd9cb2505700dd05e03ce5f5e6d262bf2f5ac51c

This would run the "diy" test.

Using Voucher Server to check an image

You can run Voucher in server mode by launching voucher_server, using the following syntax:

$ voucher_server [--port <port number>]

voucher_server supports the following flags:

Flag Short Flag Description
--config -c The path to a configuration file that should be used.
--port -p Set the port to listen on.
--timeout The number of seconds to spend checking an image, before failing.

For example:

$ voucher_server --port 8000

This would launch the server, utilizing port 8000.

You can connect to Voucher over http.

For example, using curl:

$ curl -X POST -H "Content-Type: application/json" -d "{\"image_url\": \"gcr.io/path/to/image@sha256:ab7524b7375fbf09b3784f0bbd9cb2505700dd05e03ce5f5e6d262bf2f5ac51c\"}" http://localhost:8000/all

The response will be something along the following lines:

{
    "image": "gcr.io/path/to/image@sha256:ab7524b7375fbf09b3784f0bbd9cb2505700dd05e03ce5f5e6d262bf2f5ac51c",
    "success": false,
    "results": [
        {
            "name": "provenance",
            "error": "no occurrences returned for image",
            "success": false,
            "attested": false
        },
        {
            "name": "snakeoil",
            "success": true,
            "attested": true
        },
        {
            "name": "diy",
            "success": true,
            "attested": true
        },
        {
            "name": "nobody",
            "success": true,
            "attested": true
        }
    ]
}

More details about Voucher server can be read in the API documentation.

Voucher Client

Configuration

The configuration for Voucher Client can be written as a toml, json, or yaml file, and you can specify the path to the configuration file using "-c". By default, the configuration is expected to be located at ~/.voucher{.yaml,.toml,.json}.

Below are the configuration options for Voucher Standalone and Server:

Key Description
hostname The Voucher server to connect to.
timeout The number of seconds to wait before failing (defaults to 240).
username Username to authenticate against Voucher with.
password Password to authenticate against Voucher with.

Configuration options can be overridden at runtime by setting the appropriate flag. For example, if you set the "port" flag when running voucher_server, that value will override whatever is in the configuration.

Here is an example (yaml encoded) configuration file:

---
hostname: "https://my-voucher-server"
username: "<username>"
password: "<password>"
Using Voucher Client

While you can use curl to make API calls against Voucher, you can also use voucher_client to save from making HTTP requests by hand. Unlike the other Voucher tools, voucher_client will look up the appropriate canonical version of an image reference if passed a tagged image reference.

$ voucher_client [--voucher <server> --check <check to run>] <image path>

voucher_client supports the following flags:

Flag Short Flag Description
--config The path to your configuration file, (default is $HOME/.voucher.yaml)
--check -c The Check to run on the image ("all" for all checks).
--voucher -v The Voucher server to connect to.
--username Username to authenticate against Voucher with.
--password Password to authenticate against Voucher with.
--timeout -t The number of seconds to wait before failing (defaults to 240).

For example:

$ voucher_client -v http://localhost:8000 gcr.io/path/to/image:latest

The output will be something along the following lines:

 - Attesting image: gcr.io/path/to/image@sha256:ab7524b7375fbf09b3784f0bbd9cb2505700dd05e03ce5f5e6d262bf2f5ac51c
   ✗ nobody failed
   ✗ snakeoil failed, err: vulnernable to 1 vulnerabilities: CVE-2018-12345 (high)
   ✓ diy succeeded, but wasn't attested, err: rpc error: code = AlreadyExists desc = Requested entity already exists

Contributing

Please refer to the Contributing document if you are interested in contributing to voucher!

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultCheckFactories = make(CheckFactories)

DefaultCheckFactories is the default CheckFactory collection.

View Source
var ErrNoAuth = errors.New("no configured Auth")

ErrNoAuth should be returned when something that depends on an Auth does not have one.

View Source
var ErrNoCheck = errors.New("requested check doesn't exist")

ErrNoCheck is an error that is returned when a requested check hasn't been registered.

Functions

func AddKeyToKeyRingFromReader

func AddKeyToKeyRingFromReader(keyring *KeyRing, name string, reader io.Reader) error

AddKeyToKeyRingFromReader imports the PGP keys stored in a Reader into the passed KeyRing.

func AuthToClient

func AuthToClient(ctx context.Context, auth Auth, image reference.Named) (*http.Client, error)

AuthToClient takes a struct implementing Auth and returns a new http.Client with the authentication details setup by Auth.GetTokenSource.

DEPRECATED: This function has been superceded by Auth.ToClient. This function now calls that method directly.

func GetCheckFactories

func GetCheckFactories(names ...string) (map[string]Check, error)

GetCheckFactories gets new copies of the Checks from their registered CheckFactories.

func IsCheckFactoryRegistered

func IsCheckFactoryRegistered(name string) bool

IsCheckFactoryRegistered returns true if the passed CheckFactory was registered.

func NewVulnerabilityError

func NewVulnerabilityError(vuls []Vulnerability) (err error)

NewVulnerabilityError creates a new VulnerabilityError with the passed Vulnerabilities.

func RegisterCheckFactory

func RegisterCheckFactory(name string, creator CheckFactory)

RegisterCheckFactory adds a CheckFactory to the DefaultCheckFactories that can be run. Once a Check is added, it can be referenced by the name that was passed in when this function was called.

func ShouldIncludeVulnerability

func ShouldIncludeVulnerability(test Vulnerability, baseline Severity) bool

ShouldIncludeVulnerability returns true if the passed vulnerability should be included in our vulnerability report.

func Sign

func Sign(signer *openpgp.Entity, msg string) (string, error)

Sign creates the signature for the attestation

func ToMapStringBool

func ToMapStringBool(in map[string]interface{}) (out map[string]bool)

ToMapStringBool takes a map[string]interface{} and converts it to a map[string]bool (dropping any values that do not cast to booleans cleanly).

func Verify

func Verify(keyring openpgp.KeyRing, signed string) (string, error)

Verify verifies a signed message's signature, and returns the message that was signed as well as an error if applicable.

Types

type AttestationPayload

type AttestationPayload struct {
	CheckName string
	Body      string
}

AttestationPayload is a structure that contains the Attestation data that we want to create an MetadataItem from.

func NewAttestationPayload

func NewAttestationPayload(checkName string, payload string) AttestationPayload

NewAttestationPayload creates a new AttestationPayload for the check with the passed name, with the payload as the body. The payload will then be signed by the key associated with the check (referenced by the checkName).

func (*AttestationPayload) Sign

func (payload *AttestationPayload) Sign(keyring *KeyRing) (string, string, error)

Sign takes a keyring and signs the body of the payload with it, returning that as a string.

type Auth

type Auth interface {
	GetTokenSource(context.Context, reference.Named) (oauth2.TokenSource, error)
	ToClient(ctx context.Context, image reference.Named) (*http.Client, error)
}

Auth is an interface that wraps an to an OAuth2 system, to simplify the path from having an image reference to getting access to the data that makes up that image from the registry it lives in.

type AuthorizedCheck

type AuthorizedCheck interface {
	Check
	SetAuth(Auth)
}

AuthorizedCheck represents a Voucher check that needs to be authorized. For example, a check that needs to connect to the registry will need to implement AuthorizedCheck.

type Check

type Check interface {
	Check(ImageData) (bool, error)
}

Check represents a Voucher test.

type CheckFactories

type CheckFactories map[string]CheckFactory

CheckFactories is a map of registered CheckFactories.

func (CheckFactories) Get

func (cf CheckFactories) Get(name string) CheckFactory

Get returns the CheckFactory with the passed name.

func (CheckFactories) GetNewChecks

func (cf CheckFactories) GetNewChecks(names ...string) (map[string]Check, error)

GetNewChecks gets new copies of the Checks from each of their registered CheckFactory.

func (CheckFactories) Register

func (cf CheckFactories) Register(name string, creator CheckFactory)

Register adds a new CheckFactory to this CheckFactories.

type CheckFactory

type CheckFactory func() Check

CheckFactory is a type of function that creates a new Check.

type CheckResult

type CheckResult struct {
	ImageData ImageData   `json:"-"`
	Name      string      `json:"name"`
	Err       string      `json:"error,omitempty"`
	Success   bool        `json:"success"`
	Attested  bool        `json:"attested"`
	Details   interface{} `json:"details,omitempty"`
}

CheckResult describes the result of a Check. If a check failed, it will have a status of false. If a check succeeded, but its Attestation creation failed, Succes will be true, Attested will be false. Err will contain the first error to occur.

type ImageData

type ImageData = reference.Canonical

ImageData is a Canonical Reference to the Image (includes digest and URL).

func NewImageData

func NewImageData(url string) (ImageData, error)

NewImageData creates a new ImageData item with the passed URL as a reference to the target image.

type KeyRing

type KeyRing struct {
	// contains filtered or unexported fields
}

KeyRing wraps an OpenPGP EntityList (which implements openpgp.KeyRing), adding support for determining which key is associated with which check. KeyRing implements openpgp.KeyRing, thus can be used in place of it where appropriate.

func NewKeyRing

func NewKeyRing() *KeyRing

NewKeyRing creates a new keyring from the passed EntityList. The keys in the input EntityList are then associated with the

func (*KeyRing) AddEntities

func (keyring *KeyRing) AddEntities(name string, input openpgp.EntityList)

AddEntities adds new keys from the passed EntityList to the keyring for use.

func (*KeyRing) DecryptionKeys

func (keyring *KeyRing) DecryptionKeys() []openpgp.Key

DecryptionKeys returns all private keys that are valid for decryption.

func (*KeyRing) GetSignerByName

func (keyring *KeyRing) GetSignerByName(name string) (*openpgp.Entity, error)

GetSignerByName gets the first available signing key associated with the passed name.

func (*KeyRing) KeysById

func (keyring *KeyRing) KeysById(id uint64) []openpgp.Key

KeysById returns the set of keys that have the given key id.

func (*KeyRing) KeysByIdUsage

func (keyring *KeyRing) KeysByIdUsage(id uint64, requiredUsage byte) []openpgp.Key

KeysByIdUsage returns the set of keys with the given id that also meet the key usage given by requiredUsage. The requiredUsage is expressed as the bitwise-OR of packet.KeyFlag* values.

type MetadataCheck

type MetadataCheck interface {
	Check
	SetMetadataClient(MetadataClient)
}

MetadataCheck represents a Voucher check that interacts directly with a metadata server.

type MetadataClient

type MetadataClient interface {
	CanAttest() bool
	NewPayloadBody(ImageData) (string, error)
	GetMetadata(ImageData, MetadataType) ([]MetadataItem, error)
	AddAttestationToImage(ImageData, AttestationPayload) (MetadataItem, error)
}

MetadataClient is an interface that represents something that communicates with the Metadata server.

type MetadataItem

type MetadataItem interface {
	String() string // String returns a string representation of the MetadataItem.
}

MetadataItem is a type which can be returned as a string.

type MetadataType

type MetadataType string

MetadataType is a type which represents a MetadataClient's MetadataItem type.

const (
	// VulnerabilityType is specific to MetadataItem containing vulnerabilities.
	VulnerabilityType MetadataType = "vulnerability"
	// BuildDetailsType refers to MetadataItems containing image build details.
	BuildDetailsType MetadataType = "build details"
	// AttestationType refers to MetadataItems containing Binary Authorization Attestations.
	AttestationType MetadataType = "attestation"
)

type RepoValidatorCheck added in v1.1.0

type RepoValidatorCheck interface {
	Check
	SetValidRepos(repos []string)
}

RepoValidatorCheck represents a Voucher check that validates the passed image is from a valid repo.

type Request

type Request struct {
	ImageURL string `json:"image_url"`
}

Request describes the Voucher API request structure.

type Response

type Response struct {
	Image   string        `json:"image"`
	Success bool          `json:"success"`
	Results []CheckResult `json:"results"`
}

Response describes the response from a Check call.

func NewResponse

func NewResponse(reference reference.Reference, results []CheckResult) (checkResponse Response)

NewResponse creates a new Response for the passed ImageData, with the passed results.

type Severity

type Severity int

Severity is a integer that represents how severe a vulnerability is.

const (
	NegligibleSeverity Severity = iota
	LowSeverity        Severity = iota
	MediumSeverity     Severity = iota
	UnknownSeverity    Severity = iota
	HighSeverity       Severity = iota
	CriticalSeverity   Severity = iota
)

Severity constants, which represent the severities that we track. Other systems' severities should be converted to one of the following.

func StringToSeverity

func StringToSeverity(s string) (Severity, error)

StringToSeverity returns the matching Severity to the passed string. Returns an error if there isn't a matching Severity.

func (Severity) String

func (s Severity) String() string

String returns a string representation of a Severity.

type Suite

type Suite struct {
	// contains filtered or unexported fields
}

Suite is a suite of Checks, which

func NewSuite

func NewSuite() *Suite

NewSuite creates a new Suite.

func (*Suite) Add

func (cs *Suite) Add(name string, check Check)

Add adds a Check to the checks that can be run. Once a Check is added, it can be referenced by the name that was passed in when this function was called.

func (*Suite) Attest

func (cs *Suite) Attest(metadataClient MetadataClient, results []CheckResult) []CheckResult

Attest runs through the passed []CheckResult and if a CheckResult is marked as successful, runs the CreateAttestion function in the Check corresponding to that CheckResult. Each CheckResult is updated with the details (or error) and the resulting []CheckResult is returned.

func (*Suite) Get

func (cs *Suite) Get(name string) (Check, error)

Get returns the requested Check, or nil if one does not exist.

func (*Suite) Has

func (cs *Suite) Has(name string) bool

Has returns true if the passed check exists. Returns false if it does not.

func (*Suite) Run

func (cs *Suite) Run(imageData ImageData) []CheckResult

Run executes each of the Checks specified by the activeChecks parameter.

For example, if a Suite has the "diy" and "nobody" tests, calling

Run(imageData)

will run the "diy" and "nobody" tests.

Run returns a []CheckResult with a CheckResult for each Check that was run.

func (*Suite) RunAndAttest

func (cs *Suite) RunAndAttest(metadataClient MetadataClient, imageData ImageData) []CheckResult

RunAndAttest calls Run, followed by Attest, and returns the final []CheckResult.

type VulnerabilitiesError

type VulnerabilitiesError struct {
	Vulnerabilities []Vulnerability
}

VulnerabilitiesError is an error that also contains a list of vulnerabilities.

func (VulnerabilitiesError) Error

func (err VulnerabilitiesError) Error() string

Error returns the error message for the VulnerabilitiesError

type Vulnerability

type Vulnerability struct {
	Name        string   `json:"name"`        // Name of the Vulnerability, or it's CVE number.
	Description string   `json:"description"` // Description of the Vulnerability.
	Severity    Severity `json:"severity"`    // Severity of the Vulnerability.
	FixedBy     string   `json:"fixed_by"`    // If this vulnerability was fixed, what it was fixed by.
}

Vulnerability is a type that describes a security vulnerability. Third-party scanner vulnerabilities should be converted to this type.

type VulnerabilityCheck

type VulnerabilityCheck interface {
	Check
	SetScanner(VulnerabilityScanner)
}

VulnerabilityCheck represents a Voucher test.

type VulnerabilityScanner

type VulnerabilityScanner interface {

	// FailOn sets the minimum Severity to consider an image vulnerable.
	FailOn(Severity)

	// Scan runs a scan against the passed ImageData and returns a slice of
	// Vulnerabilities.
	Scan(ImageData) ([]Vulnerability, error)
}

VulnerabilityScanner is an interface which represents a scanners that can be used to check an image for vulnerabilities. VulnerabilityScanners implement the Scan method, which takes ImageData as input and returns a slice of Vulnerabilities.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL