github.com/verrazzano/verrazzano@v1.7.1/platform-operator/helm_config/charts/verrazzano-authproxy/templates/verrazzano-authproxy-configmap.yaml (about)

     1  # Copyright (c) 2021, 2023, 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  ---
     5  apiVersion: v1
     6  kind: ConfigMap
     7  metadata:
     8    name: verrazzano-authproxy-config
     9    namespace: {{ .Release.Namespace }}
    10    labels:
    11      app: {{ .Values.name }}
    12  data:
    13    conf.lua: |
    14      local clusterHostSuffix = '{{ .Values.config.envName }}'..'.'..'{{ .Values.config.dnsSuffix }}'
    15  {{- with .Values.proxy }}
    16      local ingressHost = ngx.req.get_headers()["x-forwarded-host"]
    17      if not ingressHost then
    18        ingressHost = ngx.req.get_headers()["host"]
    19      end
    20      if not ingressHost or #ingressHost < 1 or #ingressHost > 256 then
    21          ingressHost = 'invalid-hostname'
    22      end
    23  
    24      local ingressUri = 'https://'..ingressHost
    25      local callbackPath = "{{ .OidcCallbackPath }}"
    26      local logoutPath = "{{ .OidcLogoutCallbackPath }}"
    27      local singleLogoutPath = "{{ .OidcSingleLogoutCallbackPath }}"
    28      local oidcProviderForConsole = "{{ .OidcProviderForConsole }}"
    29  
    30      local auth = require("auth").config({
    31          hostSuffix = '.'..clusterHostSuffix,
    32          callbackUri = ingressUri..callbackPath,
    33          singleLogoutUri = ingressUri..singleLogoutPath,
    34          hostUri = ingressUri
    35      })
    36  
    37      -- determine backend and set backend parameters
    38      local backend, can_redirect = auth.getBackendNameFromIngressHost(ingressHost)
    39      local backendUrl = auth.getBackendServerUrlFromName(backend)
    40  
    41      -- CORS handling
    42      local h, _ = ngx.req.get_headers()["origin"]
    43      if h ~= nil and h ~= "" then
    44          auth.debug("Origin header: "..h)
    45          if h == "*" then
    46              -- not a legit origin, could be intended to trick us into oversharing
    47              auth.bad_request("Invalid Origin header: '*'")
    48          end
    49          
    50          ngx.header["Vary"] = "Origin"
    51          local requestMethod = ngx.req.get_method()
    52          local originAllowed = auth.isOriginAllowed(h, backend, ingressUri)
    53  
    54          -- From https://tools.ietf.org/id/draft-abarth-origin-03.html#server-behavior, if the request Origin is not in
    55          -- whitelisted origins and the request method is not a safe non state changing (i.e. GET or HEAD), we should
    56          -- abort the request.
    57          if not originAllowed and requestMethod ~= "GET" and requestMethod ~= "HEAD" and requestMethod ~= "OPTIONS" then
    58              auth.forbidden("Origin: " .. h .. " not allowed.")
    59          end
    60          
    61          if originAllowed then
    62              -- From "Simple Cross-Origin Request, Actual Request, and Redirects" section of https://www.w3.org/TR/2020/SPSD-cors-20200602,
    63              -- set the value of Access-Control-Allow-Origin and Access-Control-Allow-Credentials hedaers if there is a match.
    64              ngx.header["Access-Control-Allow-Origin"] = h
    65              ngx.header["Access-Control-Allow-Credentials"] = "true"
    66              if requestMethod == "OPTIONS" then
    67                  ngx.header["Access-Control-Allow-Headers"] = "authorization, content-type"
    68                  ngx.header["Access-Control-Allow-Methods"] = "GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH"
    69              end
    70          end
    71          
    72          if requestMethod == "OPTIONS" then
    73              ngx.header["Content-Length"] = 0
    74              ngx.status = 200
    75              ngx.exit(ngx.HTTP_OK)
    76          end
    77          
    78      else
    79          if ngx.req.get_method() == "OPTIONS" then
    80              auth.bad_request("OPTIONS request with no Origin header")
    81          end
    82      end
    83  
    84      auth.debug("Processing request for backend '"..ingressHost.."'")
    85      if (backend == 'console' or backend == 'verrazzano') and (not auth.isBodyValidJson()) then
    86          auth.bad_request("Invalid request")
    87      end
    88  
    89      if (backend == 'console' or backend == 'verrazzano') then
    90          local oidcProviderFromMCSecret = auth.read_file("/api-config/oidc-provider")
    91          if oidcProviderFromMCSecret and oidcProviderFromMCSecret ~= "" then
    92              auth.debug("oidc-provider specified in multi-cluster secret, will use " .. oidcProviderFromMCSecret .. " as OIDC Provider.")
    93              auth.initOidcProvider(oidcProviderFromMCSecret)
    94          else
    95              auth.initOidcProvider(oidcProviderForConsole)
    96          end
    97      else
    98          auth.initOidcProvider("keycloak")
    99      end
   100  
   101      local authHeader = ngx.req.get_headers()["authorization"]
   102      local token = nil
   103      if authHeader then
   104          if auth.hasCredentialType(authHeader, 'Bearer') then
   105              token = auth.handleBearerToken(authHeader)
   106          elseif auth.hasCredentialType(authHeader, 'Basic') then
   107              token = auth.handleBasicAuth(authHeader)
   108          end
   109          if not token then
   110              auth.debug("No recognized credentials in authorization header")
   111          end
   112      else
   113          auth.debug("No authorization header found")
   114          if auth.requestUriMatches(ngx.var.request_uri, callbackPath) then
   115              -- we initiated authentication via pkce, and OP is delivering the code
   116              -- will redirect to target url, where token will be found in cookie
   117              auth.oidcHandleCallback()
   118          end
   119  
   120          if auth.requestUriMatches(ngx.var.request_uri, logoutPath) then
   121              -- logout was triggered
   122              auth.logout()
   123          end
   124  
   125          if auth.requestUriMatches(ngx.var.request_uri, singleLogoutPath) then
   126              -- single logout was triggered
   127              auth.singleLogout()
   128          end
   129  
   130          -- no token yet, and the request is not progressing an OIDC flow.
   131          -- check if caller has an existing session with a valid token.
   132          token = auth.getTokenFromSession()
   133  
   134          -- still no token? redirect to OP to authenticate user (if request is not a Verrazzano API call)
   135          if not token and can_redirect == true then
   136              auth.oidcAuthenticate()
   137          end
   138      end
   139  
   140      if not token then
   141          auth.unauthorized("Not authenticated")
   142      end
   143  
   144      -- token will be an id token except when console calls api proxy, then it's an access token
   145      if not auth.isAuthorized(token) then
   146          auth.forbidden("Not authorized")
   147      end
   148    
   149      local usernameFromToken = auth.usernameFromIdToken(token)
   150      auth.audit("User "..usernameFromToken.." authenticated and authorized")
   151      local userRolesFromToken = auth.getRoles(token)
   152  
   153      if backend == 'verrazzano' then
   154          local args = ngx.req.get_uri_args()
   155          if args.cluster then
   156              -- returns remote cluster server URL
   157              backendUrl = auth.handleExternalAPICall(token)
   158          else
   159              auth.handleLocalAPICall(token)
   160          end
   161      else
   162          if auth.hasCredentialType(authHeader, 'Bearer') then
   163              -- clear the auth header if it's a bearer token
   164              ngx.req.clear_header("Authorization")
   165          end
   166          -- set the oidc_user
   167          ngx.var.oidc_user = usernameFromToken
   168          ngx.var.oidc_user_roles = userRolesFromToken
   169          auth.debug("Authorized: oidc_user is "..ngx.var.oidc_user)
   170      end
   171  
   172      auth.debug("Setting backend_server_url to '"..backendUrl.."'")
   173      ngx.var.backend_server_url = backendUrl
   174    auth.lua: |
   175      local me = {}
   176      local random = require("resty.random")
   177      local base64 = require("ngx.base64")
   178      local cjson = require "cjson"
   179      local jwt = require "resty.jwt"
   180      local validators = require "resty.jwt-validators"
   181      local b64 = ngx.encode_base64
   182      local unb64url = require("ngx.base64").decode_base64url
   183  
   184      local oidcRealm = "{{ .OidcRealm }}"
   185      local oidcClient = "{{ .OIDCClientID }}"
   186      local adminClusterOidcClient = "{{ .PKCEClientID }}"
   187      local oidcDirectAccessClient = "{{ .PGClientID }}"
   188      local requiredRole = "{{ .RequiredRealmRole }}"
   189  
   190      local authStateTtlInSec = tonumber("{{ .AuthnStateTTL }}")
   191      local oidcProviderHost = "{{ .OidcProviderHost }}"
   192      local oidcProviderHostInCluster = "{{ .OidcProviderHostInCluster }}"
   193      local oidcProviderHostDex = "{{ .OidcProviderHostDex }}"
   194      local oidcProviderHostInClusterDex = "{{ .OidcProviderHostInClusterDex }}"
   195      local oidcClientSecret = "{{ .OidcProviderClientSecret }}"
   196  
   197  
   198      local oidcProviderUri = nil
   199      local oidcProviderInClusterUri = nil
   200      local oidcIssuerUri = nil
   201      local oidcIssuerUriLocal = nil
   202  
   203      local opensearchProtocol = "{{ $.Values.config.opensearch.protocol }}"
   204      local opensearchService = "{{ $.Values.config.opensearch.service }}"
   205      local osdService = "{{ $.Values.config.opensearch.osdService }}"
   206      local opensearchNamespace = "{{ $.Values.config.opensearch.namespace }}"
   207  
   208      function me.getRoles(idToken)
   209          local id_token = jwt:load_jwt(idToken)
   210          if id_token and id_token.payload and id_token.payload.realm_access and id_token.payload.realm_access.roles then
   211              return table.concat(id_token.payload.realm_access.roles, ",")
   212          end
   213  
   214          if me.oidcProvider == "dex" then
   215              return "offline_access,vz_api_access,vz_opensearch_admin,uma_authorization,default-roles-verrazzano-system"
   216          end
   217          return ""
   218      end
   219  
   220      function me.config(opts)
   221          for key, val in pairs(opts) do
   222              me[key] = val
   223          end
   224          me.initCookieEncryptor()
   225          return me
   226      end
   227  
   228      function me.initCookieEncryptor()
   229          local aes = require "resty.aes"
   230          local key = me.read_file("/api-config/cookie-encryption-key")
   231          if not key or #key ~= 64 then
   232              me.internal_server_error("Error getting cookie key")
   233          end
   234          local salt = string.sub(key, 49, 56)
   235          local encryptor, err = aes:new(key, salt, aes.cipher(256, "cbc"), aes.hash.sha256, 3)
   236          if err or not encryptor then
   237              me.internal_server_error("Unable to get encryptor, error is: '"..err.."'")
   238          end
   239          me.aes256 = encryptor
   240      end
   241  
   242      function me.initOidcProvider(oidcProvider)
   243          me.oidcProvider = oidcProvider
   244          local oidcProviderHostForRequest = ""
   245          local oidcProviderHostInClusterForRequest = ""
   246          if me.oidcProvider == "dex" then
   247              oidcProviderHostForRequest = oidcProviderHostDex
   248              oidcProviderHostInClusterForRequest = oidcProviderHostInClusterDex
   249          else
   250              oidcProviderHostForRequest = oidcProviderHost
   251              oidcProviderHostInClusterForRequest = oidcProviderHostInCluster
   252          end
   253  
   254          me.oidcProviderUri = "https://" .. oidcProviderHostForRequest
   255          if oidcProviderHostInClusterForRequest and oidcProviderHostInClusterForRequest ~= "" then
   256              me.oidcProviderInClusterUri = "http://" .. oidcProviderHostInClusterForRequest
   257          end
   258  
   259          local oidcProviderURL = me.read_file("/api-config/" .. me.oidcProvider .. "-url")
   260          if oidcProviderURL and oidcProviderURL ~= "" then
   261              me.debug(me.oidcProvider .. "-url specified in multi-cluster secret, will not use in-cluster oidc provider host.")
   262  
   263              me.oidcProviderUri = oidcProviderURL
   264              me.oidcProviderInClusterUri = nil
   265          end
   266  
   267          if me.oidcProvider == "keycloak" then
   268              me.oidcProviderUri = me.oidcProviderUri .. "/auth/realms/" .. oidcRealm
   269              if me.oidcProviderInClusterUri then
   270                  me.oidcProviderInClusterUri = me.oidcProviderInClusterUri .. "/auth/realms/" .. oidcRealm
   271              end
   272  
   273          end
   274  
   275          me.oidcIssuerUri = me.oidcProviderUri
   276          me.oidcIssuerUriLocal = me.oidcProviderInClusterUri
   277      end
   278  
   279      function me.log(logLevel, msg, name, value)
   280          local logObj = {message = msg}
   281          if name then
   282              logObj[name] = value
   283          end
   284          ngx.log(logLevel,  cjson.encode(logObj))
   285      end
   286  
   287      function me.logJson(logLevel, msg, err)
   288          if err then
   289              me.log(logLevel, msg, 'error', err)
   290          else
   291              me.log(logLevel, msg)
   292          end
   293      end
   294  
   295      function me.info(msg, obj)
   296          if obj then
   297              me.log(ngx.INFO, msg, 'object', obj)
   298          else
   299              me.log(ngx.INFO, msg)
   300          end
   301      end
   302      
   303      function me.error(msg, obj)
   304          if obj then
   305              me.log(ngx.ERR, msg, 'object', obj)
   306          else
   307              me.log(ngx.ERR, msg)
   308          end
   309      end
   310      
   311      function me.debug(msg, obj)
   312          if obj then
   313              me.log(ngx.DEBUG, msg, 'object', obj)
   314          else
   315              me.log(ngx.DEBUG, msg)
   316          end
   317      end
   318  
   319      function me.queryParams(req_uri)
   320           local i = req_uri:find("?")
   321           if not i then
   322               i = 0
   323           else
   324               i = i + 1
   325           end
   326           return ngx.decode_args(req_uri:sub(i), 0)
   327      end
   328  
   329      function me.query(req_uri, name)
   330          local i = req_uri:find("&"..name.."=")
   331          if not i then
   332          i = req_uri:find("?"..name.."=")
   333          end
   334          if not i then
   335              return nil
   336          else
   337              local begin = i+2+name:len()
   338              local endin = req_uri:find("&", begin)
   339              if not endin then
   340                  return req_uri:sub(begin)
   341              end
   342              return req_uri:sub(begin, endin-1)
   343          end
   344      end
   345      
   346      -- For now, auditing reports a log message at the debug level
   347      function me.audit(msg, err)
   348          me.logJson(ngx.DEBUG, msg, err)
   349      end
   350  
   351      function me.internal_server_error(msg, err)
   352          me.audit(msg, err)
   353          ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
   354          ngx.say("500 Internal Server Error")
   355          ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
   356      end
   357  
   358      function me.unauthorized(msg, err)
   359          me.deleteCookies("vz_authn", "vz_userinfo", "vz_state")
   360          me.audit(msg, err)
   361          ngx.status = ngx.HTTP_UNAUTHORIZED
   362          ngx.say("401 Unauthorized")
   363          ngx.exit(ngx.HTTP_UNAUTHORIZED)
   364      end
   365  
   366      function me.forbidden(msg, err)
   367          me.audit(msg, err)
   368          ngx.status = ngx.HTTP_FORBIDDEN
   369          ngx.say("403 Forbidden")
   370          ngx.exit(ngx.HTTP_FORBIDDEN)
   371      end
   372  
   373      function me.not_found(msg, err)
   374          me.audit(msg, err)
   375          ngx.status = ngx.HTTP_NOT_FOUND
   376          ngx.say("404 Not Found")
   377          ngx.exit(ngx.HTTP_NOT_FOUND)
   378      end
   379  
   380      function me.bad_request(msg, err)
   381          me.audit(msg, err)
   382          ngx.status = ngx.HTTP_BAD_REQUEST
   383          ngx.say("400 Bad Request")
   384          ngx.exit(ngx.HTTP_BAD_REQUEST)
   385      end
   386  
   387      function me.logout()
   388          local ck = me.readCookie("vz_authn")
   389          local redirectURL = me.hostUri
   390          if ck then
   391              if me.oidcProvider ~= "dex" then
   392                  local rft = ck.rt
   393                  local redirectArgs =
   394                      ngx.encode_args(
   395                      {
   396                          redirect_uri = me.hostUri
   397                      }
   398                  )
   399                  ngx.req.set_header("Content-Type", "application/x-www-form-urlencoded")
   400                  ngx.req.set_method(ngx.HTTP_POST)
   401                  redirectURL = me.getOidcProviderUri() .. "/protocol/openid-connect/logout?" .. redirectArgs
   402  
   403                  local postArgs =
   404                      ngx.encode_args(
   405                      {
   406                          refresh_token = ck.rt,
   407                          redirect_uri = redirectURL,
   408                          client_id = oidcClient
   409                      }
   410                  )
   411                  ngx.req.read_body()
   412  
   413                  ngx.req.set_body_data(postArgs)
   414              end
   415              me.deleteCookies("vz_authn", "vz_userinfo")
   416              ngx.redirect(redirectURL)
   417          end
   418      end
   419  
   420      function me.singleLogout()
   421          -- Single logout not supported yet.
   422          ngx.status = ngx.HTTP_METHOD_NOT_IMPLEMENTED
   423          ngx.say("501 Single Logout not supported.")
   424          ngx.exit(ngx.HTTP_METHOD_NOT_IMPLEMENTED)
   425      end
   426  
   427      function me.randomBase64(size)
   428          local randBytes = random.bytes(size)
   429          local encoded = base64.encode_base64url(randBytes)
   430          return string.sub(encoded, 1, size)
   431      end
   432  
   433      function me.read_file(path)
   434          local file = io.open(path, "rb")
   435          if not file then return nil end
   436          local content = file:read "*a"
   437          file:close()
   438          return content
   439      end
   440  
   441      function me.write_file(path, data)
   442        local file = io.open(path, "a+")
   443        if not file then return nil end
   444        file:write(data)
   445        file:close()
   446      end
   447  
   448      function me.hasCredentialType(authHeader, credentialType)
   449          if authHeader then
   450              local start, _ = authHeader:find(credentialType)
   451              if start then
   452                  return true
   453              end
   454          end
   455          return false
   456      end
   457  
   458      function me.requestUriMatches(requestUri, matchPath)
   459          if requestUri then
   460              if requestUri == matchPath then
   461                  return true
   462              end
   463              local start, _ = requestUri:find(matchPath..'?')
   464              if start == 1 then
   465                  return true
   466              end
   467          end
   468          return false
   469      end
   470  
   471      function me.startsWith(ingressHost, start)
   472          return string.sub(ingressHost,1,string.len(start)) == start
   473      end
   474  
   475      local authproxy_prefix = 'verrazzano-authproxy-'
   476      local in_cluster_namespace = '.verrazzano-system'
   477      local in_cluster_dns_suffix = '.svc.cluster.local'
   478      local vmi_system = '.vmi.system'
   479  
   480      function me.validateIngressHost(backend, hostname, in_cluster)
   481          -- assume backend, hostname, are always non-nil and non-empty
   482          local check_host = nil
   483          if in_cluster == true then
   484              -- try full path
   485              check_host = authproxy_prefix..backend..in_cluster_namespace..in_cluster_dns_suffix
   486              if check_host == hostname then
   487                  return backend
   488              end
   489              -- try with just the namespace
   490              check_host = authproxy_prefix..backend..in_cluster_namespace
   491              if check_host == hostname then
   492                  return backend
   493              end
   494              -- try without namespace or dns suffix
   495              check_host = authproxy_prefix..backend
   496              if check_host == hostname then
   497                  return backend
   498              end
   499          else
   500              if backend == 'verrazzano' then
   501                  check_host = backend..me.hostSuffix
   502              elseif backend == 'jaeger' then
   503                  check_host = backend..me.hostSuffix
   504              elseif backend == 'thanos-query' then
   505                  check_host = backend..me.hostSuffix
   506              elseif backend == 'thanos-query-store' then
   507                  check_host = backend..me.hostSuffix
   508              elseif backend == 'thanos-ruler' then
   509                  check_host = backend..me.hostSuffix
   510              elseif backend == 'opensearch-logging' then
   511                  check_host = 'opensearch.logging'..me.hostSuffix
   512              elseif backend == 'osd-logging' then
   513                  check_host = 'osd.logging'..me.hostSuffix
   514              elseif backend == 'alertmanager' then
   515                  check_host =  backend..me.hostSuffix
   516              else
   517                  check_host = backend..vmi_system..me.hostSuffix
   518              end
   519              if check_host == hostname then
   520                  return backend
   521              end
   522          end
   523          return 'invalid'
   524      end
   525  
   526      function me.getBackendNameFromIngressHost(ingressHost)
   527          local backend_name = 'unknown'
   528          local able_to_redirect = true
   529          local hostname = nil
   530          if ingressHost and #ingressHost > 0 then
   531              me.debug("ingressHost is '"..ingressHost.."'")
   532              -- Strip the port off, if present
   533              local first, last = nil
   534              first, last, hostname = ingressHost:find("^([^:]+)")
   535              if hostname and #hostname > 0 then
   536                  first, last, backend_name = hostname:find("^([^.]+)")
   537                  if backend_name and #backend_name > 0 then
   538                      -- Append '-logging' if the ingressHost is for OS or OSD from logging namespace
   539                      if me.startsWith(ingressHost, 'opensearch.logging') or me.startsWith(ingressHost, 'osd.logging') then
   540                          backend_name = backend_name .. '-logging'
   541                      end
   542                      -- Strip the auth proxy prefix from the extracted backend name if present
   543                      if string.sub(backend_name, 1, #authproxy_prefix) == authproxy_prefix then
   544                          backend_name = string.sub(backend_name, #authproxy_prefix+1, -1)
   545                          me.debug("Validating host (local): backend_name is '"..backend_name.."', hostname is '"..hostname.."'")
   546                          backend_name = me.validateIngressHost(backend_name, hostname, true)
   547                      else
   548                          me.debug("Validating host (ingress): backend_name is '"..backend_name.."', hostname is '"..hostname.."'")
   549                          backend_name = me.validateIngressHost(backend_name, hostname, false)
   550                      end
   551                  end
   552              end
   553          end
   554          local uri = ngx.var.request_uri
   555          if backend_name == "verrazzano" then
   556              local first, last = uri:find("/20210501/")
   557              if not first or first ~= 1 then
   558                  backend_name = "console"
   559              else
   560                  able_to_redirect = false
   561              end
   562          end
   563          if backend_name == "osd" then
   564              if not (uri == "/" or uri:find("/app/") == 1) then
   565                  able_to_redirect = false
   566              end
   567          end
   568          if backend_name == "kibana" then
   569             if not (uri == "/" or uri:find("/app/") == 1) then
   570                 able_to_redirect = false
   571             end
   572          end
   573          if able_to_redirect == true then
   574              me.debug("returning backend_name '"..backend_name.."', able_to_redirect is true")
   575          else
   576              me.debug("returning backend_name '"..backend_name.."', able_to_redirect is false")
   577          end
   578          return backend_name, able_to_redirect
   579      end
   580  
   581      function me.makeConsoleBackendUrl(port)
   582          return 'http://verrazzano-console.verrazzano-system.svc.cluster.local'..':'..port
   583      end
   584  
   585      function me.makeVmiBackendUrl(backend, port)
   586          return 'http://vmi-system-'..backend..'.verrazzano-system.svc.cluster.local'..':'..port
   587      end
   588  
   589      function me.makeMonitoringComponentBackendUrl(protocol, serviceName, port)
   590          return protocol..'://'..serviceName..'.verrazzano-monitoring.svc.cluster.local'..':'..port
   591      end
   592  
   593      function me.makeLoggingComponentBackendUrl(serviceName, port)
   594          return opensearchProtocol..'://'..serviceName..'.'..opensearchNamespace..'.svc.cluster.local'..':'..port
   595      end
   596  
   597      function me.getBackendServerUrlFromName(backend)
   598          local serverUrl = nil
   599          if backend == 'verrazzano' then
   600              -- assume we're going to the local server; if not, we'll fix up the url when we handle the remote call
   601              -- Route request to k8s API
   602              serverUrl = me.getLocalKubernetesApiUrl()
   603          elseif backend == 'console' then
   604              -- Route request to console
   605              serverUrl = me.makeConsoleBackendUrl('8000')
   606          elseif backend == 'grafana' then
   607              serverUrl = me.makeVmiBackendUrl(backend, '3000')
   608          elseif backend == 'prometheus' then
   609              serverUrl = me.makeMonitoringComponentBackendUrl('http', 'prometheus-operator-kube-p-prometheus', '9090')
   610          elseif backend == 'thanos-query' then
   611              serverUrl = me.makeMonitoringComponentBackendUrl('http', 'thanos-query-frontend', '9090')
   612          elseif backend == 'thanos-query-store' then
   613              serverUrl = me.makeMonitoringComponentBackendUrl('grpc', 'thanos-query-grpc', '10901')
   614          elseif backend == 'thanos-ruler' then
   615              serverUrl = me.makeMonitoringComponentBackendUrl('http', 'thanos-ruler', '9090')
   616          elseif backend == 'kibana' then
   617              serverUrl = me.makeLoggingComponentBackendUrl(osdService, '5601')
   618          elseif backend == 'osd' then
   619              serverUrl = me.makeLoggingComponentBackendUrl(osdService, '5601')
   620          elseif backend == 'elasticsearch' then
   621              serverUrl = me.makeLoggingComponentBackendUrl(opensearchService, '9200')
   622          elseif backend == 'opensearch' then
   623              serverUrl = me.makeLoggingComponentBackendUrl(opensearchService, '9200')
   624          elseif backend == 'opensearch-logging' then
   625              serverUrl = me.makeLoggingComponentBackendUrl('opensearch', '9200')
   626          elseif backend == 'osd-logging' then
   627              serverUrl = me.makeLoggingComponentBackendUrl('opensearch-dashboards', '5601')
   628          elseif backend == 'kiali' then
   629              serverUrl = me.makeVmiBackendUrl(backend, '20001')
   630          elseif backend == 'jaeger' then
   631              serverUrl = me.makeMonitoringComponentBackendUrl('http', 'jaeger-operator-jaeger-query', '16686')
   632          elseif backend == 'alertmanager' then
   633              serverUrl = me.makeMonitoringComponentBackendUrl('http', 'prometheus-operator-kube-p-alertmanager', '9093')
   634          else
   635              me.not_found("Invalid backend name '"..backend.."'")
   636          end
   637          return serverUrl
   638      end
   639  
   640      -- console originally sent access token by itself, as bearer token (originally obtained via pkce client)
   641      -- with combined proxy, console no longer handles tokens, but tests may be sending ID tokens.
   642      function me.handleBearerToken(authHeader)
   643          local found, index = authHeader:find('Bearer')
   644          if found then
   645              local token = string.sub(authHeader, index+2)
   646              if token then
   647                  me.debug("Found bearer token in authorization header")
   648                  me.oidcValidateBearerToken(token)
   649                  return token
   650              else
   651                  me.unauthorized("Missing token in authorization header")
   652              end
   653          end
   654          return nil
   655      end
   656  
   657      local basicCache = {}
   658  
   659      -- should only be called if some vz process is trying to access vmi using basic auth
   660      -- tokens are cached locally
   661      function me.handleBasicAuth(authHeader)
   662          -- me.debug("Checking for basic auth credentials")
   663          local found, index = authHeader:find('Basic')
   664          if not found then
   665              me.debug("No basic auth credentials found")
   666              return nil
   667          end
   668          local basicCred = string.sub(authHeader, index+2)
   669          if not basicCred then
   670              me.unauthorized("Invalid BasicAuth authorization header")
   671          end
   672          me.debug("Found basic auth credentials in authorization header")
   673          local now = ngx.time()
   674          local basicAuth = basicCache[basicCred]
   675          if basicAuth and (now < basicAuth.expiry) then
   676              me.debug("Returning cached token")
   677              return basicAuth.id_token
   678          end
   679          local decode, err = ngx.decode_base64(basicCred)
   680          if err then
   681              me.unauthorized("Unable to decode BasicAuth authorization header")
   682          end
   683          local found = decode:find(':')
   684          if not found then
   685              me.unauthorized("Invalid BasicAuth authorization header")
   686          end
   687          local u = decode:sub(1, found-1)
   688          local p = decode:sub(found+1)
   689          local tokenRes = me.oidcGetTokenWithBasicAuth(u, p)
   690          if not tokenRes then
   691              me.unauthorized("Could not get token")
   692          end
   693          me.oidcValidateIDTokenPG(tokenRes.id_token)
   694          local expires_in = tonumber(tokenRes.expires_in)
   695          for key, val in pairs(basicCache) do
   696              if val.expiry and now > val.expiry then
   697                  basicCache[key] = nil
   698              end
   699          end
   700          basicCache[basicCred] = {
   701              -- access_token = tokenRes.access_token,
   702              id_token = tokenRes.id_token,
   703              expiry = now + expires_in
   704          }
   705          return tokenRes.id_token
   706      end
   707  
   708      function me.getOidcProviderUri()
   709          if me.oidcProviderUri and me.oidcProviderUri ~= "" then
   710              return me.oidcProviderUri
   711          else
   712              return me.oidcProviderInClusterUri
   713          end
   714      end
   715  
   716      function me.getLocalOidcProviderUri()
   717          if me.oidcProviderInClusterUri and me.oidcProviderInClusterUri ~= "" then
   718              return me.oidcProviderInClusterUri
   719          else
   720              return me.oidcProviderUri
   721          end
   722      end
   723  
   724      function me.getOidcTokenUri()
   725          if me.oidcProvider == "dex" then
   726              return me.getLocalOidcProviderUri() .. "/token"
   727          end
   728  
   729          return me.getLocalOidcProviderUri() .. "/protocol/openid-connect/token"
   730      end
   731  
   732      function me.getOidcCertsUri()
   733          if me.oidcProvider == "dex" then
   734              return me.getLocalOidcProviderUri() .. "/keys"
   735          end
   736  
   737          return me.getLocalOidcProviderUri() .. "/protocol/openid-connect/certs"
   738      end
   739  
   740      function me.getOidcAuthUri()
   741          if me.oidcProvider == "dex" then
   742              return me.getOidcProviderUri() .. "/auth/local"
   743          end
   744  
   745          return me.getOidcProviderUri() .. "/protocol/openid-connect/auth"
   746      end
   747  
   748      function me.oidcAuthenticate()
   749          me.debug("Authenticating user")
   750          local sha256 = (require 'resty.sha256'):new()
   751          -- code verifier must be between 43 and 128 characters
   752          local codeVerifier = me.randomBase64(56)
   753          sha256:update(codeVerifier)
   754          local codeChallenge = base64.encode_base64url(sha256:final())
   755          local state = me.randomBase64(32)
   756          local nonce = me.randomBase64(32)
   757          local stateData = {
   758              state = state,
   759              request_uri = ngx.var.request_uri,
   760              code_verifier = codeVerifier,
   761              code_challenge = codeChallenge,
   762              nonce = nonce
   763          }
   764          local rawRedirectArgs = {
   765              client_id = oidcClient,
   766              response_type = 'code',
   767              scope = 'openid',
   768              code_challenge_method = 'S256',
   769              code_challenge = codeChallenge,
   770              state = state,
   771              nonce = nonce,
   772              redirect_uri = me.callbackUri
   773          }
   774  
   775          if me.oidcProvider == "dex" then
   776              rawRedirectArgs.scope = "openid email profile offline_access"
   777          else
   778              rawRedirectArgs.scope = "openid"
   779          end
   780  
   781          local redirectArgs = ngx.encode_args(rawRedirectArgs)
   782          local redirectURL = me.getOidcAuthUri() .. "?" .. redirectArgs
   783          -- there could be an existing (expired) vz_authn cookie.
   784          -- delete it (and vz_userinfo) to avoid exceeding max header size
   785          me.deleteCookies("vz_authn", "vz_userinfo")
   786          me.setCookie("vz_state", stateData, authStateTtlInSec, true)
   787          ngx.header["Cache-Control"] = "no-cache, no-store, max-age=0"
   788          ngx.redirect(redirectURL)
   789      end
   790  
   791      function me.oidcHandleCallback()
   792          me.debug("Handle authentication callback")
   793          local queryParams = me.queryParams(ngx.var.request_uri)
   794          local state = queryParams.state
   795          local code = queryParams.code
   796          local nonce = queryParams.nonce
   797          local cookie = me.readCookie("vz_state")
   798          if not cookie then
   799              me.unauthorized("Missing state cookie")
   800          end
   801          me.deleteCookies("vz_state")
   802          local stateCk = cookie.state
   803          -- local nonceCk = cookie.nonce
   804          local request_uri = cookie.request_uri
   805  
   806          if (state == nil) or (stateCk == nil) then
   807              me.unauthorized("Missing callback state")
   808          else
   809              if state ~= stateCk then
   810                  me.unauthorized("Invalid callback state")
   811              end
   812              if not cookie.code_verifier then
   813                  me.unauthorized("Invalid code_verifier")
   814              end
   815              local tokenRes = me.oidcGetTokenWithCode(code, cookie.code_verifier, me.callbackUri)
   816              if tokenRes then
   817                  me.oidcValidateIDTokenPKCE(tokenRes.id_token)
   818                  me.tokenToCookie(tokenRes)
   819                  ngx.redirect(request_uri)
   820              end
   821              me.unauthorized("Failed to obtain token with code")
   822          end
   823      end
   824  
   825      function me.oidcTokenRequest(formArgs)
   826          me.debug("Requesting token from OP")
   827          local tokenUri = me.getOidcTokenUri()
   828          local http = require "resty.http"
   829          local httpc = http.new()
   830          local res, err = httpc:request_uri(tokenUri, {
   831              method = "POST",
   832              body = ngx.encode_args(formArgs),
   833              headers = {
   834                  ["Content-Type"] = "application/x-www-form-urlencoded",
   835              }
   836          })
   837          if err then
   838              me.error("Failed requesting token from " .. me.oidcProvider .. ": " .. err)
   839              me.unauthorized("Error requesting token", err)
   840          end
   841          if not res then
   842              me.info("Failed requesting token from " .. me.oidcProvider .. ": response is nil")
   843              me.unauthorized("Error requesting token: response is nil")
   844          end
   845          if not (res.status == 200) then
   846              me.info(
   847                  "Failed requesting token from " .. me.oidcProvider .. ": response status code is " ..
   848                      res.status .. " and response body is " .. res.body
   849              )
   850              me.unauthorized(
   851                  "Error requesting token: response status code is " .. res.status .. " and response body is " .. res.body
   852              )
   853          end
   854          local tokenRes = cjson.decode(res.body)
   855          if tokenRes.error or tokenRes.error_description then
   856              me.info("Failed requesting token from " .. me.oidcProvider .. ": " .. tokenRes.error_description)
   857              me.unauthorized("Error requesting token: " .. tokenRes.error_description)
   858          end
   859          return tokenRes
   860      end
   861  
   862      function me.oidcGetTokenWithBasicAuth(u, p)
   863          local params = {
   864                      grant_type = 'password',
   865                      scope = 'openid',
   866                      client_id = oidcDirectAccessClient,
   867                      password = p,
   868                      username = u
   869                  }
   870          if me.oidcProvider == "dex" then
   871              params.scope = "openid profile"
   872          end
   873          return me.oidcTokenRequest(params)
   874      end
   875  
   876      function me.oidcGetTokenWithCode(code, verifier, callbackUri)
   877          local params = {
   878                  grant_type = "authorization_code",
   879                  client_id = oidcClient,
   880                  code = code,
   881                  code_verifier = verifier,
   882                  redirect_uri = callbackUri
   883              }
   884          if me.oidcProvider == "dex" then
   885              params.client_secret = oidcClientSecret
   886              params.scope = "openid offline_access email"
   887          end
   888          return me.oidcTokenRequest(params)
   889      end
   890  
   891      function me.oidcRefreshToken(rft, callbackUri)
   892          return me.oidcTokenRequest({
   893                          grant_type = 'refresh_token',
   894                          client_id = oidcClient,
   895                          refresh_token = rft,
   896                          redirect_uri = callbackUri
   897                      })
   898      end
   899  
   900      function me.oidcValidateBearerToken(token)
   901          -- console sends access tokens obtained via PKCE client
   902          -- test code sends ID tokens obtained from the PG client
   903          -- need to accept either type in Authorization header (for now)
   904          local clients = { oidcClient, oidcDirectAccessClient }
   905          if oidcClient ~= adminClusterOidcClient then
   906              clients = { oidcClient, adminClusterOidcClient, oidcDirectAccessClient }
   907          end
   908          local claim_spec = {
   909              typ = validators.equals_any_of({"Bearer", "ID"}),
   910              iss = validators.equals(me.oidcIssuerUri),
   911              azp = validators.equals_any_of(clients)
   912          }
   913  
   914          if me.oidcProvider == "dex" then
   915              claim_spec = {
   916                  iss = validators.equals(me.oidcIssuerUri),
   917                  aud = validators.equals_any_of(clients)
   918              }
   919          end
   920  
   921          me.oidcValidateTokenWithClaims(token, claim_spec)
   922      end
   923  
   924      function me.oidcValidateIDTokenPKCE(token)
   925          me.oidcValidateToken(token, "ID", me.oidcIssuerUri, oidcClient)
   926      end
   927  
   928      function me.oidcValidateIDTokenPG(token)
   929          if not me.oidcIssuerUriLocal then
   930              me.oidcValidateToken(token, "ID", me.oidcIssuerUri, oidcDirectAccessClient)
   931          else
   932              me.oidcValidateToken(token, "ID", me.oidcIssuerUriLocal, oidcDirectAccessClient)
   933          end
   934      end
   935  
   936      function me.oidcValidateToken(token, expectedType, expectedIssuer, clientName)
   937          if not token or token == "" then
   938              me.unauthorized("Nil or empty token")
   939          end
   940          if not expectedType then
   941              me.unauthorized("Nil or empty expectedType")
   942          end
   943          if not expectedIssuer then
   944              me.unauthorized("Nil or empty expectedIssuer")
   945          end
   946          if not clientName then
   947              me.unauthorized("Nil or empty clientName")
   948          end
   949          local claim_spec = {
   950              typ = validators.equals( expectedType ),
   951              iss = validators.equals( expectedIssuer ),
   952              azp = validators.equals( clientName )
   953          }
   954          if me.oidcProvider == "dex" then
   955              claim_spec = {
   956                  iss = validators.equals(me.oidcIssuerUri),
   957                  aud = validators.equals(clientName)
   958              }
   959          end
   960          me.oidcValidateTokenWithClaims(token, claim_spec)
   961      end
   962  
   963      function me.oidcValidateTokenWithClaims(token, claim_spec)
   964          me.debug("Validating JWT token")
   965          local default_claim_spec = {
   966              iat = validators.is_not_before(),
   967              exp = validators.is_not_expired(),
   968              aud = validators.required()
   969          }
   970          -- passing verify a function to retrieve key didn't seem to work, so doing load then verify
   971          local jwt_obj = jwt:load_jwt(token)
   972          if (not jwt_obj) or (not jwt_obj.header) or (not jwt_obj.header.kid) then
   973              me.unauthorized("Failed to load token or no kid")
   974          end
   975          local publicKey = me.publicKey(jwt_obj.header.kid)
   976          if not publicKey then
   977              me.unauthorized("No public key found")
   978          end
   979          -- me.debug("TOKEN: iss is "..jwt_obj.payload.iss)
   980          -- me.debug("TOKEN: oidcIssuerUri is"..me.oidcIssuerUri)
   981          local verified = jwt:verify_jwt_obj(publicKey, jwt_obj, default_claim_spec, claim_spec)
   982          if not verified or (tostring(jwt_obj.valid) == "false" or tostring(jwt_obj.verified) == "false") then
   983              me.unauthorized("Failed to validate token", jwt_obj.reason)
   984          end
   985      end
   986  
   987      function me.isAuthorized(idToken)
   988          me.debug("Checking for required role '"..requiredRole.."'")
   989          local id_token = jwt:load_jwt(idToken)
   990          local userRoles = me.getRoles(idToken)
   991          me.debug("Roles available for users '"..userRoles.."'")
   992          if userRoles:match(requiredRole) then
   993              me.debug ("Required role '"..requiredRole.."' found")
   994              return true
   995          else
   996              me.debug ("Required role '"..requiredRole.."' not found")
   997              return false
   998          end
   999      end
  1000  
  1001      function me.usernameFromIdToken(idToken)
  1002          -- me.debug("usernameFromIdToken: fetching preferred_username")
  1003          local id_token = jwt:load_jwt(idToken)
  1004          if me.oidcProvider == "dex" then
  1005              if id_token and id_token.payload and id_token.payload.name then
  1006                  return id_token.payload.name
  1007              end
  1008              me.unauthorized("nameIdToken: name not found")
  1009          end
  1010  
  1011          if id_token and id_token.payload and id_token.payload.preferred_username then
  1012              return id_token.payload.preferred_username
  1013          end
  1014          me.unauthorized("usernameFromIdToken: preferred_username not found")
  1015      end
  1016  
  1017      -- returns id token, token is refreshed first, if necessary.
  1018      -- nil token returned if no session or the refresh token has expired
  1019      function me.getTokenFromSession()
  1020          -- me.debug("Check for existing session")
  1021          local ck = me.readCookie("vz_authn")
  1022          if ck then
  1023              me.debug("Existing session found")
  1024              local rft = ck.rt
  1025              local now = ngx.time()
  1026              local expiry = tonumber(ck.expiry)
  1027              local refresh_expiry = tonumber(ck.refresh_expiry)
  1028              if now < expiry then
  1029                  -- me.debug("Returning ID token")
  1030                  return ck.it
  1031              else
  1032                  if now < refresh_expiry then
  1033                      me.debug("Token is expired, refreshing")
  1034                      local tokenRes = me.oidcRefreshToken(rft, me.callbackUri)
  1035                      if tokenRes then
  1036                          me.oidcValidateIDTokenPKCE(tokenRes.id_token)
  1037                          me.tokenToCookie(tokenRes)
  1038                          -- me.debug("Token refreshed",  tokenRes)
  1039                          return tokenRes.id_token
  1040                      else
  1041                          me.debug("No valid response from token refresh")
  1042                      end
  1043                  else
  1044                      me.debug("Refresh token expired, cannot refresh")
  1045                  end
  1046              end
  1047          else
  1048              me.debug("No existing session found")
  1049          end
  1050          -- no valid token found, delete cookie
  1051          me.deleteCookies("vz_authn", "vz_userinfo")
  1052          return nil
  1053      end
  1054  
  1055      function me.tokenToCookie(tokenRes)
  1056          -- Do we need access_token? too big > 4k
  1057          local cookiePairs = {
  1058              rt = tokenRes.refresh_token,
  1059              -- at = tokenRes.access_token,
  1060              it = tokenRes.id_token
  1061          }
  1062          -- Cookie to save username and email with http-only diabled
  1063          local userCookiePairs = {
  1064              username = ""
  1065          }
  1066          local id_token = jwt:load_jwt(tokenRes.id_token)
  1067          local expires_in = tonumber(tokenRes.expires_in)
  1068          local refresh_expires_in = expires_in
  1069          if tokenRes.refresh_expires_in then
  1070              refresh_expires_in = tonumber(tokenRes.refresh_expires_in)
  1071          end
  1072  
  1073          local now = ngx.time()
  1074          local issued_at = now
  1075          if id_token and id_token.payload then
  1076              if id_token.payload.iat then
  1077                  issued_at = tonumber(id_token.payload.iat)
  1078              else
  1079                  if id_token.payload.auth_time then
  1080                      issued_at = tonumber(id_token.payload.auth_time)
  1081                  end
  1082              end
  1083              if id_token.payload.preferred_username then
  1084                  userCookiePairs.username = id_token.payload.preferred_username
  1085              end
  1086          end
  1087          local skew = now - issued_at
  1088          -- Expire 30 secs before actual time
  1089          local expiryBuffer = 30
  1090          cookiePairs.expiry = now + expires_in - skew - expiryBuffer
  1091          cookiePairs.refresh_expiry = now + refresh_expires_in - skew - expiryBuffer
  1092          userCookiePairs.expiry = now + expires_in - skew - expiryBuffer
  1093          userCookiePairs.refresh_expiry = now + refresh_expires_in - skew - expiryBuffer
  1094          local expiresInSec = refresh_expires_in - expiryBuffer
  1095          me.setCookie("vz_authn", cookiePairs, expiresInSec, true)
  1096          me.setCookie("vz_userinfo", userCookiePairs, expiresInSec, false)
  1097      end
  1098  
  1099      function me.setCookie(ckName, cookiePairs, expiresInSec, httponly)
  1100          local ck = require "resty.cookie"
  1101          local cookie, err = ck:new()
  1102          if not cookie then
  1103              me.debug("Error setting cookie "..ckName..": "..err)
  1104              return
  1105          end
  1106          local expires = ngx.cookie_time(ngx.time() + expiresInSec)
  1107          local ckValue = ""
  1108          if ckName == "vz_userinfo" then
  1109              -- No need to encrypt in case of vz_userinfo
  1110              for key, value in pairs(cookiePairs) do
  1111                  ckValue = ckValue..key.."="..value..","
  1112              end
  1113              ckValue = ckValue:sub(1, -2)
  1114          else
  1115              ckValue = me.aes256:encrypt(cjson.encode(cookiePairs))
  1116          end
  1117          ckValue = base64.encode_base64url(ckValue)
  1118          cookie:set({key=ckName, value=ckValue, path="/", secure=true, httponly=httponly, expires=expires})
  1119      end
  1120  
  1121       function me.deleteCookies(...)
  1122          local cookies = {}
  1123          local arg = {...}
  1124          for i,v in ipairs(arg) do
  1125              cookies[i] = tostring(v)..'=; Path=/; Secure; HttpOnly; Expires=Thu, 01 Jan 1970 00:00:00 UTC;'
  1126          end
  1127          ngx.header["Set-Cookie"] = cookies
  1128      end
  1129  
  1130      function me.readCookie(ckName)
  1131          if not ckName then
  1132              return nil
  1133          end
  1134          local cookie, err = require("resty.cookie"):new()
  1135          local ck = cookie:get(ckName)
  1136          if not ck then
  1137              me.debug("Cookie not found")
  1138              return nil
  1139          end
  1140          local decoded = base64.decode_base64url(ck)
  1141          if not decoded then
  1142              me.debug("Cookie not decoded")
  1143              return nil
  1144          end
  1145          local json = me.aes256:decrypt(decoded)
  1146          if not json then
  1147              me.debug("Cookie not decrypted")
  1148              return nil
  1149          end
  1150          return cjson.decode(json)
  1151      end
  1152  
  1153      local certs = {}
  1154      local moduli = {}
  1155      local exponents = {}
  1156  
  1157      function me.realmCerts(kid)
  1158          local pk = certs[kid]
  1159          local modulus = moduli[kid]
  1160          local exponent = exponents[kid]
  1161          if pk then
  1162              return pk, nil, nil
  1163          elseif modulus and exponent then
  1164              return nil, modulus, exponent
  1165          end
  1166          local http = require "resty.http"
  1167          local httpc = http.new()
  1168          local certsUri = me.getOidcCertsUri()
  1169          local res, err = httpc:request_uri(certsUri)
  1170          if err then
  1171              me.error("Could not retrieve certs: "..err)
  1172              return nil
  1173          end
  1174          local data = cjson.decode(res.body)
  1175          if not (data.keys) then
  1176              me.error("Failed to find keys: key object is nil")
  1177              return nil
  1178          end
  1179          for i, key in pairs(data.keys) do
  1180              if key.kid and key.x5c then
  1181                  certs[key.kid] = key.x5c
  1182              elseif key.kty == "RSA" and key.n and key.e then
  1183                  moduli[key.kid] = key.n
  1184                  exponents[key.kid] = key.e
  1185              end
  1186          end
  1187          return certs[kid], moduli[kid], exponents[kid]
  1188      end
  1189  
  1190      function me.publicKey(kid)
  1191          local x5c, n, e = me.realmCerts(kid)
  1192          if x5c and #x5c ~= 0 then
  1193              return "-----BEGIN CERTIFICATE-----\n" .. x5c[1] .. "\n-----END CERTIFICATE-----"
  1194          end
  1195  
  1196          if n and e then
  1197              return me.generate_key(n, e)
  1198          end
  1199  
  1200          return nil
  1201      end
  1202  
  1203      -- api-proxy - methods for handling multi-cluster k8s API requests
  1204  
  1205      local vzApiHost = os.getenv("VZ_API_HOST")
  1206      local vzApiVersion = os.getenv("VZ_API_VERSION")
  1207  
  1208      function me.getServiceAccountToken()
  1209        me.debug("Read service account token")
  1210        local serviceAccountToken = me.read_file("/run/secrets/kubernetes.io/serviceaccount/token")
  1211        if not (serviceAccountToken) then
  1212          me.unauthorized("No service account token present in pod.")
  1213        end
  1214        return serviceAccountToken
  1215      end
  1216  
  1217      function me.getLocalKubernetesApiUrl()
  1218        local host = os.getenv("KUBERNETES_SERVICE_HOST")
  1219        local port = os.getenv("KUBERNETES_SERVICE_PORT")
  1220        local serverUrl = "https://" .. host .. ":" .. port
  1221        return serverUrl
  1222      end
  1223  
  1224      function me.getK8SResource(token, resourcePath)
  1225        local http = require "resty.http"
  1226        local httpc = http.new()
  1227        local res, err = httpc:request_uri("https://" .. vzApiHost .. "/" .. vzApiVersion .. resourcePath,{
  1228            headers = {
  1229                ["Authorization"] = "Bearer "..token
  1230            },
  1231        })
  1232        if err then
  1233          me.unauthorized("Error accessing vz api", err)
  1234        end
  1235        if not(res) or not (res.body) then
  1236          me.unauthorized("Unable to get k8s resource.")
  1237        end
  1238        local cjson = require "cjson"
  1239        return cjson.decode(res.body)
  1240      end
  1241  
  1242      function me.getVMC(token, cluster)
  1243        return me.getK8SResource(token, "/apis/clusters.verrazzano.io/v1alpha1/namespaces/verrazzano-mc/verrazzanomanagedclusters/" .. cluster)
  1244      end
  1245  
  1246      function me.getSecret(token, secret)
  1247        return me.getK8SResource(token, "/api/v1/namespaces/verrazzano-mc/secrets/" .. secret)
  1248      end
  1249  
  1250      function me.handleLocalAPICall(token)
  1251          local uri = ngx.var.uri
  1252          local first, last = uri:find("/"..vzApiVersion)
  1253          if first and first == 1 then
  1254              uri = string.sub(uri, last+1)
  1255              ngx.req.set_uri(uri)
  1256          end
  1257  
  1258          local serviceAccountToken = me.getServiceAccountToken()
  1259          me.debug("Set service account bearer token as Authorization header")
  1260          ngx.req.set_header("Authorization", "Bearer " .. serviceAccountToken)
  1261  
  1262          if not token then
  1263              me.unauthenticated("Invalid token")
  1264          end
  1265  
  1266          local jwt_obj = jwt:load_jwt(token)
  1267          if not jwt_obj then
  1268              me.unauthenticated("Invalid token")
  1269          end
  1270  
  1271          local groups = {}
  1272  
  1273          if jwt_obj.payload and jwt_obj.payload.sub then
  1274              -- Uid is ignored prior to kubernetes v1.22
  1275              me.debug(("Adding sub as Impersonate-Uid: " .. jwt_obj.payload.sub))
  1276              ngx.req.set_header("Impersonate-Uid", jwt_obj.payload.sub)
  1277              -- this group is needed for discovery, but not automatically set for impersonated users prior to v1.20.
  1278              -- the group is ignored if duplicated >= v1.20, so no harm done if we always send it.
  1279              -- adding it here so that it's present IFF we actually have a subject.
  1280              local sys_auth = "system:authenticated"
  1281              me.debug(("Including group: " .. sys_auth))
  1282              table.insert(groups, sys_auth)
  1283          end
  1284          if jwt_obj.payload and jwt_obj.payload.preferred_username then
  1285              me.debug(("Adding preferred_username as Impersonate-User: " .. jwt_obj.payload.preferred_username))
  1286              ngx.req.set_header("Impersonate-User", jwt_obj.payload.preferred_username)
  1287          end
  1288  
  1289          if me.oidcProvider == "dex" and jwt_obj.payload and jwt_obj.payload.name then
  1290              me.debug(("Adding name as Impersonate-User: " .. jwt_obj.payload.name))
  1291              ngx.req.set_header("Impersonate-User", jwt_obj.payload.name)
  1292              me.debug("Adding verrazzano-admins as Impersonate-Group")
  1293              table.insert(groups, "verrazzano-admins")
  1294          end
  1295  
  1296          if jwt_obj.payload and jwt_obj.payload.groups then
  1297              for key, grp in pairs(jwt_obj.payload.groups) do
  1298                  table.insert(groups, grp)
  1299              end
  1300          end
  1301          if #groups > 0 then
  1302              me.debug(("Adding groups as Impersonate-Group: " .. table.concat(groups, ", ")))
  1303              ngx.req.set_header("Impersonate-Group", groups)
  1304          end
  1305      end
  1306  
  1307      function me.handleExternalAPICall(token)
  1308          local args = ngx.req.get_uri_args()
  1309  
  1310          me.debug("Read vmc resource for " .. args.cluster)
  1311          local vmc = me.getVMC(token, args.cluster)
  1312          if not(vmc) or not(vmc.status) or not(vmc.status.apiUrl) then
  1313              me.unauthorized("Unable to fetch vmc api url for vmc " .. args.cluster)
  1314          end
  1315          local serverUrl = vmc.status.apiUrl
  1316  
  1317          -- To access managed cluster api server on self signed certificates, the admin cluster api server needs ca certificates for the managed cluster.
  1318          -- A secret is created in admin cluster during multi cluster setup that contains the ca certificate.
  1319          -- Here we read the name of that secret from vmc spec and retrieve the secret from cluster and read the cacrt field.
  1320          -- The value of cacrt field is decoded to get the ca certificate and is appended to file being pointed to by the proxy_ssl_trusted_certificate variable.
  1321  
  1322          if not(vmc.spec) or not(vmc.spec.caSecret) then
  1323              me.debug("ca secret name not present on vmc resource, assuming well known CA certificate exists for managed cluster " .. args.cluster)
  1324              do return end
  1325          end
  1326  
  1327          local secret = me.getSecret(token, vmc.spec.caSecret)
  1328          if not(secret) or not(secret.data) or not(secret.data["cacrt"]) or secret.data["cacrt"] == "" then
  1329              me.debug("Unable to fetch ca secret for vmc, assuming well known CA certificate exists for managed cluster " .. args.cluster)
  1330              do return end
  1331          end
  1332  
  1333          local decodedSecret = ngx.decode_base64(secret.data["cacrt"])
  1334          if not(decodedSecret) then
  1335              me.unauthorized("Unable to decode ca secret for vmc to access api server of managed cluster " .. args.cluster)
  1336          end
  1337  
  1338          local startIndex, _ = string.find(decodedSecret, "-----BEGIN CERTIFICATE-----")
  1339          local _, endIndex = string.find(decodedSecret, "-----END CERTIFICATE-----")
  1340          if startIndex >= 1 and endIndex > startIndex then
  1341              me.write_file("/etc/nginx/upstream.pem", string.sub(decodedSecret, startIndex, endIndex))
  1342          end
  1343  
  1344          -- remove the cluster query param
  1345          args["cluster"] = nil
  1346          ngx.req.set_uri_args(args)
  1347  
  1348          -- propagate the user's token as a bearer token, remote cluster won't have access to the session.
  1349          ngx.req.set_header("Authorization", "Bearer "..token)
  1350  
  1351          return serverUrl
  1352      end
  1353  
  1354      function me.isBodyValidJson()
  1355          ngx.req.read_body()
  1356          local data = ngx.req.get_body_data()
  1357          if data ~= nil then
  1358              local decoder = require("cjson.safe").decode
  1359              local decoded_data, err = decoder(data)
  1360              if err then
  1361                  me.debug("Invalid request payload: " .. data)
  1362                  return false
  1363              end
  1364          end
  1365          return true
  1366      end
  1367      
  1368      function me.isOriginAllowed(origin, backend, ingressUri)
  1369          -- As per https://datatracker.ietf.org/doc/rfc6454, "User Agent Requirements" section a "null" value for 
  1370          -- Origin header is set by user agents for privacy-sensitive contexts. However it does not defined what 
  1371          -- a privacy-sensitive context means. The "Privacy-Sensitive Contexts" section of https://wiki.mozilla.org/Security/Origin
  1372          -- defines certain contexts as privacy sensitive but there also it does not explain behaviour of server for the
  1373          -- "Access-Control-Allow-Origin" header. Therefore we do not allow such requests.
  1374          if origin == "null" then
  1375              return false
  1376          end
  1377          
  1378          if origin == ingressUri then
  1379              return true
  1380          end
  1381  
  1382          local allowedOrigins = nil
  1383          if backend == 'verrazzano' then
  1384              allowedOrigins = os.getenv("VZ_API_ALLOWED_ORIGINS")
  1385          elseif backend == 'console' then
  1386              allowedOrigins = os.getenv("VZ_CONSOLE_ALLOWED_ORIGINS")
  1387          elseif backend == 'grafana' then
  1388              allowedOrigins = os.getenv("VZ_GRAFANA_ALLOWED_ORIGINS")
  1389          elseif backend == 'prometheus' then
  1390              allowedOrigins = os.getenv("VZ_PROMETHEUS_ALLOWED_ORIGINS")
  1391          elseif backend == 'thanos-query' then
  1392              allowedOrigins = os.getenv("VZ_THANOS_QUERY_ALLOWED_ORIGINS")
  1393          elseif backend == 'thanos-query-store' then
  1394              allowedOrigins = os.getenv("VZ_QUERY_STORE_ALLOWED_ORIGINS")
  1395          elseif backend == 'thanos-ruler' then
  1396              allowedOrigins = os.getenv("VZ_THANOS_RULER_ALLOWED_ORIGINS")
  1397          elseif backend == 'kibana' then
  1398              allowedOrigins = os.getenv("VZ_KIBANA_ALLOWED_ORIGINS")
  1399          elseif backend == 'osd' then
  1400              allowedOrigins = os.getenv("VZ_KIBANA_ALLOWED_ORIGINS")
  1401          elseif backend == 'elasticsearch' then
  1402              allowedOrigins = os.getenv("VZ_ES_ALLOWED_ORIGINS")
  1403          elseif backend == 'opensearch' then
  1404              allowedOrigins = os.getenv("VZ_ES_ALLOWED_ORIGINS")
  1405          elseif backend == 'opensearch-logging' then
  1406              allowedOrigins = os.getenv("VZ_OS_LOGGING_ALLOWED_ORIGINS")
  1407          elseif backend == 'osd-logging' then
  1408              allowedOrigins = os.getenv("VZ_OSD_LOGGING_ALLOWED_ORIGINS")
  1409          elseif backend == 'kiali' then
  1410              allowedOrigins = os.getenv("VZ_KIALI_ALLOWED_ORIGINS")
  1411          elseif backend == 'jaeger' then
  1412              allowedOrigins = os.getenv("VZ_JAEGER_ALLOWED_ORIGINS")
  1413          elseif backend == 'alertmanager' then
  1414              allowedOrigins = os.getenv("VZ_ALERTMANAGER_ALLOWED_ORIGINS")
  1415          end
  1416          
  1417          if not allowedOrigins or allowedOrigins == "" then
  1418              return false
  1419          end
  1420          
  1421          for requestOrigin in string.gmatch(origin, '([^ ]+)') do
  1422              local originFound = false
  1423              for allowedOrigin in string.gmatch(allowedOrigins, '([^,]+)') do
  1424                  if requestOrigin == allowedOrigin then
  1425                      originFound = true
  1426                  end
  1427              end
  1428              if originFound == false then
  1429                  return false
  1430              end
  1431          end
  1432          return true
  1433      end
  1434  
  1435      -- Implementation of openidc_pem_from_rsa_n_and_e from lua-resty-openidc to generate public key from
  1436      -- modulus and operand values of token
  1437      function me.generate_key(n, e)
  1438          me.debug("getting PEM public key from n and e parameters of json public key")
  1439          local der_key = {
  1440              unb64url(n), unb64url(e)
  1441          }
  1442          local encoded_key = me.encode_sequence_of_integer(der_key)
  1443          local pem = me.der2pem(me.encode_sequence({
  1444              me.encode_sequence({
  1445              "\6\9\42\134\72\134\247\13\1\1\1" -- OID :rsaEncryption
  1446                  .. "\5\0" -- ASN.1 NULL of length 0
  1447              }),
  1448              me.encode_bit_string(encoded_key)
  1449          }), "PUBLIC KEY")
  1450          me.debug("Generated pem key from n and e: ", pem)
  1451          return pem
  1452      end
  1453  
  1454      function me.encode_length(length)
  1455          if length < 0x80 then
  1456              return string.char(length)
  1457          elseif length < 0x100 then
  1458              return string.char(0x81, length)
  1459          elseif length < 0x10000 then
  1460              return string.char(0x82, math.floor(length / 0x100), length % 0x100)
  1461          end
  1462          me.error("Can't encode lengths over 65535")
  1463      end
  1464  
  1465      function me.encode_sequence(array, of)
  1466          local encoded_array = array
  1467          if of then
  1468              encoded_array = {}
  1469              for i = 1, #array do
  1470                  encoded_array[i] = of(array[i])
  1471              end
  1472          end
  1473          encoded_array = table.concat(encoded_array)
  1474  
  1475          return string.char(0x30) .. me.encode_length(#encoded_array) .. encoded_array
  1476      end
  1477  
  1478      function me.encode_sequence_of_integer(array)
  1479          return me.encode_sequence(array, me.encode_binary_integer)
  1480      end
  1481  
  1482      function me.der2pem(data, typ)
  1483          local wrap = ('.'):rep(64)
  1484          local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n"
  1485          typ = typ:upper() or "CERTIFICATE"
  1486          data = b64(data)
  1487          return string.format(envelope, typ, data:gsub(wrap, '%0\n', (#data - 1) / 64), typ)
  1488      end
  1489  
  1490      function me.encode_bit_string(array)
  1491          local s = "\0" .. array -- first octet holds the number of unused bits
  1492          return "\3" .. me.encode_length(#s) .. s
  1493      end
  1494  
  1495      function me.encode_binary_integer(bytes)
  1496          if bytes:byte(1) > 127 then
  1497              -- We currenly only use this for unsigned integers,
  1498              -- however since the high bit is set here, it would look
  1499              -- like a negative signed int, so prefix with zeroes
  1500              bytes = "\0" .. bytes
  1501          end
  1502          return "\2" .. me.encode_length(#bytes) .. bytes
  1503      end
  1504  
  1505      return me
  1506    nginx.conf: |
  1507      #user  nobody;
  1508      worker_processes  1;
  1509  
  1510      error_log  /var/log/nginx/error.log info;
  1511      pid        logs/nginx.pid;
  1512  
  1513      env KUBERNETES_SERVICE_HOST;
  1514      env KUBERNETES_SERVICE_PORT;
  1515      env VZ_API_HOST;
  1516      env VZ_API_VERSION;
  1517      env VZ_API_ALLOWED_ORIGINS;
  1518      env VZ_CONSOLE_ALLOWED_ORIGINS;
  1519      env VZ_GRAFANA_ALLOWED_ORIGINS;
  1520      env VZ_PROMETHEUS_ALLOWED_ORIGINS;
  1521      env VZ_THANOS_QUERY_ALLOWED_ORIGINS;
  1522      env VZ_QUERY_STORE_ALLOWED_ORIGINS;
  1523      env VZ_THANOS_RULER_ALLOWED_ORIGINS;
  1524      env VZ_KIBANA_ALLOWED_ORIGINS;
  1525      env VZ_ES_ALLOWED_ORIGINS;
  1526      env VZ_KIALI_ALLOWED_ORIGINS;
  1527      env VZ_JAEGER_ALLOWED_ORIGINS;
  1528      env VZ_OS_LOGGING_ALLOWED_ORIGINS;
  1529      env VZ_OSD_LOGGING_ALLOWED_ORIGINS;
  1530      env VZ_ALERTMANAGER_ALLOWED_ORIGINS;
  1531  
  1532      events {
  1533          worker_connections  1024;
  1534      }
  1535  
  1536      http {
  1537          include       mime.types;
  1538          default_type  application/octet-stream;
  1539  
  1540          #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
  1541          #                  '$status $body_bytes_sent "$http_referer" '
  1542          #                  '"$http_user_agent" "$http_x_forwarded_for"';
  1543          log_format  json_combined escape=json '{'
  1544              '"@timestamp": "$time_iso8601", ' # local time in the ISO 8601 standard format
  1545              '"req_id": "$request_id", ' # the unique request id
  1546              '"upstream_status": "$upstream_status", '
  1547              '"upstream_addr": "$upstream_addr", ' # upstream backend server for proxied requests
  1548              '"message": "$request_method $http_host$request_uri", '
  1549              '"http_request": {'
  1550                  '"request_method": "$request_method", ' # request method
  1551                  '"requestUrl": "$http_host$request_uri", '
  1552                  '"status": "$status", ' # response status code
  1553                  '"requestSize": "$request_length", ' # request length (including headers and body)
  1554                  '"responseSize": "$upstream_response_length", ' # upstream response length
  1555                  '"userAgent": "$http_user_agent", ' # user agent
  1556                  '"remoteIp": "$remote_addr", ' # client IP
  1557                  '"referer": "$http_referer", ' # HTTP referer
  1558                  '"latency": "$upstream_response_time s", ' # time spent receiving upstream body
  1559                  '"protocol": "$server_protocol" ' # request protocol, like HTTP/1.1 or HTTP/2.0
  1560              '}'
  1561            '}';
  1562          sendfile        on;
  1563          #tcp_nopush     on;
  1564  
  1565          # Posts from Fluentd can require more than the default 1m max body size
  1566          client_max_body_size {{ .MaxRequestSize }};
  1567          proxy_buffer_size {{ .ProxyBufferSize }};
  1568          client_body_buffer_size 256k;
  1569  
  1570          #keepalive_timeout  0;
  1571          keepalive_timeout  65;
  1572  
  1573          #gzip  on;
  1574  
  1575          lua_package_path '/usr/local/share/lua/5.1/?.lua;;';
  1576          lua_package_cpath '/usr/local/lib/lua/5.1/?.so;;';
  1577          resolver _NAMESERVER_;
  1578  
  1579          # cache for discovery metadata documents
  1580          lua_shared_dict discovery 1m;
  1581          #  cache for JWKs
  1582          lua_shared_dict jwks 1m;
  1583  
  1584          #access_log  logs/host.access.log  main;
  1585          access_log /dev/stderr json_combined;
  1586          server_tokens off;
  1587  
  1588          #charset koi8-r;
  1589          expires           0;
  1590          #add_header        Cache-Control private;
  1591          add_header        Cache-Control no-store always;
  1592  
  1593          proxy_http_version 1.1;
  1594  
  1595          # Verrazzano api and console
  1596          server {
  1597              listen       8775;
  1598              server_name  verrazzano-proxy;
  1599  
  1600              # api
  1601              location / {
  1602                  lua_ssl_verify_depth 2;
  1603                  lua_ssl_trusted_certificate /etc/nginx/upstream.pem;
  1604                  # oauth-proxy ssl certs location: lua_ssl_trusted_certificate /etc/nginx/all-ca-certs.pem;
  1605  
  1606                  set $oidc_user "";
  1607                  set $oidc_user_roles "";
  1608                  set $backend_server_url "";
  1609                  rewrite_by_lua_file /etc/nginx/conf.lua;
  1610                  proxy_set_header X-WEBAUTH-USER $oidc_user;
  1611                  proxy_set_header x-proxy-roles $oidc_user_roles;
  1612                  proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for;
  1613                  proxy_pass $backend_server_url;
  1614                  proxy_ssl_trusted_certificate /etc/nginx/upstream.pem;
  1615              }
  1616  
  1617              location /nginx_status {
  1618                  stub_status;
  1619                  allow 127.0.0.1;
  1620                  deny all;
  1621              }
  1622          }
  1623  
  1624          # Verrazzano gRPC proxy
  1625          server {
  1626              listen       8776 http2;
  1627              server_name  verrazzano-grpc-proxy;
  1628  
  1629              location / {
  1630                  lua_ssl_verify_depth 2;
  1631                  lua_ssl_trusted_certificate /etc/nginx/upstream.pem;
  1632  
  1633                  set $oidc_user "";
  1634                  set $backend_server_url "";
  1635                  rewrite_by_lua_file /etc/nginx/conf.lua;
  1636                  proxy_set_header X-WEBAUTH-USER $oidc_user;
  1637                  grpc_pass $backend_server_url;
  1638                  proxy_ssl_trusted_certificate /etc/nginx/upstream.pem;
  1639              }
  1640          }
  1641      }
  1642    startup.sh: |
  1643      #!/bin/bash
  1644      startupDir=$(dirname $0)
  1645      cd $startupDir
  1646      cp $startupDir/nginx.conf /etc/nginx/nginx.conf
  1647      cp $startupDir/auth.lua /etc/nginx/auth.lua
  1648      cp $startupDir/conf.lua /etc/nginx/conf.lua
  1649      nameserver=$(grep -i nameserver /etc/resolv.conf | awk '{split($0,line," "); print line[2]}')
  1650      sed -i -e "s|_NAMESERVER_|${nameserver}|g" /etc/nginx/nginx.conf
  1651  
  1652      mkdir -p /etc/nginx/logs
  1653  
  1654      export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
  1655  
  1656      cat /etc/ssl/certs/ca-bundle.crt > /etc/nginx/upstream.pem
  1657  
  1658      /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf -p /etc/nginx -t
  1659      /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf -p /etc/nginx
  1660  
  1661      while [ $? -ne 0 ]; do
  1662          sleep 20
  1663          echo "retry nginx startup ..."
  1664          /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf -p /etc/nginx
  1665      done
  1666  
  1667      sh -c "$startupDir/reload.sh &"
  1668  
  1669      tail -f /var/log/nginx/error.log
  1670    reload.sh: |
  1671      #!/bin/bash
  1672  
  1673      adminCABundleMD5=""
  1674      defaultCABundleMD5=""
  1675      upstreamCACertFile="/etc/nginx/upstream.pem"
  1676      localClusterCACertFile="/api-config/default-ca-bundle"
  1677      adminClusterCACertFile="/api-config/admin-ca-bundle"
  1678      defaultCACertFile="/etc/ssl/certs/ca-bundle.crt"
  1679      tmpUpstreamCACertFile="/tmp/upstream.pem"
  1680      maxSizeTrustedCertsFileDefault=$(echo $((10*1024*1024)))
  1681      if [[ ! -z "${MAX_SIZE_TRUSTED_CERTS_FILE}" ]]; then
  1682          maxSizeTrustedCertsFileDefault=${MAX_SIZE_TRUSTED_CERTS_FILE}
  1683      fi
  1684  
  1685      function reload() {
  1686          nginx -t -p /etc/nginx
  1687          if [ $? -eq 0 ]
  1688          then
  1689              echo "Detected Nginx Configuration Change"
  1690              echo "Executing: nginx -s reload -p /etc/nginx"
  1691              nginx -s reload -p /etc/nginx
  1692          fi
  1693      }
  1694  
  1695      function reset_md5() {
  1696          adminCABundleMD5=""
  1697          defaultCABundleMD5=""
  1698      }
  1699  
  1700      function local_cert_config() {
  1701          if [[ -s $localClusterCACertFile ]]; then
  1702              md5Hash=$(md5sum "$localClusterCACertFile")
  1703              if [ "$defaultCABundleMD5" != "$md5Hash" ] ; then
  1704                  echo "Adding local CA cert to $upstreamCACertFile"
  1705                  cat $upstreamCACertFile > $tmpUpstreamCACertFile
  1706                  cat $localClusterCACertFile > $upstreamCACertFile
  1707                  cat $tmpUpstreamCACertFile >> $upstreamCACertFile
  1708                  rm -rf $tmpUpstreamCACertFile
  1709                  defaultCABundleMD5="$md5Hash"
  1710                  reload
  1711              fi
  1712          fi
  1713      }
  1714  
  1715      function admin_cluster_cert_config() {
  1716          if [[ -s $adminClusterCACertFile ]]; then
  1717              md5Hash=$(md5sum "$adminClusterCACertFile")
  1718              if [ "$adminCABundleMD5" != "$md5Hash" ] ; then
  1719                  echo "Adding admin cluster CA cert to $upstreamCACertFile"
  1720                  cat $upstreamCACertFile > $tmpUpstreamCACertFile
  1721                  cat $adminClusterCACertFile > $upstreamCACertFile
  1722                  cat $tmpUpstreamCACertFile >> $upstreamCACertFile
  1723                  rm -rf $tmpUpstreamCACertFile
  1724                  adminCABundleMD5="$md5Hash"
  1725                  reload
  1726              fi
  1727          else
  1728              if [ "$adminCABundleMD5" != "" ] ; then
  1729                  reset_md5
  1730                  local_cert_config
  1731              fi
  1732          fi
  1733      }
  1734  
  1735      function default_cert_config() {
  1736          cat $defaultCACertFile > $upstreamCACertFile
  1737      }
  1738  
  1739      while true
  1740      do
  1741          trustedCertsFileSize=$(wc -c < $upstreamCACertFile)
  1742          if [ $trustedCertsFileSize -ge $maxSizeTrustedCertsFileDefault ] ; then
  1743              echo "$upstreamCACertFile file size greater than  $maxSizeTrustedCertsFileDefault, resetting.."
  1744              reset_md5
  1745              default_cert_config
  1746          fi
  1747  
  1748          local_cert_config
  1749          admin_cluster_cert_config
  1750          sleep .1
  1751      done
  1752  {{ end }}
  1753