mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	move client auth plugins
This commit is contained in:
		| @@ -1,20 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 The Kubernetes Authors. | ||||
|  | ||||
| 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 jsonpath is a template engine using jsonpath syntax, | ||||
| // which can be seen at http://goessner.net/articles/JsonPath/. | ||||
| // In addition, it has {range} {end} function to iterate list and slice. | ||||
| package jsonpath // import "k8s.io/kubernetes/pkg/util/jsonpath" | ||||
| @@ -1,498 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 The Kubernetes Authors. | ||||
|  | ||||
| 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 jsonpath | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"k8s.io/kubernetes/third_party/forked/golang/template" | ||||
| ) | ||||
|  | ||||
| type JSONPath struct { | ||||
| 	name       string | ||||
| 	parser     *Parser | ||||
| 	stack      [][]reflect.Value //push and pop values in different scopes | ||||
| 	cur        []reflect.Value   //current scope values | ||||
| 	beginRange int | ||||
| 	inRange    int | ||||
| 	endRange   int | ||||
|  | ||||
| 	allowMissingKeys bool | ||||
| } | ||||
|  | ||||
| func New(name string) *JSONPath { | ||||
| 	return &JSONPath{ | ||||
| 		name:       name, | ||||
| 		beginRange: 0, | ||||
| 		inRange:    0, | ||||
| 		endRange:   0, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AllowMissingKeys allows a caller to specify whether they want an error if a field or map key | ||||
| // cannot be located, or simply an empty result. The receiver is returned for chaining. | ||||
| func (j *JSONPath) AllowMissingKeys(allow bool) *JSONPath { | ||||
| 	j.allowMissingKeys = allow | ||||
| 	return j | ||||
| } | ||||
|  | ||||
| // Parse parse the given template, return error | ||||
| func (j *JSONPath) Parse(text string) (err error) { | ||||
| 	j.parser, err = Parse(j.name, text) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Execute bounds data into template and write the result | ||||
| func (j *JSONPath) Execute(wr io.Writer, data interface{}) error { | ||||
| 	fullResults, err := j.FindResults(data) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for ix := range fullResults { | ||||
| 		if err := j.PrintResults(wr, fullResults[ix]); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) { | ||||
| 	if j.parser == nil { | ||||
| 		return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name) | ||||
| 	} | ||||
|  | ||||
| 	j.cur = []reflect.Value{reflect.ValueOf(data)} | ||||
| 	nodes := j.parser.Root.Nodes | ||||
| 	fullResult := [][]reflect.Value{} | ||||
| 	for i := 0; i < len(nodes); i++ { | ||||
| 		node := nodes[i] | ||||
| 		results, err := j.walk(j.cur, node) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		//encounter an end node, break the current block | ||||
| 		if j.endRange > 0 && j.endRange <= j.inRange { | ||||
| 			j.endRange -= 1 | ||||
| 			break | ||||
| 		} | ||||
| 		//encounter a range node, start a range loop | ||||
| 		if j.beginRange > 0 { | ||||
| 			j.beginRange -= 1 | ||||
| 			j.inRange += 1 | ||||
| 			for k, value := range results { | ||||
| 				j.parser.Root.Nodes = nodes[i+1:] | ||||
| 				if k == len(results)-1 { | ||||
| 					j.inRange -= 1 | ||||
| 				} | ||||
| 				nextResults, err := j.FindResults(value.Interface()) | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				fullResult = append(fullResult, nextResults...) | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 		fullResult = append(fullResult, results) | ||||
| 	} | ||||
| 	return fullResult, nil | ||||
| } | ||||
|  | ||||
| // PrintResults write the results into writer | ||||
| func (j *JSONPath) PrintResults(wr io.Writer, results []reflect.Value) error { | ||||
| 	for i, r := range results { | ||||
| 		text, err := j.evalToText(r) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if i != len(results)-1 { | ||||
| 			text = append(text, ' ') | ||||
| 		} | ||||
| 		if _, err = wr.Write(text); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // walk visits tree rooted at the given node in DFS order | ||||
| func (j *JSONPath) walk(value []reflect.Value, node Node) ([]reflect.Value, error) { | ||||
| 	switch node := node.(type) { | ||||
| 	case *ListNode: | ||||
| 		return j.evalList(value, node) | ||||
| 	case *TextNode: | ||||
| 		return []reflect.Value{reflect.ValueOf(node.Text)}, nil | ||||
| 	case *FieldNode: | ||||
| 		return j.evalField(value, node) | ||||
| 	case *ArrayNode: | ||||
| 		return j.evalArray(value, node) | ||||
| 	case *FilterNode: | ||||
| 		return j.evalFilter(value, node) | ||||
| 	case *IntNode: | ||||
| 		return j.evalInt(value, node) | ||||
| 	case *FloatNode: | ||||
| 		return j.evalFloat(value, node) | ||||
| 	case *WildcardNode: | ||||
| 		return j.evalWildcard(value, node) | ||||
| 	case *RecursiveNode: | ||||
| 		return j.evalRecursive(value, node) | ||||
| 	case *UnionNode: | ||||
| 		return j.evalUnion(value, node) | ||||
| 	case *IdentifierNode: | ||||
| 		return j.evalIdentifier(value, node) | ||||
| 	default: | ||||
| 		return value, fmt.Errorf("unexpected Node %v", node) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // evalInt evaluates IntNode | ||||
| func (j *JSONPath) evalInt(input []reflect.Value, node *IntNode) ([]reflect.Value, error) { | ||||
| 	result := make([]reflect.Value, len(input)) | ||||
| 	for i := range input { | ||||
| 		result[i] = reflect.ValueOf(node.Value) | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // evalFloat evaluates FloatNode | ||||
| func (j *JSONPath) evalFloat(input []reflect.Value, node *FloatNode) ([]reflect.Value, error) { | ||||
| 	result := make([]reflect.Value, len(input)) | ||||
| 	for i := range input { | ||||
| 		result[i] = reflect.ValueOf(node.Value) | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // evalList evaluates ListNode | ||||
| func (j *JSONPath) evalList(value []reflect.Value, node *ListNode) ([]reflect.Value, error) { | ||||
| 	var err error | ||||
| 	curValue := value | ||||
| 	for _, node := range node.Nodes { | ||||
| 		curValue, err = j.walk(curValue, node) | ||||
| 		if err != nil { | ||||
| 			return curValue, err | ||||
| 		} | ||||
| 	} | ||||
| 	return curValue, nil | ||||
| } | ||||
|  | ||||
| // evalIdentifier evaluates IdentifierNode | ||||
| func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) ([]reflect.Value, error) { | ||||
| 	results := []reflect.Value{} | ||||
| 	switch node.Name { | ||||
| 	case "range": | ||||
| 		j.stack = append(j.stack, j.cur) | ||||
| 		j.beginRange += 1 | ||||
| 		results = input | ||||
| 	case "end": | ||||
| 		if j.endRange < j.inRange { //inside a loop, break the current block | ||||
| 			j.endRange += 1 | ||||
| 			break | ||||
| 		} | ||||
| 		// the loop is about to end, pop value and continue the following execution | ||||
| 		if len(j.stack) > 0 { | ||||
| 			j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1] | ||||
| 		} else { | ||||
| 			return results, fmt.Errorf("not in range, nothing to end") | ||||
| 		} | ||||
| 	default: | ||||
| 		return input, fmt.Errorf("unrecognized identifier %v", node.Name) | ||||
| 	} | ||||
| 	return results, nil | ||||
| } | ||||
|  | ||||
| // evalArray evaluates ArrayNode | ||||
| func (j *JSONPath) evalArray(input []reflect.Value, node *ArrayNode) ([]reflect.Value, error) { | ||||
| 	result := []reflect.Value{} | ||||
| 	for _, value := range input { | ||||
|  | ||||
| 		value, isNil := template.Indirect(value) | ||||
| 		if isNil { | ||||
| 			continue | ||||
| 		} | ||||
| 		if value.Kind() != reflect.Array && value.Kind() != reflect.Slice { | ||||
| 			return input, fmt.Errorf("%v is not array or slice", value.Type()) | ||||
| 		} | ||||
| 		params := node.Params | ||||
| 		if !params[0].Known { | ||||
| 			params[0].Value = 0 | ||||
| 		} | ||||
| 		if params[0].Value < 0 { | ||||
| 			params[0].Value += value.Len() | ||||
| 		} | ||||
| 		if !params[1].Known { | ||||
| 			params[1].Value = value.Len() | ||||
| 		} | ||||
|  | ||||
| 		if params[1].Value < 0 { | ||||
| 			params[1].Value += value.Len() | ||||
| 		} | ||||
|  | ||||
| 		sliceLength := value.Len() | ||||
| 		if params[1].Value != params[0].Value { // if you're requesting zero elements, allow it through. | ||||
| 			if params[0].Value >= sliceLength { | ||||
| 				return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[0].Value, sliceLength) | ||||
| 			} | ||||
| 			if params[1].Value > sliceLength { | ||||
| 				return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[1].Value-1, sliceLength) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if !params[2].Known { | ||||
| 			value = value.Slice(params[0].Value, params[1].Value) | ||||
| 		} else { | ||||
| 			value = value.Slice3(params[0].Value, params[1].Value, params[2].Value) | ||||
| 		} | ||||
| 		for i := 0; i < value.Len(); i++ { | ||||
| 			result = append(result, value.Index(i)) | ||||
| 		} | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // evalUnion evaluates UnionNode | ||||
| func (j *JSONPath) evalUnion(input []reflect.Value, node *UnionNode) ([]reflect.Value, error) { | ||||
| 	result := []reflect.Value{} | ||||
| 	for _, listNode := range node.Nodes { | ||||
| 		temp, err := j.evalList(input, listNode) | ||||
| 		if err != nil { | ||||
| 			return input, err | ||||
| 		} | ||||
| 		result = append(result, temp...) | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| func (j *JSONPath) findFieldInValue(value *reflect.Value, node *FieldNode) (reflect.Value, error) { | ||||
| 	t := value.Type() | ||||
| 	var inlineValue *reflect.Value | ||||
| 	for ix := 0; ix < t.NumField(); ix++ { | ||||
| 		f := t.Field(ix) | ||||
| 		jsonTag := f.Tag.Get("json") | ||||
| 		parts := strings.Split(jsonTag, ",") | ||||
| 		if len(parts) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		if parts[0] == node.Value { | ||||
| 			return value.Field(ix), nil | ||||
| 		} | ||||
| 		if len(parts[0]) == 0 { | ||||
| 			val := value.Field(ix) | ||||
| 			inlineValue = &val | ||||
| 		} | ||||
| 	} | ||||
| 	if inlineValue != nil { | ||||
| 		if inlineValue.Kind() == reflect.Struct { | ||||
| 			// handle 'inline' | ||||
| 			match, err := j.findFieldInValue(inlineValue, node) | ||||
| 			if err != nil { | ||||
| 				return reflect.Value{}, err | ||||
| 			} | ||||
| 			if match.IsValid() { | ||||
| 				return match, nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return value.FieldByName(node.Value), nil | ||||
| } | ||||
|  | ||||
| // evalField evaluates field of struct or key of map. | ||||
| func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.Value, error) { | ||||
| 	results := []reflect.Value{} | ||||
| 	// If there's no input, there's no output | ||||
| 	if len(input) == 0 { | ||||
| 		return results, nil | ||||
| 	} | ||||
| 	for _, value := range input { | ||||
| 		var result reflect.Value | ||||
| 		value, isNil := template.Indirect(value) | ||||
| 		if isNil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if value.Kind() == reflect.Struct { | ||||
| 			var err error | ||||
| 			if result, err = j.findFieldInValue(&value, node); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} else if value.Kind() == reflect.Map { | ||||
| 			mapKeyType := value.Type().Key() | ||||
| 			nodeValue := reflect.ValueOf(node.Value) | ||||
| 			// node value type must be convertible to map key type | ||||
| 			if !nodeValue.Type().ConvertibleTo(mapKeyType) { | ||||
| 				return results, fmt.Errorf("%s is not convertible to %s", nodeValue, mapKeyType) | ||||
| 			} | ||||
| 			result = value.MapIndex(nodeValue.Convert(mapKeyType)) | ||||
| 		} | ||||
| 		if result.IsValid() { | ||||
| 			results = append(results, result) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(results) == 0 { | ||||
| 		if j.allowMissingKeys { | ||||
| 			return results, nil | ||||
| 		} | ||||
| 		return results, fmt.Errorf("%s is not found", node.Value) | ||||
| 	} | ||||
| 	return results, nil | ||||
| } | ||||
|  | ||||
| // evalWildcard extract all contents of the given value | ||||
| func (j *JSONPath) evalWildcard(input []reflect.Value, node *WildcardNode) ([]reflect.Value, error) { | ||||
| 	results := []reflect.Value{} | ||||
| 	for _, value := range input { | ||||
| 		value, isNil := template.Indirect(value) | ||||
| 		if isNil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		kind := value.Kind() | ||||
| 		if kind == reflect.Struct { | ||||
| 			for i := 0; i < value.NumField(); i++ { | ||||
| 				results = append(results, value.Field(i)) | ||||
| 			} | ||||
| 		} else if kind == reflect.Map { | ||||
| 			for _, key := range value.MapKeys() { | ||||
| 				results = append(results, value.MapIndex(key)) | ||||
| 			} | ||||
| 		} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String { | ||||
| 			for i := 0; i < value.Len(); i++ { | ||||
| 				results = append(results, value.Index(i)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return results, nil | ||||
| } | ||||
|  | ||||
| // evalRecursive visit the given value recursively and push all of them to result | ||||
| func (j *JSONPath) evalRecursive(input []reflect.Value, node *RecursiveNode) ([]reflect.Value, error) { | ||||
| 	result := []reflect.Value{} | ||||
| 	for _, value := range input { | ||||
| 		results := []reflect.Value{} | ||||
| 		value, isNil := template.Indirect(value) | ||||
| 		if isNil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		kind := value.Kind() | ||||
| 		if kind == reflect.Struct { | ||||
| 			for i := 0; i < value.NumField(); i++ { | ||||
| 				results = append(results, value.Field(i)) | ||||
| 			} | ||||
| 		} else if kind == reflect.Map { | ||||
| 			for _, key := range value.MapKeys() { | ||||
| 				results = append(results, value.MapIndex(key)) | ||||
| 			} | ||||
| 		} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String { | ||||
| 			for i := 0; i < value.Len(); i++ { | ||||
| 				results = append(results, value.Index(i)) | ||||
| 			} | ||||
| 		} | ||||
| 		if len(results) != 0 { | ||||
| 			result = append(result, value) | ||||
| 			output, err := j.evalRecursive(results, node) | ||||
| 			if err != nil { | ||||
| 				return result, err | ||||
| 			} | ||||
| 			result = append(result, output...) | ||||
| 		} | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // evalFilter filter array according to FilterNode | ||||
| func (j *JSONPath) evalFilter(input []reflect.Value, node *FilterNode) ([]reflect.Value, error) { | ||||
| 	results := []reflect.Value{} | ||||
| 	for _, value := range input { | ||||
| 		value, _ = template.Indirect(value) | ||||
|  | ||||
| 		if value.Kind() != reflect.Array && value.Kind() != reflect.Slice { | ||||
| 			return input, fmt.Errorf("%v is not array or slice and cannot be filtered", value) | ||||
| 		} | ||||
| 		for i := 0; i < value.Len(); i++ { | ||||
| 			temp := []reflect.Value{value.Index(i)} | ||||
| 			lefts, err := j.evalList(temp, node.Left) | ||||
|  | ||||
| 			//case exists | ||||
| 			if node.Operator == "exists" { | ||||
| 				if len(lefts) > 0 { | ||||
| 					results = append(results, value.Index(i)) | ||||
| 				} | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			if err != nil { | ||||
| 				return input, err | ||||
| 			} | ||||
|  | ||||
| 			var left, right interface{} | ||||
| 			if len(lefts) != 1 { | ||||
| 				return input, fmt.Errorf("can only compare one element at a time") | ||||
| 			} | ||||
| 			left = lefts[0].Interface() | ||||
|  | ||||
| 			rights, err := j.evalList(temp, node.Right) | ||||
| 			if err != nil { | ||||
| 				return input, err | ||||
| 			} | ||||
| 			if len(rights) != 1 { | ||||
| 				return input, fmt.Errorf("can only compare one element at a time") | ||||
| 			} | ||||
| 			right = rights[0].Interface() | ||||
|  | ||||
| 			pass := false | ||||
| 			switch node.Operator { | ||||
| 			case "<": | ||||
| 				pass, err = template.Less(left, right) | ||||
| 			case ">": | ||||
| 				pass, err = template.Greater(left, right) | ||||
| 			case "==": | ||||
| 				pass, err = template.Equal(left, right) | ||||
| 			case "!=": | ||||
| 				pass, err = template.NotEqual(left, right) | ||||
| 			case "<=": | ||||
| 				pass, err = template.LessEqual(left, right) | ||||
| 			case ">=": | ||||
| 				pass, err = template.GreaterEqual(left, right) | ||||
| 			default: | ||||
| 				return results, fmt.Errorf("unrecognized filter operator %s", node.Operator) | ||||
| 			} | ||||
| 			if err != nil { | ||||
| 				return results, err | ||||
| 			} | ||||
| 			if pass { | ||||
| 				results = append(results, value.Index(i)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return results, nil | ||||
| } | ||||
|  | ||||
| // evalToText translates reflect value to corresponding text | ||||
| func (j *JSONPath) evalToText(v reflect.Value) ([]byte, error) { | ||||
| 	iface, ok := template.PrintableValue(v) | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("can't print type %s", v.Type()) | ||||
| 	} | ||||
| 	var buffer bytes.Buffer | ||||
| 	fmt.Fprint(&buffer, iface) | ||||
| 	return buffer.Bytes(), nil | ||||
| } | ||||
| @@ -1,239 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 The Kubernetes Authors. | ||||
|  | ||||
| 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 jsonpath | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // NodeType identifies the type of a parse tree node. | ||||
| type NodeType int | ||||
|  | ||||
| // Type returns itself and provides an easy default implementation | ||||
| func (t NodeType) Type() NodeType { | ||||
| 	return t | ||||
| } | ||||
|  | ||||
| func (t NodeType) String() string { | ||||
| 	return NodeTypeName[t] | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	NodeText NodeType = iota | ||||
| 	NodeArray | ||||
| 	NodeList | ||||
| 	NodeField | ||||
| 	NodeIdentifier | ||||
| 	NodeFilter | ||||
| 	NodeInt | ||||
| 	NodeFloat | ||||
| 	NodeWildcard | ||||
| 	NodeRecursive | ||||
| 	NodeUnion | ||||
| ) | ||||
|  | ||||
| var NodeTypeName = map[NodeType]string{ | ||||
| 	NodeText:       "NodeText", | ||||
| 	NodeArray:      "NodeArray", | ||||
| 	NodeList:       "NodeList", | ||||
| 	NodeField:      "NodeField", | ||||
| 	NodeIdentifier: "NodeIdentifier", | ||||
| 	NodeFilter:     "NodeFilter", | ||||
| 	NodeInt:        "NodeInt", | ||||
| 	NodeFloat:      "NodeFloat", | ||||
| 	NodeWildcard:   "NodeWildcard", | ||||
| 	NodeRecursive:  "NodeRecursive", | ||||
| 	NodeUnion:      "NodeUnion", | ||||
| } | ||||
|  | ||||
| type Node interface { | ||||
| 	Type() NodeType | ||||
| 	String() string | ||||
| } | ||||
|  | ||||
| // ListNode holds a sequence of nodes. | ||||
| type ListNode struct { | ||||
| 	NodeType | ||||
| 	Nodes []Node // The element nodes in lexical order. | ||||
| } | ||||
|  | ||||
| func newList() *ListNode { | ||||
| 	return &ListNode{NodeType: NodeList} | ||||
| } | ||||
|  | ||||
| func (l *ListNode) append(n Node) { | ||||
| 	l.Nodes = append(l.Nodes, n) | ||||
| } | ||||
|  | ||||
| func (l *ListNode) String() string { | ||||
| 	return fmt.Sprintf("%s", l.Type()) | ||||
| } | ||||
|  | ||||
| // TextNode holds plain text. | ||||
| type TextNode struct { | ||||
| 	NodeType | ||||
| 	Text string // The text; may span newlines. | ||||
| } | ||||
|  | ||||
| func newText(text string) *TextNode { | ||||
| 	return &TextNode{NodeType: NodeText, Text: text} | ||||
| } | ||||
|  | ||||
| func (t *TextNode) String() string { | ||||
| 	return fmt.Sprintf("%s: %s", t.Type(), t.Text) | ||||
| } | ||||
|  | ||||
| // FieldNode holds field of struct | ||||
| type FieldNode struct { | ||||
| 	NodeType | ||||
| 	Value string | ||||
| } | ||||
|  | ||||
| func newField(value string) *FieldNode { | ||||
| 	return &FieldNode{NodeType: NodeField, Value: value} | ||||
| } | ||||
|  | ||||
| func (f *FieldNode) String() string { | ||||
| 	return fmt.Sprintf("%s: %s", f.Type(), f.Value) | ||||
| } | ||||
|  | ||||
| // IdentifierNode holds an identifier | ||||
| type IdentifierNode struct { | ||||
| 	NodeType | ||||
| 	Name string | ||||
| } | ||||
|  | ||||
| func newIdentifier(value string) *IdentifierNode { | ||||
| 	return &IdentifierNode{ | ||||
| 		NodeType: NodeIdentifier, | ||||
| 		Name:     value, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (f *IdentifierNode) String() string { | ||||
| 	return fmt.Sprintf("%s: %s", f.Type(), f.Name) | ||||
| } | ||||
|  | ||||
| // ParamsEntry holds param information for ArrayNode | ||||
| type ParamsEntry struct { | ||||
| 	Value int | ||||
| 	Known bool //whether the value is known when parse it | ||||
| } | ||||
|  | ||||
| // ArrayNode holds start, end, step information for array index selection | ||||
| type ArrayNode struct { | ||||
| 	NodeType | ||||
| 	Params [3]ParamsEntry //start, end, step | ||||
| } | ||||
|  | ||||
| func newArray(params [3]ParamsEntry) *ArrayNode { | ||||
| 	return &ArrayNode{ | ||||
| 		NodeType: NodeArray, | ||||
| 		Params:   params, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (a *ArrayNode) String() string { | ||||
| 	return fmt.Sprintf("%s: %v", a.Type(), a.Params) | ||||
| } | ||||
|  | ||||
| // FilterNode holds operand and operator information for filter | ||||
| type FilterNode struct { | ||||
| 	NodeType | ||||
| 	Left     *ListNode | ||||
| 	Right    *ListNode | ||||
| 	Operator string | ||||
| } | ||||
|  | ||||
| func newFilter(left, right *ListNode, operator string) *FilterNode { | ||||
| 	return &FilterNode{ | ||||
| 		NodeType: NodeFilter, | ||||
| 		Left:     left, | ||||
| 		Right:    right, | ||||
| 		Operator: operator, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (f *FilterNode) String() string { | ||||
| 	return fmt.Sprintf("%s: %s %s %s", f.Type(), f.Left, f.Operator, f.Right) | ||||
| } | ||||
|  | ||||
| // IntNode holds integer value | ||||
| type IntNode struct { | ||||
| 	NodeType | ||||
| 	Value int | ||||
| } | ||||
|  | ||||
| func newInt(num int) *IntNode { | ||||
| 	return &IntNode{NodeType: NodeInt, Value: num} | ||||
| } | ||||
|  | ||||
| func (i *IntNode) String() string { | ||||
| 	return fmt.Sprintf("%s: %d", i.Type(), i.Value) | ||||
| } | ||||
|  | ||||
| // FloatNode holds float value | ||||
| type FloatNode struct { | ||||
| 	NodeType | ||||
| 	Value float64 | ||||
| } | ||||
|  | ||||
| func newFloat(num float64) *FloatNode { | ||||
| 	return &FloatNode{NodeType: NodeFloat, Value: num} | ||||
| } | ||||
|  | ||||
| func (i *FloatNode) String() string { | ||||
| 	return fmt.Sprintf("%s: %f", i.Type(), i.Value) | ||||
| } | ||||
|  | ||||
| // WildcardNode means a wildcard | ||||
| type WildcardNode struct { | ||||
| 	NodeType | ||||
| } | ||||
|  | ||||
| func newWildcard() *WildcardNode { | ||||
| 	return &WildcardNode{NodeType: NodeWildcard} | ||||
| } | ||||
|  | ||||
| func (i *WildcardNode) String() string { | ||||
| 	return fmt.Sprintf("%s", i.Type()) | ||||
| } | ||||
|  | ||||
| // RecursiveNode means a recursive descent operator | ||||
| type RecursiveNode struct { | ||||
| 	NodeType | ||||
| } | ||||
|  | ||||
| func newRecursive() *RecursiveNode { | ||||
| 	return &RecursiveNode{NodeType: NodeRecursive} | ||||
| } | ||||
|  | ||||
| func (r *RecursiveNode) String() string { | ||||
| 	return fmt.Sprintf("%s", r.Type()) | ||||
| } | ||||
|  | ||||
| // UnionNode is union of ListNode | ||||
| type UnionNode struct { | ||||
| 	NodeType | ||||
| 	Nodes []*ListNode | ||||
| } | ||||
|  | ||||
| func newUnion(nodes []*ListNode) *UnionNode { | ||||
| 	return &UnionNode{NodeType: NodeUnion, Nodes: nodes} | ||||
| } | ||||
|  | ||||
| func (u *UnionNode) String() string { | ||||
| 	return fmt.Sprintf("%s", u.Type()) | ||||
| } | ||||
| @@ -1,433 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 The Kubernetes Authors. | ||||
|  | ||||
| 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 jsonpath | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| const eof = -1 | ||||
|  | ||||
| const ( | ||||
| 	leftDelim  = "{" | ||||
| 	rightDelim = "}" | ||||
| ) | ||||
|  | ||||
| type Parser struct { | ||||
| 	Name  string | ||||
| 	Root  *ListNode | ||||
| 	input string | ||||
| 	cur   *ListNode | ||||
| 	pos   int | ||||
| 	start int | ||||
| 	width int | ||||
| } | ||||
|  | ||||
| // Parse parsed the given text and return a node Parser. | ||||
| // If an error is encountered, parsing stops and an empty | ||||
| // Parser is returned with the error | ||||
| func Parse(name, text string) (*Parser, error) { | ||||
| 	p := NewParser(name) | ||||
| 	err := p.Parse(text) | ||||
| 	if err != nil { | ||||
| 		p = nil | ||||
| 	} | ||||
| 	return p, err | ||||
| } | ||||
|  | ||||
| func NewParser(name string) *Parser { | ||||
| 	return &Parser{ | ||||
| 		Name: name, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // parseAction parsed the expression inside delimiter | ||||
| func parseAction(name, text string) (*Parser, error) { | ||||
| 	p, err := Parse(name, fmt.Sprintf("%s%s%s", leftDelim, text, rightDelim)) | ||||
| 	// when error happens, p will be nil, so we need to return here | ||||
| 	if err != nil { | ||||
| 		return p, err | ||||
| 	} | ||||
| 	p.Root = p.Root.Nodes[0].(*ListNode) | ||||
| 	return p, nil | ||||
| } | ||||
|  | ||||
| func (p *Parser) Parse(text string) error { | ||||
| 	p.input = text | ||||
| 	p.Root = newList() | ||||
| 	p.pos = 0 | ||||
| 	return p.parseText(p.Root) | ||||
| } | ||||
|  | ||||
| // consumeText return the parsed text since last cosumeText | ||||
| func (p *Parser) consumeText() string { | ||||
| 	value := p.input[p.start:p.pos] | ||||
| 	p.start = p.pos | ||||
| 	return value | ||||
| } | ||||
|  | ||||
| // next returns the next rune in the input. | ||||
| func (p *Parser) next() rune { | ||||
| 	if int(p.pos) >= len(p.input) { | ||||
| 		p.width = 0 | ||||
| 		return eof | ||||
| 	} | ||||
| 	r, w := utf8.DecodeRuneInString(p.input[p.pos:]) | ||||
| 	p.width = w | ||||
| 	p.pos += p.width | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // peek returns but does not consume the next rune in the input. | ||||
| func (p *Parser) peek() rune { | ||||
| 	r := p.next() | ||||
| 	p.backup() | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // backup steps back one rune. Can only be called once per call of next. | ||||
| func (p *Parser) backup() { | ||||
| 	p.pos -= p.width | ||||
| } | ||||
|  | ||||
| func (p *Parser) parseText(cur *ListNode) error { | ||||
| 	for { | ||||
| 		if strings.HasPrefix(p.input[p.pos:], leftDelim) { | ||||
| 			if p.pos > p.start { | ||||
| 				cur.append(newText(p.consumeText())) | ||||
| 			} | ||||
| 			return p.parseLeftDelim(cur) | ||||
| 		} | ||||
| 		if p.next() == eof { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	// Correctly reached EOF. | ||||
| 	if p.pos > p.start { | ||||
| 		cur.append(newText(p.consumeText())) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // parseLeftDelim scans the left delimiter, which is known to be present. | ||||
| func (p *Parser) parseLeftDelim(cur *ListNode) error { | ||||
| 	p.pos += len(leftDelim) | ||||
| 	p.consumeText() | ||||
| 	newNode := newList() | ||||
| 	cur.append(newNode) | ||||
| 	cur = newNode | ||||
| 	return p.parseInsideAction(cur) | ||||
| } | ||||
|  | ||||
| func (p *Parser) parseInsideAction(cur *ListNode) error { | ||||
| 	prefixMap := map[string]func(*ListNode) error{ | ||||
| 		rightDelim: p.parseRightDelim, | ||||
| 		"[?(":      p.parseFilter, | ||||
| 		"..":       p.parseRecursive, | ||||
| 	} | ||||
| 	for prefix, parseFunc := range prefixMap { | ||||
| 		if strings.HasPrefix(p.input[p.pos:], prefix) { | ||||
| 			return parseFunc(cur) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	switch r := p.next(); { | ||||
| 	case r == eof || isEndOfLine(r): | ||||
| 		return fmt.Errorf("unclosed action") | ||||
| 	case r == ' ': | ||||
| 		p.consumeText() | ||||
| 	case r == '@' || r == '$': //the current object, just pass it | ||||
| 		p.consumeText() | ||||
| 	case r == '[': | ||||
| 		return p.parseArray(cur) | ||||
| 	case r == '"': | ||||
| 		return p.parseQuote(cur) | ||||
| 	case r == '.': | ||||
| 		return p.parseField(cur) | ||||
| 	case r == '+' || r == '-' || unicode.IsDigit(r): | ||||
| 		p.backup() | ||||
| 		return p.parseNumber(cur) | ||||
| 	case isAlphaNumeric(r): | ||||
| 		p.backup() | ||||
| 		return p.parseIdentifier(cur) | ||||
| 	default: | ||||
| 		return fmt.Errorf("unrecognized character in action: %#U", r) | ||||
| 	} | ||||
| 	return p.parseInsideAction(cur) | ||||
| } | ||||
|  | ||||
| // parseRightDelim scans the right delimiter, which is known to be present. | ||||
| func (p *Parser) parseRightDelim(cur *ListNode) error { | ||||
| 	p.pos += len(rightDelim) | ||||
| 	p.consumeText() | ||||
| 	cur = p.Root | ||||
| 	return p.parseText(cur) | ||||
| } | ||||
|  | ||||
| // parseIdentifier scans build-in keywords, like "range" "end" | ||||
| func (p *Parser) parseIdentifier(cur *ListNode) error { | ||||
| 	var r rune | ||||
| 	for { | ||||
| 		r = p.next() | ||||
| 		if isTerminator(r) { | ||||
| 			p.backup() | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	value := p.consumeText() | ||||
| 	cur.append(newIdentifier(value)) | ||||
| 	return p.parseInsideAction(cur) | ||||
| } | ||||
|  | ||||
| // parseRecursive scans the recursive desent operator .. | ||||
| func (p *Parser) parseRecursive(cur *ListNode) error { | ||||
| 	p.pos += len("..") | ||||
| 	p.consumeText() | ||||
| 	cur.append(newRecursive()) | ||||
| 	if r := p.peek(); isAlphaNumeric(r) { | ||||
| 		return p.parseField(cur) | ||||
| 	} | ||||
| 	return p.parseInsideAction(cur) | ||||
| } | ||||
|  | ||||
| // parseNumber scans number | ||||
| func (p *Parser) parseNumber(cur *ListNode) error { | ||||
| 	r := p.peek() | ||||
| 	if r == '+' || r == '-' { | ||||
| 		r = p.next() | ||||
| 	} | ||||
| 	for { | ||||
| 		r = p.next() | ||||
| 		if r != '.' && !unicode.IsDigit(r) { | ||||
| 			p.backup() | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	value := p.consumeText() | ||||
| 	i, err := strconv.Atoi(value) | ||||
| 	if err == nil { | ||||
| 		cur.append(newInt(i)) | ||||
| 		return p.parseInsideAction(cur) | ||||
| 	} | ||||
| 	d, err := strconv.ParseFloat(value, 64) | ||||
| 	if err == nil { | ||||
| 		cur.append(newFloat(d)) | ||||
| 		return p.parseInsideAction(cur) | ||||
| 	} | ||||
| 	return fmt.Errorf("cannot parse number %s", value) | ||||
| } | ||||
|  | ||||
| // parseArray scans array index selection | ||||
| func (p *Parser) parseArray(cur *ListNode) error { | ||||
| Loop: | ||||
| 	for { | ||||
| 		switch p.next() { | ||||
| 		case eof, '\n': | ||||
| 			return fmt.Errorf("unterminated array") | ||||
| 		case ']': | ||||
| 			break Loop | ||||
| 		} | ||||
| 	} | ||||
| 	text := p.consumeText() | ||||
| 	text = string(text[1 : len(text)-1]) | ||||
| 	if text == "*" { | ||||
| 		text = ":" | ||||
| 	} | ||||
|  | ||||
| 	//union operator | ||||
| 	strs := strings.Split(text, ",") | ||||
| 	if len(strs) > 1 { | ||||
| 		union := []*ListNode{} | ||||
| 		for _, str := range strs { | ||||
| 			parser, err := parseAction("union", fmt.Sprintf("[%s]", strings.Trim(str, " "))) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			union = append(union, parser.Root) | ||||
| 		} | ||||
| 		cur.append(newUnion(union)) | ||||
| 		return p.parseInsideAction(cur) | ||||
| 	} | ||||
|  | ||||
| 	// dict key | ||||
| 	reg := regexp.MustCompile(`^'([^']*)'$`) | ||||
| 	value := reg.FindStringSubmatch(text) | ||||
| 	if value != nil { | ||||
| 		parser, err := parseAction("arraydict", fmt.Sprintf(".%s", value[1])) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		for _, node := range parser.Root.Nodes { | ||||
| 			cur.append(node) | ||||
| 		} | ||||
| 		return p.parseInsideAction(cur) | ||||
| 	} | ||||
|  | ||||
| 	//slice operator | ||||
| 	reg = regexp.MustCompile(`^(-?[\d]*)(:-?[\d]*)?(:[\d]*)?$`) | ||||
| 	value = reg.FindStringSubmatch(text) | ||||
| 	if value == nil { | ||||
| 		return fmt.Errorf("invalid array index %s", text) | ||||
| 	} | ||||
| 	value = value[1:] | ||||
| 	params := [3]ParamsEntry{} | ||||
| 	for i := 0; i < 3; i++ { | ||||
| 		if value[i] != "" { | ||||
| 			if i > 0 { | ||||
| 				value[i] = value[i][1:] | ||||
| 			} | ||||
| 			if i > 0 && value[i] == "" { | ||||
| 				params[i].Known = false | ||||
| 			} else { | ||||
| 				var err error | ||||
| 				params[i].Known = true | ||||
| 				params[i].Value, err = strconv.Atoi(value[i]) | ||||
| 				if err != nil { | ||||
| 					return fmt.Errorf("array index %s is not a number", value[i]) | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			if i == 1 { | ||||
| 				params[i].Known = true | ||||
| 				params[i].Value = params[0].Value + 1 | ||||
| 			} else { | ||||
| 				params[i].Known = false | ||||
| 				params[i].Value = 0 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	cur.append(newArray(params)) | ||||
| 	return p.parseInsideAction(cur) | ||||
| } | ||||
|  | ||||
| // parseFilter scans filter inside array selection | ||||
| func (p *Parser) parseFilter(cur *ListNode) error { | ||||
| 	p.pos += len("[?(") | ||||
| 	p.consumeText() | ||||
| Loop: | ||||
| 	for { | ||||
| 		switch p.next() { | ||||
| 		case eof, '\n': | ||||
| 			return fmt.Errorf("unterminated filter") | ||||
| 		case ')': | ||||
| 			break Loop | ||||
| 		} | ||||
| 	} | ||||
| 	if p.next() != ']' { | ||||
| 		return fmt.Errorf("unclosed array expect ]") | ||||
| 	} | ||||
| 	reg := regexp.MustCompile(`^([^!<>=]+)([!<>=]+)(.+?)$`) | ||||
| 	text := p.consumeText() | ||||
| 	text = string(text[:len(text)-2]) | ||||
| 	value := reg.FindStringSubmatch(text) | ||||
| 	if value == nil { | ||||
| 		parser, err := parseAction("text", text) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		cur.append(newFilter(parser.Root, newList(), "exists")) | ||||
| 	} else { | ||||
| 		leftParser, err := parseAction("left", value[1]) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		rightParser, err := parseAction("right", value[3]) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		cur.append(newFilter(leftParser.Root, rightParser.Root, value[2])) | ||||
| 	} | ||||
| 	return p.parseInsideAction(cur) | ||||
| } | ||||
|  | ||||
| // parseQuote unquotes string inside double quote | ||||
| func (p *Parser) parseQuote(cur *ListNode) error { | ||||
| Loop: | ||||
| 	for { | ||||
| 		switch p.next() { | ||||
| 		case eof, '\n': | ||||
| 			return fmt.Errorf("unterminated quoted string") | ||||
| 		case '"': | ||||
| 			break Loop | ||||
| 		} | ||||
| 	} | ||||
| 	value := p.consumeText() | ||||
| 	s, err := strconv.Unquote(value) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("unquote string %s error %v", value, err) | ||||
| 	} | ||||
| 	cur.append(newText(s)) | ||||
| 	return p.parseInsideAction(cur) | ||||
| } | ||||
|  | ||||
| // parseField scans a field until a terminator | ||||
| func (p *Parser) parseField(cur *ListNode) error { | ||||
| 	p.consumeText() | ||||
| 	for p.advance() { | ||||
| 	} | ||||
| 	value := p.consumeText() | ||||
| 	if value == "*" { | ||||
| 		cur.append(newWildcard()) | ||||
| 	} else { | ||||
| 		cur.append(newField(strings.Replace(value, "\\", "", -1))) | ||||
| 	} | ||||
| 	return p.parseInsideAction(cur) | ||||
| } | ||||
|  | ||||
| // advance scans until next non-escaped terminator | ||||
| func (p *Parser) advance() bool { | ||||
| 	r := p.next() | ||||
| 	if r == '\\' { | ||||
| 		p.next() | ||||
| 	} else if isTerminator(r) { | ||||
| 		p.backup() | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // isTerminator reports whether the input is at valid termination character to appear after an identifier. | ||||
| func isTerminator(r rune) bool { | ||||
| 	if isSpace(r) || isEndOfLine(r) { | ||||
| 		return true | ||||
| 	} | ||||
| 	switch r { | ||||
| 	case eof, '.', ',', '[', ']', '$', '@', '{', '}': | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // isSpace reports whether r is a space character. | ||||
| func isSpace(r rune) bool { | ||||
| 	return r == ' ' || r == '\t' | ||||
| } | ||||
|  | ||||
| // isEndOfLine reports whether r is an end-of-line character. | ||||
| func isEndOfLine(r rune) bool { | ||||
| 	return r == '\r' || r == '\n' | ||||
| } | ||||
|  | ||||
| // isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. | ||||
| func isAlphaNumeric(r rune) bool { | ||||
| 	return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| package(default_visibility = ["//visibility:public"]) | ||||
|  | ||||
| licenses(["notice"]) | ||||
|  | ||||
| load( | ||||
|     "@io_bazel_rules_go//go:def.bzl", | ||||
|     "go_library", | ||||
| ) | ||||
|  | ||||
| go_library( | ||||
|     name = "go_default_library", | ||||
|     srcs = ["plugins.go"], | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//plugin/pkg/client/auth/gcp:go_default_library", | ||||
|         "//plugin/pkg/client/auth/oidc:go_default_library", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "package-srcs", | ||||
|     srcs = glob(["**"]), | ||||
|     tags = ["automanaged"], | ||||
|     visibility = ["//visibility:private"], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "all-srcs", | ||||
|     srcs = [ | ||||
|         ":package-srcs", | ||||
|         "//plugin/pkg/client/auth/gcp:all-srcs", | ||||
|         "//plugin/pkg/client/auth/oidc:all-srcs", | ||||
|     ], | ||||
|     tags = ["automanaged"], | ||||
| ) | ||||
| @@ -1,45 +0,0 @@ | ||||
| package(default_visibility = ["//visibility:public"]) | ||||
|  | ||||
| licenses(["notice"]) | ||||
|  | ||||
| load( | ||||
|     "@io_bazel_rules_go//go:def.bzl", | ||||
|     "go_library", | ||||
|     "go_test", | ||||
| ) | ||||
|  | ||||
| go_library( | ||||
|     name = "go_default_library", | ||||
|     srcs = ["gcp.go"], | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/util/jsonpath:go_default_library", | ||||
|         "//vendor:github.com/golang/glog", | ||||
|         "//vendor:golang.org/x/net/context", | ||||
|         "//vendor:golang.org/x/oauth2", | ||||
|         "//vendor:golang.org/x/oauth2/google", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/yaml", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| go_test( | ||||
|     name = "go_default_test", | ||||
|     srcs = ["gcp_test.go"], | ||||
|     library = ":go_default_library", | ||||
|     tags = ["automanaged"], | ||||
|     deps = ["//vendor:golang.org/x/oauth2"], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "package-srcs", | ||||
|     srcs = glob(["**"]), | ||||
|     tags = ["automanaged"], | ||||
|     visibility = ["//visibility:private"], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "all-srcs", | ||||
|     srcs = [":package-srcs"], | ||||
|     tags = ["automanaged"], | ||||
| ) | ||||
| @@ -1,3 +0,0 @@ | ||||
| assignees: | ||||
|   - cjcullen | ||||
|   - jlowdermilk | ||||
| @@ -1,274 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| 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 gcp | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| 	"golang.org/x/net/context" | ||||
| 	"golang.org/x/oauth2" | ||||
| 	"golang.org/x/oauth2/google" | ||||
| 	"k8s.io/apimachinery/pkg/util/yaml" | ||||
| 	restclient "k8s.io/client-go/rest" | ||||
| 	"k8s.io/kubernetes/pkg/util/jsonpath" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	if err := restclient.RegisterAuthProviderPlugin("gcp", newGCPAuthProvider); err != nil { | ||||
| 		glog.Fatalf("Failed to register gcp auth plugin: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // gcpAuthProvider is an auth provider plugin that uses GCP credentials to provide | ||||
| // tokens for kubectl to authenticate itself to the apiserver. A sample json config | ||||
| // is provided below with all recognized options described. | ||||
| // | ||||
| // { | ||||
| //   'auth-provider': { | ||||
| //     # Required | ||||
| //     "name": "gcp", | ||||
| // | ||||
| //     'config': { | ||||
| //       # Caching options | ||||
| // | ||||
| //       # Raw string data representing cached access token. | ||||
| //       "access-token": "ya29.CjWdA4GiBPTt", | ||||
| //       # RFC3339Nano expiration timestamp for cached access token. | ||||
| //       "expiry": "2016-10-31 22:31:9.123", | ||||
| // | ||||
| //       # Command execution options | ||||
| //       # These options direct the plugin to execute a specified command and parse | ||||
| //       # token and expiry time from the output of the command. | ||||
| // | ||||
| //       # Command to execute for access token. String is split on whitespace | ||||
| //       # with first field treated as the executable, remaining fields as args. | ||||
| //       # Command output will be parsed as JSON. | ||||
| //       "cmd-path": "/usr/bin/gcloud config config-helper --output=json", | ||||
| // | ||||
| //       # JSONPath to the string field that represents the access token in | ||||
| //       # command output. If omitted, defaults to "{.access_token}". | ||||
| //       "token-key": "{.credential.access_token}", | ||||
| // | ||||
| //       # JSONPath to the string field that represents expiration timestamp | ||||
| //       # of the access token in the command output. If omitted, defaults to | ||||
| //       # "{.token_expiry}" | ||||
| //       "expiry-key": ""{.credential.token_expiry}", | ||||
| // | ||||
| //       # golang reference time in the format that the expiration timestamp uses. | ||||
| //       # If omitted, defaults to time.RFC3339Nano | ||||
| //       "time-fmt": "2006-01-02 15:04:05.999999999" | ||||
| //     } | ||||
| //   } | ||||
| // } | ||||
| // | ||||
| type gcpAuthProvider struct { | ||||
| 	tokenSource oauth2.TokenSource | ||||
| 	persister   restclient.AuthProviderConfigPersister | ||||
| } | ||||
|  | ||||
| func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { | ||||
| 	cmd, useCmd := gcpConfig["cmd-path"] | ||||
| 	var ts oauth2.TokenSource | ||||
| 	var err error | ||||
| 	if useCmd { | ||||
| 		ts, err = newCmdTokenSource(cmd, gcpConfig["token-key"], gcpConfig["expiry-key"], gcpConfig["time-fmt"]) | ||||
| 	} else { | ||||
| 		ts, err = google.DefaultTokenSource(context.Background(), "https://www.googleapis.com/auth/cloud-platform") | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	cts, err := newCachedTokenSource(gcpConfig["access-token"], gcpConfig["expiry"], persister, ts, gcpConfig) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &gcpAuthProvider{cts, persister}, nil | ||||
| } | ||||
|  | ||||
| func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { | ||||
| 	return &oauth2.Transport{ | ||||
| 		Source: g.tokenSource, | ||||
| 		Base:   rt, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (g *gcpAuthProvider) Login() error { return nil } | ||||
|  | ||||
| type cachedTokenSource struct { | ||||
| 	lk          sync.Mutex | ||||
| 	source      oauth2.TokenSource | ||||
| 	accessToken string | ||||
| 	expiry      time.Time | ||||
| 	persister   restclient.AuthProviderConfigPersister | ||||
| 	cache       map[string]string | ||||
| } | ||||
|  | ||||
| func newCachedTokenSource(accessToken, expiry string, persister restclient.AuthProviderConfigPersister, ts oauth2.TokenSource, cache map[string]string) (*cachedTokenSource, error) { | ||||
| 	var expiryTime time.Time | ||||
| 	if parsedTime, err := time.Parse(time.RFC3339Nano, expiry); err == nil { | ||||
| 		expiryTime = parsedTime | ||||
| 	} | ||||
| 	if cache == nil { | ||||
| 		cache = make(map[string]string) | ||||
| 	} | ||||
| 	return &cachedTokenSource{ | ||||
| 		source:      ts, | ||||
| 		accessToken: accessToken, | ||||
| 		expiry:      expiryTime, | ||||
| 		persister:   persister, | ||||
| 		cache:       cache, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (t *cachedTokenSource) Token() (*oauth2.Token, error) { | ||||
| 	tok := t.cachedToken() | ||||
| 	if tok.Valid() && !tok.Expiry.IsZero() { | ||||
| 		return tok, nil | ||||
| 	} | ||||
| 	tok, err := t.source.Token() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	cache := t.update(tok) | ||||
| 	if t.persister != nil { | ||||
| 		if err := t.persister.Persist(cache); err != nil { | ||||
| 			glog.V(4).Infof("Failed to persist token: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return tok, nil | ||||
| } | ||||
|  | ||||
| func (t *cachedTokenSource) cachedToken() *oauth2.Token { | ||||
| 	t.lk.Lock() | ||||
| 	defer t.lk.Unlock() | ||||
| 	return &oauth2.Token{ | ||||
| 		AccessToken: t.accessToken, | ||||
| 		TokenType:   "Bearer", | ||||
| 		Expiry:      t.expiry, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string { | ||||
| 	t.lk.Lock() | ||||
| 	defer t.lk.Unlock() | ||||
| 	t.accessToken = tok.AccessToken | ||||
| 	t.expiry = tok.Expiry | ||||
| 	ret := map[string]string{} | ||||
| 	for k, v := range t.cache { | ||||
| 		ret[k] = v | ||||
| 	} | ||||
| 	ret["access-token"] = t.accessToken | ||||
| 	ret["expiry"] = t.expiry.Format(time.RFC3339Nano) | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| type commandTokenSource struct { | ||||
| 	cmd       string | ||||
| 	args      []string | ||||
| 	tokenKey  string | ||||
| 	expiryKey string | ||||
| 	timeFmt   string | ||||
| } | ||||
|  | ||||
| func newCmdTokenSource(cmd, tokenKey, expiryKey, timeFmt string) (*commandTokenSource, error) { | ||||
| 	if len(timeFmt) == 0 { | ||||
| 		timeFmt = time.RFC3339Nano | ||||
| 	} | ||||
| 	if len(tokenKey) == 0 { | ||||
| 		tokenKey = "{.access_token}" | ||||
| 	} | ||||
| 	if len(expiryKey) == 0 { | ||||
| 		expiryKey = "{.token_expiry}" | ||||
| 	} | ||||
| 	fields := strings.Fields(cmd) | ||||
| 	if len(fields) == 0 { | ||||
| 		return nil, fmt.Errorf("missing access token cmd") | ||||
| 	} | ||||
| 	return &commandTokenSource{ | ||||
| 		cmd:       fields[0], | ||||
| 		args:      fields[1:], | ||||
| 		tokenKey:  tokenKey, | ||||
| 		expiryKey: expiryKey, | ||||
| 		timeFmt:   timeFmt, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (c *commandTokenSource) Token() (*oauth2.Token, error) { | ||||
| 	fullCmd := fmt.Sprintf("%s %s", c.cmd, strings.Join(c.args, " ")) | ||||
| 	cmd := exec.Command(c.cmd, c.args...) | ||||
| 	output, err := cmd.Output() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error executing access token command %q: %v", fullCmd, err) | ||||
| 	} | ||||
| 	token, err := c.parseTokenCmdOutput(output) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error parsing output for access token command %q: %v", fullCmd, err) | ||||
| 	} | ||||
| 	return token, nil | ||||
| } | ||||
|  | ||||
| func (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oauth2.Token, error) { | ||||
| 	output, err := yaml.ToJSON(output) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var data interface{} | ||||
| 	if err := json.Unmarshal(output, &data); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	accessToken, err := parseJSONPath(data, "token-key", c.tokenKey) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error parsing token-key %q: %v", c.tokenKey, err) | ||||
| 	} | ||||
| 	expiryStr, err := parseJSONPath(data, "expiry-key", c.expiryKey) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error parsing expiry-key %q: %v", c.expiryKey, err) | ||||
| 	} | ||||
| 	var expiry time.Time | ||||
| 	if t, err := time.Parse(c.timeFmt, expiryStr); err != nil { | ||||
| 		glog.V(4).Infof("Failed to parse token expiry from %s (fmt=%s): %v", expiryStr, c.timeFmt, err) | ||||
| 	} else { | ||||
| 		expiry = t | ||||
| 	} | ||||
|  | ||||
| 	return &oauth2.Token{ | ||||
| 		AccessToken: accessToken, | ||||
| 		TokenType:   "Bearer", | ||||
| 		Expiry:      expiry, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func parseJSONPath(input interface{}, name, template string) (string, error) { | ||||
| 	j := jsonpath.New(name) | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	if err := j.Parse(template); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	if err := j.Execute(buf, input); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return buf.String(), nil | ||||
| } | ||||
| @@ -1,211 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| 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 gcp | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
|  | ||||
| func TestCmdTokenSource(t *testing.T) { | ||||
| 	fakeExpiry := time.Date(2016, 10, 31, 22, 31, 9, 123000000, time.UTC) | ||||
| 	customFmt := "2006-01-02 15:04:05.999999999" | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name                              string | ||||
| 		output                            []byte | ||||
| 		cmd, tokenKey, expiryKey, timeFmt string | ||||
| 		tok                               *oauth2.Token | ||||
| 		expectErr                         error | ||||
| 	}{ | ||||
| 		{ | ||||
| 			"defaults", | ||||
| 			[]byte(`{ | ||||
|   "access_token": "faketoken", | ||||
|   "token_expiry": "2016-10-31T22:31:09.123000000Z" | ||||
| }`), | ||||
| 			"/fake/cmd/path", "", "", "", | ||||
| 			&oauth2.Token{ | ||||
| 				AccessToken: "faketoken", | ||||
| 				TokenType:   "Bearer", | ||||
| 				Expiry:      fakeExpiry, | ||||
| 			}, | ||||
| 			nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"custom keys", | ||||
| 			[]byte(`{ | ||||
|   "token": "faketoken", | ||||
|   "token_expiry": { | ||||
|     "datetime": "2016-10-31 22:31:09.123" | ||||
|   } | ||||
| }`), | ||||
| 			"/fake/cmd/path", "{.token}", "{.token_expiry.datetime}", customFmt, | ||||
| 			&oauth2.Token{ | ||||
| 				AccessToken: "faketoken", | ||||
| 				TokenType:   "Bearer", | ||||
| 				Expiry:      fakeExpiry, | ||||
| 			}, | ||||
| 			nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"missing cmd", | ||||
| 			nil, | ||||
| 			"", "", "", "", | ||||
| 			nil, | ||||
| 			fmt.Errorf("missing access token cmd"), | ||||
| 		}, | ||||
| 		{ | ||||
| 			"missing token-key", | ||||
| 			[]byte(`{ | ||||
|   "broken": "faketoken", | ||||
|   "token_expiry": { | ||||
|     "datetime": "2016-10-31 22:31:09.123000000Z" | ||||
|   } | ||||
| }`), | ||||
| 			"/fake/cmd/path", "{.token}", "", "", | ||||
| 			nil, | ||||
| 			fmt.Errorf("error parsing token-key %q", "{.token}"), | ||||
| 		}, | ||||
| 		{ | ||||
| 			"missing expiry-key", | ||||
| 			[]byte(`{ | ||||
|   "access_token": "faketoken", | ||||
|   "expires": "2016-10-31T22:31:09.123000000Z" | ||||
| }`), | ||||
| 			"/fake/cmd/path", "", "{.expiry}", "", | ||||
| 			nil, | ||||
| 			fmt.Errorf("error parsing expiry-key %q", "{.expiry}"), | ||||
| 		}, | ||||
| 		{ | ||||
| 			"invalid expiry timestamp", | ||||
| 			[]byte(`{ | ||||
|   "access_token": "faketoken", | ||||
|   "token_expiry": "sometime soon, idk" | ||||
| }`), | ||||
| 			"/fake/cmd/path", "", "", "", | ||||
| 			&oauth2.Token{ | ||||
| 				AccessToken: "faketoken", | ||||
| 				TokenType:   "Bearer", | ||||
| 				Expiry:      time.Time{}, | ||||
| 			}, | ||||
| 			nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			"bad JSON", | ||||
| 			[]byte(`{ | ||||
|   "access_token": "faketoken", | ||||
|   "token_expiry": "sometime soon, idk" | ||||
|   ------ | ||||
| `), | ||||
| 			"/fake/cmd", "", "", "", | ||||
| 			nil, | ||||
| 			fmt.Errorf("invalid character '-' after object key:value pair"), | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		ts, err := newCmdTokenSource(tc.cmd, tc.tokenKey, tc.expiryKey, tc.timeFmt) | ||||
| 		if err != nil { | ||||
| 			if !strings.Contains(err.Error(), tc.expectErr.Error()) { | ||||
| 				t.Errorf("%s newCmdTokenSource error: %v, want %v", tc.name, err, tc.expectErr) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		tok, err := ts.parseTokenCmdOutput(tc.output) | ||||
|  | ||||
| 		if err != tc.expectErr && !strings.Contains(err.Error(), tc.expectErr.Error()) { | ||||
| 			t.Errorf("%s parseCmdTokenSource error: %v, want %v", tc.name, err, tc.expectErr) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(tok, tc.tok) { | ||||
| 			t.Errorf("%s got token %v, want %v", tc.name, tok, tc.tok) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type fakePersister struct { | ||||
| 	lk    sync.Mutex | ||||
| 	cache map[string]string | ||||
| } | ||||
|  | ||||
| func (f *fakePersister) Persist(cache map[string]string) error { | ||||
| 	f.lk.Lock() | ||||
| 	defer f.lk.Unlock() | ||||
| 	f.cache = map[string]string{} | ||||
| 	for k, v := range cache { | ||||
| 		f.cache[k] = v | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *fakePersister) read() map[string]string { | ||||
| 	ret := map[string]string{} | ||||
| 	f.lk.Lock() | ||||
| 	for k, v := range f.cache { | ||||
| 		ret[k] = v | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| type fakeTokenSource struct { | ||||
| 	token *oauth2.Token | ||||
| 	err   error | ||||
| } | ||||
|  | ||||
| func (f *fakeTokenSource) Token() (*oauth2.Token, error) { | ||||
| 	return f.token, f.err | ||||
| } | ||||
|  | ||||
| func TestCachedTokenSource(t *testing.T) { | ||||
| 	tok := &oauth2.Token{AccessToken: "fakeaccesstoken"} | ||||
| 	persister := &fakePersister{} | ||||
| 	source := &fakeTokenSource{ | ||||
| 		token: tok, | ||||
| 		err:   nil, | ||||
| 	} | ||||
| 	cache := map[string]string{ | ||||
| 		"foo": "bar", | ||||
| 		"baz": "bazinga", | ||||
| 	} | ||||
| 	ts, err := newCachedTokenSource("fakeaccesstoken", "", persister, source, cache) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	var wg sync.WaitGroup | ||||
| 	wg.Add(10) | ||||
| 	for i := 0; i < 10; i++ { | ||||
| 		go func() { | ||||
| 			_, err := ts.Token() | ||||
| 			if err != nil { | ||||
| 				t.Errorf("unexpected error: %s", err) | ||||
| 			} | ||||
| 			wg.Done() | ||||
| 		}() | ||||
| 	} | ||||
| 	wg.Wait() | ||||
| 	cache["access-token"] = "fakeaccesstoken" | ||||
| 	cache["expiry"] = tok.Expiry.Format(time.RFC3339Nano) | ||||
| 	if got := persister.read(); !reflect.DeepEqual(got, cache) { | ||||
| 		t.Errorf("got cache %v, want %v", got, cache) | ||||
| 	} | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
| package(default_visibility = ["//visibility:public"]) | ||||
|  | ||||
| licenses(["notice"]) | ||||
|  | ||||
| load( | ||||
|     "@io_bazel_rules_go//go:def.bzl", | ||||
|     "go_library", | ||||
|     "go_test", | ||||
| ) | ||||
|  | ||||
| go_library( | ||||
|     name = "go_default_library", | ||||
|     srcs = ["oidc.go"], | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//vendor:github.com/coreos/go-oidc/jose", | ||||
|         "//vendor:github.com/coreos/go-oidc/oauth2", | ||||
|         "//vendor:github.com/coreos/go-oidc/oidc", | ||||
|         "//vendor:github.com/golang/glog", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| go_test( | ||||
|     name = "go_default_test", | ||||
|     srcs = ["oidc_test.go"], | ||||
|     library = ":go_default_library", | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//plugin/pkg/auth/authenticator/token/oidc/testing:go_default_library", | ||||
|         "//vendor:github.com/coreos/go-oidc/jose", | ||||
|         "//vendor:github.com/coreos/go-oidc/key", | ||||
|         "//vendor:github.com/coreos/go-oidc/oauth2", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "package-srcs", | ||||
|     srcs = glob(["**"]), | ||||
|     tags = ["automanaged"], | ||||
|     visibility = ["//visibility:private"], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "all-srcs", | ||||
|     srcs = [":package-srcs"], | ||||
|     tags = ["automanaged"], | ||||
| ) | ||||
| @@ -1,2 +0,0 @@ | ||||
| assignees: | ||||
|   - ericchiang | ||||
| @@ -1,333 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| 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 oidc | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coreos/go-oidc/jose" | ||||
| 	"github.com/coreos/go-oidc/oauth2" | ||||
| 	"github.com/coreos/go-oidc/oidc" | ||||
| 	"github.com/golang/glog" | ||||
|  | ||||
| 	restclient "k8s.io/client-go/rest" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cfgIssuerUrl                = "idp-issuer-url" | ||||
| 	cfgClientID                 = "client-id" | ||||
| 	cfgClientSecret             = "client-secret" | ||||
| 	cfgCertificateAuthority     = "idp-certificate-authority" | ||||
| 	cfgCertificateAuthorityData = "idp-certificate-authority-data" | ||||
| 	cfgExtraScopes              = "extra-scopes" | ||||
| 	cfgIDToken                  = "id-token" | ||||
| 	cfgRefreshToken             = "refresh-token" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	if err := restclient.RegisterAuthProviderPlugin("oidc", newOIDCAuthProvider); err != nil { | ||||
| 		glog.Fatalf("Failed to register oidc auth plugin: %v", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // expiryDelta determines how earlier a token should be considered | ||||
| // expired than its actual expiration time. It is used to avoid late | ||||
| // expirations due to client-server time mismatches. | ||||
| // | ||||
| // NOTE(ericchiang): this is take from golang.org/x/oauth2 | ||||
| const expiryDelta = 10 * time.Second | ||||
|  | ||||
| var cache = newClientCache() | ||||
|  | ||||
| // Like TLS transports, keep a cache of OIDC clients indexed by issuer URL. | ||||
| type clientCache struct { | ||||
| 	mu    sync.RWMutex | ||||
| 	cache map[cacheKey]*oidcAuthProvider | ||||
| } | ||||
|  | ||||
| func newClientCache() *clientCache { | ||||
| 	return &clientCache{cache: make(map[cacheKey]*oidcAuthProvider)} | ||||
| } | ||||
|  | ||||
| type cacheKey struct { | ||||
| 	// Canonical issuer URL string of the provider. | ||||
| 	issuerURL string | ||||
|  | ||||
| 	clientID     string | ||||
| 	clientSecret string | ||||
|  | ||||
| 	// Don't use CA as cache key because we only add a cache entry if we can connect | ||||
| 	// to the issuer in the first place. A valid CA is a prerequisite. | ||||
| } | ||||
|  | ||||
| func (c *clientCache) getClient(issuer, clientID, clientSecret string) (*oidcAuthProvider, bool) { | ||||
| 	c.mu.RLock() | ||||
| 	defer c.mu.RUnlock() | ||||
| 	client, ok := c.cache[cacheKey{issuer, clientID, clientSecret}] | ||||
| 	return client, ok | ||||
| } | ||||
|  | ||||
| // setClient attempts to put the client in the cache but may return any clients | ||||
| // with the same keys set before. This is so there's only ever one client for a provider. | ||||
| func (c *clientCache) setClient(issuer, clientID, clientSecret string, client *oidcAuthProvider) *oidcAuthProvider { | ||||
| 	c.mu.Lock() | ||||
| 	defer c.mu.Unlock() | ||||
| 	key := cacheKey{issuer, clientID, clientSecret} | ||||
|  | ||||
| 	// If another client has already initialized a client for the given provider we want | ||||
| 	// to use that client instead of the one we're trying to set. This is so all transports | ||||
| 	// share a client and can coordinate around the same mutex when refreshing and writing | ||||
| 	// to the kubeconfig. | ||||
| 	if oldClient, ok := c.cache[key]; ok { | ||||
| 		return oldClient | ||||
| 	} | ||||
|  | ||||
| 	c.cache[key] = client | ||||
| 	return client | ||||
| } | ||||
|  | ||||
| func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { | ||||
| 	issuer := cfg[cfgIssuerUrl] | ||||
| 	if issuer == "" { | ||||
| 		return nil, fmt.Errorf("Must provide %s", cfgIssuerUrl) | ||||
| 	} | ||||
|  | ||||
| 	clientID := cfg[cfgClientID] | ||||
| 	if clientID == "" { | ||||
| 		return nil, fmt.Errorf("Must provide %s", cfgClientID) | ||||
| 	} | ||||
|  | ||||
| 	clientSecret := cfg[cfgClientSecret] | ||||
| 	if clientSecret == "" { | ||||
| 		return nil, fmt.Errorf("Must provide %s", cfgClientSecret) | ||||
| 	} | ||||
|  | ||||
| 	// Check cache for existing provider. | ||||
| 	if provider, ok := cache.getClient(issuer, clientID, clientSecret); ok { | ||||
| 		return provider, nil | ||||
| 	} | ||||
|  | ||||
| 	var certAuthData []byte | ||||
| 	var err error | ||||
| 	if cfg[cfgCertificateAuthorityData] != "" { | ||||
| 		certAuthData, err = base64.StdEncoding.DecodeString(cfg[cfgCertificateAuthorityData]) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	clientConfig := restclient.Config{ | ||||
| 		TLSClientConfig: restclient.TLSClientConfig{ | ||||
| 			CAFile: cfg[cfgCertificateAuthority], | ||||
| 			CAData: certAuthData, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	trans, err := restclient.TransportFor(&clientConfig) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	hc := &http.Client{Transport: trans} | ||||
|  | ||||
| 	providerCfg, err := oidc.FetchProviderConfig(hc, issuer) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error fetching provider config: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	scopes := strings.Split(cfg[cfgExtraScopes], ",") | ||||
| 	oidcCfg := oidc.ClientConfig{ | ||||
| 		HTTPClient: hc, | ||||
| 		Credentials: oidc.ClientCredentials{ | ||||
| 			ID:     clientID, | ||||
| 			Secret: clientSecret, | ||||
| 		}, | ||||
| 		ProviderConfig: providerCfg, | ||||
| 		Scope:          append(scopes, oidc.DefaultScope...), | ||||
| 	} | ||||
| 	client, err := oidc.NewClient(oidcCfg) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error creating OIDC Client: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	provider := &oidcAuthProvider{ | ||||
| 		client:    &oidcClient{client}, | ||||
| 		cfg:       cfg, | ||||
| 		persister: persister, | ||||
| 		now:       time.Now, | ||||
| 	} | ||||
|  | ||||
| 	return cache.setClient(issuer, clientID, clientSecret, provider), nil | ||||
| } | ||||
|  | ||||
| type oidcAuthProvider struct { | ||||
| 	// Interface rather than a raw *oidc.Client for testing. | ||||
| 	client OIDCClient | ||||
|  | ||||
| 	// Stubbed out for testing. | ||||
| 	now func() time.Time | ||||
|  | ||||
| 	// Mutex guards persisting to the kubeconfig file and allows synchronized | ||||
| 	// updates to the in-memory config. It also ensures concurrent calls to | ||||
| 	// the RoundTripper only trigger a single refresh request. | ||||
| 	mu        sync.Mutex | ||||
| 	cfg       map[string]string | ||||
| 	persister restclient.AuthProviderConfigPersister | ||||
| } | ||||
|  | ||||
| func (p *oidcAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { | ||||
| 	return &roundTripper{ | ||||
| 		wrapped:  rt, | ||||
| 		provider: p, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *oidcAuthProvider) Login() error { | ||||
| 	return errors.New("not yet implemented") | ||||
| } | ||||
|  | ||||
| type OIDCClient interface { | ||||
| 	refreshToken(rt string) (oauth2.TokenResponse, error) | ||||
| 	verifyJWT(jwt *jose.JWT) error | ||||
| } | ||||
|  | ||||
| type roundTripper struct { | ||||
| 	provider *oidcAuthProvider | ||||
| 	wrapped  http.RoundTripper | ||||
| } | ||||
|  | ||||
| func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | ||||
| 	token, err := r.provider.idToken() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// shallow copy of the struct | ||||
| 	r2 := new(http.Request) | ||||
| 	*r2 = *req | ||||
| 	// deep copy of the Header so we don't modify the original | ||||
| 	// request's Header (as per RoundTripper contract). | ||||
| 	r2.Header = make(http.Header) | ||||
| 	for k, s := range req.Header { | ||||
| 		r2.Header[k] = s | ||||
| 	} | ||||
| 	r2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) | ||||
|  | ||||
| 	return r.wrapped.RoundTrip(r2) | ||||
| } | ||||
|  | ||||
| func (p *oidcAuthProvider) idToken() (string, error) { | ||||
| 	p.mu.Lock() | ||||
| 	defer p.mu.Unlock() | ||||
|  | ||||
| 	if idToken, ok := p.cfg[cfgIDToken]; ok && len(idToken) > 0 { | ||||
| 		valid, err := verifyJWTExpiry(p.now(), idToken) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if valid { | ||||
| 			// If the cached id token is still valid use it. | ||||
| 			return idToken, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Try to request a new token using the refresh token. | ||||
| 	rt, ok := p.cfg[cfgRefreshToken] | ||||
| 	if !ok || len(rt) == 0 { | ||||
| 		return "", errors.New("No valid id-token, and cannot refresh without refresh-token") | ||||
| 	} | ||||
|  | ||||
| 	tokens, err := p.client.refreshToken(rt) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("could not refresh token: %v", err) | ||||
| 	} | ||||
| 	jwt, err := jose.ParseJWT(tokens.IDToken) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if err := p.client.verifyJWT(&jwt); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	// Create a new config to persist. | ||||
| 	newCfg := make(map[string]string) | ||||
| 	for key, val := range p.cfg { | ||||
| 		newCfg[key] = val | ||||
| 	} | ||||
|  | ||||
| 	if tokens.RefreshToken != "" && tokens.RefreshToken != rt { | ||||
| 		newCfg[cfgRefreshToken] = tokens.RefreshToken | ||||
| 	} | ||||
|  | ||||
| 	newCfg[cfgIDToken] = tokens.IDToken | ||||
| 	if err = p.persister.Persist(newCfg); err != nil { | ||||
| 		return "", fmt.Errorf("could not perist new tokens: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// Update the in memory config to reflect the on disk one. | ||||
| 	p.cfg = newCfg | ||||
|  | ||||
| 	return tokens.IDToken, nil | ||||
| } | ||||
|  | ||||
| // oidcClient is the real implementation of the OIDCClient interface, which is | ||||
| // used for testing. | ||||
| type oidcClient struct { | ||||
| 	client *oidc.Client | ||||
| } | ||||
|  | ||||
| func (o *oidcClient) refreshToken(rt string) (oauth2.TokenResponse, error) { | ||||
| 	oac, err := o.client.OAuthClient() | ||||
| 	if err != nil { | ||||
| 		return oauth2.TokenResponse{}, err | ||||
| 	} | ||||
|  | ||||
| 	return oac.RequestToken(oauth2.GrantTypeRefreshToken, rt) | ||||
| } | ||||
|  | ||||
| func (o *oidcClient) verifyJWT(jwt *jose.JWT) error { | ||||
| 	return o.client.VerifyJWT(*jwt) | ||||
| } | ||||
|  | ||||
| func verifyJWTExpiry(now time.Time, s string) (valid bool, err error) { | ||||
| 	jwt, err := jose.ParseJWT(s) | ||||
| 	if err != nil { | ||||
| 		return false, fmt.Errorf("invalid %q", cfgIDToken) | ||||
| 	} | ||||
| 	claims, err := jwt.Claims() | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
| 	exp, ok, err := claims.TimeClaim("exp") | ||||
| 	switch { | ||||
| 	case err != nil: | ||||
| 		return false, fmt.Errorf("failed to parse 'exp' claim: %v", err) | ||||
| 	case !ok: | ||||
| 		return false, errors.New("missing required 'exp' claim") | ||||
| 	case exp.After(now.Add(expiryDelta)): | ||||
| 		return true, nil | ||||
| 	} | ||||
|  | ||||
| 	return false, nil | ||||
| } | ||||
| @@ -1,384 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| 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 oidc | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"errors" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/coreos/go-oidc/jose" | ||||
| 	"github.com/coreos/go-oidc/key" | ||||
| 	"github.com/coreos/go-oidc/oauth2" | ||||
|  | ||||
| 	oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing" | ||||
| ) | ||||
|  | ||||
| func clearCache() { | ||||
| 	cache = newClientCache() | ||||
| } | ||||
|  | ||||
| type persister struct{} | ||||
|  | ||||
| // we don't need to actually persist anything because there's no way for us to | ||||
| // read from a persister. | ||||
| func (p *persister) Persist(map[string]string) error { return nil } | ||||
|  | ||||
| type noRefreshOIDCClient struct{} | ||||
|  | ||||
| func (c *noRefreshOIDCClient) refreshToken(rt string) (oauth2.TokenResponse, error) { | ||||
| 	return oauth2.TokenResponse{}, errors.New("alwaysErrOIDCClient: cannot refresh token") | ||||
| } | ||||
|  | ||||
| func (c *noRefreshOIDCClient) verifyJWT(jwt *jose.JWT) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type mockOIDCClient struct { | ||||
| 	tokenResponse oauth2.TokenResponse | ||||
| } | ||||
|  | ||||
| func (c *mockOIDCClient) refreshToken(rt string) (oauth2.TokenResponse, error) { | ||||
| 	return c.tokenResponse, nil | ||||
| } | ||||
|  | ||||
| func (c *mockOIDCClient) verifyJWT(jwt *jose.JWT) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func TestNewOIDCAuthProvider(t *testing.T) { | ||||
| 	tempDir, err := ioutil.TempDir(os.TempDir(), "oidc_test") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Cannot make temp dir %v", err) | ||||
| 	} | ||||
| 	cert := path.Join(tempDir, "oidc-cert") | ||||
| 	key := path.Join(tempDir, "oidc-key") | ||||
| 	defer os.RemoveAll(tempDir) | ||||
|  | ||||
| 	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key) | ||||
| 	op := oidctesting.NewOIDCProvider(t, "") | ||||
| 	srv, err := op.ServeTLSWithKeyPair(cert, key) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Cannot start server %v", err) | ||||
| 	} | ||||
| 	defer srv.Close() | ||||
|  | ||||
| 	certData, err := ioutil.ReadFile(cert) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Could not read cert bytes %v", err) | ||||
| 	} | ||||
|  | ||||
| 	makeToken := func(exp time.Time) *jose.JWT { | ||||
| 		jwt, err := jose.NewSignedJWT(jose.Claims(map[string]interface{}{ | ||||
| 			"exp": exp.UTC().Unix(), | ||||
| 		}), op.PrivKey.Signer()) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Could not create signed JWT %v", err) | ||||
| 		} | ||||
| 		return jwt | ||||
| 	} | ||||
|  | ||||
| 	t0 := time.Now() | ||||
|  | ||||
| 	goodToken := makeToken(t0.Add(time.Hour)).Encode() | ||||
| 	expiredToken := makeToken(t0.Add(-time.Hour)).Encode() | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name string | ||||
|  | ||||
| 		cfg         map[string]string | ||||
| 		wantInitErr bool | ||||
|  | ||||
| 		client       OIDCClient | ||||
| 		wantCfg      map[string]string | ||||
| 		wantTokenErr bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			// A Valid configuration | ||||
| 			name: "no id token and no refresh token", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 			}, | ||||
| 			wantTokenErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "valid config with an initial token", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 				cfgIDToken:              goodToken, | ||||
| 			}, | ||||
| 			client: new(noRefreshOIDCClient), | ||||
| 			wantCfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 				cfgIDToken:              goodToken, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "invalid ID token with a refresh token", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 				cfgRefreshToken:         "foo", | ||||
| 				cfgIDToken:              expiredToken, | ||||
| 			}, | ||||
| 			client: &mockOIDCClient{ | ||||
| 				tokenResponse: oauth2.TokenResponse{ | ||||
| 					IDToken: goodToken, | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 				cfgRefreshToken:         "foo", | ||||
| 				cfgIDToken:              goodToken, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "invalid ID token with a refresh token, server returns new refresh token", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 				cfgRefreshToken:         "foo", | ||||
| 				cfgIDToken:              expiredToken, | ||||
| 			}, | ||||
| 			client: &mockOIDCClient{ | ||||
| 				tokenResponse: oauth2.TokenResponse{ | ||||
| 					IDToken:      goodToken, | ||||
| 					RefreshToken: "bar", | ||||
| 				}, | ||||
| 			}, | ||||
| 			wantCfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 				cfgRefreshToken:         "bar", | ||||
| 				cfgIDToken:              goodToken, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "expired token and no refresh otken", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 				cfgIDToken:              expiredToken, | ||||
| 			}, | ||||
| 			wantTokenErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "valid base64d ca", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:                srv.URL, | ||||
| 				cfgCertificateAuthorityData: base64.StdEncoding.EncodeToString(certData), | ||||
| 				cfgClientID:                 "client-id", | ||||
| 				cfgClientSecret:             "client-secret", | ||||
| 			}, | ||||
| 			client:       new(noRefreshOIDCClient), | ||||
| 			wantTokenErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "missing client ID", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientSecret:         "client-secret", | ||||
| 			}, | ||||
| 			wantInitErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "missing client secret", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:            srv.URL, | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 			}, | ||||
| 			wantInitErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "missing issuer URL", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgCertificateAuthority: cert, | ||||
| 				cfgClientID:             "client-id", | ||||
| 				cfgClientSecret:         "secret", | ||||
| 			}, | ||||
| 			wantInitErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "missing TLS config", | ||||
| 			cfg: map[string]string{ | ||||
| 				cfgIssuerUrl:    srv.URL, | ||||
| 				cfgClientID:     "client-id", | ||||
| 				cfgClientSecret: "secret", | ||||
| 			}, | ||||
| 			wantInitErr: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tt := range tests { | ||||
| 		clearCache() | ||||
|  | ||||
| 		p, err := newOIDCAuthProvider("cluster.example.com", tt.cfg, new(persister)) | ||||
| 		if tt.wantInitErr { | ||||
| 			if err == nil { | ||||
| 				t.Errorf("%s: want non-nil err", tt.name) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%s: unexpected error on newOIDCAuthProvider: %v", tt.name, err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		provider := p.(*oidcAuthProvider) | ||||
| 		provider.client = tt.client | ||||
| 		provider.now = func() time.Time { return t0 } | ||||
|  | ||||
| 		if _, err := provider.idToken(); err != nil { | ||||
| 			if !tt.wantTokenErr { | ||||
| 				t.Errorf("%s: failed to get id token: %v", tt.name, err) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		if tt.wantTokenErr { | ||||
| 			t.Errorf("%s: expected to not get id token: %v", tt.name, err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if !reflect.DeepEqual(tt.wantCfg, provider.cfg) { | ||||
| 			t.Errorf("%s: expected config %#v got %#v", tt.name, tt.wantCfg, provider.cfg) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestVerifyJWTExpiry(t *testing.T) { | ||||
| 	privKey, err := key.GeneratePrivateKey() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("can't generate private key: %v", err) | ||||
| 	} | ||||
| 	makeToken := func(s string, exp time.Time, count int) *jose.JWT { | ||||
| 		jwt, err := jose.NewSignedJWT(jose.Claims(map[string]interface{}{ | ||||
| 			"test":  s, | ||||
| 			"exp":   exp.UTC().Unix(), | ||||
| 			"count": count, | ||||
| 		}), privKey.Signer()) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Could not create signed JWT %v", err) | ||||
| 		} | ||||
| 		return jwt | ||||
| 	} | ||||
|  | ||||
| 	t0 := time.Now() | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name        string | ||||
| 		jwt         *jose.JWT | ||||
| 		now         time.Time | ||||
| 		wantErr     bool | ||||
| 		wantExpired bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "valid jwt", | ||||
| 			jwt:  makeToken("foo", t0.Add(time.Hour), 1), | ||||
| 			now:  t0, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "invalid jwt", | ||||
| 			jwt:     &jose.JWT{}, | ||||
| 			now:     t0, | ||||
| 			wantErr: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:        "expired jwt", | ||||
| 			jwt:         makeToken("foo", t0.Add(-time.Hour), 1), | ||||
| 			now:         t0, | ||||
| 			wantExpired: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:        "jwt expires soon enough to be marked expired", | ||||
| 			jwt:         makeToken("foo", t0, 1), | ||||
| 			now:         t0, | ||||
| 			wantExpired: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		func() { | ||||
| 			valid, err := verifyJWTExpiry(tc.now, tc.jwt.Encode()) | ||||
| 			if err != nil { | ||||
| 				if !tc.wantErr { | ||||
| 					t.Errorf("%s: %v", tc.name, err) | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
| 			if tc.wantErr { | ||||
| 				t.Errorf("%s: expected error", tc.name) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			if valid && tc.wantExpired { | ||||
| 				t.Errorf("%s: expected token to be expired", tc.name) | ||||
| 			} | ||||
| 			if !valid && !tc.wantExpired { | ||||
| 				t.Errorf("%s: expected token to be valid", tc.name) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestClientCache(t *testing.T) { | ||||
| 	cache := newClientCache() | ||||
|  | ||||
| 	if _, ok := cache.getClient("issuer1", "id1", "secret1"); ok { | ||||
| 		t.Fatalf("got client before putting one in the cache") | ||||
| 	} | ||||
|  | ||||
| 	cli1 := new(oidcAuthProvider) | ||||
| 	cli2 := new(oidcAuthProvider) | ||||
|  | ||||
| 	gotcli := cache.setClient("issuer1", "id1", "secret1", cli1) | ||||
| 	if cli1 != gotcli { | ||||
| 		t.Fatalf("set first client and got a different one") | ||||
| 	} | ||||
|  | ||||
| 	gotcli = cache.setClient("issuer1", "id1", "secret1", cli2) | ||||
| 	if cli1 != gotcli { | ||||
| 		t.Fatalf("set a second client and didn't get the first") | ||||
| 	} | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| /* | ||||
| Copyright 2016 The Kubernetes Authors. | ||||
|  | ||||
| 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 auth | ||||
|  | ||||
| import ( | ||||
| 	// Initialize all known client auth plugins. | ||||
| 	_ "k8s.io/kubernetes/plugin/pkg/client/auth/gcp" | ||||
| 	_ "k8s.io/kubernetes/plugin/pkg/client/auth/oidc" | ||||
| ) | ||||
| @@ -75,11 +75,14 @@ save "tools/clientcmd/api" | ||||
| save "rest" | ||||
| # remove the rest/fake until we're authoritative for it (need to update for registry) | ||||
| rm -rf ${CLIENT_REPO_TEMP}/rest/fake | ||||
| save "pkg/third_party" | ||||
| save "pkg/util/cert" | ||||
| save "pkg/util/clock" | ||||
| save "pkg/util/flowcontrol" | ||||
| save "pkg/util/integer" | ||||
| save "pkg/util/jsonpath" | ||||
| save "pkg/util/testing" | ||||
| save "plugin" | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -105,7 +108,6 @@ mkcp "/pkg/client/unversioned/auth" "/pkg/client/unversioned" | ||||
| mkcp "/pkg/client/unversioned/clientcmd" "/pkg/client/unversioned" | ||||
| mkcp "/pkg/client/unversioned/portforward" "/pkg/client/unversioned" | ||||
|  | ||||
| mkcp "/plugin/pkg/client/auth" "/plugin/pkg/client" | ||||
| mkcp "/pkg/util/workqueue" "pkg/util" | ||||
| # remove this folder because it imports prometheus | ||||
| rm -rf "${CLIENT_REPO_TEMP}/pkg/util/workqueue/prometheus" | ||||
| @@ -208,7 +210,6 @@ if [ "$(find "${CLIENT_REPO_TEMP}"/pkg/client -type f -name "*.go")" ]; then | ||||
| else | ||||
|     rm -r "${CLIENT_REPO_TEMP}"/pkg/client | ||||
| fi | ||||
| mvfolder third_party pkg/third_party | ||||
| mvfolder federation pkg/federation | ||||
|  | ||||
| echo "running gofmt" | ||||
|   | ||||
| @@ -17,4 +17,4 @@ limitations under the License. | ||||
| // package jsonpath is a template engine using jsonpath syntax, | ||||
| // which can be seen at http://goessner.net/articles/JsonPath/. | ||||
| // In addition, it has {range} {end} function to iterate list and slice. | ||||
| package jsonpath | ||||
| package jsonpath // import "k8s.io/client-go/pkg/util/jsonpath" | ||||
|   | ||||
| @@ -30,7 +30,7 @@ import ( | ||||
| 	"github.com/coreos/go-oidc/key" | ||||
| 	"github.com/coreos/go-oidc/oauth2" | ||||
|  | ||||
| 	oidctesting "k8s.io/client-go/plugin/pkg/auth/authenticator/token/oidc/testing" | ||||
| 	oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing" | ||||
| ) | ||||
|  | ||||
| func clearCache() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 deads2k
					deads2k