From a75e318c1cb3745b2152280fa89fab6ba417a6ec Mon Sep 17 00:00:00 2001 From: Craig Willis Date: Fri, 16 Sep 2016 16:17:08 -0600 Subject: [PATCH] NDS-540 / NDS-483: LMA basic auth (#126) * Create basic-auth using admin password for kube-system namespace, if present * Fixed problem with admin account creation order * Allow ability to create basic auth for user other than namespace * NDS-483: Update ingress rule when password changes to trigger refresh in ingress controller --- apiserver/cmd/server/server.go | 88 ++++++++++++++++++---- apiserver/pkg/kube/kube.go | 129 ++++++++++++++++++++++----------- 2 files changed, 161 insertions(+), 56 deletions(-) diff --git a/apiserver/cmd/server/server.go b/apiserver/cmd/server/server.go index c9be68d2..5aba4dd8 100644 --- a/apiserver/cmd/server/server.go +++ b/apiserver/cmd/server/server.go @@ -30,6 +30,9 @@ import ( "github.com/golang/glog" ) +var adminUser = "admin" +var systemNamespace = "kube-system" + type Server struct { etcd *etcd.EtcdHelper kube *kube.KubeHelper @@ -243,7 +246,7 @@ func (s *Server) start(cfg Config, adminPasswd string) { Timeout: timeout, MaxRefresh: time.Hour * 24, Authenticator: func(userId string, password string) bool { - if userId == "admin" && password == adminPasswd { + if userId == adminUser && password == adminPasswd { return true } else { return s.etcd.CheckPassword(userId, password) && s.etcd.CheckAccess(userId) @@ -260,8 +263,8 @@ func (s *Server) start(cfg Config, adminPasswd string) { }, PayloadFunc: func(userId string) map[string]interface{} { payload := make(map[string]interface{}) - if userId == "admin" { - payload["admin"] = true + if userId == adminUser { + payload[adminUser] = true } payload["server"] = s.hostname payload["user"] = userId @@ -488,7 +491,7 @@ func (s *Server) GetAllAccounts(w rest.ResponseWriter, r *rest.Request) { func (s *Server) getUser(r *rest.Request) string { payload := r.Env["JWT_PAYLOAD"].(map[string]interface{}) - if payload["admin"] == true { + if payload[adminUser] == true { return "" } else { return payload["user"].(string) @@ -497,7 +500,7 @@ func (s *Server) getUser(r *rest.Request) string { func (s *Server) IsAdmin(r *rest.Request) bool { payload := r.Env["JWT_PAYLOAD"].(map[string]interface{}) - if payload["admin"] == true { + if payload[adminUser] == true { return true } else { return false @@ -589,7 +592,57 @@ func (s *Server) createBasicAuthSecret(uid string) error { return err } - _, err = s.kube.CreateBasicAuthSecret(account.Namespace, account.Password) + _, err = s.kube.CreateBasicAuthSecret(account.Namespace, account.Namespace, account.Password) + if err != nil { + glog.Error(err) + return err + } + + err = s.updateIngress(uid) + if err != nil { + glog.Error(err) + return err + } + return nil +} + +func (s *Server) updateIngress(uid string) error { + + ingresses, err := s.kube.GetIngresses(uid) + if err != nil { + glog.Error(err) + return err + } + if ingresses != nil { + for _, ingress := range ingresses { + glog.V(4).Infof("Touching ingress %s\n", ingress.Name) + _, err = s.kube.CreateUpdateIngress(uid, &ingress, true) + if err != nil { + glog.Error(err) + return err + } + } + } + + return nil +} + +func (s *Server) createLMABasicAuthSecret() error { + if s.kube.NamespaceExists(systemNamespace) { + account, err := s.etcd.GetAccount(adminUser) + if err != nil { + glog.Error(err) + return err + } + + _, err = s.kube.CreateBasicAuthSecret(systemNamespace, adminUser, account.Password) + if err != nil { + glog.Error(err) + return err + } + } + + err := s.updateIngress(systemNamespace) if err != nil { glog.Error(err) return err @@ -2274,7 +2327,7 @@ func (s *Server) loadSpecs(path string) error { func (s *Server) HandlePodEvent(eventType watch.EventType, event *k8api.Event, pod *k8api.Pod) { - if pod.Namespace != "default" && pod.Namespace != "kube-system" { + if pod.Namespace != "default" && pod.Namespace != systemNamespace { glog.V(4).Infof("HandlePodEvent %s", eventType) //name := pod.Name @@ -2369,7 +2422,7 @@ func (s *Server) HandlePodEvent(eventType watch.EventType, event *k8api.Event, p func (s *Server) HandleReplicationControllerEvent(eventType watch.EventType, event *k8api.Event, rc *k8api.ReplicationController) { - if rc.Namespace != "default" && rc.Namespace != "kube-system" { + if rc.Namespace != "default" && rc.Namespace != systemNamespace { glog.V(4).Infof("HandleReplicationControllerEvent %s", eventType) userId := rc.Namespace @@ -2522,10 +2575,10 @@ func (s *Server) createAdminUser(password string) error { glog.V(4).Infof("Creating admin user") - if !s.accountExists("admin") { + if !s.accountExists(adminUser) { account := &api.Account{ - Name: "admin", - Namespace: "admin", + Name: adminUser, + Namespace: adminUser, Description: "NDS Labs administrator", Password: password, ResourceLimits: api.AccountResourceLimits{ @@ -2535,30 +2588,35 @@ func (s *Server) createAdminUser(password string) error { MemoryDefault: s.memDefault, }, } - err := s.setupAccount(account) + err := s.etcd.PutAccount(adminUser, account, true) if err != nil { glog.Error(err) return err } - err = s.etcd.PutAccount("admin", account, true) + err = s.setupAccount(account) if err != nil { glog.Error(err) return err } } else { - account, err := s.etcd.GetAccount("admin") + account, err := s.etcd.GetAccount(adminUser) if err != nil { glog.Error(err) return err } account.Password = password - err = s.etcd.PutAccount("admin", account, true) + err = s.etcd.PutAccount(adminUser, account, true) if err != nil { glog.Error(err) return err } } + err := s.createLMABasicAuthSecret() + if err != nil { + glog.Error(err) + return err + } return nil } diff --git a/apiserver/pkg/kube/kube.go b/apiserver/pkg/kube/kube.go index 04fc2a92..ee670299 100644 --- a/apiserver/pkg/kube/kube.go +++ b/apiserver/pkg/kube/kube.go @@ -1148,45 +1148,48 @@ func (k *KubeHelper) Exec(pid string, pod string, container string, kube *KubeHe func (k *KubeHelper) CreateIngress(pid string, host string, service string, port int, tlsSecretName string, basicAuth bool) (*extensions.Ingress, error) { name := service + "-ingress" + update := true - // Delete existing ingress ingress, err := k.GetIngress(pid, name) - if ingress != nil { - k.DeleteIngress(pid, name) + if err == nil { + return nil, err } + if ingress == nil { + update = false - // https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx/examples/auth - annotations := map[string]string{} - if basicAuth { - annotations["ingress.kubernetes.io/auth-type"] = "basic" - annotations["ingress.kubernetes.io/auth-secret"] = "basic-auth" - annotations["ingress.kubernetes.io/auth-realm"] = "NDS Labs" - } + // https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx/examples/auth + annotations := map[string]string{} + if basicAuth { + annotations["ingress.kubernetes.io/auth-type"] = "basic" + annotations["ingress.kubernetes.io/auth-secret"] = "basic-auth" + annotations["ingress.kubernetes.io/auth-realm"] = "NDS Labs" + } - ingress = &extensions.Ingress{ - ObjectMeta: api.ObjectMeta{ - Name: name, - Namespace: pid, - Annotations: annotations, - }, - Spec: extensions.IngressSpec{ - TLS: []extensions.IngressTLS{ - extensions.IngressTLS{ - Hosts: []string{host}, - SecretName: tlsSecretName, - }, + ingress = &extensions.Ingress{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: pid, + Annotations: annotations, }, - Rules: []extensions.IngressRule{ - extensions.IngressRule{ - Host: host, - IngressRuleValue: extensions.IngressRuleValue{ - HTTP: &extensions.HTTPIngressRuleValue{ - Paths: []extensions.HTTPIngressPath{ - extensions.HTTPIngressPath{ - Path: "/", - Backend: extensions.IngressBackend{ - ServiceName: service, - ServicePort: intstr.FromInt(port), + Spec: extensions.IngressSpec{ + TLS: []extensions.IngressTLS{ + extensions.IngressTLS{ + Hosts: []string{host}, + SecretName: tlsSecretName, + }, + }, + Rules: []extensions.IngressRule{ + extensions.IngressRule{ + Host: host, + IngressRuleValue: extensions.IngressRuleValue{ + HTTP: &extensions.HTTPIngressRuleValue{ + Paths: []extensions.HTTPIngressPath{ + extensions.HTTPIngressPath{ + Path: "/", + Backend: extensions.IngressBackend{ + ServiceName: service, + ServicePort: intstr.FromInt(port), + }, }, }, }, @@ -1194,17 +1197,27 @@ func (k *KubeHelper) CreateIngress(pid string, host string, service string, port }, }, }, - }, + } } + return k.CreateUpdateIngress(pid, ingress, update) +} + +func (k *KubeHelper) CreateUpdateIngress(pid string, ingress *extensions.Ingress, update bool) (*extensions.Ingress, error) { + ingress.ObjectMeta.Annotations["ndslabs.org/updated"] = time.Now().String() data, err := json.Marshal(ingress) if err != nil { return nil, err } - url := k.kubeBase + extBase + "/namespaces/" + pid + "/ingresses/" + url := k.kubeBase + extBase + "/namespaces/" + pid + "/ingresses" + method := "POST" + if update { + method = "PUT" + url += "/" + ingress.Name + } glog.V(4).Infoln(url) - request, _ := http.NewRequest("POST", url, bytes.NewBuffer(data)) + request, _ := http.NewRequest(method, url, bytes.NewBuffer(data)) request.Header.Set("Content-Type", "application/json") request.Header.Set("Authorization", k.getAuthHeader()) httpresp, httperr := k.client.Do(request) @@ -1212,8 +1225,8 @@ func (k *KubeHelper) CreateIngress(pid string, host string, service string, port glog.Error(httperr) return nil, httperr } else { - if httpresp.StatusCode == http.StatusCreated { - glog.V(2).Infof("Added ingress %s-%s\n", pid, service) + if httpresp.StatusCode == http.StatusCreated || httpresp.StatusCode == http.StatusOK { + glog.V(2).Infof("Added/updated ingress %s\n", ingress.Name) data, err := ioutil.ReadAll(httpresp.Body) if err != nil { return nil, err @@ -1224,7 +1237,7 @@ func (k *KubeHelper) CreateIngress(pid string, host string, service string, port } else if httpresp.StatusCode == http.StatusConflict { return nil, fmt.Errorf("Ingress exists for namespace %s: %s\n", pid, httpresp.Status) } else { - return nil, fmt.Errorf("Error adding ingress for namespace %s: %s\n", pid, httpresp.Status) + return nil, fmt.Errorf("Error adding/updating ingress for namespace %s: %s\n", pid, httpresp.Status) } } return nil, nil @@ -1258,6 +1271,40 @@ func (k *KubeHelper) GetIngress(pid string, ingressName string) (*extensions.Ing return nil, nil } +func (k *KubeHelper) GetIngresses(pid string) ([]extensions.Ingress, error) { + + url := k.kubeBase + extBase + "/namespaces/" + pid + "/ingresses" + glog.V(4).Infoln(url) + request, _ := http.NewRequest("GET", url, nil) + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Authorization", k.getAuthHeader()) + httpresp, httperr := k.client.Do(request) + if httperr != nil { + glog.Error(httperr) + return nil, httperr + } else { + if httpresp.StatusCode == http.StatusOK { + data, err := ioutil.ReadAll(httpresp.Body) + if err != nil { + return nil, err + } + + ingressList := extensions.IngressList{} + json.Unmarshal(data, &ingressList) + + var ingresses = []extensions.Ingress{} + for _, ingress := range ingressList.Items { + ingresses = append(ingresses, ingress) + } + return ingresses, nil + + } else { + return nil, fmt.Errorf("Error getting ingresses for account %s: %s\n", pid, httpresp.Status) + } + } + return nil, nil +} + //http://kubernetes.io/docs/api-reference/extensions/v1beta1/operations/ func (k *KubeHelper) DeleteIngress(pid string, name string) (*extensions.Ingress, error) { @@ -1288,7 +1335,7 @@ func (k *KubeHelper) DeleteIngress(pid string, name string) (*extensions.Ingress return nil, nil } -func (k *KubeHelper) CreateBasicAuthSecret(pid string, hashedPassword string) (*api.Secret, error) { +func (k *KubeHelper) CreateBasicAuthSecret(pid string, username string, hashedPassword string) (*api.Secret, error) { secret, _ := k.GetSecret(pid, "basic-auth") if secret != nil { k.DeleteSecret(pid, "basic-auth") @@ -1300,7 +1347,7 @@ func (k *KubeHelper) CreateBasicAuthSecret(pid string, hashedPassword string) (* Namespace: pid, }, Data: map[string][]byte{ - "auth": []byte(fmt.Sprintf("%s:%s", pid, string(hashedPassword))), + "auth": []byte(fmt.Sprintf("%s:%s", username, string(hashedPassword))), }, } return k.CreateSecret(pid, secret)