mirror of https://github.com/k3s-io/k3s
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
commit
4672314029
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue