<commit_before>// 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 spinner is a simple package to add a spinner / progress indicator to any terminal application.
package spinner

import (
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
	"runtime"
	"strconv"
	"sync"
	"time"
	"unicode/utf8"

	"github.com/fatih/color"
)

// errInvalidColor is returned when attempting to set an invalid color
var errInvalidColor = errors.New("invalid color")

// validColors holds an array of the only colors allowed
var validColors = map[string]bool{
	// default colors for backwards compatibility
	"black":   true,
	"red":     true,
	"green":   true,
	"yellow":  true,
	"blue":    true,
	"magenta": true,
	"cyan":    true,
	"white":   true,

	// attributes
	"reset":        true,
	"bold":         true,
	"faint":        true,
	"italic":       true,
	"underline":    true,
	"blinkslow":    true,
	"blinkrapid":   true,
	"reversevideo": true,
	"concealed":    true,
	"crossedout":   true,

	// foreground text
	"fgBlack":   true,
	"fgRed":     true,
	"fgGreen":   true,
	"fgYellow":  true,
	"fgBlue":    true,
	"fgMagenta": true,
	"fgCyan":    true,
	"fgWhite":   true,

	// foreground Hi-Intensity text
	"fgHiBlack":   true,
	"fgHiRed":     true,
	"fgHiGreen":   true,
	"fgHiYellow":  true,
	"fgHiBlue":    true,
	"fgHiMagenta": true,
	"fgHiCyan":    true,
	"fgHiWhite":   true,

	// background text
	"bgBlack":   true,
	"bgRed":     true,
	"bgGreen":   true,
	"bgYellow":  true,
	"bgBlue":    true,
	"bgMagenta": true,
	"bgCyan":    true,
	"bgWhite":   true,

	// background Hi-Intensity text
	"bgHiBlack":   true,
	"bgHiRed":     true,
	"bgHiGreen":   true,
	"bgHiYellow":  true,
	"bgHiBlue":    true,
	"bgHiMagenta": true,
	"bgHiCyan":    true,
	"bgHiWhite":   true,
}

// returns a valid color's foreground text color attribute
var colorAttributeMap = map[string]color.Attribute{
	// default colors for backwards compatibility
	"black":   color.FgBlack,
	"red":     color.FgRed,
	"green":   color.FgGreen,
	"yellow":  color.FgYellow,
	"blue":    color.FgBlue,
	"magenta": color.FgMagenta,
	"cyan":    color.FgCyan,
	"white":   color.FgWhite,

	// attributes
	"reset":        color.Reset,
	"bold":         color.Bold,
	"faint":        color.Faint,
	"italic":       color.Italic,
	"underline":    color.Underline,
	"blinkslow":    color.BlinkSlow,
	"blinkrapid":   color.BlinkRapid,
	"reversevideo": color.ReverseVideo,
	"concealed":    color.Concealed,
	"crossedout":   color.CrossedOut,

	// foreground text colors
	"fgBlack":   color.FgBlack,
	"fgRed":     color.FgRed,
	"fgGreen":   color.FgGreen,
	"fgYellow":  color.FgYellow,
	"fgBlue":    color.FgBlue,
	"fgMagenta": color.FgMagenta,
	"fgCyan":    color.FgCyan,
	"fgWhite":   color.FgWhite,

	// foreground Hi-Intensity text colors
	"fgHiBlack":   color.FgHiBlack,
	"fgHiRed":     color.FgHiRed,
	"fgHiGreen":   color.FgHiGreen,
	"fgHiYellow":  color.FgHiYellow,
	"fgHiBlue":    color.FgHiBlue,
	"fgHiMagenta": color.FgHiMagenta,
	"fgHiCyan":    color.FgHiCyan,
	"fgHiWhite":   color.FgHiWhite,

	// background text colors
	"bgBlack":   color.BgBlack,
	"bgRed":     color.BgRed,
	"bgGreen":   color.BgGreen,
	"bgYellow":  color.BgYellow,
	"bgBlue":    color.BgBlue,
	"bgMagenta": color.BgMagenta,
	"bgCyan":    color.BgCyan,
	"bgWhite":   color.BgWhite,

	// background Hi-Intensity text colors
	"bgHiBlack":   color.BgHiBlack,
	"bgHiRed":     color.BgHiRed,
	"bgHiGreen":   color.BgHiGreen,
	"bgHiYellow":  color.BgHiYellow,
	"bgHiBlue":    color.BgHiBlue,
	"bgHiMagenta": color.BgHiMagenta,
	"bgHiCyan":    color.BgHiCyan,
	"bgHiWhite":   color.BgHiWhite,
}

// validColor will make sure the given color is actually allowed
func validColor(c string) bool {
	valid := false
	if validColors[c] {
		valid = true
	}
	return valid
}

// Spinner struct to hold the provided options
type Spinner struct {
	Delay      time.Duration                 // Delay is the speed of the indicator
	chars      []string                      // chars holds the chosen character set
	Prefix     string                        // Prefix is the text preppended to the indicator
	Suffix     string                        // Suffix is the text appended to the indicator
	FinalMSG   string                        // string displayed after Stop() is called
	lastOutput string                        // last character(set) written
	color      func(a ...interface{}) string // default color is white
	lock       *sync.RWMutex                 //
	Writer     io.Writer                     // to make testing better, exported so users have access
	active     bool                          // active holds the state of the spinner
	stopChan   chan struct{}                 // stopChan is a channel used to stop the indicator
}

// New provides a pointer to an instance of Spinner with the supplied options
func New(cs []string, d time.Duration, options ...Option) *Spinner {
	s:= &Spinner{
		Delay:    d,
		chars:    cs,
		color:    color.New(color.FgWhite).SprintFunc(),
		lock:     &sync.RWMutex{},
		Writer:   color.Output,
		active:   false,
		stopChan: make(chan struct{}, 1),
	}

	for _, option := range options {
		option(s)
	}

	return s
}

type Option func(*Spinner)

type Options struct {
	Color string
	Suffix string
	FinalMSG string
}

func WithColor(color string) Option {
	return func(s *Spinner) {
		s.Color(color)
	}
}

func WithSuffix(suffix string) Option {
	return func(s *Spinner) {
		s.Suffix = suffix
	}
}

func WithFinalMSG(finalMsg string) Option {
	return func(s *Spinner) {
		s.FinalMSG = finalMsg
	}
}

// Active will return whether or not the spinner is currently active
func (s *Spinner) Active() bool {
	return s.active
}

// Start will start the indicator
func (s *Spinner) Start() {
	s.lock.Lock()
	if s.active {
		s.lock.Unlock()
		return
	}
	s.active = true
	s.lock.Unlock()

	go func() {
		for {
			for i := 0; i < len(s.chars); i++ {
				select {
				case <-s.stopChan:
					return
				default:
					s.lock.Lock()
					s.erase()
					var outColor string
					if runtime.GOOS == "windows" {
						if s.Writer == os.Stderr {
							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
						} else {
							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
						}
					} else {
						outColor = fmt.Sprintf("%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
					}
					outPlain := fmt.Sprintf("%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
					fmt.Fprint(s.Writer, outColor)
					s.lastOutput = outPlain
					delay := s.Delay
					s.lock.Unlock()

					time.Sleep(delay)
				}
			}
		}
	}()
}

// Stop stops the indicator
func (s *Spinner) Stop() {
	s.lock.Lock()
	defer s.lock.Unlock()
	if s.active {
		s.active = false
		s.erase()
		if s.FinalMSG != "" {
			fmt.Fprintf(s.Writer, s.FinalMSG)
		}
		s.stopChan <- struct{}{}
	}
}

// Restart will stop and start the indicator
func (s *Spinner) Restart() {
	s.Stop()
	s.Start()
}

// Reverse will reverse the order of the slice assigned to the indicator
func (s *Spinner) Reverse() {
	s.lock.Lock()
	defer s.lock.Unlock()
	for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
		s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
	}
}

// Color will set the struct field for the given color to be used
func (s *Spinner) Color(colors ...string) error {

	colorAttributes := make([]color.Attribute, len(colors))

	// Verify colours are valid and place the appropriate attribute in the array
	for index, c := range colors {
		if !validColor(c) {
			return errInvalidColor
		}

		colorAttributes[index] = colorAttributeMap[c]
	}

	s.lock.Lock()
	s.color = color.New(colorAttributes...).SprintFunc()
	s.lock.Unlock()
	s.Restart()
	return nil
}

// UpdateSpeed will set the indicator delay to the given value
func (s *Spinner) UpdateSpeed(d time.Duration) {
	s.lock.Lock()
	defer s.lock.Unlock()
	s.Delay = d
}

// UpdateCharSet will change the current character set to the given one
func (s *Spinner) UpdateCharSet(cs []string) {
	s.lock.Lock()
	defer s.lock.Unlock()
	s.chars = cs
}

// erase deletes written characters
//
// Caller must already hold s.lock.
func (s *Spinner) erase() {
	n := utf8.RuneCountInString(s.lastOutput)
	if runtime.GOOS == "windows" {
		clearString := "\r"
		for i := 0; i < n; i++ {
			clearString += " "
		}
		fmt.Fprintf(s.Writer, clearString)
		return
	}
	del, _ := hex.DecodeString("7f")
	for _, c := range []string{
		"\b",
		string(del),
		"\b",
		"\033[K", // for macOS Terminal
	} {
		for i := 0; i < n; i++ {
			fmt.Fprintf(s.Writer, c)
		}
	}
	s.lastOutput = ""
}

// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string
func GenerateNumberSequence(length int) []string {
	numSeq := make([]string, length)
	for i := 0; i < length; i++ {
		numSeq[i] = strconv.Itoa(i)
	}
	return numSeq
}
<commit_msg>issue-70: add lock and unlock methods to the spinner as well as added missing comments on exported types<commit_after>// 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 spinner is a simple package to add a spinner / progress indicator to any terminal application.
package spinner

import (
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
	"runtime"
	"strconv"
	"sync"
	"time"
	"unicode/utf8"

	"github.com/fatih/color"
)

// errInvalidColor is returned when attempting to set an invalid color
var errInvalidColor = errors.New("invalid color")

// validColors holds an array of the only colors allowed
var validColors = map[string]bool{
	// default colors for backwards compatibility
	"black":   true,
	"red":     true,
	"green":   true,
	"yellow":  true,
	"blue":    true,
	"magenta": true,
	"cyan":    true,
	"white":   true,

	// attributes
	"reset":        true,
	"bold":         true,
	"faint":        true,
	"italic":       true,
	"underline":    true,
	"blinkslow":    true,
	"blinkrapid":   true,
	"reversevideo": true,
	"concealed":    true,
	"crossedout":   true,

	// foreground text
	"fgBlack":   true,
	"fgRed":     true,
	"fgGreen":   true,
	"fgYellow":  true,
	"fgBlue":    true,
	"fgMagenta": true,
	"fgCyan":    true,
	"fgWhite":   true,

	// foreground Hi-Intensity text
	"fgHiBlack":   true,
	"fgHiRed":     true,
	"fgHiGreen":   true,
	"fgHiYellow":  true,
	"fgHiBlue":    true,
	"fgHiMagenta": true,
	"fgHiCyan":    true,
	"fgHiWhite":   true,

	// background text
	"bgBlack":   true,
	"bgRed":     true,
	"bgGreen":   true,
	"bgYellow":  true,
	"bgBlue":    true,
	"bgMagenta": true,
	"bgCyan":    true,
	"bgWhite":   true,

	// background Hi-Intensity text
	"bgHiBlack":   true,
	"bgHiRed":     true,
	"bgHiGreen":   true,
	"bgHiYellow":  true,
	"bgHiBlue":    true,
	"bgHiMagenta": true,
	"bgHiCyan":    true,
	"bgHiWhite":   true,
}

// returns a valid color's foreground text color attribute
var colorAttributeMap = map[string]color.Attribute{
	// default colors for backwards compatibility
	"black":   color.FgBlack,
	"red":     color.FgRed,
	"green":   color.FgGreen,
	"yellow":  color.FgYellow,
	"blue":    color.FgBlue,
	"magenta": color.FgMagenta,
	"cyan":    color.FgCyan,
	"white":   color.FgWhite,

	// attributes
	"reset":        color.Reset,
	"bold":         color.Bold,
	"faint":        color.Faint,
	"italic":       color.Italic,
	"underline":    color.Underline,
	"blinkslow":    color.BlinkSlow,
	"blinkrapid":   color.BlinkRapid,
	"reversevideo": color.ReverseVideo,
	"concealed":    color.Concealed,
	"crossedout":   color.CrossedOut,

	// foreground text colors
	"fgBlack":   color.FgBlack,
	"fgRed":     color.FgRed,
	"fgGreen":   color.FgGreen,
	"fgYellow":  color.FgYellow,
	"fgBlue":    color.FgBlue,
	"fgMagenta": color.FgMagenta,
	"fgCyan":    color.FgCyan,
	"fgWhite":   color.FgWhite,

	// foreground Hi-Intensity text colors
	"fgHiBlack":   color.FgHiBlack,
	"fgHiRed":     color.FgHiRed,
	"fgHiGreen":   color.FgHiGreen,
	"fgHiYellow":  color.FgHiYellow,
	"fgHiBlue":    color.FgHiBlue,
	"fgHiMagenta": color.FgHiMagenta,
	"fgHiCyan":    color.FgHiCyan,
	"fgHiWhite":   color.FgHiWhite,

	// background text colors
	"bgBlack":   color.BgBlack,
	"bgRed":     color.BgRed,
	"bgGreen":   color.BgGreen,
	"bgYellow":  color.BgYellow,
	"bgBlue":    color.BgBlue,
	"bgMagenta": color.BgMagenta,
	"bgCyan":    color.BgCyan,
	"bgWhite":   color.BgWhite,

	// background Hi-Intensity text colors
	"bgHiBlack":   color.BgHiBlack,
	"bgHiRed":     color.BgHiRed,
	"bgHiGreen":   color.BgHiGreen,
	"bgHiYellow":  color.BgHiYellow,
	"bgHiBlue":    color.BgHiBlue,
	"bgHiMagenta": color.BgHiMagenta,
	"bgHiCyan":    color.BgHiCyan,
	"bgHiWhite":   color.BgHiWhite,
}

// validColor will make sure the given color is actually allowed
func validColor(c string) bool {
	valid := false
	if validColors[c] {
		valid = true
	}
	return valid
}

// Spinner struct to hold the provided options
type Spinner struct {
	Delay      time.Duration                 // Delay is the speed of the indicator
	chars      []string                      // chars holds the chosen character set
	Prefix     string                        // Prefix is the text preppended to the indicator
	Suffix     string                        // Suffix is the text appended to the indicator
	FinalMSG   string                        // string displayed after Stop() is called
	lastOutput string                        // last character(set) written
	color      func(a ...interface{}) string // default color is white
	lock       *sync.RWMutex                 //
	Writer     io.Writer                     // to make testing better, exported so users have access
	active     bool                          // active holds the state of the spinner
	stopChan   chan struct{}                 // stopChan is a channel used to stop the indicator
}

// New provides a pointer to an instance of Spinner with the supplied options
func New(cs []string, d time.Duration, options ...Option) *Spinner {
	s := &Spinner{
		Delay:    d,
		chars:    cs,
		color:    color.New(color.FgWhite).SprintFunc(),
		lock:     &sync.RWMutex{},
		Writer:   color.Output,
		active:   false,
		stopChan: make(chan struct{}, 1),
	}

	for _, option := range options {
		option(s)
	}

	return s
}

// Option is a function that takes a spinner and applies
// a given configuration
type Option func(*Spinner)

// Options contains fields to configure the spinner
type Options struct {
	Color    string
	Suffix   string
	FinalMSG string
}

// WithColor adds the given color to the spinner
func WithColor(color string) Option {
	return func(s *Spinner) {
		s.Color(color)
	}
}

// WithSuffix adds the given string to the spinner
// as the suffix
func WithSuffix(suffix string) Option {
	return func(s *Spinner) {
		s.Suffix = suffix
	}
}

// WithFinalMSG adds the given string ot the spinner
// as the final message to be written
func WithFinalMSG(finalMsg string) Option {
	return func(s *Spinner) {
		s.FinalMSG = finalMsg
	}
}

// Active will return whether or not the spinner is currently active
func (s *Spinner) Active() bool {
	return s.active
}

// Start will start the indicator
func (s *Spinner) Start() {
	s.lock.Lock()
	if s.active {
		s.lock.Unlock()
		return
	}
	s.active = true
	s.lock.Unlock()

	go func() {
		for {
			for i := 0; i < len(s.chars); i++ {
				select {
				case <-s.stopChan:
					return
				default:
					s.lock.Lock()
					s.erase()
					var outColor string
					if runtime.GOOS == "windows" {
						if s.Writer == os.Stderr {
							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
						} else {
							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
						}
					} else {
						outColor = fmt.Sprintf("%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
					}
					outPlain := fmt.Sprintf("%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
					fmt.Fprint(s.Writer, outColor)
					s.lastOutput = outPlain
					delay := s.Delay
					s.lock.Unlock()

					time.Sleep(delay)
				}
			}
		}
	}()
}

// Stop stops the indicator
func (s *Spinner) Stop() {
	s.lock.Lock()
	defer s.lock.Unlock()
	if s.active {
		s.active = false
		s.erase()
		if s.FinalMSG != "" {
			fmt.Fprintf(s.Writer, s.FinalMSG)
		}
		s.stopChan <- struct{}{}
	}
}

// Restart will stop and start the indicator
func (s *Spinner) Restart() {
	s.Stop()
	s.Start()
}

// Reverse will reverse the order of the slice assigned to the indicator
func (s *Spinner) Reverse() {
	s.lock.Lock()
	defer s.lock.Unlock()
	for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
		s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
	}
}

// Color will set the struct field for the given color to be used
func (s *Spinner) Color(colors ...string) error {

	colorAttributes := make([]color.Attribute, len(colors))

	// Verify colours are valid and place the appropriate attribute in the array
	for index, c := range colors {
		if !validColor(c) {
			return errInvalidColor
		}

		colorAttributes[index] = colorAttributeMap[c]
	}

	s.lock.Lock()
	s.color = color.New(colorAttributes...).SprintFunc()
	s.lock.Unlock()
	s.Restart()
	return nil
}

// UpdateSpeed will set the indicator delay to the given value
func (s *Spinner) UpdateSpeed(d time.Duration) {
	s.lock.Lock()
	defer s.lock.Unlock()
	s.Delay = d
}

// UpdateCharSet will change the current character set to the given one
func (s *Spinner) UpdateCharSet(cs []string) {
	s.lock.Lock()
	defer s.lock.Unlock()
	s.chars = cs
}

// erase deletes written characters
//
// Caller must already hold s.lock.
func (s *Spinner) erase() {
	n := utf8.RuneCountInString(s.lastOutput)
	if runtime.GOOS == "windows" {
		clearString := "\r"
		for i := 0; i < n; i++ {
			clearString += " "
		}
		fmt.Fprintf(s.Writer, clearString)
		return
	}
	del, _ := hex.DecodeString("7f")
	for _, c := range []string{
		"\b",
		string(del),
		"\b",
		"\033[K", // for macOS Terminal
	} {
		for i := 0; i < n; i++ {
			fmt.Fprintf(s.Writer, c)
		}
	}
	s.lastOutput = ""
}

// Lock allows for manual control to lock the spinner
func (s *Spinner) Lock() {
	s.lock.Lock()
}

// Unlock allows for manual control to unlock the spinner
func (s *Spinner) Unlock() {
	s.lock.Unlock()
}

// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string
func GenerateNumberSequence(length int) []string {
	numSeq := make([]string, length)
	for i := 0; i < length; i++ {
		numSeq[i] = strconv.Itoa(i)
	}
	return numSeq
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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 apiserver

import (
	"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/union"
	"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/x509"
	"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile"
)

// NewAuthenticator returns an authenticator.Request or an error
func NewAuthenticator(clientCAFile string, tokenFile string) (authenticator.Request, error) {
	authenticators := []authenticator.Request{}

	if len(clientCAFile) > 0 {
		certAuth, err := newAuthenticatorFromClientCAFile(clientCAFile)
		if err != nil {
			return nil, err
		}
		authenticators = append(authenticators, certAuth)
	}

	if len(tokenFile) > 0 {
		tokenAuth, err := newAuthenticatorFromTokenFile(tokenFile)
		if err != nil {
			return nil, err
		}
		authenticators = append(authenticators, tokenAuth)
	}

	if len(authenticators) == 0 {
		return nil, nil
	}
	if len(authenticators) == 1 {
		return authenticators[1], nil
	}
	return union.New(authenticators...), nil

}

// newAuthenticatorFromTokenFile returns an authenticator.Request or an error
func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request, error) {
	tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
	if err != nil {
		return nil, err
	}

	return bearertoken.New(tokenAuthenticator), nil
}

// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
	roots, err := util.CertPoolFromFile(clientCAFile)
	if err != nil {
		return nil, err
	}

	opts := x509.DefaultVerifyOptions()
	opts.Roots = roots

	return x509.New(opts, x509.CommonNameUserConversion), nil
}
<commit_msg>Fix off-by-one crash in pkg/apiserver/authn.go<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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 apiserver

import (
	"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/union"
	"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/x509"
	"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile"
)

// NewAuthenticator returns an authenticator.Request or an error
func NewAuthenticator(clientCAFile string, tokenFile string) (authenticator.Request, error) {
	authenticators := []authenticator.Request{}

	if len(clientCAFile) > 0 {
		certAuth, err := newAuthenticatorFromClientCAFile(clientCAFile)
		if err != nil {
			return nil, err
		}
		authenticators = append(authenticators, certAuth)
	}

	if len(tokenFile) > 0 {
		tokenAuth, err := newAuthenticatorFromTokenFile(tokenFile)
		if err != nil {
			return nil, err
		}
		authenticators = append(authenticators, tokenAuth)
	}

	if len(authenticators) == 0 {
		return nil, nil
	}
	if len(authenticators) == 1 {
		return authenticators[0], nil
	}
	return union.New(authenticators...), nil

}

// newAuthenticatorFromTokenFile returns an authenticator.Request or an error
func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request, error) {
	tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
	if err != nil {
		return nil, err
	}

	return bearertoken.New(tokenAuthenticator), nil
}

// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
	roots, err := util.CertPoolFromFile(clientCAFile)
	if err != nil {
		return nil, err
	}

	opts := x509.DefaultVerifyOptions()
	opts.Roots = roots

	return x509.New(opts, x509.CommonNameUserConversion), nil
}
<|endoftext|>


<commit_before>// Copyright 2019 The Berglas 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 berglas is the Go API for calling berglas.
package berglas

import (
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"io"
	"os"
	"strings"
	"sync"
	"time"

	kms "cloud.google.com/go/kms/apiv1"
	secretmanager "cloud.google.com/go/secretmanager/apiv1beta1"
	"cloud.google.com/go/storage"
	"github.com/golang/protobuf/ptypes"
	"github.com/golang/protobuf/ptypes/timestamp"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"google.golang.org/api/option"
	storagev1 "google.golang.org/api/storage/v1"
)

const (
	// Name, Version, ProjectURL, and UserAgent are used to uniquely identify this
	// package in logs and other binaries.
	Name       = "berglas"
	Version    = "0.4.0"
	ProjectURL = "https://github.com/GoogleCloudPlatform/berglas"
	UserAgent  = Name + "/" + Version + " (+" + ProjectURL + ")"
)

const (
	// CacheControl is the cache-control value to set on the GCS objects. This is
	// configured to use no caching, since users most likely want their secrets to
	// be immediately available.
	CacheControl = "private, no-cache, no-store, no-transform, max-age=0"

	// ChunkSize is the size in bytes of the chunks to upload.
	ChunkSize = 1024

	// MetadataIDKey is a key in the object metadata that identifies an object as
	// a secret. This is used when enumerating secrets in a bucket, in case
	// non-secrets also reside in the bucket.
	MetadataIDKey = "berglas-secret"

	// MetadataKMSKey is the key in the metadata where the name of the KMS key is
	// stored.
	MetadataKMSKey = "berglas-kms-key"
)

