mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	JWT token generation/verification
This commit is contained in:
		@@ -24,6 +24,8 @@
 | 
				
			|||||||
{% if grains.cloud is defined -%}
 | 
					{% if grains.cloud is defined -%}
 | 
				
			||||||
{% set cloud_provider = "--cloud_provider=" + grains.cloud -%}
 | 
					{% set cloud_provider = "--cloud_provider=" + grains.cloud -%}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% set service_account_key = " --service_account_private_key_file=/srv/kubernetes/server.key " -%}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% if grains.cloud == 'gce' -%}
 | 
					{% if grains.cloud == 'gce' -%}
 | 
				
			||||||
  {% if grains.cloud_config is defined -%}
 | 
					  {% if grains.cloud_config is defined -%}
 | 
				
			||||||
    {% set cloud_config = "--cloud_config=" + grains.cloud_config -%}
 | 
					    {% set cloud_config = "--cloud_config=" + grains.cloud_config -%}
 | 
				
			||||||
@@ -55,7 +57,7 @@
 | 
				
			|||||||
{% endif -%}
 | 
					{% endif -%}
 | 
				
			||||||
{% endif -%}
 | 
					{% endif -%}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% set params = "--master=127.0.0.1:8080" + " " + machines + " " + cluster_name + " " + cluster_cidr + " " + allocate_node_cidrs + " " + minion_regexp + " " + cloud_provider  + " " + sync_nodes + " " + cloud_config + " " + pillar['log_level'] -%}
 | 
					{% set params = "--master=127.0.0.1:8080" + " " + machines + " " + cluster_name + " " + cluster_cidr + " " + allocate_node_cidrs + " " + minion_regexp + " " + cloud_provider  + " " + sync_nodes + " " + cloud_config + service_account_key + pillar['log_level'] -%}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
"apiVersion": "v1beta3",
 | 
					"apiVersion": "v1beta3",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,6 +67,8 @@ type APIServer struct {
 | 
				
			|||||||
	BasicAuthFile              string
 | 
						BasicAuthFile              string
 | 
				
			||||||
	ClientCAFile               string
 | 
						ClientCAFile               string
 | 
				
			||||||
	TokenAuthFile              string
 | 
						TokenAuthFile              string
 | 
				
			||||||
 | 
						ServiceAccountKeyFile      string
 | 
				
			||||||
 | 
						ServiceAccountLookup       bool
 | 
				
			||||||
	AuthorizationMode          string
 | 
						AuthorizationMode          string
 | 
				
			||||||
	AuthorizationPolicyFile    string
 | 
						AuthorizationPolicyFile    string
 | 
				
			||||||
	AdmissionControl           string
 | 
						AdmissionControl           string
 | 
				
			||||||
@@ -162,6 +164,8 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
 | 
				
			|||||||
	fs.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, "If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.")
 | 
						fs.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, "If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.")
 | 
				
			||||||
	fs.StringVar(&s.ClientCAFile, "client-ca-file", s.ClientCAFile, "If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.")
 | 
						fs.StringVar(&s.ClientCAFile, "client-ca-file", s.ClientCAFile, "If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.")
 | 
				
			||||||
	fs.StringVar(&s.TokenAuthFile, "token-auth-file", s.TokenAuthFile, "If set, the file that will be used to secure the secure port of the API server via token authentication.")
 | 
						fs.StringVar(&s.TokenAuthFile, "token-auth-file", s.TokenAuthFile, "If set, the file that will be used to secure the secure port of the API server via token authentication.")
 | 
				
			||||||
 | 
						fs.StringVar(&s.ServiceAccountKeyFile, "service-account-key-file", s.ServiceAccountKeyFile, "File containing PEM-encoded x509 RSA private or public key, used to verify ServiceAccount tokens. If unspecified, --tls-private-key-file is used.")
 | 
				
			||||||
 | 
						fs.BoolVar(&s.ServiceAccountLookup, "service-account-lookup", s.ServiceAccountLookup, "If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
 | 
				
			||||||
	fs.StringVar(&s.AuthorizationMode, "authorization-mode", s.AuthorizationMode, "Selects how to do authorization on the secure port.  One of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
 | 
						fs.StringVar(&s.AuthorizationMode, "authorization-mode", s.AuthorizationMode, "Selects how to do authorization on the secure port.  One of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
 | 
				
			||||||
	fs.StringVar(&s.AuthorizationPolicyFile, "authorization-policy-file", s.AuthorizationPolicyFile, "File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.")
 | 
						fs.StringVar(&s.AuthorizationPolicyFile, "authorization-policy-file", s.AuthorizationPolicyFile, "File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.")
 | 
				
			||||||
	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.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(), ", "))
 | 
				
			||||||
@@ -267,7 +271,11 @@ func (s *APIServer) Run(_ []string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	n := net.IPNet(s.PortalNet)
 | 
						n := net.IPNet(s.PortalNet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	authenticator, err := apiserver.NewAuthenticator(s.BasicAuthFile, s.ClientCAFile, s.TokenAuthFile)
 | 
						// Default to the private server key for service account token signing
 | 
				
			||||||
 | 
						if s.ServiceAccountKeyFile == "" && s.TLSPrivateKeyFile != "" {
 | 
				
			||||||
 | 
							s.ServiceAccountKeyFile = s.TLSPrivateKeyFile
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						authenticator, err := apiserver.NewAuthenticator(s.BasicAuthFile, s.ClientCAFile, s.TokenAuthFile, s.ServiceAccountKeyFile, s.ServiceAccountLookup, client)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		glog.Fatalf("Invalid Authentication Config: %v", err)
 | 
							glog.Fatalf("Invalid Authentication Config: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@ limitations under the License.
 | 
				
			|||||||
package app
 | 
					package app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/http/pprof"
 | 
						"net/http/pprof"
 | 
				
			||||||
@@ -75,6 +74,7 @@ type CMServer struct {
 | 
				
			|||||||
	PodEvictionTimeout      time.Duration
 | 
						PodEvictionTimeout      time.Duration
 | 
				
			||||||
	DeletingPodsQps         float32
 | 
						DeletingPodsQps         float32
 | 
				
			||||||
	DeletingPodsBurst       int
 | 
						DeletingPodsBurst       int
 | 
				
			||||||
 | 
						ServiceAccountKeyFile   string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: Discover these by pinging the host machines, and rip out these params.
 | 
						// TODO: Discover these by pinging the host machines, and rip out these params.
 | 
				
			||||||
	NodeMilliCPU int64
 | 
						NodeMilliCPU int64
 | 
				
			||||||
@@ -143,6 +143,7 @@ func (s *CMServer) AddFlags(fs *pflag.FlagSet) {
 | 
				
			|||||||
		"Amount of time which we allow starting Node to be unresponsive before marking it unhealty.")
 | 
							"Amount of time which we allow starting Node to be unresponsive before marking it unhealty.")
 | 
				
			||||||
	fs.DurationVar(&s.NodeMonitorPeriod, "node-monitor-period", 5*time.Second,
 | 
						fs.DurationVar(&s.NodeMonitorPeriod, "node-monitor-period", 5*time.Second,
 | 
				
			||||||
		"The period for syncing NodeStatus in NodeController.")
 | 
							"The period for syncing NodeStatus in NodeController.")
 | 
				
			||||||
 | 
						fs.StringVar(&s.ServiceAccountKeyFile, "service-account-private-key-file", s.ServiceAccountKeyFile, "Filename containing a PEM-encoded private RSA key used to sign service account tokens.")
 | 
				
			||||||
	// TODO: Discover these by pinging the host machines, and rip out these flags.
 | 
						// TODO: Discover these by pinging the host machines, and rip out these flags.
 | 
				
			||||||
	// TODO: in the meantime, use resource.QuantityFlag() instead of these
 | 
						// TODO: in the meantime, use resource.QuantityFlag() instead of these
 | 
				
			||||||
	fs.Int64Var(&s.NodeMilliCPU, "node-milli-cpu", s.NodeMilliCPU, "The amount of MilliCPU provisioned on each node")
 | 
						fs.Int64Var(&s.NodeMilliCPU, "node-milli-cpu", s.NodeMilliCPU, "The amount of MilliCPU provisioned on each node")
 | 
				
			||||||
@@ -251,12 +252,24 @@ func (s *CMServer) Run(_ []string) error {
 | 
				
			|||||||
		pvclaimBinder.Run()
 | 
							pvclaimBinder.Run()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: generate signed token
 | 
						if len(s.ServiceAccountKeyFile) > 0 {
 | 
				
			||||||
	tokenGenerator := serviceaccount.TokenGeneratorFunc(func(serviceAccount api.ServiceAccount, secret api.Secret) (string, error) {
 | 
							privateKey, err := serviceaccount.ReadPrivateKey(s.ServiceAccountKeyFile)
 | 
				
			||||||
		return fmt.Sprintf("serviceaccount:%s:%s:%s:%s", serviceAccount.Namespace, serviceAccount.Name, serviceAccount.UID, secret.Name), nil
 | 
							if err != nil {
 | 
				
			||||||
	})
 | 
								glog.Errorf("Error reading key for service account token controller: %v", err)
 | 
				
			||||||
	serviceaccount.NewTokensController(kubeClient, serviceaccount.DefaultTokenControllerOptions(tokenGenerator)).Run()
 | 
							} else {
 | 
				
			||||||
	serviceaccount.NewServiceAccountsController(kubeClient, serviceaccount.DefaultServiceAccountControllerOptions()).Run()
 | 
								serviceaccount.NewTokensController(
 | 
				
			||||||
 | 
									kubeClient,
 | 
				
			||||||
 | 
									serviceaccount.DefaultTokenControllerOptions(
 | 
				
			||||||
 | 
										serviceaccount.JWTTokenGenerator(privateKey),
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
								).Run()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						serviceaccount.NewServiceAccountsController(
 | 
				
			||||||
 | 
							kubeClient,
 | 
				
			||||||
 | 
							serviceaccount.DefaultServiceAccountControllerOptions(),
 | 
				
			||||||
 | 
						).Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	select {}
 | 
						select {}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,12 +132,21 @@ trap cleanup EXIT
 | 
				
			|||||||
echo "Starting etcd"
 | 
					echo "Starting etcd"
 | 
				
			||||||
kube::etcd::start
 | 
					kube::etcd::start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SERVICE_ACCOUNT_LOOKUP=${SERVICE_ACCOUNT_LOOKUP:-false}
 | 
				
			||||||
 | 
					SERVICE_ACCOUNT_KEY=${SERVICE_ACCOUNT_KEY:-"/var/run/kubernetes/serviceaccount.key"}
 | 
				
			||||||
 | 
					# Generate ServiceAccount key if needed
 | 
				
			||||||
 | 
					if [[ ! -f "${SERVICE_ACCOUNT_KEY}" ]]; then
 | 
				
			||||||
 | 
					  openssl genrsa -out "${SERVICE_ACCOUNT_KEY}" 2048 2>/dev/null
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Admission Controllers to invoke prior to persisting objects in cluster
 | 
					# Admission Controllers to invoke prior to persisting objects in cluster
 | 
				
			||||||
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota
 | 
					ADMISSION_CONTROL=NamespaceLifecycle,NamespaceAutoProvision,LimitRanger,SecurityContextDeny,ResourceQuota
 | 
				
			||||||
 | 
					
 | 
				
			||||||
APISERVER_LOG=/tmp/kube-apiserver.log
 | 
					APISERVER_LOG=/tmp/kube-apiserver.log
 | 
				
			||||||
sudo -E "${GO_OUT}/kube-apiserver" \
 | 
					sudo -E "${GO_OUT}/kube-apiserver" \
 | 
				
			||||||
  --v=${LOG_LEVEL} \
 | 
					  --v=${LOG_LEVEL} \
 | 
				
			||||||
 | 
					  --service_account_key_file="${SERVICE_ACCOUNT_KEY}" \
 | 
				
			||||||
 | 
					  --service_account_lookup="${SERVICE_ACCOUNT_LOOKUP}" \
 | 
				
			||||||
  --admission_control="${ADMISSION_CONTROL}" \
 | 
					  --admission_control="${ADMISSION_CONTROL}" \
 | 
				
			||||||
  --address="${API_HOST}" \
 | 
					  --address="${API_HOST}" \
 | 
				
			||||||
  --port="${API_PORT}" \
 | 
					  --port="${API_PORT}" \
 | 
				
			||||||
@@ -155,6 +164,7 @@ CTLRMGR_LOG=/tmp/kube-controller-manager.log
 | 
				
			|||||||
sudo -E "${GO_OUT}/kube-controller-manager" \
 | 
					sudo -E "${GO_OUT}/kube-controller-manager" \
 | 
				
			||||||
  --v=${LOG_LEVEL} \
 | 
					  --v=${LOG_LEVEL} \
 | 
				
			||||||
  --machines="127.0.0.1" \
 | 
					  --machines="127.0.0.1" \
 | 
				
			||||||
 | 
					  --service_account_private_key_file="${SERVICE_ACCOUNT_KEY}" \
 | 
				
			||||||
  --master="${API_HOST}:${API_PORT}" >"${CTLRMGR_LOG}" 2>&1 &
 | 
					  --master="${API_HOST}:${API_PORT}" >"${CTLRMGR_LOG}" 2>&1 &
 | 
				
			||||||
CTLRMGR_PID=$!
 | 
					CTLRMGR_PID=$!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,8 +17,12 @@ limitations under the License.
 | 
				
			|||||||
package apiserver
 | 
					package apiserver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/rsa"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
 | 
				
			||||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/serviceaccount"
 | 
				
			||||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
				
			||||||
	"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile"
 | 
						"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile"
 | 
				
			||||||
	"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/basicauth"
 | 
						"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/basicauth"
 | 
				
			||||||
@@ -28,7 +32,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewAuthenticator returns an authenticator.Request or an error
 | 
					// NewAuthenticator returns an authenticator.Request or an error
 | 
				
			||||||
func NewAuthenticator(basicAuthFile, clientCAFile, tokenFile string) (authenticator.Request, error) {
 | 
					func NewAuthenticator(basicAuthFile, clientCAFile, tokenFile, serviceAccountKeyFile string, serviceAccountLookup bool, client client.Interface) (authenticator.Request, error) {
 | 
				
			||||||
	var authenticators []authenticator.Request
 | 
						var authenticators []authenticator.Request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(basicAuthFile) > 0 {
 | 
						if len(basicAuthFile) > 0 {
 | 
				
			||||||
@@ -55,6 +59,14 @@ func NewAuthenticator(basicAuthFile, clientCAFile, tokenFile string) (authentica
 | 
				
			|||||||
		authenticators = append(authenticators, tokenAuth)
 | 
							authenticators = append(authenticators, tokenAuth)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(serviceAccountKeyFile) > 0 {
 | 
				
			||||||
 | 
							serviceAccountAuth, err := newServiceAccountAuthenticator(serviceAccountKeyFile, serviceAccountLookup, client)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							authenticators = append(authenticators, serviceAccountAuth)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch len(authenticators) {
 | 
						switch len(authenticators) {
 | 
				
			||||||
	case 0:
 | 
						case 0:
 | 
				
			||||||
		return nil, nil
 | 
							return nil, nil
 | 
				
			||||||
@@ -85,6 +97,16 @@ func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request,
 | 
				
			|||||||
	return bearertoken.New(tokenAuthenticator), nil
 | 
						return bearertoken.New(tokenAuthenticator), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// newServiceAccountAuthenticator returns an authenticator.Request or an error
 | 
				
			||||||
 | 
					func newServiceAccountAuthenticator(keyfile string, lookup bool, client client.Interface) (authenticator.Request, error) {
 | 
				
			||||||
 | 
						publicKey, err := serviceaccount.ReadPublicKey(keyfile)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{publicKey}, lookup, client)
 | 
				
			||||||
 | 
						return bearertoken.New(tokenAuthenticator), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
 | 
					// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
 | 
				
			||||||
func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
 | 
					func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
 | 
				
			||||||
	roots, err := util.CertPoolFromFile(clientCAFile)
 | 
						roots, err := util.CertPoolFromFile(clientCAFile)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										228
									
								
								pkg/serviceaccount/jwt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								pkg/serviceaccount/jwt.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 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 serviceaccount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"crypto/rsa"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						jwt "github.com/dgrijalva/jwt-go"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ServiceAccountUsernamePrefix    = "serviceaccount"
 | 
				
			||||||
 | 
						ServiceAccountUsernameSeparator = ":"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Issuer = "kubernetes/serviceaccount"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SubjectClaim            = "sub"
 | 
				
			||||||
 | 
						IssuerClaim             = "iss"
 | 
				
			||||||
 | 
						ServiceAccountNameClaim = "kubernetes.io/serviceaccount/service-account.name"
 | 
				
			||||||
 | 
						ServiceAccountUIDClaim  = "kubernetes.io/serviceaccount/service-account.uid"
 | 
				
			||||||
 | 
						SecretNameClaim         = "kubernetes.io/serviceaccount/secret.name"
 | 
				
			||||||
 | 
						NamespaceClaim          = "kubernetes.io/serviceaccount/namespace"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TokenGenerator interface {
 | 
				
			||||||
 | 
						// GenerateToken generates a token which will identify the given ServiceAccount.
 | 
				
			||||||
 | 
						// The returned token will be stored in the given (and yet-unpersisted) Secret.
 | 
				
			||||||
 | 
						GenerateToken(serviceAccount api.ServiceAccount, secret api.Secret) (string, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadPrivateKey is a helper function for reading an rsa.PrivateKey from a PEM-encoded file
 | 
				
			||||||
 | 
					func ReadPrivateKey(file string) (*rsa.PrivateKey, error) {
 | 
				
			||||||
 | 
						data, err := ioutil.ReadFile(file)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return jwt.ParseRSAPrivateKeyFromPEM(data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadPublicKey is a helper function for reading an rsa.PublicKey from a PEM-encoded file
 | 
				
			||||||
 | 
					// Reads public keys from both public and private key files
 | 
				
			||||||
 | 
					func ReadPublicKey(file string) (*rsa.PublicKey, error) {
 | 
				
			||||||
 | 
						data, err := ioutil.ReadFile(file)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(data); err == nil {
 | 
				
			||||||
 | 
							return &privateKey.PublicKey, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return jwt.ParseRSAPublicKeyFromPEM(data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MakeUsername generates a username from the given namespace and ServiceAccount name.
 | 
				
			||||||
 | 
					// The resulting username can be passed to SplitUsername to extract the original namespace and ServiceAccount name.
 | 
				
			||||||
 | 
					func MakeUsername(namespace, name string) string {
 | 
				
			||||||
 | 
						return strings.Join([]string{ServiceAccountUsernamePrefix, namespace, name}, ServiceAccountUsernameSeparator)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SplitUsername returns the namespace and ServiceAccount name embedded in the given username,
 | 
				
			||||||
 | 
					// or an error if the username is not a valid name produced by MakeUsername
 | 
				
			||||||
 | 
					func SplitUsername(username string) (string, string, error) {
 | 
				
			||||||
 | 
						parts := strings.Split(username, ServiceAccountUsernameSeparator)
 | 
				
			||||||
 | 
						if len(parts) != 3 || parts[0] != ServiceAccountUsernamePrefix || len(parts[1]) == 0 || len(parts[2]) == 0 {
 | 
				
			||||||
 | 
							return "", "", fmt.Errorf("Username must be in the form %s", MakeUsername("namespace", "name"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return parts[1], parts[2], nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// JWTTokenGenerator returns a TokenGenerator that generates signed JWT tokens, using the given privateKey.
 | 
				
			||||||
 | 
					// privateKey is a PEM-encoded byte array of a private RSA key.
 | 
				
			||||||
 | 
					// JWTTokenAuthenticator()
 | 
				
			||||||
 | 
					func JWTTokenGenerator(key *rsa.PrivateKey) TokenGenerator {
 | 
				
			||||||
 | 
						return &jwtTokenGenerator{key}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type jwtTokenGenerator struct {
 | 
				
			||||||
 | 
						key *rsa.PrivateKey
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (j *jwtTokenGenerator) GenerateToken(serviceAccount api.ServiceAccount, secret api.Secret) (string, error) {
 | 
				
			||||||
 | 
						token := jwt.New(jwt.SigningMethodRS256)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Identify the issuer
 | 
				
			||||||
 | 
						token.Claims[IssuerClaim] = Issuer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Username: `serviceaccount:<namespace>:<serviceaccount>`
 | 
				
			||||||
 | 
						token.Claims[SubjectClaim] = MakeUsername(serviceAccount.Namespace, serviceAccount.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Persist enough structured info for the authenticator to be able to look up the service account and secret
 | 
				
			||||||
 | 
						token.Claims[NamespaceClaim] = serviceAccount.Namespace
 | 
				
			||||||
 | 
						token.Claims[ServiceAccountNameClaim] = serviceAccount.Name
 | 
				
			||||||
 | 
						token.Claims[ServiceAccountUIDClaim] = serviceAccount.UID
 | 
				
			||||||
 | 
						token.Claims[SecretNameClaim] = secret.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Sign and get the complete encoded token as a string
 | 
				
			||||||
 | 
						return token.SignedString(j.key)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator
 | 
				
			||||||
 | 
					// Token signatures are verified using each of the given public keys until one works (allowing key rotation)
 | 
				
			||||||
 | 
					// If lookup is true, the service account and secret referenced as claims inside the token are retrieved and verified using the given client
 | 
				
			||||||
 | 
					func JWTTokenAuthenticator(keys []*rsa.PublicKey, lookup bool, client client.Interface) authenticator.Token {
 | 
				
			||||||
 | 
						return &jwtTokenAuthenticator{keys, lookup, client}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type jwtTokenAuthenticator struct {
 | 
				
			||||||
 | 
						keys   []*rsa.PublicKey
 | 
				
			||||||
 | 
						lookup bool
 | 
				
			||||||
 | 
						client client.Interface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) {
 | 
				
			||||||
 | 
						var validationError error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, key := range j.keys {
 | 
				
			||||||
 | 
							// Attempt to verify with each key until we find one that works
 | 
				
			||||||
 | 
							parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
 | 
				
			||||||
 | 
								if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return key, nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								switch err := err.(type) {
 | 
				
			||||||
 | 
								case *jwt.ValidationError:
 | 
				
			||||||
 | 
									if (err.Errors & jwt.ValidationErrorMalformed) != 0 {
 | 
				
			||||||
 | 
										// Not a JWT, no point in continuing
 | 
				
			||||||
 | 
										return nil, false, nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (err.Errors & jwt.ValidationErrorSignatureInvalid) != 0 {
 | 
				
			||||||
 | 
										// Signature error, perhaps one of the other keys will verify the signature
 | 
				
			||||||
 | 
										// If not, we want to return this error
 | 
				
			||||||
 | 
										validationError = err
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Other errors should just return as errors
 | 
				
			||||||
 | 
								return nil, false, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If we get here, we have a token with a recognized signature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Make sure we issued the token
 | 
				
			||||||
 | 
							iss, _ := parsedToken.Claims[IssuerClaim].(string)
 | 
				
			||||||
 | 
							if iss != Issuer {
 | 
				
			||||||
 | 
								return nil, false, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Make sure the claims we need exist
 | 
				
			||||||
 | 
							sub, _ := parsedToken.Claims[SubjectClaim].(string)
 | 
				
			||||||
 | 
							if len(sub) == 0 {
 | 
				
			||||||
 | 
								return nil, false, errors.New("sub claim is missing")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							namespace, _ := parsedToken.Claims[NamespaceClaim].(string)
 | 
				
			||||||
 | 
							if len(namespace) == 0 {
 | 
				
			||||||
 | 
								return nil, false, errors.New("namespace claim is missing")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							secretName, _ := parsedToken.Claims[SecretNameClaim].(string)
 | 
				
			||||||
 | 
							if len(namespace) == 0 {
 | 
				
			||||||
 | 
								return nil, false, errors.New("secretName claim is missing")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							serviceAccountName, _ := parsedToken.Claims[ServiceAccountNameClaim].(string)
 | 
				
			||||||
 | 
							if len(serviceAccountName) == 0 {
 | 
				
			||||||
 | 
								return nil, false, errors.New("serviceAccountName claim is missing")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							serviceAccountUID, _ := parsedToken.Claims[ServiceAccountUIDClaim].(string)
 | 
				
			||||||
 | 
							if len(serviceAccountUID) == 0 {
 | 
				
			||||||
 | 
								return nil, false, errors.New("serviceAccountUID claim is missing")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if j.lookup {
 | 
				
			||||||
 | 
								// Make sure token hasn't been invalidated by deletion of the secret
 | 
				
			||||||
 | 
								secret, err := j.client.Secrets(namespace).Get(secretName)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, false, errors.New("Token has been invalidated")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if bytes.Compare(secret.Data[api.ServiceAccountTokenKey], []byte(token)) != 0 {
 | 
				
			||||||
 | 
									return nil, false, errors.New("Token does not match server's copy")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Make sure service account still exists (name and UID)
 | 
				
			||||||
 | 
								serviceAccount, err := j.client.ServiceAccounts(namespace).Get(serviceAccountName)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, false, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if string(serviceAccount.UID) != serviceAccountUID {
 | 
				
			||||||
 | 
									return nil, false, fmt.Errorf("ServiceAccount UID (%s) does not match claim (%s)", serviceAccount.UID, serviceAccountUID)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return &user.DefaultInfo{
 | 
				
			||||||
 | 
								Name:   sub,
 | 
				
			||||||
 | 
								UID:    serviceAccountUID,
 | 
				
			||||||
 | 
								Groups: []string{},
 | 
				
			||||||
 | 
							}, true, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil, false, validationError
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										243
									
								
								pkg/serviceaccount/jwt_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								pkg/serviceaccount/jwt_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,243 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 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 serviceaccount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/rsa"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"
 | 
				
			||||||
 | 
						"github.com/dgrijalva/jwt-go"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const otherPublicKey = `-----BEGIN PUBLIC KEY-----
 | 
				
			||||||
 | 
					MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArXz0QkIG1B5Bj2/W69GH
 | 
				
			||||||
 | 
					rsm5e+RC3kE+VTgocge0atqlLBek35tRqLgUi3AcIrBZ/0YctMSWDVcRt5fkhWwe
 | 
				
			||||||
 | 
					Lqjj6qvAyNyOkrkBi1NFDpJBjYJtuKHgRhNxXbOzTSNpdSKXTfOkzqv56MwHOP25
 | 
				
			||||||
 | 
					yP/NNAODUtr92D5ySI5QX8RbXW+uDn+ixul286PBW/BCrE4tuS88dA0tYJPf8LCu
 | 
				
			||||||
 | 
					sqQOwlXYH/rNUg4Pyl9xxhR5DIJR0OzNNfChjw60zieRIt2LfM83fXhwk8IxRGkc
 | 
				
			||||||
 | 
					gPZm7ZsipmfbZK2Tkhnpsa4QxDg7zHJPMsB5kxRXW0cQipXcC3baDyN9KBApNXa0
 | 
				
			||||||
 | 
					PwIDAQAB
 | 
				
			||||||
 | 
					-----END PUBLIC KEY-----`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const publicKey = `-----BEGIN PUBLIC KEY-----
 | 
				
			||||||
 | 
					MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA249XwEo9k4tM8fMxV7zx
 | 
				
			||||||
 | 
					OhcrP+WvXn917koM5Qr2ZXs4vo26e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecI
 | 
				
			||||||
 | 
					zshKuv1gKIxbbLQMOuK1eA/4HALyEkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG
 | 
				
			||||||
 | 
					51RoiMgbQxaCyYxGfNLpLAZK9L0Tctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJU
 | 
				
			||||||
 | 
					j7OTh/AjjCnMnkgvKT2tpKxYQ59PgDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEi
 | 
				
			||||||
 | 
					B4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRM
 | 
				
			||||||
 | 
					WwIDAQAB
 | 
				
			||||||
 | 
					-----END PUBLIC KEY-----
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const privateKey = `-----BEGIN RSA PRIVATE KEY-----
 | 
				
			||||||
 | 
					MIIEowIBAAKCAQEA249XwEo9k4tM8fMxV7zxOhcrP+WvXn917koM5Qr2ZXs4vo26
 | 
				
			||||||
 | 
					e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecIzshKuv1gKIxbbLQMOuK1eA/4HALy
 | 
				
			||||||
 | 
					EkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG51RoiMgbQxaCyYxGfNLpLAZK9L0T
 | 
				
			||||||
 | 
					ctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJUj7OTh/AjjCnMnkgvKT2tpKxYQ59P
 | 
				
			||||||
 | 
					gDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEiB4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp
 | 
				
			||||||
 | 
					1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRMWwIDAQABAoIBAHJx8GqyCBDNbqk7
 | 
				
			||||||
 | 
					e7/hI9iE1S10Wwol5GH2RWxqX28cYMKq+8aE2LI1vPiXO89xOgelk4DN6urX6xjK
 | 
				
			||||||
 | 
					ZBF8RRIMQy/e/O2F4+3wl+Nl4vOXV1u6iVXMsD6JRg137mqJf1Fr9elg1bsaRofL
 | 
				
			||||||
 | 
					Q7CxPoB8dhS+Qb+hj0DhlqhgA9zG345CQCAds0ZYAZe8fP7bkwrLqZpMn7Dz9WVm
 | 
				
			||||||
 | 
					++YgYYKjuE95kPuup/LtWfA9rJyE/Fws8/jGvRSpVn1XglMLSMKhLd27sE8ZUSV0
 | 
				
			||||||
 | 
					2KUzbfRGE0+AnRULRrjpYaPu0XQ2JjdNvtkjBnv27RB89W9Gklxq821eH1Y8got8
 | 
				
			||||||
 | 
					FZodjxECgYEA93pz7AQZ2xDs67d1XLCzpX84GxKzttirmyj3OIlxgzVHjEMsvw8v
 | 
				
			||||||
 | 
					sjFiBU5xEEQDosrBdSknnlJqyiq1YwWG/WDckr13d8G2RQWoySN7JVmTQfXcLoTu
 | 
				
			||||||
 | 
					YGRiiTuoEi3ab3ZqrgGrFgX7T/cHuasbYvzCvhM2b4VIR3aSxU2DTUMCgYEA4x7J
 | 
				
			||||||
 | 
					T/ErP6GkU5nKstu/mIXwNzayEO1BJvPYsy7i7EsxTm3xe/b8/6cYOz5fvJLGH5mT
 | 
				
			||||||
 | 
					Q8YvuLqBcMwZardrYcwokD55UvNLOyfADDFZ6l3WntIqbA640Ok2g1X4U8J09xIq
 | 
				
			||||||
 | 
					ZLIWK1yWbbvi4QCeN5hvWq47e8sIj5QHjIIjRwkCgYEAyNqjltxFN9zmzPDa2d24
 | 
				
			||||||
 | 
					EAvOt3pYTYBQ1t9KtqImdL0bUqV6fZ6PsWoPCgt+DBuHb+prVPGP7Bkr/uTmznU/
 | 
				
			||||||
 | 
					+AlTO+12NsYLbr2HHagkXE31DEXE7CSLa8RNjN/UKtz4Ohq7vnowJvG35FCz/mb3
 | 
				
			||||||
 | 
					FUHbtHTXa2+bGBUOTf/5Hw0CgYBxw0r9EwUhw1qnUYJ5op7OzFAtp+T7m4ul8kCa
 | 
				
			||||||
 | 
					SCL8TxGsgl+SQ34opE775dtYfoBk9a0RJqVit3D8yg71KFjOTNAIqHJm/Vyyjc+h
 | 
				
			||||||
 | 
					i9rJDSXiuczsAVfLtPVMRfS0J9QkqeG4PIfkQmVLI/CZ2ZBmsqEcX+eFs4ZfPLun
 | 
				
			||||||
 | 
					Qsxe2QKBgGuPilIbLeIBDIaPiUI0FwU8v2j8CEQBYvoQn34c95hVQsig/o5z7zlo
 | 
				
			||||||
 | 
					UsO0wlTngXKlWdOcCs1kqEhTLrstf48djDxAYAxkw40nzeJOt7q52ib/fvf4/UBy
 | 
				
			||||||
 | 
					X024wzbiw1q07jFCyfQmODzURAx1VNT7QVUMdz/N8vy47/H40AZJ
 | 
				
			||||||
 | 
					-----END RSA PRIVATE KEY-----
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getPrivateKey(data string) *rsa.PrivateKey {
 | 
				
			||||||
 | 
						key, _ := jwt.ParseRSAPrivateKeyFromPEM([]byte(data))
 | 
				
			||||||
 | 
						return key
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getPublicKey(data string) *rsa.PublicKey {
 | 
				
			||||||
 | 
						key, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(data))
 | 
				
			||||||
 | 
						return key
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestReadPrivateKey(t *testing.T) {
 | 
				
			||||||
 | 
						f, err := ioutil.TempFile("", "")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error creating tmpfile: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer os.Remove(f.Name())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := ioutil.WriteFile(f.Name(), []byte(privateKey), os.FileMode(0600)); err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error creating tmpfile: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := ReadPrivateKey(f.Name()); err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error reading key: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestReadPublicKey(t *testing.T) {
 | 
				
			||||||
 | 
						f, err := ioutil.TempFile("", "")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error creating tmpfile: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer os.Remove(f.Name())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := ioutil.WriteFile(f.Name(), []byte(publicKey), os.FileMode(0600)); err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error creating tmpfile: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := ReadPublicKey(f.Name()); err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error reading key: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestTokenGenerateAndValidate(t *testing.T) {
 | 
				
			||||||
 | 
						expectedUserName := "serviceaccount:test:my-service-account"
 | 
				
			||||||
 | 
						expectedUserUID := "12345"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Related API objects
 | 
				
			||||||
 | 
						serviceAccount := &api.ServiceAccount{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      "my-service-account",
 | 
				
			||||||
 | 
								UID:       "12345",
 | 
				
			||||||
 | 
								Namespace: "test",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						secret := &api.Secret{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      "my-secret",
 | 
				
			||||||
 | 
								Namespace: "test",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generate the token
 | 
				
			||||||
 | 
						generator := JWTTokenGenerator(getPrivateKey(privateKey))
 | 
				
			||||||
 | 
						token, err := generator.GenerateToken(*serviceAccount, *secret)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error generating token: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(token) == 0 {
 | 
				
			||||||
 | 
							t.Fatalf("no token generated")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// "Save" the token
 | 
				
			||||||
 | 
						secret.Data = map[string][]byte{
 | 
				
			||||||
 | 
							"token": []byte(token),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testCases := map[string]struct {
 | 
				
			||||||
 | 
							Client client.Interface
 | 
				
			||||||
 | 
							Keys   []*rsa.PublicKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ExpectedErr      bool
 | 
				
			||||||
 | 
							ExpectedOK       bool
 | 
				
			||||||
 | 
							ExpectedUserName string
 | 
				
			||||||
 | 
							ExpectedUserUID  string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							"no keys": {
 | 
				
			||||||
 | 
								Client:      nil,
 | 
				
			||||||
 | 
								Keys:        []*rsa.PublicKey{},
 | 
				
			||||||
 | 
								ExpectedErr: false,
 | 
				
			||||||
 | 
								ExpectedOK:  false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"invalid keys": {
 | 
				
			||||||
 | 
								Client:      nil,
 | 
				
			||||||
 | 
								Keys:        []*rsa.PublicKey{getPublicKey(otherPublicKey)},
 | 
				
			||||||
 | 
								ExpectedErr: true,
 | 
				
			||||||
 | 
								ExpectedOK:  false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"valid key": {
 | 
				
			||||||
 | 
								Client:           nil,
 | 
				
			||||||
 | 
								Keys:             []*rsa.PublicKey{getPublicKey(publicKey)},
 | 
				
			||||||
 | 
								ExpectedErr:      false,
 | 
				
			||||||
 | 
								ExpectedOK:       true,
 | 
				
			||||||
 | 
								ExpectedUserName: expectedUserName,
 | 
				
			||||||
 | 
								ExpectedUserUID:  expectedUserUID,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"rotated keys": {
 | 
				
			||||||
 | 
								Client:           nil,
 | 
				
			||||||
 | 
								Keys:             []*rsa.PublicKey{getPublicKey(otherPublicKey), getPublicKey(publicKey)},
 | 
				
			||||||
 | 
								ExpectedErr:      false,
 | 
				
			||||||
 | 
								ExpectedOK:       true,
 | 
				
			||||||
 | 
								ExpectedUserName: expectedUserName,
 | 
				
			||||||
 | 
								ExpectedUserUID:  expectedUserUID,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"valid lookup": {
 | 
				
			||||||
 | 
								Client:           testclient.NewSimpleFake(serviceAccount, secret),
 | 
				
			||||||
 | 
								Keys:             []*rsa.PublicKey{getPublicKey(publicKey)},
 | 
				
			||||||
 | 
								ExpectedErr:      false,
 | 
				
			||||||
 | 
								ExpectedOK:       true,
 | 
				
			||||||
 | 
								ExpectedUserName: expectedUserName,
 | 
				
			||||||
 | 
								ExpectedUserUID:  expectedUserUID,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"invalid secret lookup": {
 | 
				
			||||||
 | 
								Client:      testclient.NewSimpleFake(serviceAccount),
 | 
				
			||||||
 | 
								Keys:        []*rsa.PublicKey{getPublicKey(publicKey)},
 | 
				
			||||||
 | 
								ExpectedErr: true,
 | 
				
			||||||
 | 
								ExpectedOK:  false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"invalid serviceaccount lookup": {
 | 
				
			||||||
 | 
								Client:      testclient.NewSimpleFake(secret),
 | 
				
			||||||
 | 
								Keys:        []*rsa.PublicKey{getPublicKey(publicKey)},
 | 
				
			||||||
 | 
								ExpectedErr: true,
 | 
				
			||||||
 | 
								ExpectedOK:  false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for k, tc := range testCases {
 | 
				
			||||||
 | 
							authenticator := JWTTokenAuthenticator(tc.Keys, tc.Client != nil, tc.Client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							user, ok, err := authenticator.AuthenticateToken(token)
 | 
				
			||||||
 | 
							if (err != nil) != tc.ExpectedErr {
 | 
				
			||||||
 | 
								t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if ok != tc.ExpectedOK {
 | 
				
			||||||
 | 
								t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil || !ok {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if user.GetName() != tc.ExpectedUserName {
 | 
				
			||||||
 | 
								t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, user.GetName())
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if user.GetUID() != tc.ExpectedUserUID {
 | 
				
			||||||
 | 
								t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, user.GetUID())
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user