Update dependencies

This commit is contained in:
Manuel de Brito Fontes 2017-10-06 17:33:32 -03:00
parent bf5616c65b
commit d6d374b28d
13962 changed files with 48226 additions and 3618880 deletions

View file

@ -1,65 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"compression_test.go",
"cors_test.go",
"maxinflight_test.go",
"timeout_test.go",
],
library = ":go_default_library",
deps = [
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/filters:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"compression.go",
"cors.go",
"doc.go",
"longrunning.go",
"maxinflight.go",
"timeout.go",
"wrap.go",
],
deps = [
"//vendor/github.com/emicklei/go-restful:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/httplog:go_default_library",
],
)
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 @@
reviewers:
- sttts
- dims

View file

@ -1,183 +0,0 @@
/*
Copyright 2017 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 filters
import (
"compress/gzip"
"compress/zlib"
"errors"
"fmt"
"io"
"net/http"
"strings"
"github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/endpoints/request"
)
// Compressor is an interface to compression writers
type Compressor interface {
io.WriteCloser
Flush() error
}
const (
headerAcceptEncoding = "Accept-Encoding"
headerContentEncoding = "Content-Encoding"
encodingGzip = "gzip"
encodingDeflate = "deflate"
)
// WithCompression wraps an http.Handler with the Compression Handler
func WithCompression(handler http.Handler, ctxMapper request.RequestContextMapper) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
wantsCompression, encoding := wantsCompressedResponse(req, ctxMapper)
w.Header().Set("Vary", "Accept-Encoding")
if wantsCompression {
compressionWriter, err := NewCompressionResponseWriter(w, encoding)
if err != nil {
handleError(w, req, err)
runtime.HandleError(fmt.Errorf("failed to compress HTTP response: %v", err))
return
}
compressionWriter.Header().Set("Content-Encoding", encoding)
handler.ServeHTTP(compressionWriter, req)
compressionWriter.(*compressionResponseWriter).Close()
} else {
handler.ServeHTTP(w, req)
}
})
}
// wantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested.
func wantsCompressedResponse(req *http.Request, ctxMapper request.RequestContextMapper) (bool, string) {
// don't compress watches
ctx, ok := ctxMapper.Get(req)
if !ok {
return false, ""
}
info, ok := request.RequestInfoFrom(ctx)
if !ok {
return false, ""
}
if !info.IsResourceRequest {
return false, ""
}
if info.Verb == "watch" {
return false, ""
}
header := req.Header.Get(headerAcceptEncoding)
gi := strings.Index(header, encodingGzip)
zi := strings.Index(header, encodingDeflate)
// use in order of appearance
switch {
case gi == -1:
return zi != -1, encodingDeflate
case zi == -1:
return gi != -1, encodingGzip
case gi < zi:
return true, encodingGzip
default:
return true, encodingDeflate
}
}
type compressionResponseWriter struct {
writer http.ResponseWriter
compressor Compressor
encoding string
}
// NewCompressionResponseWriter returns wraps w with a compression ResponseWriter, using the given encoding
func NewCompressionResponseWriter(w http.ResponseWriter, encoding string) (http.ResponseWriter, error) {
var compressor Compressor
switch encoding {
case encodingGzip:
compressor = gzip.NewWriter(w)
case encodingDeflate:
compressor = zlib.NewWriter(w)
default:
return nil, fmt.Errorf("%s is not a supported encoding type", encoding)
}
return &compressionResponseWriter{
writer: w,
compressor: compressor,
encoding: encoding,
}, nil
}
// compressionResponseWriter implements http.Responsewriter Interface
var _ http.ResponseWriter = &compressionResponseWriter{}
func (c *compressionResponseWriter) Header() http.Header {
return c.writer.Header()
}
// compress data according to compression method
func (c *compressionResponseWriter) Write(p []byte) (int, error) {
if c.compressorClosed() {
return -1, errors.New("compressing error: tried to write data using closed compressor")
}
c.Header().Set(headerContentEncoding, c.encoding)
return c.compressor.Write(p)
}
func (c *compressionResponseWriter) WriteHeader(status int) {
c.writer.WriteHeader(status)
}
// CloseNotify is part of http.CloseNotifier interface
func (c *compressionResponseWriter) CloseNotify() <-chan bool {
return c.writer.(http.CloseNotifier).CloseNotify()
}
// Close the underlying compressor
func (c *compressionResponseWriter) Close() error {
if c.compressorClosed() {
return errors.New("Compressing error: tried to close already closed compressor")
}
c.compressor.Close()
c.compressor = nil
return nil
}
func (c *compressionResponseWriter) Flush() {
if c.compressorClosed() {
return
}
c.compressor.Flush()
}
func (c *compressionResponseWriter) compressorClosed() bool {
return nil == c.compressor
}
// RestfulWithCompression wraps WithCompression to be compatible with go-restful
func RestfulWithCompression(function restful.RouteFunction, ctxMapper request.RequestContextMapper) restful.RouteFunction {
return restful.RouteFunction(func(request *restful.Request, response *restful.Response) {
handler := WithCompression(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
response.ResponseWriter = w
request.Request = req
function(request, response)
}), ctxMapper)
handler.ServeHTTP(response.ResponseWriter, request.Request)
})
}

View file

@ -1,110 +0,0 @@
/*
Copyright 2017 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 filters
import (
"bytes"
"compress/gzip"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/endpoints/filters"
"k8s.io/apiserver/pkg/endpoints/request"
)
func TestCompression(t *testing.T) {
tests := []struct {
encoding string
watch bool
}{
{"", false},
{"gzip", true},
{"gzip", false},
}
responseData := []byte("1234")
requestContextMapper := request.NewRequestContextMapper()
for _, test := range tests {
handler := WithCompression(
http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Write(responseData)
}),
requestContextMapper,
)
handler = filters.WithRequestInfo(handler, newTestRequestInfoResolver(), requestContextMapper)
handler = request.WithRequestContext(handler, requestContextMapper)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{
Transport: &http.Transport{
DisableCompression: true,
},
}
url := server.URL + "/api/v1/pods"
if test.watch {
url = url + "?watch=1"
}
request, err := http.NewRequest("GET", url, nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
request.Header.Set("Accept-Encoding", test.encoding)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
var reader io.Reader
if test.encoding == "gzip" && !test.watch {
if response.Header.Get("Content-Encoding") != "gzip" {
t.Fatal("expected response header Content-Encoding to be set to \"gzip\"")
}
if response.Header.Get("Vary") != "Accept-Encoding" {
t.Fatal("expected response header Vary to be set to \"Accept-Encoding\"")
}
reader, err = gzip.NewReader(response.Body)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
} else {
if response.Header.Get("Content-Encoding") == "gzip" {
t.Fatal("expected response header Content-Encoding not to be set")
}
reader = response.Body
}
body, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatal("unexpected error: %v", err)
}
if !bytes.Equal(body, responseData) {
t.Fatalf("Expected response body %s to equal %s", body, responseData)
}
}
}
func newTestRequestInfoResolver() *request.RequestInfoFactory {
return &request.RequestInfoFactory{
APIPrefixes: sets.NewString("api", "apis"),
GrouplessAPIPrefixes: sets.NewString("api"),
}
}

View file

@ -1,98 +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 filters
import (
"net/http"
"regexp"
"strings"
"github.com/golang/glog"
)
// TODO: use restful.CrossOriginResourceSharing
// See github.com/emicklei/go-restful/blob/master/examples/restful-CORS-filter.go, and
// github.com/emicklei/go-restful/blob/master/examples/restful-basic-authentication.go
// Or, for a more detailed implementation use https://github.com/martini-contrib/cors
// or implement CORS at your proxy layer.
// WithCORS is a simple CORS implementation that wraps an http Handler.
// Pass nil for allowedMethods and allowedHeaders to use the defaults. If allowedOriginPatterns
// is empty or nil, no CORS support is installed.
func WithCORS(handler http.Handler, allowedOriginPatterns []string, allowedMethods []string, allowedHeaders []string, exposedHeaders []string, allowCredentials string) http.Handler {
if len(allowedOriginPatterns) == 0 {
return handler
}
allowedOriginPatternsREs := allowedOriginRegexps(allowedOriginPatterns)
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
origin := req.Header.Get("Origin")
if origin != "" {
allowed := false
for _, re := range allowedOriginPatternsREs {
if allowed = re.MatchString(origin); allowed {
break
}
}
if allowed {
w.Header().Set("Access-Control-Allow-Origin", origin)
// Set defaults for methods and headers if nothing was passed
if allowedMethods == nil {
allowedMethods = []string{"POST", "GET", "OPTIONS", "PUT", "DELETE", "PATCH"}
}
if allowedHeaders == nil {
allowedHeaders = []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "X-Requested-With", "If-Modified-Since"}
}
if exposedHeaders == nil {
exposedHeaders = []string{"Date"}
}
w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ", "))
w.Header().Set("Access-Control-Allow-Headers", strings.Join(allowedHeaders, ", "))
w.Header().Set("Access-Control-Expose-Headers", strings.Join(exposedHeaders, ", "))
w.Header().Set("Access-Control-Allow-Credentials", allowCredentials)
// Stop here if its a preflight OPTIONS request
if req.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
}
}
// Dispatch to the next handler
handler.ServeHTTP(w, req)
})
}
func allowedOriginRegexps(allowedOrigins []string) []*regexp.Regexp {
res, err := compileRegexps(allowedOrigins)
if err != nil {
glog.Fatalf("Invalid CORS allowed origin, --cors-allowed-origins flag was set to %v - %v", strings.Join(allowedOrigins, ","), err)
}
return res
}
// Takes a list of strings and compiles them into a list of regular expressions
func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
regexps := []*regexp.Regexp{}
for _, regexpStr := range regexpStrings {
r, err := regexp.Compile(regexpStr)
if err != nil {
return []*regexp.Regexp{}, err
}
regexps = append(regexps, r)
}
return regexps, nil
}

View file

@ -1,183 +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 filters
import (
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
)
func TestCORSAllowedOrigins(t *testing.T) {
table := []struct {
allowedOrigins []string
origin string
allowed bool
}{
{[]string{}, "example.com", false},
{[]string{"example.com"}, "example.com", true},
{[]string{"example.com"}, "not-allowed.com", false},
{[]string{"not-matching.com", "example.com"}, "example.com", true},
{[]string{".*"}, "example.com", true},
}
for _, item := range table {
handler := WithCORS(
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
item.allowedOrigins, nil, nil, nil, "true",
)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
request, err := http.NewRequest("GET", server.URL+"/version", nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
request.Header.Set("Origin", item.origin)
response, err := client.Do(request)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if item.allowed {
if !reflect.DeepEqual(item.origin, response.Header.Get("Access-Control-Allow-Origin")) {
t.Errorf("Expected %#v, Got %#v", item.origin, response.Header.Get("Access-Control-Allow-Origin"))
}
if response.Header.Get("Access-Control-Allow-Credentials") == "" {
t.Errorf("Expected Access-Control-Allow-Credentials header to be set")
}
if response.Header.Get("Access-Control-Allow-Headers") == "" {
t.Errorf("Expected Access-Control-Allow-Headers header to be set")
}
if response.Header.Get("Access-Control-Allow-Methods") == "" {
t.Errorf("Expected Access-Control-Allow-Methods header to be set")
}
if response.Header.Get("Access-Control-Expose-Headers") != "Date" {
t.Errorf("Expected Date in Access-Control-Expose-Headers header")
}
} else {
if response.Header.Get("Access-Control-Allow-Origin") != "" {
t.Errorf("Expected Access-Control-Allow-Origin header to not be set")
}
if response.Header.Get("Access-Control-Allow-Credentials") != "" {
t.Errorf("Expected Access-Control-Allow-Credentials header to not be set")
}
if response.Header.Get("Access-Control-Allow-Headers") != "" {
t.Errorf("Expected Access-Control-Allow-Headers header to not be set")
}
if response.Header.Get("Access-Control-Allow-Methods") != "" {
t.Errorf("Expected Access-Control-Allow-Methods header to not be set")
}
if response.Header.Get("Access-Control-Expose-Headers") == "Date" {
t.Errorf("Expected Date in Access-Control-Expose-Headers header")
}
}
}
}
func TestCORSAllowedMethods(t *testing.T) {
tests := []struct {
allowedMethods []string
method string
allowed bool
}{
{nil, "POST", true},
{nil, "GET", true},
{nil, "OPTIONS", true},
{nil, "PUT", true},
{nil, "DELETE", true},
{nil, "PATCH", true},
{[]string{"GET", "POST"}, "PATCH", false},
}
allowsMethod := func(res *http.Response, method string) bool {
allowedMethods := strings.Split(res.Header.Get("Access-Control-Allow-Methods"), ",")
for _, allowedMethod := range allowedMethods {
if strings.TrimSpace(allowedMethod) == method {
return true
}
}
return false
}
for _, test := range tests {
handler := WithCORS(
http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}),
[]string{".*"}, test.allowedMethods, nil, nil, "true",
)
server := httptest.NewServer(handler)
defer server.Close()
client := http.Client{}
request, err := http.NewRequest(test.method, server.URL+"/version", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
request.Header.Set("Origin", "allowed.com")
response, err := client.Do(request)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
methodAllowed := allowsMethod(response, test.method)
switch {
case test.allowed && !methodAllowed:
t.Errorf("Expected %v to be allowed, Got only %#v", test.method, response.Header.Get("Access-Control-Allow-Methods"))
case !test.allowed && methodAllowed:
t.Errorf("Unexpected allowed method %v, Expected only %#v", test.method, response.Header.Get("Access-Control-Allow-Methods"))
}
}
}
func TestCompileRegex(t *testing.T) {
uncompiledRegexes := []string{"endsWithMe$", "^startingWithMe"}
regexes, err := compileRegexps(uncompiledRegexes)
if err != nil {
t.Errorf("Failed to compile legal regexes: '%v': %v", uncompiledRegexes, err)
}
if len(regexes) != len(uncompiledRegexes) {
t.Errorf("Wrong number of regexes returned: '%v': %v", uncompiledRegexes, regexes)
}
if !regexes[0].MatchString("Something that endsWithMe") {
t.Errorf("Wrong regex returned: '%v': %v", uncompiledRegexes[0], regexes[0])
}
if regexes[0].MatchString("Something that doesn't endsWithMe.") {
t.Errorf("Wrong regex returned: '%v': %v", uncompiledRegexes[0], regexes[0])
}
if !regexes[1].MatchString("startingWithMe is very important") {
t.Errorf("Wrong regex returned: '%v': %v", uncompiledRegexes[1], regexes[1])
}
if regexes[1].MatchString("not startingWithMe should fail") {
t.Errorf("Wrong regex returned: '%v': %v", uncompiledRegexes[1], regexes[1])
}
}

View file

@ -1,19 +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 filters contains all the http handler chain filters which
// are not api related.
package filters // import "k8s.io/apiserver/pkg/server/filters"

View file

@ -1,37 +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 filters
import (
"net/http"
"k8s.io/apimachinery/pkg/util/sets"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
)
// BasicLongRunningRequestCheck returns true if the given request has one of the specified verbs or one of the specified subresources
func BasicLongRunningRequestCheck(longRunningVerbs, longRunningSubresources sets.String) apirequest.LongRunningRequestCheck {
return func(r *http.Request, requestInfo *apirequest.RequestInfo) bool {
if longRunningVerbs.Has(requestInfo.Verb) {
return true
}
if requestInfo.IsResourceRequest && longRunningSubresources.Has(requestInfo.Subresource) {
return true
}
return false
}
}

View file

@ -1,133 +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 filters
import (
"fmt"
"net/http"
"strings"
"time"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/metrics"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"github.com/golang/glog"
)
// Constant for the retry-after interval on rate limiting.
// TODO: maybe make this dynamic? or user-adjustable?
const retryAfter = "1"
var nonMutatingRequestVerbs = sets.NewString("get", "list", "watch")
func handleError(w http.ResponseWriter, r *http.Request, err error) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Internal Server Error: %#v", r.RequestURI)
glog.Errorf(err.Error())
}
// WithMaxInFlightLimit limits the number of in-flight requests to buffer size of the passed in channel.
func WithMaxInFlightLimit(
handler http.Handler,
nonMutatingLimit int,
mutatingLimit int,
requestContextMapper genericapirequest.RequestContextMapper,
longRunningRequestCheck apirequest.LongRunningRequestCheck,
) http.Handler {
if nonMutatingLimit == 0 && mutatingLimit == 0 {
return handler
}
var nonMutatingChan chan bool
var mutatingChan chan bool
if nonMutatingLimit != 0 {
nonMutatingChan = make(chan bool, nonMutatingLimit)
}
if mutatingLimit != 0 {
mutatingChan = make(chan bool, mutatingLimit)
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, ok := requestContextMapper.Get(r)
if !ok {
handleError(w, r, fmt.Errorf("no context found for request, handler chain must be wrong"))
return
}
requestInfo, ok := apirequest.RequestInfoFrom(ctx)
if !ok {
handleError(w, r, fmt.Errorf("no RequestInfo found in context, handler chain must be wrong"))
return
}
// Skip tracking long running events.
if longRunningRequestCheck != nil && longRunningRequestCheck(r, requestInfo) {
handler.ServeHTTP(w, r)
return
}
var c chan bool
if !nonMutatingRequestVerbs.Has(requestInfo.Verb) {
c = mutatingChan
} else {
c = nonMutatingChan
}
if c == nil {
handler.ServeHTTP(w, r)
} else {
select {
case c <- true:
defer func() { <-c }()
handler.ServeHTTP(w, r)
default:
// at this point we're about to return a 429, BUT not all actors should be rate limited. A system:master is so powerful
// that he should always get an answer. It's a super-admin or a loopback connection.
if currUser, ok := apirequest.UserFrom(ctx); ok {
for _, group := range currUser.GetGroups() {
if group == user.SystemPrivilegedGroup {
handler.ServeHTTP(w, r)
return
}
}
}
scope := "cluster"
if requestInfo.Namespace != "" {
scope = "namespace"
}
if requestInfo.Name != "" {
scope = "resource"
}
if requestInfo.IsResourceRequest {
metrics.MonitorRequest(r, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, "", scope, http.StatusTooManyRequests, 0, time.Now())
} else {
metrics.MonitorRequest(r, strings.ToUpper(requestInfo.Verb), "", requestInfo.Path, "", scope, http.StatusTooManyRequests, 0, time.Now())
}
tooManyRequests(r, w)
}
}
})
}
func tooManyRequests(req *http.Request, w http.ResponseWriter) {
// Return a 429 status indicating "Too Many Requests"
w.Header().Set("Retry-After", retryAfter)
http.Error(w, "Too many requests, please try again later.", http.StatusTooManyRequests)
}

View file

@ -1,316 +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 filters
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/user"
apifilters "k8s.io/apiserver/pkg/endpoints/filters"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
)
func createMaxInflightServer(callsWg, blockWg *sync.WaitGroup, disableCallsWg *bool, disableCallsWgMutex *sync.Mutex, nonMutating, mutating int) *httptest.Server {
longRunningRequestCheck := BasicLongRunningRequestCheck(sets.NewString("watch"), sets.NewString("proxy"))
requestContextMapper := apirequest.NewRequestContextMapper()
requestInfoFactory := &apirequest.RequestInfoFactory{APIPrefixes: sets.NewString("apis", "api"), GrouplessAPIPrefixes: sets.NewString("api")}
handler := WithMaxInFlightLimit(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// A short, accounted request that does not wait for block WaitGroup.
if strings.Contains(r.URL.Path, "dontwait") {
return
}
disableCallsWgMutex.Lock()
waitForCalls := *disableCallsWg
disableCallsWgMutex.Unlock()
if waitForCalls {
callsWg.Done()
}
blockWg.Wait()
}),
nonMutating,
mutating,
requestContextMapper,
longRunningRequestCheck,
)
handler = withFakeUser(handler, requestContextMapper)
handler = apifilters.WithRequestInfo(handler, requestInfoFactory, requestContextMapper)
handler = apirequest.WithRequestContext(handler, requestContextMapper)
return httptest.NewServer(handler)
}
func withFakeUser(handler http.Handler, requestContextMapper apirequest.RequestContextMapper) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, ok := requestContextMapper.Get(r)
if !ok {
handleError(w, r, fmt.Errorf("no context found for request, handler chain must be wrong"))
return
}
if len(r.Header["Groups"]) > 0 {
requestContextMapper.Update(r, apirequest.WithUser(ctx, &user.DefaultInfo{
Groups: r.Header["Groups"],
}))
}
handler.ServeHTTP(w, r)
})
}
// Tests that MaxInFlightLimit works, i.e.
// - "long" requests such as proxy or watch, identified by regexp are not accounted despite
// hanging for the long time,
// - "short" requests are correctly accounted, i.e. there can be only size of channel passed to the
// constructor in flight at any given moment,
// - subsequent "short" requests are rejected instantly with appropriate error,
// - subsequent "long" requests are handled normally,
// - we correctly recover after some "short" requests finish, i.e. we can process new ones.
func TestMaxInFlightNonMutating(t *testing.T) {
const AllowedNonMutatingInflightRequestsNo = 3
// Calls is used to wait until all server calls are received. We are sending
// AllowedNonMutatingInflightRequestsNo of 'long' not-accounted requests and the same number of
// 'short' accounted ones.
calls := &sync.WaitGroup{}
calls.Add(AllowedNonMutatingInflightRequestsNo * 2)
// Responses is used to wait until all responses are
// received. This prevents some async requests getting EOF
// errors from prematurely closing the server
responses := &sync.WaitGroup{}
responses.Add(AllowedNonMutatingInflightRequestsNo * 2)
// Block is used to keep requests in flight for as long as we need to. All requests will
// be unblocked at the same time.
block := &sync.WaitGroup{}
block.Add(1)
waitForCalls := true
waitForCallsMutex := sync.Mutex{}
server := createMaxInflightServer(calls, block, &waitForCalls, &waitForCallsMutex, AllowedNonMutatingInflightRequestsNo, 1)
defer server.Close()
// These should hang, but not affect accounting. use a query param match
for i := 0; i < AllowedNonMutatingInflightRequestsNo; i++ {
// These should hang waiting on block...
go func() {
if err := expectHTTPGet(server.URL+"/api/v1/namespaces/default/wait?watch=true", http.StatusOK); err != nil {
t.Error(err)
}
responses.Done()
}()
}
// Check that sever is not saturated by not-accounted calls
if err := expectHTTPGet(server.URL+"/dontwait", http.StatusOK); err != nil {
t.Error(err)
}
// These should hang and be accounted, i.e. saturate the server
for i := 0; i < AllowedNonMutatingInflightRequestsNo; i++ {
// These should hang waiting on block...
go func() {
if err := expectHTTPGet(server.URL, http.StatusOK); err != nil {
t.Error(err)
}
responses.Done()
}()
}
// We wait for all calls to be received by the server
calls.Wait()
// Disable calls notifications in the server
waitForCallsMutex.Lock()
waitForCalls = false
waitForCallsMutex.Unlock()
// Do this multiple times to show that rate limit rejected requests don't block.
for i := 0; i < 2; i++ {
if err := expectHTTPGet(server.URL, http.StatusTooManyRequests); err != nil {
t.Error(err)
}
}
// Validate that non-accounted URLs still work. use a path regex match
if err := expectHTTPGet(server.URL+"/api/v1/watch/namespaces/default/dontwait", http.StatusOK); err != nil {
t.Error(err)
}
// We should allow a single mutating request.
if err := expectHTTPPost(server.URL+"/dontwait", http.StatusOK); err != nil {
t.Error(err)
}
// Let all hanging requests finish
block.Done()
// Show that we recover from being blocked up.
// Too avoid flakyness we need to wait until at least one of the requests really finishes.
responses.Wait()
if err := expectHTTPGet(server.URL, http.StatusOK); err != nil {
t.Error(err)
}
}
func TestMaxInFlightMutating(t *testing.T) {
const AllowedMutatingInflightRequestsNo = 3
calls := &sync.WaitGroup{}
calls.Add(AllowedMutatingInflightRequestsNo)
responses := &sync.WaitGroup{}
responses.Add(AllowedMutatingInflightRequestsNo)
// Block is used to keep requests in flight for as long as we need to. All requests will
// be unblocked at the same time.
block := &sync.WaitGroup{}
block.Add(1)
waitForCalls := true
waitForCallsMutex := sync.Mutex{}
server := createMaxInflightServer(calls, block, &waitForCalls, &waitForCallsMutex, 1, AllowedMutatingInflightRequestsNo)
defer server.Close()
// These should hang and be accounted, i.e. saturate the server
for i := 0; i < AllowedMutatingInflightRequestsNo; i++ {
// These should hang waiting on block...
go func() {
if err := expectHTTPPost(server.URL+"/foo/bar", http.StatusOK); err != nil {
t.Error(err)
}
responses.Done()
}()
}
// We wait for all calls to be received by the server
calls.Wait()
// Disable calls notifications in the server
// Disable calls notifications in the server
waitForCallsMutex.Lock()
waitForCalls = false
waitForCallsMutex.Unlock()
// Do this multiple times to show that rate limit rejected requests don't block.
for i := 0; i < 2; i++ {
if err := expectHTTPPost(server.URL+"/foo/bar/", http.StatusTooManyRequests); err != nil {
t.Error(err)
}
}
// Validate that non-mutating URLs still work. use a path regex match
if err := expectHTTPGet(server.URL+"/dontwait", http.StatusOK); err != nil {
t.Error(err)
}
// Let all hanging requests finish
block.Done()
// Show that we recover from being blocked up.
// Too avoid flakyness we need to wait until at least one of the requests really finishes.
responses.Wait()
if err := expectHTTPPost(server.URL+"/foo/bar", http.StatusOK); err != nil {
t.Error(err)
}
}
// We use GET as a sample non-mutating request.
func expectHTTPGet(url string, code int) error {
r, err := http.Get(url)
if err != nil {
return fmt.Errorf("unexpected error: %v", err)
}
if r.StatusCode != code {
return fmt.Errorf("unexpected response: %v", r.StatusCode)
}
return nil
}
// We use POST as a sample mutating request.
func expectHTTPPost(url string, code int, groups ...string) error {
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader("foo bar"))
if err != nil {
return err
}
for _, group := range groups {
req.Header.Add("Groups", group)
}
r, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("unexpected error: %v", err)
}
if r.StatusCode != code {
return fmt.Errorf("unexpected response: %v", r.StatusCode)
}
return nil
}
func TestMaxInFlightSkipsMasters(t *testing.T) {
const AllowedMutatingInflightRequestsNo = 3
calls := &sync.WaitGroup{}
calls.Add(AllowedMutatingInflightRequestsNo)
responses := &sync.WaitGroup{}
responses.Add(AllowedMutatingInflightRequestsNo)
// Block is used to keep requests in flight for as long as we need to. All requests will
// be unblocked at the same time.
block := &sync.WaitGroup{}
block.Add(1)
waitForCalls := true
waitForCallsMutex := sync.Mutex{}
server := createMaxInflightServer(calls, block, &waitForCalls, &waitForCallsMutex, 1, AllowedMutatingInflightRequestsNo)
defer server.Close()
// These should hang and be accounted, i.e. saturate the server
for i := 0; i < AllowedMutatingInflightRequestsNo; i++ {
// These should hang waiting on block...
go func() {
if err := expectHTTPPost(server.URL+"/foo/bar", http.StatusOK); err != nil {
t.Error(err)
}
responses.Done()
}()
}
// We wait for all calls to be received by the server
calls.Wait()
// Disable calls notifications in the server
// Disable calls notifications in the server
waitForCallsMutex.Lock()
waitForCalls = false
waitForCallsMutex.Unlock()
// Do this multiple times to show that rate limit rejected requests don't block.
for i := 0; i < 2; i++ {
if err := expectHTTPPost(server.URL+"/dontwait", http.StatusOK, user.SystemPrivilegedGroup); err != nil {
t.Error(err)
}
}
// Let all hanging requests finish
block.Done()
responses.Wait()
}

View file

@ -1,287 +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 filters
import (
"bufio"
"encoding/json"
"fmt"
"net"
"net/http"
"strings"
"sync"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apiserver/pkg/endpoints/metrics"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
)
var errConnKilled = fmt.Errorf("kill connection/stream")
// WithTimeoutForNonLongRunningRequests times out non-long-running requests after the time given by timeout.
func WithTimeoutForNonLongRunningRequests(handler http.Handler, requestContextMapper apirequest.RequestContextMapper, longRunning apirequest.LongRunningRequestCheck, timeout time.Duration) http.Handler {
if longRunning == nil {
return handler
}
timeoutFunc := func(req *http.Request) (<-chan time.Time, func(), *apierrors.StatusError) {
// TODO unify this with apiserver.MaxInFlightLimit
ctx, ok := requestContextMapper.Get(req)
if !ok {
// if this happens, the handler chain isn't setup correctly because there is no context mapper
return time.After(timeout), func() {}, apierrors.NewInternalError(fmt.Errorf("no context found for request during timeout"))
}
requestInfo, ok := apirequest.RequestInfoFrom(ctx)
if !ok {
// if this happens, the handler chain isn't setup correctly because there is no request info
return time.After(timeout), func() {}, apierrors.NewInternalError(fmt.Errorf("no request info found for request during timeout"))
}
if longRunning(req, requestInfo) {
return nil, nil, nil
}
now := time.Now()
metricFn := func() {
scope := "cluster"
if requestInfo.Namespace != "" {
scope = "namespace"
}
if requestInfo.Name != "" {
scope = "resource"
}
if requestInfo.IsResourceRequest {
metrics.MonitorRequest(req, strings.ToUpper(requestInfo.Verb), requestInfo.Resource, requestInfo.Subresource, "", scope, http.StatusGatewayTimeout, 0, now)
} else {
metrics.MonitorRequest(req, strings.ToUpper(requestInfo.Verb), "", requestInfo.Path, "", scope, http.StatusGatewayTimeout, 0, now)
}
}
return time.After(timeout), metricFn, apierrors.NewTimeoutError(fmt.Sprintf("request did not complete within %s", timeout), 0)
}
return WithTimeout(handler, timeoutFunc)
}
// WithTimeout returns an http.Handler that runs h with a timeout
// determined by timeoutFunc. The new http.Handler calls h.ServeHTTP to handle
// each request, but if a call runs for longer than its time limit, the
// handler responds with a 504 Gateway Timeout error and the message
// provided. (If msg is empty, a suitable default message will be sent.) After
// the handler times out, writes by h to its http.ResponseWriter will return
// http.ErrHandlerTimeout. If timeoutFunc returns a nil timeout channel, no
// timeout will be enforced. recordFn is a function that will be invoked whenever
// a timeout happens.
func WithTimeout(h http.Handler, timeoutFunc func(*http.Request) (timeout <-chan time.Time, recordFn func(), err *apierrors.StatusError)) http.Handler {
return &timeoutHandler{h, timeoutFunc}
}
type timeoutHandler struct {
handler http.Handler
timeout func(*http.Request) (<-chan time.Time, func(), *apierrors.StatusError)
}
func (t *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
after, recordFn, err := t.timeout(r)
if after == nil {
t.handler.ServeHTTP(w, r)
return
}
done := make(chan struct{})
tw := newTimeoutWriter(w)
go func() {
t.handler.ServeHTTP(tw, r)
close(done)
}()
select {
case <-done:
return
case <-after:
recordFn()
tw.timeout(err)
}
}
type timeoutWriter interface {
http.ResponseWriter
timeout(*apierrors.StatusError)
}
func newTimeoutWriter(w http.ResponseWriter) timeoutWriter {
base := &baseTimeoutWriter{w: w}
_, notifiable := w.(http.CloseNotifier)
_, hijackable := w.(http.Hijacker)
switch {
case notifiable && hijackable:
return &closeHijackTimeoutWriter{base}
case notifiable:
return &closeTimeoutWriter{base}
case hijackable:
return &hijackTimeoutWriter{base}
default:
return base
}
}
type baseTimeoutWriter struct {
w http.ResponseWriter
mu sync.Mutex
// if the timeout handler has timedout
timedOut bool
// if this timeout writer has wrote header
wroteHeader bool
// if this timeout writer has been hijacked
hijacked bool
}
func (tw *baseTimeoutWriter) Header() http.Header {
tw.mu.Lock()
defer tw.mu.Unlock()
if tw.timedOut {
return http.Header{}
}
return tw.w.Header()
}
func (tw *baseTimeoutWriter) Write(p []byte) (int, error) {
tw.mu.Lock()
defer tw.mu.Unlock()
if tw.timedOut {
return 0, http.ErrHandlerTimeout
}
if tw.hijacked {
return 0, http.ErrHijacked
}
tw.wroteHeader = true
return tw.w.Write(p)
}
func (tw *baseTimeoutWriter) Flush() {
tw.mu.Lock()
defer tw.mu.Unlock()
if tw.timedOut {
return
}
if flusher, ok := tw.w.(http.Flusher); ok {
flusher.Flush()
}
}
func (tw *baseTimeoutWriter) WriteHeader(code int) {
tw.mu.Lock()
defer tw.mu.Unlock()
if tw.timedOut || tw.wroteHeader || tw.hijacked {
return
}
tw.wroteHeader = true
tw.w.WriteHeader(code)
}
func (tw *baseTimeoutWriter) timeout(err *apierrors.StatusError) {
tw.mu.Lock()
defer tw.mu.Unlock()
tw.timedOut = true
// The timeout writer has not been used by the inner handler.
// We can safely timeout the HTTP request by sending by a timeout
// handler
if !tw.wroteHeader && !tw.hijacked {
tw.w.WriteHeader(http.StatusGatewayTimeout)
enc := json.NewEncoder(tw.w)
enc.Encode(&err.ErrStatus)
} else {
// The timeout writer has been used by the inner handler. There is
// no way to timeout the HTTP request at the point. We have to shutdown
// the connection for HTTP1 or reset stream for HTTP2.
//
// Note from: Brad Fitzpatrick
// if the ServeHTTP goroutine panics, that will do the best possible thing for both
// HTTP/1 and HTTP/2. In HTTP/1, assuming you're replying with at least HTTP/1.1 and
// you've already flushed the headers so it's using HTTP chunking, it'll kill the TCP
// connection immediately without a proper 0-byte EOF chunk, so the peer will recognize
// the response as bogus. In HTTP/2 the server will just RST_STREAM the stream, leaving
// the TCP connection open, but resetting the stream to the peer so it'll have an error,
// like the HTTP/1 case.
panic(errConnKilled)
}
}
func (tw *baseTimeoutWriter) closeNotify() <-chan bool {
tw.mu.Lock()
defer tw.mu.Unlock()
if tw.timedOut {
done := make(chan bool)
close(done)
return done
}
return tw.w.(http.CloseNotifier).CloseNotify()
}
func (tw *baseTimeoutWriter) hijack() (net.Conn, *bufio.ReadWriter, error) {
tw.mu.Lock()
defer tw.mu.Unlock()
if tw.timedOut {
return nil, nil, http.ErrHandlerTimeout
}
conn, rw, err := tw.w.(http.Hijacker).Hijack()
if err == nil {
tw.hijacked = true
}
return conn, rw, err
}
type closeTimeoutWriter struct {
*baseTimeoutWriter
}
func (tw *closeTimeoutWriter) CloseNotify() <-chan bool {
return tw.closeNotify()
}
type hijackTimeoutWriter struct {
*baseTimeoutWriter
}
func (tw *hijackTimeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return tw.hijack()
}
type closeHijackTimeoutWriter struct {
*baseTimeoutWriter
}
func (tw *closeHijackTimeoutWriter) CloseNotify() <-chan bool {
return tw.closeNotify()
}
func (tw *closeHijackTimeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return tw.hijack()
}

View file

@ -1,117 +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 filters
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
"sync"
"testing"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
)
type recorder struct {
lock sync.Mutex
count int
}
func (r *recorder) Record() {
r.lock.Lock()
defer r.lock.Unlock()
r.count++
}
func (r *recorder) Count() int {
r.lock.Lock()
defer r.lock.Unlock()
return r.count
}
func TestTimeout(t *testing.T) {
sendResponse := make(chan struct{}, 1)
writeErrors := make(chan error, 1)
timeout := make(chan time.Time, 1)
resp := "test response"
timeoutErr := apierrors.NewServerTimeout(schema.GroupResource{Group: "foo", Resource: "bar"}, "get", 0)
record := &recorder{}
ts := httptest.NewServer(WithTimeout(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
<-sendResponse
_, err := w.Write([]byte(resp))
writeErrors <- err
}),
func(*http.Request) (<-chan time.Time, func(), *apierrors.StatusError) {
return timeout, record.Record, timeoutErr
}))
defer ts.Close()
// No timeouts
sendResponse <- struct{}{}
res, err := http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusOK {
t.Errorf("got res.StatusCode %d; expected %d", res.StatusCode, http.StatusOK)
}
body, _ := ioutil.ReadAll(res.Body)
if string(body) != resp {
t.Errorf("got body %q; expected %q", string(body), resp)
}
if err := <-writeErrors; err != nil {
t.Errorf("got unexpected Write error on first request: %v", err)
}
if record.Count() != 0 {
t.Errorf("invoked record method: %#v", record)
}
// Times out
timeout <- time.Time{}
res, err = http.Get(ts.URL)
if err != nil {
t.Fatal(err)
}
if res.StatusCode != http.StatusGatewayTimeout {
t.Errorf("got res.StatusCode %d; expected %d", res.StatusCode, http.StatusServiceUnavailable)
}
body, _ = ioutil.ReadAll(res.Body)
status := &metav1.Status{}
if err := json.Unmarshal(body, status); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(status, &timeoutErr.ErrStatus) {
t.Errorf("unexpected object: %s", diff.ObjectReflectDiff(&timeoutErr.ErrStatus, status))
}
if record.Count() != 1 {
t.Errorf("did not invoke record method: %#v", record)
}
// Now try to send a response
sendResponse <- struct{}{}
if err := <-writeErrors; err != http.ErrHandlerTimeout {
t.Errorf("got Write error of %v; expected %v", err, http.ErrHandlerTimeout)
}
}

View file

@ -1,43 +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 filters
import (
"net/http"
"runtime/debug"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/server/httplog"
)
// WithPanicRecovery wraps an http Handler to recover and log panics.
func WithPanicRecovery(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer runtime.HandleCrash(func(err interface{}) {
http.Error(w, "This request caused apisever to panic. Look in log for details.", http.StatusInternalServerError)
glog.Errorf("APIServer panic'd on %v %v: %v\n%s\n", req.Method, req.RequestURI, err, debug.Stack())
})
logger := httplog.NewLogged(req, &w)
defer logger.Log()
// Dispatch to the internal handler
handler.ServeHTTP(w, req)
})
}

View file

@ -1,35 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["httplog_test.go"],
library = ":go_default_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"httplog.go",
],
deps = ["//vendor/github.com/golang/glog:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -1,19 +0,0 @@
/*
Copyright 2014 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 httplog contains a helper object and functions to maintain a log
// along with an http response.
package httplog // import "k8s.io/apiserver/pkg/server/httplog"

View file

@ -1,213 +0,0 @@
/*
Copyright 2014 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 httplog
import (
"bufio"
"fmt"
"net"
"net/http"
"runtime"
"time"
"github.com/golang/glog"
)
// StacktracePred returns true if a stacktrace should be logged for this status.
type StacktracePred func(httpStatus int) (logStacktrace bool)
type logger interface {
Addf(format string, data ...interface{})
}
// Add a layer on top of ResponseWriter, so we can track latency and error
// message sources.
//
// TODO now that we're using go-restful, we shouldn't need to be wrapping
// the http.ResponseWriter. We can recover panics from go-restful, and
// the logging value is questionable.
type respLogger struct {
hijacked bool
statusRecorded bool
status int
statusStack string
addedInfo string
startTime time.Time
captureErrorOutput bool
req *http.Request
w http.ResponseWriter
logStacktracePred StacktracePred
}
// Simple logger that logs immediately when Addf is called
type passthroughLogger struct{}
// Addf logs info immediately.
func (passthroughLogger) Addf(format string, data ...interface{}) {
glog.V(2).Info(fmt.Sprintf(format, data...))
}
// DefaultStacktracePred is the default implementation of StacktracePred.
func DefaultStacktracePred(status int) bool {
return (status < http.StatusOK || status >= http.StatusInternalServerError) && status != http.StatusSwitchingProtocols
}
// NewLogged turns a normal response writer into a logged response writer.
//
// Usage:
//
// defer NewLogged(req, &w).StacktraceWhen(StatusIsNot(200, 202)).Log()
//
// (Only the call to Log() is deferred, so you can set everything up in one line!)
//
// Note that this *changes* your writer, to route response writing actions
// through the logger.
//
// Use LogOf(w).Addf(...) to log something along with the response result.
func NewLogged(req *http.Request, w *http.ResponseWriter) *respLogger {
if _, ok := (*w).(*respLogger); ok {
// Don't double-wrap!
panic("multiple NewLogged calls!")
}
rl := &respLogger{
startTime: time.Now(),
req: req,
w: *w,
logStacktracePred: DefaultStacktracePred,
}
*w = rl // hijack caller's writer!
return rl
}
// LogOf returns the logger hiding in w. If there is not an existing logger
// then a passthroughLogger will be created which will log to stdout immediately
// when Addf is called.
func LogOf(req *http.Request, w http.ResponseWriter) logger {
if _, exists := w.(*respLogger); !exists {
pl := &passthroughLogger{}
return pl
}
if rl, ok := w.(*respLogger); ok {
return rl
}
panic("Unable to find or create the logger!")
}
// Unlogged returns the original ResponseWriter, or w if it is not our inserted logger.
func Unlogged(w http.ResponseWriter) http.ResponseWriter {
if rl, ok := w.(*respLogger); ok {
return rl.w
}
return w
}
// StacktraceWhen sets the stacktrace logging predicate, which decides when to log a stacktrace.
// There's a default, so you don't need to call this unless you don't like the default.
func (rl *respLogger) StacktraceWhen(pred StacktracePred) *respLogger {
rl.logStacktracePred = pred
return rl
}
// StatusIsNot returns a StacktracePred which will cause stacktraces to be logged
// for any status *not* in the given list.
func StatusIsNot(statuses ...int) StacktracePred {
return func(status int) bool {
for _, s := range statuses {
if status == s {
return false
}
}
return true
}
}
// Addf adds additional data to be logged with this request.
func (rl *respLogger) Addf(format string, data ...interface{}) {
rl.addedInfo += "\n" + fmt.Sprintf(format, data...)
}
// Log is intended to be called once at the end of your request handler, via defer
func (rl *respLogger) Log() {
latency := time.Since(rl.startTime)
if glog.V(2) {
if !rl.hijacked {
glog.InfoDepth(1, fmt.Sprintf("%s %s: (%v) %v%v%v [%s %s]", rl.req.Method, rl.req.RequestURI, latency, rl.status, rl.statusStack, rl.addedInfo, rl.req.Header["User-Agent"], rl.req.RemoteAddr))
} else {
glog.InfoDepth(1, fmt.Sprintf("%s %s: (%v) hijacked [%s %s]", rl.req.Method, rl.req.RequestURI, latency, rl.req.Header["User-Agent"], rl.req.RemoteAddr))
}
}
}
// Header implements http.ResponseWriter.
func (rl *respLogger) Header() http.Header {
return rl.w.Header()
}
// Write implements http.ResponseWriter.
func (rl *respLogger) Write(b []byte) (int, error) {
if !rl.statusRecorded {
rl.recordStatus(http.StatusOK) // Default if WriteHeader hasn't been called
}
if rl.captureErrorOutput {
rl.Addf("logging error output: %q\n", string(b))
}
return rl.w.Write(b)
}
// Flush implements http.Flusher even if the underlying http.Writer doesn't implement it.
// Flush is used for streaming purposes and allows to flush buffered data to the client.
func (rl *respLogger) Flush() {
if flusher, ok := rl.w.(http.Flusher); ok {
flusher.Flush()
} else if glog.V(2) {
glog.InfoDepth(1, fmt.Sprintf("Unable to convert %+v into http.Flusher", rl.w))
}
}
// WriteHeader implements http.ResponseWriter.
func (rl *respLogger) WriteHeader(status int) {
rl.recordStatus(status)
rl.w.WriteHeader(status)
}
// Hijack implements http.Hijacker.
func (rl *respLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
rl.hijacked = true
return rl.w.(http.Hijacker).Hijack()
}
// CloseNotify implements http.CloseNotifier
func (rl *respLogger) CloseNotify() <-chan bool {
return rl.w.(http.CloseNotifier).CloseNotify()
}
func (rl *respLogger) recordStatus(status int) {
rl.status = status
rl.statusRecorded = true
if rl.logStacktracePred(status) {
// Only log stacks for errors
stack := make([]byte, 50*1024)
stack = stack[:runtime.Stack(stack, false)]
rl.statusStack = "\n" + string(stack)
rl.captureErrorOutput = true
} else {
rl.statusStack = ""
}
}

View file

@ -1,152 +0,0 @@
/*
Copyright 2014 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 httplog
import (
"net/http"
"net/http/httptest"
"reflect"
"testing"
)
func TestDefaultStacktracePred(t *testing.T) {
for _, x := range []int{101, 200, 204, 302, 400, 404} {
if DefaultStacktracePred(x) {
t.Fatalf("should not log on %v by default", x)
}
}
for _, x := range []int{500, 100} {
if !DefaultStacktracePred(x) {
t.Fatalf("should log on %v by default", x)
}
}
}
func TestStatusIsNot(t *testing.T) {
statusTestTable := []struct {
status int
statuses []int
want bool
}{
{http.StatusOK, []int{}, true},
{http.StatusOK, []int{http.StatusOK}, false},
{http.StatusCreated, []int{http.StatusOK, http.StatusAccepted}, true},
}
for _, tt := range statusTestTable {
sp := StatusIsNot(tt.statuses...)
got := sp(tt.status)
if got != tt.want {
t.Errorf("Expected %v, got %v", tt.want, got)
}
}
}
func TestNewLogged(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
handler := func(w http.ResponseWriter, r *http.Request) {
NewLogged(req, &w)
defer func() {
if r := recover(); r == nil {
t.Errorf("Expected NewLogged to panic")
}
}()
NewLogged(req, &w)
}
w := httptest.NewRecorder()
handler(w, req)
}
func TestLogOf(t *testing.T) {
logOfTests := []bool{true, false}
for _, makeLogger := range logOfTests {
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
handler := func(w http.ResponseWriter, r *http.Request) {
var want string
if makeLogger {
NewLogged(req, &w)
want = "*httplog.respLogger"
} else {
want = "*httplog.passthroughLogger"
}
got := reflect.TypeOf(LogOf(r, w)).String()
if want != got {
t.Errorf("Expected %v, got %v", want, got)
}
}
w := httptest.NewRecorder()
handler(w, req)
}
}
func TestUnlogged(t *testing.T) {
unloggedTests := []bool{true, false}
for _, makeLogger := range unloggedTests {
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
handler := func(w http.ResponseWriter, r *http.Request) {
want := w
if makeLogger {
NewLogged(req, &w)
}
got := Unlogged(w)
if want != got {
t.Errorf("Expected %v, got %v", want, got)
}
}
w := httptest.NewRecorder()
handler(w, req)
}
}
type testResponseWriter struct{}
func (*testResponseWriter) Header() http.Header { return nil }
func (*testResponseWriter) Write([]byte) (int, error) { return 0, nil }
func (*testResponseWriter) WriteHeader(int) {}
func TestLoggedStatus(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
var tw http.ResponseWriter = new(testResponseWriter)
logger := NewLogged(req, &tw)
logger.Write(nil)
if logger.status != http.StatusOK {
t.Errorf("expected status after write to be %v, got %v", http.StatusOK, logger.status)
}
tw = new(testResponseWriter)
logger = NewLogged(req, &tw)
logger.WriteHeader(http.StatusForbidden)
logger.Write(nil)
if logger.status != http.StatusForbidden {
t.Errorf("expected status after write to remain %v, got %v", http.StatusForbidden, logger.status)
}
}

View file

@ -1,40 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["pathrecorder_test.go"],
library = ":go_default_library",
deps = ["//vendor/github.com/stretchr/testify/assert:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"pathrecorder.go",
],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
)
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 @@
reviewers:
- sttts

View file

@ -1,18 +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 mux contains abstractions for http multiplexing of APIs.
package mux

View file

@ -1,278 +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 mux
import (
"fmt"
"net/http"
"runtime/debug"
"sort"
"strings"
"sync"
"sync/atomic"
"github.com/golang/glog"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
)
// PathRecorderMux wraps a mux object and records the registered exposedPaths.
type PathRecorderMux struct {
// name is used for logging so you can trace requests through
name string
lock sync.Mutex
notFoundHandler http.Handler
pathToHandler map[string]http.Handler
prefixToHandler map[string]http.Handler
// mux stores a pathHandler and is used to handle the actual serving.
// Turns out, we want to accept trailing slashes, BUT we don't care about handling
// everything under them. This does exactly matches only unless its explicitly requested to
// do something different
mux atomic.Value
// exposedPaths is the list of paths that should be shown at /
exposedPaths []string
// pathStacks holds the stacks of all registered paths. This allows us to show a more helpful message
// before the "http: multiple registrations for %s" panic.
pathStacks map[string]string
}
// pathHandler is an http.Handler that will satify requests first by exact match, then by prefix,
// then by notFoundHandler
type pathHandler struct {
// muxName is used for logging so you can trace requests through
muxName string
// pathToHandler is a map of exactly matching request to its handler
pathToHandler map[string]http.Handler
// this has to be sorted by most slashes then by length
prefixHandlers []prefixHandler
// notFoundHandler is the handler to use for satisfying requests with no other match
notFoundHandler http.Handler
}
// prefixHandler holds the prefix it should match and the handler to use
type prefixHandler struct {
// prefix is the prefix to test for a request match
prefix string
// handler is used to satisfy matching requests
handler http.Handler
}
// NewPathRecorderMux creates a new PathRecorderMux
func NewPathRecorderMux(name string) *PathRecorderMux {
ret := &PathRecorderMux{
name: name,
pathToHandler: map[string]http.Handler{},
prefixToHandler: map[string]http.Handler{},
mux: atomic.Value{},
exposedPaths: []string{},
pathStacks: map[string]string{},
}
ret.mux.Store(&pathHandler{notFoundHandler: http.NotFoundHandler()})
return ret
}
// ListedPaths returns the registered handler exposedPaths.
func (m *PathRecorderMux) ListedPaths() []string {
handledPaths := append([]string{}, m.exposedPaths...)
sort.Strings(handledPaths)
return handledPaths
}
func (m *PathRecorderMux) trackCallers(path string) {
if existingStack, ok := m.pathStacks[path]; ok {
utilruntime.HandleError(fmt.Errorf("registered %q from %v", path, existingStack))
}
m.pathStacks[path] = string(debug.Stack())
}
// refreshMuxLocked creates a new mux and must be called while locked. Otherwise the view of handlers may
// not be consistent
func (m *PathRecorderMux) refreshMuxLocked() {
newMux := &pathHandler{
muxName: m.name,
pathToHandler: map[string]http.Handler{},
prefixHandlers: []prefixHandler{},
notFoundHandler: http.NotFoundHandler(),
}
if m.notFoundHandler != nil {
newMux.notFoundHandler = m.notFoundHandler
}
for path, handler := range m.pathToHandler {
newMux.pathToHandler[path] = handler
}
keys := sets.StringKeySet(m.prefixToHandler).List()
sort.Sort(sort.Reverse(byPrefixPriority(keys)))
for _, prefix := range keys {
newMux.prefixHandlers = append(newMux.prefixHandlers, prefixHandler{
prefix: prefix,
handler: m.prefixToHandler[prefix],
})
}
m.mux.Store(newMux)
}
// NotFoundHandler sets the handler to use if there's no match for a give path
func (m *PathRecorderMux) NotFoundHandler(notFoundHandler http.Handler) {
m.lock.Lock()
defer m.lock.Unlock()
m.notFoundHandler = notFoundHandler
m.refreshMuxLocked()
}
// Unregister removes a path from the mux.
func (m *PathRecorderMux) Unregister(path string) {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.pathToHandler, path)
delete(m.prefixToHandler, path)
delete(m.pathStacks, path)
for i := range m.exposedPaths {
if m.exposedPaths[i] == path {
m.exposedPaths = append(m.exposedPaths[:i], m.exposedPaths[i+1:]...)
break
}
}
m.refreshMuxLocked()
}
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (m *PathRecorderMux) Handle(path string, handler http.Handler) {
m.lock.Lock()
defer m.lock.Unlock()
m.trackCallers(path)
m.exposedPaths = append(m.exposedPaths, path)
m.pathToHandler[path] = handler
m.refreshMuxLocked()
}
// HandleFunc registers the handler function for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (m *PathRecorderMux) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
m.Handle(path, http.HandlerFunc(handler))
}
// UnlistedHandle registers the handler for the given pattern, but doesn't list it.
// If a handler already exists for pattern, Handle panics.
func (m *PathRecorderMux) UnlistedHandle(path string, handler http.Handler) {
m.lock.Lock()
defer m.lock.Unlock()
m.trackCallers(path)
m.pathToHandler[path] = handler
m.refreshMuxLocked()
}
// UnlistedHandleFunc registers the handler function for the given pattern, but doesn't list it.
// If a handler already exists for pattern, Handle panics.
func (m *PathRecorderMux) UnlistedHandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) {
m.UnlistedHandle(path, http.HandlerFunc(handler))
}
// HandlePrefix is like Handle, but matches for anything under the path. Like a standard golang trailing slash.
func (m *PathRecorderMux) HandlePrefix(path string, handler http.Handler) {
if !strings.HasSuffix(path, "/") {
panic(fmt.Sprintf("%q must end in a trailing slash", path))
}
m.lock.Lock()
defer m.lock.Unlock()
m.trackCallers(path)
m.exposedPaths = append(m.exposedPaths, path)
m.prefixToHandler[path] = handler
m.refreshMuxLocked()
}
// UnlistedHandlePrefix is like UnlistedHandle, but matches for anything under the path. Like a standard golang trailing slash.
func (m *PathRecorderMux) UnlistedHandlePrefix(path string, handler http.Handler) {
if !strings.HasSuffix(path, "/") {
panic(fmt.Sprintf("%q must end in a trailing slash", path))
}
m.lock.Lock()
defer m.lock.Unlock()
m.trackCallers(path)
m.prefixToHandler[path] = handler
m.refreshMuxLocked()
}
// ServeHTTP makes it an http.Handler
func (m *PathRecorderMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
m.mux.Load().(*pathHandler).ServeHTTP(w, r)
}
// ServeHTTP makes it an http.Handler
func (h *pathHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if exactHandler, ok := h.pathToHandler[r.URL.Path]; ok {
glog.V(5).Infof("%v: %q satisfied by exact match", h.muxName, r.URL.Path)
exactHandler.ServeHTTP(w, r)
return
}
for _, prefixHandler := range h.prefixHandlers {
if strings.HasPrefix(r.URL.Path, prefixHandler.prefix) {
glog.V(5).Infof("%v: %q satisfied by prefix %v", h.muxName, r.URL.Path, prefixHandler.prefix)
prefixHandler.handler.ServeHTTP(w, r)
return
}
}
glog.V(5).Infof("%v: %q satisfied by NotFoundHandler", h.muxName, r.URL.Path)
h.notFoundHandler.ServeHTTP(w, r)
}
// byPrefixPriority sorts url prefixes by the order in which they should be tested by the mux
// this has to be sorted by most slashes then by length so that we can iterate straight
// through to match the "best" one first.
type byPrefixPriority []string
func (s byPrefixPriority) Len() int { return len(s) }
func (s byPrefixPriority) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byPrefixPriority) Less(i, j int) bool {
lhsNumParts := strings.Count(s[i], "/")
rhsNumParts := strings.Count(s[j], "/")
if lhsNumParts != rhsNumParts {
return lhsNumParts < rhsNumParts
}
lhsLen := len(s[i])
rhsLen := len(s[j])
if lhsLen != rhsLen {
return lhsLen < rhsLen
}
return strings.Compare(s[i], s[j]) < 0
}

View file

@ -1,135 +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 mux
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSecretHandlers(t *testing.T) {
c := NewPathRecorderMux("test")
c.UnlistedHandleFunc("/secret", func(http.ResponseWriter, *http.Request) {})
c.HandleFunc("/nonswagger", func(http.ResponseWriter, *http.Request) {})
assert.NotContains(t, c.ListedPaths(), "/secret")
assert.Contains(t, c.ListedPaths(), "/nonswagger")
}
func TestUnregisterHandlers(t *testing.T) {
first := 0
second := 0
c := NewPathRecorderMux("test")
s := httptest.NewServer(c)
defer s.Close()
c.UnlistedHandleFunc("/secret", func(http.ResponseWriter, *http.Request) {})
c.HandleFunc("/nonswagger", func(http.ResponseWriter, *http.Request) {
first = first + 1
})
assert.NotContains(t, c.ListedPaths(), "/secret")
assert.Contains(t, c.ListedPaths(), "/nonswagger")
resp, _ := http.Get(s.URL + "/nonswagger")
assert.Equal(t, first, 1)
assert.Equal(t, resp.StatusCode, http.StatusOK)
c.Unregister("/nonswagger")
assert.NotContains(t, c.ListedPaths(), "/nonswagger")
resp, _ = http.Get(s.URL + "/nonswagger")
assert.Equal(t, first, 1)
assert.Equal(t, resp.StatusCode, http.StatusNotFound)
c.HandleFunc("/nonswagger", func(http.ResponseWriter, *http.Request) {
second = second + 1
})
assert.Contains(t, c.ListedPaths(), "/nonswagger")
resp, _ = http.Get(s.URL + "/nonswagger")
assert.Equal(t, first, 1)
assert.Equal(t, second, 1)
assert.Equal(t, resp.StatusCode, http.StatusOK)
}
func TestPrefixHandlers(t *testing.T) {
c := NewPathRecorderMux("test")
s := httptest.NewServer(c)
defer s.Close()
secretPrefixCount := 0
c.UnlistedHandlePrefix("/secretPrefix/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
secretPrefixCount = secretPrefixCount + 1
}))
publicPrefixCount := 0
c.HandlePrefix("/publicPrefix/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
publicPrefixCount = publicPrefixCount + 1
}))
precisePrefixCount := 0
c.HandlePrefix("/publicPrefix/but-more-precise/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
precisePrefixCount = precisePrefixCount + 1
}))
exactMatchCount := 0
c.Handle("/publicPrefix/exactmatch", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
exactMatchCount = exactMatchCount + 1
}))
slashMatchCount := 0
c.Handle("/otherPublic/exactmatchslash/", http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
slashMatchCount = slashMatchCount + 1
}))
fallThroughCount := 0
c.NotFoundHandler(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
fallThroughCount = fallThroughCount + 1
}))
assert.NotContains(t, c.ListedPaths(), "/secretPrefix/")
assert.Contains(t, c.ListedPaths(), "/publicPrefix/")
resp, _ := http.Get(s.URL + "/fallthrough")
assert.Equal(t, 1, fallThroughCount)
assert.Equal(t, resp.StatusCode, http.StatusOK)
resp, _ = http.Get(s.URL + "/publicPrefix")
assert.Equal(t, 2, fallThroughCount)
assert.Equal(t, resp.StatusCode, http.StatusOK)
http.Get(s.URL + "/publicPrefix/")
assert.Equal(t, 1, publicPrefixCount)
http.Get(s.URL + "/publicPrefix/something")
assert.Equal(t, 2, publicPrefixCount)
http.Get(s.URL + "/publicPrefix/but-more-precise")
assert.Equal(t, 3, publicPrefixCount)
http.Get(s.URL + "/publicPrefix/but-more-precise/")
assert.Equal(t, 1, precisePrefixCount)
http.Get(s.URL + "/publicPrefix/but-more-precise/more-stuff")
assert.Equal(t, 2, precisePrefixCount)
http.Get(s.URL + "/publicPrefix/exactmatch")
assert.Equal(t, 1, exactMatchCount)
http.Get(s.URL + "/publicPrefix/exactmatch/")
assert.Equal(t, 4, publicPrefixCount)
http.Get(s.URL + "/otherPublic/exactmatchslash")
assert.Equal(t, 3, fallThroughCount)
http.Get(s.URL + "/otherPublic/exactmatchslash/")
assert.Equal(t, 1, slashMatchCount)
http.Get(s.URL + "/secretPrefix/")
assert.Equal(t, 1, secretPrefixCount)
http.Get(s.URL + "/secretPrefix/something")
assert.Equal(t, 2, secretPrefixCount)
}

View file

@ -1,102 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["serving_test.go"],
data = glob(["testdata/**"]),
library = ":go_default_library",
deps = [
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"admission.go",
"audit.go",
"authentication.go",
"authorization.go",
"coreapi.go",
"doc.go",
"etcd.go",
"feature.go",
"recommended.go",
"server_run_options.go",
"serving.go",
],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/gopkg.in/natefinch/lumberjack.v2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/audit:go_default_library",
"//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/audit/log:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/audit/webhook:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/authentication/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/authorization/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/util/cert:go_default_library",
],
)
filegroup(
name = "testdata",
srcs = glob(["testdata/*"]),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apiserver/pkg/server/options/encryptionconfig:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -1,15 +0,0 @@
reviewers:
- smarterclayton
- wojtek-t
- deads2k
- liggitt
- nikhiljindal
- sttts
- jlowdermilk
- soltysh
- dims
- cjcullen
- ericchiang
- ping035627
- xiangpengzhao
- enj

View file

@ -1,97 +0,0 @@
/*
Copyright 2017 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 options
import (
"fmt"
"strings"
"github.com/spf13/pflag"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/server"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
)
// AdmissionOptions holds the admission options
type AdmissionOptions struct {
PluginNames []string
ConfigFile string
Plugins *admission.Plugins
}
// NewAdmissionOptions creates a new instance of AdmissionOptions
// Note:
// In addition it calls RegisterAllAdmissionPlugins to register
// all generic admission plugins.
func NewAdmissionOptions() *AdmissionOptions {
options := &AdmissionOptions{
Plugins: &admission.Plugins{},
PluginNames: []string{},
}
server.RegisterAllAdmissionPlugins(options.Plugins)
return options
}
// AddFlags adds flags related to admission for a specific APIServer to the specified FlagSet
func (a *AdmissionOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringSliceVar(&a.PluginNames, "admission-control", a.PluginNames, ""+
"Ordered list of plug-ins to do admission control of resources into cluster. "+
"Comma-delimited list of: "+strings.Join(a.Plugins.Registered(), ", ")+".")
fs.StringVar(&a.ConfigFile, "admission-control-config-file", a.ConfigFile,
"File with admission control configuration.")
}
// ApplyTo adds the admission chain to the server configuration
// the method lazily initializes a generic plugin that is appended to the list of pluginInitializers
// note this method uses:
// genericconfig.LoopbackClientConfig
// genericconfig.SharedInformerFactory
// genericconfig.Authorizer
func (a *AdmissionOptions) ApplyTo(c *server.Config, informers informers.SharedInformerFactory, pluginInitializers ...admission.PluginInitializer) error {
pluginsConfigProvider, err := admission.ReadAdmissionConfiguration(a.PluginNames, a.ConfigFile)
if err != nil {
return fmt.Errorf("failed to read plugin config: %v", err)
}
clientset, err := kubernetes.NewForConfig(c.LoopbackClientConfig)
if err != nil {
return err
}
genericInitializer, err := initializer.New(clientset, informers, c.Authorizer)
if err != nil {
return err
}
initializersChain := admission.PluginInitializers{}
pluginInitializers = append(pluginInitializers, genericInitializer)
initializersChain = append(initializersChain, pluginInitializers...)
admissionChain, err := a.Plugins.NewFromPlugins(a.PluginNames, pluginsConfigProvider, initializersChain)
if err != nil {
return err
}
c.AdmissionControl = admissionChain
return nil
}
func (a *AdmissionOptions) Validate() []error {
errs := []error{}
return errs
}

View file

@ -1,265 +0,0 @@
/*
Copyright 2017 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 options
import (
"fmt"
"io"
"os"
"strings"
"github.com/golang/glog"
"github.com/spf13/pflag"
"gopkg.in/natefinch/lumberjack.v2"
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/audit/policy"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/server"
utilfeature "k8s.io/apiserver/pkg/util/feature"
pluginlog "k8s.io/apiserver/plugin/pkg/audit/log"
pluginwebhook "k8s.io/apiserver/plugin/pkg/audit/webhook"
)
func appendBackend(existing, newBackend audit.Backend) audit.Backend {
if existing == nil {
return newBackend
}
return audit.Union(existing, newBackend)
}
func advancedAuditingEnabled() bool {
return utilfeature.DefaultFeatureGate.Enabled(features.AdvancedAuditing)
}
type AuditOptions struct {
// Policy configuration file for filtering audit events that are captured.
// If unspecified, a default is provided.
PolicyFile string
// Plugin options
LogOptions AuditLogOptions
WebhookOptions AuditWebhookOptions
}
// AuditLogOptions determines the output of the structured audit log by default.
// If the AdvancedAuditing feature is set to false, AuditLogOptions holds the legacy
// audit log writer.
type AuditLogOptions struct {
Path string
MaxAge int
MaxBackups int
MaxSize int
Format string
}
// AuditWebhookOptions control the webhook configuration for audit events.
type AuditWebhookOptions struct {
ConfigFile string
// Should the webhook asynchronous batch events to the webhook backend or
// should the webhook block responses?
//
// Defaults to asynchronous batch events.
Mode string
}
func NewAuditOptions() *AuditOptions {
return &AuditOptions{
WebhookOptions: AuditWebhookOptions{Mode: pluginwebhook.ModeBatch},
LogOptions: AuditLogOptions{Format: pluginlog.FormatJson},
}
}
// Validate checks invalid config combination
func (o *AuditOptions) Validate() []error {
if o == nil {
return nil
}
allErrors := []error{}
if !advancedAuditingEnabled() {
if len(o.PolicyFile) > 0 {
allErrors = append(allErrors, fmt.Errorf("feature '%s' must be enabled to set option --audit-policy-file", features.AdvancedAuditing))
}
if len(o.WebhookOptions.ConfigFile) > 0 {
allErrors = append(allErrors, fmt.Errorf("feature '%s' must be enabled to set option --audit-webhook-config-file", features.AdvancedAuditing))
}
} else {
// check webhook mode
validMode := false
for _, m := range pluginwebhook.AllowedModes {
if m == o.WebhookOptions.Mode {
validMode = true
break
}
}
if !validMode {
allErrors = append(allErrors, fmt.Errorf("invalid audit webhook mode %s, allowed modes are %q", o.WebhookOptions.Mode, strings.Join(pluginwebhook.AllowedModes, ",")))
}
// check log format
validFormat := false
for _, f := range pluginlog.AllowedFormats {
if f == o.LogOptions.Format {
validFormat = true
break
}
}
if !validFormat {
allErrors = append(allErrors, fmt.Errorf("invalid audit log format %s, allowed formats are %q", o.LogOptions.Format, strings.Join(pluginlog.AllowedFormats, ",")))
}
}
// Check validities of MaxAge, MaxBackups and MaxSize of log options
if o.LogOptions.MaxAge < 0 {
allErrors = append(allErrors, fmt.Errorf("--audit-log-maxage %v can't be a negative number", o.LogOptions.MaxAge))
}
if o.LogOptions.MaxBackups < 0 {
allErrors = append(allErrors, fmt.Errorf("--audit-log-maxbackup %v can't be a negative number", o.LogOptions.MaxBackups))
}
if o.LogOptions.MaxSize < 0 {
allErrors = append(allErrors, fmt.Errorf("--audit-log-maxsize %v can't be a negative number", o.LogOptions.MaxSize))
}
return allErrors
}
func (o *AuditOptions) AddFlags(fs *pflag.FlagSet) {
if o == nil {
return
}
fs.StringVar(&o.PolicyFile, "audit-policy-file", o.PolicyFile,
"Path to the file that defines the audit policy configuration. Requires the 'AdvancedAuditing' feature gate."+
" With AdvancedAuditing, a profile is required to enable auditing.")
o.LogOptions.AddFlags(fs)
o.WebhookOptions.AddFlags(fs)
}
func (o *AuditOptions) ApplyTo(c *server.Config) error {
if o == nil {
return nil
}
// Apply legacy audit options if advanced audit is not enabled.
if !advancedAuditingEnabled() {
return o.LogOptions.legacyApplyTo(c)
}
// Apply advanced options if advanced audit is enabled.
// 1. Apply generic options.
if err := o.applyTo(c); err != nil {
return err
}
// 2. Apply plugin options.
if err := o.LogOptions.advancedApplyTo(c); err != nil {
return err
}
if err := o.WebhookOptions.applyTo(c); err != nil {
return err
}
if c.AuditBackend != nil && c.AuditPolicyChecker == nil {
glog.V(2).Info("No audit policy file provided for AdvancedAuditing, no events will be recorded.")
}
return nil
}
func (o *AuditOptions) applyTo(c *server.Config) error {
if o.PolicyFile == "" {
return nil
}
p, err := policy.LoadPolicyFromFile(o.PolicyFile)
if err != nil {
return fmt.Errorf("loading audit policy file: %v", err)
}
c.AuditPolicyChecker = policy.NewChecker(p)
return nil
}
func (o *AuditLogOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.Path, "audit-log-path", o.Path,
"If set, all requests coming to the apiserver will be logged to this file. '-' means standard out.")
fs.IntVar(&o.MaxAge, "audit-log-maxage", o.MaxBackups,
"The maximum number of days to retain old audit log files based on the timestamp encoded in their filename.")
fs.IntVar(&o.MaxBackups, "audit-log-maxbackup", o.MaxBackups,
"The maximum number of old audit log files to retain.")
fs.IntVar(&o.MaxSize, "audit-log-maxsize", o.MaxSize,
"The maximum size in megabytes of the audit log file before it gets rotated.")
fs.StringVar(&o.Format, "audit-log-format", o.Format,
"Format of saved audits. \"legacy\" indicates 1-line text format for each event."+
" \"json\" indicates structured json format. Requires the 'AdvancedAuditing' feature"+
" gate. Known formats are "+strings.Join(pluginlog.AllowedFormats, ",")+".")
}
func (o *AuditLogOptions) getWriter() io.Writer {
if o.Path == "" {
return nil
}
var w io.Writer = os.Stdout
if o.Path != "-" {
w = &lumberjack.Logger{
Filename: o.Path,
MaxAge: o.MaxAge,
MaxBackups: o.MaxBackups,
MaxSize: o.MaxSize,
}
}
return w
}
func (o *AuditLogOptions) advancedApplyTo(c *server.Config) error {
if w := o.getWriter(); w != nil {
c.AuditBackend = appendBackend(c.AuditBackend, pluginlog.NewBackend(w, o.Format, auditv1beta1.SchemeGroupVersion))
}
return nil
}
func (o *AuditLogOptions) legacyApplyTo(c *server.Config) error {
c.LegacyAuditWriter = o.getWriter()
return nil
}
func (o *AuditWebhookOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&o.ConfigFile, "audit-webhook-config-file", o.ConfigFile,
"Path to a kubeconfig formatted file that defines the audit webhook configuration."+
" Requires the 'AdvancedAuditing' feature gate.")
fs.StringVar(&o.Mode, "audit-webhook-mode", o.Mode,
"Strategy for sending audit events. Blocking indicates sending events should block"+
" server responses. Batch causes the webhook to buffer and send events"+
" asynchronously. Known modes are "+strings.Join(pluginwebhook.AllowedModes, ",")+".")
}
func (o *AuditWebhookOptions) applyTo(c *server.Config) error {
if o.ConfigFile == "" {
return nil
}
webhook, err := pluginwebhook.NewBackend(o.ConfigFile, o.Mode, auditv1beta1.SchemeGroupVersion)
if err != nil {
return fmt.Errorf("initializing audit webhook: %v", err)
}
c.AuditBackend = appendBackend(c.AuditBackend, webhook)
return nil
}

View file

@ -1,388 +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 options
import (
"encoding/json"
"fmt"
"io/ioutil"
"time"
"github.com/golang/glog"
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
"k8s.io/apiserver/pkg/server"
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
type RequestHeaderAuthenticationOptions struct {
UsernameHeaders []string
GroupHeaders []string
ExtraHeaderPrefixes []string
ClientCAFile string
AllowedNames []string
}
func (s *RequestHeaderAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
if s == nil {
return
}
fs.StringSliceVar(&s.UsernameHeaders, "requestheader-username-headers", s.UsernameHeaders, ""+
"List of request headers to inspect for usernames. X-Remote-User is common.")
fs.StringSliceVar(&s.GroupHeaders, "requestheader-group-headers", s.GroupHeaders, ""+
"List of request headers to inspect for groups. X-Remote-Group is suggested.")
fs.StringSliceVar(&s.ExtraHeaderPrefixes, "requestheader-extra-headers-prefix", s.ExtraHeaderPrefixes, ""+
"List of request header prefixes to inspect. X-Remote-Extra- is suggested.")
fs.StringVar(&s.ClientCAFile, "requestheader-client-ca-file", s.ClientCAFile, ""+
"Root certificate bundle to use to verify client certificates on incoming requests "+
"before trusting usernames in headers specified by --requestheader-username-headers")
fs.StringSliceVar(&s.AllowedNames, "requestheader-allowed-names", s.AllowedNames, ""+
"List of client certificate common names to allow to provide usernames in headers "+
"specified by --requestheader-username-headers. If empty, any client certificate validated "+
"by the authorities in --requestheader-client-ca-file is allowed.")
}
// ToAuthenticationRequestHeaderConfig returns a RequestHeaderConfig config object for these options
// if necessary, nil otherwise.
func (s *RequestHeaderAuthenticationOptions) ToAuthenticationRequestHeaderConfig() *authenticatorfactory.RequestHeaderConfig {
if len(s.ClientCAFile) == 0 {
return nil
}
return &authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: s.UsernameHeaders,
GroupHeaders: s.GroupHeaders,
ExtraHeaderPrefixes: s.ExtraHeaderPrefixes,
ClientCA: s.ClientCAFile,
AllowedClientNames: s.AllowedNames,
}
}
type ClientCertAuthenticationOptions struct {
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
ClientCA string
}
func (s *ClientCertAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.ClientCA, "client-ca-file", s.ClientCA, ""+
"If set, any request presenting a client certificate signed by one of "+
"the authorities in the client-ca-file is authenticated with an identity "+
"corresponding to the CommonName of the client certificate.")
}
// DelegatingAuthenticationOptions provides an easy way for composing API servers to delegate their authentication to
// the root kube API server. The API federator will act as
// a front proxy and direction connections will be able to delegate to the core kube API server
type DelegatingAuthenticationOptions struct {
// RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the
// TokenAccessReview.authentication.k8s.io endpoint for checking tokens.
RemoteKubeConfigFile string
// CacheTTL is the length of time that a token authentication answer will be cached.
CacheTTL time.Duration
ClientCert ClientCertAuthenticationOptions
RequestHeader RequestHeaderAuthenticationOptions
SkipInClusterLookup bool
}
func NewDelegatingAuthenticationOptions() *DelegatingAuthenticationOptions {
return &DelegatingAuthenticationOptions{
// very low for responsiveness, but high enough to handle storms
CacheTTL: 10 * time.Second,
ClientCert: ClientCertAuthenticationOptions{},
RequestHeader: RequestHeaderAuthenticationOptions{
UsernameHeaders: []string{"x-remote-user"},
GroupHeaders: []string{"x-remote-group"},
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
},
}
}
func (s *DelegatingAuthenticationOptions) Validate() []error {
allErrors := []error{}
return allErrors
}
func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.RemoteKubeConfigFile, "authentication-kubeconfig", s.RemoteKubeConfigFile, ""+
"kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+
"tokenaccessreviews.authentication.k8s.io.")
fs.DurationVar(&s.CacheTTL, "authentication-token-webhook-cache-ttl", s.CacheTTL,
"The duration to cache responses from the webhook token authenticator.")
s.ClientCert.AddFlags(fs)
s.RequestHeader.AddFlags(fs)
fs.BoolVar(&s.SkipInClusterLookup, "authentication-skip-lookup", s.SkipInClusterLookup, ""+
"If false, the authentication-kubeconfig will be used to lookup missing authentication "+
"configuration from the cluster.")
}
func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.Config) error {
if s == nil {
c.Authenticator = nil
return nil
}
clientCA, err := s.getClientCA()
if err != nil {
return err
}
c, err = c.ApplyClientCert(clientCA.ClientCA)
if err != nil {
return fmt.Errorf("unable to load client CA file: %v", err)
}
requestHeader, err := s.getRequestHeader()
if err != nil {
return err
}
c, err = c.ApplyClientCert(requestHeader.ClientCAFile)
if err != nil {
return fmt.Errorf("unable to load client CA file: %v", err)
}
cfg, err := s.ToAuthenticationConfig()
if err != nil {
return err
}
authenticator, securityDefinitions, err := cfg.New()
if err != nil {
return err
}
c.Authenticator = authenticator
if c.OpenAPIConfig != nil {
c.OpenAPIConfig.SecurityDefinitions = securityDefinitions
}
c.SupportsBasicAuth = false
return nil
}
func (s *DelegatingAuthenticationOptions) ToAuthenticationConfig() (authenticatorfactory.DelegatingAuthenticatorConfig, error) {
tokenClient, err := s.newTokenAccessReview()
if err != nil {
return authenticatorfactory.DelegatingAuthenticatorConfig{}, err
}
clientCA, err := s.getClientCA()
if err != nil {
return authenticatorfactory.DelegatingAuthenticatorConfig{}, err
}
requestHeader, err := s.getRequestHeader()
if err != nil {
return authenticatorfactory.DelegatingAuthenticatorConfig{}, err
}
ret := authenticatorfactory.DelegatingAuthenticatorConfig{
Anonymous: true,
TokenAccessReviewClient: tokenClient,
CacheTTL: s.CacheTTL,
ClientCAFile: clientCA.ClientCA,
RequestHeaderConfig: requestHeader.ToAuthenticationRequestHeaderConfig(),
}
return ret, nil
}
const (
authenticationConfigMapNamespace = metav1.NamespaceSystem
authenticationConfigMapName = "extension-apiserver-authentication"
authenticationRoleName = "extension-apiserver-authentication-reader"
)
func (s *DelegatingAuthenticationOptions) getClientCA() (*ClientCertAuthenticationOptions, error) {
if len(s.ClientCert.ClientCA) > 0 || s.SkipInClusterLookup {
return &s.ClientCert, nil
}
incluster, err := s.lookupInClusterClientCA()
if err != nil {
glog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+
"'kubectl create rolebinding -n %s ROLE_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'",
authenticationConfigMapName, authenticationConfigMapNamespace, authenticationConfigMapNamespace, authenticationRoleName)
return nil, err
}
if incluster == nil {
return nil, fmt.Errorf("cluster doesn't provide client-ca-file")
}
return incluster, nil
}
func (s *DelegatingAuthenticationOptions) getRequestHeader() (*RequestHeaderAuthenticationOptions, error) {
if len(s.RequestHeader.ClientCAFile) > 0 || s.SkipInClusterLookup {
return &s.RequestHeader, nil
}
incluster, err := s.lookupInClusterRequestHeader()
if err != nil {
glog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+
"'kubectl create rolebinding -n %s ROLE_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'",
authenticationConfigMapName, authenticationConfigMapNamespace, authenticationConfigMapNamespace, authenticationRoleName)
return nil, err
}
if incluster == nil {
return nil, fmt.Errorf("cluster doesn't provide requestheader-client-ca-file")
}
return incluster, nil
}
func (s *DelegatingAuthenticationOptions) lookupInClusterClientCA() (*ClientCertAuthenticationOptions, error) {
clientConfig, err := s.getClientConfig()
if err != nil {
return nil, err
}
client, err := coreclient.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
authConfigMap, err := client.ConfigMaps(authenticationConfigMapNamespace).Get(authenticationConfigMapName, metav1.GetOptions{})
if err != nil {
return nil, err
}
clientCA, ok := authConfigMap.Data["client-ca-file"]
if !ok {
return nil, nil
}
f, err := ioutil.TempFile("", "client-ca-file")
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(f.Name(), []byte(clientCA), 0600); err != nil {
return nil, err
}
return &ClientCertAuthenticationOptions{ClientCA: f.Name()}, nil
}
func (s *DelegatingAuthenticationOptions) lookupInClusterRequestHeader() (*RequestHeaderAuthenticationOptions, error) {
clientConfig, err := s.getClientConfig()
if err != nil {
return nil, err
}
client, err := coreclient.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
authConfigMap, err := client.ConfigMaps(authenticationConfigMapNamespace).Get(authenticationConfigMapName, metav1.GetOptions{})
if err != nil {
return nil, err
}
requestHeaderCA, ok := authConfigMap.Data["requestheader-client-ca-file"]
if !ok {
return nil, nil
}
f, err := ioutil.TempFile("", "requestheader-client-ca-file")
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(f.Name(), []byte(requestHeaderCA), 0600); err != nil {
return nil, err
}
usernameHeaders, err := deserializeStrings(authConfigMap.Data["requestheader-username-headers"])
if err != nil {
return nil, err
}
groupHeaders, err := deserializeStrings(authConfigMap.Data["requestheader-group-headers"])
if err != nil {
return nil, err
}
extraHeaderPrefixes, err := deserializeStrings(authConfigMap.Data["requestheader-extra-headers-prefix"])
if err != nil {
return nil, err
}
allowedNames, err := deserializeStrings(authConfigMap.Data["requestheader-allowed-names"])
if err != nil {
return nil, err
}
return &RequestHeaderAuthenticationOptions{
UsernameHeaders: usernameHeaders,
GroupHeaders: groupHeaders,
ExtraHeaderPrefixes: extraHeaderPrefixes,
ClientCAFile: f.Name(),
AllowedNames: allowedNames,
}, nil
}
func deserializeStrings(in string) ([]string, error) {
if len(in) == 0 {
return nil, nil
}
var ret []string
if err := json.Unmarshal([]byte(in), &ret); err != nil {
return nil, err
}
return ret, nil
}
func (s *DelegatingAuthenticationOptions) getClientConfig() (*rest.Config, error) {
var clientConfig *rest.Config
var err error
if len(s.RemoteKubeConfigFile) > 0 {
loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: s.RemoteKubeConfigFile}
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
clientConfig, err = loader.ClientConfig()
} else {
// without the remote kubeconfig file, try to use the in-cluster config. Most addon API servers will
// use this path
clientConfig, err = rest.InClusterConfig()
}
if err != nil {
return nil, err
}
// set high qps/burst limits since this will effectively limit API server responsiveness
clientConfig.QPS = 200
clientConfig.Burst = 400
return clientConfig, nil
}
func (s *DelegatingAuthenticationOptions) newTokenAccessReview() (authenticationclient.TokenReviewInterface, error) {
clientConfig, err := s.getClientConfig()
if err != nil {
return nil, err
}
client, err := authenticationclient.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
return client.TokenReviews(), nil
}

View file

@ -1,138 +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 options
import (
"time"
"github.com/spf13/pflag"
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
"k8s.io/apiserver/pkg/server"
authorizationclient "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// DelegatingAuthorizationOptions provides an easy way for composing API servers to delegate their authorization to
// the root kube API server
type DelegatingAuthorizationOptions struct {
// RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the
// SubjectAccessReview.authorization.k8s.io endpoint for checking tokens.
RemoteKubeConfigFile string
// AllowCacheTTL is the length of time that a successful authorization response will be cached
AllowCacheTTL time.Duration
// DenyCacheTTL is the length of time that an unsuccessful authorization response will be cached.
// You generally want more responsive, "deny, try again" flows.
DenyCacheTTL time.Duration
}
func NewDelegatingAuthorizationOptions() *DelegatingAuthorizationOptions {
return &DelegatingAuthorizationOptions{
// very low for responsiveness, but high enough to handle storms
AllowCacheTTL: 10 * time.Second,
DenyCacheTTL: 10 * time.Second,
}
}
func (s *DelegatingAuthorizationOptions) Validate() []error {
allErrors := []error{}
return allErrors
}
func (s *DelegatingAuthorizationOptions) AddFlags(fs *pflag.FlagSet) {
if s == nil {
return
}
fs.StringVar(&s.RemoteKubeConfigFile, "authorization-kubeconfig", s.RemoteKubeConfigFile, ""+
"kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+
" subjectaccessreviews.authorization.k8s.io.")
fs.DurationVar(&s.AllowCacheTTL, "authorization-webhook-cache-authorized-ttl",
s.AllowCacheTTL,
"The duration to cache 'authorized' responses from the webhook authorizer.")
fs.DurationVar(&s.DenyCacheTTL,
"authorization-webhook-cache-unauthorized-ttl", s.DenyCacheTTL,
"The duration to cache 'unauthorized' responses from the webhook authorizer.")
}
func (s *DelegatingAuthorizationOptions) ApplyTo(c *server.Config) error {
if s == nil {
c.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
return nil
}
cfg, err := s.ToAuthorizationConfig()
if err != nil {
return err
}
authorizer, err := cfg.New()
if err != nil {
return err
}
c.Authorizer = authorizer
return nil
}
func (s *DelegatingAuthorizationOptions) ToAuthorizationConfig() (authorizerfactory.DelegatingAuthorizerConfig, error) {
sarClient, err := s.newSubjectAccessReview()
if err != nil {
return authorizerfactory.DelegatingAuthorizerConfig{}, err
}
ret := authorizerfactory.DelegatingAuthorizerConfig{
SubjectAccessReviewClient: sarClient,
AllowCacheTTL: s.AllowCacheTTL,
DenyCacheTTL: s.DenyCacheTTL,
}
return ret, nil
}
func (s *DelegatingAuthorizationOptions) newSubjectAccessReview() (authorizationclient.SubjectAccessReviewInterface, error) {
var clientConfig *rest.Config
var err error
if len(s.RemoteKubeConfigFile) > 0 {
loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: s.RemoteKubeConfigFile}
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
clientConfig, err = loader.ClientConfig()
} else {
// without the remote kubeconfig file, try to use the in-cluster config. Most addon API servers will
// use this path
clientConfig, err = rest.InClusterConfig()
}
if err != nil {
return nil, err
}
// set high qps/burst limits since this will effectively limit API server responsiveness
clientConfig.QPS = 200
clientConfig.Burst = 400
client, err := authorizationclient.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
return client.SubjectAccessReviews(), nil
}

View file

@ -1,83 +0,0 @@
/*
Copyright 2017 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 options
import (
"fmt"
"time"
"github.com/spf13/pflag"
"k8s.io/apiserver/pkg/server"
clientgoinformers "k8s.io/client-go/informers"
clientgoclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// CoreAPIOptions contains options to configure the connection to a core API Kubernetes apiserver.
type CoreAPIOptions struct {
// CoreAPIKubeconfigPath is a filename for a kubeconfig file to contact the core API server with.
// If it is not set, the in cluster config is used.
CoreAPIKubeconfigPath string
}
func NewCoreAPIOptions() *CoreAPIOptions {
return &CoreAPIOptions{}
}
func (o *CoreAPIOptions) AddFlags(fs *pflag.FlagSet) {
if o == nil {
return
}
fs.StringVar(&o.CoreAPIKubeconfigPath, "kubeconfig", o.CoreAPIKubeconfigPath,
"kubeconfig file pointing at the 'core' kubernetes server.")
}
func (o *CoreAPIOptions) ApplyTo(config *server.RecommendedConfig) error {
if o == nil {
return nil
}
// create shared informer for Kubernetes APIs
var kubeconfig *rest.Config
var err error
if len(o.CoreAPIKubeconfigPath) > 0 {
loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: o.CoreAPIKubeconfigPath}
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
kubeconfig, err = loader.ClientConfig()
if err != nil {
return fmt.Errorf("failed to load kubeconfig at %q: %v", o.CoreAPIKubeconfigPath, err)
}
} else {
kubeconfig, err = rest.InClusterConfig()
if err != nil {
return err
}
}
clientgoExternalClient, err := clientgoclientset.NewForConfig(kubeconfig)
if err != nil {
return fmt.Errorf("failed to create Kubernetes clientset: %v", err)
}
config.SharedInformerFactory = clientgoinformers.NewSharedInformerFactory(clientgoExternalClient, 10*time.Minute)
return nil
}
func (o *CoreAPIOptions) Validate() []error {
return nil
}

View file

@ -1,21 +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 options is the public flags and options used by a generic api
// server. It takes a minimal set of dependencies and does not reference
// implementations, in order to ensure it may be reused by multiple components
// (such as CLI commands that wish to generate or validate config).
package options // import "k8s.io/apiserver/pkg/server/options"

View file

@ -1,50 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"config.go",
"plugins.go",
"types.go",
],
deps = [
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/identity:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/secretbox:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["encryptionconfig_test.go"],
library = ":go_default_library",
deps = [
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/envelope:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -1,295 +0,0 @@
/*
Copyright 2017 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 encryptionconfig
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"os"
yaml "github.com/ghodss/yaml"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/storage/value"
aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope"
"k8s.io/apiserver/pkg/storage/value/encrypt/identity"
"k8s.io/apiserver/pkg/storage/value/encrypt/secretbox"
)
const (
aesCBCTransformerPrefixV1 = "k8s:enc:aescbc:v1:"
aesGCMTransformerPrefixV1 = "k8s:enc:aesgcm:v1:"
secretboxTransformerPrefixV1 = "k8s:enc:secretbox:v1:"
kmsTransformerPrefixV1 = "k8s:enc:kms:v1:"
)
// GetTransformerOverrides returns the transformer overrides by reading and parsing the encryption provider configuration file
func GetTransformerOverrides(filepath string) (map[schema.GroupResource]value.Transformer, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, fmt.Errorf("error opening encryption provider configuration file %q: %v", filepath, err)
}
defer f.Close()
result, err := ParseEncryptionConfiguration(f)
if err != nil {
return nil, fmt.Errorf("error while parsing encryption provider configuration file %q: %v", filepath, err)
}
return result, nil
}
// ParseEncryptionConfiguration parses configuration data and returns the transformer overrides
func ParseEncryptionConfiguration(f io.Reader) (map[schema.GroupResource]value.Transformer, error) {
configFileContents, err := ioutil.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("could not read contents: %v", err)
}
var config EncryptionConfig
err = yaml.Unmarshal(configFileContents, &config)
if err != nil {
return nil, fmt.Errorf("error while parsing file: %v", err)
}
if config.Kind == "" {
return nil, fmt.Errorf("invalid configuration file, missing Kind")
}
if config.Kind != "EncryptionConfig" {
return nil, fmt.Errorf("invalid configuration kind %q provided", config.Kind)
}
// TODO config.APIVersion is unchecked
resourceToPrefixTransformer := map[schema.GroupResource][]value.PrefixTransformer{}
// For each entry in the configuration
for _, resourceConfig := range config.Resources {
transformers, err := GetPrefixTransformers(&resourceConfig)
if err != nil {
return nil, err
}
// For each resource, create a list of providers to use
for _, resource := range resourceConfig.Resources {
gr := schema.ParseGroupResource(resource)
resourceToPrefixTransformer[gr] = append(
resourceToPrefixTransformer[gr], transformers...)
}
}
result := map[schema.GroupResource]value.Transformer{}
for gr, transList := range resourceToPrefixTransformer {
result[gr] = value.NewMutableTransformer(value.NewPrefixTransformers(fmt.Errorf("no matching prefix found"), transList...))
}
return result, nil
}
// GetPrefixTransformers constructs and returns the appropriate prefix transformers for the passed resource using its configuration
func GetPrefixTransformers(config *ResourceConfig) ([]value.PrefixTransformer, error) {
var result []value.PrefixTransformer
for _, provider := range config.Providers {
found := false
var transformer value.PrefixTransformer
var err error
if provider.AESGCM != nil {
transformer, err = GetAESPrefixTransformer(provider.AESGCM, aestransformer.NewGCMTransformer, aesGCMTransformerPrefixV1)
if err != nil {
return result, err
}
found = true
}
if provider.AESCBC != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
transformer, err = GetAESPrefixTransformer(provider.AESCBC, aestransformer.NewCBCTransformer, aesCBCTransformerPrefixV1)
found = true
}
if provider.Secretbox != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
transformer, err = GetSecretboxPrefixTransformer(provider.Secretbox)
found = true
}
if provider.Identity != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
transformer = value.PrefixTransformer{
Transformer: identity.NewEncryptCheckTransformer(),
Prefix: []byte{},
}
found = true
}
if provider.KMS != nil {
if found == true {
return nil, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
f, err := os.Open(provider.KMS.ConfigFile)
if err != nil {
return nil, fmt.Errorf("error opening KMS provider configuration file %q: %v", provider.KMS.ConfigFile, err)
}
defer f.Close()
envelopeService, pluginFound, err := KMSPluginRegistry.getPlugin(provider.KMS.Name, f)
if err != nil {
return nil, fmt.Errorf("could not configure KMS plugin %q, %v", provider.KMS.Name, err)
}
if pluginFound == false {
return nil, fmt.Errorf("KMS plugin %q not found", provider.KMS.Name)
}
transformer, err = getEnvelopePrefixTransformer(provider.KMS, envelopeService, kmsTransformerPrefixV1)
found = true
}
if err != nil {
return result, err
}
result = append(result, transformer)
if found == false {
return result, fmt.Errorf("invalid provider configuration: at least one provider must be specified")
}
}
return result, nil
}
// BlockTransformerFunc takes an AES cipher block and returns a value transformer.
type BlockTransformerFunc func(cipher.Block) value.Transformer
// GetAESPrefixTransformer returns a prefix transformer from the provided configuration.
// Returns an AES transformer based on the provided prefix and block transformer.
func GetAESPrefixTransformer(config *AESConfig, fn BlockTransformerFunc, prefix string) (value.PrefixTransformer, error) {
var result value.PrefixTransformer
if len(config.Keys) == 0 {
return result, fmt.Errorf("aes provider has no valid keys")
}
for _, key := range config.Keys {
if key.Name == "" {
return result, fmt.Errorf("key with invalid name provided")
}
if key.Secret == "" {
return result, fmt.Errorf("key %v has no provided secret", key.Name)
}
}
keyTransformers := []value.PrefixTransformer{}
for _, keyData := range config.Keys {
key, err := base64.StdEncoding.DecodeString(keyData.Secret)
if err != nil {
return result, fmt.Errorf("could not obtain secret for named key %s: %s", keyData.Name, err)
}
block, err := aes.NewCipher(key)
if err != nil {
return result, fmt.Errorf("error while creating cipher for named key %s: %s", keyData.Name, err)
}
// Create a new PrefixTransformer for this key
keyTransformers = append(keyTransformers,
value.PrefixTransformer{
Transformer: fn(block),
Prefix: []byte(keyData.Name + ":"),
})
}
// Create a prefixTransformer which can choose between these keys
keyTransformer := value.NewPrefixTransformers(
fmt.Errorf("no matching key was found for the provided AES transformer"), keyTransformers...)
// Create a PrefixTransformer which shall later be put in a list with other providers
result = value.PrefixTransformer{
Transformer: keyTransformer,
Prefix: []byte(prefix),
}
return result, nil
}
// GetSecretboxPrefixTransformer returns a prefix transformer from the provided configuration
func GetSecretboxPrefixTransformer(config *SecretboxConfig) (value.PrefixTransformer, error) {
var result value.PrefixTransformer
if len(config.Keys) == 0 {
return result, fmt.Errorf("secretbox provider has no valid keys")
}
for _, key := range config.Keys {
if key.Name == "" {
return result, fmt.Errorf("key with invalid name provided")
}
if key.Secret == "" {
return result, fmt.Errorf("key %v has no provided secret", key.Name)
}
}
keyTransformers := []value.PrefixTransformer{}
for _, keyData := range config.Keys {
key, err := base64.StdEncoding.DecodeString(keyData.Secret)
if err != nil {
return result, fmt.Errorf("could not obtain secret for named key %s: %s", keyData.Name, err)
}
if len(key) != 32 {
return result, fmt.Errorf("expected key size 32 for secretbox provider, got %v", len(key))
}
keyArray := [32]byte{}
copy(keyArray[:], key)
// Create a new PrefixTransformer for this key
keyTransformers = append(keyTransformers,
value.PrefixTransformer{
Transformer: secretbox.NewSecretboxTransformer(keyArray),
Prefix: []byte(keyData.Name + ":"),
})
}
// Create a prefixTransformer which can choose between these keys
keyTransformer := value.NewPrefixTransformers(
fmt.Errorf("no matching key was found for the provided Secretbox transformer"), keyTransformers...)
// Create a PrefixTransformer which shall later be put in a list with other providers
result = value.PrefixTransformer{
Transformer: keyTransformer,
Prefix: []byte(secretboxTransformerPrefixV1),
}
return result, nil
}
// getEnvelopePrefixTransformer returns a prefix transformer from the provided config.
// envelopeService is used as the root of trust.
func getEnvelopePrefixTransformer(config *KMSConfig, envelopeService envelope.Service, prefix string) (value.PrefixTransformer, error) {
envelopeTransformer, err := envelope.NewEnvelopeTransformer(envelopeService, config.CacheSize, aestransformer.NewCBCTransformer)
if err != nil {
return value.PrefixTransformer{}, err
}
return value.PrefixTransformer{
Transformer: envelopeTransformer,
Prefix: []byte(prefix + config.Name + ":"),
}, nil
}

View file

@ -1,339 +0,0 @@
/*
Copyright 2017 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 encryptionconfig
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"os"
"strings"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/storage/value"
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope"
)
const (
sampleText = "abcdefghijklmnopqrstuvwxyz"
sampleContextText = "0123456789"
// Modify these in all configurations if changed
testEnvelopeServiceConfigPath = "testproviderconfig"
testEnvelopeServiceProviderName = "testprovider"
correctConfigWithIdentityFirst = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
- namespaces
providers:
- identity: {}
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- kms:
name: testprovider
configfile: testproviderconfig
cachesize: 10
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
`
correctConfigWithAesGcmFirst = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- kms:
name: testprovider
configfile: testproviderconfig
cachesize: 10
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- identity: {}
`
correctConfigWithAesCbcFirst = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- kms:
name: testprovider
configfile: testproviderconfig
cachesize: 10
- identity: {}
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
`
correctConfigWithSecretboxFirst = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- kms:
name: testprovider
configfile: testproviderconfig
cachesize: 10
- identity: {}
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
`
correctConfigWithKMSFirst = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- kms:
name: testprovider
configfile: testproviderconfig
cachesize: 10
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- identity: {}
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
`
incorrectConfigNoSecretForKey = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- namespaces
- secrets
providers:
- aesgcm:
keys:
- name: key1
`
incorrectConfigInvalidKey = `
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- namespaces
- secrets
providers:
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: YSBzZWNyZXQgYSBzZWNyZXQ=
`
)
// testEnvelopeService is a mock envelope service which can be used to simulate remote Envelope services
// for testing of the envelope transformer with other transformers.
type testEnvelopeService struct {
disabled bool
}
func (t *testEnvelopeService) Decrypt(data string) ([]byte, error) {
if t.disabled {
return nil, fmt.Errorf("Envelope service was disabled")
}
return base64.StdEncoding.DecodeString(data)
}
func (t *testEnvelopeService) Encrypt(data []byte) (string, error) {
if t.disabled {
return "", fmt.Errorf("Envelope service was disabled")
}
return base64.StdEncoding.EncodeToString(data), nil
}
func (t *testEnvelopeService) SetDisabledStatus(status bool) {
t.disabled = status
}
var _ envelope.Service = &testEnvelopeService{}
func TestEncryptionProviderConfigCorrect(t *testing.T) {
os.OpenFile(testEnvelopeServiceConfigPath, os.O_CREATE, 0666)
defer os.Remove(testEnvelopeServiceConfigPath)
KMSPluginRegistry.Register(testEnvelopeServiceProviderName, func(_ io.Reader) (envelope.Service, error) {
return &testEnvelopeService{}, nil
})
// Creates compound/prefix transformers with different ordering of available transformers.
// Transforms data using one of them, and tries to untransform using the others.
// Repeats this for all possible combinations.
identityFirstTransformerOverrides, err := ParseEncryptionConfiguration(strings.NewReader(correctConfigWithIdentityFirst))
if err != nil {
t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithIdentityFirst)
}
aesGcmFirstTransformerOverrides, err := ParseEncryptionConfiguration(strings.NewReader(correctConfigWithAesGcmFirst))
if err != nil {
t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithAesGcmFirst)
}
aesCbcFirstTransformerOverrides, err := ParseEncryptionConfiguration(strings.NewReader(correctConfigWithAesCbcFirst))
if err != nil {
t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithAesCbcFirst)
}
secretboxFirstTransformerOverrides, err := ParseEncryptionConfiguration(strings.NewReader(correctConfigWithSecretboxFirst))
if err != nil {
t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithSecretboxFirst)
}
kmsFirstTransformerOverrides, err := ParseEncryptionConfiguration(strings.NewReader(correctConfigWithKMSFirst))
if err != nil {
t.Fatalf("error while parsing configuration file: %s.\nThe file was:\n%s", err, correctConfigWithKMSFirst)
}
// Pick the transformer for any of the returned resources.
identityFirstTransformer := identityFirstTransformerOverrides[schema.ParseGroupResource("secrets")]
aesGcmFirstTransformer := aesGcmFirstTransformerOverrides[schema.ParseGroupResource("secrets")]
aesCbcFirstTransformer := aesCbcFirstTransformerOverrides[schema.ParseGroupResource("secrets")]
secretboxFirstTransformer := secretboxFirstTransformerOverrides[schema.ParseGroupResource("secrets")]
kmsFirstTransformer := kmsFirstTransformerOverrides[schema.ParseGroupResource("secrets")]
context := value.DefaultContext([]byte(sampleContextText))
originalText := []byte(sampleText)
transformers := []struct {
Transformer value.Transformer
Name string
}{
{aesGcmFirstTransformer, "aesGcmFirst"},
{aesCbcFirstTransformer, "aesCbcFirst"},
{secretboxFirstTransformer, "secretboxFirst"},
{identityFirstTransformer, "identityFirst"},
{kmsFirstTransformer, "kmsFirst"},
}
for _, testCase := range transformers {
transformedData, err := testCase.Transformer.TransformToStorage(originalText, context)
if err != nil {
t.Fatalf("%s: error while transforming data to storage: %s", testCase.Name, err)
}
for _, transformer := range transformers {
untransformedData, stale, err := transformer.Transformer.TransformFromStorage(transformedData, context)
if err != nil {
t.Fatalf("%s: error while reading using %s transformer: %s", testCase.Name, transformer.Name, err)
}
if stale != (transformer.Name != testCase.Name) {
t.Fatalf("%s: wrong stale information on reading using %s transformer, should be %v", testCase.Name, transformer.Name, testCase.Name == transformer.Name)
}
if bytes.Compare(untransformedData, originalText) != 0 {
t.Fatalf("%s: %s transformer transformed data incorrectly. Expected: %v, got %v", testCase.Name, transformer.Name, originalText, untransformedData)
}
}
}
}
// Throw error if key has no secret
func TestEncryptionProviderConfigNoSecretForKey(t *testing.T) {
if _, err := ParseEncryptionConfiguration(strings.NewReader(incorrectConfigNoSecretForKey)); err == nil {
t.Fatalf("invalid configuration file (one key has no secret) got parsed:\n%s", incorrectConfigNoSecretForKey)
}
}
// Throw error if invalid key for AES
func TestEncryptionProviderConfigInvalidKey(t *testing.T) {
if _, err := ParseEncryptionConfiguration(strings.NewReader(incorrectConfigInvalidKey)); err == nil {
t.Fatalf("invalid configuration file (bad AES key) got parsed:\n%s", incorrectConfigInvalidKey)
}
}

View file

@ -1,118 +0,0 @@
/*
Copyright 2017 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 encryptionconfig
import (
"bytes"
"io"
"io/ioutil"
"reflect"
"sync"
"github.com/golang/glog"
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope"
)
// Factory is a function that returns an envelope Service for encryption providers.
// The config parameter provides an io.Reader handler to the factory in
// order to load specific configurations. If no configuration is provided
// the parameter is nil.
type Factory func(config io.Reader) (envelope.Service, error)
// KMSPlugins contains all registered KMS options.
type KMSPlugins struct {
lock sync.RWMutex
registry map[string]Factory
}
var (
// PluginEnabledFn checks whether a plugin is enabled. By default, if you ask about it, it's enabled.
PluginEnabledFn = func(name string, config io.Reader) bool {
return true
}
// KMSPluginRegistry contains the registered KMS plugins which can be used for configuring
// encryption providers.
KMSPluginRegistry = KMSPlugins{}
)
// PluginEnabledFunc is a function type that can provide an external check on whether an admission plugin may be enabled
type PluginEnabledFunc func(name string, config io.Reader) bool
// Register registers a plugin Factory by name. This
// is expected to happen during app startup. It does not allow
// registering a plugin by the same name twice.
func (ps *KMSPlugins) Register(name string, plugin Factory) {
ps.lock.Lock()
defer ps.lock.Unlock()
_, found := ps.registry[name]
if ps.registry == nil {
ps.registry = map[string]Factory{}
}
if found {
glog.Fatalf("KMS plugin %q was registered twice", name)
} else {
ps.registry[name] = plugin
glog.V(1).Infof("Registered KMS plugin %q", name)
}
}
// getPlugin creates an instance of the named plugin. It returns `false` if the
// the name is not known. The error is returned only when the named provider was
// known but failed to initialize. The config parameter specifies the io.Reader
// handler of the configuration file for the cloud provider, or nil for no configuration.
func (ps *KMSPlugins) getPlugin(name string, config io.Reader) (envelope.Service, bool, error) {
f, found := ps.fetchPluginFromRegistry(name)
if !found {
return nil, false, nil
}
config1, config2, err := splitStream(config)
if err != nil {
return nil, true, err
}
if !PluginEnabledFn(name, config1) {
return nil, true, nil
}
ret, err := f(config2)
return ret, true, err
}
// fetchPluginFromRegistry tries to get a registered plugin with the requested name.
func (ps *KMSPlugins) fetchPluginFromRegistry(name string) (Factory, bool) {
ps.lock.RLock()
defer ps.lock.RUnlock()
// Map lookup defaults to single value context
f, found := ps.registry[name]
return f, found
}
// splitStream reads the stream bytes and constructs two copies of it.
func splitStream(config io.Reader) (io.Reader, io.Reader, error) {
if config == nil || reflect.ValueOf(config).IsNil() {
return nil, nil, nil
}
configBytes, err := ioutil.ReadAll(config)
if err != nil {
return nil, nil, err
}
return bytes.NewBuffer(configBytes), bytes.NewBuffer(configBytes), nil
}

View file

@ -1,86 +0,0 @@
/*
Copyright 2017 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 encryptionconfig
// EncryptionConfig stores the complete configuration for encryption providers.
type EncryptionConfig struct {
// kind is the type of configuration file.
Kind string `json:"kind"`
// apiVersion is the API version this file has to be parsed as.
APIVersion string `json:"apiVersion"`
// resources is a list containing resources, and their corresponding encryption providers.
Resources []ResourceConfig `json:"resources"`
}
// ResourceConfig stores per resource configuration.
type ResourceConfig struct {
// resources is a list of kubernetes resources which have to be encrypted.
Resources []string `json:"resources"`
// providers is a list of transformers to be used for reading and writing the resources to disk.
// eg: aesgcm, aescbc, secretbox, identity.
Providers []ProviderConfig `json:"providers"`
}
// ProviderConfig stores the provided configuration for an encryption provider.
type ProviderConfig struct {
// aesgcm is the configuration for the AES-GCM transformer.
AESGCM *AESConfig `json:"aesgcm,omitempty"`
// aescbc is the configuration for the AES-CBC transformer.
AESCBC *AESConfig `json:"aescbc,omitempty"`
// secretbox is the configuration for the Secretbox based transformer.
Secretbox *SecretboxConfig `json:"secretbox,omitempty"`
// identity is the (empty) configuration for the identity transformer.
Identity *IdentityConfig `json:"identity,omitempty"`
// kms contains the name, cache size and path to configuration file for a KMS based envelope transformer.
KMS *KMSConfig `json:"kms,omitempty"`
}
// AESConfig contains the API configuration for an AES transformer.
type AESConfig struct {
// keys is a list of keys to be used for creating the AES transformer.
// Each key has to be 32 bytes long for AES-CBC and 16, 24 or 32 bytes for AES-GCM.
Keys []Key `json:"keys"`
}
// SecretboxConfig contains the API configuration for an Secretbox transformer.
type SecretboxConfig struct {
// keys is a list of keys to be used for creating the Secretbox transformer.
// Each key has to be 32 bytes long.
Keys []Key `json:"keys"`
}
// Key contains name and secret of the provided key for a transformer.
type Key struct {
// name is the name of the key to be used while storing data to disk.
Name string `json:"name"`
// secret is the actual key, encoded in base64.
Secret string `json:"secret"`
}
// IdentityConfig is an empty struct to allow identity transformer in provider configuration.
type IdentityConfig struct{}
// KMSConfig contains the name, cache size and path to configuration file for a KMS based envelope transformer.
type KMSConfig struct {
// name is the name of the KMS plugin to be used.
Name string `json:"name"`
// cacheSize is the maximum number of secrets which are cached in memory. The default value is 1000.
// +optional
CacheSize int `json:"cachesize,omitempty"`
// configfile is the path to the configuration file for the named KMS provider.
ConfigFile string `json:"configfile"`
}

View file

@ -1,275 +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 options
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/healthz"
serverstorage "k8s.io/apiserver/pkg/server/storage"
"k8s.io/apiserver/pkg/storage/etcd3/preflight"
"k8s.io/apiserver/pkg/storage/storagebackend"
)
type EtcdOptions struct {
// The value of Paging on StorageConfig will be overriden by the
// calculated feature gate value.
StorageConfig storagebackend.Config
EncryptionProviderConfigFilepath string
EtcdServersOverrides []string
// To enable protobuf as storage format, it is enough
// to set it to "application/vnd.kubernetes.protobuf".
DefaultStorageMediaType string
DeleteCollectionWorkers int
EnableGarbageCollection bool
// Set EnableWatchCache to false to disable all watch caches
EnableWatchCache bool
// Set DefaultWatchCacheSize to zero to disable watch caches for those resources that have no explicit cache size set
DefaultWatchCacheSize int
// WatchCacheSizes represents override to a given resource
WatchCacheSizes []string
}
var storageTypes = sets.NewString(
storagebackend.StorageTypeUnset,
storagebackend.StorageTypeETCD2,
storagebackend.StorageTypeETCD3,
)
func NewEtcdOptions(backendConfig *storagebackend.Config) *EtcdOptions {
return &EtcdOptions{
StorageConfig: *backendConfig,
DefaultStorageMediaType: "application/json",
DeleteCollectionWorkers: 1,
EnableGarbageCollection: true,
EnableWatchCache: true,
DefaultWatchCacheSize: 100,
}
}
func (s *EtcdOptions) Validate() []error {
if s == nil {
return nil
}
allErrors := []error{}
if len(s.StorageConfig.ServerList) == 0 {
allErrors = append(allErrors, fmt.Errorf("--etcd-servers must be specified"))
}
if !storageTypes.Has(s.StorageConfig.Type) {
allErrors = append(allErrors, fmt.Errorf("--storage-backend invalid, must be 'etcd3' or 'etcd2'. If not specified, it will default to 'etcd3'"))
}
return allErrors
}
// AddEtcdFlags adds flags related to etcd storage for a specific APIServer to the specified FlagSet
func (s *EtcdOptions) AddFlags(fs *pflag.FlagSet) {
if s == nil {
return
}
fs.StringSliceVar(&s.EtcdServersOverrides, "etcd-servers-overrides", s.EtcdServersOverrides, ""+
"Per-resource etcd servers overrides, comma separated. The individual override "+
"format: group/resource#servers, where servers are http://ip:port, semicolon separated.")
fs.StringVar(&s.DefaultStorageMediaType, "storage-media-type", s.DefaultStorageMediaType, ""+
"The media type to use to store objects in storage. "+
"Some resources or storage backends may only support a specific media type and will ignore this setting.")
fs.IntVar(&s.DeleteCollectionWorkers, "delete-collection-workers", s.DeleteCollectionWorkers,
"Number of workers spawned for DeleteCollection call. These are used to speed up namespace cleanup.")
fs.BoolVar(&s.EnableGarbageCollection, "enable-garbage-collector", s.EnableGarbageCollection, ""+
"Enables the generic garbage collector. MUST be synced with the corresponding flag "+
"of the kube-controller-manager.")
fs.BoolVar(&s.EnableWatchCache, "watch-cache", s.EnableWatchCache,
"Enable watch caching in the apiserver")
fs.IntVar(&s.DefaultWatchCacheSize, "default-watch-cache-size", s.DefaultWatchCacheSize,
"Default watch cache size. If zero, watch cache will be disabled for resources that do not have a default watch size set.")
fs.StringSliceVar(&s.WatchCacheSizes, "watch-cache-sizes", s.WatchCacheSizes, ""+
"List of watch cache sizes for every resource (pods, nodes, etc.), comma separated. "+
"The individual override format: resource#size, where size is a number. It takes effect "+
"when watch-cache is enabled.")
fs.StringVar(&s.StorageConfig.Type, "storage-backend", s.StorageConfig.Type,
"The storage backend for persistence. Options: 'etcd3' (default), 'etcd2'.")
fs.IntVar(&s.StorageConfig.DeserializationCacheSize, "deserialization-cache-size", s.StorageConfig.DeserializationCacheSize,
"Number of deserialized json objects to cache in memory.")
fs.StringSliceVar(&s.StorageConfig.ServerList, "etcd-servers", s.StorageConfig.ServerList,
"List of etcd servers to connect with (scheme://ip:port), comma separated.")
fs.StringVar(&s.StorageConfig.Prefix, "etcd-prefix", s.StorageConfig.Prefix,
"The prefix to prepend to all resource paths in etcd.")
fs.StringVar(&s.StorageConfig.KeyFile, "etcd-keyfile", s.StorageConfig.KeyFile,
"SSL key file used to secure etcd communication.")
fs.StringVar(&s.StorageConfig.CertFile, "etcd-certfile", s.StorageConfig.CertFile,
"SSL certification file used to secure etcd communication.")
fs.StringVar(&s.StorageConfig.CAFile, "etcd-cafile", s.StorageConfig.CAFile,
"SSL Certificate Authority file used to secure etcd communication.")
fs.BoolVar(&s.StorageConfig.Quorum, "etcd-quorum-read", s.StorageConfig.Quorum,
"If true, enable quorum read.")
fs.StringVar(&s.EncryptionProviderConfigFilepath, "experimental-encryption-provider-config", s.EncryptionProviderConfigFilepath,
"The file containing configuration for encryption providers to be used for storing secrets in etcd")
}
func (s *EtcdOptions) ApplyTo(c *server.Config) error {
if s == nil {
return nil
}
s.addEtcdHealthEndpoint(c)
c.RESTOptionsGetter = &SimpleRestOptionsFactory{Options: *s}
return nil
}
func (s *EtcdOptions) ApplyWithStorageFactoryTo(factory serverstorage.StorageFactory, c *server.Config) error {
s.addEtcdHealthEndpoint(c)
c.RESTOptionsGetter = &storageFactoryRestOptionsFactory{Options: *s, StorageFactory: factory}
return nil
}
func (s *EtcdOptions) addEtcdHealthEndpoint(c *server.Config) {
c.HealthzChecks = append(c.HealthzChecks, healthz.NamedCheck("etcd", func(r *http.Request) error {
done, err := preflight.EtcdConnection{ServerList: s.StorageConfig.ServerList}.CheckEtcdServers()
if !done {
return fmt.Errorf("etcd failed")
}
if err != nil {
return err
}
return nil
}))
}
type SimpleRestOptionsFactory struct {
Options EtcdOptions
}
func (f *SimpleRestOptionsFactory) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
ret := generic.RESTOptions{
StorageConfig: &f.Options.StorageConfig,
Decorator: generic.UndecoratedStorage,
EnableGarbageCollection: f.Options.EnableGarbageCollection,
DeleteCollectionWorkers: f.Options.DeleteCollectionWorkers,
ResourcePrefix: resource.Group + "/" + resource.Resource,
}
if f.Options.EnableWatchCache {
sizes, err := ParseWatchCacheSizes(f.Options.WatchCacheSizes)
if err != nil {
return generic.RESTOptions{}, err
}
cacheSize, ok := sizes[resource]
if !ok {
cacheSize = f.Options.DefaultWatchCacheSize
}
ret.Decorator = genericregistry.StorageWithCacher(cacheSize)
}
return ret, nil
}
type storageFactoryRestOptionsFactory struct {
Options EtcdOptions
StorageFactory serverstorage.StorageFactory
}
func (f *storageFactoryRestOptionsFactory) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
storageConfig, err := f.StorageFactory.NewConfig(resource)
if err != nil {
return generic.RESTOptions{}, fmt.Errorf("unable to find storage destination for %v, due to %v", resource, err.Error())
}
ret := generic.RESTOptions{
StorageConfig: storageConfig,
Decorator: generic.UndecoratedStorage,
DeleteCollectionWorkers: f.Options.DeleteCollectionWorkers,
EnableGarbageCollection: f.Options.EnableGarbageCollection,
ResourcePrefix: f.StorageFactory.ResourcePrefix(resource),
}
if f.Options.EnableWatchCache {
sizes, err := ParseWatchCacheSizes(f.Options.WatchCacheSizes)
if err != nil {
return generic.RESTOptions{}, err
}
cacheSize, ok := sizes[resource]
if !ok {
cacheSize = f.Options.DefaultWatchCacheSize
}
ret.Decorator = genericregistry.StorageWithCacher(cacheSize)
}
return ret, nil
}
// ParseWatchCacheSizes turns a list of cache size values into a map of group resources
// to requested sizes.
func ParseWatchCacheSizes(cacheSizes []string) (map[schema.GroupResource]int, error) {
watchCacheSizes := make(map[schema.GroupResource]int)
for _, c := range cacheSizes {
tokens := strings.Split(c, "#")
if len(tokens) != 2 {
return nil, fmt.Errorf("invalid value of watch cache size: %s", c)
}
size, err := strconv.Atoi(tokens[1])
if err != nil {
return nil, fmt.Errorf("invalid size of watch cache size: %s", c)
}
if size < 0 {
return nil, fmt.Errorf("watch cache size cannot be negative: %s", c)
}
watchCacheSizes[schema.ParseGroupResource(tokens[0])] = size
}
return watchCacheSizes, nil
}
// WriteWatchCacheSizes turns a map of cache size values into a list of string specifications.
func WriteWatchCacheSizes(watchCacheSizes map[schema.GroupResource]int) ([]string, error) {
var cacheSizes []string
for resource, size := range watchCacheSizes {
if size < 0 {
return nil, fmt.Errorf("watch cache size cannot be negative for resource %s", resource)
}
cacheSizes = append(cacheSizes, fmt.Sprintf("%s#%d", resource.String(), size))
}
return cacheSizes, nil
}

View file

@ -1,74 +0,0 @@
/*
Copyright 2017 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 options
import (
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/server"
)
type FeatureOptions struct {
EnableProfiling bool
EnableContentionProfiling bool
EnableSwaggerUI bool
}
func NewFeatureOptions() *FeatureOptions {
defaults := server.NewConfig(serializer.CodecFactory{})
return &FeatureOptions{
EnableProfiling: defaults.EnableProfiling,
EnableContentionProfiling: defaults.EnableContentionProfiling,
EnableSwaggerUI: defaults.EnableSwaggerUI,
}
}
func (o *FeatureOptions) AddFlags(fs *pflag.FlagSet) {
if o == nil {
return
}
fs.BoolVar(&o.EnableProfiling, "profiling", o.EnableProfiling,
"Enable profiling via web interface host:port/debug/pprof/")
fs.BoolVar(&o.EnableContentionProfiling, "contention-profiling", o.EnableContentionProfiling,
"Enable lock contention profiling, if profiling is enabled")
fs.BoolVar(&o.EnableSwaggerUI, "enable-swagger-ui", o.EnableSwaggerUI,
"Enables swagger ui on the apiserver at /swagger-ui")
}
func (o *FeatureOptions) ApplyTo(c *server.Config) error {
if o == nil {
return nil
}
c.EnableProfiling = o.EnableProfiling
c.EnableContentionProfiling = o.EnableContentionProfiling
c.EnableSwaggerUI = o.EnableSwaggerUI
return nil
}
func (o *FeatureOptions) Validate() []error {
if o == nil {
return nil
}
errs := []error{}
return errs
}

View file

@ -1,98 +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 options
import (
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/storage/storagebackend"
)
// RecommendedOptions contains the recommended options for running an API server.
// If you add something to this list, it should be in a logical grouping.
// Each of them can be nil to leave the feature unconfigured on ApplyTo.
type RecommendedOptions struct {
Etcd *EtcdOptions
SecureServing *SecureServingOptions
Authentication *DelegatingAuthenticationOptions
Authorization *DelegatingAuthorizationOptions
Audit *AuditOptions
Features *FeatureOptions
CoreAPI *CoreAPIOptions
}
func NewRecommendedOptions(prefix string, copier runtime.ObjectCopier, codec runtime.Codec) *RecommendedOptions {
return &RecommendedOptions{
Etcd: NewEtcdOptions(storagebackend.NewDefaultConfig(prefix, copier, codec)),
SecureServing: NewSecureServingOptions(),
Authentication: NewDelegatingAuthenticationOptions(),
Authorization: NewDelegatingAuthorizationOptions(),
Audit: NewAuditOptions(),
Features: NewFeatureOptions(),
CoreAPI: NewCoreAPIOptions(),
}
}
func (o *RecommendedOptions) AddFlags(fs *pflag.FlagSet) {
o.Etcd.AddFlags(fs)
o.SecureServing.AddFlags(fs)
o.Authentication.AddFlags(fs)
o.Authorization.AddFlags(fs)
o.Audit.AddFlags(fs)
o.Features.AddFlags(fs)
o.CoreAPI.AddFlags(fs)
}
func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error {
if err := o.Etcd.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.SecureServing.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.Authentication.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.Authorization.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.Audit.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.Features.ApplyTo(&config.Config); err != nil {
return err
}
if err := o.CoreAPI.ApplyTo(config); err != nil {
return err
}
return nil
}
func (o *RecommendedOptions) Validate() []error {
errors := []error{}
errors = append(errors, o.Etcd.Validate()...)
errors = append(errors, o.SecureServing.Validate()...)
errors = append(errors, o.Authentication.Validate()...)
errors = append(errors, o.Authorization.Validate()...)
errors = append(errors, o.Audit.Validate()...)
errors = append(errors, o.Features.Validate()...)
errors = append(errors, o.CoreAPI.Validate()...)
return errors
}

View file

@ -1,154 +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 options
import (
"fmt"
"net"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/server"
utilfeature "k8s.io/apiserver/pkg/util/feature"
// add the generic feature gates
_ "k8s.io/apiserver/pkg/features"
"github.com/spf13/pflag"
)
// ServerRunOptions contains the options while running a generic api server.
type ServerRunOptions struct {
AdvertiseAddress net.IP
CorsAllowedOriginList []string
ExternalHost string
MaxRequestsInFlight int
MaxMutatingRequestsInFlight int
RequestTimeout time.Duration
MinRequestTimeout int
TargetRAMMB int
}
func NewServerRunOptions() *ServerRunOptions {
defaults := server.NewConfig(serializer.CodecFactory{})
return &ServerRunOptions{
MaxRequestsInFlight: defaults.MaxRequestsInFlight,
MaxMutatingRequestsInFlight: defaults.MaxMutatingRequestsInFlight,
RequestTimeout: defaults.RequestTimeout,
MinRequestTimeout: defaults.MinRequestTimeout,
}
}
// ApplyOptions applies the run options to the method receiver and returns self
func (s *ServerRunOptions) ApplyTo(c *server.Config) error {
c.CorsAllowedOriginList = s.CorsAllowedOriginList
c.ExternalAddress = s.ExternalHost
c.MaxRequestsInFlight = s.MaxRequestsInFlight
c.MaxMutatingRequestsInFlight = s.MaxMutatingRequestsInFlight
c.RequestTimeout = s.RequestTimeout
c.MinRequestTimeout = s.MinRequestTimeout
c.PublicAddress = s.AdvertiseAddress
return nil
}
// DefaultAdvertiseAddress sets the field AdvertiseAddress if unset. The field will be set based on the SecureServingOptions.
func (s *ServerRunOptions) DefaultAdvertiseAddress(secure *SecureServingOptions) error {
if secure == nil {
return nil
}
if s.AdvertiseAddress == nil || s.AdvertiseAddress.IsUnspecified() {
hostIP, err := secure.DefaultExternalAddress()
if err != nil {
return fmt.Errorf("Unable to find suitable network address.error='%v'. "+
"Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this.", err)
}
s.AdvertiseAddress = hostIP
}
return nil
}
// Validate checks validation of ServerRunOptions
func (s *ServerRunOptions) Validate() []error {
errors := []error{}
if s.TargetRAMMB < 0 {
errors = append(errors, fmt.Errorf("--target-ram-mb can not be negative value"))
}
if s.MaxRequestsInFlight < 0 {
errors = append(errors, fmt.Errorf("--max-requests-inflight can not be negative value"))
}
if s.MaxMutatingRequestsInFlight < 0 {
errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight can not be negative value"))
}
if s.RequestTimeout.Nanoseconds() < 0 {
errors = append(errors, fmt.Errorf("--request-timeout can not be negative value"))
}
return errors
}
// AddFlags adds flags for a specific APIServer to the specified FlagSet
func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
// Note: the weird ""+ in below lines seems to be the only way to get gofmt to
// arrange these text blocks sensibly. Grrr.
fs.IPVar(&s.AdvertiseAddress, "advertise-address", s.AdvertiseAddress, ""+
"The IP address on which to advertise the apiserver to members of the cluster. This "+
"address must be reachable by the rest of the cluster. If blank, the --bind-address "+
"will be used. If --bind-address is unspecified, the host's default interface will "+
"be used.")
fs.StringSliceVar(&s.CorsAllowedOriginList, "cors-allowed-origins", s.CorsAllowedOriginList, ""+
"List of allowed origins for CORS, comma separated. An allowed origin can be a regular "+
"expression to support subdomain matching. If this list is empty CORS will not be enabled.")
fs.IntVar(&s.TargetRAMMB, "target-ram-mb", s.TargetRAMMB,
"Memory limit for apiserver in MB (used to configure sizes of caches, etc.)")
fs.StringVar(&s.ExternalHost, "external-hostname", s.ExternalHost,
"The hostname to use when generating externalized URLs for this master (e.g. Swagger API Docs).")
deprecatedMasterServiceNamespace := metav1.NamespaceDefault
fs.StringVar(&deprecatedMasterServiceNamespace, "master-service-namespace", deprecatedMasterServiceNamespace, ""+
"DEPRECATED: the namespace from which the kubernetes master services should be injected into pods.")
fs.IntVar(&s.MaxRequestsInFlight, "max-requests-inflight", s.MaxRequestsInFlight, ""+
"The maximum number of non-mutating requests in flight at a given time. When the server exceeds this, "+
"it rejects requests. Zero for no limit.")
fs.IntVar(&s.MaxMutatingRequestsInFlight, "max-mutating-requests-inflight", s.MaxMutatingRequestsInFlight, ""+
"The maximum number of mutating requests in flight at a given time. When the server exceeds this, "+
"it rejects requests. Zero for no limit.")
fs.DurationVar(&s.RequestTimeout, "request-timeout", s.RequestTimeout, ""+
"An optional field indicating the duration a handler must keep a request open before timing "+
"it out. This is the default request timeout for requests but may be overridden by flags such as "+
"--min-request-timeout for specific types of requests.")
fs.IntVar(&s.MinRequestTimeout, "min-request-timeout", s.MinRequestTimeout, ""+
"An optional field indicating the minimum number of seconds a handler must keep "+
"a request open before timing it out. Currently only honored by the watch request "+
"handler, which picks a randomized value above this number as the connection timeout, "+
"to spread out load.")
utilfeature.DefaultFeatureGate.AddFlag(fs)
}

View file

@ -1,288 +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 options
import (
"crypto/tls"
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"path"
"strconv"
"github.com/golang/glog"
"github.com/pborman/uuid"
"github.com/spf13/pflag"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/server"
utilflag "k8s.io/apiserver/pkg/util/flag"
certutil "k8s.io/client-go/util/cert"
)
type SecureServingOptions struct {
BindAddress net.IP
BindPort int
// ServerCert is the TLS cert info for serving secure traffic
ServerCert GeneratableKeyCert
// SNICertKeys are named CertKeys for serving secure traffic with SNI support.
SNICertKeys []utilflag.NamedCertKey
}
type CertKey struct {
// CertFile is a file containing a PEM-encoded certificate, and possibly the complete certificate chain
CertFile string
// KeyFile is a file containing a PEM-encoded private key for the certificate specified by CertFile
KeyFile string
}
type GeneratableKeyCert struct {
CertKey CertKey
// CACertFile is an optional file containing the certificate chain for CertKey.CertFile
CACertFile string
// CertDirectory is a directory that will contain the certificates. If the cert and key aren't specifically set
// this will be used to derive a match with the "pair-name"
CertDirectory string
// PairName is the name which will be used with CertDirectory to make a cert and key names
// It becomes CertDirector/PairName.crt and CertDirector/PairName.key
PairName string
}
func NewSecureServingOptions() *SecureServingOptions {
return &SecureServingOptions{
BindAddress: net.ParseIP("0.0.0.0"),
BindPort: 443,
ServerCert: GeneratableKeyCert{
PairName: "apiserver",
CertDirectory: "apiserver.local.config/certificates",
},
}
}
func (s *SecureServingOptions) DefaultExternalAddress() (net.IP, error) {
return utilnet.ChooseBindAddress(s.BindAddress)
}
func (s *SecureServingOptions) Validate() []error {
if s == nil {
return nil
}
errors := []error{}
if s.BindPort < 0 || s.BindPort > 65535 {
errors = append(errors, fmt.Errorf("--secure-port %v must be between 0 and 65535, inclusive. 0 for turning off secure port.", s.BindPort))
}
return errors
}
func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) {
if s == nil {
return
}
fs.IPVar(&s.BindAddress, "bind-address", s.BindAddress, ""+
"The IP address on which to listen for the --secure-port port. The "+
"associated interface(s) must be reachable by the rest of the cluster, and by CLI/web "+
"clients. If blank, all interfaces will be used (0.0.0.0).")
fs.IntVar(&s.BindPort, "secure-port", s.BindPort, ""+
"The port on which to serve HTTPS with authentication and authorization. If 0, "+
"don't serve HTTPS at all.")
fs.StringVar(&s.ServerCert.CertDirectory, "cert-dir", s.ServerCert.CertDirectory, ""+
"The directory where the TLS certs are located. "+
"If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored.")
fs.StringVar(&s.ServerCert.CertKey.CertFile, "tls-cert-file", s.ServerCert.CertKey.CertFile, ""+
"File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated "+
"after server cert). If HTTPS serving is enabled, and --tls-cert-file and "+
"--tls-private-key-file are not provided, a self-signed certificate and key "+
"are generated for the public address and saved to /var/run/kubernetes.")
fs.StringVar(&s.ServerCert.CertKey.KeyFile, "tls-private-key-file", s.ServerCert.CertKey.KeyFile,
"File containing the default x509 private key matching --tls-cert-file.")
fs.StringVar(&s.ServerCert.CACertFile, "tls-ca-file", s.ServerCert.CACertFile, "If set, this "+
"certificate authority will used for secure access from Admission "+
"Controllers. This must be a valid PEM-encoded CA bundle. Altneratively, the certificate authority "+
"can be appended to the certificate provided by --tls-cert-file.")
fs.Var(utilflag.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+
"A pair of x509 certificate and private key file paths, optionally suffixed with a list of "+
"domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+
"segments. If no domain patterns are provided, the names of the certificate are "+
"extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns "+
"trump over extracted names. For multiple key/certificate pairs, use the "+
"--tls-sni-cert-key multiple times. "+
"Examples: \"example.crt,example.key\" or \"foo.crt,foo.key:*.foo.com,foo.com\".")
}
func (s *SecureServingOptions) AddDeprecatedFlags(fs *pflag.FlagSet) {
fs.IPVar(&s.BindAddress, "public-address-override", s.BindAddress,
"DEPRECATED: see --bind-address instead.")
fs.MarkDeprecated("public-address-override", "see --bind-address instead.")
}
// ApplyTo fills up serving information in the server configuration.
func (s *SecureServingOptions) ApplyTo(c *server.Config) error {
if s == nil {
return nil
}
if s.BindPort <= 0 {
return nil
}
if err := s.applyServingInfoTo(c); err != nil {
return err
}
// create self-signed cert+key with the fake server.LoopbackClientServerNameOverride and
// let the server return it when the loopback client connects.
certPem, keyPem, err := certutil.GenerateSelfSignedCertKey(server.LoopbackClientServerNameOverride, nil, nil)
if err != nil {
return fmt.Errorf("failed to generate self-signed certificate for loopback connection: %v", err)
}
tlsCert, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
return fmt.Errorf("failed to generate self-signed certificate for loopback connection: %v", err)
}
secureLoopbackClientConfig, err := c.SecureServingInfo.NewLoopbackClientConfig(uuid.NewRandom().String(), certPem)
switch {
// if we failed and there's no fallback loopback client config, we need to fail
case err != nil && c.LoopbackClientConfig == nil:
return err
// if we failed, but we already have a fallback loopback client config (usually insecure), allow it
case err != nil && c.LoopbackClientConfig != nil:
default:
c.LoopbackClientConfig = secureLoopbackClientConfig
c.SecureServingInfo.SNICerts[server.LoopbackClientServerNameOverride] = &tlsCert
}
return nil
}
func (s *SecureServingOptions) applyServingInfoTo(c *server.Config) error {
if s.BindPort <= 0 {
return nil
}
secureServingInfo := &server.SecureServingInfo{
BindAddress: net.JoinHostPort(s.BindAddress.String(), strconv.Itoa(s.BindPort)),
}
serverCertFile, serverKeyFile := s.ServerCert.CertKey.CertFile, s.ServerCert.CertKey.KeyFile
// load main cert
if len(serverCertFile) != 0 || len(serverKeyFile) != 0 {
tlsCert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
if err != nil {
return fmt.Errorf("unable to load server certificate: %v", err)
}
secureServingInfo.Cert = &tlsCert
}
// optionally load CA cert
if len(s.ServerCert.CACertFile) != 0 {
pemData, err := ioutil.ReadFile(s.ServerCert.CACertFile)
if err != nil {
return fmt.Errorf("failed to read certificate authority from %q: %v", s.ServerCert.CACertFile, err)
}
block, pemData := pem.Decode(pemData)
if block == nil {
return fmt.Errorf("no certificate found in certificate authority file %q", s.ServerCert.CACertFile)
}
if block.Type != "CERTIFICATE" {
return fmt.Errorf("expected CERTIFICATE block in certiticate authority file %q, found: %s", s.ServerCert.CACertFile, block.Type)
}
secureServingInfo.CACert = &tls.Certificate{
Certificate: [][]byte{block.Bytes},
}
}
// load SNI certs
namedTLSCerts := make([]server.NamedTLSCert, 0, len(s.SNICertKeys))
for _, nck := range s.SNICertKeys {
tlsCert, err := tls.LoadX509KeyPair(nck.CertFile, nck.KeyFile)
namedTLSCerts = append(namedTLSCerts, server.NamedTLSCert{
TLSCert: tlsCert,
Names: nck.Names,
})
if err != nil {
return fmt.Errorf("failed to load SNI cert and key: %v", err)
}
}
var err error
secureServingInfo.SNICerts, err = server.GetNamedCertificateMap(namedTLSCerts)
if err != nil {
return err
}
c.SecureServingInfo = secureServingInfo
c.ReadWritePort = s.BindPort
return nil
}
func (s *SecureServingOptions) MaybeDefaultWithSelfSignedCerts(publicAddress string, alternateDNS []string, alternateIPs []net.IP) error {
if s == nil {
return nil
}
keyCert := &s.ServerCert.CertKey
if s.BindPort == 0 || len(keyCert.CertFile) != 0 || len(keyCert.KeyFile) != 0 {
return nil
}
keyCert.CertFile = path.Join(s.ServerCert.CertDirectory, s.ServerCert.PairName+".crt")
keyCert.KeyFile = path.Join(s.ServerCert.CertDirectory, s.ServerCert.PairName+".key")
canReadCertAndKey, err := certutil.CanReadCertAndKey(keyCert.CertFile, keyCert.KeyFile)
if err != nil {
return err
}
if !canReadCertAndKey {
// add either the bind address or localhost to the valid alternates
bindIP := s.BindAddress.String()
if bindIP == "0.0.0.0" {
alternateDNS = append(alternateDNS, "localhost")
} else {
alternateIPs = append(alternateIPs, s.BindAddress)
}
if cert, key, err := certutil.GenerateSelfSignedCertKey(publicAddress, alternateIPs, alternateDNS); err != nil {
return fmt.Errorf("unable to generate self signed cert: %v", err)
} else {
if err := certutil.WriteCert(keyCert.CertFile, cert); err != nil {
return err
}
if err := certutil.WriteKey(keyCert.KeyFile, key); err != nil {
return err
}
glog.Infof("Generated self-signed cert (%s, %s)", keyCert.CertFile, keyCert.KeyFile)
}
}
return nil
}

View file

@ -1,725 +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 options
import (
"bytes"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/version"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/server"
. "k8s.io/apiserver/pkg/server"
utilflag "k8s.io/apiserver/pkg/util/flag"
"k8s.io/client-go/discovery"
restclient "k8s.io/client-go/rest"
)
func setUp(t *testing.T) Config {
scheme := runtime.NewScheme()
codecs := serializer.NewCodecFactory(scheme)
config := NewConfig(codecs)
config.RequestContextMapper = genericapirequest.NewRequestContextMapper()
return *config
}
type TestCertSpec struct {
host string
names, ips []string // in certificate
}
type NamedTestCertSpec struct {
TestCertSpec
explicitNames []string // as --tls-sni-cert-key explicit names
}
func TestGetNamedCertificateMap(t *testing.T) {
tests := []struct {
certs []NamedTestCertSpec
explicitNames []string
expected map[string]int // name to certs[*] index
errorString string
}{
{
// empty certs
expected: map[string]int{},
},
{
// only one cert
certs: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "test.com",
},
},
},
expected: map[string]int{
"test.com": 0,
},
},
{
// ips are ignored
certs: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "test.com",
ips: []string{"1.2.3.4"},
},
},
},
expected: map[string]int{
"test.com": 0,
},
},
{
// two certs with the same name
certs: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "test.com",
},
},
{
TestCertSpec: TestCertSpec{
host: "test.com",
},
},
},
expected: map[string]int{
"test.com": 0,
},
},
{
// two certs with different names
certs: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "test2.com",
},
},
{
TestCertSpec: TestCertSpec{
host: "test1.com",
},
},
},
expected: map[string]int{
"test1.com": 1,
"test2.com": 0,
},
},
{
// two certs with the same name, explicit trumps
certs: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "test.com",
},
},
{
TestCertSpec: TestCertSpec{
host: "test.com",
},
explicitNames: []string{"test.com"},
},
},
expected: map[string]int{
"test.com": 1,
},
},
{
// certs with partial overlap; ips are ignored
certs: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "a",
names: []string{"a.test.com", "test.com"},
},
},
{
TestCertSpec: TestCertSpec{
host: "b",
names: []string{"b.test.com", "test.com"},
},
},
},
expected: map[string]int{
"a": 0, "b": 1,
"a.test.com": 0, "b.test.com": 1,
"test.com": 0,
},
},
{
// wildcards
certs: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "a",
names: []string{"a.test.com", "test.com"},
},
explicitNames: []string{"*.test.com", "test.com"},
},
{
TestCertSpec: TestCertSpec{
host: "b",
names: []string{"b.test.com", "test.com"},
},
explicitNames: []string{"dev.test.com", "test.com"},
}},
expected: map[string]int{
"test.com": 0,
"*.test.com": 0,
"dev.test.com": 1,
},
},
}
NextTest:
for i, test := range tests {
var namedTLSCerts []NamedTLSCert
bySignature := map[string]int{} // index in test.certs by cert signature
for j, c := range test.certs {
cert, err := createTestTLSCerts(c.TestCertSpec)
if err != nil {
t.Errorf("%d - failed to create cert %d: %v", i, j, err)
continue NextTest
}
namedTLSCerts = append(namedTLSCerts, NamedTLSCert{
TLSCert: cert,
Names: c.explicitNames,
})
sig, err := certSignature(cert)
if err != nil {
t.Errorf("%d - failed to get signature for %d: %v", i, j, err)
continue NextTest
}
bySignature[sig] = j
}
certMap, err := GetNamedCertificateMap(namedTLSCerts)
if err == nil && len(test.errorString) != 0 {
t.Errorf("%d - expected no error, got: %v", i, err)
} else if err != nil && err.Error() != test.errorString {
t.Errorf("%d - expected error %q, got: %v", i, test.errorString, err)
} else {
got := map[string]int{}
for name, cert := range certMap {
x509Certs, err := x509.ParseCertificates(cert.Certificate[0])
assert.NoError(t, err, "%d - invalid certificate for %q", i, name)
assert.True(t, len(x509Certs) > 0, "%d - expected at least one x509 cert in tls cert for %q", i, name)
got[name] = bySignature[x509CertSignature(x509Certs[0])]
}
assert.EqualValues(t, test.expected, got, "%d - wrong certificate map", i)
}
}
}
func TestServerRunWithSNI(t *testing.T) {
tests := map[string]struct {
Cert TestCertSpec
SNICerts []NamedTestCertSpec
ExpectedCertIndex int
// passed in the client hello info, "localhost" if unset
ServerName string
// optional ip or hostname to pass to NewLoopbackClientConfig
LoopbackClientBindAddressOverride string
ExpectLoopbackClientError bool
}{
"only one cert": {
Cert: TestCertSpec{
host: "localhost",
ips: []string{"127.0.0.1"},
},
ExpectedCertIndex: -1,
},
"cert with multiple alternate names": {
Cert: TestCertSpec{
host: "localhost",
names: []string{"test.com"},
ips: []string{"127.0.0.1"},
},
ExpectedCertIndex: -1,
ServerName: "test.com",
},
"one SNI and the default cert with the same name": {
Cert: TestCertSpec{
host: "localhost",
ips: []string{"127.0.0.1"},
},
SNICerts: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "localhost",
},
},
},
ExpectedCertIndex: 0,
},
"matching SNI cert": {
Cert: TestCertSpec{
host: "localhost",
ips: []string{"127.0.0.1"},
},
SNICerts: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "test.com",
},
},
},
ExpectedCertIndex: 0,
ServerName: "test.com",
},
"matching IP in SNI cert and the server cert": {
// IPs must not be passed via SNI. Hence, the ServerName in the
// HELLO packet is empty and the server should select the non-SNI cert.
Cert: TestCertSpec{
host: "localhost",
ips: []string{"10.0.0.1", "127.0.0.1"},
},
SNICerts: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "test.com",
ips: []string{"10.0.0.1"},
},
},
},
ExpectedCertIndex: -1,
ServerName: "10.0.0.1",
},
"wildcards": {
Cert: TestCertSpec{
host: "localhost",
ips: []string{"127.0.0.1"},
},
SNICerts: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "test.com",
names: []string{"*.test.com"},
},
},
},
ExpectedCertIndex: 0,
ServerName: "www.test.com",
},
"loopback: LoopbackClientServerNameOverride not on any cert": {
Cert: TestCertSpec{
host: "test.com",
},
SNICerts: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "localhost",
},
},
},
ExpectedCertIndex: 0,
},
"loopback: LoopbackClientServerNameOverride on server cert": {
Cert: TestCertSpec{
host: server.LoopbackClientServerNameOverride,
},
SNICerts: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: "localhost",
},
},
},
ExpectedCertIndex: 0,
},
"loopback: LoopbackClientServerNameOverride on SNI cert": {
Cert: TestCertSpec{
host: "localhost",
},
SNICerts: []NamedTestCertSpec{
{
TestCertSpec: TestCertSpec{
host: server.LoopbackClientServerNameOverride,
},
},
},
ExpectedCertIndex: -1,
},
"loopback: bind to 0.0.0.0 => loopback uses localhost": {
Cert: TestCertSpec{
host: "localhost",
},
ExpectedCertIndex: -1,
LoopbackClientBindAddressOverride: "0.0.0.0",
},
}
specToName := func(spec TestCertSpec) string {
name := spec.host + "_" + strings.Join(spec.names, ",") + "_" + strings.Join(spec.ips, ",")
return strings.Replace(name, "*", "star", -1)
}
NextTest:
for title, test := range tests {
// create server cert
certDir := "testdata/" + specToName(test.Cert)
serverCertBundleFile := filepath.Join(certDir, "cert")
serverKeyFile := filepath.Join(certDir, "key")
err := getOrCreateTestCertFiles(serverCertBundleFile, serverKeyFile, test.Cert)
if err != nil {
t.Errorf("%q - failed to create server cert: %v", title, err)
continue NextTest
}
ca, err := caCertFromBundle(serverCertBundleFile)
if err != nil {
t.Errorf("%q - failed to extract ca cert from server cert bundle: %v", title, err)
continue NextTest
}
caCerts := []*x509.Certificate{ca}
// create SNI certs
var namedCertKeys []utilflag.NamedCertKey
serverSig, err := certFileSignature(serverCertBundleFile, serverKeyFile)
if err != nil {
t.Errorf("%q - failed to get server cert signature: %v", title, err)
continue NextTest
}
signatures := map[string]int{
serverSig: -1,
}
for j, c := range test.SNICerts {
sniDir := filepath.Join(certDir, specToName(c.TestCertSpec))
certBundleFile := filepath.Join(sniDir, "cert")
keyFile := filepath.Join(sniDir, "key")
err := getOrCreateTestCertFiles(certBundleFile, keyFile, c.TestCertSpec)
if err != nil {
t.Errorf("%q - failed to create SNI cert %d: %v", title, j, err)
continue NextTest
}
namedCertKeys = append(namedCertKeys, utilflag.NamedCertKey{
KeyFile: keyFile,
CertFile: certBundleFile,
Names: c.explicitNames,
})
ca, err := caCertFromBundle(certBundleFile)
if err != nil {
t.Errorf("%q - failed to extract ca cert from SNI cert %d: %v", title, j, err)
continue NextTest
}
caCerts = append(caCerts, ca)
// store index in namedCertKeys with the signature as the key
sig, err := certFileSignature(certBundleFile, keyFile)
if err != nil {
t.Errorf("%q - failed get SNI cert %d signature: %v", title, j, err)
continue NextTest
}
signatures[sig] = j
}
stopCh := make(chan struct{})
func() {
defer close(stopCh)
// launch server
config := setUp(t)
v := fakeVersion()
config.Version = &v
config.EnableIndex = true
secureOptions := &SecureServingOptions{
BindAddress: net.ParseIP("127.0.0.1"),
BindPort: 6443,
ServerCert: GeneratableKeyCert{
CertKey: CertKey{
CertFile: serverCertBundleFile,
KeyFile: serverKeyFile,
},
},
SNICertKeys: namedCertKeys,
}
config.LoopbackClientConfig = &restclient.Config{}
if err := secureOptions.ApplyTo(&config); err != nil {
t.Errorf("%q - failed applying the SecureServingOptions: %v", title, err)
return
}
s, err := config.Complete(nil).New("test", server.EmptyDelegate)
if err != nil {
t.Errorf("%q - failed creating the server: %v", title, err)
return
}
// patch in a 0-port to enable auto port allocation
s.SecureServingInfo.BindAddress = "127.0.0.1:0"
// add poststart hook to know when the server is up.
startedCh := make(chan struct{})
s.AddPostStartHook("test-notifier", func(context PostStartHookContext) error {
close(startedCh)
return nil
})
preparedServer := s.PrepareRun()
go func() {
if err := preparedServer.Run(stopCh); err != nil {
t.Fatal(err)
}
}()
// load ca certificates into a pool
roots := x509.NewCertPool()
for _, caCert := range caCerts {
roots.AddCert(caCert)
}
<-startedCh
effectiveSecurePort := fmt.Sprintf("%d", preparedServer.EffectiveSecurePort())
// try to dial
addr := fmt.Sprintf("localhost:%s", effectiveSecurePort)
t.Logf("Dialing %s as %q", addr, test.ServerName)
conn, err := tls.Dial("tcp", addr, &tls.Config{
RootCAs: roots,
ServerName: test.ServerName, // used for SNI in the client HELLO packet
})
if err != nil {
t.Errorf("%q - failed to connect: %v", title, err)
return
}
// check returned server certificate
sig := x509CertSignature(conn.ConnectionState().PeerCertificates[0])
gotCertIndex, found := signatures[sig]
if !found {
t.Errorf("%q - unknown signature returned from server: %s", title, sig)
}
if gotCertIndex != test.ExpectedCertIndex {
t.Errorf("%q - expected cert index %d, got cert index %d", title, test.ExpectedCertIndex, gotCertIndex)
}
conn.Close()
// check that the loopback client can connect
host := "127.0.0.1"
if len(test.LoopbackClientBindAddressOverride) != 0 {
host = test.LoopbackClientBindAddressOverride
}
s.LoopbackClientConfig.Host = net.JoinHostPort(host, effectiveSecurePort)
if test.ExpectLoopbackClientError {
if err == nil {
t.Errorf("%q - expected error creating loopback client config", title)
}
return
}
if err != nil {
t.Errorf("%q - failed creating loopback client config: %v", title, err)
return
}
client, err := discovery.NewDiscoveryClientForConfig(s.LoopbackClientConfig)
if err != nil {
t.Errorf("%q - failed to create loopback client: %v", title, err)
return
}
got, err := client.ServerVersion()
if err != nil {
t.Errorf("%q - failed to connect with loopback client: %v", title, err)
return
}
if expected := &v; !reflect.DeepEqual(got, expected) {
t.Errorf("%q - loopback client didn't get correct version info: expected=%v got=%v", title, expected, got)
}
}()
}
}
func parseIPList(ips []string) []net.IP {
var netIPs []net.IP
for _, ip := range ips {
netIPs = append(netIPs, net.ParseIP(ip))
}
return netIPs
}
func createTestTLSCerts(spec TestCertSpec) (tlsCert tls.Certificate, err error) {
certPem, keyPem, err := generateSelfSignedCertKey(spec.host, parseIPList(spec.ips), spec.names)
if err != nil {
return tlsCert, err
}
tlsCert, err = tls.X509KeyPair(certPem, keyPem)
return tlsCert, err
}
func getOrCreateTestCertFiles(certFileName, keyFileName string, spec TestCertSpec) (err error) {
if _, err := os.Stat(certFileName); err == nil {
if _, err := os.Stat(keyFileName); err == nil {
return nil
}
}
certPem, keyPem, err := generateSelfSignedCertKey(spec.host, parseIPList(spec.ips), spec.names)
if err != nil {
return err
}
os.MkdirAll(filepath.Dir(certFileName), os.FileMode(0755))
err = ioutil.WriteFile(certFileName, certPem, os.FileMode(0755))
if err != nil {
return err
}
os.MkdirAll(filepath.Dir(keyFileName), os.FileMode(0755))
err = ioutil.WriteFile(keyFileName, keyPem, os.FileMode(0755))
if err != nil {
return err
}
return nil
}
func caCertFromBundle(bundlePath string) (*x509.Certificate, error) {
pemData, err := ioutil.ReadFile(bundlePath)
if err != nil {
return nil, err
}
// fetch last block
var block *pem.Block
for {
var nextBlock *pem.Block
nextBlock, pemData = pem.Decode(pemData)
if nextBlock == nil {
if block == nil {
return nil, fmt.Errorf("no certificate found in %q", bundlePath)
}
return x509.ParseCertificate(block.Bytes)
}
block = nextBlock
}
}
func x509CertSignature(cert *x509.Certificate) string {
return base64.StdEncoding.EncodeToString(cert.Signature)
}
func certFileSignature(certFile, keyFile string) (string, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return "", err
}
return certSignature(cert)
}
func certSignature(cert tls.Certificate) (string, error) {
x509Certs, err := x509.ParseCertificates(cert.Certificate[0])
if err != nil {
return "", err
}
return x509CertSignature(x509Certs[0]), nil
}
func fakeVersion() version.Info {
return version.Info{
Major: "42",
Minor: "42",
GitVersion: "42",
GitCommit: "34973274ccef6ab4dfaaf86599792fa9c3fe4689",
GitTreeState: "Dirty",
}
}
// generateSelfSignedCertKey creates a self-signed certificate and key for the given host.
// Host may be an IP or a DNS name
// You may also specify additional subject alt names (either ip or dns names) for the certificate
func generateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) {
priv, err := rsa.GenerateKey(cryptorand.Reader, 2048)
if err != nil {
return nil, nil, err
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
},
NotBefore: time.Unix(0, 0),
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
if ip := net.ParseIP(host); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, host)
}
template.IPAddresses = append(template.IPAddresses, alternateIPs...)
template.DNSNames = append(template.DNSNames, alternateDNS...)
derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, nil, err
}
// Generate cert
certBuffer := bytes.Buffer{}
if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return nil, nil, err
}
// Generate key
keyBuffer := bytes.Buffer{}
if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
return nil, nil, err
}
return certBuffer.Bytes(), keyBuffer.Bytes(), nil
}

View file

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAvMS0wKwYDVQQDDCRhcGlz
ZXJ2ZXItbG9vcGJhY2stY2xpZW50QDE0OTYzMzA2NTYwIBcNNzAwMTAxMDAwMDAw
WhgPMjExNzA1MDgxNTI0MTZaMC8xLTArBgNVBAMMJGFwaXNlcnZlci1sb29wYmFj
ay1jbGllbnRAMTQ5NjMzMDY1NjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAMScrdc+XlUPHsDvnN2fFxSSQrhrKBlrHBcelaiFwR1vWt2EXxErVUoRGA+1
jvQ5Y4NePS46Zpq2N+yibiWjaC0fsS4W2UvPAfdD4nxOp5SaTBy5A0XI+JU8QmbI
Rls+C/fSeYaXSKp5Jq41uTKGc+QwT9pqCeV7N74DSc6kvNh7mIBc4YCOzMnhQq5H
KQVCeDPURxfh7Ew+v13dLYDq7V6O1jBjfg3Rm/RRFnM78kcc51QTOo9ZSs5HgahU
eM4YirHz8ujswhgxzH43TeOGds3McGKv1HMHKJKinX02IHJBqV6U5y/AH92wi0Sp
IKgJ4GJaj/Vda/kPUtf5cL2sOt0CAwEAAaNeMFwwDgYDVR0PAQH/BAQDAgKkMBMG
A1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wJAYDVR0RBB0wG4IZ
YXBpc2VydmVyLWxvb3BiYWNrLWNsaWVudDANBgkqhkiG9w0BAQsFAAOCAQEALlZe
LJ3i6WQhpgV3jPWuIBTXJ0bb3cbYnk4JLWYW9sIjMakfAlZK9VrfRZKOhI8DsGsG
PVcYOyc5TQM0JkcsCKajyA6HktpMFEtxBfSJOlRAdvvj1GtRnifcA5xBqn1SzL2H
tFx5etii70spZD3jDht2bKLZmL2NbGGVOWiKdtz6qR+V9U4F+uYCq+phKgnLeDz3
RiMIGvDpliWU/R1jgdi3RENtP8QGhjiwVDGYJBB26ZQGDkd4HYPZj9bROMknnRFk
fbP/P+cmvXrwScvrXLFkNQe/4LfJAJnod4ropQ84z/Cu2JaFbJau4TSlbp57y9ON
7fpK9SV4bTCyO34Uew==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAxJyt1z5eVQ8ewO+c3Z8XFJJCuGsoGWscFx6VqIXBHW9a3YRf
EStVShEYD7WO9Dljg149LjpmmrY37KJuJaNoLR+xLhbZS88B90PifE6nlJpMHLkD
Rcj4lTxCZshGWz4L99J5hpdIqnkmrjW5MoZz5DBP2moJ5Xs3vgNJzqS82HuYgFzh
gI7MyeFCrkcpBUJ4M9RHF+HsTD6/Xd0tgOrtXo7WMGN+DdGb9FEWczvyRxznVBM6
j1lKzkeBqFR4zhiKsfPy6OzCGDHMfjdN44Z2zcxwYq/UcwcokqKdfTYgckGpXpTn
L8Af3bCLRKkgqAngYlqP9V1r+Q9S1/lwvaw63QIDAQABAoIBAQCCiJ9mM3M/t80s
PSmBflk9gCmp9QLndVRuCRFFlJ/1IfRK89KGPKXgid7WtyzU+rEWC+BEq+NpPid9
BQ5qEMZehxcESYk1KpfciYZ6D2J1Z8e2niMNFvPraNmREdCcTWJ2w5eJgzFsdQj4
SpTNdbTFwEmT8ijl4dvNXLOI7ZivhJ1vVGW2qUJQyR5xo+GuXZZVVJW6vndkzHoA
SJDSii4PMAisibu+Uw8JQM1rYFD2olvSWSnjz+kCk2IbrqhVPOW9PoYETn2DxXfY
0miUGq4KwtNjFnqJO+CDTJL6Y7LrZbKdSVTKfiY5ReLckWhx4RKn01T9RhsrS979
bee+iynlAoGBAOVDKy9t3kN7FTQJyMGa3QAVUV8B5OPUIyuUOhzMrbIFECtYFXxY
jsnRNf1i2PbVsl9WDrPikNCVyn5Tf4xU7YUH7/Jo+Ha978Fu9tNf95RK+A2GKtiO
8jQa6nkII8Fmql9o3xjCEQD0VBirrm1h3sbC3ylKL4u+yC0a6W7vFRRfAoGBANuK
tIvCRGoEyn8nzSXUez7+/9sIwU7jV6lXL7qgdukr7o/ZBqer48CDlehXU48yiVpB
TuMZnloj/Ndf9X07t/yYwfCP7lpdq6lR7EhG5yS0WjgHjv5+QKLVA7ocrEufgBKA
Wdb+96eyOb1bgBMprtYA8wbhblhut8rk+i8SkdpDAoGBAJPxY0XI/VvIamdZRP6C
Y8iw+QB5JW5Lr6JolZOOFSxCjiuaPMswDnx3kAKCLgYi4mcZDoXQV6jycGhSk4VI
LLni5FkmmeduK/BEHe1LCWXddvKrOKzkmbE8CUCvGvurt5e4AfdlDDn0cOw2pWma
dXsHt6BaahIgu+Y3bpmSmKS7AoGBAJGlhX2qleSX5RdSemD8g/8ZAPh46HXDkSaA
9uMjDYBHau73ldiqyl+vbEZFmOPJLSI5bXJwToOSmGzx9lTsx3TGivNjKUe+GOIt
vUl98F6DX88IBK+FZrAgDWbtGsjYugiMKg3d/MK+FFwxVdhgsOK8on4QFDqKyVfL
bMbJ1QUtAoGBAK4nM5GZyefXylAnq+pzGTStu0Q1AVzN0iWO0G0Q6gaItvk82Scf
wVoEGE9a2pDCR1I1mbJn1jRxd6/GtXvR4TJSxPILS+B8aZFkxl9WIWjv/mnKJkVL
PmSNPbieuban9TRBu6nQfWOfLMHmBnG/QXbx2X1WCRoXjICT41gJ5et3
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRsb2Nh
bGhvc3RAMTQ5NjMzMDY1NzAgFw03MDAxMDEwMDAwMDBaGA8yMTE3MDUwODE1MjQx
N1owHzEdMBsGA1UEAwwUbG9jYWxob3N0QDE0OTYzMzA2NTcwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDhpeY6j6ujZc2W0paC9pmVHlEhbnsH/L0N3JjA
WooyZWgsARSlv95Vh2M1gVymu7mah3HHzQkVe6vN5bfngOO4A2d3fxYP0304+3YE
TCariZzcEk9Ot90VHNasTVMOqZ2qdGRUvEXbpRWlIhA+T9FgNxpBDQV5yb5w67Dl
kBM22wn5l/YM/4HqSOTVQON1SmmLUPlnWtzxVRDKPzI2zxpKkzW4U1bYgdwM/Xeo
B2GjU+1UsBlJWGrRQhCkiFuZ92agls1bsu3+PE2leoQf6vZE+pc0m89v0lqlcgOQ
TcI19BFvLjUneDbu4kFZWDO+f23hhEcHut26IXtOoAIvk9uhAgMBAAGjTjBMMA4G
A1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
AQH/MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAHlVy
2H1FxDXAJKeZkFCNAHOiThcm79s7P8+BXnYbQMgdHAiQIQFQDDwbntA607m0FRoZ
1H1n/rzkRXRrNJRPEd3aSnurq6oMMZa1Pai4e2f+kyThpIw6FtI2y/Jb7lKB428g
9AHtKSd71MEArOvWHi1IcX6rmH/vU5ezBgPpjtEC/5bI8PJMqr2osesjppKwwwjf
BTq+AjvxltPDb+fykto41IMzpM7KRO/vat/dZ5OZZOhDpbJlAAWUfCdlWcL0kqTt
ok+biBB4xShOwpSyKCmJg4otlvN7qJ34fCjYkQY+PXzGS6YV34Wj64F8mReTt4LO
glampIa1cLUWPMlYPQ==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA4aXmOo+ro2XNltKWgvaZlR5RIW57B/y9DdyYwFqKMmVoLAEU
pb/eVYdjNYFcpru5modxx80JFXurzeW354DjuANnd38WD9N9OPt2BEwmq4mc3BJP
TrfdFRzWrE1TDqmdqnRkVLxF26UVpSIQPk/RYDcaQQ0Fecm+cOuw5ZATNtsJ+Zf2
DP+B6kjk1UDjdUppi1D5Z1rc8VUQyj8yNs8aSpM1uFNW2IHcDP13qAdho1PtVLAZ
SVhq0UIQpIhbmfdmoJbNW7Lt/jxNpXqEH+r2RPqXNJvPb9JapXIDkE3CNfQRby41
J3g27uJBWVgzvn9t4YRHB7rduiF7TqACL5PboQIDAQABAoIBABg+G6MVBmqyqEjY
7SS627tCm5LM7Njp1WHk3mBkGc7aSDvaDd7U400rE4SPFmYvavxoKtg9dglh1Q50
7LIpjc2aSAZ1l+gxX5W8pmxFxi3NxhV6vhyF8nGvPH0cfZzoqjDC+niEFblIXi/0
newj5SnCIcb1YC46d4RoPgQb24ScBiQi3DeuqiDPQO3cyePyr5TEpeL4djB8YgK3
yAgKFotsox1sMz1r0AX4JdoC2cYBh6qmGs+qdkMYW3O5XKjkkWS7dglqHIchhsrr
P38kuyp30fMrE40+5G9hKi3WO/nl3FP/eDM8BD60QF1edXobszfd8BsVnrBfbfjP
wDP51rECgYEA5/hLWVPXvTkSJeWzkEYx2PvubClr4fZE8j4oYTikD3q86R6UkEhe
c/yZd9WNBgwUrLG4SV3TaqEWvyUEnsaNbI1YK7e4PrhMSLLZ0G040jW8TA1SE2kT
fnJ5g4ZRWTNvksBTcs3QtMC8FzFazQ7gKIYz+SXopxeww5D8T/ok/WUCgYEA+QXz
/mYUnUMt4kWfcMDDbiYCgVSJK71E5M5qH2yxjB7NFLcl0+udQ5FrrW2YEgr3DnSV
Okbg4ZcmdnjCOU7Xu5BlU0GWe5lXiOBf0sUxrNU3OY5voVPYgSXJgTPRzguTpetA
2Liv/7x5dZ0krIKRTCKWu0iwcdOOaPrEouln740CgYBcrrckXLyBLTFUFP/2sCUw
hhBQBQjGwsfvZehqV+AtEyQxnHdNYqzieNW0rI1pEiCck3GHVsam/vAAtUCwU7Kj
4aGN7hzeanKIRXWDsnAWhUPTuyrE+KHCTujAJUWRd1yn7NIqt5Mg9TTxq1WVQtFL
P7u6W2wmfu+wY9h79/0ZhQKBgQCQoRt/wmvwlmbcxxD+HIakv6Cu2pk8dRXBSf0q
uSc5OV5kzKZEudBBwQDn3gH3YLTyiVVuXmIXw4DMSACN5jO3tPqUa0BqErpbPe2J
bYwXVFJZtFczPI4/G+I3jxAoemLLd6HW/RUDE8T7QUReiQq4TFJ8/Y+03GkWoaZ4
89SwtQKBgQCmWHxz0eXQk3YRk0kupSrK88pZoc9wvcWYhzkiQpP6+hYPxH+n+U6s
NjHnQAVlRUIP0VisQRkBC4iy2ca/gVVhKgygVBuHC97fKAHlpUlO6Y7Ow031stAy
K3zrTjuVwsz8C6bFCxHl5ojP+0cSoK7qej09vj/te1DNvkYuE/0j4Q==
-----END RSA PRIVATE KEY-----

View file

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDOTCCAiGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAvMS0wKwYDVQQDDCRhcGlz
ZXJ2ZXItbG9vcGJhY2stY2xpZW50QDE0OTYzMzA2NTgwIBcNNzAwMTAxMDAwMDAw
WhgPMjExNzA1MDgxNTI0MThaMC8xLTArBgNVBAMMJGFwaXNlcnZlci1sb29wYmFj
ay1jbGllbnRAMTQ5NjMzMDY1ODCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAPGTpeR8wkr9yxMV8LLI7UaImRF6T0ZT2HL7cmjZDbV3Ow9L8UXvlQ0SdKbT
ph2IU5Ww4h3xSiXXXGh0FSXALs1Ieg8DmMYKHNcnOXpShwuXBJ0NCpPSLnAO+2hg
Jq+ULvBTGjQhvCX+cjI5IquYp0sSbERS/+46cGrwWoye67Z9Xo6enNjHG8YZf35B
Wspvm0awUw1KcXkp+lJ9zoblBeXm+uXdMhzEyRpghRslwvtPKyU8YhTN63p0Ru1E
Kig3pctotL2n1/7+tR8yhjEstLiydHYWhLkX6wsUv+rNirUGVUBjF5VFtERzMPRK
0dh/hs3v2JSby56IxRcqqTSzvfECAwEAAaNeMFwwDgYDVR0PAQH/BAQDAgKkMBMG
A1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wJAYDVR0RBB0wG4IZ
YXBpc2VydmVyLWxvb3BiYWNrLWNsaWVudDANBgkqhkiG9w0BAQsFAAOCAQEAEEd0
MOqC0POVtxGCy/BHR6sVcf1r1BVTt28p7u1yJIdrOKuknlILt9iSNfFcNPRWIP+u
7SNPT8td6yViBgn7UMjvFxZgQn6kTsgTfdRV7eMxoX/jYzt0g44k0oVGv+xiJ+8q
m5Ng+tMOMuY1+JganxNad3mwdff4uKxXuVQsV+mjXBwaJHIdxBmCFqg/dGB60lyb
fxFjuTAT8xyL+UE3VUoCLFdNDgIsm0v3uj8nBsASW1LeZSbMQKUJXGlvEpj5U89d
B9ZouygfISPKQ02WYYDfSmtGCqX5sFZRh2uSPp0/UPUB+hUk7C0KN6TgsB9kOAD1
Sa0X3+KhMRVOk7riaQ==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA8ZOl5HzCSv3LExXwssjtRoiZEXpPRlPYcvtyaNkNtXc7D0vx
Re+VDRJ0ptOmHYhTlbDiHfFKJddcaHQVJcAuzUh6DwOYxgoc1yc5elKHC5cEnQ0K
k9IucA77aGAmr5Qu8FMaNCG8Jf5yMjkiq5inSxJsRFL/7jpwavBajJ7rtn1ejp6c
2Mcbxhl/fkFaym+bRrBTDUpxeSn6Un3OhuUF5eb65d0yHMTJGmCFGyXC+08rJTxi
FM3renRG7UQqKDely2i0vafX/v61HzKGMSy0uLJ0dhaEuRfrCxS/6s2KtQZVQGMX
lUW0RHMw9ErR2H+Gze/YlJvLnojFFyqpNLO98QIDAQABAoIBAQCu+8LrSU83ILSr
I3ZRGmd8cs9lMJIhn/Ql/Wq29ZoPesjl4J1oig0OBfNHMAGgXLImuJRhGfkahe+i
nYr/6EA+ZCQ9od5UfNi9I+VnbEXzqDUoSV0DC/ilJMUIqrT68zw4ygkt/82YO8Ge
DXLGlvF+2F5h8zj854msfGMqfqbAbJDynA0ZDuq8SORUb/1RIiAZRrABZ1n6HsXv
cZb12wcycbxKTPfISkJDJ+PUVmCcm/eO3jLDgy2hAY5bCG6+HIO/N407Ze83TA81
nBRM2WEA9BJbRmmB2yAEy2aJamT5lo5qORElmW0gEpAEceRgYi/VjPyvwjOw7IW3
SlEDaYbNAoGBAPzt80mwWzgLH0SIqUadXbJILqZ1j/0sCr4r9CfIPppbZckaXbaC
iokh3dRaVBndI8z/aPz5+wmvNjqxQ4Jz+411ILK7G0ogYRJcTaQB7Vc2QTPtdu3t
HE1xCktyTAcyVJtrVYfpqaAKZ/eEnDmatxrMW1nmnFlZxBZErnSAjMl7AoGBAPSC
anORXVXqbM6d7XKXiXYLkagmB92LP36DQ3OXiVIH3CD2weItRvqup9+nIdMJMDyN
PR9CXSQXclFjVqpgPFKEAUNcGbTea45Kykw07CUYEdYRXRYtij0hhsRon+T6vsE7
eEQQySG0ub6cTsFi/AuxWlPCkQLIhfCkVW4kvqyDAoGBAKBXEGXkgM9Ubav6nqT/
WciNbdVkrbUnWGAaaRtXHCiqTCpiponu03f/mirmYG5M/EcWmy3UI0qOYmhuu4dV
k2CnoeTsLG6ffCoxjZ6tAhLJeCg9H6nRKKIHSSlu+beONoiYUI5qmHe2YRZOYOM7
q1w3tKmXQgJ6n44mWBnO7HFFAoGBAJF5Rnyb8Dw9zZ7gt3WGU3K4T2ELv4JAZvaO
goMzlbAInR+/sHsxFRnw8FBQz/8PaHOeVM9UXLYsfQePYvDbcQhnIZlCIslJzBRM
yy+sDRPEzlfe3NNlJOpaGCSjWv6RcQzmpRdaJVC4hgqyVOkaS3tpeaR8Dmrpg0c5
ftqZYhihAoGAMRbt+21F1BD3AGDseGIg4MQIEPfk7nh/cVFBcnXAqV49dmm1/g7G
L2itzJlTb1oOnlY9qBksocvnj3w8hsrWFvt/s0wv1e3uUJ1elCb18Rm4UQtr9td6
XFqEgDaDYyj1iEmn+PrjQc+DqHn6JySLW4LTM82KWwDnMttvwLFhaOM=
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRsb2Nh
bGhvc3RAMTQ5NjMzMDY1NjAgFw03MDAxMDEwMDAwMDBaGA8yMTE3MDUwODE1MjQx
NlowHzEdMBsGA1UEAwwUbG9jYWxob3N0QDE0OTYzMzA2NTYwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDozgb3GKC3n9wREqYUm92/T0+NnEYkaWOitXAH
BW/Gj9PAF4sggnAey62H7mkBaUpw3s3RXyrqnRqrXVWvWgbSpf0Elp3zfos4raEf
hgWhqMh/SEf5ETHNRyfebxTSeQYRuZsvAPEsRWKf3AKFEykfW9y/GnKYM1elL0nx
Fgh0tq943epjHuzKfVWp8LxnFd7fyL+BQsvBNnYz0SH0F9oTUU3nDCS6Os/+EtNQ
SjP54HQ78XczI9AMvsFUS7yFEQdM8VW9uYhdv/Oyi9SLywhOCHSnV9buJ6F8URzO
B5Cawdteew7U+EAXbEwY2D84NARF/J8iA3Eh7+UKd27WA+pJAgMBAAGjTjBMMA4G
A1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
AQH/MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEA5vjR
Q4Rjx8ko8+Zt9gRceKCVWEmdZLGkjwdW04YaIcrh41svm+tgxwUMdQ2beNjUbLZX
DAVvK99HiANBc9Ee4DyQFkLI2z0EiAJpuxMjZlyzMmKLeijOkIkxYIshhfc2Y2Fr
NIf6OXmFBEjqsudxkQiGfSqc443GseJmmiuhW3JR1a6ARgut4AtPsPv2TijX7tcR
Z9tjzAMiubNDQq0g/Eh9vLZkv8tNTqoJ5N5XV8jcovNWRcIIzl+lXBfy0o68LwO4
Hn/MzcMxOdbNfFw5nMaDuQK4oO0vT3D9nedQkdV3keLZtagN9/PO1XtkCSQ5q7Le
LS6fs5zA/9sR2kEuGw==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA6M4G9xigt5/cERKmFJvdv09PjZxGJGljorVwBwVvxo/TwBeL
IIJwHsuth+5pAWlKcN7N0V8q6p0aq11Vr1oG0qX9BJad836LOK2hH4YFoajIf0hH
+RExzUcn3m8U0nkGEbmbLwDxLEVin9wChRMpH1vcvxpymDNXpS9J8RYIdLaveN3q
Yx7syn1VqfC8ZxXe38i/gULLwTZ2M9Eh9BfaE1FN5wwkujrP/hLTUEoz+eB0O/F3
MyPQDL7BVEu8hREHTPFVvbmIXb/zsovUi8sITgh0p1fW7iehfFEczgeQmsHbXnsO
1PhAF2xMGNg/ODQERfyfIgNxIe/lCndu1gPqSQIDAQABAoIBAQDBke1Ar32SsJzu
pS7qm975HxzqhjHcPLTXqT0I2YBi/Vv3sJvB4DMRqBEVoSo2PjHjP56qpBKvGQn/
yo9gSd66b8G9eu7Cc0mXSudZqror/IcZLiSJ974NDUkdLgeGKZLeUhGyN3pQcTDb
8rc1pwTeSid1zqvR2X7ankNA7i0BvP6UXlvWM8kUvfFDwPXhHPZtkFELc4TeGIzB
e/l9Uk0DAgplkhM32s+0rTZ7wmcSUK9eh7/kiQCUKvdCVwegsK9iCVMaYIMggut1
3KEHuVIbuLdjFu/tF4SN85funMIPxD7rKf38aDrR1vhQc5tq88dKwVrD8EANcA/f
34mAWSIBAoGBAPZLAGJ2McxIoK7ymyvGdNta0glzzbww20xLB3MHYpo+cgff4DN0
3nA/es49So1m/r0MKjnC0qxVMceTrU4u43nROwbLNlFBZ8hzoEVobCCoykWKkhQO
hS6PkxBZ2s8nsSQCr3X+CpHd+JqT7Y98ydWVnE+kDOFIPr16j1SbBJS5AoGBAPH6
73BDmHk4fBb9J2edNtx+7/1/+Lbb96qyy7LnA9zxdXgqjeZwFW/tNjtD2l+TPWlR
O1L0NkfnhzWPd7rDlQA7Tzr0TLF/H34+ycbaorc22M6GEg9zJwRY42EGbYJE5uRZ
Sq4jXOTOAta/lb+UjqWI7eYyH89SKlYcDut1VVoRAoGBANucUvJ427yBAi4dC3nE
EszJNcHR7aAhnFlyCWlrK2PHzJvy5xJa4kEKiXE+u6xMK1bccwZgDsQ1jxabOowd
SR6FD3HmXz+i52yYlU9uZgPVHc9WWRzjOaQjx4lr5ayTguNONbArXrdkStlikBBC
wCHfrqpg6E80wSTpFMbnFvWZAoGAPNSDOqqTeu27LCBJtp9r+jQi6owwHu/fK9c0
o+21J+a/TA0OjP5iNTfhqTZegCZIiuAz1J+1wY+visRdKeMe4ucF/OELIpe9eAQa
v1WZEAvGpuxHQAEHru0Vg/+UqeneTUsxHeOf55juV8Pjxjx3O5/XhkXVf029fNYi
3ggqA7ECgYEAgowePEGL7l27raa6F9h4wuosfuFFdrrcOJeGxNV9jAe2s2KKoQFO
lCv/AcCsRepf9G235TqQ+if8a4p7OsHL9kxOspKBJKnwq3u+jgcJTl+HLWgnDjIc
Yk2xlnRC4N4OiGQrxjOmFXQdXiBK3T4CJnw5g0MynrdwiJCCCnEIG+A=
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDFTCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRsb2Nh
bGhvc3RAMTQ5NjMzMDY1OTAgFw03MDAxMDEwMDAwMDBaGA8yMTE3MDUwODE1MjQx
OVowHzEdMBsGA1UEAwwUbG9jYWxob3N0QDE0OTYzMzA2NTkwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQD2NQ2T3x1nBhqWOCjiED71R+j80XsFxSMZXNJB
wUCCGZysf6OGO6M+NYZ2qov58gfGrcZYBJWwuSRF+0NeoVzkCu3AJkTKXSdtv2vi
M248yBUhvwPwbTcoMyG7p6LhLlmlPpP/7n9yVfVtRzFgy4UPYAlJmTnakk9jDKXM
gi717BjntQ01+2I/HIZls5B5vLwWuHtWrdng39Evn1RwtJrLUVJE5b6/+otvokXB
DpKMb/aI230uw9D3Csi+6iAKK9JuhCEe/S3ezJWg5SugG6UrwGBLZYavXP7iiB9p
u32XIe8lMqQfsiur7iCQehwxTdMJ7uR5STS5eqfoV2SdnlN/AgMBAAGjWjBYMA4G
A1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
AQH/MCAGA1UdEQQZMBeCCWxvY2FsaG9zdIcECgAAAYcEfwAAATANBgkqhkiG9w0B
AQsFAAOCAQEAuVVTMV237szi8c9Oy5ulsmgYkp6MqfkKryy6Zi6r5UtJAWToAFu3
dvHKnSQNgiWDewVKn+pMGNZuo3o6PGxdq/E2ZQ6/oCxNRThb7hC5IK4Yot40EUQ4
ZavrmON0velXZJ3MwbzSzstLnJCR3maO+Ko0KPC/u4LADCQyeVrEZ4IP/RPQ2KZt
mdmeHTm53tLPrcNSnaoW+tMs6JxAT30xOLnjwYfEtbMCVhGB+aHQIGFS620qZ+dn
oTqrjempIIos0Vu68ZhCaDwgB9FiNtFMplxrBvOIfWO1Dz9AwPxP1fCMlZwtFJt8
vuRu05WPJelfnEKXIuGhuGf369xyfw5cIA==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEA9jUNk98dZwYaljgo4hA+9Ufo/NF7BcUjGVzSQcFAghmcrH+j
hjujPjWGdqqL+fIHxq3GWASVsLkkRftDXqFc5ArtwCZEyl0nbb9r4jNuPMgVIb8D
8G03KDMhu6ei4S5ZpT6T/+5/clX1bUcxYMuFD2AJSZk52pJPYwylzIIu9ewY57UN
NftiPxyGZbOQeby8Frh7Vq3Z4N/RL59UcLSay1FSROW+v/qLb6JFwQ6SjG/2iNt9
LsPQ9wrIvuogCivSboQhHv0t3syVoOUroBulK8BgS2WGr1z+4ogfabt9lyHvJTKk
H7Irq+4gkHocMU3TCe7keUk0uXqn6FdknZ5TfwIDAQABAoIBAQDKBSg58HZAxcpk
/nnVXL08KCAt6nlGd8bgFcDUgYy78+YRyL3V8/NzpRnLHw+yLH1i8c3xBPoKwy/X
WF+kM0V0PN9urjf90Ef9DneoaMkIiKiGQyUpT4RVsPb58oh8urqGE33/H6RlndC/
NQ54PSbWQdf9EwRcq3cGcCdPUnbUA6Ur/m03kIWYMnPKZeXqTCA8AMfsLuoXyf3G
H9Q/ReGWok2fOkw8I/2D2HOXh+vnPoOOWbxf75DAt7tk2tRrgvQ7cI6TOJo6AImD
E1rHU7lrVhyo2AK+8BfjihGgtgt1qCwEeviuJn20wRQwTpi3r+aflaVCgsZADoc5
6bEkjP/pAoGBAPZVyIIg4W44lHz7xu0DMAbdK2CwaYPPhEewVNTCmtq6wvP/CKz/
gZRv7HLFrEAPuMuSPi0+0lzbUf4IfHeP79jzPEsoHlzaSyKVho91uttsTYGRS0yD
M2Kd7uavXH/lRR16h6TlGRgNTAfWh8I+WimzBi1IHP8LQPv9hS5CUseLAoGBAP/d
/FLXTQJ4bsb3Yune/xoerrMo05cUY8BM6WZ3s2wdPSMnTkh921CyzGzfWLpDHrww
rFBrbs0O7YijRU5gjwddxVMT/qcwNEETEWw38X3kfPx8TCYKT5OsD3HoiTKMUVJG
ehdADEm8mh918pgSvW/hvFaW98rIWjVwTt9djkJdAoGBAOkQJJMp9N/J3IOb24wn
desomn/yqQIy9p61MK3UKZtFFGqyI006uSjt9DJq4BwEQ/0nMniwdWuxfhPxh4G/
x1fmWmdpX8DOMERaYGOfKdms0HAEPc/4Wke10XCk/clr7EaFxYy3zVQ6UHMiAHRB
diD55qA1GGoeBa/lH3qy+YsdAoGBAKP4j65LGaf+pAdQHIyCAvurNAvOmyKTx4VC
BMVM6qfbaQWxN8LPHRBvNQCEdWkQgI7bpmH3J0+0RFcvQsgCq0ZuOnmOM8DS7HnT
jXPLujASkGKCCY7LRsn9FvzJp7rEH41Zav6hZf6PJjoJLUl1oP8lK7xWDUBf+yeJ
7JHKc9NlAoGBAMhca2WXdxX1R61zdjqa7Da9fC1a8cdkPHsN9CA6ZMxGgfrbZHsY
YbqPhjuupOJe5B5MZ12oRqye3u6SuzoJMcSBOjKMLX/ME5fOxNRiAGgm1WEbDeFJ
3+ZyymShyUbpRLOg4fUC1vptzSJ1i93rXOZI6Vs0rWMj+PKDEbmFLiT8
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDDBN0ZXN0
LmNvbUAxNDk2MzMwNjU5MCAXDTcwMDEwMTAwMDAwMFoYDzIxMTcwNTA4MTUyNDE5
WjAeMRwwGgYDVQQDDBN0ZXN0LmNvbUAxNDk2MzMwNjU5MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAwRrWvzL0zR9OFg1ipRU+Pvd9v+WN4ILKxU2jxlut
vZOevsyxflzRNOQImejpdXEtA/yZTKNYOdU+dMdPDdSsMPFzRgzQfiJDKqJw4jB+
SF2XROuZEYMjxoJtueAGnw1DO9tgzn40NtHrPJNKadrYljEF+zycNLcPf78GK/ug
+5rK5MkpNVmxO4yjqxk5ZB3KD4Gol3RaheVKYmTfjNIAO0wk8O2NbvG/JxOPTpDS
aElgpfdy3Bxv4JJpjgNKrHEUYUqkwlqmrinTw8MSfKJq0MJ8nIx8HRojrYrf6Jh8
zDpPYuqlUygcwSz0sQEY6UceaupQmlvDT9QLLVBm6aOibQIDAQABo1MwUTAOBgNV
HQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
/zAZBgNVHREEEjAQggh0ZXN0LmNvbYcECgAAATANBgkqhkiG9w0BAQsFAAOCAQEA
jdyL+g1UlZ9UsnYA60iu3n9O8nzUU13z6d3TR3vFBiaTAiVYgcEoL5BmhZgn5jcW
AlQqmVicma4rypK50GZL7/fJBwiaUjkGNBh4ybfO2TRyXbmTCtMg+1FUe/LptxD4
IWvJvf/Y7Vv/Lb1Gh0MXUcNeyLThletz6tJI3NAQX3meZm2J6/PBoERP7KXo7C4F
K21/V3aD8Zj7gs7LQLePz14ARAuovd/LDFmAHBcL3EIXiegDGe2c34IKupTPiysE
KIWAgOWSBQ9iZw4Ak7eFSDPIO9Xj1X+D1Bcb2EMcYw/9xJt8JFAqFFjMHPcKxCkQ
oY+IHfc38TMOEeAr8N+W2g==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwRrWvzL0zR9OFg1ipRU+Pvd9v+WN4ILKxU2jxlutvZOevsyx
flzRNOQImejpdXEtA/yZTKNYOdU+dMdPDdSsMPFzRgzQfiJDKqJw4jB+SF2XROuZ
EYMjxoJtueAGnw1DO9tgzn40NtHrPJNKadrYljEF+zycNLcPf78GK/ug+5rK5Mkp
NVmxO4yjqxk5ZB3KD4Gol3RaheVKYmTfjNIAO0wk8O2NbvG/JxOPTpDSaElgpfdy
3Bxv4JJpjgNKrHEUYUqkwlqmrinTw8MSfKJq0MJ8nIx8HRojrYrf6Jh8zDpPYuql
UygcwSz0sQEY6UceaupQmlvDT9QLLVBm6aOibQIDAQABAoIBACeo73oNaSHH0C3P
SfdFyab9BaKn7t+xfRvQulY+9gv9iZj+SWX+gikuvGV/5JLuT6SF+KY41iHqng01
8hKRH1xd+qLkdt2xA8J54l1SQF10e2D4UlO6b1qR5x9J15JLEwf0IonGecrYikvC
pIHhJKKUJvpWlG5vOouuHAJkh8ekwz17cEle6IOPY+p9hiivk80npUzbdybnHK26
pNGPICich8sUWNaiSUia/TCwIDzU4NkNhsOxoV/QZOTOFnHl4lR81dSeptdVusPg
JdC0+VCMc8wGgXdDqWxPzDdT7CIGNZcqpltBEe/27QPVYxckK3kDkOFRXshD5lPv
56USHrECgYEA9pB5ChHu9JqkedN8QgUfipl/doAOz80vghG0gi+wvkeD/XrzXaES
rXlkGJFKbPkTGhemSzBPQNEbpQa8Y+h+GfKLHux+Kd7pWRym4vXojxeJyaCCG73i
d2qtoHi5QeZOY36AJT7bq3T1ZKHG157vEXRghth+ozpzb1w+KeO1ZNcCgYEAyH6j
FpowWYdsPljqx+7SfoPdes/LGtfIWnGRM12C6H2Lb9FX6l1yIMNJQgYhyRPSbifL
uArj/Jf9LskaaRYHvjXbtBANHHU92kBZszMj3x437otADwL9iWsV/cyIDkei5QTl
fP//M5RSZj2WyRQJ8hXTA7Ya5e3rGJYlHQ1aRlsCgYBJMK2dXaFvHpCAUVTrTBYG
0HXTuUOsT54woAzTMFDoytXVYq/nNS8UK5qY6FgNbQpMjoSggSClfu0T2aIGjjcQ
gLznWxBAYZknCKhJavGzuCsAnRLCJWWaSSJtJijn9POD+UMUy0nt5XQKgTNDQjx5
E/CrVoyQ64LkpZ8WVC++VQKBgQCLUMu8cem03FAPxrNluAKWHMTyiJ8mCNjUV+PA
YHMNX+dbDIlddg9OysQF18L0OQzYtFhvi0m+hFJOhzkN2lwJBN2kch7aLnGLTXnG
9nsvl4zf+ezKQZaxPTLrx4qm+YosP0nDoRLQ4XicSKGVGZKLoDSfeJOaP8dDr1kc
peGbzwKBgQC3Tz57NVbIDa3SdMlpgV5iUj+AQ4Q4OLxAWzePttic1+nalQyFLMBI
dq4HytJEen/CK/XwelHpMfMv/t2bZcqags+Epm9Qhd7jLMaFsmrscmdjXdp41kRR
KOjZHBUuhK7HfeBLJ7oLGDlXHvtfwC59DRLipYrtJLG4Etkcjfav8Q==
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDDzCCAfegAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRsb2Nh
bGhvc3RAMTQ5NjMzMDY1NDAgFw03MDAxMDEwMDAwMDBaGA8yMTE3MDUwODE1MjQx
NFowHzEdMBsGA1UEAwwUbG9jYWxob3N0QDE0OTYzMzA2NTQwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQD8AqsPL2/NyBR9LSPSkLedA2pI5DeQ+z7Zfp9K
8EmbCcawXZabrkPCr0LwNyylynssZXqGLYWvwpl8zGk0E2Gr++diIg17uZ4lZUKN
jYhGdV8Y1Bx2Wl1At4LLe+bmxv01hslT4EDRzHfZU/wy/gTtYMJLuQOKqddW3lkV
FXNgJOVGmIm1tehwDCJhL9NIyQcy6w4i86e5B8q+pk3V95mOlKR7lHhk1ZFsMCKh
TCYG/qyu+ZUkKvZCoyV9SwuaN8Df3TT6MYSX2ulvmze2okfePwTeKMpwudrfn440
0TWrWKJuMdNe9bHXsvhvPZlqBCouIqPC1KiuMg+Pcx0laz9BAgMBAAGjVDBSMA4G
A1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
AQH/MBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOC
AQEAXHWVJ8fuPrF7Bgj3i7fyH0wE6mHeUMSy/bxXdbrkY/H1wGMxHZa8uAodXeNa
Nb3pBzroqZcVWjkh56knGgeZE4vmkWWZZ3G0Bj5jr3kzhZ9Tpd1LyFOhIdUttHPs
DOVfD7AIl3v4xlMDwlYYQKrt1LzHZX1n7sJHJK1s5pL/lB9PdUGka6XLXo7meKPL
KluKBF729MfwBx/GNrQEh4Hw9/KVnBisKKdWqowrKzVzGj3hv2q+wCxDuUZyl7Cs
nw07CGIn+t7+5/jRyEoIwUiOydvaOFkXgOpWxHQVEj+7Hw+rstDbMDQ2+p4LpJjC
bLBrGtpaitmhTPyT+SSR7AZisg==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA/AKrDy9vzcgUfS0j0pC3nQNqSOQ3kPs+2X6fSvBJmwnGsF2W
m65Dwq9C8Dcspcp7LGV6hi2Fr8KZfMxpNBNhq/vnYiINe7meJWVCjY2IRnVfGNQc
dlpdQLeCy3vm5sb9NYbJU+BA0cx32VP8Mv4E7WDCS7kDiqnXVt5ZFRVzYCTlRpiJ
tbXocAwiYS/TSMkHMusOIvOnuQfKvqZN1feZjpSke5R4ZNWRbDAioUwmBv6srvmV
JCr2QqMlfUsLmjfA3900+jGEl9rpb5s3tqJH3j8E3ijKcLna35+ONNE1q1iibjHT
XvWx17L4bz2ZagQqLiKjwtSorjIPj3MdJWs/QQIDAQABAoIBACnOZ4JFZE7G+l8p
td+gklVB08fz1CQBChQapWJRKmTF4mgvnmSFLEiHupsPDCEUzgo3a3QG6T6XEc9a
Obmh3Xoid+gXrhTwXgPmIHxLFmhZ4ETGK1KW+xFZYCFRlKSELk1K5us7MIWdXeR3
wkbTTQ30F0dokeXW9xo6GJ0eyd0Q7ZHzePI+Gh9ORLNeRdyk7Px6cFuDtox78r7b
teQMGNeEWtdJArWIbjxPprp84uo4qb876W2TBCt/rrFp1CRfUXtjSYQXTEsvon76
is64X3lQhZR3HCbtjbRVhq76aNJQweVIO9CHkeLwyzGc/PyblNJNM1blIWbgQHvy
Zr8kYAECgYEA/0e4gCGohQILo4E1EnhWuHwKPACsBHkQ/D3JT2H/yo8IZUvnsPZJ
0iNhDnnPi892Xe9n7q8LG+9FVO8OYghfiF4n66dAEO8J0UFsA5CLNGr69noQEUvq
GJdOvX+NGFOzCaoX8ld3xTt9V+pvMFVPFKIoy6bns4PJlllG88vgweECgYEA/LiW
UKYomY3T8e7KgCIilkNrKWYv2qCKgoXMHerI+gz9BvcSdDRz7GHpkV9ZP9GJuJ3n
uLHdUNkFam5jtpwfqjiBvrINgqu8/jesPeocxQwJf3x5iGazxUCEK5YonePyuxih
e3pR+ez1b3wjBfVshs8tg58F0OdO3qVu+IZM6WECgYEAz+Ie7DD8o7vjsHWSu4TF
0hwo1W4Spt3UKCVEn2GFxPUveBYVCjW2Lerjqb8mdYsvsBU6hNOE722s/xphkxGQ
gmNyjOO5N1EBpKdrB4gY5EnOLxN9rCgu7XGiDI0nYE0gulkKgS4becEVNJfTjf+a
k4nuQNGdU8GZUtdHAVfgouECgYEA5ksrJxkTPEKjFf89ZyA0FmwfVh15lCfhsGlO
hSCzvjFhzhwOI/i8fncHCeMyiFZgwuyaAZOLkyYAqmkn0ntY25wcl3LXnvDzC1Ie
UllpmCKM8TCz6xlzzlcpWjK+lornYZljZgskcrR9P/eGTuLD3+Ehn6Sk/yYZmAh3
JF/17GECgYEA2x7fCZN+wr+CmG74GFdNnghqOkQQ9JleGOseHM+JIRD3J3WkuZ9x
Dfhe68OEaUBsmB3ttDiHQ5mS+YyXIZ2st6/0KQ7v6jEM3CwUoq+SZUqV8FEIBf0+
r3yQI5sB/ZxWvsshLtlLUgdAby+ava8mG9M5RwUjp8kdmzlkCa54cc8=
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRsb2Nh
bGhvc3RAMTQ5NjMzMDY1NTAgFw03MDAxMDEwMDAwMDBaGA8yMTE3MDUwODE1MjQx
NVowHzEdMBsGA1UEAwwUbG9jYWxob3N0QDE0OTYzMzA2NTUwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDfzQINLvjB3E/ea9+PW5SLys8Q4JqE9HUwLTJY
obQ6jqf4BTxtQWRpyu+uO3ALT+oCYifZGSlyYbBnnrCNClbwZYAHT6OkDD7f9w/q
xCYMosgvZhCCILHfolCYdFLMlYQg+pz4O+ct89J1pqyhEQBZRkUTLrXwqNUOfT1a
uBPbIkXBlBn9yG3c9zj7uRiSEiDA6ZcfxDLh2RCEjZezUfL0EExU/ZHCVghew9cg
OQ8T9dpMzCSQf8rG/higV4BZA66KCC+ETm2VXkqgWxJre+bnELHGXbiQxP5t21Jt
ZXjjYcUs1YluS0nLBJiZdn1lYGM2Nok8JAKzg19XYnlsPJXfAgMBAAGjTjBMMA4G
A1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
AQH/MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAIAyV
xfmc5DMKgPUt2y6GLICybnI07SaNdzZf/5L3nM9b20Q26VLdZK4TbFsBhR4lfFPU
F4oVwvYbw1bPT9u9RSy0wLRz8a/mJJX/cmh+OBhYJf7nrGuvYADHfi8zhfvnmgIG
6PFezabzKgz5jkr0rbj32mP6sPpN1dzeDWESl7rcCfS1UHX4dlCKxnRqAYUXS4Kz
f4VMQV8y9Q00/D9fH5xLxg2jyHVc+LfD9tA1r0EJKCB7p612k96YGFFqYqxQOYmr
/Ut+EN0w/YMHuzMjhaEi+9T6r/VMYr3l8B8U1z0PYPFbbvMCGZUf53cBEjjxjg4n
sQgzzTc/yfYXGcVVcA==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA380CDS74wdxP3mvfj1uUi8rPEOCahPR1MC0yWKG0Oo6n+AU8
bUFkacrvrjtwC0/qAmIn2RkpcmGwZ56wjQpW8GWAB0+jpAw+3/cP6sQmDKLIL2YQ
giCx36JQmHRSzJWEIPqc+DvnLfPSdaasoREAWUZFEy618KjVDn09WrgT2yJFwZQZ
/cht3Pc4+7kYkhIgwOmXH8Qy4dkQhI2Xs1Hy9BBMVP2RwlYIXsPXIDkPE/XaTMwk
kH/Kxv4YoFeAWQOuiggvhE5tlV5KoFsSa3vm5xCxxl24kMT+bdtSbWV442HFLNWJ
bktJywSYmXZ9ZWBjNjaJPCQCs4NfV2J5bDyV3wIDAQABAoIBADoAbsyt5MiRaZ2y
hXEpAfms6Tdtjn5wwMJopEi2N/iJKl0sNjn2naiRcMbOHr6/yl0txTgNvmEnkkl4
pjcXe+q305ORf06v4ce3x0dH6LiTo6AtKpAfGxpjfdHVK/Rbt6+4Y+At3j9Zlqzp
MopX1qhMfvB2xa6GgxlLOhB97p926VtKYOEoDke8uP48HJTabeiFKpn76k+0HwS0
J58Kb647DM1rqGpZW17dPb2ovi2eznATMeAgveNvVaXipUCsqnrJI+ywrLP4TBll
Mly+DZ42e1YnM+rd0tzgC/YNO8h7lF3YnthdQ77h3Yw09cvtmHPEuURgGiHIH5IY
HlYKBEkCgYEA7KXOLnufN1J7QG9CqsWhKI+ZBGGTd3LmXMHTM1tLpFcQaRNd/kxg
NVo8y4VEpCdatKj+yRe3wPZ8rO/Q074Rzys4VyTP8BNn9YjtttbP8zBDmLqkXAAc
xVKNDiuFQ40HSIdexZVP/YVCp210q6YG3OPoT4W9QvjfPHMfzW+2euUCgYEA8hpB
PMyl/RL2WHQHdKRRss0k8AWjtoKzepRkzrwptzgFOQZSmuvfdiPStp126iCinzag
EPsCo3T/1E1Fj4LgOIPhRd/ibXYSAParxmmx3rMTrBwo3TRf2c9nZPKAygsMNF59
T5ySJzU6tWE0PEtCIITW+WnQeORPzW3vE0nSzXMCgYEAwHyBP8foEryxahDVvkgw
NOjQgwLHS0KXY914Z+6bkVoMDBvnuV/wZZU3nCeTVJ56JjbLv0/edENJ1/Tc/E4+
+C7kjeVwiYuPZOVyzeTekqWwZftyky64ODxUZbE6nV6KDRGnIbm4rbMGK19ZpgDO
1j8/B+f1sRLzqZzOwnBako0CgYAVSR2nPM7zOzGRgVIOfUv5jnPK7pQAPemNw3JB
guWgCDrdUqL8n/RbrLaf+6Qy3X+uw9y64XbP7D956PlOrtEL5vxtaku8F/9pOkbM
AQr0a4Y1QZyOJkavHb8XbwLHwMTn7c7Sqw9mhxWMUOmIy40a0tMbxqPfnSMvCpVU
es8f5QKBgQCeFljjAnEQpuUduug/DAScAjTEbJD/18wAu1PuIM/+/zFEVaOs5w0z
uHiiPq3fiaaoBQHZofd8mxsugfLYftAP25Qw8X/WgWPg4CLGz3Br3HgnV6RyWXr6
R600l5lWe/ZU5epjEsToc20H9z9TbXaSKWMZiMIaMnAeboSfTtQY3g==
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDDBN0ZXN0
LmNvbUAxNDk2MzMwNjU4MCAXDTcwMDEwMTAwMDAwMFoYDzIxMTcwNTA4MTUyNDE4
WjAeMRwwGgYDVQQDDBN0ZXN0LmNvbUAxNDk2MzMwNjU4MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAtNQ9DkyC1Dza3OYBLcCPeJNKC8BvYVVTL+FFzpS5
AEPNdNA/Ezk42SBWNvaY+HYBrZniOSVMkTPFT9i5sT8bBBrUUvwGk1r6tpXDlHw3
/S7QThqZLkGcAscmahp0mWnIIGcJxZBVMR1cfFbYNumNR9UyjUDQumCb8hlT3q0n
pEH3CL3jRpdcFolczotUzGk9u07v81gIWgQVCyYYu3WuuRjviLq8SVoHlAWvVsdW
rGcG1LS/OKAgECYVUxs1Qauw5VjlXPGkY7gOOnjNcl3IHZU2+7dGFw1gBf+gEC5E
/qr2fI7Lwx7R71sY0PANuuDLGfrCAaCjG3HZ01MC5Ktx8wIDAQABo00wSzAOBgNV
HQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
/zATBgNVHREEDDAKggh0ZXN0LmNvbTANBgkqhkiG9w0BAQsFAAOCAQEApTrZW/xo
EMiNgBnRyqR0wHXpuQS/V5h75CtDKE4/Yl57cQlh30SX10anRgNpeBmaZjyFQp0P
3MAQoxUh8rOeQBcqZp7TiIXAk8w4rYWpFx59GGgiO5yD1vOj+9cIvAFDR0Fe1kbb
kLo8s93l4yNFTdxIIIDMgNpPcp2620wkecR3PwRV1jlM/7nMfgfRJagN2+Xq7mKC
ELzGatuXBpiy+jwCirmgmE8/MxbFbsR9L4EdW1kdYhRT+rnSNIEN3zI8GXWIH9Hu
91cGTgKiz1znXtlmLAaIoGTahv23YUV7tbQlrtZDZjpqvTN6yM3vS0HIoSxQnT2l
yizQSNuYSUD4HA==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAtNQ9DkyC1Dza3OYBLcCPeJNKC8BvYVVTL+FFzpS5AEPNdNA/
Ezk42SBWNvaY+HYBrZniOSVMkTPFT9i5sT8bBBrUUvwGk1r6tpXDlHw3/S7QThqZ
LkGcAscmahp0mWnIIGcJxZBVMR1cfFbYNumNR9UyjUDQumCb8hlT3q0npEH3CL3j
RpdcFolczotUzGk9u07v81gIWgQVCyYYu3WuuRjviLq8SVoHlAWvVsdWrGcG1LS/
OKAgECYVUxs1Qauw5VjlXPGkY7gOOnjNcl3IHZU2+7dGFw1gBf+gEC5E/qr2fI7L
wx7R71sY0PANuuDLGfrCAaCjG3HZ01MC5Ktx8wIDAQABAoIBAQCE4IjVHwfsKaOy
OxmWoy9KLQF2TTwLTAXK40DGoqanOSuasQQIfj313zs7PE4AFcb56GBCGdR/xokx
OVKKmpKhm412RVgXo/0jej2zuOvzkhjjq8vwH6oiqHDj+vi/WuoCVXTqxjqz0p2J
iM9s2oZEyLdS8NPwgNlXN6FXuemtWsao5ramsG8c0XM08zWazkrLGXf63dsNlEoL
9zzpP2nNx1DVVDTUhN3mpJE60IG0niBE1X3CuIrq4A/hIOut6qa9VgdOtnnwsAyG
hB3ZITeoaRowedmREUSSuzXnWkfHSa5yJoRlhje4mV+RRdhZ+1DSa1jzn1ZtZhjV
1/6KQWOxAoGBAMdTlTm+9c3rtqZVgva1WfwRpfd2B+HAy/LuBQMSNEmMXn5ooJSK
05eXMoCQC/+3fArCjYl+I7u9GHFKa4JeLjzWbqPbski5cTEHXIINGsnE9OI5pug9
EbiZvSeimlL9Lw2BUQVW777cfz0esI+Tg514mn43gV633BWZH/AP3LHtAoGBAOg+
R2EnDs60m0d0apR4kuZkoJDfF+uYgqyD+SMDDzgBSr4IDvWRzKAhYn+R1HsWeOxi
U+8kx5zmDVw+GHg397FcTIVfnuQKwmBZU/XC7oKVu6wshMXFbXUSQMmIuDbu5xmZ
1uZvMHtdszDwBWn5zGRSM3oXSQcwburJ6f1KSbdfAoGBAJE/8jUzV5GoG+BX3eiD
vL/36FZMt2+l+7jaA+L6CAo0dMNu4N190LTdpBqVXXkJryV0Iom31rg/EqmzzmYg
1VbV96gGN4PRRo+wypOmkwfHM6AK3PrNTbWUqodQcV7aSshvnKfkcy5FJZ9XVtg5
aaZ9tS8WqXppRsKiY+ie1nb9AoGBALtl1Y8NRyOAFHP5+i5HhLGnrPB5NAIFFBxE
eXnI9DTKxhbExd1dgnILvkEV2RBhN732MagcU33FHC68ZF5NOu7AfXZ5i+qSy/mW
2urKGk8Ap2jsm05CWmpHCkQUsCCTcaL0wbU7LzG8j+UDKDs1N7oTojn4JBNK/5Uw
zEB3+zedAoGAYAI8WQm20ElzbBB8MqL31zM3LZ5Dp/GIjPWhjwEOsGt2oCSrLxC0
xcYRq73St5RS78uGSJxAQaMQna52idifxRE6D3IyAvsMAbn1q+LCqST9zy5jzmO8
tUNvhnkJs1jdDjIzWVtbtMWb7/5wWANx5989B1+QWU14Qmb7uziMVR8=
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDEjCCAfqgAwIBAgIBATANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDDBN0ZXN0
LmNvbUAxNDk2MzMwNjYwMCAXDTcwMDEwMTAwMDAwMFoYDzIxMTcwNTA4MTUyNDIw
WjAeMRwwGgYDVQQDDBN0ZXN0LmNvbUAxNDk2MzMwNjYwMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAwLNxhHFPJFGnDoOnt/ELutNthoJNYLwus52+GLD1
VYDDBA7766rzAxK8wGF9vGbXZX7L1uW3VJyJJLzZR1bBTdeOWXpyscz+33+jETbn
Eg2Dp7KbdFAFw66B90vxLbHKbQtH63VtNg9lh+d0K4QI6SGFlI/Mv9VWawpKk1P5
X1cgl1EgR5e4kIgQsrkO+MRc0SLZG/s9MvThrHVlZLWPjRaiqk1GDxvBjfcBoPzZ
0jOHhWLJGWZcwXZ5brqPcqn+YMceXQlxrjxJvyq02DEWjtfimu7qoZ3+fgQy4rJ0
GzPaDvwnkwvJQ2iN59mcybfg6AoblCOt1ypIqouMrI/J3QIDAQABo1kwVzAOBgNV
HQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
/zAfBgNVHREEGDAWggh0ZXN0LmNvbYIKKi50ZXN0LmNvbTANBgkqhkiG9w0BAQsF
AAOCAQEAGQ+K0dnTaX5Ry9PLA6M8yrMhq5gnX2RfyIMXCuFfMqjdju4BkTR+6zm2
El8Z6glQ6QsKYHR4XhlhHFOP+gGyaTrsDSV2qCgphJmtn6QWOSfEmBRNNCTpMXfS
5Ek/2dXItMjmnMdDOqcLofQyIIQFE9VpLyaFN0n1w9k6EscwSxsMiBVFwOhHpBex
BPJnrKBDWNVHjgocUI3YzN2TzzRxVxB/xc5+Sl/jnpguad+q/wjFgpr9p2a4yAS7
W5bXcA1S4iSp8uKVv0JM/cfFlF094ft88A/SIt8Sn8BmeOGQtSk/sf5mFbr7TRqE
oDuKNM5AIM/fClQdlbKo7xpcJCiRkQ==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwLNxhHFPJFGnDoOnt/ELutNthoJNYLwus52+GLD1VYDDBA77
66rzAxK8wGF9vGbXZX7L1uW3VJyJJLzZR1bBTdeOWXpyscz+33+jETbnEg2Dp7Kb
dFAFw66B90vxLbHKbQtH63VtNg9lh+d0K4QI6SGFlI/Mv9VWawpKk1P5X1cgl1Eg
R5e4kIgQsrkO+MRc0SLZG/s9MvThrHVlZLWPjRaiqk1GDxvBjfcBoPzZ0jOHhWLJ
GWZcwXZ5brqPcqn+YMceXQlxrjxJvyq02DEWjtfimu7qoZ3+fgQy4rJ0GzPaDvwn
kwvJQ2iN59mcybfg6AoblCOt1ypIqouMrI/J3QIDAQABAoIBADlQ5lHTdgvCcaMC
i89NVgTSwEAC38sHDgkEwTcM41v0OY0qe8nkBfYDC7tZBBIWsLiSl0Sq/PWgvbX8
qc/rQQHLPJ7mqdiytZh6mWEUaHRZKrClklFtP9mtPSD2KJ1DxYbblR+2xK4QtE1R
DZ+n3RKikt3WS7s/VD3wSLA7iO/PMJ+ER3kPxZWsVYJw6KpmR8U/v881eM7NGoK3
SwYSqXfcRidNOl68Je3vm3MpK7SiPEOX9AGJUXlkpjJcyOqEE+X23wNSZUpKT+i7
NIrXaEml1nKlTRMlDWi5ZPqzhpguc5Nmn0Tu+01ZClwcu+/7b4C5tkIjXQnyjXsO
0o1EqcECgYEAyEJwGl6KvvYaRsBiFRWIN1950S87O0grOnwb3/FedW4CEf9Lm/Xj
653HTe3zYAJDlsRFCOQ/KXi6nUjAAv8DAydKWmr5EonNj+VXT0Sw32N681lDMPzK
4csJek2bWt2alKbachKAq/gr00837HELl1ALr1X3a3HXr6hZHNjUHpECgYEA9lZq
tXDDV+emyIf/O9LwDUkctHc06qD+uBfiuW0DqVeYPE7E3QPsPzyjnwSfqmgYlpKm
Ku60MfmAVXT8kUSBUuY9ph5AOE/5LwkM3ONc34UBtkruzHUccGXuXelUzmnAQLhi
ZJiWJJrHYICcOZNzd9UmqYye66lyzp9NWe7ttI0CgYB8qYcZkzLxz0fqNUaZc275
cXDmvG016SYn4SyFCr9PgKy/QVpy+u3oNa7lHzR61s32Y7XJ5DRBf8tDsA7/jnn0
k5fgk2j7llcltZHVYaBtX4MhS7bdHvC5AGik/vyv4vWghgHEMypwYyv8/fqFMJYT
mpu7iVtdQmAPFFBs+bmKUQKBgQC4Hm8znkzjbymhyMRkmo0B4RurZ04N8LdyMags
n+aUs/v9V4KdD0mNGm3RdUtBuvxlzsaUYEYe9DWPqhj9Gw7uP1jkyMeHT38YQNN0
ZhwMlQVqkydZek7/U7COUNAqV7Byr14bglD2mxlab8ZU4njzoEV9hLVjvwsolLjj
90mEIQKBgF/IE4+wL9sdeQ7MGanYHFzQtyKxWnRzCGWDkHV9InGZ2I7O3AeqDC76
fXhiRoRbBMSgddyOYTUoaPhwgqPGoC9H/3ZHnyx5fokl0FTNfzk1mnY1n1HD2roR
p76hbMs+TjIGG+lNDso3WuV21Gn+vynDVO9FQumgeaEc+CLXpwEq
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRsb2Nh
bGhvc3RAMTQ5NjMzMDY1ODAgFw03MDAxMDEwMDAwMDBaGA8yMTE3MDUwODE1MjQx
OFowHzEdMBsGA1UEAwwUbG9jYWxob3N0QDE0OTYzMzA2NTgwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDO4DuzjpGF3Em7sIrAv6/19KyTVLxV4jM0XX8G
S8Uo04oyovHnHqfCDd+yaGs1glxD95eqZ5kZDgQNXhn+oZkNvcPJJ8O2tPhHwvjC
PR3KzNL3dFMo6+7BCmBRIVvSNHkkZ5lyoOSE7deGvoKl3KuEWUGwUiGnPx95snCD
HPfgHYto1zSI2NbdnF4hRuOiSFhettMZiwpXEYGhcMh1RYDYxPf+5/XcFz3VCdpF
1COXSYIS2r60H08dH1u6rO1iliJaRh+jqk998exdi0TwJvVTiaubMRZxtGkPNxjE
tSukjDA9UaqAFBnireXj2qc+a6B7AMsoTymn8Ka6QJyt+3ZdAgMBAAGjXjBcMA4G
A1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
AQH/MCQGA1UdEQQdMBuCCWxvY2FsaG9zdIIIdGVzdC5jb22HBH8AAAEwDQYJKoZI
hvcNAQELBQADggEBAAOc/JL/2xfnPAcwzxvF/ssYK6EpjD6plG+OhlIj5UiXo/z4
5IcLCg+N1P7dGaiSMfI/hIM+M/z24QMk6gXFMrVUFh6sm49dNmTry4Qsyp8Up5HD
ettKLaWG3YA08VeqJ0C2xsFh7nJnrlzQad1dwtnj6ActWx/2g8SFkfpQYLba/YI/
YC3biwKGY9P7lmCP5p4234blenJHJW5hbwNyLManvEA9O39Rr4oph2Z1YtmpsNM/
p+xioLTx7oFMtRkBBpmzSKJgLAqCxVg7P+MzjS1s3W49QLI8F1sZ+mXBrZxbALhQ
TtOjvUx+vb6Gihkzn8XkGdbgsiOTMBoT0EmIBJE=
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAzuA7s46RhdxJu7CKwL+v9fSsk1S8VeIzNF1/BkvFKNOKMqLx
5x6nwg3fsmhrNYJcQ/eXqmeZGQ4EDV4Z/qGZDb3DySfDtrT4R8L4wj0dyszS93RT
KOvuwQpgUSFb0jR5JGeZcqDkhO3Xhr6CpdyrhFlBsFIhpz8febJwgxz34B2LaNc0
iNjW3ZxeIUbjokhYXrbTGYsKVxGBoXDIdUWA2MT3/uf13Bc91QnaRdQjl0mCEtq+
tB9PHR9buqztYpYiWkYfo6pPffHsXYtE8Cb1U4mrmzEWcbRpDzcYxLUrpIwwPVGq
gBQZ4q3l49qnPmugewDLKE8pp/CmukCcrft2XQIDAQABAoIBAQC4LgSZAKXook32
zt9JaZ+T/yih5r/iSofB6DeN+GloxqT0HgP1+2Hpr4ZoBPTl59CTJlyd9qvkFYlg
CVZlT6O/CyLC9ScWhBnDcf8cl2LtydtNOgBLn3mp3b4QnP1tsQ/lbGK+HiY1frby
nKweVM9S/lAwVX7mspkoNMhcwq6glLL1QnJmLfmHtsOik4e+eD051aYI4OcfkFFo
T5f6Jg5oNC0L9x+ona1avfD2eBmXmUv8kV4+NSeu+pM+hWnc6r5XnHdXgo0Fu+nf
CQ6wUkyXQAmANI/qo+73G3lEZ7KA9mqrCTbM8sulC4c7+Qpb1vHjuz7pSMj57k67
tI2lPGW1AoGBAPDdooMFZIq6n7wt160MlYDhLujcPaMzgiXWPg9TOLzuWrSZleGH
k818LTXKlQP2ffVADlwHvyi0E7yZVzmG2Xt6Gi8lkGIP5LZK81BlsBspX00bvJCM
VeyE6flSwLA86bQt8spYNAOMMc12F2i3y+YkoKfQmgMiG3H7RjM4Yeq3AoGBANvf
3bh7iHs74oZz40em/W+LdAFBE29FeCloqIXnueHVTLdpHFepmp5OBxogkaYJlVOF
0B/cl8sYn/6CtqKHzhCcCc8V/54O15n/yNRMru4GU0Z5mhaxWvypJM3WXTVvC612
ODM7shXtFQAF5kgeGSDFOepUG1XRPqPjBmSOFiOLAoGAL/kkzg276bmj6PoEc+4G
tOWkzzJWgHPY3ypdr/W2XkLnBAN3C7VSJeIuqwEPTvvAOKGqen/8feHn8G9mJQoL
tQErx+omaLGbrwcckQ2/1pkV5yi4FN4umu4aH9Nwl2l6bq2cJtsMeQxqOu7jWLMK
xGrVKLduhWM8riYK5Fa9gd8CgYByWA+UOxoVDGWXinjt8FfoRFuFs3QJ79S3pCfP
y1yNp0GIOG9230r7fJSQ79MOx/kSQIqlpzCBfELoNs4H5Bwz4xHu0f/nEMzq12x3
i5GFU2DHS8Hofz9UGhp7uTkr6p3mDY7YhfjtEihTpL3HT2+97RNbW3YDwReUXyIZ
9RJqVQKBgEQzEKMFpA5NTzs4BanS8372XXKBRhP0CCTcxS23k/kkYFd0Swnsmv+s
ZCJhV5B1Uz0aSK9jfvCaEfwHjdhxfyYBzoXYVxaxphwNY1Vq7QaEzzyXWKDK3beS
9pwnZf6bEQEvvXyJ3ssqLaHWCoViHe0h/Atsj6UOYhQt5mXzZDEK
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDDBN0ZXN0
LmNvbUAxNDk2MzMwNjU1MCAXDTcwMDEwMTAwMDAwMFoYDzIxMTcwNTA4MTUyNDE1
WjAeMRwwGgYDVQQDDBN0ZXN0LmNvbUAxNDk2MzMwNjU1MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEApFk4QTS28tu20CSV1s0aCC7kV7p/V8XziC5g98OI
GO9s8uVrzc51xsnJ3Y3+Xnx43x9G9VjgSNhPAuE7+/UnP6iAkZZV0mOD3xwwILwy
JK6gZOcZzd9BX9JldSnjv64r5peg5mzdhNqpRs0i3oPcfriW0sGkzl6IQlsowGoS
eDkO6+Z0IpQDo4CqcWond78LDyvqwbfFReyHEtz8D17sciGni6QPK3kGBFDiO0Ay
keV4GqEXw13ryKzVun/haLmekRf8svAU8+pjpHY0uc1cv0l0nTIZdwnp2EIF0msa
EKYyUUpcuFkMQTToYzj9LaIQmvAGIVIouNAydbheBifySwIDAQABo00wSzAOBgNV
HQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
/zATBgNVHREEDDAKggh0ZXN0LmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAWlrvejAW
DpKWBfgQpJLpL56v6dFkCjhThtvDrfvpx/gL0p2QQOIrgD7o+/+fmockmpphf8zc
lAFS/M2IuuvJUN7qvq/4pnUa527LsQFIQSG4GhNfHHdKfISLdjgy7LRlmjLnnSu3
9qa5KvJg9SIWpe41LbeQPB8fLYNuF+tPzrI7QLp/VbMSSyNz4wzpdk8/+r+3KmU8
D1SEQcaEIg7/YlyJ7TZiWiFlZRFcObE11lRYo6tnh8t2e7s0IqC1JwiAmCk0j0iZ
kHE8yND+CuO8QVDxnmu86STL+q0ldZL6C/1lLlnKFKLp+bZfW+s+kds7lDSmUMDi
CbImL3sIf6clIg==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEApFk4QTS28tu20CSV1s0aCC7kV7p/V8XziC5g98OIGO9s8uVr
zc51xsnJ3Y3+Xnx43x9G9VjgSNhPAuE7+/UnP6iAkZZV0mOD3xwwILwyJK6gZOcZ
zd9BX9JldSnjv64r5peg5mzdhNqpRs0i3oPcfriW0sGkzl6IQlsowGoSeDkO6+Z0
IpQDo4CqcWond78LDyvqwbfFReyHEtz8D17sciGni6QPK3kGBFDiO0AykeV4GqEX
w13ryKzVun/haLmekRf8svAU8+pjpHY0uc1cv0l0nTIZdwnp2EIF0msaEKYyUUpc
uFkMQTToYzj9LaIQmvAGIVIouNAydbheBifySwIDAQABAoIBAC07+UXJMomJe7my
OCOU9BNIhMS4qhAIhcz3puzKvozOaVg5WQCeUMBUDeGTEW4EKGiQ/UDOI30pPCX4
xD/G6/STdj+b9ycfCM7BvWVlJw/9mFvtV6bYSfALwq0KyIqbb63aEgkZ8aZ9YJYC
JBTGiku+lpSPg8LhNfO9j2vWRhsceFEtdkv18T9sCSOQTYIrDKFdbxsl49A/D9WZ
YERYVWmWsSHl4CkKAzrQRMIrREgYJk5R3Qt0Qk+3a4BYlmDG6/5Ayfi0sx+bpcK+
255GbRPhe9rTUI6uIc57q7TxGOIG+ekTVqkL87bVR8+oLY/5ARW3jXedB+Th2ku4
/JziseECgYEAx77MOmRPx5ggNQqmSfcaxozmX8Cil6pEZRQxdtdgum9QnyXhG5q9
J8Onw2o5+u+KsTmXIx4CdcA+fL4Z9D/g6GHsi87lJT6KhdHGAmPsEVFQYc0kme3X
hEfGppP4G4haHBzhNh5V9CxlZ7aHsB9J/LGZ2Utz+kvGHE/8GKiERNMCgYEA0qJj
8pdnnObFW4x+/TrOzf9jxAyB7xp3ZTp1gayzmrG4cQr5OMhAZEXG5t1JOg5SvQXh
LRWDr21BtbizJoFcAe0FzMdWIR8fz8RfqiFlfZSnMBnkhQrngu8CghAkg3g0h0m1
tSSpaqQGVRdOFG1A/HSjiNdsBpqLGxejdUfZkakCgYEAuIAFdr4nbu6WeUf/93w3
EEUfmjx+MudES7VORz6EC1E8v/QmLPBOXkd8U6VwLthKXI8cioRysz9CuMiDgrfq
T9v7udv+jtto6listJKFgC2CuQPbFg+6LWZ7GGy/FPdqfGM0p19395V2iUjxpn4Q
QkfuGud2FjEdqhM7ga9OlZ8CgYEAvwXM1NYgfnf/Jh6NkEREBygFJ2gccmazPxe8
N4UTQUjJt6M3EBrz4rwxPXAUF9WjjY+GfieTzIWuYQqHVnVcptZ5OxTQdKtQY1F5
XMZ6z2AAG6xvxYR1HxZ5VNEk/CIIqWTcqNH93xuwLn07L2VB2XJCWTjc3ERNwZ03
fYZStzkCgYBtXFo7OC80f6xRDa1NfQfP4vP6dMczZBZBx2A/rCybwtQsJ40duEB2
hGmgqIrRfh7H7KC8W792xzE1PKUXztTzxxpIr7Ho/nDUWzambMi39EYyBJ6Fp7z2
RkPJFDkAYDpx/v357886y7HFz8pUUNd+z9V9CLemVqhjBWmIaLEX/w==
-----END RSA PRIVATE KEY-----

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRsb2Nh
bGhvc3RAMTQ5NjMzMDY1NTAgFw03MDAxMDEwMDAwMDBaGA8yMTE3MDUwODE1MjQx
NVowHzEdMBsGA1UEAwwUbG9jYWxob3N0QDE0OTYzMzA2NTUwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCmZQErnj8vlA5uvizHXnGmvDCpeOkc5Ldbj0Hz
zqTCiwOSTBeestdN6oLKvxJVlvyO3UQJxhJWr00QRwdCYIvLIDmiBQjb7ciOPcl8
WdLjBtZ36288E9P1nVwFi2RwLo/XzZlCYX0aRe/SM7/h5zzNxBR6FdRvtI1d5as4
qiGdj7TqCFIJ/8jaBfxlusjEJd+xc0Cn8IAmSpPJYCCDmxjAzw0D12Ui3toTFHUI
J9BA7iXTVvMbfau8KQJTPeu/HQ4zgU//4GW1Qo52gNzfY37iliZaG3HTWD1pxTFO
qdDuprFIffsQrQ7mHAkGnAExxZYc2cE4UF+ME8dV0/6nTQA1AgMBAAGjTjBMMA4G
A1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
AQH/MBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAdUUo
i0A3ttsTca5n9OY4EbDPQz7uAhXkihwoG2YeqnucrNXg+SaS+7hFA9sa1gY5NmDF
96OvmQLBEXVz3uLEiv2KGtY5yiAo6iTtk2gtoGAloULEJ018XGQ0yNuDWUfRLyc9
5nA8CBPEnBR7MjO6XhvvFMbAjtXoDKFqVeq3okXbL7ygUjlpwv+Sbps+DjQdmnIQ
A90S7gcFqaCmy2KnzADartasIImy/78R9r68UdJrsmyy/aPNnJ/9AbbU8Z20JFcu
clKOENaKJe/o1nOVzZA2E2oVElT++2wNfomDN5ae77cLoCjZPOBArFlhwyk16YIa
wX3ITLz6Ibr9Iy7DvA==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApmUBK54/L5QObr4sx15xprwwqXjpHOS3W49B886kwosDkkwX
nrLXTeqCyr8SVZb8jt1ECcYSVq9NEEcHQmCLyyA5ogUI2+3Ijj3JfFnS4wbWd+tv
PBPT9Z1cBYtkcC6P182ZQmF9GkXv0jO/4ec8zcQUehXUb7SNXeWrOKohnY+06ghS
Cf/I2gX8ZbrIxCXfsXNAp/CAJkqTyWAgg5sYwM8NA9dlIt7aExR1CCfQQO4l01bz
G32rvCkCUz3rvx0OM4FP/+BltUKOdoDc32N+4pYmWhtx01g9acUxTqnQ7qaxSH37
EK0O5hwJBpwBMcWWHNnBOFBfjBPHVdP+p00ANQIDAQABAoIBAEqbVTgCf2BM6V+8
Q4WtEhZpoQQL47IB1zjldfPkfrl/7T0Ggzy0AZe9A6H2pDjeNyWzjlM3jXdyqIIY
5udbNLMCIvVDfqJl7pvgllv8RnNm2HjyhnCQj2Q8h0sxIfqu1e40EHjUD2zdWNeO
PoYC0Z+NQIUxDox7jzs/xFLm7aWgVTYk86iFa0/uYwnCQpGHR5OkBOhVdkqBMUrH
7hPIoKgLNHT8/jhugv+GamtAmys1KoqIvZsc/XQCMPVmE55n0OBtooQ3fhBXJC0x
UZgJGKNLSjxt7uxHb/BQk5DtFJmPck0derMNkM8WQ6o/XSjlSiKtsct8c7us/c7r
9OFkcy0CgYEAwB6uKr3Al1IM16t6yEEdfrNc/koJ9AQ3sVHSE5CjFBLMs6gL+Feo
r3oFUglY1QB6v+5YUVsLovJLMHuH1x7fKHmd0Fmx75uVzurG2JdKwCiV39PPKYjr
238Yw3Nk6IllPCn+SOIvPDhiuvPwX8FylKGPjsi7AHphKN0f9EEnL6cCgYEA3biT
l8yvpgPpZrpTaMlotKvZkTKtb212a5EdHn0bk5fSM9xaRcumRsWd9eWGutHpMbZx
wj1jAFaGDleBl08zHAr5jsY7i1C3CFKFEUbYR5Qu5+Ot/gdEyO2+j0ehluPOBFad
P514bnWpdct8Khv2dW3OJMzVdFM7M06MXkQFLMMCgYAugT63qecMuhfRvPijsN9q
5BAGnfV60+qHSb8IbWKMYWCbvHnpS47Rqz/Oc+TE6rDL9O38A4X1k6EPw1dGg3qR
WZ8R6CRiU88Xn00y8Kxvh2OAIs1qyHIPV9yusygiAeA3iBK9ocK9WQjNKG9vPmx1
/gW6Emzx2K3cLmqlvX+20QKBgA82yCHlcLQSlqRu6qRJFb83/5DgCliYCUUuSvI5
oXfKNilK2ILri3hVvRYPbuRI3RiFLV8nuTBfv4kiZgkM7TPiaOdsIM/ZdQXEK89Y
riSTPXi5/cltlEG5VpccUjE5ZnyTuOyRkJ77weoMUXPOQWYjZ77Ms2TtBSXrIOSj
BDvjAoGBALAag/LXYvi7l4xIuUMuo1HC4YXZVSqDM7kftLdSUGOl/zGV/0QkSh76
KCVOwXOCmzFeImrZYYDf4uu1wW7paprNaCV6EiChhRgCrjkZIpyw6RHAGshp3a3e
BjXCIHOW5TsWRdR+IvjzIMp9tbHCEvjSw0jDm3JiaSYGRD1AldhV
-----END RSA PRIVATE KEY-----

View file

@ -1,53 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"index.go",
"metrics.go",
"openapi.go",
"profiling.go",
"swagger.go",
"swaggerui.go",
"version.go",
],
deps = [
"//vendor/github.com/elazarl/go-bindata-assetfs:go_default_library",
"//vendor/github.com/emicklei/go-restful:go_default_library",
"//vendor/github.com/emicklei/go-restful-swagger12:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/mux:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/routes/data/swagger:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/etcd/metrics:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/handler:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apiserver/pkg/server/routes/data/swagger:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -1,2 +0,0 @@
reviewers:
- sttts

View file

@ -1,12 +0,0 @@
The datafiles contained in these directories were generated by the script
```sh
hack/build-ui.sh
```
Do not edit by hand.
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/genericapiserver/addons/data/README.md?pixel)]()
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/genericapiserver/server/routes/data/README.md?pixel)]()

View file

@ -1,24 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["datafile.go"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

File diff suppressed because one or more lines are too long

View file

@ -1,18 +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 routes holds a collection of optional genericapiserver http handlers.
package routes

View file

@ -1,69 +0,0 @@
/*
Copyright 2014 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 routes
import (
"net/http"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/server/mux"
)
// ListedPathProvider is an interface for providing paths that should be reported at /.
type ListedPathProvider interface {
// ListedPaths is an alphabetically sorted list of paths to be reported at /.
ListedPaths() []string
}
// ListedPathProviders is a convenient way to combine multiple ListedPathProviders
type ListedPathProviders []ListedPathProvider
// ListedPaths unions and sorts the included paths.
func (p ListedPathProviders) ListedPaths() []string {
ret := sets.String{}
for _, provider := range p {
for _, path := range provider.ListedPaths() {
ret.Insert(path)
}
}
return ret.List()
}
// Index provides a webservice for the http root / listing all known paths.
type Index struct{}
// Install adds the Index webservice to the given mux.
func (i Index) Install(pathProvider ListedPathProvider, mux *mux.PathRecorderMux) {
handler := IndexLister{StatusCode: http.StatusOK, PathProvider: pathProvider}
mux.UnlistedHandle("/", handler)
mux.UnlistedHandle("/index.html", handler)
}
// IndexLister lists the available indexes with the status code provided
type IndexLister struct {
StatusCode int
PathProvider ListedPathProvider
}
// ServeHTTP serves the available paths.
func (i IndexLister) ServeHTTP(w http.ResponseWriter, r *http.Request) {
responsewriters.WriteRawJSON(i.StatusCode, metav1.RootPaths{Paths: i.PathProvider.ListedPaths()}, w)
}

View file

@ -1,54 +0,0 @@
/*
Copyright 2014 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 routes
import (
"io"
"net/http"
apimetrics "k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/apiserver/pkg/server/mux"
etcdmetrics "k8s.io/apiserver/pkg/storage/etcd/metrics"
"github.com/prometheus/client_golang/prometheus"
)
// DefaultMetrics installs the default prometheus metrics handler
type DefaultMetrics struct{}
// Install adds the DefaultMetrics handler
func (m DefaultMetrics) Install(c *mux.PathRecorderMux) {
c.Handle("/metrics", prometheus.Handler())
}
// MetricsWithReset install the prometheus metrics handler extended with support for the DELETE method
// which resets the metrics.
type MetricsWithReset struct{}
// Install adds the MetricsWithReset handler
func (m MetricsWithReset) Install(c *mux.PathRecorderMux) {
defaultMetricsHandler := prometheus.Handler().ServeHTTP
c.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
if req.Method == "DELETE" {
apimetrics.Reset()
etcdmetrics.Reset()
io.WriteString(w, "metrics reset\n")
return
}
defaultMetricsHandler(w, req)
})
}

View file

@ -1,39 +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 routes
import (
"github.com/emicklei/go-restful"
"github.com/golang/glog"
"k8s.io/apiserver/pkg/server/mux"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/handler"
)
// OpenAPI installs spec endpoints for each web service.
type OpenAPI struct {
Config *common.Config
}
// Install adds the SwaggerUI webservice to the given mux.
func (oa OpenAPI) Install(c *restful.Container, mux *mux.PathRecorderMux) {
_, err := handler.BuildAndRegisterOpenAPIService("/swagger.json", c.RegisteredWebServices(), oa.Config, mux)
if err != nil {
glog.Fatalf("Failed to register open api spec for root: %v", err)
}
}

View file

@ -1,36 +0,0 @@
/*
Copyright 2014 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 routes
import (
"net/http"
"net/http/pprof"
"k8s.io/apiserver/pkg/server/mux"
)
// Profiling adds handlers for pprof under /debug/pprof.
type Profiling struct{}
// Install adds the Profiling webservice to the given mux.
func (d Profiling) Install(c *mux.PathRecorderMux) {
c.UnlistedHandle("/debug/pprof", http.HandlerFunc(pprof.Index))
c.UnlistedHandlePrefix("/debug/pprof/", http.HandlerFunc(pprof.Index))
c.UnlistedHandleFunc("/debug/pprof/profile", pprof.Profile)
c.UnlistedHandleFunc("/debug/pprof/symbol", pprof.Symbol)
c.UnlistedHandleFunc("/debug/pprof/trace", pprof.Trace)
}

View file

@ -1,36 +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 routes
import (
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful-swagger12"
)
// Swagger installs the /swaggerapi/ endpoint to allow schema discovery
// and traversal. It is optional to allow consumers of the Kubernetes GenericAPIServer to
// register their own web services into the Kubernetes mux prior to initialization
// of swagger, so that other resource types show up in the documentation.
type Swagger struct {
Config *swagger.Config
}
// Install adds the SwaggerUI webservice to the given mux.
func (s Swagger) Install(c *restful.Container) {
s.Config.WebServices = c.RegisteredWebServices()
swagger.RegisterSwaggerService(*s.Config, c)
}

View file

@ -1,40 +0,0 @@
/*
Copyright 2014 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 routes
import (
"net/http"
assetfs "github.com/elazarl/go-bindata-assetfs"
"k8s.io/apiserver/pkg/server/mux"
"k8s.io/apiserver/pkg/server/routes/data/swagger"
)
// SwaggerUI exposes files in third_party/swagger-ui/ under /swagger-ui.
type SwaggerUI struct{}
// Install adds the SwaggerUI webservice to the given mux.
func (l SwaggerUI) Install(c *mux.PathRecorderMux) {
fileServer := http.FileServer(&assetfs.AssetFS{
Asset: swagger.Asset,
AssetDir: swagger.AssetDir,
Prefix: "third_party/swagger-ui",
})
prefix := "/swagger-ui/"
c.HandlePrefix(prefix, http.StripPrefix(prefix, fileServer))
}

View file

@ -1,57 +0,0 @@
/*
Copyright 2014 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 routes
import (
"net/http"
"github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/version"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
)
// Version provides a webservice with version information.
type Version struct {
Version *version.Info
}
// Install registers the APIServer's `/version` handler.
func (v Version) Install(c *restful.Container) {
if v.Version == nil {
return
}
// Set up a service to return the git code version.
versionWS := new(restful.WebService)
versionWS.Path("/version")
versionWS.Doc("git code version from which this is built")
versionWS.Route(
versionWS.GET("/").To(v.handleVersion).
Doc("get the code version").
Operation("getCodeVersion").
Produces(restful.MIME_JSON).
Consumes(restful.MIME_JSON).
Writes(version.Info{}))
c.Add(versionWS)
}
// handleVersion writes the server's version information.
func (v Version) handleVersion(req *restful.Request, resp *restful.Response) {
responsewriters.WriteRawJSON(http.StatusOK, *v.Version, resp.ResponseWriter)
}

View file

@ -1,64 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"resource_config_test.go",
"storage_factory_test.go",
],
library = ":go_default_library",
deps = [
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/example:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/example/install:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/example/v1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"resource_config.go",
"resource_encoding_config.go",
"storage_codec.go",
"storage_factory.go",
],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/recognizer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -1,18 +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 storage contains the plumbing to setup the etcd storage of the apiserver.
package storage

View file

@ -1,185 +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 storage
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
)
// APIResourceConfigSource is the interface to determine which versions and resources are enabled
type APIResourceConfigSource interface {
AnyVersionOfResourceEnabled(resource schema.GroupResource) bool
ResourceEnabled(resource schema.GroupVersionResource) bool
AllResourcesForVersionEnabled(version schema.GroupVersion) bool
AnyResourcesForVersionEnabled(version schema.GroupVersion) bool
AnyResourcesForGroupEnabled(group string) bool
}
// Specifies the overrides for various API group versions.
// This can be used to enable/disable entire group versions or specific resources.
type GroupVersionResourceConfig struct {
// Whether to enable or disable this entire group version. This dominates any enablement check.
// Enable=true means the group version is enabled, and EnabledResources/DisabledResources are considered.
// Enable=false means the group version is disabled, and EnabledResources/DisabledResources are not considered.
Enable bool
// DisabledResources lists the resources that are specifically disabled for a group/version
// DisabledResources trumps EnabledResources
DisabledResources sets.String
// EnabledResources lists the resources that should be enabled by default. This is a little
// unusual, but we need it for compatibility with old code for now. An empty set means
// enable all, a non-empty set means that all other resources are disabled.
EnabledResources sets.String
}
var _ APIResourceConfigSource = &ResourceConfig{}
type ResourceConfig struct {
GroupVersionResourceConfigs map[schema.GroupVersion]*GroupVersionResourceConfig
}
func NewResourceConfig() *ResourceConfig {
return &ResourceConfig{GroupVersionResourceConfigs: map[schema.GroupVersion]*GroupVersionResourceConfig{}}
}
func NewGroupVersionResourceConfig() *GroupVersionResourceConfig {
return &GroupVersionResourceConfig{Enable: true, DisabledResources: sets.String{}, EnabledResources: sets.String{}}
}
// DisableVersions disables the versions entirely. No resources (even those whitelisted in EnabledResources) will be enabled
func (o *ResourceConfig) DisableVersions(versions ...schema.GroupVersion) {
for _, version := range versions {
_, versionExists := o.GroupVersionResourceConfigs[version]
if !versionExists {
o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig()
}
o.GroupVersionResourceConfigs[version].Enable = false
}
}
func (o *ResourceConfig) EnableVersions(versions ...schema.GroupVersion) {
for _, version := range versions {
_, versionExists := o.GroupVersionResourceConfigs[version]
if !versionExists {
o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig()
}
o.GroupVersionResourceConfigs[version].Enable = true
}
}
func (o *ResourceConfig) DisableResources(resources ...schema.GroupVersionResource) {
for _, resource := range resources {
version := resource.GroupVersion()
_, versionExists := o.GroupVersionResourceConfigs[version]
if !versionExists {
o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig()
}
o.GroupVersionResourceConfigs[version].DisabledResources.Insert(resource.Resource)
}
}
func (o *ResourceConfig) EnableResources(resources ...schema.GroupVersionResource) {
for _, resource := range resources {
version := resource.GroupVersion()
_, versionExists := o.GroupVersionResourceConfigs[version]
if !versionExists {
o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig()
}
o.GroupVersionResourceConfigs[version].EnabledResources.Insert(resource.Resource)
o.GroupVersionResourceConfigs[version].DisabledResources.Delete(resource.Resource)
}
}
// AnyResourcesForVersionEnabled only considers matches based on exactly group/resource lexical matching. This means that
// resource renames across versions are NOT considered to be the same resource by this method. You'll need to manually check
// using the ResourceEnabled function.
func (o *ResourceConfig) AnyVersionOfResourceEnabled(resource schema.GroupResource) bool {
for version := range o.GroupVersionResourceConfigs {
if version.Group != resource.Group {
continue
}
if o.ResourceEnabled(version.WithResource(resource.Resource)) {
return true
}
}
return false
}
func (o *ResourceConfig) ResourceEnabled(resource schema.GroupVersionResource) bool {
versionOverride, versionExists := o.GroupVersionResourceConfigs[resource.GroupVersion()]
if !versionExists {
return false
}
if !versionOverride.Enable {
return false
}
if versionOverride.DisabledResources.Has(resource.Resource) {
return false
}
if len(versionOverride.EnabledResources) > 0 {
return versionOverride.EnabledResources.Has(resource.Resource)
}
return true
}
func (o *ResourceConfig) AllResourcesForVersionEnabled(version schema.GroupVersion) bool {
versionOverride, versionExists := o.GroupVersionResourceConfigs[version]
if !versionExists {
return false
}
if !versionOverride.Enable {
return false
}
if len(versionOverride.EnabledResources) == 0 && len(versionOverride.DisabledResources) == 0 {
return true
}
return false
}
func (o *ResourceConfig) AnyResourcesForVersionEnabled(version schema.GroupVersion) bool {
versionOverride, versionExists := o.GroupVersionResourceConfigs[version]
if !versionExists {
return false
}
return versionOverride.Enable
}
func (o *ResourceConfig) AnyResourcesForGroupEnabled(group string) bool {
for version := range o.GroupVersionResourceConfigs {
if version.Group == group {
if o.AnyResourcesForVersionEnabled(version) {
return true
}
}
}
return false
}

View file

@ -1,160 +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 storage
import (
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestDisabledVersion(t *testing.T) {
g1v1 := schema.GroupVersion{Group: "group1", Version: "version1"}
g1v2 := schema.GroupVersion{Group: "group1", Version: "version2"}
g2v1 := schema.GroupVersion{Group: "group2", Version: "version1"}
g3v1 := schema.GroupVersion{Group: "group3", Version: "version1"}
resourceType := "the-resource"
disabledResourceType := "the-disabled-resource"
config := NewResourceConfig()
config.DisableVersions(g1v1)
config.EnableVersions(g1v2, g3v1)
config.EnableResources(g1v1.WithResource(resourceType), g2v1.WithResource(resourceType))
config.DisableResources(g1v2.WithResource(disabledResourceType))
expectedEnabledResources := []schema.GroupVersionResource{
g1v2.WithResource(resourceType),
g2v1.WithResource(resourceType),
}
expectedDisabledResources := []schema.GroupVersionResource{
g1v1.WithResource(resourceType), g1v1.WithResource(disabledResourceType),
g1v2.WithResource(disabledResourceType),
g2v1.WithResource(disabledResourceType),
}
for _, expectedResource := range expectedEnabledResources {
if !config.ResourceEnabled(expectedResource) {
t.Errorf("expected enabled for %v, from %v", expectedResource, config)
}
}
for _, expectedResource := range expectedDisabledResources {
if config.ResourceEnabled(expectedResource) {
t.Errorf("expected disabled for %v, from %v", expectedResource, config)
}
}
if e, a := false, config.AnyResourcesForVersionEnabled(g1v1); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := false, config.AllResourcesForVersionEnabled(g1v1); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := true, config.AnyResourcesForVersionEnabled(g1v2); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := false, config.AllResourcesForVersionEnabled(g1v2); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := true, config.AnyResourcesForVersionEnabled(g3v1); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := true, config.AllResourcesForVersionEnabled(g3v1); e != a {
t.Errorf("expected %v, got %v", e, a)
}
expectedEnabledAnyVersionResources := []schema.GroupResource{
{Group: "group1", Resource: resourceType},
}
expectedDisabledAnyResources := []schema.GroupResource{
{Group: "group1", Resource: disabledResourceType},
}
for _, expectedResource := range expectedEnabledAnyVersionResources {
if !config.AnyVersionOfResourceEnabled(expectedResource) {
t.Errorf("expected enabled for %v, from %v", expectedResource, config)
}
}
for _, expectedResource := range expectedDisabledAnyResources {
if config.AnyVersionOfResourceEnabled(expectedResource) {
t.Errorf("expected disabled for %v, from %v", expectedResource, config)
}
}
}
func TestAnyResourcesForGroupEnabled(t *testing.T) {
tests := []struct {
name string
creator func() APIResourceConfigSource
testGroup string
expectedResult bool
}{
{
name: "empty",
creator: func() APIResourceConfigSource {
return NewResourceConfig()
},
testGroup: "one",
expectedResult: false,
},
{
name: "present, but disabled",
creator: func() APIResourceConfigSource {
ret := NewResourceConfig()
ret.DisableVersions(schema.GroupVersion{Group: "one", Version: "version1"})
return ret
},
testGroup: "one",
expectedResult: false,
},
{
name: "present, and one version enabled",
creator: func() APIResourceConfigSource {
ret := NewResourceConfig()
ret.DisableVersions(schema.GroupVersion{Group: "one", Version: "version1"})
ret.EnableVersions(schema.GroupVersion{Group: "one", Version: "version2"})
return ret
},
testGroup: "one",
expectedResult: true,
},
{
name: "present, and one resource",
creator: func() APIResourceConfigSource {
ret := NewResourceConfig()
ret.DisableVersions(schema.GroupVersion{Group: "one", Version: "version1"})
ret.EnableResources(schema.GroupVersionResource{Group: "one", Version: "version2", Resource: "foo"})
return ret
},
testGroup: "one",
expectedResult: true,
},
}
for _, tc := range tests {
if e, a := tc.expectedResult, tc.creator().AnyResourcesForGroupEnabled(tc.testGroup); e != a {
t.Errorf("%s: expected %v, got %v", tc.name, e, a)
}
}
}

View file

@ -1,119 +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 storage
import (
"k8s.io/apimachinery/pkg/apimachinery/registered"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type ResourceEncodingConfig interface {
// StorageEncoding returns the serialization format for the resource.
// TODO this should actually return a GroupVersionKind since you can logically have multiple "matching" Kinds
// For now, it returns just the GroupVersion for consistency with old behavior
StorageEncodingFor(schema.GroupResource) (schema.GroupVersion, error)
// InMemoryEncodingFor returns the groupVersion for the in memory representation the storage should convert to.
InMemoryEncodingFor(schema.GroupResource) (schema.GroupVersion, error)
}
type DefaultResourceEncodingConfig struct {
groups map[string]*GroupResourceEncodingConfig
registry *registered.APIRegistrationManager
}
type GroupResourceEncodingConfig struct {
DefaultExternalEncoding schema.GroupVersion
ExternalResourceEncodings map[string]schema.GroupVersion
DefaultInternalEncoding schema.GroupVersion
InternalResourceEncodings map[string]schema.GroupVersion
}
var _ ResourceEncodingConfig = &DefaultResourceEncodingConfig{}
func NewDefaultResourceEncodingConfig(registry *registered.APIRegistrationManager) *DefaultResourceEncodingConfig {
return &DefaultResourceEncodingConfig{groups: map[string]*GroupResourceEncodingConfig{}, registry: registry}
}
func newGroupResourceEncodingConfig(defaultEncoding, defaultInternalVersion schema.GroupVersion) *GroupResourceEncodingConfig {
return &GroupResourceEncodingConfig{
DefaultExternalEncoding: defaultEncoding, ExternalResourceEncodings: map[string]schema.GroupVersion{},
DefaultInternalEncoding: defaultInternalVersion, InternalResourceEncodings: map[string]schema.GroupVersion{},
}
}
func (o *DefaultResourceEncodingConfig) SetVersionEncoding(group string, externalEncodingVersion, internalVersion schema.GroupVersion) {
_, groupExists := o.groups[group]
if !groupExists {
o.groups[group] = newGroupResourceEncodingConfig(externalEncodingVersion, internalVersion)
}
o.groups[group].DefaultExternalEncoding = externalEncodingVersion
o.groups[group].DefaultInternalEncoding = internalVersion
}
func (o *DefaultResourceEncodingConfig) SetResourceEncoding(resourceBeingStored schema.GroupResource, externalEncodingVersion, internalVersion schema.GroupVersion) {
group := resourceBeingStored.Group
_, groupExists := o.groups[group]
if !groupExists {
o.groups[group] = newGroupResourceEncodingConfig(externalEncodingVersion, internalVersion)
}
o.groups[group].ExternalResourceEncodings[resourceBeingStored.Resource] = externalEncodingVersion
o.groups[group].InternalResourceEncodings[resourceBeingStored.Resource] = internalVersion
}
func (o *DefaultResourceEncodingConfig) StorageEncodingFor(resource schema.GroupResource) (schema.GroupVersion, error) {
groupMeta, err := o.registry.Group(resource.Group)
if err != nil {
return schema.GroupVersion{}, err
}
groupEncoding, groupExists := o.groups[resource.Group]
if !groupExists {
// return the most preferred external version for the group
return groupMeta.GroupVersion, nil
}
resourceOverride, resourceExists := groupEncoding.ExternalResourceEncodings[resource.Resource]
if !resourceExists {
return groupEncoding.DefaultExternalEncoding, nil
}
return resourceOverride, nil
}
func (o *DefaultResourceEncodingConfig) InMemoryEncodingFor(resource schema.GroupResource) (schema.GroupVersion, error) {
if _, err := o.registry.Group(resource.Group); err != nil {
return schema.GroupVersion{}, err
}
groupEncoding, groupExists := o.groups[resource.Group]
if !groupExists {
return schema.GroupVersion{Group: resource.Group, Version: runtime.APIVersionInternal}, nil
}
resourceOverride, resourceExists := groupEncoding.InternalResourceEncodings[resource.Resource]
if !resourceExists {
return groupEncoding.DefaultInternalEncoding, nil
}
return resourceOverride, nil
}

View file

@ -1,108 +0,0 @@
/*
Copyright 2017 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 storage
import (
"fmt"
"mime"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
"k8s.io/apiserver/pkg/storage/storagebackend"
"github.com/golang/glog"
)
// StorageCodecConfig are the arguments passed to newStorageCodecFn
type StorageCodecConfig struct {
StorageMediaType string
StorageSerializer runtime.StorageSerializer
StorageVersion schema.GroupVersion
MemoryVersion schema.GroupVersion
Config storagebackend.Config
EncoderDecoratorFn func(runtime.Encoder) runtime.Encoder
DecoderDecoratorFn func([]runtime.Decoder) []runtime.Decoder
}
// NewStorageCodec assembles a storage codec for the provided storage media type, the provided serializer, and the requested
// storage and memory versions.
func NewStorageCodec(opts StorageCodecConfig) (runtime.Codec, error) {
mediaType, _, err := mime.ParseMediaType(opts.StorageMediaType)
if err != nil {
return nil, fmt.Errorf("%q is not a valid mime-type", opts.StorageMediaType)
}
if opts.Config.Type == storagebackend.StorageTypeETCD2 && mediaType != "application/json" {
glog.Warningf(`storage type %q does not support media type %q, using "application/json"`, storagebackend.StorageTypeETCD2, mediaType)
mediaType = "application/json"
}
serializer, ok := runtime.SerializerInfoForMediaType(opts.StorageSerializer.SupportedMediaTypes(), mediaType)
if !ok {
return nil, fmt.Errorf("unable to find serializer for %q", mediaType)
}
s := serializer.Serializer
// make sure the selected encoder supports string data
if !serializer.EncodesAsText && opts.Config.Type == storagebackend.StorageTypeETCD2 {
return nil, fmt.Errorf("storage type %q does not support binary media type %q", storagebackend.StorageTypeETCD2, mediaType)
}
// Give callers the opportunity to wrap encoders and decoders. For decoders, each returned decoder will
// be passed to the recognizer so that multiple decoders are available.
var encoder runtime.Encoder = s
if opts.EncoderDecoratorFn != nil {
encoder = opts.EncoderDecoratorFn(encoder)
}
decoders := []runtime.Decoder{
// selected decoder as the primary
s,
// universal deserializer as a fallback
opts.StorageSerializer.UniversalDeserializer(),
// base64-wrapped universal deserializer as a last resort.
// this allows reading base64-encoded protobuf, which should only exist if etcd2+protobuf was used at some point.
// data written that way could exist in etcd2, or could have been migrated to etcd3.
// TODO: flag this type of data if we encounter it, require migration (read to decode, write to persist using a supported encoder), and remove in 1.8
runtime.NewBase64Serializer(nil, opts.StorageSerializer.UniversalDeserializer()),
}
if opts.DecoderDecoratorFn != nil {
decoders = opts.DecoderDecoratorFn(decoders)
}
// Ensure the storage receives the correct version.
encoder = opts.StorageSerializer.EncoderForVersion(
encoder,
runtime.NewMultiGroupVersioner(
opts.StorageVersion,
schema.GroupKind{Group: opts.StorageVersion.Group},
schema.GroupKind{Group: opts.MemoryVersion.Group},
),
)
decoder := opts.StorageSerializer.DecoderToVersion(
recognizer.NewDecoder(decoders...),
runtime.NewMultiGroupVersioner(
opts.MemoryVersion,
schema.GroupKind{Group: opts.MemoryVersion.Group},
schema.GroupKind{Group: opts.StorageVersion.Group},
),
)
return runtime.NewCodec(encoder, decoder), nil
}

View file

@ -1,353 +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 storage
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"strings"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/apiserver/pkg/storage/value"
utilfeature "k8s.io/apiserver/pkg/util/feature"
)
// Backend describes the storage servers, the information here should be enough
// for health validations.
type Backend struct {
// the url of storage backend like: https://etcd.domain:2379
Server string
// the required tls config
TLSConfig *tls.Config
}
// StorageFactory is the interface to locate the storage for a given GroupResource
type StorageFactory interface {
// New finds the storage destination for the given group and resource. It will
// return an error if the group has no storage destination configured.
NewConfig(groupResource schema.GroupResource) (*storagebackend.Config, error)
// ResourcePrefix returns the overridden resource prefix for the GroupResource
// This allows for cohabitation of resources with different native types and provides
// centralized control over the shape of etcd directories
ResourcePrefix(groupResource schema.GroupResource) string
// Backends gets all backends for all registered storage destinations.
// Used for getting all instances for health validations.
Backends() []Backend
}
// DefaultStorageFactory takes a GroupResource and returns back its storage interface. This result includes:
// 1. Merged etcd config, including: auth, server locations, prefixes
// 2. Resource encodings for storage: group,version,kind to store as
// 3. Cohabitating default: some resources like hpa are exposed through multiple APIs. They must agree on 1 and 2
type DefaultStorageFactory struct {
// StorageConfig describes how to create a storage backend in general.
// Its authentication information will be used for every storage.Interface returned.
StorageConfig storagebackend.Config
Overrides map[schema.GroupResource]groupResourceOverrides
DefaultResourcePrefixes map[schema.GroupResource]string
// DefaultMediaType is the media type used to store resources. If it is not set, "application/json" is used.
DefaultMediaType string
// DefaultSerializer is used to create encoders and decoders for the storage.Interface.
DefaultSerializer runtime.StorageSerializer
// ResourceEncodingConfig describes how to encode a particular GroupVersionResource
ResourceEncodingConfig ResourceEncodingConfig
// APIResourceConfigSource indicates whether the *storage* is enabled, NOT the API
// This is discrete from resource enablement because those are separate concerns. How this source is configured
// is left to the caller.
APIResourceConfigSource APIResourceConfigSource
// newStorageCodecFn exists to be overwritten for unit testing.
newStorageCodecFn func(opts StorageCodecConfig) (codec runtime.Codec, err error)
}
type groupResourceOverrides struct {
// etcdLocation contains the list of "special" locations that are used for particular GroupResources
// These are merged on top of the StorageConfig when requesting the storage.Interface for a given GroupResource
etcdLocation []string
// etcdPrefix is the base location for a GroupResource.
etcdPrefix string
// etcdResourcePrefix is the location to use to store a particular type under the `etcdPrefix` location
// If empty, the default mapping is used. If the default mapping doesn't contain an entry, it will use
// the ToLowered name of the resource, not including the group.
etcdResourcePrefix string
// mediaType is the desired serializer to choose. If empty, the default is chosen.
mediaType string
// serializer contains the list of "special" serializers for a GroupResource. Resource=* means for the entire group
serializer runtime.StorageSerializer
// cohabitatingResources keeps track of which resources must be stored together. This happens when we have multiple ways
// of exposing one set of concepts. autoscaling.HPA and extensions.HPA as a for instance
// The order of the slice matters! It is the priority order of lookup for finding a storage location
cohabitatingResources []schema.GroupResource
// encoderDecoratorFn is optional and may wrap the provided encoder prior to being serialized.
encoderDecoratorFn func(runtime.Encoder) runtime.Encoder
// decoderDecoratorFn is optional and may wrap the provided decoders (can add new decoders). The order of
// returned decoders will be priority for attempt to decode.
decoderDecoratorFn func([]runtime.Decoder) []runtime.Decoder
// transformer is optional and shall encrypt that resource at rest.
transformer value.Transformer
// disablePaging will prevent paging on the provided resource.
disablePaging bool
}
// Apply overrides the provided config and options if the override has a value in that position
func (o groupResourceOverrides) Apply(config *storagebackend.Config, options *StorageCodecConfig) {
if len(o.etcdLocation) > 0 {
config.ServerList = o.etcdLocation
}
if len(o.etcdPrefix) > 0 {
config.Prefix = o.etcdPrefix
}
if len(o.mediaType) > 0 {
options.StorageMediaType = o.mediaType
}
if o.serializer != nil {
options.StorageSerializer = o.serializer
}
if o.encoderDecoratorFn != nil {
options.EncoderDecoratorFn = o.encoderDecoratorFn
}
if o.decoderDecoratorFn != nil {
options.DecoderDecoratorFn = o.decoderDecoratorFn
}
if o.transformer != nil {
config.Transformer = o.transformer
}
if o.disablePaging {
config.Paging = false
}
}
var _ StorageFactory = &DefaultStorageFactory{}
const AllResources = "*"
// specialDefaultResourcePrefixes are prefixes compiled into Kubernetes.
// TODO: move out of this package, it is not generic
var specialDefaultResourcePrefixes = map[schema.GroupResource]string{
{Group: "", Resource: "replicationControllers"}: "controllers",
{Group: "", Resource: "replicationcontrollers"}: "controllers",
{Group: "", Resource: "endpoints"}: "services/endpoints",
{Group: "", Resource: "nodes"}: "minions",
{Group: "", Resource: "services"}: "services/specs",
{Group: "extensions", Resource: "ingresses"}: "ingress",
{Group: "extensions", Resource: "podsecuritypolicies"}: "podsecuritypolicy",
}
func NewDefaultStorageFactory(config storagebackend.Config, defaultMediaType string, defaultSerializer runtime.StorageSerializer, resourceEncodingConfig ResourceEncodingConfig, resourceConfig APIResourceConfigSource) *DefaultStorageFactory {
config.Paging = utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
if len(defaultMediaType) == 0 {
defaultMediaType = runtime.ContentTypeJSON
}
return &DefaultStorageFactory{
StorageConfig: config,
Overrides: map[schema.GroupResource]groupResourceOverrides{},
DefaultMediaType: defaultMediaType,
DefaultSerializer: defaultSerializer,
ResourceEncodingConfig: resourceEncodingConfig,
APIResourceConfigSource: resourceConfig,
DefaultResourcePrefixes: specialDefaultResourcePrefixes,
newStorageCodecFn: NewStorageCodec,
}
}
func (s *DefaultStorageFactory) SetEtcdLocation(groupResource schema.GroupResource, location []string) {
overrides := s.Overrides[groupResource]
overrides.etcdLocation = location
s.Overrides[groupResource] = overrides
}
func (s *DefaultStorageFactory) SetEtcdPrefix(groupResource schema.GroupResource, prefix string) {
overrides := s.Overrides[groupResource]
overrides.etcdPrefix = prefix
s.Overrides[groupResource] = overrides
}
// SetDisableAPIListChunking allows a specific resource to disable paging at the storage layer, to prevent
// exposure of key names in continuations. This may be overriden by feature gates.
func (s *DefaultStorageFactory) SetDisableAPIListChunking(groupResource schema.GroupResource) {
overrides := s.Overrides[groupResource]
overrides.disablePaging = true
s.Overrides[groupResource] = overrides
}
// SetResourceEtcdPrefix sets the prefix for a resource, but not the base-dir. You'll end up in `etcdPrefix/resourceEtcdPrefix`.
func (s *DefaultStorageFactory) SetResourceEtcdPrefix(groupResource schema.GroupResource, prefix string) {
overrides := s.Overrides[groupResource]
overrides.etcdResourcePrefix = prefix
s.Overrides[groupResource] = overrides
}
func (s *DefaultStorageFactory) SetSerializer(groupResource schema.GroupResource, mediaType string, serializer runtime.StorageSerializer) {
overrides := s.Overrides[groupResource]
overrides.mediaType = mediaType
overrides.serializer = serializer
s.Overrides[groupResource] = overrides
}
func (s *DefaultStorageFactory) SetTransformer(groupResource schema.GroupResource, transformer value.Transformer) {
overrides := s.Overrides[groupResource]
overrides.transformer = transformer
s.Overrides[groupResource] = overrides
}
// AddCohabitatingResources links resources together the order of the slice matters! its the priority order of lookup for finding a storage location
func (s *DefaultStorageFactory) AddCohabitatingResources(groupResources ...schema.GroupResource) {
for _, groupResource := range groupResources {
overrides := s.Overrides[groupResource]
overrides.cohabitatingResources = groupResources
s.Overrides[groupResource] = overrides
}
}
func (s *DefaultStorageFactory) AddSerializationChains(encoderDecoratorFn func(runtime.Encoder) runtime.Encoder, decoderDecoratorFn func([]runtime.Decoder) []runtime.Decoder, groupResources ...schema.GroupResource) {
for _, groupResource := range groupResources {
overrides := s.Overrides[groupResource]
overrides.encoderDecoratorFn = encoderDecoratorFn
overrides.decoderDecoratorFn = decoderDecoratorFn
s.Overrides[groupResource] = overrides
}
}
func getAllResourcesAlias(resource schema.GroupResource) schema.GroupResource {
return schema.GroupResource{Group: resource.Group, Resource: AllResources}
}
func (s *DefaultStorageFactory) getStorageGroupResource(groupResource schema.GroupResource) schema.GroupResource {
for _, potentialStorageResource := range s.Overrides[groupResource].cohabitatingResources {
if s.APIResourceConfigSource.AnyVersionOfResourceEnabled(potentialStorageResource) {
return potentialStorageResource
}
}
return groupResource
}
// New finds the storage destination for the given group and resource. It will
// return an error if the group has no storage destination configured.
func (s *DefaultStorageFactory) NewConfig(groupResource schema.GroupResource) (*storagebackend.Config, error) {
chosenStorageResource := s.getStorageGroupResource(groupResource)
// operate on copy
storageConfig := s.StorageConfig
codecConfig := StorageCodecConfig{
StorageMediaType: s.DefaultMediaType,
StorageSerializer: s.DefaultSerializer,
}
if override, ok := s.Overrides[getAllResourcesAlias(chosenStorageResource)]; ok {
override.Apply(&storageConfig, &codecConfig)
}
if override, ok := s.Overrides[chosenStorageResource]; ok {
override.Apply(&storageConfig, &codecConfig)
}
var err error
codecConfig.StorageVersion, err = s.ResourceEncodingConfig.StorageEncodingFor(chosenStorageResource)
if err != nil {
return nil, err
}
codecConfig.MemoryVersion, err = s.ResourceEncodingConfig.InMemoryEncodingFor(groupResource)
if err != nil {
return nil, err
}
codecConfig.Config = storageConfig
storageConfig.Codec, err = s.newStorageCodecFn(codecConfig)
if err != nil {
return nil, err
}
glog.V(3).Infof("storing %v in %v, reading as %v from %#v", groupResource, codecConfig.StorageVersion, codecConfig.MemoryVersion, codecConfig.Config)
return &storageConfig, nil
}
// Backends returns all backends for all registered storage destinations.
// Used for getting all instances for health validations.
func (s *DefaultStorageFactory) Backends() []Backend {
servers := sets.NewString(s.StorageConfig.ServerList...)
for _, overrides := range s.Overrides {
servers.Insert(overrides.etcdLocation...)
}
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
if len(s.StorageConfig.CertFile) > 0 && len(s.StorageConfig.KeyFile) > 0 {
cert, err := tls.LoadX509KeyPair(s.StorageConfig.CertFile, s.StorageConfig.KeyFile)
if err != nil {
glog.Errorf("failed to load key pair while getting backends: %s", err)
} else {
tlsConfig.Certificates = []tls.Certificate{cert}
}
}
if len(s.StorageConfig.CAFile) > 0 {
if caCert, err := ioutil.ReadFile(s.StorageConfig.CAFile); err != nil {
glog.Errorf("failed to read ca file while getting backends: %s", err)
} else {
caPool := x509.NewCertPool()
caPool.AppendCertsFromPEM(caCert)
tlsConfig.RootCAs = caPool
tlsConfig.InsecureSkipVerify = false
}
}
backends := []Backend{}
for server := range servers {
backends = append(backends, Backend{
Server: server,
TLSConfig: tlsConfig,
})
}
return backends
}
func (s *DefaultStorageFactory) ResourcePrefix(groupResource schema.GroupResource) string {
chosenStorageResource := s.getStorageGroupResource(groupResource)
groupOverride := s.Overrides[getAllResourcesAlias(chosenStorageResource)]
exactResourceOverride := s.Overrides[chosenStorageResource]
etcdResourcePrefix := s.DefaultResourcePrefixes[chosenStorageResource]
if len(groupOverride.etcdResourcePrefix) > 0 {
etcdResourcePrefix = groupOverride.etcdResourcePrefix
}
if len(exactResourceOverride.etcdResourcePrefix) > 0 {
etcdResourcePrefix = exactResourceOverride.etcdResourcePrefix
}
if len(etcdResourcePrefix) == 0 {
etcdResourcePrefix = strings.ToLower(chosenStorageResource.Resource)
}
return etcdResourcePrefix
}

View file

@ -1,175 +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 storage
import (
"os"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/apimachinery/announced"
"k8s.io/apimachinery/pkg/apimachinery/registered"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/apis/example"
exampleinstall "k8s.io/apiserver/pkg/apis/example/install"
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
"k8s.io/apiserver/pkg/storage/storagebackend"
)
var (
v1GroupVersion = schema.GroupVersion{Group: "", Version: "v1"}
registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS"))
announce = make(announced.APIGroupFactoryRegistry)
scheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(scheme)
parameterCodec = runtime.NewParameterCodec(scheme)
)
func init() {
metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)
scheme.AddUnversionedTypes(v1GroupVersion,
&metav1.Status{},
&metav1.APIVersions{},
&metav1.APIGroupList{},
&metav1.APIGroup{},
&metav1.APIResourceList{},
)
exampleinstall.Install(announce, registry, scheme)
}
type fakeNegotiater struct {
serializer, streamSerializer runtime.Serializer
framer runtime.Framer
types, streamTypes []string
}
func (n *fakeNegotiater) SupportedMediaTypes() []runtime.SerializerInfo {
var out []runtime.SerializerInfo
for _, s := range n.types {
info := runtime.SerializerInfo{Serializer: n.serializer, MediaType: s, EncodesAsText: true}
for _, t := range n.streamTypes {
if t == s {
info.StreamSerializer = &runtime.StreamSerializerInfo{
EncodesAsText: true,
Framer: n.framer,
Serializer: n.streamSerializer,
}
}
}
out = append(out, info)
}
return out
}
func (n *fakeNegotiater) UniversalDeserializer() runtime.Decoder {
return n.serializer
}
func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return n.serializer
}
func (n *fakeNegotiater) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return n.serializer
}
func TestConfigurableStorageFactory(t *testing.T) {
ns := &fakeNegotiater{types: []string{"test/test"}}
f := NewDefaultStorageFactory(storagebackend.Config{}, "test/test", ns, NewDefaultResourceEncodingConfig(registry), NewResourceConfig())
f.AddCohabitatingResources(example.Resource("test"), schema.GroupResource{Resource: "test2", Group: "2"})
called := false
testEncoderChain := func(e runtime.Encoder) runtime.Encoder {
called = true
return e
}
f.AddSerializationChains(testEncoderChain, nil, example.Resource("test"))
f.SetEtcdLocation(example.Resource("*"), []string{"/server2"})
f.SetEtcdPrefix(example.Resource("test"), "/prefix_for_test")
config, err := f.NewConfig(example.Resource("test"))
if err != nil {
t.Fatal(err)
}
if config.Prefix != "/prefix_for_test" || !reflect.DeepEqual(config.ServerList, []string{"/server2"}) {
t.Errorf("unexpected config %#v", config)
}
if !called {
t.Errorf("expected encoder chain to be called")
}
}
func TestUpdateEtcdOverrides(t *testing.T) {
registry := registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS"))
announced := make(announced.APIGroupFactoryRegistry)
exampleinstall.Install(announced, registry, scheme)
testCases := []struct {
resource schema.GroupResource
servers []string
}{
{
resource: schema.GroupResource{Group: example.GroupName, Resource: "resource"},
servers: []string{"http://127.0.0.1:10000"},
},
{
resource: schema.GroupResource{Group: example.GroupName, Resource: "resource"},
servers: []string{"http://127.0.0.1:10000", "http://127.0.0.1:20000"},
},
{
resource: schema.GroupResource{Group: example.GroupName, Resource: "resource"},
servers: []string{"http://127.0.0.1:10000"},
},
}
defaultEtcdLocation := []string{"http://127.0.0.1"}
for i, test := range testCases {
defaultConfig := storagebackend.Config{
Prefix: "/registry",
ServerList: defaultEtcdLocation,
Copier: scheme,
}
storageFactory := NewDefaultStorageFactory(defaultConfig, "", codecs, NewDefaultResourceEncodingConfig(registry), NewResourceConfig())
storageFactory.SetEtcdLocation(test.resource, test.servers)
var err error
config, err := storageFactory.NewConfig(test.resource)
if err != nil {
t.Errorf("%d: unexpected error %v", i, err)
continue
}
if !reflect.DeepEqual(config.ServerList, test.servers) {
t.Errorf("%d: expected %v, got %v", i, test.servers, config.ServerList)
continue
}
config, err = storageFactory.NewConfig(schema.GroupResource{Group: examplev1.GroupName, Resource: "unlikely"})
if err != nil {
t.Errorf("%d: unexpected error %v", i, err)
continue
}
if !reflect.DeepEqual(config.ServerList, defaultEtcdLocation) {
t.Errorf("%d: expected %v, got %v", i, defaultEtcdLocation, config.ServerList)
continue
}
}
}