// Client is a berglas client
type Client struct {
	kmsClient           *kms.KeyManagementClient
	secretManagerClient *secretmanager.Client
	storageClient       *storage.Client
	storageIAMClient    *storagev1.Service

	loggerLock sync.RWMutex
	logger     *logrus.Logger
}

// New creates a new berglas client.
func New(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
	opts = append(opts, option.WithUserAgent(UserAgent))

	var c Client

	kmsClient, err := kms.NewKeyManagementClient(ctx, opts...)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create kms client")
	}
	c.kmsClient = kmsClient

	secretManagerClient, err := secretmanager.NewClient(ctx, opts...)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create secretManager client")
	}
	c.secretManagerClient = secretManagerClient

	storageClient, err := storage.NewClient(ctx, opts...)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create storage client")
	}
	c.storageClient = storageClient

	storageIAMClient, err := storagev1.NewService(ctx, opts...)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create storagev1 client")
	}
	c.storageIAMClient = storageIAMClient

	c.logger = &logrus.Logger{
		Out:          os.Stderr,
		Formatter:    new(logrus.JSONFormatter),
		Hooks:        make(logrus.LevelHooks),
		Level:        logrus.FatalLevel,
		ReportCaller: true,
	}

	return &c, nil
}

// Secret represents a secret.
type Secret struct {
	// Parent is the resource container. For Cloud Storage secrets, this is the
	// bucket name. For Secret Manager secrets, this is the project ID.
	Parent string

	// Name of the secret.
	Name string

	// Plaintext value of the secret. This may be empty.
	Plaintext []byte

	// Version indicates a secret's version. Secret Manager only.
	Version string

	// UpdatedAt indicates when a secret was last updated.
	UpdatedAt time.Time

	// Generation and Metageneration indicates a secret's version. Cloud Storage
	// only.
	Generation, Metageneration int64

	// KMSKey is the key used to encrypt the secret key. Cloud Storage only.
	KMSKey string
}

