diff --git a/cluster/saltbase/salt/kube-controller-manager/kube-controller-manager.manifest b/cluster/saltbase/salt/kube-controller-manager/kube-controller-manager.manifest index 30a06d75ae6..969c390b7fe 100644 --- a/cluster/saltbase/salt/kube-controller-manager/kube-controller-manager.manifest +++ b/cluster/saltbase/salt/kube-controller-manager/kube-controller-manager.manifest @@ -28,7 +28,13 @@ {% endif -%} {% endif -%} -{% set params = "--master=127.0.0.1:8080" + " " + cluster_name + " " + cluster_cidr + " " + allocate_node_cidrs + " " + cloud_provider + " " + cloud_config + service_account_key + pillar['log_level'] -%} +{% set root_ca_file = "" -%} + +{% if grains['cloud'] is defined and grains.cloud in [ 'aws', 'gce' ] %} + {% set root_ca_file = "--root_ca_file=/srv/kubernetes/ca.crt" -%} +{% endif -%} + +{% set params = "--master=127.0.0.1:8080" + " " + cluster_name + " " + cluster_cidr + " " + allocate_node_cidrs + " " + cloud_provider + " " + cloud_config + service_account_key + pillar['log_level'] + " " + root_ca_file -%} { "apiVersion": "v1beta3", diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 9df4b646cbd..0733ca1449f 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -23,6 +23,8 @@ limitations under the License. package app import ( + "fmt" + "io/ioutil" "net" "net/http" "net/http/pprof" @@ -73,6 +75,7 @@ type CMServer struct { DeletingPodsQps float32 DeletingPodsBurst int ServiceAccountKeyFile string + RootCAFile string ClusterName string ClusterCIDR util.IPNet @@ -156,6 +159,7 @@ func (s *CMServer) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&s.AllocateNodeCIDRs, "allocate-node-cidrs", false, "Should CIDRs for Pods be allocated and set on the cloud provider.") fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig)") fs.StringVar(&s.Kubeconfig, "kubeconfig", s.Kubeconfig, "Path to kubeconfig file with authorization and master location information.") + fs.StringVar(&s.RootCAFile, "root-ca-file", s.RootCAFile, "If set, this root certificate authority will be included in service account's token secret. This must be a valid PEM-encoded CA bundle.") } // Run runs the CMServer. This should never exit. @@ -243,6 +247,20 @@ func (s *CMServer) Run(_ []string) error { } pvRecycler.Run() + var rootCA []byte + + if s.RootCAFile != "" { + rootCA, err := ioutil.ReadFile(s.RootCAFile) + if err != nil { + return fmt.Errorf("error reading root-ca-file at %s: %v", s.RootCAFile, err) + } + if _, err := util.CertsFromPEM(rootCA); err != nil { + return fmt.Errorf("error parsing root-ca-file at %s: %v", s.RootCAFile, err) + } + } else { + rootCA = kubeconfig.CAData + } + if len(s.ServiceAccountKeyFile) > 0 { privateKey, err := serviceaccount.ReadPrivateKey(s.ServiceAccountKeyFile) if err != nil { @@ -250,9 +268,10 @@ func (s *CMServer) Run(_ []string) error { } else { serviceaccount.NewTokensController( kubeClient, - serviceaccount.DefaultTokenControllerOptions( - serviceaccount.JWTTokenGenerator(privateKey), - ), + serviceaccount.TokensControllerOptions{ + TokenGenerator: serviceaccount.JWTTokenGenerator(privateKey), + RootCA: rootCA, + }, ).Run() } } diff --git a/contrib/mesos/pkg/controllermanager/controllermanager.go b/contrib/mesos/pkg/controllermanager/controllermanager.go index 75651f7a26a..43781d45c80 100644 --- a/contrib/mesos/pkg/controllermanager/controllermanager.go +++ b/contrib/mesos/pkg/controllermanager/controllermanager.go @@ -157,9 +157,10 @@ func (s *CMServer) Run(_ []string) error { } else { serviceaccount.NewTokensController( kubeClient, - serviceaccount.DefaultTokenControllerOptions( - serviceaccount.JWTTokenGenerator(privateKey), - ), + serviceaccount.TokensControllerOptions{ + TokenGenerator: serviceaccount.JWTTokenGenerator(privateKey), + RootCA: kubeconfig.CAData, + }, ).Run() } } diff --git a/pkg/api/types.go b/pkg/api/types.go index bbf9a2e8dd4..f26149b8110 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1961,6 +1961,8 @@ const ( ServiceAccountTokenKey = "token" // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + // ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets + ServiceAccountRootCAKey = "ca.crt" // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg // diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index 3bce90ef9b8..23f0865190d 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -1862,6 +1862,8 @@ const ( ServiceAccountTokenKey = "token" // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + // ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets + ServiceAccountRootCAKey = "ca.crt" // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg // diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 7c5d844e8fe..efe404203c3 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -1868,6 +1868,8 @@ const ( ServiceAccountTokenKey = "token" // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + // ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets + ServiceAccountRootCAKey = "ca.crt" // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg // diff --git a/pkg/client/helper.go b/pkg/client/helper.go index 36508a25fa3..cb658838a8c 100644 --- a/pkg/client/helper.go +++ b/pkg/client/helper.go @@ -29,6 +29,7 @@ import ( "strings" "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -236,17 +237,24 @@ func NewOrDie(c *Config) *Client { // running inside a pod running on kuberenetes. It will return an error if // called from a process not running in a kubernetes environment. func InClusterConfig() (*Config, error) { - token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") + token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountTokenKey) if err != nil { return nil, err } + tlsClientConfig := TLSClientConfig{} + rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountRootCAKey + if _, err := util.CertPoolFromFile(rootCAFile); err != nil { + glog.Errorf("expected to load root ca config from %s, but got err: %v", rootCAFile, err) + } else { + tlsClientConfig.CAFile = rootCAFile + } + return &Config{ // TODO: switch to using cluster DNS. - Host: "https://" + net.JoinHostPort(os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")), - Version: "v1beta3", - BearerToken: string(token), - // TODO: package certs along with the token - Insecure: true, + Host: "https://" + net.JoinHostPort(os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")), + Version: "v1beta3", + BearerToken: string(token), + TLSClientConfig: tlsClientConfig, }, nil } diff --git a/pkg/serviceaccount/tokens_controller.go b/pkg/serviceaccount/tokens_controller.go index 562fdccee8e..5462781c3cf 100644 --- a/pkg/serviceaccount/tokens_controller.go +++ b/pkg/serviceaccount/tokens_controller.go @@ -44,11 +44,8 @@ type TokensControllerOptions struct { // SecretResync is the time.Duration at which to fully re-list secrets. // If zero, re-list will be delayed as long as possible SecretResync time.Duration -} - -// DefaultTokenControllerOptions returns -func DefaultTokenControllerOptions(tokenGenerator TokenGenerator) TokensControllerOptions { - return TokensControllerOptions{TokenGenerator: tokenGenerator} + // This CA will be added in the secretes of service accounts + RootCA []byte } // NewTokensController returns a new *TokensController. @@ -56,6 +53,7 @@ func NewTokensController(cl client.Interface, options TokensControllerOptions) * e := &TokensController{ client: cl, token: options.TokenGenerator, + rootCA: options.RootCA, } e.serviceAccounts, e.serviceAccountController = framework.NewIndexerInformer( @@ -110,6 +108,8 @@ type TokensController struct { client client.Interface token TokenGenerator + rootCA []byte + serviceAccounts cache.Indexer secrets cache.Indexer @@ -293,6 +293,9 @@ func (e *TokensController) createSecret(serviceAccount *api.ServiceAccount) erro return err } secret.Data[api.ServiceAccountTokenKey] = []byte(token) + if e.rootCA != nil && len(e.rootCA) > 0 { + secret.Data[api.ServiceAccountRootCAKey] = e.rootCA + } // Save the secret if _, err := e.client.Secrets(serviceAccount.Namespace).Create(secret); err != nil { @@ -337,6 +340,9 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *api.ServiceAcco if ok && len(tokenData) > 0 { return nil } + if e.rootCA != nil && len(e.rootCA) > 0 { + secret.Data[api.ServiceAccountRootCAKey] = e.rootCA + } // Generate the token token, err := e.token.GenerateToken(*serviceAccount, *secret) diff --git a/pkg/serviceaccount/tokens_controller_test.go b/pkg/serviceaccount/tokens_controller_test.go index 16ac25a4fa0..5871ba6ec82 100644 --- a/pkg/serviceaccount/tokens_controller_test.go +++ b/pkg/serviceaccount/tokens_controller_test.go @@ -378,7 +378,7 @@ func TestTokenCreation(t *testing.T) { client := testclient.NewSimpleFake(tc.ClientObjects...) - controller := NewTokensController(client, DefaultTokenControllerOptions(generator)) + controller := NewTokensController(client, TokensControllerOptions{TokenGenerator: generator}) // Tell the token controller whether its stores have been synced controller.serviceAccountsSynced = func() bool { return !tc.ServiceAccountsSyncPending } diff --git a/pkg/util/crypto.go b/pkg/util/crypto.go index 03cd1a7e3b3..f312246bb57 100644 --- a/pkg/util/crypto.go +++ b/pkg/util/crypto.go @@ -123,16 +123,16 @@ func certificatesFromFile(file string) ([]*x509.Certificate, error) { if err != nil { return nil, err } - certs, err := certsFromPEM(pemBlock) + certs, err := CertsFromPEM(pemBlock) if err != nil { return nil, fmt.Errorf("error reading %s: %s", file, err) } return certs, nil } -// certsFromPEM returns the x509.Certificates contained in the given PEM-encoded byte array +// CertsFromPEM returns the x509.Certificates contained in the given PEM-encoded byte array // Returns an error if a certificate could not be parsed, or if the data does not contain any certificates -func certsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) { +func CertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) { ok := false certs := []*x509.Certificate{} for len(pemCerts) > 0 { diff --git a/test/integration/service_account_test.go b/test/integration/service_account_test.go index 3b07c39b146..4a955ed2032 100644 --- a/test/integration/service_account_test.go +++ b/test/integration/service_account_test.go @@ -422,7 +422,7 @@ func startServiceAccountTestServer(t *testing.T) (*client.Client, client.Config, }) // Start the service account and service account token controllers - tokenController := serviceaccount.NewTokensController(rootClient, serviceaccount.DefaultTokenControllerOptions(serviceaccount.JWTTokenGenerator(serviceAccountKey))) + tokenController := serviceaccount.NewTokensController(rootClient, serviceaccount.TokensControllerOptions{TokenGenerator: serviceaccount.JWTTokenGenerator(serviceAccountKey)}) tokenController.Run() serviceAccountController := serviceaccount.NewServiceAccountsController(rootClient, serviceaccount.DefaultServiceAccountsControllerOptions()) serviceAccountController.Run()