Merge pull request #41682 from perotinus/unpwandtokens

Automatic merge from submit-queue (batch tested with PRs 41984, 41682, 41924, 41928)

Add options to kubefed telling it to generate HTTP Basic and/or token credentials for the Federated API server

fixes #41265.

**Release notes**:
```release-note
Adds two options to kubefed, `-apiserver-enable-basic-auth` and `-apiserver-enable-token-auth`, which generate an HTTP Basic username/password and a token respectively for the Federated API server.
```
This commit is contained in:
Kubernetes Submit Queue 2017-03-02 10:51:10 -08:00 committed by GitHub
commit 4672314029
6 changed files with 321 additions and 93 deletions

View File

@ -85,7 +85,9 @@ function init() {
--dns-zone-name="${DNS_ZONE_NAME}" \ --dns-zone-name="${DNS_ZONE_NAME}" \
--dns-provider="${DNS_PROVIDER}" \ --dns-provider="${DNS_PROVIDER}" \
--image="${kube_registry}/hyperkube-amd64:${kube_version}" \ --image="${kube_registry}/hyperkube-amd64:${kube_version}" \
--apiserver-arg-overrides="--storage-backend=etcd2" --apiserver-arg-overrides="--storage-backend=etcd2" \
--apiserver-enable-basic-auth=true \
--apiserver-enable-token-auth=true
} }
# join_clusters joins the clusters in the local kubeconfig to federation. The clusters # join_clusters joins the clusters in the local kubeconfig to federation. The clusters

View File

