add client-ca to configmap in kube-public

This commit is contained in:
deads2k 2017-02-21 07:48:04 -05:00
parent 1320021aaf
commit 4a06b69579
5 changed files with 383 additions and 0 deletions

View File

@ -22,6 +22,7 @@ package app
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -331,9 +332,26 @@ func Run(s *options.ServerRunOptions) error {
return err return err
} }
clientCA, err := readCAorNil(s.Authentication.ClientCert.ClientCA)
if err != nil {
return err
}
requestHeaderProxyCA, err := readCAorNil(s.Authentication.RequestHeader.ClientCAFile)
if err != nil {
return err
}
config := &master.Config{ config := &master.Config{
GenericConfig: genericConfig, GenericConfig: genericConfig,
ClientCARegistrationHook: master.ClientCARegistrationHook{
ClientCA: clientCA,
RequestHeaderUsernameHeaders: s.Authentication.RequestHeader.UsernameHeaders,
RequestHeaderGroupHeaders: s.Authentication.RequestHeader.GroupHeaders,
RequestHeaderExtraHeaderPrefixes: s.Authentication.RequestHeader.ExtraHeaderPrefixes,
RequestHeaderCA: requestHeaderProxyCA,
RequestHeaderAllowedNames: s.Authentication.RequestHeader.AllowedNames,
},
APIResourceConfigSource: storageFactory.APIResourceConfigSource, APIResourceConfigSource: storageFactory.APIResourceConfigSource,
StorageFactory: storageFactory, StorageFactory: storageFactory,
EnableCoreControllers: true, EnableCoreControllers: true,
@ -372,6 +390,13 @@ func Run(s *options.ServerRunOptions) error {
return nil return nil
} }
func readCAorNil(file string) ([]byte, error) {
if len(file) == 0 {
return nil, nil
}
return ioutil.ReadFile(file)
}
// PostProcessSpec adds removed definitions for backward compatibility // PostProcessSpec adds removed definitions for backward compatibility
func postProcessOpenAPISpecForBackwardCompatibility(s *spec.Swagger) (*spec.Swagger, error) { func postProcessOpenAPISpecForBackwardCompatibility(s *spec.Swagger) (*spec.Swagger, error) {
compatibilityMap := map[string]string{ compatibilityMap := map[string]string{

View File

@ -11,6 +11,7 @@ load(
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"client_ca_hook.go",
"controller.go", "controller.go",
"doc.go", "doc.go",
"import_known_versions.go", "import_known_versions.go",
@ -90,6 +91,7 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"client_ca_hook_test.go",
"controller_test.go", "controller_test.go",
"import_known_versions_test.go", "import_known_versions_test.go",
"master_openapi_test.go", "master_openapi_test.go",
@ -125,6 +127,7 @@ go_test(
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema", "//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/intstr", "//vendor:k8s.io/apimachinery/pkg/util/intstr",
"//vendor:k8s.io/apimachinery/pkg/util/net", "//vendor:k8s.io/apimachinery/pkg/util/net",
"//vendor:k8s.io/apimachinery/pkg/util/sets", "//vendor:k8s.io/apimachinery/pkg/util/sets",

View File

@ -0,0 +1,124 @@
/*
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 master
import (
"encoding/json"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/kubernetes/pkg/api"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
)
type ClientCARegistrationHook struct {
ClientCA []byte
RequestHeaderUsernameHeaders []string
RequestHeaderGroupHeaders []string
RequestHeaderExtraHeaderPrefixes []string
RequestHeaderCA []byte
RequestHeaderAllowedNames []string
}
func (h ClientCARegistrationHook) PostStartHook(hookContext genericapiserver.PostStartHookContext) error {
if len(h.ClientCA) == 0 && len(h.RequestHeaderCA) == 0 {
return nil
}
client, err := coreclient.NewForConfig(hookContext.LoopbackClientConfig)
if err != nil {
utilruntime.HandleError(err)
return nil
}
h.writeClientCAs(client)
return nil
}
// writeClientCAs is here for unit testing with a fake client
func (h ClientCARegistrationHook) writeClientCAs(client coreclient.CoreInterface) {
if _, err := client.Namespaces().Create(&api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: metav1.NamespaceSystem}}); err != nil && !apierrors.IsAlreadyExists(err) {
utilruntime.HandleError(err)
return
}
data := map[string]string{}
if len(h.ClientCA) > 0 {
data["client-ca-file"] = string(h.ClientCA)
}
if len(h.RequestHeaderCA) > 0 {
var err error
data["requestheader-username-headers"], err = jsonSerializeStringSlice(h.RequestHeaderUsernameHeaders)
if err != nil {
utilruntime.HandleError(err)
return
}
data["requestheader-group-headers"], err = jsonSerializeStringSlice(h.RequestHeaderGroupHeaders)
if err != nil {
utilruntime.HandleError(err)
return
}
data["requestheader-extra-headers-prefix"], err = jsonSerializeStringSlice(h.RequestHeaderExtraHeaderPrefixes)
if err != nil {
utilruntime.HandleError(err)
return
}
data["requestheader-client-ca-file"] = string(h.RequestHeaderCA)
data["requestheader-allowed-names"], err = jsonSerializeStringSlice(h.RequestHeaderAllowedNames)
if err != nil {
utilruntime.HandleError(err)
return
}
}
if err := writeConfigMap(client, "extension-apiserver-authentication", data); err != nil {
utilruntime.HandleError(err)
}
return
}
func jsonSerializeStringSlice(in []string) (string, error) {
out, err := json.Marshal(in)
if err != nil {
return "", err
}
return string(out), err
}
func writeConfigMap(client coreclient.ConfigMapsGetter, name string, data map[string]string) error {
existing, err := client.ConfigMaps(metav1.NamespaceSystem).Get(name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
_, err := client.ConfigMaps(metav1.NamespaceSystem).Create(&api.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: name},
Data: data,
})
return err
}
if err != nil {
return err
}
existing.Data = data
_, err = client.ConfigMaps(metav1.NamespaceSystem).Update(existing)
return err
}

View File

@ -0,0 +1,223 @@
/*
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 master
import (
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
clienttesting "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
)
func TestWriteClientCAs(t *testing.T) {
tests := []struct {
name string
hook ClientCARegistrationHook
preexistingObjs []runtime.Object
expectedConfigMaps map[string]*api.ConfigMap
expectUpdate bool
}{
{
name: "basic",
hook: ClientCARegistrationHook{
ClientCA: []byte("foo"),
RequestHeaderUsernameHeaders: []string{"alfa", "bravo", "charlie"},
RequestHeaderGroupHeaders: []string{"delta"},
RequestHeaderExtraHeaderPrefixes: []string{"echo", "foxtrot"},
RequestHeaderCA: []byte("bar"),
RequestHeaderAllowedNames: []string{"first", "second"},
},
expectedConfigMaps: map[string]*api.ConfigMap{
"extension-apiserver-authentication": {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"client-ca-file": "foo",
"requestheader-username-headers": `["alfa","bravo","charlie"]`,
"requestheader-group-headers": `["delta"]`,
"requestheader-extra-headers-prefix": `["echo","foxtrot"]`,
"requestheader-client-ca-file": "bar",
"requestheader-allowed-names": `["first","second"]`,
},
},
},
},
{
name: "skip extension-apiserver-authentication",
hook: ClientCARegistrationHook{
RequestHeaderCA: []byte("bar"),
RequestHeaderAllowedNames: []string{"first", "second"},
},
expectedConfigMaps: map[string]*api.ConfigMap{
"extension-apiserver-authentication": {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `null`,
"requestheader-group-headers": `null`,
"requestheader-extra-headers-prefix": `null`,
"requestheader-client-ca-file": "bar",
"requestheader-allowed-names": `["first","second"]`,
},
},
},
},
{
name: "skip extension-apiserver-authentication",
hook: ClientCARegistrationHook{
ClientCA: []byte("foo"),
},
expectedConfigMaps: map[string]*api.ConfigMap{
"extension-apiserver-authentication": {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"client-ca-file": "foo",
},
},
},
},
{
name: "empty allowed names",
hook: ClientCARegistrationHook{
RequestHeaderCA: []byte("bar"),
},
expectedConfigMaps: map[string]*api.ConfigMap{
"extension-apiserver-authentication": {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `null`,
"requestheader-group-headers": `null`,
"requestheader-extra-headers-prefix": `null`,
"requestheader-client-ca-file": "bar",
"requestheader-allowed-names": `null`,
},
},
},
},
{
name: "overwrite extension-apiserver-authentication",
hook: ClientCARegistrationHook{
ClientCA: []byte("foo"),
},
preexistingObjs: []runtime.Object{
&api.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"client-ca-file": "other",
},
},
},
expectedConfigMaps: map[string]*api.ConfigMap{
"extension-apiserver-authentication": {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"client-ca-file": "foo",
},
},
},
expectUpdate: true,
},
{
name: "overwrite extension-apiserver-authentication requestheader",
hook: ClientCARegistrationHook{
RequestHeaderUsernameHeaders: []string{},
RequestHeaderGroupHeaders: []string{},
RequestHeaderExtraHeaderPrefixes: []string{},
RequestHeaderCA: []byte("bar"),
RequestHeaderAllowedNames: []string{},
},
preexistingObjs: []runtime.Object{
&api.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `null`,
"requestheader-group-headers": `null`,
"requestheader-extra-headers-prefix": `null`,
"requestheader-client-ca-file": "something",
"requestheader-allowed-names": `null`,
},
},
},
expectedConfigMaps: map[string]*api.ConfigMap{
"extension-apiserver-authentication": {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"requestheader-username-headers": `[]`,
"requestheader-group-headers": `[]`,
"requestheader-extra-headers-prefix": `[]`,
"requestheader-client-ca-file": "bar",
"requestheader-allowed-names": `[]`,
},
},
},
expectUpdate: true,
},
{
name: "namespace exists",
hook: ClientCARegistrationHook{
ClientCA: []byte("foo"),
},
preexistingObjs: []runtime.Object{
&api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: metav1.NamespaceSystem}},
},
expectedConfigMaps: map[string]*api.ConfigMap{
"extension-apiserver-authentication": {
ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "extension-apiserver-authentication"},
Data: map[string]string{
"client-ca-file": "foo",
},
},
},
},
}
for _, test := range tests {
client := fake.NewSimpleClientset(test.preexistingObjs...)
test.hook.writeClientCAs(client.Core())
actualConfigMaps, updated := getFinalConfiMaps(client)
if !reflect.DeepEqual(test.expectedConfigMaps, actualConfigMaps) {
t.Errorf("%s: %v", test.name, diff.ObjectReflectDiff(test.expectedConfigMaps, actualConfigMaps))
continue
}
if test.expectUpdate != updated {
t.Errorf("%s: expected %v, got %v", test.name, test.expectUpdate, updated)
continue
}
}
}
func getFinalConfiMaps(client *fake.Clientset) (map[string]*api.ConfigMap, bool) {
ret := map[string]*api.ConfigMap{}
updated := false
for _, action := range client.Actions() {
if action.Matches("create", "configmaps") {
obj := action.(clienttesting.CreateAction).GetObject().(*api.ConfigMap)
ret[obj.Name] = obj
}
if action.Matches("update", "configmaps") {
updated = true
obj := action.(clienttesting.UpdateAction).GetObject().(*api.ConfigMap)
ret[obj.Name] = obj
}
}
return ret, updated
}

View File

@ -80,6 +80,8 @@ const (
type Config struct { type Config struct {
GenericConfig *genericapiserver.Config GenericConfig *genericapiserver.Config
ClientCARegistrationHook ClientCARegistrationHook
APIResourceConfigSource serverstorage.APIResourceConfigSource APIResourceConfigSource serverstorage.APIResourceConfigSource
StorageFactory serverstorage.StorageFactory StorageFactory serverstorage.StorageFactory
EnableCoreControllers bool EnableCoreControllers bool
@ -135,6 +137,8 @@ type EndpointReconcilerConfig struct {
// Master contains state for a Kubernetes cluster master/api server. // Master contains state for a Kubernetes cluster master/api server.
type Master struct { type Master struct {
GenericAPIServer *genericapiserver.GenericAPIServer GenericAPIServer *genericapiserver.GenericAPIServer
ClientCARegistrationHook ClientCARegistrationHook
} }
type completedConfig struct { type completedConfig struct {
@ -251,6 +255,10 @@ func (c completedConfig) New() (*Master, error) {
m.installTunneler(c.Tunneler, corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes()) m.installTunneler(c.Tunneler, corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes())
} }
if err := m.GenericAPIServer.AddPostStartHook("ca-registration", c.ClientCARegistrationHook.PostStartHook); err != nil {
glog.Fatalf("Error registering PostStartHook %q: %v", "ca-registration", err)
}
return m, nil return m, nil
} }