Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Policy match validate #374

Merged
merged 3 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions internal/delivery/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ func UnmarshalRequestInput(r *http.Request, in any) error {
return nil
}

// Http Request가 아닌 경우에도 domain 객체 validate가 필요한 경우 호출
// 예를 들어 정책의 match를 RawYaml로 전달받았을 경우 이를 domain.Match 객체로 unmarshalling 한 후 domain.Match를 이용해서 validate 가능
func ValidateDomainObject(in any) error {
err := validate.Struct(in)
if err != nil {
var valErrs validator_.ValidationErrors
if errors.As(err, &valErrs) {
for _, e := range valErrs {
return httpErrors.NewBadRequestError(err, "", e.Translate(trans))
}
}
}

return nil
}

/*
func (h *APIHandler) GetClientFromClusterId(clusterId string) (*kubernetes.Clientset, error) {
const prefix = "CACHE_KEY_KUBE_CLIENT_"
Expand Down
10 changes: 10 additions & 0 deletions internal/delivery/http/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ func (h *PolicyHandler) CreatePolicy(w http.ResponseWriter, r *http.Request) {
ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("match yaml error: %s", err), "P_INVALID_MATCH", ""))
return
}

if err := ValidateDomainObject(match); err != nil {
ErrorJSON(w, r, err)
return
}
}

if len(input.PolicyResourceName) > 0 {
Expand Down Expand Up @@ -180,6 +185,11 @@ func (h *PolicyHandler) UpdatePolicy(w http.ResponseWriter, r *http.Request) {
ErrorJSON(w, r, httpErrors.NewBadRequestError(fmt.Errorf("match yaml error: %s", err), "P_INVALID_MATCH", ""))
return
}

if err := ValidateDomainObject(match); err != nil {
ErrorJSON(w, r, err)
return
}
}

var templateId *uuid.UUID = nil
Expand Down
142 changes: 141 additions & 1 deletion internal/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@ package validator

import (
"regexp"
"strings"
"unicode/utf8"

"github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator"
validator "github.com/go-playground/validator/v10"
en_translations "github.com/go-playground/validator/v10/translations/en"
"github.com/openinfradev/tks-api/pkg/domain"
"github.com/opentracing/opentracing-go/log"
)

const REGEX_RFC1123 = `^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`
const (
REGEX_RFC1123 = `^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`
REGEX_SIMPLE_SEMVER = `^v\d+\.\d+\.\d+$`
REGEX_PASCAL_CASE = `^([A-Z][a-z\d]+)+$` // 대문자로 시작하는 camel case(pascal case or upper camel case)를 표현한 정규식
REGEX_RFC1123_DNS_LABEL = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
REGEX_RESOURCE_NAME = `^` + REGEX_RFC1123_DNS_LABEL + "$"
REGEX_RFC1123_SUBDOMAIN = `^` + REGEX_RFC1123_DNS_LABEL + `(\.` + REGEX_RFC1123_DNS_LABEL + `)*$`
)

func NewValidator() (*validator.Validate, *ut.UniversalTranslator) {
en := en.New()
Expand All @@ -27,6 +36,11 @@ func NewValidator() (*validator.Validate, *ut.UniversalTranslator) {
// register custom validator
_ = v.RegisterValidation("rfc1123", validateRfc1123)
_ = v.RegisterValidation("name", validateName)
_ = v.RegisterValidation("version", validateVersion)
_ = v.RegisterValidation("pascalcase", validatePascalCase)
_ = v.RegisterValidation("resourcename", validateResourceName)
_ = v.RegisterValidation("matchnamespace", validateMatchNamespace)
_ = v.RegisterValidation("matchkinds", validateMatchKinds)

// register custom error
_ = v.RegisterTranslation("required", trans, func(ut ut.Translator) error {
Expand Down Expand Up @@ -65,3 +79,129 @@ func validateName(fl validator.FieldLevel) bool {

return utf8.RuneCountInString(fl.Field().String()) <= 30
}

func validateVersion(fl validator.FieldLevel) bool {
if fl.Field().String() == "" {
return false
}

r, _ := regexp.Compile(REGEX_SIMPLE_SEMVER)
return r.MatchString(fl.Field().String())
}

func validatePascalCase(fl validator.FieldLevel) bool {
if fl.Field().String() == "" {
return false
}

r, _ := regexp.Compile(REGEX_PASCAL_CASE)
return r.MatchString(fl.Field().String())
}

func validateResourceName(fl validator.FieldLevel) bool {
if fl.Field().String() == "" {
return false
}

r, _ := regexp.Compile(REGEX_RESOURCE_NAME)
return r.MatchString(fl.Field().String())
}

func validateMatchKinds(fl validator.FieldLevel) bool {
kinds, ok := fl.Field().Interface().([]domain.Kinds)
if !ok {
return false
}

for _, kind := range kinds {
if ok := validateMatchKindAPIGroup(kind.APIGroups) && validateMatchKindKind(kind.Kinds); !ok {
return false
}
}

return true
}

func validateMatchKindAPIGroup(apigroups []string) bool {
if len(apigroups) == 0 {
return true
}

containsWildcard := false

r, _ := regexp.Compile(REGEX_RFC1123_SUBDOMAIN)

for _, apigroup := range apigroups {
if apigroup == "*" || apigroup == "" {
containsWildcard = true
} else {
if !r.MatchString(apigroup) {
return false
}
}
}

if containsWildcard && len(apigroups) != 1 {
return false
}

return true
}

func validateMatchKindKind(kinds []string) bool {
if len(kinds) == 0 {
return true
}

containsWildcard := false

r, _ := regexp.Compile(REGEX_PASCAL_CASE)

for _, kind := range kinds {
if kind == "*" || kind == "" {
containsWildcard = true
} else {
if !r.MatchString(kind) {
return false
}
}
}

if containsWildcard && len(kinds) != 1 {
return false
}

return true
}

func validateMatchNamespace(fl validator.FieldLevel) bool {
namespaces, ok := fl.Field().Interface().([]string)
if !ok {
return false
}

if len(namespaces) == 0 {
return true
}

containsWildcard := false

r, _ := regexp.Compile(REGEX_RESOURCE_NAME)

for _, namespace := range namespaces {
if namespace == "*" || namespace == "" {
containsWildcard = true
} else {
trimmed := strings.TrimSuffix(strings.TrimPrefix(namespace, "*"), "*")
if !r.MatchString(trimmed) {
return false
}
}
}

if containsWildcard && len(namespaces) != 1 {
return false
}

return true
}
49 changes: 8 additions & 41 deletions pkg/domain/admin/policy-template.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package admin
import (
"time"

"github.com/google/uuid"
"github.com/openinfradev/tks-api/pkg/domain"
)

Expand Down Expand Up @@ -43,9 +42,9 @@ type SimplePolicyTemplateResponse struct {
}

type CreatePolicyTemplateRequest struct {
TemplateName string `json:"templateName" example:"필수 Label 검사" validate:"name"`
Kind string `json:"kind" example:"K8sRequiredLabels" validate:"required"`
Severity string `json:"severity" enums:"low,medium,high" example:"medium"`
TemplateName string `json:"templateName" validate:"required,name" example:"필수 Label 검사"`
Kind string `json:"kind" example:"K8sRequiredLabels" validate:"required,pascalcase"`
Severity string `json:"severity" validate:"required,oneof=low medium high" enums:"low,medium,high" example:"medium"`
Deprecated bool `json:"deprecated" example:"false"`
Description string `json:"description,omitempty" example:"이 정책은 ..."`
ParametersSchema []*domain.ParameterDef `json:"parametersSchema,omitempty"`
Expand All @@ -65,46 +64,14 @@ type CreateOrganizationPolicyTemplateReponse struct {
ID string `json:"id" example:"d98ef5f1-4a68-4047-a446-2207787ce3ff"`
}

type UpdateCommmonPolicyTemplateRequest struct {
TemplateName string `json:"templateName" example:"필수 Label 검사"`
Description string `json:"description,omitempty"`
Severity string `json:"severity" enums:"low,medium,high" example:"medium"`
Deprecated bool `json:"deprecated" example:"false"`
// Tags []string `json:"tags,omitempty"`
}

type UpdatePolicyTemplateUpdate struct {
ID uuid.UUID
Type string
UpdatorId uuid.UUID
TemplateName *string
Description *string
Severity *string
Deprecated *bool
PermittedOrganizationIds *[]string
}

func (dto *UpdatePolicyTemplateUpdate) IsNothingToUpdate() bool {
return dto.TemplateName == nil &&
dto.Description == nil &&
dto.Severity == nil &&
dto.Deprecated == nil &&
dto.PermittedOrganizationIds == nil
}

type UpdatePolicyTemplateRequest struct {
TemplateName *string `json:"templateName,omitempty" example:"필수 Label 검사"`
TemplateName *string `json:"templateName,omitempty" validate:"required,name" example:"필수 Label 검사"`
Description *string `json:"description,omitempty"`
Severity *string `json:"severity,omitempty" enums:"low,medium,high" example:"medium"`
Severity *string `json:"severity,omitempty" validate:"oneof=low medium high" enums:"low,medium,high" example:"medium"`
Deprecated *bool `json:"deprecated,omitempty" example:"false"`
PermittedOrganizationIds *[]string `json:"permittedOrganizationIds,omitempty"`
}

type UpdateOrganizationPolicyTemplateRequest struct {
UpdateCommmonPolicyTemplateRequest
Mandatory bool `json:"mandatory"`
}

type GetPolicyTemplateDeployResponse struct {
DeployVersion map[string]string `json:"deployVersion"`
}
Expand All @@ -126,9 +93,9 @@ type GetPolicyTemplateVersionResponse struct {
}

type CreatePolicyTemplateVersionRequest struct {
VersionUpType string `json:"versionUpType" enums:"major,minor,patch" example:"minor" validate:"required"`
CurrentVersion string `json:"currentVersion" example:"v1.0.0" validate:"required"`
ExpectedVersion string `json:"expectedVersion" example:"v1.1.0" validate:"required"`
VersionUpType string `json:"versionUpType" validate:"required,oneof=major minor patch" enums:"major,minor,patch" example:"minor"`
CurrentVersion string `json:"currentVersion" validate:"required,version" example:"v1.0.0"`
ExpectedVersion string `json:"expectedVersion" validate:"required,version" example:"v1.1.0"`

ParametersSchema []*domain.ParameterDef `json:"parametersSchema,omitempty"`
// "type: object\nproperties: message:\n type: string\n labels:\n type: array\n items:\n type: object\n properties:\n key:\n type: string\n allowedRegex:\n type: string"
Expand Down
45 changes: 8 additions & 37 deletions pkg/domain/policy-template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package domain

import (
"time"

"github.com/google/uuid"
)

type PolicyTemplateResponse struct {
Expand Down Expand Up @@ -34,9 +32,9 @@ type SimplePolicyTemplateResponse struct {
}

type CreatePolicyTemplateRequest struct {
TemplateName string `json:"templateName" example:"필수 Label 검사" validate:"name"`
Kind string `json:"kind" example:"K8sRequiredLabels" validate:"required"`
Severity string `json:"severity" enums:"low,medium,high" example:"medium"`
TemplateName string `json:"templateName" validate:"required,name" example:"필수 Label 검사"`
Kind string `json:"kind" example:"K8sRequiredLabels" validate:"required,pascalcase"`
Severity string `json:"severity" validate:"required,oneof=low medium high" enums:"low,medium,high" example:"medium"`
Deprecated bool `json:"deprecated" example:"false"`
Description string `json:"description,omitempty" example:"이 정책은 ..."`
ParametersSchema []*ParameterDef `json:"parametersSchema,omitempty"`
Expand All @@ -52,37 +50,10 @@ type CreatePolicyTemplateReponse struct {
ID string `json:"id" example:"d98ef5f1-4a68-4047-a446-2207787ce3ff"`
}

type UpdateCommmonPolicyTemplateRequest struct {
TemplateName string `json:"templateName" example:"필수 Label 검사"`
Description string `json:"description,omitempty"`
Severity string `json:"severity" enums:"low,medium,high" example:"medium"`
Deprecated bool `json:"deprecated" example:"false"`
// Tags []string `json:"tags,omitempty"`
}

type UpdatePolicyTemplateUpdate struct {
ID uuid.UUID
Type string
UpdatorId uuid.UUID
TemplateName *string
Description *string
Severity *string
Deprecated *bool
PermittedOrganizationIds *[]string
}

func (dto *UpdatePolicyTemplateUpdate) IsNothingToUpdate() bool {
return dto.TemplateName == nil &&
dto.Description == nil &&
dto.Severity == nil &&
dto.Deprecated == nil &&
dto.PermittedOrganizationIds == nil
}

type UpdatePolicyTemplateRequest struct {
TemplateName *string `json:"templateName,omitempty" example:"필수 Label 검사"`
TemplateName *string `json:"templateName,omitempty" validate:"required,name" example:"필수 Label 검사"`
Description *string `json:"description,omitempty"`
Severity *string `json:"severity,omitempty" enums:"low,medium,high" example:"medium"`
Severity *string `json:"severity,omitempty" validate:"oneof=low medium high" enums:"low,medium,high" example:"medium"`
Deprecated *bool `json:"deprecated,omitempty" example:"false"`
PermittedOrganizationIds *[]string `json:"permittedOrganizationIds,omitempty"`
}
Expand All @@ -100,9 +71,9 @@ type GetPolicyTemplateVersionResponse struct {
}

type CreatePolicyTemplateVersionRequest struct {
VersionUpType string `json:"versionUpType" enums:"major,minor,patch" example:"minor" validate:"required"`
CurrentVersion string `json:"currentVersion" example:"v1.0.0" validate:"required"`
ExpectedVersion string `json:"expectedVersion" example:"v1.1.0" validate:"required"`
VersionUpType string `json:"versionUpType" validate:"required,oneof=major minor patch" enums:"major,minor,patch" example:"minor"`
CurrentVersion string `json:"currentVersion" validate:"required,version" example:"v1.0.0"`
ExpectedVersion string `json:"expectedVersion" validate:"required,version" example:"v1.1.0"`

ParametersSchema []*ParameterDef `json:"parametersSchema,omitempty"`
// "type: object\nproperties: message:\n type: string\n labels:\n type: array\n items:\n type: object\n properties:\n key:\n type: string\n allowedRegex:\n type: string"
Expand Down
Loading
Loading