@ -27,6 +27,7 @@ go_library(
"//vendor:github.com/spf13/pflag", "//vendor:github.com/spf13/pflag",
"//vendor:k8s.io/apimachinery/pkg/api/resource", "//vendor:k8s.io/apimachinery/pkg/api/resource",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/util/uuid",
"//vendor:k8s.io/apimachinery/pkg/util/wait", "//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/client-go/tools/clientcmd", "//vendor:k8s.io/client-go/tools/clientcmd",
"//vendor:k8s.io/client-go/tools/clientcmd/api", "//vendor:k8s.io/client-go/tools/clientcmd/api",

View File

@ -43,6 +43,7 @@ import (
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api" clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@ -148,6 +149,8 @@ type initFederationOptions struct {
apiServerServiceTypeString string apiServerServiceTypeString string
apiServerServiceType v1.ServiceType apiServerServiceType v1.ServiceType
apiServerAdvertiseAddress string apiServerAdvertiseAddress string
apiServerEnableHTTPBasicAuth bool
apiServerEnableTokenAuth bool
} }
func (o *initFederationOptions) Bind(flags *pflag.FlagSet) { func (o *initFederationOptions) Bind(flags *pflag.FlagSet) {
@ -164,6 +167,8 @@ func (o *initFederationOptions) Bind(flags *pflag.FlagSet) {
flags.StringVar(&o.controllerManagerOverridesString, "controllermanager-arg-overrides", "", "comma separated list of federation-controller-manager arguments to override: Example \"--arg1=value1,--arg2=value2...\"") flags.StringVar(&o.controllerManagerOverridesString, "controllermanager-arg-overrides", "", "comma separated list of federation-controller-manager arguments to override: Example \"--arg1=value1,--arg2=value2...\"")
flags.StringVar(&o.apiServerServiceTypeString, apiserverServiceTypeFlag, string(v1.ServiceTypeLoadBalancer), "The type of service to create for federation API server. Options: 'LoadBalancer' (default), 'NodePort'.") flags.StringVar(&o.apiServerServiceTypeString, apiserverServiceTypeFlag, string(v1.ServiceTypeLoadBalancer), "The type of service to create for federation API server. Options: 'LoadBalancer' (default), 'NodePort'.")
flags.StringVar(&o.apiServerAdvertiseAddress, apiserverAdvertiseAddressFlag, "", "Preferred address to advertise api server nodeport service. Valid only if '"+apiserverServiceTypeFlag+"=NodePort'.") flags.StringVar(&o.apiServerAdvertiseAddress, apiserverAdvertiseAddressFlag, "", "Preferred address to advertise api server nodeport service. Valid only if '"+apiserverServiceTypeFlag+"=NodePort'.")
flags.BoolVar(&o.apiServerEnableHTTPBasicAuth, "apiserver-enable-basic-auth", false, "Enables HTTP Basic authentication for the federation-apiserver. Defaults to false.")
flags.BoolVar(&o.apiServerEnableTokenAuth, "apiserver-enable-token-auth", false, "Enables token authentication for the federation-apiserver. Defaults to false.")
} }
// NewCmdInit defines the `init` command that bootstraps a federation // NewCmdInit defines the `init` command that bootstraps a federation
@ -196,6 +201,13 @@ type entityKeyPairs struct {
admin *triple.KeyPair admin *triple.KeyPair
} }
type credentials struct {
username string
password string
token string
certEntKeyPairs *entityKeyPairs
}
// Complete ensures that options are valid and marshals them if necessary. // Complete ensures that options are valid and marshals them if necessary.
func (i *initFederation) Complete(cmd *cobra.Command, args []string) error { func (i *initFederation) Complete(cmd *cobra.Command, args []string) error {
if len(i.options.dnsProvider) == 0 { if len(i.options.dnsProvider) == 0 {
@ -274,19 +286,20 @@ func (i *initFederation) Run(cmdOut io.Writer, config util.AdminConfig) error {
return err return err
} }
// 3. Generate TLS certificates and credentials // 3a. Generate TLS certificates and credentials, and other credentials if needed
entKeyPairs, err := genCerts(i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, svc.Name, HostClusterLocalDNSZoneName, ips, hostnames) credentials, err := generateCredentials(i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, svc.Name, HostClusterLocalDNSZoneName, serverCredName, ips, hostnames, i.options.apiServerEnableHTTPBasicAuth, i.options.apiServerEnableTokenAuth, i.options.dryRun)
if err != nil { if err != nil {
return err return err
} }
_, err = createAPIServerCredentialsSecret(hostClientset, i.commonOptions.FederationSystemNamespace, serverCredName, entKeyPairs, i.options.dryRun) // 3b. Create the secret containing the credentials.
_, err = createAPIServerCredentialsSecret(hostClientset, i.commonOptions.FederationSystemNamespace, serverCredName, credentials, i.options.dryRun)
if err != nil { if err != nil {
return err return err
} }
// 4. Create a kubeconfig secret // 4. Create a kubeconfig secret
_, err = createControllerManagerKubeconfigSecret(hostClientset, i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, svc.Name, cmKubeconfigName, entKeyPairs, i.options.dryRun) _, err = createControllerManagerKubeconfigSecret(hostClientset, i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, svc.Name, cmKubeconfigName, credentials.certEntKeyPairs, i.options.dryRun)
if err != nil { if err != nil {
return err return err
} }
@ -311,7 +324,7 @@ func (i *initFederation) Run(cmdOut io.Writer, config util.AdminConfig) error {
} }
// 6. Create federation API server // 6. Create federation API server
_, err = createAPIServer(hostClientset, i.commonOptions.FederationSystemNamespace, serverName, i.options.image, serverCredName, advertiseAddress, i.options.apiServerOverrides, pvc, i.options.dryRun) _, err = createAPIServer(hostClientset, i.commonOptions.FederationSystemNamespace, serverName, i.options.image, advertiseAddress, serverCredName, i.options.apiServerEnableHTTPBasicAuth, i.options.apiServerEnableTokenAuth, i.options.apiServerOverrides, pvc, i.options.dryRun)
if err != nil { if err != nil {
return err return err
} }
@ -358,7 +371,7 @@ func (i *initFederation) Run(cmdOut io.Writer, config util.AdminConfig) error {
// 8. Write the federation API server endpoint info, credentials // 8. Write the federation API server endpoint info, credentials
// and context to kubeconfig // and context to kubeconfig
err = updateKubeconfig(config, i.commonOptions.Name, endpoint, i.commonOptions.Kubeconfig, entKeyPairs, i.options.dryRun) err = updateKubeconfig(config, i.commonOptions.Name, endpoint, i.commonOptions.Kubeconfig, credentials, i.options.dryRun)
if err != nil { if err != nil {
return err return err
} }
@ -498,6 +511,25 @@ func waitForLoadBalancerAddress(clientset client.Interface, svc *api.Service, dr
return ips, hostnames, nil return ips, hostnames, nil
} }
func generateCredentials(svcNamespace, name, svcName, localDNSZoneName, serverCredName string, ips, hostnames []string, enableHTTPBasicAuth, enableTokenAuth, dryRun bool) (*credentials, error) {
credentials := credentials{
username: AdminCN,
}
if enableHTTPBasicAuth {
credentials.password = string(uuid.NewUUID())
}
if enableTokenAuth {
credentials.token = string(uuid.NewUUID())
}
entKeyPairs, err := genCerts(svcNamespace, name, svcName, localDNSZoneName, ips, hostnames)
if err != nil {
return nil, err
}
credentials.certEntKeyPairs = entKeyPairs
return &credentials, nil
}
func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnames []string) (*entityKeyPairs, error) { func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnames []string) (*entityKeyPairs, error) {
ca, err := triple.NewCA(name) ca, err := triple.NewCA(name)
if err != nil { if err != nil {
@ -523,18 +555,26 @@ func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnam
}, nil }, nil
} }
func createAPIServerCredentialsSecret(clientset client.Interface, namespace, credentialsName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) { func createAPIServerCredentialsSecret(clientset client.Interface, namespace, credentialsName string, credentials *credentials, dryRun bool) (*api.Secret, error) {
// Build the secret object with API server credentials. // Build the secret object with API server credentials.
data := map[string][]byte{
"ca.crt": certutil.EncodeCertPEM(credentials.certEntKeyPairs.ca.Cert),
"server.crt": certutil.EncodeCertPEM(credentials.certEntKeyPairs.server.Cert),
"server.key": certutil.EncodePrivateKeyPEM(credentials.certEntKeyPairs.server.Key),
}
if credentials.password != "" {
data["basicauth.csv"] = authFileContents(credentials.username, credentials.password)
}
if credentials.token != "" {
data["token.csv"] = authFileContents(credentials.username, credentials.token)
}
secret := &api.Secret{ secret := &api.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: credentialsName, Name: credentialsName,
Namespace: namespace, Namespace: namespace,
}, },
Data: map[string][]byte{ Data: data,
"ca.crt": certutil.EncodeCertPEM(entKeyPairs.ca.Cert),
"server.crt": certutil.EncodeCertPEM(entKeyPairs.server.Cert),
"server.key": certutil.EncodePrivateKeyPEM(entKeyPairs.server.Key),
},
} }
if dryRun { if dryRun {
@ -591,7 +631,7 @@ func createPVC(clientset client.Interface, namespace, svcName, etcdPVCapacity st
return clientset.Core().PersistentVolumeClaims(namespace).Create(pvc) return clientset.Core().PersistentVolumeClaims(namespace).Create(pvc)
} }
func createAPIServer(clientset client.Interface, namespace, name, image, credentialsName, advertiseAddress string, argOverrides map[string]string, pvc *api.PersistentVolumeClaim, dryRun bool) (*extensions.Deployment, error) { func createAPIServer(clientset client.Interface, namespace, name, image, advertiseAddress, credentialsName string, hasHTTPBasicAuthFile, hasTokenAuthFile bool, argOverrides map[string]string, pvc *api.PersistentVolumeClaim, dryRun bool) (*extensions.Deployment, error) {
command := []string{ command := []string{
"/hyperkube", "/hyperkube",
"federation-apiserver", "federation-apiserver",
@ -609,6 +649,12 @@ func createAPIServer(clientset client.Interface, namespace, name, image, credent
if advertiseAddress != "" { if advertiseAddress != "" {
argsMap["--advertise-address"] = advertiseAddress argsMap["--advertise-address"] = advertiseAddress
} }
if hasHTTPBasicAuthFile {
argsMap["--basic-auth-file"] = "/etc/federation/apiserver/basicauth.csv"
}
if hasTokenAuthFile {
argsMap["--token-auth-file"] = "/etc/federation/apiserver/token.csv"
}
args := argMapsToArgStrings(argsMap, argOverrides) args := argMapsToArgStrings(argsMap, argOverrides)
command = append(command, args...) command = append(command, args...)
@ -936,7 +982,7 @@ func printSuccess(cmdOut io.Writer, ips, hostnames []string, svc *api.Service) e
return err return err
} }
func updateKubeconfig(config util.AdminConfig, name, endpoint, kubeConfigPath string, entKeyPairs *entityKeyPairs, dryRun bool) error { func updateKubeconfig(config util.AdminConfig, name, endpoint, kubeConfigPath string, credentials *credentials, dryRun bool) error {
po := config.PathOptions() po := config.PathOptions()
po.LoadingRules.ExplicitPath = kubeConfigPath po.LoadingRules.ExplicitPath = kubeConfigPath
kubeconfig, err := po.GetStartingConfig() kubeconfig, err := po.GetStartingConfig()
@ -951,13 +997,20 @@ func updateKubeconfig(config util.AdminConfig, name, endpoint, kubeConfigPath st
endpoint = fmt.Sprintf("https://%s", endpoint) endpoint = fmt.Sprintf("https://%s", endpoint)
} }
cluster.Server = endpoint cluster.Server = endpoint
cluster.CertificateAuthorityData = certutil.EncodeCertPEM(entKeyPairs.ca.Cert) cluster.CertificateAuthorityData = certutil.EncodeCertPEM(credentials.certEntKeyPairs.ca.Cert)
// Populate credentials. // Populate credentials.
authInfo := clientcmdapi.NewAuthInfo() authInfo := clientcmdapi.NewAuthInfo()
authInfo.ClientCertificateData = certutil.EncodeCertPEM(entKeyPairs.admin.Cert) authInfo.ClientCertificateData = certutil.EncodeCertPEM(credentials.certEntKeyPairs.admin.Cert)
authInfo.ClientKeyData = certutil.EncodePrivateKeyPEM(entKeyPairs.admin.Key) authInfo.ClientKeyData = certutil.EncodePrivateKeyPEM(credentials.certEntKeyPairs.admin.Key)
authInfo.Username = AdminCN authInfo.Token = credentials.token
var httpBasicAuthInfo *clientcmdapi.AuthInfo
if credentials.password != "" {
httpBasicAuthInfo = clientcmdapi.NewAuthInfo()
httpBasicAuthInfo.Password = credentials.password
httpBasicAuthInfo.Username = credentials.username
}
// Populate context. // Populate context.
context := clientcmdapi.NewContext() context := clientcmdapi.NewContext()
@ -968,6 +1021,9 @@ func updateKubeconfig(config util.AdminConfig, name, endpoint, kubeConfigPath st
// credentials and context. // credentials and context.
kubeconfig.Clusters[name] = cluster kubeconfig.Clusters[name] = cluster
kubeconfig.AuthInfos[name] = authInfo kubeconfig.AuthInfos[name] = authInfo
if httpBasicAuthInfo != nil {
kubeconfig.AuthInfos[fmt.Sprintf("%s-basic-auth", name)] = httpBasicAuthInfo
}
kubeconfig.Contexts[name] = context kubeconfig.Contexts[name] = context
if !dryRun { if !dryRun {
@ -1034,3 +1090,9 @@ func addDNSProviderConfig(dep *extensions.Deployment, secretName string) *extens
return dep return dep
} }
// authFileContents returns a CSV string containing the contents of an
// authentication file in the format required by the federation-apiserver.
func authFileContents(username, authSecret string) []byte {
return []byte(fmt.Sprintf("%s,%s,%s\n", authSecret, username, uuid.NewUUID()))
}

View File

@ -79,21 +79,23 @@ func TestInitFederation(t *testing.T) {
defer kubefedtesting.RemoveFakeKubeconfigFiles(fakeKubeFiles) defer kubefedtesting.RemoveFakeKubeconfigFiles(fakeKubeFiles)
testCases := []struct { testCases := []struct {
federation string federation string
kubeconfigGlobal string kubeconfigGlobal string
kubeconfigExplicit string kubeconfigExplicit string
dnsZoneName string dnsZoneName string
lbIP string lbIP string
apiserverServiceType v1.ServiceType apiserverServiceType v1.ServiceType
advertiseAddress string advertiseAddress string
image string image string
etcdPVCapacity string etcdPVCapacity string
etcdPersistence string etcdPersistence string
expectedErr string expectedErr string
dnsProviderConfig string dnsProviderConfig string
dryRun string dryRun string
apiserverArgOverrides string apiserverArgOverrides string
cmArgOverrides string cmArgOverrides string
apiserverEnableHTTPBasicAuth bool
apiserverEnableTokenAuth bool
}{ }{
{ {
federation: "union", federation: "union",
@ -175,6 +177,21 @@ func TestInitFederation(t *testing.T) {
expectedErr: "", expectedErr: "",
dryRun: "", dryRun: "",
}, },
{
federation: "union",
kubeconfigGlobal: fakeKubeFiles[0],
kubeconfigExplicit: "",
dnsZoneName: "example.test.",
apiserverServiceType: v1.ServiceTypeNodePort,
advertiseAddress: nodeIP,
image: "example.test/foo:bar",
etcdPVCapacity: "5Gi",
etcdPersistence: "true",
expectedErr: "",
dryRun: "",
apiserverEnableHTTPBasicAuth: true,
apiserverEnableTokenAuth: true,
},
} }
//TODO: implement a negative case for dry run //TODO: implement a negative case for dry run
@ -191,7 +208,7 @@ func TestInitFederation(t *testing.T) {
tc.dnsProviderConfig = tmpfile.Name() tc.dnsProviderConfig = tmpfile.Name()
defer os.Remove(tmpfile.Name()) defer os.Remove(tmpfile.Name())
} }
hostFactory, err := fakeInitHostFactory(tc.apiserverServiceType, tc.federation, util.DefaultFederationSystemNamespace, tc.advertiseAddress, tc.lbIP, tc.dnsZoneName, tc.image, dnsProvider, tc.dnsProviderConfig, tc.etcdPersistence, tc.etcdPVCapacity, tc.apiserverArgOverrides, tc.cmArgOverrides) hostFactory, err := fakeInitHostFactory(tc.apiserverServiceType, tc.federation, util.DefaultFederationSystemNamespace, tc.advertiseAddress, tc.lbIP, tc.dnsZoneName, tc.image, dnsProvider, tc.dnsProviderConfig, tc.etcdPersistence, tc.etcdPVCapacity, tc.apiserverArgOverrides, tc.cmArgOverrides, tc.apiserverEnableHTTPBasicAuth, tc.apiserverEnableTokenAuth)
if err != nil { if err != nil {
t.Fatalf("[%d] unexpected error: %v", i, err) t.Fatalf("[%d] unexpected error: %v", i, err)
} }
@ -227,6 +244,12 @@ func TestInitFederation(t *testing.T) {
if tc.dryRun == "valid-run" { if tc.dryRun == "valid-run" {
cmd.Flags().Set("dry-run", "true") cmd.Flags().Set("dry-run", "true")
} }
if tc.apiserverEnableHTTPBasicAuth {
cmd.Flags().Set("apiserver-enable-basic-auth", "true")
}
if tc.apiserverEnableTokenAuth {
cmd.Flags().Set("apiserver-enable-token-auth", "true")
}
cmd.Run(cmd, []string{tc.federation}) cmd.Run(cmd, []string{tc.federation})
@ -253,7 +276,7 @@ func TestInitFederation(t *testing.T) {
return return
} }
testKubeconfigUpdate(t, tc.apiserverServiceType, tc.federation, tc.advertiseAddress, tc.lbIP, tc.kubeconfigGlobal, tc.kubeconfigExplicit) testKubeconfigUpdate(t, tc.apiserverServiceType, tc.federation, tc.advertiseAddress, tc.lbIP, tc.kubeconfigGlobal, tc.kubeconfigExplicit, tc.apiserverEnableHTTPBasicAuth, tc.apiserverEnableTokenAuth)
} }
} }
@ -554,7 +577,7 @@ func TestCertsHTTPS(t *testing.T) {
} }
} }
func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, namespaceName, advertiseAddress, lbIp, dnsZoneName, image, dnsProvider, dnsProviderConfig, etcdPersistence, etcdPVCapacity, apiserverOverrideArg, cmOverrideArg string) (cmdutil.Factory, error) { func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, namespaceName, advertiseAddress, lbIp, dnsZoneName, image, dnsProvider, dnsProviderConfig, etcdPersistence, etcdPVCapacity, apiserverOverrideArg, cmOverrideArg string, apiserverEnableHTTPBasicAuth, apiserverEnableTokenAuth bool) (cmdutil.Factory, error) {
svcName := federationName + "-apiserver" svcName := federationName + "-apiserver"
svcUrlPrefix := "/api/v1/namespaces/federation-system/services" svcUrlPrefix := "/api/v1/namespaces/federation-system/services"
credSecretName := svcName + "-credentials" credSecretName := svcName + "-credentials"
@ -782,6 +805,12 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
} else { } else {
apiserverArgs = append(apiserverArgs, "--client-ca-file=/etc/federation/apiserver/ca.crt") apiserverArgs = append(apiserverArgs, "--client-ca-file=/etc/federation/apiserver/ca.crt")
} }
if apiserverEnableHTTPBasicAuth {
apiserverArgs = append(apiserverArgs, "--basic-auth-file=/etc/federation/apiserver/basicauth.csv")
}
if apiserverEnableTokenAuth {
apiserverArgs = append(apiserverArgs, "--token-auth-file=/etc/federation/apiserver/token.csv")
}
sort.Strings(apiserverArgs) sort.Strings(apiserverArgs)
apiserverCommand = append(apiserverCommand, apiserverArgs...) apiserverCommand = append(apiserverCommand, apiserverArgs...)
@ -1018,7 +1047,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return nil, err return nil, err
} }
if !apiequality.Semantic.DeepEqual(got, namespace) { if !apiequality.Semantic.DeepEqual(got, namespace) {
return nil, fmt.Errorf("Unexpected namespace object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, namespace)) return nil, fmt.Errorf("unexpected namespace object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, namespace))
} }
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &namespace)}, nil return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &namespace)}, nil
case p == svcUrlPrefix && m == http.MethodPost: case p == svcUrlPrefix && m == http.MethodPost:
@ -1032,7 +1061,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return nil, err return nil, err
} }
if !apiequality.Semantic.DeepEqual(got, svc) { if !apiequality.Semantic.DeepEqual(got, svc) {
return nil, fmt.Errorf("Unexpected service object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, svc)) return nil, fmt.Errorf("unexpected service object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, svc))
} }
if apiserverServiceType == v1.ServiceTypeNodePort { if apiserverServiceType == v1.ServiceTypeNodePort {
svc.Spec.Type = v1.ServiceTypeNodePort svc.Spec.Type = v1.ServiceTypeNodePort
@ -1055,21 +1084,36 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Obtained secret contains generated data which cannot
// be compared, so we just nullify the generated part
// and compare the rest of the secret. The generated
// parts are tested in other tests.
got.Data = nil
switch got.Name { switch got.Name {
case credSecretName: case credSecretName:
want = credSecret want = credSecret
if apiserverEnableHTTPBasicAuth {
if got.Data["basicauth.csv"] == nil {
return nil, fmt.Errorf("expected secret data key 'basicauth.csv', but got nil")
}
} else {
if got.Data["basicauth.csv"] != nil {
return nil, fmt.Errorf("unexpected secret data key 'basicauth.csv'")
}
}
if apiserverEnableTokenAuth {
if got.Data["token.csv"] == nil {
return nil, fmt.Errorf("expected secret data key 'token.csv', but got nil")
}
} else {
if got.Data["token.csv"] != nil {
return nil, fmt.Errorf("unexpected secret data key 'token.csv'")
}
}
case cmKubeconfigSecretName: case cmKubeconfigSecretName:
want = cmKubeconfigSecret want = cmKubeconfigSecret
case dnsProviderSecretName: case dnsProviderSecretName:
want = cmDNSProviderSecret want = cmDNSProviderSecret
} }
got.Data = nil
if !apiequality.Semantic.DeepEqual(got, want) { if !apiequality.Semantic.DeepEqual(got, want) {
return nil, fmt.Errorf("Unexpected secret object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want)) return nil, fmt.Errorf("unexpected secret object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want))
} }
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &want)}, nil return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &want)}, nil
case p == "/api/v1/namespaces/federation-system/persistentvolumeclaims" && m == http.MethodPost: case p == "/api/v1/namespaces/federation-system/persistentvolumeclaims" && m == http.MethodPost:
@ -1083,7 +1127,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return nil, err return nil, err
} }
if !apiequality.Semantic.DeepEqual(got, pvc) { if !apiequality.Semantic.DeepEqual(got, pvc) {
return nil, fmt.Errorf("Unexpected PVC object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, pvc)) return nil, fmt.Errorf("unexpected PVC object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, pvc))
} }
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &pvc)}, nil return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &pvc)}, nil
case p == "/apis/extensions/v1beta1/namespaces/federation-system/deployments" && m == http.MethodPost: case p == "/apis/extensions/v1beta1/namespaces/federation-system/deployments" && m == http.MethodPost:
@ -1103,7 +1147,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
want = *cm want = *cm
} }
if !apiequality.Semantic.DeepEqual(got, want) { if !apiequality.Semantic.DeepEqual(got, want) {
return nil, fmt.Errorf("Unexpected deployment object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want)) return nil, fmt.Errorf("unexpected deployment object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want))
} }
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(extCodec, &want)}, nil return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(extCodec, &want)}, nil
case p == "/api/v1/namespaces/federation-system/pods" && m == http.MethodGet: case p == "/api/v1/namespaces/federation-system/pods" && m == http.MethodGet:
@ -1119,7 +1163,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return nil, err return nil, err
} }
if !api.Semantic.DeepEqual(got, sa) { if !api.Semantic.DeepEqual(got, sa) {
return nil, fmt.Errorf("Unexpected service account object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, sa)) return nil, fmt.Errorf("unexpected service account object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, sa))
} }
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &sa)}, nil return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &sa)}, nil
case p == "/apis/rbac.authorization.k8s.io/v1beta1/namespaces/federation-system/roles" && m == http.MethodPost: case p == "/apis/rbac.authorization.k8s.io/v1beta1/namespaces/federation-system/roles" && m == http.MethodPost:
@ -1133,7 +1177,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return nil, err return nil, err
} }
if !api.Semantic.DeepEqual(got, role) { if !api.Semantic.DeepEqual(got, role) {
return nil, fmt.Errorf("Unexpected role object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, role)) return nil, fmt.Errorf("unexpected role object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, role))
} }
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &role)}, nil return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &role)}, nil
case p == "/apis/rbac.authorization.k8s.io/v1beta1/namespaces/federation-system/rolebindings" && m == http.MethodPost: case p == "/apis/rbac.authorization.k8s.io/v1beta1/namespaces/federation-system/rolebindings" && m == http.MethodPost:
@ -1147,7 +1191,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return nil, err return nil, err
} }
if !api.Semantic.DeepEqual(got, rolebinding) { if !api.Semantic.DeepEqual(got, rolebinding) {
return nil, fmt.Errorf("Unexpected rolebinding object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, rolebinding)) return nil, fmt.Errorf("unexpected rolebinding object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, rolebinding))
} }
return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &rolebinding)}, nil return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(rbacCodec, &rolebinding)}, nil
case p == "/api/v1/nodes" && m == http.MethodGet: case p == "/api/v1/nodes" && m == http.MethodGet:
@ -1160,7 +1204,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return f, nil return f, nil
} }
func testKubeconfigUpdate(t *testing.T, apiserverServiceType v1.ServiceType, federationName, advertiseAddress, lbIP, kubeconfigGlobal, kubeconfigExplicit string) { func testKubeconfigUpdate(t *testing.T, apiserverServiceType v1.ServiceType, federationName, advertiseAddress, lbIP, kubeconfigGlobal, kubeconfigExplicit string, apiserverEnableHTTPBasicAuth, apiserverEnableTokenAuth bool) {
filename := kubeconfigGlobal filename := kubeconfigGlobal
if kubeconfigExplicit != "" { if kubeconfigExplicit != "" {
filename = kubeconfigExplicit filename = kubeconfigExplicit
@ -1197,8 +1241,30 @@ func testKubeconfigUpdate(t *testing.T, apiserverServiceType v1.ServiceType, fed
t.Errorf("Expected client key to be non-empty") t.Errorf("Expected client key to be non-empty")
return return
} }
if authInfo.Username != AdminCN { if !apiserverEnableTokenAuth && len(authInfo.Token) != 0 {
t.Errorf("Want username: %q, got: %q", AdminCN, authInfo.Username) t.Errorf("Expected token to be empty: got: %s", authInfo.Token)
}
if apiserverEnableTokenAuth && len(authInfo.Token) == 0 {
t.Errorf("Expected token to be non-empty")
}
httpBasicAuthInfo, ok := config.AuthInfos[fmt.Sprintf("%s-basic-auth", federationName)]
if !apiserverEnableHTTPBasicAuth && ok {
t.Errorf("Expected basic auth AuthInfo entry not to exist: got %v", httpBasicAuthInfo)
return
}
if apiserverEnableHTTPBasicAuth {
if !ok {
t.Errorf("Expected basic auth AuthInfo entry to exist")
return
}
if httpBasicAuthInfo.Username != "admin" {
t.Errorf("Unexpected username in basic auth AuthInfo entry: got %s, want admin", httpBasicAuthInfo.Username)
}
if len(httpBasicAuthInfo.Password) == 0 {
t.Errorf("Expected basic auth AuthInfo entry to contain password")
}
} }
context, ok := config.Contexts[federationName] context, ok := config.Contexts[federationName]

View File

@ -31,6 +31,11 @@ api-servers
api-server-service-type api-server-service-type
api-token api-token
api-version api-version
apiserver-arg-overrides
apiserver-count
apiserver-count
apiserver-enable-basic-auth
apiserver-enable-token-auth
attach-detach-reconcile-sync-period attach-detach-reconcile-sync-period
audit-log-maxage audit-log-maxage
audit-log-maxbackup audit-log-maxbackup

View File

@ -21,7 +21,6 @@ import (
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e/framework"
fedframework "k8s.io/kubernetes/test/e2e_federation/framework" fedframework "k8s.io/kubernetes/test/e2e_federation/framework"
@ -30,6 +29,8 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
// TODO: These tests should be integration tests rather than e2e tests, when the
// integration test harness is ready.
var _ = framework.KubeDescribe("[Feature:Federation]", func() { var _ = framework.KubeDescribe("[Feature:Federation]", func() {
f := fedframework.NewDefaultFederatedFramework("federation-apiserver-authn") f := fedframework.NewDefaultFederatedFramework("federation-apiserver-authn")
@ -38,72 +39,163 @@ var _ = framework.KubeDescribe("[Feature:Federation]", func() {
fedframework.SkipUnlessFederated(f.ClientSet) fedframework.SkipUnlessFederated(f.ClientSet)
}) })
It("should accept cluster resources when the client has right authentication credentials", func() { It("should accept cluster resources when the client has certificate authentication credentials", func() {
fedframework.SkipUnlessFederated(f.ClientSet) fcs, err := federationClientSetWithCert()
framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name nsName := f.FederationNamespace.Name
svc := createServiceOrFail(f.FederationClientset, nsName, FederatedServiceName) svc := createServiceOrFail(fcs, nsName, FederatedServiceName)
deleteServiceOrFail(f.FederationClientset, nsName, svc.Name, nil) deleteServiceOrFail(f.FederationClientset, nsName, svc.Name, nil)
}) })
It("should not accept cluster resources when the client has invalid authentication credentials", func() { It("should accept cluster resources when the client has HTTP Basic authentication credentials", func() {
fedframework.SkipUnlessFederated(f.ClientSet) fcs, err := federationClientSetWithBasicAuth(true /* valid */)
contexts := f.GetUnderlyingFederatedContexts()
// `contexts` is obtained by calling
// `f.GetUnderlyingFederatedContexts()`. This function in turn
// checks that the contexts it returns does not include the
// federation API server context. So `contexts` is guaranteed to
// contain only the underlying Kubernetes cluster contexts.
fcs, err := invalidAuthFederationClientSet(contexts[0].User)
framework.ExpectNoError(err) framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name nsName := f.FederationNamespace.Name
svc, err := createService(fcs, nsName, FederatedServiceName) svc, err := createService(fcs, nsName, FederatedServiceName)
Expect(errors.IsUnauthorized(err)).To(BeTrue()) Expect(err).NotTo(HaveOccurred())
if err == nil && svc != nil { deleteServiceOrFail(fcs, nsName, svc.Name, nil)
deleteServiceOrFail(fcs, nsName, svc.Name, nil) })
}
It("should accept cluster resources when the client has token authentication credentials", func() {
fcs, err := federationClientSetWithToken(true /* valid */)
framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name
svc, err := createService(fcs, nsName, FederatedServiceName)
Expect(err).NotTo(HaveOccurred())
deleteServiceOrFail(fcs, nsName, svc.Name, nil)
}) })
It("should not accept cluster resources when the client has no authentication credentials", func() { It("should not accept cluster resources when the client has no authentication credentials", func() {
fedframework.SkipUnlessFederated(f.ClientSet) fcs, err := unauthenticatedFederationClientSet()
fcs, err := invalidAuthFederationClientSet(nil)
framework.ExpectNoError(err) framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name nsName := f.FederationNamespace.Name
svc, err := createService(fcs, nsName, FederatedServiceName) _, err = createService(fcs, nsName, FederatedServiceName)
Expect(errors.IsUnauthorized(err)).To(BeTrue()) Expect(errors.IsUnauthorized(err)).To(BeTrue())
if err == nil && svc != nil {
deleteServiceOrFail(fcs, nsName, svc.Name, nil)
}
}) })
// TODO: Add a test for invalid certificate credentials. The certificate is validated for
// correct format, so it cannot contain random noise.
It("should not accept cluster resources when the client has invalid HTTP Basic authentication credentials", func() {
fcs, err := federationClientSetWithBasicAuth(false /* invalid */)
framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name
_, err = createService(fcs, nsName, FederatedServiceName)
Expect(errors.IsUnauthorized(err)).To(BeTrue())
})
It("should not accept cluster resources when the client has invalid token authentication credentials", func() {
fcs, err := federationClientSetWithToken(false /* invalid */)
framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name
_, err = createService(fcs, nsName, FederatedServiceName)
Expect(errors.IsUnauthorized(err)).To(BeTrue())
})
}) })
}) })
func invalidAuthFederationClientSet(user *framework.KubeUser) (*federation_clientset.Clientset, error) { // unauthenticatedFederationClientSet returns a Federation Clientset configured with
overrides := &clientcmd.ConfigOverrides{} // no authentication credentials.
if user != nil { func unauthenticatedFederationClientSet() (*federation_clientset.Clientset, error) {
overrides = &clientcmd.ConfigOverrides{ config, err := fedframework.LoadFederatedConfig(&clientcmd.ConfigOverrides{})
AuthInfo: clientcmdapi.AuthInfo{ if err != nil {
Token: user.User.Token, return nil, err
Username: user.User.Username, }
Password: user.User.Password, config.Insecure = true
}, config.CAData = []byte{}
} config.CertData = []byte{}
config.KeyData = []byte{}
config.BearerToken = ""
c, err := federation_clientset.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating federation clientset: %v", err)
} }
config, err := fedframework.LoadFederatedConfig(overrides) return c, nil
}
// federationClientSetWithCert returns a Federation Clientset configured with
// certificate authentication credentials.
func federationClientSetWithCert() (*federation_clientset.Clientset, error) {
config, err := fedframework.LoadFederatedConfig(&clientcmd.ConfigOverrides{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if user == nil { config.BearerToken = ""
config.Password = ""
config.BearerToken = "" c, err := federation_clientset.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating federation clientset: %v", err)
}
return c, nil
}
// federationClientSetWithBasicAuth returns a Federation Clientset configured with
// HTTP Basic authentication credentials.
func federationClientSetWithBasicAuth(valid bool) (*federation_clientset.Clientset, error) {
config, err := fedframework.LoadFederatedConfig(&clientcmd.ConfigOverrides{})
if err != nil {
return nil, err
}
config.Insecure = true
config.CAData = []byte{}
config.CertData = []byte{}
config.KeyData = []byte{}
config.BearerToken = ""
if !valid {
config.Username = "" config.Username = ""
config.Password = ""
} else {
// This is a hacky approach to getting the basic auth credentials, but since
// the token and the username/password cannot live in the same AuthInfo object,
// and because we do not want to store basic auth credentials with token and
// certificate credentials for security reasons, we must dig it out by hand.
c, err := framework.RestclientConfig(framework.TestContext.FederatedKubeContext)
if err != nil {
return nil, err
}
if authInfo, ok := c.AuthInfos[fmt.Sprintf("%s-basic-auth", framework.TestContext.FederatedKubeContext)]; ok {
config.Username = authInfo.Username
config.Password = authInfo.Password
}
}
c, err := federation_clientset.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("error creating federation clientset: %v", err)
}
return c, nil
}
// federationClientSetWithToken returns a Federation Clientset configured with
// token authentication credentials.
func federationClientSetWithToken(valid bool) (*federation_clientset.Clientset, error) {
config, err := fedframework.LoadFederatedConfig(&clientcmd.ConfigOverrides{})
if err != nil {
return nil, err
}
config.Insecure = true
config.CAData = []byte{}
config.CertData = []byte{}
config.KeyData = []byte{}
config.Username = ""
config.Password = ""
if !valid {
config.BearerToken = "invalid"
} }
c, err := federation_clientset.NewForConfig(config) c, err := federation_clientset.NewForConfig(config)