diff --git a/app/controllers/projects/serverless/functions_controller.rb b/app/controllers/projects/serverless/functions_controller.rb
index 4b0d001fca6a94fd52f3289cef50f1b1951b9e12..0b55414d3909afc66f1e37d4846ea794eb541c6e 100644
--- a/app/controllers/projects/serverless/functions_controller.rb
+++ b/app/controllers/projects/serverless/functions_controller.rb
@@ -8,11 +8,15 @@ class FunctionsController < Projects::ApplicationController
       def index
         respond_to do |format|
           format.json do
-            functions = finder.execute
+            functions = finder.execute.select do |function|
+              can?(@current_user, :read_cluster, function.cluster)
+            end
+
+            serialized_functions = serialize_function(functions)
 
             render json: {
               knative_installed: finder.knative_installed,
-              functions: serialize_function(functions)
+              functions: serialized_functions
             }.to_json
           end
 
@@ -23,11 +27,14 @@ def index
       end
 
       def show
-        @service = serialize_function(finder.service(params[:environment_id], params[:id]))
-        @prometheus = finder.has_prometheus?(params[:environment_id])
+        function = finder.service(params[:environment_id], params[:id])
+        return not_found unless function && can?(@current_user, :read_cluster, function.cluster)
 
+        @service = serialize_function(function)
         return not_found if @service.nil?
 
+        @prometheus = finder.has_prometheus?(params[:environment_id])
+
         respond_to do |format|
           format.json do
             render json: @service
diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb
index 4e0b69f47e593f1bb888b0d10be4d3de8c331167..3b4ecbb5387fed75656f81bcb3d1a516a5f05e45 100644
--- a/app/finders/projects/serverless/functions_finder.rb
+++ b/app/finders/projects/serverless/functions_finder.rb
@@ -93,24 +93,32 @@ def knative_service(environment_scope, name)
             .services
             .select { |svc| svc["metadata"]["name"] == name }
 
-          add_metadata(finder, services).first unless services.nil?
+          attributes = add_metadata(finder, services).first
+          next unless attributes
+
+          Gitlab::Serverless::Service.new(attributes)
         end
       end
 
       def knative_services
         services_finders.map do |finder|
-          services = finder.services
+          attributes = add_metadata(finder, finder.services)
 
-          add_metadata(finder, services) unless services.nil?
+          attributes&.map do |attributes|
+            Gitlab::Serverless::Service.new(attributes)
+          end
         end
       end
 
       def add_metadata(finder, services)
+        return if services.nil?
+
         add_pod_count = services.one?
 
         services.each do |s|
           s["environment_scope"] = finder.cluster.environment_scope
-          s["cluster_id"] = finder.cluster.id
+          s["environment"] = finder.environment
+          s["cluster"] = finder.cluster
 
           if add_pod_count
             s["podcount"] = finder
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 7855db960c90bd736e6e9edbb6da7c2e686feb3c..7e76d324bdc417e4544f9852710aabd0da785826 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -290,6 +290,12 @@ def clusterable
       end
     end
 
+    def serverless_domain
+      strong_memoize(:serverless_domain) do
+        self.application_knative&.serverless_domain_cluster
+      end
+    end
+
     private
 
     def unique_management_project_environment_scope
diff --git a/app/serializers/projects/serverless/service_entity.rb b/app/serializers/projects/serverless/service_entity.rb
index 10360e575bbc409039a2461584e773a9f9471b8c..05beb562e40907d8ee36d0e6e898b505cbd50c0e 100644
--- a/app/serializers/projects/serverless/service_entity.rb
+++ b/app/serializers/projects/serverless/service_entity.rb
@@ -5,91 +5,31 @@ module Serverless
     class ServiceEntity < Grape::Entity
       include RequestAwareEntity
 
