// Copyright 2019 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 fuzz implements go-fuzz fuzzing functions for the validator.go file.
// See https://github.com/dvyukov/go-fuzz
package fuzz
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/GoogleCloudPlatform/config-validator/pkg/api/validator"
"github.com/GoogleCloudPlatform/config-validator/pkg/gcv"
"google.golang.org/protobuf/encoding/protojson"
)
var vdt *gcv.Validator
// Initialize the validator only once, then reuse it across Fuzz invocations.
func init() {
// Determine the init files directory at runtime, since it is different
// when running under OSS-Fuzz.
// See https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/
binaryPath := os.Args[0]
var localPolicyDir string
if strings.HasPrefix(binaryPath, "/tmp/go-fuzz") {
// Running locally, use relative test/cf directory.
localPolicyDir = "../../test/cf"
} else {
// Running under OSS-Fuzz.
// The build script for it dumps the files under this directory.
binaryDir := filepath.Dir(binaryPath)
localPolicyDir = filepath.Join(binaryDir, "validatorfiles")
}
localLibraryDir := filepath.Join(localPolicyDir, "library")
var err error
vdt, err = gcv.NewValidator([]string{localPolicyDir}, localLibraryDir)
if err != nil {
panic(fmt.Sprintf("unexpected error creating validator: %v", err))
}
}
// Fuzz fuzzes reviewing assets.
func Fuzz(data []byte) (score int) {
// Try interpreting data as an Asset.
// Exit early if invalid.
asset := &validator.Asset{}
if err := protojson.Unmarshal(data, asset); err != nil {
return 0
}
// Try reviewing the asset.
// The actual findings don't matter, but it shouldn't crash.
if _, err := vdt.ReviewAsset(context.Background(), asset); err != nil {
return 0
}
return 1
}
// Copyright 2019 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.
// The definition of validator RPC service.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc v4.24.0
// source: validator.proto
package validator
import (
accesscontextmanagerpb "cloud.google.com/go/accesscontextmanager/apiv1/accesscontextmanagerpb"
assetpb "cloud.google.com/go/asset/apiv1/assetpb"
iampb "cloud.google.com/go/iam/apiv1/iampb"
orgpolicypb "cloud.google.com/go/orgpolicy/apiv1/orgpolicypb"
orgpolicypb1 "cloud.google.com/go/orgpolicy/apiv2/orgpolicypb"
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
structpb "google.golang.org/protobuf/types/known/structpb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Asset contains GCP resource metadata and additional metadata set on a resource, such as Cloud IAM policy.
// WARNING: these field names are directly used to structure data passed to templates.
// Changes in field names will result in changes to the data provided to the templates.
type Asset struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// GCP resource name as defined by Cloud Asset Inventory.
// See https://cloud.google.com/resource-manager/docs/cloud-asset-inventory/resource-name-format for the format.
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// Cloud Asset Inventory type (CAI API v1 format). Example: "sqladmin.googleapis.com/Instance" is the type of Cloud SQL instance.
// This field has a redundant "asset" prefix to be consistent with Cloud Asset Inventory output.
// See https://cloud.google.com/resource-manager/docs/cloud-asset-inventory/overview#supported_resource_types for the list of types.
AssetType string `protobuf:"bytes,2,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"`
// Ancestral project/folder/org information in a path-like format.
// For example, a GCP project that is nested under a folder may have the following path:
// organization/9999/folder/8888/project/7777
AncestryPath string `protobuf:"bytes,3,opt,name=ancestry_path,json=ancestryPath,proto3" json:"ancestry_path,omitempty"`
// GCP resource metadata.
Resource *assetpb.Resource `protobuf:"bytes,4,opt,name=resource,proto3" json:"resource,omitempty"`
// IAM policy associated with the resource.
IamPolicy *iampb.Policy `protobuf:"bytes,5,opt,name=iam_policy,json=iamPolicy,proto3" json:"iam_policy,omitempty"`
// Ancestor list as returned by CAI (added sometime around Oct 2019)
Ancestors []string `protobuf:"bytes,6,rep,name=ancestors,proto3" json:"ancestors,omitempty"`
// Representation of the Cloud Organization Policy set on an asset. For each
// asset, there could be multiple Organization policies with different
// constraints.
OrgPolicy []*orgpolicypb.Policy `protobuf:"bytes,7,rep,name=org_policy,json=orgPolicy,proto3" json:"org_policy,omitempty"`
// Representation of the Cloud Organization access policy.
//
// Types that are assignable to AccessContextPolicy:
//
// *Asset_AccessPolicy
// *Asset_AccessLevel
// *Asset_ServicePerimeter
AccessContextPolicy isAsset_AccessContextPolicy `protobuf_oneof:"access_context_policy"`
// Representation of the Cloud Organization Policy V2 set on an asset.
// There can be multiple V2 Organization Policies for an asset.
V2OrgPolicies []*orgpolicypb1.Policy `protobuf:"bytes,11,rep,name=v2_org_policies,json=v2OrgPolicies,proto3" json:"v2_org_policies,omitempty"`
}
func (x *Asset) Reset() {
*x = Asset{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Asset) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Asset) ProtoMessage() {}
func (x *Asset) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Asset.ProtoReflect.Descriptor instead.
func (*Asset) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{0}
}
func (x *Asset) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Asset) GetAssetType() string {
if x != nil {
return x.AssetType
}
return ""
}
func (x *Asset) GetAncestryPath() string {
if x != nil {
return x.AncestryPath
}
return ""
}
func (x *Asset) GetResource() *assetpb.Resource {
if x != nil {
return x.Resource
}
return nil
}
func (x *Asset) GetIamPolicy() *iampb.Policy {
if x != nil {
return x.IamPolicy
}
return nil
}
func (x *Asset) GetAncestors() []string {
if x != nil {
return x.Ancestors
}
return nil
}
func (x *Asset) GetOrgPolicy() []*orgpolicypb.Policy {
if x != nil {
return x.OrgPolicy
}
return nil
}
func (m *Asset) GetAccessContextPolicy() isAsset_AccessContextPolicy {
if m != nil {
return m.AccessContextPolicy
}
return nil
}
func (x *Asset) GetAccessPolicy() *accesscontextmanagerpb.AccessPolicy {
if x, ok := x.GetAccessContextPolicy().(*Asset_AccessPolicy); ok {
return x.AccessPolicy
}
return nil
}
func (x *Asset) GetAccessLevel() *accesscontextmanagerpb.AccessLevel {
if x, ok := x.GetAccessContextPolicy().(*Asset_AccessLevel); ok {
return x.AccessLevel
}
return nil
}
func (x *Asset) GetServicePerimeter() *accesscontextmanagerpb.ServicePerimeter {
if x, ok := x.GetAccessContextPolicy().(*Asset_ServicePerimeter); ok {
return x.ServicePerimeter
}
return nil
}
func (x *Asset) GetV2OrgPolicies() []*orgpolicypb1.Policy {
if x != nil {
return x.V2OrgPolicies
}
return nil
}
type isAsset_AccessContextPolicy interface {
isAsset_AccessContextPolicy()
}
type Asset_AccessPolicy struct {
AccessPolicy *accesscontextmanagerpb.AccessPolicy `protobuf:"bytes,8,opt,name=access_policy,json=accessPolicy,proto3,oneof"`
}
type Asset_AccessLevel struct {
AccessLevel *accesscontextmanagerpb.AccessLevel `protobuf:"bytes,9,opt,name=access_level,json=accessLevel,proto3,oneof"`
}
type Asset_ServicePerimeter struct {
ServicePerimeter *accesscontextmanagerpb.ServicePerimeter `protobuf:"bytes,10,opt,name=service_perimeter,json=servicePerimeter,proto3,oneof"`
}
func (*Asset_AccessPolicy) isAsset_AccessContextPolicy() {}
func (*Asset_AccessLevel) isAsset_AccessContextPolicy() {}
func (*Asset_ServicePerimeter) isAsset_AccessContextPolicy() {}
// Constraint contains the configuration for a constraint.
type Constraint struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// ApiVersion is the version of the API.
ApiVersion string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"`
// Kind is the kind of object.
Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"`
// Metadata contains the user-provided constraint metadata.
Metadata *structpb.Value `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"`
// Spec is the object spec.
Spec *structpb.Value `protobuf:"bytes,6,opt,name=spec,proto3" json:"spec,omitempty"`
}
func (x *Constraint) Reset() {
*x = Constraint{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Constraint) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Constraint) ProtoMessage() {}
func (x *Constraint) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Constraint.ProtoReflect.Descriptor instead.
func (*Constraint) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{1}
}
func (x *Constraint) GetApiVersion() string {
if x != nil {
return x.ApiVersion
}
return ""
}
func (x *Constraint) GetKind() string {
if x != nil {
return x.Kind
}
return ""
}
func (x *Constraint) GetMetadata() *structpb.Value {
if x != nil {
return x.Metadata
}
return nil
}
func (x *Constraint) GetSpec() *structpb.Value {
if x != nil {
return x.Spec
}
return nil
}
// Violation contains the relevant information to explain how a constraint is violated.
type Violation struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The name of the constraint that's violated.
Constraint string `protobuf:"bytes,1,opt,name=constraint,proto3" json:"constraint,omitempty"`
// GCP resource name. This is the same name in Asset.
Resource string `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"`
// Human readable error message.
Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
// Metadata is optional. It contains the constraint-specific information that can potentially be used for remediation.
// Example: In a firewall rule constraint violation, Metadata can contain the open port number.
Metadata *structpb.Value `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"`
// The full constraint configuration.
ConstraintConfig *Constraint `protobuf:"bytes,5,opt,name=constraint_config,json=constraintConfig,proto3" json:"constraint_config,omitempty"`
// The constraint severity
Severity string `protobuf:"bytes,6,opt,name=severity,proto3" json:"severity,omitempty"`
}
func (x *Violation) Reset() {
*x = Violation{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Violation) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Violation) ProtoMessage() {}
func (x *Violation) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Violation.ProtoReflect.Descriptor instead.
func (*Violation) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{2}
}
func (x *Violation) GetConstraint() string {
if x != nil {
return x.Constraint
}
return ""
}
func (x *Violation) GetResource() string {
if x != nil {
return x.Resource
}
return ""
}
func (x *Violation) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *Violation) GetMetadata() *structpb.Value {
if x != nil {
return x.Metadata
}
return nil
}
func (x *Violation) GetConstraintConfig() *Constraint {
if x != nil {
return x.ConstraintConfig
}
return nil
}
func (x *Violation) GetSeverity() string {
if x != nil {
return x.Severity
}
return ""
}
type AddDataRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Assets []*Asset `protobuf:"bytes,1,rep,name=assets,proto3" json:"assets,omitempty"`
}
func (x *AddDataRequest) Reset() {
*x = AddDataRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddDataRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddDataRequest) ProtoMessage() {}
func (x *AddDataRequest) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddDataRequest.ProtoReflect.Descriptor instead.
func (*AddDataRequest) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{3}
}
func (x *AddDataRequest) GetAssets() []*Asset {
if x != nil {
return x.Assets
}
return nil
}
type AddDataResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *AddDataResponse) Reset() {
*x = AddDataResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AddDataResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddDataResponse) ProtoMessage() {}
func (x *AddDataResponse) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddDataResponse.ProtoReflect.Descriptor instead.
func (*AddDataResponse) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{4}
}
type AuditRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *AuditRequest) Reset() {
*x = AuditRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuditRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuditRequest) ProtoMessage() {}
func (x *AuditRequest) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuditRequest.ProtoReflect.Descriptor instead.
func (*AuditRequest) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{5}
}
type AuditResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Violations []*Violation `protobuf:"bytes,1,rep,name=violations,proto3" json:"violations,omitempty"`
}
func (x *AuditResponse) Reset() {
*x = AuditResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AuditResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AuditResponse) ProtoMessage() {}
func (x *AuditResponse) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AuditResponse.ProtoReflect.Descriptor instead.
func (*AuditResponse) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{6}
}
func (x *AuditResponse) GetViolations() []*Violation {
if x != nil {
return x.Violations
}
return nil
}
type ResetRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ResetRequest) Reset() {
*x = ResetRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResetRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResetRequest) ProtoMessage() {}
func (x *ResetRequest) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResetRequest.ProtoReflect.Descriptor instead.
func (*ResetRequest) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{7}
}
type ResetResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ResetResponse) Reset() {
*x = ResetResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResetResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResetResponse) ProtoMessage() {}
func (x *ResetResponse) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResetResponse.ProtoReflect.Descriptor instead.
func (*ResetResponse) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{8}
}
type ReviewRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Assets []*Asset `protobuf:"bytes,1,rep,name=assets,proto3" json:"assets,omitempty"`
}
func (x *ReviewRequest) Reset() {
*x = ReviewRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReviewRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReviewRequest) ProtoMessage() {}
func (x *ReviewRequest) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReviewRequest.ProtoReflect.Descriptor instead.
func (*ReviewRequest) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{9}
}
func (x *ReviewRequest) GetAssets() []*Asset {
if x != nil {
return x.Assets
}
return nil
}
type ReviewResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Violations []*Violation `protobuf:"bytes,1,rep,name=violations,proto3" json:"violations,omitempty"`
}
func (x *ReviewResponse) Reset() {
*x = ReviewResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_validator_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ReviewResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ReviewResponse) ProtoMessage() {}
func (x *ReviewResponse) ProtoReflect() protoreflect.Message {
mi := &file_validator_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ReviewResponse.ProtoReflect.Descriptor instead.
func (*ReviewResponse) Descriptor() ([]byte, []int) {
return file_validator_proto_rawDescGZIP(), []int{10}
}
func (x *ReviewResponse) GetViolations() []*Violation {
if x != nil {
return x.Violations
}
return nil
}
var File_validator_proto protoreflect.FileDescriptor
var file_validator_proto_rawDesc = []byte{
0x0a, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x09, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x1a, 0x1a, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x69, 0x61, 0x6d, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x22, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x63,
0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x73,
0x73, 0x65, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x29, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x72, 0x67, 0x70, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x72, 0x67, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3a, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x69, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e,
0x74, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x61,
0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x3b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x74, 0x79, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x65, 0x73,
0x73, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3f,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f,
0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f,
0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
0x29, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x72,
0x67, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x6f, 0x72, 0x67, 0x70, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb9, 0x05, 0x0a, 0x05, 0x41,
0x73, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x65,
0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x73,
0x73, 0x65, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x6e, 0x63, 0x65, 0x73,
0x74, 0x72, 0x79, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
0x61, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x72, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x3b, 0x0a, 0x08,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x61, 0x73,
0x73, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,
0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x69, 0x61, 0x6d,
0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x69, 0x61, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x52, 0x09, 0x69, 0x61, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12,
0x1c, 0x0a, 0x09, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03,
0x28, 0x09, 0x52, 0x09, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x40, 0x0a,
0x0a, 0x6f, 0x72, 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x07, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x21, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
0x2e, 0x6f, 0x72, 0x67, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x52, 0x09, 0x6f, 0x72, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12,
0x5c, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79,
0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x63,
0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x76, 0x31,
0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x48, 0x00, 0x52,
0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x59, 0x0a,
0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x09, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x69, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e, 0x74,
0x65, 0x78, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63,
0x63, 0x65, 0x73, 0x73, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x63, 0x63,
0x65, 0x73, 0x73, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x68, 0x0a, 0x11, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x18, 0x0a, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x69, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x63, 0x6f, 0x6e, 0x74,
0x65, 0x78, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x48, 0x00,
0x52, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x74,
0x65, 0x72, 0x12, 0x49, 0x0a, 0x0f, 0x76, 0x32, 0x5f, 0x6f, 0x72, 0x67, 0x5f, 0x70, 0x6f, 0x6c,
0x69, 0x63, 0x69, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6f, 0x72, 0x67, 0x70, 0x6f,
0x6c, 0x69, 0x63, 0x79, 0x2e, 0x76, 0x32, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0d,
0x76, 0x32, 0x4f, 0x72, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x42, 0x17, 0x0a,
0x15, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f,
0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0xa1, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x73, 0x74,
0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x32, 0x0a, 0x08, 0x6d, 0x65,
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a,
0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0xf5, 0x01, 0x0a, 0x09, 0x56,
0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x73,
0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f,
0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x32,
0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0x12, 0x42, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74,
0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,
0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72,
0x61, 0x69, 0x6e, 0x74, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69,
0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69,
0x74, 0x79, 0x22, 0x3a, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x06, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72,
0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x06, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x22, 0x11,
0x0a, 0x0f, 0x41, 0x64, 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x45, 0x0a, 0x0d, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
0x6f, 0x72, 0x2e, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x76, 0x69,
0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x0e, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x65,
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x0f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x0a, 0x0d, 0x52, 0x65, 0x76,
0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x06, 0x61, 0x73,
0x73, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x61, 0x6c,
0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x52, 0x06, 0x61, 0x73,
0x73, 0x65, 0x74, 0x73, 0x22, 0x46, 0x0a, 0x0e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x76, 0x61, 0x6c,
0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x0a, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x8c, 0x02, 0x0a,
0x09, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x42, 0x0a, 0x07, 0x41, 0x64,
0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x19, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f,
0x72, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1a, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x41, 0x64, 0x64,
0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3c,
0x0a, 0x05, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x17, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61,
0x74, 0x6f, 0x72, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x18, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x41, 0x75, 0x64,
0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x05,
0x52, 0x65, 0x73, 0x65, 0x74, 0x12, 0x17, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f,
0x72, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18,
0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x06, 0x52, 0x65,
0x76, 0x69, 0x65, 0x77, 0x12, 0x18, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72,
0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19,
0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65,
0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0c, 0x5a, 0x0a, 0x76,
0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_validator_proto_rawDescOnce sync.Once
file_validator_proto_rawDescData = file_validator_proto_rawDesc
)
func file_validator_proto_rawDescGZIP() []byte {
file_validator_proto_rawDescOnce.Do(func() {
file_validator_proto_rawDescData = protoimpl.X.CompressGZIP(file_validator_proto_rawDescData)
})
return file_validator_proto_rawDescData
}
var file_validator_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_validator_proto_goTypes = []interface{}{
(*Asset)(nil), // 0: validator.Asset
(*Constraint)(nil), // 1: validator.Constraint
(*Violation)(nil), // 2: validator.Violation
(*AddDataRequest)(nil), // 3: validator.AddDataRequest
(*AddDataResponse)(nil), // 4: validator.AddDataResponse
(*AuditRequest)(nil), // 5: validator.AuditRequest
(*AuditResponse)(nil), // 6: validator.AuditResponse
(*ResetRequest)(nil), // 7: validator.ResetRequest
(*ResetResponse)(nil), // 8: validator.ResetResponse
(*ReviewRequest)(nil), // 9: validator.ReviewRequest
(*ReviewResponse)(nil), // 10: validator.ReviewResponse
(*assetpb.Resource)(nil), // 11: google.cloud.asset.v1.Resource
(*iampb.Policy)(nil), // 12: google.iam.v1.Policy
(*orgpolicypb.Policy)(nil), // 13: google.cloud.orgpolicy.v1.Policy
(*accesscontextmanagerpb.AccessPolicy)(nil), // 14: google.identity.accesscontextmanager.v1.AccessPolicy
(*accesscontextmanagerpb.AccessLevel)(nil), // 15: google.identity.accesscontextmanager.v1.AccessLevel
(*accesscontextmanagerpb.ServicePerimeter)(nil), // 16: google.identity.accesscontextmanager.v1.ServicePerimeter
(*orgpolicypb1.Policy)(nil), // 17: google.cloud.orgpolicy.v2.Policy
(*structpb.Value)(nil), // 18: google.protobuf.Value
}
var file_validator_proto_depIdxs = []int32{
11, // 0: validator.Asset.resource:type_name -> google.cloud.asset.v1.Resource
12, // 1: validator.Asset.iam_policy:type_name -> google.iam.v1.Policy
13, // 2: validator.Asset.org_policy:type_name -> google.cloud.orgpolicy.v1.Policy
14, // 3: validator.Asset.access_policy:type_name -> google.identity.accesscontextmanager.v1.AccessPolicy
15, // 4: validator.Asset.access_level:type_name -> google.identity.accesscontextmanager.v1.AccessLevel
16, // 5: validator.Asset.service_perimeter:type_name -> google.identity.accesscontextmanager.v1.ServicePerimeter
17, // 6: validator.Asset.v2_org_policies:type_name -> google.cloud.orgpolicy.v2.Policy
18, // 7: validator.Constraint.metadata:type_name -> google.protobuf.Value
18, // 8: validator.Constraint.spec:type_name -> google.protobuf.Value
18, // 9: validator.Violation.metadata:type_name -> google.protobuf.Value
1, // 10: validator.Violation.constraint_config:type_name -> validator.Constraint
0, // 11: validator.AddDataRequest.assets:type_name -> validator.Asset
2, // 12: validator.AuditResponse.violations:type_name -> validator.Violation
0, // 13: validator.ReviewRequest.assets:type_name -> validator.Asset
2, // 14: validator.ReviewResponse.violations:type_name -> validator.Violation
3, // 15: validator.Validator.AddData:input_type -> validator.AddDataRequest
5, // 16: validator.Validator.Audit:input_type -> validator.AuditRequest
7, // 17: validator.Validator.Reset:input_type -> validator.ResetRequest
9, // 18: validator.Validator.Review:input_type -> validator.ReviewRequest
4, // 19: validator.Validator.AddData:output_type -> validator.AddDataResponse
6, // 20: validator.Validator.Audit:output_type -> validator.AuditResponse
8, // 21: validator.Validator.Reset:output_type -> validator.ResetResponse
10, // 22: validator.Validator.Review:output_type -> validator.ReviewResponse
19, // [19:23] is the sub-list for method output_type
15, // [15:19] is the sub-list for method input_type
15, // [15:15] is the sub-list for extension type_name
15, // [15:15] is the sub-list for extension extendee
0, // [0:15] is the sub-list for field type_name
}
func init() { file_validator_proto_init() }
func file_validator_proto_init() {
if File_validator_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_validator_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Asset); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Constraint); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Violation); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddDataRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AddDataResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuditRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AuditResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ResetRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ResetResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ReviewRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_validator_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ReviewResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_validator_proto_msgTypes[0].OneofWrappers = []interface{}{
(*Asset_AccessPolicy)(nil),
(*Asset_AccessLevel)(nil),
(*Asset_ServicePerimeter)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_validator_proto_rawDesc,
NumEnums: 0,
NumMessages: 11,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_validator_proto_goTypes,
DependencyIndexes: file_validator_proto_depIdxs,
MessageInfos: file_validator_proto_msgTypes,
}.Build()
File_validator_proto = out.File
file_validator_proto_rawDesc = nil
file_validator_proto_goTypes = nil
file_validator_proto_depIdxs = nil
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// ValidatorClient is the client API for Validator service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type ValidatorClient interface {
// AddData adds GCP resource metadata to be audited later.
AddData(ctx context.Context, in *AddDataRequest, opts ...grpc.CallOption) (*AddDataResponse, error)
// Audit checks the GCP resource metadata that has been added via AddData to determine if any of the constraint is violated.
Audit(ctx context.Context, in *AuditRequest, opts ...grpc.CallOption) (*AuditResponse, error)
// Reset clears previously added data from the underlying query evaluation engine.
Reset(ctx context.Context, in *ResetRequest, opts ...grpc.CallOption) (*ResetResponse, error)
// Review checks the GCP resources and returns any constraint violations. Note that referential checks are not supported
// with this mode.
Review(ctx context.Context, in *ReviewRequest, opts ...grpc.CallOption) (*ReviewResponse, error)
}
type validatorClient struct {
cc grpc.ClientConnInterface
}
func NewValidatorClient(cc grpc.ClientConnInterface) ValidatorClient {
return &validatorClient{cc}
}
func (c *validatorClient) AddData(ctx context.Context, in *AddDataRequest, opts ...grpc.CallOption) (*AddDataResponse, error) {
out := new(AddDataResponse)
err := c.cc.Invoke(ctx, "/validator.Validator/AddData", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *validatorClient) Audit(ctx context.Context, in *AuditRequest, opts ...grpc.CallOption) (*AuditResponse, error) {
out := new(AuditResponse)
err := c.cc.Invoke(ctx, "/validator.Validator/Audit", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *validatorClient) Reset(ctx context.Context, in *ResetRequest, opts ...grpc.CallOption) (*ResetResponse, error) {
out := new(ResetResponse)
err := c.cc.Invoke(ctx, "/validator.Validator/Reset", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *validatorClient) Review(ctx context.Context, in *ReviewRequest, opts ...grpc.CallOption) (*ReviewResponse, error) {
out := new(ReviewResponse)
err := c.cc.Invoke(ctx, "/validator.Validator/Review", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ValidatorServer is the server API for Validator service.
type ValidatorServer interface {
// AddData adds GCP resource metadata to be audited later.
AddData(context.Context, *AddDataRequest) (*AddDataResponse, error)
// Audit checks the GCP resource metadata that has been added via AddData to determine if any of the constraint is violated.
Audit(context.Context, *AuditRequest) (*AuditResponse, error)
// Reset clears previously added data from the underlying query evaluation engine.
Reset(context.Context, *ResetRequest) (*ResetResponse, error)
// Review checks the GCP resources and returns any constraint violations. Note that referential checks are not supported
// with this mode.
Review(context.Context, *ReviewRequest) (*ReviewResponse, error)
}
// UnimplementedValidatorServer can be embedded to have forward compatible implementations.
type UnimplementedValidatorServer struct {
}
func (*UnimplementedValidatorServer) AddData(context.Context, *AddDataRequest) (*AddDataResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddData not implemented")
}
func (*UnimplementedValidatorServer) Audit(context.Context, *AuditRequest) (*AuditResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Audit not implemented")
}
func (*UnimplementedValidatorServer) Reset(context.Context, *ResetRequest) (*ResetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Reset not implemented")
}
func (*UnimplementedValidatorServer) Review(context.Context, *ReviewRequest) (*ReviewResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Review not implemented")
}
func RegisterValidatorServer(s *grpc.Server, srv ValidatorServer) {
s.RegisterService(&_Validator_serviceDesc, srv)
}
func _Validator_AddData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddDataRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ValidatorServer).AddData(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/validator.Validator/AddData",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ValidatorServer).AddData(ctx, req.(*AddDataRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Validator_Audit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AuditRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ValidatorServer).Audit(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/validator.Validator/Audit",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ValidatorServer).Audit(ctx, req.(*AuditRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Validator_Reset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ValidatorServer).Reset(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/validator.Validator/Reset",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ValidatorServer).Reset(ctx, req.(*ResetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Validator_Review_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ReviewRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ValidatorServer).Review(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/validator.Validator/Review",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ValidatorServer).Review(ctx, req.(*ReviewRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Validator_serviceDesc = grpc.ServiceDesc{
ServiceName: "validator.Validator",
HandlerType: (*ValidatorServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "AddData",
Handler: _Validator_AddData_Handler,
},
{
MethodName: "Audit",
Handler: _Validator_Audit_Handler,
},
{
MethodName: "Reset",
Handler: _Validator_Reset_Handler,
},
{
MethodName: "Review",
Handler: _Validator_Review_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "validator.proto",
}
// Copyright 2019 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 asset
import (
"encoding/json"
"regexp"
"strings"
"github.com/GoogleCloudPlatform/config-validator/pkg/api/validator"
"github.com/GoogleCloudPlatform/config-validator/pkg/gcv/configs"
"github.com/golang/glog"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const logRequestsVerboseLevel = 2
func ValidateAsset(asset *validator.Asset) error {
var result *multierror.Error
if asset.GetName() == "" {
result = multierror.Append(result, errors.New("missing asset name"))
}
if asset.GetAncestryPath() == "" {
result = multierror.Append(result, errors.Errorf("asset %q missing ancestry path", asset.GetName()))
}
if asset.GetAssetType() == "" {
result = multierror.Append(result, errors.Errorf("asset %q missing type", asset.GetName()))
}
if asset.GetResource() == nil && asset.GetIamPolicy() == nil && asset.GetOrgPolicy() == nil && asset.GetAccessContextPolicy() == nil && asset.GetV2OrgPolicies() == nil {
result = multierror.Append(result, errors.Errorf("asset %q missing all of these: resource, IAM policy, Org Policy, Access Context Policy, v2 Org Policy", asset.GetName()))
}
return result.ErrorOrNil()
}
func ConvertResourceViaJSONToInterface(asset *validator.Asset) (interface{}, error) {
if asset == nil {
return nil, nil
}
m := &protojson.MarshalOptions{
UseProtoNames: true,
}
if asset.Resource != nil {
CleanStructValue(asset.Resource.Data)
}
glog.V(logRequestsVerboseLevel).Infof("converting asset to golang interface: %v", asset)
buf, err := m.Marshal(asset)
if err != nil {
return nil, errors.Wrapf(err, "marshalling to json with asset %s: %v", asset.Name, asset)
}
var f interface{}
if err := json.Unmarshal(buf, &f); err != nil {
return nil, errors.Wrapf(err, "marshalling from json with asset %s: %v", asset.Name, asset)
}
return f, nil
}
// SanitizeAncestryPath will populate the AncestryPath field from the ancestors list, or fix the pre-populated one
// if no ancestry list is provided.
func SanitizeAncestryPath(asset *validator.Asset) error {
if len(asset.Ancestors) != 0 {
asset.AncestryPath = AncestryPath(asset.Ancestors)
return nil
}
if asset.AncestryPath != "" {
asset.AncestryPath = configs.NormalizeAncestry(asset.AncestryPath)
return nil
}
return errors.Errorf("no ancestry information for asset %s", asset.String())
}
// AncestryPath returns the ancestry path from a given ancestors list
func AncestryPath(ancestors []string) string {
cnt := len(ancestors)
revAncestors := make([]string, len(ancestors))
for idx := 0; idx < cnt; idx++ {
revAncestors[cnt-idx-1] = ancestors[idx]
}
return strings.Join(revAncestors, "/")
}
// ConvertCAIToK8s will convert a supported CAI Asset to a K8S resource and populate any omitted fields.
func ConvertCAIToK8s(asset map[string]interface{}) (*unstructured.Unstructured, error) {
groupKind, found, err := unstructured.NestedString(asset, "asset_type")
if err != nil {
return nil, errors.Wrapf(err, "failed to access asset_type field")
}
if !found {
return nil, errors.Errorf("asset_type field not found")
}
parts := strings.Split(groupKind, "/")
if len(parts) != 2 {
return nil, errors.Errorf("expected asset_type to be of form \"<group>/<kind>\", got %s", groupKind)
}
group := parts[0]
kind := parts[1]
// CAI pretends that the core resources are part of the "k8s.io" apiGroup. For compatibility with what one would
// see in kubernetes, we set the group to empty string ("").
if group == "k8s.io" {
group = ""
}
version, found, err := unstructured.NestedString(asset, "resource", "version")
if err != nil {
return nil, errors.Wrapf(err, "failed to access resource.version field")
}
if !found {
return nil, errors.Errorf("resource.version field not found")
}
resource, found, err := unstructured.NestedMap(asset, "resource", "data")
if err != nil {
return nil, errors.Wrapf(err, "failed to access resource.data field")
}
if !found {
return nil, errors.Errorf("resource.data field not found")
}
u := &unstructured.Unstructured{Object: resource}
u.SetGroupVersionKind(schema.GroupVersionKind{
Group: group,
Version: version,
Kind: kind,
})
ancestors, found, err := unstructured.NestedStringSlice(asset, "ancestors")
if err != nil {
return nil, errors.Wrapf(err, "failed to access ancestors field")
}
if !found {
return nil, errors.Errorf("ancestors field not found")
}
annotations := u.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations["validator.forsetisecurity.org/ancestorPath"] = AncestryPath(ancestors)
u.SetAnnotations(annotations)
return u, nil
}
// ConvertToAdmissionRequest converts a CAI asset containing a K8S type to an AdmissionRequest which is the format that
// the Gatekeeper Constraint Framework target expects.
func ConvertToAdmissionRequest(asset map[string]interface{}) (*admissionv1beta1.AdmissionRequest, error) {
resource, err := ConvertCAIToK8s(asset)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert CAI asset to k8s resource")
}
resourceJSON, err := json.Marshal(resource.Object)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal k8s resource (converted from CAI asset) to JSON")
}
gvk := resource.GroupVersionKind()
req := &admissionv1beta1.AdmissionRequest{
Kind: metav1.GroupVersionKind{
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
},
Object: runtime.RawExtension{
Raw: resourceJSON,
},
Name: resource.GetName(),
}
return req, nil
}
// k8s assset names will follow pattern:
// //container.googleapis.com/projects/*/(locations|zones)/*/clusters/*/k8s
var assetPath = regexp.MustCompile(`^//container\.googleapis\.com/projects/[^/]*/(locations|zones)/[^/]*/clusters/[^/]*/k8s`)
// IsK8S returns true if the CAI asset is an asset from a kubernetes cluster.
func IsK8S(asset map[string]interface{}) bool {
assetName, found, err := unstructured.NestedString(asset, "name")
if !found || err != nil {
return false
}
return assetPath.MatchString(assetName)
}
// Copyright 2019 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 asset
import (
"google.golang.org/protobuf/types/known/structpb"
)
// CleanProtoValue recursively updates proto Values that have a nil .Kind field
// to be a NullValue to avoid issues with the jsonpb.Marshaler.
// This issue arose when calling GCV from python.
func CleanProtoValue(v *structpb.Value) {
if v == nil {
return
}
switch t := v.Kind.(type) {
case *structpb.Value_NullValue, *structpb.Value_NumberValue, *structpb.Value_StringValue, *structpb.Value_BoolValue:
case *structpb.Value_StructValue:
CleanStructValue(t.StructValue)
case *structpb.Value_ListValue:
if list := t.ListValue; list != nil {
for i := range list.Values {
CleanProtoValue(list.Values[i])
}
}
default: // No other kinds should be allowed (including nil).
v.Kind = &structpb.Value_NullValue{}
}
}
func CleanStructValue(s *structpb.Struct) {
if s == nil {
return
}
for k := range s.Fields {
CleanProtoValue(s.Fields[k])
}
}
// Copyright 2019 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 gcptarget is a constraint framework target for config-validator to use for integrating with the opa constraint framework.
package gcptarget
import (
"encoding/json"
"errors"
"fmt"
"log"
"regexp"
"strings"
"github.com/GoogleCloudPlatform/config-validator/pkg/api/validator"
asset2 "github.com/GoogleCloudPlatform/config-validator/pkg/asset"
"github.com/open-policy-agent/frameworks/constraint/pkg/core/constraints"
"github.com/open-policy-agent/frameworks/constraint/pkg/handler"
"github.com/open-policy-agent/frameworks/constraint/pkg/types"
"google.golang.org/protobuf/encoding/protojson"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// Name is the target name for GCPTarget
const Name = "validation.gcp.forsetisecurity.org"
// GCPTarget is the constraint framework target for CAI asset data
type GCPTarget struct {
}
var _ handler.TargetHandler = &GCPTarget{}
// New returns a new GCPTarget
func New() *GCPTarget {
return &GCPTarget{}
}
// ToMatcher converts .spec.match in mutators to Matcher.
func (h *GCPTarget) ToMatcher(constraint *unstructured.Unstructured) (constraints.Matcher, error) {
match, ok, err := unstructured.NestedMap(constraint.Object, "spec", "match")
if err != nil {
return nil, fmt.Errorf("unable to get spec.match: %w", err)
}
if !ok {
return &matcher{ancestries: []string{"**"}, excludedAncestries: []string{}}, nil
}
include, ok, err := unstructured.NestedStringSlice(match, "ancestries")
if err != nil {
return nil, fmt.Errorf("unable to get string slice from spec.match.ancestries: %w", err)
}
if !ok {
include, ok, err = unstructured.NestedStringSlice(match, "target")
if err != nil {
return nil, fmt.Errorf("unable to get string slice from spec.match.target: %w", err)
}
if !ok {
include = []string{"**"}
}
}
exclude, ok, err := unstructured.NestedStringSlice(match, "excludedAncestries")
if err != nil {
return nil, fmt.Errorf("unable to get string slice from spec.match.excludedAncestries: %w", err)
}
if !ok {
exclude, ok, err = unstructured.NestedStringSlice(match, "exclude")
if err != nil {
return nil, fmt.Errorf("unable to get string slice from spec.match.exclude: %w", err)
}
if !ok {
exclude = []string{}
}
}
return &matcher{
ancestries: include,
excludedAncestries: exclude,
}, nil
}
// MatchSchema implements client.MatchSchemaProvider
func (g *GCPTarget) MatchSchema() apiextensions.JSONSchemaProps {
return apiextensions.JSONSchemaProps{
Type: "object",
Properties: map[string]apiextensions.JSONSchemaProps{
"target": {
Type: "array",
Items: &apiextensions.JSONSchemaPropsOrArray{
Schema: &apiextensions.JSONSchemaProps{
Type: "string",
},
},
},
"exclude": {
Type: "array",
Items: &apiextensions.JSONSchemaPropsOrArray{
Schema: &apiextensions.JSONSchemaProps{
Type: "string",
},
},
},
"ancestries": {
Type: "array",
Items: &apiextensions.JSONSchemaPropsOrArray{
Schema: &apiextensions.JSONSchemaProps{
Type: "string",
},
},
},
"excludedAncestries": {
Type: "array",
Items: &apiextensions.JSONSchemaPropsOrArray{
Schema: &apiextensions.JSONSchemaProps{
Type: "string",
},
},
},
},
}
}
// GetName implements handler.TargetHandler
func (g *GCPTarget) GetName() string {
return Name
}
// ProcessData implements handler.TargetHandler
func (g *GCPTarget) ProcessData(obj interface{}) (bool, []string, interface{}, error) {
return false, nil, nil, errors.New("storing data for referential constraint eval is not supported at this time.")
}
// HandleReview implements handler.TargetHandler
func (g *GCPTarget) HandleReview(obj interface{}) (bool, interface{}, error) {
switch asset := obj.(type) {
case *validator.Asset:
return g.handleAsset(asset)
case map[string]interface{}:
if _, found, err := unstructured.NestedString(asset, "name"); !found || err != nil {
return false, nil, err
}
if _, found, err := unstructured.NestedString(asset, "asset_type"); !found || err != nil {
return false, nil, err
}
if _, found, err := unstructured.NestedString(asset, "ancestry_path"); !found || err != nil {
return false, nil, err
}
_, foundResource, err := unstructured.NestedMap(asset, "resource")
if err != nil {
return false, nil, err
}
_, foundIam, err := unstructured.NestedMap(asset, "iam_policy")
if err != nil {
return false, nil, err
}
foundOrgPolicy := false
if asset["org_policy"] != nil {
foundOrgPolicy = true
}
foundV2OrgPolicy := false
if asset["v2_org_policies"] != nil {
foundV2OrgPolicy = true
}
_, foundAccessPolicy, err := unstructured.NestedMap(asset, "access_policy")
if err != nil {
return false, nil, err
}
_, foundAcessLevel, err := unstructured.NestedMap(asset, "access_level")
if err != nil {
return false, nil, err
}
_, foundServicePerimeter, err := unstructured.NestedMap(asset, "service_perimeter")
if err != nil {
return false, nil, err
}
if !foundIam && !foundResource && !foundOrgPolicy && !foundV2OrgPolicy && !foundAccessPolicy && !foundAcessLevel && !foundServicePerimeter {
return false, nil, nil
}
resourceTypes := 0
if foundResource {
resourceTypes++
}
if foundIam {
resourceTypes++
}
if foundOrgPolicy {
resourceTypes++
}
if foundV2OrgPolicy {
resourceTypes++
}
if foundAccessPolicy {
resourceTypes++
}
if foundAcessLevel {
resourceTypes++
}
if foundServicePerimeter {
resourceTypes++
}
if resourceTypes > 1 {
return false, nil, fmt.Errorf("malformed asset has more than one of: resource, iam policy, org policy, access context policy: %v", asset)
}
return true, asset, nil
}
return false, nil, nil
}
// handleAsset handles input from CAI assets as received via the gRPC interface.
func (g *GCPTarget) handleAsset(asset *validator.Asset) (bool, interface{}, error) {
if asset.Resource == nil {
return false, nil, fmt.Errorf("CAI asset's resource field is nil %s", asset)
}
asset2.CleanStructValue(asset.Resource.Data)
m := &protojson.MarshalOptions{
UseProtoNames: true,
}
buf, err := m.Marshal(asset)
if err != nil {
return false, nil, fmt.Errorf("marshalling to json with asset %s: %v. %w", asset.Name, asset, err)
}
var f interface{}
if err := json.Unmarshal(buf, &f); err != nil {
return false, nil, fmt.Errorf("marshalling from json with asset %s: %v. %w", asset.Name, asset, err)
}
return true, f, nil
}
// HandleViolation implements handler.TargetHandler
func (g *GCPTarget) HandleViolation(result *types.Result) error {
return nil
}
/*
cases
organizations/*
organizations/[0-9]+/*
organizations/[0-9]+/folders/*
organizations/[0-9]+/folders/[0-9]+/*
organizations/[0-9]+/folders/[0-9]+/projects/*
organizations/[0-9]+/folders/[0-9]+/projects/[0-9]+
folders/*
folders/[0-9]+/*
folders/[0-9]+/projects/*
folders/[0-9]+/projects/[0-9]+
projects/*
projects/[0-9]+
*/
const (
organization = "organizations"
folder = "folders"
project = "projects"
)
const (
stateStart = "stateStart"
stateFolder = "stateFolder"
stateProject = "stateProject"
)
var numberRegex = regexp.MustCompile(`^[0-9]+\*{0,2}$`)
// From https://cloud.google.com/resource-manager/docs/creating-managing-projects:
// The project ID must be a unique string of 6 to 30 lowercase letters, digits, or hyphens. It must start with a letter, and cannot have a trailing hyphen.
var projectIDRegex = regexp.MustCompile(`^[a-z][a-z0-9-]{5,27}[a-z0-9]$`)
// checkPathGlob
func checkPathGlob(expression string) error {
// check for path components / numbers
parts := strings.Split(expression, "/")
state := stateStart
for i := 0; i < len(parts); i++ {
item := parts[i]
switch {
case item == organization:
if state != stateStart {
return fmt.Errorf("unexpected %s element %d in %s", item, i, expression)
}
state = stateFolder
case item == folder:
if state != stateStart && state != stateFolder {
return fmt.Errorf("unexpected %s element %d in %s", item, i, expression)
}
state = stateFolder
case item == project:
state = stateProject
case item == "*":
case item == "**":
case item == "unknown":
case numberRegex.MatchString(item):
case state == stateProject && projectIDRegex.MatchString(item):
default:
return fmt.Errorf("unexpected item %s element %d in %s", item, i, expression)
}
}
return nil
}
func checkPathGlobs(rs []string) error {
for idx, r := range rs {
if err := checkPathGlob(r); err != nil {
return fmt.Errorf("idx [%d]: %w", idx, err)
}
}
return nil
}
// ValidateConstraint implements handler.TargetHandler
func (g *GCPTarget) ValidateConstraint(constraint *unstructured.Unstructured) error {
ancestries, ancestriesFound, ancestriesErr := unstructured.NestedStringSlice(constraint.Object, "spec", "match", "ancestries")
targets, targetsFound, targetsErr := unstructured.NestedStringSlice(constraint.Object, "spec", "match", "target")
if ancestriesFound && targetsFound {
return errors.New("only one of spec.match.ancestries and spec.match.target can be specified")
} else if ancestriesFound {
if ancestriesErr != nil {
return fmt.Errorf("invalid spec.match.ancestries: %s", ancestriesErr)
}
if ancestriesErr := checkPathGlobs(ancestries); ancestriesErr != nil {
return fmt.Errorf("invalid glob in spec.match.ancestries: %w", ancestriesErr)
}
} else if targetsFound {
// TODO b/232980918: replace with zapLogger.Warn
log.Print(
"spec.match.target is deprecated and will be removed in a future release. Use spec.match.ancestries instead",
)
if targetsErr != nil {
return fmt.Errorf("invalid spec.match.target: %s", targetsErr)
}
if targetsErr := checkPathGlobs(targets); targetsErr != nil {
return fmt.Errorf("invalid glob in spec.match.target: %w", targetsErr)
}
}
excludedAncestries, excludedAncestriesFound, excludedAncestriesErr := unstructured.NestedStringSlice(constraint.Object, "spec", "match", "excludedAncestries")
excludes, excludesFound, excludesErr := unstructured.NestedStringSlice(constraint.Object, "spec", "match", "exclude")
if excludedAncestriesFound && excludesFound {
return errors.New("only one of spec.match.excludedAncestries and spec.match.exclude can be specified")
} else if excludedAncestriesFound {
if excludedAncestriesErr != nil {
return fmt.Errorf("invalid spec.match.excludedAncestries: %s", excludedAncestriesErr)
}
if excludedAncestriesErr := checkPathGlobs(excludedAncestries); excludedAncestriesErr != nil {
return fmt.Errorf("invalid glob in spec.match.excludedAncestries: %w", excludedAncestriesErr)
}
} else if excludesFound {
// TODO b/232980918: replace with zapLogger.Warn
log.Print(
"spec.match.exclude is deprecated and will be removed in a future release. Use spec.match.excludedAncestries instead",
)
if excludesErr != nil {
return fmt.Errorf("invalid spec.match.exclude: %s", excludesErr)
}
if excludesErr := checkPathGlobs(excludes); excludesErr != nil {
return fmt.Errorf("invalid glob in spec.match.exclude: %w", excludesErr)
}
}
return nil
}
// Copyright 2022 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 gcptarget
import (
"fmt"
"github.com/gobwas/glob"
)
var ErrInvalidReview = fmt.Errorf("unexpected type of review, expect map[string]interface{}")
var ErrInvalidAncestryPath = fmt.Errorf("unexpected type of ancestry path in review object")
type matcher struct {
ancestries []string
excludedAncestries []string
}
func (m *matcher) Match(review interface{}) (bool, error) {
reviewObj, ok := review.(map[string]interface{})
if !ok {
return false, ErrInvalidReview
}
ancestryPath, ok := reviewObj["ancestry_path"].(string)
if !ok {
return false, ErrInvalidAncestryPath
}
matchAncestries := false
for _, pattern := range m.ancestries {
g := glob.MustCompile(pattern, '/')
if g.Match(ancestryPath) {
matchAncestries = true
break
}
}
if !matchAncestries {
return false, nil
}
for _, pattern := range m.excludedAncestries {
g := glob.MustCompile(pattern, '/')
if g.Match(ancestryPath) {
return false, nil
}
}
return true, nil
}
// Copyright 2019 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.
// configs helps with loading and parsing configuration files
package configs
import (
"context"
"fmt"
"regexp"
"sort"
"strings"
"github.com/golang/glog"
"github.com/GoogleCloudPlatform/config-validator/pkg/multierror"
cfapis "github.com/open-policy-agent/frameworks/constraint/pkg/apis"
cftemplates "github.com/open-policy-agent/frameworks/constraint/pkg/core/templates"
"github.com/open-policy-agent/frameworks/constraint/pkg/regorewriter"
"github.com/open-policy-agent/opa/ast"
"github.com/pkg/errors"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubectl/pkg/scheme"
)
func init() {
utilruntime.Must(cfapis.AddToScheme(scheme.Scheme))
utilruntime.Must(apiextensions.AddToScheme(scheme.Scheme))
utilruntime.Must(apiextensionsv1beta1.AddToScheme(scheme.Scheme))
}
// TODO: Using constant from gcptarget/tftarget packages causes circular reference. Fix circular reference and use <package>.Name
const (
K8STargetName = "admission.k8s.gatekeeper.sh"
GCPTargetName = "validation.gcp.forsetisecurity.org"
TFTargetName = "validation.resourcechange.terraform.cloud.google.com"
)
const (
constraintGroup = "constraints.gatekeeper.sh"
templateGroup = "templates.gatekeeper.sh"
yamlPath = GCPTargetName + "/yamlpath"
OriginalName = GCPTargetName + "/originalName"
)
const (
gcpConstraint = "gcp"
k8sConstraint = "k8s"
tfConstraint = "terraform"
)
func setAnnotation(u *unstructured.Unstructured, key, value string) {
annotations := u.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
annotations[key] = value
u.SetAnnotations(annotations)
}
// PolicyFile represents a .yaml file with its path and contents,
// which may or may not have been loaded from the file system.
type PolicyFile struct {
Path string
Content []byte
}
// LoadUnstructured loads .yaml files from the provided directories as k8s
// unstructured.Unstructured types.
func LoadUnstructured(dirs []string) ([]*unstructured.Unstructured, error) {
var files []*PolicyFile
for _, dir := range dirs {
dirPath, err := NewPath(dir)
if err != nil {
return nil, err
}
dirFiles, err := dirPath.ReadAll(context.Background(), SuffixPredicate(".yaml"))
if err != nil {
return nil, err
}
for _, dirFile := range dirFiles {
files = append(files, &PolicyFile{
Path: dirFile.Path,
Content: dirFile.Content,
})
}
}
yamlDocs, err := LoadUnstructuredFromContents(files)
if err != nil {
return nil, err
}
if len(yamlDocs) == 0 {
return nil, fmt.Errorf("zero configurations found in the provided directories: %v", dirs)
}
return yamlDocs, nil
}
// LoadUnstructuredFromContents loads provided file contents as k8s unstructured.Unstructured types.
func LoadUnstructuredFromContents(files []*PolicyFile) ([]*unstructured.Unstructured, error) {
var yamlDocs []*unstructured.Unstructured
for _, file := range files {
documents := strings.Split(string(file.Content), "\n---")
for _, rawDoc := range documents {
document := strings.TrimLeft(rawDoc, "\n ")
if len(document) == 0 {
continue
}
var u unstructured.Unstructured
_, _, err := scheme.Codecs.UniversalDeserializer().Decode([]byte(document), nil, &u)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode %s", file.Path)
}
setAnnotation(&u, yamlPath, file.Path)
yamlDocs = append(yamlDocs, &u)
}
}
return yamlDocs, nil
}
const regoAdapter = `
violation[{"msg": message, "details": metadata}] {
deny[{"msg": message, "details": metadata}] with input as {"asset": input.review, "constraint": {"spec": {"parameters": input.parameters}}}
}
`
func injectRegoAdapter(rego string) string {
return rego + "\n" + regoAdapter
}
// convertLegacyConstraintTemplate handles converting a legacy forseti v1alpha1 ConstraintTemplate
// to a constraint framework v1alpha1 ConstraintTemplate.
// Legacy constraint templates use `deny` as an entrypoint and the expected inputs are:
// - `input.asset`: the CAI asset being reviewed (new templates use `input.review`)
// - `input.constraint.spec.parameters`: the parameters from the constraint template (new templates use `input.parameters`)
func convertLegacyConstraintTemplate(u *unstructured.Unstructured, regoLib []string) error {
targetMap, found, err := unstructured.NestedMap(u.Object, "spec", "targets")
if err != nil && !found {
return nil
}
if u.GroupVersionKind().Version != "v1alpha1" {
return errors.Errorf("only v1alpha1 constraint templates are eligible for legacy conversion")
}
// Make name match kind as appropriate
ctKind, found, err := unstructured.NestedString(u.Object, "spec", "crd", "spec", "names", "kind")
if err != nil {
return errors.Wrapf(err, "invalid kind at spec.crd.spec.names.kind")
}
if !found {
return errors.Errorf("No kind found at spec.crd.spec.names.kind")
}
if len(targetMap) != 1 {
return errors.Errorf("got invalid number of targets %d", len(targetMap))
}
// Transcode target
var targets []interface{}
for name, targetIface := range targetMap {
legacyTarget, ok := targetIface.(map[string]interface{})
if !ok {
return errors.Errorf("wrong type in legacy target")
}
target := map[string]interface{}{}
regoIface, found := legacyTarget["rego"]
if !found {
return errors.Errorf("no rego specified in template")
}
rego, ok := regoIface.(string)
if !ok {
return errors.Errorf("failed to get rego from template")
}
rr, err := regorewriter.New(regorewriter.NewPackagePrefixer("lib"), []string{"data.validator"}, nil)
if err != nil {
return errors.Wrapf(err, "failed to create rego rewriter")
}
for idx, lib := range regoLib {
path := fmt.Sprintf("idx-%d.rego", idx)
m, err := ast.ParseModule(path, lib)
if err != nil {
return fmt.Errorf("failed to ParseModule with path %s: %w", path, err)
}
if err := rr.AddLib(path, m); err != nil {
return errors.Wrapf(err, "failed to add lib %d", idx)
}
}
path := "template-rego"
m, err := ast.ParseModule(path, injectRegoAdapter(rego))
if err != nil {
return fmt.Errorf("failed to ParseModule with path %s: %w", path, err)
}
if err := rr.AddEntryPoint(path, m); err != nil {
return errors.Wrapf(err, "failed to add source")
}
srcs, err := rr.Rewrite()
if err != nil {
return errors.Wrapf(err, "failed to rewrite")
}
if len(srcs.EntryPoints) != 1 {
return errors.Errorf("invalid number of entrypoints")
}
newRego, err := srcs.EntryPoints[0].Content()
if err != nil {
return errors.Wrapf(err, "failed to convert rego to bytes")
}
var libs []interface{}
for _, lib := range srcs.Libs {
libBytes, err := lib.Content()
if err != nil {
return errors.Wrapf(err, "failed to convert lib to bytes")
}
libs = append(libs, string(libBytes))
}
target["rego"] = string(newRego)
target["libs"] = libs
target["target"] = name
targets = append(targets, target)
}
if err := unstructured.SetNestedSlice(u.Object, targets, "spec", "targets"); err != nil {
return errors.Wrapf(err, "failed to set transcoded target spec")
}
originalName := u.GetName()
u.SetName(strings.ToLower(ctKind))
setAnnotation(u, OriginalName, originalName)
return nil
}
var terminatingStarRegex = regexp.MustCompilePOSIX(`/\*$`)
var starRegex = regexp.MustCompilePOSIX(`/\*/`)
func fixLegacyMatcher(ancestry string) string {
normalized := NormalizeAncestry(ancestry)
return starRegex.ReplaceAllString(
terminatingStarRegex.ReplaceAllString(normalized, "/**"),
"/**/",
)
}
func NormalizeAncestry(val string) string {
for _, r := range []struct {
old string
new string
}{
{"organization/", "organizations/"},
{"folder/", "folders/"},
{"project/", "projects/"},
} {
val = strings.ReplaceAll(val, r.old, r.new)
}
return val
}
func convertLegacyResourceName(u *unstructured.Unstructured) {
originalName := u.GetName()
compatibleName := strings.ReplaceAll(strings.ToLower(originalName), "_", "-")
if originalName == compatibleName {
return
}
u.SetName(compatibleName)
setAnnotation(u, OriginalName, originalName)
}
func convertLegacyCRM(obj map[string]interface{}, field ...string) error {
strs, found, err := unstructured.NestedStringSlice(obj, field...)
if err != nil {
return errors.Wrapf(err, "invalid field type for %s", field)
}
if !found {
return nil
}
for idx, val := range strs {
strs[idx] = fixLegacyMatcher(val)
}
return unstructured.SetNestedStringSlice(obj, strs, field...)
}
func convertLegacyConstraint(u *unstructured.Unstructured) error {
convertLegacyResourceName(u)
if err := convertLegacyCRM(u.Object, "spec", "match", "target"); err != nil {
return err
}
if err := convertLegacyCRM(u.Object, "spec", "match", "exclude"); err != nil {
return err
}
return nil
}
// Configuration represents the configuration files fed into FCV.
type Configuration struct {
GCPTemplates []*cftemplates.ConstraintTemplate // Constraint Templates for GCP
GCPConstraints []*unstructured.Unstructured // Constraints for GCP
K8STemplates []*cftemplates.ConstraintTemplate // Constraint Templates for GKE
K8SConstraints []*unstructured.Unstructured // Constraints for GKE
TFTemplates []*cftemplates.ConstraintTemplate // Constraint Templates for TF
TFConstraints []*unstructured.Unstructured // Constraints for TF
// regoLib contains the set of rego libraries, it is only used during construction of Configuration
regoLib []string
// allConstraints contains all input constraints, it is only used during construction of Configuration
allConstraints []*unstructured.Unstructured
// templateNames is a set of the names of all templates for checking exclusivity.
templateNames map[string]*cftemplates.ConstraintTemplate
// templateNames is a set of the kinds of all templates for checking exclusivity.
templateKinds map[string]*cftemplates.ConstraintTemplate
}
func newConfiguration() *Configuration {
return &Configuration{
templateNames: map[string]*cftemplates.ConstraintTemplate{},
templateKinds: map[string]*cftemplates.ConstraintTemplate{},
}
}
// LoadRegoFiles load rego policy library files from the given directory.
func LoadRegoFiles(dir string) ([]string, error) {
dirPath, err := NewPath(dir)
if err != nil {
return nil, errors.Wrapf(err, "failed to handle path for %s", dir)
}
files, err := dirPath.ReadAll(context.Background(), SuffixPredicate(".rego"))
if err != nil {
return nil, errors.Wrapf(err, "failed to read files from %s", dir)
}
var libs []string
for _, f := range files {
libs = append(libs, string(f.Content))
}
sort.Strings(libs)
return libs, nil
}
func (c *Configuration) loadUnstructured(u *unstructured.Unstructured) error {
switch u.GroupVersionKind().Group {
case constraintGroup:
if u.GroupVersionKind().Version == "v1alpha1" {
glog.Warning(
"v1alpha1 constraints are deprecated and will be removed in a future release. " +
"Please upgrade: https://github.com/GoogleCloudPlatform/policy-library/blob/main/docs/constraint_template_authoring.md#updating-from-v1alpha1-templates",
)
}
c.allConstraints = append(c.allConstraints, u)
case templateGroup:
if u.GroupVersionKind().Kind != "ConstraintTemplate" {
return errors.Errorf("unexpected data type %s in group %s", u.GroupVersionKind(), templateGroup)
}
switch u.GroupVersionKind().Version {
case "v1alpha1":
glog.Warning(
"v1alpha1 constraint templates are deprecated and will be removed in a future release. " +
"Please upgrade: https://github.com/GoogleCloudPlatform/policy-library/blob/main/docs/constraint_template_authoring.md#updating-from-v1alpha1-templates",
)
openAPIResult := configValidatorV1Alpha1SchemaValidator.Validate(u.Object)
if openAPIResult.HasErrorsOrWarnings() {
return errors.Wrapf(openAPIResult.AsError(), "v1alpha1 validation failure")
}
if err := convertLegacyConstraintTemplate(u, c.regoLib); err != nil {
return errors.Wrapf(err, "failed to convert legacy forseti ConstraintTemplate "+
"to ConstraintFramework format, this is likely due to an issue in the spec.crd.spec.validation field")
}
case "v1beta1":
openAPIResult := configValidatorV1Beta1SchemaValidator.Validate(u.Object)
if openAPIResult.HasErrorsOrWarnings() {
return errors.Wrapf(openAPIResult.AsError(), "v1beta1 validation failure")
}
default:
return errors.Errorf("unrecognized ConstraintTemplate version %s", u.GroupVersionKind().Version)
}
groupVersioner := runtime.GroupVersioner(schema.GroupVersions(scheme.Scheme.PrioritizedVersionsAllGroups()))
obj, err := scheme.Scheme.ConvertToVersion(u, groupVersioner)
if err != nil {
return errors.Wrapf(err, "failed to convert unstructured ConstraintTemplate to versioned")
}
var ct cftemplates.ConstraintTemplate
if err := scheme.Scheme.Convert(obj, &ct, nil); err != nil {
return errors.Wrapf(err, "failed to convert to versioned constraint template internal struct")
}
if ct.Spec.CRD.Spec.Validation.OpenAPIV3Schema.Type == "" {
glog.Warning(
"spec.crd.spec.validation.openAPIV3Schema is missing the type: declaration. " +
"Please upgrade: https://open-policy-agent.github.io/gatekeeper/website/docs/constrainttemplates#v1-constraint-template",
)
ct.Spec.CRD.Spec.Validation.OpenAPIV3Schema.Type = "object"
}
if dup, found := c.templateNames[ct.Name]; found {
return errors.Errorf(
"ConstraintTemplate %q declared at path %q has duplicate name conflict with template declared at path %q",
ct.Name, ct.GetAnnotations()[yamlPath], dup.GetAnnotations()[yamlPath])
}
c.templateNames[ct.Name] = &ct
if dup, found := c.templateKinds[ct.Name]; found {
return errors.Errorf(
"ConstraintTemplate %q crd kind %q declared at path %q has duplicate kind conflict with template declared at path %q",
ct.Name, ct.Spec.CRD.Spec.Names.Kind, ct.GetAnnotations()[yamlPath], dup.GetAnnotations()[yamlPath])
}
c.templateKinds[ct.Name] = &ct
for _, target := range ct.Spec.Targets {
switch target.Target {
case GCPTargetName:
c.GCPTemplates = append(c.GCPTemplates, &ct)
case TFTargetName:
if u.GroupVersionKind().Version == "v1alpha1" {
return errors.Errorf("v1alpha1 templates are not supported for terraform templates. Please upgrade.")
}
c.TFTemplates = append(c.TFTemplates, &ct)
case K8STargetName:
c.K8STemplates = append(c.K8STemplates, &ct)
default:
return errors.Errorf("")
}
}
default:
glog.V(1).Infof("Ignoring %s %s", u.GroupVersionKind(), u.GetName())
}
return nil
}
func (c *Configuration) finishLoad() error {
templates := map[string]string{}
for _, t := range c.GCPTemplates {
templates[t.Spec.CRD.Spec.Names.Kind] = gcpConstraint
}
for _, t := range c.TFTemplates {
templates[t.Spec.CRD.Spec.Names.Kind] = tfConstraint
}
for _, t := range c.K8STemplates {
templates[t.Spec.CRD.Spec.Names.Kind] = k8sConstraint
}
byTemplate := map[string]map[string]*unstructured.Unstructured{}
allConstraints := c.allConstraints
c.allConstraints = nil
for _, constraint := range allConstraints {
gvk := constraint.GroupVersionKind()
if gvk.Version == "v1alpha1" {
if err := convertLegacyConstraint(constraint); err != nil {
return fmt.Errorf("failed to convert constraint: %w", err)
}
}
templateConstraints, found := byTemplate[constraint.GetKind()]
if !found {
templateConstraints = map[string]*unstructured.Unstructured{}
byTemplate[constraint.GetKind()] = templateConstraints
}
if dup, found := templateConstraints[constraint.GetName()]; found {
return errors.Errorf(
"Constraint %q declared at path %q has duplicate name conflict with constraint declared at path %q",
dup.GetName(), dup.GetAnnotations()[yamlPath], constraint.GetAnnotations()[yamlPath])
}
switch templates[gvk.Kind] {
case gcpConstraint:
c.GCPConstraints = append(c.GCPConstraints, constraint)
case tfConstraint:
c.TFConstraints = append(c.TFConstraints, constraint)
case k8sConstraint:
c.K8SConstraints = append(c.K8SConstraints, constraint)
default:
return errors.Errorf("constraint %s does not correspond to any templates", gvk)
}
}
return nil
}
// NewConfiguration returns the configuration from the list of provided directories.
func NewConfiguration(dirs []string, libDir string) (*Configuration, error) {
unstructuredObjects, err := LoadUnstructured(dirs)
if err != nil {
return nil, err
}
regoLib, err := LoadRegoFiles(libDir)
if err != nil {
return nil, err
}
return NewConfigurationFromContents(unstructuredObjects, regoLib)
}
// NewConfigurationFromContents returns the configuration from the given
// unstructured objects and the rego library file contents.
// This can be used by code that may not have access to a file system and passes in the contents directly.
func NewConfigurationFromContents(unstructuredObjects []*unstructured.Unstructured, regoLib []string) (*Configuration, error) {
configuration := newConfiguration()
configuration.regoLib = regoLib
var errs multierror.Errors
for _, u := range unstructuredObjects {
if err := configuration.loadUnstructured(u); err != nil {
yamlPath := u.GetAnnotations()[yamlPath]
name := u.GetName()
errs.Add(errors.Wrapf(err, "failed to load resource %s %s", yamlPath, name))
}
}
if !errs.Empty() {
return nil, errs.ToError()
}
if err := configuration.finishLoad(); err != nil {
return nil, errors.Wrapf(err, "config error")
}
return configuration, nil
}
// Copyright 2019 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 configs helps with loading and parsing configuration files
package configs
import (
"context"
"fmt"
"io"
"log"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"cloud.google.com/go/storage"
"github.com/golang/glog"
"github.com/pkg/errors"
"google.golang.org/api/iterator"
)
var (
globals struct {
// once for only running GCS client setup once
once sync.Once
client *storage.Client
}
)
// configGCSClient sets up the GCS client when needed.
func configGCSClient() {
ctx := context.Background()
var err error
globals.client, err = storage.NewClient(ctx)
if err != nil {
log.Fatal(err)
}
}
// NewPath returns a new Path to a local or gcs file.
func NewPath(path string) (Path, error) {
fileURL, err := url.Parse(path)
if err != nil {
return nil, err
}
if fileURL.Scheme == "gs" {
globals.once.Do(configGCSClient)
return &gcsPath{
bucket: fileURL.Host,
path: strings.TrimLeft(fileURL.Path, "/"),
}, nil
}
// local fileIface could be dirIface or fileIface
return &localPath{path: path}, nil
}
// File represents the contents of a file
type File struct {
// Path is the path to the file.
Path string
// Content is the full contents for the file.
Content []byte
}
// readPredicate is a predicate function for ReadAll to determine whether to read a file
type readPredicate func(path string) bool
// SuffixPredicate returns read predicate that returns true if the file name has the specified suffix.
func SuffixPredicate(suffix string) readPredicate {
return func(path string) bool {
return strings.HasSuffix(path, suffix)
}
}
func matchesPredicates(path string, predicates []readPredicate) bool {
for _, predicate := range predicates {
if !predicate(path) {
return false
}
}
return true
}
// Path represents a path to a file or directory.
type Path interface {
// ReadAll will read the given file, or recursively read all files under the specified directory.
ReadAll(ctx context.Context, predicates ...readPredicate) ([]File, error)
}
// localPath handles local file paths.
type localPath struct {
path string
}
// ReadAll implements Path
func (p *localPath) ReadAll(ctx context.Context, predicates ...readPredicate) ([]File, error) {
var files []File
visit := func(path string, f os.FileInfo, err error) error {
if err != nil {
return errors.Wrapf(err, "error visiting path %s", path)
}
if f.IsDir() {
return nil
}
if !matchesPredicates(path, predicates) {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return errors.Wrapf(err, "failed to read %s", path)
}
files = append(files, File{Path: path, Content: content})
return nil
}
err := filepath.Walk(p.path, visit)
if err != nil {
return nil, errors.Wrapf(err, "failed to read files in %s", p.path)
}
return files, nil
}
// gcsPath represents an object or prefix on GCS.
type gcsPath struct {
bucket string
path string
}
// read reads an object from GCS
func (p *gcsPath) read(ctx context.Context, bucket *storage.BucketHandle, name string) (File, error) {
fileName := fmt.Sprintf("gs://%s/%s", p.bucket, name)
glog.V(2).Infof("Listing GCS Object %s", fileName)
reader, err := bucket.Object(name).NewReader(ctx)
if err != nil {
return File{}, errors.Wrapf(err, "failed to read object %s", fileName)
}
defer func() {
if err := reader.Close(); err != nil {
glog.Warningf("failed to close %s: %s", fileName, err)
}
}()
data, err := io.ReadAll(reader)
if err != nil {
return File{}, errors.Wrapf(err, "failed to read %s", fileName)
}
return File{
Content: data,
Path: fileName,
}, nil
}
// ReadAll implements Path
func (p *gcsPath) ReadAll(ctx context.Context, predicates ...readPredicate) ([]File, error) {
var files []File
bucket := globals.client.Bucket(p.bucket)
it := bucket.Objects(ctx, &storage.Query{
Prefix: p.path,
})
glog.V(2).Infof("Listing files in GCS at host %s and path %s", p.bucket, p.path)
for {
attrs, err := it.Next()
if err != nil {
if err == iterator.Done {
break
}
return nil, err
}
if !matchesPredicates(attrs.Name, predicates) {
continue
}
file, err := p.read(ctx, bucket, attrs.Name)
if err != nil {
return nil, errors.Wrapf(err, "")
}
files = append(files, file)
}
if len(files) == 0 {
return nil, errors.Errorf("no objects found at gs://%s/%s", p.bucket, p.path)
}
return files, nil
}
package configs
import (
"github.com/go-openapi/spec"
)
func refProperty(refURI string) *spec.Schema {
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef(refURI),
},
}
}
var openAPISpecSchemaDefinitions = map[string]spec.Schema{
"jsonschemaprops": {
SchemaProps: spec.SchemaProps{
Type: objectType,
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Properties: map[string]spec.Schema{
"id": *spec.StringProperty(),
"schema": *spec.StringProperty(),
"ref": *spec.StringProperty(),
"description": *spec.StringProperty(),
"type": *spec.StringProperty(),
"format": *spec.StringProperty(),
"title": *spec.StringProperty(),
"default": *refProperty("#/definitions/json"),
"maximum": *spec.Float64Property(),
"exclusiveMaximum": *spec.BooleanProperty(),
"minimum": *spec.Float64Property(),
"exclusiveMinimum": *spec.BooleanProperty(),
"maxLength": *spec.Int64Property(),
"minLength": *spec.Int64Property(),
"pattern": *spec.StringProperty(),
"maxItems": *spec.Int64Property(),
"minItems": *spec.Int64Property(),
"uniqueItems": *spec.BooleanProperty(),
"multipleOf": *spec.Float64Property(),
"enum": *spec.ArrayProperty(refProperty("#/definitions/json")),
"maxProperties": *spec.Int64Property(),
"minProperties": *spec.Int64Property(),
"required": *spec.ArrayProperty(spec.StringProperty()),
"items": *refProperty("#/definitions/jsonschemaprops"),
"allOf": *spec.ArrayProperty(refProperty("#/definitions/jsonschemaprops")),
"oneOf": *spec.ArrayProperty(refProperty("#/definitions/jsonschemaprops")),
"anyOf": *spec.ArrayProperty(refProperty("#/definitions/jsonschemaprops")),
"not": *refProperty("#/definitions/jsonschemaprops"),
"properties": *spec.MapProperty(refProperty("#/definitions/jsonschemaprops")),
"additionalProperties": *refProperty("#/definitions/jsonschemapropsorbool"),
"patternProperties": *spec.MapProperty(refProperty("#/definitions/jsonschemaprops")),
"dependencies": *spec.MapProperty(refProperty("#/definitions/jsonschemapropsorstringarray")),
"additionalItems": *refProperty("#/definitions/jsonschemapropsorbool"),
"externalDocs": *refProperty("#/definitions/externaldocumentation"),
"example": *refProperty("#/definitions/json"),
"nullable": *spec.BooleanProperty(),
},
},
},
"json": {SchemaProps: spec.SchemaProps{ID: "#json"}},
"externaldocumentation": {
SchemaProps: spec.SchemaProps{
Type: objectType,
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Properties: map[string]spec.Schema{
"description": *spec.StringProperty(),
"url": *spec.StringProperty(),
},
},
},
"jsonschemapropsorstringarray": {
SchemaProps: spec.SchemaProps{
OneOf: []spec.Schema{*spec.ArrayProperty(spec.StringProperty()), *refProperty("#/definitions/jsonschemaprops")},
},
},
"jsonschemapropsorbool": {
SchemaProps: spec.SchemaProps{
OneOf: []spec.Schema{*spec.BooleanProperty(), *refProperty("#/definitions/jsonschemaprops")},
},
},
}
// Copyright 2019 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 configs
import (
"fmt"
"github.com/go-openapi/spec"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
)
var (
objectType = spec.StringOrArray{"object"}
)
func mustMergeDefs(defMaps ...map[string]spec.Schema) map[string]spec.Schema {
merged := map[string]spec.Schema{}
for _, defMap := range defMaps {
for name, def := range defMap {
if _, duplicate := merged[name]; duplicate {
panic(fmt.Sprintf("duplicate def %s", name))
}
merged[name] = def
}
}
return merged
}
var constraintDefinitions = map[string]spec.Schema{
"metadata": {
SchemaProps: spec.SchemaProps{
Type: objectType,
Required: []string{"name"},
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Properties: map[string]spec.Schema{
"name": *spec.StringProperty(),
"labels": *spec.MapProperty(spec.StringProperty()),
"annotations": *spec.MapProperty(spec.StringProperty()),
},
},
},
"speccrd": {
SchemaProps: spec.SchemaProps{
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Required: []string{"spec"},
Properties: map[string]spec.Schema{
"spec": {
SchemaProps: spec.SchemaProps{
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Required: []string{"names"},
Properties: map[string]spec.Schema{
"names": {
SchemaProps: spec.SchemaProps{
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Required: []string{"kind"},
Properties: map[string]spec.Schema{
"kind": *spec.StringProperty(),
},
},
},
"validation": {
SchemaProps: spec.SchemaProps{
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Required: []string{"openAPIV3Schema"},
Properties: map[string]spec.Schema{
"openAPIV3Schema": *refProperty("#/definitions/jsonschemaprops"),
},
},
},
},
},
},
},
},
},
"alphav1spec": {
SchemaProps: spec.SchemaProps{
Type: objectType,
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Required: []string{"crd", "targets"},
Properties: map[string]spec.Schema{
"crd": *refProperty("#/definitions/speccrd"),
"targets": *spec.MapProperty(&spec.Schema{
SchemaProps: spec.SchemaProps{
Type: objectType,
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Required: []string{"rego"},
Properties: map[string]spec.Schema{
"rego": *spec.StringProperty(),
"libs": *spec.ArrayProperty(spec.StringProperty()),
},
},
}),
},
},
},
"betav1spec": {
SchemaProps: spec.SchemaProps{
Type: objectType,
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Required: []string{"crd", "targets"},
Properties: map[string]spec.Schema{
"crd": *refProperty("#/definitions/speccrd"),
// convert to array here.
"targets": *spec.ArrayProperty(&spec.Schema{
VendorExtensible: spec.VendorExtensible{},
SchemaProps: spec.SchemaProps{
Type: objectType,
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Required: []string{"target", "rego"},
Properties: map[string]spec.Schema{
"target": *spec.StringProperty(),
"rego": *spec.StringProperty(),
"libs": *spec.ArrayProperty(spec.StringProperty()),
},
},
}),
},
},
},
}
// configValidatorV1Alpha1Schema is the legacy config validator schema for CF-like templates. Note that there's
// a subtle difference between this where "targets" is a map rather than an array.
var configValidatorV1Alpha1Schema = spec.Schema{
SchemaProps: spec.SchemaProps{
Definitions: mustMergeDefs(openAPISpecSchemaDefinitions, constraintDefinitions),
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Properties: map[string]spec.Schema{
"apiVersion": *spec.StringProperty(),
"kind": *spec.StringProperty(),
"metadata": *refProperty("#/definitions/metadata"),
"spec": *refProperty("#/definitions/alphav1spec"),
},
},
}
var configValidatorV1Alpha1SchemaValidator = validate.NewSchemaValidator(
&configValidatorV1Alpha1Schema, nil, "", strfmt.Default)
var configValidatorV1Beta1Schema = spec.Schema{
SchemaProps: spec.SchemaProps{
Definitions: mustMergeDefs(openAPISpecSchemaDefinitions, constraintDefinitions),
AdditionalProperties: &spec.SchemaOrBool{Allows: false},
Properties: map[string]spec.Schema{
"apiVersion": *spec.StringProperty(),
"kind": *spec.StringProperty(),
"metadata": *refProperty("#/definitions/metadata"),
"spec": *refProperty("#/definitions/betav1spec"),
},
},
}
var configValidatorV1Beta1SchemaValidator = validate.NewSchemaValidator(
&configValidatorV1Beta1Schema, nil, "", strfmt.Default)
// Copyright 2019 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 gcv
import (
"context"
"flag"
"runtime"
"github.com/GoogleCloudPlatform/config-validator/pkg/api/validator"
"github.com/GoogleCloudPlatform/config-validator/pkg/multierror"
"github.com/golang/glog"
"github.com/pkg/errors"
)
var flags struct {
workerCount int
}
func init() {
flag.IntVar(
&flags.workerCount,
"workerCount",
runtime.NumCPU(),
"Number of workers that Validator will spawn to handle validate calls, this defaults to core count on the host")
}
// ParallelValidator handles making parallel calls to Validator during a Review call.
type ParallelValidator struct {
cv ConfigValidator
work chan func()
}
type assetResult struct {
violations []*validator.Violation
err error
}
// NewParallelValidator creates a new instance with the given stop channel and validator
func NewParallelValidator(stopChannel <-chan struct{}, cv ConfigValidator) *ParallelValidator {
pv := &ParallelValidator{
// channel size of number of workers seems sufficient to prevent blocking,
// this is really just an assumption with no actual perf benchmarking.
work: make(chan func(), flags.workerCount),
cv: cv,
}
go func() {
<-stopChannel
glog.Infof("validator shutdown requested via stopChannel close")
close(pv.work)
}()
workerCount := flags.workerCount
glog.Infof("validator starting %d workers", workerCount)
for i := 0; i < workerCount; i++ {
go pv.reviewWorker(i)
}
return pv
}
// reviewWorker is the function that each worker goroutine will use
func (v *ParallelValidator) reviewWorker(idx int) {
glog.V(1).Infof("worker %d starting", idx)
for f := range v.work {
f()
}
glog.V(1).Infof("worker %d terminated", idx)
}
// handleReview is the wrapper function for individual asset reviews.
func (v *ParallelValidator) handleReview(ctx context.Context, idx int, asset *validator.Asset, resultChan chan<- *assetResult) func() {
return func() {
resultChan <- func() *assetResult {
violations, err := v.cv.ReviewAsset(ctx, asset)
if err != nil {
return &assetResult{err: errors.Wrapf(err, "index %d", idx)}
}
return &assetResult{violations: violations}
}()
}
}
// Review evaluates each asset in the review request in parallel and returns any
// violations found.
func (v *ParallelValidator) Review(ctx context.Context, request *validator.ReviewRequest) (*validator.ReviewResponse, error) {
assetCount := len(request.Assets)
// channel size of number of workers seems sufficient to prevent blocking,
// this is really just an assumption with no actual perf benchmarking.
resultChan := make(chan *assetResult, flags.workerCount)
defer close(resultChan)
go func() {
for idx, asset := range request.Assets {
v.work <- v.handleReview(ctx, idx, asset, resultChan)
}
}()
response := &validator.ReviewResponse{}
var errs multierror.Errors
for i := 0; i < assetCount; i++ {
result := <-resultChan
if result.err != nil {
errs.Add(result.err)
continue
}
response.Violations = append(response.Violations, result.violations...)
}
if !errs.Empty() {
return response, errs.ToError()
}
return response, nil
}
// Copyright 2020 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 gcv
import (
"encoding/json"
"fmt"
"reflect"
"github.com/GoogleCloudPlatform/config-validator/pkg/api/validator"
"github.com/GoogleCloudPlatform/config-validator/pkg/gcv/configs"
cftypes "github.com/open-policy-agent/frameworks/constraint/pkg/types"
"github.com/pkg/errors"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/structpb"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const (
ConstraintKey = "constraint"
)
// Result is the result of reviewing an individual resource
type Result struct {
// The name of the resource as given to Config Validator
Name string
// InputResource is the resource as given to Config Validator. This may be a
// CAI Asset or a Terraform Resource Change.
InputResource map[string]interface{}
// ReviewResource is the resource sent to Constraint Framework for review.
// This may be a CAI Asset, K8S resource, or Terraform Resource Change.
ReviewResource map[string]interface{}
// ConstraintViolations are the constraints that were not satisfied during review.
ConstraintViolations []ConstraintViolation
}
// NewResult creates a Result from the provided CF Response.
func NewResult(
target, name string,
inputResource map[string]interface{},
reviewResource map[string]interface{},
responses *cftypes.Responses) (*Result, error) {
cfResponse, found := responses.ByTarget[target]
if !found {
return nil, errors.Errorf("No response for target %s", target)
}
result := &Result{
Name: name,
InputResource: inputResource,
ReviewResource: reviewResource,
ConstraintViolations: make([]ConstraintViolation, len(cfResponse.Results)),
}
for idx, cfResult := range cfResponse.Results {
for k := range cfResult.Metadata {
if k == ConstraintKey {
return nil, errors.Errorf("constraint template metadata contains reserved key %s", ConstraintKey)
}
}
severity, found, err := unstructured.NestedString(cfResult.Constraint.Object, "spec", "severity")
if err != nil || !found {
severity = ""
}
result.ConstraintViolations[idx] = ConstraintViolation{
Message: cfResult.Msg,
Metadata: cfResult.Metadata,
Constraint: cfResult.Constraint,
Severity: severity,
}
}
return result, nil
}
// ConstraintViolations represents an unsatisfied constraint
type ConstraintViolation struct {
// Message is a human readable message for the violation
Message string
// Metadata is the metadata returned by the constraint check
Metadata map[string]interface{}
// Constraint is the K8S resource of the constraint that triggered the violation
Constraint *unstructured.Unstructured
// Constraint Severity
Severity string
}
// ToInsights returns the result represented as a slice of insights.
func (r *Result) ToInsights() []*Insight {
if len(r.ConstraintViolations) == 0 {
return nil
}
insights := make([]*Insight, len(r.ConstraintViolations))
for idx, cv := range r.ConstraintViolations {
i := &Insight{
Description: cv.Message,
TargetResources: []string{r.Name},
InsightSubtype: cv.name(),
Content: map[string]interface{}{
"resource": r.InputResource,
"metadata": cv.metadata(nil),
},
Category: "SECURITY",
}
insights[idx] = i
}
return insights
}
func (r *Result) ToViolations() ([]*validator.Violation, error) {
auxMetadata := map[string]interface{}{}
ancestryPath, found, err := unstructured.NestedString(r.InputResource, ancestryPathKey)
if err != nil {
return nil, errors.Wrapf(err, "error getting ancestry path from %v", r.InputResource)
}
if found {
auxMetadata[ancestryPathKey] = ancestryPath
}
var violations []*validator.Violation
for _, rv := range r.ConstraintViolations {
violation, err := rv.toViolation(r.Name, auxMetadata)
if err != nil {
return nil, errors.Wrapf(err, "failed to convert result")
}
violations = append(violations, violation)
}
return violations, nil
}
func (cv *ConstraintViolation) metadata(auxMetadata map[string]interface{}) map[string]interface{} {
labels := cv.Constraint.GetLabels()
if labels == nil {
labels = map[string]string{}
}
annotations := cv.Constraint.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
}
params, found, err := unstructured.NestedMap(cv.Constraint.Object, "spec", "parameters")
if err != nil {
panic(fmt.Sprintf(
"constraint has invalid schema (%#v), should have already been validated, "+
" .spec.parameters got schema error on access: %s", cv.Constraint.Object, err))
}
if !found {
params = map[string]interface{}{}
}
metadata := map[string]interface{}{
ConstraintKey: map[string]interface{}{
"labels": labels,
"annotations": annotations,
"parameters": params,
},
}
for k, v := range auxMetadata {
metadata[k] = v
}
for k, v := range cv.Metadata {
metadata[k] = v
}
return metadata
}
// name returns the name for the constraint, this is given as "[Kind].[Name]" to uniquely identify which template and
// constraint the violation came from.
func (cv *ConstraintViolation) name() string {
name := cv.Constraint.GetName()
ans := cv.Constraint.GetAnnotations()
if ans != nil {
if originalName, ok := ans[configs.OriginalName]; ok {
name = originalName
}
}
return fmt.Sprintf("%s.%s", cv.Constraint.GetKind(), name)
}
// toViolation converts the constriant to a violation.
func (cv *ConstraintViolation) toViolation(name string, auxMetadata map[string]interface{}) (*validator.Violation, error) {
metadataJson, err := json.Marshal(cv.metadata(auxMetadata))
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal result metadata %v to json", cv.Metadata)
}
metadata := &structpb.Value{}
if err := protojson.Unmarshal(metadataJson, metadata); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal json %s into structpb", string(metadataJson))
}
// Extract the object fields if they exists.
var apiVersion string
if constraintAPIVersion, ok := cv.Constraint.Object["apiVersion"]; ok {
apiVersion = fmt.Sprintf("%s", constraintAPIVersion)
}
var kind string
if constraintKind, ok := cv.Constraint.Object["kind"]; ok {
kind = fmt.Sprintf("%s", constraintKind)
}
var pbMetadata *structpb.Value
if constraintMetadata, ok := cv.Constraint.Object["metadata"]; ok {
if pbMetadata, err = convertToProtoVal(constraintMetadata); err != nil {
return nil, errors.Wrapf(err, "failed to convert constraint metadata into structpb.Value")
}
}
var pbSpec *structpb.Value
if constraintSpec, ok := cv.Constraint.Object["spec"]; ok {
if pbSpec, err = convertToProtoVal(constraintSpec); err != nil {
return nil, errors.Wrapf(err, "failed to convert constraint spec into structpb.Value")
}
}
// Build the ConstraintConfig proto.
constraintConfig := &validator.Constraint{
ApiVersion: apiVersion,
Kind: kind,
Metadata: pbMetadata,
Spec: pbSpec,
}
return &validator.Violation{
Constraint: cv.name(),
ConstraintConfig: constraintConfig,
Resource: name,
Message: cv.Message,
Metadata: metadata,
Severity: cv.Severity,
}, nil
}
type convertFailed struct {
err error
}
// convertToProtoVal converts an interface into a proto struct value.
func convertToProtoVal(from interface{}) (val *structpb.Value, err error) {
defer func() {
if x := recover(); x != nil {
convFail, ok := x.(*convertFailed)
if !ok {
panic(x)
}
val = nil
err = errors.Errorf("failed to convert proto val: %s", convFail.err)
}
}()
val = convertToProtoValInternal(from)
return
}
func convertToProtoValInternal(from interface{}) *structpb.Value {
if from == nil {
return nil
}
switch val := from.(type) {
case map[string]interface{}:
fields := map[string]*structpb.Value{}
for k, v := range val {
fields[k] = convertToProtoValInternal(v)
}
return &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: fields,
},
}}
case []interface{}:
vals := make([]*structpb.Value, len(val))
for idx, v := range val {
vals[idx] = convertToProtoValInternal(v)
}
return &structpb.Value{
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{Values: vals},
},
}
case string:
return &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: val}}
case int:
return &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: float64(val)}}
case int64:
return &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: float64(val)}}
case float64:
return &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: val}}
case float32:
return &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: float64(val)}}
case bool:
return &structpb.Value{Kind: &structpb.Value_BoolValue{BoolValue: val}}
default:
panic(&convertFailed{errors.Errorf("Unhandled type %v (%s)", from, reflect.TypeOf(from).String())})
}
}
// Copyright 2019 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 gcv provides a library and a RPC service for Forseti Config Validator.
package gcv
import (
"context"
"encoding/json"
"fmt"
"github.com/GoogleCloudPlatform/config-validator/pkg/api/validator"
asset2 "github.com/GoogleCloudPlatform/config-validator/pkg/asset"
"github.com/GoogleCloudPlatform/config-validator/pkg/gcptarget"
"github.com/GoogleCloudPlatform/config-validator/pkg/gcv/configs"
"github.com/GoogleCloudPlatform/config-validator/pkg/multierror"
"github.com/GoogleCloudPlatform/config-validator/pkg/tftarget"
"github.com/golang/glog"
cfclient "github.com/open-policy-agent/frameworks/constraint/pkg/client"
"github.com/open-policy-agent/frameworks/constraint/pkg/client/drivers/rego"
cftemplates "github.com/open-policy-agent/frameworks/constraint/pkg/core/templates"
"github.com/open-policy-agent/frameworks/constraint/pkg/handler"
k8starget "github.com/open-policy-agent/gatekeeper/v3/pkg/target"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
const (
logRequestsVerboseLevel = 2
// The JSON object key for ancestry path
ancestryPathKey = "ancestry_path"
// The JSON object key for ancestors list
ancestorSliceKey = "ancestors"
)
type ConfigValidator interface {
ReviewAsset(ctx context.Context, asset *validator.Asset) ([]*validator.Violation, error)
}
// Validator checks GCP resource metadata for constraint violation.
//
// Expected usage pattern:
// - call NewValidator to create a new Validator
// - call AddData one or more times to add the GCP resource metadata to check
// - call Audit to validate the GCP resource metadata that has been added so far
// - call Reset to delete existing data
// - call AddData to add a new set of GCP resource metadata to check
// - call Reset to delete existing data
//
// Any data added in AddData stays in the underlying rule evaluation engine's memory.
// To avoid out of memory errors, callers can invoke Reset to delete existing data.
type Validator struct {
gcpCFClient *cfclient.Client
k8sCFClient *cfclient.Client
tfCFClient *cfclient.Client
}
// Stores functional options for CF client
type initOptions struct {
driverArgs []rego.Arg
clientArgs []cfclient.Opt
}
type Option = func(*initOptions)
func DisableBuiltins(builtins ...string) Option {
return func(o *initOptions) {
o.driverArgs = append(o.driverArgs, rego.DisableBuiltins(builtins...))
}
}
// NewValidatorConfig returns a new ValidatorConfig.
// By default it will initialize the underlying query evaluation engine by loading supporting library, constraints, and constraint templates.
// We may want to make this initialization behavior configurable in the future.
func NewValidatorConfig(policyPaths []string, policyLibraryPath string) (*configs.Configuration, error) {
if len(policyPaths) == 0 {
return nil, fmt.Errorf("No policy path set, provide an option to set the policy path gcv.PolicyPath")
}
if policyLibraryPath == "" {
return nil, fmt.Errorf("No policy library set")
}
glog.V(logRequestsVerboseLevel).Infof("loading policy dir: %v lib dir: %s", policyPaths, policyLibraryPath)
return configs.NewConfiguration(policyPaths, policyLibraryPath)
}
func newCFClient(
targetHandler handler.TargetHandler,
templates []*cftemplates.ConstraintTemplate,
constraints []*unstructured.Unstructured,
opts ...Option) (
*cfclient.Client, error) {
options := &initOptions{
driverArgs: []rego.Arg{rego.Tracing(false)},
clientArgs: []cfclient.Opt{cfclient.Targets(targetHandler)},
}
for _, opt := range opts {
opt(options)
}
driver, err := rego.New(options.driverArgs...)
if err != nil {
return nil, fmt.Errorf("unable to create new driver: %w", err)
}
// Append driver option after creation
args := append(options.clientArgs, cfclient.Driver(driver))
cfClient, err := cfclient.NewClient(args...)
if err != nil {
return nil, fmt.Errorf("unable to set up Constraint Framework client: %w", err)
}
ctx := context.Background()
var errs multierror.Errors
for _, template := range templates {
if _, err := cfClient.AddTemplate(ctx, template); err != nil {
errs.Add(fmt.Errorf("failed to add template %v: %w", template, err))
}
}
if !errs.Empty() {
return nil, errs.ToError()
}
for _, constraint := range constraints {
if _, err := cfClient.AddConstraint(ctx, constraint); err != nil {
errs.Add(fmt.Errorf("failed to add constraint %s: %w", constraint, err))
}
}
if !errs.Empty() {
return nil, errs.ToError()
}
return cfClient, nil
}
// NewValidatorFromConfig creates the validator from a config.
func NewValidatorFromConfig(config *configs.Configuration, opts ...Option) (*Validator, error) {
gcpCFClient, err := newCFClient(gcptarget.New(), config.GCPTemplates, config.GCPConstraints, opts...)
if err != nil {
return nil, fmt.Errorf("unable to set up GCP Constraint Framework client: %w", err)
}
k8sCFClient, err := newCFClient(&k8starget.K8sValidationTarget{}, config.K8STemplates, config.K8SConstraints, opts...)
if err != nil {
return nil, fmt.Errorf("unable to set up K8S Constraint Framework client: %w", err)
}
tfCFClient, err := newCFClient(tftarget.New(), config.TFTemplates, config.TFConstraints, opts...)
if err != nil {
return nil, fmt.Errorf("unable to set up TF Constraint Framework client: %w", err)
}
ret := &Validator{
gcpCFClient: gcpCFClient,
k8sCFClient: k8sCFClient,
tfCFClient: tfCFClient,
}
return ret, nil
}
// NewValidator returns a new Validator.
// By default it will initialize the underlying query evaluation engine by loading supporting library, constraints, and constraint templates.
// We may want to make this initialization behavior configurable in the future.
func NewValidator(policyPaths []string, policyLibraryPath string, opts ...Option) (*Validator, error) {
config, err := NewValidatorConfig(policyPaths, policyLibraryPath)
if err != nil {
return nil, err
}
return NewValidatorFromConfig(config, opts...)
}
// NewValidatorFromContents returns a new Validator built from the provided contents of the policy constraints and policy library.
// This provides a way to create a validator directly from contents instead of reading from the file system.
// policyLibrary is a slice of file contents of all policy library files.
func NewValidatorFromContents(policyFiles []*configs.PolicyFile, policyLibrary []string, opts ...Option) (*Validator, error) {
if len(policyFiles) == 0 {
return nil, fmt.Errorf("No policy constraints provided")
}
if len(policyLibrary) == 0 {
return nil, fmt.Errorf("No policy library provided")
}
unstructuredObjects, err := configs.LoadUnstructuredFromContents(policyFiles)
if err != nil {
return nil, err
}
config, err := configs.NewConfigurationFromContents(unstructuredObjects, policyLibrary)
if err != nil {
return nil, err
}
return NewValidatorFromConfig(config, opts...)
}
// ReviewAsset reviews a single asset.
func (v *Validator) ReviewAsset(ctx context.Context, asset *validator.Asset) ([]*validator.Violation, error) {
// Sanitize the ancestry path first, so that an asset that only provides ancestors
// can still pass ValidateAsset.
if err := asset2.SanitizeAncestryPath(asset); err != nil {
return nil, err
}
if err := asset2.ValidateAsset(asset); err != nil {
return nil, err
}
assetInterface, err := asset2.ConvertResourceViaJSONToInterface(asset)
if err != nil {
return nil, err
}
assetMapInterface := assetInterface.(map[string]interface{})
result, err := v.ReviewUnmarshalledJSON(ctx, assetMapInterface)
if err != nil {
return nil, err
}
return result.ToViolations()
}
// ReviewTFResourceChange evaluates a single terraform resource change without any threading in the background.
func (v *Validator) ReviewTFResourceChange(ctx context.Context, inputResource map[string]interface{}) ([]*validator.Violation, error) {
target := tftarget.New()
handled, _, err := target.HandleReview(inputResource)
if !handled {
return nil, fmt.Errorf("Unhandled resource: %w", err)
}
responses, err := v.tfCFClient.Review(ctx, inputResource)
if err != nil {
return nil, fmt.Errorf("TF target Constraint Framework review call failed: %w", err)
}
result, err := NewResult(tftarget.Name, inputResource["address"].(string), inputResource, inputResource, responses)
if err != nil {
return nil, err
}
return result.ToViolations()
}
// fixAncestry will try to use the ancestors array to create the ancestorPath
// value if it is not present.
func (v *Validator) fixAncestry(input map[string]interface{}) error {
ancestors, found, err := unstructured.NestedStringSlice(input, ancestorSliceKey)
if found && err == nil {
input[ancestryPathKey] = asset2.AncestryPath(ancestors)
return nil
}
ancestry, found, err := unstructured.NestedString(input, ancestryPathKey)
if found && err == nil {
input[ancestryPathKey] = configs.NormalizeAncestry(ancestry)
return nil
}
return fmt.Errorf("asset missing ancestry information: %v", input)
}
// ReviewJSON reviews the content of a JSON string
func (v *Validator) ReviewJSON(ctx context.Context, data string) (*Result, error) {
asset := map[string]interface{}{}
if err := json.Unmarshal([]byte(data), &asset); err != nil {
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
}
return v.ReviewUnmarshalledJSON(ctx, asset)
}
// ReviewJSON evaluates a single asset without any threading in the background.
func (v *Validator) ReviewUnmarshalledJSON(ctx context.Context, asset map[string]interface{}) (*Result, error) {
if err := v.fixAncestry(asset); err != nil {
return nil, err
}
if asset2.IsK8S(asset) {
return v.reviewK8SResource(ctx, asset)
}
return v.reviewGCPResource(ctx, asset)
}
// reviewK8SResource will convert CAI assets to k8s resources then pass them to the cf client with the gatekeeper target.
func (v *Validator) reviewK8SResource(ctx context.Context, asset map[string]interface{}) (*Result, error) {
k8sResource, err := asset2.ConvertCAIToK8s(asset)
if err != nil {
return nil, fmt.Errorf("failed to convert asset to admission request: %w", err)
}
responses, err := v.k8sCFClient.Review(ctx, k8sResource)
if err != nil {
return nil, fmt.Errorf("K8S target Constraint Framework review call failed: %w", err)
}
return NewResult(configs.K8STargetName, asset["name"].(string), asset, k8sResource.Object, responses)
}
// reviewGCPResource will pass CAI assets to the cf client with the GCP target.
func (v *Validator) reviewGCPResource(ctx context.Context, asset map[string]interface{}) (*Result, error) {
responses, err := v.gcpCFClient.Review(ctx, asset)
if err != nil {
return nil, fmt.Errorf("GCP target Constraint Framework review call failed: %w", err)
}
return NewResult(gcptarget.Name, asset["name"].(string), asset, asset, responses)
}
// Copyright 2019 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 multierror
import (
"fmt"
"io"
"strings"
)
// errorImpl handles implementing a list of errors.
type errorImpl []error
// Error implements error.
func (errs errorImpl) Error() string {
var s []string
for _, err := range errs {
s = append(s, err.Error())
}
return strings.Join(s, ", ")
}
// Format implements fmt.Formatter to make this play nice with handling stack traces produced from
// github.com/pkg/errors
func (errs errorImpl) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
_, _ = fmt.Fprintf(s, "errors (%d):\n", len(errs))
for _, err := range errs {
if formatter, ok := err.(fmt.Formatter); ok {
_, _ = io.WriteString(s, " ")
formatter.Format(s, verb)
_, _ = io.WriteString(s, "\n")
} else {
_, _ = fmt.Fprintf(s, " %v\n", err)
}
}
case 's':
_, _ = io.WriteString(s, errs.Error())
case 'q':
_, _ = fmt.Fprintf(s, "%q", errs.Error())
}
}
// Errors allows for returning multiple errors in one error
type Errors struct {
errs []error
}
// ToError returns the error if populated, or nil if none exists.
func (e *Errors) ToError() error {
if len(e.errs) == 0 {
return nil
}
return errorImpl(e.errs)
}
func (e *Errors) Empty() bool {
return len(e.errs) == 0
}
func (e *Errors) Add(err error) {
if err == nil {
return
}
if ei, ok := err.(errorImpl); ok {
e.errs = append(e.errs, ei...)
return
}
e.errs = append(e.errs, err)
}
func (e *Errors) AddF(err error, mod func(error) error) {
if err == nil {
return
}
e.errs = append(e.errs, mod(err))
}
// Copyright 2022 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 tftarget
import (
"fmt"
"github.com/gobwas/glob"
)
type matcher struct {
addresses []string
excludedAddresses []string
}
var ErrInvalidReview = fmt.Errorf("unexpected type of review, expect map[string]interface{}")
var ErrInvalidAddress = fmt.Errorf("unexpected type of address in review object")
// Match returns true if the Matcher's Constraint should run against the
// passed review object.
func (m *matcher) Match(review interface{}) (bool, error) {
reviewObj, ok := review.(map[string]interface{})
if !ok {
return false, ErrInvalidReview
}
address, ok := reviewObj["address"].(string)
if !ok {
return false, ErrInvalidAddress
}
matched := false
for _, pattern := range m.addresses {
g := glob.MustCompile(pattern, '.')
if g.Match(address) {
matched = true
break
}
}
if !matched {
return false, nil
}
for _, pattern := range m.excludedAddresses {
g := glob.MustCompile(pattern, '.')
if g.Match(address) {
return false, nil
}
}
return true, nil
}
// Copyright 2019 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 tftarget is a constraint framework target for config-validator to use for integrating with the opa constraint framework.
package tftarget
import (
"fmt"
"regexp"
"strings"
"github.com/open-policy-agent/frameworks/constraint/pkg/core/constraints"
"github.com/open-policy-agent/frameworks/constraint/pkg/handler"
"github.com/open-policy-agent/frameworks/constraint/pkg/types"
"github.com/pkg/errors"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// Name is the target name for TFTarget
const Name = "validation.resourcechange.terraform.cloud.google.com"
// TFTarget is the constraint framework target for config-validator
type TFTarget struct {
}
var _ handler.TargetHandler = &TFTarget{}
// New returns a new TFTarget
func New() *TFTarget {
return &TFTarget{}
}
// ToMatcher implements client.ToMatcher
func (g *TFTarget) ToMatcher(constraint *unstructured.Unstructured) (constraints.Matcher, error) {
match, ok, err := unstructured.NestedMap(constraint.Object, "spec", "match")
if err != nil {
return nil, fmt.Errorf("unable to get spec.match: %w", err)
}
if !ok {
return &matcher{addresses: []string{"**"}, excludedAddresses: []string{}}, nil
}
include, ok, err := unstructured.NestedStringSlice(match, "addresses")
if err != nil {
return nil, fmt.Errorf("unable to get string slice from spec.match.addresses: %w", err)
}
if !ok {
include = []string{"**"}
}
exclude, ok, err := unstructured.NestedStringSlice(match, "excludedAddresses")
if err != nil {
return nil, fmt.Errorf("unable to get string slice from spec.match.excludedAddresses: %w", err)
}
if !ok {
exclude = []string{}
}
return &matcher{
addresses: include,
excludedAddresses: exclude,
}, nil
}
// MatchSchema implements client.MatchSchemaProvider
func (g *TFTarget) MatchSchema() apiextensions.JSONSchemaProps {
return apiextensions.JSONSchemaProps{
Type: "object",
Properties: map[string]apiextensions.JSONSchemaProps{
"addresses": {
Type: "array",
Items: &apiextensions.JSONSchemaPropsOrArray{
Schema: &apiextensions.JSONSchemaProps{
Type: "string",
},
},
},
"excludedAddresses": {
Type: "array",
Items: &apiextensions.JSONSchemaPropsOrArray{
Schema: &apiextensions.JSONSchemaProps{
Type: "string",
},
},
},
},
}
}
// GetName implements handler.TargetHandler
func (g *TFTarget) GetName() string {
return Name
}
// ProcessData implements handler.TargetHandler
func (g *TFTarget) ProcessData(obj interface{}) (bool, []string, interface{}, error) {
return false, nil, nil, errors.Errorf("storing data for referential constraint eval is not supported at this time.")
}
// HandleReview implements handler.TargetHandler
func (g *TFTarget) HandleReview(obj interface{}) (bool, interface{}, error) {
switch resource := obj.(type) {
case map[string]interface{}:
if _, found, err := unstructured.NestedString(resource, "name"); !found || err != nil {
return false, nil, err
}
if _, found, err := unstructured.NestedString(resource, "address"); !found || err != nil {
return false, nil, err
}
if _, found, err := unstructured.NestedMap(resource, "change"); !found || err != nil {
return false, nil, err
}
if _, found, err := unstructured.NestedString(resource, "type"); !found || err != nil {
return false, nil, err
}
return true, resource, nil
}
return false, nil, nil
}
// HandleViolation implements handler.TargetHandler
func (g *TFTarget) HandleViolation(result *types.Result) error {
return nil
}
var partRegex = regexp.MustCompile(`[\w.\-_\[\]\d]+`)
// checkPathGlob
func checkPathGlob(expression string) error {
// check for path components / numbers
parts := strings.Split(expression, ".")
for i := 0; i < len(parts); i++ {
item := parts[i]
switch {
case item == "*":
case item == "**":
case partRegex.MatchString(item):
default:
return errors.Errorf("unexpected item %s element %d in %s", item, i, expression)
}
}
return nil
}
func checkPathGlobs(rs []string) error {
for idx, r := range rs {
if err := checkPathGlob(r); err != nil {
return errors.Wrapf(err, "idx: %d", idx)
}
}
return nil
}
// ValidateConstraint implements handler.TargetHandler
func (g *TFTarget) ValidateConstraint(constraint *unstructured.Unstructured) error {
includes, found, err := unstructured.NestedStringSlice(constraint.Object, "spec", "match", "addresses")
if err != nil {
return errors.Errorf("invalid spec.match.addresses: %s", err)
}
if found {
if err := checkPathGlobs(includes); err != nil {
return errors.Wrapf(err, "invalid glob in target")
}
}
excludes, found, err := unstructured.NestedStringSlice(constraint.Object, "spec", "match", "excludedAddresses")
if err != nil {
return errors.Errorf("invalid spec.match.excludedAddresses: %s", err)
}
if found {
if err := checkPathGlobs(excludes); err != nil {
return errors.Wrapf(err, "invalid glob in exclude")
}
}
return nil
}