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-provider="${DNS_PROVIDER}" \
--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

View File

@ -27,6 +27,7 @@ go_library(
"//vendor:github.com/spf13/pflag",
"//vendor:k8s.io/apimachinery/pkg/api/resource",
"//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/client-go/tools/clientcmd",
"//vendor:k8s.io/client-go/tools/clientcmd/api",

View File

@ -43,6 +43,7 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@ -148,6 +149,8 @@ type initFederationOptions struct {
apiServerServiceTypeString string
apiServerServiceType v1.ServiceType
apiServerAdvertiseAddress string
apiServerEnableHTTPBasicAuth bool
apiServerEnableTokenAuth bool
}
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.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.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
@ -196,6 +201,13 @@ type entityKeyPairs struct {
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.
func (i *initFederation) Complete(cmd *cobra.Command, args []string) error {
if len(i.options.dnsProvider) == 0 {
@ -274,19 +286,20 @@ func (i *initFederation) Run(cmdOut io.Writer, config util.AdminConfig) error {
return err
}
// 3. Generate TLS certificates and credentials
entKeyPairs, err := genCerts(i.commonOptions.FederationSystemNamespace, i.commonOptions.Name, svc.Name, HostClusterLocalDNSZoneName, ips, hostnames)
// 3a. Generate TLS certificates and credentials, and other credentials if needed
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 {
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 {
return err
}
// 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 {
return err
}
@ -311,7 +324,7 @@ func (i *initFederation) Run(cmdOut io.Writer, config util.AdminConfig) error {
}
// 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 {
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
// 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 {
return err
}
@ -498,6 +511,25 @@ func waitForLoadBalancerAddress(clientset client.Interface, svc *api.Service, dr
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) {
ca, err := triple.NewCA(name)
if err != nil {
@ -523,18 +555,26 @@ func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnam
}, 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.
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{
ObjectMeta: metav1.ObjectMeta{
Name: credentialsName,
Namespace: namespace,
},
Data: map[string][]byte{
"ca.crt": certutil.EncodeCertPEM(entKeyPairs.ca.Cert),
"server.crt": certutil.EncodeCertPEM(entKeyPairs.server.Cert),
"server.key": certutil.EncodePrivateKeyPEM(entKeyPairs.server.Key),
},
Data: data,
}
if dryRun {
@ -591,7 +631,7 @@ func createPVC(clientset client.Interface, namespace, svcName, etcdPVCapacity st
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{
"/hyperkube",
"federation-apiserver",
@ -609,6 +649,12 @@ func createAPIServer(clientset client.Interface, namespace, name, image, credent
if 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)
command = append(command, args...)
@ -936,7 +982,7 @@ func printSuccess(cmdOut io.Writer, ips, hostnames []string, svc *api.Service) e
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.LoadingRules.ExplicitPath = kubeConfigPath
kubeconfig, err := po.GetStartingConfig()
@ -951,13 +997,20 @@ func updateKubeconfig(config util.AdminConfig, name, endpoint, kubeConfigPath st
endpoint = fmt.Sprintf("https://%s", endpoint)
}
cluster.Server = endpoint
cluster.CertificateAuthorityData = certutil.EncodeCertPEM(entKeyPairs.ca.Cert)
cluster.CertificateAuthorityData = certutil.EncodeCertPEM(credentials.certEntKeyPairs.ca.Cert)
// Populate credentials.
authInfo := clientcmdapi.NewAuthInfo()
authInfo.ClientCertificateData = certutil.EncodeCertPEM(entKeyPairs.admin.Cert)
authInfo.ClientKeyData = certutil.EncodePrivateKeyPEM(entKeyPairs.admin.Key)
authInfo.Username = AdminCN
authInfo.ClientCertificateData = certutil.EncodeCertPEM(credentials.certEntKeyPairs.admin.Cert)
authInfo.ClientKeyData = certutil.EncodePrivateKeyPEM(credentials.certEntKeyPairs.admin.Key)
authInfo.Token = credentials.token
var httpBasicAuthInfo *clientcmdapi.AuthInfo
if credentials.password != "" {
httpBasicAuthInfo = clientcmdapi.NewAuthInfo()
httpBasicAuthInfo.Password = credentials.password
httpBasicAuthInfo.Username = credentials.username
}
// Populate context.
context := clientcmdapi.NewContext()
@ -968,6 +1021,9 @@ func updateKubeconfig(config util.AdminConfig, name, endpoint, kubeConfigPath st
// credentials and context.
kubeconfig.Clusters[name] = cluster
kubeconfig.AuthInfos[name] = authInfo
if httpBasicAuthInfo != nil {
kubeconfig.AuthInfos[fmt.Sprintf("%s-basic-auth", name)] = httpBasicAuthInfo
}
kubeconfig.Contexts[name] = context
if !dryRun {
@ -1034,3 +1090,9 @@ func addDNSProviderConfig(dep *extensions.Deployment, secretName string) *extens
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

@ -94,6 +94,8 @@ func TestInitFederation(t *testing.T) {
dryRun string
apiserverArgOverrides string
cmArgOverrides string
apiserverEnableHTTPBasicAuth bool
apiserverEnableTokenAuth bool
}{
{
federation: "union",
@ -175,6 +177,21 @@ func TestInitFederation(t *testing.T) {
expectedErr: "",
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
@ -191,7 +208,7 @@ func TestInitFederation(t *testing.T) {
tc.dnsProviderConfig = 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 {
t.Fatalf("[%d] unexpected error: %v", i, err)
}
@ -227,6 +244,12 @@ func TestInitFederation(t *testing.T) {
if tc.dryRun == "valid-run" {
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})
@ -253,7 +276,7 @@ func TestInitFederation(t *testing.T) {
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"
svcUrlPrefix := "/api/v1/namespaces/federation-system/services"
credSecretName := svcName + "-credentials"
@ -782,6 +805,12 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
} else {
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)
apiserverCommand = append(apiserverCommand, apiserverArgs...)
@ -1018,7 +1047,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return nil, err
}
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
case p == svcUrlPrefix && m == http.MethodPost:
@ -1032,7 +1061,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
return nil, err
}
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 {
svc.Spec.Type = v1.ServiceTypeNodePort
@ -1055,21 +1084,36 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
if err != nil {
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 {
case credSecretName:
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:
want = cmKubeconfigSecret
case dnsProviderSecretName:
want = cmDNSProviderSecret
}
got.Data = nil
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
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
}
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
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
}
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
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
}
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
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
}
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
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
}
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
case p == "/api/v1/nodes" && m == http.MethodGet:
@ -1160,7 +1204,7 @@ func fakeInitHostFactory(apiserverServiceType v1.ServiceType, federationName, na
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
if 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")
return
}
if authInfo.Username != AdminCN {
t.Errorf("Want username: %q, got: %q", AdminCN, authInfo.Username)
if !apiserverEnableTokenAuth && len(authInfo.Token) != 0 {
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]

View File

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

View File

@ -21,7 +21,6 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"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/test/e2e/framework"
fedframework "k8s.io/kubernetes/test/e2e_federation/framework"
@ -30,6 +29,8 @@ import (
. "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() {
f := fedframework.NewDefaultFederatedFramework("federation-apiserver-authn")
@ -38,72 +39,163 @@ var _ = framework.KubeDescribe("[Feature:Federation]", func() {
fedframework.SkipUnlessFederated(f.ClientSet)
})
It("should accept cluster resources when the client has right authentication credentials", func() {
fedframework.SkipUnlessFederated(f.ClientSet)
It("should accept cluster resources when the client has certificate authentication credentials", func() {
fcs, err := federationClientSetWithCert()
framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name
svc := createServiceOrFail(f.FederationClientset, nsName, FederatedServiceName)
svc := createServiceOrFail(fcs, nsName, FederatedServiceName)
deleteServiceOrFail(f.FederationClientset, nsName, svc.Name, nil)
})
It("should not accept cluster resources when the client has invalid authentication credentials", func() {
fedframework.SkipUnlessFederated(f.ClientSet)
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)
It("should accept cluster resources when the client has HTTP Basic authentication credentials", func() {
fcs, err := federationClientSetWithBasicAuth(true /* valid */)
framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name
svc, err := createService(fcs, nsName, FederatedServiceName)
Expect(errors.IsUnauthorized(err)).To(BeTrue())
if err == nil && svc != nil {
Expect(err).NotTo(HaveOccurred())
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() {
fedframework.SkipUnlessFederated(f.ClientSet)
fcs, err := invalidAuthFederationClientSet(nil)
fcs, err := unauthenticatedFederationClientSet()
framework.ExpectNoError(err)
nsName := f.FederationNamespace.Name
svc, err := createService(fcs, nsName, FederatedServiceName)
_, err = createService(fcs, nsName, FederatedServiceName)
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) {
overrides := &clientcmd.ConfigOverrides{}
if user != nil {
overrides = &clientcmd.ConfigOverrides{
AuthInfo: clientcmdapi.AuthInfo{
Token: user.User.Token,
Username: user.User.Username,
Password: user.User.Password,
},
// unauthenticatedFederationClientSet returns a Federation Clientset configured with
// no authentication credentials.
func unauthenticatedFederationClientSet() (*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 = ""
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 {
return nil, err
}
if user == nil {
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.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)