mirror of https://github.com/k3s-io/k3s
Merge pull request #24902 from cjcullen/webhookAuthn
Automatic merge from submit-queue Webhook Token Authenticator Add a webhook token authenticator plugin to allow a remote service to make authentication decisions.pull/6/head
commit
0ef4c6407b
|
@ -58,6 +58,11 @@ type APIServer struct {
|
|||
SSHUser string
|
||||
ServiceAccountKeyFile string
|
||||
ServiceAccountLookup bool
|
||||
WebhookTokenAuthnConfigFile string
|
||||
// The default values for StorageVersions. StorageVersions overrides
|
||||
// these; you can change this if you want to change the defaults (e.g.,
|
||||
// for testing). This is not actually exposed as a flag.
|
||||
DefaultStorageVersions string
|
||||
TokenAuthFile string
|
||||
WatchCacheSizes []string
|
||||
}
|
||||
|
@ -104,6 +109,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
|
|||
fs.StringVar(&s.AuthorizationMode, "authorization-mode", s.AuthorizationMode, "Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
|
||||
fs.StringVar(&s.AuthorizationConfig.PolicyFile, "authorization-policy-file", s.AuthorizationConfig.PolicyFile, "File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.")
|
||||
fs.StringVar(&s.AuthorizationConfig.WebhookConfigFile, "authorization-webhook-config-file", s.AuthorizationConfig.WebhookConfigFile, "File with webhook configuration in kubeconfig format, used with --authorization-mode=Webhook. The API server will query the remote service to determine access on the API server's secure port.")
|
||||
fs.StringVar(&s.WebhookTokenAuthnConfigFile, "authentication-token-webhook-config-file", s.WebhookTokenAuthnConfigFile, "File with webhook configuration for token authentication in kubeconfig format. The API server will query the remote service to determine authentication for bearer tokens.")
|
||||
fs.StringVar(&s.AdmissionControl, "admission-control", s.AdmissionControl, "Ordered list of plug-ins to do admission control of resources into cluster. Comma-delimited list of: "+strings.Join(admission.GetPlugins(), ", "))
|
||||
fs.StringVar(&s.AdmissionControlConfigFile, "admission-control-config-file", s.AdmissionControlConfigFile, "File with admission control configuration.")
|
||||
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.")
|
||||
|
|
|
@ -189,6 +189,7 @@ func Run(s *options.APIServer) error {
|
|||
ServiceAccountLookup: s.ServiceAccountLookup,
|
||||
ServiceAccountTokenGetter: serviceAccountGetter,
|
||||
KeystoneURL: s.KeystoneURL,
|
||||
WebhookTokenAuthnConfigFile: s.WebhookTokenAuthnConfigFile,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -35,6 +35,8 @@ func main() {
|
|||
arguments.InputDirs = []string{
|
||||
"k8s.io/kubernetes/pkg/api/v1",
|
||||
"k8s.io/kubernetes/pkg/api",
|
||||
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io",
|
||||
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io/v1beta1",
|
||||
"k8s.io/kubernetes/pkg/apis/authorization",
|
||||
"k8s.io/kubernetes/pkg/apis/authorization/v1beta1",
|
||||
"k8s.io/kubernetes/pkg/apis/autoscaling",
|
||||
|
|
|
@ -35,6 +35,8 @@ func main() {
|
|||
arguments.InputDirs = []string{
|
||||
"k8s.io/kubernetes/pkg/api",
|
||||
"k8s.io/kubernetes/pkg/api/v1",
|
||||
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io",
|
||||
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io/v1beta1",
|
||||
"k8s.io/kubernetes/pkg/apis/authorization",
|
||||
"k8s.io/kubernetes/pkg/apis/authorization/v1beta1",
|
||||
"k8s.io/kubernetes/pkg/apis/autoscaling",
|
||||
|
|
|
@ -56,6 +56,7 @@ kube-apiserver
|
|||
--advertise-address=<nil>: 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.
|
||||
--allow-privileged[=false]: If true, allow privileged containers.
|
||||
--apiserver-count=1: The number of apiservers running in the cluster
|
||||
--authentication-token-webhook-config-file="": File with webhook configuration for token authentication in kubeconfig format. The API server will query the remote service to determine authentication for bearer tokens.
|
||||
--authorization-mode="AlwaysAllow": Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: AlwaysAllow,AlwaysDeny,ABAC,Webhook
|
||||
--authorization-policy-file="": File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.
|
||||
--authorization-webhook-config-file="": File with webhook configuration in kubeconfig format, used with --authorization-mode=Webhook. The API server will query the remote service to determine access on the API server's secure port.
|
||||
|
@ -111,7 +112,7 @@ kube-apiserver
|
|||
--ssh-user="": If non-empty, use secure SSH proxy to the nodes, using this user name
|
||||
--storage-backend="": The storage backend for persistence. Options: 'etcd2' (default), 'etcd3'.
|
||||
--storage-media-type="application/json": The media type to use to store objects in storage. Defaults to application/json. Some resources may only support a specific media type and will ignore this setting.
|
||||
--storage-versions="apps/v1alpha1,authorization.k8s.io/v1beta1,autoscaling/v1,batch/v1,componentconfig/v1alpha1,extensions/v1beta1,metrics/v1alpha1,policy/v1alpha1,v1": The per-group version to store resources in. Specified in the format "group1/version1,group2/version2,...". In the case where objects are moved from one group to the other, you may specify the format "group1=group2/v1beta1,group3/v1beta1,...". You only need to pass the groups you wish to change from the defaults. It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.
|
||||
--storage-versions="apps/v1alpha1,authentication.k8s.io/v1beta1,authorization.k8s.io/v1beta1,autoscaling/v1,batch/v1,componentconfig/v1alpha1,extensions/v1beta1,metrics/v1alpha1,policy/v1alpha1,v1": The per-group version to store resources in. Specified in the format "group1/version1,group2/version2,...". In the case where objects are moved from one group to the other, you may specify the format "group1=group2/v1beta1,group3/v1beta1,...". You only need to pass the groups you wish to change from the defaults. It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.
|
||||
--tls-cert-file="": File containing 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.
|
||||
--tls-private-key-file="": File containing x509 private key matching --tls-cert-file.
|
||||
--token-auth-file="": If set, the file that will be used to secure the secure port of the API server via token authentication.
|
||||
|
|
|
@ -18,6 +18,7 @@ api-token
|
|||
api-version
|
||||
apiserver-count
|
||||
auth-path
|
||||
authentication-token-webhook-config-file
|
||||
authorization-mode
|
||||
authorization-policy-file
|
||||
authorization-webhook-config-file
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||
|
||||
package authentication
|
||||
|
||||
import (
|
||||
api "k8s.io/kubernetes/pkg/api"
|
||||
unversioned "k8s.io/kubernetes/pkg/api/unversioned"
|
||||
conversion "k8s.io/kubernetes/pkg/conversion"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := api.Scheme.AddGeneratedDeepCopyFuncs(
|
||||
DeepCopy_authenticationk8sio_TokenReview,
|
||||
DeepCopy_authenticationk8sio_TokenReviewSpec,
|
||||
DeepCopy_authenticationk8sio_TokenReviewStatus,
|
||||
DeepCopy_authenticationk8sio_UserInfo,
|
||||
); err != nil {
|
||||
// if one of the deep copy functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func DeepCopy_authenticationk8sio_TokenReview(in TokenReview, out *TokenReview, c *conversion.Cloner) error {
|
||||
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DeepCopy_authenticationk8sio_TokenReviewSpec(in.Spec, &out.Spec, c); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DeepCopy_authenticationk8sio_TokenReviewStatus(in.Status, &out.Status, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeepCopy_authenticationk8sio_TokenReviewSpec(in TokenReviewSpec, out *TokenReviewSpec, c *conversion.Cloner) error {
|
||||
out.Token = in.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeepCopy_authenticationk8sio_TokenReviewStatus(in TokenReviewStatus, out *TokenReviewStatus, c *conversion.Cloner) error {
|
||||
out.Authenticated = in.Authenticated
|
||||
if err := DeepCopy_authenticationk8sio_UserInfo(in.User, &out.User, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeepCopy_authenticationk8sio_UserInfo(in UserInfo, out *UserInfo, c *conversion.Cloner) error {
|
||||
out.Username = in.Username
|
||||
out.UID = in.UID
|
||||
if in.Groups != nil {
|
||||
in, out := in.Groups, &out.Groups
|
||||
*out = make([]string, len(in))
|
||||
copy(*out, in)
|
||||
} else {
|
||||
out.Groups = nil
|
||||
}
|
||||
if in.Extra != nil {
|
||||
in, out := in.Extra, &out.Extra
|
||||
*out = make(map[string][]string)
|
||||
for key, val := range in {
|
||||
if newVal, err := c.DeepCopy(val); err != nil {
|
||||
return err
|
||||
} else {
|
||||
(*out)[key] = newVal.([]string)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.Extra = nil
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package install installs the experimental API group, making it available as
|
||||
// an option to all of the API encoding/decoding machinery.
|
||||
package install
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/meta"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apimachinery"
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io"
|
||||
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
const importPrefix = "k8s.io/kubernetes/pkg/apis/authentication.k8s.io"
|
||||
|
||||
var accessor = meta.NewAccessor()
|
||||
|
||||
// availableVersions lists all known external versions for this group from most preferred to least preferred
|
||||
var availableVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||
|
||||
func init() {
|
||||
registered.RegisterVersions(availableVersions)
|
||||
externalVersions := []unversioned.GroupVersion{}
|
||||
for _, v := range availableVersions {
|
||||
if registered.IsAllowedVersion(v) {
|
||||
externalVersions = append(externalVersions, v)
|
||||
}
|
||||
}
|
||||
if len(externalVersions) == 0 {
|
||||
glog.V(4).Infof("No version is registered for group %v", authentication.GroupName)
|
||||
return
|
||||
}
|
||||
|
||||
if err := registered.EnableVersions(externalVersions...); err != nil {
|
||||
glog.V(4).Infof("%v", err)
|
||||
return
|
||||
}
|
||||
if err := enableVersions(externalVersions); err != nil {
|
||||
glog.V(4).Infof("%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: enableVersions should be centralized rather than spread in each API
|
||||
// group.
|
||||
// We can combine registered.RegisterVersions, registered.EnableVersions and
|
||||
// registered.RegisterGroup once we have moved enableVersions there.
|
||||
func enableVersions(externalVersions []unversioned.GroupVersion) error {
|
||||
addVersionsToScheme(externalVersions...)
|
||||
preferredExternalVersion := externalVersions[0]
|
||||
|
||||
groupMeta := apimachinery.GroupMeta{
|
||||
GroupVersion: preferredExternalVersion,
|
||||
GroupVersions: externalVersions,
|
||||
RESTMapper: newRESTMapper(externalVersions),
|
||||
SelfLinker: runtime.SelfLinker(accessor),
|
||||
InterfacesFor: interfacesFor,
|
||||
}
|
||||
|
||||
if err := registered.RegisterGroup(groupMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
api.RegisterRESTMapper(groupMeta.RESTMapper)
|
||||
return nil
|
||||
}
|
||||
|
||||
func addVersionsToScheme(externalVersions ...unversioned.GroupVersion) {
|
||||
// add the internal version to Scheme
|
||||
authentication.AddToScheme(api.Scheme)
|
||||
// add the enabled external versions to Scheme
|
||||
for _, v := range externalVersions {
|
||||
if !registered.IsEnabledVersion(v) {
|
||||
glog.Errorf("Version %s is not enabled, so it will not be added to the Scheme.", v)
|
||||
continue
|
||||
}
|
||||
switch v {
|
||||
case v1beta1.SchemeGroupVersion:
|
||||
v1beta1.AddToScheme(api.Scheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper {
|
||||
rootScoped := sets.NewString("TokenReview")
|
||||
ignoredKinds := sets.NewString()
|
||||
return api.NewDefaultRESTMapper(externalVersions, interfacesFor, importPrefix, ignoredKinds, rootScoped)
|
||||
}
|
||||
|
||||
func interfacesFor(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
|
||||
switch version {
|
||||
case v1beta1.SchemeGroupVersion:
|
||||
return &meta.VersionInterfaces{
|
||||
ObjectConvertor: api.Scheme,
|
||||
MetadataAccessor: accessor,
|
||||
}, nil
|
||||
default:
|
||||
g, _ := registered.Group(authentication.GroupName)
|
||||
return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, g.GroupVersions)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "authentication.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
|
||||
func Kind(kind string) unversioned.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns back a Group qualified GroupResource
|
||||
func Resource(resource string) unversioned.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
addKnownTypes(scheme)
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&TokenReview{},
|
||||
)
|
||||
}
|
||||
|
||||
func (obj *TokenReview) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
)
|
||||
|
||||
// TokenReview attempts to authenticate a token to a known user.
|
||||
type TokenReview struct {
|
||||
unversioned.TypeMeta
|
||||
|
||||
// Spec holds information about the request being evaluated
|
||||
Spec TokenReviewSpec
|
||||
|
||||
// Status is filled in by the server and indicates whether the request can be authenticated.
|
||||
Status TokenReviewStatus
|
||||
}
|
||||
|
||||
// TokenReviewSpec is a description of the token authentication request.
|
||||
type TokenReviewSpec struct {
|
||||
// Token is the opaque bearer token.
|
||||
Token string
|
||||
}
|
||||
|
||||
// TokenReviewStatus is the result of the token authentication request.
|
||||
type TokenReviewStatus struct {
|
||||
// Authenticated indicates that the token was associated with a known user.
|
||||
Authenticated bool
|
||||
// User is the UserInfo associated with the provided token.
|
||||
User UserInfo
|
||||
}
|
||||
|
||||
// UserInfo holds the information about the user needed to implement the
|
||||
// user.Info interface.
|
||||
type UserInfo struct {
|
||||
// The name that uniquely identifies this user among all active users.
|
||||
Username string
|
||||
// A unique value that identifies this user across time. If this user is
|
||||
// deleted and another user by the same name is added, they will have
|
||||
// different UIDs.
|
||||
UID string
|
||||
// The names of groups this user is a part of.
|
||||
Groups []string
|
||||
// Any additional information provided by the authenticator.
|
||||
Extra map[string][]string
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func addConversionFuncs(scheme *runtime.Scheme) {
|
||||
// Add non-generated conversion functions
|
||||
err := scheme.AddConversionFuncs()
|
||||
if err != nil {
|
||||
// If one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file was autogenerated by conversion-gen. Do not edit it manually!
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
api "k8s.io/kubernetes/pkg/api"
|
||||
authentication_k8s_io "k8s.io/kubernetes/pkg/apis/authentication.k8s.io"
|
||||
conversion "k8s.io/kubernetes/pkg/conversion"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := api.Scheme.AddGeneratedConversionFuncs(
|
||||
Convert_v1beta1_TokenReview_To_authenticationk8sio_TokenReview,
|
||||
Convert_authenticationk8sio_TokenReview_To_v1beta1_TokenReview,
|
||||
Convert_v1beta1_TokenReviewSpec_To_authenticationk8sio_TokenReviewSpec,
|
||||
Convert_authenticationk8sio_TokenReviewSpec_To_v1beta1_TokenReviewSpec,
|
||||
Convert_v1beta1_TokenReviewStatus_To_authenticationk8sio_TokenReviewStatus,
|
||||
Convert_authenticationk8sio_TokenReviewStatus_To_v1beta1_TokenReviewStatus,
|
||||
Convert_v1beta1_UserInfo_To_authenticationk8sio_UserInfo,
|
||||
Convert_authenticationk8sio_UserInfo_To_v1beta1_UserInfo,
|
||||
); err != nil {
|
||||
// if one of the conversion functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_TokenReview_To_authenticationk8sio_TokenReview(in *TokenReview, out *authentication_k8s_io.TokenReview, s conversion.Scope) error {
|
||||
if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_v1beta1_TokenReviewSpec_To_authenticationk8sio_TokenReviewSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_v1beta1_TokenReviewStatus_To_authenticationk8sio_TokenReviewStatus(&in.Status, &out.Status, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1beta1_TokenReview_To_authenticationk8sio_TokenReview(in *TokenReview, out *authentication_k8s_io.TokenReview, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_TokenReview_To_authenticationk8sio_TokenReview(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_authenticationk8sio_TokenReview_To_v1beta1_TokenReview(in *authentication_k8s_io.TokenReview, out *TokenReview, s conversion.Scope) error {
|
||||
if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_authenticationk8sio_TokenReviewSpec_To_v1beta1_TokenReviewSpec(&in.Spec, &out.Spec, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_authenticationk8sio_TokenReviewStatus_To_v1beta1_TokenReviewStatus(&in.Status, &out.Status, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_authenticationk8sio_TokenReview_To_v1beta1_TokenReview(in *authentication_k8s_io.TokenReview, out *TokenReview, s conversion.Scope) error {
|
||||
return autoConvert_authenticationk8sio_TokenReview_To_v1beta1_TokenReview(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_TokenReviewSpec_To_authenticationk8sio_TokenReviewSpec(in *TokenReviewSpec, out *authentication_k8s_io.TokenReviewSpec, s conversion.Scope) error {
|
||||
out.Token = in.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1beta1_TokenReviewSpec_To_authenticationk8sio_TokenReviewSpec(in *TokenReviewSpec, out *authentication_k8s_io.TokenReviewSpec, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_TokenReviewSpec_To_authenticationk8sio_TokenReviewSpec(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_authenticationk8sio_TokenReviewSpec_To_v1beta1_TokenReviewSpec(in *authentication_k8s_io.TokenReviewSpec, out *TokenReviewSpec, s conversion.Scope) error {
|
||||
out.Token = in.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_authenticationk8sio_TokenReviewSpec_To_v1beta1_TokenReviewSpec(in *authentication_k8s_io.TokenReviewSpec, out *TokenReviewSpec, s conversion.Scope) error {
|
||||
return autoConvert_authenticationk8sio_TokenReviewSpec_To_v1beta1_TokenReviewSpec(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_TokenReviewStatus_To_authenticationk8sio_TokenReviewStatus(in *TokenReviewStatus, out *authentication_k8s_io.TokenReviewStatus, s conversion.Scope) error {
|
||||
out.Authenticated = in.Authenticated
|
||||
if err := Convert_v1beta1_UserInfo_To_authenticationk8sio_UserInfo(&in.User, &out.User, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1beta1_TokenReviewStatus_To_authenticationk8sio_TokenReviewStatus(in *TokenReviewStatus, out *authentication_k8s_io.TokenReviewStatus, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_TokenReviewStatus_To_authenticationk8sio_TokenReviewStatus(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_authenticationk8sio_TokenReviewStatus_To_v1beta1_TokenReviewStatus(in *authentication_k8s_io.TokenReviewStatus, out *TokenReviewStatus, s conversion.Scope) error {
|
||||
out.Authenticated = in.Authenticated
|
||||
if err := Convert_authenticationk8sio_UserInfo_To_v1beta1_UserInfo(&in.User, &out.User, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_authenticationk8sio_TokenReviewStatus_To_v1beta1_TokenReviewStatus(in *authentication_k8s_io.TokenReviewStatus, out *TokenReviewStatus, s conversion.Scope) error {
|
||||
return autoConvert_authenticationk8sio_TokenReviewStatus_To_v1beta1_TokenReviewStatus(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_UserInfo_To_authenticationk8sio_UserInfo(in *UserInfo, out *authentication_k8s_io.UserInfo, s conversion.Scope) error {
|
||||
out.Username = in.Username
|
||||
out.UID = in.UID
|
||||
if in.Groups != nil {
|
||||
in, out := &in.Groups, &out.Groups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
} else {
|
||||
out.Groups = nil
|
||||
}
|
||||
if in.Extra != nil {
|
||||
in, out := &in.Extra, &out.Extra
|
||||
*out = make(map[string][]string, len(*in))
|
||||
for key, val := range *in {
|
||||
newVal := new([]string)
|
||||
// TODO: Inefficient conversion - can we improve it?
|
||||
if err := s.Convert(&val, newVal, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[key] = *newVal
|
||||
}
|
||||
} else {
|
||||
out.Extra = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1beta1_UserInfo_To_authenticationk8sio_UserInfo(in *UserInfo, out *authentication_k8s_io.UserInfo, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_UserInfo_To_authenticationk8sio_UserInfo(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_authenticationk8sio_UserInfo_To_v1beta1_UserInfo(in *authentication_k8s_io.UserInfo, out *UserInfo, s conversion.Scope) error {
|
||||
out.Username = in.Username
|
||||
out.UID = in.UID
|
||||
if in.Groups != nil {
|
||||
in, out := &in.Groups, &out.Groups
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
} else {
|
||||
out.Groups = nil
|
||||
}
|
||||
if in.Extra != nil {
|
||||
in, out := &in.Extra, &out.Extra
|
||||
*out = make(map[string][]string, len(*in))
|
||||
for key, val := range *in {
|
||||
newVal := new([]string)
|
||||
// TODO: Inefficient conversion - can we improve it?
|
||||
if err := s.Convert(&val, newVal, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
(*out)[key] = *newVal
|
||||
}
|
||||
} else {
|
||||
out.Extra = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_authenticationk8sio_UserInfo_To_v1beta1_UserInfo(in *authentication_k8s_io.UserInfo, out *UserInfo, s conversion.Scope) error {
|
||||
return autoConvert_authenticationk8sio_UserInfo_To_v1beta1_UserInfo(in, out, s)
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
api "k8s.io/kubernetes/pkg/api"
|
||||
unversioned "k8s.io/kubernetes/pkg/api/unversioned"
|
||||
conversion "k8s.io/kubernetes/pkg/conversion"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := api.Scheme.AddGeneratedDeepCopyFuncs(
|
||||
DeepCopy_v1beta1_TokenReview,
|
||||
DeepCopy_v1beta1_TokenReviewSpec,
|
||||
DeepCopy_v1beta1_TokenReviewStatus,
|
||||
DeepCopy_v1beta1_UserInfo,
|
||||
); err != nil {
|
||||
// if one of the deep copy functions is malformed, detect it immediately.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func DeepCopy_v1beta1_TokenReview(in TokenReview, out *TokenReview, c *conversion.Cloner) error {
|
||||
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DeepCopy_v1beta1_TokenReviewSpec(in.Spec, &out.Spec, c); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DeepCopy_v1beta1_TokenReviewStatus(in.Status, &out.Status, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeepCopy_v1beta1_TokenReviewSpec(in TokenReviewSpec, out *TokenReviewSpec, c *conversion.Cloner) error {
|
||||
out.Token = in.Token
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeepCopy_v1beta1_TokenReviewStatus(in TokenReviewStatus, out *TokenReviewStatus, c *conversion.Cloner) error {
|
||||
out.Authenticated = in.Authenticated
|
||||
if err := DeepCopy_v1beta1_UserInfo(in.User, &out.User, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeepCopy_v1beta1_UserInfo(in UserInfo, out *UserInfo, c *conversion.Cloner) error {
|
||||
out.Username = in.Username
|
||||
out.UID = in.UID
|
||||
if in.Groups != nil {
|
||||
in, out := in.Groups, &out.Groups
|
||||
*out = make([]string, len(in))
|
||||
copy(*out, in)
|
||||
} else {
|
||||
out.Groups = nil
|
||||
}
|
||||
if in.Extra != nil {
|
||||
in, out := in.Extra, &out.Extra
|
||||
*out = make(map[string][]string)
|
||||
for key, val := range in {
|
||||
if newVal, err := c.DeepCopy(val); err != nil {
|
||||
return err
|
||||
} else {
|
||||
(*out)[key] = newVal.([]string)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.Extra = nil
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) {
|
||||
scheme.AddDefaultingFuncs()
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +genconversion=true
|
||||
package v1beta1
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "authentication.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: "v1beta1"}
|
||||
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
// Add the API to Scheme.
|
||||
addKnownTypes(scheme)
|
||||
addDefaultingFuncs(scheme)
|
||||
addConversionFuncs(scheme)
|
||||
}
|
||||
|
||||
// Adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&TokenReview{},
|
||||
)
|
||||
}
|
||||
|
||||
func (obj *TokenReview) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
)
|
||||
|
||||
// TokenReview attempts to authenticate a token to a known user.
|
||||
type TokenReview struct {
|
||||
unversioned.TypeMeta `json:",inline"`
|
||||
|
||||
// Spec holds information about the request being evaluated
|
||||
Spec TokenReviewSpec `json:"spec"`
|
||||
|
||||
// Status is filled in by the server and indicates whether the request can be authenticated.
|
||||
Status TokenReviewStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// TokenReviewSpec is a description of the token authentication request.
|
||||
type TokenReviewSpec struct {
|
||||
// Token is the opaque bearer token.
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// TokenReviewStatus is the result of the token authentication request.
|
||||
type TokenReviewStatus struct {
|
||||
// Authenticated indicates that the token was associated with a known user.
|
||||
Authenticated bool `json:"authenticated,omitempty"`
|
||||
// User is the UserInfo associated with the provided token.
|
||||
User UserInfo `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// UserInfo holds the information about the user needed to implement the
|
||||
// user.Info interface.
|
||||
type UserInfo struct {
|
||||
// The name that uniquely identifies this user among all active users.
|
||||
Username string `json:"username,omitempty"`
|
||||
// A unique value that identifies this user across time. If this user is
|
||||
// deleted and another user by the same name is added, they will have
|
||||
// different UIDs.
|
||||
UID string `json:"uid,omitempty"`
|
||||
// The names of groups this user is a part of.
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
// Any additional information provided by the authenticator.
|
||||
Extra map[string][]string `json:"extra,omitempty"`
|
||||
}
|
|
@ -30,6 +30,7 @@ import (
|
|||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook"
|
||||
)
|
||||
|
||||
type AuthenticatorConfig struct {
|
||||
|
@ -45,6 +46,7 @@ type AuthenticatorConfig struct {
|
|||
ServiceAccountLookup bool
|
||||
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
|
||||
KeystoneURL string
|
||||
WebhookTokenAuthnConfigFile string
|
||||
}
|
||||
|
||||
// New returns an authenticator.Request or an error that supports the standard
|
||||
|
@ -100,6 +102,14 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
|||
authenticators = append(authenticators, keystoneAuth)
|
||||
}
|
||||
|
||||
if len(config.WebhookTokenAuthnConfigFile) > 0 {
|
||||
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authenticators = append(authenticators, webhookTokenAuth)
|
||||
}
|
||||
|
||||
switch len(authenticators) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
|
@ -187,3 +197,12 @@ func newAuthenticatorFromKeystoneURL(keystoneURL string) (authenticator.Request,
|
|||
|
||||
return basicauth.New(keystoneAuthenticator), nil
|
||||
}
|
||||
|
||||
func newWebhookTokenAuthenticator(webhookConfigFile string) (authenticator.Request, error) {
|
||||
webhookTokenAuthenticator, err := webhook.New(webhookConfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bearertoken.New(webhookTokenAuthenticator), nil
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
_ "k8s.io/kubernetes/pkg/api/install"
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
_ "k8s.io/kubernetes/pkg/apis/apps/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/authentication.k8s.io/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/batch/install"
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file is an exact copy of
|
||||
// plugin/pkg/auth/authorizer/webhook/certs_test.go
|
||||
|
||||
package webhook
|
||||
|
||||
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA6IVXGPX5yP2Q6TAlQXIQsavzSqZ973iZvpQBGTI6M98gTSVm
|
||||
eBYE3o7S8e6WTI3DCnWwqc8Md1rT92FtaQLwv+uMNXijLio5RVBqjUEbunD5In/+
|
||||
T/y5sE9P3CzcWy6CEhIvORAZj6UlvgZzbRwI91+EVFR5jd8JU0e/L9Ds1jLZFyQw
|
||||
Kc1ADo+Tj9O4l0WtpRlrhzTgoor4C3fAQZm0mq+llTnxCmw+lhy8t88bPG1cMwdd
|
||||
DtUTbpetc++2JZ62Q3F1nqcX1EcHDidR0x3j+3357BLkXRK4MQsWLYLzeZ3X1ghW
|
||||
XT062H866PcIV+MX4H58spMN5cVYk5YTneGihQIDAQABAoIBAHU7FQieq4ssXK1U
|
||||
+tOeQNBzUzxl6MSd11YApPUhH7sbWdvLaXhOEbJr6+rSUbDTIGzbnXBf1XcvsgLd
|
||||
eh4hv2PjzFMBObSC0VEjFDWXh/VeFB3SzlNhpfVAZ5EohQjrz+RwiqKIfXqw1vCR
|
||||
rAxswBCIdd1WodpngvocCEaBXYc4MblaPhJDVtxQe8ndEakkSDlX9Z3qIaIGyXRa
|
||||
NvY/yURVuXhwDDd7C2QBT6CXGWhldAg7xrRVTcIoqAUfZCgfis0H8cQOa1cGNsbW
|
||||
t/oHm1fYTxMKFPhWQG0oimx+XJ07BeGgraDRLnxxNnGWTg/W33bc0ZCxCVT0Q5p9
|
||||
kMMfQUECgYEA9cewTK4ZRKC4bTdwqLTh3cyMkbyN4kBHmB1mS2FV/T0l4oZThM//
|
||||
OZ6KFnRCuvfuJIOa70s2bqUYky8NTQAidnnbTW2nZ/E5JdeIBs1fAfadAqiPdmkf
|
||||
MhvjBF/XfLnbCuXx3jA7GmNCpunJysuLtQzwlQlZLojN231uS+3LFbkCgYEA8jCC
|
||||
MgKYaDWssQbT7zfk5MxyZIH3F9N8K2RBIDSVuMo/E1LCIJ06/k+4jdv8nAWYJXcN
|
||||
eyLG7l0SXqrpMBSc9+ZTJgmbo0Mw+npvJHbJvAtD/XOSPjlIqkzPAUrxuiBYxa5S
|
||||
IfKZibygXKAbQMEwY7I4sTbBtIyiQmo9csxt2S0CgYEAiBi1VSCquUfOGBw09BaF
|
||||
Y85aoHCqmHhDrMXK2T7i4MG1csQzBz4t8/gIOvrR4LpdUjbV2l/pmkctXoMVeGf0
|
||||
rWo4t51ar8HxhTTeC/Y4/9tRgiFYn5cCQTsT8F4p8tTvqA9AaWqHr8r7I3Yd2X/w
|
||||
sqahqcVtbskuRLYmF0FrzXECgYAeiR0xPwCGSxYt78Vy6OI0Ms7Ne1FzMJf8RJSt
|
||||
gdPKy70uK4YMZKaWf+iuAimUZmQrfRo3B0h7r0JsqzHhfQfZfbHIHvf/mq4nNp6i
|
||||
w1NmISl+YD71F3Xg+vQynodhx0hKDFOQsizHn/+8DffBr1nxh/v75AKCSCUBKLH8
|
||||
sme7NQKBgDHQac2TmDSelE2uXTGxEVDQs/EpdJh7oCTLQ99Xud/DsaCOrt2s7aRX
|
||||
1FEohsCaUnqwS07/iH2o6Qb/qOteufB9I7FG85nAvqmP5dI4crGNNa8Rl6fXJaR8
|
||||
TUwpZmylTKEJ9zLt2PADglyDrQ2D+1WNzh966Oo9c+kZt4WJM0aF
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAKK9m2Cfg5uhMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMBsxGTAXBgNVBAMMEHdlYmhvb2tfYXV0aHpfY2EwggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQDohVcY9fnI/ZDpMCVBchCxq/NKpn3veJm+lAEZ
|
||||
Mjoz3yBNJWZ4FgTejtLx7pZMjcMKdbCpzwx3WtP3YW1pAvC/64w1eKMuKjlFUGqN
|
||||
QRu6cPkif/5P/LmwT0/cLNxbLoISEi85EBmPpSW+BnNtHAj3X4RUVHmN3wlTR78v
|
||||
0OzWMtkXJDApzUAOj5OP07iXRa2lGWuHNOCiivgLd8BBmbSar6WVOfEKbD6WHLy3
|
||||
zxs8bVwzB10O1RNul61z77YlnrZDcXWepxfURwcOJ1HTHeP7ffnsEuRdErgxCxYt
|
||||
gvN5ndfWCFZdPTrYfzro9whX4xfgfnyykw3lxViTlhOd4aKFAgMBAAGjUDBOMB0G
|
||||
A1UdDgQWBBSumZL6MMwmFGyhQAwl/v0lYDzdZjAfBgNVHSMEGDAWgBSumZL6MMwm
|
||||
FGyhQAwl/v0lYDzdZjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAG
|
||||
6k+bZxKYq4PVZHWTKA7RSjv95FMMr4RSFwKn/n8TUD44ANWYqDrEfVmxAMn3NVK9
|
||||
ckA8mIRym4IGiWD9eBGgPNNtbAq8Wl/9+5qbDMerpXuRnG3wNY7RU75Rl008m52r
|
||||
c2i86ZPUi2fAJZyMf5StWE21oKiDYYQqlB6xxsIj6OHhf7536vEysoztNX5FpS2n
|
||||
q8wG0EhJVhG+Qyww8IlZA5Cjoh71Eqkcwb4cuLjPypxmLm0ywZ/6KgzV+IF+CT2v
|
||||
TJIpMokDUKlRi9cWSqkWXFE6xbCmhrrwKYsi0X6Vvi7a0pmOnSzKCQl8jN8u4A9R
|
||||
xar2YeJ6mCCzSAPM69DP
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAon7dRV4Br10dLcf8zgs/hOHouELveFr8tuWVIFivxSdnac2k
|
||||
6dM4iQ2uYS9nTXxNhyJJ/TX/MHEYc4gSXoqUbtx9jE3VA4mCKDhO7cJtCYxq0QV/
|
||||
PlQCiAPjn5nUMt9ACdii7/uTFDl46bK9K6ajvKHfHoWeYaJsF54kxBq5IMj+QaB2
|
||||
nc+pba00bGG09sYcHyD37QH+ugx64x+21xMYj2LB/uPoqZM0kj1GHPxAs8GqFq2P
|
||||
gwkv589AlHqt2iMCTAqED2jcg4FeS2r1DeYHwGyGAPfWTdA8RZ+gZ/P0Gj91T+4B
|
||||
9srR7BybUFjf1KxEcvPXBvP5r8OwOiYjS8hx/wIDAQABAoIBAQCVBQ9bfDjDX/tQ
|
||||
buVS+FHKRXss8IW4tIiqGqXGQk7/2YEnMKaaoVBpsBhJnDV6hBJ9aV69TnW3MSCh
|
||||
YxqlhSVW/fJNZ1uAoOyygeEwfmuMpC+ZfRcSS+z+W8K2LVbDSKXr4babqvVZSNOw
|
||||
TnDZxTrH1RNPZG65T0Ed77P7/B3nB7aeB2UMuHMQNZ3KrYDTck2R2uTGp+29TplN
|
||||
blS4VAg2/9KqFr7jkS3/C4jjxVd7d9mm0VdAvLcvENVXqSTYV8xDp+VLTnmtXi5f
|
||||
LXcopS+zKtKqT7MM7RA2sKrmSfrQBIXW2E1kfDFtpZHajhDutdYkSTH665W1G23M
|
||||
dIgy3ajhAoGBANE4AhMUVfQqXUCU0UjUDxiOy/8XcKiW/dKhRR1DOQY24J/k+UWv
|
||||
PEGVcBW4tgalYkTl/AW6hsNfubZaJuw05cHIKdL3df6ug7BUiJpmIv3sjrvPRYvA
|
||||
WY1UTb3EJrswGz8S2l5+2S3WFTCfK7S6N6Stfi1x6rMJBuOss7HGqdh3AoGBAMbU
|
||||
WavRqGRsvJFfE5bahXbFpkGWT++BTMP+lzK31z24JjmJdwO+ABWU4/xaXayA4skH
|
||||
PrzlYUcGJWIedb6W4dvz0sA59yflQzYmREkQPE+wbyor003y7mB8LpFiCnfaFhRn
|
||||
hoowkyIY+xM4UeDXWWt3DhBElgfA8fYZdiNJEhy5AoGBAMwYUw3BvMffu/CQPElL
|
||||
dR6DzsUeXKxZ/2pGIGIXfb1uM1pHyFQOSj3ARgMqmYeKNn73zA7akzRsYYJeF7I9
|
||||
OBT96q7+8IBuRdDx5gCYunHzHppf7HwUPEf+gYgpnY7lsu6ouZWNMNfiC/HOlJhN
|
||||
QJLJHFnA0y+sEqhvhSxbnLypAoGBALHCZ+kVKFegX3YYaosUEv589obsu8qE7vzL
|
||||
QKI3elfTq1kFbUILPEgPNUUIBXeUQy03LP/0k2PMOt/eG6apfoQHGQSCzlT8w3pF
|
||||
/AbWXRVhyAEL7X5jEntwirGv1WwRrmvPopkplGGHs/EbCRjbbzaE2i3xI7EK70f2
|
||||
u4gQbAEBAoGAVR4u8g5Tx2Gunzh7tfJJ5e3xGBGS3Yq+JqUVNI6t6KIAPh0rM+aD
|
||||
9tDgcwn8Vn5YU7YkqA2T8OOFsbJfrfZ7y7+oeMFukuIyxgmy9n/V/tCIrV/lR7A5
|
||||
3iYhanTUbQswx19pSRgsXi7fo9Fi/dmUwyHi18uz5FdLyCTsMbf3uA8=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAPqJyUfmRxGLMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMBsxGTAXBgNVBAMMEHdlYmhvb2tfYXV0aHpfY2EwggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQCift1FXgGvXR0tx/zOCz+E4ei4Qu94Wvy25ZUg
|
||||
WK/FJ2dpzaTp0ziJDa5hL2dNfE2HIkn9Nf8wcRhziBJeipRu3H2MTdUDiYIoOE7t
|
||||
wm0JjGrRBX8+VAKIA+OfmdQy30AJ2KLv+5MUOXjpsr0rpqO8od8ehZ5homwXniTE
|
||||
GrkgyP5BoHadz6ltrTRsYbT2xhwfIPftAf66DHrjH7bXExiPYsH+4+ipkzSSPUYc
|
||||
/ECzwaoWrY+DCS/nz0CUeq3aIwJMCoQPaNyDgV5LavUN5gfAbIYA99ZN0DxFn6Bn
|
||||
8/QaP3VP7gH2ytHsHJtQWN/UrERy89cG8/mvw7A6JiNLyHH/AgMBAAGjUDBOMB0G
|
||||
A1UdDgQWBBS6IGeGHZCylibt0GzY0dP6C0J9VjAfBgNVHSMEGDAWgBS6IGeGHZCy
|
||||
libt0GzY0dP6C0J9VjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAi
|
||||
A1dp75kbePFZsUNjxN6B/Pv0vSoaOjQkc4hpxKbI4VRCuPGmMRFYTlKCzoZ53OqQ
|
||||
2Jmu1Zbzel/bV5vXrW0BOfUpfWYzd/usIJEuTgU8ijBIB+IHAXYwwxeKRcz3C+7+
|
||||
9RBMF7gSg9pU2hrSvjhh7Q96IMJ42Z7tI3WD8SZaQLjY1NW1jrQVsg66ktdMke7x
|
||||
zC8oIRIBH4W6l5s7jtZx1k305NE04pigcFLxCxOmicKd66ysI5hAZkD7y0dgwgtL
|
||||
IqCQy6t7uJDydRiNRfPFr9Eg7uOu83JGw11f3bGVhJVCbzHyKddvkQsQbdaMHRgZ
|
||||
zgmWLORg+ls1H1oaJiNW
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAtegsP499au5ZxlwM26rk3TnRgakchQi/9bhfMr0LaEKng1lR
|
||||
XopzzGuGeZQswzbx7iiH89JzFkurZoEmZwtS4Aybit92VOSv0EUnyx7WR3V21ObZ
|
||||
iQO0rr0UmG84NjdzATkqF+R5Z+HN9shwgBI4PR1j/ybCt7jNz+OM/VmqsgzoKLoa
|
||||
bGrx7LCTPk8y5G8AoPOrIAP+9WHJsKQSRT8Lru4lYqseBxvhjqo8NRqzZLg79ldY
|
||||
aKFqa2N2zr5qp94sG3/zihNDxjZvyyn9c8qvPBL0xOyayvOJG8eZUmjQpUMv7Jk3
|
||||
qFmdMgGaDJRw0Qg6+/Zt6MHNs6Rbb8hmwuMSpwIDAQABAoIBAQCjzeFijwzKKL4w
|
||||
0B1IBhi3WeReFPG4nkt1ssQPBYrrJPKBZgHO13A1STI78wFn/OdYpajfF8hI8HT1
|
||||
BiGVsu27Eb9TC60b/x6OtmeCEk+044LRbtu+9NZUb7HHHogI0l++X0KXZ0coE38L
|
||||
1izwNvfrmLa+QaIgHMtAg9EnJwJ993n4L31GovWh8MGmVyJX/F92y+agNwWkNYYp
|
||||
iLWFyon+HbNVL13WOOYnYEdA8Me3+Gucy1EOfWMF7mgmuO2vcfnxXd6b16VjAwtE
|
||||
jGCQfzgpWGHLpgwoBgDmnPUbdNPUT3MbA9jqG2mlnBSBQveYgKrmFdDYnAjnCM4L
|
||||
uF2ztBzhAoGBAOYc3sF3YjpIIMsyH9omqtfOuxO+oZkpb2vB9kgdXCDcG870M+BC
|
||||
bNzV7DCSV8QAUqjKQK1r3gq62UZMLXZbG8x5UnM8/EK0X1CSqygwSWjGpYxIQEhh
|
||||
O2lq69WipkNDnX1ZmrvEdHD2cxqkkXZ7bdRKRasrFJgvJa3XbiJ18KYxAoGBAMpe
|
||||
/72EcX9oL3KT8tJSpvasrw17p/XkMMCxTp3IDb3krF/4k5bYF61F68/LNSy3xkos
|
||||
ZrPUK/U160iuHSYCpMq4pPmlWgKq4hmUMOt+8Yy622zDlugarq9VLqvSdGHm+r6F
|
||||
5fHilXB0UsTXXOuLZWLcSQ0MBgiaVCLb2AmXZhhXAoGAEjSchw/r7JKCTbE0hezj
|
||||
PVm0wVYmsNhvYUYiNwhjnpHrfU8iv45h0IL4QcuCOBaSc5o0zcOn+I9Z207xldiV
|
||||
dXLvzAA6MQjWNai08+QGGs0EkfmxZEiVC70S1X8dylqSHjW1oT9kuv80khoNDCOt
|
||||
x8rsgiNRaMzqHTvbEczk8jECgYB2Od+wSULBSw2FI5fVdcHjFGlEODycs44j1LH4
|
||||
DZqxmHl3q9IVavMSIGouQCo1kLuAM8ZgQpDXtYNaN5YB0cOSRyLiUc5vBoQGq4OU
|
||||
4Nme/L8aIH315TiuZ9ZXPSEO3REZ40G9+UCSrPJ52tOHLC2z/ruSqraPqhGDN+pT
|
||||
WCamCwKBgEPa+kVrPs0khQH8+sbFbU9ifj4fhPAiSwj2fKuXFro2mE205vAMHye/
|
||||
SYs/mPzYzKSd7F+7Zk6oVrgFVskTiReW3phF+cIl+CdcnIenF0jW1PVgGw8znu+P
|
||||
SbHSdqV+tB7AW2J7sH8TZtfMUPAK2MJ4S+1uaHK86K79ym4Rz0E2
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC/zCCAeegAwIBAgIJAN7rkfhaX8FZMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfYXV0aHpfc2VydmVyMIIBIjANBgkq
|
||||
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtegsP499au5ZxlwM26rk3TnRgakchQi/
|
||||
9bhfMr0LaEKng1lRXopzzGuGeZQswzbx7iiH89JzFkurZoEmZwtS4Aybit92VOSv
|
||||
0EUnyx7WR3V21ObZiQO0rr0UmG84NjdzATkqF+R5Z+HN9shwgBI4PR1j/ybCt7jN
|
||||
z+OM/VmqsgzoKLoabGrx7LCTPk8y5G8AoPOrIAP+9WHJsKQSRT8Lru4lYqseBxvh
|
||||
jqo8NRqzZLg79ldYaKFqa2N2zr5qp94sG3/zihNDxjZvyyn9c8qvPBL0xOyayvOJ
|
||||
G8eZUmjQpUMv7Jk3qFmdMgGaDJRw0Qg6+/Zt6MHNs6Rbb8hmwuMSpwIDAQABo0Aw
|
||||
PjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDATAP
|
||||
BgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCZHB9UCl2CfylWP3db
|
||||
xUamawnRoTYlsOcUh4f2tlHMY+vYiEStN+LECk62YpeaHl/nz/lk7g1Jx9aua39z
|
||||
wFIHiXYhwSWOtgmzpbxYLye1yajKXbbA1T7mEZJTjewDB9i1LcB9W3EV5VJ8Y1GY
|
||||
AYKuKQ4Cb1HrqLsrw/1PDm0VouWzf2ESv8CBvAv/pYLVfwgS6WsUqn9wycpLEnqQ
|
||||
RK66/AoiOaxUIjEP0O1q6pi6Mag7XAfeNtx8J0VGt4cRG4rvWCbKVUyvKfUCkipN
|
||||
gJu09S+KIz3x1CJLRuJX9tB+cFnnykDLQ2IKg7x44O83ikNk8+Di3iT/awCguWPE
|
||||
rHh5
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA5ij4WXWGvbmfAYhEafKRvLEHSkUCYIDjwQAlnHoLf/lz+Fh2
|
||||
DEv4lcBaycwk3+LVUGKgYOg91txYJvGD3HcmVThXZvcgJd4V9Ll3aY/6xVRCenWi
|
||||
UNgVQVQITGkMn09ZkSXbZCK4wqz9oTVh0Ti5a7apOS2V07yL0q7vw003v5TBqzC/
|
||||
FgRwE0bv1rKYYQ80WbDlYkkYGf216zQTwS4g/nShCZAX9eqSfbBg6B/A3OwpbIfx
|
||||
09BWuwWhp5QnS4w002gGWavRFNzu8pUHUv6zMN8OKpasv+Na+ZB+gMt4+e2Y7qNz
|
||||
76QL23eGwc6oWn8lQBtkDLmLIa6jbWX067U76QIDAQABAoIBAQCJpGzJSzC2W8DM
|
||||
sMqBNdCUMKZ0cwq13b7W2BimGJKyCOOi3HxUZEaYf/2Leyt+PPBm72SML7dzvDh3
|
||||
qa269gKVqmkSqa2vF763qQbRuYo14msTQzA7+s3TUMbZs2UaDOE6nZIzs1QdEElp
|
||||
1DvYXHz+/rD7Adj9VF+mMnouqQoy5kgJTnVZ8sOyl/9R6F67xKBIvcrtPfqVZzuG
|
||||
2hGAMUnawxFUajQC7BynIeCWrk79SUmQgilyNgRdY6+rGh2uRupIxuiAukPtuag1
|
||||
Li+wnNl1UGECtv9ZnnboKvg2334k5vhYScGRJbwbr7Zt3ZaNd0Z/DE9kTtnhBS7v
|
||||
9qWdc7CBAoGBAPR4hz1fhHFiPmMEAGuiNms6WdyIfyonIRYas8ZDKUQGdxn/aO8a
|
||||
CURktHRlm6iYT+j1cbf3RnLEN9pNr3V2EySOMc+rXUNifcP7Vl53akAQmISUfQWG
|
||||
UfwaNLicbavf6m9UCiwWByAZghqDZSLiwmLHIjGcSJQiFuhZryioDydxAoGBAPED
|
||||
q1Z7oNhzwRYie9OB5ylnrCH8G3yFl8egBmQrPJKIQHA9mAGg01LEJwQNoWewyAWx
|
||||
jfeFtWvIgZkj49cluZgHYyF81jApaNraxtXAgIwC1n7oAIttmeklZ/V1HntknG3Y
|
||||
ow2bV/NA3aPOTPYxW8oDv7U9lvwve7kIFxeWjE/5AoGASfXI3G1wUSkqvKPySJ3b
|
||||
ntcZZpm49xS9csWDS+D3tAfMsoXNxkB3O0TIP0qaLAhgbJcM314k5wWr7BSCl6Ow
|
||||
KOgH887hOUirycXZHF0+PMGIktulcy1u0jlPZ+aTW2MztpiTN0E2yKRO8xx7VXGK
|
||||
431hP+cLIh2qFoNDdaZaZ1ECgYEArw++PWQxMefqgVxs2vXJZY7TPiA0Ct+ynqKC
|
||||
4fFx3vGu9JgYuF4MAVtPB6eq7HlA4LnWZ8ssOuz6DbU/AoB5bY84FxPpNDRv4D/3
|
||||
Gz3nYUuSZ72234+tsuaju2vlxzUOVs97qB+E48Di/N+VkWHKzVKpxkjFScpnsL/K
|
||||
niyRIGkCgYEAriuxbOCczL/j6u2Xq1ngEsGg+RXjtOYGoJWo7B8qlVL4nF8w1Nbd
|
||||
FxEmOChQgUnBdwb93qHCSq0Fidf7OfewrfJJkstWIh3zPS4umLZo7R3YblncpdfT
|
||||
M197uckIWccZml2jF/c7nvK+MjwDRhkOl2a6HzMxcdBwYUJmSwmIZ4k=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC7jCCAdagAwIBAgIJAN7rkfhaX8FaMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfYXV0aHpfY2xpZW50MIIBIjANBgkq
|
||||
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5ij4WXWGvbmfAYhEafKRvLEHSkUCYIDj
|
||||
wQAlnHoLf/lz+Fh2DEv4lcBaycwk3+LVUGKgYOg91txYJvGD3HcmVThXZvcgJd4V
|
||||
9Ll3aY/6xVRCenWiUNgVQVQITGkMn09ZkSXbZCK4wqz9oTVh0Ti5a7apOS2V07yL
|
||||
0q7vw003v5TBqzC/FgRwE0bv1rKYYQ80WbDlYkkYGf216zQTwS4g/nShCZAX9eqS
|
||||
fbBg6B/A3OwpbIfx09BWuwWhp5QnS4w002gGWavRFNzu8pUHUv6zMN8OKpasv+Na
|
||||
+ZB+gMt4+e2Y7qNz76QL23eGwc6oWn8lQBtkDLmLIa6jbWX067U76QIDAQABoy8w
|
||||
LTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDAjAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEA2IZNhkVrSTAIeP2N2WzOHqbFbGyO+NA8G9Hb5fiX
|
||||
e1YS2Ku3ERYNr+HLxNHCsXiSUKjjBmXMc4z0XaHJznEKEbotZftjTlTQlHi3/5vm
|
||||
dIG18pmO/E5ebVXl6pU96v/hBd8N5rWp9WUKgP0y59r/JA+oNpmd10A+RyaOyrFK
|
||||
rBm8Z8rvDYMrXSpOwx9BNDuhqzbdG8MYw5vO55Er3hwTXoapsMqSh5s9+OFFpUJi
|
||||
2uEoQlwWiYRtQj6g4wgr4woDEbv8XxsHqGfs+GSnmRsB69xRI24lEtC+nS6Rz3Sh
|
||||
YWeN0gD8PsQC1KJVv6xCGo1yXSEwytRMB23XYtAZahLdLg==
|
||||
-----END CERTIFICATE-----`)
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package webhook implements the authenticator.Token interface using HTTP webhooks.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/plugin/pkg/webhook"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/authentication.k8s.io/install"
|
||||
)
|
||||
|
||||
var (
|
||||
groupVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||
)
|
||||
|
||||
// Ensure WebhookTokenAuthenticator implements the authenticator.Token interface.
|
||||
var _ authenticator.Token = (*WebhookTokenAuthenticator)(nil)
|
||||
|
||||
type WebhookTokenAuthenticator struct {
|
||||
*webhook.GenericWebhook
|
||||
}
|
||||
|
||||
// New creates a new WebhookTokenAuthenticator from the provided kubeconfig file.
|
||||
func New(kubeConfigFile string) (*WebhookTokenAuthenticator, error) {
|
||||
gw, err := webhook.NewGenericWebhook(kubeConfigFile, groupVersions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &WebhookTokenAuthenticator{gw}, nil
|
||||
}
|
||||
|
||||
// AuthenticateToken
|
||||
func (w *WebhookTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) {
|
||||
r := &v1beta1.TokenReview{
|
||||
Spec: v1beta1.TokenReviewSpec{
|
||||
Token: token,
|
||||
},
|
||||
}
|
||||
result := w.RestClient.Post().Body(r).Do()
|
||||
if err := result.Error(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err := result.Into(r); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !r.Status.Authenticated {
|
||||
return nil, false, nil
|
||||
}
|
||||
return &user.DefaultInfo{
|
||||
Name: r.Status.User.Username,
|
||||
UID: r.Status.User.UID,
|
||||
Groups: r.Status.User.Groups,
|
||||
}, true, nil
|
||||
}
|
|
@ -0,0 +1,352 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apis/authentication.k8s.io/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
// Service mocks a remote authentication service.
|
||||
type Service interface {
|
||||
// Review looks at the TokenReviewSpec and provides an authentication
|
||||
// response in the TokenReviewStatus.
|
||||
Review(*v1beta1.TokenReview)
|
||||
}
|
||||
|
||||
// NewTestServer wraps a Service as an httptest.Server.
|
||||
func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error) {
|
||||
var tlsConfig *tls.Config
|
||||
if cert != nil {
|
||||
cert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
if caCert != nil {
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caCert)
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.ClientCAs = rootCAs
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
serveHTTP := func(w http.ResponseWriter, r *http.Request) {
|
||||
var review v1beta1.TokenReview
|
||||
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.Review(&review)
|
||||
type userInfo struct {
|
||||
Username string `json:"username"`
|
||||
UID string `json:"uid"`
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
type status struct {
|
||||
Authenticated bool `json:"authenticated"`
|
||||
User userInfo `json:"user"`
|
||||
}
|
||||
resp := struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Status status `json:"status"`
|
||||
}{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Status: status{
|
||||
review.Status.Authenticated,
|
||||
userInfo{
|
||||
Username: review.Status.User.Username,
|
||||
UID: review.Status.User.UID,
|
||||
Groups: review.Status.User.Groups,
|
||||
},
|
||||
},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
|
||||
server.TLS = tlsConfig
|
||||
server.StartTLS()
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// A service that can be set to say yes or no to authentication requests.
|
||||
type mockService struct {
|
||||
allow bool
|
||||
}
|
||||
|
||||
func (m *mockService) Review(r *v1beta1.TokenReview) {
|
||||
r.Status.Authenticated = m.allow
|
||||
if m.allow {
|
||||
r.Status.User.Username = "realHooman@email.com"
|
||||
}
|
||||
}
|
||||
func (m *mockService) Allow() { m.allow = true }
|
||||
func (m *mockService) Deny() { m.allow = false }
|
||||
|
||||
// newTokenAuthenticator creates a temporary kubeconfig file from the provided
|
||||
// arguments and attempts to load a new WebhookTokenAuthenticator from it.
|
||||
func newTokenAuthenticator(serverURL string, clientCert, clientKey, ca []byte) (*WebhookTokenAuthenticator, error) {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
config := v1.Config{
|
||||
Clusters: []v1.NamedCluster{
|
||||
{
|
||||
Cluster: v1.Cluster{Server: serverURL, CertificateAuthorityData: ca},
|
||||
},
|
||||
},
|
||||
AuthInfos: []v1.NamedAuthInfo{
|
||||
{
|
||||
AuthInfo: v1.AuthInfo{ClientCertificateData: clientCert, ClientKeyData: clientKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(p)
|
||||
}
|
||||
|
||||
func TestTLSConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
clientCert, clientKey, clientCA []byte
|
||||
serverCert, serverKey, serverCA []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "TLS setup between client and server",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth",
|
||||
clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth, client provides it",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
},
|
||||
{
|
||||
test: "Client does not trust server",
|
||||
clientCert: clientCert, clientKey: clientKey,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not trust client",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
// Plugin does not support insecure configurations.
|
||||
test: "Server is using insecure connection",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
|
||||
server, err := NewTestServer(service, tt.serverCert, tt.serverKey, tt.serverCA)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newTokenAuthenticator(server.URL, tt.clientCert, tt.clientKey, tt.clientCA)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Allow all and see if we get an error.
|
||||
service.Allow()
|
||||
_, authenticated, err := wh.AuthenticateToken("t0k3n")
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making authorization request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !authenticated {
|
||||
t.Errorf("%s: failed to authenticate token", tt.test)
|
||||
return
|
||||
}
|
||||
|
||||
service.Deny()
|
||||
_, authenticated, err = wh.AuthenticateToken("t0k3n")
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpectedly failed AuthenticateToken", tt.test)
|
||||
}
|
||||
if authenticated {
|
||||
t.Errorf("%s: incorrectly authenticated token", tt.test)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// recorderService records all token review requests, and responds with the
|
||||
// provided TokenReviewStatus.
|
||||
type recorderService struct {
|
||||
lastRequest v1beta1.TokenReview
|
||||
response v1beta1.TokenReviewStatus
|
||||
}
|
||||
|
||||
func (rec *recorderService) Review(r *v1beta1.TokenReview) {
|
||||
rec.lastRequest = *r
|
||||
r.Status = rec.response
|
||||
}
|
||||
|
||||
func TestWebhookTokenAuthenticator(t *testing.T) {
|
||||
serv := &recorderService{}
|
||||
|
||||
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
wh, err := newTokenAuthenticator(s.URL, clientCert, clientKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expTypeMeta := unversioned.TypeMeta{
|
||||
APIVersion: "authentication.k8s.io/v1beta1",
|
||||
Kind: "TokenReview",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
serverResponse v1beta1.TokenReviewStatus
|
||||
expectedAuthenticated bool
|
||||
expectedUser *user.DefaultInfo
|
||||
}{
|
||||
// Successful response should pass through all user info.
|
||||
{
|
||||
serverResponse: v1beta1.TokenReviewStatus{
|
||||
Authenticated: true,
|
||||
User: v1beta1.UserInfo{
|
||||
Username: "somebody",
|
||||
},
|
||||
},
|
||||
expectedAuthenticated: true,
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "somebody",
|
||||
},
|
||||
},
|
||||
{
|
||||
serverResponse: v1beta1.TokenReviewStatus{
|
||||
Authenticated: true,
|
||||
User: v1beta1.UserInfo{
|
||||
Username: "person@place.com",
|
||||
UID: "abcd-1234",
|
||||
Groups: []string{"stuff-dev", "main-eng"},
|
||||
},
|
||||
},
|
||||
expectedAuthenticated: true,
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "person@place.com",
|
||||
UID: "abcd-1234",
|
||||
Groups: []string{"stuff-dev", "main-eng"},
|
||||
},
|
||||
},
|
||||
// Unauthenticated shouldn't even include extra provided info.
|
||||
{
|
||||
serverResponse: v1beta1.TokenReviewStatus{
|
||||
Authenticated: false,
|
||||
User: v1beta1.UserInfo{
|
||||
Username: "garbage",
|
||||
UID: "abcd-1234",
|
||||
Groups: []string{"not-actually-used"},
|
||||
},
|
||||
},
|
||||
expectedAuthenticated: false,
|
||||
expectedUser: nil,
|
||||
},
|
||||
{
|
||||
serverResponse: v1beta1.TokenReviewStatus{
|
||||
Authenticated: false,
|
||||
},
|
||||
expectedAuthenticated: false,
|
||||
expectedUser: nil,
|
||||
},
|
||||
}
|
||||
token := "my-s3cr3t-t0ken"
|
||||
for i, tt := range tests {
|
||||
serv.response = tt.serverResponse
|
||||
user, authenticated, err := wh.AuthenticateToken(token)
|
||||
if err != nil {
|
||||
t.Errorf("case %d: authentication failed: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if serv.lastRequest.Spec.Token != token {
|
||||
t.Errorf("case %d: Server did not see correct token. Got %q, expected %q.",
|
||||
i, serv.lastRequest.Spec.Token, token)
|
||||
}
|
||||
if !reflect.DeepEqual(serv.lastRequest.TypeMeta, expTypeMeta) {
|
||||
t.Errorf("case %d: Server did not see correct TypeMeta. Got %v, expected %v",
|
||||
i, serv.lastRequest.TypeMeta, expTypeMeta)
|
||||
}
|
||||
if authenticated != tt.expectedAuthenticated {
|
||||
t.Errorf("case %d: Plugin returned incorrect authentication response. Got %t, expected %t.",
|
||||
i, authenticated, tt.expectedAuthenticated)
|
||||
}
|
||||
if user != nil && tt.expectedUser != nil && !reflect.DeepEqual(user, tt.expectedUser) {
|
||||
t.Errorf("case %d: Plugin returned incorrect user. Got %v, expected %v",
|
||||
i, user, tt.expectedUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type authenticationUserInfo v1beta1.UserInfo
|
||||
|
||||
func (a *authenticationUserInfo) GetName() string { return a.Username }
|
||||
func (a *authenticationUserInfo) GetUID() string { return a.UID }
|
||||
func (a *authenticationUserInfo) GetGroups() []string { return a.Groups }
|
||||
func (a *authenticationUserInfo) GetExtra() map[string][]string { return a.Extra }
|
||||
|
||||
// Ensure v1beta1.UserInfo contains the fields necessary to implement the
|
||||
// user.Info interface.
|
||||
var _ user.Info = (*authenticationUserInfo)(nil)
|
|
@ -19,35 +19,24 @@ package webhook
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
"k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
runtimeserializer "k8s.io/kubernetes/pkg/runtime/serializer"
|
||||
"k8s.io/kubernetes/pkg/runtime/serializer/json"
|
||||
"k8s.io/kubernetes/pkg/runtime/serializer/versioning"
|
||||
"k8s.io/kubernetes/plugin/pkg/webhook"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
|
||||
)
|
||||
|
||||
var (
|
||||
encodeVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||
decodeVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||
|
||||
requireEnabled = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||
groupVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||
)
|
||||
|
||||
// Ensure Webhook implements the authorizer.Authorizer interface.
|
||||
var _ authorizer.Authorizer = (*WebhookAuthorizer)(nil)
|
||||
|
||||
type WebhookAuthorizer struct {
|
||||
restClient *restclient.RESTClient
|
||||
*webhook.GenericWebhook
|
||||
}
|
||||
|
||||
// New creates a new WebhookAuthorizer from the provided kubeconfig file.
|
||||
|
@ -71,37 +60,11 @@ type WebhookAuthorizer struct {
|
|||
// For additional HTTP configuration, refer to the kubeconfig documentation
|
||||
// http://kubernetes.io/v1.1/docs/user-guide/kubeconfig-file.html.
|
||||
func New(kubeConfigFile string) (*WebhookAuthorizer, error) {
|
||||
|
||||
for _, groupVersion := range requireEnabled {
|
||||
if !registered.IsEnabledVersion(groupVersion) {
|
||||
return nil, fmt.Errorf("webhook authz plugin requires enabling extension resource: %s", groupVersion)
|
||||
}
|
||||
}
|
||||
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeConfigFile
|
||||
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
|
||||
|
||||
clientConfig, err := loader.ClientConfig()
|
||||
gw, err := webhook.NewGenericWebhook(kubeConfigFile, groupVersions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializer := json.NewSerializer(json.DefaultMetaFactory, api.Scheme, runtime.ObjectTyperToTyper(api.Scheme), false)
|
||||
codec := versioning.NewCodecForScheme(api.Scheme, serializer, serializer, encodeVersions, decodeVersions)
|
||||
clientConfig.ContentConfig.NegotiatedSerializer = runtimeserializer.NegotiatedSerializerWrapper(
|
||||
runtime.SerializerInfo{Serializer: codec},
|
||||
runtime.StreamSerializerInfo{},
|
||||
)
|
||||
|
||||
restClient, err := restclient.UnversionedRESTClientFor(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(ericchiang): Can we ensure remote service is reachable?
|
||||
|
||||
return &WebhookAuthorizer{restClient}, nil
|
||||
return &WebhookAuthorizer{gw}, nil
|
||||
}
|
||||
|
||||
// Authorize makes a REST request to the remote service describing the attempted action as a JSON
|
||||
|
@ -171,7 +134,7 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (err error) {
|
|||
Verb: attr.GetVerb(),
|
||||
}
|
||||
}
|
||||
result := w.restClient.Post().Body(r).Do()
|
||||
result := w.RestClient.Post().Body(r).Do()
|
||||
if err := result.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package webhook implements a generic HTTP webhook plugin.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
runtimeserializer "k8s.io/kubernetes/pkg/runtime/serializer"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
|
||||
)
|
||||
|
||||
type GenericWebhook struct {
|
||||
RestClient *restclient.RESTClient
|
||||
}
|
||||
|
||||
// New creates a new GenericWebhook from the provided kubeconfig file.
|
||||
func NewGenericWebhook(kubeConfigFile string, groupVersions []unversioned.GroupVersion) (*GenericWebhook, error) {
|
||||
for _, groupVersion := range groupVersions {
|
||||
if !registered.IsEnabledVersion(groupVersion) {
|
||||
return nil, fmt.Errorf("webhook plugin requires enabling extension resource: %s", groupVersion)
|
||||
}
|
||||
}
|
||||
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeConfigFile
|
||||
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
|
||||
|
||||
clientConfig, err := loader.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codec := api.Codecs.LegacyCodec(groupVersions...)
|
||||
clientConfig.ContentConfig.NegotiatedSerializer = runtimeserializer.NegotiatedSerializerWrapper(
|
||||
runtime.SerializerInfo{Serializer: codec},
|
||||
runtime.StreamSerializerInfo{},
|
||||
)
|
||||
|
||||
restClient, err := restclient.UnversionedRESTClientFor(clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(ericchiang): Can we ensure remote service is reachable?
|
||||
|
||||
return &GenericWebhook{restClient}, nil
|
||||
}
|
|
@ -38,6 +38,7 @@ import (
|
|||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
authenticationv1beta1 "k8s.io/kubernetes/pkg/apis/authentication.k8s.io/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
|
@ -46,10 +47,12 @@ import (
|
|||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer/abac"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1"
|
||||
"k8s.io/kubernetes/pkg/master"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/admit"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/tokentest"
|
||||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
|
@ -66,6 +69,29 @@ func getTestTokenAuth() authenticator.Request {
|
|||
return bearertoken.New(tokenAuthenticator)
|
||||
}
|
||||
|
||||
func getTestWebhookTokenAuth(serverURL string) (authenticator.Request, error) {
|
||||
kubecfgFile, err := ioutil.TempFile("", "webhook-kubecfg")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.Remove(kubecfgFile.Name())
|
||||
config := v1.Config{
|
||||
Clusters: []v1.NamedCluster{
|
||||
{
|
||||
Cluster: v1.Cluster{Server: serverURL},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := json.NewEncoder(kubecfgFile).Encode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webhookTokenAuth, err := webhook.New(kubecfgFile.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bearertoken.New(webhookTokenAuth), nil
|
||||
}
|
||||
|
||||
func path(resource, namespace, name string) string {
|
||||
return testapi.Default.ResourcePath(resource, namespace, name)
|
||||
}
|
||||
|
@ -1221,3 +1247,128 @@ func TestReadOnlyAuthorization(t *testing.T) {
|
|||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebhookTokenAuthenticator tests that a master can use the webhook token
|
||||
// authenticator to call out to a remote web server for authentication
|
||||
// decisions.
|
||||
func TestWebhookTokenAuthenticator(t *testing.T) {
|
||||
framework.DeleteAllEtcdKeys()
|
||||
|
||||
var m *master.Master
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
m.Handler.ServeHTTP(w, req)
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
authServer := newTestWebhookTokenAuthServer()
|
||||
defer authServer.Close()
|
||||
authenticator, err := getTestWebhookTokenAuth(authServer.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting webhook token authenticator server: %v", err)
|
||||
}
|
||||
|
||||
masterConfig := framework.NewIntegrationTestMasterConfig()
|
||||
masterConfig.Authenticator = authenticator
|
||||
masterConfig.Authorizer = allowAliceAuthorizer{}
|
||||
m, err = master.New(masterConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("error in bringing up the master: %v", err)
|
||||
}
|
||||
|
||||
transport := http.DefaultTransport
|
||||
|
||||
for _, r := range getTestRequests() {
|
||||
// Expect Bob's requests to all fail.
|
||||
token := BobToken
|
||||
bodyBytes := bytes.NewReader([]byte(r.body))
|
||||
req, err := http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
func() {
|
||||
resp, err := transport.RoundTrip(req)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Logf("case %v", r)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// Expect all of Bob's actions to return Forbidden
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Logf("case %v", r)
|
||||
t.Errorf("Expected http.Forbidden, but got %s", resp.Status)
|
||||
}
|
||||
}()
|
||||
// Expect Alice's requests to succeed.
|
||||
token = AliceToken
|
||||
bodyBytes = bytes.NewReader([]byte(r.body))
|
||||
req, err = http.NewRequest(r.verb, s.URL+r.URL, bodyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
func() {
|
||||
resp, err := transport.RoundTrip(req)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Logf("case %v", r)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// Expect all of Alice's actions to at least get past authn/authz.
|
||||
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
|
||||
t.Logf("case %v", r)
|
||||
t.Errorf("Expected something other than Unauthorized/Forbidden, but got %s", resp.Status)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// newTestWebhookTokenAuthServer creates an http token authentication server
|
||||
// that knows about both Alice and Bob.
|
||||
func newTestWebhookTokenAuthServer() *httptest.Server {
|
||||
serveHTTP := func(w http.ResponseWriter, r *http.Request) {
|
||||
var review authenticationv1beta1.TokenReview
|
||||
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
type userInfo struct {
|
||||
Username string `json:"username"`
|
||||
UID string `json:"uid"`
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
type status struct {
|
||||
Authenticated bool `json:"authenticated"`
|
||||
User userInfo `json:"user"`
|
||||
}
|
||||
var username, uid string
|
||||
authenticated := false
|
||||
if review.Spec.Token == AliceToken {
|
||||
authenticated, username, uid = true, "alice", "1"
|
||||
} else if review.Spec.Token == BobToken {
|
||||
authenticated, username, uid = true, "bob", "2"
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Status status `json:"status"`
|
||||
}{
|
||||
APIVersion: authenticationv1beta1.SchemeGroupVersion.String(),
|
||||
Status: status{
|
||||
authenticated,
|
||||
userInfo{
|
||||
Username: username,
|
||||
UID: uid,
|
||||
},
|
||||
},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
|
||||
server.Start()
|
||||
return server
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue