move client auth plugins

pull/6/head
deads2k 2017-01-19 16:35:04 -05:00
parent af2c52085d
commit 2f51cc4ce4
19 changed files with 5 additions and 2552 deletions

View File

@ -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"

View File

@ -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
}

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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"],
)

View File

@ -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"],
)

View File

@ -1,3 +0,0 @@
assignees:
- cjcullen
- jlowdermilk

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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"],
)

View File

@ -1,2 +0,0 @@
assignees:
- ericchiang

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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"
)

View File

@ -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"

View File

@ -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"

View File

@ -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() {