-      expose :name do |service|
-        service.dig('metadata', 'name')
-      end
-
-      expose :namespace do |service|
-        service.dig('metadata', 'namespace')
-      end
-
-      expose :environment_scope do |service|
-        service.dig('environment_scope')
-      end
-
-      expose :cluster_id do |service|
-        service.dig('cluster_id')
-      end
+      expose :name
+      expose :namespace
+      expose :environment_scope
+      expose :podcount
+      expose :created_at
+      expose :image
+      expose :description
+      expose :url
 
       expose :detail_url do |service|
         project_serverless_path(
           request.project,
-          service.dig('environment_scope'),
-          service.dig('metadata', 'name'))
-      end
-
-      expose :podcount do |service|
-        service.dig('podcount')
+          service.environment_scope,
+          service.name)
       end
 
       expose :metrics_url do |service|
         project_serverless_metrics_path(
           request.project,
-          service.dig('environment_scope'),
-          service.dig('metadata', 'name')) + ".json"
-      end
-
-      expose :created_at do |service|
-        service.dig('metadata', 'creationTimestamp')
-      end
-
-      expose :url do |service|
-        knative_06_07_url(service) || knative_05_url(service)
-      end
-
-      expose :description do |service|
-        knative_07_description(service) || knative_05_06_description(service)
+          service.environment_scope,
+          service.name, format: :json)
       end
 
-      expose :image do |service|
-        service.dig(
-          'spec',
-          'runLatest',
-          'configuration',
-          'build',
-          'template',
-          'name')
-      end
-
-      private
-
-      def knative_07_description(service)
-        service.dig(
-          'spec',
-          'template',
-          'metadata',
-          'annotations',
-          'Description'
-        )
-      end
-
-      def knative_05_url(service)
-        "http://#{service.dig('status', 'domain')}"
-      end
-
-      def knative_06_07_url(service)
-        service.dig('status', 'url')
-      end
-
-      def knative_05_06_description(service)
-        service.dig(
-          'spec',
-          'runLatest',
-          'configuration',
-          'revisionTemplate',
-          'metadata',
-          'annotations',
-          'Description')
+      expose :cluster_id do |service|
+        service.cluster&.id
       end
     end
   end
diff --git a/lib/gitlab/serverless/service.rb b/lib/gitlab/serverless/service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..643e076c5875032ccc58d481295cee66ed7a2098
--- /dev/null
+++ b/lib/gitlab/serverless/service.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+class Gitlab::Serverless::Service
+  include Gitlab::Utils::StrongMemoize
+
+  def initialize(attributes)
+    @attributes = attributes
+  end
+
+  def name
+    @attributes.dig('metadata', 'name')
+  end
+
+  def namespace
+    @attributes.dig('metadata', 'namespace')
+  end
+
+  def environment_scope
+    @attributes.dig('environment_scope')
+  end
+
+  def environment
+    @attributes.dig('environment')
+  end
+
+  def podcount
+    @attributes.dig('podcount')
+  end
+
+  def created_at
+    strong_memoize(:created_at) do
+      timestamp = @attributes.dig('metadata', 'creationTimestamp')
+      DateTime.parse(timestamp) if timestamp
+    end
+  end
+
+  def image
+    @attributes.dig(
+      'spec',
+      'runLatest',
+      'configuration',
+      'build',
+      'template',
+      'name')
+  end
+
+  def description
+    knative_07_description || knative_05_06_description
+  end
+
+  def cluster
+    @attributes.dig('cluster')
+  end
+
+  def url
+    proxy_url || knative_06_07_url || knative_05_url
+  end
+
+  private
+
+  def proxy_url
+    if cluster&.serverless_domain
+      Gitlab::Serverless::FunctionURI.new(function: name, cluster: cluster.serverless_domain, environment: environment)
+    end
+  end
+
+  def knative_07_description
+    @attributes.dig(
+      'spec',
+      'template',
+      'metadata',
+      'annotations',
+      'Description'
+    )
+  end
+
+  def knative_05_06_description
+    @attributes.dig(
+      'spec',
+      'runLatest',
+      'configuration',
+      'revisionTemplate',
+      'metadata',
+      'annotations',
+      'Description')
+  end
+
+  def knative_05_url
+    domain = @attributes.dig('status', 'domain')
+    return unless domain
+
+    "http://#{domain}"
+  end
+
+  def knative_06_07_url
+    @attributes.dig('status', 'url')
+  end
+end
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index f0153ac37bff3f7dedff127103db1f9d01524dbc..db7533eb60952fc9b388da3c52912b19e014ae56 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -14,9 +14,11 @@
   let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
   let(:knative_services_finder) { environment.knative_services_finder }
   let(:function_description) { 'A serverless function' }
