Skip to content

Commit

Permalink
Merge pull request #207 from xuzhu-591/feat-validating-admission-webhook
Browse files Browse the repository at this point in the history
Feat validating admission webhook
  • Loading branch information
xuzhu-591 authored Jan 8, 2024
2 parents de79c13 + f8e5e61 commit 4c1159d
Show file tree
Hide file tree
Showing 12 changed files with 727 additions and 3 deletions.
12 changes: 11 additions & 1 deletion core/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ import (
"github.com/horizoncd/horizon/core/middleware/auth"
"github.com/horizoncd/horizon/core/middleware/requestid"
gitlablib "github.com/horizoncd/horizon/lib/gitlab"
"github.com/horizoncd/horizon/pkg/admission"
"github.com/horizoncd/horizon/pkg/cd"
clustermetrcis "github.com/horizoncd/horizon/pkg/cluster/metrics"
admissionconfig "github.com/horizoncd/horizon/pkg/config/admission"
"github.com/horizoncd/horizon/pkg/environment/service"
eventservice "github.com/horizoncd/horizon/pkg/event/service"
"github.com/horizoncd/horizon/pkg/grafana"
Expand Down Expand Up @@ -137,6 +139,7 @@ import (
templatev2 "github.com/horizoncd/horizon/core/http/api/v2/template"
"github.com/horizoncd/horizon/core/http/health"
"github.com/horizoncd/horizon/core/http/metrics"
admissionmiddle "github.com/horizoncd/horizon/core/middleware/admission"
ginlogmiddle "github.com/horizoncd/horizon/core/middleware/ginlog"
logmiddle "github.com/horizoncd/horizon/core/middleware/log"
metricsmiddle "github.com/horizoncd/horizon/core/middleware/metrics"
Expand Down Expand Up @@ -227,6 +230,10 @@ func ParseFlags() *Flags {
return &flags
}

func InitAdmissionWebhook(config admissionconfig.Admission) {
admission.NewHTTPWebhooks(config)
}

func InitLog(flags *Flags) {
if flags.Environment == "production" {
logrus.SetFormatter(&logrus.JSONFormatter{})
Expand Down Expand Up @@ -624,7 +631,8 @@ func Init(ctx context.Context, flags *Flags, coreConfig *config.Config) {
middleware.MethodAndPathSkipper(http.MethodPost, regexp.MustCompile("^/apis/core/v[12]/users/login"))),
prehandlemiddle.Middleware(r, manager),
auth.Middleware(rbacAuthorizer, authzSkippers...),
tagmiddle.Middleware(), // tag middleware, parse and attach tagSelector to context
tagmiddle.Middleware(),
admissionmiddle.Middleware(authzSkippers...),
}
r.Use(middlewares...)

Expand Down Expand Up @@ -725,6 +733,8 @@ func Run(flags *Flags) {
panic(err)
}

InitAdmissionWebhook(configs.Admission)

// enable pprof
runPProfServer(&configs.PProf)

Expand Down
2 changes: 2 additions & 0 deletions core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"io/ioutil"
"strings"

"github.com/horizoncd/horizon/pkg/config/admission"
"github.com/horizoncd/horizon/pkg/config/argocd"
"github.com/horizoncd/horizon/pkg/config/authenticate"
"github.com/horizoncd/horizon/pkg/config/autofree"
Expand Down Expand Up @@ -67,6 +68,7 @@ type Config struct {
TemplateUpgradeMapper template.UpgradeMapper `yaml:"templateUpgradeMapper"`
KubernetesEvent k8sevent.Config `yaml:"kubernetesEvent"`
Clean clean.Config `yaml:"clean"`
Admission admission.Admission `yaml:"admission"`
}

func LoadConfig(configFilePath string) (*Config, error) {
Expand Down
2 changes: 1 addition & 1 deletion core/errors/horizonerrors.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ var (
ErrGenerateRandomID = errors.New("failed to generate random id")
ErrDisabled = errors.New("entity is disabled")
ErrDuplicatedKey = errors.New("duplicated keys")
// ErrInternal = errors.New("internal error")
ErrValidatingFailed = errors.New("validating failed")

// http
ErrHTTPRespNotAsExpected = errors.New("http response is not as expected")
Expand Down
84 changes: 84 additions & 0 deletions core/middleware/admission/admission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package admission

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"

"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/horizoncd/horizon/core/common"
"github.com/horizoncd/horizon/core/middleware"
admissionwebhook "github.com/horizoncd/horizon/pkg/admission"
admissionmodels "github.com/horizoncd/horizon/pkg/admission/models"
"github.com/horizoncd/horizon/pkg/auth"
"github.com/horizoncd/horizon/pkg/server/response"
"github.com/horizoncd/horizon/pkg/server/rpcerror"
"github.com/horizoncd/horizon/pkg/util/log"
)

// Middleware to validate and mutate admission request
func Middleware(skippers ...middleware.Skipper) gin.HandlerFunc {
return middleware.New(func(c *gin.Context) {
// get auth record
record, ok := c.Get(common.ContextAuthRecord)
if !ok {
response.AbortWithRPCError(c,
rpcerror.BadRequestError.WithErrMsg("request with no auth record"))
return
}
attr := record.(auth.AttributesRecord)
// non resource request or read only request should be ignored
if !attr.IsResourceRequest() || attr.IsReadOnly() {
c.Next()
return
}
var object interface{}
// read request body and avoid side-effects on c.Request.Body
bodyBytes, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
response.AbortWithRPCError(c,
rpcerror.ParamError.WithErrMsg(fmt.Sprintf("request body is invalid, err: %v", err)))
return
}
// restore the request body
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
if len(bodyBytes) > 0 {
contentType := c.ContentType()
if contentType == binding.MIMEJSON || contentType == "" {
if err := json.Unmarshal(bodyBytes, &object); err != nil {
response.AbortWithRPCError(c,
rpcerror.ParamError.WithErrMsg(fmt.Sprintf("unmarshal request body failed, err: %v", err)))
return
}
} else {
log.Errorf(c, "unsupported content type: %s", contentType)
response.AbortWithRPCError(c,
rpcerror.ParamError.WithErrMsg(fmt.Sprintf("unsupported content type: %s", contentType)))
return
}
}
// fill in the request url query into admission request options
queries := c.Request.URL.Query()
options := make(map[string]interface{}, len(queries))
for k, v := range queries {
options[k] = v
}
admissionRequest := &admissionwebhook.Request{
Operation: admissionmodels.Operation(attr.GetVerb()),
Resource: attr.GetResource(),
Name: attr.GetName(),
SubResource: attr.GetSubResource(),
Version: attr.GetAPIVersion(),
Object: object,
Options: options,
}
if err := admissionwebhook.Validating(c, admissionRequest); err != nil {
response.AbortWithRPCError(c,
rpcerror.ParamError.WithErrMsg(fmt.Sprintf("admission validating failed: %v", err)))
return
}
c.Next()
}, skippers...)
}
2 changes: 1 addition & 1 deletion core/middleware/tag/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
)

// Middleware to parse tagSelector params
// Middleware to parse and attach tagSelector to context
func Middleware(skippers ...middleware.Skipper) gin.HandlerFunc {
return middleware.New(func(c *gin.Context) {
// parse tagSelector
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/hashicorp/go-retryablehttp v0.6.8
github.com/igm/sockjs-go v3.0.2+incompatible // indirect
github.com/johannesboyne/gofakes3 v0.0.0-20210819161434-5c8dfcfe5310
github.com/mattbaird/jsonpatch v0.0.0-20230413205102-771768614e91
github.com/mozillazg/go-pinyin v0.18.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0
Expand All @@ -37,6 +38,7 @@ require (
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/evanphx/json-patch.v5 v5.6.0
gopkg.in/igm/sockjs-go.v3 v3.0.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,8 @@ github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHef
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
github.com/mattbaird/jsonpatch v0.0.0-20230413205102-771768614e91 h1:JnZSkFP1/GLwKCEuuWVhsacvbDQIVa5BRwAwd+9k2Vw=
github.com/mattbaird/jsonpatch v0.0.0-20230413205102-771768614e91/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
Expand Down Expand Up @@ -2473,6 +2475,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v5 v5.6.0 h1:BMT6KIwBD9CaU91PJCZIe46bDmBWa9ynTQgJIOpfQBk=
gopkg.in/evanphx/json-patch.v5 v5.6.0/go.mod h1:/kvTRh1TVm5wuM6OkHxqXtE/1nUZZpihg29RtuIyfvk=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
Expand Down
Loading

0 comments on commit 4c1159d

Please sign in to comment.