Fix update/patch and add webhook auth

This commit is contained in:
Darren Shepherd
2019-09-11 10:32:01 -07:00
parent 7f5d248680
commit ef235af49c
22 changed files with 1453 additions and 195 deletions

View File

@@ -195,6 +195,8 @@ func determineVerb(apiOp *types.APIRequest) Verb {
return Get
case http.MethodPost:
return Create
case http.MethodPatch:
return Update
case http.MethodPut:
return Update
case http.MethodDelete:

93
vendor/github.com/rancher/norman/pkg/auth/filter.go generated vendored Normal file
View File

@@ -0,0 +1,93 @@
package auth
import (
"net/http"
"strings"
v1 "k8s.io/api/authentication/v1"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
)
type Authenticator interface {
Authenticate(req *http.Request) (user.Info, bool, error)
}
func NewWebhookAuthenticator(kubeConfigFile string) (Authenticator, error) {
wh, err := webhook.New(kubeConfigFile, nil)
if err != nil {
return nil, err
}
return &webhookAuth{
auth: wh,
}, nil
}
func NewWebhookMiddleware(kubeConfigFile string) (func(http.ResponseWriter, *http.Request, http.Handler), error) {
auth, err := NewWebhookAuthenticator(kubeConfigFile)
if err != nil {
return nil, err
}
return ToMiddleware(auth), nil
}
type webhookAuth struct {
auth authenticator.Token
}
func (w *webhookAuth) Authenticate(req *http.Request) (user.Info, bool, error) {
token := req.Header.Get("Authorization")
if strings.HasPrefix(token, "Bearer ") {
token = strings.TrimPrefix(token, "Bearer ")
} else {
token = ""
}
if token == "" {
cookie, err := req.Cookie("R_SESS")
if err != nil && err != http.ErrNoCookie {
return nil, false, err
} else if err != http.ErrNoCookie && len(cookie.Value) > 0 {
token = "cookie://" + cookie.Value
}
}
if token == "" {
return nil, false, nil
}
resp, ok, err := w.auth.AuthenticateToken(req.Context(), token)
if resp == nil {
return nil, ok, err
}
return resp.User, ok, err
}
func ToMiddleware(auth Authenticator) func(rw http.ResponseWriter, req *http.Request, next http.Handler) {
return func(rw http.ResponseWriter, req *http.Request, next http.Handler) {
info, ok, err := auth.Authenticate(req)
if err != nil {
rw.WriteHeader(http.StatusServiceUnavailable)
rw.Write([]byte(err.Error()))
return
}
if !ok {
rw.WriteHeader(http.StatusUnauthorized)
return
}
ctx := request.WithUser(req.Context(), info)
req = req.WithContext(ctx)
req.Header.Set(v1.ImpersonateUserHeader, info.GetName())
for _, group := range info.GetGroups() {
req.Header.Set(v1.ImpersonateGroupHeader, group)
}
next.ServeHTTP(rw, req)
}
}

View File

@@ -22,7 +22,7 @@ func (o Object) Map(names ...string) Object {
func (o Object) Slice(names ...string) (result []Object) {
v := values.GetValueN(o, names...)
for _, item := range convert.ToInterfaceSlice(v) {
result = append(result, Object(convert.ToMapInterface(item)))
result = append(result, convert.ToMapInterface(item))
}
return
}

View File

@@ -1,17 +1,22 @@
package proxy
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"sync"
types2 "k8s.io/apimachinery/pkg/types"
"github.com/rancher/norman/pkg/types/convert"
errors2 "github.com/pkg/errors"
"github.com/rancher/norman/pkg/types"
"github.com/rancher/norman/pkg/types/convert/merge"
"github.com/rancher/norman/pkg/types/values"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/watch"
@@ -260,31 +265,32 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.Schema, params typ
return types.APIObject{}, err
}
for i := 0; i < 5; i++ {
resp, err := k8sClient.Get(id, metav1.GetOptions{})
if apiOp.Method == http.MethodPatch {
bytes, err := json.Marshal(data)
if err != nil {
return types.APIObject{}, err
}
resourceVersion, existing := resp.GetResourceVersion(), resp.Object
existing = merge.APIUpdateMerge(schema.InternalSchema, apiOp.Schemas, existing, data, apiOp.Option("replace") == "true")
values.PutValue(existing, resourceVersion, "metadata", "resourceVersion")
if len(apiOp.Namespaces) > 0 {
values.PutValue(existing, apiOp.Namespaces[0], "metadata", "namespace")
}
values.PutValue(existing, id, "metadata", "name")
resp, err = k8sClient.Update(&unstructured.Unstructured{Object: existing}, metav1.UpdateOptions{})
if errors.IsConflict(err) {
continue
} else if err != nil {
resp, err := k8sClient.Patch(id, types2.StrategicMergePatchType, bytes, metav1.PatchOptions{})
if err != nil {
return types.APIObject{}, err
}
_, result, err = s.singleResult(apiOp, schema, resp)
return types.ToAPI(result), err
}
resourceVersion := convert.ToString(values.GetValueN(data, "metadata", "resourceVersion"))
if resourceVersion == "" {
return types.APIObject{}, fmt.Errorf("metadata.resourceVersion is required for update")
}
resp, err := k8sClient.Update(&unstructured.Unstructured{Object: data}, metav1.UpdateOptions{})
if err != nil {
return types.APIObject{}, err
}
_, result, err = s.singleResult(apiOp, schema, resp)
return types.ToAPI(result), err
}

View File

@@ -1,156 +0,0 @@
package merge
import (
"strings"
"github.com/rancher/norman/pkg/types"
convert2 "github.com/rancher/norman/pkg/types/convert"
definition2 "github.com/rancher/norman/pkg/types/definition"
)
func APIUpdateMerge(schema *types.Schema, schemas *types.Schemas, dest, src map[string]interface{}, replace bool) map[string]interface{} {
result := UpdateMerge(schema, schemas, dest, src, replace)
if s, ok := dest["status"]; ok {
result["status"] = s
}
if m, ok := dest["metadata"]; ok {
result["metadata"] = mergeMetadata(convert2.ToMapInterface(m), convert2.ToMapInterface(src["metadata"]))
}
return result
}
func UpdateMerge(schema *types.Schema, schemas *types.Schemas, dest, src map[string]interface{}, replace bool) map[string]interface{} {
return mergeMaps("", nil, schema, schemas, replace, dest, src)
}
func isProtected(k string) bool {
if !strings.Contains(k, "cattle.io/") || (isField(k) && k != "field.cattle.io/creatorId") {
return false
}
return true
}
func isField(k string) bool {
return strings.HasPrefix(k, "field.cattle.io/")
}
func mergeProtected(dest, src map[string]interface{}) map[string]interface{} {
if src == nil {
return dest
}
result := copyMap(dest)
for k, v := range src {
if isProtected(k) {
continue
}
result[k] = v
}
for k := range dest {
if isProtected(k) || isField(k) {
continue
}
if _, ok := src[k]; !ok {
delete(result, k)
}
}
return result
}
func mergeMetadata(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
result := copyMap(dest)
labels := convert2.ToMapInterface(dest["labels"])
srcLabels := convert2.ToMapInterface(src["labels"])
labels = mergeProtected(labels, srcLabels)
annotations := convert2.ToMapInterface(dest["annotations"])
srcAnnotation := convert2.ToMapInterface(src["annotations"])
annotations = mergeProtected(annotations, srcAnnotation)
result["labels"] = labels
result["annotations"] = annotations
return result
}
func merge(field, fieldType string, parentSchema, schema *types.Schema, schemas *types.Schemas, replace bool, dest, src interface{}) interface{} {
if isMap(field, schema, schemas) {
return src
}
sm, smOk := src.(map[string]interface{})
dm, dmOk := dest.(map[string]interface{})
if smOk && dmOk {
fieldType, fieldSchema := getSchema(field, fieldType, parentSchema, schema, schemas)
return mergeMaps(fieldType, schema, fieldSchema, schemas, replace, dm, sm)
}
return src
}
func getSchema(field, parentFieldType string, parentSchema, schema *types.Schema, schemas *types.Schemas) (string, *types.Schema) {
if schema == nil {
if definition2.IsMapType(parentFieldType) && parentSchema != nil {
subType := definition2.SubType(parentFieldType)
s := schemas.Schema(subType)
if s != nil && s.InternalSchema != nil {
s = s.InternalSchema
}
return subType, s
}
return "", nil
}
fieldType := schema.ResourceFields[field].Type
s := schemas.Schema(fieldType)
if s != nil && s.InternalSchema != nil {
return fieldType, s.InternalSchema
}
return fieldType, s
}
func isMap(field string, schema *types.Schema, schemas *types.Schemas) bool {
if schema == nil {
return false
}
f := schema.ResourceFields[field]
mapType := definition2.IsMapType(f.Type)
if !mapType {
return false
}
subType := definition2.SubType(f.Type)
return schemas.Schema(subType) == nil
}
func mergeMaps(fieldType string, parentSchema, schema *types.Schema, schemas *types.Schemas, replace bool, dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
result := copyMapReplace(schema, dest, replace)
for k, v := range src {
result[k] = merge(k, fieldType, parentSchema, schema, schemas, replace, dest[k], v)
}
return result
}
func copyMap(src map[string]interface{}) map[string]interface{} {
result := map[string]interface{}{}
for k, v := range src {
result[k] = v
}
return result
}
func copyMapReplace(schema *types.Schema, src map[string]interface{}, replace bool) map[string]interface{} {
result := map[string]interface{}{}
for k, v := range src {
if replace && schema != nil {
f := schema.ResourceFields[k]
if f.Update {
continue
}
}
result[k] = v
}
return result
}

View File

@@ -45,7 +45,14 @@ func getHost(r *http.Request, scheme string) string {
func getScheme(r *http.Request) string {
scheme := r.Header.Get(ForwardedProtoHeader)
if scheme != "" {
return scheme
switch scheme {
case "ws":
return "http"
case "wss":
return "https"
default:
return scheme
}
} else if r.TLS != nil {
return "https"
}