Skip to content

Commit

Permalink
feat: add namespace to external data request key (#1201)
Browse files Browse the repository at this point in the history
  • Loading branch information
binbin-li authored Dec 14, 2023
1 parent d64c713 commit ec92e2a
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 6 deletions.
20 changes: 15 additions & 5 deletions httpserver/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ import (

const apiVersion = "externaldata.gatekeeper.sh/v1alpha1"

// verify validates provided images against the configured policy.
// The image key could be either a standalone image(repo:tag) or an image within a specific namespace([namespace]repo:tag).
// e.g.
// 1. docker.io/library/nginx:latest an image without a namespace would be evaluated by cluster-wide policy.
// 2. [ratify]docker.io/library/nginx:latest an image with a namespace would be evaluated by namespaced policy.
func (server *Server) verify(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
startTime := time.Now()
sanitizedMethod := utils.SanitizeString(r.Method)
Expand All @@ -62,20 +67,25 @@ func (server *Server) verify(ctx context.Context, w http.ResponseWriter, r *http
mu := sync.Mutex{}

// iterate over all keys
for _, subject := range providerRequest.Request.Keys {
for _, key := range providerRequest.Request.Keys {
wg.Add(1)
go func(subject string) {
go func(key string) {
defer wg.Done()
routineStartTime := time.Now()
returnItem := externaldata.Item{
Key: subject,
Key: key,
}
defer func() {
mu.Lock()
results = append(results, returnItem)
mu.Unlock()
}()
subjectReference, err := pkgUtils.ParseSubjectReference(subject)
requestKey, err := pkgUtils.ParseRequestKey(key)
if err != nil {
returnItem.Error = err.Error()
return
}
subjectReference, err := pkgUtils.ParseSubjectReference(requestKey.Subject)
if err != nil {
returnItem.Error = err.Error()
return
Expand Down Expand Up @@ -129,7 +139,7 @@ func (server *Server) verify(ctx context.Context, w http.ResponseWriter, r *http

returnItem.Value = fromVerifyResult(result, server.GetExecutor().PolicyEnforcer.GetPolicyType(ctx))
logger.GetLogger(ctx, server.LogOption).Debugf("verification: execution time for image %s: %dms", resolvedSubjectReference, time.Since(routineStartTime).Milliseconds())
}(utils.SanitizeString(subject))
}(utils.SanitizeString(key))
}
wg.Wait()
elapsedTime := time.Since(startTime).Milliseconds()
Expand Down
33 changes: 32 additions & 1 deletion pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package utils

import (
"fmt"
"regexp"
"strings"

_ "crypto/sha256" // required package for digest.Parse
Expand All @@ -27,7 +28,18 @@ import (
"github.com/opencontainers/go-digest"
)

const RatifyNamespaceEnvVar = "RATIFY_NAMESPACE"
const (
RatifyNamespaceEnvVar = "RATIFY_NAMESPACE"
subjectPattern = `(\[(.*?)\])?(.*)`
)

// RequestKey is a structured external data request key.
type RequestKey struct {
// Subject is image name in the request key.
Subject string
// Namespace is the scope of the image.
Namespace string
}

// ParseDigest parses the given string and returns a validated Digest object.
func ParseDigest(digestStr string) (digest.Digest, error) {
Expand Down Expand Up @@ -64,3 +76,22 @@ func ParseSubjectReference(subRef string) (common.Reference, error) {
func TrimSpaceAndToLower(input string) string {
return strings.ToLower(strings.TrimSpace(input))
}

// ParseRequestKey parses key string to a structured RequestKey object.
// Example 1:
// key: [gatekeeper-system]docker.io/test/hello:v1
// match slice: ["[gatekeeper-system]docker.io/test/hello:v1" "[gatekeeper-system]" "gatekeeper-system" "docker.io/test/hello:v1"]
// Example 2:
// key: docker.io/test/hello:v1
// match slice: ["docker.io/test/hello:v1" "" "" "docker.io/test/hello:v1"]
func ParseRequestKey(key string) (RequestKey, error) {
re := regexp.MustCompile(subjectPattern)
match := re.FindStringSubmatch(key)
if match == nil || len(match) < 4 {
return RequestKey{}, fmt.Errorf("invalid request key: %s", key)
}
return RequestKey{
Namespace: match[2],
Subject: match[3],
}, nil
}
56 changes: 56 additions & 0 deletions pkg/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ limitations under the License.
package utils

import (
"fmt"
"strings"
"testing"

"github.com/deislabs/ratify/pkg/common"
"github.com/opencontainers/go-digest"
)

const (
testRepo = "docker.io/test/hello:v1"
testNamespace = "test"
)

func TestParseDigest_ReturnsExpected(t *testing.T) {
dg, err := ParseDigest("sha256:a0fc570a245b09ed752c42d600ee3bb5b4f77bbd70d8898780b7ab43454530eb")

Expand Down Expand Up @@ -143,3 +149,53 @@ func TestTrimSpaceAndToLower_ReturnsExpected(t *testing.T) {
}
}
}

func TestParseRequestKey(t *testing.T) {
testCases := []struct {
name string
key string
result RequestKey
}{
{
name: "namespaced image",
key: fmt.Sprintf("[%s]%s", testNamespace, testRepo),
result: RequestKey{
Subject: testRepo,
Namespace: testNamespace,
},
},
{
name: "clustered image",
key: testRepo,
result: RequestKey{
Subject: testRepo,
Namespace: "",
},
},
{
name: "no valid image",
key: fmt.Sprintf("[%s]", testNamespace),
result: RequestKey{
Subject: "",
Namespace: testNamespace,
},
},
{
name: "empty string",
key: "",
result: RequestKey{},
},
{
name: "invalid key",
key: "\n",
result: RequestKey{},
},
}

for _, tc := range testCases {
result, _ := ParseRequestKey(tc.key)
if result.Subject != tc.result.Subject {
t.Fatalf("ParseRequestKey output expected %v actual %v", tc.result.Subject, result.Subject)
}
}
}

0 comments on commit ec92e2a

Please sign in to comment.