// secretFromAttrs constructs a secret from the given object attributes and
// plaintext.
func secretFromAttrs(bucket string, attrs *storage.ObjectAttrs, plaintext []byte) *Secret {
	return &Secret{
		Parent:         bucket,
		Name:           attrs.Name,
		Generation:     attrs.Generation,
		Metageneration: attrs.Metageneration,
		UpdatedAt:      attrs.Updated,
		KMSKey:         attrs.Metadata[MetadataKMSKey],
		Plaintext:      plaintext,
	}
}

func timestampToTime(ts *timestamp.Timestamp) time.Time {
	t, _ := ptypes.Timestamp(ts)
	return t
}

// kmsKeyIncludesVersion returns true if the given KMS key reference includes
// a version.
func kmsKeyIncludesVersion(s string) bool {
	return strings.Count(s, "/") > 7
}

// kmsKeyTrimVersion trims the version from a KMS key reference if it exists.
func kmsKeyTrimVersion(s string) string {
	if !kmsKeyIncludesVersion(s) {
		return s
	}

	parts := strings.SplitN(s, "/", 9)
	return strings.Join(parts[0:8], "/")
}

// envelopeDecrypt decrypts the data with the dek, returning the plaintext and
// any errors that occur.
func envelopeDecrypt(dek, data []byte) ([]byte, error) {
	block, err := aes.NewCipher(dek)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create cipher from dek")
	}

	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create gcm from dek")
	}

	size := aesgcm.NonceSize()
	if len(data) < size {
		return nil, errors.New("malformed ciphertext")
	}
	nonce, ciphertext := data[:size], data[size:]

	plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, errors.Wrap(err, "failed to decrypt ciphertext with dek")
	}
	return plaintext, nil
}

