diff --git a/nginx-controller/Dockerfile b/nginx-controller/Dockerfile index 811eef67fc..0a7519c238 100644 --- a/nginx-controller/Dockerfile +++ b/nginx-controller/Dockerfile @@ -9,7 +9,7 @@ COPY nginx-ingress nginx/templates/nginx.ingress.tmpl nginx/templates/nginx.tmpl RUN rm /etc/nginx/conf.d/* -RUN mkdir -p /etc/nginx/ssl -ADD default.pem /etc/nginx/ssl +RUN mkdir -p /etc/nginx/secrets +ADD default.pem /etc/nginx/secrets/default ENTRYPOINT ["/nginx-ingress"] diff --git a/nginx-controller/DockerfileForAlpine b/nginx-controller/DockerfileForAlpine index a4bd87a282..96074b5601 100644 --- a/nginx-controller/DockerfileForAlpine +++ b/nginx-controller/DockerfileForAlpine @@ -9,7 +9,7 @@ COPY nginx-ingress nginx/templates/nginx.ingress.tmpl nginx/templates/nginx.tmpl RUN rm /etc/nginx/conf.d/* -RUN mkdir -p /etc/nginx/ssl -ADD default.pem /etc/nginx/ssl +RUN mkdir -p /etc/nginx/secrets +ADD default.pem /etc/nginx/secrets/default ENTRYPOINT ["/nginx-ingress"] diff --git a/nginx-controller/DockerfileForPlus b/nginx-controller/DockerfileForPlus index 952ab8b211..6e20d1446f 100644 --- a/nginx-controller/DockerfileForPlus +++ b/nginx-controller/DockerfileForPlus @@ -28,13 +28,10 @@ EXPOSE 80 443 8080 RUN ln -sf /proc/1/fd/1 /var/log/nginx/access.log \ && ln -sf /proc/1/fd/2 /var/log/nginx/error.log -# nginx will store lists of upstream servers in this directory -RUN mkdir -p /var/lib/nginx/state && chown -R nginx:nginx /var/lib/nginx - COPY nginx-ingress nginx/templates/nginx-plus.ingress.tmpl nginx/templates/nginx-plus.tmpl / RUN rm /etc/nginx/conf.d/* -RUN mkdir -p /etc/nginx/ssl -ADD default.pem /etc/nginx/ssl +RUN mkdir -p /etc/nginx/secrets +ADD default.pem /etc/nginx/secrets/default ENTRYPOINT ["/nginx-ingress"] diff --git a/nginx-controller/controller/controller.go b/nginx-controller/controller/controller.go index 48980991ea..df5a970f0f 100644 --- a/nginx-controller/controller/controller.go +++ b/nginx-controller/controller/controller.go @@ -227,7 +227,7 @@ func NewLoadBalancerController(kubeClient kubernetes.Interface, resyncPeriod tim return } } - if err := ValidateTLSSecret(remSecr); err != nil { + if err := lbc.ValidateSecret(remSecr); err != nil { return } @@ -235,8 +235,8 @@ func NewLoadBalancerController(kubeClient kubernetes.Interface, resyncPeriod tim lbc.syncQueue.enqueue(obj) }, UpdateFunc: func(old, cur interface{}) { - errOld := ValidateTLSSecret(old.(*api_v1.Secret)) - errCur := ValidateTLSSecret(cur.(*api_v1.Secret)) + errOld := lbc.ValidateSecret(old.(*api_v1.Secret)) + errCur := lbc.ValidateSecret(cur.(*api_v1.Secret)) if errOld != nil && errCur != nil { return } @@ -671,7 +671,7 @@ func (lbc *LoadBalancerController) syncSecret(task Task) { if !secrExists { glog.V(2).Infof("Deleting Secret: %v\n", key) - if err := lbc.cnf.DeleteTLSSecret(key, ings); err != nil { + if err := lbc.cnf.DeleteSecret(key, ings); err != nil { glog.Errorf("Error when deleting Secret: %v: %v", key, err) } @@ -689,7 +689,7 @@ func (lbc *LoadBalancerController) syncSecret(task Task) { secret := obj.(*api_v1.Secret) if key == lbc.defaultServerSecret { - err := ValidateTLSSecret(secret) + err := nginx.ValidateTLSSecret(secret) if err != nil { glog.Errorf("Couldn't validate the default server Secret %v: %v", key, err) lbc.recorder.Eventf(secret, api_v1.EventTypeWarning, "Rejected", "the default server Secret %v was rejected, using the previous version: %v", key, err) @@ -706,10 +706,10 @@ func (lbc *LoadBalancerController) syncSecret(task Task) { } if len(ings) > 0 { - err := ValidateTLSSecret(secret) + err := lbc.ValidateSecret(secret) if err != nil { glog.Errorf("Couldn't validate secret %v: %v", key, err) - if err := lbc.cnf.DeleteTLSSecret(key, ings); err != nil { + if err := lbc.cnf.DeleteSecret(key, ings); err != nil { glog.Errorf("Error when deleting Secret: %v: %v", key, err) } for _, ing := range ings { @@ -720,7 +720,7 @@ func (lbc *LoadBalancerController) syncSecret(task Task) { return } - if err := lbc.cnf.AddOrUpdateTLSSecret(secret, true); err != nil { + if err := lbc.cnf.AddOrUpdateSecret(secret); err != nil { glog.Errorf("Error when updating Secret %v: %v", key, err) lbc.recorder.Eventf(secret, api_v1.EventTypeWarning, "UpdatedWithError", "%v was updated, but not applied: %v", key, err) for _, ing := range ings { @@ -742,6 +742,8 @@ func (lbc *LoadBalancerController) findIngressesForSecret(secret string) ([]exte if err != nil { return nil, fmt.Errorf("Couldn't get the list of Ingress resources: %v", err) } + +items: for _, ing := range ings.Items { if !isNginxIngress(&ing) { continue @@ -749,6 +751,14 @@ func (lbc *LoadBalancerController) findIngressesForSecret(secret string) ([]exte for _, tls := range ing.Spec.TLS { if tls.SecretName == secret { res = append(res, ing) + continue items + } + } + if lbc.nginxPlus { + if jwtKey, exists := ing.Annotations[nginx.JWTKeyAnnotation]; exists { + if jwtKey == secret { + res = append(res, ing) + } } } } @@ -795,18 +805,36 @@ func (lbc *LoadBalancerController) createIngress(ing *extensions.Ingress) (*ngin Ingress: ing, } - ingEx.Secrets = make(map[string]*api_v1.Secret) + ingEx.TLSSecrets = make(map[string]*api_v1.Secret) for _, tls := range ing.Spec.TLS { secretName := tls.SecretName secret, err := lbc.client.Core().Secrets(ing.Namespace).Get(secretName, meta_v1.GetOptions{}) if err != nil { return nil, fmt.Errorf("Error retrieving secret %v for Ingress %v: %v", secretName, ing.Name, err) } - err = ValidateTLSSecret(secret) + err = nginx.ValidateTLSSecret(secret) if err != nil { return nil, fmt.Errorf("Error validating secret %v for Ingress %v: %v", secretName, ing.Name, err) } - ingEx.Secrets[secretName] = secret + ingEx.TLSSecrets[secretName] = secret + } + + if lbc.nginxPlus { + if jwtKey, exists := ingEx.Ingress.Annotations[nginx.JWTKeyAnnotation]; exists { + secretName := jwtKey + + secret, err := lbc.client.Core().Secrets(ing.Namespace).Get(secretName, meta_v1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("Error retrieving secret %v for Ingress %v: %v", secretName, ing.Name, err) + } + + err = nginx.ValidateJWKSecret(secret) + if err != nil { + return nil, fmt.Errorf("Error validating secret %v for Ingress %v: %v", secretName, ing.Name, err) + } + + ingEx.JWTKey = secret + } } ingEx.Endpoints = make(map[string][]string) @@ -950,6 +978,8 @@ func (lbc *LoadBalancerController) getServiceForIngressBackend(backend *extensio return nil, fmt.Errorf("service %s doesn't exists", svcKey) } +// ParseNamespaceName parses the string in the / format and returns the name and the namespace. +// It returns an error in case the string does not follow the / format. func ParseNamespaceName(value string) (ns string, name string, err error) { res := strings.Split(value, "/") if len(res) != 2 { @@ -965,3 +995,20 @@ func isNginxIngress(ing *extensions.Ingress) bool { return true } + +// ValidateSecret validates that the secret follows the TLS Secret format. +// For NGINX Plus, it also checks if the secret follows the JWK Secret format. +func (lbc *LoadBalancerController) ValidateSecret(secret *api_v1.Secret) error { + err1 := nginx.ValidateTLSSecret(secret) + if !lbc.nginxPlus { + return err1 + } + + err2 := nginx.ValidateJWKSecret(secret) + + if err1 == nil || err2 == nil { + return nil + } + + return fmt.Errorf("Secret is not a TLS or JWK secret") +} diff --git a/nginx-controller/controller/secret.go b/nginx-controller/controller/secret.go deleted file mode 100644 index 5ab63f0134..0000000000 --- a/nginx-controller/controller/secret.go +++ /dev/null @@ -1,20 +0,0 @@ -package controller - -import ( - "fmt" - - api_v1 "k8s.io/client-go/pkg/api/v1" -) - -// ValidateTLSSecret validates the secret. If it is valid, the function returns nil. -func ValidateTLSSecret(secret *api_v1.Secret) error { - if _, exists := secret.Data[api_v1.TLSCertKey]; !exists { - return fmt.Errorf("Secret doesn't have %v", api_v1.TLSCertKey) - } - - if _, exists := secret.Data[api_v1.TLSPrivateKeyKey]; !exists { - return fmt.Errorf("Secret doesn't have %v", api_v1.TLSPrivateKeyKey) - } - - return nil -} diff --git a/nginx-controller/main.go b/nginx-controller/main.go index 6ea87bbbee..beb7d09982 100644 --- a/nginx-controller/main.go +++ b/nginx-controller/main.go @@ -48,7 +48,7 @@ var ( defaultServerSecret = flag.String("default-server-tls-secret", "", `Specifies a secret with a TLS certificate and key for SSL termination of the default server. The value must follow the following format: /. - If not specified, the key and the cert from /etc/nginx/default.pem is used.`) + If not specified, the key and the cert from /etc/nginx/default is used.`) ) func main() { @@ -100,13 +100,13 @@ func main() { if err != nil { glog.Fatalf("Error when getting %v: %v", *defaultServerSecret, err) } - err = controller.ValidateTLSSecret(secret) + err = nginx.ValidateTLSSecret(secret) if err != nil { glog.Fatalf("%v is invalid: %v", *defaultServerSecret, err) } bytes := nginx.GenerateCertAndKeyFileContent(secret) - ngxc.AddOrUpdatePemFile(nginx.DefaultServerPemName, bytes) + ngxc.AddOrUpdateSecretFile(nginx.DefaultServerSecretName, bytes, nginx.TLSSecretFileMode) } nginxDone := make(chan error, 1) diff --git a/nginx-controller/nginx/config.go b/nginx-controller/nginx/config.go index 03039f6768..2b359c812d 100644 --- a/nginx-controller/nginx/config.go +++ b/nginx-controller/nginx/config.go @@ -36,6 +36,11 @@ type Config struct { MainServerSSLPreferServerCiphers bool MainServerSSLCiphers string MainServerSSLDHParam string + + JWTRealm string + JWTKey string + JWTToken string + JWTLoginURL string } // NewDefaultConfig creates a Config with default values diff --git a/nginx-controller/nginx/configurator.go b/nginx-controller/nginx/configurator.go index 370431c81a..6bdba32988 100644 --- a/nginx-controller/nginx/configurator.go +++ b/nginx-controller/nginx/configurator.go @@ -3,6 +3,7 @@ package nginx import ( "bytes" "fmt" + "os" "strings" "github.com/golang/glog" @@ -13,7 +14,15 @@ import ( ) const emptyHost = "" -const DefaultServerPemName = "default" + +// DefaultServerSecretName is the filename of the Secret with a TLS cert and a key for the default server +const DefaultServerSecretName = "default" + +// JWTKey is the key of the data field of a Secret where the JWK must be stored. +const JWTKey = "jwk" + +// JWTKeyAnnotation is the annotation where the Secret with a JWK is specified. +const JWTKeyAnnotation = "nginx.com/jwt-key" // Configurator transforms an Ingress resource into NGINX Configuration type Configurator struct { @@ -33,6 +42,7 @@ func NewConfigurator(nginx *NginxController, config *Config, nginxAPI *plus.Ngin return &cnf } +// AddOrUpdateDHParam creates a dhparam file with the content of the string. func (cnf *Configurator) AddOrUpdateDHParam(content string) (string, error) { return cnf.nginx.AddOrUpdateDHParam(content) } @@ -48,19 +58,19 @@ func (cnf *Configurator) AddOrUpdateIngress(ingEx *IngressEx) error { } func (cnf *Configurator) addOrUpdateIngress(ingEx *IngressEx) { - pems := cnf.updateCertificates(ingEx) - nginxCfg := cnf.generateNginxCfg(ingEx, pems) + pems, jwtKeyFileName := cnf.updateSecrets(ingEx) + nginxCfg := cnf.generateNginxCfg(ingEx, pems, jwtKeyFileName) name := objectMetaToFileName(&ingEx.Ingress.ObjectMeta) cnf.nginx.AddOrUpdateIngress(name, nginxCfg) } -func (cnf *Configurator) updateCertificates(ingEx *IngressEx) map[string]string { +func (cnf *Configurator) updateSecrets(ingEx *IngressEx) (map[string]string, string) { pems := make(map[string]string) for _, tls := range ingEx.Ingress.Spec.TLS { secretName := tls.SecretName - pemFileName := cnf.addOrUpdateTLSSecret(ingEx.Secrets[secretName]) + pemFileName := cnf.addOrUpdateSecret(ingEx.TLSSecrets[secretName]) for _, host := range tls.Hosts { pems[host] = pemFileName @@ -70,9 +80,16 @@ func (cnf *Configurator) updateCertificates(ingEx *IngressEx) map[string]string } } - return pems + jwtKeyFileName := "" + + if cnf.isPlus() && ingEx.JWTKey != nil { + jwtKeyFileName = cnf.addOrUpdateSecret(ingEx.JWTKey) + } + + return pems, jwtKeyFileName } -func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]string) IngressNginxConfig { + +func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]string, jwtKeyFileName string) IngressNginxConfig { ingCfg := cnf.createConfig(ingEx) upstreams := make(map[string]Upstream) @@ -123,6 +140,13 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri server.SSLCertificateKey = pemFile } + if jwtKeyFileName != "" { + server.JWTKey = jwtKeyFileName + server.JWTRealm = ingCfg.JWTRealm + server.JWTToken = ingCfg.JWTToken + server.JWTLoginURL = ingCfg.JWTLoginURL + } + var locations []Location rootLocation := false @@ -271,6 +295,22 @@ func (cnf *Configurator) createConfig(ingEx *IngressEx) Config { if proxyMaxTempFileSize, exists := ingEx.Ingress.Annotations["nginx.org/proxy-max-temp-file-size"]; exists { ingCfg.ProxyMaxTempFileSize = proxyMaxTempFileSize } + + if cnf.isPlus() { + if jwtRealm, exists := ingEx.Ingress.Annotations["nginx.com/jwt-realm"]; exists { + ingCfg.JWTRealm = jwtRealm + } + if jwtKey, exists := ingEx.Ingress.Annotations[JWTKeyAnnotation]; exists { + ingCfg.JWTKey = fmt.Sprintf("%v/%v", ingEx.Ingress.Namespace, jwtKey) + } + if jwtToken, exists := ingEx.Ingress.Annotations["nginx.com/jwt-token"]; exists { + ingCfg.JWTToken = jwtToken + } + if jwtLoginURL, exists := ingEx.Ingress.Annotations["nginx.com/jwt-login-url"]; exists { + ingCfg.JWTLoginURL = jwtLoginURL + } + } + return ingCfg } @@ -306,17 +346,17 @@ func parseRewrites(service string) (serviceName string, rewrite string, err erro parts := strings.SplitN(service, " ", 2) if len(parts) != 2 { - return "", "", fmt.Errorf("Invalid rewrite format: %s\n", service) + return "", "", fmt.Errorf("Invalid rewrite format: %s", service) } svcNameParts := strings.Split(parts[0], "=") if len(svcNameParts) != 2 { - return "", "", fmt.Errorf("Invalid rewrite format: %s\n", svcNameParts) + return "", "", fmt.Errorf("Invalid rewrite format: %s", svcNameParts) } rwPathParts := strings.Split(parts[1], "=") if len(rwPathParts) != 2 { - return "", "", fmt.Errorf("Invalid rewrite format: %s\n", rwPathParts) + return "", "", fmt.Errorf("Invalid rewrite format: %s", rwPathParts) } return svcNameParts[1], rwPathParts[1], nil @@ -354,12 +394,12 @@ func parseStickyService(service string) (serviceName string, stickyCookie string parts := strings.SplitN(service, " ", 2) if len(parts) != 2 { - return "", "", fmt.Errorf("Invalid sticky-cookie service format: %s\n", service) + return "", "", fmt.Errorf("Invalid sticky-cookie service format: %s", service) } svcNameParts := strings.Split(parts[0], "=") if len(svcNameParts) != 2 { - return "", "", fmt.Errorf("Invalid sticky-cookie service format: %s\n", svcNameParts) + return "", "", fmt.Errorf("Invalid sticky-cookie service format: %s", svcNameParts) } return svcNameParts[1], parts[1], nil @@ -430,11 +470,12 @@ func upstreamMapToSlice(upstreams map[string]Upstream) []Upstream { return result } -// AddOrUpdateTLSSecret creates or updates a file with the content of the TLS secret -func (cnf *Configurator) AddOrUpdateTLSSecret(secret *api_v1.Secret, reload bool) error { - cnf.addOrUpdateTLSSecret(secret) +// AddOrUpdateSecret creates or updates a file with the content of the secret +func (cnf *Configurator) AddOrUpdateSecret(secret *api_v1.Secret) error { + cnf.addOrUpdateSecret(secret) - if !reload { + kind, _ := GetSecretKind(secret) + if cnf.isPlus() && kind == JWK { return nil } @@ -444,15 +485,27 @@ func (cnf *Configurator) AddOrUpdateTLSSecret(secret *api_v1.Secret, reload bool return nil } -func (cnf *Configurator) addOrUpdateTLSSecret(secret *api_v1.Secret) string { +func (cnf *Configurator) addOrUpdateSecret(secret *api_v1.Secret) string { name := objectMetaToFileName(&secret.ObjectMeta) - data := GenerateCertAndKeyFileContent(secret) - return cnf.nginx.AddOrUpdatePemFile(name, data) + + var data []byte + var mode os.FileMode + + kind, _ := GetSecretKind(secret) + if cnf.isPlus() && kind == JWK { + mode = jwkSecretFileMode + data = []byte(secret.Data[JWTKey]) + } else { + mode = TLSSecretFileMode + data = GenerateCertAndKeyFileContent(secret) + } + return cnf.nginx.AddOrUpdateSecretFile(name, data, mode) } +// AddOrUpdateDefaultServerTLSSecret creates or updates a file with a TLS cert and a key from the secret for the default server. func (cnf *Configurator) AddOrUpdateDefaultServerTLSSecret(secret *api_v1.Secret) error { data := GenerateCertAndKeyFileContent(secret) - cnf.nginx.AddOrUpdatePemFile(DefaultServerPemName, data) + cnf.nginx.AddOrUpdateSecretFile(DefaultServerSecretName, data, TLSSecretFileMode) if err := cnf.nginx.Reload(); err != nil { return fmt.Errorf("Error when reloading NGINX when updating the default server Secret: %v", err) @@ -471,13 +524,13 @@ func GenerateCertAndKeyFileContent(secret *api_v1.Secret) []byte { return res.Bytes() } -// DeleteTLSSecret deletes the file associated with the TLS secret and the configuration files for the Ingress resources. NGINX is reloaded only when len(ings) > 0 -func (cnf *Configurator) DeleteTLSSecret(key string, ings []extensions.Ingress) error { +// DeleteSecret deletes the file associated with the secret and the configuration files for the Ingress resources. NGINX is reloaded only when len(ings) > 0 +func (cnf *Configurator) DeleteSecret(key string, ings []extensions.Ingress) error { for _, ing := range ings { cnf.nginx.DeleteIngress(objectMetaToFileName(&ing.ObjectMeta)) } - cnf.nginx.DeletePemFile(keyToFileName(key)) + cnf.nginx.DeleteSecretFile(keyToFileName(key)) if len(ings) > 0 { if err := cnf.nginx.Reload(); err != nil { diff --git a/nginx-controller/nginx/ingress.go b/nginx-controller/nginx/ingress.go index d6e9dbf64e..e5500d4909 100644 --- a/nginx-controller/nginx/ingress.go +++ b/nginx-controller/nginx/ingress.go @@ -8,7 +8,8 @@ import ( // IngressEx holds an Ingress along with Secrets and Endpoints of the services // that are referenced in this Ingress type IngressEx struct { - Ingress *extensions.Ingress - Secrets map[string]*api_v1.Secret - Endpoints map[string][]string + Ingress *extensions.Ingress + TLSSecrets map[string]*api_v1.Secret + JWTKey *api_v1.Secret + Endpoints map[string][]string } diff --git a/nginx-controller/nginx/nginx.go b/nginx-controller/nginx/nginx.go index d78a067837..1ef11d23a9 100644 --- a/nginx-controller/nginx/nginx.go +++ b/nginx-controller/nginx/nginx.go @@ -14,10 +14,14 @@ import ( const dhparamFilename = "dhparam.pem" +// TLSSecretFileMode defines the default filemode for files with TLS Secrets +const TLSSecretFileMode = 0600 +const jwkSecretFileMode = 0644 + // NginxController Updates NGINX configuration, starts and reloads NGINX type NginxController struct { nginxConfdPath string - nginxCertsPath string + nginxSecretsPath string local bool healthStatus bool nginxConfTemplatePath string @@ -67,6 +71,11 @@ type Server struct { RealIPHeader string SetRealIPFrom []string RealIPRecursive bool + + JWTKey string + JWTRealm string + JWTToken string + JWTLoginURL string } // Location describes an NGINX location @@ -117,7 +126,7 @@ func NewUpstreamWithDefaultServer(name string) Upstream { func NewNginxController(nginxConfPath string, local bool, healthStatus bool, nginxConfTemplatePath string, nginxIngressTemplatePath string) (*NginxController, error) { ngxc := NginxController{ nginxConfdPath: path.Join(nginxConfPath, "conf.d"), - nginxCertsPath: path.Join(nginxConfPath, "ssl"), + nginxSecretsPath: path.Join(nginxConfPath, "secrets"), local: local, healthStatus: healthStatus, nginxConfTemplatePath: nginxConfTemplatePath, @@ -156,7 +165,7 @@ func (nginx *NginxController) AddOrUpdateIngress(name string, config IngressNgin // AddOrUpdateDHParam creates the servers dhparam.pem file func (nginx *NginxController) AddOrUpdateDHParam(dhparam string) (string, error) { - fileName := nginx.nginxCertsPath + "/" + dhparamFilename + fileName := nginx.nginxSecretsPath + "/" + dhparamFilename if !nginx.local { pem, err := os.Create(fileName) if err != nil { @@ -172,44 +181,48 @@ func (nginx *NginxController) AddOrUpdateDHParam(dhparam string) (string, error) return fileName, nil } -// AddOrUpdatePemFile creates a .pem file wth the cert and the key with the -// specified name -func (nginx *NginxController) AddOrUpdatePemFile(name string, content []byte) string { - pemFileName := nginx.getPemFileName(name) +// AddOrUpdateSecretFile creates a file with the specified name, content and mode. +func (nginx *NginxController) AddOrUpdateSecretFile(name string, content []byte, mode os.FileMode) string { + filename := nginx.getSecretFileName(name) if !nginx.local { - pem, err := ioutil.TempFile(nginx.nginxCertsPath, name) + file, err := ioutil.TempFile(nginx.nginxSecretsPath, name) + if err != nil { + glog.Fatalf("Couldn't create a temp file for the secret file %v: %v", name, err) + } + + err = file.Chmod(mode) if err != nil { - glog.Fatalf("Couldn't create a temp file for the pem file %v: %v", name, err) + glog.Fatalf("Couldn't change the mode of the temp secret file %v: %v", file.Name(), err) } - _, err = pem.Write(content) + _, err = file.Write(content) if err != nil { - glog.Fatalf("Couldn't write to the temp pem file %v: %v", pem.Name(), err) + glog.Fatalf("Couldn't write to the temp secret file %v: %v", file.Name(), err) } - err = pem.Close() + err = file.Close() if err != nil { - glog.Fatalf("Couldn't close the temp pem file %v: %v", pem.Name(), err) + glog.Fatalf("Couldn't close the temp secret file %v: %v", file.Name(), err) } - err = os.Rename(pem.Name(), pemFileName) + err = os.Rename(file.Name(), filename) if err != nil { - glog.Fatalf("Fail to rename the temp pem file %v to %v: %v", pem.Name(), pemFileName, err) + glog.Fatalf("Fail to rename the temp secret file %v to %v: %v", file.Name(), filename, err) } } - return pemFileName + return filename } -// DeletePemFile deletes the pem file -func (nginx *NginxController) DeletePemFile(name string) { - pemFileName := nginx.getPemFileName(name) - glog.V(3).Infof("deleting %v", pemFileName) +// DeleteSecretFile the file with a Secret +func (nginx *NginxController) DeleteSecretFile(name string) { + filename := nginx.getSecretFileName(name) + glog.V(3).Infof("deleting %v", filename) if !nginx.local { - if err := os.Remove(pemFileName); err != nil { - glog.Warningf("Failed to delete %v: %v", pemFileName, err) + if err := os.Remove(filename); err != nil { + glog.Warningf("Failed to delete %v: %v", filename, err) } } @@ -219,8 +232,8 @@ func (nginx *NginxController) getIngressNginxConfigFileName(name string) string return path.Join(nginx.nginxConfdPath, name+".conf") } -func (nginx *NginxController) getPemFileName(name string) string { - return path.Join(nginx.nginxCertsPath, name+".pem") +func (nginx *NginxController) getSecretFileName(name string) string { + return path.Join(nginx.nginxSecretsPath, name) } func (nginx *NginxController) templateIt(config IngressNginxConfig, filename string) { diff --git a/nginx-controller/nginx/secret.go b/nginx-controller/nginx/secret.go new file mode 100644 index 0000000000..fe2f0e5ecf --- /dev/null +++ b/nginx-controller/nginx/secret.go @@ -0,0 +1,48 @@ +package nginx + +import ( + "fmt" + + api_v1 "k8s.io/client-go/pkg/api/v1" +) + +const ( + // TLS Secret + TLS = iota + // JWK Secret + JWK +) + +// ValidateTLSSecret validates the secret. If it is valid, the function returns nil. +func ValidateTLSSecret(secret *api_v1.Secret) error { + if _, exists := secret.Data[api_v1.TLSCertKey]; !exists { + return fmt.Errorf("Secret doesn't have %v", api_v1.TLSCertKey) + } + + if _, exists := secret.Data[api_v1.TLSPrivateKeyKey]; !exists { + return fmt.Errorf("Secret doesn't have %v", api_v1.TLSPrivateKeyKey) + } + + return nil +} + +// ValidateJWKSecret validates the secret. If it is valid, the function returns nil. +func ValidateJWKSecret(secret *api_v1.Secret) error { + if _, exists := secret.Data[JWTKey]; !exists { + return fmt.Errorf("Secret doesn't have %v", JWTKey) + } + + return nil +} + +// GetSecretKind returns the kind of the Secret. +func GetSecretKind(secret *api_v1.Secret) (int, error) { + if err := ValidateTLSSecret(secret); err == nil { + return TLS, nil + } + if err := ValidateJWKSecret(secret); err == nil { + return JWK, nil + } + + return 0, fmt.Errorf("Unknown Secret") +} diff --git a/nginx-controller/nginx/templates/nginx-plus.ingress.tmpl b/nginx-controller/nginx/templates/nginx-plus.ingress.tmpl index 0459bed35f..4549b34d44 100644 --- a/nginx-controller/nginx/templates/nginx-plus.ingress.tmpl +++ b/nginx-controller/nginx/templates/nginx-plus.ingress.tmpl @@ -46,6 +46,19 @@ server { } {{- end}} + {{ if $server.JWTKey}} + auth_jwt_key_file {{$server.JWTKey}}; + auth_jwt "{{$server.JWTRealm}}"{{if $server.JWTToken}} token={{$server.JWTToken}}{{end}}; + + {{- if $server.JWTLoginURL}} + error_page 401 @login_url; + location @login_url { + internal; + return 302 {{$server.JWTLoginURL}}; + } + {{- end}} + {{- end}} + {{- if $server.ServerSnippets}} {{range $value := $server.ServerSnippets}} {{$value}}{{end}} diff --git a/nginx-controller/nginx/templates/nginx-plus.tmpl b/nginx-controller/nginx/templates/nginx-plus.tmpl index 072ac53224..ee320f8d78 100644 --- a/nginx-controller/nginx/templates/nginx-plus.tmpl +++ b/nginx-controller/nginx/templates/nginx-plus.tmpl @@ -56,8 +56,8 @@ http { listen 80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}}; listen 443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}}; - ssl_certificate /etc/nginx/ssl/default.pem; - ssl_certificate_key /etc/nginx/ssl/default.pem; + ssl_certificate /etc/nginx/secrets/default; + ssl_certificate_key /etc/nginx/secrets/default; server_name _; server_tokens "{{.ServerTokens}}"; diff --git a/nginx-controller/nginx/templates/nginx.tmpl b/nginx-controller/nginx/templates/nginx.tmpl index bd174a6b60..d2b82c7518 100644 --- a/nginx-controller/nginx/templates/nginx.tmpl +++ b/nginx-controller/nginx/templates/nginx.tmpl @@ -54,8 +54,8 @@ http { listen 80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}}; listen 443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}}; - ssl_certificate /etc/nginx/ssl/default.pem; - ssl_certificate_key /etc/nginx/ssl/default.pem; + ssl_certificate /etc/nginx/secrets/default; + ssl_certificate_key /etc/nginx/secrets/default; server_name _; server_tokens "{{.ServerTokens}}"; diff --git a/nginx-controller/nginx/templates/templates_test.go b/nginx-controller/nginx/templates/templates_test.go new file mode 100644 index 0000000000..28d7be6a49 --- /dev/null +++ b/nginx-controller/nginx/templates/templates_test.go @@ -0,0 +1,74 @@ +package templates + +import ( + "bytes" + "testing" + "text/template" + + "github.com/nginxinc/kubernetes-ingress/nginx-controller/nginx" +) + +const nginxIngressTmpl = "nginx.ingress.tmpl" +const nginxPlusIngressTmpl = "nginx-plus.ingress.tmpl" + +var testUps = nginx.Upstream{ + Name: "test", + UpstreamServers: []nginx.UpstreamServer{ + {"127.0.0.1", "8181"}, + }, +} + +var ingCfg = nginx.IngressNginxConfig{ + + Servers: []nginx.Server{ + nginx.Server{ + Name: "test.example.com", + ServerTokens: "off", + StatusZone: "test.example.com", + JWTKey: "/etc/nginx/secrets/key.jwk", + JWTRealm: "closed site", + JWTToken: "$cookie_auth_token", + JWTLoginURL: "https://test.example.com/login", + Locations: []nginx.Location{ + nginx.Location{ + Path: "/", + Upstream: testUps, + ProxyConnectTimeout: "10s", + ProxyReadTimeout: "10s", + ClientMaxBodySize: "2m", + }, + }, + }, + }, + Upstreams: []nginx.Upstream{testUps}, +} + +func TestIngressForNGINXPlus(t *testing.T) { + tmpl, err := template.New(nginxPlusIngressTmpl).ParseFiles(nginxPlusIngressTmpl) + if err != nil { + t.Fatalf("Failed to parse template file: %v", err) + } + + var buf bytes.Buffer + + err = tmpl.Execute(&buf, ingCfg) + t.Log(string(buf.Bytes())) + if err != nil { + t.Fatalf("Failed to write template %v", err) + } +} + +func TestIngressForNGINX(t *testing.T) { + tmpl, err := template.New(nginxIngressTmpl).ParseFiles(nginxIngressTmpl) + if err != nil { + t.Fatalf("Failed to parse template file: %v", err) + } + + var buf bytes.Buffer + + err = tmpl.Execute(&buf, ingCfg) + t.Log(string(buf.Bytes())) + if err != nil { + t.Fatalf("Failed to write template %v", err) + } +}