Skip to content

Commit 13b9f5f

Browse files
enable image verification with a cosign verify-manifest function (#346)
* add image package and implement image profile Signed-off-by: Hirokuni-Kitahara1 <[email protected]> * update image verification Signed-off-by: Hirokuni-Kitahara1 <[email protected]> * add sample constraint with image profile Signed-off-by: Hirokuni-Kitahara1 <[email protected]> * update image verify codes Signed-off-by: Hirokuni-Kitahara1 <[email protected]> * update image verify codes Signed-off-by: Hirokuni-Kitahara1 <[email protected]>
1 parent e0d41e2 commit 13b9f5f

File tree

11 files changed

+1384
-99
lines changed

11 files changed

+1384
-99
lines changed

integrity-shield-operator/go.sum

Lines changed: 316 additions & 22 deletions
Large diffs are not rendered by default.

observer/go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/IBM/integrity-shield/observer v0.0.0-00010101000000-000000000000
77
github.com/IBM/integrity-shield/shield v0.0.0-00010101000000-000000000000
88
github.com/pkg/errors v0.9.1
9-
github.com/sigstore/k8s-manifest-sigstore v0.0.0-20210820081408-1767e96c5fe2
9+
github.com/sigstore/k8s-manifest-sigstore v0.0.0-20210909071548-2120192e4ff7
1010
github.com/sirupsen/logrus v1.8.1
1111
k8s.io/api v0.21.3
1212
k8s.io/apimachinery v0.21.3
@@ -17,7 +17,6 @@ replace (
1717
github.com/IBM/integrity-shield/observer => ./
1818
github.com/IBM/integrity-shield/shield => ../shield
1919
github.com/IBM/integrity-shield/webhook/admission-controller => ../webhook/admission-controller
20-
github.com/sigstore/k8s-manifest-sigstore => github.com/hirokuni-kitahara/k8s-manifest-sigstore v0.0.0-20210901055134-ae30242ab9d1
2120
k8s.io/kubectl => k8s.io/kubectl v0.21.2
2221

2322
)

observer/go.sum

Lines changed: 292 additions & 23 deletions
Large diffs are not rendered by default.

shield/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ require (
77
github.com/ghodss/yaml v1.0.0
88
github.com/jinzhu/copier v0.3.2
99
github.com/pkg/errors v0.9.1
10-
github.com/sigstore/k8s-manifest-sigstore v0.0.0-20210820081408-1767e96c5fe2
10+
github.com/sigstore/cosign v1.1.0
11+
github.com/sigstore/k8s-manifest-sigstore v0.0.0-20210909071548-2120192e4ff7
1112
github.com/sirupsen/logrus v1.8.1
1213
k8s.io/api v0.21.3
1314
k8s.io/apimachinery v0.21.3
@@ -17,7 +18,6 @@ require (
1718

1819
replace (
1920
github.com/IBM/integrity-shield/shield => ./
20-
github.com/sigstore/k8s-manifest-sigstore => github.com/hirokuni-kitahara/k8s-manifest-sigstore v0.0.0-20210901055134-ae30242ab9d1
2121
k8s.io/kubectl => k8s.io/kubectl v0.21.2
2222
)
2323

shield/go.sum

Lines changed: 290 additions & 20 deletions
Large diffs are not rendered by default.

shield/pkg/config/parameter.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ type KeyConfig struct {
5050
KeySecretNamespace string `json:"keySecretNamespace,omitempty"`
5151
}
5252

53+
type ImageRef string
54+
type ImageRefList []ImageRef
55+
56+
func (l ImageRefList) Match(imageRef string) bool {
57+
if len(l) == 0 {
58+
return true
59+
}
60+
for _, r := range l {
61+
if r.Match(imageRef) {
62+
return true
63+
}
64+
}
65+
return false
66+
}
67+
68+
func (r ImageRef) Match(imageRef string) bool {
69+
return k8smnfutil.MatchPattern(string(r), imageRef)
70+
}
71+
5372
type ObjectUserBindingList []ObjectUserBinding
5473

5574
type ObjectUserBinding struct {
@@ -58,6 +77,9 @@ type ObjectUserBinding struct {
5877
}
5978

6079
type ImageProfile struct {
80+
KeyConfigs []KeyConfig `json:"keyConfigs,omitempty"`
81+
Match ImageRefList `json:"match,omitempty"`
82+
Exclude ImageRefList `json:"exclude,omitempty"`
6183
}
6284

6385
func (p *ParameterObject) DeepCopyInto(p2 *ParameterObject) {
@@ -84,3 +106,18 @@ func (l ObjectUserBindingList) Match(obj unstructured.Unstructured, username str
84106
}
85107
return false
86108
}
109+
110+
// if any profile condition is defined, image profile returns enabled = true
111+
func (p ImageProfile) Enabled() bool {
112+
return len(p.Match) > 0 || len(p.Exclude) > 0
113+
}
114+
115+
// returns if this profile matches the specified image ref or not
116+
func (p ImageProfile) MatchWith(imageRef string) bool {
117+
matched := p.Match.Match(imageRef)
118+
excluded := false
119+
if len(p.Exclude) > 0 {
120+
excluded = p.Exclude.Match(imageRef)
121+
}
122+
return matched && !excluded
123+
}

shield/pkg/image/image.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//
2+
// Copyright 2020 IBM Corporation
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
package image
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"io/ioutil"
23+
"os"
24+
"path/filepath"
25+
"time"
26+
27+
"github.com/ghodss/yaml"
28+
"github.com/pkg/errors"
29+
30+
ishieldconfig "github.com/IBM/integrity-shield/shield/pkg/config"
31+
cosigncli "github.com/sigstore/cosign/cmd/cosign/cli"
32+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33+
)
34+
35+
type ImageVerifyResult struct {
36+
Object unstructured.Unstructured `json:"object"`
37+
ImageRef string `json:"imageRef"`
38+
Verified bool `json:"verified"`
39+
InScope bool `json:"inScope"`
40+
Signer string `json:"signer"`
41+
SignedTime *time.Time `json:"signedTime"`
42+
FailReason string `json:"failReason"`
43+
}
44+
45+
type ImageVerifyOption struct {
46+
KeyPath string
47+
}
48+
49+
// verify all images in a container of the specified resource
50+
func VerifyImageInManifest(resource unstructured.Unstructured, profile ishieldconfig.ImageProfile) (bool, error) {
51+
yamlBytes, err := yaml.Marshal(resource.Object)
52+
if err != nil {
53+
return false, errors.Wrap(err, "failed to yaml.Marshal() the resource")
54+
}
55+
tmpDir, err := ioutil.TempDir("", "verify-image")
56+
if err != nil {
57+
return false, fmt.Errorf("failed to create temp dir: %s", err.Error())
58+
}
59+
defer os.RemoveAll(tmpDir)
60+
61+
manifestPath := filepath.Join(tmpDir, "manifest.yaml")
62+
err = ioutil.WriteFile(manifestPath, yamlBytes, 0644)
63+
if err != nil {
64+
return false, fmt.Errorf("failed to create temp manifest file: %s", err.Error())
65+
}
66+
67+
keyPathList := []string{}
68+
for _, keyConfig := range profile.KeyConfigs {
69+
if keyConfig.KeySecretName != "" {
70+
keyPath, err := ishieldconfig.LoadKeySecret(keyConfig.KeySecretNamespace, keyConfig.KeySecretName)
71+
if err != nil {
72+
return false, errors.Wrap(err, "failed to load a key secret for image verification")
73+
}
74+
keyPathList = append(keyPathList, keyPath)
75+
}
76+
}
77+
if len(keyPathList) == 0 {
78+
keyPathList = []string{""} // for keyless verification
79+
}
80+
81+
allImagesVerified := false
82+
failReason := ""
83+
// overallFailReason := ""
84+
for _, keyPath := range keyPathList {
85+
cmd := cosigncli.VerifyManifestCommand{VerifyCommand: cosigncli.VerifyCommand{}}
86+
if keyPath != "" {
87+
cmd.KeyRef = keyPath
88+
}
89+
90+
var verifiedWithThisKey bool
91+
// currently cosigncli.VerifyManifestCommand.Exec() does not return detail information like image names and their signer names
92+
// TODO: create an issue in sigstore/cosign for this function to return some additional information
93+
iErr := cmd.Exec(context.Background(), []string{manifestPath})
94+
if iErr == nil {
95+
verifiedWithThisKey = true
96+
} else {
97+
failReason = iErr.Error()
98+
}
99+
if verifiedWithThisKey {
100+
allImagesVerified = true
101+
break
102+
}
103+
}
104+
var retErr error
105+
if !allImagesVerified {
106+
retErr = errors.New(failReason)
107+
}
108+
109+
return allImagesVerified, retErr
110+
}

shield/pkg/shield/request_handler.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2828

2929
k8smnfconfig "github.com/IBM/integrity-shield/shield/pkg/config"
30+
ishieldimage "github.com/IBM/integrity-shield/shield/pkg/image"
3031
"github.com/sigstore/k8s-manifest-sigstore/pkg/k8smanifest"
3132
"github.com/sigstore/k8s-manifest-sigstore/pkg/util/kubeutil"
3233
"github.com/sigstore/k8s-manifest-sigstore/pkg/util/mapnode"
@@ -217,19 +218,29 @@ func RequestHandler(req admission.Request, paramObj *k8smnfconfig.ParameterObjec
217218
allow = true
218219
message = "not protected"
219220
}
220-
// image verify result
221+
222+
// image verify
221223
imageAllow := true
222224
imageMessage := ""
223-
if len(result.ImageVerifyResults) != 0 {
224-
for _, res := range result.ImageVerifyResults {
225-
if res.InScope && !res.Verified {
226-
imageAllow = false
225+
var imageVerifyResults []ishieldimage.ImageVerifyResult
226+
if paramObj.ImageProfile.Enabled() {
227+
_, err = ishieldimage.VerifyImageInManifest(resource, paramObj.ImageProfile)
228+
if err != nil {
229+
log.Errorf("failed to verify images: %s", err.Error())
230+
imageAllow = false
231+
imageMessage = "Image signature verification is required, but failed to verify signature: " + err.Error()
232+
233+
} else {
234+
for _, res := range imageVerifyResults {
235+
if res.InScope && !res.Verified {
236+
imageAllow = false
237+
imageMessage = "Image signature verification is required, but failed to verify signature: " + res.FailReason
238+
break
239+
}
227240
}
228241
}
229242
}
230-
if !imageAllow {
231-
imageMessage = "Image signature verification is required, but failed to verify signature."
232-
}
243+
233244
if allow && !imageAllow {
234245
message = imageMessage
235246
allow = false

webhook/admission-controller/go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/ghodss/yaml v1.0.0
88
github.com/jinzhu/copier v0.3.2
99
github.com/pkg/errors v0.9.1
10-
github.com/sigstore/k8s-manifest-sigstore v0.0.0-20210820081408-1767e96c5fe2
10+
github.com/sigstore/k8s-manifest-sigstore v0.0.0-20210909071548-2120192e4ff7
1111
github.com/sirupsen/logrus v1.8.1
1212
k8s.io/api v0.21.3
1313
k8s.io/apimachinery v0.21.3
@@ -18,7 +18,6 @@ require (
1818
replace (
1919
github.com/IBM/integrity-shield/shield => ../../shield
2020
github.com/IBM/integrity-shield/webhook/admission-controller => ./
21-
github.com/sigstore/k8s-manifest-sigstore => github.com/hirokuni-kitahara/k8s-manifest-sigstore v0.0.0-20210901055134-ae30242ab9d1
2221
k8s.io/kubectl => k8s.io/kubectl v0.21.2
2322

2423
)

0 commit comments

Comments
 (0)