// envelopeEncrypt generates a unique DEK and encrypts the plaintext with the
// given key. The encryption key and resulting ciphertext are returned.
func envelopeEncrypt(plaintext []byte) ([]byte, []byte, error) {
	key := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return nil, nil, errors.Wrap(err, "failed to generate random key bytes")
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, nil, errors.Wrap(err, "failed to create cipher from key")
	}

	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, nil, errors.Wrap(err, "failed to create gcm cipher")
	}

	// Generate nonce
	nonce := make([]byte, aesgcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, nil, errors.Wrap(err, "failed to generate random nonce bytes")
	}

	// Encrypt the ciphertext with the DEK
	ciphertext := aesgcm.Seal(nonce, nonce, plaintext, nil)

	return key, ciphertext, nil
}
<commit_msg>Bump for release<commit_after>// Copyright 2019 The Berglas 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 berglas is the Go API for calling berglas.
package berglas

import (
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"io"
	"os"
	"strings"
	"sync"
	"time"

	kms "cloud.google.com/go/kms/apiv1"
	secretmanager "cloud.google.com/go/secretmanager/apiv1beta1"
	"cloud.google.com/go/storage"
	"github.com/golang/protobuf/ptypes"
	"github.com/golang/protobuf/ptypes/timestamp"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"google.golang.org/api/option"
	storagev1 "google.golang.org/api/storage/v1"
)

const (
	// Name, Version, ProjectURL, and UserAgent are used to uniquely identify this
	// package in logs and other binaries.
	Name       = "berglas"
	Version    = "0.5.0"
	ProjectURL = "https://github.com/GoogleCloudPlatform/berglas"
	UserAgent  = Name + "/" + Version + " (+" + ProjectURL + ")"
)

const (
	// CacheControl is the cache-control value to set on the GCS objects. This is
	// configured to use no caching, since users most likely want their secrets to
	// be immediately available.
	CacheControl = "private, no-cache, no-store, no-transform, max-age=0"

	// ChunkSize is the size in bytes of the chunks to upload.
	ChunkSize = 1024

	// MetadataIDKey is a key in the object metadata that identifies an object as
	// a secret. This is used when enumerating secrets in a bucket, in case
	// non-secrets also reside in the bucket.
	MetadataIDKey = "berglas-secret"

	// MetadataKMSKey is the key in the metadata where the name of the KMS key is
	// stored.
	MetadataKMSKey = "berglas-kms-key"
)