+  let(:function_name) { 'some-function-name' }
   let(:knative_stub_options) do
-    { namespace: namespace.namespace, name: cluster.project.name, description: function_description }
+    { namespace: namespace.namespace, name: function_name, description: function_description }
   end
+  let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
 
   let(:namespace) do
     create(:cluster_kubernetes_namespace,
@@ -87,25 +89,65 @@ def params(opts = {})
       end
 
       context 'when functions were found' do
-        let(:functions) { ["asdf"] }
+        let(:functions) { [{}, {}] }
 
         before do
-          stub_kubeclient_knative_services(namespace: namespace.namespace)
-          get :index, params: params({ format: :json })
+          stub_kubeclient_knative_services(namespace: namespace.namespace, cluster_id: cluster.id, name: function_name)
         end
 
         it 'returns functions' do
+          get :index, params: params({ format: :json })
           expect(json_response["functions"]).not_to be_empty
         end
 
-        it { expect(response).to have_gitlab_http_status(:ok) }
+        it 'filters out the functions whose cluster the user does not have permission to read' do
+          allow(controller).to receive(:can?).and_return(true)
+          expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false)
+
+          get :index, params: params({ format: :json })
+
+          expect(json_response["functions"]).to be_empty
+        end
+
+        it 'returns a successful response status' do
+          get :index, params: params({ format: :json })
+          expect(response).to have_gitlab_http_status(:ok)
+        end
+
+        context 'when there is serverless domain for a cluster' do
+          let!(:serverless_domain_cluster) do
+            create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
+          end
+
+          it 'returns JSON with function details with serverless domain URL' do
+            get :index, params: params({ format: :json })
+            expect(response).to have_gitlab_http_status(:ok)
+
+            expect(json_response["functions"]).not_to be_empty
+
+            expect(json_response["functions"]).to all(
+              include(
+                'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}"
+              )
+            )
+          end
+        end
+
+        context 'when there is no serverless domain for a cluster' do
+          it 'keeps function URL as it was' do
+            expect(Gitlab::Serverless::Domain).not_to receive(:new)
+
+            get :index, params: params({ format: :json })
+            expect(response).to have_gitlab_http_status(:ok)
+          end
+        end
       end
     end
   end
 
   describe 'GET #show' do
-    context 'invalid data' do
-      it 'has a bad function name' do
+    context 'with function that does not exist' do
+      it 'returns 404' do
         get :show, params: params({ format: :json, environment_id: "*", id: "foo" })
         expect(response).to have_gitlab_http_status(:not_found)
       end
@@ -113,15 +155,50 @@ def params(opts = {})
 
     context 'with valid data', :use_clean_rails_memory_store_caching do
       shared_examples 'GET #show with valid data' do
-        it 'has a valid function name' do
-          get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name })
+        context 'when there is serverless domain for a cluster' do
+          let!(:serverless_domain_cluster) do
+            create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
+          end
+
+          it 'returns JSON with function details with serverless domain URL' do
+            get :show, params: params({ format: :json, environment_id: "*", id: function_name })
+            expect(response).to have_gitlab_http_status(:ok)
+
+            expect(json_response).to include(
+              'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}"
+            )
+          end
+
+          it 'returns 404 when user does not have permission to read the cluster' do
+            allow(controller).to receive(:can?).and_return(true)
+            expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false)
+
+            get :show, params: params({ format: :json, environment_id: "*", id: function_name })
+
+            expect(response).to have_gitlab_http_status(:not_found)
+          end
+        end
+
+        context 'when there is no serverless domain for a cluster' do
+          it 'keeps function URL as it was' do
+            get :show, params: params({ format: :json, environment_id: "*", id: function_name })
+            expect(response).to have_gitlab_http_status(:ok)
+
+            expect(json_response).to include(
+              'url' => "http://#{function_name}.#{namespace.namespace}.example.com"
+            )
+          end
+        end
+
+        it 'return json with function details' do
+          get :show, params: params({ format: :json, environment_id: "*", id: function_name })
           expect(response).to have_gitlab_http_status(:ok)
 
           expect(json_response).to include(
-            'name' => project.name,
-            'url' => "http://#{project.name}.#{namespace.namespace}.example.com",
+            'name' => function_name,
+            'url' => "http://#{function_name}.#{namespace.namespace}.example.com",
             'description' => function_description,
-            'podcount' => 1
+            'podcount' => 0
           )
         end
       end
