Skip to content

Commit

Permalink
Add Kriti Signer implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
wteiken committed Aug 20, 2018
1 parent c935672 commit 0e626c1
Show file tree
Hide file tree
Showing 11 changed files with 863 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
out/
.idea/
*~
95 changes: 95 additions & 0 deletions cmd/kritis/gcbsigner/main.go
@@ -0,0 +1,95 @@
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main

import (
"flag"
"fmt"
"os"

"github.com/golang/glog"
"github.com/grafeas/kritis/pkg/kritis/crd/buildpolicy"
"github.com/grafeas/kritis/pkg/kritis/gcbsigner"
"github.com/grafeas/kritis/pkg/kritis/metadata/containeranalysis"
"github.com/grafeas/kritis/pkg/kritis/secrets"
"golang.org/x/net/context"

"cloud.google.com/go/pubsub"
)

func main() {
gcbProject := flag.String("gcb_project", "", "Id of the project running GCB")
gcbSubscription := flag.String("gcb_subscription", "build-signer", "Name of the GCB subscription")
resourceNamespace := flag.String("resource_namespace", os.Getenv("SIGNER_NAMESPACE"), "Namespace the signer CRDs and secrets are stored in")
flag.Parse()

err := run(*gcbProject, *gcbSubscription, *resourceNamespace)
if err != nil {
glog.Fatalf("Error running signer: %v", err)
}
}

func run(gcbProject string, gcbSubscription string, ns string) error {
ctx := context.Background()
client, err := pubsub.NewClient(ctx, gcbProject)
if err != nil {
return fmt.Errorf("Could not create pubsub Client: %v", err)
}

sub := client.Subscription(gcbSubscription)
for err == nil {
glog.Infof("Listening")
err = sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
if err := process(ns, msg); err != nil {
glog.Errorf("Error signing: %v", err)
msg.Nack()
} else {
msg.Ack()
}
})
}
return fmt.Errorf("Error receiving message: %v", err)
}

func process(ns string, msg *pubsub.Message) error {
buildInfos, err := gcbsigner.ExtractImageBuildInfoFromEvent(msg)
if err != nil {
return err
}
if buildInfos == nil {
// No relevant builds in this event
return nil
}
bps, err := buildpolicy.BuildPolicies(ns)
if err != nil {
return err
}
client, err := containeranalysis.NewContainerAnalysisClient()
if err != nil {
return err
}

r := gcbsigner.New(client, &gcbsigner.Config{
Secret: secrets.Fetch,
Validate: buildpolicy.ValidateBuildPolicy,
})
for _, buildInfo := range buildInfos {
if err := r.ValidateAndSign(buildInfo, bps); err != nil {
return err
}
}
return nil
}
10 changes: 6 additions & 4 deletions pkg/kritis/apis/kritis/v1beta1/types.go
Expand Up @@ -71,10 +71,12 @@ type BuildPolicy struct {

// BuildPolicySpec is the spec for a BuildPolicy resource
type BuildPolicySpec struct {
AttestationAuthorityName string `yaml:"attestationAuthorityName"`
BuildRequirements struct {
BuiltFrom string `yaml:"builtFrom"`
} `yaml:"buildRequirements"`
AttestationAuthorityName string `yaml:"attestationAuthorityName"`
BuildRequirements BuildRequirements `yaml:"buildRequirements"`
}

type BuildRequirements struct {
BuiltFrom string `yaml:"builtFrom"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down
16 changes: 16 additions & 0 deletions pkg/kritis/apis/kritis/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions pkg/kritis/crd/buildpolicy/buildpolicy.go
@@ -0,0 +1,62 @@
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package buildpolicy

import (
"fmt"
"regexp"

"github.com/grafeas/kritis/pkg/kritis/apis/kritis/v1beta1"
clientset "github.com/grafeas/kritis/pkg/kritis/client/clientset/versioned"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
)

// ValidateFunc defines the type for Validating Build Policies
type ValidateFunc func(bp v1beta1.BuildPolicy, buildFrom string) error

// SigningPolicies returns all ISP's in the specified namespaces
// Pass in an empty string to get all ISPs in all namespaces
func BuildPolicies(namespace string) ([]v1beta1.BuildPolicy, error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("error building config: %v", err)
}

client, err := clientset.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error building clientset: %v", err)
}
list, err := client.KritisV1beta1().BuildPolicies(namespace).List(metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("error listing all image policy requirements: %v", err)
}
return list.Items, nil
}

// ValidateBuildPolicy checks if an image satisfies BP requirements.
// It returns an error if an image does not pass,
func ValidateBuildPolicy(bp v1beta1.BuildPolicy, builtFrom string) error {
ok, err := regexp.MatchString(bp.Spec.BuildRequirements.BuiltFrom, builtFrom)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("Source %q does not match required %q", builtFrom, bp.Spec.BuildRequirements.BuiltFrom)
}
return nil
}
70 changes: 70 additions & 0 deletions pkg/kritis/crd/buildpolicy/buildpolicy_test.go
@@ -0,0 +1,70 @@
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package buildpolicy

import (
"testing"

"github.com/grafeas/kritis/pkg/kritis/apis/kritis/v1beta1"
)

func Test_ValidateBuildPolicy(t *testing.T) {
var tests = []struct {
name string
requirements string
builtFrom string
expectedErr bool
}{
{
name: "simple string match",
requirements: "some_source",
builtFrom: "some_source",
},
{
name: "regexp string match",
requirements: ".*_source",
builtFrom: "some_source",
},
{
name: "no match fails",
requirements: ".*_source",
builtFrom: "no_match",
expectedErr: true,
},
{
name: "invald regexp fails",
requirements: "*",
builtFrom: "test",
expectedErr: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
bp := v1beta1.BuildPolicy{
Spec: v1beta1.BuildPolicySpec{
BuildRequirements: v1beta1.BuildRequirements{
BuiltFrom: tc.requirements,
},
},
}
err := ValidateBuildPolicy(bp, tc.builtFrom)
if tc.expectedErr != (err != nil) {
t.Errorf("expected ValidateAndSign to return error %t, actual error %s", tc.expectedErr, err)
}
})
}
}
88 changes: 88 additions & 0 deletions pkg/kritis/gcbsigner/gcb_event_parser.go
@@ -0,0 +1,88 @@
/*
Copyright 2018 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gcbsigner

import (
"encoding/json"
"fmt"
"strings"

"github.com/golang/glog"

"cloud.google.com/go/pubsub"
)

func ExtractImageBuildInfoFromEvent(msg *pubsub.Message) ([]ImageBuildInfo, error) {
// TODO this should validate the Informatian against the information in
// Container Analysis that is created by Cloud Builder
var event BuildEvent
if err := json.Unmarshal(msg.Data, &event); err != nil {
return nil, err
}
glog.Infof("build %q, status: %q", event.ID, event.Status)
glog.Infof("messages: %q", msg.Data)
if event.Status == "SUCCESS" {
glog.Infof("complete build %q", event.ID)
buildInfo := make([]ImageBuildInfo, 0, len(event.Results.Images))
for _, image := range event.Results.Images {
if strings.Contains(image.Name, ":latest") {
continue
}
imageRef := fmt.Sprintf("%s@%s", image.Name, image.Digest)
glog.Infof("process image %s", imageRef)
sourceSuffix := fmt.Sprintf("@%s", event.Source.RepoSource.CommitSha)
if len(sourceSuffix) == 1 {
sourceSuffix = fmt.Sprintf(":%s", event.Source.RepoSource.TagName)
}
if len(sourceSuffix) == 1 {
sourceSuffix = fmt.Sprintf(":%s", event.Source.RepoSource.BranchName)
}

source := fmt.Sprintf("https://source.developers.google.com/p/%s/r/%s%s", event.Source.RepoSource.ProjectID, event.Source.RepoSource.RepoName, sourceSuffix)
buildInfo = append(buildInfo, ImageBuildInfo{
BuildID: event.ID,
ImageRef: imageRef,
BuiltFrom: source,
})
}
return buildInfo, nil
}
return nil, nil
}

type BuildSource struct {
RepoSource struct {
RepoName string
ProjectID string
BranchName string
TagName string
CommitSha string
}
}

type BuildResults struct {
Images []struct {
Name string
Digest string
}
}

type BuildEvent struct {
ID string
Status string
Source BuildSource
Results BuildResults
}

0 comments on commit 0e626c1

Please sign in to comment.