// Client is a berglas client
type Client struct {
	kmsClient           *kms.KeyManagementClient
	secretManagerClient *secretmanager.Client
	storageClient       *storage.Client
	storageIAMClient    *storagev1.Service

	loggerLock sync.RWMutex
	logger     *logrus.Logger
}

// New creates a new berglas client.
func New(ctx context.Context, opts ...option.ClientOption) (*Client, error) {
	opts = append(opts, option.WithUserAgent(UserAgent))

	var c Client

	kmsClient, err := kms.NewKeyManagementClient(ctx, opts...)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create kms client")
	}
	c.kmsClient = kmsClient

	secretManagerClient, err := secretmanager.NewClient(ctx, opts...)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create secretManager client")
	}
	c.secretManagerClient = secretManagerClient

	storageClient, err := storage.NewClient(ctx, opts...)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create storage client")
	}
	c.storageClient = storageClient

	storageIAMClient, err := storagev1.NewService(ctx, opts...)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create storagev1 client")
	}
	c.storageIAMClient = storageIAMClient

	c.logger = &logrus.Logger{
		Out:          os.Stderr,
		Formatter:    new(logrus.JSONFormatter),
		Hooks:        make(logrus.LevelHooks),
		Level:        logrus.FatalLevel,
		ReportCaller: true,
	}

	return &c, nil
}

// Secret represents a secret.
type Secret struct {
	// Parent is the resource container. For Cloud Storage secrets, this is the
	// bucket name. For Secret Manager secrets, this is the project ID.
	Parent string

	// Name of the secret.
	Name string

	// Plaintext value of the secret. This may be empty.
	Plaintext []byte

	// Version indicates a secret's version. Secret Manager only.
	Version string

	// UpdatedAt indicates when a secret was last updated.
	UpdatedAt time.Time

	// Generation and Metageneration indicates a secret's version. Cloud Storage
	// only.
	Generation, Metageneration int64

	// KMSKey is the key used to encrypt the secret key. Cloud Storage only.
	KMSKey string
}

// secretFromAttrs constructs a secret from the given object attributes and
// plaintext.
func secretFromAttrs(bucket string, attrs *storage.ObjectAttrs, plaintext []byte) *Secret {
	return &Secret{
		Parent:         bucket,
		Name:           attrs.Name,
		Generation:     attrs.Generation,
		Metageneration: attrs.Metageneration,
		UpdatedAt:      attrs.Updated,
		KMSKey:         attrs.Metadata[MetadataKMSKey],
		Plaintext:      plaintext,
	}
}

func timestampToTime(ts *timestamp.Timestamp) time.Time {
	t, _ := ptypes.Timestamp(ts)
	return t
}

// kmsKeyIncludesVersion returns true if the given KMS key reference includes
// a version.
func kmsKeyIncludesVersion(s string) bool {
	return strings.Count(s, "/") > 7
}

// kmsKeyTrimVersion trims the version from a KMS key reference if it exists.
func kmsKeyTrimVersion(s string) string {
	if !kmsKeyIncludesVersion(s) {
		return s
	}

	parts := strings.SplitN(s, "/", 9)
	return strings.Join(parts[0:8], "/")
}

// envelopeDecrypt decrypts the data with the dek, returning the plaintext and
// any errors that occur.
func envelopeDecrypt(dek, data []byte) ([]byte, error) {
	block, err := aes.NewCipher(dek)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create cipher from dek")
	}

	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create gcm from dek")
	}

	size := aesgcm.NonceSize()
	if len(data) < size {
		return nil, errors.New("malformed ciphertext")
	}
	nonce, ciphertext := data[:size], data[size:]

	plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, errors.Wrap(err, "failed to decrypt ciphertext with dek")
	}
	return plaintext, nil
}

// envelopeEncrypt generates a unique DEK and encrypts the plaintext with the
// given key. The encryption key and resulting ciphertext are returned.
func envelopeEncrypt(plaintext []byte) ([]byte, []byte, error) {
	key := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		return nil, nil, errors.Wrap(err, "failed to generate random key bytes")
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, nil, errors.Wrap(err, "failed to create cipher from key")
	}

	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, nil, errors.Wrap(err, "failed to create gcm cipher")
	}

	// Generate nonce
	nonce := make([]byte, aesgcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return nil, nil, errors.Wrap(err, "failed to generate random nonce bytes")
	}

	// Encrypt the ciphertext with the DEK
	ciphertext := aesgcm.Seal(nonce, nonce, plaintext, nil)

	return key, ciphertext, nil
}
<|endoftext|>