github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/resources/ingresses/ingress.go (about) 1 // Copyright (C) 2020, 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package ingresses 5 6 import ( 7 "fmt" 8 "strconv" 9 10 vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1" 11 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/config" 12 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants" 13 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources" 14 "go.uber.org/zap" 15 netv1 "k8s.io/api/networking/v1" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 ) 18 19 var defaultIngressClassName = "verrazzano-nginx" 20 21 func createIngressRuleElement(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, componentDetails config.ComponentDetails) netv1.IngressRule { 22 serviceName := resources.GetMetaName(vmo.Name, componentDetails.Name) 23 endpointName := componentDetails.EndpointName 24 if endpointName == "" { 25 endpointName = componentDetails.Name 26 } 27 fqdn := fmt.Sprintf("%s.%s", endpointName, vmo.Spec.URI) 28 pathType := netv1.PathTypeImplementationSpecific 29 30 return netv1.IngressRule{ 31 Host: fqdn, 32 IngressRuleValue: netv1.IngressRuleValue{ 33 HTTP: &netv1.HTTPIngressRuleValue{ 34 Paths: []netv1.HTTPIngressPath{ 35 { 36 Path: "/", 37 PathType: &pathType, 38 Backend: netv1.IngressBackend{ 39 Service: &netv1.IngressServiceBackend{ 40 Name: serviceName, 41 Port: netv1.ServiceBackendPort{ 42 Number: int32(componentDetails.Port), 43 }, 44 }, 45 }, 46 }, 47 }, 48 }, 49 }, 50 } 51 } 52 53 func createIngressElementNoBasicAuth(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, hostName string, componentDetails config.ComponentDetails, ingressRule netv1.IngressRule) (*netv1.Ingress, error) { 54 var hosts = []string{hostName} 55 ingressClassName := getIngressClassName(vmo) 56 ingress := &netv1.Ingress{ 57 ObjectMeta: metav1.ObjectMeta{ 58 Annotations: map[string]string{}, 59 Labels: resources.GetMetaLabels(vmo), 60 Name: fmt.Sprintf("%s%s-%s", constants.VMOServiceNamePrefix, vmo.Name, componentDetails.Name), 61 Namespace: vmo.Namespace, 62 OwnerReferences: resources.GetOwnerReferences(vmo), 63 }, 64 Spec: netv1.IngressSpec{ 65 66 TLS: []netv1.IngressTLS{ 67 { 68 Hosts: hosts, 69 SecretName: fmt.Sprintf("%s-tls-%s", vmo.Name, componentDetails.Name), 70 }, 71 }, 72 Rules: []netv1.IngressRule{ingressRule}, 73 IngressClassName: &ingressClassName, 74 }, 75 } 76 77 ingress.Annotations["nginx.ingress.kubernetes.io/proxy-body-size"] = constants.NginxClientMaxBodySize 78 79 if len(vmo.Spec.IngressTargetDNSName) != 0 { 80 ingress.Annotations["external-dns.alpha.kubernetes.io/target"] = vmo.Spec.IngressTargetDNSName 81 ingress.Annotations["external-dns.alpha.kubernetes.io/ttl"] = strconv.Itoa(constants.ExternalDNSTTLSeconds) 82 } 83 // if we specify AutoSecret: true we attach an annotation that will create a cert 84 if vmo.Spec.AutoSecret { 85 // we must create a secret name too 86 ingress.Annotations["kubernetes.io/tls-acme"] = "true" 87 } else { 88 ingress.Annotations["kubernetes.io/tls-acme"] = "false" 89 } 90 91 ingress.Annotations["cert-manager.io/common-name"] = hostName 92 return ingress, nil 93 } 94 95 func addBasicAuthIngressAnnotations(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, ingress *netv1.Ingress, healthLocations string) { 96 ingress.Annotations["nginx.ingress.kubernetes.io/auth-type"] = "basic" 97 ingress.Annotations["nginx.ingress.kubernetes.io/auth-secret"] = vmo.Spec.SecretName 98 ingress.Annotations["nginx.ingress.kubernetes.io/auth-realm"] = vmo.Spec.URI + " auth" 99 //For custom location snippets k8s recommends we use server-snippet instead of configuration-snippet 100 // With ingress controller 0.24.1 our code using configuration-snippet no longer works 101 ingress.Annotations["nginx.ingress.kubernetes.io/server-snippet"] = healthLocations 102 } 103 104 func createIngressElement(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, hostName string, componentDetails config.ComponentDetails, ingressRule netv1.IngressRule, healthLocations string) (*netv1.Ingress, error) { 105 ingress, err := createIngressElementNoBasicAuth(vmo, hostName, componentDetails, ingressRule) 106 if err != nil { 107 return ingress, err 108 } 109 addBasicAuthIngressAnnotations(vmo, ingress, healthLocations) 110 return ingress, nil 111 } 112 113 // New will return a new Service for VMO that needs to executed for on Complete 114 func New(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, existingIngresses map[string]*netv1.Ingress) ([]*netv1.Ingress, error) { 115 var ingresses []*netv1.Ingress 116 117 // Only create ingress if URI and secret name specified 118 if len(vmo.Spec.URI) <= 0 { 119 zap.S().Debugw("URI not specified, skipping ingress creation") 120 return ingresses, nil 121 } 122 123 // Create Ingress Rule for API Endpoint 124 if !config.API.Disabled { 125 ingRule := createIngressRuleElement(vmo, config.API) 126 host := config.API.Name + "." + vmo.Spec.URI 127 healthLocations := noAuthOnHealthCheckSnippet(vmo, "", config.API) 128 ingress, err := createIngressElement(vmo, host, config.API, ingRule, healthLocations) 129 if err != nil { 130 return ingresses, err 131 } 132 setNginxRoutingAnnotations(ingress) 133 ingresses = append(ingresses, ingress) 134 } 135 136 if vmo.Spec.Grafana.Enabled { 137 if config.Grafana.OidcProxy != nil { 138 ingresses = append(ingresses, newOidcProxyIngress(vmo, &config.Grafana)) 139 } else { 140 // Create Ingress Rule for Grafana Endpoint 141 ingRule := createIngressRuleElement(vmo, config.Grafana) 142 host := config.Grafana.Name + "." + vmo.Spec.URI 143 ingress, err := createIngressElementNoBasicAuth(vmo, host, config.Grafana, ingRule) 144 if err != nil { 145 return ingresses, err 146 } 147 ingresses = append(ingresses, ingress) 148 } 149 } 150 if vmo.Spec.Kibana.Enabled { 151 if config.Kibana.OidcProxy != nil { 152 ingress := newOidcProxyIngress(vmo, &config.OpenSearchDashboards) 153 ingresses = append(ingresses, ingress) 154 redirectIngress := createRedirectIngressIfNecessary(vmo, existingIngresses, &config.Kibana, &config.OpenSearchDashboardsRedirect) 155 if redirectIngress != nil { 156 redirectIngress.Annotations["nginx.ingress.kubernetes.io/proxy-body-size"] = "65M" 157 redirectIngress.Annotations["nginx.ingress.kubernetes.io/permanent-redirect"] = "https://" + resources.OidcProxyIngressHost(vmo, &config.OpenSearchDashboards) 158 ingresses = append(ingresses, redirectIngress) 159 } 160 } else { 161 // Create Ingress Rule for Kibana Endpoint 162 ingRule := createIngressRuleElement(vmo, config.OpenSearchDashboards) 163 host := config.Kibana.Name + "." + vmo.Spec.URI 164 healthLocations := noAuthOnHealthCheckSnippet(vmo, "", config.OpenSearchDashboards) 165 ingress, err := createIngressElement(vmo, host, config.Kibana, ingRule, healthLocations) 166 if err != nil { 167 return ingresses, err 168 } 169 ingresses = append(ingresses, ingress) 170 redirectIngress := createRedirectIngressIfNecessary(vmo, existingIngresses, &config.Kibana, &config.OpenSearchDashboardsRedirect) 171 if redirectIngress != nil { 172 redirectIngress.Annotations["nginx.ingress.kubernetes.io/proxy-body-size"] = "65M" 173 redirectIngress.Annotations["nginx.ingress.kubernetes.io/permanent-redirect"] = "https://" + resources.OidcProxyIngressHost(vmo, &config.OpenSearchDashboards) 174 ingresses = append(ingresses, redirectIngress) 175 } 176 } 177 } 178 if vmo.Spec.Elasticsearch.Enabled { 179 if config.ElasticsearchIngest.OidcProxy != nil { 180 ingress := newOidcProxyIngress(vmo, &config.OpensearchIngest) 181 ingress.Annotations["nginx.ingress.kubernetes.io/proxy-body-size"] = "65M" 182 ingresses = append(ingresses, ingress) 183 redirectIngress := createRedirectIngressIfNecessary(vmo, existingIngresses, &config.ElasticsearchIngest, &config.OpensearchIngestRedirect) 184 if redirectIngress != nil { 185 redirectIngress.Annotations["nginx.ingress.kubernetes.io/proxy-body-size"] = "65M" 186 redirectIngress.Annotations["nginx.ingress.kubernetes.io/permanent-redirect"] = "https://" + resources.OidcProxyIngressHost(vmo, &config.OpensearchIngest) 187 ingresses = append(ingresses, redirectIngress) 188 } 189 } else { 190 var ingress *netv1.Ingress 191 ingRule := createIngressRuleElement(vmo, config.ElasticsearchIngest) 192 host := config.ElasticsearchIngest.EndpointName + "." + vmo.Spec.URI 193 healthLocations := noAuthOnHealthCheckSnippet(vmo, "", config.ElasticsearchIngest) 194 ingress, err := createIngressElement(vmo, host, config.ElasticsearchIngest, ingRule, healthLocations) 195 if err != nil { 196 return ingresses, err 197 } 198 ingress.Annotations["nginx.ingress.kubernetes.io/proxy-read-timeout"] = constants.NginxProxyReadTimeoutForKibana 199 ingresses = append(ingresses, ingress) 200 redirectIngress := createRedirectIngressIfNecessary(vmo, existingIngresses, &config.ElasticsearchIngest, &config.OpensearchIngestRedirect) 201 if redirectIngress != nil { 202 redirectIngress.Annotations["nginx.ingress.kubernetes.io/proxy-body-size"] = "65M" 203 redirectIngress.Annotations["nginx.ingress.kubernetes.io/permanent-redirect"] = "https://" + resources.OidcProxyIngressHost(vmo, &config.OpensearchIngest) 204 ingresses = append(ingresses, redirectIngress) 205 } 206 } 207 208 } 209 return ingresses, nil 210 } 211 212 // setNginxRoutingAnnotations adds the nginx annotations required for routing via istio envoy 213 func setNginxRoutingAnnotations(ingress *netv1.Ingress) { 214 ingress.Annotations["nginx.ingress.kubernetes.io/service-upstream"] = "true" 215 ingress.Annotations["nginx.ingress.kubernetes.io/upstream-vhost"] = "${service_name}.${namespace}.svc.cluster.local" 216 } 217 218 // noAuthOnHealthCheckSnippet returns an NGINX configuration snippet with Basic Authentication disabled for the the 219 // specified component's health check path. 220 func noAuthOnHealthCheckSnippet(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, disambiguationRoot string, componentDetails config.ComponentDetails) string { 221 // Added = check so nginx matches only this path i.e. strict check 222 return `location = ` + disambiguationRoot + componentDetails.LivenessHTTPPath + ` { 223 auth_basic off; 224 auth_request off; 225 proxy_pass ` + fmt.Sprintf("http://%s.%s.svc.cluster.local:%d%s", constants.VMOServiceNamePrefix+vmo.Name+"-"+componentDetails.Name, vmo.Namespace, componentDetails.Port, componentDetails.LivenessHTTPPath) + `; 226 } 227 ` 228 } 229 230 // newOidcProxyIngress creates the Ingress of the OidcProxy 231 func newOidcProxyIngress(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, component *config.ComponentDetails) *netv1.Ingress { 232 port, err := strconv.ParseInt(resources.AuthProxyPort(), 10, 32) 233 if err != nil { 234 port = 8775 235 } 236 serviceName := resources.AuthProxyMetaName() 237 ingressHost := resources.OidcProxyIngressHost(vmo, component) 238 pathType := netv1.PathTypeImplementationSpecific 239 ingressClassName := getIngressClassName(vmo) 240 ingressRule := netv1.IngressRule{ 241 Host: ingressHost, 242 IngressRuleValue: netv1.IngressRuleValue{ 243 HTTP: &netv1.HTTPIngressRuleValue{ 244 Paths: []netv1.HTTPIngressPath{ 245 { 246 Path: "/()(.*)", 247 PathType: &pathType, 248 Backend: netv1.IngressBackend{ 249 Service: &netv1.IngressServiceBackend{ 250 Name: serviceName, 251 Port: netv1.ServiceBackendPort{ 252 Number: int32(port), 253 }, 254 }, 255 }, 256 }, 257 }, 258 }, 259 }, 260 } 261 ingress := &netv1.Ingress{ 262 ObjectMeta: metav1.ObjectMeta{ 263 Annotations: map[string]string{}, 264 Labels: resources.GetMetaLabels(vmo), 265 Name: fmt.Sprintf("%s%s-%s", constants.VMOServiceNamePrefix, vmo.Name, component.Name), 266 Namespace: vmo.Namespace, 267 OwnerReferences: resources.GetOwnerReferences(vmo), 268 }, 269 Spec: netv1.IngressSpec{ 270 TLS: []netv1.IngressTLS{ 271 { 272 Hosts: []string{ingressHost}, 273 SecretName: fmt.Sprintf("%s-tls-%s", vmo.Name, component.Name), 274 }, 275 }, 276 Rules: []netv1.IngressRule{ingressRule}, 277 IngressClassName: &ingressClassName, 278 }, 279 } 280 ingress.Annotations["nginx.ingress.kubernetes.io/proxy-body-size"] = constants.NginxClientMaxBodySize 281 if len(vmo.Spec.IngressTargetDNSName) != 0 { 282 ingress.Annotations["external-dns.alpha.kubernetes.io/target"] = vmo.Spec.IngressTargetDNSName 283 ingress.Annotations["external-dns.alpha.kubernetes.io/ttl"] = strconv.Itoa(constants.ExternalDNSTTLSeconds) 284 } 285 if vmo.Spec.AutoSecret { 286 ingress.Annotations["kubernetes.io/tls-acme"] = "true" 287 } else { 288 ingress.Annotations["kubernetes.io/tls-acme"] = "false" 289 } 290 ingress.Annotations["nginx.ingress.kubernetes.io/rewrite-target"] = "/$2" 291 setNginxRoutingAnnotations(ingress) 292 ingress.Annotations["cert-manager.io/common-name"] = ingressHost 293 return ingress 294 } 295 296 func getIngressClassName(vmi *vmcontrollerv1.VerrazzanoMonitoringInstance) string { 297 if vmi.Spec.IngressClassName != nil && *vmi.Spec.IngressClassName != "" { 298 return *vmi.Spec.IngressClassName 299 } 300 return defaultIngressClassName 301 } 302 303 // createRedirectIngressIfNecessary creates a new ingress for permanent redirection if required 304 // For upgrade, if the user has deprecated Elasticsearch/Kibana ingress 305 // Then create a new ingress for permanent redirection 306 func createRedirectIngressIfNecessary(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, existingIngresses map[string]*netv1.Ingress, deprecatedIngressComponent *config.ComponentDetails, component *config.ComponentDetails) *netv1.Ingress { 307 var ingress *netv1.Ingress 308 // If the existing ingress with deprecated component name exists then create a new ingress for permanent redirection 309 if _, ok := existingIngresses[resources.GetMetaName(vmo.Name, deprecatedIngressComponent.Name)]; ok { 310 ingress = newOidcProxyIngress(vmo, component) 311 } 312 // If the redirect ingress exists then return the original redirect ingress. 313 if _, ok := existingIngresses[resources.GetMetaName(vmo.Name, component.Name)]; ok { 314 ingress = newOidcProxyIngress(vmo, component) 315 } 316 return ingress 317 }