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.
```
pull/6/head
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

@ -79,21 +79,23 @@ func TestInitFederation(t *testing.T) {
defer kubefedtesting.RemoveFakeKubeconfigFiles(fakeKubeFiles)
testCases := []struct {
federation string
kubeconfigGlobal string
kubeconfigExplicit string
dnsZoneName string
lbIP string
apiserverServiceType v1.ServiceType
advertiseAddress string
image string
etcdPVCapacity string
etcdPersistence string
expectedErr string
dnsProviderConfig string
dryRun string
apiserverArgOverrides string
cmArgOverrides string
federation string
kubeconfigGlobal string
kubeconfigExplicit string
dnsZoneName string
lbIP string
apiserverServiceType v1.ServiceType
advertiseAddress string
image string
etcdPVCapacity string
etcdPersistence string
expectedErr string
dnsProviderConfig string
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 {
deleteServiceOrFail(fcs, nsName, svc.Name, 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 = ""
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)