@@ -180,8 +257,8 @@ def params(opts = {})
                                          'knative_installed' => 'checking',
                                          'functions' => [
                                            a_hash_including(
-                                             'name' => project.name,
-                                             'url' => "http://#{project.name}.#{namespace.namespace}.example.com",
+                                             'name' => function_name,
+                                             'url' => "http://#{function_name}.#{namespace.namespace}.example.com",
                                              'description' => function_description
                                            )
                                          ]
diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb
index 67eda297b918cc1d2729620ac6564f23b6bc98be..4e9f3d371cefd09910e2400b4cd4f4a59d930fe8 100644
--- a/spec/finders/projects/serverless/functions_finder_spec.rb
+++ b/spec/finders/projects/serverless/functions_finder_spec.rb
@@ -153,8 +153,8 @@
           *knative_services_finder.cache_args)
 
         result = finder.service(cluster.environment_scope, cluster.project.name)
-        expect(result).not_to be_empty
-        expect(result["metadata"]["name"]).to be_eql(cluster.project.name)
+        expect(result).to be_present
+        expect(result.name).to be_eql(cluster.project.name)
       end
 
       it 'has metrics', :use_clean_rails_memory_store_caching do
diff --git a/spec/lib/gitlab/serverless/service_spec.rb b/spec/lib/gitlab/serverless/service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f618dd02cdb3f8275619e9dfa84f1c66e894273a
--- /dev/null
+++ b/spec/lib/gitlab/serverless/service_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Serverless::Service do
+  let(:cluster) { create(:cluster) }
+  let(:environment) { create(:environment) }
+  let(:attributes) do
+    {
+      'apiVersion' => 'serving.knative.dev/v1alpha1',
+      'kind' => 'Service',
+      'metadata' => {
+        'creationTimestamp' => '2019-10-22T21:19:13Z',
+        'name' => 'kubetest',
+        'namespace' => 'project1-1-environment1'
+      },
+      'spec' => {
+        'runLatest' => {
+          'configuration' => {
+            'build' => {
+              'template' => {
+                'name' => 'some-image'
+              }
+            }
+          }
+        }
+      },
+      'environment_scope' => '*',
+      'cluster' => cluster,
+      'environment' => environment,
+      'podcount' => 0
+    }
+  end
+
+  it 'exposes methods extracting data from the attributes hash' do
+    service = Gitlab::Serverless::Service.new(attributes)
+
+    expect(service.name).to eq('kubetest')
+    expect(service.namespace).to eq('project1-1-environment1')
+    expect(service.environment_scope).to eq('*')
+    expect(service.podcount).to eq(0)
+    expect(service.created_at).to eq(DateTime.parse('2019-10-22T21:19:13Z'))
+    expect(service.image).to eq('some-image')
+    expect(service.cluster).to eq(cluster)
+    expect(service.environment).to eq(environment)
+  end
+
+  it 'returns nil for missing attributes' do
+    service = Gitlab::Serverless::Service.new({})
+
+    [:name, :namespace, :environment_scope, :cluster, :podcount, :created_at, :image, :description, :url, :environment].each do |method|
+      expect(service.send(method)).to be_nil
+    end
+  end
+
+  describe '#description' do
+    it 'extracts the description in knative 7 format if available' do
+      attributes = {
+        'spec' => {
+          'template' => {
+            'metadata' => {
+              'annotations' => {
+                'Description' => 'some description'
+              }
+            }
+          }
+        }
+      }
+      service = Gitlab::Serverless::Service.new(attributes)
+
+      expect(service.description).to eq('some description')
+    end
+
+    it 'extracts the description in knative 5/6 format if 7 is not available' do
+      attributes = {
+        'spec' => {
+          'runLatest' => {
+            'configuration' => {
+              'revisionTemplate' => {
+                'metadata' => {
+                  'annotations' => {
+                    'Description' => 'some description'
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+      service = Gitlab::Serverless::Service.new(attributes)
+
+      expect(service.description).to eq('some description')
+    end
+  end
+
+  describe '#url' do
+    it 'returns proxy URL if cluster has serverless domain' do
+      # cluster = create(:cluster)
+      knative = create(:clusters_applications_knative, :installed, cluster: cluster)
+      create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
+      service = Gitlab::Serverless::Service.new(attributes.merge('cluster' => cluster))
+
+      expect(Gitlab::Serverless::FunctionURI).to receive(:new).with(
+        function: service.name,
+        cluster: service.cluster.serverless_domain,
+        environment: service.environment
+      ).and_return('https://proxy.example.com')
+
+      expect(service.url).to eq('https://proxy.example.com')
+    end
+
+    it 'returns the URL from the knative 6/7 format' do
+      attributes = {
+        'status' => {
+          'url' => 'https://example.com'
+        }
+      }
+      service = Gitlab::Serverless::Service.new(attributes)
+
+      expect(service.url).to eq('https://example.com')
+    end
+
+    it 'returns the URL from the knative 5 format' do
+      attributes = {
+        'status' => {
+          'domain' => 'example.com'
+        }
+      }
+      service = Gitlab::Serverless::Service.new(attributes)
+
+      expect(service.url).to eq('http://example.com')
+    end
+  end
+end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 0d312575e916edbaaadd376cd4e67d5c8d07794d..e2d96db02be49462cee6ddaedf972413c9298f38 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -557,7 +557,7 @@ def kube_deployment(name: "kube-deployment", environment_slug: "production", pro
   end
 
   # noinspection RubyStringKeysInHashInspection
-  def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
+  def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 9)
     { "apiVersion" => "serving.knative.dev/v1alpha1",
       "kind" => "Service",
       "metadata" =>
@@ -612,12 +612,12 @@ def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.
         "url" => "http://#{name}.#{namespace}.#{domain}"
       },
       "environment_scope" => environment,
-      "cluster_id" => 9,
+      "cluster_id" => cluster_id,
       "podcount" => 0 }
   end
 
   # noinspection RubyStringKeysInHashInspection
-  def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
+  def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 5)
     { "apiVersion" => "serving.knative.dev/v1alpha1",
       "kind" => "Service",
       "metadata" =>
@@ -664,12 +664,12 @@ def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.
           "traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }],
           "url" => "http://#{name}.#{namespace}.#{domain}" },
       "environment_scope" => environment,
-      "cluster_id" => 5,
+      "cluster_id" => cluster_id,
       "podcount" => 0 }
   end
 
   # noinspection RubyStringKeysInHashInspection
-  def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
+  def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 5)
     { "apiVersion" => "serving.knative.dev/v1alpha1",
       "kind" => "Service",
       "metadata" =>
@@ -716,12 +716,12 @@ def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.
           "traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }],
           "url" => "http://#{name}.#{namespace}.#{domain}" },
       "environment_scope" => environment,
-      "cluster_id" => 5,
+      "cluster_id" => cluster_id,
       "podcount" => 0 }
   end
 
   # noinspection RubyStringKeysInHashInspection
-  def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production')
+  def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 8)
     { "apiVersion" => "serving.knative.dev/v1alpha1",
       "kind" => "Service",
       "metadata" =>
@@ -771,7 +771,7 @@ def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.
           "observedGeneration" => 1,
           "traffic" => [{ "percent" => 100, "revisionName" => "#{name}-58qgr" }] },
       "environment_scope" => environment,
-      "cluster_id" => 8,
+      "cluster_id" => cluster_id,
       "podcount" => 0 }
   end