package context import ( "encoding/json" "strings" "sync" jsonpatch "github.com/evanphx/json-patch" "github.com/go-logr/logr" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" "sigs.k8s.io/controller-runtime/pkg/log" ) //Interface to manage context operations type Interface interface { //AddJSON merges the json with context AddJSON(dataRaw []byte) error //AddResource merges resource json under request.object AddResource(dataRaw []byte) error //AddUserInfo merges userInfo json under kyverno.userInfo AddUserInfo(userInfo kyverno.UserInfo) error //AddSA merges serrviceaccount AddSA(userName string) error EvalInterface } //EvalInterface ... to evaluate type EvalInterface interface { Query(query string) (interface{}, error) } //Context stores the data resources as JSON type Context struct { mu sync.RWMutex jsonRaw []byte whiteListVars []string log logr.Logger } //NewContext returns a new context // pass the list of variables to be white-listed func NewContext(whiteListVars ...string) *Context { ctx := Context{ // data: map[string]interface{}{}, jsonRaw: []byte(`{}`), // empty json struct whiteListVars: whiteListVars, log: log.Log.WithName("context"), } return &ctx } // AddJSON merges json data func (ctx *Context) AddJSON(dataRaw []byte) error { var err error ctx.mu.Lock() defer ctx.mu.Unlock() // merge json ctx.jsonRaw, err = jsonpatch.MergePatch(ctx.jsonRaw, dataRaw) if err != nil { ctx.log.Error(err, "failed to merge JSON data") return err } return nil } //AddResource data at path: request.object func (ctx *Context) AddResource(dataRaw []byte) error { // unmarshall the resource struct var data interface{} if err := json.Unmarshal(dataRaw, &data); err != nil { ctx.log.Error(err, "failed to unmarshall the resource") return err } modifiedResource := struct { Request interface{} `json:"request"` }{ Request: struct { Object interface{} `json:"object"` }{ Object: data, }, } objRaw, err := json.Marshal(modifiedResource) if err != nil { ctx.log.Error(err, "failed to marshal the resource") return err } return ctx.AddJSON(objRaw) } //AddUserInfo adds userInfo at path request.userInfo func (ctx *Context) AddUserInfo(userRequestInfo kyverno.RequestInfo) error { modifiedResource := struct { Request interface{} `json:"request"` }{ Request: userRequestInfo, } objRaw, err := json.Marshal(modifiedResource) if err != nil { ctx.log.Error(err, "failed to marshal the UserInfo") return err } return ctx.AddJSON(objRaw) } //AddSA removes prefix 'system:serviceaccount:' and namespace, then loads only SA name and SA namespace func (ctx *Context) AddSA(userName string) error { saPrefix := "system:serviceaccount:" var sa string saName := "" saNamespace := "" if len(userName) <= len(saPrefix) { sa = "" } else { sa = userName[len(saPrefix):] } // filter namespace groups := strings.Split(sa, ":") if len(groups) >= 2 { saName = groups[1] saNamespace = groups[0] } saNameObj := struct { SA string `json:"serviceAccountName"` }{ SA: saName, } saNameRaw, err := json.Marshal(saNameObj) if err != nil { ctx.log.Error(err, "failed to marshal the SA") return err } if err := ctx.AddJSON(saNameRaw); err != nil { return err } saNsObj := struct { SA string `json:"serviceAccountNamespace"` }{ SA: saNamespace, } saNsRaw, err := json.Marshal(saNsObj) if err != nil { ctx.log.Error(err, "failed to marshal the SA namespace") return err } if err := ctx.AddJSON(saNsRaw); err != nil { return err } return nil }